ghostwriter-cli 0.1.0__tar.gz → 0.1.1__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.
Files changed (25) hide show
  1. {ghostwriter_cli-0.1.0 → ghostwriter_cli-0.1.1}/PKG-INFO +3 -3
  2. {ghostwriter_cli-0.1.0 → ghostwriter_cli-0.1.1}/README.md +2 -2
  3. {ghostwriter_cli-0.1.0 → ghostwriter_cli-0.1.1}/docs/index.html +31 -22
  4. {ghostwriter_cli-0.1.0 → ghostwriter_cli-0.1.1}/pyproject.toml +1 -1
  5. {ghostwriter_cli-0.1.0 → ghostwriter_cli-0.1.1}/src/ghostwriter/pipeline.py +42 -24
  6. {ghostwriter_cli-0.1.0 → ghostwriter_cli-0.1.1}/.github/workflows/publish.yml +0 -0
  7. {ghostwriter_cli-0.1.0 → ghostwriter_cli-0.1.1}/.gitignore +0 -0
  8. {ghostwriter_cli-0.1.0 → ghostwriter_cli-0.1.1}/CHANGELOG.md +0 -0
  9. {ghostwriter_cli-0.1.0 → ghostwriter_cli-0.1.1}/CLAUDE.md +0 -0
  10. {ghostwriter_cli-0.1.0 → ghostwriter_cli-0.1.1}/LICENSE +0 -0
  11. {ghostwriter_cli-0.1.0 → ghostwriter_cli-0.1.1}/src/ghostwriter/__init__.py +0 -0
  12. {ghostwriter_cli-0.1.0 → ghostwriter_cli-0.1.1}/src/ghostwriter/__main__.py +0 -0
  13. {ghostwriter_cli-0.1.0 → ghostwriter_cli-0.1.1}/src/ghostwriter/cleaner.py +0 -0
  14. {ghostwriter_cli-0.1.0 → ghostwriter_cli-0.1.1}/src/ghostwriter/cli.py +0 -0
  15. {ghostwriter_cli-0.1.0 → ghostwriter_cli-0.1.1}/src/ghostwriter/config.py +0 -0
  16. {ghostwriter_cli-0.1.0 → ghostwriter_cli-0.1.1}/src/ghostwriter/ghost.py +0 -0
  17. {ghostwriter_cli-0.1.0 → ghostwriter_cli-0.1.1}/src/ghostwriter/lexical.py +0 -0
  18. {ghostwriter_cli-0.1.0 → ghostwriter_cli-0.1.1}/src/ghostwriter/normalize.py +0 -0
  19. {ghostwriter_cli-0.1.0 → ghostwriter_cli-0.1.1}/src/ghostwriter/wechat.py +0 -0
  20. {ghostwriter_cli-0.1.0 → ghostwriter_cli-0.1.1}/tests/__init__.py +0 -0
  21. {ghostwriter_cli-0.1.0 → ghostwriter_cli-0.1.1}/tests/test_cleaner.py +0 -0
  22. {ghostwriter_cli-0.1.0 → ghostwriter_cli-0.1.1}/tests/test_config.py +0 -0
  23. {ghostwriter_cli-0.1.0 → ghostwriter_cli-0.1.1}/tests/test_lexical.py +0 -0
  24. {ghostwriter_cli-0.1.0 → ghostwriter_cli-0.1.1}/tests/test_normalize.py +0 -0
  25. {ghostwriter_cli-0.1.0 → ghostwriter_cli-0.1.1}/tests/test_pipeline.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ghostwriter-cli
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: Markdown → Ghost → WeChat publishing pipeline
5
5
  Project-URL: Homepage, https://github.com/yinguobing/ghostwriter
6
6
  Author: yinguobing
@@ -46,7 +46,7 @@ pip install -e ".[dev]"
46
46
  ### 方式 1:环境变量(推荐用于 CI/Docker)
47
47
 
