komix 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.
@@ -0,0 +1,29 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*.*.*"
7
+
8
+ jobs:
9
+ build-and-publish:
10
+ runs-on: ubuntu-latest
11
+ permissions:
12
+ id-token: write
13
+
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+
17
+ - name: Set up Python
18
+ uses: actions/setup-python@v5
19
+ with:
20
+ python-version: "3.12"
21
+
22
+ - name: Install build tools
23
+ run: pip install build
24
+
25
+ - name: Build wheel and sdist
26
+ run: python -m build
27
+
28
+ - name: Publish to PyPI
29
+ uses: pypa/gh-action-pypi-publish@release/v1
komix-0.1.0/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
@@ -0,0 +1 @@
1
+ 3.12
komix-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,282 @@
1
+ Metadata-Version: 2.4
2
+ Name: komix
3
+ Version: 0.1.0
4
+ Summary: Lightweight Comic Book Manager
5
+ License-Expression: MIT
6
+ Keywords: ACGN,comic,fastapi,pluggy
7
+ Classifier: Development Status :: 3 - Alpha
8
+ Classifier: Framework :: FastAPI
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python :: 3
12
+ Requires-Python: >=3.12
13
+ Requires-Dist: aiofiles>=25.1.0
14
+ Requires-Dist: aiohttp>=3.14.1
15
+ Requires-Dist: fastapi>=0.136.3
16
+ Requires-Dist: pluggy>=1.6.0
17
+ Requires-Dist: typer>=0.26.7
18
+ Description-Content-Type: text/markdown
19
+
20
+ # KoMiX — Lightweight Comic Book Manager
21
+
22
+ KoMiX 是一个轻量级本地漫画库管理工具,面向 ACGN 爱好者。它提供 Web UI 浏览本地漫画目录,并通过**插件系统**从网络数据源自动获取漫画元数据和封面图。
23
+
24
+ ## 特性
25
+
26
+ - **目录即漫画** — 将一个目录下的每个子目录视为一本漫画,自动发现并展示为海报墙
27
+ - **元数据刮削** — 通过插件自动搜索并下载漫画信息(标题、作者、简介、标签)和封面图
28
+ - **ComicInfo.xml** — 元数据以标准 `ComicInfo.xml` 格式写入漫画目录,兼容其他漫画管理工具
29
+ - **插件驱动** — 基于 `pluggy` 的插件系统,数据源完全由外部插件提供,可任意扩展
30
+ - **单文件 Web UI** — 纯 HTML/JS 前端,无框架依赖,内置封面海报网格、全选/批量刮削等交互
31
+ - **快速启动** — Typer CLI + FastAPI 后端,一条命令启动服务
32
+
33
+ ## 快速开始
34
+
35
+ ### 安装
36
+
37
+ ```bash
38
+ pip install komix
39
+ ```
40
+
41
+ ### 启动服务
42
+
43
+ ```bash
44
+ komix serve # 默认 http://127.0.0.1:7788
45
+ komix serve --host 0.0.0.0 --port 8080
46
+ ```
47
+
48
+ ### 使用
49
+
50
+ 1. 浏览器打开 `http://127.0.0.1:7788`
51
+ 2. 输入漫画库根目录路径(例如 `/mnt/comics`)
52
+ 3. 选择需要刮削的漫画目录
53
+ 4. 点击「刮削所选项目」按钮,等待插件获取元数据和封面
54
+
55
+ > 首次使用需要安装至少一个数据源插件,否则刮削不会产生任何效果。参见下方插件开发指南。
56
+
57
+ ---
58
+
59
+ ## 技术架构
60
+
61
+ | 模块 | 文件 | 职责 |
62
+ |------|------|------|
63
+ | 数据模型 | `comicinfo.py` | `ComicInfo` Pydantic 数据类 |
64
+ | 插件契约 | `hookspecs.py` | 定义 3 个插件 Hook 接口 |
65
+ | 插件管理 | `api.py` | 插件加载、元数据读写、封面下载 |
66
+ | Web API | `routes.py` | FastAPI 路由:目录列表、元数据查询、刮削触发 |
67
+ | 命令行 | `main.py` | Typer CLI,封装 FastAPI 应用并通过 uvicorn 启动 |
68
+ | Web UI | `index.html` | 单页应用,原生 JS 实现海报网格 |
69
+
70
+ ### 技术栈
71
+
72
+ - **Python 3.12+**
73
+ - **FastAPI** — Web 框架
74
+ - **Typer** — 命令行接口
75
+ - **pluggy** — 插件系统
76
+ - **Pydantic** — 数据校验与序列化
77
+ - **aiohttp / aiofiles** — 异步 HTTP 和文件 I/O
78
+ - **uvicorn** — ASGI 服务器
79
+ - **hatchling** — 构建系统
80
+
81
+ ### API 端点
82
+
83
+ | 方法 | 路径 | 说明 |
84
+ |------|------|------|
85
+ | `GET` | `/` | 返回 Web UI 页面 |
86
+ | `GET` | `/api/comics?rootdir=<path>` | 列出根目录下所有子目录 |
87
+ | `GET` | `/api/comicinfo?dirpath=<path>` | 读取目录中的 `ComicInfo.xml` |
88
+ | `GET` | `/api/cover?dirpath=<path>` | 返回目录中的 `cover.jpg` |
89
+ | `PUT` | `/api/scrape?dirpath=<path>` | 对指定目录触发插件刮削流程 |
90
+
91
+ ---
92
+
93
+ ## 插件开发指南
94
+
95
+ KoMiX 使用 `pluggy` 作为插件框架,插件以 Python 包形式发布,通过 setuptools entry point 注册。
96
+
97
+ ### 1. 插件接口说明
98
+
99
+ 插件需要实现以下 3 个 Hook 函数:
100
+
101
+ ```python
102
+ def search(query: str) -> list[ComicInfo]:
103
+ """根据搜索关键词(通常是目录名)搜索漫画,返回匹配列表。"""
104
+
105
+ def fetch_comicinfo(comic_id: str) -> ComicInfo:
106
+ """根据漫画 ID 获取完整元数据。"""
107
+
108
+ def fetch_cover_url(comic_id: str) -> str:
109
+ """根据漫画 ID 获取封面图片 URL。"""
110
+ ```
111
+
112
+ 其中 `ComicInfo` 的数据结构为:
113
+
114
+ ```python
115
+ @dataclass
116
+ class ComicInfo:
117
+ title: str = ""
118
+ id: str = ""
119
+ author: str = ""
120
+ summary: str = ""
121
+ tags: list[str] = field(default_factory=list)
122
+ ```
123
+
124
+ ### 2. 快速开始:编写一个插件
125
+
126
+ 创建项目结构:
127
+
128
+ ```
129
+ komix-mysource/
130
+ ├── pyproject.toml
131
+ └── src/
132
+ └── komix_mysource/
133
+ ├── __init__.py
134
+ └── plugin.py
135
+ ```
136
+
137
+ **pyproject.toml**:
138
+
139
+ ```toml
140
+ [build-system]
141
+ requires = ["hatchling"]
142
+ build-backend = "hatchling.build"
143
+
144
+ [project]
145
+ name = "komix-mysource"
146
+ version = "0.1.0"
147
+ requires-python = ">=3.12"
148
+ dependencies = [
149
+ "komix",
150
+ "aiohttp",
151
+ ]
152
+
153
+ [project.entry-points.komix]
154
+ mysource = "komix_mysource.plugin:MySource"
155
+
156
+ [tool.hatch.build.targets.wheel]
157
+ packages = ["src/komix_mysource"]
158
+ ```
159
+
160
+ **src/komix_mysource/plugin.py**:
161
+
162
+ ```python
163
+ import aiohttp
164
+ from komix import hookimpl
165
+ from komix.comicinfo import ComicInfo
166
+
167
+
168
+ class MySource:
169
+ @hookimpl
170
+ def search(self, query: str):
171
+ # 实现搜索逻辑,返回 ComicInfo 列表
172
+ return [ComicInfo(
173
+ title=query,
174
+ id="12345",
175
+ author="作者名",
176
+ summary="简介文本",
177
+ tags=["标签1", "标签2"],
178
+ )]
179
+
180
+ @hookimpl
181
+ def fetch_comicinfo(self, comic_id: str):
182
+ # 根据 ID 获取完整元数据
183
+ return ComicInfo(
184
+ title="漫画标题",
185
+ id=comic_id,
186
+ author="作者名",
187
+ summary="详细简介...",
188
+ tags=["标签1", "标签2"],
189
+ )
190
+
191
+ @hookimpl
192
+ def fetch_cover_url(self, comic_id: str):
193
+ # 返回封面图片的 URL
194
+ return f"https://example.com/covers/{comic_id}.jpg"
195
+ ```
196
+
197
+ ### 3. 安装插件
198
+
199
+ ```bash
200
+ pip install komix-mysource
201
+ ```
202
+
203
+ 安装后启动 `komix serve`,插件即被自动发现并生效。
204
+
205
+ ### 4. 插件执行流程
206
+
207
+ 当用户点击「刮削所选项目」时,KoMiX 对每个选中的目录执行如下流程:
208
+
209
+ 1. 以目录名作为 `query` 调用所有插件的 `search()`,汇总搜索结果
210
+ 2. 取第一个搜索结果,以 `comic_id` 调用 `fetch_comicinfo()` 获取元数据
211
+ 3. 以 `comic_id` 调用 `fetch_cover_url()` 获取封面 URL
212
+ 4. 将元数据写入 `ComicInfo.xml`,将封面下载为 `cover.jpg`,均存放于该目录下
213
+
214
+ ### 5. 开发调试
215
+
216
+ 插件开发期间推荐使用 `uv` 管理环境并安装本地插件:
217
+
218
+ ```bash
219
+ cd komix-mysource
220
+ uv sync # 安装依赖
221
+ uv pip install -e . # 可编辑安装
222
+ komix serve # 启动服务,插件自动被加载
223
+ ```
224
+
225
+ ### 6. 更复杂的例子:异步 HTTP 调用
226
+
227
+ 如果你的插件需要调用远程 API,推荐使用 `aiohttp` 保持异步(但请注意:`pluggy` 的 hook 调用是同步的,如果你的实现本身是同步的,可以直接用 `requests` 等同步库):
228
+
229
+ ```python
230
+ import aiohttp
231
+ from komix import hookimpl
232
+ from komix.comicinfo import ComicInfo
233
+
234
+ class MySource:
235
+ API_BASE = "https://api.example.com/v1"
236
+
237
+ @hookimpl
238
+ def search(self, query: str):
239
+ # 注意:hook 是同步调用的,但你可以内部使用 asyncio
240
+ import asyncio
241
+ return asyncio.run(self._async_search(query))
242
+
243
+ async def _async_search(self, query: str):
244
+ async with aiohttp.ClientSession() as session:
245
+ async with session.get(f"{self.API_BASE}/search", params={"q": query}) as resp:
246
+ data = await resp.json()
247
+ return [
248
+ ComicInfo(
249
+ id=item["id"],
250
+ title=item["title"],
251
+ author=item.get("author", ""),
252
+ summary=item.get("description", ""),
253
+ tags=item.get("genres", []),
254
+ )
255
+ for item in data["results"]
256
+ ]
257
+ ```
258
+
259
+ ### 7. 发布插件到 PyPI
260
+
261
+ 1. 在 `pyproject.toml` 中完善项目元信息
262
+ 2. 使用 `hatch build` 构建发行版
263
+ 3. 使用 `twine upload dist/*` 发布到 PyPI
264
+
265
+ 发布后用户通过 `pip install komix-mysource` 即可使用。
266
+
267
+ ---
268
+
269
+ ## 开发
270
+
271
+ ```bash
272
+ git clone https://github.com/user/komix.git
273
+ cd komix
274
+ uv sync # 安装依赖(含开发依赖)
275
+ uv run komix serve # 启动开发服务器
276
+ ruff check # 代码检查
277
+ ruff format # 代码格式化
278
+ ```
279
+
280
+ ## License
281
+
282
+ MIT
komix-0.1.0/README.md ADDED
@@ -0,0 +1,263 @@
1
+ # KoMiX — Lightweight Comic Book Manager
2
+
3
+ KoMiX 是一个轻量级本地漫画库管理工具,面向 ACGN 爱好者。它提供 Web UI 浏览本地漫画目录,并通过**插件系统**从网络数据源自动获取漫画元数据和封面图。
4
+
5
+ ## 特性
6
+
7
+ - **目录即漫画** — 将一个目录下的每个子目录视为一本漫画,自动发现并展示为海报墙
8
+ - **元数据刮削** — 通过插件自动搜索并下载漫画信息(标题、作者、简介、标签)和封面图
9
+ - **ComicInfo.xml** — 元数据以标准 `ComicInfo.xml` 格式写入漫画目录,兼容其他漫画管理工具
10
+ - **插件驱动** — 基于 `pluggy` 的插件系统,数据源完全由外部插件提供,可任意扩展
11
+ - **单文件 Web UI** — 纯 HTML/JS 前端,无框架依赖,内置封面海报网格、全选/批量刮削等交互
12
+ - **快速启动** — Typer CLI + FastAPI 后端,一条命令启动服务
13
+
14
+ ## 快速开始
15
+
16
+ ### 安装
17
+
18
+ ```bash
19
+ pip install komix
20
+ ```
21
+
22
+ ### 启动服务
23
+
24
+ ```bash
25
+ komix serve # 默认 http://127.0.0.1:7788
26
+ komix serve --host 0.0.0.0 --port 8080
27
+ ```
28
+
29
+ ### 使用
30
+
31
+ 1. 浏览器打开 `http://127.0.0.1:7788`
32
+ 2. 输入漫画库根目录路径(例如 `/mnt/comics`)
33
+ 3. 选择需要刮削的漫画目录
34
+ 4. 点击「刮削所选项目」按钮,等待插件获取元数据和封面
35
+
36
+ > 首次使用需要安装至少一个数据源插件,否则刮削不会产生任何效果。参见下方插件开发指南。
37
+
38
+ ---
39
+
40
+ ## 技术架构
41
+
42
+ | 模块 | 文件 | 职责 |
43
+ |------|------|------|
44
+ | 数据模型 | `comicinfo.py` | `ComicInfo` Pydantic 数据类 |
45
+ | 插件契约 | `hookspecs.py` | 定义 3 个插件 Hook 接口 |
46
+ | 插件管理 | `api.py` | 插件加载、元数据读写、封面下载 |
47
+ | Web API | `routes.py` | FastAPI 路由:目录列表、元数据查询、刮削触发 |
48
+ | 命令行 | `main.py` | Typer CLI,封装 FastAPI 应用并通过 uvicorn 启动 |
49
+ | Web UI | `index.html` | 单页应用,原生 JS 实现海报网格 |
50
+
51
+ ### 技术栈
52
+
53
+ - **Python 3.12+**
54
+ - **FastAPI** — Web 框架
55
+ - **Typer** — 命令行接口
56
+ - **pluggy** — 插件系统
57
+ - **Pydantic** — 数据校验与序列化
58
+ - **aiohttp / aiofiles** — 异步 HTTP 和文件 I/O
59
+ - **uvicorn** — ASGI 服务器
60
+ - **hatchling** — 构建系统
61
+
62
+ ### API 端点
63
+
64
+ | 方法 | 路径 | 说明 |
65
+ |------|------|------|
66
+ | `GET` | `/` | 返回 Web UI 页面 |
67
+ | `GET` | `/api/comics?rootdir=<path>` | 列出根目录下所有子目录 |
68
+ | `GET` | `/api/comicinfo?dirpath=<path>` | 读取目录中的 `ComicInfo.xml` |
69
+ | `GET` | `/api/cover?dirpath=<path>` | 返回目录中的 `cover.jpg` |
70
+ | `PUT` | `/api/scrape?dirpath=<path>` | 对指定目录触发插件刮削流程 |
71
+
72
+ ---
73
+
74
+ ## 插件开发指南
75
+
76
+ KoMiX 使用 `pluggy` 作为插件框架,插件以 Python 包形式发布,通过 setuptools entry point 注册。
77
+
78
+ ### 1. 插件接口说明
79
+
80
+ 插件需要实现以下 3 个 Hook 函数:
81
+
82
+ ```python
83
+ def search(query: str) -> list[ComicInfo]:
84
+ """根据搜索关键词(通常是目录名)搜索漫画,返回匹配列表。"""
85
+
86
+ def fetch_comicinfo(comic_id: str) -> ComicInfo:
87
+ """根据漫画 ID 获取完整元数据。"""
88
+
89
+ def fetch_cover_url(comic_id: str) -> str:
90
+ """根据漫画 ID 获取封面图片 URL。"""
91
+ ```
92
+
93
+ 其中 `ComicInfo` 的数据结构为:
94
+
95
+ ```python
96
+ @dataclass
97
+ class ComicInfo:
98
+ title: str = ""
99
+ id: str = ""
100
+ author: str = ""
101
+ summary: str = ""
102
+ tags: list[str] = field(default_factory=list)
103
+ ```
104
+
105
+ ### 2. 快速开始:编写一个插件
106
+
107
+ 创建项目结构:
108
+
109
+ ```
110
+ komix-mysource/
111
+ ├── pyproject.toml
112
+ └── src/
113
+ └── komix_mysource/
114
+ ├── __init__.py
115
+ └── plugin.py
116
+ ```
117
+
118
+ **pyproject.toml**:
119
+
120
+ ```toml
121
+ [build-system]
122
+ requires = ["hatchling"]
123
+ build-backend = "hatchling.build"
124
+
125
+ [project]
126
+ name = "komix-mysource"
127
+ version = "0.1.0"
128
+ requires-python = ">=3.12"
129
+ dependencies = [
130
+ "komix",
131
+ "aiohttp",
132
+ ]
133
+
134
+ [project.entry-points.komix]
135
+ mysource = "komix_mysource.plugin:MySource"
136
+
137
+ [tool.hatch.build.targets.wheel]
138
+ packages = ["src/komix_mysource"]
139
+ ```
140
+
141
+ **src/komix_mysource/plugin.py**:
142
+
143
+ ```python
144
+ import aiohttp
145
+ from komix import hookimpl
146
+ from komix.comicinfo import ComicInfo
147
+
148
+
149
+ class MySource:
150
+ @hookimpl
151
+ def search(self, query: str):
152
+ # 实现搜索逻辑,返回 ComicInfo 列表
153
+ return [ComicInfo(
154
+ title=query,
155
+ id="12345",
156
+ author="作者名",
157
+ summary="简介文本",
158
+ tags=["标签1", "标签2"],
159
+ )]
160
+
161
+ @hookimpl
162
+ def fetch_comicinfo(self, comic_id: str):
163
+ # 根据 ID 获取完整元数据
164
+ return ComicInfo(
165
+ title="漫画标题",
166
+ id=comic_id,
167
+ author="作者名",
168
+ summary="详细简介...",
169
+ tags=["标签1", "标签2"],
170
+ )
171
+
172
+ @hookimpl
173
+ def fetch_cover_url(self, comic_id: str):
174
+ # 返回封面图片的 URL
175
+ return f"https://example.com/covers/{comic_id}.jpg"
176
+ ```
177
+
178
+ ### 3. 安装插件
179
+
180
+ ```bash
181
+ pip install komix-mysource
182
+ ```
183
+
184
+ 安装后启动 `komix serve`,插件即被自动发现并生效。
185
+
186
+ ### 4. 插件执行流程
187
+
188
+ 当用户点击「刮削所选项目」时,KoMiX 对每个选中的目录执行如下流程:
189
+
190
+ 1. 以目录名作为 `query` 调用所有插件的 `search()`,汇总搜索结果
191
+ 2. 取第一个搜索结果,以 `comic_id` 调用 `fetch_comicinfo()` 获取元数据
192
+ 3. 以 `comic_id` 调用 `fetch_cover_url()` 获取封面 URL
193
+ 4. 将元数据写入 `ComicInfo.xml`,将封面下载为 `cover.jpg`,均存放于该目录下
194
+
195
+ ### 5. 开发调试
196
+
197
+ 插件开发期间推荐使用 `uv` 管理环境并安装本地插件:
198
+
199
+ ```bash
200
+ cd komix-mysource
201
+ uv sync # 安装依赖
202
+ uv pip install -e . # 可编辑安装
203
+ komix serve # 启动服务,插件自动被加载
204
+ ```
205
+
206
+ ### 6. 更复杂的例子:异步 HTTP 调用
207
+
208
+ 如果你的插件需要调用远程 API,推荐使用 `aiohttp` 保持异步(但请注意:`pluggy` 的 hook 调用是同步的,如果你的实现本身是同步的,可以直接用 `requests` 等同步库):
209
+
210
+ ```python
211
+ import aiohttp
212
+ from komix import hookimpl
213
+ from komix.comicinfo import ComicInfo
214
+
215
+ class MySource:
216
+ API_BASE = "https://api.example.com/v1"
217
+
218
+ @hookimpl
219
+ def search(self, query: str):
220
+ # 注意:hook 是同步调用的,但你可以内部使用 asyncio
221
+ import asyncio
222
+ return asyncio.run(self._async_search(query))
223
+
224
+ async def _async_search(self, query: str):
225
+ async with aiohttp.ClientSession() as session:
226
+ async with session.get(f"{self.API_BASE}/search", params={"q": query}) as resp:
227
+ data = await resp.json()
228
+ return [
229
+ ComicInfo(
230
+ id=item["id"],
231
+ title=item["title"],
232
+ author=item.get("author", ""),
233
+ summary=item.get("description", ""),
234
+ tags=item.get("genres", []),
235
+ )
236
+ for item in data["results"]
237
+ ]
238
+ ```
239
+
240
+ ### 7. 发布插件到 PyPI
241
+
242
+ 1. 在 `pyproject.toml` 中完善项目元信息
243
+ 2. 使用 `hatch build` 构建发行版
244
+ 3. 使用 `twine upload dist/*` 发布到 PyPI
245
+
246
+ 发布后用户通过 `pip install komix-mysource` 即可使用。
247
+
248
+ ---
249
+
250
+ ## 开发
251
+
252
+ ```bash
253
+ git clone https://github.com/user/komix.git
254
+ cd komix
255
+ uv sync # 安装依赖(含开发依赖)
256
+ uv run komix serve # 启动开发服务器
257
+ ruff check # 代码检查
258
+ ruff format # 代码格式化
259
+ ```
260
+
261
+ ## License
262
+
263
+ MIT
@@ -0,0 +1,38 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "komix"
7
+ version = "0.1.0"
8
+ description = "Lightweight Comic Book Manager"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ classifiers = [
12
+ "Programming Language :: Python :: 3",
13
+ "License :: OSI Approved :: MIT License",
14
+ "Operating System :: OS Independent",
15
+ "Framework :: FastAPI",
16
+ "Development Status :: 3 - Alpha"
17
+ ]
18
+ keywords = ["pluggy", "fastapi", "ACGN", "comic"]
19
+ requires-python = ">=3.12"
20
+ dependencies = [
21
+ "aiofiles>=25.1.0",
22
+ "aiohttp>=3.14.1",
23
+ "fastapi>=0.136.3",
24
+ "pluggy>=1.6.0",
25
+ "typer>=0.26.7",
26
+ ]
27
+
28
+ [project.scripts]
29
+ komix = "komix.main:app"
30
+
31
+ [dependency-groups]
32
+ dev = [
33
+ "fastapi[standard]>=0.136.3",
34
+ "ruff>=0.15.17",
35
+ ]
36
+
37
+ [tool.hatch.build.targets.wheel]
38
+ packages = ["src/komix"]
@@ -0,0 +1,12 @@
1
+ from importlib.metadata import PackageNotFoundError, version
2
+
3
+ import pluggy
4
+
5
+ from . import comicinfo
6
+
7
+ try:
8
+ __version__ = version("komix")
9
+ except PackageNotFoundError:
10
+ __version__ = "0.1.0"
11
+
12
+ hookimpl = pluggy.HookimplMarker("komix")
@@ -0,0 +1,57 @@
1
+ import asyncio
2
+ import xml.etree.ElementTree as ET
3
+ from pathlib import Path
4
+
5
+ import aiofiles
6
+ import aiohttp
7
+ import pluggy
8
+
9
+ from . import hookspecs
10
+ from .comicinfo import ComicInfo
11
+
12
+ pm = pluggy.PluginManager("komix")
13
+ pm.add_hookspecs(hookspecs)
14
+ pm.load_setuptools_entrypoints("komix")
15
+
16
+
17
+ async def read_comicinfo(xmlpath: Path) -> ComicInfo:
18
+ async with aiofiles.open(xmlpath, "r") as f:
19
+ xml = await f.read()
20
+ root = ET.fromstring(xml)
21
+ return ComicInfo(
22
+ title=root.findtext("Title", default=""),
23
+ id=root.findtext("ID", default=""),
24
+ author=root.findtext("Author", default=""),
25
+ summary=root.findtext("Summary", default=""),
26
+ tags=root.findtext("Tags", default="").split(", "),
27
+ )
28
+
29
+
30
+ async def _write_comicinfo(comicinfo: ComicInfo, xmlpath: Path):
31
+ root = ET.Element("ComicInfo")
32
+ ET.SubElement(root, "Title").text = comicinfo.title
33
+ ET.SubElement(root, "ID").text = comicinfo.id
34
+ ET.SubElement(root, "Author").text = comicinfo.author
35
+ ET.SubElement(root, "Summary").text = comicinfo.summary
36
+ ET.SubElement(root, "Tags").text = ", ".join(comicinfo.tags)
37
+ async with aiofiles.open(xmlpath, "w") as f:
38
+ await f.write(ET.tostring(root, encoding="utf-8"))
39
+
40
+
41
+ async def _download_cover(url: str, jpgpath: Path):
42
+ async with aiofiles.open(jpgpath, "wb") as f:
43
+ async with aiohttp.ClientSession() as session:
44
+ async with session.get(url) as response:
45
+ await f.write(await response.read())
46
+
47
+
48
+ async def scrape_by_dirname(dirpath: Path):
49
+ comics = pm.hook.search(query=dirpath.name)
50
+ if not comics:
51
+ return
52
+
53
+ comic0 = comics[0]
54
+ comicinfo = pm.hook.fetch_comicinfo(comic_id=comic0.id)
55
+ cover_url = pm.hook.fetch_cover_url(comic_id=comic0.id)
56
+ await _write_comicinfo(comicinfo, dirpath / "ComicInfo.xml")
57
+ await _download_cover(cover_url, dirpath / "cover.jpg")