A Python tool to publish articles to WeChat Official Account (微信公众号) draft box via API. Supports multiple article formats/templates, automatic image upload, and Chinese text.
- 📝 Publish articles to WeChat Official Account draft box via API
- 🖼️ Automatic image upload and URL/media_id handling (permanent materials)
- 📋 Template system for different article formats (standard, news, tutorial, review, etc.)
- 🇨🇳 Full Chinese text support (UTF-8) with proper encoding
- 📄 Markdown to HTML conversion with inline styles for WeChat compatibility
- ✅ Validation before publishing (metadata, sections, images)
- 🔒 Secure credential management via YAML config or environment variables
- 🖥️ CLI interface with dry-run mode for testing
- 🗂️ Auto-detection of image folders based on article name
- Python 3.7+
- WeChat Official Account with API access enabled
- AppID and AppSecret credentials
-
Clone or download this repository
-
Install dependencies:
pip install -r requirements.txtRequired packages:
requests- HTTP client for WeChat APIpyyaml- YAML configuration parsingclick- CLI frameworkpython-dotenv- Environment variable managementjinja2- Template rendering engine
- Create config file:
cp config.yaml.example config.yaml- Edit
config.yamlwith your WeChat API credentials:
wechat:
app_id: "your_app_id_here"
app_secret: "your_app_secret_here"
default_author: "Your Name"
templates:
directory: "templates"
default: "standard"Or use environment variables:
export WECHAT_APP_ID="your_app_id"
export WECHAT_APP_SECRET="your_app_secret"
export WECHAT_DEFAULT_AUTHOR="Your Name"- Log in to WeChat Official Account Platform
- Go to Settings → Developer Settings (设置 → 开发设置)
- Enable API interface (启用接口)
- Create an AppID and AppSecret
- Add your AppID and AppSecret to
config.yaml
Note: Keep your credentials secure. Never commit config.yaml to version control.
# Publish article (images folder auto-detected)
python src/main.py articles/my_article.md
# Specify template
python src/main.py articles/my_article.md --template news
# Specify images folder explicitly
python src/main.py articles/my_article.md --images ./articles/my_article/images
# Preview without publishing (dry-run)
python src/main.py articles/my_article.md --dry-run
# List available templates
python src/main.py --list-templates悦彤有佳/
├── articles/ # Article storage directory
│ ├── example_article.md # Example article with frontmatter
│ ├── example_article/ # Images for example_article
│ │ ├── cover.jpg
│ │ └── image1.jpg
│ └── README.md # Article guidelines
├── src/ # Source code
│ ├── __init__.py
│ ├── main.py # CLI entry point
│ ├── article_parser.py # Markdown parser & HTML converter
│ ├── config.py # Configuration loader
│ ├── template_manager.py # Template validation & rendering
│ └── wechat_api.py # WeChat API client
├── templates/ # Article templates (YAML)
│ ├── standard.yaml # Default template
│ ├── news.yaml # News article format
│ ├── tutorial.yaml # Tutorial/how-to format
│ └── review.yaml # Review/analysis format
├── config.yaml # Configuration (not in git)
├── config.yaml.example # Configuration template
├── requirements.txt # Python dependencies
├── README.md # This file
└── QUICKSTART.md # Quick start guide
-
Article Parser (
article_parser.py)- Parses Markdown with YAML frontmatter
- Converts Markdown to HTML with inline styles
- Handles image references (multiple formats)
- Extracts content sections for template validation
- Supports headers, lists, blockquotes, emphasis, images
-
WeChat API Client (
wechat_api.py)- Access token management with automatic refresh
- Image upload (permanent materials with URL)
- Draft creation (supports single/multiple articles)
- Error handling with custom exceptions
-
Template Manager (
template_manager.py)- Loads templates from YAML files
- Validates articles against template requirements
- Renders HTML using Jinja2 templates
- Supports metadata, section, and image validation
-
Configuration (
config.py)- Loads from
config.yamlor environment variables - Supports
.envfiles via python-dotenv - Provides access to WeChat and template configs
- Loads from
-
CLI Interface (
main.py)- Command-line interface with Click framework
- Auto-detects image folders
- Dry-run mode for testing
- Template listing
- Comprehensive error messages and progress updates
Articles are plain text Markdown files (.md) with YAML frontmatter:
---
title: "我的文章标题"
author: "作者名称"
template: "standard"
publish_date: "2024-01-15"
category: "科技"
tags: ["人工智能", "机器学习"]
cover_image: "cover.jpg"
digest: "文章摘要,用于预览"
---
## 文章开头
这是文章内容的第一段。
### 小标题
更多内容,支持以下Markdown语法:
- **粗体文本** 使用 `**text**` 或 `__text__`
- *斜体文本* 使用 `*text*` 或 `_text_`
- 无序列表使用 `*` 或 `-`
- 有序列表使用 `1.` `2.` 等
### 插入图片

