kmoe-manga-downloader 1.1.2__py3-none-any.whl → 1.2.1__py3-none-any.whl
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.
- kmdr/core/__init__.py +5 -3
- kmdr/core/bases.py +61 -87
- kmdr/core/constants.py +79 -0
- kmdr/core/context.py +40 -0
- kmdr/core/defaults.py +92 -33
- kmdr/core/error.py +10 -2
- kmdr/core/session.py +16 -0
- kmdr/core/structure.py +2 -0
- kmdr/core/utils.py +41 -40
- kmdr/main.py +24 -15
- kmdr/module/__init__.py +5 -5
- kmdr/module/authenticator/CookieAuthenticator.py +14 -7
- kmdr/module/authenticator/LoginAuthenticator.py +37 -23
- kmdr/module/authenticator/__init__.py +2 -0
- kmdr/module/authenticator/utils.py +60 -46
- kmdr/module/configurer/BaseUrlUpdator.py +16 -0
- kmdr/module/configurer/ConfigClearer.py +7 -2
- kmdr/module/configurer/ConfigUnsetter.py +2 -2
- kmdr/module/configurer/OptionLister.py +24 -5
- kmdr/module/configurer/OptionSetter.py +2 -2
- kmdr/module/configurer/__init__.py +5 -0
- kmdr/module/configurer/option_validate.py +14 -12
- kmdr/module/downloader/DirectDownloader.py +18 -6
- kmdr/module/downloader/ReferViaDownloader.py +36 -24
- kmdr/module/downloader/__init__.py +2 -0
- kmdr/module/downloader/download_utils.py +322 -0
- kmdr/module/downloader/misc.py +62 -0
- kmdr/module/lister/BookUrlLister.py +4 -3
- kmdr/module/lister/FollowedBookLister.py +62 -24
- kmdr/module/lister/__init__.py +2 -0
- kmdr/module/lister/utils.py +49 -29
- kmdr/module/picker/ArgsFilterPicker.py +1 -1
- kmdr/module/picker/DefaultVolPicker.py +34 -5
- kmdr/module/picker/__init__.py +2 -0
- {kmoe_manga_downloader-1.1.2.dist-info → kmoe_manga_downloader-1.2.1.dist-info}/METADATA +48 -23
- kmoe_manga_downloader-1.2.1.dist-info/RECORD +43 -0
- kmdr/module/downloader/utils.py +0 -157
- kmoe_manga_downloader-1.1.2.dist-info/RECORD +0 -33
- {kmoe_manga_downloader-1.1.2.dist-info → kmoe_manga_downloader-1.2.1.dist-info}/WHEEL +0 -0
- {kmoe_manga_downloader-1.1.2.dist-info → kmoe_manga_downloader-1.2.1.dist-info}/entry_points.txt +0 -0
- {kmoe_manga_downloader-1.1.2.dist-info → kmoe_manga_downloader-1.2.1.dist-info}/licenses/LICENSE +0 -0
- {kmoe_manga_downloader-1.1.2.dist-info → kmoe_manga_downloader-1.2.1.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kmoe-manga-downloader
|
|
3
|
-
Version: 1.1
|
|
3
|
+
Version: 1.2.1
|
|
4
4
|
Summary: A CLI-downloader for site @kox.moe.
|
|
5
5
|
Author-email: Chris Zheng <chrisis58@outlook.com>
|
|
6
6
|
License: MIT License
|
|
@@ -35,17 +35,20 @@ Classifier: Operating System :: OS Independent
|
|
|
35
35
|
Requires-Python: >=3.9
|
|
36
36
|
Description-Content-Type: text/markdown
|
|
37
37
|
License-File: LICENSE
|
|
38
|
-
Requires-Dist: beautifulsoup4
|
|
39
|
-
Requires-Dist:
|
|
40
|
-
Requires-Dist:
|
|
41
|
-
Requires-Dist:
|
|
38
|
+
Requires-Dist: beautifulsoup4~=4.13.4
|
|
39
|
+
Requires-Dist: aiohttp~=3.12.15
|
|
40
|
+
Requires-Dist: aiofiles~=24.1.0
|
|
41
|
+
Requires-Dist: async-lru~=2.0.5
|
|
42
|
+
Requires-Dist: rich~=13.9.4
|
|
43
|
+
Requires-Dist: yarl~=1.20.1
|
|
44
|
+
Requires-Dist: typing-extensions~=4.15.0
|
|
42
45
|
Dynamic: license-file
|
|
43
46
|
|
|
44
47
|
# Kmoe Manga Downloader
|
|
45
48
|
|
|
46
49
|
[](https://pepy.tech/projects/kmoe-manga-downloader) [](https://pypi.org/project/kmoe-manga-downloader/) [](https://github.com/chrisis58/kmdr/actions/workflows/unit-test.yml) [](https://www.python.org/) [](https://github.com/chrisis58/kmdr/blob/main/LICENSE)
|
|
47
50
|
|
|
48
|
-
`kmdr (Kmoe Manga Downloader)` 是一个 Python
|
|
51
|
+
`kmdr (Kmoe Manga Downloader)` 是一个 Python 终端应用,用于从 [Kmoe](https://kxx.moe/) 网站下载漫画。它支持在终端环境下的登录、下载指定漫画及其卷,并支持回调脚本执行。
|
|
49
52
|
|
|
50
53
|
<p align="center">
|
|
51
54
|
<img src="assets/kmdr-demo.gif" alt="kmdr 使用演示" width="720">
|
|
@@ -53,11 +56,17 @@ Dynamic: license-file
|
|
|
53
56
|
|
|
54
57
|
## ✨功能特性
|
|
55
58
|
|
|
56
|
-
-
|
|
57
|
-
-
|
|
58
|
-
-
|
|
59
|
-
-
|
|
60
|
-
-
|
|
59
|
+
- **现代化终端界面**: 基于 [rich](https://github.com/Textualize/rich) 构建的终端用户界面(TUI),提供进度条和菜单等现代化、美观的交互式终端界面。
|
|
60
|
+
- **凭证和配置管理**: 应用自动维护登录凭证和下载设置,实现一次配置、持久有效,提升使用效率。
|
|
61
|
+
- **高效下载的性能**: 采用 `asyncio` 并发分片下载方式,充分利用网络带宽,显著加速单个大文件的下载速度。
|
|
62
|
+
- **强大的高可用性**: 内置自动重试与断点续传机制,无惧网络中断,确保下载任务在不稳定环境下依然能够成功。
|
|
63
|
+
- **灵活的自动化接口**: 支持在每个文件下载成功后自动执行自定义回调脚本,轻松集成到您的自动化工作流。
|
|
64
|
+
|
|
65
|
+
## 🖼️ 使用场景
|
|
66
|
+
|
|
67
|
+
- **通用的加速体验**: 采用并发分片下载方式,充分地利用不同类型用户的网络带宽,提升数据传输效率,从而有效缩短下载的等待时间。
|
|
68
|
+
- **灵活部署与远程控制**: 支持在远端服务器或 NAS 上运行,可以在其他设备(PC、平板)上浏览,而通过简单的命令触发远程下载任务,实现浏览与存储的分离。
|
|
69
|
+
- **智能化自动追新**: 应用支持识别重复内容,可配合定时任务实等现无人值守下载最新的内容,轻松打造时刻保持同步的个人资料库。
|
|
61
70
|
|
|
62
71
|
## 🛠️安装应用
|
|
63
72
|
|
|
@@ -71,7 +80,7 @@ pip install kmoe-manga-downloader
|
|
|
71
80
|
|
|
72
81
|
### 1. 登录 `kmoe`
|
|
73
82
|
|
|
74
|
-
首先需要登录 `
|
|
83
|
+
首先需要登录 `kmoe` 并保存登录状态(Cookie)。
|
|
75
84
|
|
|
76
85
|
```bash
|
|
77
86
|
kmdr login -u <your_username> -p <your_password>
|
|
@@ -89,26 +98,28 @@ kmdr login -u <your_username>
|
|
|
89
98
|
|
|
90
99
|
```bash
|
|
91
100
|
# 在当前目录下载第一、二、三卷
|
|
92
|
-
kmdr download --dest . --book-url https://
|
|
93
|
-
|
|
101
|
+
kmdr download --dest . --book-url https://kxx.moe/c/50076.htm --volume 1,2,3
|
|
102
|
+
# 下面命令的功能与上面相同
|
|
103
|
+
kmdr download -l https://kxx.moe/c/50076.htm -v 1-3
|
|
94
104
|
```
|
|
95
105
|
|
|
96
106
|
```bash
|
|
97
107
|
# 在目标目录下载全部番外篇
|
|
98
|
-
kmdr download --dest path/to/destination --book-url https://
|
|
99
|
-
|
|
108
|
+
kmdr download --dest path/to/destination --book-url https://kxx.moe/c/50076.htm --vol-type extra -v all
|
|
109
|
+
# 下面命令的功能与上面相同
|
|
110
|
+
kmdr download -d path/to/destination -l https://kxx.moe/c/50076.htm -t extra -v all
|
|
100
111
|
```
|
|
101
112
|
|
|
102
113
|
#### 常用参数说明:
|
|
103
114
|
|
|
104
|
-
- `-d`, `--dest`:
|
|
105
|
-
- `-l`, `--book-url`:
|
|
106
|
-
- `-v`, `--volume`:
|
|
115
|
+
- `-d`, `--dest`: 下载的目标目录(默认为当前目录),在此基础上会额外添加一个为漫画名称的子目录
|
|
116
|
+
- `-l`, `--book-url`: 指定漫画的主页地址
|
|
117
|
+
- `-v`, `--volume`: 指定下载的卷,多个用逗号分隔,例如 `1,2,3` 或 `1-5,8`,`all` 表示全部
|
|
107
118
|
- `-t`, `--vol-type`: 卷类型,`vol`: 单行本(默认);`extra`: 番外;`seri`: 连载话;`all`: 全部
|
|
108
119
|
- `-p`, `--proxy`: 代理服务器地址
|
|
109
|
-
- `-r`, `--retry`:
|
|
120
|
+
- `-r`, `--retry`: 下载失败时的重试次数,默认为 3
|
|
110
121
|
- `-c`, `--callback`: 下载完成后的回调脚本(使用方式详见 [4. 回调函数](https://github.com/chrisis58/kmoe-manga-downlaoder?tab=readme-ov-file#4-%E5%9B%9E%E8%B0%83%E5%87%BD%E6%95%B0))
|
|
111
|
-
- `--num-workers`:
|
|
122
|
+
- `--num-workers`: 最大下载并发数量,默认为 8
|
|
112
123
|
|
|
113
124
|
> 完整的参数说明可以从 `help` 指令中获取。
|
|
114
125
|
|
|
@@ -125,7 +136,7 @@ kmdr status
|
|
|
125
136
|
你可以设置一个回调函数,下载完成后执行。回调可以是任何你想要的命令:
|
|
126
137
|
|
|
127
138
|
```bash
|
|
128
|
-
kmdr download -d path/to/destination --book-url https://
|
|
139
|
+
kmdr download -d path/to/destination --book-url https://kxx.moe/c/50076.htm -v 1-3 \
|
|
129
140
|
--callback "echo '{b.name} {v.name} downloaded!' >> ~/kmdr.log"
|
|
130
141
|
```
|
|
131
142
|
|
|
@@ -141,7 +152,7 @@ kmdr download -d path/to/destination --book-url https://kox.moe/c/50076.htm -v 1
|
|
|
141
152
|
| b.name | 对应漫画的名字 |
|
|
142
153
|
| b.author | 对应漫画的作者 |
|
|
143
154
|
|
|
144
|
-
> 完整的可用参数请参考 [structure.py](https://github.com/chrisis58/
|
|
155
|
+
> 完整的可用参数请参考 [structure.py](https://github.com/chrisis58/kmoe-manga-downloader/blob/main/src/kmdr/core/structure.py#L11) 中关于 `VolInfo` 的定义。
|
|
145
156
|
|
|
146
157
|
### 5. 持久化配置
|
|
147
158
|
|
|
@@ -165,6 +176,20 @@ kmdr config -s num_workers=5 "callback=echo '{b.name} {v.name} downloaded!' >> ~
|
|
|
165
176
|
|
|
166
177
|
> 当前仅支持部分下载参数的持久化:`num_workers`, `dest`, `retry`, `callback`, `proxy`
|
|
167
178
|
|
|
179
|
+
### 6. 镜像源切换
|
|
180
|
+
|
|
181
|
+
为了保证服务的长期可用性,并让用户能根据自己的网络环境选择最快的服务器,应用支持灵活地切换镜像源。
|
|
182
|
+
|
|
183
|
+
当您发现默认源(当前为 `kxx.moe`)访问缓慢或失效时,可以通过 `config` 命令轻松切换到其他备用镜像源:
|
|
184
|
+
|
|
185
|
+
```
|
|
186
|
+
kmdr config --base-url https://mox.moe
|
|
187
|
+
# 或者
|
|
188
|
+
kmdr config -b https://mox.moe
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
你可以参考 [镜像目录](./mirror/mirrors.json) 来选择合适的镜像源,如果你发现部分镜像源过时或者有缺失,欢迎贡献你的内容!
|
|
192
|
+
|
|
168
193
|
## ⚠️ 声明
|
|
169
194
|
|
|
170
195
|
- 本工具仅作学习、研究、交流使用,使用本工具的用户应自行承担风险
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
kmdr/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
kmdr/main.py,sha256=EubhpLIrGHl9XjHXdmhROg3RBKyvHPuPaZPTaSYDsxQ,1355
|
|
3
|
+
kmdr/core/__init__.py,sha256=mTBPcw-mEKfHPV1bgwDx-JoO2G6dQ9QcOnQiEMmi7DE,342
|
|
4
|
+
kmdr/core/bases.py,sha256=muUjcuEm-n51YTHiU7LTwfQm_AwkcNJ5TzIdPZNpK0o,3929
|
|
5
|
+
kmdr/core/constants.py,sha256=_jY56Tpsg7U7Xw4Ht4Q46W4fNu8kyNCEicoTjGgKVUI,1802
|
|
6
|
+
kmdr/core/context.py,sha256=cy4_Iar-dpsWQiLGIJaitNEVw6uwRkzdvF6iYDjLWl0,930
|
|
7
|
+
kmdr/core/defaults.py,sha256=6A3nECLdbb3zVngdFKn_U1GrtcJRMor8Ju4XebCutU8,8459
|
|
8
|
+
kmdr/core/error.py,sha256=1UR16-5E-51hV8Rtg1Ua0Ro7jGfmLpFsTBFkoD6hmto,884
|
|
9
|
+
kmdr/core/registry.py,sha256=KYjvww5WRuUg5SeIeWZb96803-kgrTIKYvFTj_7bPfo,5560
|
|
10
|
+
kmdr/core/session.py,sha256=lFH4lnriwqvw-tlB3MEgRedX7h_LdtaR21Rfo-HAhRs,498
|
|
11
|
+
kmdr/core/structure.py,sha256=EQG1-8kHQ22Not2S7Q_jGxE0nb9XUbLh3JcNTb_nkvk,1199
|
|
12
|
+
kmdr/core/utils.py,sha256=MbJQM5943IEVr4J4j7zdPKuepKh3iJmI5fy6QYnIVr8,2424
|
|
13
|
+
kmdr/module/__init__.py,sha256=hY6lMBpxSn93t7OKnEbXUwTok2XqQoQbuyq1HLeC4Kc,125
|
|
14
|
+
kmdr/module/authenticator/CookieAuthenticator.py,sha256=ur_MtaeNmcrLQYpFplTvVGyd1EocR-_jocPj6K5z1uY,1353
|
|
15
|
+
kmdr/module/authenticator/LoginAuthenticator.py,sha256=8xtCIwR_GkHniJc2JMYwRYLdL4KfQ8d5phEz_NfzMpU,2529
|
|
16
|
+
kmdr/module/authenticator/__init__.py,sha256=iSWhq-suAM1BrABRuTWiCKvZ1fkmf5HQ2GmbZ341GK0,103
|
|
17
|
+
kmdr/module/authenticator/utils.py,sha256=7_oHc-9Ab48T-cTNMWEke6_OI0Z3rg-eb5LyzRzCE9M,3179
|
|
18
|
+
kmdr/module/configurer/BaseUrlUpdator.py,sha256=cTOadDJI38rIVsCb1UvOqODY_UnxUY-1TzR-tMwKcME,501
|
|
19
|
+
kmdr/module/configurer/ConfigClearer.py,sha256=2Ra-EV4fuDilWwoUS8NBSL1__-50D0YD0P6kHurH62c,480
|
|
20
|
+
kmdr/module/configurer/ConfigUnsetter.py,sha256=WVCETKg80vGCDEoaN5XWG7MYDJo-eGit-n53rBk46ZE,597
|
|
21
|
+
kmdr/module/configurer/OptionLister.py,sha256=wHcx8XGjjUL6Nj4K909aqikVK06UE7CXPo6XkZc3q3c,1466
|
|
22
|
+
kmdr/module/configurer/OptionSetter.py,sha256=g3Nid0cG0VFhEguAcHwsOpfOluf-8lKpwZLemrUfP-A,893
|
|
23
|
+
kmdr/module/configurer/__init__.py,sha256=nSWGwUCcwOkvg28zxRd0S3b4jeag_W0IhzP74xq0ewE,204
|
|
24
|
+
kmdr/module/configurer/option_validate.py,sha256=Mn91UmpTI8zVV9Em8rL2mjNV21h0iYlzT8xoWDLEI68,3387
|
|
25
|
+
kmdr/module/downloader/DirectDownloader.py,sha256=IedLaw6cGH-7Rwsvvs_27kqCDbzfHnNT5GP6ERHEo9o,1370
|
|
26
|
+
kmdr/module/downloader/ReferViaDownloader.py,sha256=oM9S8BURmMQL3KPSkb0XJw-SvSIzCiJsWse4XIahKCY,1887
|
|
27
|
+
kmdr/module/downloader/__init__.py,sha256=PajPBQ4ZJwz_6Ok6k-HV7-YSibxAW5m5voYyP-U-_f4,97
|
|
28
|
+
kmdr/module/downloader/download_utils.py,sha256=CXpsMujgAxCQbokJ9RiQdKFozr_AgbhkJ9n-V_JWr4U,11882
|
|
29
|
+
kmdr/module/downloader/misc.py,sha256=-gKmqqENQ2ukTVou9cDan0K2MDfwX43W8LyTw3o-oVM,1816
|
|
30
|
+
kmdr/module/lister/BookUrlLister.py,sha256=R1sak_GQG8HKfdSzHF-Dy1iby6TgFlxKJLf2dwBr0Bw,550
|
|
31
|
+
kmdr/module/lister/FollowedBookLister.py,sha256=WeMm7c81hbLr5TkrgQiBx8pQ1odOxWXn_xvQ9LxYZYs,2933
|
|
32
|
+
kmdr/module/lister/__init__.py,sha256=VVRMRGXASdIagzlmOuhy9gKjJzf8Rj3000BPzeWpHHE,91
|
|
33
|
+
kmdr/module/lister/utils.py,sha256=iUA0zu8JzKUouT3rwM_fr8knlHSPqApqeGSYOFI2D7I,3575
|
|
34
|
+
kmdr/module/picker/ArgsFilterPicker.py,sha256=wBCDa6KsSSOLLQk8D4ftZ9ZnwPLiHIirxf6gGBq3I08,1782
|
|
35
|
+
kmdr/module/picker/DefaultVolPicker.py,sha256=XD5mo48bwpa6T69AIxWJVvrX1daPsxyG8AFsJ4RLTBc,1760
|
|
36
|
+
kmdr/module/picker/__init__.py,sha256=qYELEkv0nFT8DadItB6fdUTED2CHrC43wuPKO7QrCCQ,93
|
|
37
|
+
kmdr/module/picker/utils.py,sha256=lpxM7q9BJeupFQy8glBrHu1o4E38dk7iLexzKytAE6g,1222
|
|
38
|
+
kmoe_manga_downloader-1.2.1.dist-info/licenses/LICENSE,sha256=bKQlsXu8mAYKRZyoZKOEqMcCc8YjT5Q3Hgr21e0yU4E,1068
|
|
39
|
+
kmoe_manga_downloader-1.2.1.dist-info/METADATA,sha256=0YGgNLfT2bFWscpO-N3QWsDbu19hMEYcXlb5yFbo3Wc,9693
|
|
40
|
+
kmoe_manga_downloader-1.2.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
41
|
+
kmoe_manga_downloader-1.2.1.dist-info/entry_points.txt,sha256=DGMytQAhx4uNuKQL7BPkiWESHLXkH-2KSEqwHdygNPA,47
|
|
42
|
+
kmoe_manga_downloader-1.2.1.dist-info/top_level.txt,sha256=e0qxOgWp0tl3GLpmXGjZv3--q_TLoJ7GztM48Ov27wM,5
|
|
43
|
+
kmoe_manga_downloader-1.2.1.dist-info/RECORD,,
|
kmdr/module/downloader/utils.py
DELETED
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
from typing import Callable, Optional, Union
|
|
2
|
-
import os
|
|
3
|
-
import time
|
|
4
|
-
import threading
|
|
5
|
-
from functools import wraps
|
|
6
|
-
|
|
7
|
-
from requests import Session, HTTPError
|
|
8
|
-
from requests.exceptions import ChunkedEncodingError
|
|
9
|
-
from tqdm import tqdm
|
|
10
|
-
import re
|
|
11
|
-
|
|
12
|
-
BLOCK_SIZE_REDUCTION_FACTOR = 0.75
|
|
13
|
-
MIN_BLOCK_SIZE = 2048
|
|
14
|
-
|
|
15
|
-
def download_file(
|
|
16
|
-
session: Session,
|
|
17
|
-
url: Union[str, Callable[[], str]],
|
|
18
|
-
dest_path: str,
|
|
19
|
-
filename: str,
|
|
20
|
-
retry_times: int = 3,
|
|
21
|
-
headers: Optional[dict] = None,
|
|
22
|
-
callback: Optional[Callable] = None,
|
|
23
|
-
):
|
|
24
|
-
"""
|
|
25
|
-
下载文件
|
|
26
|
-
|
|
27
|
-
:param session: requests.Session 对象
|
|
28
|
-
:param url: 下载链接或者其 Supplier
|
|
29
|
-
:param dest_path: 目标路径
|
|
30
|
-
:param filename: 文件名
|
|
31
|
-
:param retry_times: 重试次数
|
|
32
|
-
:param headers: 请求头
|
|
33
|
-
:param callback: 下载完成后的回调函数
|
|
34
|
-
"""
|
|
35
|
-
if headers is None:
|
|
36
|
-
headers = {}
|
|
37
|
-
filename_downloading = f'{filename}.downloading'
|
|
38
|
-
file_path = os.path.join(dest_path, filename)
|
|
39
|
-
tmp_file_path = os.path.join(dest_path, filename_downloading)
|
|
40
|
-
|
|
41
|
-
if not os.path.exists(dest_path):
|
|
42
|
-
os.makedirs(dest_path, exist_ok=True)
|
|
43
|
-
|
|
44
|
-
if os.path.exists(file_path):
|
|
45
|
-
tqdm.write(f"{filename} 已经存在")
|
|
46
|
-
return
|
|
47
|
-
|
|
48
|
-
block_size = 8192
|
|
49
|
-
|
|
50
|
-
attempts_left = retry_times + 1
|
|
51
|
-
progress_bar = tqdm(
|
|
52
|
-
total=0, unit='B', unit_scale=True,
|
|
53
|
-
desc=f'{filename} (连接中...)',
|
|
54
|
-
leave=False,
|
|
55
|
-
dynamic_ncols=True
|
|
56
|
-
)
|
|
57
|
-
|
|
58
|
-
try:
|
|
59
|
-
while attempts_left > 0:
|
|
60
|
-
attempts_left -= 1
|
|
61
|
-
|
|
62
|
-
resume_from = os.path.getsize(tmp_file_path) if os.path.exists(tmp_file_path) else 0
|
|
63
|
-
|
|
64
|
-
if resume_from:
|
|
65
|
-
headers['Range'] = f'bytes={resume_from}-'
|
|
66
|
-
|
|
67
|
-
try:
|
|
68
|
-
current_url = url() if callable(url) else url
|
|
69
|
-
with session.get(url=current_url, stream=True, headers=headers) as r:
|
|
70
|
-
r.raise_for_status()
|
|
71
|
-
|
|
72
|
-
total_size_in_bytes = int(r.headers.get('content-length', 0)) + resume_from
|
|
73
|
-
|
|
74
|
-
progress_bar.set_description(f'{filename}')
|
|
75
|
-
progress_bar.total = total_size_in_bytes
|
|
76
|
-
progress_bar.n = resume_from
|
|
77
|
-
progress_bar.refresh()
|
|
78
|
-
|
|
79
|
-
with open(tmp_file_path, 'ab') as f:
|
|
80
|
-
for chunk in r.iter_content(chunk_size=block_size):
|
|
81
|
-
if chunk:
|
|
82
|
-
f.write(chunk)
|
|
83
|
-
progress_bar.update(len(chunk))
|
|
84
|
-
|
|
85
|
-
if os.path.getsize(tmp_file_path) >= total_size_in_bytes:
|
|
86
|
-
os.rename(tmp_file_path, file_path)
|
|
87
|
-
if callback:
|
|
88
|
-
callback()
|
|
89
|
-
return
|
|
90
|
-
|
|
91
|
-
except Exception as e:
|
|
92
|
-
if attempts_left > 0:
|
|
93
|
-
progress_bar.set_description(f'{filename} (重试中...)')
|
|
94
|
-
if isinstance(e, ChunkedEncodingError):
|
|
95
|
-
new_block_size = max(int(block_size * BLOCK_SIZE_REDUCTION_FACTOR), MIN_BLOCK_SIZE)
|
|
96
|
-
if new_block_size < block_size:
|
|
97
|
-
block_size = new_block_size
|
|
98
|
-
|
|
99
|
-
# 避免限流
|
|
100
|
-
time.sleep(3)
|
|
101
|
-
else:
|
|
102
|
-
raise e
|
|
103
|
-
finally:
|
|
104
|
-
if progress_bar.total and progress_bar.n >= progress_bar.total:
|
|
105
|
-
tqdm.write(f"{filename} 下载完成")
|
|
106
|
-
elif progress_bar.total is not None:
|
|
107
|
-
tqdm.write(f"{filename} 下载失败")
|
|
108
|
-
progress_bar.close()
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
def safe_filename(name: str) -> str:
|
|
112
|
-
"""
|
|
113
|
-
替换非法文件名字符为下划线
|
|
114
|
-
"""
|
|
115
|
-
return re.sub(r'[\\/:*?"<>|]', '_', name)
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
function_cache = {}
|
|
119
|
-
|
|
120
|
-
def cached_by_kwargs(func):
|
|
121
|
-
"""
|
|
122
|
-
根据关键字参数缓存函数结果的装饰器。
|
|
123
|
-
|
|
124
|
-
Example:
|
|
125
|
-
>>> @kwargs_cached
|
|
126
|
-
>>> def add(a, b, c):
|
|
127
|
-
>>> return a + b + c
|
|
128
|
-
>>> result1 = add(1, 2, c=3) # Calls the function
|
|
129
|
-
>>> result2 = add(3, 2, c=3) # Uses cached result
|
|
130
|
-
>>> assert result1 == result2 # Both results are the same
|
|
131
|
-
"""
|
|
132
|
-
|
|
133
|
-
global function_cache
|
|
134
|
-
if func not in function_cache:
|
|
135
|
-
function_cache[func] = {}
|
|
136
|
-
|
|
137
|
-
@wraps(func)
|
|
138
|
-
def wrapper(*args, **kwargs):
|
|
139
|
-
if not kwargs:
|
|
140
|
-
return func(*args, **kwargs)
|
|
141
|
-
|
|
142
|
-
key = frozenset(kwargs.items())
|
|
143
|
-
|
|
144
|
-
if key not in function_cache[func]:
|
|
145
|
-
function_cache[func][key] = func(*args, **kwargs)
|
|
146
|
-
return function_cache[func][key]
|
|
147
|
-
|
|
148
|
-
return wrapper
|
|
149
|
-
|
|
150
|
-
def clear_cache(func):
|
|
151
|
-
assert hasattr(func, "__wrapped__"), "Function is not wrapped"
|
|
152
|
-
global function_cache
|
|
153
|
-
|
|
154
|
-
wrapped = func.__wrapped__
|
|
155
|
-
|
|
156
|
-
if wrapped in function_cache:
|
|
157
|
-
function_cache[wrapped] = {}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
kmdr/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
kmdr/main.py,sha256=F_zXjRr2QIBdNrqvCqpSNkFSxVY6ySfm-mk-Gg9qy7g,1018
|
|
3
|
-
kmdr/core/__init__.py,sha256=WT4MHsww1KOcIlf3z_AK5-4mXNFNBoCE1REQ0dvf9DE,281
|
|
4
|
-
kmdr/core/bases.py,sha256=uDLamjuRfwcVhbGI99aSUZBi-fN32OM2fl91t0bbVog,4484
|
|
5
|
-
kmdr/core/defaults.py,sha256=9vVN0xywLOHm3tcNf1GGJ9RTXB38YLTvBMqxmp0jEAE,6582
|
|
6
|
-
kmdr/core/error.py,sha256=ZXsiD-Ihe0zo5wdNgd7Rg-ISZifsiU5C6FeHesurfy4,597
|
|
7
|
-
kmdr/core/registry.py,sha256=KYjvww5WRuUg5SeIeWZb96803-kgrTIKYvFTj_7bPfo,5560
|
|
8
|
-
kmdr/core/structure.py,sha256=JHTuEHuRJLx1dEgg59WDJCZKORvwPJWuAb6nT1cSFNA,1163
|
|
9
|
-
kmdr/core/utils.py,sha256=eBoj8O37kIQSN6S72EPpv83YRQlplBZ1FzEvehOLEKU,1953
|
|
10
|
-
kmdr/module/__init__.py,sha256=aSuTcHhuBHUi1xNj5HTsXxhvegGBTRPNKj4JAPLC7LE,319
|
|
11
|
-
kmdr/module/authenticator/CookieAuthenticator.py,sha256=5LzqtpTCfKufcjbzFeJIAUbUaTxo2W4mN1ki8CDel2Y,1002
|
|
12
|
-
kmdr/module/authenticator/LoginAuthenticator.py,sha256=PnLGpgSZCA6GvtYFAKeCIRvjtojwIeF4it50QdTXuu8,1827
|
|
13
|
-
kmdr/module/authenticator/utils.py,sha256=fgheXC2--ZQ53zap6gF4lA88CzJRN4ljgKmOs3IbiJU,2415
|
|
14
|
-
kmdr/module/configurer/ConfigClearer.py,sha256=S5VxJ5latDK2-0Xf77_fVECXah7DhbI0exWE6AwUUkc,355
|
|
15
|
-
kmdr/module/configurer/ConfigUnsetter.py,sha256=ZjySNAQj4-j9k68_tbXa2Z9G0TqGgfKPRCtIf939Hfg,531
|
|
16
|
-
kmdr/module/configurer/OptionLister.py,sha256=qc-nWPft_EtbEgJnz32h3nwGyGbe9oabKZpm6dIJi_o,516
|
|
17
|
-
kmdr/module/configurer/OptionSetter.py,sha256=9MIkWZb-aFUTqpVCVrnri3JZTE0zO3CIYuN_nz5yzzY,842
|
|
18
|
-
kmdr/module/configurer/option_validate.py,sha256=DdtUG0E-LgjjHQgPBSyRZ_ID40QRRFSS_FEQHdK94hM,3327
|
|
19
|
-
kmdr/module/downloader/DirectDownloader.py,sha256=5ny3or9fj2rb3qGxEWBmiEs5vlIfvCSGFiMoFISs_J8,1060
|
|
20
|
-
kmdr/module/downloader/ReferViaDownloader.py,sha256=_x-hniGmIqc_7OFY-kQUrOIH0TwDcf-_NCsmVql2pp0,1694
|
|
21
|
-
kmdr/module/downloader/utils.py,sha256=qGp_c0coVENDKPNUpA7uZoHoDxBIj5jQHlw8qf4NRpQ,4814
|
|
22
|
-
kmdr/module/lister/BookUrlLister.py,sha256=Mq7EBXWSKd-6cygfkTjmOvgcUUaJI4NMQiaEIv9VDSk,470
|
|
23
|
-
kmdr/module/lister/FollowedBookLister.py,sha256=_fShmCAsZQqboeuRtLBjo6d9CkGpat8xNKC3COtKllc,1537
|
|
24
|
-
kmdr/module/lister/utils.py,sha256=0EHQIA05EZ1V0_SfkJ8demjzMRmFh9Q5qPvE6jvBqSU,2560
|
|
25
|
-
kmdr/module/picker/ArgsFilterPicker.py,sha256=f3suMPPeFEtB3u7aUY63k_sGIOP196R-VviQ9RfDBTA,1756
|
|
26
|
-
kmdr/module/picker/DefaultVolPicker.py,sha256=kpG5dvv1UKMhSA01VKGNB59zM5uszspyUVfRlL9aQA0,750
|
|
27
|
-
kmdr/module/picker/utils.py,sha256=lpxM7q9BJeupFQy8glBrHu1o4E38dk7iLexzKytAE6g,1222
|
|
28
|
-
kmoe_manga_downloader-1.1.2.dist-info/licenses/LICENSE,sha256=bKQlsXu8mAYKRZyoZKOEqMcCc8YjT5Q3Hgr21e0yU4E,1068
|
|
29
|
-
kmoe_manga_downloader-1.1.2.dist-info/METADATA,sha256=vPMrYaRtOC0rTenqPS2QWj5Inq1X1gYlMbSZ2HANAPQ,7869
|
|
30
|
-
kmoe_manga_downloader-1.1.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
31
|
-
kmoe_manga_downloader-1.1.2.dist-info/entry_points.txt,sha256=DGMytQAhx4uNuKQL7BPkiWESHLXkH-2KSEqwHdygNPA,47
|
|
32
|
-
kmoe_manga_downloader-1.1.2.dist-info/top_level.txt,sha256=e0qxOgWp0tl3GLpmXGjZv3--q_TLoJ7GztM48Ov27wM,5
|
|
33
|
-
kmoe_manga_downloader-1.1.2.dist-info/RECORD,,
|
|
File without changes
|
{kmoe_manga_downloader-1.1.2.dist-info → kmoe_manga_downloader-1.2.1.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{kmoe_manga_downloader-1.1.2.dist-info → kmoe_manga_downloader-1.2.1.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
{kmoe_manga_downloader-1.1.2.dist-info → kmoe_manga_downloader-1.2.1.dist-info}/top_level.txt
RENAMED
|
File without changes
|