{"record":{"id":"817c821efecbcc9d","repo":"yikart/AiToEarn","slug":"error-response-data-error-error-mes-817c82","errorCode":null,"errorMessage":"上传媒体失败: ${error.response?.data?.error || error.message}","messagePattern":"上传媒体失败: (.+?)","errorType":"exception","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"project/aitoearn-electron/server/src/modules/plat/twitter/twitter.service.ts","lineNumber":177,"sourceCode":"      // 创建表单数据\n      const formData = new FormData();\n      formData.append('media', new Blob([mediaFile], { type: mimeType }));\n\n      const response = await lastValueFrom(\n        this.httpService.post(url, formData, {\n          headers: {\n            'Content-Type': 'multipart/form-data',\n            'Authorization': `Bearer ${accessToken}`\n          }\n        })\n      );\n\n      this.logger.log(`媒体上传成功: userId=${userId}, accountId=${accountId}`);\n\n      return response.data\n    } catch (error) {\n      this.logger.error(`上传媒体失败: ${error.message}`, error.stack);\n      throw new BadRequestException(`上传媒体失败: ${error.response?.data?.error || error.message}`);\n    }\n  }\n\n  /**\n   * 删除推文\n   * @param userId 用户ID\n   * @param accountId Twitter账号ID\n   * @param tweetId 推文ID\n   * @returns 删除结果\n   */\n  async deleteTweet(accessToken, userId: string, accountId: string, tweetId: string) {\n    try {\n\n      const url = `${TWITTER_API_V2_URL}/tweets/${tweetId}`;\n\n      await lastValueFrom(\n        this.httpService.delete(url, {\n          headers: {","sourceCodeStart":159,"sourceCodeEnd":195,"githubUrl":"https://github.com/yikart/AiToEarn/blob/d3aa8bea5b146a8675607cf0144d891aad3e9683/project/aitoearn-electron/server/src/modules/plat/twitter/twitter.service.ts#L159-L195","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Read the embedded error.response?.data?.error for Twitter's specific reason (invalid media, too large, etc.).","Validate file size and mimetype client-side against Twitter limits before uploading (images ≤5MB, GIF ≤15MB, video ≤512MB, formats PNG/JPEG/WEBP/GIF/MP4).","For 401, re-authenticate the account to refresh the access token.","Ensure the media category parameter matches the file type for chunked uploads (video uses tweet_video).","Add retry for transient network errors during large video uploads."],"exampleFix":"// before (client)\nconst fd = new FormData(); fd.append('file', anyFile);\n// after\nif (anyFile.size > 5 * 1024 * 1024 || !['image/png','image/jpeg','image/webp','image/gif'].includes(anyFile.type)) {\n  throw new Error('不支持的媒体格式或大小');\n}\nconst fd = new FormData(); fd.append('file', anyFile);","handlingStrategy":"validation","validationCode":"const LIMITS = { 'image/png': 5, 'image/jpeg': 5, 'image/webp': 5, 'image/gif': 15, 'video/mp4': 512 };\nconst maxMB = LIMITS[file.type];\nif (!maxMB || file.size > maxMB * 1024 * 1024) throw new Error('媒体格式不支持或文件过大');","typeGuard":"function isUploadableMedia(f: File): f is File {\n  return ['image/png','image/jpeg','image/webp','image/gif','video/mp4'].includes(f.type) && f.size <= 512 * 1024 * 1024;\n}","tryCatchPattern":"try {\n  await api.post('/twitter/media/upload', fd);\n} catch (e) {\n  const msg = e.response?.data?.message || '';\n  if (msg.includes('上传媒体失败')) {\n    if (/too large|invalid media/i.test(msg)) notifyUserToCompressOrConvert();\n    else if (/401|Unauthorized/i.test(msg)) await reauthenticate(accountId);\n  }\n}","preventionTips":["Check file size/type against Twitter media limits before upload.","Match the media category (image/gif/video) to the file type in upload parameters.","Use chunked upload for large videos.","Convert unsupported formats (HEIC etc.) client-side first.","Retry network interruptions on large uploads, resuming from the last chunk."],"tags":["twitter-api","media-upload","http-client","file-validation","oauth"],"backgroundTag":"upstream-api-request-failed","analyzedSha":"d3aa8bea5b146a8675607cf0144d891aad3e9683","analyzedAt":"2026-08-31T14:19:24.185Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}