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.
Files changed (38) hide show
  1. ktoolbox-0.2.1/LICENSE +21 -0
  2. {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/PKG-INFO +49 -9
  3. {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/README.md +46 -7
  4. ktoolbox-0.2.1/ktoolbox/__init__.py +4 -0
  5. {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/__main__.py +7 -4
  6. {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/action/job.py +32 -16
  7. {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/api/base.py +1 -1
  8. {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/cli.py +10 -2
  9. {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/configuration.py +3 -0
  10. {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/downloader/downloader.py +12 -10
  11. {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/job/runner.py +0 -2
  12. {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/utils.py +16 -15
  13. {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/pyproject.toml +8 -3
  14. ktoolbox-0.1.0.dev0/LICENSE +0 -674
  15. ktoolbox-0.1.0.dev0/ktoolbox/__init__.py +0 -4
  16. {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/action/__init__.py +0 -0
  17. {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/action/base.py +0 -0
  18. {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/action/fetch.py +0 -0
  19. {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/action/search.py +0 -0
  20. {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/api/__init__.py +0 -0
  21. {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/api/misc/__init__.py +0 -0
  22. {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/api/misc/get_app_version.py +0 -0
  23. {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/api/model/__init__.py +0 -0
  24. {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/api/model/announcement.py +0 -0
  25. {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/api/model/creator.py +0 -0
  26. {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/api/model/post.py +0 -0
  27. {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/api/posts/__init__.py +0 -0
  28. {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/api/posts/get_announcement.py +0 -0
  29. {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/api/posts/get_creator_post.py +0 -0
  30. {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/api/posts/get_creators.py +0 -0
  31. {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/api/posts/get_post.py +0 -0
  32. {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/api/utils.py +0 -0
  33. {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/downloader/__init__.py +0 -0
  34. {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/downloader/base.py +0 -0
  35. {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/enum.py +0 -0
  36. {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/job/__init__.py +0 -0
  37. {ktoolbox-0.1.0.dev0 → ktoolbox-0.2.1}/ktoolbox/job/model.py +0 -0
  38. {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.0.dev0
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-GPLv3-blue" alt="GPLv3"/>
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/commits/master">
51
- <img src="https://img.shields.io/github/last-commit/Ljzd-PRO/KToolBox/master" alt="Last Commit"/>
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-Win%20|%20Linux%20|%20macOS-blue" alt="Platform Win | Linux | macOS"/>
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
- ![Preview](./static/preview-1.png)
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
- > **Warning**
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
+ ![codecov.io](https://codecov.io/gh/Ljzd-PRO/KToolBox/graphs/sunburst.svg?token=5XK9CYQHQN)
242
+
203
243
  ## License
204
244
 
205
- KToolBox is licensed under GPLv3.
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-GPLv3-blue" alt="GPLv3"/>
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/commits/master">
20
- <img src="https://img.shields.io/github/last-commit/Ljzd-PRO/KToolBox/master" alt="Last Commit"/>
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-Win%20|%20Linux%20|%20macOS-blue" alt="Platform Win | Linux | macOS"/>
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
- ![Preview](./static/preview-1.png)
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
- > **Warning**
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
+ ![codecov.io](https://codecov.io/gh/Ljzd-PRO/KToolBox/graphs/sunburst.svg?token=5XK9CYQHQN)
210
+
172
211
  ## License
173
212
 
174
- KToolBox is licensed under GPLv3.
213
+ KToolBox is licensed under MIT.
175
214
 
176
215
  Copyright © 2023 by Ljzd-PRO.
@@ -0,0 +1,4 @@
1
+ __title__ = "KToolBox"
2
+ # noinspection SpellCheckingInspection
3
+ __description__ = "A useful CLI tool for downloading posts in Kemono.party / .su"
4
+ __version__ = "0.2.1"
@@ -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
- if __name__ == "__main__":
9
- logger_init(std_level=logging.WARNING)
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
- jobs: List[Job] = []
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
- content_path = post_path / post_structure.content_filepath
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
- for attachment in post.attachments:
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
- jobs.append(
51
- Job(
52
- path=post_path,
53
- alt_filename=post.file.name,
54
- server_path=post.file.path,
55
- type=PostFileTypeEnum.File
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
- if content_path:
59
- if not content_path.parent.is_dir():
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, Union, Tuple
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(std_level: Union[str, int] = None, disable_stdout: bool = False):
107
+ def logger_init(cli_use: bool = False, disable_stdout: bool = False):
107
108
  """
108
109
  Initialize ``loguru`` logger
109
110
 
110
- :param std_level: Standard output log filter level
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 std_level is not None:
116
+ elif cli_use:
116
117
  logger.remove()
117
118
  logger.add(
118
119
  sys.stderr,
119
- level=std_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.0-dev"
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 = "^0.22.1"
54
+ datamodel-code-generator = ">=0.22.1,<0.24.0"
50
55
 
51
56
  #[tool.poetry.group.uvloop]
52
57
  #optional = true