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

  1. Treat 404 'not liked' as success for idempotent unlike flows.
  2. Refresh the user access token on 401 before retrying.
  3. Confirm the path userId matches the authenticated token owner.
  4. Ensure the token was granted tweet.write scope at authorization time.
  5. 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

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


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