IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    Memos同步至Mastodon(长毛象)

    @1900\'Blog发表于 2025-04-12 10:26:22
    love 0

    Photo by Chethan / Unsplash

    我一直把Memos当作微博来用,偶尔也会手动同步到长毛象,但也只是偶尔。原因是即便在用梯子的情况下长毛象打开也太慢了,毕竟大多数实例都在墙外,且长毛象挺吃服务器资源的。

    目前又不太想自建实例,虽然现在弄了NAS,理论上可以搭在上面,但是Ghost似乎在6月份要推出联邦宇宙服务了,到时候可以直接迁移到那个上面去,也就一直没下手。

    之前看到过蜗牛哥出过一期长毛象同步到Memos的方案,和我的需求是相反的,因为今天周末恰好有空,所以就捣鼓了一下,弄了一版。

    因为Memos可以设置WebHook调用,所以我的思路是:

    Memos发布 -> 触发WebHook调用 -> 数据转发给Cloudflare Worker -> Worker里用Mastodon API发布嘟文

    实现

    先去长毛象上申请Access token ,路径为 :

    偏好设置 -> 开发 -> 创建新应用 -> 起个名字,勾选 write:statuses、write:media 权限

    然后就是Cloudflare Worker的实现了,我用DeepseekR1跑了一版,稍加改动就能初步使用了。

    💡
    如果你的Memos服务器在国内,那可能需要给Cloudflare worker绑定一个域名,不然会无法访问。
    // cloudflare-worker.js
    const MASTODON_INSTANCE = ""; // 实例地址
    const ACCESS_TOKEN = ""; // 访问token
    
    async function uploadMediaFromUrl(imageUrl, mimeType) {
      try {
        // 从 URL 获取图片数据
        const imageResponse = await fetch(imageUrl);
        if (!imageResponse.ok) throw new Error(`下载图片失败: ${imageResponse.status}`);
        
        // 转换为可上传的格式
        const blob = await imageResponse.blob();
        const formData = new FormData();
        formData.append("file", blob); // 文件名按需处理
        
        // 上传到 Mastodon
        const uploadRes = await fetch(`${MASTODON_INSTANCE}/api/v2/media`, {
          method: "POST",
          headers: { Authorization: `Bearer ${ACCESS_TOKEN}` },
          body: formData
        });
        
        if (!uploadRes.ok) throw new Error(`上传失败: ${await uploadRes.text()}`);
        return uploadRes.json();
      } catch (error) {
        console.error("媒体上传错误:", error);
        throw new Error(`图片处理失败: ${error.message}`);
      }
    }
    
    async function handlePost(request) {
      try {
        const { memo } = await request.json();
        const { content, visibility, resourceList = [] } = memo;
        
        
    
        // 验证内容
        if (!content) return new Response(JSON.stringify({ error: "内容不能为空" }), { status: 400 });
    
        // 处理最多 4 张图片
        const validResources = resourceList
          .filter(res => res.type?.startsWith("image/"))
          .slice(0, 4);
    
        // 并行上传所有图片
        const mediaUploads = validResources.map(async res => {
          const media = await uploadMediaFromUrl(res.externalLink, res.type);
          return media.id;
        });
    
        const mediaIds = await Promise.all(mediaUploads);
    
        // 构建嘟文参数
        const params = new URLSearchParams({
          status: content,
          visibility: visibility.toLowerCase() || "public" // 默认公开
        });
        mediaIds.forEach(id => params.append("media_ids[]", id));
    
        // 发布嘟文
        const postRes = await fetch(`${MASTODON_INSTANCE}/api/v1/statuses`, {
          method: "POST",
          headers: {
            Authorization: `Bearer ${ACCESS_TOKEN}`,
            "Content-Type": "application/x-www-form-urlencoded"
          },
          body: params
        });
    
        if (!postRes.ok) throw new Error(await postRes.text());
        return new Response(JSON.stringify(await postRes.json()), { status: 200 });
        
      } catch (error) {
        console.error("处理错误:", error);
        return new Response(JSON.stringify({ error: error.message }), { status: 500 });
      }
    }
    
    // Worker 入口
    export default { fetch: handlePost };

    CloudFlare Worker代码

    给Memos启用Webhook

    设置 -> 偏好设置 -> Webhook -> 创建 -> 起个名字,填入Cloudflare Worker的地址

    一些不足

    这种方式同步速度应该会稍快,但是还是有一些限制,比如

    1. 图片太大超过Worker可运行时间会上传失败。
    2. Mastodon API好像最大只能传4张图片。
    3. 向Memos的TG机器人发的消息不会触发Webhook。
    4. 长毛象好像不支持 Markdown。
    5. 我使用的Memos后端版本为 v0.18.1
    6. 等等,目前只发现上面2个,应该不止。


沪ICP备19023445号-2号
友情链接