Skip to content

Bump to v0.11.0 #187

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 29 commits into from
Nov 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
983d186
build(deps): bump fire from 0.6.0 to 0.7.0
dependabot[bot] Oct 28, 2024
cc3a829
Merge pull request #175 from Ljzd-PRO/dependabot/pip/devel/fire-0.7.0
Ljzd-PRO Oct 29, 2024
e1d22c4
build(deps): bump tenacity from 8.5.0 to 9.0.0
dependabot[bot] Oct 29, 2024
2b0e207
Merge pull request #178 from Ljzd-PRO/dependabot/pip/devel/tenacity-9…
Ljzd-PRO Oct 30, 2024
5e763e7
build(deps-dev): bump pyinstaller from 6.10.0 to 6.11.0
dependabot[bot] Oct 30, 2024
646b5e8
Merge pull request #177 from Ljzd-PRO/dependabot/pip/devel/pyinstalle…
Ljzd-PRO Oct 30, 2024
ce9b5dd
build(deps-dev): bump pytest from 8.2.1 to 8.3.3
dependabot[bot] Oct 30, 2024
5c07483
Merge pull request #174 from Ljzd-PRO/dependabot/pip/devel/pytest-8.3.3
Ljzd-PRO Oct 31, 2024
dae3e61
build(deps): bump pydantic-settings from 2.6.0 to 2.6.1
dependabot[bot] Nov 4, 2024
2019010
feat: search creator that have similar name
Ljzd-PRO Nov 8, 2024
4b38efc
refactor: use `datetime` for `Creator.indexed` and `Creator.updated`
Ljzd-PRO Nov 8, 2024
94e259d
docs: update CHANGELOG.md
Ljzd-PRO Nov 8, 2024
729d7e6
build: bump version
Ljzd-PRO Nov 8, 2024
a52bcff
build(deps): bump tqdm from 4.66.5 to 4.67.0
dependabot[bot] Nov 11, 2024
24aaab3
build(deps-dev): bump mkdocs-material from 9.5.42 to 9.5.44
dependabot[bot] Nov 11, 2024
55c9585
build(deps-dev): bump pyinstaller from 6.11.0 to 6.11.1
dependabot[bot] Nov 11, 2024
ef6b641
docs: Update TODO
Ljzd-PRO Nov 12, 2024
8eafe06
docs: Update TODO
Ljzd-PRO Nov 12, 2024
415b1f7
perf: use shared httpx client
Ljzd-PRO Nov 18, 2024
9d3b317
docs: update CHANGELOG.md for HTTPX client improvements
Ljzd-PRO Nov 18, 2024
f5925eb
build: Add BSD-3-Clause license to pyproject.toml
Ljzd-PRO Nov 18, 2024
b584361
Merge branch 'master' into devel
Ljzd-PRO Nov 18, 2024
49a8abd
docs: Update feature descriptions
Ljzd-PRO Nov 18, 2024
ca91469
build: Update descriptions
Ljzd-PRO Nov 18, 2024
ab65700
Merge pull request #186 from Ljzd-PRO/dependabot/pip/devel/pyinstalle…
Ljzd-PRO Nov 18, 2024
a912bc2
Merge pull request #185 from Ljzd-PRO/dependabot/pip/devel/mkdocs-mat…
Ljzd-PRO Nov 18, 2024
1a5e058
Merge pull request #184 from Ljzd-PRO/dependabot/pip/devel/tqdm-4.67.0
Ljzd-PRO Nov 18, 2024
a9cbdb7
Merge pull request #181 from Ljzd-PRO/dependabot/pip/devel/pydantic-s…
Ljzd-PRO Nov 18, 2024
fc37cf0
Merge remote-tracking branch 'origin/devel' into devel
Ljzd-PRO Nov 18, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 8 additions & 11 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,21 @@

### 💡 Feature

- Added a **graphical configuration editor**, making configuration editing simple and convenient.
- Run `ktoolbox config-editor`
- Added a command to generate a complete sample configuration file.
- Run `ktoolbox example-env`
- **Python** versions below 3.8.1 (**<3.8.1**) are no longer supported
- The `search-creator` command will include search results with similar names.
- For example, the search parameter `--name abc` might return author information such as: `abc, abcd, hi-abc`
- Share an HTTPX client to reuse underlying TCP connections through an HTTP connection pool when calling APIs and downloading,
**significantly improving query and download speeds as well as connection stability**

