From 970f5e741ad8c596032e9e704097e4efe533d195 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jim=20Liu=20=E5=AE=9D=E7=8E=89?= Date: Fri, 6 Mar 2026 23:38:23 -0600 Subject: [PATCH] feat(baoyu-post-to-wechat): default bottom citations for markdown input Enable bottom citations by default for markdown articles. Add --no-cite flag to all posting scripts (wechat-api, wechat-article, md-to-wechat) to optionally disable. WeChat mp links stay inline. --- skills/baoyu-post-to-wechat/SKILL.md | 11 ++++++--- .../references/article-posting.md | 6 +++++ .../scripts/md-to-wechat.ts | 18 ++++++++++++--- .../scripts/wechat-api.ts | 22 ++++++++++++++---- .../scripts/wechat-article.ts | 23 +++++++++++++++---- 5 files changed, 65 insertions(+), 15 deletions(-) diff --git a/skills/baoyu-post-to-wechat/SKILL.md b/skills/baoyu-post-to-wechat/SKILL.md index 1705678..83981d3 100644 --- a/skills/baoyu-post-to-wechat/SKILL.md +++ b/skills/baoyu-post-to-wechat/SKILL.md @@ -1,6 +1,6 @@ --- name: baoyu-post-to-wechat -description: Posts content to WeChat Official Account (微信公众号) via API or Chrome CDP. Supports article posting (文章) with HTML, markdown, or plain text input, and image-text posting (贴图, formerly 图文) with multiple images. Use when user mentions "发布公众号", "post to wechat", "微信公众号", or "贴图/图文/文章". +description: Posts content to WeChat Official Account (微信公众号) via API or Chrome CDP. Supports article posting (文章) with HTML, markdown, or plain text input, and image-text posting (贴图, formerly 图文) with multiple images. Markdown article workflows default to converting ordinary external links into bottom citations for WeChat-friendly output. Use when user mentions "发布公众号", "post to wechat", "微信公众号", or "贴图/图文/文章". --- # Post to WeChat Official Account @@ -252,10 +252,15 @@ WECHAT_APP_SECRET= **CRITICAL**: Publishing scripts handle markdown conversion internally. Do NOT pre-convert markdown to HTML — pass the original markdown file directly. This ensures the API method renders images as `` tags (for API upload) while the browser method uses placeholders (for paste-and-replace workflow). +**Markdown citation default**: +- For markdown input, ordinary external links are converted to bottom citations by default. +- Use `--no-cite` only if the user explicitly wants to keep ordinary external links inline. +- Existing HTML input is left as-is; no extra citation conversion is applied. + **API method** (accepts `.md` or `.html`): ```bash -${BUN_X} ${SKILL_DIR}/scripts/wechat-api.ts --theme [--color ] [--title ] [--summary <summary>] [--author <author>] [--cover <cover_path>] +${BUN_X} ${SKILL_DIR}/scripts/wechat-api.ts <file> --theme <theme> [--color <color>] [--title <title>] [--summary <summary>] [--author <author>] [--cover <cover_path>] [--no-cite] ``` **CRITICAL**: Always include `--theme` parameter. Never omit it, even if using `default`. Only include `--color` if explicitly set by user or EXTEND.md. @@ -274,7 +279,7 @@ If script parameters do not expose the two comment fields, still ensure final AP **Browser method** (accepts `--markdown` or `--html`): ```bash -${BUN_X} ${SKILL_DIR}/scripts/wechat-article.ts --markdown <markdown_file> --theme <theme> [--color <color>] +${BUN_X} ${SKILL_DIR}/scripts/wechat-article.ts --markdown <markdown_file> --theme <theme> [--color <color>] [--no-cite] ${BUN_X} ${SKILL_DIR}/scripts/wechat-article.ts --html <html_file> ``` diff --git a/skills/baoyu-post-to-wechat/references/article-posting.md b/skills/baoyu-post-to-wechat/references/article-posting.md index c0d6c8d..1d9a4f9 100644 --- a/skills/baoyu-post-to-wechat/references/article-posting.md +++ b/skills/baoyu-post-to-wechat/references/article-posting.md @@ -11,6 +11,9 @@ ${BUN_X} ./scripts/wechat-article.ts --markdown article.md # With theme ${BUN_X} ./scripts/wechat-article.ts --markdown article.md --theme grace +# Disable bottom citations for ordinary external links +${BUN_X} ./scripts/wechat-article.ts --markdown article.md --no-cite + # With explicit options ${BUN_X} ./scripts/wechat-article.ts --markdown article.md --author "作者名" --summary "摘要" ``` @@ -21,6 +24,7 @@ ${BUN_X} ./scripts/wechat-article.ts --markdown article.md --author "作者名" |-----------|-------------| | `--markdown <path>` | Markdown file to convert and post | | `--theme <name>` | Theme: default, grace, simple, modern | +| `--no-cite` | Keep ordinary external links inline instead of converting them to bottom citations | | `--title <text>` | Override title (auto-extracted from markdown) | | `--author <name>` | Author name | | `--summary <text>` | Article summary | @@ -51,6 +55,8 @@ Regular paragraph with **bold** and *italic*. [Link text](https://example.com) ``` +Markdown mode converts ordinary external links into bottom citations by default for WeChat-friendly output. Use `--no-cite` to disable that behavior. + ## Image Handling 1. **Parse**: Images in markdown are replaced with `WECHATIMGPH_N` diff --git a/skills/baoyu-post-to-wechat/scripts/md-to-wechat.ts b/skills/baoyu-post-to-wechat/scripts/md-to-wechat.ts index bfc0da8..c57a355 100644 --- a/skills/baoyu-post-to-wechat/scripts/md-to-wechat.ts +++ b/skills/baoyu-post-to-wechat/scripts/md-to-wechat.ts @@ -112,10 +112,14 @@ function parseFrontmatter(content: string): { frontmatter: Record<string, string return { frontmatter, body: match[2]! }; } -export async function convertMarkdown(markdownPath: string, options?: { title?: string; theme?: string; color?: string }): Promise<ParsedResult> { +export async function convertMarkdown( + markdownPath: string, + options?: { title?: string; theme?: string; color?: string; citeStatus?: boolean } +): Promise<ParsedResult> { const baseDir = path.dirname(markdownPath); const content = fs.readFileSync(markdownPath, 'utf-8'); const theme = options?.theme ?? 'default'; + const citeStatus = options?.citeStatus ?? true; const { frontmatter, body } = parseFrontmatter(content); @@ -185,8 +189,9 @@ export async function convertMarkdown(markdownPath: string, options?: { title?: const renderArgs = ['-y', 'bun', renderScript, tempMdPath, '--theme', theme]; if (options?.color) renderArgs.push('--color', options.color); + if (citeStatus) renderArgs.push('--cite'); - console.error(`[md-to-wechat] Rendering markdown with theme: ${theme}${options?.color ? `, color: ${options.color}` : ''}`); + console.error(`[md-to-wechat] Rendering markdown with theme: ${theme}${options?.color ? `, color: ${options.color}` : ''}, citeStatus: ${citeStatus}`); const result = spawnSync('npx', renderArgs, { stdio: ['inherit', 'pipe', 'pipe'], @@ -232,6 +237,7 @@ Options: --title <title> Override title --theme <name> Theme name (default, grace, simple, modern) --color <name|hex> Primary color (blue, green, vermilion, etc. or hex) + --no-cite Disable bottom citations for ordinary external links --help Show this help Output JSON format: @@ -251,6 +257,7 @@ Example: npx -y bun md-to-wechat.ts article.md npx -y bun md-to-wechat.ts article.md --theme grace npx -y bun md-to-wechat.ts article.md --theme modern --color blue + npx -y bun md-to-wechat.ts article.md --no-cite `); process.exit(0); } @@ -265,6 +272,7 @@ async function main(): Promise<void> { let title: string | undefined; let theme: string | undefined; let color: string | undefined; + let citeStatus = true; for (let i = 0; i < args.length; i++) { const arg = args[i]!; @@ -274,6 +282,10 @@ async function main(): Promise<void> { theme = args[++i]; } else if (arg === '--color' && args[i + 1]) { color = args[++i]; + } else if (arg === '--cite') { + citeStatus = true; + } else if (arg === '--no-cite') { + citeStatus = false; } else if (!arg.startsWith('-')) { markdownPath = arg; } @@ -289,7 +301,7 @@ async function main(): Promise<void> { process.exit(1); } - const result = await convertMarkdown(markdownPath, { title, theme, color }); + const result = await convertMarkdown(markdownPath, { title, theme, color, citeStatus }); console.log(JSON.stringify(result, null, 2)); } diff --git a/skills/baoyu-post-to-wechat/scripts/wechat-api.ts b/skills/baoyu-post-to-wechat/scripts/wechat-api.ts index 9442cca..532e2d5 100644 --- a/skills/baoyu-post-to-wechat/scripts/wechat-api.ts +++ b/skills/baoyu-post-to-wechat/scripts/wechat-api.ts @@ -307,7 +307,12 @@ function parseFrontmatter(content: string): { frontmatter: Record<string, string return { frontmatter, body: match[2]! }; } -function renderMarkdownToHtml(markdownPath: string, theme: string = "default", color?: string): string { +function renderMarkdownToHtml( + markdownPath: string, + theme: string = "default", + color?: string, + citeStatus: boolean = true +): string { const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const renderScript = path.join(__dirname, "md", "render.ts"); @@ -315,8 +320,9 @@ function renderMarkdownToHtml(markdownPath: string, theme: string = "default", c const renderArgs = ["-y", "bun", renderScript, markdownPath, "--theme", theme]; if (color) renderArgs.push("--color", color); + if (citeStatus) renderArgs.push("--cite"); - console.error(`[wechat-api] Rendering markdown with theme: ${theme}${color ? `, color: ${color}` : ""}`); + console.error(`[wechat-api] Rendering markdown with theme: ${theme}${color ? `, color: ${color}` : ""}, citeStatus: ${citeStatus}`); const result = spawnSync("npx", renderArgs, { stdio: ["inherit", "pipe", "pipe"], cwd: baseDir, @@ -362,6 +368,7 @@ Options: --theme <name> Theme name for markdown (default, grace, simple, modern). Default: default --color <name|hex> Primary color (blue, green, vermilion, etc. or hex) --cover <path> Cover image path (local or URL) + --no-cite Disable bottom citations for ordinary external links in markdown mode --dry-run Parse and render only, don't publish --help Show this help @@ -390,6 +397,7 @@ Example: npx -y bun wechat-api.ts article.html --title "My Article" npx -y bun wechat-api.ts images/ --type newspic --title "Photo Album" npx -y bun wechat-api.ts article.md --dry-run + npx -y bun wechat-api.ts article.md --no-cite `); process.exit(0); } @@ -404,6 +412,7 @@ interface CliArgs { theme: string; color?: string; cover?: string; + citeStatus: boolean; dryRun: boolean; } @@ -417,6 +426,7 @@ function parseArgs(argv: string[]): CliArgs { isHtml: false, articleType: "news", theme: "default", + citeStatus: true, dryRun: false, }; @@ -439,6 +449,10 @@ function parseArgs(argv: string[]): CliArgs { args.color = argv[++i]; } else if (arg === "--cover" && argv[i + 1]) { args.cover = argv[++i]; + } else if (arg === "--cite") { + args.citeStatus = true; + } else if (arg === "--no-cite") { + args.citeStatus = false; } else if (arg === "--dry-run") { args.dryRun = true; } else if (arg.startsWith("--") && argv[i + 1] && !argv[i + 1]!.startsWith("-")) { @@ -513,8 +527,8 @@ async function main(): Promise<void> { if (!author) author = frontmatter.author || ""; if (!digest) digest = frontmatter.digest || frontmatter.summary || frontmatter.description || ""; - console.error(`[wechat-api] Theme: ${args.theme}${args.color ? `, color: ${args.color}` : ""}`); - htmlPath = renderMarkdownToHtml(filePath, args.theme, args.color); + console.error(`[wechat-api] Theme: ${args.theme}${args.color ? `, color: ${args.color}` : ""}, citeStatus: ${args.citeStatus}`); + htmlPath = renderMarkdownToHtml(filePath, args.theme, args.color, args.citeStatus); console.error(`[wechat-api] HTML generated: ${htmlPath}`); htmlContent = extractHtmlContent(htmlPath); } diff --git a/skills/baoyu-post-to-wechat/scripts/wechat-article.ts b/skills/baoyu-post-to-wechat/scripts/wechat-article.ts index a7870d4..083a72a 100644 --- a/skills/baoyu-post-to-wechat/scripts/wechat-article.ts +++ b/skills/baoyu-post-to-wechat/scripts/wechat-article.ts @@ -20,6 +20,7 @@ interface ArticleOptions { markdownFile?: string; theme?: string; color?: string; + citeStatus?: boolean; author?: string; summary?: string; images?: string[]; @@ -182,13 +183,19 @@ async function pasteFromClipboardInEditor(session: ChromeSession): Promise<void> await sleep(1000); } -async function parseMarkdownWithPlaceholders(markdownPath: string, theme?: string, color?: string): Promise<{ title: string; author: string; summary: string; htmlPath: string; contentImages: ImageInfo[] }> { +async function parseMarkdownWithPlaceholders( + markdownPath: string, + theme?: string, + color?: string, + citeStatus: boolean = true +): Promise<{ title: string; author: string; summary: string; htmlPath: string; contentImages: ImageInfo[] }> { const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const mdToWechatScript = path.join(__dirname, 'md-to-wechat.ts'); const args = ['-y', 'bun', mdToWechatScript, markdownPath]; if (theme) args.push('--theme', theme); if (color) args.push('--color', color); + if (!citeStatus) args.push('--no-cite'); const result = spawnSync('npx', args, { stdio: ['inherit', 'pipe', 'pipe'] }); if (result.status !== 0) { @@ -383,7 +390,7 @@ async function removeExtraEmptyLineAfterImage(session: ChromeSession): Promise<b } export async function postArticle(options: ArticleOptions): Promise<void> { - const { title, content, htmlFile, markdownFile, theme, color, author, summary, images = [], submit = false, profileDir, cdpPort } = options; + const { title, content, htmlFile, markdownFile, theme, color, citeStatus = true, author, summary, images = [], submit = false, profileDir, cdpPort } = options; let { contentImages = [] } = options; let effectiveTitle = title || ''; let effectiveAuthor = author || ''; @@ -392,7 +399,7 @@ export async function postArticle(options: ArticleOptions): Promise<void> { if (markdownFile) { console.log(`[wechat] Parsing markdown: ${markdownFile}`); - const parsed = await parseMarkdownWithPlaceholders(markdownFile, theme, color); + const parsed = await parseMarkdownWithPlaceholders(markdownFile, theme, color, citeStatus); effectiveTitle = effectiveTitle || parsed.title; effectiveAuthor = effectiveAuthor || parsed.author; effectiveSummary = effectiveSummary || parsed.summary; @@ -677,6 +684,7 @@ Options: --markdown <path> Markdown file to convert and post (recommended) --theme <name> Theme for markdown (default, grace, simple, modern) --color <name|hex> Primary color (blue, green, vermilion, etc. or hex) + --no-cite Disable bottom citations for ordinary external links in markdown mode --author <name> Author name --summary <text> Article summary --image <path> Content image, can repeat (only with --content) @@ -687,13 +695,15 @@ Options: Examples: npx -y bun wechat-article.ts --markdown article.md npx -y bun wechat-article.ts --markdown article.md --theme grace --submit + npx -y bun wechat-article.ts --markdown article.md --no-cite npx -y bun wechat-article.ts --title "标题" --content "内容" --image img.png npx -y bun wechat-article.ts --title "标题" --html article.html --submit Markdown mode: Images in markdown are converted to placeholders. After pasting HTML, each placeholder is selected, scrolled into view, deleted, and replaced - with the actual image via paste. + with the actual image via paste. Ordinary external links are converted to + bottom citations by default. `); process.exit(0); } @@ -709,6 +719,7 @@ async function main(): Promise<void> { let markdownFile: string | undefined; let theme: string | undefined; let color: string | undefined; + let citeStatus = true; let author: string | undefined; let summary: string | undefined; let submit = false; @@ -723,6 +734,8 @@ async function main(): Promise<void> { else if (arg === '--markdown' && args[i + 1]) markdownFile = args[++i]; else if (arg === '--theme' && args[i + 1]) theme = args[++i]; else if (arg === '--color' && args[i + 1]) color = args[++i]; + else if (arg === '--cite') citeStatus = true; + else if (arg === '--no-cite') citeStatus = false; else if (arg === '--author' && args[i + 1]) author = args[++i]; else if (arg === '--summary' && args[i + 1]) summary = args[++i]; else if (arg === '--image' && args[i + 1]) images.push(args[++i]!); @@ -734,7 +747,7 @@ async function main(): Promise<void> { if (!markdownFile && !htmlFile && !title) { console.error('Error: --title is required (or use --markdown/--html)'); process.exit(1); } if (!markdownFile && !htmlFile && !content) { console.error('Error: --content, --html, or --markdown is required'); process.exit(1); } - await postArticle({ title: title || '', content, htmlFile, markdownFile, theme, color, author, summary, images, submit, profileDir, cdpPort }); + await postArticle({ title: title || '', content, htmlFile, markdownFile, theme, color, citeStatus, author, summary, images, submit, profileDir, cdpPort }); } await main().then(() => {