> 这是引用块,显示为带左边框的样式
---
这是分隔线。Supported Markdown Features:
- Headers:
#,##,###,#### - Bold:
**text**or__text__ - Italic:
*text*or_text_ - Lists: Unordered (
*,-) and ordered (1.,2.) - Blockquotes:
> text - Horizontal rules:
--- - Images:

Image Reference Formats:
- Markdown style:
(recommended) - Simple format:
[image:image.jpg] - Cover image: specified in frontmatter as
cover_image: "cover.jpg"
Important Notes:
- Article file contains only text and image references
- Actual image files must exist in the article's images folder
- Images are automatically uploaded to WeChat when publishing
- All HTML is generated with inline styles for WeChat compatibility
- Title is limited to 32 bytes (UTF-8 encoded, ~10-11 Chinese characters)
- Digest is limited to 120 characters
Templates are YAML files defining article structure and validation rules. Each template specifies:
- Required and optional metadata fields
- Required and optional content sections
- Image requirements (cover, max count)
- HTML rendering template (Jinja2)
Available templates:
standard- Basic article format (default)news- News article formattutorial- Tutorial/how-to formatreview- Product review format
Template Structure Example:
name: "standard"
display_name: "标准文章"
description: "基本文章模板,适用于一般文章格式"
metadata:
required: [title, author]
optional: [publish_date, category, tags, cover_image]
sections:
required: [body]
optional: [introduction, conclusion]
images:
cover_required: false
inline_allowed: true
max_count: 20
html_template: |
<div class="article-standard">
<h1>{{title}}</h1>
{% if author %}<div class="author">作者:{{author}}</div>{% endif %}
<div class="content">{{content}}</div>
</div>Creating Custom Templates:
- Create a new YAML file in
templates/directory - Define validation rules and HTML template
- Use in articles with
template: "your_template_name"
- Formats: JPG, JPEG, PNG only
- Size: Maximum 10MB per image
- Upload Type: Permanent materials (required for drafts)
- Returns: Both
media_id(for cover) andurl(for content images)
Each article should have its own images folder:
articles/
├── my_article.md
└── my_article/ ← Same name as article file
├── cover.jpg
├── image1.jpg
└── image2.png
The tool automatically:
- Detects image folder (named after article, or use
--imagesoption) - Finds image files referenced in article (case-insensitive matching)
- Validates format (.jpg, .jpeg, .png) and size (max 10MB)
- Uploads to WeChat permanent material library
- Receives
media_idandurlfrom WeChat API - Converts Markdown image references to HTML
<img>tags with inline styles - Includes both
data-mediaidandsrcattributes for compatibility
Markdown references are converted to HTML with inline styles:
Becomes:
<img data-mediaid="MEDIA_ID_HERE"
src="https://mmbiz.qpic.cn/..."
data-src="https://mmbiz.qpic.cn/..."
alt="Alt text"
style="max-width: 100%;height: auto;display: block;margin: 10px auto;" />Test your article without uploading to WeChat:
python src/main.py articles/my_article.md --dry-runThis will:
- ✅ Validate article structure and metadata
- ✅ Check all images exist
- ✅ Verify template requirements
- ✅ Show what would be uploaded
- ❌ Not upload images or create drafts
The tool automatically detects:
- Images folder: Looks for folder with same name as article
- Template: Uses default if not specified in frontmatter
- Image format: Supports case-insensitive matching
When publishing, the tool shows detailed progress:
- Access token status
- Each image upload with media_id and URL
- HTML conversion statistics
- Image tag generation details
- Duplicate URL detection (warnings)
- Access token caching: Tokens cached for ~2 hours, auto-refresh
- Permanent materials: Images uploaded as permanent materials (not temporary)
- Draft API: Uses WeChat draft creation API (not direct publish)
- URL handling: Converts HTTP to HTTPS automatically
- Error handling: Detailed error messages with codes
The parser supports case-insensitive image filename matching:
- Article references:
 - File on disk:
