kmoe-manga-downloader 1.2.1__py3-none-any.whl → 1.2.3b0__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/__init__.py +4 -0
- kmdr/_version.py +34 -0
- kmdr/core/__init__.py +6 -4
- kmdr/core/bases.py +24 -19
- kmdr/core/console.py +67 -0
- kmdr/core/constants.py +17 -22
- kmdr/core/context.py +18 -3
- kmdr/core/defaults.py +22 -7
- kmdr/core/error.py +23 -1
- kmdr/core/protocol.py +10 -0
- kmdr/core/session.py +111 -8
- kmdr/core/utils.py +51 -2
- kmdr/main.py +31 -10
- kmdr/module/authenticator/CookieAuthenticator.py +4 -8
- kmdr/module/authenticator/LoginAuthenticator.py +8 -21
- kmdr/module/authenticator/utils.py +16 -19
- kmdr/module/configurer/BaseUrlUpdator.py +3 -2
- kmdr/module/configurer/ConfigClearer.py +3 -2
- kmdr/module/configurer/ConfigUnsetter.py +3 -2
- kmdr/module/configurer/OptionLister.py +3 -2
- kmdr/module/configurer/OptionSetter.py +3 -2
- kmdr/module/configurer/option_validate.py +11 -11
- kmdr/module/downloader/DirectDownloader.py +10 -13
- kmdr/module/downloader/ReferViaDownloader.py +11 -12
- kmdr/module/downloader/download_utils.py +53 -7
- kmdr/module/downloader/misc.py +2 -0
- kmdr/module/lister/FollowedBookLister.py +4 -4
- kmdr/module/lister/utils.py +10 -8
- kmdr/module/picker/DefaultVolPicker.py +2 -1
- kmdr/module/picker/utils.py +14 -6
- {kmoe_manga_downloader-1.2.1.dist-info → kmoe_manga_downloader-1.2.3b0.dist-info}/METADATA +1 -1
- kmoe_manga_downloader-1.2.3b0.dist-info/RECORD +46 -0
- kmoe_manga_downloader-1.2.1.dist-info/RECORD +0 -43
- {kmoe_manga_downloader-1.2.1.dist-info → kmoe_manga_downloader-1.2.3b0.dist-info}/WHEEL +0 -0
- {kmoe_manga_downloader-1.2.1.dist-info → kmoe_manga_downloader-1.2.3b0.dist-info}/entry_points.txt +0 -0
- {kmoe_manga_downloader-1.2.1.dist-info → kmoe_manga_downloader-1.2.3b0.dist-info}/licenses/LICENSE +0 -0
- {kmoe_manga_downloader-1.2.1.dist-info → kmoe_manga_downloader-1.2.3b0.dist-info}/top_level.txt +0 -0
|
@@ -12,13 +12,15 @@ import aiofiles.os as aio_os
|
|
|
12
12
|
from rich.progress import Progress
|
|
13
13
|
from aiohttp.client_exceptions import ClientPayloadError
|
|
14
14
|
|
|
15
|
+
from kmdr.core.console import info, log, debug
|
|
16
|
+
|
|
15
17
|
from .misc import STATUS, StateManager
|
|
16
18
|
|
|
17
19
|
BLOCK_SIZE_REDUCTION_FACTOR = 0.75
|
|
18
20
|
MIN_BLOCK_SIZE = 2048
|
|
19
21
|
|
|
20
22
|
|
|
21
|
-
@deprecated("
|
|
23
|
+
@deprecated("本函数可能不会积极维护,请改用 'download_file_multipart'")
|
|
22
24
|
async def download_file(
|
|
23
25
|
session: aiohttp.ClientSession,
|
|
24
26
|
semaphore: asyncio.Semaphore,
|
|
@@ -53,9 +55,11 @@ async def download_file(
|
|
|
53
55
|
await aio_os.makedirs(dest_path, exist_ok=True)
|
|
54
56
|
|
|
55
57
|
if await aio_os.path.exists(file_path):
|
|
56
|
-
|
|
58
|
+
info(f"[yellow]{filename} 已经存在[/yellow]")
|
|
57
59
|
return
|
|
58
60
|
|
|
61
|
+
log("开始下载文件:", filename, "到路径:", dest_path)
|
|
62
|
+
|
|
59
63
|
block_size = 8192
|
|
60
64
|
attempts_left = retry_times + 1
|
|
61
65
|
task_id = None
|
|
@@ -105,7 +109,7 @@ async def download_file(
|
|
|
105
109
|
else:
|
|
106
110
|
raise IOError(f"Failed to download {filename} after {retry_times} retries.")
|
|
107
111
|
|
|
108
|
-
|
|
112
|
+
await aio_os.rename(filename_downloading, file_path)
|
|
109
113
|
|
|
110
114
|
except Exception as e:
|
|
111
115
|
if task_id is not None:
|
|
@@ -154,16 +158,23 @@ async def download_file_multipart(
|
|
|
154
158
|
await aio_os.makedirs(dest_path, exist_ok=True)
|
|
155
159
|
|
|
156
160
|
if await aio_os.path.exists(file_path):
|
|
157
|
-
|
|
161
|
+
info(f"[blue]{filename} 已经存在[/blue]")
|
|
158
162
|
return
|
|
159
163
|
|
|
164
|
+
log(f"开始下载文件: {filename} 到路径: {dest_path}")
|
|
165
|
+
|
|
160
166
|
part_paths = []
|
|
161
167
|
part_expected_sizes = []
|
|
162
168
|
task_id = None
|
|
169
|
+
|
|
170
|
+
state_manager: Optional[StateManager] = None
|
|
163
171
|
try:
|
|
164
172
|
current_url = await fetch_url(url)
|
|
165
173
|
|
|
166
174
|
async with session.head(current_url, headers=headers, allow_redirects=True) as response:
|
|
175
|
+
# 注意:这个请求完成后,服务器就会记录这次下载,并消耗对应的流量配额,详细的规则请参考网站说明:
|
|
176
|
+
# 注 1 : 訂閱連載中的漫畫,有更新時自動推送的卷(冊),暫不計算在使用額度中,不扣減使用額度。
|
|
177
|
+
# 注 2 : 對同一卷(冊)書在 12 小時內重複*下載*,不會重複扣減額度。但重復推送是會扣減的。
|
|
167
178
|
response.raise_for_status()
|
|
168
179
|
total_size = int(response.headers['Content-Length'])
|
|
169
180
|
|
|
@@ -206,14 +217,14 @@ async def download_file_multipart(
|
|
|
206
217
|
if all(results):
|
|
207
218
|
await state_manager.request_status_update(part_id=StateManager.PARENT_ID, status=STATUS.MERGING)
|
|
208
219
|
await _merge_parts(part_paths, filename_downloading)
|
|
209
|
-
|
|
220
|
+
await aio_os.rename(filename_downloading, file_path)
|
|
210
221
|
else:
|
|
211
222
|
# 如果有任何一个分片校验失败,则视为下载失败
|
|
212
223
|
await state_manager.request_status_update(part_id=StateManager.PARENT_ID, status=STATUS.FAILED)
|
|
213
224
|
|
|
214
225
|
finally:
|
|
215
226
|
if await aio_os.path.exists(file_path):
|
|
216
|
-
if task_id is not None:
|
|
227
|
+
if task_id is not None and state_manager is not None:
|
|
217
228
|
await state_manager.request_status_update(part_id=StateManager.PARENT_ID, status=STATUS.COMPLETED)
|
|
218
229
|
|
|
219
230
|
cleanup_tasks = [aio_os.remove(p) for p in part_paths if await aio_os.path.exists(p)]
|
|
@@ -222,7 +233,7 @@ async def download_file_multipart(
|
|
|
222
233
|
if callback:
|
|
223
234
|
callback()
|
|
224
235
|
else:
|
|
225
|
-
if task_id is not None:
|
|
236
|
+
if task_id is not None and state_manager is not None:
|
|
226
237
|
await state_manager.request_status_update(part_id=StateManager.PARENT_ID, status=STATUS.FAILED)
|
|
227
238
|
|
|
228
239
|
async def _download_part(
|
|
@@ -256,6 +267,7 @@ async def _download_part(
|
|
|
256
267
|
local_headers['Range'] = f'bytes={current_start}-{end}'
|
|
257
268
|
|
|
258
269
|
async with semaphore:
|
|
270
|
+
debug("开始下载分片:", os.path.basename(part_path), "范围:", current_start, "-", end)
|
|
259
271
|
async with session.get(url, headers=local_headers) as response:
|
|
260
272
|
response.raise_for_status()
|
|
261
273
|
|
|
@@ -266,13 +278,22 @@ async def _download_part(
|
|
|
266
278
|
if chunk:
|
|
267
279
|
await f.write(chunk)
|
|
268
280
|
state_manager.advance(len(chunk))
|
|
281
|
+
log("分片", os.path.basename(part_path), "下载完成。")
|
|
269
282
|
return
|
|
283
|
+
|
|
284
|
+
except asyncio.CancelledError:
|
|
285
|
+
# 如果任务被取消,更新状态为已取消
|
|
286
|
+
await state_manager.request_status_update(part_id=start, status=STATUS.CANCELLED)
|
|
287
|
+
raise
|
|
288
|
+
|
|
270
289
|
except Exception as e:
|
|
271
290
|
if attempts_left > 0:
|
|
291
|
+
debug("分片", os.path.basename(part_path), "下载出错:", e, ",正在重试... 剩余重试次数:", attempts_left)
|
|
272
292
|
await asyncio.sleep(3)
|
|
273
293
|
await state_manager.request_status_update(part_id=start, status=STATUS.WAITING)
|
|
274
294
|
else:
|
|
275
295
|
# console.print(f"[red]分片 {os.path.basename(part_path)} 下载失败: {e}[/red]")
|
|
296
|
+
debug("分片", os.path.basename(part_path), "下载失败:", e)
|
|
276
297
|
await state_manager.request_status_update(part_id=start, status=STATUS.PARTIALLY_FAILED)
|
|
277
298
|
|
|
278
299
|
async def _validate_part(part_path: str, expected_size: int) -> bool:
|
|
@@ -282,6 +303,7 @@ async def _validate_part(part_path: str, expected_size: int) -> bool:
|
|
|
282
303
|
return actual_size == expected_size
|
|
283
304
|
|
|
284
305
|
async def _merge_parts(part_paths: list[str], final_path: str):
|
|
306
|
+
debug("合并分片到最终文件:", final_path)
|
|
285
307
|
async with aiofiles.open(final_path, 'wb') as final_file:
|
|
286
308
|
try:
|
|
287
309
|
for part_path in part_paths:
|
|
@@ -297,7 +319,31 @@ async def _merge_parts(part_paths: list[str], final_path: str):
|
|
|
297
319
|
raise e
|
|
298
320
|
|
|
299
321
|
|
|
322
|
+
CHAR_MAPPING = {
|
|
323
|
+
'\\': '\',
|
|
324
|
+
'/': '/',
|
|
325
|
+
':': ':',
|
|
326
|
+
'*': '*',
|
|
327
|
+
'?': '?',
|
|
328
|
+
'"': '"',
|
|
329
|
+
'<': '<',
|
|
330
|
+
'>': '>',
|
|
331
|
+
'|': '|',
|
|
332
|
+
}
|
|
333
|
+
DEFAULT_ILLEGAL_CHARS_REPLACEMENT = '_'
|
|
334
|
+
ILLEGAL_CHARS_RE = re.compile(r'[\\/:*?"<>|]')
|
|
335
|
+
|
|
336
|
+
def readable_safe_filename(name: str) -> str:
|
|
337
|
+
"""
|
|
338
|
+
将字符串转换为安全的文件名,替换掉非法字符。
|
|
339
|
+
"""
|
|
340
|
+
def replace_char(match):
|
|
341
|
+
char = match.group(0)
|
|
342
|
+
return CHAR_MAPPING.get(char, DEFAULT_ILLEGAL_CHARS_REPLACEMENT)
|
|
343
|
+
|
|
344
|
+
return ILLEGAL_CHARS_RE.sub(replace_char, name).strip()
|
|
300
345
|
|
|
346
|
+
@deprecated("请使用 'readable_safe_filename'")
|
|
301
347
|
def safe_filename(name: str) -> str:
|
|
302
348
|
"""
|
|
303
349
|
替换非法文件名字符为下划线
|
kmdr/module/downloader/misc.py
CHANGED
|
@@ -12,6 +12,7 @@ class STATUS(Enum):
|
|
|
12
12
|
COMPLETED='[green]完成[/green]'
|
|
13
13
|
PARTIALLY_FAILED='[red]分片失败[/red]'
|
|
14
14
|
FAILED='[red]失败[/red]'
|
|
15
|
+
CANCELLED='[yellow]已取消[/yellow]'
|
|
15
16
|
|
|
16
17
|
@property
|
|
17
18
|
def order(self) -> int:
|
|
@@ -23,6 +24,7 @@ class STATUS(Enum):
|
|
|
23
24
|
STATUS.COMPLETED: 5,
|
|
24
25
|
STATUS.PARTIALLY_FAILED: 6,
|
|
25
26
|
STATUS.FAILED: 7,
|
|
27
|
+
STATUS.CANCELLED: 8,
|
|
26
28
|
}
|
|
27
29
|
return order_mapping[self]
|
|
28
30
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
from urllib.parse import urljoin
|
|
3
2
|
|
|
4
3
|
from bs4 import BeautifulSoup
|
|
5
4
|
from rich.table import Table
|
|
@@ -8,6 +7,7 @@ from rich.prompt import IntPrompt
|
|
|
8
7
|
from kmdr.core import Lister, LISTERS, BookInfo, VolInfo
|
|
9
8
|
from kmdr.core.utils import async_retry
|
|
10
9
|
from kmdr.core.constants import API_ROUTE
|
|
10
|
+
from kmdr.core.console import info
|
|
11
11
|
|
|
12
12
|
from .utils import extract_book_info_and_volumes
|
|
13
13
|
|
|
@@ -24,7 +24,7 @@ class FollowedBookLister(Lister):
|
|
|
24
24
|
books = await self._list_followed_books()
|
|
25
25
|
|
|
26
26
|
if not books:
|
|
27
|
-
|
|
27
|
+
info("[yellow]关注列表为空。[/yellow]")
|
|
28
28
|
exit(0)
|
|
29
29
|
|
|
30
30
|
table = Table(title="关注的书籍列表", show_header=True, header_style="bold blue")
|
|
@@ -43,7 +43,7 @@ class FollowedBookLister(Lister):
|
|
|
43
43
|
book.status
|
|
44
44
|
)
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
info(table)
|
|
47
47
|
|
|
48
48
|
valid_choices = [str(i) for i in range(1, len(books) + 1)]
|
|
49
49
|
|
|
@@ -63,7 +63,7 @@ class FollowedBookLister(Lister):
|
|
|
63
63
|
|
|
64
64
|
@async_retry()
|
|
65
65
|
async def _list_followed_books(self) -> 'list[BookInfo]':
|
|
66
|
-
async with self._session.get(
|
|
66
|
+
async with self._session.get(API_ROUTE.MY_FOLLOW) as response:
|
|
67
67
|
response.raise_for_status()
|
|
68
68
|
html_text = await response.text()
|
|
69
69
|
|
kmdr/module/lister/utils.py
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
from bs4 import BeautifulSoup
|
|
2
2
|
import re
|
|
3
3
|
from typing import Optional
|
|
4
|
-
from urllib.parse import urljoin
|
|
5
4
|
|
|
6
5
|
from yarl import URL
|
|
7
6
|
from aiohttp import ClientSession as Session
|
|
8
7
|
|
|
9
8
|
from kmdr.core import BookInfo, VolInfo, VolumeType
|
|
10
9
|
from kmdr.core.utils import async_retry
|
|
10
|
+
from kmdr.core.console import debug
|
|
11
11
|
|
|
12
12
|
@async_retry()
|
|
13
13
|
async def extract_book_info_and_volumes(session: Session, url: str, book_info: Optional[BookInfo] = None) -> tuple[BookInfo, list[VolInfo]]:
|
|
@@ -20,19 +20,21 @@ async def extract_book_info_and_volumes(session: Session, url: str, book_info: O
|
|
|
20
20
|
"""
|
|
21
21
|
structured_url = URL(url)
|
|
22
22
|
|
|
23
|
+
# 移除移动端路径部分,统一为桌面端路径
|
|
24
|
+
# 因为移动端页面的结构与桌面端不同,可能会影响解析
|
|
25
|
+
route = structured_url.path
|
|
23
26
|
if structured_url.path.startswith('/m/'):
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
structured_url = structured_url.with_path(structured_url.path.replace('/m/', '', 1))
|
|
27
|
+
debug("检测到移动端链接,转换为桌面端链接进行处理。")
|
|
28
|
+
route = structured_url.path[2:]
|
|
27
29
|
|
|
28
|
-
async with session.get(
|
|
30
|
+
async with session.get(route) as response:
|
|
29
31
|
response.raise_for_status()
|
|
30
32
|
|
|
31
33
|
# 如果后续有性能问题,可以先考虑使用 lxml 进行解析
|
|
32
34
|
book_page = BeautifulSoup(await response.text(), 'html.parser')
|
|
33
35
|
|
|
34
36
|
book_info = __extract_book_info(url, book_page, book_info)
|
|
35
|
-
volumes = await __extract_volumes(session,
|
|
37
|
+
volumes = await __extract_volumes(session, book_page)
|
|
36
38
|
|
|
37
39
|
return book_info, volumes
|
|
38
40
|
|
|
@@ -51,13 +53,13 @@ def __extract_book_info(url: str, book_page: BeautifulSoup, book_info: Optional[
|
|
|
51
53
|
)
|
|
52
54
|
|
|
53
55
|
|
|
54
|
-
async def __extract_volumes(session: Session,
|
|
56
|
+
async def __extract_volumes(session: Session, book_page: BeautifulSoup) -> list[VolInfo]:
|
|
55
57
|
script = book_page.find_all('script', language="javascript")[-1].text
|
|
56
58
|
|
|
57
59
|
pattern = re.compile(r'/book_data.php\?h=\w+')
|
|
58
60
|
book_data_url = pattern.search(script).group(0)
|
|
59
61
|
|
|
60
|
-
async with session.get(url =
|
|
62
|
+
async with session.get(url = book_data_url) as response:
|
|
61
63
|
response.raise_for_status()
|
|
62
64
|
|
|
63
65
|
book_data = (await response.text()).split('\n')
|
|
@@ -2,6 +2,7 @@ from rich.table import Table
|
|
|
2
2
|
from rich.prompt import Prompt
|
|
3
3
|
|
|
4
4
|
from kmdr.core import Picker, PICKERS, VolInfo
|
|
5
|
+
from kmdr.core.console import info
|
|
5
6
|
|
|
6
7
|
from .utils import resolve_volume
|
|
7
8
|
|
|
@@ -35,7 +36,7 @@ class DefaultVolPicker(Picker):
|
|
|
35
36
|
f"{volume.size:.2f}"
|
|
36
37
|
)
|
|
37
38
|
|
|
38
|
-
|
|
39
|
+
info(table)
|
|
39
40
|
|
|
40
41
|
choice_str = Prompt.ask(
|
|
41
42
|
"[green]请选择要下载的卷序号 (例如 'all', '1,2,3', '1-3,4-6')[/green]",
|
kmdr/module/picker/utils.py
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from typing import Optional
|
|
2
2
|
|
|
3
|
+
from kmdr.core.error import ArgsResolveError
|
|
4
|
+
|
|
3
5
|
def resolve_volume(volume: str) -> Optional[set[int]]:
|
|
4
6
|
if volume == 'all':
|
|
5
7
|
return None
|
|
@@ -18,20 +20,26 @@ def resolve_volume(volume: str) -> Optional[set[int]]:
|
|
|
18
20
|
|
|
19
21
|
if (volume := volume.strip()).isdigit():
|
|
20
22
|
# 只有一个数字
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
if (volume_digit := int(volume)) <= 0:
|
|
24
|
+
raise ArgsResolveError(f"卷号必须大于 0,当前值为 {volume_digit}。")
|
|
25
|
+
return {volume_digit}
|
|
23
26
|
elif '-' in volume and volume.count('-') == 1 and ',' not in volume:
|
|
24
27
|
# 使用了范围符号
|
|
25
28
|
start, end = volume.split('-')
|
|
26
29
|
|
|
27
|
-
|
|
30
|
+
if not start.strip().isdigit() or not end.strip().isdigit():
|
|
31
|
+
raise ArgsResolveError(f"无效的范围格式: {volume}。请使用 'start-end' 或 'start, end'。")
|
|
28
32
|
|
|
29
33
|
start = int(start.strip())
|
|
30
34
|
end = int(end.strip())
|
|
31
35
|
|
|
32
|
-
|
|
33
|
-
|
|
36
|
+
if start <= 0:
|
|
37
|
+
raise ArgsResolveError(f"卷号必须大于 0,当前值为 {start}。")
|
|
38
|
+
if end <= 0:
|
|
39
|
+
raise ArgsResolveError(f"卷号必须大于 0,当前值为 {end}。")
|
|
40
|
+
if start > end:
|
|
41
|
+
raise ArgsResolveError(f"起始卷号必须小于或等于结束卷号,当前值为 {start} - {end}。")
|
|
34
42
|
|
|
35
43
|
return set(range(start, end + 1))
|
|
36
44
|
|
|
37
|
-
raise
|
|
45
|
+
raise ArgsResolveError(f"无效的卷号格式: {volume}。请使用 'all', '1,2,3', '1-3', 或 '1-3,4-6'。")
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
kmdr/__init__.py,sha256=iLmy2rOkHS_4KZWMD8BgT7R3tLMKeaTCDVf3B4FyYxM,91
|
|
2
|
+
kmdr/_version.py,sha256=wqiogbpHngoX8QtZ_nlsUei1NMVQXPtFkYVQOrBjkgo,712
|
|
3
|
+
kmdr/main.py,sha256=xNUeU1voEMEqlf8z14EyiV-j61HPsRA3t2Nzreoaib4,2227
|
|
4
|
+
kmdr/core/__init__.py,sha256=aR3mhP1A81oTmTnBOonVQm_mFvFeNCOo7kalZVfszAc,416
|
|
5
|
+
kmdr/core/bases.py,sha256=br3HJC4mWL2jN4Y5QeXB5SwA0xnOfi2ougr5pEx7j8s,3989
|
|
6
|
+
kmdr/core/console.py,sha256=VziwGJQygF-zCrzTApPF-I9BgZKFSqCTiZ_pc5n8C-4,1950
|
|
7
|
+
kmdr/core/constants.py,sha256=JFj8FHO9ab6AeIzHz3LaC8l3ziLLFi75vil8wMPfZoA,1869
|
|
8
|
+
kmdr/core/context.py,sha256=IU5zHALsl4s9FeQJukhs703RAUe57Qf5P-nUAqGcW2A,1368
|
|
9
|
+
kmdr/core/defaults.py,sha256=tF41i145znhhDGVNL5irQpdF4lV_1tkWtvToRfm2R4s,8968
|
|
10
|
+
kmdr/core/error.py,sha256=qdbMEU6YhRcdbxlfZKKydoMFYXOCJQ473EYzl3mO33Q,1585
|
|
11
|
+
kmdr/core/protocol.py,sha256=ntHmnrvg_nZXh2-It0Wu9g2e8f3YpOLfpoRsWniG8as,247
|
|
12
|
+
kmdr/core/registry.py,sha256=KYjvww5WRuUg5SeIeWZb96803-kgrTIKYvFTj_7bPfo,5560
|
|
13
|
+
kmdr/core/session.py,sha256=ZrnoxZWUwLSMiLIOZDGEQZcH7RuBLcc5e2E0m17qX-s,4937
|
|
14
|
+
kmdr/core/structure.py,sha256=EQG1-8kHQ22Not2S7Q_jGxE0nb9XUbLh3JcNTb_nkvk,1199
|
|
15
|
+
kmdr/core/utils.py,sha256=_tRLggprXTGFvbRzyt3dgapQr2wI5NQ8Hc4_BHVj7lU,4069
|
|
16
|
+
kmdr/module/__init__.py,sha256=hY6lMBpxSn93t7OKnEbXUwTok2XqQoQbuyq1HLeC4Kc,125
|
|
17
|
+
kmdr/module/authenticator/CookieAuthenticator.py,sha256=lv_monQ7QUMfgPPgxoV1xr_KyUtwCKZhqJ8umv3W6xo,1108
|
|
18
|
+
kmdr/module/authenticator/LoginAuthenticator.py,sha256=5nyrzxYEGiRQ9ulnhjpuUWYeog-ALZlCCSAYkbwlKMw,1890
|
|
19
|
+
kmdr/module/authenticator/__init__.py,sha256=iSWhq-suAM1BrABRuTWiCKvZ1fkmf5HQ2GmbZ341GK0,103
|
|
20
|
+
kmdr/module/authenticator/utils.py,sha256=b49cwKjHVqOIHei0SUU5kMtWAgkYU0NH3YvDvdPhkxM,3053
|
|
21
|
+
kmdr/module/configurer/BaseUrlUpdator.py,sha256=OsdMUuyGujfqE6vEPt96sCJkJTfsPxAf5z2hbjcNZsQ,522
|
|
22
|
+
kmdr/module/configurer/ConfigClearer.py,sha256=irGBsck1EhTWl4IIOPF5b4o4TyrGvDAzibg9reQsI9A,501
|
|
23
|
+
kmdr/module/configurer/ConfigUnsetter.py,sha256=tbrOGKnTSeKhdMukIBPFqtdctEiQhu7GRThLthFYZew,602
|
|
24
|
+
kmdr/module/configurer/OptionLister.py,sha256=hqOtXfPcunzqkrq6Qx1KyZwEivp_vbpTVZ8PP3Q0MbY,1471
|
|
25
|
+
kmdr/module/configurer/OptionSetter.py,sha256=BQXIgyLoMDSNua2x96FtX-9JmJhVpPJ5o0tWHj_-7B0,898
|
|
26
|
+
kmdr/module/configurer/__init__.py,sha256=nSWGwUCcwOkvg28zxRd0S3b4jeag_W0IhzP74xq0ewE,204
|
|
27
|
+
kmdr/module/configurer/option_validate.py,sha256=tb6fvj01cDiPTQa6tC4-dYcTfUJUX3DBmkHKAKeuW3g,3358
|
|
28
|
+
kmdr/module/downloader/DirectDownloader.py,sha256=SPnzKDpVPg3utyrPrFr4pYiMI5w3eRCMemcYevH9VzY,1320
|
|
29
|
+
kmdr/module/downloader/ReferViaDownloader.py,sha256=o565u6W4G_4UFoqsLkXGRFIlRSMueZB_ZMdiAfhUVCc,1970
|
|
30
|
+
kmdr/module/downloader/__init__.py,sha256=PajPBQ4ZJwz_6Ok6k-HV7-YSibxAW5m5voYyP-U-_f4,97
|
|
31
|
+
kmdr/module/downloader/download_utils.py,sha256=JBAlNUQdWysKIGJJWaZibH8IpkIXP_kUEVxoC_bLbhg,13944
|
|
32
|
+
kmdr/module/downloader/misc.py,sha256=dS4GDHYxzKcqdhFNVJQG-4bS4IXMDnDrwi6eSWubl20,1892
|
|
33
|
+
kmdr/module/lister/BookUrlLister.py,sha256=R1sak_GQG8HKfdSzHF-Dy1iby6TgFlxKJLf2dwBr0Bw,550
|
|
34
|
+
kmdr/module/lister/FollowedBookLister.py,sha256=5r44sW_ypnRM5nBBd-4AL_WcNYCRklMewvMj1O2xCGc,2880
|
|
35
|
+
kmdr/module/lister/__init__.py,sha256=VVRMRGXASdIagzlmOuhy9gKjJzf8Rj3000BPzeWpHHE,91
|
|
36
|
+
kmdr/module/lister/utils.py,sha256=phAdtGI3F5kT6x4JFOiN39Ei_yY5JdwQG-tfRZ6MVvs,3595
|
|
37
|
+
kmdr/module/picker/ArgsFilterPicker.py,sha256=wBCDa6KsSSOLLQk8D4ftZ9ZnwPLiHIirxf6gGBq3I08,1782
|
|
38
|
+
kmdr/module/picker/DefaultVolPicker.py,sha256=qScmRtwpe1u3fY7HMIo15YXtwB4LYAIJaTu_EkrAJbs,1780
|
|
39
|
+
kmdr/module/picker/__init__.py,sha256=qYELEkv0nFT8DadItB6fdUTED2CHrC43wuPKO7QrCCQ,93
|
|
40
|
+
kmdr/module/picker/utils.py,sha256=baJzpQSSSBdTPZ2scrEeNOssGYOL0nNwxN1gTsOAqnc,1604
|
|
41
|
+
kmoe_manga_downloader-1.2.3b0.dist-info/licenses/LICENSE,sha256=bKQlsXu8mAYKRZyoZKOEqMcCc8YjT5Q3Hgr21e0yU4E,1068
|
|
42
|
+
kmoe_manga_downloader-1.2.3b0.dist-info/METADATA,sha256=9Z42LEHwL9wZVCRiUbR7FX2BsO1jBmu5dpXeIFRtN_c,9695
|
|
43
|
+
kmoe_manga_downloader-1.2.3b0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
44
|
+
kmoe_manga_downloader-1.2.3b0.dist-info/entry_points.txt,sha256=DGMytQAhx4uNuKQL7BPkiWESHLXkH-2KSEqwHdygNPA,47
|
|
45
|
+
kmoe_manga_downloader-1.2.3b0.dist-info/top_level.txt,sha256=e0qxOgWp0tl3GLpmXGjZv3--q_TLoJ7GztM48Ov27wM,5
|
|
46
|
+
kmoe_manga_downloader-1.2.3b0.dist-info/RECORD,,
|
|
@@ -1,43 +0,0 @@
|
|
|
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,,
|
|
File without changes
|
{kmoe_manga_downloader-1.2.1.dist-info → kmoe_manga_downloader-1.2.3b0.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{kmoe_manga_downloader-1.2.1.dist-info → kmoe_manga_downloader-1.2.3b0.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
{kmoe_manga_downloader-1.2.1.dist-info → kmoe_manga_downloader-1.2.3b0.dist-info}/top_level.txt
RENAMED
|
File without changes
|