n8n实现Misskey向Mastodon、GoToSocial及Memos的多端同步
前言
25年下半年,我才第一次接触联邦宇宙的概念。
以下摘自 联邦宇宙 - 维基百科,自由的百科全书 :
联邦宇宙(英语:Fediverse,简称Fedi)在英文中是“联邦”(Federation)和“宇宙”(Universe)的混成词。
联邦宇宙由一系列自由软件组成,有一组互联的服务器(用户自建或第三方托管),一起提供网络发布(如社交媒体、微博、博客或者网站)或者文件托管功能。
虽然各个服务器是独立运行的,且各个实例繁多,内容多样, 但服务器之间可以彼此互通。
在不同的服务器(实例)上,用户可以创建不同帐号,因为服务器上运行的软件支持一种或多种遵循开放标准的通信协议,能够跨越实例边界而通信。
与在单一服务器上运行的传统社交网络相比,联邦宇宙的运行方式更开放,其服务器的分散性,使联邦宇宙更安全可靠。
从一开始接触,我就对此概念非常感兴趣,并尝试搭建自己的联邦宇宙服务器,12月底,从较为复杂的Mastodon(又称乳齿象、长毛象或万象)开始,到Misskey,再到最简单占用资源最小的GoToSocial,一个个都搭建体验了一遍,各有优缺点,还有于25年6月初就已搭建的memos 0.18.2服务,虽无联邦的功能,但同样属微博、说说类型,一时间竟无法确定最终使用哪个系统。
刚好,同一时期接触到了n8n工具,就决定以系统占用量不如Mastodon大、功能比GoToSocial更丰富的Misskey作为发布端,利用自动化工作流工具n8n,实现多端同步功能。
在这篇文章中,将分享如何通过 1Panel 部署 n8n,并记录一下同步工作流针对不同平台特性遇到的一些典型问题。
一、 n8n 的部署与关键配置
我的 n8n 是通过 1Panel 应用商店安装的。虽然应用商店的一键部署很方便,但为了让 n8n 的 Webhook 能够被外部正确识别,以及优化系统资源,建议安装前自定义 docker-compose.yml 文件。
1. 为什么需要自定义配置?
在默认安装下,n8n 的 Webhook 节点生成的 URL 往往是内网 IP 或 localhost,会导致 Misskey 无法回调成功。因此,我们需要通过环境变量 WEBHOOK_URL 明确告诉 n8n 的对外访问域名。
同时,为了减少控制台不必要的报错日志并保护隐私,关闭了诊断数据上传。
2. docker-compose 配置
目前正在使用的配置片段,关注环境变量:
networks:
1panel-network:
external: true
services:
n8n:
container_name: ${CONTAINER_NAME}
deploy:
resources:
limits:
cpus: ${CPUS}
memory: ${MEMORY_LIMIT}
environment:
# 必须设置为 false,配合反代使用
N8N_SECURE_COOKIE: false
# 指定外部访问的域名,否则 Webhook 无法被正确触发
WEBHOOK_URL: https://n8n.xiaoten.com/
# 关闭诊断数据,减少日志干扰
N8N_DIAGNOSTICS_ENABLED: false
image: n8nio/n8n:2.2.2
labels:
createdBy: Apps
networks:
- 1panel-network
ports:
- ${HOST_IP}:${PANEL_APP_PORT_HTTP}:5678
restart: always
volumes:
- ./data:/home/node/.n8n配置完成后,重启容器,会发现 Webhook 节点里生成的 URL 变成了自定义域名 https://n8n.xiaoten.com/...。
二、 工作流逻辑备忘
整个工作流的核心逻辑并不复杂:Webhook 接收 -> 过滤 -> 下载图片 -> 分发到各平台。
整个流程图如下:

1. 过滤逻辑:识别用户ID
Misskey 的 Webhook 会推送所有动态。我们需要 If 节点 进行过滤。 在 n8n 的 JSON 结构中,我是这样配置 conditions 的:
{
"parameters": {
"conditions": {
"combinator": "and",
"conditions": [
{
// 确保 userId 是我自己
"leftValue": "={{ $json.body.body.note.user.id }}",
"rightValue": "这里填你自己的Misskey_User_ID",
"operator": {
"type": "string",
"operation": "equals"
}
},
{
// 确保不是回复 (Reply)
"leftValue": "={{ $json.body.body.note.replyId }}",
"rightValue": "",
"operator": {
"type": "string",
"operation": "empty",
"singleValue": true
}
},
{
// 确保不是转帖 (Renote)
"leftValue": "={{ $json.body.body.note.renoteId }}",
"rightValue": "",
"operator": {
"type": "string",
"operation": "empty",
"singleValue": true
}
}
]
}
},
"type": "n8n-nodes-base.if",
"name": "是否是xiaoten发新帖"
}可视化界面上如下设置:

