yikart/AiToEarn · error · BadRequestException

搜索推文失败: ${error.response?.data?.error || error.message}

Error message

搜索推文失败: ${error.response?.data?.error || error.message}

What it means

searchTweets queries the Twitter API v2 recent-search endpoint with a Bearer token and returns response.data. Axios-level failures are rethrown as BadRequestException with Twitter's error detail (error.response.data.error) or the Axios message.

Source

Thrown at project/aitoearn-electron/server/src/modules/plat/twitter/twitter.service.ts:308

        'max_results': isNaN(validMaxResults) ? 10 : validMaxResults, // 如果是NaN则使用默认值10
        'tweet.fields': 'created_at,public_metrics,text',
        'expansions': 'attachments.media_keys',
        'media.fields': 'url,preview_image_url,type'
      };

      const response = await lastValueFrom(
        this.httpService.get(url, {
          params,
          headers: {
            'Authorization': `Bearer ${accessToken}`
          }
        })
      );

      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 likeTweet(accessToken: string, userId: string, accountId: string, tweetId: string) {
    try {
      // Twitter API V2 点赞端点
      const url = `${TWITTER_API_V2_URL}/users/${accountId}/likes`;
      const data = {
        tweet_id: tweetId
      };

View on GitHub (pinned to d3aa8bea5b)

Solutions

  1. Validate/encode the search query: escape quotes, remove unsupported operators, respect the char limit.
  2. Confirm your API project tier includes recent search (Essential/Pro); upgrade if 403 'client-not-enrolled' appears.
  3. Refresh the access token on 401 errors.
  4. Use an app-only Bearer token for recent search if user context is not required.
  5. Back off and retry on 429 using the x-rate-limit-reset header.

Example fix

// before
const q = `from: ${user} "new post"`
const res = await twitterService.searchTweets(accessToken, userId, q)
// after
const q = `from:${userHandle} "new post"`.slice(0, 512)
if (!q.trim()) throw new Error('empty query')
const res = await twitterService.searchTweets(accessToken, userId, q)
Defensive patterns

Strategy: validation

Validate before calling

function isValidSearchQuery(q: string): boolean {
  return typeof q === 'string' && q.trim().length > 0 && q.length <= 512 &&
    (q.split('"').length - 1) % 2 === 0 // balanced quotes
}

Type guard

const hasValidQuery = (q: unknown): q is string =>
  typeof q === 'string' && q.trim().length > 0 && q.length <= 512

Try / catch

if (!hasValidQuery(query)) throw new Error('invalid search query')
try {
  return await twitterService.searchTweets(accessToken, userId, query)
} catch (e) {
  if (/invalid query|400/i.test(String(e.message))) throw new Error(`query rejected by Twitter: ${query}`)
  if (/429/.test(String(e.message))) { await sleep(16000); return retry() }
  throw e
}

Prevention

When it happens

Trigger: Malformed query operators in the search string (400 Invalid query, e.g. unbalanced quotes or invalid operator), expired token (401), using user-context token where app-only is required or vice versa (403), recent search only covering the last 7 days, or 429 rate limiting.

Common situations: Users typing raw hashtags/keywords with unsupported operators; free tier lacking recent search access entirely; hitting the very low free-tier rate limit; query longer than 512/1024 char limit.

Related errors


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