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.
This commit is contained in:
Jim Liu 宝玉
2026-03-06 23:38:23 -06:00
parent 8cea550627
commit 970f5e741a
5 changed files with 65 additions and 15 deletions
+8 -3
View File
@@ -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=<user_input>
**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 `<img>` 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 <file> --theme <theme> [--color <color>] [--title <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>
```
@@ -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`
@@ -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));
}
@@ -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);
}
@@ -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(() => {