diff --git a/skills/baoyu-post-to-wechat/scripts/wechat-api.ts b/skills/baoyu-post-to-wechat/scripts/wechat-api.ts
index 06ed041..9c18436 100644
--- a/skills/baoyu-post-to-wechat/scripts/wechat-api.ts
+++ b/skills/baoyu-post-to-wechat/scripts/wechat-api.ts
@@ -28,6 +28,18 @@ interface PublishResponse {
errmsg?: string;
}
+type ArticleType = "news" | "newspic";
+
+interface ArticleOptions {
+ title: string;
+ author?: string;
+ digest?: string;
+ content: string;
+ thumbMediaId: string;
+ articleType: ArticleType;
+ imageMediaIds?: string[];
+}
+
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 DRAFT_URL = "https://api.weixin.qq.com/cgi-bin/draft/add";
@@ -176,16 +188,17 @@ async function uploadImagesInHtml(
html: string,
accessToken: string,
baseDir: string
-): Promise<{ html: string; firstMediaId: string }> {
+): Promise<{ html: string; firstMediaId: string; allMediaIds: string[] }> {
const imgRegex = /
]*\ssrc=["']([^"']+)["'][^>]*>/gi;
const matches = [...html.matchAll(imgRegex)];
if (matches.length === 0) {
- return { html, firstMediaId: "" };
+ return { html, firstMediaId: "", allMediaIds: [] };
}
let firstMediaId = "";
let updatedHtml = html;
+ const allMediaIds: string[] = [];
for (const match of matches) {
const [fullTag, src] = match;
@@ -208,6 +221,7 @@ async function uploadImagesInHtml(
.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;
}
@@ -216,28 +230,51 @@ async function uploadImagesInHtml(
}
}
- return { html: updatedHtml, firstMediaId };
+ return { html: updatedHtml, firstMediaId, allMediaIds };
}
async function publishToDraft(
- title: string,
- html: string,
- thumbMediaId: string,
+ options: ArticleOptions,
accessToken: string
): Promise {
const url = `${DRAFT_URL}?access_token=${accessToken}`;
+
+ let article: Record;
+
+ if (options.articleType === "newspic") {
+ if (!options.imageMediaIds || options.imageMediaIds.length === 0) {
+ throw new Error("newspic requires at least one image");
+ }
+ article = {
+ article_type: "newspic",
+ title: options.title,
+ content: options.content,
+ need_open_comment: 1,
+ only_fans_can_comment: 0,
+ image_info: {
+ image_list: options.imageMediaIds.map(id => ({ image_media_id: id })),
+ },
+ };
+ if (options.author) article.author = options.author;
+ } else {
+ article = {
+ article_type: "news",
+ title: options.title,
+ content: options.content,
+ thumb_media_id: options.thumbMediaId,
+ need_open_comment: 1,
+ only_fans_can_comment: 0,
+ };
+ if (options.author) article.author = options.author;
+ if (options.digest) article.digest = options.digest;
+ }
+
const res = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
- body: JSON.stringify({
- articles: [{
- title,
- content: html,
- thumb_media_id: thumbMediaId,
- }],
- }),
+ body: JSON.stringify({ articles: [article] }),
});
const data = await res.json() as PublishResponse;
@@ -315,13 +352,24 @@ Arguments:
file Markdown (.md) or HTML (.html) file
Options:
+ --type Article type: news (文章, default) or newspic (图文)
--title Override title
- --summary Article summary (for future use)
+ --author Author name (max 16 chars)
+ --summary Article summary/digest (max 128 chars)
--theme Theme name for markdown (default, grace, simple). Default: default
--cover Cover image path (local or URL)
--dry-run Parse and render only, don't publish
--help Show this help
+Frontmatter Fields (markdown):
+ title Article title
+ author Author name
+ digest/summary Article summary
+ featureImage/coverImage/cover/image Cover image path
+
+Comments:
+ Comments are enabled by default, open to all users.
+
Environment Variables:
WECHAT_APP_ID WeChat App ID
WECHAT_APP_SECRET WeChat App Secret
@@ -334,7 +382,9 @@ Config File Locations (in priority order):
Example:
npx -y bun wechat-api.ts article.md
npx -y bun wechat-api.ts article.md --theme grace --cover cover.png
+ npx -y bun wechat-api.ts article.md --author "Author Name" --summary "Brief intro"
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
`);
process.exit(0);
@@ -343,7 +393,9 @@ Example:
interface CliArgs {
filePath: string;
isHtml: boolean;
+ articleType: ArticleType;
title?: string;
+ author?: string;
summary?: string;
theme: string;
cover?: string;
@@ -358,14 +410,22 @@ function parseArgs(argv: string[]): CliArgs {
const args: CliArgs = {
filePath: "",
isHtml: false,
+ articleType: "news",
theme: "default",
dryRun: false,
};
for (let i = 0; i < argv.length; i++) {
const arg = argv[i]!;
- if (arg === "--title" && argv[i + 1]) {
+ if (arg === "--type" && argv[i + 1]) {
+ const t = argv[++i]!.toLowerCase();
+ if (t === "news" || t === "newspic") {
+ args.articleType = t;
+ }
+ } else if (arg === "--title" && argv[i + 1]) {
args.title = argv[++i];
+ } else if (arg === "--author" && argv[i + 1]) {
+ args.author = argv[++i];
} else if (arg === "--summary" && argv[i + 1]) {
args.summary = argv[++i];
} else if (arg === "--theme" && argv[i + 1]) {
@@ -410,6 +470,8 @@ async function main(): Promise {
const baseDir = path.dirname(filePath);
let title = args.title || "";
+ let author = args.author || "";
+ let digest = args.summary || "";
let htmlPath: string;
let htmlContent: string;
let frontmatter: Record = {};
@@ -422,9 +484,9 @@ async function main(): Promise {
const mdContent = fs.readFileSync(mdPath, "utf-8");
const parsed = parseFrontmatter(mdContent);
frontmatter = parsed.frontmatter;
- if (!title && frontmatter.title) {
- title = frontmatter.title;
- }
+ if (!title && frontmatter.title) title = frontmatter.title;
+ if (!author) author = frontmatter.author || "";
+ if (!digest) digest = frontmatter.digest || frontmatter.summary || frontmatter.description || "";
}
if (!title) {
title = extractHtmlTitle(fs.readFileSync(htmlPath, "utf-8"));
@@ -441,6 +503,8 @@ async function main(): Promise {
const h1Match = body.match(/^#\s+(.+)$/m);
if (h1Match) title = h1Match[1]!;
}
+ if (!author) author = frontmatter.author || "";
+ if (!digest) digest = frontmatter.digest || frontmatter.summary || frontmatter.description || "";
console.error(`[wechat-api] Theme: ${args.theme}`);
htmlPath = renderMarkdownToHtml(filePath, args.theme);
@@ -454,10 +518,16 @@ async function main(): Promise {
}
console.error(`[wechat-api] Title: ${title}`);
+ if (author) console.error(`[wechat-api] Author: ${author}`);
+ if (digest) console.error(`[wechat-api] Digest: ${digest.slice(0, 50)}...`);
+ console.error(`[wechat-api] Type: ${args.articleType}`);
if (args.dryRun) {
console.log(JSON.stringify({
+ articleType: args.articleType,
title,
+ author: author || undefined,
+ digest: digest || undefined,
htmlPath,
contentLength: htmlContent.length,
}, null, 2));
@@ -469,7 +539,7 @@ async function main(): Promise {
const accessToken = await fetchAccessToken(config.appId, config.appSecret);
console.error("[wechat-api] Uploading images...");
- const { html: processedHtml, firstMediaId } = await uploadImagesInHtml(
+ const { html: processedHtml, firstMediaId, allMediaIds } = await uploadImagesInHtml(
htmlContent,
accessToken,
baseDir
@@ -497,18 +567,32 @@ async function main(): Promise {
}
}
- if (!thumbMediaId) {
+ if (args.articleType === "news" && !thumbMediaId) {
console.error("Error: No cover image. Provide via --cover, frontmatter.featureImage, or include an image in content.");
process.exit(1);
}
+ if (args.articleType === "newspic" && allMediaIds.length === 0) {
+ console.error("Error: newspic requires at least one image in content.");
+ process.exit(1);
+ }
+
console.error("[wechat-api] Publishing to draft...");
- const result = await publishToDraft(title, htmlContent, thumbMediaId, accessToken);
+ const result = await publishToDraft({
+ title,
+ author: author || undefined,
+ digest: digest || undefined,
+ content: htmlContent,
+ thumbMediaId,
+ articleType: args.articleType,
+ imageMediaIds: args.articleType === "newspic" ? allMediaIds : undefined,
+ }, accessToken);
console.log(JSON.stringify({
success: true,
media_id: result.media_id,
title,
+ articleType: args.articleType,
}, null, 2));
console.error(`[wechat-api] Published successfully! media_id: ${result.media_id}`);