ghostwriter-cli 0.1.0__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/.github/workflows/publish.yml +21 -0
- ghostwriter_cli-0.1.0/.gitignore +6 -0
- ghostwriter_cli-0.1.0/CHANGELOG.md +8 -0
- ghostwriter_cli-0.1.0/CLAUDE.md +113 -0
- ghostwriter_cli-0.1.0/LICENSE +21 -0
- ghostwriter_cli-0.1.0/PKG-INFO +172 -0
- ghostwriter_cli-0.1.0/README.md +157 -0
- ghostwriter_cli-0.1.0/docs/index.html +666 -0
- ghostwriter_cli-0.1.0/pyproject.toml +35 -0
- ghostwriter_cli-0.1.0/src/ghostwriter/__init__.py +28 -0
- ghostwriter_cli-0.1.0/src/ghostwriter/__main__.py +5 -0
- ghostwriter_cli-0.1.0/src/ghostwriter/cleaner.py +130 -0
- ghostwriter_cli-0.1.0/src/ghostwriter/cli.py +500 -0
- ghostwriter_cli-0.1.0/src/ghostwriter/config.py +242 -0
- ghostwriter_cli-0.1.0/src/ghostwriter/ghost.py +126 -0
- ghostwriter_cli-0.1.0/src/ghostwriter/lexical.py +252 -0
- ghostwriter_cli-0.1.0/src/ghostwriter/normalize.py +23 -0
- ghostwriter_cli-0.1.0/src/ghostwriter/pipeline.py +386 -0
- ghostwriter_cli-0.1.0/src/ghostwriter/wechat.py +125 -0
- ghostwriter_cli-0.1.0/tests/__init__.py +0 -0
- ghostwriter_cli-0.1.0/tests/test_cleaner.py +100 -0
- ghostwriter_cli-0.1.0/tests/test_config.py +193 -0
- ghostwriter_cli-0.1.0/tests/test_lexical.py +119 -0
- ghostwriter_cli-0.1.0/tests/test_normalize.py +41 -0
- ghostwriter_cli-0.1.0/tests/test_pipeline.py +185 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
publish:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
permissions:
|
|
11
|
+
id-token: write
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
- uses: actions/setup-python@v5
|
|
15
|
+
with:
|
|
16
|
+
python-version: "3.x"
|
|
17
|
+
- run: pip install -e ".[dev]"
|
|
18
|
+
- run: python -m pytest tests/ -v
|
|
19
|
+
- run: pip install build
|
|
20
|
+
- run: python -m build
|
|
21
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Project overview
|
|
6
|
+
|
|
7
|
+
ghostwriter is a Python CLI that implements a **Markdown → Ghost → WeChat** publishing pipeline. Write Markdown, publish to a Ghost blog, and sync to a WeChat Official Account draft.
|
|
8
|
+
|
|
9
|
+
## Commands
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Install (from PyPI)
|
|
13
|
+
pip install ghostwriter-cli
|
|
14
|
+
|
|
15
|
+
# Install (editable, with dev deps)
|
|
16
|
+
pip install -e ".[dev]"
|
|
17
|
+
|
|
18
|
+
# Run tests
|
|
19
|
+
python -m pytest tests/ -v
|
|
20
|
+
|
|
21
|
+
# CLI
|
|
22
|
+
ghostwriter list # list Ghost posts
|
|
23
|
+
ghostwriter publish article.md # publish Markdown to Ghost
|
|
24
|
+
ghostwriter publish article.md --draft --wechat
|
|
25
|
+
ghostwriter <article-id> # sync Ghost→WeChat
|
|
26
|
+
ghostwriter --preview <article-id> # preview HTML, no draft
|
|
27
|
+
ghostwriter config # show config (secrets masked)
|
|
28
|
+
ghostwriter config set <key> <value> # write config key
|
|
29
|
+
ghostwriter config path # print config file path
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Configuration architecture
|
|
33
|
+
|
|
34
|
+
Two-tier system, env vars take priority:
|
|
35
|
+
|
|
36
|
+
1. **Environment variables** (primary, CI/Docker):
|
|
37
|
+
`GHOSTWRITER_GHOST_API_URL`, `GHOSTWRITER_GHOST_ADMIN_KEY_ID`,
|
|
38
|
+
`GHOSTWRITER_GHOST_ADMIN_KEY`, `GHOSTWRITER_WECHAT_APPID`,
|
|
39
|
+
`GHOSTWRITER_WECHAT_SECRET`
|
|
40
|
+
|
|
41
|
+
2. **Config file** (fallback): `~/.config/ghostwriter/config.json`,
|
|
42
|
+
managed via `ghostwriter config set <key> <value>`
|
|
43
|
+
|
|
44
|
+
`load_config()` validates all 5 required keys are present, exits with helpful
|
|
45
|
+
error if any are missing. See `src/ghostwriter/config.py`.
|
|
46
|
+
|
|
47
|
+
## Package structure
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
src/ghostwriter/
|
|
51
|
+
├── __init__.py # public API re-exports
|
|
52
|
+
├── __main__.py # python -m ghostwriter
|
|
53
|
+
├── cli.py # CLI dispatch: main(), sync_article, publish_md_to_ghost, _cmd_publish
|
|
54
|
+
├── config.py # load_config(), set_config_value(), show_config()
|
|
55
|
+
├── ghost.py # Ghost Admin API: JWT auth, CRUD, image upload, author lookup
|
|
56
|
+
├── wechat.py # WeChat API: token cache, material upload, draft creation
|
|
57
|
+
├── cleaner.py # _WeChatCleaner HTML parser + whitelists + clean_html_for_wechat
|
|
58
|
+
├── pipeline.py # process_html() + 14 transform stages (hr, table, links, lists, etc.)
|
|
59
|
+
├── lexical.py # md_to_ghost_lexical(): Markdown → Ghost Lexical JSON
|
|
60
|
+
└── normalize.py # normalize_title(): Unicode → ASCII for WeChat compatibility
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Architecture
|
|
64
|
+
|
|
65
|
+
### 1. Ghost API layer (`ghost.py`)
|
|
66
|
+
JWT auth (`get_ghost_token`) → generic `_api_request` helper used by
|
|
67
|
+
`ghost_api_get/post/put/delete`. All Admin API calls go through path
|
|
68
|
+
normalization: `{api_url.rstrip('/')}/{path.lstrip('/')}`.
|
|
69
|
+
`upload_image_to_ghost` and `get_ghost_authors` build their own URLs (Content API).
|
|
70
|
+
|
|
71
|
+
### 2. WeChat API layer (`wechat.py`)
|
|
72
|
+
Token management with disk cache (`/tmp/wechat_token.json`), permanent
|
|
73
|
+
material upload for images, draft creation. Token is cached until 60s
|
|
74
|
+
before expiry.
|
|
75
|
+
|
|
76
|
+
### 3. HTML processing pipeline (`pipeline.py`)
|
|
77
|
+
**Core of Ghost→WeChat conversion.** Order is strict — reordering breaks things:
|
|
78
|
+
|
|
79
|
+
1. Replace image URLs with WeChat CDN URLs
|
|
80
|
+
2. Strip Ghost HTML comments (`<!--kg-card-*-->`)
|
|
81
|
+
3. Convert `<hr>` → styled `<div>` (before whitelist)
|
|
82
|
+
4. Convert `<table>` → inline-block `<span>` layout (before whitelist)
|
|
83
|
+
5. Extract code blocks to placeholders (protect from filter)
|
|
84
|
+
6. Three-level whitelist filter: tags → attributes → CSS properties
|
|
85
|
+
7. Restore code blocks with WeChat-safe `<pre>` styling
|
|
86
|
+
8. Apply default styles (heading sizes, inline code, blockquote)
|
|
87
|
+
9. Flatten nested blockquotes
|
|
88
|
+
10. Paragraph/image spacing
|
|
89
|
+
11. Convert links to `text [url]`
|
|
90
|
+
12. Convert `<ol>` → numbered `<p>`, `<ul>` → bullet `<p>`
|
|
91
|
+
|
|
92
|
+
Whitelists live in `cleaner.py`: `WECHAT_SAFE_TAGS`, `WECHAT_SAFE_ATTRS`,
|
|
93
|
+
`WECHAT_SAFE_STYLES`.
|
|
94
|
+
|
|
95
|
+
### 4. Markdown → Ghost Lexical (`lexical.py`)
|
|
96
|
+
Converts Markdown to Ghost Lexical JSON (not mobiledoc). Tables become
|
|
97
|
+
`html` cards. Supports headings, code blocks, lists, hr, inline formatting
|
|
98
|
+
(bold, italic, code, links).
|
|
99
|
+
|
|
100
|
+
### 5. CLI (`cli.py`)
|
|
101
|
+
Commands: `list`, `publish`, `config`, sync (by article ID or `--preview`).
|
|
102
|
+
`main()` handles dispatch. `sync_article()` is the Ghost→WeChat flow.
|
|
103
|
+
`publish_md_to_ghost()` handles Markdown→Ghost with author lookup fallback.
|
|
104
|
+
|
|
105
|
+
## Key design decisions
|
|
106
|
+
|
|
107
|
+
- **Code block protection:** Extracted to `__CODE_BLOCK_PLACEHOLDER__N__` before
|
|
108
|
+
the whitelist filter, restored afterward. Prevents filter from stripping tags.
|
|
109
|
+
- **Author fallback chain:** Ghost Content API → hardcoded IDs for known slugs.
|
|
110
|
+
- **WeChat constraints:** Author ≤8 bytes, digest ≤120 bytes, titles must not
|
|
111
|
+
contain Unicode special characters (normalized via `normalize_title()`).
|
|
112
|
+
- **Preview mode skips WeChat:** `--preview` only reads Ghost article and saves
|
|
113
|
+
HTML to `/tmp`, no WeChat credentials needed.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 yinguobing
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ghostwriter-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Markdown → Ghost → WeChat publishing pipeline
|
|
5
|
+
Project-URL: Homepage, https://github.com/yinguobing/ghostwriter
|
|
6
|
+
Author: yinguobing
|
|
7
|
+
License: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Python: >=3.9
|
|
10
|
+
Requires-Dist: pyjwt>=2.0
|
|
11
|
+
Requires-Dist: requests>=2.25
|
|
12
|
+
Provides-Extra: dev
|
|
13
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# ghostwriter
|
|
17
|
+
|
|
18
|
+
Markdown → Ghost → WeChat 发布管道。写一次,多处发布。
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
markdown → Ghost 博客 → 微信公众号
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## 功能
|
|
25
|
+
|
|
26
|
+
- **发布**:Markdown 文件直接发布到 Ghost 博客(Lexical 格式,编辑器可编辑)
|
|
27
|
+
- **同步**:已发布的 Ghost 文章同步到微信公众号草稿箱
|
|
28
|
+
- **全自动**:发布后一步同步到微信
|
|
29
|
+
|
|
30
|
+
## 安装
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# 从 PyPI 安装(推荐)
|
|
34
|
+
pip install ghostwriter-cli
|
|
35
|
+
|
|
36
|
+
# 或从源码安装(开发模式)
|
|
37
|
+
git clone https://github.com/yinguobing/ghostwriter.git
|
|
38
|
+
cd ghostwriter
|
|
39
|
+
pip install -e ".[dev]"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## 配置
|
|
43
|
+
|
|
44
|
+
支持两种配置方式(环境变量优先):
|
|
45
|
+
|
|
46
|
+
### 方式 1:环境变量(推荐用于 CI/Docker)
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
export GHOSTWRITER_GHOST_API_URL="https://yinguobing.com"
|
|
50
|
+
export GHOSTWRITER_GHOST_ADMIN_KEY_ID="your_key_id"
|
|
51
|
+
export GHOSTWRITER_GHOST_ADMIN_KEY="your_hex_secret"
|
|
52
|
+
export GHOSTWRITER_WECHAT_APPID="your_wechat_appid"
|
|
53
|
+
export GHOSTWRITER_WECHAT_SECRET="your_wechat_secret"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 方式 2:配置文件(适合本地使用)
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# 交互式设置
|
|
60
|
+
ghostwriter config set ghost.api_url https://yinguobing.com
|
|
61
|
+
ghostwriter config set ghost.admin_key_id your_key_id
|
|
62
|
+
ghostwriter config set ghost.admin_key your_hex_secret
|
|
63
|
+
ghostwriter config set wechat.appid your_wechat_appid
|
|
64
|
+
ghostwriter config set wechat.secret your_wechat_secret
|
|
65
|
+
|
|
66
|
+
# 查看当前配置(密钥已脱敏)
|
|
67
|
+
ghostwriter config
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
配置文件保存在 `~/.config/ghostwriter/config.json`。
|
|
71
|
+
|
|
72
|
+
可选字段:
|
|
73
|
+
```bash
|
|
74
|
+
# 作者映射(Ghost Content API 不可用时的离线回退)
|
|
75
|
+
ghostwriter config set authors.<slug> <ghost_author_id>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Ghost Admin API Key 获取:**
|
|
79
|
+
Ghost 后台 → Settings → Advanced → Integrations → Add custom integration → 复制 `Admin API Key`(格式为 `key_id:hex_secret`,拆成两段填入)
|
|
80
|
+
|
|
81
|
+
**微信 AppID/Secret 获取:**
|
|
82
|
+
[微信公众平台](https://mp.weixin.qq.com) → 设置与开发 → 基本配置
|
|
83
|
+
|
|
84
|
+
## 用法
|
|
85
|
+
|
|
86
|
+
### 列出 Ghost 文章
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
ghostwriter list
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### 发布 Markdown 到 Ghost
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
# 直接发布
|
|
96
|
+
ghostwriter publish article.md
|
|
97
|
+
|
|
98
|
+
# 指定标题和标签
|
|
99
|
+
ghostwriter publish article.md --title "我的文章" --tags Ghost,开源
|
|
100
|
+
|
|
101
|
+
# 指定作者(slug,默认 xiaohei)
|
|
102
|
+
ghostwriter publish article.md --author guobing
|
|
103
|
+
|
|
104
|
+
# 先存草稿
|
|
105
|
+
ghostwriter publish article.md --draft
|
|
106
|
+
|
|
107
|
+
# 发布后自动同步到微信
|
|
108
|
+
ghostwriter publish article.md --wechat
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### 同步 Ghost 文章到微信草稿
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
# 先列出文章获取 ID
|
|
115
|
+
ghostwriter list
|
|
116
|
+
|
|
117
|
+
# 同步指定文章
|
|
118
|
+
ghostwriter <article-id>
|
|
119
|
+
|
|
120
|
+
# 预览 HTML(不创建草稿)
|
|
121
|
+
ghostwriter --preview <article-id>
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## 管道说明
|
|
125
|
+
|
|
126
|
+
### Markdown → Ghost(`publish` 命令)
|
|
127
|
+
|
|
128
|
+
将 Markdown 文件转换为 Ghost 的 **Lexical 格式**(基于 `@tryghost/kg-lexical-html-renderer`),支持:
|
|
129
|
+
|
|
130
|
+
- 标题(h1~h6)
|
|
131
|
+
- 段落、粗体、斜体、行内代码、链接
|
|
132
|
+
- 围栏代码块(带语言标记)
|
|
133
|
+
- 表格(以 HTML card 渲染)
|
|
134
|
+
- 有序/无序列表
|
|
135
|
+
- 分割线
|
|
136
|
+
|
|
137
|
+
转换后的文章在 Ghost 编辑器里可以正常编辑。
|
|
138
|
+
|
|
139
|
+
### Ghost → 微信(`sync` 命令)
|
|
140
|
+
|
|
141
|
+
从 Ghost API 获取文章 HTML,经过多层处理管道后推送到微信公众号草稿箱:
|
|
142
|
+
|
|
143
|
+
1. 图片上传到微信永久素材,替换为 CDN 地址
|
|
144
|
+
2. 白名单三层过滤(标签/属性/样式)
|
|
145
|
+
3. 代码块保护与恢复
|
|
146
|
+
4. 微信不支持的标签转换(table → div, ol/ul → 前缀段落, hr → 分隔线)
|
|
147
|
+
5. 默认样式补全
|
|
148
|
+
|
|
149
|
+
## 项目结构
|
|
150
|
+
|
|
151
|
+
```
|
|
152
|
+
ghostwriter/
|
|
153
|
+
├── pyproject.toml # 项目元数据、构建配置
|
|
154
|
+
├── src/ghostwriter/ # 源码包
|
|
155
|
+
│ ├── cli.py # CLI 入口与命令分发
|
|
156
|
+
│ ├── config.py # 配置文件加载
|
|
157
|
+
│ ├── ghost.py # Ghost Admin API 客户端
|
|
158
|
+
│ ├── wechat.py # 微信公众号 API 客户端
|
|
159
|
+
│ ├── cleaner.py # HTML 白名单过滤器
|
|
160
|
+
│ ├── pipeline.py # Ghost → 微信 HTML 处理管道
|
|
161
|
+
│ ├── lexical.py # Markdown → Ghost Lexical 转换器
|
|
162
|
+
│ └── normalize.py # Unicode 标题规范化
|
|
163
|
+
├── tests/ # 单元测试(pytest,74个)
|
|
164
|
+
└── docs/ # 项目页面
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## 注意事项
|
|
168
|
+
|
|
169
|
+
- 微信草稿标题限制:Unicode 特殊字符(弯引号、破折号等)会触发 45003 错误,脚本会自动处理
|
|
170
|
+
- 作者字段在微信公众号中限制 8 字节
|
|
171
|
+
- 代码块使用 `<pre>` + 语言标签的样式方案,微信中可用
|
|
172
|
+
- `--wechat` 参数需要在 Ghost API 中能通过 slug 查到刚发布的文章
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# ghostwriter
|
|
2
|
+
|
|
3
|
+
Markdown → Ghost → WeChat 发布管道。写一次,多处发布。
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
markdown → Ghost 博客 → 微信公众号
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## 功能
|
|
10
|
+
|
|
11
|
+
- **发布**:Markdown 文件直接发布到 Ghost 博客(Lexical 格式,编辑器可编辑)
|
|
12
|
+
- **同步**:已发布的 Ghost 文章同步到微信公众号草稿箱
|
|
13
|
+
- **全自动**:发布后一步同步到微信
|
|
14
|
+
|
|
15
|
+
## 安装
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# 从 PyPI 安装(推荐)
|
|
19
|
+
pip install ghostwriter-cli
|
|
20
|
+
|
|
21
|
+
# 或从源码安装(开发模式)
|
|
22
|
+
git clone https://github.com/yinguobing/ghostwriter.git
|
|
23
|
+
cd ghostwriter
|
|
24
|
+
pip install -e ".[dev]"
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## 配置
|
|
28
|
+
|
|
29
|
+
支持两种配置方式(环境变量优先):
|
|
30
|
+
|
|
31
|
+
### 方式 1:环境变量(推荐用于 CI/Docker)
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
export GHOSTWRITER_GHOST_API_URL="https://yinguobing.com"
|
|
35
|
+
export GHOSTWRITER_GHOST_ADMIN_KEY_ID="your_key_id"
|
|
36
|
+
export GHOSTWRITER_GHOST_ADMIN_KEY="your_hex_secret"
|
|
37
|
+
export GHOSTWRITER_WECHAT_APPID="your_wechat_appid"
|
|
38
|
+
export GHOSTWRITER_WECHAT_SECRET="your_wechat_secret"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### 方式 2:配置文件(适合本地使用)
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
# 交互式设置
|
|
45
|
+
ghostwriter config set ghost.api_url https://yinguobing.com
|
|
46
|
+
ghostwriter config set ghost.admin_key_id your_key_id
|
|
47
|
+
ghostwriter config set ghost.admin_key your_hex_secret
|
|
48
|
+
ghostwriter config set wechat.appid your_wechat_appid
|
|
49
|
+
ghostwriter config set wechat.secret your_wechat_secret
|
|
50
|
+
|
|
51
|
+
# 查看当前配置(密钥已脱敏)
|
|
52
|
+
ghostwriter config
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
配置文件保存在 `~/.config/ghostwriter/config.json`。
|
|
56
|
+
|
|
57
|
+
可选字段:
|
|
58
|
+
```bash
|
|
59
|
+
# 作者映射(Ghost Content API 不可用时的离线回退)
|
|
60
|
+
ghostwriter config set authors.<slug> <ghost_author_id>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**Ghost Admin API Key 获取:**
|
|
64
|
+
Ghost 后台 → Settings → Advanced → Integrations → Add custom integration → 复制 `Admin API Key`(格式为 `key_id:hex_secret`,拆成两段填入)
|
|
65
|
+
|
|
66
|
+
**微信 AppID/Secret 获取:**
|
|
67
|
+
[微信公众平台](https://mp.weixin.qq.com) → 设置与开发 → 基本配置
|
|
68
|
+
|
|
69
|
+
## 用法
|
|
70
|
+
|
|
71
|
+
### 列出 Ghost 文章
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
ghostwriter list
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 发布 Markdown 到 Ghost
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
# 直接发布
|
|
81
|
+
ghostwriter publish article.md
|
|
82
|
+
|
|
83
|
+
# 指定标题和标签
|
|
84
|
+
ghostwriter publish article.md --title "我的文章" --tags Ghost,开源
|
|
85
|
+
|
|
86
|
+
# 指定作者(slug,默认 xiaohei)
|
|
87
|
+
ghostwriter publish article.md --author guobing
|
|
88
|
+
|
|
89
|
+
# 先存草稿
|
|
90
|
+
ghostwriter publish article.md --draft
|
|
91
|
+
|
|
92
|
+
# 发布后自动同步到微信
|
|
93
|
+
ghostwriter publish article.md --wechat
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### 同步 Ghost 文章到微信草稿
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
# 先列出文章获取 ID
|
|
100
|
+
ghostwriter list
|
|
101
|
+
|
|
102
|
+
# 同步指定文章
|
|
103
|
+
ghostwriter <article-id>
|
|
104
|
+
|
|
105
|
+
# 预览 HTML(不创建草稿)
|
|
106
|
+
ghostwriter --preview <article-id>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## 管道说明
|
|
110
|
+
|
|
111
|
+
### Markdown → Ghost(`publish` 命令)
|
|
112
|
+
|
|
113
|
+
将 Markdown 文件转换为 Ghost 的 **Lexical 格式**(基于 `@tryghost/kg-lexical-html-renderer`),支持:
|
|
114
|
+
|
|
115
|
+
- 标题(h1~h6)
|
|
116
|
+
- 段落、粗体、斜体、行内代码、链接
|
|
117
|
+
- 围栏代码块(带语言标记)
|
|
118
|
+
- 表格(以 HTML card 渲染)
|
|
119
|
+
- 有序/无序列表
|
|
120
|
+
- 分割线
|
|
121
|
+
|
|
122
|
+
转换后的文章在 Ghost 编辑器里可以正常编辑。
|
|
123
|
+
|
|
124
|
+
### Ghost → 微信(`sync` 命令)
|
|
125
|
+
|
|
126
|
+
从 Ghost API 获取文章 HTML,经过多层处理管道后推送到微信公众号草稿箱:
|
|
127
|
+
|
|
128
|
+
1. 图片上传到微信永久素材,替换为 CDN 地址
|
|
129
|
+
2. 白名单三层过滤(标签/属性/样式)
|
|
130
|
+
3. 代码块保护与恢复
|
|
131
|
+
4. 微信不支持的标签转换(table → div, ol/ul → 前缀段落, hr → 分隔线)
|
|
132
|
+
5. 默认样式补全
|
|
133
|
+
|
|
134
|
+
## 项目结构
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
ghostwriter/
|
|
138
|
+
├── pyproject.toml # 项目元数据、构建配置
|
|
139
|
+
├── src/ghostwriter/ # 源码包
|
|
140
|
+
│ ├── cli.py # CLI 入口与命令分发
|
|
141
|
+
│ ├── config.py # 配置文件加载
|
|
142
|
+
│ ├── ghost.py # Ghost Admin API 客户端
|
|
143
|
+
│ ├── wechat.py # 微信公众号 API 客户端
|
|
144
|
+
│ ├── cleaner.py # HTML 白名单过滤器
|
|
145
|
+
│ ├── pipeline.py # Ghost → 微信 HTML 处理管道
|
|
146
|
+
│ ├── lexical.py # Markdown → Ghost Lexical 转换器
|
|
147
|
+
│ └── normalize.py # Unicode 标题规范化
|
|
148
|
+
├── tests/ # 单元测试(pytest,74个)
|
|
149
|
+
└── docs/ # 项目页面
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## 注意事项
|
|
153
|
+
|
|
154
|
+
- 微信草稿标题限制:Unicode 特殊字符(弯引号、破折号等)会触发 45003 错误,脚本会自动处理
|
|
155
|
+
- 作者字段在微信公众号中限制 8 字节
|
|
156
|
+
- 代码块使用 `<pre>` + 语言标签的样式方案,微信中可用
|
|
157
|
+
- `--wechat` 参数需要在 Ghost API 中能通过 slug 查到刚发布的文章
|