{
  "name": "انتشار هوشمند در لینکدین (Gemini) — v2.0",
  "nodes": [
    {
      "parameters": {
        "content": "# 📘 انتشار هوشمند در لینکدین — نسخه 2.0\n\n## این ورکفلو چه می‌کند؟\nبا **یک فرم ساده** چهار نوع پست در لینکدین منتشر می‌کند، بدون اینکه لینکدین را باز کنید:\n\n| نوع پست | چه چیزی وارد می‌کنید | نتیجه |\n|---|---|---|\n| **متن ساده** | متن | پست متنی |\n| **پست با عکس** | متن + عکس (+ عنوان) | پست تصویری |\n| **اشتراک لینک (مقاله)** | لینک + متن (+ عکس بندانگشتی) | پست با پیش‌نمایش لینک |\n| **تولید خودکار با هوش مصنوعی** | فقط لینک (+ عکس، زاویه دلخواه) | Gemini مقاله را می‌خواند و پست حرفه‌ای می‌نویسد |\n\n## مسیر کلی\n```\nفرم → هوش مصنوعی؟\n   بله → خواندن صفحه → استخراج متن → Gemini (نوشتن پست)\n   خیر ──────────────────────────────────────────┐\n                                     آماده‌سازی پست → نوع انتشار\n                                        عکس → LinkedIn (Image)\n                                        لینک → LinkedIn (Article)\n                                        متن  → LinkedIn (Text)\n```\n\n## اعتبارنامه‌ها (فقط ۲ تا)\n| نوع | کجا | مقدار |\n|---|---|---|\n| LinkedIn OAuth2 API | ۳ گره LinkedIn | Client ID / Secret اپ لینکدین |\n| Header Auth «Gemini API Key» | Gemini – Write Post | `x-goog-api-key` = کلید Gemini |\n\n## راه‌اندازی سریع\n1. اپ لینکدین بساز (developer.linkedin.com) و محصول **Share on LinkedIn** و **Sign In with LinkedIn using OpenID Connect** را فعال کن\n2. OAuth Redirect URL که n8n نشان می‌دهد را در اپ لینکدین بگذار\n3. در هر ۳ گره LinkedIn، **Person** را از لیست انتخاب کن\n4. ورکفلو را **Active** کن و **Production URL** فرم را باز کن\n\n---\nطراحی و آموزش: **مرتضی عظیمی** — mortezaazimi.ir",
        "height": 1240,
        "width": 660,
        "color": 4
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -1180,
        -460
      ],
      "id": "2e9f18e3-28d7-4fe7-b9c1-8680a1fa1245",
      "name": "📘 راهنمای کلی"
    },
    {
      "parameters": {
        "content": "## 1️⃣ فرم انتشار\n\nیک فرم برای هر چهار حالت. فیلدها:\n- **نوع پست** (اجباری)\n- **متن پست** — در حالت هوش مصنوعی، زاویه یا نکته‌ی دلخواه شما (اختیاری)\n- **عنوان** — برای پیش‌نمایش لینک یا خط اول پست عکس‌دار\n- **لینک** — برای اشتراک لینک و هوش مصنوعی\n- **عکس** — jpg یا png\n- **زبان پست هوش مصنوعی** — فارسی یا English\n\n💡 آدرس فرم را از تب **Production URL** همین گره بردار و بوکمارک کن.\n⚠️ اسم فیلدها را عوض نکن؛ گره «Prepare Post» با همین اسم‌ها کار می‌کند.",
        "height": 760,
        "width": 440,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -480,
        -460
      ],
      "id": "51efa2be-ea23-49eb-8538-5f65356903a2",
      "name": "۱ — فرم"
    },
    {
      "parameters": {
        "content": "## 2️⃣ نوشتن پست با هوش مصنوعی\n\nفقط وقتی نوع پست «تولید خودکار با هوش مصنوعی» باشد اجرا می‌شود:\n1. **Fetch Page** — صفحه‌ی لینک را دانلود می‌کند\n2. **Page → Text** — منو، فوتر و کدها را حذف می‌کند و متن اصلی مقاله را برمی‌دارد\n3. **Gemini – Write Post** — یک پست لینکدین حرفه‌ای (قلاب، نکات کلیدی، سؤال پایانی، هشتگ) به‌صورت JSON می‌نویسد\n\n✏️ لحن و قواعد نوشتن در `systemInstruction` بدنه‌ی گره Gemini است.\n💡 مدل پیش‌فرض `gemini-3.5-flash-lite`؛ برای متن قوی‌تر `gemini-3.8-flash` را در URL بگذار.\n⚠️ بعضی سایت‌ها جلوی ربات‌ها را می‌گیرند؛ در این صورت پیام خطای واضح می‌گیری.",
        "height": 460,
        "width": 820,
        "color": 5
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        0,
        -640
      ],
      "id": "9308b09f-f8dc-4e5f-bead-eb4f5644de0d",
      "name": "۲ — هوش مصنوعی"
    },
    {
      "parameters": {
        "content": "## 3️⃣ آماده‌سازی پست\n\n**Prepare Post** همه‌ی حالت‌ها را یکی می‌کند و خطاها را با پیام فارسی نشان می‌دهد:\n- `format` — image / article / text\n- `text` — متن نهایی (حداکثر ۳۰۰۰ کاراکتر)\n- `title` و `url`\n- عکس فرم را به‌عنوان `data` آماده می‌کند\n\nدر حالت هوش مصنوعی: اگر عکس دادی → پست عکس‌دار (لینک داخل متن)، وگرنه → پست لینک با پیش‌نمایش.",
        "height": 760,
        "width": 400,
        "color": 6
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        860,
        -460
      ],
      "id": "78d598a8-89bd-48f1-b5ad-0875822d0e66",
      "name": "۳ — آماده‌سازی"
    },
    {
      "parameters": {
        "content": "## 4️⃣ انتشار در لینکدین\n\n**Route by Format** پست را به گره درست می‌فرستد:\n- **LinkedIn – Image** — پست با عکس\n- **LinkedIn – Article** — پست با پیش‌نمایش لینک (و عکس بندانگشتی اگر داده باشی)\n- **LinkedIn – Text** — پست متنی\n\n✏️ در هر ۳ گره فیلد **Person** را از لیست انتخاب کن.\n🏢 برای انتشار از طرف **صفحه‌ی شرکت**، Post As را روی Organization بگذار و URN صفحه را وارد کن.\n👁 Visibility پیش‌فرض: Public.",
        "height": 1040,
        "width": 700,
        "color": 4
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1300,
        -640
      ],
      "id": "09884f69-ee6b-4d71-bb4b-8787c3fdb312",
      "name": "۴ — انتشار"
    },
    {
      "parameters": {
        "formTitle": "انتشار پست در لینکدین",
        "formDescription": "نوع پست را انتخاب کنید و فیلدهای لازم را پر کنید. در حالت هوش مصنوعی فقط لینک کافی است.",
        "formFields": {
          "values": [
            {
              "fieldLabel": "نوع پست",
              "fieldType": "dropdown",
              "requiredField": true,
              "fieldOptions": {
                "values": [
                  {
                    "option": "متن ساده"
                  },
                  {
                    "option": "پست با عکس"
                  },
                  {
                    "option": "اشتراک لینک (مقاله)"
                  },
                  {
                    "option": "تولید خودکار با هوش مصنوعی از روی لینک"
                  }
                ]
              }
            },
            {
              "fieldLabel": "متن پست",
              "fieldType": "textarea",
              "placeholder": "متن پست — در حالت هوش مصنوعی: زاویه یا نکته‌ی دلخواه (اختیاری)"
            },
            {
              "fieldLabel": "عنوان",
              "placeholder": "اختیاری"
            },
            {
              "fieldLabel": "لینک",
              "placeholder": "https://..."
            },
            {
              "fieldLabel": "عکس",
              "fieldType": "file",
              "multipleFiles": false,
              "acceptFileTypes": ".jpg, .jpeg, .png"
            },
            {
              "fieldLabel": "زبان پست هوش مصنوعی",
              "fieldType": "dropdown",
              "fieldOptions": {
                "values": [
                  {
                    "option": "فارسی"
                  },
                  {
                    "option": "English"
                  }
                ]
              }
            }
          ]
        },
        "options": {
          "appendAttribution": false,
          "buttonLabel": "انتشار در لینکدین",
          "respondWithOptions": {
            "values": {
              "respondWith": "text",
              "formSubmittedText": "دریافت شد ✅ پست شما تا چند ثانیه‌ی دیگر در لینکدین منتشر می‌شود."
            }
          }
        }
      },
      "type": "n8n-nodes-base.formTrigger",
      "typeVersion": 2.2,
      "position": [
        -420,
        -100
      ],
      "id": "99d9cab9-a046-47e3-b653-c3d6b78670a1",
      "name": "LinkedIn Post Form",
      "webhookId": "ec2ad2e8-657b-4ed3-a022-69143a6319cf"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "f484cdcc-7ba7-47f5-bd8b-5d9c48fa198b",
              "leftValue": "={{ $json['نوع پست'] }}",
              "rightValue": "هوش مصنوعی",
              "operator": {
                "type": "string",
                "operation": "contains"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        -180,
        -100
      ],
      "id": "ce594fff-a6fb-4859-ab08-470857875342",
      "name": "AI Mode?"
    },
    {
      "parameters": {
        "url": "={{ $json['لینک'] }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "User-Agent",
              "value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0 Safari/537.36"
            },
            {
              "name": "Accept-Language",
              "value": "fa,en;q=0.8"
            }
          ]
        },
        "options": {
          "response": {
            "response": {
              "responseFormat": "text"
            }
          },
          "timeout": 20000
        }
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        80,
        -320
      ],
      "id": "4f1f9f7e-d644-4539-970b-59012bdeb8e8",
      "name": "Fetch Page"
    },
    {
      "parameters": {
        "jsCode": "// ─────────────────────────────────────────────────────────────\n// تبدیل HTML صفحه به متن خالص برای Gemini\n// ─────────────────────────────────────────────────────────────\nconst html = String($input.first().json.data || '');\n\nconst pick = re => { const m = html.match(re); return m ? m[1].trim() : ''; };\nconst decode = s => s\n  .replace(/&nbsp;/g, ' ').replace(/&amp;/g, '&').replace(/&quot;/g, '\"').replace(/&#39;|&apos;/g, \"'\")\n  .replace(/&lt;/g, '<').replace(/&gt;/g, '>')\n  .replace(/&#(\\d+);/g, (_, n) => String.fromCodePoint(+n))\n  .replace(/&#x([0-9a-f]+);/gi, (_, n) => String.fromCodePoint(parseInt(n, 16)));\n\nconst pageTitle = decode(pick(/<meta[^>]+property=[\"']og:title[\"'][^>]+content=[\"']([^\"']+)/i) || pick(/<title[^>]*>([\\s\\S]*?)<\\/title>/i));\nconst pageDesc = decode(pick(/<meta[^>]+name=[\"']description[\"'][^>]+content=[\"']([^\"']+)/i));\n\n// اگر <article> یا <main> داشت، فقط همان بخش را برمی‌داریم\nlet body = (html.match(/<article[\\s\\S]*?<\\/article>/i) || html.match(/<main[\\s\\S]*?<\\/main>/i) || [html])[0];\nbody = body\n  .replace(/<(script|style|noscript|svg|nav|footer|header|aside|form|iframe)[\\s\\S]*?<\\/\\1>/gi, ' ')\n  .replace(/<\\/(p|div|h[1-6]|li|br|tr|section)>/gi, '\\n')\n  .replace(/<[^>]+>/g, ' ');\nconst pageText = decode(body).replace(/[ \\t]+/g, ' ').replace(/\\n\\s*\\n+/g, '\\n').trim().slice(0, 15000);\n\nif (pageText.length < 200) throw new Error('متن کافی از این لینک خوانده نشد. ممکن است سایت جلوی ربات‌ها را بگیرد؛ لینک دیگری امتحان کنید.');\nreturn [{ json: { pageTitle, pageDesc, pageText } }];\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        300,
        -320
      ],
      "id": "0eab2f2a-32e5-4df4-a546-94adaa961630",
      "name": "Page → Text"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://generativelanguage.googleapis.com/v1beta/models/gemini-3.5-flash-lite:generateContent",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({\n  systemInstruction: { parts: [{ text: \"You are a senior LinkedIn ghostwriter for business owners and managers.\\nRead the article text and write ONE LinkedIn post about its most valuable insight.\\n\\nRules:\\n- Write in the language requested by the user (Persian or English).\\n- First line is a strong hook (max 12 words) that makes busy professionals stop scrolling.\\n- Short paragraphs of 1-2 lines with blank lines between them.\\n- Share 2-3 concrete takeaways from the article. Never invent facts, numbers or quotes that are not in the article.\\n- Speak as the author sharing a lesson, not as a summary bot. Do not say \\\"this article\\\".\\n- End with one question that invites comments.\\n- Add 3-5 relevant hashtags on the last line.\\n- 120 to 220 words. No markdown, no asterisks, no bold. Emojis: at most 2.\\n- If the user gave extra notes or an angle, follow them.\\n- title: a short headline (max 10 words) for the link preview.\\n\\nReturn JSON only: {\\\"title\\\": \\\"...\\\", \\\"post\\\": \\\"...\\\"}\" }] },\n  contents: [{\n    role: 'user',\n    parts: [{ text:\n      'Language: ' + ($('LinkedIn Post Form').item.json['زبان پست هوش مصنوعی'] === 'English' ? 'English' : 'Persian (Farsi)') + '\\n' +\n      'Source URL: ' + $('LinkedIn Post Form').item.json['لینک'] + '\\n' +\n      'Page title: ' + $json.pageTitle + '\\n' +\n      'Page description: ' + $json.pageDesc + '\\n' +\n      'User notes / angle: ' + ($('LinkedIn Post Form').item.json['متن پست'] || '-') + '\\n\\n' +\n      'ARTICLE TEXT:\\n' + $json.pageText\n    }]\n  }],\n  generationConfig: {\n    responseMimeType: 'application/json',\n    responseSchema: {\n      type: 'OBJECT',\n      properties: { title: { type: 'STRING' }, post: { type: 'STRING' } },\n      required: ['title', 'post']\n    }\n  }\n}) }}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        520,
        -320
      ],
      "id": "b9c44ece-bfdc-4d5c-b150-bd66bbe015ea",
      "name": "Gemini – Write Post"
    },
    {
      "parameters": {
        "jsCode": "// ─────────────────────────────────────────────────────────────\n// آماده‌سازی پست: همه‌ی حالت‌ها اینجا یکی می‌شوند\n// خروجی: format (text | article | image)، text، title، url و عکس (binary: data)\n// ─────────────────────────────────────────────────────────────\nconst form = $('LinkedIn Post Form').first();\nconst f = form.json;\nconst type = f['نوع پست'] || 'متن ساده';\nconst isAI = type.includes('هوش مصنوعی');\n\nlet text = String(f['متن پست'] || '').trim();\nlet title = String(f['عنوان'] || '').trim();\nconst url = String(f['لینک'] || '').trim();\n\n// اولین فایل آپلودشده در فرم (اسم فیلد مهم نیست)\nconst image = form.binary ? Object.values(form.binary).find(b => /^image\\//.test(b.mimeType || '')) : null;\n\nif (isAI) {\n  const parts = $input.first().json.candidates?.[0]?.content?.parts || [];\n  const raw = parts.filter(p => p.text && !p.thought).map(p => p.text).join('').trim()\n    .replace(/^```(?:json)?/i, '').replace(/```$/, '').trim();\n  let ai;\n  try { ai = JSON.parse(raw); }\n  catch (e) { throw new Error('خروجی Gemini قابل خواندن نبود: ' + raw.slice(0, 200)); }\n  title = title || String(ai.title || '').trim();\n  text = String(ai.post || '').trim();\n}\n\nif (!text) throw new Error('متن پست خالی است. فیلد «متن پست» را پر کنید.');\n\nlet format = 'text';\nif (type === 'پست با عکس') {\n  if (!image) throw new Error('برای «پست با عکس» باید یک عکس (jpg یا png) آپلود کنید.');\n  format = 'image';\n} else if (type === 'اشتراک لینک (مقاله)') {\n  if (!url) throw new Error('برای «اشتراک لینک» فیلد «لینک» را پر کنید.');\n  format = 'article';\n} else if (isAI) {\n  format = image ? 'image' : 'article';\n}\n\n// در پست عکس‌دار، لینک داخل متن می‌آید\nif (format === 'image' && url && !text.includes(url)) text += '\\n\\n🔗 ' + url;\n// عنوانِ پست دستی عکس‌دار، خط اول متن می‌شود\nif (format === 'image' && !isAI && title) text = title + '\\n\\n' + text;\n\n// سقف متن پست لینکدین ۳۰۰۰ کاراکتر است\nif (text.length > 3000) text = text.slice(0, 2990).trim() + '…';\n\nconst out = { json: { format, text, title: title.slice(0, 200), url } };\nif (image) out.binary = { data: image };\nreturn [out];\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        960,
        -100
      ],
      "id": "0ab4e449-d007-4794-b7f5-77f46c6bee3f",
      "name": "Prepare Post"
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "5b85111c-c3ee-491d-9c2a-32076f8d3481",
                    "leftValue": "={{ $json.format }}",
                    "rightValue": "image",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "Image"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "960020e0-b480-4a91-b803-cf0a255559fe",
                    "leftValue": "={{ $json.format }}",
                    "rightValue": "article",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "Article"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "8d8be0b8-bfc1-462f-964d-6d0e00bc0872",
                    "leftValue": "={{ $json.format }}",
                    "rightValue": "text",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "Text"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.2,
      "position": [
        1180,
        -100
      ],
      "id": "600f17f6-1194-4199-9433-177b7533ebd9",
      "name": "Route by Format"
    },
    {
      "parameters": {
        "person": "",
        "text": "={{ $json.text }}",
        "shareMediaCategory": "IMAGE",
        "additionalFields": {
          "visibility": "PUBLIC"
        },
        "binaryPropertyName": "data"
      },
      "type": "n8n-nodes-base.linkedIn",
      "typeVersion": 1,
      "position": [
        1560,
        -300
      ],
      "id": "0bcd431a-2ed6-463d-a06f-ee6ba0ffbc0b",
      "name": "LinkedIn – Image"
    },
    {
      "parameters": {
        "person": "",
        "text": "={{ $json.text }}",
        "shareMediaCategory": "ARTICLE",
        "additionalFields": {
          "visibility": "PUBLIC",
          "originalUrl": "={{ $json.url }}",
          "title": "={{ $json.title }}",
          "thumbnailBinaryPropertyName": "={{ $binary.data ? 'data' : '' }}"
        }
      },
      "type": "n8n-nodes-base.linkedIn",
      "typeVersion": 1,
      "position": [
        1560,
        -100
      ],
      "id": "964c6c90-1c8d-4259-b6b8-845ec160a64e",
      "name": "LinkedIn – Article"
    },
    {
      "parameters": {
        "person": "",
        "text": "={{ $json.text }}",
        "shareMediaCategory": "NONE",
        "additionalFields": {
          "visibility": "PUBLIC"
        }
      },
      "type": "n8n-nodes-base.linkedIn",
      "typeVersion": 1,
      "position": [
        1560,
        100
      ],
      "id": "ec028b5e-bdc1-4104-b3ad-3b5d9e489b72",
      "name": "LinkedIn – Text"
    }
  ],
  "pinData": {},
  "connections": {
    "LinkedIn Post Form": {
      "main": [
        [
          {
            "node": "AI Mode?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Mode?": {
      "main": [
        [
          {
            "node": "Fetch Page",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Prepare Post",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Page": {
      "main": [
        [
          {
            "node": "Page → Text",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Page → Text": {
      "main": [
        [
          {
            "node": "Gemini – Write Post",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gemini – Write Post": {
      "main": [
        [
          {
            "node": "Prepare Post",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Post": {
      "main": [
        [
          {
            "node": "Route by Format",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route by Format": {
      "main": [
        [
          {
            "node": "LinkedIn – Image",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "LinkedIn – Article",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "LinkedIn – Text",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "tags": []
}