From e79a42fd9471e020d8b984e98b8c1ca6bd4a83b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=AA=E4=B8=8D=E8=83=BD=E5=81=9C?= Date: Wed, 18 Mar 2026 17:23:48 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=AD=A3=E6=96=87=E5=9B=BE=E7=89=87?= =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E4=BD=BF=E7=94=A8=20media/uploadimg=20?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 区分正文图片和封面图片的上传接口 - 正文图片使用 media/uploadimg (返回 URL) - 封面图片使用 material/add_material (返回 media_id) - 添加 uploadType 参数支持两种上传方式 - 优化错误提示,告知用户 news 类型需要封面图 --- .../scripts/wechat-api.ts | 85 +++++++++++-------- 1 file changed, 51 insertions(+), 34 deletions(-) diff --git a/skills/baoyu-post-to-wechat/scripts/wechat-api.ts b/skills/baoyu-post-to-wechat/scripts/wechat-api.ts index e1127ad..4802736 100644 --- a/skills/baoyu-post-to-wechat/scripts/wechat-api.ts +++ b/skills/baoyu-post-to-wechat/scripts/wechat-api.ts @@ -52,7 +52,8 @@ interface ArticleOptions { } const TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token"; -const UPLOAD_URL = "https://api.weixin.qq.com/cgi-bin/material/add_material"; +const UPLOAD_BODY_IMG_URL = "https://api.weixin.qq.com/cgi-bin/media/uploadimg"; +const UPLOAD_MATERIAL_URL = "https://api.weixin.qq.com/cgi-bin/material/add_material"; const DRAFT_URL = "https://api.weixin.qq.com/cgi-bin/draft/add"; @@ -75,7 +76,8 @@ async function fetchAccessToken(appId: string, appSecret: string): Promise { let fileBuffer: Buffer; let filename: string; @@ -133,7 +135,9 @@ async function uploadImage( const footerBuffer = Buffer.from(footer, "utf-8"); const body = Buffer.concat([headerBuffer, fileBuffer, footerBuffer]); - const url = `${UPLOAD_URL}?access_token=${accessToken}&type=image`; + // 根据上传类型选择不同的接口 + const uploadUrl = uploadType === "body" ? UPLOAD_BODY_IMG_URL : UPLOAD_MATERIAL_URL; + const url = `${uploadUrl}?type=image&access_token=${accessToken}`; const res = await fetch(url, { method: "POST", headers: { @@ -147,11 +151,23 @@ async function uploadImage( throw new Error(`Upload failed ${data.errcode}: ${data.errmsg}`); } - if (data.url?.startsWith("http://")) { - data.url = data.url.replace(/^http:\/\//i, "https://"); + // media/uploadimg 接口只返回 URL,material/add_material 返回 media_id + if (uploadType === "body") { + // 正文图片上传,返回 URL + if (data.url?.startsWith("http://")) { + data.url = data.url.replace(/^http:\/\//i, "https://"); + } + return { + url: data.url, + media_id: "", + } as UploadResponse; + } else { + // 封面图片上传,返回 media_id + if (data.url?.startsWith("http://")) { + data.url = data.url.replace(/^http:\/\//i, "https://"); + } + return data; } - - return data; } async function uploadImagesInHtml( @@ -159,15 +175,15 @@ async function uploadImagesInHtml( accessToken: string, baseDir: string, contentImages: ImageInfo[] = [], -): Promise<{ html: string; firstMediaId: string; allMediaIds: string[] }> { +): Promise<{ html: string; firstImageUrl: string; allMediaIds: string[] }> { const imgRegex = /]*\ssrc=["']([^"']+)["'][^>]*>/gi; const matches = [...html.matchAll(imgRegex)]; if (matches.length === 0 && contentImages.length === 0) { - return { html, firstMediaId: "", allMediaIds: [] }; + return { html, firstImageUrl: "", allMediaIds: [] }; } - let firstMediaId = ""; + let firstImageUrl = ""; let updatedHtml = html; const allMediaIds: string[] = []; const uploadedBySource = new Map(); @@ -177,8 +193,8 @@ async function uploadImagesInHtml( if (!src) continue; if (src.startsWith("https://mmbiz.qpic.cn")) { - if (!firstMediaId) { - firstMediaId = src; + if (!firstImageUrl) { + firstImageUrl = src; } continue; } @@ -186,20 +202,20 @@ async function uploadImagesInHtml( const localPathMatch = fullTag.match(/data-local-path=["']([^"']+)["']/); const imagePath = localPathMatch ? localPathMatch[1]! : src; - console.error(`[wechat-api] Uploading image: ${imagePath}`); + console.error(`[wechat-api] Uploading body image: ${imagePath}`); try { let resp = uploadedBySource.get(imagePath); if (!resp) { - resp = await uploadImage(imagePath, accessToken, baseDir); + // 正文图片使用 media/uploadimg 接口 + resp = await uploadImage(imagePath, accessToken, baseDir, "body"); uploadedBySource.set(imagePath, resp); } const newTag = fullTag .replace(/\ssrc=["'][^"']+["']/, ` src="${resp.url}"`) .replace(/\sdata-local-path=["'][^"']+["']/, ""); updatedHtml = updatedHtml.replace(fullTag, newTag); - allMediaIds.push(resp.media_id); - if (!firstMediaId) { - firstMediaId = resp.media_id; + if (!firstImageUrl) { + firstImageUrl = resp.url; } } catch (err) { console.error(`[wechat-api] Failed to upload ${imagePath}:`, err); @@ -210,27 +226,27 @@ async function uploadImagesInHtml( if (!updatedHtml.includes(image.placeholder)) continue; const imagePath = image.localPath || image.originalPath; - console.error(`[wechat-api] Uploading placeholder image: ${imagePath}`); + console.error(`[wechat-api] Uploading body image: ${imagePath}`); try { let resp = uploadedBySource.get(imagePath); if (!resp) { - resp = await uploadImage(imagePath, accessToken, baseDir); + // 正文图片使用 media/uploadimg 接口 + resp = await uploadImage(imagePath, accessToken, baseDir, "body"); uploadedBySource.set(imagePath, resp); } const replacementTag = ``; updatedHtml = replaceAllPlaceholders(updatedHtml, image.placeholder, replacementTag); - allMediaIds.push(resp.media_id); - if (!firstMediaId) { - firstMediaId = resp.media_id; + if (!firstImageUrl) { + firstImageUrl = resp.url; } } catch (err) { console.error(`[wechat-api] Failed to upload placeholder ${image.placeholder}:`, err); } } - return { html: updatedHtml, firstMediaId, allMediaIds }; + return { html: updatedHtml, firstImageUrl, allMediaIds }; } async function publishToDraft( @@ -592,8 +608,8 @@ async function main(): Promise { console.error("[wechat-api] Fetching access token..."); const accessToken = await fetchAccessToken(creds.appId, creds.appSecret); - console.error("[wechat-api] Uploading images..."); - const { html: processedHtml, firstMediaId, allMediaIds } = await uploadImagesInHtml( + console.error("[wechat-api] Uploading body images..."); + const { html: processedHtml, firstImageUrl, allMediaIds } = await uploadImagesInHtml( htmlContent, accessToken, baseDir, @@ -613,16 +629,17 @@ async function main(): Promise { if (coverPath) { console.error(`[wechat-api] Uploading cover: ${coverPath}`); - const coverResp = await uploadImage(coverPath, accessToken, baseDir); + // 封面图片使用 material/add_material 接口 + const coverResp = await uploadImage(coverPath, accessToken, baseDir, "material"); thumbMediaId = coverResp.media_id; - } else if (firstMediaId) { - if (firstMediaId.startsWith("https://")) { - console.error(`[wechat-api] Uploading first image as cover: ${firstMediaId}`); - const coverResp = await uploadImage(firstMediaId, accessToken, baseDir); - thumbMediaId = coverResp.media_id; - } else { - thumbMediaId = firstMediaId; - } + console.error(`[wechat-api] Cover uploaded successfully, media_id: ${thumbMediaId}`); + } else if (firstImageUrl && args.articleType === "news") { + // news 类型需要 thumb_media_id,正文图片的 URL 无法使用 + console.error(`[wechat-api] Warning: No cover image provided for news article.`); + console.error(`[wechat-api] The first body image URL is: ${firstImageUrl}`); + 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) {