wechat-media-writer 2.2.8 → 2.2.10

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/README.md CHANGED
@@ -122,12 +122,10 @@ wechat-media-writer/
122
122
  │ └── cli.js # CLI 入口
123
123
  ├── scripts/
124
124
  │ ├── book_cover.py # 书籍封面获取
125
- │ ├── image_downloader.py # 主题插图下载
125
+ │ ├── image_downloader.py # 主题插图下载(含美学评分挑选封面)
126
126
  │ ├── wechat_api.py # 微信 API 封装(token/上传/草稿)
127
127
  │ ├── publish.py # 一键发布主脚本(含封面/插图自动补齐)
128
- │ └── install.js # npm postinstall(检查 Python/Pillow)
129
- └── references/
130
- └── publish_template.py # 发布脚本模板
128
+ │ └── install.js # npm postinstall(检查 Python/Pillow + 注册 Skill
131
129
  ```
132
130
 
133
131
  ## 发布到 npm
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,7 +153,7 @@ 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
  <!-- 金句引用块(圆角浅背景,无左侧竖条) -->
@@ -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
  <!-- 分隔符(章节之间) -->
@@ -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>
@@ -397,6 +397,7 @@ python3 scripts/publish.py --title "标题" --content-file content.html --cover-
397
397
  <p style="font-size:14px;color:{T2};text-align:center;margin:0 0 4px;line-height:1.8;font-weight:bold;">
398
398
  <span>「 一句话总结:核心洞察 」</span>
399
399
  </p>
400
+ <p style="text-align:center;margin:24px 0 0;color:#ccc;">· END ·</p>
400
401
  ```
401
402
 
402
403
  ### 评分格式要求
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wechat-media-writer",
3
- "version": "2.2.8",
3
+ "version": "2.2.10",
4
4
  "description": "微信公众号书评、影评文章自动生成与发布 - Skill",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -9,7 +9,6 @@
9
9
  "files": [
10
10
  "bin/",
11
11
  "scripts/",
12
- "references/",
13
12
  "SKILL.md",
14
13
  "README.md"
15
14
  ],
@@ -74,22 +74,35 @@ THEME_CANDIDATES = {
74
74
  }
75
75
 
76
76
 
77
- def download_image(url, output_path):
78
- """下载图片到指定路径"""
77
+ def download_image(url, output_path, retries=3, retry_delay=1):
78
+ """下载图片到指定路径,带重试机制"""
79
+ import time
80
+
79
81
  ctx = ssl.create_default_context()
80
82
  ctx.check_hostname = False
81
83
  ctx.verify_mode = ssl.CERT_NONE
82
84
 
83
85
  req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
84
- try:
85
- data = urllib.request.urlopen(req, context=ctx, timeout=15).read()
86
- os.makedirs(os.path.dirname(output_path), exist_ok=True)
87
- with open(output_path, "wb") as f:
88
- f.write(data)
89
- return True
90
- except Exception as e:
91
- print(f"下载失败: {e}")
92
- 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
93
106
 
94
107
 
95
108
  def download_pexels_image(pid, output_path, w=1200):
@@ -154,12 +167,14 @@ def aesthetics_score(image_path):
154
167
  def download_theme_images(theme, output_dir, count=6):
155
168
  """根据主题下载多张精美图片
156
169
 
157
- 自动跳过 404/失败候选,下载到临时文件名,再按美学评分重命名为 img_1.jpg ~ img_N.jpg
170
+ 自动跳过 404/失败候选(带 3 次重试),下载到临时文件名,
171
+ 再按美学评分重命名为 img_1.jpg ~ img_N.jpg
158
172
  返回: 已下载图片路径列表(按美学评分降序)
159
173
  """
160
174
  os.makedirs(output_dir, exist_ok=True)
161
175
  candidates = THEME_CANDIDATES.get(theme, THEME_CANDIDATES["books"])
162
176
  downloaded = [] # 临时文件列表
