yikart/AiToEarn · error · BadRequestException
发布推文失败: ${error.response?.data?.error || error.message}
Error message
发布推文失败: ${error.response?.data?.error || error.message} What it means
createTweet catches failures from the Twitter POST /tweets call and rethrows as BadRequestException '发布推文失败: ...'. Before throwing it marks the publish record (PubRecordModel) as FAIL, so the local publish history reflects the failure. The embedded detail comes from Twitter's error payload or the axios error message.
Source
Thrown at project/aitoearn-electron/server/src/modules/plat/twitter/twitter.service.ts:140
'Content-Type': 'application/json',
'Authorization': `Bearer ${accessToken}`
}
})
);
this.logger.log(`成功发布推文: userId=${userId}, accountId=${accountId}`);
// 更新发布记录
await this.PubRecordModel.updateOne({ id:newId }, {
status: PubStatus.RELEASED,
publishTime: new Date()
});
return response.data.data
} catch (error) {
await this.PubRecordModel.updateOne({ id:newId }, { status: PubStatus.FAIL });
this.logger.error(`发布推文失败: ${error.message}`, error.stack);
throw new BadRequestException(`发布推文失败: ${error.response?.data?.error || error.message}`);
}
}
/**
* 上传媒体文件
* @param userId 用户ID
* @param accountId Twitter账号ID
* @param mediaFile 媒体文件Buffer
* @param mimeType 媒体类型
* @returns 媒体上传结果
*/
async uploadMedia(accessToken, userId: string, accountId: string, mediaFile: Buffer, mimeType: string) {
try {
// Twitter有单独的媒体上传API
const url = 'https://upload.twitter.com/1.1/media/upload.json';
View on GitHub (pinned to d3aa8bea5b)
Solutions
- Read the embedded error.response.data.error for Twitter's exact code; for 403 duplicate, change content or wait before reposting.
- For 401, re-authenticate the account to obtain a fresh access token.
- Validate tweet length (<=280 chars) and media ids before calling the endpoint.
- For 429, respect rate limits and space out scheduled posts.
- Check the publish record marked FAIL to correlate with the failed attempt and retry with a new id.
Defensive patterns
Strategy: try-catch
Validate before calling
if (tweetText.length > 280) throw new Error('推文超过280字符');
if (!mediaIds.every(id => !!id)) throw new Error('存在无效的媒体ID');
const recentHash = hashOf(tweetText); if (recentlyPosted.has(recentHash)) throw new Error('疑似重复推文'); Type guard
function isPublishFailure(msg: string): boolean {
return typeof msg === 'string' && msg.startsWith('发布推文失败:');
} Try / catch
try {
await api.post('/twitter/tweet', { tweetId: undefined, accountId, text, mediaIds });
} catch (e) {
const msg = e.response?.data?.message || '';
if (msg.includes('发布推文失败')) {
if (/duplicate|forbidden/i.test(msg)) await retryWithEditedContent();
else if (/401|Unauthorized/i.test(msg)) await reauthenticate(accountId);
}
} Prevention
- Enforce the 280-char limit and media validation before posting.
- Debounce/dedupe resubmissions to avoid Twitter duplicate-tweet 403s.
- Mark and retry failed publish records with new content or after backoff.
- Handle 429 by scheduling posts instead of firing in bursts.
- Re-auth accounts after users revoke app access.
When it happens
Trigger: Twitter rejects the tweet creation: duplicate content (403 forbidden duplicate), text over 280 chars, invalid media metadata, expired access token (401), account suspended, or 429 rate limit.
Common situations: Retrying the same post right after a timeout causes Twitter's duplicate-tweet 403; media id from upload expired/invalid; scheduled posts pile up and hit posting rate limits; token revoked by user revoking app access.
Related errors
- 获取Twitter时间线失败: ${error.response?.data?.error || error.messa
- 上传媒体失败: ${error.response?.data?.error || error.message}
- 获取推文统计数据失败: ${error.response?.data?.error || error.message}
- 删除推文失败: ${error.response?.data?.error || error.message}
- 获取推文详情失败: ${error.response?.data?.error || error.message}
AI-assisted analysis of yikart/AiToEarn@d3aa8bea5b (2026-08-31).
Data as JSON: /api/errors/0d0e3e49729026fc.
Report an issue: GitHub.