Memos同步至Mastodon(长毛象)

Memos同步至Mastodon(长毛象)

<span style="white-space: pre-wrap;">Photo by </span><a href="https://unsplash.com/@ch3thanhs?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit"><span style="white-space: pre-wrap;">Chethan</span></a><span style="white-space: pre-wrap;"> / </span><a href="https://unsplash.com/?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit"><span style="white-space: pre-wrap;">Unsplash</span></a>
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个,应该不止。

加入评论