Install
openclaw skills install @lutongsuo/html-to-wechatTake existing HTML content (file, URL, or pasted HTML) and publish it directly to WeChat Official Account draft box. No file conversion, no web scraping — just HTML to WeChat compatibility check to publish. Use when the user already has HTML and wants to push it to WeChat.
openclaw skills install @lutongsuo/html-to-wechatTake existing HTML content — a file, a URL, or pasted HTML — and publish it to the WeChat Official Account draft box in one streamlined workflow.
This is the simplest WeChat publishing skill: no file conversion, no web scraping. Just HTML, a compatibility check, and publish.
Accept requests like:
Return a published draft in the WeChat draft box, not a proposal.
You need a WeChat Official Account (服务号 or 订阅号 with API access).
Get your credentials:
SECURITY: NEVER hardcode credentials in scripts or source files. Always use environment variables or interactive prompts.
Set environment variables (recommended):
On macOS / Linux:
export WECHAT_APP_ID="your_appid_here"
export WECHAT_APP_SECRET="your_appsecret_here"
On Windows (PowerShell, persistent):
[Environment]::SetEnvironmentVariable("WECHAT_APP_ID", "your_appid_here", "User")
[Environment]::SetEnvironmentVariable("WECHAT_APP_SECRET", "your_appsecret_here", "User")
On Windows (Command Prompt, session only):
set WECHAT_APP_ID=your_appid_here
set WECHAT_APP_SECRET=your_appsecret_here
No environment variables set? The publish script will prompt interactively on first run. You can also pass credentials at runtime via Python subprocess (see Phase 5).
This skill depends on the anything-to-wechat skill from ClawHub:
clawhub install anything-to-wechat
publish_to_wechat.py (WeChat API publishing) and convert_for_wechat.py (HTML compatibility conversion)| Dependency | Required | Purpose |
|---|---|---|
beautifulsoup4 (pip) | Yes | HTML parsing for validation and conversion |
cssutils (pip) | Yes | CSS parsing for style inlining |
Pillow (pip) | Yes | Cover image compression |
requests (pip) | Yes | WeChat API HTTP calls |
| anything-to-wechat skill | Yes | publish_to_wechat.py and convert_for_wechat.py |
All pip dependencies auto-install on first use if missing.
Determine the source of the HTML content.
If the user has already provided one of the following, skip to Phase 2:
./article.html)Otherwise, ask the user:
Question: "请提供你想发布到微信公众号的 HTML 内容"
Options:
- "本地 HTML 文件路径"
- "粘贴 URL 链接"
- "直接粘贴 HTML 代码"
Handling each input type:
| Input Type | Action |
|---|---|
| Local file path | Read the file directly. Use the Read tool or open in Python. |
| URL | Use the WebFetch tool to retrieve the page HTML source. |
| Pasted HTML | Save the pasted content to a temporary file (e.g., <workspace>/input.html). |
Important for URLs: Use WebFetch only to fetch the raw HTML. If the page requires JavaScript rendering or returns a login wall, inform the user and ask them to provide the HTML file directly.
Save the HTML to <workspace>/input.html before proceeding.
Before converting, inspect the HTML for known WeChat incompatibilities.
Read the HTML file and check for the following issues:
| Issue | Detection Method | Severity |
|---|---|---|
<style> tags | Search for <style in HTML | HIGH -- WeChat removes all style blocks |
<link> stylesheet tags | Search for <link.*stylesheet | HIGH -- External stylesheets are stripped |
CSS variables (var(--xxx)) | Regex: var\(-- | HIGH -- Rendered as blank |
<script> tags | Search for <script | CRITICAL -- Removed entirely, may indicate interactive content |
position: fixed or position: sticky | Regex: position:\s*(fixed|sticky) | MEDIUM -- Layout breakage |
backdrop-filter | Search for backdrop-filter | LOW -- Visual degradation |
z-index | Regex: z-index | LOW -- Layering ignored |
External fonts (@font-face, Google Fonts) | Search for @font-face, fonts.googleapis | MEDIUM -- Font fallback used |
<iframe> embeds | Search for <iframe | HIGH -- Removed by WeChat |
<video> / <audio> tags | Search for <video, <audio | MEDIUM -- May not play |
| Requirement | Check Method |
|---|---|
| Inline styles only | Verify all elements use style="" attributes |
Light background (#ffffff or near-white) | Check background-color values |
| Max-width 680px on outer container | Check container width constraints |
| System fonts only | Verify font-family uses -apple-system, 'PingFang SC', 'Microsoft YaHei', sans-serif |
| Total HTML size under 2MB | Check file size |
Images via <img> tags with explicit styles | Verify image markup |
Decision after validation:
Report the validation results to the user with a brief summary:
[WeChat Compatibility Check]
- Found 2 <style> tags (will be inlined)
- Found CSS variables (will be resolved)
- Found position:sticky (will be removed)
- File size: 145KB (OK, under 2MB limit)
- Background: #ffffff (OK)
If Phase 2 found incompatibilities, run the conversion script from the anything-to-wechat skill:
python "<anything-to-wechat_skill_dir>/scripts/convert_for_wechat.py" \
--input "<workspace>/input.html" \
--output "<workspace>/wechat_article.html"
This script automatically:
<style> tags and inlines them as style="" attributesvar(--xxx)) to literal color valuesposition: fixed/sticky, backdrop-filter, z-index)After conversion: Re-validate the output by reading wechat_article.html and confirming:
<style> tags remain<script> tags remainstyle="" attributesIf any issues persist after conversion, manually fix them using the Edit tool:
<style> or <script> blocks#ffffffmax-width: 680px; margin: 0 auto; to the outermost containerfont-family: -apple-system, 'PingFang SC', 'Microsoft YaHei', sans-serif on the bodyIf no conversion was needed in Phase 2, copy the input file:
copy "<workspace>/input.html" "<workspace>/wechat_article.html"
WeChat requires a cover image for thumb_media_id upload. The image must be under 64KB.
Option A: User provides a cover image
If the user supplied a cover image path, use it directly and proceed to compression.
Option B: Generate a cover image
Use the ImageGen tool with a prompt derived from the HTML content:
<title> or first <h1> to determine the topic1024x768 (WeChat cover ratio 4:3)<workspace>/wechat_cover.pngCompress the cover image:
WeChat requires cover images (thumb_media_id) to be under 64KB.
python "<skill_dir>/scripts/compress_image.py" \
--input "<workspace>/wechat_cover.png" \
--output "<workspace>/wechat_cover_compressed.jpg" \
--max-size 64
Always run compression, even if the image seems small -- it will not enlarge files.
Fallback: If compress_image.py fails to reach 64KB, use ImageGen to regenerate a simpler cover image (fewer details, simpler composition, larger flat color areas) and try compression again.
Use publish_to_wechat.py from the anything-to-wechat skill.
IMPORTANT: Credentials must NEVER be hardcoded in scripts. Use environment variables or the Python subprocess pattern below.
WECHAT_APP_ID="$WECHAT_APP_ID" WECHAT_APP_SECRET="$WECHAT_APP_SECRET" \
python "<anything-to-wechat_skill_dir>/scripts/publish_to_wechat.py" \
--file "<workspace>/wechat_article.html" \
--title "<article_title>" \
--cover "<workspace>/wechat_cover_compressed.jpg" \
--digest "<article_summary_under_120_chars>"
On Windows, environment variable passing via set in cmd or $env: in PowerShell can be unreliable across sessions and may have encoding issues with Chinese characters. Use the Python subprocess pattern:
python -c "
import os, subprocess, sys
os.environ['WECHAT_APP_ID'] = '<app_id>'
os.environ['WECHAT_APP_SECRET'] = '<app_secret>'
result = subprocess.run([
sys.executable,
r'<anything-to-wechat_skill_dir>\scripts\publish_to_wechat.py',
'--file', r'<workspace>\wechat_article.html',
'--title', '<article_title>',
'--cover', r'<workspace>\wechat_cover_compressed.jpg',
'--digest', '<article_summary_under_120_chars>'
], capture_output=True, text=True, encoding='utf-8')
print(result.stdout)
print(result.stderr)
"
Key Windows notes:
encoding='utf-8' to subprocess.run to handle Chinese titles and digests correctlyr'...') for Windows file paths to avoid backslash escape issuesos.environ in the subprocess wrapper onlyIf credentials are not set: The script will prompt interactively for AppID and AppSecret.
Handle IP whitelist errors:
If the API returns ip not in whitelist:
<ip> 添加到微信公众号后台的 IP 白名单中(设置与开发 -> 基本配置 -> IP白名单)"After successful publishing, report to the user:
Include the following summary:
[Publish Complete]
- Title: <article_title>
- Media ID: <media_id>
- Cover image: <compressed_cover_path> (<file_size>KB)
- HTML file: <wechat_article_path> (<file_size>KB)
- Digest: <article_summary>
- Next step: Log in to https://mp.weixin.qq.com/ to review and publish
| Stripped | Effect |
|---|---|
<style> tags | All block-level CSS lost |
<link rel="stylesheet"> | External stylesheets removed |
CSS variables (var(--*)) | Rendered as empty / broken |
position: fixed / sticky | Layout elements may overlap or disappear |
backdrop-filter | Visual effect lost |
z-index | Element layering ignored |
@font-face / web fonts | Falls back to system font |
<script> tags | All JavaScript removed |
<iframe> tags | Embedded content removed |
| External images (some) | Must be uploaded to WeChat CDN |
| Requirement | Detail |
|---|---|
| Inline styles | All styling via style="" attributes on elements |
| Light background | #ffffff or near-white (WeChat renders on white) |
| Max-width 680px | Outer container width for article display |
| System fonts | -apple-system, 'PingFang SC', 'Microsoft YaHei', sans-serif |
| Under 2MB total | Combined HTML size limit |
<img> tags | Images must use <img> with inline width/height styles |
| Cover under 64KB | For thumb_media_id upload |
<section style="max-width: 680px; margin: 0 auto; padding: 20px; background: #ffffff; font-family: -apple-system, 'PingFang SC', 'Microsoft YaHei', sans-serif; color: #333333; line-height: 1.8;">
<h1 style="font-size: 22px; font-weight: bold; color: #1a1a1a; margin-bottom: 16px;">Article Title</h1>
<p style="font-size: 16px; margin-bottom: 16px;">Article content...</p>
<img src="..." style="width: 100%; height: auto; border-radius: 8px; margin: 16px 0;" />
</section>
| Error | Cause | Action |
|---|---|---|
<style> tags remain after conversion | Conversion script missed nested styles | Manually inline remaining styles with Edit tool |
| HTML file exceeds 2MB | Too many inline assets or large content | Simplify content, reduce image count, split into parts |
| Cover image > 64KB after compression | Image too complex for JPEG compression | Regenerate simpler cover via ImageGen, retry compression |
| Cover image upload fails | WeChat CDN rejection | Retry with different format (JPG instead of PNG) or smaller dimensions |
invalid content from WeChat API | Remaining unsupported CSS or HTML | Re-run convert_for_wechat.py, manually inspect output |
ip not in whitelist | Server IP not in WeChat backend whitelist | Extract IP from error, guide user to add it, retry |
access_token expired | Token TTL exceeded (7200s) | Re-fetch token and retry publish |
| No AppID / AppSecret | Credentials not configured | Script prompts interactively, or ask user to set env vars |
beautifulsoup4 not installed | Missing pip dependency | Auto-installed by convert_for_wechat.py on first run |
| Chinese characters garbled (Windows) | Console encoding not UTF-8 | Use encoding='utf-8' in Python subprocess pattern |
| File not found | Incorrect path or workspace issue | Verify path exists before proceeding |
| URL returns 403 / login wall | Site blocks automated fetching | Ask user to provide HTML file directly |
| Script | Location | Purpose |
|---|---|---|
compress_image.py | html-to-wechat/scripts/ | Compress images to meet WeChat 64KB cover limit |
convert_for_wechat.py | anything-to-wechat/scripts/ | Convert HTML with style tags to inline-style WeChat HTML |
publish_to_wechat.py | anything-to-wechat/scripts/ | Publish HTML to WeChat Official Account draft box |
| Variable | Required | Description |
|---|---|---|
WECHAT_APP_ID | Yes | WeChat Official Account AppID. Read from env var or prompted interactively. |
WECHAT_APP_SECRET | Yes | WeChat Official Account AppSecret. Read from env var or prompted interactively. |
Never commit credentials to version control. Always use environment variables, and the publish script falls back to interactive prompts when env vars are missing.