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

  1. Treat 'already liked' responses as success and continue (idempotent handling).
  2. Refresh the user token on 401 and ensure tweet.write scope was granted.
  3. Verify the userId path param equals the token owner's user id.
  4. Check tweet existence before liking to skip 404s.
  5. 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

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


AI-assisted analysis of yikart/AiToEarn@d3aa8bea5b (2026-08-31). Data as JSON: /api/errors/d5f2f43167045377. Report an issue: GitHub.