yikart/AiToEarn · error · BadRequestException

上传媒体失败: ${error.response?.data?.error || error.message}

Error message

上传媒体失败: ${error.response?.data?.error || error.message}

What it means

uploadMedia (service) catches failures of the Twitter media upload (v1.1 media/upload) call and rethrows as BadRequestException '上传媒体失败: ...'. The detail is taken from Twitter's error payload or the axios error message. Failures typically stem from invalid media format/size or auth problems.

Source

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

      // 创建表单数据
      const formData = new FormData();
      formData.append('media', new Blob([mediaFile], { type: mimeType }));

      const response = await lastValueFrom(
        this.httpService.post(url, formData, {
          headers: {
            'Content-Type': 'multipart/form-data',
            'Authorization': `Bearer ${accessToken}`
          }
        })
      );

      this.logger.log(`媒体上传成功: userId=${userId}, accountId=${accountId}`);

      return response.data
    } catch (error) {
      this.logger.error(`上传媒体失败: ${error.message}`, error.stack);
      throw new BadRequestException(`上传媒体失败: ${error.response?.data?.error || error.message}`);
    }
  }

  /**
   * 删除推文
   * @param userId 用户ID
   * @param accountId Twitter账号ID
   * @param tweetId 推文ID
   * @returns 删除结果
   */
  async deleteTweet(accessToken, userId: string, accountId: string, tweetId: string) {
    try {

      const url = `${TWITTER_API_V2_URL}/tweets/${tweetId}`;

      await lastValueFrom(
        this.httpService.delete(url, {
          headers: {

View on GitHub (pinned to d3aa8bea5b)

Solutions

  1. Read the embedded error.response?.data?.error for Twitter's specific reason (invalid media, too large, etc.).
  2. Validate file size and mimetype client-side against Twitter limits before uploading (images ≤5MB, GIF ≤15MB, video ≤512MB, formats PNG/JPEG/WEBP/GIF/MP4).
  3. For 401, re-authenticate the account to refresh the access token.
  4. Ensure the media category parameter matches the file type for chunked uploads (video uses tweet_video).
  5. Add retry for transient network errors during large video uploads.

Example fix

// before (client)
const fd = new FormData(); fd.append('file', anyFile);
// after
if (anyFile.size > 5 * 1024 * 1024 || !['image/png','image/jpeg','image/webp','image/gif'].includes(anyFile.type)) {
  throw new Error('不支持的媒体格式或大小');
}
const fd = new FormData(); fd.append('file', anyFile);
Defensive patterns

Strategy: validation

Validate before calling

const LIMITS = { 'image/png': 5, 'image/jpeg': 5, 'image/webp': 5, 'image/gif': 15, 'video/mp4': 512 };
const maxMB = LIMITS[file.type];
if (!maxMB || file.size > maxMB * 1024 * 1024) throw new Error('媒体格式不支持或文件过大');

Type guard

function isUploadableMedia(f: File): f is File {
  return ['image/png','image/jpeg','image/webp','image/gif','video/mp4'].includes(f.type) && f.size <= 512 * 1024 * 1024;
}

Try / catch

try {
  await api.post('/twitter/media/upload', fd);
} catch (e) {
  const msg = e.response?.data?.message || '';
  if (msg.includes('上传媒体失败')) {
    if (/too large|invalid media/i.test(msg)) notifyUserToCompressOrConvert();
    else if (/401|Unauthorized/i.test(msg)) await reauthenticate(accountId);
  }
}

Prevention

When it happens

Trigger: Uploading a file whose mimetype/size Twitter rejects (unsupported format, >5MB images, >512MB video), chunked upload INIT/APPEND/FINALIZE failure, expired access token, or network interruption mid-upload.

Common situations: Client uploads GIF/HEIC formats Twitter doesn't accept; oversized video without chunked upload parameters; the media category doesn't match the file type (e.g. tweet_image vs tweet_video); account token revoked between upload request and API call.

Related errors


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