Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Post text, images, videos, and long-form articles to Weibo (微博) via Chrome browser automation.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
scripts/md-to-html.ts
1import fs from "node:fs";2import os from "node:os";3import path from "node:path";4import process from "node:process";56import {7extractSummaryFromBody,8extractTitleFromMarkdown,9parseFrontmatter,10pickFirstString,11renderMarkdownDocument,12replaceMarkdownImagesWithPlaceholders,13resolveColorToken,14resolveContentImages,15resolveImagePath,16serializeFrontmatter,17stripWrappingQuotes,18} from "baoyu-md";1920interface ImageInfo {21placeholder: string;22localPath: string;23originalPath: string;24alt?: string;25}2627interface ParsedMarkdown {28title: string;29summary: string;30shortSummary: string;31coverImage: string | null;32contentImages: ImageInfo[];33html: string;34}3536export async function parseMarkdown(37markdownPath: string,38options?: {39coverImage?: string;40title?: string;41tempDir?: string;42theme?: string;43color?: string;44citeStatus?: boolean;45},46): Promise<ParsedMarkdown> {47const content = fs.readFileSync(markdownPath, "utf-8");48const baseDir = path.dirname(markdownPath);49const tempDir = options?.tempDir ?? fs.mkdtempSync(path.join(os.tmpdir(), "weibo-article-images-"));5051const { frontmatter, body } = parseFrontmatter(content);5253let title = stripWrappingQuotes(options?.title ?? "")54|| stripWrappingQuotes(frontmatter.title ?? "")55|| extractTitleFromMarkdown(body);56if (!title) {57title = path.basename(markdownPath, path.extname(markdownPath));58}5960let summary = stripWrappingQuotes(frontmatter.summary ?? "")61|| stripWrappingQuotes(frontmatter.description ?? "")62|| stripWrappingQuotes(frontmatter.excerpt ?? "");63if (!summary) {64summary = extractSummaryFromBody(body, 44);65}66const shortSummary = extractSummaryFromBody(body, 44);6768const coverImagePath = stripWrappingQuotes(options?.coverImage ?? "")69|| pickFirstString(frontmatter, ["featureImage", "cover_image", "coverImage", "cover", "image"])70|| null;7172const { images, markdown: rewrittenBody } = replaceMarkdownImagesWithPlaceholders(73body,74"WBIMGPH_",75);76const rewrittenMarkdown = `${serializeFrontmatter(frontmatter)}${rewrittenBody}`;7778const { html } = await renderMarkdownDocument(rewrittenMarkdown, {79citeStatus: options?.citeStatus ?? false,80defaultTitle: title,81keepTitle: false,82primaryColor: resolveColorToken(options?.color),83theme: options?.theme,84});8586const contentImages = await resolveContentImages(images, baseDir, tempDir, "md-to-html");8788let resolvedCoverImage: string | null = null;89if (coverImagePath) {90resolvedCoverImage = await resolveImagePath(coverImagePath, baseDir, tempDir, "md-to-html");91}9293return {94title,95summary,96shortSummary,97coverImage: resolvedCoverImage,98contentImages,99html,100};101}102103async function main(): Promise<void> {104const args = process.argv.slice(2);105if (args.length === 0 || args.includes("--help") || args.includes("-h")) {106console.log(`Convert Markdown to HTML for Weibo article publishing107108Usage:109npx -y bun md-to-html.ts <markdown_file> [options]110111Options:112--title <title> Override title113--cover <image> Override cover image114--output <json|html> Output format (default: json)115--html-only Output only the HTML content116--save-html <path> Save HTML to file117--help Show this help118`);119process.exit(0);120}121122let markdownPath: string | undefined;123let title: string | undefined;124let coverImage: string | undefined;125let outputFormat: "json" | "html" = "json";126let htmlOnly = false;127let saveHtmlPath: string | undefined;128129for (let i = 0; i < args.length; i++) {130const arg = args[i]!;131if (arg === "--title" && args[i + 1]) {132title = args[++i];133} else if (arg === "--cover" && args[i + 1]) {134coverImage = args[++i];135} else if (arg === "--output" && args[i + 1]) {136outputFormat = args[++i] as "json" | "html";137} else if (arg === "--html-only") {138htmlOnly = true;139} else if (arg === "--save-html" && args[i + 1]) {140saveHtmlPath = args[++i];141} else if (!arg.startsWith("-")) {142markdownPath = arg;143}144}145146if (!markdownPath || !fs.existsSync(markdownPath)) {147console.error("Error: Valid markdown file path required");148process.exit(1);149}150151const result = await parseMarkdown(markdownPath, { title, coverImage });152153if (saveHtmlPath) {154fs.writeFileSync(saveHtmlPath, result.html, "utf-8");155console.error(`[md-to-html] HTML saved to: ${saveHtmlPath}`);156}157158if (htmlOnly || outputFormat === "html") {159console.log(result.html);160} else {161console.log(JSON.stringify(result, null, 2));162}163}164165if (import.meta.main ?? (process.argv[1] && path.resolve(process.argv[1]) === path.resolve(import.meta.filename ?? ""))) {166await main().catch((error) => {167console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);168process.exit(1);169});170}171