{
  "name": "댓글 → DM 자동발송 (팔로워 확인) · by 정곡",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "GET",
        "path": "ig-comment",
        "responseMode": "responseNode",
        "options": {}
      },
      "id": "wh-verify",
      "name": "① 웹훅 인증 (GET)",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        -300,
        -200
      ],
      "webhookId": "commentdm-ig-comment-verify"
    },
    {
      "parameters": {
        "respondWith": "text",
        "responseBody": "={{ $json.query['hub.challenge'] }}",
        "options": {}
      },
      "id": "wh-challenge",
      "name": "② hub.challenge 반환",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        -20,
        -200
      ]
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "ig-comment",
        "responseCode": 200,
        "options": {}
      },
      "id": "wh-event",
      "name": "③ 웹훅 수신 (POST)",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        -300,
        100
      ],
      "webhookId": "commentdm-ig-comment-event"
    },
    {
      "parameters": {
        "jsCode": "// ╔══════════════════════════════════════════════════════╗\n// ║  설정 — 여기만 고치면 됩니다                              ║\n// ╚══════════════════════════════════════════════════════╝\n\n// 팔로워에게만 링크를 줄지.  false 로 바꾸면 댓글 즉시 링크 발송\nconst REQUIRE_FOLLOW = true;\n\n// 1단계 문구 (팔로우 요청). REQUIRE_FOLLOW 가 true 일 때 댓글에 대한 첫 DM\nconst STEP1 = [\n  '안녕하세요! 👋',\n  '',\n  '요청하신 자료 준비됐어요.',\n  '',\n  '① 팔로우',\n  '② 이 메시지에 「보내줘」 답장',\n  '',\n  '바로 링크 보내드릴게요 😊'\n].join('\\n');\n\n// 키워드 → 최종 자료 문구.  새 편 나오면 블록 하나 추가\nconst RULES = [\n\n  // ── 예시 · 키워드 「자료」 ────────────────\n  //   댓글에 \"자료\" 가 들어 있으면 아래 문구를 보냅니다.\n  //   키워드와 링크만 본인 것으로 바꾸세요.\n  {\n    keyword: '자료',\n    reply: [\n      '요청하신 자료예요 🙂',\n      '',\n      '→ https://여기에-링크를-넣으세요',\n      '',\n      '궁금한 게 있으면 편하게 답장 주세요.'\n    ].join('\\n')\n  },\n\n  // ── 02편 · (키워드) ── 복사해서 쓰세요 ─────────\n  // {\n  //   keyword: '키워드',\n  //   reply: [\n  //     '(자료 한 줄 설명)',\n  //     '',\n  //     '→ https://jeonggok.com/02-xxx.html',\n  //     '',\n  //     '궁금한 거 있으면 여기로 편하게 답주세요.'\n  //   ].join('\\n')\n  // },\n\n];\n// ══════════════════════════════════════════════════════════\n\nconst store = $getWorkflowStaticData('global');\nstore.pending = store.pending || {};\n\nconst out = [];\n\nfor (const item of $input.all()) {\n  const body = item.json.body ?? item.json;\n  if (body.object !== 'instagram') continue;\n\n  for (const entry of (body.entry ?? [])) {\n    const myId = String(entry.id ?? '');\n\n    for (const ch of (entry.changes ?? [])) {\n      if (ch.field !== 'comments') continue;\n\n      const v      = ch.value ?? {};\n      const text   = String(v.text ?? '');\n      const fromId = String(v.from?.id ?? '');\n\n      if (fromId && myId && fromId === myId) continue;          // 내 댓글 무시\n\n      const upper = text.toUpperCase();\n      const rule = RULES.find(r => upper.includes(String(r.keyword).toUpperCase()));\n      if (!rule) continue;                                        // 키워드 없음\n\n      let replyText;\n      if (REQUIRE_FOLLOW) {\n        // 대기 명단에 기록 → 답장 오면 ⑦~⑩ 이 처리\n        store.pending[fromId] = { keyword: rule.keyword, reply: rule.reply, ts: Date.now() };\n        replyText = STEP1;\n      } else {\n        replyText = '안녕하세요! 👋\\n\\n' + rule.reply;\n      }\n\n      out.push({ json: {\n        comment_id:    v.id,\n        from_id:       fromId,\n        from_username: v.from?.username ?? null,\n        comment_text:  text,\n        matched:       rule.keyword,\n        reply_text:    replyText\n      }});\n    }\n  }\n}\n\nreturn out;\n"
      },
      "id": "parse",
      "name": "④ 댓글 → 1단계",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -20,
        0
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://graph.instagram.com/v23.0/me/messages",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ recipient: { comment_id: $json.comment_id }, message: { text: $json.reply_text } }) }}",
        "options": {}
      },
      "id": "send-dm",
      "name": "⑤ DM 1단계 발송 (Private Reply)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        260,
        0
      ],
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {},
      "id": "done1",
      "name": "⑥ 완료",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        540,
        0
      ]
    },
    {
      "parameters": {
        "jsCode": "// DM 답장 웹훅에서 \"대기 명단에 있는 사람\"의 메시지만 골라냅니다.\n// 대기 명단에 없는 DM(일반 문의)은 여기서 걸러져서 자동화가 건드리지 않습니다.\n\nconst store = $getWorkflowStaticData('global');\nstore.pending = store.pending || {};\n\nconst out = [];\n\nfor (const item of $input.all()) {\n  const body = item.json.body ?? item.json;\n  if (body.object !== 'instagram') continue;\n\n  for (const entry of (body.entry ?? [])) {\n    const myId = String(entry.id ?? '');\n\n    for (const m of (entry.messaging ?? [])) {\n      const msg = m.message;\n      if (!msg) continue;                       // 읽음·반응 등은 무시\n      if (msg.is_echo) continue;                // 내가 보낸 메시지 에코 무시\n\n      const senderId = String(m.sender?.id ?? '');\n      if (!senderId || senderId === myId) continue;\n\n      const pend = store.pending[senderId];\n      if (!pend) continue;                      // 대기 명단에 없음 → 사람이 처리\n\n      out.push({ json: {\n        sender_id: senderId,\n        text:      String(msg.text ?? ''),\n        keyword:   pend.keyword,\n        reply:     pend.reply\n      }});\n    }\n  }\n}\n\nreturn out;\n"
      },
      "id": "reply-parse",
      "name": "⑦ 답장 파싱",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -20,
        220
      ]
    },
    {
      "parameters": {
        "method": "GET",
        "url": "=https://graph.instagram.com/v23.0/{{ $json.sender_id }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "fields",
              "value": "username,is_user_follow_business,is_business_follow_user"
            }
          ]
        },
        "options": {}
      },
      "id": "follow-check",
      "name": "⑧ 팔로우 조회",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        260,
        220
      ],
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "jsCode": "// ⑧ 팔로우 조회 결과를 보고 보낼 문구를 정합니다.\n\nconst FOLLOW_ASK = [\n  '아직 팔로우가 안 되어 있어요 🙏',\n  '',\n  '팔로우 하신 뒤 「보내줘」 한 번 더 보내주세요.',\n  '바로 보내드릴게요!'\n].join('\\n');\n\n// 팔로우 조회 자체가 실패(권한·필드 미지원)하면 어떻게 할지.\n// true  = 그냥 링크 보냄 (사람이 안 갇힘)   false = 팔로우 요청 문구\nconst FAIL_OPEN = true;\n\nconst store = $getWorkflowStaticData('global');\nstore.pending = store.pending || {};\n\nconst replies  = $('⑦ 답장 파싱').all();\nconst profiles = $input.all();\n\nconst out = [];\n\nfor (let i = 0; i < profiles.length; i++) {\n  const r = replies[i]?.json ?? {};\n  const p = profiles[i]?.json ?? {};\n\n  const lookupFailed = (p.error !== undefined) || (p.is_user_follow_business === undefined);\n  const follows = lookupFailed ? FAIL_OPEN : (p.is_user_follow_business === true);\n\n  let text;\n  if (follows) {\n    text = '팔로우 감사합니다 😊\\n\\n' + r.reply;\n    delete store.pending[r.sender_id];        // 발송 완료 → 명단에서 제거\n  } else {\n    text = FOLLOW_ASK;\n  }\n\n  out.push({ json: {\n    recipient_id: r.sender_id,\n    follows:      follows,\n    lookup_failed: lookupFailed,\n    keyword:      r.keyword,\n    text:         text\n  }});\n}\n\nreturn out;\n"
      },
      "id": "branch",
      "name": "⑨ 팔로우 분기",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        540,
        220
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://graph.instagram.com/v23.0/me/messages",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ recipient: { id: $json.recipient_id }, message: { text: $json.text } }) }}",
        "options": {}
      },
      "id": "send-final",
      "name": "⑩ DM 발송",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        820,
        220
      ],
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {},
      "id": "done2",
      "name": "⑪ 완료",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        1100,
        220
      ]
    }
  ],
  "connections": {
    "① 웹훅 인증 (GET)": {
      "main": [
        [
          {
            "node": "② hub.challenge 반환",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "③ 웹훅 수신 (POST)": {
      "main": [
        [
          {
            "node": "④ 댓글 → 1단계",
            "type": "main",
            "index": 0
          },
          {
            "node": "⑦ 답장 파싱",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "④ 댓글 → 1단계": {
      "main": [
        [
          {
            "node": "⑤ DM 1단계 발송 (Private Reply)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "⑤ DM 1단계 발송 (Private Reply)": {
      "main": [
        [
          {
            "node": "⑥ 완료",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "⑦ 답장 파싱": {
      "main": [
        [
          {
            "node": "⑧ 팔로우 조회",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "⑧ 팔로우 조회": {
      "main": [
        [
          {
            "node": "⑨ 팔로우 분기",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "⑨ 팔로우 분기": {
      "main": [
        [
          {
            "node": "⑩ DM 발송",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "⑩ DM 발송": {
      "main": [
        [
          {
            "node": "⑪ 완료",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "pinData": {}
}