{
  "_meta": {
    "\u8aac\u660e": "WF04: vtuber_groups \u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u6295\u7a3f\u30ef\u30fc\u30af\u30d5\u30ed\u30fc",
    "n8nURL": "https://n8n.appexx.me",
    "\u6700\u7d42\u66f4\u65b0": "2026-03-18",
    "\u72b6\u614b": "\u30a4\u30f3\u30dd\u30fc\u30c8\u7528\uff08n8n\u4e0a\u3067\u30a4\u30f3\u30dd\u30fc\u30c8\u5f8c\u3001\u8a8d\u8a3c\u60c5\u5831\u3092\u8a2d\u5b9a\u3057\u3066\u304f\u3060\u3055\u3044\uff09",
    "\u30d5\u30ed\u30fc": "Schedule(6h) \u2192 Read Config \u2192 Parse Config \u2192 Read groups_db \u2192 Filter Pending(\u6700\u59273\u4ef6) \u2192 Has Pending? \u2192 Gemini & Prepare WP Body \u2192 Create WP Post \u2192 Extract WP Response \u2192 Update groups_db \u2192 Append Log",
    "Google\u30b9\u30d7\u30ec\u30c3\u30c9\u30b7\u30fc\u30c8": {
      "ID": "1WwyRbzS7XfVqcaH6Ipe2JFqMtD-X1c5NKXJViaZAtvM",
      "groups_db\u30b7\u30fc\u30c8\u5fc5\u9808\u5217": [
        "slug\uff08URL\u30b9\u30e9\u30c3\u30b0\u30fb\u30e6\u30cb\u30fc\u30af\u30ad\u30fc\uff09",
        "name\uff08\u30b0\u30eb\u30fc\u30d7\u540d\u30fb\u65e5\u672c\u8a9e\uff09",
        "name_en\uff08\u82f1\u8a9e\u540d\uff09",
        "grp_agency_name\uff08\u904b\u55b6\u4f1a\u793e\u30fb\u4e8b\u52d9\u6240\u540d\uff09",
        "grp_type\uff08\u4f01\u696d / \u500b\u4eba / \u975e\u516c\u5f0f\uff09",
        "grp_founded_date\uff08\u8a2d\u7acb\u65e5 YYYY-MM-DD\uff09",
        "grp_member_count\uff08\u30e1\u30f3\u30d0\u30fc\u6570\uff09",
        "grp_total_subscribers\uff08\u5408\u8a08\u767b\u9332\u8005\u6570\uff09",
        "grp_official_website\uff08\u516c\u5f0f\u30b5\u30a4\u30c8URL\uff09",
        "description_hint\uff08Gemini\u3078\u306e\u30d2\u30f3\u30c8\u6587\uff09",
        "dir_status\uff08\u7a7a=\u672a\u51e6\u7406 / published=\u6295\u7a3f\u6e08\u307f\uff09",
        "dir_post_id\uff08\u6295\u7a3f\u5f8c\u306eWP Post ID\uff09",
        "published_url\uff08\u6295\u7a3f\u5f8c\u306eURL\uff09"
      ]
    }
  },
  "name": "WF04 - VTuber Groups Directory Post",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours",
              "hoursInterval": 6
            }
          ]
        }
      },
      "id": "wf04-schedule",
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        -1840,
        144
      ]
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "value": "1WwyRbzS7XfVqcaH6Ipe2JFqMtD-X1c5NKXJViaZAtvM",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "config",
          "mode": "name"
        },
        "options": {}
      },
      "id": "wf04-read-config",
      "name": "Read Config",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        -1616,
        144
      ]
    },
    {
      "parameters": {
        "jsCode": "const cfg = {};\nfor (const item of $input.all()) {\n  if (item.json.key) cfg[item.json.key] = item.json.value;\n}\nreturn [{ json: cfg }];"
      },
      "id": "wf04-parse-config",
      "name": "Parse Config",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -1392,
        144
      ]
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "value": "={{ $('Parse Config').item.json.sheets_id }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "groups_db",
          "mode": "name"
        },
        "options": {}
      },
      "id": "wf04-read-groups",
      "name": "Read groups_db",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        -1168,
        144
      ]
    },
    {
      "parameters": {
        "jsCode": "// dir_status \u304c\u7a7a\u306e\u884c\u3092\u6700\u59273\u4ef6\u53d6\u5f97\u3057\u3001config \u3092\u5404\u30a2\u30a4\u30c6\u30e0\u306b\u4ed8\u4e0e\nconst cfg = $('Parse Config').item.json;\nconst pending = $input.all()\n  .filter(i => !i.json.dir_status || i.json.dir_status.trim() === '')\n  .slice(0, 3);\n\nif (pending.length === 0) return [];\n\nreturn pending.map(i => ({ json: { ...i.json, _config: cfg } }));"
      },
      "id": "wf04-filter-pending",
      "name": "Filter Pending",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -960,
        144
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "cond-wf04",
              "leftValue": "={{ $input.all().length }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "gt"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "wf04-has-pending",
      "name": "Has Pending?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        -736,
        144
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "function encodeBase64(str) {\n  const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';\n  let result = '', i = 0;\n  while (i < str.length) {\n    const a = str.charCodeAt(i++);\n    const b = i < str.length ? str.charCodeAt(i++) : NaN;\n    const c = i < str.length ? str.charCodeAt(i++) : NaN;\n    result += chars[a >> 2];\n    result += chars[((a & 3) << 4) | ((isNaN(b) ? 0 : b) >> 4)];\n    result += isNaN(b) ? '=' : chars[((b & 15) << 2) | ((isNaN(c) ? 0 : c) >> 6)];\n    result += isNaN(c) ? '=' : chars[c & 63];\n  }\n  return result;\n}\n\nconst grp = $input.item.json;\nconst cfg = grp._config;\n\nif (!grp.name) return { json: { skip: true, reason: 'no name' } };\n\nconst memberCount = parseInt(grp.grp_member_count) || 0;\nconst totalSubs = parseInt(grp.grp_total_subscribers) || 0;\nconst subsFormatted = totalSubs >= 10000 ? Math.round(totalSubs / 10000) + '\u4e07\u4eba' : totalSubs.toLocaleString('ja-JP') + '\u4eba';\n\nconst promptText = '\u4ee5\u4e0b\u306eVTuber\u30b0\u30eb\u30fc\u30d7\u30fb\u4e8b\u52d9\u6240\u306b\u3064\u3044\u3066\u65e5\u672c\u8a9e\u3067SEO\u8a18\u4e8b\u3092\u4f5c\u6210\u3057\u3066\u304f\u3060\u3055\u3044\u3002\\n\\n'\n  + '\u30b0\u30eb\u30fc\u30d7\u540d: ' + grp.name\n  + '\\n\u82f1\u8a9e\u540d: ' + (grp.name_en || '')\n  + '\\n\u904b\u55b6\u4f1a\u793e: ' + (grp.grp_agency_name || '\u4e0d\u660e')\n  + '\\n\u7a2e\u5225: ' + (grp.grp_type || '\u4f01\u696d')\n  + '\\n\u8a2d\u7acb\u65e5: ' + (grp.grp_founded_date || '\u4e0d\u660e')\n  + '\\n\u30e1\u30f3\u30d0\u30fc\u6570: ' + (memberCount ? memberCount + '\u540d' : '\u591a\u6570')\n  + '\\n\u5408\u8a08\u767b\u9332\u8005\u6570: ' + (totalSubs ? subsFormatted : '\u975e\u516c\u958b')\n  + '\\n\u516c\u5f0f\u30b5\u30a4\u30c8: ' + (grp.grp_official_website || '\u306a\u3057')\n  + '\\n\u88dc\u8db3\u60c5\u5831: ' + (grp.description_hint || '')\n  + '\\n\\n\u8a18\u4e8b\u69cb\u6210\uff08\u5404\u30bb\u30af\u30b7\u30e7\u30f3\u3092H2\u30bf\u30b0\u3067\u533a\u5207\u308b\uff09:'\n  + '\\n\u2460' + grp.name + '\u3068\u306f\uff1f\u6982\u8981\u30fb\u7279\u5fb4\uff08300\u5b57\u4ee5\u4e0a\uff09'\n  + '\\n\u2461\u6240\u5c5e\u30e1\u30f3\u30d0\u30fc\u30fb\u4ee3\u8868\u7684\u306aVTuber\uff08200\u5b57\u4ee5\u4e0a\uff09'\n  + '\\n\u2462' + grp.name + '\u306e\u6b74\u53f2\u30fb\u8a2d\u7acb\u80cc\u666f\uff08200\u5b57\u4ee5\u4e0a\uff09'\n  + '\\n\u2463\u3069\u3093\u306a\u4eba\u306b\u304a\u3059\u3059\u3081\uff1f\u697d\u3057\u307f\u65b9\uff08150\u5b57\u4ee5\u4e0a\uff09'\n  + '\\n\\n\u51fa\u529b\u5f62\u5f0f: {\"html_content\":\"<h2>...</h2>...\",\"seo_title\":\"...\",\"seo_desc\":\"...\",\"target_kw\":\"...\"}';\n\nlet aiHtml = '', aiTitle = '', aiDesc = '', aiKw = '';\n\ntry {\n  const gemRes = await this.helpers.httpRequest({\n    method: 'POST',\n    url: 'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=' + cfg.gemini_api_key,\n    headers: { 'Content-Type': 'application/json' },\n    body: {\n      systemInstruction: { parts: [{ text: '\u3042\u306a\u305f\u306fVTuber\u30fb\u30a8\u30f3\u30bf\u30e1\u5c02\u9580\u306eSEO\u30e9\u30a4\u30bf\u30fc\u3067\u3059\u3002JSON\u5f62\u5f0f\u306e\u307f\u3067\u51fa\u529b\u3057\u3066\u304f\u3060\u3055\u3044\u3002' }] },\n      contents: [{ role: 'user', parts: [{ text: promptText }] }],\n      generationConfig: { maxOutputTokens: 2000, temperature: 0.7, responseMimeType: 'application/json' }\n    },\n    json: true\n  });\n  const ai = JSON.parse(gemRes.candidates[0].content.parts[0].text.trim());\n  aiHtml  = String(ai.html_content || '');\n  aiTitle = String(ai.seo_title   || '');\n  aiDesc  = String(ai.seo_desc    || '');\n  aiKw    = String(ai.target_kw   || '');\n} catch(e) {\n  aiHtml  = '<h2>' + grp.name + '\u3068\u306f\uff1f</h2><p>' + grp.name + '\u306f' + (grp.grp_agency_name || '') + '\u304c\u904b\u55b6\u3059\u308bVTuber\u30b0\u30eb\u30fc\u30d7\u3067\u3059\u3002</p>';\n  aiTitle = grp.name + ' \u30e1\u30f3\u30d0\u30fc\u30fb\u30d7\u30ed\u30d5\u30a3\u30fc\u30eb\uff5cVTuber\u30b0\u30eb\u30fc\u30d7\u7d39\u4ecb';\n  aiDesc  = grp.name + '\u306e\u30e1\u30f3\u30d0\u30fc\u4e00\u89a7\u30fb\u8a2d\u7acb\u60c5\u5831\u30fb\u7279\u5fb4\u3092\u307e\u3068\u3081\u3066\u3044\u307e\u3059\u3002VTuber\u30b0\u30eb\u30fc\u30d7\u3092\u63a2\u3057\u3066\u3044\u308b\u306a\u3089\u305c\u3072\u30c1\u30a7\u30c3\u30af\u3002';\n  aiKw    = grp.name + ' \u30e1\u30f3\u30d0\u30fc \u30d7\u30ed\u30d5\u30a3\u30fc\u30eb';\n}\n\nconst authHeader = 'Basic ' + encodeBase64(cfg.wp_user + ':' + cfg.wp_app_password);\n\nconst wpBody = {\n  title: grp.name + ' \u30e1\u30f3\u30d0\u30fc\u4e00\u89a7\u30fb\u30d7\u30ed\u30d5\u30a3\u30fc\u30eb',\n  content: aiHtml,\n  status: 'publish',\n  slug: grp.slug,\n  meta: {\n    grp_type:              grp.grp_type || '\u4f01\u696d',\n    grp_founded_date:      grp.grp_founded_date || '',\n    grp_member_count:      memberCount,\n    grp_total_subscribers: totalSubs,\n    grp_official_website:  grp.grp_official_website || '',\n    grp_agency_name:       grp.grp_agency_name || '',\n    _seopress_titles_title:            aiTitle,\n    _seopress_titles_desc:             aiDesc,\n    _seopress_analysis_target_kw:      aiKw,\n    _seopress_social_fb_title:         aiTitle,\n    _seopress_social_fb_desc:          aiDesc,\n    _seopress_social_twitter_title:    aiTitle,\n    _seopress_social_twitter_desc:     aiDesc\n  }\n};\n\nreturn { json: { wpBody, authHeader, wp_url: cfg.wp_url, sheets_id: cfg.sheets_id, slug: grp.slug, name: grp.name } };"
      },
      "id": "wf04-gemini-prepare",
      "name": "Gemini & Prepare WP Body",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -288,
        32
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $json.wp_url }}/wp-json/wp/v2/vtuber_groups",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "={{ $json.authHeader }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "contentType": "raw",
        "rawContentType": "application/json",
        "body": "={{ JSON.stringify($json.wpBody) }}",
        "options": {
          "timeout": 30000
        }
      },
      "id": "wf04-create-post",
      "name": "Create WP Post",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        160,
        32
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const wpRes = $input.item.json;\nconst prev  = $('Gemini & Prepare WP Body').item.json;\nreturn { json: { slug: prev.slug, dir_status: 'published', dir_post_id: String(wpRes.id), published_url: wpRes.link, sheets_id: prev.sheets_id, timestamp: new Date().toISOString() } };"
      },
      "id": "wf04-extract-response",
      "name": "Extract WP Response",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        368,
        32
      ]
    },
    {
      "parameters": {
        "operation": "update",
        "documentId": {
          "__rl": true,
          "value": "={{ $json.sheets_id }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "groups_db",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "autoMapInputData",
          "value": {},
          "matchingColumns": [
            "slug"
          ],
          "schema": [
            {
              "id": "slug",
              "displayName": "slug",
              "defaultMatch": true
            },
            {
              "id": "dir_status",
              "displayName": "dir_status"
            },
            {
              "id": "dir_post_id",
              "displayName": "dir_post_id"
            },
            {
              "id": "published_url",
              "displayName": "published_url"
            }
          ]
        },
        "options": {}
      },
      "id": "wf04-update-sheet",
      "name": "Update groups_db",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        592,
        32
      ]
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "={{ $('Extract WP Response').item.json.sheets_id }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "logs",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "timestamp": "={{ $('Extract WP Response').item.json.timestamp }}",
            "workflow": "WF04-groups-post",
            "keyword_or_slug": "={{ $('Extract WP Response').item.json.slug }}",
            "status": "published",
            "error_message": "",
            "retry_count": "0"
          },
          "schema": [
            {
              "id": "timestamp"
            },
            {
              "id": "workflow"
            },
            {
              "id": "keyword_or_slug"
            },
            {
              "id": "status"
            },
            {
              "id": "error_message"
            },
            {
              "id": "retry_count"
            }
          ]
        },
        "options": {}
      },
      "id": "wf04-append-log",
      "name": "Append Log",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        816,
        32
      ]
    }
  ],
  "connections": {
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Read Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Config": {
      "main": [
        [
          {
            "node": "Parse Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Config": {
      "main": [
        [
          {
            "node": "Read groups_db",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read groups_db": {
      "main": [
        [
          {
            "node": "Filter Pending",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Pending": {
      "main": [
        [
          {
            "node": "Has Pending?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has Pending?": {
      "main": [
        [
          {
            "node": "Gemini & Prepare WP Body",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    },
    "Gemini & Prepare WP Body": {
      "main": [
        [
          {
            "node": "Create WP Post",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create WP Post": {
      "main": [
        [
          {
            "node": "Extract WP Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract WP Response": {
      "main": [
        [
          {
            "node": "Update groups_db",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update groups_db": {
      "main": [
        [
          {
            "node": "Append Log",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  }
}