image1.jpg - Still works! ✅
File: articles/simple_article.md
---
title: "简单文章"
author: "作者"
---
这是文章内容。
Folder structure:
articles/
├── simple_article.md
└── simple_article/
└── example.jpg
Command:
python src/main.py articles/simple_article.mdFile: articles/news_article.md
---
title: "重要新闻"
author: "记者"
template: "news"
publish_date: "2024-01-15"
location: "北京"
cover_image: "cover.jpg"
---
这是新闻开头。
Folder structure:
articles/
├── news_article.md
└── news_article/
├── cover.jpg
└── news_photo.jpg
Command:
python src/main.py articles/news_article.md --template newsThe tool provides comprehensive error handling:
-
Missing credentials
Error: Configuration error: WeChat API credentials not found→ Create
config.yamlor set environment variables -
Missing images
Error: Missing image files in /path/to/images: - image1.jpg - image2.png→ Create images folder and add missing files
-
Validation errors
Validation errors: - Missing required metadata field: title - Missing required section: body→ Add required fields to article frontmatter
-
API errors
Error: Failed to get access token: 40001 - invalid credential→ Check AppID and AppSecret in config
-
Image format errors
Error: Unsupported image format: .gif (only JPG/PNG supported)→ Convert images to JPG or PNG
-
Image size errors
Error: Image file too large: 12582912 bytes (max 10485760 bytes)→ Compress images to under 10MB
Q: "Images folder not found"
- Ensure folder exists:
articles/my_article/forarticles/my_article.md - Or specify explicitly:
--images ./path/to/images
Q: "Some images were not converted"
- Check image filenames match exactly (case-insensitive supported)
- Ensure image files exist in images folder
- Use supported reference formats:

