(原) Memos免费开源备忘录

原创文章,请后转载,并注明出处。

Memos 是一个Golang开发的免费开源备忘录. 在Github上47.5K的Star.

我也建了一个自己的memos https://m.scwy.net. 或许用它代替手机上的记事本.

开放了注册, 网友或其它朋友也可以用.

添加自己的Token(左下角用户设置-我的帐号-访问令牌), 下载APP, 可以在手机上使用它.


  1. 添加了WebHook, 还是比较简单. 但给自己的备忘录加个WebHook的意义在哪里? 不是应该给所有用户加WebHook才有用吗?
  2. 发现有时会无法保存, 如果保存了一些非法UTF8字符, 还会导致内容不能显示.
  3. Memos与博客的区别在哪里? 为什么有了博客还要个这? Memos也能作为博客使用,它是动态的.而Hugo是静态的.若象我两者都有,可以把Memos作为零碎信息的记录, 而博客作为整理过的文字.

  1. 二次开发除了golang环境,就是npm了. 进入web目录, npm install, 然后 npm run dev.
    如果npm install 不成功, 使用npm 20.19.0版本是可以的(官方文档已旧).
    如果本机npm版本不对, 可以安装nvm进行npm版本管理:
    nvm install 20.19.0 –> nvm use 20.19.0

  2. 要编译为独立运行的程序, 需要npm run build, 将生成的dist放入 server/router/frontend 目录中, 再进行go build

  3. 添加系统WebHook, 让所有的操作都发送给hook. 这对我这种小站或许有一点点作用.

(1). 添加命令行功能
cmd/memos/main.go中添加一个Webhook参数的配置

			instanceProfile := &profile.Profile{
				Mode:        viper.GetString("mode"),
				Addr:        viper.GetString("addr"),
				Port:        viper.GetInt("port"),
				UNIXSock:    viper.GetString("unix-sock"),
				Data:        viper.GetString("data"),
				Driver:      viper.GetString("driver"),
				DSN:         viper.GetString("dsn"),
				InstanceURL: viper.GetString("instance-url"),
				Webhook:     viper.GetString("webhook"),
				Version:     version.GetCurrentVersion(viper.GetString("mode")),
			}

....

	rootCmd.PersistentFlags().String("mode", "dev", `mode of server, can be "prod" or "dev" or "demo"`)
	rootCmd.PersistentFlags().String("addr", "", "address of server")
	rootCmd.PersistentFlags().Int("port", 8081, "port of server")
	rootCmd.PersistentFlags().String("unix-sock", "", "path to the unix socket, overrides --addr and --port")
	rootCmd.PersistentFlags().String("data", "", "data directory")
	rootCmd.PersistentFlags().String("driver", "sqlite", "database driver")
	rootCmd.PersistentFlags().String("dsn", "", "database source name(aka. DSN)")
	rootCmd.PersistentFlags().String("instance-url", "", "the url of your memos instance")
	rootCmd.PersistentFlags().String("webhook", "", "the global webhook URL for all user activities")

....

	if err := viper.BindPFlag("webhook", rootCmd.PersistentFlags().Lookup("webhook")); err != nil {
		panic(err)
	}
 对应的配置文件 internal/profile/profile.go中
type Profile struct {
	// Mode can be "prod" or "dev" or "demo"
	Mode string
	// Addr is the binding address for server
	Addr string
	// Port is the binding port for server
	Port int
	// UNIXSock is the IPC binding path. Overrides Addr and Port
	UNIXSock string
	// Data is the data directory
	Data string
	// DSN points to where memos stores its own data
	DSN string
	// Driver is the database driver
	// sqlite, mysql
	Driver string
	// Version is the current version of server
	Version string
	// InstanceURL is the url of your memos instance.
	InstanceURL string
	// Webhook is the global webhook URL for all user activities.
	Webhook string
}

(2). 响应WebHook 在通常用户的WebHook响应处, 添加全局的WebHook. 文件 server/router/api/v1/memo_service.go, dispatchMemoRelatedWebhook函数中

	for _, hook := range webhooks {
		payload, err := convertMemoToWebhookPayload(memo)
		if err != nil {
			return errors.Wrap(err, "failed to convert memo to webhook payload")
		}
		payload.ActivityType = activityType
		payload.URL = hook.Url

		// Use asynchronous webhook dispatch
		webhook.PostAsync(payload)
	}

	// 添加这一部份
	if s.Profile.Webhook != "" {
		payload, err := convertMemoToWebhookPayload(memo)
		if err != nil {
			return errors.Wrap(err, "failed to convert memo to webhook payload for global webhook")
		}
		payload.ActivityType = activityType
		payload.URL = s.Profile.Webhook

		// Use asynchronous webhook dispatch
		webhook.PostAsync(payload)
	}

	return nil
  1. 继续添加全局WebHook事件.(虽然我在官网文档看到过,但是不是升级就取消了?)
    (1). 注册事件
    server/router/api/v1/user_service.go文件CreateUser函数最后return之前添加
	if s.Profile.Webhook != "" {
		payload, err := convertUserToWebhookPayload(user, user.ID)
		if err != nil {
			slog.Warn("Failed to convert user to webhook payload for global webhook", slog.Any("err", err))
		} else {
			payload.URL = s.Profile.Webhook
			payload.ActivityType = "USER_CREATED"

			// 使用异步方式发送WebHook
			webhook.PostAsync(payload)
		}
	}

...

// 添加辅助函数来转换用户数据为WebHook payload
func convertUserToWebhookPayload(user *store.User, creatorID int32) (*webhook.WebhookRequestPayload, error) {
	return &webhook.WebhookRequestPayload{
		Creator: fmt.Sprintf("%s%d", UserNamePrefix, creatorID),
		// 注意:WebhookRequestPayload结构体目前只支持Memo字段
		// 如果需要传递用户数据,可能需要扩展结构体或使用其他方式
		// 这里可以考虑将用户数据转换为JSON字符串放在Memo字段中,或修改WebhookRequestPayload结构
	}, nil
}

(2). 添加登陆WebHook
在 server/router/api/v1/auth_service.go 函数SignIn中添加, 也是在return之前

	// 发送到全局WebHook
	if s.Profile.Webhook != "" {
		payload, err := convertUserToWebhookPayload(existingUser, existingUser.ID)
		if err != nil {
			return nil, status.Errorf(codes.Internal, "failed to create webhook payload: %v", err)
		}
		payload.URL = s.Profile.Webhook
		payload.ActivityType = "USER_SIGNED_IN"

		// 使用异步方式发送WebHook
		webhook.PostAsync(payload)
	}

  1. 通过 gox -os=“linux” -arch=“arm” 编译为Termux可用版本。
    这样手机上就可以运行备忘录了。如果通过内网,通过Caddy,通过公网服务器。那任何人都可以通过域名进行访问。
    也等于可以用旧手机作为服务器运行一个人人可访问的备忘录。