惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
人人都是产品经理
人人都是产品经理
Cisco Talos Blog
Cisco Talos Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
V
V2EX
博客园 - 三生石上(FineUI控件)
Martin Fowler
Martin Fowler
WordPress大学
WordPress大学
D
Docker
S
SegmentFault 最新的问题
博客园 - 聂微东
美团技术团队
Apple Machine Learning Research
Apple Machine Learning Research
月光博客
月光博客
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Last Week in AI
Last Week in AI
M
MIT News - Artificial intelligence
F
Fortinet All Blogs
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
The GitHub Blog
The GitHub Blog
GbyAI
GbyAI
L
LangChain Blog
Vercel News
Vercel News
博客园 - 叶小钗
MongoDB | Blog
MongoDB | Blog
Stack Overflow Blog
Stack Overflow Blog
H
Help Net Security
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
The Cloudflare Blog
Engineering at Meta
Engineering at Meta
T
Threat Research - Cisco Blogs
T
Threatpost
Scott Helme
Scott Helme
T
Tailwind CSS Blog
Latest news
Latest news
Stack Overflow Blog
Stack Overflow Blog
Blog — PlanetScale
Blog — PlanetScale
The Register - Security
The Register - Security
罗磊的独立博客
P
Proofpoint News Feed
腾讯CDC
S
Schneier on Security
雷峰网
雷峰网
A
About on SuperTechFans
T
Tenable Blog
F
Full Disclosure
Cyberwarzone
Cyberwarzone
博客园_首页
有赞技术团队
有赞技术团队
K
Kaspersky official blog

eallion's Blog

春假清明自驾游 Ubuntu 25.10 安装和配置 秋假 彩礼 2025 博客变化 预制菜 联邦礼仪之一 重拾写博客的乐趣 少儿 TED - 时间管理大师 n8n 之备份 Mastodon 嘟文 如何备份 Mastodon Docker 部署 Mastodon NAS 折腾记
n8n 之同步博客到 Mastodon
Charles Chin · 2025-06-14 · via eallion's Blog

有很多方式来同步博客内容到 Mastodon,比如我曾经就是使用的 GitHub Actions 在构建时就同步了。但我们不是已经部署了一个 n8n 吗?就把同步流程搬到 n8n 上了。

n8n 是一个强大的自动化工具,可以用来备份 Mastodon 嘟文——《 n8n 之备份 Mastodon 嘟文》。今天我们来看看如何使用 n8n 将博客内容同步到 Mastodon。

中间会引入 Sink 短链接服务,Sink 也是判断博客文章是否是新发布的依据。用 Sink 来生成 Mastodon 嘟文的短链接,创建博客文章到 Mastodon 嘟文的联系,把一篇博客文章和一条嘟文对应起来,建立双向链接。见文末评论区的 Discuss on Mastodon。Hugo 的 Comments.html 模板中的代码:

<a href="https://s.e5n.cc/{{ .Slug }}">Mastodon</a>

比如:
文章的 URL 是:https://www.eallion.com/n8n-sync-blog-to-mastodon
文章的 Slug 是:n8n-sync-blog-to-mastodon
Sink 短链接就是:Sink 域名 + Slug

