fix: 修复newspic类型图片上传和news类型封面兜底逻辑

- uploadImagesInHtml 函数添加 articleType 参数支持
- 为 newspic 类型正文图片额外调用 material 接口收集 media_id
- 返回 firstImageSource 用于 news 类型封面兜底逻辑
- 恢复 news 类型没有显式封面时使用第一张正文图作为封面的逻辑
This commit is contained in:
浪不能停 2026-03-20 13:57:59 +08:00
parent e79a42fd94
commit 747977416d
1 changed files with 46 additions and 16 deletions

View File

@ -175,17 +175,19 @@ async function uploadImagesInHtml(
accessToken: string, accessToken: string,
baseDir: string, baseDir: string,
contentImages: ImageInfo[] = [], contentImages: ImageInfo[] = [],
): Promise<{ html: string; firstImageUrl: string; allMediaIds: string[] }> { articleType: ArticleType = "news",
): Promise<{ html: string; firstImageUrl: string; firstImageSource: string; imageMediaIds: string[] }> {
const imgRegex = /<img[^>]*\ssrc=["']([^"']+)["'][^>]*>/gi; const imgRegex = /<img[^>]*\ssrc=["']([^"']+)["'][^>]*>/gi;
const matches = [...html.matchAll(imgRegex)]; const matches = [...html.matchAll(imgRegex)];
if (matches.length === 0 && contentImages.length === 0) { if (matches.length === 0 && contentImages.length === 0) {
return { html, firstImageUrl: "", allMediaIds: [] }; return { html, firstImageUrl: "", firstImageSource: "", imageMediaIds: [] };
} }
let firstImageUrl = ""; let firstImageUrl = "";
let firstImageSource = "";
let updatedHtml = html; let updatedHtml = html;
const allMediaIds: string[] = []; const imageMediaIds: string[] = [];
const uploadedBySource = new Map<string, UploadResponse>(); const uploadedBySource = new Map<string, UploadResponse>();
for (const match of matches) { for (const match of matches) {
@ -206,7 +208,7 @@ async function uploadImagesInHtml(
try { try {
let resp = uploadedBySource.get(imagePath); let resp = uploadedBySource.get(imagePath);
if (!resp) { if (!resp) {
// 正文图片使用 media/uploadimg 接口 // 正文图片使用 media/uploadimg 接口获取 URL
resp = await uploadImage(imagePath, accessToken, baseDir, "body"); resp = await uploadImage(imagePath, accessToken, baseDir, "body");
uploadedBySource.set(imagePath, resp); uploadedBySource.set(imagePath, resp);
} }
@ -217,6 +219,21 @@ async function uploadImagesInHtml(
if (!firstImageUrl) { if (!firstImageUrl) {
firstImageUrl = resp.url; firstImageUrl = resp.url;
} }
// 如果是 newspic 类型,额外调用 material 接口收集 media_id
if (articleType === "newspic") {
let materialResp = uploadedBySource.get(`${imagePath}:material`);
if (!materialResp) {
materialResp = await uploadImage(imagePath, accessToken, baseDir, "material");
uploadedBySource.set(`${imagePath}:material`, materialResp);
}
if (materialResp.media_id) {
imageMediaIds.push(materialResp.media_id);
if (!firstImageSource) {
firstImageSource = materialResp.media_id;
}
}
}
} catch (err) { } catch (err) {
console.error(`[wechat-api] Failed to upload ${imagePath}:`, err); console.error(`[wechat-api] Failed to upload ${imagePath}:`, err);
} }
@ -231,7 +248,7 @@ async function uploadImagesInHtml(
try { try {
let resp = uploadedBySource.get(imagePath); let resp = uploadedBySource.get(imagePath);
if (!resp) { if (!resp) {
// 正文图片使用 media/uploadimg 接口 // 正文图片使用 media/uploadimg 接口获取 URL
resp = await uploadImage(imagePath, accessToken, baseDir, "body"); resp = await uploadImage(imagePath, accessToken, baseDir, "body");
uploadedBySource.set(imagePath, resp); uploadedBySource.set(imagePath, resp);
} }
@ -241,12 +258,27 @@ async function uploadImagesInHtml(
if (!firstImageUrl) { if (!firstImageUrl) {
firstImageUrl = resp.url; firstImageUrl = resp.url;
} }
// 如果是 newspic 类型,额外调用 material 接口收集 media_id
if (articleType === "newspic") {
let materialResp = uploadedBySource.get(`${imagePath}:material`);
if (!materialResp) {
materialResp = await uploadImage(imagePath, accessToken, baseDir, "material");
uploadedBySource.set(`${imagePath}:material`, materialResp);
}
if (materialResp.media_id) {
imageMediaIds.push(materialResp.media_id);
if (!firstImageSource) {
firstImageSource = materialResp.media_id;
}
}
}
} catch (err) { } catch (err) {
console.error(`[wechat-api] Failed to upload placeholder ${image.placeholder}:`, err); console.error(`[wechat-api] Failed to upload placeholder ${image.placeholder}:`, err);
} }
} }
return { html: updatedHtml, firstImageUrl, allMediaIds }; return { html: updatedHtml, firstImageUrl, firstImageSource, imageMediaIds };
} }
async function publishToDraft( async function publishToDraft(
@ -609,11 +641,12 @@ async function main(): Promise<void> {
const accessToken = await fetchAccessToken(creds.appId, creds.appSecret); const accessToken = await fetchAccessToken(creds.appId, creds.appSecret);
console.error("[wechat-api] Uploading body images..."); console.error("[wechat-api] Uploading body images...");
const { html: processedHtml, firstImageUrl, allMediaIds } = await uploadImagesInHtml( const { html: processedHtml, firstImageUrl, firstImageSource, imageMediaIds } = await uploadImagesInHtml(
htmlContent, htmlContent,
accessToken, accessToken,
baseDir, baseDir,
contentImages, contentImages,
args.articleType,
); );
htmlContent = processedHtml; htmlContent = processedHtml;
@ -633,13 +666,10 @@ async function main(): Promise<void> {
const coverResp = await uploadImage(coverPath, accessToken, baseDir, "material"); const coverResp = await uploadImage(coverPath, accessToken, baseDir, "material");
thumbMediaId = coverResp.media_id; thumbMediaId = coverResp.media_id;
console.error(`[wechat-api] Cover uploaded successfully, media_id: ${thumbMediaId}`); console.error(`[wechat-api] Cover uploaded successfully, media_id: ${thumbMediaId}`);
} else if (firstImageUrl && args.articleType === "news") { } else if (firstImageSource && args.articleType === "news") {
// news 类型需要 thumb_media_id,正文图片的 URL 无法使用 // news 类型没有封面时,使用第一张正文图的 media_id 作为封面(兜底逻辑)
console.error(`[wechat-api] Warning: No cover image provided for news article.`); thumbMediaId = firstImageSource;
console.error(`[wechat-api] The first body image URL is: ${firstImageUrl}`); console.error(`[wechat-api] Using first body image as cover (fallback), media_id: ${thumbMediaId}`);
console.error(`[wechat-api] However, news articles require thumb_media_id (not URL).`);
console.error(`[wechat-api] Please provide --cover parameter or set coverImage in frontmatter.`);
process.exit(1);
} }
if (args.articleType === "news" && !thumbMediaId) { if (args.articleType === "news" && !thumbMediaId) {
@ -647,7 +677,7 @@ async function main(): Promise<void> {
process.exit(1); process.exit(1);
} }
if (args.articleType === "newspic" && allMediaIds.length === 0) { if (args.articleType === "newspic" && imageMediaIds.length === 0) {
console.error("Error: newspic requires at least one image in content."); console.error("Error: newspic requires at least one image in content.");
process.exit(1); process.exit(1);
} }
@ -660,7 +690,7 @@ async function main(): Promise<void> {
content: htmlContent, content: htmlContent,
thumbMediaId, thumbMediaId,
articleType: args.articleType, articleType: args.articleType,
imageMediaIds: args.articleType === "newspic" ? allMediaIds : undefined, imageMediaIds: args.articleType === "newspic" ? imageMediaIds : undefined,
needOpenComment: resolved.need_open_comment, needOpenComment: resolved.need_open_comment,
onlyFansCanComment: resolved.only_fans_can_comment, onlyFansCanComment: resolved.only_fans_can_comment,
}, accessToken); }, accessToken);