48
48
  ```bash
49
- export GHOSTWRITER_GHOST_API_URL="https://yinguobing.com"
49
+ export GHOSTWRITER_GHOST_API_URL="https://your-ghost-blog.com"
50
50
  export GHOSTWRITER_GHOST_ADMIN_KEY_ID="your_key_id"
51
51
  export GHOSTWRITER_GHOST_ADMIN_KEY="your_hex_secret"
52
52
  export GHOSTWRITER_WECHAT_APPID="your_wechat_appid"
@@ -57,7 +57,7 @@ export GHOSTWRITER_WECHAT_SECRET="your_wechat_secret"
57
57
 
58
58
  ```bash
59
59
  # 交互式设置
60
- ghostwriter config set ghost.api_url https://yinguobing.com
60
+ ghostwriter config set ghost.api_url https://your-ghost-blog.com
61
61
  ghostwriter config set ghost.admin_key_id your_key_id
62
62
  ghostwriter config set ghost.admin_key your_hex_secret
63
63
  ghostwriter config set wechat.appid your_wechat_appid
@@ -31,7 +31,7 @@ pip install -e ".[dev]"
31
31
  ### 方式 1:环境变量(推荐用于 CI/Docker)
32
32
 
33
33
  ```bash
34
- export GHOSTWRITER_GHOST_API_URL="https://yinguobing.com"
34
+ export GHOSTWRITER_GHOST_API_URL="https://your-ghost-blog.com"
35
35
  export GHOSTWRITER_GHOST_ADMIN_KEY_ID="your_key_id"
36
36
  export GHOSTWRITER_GHOST_ADMIN_KEY="your_hex_secret"
37
37
  export GHOSTWRITER_WECHAT_APPID="your_wechat_appid"
@@ -42,7 +42,7 @@ export GHOSTWRITER_WECHAT_SECRET="your_wechat_secret"
42
42
 
43
43
  ```bash
44
44
  # 交互式设置
45
- ghostwriter config set ghost.api_url https://yinguobing.com
45
+ ghostwriter config set ghost.api_url https://your-ghost-blog.com
46
46
  ghostwriter config set ghost.admin_key_id your_key_id
47
47
  ghostwriter config set ghost.admin_key your_hex_secret
48
48
  ghostwriter config set wechat.appid your_wechat_appid
@@ -541,29 +541,38 @@
541
541
  <span class="dot r"></span><span class="dot y"></span><span class="dot g"></span>
542
542
  <span style="margin-left:8px">terminal</span>
543
543
  </div>
544
- <pre>git clone https://github.com/yinguobing/ghostwriter.git
544
+ <pre><span class="cm"># 从 PyPI 安装(推荐)</span>
545
+ pip install ghostwriter-cli
546
+
547
+ <span class="cm"># 或从源码安装</span>
548
+ git clone https://github.com/yinguobing/ghostwriter.git
545
549
  cd ghostwriter
546
- pip install requests PyJWT</pre>
550
+ pip install -e <span class="str">".[dev]"</span></pre>
547
551
  </div>
548
552
 
549
553
  <h3>配置</h3>
550
- <p>创建 <code>config.json</code>,填入微信公众号和 Ghost 的 API 密钥:</p>
554
+ <p>支持环境变量(CI/Docker 首选)或配置文件两种方式:</p>
551
555
  <div class="code-block">
552
556
  <div class="code-header">
553
557
  <span class="dot r"></span><span class="dot y"></span><span class="dot g"></span>
554
- <span style="margin-left:8px">config.json</span>
558
+ <span style="margin-left:8px">terminal</span>
555
559
  </div>
