From 28a7db6129034f166af92be04ac7c57cb1bde0d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jim=20Liu=20=E5=AE=9D=E7=8E=89?= Date: Thu, 29 Jan 2026 17:17:58 -0600 Subject: [PATCH] chore: release v1.25.4 --- .claude-plugin/marketplace.json | 2 +- CHANGELOG.md | 8 +++ CHANGELOG.zh.md | 8 +++ skills/baoyu-markdown-to-html/scripts/main.ts | 10 ++- .../scripts/wechat-api.ts | 33 +++++++-- .../scripts/wechat-article.ts | 72 +++++++++++++++++-- 6 files changed, 121 insertions(+), 12 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index d18506c..4f522c6 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -6,7 +6,7 @@ }, "metadata": { "description": "Skills shared by Baoyu for improving daily work efficiency", - "version": "1.25.3" + "version": "1.25.4" }, "plugins": [ { diff --git a/CHANGELOG.md b/CHANGELOG.md index a012409..66ccecf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ English | [中文](./CHANGELOG.zh.md) +## 1.25.4 - 2026-01-29 + +### Fixes +- `baoyu-markdown-to-html`: generate proper `` tags with `data-local-path` attribute instead of text placeholders. +- `baoyu-post-to-wechat`: fix API publishing to read image paths from `data-local-path` attribute; fix title/cover extraction from corresponding `.md` frontmatter when publishing HTML files. +- `baoyu-post-to-wechat`: fix CLI argument parsing to handle unknown parameters gracefully; add `--summary` parameter support. +- `baoyu-post-to-wechat`: fix browser publishing to convert `` tags back to text placeholders before paste. + ## 1.25.3 - 2026-01-28 ### Features diff --git a/CHANGELOG.zh.md b/CHANGELOG.zh.md index 8c0eb7d..e04d988 100644 --- a/CHANGELOG.zh.md +++ b/CHANGELOG.zh.md @@ -2,6 +2,14 @@ [English](./CHANGELOG.md) | 中文 +## 1.25.4 - 2026-01-29 + +### 修复 +- `baoyu-markdown-to-html`:生成带 `data-local-path` 属性的 `` 标签,而非纯文本占位符。 +- `baoyu-post-to-wechat`:修复 API 发布时从 `data-local-path` 属性读取图片路径;修复发布 HTML 文件时从对应 `.md` 的 frontmatter 提取标题和封面图。 +- `baoyu-post-to-wechat`:修复命令行参数解析,正确跳过未知参数;新增 `--summary` 参数支持。 +- `baoyu-post-to-wechat`:修复浏览器发布模式,粘贴前将 `` 标签转换回文本占位符。 + ## 1.25.3 - 2026-01-28 ### 新功能 diff --git a/skills/baoyu-markdown-to-html/scripts/main.ts b/skills/baoyu-markdown-to-html/scripts/main.ts index 03de6e9..9c34c30 100644 --- a/skills/baoyu-markdown-to-html/scripts/main.ts +++ b/skills/baoyu-markdown-to-html/scripts/main.ts @@ -214,7 +214,6 @@ export async function convertMarkdown(markdownPath: string, options?: { title?: } fs.copyFileSync(tempHtmlPath, finalHtmlPath); - console.error(`[markdown-to-html] HTML saved to: ${finalHtmlPath}`); const contentImages: ImageInfo[] = []; for (const img of images) { @@ -226,6 +225,15 @@ export async function convertMarkdown(markdownPath: string, options?: { title?: }); } + let htmlContent = fs.readFileSync(finalHtmlPath, 'utf-8'); + for (const img of contentImages) { + const imgTag = ``; + htmlContent = htmlContent.replace(img.placeholder, imgTag); + } + fs.writeFileSync(finalHtmlPath, htmlContent, 'utf-8'); + + console.error(`[markdown-to-html] HTML saved to: ${finalHtmlPath}`); + return { title, author, diff --git a/skills/baoyu-post-to-wechat/scripts/wechat-api.ts b/skills/baoyu-post-to-wechat/scripts/wechat-api.ts index 82c8e3d..06ed041 100644 --- a/skills/baoyu-post-to-wechat/scripts/wechat-api.ts +++ b/skills/baoyu-post-to-wechat/scripts/wechat-api.ts @@ -177,7 +177,7 @@ async function uploadImagesInHtml( accessToken: string, baseDir: string ): Promise<{ html: string; firstMediaId: string }> { - const imgRegex = /]+src=["']([^"']+)["'][^>]*>/gi; + const imgRegex = /]*\ssrc=["']([^"']+)["'][^>]*>/gi; const matches = [...html.matchAll(imgRegex)]; if (matches.length === 0) { @@ -188,7 +188,7 @@ async function uploadImagesInHtml( let updatedHtml = html; for (const match of matches) { - const [, src] = match; + const [fullTag, src] = match; if (!src) continue; if (src.startsWith("https://mmbiz.qpic.cn")) { @@ -198,15 +198,21 @@ async function uploadImagesInHtml( continue; } - console.error(`[wechat-api] Uploading image: ${src}`); + const localPathMatch = fullTag.match(/data-local-path=["']([^"']+)["']/); + const imagePath = localPathMatch ? localPathMatch[1]! : src; + + console.error(`[wechat-api] Uploading image: ${imagePath}`); try { - const resp = await uploadImage(src, accessToken, baseDir); - updatedHtml = updatedHtml.replace(src, resp.url); + const resp = await uploadImage(imagePath, accessToken, baseDir); + const newTag = fullTag + .replace(/\ssrc=["'][^"']+["']/, ` src="${resp.url}"`) + .replace(/\sdata-local-path=["'][^"']+["']/, ""); + updatedHtml = updatedHtml.replace(fullTag, newTag); if (!firstMediaId) { firstMediaId = resp.media_id; } } catch (err) { - console.error(`[wechat-api] Failed to upload ${src}:`, err); + console.error(`[wechat-api] Failed to upload ${imagePath}:`, err); } } @@ -310,6 +316,7 @@ Arguments: Options: --title Override title + --summary <text> Article summary (for future use) --theme <name> Theme name for markdown (default, grace, simple). Default: default --cover <path> Cover image path (local or URL) --dry-run Parse and render only, don't publish @@ -337,6 +344,7 @@ interface CliArgs { filePath: string; isHtml: boolean; title?: string; + summary?: string; theme: string; cover?: string; dryRun: boolean; @@ -358,12 +366,16 @@ function parseArgs(argv: string[]): CliArgs { const arg = argv[i]!; if (arg === "--title" && argv[i + 1]) { args.title = argv[++i]; + } else if (arg === "--summary" && argv[i + 1]) { + args.summary = argv[++i]; } else if (arg === "--theme" && argv[i + 1]) { args.theme = argv[++i]!; } else if (arg === "--cover" && argv[i + 1]) { args.cover = argv[++i]; } else if (arg === "--dry-run") { args.dryRun = true; + } else if (arg.startsWith("--") && argv[i + 1] && !argv[i + 1]!.startsWith("-")) { + i++; } else if (!arg.startsWith("-")) { args.filePath = arg; } @@ -405,6 +417,15 @@ async function main(): Promise<void> { if (args.isHtml) { htmlPath = filePath; htmlContent = extractHtmlContent(htmlPath); + const mdPath = filePath.replace(/\.html$/i, ".md"); + if (fs.existsSync(mdPath)) { + const mdContent = fs.readFileSync(mdPath, "utf-8"); + const parsed = parseFrontmatter(mdContent); + frontmatter = parsed.frontmatter; + if (!title && frontmatter.title) { + title = frontmatter.title; + } + } if (!title) { title = extractHtmlTitle(fs.readFileSync(htmlPath, "utf-8")); } diff --git a/skills/baoyu-post-to-wechat/scripts/wechat-article.ts b/skills/baoyu-post-to-wechat/scripts/wechat-article.ts index b237156..5e70825 100644 --- a/skills/baoyu-post-to-wechat/scripts/wechat-article.ts +++ b/skills/baoyu-post-to-wechat/scripts/wechat-article.ts @@ -115,7 +115,7 @@ async function sendPaste(cdp?: CdpConnection, sessionId?: string): Promise<void> } } -async function copyHtmlFromBrowser(cdp: CdpConnection, htmlFilePath: string): Promise<void> { +async function copyHtmlFromBrowser(cdp: CdpConnection, htmlFilePath: string, contentImages: ImageInfo[] = []): Promise<void> { const absolutePath = path.isAbsolute(htmlFilePath) ? htmlFilePath : path.resolve(process.cwd(), htmlFilePath); const fileUrl = `file://${absolutePath}`; @@ -128,6 +128,28 @@ async function copyHtmlFromBrowser(cdp: CdpConnection, htmlFilePath: string): Pr await cdp.send('Runtime.enable', {}, { sessionId }); await sleep(2000); + if (contentImages.length > 0) { + console.log('[wechat] Replacing img tags with placeholders for browser paste...'); + const replacements = contentImages.map(img => ({ placeholder: img.placeholder, localPath: img.localPath })); + await cdp.send<{ result: { value: unknown } }>('Runtime.evaluate', { + expression: ` + (function() { + const replacements = ${JSON.stringify(replacements)}; + for (const r of replacements) { + const imgs = document.querySelectorAll('img[src="' + r.placeholder + '"], img[data-local-path="' + r.localPath + '"]'); + for (const img of imgs) { + const text = document.createTextNode(r.placeholder); + img.parentNode.replaceChild(text, img); + } + } + return true; + })() + `, + returnByValue: true, + }, { sessionId }); + await sleep(500); + } + console.log('[wechat] Selecting #output content...'); await cdp.send<{ result: { value: unknown } }>('Runtime.evaluate', { expression: ` @@ -176,7 +198,7 @@ async function parseMarkdownWithPlaceholders(markdownPath: string, theme?: strin return JSON.parse(output); } -function parseHtmlMeta(htmlPath: string): { title: string; author: string; summary: string } { +function parseHtmlMeta(htmlPath: string): { title: string; author: string; summary: string; contentImages: ImageInfo[] } { const content = fs.readFileSync(htmlPath, 'utf-8'); let title = ''; @@ -203,7 +225,45 @@ function parseHtmlMeta(htmlPath: string): { title: string; author: string; summa } } - return { title, author, summary }; + const mdPath = htmlPath.replace(/\.html$/i, '.md'); + if (fs.existsSync(mdPath)) { + const mdContent = fs.readFileSync(mdPath, 'utf-8'); + const fmMatch = mdContent.match(/^---\r?\n([\s\S]*?)\r?\n---/); + if (fmMatch) { + const lines = fmMatch[1]!.split('\n'); + for (const line of lines) { + const colonIdx = line.indexOf(':'); + if (colonIdx > 0) { + const key = line.slice(0, colonIdx).trim(); + let value = line.slice(colonIdx + 1).trim(); + if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) { + value = value.slice(1, -1); + } + if (key === 'title' && !title) title = value; + if (key === 'author' && !author) author = value; + if ((key === 'description' || key === 'summary') && !summary) summary = value; + } + } + } + } + + const contentImages: ImageInfo[] = []; + const imgRegex = /<img[^>]*\ssrc=["']([^"']+)["'][^>]*>/gi; + const matches = [...content.matchAll(imgRegex)]; + for (const match of matches) { + const [fullTag, src] = match; + if (!src || src.startsWith('http')) continue; + const localPathMatch = fullTag.match(/data-local-path=["']([^"']+)["']/); + if (localPathMatch) { + contentImages.push({ + placeholder: src, + localPath: localPathMatch[1]!, + originalPath: src, + }); + } + } + + return { title, author, summary, contentImages }; } async function selectAndReplacePlaceholder(session: ChromeSession, placeholder: string): Promise<boolean> { @@ -273,9 +333,13 @@ export async function postArticle(options: ArticleOptions): Promise<void> { effectiveAuthor = effectiveAuthor || meta.author; effectiveSummary = effectiveSummary || meta.summary; effectiveHtmlFile = htmlFile; + if (meta.contentImages.length > 0) { + contentImages = meta.contentImages; + } console.log(`[wechat] Title: ${effectiveTitle || '(empty)'}`); console.log(`[wechat] Author: ${effectiveAuthor || '(empty)'}`); console.log(`[wechat] Summary: ${effectiveSummary || '(empty)'}`); + console.log(`[wechat] Found ${contentImages.length} images to insert`); } if (effectiveTitle && effectiveTitle.length > 64) throw new Error(`Title too long: ${effectiveTitle.length} chars (max 64)`); @@ -416,7 +480,7 @@ export async function postArticle(options: ArticleOptions): Promise<void> { if (effectiveHtmlFile && fs.existsSync(effectiveHtmlFile)) { console.log(`[wechat] Copying HTML content from: ${effectiveHtmlFile}`); - await copyHtmlFromBrowser(cdp, effectiveHtmlFile); + await copyHtmlFromBrowser(cdp, effectiveHtmlFile, contentImages); await sleep(500); console.log('[wechat] Pasting into editor...'); await pasteFromClipboardInEditor(session);