Q: "WARNING: All images may be the same!"
- WeChat returned same URL for different images
- Check if you accidentally uploaded same image multiple times
- Verify each image file is unique
Q: "Title truncated to 32 bytes"
- WeChat limit: 32 bytes UTF-8 (≈10-11 Chinese characters)
- Tool automatically truncates with "..." suffix
- Consider shortening title in frontmatter
Q: "Access token expired"
- Token automatically refreshes (7200s = 2 hours)
- Manual refresh: restart the tool
- Check network connection to api.weixin.qq.com
For detailed debugging, check the .cursor/debug.log file which contains:
- Image upload details
- Image mapping information
- HTML conversion steps
- API responses
- Check that image files exist in the images folder
- Verify filenames match exactly (case-sensitive on some systems)
- Make sure images are JPG or PNG format
- Check available templates with
--list-templates - Verify template name is correct
- Make sure template YAML files are in
templates/directory
- Check your AppID and AppSecret are correct
- Verify API access is enabled in WeChat Official Account settings
- Check rate limits (2000 image uploads per day)
- Ensure images are valid format and size
The codebase follows Python best practices:
- Type hints for function parameters and returns
- Docstrings for all classes and methods
- Exception handling with custom exception classes
- Separation of concerns (parsing, API, templates, config)
- UTF-8 encoding throughout for Chinese text support
src/
├── main.py # CLI entry point with Click framework
├── article_parser.py # Markdown parser & HTML converter
│ └── ArticleParser # Parse frontmatter, convert Markdown to HTML
├── wechat_api.py # WeChat API client
│ └── WeChatAPI # Token management, image upload, draft creation
├── template_manager.py # Template validation & rendering
│ └── TemplateManager # Load templates, validate, render with Jinja2
└── config.py # Configuration loader
└── Config # Load from YAML or environment variables
To test the tool:
-
Dry run (no upload):
python src/main.py articles/example_article.md --dry-run
-
Validate configuration:
python -c "from src.config import Config; c = Config(); print(c.get_wechat_config())" -
List templates:
python src/main.py --list-templates
Adding New Templates:
- Create a new YAML file in
templates/ - Define metadata requirements, sections, and HTML template
- Reference in articles:
template: "your_template"
Adding New Image Reference Formats:
Edit article_parser.py and add pattern to IMAGE_PATTERNS:
IMAGE_PATTERNS = [
(r'!\[([^\]]*)\]\(([^)]+)\)', 'markdown'),
(r'\[image:([^\]]+)\]', 'simple'),
(r'YOUR_NEW_PATTERN', 'custom'),
]Adding New Markdown Features:
Edit the convert_to_html() method in article_parser.py
| Package | Version | Purpose |
|---|---|---|
requests |
≥2.31.0 | HTTP client for WeChat API calls |
pyyaml |
≥6.0 | Parse YAML configuration and templates |
click |
≥8.1.0 | CLI framework for command-line interface |
python-dotenv |
≥1.0.0 | Load environment variables from .env |
jinja2 |
≥3.1.0 | Template rendering engine for HTML |
-
Get Access Token
- Endpoint:
/cgi-bin/token - Method: GET
- Params:
grant_type,appid,secret - Cached for ~2 hours (7200s)
- Endpoint:
-
Upload Permanent Material
- Endpoint:
/cgi-bin/material/add_material - Method: POST
- Type: image
- Returns:
media_idandurl - Max size: 10MB
- Formats: JPG, PNG
- Endpoint:
-
Create Draft
- Endpoint:
/cgi-bin/draft/add - Method: POST
- Accepts: Array of articles with
title,author,content,thumb_media_id - Returns: Draft
media_id
- Endpoint:
config.yaml:
wechat:
app_id: "wx..." # WeChat AppID (required)
app_secret: "..." # WeChat AppSecret (required)
default_author: "作者" # Default author name (optional)
templates:
directory: "templates" # Templates directory (default: "templates")
default: "standard" # Default template (default: "standard")Environment Variables:
WECHAT_APP_ID- WeChat AppIDWECHAT_APP_SECRET- WeChat AppSecretWECHAT_DEFAULT_AUTHOR- Default author name
Contributions welcome! Areas for improvement:
-
Support for more Markdown features (tables, code blocks with syntax highlighting)
-
Batch publishing multiple articles
-
Direct publish (not just draft)
-
Support for video uploads
-
Web interface (Flask/FastAPI)
-
Article scheduling
-
Image optimization/compression before upload
-
Retry logic for failed uploads
-
Progress bar for large batches
-
Image optimization/compression before upload
-
Retry logic for failed uploads
-
Progress bar for large batches
- Never commit
config.yamlwith real credentials to version control (already in.gitignore) - Use
.envfiles for local development (also gitignored) - Rotate AppSecret regularly via WeChat Official Account platform
- Use environment variables in production environments
- Keep
requirements.txtupdated for security patches - Limit API access - only enable necessary permissions
- Monitor usage - WeChat has rate limits (2000 image uploads/day)
- Title limited to 32 bytes UTF-8 (≈10-11 Chinese characters) due to WeChat API
- Digest limited to 120 characters
- Maximum 20 images per article (can be configured in template)
- Maximum 10MB per image
- Only JPG and PNG formats supported
- Creates drafts only (not direct publish)
- No support for video/audio yet
- No batch processing of multiple articles
- ✅ Markdown to HTML conversion with inline styles
- ✅ Multiple template support (standard, news, tutorial, review)
- ✅ Permanent material upload with URL support
- ✅ Case-insensitive image matching
- ✅ Dry-run mode for testing
- ✅ Comprehensive error handling
- ✅ Auto-detection of image folders
- ✅ Debug logging for troubleshooting
- ✅ UTF-8 Chinese text support throughout
- WeChat Official Account Platform API documentation
- Python community for excellent libraries (requests, click, jinja2, pyyaml)
- Contributors and testers
This project is provided as-is for educational and personal use purposes.
For issues or questions:
- Check the Error Handling & Troubleshooting section above
- Review QUICKSTART.md for step-by-step guide
- Check WeChat Official Account API documentation
- Verify your API credentials and permissions
- Look at example articles in
articles/directory
Last Updated: January 2026
Python Version: 3.7+
Platform: Cross-platform (macOS, Linux, Windows)
Author: 悦彤有佳 Development Team