177
+ failed_pids = []
163
178
 
164
179
  # 1) 全部下载到临时文件
165
180
  for i, pid in enumerate(candidates):
@@ -168,8 +183,13 @@ def download_theme_images(theme, output_dir, count=6):
168
183
  tmp_path = os.path.join(output_dir, f"_tmp_{theme}_{i}_{pid}.jpg")
169
184
  if download_pexels_image(pid, tmp_path):
170
185
  downloaded.append(tmp_path)
186
+ else:
187
+ failed_pids.append(pid)
171
188
 
189
+ if failed_pids:
190
+ print(f" ⚠️ 候选 {failed_pids} 下载失败(共 {len(failed_pids)} 张)")
172
191
  if not downloaded:
192
+ print(f" ❌ 主题 {theme} 所有候选图下载失败")
173
193
  return []
174
194
 
175
195
  # 2) 按美学评分排序
@@ -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):
@@ -1,248 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- 微信公众号读书拆解发布脚本模板
4
- 使用 scripts/ 目录下的模块
5
-
6
- 用法:
7
- 1. 填写下方的变量(标题、摘要、HTML内容等)
8
- 2. 确保封面图已准备好(使用 scripts/image_downloader.py 下载主题贴图)
9
- 3. 运行: python3 references/publish_template.py
10
- """
11
-
12
- import os
13
- import sys
14
-
15
- scripts_dir = os.path.join(
16
- os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "scripts"
17
- )
18
- sys.path.insert(0, scripts_dir)
19
-
20
- from wechat_api import load_config, get_access_token, upload_cover, create_draft
21
-
22
-
23
- # ========== 替换以下内容 ==========
24
-
25
- TITLE = "《书名》全书深度拆解:犀利洞察"
26
- AUTHOR = "读书笔记"
27
- DIGEST = "摘要文本,不超过120字。"
28
-
29
- # 封面图目录(包含 cover_900x500.jpg + img_*.jpg 主题贴图)
30
- COVER_DIR = "/tmp/book_output"
31
-
32
- # 主题色:主色(深色)+ 强调色(暖/亮色)
33
- T = "#2E5266" # 主色
34
- T2 = "#D4A24C" # 强调色
35
- BODY = "#555" # 正文色
36
- CBG = "#F0EDE5" # 引用块背景
37
-
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段式,无数字编号,全文主题贴图穿插)
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
-
50
- <p style="font-size:12px;color:{T2};text-align:center;letter-spacing:4px;margin:24px 0 8px;line-height:3em;">
51
- <span>📖 读书拆解</span>
52
- </p>
53
-
54
- <p style="font-size:22px;font-weight:bold;color:{T};text-align:center;margin:0 0 6px;line-height:1.5;">
55
- <span>《书名》全书深度拆解</span>
56
- </p>
57
-
58
- <p style="font-size:14px;color:#999;text-align:center;margin:0 0 20px;line-height:3em;">
59
- <span>副标题(一句话点睛)</span>
60
- </p>
61
-
62
- <!-- 主题配图1(头部) -->
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="主题配图"/>
65
- </p>
66
-
67
- <!-- 先搞懂写书的这个人 -->
68
- <p style="font-size:18px;font-weight:bold;color:{T};text-align:center;margin:0 0 16px;line-height:1em;">
69
- <span>先搞懂写书的这个人</span>
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:16px 20px;background:{CBG};border-radius:8px;">
82
- <span>金句或核心观点。整段用主题主色加粗,圆角浅背景与正文形成视觉对比。</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>
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>
121
- <p style="font-size:16px;line-height:2;color:{BODY};margin:0 0 16px;text-indent:2em;">
122
- <span>短板一:客观指出书中局限。<br/>短板二:逻辑漏洞分析。<br/>短板三:适用边界。<br/>短板四:时代局限。</span>
123
- </p>
124
-
125
- <!-- 金句二 -->
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
- </p>
129
-
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>
150
-
151
- <p style="font-size:14px;color:#ccc;text-align:center;letter-spacing:8px;margin:20px 0;">· · · · · ·</p>
152
-
153
- <!-- 值不值得花时间读 -->
154
- <p style="font-size:18px;font-weight:bold;color:{T};text-align:center;margin:0 0 16px;line-height:1em;">
155
- <span>值不值得花时间读</span>
156
- </p>
157
-
158
- <p style="font-size:28px;text-align:center;margin:0 0 8px;">
159
- <span style="color:{T2};">★★★★☆</span>
160
- </p>
161
-
162
- <p style="font-size:16px;color:{BODY};text-align:center;margin:0 0 16px;">
163
- <span>4 / 5 星</span>
164
- </p>
165
-
166
- <p style="font-size:16px;line-height:2;color:{BODY};margin:0 0 16px;text-indent:2em;">
167
- <span>为什么给X星?简要说明评分理由,诚实评价,3-4星是常态,5星极少。</span>
168
- </p>
169
-
170
- <p style="font-size:16px;line-height:2;color:{BODY};margin:0 0 8px;text-indent:2em;">
171
- <strong style="color:{T};">适合人群:</strong>
172
- </p>
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>
181
- </p>
182
-
183
- <p style="font-size:16px;line-height:2;color:{BODY};margin:0 0 8px;text-indent:2em;">
184
- <strong style="color:{T};">不适合人群:</strong>
185
- </p>
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>
194
- </p>
195
-
196
- <!-- 结尾页:主题配图 + 书名 + 作者 + 核心总结 -->
197
- <p style="margin:0 0 16px;text-align:center;">
198
- <img src="{THEME_IMG_END}" style="width:100%;max-width:600px;display:block;margin:0 auto;border-radius:4px;" alt="主题配图"/>
199
- </p>
200
-
201
- <p style="font-size:14px;color:#ccc;text-align:center;letter-spacing:8px;margin:24px 0 16px;">· · · · · ·</p>
202
-
203
- <p style="font-size:18px;font-weight:bold;color:{T};text-align:center;margin:0 0 8px;line-height:1.6;">
204
- <span>📖 《书名》</span>
205
- </p>
206
- <p style="font-size:14px;color:{BODY};text-align:center;margin:0 0 4px;line-height:1.6;">
207
- <span>作者 · 读书笔记</span>
208
- </p>
209
- <p style="font-size:14px;color:{T2};text-align:center;margin:8px 0 32px;line-height:1.8;font-weight:bold;">
210
- <span>「 一句话总结:核心洞察 」</span>
211
- </p>
212
-
213
- </section>"""
214
-
215
-
216
- def main():
217
- print("=" * 50)
218
- print("微信公众号 - 书名 发布")
219
- print("=" * 50)
220
-
221
- config = load_config()
222
- token = get_access_token(config)
223
- print("\n✓ Token 已获取\n")
224
-
225
- cover_900_path = os.path.join(COVER_DIR, "cover_900x500.jpg")
226
- if not os.path.exists(cover_900_path):
227
- print(f"错误: 封面图不存在 {cover_900_path}", file=sys.stderr)
228
- print("请先运行主题贴图下载与封面裁剪流程", file=sys.stderr)
229
- sys.exit(1)
230
-
231
- print("上传封面图...")
232
- cover_mid = upload_cover(token, cover_900_path)
233
-
234
- print("\n创建草稿...")
235
- draft_id = create_draft(token, TITLE, AUTHOR, DIGEST, HTML, cover_mid)
236
-
237
- print("\n" + "=" * 50)
238
- print("发布成功!")
239
- print("=" * 50)
240
- print(f"标题: {TITLE}")
241
- print(f"作者: {AUTHOR}")
242
- print(f"草稿ID: {draft_id}")
243
- print(f"\n请前往微信公众平台查看草稿:")
244
- print(f"https://mp.weixin.qq.com")
245
-
246
-
247
- if __name__ == "__main__":
248
- main()