wechat-media-writer 2.2.3 → 2.2.4

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.
package/SKILL.md CHANGED
@@ -137,9 +137,9 @@ Token 缓存在 `~/.wechat/token_cache.json`(自动管理,2小时有效期
137
137
  <span>副标题(一句话点睛)</span>
138
138
  </p>
139
139
 
140
- <!-- 书籍封面图(第一张图,使用书籍原封面) -->
140
+ <!-- 主题配图(第一张图,使用主题贴图,非书籍原图) -->
141
141
  <p style="margin:0 0 20px;text-align:center;">
142
- <img src="{BOOK_COVER_URL}" style="width:100%;max-width:600px;display:block;margin:0 auto;border-radius:4px;" alt="《书名》封面" />
142
+ <img src="{THEME_IMG_1}" style="width:100%;max-width:600px;display:block;margin:0 auto;border-radius:4px;" alt="主题配图" />
143
143
  </p>
144
144
 
145
145
  <!-- 章节(无数字编号,直接标题) -->
@@ -150,11 +150,26 @@ Token 缓存在 `~/.wechat/token_cache.json`(自动管理,2小时有效期
150
150
  <span>正文段落。所有段落必须有 text-indent:2em。</span>
151
151
  </p>
152
152
 
153
- <!-- 引用块 -->
153
+ <!-- 主题配图(章节间穿插,与文风统一) -->
154
+ <p style="margin:0 0 20px;text-align:center;">
155
+ <img src="{THEME_IMG_2}" style="width:100%;max-width:600px;display:block;margin:0 auto;border-radius:4px;" alt="主题配图" />
156
+ </p>
157
+
158
+ <!-- 金句引用块(用主题色高亮,加左侧色条) -->
154
159
  <p
155
- style="font-size:17px;line-height:1.9;color:rgb(T_R,T_G,T_B);font-weight:bold;text-indent:2em;margin:0 0 20px;padding:14px 18px;background:rgb(CBG);border-radius:8px;"
160
+ style="font-size:17px;line-height:1.9;color:{T};font-weight:bold;text-indent:2em;margin:0 0 20px;padding:14px 18px;background:{CBG};border-left:4px solid {T2};border-radius:6px;"
156
161
  >
157
- <span>金句或核心观点引用。</span>
162
+ <span style="color:{T2};">▍</span> 金句或核心观点引用。整段用主题主色加粗,加左侧强调色条,与正文形成视觉对比。
163
+ </p>
164
+
165
+ <!-- 重点启发语句(行内主题色 + 强调色高亮) -->
166
+ <p style="font-size:16px;line-height:2;color:{BODY};margin:0 0 16px;text-indent:2em;">
167
+ <span>正文段落中关键句子用 <strong style="color:{T};background:{CBG};padding:1px 6px;border-radius:3px;">主题主色高亮</strong>,核心概念用 <span style="color:{T2};font-weight:bold;">强调色加粗</span>,让读者一眼锁定重点。</span>
168
+ </p>
169
+
170
+ <!-- 主题配图(章节中段) -->
171
+ <p style="margin:0 0 20px;text-align:center;">
172
+ <img src="{THEME_IMG_3}" style="width:100%;max-width:600px;display:block;margin:0 auto;border-radius:4px;" alt="主题配图" />
158
173
  </p>
159
174
 
160
175
  <!-- 分隔符(章节之间) -->
@@ -162,54 +177,59 @@ Token 缓存在 `~/.wechat/token_cache.json`(自动管理,2小时有效期
162
177
  </section>
163
178
  ```
164
179
 
165
- ### 关键排版规则
180
+ ### 关键排版规则
166
181
 
167
182
  1. **所有正文段落必须有 `text-indent:2em`** — 首行缩进
168
183
  2. **图片URL必须用完整的 `mmbiz.qpic.cn` 硬编码URL** — 不用模板变量
169
184
  3. **`<img>` 必须包裹在 `<p>` 中**,且 `style="width:100%;display:block;border-radius:4px;"`
170
185
  4. **行高统一 `line-height:2`** — 阅读舒适
171
- 5. **引用块用 `<p>` + `background` + `border-radius:8px`** — 不是 `<blockquote>`
186
+ 5. **引用块用 `<p>` + `background` + `border-radius:8px` + 左侧 `border-left:4px solid {T2}` 色条** — 不是 `<blockquote>`
172
187
  6. **章节标题不使用数字编号** — 直接用"作者背景与创作时代",不用"壹"
173
188
  7. **强调文字用 `<strong style="color:{T};">`** — 不是加粗或改色
174
- 8. **星标用 `<span>★★★★☆</span>`** `font-size:28px;color:{T2}`
175
- 9. **每篇文章只在一个 `<section>` 内** 不嵌套多个 section
176
- 10. **第一张图必须是书籍封面**使用书籍原封面图,居中显示,max-width:600px
189
+ 8. **重点语句/概念高亮**:用 `<strong style="color:{T};background:{CBG};padding:1px 6px;border-radius:3px;">` 或 `<span style="color:{T2};font-weight:bold;">` — 让主题色贯穿全文
190
+ 9. **星标用 `<span>★★★★☆</span>`**`font-size:28px;color:{T2}`
191
+ 10. **每篇文章只在一个 `<section>` 内** 不嵌套多个 section
192
+ 11. **全文插图统一为主题贴图** — 不再使用书籍原封面(书籍原图往往与文章主题风格不搭),全部用主题贴图
193
+ 12. **图片在章节间穿插** — 每 1-2 段正文插一张图,避免长段无图的视觉疲劳
177
194
 
178
195
  ## 图片工作流
179
196
 
180
- ### 第一步:获取书籍封面图
197
+ ### 第一步:准备主题贴图(不再使用书籍原图作为正文配图)
181
198
 
182
- **书评文章的第一张图必须是书籍原封面**
199
+ **书评文章的所有插图统一为主题贴图**(Pexels 等无版权图库),与文章主题、章节氛围贴合。
200
+ 不推荐使用书籍原图作为正文封面——书籍封面图风格往往与内容图不一致,且某些封面图清晰度/分辨率不适合正文展示。
183
201
 
184
- 使用 `scripts/book_cover.py` 模块自动获取:
202
+ 使用 `scripts/image_downloader.py` 下载主题贴图:
185
203
 
186
204
  ```python
187
- from book_cover import fetch_book_cover, download_and_process_cover
188
-
189
- # 自动获取封面URL(Google Books -> Open Library -> 豆瓣)
190
- cover_url = fetch_book_cover("书名", "作者", "ISBN")
205
+ from image_downloader import download_theme_images
191
206
 
192
- # 下载并处理(生成600px文章封面 + 900x500公众号封面)
193
- if cover_url:
194
- cover_path, cover_900_path = download_and_process_cover(cover_url, "/tmp/output")
207
+ # 根据主题下载 6-8 张贴图(用于正文穿插)
208
+ urls = download_theme_images("books", "/tmp/images", count=6)
195
209
  ```
196
210
 
197
- **封面尺寸要求**:
198
- - 文章内封面:宽度 600px,高度按比例缩放
199
- - 公众号封面:900x500px(用于文章列表展示)
200
-
201
- ### 第二步:下载主题插图
211
+ ### 第二步:制作公众号封面图
202
212
 
203
- 使用 `scripts/image_downloader.py` 下载:
213
+ 公众号封面(`cover_900x500.jpg`)用主题贴图中的一张裁剪为 900x500 比例:
204
214
 
205
215
  ```python
206
- from image_downloader import download_theme_images
207
-
208
- # 根据主题下载5张插图
209
- urls = download_theme_images("nature", "/tmp/images", count=5)
216
+ from PIL import Image
217
+ img = Image.open("img_1.jpg")
218
+ w, h = img.size
219
+ target_ratio = 900 / 500
220
+ if w / h > target_ratio:
221
+ new_w = int(h * target_ratio)
222
+ left = (w - new_w) // 2
223
+ img = img.crop((left, 0, left + new_w, h))
224
+ else:
225
+ new_h = int(w / target_ratio)
226
+ top = (h - new_h) // 2
227
+ img = img.crop((0, top, w, top + new_h))
228
+ img = img.resize((900, 500), Image.LANCZOS)
229
+ img.save("cover_900x500.jpg", "JPEG", quality=85)
210
230
  ```
211
231
 
212
- 每篇文章需要 5-6 张内容图 + 1张书籍封面图 + 1张封面图(900x500用于公众号封面)。
232
+ `scripts/book_cover.py` `download_and_process_cover()` 函数会从主题贴图(`img_1.jpg`)裁剪生成 900x500 封面。
213
233
 
214
234
  ### 第三步:上传图片到微信
215
235
 
@@ -230,6 +250,8 @@ img_url = upload_content_image(token, "image.jpg")
230
250
  将上传后的 media_id 和 mmbiz URL **硬编码到发布脚本中**,不用模板变量。
231
251
  这是因为微信图片URL有时效性,硬编码已验证的URL最可靠。
232
252
 
253
+ 每篇文章需要:1张公众号封面 + 6-8张正文主题贴图。
254
+
233
255
  ## 微信 API 集成
234
256
 
235
257
  ### API 列表
@@ -354,15 +376,32 @@ python3 scripts/publish.py --title "标题" --content-file content.html --cover-
354
376
  <p style="font-size:16px;line-height:2;color:{BODY};margin:0 0 8px;text-indent:2em;">
355
377
  <strong style="color:{T};">适合人群:</strong>
356
378
  </p>
357
- <p style="font-size:16px;line-height:2;color:{BODY};margin:0 0 16px;padding-left:2em;">
358
- <span>✓ 创业者/管理者——具体理由<br />✓ 陷入职业倦怠期的人——具体理由<br />✓ 对某领域感兴趣的读者——具体理由</span>
379
+ <p style="font-size:16px;line-height:2;color:{BODY};margin:0 0 16px;text-indent:2em;">
380
+ <span>✅ 创业者/管理者——具体理由<br />✅ 陷入职业倦怠期的人——具体理由<br />✅ 对某领域感兴趣的读者——具体理由</span>
359
381
  </p>
360
382
 
361
383
  <p style="font-size:16px;line-height:2;color:{BODY};margin:0 0 8px;text-indent:2em;">
362
384
  <strong style="color:{T};">不适合人群:</strong>
363
385
  </p>
364
- <p style="font-size:16px;line-height:2;color:{BODY};margin:0 0 16px;padding-left:2em;">
365
- <span>✗ 已读过类似书籍的人——内容高度重复<br />✗ 讨厌某种风格的人——具体理由<br />✗ 寻找其他类型内容的人——具体理由</span>
386
+ <p style="font-size:16px;line-height:2;color:{BODY};margin:0 0 16px;text-indent:2em;">
387
+ <span>🚫 已读过类似书籍的人——内容高度重复<br />🚫 讨厌某种风格的人——具体理由<br />🚫 寻找其他类型内容的人——具体理由</span>
388
+ </p>
389
+
390
+ <!-- 结尾页(与正文风格延续,使用主题色) -->
391
+ <p style="margin:0 0 16px;text-align:center;">
392
+ <img src="{THEME_IMG_END}" style="width:100%;max-width:600px;display:block;margin:0 auto;border-radius:4px;" alt="主题配图" />
393
+ </p>
394
+
395
+ <p style="font-size:14px;color:#ccc;text-align:center;letter-spacing:8px;margin:24px 0 16px;">· · · · · ·</p>
396
+
397
+ <p style="font-size:18px;font-weight:bold;color:{T};text-align:center;margin:0 0 8px;line-height:1.6;">
398
+ <span>📖 《书名》</span>
399
+ </p>
400
+ <p style="font-size:14px;color:{BODY};text-align:center;margin:0 0 4px;line-height:1.6;">
401
+ <span>作者 · 读书笔记</span>
402
+ </p>
403
+ <p style="font-size:14px;color:{T2};text-align:center;margin:0 0 4px;line-height:1.8;font-weight:bold;">
404
+ <span>「 一句话总结:核心洞察 」</span>
366
405
  </p>
367
406
  ```
368
407
 
@@ -371,6 +410,8 @@ python3 scripts/publish.py --title "标题" --content-file content.html --cover-
371
410
  1. **星级显示**:`font-size:28px;color:{T2}`,居中
372
411
  2. **分数显示**:`4 / 5 星`,在星标下方
373
412
  3. **评分理由**:1-2句话解释为什么给这个分数
374
- 4. **适合人群**:用 标记,每条用——连接理由
375
- 5. **不适合人群**:用 标记,每条用——连接理由
376
- 6. **不要直接放链接**——所有内容用文案表达
413
+ 4. **适合人群**:用 `✅ ` 标记,每条用——连接理由
414
+ 5. **不适合人群**:用 `🚫 ` 标记,每条用——连接理由
415
+ 6. **人群列表与首行对齐**:用 `text-indent:2em`(不用 `padding-left`),保证列表项与"适合人群:"首行视觉对齐
416
+ 7. **结尾页**:用主题主色显示书名+作者,强调色显示一句话总结,配一张主题贴图作收尾
417
+ 8. **不要直接放链接**——所有内容用文案表达
package/bin/cli.js CHANGED
@@ -43,9 +43,9 @@ function showHelp() {
43
43
  npx wechat-media-writer <command> [options]
44
44
 
45
45
  命令:
46
- cover <书名/电影名> [作者] [ISBN] 获取书籍封面
47
- download <主题> [数量] 下载主题插图
48
- publish 发布文章到微信(封面/插图缺失会自动补齐)
46
+ cover <书名/电影名> [作者] [ISBN] 获取书籍原封面(仅用于书籍插图占位)
47
+ download <主题> [数量] [输出目录] 下载主题贴图
48
+ publish 发布文章到微信(主题贴图/封面缺失会自动补齐)
49
49
  help 显示帮助信息
50
50
 
51
51
  publish 选项:
@@ -55,20 +55,14 @@ publish 选项:
55
55
  --author <作者> 文章作者,默认"读书笔记"
56
56
  --digest <摘要> 文章摘要
57
57
  --theme <主题> 图片主题:abstract/books/nature/technology/business,默认 abstract
58
- --image-count <数量> 主题插图数量,默认 5
59
- --book-title <书名> 书籍标题(封面缺失时自动获取)
60
- --book-author <作者> 书籍作者
61
- --book-isbn <ISBN> 书籍ISBN
58
+ --image-count <数量> 主题贴图数量,默认 6(5正文 + 1结尾)
62
59
 
63
60
  示例:
64
- npx wechat-media-writer cover "人类简史" "尤瓦尔·赫拉利"
65
- npx wechat-media-writer download books 5
66
- npx wechat-media-writer publish \\
67
- --title "《人类简史》全书深度拆解" \\
68
- --content-file content.html \\
69
- --cover-dir /tmp/wx \\
70
- --book-title "人类简史" \\
71
- --book-author "尤瓦尔·赫拉利" \\
61
+ npx wechat-media-writer download books 6
62
+ npx wechat-media-writer publish ^
63
+ --title "《人类简史》全书深度拆解" ^
64
+ --content-file content.html ^
65
+ --cover-dir /tmp/wx ^
72
66
  --theme books
73
67
 
74
68
  配置:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wechat-media-writer",
3
- "version": "2.2.3",
3
+ "version": "2.2.4",
4
4
  "description": "微信公众号书评、影评文章自动生成与发布 - Skill",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -5,14 +5,13 @@
5
5
 
6
6
  用法:
7
7
  1. 填写下方的变量(标题、摘要、HTML内容等)
8
- 2. 确保封面图已准备好(使用 scripts/book_cover.py 获取)
8
+ 2. 确保封面图已准备好(使用 scripts/image_downloader.py 下载主题贴图)
9
9
  3. 运行: python3 references/publish_template.py
10
10
  """
11
11
 
12
12
  import os
13
13
  import sys
14
14
 
15
- # 添加 scripts 目录到模块搜索路径
16
15
  scripts_dir = os.path.join(
17
16
  os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "scripts"
18
17
  )
@@ -23,12 +22,11 @@ from wechat_api import load_config, get_access_token, upload_cover, create_draft
23
22
 
24
23
  # ========== 替换以下内容 ==========
25
24
 
26
- # 标题和摘要
27
25
  TITLE = "《书名》全书深度拆解:犀利洞察"
28
26
  AUTHOR = "读书笔记"
29
27
  DIGEST = "摘要文本,不超过120字。"
30
28
 
31
- # 封面图目录(包含 cover_900x500.jpg
29
+ # 封面图目录(包含 cover_900x500.jpg + img_*.jpg 主题贴图)
32
30
  COVER_DIR = "/tmp/book_output"
33
31
 
34
32
  # 主题色:主色(深色)+ 强调色(暖/亮色)
@@ -37,7 +35,16 @@ T2 = "#D4A24C" # 强调色
37
35
  BODY = "#555" # 正文色
38
36
  CBG = "#F0EDE5" # 引用块背景
39
37
 
40
- # 文章 HTML 内容(7段式,无数字编号)
38
+ # 图片 URL(替换为上传微信后返回的 mmbiz.qpic.cn URL)
39
+ # 通过 publish.py 自动上传后,从 upload_result.json 中获取
40
+ THEME_IMG_1 = "http://mmbiz.qpic.cn/REPLACE_WITH_THEME_IMG_1"
41
+ THEME_IMG_2 = "http://mmbiz.qpic.cn/REPLACE_WITH_THEME_IMG_2"
42
+ THEME_IMG_3 = "http://mmbiz.qpic.cn/REPLACE_WITH_THEME_IMG_3"
43
+ THEME_IMG_4 = "http://mmbiz.qpic.cn/REPLACE_WITH_THEME_IMG_4"
44
+ THEME_IMG_5 = "http://mmbiz.qpic.cn/REPLACE_WITH_THEME_IMG_5"
45
+ THEME_IMG_END = "http://mmbiz.qpic.cn/REPLACE_WITH_THEME_IMG_END"
46
+
47
+ # 文章 HTML 内容(7段式,无数字编号,全文主题贴图穿插)
41
48
  HTML = f"""<section style="margin:0;padding:0;font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Hiragino Sans GB','Microsoft YaHei',sans-serif;color:#333;background:#FBF8FC;">
42
49
 
43
50
  <p style="font-size:12px;color:{T2};text-align:center;letter-spacing:4px;margin:24px 0 8px;line-height:3em;">
@@ -52,25 +59,94 @@ HTML = f"""<section style="margin:0;padding:0;font-family:-apple-system,BlinkMac
52
59
  <span>副标题(一句话点睛)</span>
53
60
  </p>
54
61
 
55
- <!-- 书籍封面图(第一张图) -->
62
+ <!-- 主题配图1(头部) -->
56
63
  <p style="margin:0 0 20px;text-align:center;">
57
- <img src="http://mmbiz.qpic.cn/REPLACE_WITH_BOOK_COVER_URL" style="width:100%;max-width:600px;display:block;margin:0 auto;border-radius:4px;" alt="《书名》封面"/>
64
+ <img src="{THEME_IMG_1}" style="width:100%;max-width:600px;display:block;margin:0 auto;border-radius:4px;" alt="主题配图"/>
58
65
  </p>
59
66
 
60
- <!-- 作者背景与创作时代(无数字编号) -->
67
+ <!-- 作者背景与创作时代 -->
61
68
  <p style="font-size:18px;font-weight:bold;color:{T};text-align:center;margin:0 0 16px;line-height:1em;">
62
69
  <span>作者背景与创作时代</span>
63
70
  </p>
71
+ <p style="font-size:16px;line-height:2;color:{BODY};margin:0 0 16px;text-indent:2em;">
72
+ <span>正文段落。所有段落必须有 text-indent:2em。重点句用 <strong style="color:{T};background:{CBG};padding:1px 6px;border-radius:3px;">主题主色高亮</strong>,核心概念用 <span style="color:{T2};font-weight:bold;">强调色加粗</span>。</span>
73
+ </p>
74
+
75
+ <!-- 主题配图2(作者背景后) -->
76
+ <p style="margin:0 0 20px;text-align:center;">
77
+ <img src="{THEME_IMG_2}" style="width:100%;max-width:600px;display:block;margin:0 auto;border-radius:4px;" alt="主题配图"/>
78
+ </p>
79
+
80
+ <!-- 金句引用块(左侧色条 + 主题色加粗) -->
81
+ <p style="font-size:17px;line-height:1.9;color:{T};font-weight:bold;text-indent:2em;margin:0 0 20px;padding:14px 18px;background:{CBG};border-left:4px solid {T2};border-radius:6px;">
82
+ <span style="color:{T2};">▍</span> 金句或核心观点。整段用主题主色加粗,加左侧强调色条与正文形成视觉对比。
83
+ </p>
84
+
85
+ <!-- 全书完整逻辑思维导图 -->
86
+ <p style="font-size:18px;font-weight:bold;color:{T};text-align:center;margin:0 0 16px;line-height:1em;">
87
+ <span>全书完整逻辑思维导图</span>
88
+ </p>
89
+ <p style="font-size:16px;line-height:2;color:{BODY};margin:0 0 16px;text-indent:2em;">
90
+ <span>本章按章节脉络梳理<strong style="color:{T};background:{CBG};padding:1px 6px;border-radius:3px;">全书核心逻辑</strong>,用文字框图提炼主线。</span>
91
+ </p>
92
+
93
+ <!-- 主题配图3(逻辑导图后) -->
94
+ <p style="margin:0 0 20px;text-align:center;">
95
+ <img src="{THEME_IMG_3}" style="width:100%;max-width:600px;display:block;margin:0 auto;border-radius:4px;" alt="主题配图"/>
96
+ </p>
97
+
98
+ <!-- 三大颠覆性核心论点 -->
99
+ <p style="font-size:18px;font-weight:bold;color:{T};text-align:center;margin:0 0 16px;line-height:1em;">
100
+ <span>三大颠覆性核心论点</span>
101
+ </p>
102
+ <p style="font-size:16px;line-height:2;color:{BODY};margin:0 0 16px;text-indent:2em;">
103
+ <span><span style="color:{T2};font-weight:bold;">论点一:</span><strong style="color:{T};background:{CBG};padding:1px 6px;border-radius:3px;">核心颠覆性观点</strong>,附书中具体案例支撑。</span>
104
+ </p>
105
+ <p style="font-size:16px;line-height:2;color:{BODY};margin:0 0 16px;text-indent:2em;">
106
+ <span><span style="color:{T2};font-weight:bold;">论点二:</span><strong style="color:{T};background:{CBG};padding:1px 6px;border-radius:3px;">核心颠覆性观点</strong>,附书中具体案例支撑。</span>
107
+ </p>
108
+ <p style="font-size:16px;line-height:2;color:{BODY};margin:0 0 16px;text-indent:2em;">
109
+ <span><span style="color:{T2};font-weight:bold;">论点三:</span><strong style="color:{T};background:{CBG};padding:1px 6px;border-radius:3px;">核心颠覆性观点</strong>,附书中具体案例支撑。</span>
110
+ </p>
111
+
112
+ <!-- 主题配图4(论点后) -->
113
+ <p style="margin:0 0 20px;text-align:center;">
114
+ <img src="{THEME_IMG_4}" style="width:100%;max-width:600px;display:block;margin:0 auto;border-radius:4px;" alt="主题配图"/>
115
+ </p>
64
116
 
117
+ <!-- 批判性客观评析 -->
118
+ <p style="font-size:18px;font-weight:bold;color:{T};text-align:center;margin:0 0 16px;line-height:1em;">
119
+ <span>批判性客观评析</span>
120
+ </p>
65
121
  <p style="font-size:16px;line-height:2;color:{BODY};margin:0 0 16px;text-indent:2em;">
66
- <span>正文段落。注意:所有段落必须有 text-indent:2em。</span>
122
+ <span>短板一:客观指出书中局限。<br/>短板二:逻辑漏洞分析。<br/>短板三:适用边界。<br/>短板四:时代局限。</span>
67
123
  </p>
68
124
 
69
- <p style="font-size:17px;line-height:1.9;color:{T};font-weight:bold;text-indent:2em;margin:0 0 20px;padding:14px 18px;background:{CBG};border-radius:8px;">
70
- <span>引用块:金句或核心观点。</span>
125
+ <!-- 金句二 -->
126
+ <p style="font-size:17px;line-height:1.9;color:{T};font-weight:bold;text-indent:2em;margin:0 0 20px;padding:14px 18px;background:{CBG};border-left:4px solid {T2};border-radius:6px;">
127
+ <span style="color:{T2};">▍</span> 第二个金句或关键启发。
71
128
  </p>
72
129
 
73
- <!-- 其他章节 -->
130
+ <!-- 收获与成长 -->
131
+ <p style="font-size:18px;font-weight:bold;color:{T};text-align:center;margin:0 0 16px;line-height:1em;">
132
+ <span>收获与成长</span>
133
+ </p>
134
+ <p style="font-size:16px;line-height:2;color:{BODY};margin:0 0 16px;text-indent:2em;">
135
+ <span>读完能带走的<strong style="color:{T};background:{CBG};padding:1px 6px;border-radius:3px;">三样东西</strong>,从个人成长视角展开。</span>
136
+ </p>
137
+
138
+ <!-- 主题配图5(收获与成长后) -->
139
+ <p style="margin:0 0 20px;text-align:center;">
140
+ <img src="{THEME_IMG_5}" style="width:100%;max-width:600px;display:block;margin:0 auto;border-radius:4px;" alt="主题配图"/>
141
+ </p>
142
+
143
+ <!-- 现实落地应用方案 -->
144
+ <p style="font-size:18px;font-weight:bold;color:{T};text-align:center;margin:0 0 16px;line-height:1em;">
145
+ <span>现实落地应用方案</span>
146
+ </p>
147
+ <p style="font-size:16px;line-height:2;color:{BODY};margin:0 0 16px;text-indent:2em;">
148
+ <span>方案一:具体执行步骤。<br/>方案二:可操作建议。<br/>方案三:拿来就用的行动项。</span>
149
+ </p>
74
150
 
75
151
  <p style="font-size:14px;color:#ccc;text-align:center;letter-spacing:8px;margin:20px 0;">· · · · · ·</p>
76
152
 
@@ -88,30 +164,38 @@ HTML = f"""<section style="margin:0;padding:0;font-family:-apple-system,BlinkMac
88
164
  </p>
89
165
 
90
166
  <p style="font-size:16px;line-height:2;color:{BODY};margin:0 0 16px;text-indent:2em;">
91
- <span>为什么给X星?简要说明评分理由。</span>
167
+ <span>为什么给X星?简要说明评分理由,诚实评价,3-4星是常态,5星极少。</span>
92
168
  </p>
93
169
 
94
170
  <p style="font-size:16px;line-height:2;color:{BODY};margin:0 0 8px;text-indent:2em;">
95
171
  <strong style="color:{T};">适合人群:</strong>
96
172
  </p>
97
173
  <p style="font-size:16px;line-height:2;color:{BODY};margin:0 0 16px;text-indent:2em;">
98
- <span>✓ 人群1——具体理由<br/>✓ 人群2——具体理由<br/>✓ 人群3——具体理由</span>
174
+ <span>✅&nbsp;&nbsp;创业者/管理者——理解系统脆弱性和反脆弱设计<br />✅&nbsp;&nbsp;对个人成长感兴趣的读者——掌握从压力中受益的方法<br />✅&nbsp;&nbsp;对决策科学感兴趣的读者——理解非线性风险的本质</span>
99
175
  </p>
100
176
 
101
177
  <p style="font-size:16px;line-height:2;color:{BODY};margin:0 0 8px;text-indent:2em;">
102
178
  <strong style="color:{T};">不适合人群:</strong>
103
179
  </p>
104
180
  <p style="font-size:16px;line-height:2;color:{BODY};margin:0 0 16px;text-indent:2em;">
105
- <span>✗ 人群1——具体理由<br/>✗ 人群2——具体理由<br/>✗ 人群3——具体理由</span>
181
+ <span>🚫&nbsp;&nbsp;已读过类似书籍的人——内容高度重复<br />🚫&nbsp;&nbsp;讨厌抽象论证的人——本书充满概念性思维<br />🚫&nbsp;&nbsp;寻找具体操作手册的人——本书偏哲学思辨</span>
106
182
  </p>
107
183
 
108
- <p style="font-size:14px;color:#ccc;text-align:center;letter-spacing:8px;margin:20px 0;">· · · · · ·</p>
184
+ <!-- 结尾页:主题配图 + 书名 + 作者 + 核心总结 -->
185
+ <p style="margin:0 0 16px;text-align:center;">
186
+ <img src="{THEME_IMG_END}" style="width:100%;max-width:600px;display:block;margin:0 auto;border-radius:4px;" alt="主题配图"/>
187
+ </p>
109
188
 
110
- <p style="font-size:16px;line-height:2;color:{T};text-align:center;margin:0 0 4px;font-weight:bold;text-indent:2em;">
111
- <span>📖 《书名》作者 · 读书笔记</span>
189
+ <p style="font-size:14px;color:#ccc;text-align:center;letter-spacing:8px;margin:24px 0 16px;">· · · · · ·</p>
190
+
191
+ <p style="font-size:18px;font-weight:bold;color:{T};text-align:center;margin:0 0 8px;line-height:1.6;">
192
+ <span>📖 《书名》</span>
193
+ </p>
194
+ <p style="font-size:14px;color:{BODY};text-align:center;margin:0 0 4px;line-height:1.6;">
195
+ <span>作者 · 读书笔记</span>
112
196
  </p>
113
- <p style="font-size:14px;color:#999;text-align:center;margin:0 0 4px;text-indent:2em;">
114
- <span>一句话总结</span>
197
+ <p style="font-size:14px;color:{T2};text-align:center;margin:0 0 24px;line-height:1.8;font-weight:bold;">
198
+ <span>「 一句话总结:核心洞察 」</span>
115
199
  </p>
116
200
 
117
201
  </section>"""
@@ -122,22 +206,19 @@ def main():
122
206
  print("微信公众号 - 书名 发布")
123
207
  print("=" * 50)
124
208
 
125
- # 1. 加载配置和获取token
126
209
  config = load_config()
127
210
  token = get_access_token(config)
128
211
  print("\n✓ Token 已获取\n")
129
212
 
130
- # 2. 上传封面图
131
213
  cover_900_path = os.path.join(COVER_DIR, "cover_900x500.jpg")
132
214
  if not os.path.exists(cover_900_path):
133
215
  print(f"错误: 封面图不存在 {cover_900_path}", file=sys.stderr)
134
- print("请先运行: python3 scripts/book_cover.py '书名' '作者'", file=sys.stderr)
216
+ print("请先运行主题贴图下载与封面裁剪流程", file=sys.stderr)
135
217
  sys.exit(1)
136
218
 
137
219
  print("上传封面图...")
138
220
  cover_mid = upload_cover(token, cover_900_path)
139
221
 
140
- # 3. 创建草稿
141
222
  print("\n创建草稿...")
142
223
  draft_id = create_draft(token, TITLE, AUTHOR, DIGEST, HTML, cover_mid)
143
224
 
@@ -97,7 +97,12 @@ def fetch_book_cover_douban(book_title, author=""):
97
97
 
98
98
 
99
99
  def fetch_book_cover(book_title, author="", isbn=""):
100
- """按优先级尝试获取书籍封面,最后回退到占位图"""
100
+ """按优先级尝试获取书籍封面(书籍原图),最后回退到占位图
101
+
102
+ 注意:此函数返回的是书籍原图,仅用于 article 内首图占位。
103
+ 公众号封面(cover_900x500.jpg)现在改用主题贴图,与正文风格统一。
104
+ 如需主题封面,请调用 fetch_theme_cover()。
105
+ """
101
106
  cover_url = None
102
107
 
103
108
  print(f"尝试从 Google Books 获取《{book_title}》封面...")
@@ -112,15 +117,29 @@ def fetch_book_cover(book_title, author="", isbn=""):
112
117
  cover_url = fetch_book_cover_douban(book_title, author)
113
118
 
114
119
  if not cover_url:
115
- print("⚠️ 三个数据源均未返回封面,使用占位图占位")
120
+ print("⚠️ 三个数据源均未返回书籍封面,回退到占位图")
116
121
  cover_url = "https://images.pexels.com/photos/159711/books-bookstore-book-reading-159711.jpeg?auto=compress&cs=tinysrgb&w=600"
117
122
 
118
- print(f"✓ 封面URL: {cover_url}")
123
+ print(f"✓ 书籍封面URL: {cover_url}")
119
124
  return cover_url
120
125
 
121
126
 
127
+ def fetch_theme_cover(theme="abstract"):
128
+ """从 Pexels CDN 获取与主题贴图一致的主题封面(推荐使用)"""
129
+ theme_urls = {
130
+ "abstract": "https://images.pexels.com/photos/2693208/pexels-photo-2693208.jpeg?auto=compress&cs=tinysrgb&w=1080",
131
+ "books": "https://images.pexels.com/photos/159711/books-bookstore-book-reading-159711.jpeg?auto=compress&cs=tinysrgb&w=1080",
132
+ "nature": "https://images.pexels.com/photos/2387873/pexels-photo-2387873.jpeg?auto=compress&cs=tinysrgb&w=1080",
133
+ "technology": "https://images.pexels.com/photos/3568520/pexels-photo-3568520.jpeg?auto=compress&cs=tinysrgb&w=1080",
134
+ "business": "https://images.pexels.com/photos/3184292/pexels-photo-3184292.jpeg?auto=compress&cs=tinysrgb&w=1080",
135
+ }
136
+ url = theme_urls.get(theme, theme_urls["abstract"])
137
+ print(f"✓ 主题封面URL ({theme}): {url}")
138
+ return url
139
+
140
+
122
141
  def download_and_process_cover(cover_url, output_dir):
123
- """下载并处理封面图"""
142
+ """从主题贴图URL下载并处理为 900x500 公众号封面 + 600px 文章封面"""
124
143
  from PIL import Image
125
144
 
126
145
  ctx = ssl.create_default_context()
@@ -131,10 +150,11 @@ def download_and_process_cover(cover_url, output_dir):
131
150
  data = urllib.request.urlopen(req, context=ctx, timeout=15).read()
132
151
 
133
152
  os.makedirs(output_dir, exist_ok=True)
134
- original_path = os.path.join(output_dir, "book_cover_original.jpg")
153
+ original_path = os.path.join(output_dir, "theme_cover_original.jpg")
135
154
  with open(original_path, "wb") as f:
136
155
  f.write(data)
137
156
 
157
+ # 文章内配图(600px 宽)
138
158
  img = Image.open(original_path)
139
159
  w, h = img.size
140
160
  if w > 600:
@@ -142,10 +162,40 @@ def download_and_process_cover(cover_url, output_dir):
142
162
  new_w = 600
143
163
  new_h = int(h * ratio)
144
164
  img = img.resize((new_w, new_h), Image.LANCZOS)
165
+ img.save(os.path.join(output_dir, "theme_cover_600.jpg"), "JPEG", quality=90)
145
166
 
146
- cover_path = os.path.join(output_dir, "book_cover.jpg")
147
- img.save(cover_path, "JPEG", quality=90)
167
+ # 公众号封面(900x500)
168
+ img2 = Image.open(original_path)
169
+ w, h = img2.size
170
+ target_ratio = 900 / 500
171
+ if w / h > target_ratio:
172
+ new_w = int(h * target_ratio)
173
+ left = (w - new_w) // 2
174
+ img2 = img2.crop((left, 0, left + new_w, h))
175
+ else:
176
+ new_h = int(w / target_ratio)
177
+ top = (h - new_h) // 2
178
+ img2 = img2.crop((0, top, w, top + new_h))
179
+ img2 = img2.resize((900, 500), Image.LANCZOS)
180
+ cover_900_path = os.path.join(output_dir, "cover_900x500.jpg")
181
+ img2.save(cover_900_path, "JPEG", quality=90)
148
182
 
183
+ print(f"✓ 主题封面已保存: theme_cover_600.jpg")
184
+ print(f"✓ 公众号封面已保存: {cover_900_path}")
185
+
186
+ return os.path.join(output_dir, "theme_cover_600.jpg"), cover_900_path
187
+
188
+
189
+ def crop_cover_from_local(source_path, output_dir):
190
+ """从本地主题贴图裁剪为 900x500 公众号封面(用于已有主题图但缺封面图的情况)"""
191
+ from PIL import Image
192
+
193
+ if not os.path.exists(source_path):
194
+ print(f"错误: 源图不存在 {source_path}", file=sys.stderr)
195
+ return None
196
+
197
+ os.makedirs(output_dir, exist_ok=True)
198
+ img = Image.open(source_path)
149
199
  w, h = img.size
150
200
  target_ratio = 900 / 500
151
201
  if w / h > target_ratio:
@@ -160,11 +210,10 @@ def download_and_process_cover(cover_url, output_dir):
160
210
 
161
211
  cover_900_path = os.path.join(output_dir, "cover_900x500.jpg")
162
212
  img.save(cover_900_path, "JPEG", quality=90)
163
-
164
- print(f"✓ 封面图已保存: {cover_path}")
165
- print(f"✓ 公众号封面已保存: {cover_900_path}")
166
-
167
- return cover_path, cover_900_path
213
+ print(
214
+ f"✓ 公众号封面已生成(基于 {os.path.basename(source_path)}): {cover_900_path}"
215
+ )
216
+ return cover_900_path
168
217
 
169
218
 
170
219
  if __name__ == "__main__":
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env python3
2
2
  """
3
3
  微信公众号读书拆解发布脚本
4
- 完整流程:获取封面 -> 下载插图 -> 上传图片 -> 创建草稿
4
+ 完整流程:准备主题贴图 -> 裁剪公众号封面 -> 上传图片 -> 创建草稿
5
5
  """
6
6
 
7
7
  import argparse
@@ -12,7 +12,7 @@ import sys
12
12
  # 添加当前目录到模块搜索路径
13
13
  sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
14
14
 
15
- from book_cover import fetch_book_cover, download_and_process_cover
15
+ from book_cover import crop_cover_from_local, fetch_theme_cover
16
16
  from image_downloader import download_theme_images
17
17
  from wechat_api import (
18
18
  load_config,
@@ -23,34 +23,12 @@ from wechat_api import (
23
23
  )
24
24
 
25
25
 
26
- def ensure_book_cover(cover_dir, book_title="", book_author="", book_isbn=""):
27
- """确保封面图存在,缺失则自动获取"""
28
- cover_900_path = os.path.join(cover_dir, "cover_900x500.jpg")
29
- book_cover_path = os.path.join(cover_dir, "book_cover.jpg")
30
-
31
- if os.path.exists(cover_900_path) and os.path.exists(book_cover_path):
32
- print("✓ 封面图已存在,跳过获取")
33
- return
34
-
35
- if not book_title:
36
- print(
37
- f"⚠️ 缺少封面图且未提供 --book-title,无法自动获取。\n"
38
- f" 期望路径: {cover_900_path}",
39
- file=sys.stderr,
40
- )
41
- sys.exit(1)
26
+ def ensure_theme_images(cover_dir, theme="abstract", count=6):
27
+ """确保主题插图存在,缺失则自动下载
42
28
 
43
- print(f"封面图缺失,自动获取《{book_title}》封面...")
29
+ 默认 6 张:用于正文穿插(5 张)+ 结尾页(1 张)= 6 张
30
+ """
44
31
  os.makedirs(cover_dir, exist_ok=True)
45
- cover_url = fetch_book_cover(book_title, book_author, book_isbn)
46
- if not cover_url:
47
- print("错误: 无法获取书籍封面URL", file=sys.stderr)
48
- sys.exit(1)
49
- download_and_process_cover(cover_url, cover_dir)
50
-
51
-
52
- def ensure_theme_images(cover_dir, theme="abstract", count=5):
53
- """确保主题插图存在,缺失则自动下载"""
54
32
  existing = [
55
33
  f
56
34
  for f in os.listdir(cover_dir)
@@ -64,6 +42,32 @@ def ensure_theme_images(cover_dir, theme="abstract", count=5):
64
42
  download_theme_images(theme, cover_dir, count)
65
43
 
66
44
 
45
+ def ensure_cover_image(cover_dir, theme="abstract"):
46
+ """确保公众号封面(900x500)存在,缺失则从主题图裁剪"""
47
+ cover_900_path = os.path.join(cover_dir, "cover_900x500.jpg")
48
+ if os.path.exists(cover_900_path):
49
+ print("✓ 公众号封面已存在,跳过裁剪")
50
+ return
51
+
52
+ # 优先用本地第一张主题图裁剪;否则从主题URL下载
53
+ candidates = sorted(
54
+ [
55
+ f
56
+ for f in os.listdir(cover_dir)
57
+ if f.startswith("img_") and f.endswith((".jpg", ".jpeg", ".png"))
58
+ ]
59
+ )
60
+ if candidates:
61
+ crop_cover_from_local(os.path.join(cover_dir, candidates[0]), cover_dir)
62
+ else:
63
+ # 极少见:连主题图都没有的情况
64
+ print("主题图与封面均缺失,从网络拉取主题封面...")
65
+ from book_cover import download_and_process_cover
66
+
67
+ url = fetch_theme_cover(theme)
68
+ download_and_process_cover(url, cover_dir)
69
+
70
+
67
71
  def publish_article(
68
72
  title,
69
73
  author,
@@ -71,41 +75,38 @@ def publish_article(
71
75
  content_html,
72
76
  cover_dir,
73
77
  theme="abstract",
74
- image_count=5,
75
- book_title="",
76
- book_author="",
77
- book_isbn="",
78
+ image_count=6,
78
79
  ):
79
- """完整发布流程:获取封面 -> 下载插图 -> 上传图片 -> 创建草稿"""
80
+ """完整发布流程:准备主题贴图 -> 裁剪公众号封面 -> 上传图片 -> 创建草稿"""
80
81
  print("=" * 50)
81
82
  print("微信公众号 - 书评发布")
82
83
  print("=" * 50)
83
84
 
84
85
  os.makedirs(cover_dir, exist_ok=True)
85
86
 
86
- # 1. 确保封面图存在
87
- print("\n[1/4] 准备封面图...")
88
- ensure_book_cover(cover_dir, book_title, book_author, book_isbn)
89
-
90
- # 2. 确保主题插图存在
91
- print("\n[2/4] 准备主题插图...")
87
+ # 1. 准备主题贴图
88
+ print("\n[1/4] 准备主题贴图...")
92
89
  ensure_theme_images(cover_dir, theme, image_count)
93
90
 
94
- # 3. 加载配置和获取token
91
+ # 2. 准备公众号封面(900x500)
92
+ print("\n[2/4] 准备公众号封面...")
93
+ ensure_cover_image(cover_dir, theme)
94
+
95
+ # 3. 加载配置和获取 token
95
96
  config = load_config()
96
97
  token = get_access_token(config)
97
98
  print("\n✓ Token 已获取\n")
98
99
 
99
- # 4. 上传封面图
100
+ # 4. 上传公众号封面
100
101
  cover_900_path = os.path.join(cover_dir, "cover_900x500.jpg")
101
102
  if not os.path.exists(cover_900_path):
102
103
  print(f"错误: 封面图不存在 {cover_900_path}", file=sys.stderr)
103
104
  sys.exit(1)
104
105
 
105
- print("上传封面图...")
106
+ print("上传公众号封面...")
106
107
  cover_mid = upload_cover(token, cover_900_path)
107
108
 
108
- # 5. 上传内容图片
109
+ # 5. 上传内容图片(img_*.jpg)
109
110
  print("\n上传内容图片...")
110
111
  image_urls = {}
111
112
  for fname in sorted(os.listdir(cover_dir)):
@@ -116,14 +117,7 @@ def publish_article(
116
117
  name = os.path.splitext(fname)[0]
117
118
  image_urls[name] = url
118
119
 
119
- # 6. 上传书籍封面图(用于文章内显示)
120
- book_cover_path = os.path.join(cover_dir, "book_cover.jpg")
121
- if os.path.exists(book_cover_path):
122
- book_cover_url = upload_content_image(token, book_cover_path)
123
- if book_cover_url:
124
- image_urls["book_cover"] = book_cover_url
125
-
126
- # 7. 保存上传结果
120
+ # 6. 保存上传结果
127
121
  result_path = os.path.join(cover_dir, "upload_result.json")
128
122
  with open(result_path, "w") as f:
129
123
  json.dump(
@@ -137,7 +131,7 @@ def publish_article(
137
131
  )
138
132
  print(f"\n✓ 上传结果已保存: {result_path}")
139
133
 
140
- # 8. 创建草稿
134
+ # 7. 创建草稿
141
135
  print("\n创建草稿...")
142
136
  draft_id = create_draft(token, title, author, digest, content_html, cover_mid)
143
137
 
@@ -161,10 +155,9 @@ def main():
161
155
  parser.add_argument("--content-file", required=True, help="HTML内容文件路径")
162
156
  parser.add_argument("--cover-dir", required=True, help="封面和图片目录")
163
157
  parser.add_argument("--theme", default="abstract", help="图片主题")
164
- parser.add_argument("--image-count", type=int, default=5, help="主题插图数量")
165
- parser.add_argument("--book-title", default="", help="书籍标题(缺封面时自动获取)")
166
- parser.add_argument("--book-author", default="", help="书籍作者")
167
- parser.add_argument("--book-isbn", default="", help="书籍ISBN")
158
+ parser.add_argument(
159
+ "--image-count", type=int, default=6, help="主题插图数量(默认6:5正文+1结尾)"
160
+ )
168
161
  args = parser.parse_args()
169
162
 
170
163
  with open(args.content_file, "r", encoding="utf-8") as f:
@@ -178,9 +171,6 @@ def main():
178
171
  args.cover_dir,
179
172
  args.theme,
180
173
  args.image_count,
181
- args.book_title,
182
- args.book_author,
183
- args.book_isbn,
184
174
  )
185
175
 
186
176