diff --git a/skills/baoyu-post-to-wechat/scripts/wechat-api.ts b/skills/baoyu-post-to-wechat/scripts/wechat-api.ts index b4b92dd..e1127ad 100644 --- a/skills/baoyu-post-to-wechat/scripts/wechat-api.ts +++ b/skills/baoyu-post-to-wechat/scripts/wechat-api.ts @@ -23,6 +23,20 @@ interface PublishResponse { errmsg?: string; } +interface ImageInfo { + placeholder: string; + localPath: string; + originalPath: string; +} + +interface MarkdownRenderResult { + title: string; + author: string; + summary: string; + htmlPath: string; + contentImages: ImageInfo[]; +} + type ArticleType = "news" | "newspic"; interface ArticleOptions { @@ -143,18 +157,20 @@ async function uploadImage( async function uploadImagesInHtml( html: string, accessToken: string, - baseDir: string + baseDir: string, + contentImages: ImageInfo[] = [], ): Promise<{ html: string; firstMediaId: string; allMediaIds: string[] }> { const imgRegex = /]*\ssrc=["']([^"']+)["'][^>]*>/gi; const matches = [...html.matchAll(imgRegex)]; - if (matches.length === 0) { + if (matches.length === 0 && contentImages.length === 0) { return { html, firstMediaId: "", allMediaIds: [] }; } let firstMediaId = ""; let updatedHtml = html; const allMediaIds: string[] = []; + const uploadedBySource = new Map(); for (const match of matches) { const [fullTag, src] = match; @@ -172,7 +188,11 @@ async function uploadImagesInHtml( console.error(`[wechat-api] Uploading image: ${imagePath}`); try { - const resp = await uploadImage(imagePath, accessToken, baseDir); + let resp = uploadedBySource.get(imagePath); + if (!resp) { + resp = await uploadImage(imagePath, accessToken, baseDir); + uploadedBySource.set(imagePath, resp); + } const newTag = fullTag .replace(/\ssrc=["'][^"']+["']/, ` src="${resp.url}"`) .replace(/\sdata-local-path=["'][^"']+["']/, ""); @@ -186,6 +206,30 @@ async function uploadImagesInHtml( } } + for (const image of contentImages) { + if (!updatedHtml.includes(image.placeholder)) continue; + + const imagePath = image.localPath || image.originalPath; + console.error(`[wechat-api] Uploading placeholder image: ${imagePath}`); + + try { + let resp = uploadedBySource.get(imagePath); + if (!resp) { + resp = await uploadImage(imagePath, accessToken, baseDir); + uploadedBySource.set(imagePath, resp); + } + + const replacementTag = ``; + updatedHtml = replaceAllPlaceholders(updatedHtml, image.placeholder, replacementTag); + allMediaIds.push(resp.media_id); + if (!firstMediaId) { + firstMediaId = resp.media_id; + } + } catch (err) { + console.error(`[wechat-api] Failed to upload placeholder ${image.placeholder}:`, err); + } + } + return { html: updatedHtml, firstMediaId, allMediaIds }; } @@ -245,7 +289,7 @@ async function publishToDraft( } function parseFrontmatter(content: string): { frontmatter: Record; body: string } { - const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/); + const match = content.match(/^\s*---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/); if (!match) return { frontmatter: {}, body: content }; const frontmatter: Record = {}; @@ -266,38 +310,42 @@ function parseFrontmatter(content: string): { frontmatter: Record { let htmlPath: string; let htmlContent: string; let frontmatter: Record = {}; + let contentImages: ImageInfo[] = []; if (args.isHtml) { htmlPath = filePath; @@ -491,8 +540,14 @@ async function main(): Promise { if (!digest) digest = frontmatter.digest || frontmatter.summary || frontmatter.description || ""; console.error(`[wechat-api] Theme: ${args.theme}${args.color ? `, color: ${args.color}` : ""}, citeStatus: ${args.citeStatus}`); - htmlPath = renderMarkdownToHtml(filePath, args.theme, args.color, args.citeStatus); + const rendered = renderMarkdownWithPlaceholders(filePath, args.theme, args.color, args.citeStatus, args.title); + htmlPath = rendered.htmlPath; + contentImages = rendered.contentImages; + if (!title) title = rendered.title; + if (!author) author = rendered.author; + if (!digest) digest = rendered.summary; console.error(`[wechat-api] HTML generated: ${htmlPath}`); + console.error(`[wechat-api] Placeholder images: ${contentImages.length}`); htmlContent = extractHtmlContent(htmlPath); } @@ -527,6 +582,7 @@ async function main(): Promise { digest: digest || undefined, htmlPath, contentLength: htmlContent.length, + placeholderImageCount: contentImages.length || undefined, account: resolved.alias || undefined, }, null, 2)); return; @@ -540,7 +596,8 @@ async function main(): Promise { const { html: processedHtml, firstMediaId, allMediaIds } = await uploadImagesInHtml( htmlContent, accessToken, - baseDir + baseDir, + contentImages, ); htmlContent = processedHtml;