本工具将 Markdown 转换为符合项目中模板的微信公众号 HTML。模板位于 templates/ 目录,生成的 HTML 将内嵌这些模板片段以满足样式要求。
- 正文区块:除最后的结束符外,所有内容都包裹在
templates/正文区块.html的{content}中。 - 空行:遇到空行时,在当前区块末尾插入一次
templates/空行.html,随后结束当前区块并重启新正文区块。 - 分隔符:
---或***作为分隔符,单独顶层输出templates/分割线.html,并重启正文区块。 - 标题:
# 标题->templates/一级标题.html(自动递增 Part.{index},两位数)## 标题->templates/二级标题.html
- 引用:连续以
>开头的行会合并为一个引用块,使用templates/引用.html。 - 代码块:围栏代码(``` 或 ~~~)不会使用代码模板,而是通过
templates/文本.html嵌入文本,内容为 `code:` 前缀 + 代码,换行用 `\n` 字面量。 - 图片:Markdown 图片语法会插入
templates/图片.html模板。模板内置示例图片,脚本不会替换图片 URL。 - 列表/表格:不会直接嵌入示例模板,脚本会按项目样式生成
ul/ol/table的 HTML,并在li/td中嵌入文本模板样式。 - 固定区块:
- 文章开头:在首个正文区块内插入
templates/关注我们_top.html - 文章结尾:在最后一个正文区块内插入
templates/关注我们_bottom.html - 页面最末尾:
templates/结束符.html
- 文章开头:在首个正文区块内插入
# 生成同名 .html
python md2wechat.py input.md
# 指定输出路径
python md2wechat.py input.md -o out.htmlmd2wechat.py转换脚本templates/模板目录(必须存在)
- 图片模板为固定示例图,如需替换为 Markdown 中的图片,请在生成后手工替换或二次开发脚本(搜索
tpl_img)。 - 列表缩进以 2 个空格为一个层级;不同 Markdown 编辑器的缩进可能不同,必要时先规范缩进。
- 表格支持常见管道风格(带分隔行
| --- | --- |)。
项目已提供 sample.md 示例:
python md2wechat.py sample.md -o sample.html转换成功后,会在项目根目录生成 sample.html,可在浏览器或公众号编辑器中查看效果。
本项目提供一个基于 Streamlit 的投稿入口:上传 .md 或包含多篇文章与资源的 .zip,自动转换并上报至远端 FastAPI 存储。
pip install -r requirements.txt
streamlit run app.py首次运行后,浏览器访问 http://localhost:8501。
创建或编辑 /.streamlit/secrets.toml:
[remote]
# 远端 FastAPI 服务根地址(不要以 / 结尾)
base_url = "https://your-remote-host:8443"
# 访问令牌(如服务端需要鉴权,则必填)
token = "your-token"
# 测试环境可关闭证书校验;生产请改为 true
verify_ssl = false
[smtp]
host = "smtp.example.com"
port = 465
username = "bot@example.com"
password = "******"
from = "bot@example.com"
to_admin = "admin@example.com"
reply_to = "noreply@example.com"
# 可选:同步调试模式(将后台任务改为前台执行,便于页面直接显示错误)
[debug]
sync = falseapp.py中的execute_job()调用services/processor.py的process_upload()完成转换:- 生成
out/*.html、meta.json
- 生成
services/remote_api.py将上述产物打包为payload.zip,并连同manifest表单字段一起POST至服务端:- 表单字段:
manifest(JSON 字符串) - 文件字段:
payload_zip(application/zip)
- 表单字段:
- 发送管理员邮件摘要(如配置了 SMTP)。
manifest 典型字段:
{
"wechat": "wx_id",
"email": "user@example.com",
"original_filename": "upload.zip",
"folder_name": "20250101_foo_example.com",
"timestamp": "20250101_120000",
"duration_ms": 1234
}POST {base_url}/api/submissions- headers:
Authorization: Bearer <token>(如启用鉴权) - form-data:
manifest(JSON 字符串) - files:
payload_zip(zip,内部包含out/*.html、meta.json、uploads/<原始文件>) - 响应示例:
{"folder": "TEST_20250101_120000"}
- headers:
项目内含 test/server_test.py 作为简单连通性示例(演示最小表单模式);实际应用侧使用的是 zip+manifest 方案,详见 services/remote_api.py。
app.pyStreamlit 投稿入口services/processor.py处理上传与 Markdown 转换services/remote_api.py打包payload.zip并提交至远端services/mailer.py管理员通知邮件templates/微信样式模板片段(必须存在)
- 警告
InsecureRequestWarning: Unverified HTTPS request ...:- 在开发/内网测试可将
verify_ssl=false;若为公网生产,请配置有效证书并设为true。
- 在开发/内网测试可将
- 页面无响应或被中止:
- 检查远端服务可达性、
base_url是否正确、token 是否有效; - 如需在页面直接看到错误细节,将
secrets.toml中的[debug].sync = true临时开启同步调试。
- 检查远端服务可达性、
- 提交成功但页面无“远端存储”输出:
- 服务端应在响应 JSON 中返回
folder字段;若无该字段,客户端只会显示“未创建”。
- 服务端应在响应 JSON 中返回
.gitignore 已忽略 __pycache__/、虚拟环境目录、构建缓存与 /.streamlit/secrets.toml 等敏感文件,避免误提交。