{"record":{"id":"a4d877a6d90da3b1","repo":"yikart/AiToEarn","slug":"error-response-data-error-message-a4d877","errorCode":null,"errorMessage":"获取评论失败: ${error.response?.data?.error?.message || error.message}","messagePattern":"获取评论失败: (.+?)","errorType":"exception","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"project/aitoearn-electron/server/src/modules/plat/tiktok/tiktok.service.ts","lineNumber":778,"sourceCode":"      };\n\n      if (cursor) {\n        params.cursor = cursor;\n      }\n\n      const { data } = await firstValueFrom(\n        this.httpService.get(`${this.apiBaseUrl}/v2/comment/list/`, {\n          params,\n          headers: {\n            'Authorization': `Bearer ${accessToken}`\n          }\n        })\n      );\n\n      return data.data;\n    } catch (error) {\n      this.logger.error('获取TikTok视频评论失败:', error.response?.data || error.message);\n      throw new BadRequestException(`获取评论失败: ${error.response?.data?.error?.message || error.message}`);\n    }\n  }\n\n  /**\n   * 发表评论\n   * @param accessToken 访问令牌\n   * @param commentDto 评论参数\n   * @returns 评论结果\n   */\n  async postComment(\n    accessToken: string,\n    commentDto: TikTokCommentDto\n  ): Promise<any> {\n    try {\n      const { data } = await firstValueFrom(\n        this.httpService.post(`${this.apiBaseUrl}/v2/comment/post/`, {\n          video_id: commentDto.videoId,\n          text: commentDto.text","sourceCodeStart":760,"sourceCodeEnd":796,"githubUrl":"https://github.com/yikart/AiToEarn/blob/d3aa8bea5b146a8675607cf0144d891aad3e9683/project/aitoearn-electron/server/src/modules/plat/tiktok/tiktok.service.ts#L760-L796","documentation":"The catch-all of getVideoComments (tiktok.service.ts:776-779). The GET to /v2/comment/list/ failed, and the Axios error is re-wrapped as BadRequestException('获取评论失败: ...') with TikTok's error message when present. This service surface requires the comment.list scope and the video must belong to the token's user.","triggerScenarios":"GET /v2/comment/list/ returns 4xx/5xx: missing comment.list scope, video_id not owned by the user, invalid cursor from a stale pagination, invalid max_count outside 1-50, or expired token / network failure.","commonSituations":"App approved without comment read scopes; comment APIs unavailable in the app's region or for the video's privacy setting; stale cursors after comments were deleted; calling for videos published by other users.","solutions":["Check the '获取TikTok视频评论失败:' log for TikTok's error code (e.g. access token invalid, permission denied).","Ensure the OAuth flow requested comment.list (and comment.list.generate for replies) and re-authenticate.","Verify video_id belongs to the token's user; comments on others' videos are not listable via this API.","Reset pagination to omit cursor on the first call and always use fresh cursors from the previous response.","For 429, back off — the comment list endpoint is rate-limited."],"exampleFix":"null","handlingStrategy":"validation","validationCode":"// Ensure scope before calling; TikTok exposes no scope-check endpoint per user,\n// so gate at token storage time:\nconst required = ['comment.list'];\nconst granted = storedToken.scopes ?? [];\nif (!required.every(s => granted.includes(s))) {\n  throw new Error(`missing scopes: ${required.filter(s => !granted.includes(s)).join(',')}`);\n}","typeGuard":"function isCommentListPayload(d: unknown): d is { comments?: unknown[]; has_more?: boolean; cursor?: string } {\n  const o = d as any;\n  return o === null || typeof o === 'object';\n}","tryCatchPattern":"try {\n  const comments = await tiktokService.getVideoComments(token, videoId);\n} catch (e) {\n  if (/permission|scope|access_token/i.test(e.message)) {\n    // trigger OAuth re-consent with comment.list included\n  } else if ((e as any).response?.status === 429) {\n    await backoffAndRetry(() => tiktokService.getVideoComments(token, videoId));\n  } else throw e;\n}","preventionTips":["Request comment.list scope during OAuth and store the granted scope list.","Only list comments for videos owned by the token's user.","Always pass fresh cursors from the previous response, never stale ones.","Respect rate limits: keep max_count within 1-50 and space out calls."],"tags":["tiktok","comments","http-client","permissions"],"backgroundTag":"insufficient-api-scope","analyzedSha":"d3aa8bea5b146a8675607cf0144d891aad3e9683","analyzedAt":"2026-08-31T14:19:24.185Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}