md2wx-cli 0.2.0__tar.gz → 0.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- md2wx_cli-0.2.2/PKG-INFO +254 -0
- md2wx_cli-0.2.2/README.md +239 -0
- {md2wx_cli-0.2.0 → md2wx_cli-0.2.2}/pyproject.toml +1 -1
- {md2wx_cli-0.2.0 → md2wx_cli-0.2.2}/src/md2wx/__init__.py +1 -1
- {md2wx_cli-0.2.0 → md2wx_cli-0.2.2}/src/md2wx/converter.py +43 -4
- {md2wx_cli-0.2.0 → md2wx_cli-0.2.2}/src/md2wx/styler.py +19 -1
- {md2wx_cli-0.2.0 → md2wx_cli-0.2.2}/src/md2wx/themes/bauhaus.yaml +208 -207
- {md2wx_cli-0.2.0 → md2wx_cli-0.2.2}/src/md2wx/themes/bold-green.yaml +199 -198
- {md2wx_cli-0.2.0 → md2wx_cli-0.2.2}/src/md2wx/themes/bold-navy.yaml +198 -197
- {md2wx_cli-0.2.0 → md2wx_cli-0.2.2}/src/md2wx/themes/default.yaml +1 -0
- {md2wx_cli-0.2.0 → md2wx_cli-0.2.2}/src/md2wx/themes/github-tech.yaml +2 -0
- md2wx_cli-0.2.2/src/md2wx_cli.egg-info/PKG-INFO +254 -0
- md2wx_cli-0.2.0/PKG-INFO +0 -184
- md2wx_cli-0.2.0/README.md +0 -169
- md2wx_cli-0.2.0/src/md2wx_cli.egg-info/PKG-INFO +0 -184
- {md2wx_cli-0.2.0 → md2wx_cli-0.2.2}/LICENSE +0 -0
- {md2wx_cli-0.2.0 → md2wx_cli-0.2.2}/setup.cfg +0 -0
- {md2wx_cli-0.2.0 → md2wx_cli-0.2.2}/src/md2wx/cli.py +0 -0
- {md2wx_cli-0.2.0 → md2wx_cli-0.2.2}/src/md2wx/clipboard.py +0 -0
- {md2wx_cli-0.2.0 → md2wx_cli-0.2.2}/src/md2wx/config.py +0 -0
- {md2wx_cli-0.2.0 → md2wx_cli-0.2.2}/src/md2wx/containers.py +0 -0
- {md2wx_cli-0.2.0 → md2wx_cli-0.2.2}/src/md2wx/fetcher.py +0 -0
- {md2wx_cli-0.2.0 → md2wx_cli-0.2.2}/src/md2wx/gallery.py +0 -0
- {md2wx_cli-0.2.0 → md2wx_cli-0.2.2}/src/md2wx/publisher.py +0 -0
- {md2wx_cli-0.2.0 → md2wx_cli-0.2.2}/src/md2wx/theme.py +0 -0
- {md2wx_cli-0.2.0 → md2wx_cli-0.2.2}/src/md2wx/theme_extractor.py +0 -0
- {md2wx_cli-0.2.0 → md2wx_cli-0.2.2}/src/md2wx/validator.py +0 -0
- {md2wx_cli-0.2.0 → md2wx_cli-0.2.2}/src/md2wx/wechat_api.py +0 -0
- {md2wx_cli-0.2.0 → md2wx_cli-0.2.2}/src/md2wx_cli.egg-info/SOURCES.txt +0 -0
- {md2wx_cli-0.2.0 → md2wx_cli-0.2.2}/src/md2wx_cli.egg-info/dependency_links.txt +0 -0
- {md2wx_cli-0.2.0 → md2wx_cli-0.2.2}/src/md2wx_cli.egg-info/entry_points.txt +0 -0
- {md2wx_cli-0.2.0 → md2wx_cli-0.2.2}/src/md2wx_cli.egg-info/requires.txt +0 -0
- {md2wx_cli-0.2.0 → md2wx_cli-0.2.2}/src/md2wx_cli.egg-info/top_level.txt +0 -0
md2wx_cli-0.2.2/PKG-INFO
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: md2wx-cli
|
|
3
|
+
Version: 0.2.2
|
|
4
|
+
Summary: 轻量级 Markdown 转微信公众号 HTML CLI 工具(支持剪贴板直达、主题提取、WeChatSync 联动)
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Requires-Python: >=3.11
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Dist: markdown>=3.10
|
|
10
|
+
Requires-Dist: beautifulsoup4>=4.14
|
|
11
|
+
Requires-Dist: cssutils>=2.11
|
|
12
|
+
Requires-Dist: pyyaml>=6.0
|
|
13
|
+
Requires-Dist: Pygments>=2.17
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
|
|
16
|
+
<div align="center">
|
|
17
|
+
|
|
18
|
+
<img src="examples/guide/images/cover.png" alt="md2wx-cli:Markdown 直达微信公众号" width="640">
|
|
19
|
+
|
|
20
|
+
# md2wx-cli
|
|
21
|
+
|
|
22
|
+
**从 Markdown 到微信公众号,全链路命令行闭环。**
|
|
23
|
+
|
|
24
|
+
[](https://pypi.org/project/md2wx-cli/)
|
|
25
|
+
[](https://pypi.org/project/md2wx-cli/)
|
|
26
|
+
[](LICENSE)
|
|
27
|
+
[](tests/)
|
|
28
|
+
|
|
29
|
+
*排版引擎 · 主题系统 · 草稿箱直传 · 合规体检 · 推文逆向 · AI 排版与写作技能栈*
|
|
30
|
+
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## 为什么需要它
|
|
36
|
+
|
|
37
|
+
写完一篇 Markdown,真正的麻烦才开始:贴进公众号编辑器样式全丢、代码块乱掉、外链变死文字、图片一张张重传。网页转换器要开网站、反复粘贴,改个错别字又得重来。
|
|
38
|
+
|
|
39
|
+
md2wx-cli 把这条流水线压成一条命令——**改完重跑一遍,十秒出新版**。
|
|
40
|
+
|
|
41
|
+
| 你写的 | 微信里呈现的 | 谁来处理 |
|
|
42
|
+
| :--- | :--- | :--- |
|
|
43
|
+
| 标准 Markdown | 全内联 CSS 排版 | 转换引擎 |
|
|
44
|
+
| ```` ```bash ```` 代码块 | 语法高亮 + 横向滚动 + 暗黑模式适配 | Pygments + 微信修复层 |
|
|
45
|
+
| `[链接](url)` | 文末上标脚注(微信正文禁止外链点击) | 链接转脚注 |
|
|
46
|
+
| 中英混排 | 自动加空格 | CJK 间距引擎 |
|
|
47
|
+
| 本地图片 | 自动上传微信永久图床并替换链接 | 发布管线 |
|
|
48
|
+
|
|
49
|
+
## 安装
|
|
50
|
+
|
|
51
|
+
Python ≥ 3.11。
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pip install md2wx-cli
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
免安装直接运行:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
uvx md2wx-cli article.md -c
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
> 命令入口名为 `md2wx`。PyPI 包名 `md2wx-cli` 与 import 名 `md2wx` 分离(`md2wx` 在 PyPI 上已被其他项目占用)。
|
|
64
|
+
|
|
65
|
+
## 快速开始
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# 转换并写入系统剪贴板,公众号编辑器 Ctrl+V 直接粘贴
|
|
69
|
+
md2wx article.md -c
|
|
70
|
+
|
|
71
|
+
# 输出 HTML 文件
|
|
72
|
+
md2wx article.md -o output.html
|
|
73
|
+
|
|
74
|
+
# 切换主题
|
|
75
|
+
md2wx article.md -t github-tech -c
|
|
76
|
+
|
|
77
|
+
# 生成完整 <html> 独立页面,双击即可预览
|
|
78
|
+
md2wx article.md -o preview.html --full-page
|
|
79
|
+
|
|
80
|
+
# 支持标准输入管道
|
|
81
|
+
cat article.md | md2wx -c
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## 主题系统
|
|
85
|
+
|
|
86
|
+
内置 5 套主题,`-t` 一键切换:
|
|
87
|
+
|
|
88
|
+
| 主题 | 风格 | 适合内容 |
|
|
89
|
+
| :--- | :--- | :--- |
|
|
90
|
+
| `default` | 现代极简杂志编辑风 | 通用,默认选项 |
|
|
91
|
+
| `github-tech` | GitHub 开发者文档风 | 教程、源码解析 |
|
|
92
|
+
| `bauhaus` | 包豪斯几何设计风 | 设计类、观点文 |
|
|
93
|
+
| `bold-green` | 清新高对比科技绿 | 环保、健康主题 |
|
|
94
|
+
| `bold-navy` | 深邃藏青商务编辑风 | 深度长文、行业分析 |
|
|
95
|
+
|
|
96
|
+
<img src="examples/guide/images/theme-gallery.png" alt="五套主题对比" width="640">
|
|
97
|
+
|
|
98
|
+
不确定选哪套?一条命令出全主题交互画廊,浏览器里秒级切换、选中即复制:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
md2wx gallery article.md -o gallery.html
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### 自定义与逆向提取
|
|
105
|
+
|
|
106
|
+
主题是 YAML 文件,字体栈、配色、标题层级、代码高亮全部可调。代码高亮配色默认按代码底色明暗自动选择(深底 `one-dark` / 浅底 `friendly`),也可显式指定:
|
|
107
|
+
|
|
108
|
+
```yaml
|
|
109
|
+
code_highlight_style: "one-dark" # 任意 Pygments 风格:dracula / monokai / nord / tango ...
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
还能从任意一篇微信推文或本地设计稿反向提取一套新主题:
|
|
113
|
+
|
|
114
|
+
<details>
|
|
115
|
+
<summary><b>learn-theme 用法展开</b></summary>
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
# 从一篇排版不错的微信推文学一套主题
|
|
119
|
+
md2wx learn-theme --url "https://mp.weixin.qq.com/s/xxxxxx" -n brand-editorial
|
|
120
|
+
|
|
121
|
+
# 从本地设计快照目录提取
|
|
122
|
+
md2wx learn-theme --dir ./design_folder -n brand-editorial
|
|
123
|
+
|
|
124
|
+
# 从单个 HTML 文件提取
|
|
125
|
+
md2wx learn-theme ./index.html -n brand-editorial
|
|
126
|
+
|
|
127
|
+
# 直接套用
|
|
128
|
+
md2wx article.md -t brand-editorial -c
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
提取内容覆盖 6 大组件规范与双字体栈,缺失组件由 `github-tech` 安全兜底。
|
|
132
|
+
|
|
133
|
+
</details>
|
|
134
|
+
|
|
135
|
+
## 发布到公众号草稿箱
|
|
136
|
+
|
|
137
|
+
前置条件:公众号 AppID / AppSecret(公众平台「设置与开发 → 基本配置」获取),且将本机出口 IP 加入后台 **IP 白名单**(否则报 `errcode=40164`)。
|
|
138
|
+
|
|
139
|
+
<details>
|
|
140
|
+
<summary><b>首次配置与一键直传</b></summary>
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
# 配置凭据,永久保存至 ~/.md2wx/config.yaml
|
|
144
|
+
md2wx config set wechat.appid "wx1234567890abcdef"
|
|
145
|
+
md2wx config set wechat.secret "你的AppSecret"
|
|
146
|
+
|
|
147
|
+
# 直传:本地图自动上微信永久图床并替换链接、封面上传为永久素材、创建草稿
|
|
148
|
+
md2wx article.md -p --title "文章标题" --author "作者" --cover images/cover.png
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
命令返回 `media_id`,登录 mp.weixin.qq.com 在草稿箱确认后手动群发。**直传只写草稿箱,不会自动群发。**
|
|
152
|
+
|
|
153
|
+
凭据读取优先级:命令行参数(`--appid` / `--secret`)> 环境变量 > `~/.md2wx/config.yaml`,方便在 CI 中注入。
|
|
154
|
+
|
|
155
|
+
> `--cover` 路径相对于输入 Markdown 文件所在目录解析,建议传绝对路径。
|
|
156
|
+
|
|
157
|
+
</details>
|
|
158
|
+
|
|
159
|
+
## 发布前体检与推文逆向
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
# 16+ 条微信平台硬规则检测:失效图片引用、裸外链、非法 CSS(fixed/grid/animation)
|
|
163
|
+
md2wx check article.md
|
|
164
|
+
|
|
165
|
+
# 抓取任意微信推文,连图带样式逆向回 Markdown
|
|
166
|
+
md2wx fetch "https://mp.weixin.qq.com/s/xxxxxx" -o draft.md
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
`fetch` 自动下载配图至 `draft-assets/`,居中高亮、白底卡片、实心横幅被识别还原为对应容器语法。
|
|
170
|
+
|
|
171
|
+
## 容器排版语法
|
|
172
|
+
|
|
173
|
+
标准 Markdown 之外,原生支持几种扩展容器(渲染为微信兼容的内联样式组件):
|
|
174
|
+
|
|
175
|
+
| 语法 | 渲染效果 | 适用场景 |
|
|
176
|
+
| :--- | :--- | :--- |
|
|
177
|
+
| `> 金句` | 浅蓝底居中加粗胶囊 | 核心观点,全文 1~3 处 |
|
|
178
|
+
| `::: banner 标题` … `:::` | 实色横幅 | 文末 CTA,0~1 处 |
|
|
179
|
+
| `::: card 标题` … `:::` | 白底描边浮起卡片 | 工具/项目推荐,0~2 处 |
|
|
180
|
+
| `::: center` … `:::` | 居中浅灰文字条 | 致谢、时间信息 |
|
|
181
|
+
| `> [!TIP]` / `[!WARNING]` | 功能提示 / 琥珀警示框 | 窍门与风险 |
|
|
182
|
+
| `✦`(独立成行) | 居中微淡星标分隔 | 正文与文末链接间 |
|
|
183
|
+
|
|
184
|
+
## AI 技能栈(Agent Skills)
|
|
185
|
+
|
|
186
|
+
不止是 CLI。本仓库 `skills/` 目录附带 5 个 Agent Skills(SKILL.md 规范),覆盖「写作 → 配图 → 排版 → 发布」四个环节,供 Claude Code、WorkBuddy 等 Agent 直接调用:
|
|
187
|
+
|
|
188
|
+
| 技能 | 环节 | 职责 |
|
|
189
|
+
| :--- | :---: | :--- |
|
|
190
|
+
| [`md2wx-use`](../skills/md2wx-use/) | 发布 | 指导 Agent 确定性地调用 CLI 全流程:排版、画廊、抓取、提取、体检、直传 |
|
|
191
|
+
| [`md2wx-format`](../skills/md2wx-format/) | 排版 | 按杂志级呼吸感标准重构 Markdown,输出 `-formatted.md`,附语法决策矩阵与密度约束 |
|
|
192
|
+
| [`writing-with-dna`](../skills/writing-with-dna/) | 写作 | 「活人感」中文创作框架:动笔前查个人写作 DNA、交互确认标题大纲、成稿存快照 |
|
|
193
|
+
| [`writing-visual`](../skills/writing-visual/) | 配图 | Q 版手绘科技图解与公众号封面图自动生成,产出插图版文章副本 |
|
|
194
|
+
| [`writing-dna-distill`](../skills/writing-dna-distill/) | 训练 | 从历史文章蒸馏个人写作 DNA,改稿 diff 飞轮持续进化 |
|
|
195
|
+
|
|
196
|
+
### 完整工作流
|
|
197
|
+
|
|
198
|
+
```
|
|
199
|
+
writing-dna-distill(蒸馏个人风格,一次性)
|
|
200
|
+
│
|
|
201
|
+
▼
|
|
202
|
+
writing-with-dna ──► writing-visual ──► md2wx-format ──► md2wx-use
|
|
203
|
+
从 0 写稿 生成配图 排版重构 转换 + 直传草稿箱
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
<details>
|
|
207
|
+
<summary><b>安装 Skills 到你的 Agent</b></summary>
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
# 用户级(跨项目可用)
|
|
211
|
+
cp -r skills/* ~/.workbuddy/skills/
|
|
212
|
+
|
|
213
|
+
# 或项目级(仅当前项目)
|
|
214
|
+
cp -r skills/* <你的项目>/.workbuddy/skills/
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
安装后对 Agent 说「转成微信排版」「生成主题画廊」「按我的 DNA 写一篇 XX」即可触发对应技能。
|
|
218
|
+
|
|
219
|
+
</details>
|
|
220
|
+
|
|
221
|
+
### 仓库结构
|
|
222
|
+
|
|
223
|
+
```
|
|
224
|
+
Wechatwriting/ ← 仓库根
|
|
225
|
+
├── md2wx-cli/ ← CLI 包(PyPI: md2wx-cli)
|
|
226
|
+
│ ├── src/md2wx/ ← 源码 + themes/ 内置主题
|
|
227
|
+
│ ├── tests/ ← 28 个测试用例
|
|
228
|
+
│ └── examples/guide/ ← 完整示例:文章 + 配图 + 三主题 HTML
|
|
229
|
+
└── skills/ ← 5 个 Agent Skills
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## 配置文件
|
|
233
|
+
|
|
234
|
+
`~/.md2wx/config.yaml`:
|
|
235
|
+
|
|
236
|
+
```yaml
|
|
237
|
+
wechat:
|
|
238
|
+
appid: "wx1234567890abcdef"
|
|
239
|
+
secret: "你的AppSecret"
|
|
240
|
+
author: "你的笔名"
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## 开发
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
git clone <repo-url>
|
|
247
|
+
cd <repo-url>/md2wx-cli
|
|
248
|
+
pip install -e .
|
|
249
|
+
python -m pytest tests/
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## License
|
|
253
|
+
|
|
254
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<img src="examples/guide/images/cover.png" alt="md2wx-cli:Markdown 直达微信公众号" width="640">
|
|
4
|
+
|
|
5
|
+
# md2wx-cli
|
|
6
|
+
|
|
7
|
+
**从 Markdown 到微信公众号,全链路命令行闭环。**
|
|
8
|
+
|
|
9
|
+
[](https://pypi.org/project/md2wx-cli/)
|
|
10
|
+
[](https://pypi.org/project/md2wx-cli/)
|
|
11
|
+
[](LICENSE)
|
|
12
|
+
[](tests/)
|
|
13
|
+
|
|
14
|
+
*排版引擎 · 主题系统 · 草稿箱直传 · 合规体检 · 推文逆向 · AI 排版与写作技能栈*
|
|
15
|
+
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 为什么需要它
|
|
21
|
+
|
|
22
|
+
写完一篇 Markdown,真正的麻烦才开始:贴进公众号编辑器样式全丢、代码块乱掉、外链变死文字、图片一张张重传。网页转换器要开网站、反复粘贴,改个错别字又得重来。
|
|
23
|
+
|
|
24
|
+
md2wx-cli 把这条流水线压成一条命令——**改完重跑一遍,十秒出新版**。
|
|
25
|
+
|
|
26
|
+
| 你写的 | 微信里呈现的 | 谁来处理 |
|
|
27
|
+
| :--- | :--- | :--- |
|
|
28
|
+
| 标准 Markdown | 全内联 CSS 排版 | 转换引擎 |
|
|
29
|
+
| ```` ```bash ```` 代码块 | 语法高亮 + 横向滚动 + 暗黑模式适配 | Pygments + 微信修复层 |
|
|
30
|
+
| `[链接](url)` | 文末上标脚注(微信正文禁止外链点击) | 链接转脚注 |
|
|
31
|
+
| 中英混排 | 自动加空格 | CJK 间距引擎 |
|
|
32
|
+
| 本地图片 | 自动上传微信永久图床并替换链接 | 发布管线 |
|
|
33
|
+
|
|
34
|
+
## 安装
|
|
35
|
+
|
|
36
|
+
Python ≥ 3.11。
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install md2wx-cli
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
免安装直接运行:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
uvx md2wx-cli article.md -c
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
> 命令入口名为 `md2wx`。PyPI 包名 `md2wx-cli` 与 import 名 `md2wx` 分离(`md2wx` 在 PyPI 上已被其他项目占用)。
|
|
49
|
+
|
|
50
|
+
## 快速开始
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# 转换并写入系统剪贴板,公众号编辑器 Ctrl+V 直接粘贴
|
|
54
|
+
md2wx article.md -c
|
|
55
|
+
|
|
56
|
+
# 输出 HTML 文件
|
|
57
|
+
md2wx article.md -o output.html
|
|
58
|
+
|
|
59
|
+
# 切换主题
|
|
60
|
+
md2wx article.md -t github-tech -c
|
|
61
|
+
|
|
62
|
+
# 生成完整 <html> 独立页面,双击即可预览
|
|
63
|
+
md2wx article.md -o preview.html --full-page
|
|
64
|
+
|
|
65
|
+
# 支持标准输入管道
|
|
66
|
+
cat article.md | md2wx -c
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## 主题系统
|
|
70
|
+
|
|
71
|
+
内置 5 套主题,`-t` 一键切换:
|
|
72
|
+
|
|
73
|
+
| 主题 | 风格 | 适合内容 |
|
|
74
|
+
| :--- | :--- | :--- |
|
|
75
|
+
| `default` | 现代极简杂志编辑风 | 通用,默认选项 |
|
|
76
|
+
| `github-tech` | GitHub 开发者文档风 | 教程、源码解析 |
|
|
77
|
+
| `bauhaus` | 包豪斯几何设计风 | 设计类、观点文 |
|
|
78
|
+
| `bold-green` | 清新高对比科技绿 | 环保、健康主题 |
|
|
79
|
+
| `bold-navy` | 深邃藏青商务编辑风 | 深度长文、行业分析 |
|
|
80
|
+
|
|
81
|
+
<img src="examples/guide/images/theme-gallery.png" alt="五套主题对比" width="640">
|
|
82
|
+
|
|
83
|
+
不确定选哪套?一条命令出全主题交互画廊,浏览器里秒级切换、选中即复制:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
md2wx gallery article.md -o gallery.html
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### 自定义与逆向提取
|
|
90
|
+
|
|
91
|
+
主题是 YAML 文件,字体栈、配色、标题层级、代码高亮全部可调。代码高亮配色默认按代码底色明暗自动选择(深底 `one-dark` / 浅底 `friendly`),也可显式指定:
|
|
92
|
+
|
|
93
|
+
```yaml
|
|
94
|
+
code_highlight_style: "one-dark" # 任意 Pygments 风格:dracula / monokai / nord / tango ...
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
还能从任意一篇微信推文或本地设计稿反向提取一套新主题:
|
|
98
|
+
|
|
99
|
+
<details>
|
|
100
|
+
<summary><b>learn-theme 用法展开</b></summary>
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
# 从一篇排版不错的微信推文学一套主题
|
|
104
|
+
md2wx learn-theme --url "https://mp.weixin.qq.com/s/xxxxxx" -n brand-editorial
|
|
105
|
+
|
|
106
|
+
# 从本地设计快照目录提取
|
|
107
|
+
md2wx learn-theme --dir ./design_folder -n brand-editorial
|
|
108
|
+
|
|
109
|
+
# 从单个 HTML 文件提取
|
|
110
|
+
md2wx learn-theme ./index.html -n brand-editorial
|
|
111
|
+
|
|
112
|
+
# 直接套用
|
|
113
|
+
md2wx article.md -t brand-editorial -c
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
提取内容覆盖 6 大组件规范与双字体栈,缺失组件由 `github-tech` 安全兜底。
|
|
117
|
+
|
|
118
|
+
</details>
|
|
119
|
+
|
|
120
|
+
## 发布到公众号草稿箱
|
|
121
|
+
|
|
122
|
+
前置条件:公众号 AppID / AppSecret(公众平台「设置与开发 → 基本配置」获取),且将本机出口 IP 加入后台 **IP 白名单**(否则报 `errcode=40164`)。
|
|
123
|
+
|
|
124
|
+
<details>
|
|
125
|
+
<summary><b>首次配置与一键直传</b></summary>
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
# 配置凭据,永久保存至 ~/.md2wx/config.yaml
|
|
129
|
+
md2wx config set wechat.appid "wx1234567890abcdef"
|
|
130
|
+
md2wx config set wechat.secret "你的AppSecret"
|
|
131
|
+
|
|
132
|
+
# 直传:本地图自动上微信永久图床并替换链接、封面上传为永久素材、创建草稿
|
|
133
|
+
md2wx article.md -p --title "文章标题" --author "作者" --cover images/cover.png
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
命令返回 `media_id`,登录 mp.weixin.qq.com 在草稿箱确认后手动群发。**直传只写草稿箱,不会自动群发。**
|
|
137
|
+
|
|
138
|
+
凭据读取优先级:命令行参数(`--appid` / `--secret`)> 环境变量 > `~/.md2wx/config.yaml`,方便在 CI 中注入。
|
|
139
|
+
|
|
140
|
+
> `--cover` 路径相对于输入 Markdown 文件所在目录解析,建议传绝对路径。
|
|
141
|
+
|
|
142
|
+
</details>
|
|
143
|
+
|
|
144
|
+
## 发布前体检与推文逆向
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
# 16+ 条微信平台硬规则检测:失效图片引用、裸外链、非法 CSS(fixed/grid/animation)
|
|
148
|
+
md2wx check article.md
|
|
149
|
+
|
|
150
|
+
# 抓取任意微信推文,连图带样式逆向回 Markdown
|
|
151
|
+
md2wx fetch "https://mp.weixin.qq.com/s/xxxxxx" -o draft.md
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
`fetch` 自动下载配图至 `draft-assets/`,居中高亮、白底卡片、实心横幅被识别还原为对应容器语法。
|
|
155
|
+
|
|
156
|
+
## 容器排版语法
|
|
157
|
+
|
|
158
|
+
标准 Markdown 之外,原生支持几种扩展容器(渲染为微信兼容的内联样式组件):
|
|
159
|
+
|
|
160
|
+
| 语法 | 渲染效果 | 适用场景 |
|
|
161
|
+
| :--- | :--- | :--- |
|
|
162
|
+
| `> 金句` | 浅蓝底居中加粗胶囊 | 核心观点,全文 1~3 处 |
|
|
163
|
+
| `::: banner 标题` … `:::` | 实色横幅 | 文末 CTA,0~1 处 |
|
|
164
|
+
| `::: card 标题` … `:::` | 白底描边浮起卡片 | 工具/项目推荐,0~2 处 |
|
|
165
|
+
| `::: center` … `:::` | 居中浅灰文字条 | 致谢、时间信息 |
|
|
166
|
+
| `> [!TIP]` / `[!WARNING]` | 功能提示 / 琥珀警示框 | 窍门与风险 |
|
|
167
|
+
| `✦`(独立成行) | 居中微淡星标分隔 | 正文与文末链接间 |
|
|
168
|
+
|
|
169
|
+
## AI 技能栈(Agent Skills)
|
|
170
|
+
|
|
171
|
+
不止是 CLI。本仓库 `skills/` 目录附带 5 个 Agent Skills(SKILL.md 规范),覆盖「写作 → 配图 → 排版 → 发布」四个环节,供 Claude Code、WorkBuddy 等 Agent 直接调用:
|
|
172
|
+
|
|
173
|
+
| 技能 | 环节 | 职责 |
|
|
174
|
+
| :--- | :---: | :--- |
|
|
175
|
+
| [`md2wx-use`](../skills/md2wx-use/) | 发布 | 指导 Agent 确定性地调用 CLI 全流程:排版、画廊、抓取、提取、体检、直传 |
|
|
176
|
+
| [`md2wx-format`](../skills/md2wx-format/) | 排版 | 按杂志级呼吸感标准重构 Markdown,输出 `-formatted.md`,附语法决策矩阵与密度约束 |
|
|
177
|
+
| [`writing-with-dna`](../skills/writing-with-dna/) | 写作 | 「活人感」中文创作框架:动笔前查个人写作 DNA、交互确认标题大纲、成稿存快照 |
|
|
178
|
+
| [`writing-visual`](../skills/writing-visual/) | 配图 | Q 版手绘科技图解与公众号封面图自动生成,产出插图版文章副本 |
|
|
179
|
+
| [`writing-dna-distill`](../skills/writing-dna-distill/) | 训练 | 从历史文章蒸馏个人写作 DNA,改稿 diff 飞轮持续进化 |
|
|
180
|
+
|
|
181
|
+
### 完整工作流
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
writing-dna-distill(蒸馏个人风格,一次性)
|
|
185
|
+
│
|
|
186
|
+
▼
|
|
187
|
+
writing-with-dna ──► writing-visual ──► md2wx-format ──► md2wx-use
|
|
188
|
+
从 0 写稿 生成配图 排版重构 转换 + 直传草稿箱
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
<details>
|
|
192
|
+
<summary><b>安装 Skills 到你的 Agent</b></summary>
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
# 用户级(跨项目可用)
|
|
196
|
+
cp -r skills/* ~/.workbuddy/skills/
|
|
197
|
+
|
|
198
|
+
# 或项目级(仅当前项目)
|
|
199
|
+
cp -r skills/* <你的项目>/.workbuddy/skills/
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
安装后对 Agent 说「转成微信排版」「生成主题画廊」「按我的 DNA 写一篇 XX」即可触发对应技能。
|
|
203
|
+
|
|
204
|
+
</details>
|
|
205
|
+
|
|
206
|
+
### 仓库结构
|
|
207
|
+
|
|
208
|
+
```
|
|
209
|
+
Wechatwriting/ ← 仓库根
|
|
210
|
+
├── md2wx-cli/ ← CLI 包(PyPI: md2wx-cli)
|
|
211
|
+
│ ├── src/md2wx/ ← 源码 + themes/ 内置主题
|
|
212
|
+
│ ├── tests/ ← 28 个测试用例
|
|
213
|
+
│ └── examples/guide/ ← 完整示例:文章 + 配图 + 三主题 HTML
|
|
214
|
+
└── skills/ ← 5 个 Agent Skills
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## 配置文件
|
|
218
|
+
|
|
219
|
+
`~/.md2wx/config.yaml`:
|
|
220
|
+
|
|
221
|
+
```yaml
|
|
222
|
+
wechat:
|
|
223
|
+
appid: "wx1234567890abcdef"
|
|
224
|
+
secret: "你的AppSecret"
|
|
225
|
+
author: "你的笔名"
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## 开发
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
git clone <repo-url>
|
|
232
|
+
cd <repo-url>/md2wx-cli
|
|
233
|
+
pip install -e .
|
|
234
|
+
python -m pytest tests/
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## License
|
|
238
|
+
|
|
239
|
+
[MIT](LICENSE)
|
|
@@ -36,6 +36,7 @@ class WeChatConverter:
|
|
|
36
36
|
def __init__(self, theme: Optional[Theme] = None, theme_name: str = "default"):
|
|
37
37
|
self._theme = theme if theme is not None else load_theme(theme_name)
|
|
38
38
|
self._css_rules = get_inline_css_rules(self._theme)
|
|
39
|
+
self._pygments_style = self._resolve_pygments_style()
|
|
39
40
|
|
|
40
41
|
def convert(self, markdown_text: str, base_dir: Optional[Path] = None) -> ConvertResult:
|
|
41
42
|
title = self._extract_title(markdown_text)
|
|
@@ -48,8 +49,8 @@ class WeChatConverter:
|
|
|
48
49
|
markdown_text = process_containers(markdown_text, accent)
|
|
49
50
|
markdown_text = self._fix_cjk_spacing(markdown_text)
|
|
50
51
|
|
|
51
|
-
# 2. 基础 Markdown → HTML
|
|
52
|
-
raw_html = self._markdown_to_html(markdown_text)
|
|
52
|
+
# 2. 基础 Markdown → HTML 解析(解包 codehilite 外层 div)
|
|
53
|
+
raw_html = self._unwrap_codehilite_div(self._markdown_to_html(markdown_text))
|
|
53
54
|
soup = BeautifulSoup(raw_html, "html.parser")
|
|
54
55
|
|
|
55
56
|
# 3. 本地图片路径转换与外框样式
|
|
@@ -132,9 +133,41 @@ class WeChatConverter:
|
|
|
132
133
|
"markdown.extensions.sane_lists",
|
|
133
134
|
"markdown.extensions.codehilite",
|
|
134
135
|
]
|
|
135
|
-
|
|
136
|
+
# 注意:extension_configs 的键必须与扩展注册路径完全一致,
|
|
137
|
+
# 使用短名 "codehilite" 在 Markdown 3.x 下会静默失效(noclasses/pygments_style 均不生效)
|
|
138
|
+
configs = {
|
|
139
|
+
"markdown.extensions.codehilite": {
|
|
140
|
+
"linenums": False,
|
|
141
|
+
"guess_lang": True,
|
|
142
|
+
"noclasses": True,
|
|
143
|
+
"pygments_style": self._pygments_style,
|
|
144
|
+
}
|
|
145
|
+
}
|
|
136
146
|
return markdown.Markdown(extensions=exts, extension_configs=configs).convert(text)
|
|
137
147
|
|
|
148
|
+
def _resolve_pygments_style(self) -> str:
|
|
149
|
+
"""解析代码高亮风格:主题显式声明优先,否则按 code_bg 亮度自动选择。"""
|
|
150
|
+
style = str(self._theme._raw_data.get("code_highlight_style", "")).strip()
|
|
151
|
+
if style:
|
|
152
|
+
return style
|
|
153
|
+
code_bg = str(self._theme.colors.get("code_bg", "#f8f8f8"))
|
|
154
|
+
m = re.search(r"^#?([0-9a-fA-F]{6})$", code_bg)
|
|
155
|
+
if m:
|
|
156
|
+
r, g, b = (int(m.group(1)[i : i + 2], 16) for i in (0, 2, 4))
|
|
157
|
+
# 相对亮度粗估:暗底用暗色系高亮风格,浅底用浅色系
|
|
158
|
+
if 0.299 * r + 0.587 * g + 0.114 * b < 128:
|
|
159
|
+
return "one-dark"
|
|
160
|
+
return "friendly"
|
|
161
|
+
|
|
162
|
+
def _unwrap_codehilite_div(self, raw_html: str) -> str:
|
|
163
|
+
"""解包 codehilite/highlight 包装 div,避免其内联背景色在微信端形成色差外框。"""
|
|
164
|
+
return re.sub(
|
|
165
|
+
r'<div class="(?:codehilite|highlight)"[^>]*>\s*(<pre.*?</pre>)\s*</div>',
|
|
166
|
+
r"\1",
|
|
167
|
+
raw_html,
|
|
168
|
+
flags=re.DOTALL,
|
|
169
|
+
)
|
|
170
|
+
|
|
138
171
|
def _process_images(self, soup: BeautifulSoup, base_dir: Optional[Path]) -> list[str]:
|
|
139
172
|
images = []
|
|
140
173
|
for img in soup.find_all("img"):
|
|
@@ -168,7 +201,13 @@ class WeChatConverter:
|
|
|
168
201
|
tokens.append(m.group(0))
|
|
169
202
|
return f"\x00TK{len(tokens)-1}\x00"
|
|
170
203
|
|
|
171
|
-
|
|
204
|
+
# 行内代码、URL、Markdown 图片/链接语法整体掩码保护:
|
|
205
|
+
# 否则 CJK 空格会插入图片/链接的相对路径(如 RAG解析_xxx → RAG 解析_xxx),导致资源找不到
|
|
206
|
+
masked = re.sub(
|
|
207
|
+
r"`[^`\n]+?`|https?://[^\s)\]]+|!?\[[^\]\n]*\]\([^)\n]+\)",
|
|
208
|
+
mask_inline,
|
|
209
|
+
line,
|
|
210
|
+
)
|
|
172
211
|
masked = re.sub(f"({cjk})({latin})", r"\1 \2", masked)
|
|
173
212
|
masked = re.sub(f"({latin})({cjk})", r"\1 \2", masked)
|
|
174
213
|
for idx, tok in enumerate(tokens):
|
|
@@ -107,7 +107,15 @@ def apply_inline_styles(soup: BeautifulSoup, theme: Theme, css_rules: dict) -> N
|
|
|
107
107
|
if extra:
|
|
108
108
|
img["style"] = f"{est}; {'; '.join(extra)}" if est else "; ".join(extra)
|
|
109
109
|
|
|
110
|
-
# 7.
|
|
110
|
+
# 7. 代码块横向滚动保障;pre 显式声明文字色(内层 code 为 color: inherit,
|
|
111
|
+
# 若 pre 缺省 color 会继承正文灰,在深色代码底上完全不可读)
|
|
112
|
+
def _is_dark_bg(bg: str) -> bool:
|
|
113
|
+
m = re.search(r"([0-9a-fA-F]{6})", bg or "")
|
|
114
|
+
if not m:
|
|
115
|
+
return False
|
|
116
|
+
r, g, b = (int(m.group(1)[i : i + 2], 16) for i in (0, 2, 4))
|
|
117
|
+
return 0.299 * r + 0.587 * g + 0.114 * b < 128
|
|
118
|
+
|
|
111
119
|
for pre in soup.find_all("pre"):
|
|
112
120
|
for code in pre.find_all("code"):
|
|
113
121
|
code["style"] = (
|
|
@@ -117,6 +125,16 @@ def apply_inline_styles(soup: BeautifulSoup, theme: Theme, css_rules: dict) -> N
|
|
|
117
125
|
"color: inherit !important; padding: 0 !important; border: none !important; "
|
|
118
126
|
"font-size: inherit; font-family: inherit; line-height: inherit"
|
|
119
127
|
)
|
|
128
|
+
# 解析 pre 现有内联样式(注意 "background-color" 含 "color" 子串,须按键精确判断)
|
|
129
|
+
pre_items = [item.split(":", 1) for item in pre.get("style", "").split(";") if ":" in item]
|
|
130
|
+
sdict = {k.strip(): v.strip() for k, v in pre_items}
|
|
131
|
+
if "color" not in sdict:
|
|
132
|
+
bg = sdict.get("background") or sdict.get("background-color") or str(colors.get("code_bg", ""))
|
|
133
|
+
if _is_dark_bg(bg):
|
|
134
|
+
fallback = str(colors.get("code_color", "#e2e8f0"))
|
|
135
|
+
else:
|
|
136
|
+
fallback = str(colors.get("text", "#333333"))
|
|
137
|
+
pre["style"] = f"color: {fallback}; {pre.get('style', '')}" if pre.get("style") else f"color: {fallback}"
|
|
120
138
|
|
|
121
139
|
|
|
122
140
|
def preserve_code_block_newlines(soup: BeautifulSoup) -> None:
|