跳过正文
  1. 文章/

使用n8n将Mastodon同步至Gotosocial

··2822 字·6 分钟· loading ·
目录

前言
#

本篇内容包含AI相关内容 本人是技术小白,所以这种需要编程水平的技术活就要委托一下Codex帮我写代码和使用介绍。

起因是想 Mastodon 和 Gotosocial 同步使用,之后就不用麻烦自己再用 Slurp 再导入一遍,于是想到能不能两边同步发送,看到博主 eallion 早就已经探索过这方面的内容,于是我也想着用n8n去做一个同步发文的workflow。

运行原理
#

因为 GoToSocial 兼容 Mastodon API,所以这件事本质上就是:

  1. Mastodon 用 Webhook 把事件推给 n8n
  2. n8n 收到后整理正文、媒体和回复信息
  3. 然后调用 GoToSocial API 发文或更新

我最后整理成了一份可直接导入的 n8n workflow JSON,主要支持这些功能:

  1. 新建嘟文同步到 GoToSocial
  2. 编辑嘟文时同步更新
  3. 图片和视频一起同步
  4. 回复保持回复关系,而不是发成独立帖

我的运行环境
#

  • Mastodon 4.6.0
  • GoToSocial 0.22.0
  • n8n 2.26.4

使用教程
#

创建 Credentials
#

首先需要创建 Gotosocial 的 Access Token,可以使用网页工具辅助,也可以参考 Gotosocial 官方文档。 随后在n8n中创建 Credentials,我使用的是 Bearer Auth account。

导入Workflow
#

先把 workflow JSON 导入 n8n。

这份 workflow 使用的是 Data Table 版本,也就是会把 Mastodon 和 GoToSocial 的嘟文 ID 映射保存到一张表里,这样后续编辑和回复才能正常对应到原帖。

先创建 Data Table

在 n8n 里先建一张 Data Table,名字设成:

mastodon_gts_sync_map

表里需要这些列:

mastodon_status_id
gts_status_id
last_event
mastodon_url
updated_at

这张表的作用很简单:记录每条 Mastodon 嘟文在 GoToSocial 里对应的是哪条嘟文。

修改配置节点
#

导入后,打开 workflow 里的 Config - Edit Me 节点,把配置改成你自己的:

const config = {
  mastodonAccountId: '',
  gtsBaseUrl: 'https://your-gotosocial-domain',
  gtsBearerToken: 'replace-with-your-gotosocial-bearer-token',
  mappingTableName: 'mastodon_gts_sync_map',
  missingUpdateBehavior: 'skip'
};

其中:

  • mastodonAccountId
    如果留空,就不过滤账号
    如果你只想同步某一个 Mastodon 账号,可以填这个账号的 ID

  • gtsBaseUrl
    你的 GoToSocial 实例地址

  • gtsBearerToken
    GoToSocial 的 Bearer Token

  • mappingTableName
    就是前面创建的 Data Table 名字

  • missingUpdateBehavior
    推荐保留 skip
    这样遇到没有映射的编辑事件时,会直接跳过,避免误发成新帖

配置 Mastodon Webhook
#

然后在 Mastodon 后台配置 Webhook,把地址指向 n8n 这个 workflow 的 Production URL

注意这里要用生产地址,不要用测试地址。

发布 workflow 之后,Mastodon 发文、编辑、回复时,就会自动把事件推送到 n8n。

正式启用
#

全部配置完之后:

  1. 点击右上角 Publish
  2. 确认 Mastodon 配置的是 workflow 的 Production URL
  3. 不需要手动点 Execute workflow

之后只要在 Mastodon 发文,n8n 就会自动运行。

代码部分
#

