yikart/AiToEarn · error · BadRequestException
点赞推文失败: ${error.response?.data?.error || error.message}
Error message
点赞推文失败: ${error.response?.data?.error || error.message} What it means
likeTweet calls POST /2/users/:userId/likes with {tweet_id} in the body and a Bearer token. Twitter API v2 errors from Axios are wrapped in BadRequestException, preferring error.response.data.error from Twitter's payload.
Source
Thrown at project/aitoearn-electron/server/src/modules/plat/twitter/twitter.service.ts:341
const url = `${TWITTER_API_V2_URL}/users/${accountId}/likes`;
const data = {
tweet_id: tweetId
};
const response = await lastValueFrom(
this.httpService.post(url, data, {
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
}
})
);
this.logger.log(`成功点赞推文: userId=${userId}, accountId=${accountId}, tweetId=${tweetId}`);
return response.data;
} catch (error) {
this.logger.error(`点赞推文失败: ${error.message}`, error.stack);
throw new BadRequestException(`点赞推文失败: ${error.response?.data?.error || error.message}`);
}
}
/**
* 取消对推文的点赞
* @param accessToken 访问令牌
* @param userId 用户ID
* @param accountId Twitter账号ID
* @param tweetId 推文ID
* @returns 取消点赞结果
*/
async unlikeTweet(accessToken: string, userId: string, accountId: string, tweetId: string) {
try {
// Twitter API V2 取消点赞端点
const url = `${TWITTER_API_V2_URL}/users/${accountId}/likes/${tweetId}`;
const response = await lastValueFrom(
this.httpService.delete(url, {View on GitHub (pinned to d3aa8bea5b)
Solutions
- Treat 'already liked' responses as success and continue (idempotent handling).
- Refresh the user token on 401 and ensure tweet.write scope was granted.
- Verify the userId path param equals the token owner's user id.
- Check tweet existence before liking to skip 404s.
- Inspect error.response.data.errors for Twitter's exact title/detail and implement dedupe keys in the caller.
Example fix
// before
await twitterService.likeTweet(accessToken, userId, userId, tweetId)
// after
try {
await twitterService.likeTweet(accessToken, userId, userId, String(tweetId))
} catch (e) {
if (/already liked/i.test(e.message)) return // idempotent
throw e
} Defensive patterns
Strategy: try-catch
Validate before calling
if (!accessToken || !userId || !/^[0-9]+$/.test(String(tweetId))) throw new Error('invalid likeTweet arguments') Type guard
const isAlreadyLiked = (msg: string): boolean => /already liked/i.test(msg)
Try / catch
try {
await twitterService.likeTweet(accessToken, userId, userId, String(tweetId))
} catch (e) {
const msg = e?.response?.data?.error || e.message
if (isAlreadyLiked(msg)) return // idempotent success
if (/401|unauthorized/i.test(msg)) return retryWithFreshToken()
throw e
} Prevention
- Debounce double-clicks on the like button in the UI
- Track liked state locally to skip redundant API calls
- Ensure tweet.write scope is requested during authorization
- Use the token owner's user id in the URL path
When it happens
Trigger: Tweet already liked (409 Conflict / 'user has already liked tweet'), tweet deleted (404), expired token (401), or the authenticated userId not matching the token's user (403).
Common situations: Re-running like jobs on already-liked tweets; double-click causing duplicate requests; tokens refreshed without the tweet.write scope; liking tweets of suspended accounts.
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/d5f2f43167045377.
Report an issue: GitHub.