mirror of
https://github.com/JimLiu/baoyu-skills.git
synced 2026-07-18 00:19:47 +08:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 28a7db6129 |
@@ -6,7 +6,7 @@
|
|||||||
},
|
},
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"description": "Skills shared by Baoyu for improving daily work efficiency",
|
"description": "Skills shared by Baoyu for improving daily work efficiency",
|
||||||
"version": "1.25.3"
|
"version": "1.25.4"
|
||||||
},
|
},
|
||||||
"plugins": [
|
"plugins": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,6 +2,14 @@
|
|||||||
|
|
||||||
English | [中文](./CHANGELOG.zh.md)
|
English | [中文](./CHANGELOG.zh.md)
|
||||||
|
|
||||||
|
## 1.25.4 - 2026-01-29
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
- `baoyu-markdown-to-html`: generate proper `<img>` 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 `<img>` tags back to text placeholders before paste.
|
||||||
|
|
||||||
## 1.25.3 - 2026-01-28
|
## 1.25.3 - 2026-01-28
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|||||||
@@ -2,6 +2,14 @@
|
|||||||
|
|
||||||
[English](./CHANGELOG.md) | 中文
|
[English](./CHANGELOG.md) | 中文
|
||||||
|
|
||||||
|
## 1.25.4 - 2026-01-29
|
||||||
|
|
||||||
|
### 修复
|
||||||
|
- `baoyu-markdown-to-html`:生成带 `data-local-path` 属性的 `<img>` 标签,而非纯文本占位符。
|
||||||
|
- `baoyu-post-to-wechat`:修复 API 发布时从 `data-local-path` 属性读取图片路径;修复发布 HTML 文件时从对应 `.md` 的 frontmatter 提取标题和封面图。
|
||||||
|
- `baoyu-post-to-wechat`:修复命令行参数解析,正确跳过未知参数;新增 `--summary` 参数支持。
|
||||||
|
- `baoyu-post-to-wechat`:修复浏览器发布模式,粘贴前将 `<img>` 标签转换回文本占位符。
|
||||||
|
|
||||||
## 1.25.3 - 2026-01-28
|
## 1.25.3 - 2026-01-28
|
||||||
|
|
||||||
### 新功能
|
### 新功能
|
||||||
|
|||||||
@@ -214,7 +214,6 @@ export async function convertMarkdown(markdownPath: string, options?: { title?:
|
|||||||
}
|
}
|
||||||
|
|
||||||
fs.copyFileSync(tempHtmlPath, finalHtmlPath);
|
fs.copyFileSync(tempHtmlPath, finalHtmlPath);
|
||||||
console.error(`[markdown-to-html] HTML saved to: ${finalHtmlPath}`);
|
|
||||||
|
|
||||||
const contentImages: ImageInfo[] = [];
|
const contentImages: ImageInfo[] = [];
|
||||||
for (const img of images) {
|
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 = `<img src="${img.placeholder}" data-local-path="${img.localPath}" style="display: block; width: 100%; margin: 1.5em auto;">`;
|
||||||
|
htmlContent = htmlContent.replace(img.placeholder, imgTag);
|
||||||
|
}
|
||||||
|
fs.writeFileSync(finalHtmlPath, htmlContent, 'utf-8');
|
||||||
|
|
||||||
|
console.error(`[markdown-to-html] HTML saved to: ${finalHtmlPath}`);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title,
|
title,
|
||||||
author,
|
author,
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ async function uploadImagesInHtml(
|
|||||||
accessToken: string,
|
accessToken: string,
|
||||||
baseDir: string
|
baseDir: string
|
||||||
): Promise<{ html: string; firstMediaId: string }> {
|
): Promise<{ html: string; firstMediaId: string }> {
|
||||||
const imgRegex = /<img[^>]+src=["']([^"']+)["'][^>]*>/gi;
|
const imgRegex = /<img[^>]*\ssrc=["']([^"']+)["'][^>]*>/gi;
|
||||||
const matches = [...html.matchAll(imgRegex)];
|
const matches = [...html.matchAll(imgRegex)];
|
||||||
|
|
||||||
if (matches.length === 0) {
|
if (matches.length === 0) {
|
||||||
@@ -188,7 +188,7 @@ async function uploadImagesInHtml(
|
|||||||
let updatedHtml = html;
|
let updatedHtml = html;
|
||||||
|
|
||||||
for (const match of matches) {
|
for (const match of matches) {
|
||||||
const [, src] = match;
|
const [fullTag, src] = match;
|
||||||
if (!src) continue;
|
if (!src) continue;
|
||||||
|
|
||||||
if (src.startsWith("https://mmbiz.qpic.cn")) {
|
if (src.startsWith("https://mmbiz.qpic.cn")) {
|
||||||
@@ -198,15 +198,21 @@ async function uploadImagesInHtml(
|
|||||||
continue;
|
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 {
|
try {
|
||||||
const resp = await uploadImage(src, accessToken, baseDir);
|
const resp = await uploadImage(imagePath, accessToken, baseDir);
|
||||||
updatedHtml = updatedHtml.replace(src, resp.url);
|
const newTag = fullTag
|
||||||
|
.replace(/\ssrc=["'][^"']+["']/, ` src="${resp.url}"`)
|
||||||
|
.replace(/\sdata-local-path=["'][^"']+["']/, "");
|
||||||
|
updatedHtml = updatedHtml.replace(fullTag, newTag);
|
||||||
if (!firstMediaId) {
|
if (!firstMediaId) {
|
||||||
firstMediaId = resp.media_id;
|
firstMediaId = resp.media_id;
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} 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:
|
Options:
|
||||||
--title <title> Override title
|
--title <title> Override title
|
||||||
|
--summary <text> Article summary (for future use)
|
||||||
--theme <name> Theme name for markdown (default, grace, simple). Default: default
|
--theme <name> Theme name for markdown (default, grace, simple). Default: default
|
||||||
--cover <path> Cover image path (local or URL)
|
--cover <path> Cover image path (local or URL)
|
||||||
--dry-run Parse and render only, don't publish
|
--dry-run Parse and render only, don't publish
|
||||||
@@ -337,6 +344,7 @@ interface CliArgs {
|
|||||||
filePath: string;
|
filePath: string;
|
||||||
isHtml: boolean;
|
isHtml: boolean;
|
||||||
title?: string;
|
title?: string;
|
||||||
|
summary?: string;
|
||||||
theme: string;
|
theme: string;
|
||||||
cover?: string;
|
cover?: string;
|
||||||
dryRun: boolean;
|
dryRun: boolean;
|
||||||
@@ -358,12 +366,16 @@ function parseArgs(argv: string[]): CliArgs {
|
|||||||
const arg = argv[i]!;
|
const arg = argv[i]!;
|
||||||
if (arg === "--title" && argv[i + 1]) {
|
if (arg === "--title" && argv[i + 1]) {
|
||||||
args.title = argv[++i];
|
args.title = argv[++i];
|
||||||
|
} else if (arg === "--summary" && argv[i + 1]) {
|
||||||
|
args.summary = argv[++i];
|
||||||
} else if (arg === "--theme" && argv[i + 1]) {
|
} else if (arg === "--theme" && argv[i + 1]) {
|
||||||
args.theme = argv[++i]!;
|
args.theme = argv[++i]!;
|
||||||
} else if (arg === "--cover" && argv[i + 1]) {
|
} else if (arg === "--cover" && argv[i + 1]) {
|
||||||
args.cover = argv[++i];
|
args.cover = argv[++i];
|
||||||
} else if (arg === "--dry-run") {
|
} else if (arg === "--dry-run") {
|
||||||
args.dryRun = true;
|
args.dryRun = true;
|
||||||
|
} else if (arg.startsWith("--") && argv[i + 1] && !argv[i + 1]!.startsWith("-")) {
|
||||||
|
i++;
|
||||||
} else if (!arg.startsWith("-")) {
|
} else if (!arg.startsWith("-")) {
|
||||||
args.filePath = arg;
|
args.filePath = arg;
|
||||||
}
|
}
|
||||||
@@ -405,6 +417,15 @@ async function main(): Promise<void> {
|
|||||||
if (args.isHtml) {
|
if (args.isHtml) {
|
||||||
htmlPath = filePath;
|
htmlPath = filePath;
|
||||||
htmlContent = extractHtmlContent(htmlPath);
|
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) {
|
if (!title) {
|
||||||
title = extractHtmlTitle(fs.readFileSync(htmlPath, "utf-8"));
|
title = extractHtmlTitle(fs.readFileSync(htmlPath, "utf-8"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 absolutePath = path.isAbsolute(htmlFilePath) ? htmlFilePath : path.resolve(process.cwd(), htmlFilePath);
|
||||||
const fileUrl = `file://${absolutePath}`;
|
const fileUrl = `file://${absolutePath}`;
|
||||||
|
|
||||||
@@ -128,6 +128,28 @@ async function copyHtmlFromBrowser(cdp: CdpConnection, htmlFilePath: string): Pr
|
|||||||
await cdp.send('Runtime.enable', {}, { sessionId });
|
await cdp.send('Runtime.enable', {}, { sessionId });
|
||||||
await sleep(2000);
|
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...');
|
console.log('[wechat] Selecting #output content...');
|
||||||
await cdp.send<{ result: { value: unknown } }>('Runtime.evaluate', {
|
await cdp.send<{ result: { value: unknown } }>('Runtime.evaluate', {
|
||||||
expression: `
|
expression: `
|
||||||
@@ -176,7 +198,7 @@ async function parseMarkdownWithPlaceholders(markdownPath: string, theme?: strin
|
|||||||
return JSON.parse(output);
|
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');
|
const content = fs.readFileSync(htmlPath, 'utf-8');
|
||||||
|
|
||||||
let title = '';
|
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> {
|
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;
|
effectiveAuthor = effectiveAuthor || meta.author;
|
||||||
effectiveSummary = effectiveSummary || meta.summary;
|
effectiveSummary = effectiveSummary || meta.summary;
|
||||||
effectiveHtmlFile = htmlFile;
|
effectiveHtmlFile = htmlFile;
|
||||||
|
if (meta.contentImages.length > 0) {
|
||||||
|
contentImages = meta.contentImages;
|
||||||
|
}
|
||||||
console.log(`[wechat] Title: ${effectiveTitle || '(empty)'}`);
|
console.log(`[wechat] Title: ${effectiveTitle || '(empty)'}`);
|
||||||
console.log(`[wechat] Author: ${effectiveAuthor || '(empty)'}`);
|
console.log(`[wechat] Author: ${effectiveAuthor || '(empty)'}`);
|
||||||
console.log(`[wechat] Summary: ${effectiveSummary || '(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)`);
|
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)) {
|
if (effectiveHtmlFile && fs.existsSync(effectiveHtmlFile)) {
|
||||||
console.log(`[wechat] Copying HTML content from: ${effectiveHtmlFile}`);
|
console.log(`[wechat] Copying HTML content from: ${effectiveHtmlFile}`);
|
||||||
await copyHtmlFromBrowser(cdp, effectiveHtmlFile);
|
await copyHtmlFromBrowser(cdp, effectiveHtmlFile, contentImages);
|
||||||
await sleep(500);
|
await sleep(500);
|
||||||
console.log('[wechat] Pasting into editor...');
|
console.log('[wechat] Pasting into editor...');
|
||||||
await pasteFromClipboardInEditor(session);
|
await pasteFromClipboardInEditor(session);
|
||||||
|
|||||||
Reference in New Issue
Block a user