ktoolbox 0.1.0.dev0__tar.gz → 0.2.1__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.
- ktoolbox-0.2.1/LICENSE +21 -0
- {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/PKG-INFO +49 -9
- {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/README.md +46 -7
- ktoolbox-0.2.1/ktoolbox/__init__.py +4 -0
- {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/__main__.py +7 -4
- {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/action/job.py +32 -16
- {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/api/base.py +1 -1
- {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/cli.py +10 -2
- {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/configuration.py +3 -0
- {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/downloader/downloader.py +12 -10
- {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/job/runner.py +0 -2
- {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/utils.py +16 -15
- {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/pyproject.toml +8 -3
- ktoolbox-0.1.0.dev0/LICENSE +0 -674
- ktoolbox-0.1.0.dev0/ktoolbox/__init__.py +0 -4
- {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/action/__init__.py +0 -0
- {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/action/base.py +0 -0
- {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/action/fetch.py +0 -0
- {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/action/search.py +0 -0
- {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/api/__init__.py +0 -0
- {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/api/misc/__init__.py +0 -0
- {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/api/misc/get_app_version.py +0 -0
- {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/api/model/__init__.py +0 -0
- {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/api/model/announcement.py +0 -0
- {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/api/model/creator.py +0 -0
- {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/api/model/post.py +0 -0
- {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/api/posts/__init__.py +0 -0
- {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/api/posts/get_announcement.py +0 -0
- {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/api/posts/get_creator_post.py +0 -0
- {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/api/posts/get_creators.py +0 -0
- {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/api/posts/get_post.py +0 -0
- {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/api/utils.py +0 -0
- {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/downloader/__init__.py +0 -0
- {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/downloader/base.py +0 -0
- {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/enum.py +0 -0
- {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/job/__init__.py +0 -0
- {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/job/model.py +0 -0
- {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/model.py +0 -0
ktoolbox-0.2.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Ljzd PRO
|
|
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.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: ktoolbox
|
|
3
|
-
Version: 0.1
|
|
4
|
-
Summary: A useful tool for Kemono.party
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: A useful CLI tool for downloading posts in Kemono.party / .su
|
|
5
5
|
Home-page: https://github.com/Ljzd-PRO/KToolBox
|
|
6
6
|
Keywords: kemono,kemono.party,cli-app,downloader,os-independent
|
|
7
7
|
Author: Ljzd-PRO
|
|
@@ -20,6 +20,7 @@ Requires-Dist: aiofiles (>=23.2.1,<24.0.0)
|
|
|
20
20
|
Requires-Dist: fire (>=0.5.0,<0.6.0)
|
|
21
21
|
Requires-Dist: httpx (<=0.25.0)
|
|
22
22
|
Requires-Dist: loguru (>=0.7.2,<0.8.0)
|
|
23
|
+
Requires-Dist: pathvalidate (>=3.2.0,<4.0.0)
|
|
23
24
|
Requires-Dist: pydantic (>=2.4.2,<3.0.0)
|
|
24
25
|
Requires-Dist: pydantic-settings (>=2.0.3,<3.0.0)
|
|
25
26
|
Requires-Dist: tenacity (>=8.2.3,<9.0.0)
|
|
@@ -29,6 +30,10 @@ Project-URL: Documentation, https://github.com/Ljzd-PRO/KToolBox/wiki
|
|
|
29
30
|
Project-URL: Repository, https://github.com/Ljzd-PRO/KToolBox
|
|
30
31
|
Description-Content-Type: text/markdown
|
|
31
32
|
|
|
33
|
+
<p align="center" style="text-decoration:none">
|
|
34
|
+
<img align="center" src="https://raw.githubusercontent.com/Ljzd-PRO/KToolBox/master/static/repository-open-graph-2.svg" alt="logo">
|
|
35
|
+
</p>
|
|
36
|
+
|
|
32
37
|
<h1 align="center">
|
|
33
38
|
KToolBox
|
|
34
39
|
</h1>
|
|
@@ -44,11 +49,11 @@ Description-Content-Type: text/markdown
|
|
|
44
49
|
</a>
|
|
45
50
|
|
|
46
51
|
<a href="./LICENSE">
|
|
47
|
-
<img src="https://img.shields.io/badge/License-
|
|
52
|
+
<img src="https://img.shields.io/badge/License-MIT-blue" alt="MIT"/>
|
|
48
53
|
</a>
|
|
49
54
|
|
|
50
|
-
<a href="https://github.com/Ljzd-PRO/KToolBox/
|
|
51
|
-
<img src="https://img.shields.io/github/last-commit/Ljzd-PRO/KToolBox/
|
|
55
|
+
<a href="https://github.com/Ljzd-PRO/KToolBox/activity">
|
|
56
|
+
<img src="https://img.shields.io/github/last-commit/Ljzd-PRO/KToolBox/devel" alt="Last Commit"/>
|
|
52
57
|
</a>
|
|
53
58
|
|
|
54
59
|
<a href="https://codecov.io/gh/Ljzd-PRO/KToolBox" target="_blank">
|
|
@@ -56,7 +61,7 @@ Description-Content-Type: text/markdown
|
|
|
56
61
|
</a>
|
|
57
62
|
|
|
58
63
|
<a style="text-decoration:none">
|
|
59
|
-
<img src="https://img.shields.io/badge/Platform-
|
|
64
|
+
<img src="https://img.shields.io/badge/Platform-Windows%20|%20Linux%20|%20macOS-blue" alt="Platform Win | Linux | macOS"/>
|
|
60
65
|
</a>
|
|
61
66
|
</p>
|
|
62
67
|
|
|
@@ -64,7 +69,7 @@ Description-Content-Type: text/markdown
|
|
|
64
69
|
<a href="./README.md">English</a> | <a href="./README_zh-CN.md">中文</a>
|
|
65
70
|
</p>
|
|
66
71
|
|
|
67
|
-
|
|
72
|
+
<img src="https://cdn.jsdelivr.net/gh/Ljzd-PRO/KToolBox@latest/static/preview-1.png" alt="Preview">
|
|
68
73
|
|
|
69
74
|
## Features
|
|
70
75
|
|
|
@@ -83,9 +88,11 @@ Description-Content-Type: text/markdown
|
|
|
83
88
|
|
|
84
89
|
## Tutorial
|
|
85
90
|
|
|
91
|
+
### Command
|
|
92
|
+
|
|
86
93
|
For more information, use the help command.
|
|
87
94
|
|
|
88
|
-
>
|
|
95
|
+
> [!Warning]
|
|
89
96
|
> The return text here are only **demo**, some of them may be **outdated**.
|
|
90
97
|
|
|
91
98
|
- Install KToolBox:
|
|
@@ -181,6 +188,35 @@ For more information, use the help command.
|
|
|
181
188
|
|
|
182
189
|
The `creator-indices.ktoolbox` file contains the information and filepath of posts inside the directory.
|
|
183
190
|
|
|
191
|
+
### Configuration
|
|
192
|
+
|
|
193
|
+
- KToolBox read **`prod.env` file** in the work folder or **environment variables** to store configuration
|
|
194
|
+
- **All configuration options** defined in [`ktoolbox/config.py`](ktoolbox/configuration.py)
|
|
195
|
+
- Use `__` to specify the sub option, like `KTOOLBOX_API__SCHEME` means `api.scheme`
|
|
196
|
+
- All configuration options are optional
|
|
197
|
+
|
|
198
|
+
#### `prod.env` file example
|
|
199
|
+
|
|
200
|
+
```dotenv
|
|
201
|
+
# Download 10 files at the same time.
|
|
202
|
+
KTOOLBOX_JOB__COUNT=10
|
|
203
|
+
|
|
204
|
+
# Allocate 102400 Bytes as buffer for each download job
|
|
205
|
+
KTOOLBOX_DOWNLOADER__BUFFER_SIZE=102400
|
|
206
|
+
|
|
207
|
+
# Set post attachments directory path as `./`, it means to save all attachments files in post directory
|
|
208
|
+
# without making a new sub directory to storage them
|
|
209
|
+
KTOOLBOX_JOB__POST_STRUCTURE__ATTACHMENTS=./
|
|
210
|
+
|
|
211
|
+
# Disable SSL certificate verification for Kemono API server and download server
|
|
212
|
+
# It's useful when certificate on Kemono server expired. (SSL: CERTIFICATE_VERIFY_FAILED)
|
|
213
|
+
KTOOLBOX_SSL_VERIFY=False
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## About Development
|
|
217
|
+
|
|
218
|
+
- Development branch: [🔗devel](https://github.com/Ljzd-PRO/KToolBox/tree/devel)
|
|
219
|
+
|
|
184
220
|
## About Kemono
|
|
185
221
|
|
|
186
222
|
Description from https://kemono.su :
|
|
@@ -200,9 +236,13 @@ Description from https://kemono.su :
|
|
|
200
236
|
> Contributors here upload content and share it here for easy searching and organization. \
|
|
201
237
|
> To get started viewing content, either search for creators on the artists page, or search for content on the posts page.
|
|
202
238
|
|
|
239
|
+
## Code Coverage
|
|
240
|
+
|
|
241
|
+

|
|
242
|
+
|
|
203
243
|
## License
|
|
204
244
|
|
|
205
|
-
KToolBox is licensed under
|
|
245
|
+
KToolBox is licensed under MIT.
|
|
206
246
|
|
|
207
247
|
Copyright © 2023 by Ljzd-PRO.
|
|
208
248
|
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
<p align="center" style="text-decoration:none">
|
|
2
|
+
<img align="center" src="https://raw.githubusercontent.com/Ljzd-PRO/KToolBox/master/static/repository-open-graph-2.svg" alt="logo">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
1
5
|
<h1 align="center">
|
|
2
6
|
KToolBox
|
|
3
7
|
</h1>
|
|
@@ -13,11 +17,11 @@
|
|
|
13
17
|
</a>
|
|
14
18
|
|
|
15
19
|
<a href="./LICENSE">
|
|
16
|
-
<img src="https://img.shields.io/badge/License-
|
|
20
|
+
<img src="https://img.shields.io/badge/License-MIT-blue" alt="MIT"/>
|
|
17
21
|
</a>
|
|
18
22
|
|
|
19
|
-
<a href="https://github.com/Ljzd-PRO/KToolBox/
|
|
20
|
-
<img src="https://img.shields.io/github/last-commit/Ljzd-PRO/KToolBox/
|
|
23
|
+
<a href="https://github.com/Ljzd-PRO/KToolBox/activity">
|
|
24
|
+
<img src="https://img.shields.io/github/last-commit/Ljzd-PRO/KToolBox/devel" alt="Last Commit"/>
|
|
21
25
|
</a>
|
|
22
26
|
|
|
23
27
|
<a href="https://codecov.io/gh/Ljzd-PRO/KToolBox" target="_blank">
|
|
@@ -25,7 +29,7 @@
|
|
|
25
29
|
</a>
|
|
26
30
|
|
|
27
31
|
<a style="text-decoration:none">
|
|
28
|
-
<img src="https://img.shields.io/badge/Platform-
|
|
32
|
+
<img src="https://img.shields.io/badge/Platform-Windows%20|%20Linux%20|%20macOS-blue" alt="Platform Win | Linux | macOS"/>
|
|
29
33
|
</a>
|
|
30
34
|
</p>
|
|
31
35
|
|
|
@@ -33,7 +37,7 @@
|
|
|
33
37
|
<a href="./README.md">English</a> | <a href="./README_zh-CN.md">中文</a>
|
|
34
38
|
</p>
|
|
35
39
|
|
|
36
|
-
|
|
40
|
+
<img src="https://cdn.jsdelivr.net/gh/Ljzd-PRO/KToolBox@latest/static/preview-1.png" alt="Preview">
|
|
37
41
|
|
|
38
42
|
## Features
|
|
39
43
|
|
|
@@ -52,9 +56,11 @@
|
|
|
52
56
|
|
|
53
57
|
## Tutorial
|
|
54
58
|
|
|
59
|
+
### Command
|
|
60
|
+
|
|
55
61
|
For more information, use the help command.
|
|
56
62
|
|
|
57
|
-
>
|
|
63
|
+
> [!Warning]
|
|
58
64
|
> The return text here are only **demo**, some of them may be **outdated**.
|
|
59
65
|
|
|
60
66
|
- Install KToolBox:
|
|
@@ -150,6 +156,35 @@ For more information, use the help command.
|
|
|
150
156
|
|
|
151
157
|
The `creator-indices.ktoolbox` file contains the information and filepath of posts inside the directory.
|
|
152
158
|
|
|
159
|
+
### Configuration
|
|
160
|
+
|
|
161
|
+
- KToolBox read **`prod.env` file** in the work folder or **environment variables** to store configuration
|
|
162
|
+
- **All configuration options** defined in [`ktoolbox/config.py`](ktoolbox/configuration.py)
|
|
163
|
+
- Use `__` to specify the sub option, like `KTOOLBOX_API__SCHEME` means `api.scheme`
|
|
164
|
+
- All configuration options are optional
|
|
165
|
+
|
|
166
|
+
#### `prod.env` file example
|
|
167
|
+
|
|
168
|
+
```dotenv
|
|
169
|
+
# Download 10 files at the same time.
|
|
170
|
+
KTOOLBOX_JOB__COUNT=10
|
|
171
|
+
|
|
172
|
+
# Allocate 102400 Bytes as buffer for each download job
|
|
173
|
+
KTOOLBOX_DOWNLOADER__BUFFER_SIZE=102400
|
|
174
|
+
|
|
175
|
+
# Set post attachments directory path as `./`, it means to save all attachments files in post directory
|
|
176
|
+
# without making a new sub directory to storage them
|
|
177
|
+
KTOOLBOX_JOB__POST_STRUCTURE__ATTACHMENTS=./
|
|
178
|
+
|
|
179
|
+
# Disable SSL certificate verification for Kemono API server and download server
|
|
180
|
+
# It's useful when certificate on Kemono server expired. (SSL: CERTIFICATE_VERIFY_FAILED)
|
|
181
|
+
KTOOLBOX_SSL_VERIFY=False
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## About Development
|
|
185
|
+
|
|
186
|
+
- Development branch: [🔗devel](https://github.com/Ljzd-PRO/KToolBox/tree/devel)
|
|
187
|
+
|
|
153
188
|
## About Kemono
|
|
154
189
|
|
|
155
190
|
Description from https://kemono.su :
|
|
@@ -169,8 +204,12 @@ Description from https://kemono.su :
|
|
|
169
204
|
> Contributors here upload content and share it here for easy searching and organization. \
|
|
170
205
|
> To get started viewing content, either search for creators on the artists page, or search for content on the posts page.
|
|
171
206
|
|
|
207
|
+
## Code Coverage
|
|
208
|
+
|
|
209
|
+

|
|
210
|
+
|
|
172
211
|
## License
|
|
173
212
|
|
|
174
|
-
KToolBox is licensed under
|
|
213
|
+
KToolBox is licensed under MIT.
|
|
175
214
|
|
|
176
215
|
Copyright © 2023 by Ljzd-PRO.
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
|
|
3
1
|
import fire
|
|
4
2
|
|
|
5
3
|
from ktoolbox.cli import KToolBoxCli
|
|
6
4
|
from ktoolbox.utils import logger_init
|
|
7
5
|
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
|
|
7
|
+
def main():
|
|
8
|
+
logger_init(cli_use=True)
|
|
10
9
|
fire.Fire(KToolBoxCli)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
if __name__ == "__main__":
|
|
13
|
+
main()
|
|
@@ -2,6 +2,8 @@ from pathlib import Path
|
|
|
2
2
|
from typing import List, Union, Tuple
|
|
3
3
|
|
|
4
4
|
import aiofiles
|
|
5
|
+
from loguru import logger
|
|
6
|
+
from pathvalidate import sanitize_filename
|
|
5
7
|
|
|
6
8
|
from ktoolbox.action import ActionRet, fetch_all_creator_posts, FetchInterruptError
|
|
7
9
|
from ktoolbox.api.model import Post
|
|
@@ -29,16 +31,25 @@ async def create_job_from_post(
|
|
|
29
31
|
``True`` & ``None`` -> ``config.job.post_structure``
|
|
30
32
|
:param dump_post_data: Whether to dump post data (post.json) in post directory
|
|
31
33
|
"""
|
|
32
|
-
|
|
34
|
+
post_path.mkdir(exist_ok=True)
|
|
35
|
+
|
|
36
|
+
# Load ``PostStructureConfiguration``
|
|
33
37
|
if post_structure in [True, None]:
|
|
34
38
|
post_structure = config.job.post_structure
|
|
35
39
|
if post_structure:
|
|
36
|
-
attachments_path = post_path / post_structure.attachments
|
|
37
|
-
|
|
40
|
+
attachments_path = post_path / post_structure.attachments # attachments
|
|
41
|
+
attachments_path.mkdir(exist_ok=True)
|
|
42
|
+
content_path = post_path / post_structure.content_filepath # content
|
|
43
|
+
content_path.parent.mkdir(exist_ok=True)
|
|
38
44
|
else:
|
|
39
45
|
attachments_path = post_path
|
|
40
46
|
content_path = None
|
|
41
|
-
|
|
47
|
+
|
|
48
|
+
# Create jobs
|
|
49
|
+
jobs: List[Job] = []
|
|
50
|
+
for attachment in post.attachments: # attachments
|
|
51
|
+
if not attachment.path:
|
|
52
|
+
continue
|
|
42
53
|
jobs.append(
|
|
43
54
|
Job(
|
|
44
55
|
path=attachments_path,
|
|
@@ -47,17 +58,18 @@ async def create_job_from_post(
|
|
|
47
58
|
type=PostFileTypeEnum.Attachment
|
|
48
59
|
)
|
|
49
60
|
)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
61
|
+
if post.file.path: # file
|
|
62
|
+
jobs.append(
|
|
63
|
+
Job(
|
|
64
|
+
path=post_path,
|
|
65
|
+
alt_filename=f"{post.id}_{post.file.name}",
|
|
66
|
+
server_path=post.file.path,
|
|
67
|
+
type=PostFileTypeEnum.File
|
|
68
|
+
)
|
|
56
69
|
)
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
content_path.parent.mkdir()
|
|
70
|
+
|
|
71
|
+
# Write content file
|
|
72
|
+
if content_path and post.content:
|
|
61
73
|
async with aiofiles.open(content_path, "w", encoding=config.downloader.encoding) as f:
|
|
62
74
|
await f.write(post.content)
|
|
63
75
|
if dump_post_data:
|
|
@@ -65,6 +77,7 @@ async def create_job_from_post(
|
|
|
65
77
|
await f.write(
|
|
66
78
|
post.model_dump_json(indent=config.json_dump_indent)
|
|
67
79
|
)
|
|
80
|
+
|
|
68
81
|
return jobs
|
|
69
82
|
|
|
70
83
|
|
|
@@ -117,6 +130,7 @@ async def create_job_from_creator(
|
|
|
117
130
|
mix_posts = config.job.mix_posts if mix_posts is None else mix_posts
|
|
118
131
|
|
|
119
132
|
# Get posts
|
|
133
|
+
logger.info(f"Start fetching posts from creator {creator_id}")
|
|
120
134
|
if all_pages:
|
|
121
135
|
post_list: List[Post] = []
|
|
122
136
|
try:
|
|
@@ -130,18 +144,20 @@ async def create_job_from_creator(
|
|
|
130
144
|
post_list = ret.data
|
|
131
145
|
else:
|
|
132
146
|
return ActionRet(**ret.model_dump(mode="python"))
|
|
147
|
+
logger.info(f"Get {len(post_list)} posts, start creating jobs")
|
|
133
148
|
|
|
134
149
|
# Filter posts and generate ``CreatorIndices``
|
|
135
150
|
if not mix_posts:
|
|
136
151
|
indices = None
|
|
137
152
|
if update_from:
|
|
138
153
|
post_list, indices = filter_posts_with_indices(post_list, update_from)
|
|
154
|
+
logger.info(f"{len(post_list)} posts will be downloaded")
|
|
139
155
|
elif save_creator_indices: # It's unnecessary to create indices again when ``update_from`` was provided
|
|
140
156
|
indices = CreatorIndices(
|
|
141
157
|
creator_id=creator_id,
|
|
142
158
|
service=service,
|
|
143
159
|
posts={post.id: post for post in post_list},
|
|
144
|
-
posts_path={post.id: path / post.title for post in post_list}
|
|
160
|
+
posts_path={post.id: path / sanitize_filename(post.title) for post in post_list}
|
|
145
161
|
)
|
|
146
162
|
if indices:
|
|
147
163
|
async with aiofiles.open(
|
|
@@ -154,7 +170,7 @@ async def create_job_from_creator(
|
|
|
154
170
|
job_list: List[Job] = []
|
|
155
171
|
for post in post_list:
|
|
156
172
|
# Get post path
|
|
157
|
-
default_post_path = path if mix_posts else path / post.title
|
|
173
|
+
default_post_path = path if mix_posts else path / sanitize_filename(post.title)
|
|
158
174
|
if update_from:
|
|
159
175
|
if not (post_path := update_from.posts_path.get(post.id)):
|
|
160
176
|
post_path = default_post_path
|
|
@@ -94,7 +94,7 @@ class BaseAPI(ABC, Generic[_T]):
|
|
|
94
94
|
url_parts = [config.api.scheme, config.api.netloc, f"{config.api.path}{path}", '', '', '']
|
|
95
95
|
url = urlunparse(url_parts)
|
|
96
96
|
try:
|
|
97
|
-
async with httpx.AsyncClient() as client:
|
|
97
|
+
async with httpx.AsyncClient(verify=config.ssl_verify) as client:
|
|
98
98
|
res = await client.request(
|
|
99
99
|
method=cls.method,
|
|
100
100
|
url=url,
|
|
@@ -3,6 +3,7 @@ from typing import Union, overload
|
|
|
3
3
|
|
|
4
4
|
import aiofiles
|
|
5
5
|
from loguru import logger
|
|
6
|
+
from pathvalidate import sanitize_filename
|
|
6
7
|
|
|
7
8
|
from ktoolbox import __version__
|
|
8
9
|
from ktoolbox.action import create_job_from_post, create_job_from_creator
|
|
@@ -173,7 +174,7 @@ class KToolBoxCli:
|
|
|
173
174
|
if ret:
|
|
174
175
|
job_list = await create_job_from_post(
|
|
175
176
|
post=ret.data,
|
|
176
|
-
post_path=path / ret.data.title,
|
|
177
|
+
post_path=path / sanitize_filename(ret.data.title),
|
|
177
178
|
dump_post_data=dump_post_data
|
|
178
179
|
)
|
|
179
180
|
job_runner = JobRunner(job_list=job_list)
|
|
@@ -267,6 +268,13 @@ class KToolBoxCli:
|
|
|
267
268
|
creator = next(creator_ret.data, None)
|
|
268
269
|
if creator:
|
|
269
270
|
creator_name = creator.name
|
|
271
|
+
logger.info(
|
|
272
|
+
generate_msg(
|
|
273
|
+
"Got creator information",
|
|
274
|
+
name=creator.name,
|
|
275
|
+
id=creator.id
|
|
276
|
+
)
|
|
277
|
+
)
|
|
270
278
|
else:
|
|
271
279
|
logger.warning(
|
|
272
280
|
generate_msg(
|
|
@@ -274,7 +282,7 @@ class KToolBoxCli:
|
|
|
274
282
|
detail=creator_ret.message
|
|
275
283
|
)
|
|
276
284
|
)
|
|
277
|
-
creator_path = path / creator_name
|
|
285
|
+
creator_path = path / sanitize_filename(creator_name)
|
|
278
286
|
|
|
279
287
|
creator_path.mkdir(exist_ok=True)
|
|
280
288
|
ret = await create_job_from_creator(
|
|
@@ -116,6 +116,9 @@ class Configuration(BaseSettings):
|
|
|
116
116
|
job: JobConfiguration = JobConfiguration()
|
|
117
117
|
logger: LoggerConfiguration = LoggerConfiguration()
|
|
118
118
|
|
|
119
|
+
ssl_verify: bool = True
|
|
120
|
+
# noinspection SpellCheckingInspection
|
|
121
|
+
"""Enable SSL certificate verification for Kemono API server and download server"""
|
|
119
122
|
json_dump_indent: int = 4
|
|
120
123
|
"""Indent of JSON file dump"""
|
|
121
124
|
|
|
@@ -113,9 +113,20 @@ class Downloader:
|
|
|
113
113
|
:return: ``DownloaderRet`` which contain the actual output filename
|
|
114
114
|
:raise CancelledError
|
|
115
115
|
"""
|
|
116
|
+
# Use ``self._alt_filename`` instead of filename from server,
|
|
117
|
+
# to make the process more efficient
|
|
118
|
+
if (self._path / self._filename).is_file():
|
|
119
|
+
return DownloaderRet(
|
|
120
|
+
code=RetCodeEnum.FileExisted,
|
|
121
|
+
message=generate_msg(
|
|
122
|
+
"Download file existed, skipped",
|
|
123
|
+
path=self._path / self._filename
|
|
124
|
+
)
|
|
125
|
+
)
|
|
126
|
+
|
|
116
127
|
tqdm_class: Type[std_tqdm] = tqdm_class or tqdm.asyncio.tqdm
|
|
117
128
|
async with self._lock:
|
|
118
|
-
async with httpx.AsyncClient() as client:
|
|
129
|
+
async with httpx.AsyncClient(verify=config.ssl_verify) as client:
|
|
119
130
|
async with client.stream(
|
|
120
131
|
method="GET",
|
|
121
132
|
url=self._url,
|
|
@@ -138,15 +149,6 @@ class Downloader:
|
|
|
138
149
|
filename = urllib.parse.unquote(Path(self._url).name)
|
|
139
150
|
self._filename = filename
|
|
140
151
|
|
|
141
|
-
if (self._path / filename).is_file():
|
|
142
|
-
return DownloaderRet(
|
|
143
|
-
code=RetCodeEnum.FileExisted,
|
|
144
|
-
message=generate_msg(
|
|
145
|
-
"Download file existed, skipped",
|
|
146
|
-
path=self._path / filename
|
|
147
|
-
)
|
|
148
|
-
)
|
|
149
|
-
|
|
150
152
|
# Download
|
|
151
153
|
temp_filepath = (self._path / filename).with_suffix(f".{config.downloader.temp_suffix}")
|
|
152
154
|
total_size = int(length_str) if (length_str := res.headers.get("Content-Length")) else None
|
|
@@ -74,8 +74,6 @@ class JobRunner:
|
|
|
74
74
|
failed_num = 0
|
|
75
75
|
while not self._job_queue.empty():
|
|
76
76
|
job = await self._job_queue.get()
|
|
77
|
-
if not job.path.is_dir():
|
|
78
|
-
job.path.mkdir()
|
|
79
77
|
|
|
80
78
|
# Create downloader
|
|
81
79
|
url_parts = [config.downloader.scheme, config.api.files_netloc, job.server_path, '', '', '']
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import cgi
|
|
2
|
+
import logging
|
|
2
3
|
import sys
|
|
3
4
|
import urllib.parse
|
|
4
5
|
from pathlib import Path
|
|
5
|
-
from typing import Generic, TypeVar, Optional, Dict, List,
|
|
6
|
+
from typing import Generic, TypeVar, Optional, Dict, List, Tuple
|
|
6
7
|
|
|
7
8
|
import aiofiles
|
|
8
9
|
from loguru import logger
|
|
@@ -103,31 +104,31 @@ def generate_msg(title: str = None, **kwargs):
|
|
|
103
104
|
return f"{title} - {kwargs}" if kwargs else title
|
|
104
105
|
|
|
105
106
|
|
|
106
|
-
def logger_init(
|
|
107
|
+
def logger_init(cli_use: bool = False, disable_stdout: bool = False):
|
|
107
108
|
"""
|
|
108
109
|
Initialize ``loguru`` logger
|
|
109
110
|
|
|
110
|
-
:param
|
|
111
|
+
:param cli_use: Set logger level ``INFO`` and filter out ``SUCCESS``
|
|
111
112
|
:param disable_stdout: Disable default output stream
|
|
112
113
|
"""
|
|
113
114
|
if disable_stdout:
|
|
114
115
|
logger.remove()
|
|
115
|
-
elif
|
|
116
|
+
elif cli_use:
|
|
116
117
|
logger.remove()
|
|
117
118
|
logger.add(
|
|
118
119
|
sys.stderr,
|
|
119
|
-
level=
|
|
120
|
-
|
|
121
|
-
path = config.logger.path
|
|
122
|
-
if not path.is_dir():
|
|
123
|
-
path.mkdir()
|
|
124
|
-
if path is not None:
|
|
125
|
-
logger.add(
|
|
126
|
-
path / DataStorageNameEnum.LogData.value,
|
|
127
|
-
level=config.logger.level,
|
|
128
|
-
rotation=config.logger.rotation,
|
|
129
|
-
diagnose=True
|
|
120
|
+
level=logging.INFO,
|
|
121
|
+
filter=lambda record: record["level"].name != "SUCCESS"
|
|
130
122
|
)
|
|
123
|
+
if path := config.logger.path:
|
|
124
|
+
path.mkdir(exist_ok=True)
|
|
125
|
+
if path is not None:
|
|
126
|
+
logger.add(
|
|
127
|
+
path / DataStorageNameEnum.LogData.value,
|
|
128
|
+
level=config.logger.level,
|
|
129
|
+
rotation=config.logger.rotation,
|
|
130
|
+
diagnose=True
|
|
131
|
+
)
|
|
131
132
|
|
|
132
133
|
|
|
133
134
|
async def dump_search(result: List[BaseModel], path: Path):
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "ktoolbox"
|
|
3
|
-
version = "0.1
|
|
4
|
-
description = "A useful tool for Kemono.party"
|
|
3
|
+
version = "0.2.1"
|
|
4
|
+
description = "A useful CLI tool for downloading posts in Kemono.party / .su"
|
|
5
5
|
authors = ["Ljzd-PRO <ljzd@office.ljzd-pro.asia>"]
|
|
6
6
|
readme = "README.md"
|
|
7
7
|
homepage = "https://github.com/Ljzd-PRO/KToolBox"
|
|
8
8
|
repository = "https://github.com/Ljzd-PRO/KToolBox"
|
|
9
9
|
documentation = "https://github.com/Ljzd-PRO/KToolBox/wiki"
|
|
10
|
+
|
|
10
11
|
keywords = ["kemono", "kemono.party", "cli-app", "downloader", "os-independent"]
|
|
11
12
|
classifiers = [
|
|
12
13
|
"Programming Language :: Python :: 3",
|
|
@@ -21,6 +22,9 @@ packages = [{ include = "ktoolbox" }]
|
|
|
21
22
|
[tool.poetry.urls]
|
|
22
23
|
"Bug Tracker" = "https://github.com/Ljzd-PRO/KToolBox/issues"
|
|
23
24
|
|
|
25
|
+
[tool.poetry.scripts]
|
|
26
|
+
ktoolbox = "ktoolbox.__main__:main"
|
|
27
|
+
|
|
24
28
|
[tool.poetry.dependencies]
|
|
25
29
|
python = ">=3.8,<3.12"
|
|
26
30
|
pydantic = "^2.4.2"
|
|
@@ -31,6 +35,7 @@ fire = "^0.5.0"
|
|
|
31
35
|
tqdm = "^4.66.1"
|
|
32
36
|
loguru = "^0.7.2"
|
|
33
37
|
aiofiles = "^23.2.1"
|
|
38
|
+
pathvalidate = "^3.2.0"
|
|
34
39
|
|
|
35
40
|
#[tool.poetry.group.uvloop.dependencies]
|
|
36
41
|
#uvloop = "^0.17.0"
|
|
@@ -46,7 +51,7 @@ pytest-cov = "==4.1.0"
|
|
|
46
51
|
allpairspy = "==2.5.1"
|
|
47
52
|
|
|
48
53
|
[tool.poetry.group.dev.dependencies]
|
|
49
|
-
datamodel-code-generator = "
|
|
54
|
+
datamodel-code-generator = ">=0.22.1,<0.24.0"
|
|
50
55
|
|
|
51
56
|
#[tool.poetry.group.uvloop]
|
|
52
57
|
#optional = true
|