wechat-media-writer 2.2.7 → 2.2.9

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
@@ -140,7 +140,7 @@ Token 缓存在 `~/.wechat/token_cache.json`(自动管理,2小时有效期
140
140
 
141
141
  <!-- 主题配图(第一张图,使用主题贴图,非书籍原图) -->
142
142
  <p style="margin:0 0 20px;text-align:center;">
143
- <img src="{THEME_IMG_1}" style="width:100%;max-width:600px;display:block;margin:0 auto;border-radius:4px;" alt="主题配图" />
143
+ <img src="{THEME_IMG_1}" style="width:100%;max-width:600px;display:block;margin:0 auto;border-radius:4px;" alt="" />
144
144
  </p>
145
145
 
146
146
  <!-- 章节(无数字编号,直接标题) -->
@@ -153,14 +153,14 @@ Token 缓存在 `~/.wechat/token_cache.json`(自动管理,2小时有效期
153
153
 
154
154
  <!-- 主题配图(章节间穿插,与文风统一) -->
155
155
  <p style="margin:0 0 20px;text-align:center;">
156
- <img src="{THEME_IMG_2}" style="width:100%;max-width:600px;display:block;margin:0 auto;border-radius:4px;" alt="主题配图" />
156
+ <img src="{THEME_IMG_2}" style="width:100%;max-width:600px;display:block;margin:0 auto;border-radius:4px;" alt="" />
157
157
  </p>
158
158
 
159
- <!-- 金句引用块(用主题色高亮,加左侧色条) -->
159
+ <!-- 金句引用块(圆角浅背景,无左侧竖条) -->
160
160
  <p
161
- 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;"
161
+ style="font-size:17px;line-height:1.9;color:{T};font-weight:bold;text-indent:2em;margin:0 0 20px;padding:16px 20px;background:{CBG};border-radius:8px;"
162
162
  >
163
- <span style="color:{T2};">▍</span> 金句或核心观点引用。整段用主题主色加粗,加左侧强调色条,与正文形成视觉对比。
163
+ <span>金句或核心观点引用。整段用主题主色加粗,圆角浅背景与正文形成视觉对比。</span>
164
164
  </p>
165
165
 
166
166
  <!-- 重点启发语句(行内主题色 + 强调色高亮) -->
@@ -170,7 +170,7 @@ Token 缓存在 `~/.wechat/token_cache.json`(自动管理,2小时有效期
170
170
 
171
171
  <!-- 主题配图(章节中段) -->
172
172
  <p style="margin:0 0 20px;text-align:center;">
173
- <img src="{THEME_IMG_3}" style="width:100%;max-width:600px;display:block;margin:0 auto;border-radius:4px;" alt="主题配图" />
173
+ <img src="{THEME_IMG_3}" style="width:100%;max-width:600px;display:block;margin:0 auto;border-radius:4px;" alt="" />
174
174
  </p>
175
175
 
176
176
  <!-- 分隔符(章节之间) -->
@@ -184,7 +184,7 @@ Token 缓存在 `~/.wechat/token_cache.json`(自动管理,2小时有效期
184
184
  2. **图片URL必须用完整的 `mmbiz.qpic.cn` 硬编码URL** — 不用模板变量
185
185
  3. **`<img>` 必须包裹在 `<p>` 中**,且 `style="width:100%;display:block;border-radius:4px;"`
186
186
  4. **行高统一 `line-height:2`** — 阅读舒适
187
- 5. **引用块用 `<p>` + `background` + `border-radius:8px` + 左侧 `border-left:4px solid {T2}` 色条** 不是 `<blockquote>`
187
+ 5. **引用块用 `<p>` + `background` + `border-radius:8px`**圆角浅背景卡片样式,不要左侧竖条
188
188
  6. **章节标题不使用数字编号** — 直接用"作者背景与创作时代",不用"壹"
189
189
  7. **强调文字用 `<strong style="color:{T};">`** — 不是加粗或改色
190
190
  8. **重点语句/概念高亮**:用 `<strong style="color:{T};background:{CBG};padding:1px 6px;border-radius:3px;">` 或 `<span style="color:{T2};font-weight:bold;">` — 让主题色贯穿全文
@@ -383,7 +383,7 @@ python3 scripts/publish.py --title "标题" --content-file content.html --cover-
383
383
 
384
384
  <!-- 结尾页(与正文风格延续,使用主题色) -->
385
385
  <p style="margin:0 0 16px;text-align:center;">
386
- <img src="{THEME_IMG_END}" style="width:100%;max-width:600px;display:block;margin:0 auto;border-radius:4px;" alt="主题配图" />
386
+ <img src="{THEME_IMG_END}" style="width:100%;max-width:600px;display:block;margin:0 auto;border-radius:4px;" alt="" />
387
387
  </p>
388
388
 
389
389
  <p style="font-size:14px;color:#ccc;text-align:center;letter-spacing:8px;margin:24px 0 16px;">· · · · · ·</p>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wechat-media-writer",
3
- "version": "2.2.7",
3
+ "version": "2.2.9",
4
4
  "description": "微信公众号书评、影评文章自动生成与发布 - Skill",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -45,7 +45,7 @@ THEME_IMG_5 = "http://mmbiz.qpic.cn/REPLACE_WITH_THEME_IMG_5"
45
45
  THEME_IMG_END = "http://mmbiz.qpic.cn/REPLACE_WITH_THEME_IMG_END"
46
46
 
47
47
  # 文章 HTML 内容(7段式,无数字编号,全文主题贴图穿插)
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;">
48
+ HTML = f"""<section style="margin:0;padding:0 0 48px;font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Hiragino Sans GB','Microsoft YaHei',sans-serif;color:#333;background:#FBF8FC;">
49
49
 
50
50
  <p style="font-size:12px;color:{T2};text-align:center;letter-spacing:4px;margin:24px 0 8px;line-height:3em;">
51
51
  <span>📖 读书拆解</span>
@@ -61,7 +61,7 @@ HTML = f"""<section style="margin:0;padding:0;font-family:-apple-system,BlinkMac
61
61
 
62
62
  <!-- 主题配图1(头部) -->
63
63
  <p style="margin:0 0 20px;text-align:center;">
64
- <img src="{THEME_IMG_1}" 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=""/>
65
65
  </p>
66
66
 
67
67
  <!-- 先搞懂写书的这个人 -->
@@ -74,12 +74,12 @@ HTML = f"""<section style="margin:0;padding:0;font-family:-apple-system,BlinkMac
74
74
 
75
75
  <!-- 主题配图2(先搞懂写书的这个人后) -->
76
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="主题配图"/>
77
+ <img src="{THEME_IMG_2}" style="width:100%;max-width:600px;display:block;margin:0 auto;border-radius:4px;" alt=""/>
78
78
  </p>
79
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> 金句或核心观点。整段用主题主色加粗,加左侧强调色条与正文形成视觉对比。
80
+ <!-- 金句引用块(圆角浅背景,无左侧竖条) -->
81
+ <p style="font-size:17px;line-height:1.9;color:{T};font-weight:bold;text-indent:2em;margin:0 0 20px;padding:16px 20px;background:{CBG};border-radius:8px;">
82
+ <span>金句或核心观点。整段用主题主色加粗,圆角浅背景与正文形成视觉对比。</span>
83
83
  </p>
84
84
 
85
85
  <!-- 全书脉络 -->
@@ -92,7 +92,7 @@ HTML = f"""<section style="margin:0;padding:0;font-family:-apple-system,BlinkMac
92
92
 
93
93
  <!-- 主题配图3(逻辑导图后) -->
94
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="主题配图"/>
95
+ <img src="{THEME_IMG_3}" style="width:100%;max-width:600px;display:block;margin:0 auto;border-radius:4px;" alt=""/>
96
96
  </p>
97
97
 
98
98
  <!-- 三个颠覆认知的真相 -->
@@ -111,7 +111,7 @@ HTML = f"""<section style="margin:0;padding:0;font-family:-apple-system,BlinkMac
111
111
 
112
112
  <!-- 主题配图4(论点后) -->
113
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="主题配图"/>
114
+ <img src="{THEME_IMG_4}" style="width:100%;max-width:600px;display:block;margin:0 auto;border-radius:4px;" alt=""/>
115
115
  </p>
116
116
 
117
117
  <!-- 这本书的短板,不吐不快 -->
@@ -123,8 +123,8 @@ HTML = f"""<section style="margin:0;padding:0;font-family:-apple-system,BlinkMac
123
123
  </p>
124
124
 
125
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> 第二个金句或关键启发。
126
+ <p style="font-size:17px;line-height:1.9;color:{T};font-weight:bold;text-indent:2em;margin:0 0 20px;padding:16px 20px;background:{CBG};border-radius:8px;">
127
+ <span>第二个金句或关键启发。</span>
128
128
  </p>
129
129
 
130
130
  <!-- 读完真正留下的东西 -->
@@ -137,7 +137,7 @@ HTML = f"""<section style="margin:0;padding:0;font-family:-apple-system,BlinkMac
137
137
 
138
138
  <!-- 主题配图5(读完真正留下的东西后) -->
139
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="主题配图"/>
140
+ <img src="{THEME_IMG_5}" style="width:100%;max-width:600px;display:block;margin:0 auto;border-radius:4px;" alt=""/>
141
141
  </p>
142
142
 
143
143
  <!-- 四条拿来就用的行动项 -->
@@ -170,20 +170,32 @@ HTML = f"""<section style="margin:0;padding:0;font-family:-apple-system,BlinkMac
170
170
  <p style="font-size:16px;line-height:2;color:{BODY};margin:0 0 8px;text-indent:2em;">
171
171
  <strong style="color:{T};">适合人群:</strong>
172
172
  </p>
173
- <p style="font-size:16px;line-height:2;color:{BODY};margin:0 0 16px;text-indent:2em;">
174
- <span>✅&nbsp;&nbsp;创业者/管理者——理解系统脆弱性和反脆弱设计<br />✅&nbsp;&nbsp;对个人成长感兴趣的读者——掌握从压力中受益的方法<br />✅&nbsp;&nbsp;对决策科学感兴趣的读者——理解非线性风险的本质</span>
173
+ <p style="font-size:16px;line-height:2;color:{BODY};margin:0 0 8px;padding-left:2em;">
174
+ <span>✅&nbsp;&nbsp;创业者/管理者——理解系统脆弱性和反脆弱设计</span>
175
+ </p>
176
+ <p style="font-size:16px;line-height:2;color:{BODY};margin:0 0 8px;padding-left:2em;">
177
+ <span>✅&nbsp;&nbsp;对个人成长感兴趣的读者——掌握从压力中受益的方法</span>
178
+ </p>
179
+ <p style="font-size:16px;line-height:2;color:{BODY};margin:0 0 16px;padding-left:2em;">
180
+ <span>✅&nbsp;&nbsp;对决策科学感兴趣的读者——理解非线性风险的本质</span>
175
181
  </p>
176
182
 
177
183
  <p style="font-size:16px;line-height:2;color:{BODY};margin:0 0 8px;text-indent:2em;">
178
184
  <strong style="color:{T};">不适合人群:</strong>
179
185
  </p>
180
- <p style="font-size:16px;line-height:2;color:{BODY};margin:0 0 16px;text-indent:2em;">
181
- <span>🚫&nbsp;&nbsp;已读过类似书籍的人——内容高度重复<br />🚫&nbsp;&nbsp;讨厌抽象论证的人——本书充满概念性思维<br />🚫&nbsp;&nbsp;寻找具体操作手册的人——本书偏哲学思辨</span>
186
+ <p style="font-size:16px;line-height:2;color:{BODY};margin:0 0 8px;padding-left:2em;">
187
+ <span>🚫&nbsp;&nbsp;已读过类似书籍的人——内容高度重复</span>
188
+ </p>
189
+ <p style="font-size:16px;line-height:2;color:{BODY};margin:0 0 8px;padding-left:2em;">
190
+ <span>🚫&nbsp;&nbsp;讨厌抽象论证的人——本书充满概念性思维</span>
191
+ </p>
192
+ <p style="font-size:16px;line-height:2;color:{BODY};margin:0 0 24px;padding-left:2em;">
193
+ <span>🚫&nbsp;&nbsp;寻找具体操作手册的人——本书偏哲学思辨</span>
182
194
  </p>
183
195
 
184
196
  <!-- 结尾页:主题配图 + 书名 + 作者 + 核心总结 -->
185
197
  <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="主题配图"/>
198
+ <img src="{THEME_IMG_END}" style="width:100%;max-width:600px;display:block;margin:0 auto;border-radius:4px;" alt=""/>
187
199
  </p>
188
200
 
189
201
  <p style="font-size:14px;color:#ccc;text-align:center;letter-spacing:8px;margin:24px 0 16px;">· · · · · ·</p>
@@ -194,7 +206,7 @@ HTML = f"""<section style="margin:0;padding:0;font-family:-apple-system,BlinkMac
194
206
  <p style="font-size:14px;color:{BODY};text-align:center;margin:0 0 4px;line-height:1.6;">
195
207
  <span>作者 · 读书笔记</span>
196
208
  </p>
197
- <p style="font-size:14px;color:{T2};text-align:center;margin:0 0 24px;line-height:1.8;font-weight:bold;">
209
+ <p style="font-size:14px;color:{T2};text-align:center;margin:8px 0 32px;line-height:1.8;font-weight:bold;">
198
210
  <span>「 一句话总结:核心洞察 」</span>
199
211
  </p>
200
212
 
@@ -13,18 +13,21 @@ import urllib.request
13
13
  from PIL import Image, ImageStat
14
14
 
15
15
 
16
- # 精选 Pexels 候选 ID(每个主题 8-10 张,全部已测试可用)
17
- # 选择标准:构图饱满、色彩丰富、主题鲜明
16
+ # 精选 Pexels 候选 ID(每个主题 8-10 张,实地下载验证)
17
+ # 选择标准:构图饱满、色彩丰富、主题鲜明、与书评/影评审美契合
18
18
  THEME_CANDIDATES = {
19
19
  "books": [
20
- 256450, # 书与咖啡
21
- 590493, # 翻页
22
- 762687, # 书架
23
- 1029141, # 书页
24
- 3568520, # 笔记本与笔
25
- 3568521, # 笔记本俯拍
26
- 1112048, # 翻页特写
27
- 4429270, # 复古书籍
20
+ # Pexels 搜索"book"结果中精选的高质量图
21
+ 415078, # 草地上开本书(封面候选极佳)
22
+ 433333, # 书堆黑白(封面候选极佳,构图饱满)
23
+ 4861364, # 两人读书
24
+ 5913138, # 书+花+咖啡
25
+ 6001171, # 床上读书
26
+ 7034613, # 书架书+植物
27
+ 1792734, # 阳光读书
28
+ 11197155, # 古书桌上
29
+ 13580974, # 翻开的书页
30
+ 904616, # 咖啡+书+花俯拍
28
31
  ],
29
32
  "abstract": [
30
33
  1108572, # 抽象光影
@@ -45,6 +48,8 @@ THEME_CANDIDATES = {
45
48
  3551226, # 风景
46
49
  3551244, # 风景光影
47
50
  3551419, # 风景横幅
51
+ 417173, # 雪峰
52
+ 355770, # 黑白雪山
48
53
  ],
49
54
  "technology": [
50
55
  3568520, # 代码屏幕
@@ -69,22 +74,35 @@ THEME_CANDIDATES = {
69
74
  }
70
75
 
71
76
 
72
- def download_image(url, output_path):
73
- """下载图片到指定路径"""
77
+ def download_image(url, output_path, retries=3, retry_delay=1):
78
+ """下载图片到指定路径,带重试机制"""
79
+ import time
80
+
74
81
  ctx = ssl.create_default_context()
75
82
  ctx.check_hostname = False
76
83
  ctx.verify_mode = ssl.CERT_NONE
77
84
 
78
85
  req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
79
- try:
80
- data = urllib.request.urlopen(req, context=ctx, timeout=15).read()
81
- os.makedirs(os.path.dirname(output_path), exist_ok=True)
82
- with open(output_path, "wb") as f:
83
- f.write(data)
84
- return True
85
- except Exception as e:
86
- print(f"下载失败: {e}")
87
- return False
86
+ os.makedirs(os.path.dirname(output_path), exist_ok=True)
87
+
88
+ for attempt in range(1, retries + 1):
89
+ try:
90
+ data = urllib.request.urlopen(req, context=ctx, timeout=20).read()
91
+ # 验证下载到的是真实图片(> 5KB 且 magic bytes 正确)
92
+ if len(data) < 5000:
93
+ raise ValueError(f"下载文件过小: {len(data)} bytes")
94
+ if not data[:3] in (b"\xff\xd8\xff", b"\x89PN", b"GIF"):
95
+ raise ValueError(f"非图片格式: {data[:8]!r}")
96
+ with open(output_path, "wb") as f:
97
+ f.write(data)
98
+ return True
99
+ except Exception as e:
100
+ if attempt < retries:
101
+ time.sleep(retry_delay * attempt)
102
+ continue
103
+ print(f"下载失败(重试 {retries} 次后放弃): {e}")
104
+ return False
105
+ return False
88
106
 
89
107
 
90
108
  def download_pexels_image(pid, output_path, w=1200):
@@ -149,31 +167,58 @@ def aesthetics_score(image_path):
149
167
  def download_theme_images(theme, output_dir, count=6):
150
168
  """根据主题下载多张精美图片
151
169
 
152
- 自动跳过 404 候选,失败时回退到主题内其他候选。
170
+ 自动跳过 404/失败候选(带 3 次重试),下载到临时文件名,
171
+ 再按美学评分重命名为 img_1.jpg ~ img_N.jpg
153
172
  返回: 已下载图片路径列表(按美学评分降序)
154
173
  """
174
+ os.makedirs(output_dir, exist_ok=True)
155
175
  candidates = THEME_CANDIDATES.get(theme, THEME_CANDIDATES["books"])
156
- downloaded = []
176
+ downloaded = [] # 临时文件列表
177
+ failed_pids = []
157
178
 
179
+ # 1) 全部下载到临时文件
158
180
  for i, pid in enumerate(candidates):
159
181
  if len(downloaded) >= count:
160
182
  break
161
- output_path = os.path.join(output_dir, f"img_{len(downloaded) + 1}.jpg")
162
- if download_pexels_image(pid, output_path):
163
- downloaded.append(output_path)
164
-
165
- # 按美学评分降序排序(封面挑选时会取第一张)
183
+ tmp_path = os.path.join(output_dir, f"_tmp_{theme}_{i}_{pid}.jpg")
184
+ if download_pexels_image(pid, tmp_path):
185
+ downloaded.append(tmp_path)
186
+ else:
187
+ failed_pids.append(pid)
188
+
189
+ if failed_pids:
190
+ print(f" ⚠️ 候选 {failed_pids} 下载失败(共 {len(failed_pids)} 张)")
191
+ if not downloaded:
192
+ print(f" ❌ 主题 {theme} 所有候选图下载失败")
193
+ return []
194
+
195
+ # 2) 按美学评分排序
166
196
  scored = [(p, aesthetics_score(p)) for p in downloaded]
167
197
  scored.sort(key=lambda x: x[1], reverse=True)
168
198
 
169
- # 按评分重命名回 img_N.jpg
199
+ # 3) 先清空目标 img_N.jpg(避免 rename 冲突或 macOS 覆盖丢失数据)
200
+ for i in range(1, count + 1):
201
+ f = os.path.join(output_dir, f"img_{i}.jpg")
202
+ if os.path.exists(f):
203
+ os.remove(f)
204
+
205
+ # 4) 重命名到 img_N.jpg
206
+ result = []
170
207
  for new_idx, (path, score) in enumerate(scored, 1):
171
208
  new_path = os.path.join(output_dir, f"img_{new_idx}.jpg")
172
- if path != new_path:
173
- os.rename(path, new_path)
209
+ os.rename(path, new_path)
210
+ result.append(new_path)
174
211
  print(f" img_{new_idx}.jpg 评分 {score}")
175
212
 
176
- return [p for p, _ in scored]
213
+ # 5) 清理可能残留的临时文件
214
+ for fname in os.listdir(output_dir):
215
+ if fname.startswith("_tmp_"):
216
+ try:
217
+ os.remove(os.path.join(output_dir, fname))
218
+ except OSError:
219
+ pass
220
+
221
+ return result
177
222
 
178
223
 
179
224
  def pick_best_cover(theme_images_dir):
@@ -40,7 +40,13 @@ def ensure_theme_images(cover_dir, theme="books", count=6):
40
40
  return
41
41
 
42
42
  print(f"主题插图不足,自动下载 {count} 张(主题: {theme})...")
43
- download_theme_images(theme, cover_dir, count)
43
+ result = download_theme_images(theme, cover_dir, count)
44
+
45
+ if len(result) < count:
46
+ print(
47
+ f"⚠️ 实际下载 {len(result)}/{count} 张,建议换其他主题或重试",
48
+ file=sys.stderr,
49
+ )
44
50
 
45
51
 
46
52
  def ensure_cover_image(cover_dir):