|
| 1 | +# Feature 机制——下载附加行为 |
| 2 | + |
| 3 | +## 1. 需求场景 |
| 4 | + |
| 5 | +下载本子后,很多用户有进一步导出的需求: |
| 6 | +- 导出为 **PDF**:方便在电子阅读器上查看 |
| 7 | +- 导出为 **ZIP**:方便传输和存档 |
| 8 | +- 合并为 **长图**:方便一张图看完整个章节 |
| 9 | + |
| 10 | +jmcomic 一直通过内置插件(`img2pdf`、`zip`、`long_img`)支持这些功能,但传统方式需要在 YAML 配置文件中编写插件配置,门槛偏高。 |
| 11 | + |
| 12 | +从最新版本起,jmcomic 引入了 **Feature(特性)** 机制——一套通用的**下载附加行为系统**,让你用一行代码搞定导出。Feature 不仅能调用插件,还能封装任意自定义逻辑(通知、清理等),并且会根据调用方式自动选择最合理的配置。 |
| 13 | + |
| 14 | +内置了三个开箱即用的导出 Feature: |
| 15 | + |
| 16 | +| Feature | 效果 | |
| 17 | +|---------|------| |
| 18 | +| `Feature.export_pdf` | 下载完自动导出为 PDF | |
| 19 | +| `Feature.export_zip` | 下载完自动打包为 ZIP | |
| 20 | +| `Feature.export_long_img` | 下载完自动拼接为长图 PNG | |
| 21 | + |
| 22 | +## 2. 快速上手 |
| 23 | + |
| 24 | +### 2.1 导出 PDF——基本用法示例 |
| 25 | + |
| 26 | +```python |
| 27 | +from jmcomic import download_album, Feature |
| 28 | + |
| 29 | +# 只需要加一个 extra 参数,就能在下载完成后自动导出 PDF |
| 30 | +download_album('123', option, extra=Feature.export_pdf) |
| 31 | +``` |
| 32 | + |
| 33 | +效果:在**当前工作目录**下生成以本子标题命名的 PDF 文件: |
| 34 | + |
| 35 | +``` |
| 36 | +./ |
| 37 | +├── [本子标题].pdf ← 整本合并为 1 个 PDF |
| 38 | +``` |
| 39 | + |
| 40 | +### 2.2 需要多种导出格式(PDF、ZIP)——直接组合 Feature |
| 41 | + |
| 42 | +用 `+` 号组合,同时导出多种格式: |
| 43 | + |
| 44 | +```python |
| 45 | +# 下载完后同时导出 PDF 和 ZIP |
| 46 | +download_album('123', option, extra=Feature.export_pdf + Feature.export_zip) |
| 47 | + |
| 48 | +# 也支持列表语法 |
| 49 | +download_album('123', option, extra=[Feature.export_pdf, Feature.export_zip]) |
| 50 | +``` |
| 51 | + |
| 52 | +### 2.3 自定义参数 |
| 53 | + |
| 54 | +像调用函数一样传入自定义参数,可以改变输出目录、命名规则等: |
| 55 | + |
| 56 | +```python |
| 57 | +# 示例 1:指定输出目录和命名规则 |
| 58 | +download_album('123', option, extra=Feature.export_pdf( |
| 59 | + pdf_dir='D:/my_pdfs', # PDF 保存到 D:/my_pdfs 文件夹 |
| 60 | + filename_rule='Ptitle', # 用章节标题作为文件名 |
| 61 | + delete_original_file=True, # 合并完 PDF 后删除原图 |
| 62 | +)) |
| 63 | + |
| 64 | +# 示例 2:全都要——ZIP 存盘 + 长图阅读 |
| 65 | +combo = ( |
| 66 | + Feature.export_zip(zip_dir='D:/zips') |
| 67 | + + Feature.export_long_img(img_dir='D:/long_imgs') |
| 68 | +) |
| 69 | +download_album('123', option, extra=combo) |
| 70 | +``` |
| 71 | + |
| 72 | +### 2.4 download_photo 也支持 |
| 73 | + |
| 74 | +```python |
| 75 | +from jmcomic import download_photo, Feature |
| 76 | + |
| 77 | +# 对单个章节导出 |
| 78 | +download_photo('456', option, extra=Feature.export_pdf) |
| 79 | +``` |
| 80 | + |
| 81 | +效果:在当前工作目录下生成以章节标题命名的 PDF: |
| 82 | + |
| 83 | +``` |
| 84 | +./ |
| 85 | +├── [章节标题].pdf ← 该章节导出为 1 个 PDF |
| 86 | +``` |
| 87 | + |
| 88 | +> 💡 **提示**:同一个 Feature,通过 `download_album` 和 `download_photo` 调用时会自动适配不同的导出行为,详见下方 [智能适配规则](#智能适配规则)。 |
| 89 | +
|
| 90 | +### 2.5 智能适配规则 |
| 91 | + |
| 92 | +内置的导出 Feature 会根据调用的 API **自动适配**参数(命名规则、打包级别等): |
| 93 | + |
| 94 | +| 调用方式 | Feature.export_pdf | Feature.export_zip | Feature.export_long_img | |
| 95 | +|---------|-------------------|-------------------|----------------------| |
| 96 | +| `download_album` | 整本合并为 1 个 PDF<br>`[本子标题].pdf` | 整本打包为 1 个 ZIP<br>`[本子标题].zip` | 所有章节合并为 1 张长图<br>`[本子ID].png` | |
| 97 | +| `download_photo` | 该章节导出为 PDF<br>`[章节标题].pdf` | 该章节打包为 ZIP<br>`[章节标题].zip` | 该章节拼接为长图<br>`[章节ID].png` | |
| 98 | + |
| 99 | +当你显式传入参数时(如 `filename_rule='Ptitle'`),**你的配置优先**,不会被自适应覆盖。 |
| 100 | + |
| 101 | +> 💡 **提示**:更多可选参数(如加密密码 `encrypt`、后缀名 `suffix` 等),参考 [Plugin 插件参数大全](./6_plugin.md#参数)。 |
| 102 | +
|
| 103 | +## 3. 传统写法(YAML 插件配置) |
| 104 | + |
| 105 | +如果你更习惯配置文件,仍然可以使用传统的插件配置方式: |
| 106 | + |
| 107 | +```yaml |
| 108 | +# option.yml |
| 109 | +plugins: |
| 110 | + after_album: |
| 111 | + - plugin: img2pdf |
| 112 | + kwargs: |
| 113 | + pdf_dir: ./output |
| 114 | + filename_rule: Atitle |
| 115 | + - plugin: zip |
| 116 | + kwargs: |
| 117 | + level: album |
| 118 | + zip_dir: ./output |
| 119 | +``` |
| 120 | +
|
| 121 | +传统写法的更多细节见 → [Plugin 插件教程](./6_plugin.md) |
| 122 | +
|
| 123 | +## 4. Feature 架构设计 |
| 124 | +
|
| 125 | +### 类层次 |
| 126 | +
|
| 127 | +``` |
| 128 | +Feature (基类) |
| 129 | + ├── PluginFeature ← 封装插件调用,参数根据来源自适应 |
| 130 | + └── 你的自定义 Feature ← 继承 Feature,实现任意逻辑 |
| 131 | +``` |
| 132 | + |
| 133 | +- **Feature 基类**:通用的附加行为抽象,不绑定任何具体实现。默认在所有生命周期钩子中执行。 |
| 134 | +- **PluginFeature**:Feature 的子类,专门封装 jmcomic 插件。除了调用插件之外,还会根据调用来源动态适配 `filename_rule`、`level` 等参数。 |
| 135 | + |
| 136 | +### 执行流程 |
| 137 | + |
| 138 | +Feature **自然嵌入到 downloader 的生命周期钩子**中自动触发: |
| 139 | + |
| 140 | +``` |
| 141 | +api.download_album(extra=Feature.export_pdf) |
| 142 | + │ |
| 143 | + ├→ dler.add_features(pdf, 'download_album') # 注册: [(pdf, 'download_album')] |
| 144 | + │ |
| 145 | + └→ dler.download_album(id) |
| 146 | + │ |
| 147 | + ├→ before_album(album) |
| 148 | + │ |
| 149 | + ├→ download_by_photo_detail(photo) |
| 150 | + │ ├→ before_photo(photo) |
| 151 | + │ ├→ download images ... |
| 152 | + │ └→ after_photo(photo) |
| 153 | + │ └→ _invoke_features_for('after_photo') |
| 154 | + │ └→ pdf.should_invoke('after_photo', 'download_album') → False ✗ 跳过 |
| 155 | + │ |
| 156 | + └→ after_album(album) |
| 157 | + └→ _invoke_features_for('after_album') |
| 158 | + └→ pdf.should_invoke('after_album', 'download_album') → True ✓ 执行! |
| 159 | + └→ _adapt_kwargs('download_album') |
| 160 | + # Atitle 不变, Ptitle→Atitle, Pid→Aid, level→album |
| 161 | +``` |
| 162 | + |
| 163 | +> 💡 **关键点**: |
| 164 | +> |
| 165 | +> - **执行时机**:`PluginFeature` 根据注册来源自动推导(`download_album` → `after_album`,`download_photo` → `after_photo`)。自定义 Feature 默认在所有钩子都会执行,你可以覆写 `should_invoke` 来控制。 |
| 166 | +> - **参数自适应**:`PluginFeature` 的 `filename_rule` 前缀(A/P)和 `level`(album/photo)会根据来源动态适配。用户显式传入的参数不会被覆盖。 |
| 167 | +
|
| 168 | +### 自定义 Feature |
| 169 | + |
| 170 | +Feature 基类完全不绑定插件,你可以实现任意逻辑: |
| 171 | + |
| 172 | +```python |
| 173 | +from jmcomic import Feature, download_album |
| 174 | + |
| 175 | +class NotifyFeature(Feature): |
| 176 | + """下载完成后发送通知""" |
| 177 | + def invoke(self, option, **context): |
| 178 | + album = context.get('album') |
| 179 | + if album: |
| 180 | + print(f'下载完成通知: {album.name}') |
| 181 | + |
| 182 | +# 使用 |
| 183 | +download_album('123', option, extra=NotifyFeature()) |
| 184 | +``` |
| 185 | + |
| 186 | +### 自定义 PluginFeature |
| 187 | + |
| 188 | +如果你注册了自定义插件,也可以创建对应的 PluginFeature: |
| 189 | + |
| 190 | +```python |
| 191 | +from jmcomic import PluginFeature, Feature |
| 192 | + |
| 193 | +# 假设你注册了一个 plugin_key 为 'my_export' 的插件 |
| 194 | +Feature.my_export = PluginFeature('my_export', output_dir='./my_output') |
| 195 | + |
| 196 | +# 使用 |
| 197 | +download_album('123', option, extra=Feature.my_export) |
| 198 | +``` |
0 commit comments