{"record":{"id":"e70f02f0a4ec840f","repo":"yikart/AiToEarn","slug":"error-response-data-error-error","errorCode":null,"errorMessage":"获取推文统计数据失败: ${error.response?.data?.error || error.message}","messagePattern":"获取推文统计数据失败: (.+?)","errorType":"exception","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"project/aitoearn-electron/server/src/modules/plat/twitter/twitter.service.ts","lineNumber":270,"sourceCode":"\n      const url = `${TWITTER_API_V2_URL}/tweets/${tweetId}`;\n      const params = {\n        'tweet.fields': 'public_metrics,non_public_metrics,organic_metrics', // 注意：某些指标需要高级API访问权限\n      };\n\n      const response = await lastValueFrom(\n        this.httpService.get(url, {\n          params,\n          headers: {\n            'Authorization': `Bearer ${accessToken}`\n          }\n        })\n      );\n\n      return response.data;\n    } catch (error) {\n      this.logger.error(`获取推文统计数据失败: ${error.message}`, error.stack);\n      throw new BadRequestException(`获取推文统计数据失败: ${error.response?.data?.error || error.message}`);\n    }\n  }\n\n  /**\n   * 查找用户的推文\n   * @param userId 用户ID\n   * @param accountId Twitter账号ID\n   * @param query 查询内容\n   * @param maxResults 最大结果数\n   * @returns 搜索结果\n   */\n  async searchTweets(accessToken, userId: string, accountId: string, query: string, maxResults: number = 10) {\n    try {\n      // 确保maxResults是一个有效的整数\n      const validMaxResults = Number(maxResults);\n\n      const url = `${TWITTER_API_V2_URL}/tweets/search/recent`;\n      const params = {","sourceCodeStart":252,"sourceCodeEnd":288,"githubUrl":"https://github.com/yikart/AiToEarn/blob/d3aa8bea5b146a8675607cf0144d891aad3e9683/project/aitoearn-electron/server/src/modules/plat/twitter/twitter.service.ts#L252-L288","documentation":"getTweetMetrics calls GET /2/tweets/:tweetId (with public_metrics fields) and returns response.data. Any failure from the Twitter API v2 call is wrapped in a BadRequestException, message taken from error.response.data.error or error.message.","triggerScenarios":"Expired or revoked user token (401), tweet not found/deleted (404), missing metrics field params for the access tier, or Twitter rate limit (429) on the metrics endpoint.","commonSituations":"Batch metric jobs hitting 429 rate limits; tracking metrics of tweets deleted by the author; old code calling after token rotation; free-tier access without metrics fields.","solutions":["Refresh the user access token on 401 and retry once.","Honor 429: back off until rate-limit reset before calling again.","Skip tweets that return 404 (deleted) instead of failing the whole batch.","Verify public_metrics is requested in tweet.fields and your API tier allows it.","Log error.response.data.errors for Twitter's precise error code."],"exampleFix":"// before\nconst metrics = await twitterService.getTweetMetrics(accessToken, userId, accountId, tweetId)\n// after\ntry {\n  const metrics = await twitterService.getTweetMetrics(accessToken, userId, accountId, tweetId)\n} catch (e) {\n  if (String(e.message).includes('429')) await sleep(15000)\n  else if (/not found/i.test(e.message)) return null\n  else throw e\n}","handlingStrategy":"retry","validationCode":"if (!accessToken) throw new Error('missing access token before getTweetMetrics')","typeGuard":"const isRateLimited = (e: unknown): boolean =>\n  (e as any)?.response?.status === 429 || /429|rate limit/i.test(String((e as any)?.message))","tryCatchPattern":"const fetchMetrics = async (attempt = 0): Promise<any> => {\n  try {\n    return await twitterService.getTweetMetrics(accessToken, userId, accountId, tweetId)\n  } catch (e) {\n    if (isRateLimited(e) && attempt < 3) {\n      await sleep(15000 * 2 ** attempt)\n      return fetchMetrics(attempt + 1)\n    }\n    if (/not found/i.test(String(e.message))) return null\n    throw e\n  }\n}","preventionTips":["Respect x-rate-limit-reset headers in batch metric jobs","Space out metrics polling instead of tight loops","Track token expiry and refresh before it lapses","Skip deleted tweets instead of failing the batch"],"tags":["twitter-api","rate-limit","oauth","nestjs"],"backgroundTag":"twitter-api-request-failed","analyzedSha":"d3aa8bea5b146a8675607cf0144d891aad3e9683","analyzedAt":"2026-08-31T14:19:24.185Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}