{"record":{"id":"df9e14663203031c","repo":"yikart/AiToEarn","slug":"error-response-data-error-message-df9e14","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":104,"sourceCode":"    videoId: string\n  ): Promise<any> {\n    try {\n      const { data } = await firstValueFrom(\n        this.httpService.get(`${this.apiBaseUrl}/v2/video/info/`, {\n          params: {\n            fields: 'id,create_time,video_description,duration,height,width,share_count,comment_count,like_count,view_count,title,embed_link,embed_html,thumbnail_url',\n            video_id: videoId\n          },\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   * 方式1：初始化视频上传（旧版）\n   * @param accessToken 访问令牌\n   * @param videoSize 视频文件总大小（字节）\n   * @param chunkSize 分片大小（字节），默认为5MB\n   * @returns 初始化结果，包含上传所需的参数\n   */\n  async initVideoUpload(\n    accessToken: string,\n    videoSize?: number,\n    chunkSize: number = 5 * 1024 * 1024 // 默认5MB\n  ): Promise<any> {\n    try {\n      const requestBody: any = {};\n      ","sourceCodeStart":86,"sourceCodeEnd":122,"githubUrl":"https://github.com/yikart/AiToEarn/blob/d3aa8bea5b146a8675607cf0144d891aad3e9683/project/aitoearn-electron/server/src/modules/plat/tiktok/tiktok.service.ts#L86-L122","documentation":"getVideoDetail catches any error from TikTok's video-detail (query) endpoint and re-throws it as a 400 BadRequestException prefixed '获取视频详情失败:' with TikTok's error message (error.response.data.error.message) or the transport error message. Like 418, the actual cause lives in the embedded message — typically auth or an invalid video_id/fields combination.","triggerScenarios":"Querying video details with an invalid or deleted videoId, an expired access token, disallowed field names, or while TikTok is unreachable/rate-limited.","commonSituations":"Videos deleted or made private on TikTok after being indexed; tokens expired for accounts unused for a long time; requesting fields not in the token's scopes; wrong environment (sandbox vs production) tokens.","solutions":["Inspect the embedded error.message to get TikTok's precise error code.","Refresh the account's access token if the message indicates token expiry/invalidity.","Verify the videoId exists and is accessible to the authorizing account.","Restrict the fields parameter to values allowed by TikTok's video-query API and the token scopes."],"exampleFix":"// before\nconst detail = await tiktokService.getVideoDetail(token, videoId);\n// after\ntry {\n  const detail = await tiktokService.getVideoDetail(token, videoId);\n} catch (e) {\n  if (String(e.message).includes('invalid video_id')) return null; // video gone\n  throw e;\n}","handlingStrategy":"try-catch","validationCode":"if (!accessToken) throw new Error('Missing TikTok access token');\nif (!videoId || typeof videoId !== 'string') throw new Error('A valid videoId is required');","typeGuard":"function isVideoGone(msg: string): boolean {\n  return /video_id|invalid|not.?found|deleted/i.test(msg);\n}","tryCatchPattern":"try {\n  return await api.post('/tiktok/video/detail', { accountId, videoId });\n} catch (e) {\n  const msg = e.response?.data?.message || e.message;\n  if (isTokenError(msg)) return retryWithRefreshedToken();\n  if (isVideoGone(msg)) return null; // treat deleted/private videos as absent\n  throw e;\n}","preventionTips":["Treat 'not found'-style embedded messages as data-absence, not crashes.","Keep the fields list in sync with TikTok's video-query API and token scopes.","Refresh tokens for dormant accounts before querying details.","Distinguish sandbox vs production tokens per environment to avoid 401-style upstream rejections."],"tags":["tiktok","api-error","http","token-expired","wrapped-error"],"backgroundTag":"upstream-api-error-wrapped","analyzedSha":"d3aa8bea5b146a8675607cf0144d891aad3e9683","analyzedAt":"2026-08-31T14:19:24.185Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}