{
  "name": "MADIAD Hub - Hẹn giờ đăng bài",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes",
              "minutesInterval": 5
            }
          ]
        }
      },
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        -160,
        0
      ],
      "id": "Chạy mỗi 5 phút",
      "name": "Chạy mỗi 5 phút"
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "value": "",
          "mode": "list",
          "cachedResultName": "[MADIAD Template] Lịch Đăng Hub"
        },
        "sheetName": {
          "__rl": true,
          "value": "Cấu Hình",
          "mode": "name"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        60,
        0
      ],
      "id": "Đọc Cấu Hình",
      "name": "Đọc Cấu Hình"
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "value": "",
          "mode": "list",
          "cachedResultName": "[MADIAD Template] Lịch Đăng Hub"
        },
        "sheetName": {
          "__rl": true,
          "value": "Lịch Đăng",
          "mode": "name"
        },
        "options": {
          "dataLocationOnSheet": {
            "values": {
              "rangeDefinition": "specifyRange",
              "headerRow": 1,
              "firstDataRow": 3
            }
          }
        }
      },
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        280,
        0
      ],
      "id": "Đọc Lịch Đăng",
      "name": "Đọc Lịch Đăng"
    },
    {
      "parameters": {
        "jsCode": "\nconst cfg = {};\nfor (const it of $('Đọc Cấu Hình').all()) {\n  const k = String(it.json['Khoá'] ?? '').trim();\n  if (k) cfg[k] = String(it.json['Giá Trị'] ?? '').trim();\n}\nif (!cfg.profile_id) throw new Error('Chưa điền profile_id ở tab Cấu Hình. Lấy trong Dashboard MADIAD Hub.');\nconst KENH = { 'Facebook': 'facebook', 'Instagram': 'instagram', 'TikTok': 'tiktok', 'YouTube': 'youtube', 'LinkedIn': 'linkedin', 'X': 'x', 'Threads': 'threads', 'Pinterest': 'pinterest', 'Reddit': 'reddit', 'Bluesky': 'bluesky', 'Google Business': 'google_business', 'Telegram': 'telegram', 'Discord': 'discord', 'Slack': 'slack', 'Mastodon': 'mastodon', 'Nostr': 'nostr', 'Lemmy': 'lemmy', 'dev.to': 'devto', 'Hashnode': 'hashnode', 'WordPress': 'wordpress', 'Whop': 'whop', 'listmonk': 'listmonk' };\nconst IMG = ['.jpg', '.jpeg', '.png', '.webp', '.gif'];\nconst VID = ['.mp4', '.mov', '.m4v', '.webm'];\nconst NEEDS_TITLE = ['youtube', 'reddit', 'pinterest'];\nconst OPEN = ['', 'Nháp', 'Chờ Đăng'];\n\nfunction buildPost(r, cfg, KENH, IMG, VID, NEEDS_TITLE, execId) {\n  const caption = String(r['Nội Dung'] ?? '').trim();\n  const raw = String(r['Kênh Đăng'] ?? '').trim();\n  // Dòng chưa soạn xong thì bỏ qua im lặng, không coi là lỗi.\n  if (!caption || !raw) return null;\n\n  const platforms = raw.split(',').map(s => KENH[s.trim()]).filter(Boolean);\n  if (!platforms.length) return { _row: r.row_number, _err: 'Cột Kênh Đăng không khớp kênh nào. Chọn lại từ dropdown.' };\n\n  const links = String(r['Media'] ?? '').split(/[\\n,]/).map(s => s.trim()).filter(Boolean);\n  const bad = links.filter(l => !l.startsWith('https://'));\n  if (bad.length) return { _row: r.row_number, _err: 'Link media phải bắt đầu bằng https:// , đang là: ' + bad[0] };\n\n  const low = links.map(l => l.toLowerCase());\n  let endpoint = 'text';\n  if (links.length) endpoint = low.some(l => VID.some(e => l.includes(e))) ? 'video' : 'photos';\n\n  const body = { profile_id: cfg.profile_id, platforms, caption };\n  const title = String(r['Tiêu Đề'] ?? '').trim();\n  if (title) for (const p of platforms) if (NEEDS_TITLE.includes(p)) body[p + '_title'] = title;\n  const fc = String(r['Bình Luận Đầu'] ?? '').trim();\n  if (fc) body.first_comment = fc;\n  if (platforms.includes('pinterest')) {\n    if (!cfg.pinterest_board_id) return { _row: r.row_number, _err: 'Đăng Pinterest cần pinterest_board_id ở tab Cấu Hình.' };\n    body.pinterest_board_id = cfg.pinterest_board_id;\n  }\n  if (endpoint === 'video') body.video_url = links[0];\n  if (endpoint === 'photos') body.photo_urls = links;\n\n  // Mã Bài là chìa khoá chống đăng trùng. Sinh TRƯỚC khi gọi API và ghi ngay xuống sheet, để một lần chạy lại không bao giờ tạo chìa khác cho cùng một bài.\n  const id = String(r['Mã Bài'] ?? '').trim() || ('P-' + execId + '-' + r.row_number);\n  return { _row: r.row_number, _id: id, _endpoint: endpoint, body };\n}\n\n// Lịch do chính n8n của bạn giữ, không đẩy sang máy chủ. Lý do: job hẹn giờ phía máy chủ bỏ qua lựa chọn Trang Facebook, nên tài khoản nhiều Trang sẽ bị đăng nhầm chỗ mà không báo gì.\nconst tz = cfg.timezone || 'Asia/Ho_Chi_Minh';\nconst p2 = (x) => String(x).padStart(2, '0');\nfunction key(s) {\n  s = String(s ?? '').trim(); if (!s) return null;\n  let m = s.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})[ T](\\d{1,2}):(\\d{2})/);\n  if (m) return m[1] + p2(m[2]) + p2(m[3]) + p2(m[4]) + m[5];\n  m = s.match(/^(\\d{1,2})\\/(\\d{1,2})\\/(\\d{4})[ ,]+(\\d{1,2}):(\\d{2})/);\n  if (m) return m[3] + p2(m[2]) + p2(m[1]) + p2(m[4]) + m[5];\n  return null;\n}\nlet now;\ntry { now = DateTime.now().setZone(tz).toFormat('yyyyMMddHHmm'); }\ncatch (e) {\n  const d = new Date(Date.now() + 7 * 3600 * 1000);\n  now = d.getUTCFullYear() + p2(d.getUTCMonth() + 1) + p2(d.getUTCDate()) + p2(d.getUTCHours()) + p2(d.getUTCMinutes());\n}\nconst perRun = parseInt(cfg.rows_per_run || '10', 10) || 10;\nconst out = [];\nfor (const it of $input.all()) {\n  if (out.length >= perRun) break;\n  const r = it.json;\n  if (String(r['Hành Động'] ?? '').trim() !== 'Hẹn giờ') continue;\n  if (!OPEN.includes(String(r['Trạng Thái'] ?? '').trim())) continue;\n  const k = key(r['Hẹn Giờ']);\n  if (!k) { out.push({ json: { _row: r.row_number, _err: 'Cột Hẹn Giờ không đọc được. Dùng dạng 2026-09-10 08:30 và để ô ở định dạng Văn bản.' } }); continue; }\n  if (k > now) continue;\n  const b = buildPost(r, cfg, KENH, IMG, VID, NEEDS_TITLE, $execution.id);\n  if (b) out.push({ json: b });\n}\nreturn out;\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        500,
        0
      ],
      "id": "Chuẩn bị bài đăng",
      "name": "Chuẩn bị bài đăng"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "version": 2
          },
          "conditions": [
            {
              "id": "c",
              "operator": {
                "type": "string",
                "operation": "notExists"
              },
              "leftValue": "={{ $json._err }}",
              "rightValue": ""
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [
        720,
        0
      ],
      "id": "Dòng hợp lệ?",
      "name": "Dòng hợp lệ?"
    },
    {
      "parameters": {
        "operation": "update",
        "documentId": {
          "__rl": true,
          "value": "",
          "mode": "list",
          "cachedResultName": "[MADIAD Template] Lịch Đăng Hub"
        },
        "sheetName": {
          "__rl": true,
          "value": "Lịch Đăng",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "row_number": "={{ $json._row }}",
            "Mã Bài": "={{ $json._id }}",
            "Trạng Thái": "Đang Đăng",
            "Lỗi": ""
          },
          "matchingColumns": [
            "row_number"
          ],
          "schema": []
        },
        "options": {
          "dataLocationOnSheet": {
            "values": {
              "rangeDefinition": "specifyRange",
              "headerRow": 1,
              "firstDataRow": 3
            }
          }
        }
      },
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        940,
        -120
      ],
      "id": "Đánh dấu Đang Đăng",
      "name": "Đánh dấu Đang Đăng",
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://api.madiad.com/v1/posts/{{ $('Chuẩn bị bài đăng').item.json._endpoint }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "X-Madiad-Source",
              "value": "tpl-schedule-at-v1"
            },
            {
              "name": "Idempotency-Key",
              "value": "={{ $('Chuẩn bị bài đăng').item.json._id }}"
            }
          ]
        },
        "options": {
          "response": {
            "response": {
              "neverError": true,
              "fullResponse": true
            }
          }
        },
        "sendBody": true,
        "contentType": "json",
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify($('Chuẩn bị bài đăng').item.json.body) }}"
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [
        1160,
        -120
      ],
      "id": "Gọi MADIAD Hub",
      "name": "Gọi MADIAD Hub",
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "version": 2
          },
          "conditions": [
            {
              "id": "c",
              "operator": {
                "type": "number",
                "operation": "lt"
              },
              "leftValue": "={{ $json.statusCode }}",
              "rightValue": 300
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [
        1380,
        -120
      ],
      "id": "Đăng được không?",
      "name": "Đăng được không?"
    },
    {
      "parameters": {
        "operation": "update",
        "documentId": {
          "__rl": true,
          "value": "",
          "mode": "list",
          "cachedResultName": "[MADIAD Template] Lịch Đăng Hub"
        },
        "sheetName": {
          "__rl": true,
          "value": "Lịch Đăng",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "row_number": "={{ $('Chuẩn bị bài đăng').item.json._row }}",
            "Trạng Thái": "={{ $json.body?.request_id ? 'Đang Đăng' : 'Đã Đăng' }}",
            "Mã Yêu Cầu": "={{ $json.body?.request_id ?? '' }}",
            "Link Bài Đăng": "={{ Object.entries($json.body?.results ?? {}).map(([k,v]) => k + ': ' + (v?.url ?? (v?.success === false ? ('hỏng, ' + (v?.error ?? '')) : 'đã nhận'))).join('\\n') }}",
            "Mã Bài Đăng": "={{ Object.entries($json.body?.results ?? {}).filter(([,v]) => v?.publish_id).map(([k,v]) => k + '=' + v.publish_id).join(';') }}",
            "Đăng Lúc": "={{ $json.body?.request_id ? '' : $now.setZone('Asia/Ho_Chi_Minh').toFormat('dd/MM/yyyy HH:mm') }}",
            "Lỗi": ""
          },
          "matchingColumns": [
            "row_number"
          ],
          "schema": []
        },
        "options": {
          "dataLocationOnSheet": {
            "values": {
              "rangeDefinition": "specifyRange",
              "headerRow": 1,
              "firstDataRow": 3
            }
          }
        }
      },
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        1600,
        -220
      ],
      "id": "Ghi Kết Quả",
      "name": "Ghi Kết Quả",
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "operation": "update",
        "documentId": {
          "__rl": true,
          "value": "",
          "mode": "list",
          "cachedResultName": "[MADIAD Template] Lịch Đăng Hub"
        },
        "sheetName": {
          "__rl": true,
          "value": "Lịch Đăng",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "row_number": "={{ $('Chuẩn bị bài đăng').item.json._row }}",
            "Trạng Thái": "Lỗi",
            "Lỗi": "={{ $json.body?.error?.message ?? ('HTTP ' + ($json.statusCode ?? '?')) }}"
          },
          "matchingColumns": [
            "row_number"
          ],
          "schema": []
        },
        "options": {
          "dataLocationOnSheet": {
            "values": {
              "rangeDefinition": "specifyRange",
              "headerRow": 1,
              "firstDataRow": 3
            }
          }
        }
      },
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        1600,
        -20
      ],
      "id": "Ghi Lỗi",
      "name": "Ghi Lỗi",
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "operation": "update",
        "documentId": {
          "__rl": true,
          "value": "",
          "mode": "list",
          "cachedResultName": "[MADIAD Template] Lịch Đăng Hub"
        },
        "sheetName": {
          "__rl": true,
          "value": "Lịch Đăng",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "row_number": "={{ $json._row }}",
            "Trạng Thái": "Lỗi",
            "Lỗi": "={{ $json._err }}"
          },
          "matchingColumns": [
            "row_number"
          ],
          "schema": []
        },
        "options": {
          "dataLocationOnSheet": {
            "values": {
              "rangeDefinition": "specifyRange",
              "headerRow": 1,
              "firstDataRow": 3
            }
          }
        }
      },
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        940,
        140
      ],
      "id": "Ghi Lỗi Dữ Liệu",
      "name": "Ghi Lỗi Dữ Liệu",
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "content": "## MADIAD Hub - Hẹn giờ đăng bài\n\n**Ba việc trước khi bật:**\n1. Chọn Google Sheet của bạn ở TẤT CẢ các node Google Sheets.\n2. Tạo credential **Header Auth** tên `MADIAD Hub`:\n   Name `Authorization` , Value `Bearer <API key của bạn>`\n   rồi chọn nó ở node gọi API.\n3. Điền `profile_id` ở tab Cấu Hình của sheet.\n\nLịch do chính n8n của bạn giữ, không đẩy sang máy chủ. Lý do: job hẹn giờ phía máy chủ bỏ qua lựa chọn Trang Facebook, nên tài khoản nhiều Trang sẽ bị đăng nhầm chỗ mà không báo gì.",
        "height": 470,
        "width": 440,
        "color": 5
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -620,
        -200
      ],
      "id": "Hướng dẫn",
      "name": "Hướng dẫn"
    }
  ],
  "connections": {
    "Chạy mỗi 5 phút": {
      "main": [
        [
          {
            "node": "Đọc Cấu Hình",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Đọc Cấu Hình": {
      "main": [
        [
          {
            "node": "Đọc Lịch Đăng",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Đọc Lịch Đăng": {
      "main": [
        [
          {
            "node": "Chuẩn bị bài đăng",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Chuẩn bị bài đăng": {
      "main": [
        [
          {
            "node": "Dòng hợp lệ?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Dòng hợp lệ?": {
      "main": [
        [
          {
            "node": "Đánh dấu Đang Đăng",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Ghi Lỗi Dữ Liệu",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Đánh dấu Đang Đăng": {
      "main": [
        [
          {
            "node": "Gọi MADIAD Hub",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gọi MADIAD Hub": {
      "main": [
        [
          {
            "node": "Đăng được không?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Đăng được không?": {
      "main": [
        [
          {
            "node": "Ghi Kết Quả",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Ghi Lỗi",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "pinData": {},
  "meta": {
    "templateCredsSetupCompleted": false
  }
}