8
n8n 中文网amn8n.com

通过Telegram的DOI URL自动导入研究论文到Zotero

高级

这是一个AI Summarization, Multimodal AI领域的自动化工作流,包含 25 个节点。主要使用 If, Set, Code, Merge, Telegram 等节点。 从Telegram导入研究论文到Zotero,含AI摘要总结

前置要求
  • Telegram Bot Token
  • 可能需要目标 API 的认证凭证
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
  "id": "njoCDW50H4hQEGIY",
  "meta": {
    "instanceId": "0d045f8fe3802ff2be0bb9a9ea445ee6c9ed61973377effe00767e483681e2f4",
    "templateCredsSetupCompleted": true
  },
  "name": "通过 Telegram 的 DOI URL 自动导入研究论文到 Zotero",
  "tags": [],
  "nodes": [
    {
      "id": "474334ec-ff15-4a12-a200-50eb928f11db",
      "name": "Telegram 触发器",
      "type": "n8n-nodes-base.telegramTrigger",
      "position": [
        9.007782644689968,
        0
      ],
      "webhookId": "d5ec43d4-76ab-4e79-9822-f42d522b605b",
      "parameters": {
        "updates": [
          "message"
        ],
        "additionalFields": {}
      },
      "typeVersion": 1.2
    },
    {
      "id": "80605785-8fd8-4d86-893d-e33d9cff315e",
      "name": "代码",
      "type": "n8n-nodes-base.code",
      "position": [
        220,
        0
      ],
      "parameters": {
        "jsCode": "// Node: Code (Parse DOI)\nconst txt = $json.message?.text?.trim() || \"\";\n\nfunction extractDOI(s) {\n  // 1) doi.org/...\n  let m = s.match(/doi\\.org\\/(10\\.\\d{4,9}\\/\\S+)/i);\n  if (m) return m[1].replace(/[)\\].,;]*$/, \"\");\n  // 2) raw DOI\n  m = s.match(/(10\\.\\d{4,9}\\/\\S+)/i);\n  if (m) return m[1].replace(/[)\\].,;]*$/, \"\");\n  // 3) arXiv link\n  m = s.match(/arxiv\\.org\\/abs\\/([\\w\\-.]+)/i);\n  if (m) return `10.48550/arXiv.${m[1]}`;\n  return null;\n}\n\nconst doi = extractDOI(txt);\nif (!doi) {\n  return [{ error: true, message: \"DOI not found. Please send a DOI or arXiv/doi.org link.\" }];\n}\n\nreturn [{ doi }];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "7e96135b-4bfb-4a05-a7fe-4936f771e7e5",
      "name": "HTTP 请求2",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueRegularOutput",
      "position": [
        960,
        0
      ],
      "parameters": {
        "url": "=https://api.datacite.org/dois/{{ $('Code').item.json.doi }}",
        "options": {}
      },
      "typeVersion": 4.2
    },
    {
      "id": "6562fd43-ad5a-4172-bf40-e627adec9b65",
      "name": "HTTP请求3",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueRegularOutput",
      "position": [
        960,
        -260
      ],
      "parameters": {
        "url": "=https://api.crossref.org/works/{{ $('Code').item.json.doi }}",
        "options": {}
      },
      "typeVersion": 4.2,
      "alwaysOutputData": false
    },
    {
      "id": "44f8af5d-d0c5-4fbc-9717-9bdcc80f6389",
      "name": "HTTP 请求4",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueRegularOutput",
      "position": [
        960,
        260
      ],
      "parameters": {
        "url": "=https://api.unpaywall.org/v2/{{ $('Code').item.json.doi }}?email=your-email@example.com",
        "options": {}
      },
      "typeVersion": 4.2
    },
    {
      "id": "b63893ad-f200-4c1b-ba07-7f429f38f2ff",
      "name": "HTTP 请求",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        640,
        0
      ],
      "parameters": {
        "url": "=https://api.zotero.org/keys/{{ $json['Zotero API Key'] }}",
        "options": {},
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Zotero-API-Key",
              "value": "={{ $json['Zotero API Key'] }}"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "9f834057-efc6-4f75-b114-4db5d39bd498",
      "name": "编辑字段",
      "type": "n8n-nodes-base.set",
      "position": [
        420,
        0
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "2530066f-becf-4a83-b96c-a91ddd4aceb1",
              "name": "Zotero API Key",
              "type": "string",
              "value": "={{ $credentials.zoteroApi.key }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "d7b1e0b4-dee1-4359-8da6-b9f3df1854f6",
      "name": "代码4",
      "type": "n8n-nodes-base.code",
      "position": [
        1680,
        0
      ],
      "parameters": {
        "jsCode": "// Code4 — Build Zotero parent metadata + pick best PDF URL\n// Input from Merge: $json.crossref, $json.datacite, $json.unpay, and $json.doi\n\nconst doi = $json.doi;\n\n// Unwrap API results if not error\nconst crossrefBody = ($json.crossref && !$json.crossref.error) ? $json.crossref.message : null;\nconst dataciteAttr = ($json.datacite && !$json.datacite.error) ? $json.datacite.data?.attributes : null;\nconst unpayBody    = ($json.unpay && !$json.unpay.error) ? $json.unpay : null;\n\nconst pick = (...vals) => vals.find(v => {\n  if (v === undefined || v === null) return false;\n  if (typeof v === 'string') return v.trim().length > 0;\n  return true;\n}) ?? null;\n\n/* ---------- PDF pickers ---------- */\nfunction fromCrossref(m) {\n  if (!m) return null;\n  const links = m.link || [];\n  for (const l of links) {\n    const ct  = (l['content-type'] || '').toLowerCase();\n    const url = l.URL || '';\n    if (ct.includes('pdf') || /\\.pdf(\\?|$)/i.test(url)) return url;\n  }\n  return null;\n}\n\nfunction fromDataCite(attr) {\n  if (!attr) return null;\n  // try identifiers -> arXiv\n  const ids = attr.identifiers || [];\n  const arxiv = ids.find(i => /arxiv/i.test(i.identifierType || ''))?.identifier;\n  if (arxiv) {\n    const id = String(arxiv).replace(/^arXiv:/i,'');\n    return `https://arxiv.org/pdf/${id}.pdf`;\n  }\n  return null;\n}\n\nfunction fromUnpay(u) {\n  if (!u) return null;\n  if (u.best_oa_location?.url_for_pdf) return u.best_oa_location.url_for_pdf;\n  if (/\\.pdf(\\?|$)/i.test(u.best_oa_location?.url || '')) return u.best_oa_location.url;\n  for (const loc of (u.oa_locations || [])) {\n    if (loc.url_for_pdf) return loc.url_for_pdf;\n    if (/\\.pdf(\\?|$)/i.test(loc.url || '')) return loc.url;\n  }\n  return null;\n}\n\n// Priority: Crossref → Unpaywall → DataCite → arXiv fallback (from DOI)\nlet pdfUrl = fromCrossref(crossrefBody);\nif (!pdfUrl) pdfUrl = fromUnpay(unpayBody);\nif (!pdfUrl) pdfUrl = fromDataCite(dataciteAttr);\nif (!pdfUrl && /^10\\.48550\\/arxiv\\./i.test(doi || '')) {\n  const arxivId = doi.replace(/^10\\.48550\\/arxiv\\./i, '');\n  pdfUrl = `https://arxiv.org/pdf/${arxivId}.pdf`;\n}\n\n/* ---------- Parent metadata (Zotero item) ---------- */\n// Title\nconst title = pick(\n  crossrefBody?.title?.[0],\n  dataciteAttr?.titles?.[0]?.title,\n  doi\n);\n\n// Date (YYYY, YYYY-MM, or YYYY-MM-DD)\nconst issued = crossrefBody?.issued?.['date-parts']?.[0] || [];\nconst dateStr = issued.length\n  ? issued.join('-')\n  : pick(\n      dataciteAttr?.published,\n      dataciteAttr?.publishedOnline,\n      dataciteAttr?.publishedPrint,\n      dataciteAttr?.created\n    ) || \"\";\n\n// Journal / container\nconst publicationTitle = pick(\n  crossrefBody?.['container-title']?.[0],\n  dataciteAttr?.container?.title\n) || \"\";\n\n// Landing page (not PDF)\nconst landingFromCR = crossrefBody?.URL;\nconst landingFromUP = unpayBody?.best_oa_location?.url || unpayBody?.oa_locations?.[0]?.url;\nconst landingFromDC = dataciteAttr?.url;\nconst url = pick(landingFromCR, landingFromUP, landingFromDC) || \"\";\n\n// Abstract\nlet abstractNote = \"\";\nif (Array.isArray(dataciteAttr?.descriptions) && dataciteAttr.descriptions.length) {\n  const absExact = dataciteAttr.descriptions.find(d => /abstract/i.test(d.descriptionType || ''));\n  abstractNote = pick(absExact?.description, dataciteAttr.descriptions[0]?.description) || \"\";\n}\n\n// Creators\nlet creators = [];\nif (Array.isArray(crossrefBody?.author)) {\n  creators = crossrefBody.author.map(a => ({\n    creatorType: \"author\",\n    firstName: a.given || \"\",\n    lastName:  a.family || (a.name || \"\")\n  }));\n} else if (Array.isArray(dataciteAttr?.creators)) {\n  creators = dataciteAttr.creators.map(c => ({\n    creatorType: \"author\",\n    firstName: c.givenName || \"\",\n    lastName:  c.familyName || (c.name || \"\")\n  }));\n}\n\n// Tags\nconst tags = [];\nif (Array.isArray(crossrefBody?.subject)) {\n  for (const s of crossrefBody.subject.slice(0, 5)) tags.push({ tag: String(s) });\n}\ntags.push({ tag: \"from-telegram\" });\n\n// Item type heuristic\nconst itemType = publicationTitle ? \"journalArticle\" : \"preprint\";\n\n// Build meta object for POST /items (parent)\nconst meta = {\n  itemType,\n  title,\n  DOI: doi || \"\",\n  url,\n  date: dateStr,\n  publicationTitle,\n  abstractNote,\n  creators,\n  tags\n};\n\n// Unified output for next step:\n// - meta: used for \"Zotero - Create Parent\"\n// - pdfUrl + hasPdf: for IF & attachment creation (linked_url)\nreturn [{\n  doi,\n  title,\n  meta,\n  pdfUrl,\n  hasPdf: Boolean(pdfUrl)\n}];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "c7aa9f0e-d58a-48d8-8353-f2c60feb42fc",
      "name": "合并",
      "type": "n8n-nodes-base.merge",
      "position": [
        1520,
        0
      ],
      "parameters": {
        "mode": "combine",
        "options": {},
        "combineBy": "combineByPosition",
        "numberInputs": 3
      },
      "executeOnce": true,
      "typeVersion": 3.2
    },
    {
      "id": "5f26c1c8-f21f-4aca-b4ae-84ff86c970f4",
      "name": "合并二进制项",
      "type": "n8n-nodes-base.if",
      "position": [
        1920,
        0
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "520e9b80-afa5-45e1-b572-88a855298b2c",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.hasPdf }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "987447af-6fc0-4ef7-afa6-cf43066fd476",
      "name": "HTTP 请求9",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2160,
        0
      ],
      "parameters": {
        "url": "=https://api.zotero.org/users/{{ $('HTTP Request').item.json.userID }}/items",
        "method": "POST",
        "options": {},
        "jsonBody": "={{ [ $('Code4').item.json.meta ] }}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "headerParameters": {
          "parameters": [
            {
              "name": "ZOTERO-API-KEY",
              "value": "={{ $('HTTP Request').item.json.key }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "d9369d30-23f6-4425-8064-160a40189650",
      "name": "编辑字段 1",
      "type": "n8n-nodes-base.set",
      "position": [
        1180,
        -260
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "0df8889d-a22d-4512-a2dc-3e4700f37fe4",
              "name": "crossref",
              "type": "object",
              "value": "={{ $json }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "a50771b9-b2a1-4ed4-8c7e-1b0b1d629285",
      "name": "编辑字段2",
      "type": "n8n-nodes-base.set",
      "position": [
        1180,
        0
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "0df8889d-a22d-4512-a2dc-3e4700f37fe4",
              "name": "datacite",
              "type": "object",
              "value": "={{ $json }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "dd594610-4115-4035-af02-bec335a10212",
      "name": "编辑字段3",
      "type": "n8n-nodes-base.set",
      "position": [
        1180,
        260
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "0df8889d-a22d-4512-a2dc-3e4700f37fe4",
              "name": "unpay",
              "type": "object",
              "value": "={{ $json }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "0dfa6558-08a4-4a7a-94a2-3d02371aa938",
      "name": "HTTP请求1",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2380,
        0
      ],
      "parameters": {
        "url": "=https://api.zotero.org/users/{{ $('HTTP Request').item.json.userID }}/items",
        "method": "POST",
        "options": {},
        "jsonBody": "={{ [\n  {\n    itemType: \"attachment\",\n    parentItem: $json.successful['0'].key,\n    linkMode: \"linked_url\",\n    title: \"Fulltext PDF\",\n    url: $('If3').item.json.pdfUrl,\n    contentType: \"application/pdf\"\n  }\n] }}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "headerParameters": {
          "parameters": [
            {
              "name": "Zotero-API-Key",
              "value": "={{ $('HTTP Request').item.json.key }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "594c9d30-853d-4447-a42d-c3eb4068287f",
      "name": "便签",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -580,
        -440
      ],
      "parameters": {
        "width": 540,
        "height": 1120,
        "content": "# 通过 Telegram 的 DOI URL 自动导入研究论文到 Zotero"
      },
      "typeVersion": 1
    },
    {
      "id": "77de37c2-b6f5-4fba-9932-154f3144a73a",
      "name": "基础 LLM 链",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "position": [
        2600,
        0
      ],
      "parameters": {
        "text": "=Summarize the following abstract:  \n\n{{ $('HTTP Request9').item.json.successful['0'].data.abstractNote }}",
        "batching": {},
        "messages": {
          "messageValues": [
            {
              "message": "=You are an assistant that summarizes academic paper abstracts.   Your task is to generate a short and clear summary of the abstract in plain English.   Keep the summary concise (2–3 sentences), highlight the main contribution or finding, and avoid technical jargon if possible.  "
            }
          ]
        },
        "promptType": "define"
      },
      "typeVersion": 1.7
    },
    {
      "id": "f5af851a-9a9e-445c-b9c6-a96fdea644f4",
      "name": "OpenRouter Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
      "position": [
        2600,
        200
      ],
      "parameters": {
        "model": "google/gemini-2.0-flash-exp:free",
        "options": {}
      },
      "typeVersion": 1
    },
    {
      "id": "aa28ca2d-8590-431f-b01f-c80494be010c",
      "name": "发送短信",
      "type": "n8n-nodes-base.telegram",
      "position": [
        2940,
        0
      ],
      "webhookId": "0cb93924-6255-48e4-815f-ef2ecd0a8099",
      "parameters": {
        "text": "=📄 Research Paper Summary\nTitle: {{ $('HTTP Request9').item.json.successful['0'].data.title }}\nURL:  {{ $('HTTP Request1').item.json.successful['0'].data.url }}\n\n📝 Summary:\n{{ $json.text }}\n",
        "chatId": "={{ $('Telegram Trigger').item.json.message.chat.id }}",
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "74ac8241-a6b5-4229-99f0-c57b2ae36009",
      "name": "便签1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -20,
        -180
      ],
      "parameters": {
        "color": 2,
        "width": 780,
        "height": 380,
        "content": "- 当用户向您的 Telegram 机器人发送消息时启动工作流。"
      },
      "typeVersion": 1
    },
    {
      "id": "cc9533ee-5edc-4573-9dfa-1cca98a3c854",
      "name": "便签2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        820,
        -500
      ],
      "parameters": {
        "color": 4,
        "width": 600,
        "height": 940,
        "content": "- **Crossref API 请求** – 使用提供的 DOI 从 Crossref 获取参考文献数据。"
      },
      "typeVersion": 1
    },
    {
      "id": "23d15a9f-ebeb-416f-a66d-967cc27b9cf9",
      "name": "便签3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1460,
        -280
      ],
      "parameters": {
        "color": 5,
        "width": 360,
        "height": 440,
        "content": "- 将来自 Crossref、DataCite 和 Unpaywall 的元数据合并为一个对象。"
      },
      "typeVersion": 1
    },
    {
      "id": "66f99fac-0698-400d-9bb2-1e57ce4c05c0",
      "name": "发送文本消息1",
      "type": "n8n-nodes-base.telegram",
      "position": [
        2160,
        200
      ],
      "webhookId": "0cb93924-6255-48e4-815f-ef2ecd0a8099",
      "parameters": {
        "text": "=PDF not found",
        "chatId": "={{ $('Telegram Trigger').item.json.message.chat.id }}",
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "bd043ae9-6d80-4bf0-b80b-79bb921b3340",
      "name": "便签4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1880,
        -200
      ],
      "parameters": {
        "color": 6,
        "width": 640,
        "height": 600,
        "content": "- 使用 IF 节点检查 PDF URL 是否可用。"
      },
      "typeVersion": 1
    },
    {
      "id": "60bda7fc-c9dc-457a-8cc1-8f6a2c981a49",
      "name": "便签5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2580,
        -200
      ],
      "parameters": {
        "width": 600,
        "height": 600,
        "content": "- 使用基础 LLM 链总结论文摘要。"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "f31f5475-f6e7-47c7-afdd-0b9ad3346cf3",
  "connections": {
    "If3": {
      "main": [
        [
          {
            "node": "HTTP Request9",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Send a text message1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code": {
      "main": [
        [
          {
            "node": "Edit Fields",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code4": {
      "main": [
        [
          {
            "node": "If3",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge": {
      "main": [
        [
          {
            "node": "Code4",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Edit Fields": {
      "main": [
        [
          {
            "node": "HTTP Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Edit Fields1": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Edit Fields2": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Edit Fields3": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 2
          }
        ]
      ]
    },
    "HTTP Request": {
      "main": [
        [
          {
            "node": "HTTP Request3",
            "type": "main",
            "index": 0
          },
          {
            "node": "HTTP Request2",
            "type": "main",
            "index": 0
          },
          {
            "node": "HTTP Request4",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Request1": {
      "main": [
        [
          {
            "node": "Basic LLM Chain",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Request2": {
      "main": [
        [
          {
            "node": "Edit Fields2",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    },
    "HTTP Request3": {
      "main": [
        [
          {
            "node": "Edit Fields1",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    },
    "HTTP Request4": {
      "main": [
        [
          {
            "node": "Edit Fields3",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Request9": {
      "main": [
        [
          {
            "node": "HTTP Request1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Basic LLM Chain": {
      "main": [
        [
          {
            "node": "Send a text message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Telegram Trigger": {
      "main": [
        [
          {
            "node": "Code",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenRouter Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Basic LLM Chain",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    }
  }
}
常见问题

如何使用这个工作流?

复制上方的 JSON 配置代码,在您的 n8n 实例中创建新工作流并选择「从 JSON 导入」,粘贴配置后根据需要修改凭证设置即可。

这个工作流适合什么场景?

高级 - AI 摘要总结, 多模态 AI

需要付费吗?

本工作流完全免费,您可以直接导入使用。但请注意,工作流中使用的第三方服务(如 OpenAI API)可能需要您自行付费。

工作流信息
难度等级
高级
节点数量25
分类2
节点类型10
难度说明

适合高级用户,包含 16+ 个节点的复杂工作流

作者
Budi SJ

Budi SJ

@budisj

I’m a Product Designer who also works as an Automation Developer. With a background in product design and systems thinking, I build user-centered workflows. My focus is on helping teams and businesses work more productively through impactful automation systems.

外部链接
在 n8n.io 查看

分享此工作流