const path = originalLink.split('/').slice(3).join('/'); // 移除协议和域名部分
const slug = path.replace(/\/$/, ''); // 去除末尾的斜杠
{
  "name": "SinkBlogtoMastodon",
  "nodes": [
    {
      "parameters": {
        "owner": {
          "__rl": true,
          "value": "gh_username",
          "mode": "name"
        },
        "repository": {
          "__rl": true,
          "value": "gh_blog_repo",
          "mode": "name"
        },
        "events": [
          "push"
        ],
        "options": {}
      },
      "type": "n8n-nodes-base.githubTrigger",
      "typeVersion": 1,
      "position": [
        -240,
        -420
      ],
      "id": "6ea99614-9c0f-49a7-88db-f552dbac7952",
      "name": "Github Trigger",
      "webhookId": "b33dca07-0939-4107-a315-231bfab13df3",
      "credentials": {
        "githubApi": {
          "id": "8qzxqmikk9WEzTtg",
          "name": "GitHub account"
        }
      }
    },
    {
      "parameters": {
        "url": "https://my.blog/rss",
        "options": {}
      },
      "type": "n8n-nodes-base.rssFeedRead",
      "typeVersion": 1.1,
      "position": [
        380,
        -120
      ],
      "id": "b48c23ba-ef9a-4c2a-8d0d-e73c0de35ec1",
      "name": "BlogRSS",
      "alwaysOutputData": true,
      "executeOnce": true,
      "retryOnFail": true,
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "url": "={{ $json.newLink }}",
        "options": {
          "response": {
            "response": {
              "fullResponse": true,
              "neverError": true
            }
          }
        }
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        900,
        -120
      ],
      "id": "9fe05f54-5107-40e4-b979-2e22e029d4cc",
      "name": "FetchNewURL",
      "alwaysOutputData": true,
      "executeOnce": true,
      "retryOnFail": true,
      "notesInFlow": true,
      "onError": "continueErrorOutput",
      "notes": "Fetch New URL"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "84ba6f92-1023-4902-997c-c6e3efd6ea38",
              "leftValue": "={{ $json.statusCode }}",
              "rightValue": "404",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "looseTypeValidation": true,
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1160,
        -120
      ],
      "id": "fdf2464c-12c2-4ea8-85f5-f38ad9d0ff2a",
      "name": "CheckSlugExists",
      "executeOnce": true,
      "retryOnFail": true,
      "notesInFlow": true,
      "onError": "continueErrorOutput",
      "notes": "Check if 404"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://sink.to/api/link/create",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "authorization",
              "value": "Bearer ehJy..."
            },
            {
              "name": "content-type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "url",
              "value": "={{ $json.body.uri }}"
            },
            {
              "name": "slug",
              "value": "={{ $('ConvertBlogPost').item.json.slug }}"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1680,
        -120
      ],
      "id": "0b8ed4aa-ddba-4d1e-af96-24c39acb0b30",
      "name": "SinkAPICreate",
      "executeOnce": true,
      "retryOnFail": true,
      "notesInFlow": true,
      "notes": "Shorter the link with Sink"
    },
    {
      "parameters": {
        "jsCode": "// 获取 RSS 节点的第一条数据的 link\nconst originalTitle = $input.all()[0].json.title;\nconst originalLink = $input.all()[0].json.link;\nconst originalContent = $input.all()[0].json.content;\n\n// 替换域名为 s.e5n.cc\nconst newLink = originalLink.replace('www.eallion.com', 's.e5n.cc');\n\n// 提取 slug(例如 nas-building)\n// 使用字符串方法提取路径部分\nconst path = originalLink.split('/').slice(3).join('/'); // 移除协议和域名部分\nconst slug = path.replace(/\\/$/, ''); // 去除末尾的斜杠\n\n// 返回 newLink、originalLink 和 slug\nreturn [\n  {\n    json: {\n      originalTitle,\n      newLink,\n      originalLink,\n      slug,\n      originalContent,\n    },\n  },\n];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        640,
        -120
      ],
      "id": "2750e5ce-1a04-4cfd-bc53-27cd0aff166a",
      "name": "ConvertBlogPost",
      "executeOnce": true,
      "retryOnFail": true,
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://mastodon.instance/api/v1/statuses",
        "authentication": "genericCredentialType",
        "genericAuthType": "oAuth2Api",
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "status",
              "value": "=博客发布新文章:《{{ $('ConvertBlogPost').item.json.originalTitle }}》\n{{ $('ConvertBlogPost').item.json.originalLink }}\n{{ $('ConvertBlogPost').item.json.originalContent }}"
            },
            {
              "name": "visibility",
              "value": "public"
            }
          ]
        },
        "options": {
          "response": {
            "response": {
              "fullResponse": true,
              "neverError": true
            }
          }
        }
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1420,
        -120
      ],
      "id": "6f734551-c959-41e2-b05e-8691e76a265d",
      "name": "POSTtoMastodon",
      "notesInFlow": true,
      "executeOnce": true,
      "retryOnFail": true,
      "alwaysOutputData": true,
      "credentials": {
        "oAuth2Api": {
          "id": "GqNI5NcHBoCsIywV",
          "name": "Mastodon e5n"
        }
      },
      "onError": "continueErrorOutput",
      "notes": "POST to Mastodon statuses"
    },
    {
      "parameters": {},
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        -200,
        160
      ],
      "id": "f38321ef-5d2b-46b6-ad80-bb9ddda155eb",
      "name": "手动(测试用)",
      "disabled": true
    },
    {
      "parameters": {
        "unit": "minutes"
      },
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        40,
        -420
      ],
      "id": "4326ba6f-794e-420f-a961-998973c16989",
      "name": "Wait",
      "webhookId": "ad0849a8-63c0-4b84-97d8-86bfe59e3dab",
      "alwaysOutputData": true,
      "executeOnce": true,
      "retryOnFail": true,
      "notesInFlow": false
    }
  ],
  "pinData": {},
  "connections": {
    "Github Trigger": {
      "main": [
        [
          {
            "node": "Wait",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "BlogRSS": {
      "main": [
        [
          {
            "node": "ConvertBlogPost",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "FetchNewURL": {
      "main": [
        [
          {
            "node": "CheckSlugExists",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "CheckSlugExists": {
      "main": [
        [
          {
            "node": "POSTtoMastodon",
            "type": "main",
            "index": 0
          }
        ],
        [],
        []
      ]
    },
    "ConvertBlogPost": {
      "main": [
        [
          {
            "node": "FetchNewURL",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "POSTtoMastodon": {
      "main": [
        [
          {
            "node": "SinkAPICreate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "手动(测试用)": {
      "main": [
        [
          {
            "node": "BlogRSS",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait": {
      "main": [
        [
          {
            "node": "BlogRSS",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": true,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "f1aa189b-645b-44e1-906a-fdee06face62",
  "meta": {
    "templateCredsSetupCompleted": true,
    "instanceId": "f9ad6c4f03ea7c2c4f16a8dda8e47b847dcd27971aa32eadc1b11bbddddddddd"
  },
  "id": "KzVqqCxAGWaVZAmG",
  "tags": []
}