556
- <pre>{
557
- <span class="str">"wechat"</span>: {
558
- <span class="str">"appid"</span>: <span class="str">"your_wechat_appid"</span>,
559
- <span class="str">"secret"</span>: <span class="str">"your_wechat_secret"</span>
560
- },
561
- <span class="str">"ghost"</span>: {
562
- <span class="str">"api_url"</span>: <span class="str">"https://yinguobing.com"</span>,
563
- <span class="str">"admin_key_id"</span>: <span class="str">"your_key_id"</span>,
564
- <span class="str">"admin_key"</span>: <span class="str">"your_hex_secret"</span>
565
- }
566
- }</pre>
560
+ <pre><span class="cm"># 方式 1:环境变量(CI/Docker 首选)</span>
561
+ <span class="kw">export</span> GHOSTWRITER_GHOST_API_URL=<span class="str">"https://your-ghost-blog.com"</span>
562
+ <span class="kw">export</span> GHOSTWRITER_GHOST_ADMIN_KEY_ID=<span class="str">"your_key_id"</span>
563
+ <span class="kw">export</span> GHOSTWRITER_GHOST_ADMIN_KEY=<span class="str">"your_hex_secret"</span>
564
+ <span class="kw">export</span> GHOSTWRITER_WECHAT_APPID=<span class="str">"your_wechat_appid"</span>
565
+ <span class="kw">export</span> GHOSTWRITER_WECHAT_SECRET=<span class="str">"your_wechat_secret"</span>
566
+
567
+ <span class="cm"># 方式 2:CLI 交互式配置</span>
568
+ ghostwriter config set ghost.api_url https://your-ghost-blog.com
569
+ ghostwriter config set ghost.admin_key_id your_key_id
570
+ ghostwriter config set ghost.admin_key your_hex_secret
571
+ ghostwriter config set wechat.appid your_wechat_appid
572
+ ghostwriter config set wechat.secret your_wechat_secret
573
+
574
+ <span class="cm"># 查看当前配置(密钥已脱敏)</span>
575
+ ghostwriter config</pre>
567
576
  </div>
568
577
 
569
578
  <h3>使用</h3>
@@ -573,19 +582,19 @@ pip install requests PyJWT</pre>
573
582
  <span style="margin-left:8px">terminal</span>
574
583
  </div>
575
584
  <pre><span class="cm"># 列出 Ghost 文章</span>
576
- python3 ghostwriter.py list
585
+ ghostwriter list
577
586
 
578
587
  <span class="cm"># 发布 Markdown 到 Ghost</span>
579
- python3 ghostwriter.py publish article.md
588
+ ghostwriter publish article.md
580
589
 
581
590
  <span class="cm"># 同步到微信草稿</span>
582
- python3 ghostwriter.py &lt;article-id&gt;
583
-
584
- <span class="cm"># 一键发布 + 同步</span>
585
- python3 ghostwriter.py publish article.md --wechat
591
+ ghostwriter sync &lt;article-id&gt;
586
592
 
587
593
  <span class="cm"># 预览 HTML(不创建草稿)</span>
588
- python3 ghostwriter.py --preview &lt;article-id&gt;</pre>
594
+ ghostwriter sync --preview &lt;article-id&gt;
595
+
596
+ <span class="cm"># 一键发布 + 同步</span>
597
+ ghostwriter publish article.md --wechat</pre>
589
598
  </div>
590
599
  </section>
591
600
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "ghostwriter-cli"
7
- version = "0.1.0"
7
+ version = "0.1.1"
8
8
  description = "Markdown → Ghost → WeChat publishing pipeline"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -93,6 +93,20 @@ def restore_code_blocks(html):
93
93
 
94
94
  # ── Element transformers ─────────────────────────────────────
95
95
 
96
+ def convert_figure_div(html):
97
+ """Convert <figure> to <div> and <figcaption> to <p>.
98
+
99
+ Ghost uses <figure> to wrap images (image cards, bookmark cards),
100
+ but WeChat's whitelist doesn't include <figure> or <figcaption>.
101
+ Without this conversion, all images inside <figure> get stripped.
102
+ """
103
+ html = re.sub(r'<figure(\s[^>]*)>', r'<div\1>', html)
104
+ html = re.sub(r'</figure>', '</div>', html)
105
+ html = re.sub(r'<figcaption>', '<p>', html)
106
+ html = re.sub(r'</figcaption>', '</p>', html)
107
+ return html
108
+
109
+
96
110
  def convert_hr(html):
97
111
  """Convert <hr> to a styled divider <div>."""
