
Wechat Article Publisher
Turn a Markdown draft into WeChat Official Account–ready title, cover, positioned images, and HTML body via a structured parse script.
Overview
Wechat Article Publisher is an agent skill for the Grow phase that parses Markdown into WeChat Official Account title, cover, block-indexed images, and HTML content.
Install
npx skills add https://github.com/iamzifei/wechat-article-publisher-skill --skill wechat-article-publisherWhat is this skill?
- Extracts title from first H1/H2 or first line of Markdown
- Selects cover image as the first image in the document
- Maps content images with block_index and after_text for precise insertion order
- Outputs JSON or HTML with images stripped from body markup for publisher APIs
- Splits Markdown into logical blocks including code fences and quotes
- Content images include block_index (0-indexed), path, and after_text context fields in JSON output
Adoption & trust: 2.1k installs on skills.sh; 142 GitHub stars; 2/3 security scanners passed (skills.sh audits).
What problem does it solve?
You have a Markdown article but WeChat publishing needs a title, cover asset, ordered inline images, and clean HTML—not raw MD pasted by hand.
Who is it for?
Builders shipping content to Chinese audiences who maintain posts in Markdown and automate WeChat OA uploads with local image paths.
Skip if: Teams that only publish to English blogs or need full WeChat API OAuth, scheduling, and analytics inside one skill.
When should I use this skill?
Converting a Markdown draft into WeChat-ready title, cover, HTML, and positioned images before upload.
What do I get? / Deliverables
You get machine-readable JSON or HTML with block_index image placement so an agent or script can publish to WeChat OA without manual reformatting.
- JSON bundle with title, cover_image, content_images, html, and total_blocks
- Optional HTML-only export for manual or scripted publishing
Recommended Skills
Journey fit
Publishing formatted articles to WeChat OA is a recurring growth motion once the product story exists, not a one-off validate prototype. Content is the shelf for repurposing Markdown into channel-specific HTML and image placement for Chinese social distribution.
How it compares
Use as a Markdown-to-WeChat structure converter instead of a general static-site generator or generic HTML exporter.
Common Questions / FAQ
Who is wechat-article-publisher for?
It is for solo founders and marketers who draft in Markdown and need structured title, cover, and image metadata for WeChat Official Account publishing workflows.
When should I use wechat-article-publisher?
Use it in Grow content work when preparing a post for WeChat OA, batch-converting MD drafts, or feeding an upload automation that needs block_index image positions.
Is wechat-article-publisher safe to install?
It is primarily a local file parser; review the Security Audits panel on this page and avoid pointing it at untrusted Markdown that references sensitive paths.
SKILL.md
READMESKILL.md - Wechat Article Publisher
#!/usr/bin/env python3 """ Parse Markdown for WeChat Official Account article publishing. Extracts: - Title (from first H1/H2 or first line) - Cover image (first image) - Content images with block index for precise positioning - HTML content (images stripped) Usage: python parse_markdown.py <markdown_file> [--output json|html] Output (JSON): { "title": "Article Title", "cover_image": "/path/to/cover.jpg", "content_images": [ {"path": "/path/to/img.jpg", "block_index": 3, "after_text": "context..."}, ... ], "html": "<p>Content...</p><h2>Section</h2>...", "total_blocks": 25 } The block_index indicates which block element (0-indexed) the image should follow. This allows precise positioning without relying on text matching. """ import argparse import json import os import re import sys from pathlib import Path def split_into_blocks(markdown: str) -> list[str]: """Split markdown into logical blocks (paragraphs, headers, quotes, code blocks, etc.).""" blocks = [] current_block = [] in_code_block = False code_block_lines = [] lines = markdown.split('\n') for line in lines: stripped = line.strip() # Handle code block boundaries if stripped.startswith('```'): if in_code_block: # End of code block in_code_block = False if code_block_lines: # Mark as code block with special prefix for later processing # Use ___CODE_BLOCK_START___ and ___CODE_BLOCK_END___ to preserve content blocks.append('___CODE_BLOCK_START___' + '\n'.join(code_block_lines) + '___CODE_BLOCK_END___') code_block_lines = [] else: # Start of code block if current_block: blocks.append('\n'.join(current_block)) current_block = [] in_code_block = True continue # If inside code block, collect ALL lines (including empty lines) if in_code_block: code_block_lines.append(line) continue # Empty line signals end of block if not stripped: if current_block: blocks.append('\n'.join(current_block)) current_block = [] continue # Headers, blockquotes are their own blocks if stripped.startswith(('#', '>')): if current_block: blocks.append('\n'.join(current_block)) current_block = [] blocks.append(stripped) continue # Image on its own line is its own block if re.match(r'^!\[.*\]\(.*\)$', stripped): if current_block: blocks.append('\n'.join(current_block)) current_block = [] blocks.append(stripped) continue current_block.append(line) if current_block: blocks.append('\n'.join(current_block)) # Handle unclosed code block if code_block_lines: blocks.append('___CODE_BLOCK_START___' + '\n'.join(code_block_lines) + '___CODE_BLOCK_END___') return blocks def extract_images_with_block_index(markdown: str, base_path: Path) -> tuple[list[dict], str, int]: """Extract images with their block index position. Returns: (image_list, markdown_without_images, total_blocks) """ blocks = split_into_blocks(markdown) images = [] clean_blocks = [] img_pattern = re.compile(r'^!\[([^\]]*)\]\(([^)]+)\)$') for i, block in enumerate(blocks): match = img_pattern.match(block.strip()) if match: alt_text = match.group(1) img_path = match.group(2) # Resolve relative paths if not os.path.isabs(img_path): full_path = str(base_path / img_path) else: full_path = img_path # block_index is the index