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.
- {ghostwriter_cli-0.1.0 → ghostwriter_cli-0.1.1}/PKG-INFO +3 -3
- {ghostwriter_cli-0.1.0 → ghostwriter_cli-0.1.1}/README.md +2 -2
- {ghostwriter_cli-0.1.0 → ghostwriter_cli-0.1.1}/docs/index.html +31 -22
- {ghostwriter_cli-0.1.0 → ghostwriter_cli-0.1.1}/pyproject.toml +1 -1
- {ghostwriter_cli-0.1.0 → ghostwriter_cli-0.1.1}/src/ghostwriter/pipeline.py +42 -24
- {ghostwriter_cli-0.1.0 → ghostwriter_cli-0.1.1}/.github/workflows/publish.yml +0 -0
- {ghostwriter_cli-0.1.0 → ghostwriter_cli-0.1.1}/.gitignore +0 -0
- {ghostwriter_cli-0.1.0 → ghostwriter_cli-0.1.1}/CHANGELOG.md +0 -0
- {ghostwriter_cli-0.1.0 → ghostwriter_cli-0.1.1}/CLAUDE.md +0 -0
- {ghostwriter_cli-0.1.0 → ghostwriter_cli-0.1.1}/LICENSE +0 -0
- {ghostwriter_cli-0.1.0 → ghostwriter_cli-0.1.1}/src/ghostwriter/__init__.py +0 -0
- {ghostwriter_cli-0.1.0 → ghostwriter_cli-0.1.1}/src/ghostwriter/__main__.py +0 -0
- {ghostwriter_cli-0.1.0 → ghostwriter_cli-0.1.1}/src/ghostwriter/cleaner.py +0 -0
- {ghostwriter_cli-0.1.0 → ghostwriter_cli-0.1.1}/src/ghostwriter/cli.py +0 -0
- {ghostwriter_cli-0.1.0 → ghostwriter_cli-0.1.1}/src/ghostwriter/config.py +0 -0
- {ghostwriter_cli-0.1.0 → ghostwriter_cli-0.1.1}/src/ghostwriter/ghost.py +0 -0
- {ghostwriter_cli-0.1.0 → ghostwriter_cli-0.1.1}/src/ghostwriter/lexical.py +0 -0
- {ghostwriter_cli-0.1.0 → ghostwriter_cli-0.1.1}/src/ghostwriter/normalize.py +0 -0
- {ghostwriter_cli-0.1.0 → ghostwriter_cli-0.1.1}/src/ghostwriter/wechat.py +0 -0
- {ghostwriter_cli-0.1.0 → ghostwriter_cli-0.1.1}/tests/__init__.py +0 -0
- {ghostwriter_cli-0.1.0 → ghostwriter_cli-0.1.1}/tests/test_cleaner.py +0 -0
- {ghostwriter_cli-0.1.0 → ghostwriter_cli-0.1.1}/tests/test_config.py +0 -0
- {ghostwriter_cli-0.1.0 → ghostwriter_cli-0.1.1}/tests/test_lexical.py +0 -0
- {ghostwriter_cli-0.1.0 → ghostwriter_cli-0.1.1}/tests/test_normalize.py +0 -0
- {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.
|
|
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://
|
|
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://
|
|
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://
|
|
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://
|
|
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
|
|
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
|
|
550
|
+
pip install -e <span class="str">".[dev]"</span></pre>
|
|
547
551
|
</div>
|
|
548
552
|
|
|
549
553
|
<h3>配置</h3>
|
|
550
|
-
<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">
|
|
558
|
+
<span style="margin-left:8px">terminal</span>
|
|
555
559
|
</div>
|
|
556
|
-
<pre>
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
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
|
-
|
|
585
|
+
ghostwriter list
|
|
577
586
|
|
|
578
587
|
<span class="cm"># 发布 Markdown 到 Ghost</span>
|
|
579
|
-
|
|
588
|
+
ghostwriter publish article.md
|
|
580
589
|
|
|
581
590
|
<span class="cm"># 同步到微信草稿</span>
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
<span class="cm"># 一键发布 + 同步</span>
|
|
585
|
-
python3 ghostwriter.py publish article.md --wechat
|
|
591
|
+
ghostwriter sync <article-id>
|
|
586
592
|
|
|
587
593
|
<span class="cm"># 预览 HTML(不创建草稿)</span>
|
|
588
|
-
|
|
594
|
+
ghostwriter sync --preview <article-id>
|
|
595
|
+
|
|
596
|
+
<span class="cm"># 一键发布 + 同步</span>
|
|
597
|
+
ghostwriter publish article.md --wechat</pre>
|
|
589
598
|
</div>
|
|
590
599
|
</section>
|
|
591
600
|
|
|
@@ -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 <
|
|
324
|
-
4. Convert <
|
|
325
|
-
5.
|
|
326
|
-
6.
|
|
327
|
-
7.
|
|
328
|
-
8.
|
|
329
|
-
9.
|
|
330
|
-
10.
|
|
331
|
-
11. Add
|
|
332
|
-
12.
|
|
333
|
-
13. Convert
|
|
334
|
-
14. Convert
|
|
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 <
|
|
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
|
-
#
|
|
365
|
+
# 5. Convert <table> (before whitelist — <table> not in safe tags)
|
|
348
366
|
html = convert_table_to_div(html)
|
|
349
367
|
|
|
350
|
-
#
|
|
368
|
+
# 6. Protect code blocks
|
|
351
369
|
html = convert_code_blocks(html)
|
|
352
370
|
|
|
353
|
-
#
|
|
371
|
+
# 7. Three-level whitelist filter
|
|
354
372
|
html = clean_html_for_wechat(html)
|
|
355
373
|
|
|
356
|
-
#
|
|
374
|
+
# 8. Restore code blocks
|
|
357
375
|
html = restore_code_blocks(html)
|
|
358
376
|
|
|
359
|
-
#
|
|
377
|
+
# 9. Default styles
|
|
360
378
|
html = apply_wechat_styles(html)
|
|
361
379
|
|
|
362
|
-
#
|
|
380
|
+
# 10. Flatten nested blockquotes
|
|
363
381
|
html = flatten_nested_blockquotes(html)
|
|
364
382
|
|
|
365
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
395
|
+
# 13. Links → text [url]
|
|
378
396
|
html = convert_links(html)
|
|
379
397
|
|
|
380
|
-
#
|
|
398
|
+
# 14. Ordered lists → numbered <p>
|
|
381
399
|
html = convert_ordered_list(html)
|
|
382
400
|
|
|
383
|
-
#
|
|
401
|
+
# 15. Unordered lists → bullet <p>
|
|
384
402
|
html = convert_unordered_list(html)
|
|
385
403
|
|
|
386
404
|
return html
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|