这样保证只有指定用户ID发布的帖子才会被同步出去。
2. 同步到 Mastodon
Mastodon 的 API 比较标准。如果是纯文本,直接发帖即可;如果是带图内容,流程如下:
下载图片:n8n 先将 Misskey 的图片下载到内存。
上传媒体:调用 Mastodon 的
/api/v2/media接口上传图片,获得media_id。合并 ID:如果是多图,需要将多个 ID 合并为一个数组。
发布状态:调用
/api/v1/statuses,将正文和media_ids一起发送。
这部分比较顺畅,难点在于合并 ID。如果发送多张图,n8n 会运行多次上传节点,这时则需要一个 Code 节点 来把这些零散的运行结果聚合起来:
// Merge Mastodon IDs 节点代码
const items = $input.all();
// 按照原始顺序提取 ID
const sortedIds = items
.map(item => ({
id: item.json.id,
index: item.pairedItem.item
}))
.sort((a, b) => a.index - b.index)
.map(data => data.id);
return { json: { media_ids: sortedIds } };3. 同步到 GoToSocial
一开始我将 GoToSocial (GTS) 实例搭建在一个非24小时运行的服务器上,如果不做处理,一旦 GTS 掉线,整个 n8n 流程就会报错停止。
虽然现在已经将此实例转移到一个持续在线的服务器上,但为了防止之间干扰,实现方式还是保留了。
我是参考了“探针”逻辑,检测GTS实例的在线状态,来进入对应的工作流,防止整个工作流报错停止。
Check GTS Status: 在正式发送前,先用一个 HTTP Request 节点去 ping 一下 GTS 的实例信息接口
/api/v1/instance。 关键配置:必须开启“错误时继续执行”。// 节点配置片段 { "url": "https://social.poto.top/api/v1/instance", "options": { "timeout": 3000 // 3秒超时,防止卡死 }, "onError": "continueRegularOutput" // 报错不要停! }Is GTS Online? (If 节点): 紧接着判断上一步的结果。
条件:判断返回的
uri字段是否不为空。True:说明 GTS 在线,执行后续的发图/发帖流程。
False:说明 GTS 挂了,直接走
NoOp(空操作) 分支,跳过 GTS 同步。
通过这个简单的逻辑判断,即便 GTS 不在线,也不影响其他同步流程。
4. 同步到 Memos
Memos (v0.18.2) 的 API 在处理多张图片时,如果并发上传,返回的 Resource ID 顺序往往是乱的。会导致在 Misskey 发了“图1、图2、图3”,同步到 Memos 变成了“图2、图1、图3”。
为了保证顺序,引入了 Loop Over Items (SplitInBatches) 循环节点:
切片循环:将图片数组拆分,强制 n8n 每次只处理一张图片。
顺序上传:在循环中调用 Memos 的上传接口。
重组 Markdown:
这是最关键的一步。循环结束后,我使用一个 Code 节点,将所有上传成功的 ID 重新提取出来,并按照原始顺序拼接成 Memos 支持的 Markdown 格式:
// Code 节点逻辑片段 const mediaMarkdown = items.map(item => { const filename = item.json.filename || 'image'; const id = item.json.id; // 生成 Memos 标准图片语法  return ``; }).join("\n");
通过这种排队上传的方式,解决了图片乱序的问题。
三、 总结
通过 n8n 将 Misskey 作为内容源分发,其主要逻辑非常清晰,但是针对不同平台的部分特征有不同的处理方法,但其处理方法均能够借助AI轻松实现,本次也是试验了一下该想法的可行性,正常情况也不会同时维护这么多平台。
因为以上需求,算是对n8n有了非常简单的入门,因为用到的是最基本的功能,甚至还没有触及其核心功能(AI Agent)。n8n可实现的功能远不止于此,还有待进一步学习。
© 转载需附带本文链接,依据 CC BY-NC-SA 4.0 发布。