98
112
  return re.sub(
@@ -320,18 +334,19 @@ def process_html(html_content, image_map):
320
334
  Pipeline stages (order is critical):
321
335
  1. Replace image URLs with WeChat CDN URLs
322
336
  2. Remove Ghost-specific HTML comments (<!--kg-card-*-->)
323
- 3. Convert <hr> to styled <div> (before whitelist filter)
324
- 4. Convert <table> to inline-block layout (before whitelist filter)
325
- 5. Protect code blocks with placeholders
326
- 6. Three-level whitelist filter (tags → attrs → styles)
327
- 7. Restore code blocks with WeChat-safe styling
328
- 8. Apply default styles (headings, code, blockquote)
329
- 9. Flatten nested blockquotes
330
- 10. Add paragraph spacing
331
- 11. Add image spacing
332
- 12. Convert links to text [url] format
333
- 13. Convert ordered lists to numbered <p>
334
- 14. Convert unordered lists to bulleted <p>
337
+ 3. Convert <figure>/<figcaption> to <div>/<p> (before whitelist)
338
+ 4. Convert <hr> to styled <div> (before whitelist filter)
339
+ 5. Convert <table> to inline-block layout (before whitelist filter)
340
+ 6. Protect code blocks with placeholders
341
+ 7. Three-level whitelist filter (tags attrs → styles)
342
+ 8. Restore code blocks with WeChat-safe styling
343
+ 9. Apply default styles (headings, code, blockquote)
344
+ 10. Flatten nested blockquotes
345
+ 11. Add paragraph spacing
346
+ 12. Add image spacing
347
+ 13. Convert links to text [url] format
348
+ 14. Convert ordered lists to numbered <p>
349
+ 15. Convert unordered lists to bulleted <p>
335
350
 
336
351
  Returns HTML suitable for WeChat draft creation.
337
352
  """
@@ -341,46 +356,49 @@ def process_html(html_content, image_map):
341
356
  # 2. Remove Ghost comments
342
357
  html = clean_ghost_comments(html)
343
358
 
344
- # 3. Convert <hr> (before whitelist — <hr> not in safe tags)
359
+ # 3. Convert <figure>/<figcaption> (before whitelist — not in safe tags)
360
+ html = convert_figure_div(html)
361
+
362
+ # 4. Convert <hr> (before whitelist — <hr> not in safe tags)
345
363
  html = convert_hr(html)
346
364
 
347
- # 4. Convert <table> (before whitelist — <table> not in safe tags)
365
+ # 5. Convert <table> (before whitelist — <table> not in safe tags)
348
366
  html = convert_table_to_div(html)
349
367
 
350
- # 5. Protect code blocks
368
+ # 6. Protect code blocks
351
369
  html = convert_code_blocks(html)
352
370
 
353
- # 6. Three-level whitelist filter
371
+ # 7. Three-level whitelist filter
354
372
  html = clean_html_for_wechat(html)
355
373
 
356
- # 7. Restore code blocks
374
+ # 8. Restore code blocks
357
375
  html = restore_code_blocks(html)
358
376
 
359
- # 8. Default styles
377
+ # 9. Default styles
360
378
  html = apply_wechat_styles(html)
361
379
 
362
- # 9. Flatten nested blockquotes
380
+ # 10. Flatten nested blockquotes
363
381
  html = flatten_nested_blockquotes(html)
364
382
 
365
- # 10. Paragraph spacing
383
+ # 11. Paragraph spacing
366
384
  html = re.sub(
367
385
  r'<p\b(?!\s+[^>]*style=)([^>]*)>',
368
386
  r'<p style="margin-bottom: 16px;">', html,
369
387
  )
370
388
 
371
- # 11. Image spacing
389
+ # 12. Image spacing
372
390
  html = re.sub(
373
391
  r'<img([^>]*)>',
374
392
  r'<img style="margin-bottom: 16px;"\1>', html,
375
393
  )
376
394
 
377
- # 12. Links → text [url]
395
+ # 13. Links → text [url]
378
396
  html = convert_links(html)
379
397
 
380
- # 13. Ordered lists → numbered <p>
398
+ # 14. Ordered lists → numbered <p>
381
399
  html = convert_ordered_list(html)
382
400
 
383
- # 14. Unordered lists → bullet <p>
401
+ # 15. Unordered lists → bullet <p>
384
402
  html = convert_unordered_list(html)
385
403
 
386
404
  return html
File without changes