{"record":{"id":"154cb9e9fc4976da","repo":"yikart/AiToEarn","slug":"error-response-data-error-error-mes-154cb9","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":206,"sourceCode":"  async deleteTweet(accessToken, userId: string, accountId: string, tweetId: string) {\n    try {\n\n      const url = `${TWITTER_API_V2_URL}/tweets/${tweetId}`;\n\n      await lastValueFrom(\n        this.httpService.delete(url, {\n          headers: {\n            'Authorization': `Bearer ${accessToken}`\n          }\n        })\n      );\n\n      this.logger.log(`成功删除推文: tweetId=${tweetId}, accountId=${accountId}`);\n\n      return true\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 tweetId 推文ID\n   * @returns 推文详情\n   */\n  async getTweetDetail(accessToken, userId: string, accountId: string, tweetId: string) {\n    try {\n\n      const url = `${TWITTER_API_V2_URL}/tweets/${tweetId}`;\n      const params = {\n        'tweet.fields': 'created_at,public_metrics,text,source',\n        'expansions': 'attachments.media_keys,author_id',\n        'media.fields': 'url,preview_image_url,type'","sourceCodeStart":188,"sourceCodeEnd":224,"githubUrl":"https://github.com/yikart/AiToEarn/blob/d3aa8bea5b146a8675607cf0144d891aad3e9683/project/aitoearn-electron/server/src/modules/plat/twitter/twitter.service.ts#L188-L224","documentation":"deleteTweet calls DELETE https://api.twitter.com/2/tweets/:tweetId with a Bearer user token. Any Axios error from Twitter API v2 (HTTP 4xx/5xx) is caught and rethrown as a NestJS BadRequestException with the message taken from error.response.data.error (the Twitter error detail) or error.message. It is a wrapped upstream Twitter API failure, not a local validation error.","triggerScenarios":"Calling deleteTweet with an expired/revoked OAuth2 user access token (401), a tweetId that does not exist or was already deleted (404 'Not found Tweet'), or an account whose app lacks tweet.delete scope / does not own the tweet (403 'Client not allowed to access this resource' or authorization error).","commonSituations":"Stale tokens after user re-authorizes the app; deleting a tweet that was already removed manually; using an app-only Bearer token instead of the user context token; incorrect tweetId passed from a frontend (e.g. truncated or non-numeric id); free-tier API access lacking write endpoints.","solutions":["Refresh the user's Twitter access token via TwitterAuthService before retrying (401 means expired).","Verify the tweetId exists and belongs to accountId by calling GET /2/tweets/:id first.","Ensure the OAuth2 token was issued with tweet.delete (tweet.write) scope and is a user-context token, not app-only Bearer.","Inspect error.response.data (Twitter returns details/errors arrays) in the logged stack for the exact reason.","Retry with exponential backoff only for 429/5xx; treat 403/404 as permanent."],"exampleFix":"// before\nawait twitterService.deleteTweet(accessToken, userId, accountId, tweetId)\n// after\ntry {\n  const freshToken = await twitterAuthService.ensureFreshToken(accountId)\n  await twitterService.deleteTweet(freshToken, userId, accountId, String(tweetId))\n} catch (e) {\n  if (e.getStatus?.() === 400 && /not found/i.test(e.message)) return // already deleted\n  throw e\n}","handlingStrategy":"try-catch","validationCode":"if (!accessToken || !/^[0-9]+$/.test(String(tweetId))) throw new Error('invalid deleteTweet arguments')","typeGuard":"function hasTwitterErrorDetail(e: unknown): e is { response: { data: { error: string } } } {\n  return typeof e === 'object' && e !== null && 'response' in e &&\n    typeof (e as any).response?.data?.error === 'string'\n}","tryCatchPattern":"try {\n  await twitterService.deleteTweet(accessToken, userId, accountId, tweetId)\n} catch (e) {\n  const detail = e?.response?.data?.error || e.message\n  if (/401|unauthorized/i.test(detail)) await refreshTokenAndRetry()\n  else if (/not found/i.test(detail)) return // treat as deleted\n  else throw new Error(`删除推文失败: ${detail}`)\n}","preventionTips":["Always keep tweet IDs as strings to avoid JS precision loss","Refresh Twitter user tokens before scheduled write operations","Request tweet.write scope at authorization time","Treat delete of already-deleted tweets as idempotent success"],"tags":["twitter-api","http-4xx","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"}