{
  "name": "Mastodon to GoToSocial - Data Table version",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "mastodon-to-gotosocial-datatable",
        "responseMode": "onReceived",
        "options": {
          "responseCode": 200,
          "responseData": "firstEntryJson"
        }
      },
      "id": "f0c11111-0001-4000-8000-000000000001",
      "name": "Mastodon Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        -1320,
        40
      ],
      "webhookId": "mastodon-to-gotosocial-datatable"
    },
    {
      "parameters": {
        "jsCode": "const item = $input.first().json;\n\nconst config = {\n  mastodonAccountId: '',\n  gtsBaseUrl: 'https://你的-gotosocial-域名',\n  gtsBearerToken: '填你的 GoToSocial bearer token',\n  mappingTableName: 'mastodon_gts_sync_map',\n  missingUpdateBehavior: 'skip'\n};\n\nreturn [{ json: { ...item, config } }];"
      },
      "id": "f0c11111-0002-4000-8000-000000000002",
      "name": "Config - Edit Me",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -1100,
        40
      ]
    },
    {
      "parameters": {
        "jsCode": "const input = $input.first().json;\nconst config = input.config;\nconst body = input.body ?? input;\nconst payload = typeof body === 'string' ? JSON.parse(body) : body;\nlet status = payload.object ?? payload.status ?? payload;\nif (typeof status === 'string') status = JSON.parse(status);\nconst eventName = String(payload.event ?? payload.type ?? '');\n\nconst htmlToText = (html) => String(html ?? '')\n  .replace(/<\\/?p[^>]*>/gi, '\\n')\n  .replace(/<br\\s*\\/?\\s*>/gi, '\\n')\n  .replace(/<[^>]+>/g, '')\n  .replace(/&nbsp;/g, ' ')\n  .replace(/&amp;/g, '&')\n  .replace(/&lt;/g, '<')\n  .replace(/&gt;/g, '>')\n  .replace(/&quot;/g, '\"')\n  .replace(/&#39;/g, \"'\")\n  .replace(/\\n{3,}/g, '\\n\\n')\n  .trim();\n\nconst accountId = String(status.account?.id ?? '');\nconst wantedAccountId = String(config.mastodonAccountId ?? '').trim();\n\nif (!status.id) return [{ json: { skipped: true, reason: 'Webhook payload did not contain a status id', config, payload } }];\nif (wantedAccountId && accountId !== wantedAccountId) return [{ json: { skipped: true, reason: `Account id mismatch: webhook account ${accountId}, configured ${wantedAccountId}`, config, statusId: status.id } }];\nif (status.reblog) return [{ json: { skipped: true, reason: 'Reblog/boost skipped', config, statusId: status.id } }];\n\nconst normalizedEvent = eventName.includes('update') || eventName.includes('edited') || status.edited_at ? 'updated' : 'created';\n\nreturn [{\n  json: {\n    config,\n    skipped: false,\n    mastodonStatusId: String(status.id),\n    parentMastodonStatusId: status.in_reply_to_id ? String(status.in_reply_to_id) : '',\n    isReply: Boolean(status.in_reply_to_id),\n    event: normalizedEvent,\n    mastodonUrl: status.url ?? status.uri ?? '',\n    sourceText: htmlToText(status.content ?? ''),\n    sourceSpoilerText: status.spoiler_text ?? '',\n    visibility: status.visibility ?? 'public',\n    sensitive: Boolean(status.sensitive),\n    language: status.language ?? 'zh',\n    mediaAttachments: Array.isArray(status.media_attachments) ? status.media_attachments : []\n  }\n}];"
      },
      "id": "f0c11111-0003-4000-8000-000000000003",
      "name": "Normalize Mastodon Event",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -880,
        40
      ]
    },
    {
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{ $json.skipped }}",
              "value2": false
            }
          ]
        }
      },
      "id": "f0c11111-0004-4000-8000-000000000004",
      "name": "Skip Filter",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        -660,
        40
      ]
    },
    {
      "parameters": {
        "operation": "get",
        "dataTableId": {
          "__rl": true,
          "value": "={{ $json.config.mappingTableName }}",
          "mode": "name"
        },
        "matchType": "allConditions",
        "filters": {
          "conditions": [
            {
              "keyName": "mastodon_status_id",
              "keyValue": "={{ $json.mastodonStatusId }}"
            }
          ]
        },
        "returnAll": true
      },
      "id": "f0c11111-0005-4000-8000-000000000005",
      "name": "Get Existing Mapping",
      "type": "n8n-nodes-base.dataTable",
      "typeVersion": 1,
      "position": [
        -420,
        40
      ],
      "alwaysOutputData": true
    },
    {
      "parameters": {
        "operation": "get",
        "dataTableId": {
          "__rl": true,
          "value": "={{ $node['Config - Edit Me'].json.config.mappingTableName }}",
          "mode": "name"
        },
        "matchType": "allConditions",
        "filters": {
          "conditions": [
            {
              "keyName": "mastodon_status_id",
              "keyValue": "={{ $node['Normalize Mastodon Event'].json.parentMastodonStatusId }}"
            }
          ]
        },
        "returnAll": true
      },
      "id": "f0c11111-0005-4000-8000-000000000005-parent",
      "name": "Get Parent Mapping",
      "type": "n8n-nodes-base.dataTable",
      "typeVersion": 1,
      "position": [
        -420,
        220
      ],
      "alwaysOutputData": true
    },
    {
      "parameters": {
        "jsCode": "const original = $node['Normalize Mastodon Event'].json;\nconst rows = $items('Get Existing Mapping');\nconst firstRow = rows[0]?.json ?? {};\nconst existingId = firstRow.gts_status_id ? String(firstRow.gts_status_id) : '';\nconst parentRows = original.isReply ? $items('Get Parent Mapping') : [];\nconst parentFirstRow = parentRows[0]?.json ?? {};\nconst parentGtsId = parentFirstRow.gts_status_id ? String(parentFirstRow.gts_status_id) : '';\nconst behavior = original.config.missingUpdateBehavior || 'skip';\n\nif (original.event === 'created' && existingId) {\n  return [];\n}\n\nif (original.event === 'updated' && !existingId) {\n  if (behavior === 'error') {\n    throw new Error(`收到 Mastodon 编辑事件 ${original.mastodonStatusId},但 Data Table 里还没有对应的 GoToSocial 嘟文 ID。`);\n  }\n  if (behavior === 'skip') {\n    return [];\n  }\n}\n\nif (original.isReply && !parentGtsId) {\n  return [];\n}\n\nconst shouldUpdate = Boolean(existingId);\nconst gtsBaseUrl = (original.config.gtsBaseUrl || '').replace(/\\/$/, '');\n\nreturn [{\n  json: {\n    ...original,\n    operation: shouldUpdate ? 'update' : 'create',\n    method: shouldUpdate ? 'PUT' : 'POST',\n    url: shouldUpdate\n      ? `${gtsBaseUrl}/api/v1/statuses/${existingId}`\n      : `${gtsBaseUrl}/api/v1/statuses`,\n    existingGtsStatusId: existingId,\n    parentGtsStatusId: parentGtsId\n  }\n}];"
      },
      "id": "f0c11111-0006-4000-8000-000000000006",
      "name": "Resolve Operation",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -180,
        40
      ]
    },
    {
      "parameters": {
        "conditions": {
          "number": [
            {
              "value1": "={{ ($json.mediaAttachments || []).length }}",
              "operation": "larger",
              "value2": 0
            }
          ]
        }
      },
      "id": "f0c11111-0007-4000-8000-000000000007",
      "name": "Has Media?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        40,
        40
      ]
    },
    {
      "parameters": {
        "jsCode": "const original = $input.first().json;\nreturn original.mediaAttachments\n  .map((media, index) => ({ media, index }))\n  .filter(({ media }) => media.url || media.remote_url || media.preview_url)\n  .map(({ media, index }) => ({\n    json: {\n      ...original,\n      media: {\n        index,\n        id: media.id ?? '',\n        type: media.type ?? '',\n        url: media.url ?? media.remote_url ?? media.preview_url,\n        description: media.description ?? ''\n      }\n    }\n  }));"
      },
      "id": "f0c11111-0008-4000-8000-000000000008",
      "name": "Prepare Media Items",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        280,
        -120
      ]
    },
    {
      "parameters": {
        "url": "={{ $json.media.url }}",
        "options": {
          "response": {
            "response": {
              "responseFormat": "file",
              "outputPropertyName": "data"
            }
          }
        }
      },
      "id": "f0c11111-0009-4000-8000-000000000009",
      "name": "Download Mastodon Media",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        500,
        -120
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ ($json.config.gtsBaseUrl || '').replace(/\\/$/, '') + '/api/v2/media' }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "={{ 'Bearer ' + $json.config.gtsBearerToken }}"
            }
          ]
        },
        "sendBody": true,
        "contentType": "multipart-form-data",
        "bodyParameters": {
          "parameters": [
            {
              "parameterType": "formBinaryData",
              "name": "file",
              "inputDataFieldName": "data"
            },
            {
              "name": "description",
              "value": "={{ $json.media.description || '' }}"
            }
          ]
        },
        "options": {}
      },
      "id": "f0c11111-0010-4000-8000-000000000010",
      "name": "Upload Media to GoToSocial",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        720,
        -120
      ],
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 3000
    },
    {
      "parameters": {
        "jsCode": "const uploads = $input.all();\nif (uploads.length === 0) return [];\nconst originals = $items('Prepare Media Items');\nconst original = originals[0].json;\nconst mediaIds = uploads.map(item => String(item.json.id)).filter(Boolean);\nconst body = {\n  status: original.sourceText,\n  spoiler_text: original.sourceSpoilerText ?? '',\n  sensitive: Boolean(original.sensitive),\n  visibility: original.visibility || 'public',\n  language: original.language || 'zh',\n  media_ids: mediaIds\n};\nif (original.parentGtsStatusId) {\n  body.in_reply_to_id = original.parentGtsStatusId;\n}\n\nreturn [{\n  json: {\n    config: original.config,\n    mastodonStatusId: original.mastodonStatusId,\n    mastodonUrl: original.mastodonUrl,\n    operation: original.operation,\n    method: original.method,\n    url: original.url,\n    body\n  }\n}];"
      },
      "id": "f0c11111-0011-4000-8000-000000000011",
      "name": "Prepare Status With Media",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        940,
        -120
      ]
    },
    {
      "parameters": {
        "method": "={{ $json.method }}",
        "url": "={{ $json.url }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "={{ 'Bearer ' + $json.config.gtsBearerToken }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify($json.body) }}",
        "options": {}
      },
      "id": "f0c11111-0012-4000-8000-000000000012",
      "name": "Create or Update GoToSocial Status - Media",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1160,
        -120
      ],
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 3000
    },
    {
      "parameters": {
        "operation": "upsert",
        "dataTableId": {
          "__rl": true,
          "value": "={{ $node['Config - Edit Me'].json.config.mappingTableName }}",
          "mode": "name"
        },
        "matchType": "allConditions",
        "filters": {
          "conditions": [
            {
              "keyName": "mastodon_status_id",
              "keyValue": "={{ $node['Prepare Status With Media'].json.mastodonStatusId }}"
            }
          ]
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "mastodon_status_id": "={{ $node['Prepare Status With Media'].json.mastodonStatusId }}",
            "gts_status_id": "={{ $json.id }}",
            "last_event": "={{ $node['Prepare Status With Media'].json.operation }}",
            "mastodon_url": "={{ $node['Prepare Status With Media'].json.mastodonUrl || '' }}",
            "updated_at": "={{ $now.toISO() }}"
          },
          "matchingColumns": [],
          "schema": [
            {
              "id": "mastodon_status_id",
              "displayName": "mastodon_status_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "readOnly": false,
              "removed": false
            },
            {
              "id": "gts_status_id",
              "displayName": "gts_status_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "readOnly": false,
              "removed": false
            },
            {
              "id": "last_event",
              "displayName": "last_event",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "readOnly": false,
              "removed": false
            },
            {
              "id": "mastodon_url",
              "displayName": "mastodon_url",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "readOnly": false,
              "removed": false
            },
            {
              "id": "updated_at",
              "displayName": "updated_at",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "readOnly": false,
              "removed": false
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {}
      },
      "id": "f0c11111-0013-4000-8000-000000000013",
      "name": "Upsert Mapping - Media",
      "type": "n8n-nodes-base.dataTable",
      "typeVersion": 1,
      "position": [
        1380,
        -120
      ]
    },
    {
      "parameters": {
        "jsCode": "const original = $input.first().json;\nconst body = {\n  status: original.sourceText ?? '',\n  spoiler_text: original.sourceSpoilerText ?? '',\n  sensitive: Boolean(original.sensitive),\n  visibility: original.visibility || 'public',\n  language: original.language || 'zh'\n};\nif (original.parentGtsStatusId) {\n  body.in_reply_to_id = original.parentGtsStatusId;\n}\nreturn [{\n  json: {\n    config: original.config,\n    mastodonStatusId: original.mastodonStatusId,\n    mastodonUrl: original.mastodonUrl,\n    operation: original.operation,\n    method: original.method,\n    url: original.url,\n    body\n  }\n}];"
      },
      "id": "f0c11111-0014-4000-8000-000000000014",
      "name": "Prepare Status Without Media",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        280,
        160
      ]
    },
    {
      "parameters": {
        "method": "={{ $json.method }}",
        "url": "={{ $json.url }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "={{ 'Bearer ' + $json.config.gtsBearerToken }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify($json.body) }}",
        "options": {}
      },
      "id": "f0c11111-0015-4000-8000-000000000015",
      "name": "Create or Update GoToSocial Status - No Media",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        500,
        160
      ],
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 3000
    },
    {
      "parameters": {
        "operation": "upsert",
        "dataTableId": {
          "__rl": true,
          "value": "={{ $node['Config - Edit Me'].json.config.mappingTableName }}",
          "mode": "name"
        },
        "matchType": "allConditions",
        "filters": {
          "conditions": [
            {
              "keyName": "mastodon_status_id",
              "keyValue": "={{ $node['Prepare Status Without Media'].json.mastodonStatusId }}"
            }
          ]
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "mastodon_status_id": "={{ $node['Prepare Status Without Media'].json.mastodonStatusId }}",
            "gts_status_id": "={{ $json.id }}",
            "last_event": "={{ $node['Prepare Status Without Media'].json.operation }}",
            "mastodon_url": "={{ $node['Prepare Status Without Media'].json.mastodonUrl || '' }}",
            "updated_at": "={{ $now.toISO() }}"
          },
          "matchingColumns": [],
          "schema": [
            {
              "id": "mastodon_status_id",
              "displayName": "mastodon_status_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "readOnly": false,
              "removed": false
            },
            {
              "id": "gts_status_id",
              "displayName": "gts_status_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "readOnly": false,
              "removed": false
            },
            {
              "id": "last_event",
              "displayName": "last_event",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "readOnly": false,
              "removed": false
            },
            {
              "id": "mastodon_url",
              "displayName": "mastodon_url",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "readOnly": false,
              "removed": false
            },
            {
              "id": "updated_at",
              "displayName": "updated_at",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "readOnly": false,
              "removed": false
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {}
      },
      "id": "f0c11111-0016-4000-8000-000000000016",
      "name": "Upsert Mapping - No Media",
      "type": "n8n-nodes-base.dataTable",
      "typeVersion": 1,
      "position": [
        720,
        160
      ]
    }
  ],
  "connections": {
    "Mastodon Webhook": {
      "main": [
        [
          {
            "node": "Config - Edit Me",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Config - Edit Me": {
      "main": [
        [
          {
            "node": "Normalize Mastodon Event",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Mastodon Event": {
      "main": [
        [
          {
            "node": "Skip Filter",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Skip Filter": {
      "main": [
        [
          {
            "node": "Get Existing Mapping",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    },
    "Get Existing Mapping": {
      "main": [
        [
          {
            "node": "Get Parent Mapping",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Parent Mapping": {
      "main": [
        [
          {
            "node": "Resolve Operation",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Resolve Operation": {
      "main": [
        [
          {
            "node": "Has Media?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has Media?": {
      "main": [
        [
          {
            "node": "Prepare Media Items",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Prepare Status Without Media",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Media Items": {
      "main": [
        [
          {
            "node": "Download Mastodon Media",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Download Mastodon Media": {
      "main": [
        [
          {
            "node": "Upload Media to GoToSocial",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Upload Media to GoToSocial": {
      "main": [
        [
          {
            "node": "Prepare Status With Media",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Status With Media": {
      "main": [
        [
          {
            "node": "Create or Update GoToSocial Status - Media",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create or Update GoToSocial Status - Media": {
      "main": [
        [
          {
            "node": "Upsert Mapping - Media",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Status Without Media": {
      "main": [
        [
          {
            "node": "Create or Update GoToSocial Status - No Media",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create or Update GoToSocial Status - No Media": {
      "main": [
        [
          {
            "node": "Upsert Mapping - No Media",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "pinData": {},
  "settings": {
    "executionOrder": "v1",
    "saveDataErrorExecution": "all",
    "saveDataSuccessExecution": "all",
    "saveManualExecutions": true
  },
  "tags": [],
  "versionId": "f0c11111-9999-4000-8000-000000000999"
}