yikart/AiToEarn · error · BadRequestException
获取推文详情失败: ${error.response?.data?.error || error.message}
Error message
获取推文详情失败: ${error.response?.data?.error || error.message} What it means
getTweetDetail calls GET /2/tweets/:tweetId with tweet.field/expansion query params using a Bearer token. Axios errors from Twitter API v2 are wrapped in a BadRequestException whose message prefers Twitter's error.response.data.error detail over error.message.
Source
Thrown at project/aitoearn-electron/server/src/modules/plat/twitter/twitter.service.ts:239
const params = {
'tweet.fields': 'created_at,public_metrics,text,source',
'expansions': 'attachments.media_keys,author_id',
'media.fields': 'url,preview_image_url,type'
};
const response = await lastValueFrom(
this.httpService.get(url, {
params,
headers: {
'Authorization': `Bearer ${accessToken}`
}
})
);
return response.data.data
} catch (error) {
this.logger.error(`获取推文详情失败: ${error.message}`, error.stack);
throw new BadRequestException(`获取推文详情失败: ${error.response?.data?.error || error.message}`);
}
}
/**
* 获取推文的统计数据
* @param userId 用户ID
* @param accountId Twitter账号ID
* @param tweetId 推文ID
* @returns 推文统计数据
*/
async getTweetMetrics(accessToken, userId: string, accountId: string, tweetId: string) {
try {
const url = `${TWITTER_API_V2_URL}/tweets/${tweetId}`;
const params = {
'tweet.fields': 'public_metrics,non_public_metrics,organic_metrics', // 注意:某些指标需要高级API访问权限
};
View on GitHub (pinned to d3aa8bea5b)
Solutions
- Refresh the user access token when the message contains a 401/Unauthorized detail.
- Keep tweet IDs as strings end-to-end to avoid precision loss.
- Check tweet existence first with GET /2/tweets/:id and handle 404 gracefully.
- Confirm the token's scopes (tweet.read) and that the account can view the tweet.
- Read the full error.response.data.errors array for Twitter's exact error title/parameters.
Example fix
// before
const detail = await twitterService.getTweetDetail(accessToken, userId, accountId, tweetId)
// after
if (!/^[0-9]+$/.test(String(tweetId))) throw new Error('invalid tweetId')
const detail = await twitterService.getTweetDetail(accessToken, userId, accountId, String(tweetId)).catch(e => null)
if (!detail) return null // tweet deleted or inaccessible Defensive patterns
Strategy: try-catch
Validate before calling
if (!/^[0-9]+$/.test(String(tweetId))) throw new Error('tweetId must be a numeric string') Type guard
const isAxiosLike = (e: unknown): e is { response?: { status?: number; data?: { error?: string } } } =>
typeof e === 'object' && e !== null && 'response' in e Try / catch
try {
return await twitterService.getTweetDetail(accessToken, userId, accountId, tweetId)
} catch (e) {
if (isAxiosLike(e) && e.response?.status === 404) return null
if (isAxiosLike(e) && e.response?.status === 401) return retryWithFreshToken()
throw e
} Prevention
- Handle 404 as 'tweet deleted' rather than an application error
- Refresh tokens proactively based on expires_in tracking
- Verify read scope (tweet.read) is granted
- Never convert tweet IDs through Number()
When it happens
Trigger: Requesting a nonexistent or deleted tweetId (404), expired token (401), tweet not visible to the authorizing account (403 protected/private tweet), or malformed tweetId.
Common situations: Deleted tweets being re-fetched for analytics; private/protected accounts whose tweets the app can't read; API tier lacking access; expired cached access token; id passed as a float losing precision in JavaScript.
Related errors
- 删除推文失败: ${error.response?.data?.error || error.message}
- 点赞推文失败: ${error.response?.data?.error || error.message}
- 取消点赞推文失败: ${error.response?.data?.error || error.message}
- 获取推文统计数据失败: ${error.response?.data?.error || error.message}
- 搜索推文失败: ${error.response?.data?.error || error.message}
AI-assisted analysis of yikart/AiToEarn@d3aa8bea5b (2026-08-31).
Data as JSON: /api/errors/b8b982f61bf2352f.
Report an issue: GitHub.