[//]: # (### 🪲 Fix)

- - -

### 💡 新特性

- 新增 **图形化配置编辑器**,配置编辑将变得简单方便
- 运行 `ktoolbox config-editor`
- 新增命令可生成完整的配置文件样例
- 运行 `ktoolbox example-env`
- **Python** 3.8.1 以下 (**<3.8.1**) 的版本不再受支持
- search-creator 搜索作者的命令将包含那些名字相近的搜索结果
- 如搜索参数 `--name abc` 可能得到如下作者信息:`abc, abcd, hi-abc`
- 共享 HTTPX 客户端,调用 API 和下载时将通过 HTTP 连接池重用底层 TCP 连接,**显著提升查询、下载速度和连接稳定性**

[//]: # (### 🪲 修复)

**Full Changelog**: https://github.com/Ljzd-PRO/KToolBox/compare/v0.9.0...v0.10.0
**Full Changelog**: https://github.com/Ljzd-PRO/KToolBox/compare/v0.10.0...v0.11.0
21 changes: 11 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

<p align="center">
KToolBox is a useful CLI tool for downloading posts content in
<a href="https://kemono.su/">Kemono.party / Kemono.su</a>
<a href="https://kemono.su/">Kemono.su / Kemono.party</a>
</p>

<p align="center">
Expand Down Expand Up @@ -51,19 +51,20 @@

## Features

- Support for **multi-thread** downloads (technically, coroutine)
- **Retry** after download failed
- Ability to download individual post as well as **all post** by a specified creator/artist
- **Update downloaded** creator/artist directories to the latest status
- Customize the **structure** of downloaded post/creator **directories**
- Search for creators/artists and posts, and **export the results**
- **Cross-platform** support & **iOS shortcuts** available
- For Coomer.su / Coomer.party support, check document [Coomer](https://ktoolbox.readthedocs.io/latest/coomer/) for more.
- Supports concurrent downloads
- Automatically retries API calls and downloads after failures
- Allows downloading individual posts or **all posts** of a specified artist
- Can **update downloaded** artist directories to the latest state
- Supports customizable **file and directory naming formats** and **directory structures** for downloaded posts/artists
- Enables excluding **specified file formats** or downloading only specified formats
- Allows searching for artists and posts, with options to export results
- Compatible with all platforms, with iOS shortcuts provided
- For support related to _Coomer.su / Coomer.party_, please refer to the documentation: [Coomer](https://ktoolbox.readthedocs.io/latest/coomer/)

## Dev Plan

- [ ] GUI
- [x] Add uvloop support for Unix platform
- [ ] Discord support

## Tutorial

Expand Down
17 changes: 9 additions & 8 deletions README_zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

<p align="center">
KToolBox 是一个用于下载
<a href="https://kemono.su/">Kemono.party / Kemono.su</a>
<a href="https://kemono.su/">Kemono.su / Kemono.party</a>
中作品内容的实用命令行工具
</p>

Expand Down Expand Up @@ -52,19 +52,20 @@

## 功能

- 支持 **多线程** 下载(技术上是协程)
- 下载失败后进行 **重试**
- 支持并发下载
- API 调用和下载失败后 **自动重试**
- 支持下载单个作品以及指定的画师的 **所有作品**
- 可 **更新已下载** 的画师目录至最新状态
- 可自定义下载的作品/画师 **目录结构**
- 可搜索画师和作品,并 **导出结果**
- 支持全平台,并提供 **iOS 快捷指令**
- 对于 Coomer.su / Coomer.party 的支持,请查看文档 [Coomer](https://ktoolbox.readthedocs.io/latest/zh/coomer/)。
- 支持自定义下载的作品/画师 **文件和目录名格式**、**目录结构**
- 支持排除 **指定格式** 的文件或仅下载指定格式的文件
- 可搜索画师和作品,并导出结果
- 支持全平台,并提供 iOS 快捷指令
- 对于 Coomer.su / Coomer.party 的支持,请查看文档 [Coomer](https://ktoolbox.readthedocs.io/latest/zh/coomer/)

## 开发计划

- [ ] GUI
- [x] 对 Unix 平台增加 uvloop 支持
- [ ] Discord 下载支持

## 使用方法

Expand Down
16 changes: 9 additions & 7 deletions docs/en/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,15 @@

## Features

- Support for **multi-thread** downloads (technically, coroutine)
- **Retry** after download failed
- Ability to download individual post as well as **all post** by a specified creator/artist
- **Update downloaded** creator/artist directories to the latest status
- Customize the **structure** of downloaded post/creator **directories**
- Search for creators/artists and posts, and **export the results**
- **Cross-platform** support & **iOS shortcuts** available
- Supports concurrent downloads
- Automatically retries API calls and downloads after failures
- Allows downloading individual posts or **all posts** of a specified artist
- Can **update downloaded** artist directories to the latest state
- Supports customizable **file and directory naming formats** and **directory structures** for downloaded posts/artists
- Enables excluding **specified file formats** or downloading only specified formats
- Allows searching for artists and posts, with options to export results
- Compatible with all platforms, with iOS shortcuts provided
- For support related to _Coomer.su / Coomer.party_, please refer to the documentation: [Coomer](https://ktoolbox.readthedocs.io/latest/coomer/)

## Tutorial

Expand Down
14 changes: 8 additions & 6 deletions docs/zh/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

<p style="text-align: center">
KToolBox 是一个用于下载
<a href="https://kemono.su/">Kemono.party / Kemono.su</a>
<a href="https://kemono.su/">Kemono.su / Kemono.party</a>
中作品内容的实用命令行工具
</p>

Expand Down Expand Up @@ -40,13 +40,15 @@

## 功能

- 支持 **多线程** 下载(技术上是协程)
- 下载失败后进行 **重试**
- 支持并发下载
- API 调用和下载失败后 **自动重试**
- 支持下载单个作品以及指定的画师的 **所有作品**
- 可 **更新已下载** 的画师目录至最新状态
- 可自定义下载的作品/画师 **目录结构**
- 可搜索画师和作品,并 **导出结果**
- 支持全平台,并提供 **iOS 快捷指令**
- 支持自定义下载的作品/画师 **文件和目录名格式**、**目录结构**
- 支持排除 **指定格式** 的文件或仅下载指定格式的文件
- 可搜索画师和作品,并导出结果
- 支持全平台,并提供 iOS 快捷指令
- 对于 Coomer.su / Coomer.party 的支持,请查看文档 [Coomer](https://ktoolbox.readthedocs.io/latest/zh/coomer/)

## 使用方法

Expand Down
2 changes: 1 addition & 1 deletion ktoolbox/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__title__ = "KToolBox"
# noinspection SpellCheckingInspection
__description__ = "A useful CLI tool for downloading posts in Kemono.party / .su"
__version__ = "v0.10.0"
__version__ = "v0.11.0"
32 changes: 16 additions & 16 deletions ktoolbox/action/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,23 @@ async def search_creator(id: str = None, name: str = None, service: str = None)
:param service: The service for the creator
"""

async def inner(**kwargs):
def filter_func(creator: Creator):
"""Filter creators with attributes"""
for key, value in kwargs.items():
if value is None:
continue
elif creator.__getattribute__(key) != value:
return False
return True

ret = await get_creators()
if not ret:
return ret
creators = ret.data
return ActionRet(data=iter(filter(filter_func, creators)))
def filter_func(creator: Creator):
"""Filter creators with attributes"""
if id is not None and creator.id != id:
return False
if name is not None and name not in creator.name:
return False
if service is not None and creator.service != service:
return False
return True

return await inner(id=id, name=name, service=service)
ret = await get_creators()
if not ret:
base_ret = BaseRet.model_validate(ret.model_dump())
base_ret.data = iter([])
return base_ret
creators = ret.data
return ActionRet(data=iter(filter(filter_func, creators)))


# noinspection PyShadowingBuiltins
Expand Down
16 changes: 8 additions & 8 deletions ktoolbox/api/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ class BaseAPI(ABC, Generic[_T]):
path: str = "/"
method: Literal["get", "post"]
extra_validator: Optional[Callable[[str], BaseModel]] = None
client = httpx.AsyncClient(verify=config.ssl_verify)

Response = BaseModel
"""API response model"""
Expand Down Expand Up @@ -110,14 +111,13 @@ async def request(cls, path: str = None, **kwargs) -> APIRet[_T]:
url_parts = [config.api.scheme, config.api.netloc, f"{config.api.path}{path}", '', '', '']
url = str(urlunparse(url_parts))
try:
async with httpx.AsyncClient(verify=config.ssl_verify) as client:
res = await client.request(
method=cls.method,
url=url,
timeout=config.api.timeout,
follow_redirects=True,
**kwargs
)
res = await cls.client.request(
method=cls.method,
url=url,
timeout=config.api.timeout,
follow_redirects=True,
**kwargs
)
except Exception as e:
return APIRet(
code=RetCodeEnum.NetWorkError,
Expand Down
6 changes: 4 additions & 2 deletions ktoolbox/api/model/creator.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from datetime import datetime

from pydantic import BaseModel

__all__ = ["Creator"]
Expand All @@ -10,11 +12,11 @@ class Creator(BaseModel):
"""The number of times this creator has been favorited"""
id: str
"""The ID of the creator"""
indexed: float
indexed: datetime
"""Timestamp when the creator was indexed, Unix time as integer"""
name: str
"""The name of the creator"""
service: str
"""The service for the creator"""
updated: float
updated: datetime
"""Timestamp when the creator was last updated, Unix time as integer"""
96 changes: 48 additions & 48 deletions ktoolbox/downloader/downloader.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class Downloader:
"""
:ivar _save_filename: The actual filename for saving.
"""
client = httpx.AsyncClient(verify=config.ssl_verify)

def __init__(
self,
Expand Down Expand Up @@ -172,56 +173,55 @@ async def run(

tqdm_class: Type[std_tqdm] = tqdm_class or tqdm.asyncio.tqdm
async with self._lock:
async with httpx.AsyncClient(verify=config.ssl_verify) as client:
async with client.stream(
method="GET",
url=self._url,
follow_redirects=True,
timeout=config.downloader.timeout
) as res: # type: httpx.Response
if res.status_code != httpx.codes.OK:
return DownloaderRet(
code=RetCodeEnum.GeneralFailure,
message=generate_msg(
"Download failed",
status_code=res.status_code,
filename=save_filepath
)
async with self.client.stream(
method="GET",
url=self._url,
follow_redirects=True,
timeout=config.downloader.timeout
) as res: # type: httpx.Response
if res.status_code != httpx.codes.OK:
return DownloaderRet(
code=RetCodeEnum.GeneralFailure,
message=generate_msg(
"Download failed",
status_code=res.status_code,
filename=save_filepath
)

# Get filename for saving and check if file exists (Second-time duplicate file check)
# Priority order can be referenced from the constructor's documentation
self._save_filename = self._designated_filename or sanitize_filename(
filename_from_headers(res.headers)
) or server_path_filename
save_filepath = self._path / self._save_filename
file_existed, ret_msg = duplicate_file_check(save_filepath, bucket_file_path)
if file_existed:
return DownloaderRet(
code=RetCodeEnum.FileExisted,
message=generate_msg(
ret_msg,
path=save_filepath
)
)

# Download
temp_filepath = Path(f"{save_filepath}.{config.downloader.temp_suffix}")
total_size = int(length_str) if (length_str := res.headers.get("Content-Length")) else None
async with aiofiles.open(str(temp_filepath), "wb", self._buffer_size) as f:
chunk_iterator = res.aiter_bytes(self._chunk_size)
t = tqdm_class(
desc=self._save_filename,
total=total_size,
disable=not progress,
unit="B",
unit_scale=True
)

# Get filename for saving and check if file exists (Second-time duplicate file check)
# Priority order can be referenced from the constructor's documentation
self._save_filename = self._designated_filename or sanitize_filename(
filename_from_headers(res.headers)
) or server_path_filename
save_filepath = self._path / self._save_filename
file_existed, ret_msg = duplicate_file_check(save_filepath, bucket_file_path)
if file_existed:
return DownloaderRet(
code=RetCodeEnum.FileExisted,
message=generate_msg(
ret_msg,
path=save_filepath
)
async for chunk in chunk_iterator:
if self._stop:
raise CancelledError
await f.write(chunk)
t.update(len(chunk)) # Update progress bar
)

# Download
temp_filepath = Path(f"{save_filepath}.{config.downloader.temp_suffix}")
total_size = int(length_str) if (length_str := res.headers.get("Content-Length")) else None
async with aiofiles.open(str(temp_filepath), "wb", self._buffer_size) as f:
chunk_iterator = res.aiter_bytes(self._chunk_size)
t = tqdm_class(
desc=self._save_filename,
total=total_size,
disable=not progress,
unit="B",
unit_scale=True
)
async for chunk in chunk_iterator:
if self._stop:
raise CancelledError
await f.write(chunk)
t.update(len(chunk)) # Update progress bar

# Download finished
if config.downloader.use_bucket:
Expand Down
Loading
Loading