yikart/AiToEarn · error · BadRequestException
取消点赞推文失败: ${error.response?.data?.error || error.message}
Error message
取消点赞推文失败: ${error.response?.data?.error || error.message} What it means
unlikeTweet calls DELETE /2/users/:userId/likes/:tweetId with a Bearer token. Any Twitter API v2 error returned via Axios is rethrown as a BadRequestException using error.response.data.error or error.message.
Source
Thrown at project/aitoearn-electron/server/src/modules/plat/twitter/twitter.service.ts:370
*/
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, {
headers: {
'Authorization': `Bearer ${accessToken}`
}
})
);
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}`);
}
}
}
View on GitHub (pinned to d3aa8bea5b)
Solutions
- Treat 404 'not liked' as success for idempotent unlike flows.
- Refresh the user access token on 401 before retrying.
- Confirm the path userId matches the authenticated token owner.
- Ensure the token was granted tweet.write scope at authorization time.
- Log full error.response.data.errors to distinguish rate limit (429) from permission (403) failures.
Example fix
// before
await twitterService.unlikeTweet(accessToken, userId, otherUserId, tweetId)
// after
try {
await twitterService.unlikeTweet(accessToken, userId, userId, String(tweetId))
} catch (e) {
if (/has not liked|not found/i.test(e.message)) return // already unliked
throw e
} Defensive patterns
Strategy: try-catch
Validate before calling
if (!accessToken || userId !== tokenOwnerId) throw new Error('unlikeTweet requires token-owner userId') Type guard
const isNotLiked = (msg: string): boolean => /has not liked|not found/i.test(msg)
Try / catch
try {
await twitterService.unlikeTweet(accessToken, userId, userId, String(tweetId))
} catch (e) {
const msg = e?.response?.data?.error || e.message
if (isNotLiked(msg)) return // already unliked: idempotent
if (/401/.test(msg)) return retryAfterRefresh()
throw e
} Prevention
- Design unlike flows to tolerate 'not liked' 404 responses
- Match the path userId to the authenticated user, never a different account
- Refresh tokens before bulk unlike jobs
- Log error.response.data.errors for precise Twitter error codes
When it happens
Trigger: Tweet not previously liked (404 'user has not liked tweet'), tweet deleted (404), expired/revoked token (401), or path userId not matching the token user (403).
Common situations: Bulk unlike jobs running against likes already removed; account re-authorization losing tweet.write scope; stale cached tokens; wrong userId argument order when calling the service.
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/e70fd76511d7d6e4.
Report an issue: GitHub.