{
  "id": "D7YEcQ01Iz9rYzrG",
  "name": "Get Douyin video comment replies with JustOneAPI",
  "tags": [],
  "nodes": [
    {
      "id": "48a719d5-9648-44bc-9336-516082dd7558",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        992,
        704
      ],
      "parameters": {
        "width": 480,
        "height": 496,
        "content": "## Get Douyin video comment replies with JustOneAPI\n\n### How it works\n\n1. Trigger workflow manually\n2. Prepare API and comment parameters\n3. Fetch Douyin video comments\n4. Select first comment with replies\n5. Check if comment has ID\n6. If yes: fetch and process replies\n7. If no: build empty reply output\n8. Output final results\n\n### Setup steps\n\n- - [ ] Configure JustOneAPI credentials in HTTP Request nodes\n- - [ ] Set Douyin video ID in 'Prepare API and Comment Inputs' node\n- - [ ] Verify API endpoints in both HTTP Request nodes\n\n### Customization\n\nYou can modify the comment selection logic in 'Select First Comment With Replies' to handle different reply retrieval scenarios."
      },
      "typeVersion": 1
    },
    {
      "id": "44c2df53-28dc-4621-bf61-6ce7cded00c4",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1552,
        704
      ],
      "parameters": {
        "color": 7,
        "height": 112,
        "content": "## Workflow Trigger\n\nInitial manual trigger to start the workflow"
      },
      "typeVersion": 1
    },
    {
      "id": "e9dd128e-2852-44cb-8b0c-07ea56675383",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1872,
        704
      ],
      "parameters": {
        "color": 7,
        "height": 112,
        "content": "## Input Preparation\n\nPrepares API parameters and comment selection criteria"
      },
      "typeVersion": 1
    },
    {
      "id": "5a43d51a-e7da-4f21-8526-1a1be52c4798",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2192,
        704
      ],
      "parameters": {
        "color": 7,
        "width": 304,
        "height": 112,
        "content": "## Fetch Douyin Comments\n\nMakes API call to retrieve video comments"
      },
      "typeVersion": 1
    },
    {
      "id": "d9d6b52b-202a-4050-ab8d-a11c476554c1",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2544,
        704
      ],
      "parameters": {
        "color": 7,
        "width": 288,
        "height": 112,
        "content": "## Comment Selection\n\nIdentifies first comment with replies for further processing"
      },
      "typeVersion": 1
    },
    {
      "id": "5d812d36-1a96-4651-8c52-447e96345004",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2880,
        704
      ],
      "parameters": {
        "color": 7,
        "width": 272,
        "height": 112,
        "content": "## Comment Validation\n\nChecks if selected comment has valid ID for reply retrieval"
      },
      "typeVersion": 1
    },
    {
      "id": "7b60d774-fa86-43e7-a91d-8a2084c774d5",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3216,
        704
      ],
      "parameters": {
        "color": 7,
        "width": 816,
        "height": 112,
        "content": "## Reply Processing Path\n\nMain path for fetching and processing comment replies"
      },
      "typeVersion": 1
    },
    {
      "id": "59f9fc21-2fe7-4f11-a3a4-f1500365af51",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3216,
        1136
      ],
      "parameters": {
        "color": 7,
        "width": 496,
        "height": 96,
        "content": "## No Reply Path\n\nAlternative path when no valid comment ID is found"
      },
      "typeVersion": 1
    },
    {
      "id": "4b5267f5-8aa0-4d9d-9bdd-a3f0ce94a508",
      "name": "Sticky Note8",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2544,
        1120
      ],
      "parameters": {
        "color": 7,
        "width": 400,
        "height": 112,
        "content": "## Raw Data Outputs\n\nOptional outputs for raw API responses (debugging)"
      },
      "typeVersion": 1
    },
    {
      "id": "f951678b-ac21-49d7-9907-d98f1d3f2db7",
      "name": "Manual Execution Trigger",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        1616,
        928
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "5b863797-b634-4adf-9a23-5d6bda3692f0",
      "name": "Prepare API and Comment Data",
      "type": "n8n-nodes-base.set",
      "position": [
        1936,
        928
      ],
      "parameters": {
        "mode": "raw",
        "options": {},
        "jsonOutput": "{\n  \"baseUrl\": \"https://api.justoneapi.com\",\n  \"token\": \"YOUR_JUSTONEAPI_TOKEN\",\n  \"awemeId\": \"YOUR_DOUYIN_AWEME_ID\",\n  \"commentPage\": 1,\n  \"replyPage\": 1\n}"
      },
      "typeVersion": 3.4
    },
    {
      "id": "4010f7b9-a6d2-41e0-86fb-dfa04ddf07a7",
      "name": "Fetch Video Comments via API",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2272,
        928
      ],
      "parameters": {
        "url": "={{$json.baseUrl + '/api/douyin/get-video-comment/v1'}}",
        "options": {
          "timeout": 60000,
          "redirect": {
            "redirect": {}
          }
        },
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "token",
              "value": "={{$json.token}}"
            },
            {
              "name": "awemeId",
              "value": "={{$json.awemeId}}"
            },
            {
              "name": "page",
              "value": "={{$json.commentPage}}"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "b649b6f9-fc7f-405c-85ce-53a60b89ca51",
      "name": "Output Raw Comments Data",
      "type": "n8n-nodes-base.set",
      "position": [
        2592,
        1312
      ],
      "parameters": {
        "mode": "raw",
        "options": {},
        "jsonOutput": "={{$json}}"
      },
      "typeVersion": 3.4
    },
    {
      "id": "a0182f3d-5890-4cce-944b-37df40b961c9",
      "name": "Select Comment with Replies",
      "type": "n8n-nodes-base.code",
      "position": [
        2592,
        928
      ],
      "parameters": {
        "jsCode": "const cfg = $('Prepare API and Comment Data').first().json;\nconst raw = $json;\n\nfunction firstImageUrl(image) {\n  return Array.isArray(image?.url_list) ? image.url_list[0] || null : null;\n}\n\nconst root = Array.isArray(raw) ? raw[0] : raw;\nconst data = root?.data || {};\nconst comments = Array.isArray(data.comments) ? data.comments : [];\n\nfunction cleanUser(user = {}) {\n  return {\n    uid: user.uid ?? null,\n    shortId: user.short_id ?? null,\n    secUid: user.sec_uid ?? null,\n    nickname: user.nickname ?? null,\n    signature: user.signature ?? null,\n    uniqueId: user.unique_id ?? null,\n    avatarThumb: firstImageUrl(user.avatar_thumb),\n    avatarMedium: firstImageUrl(user.avatar_medium),\n    isVerified: user.is_verified ?? null,\n    followStatus: user.follow_status ?? null,\n    followerCount: user.follower_count ?? null,\n    customVerify: user.custom_verify ?? null,\n    enterpriseVerifyReason: user.enterprise_verify_reason ?? null,\n    verificationType: user.verification_type ?? null,\n    region: user.region ?? null\n  };\n}\n\nfunction cleanTopLevelComment(comment) {\n  return {\n    commentId: comment.cid ?? null,\n    text: comment.text ?? null,\n    awemeId: comment.aweme_id ?? null,\n    createTime: comment.create_time ?? null,\n    likeCount: comment.digg_count ?? null,\n    status: comment.status ?? null,\n    replyCommentTotal: comment.reply_comment_total ?? null,\n    replyId: comment.reply_id ?? null,\n    replyToReplyId: comment.reply_to_reply_id ?? null,\n    isAuthorDigged: comment.is_author_digged ?? null,\n    userDigged: comment.user_digged ?? null,\n    isHot: comment.is_hot ?? null,\n    labelText: comment.label_text ?? null,\n    labelType: comment.label_type ?? null,\n    contentType: comment.content_type ?? null,\n    imageList: comment.image_list ?? null,\n    user: cleanUser(comment.user)\n  };\n}\n\nconst cleanedComments = comments.map(cleanTopLevelComment);\nconst selectedComment = cleanedComments.find(comment => Number(comment.replyCommentTotal || 0) > 0) || null;\n\nreturn [{\n  json: {\n    baseUrl: cfg.baseUrl,\n    token: cfg.token,\n    awemeId: cfg.awemeId,\n    commentPage: cfg.commentPage,\n    replyPage: cfg.replyPage,\n    commentsMeta: {\n      statusCode: data.status_code ?? null,\n      cursor: data.cursor ?? null,\n      hasMore: data.has_more ?? null,\n      total: data.total ?? null,\n      replyStyle: data.reply_style ?? null,\n      hotsoonFilteredCount: data.hotsoon_filtered_count ?? null,\n      foldedCommentCount: data.folded_comment_count ?? null,\n      logId: data.extra?.logid ?? null,\n      imprId: data.log_pb?.impr_id ?? null\n    },\n    totalCommentsReturned: cleanedComments.length,\n    hasCommentWithReplies: Boolean(selectedComment?.commentId),\n    commentId: selectedComment?.commentId || null,\n    selectedComment,\n    comments: cleanedComments,\n    rawCommentsResponse: raw\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "7ae06198-2dc1-4eb1-95be-5c65875fa88f",
      "name": "Check for Comment ID",
      "type": "n8n-nodes-base.if",
      "position": [
        2928,
        928
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "hasCommentIdCondition",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{$json.commentId}}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "82e6e947-fdd3-41c9-8121-55491a87a7f7",
      "name": "Fetch Comment Replies via API",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        3264,
        832
      ],
      "parameters": {
        "url": "={{$json.baseUrl + '/api/douyin/get-video-sub-comment/v1'}}",
        "options": {
          "timeout": 60000,
          "redirect": {
            "redirect": {}
          }
        },
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "token",
              "value": "={{$json.token}}"
            },
            {
              "name": "commentId",
              "value": "={{$json.commentId}}"
            },
            {
              "name": "page",
              "value": "={{$json.replyPage}}"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "d078f3a9-14bc-46a7-b213-33ebb052e087",
      "name": "Output Raw Replies Data",
      "type": "n8n-nodes-base.set",
      "position": [
        3568,
        992
      ],
      "parameters": {
        "mode": "raw",
        "options": {},
        "jsonOutput": "={{$json}}"
      },
      "typeVersion": 3.4
    },
    {
      "id": "d9f31eae-ea1f-4333-a663-459c700b976e",
      "name": "Build Final Reply Data",
      "type": "n8n-nodes-base.code",
      "position": [
        3568,
        832
      ],
      "parameters": {
        "jsCode": "const selectedData = $('Select Comment with Replies').first().json;\nconst rawReplies = $json;\n\nfunction firstImageUrl(image) {\n  return Array.isArray(image?.url_list) ? image.url_list[0] || null : null;\n}\n\nconst root = Array.isArray(rawReplies) ? rawReplies[0] : rawReplies;\nconst data = root?.data || {};\nconst replies = Array.isArray(data.comments) ? data.comments : [];\n\nfunction cleanUser(user = {}) {\n  return {\n    uid: user.uid ?? null,\n    shortId: user.short_id ?? null,\n    secUid: user.sec_uid ?? null,\n    nickname: user.nickname ?? null,\n    signature: user.signature ?? null,\n    uniqueId: user.unique_id ?? null,\n    avatarThumb: firstImageUrl(user.avatar_thumb),\n    avatarMedium: firstImageUrl(user.avatar_medium),\n    isVerified: user.is_verified ?? null,\n    followStatus: user.follow_status ?? null,\n    followerCount: user.follower_count ?? null,\n    customVerify: user.custom_verify ?? null,\n    enterpriseVerifyReason: user.enterprise_verify_reason ?? null,\n    verificationType: user.verification_type ?? null,\n    region: user.region ?? null\n  };\n}\n\nfunction cleanReply(reply) {\n  return {\n    replyId: reply.cid ?? null,\n    text: reply.text ?? null,\n    awemeId: reply.aweme_id ?? null,\n    rootCommentId: reply.root_comment_id ?? null,\n    parentCommentId: reply.reply_id ?? null,\n    replyToReplyId: reply.reply_to_reply_id ?? null,\n    level: reply.level ?? null,\n    createTime: reply.create_time ?? null,\n    likeCount: reply.digg_count ?? null,\n    status: reply.status ?? null,\n    commentReplyTotal: reply.comment_reply_total ?? null,\n    userDigged: reply.user_digged ?? null,\n    isAuthorDigged: reply.is_author_digged ?? null,\n    isHot: reply.is_hot ?? null,\n    labelText: reply.label_text ?? null,\n    labelType: reply.label_type ?? null,\n    contentType: reply.content_type ?? null,\n    isFolded: reply.is_folded ?? null,\n    imageList: reply.image_list ?? null,\n    user: cleanUser(reply.user)\n  };\n}\n\nconst cleanedReplies = replies.map(cleanReply);\n\nreturn [{\n  json: {\n    awemeId: selectedData.awemeId,\n    commentPage: selectedData.commentPage,\n    replyPage: selectedData.replyPage,\n    hasCommentWithReplies: selectedData.hasCommentWithReplies,\n    commentsMeta: selectedData.commentsMeta,\n    selectedComment: selectedData.selectedComment,\n    repliesMeta: {\n      statusCode: data.status_code ?? null,\n      cursor: data.cursor ?? null,\n      mergeCursor: data.merge_cursor ?? null,\n      hasMore: data.has_more ?? null,\n      total: data.total ?? null,\n      logId: data.extra?.logid ?? null,\n      imprId: data.log_pb?.impr_id ?? null\n    },\n    totalCommentsReturned: selectedData.totalCommentsReturned,\n    replyCountReturned: cleanedReplies.length,\n    comments: selectedData.comments,\n    replies: cleanedReplies,\n    rawCommentsResponse: selectedData.rawCommentsResponse,\n    rawRepliesResponse: rawReplies\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "d105af4d-63f9-43bf-9dfa-7a728cca3b92",
      "name": "Output Final Reply Data",
      "type": "n8n-nodes-base.set",
      "position": [
        3888,
        832
      ],
      "parameters": {
        "mode": "raw",
        "options": {},
        "jsonOutput": "={{$json}}"
      },
      "typeVersion": 3.4
    },
    {
      "id": "300640fe-57a6-4af1-8915-4dd0207cd859",
      "name": "Build No Replies Data",
      "type": "n8n-nodes-base.code",
      "position": [
        3264,
        1248
      ],
      "parameters": {
        "jsCode": "const data = $json;\n\nreturn [{\n  json: {\n    awemeId: data.awemeId,\n    commentPage: data.commentPage,\n    replyPage: data.replyPage,\n    hasCommentWithReplies: false,\n    message: 'No top-level comment with reply_comment_total greater than 0 was found in the returned comments page. Review the raw comments response or try another commentPage value.',\n    commentsMeta: data.commentsMeta,\n    totalCommentsReturned: data.totalCommentsReturned,\n    comments: data.comments,\n    rawCommentsResponse: data.rawCommentsResponse\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "c1546f43-c060-4913-a61c-0d9eaf082d65",
      "name": "Output No Replies Data",
      "type": "n8n-nodes-base.set",
      "position": [
        3568,
        1248
      ],
      "parameters": {
        "mode": "raw",
        "options": {},
        "jsonOutput": "={{$json}}"
      },
      "typeVersion": 3.4
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1"
  },
  "versionId": "501d481b-d4fb-4a98-9081-93464366b2d0",
  "connections": {
    "Check for Comment ID": {
      "main": [
        [
          {
            "node": "Fetch Comment Replies via API",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Build No Replies Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build No Replies Data": {
      "main": [
        [
          {
            "node": "Output No Replies Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Final Reply Data": {
      "main": [
        [
          {
            "node": "Output Final Reply Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Manual Execution Trigger": {
      "main": [
        [
          {
            "node": "Prepare API and Comment Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Select Comment with Replies": {
      "main": [
        [
          {
            "node": "Check for Comment ID",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Video Comments via API": {
      "main": [
        [
          {
            "node": "Select Comment with Replies",
            "type": "main",
            "index": 0
          },
          {
            "node": "Output Raw Comments Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare API and Comment Data": {
      "main": [
        [
          {
            "node": "Fetch Video Comments via API",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Comment Replies via API": {
      "main": [
        [
          {
            "node": "Build Final Reply Data",
            "type": "main",
            "index": 0
          },
          {
            "node": "Output Raw Replies Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}