Skip to content

Fix/p0 p3 code review fixes - #552

Closed
lujih wants to merge 7 commits into
hect0x7:masterfrom
lujih:fix/P0-P3-code-review-fixes
Closed

Fix/p0 p3 code review fixes#552
lujih wants to merge 7 commits into
hect0x7:masterfrom
lujih:fix/P0-P3-code-review-fixes

Conversation

@lujih

@lujih lujih commented Jul 22, 2026

Copy link
Copy Markdown

Summary by CodeRabbit

  • New Features

    • Added optional dependency support for extended archive, image, conversion, and system utilities.
    • Added configurable image decoding worker settings.
    • Added album URL formatting support.
  • Bug Fixes

    • Improved download handling, callbacks, and concurrent cache access.
    • Added safeguards against unsafe file paths and deletions.
    • Fixed empty-download handling and text truncation formatting.
    • Improved command execution reliability and cleanup after failures.
  • Documentation

    • Clarified the encryption algorithm description without a version-specific suffix.

lujih and others added 7 commits June 26, 2026 19:10
- _download_single_image: os.path.exists -> file_exists (from common)
- _decode_pool: decode_worker 默认 min(4, cpu_count or 1),防无上限创建线程
- jm_config: DEFAULT_OPTION_DICT['download']['threading'] 新增 decode_worker 字段
- jm_config: option_default_dict() 中 decode_worker 默认 min(4, cpu_count or 1)
- jm_async_downloader: decode_worker 从 option.download.threading.decode_worker 回退
- 移除 jm_async_downloader 中已无用的 import os
- P0-1: zip_with_password 使用 shlex.quote() 防止 shell 命令注入
- P0-2: execute_cmd 从 os.system 改为 subprocess.run,添加注入警告文档
- P0-3: execute_deletion 添加路径包含校验,只删除 base_dir 范围内的文件
- P0-4: decide_image_save_dir 添加 realpath 校验,防止路径遍历逃逸 base_dir
v2.7.2: 更新JM内置域名; 废弃失效的 GitHub 域名抓取实现; 完善 async API,新增 categories_fil…
docs: 新增used by,star history图,更新 repo card 链接。 (#550)
P0 (安全/稳定):
- jm_plugin.py: execute_cmd/execute_multi_line_cmd 支持列表参数避免 shell 注入; zip_with_password 改用 subprocess 列表形式 + cwd
- jm_client_impl.py: FutureWrapper.result() 用 try/finally 确保异常时 done 标志被设置
- jm_option.py: call_all_plugin 捕获 KeyboardInterrupt/SystemExit 改为直接 re-raise
- jm_exception.py + jm_config.py: 将 format_album_url 下沉到 jm_config 消除循环依赖
- jm_client_impl.py: JSON 检查限制前 1024 字符; resp.request.url 加防御性 getattr

P1 (可维护性):
- jm_toolkit.py: limit_text 补上缺失的闭合括号
- jm_client_impl.py + jm_config.py: SCRAMBLE_CACHE 添加 threading.Lock 双重检查锁定

P2 (代码整洁):
- api.py: 提取 _download_and_return/_download_async_and_return 消除 sync/async 重复
- pyproject.toml: 添加 curl-cffi/pillow/pycryptodome/pyyaml 版本下限; 声明可选依赖

P3 (小改进):
- jm_client_impl.py: 删除空的 get_username_from_cookies 方法
- cli.py: 空参数时打印使用提示
- README.md: 移除过时版本号 (1.6.3)
- jm_option.py: float 版本比较改为 tuple 比较
@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR centralizes synchronous and asynchronous download handling, adds configuration defaults and cache synchronization, strengthens path and command safety, updates shared URL and client utilities, improves CLI handling, and adjusts project dependencies and documentation.

Changes

Core runtime updates

Layer / File(s) Summary
Shared download orchestration
src/jmcomic/api.py
Album and photo download APIs delegate common synchronous and asynchronous lifecycle handling to shared helpers.
Configuration and downloader state
src/jmcomic/jm_config.py, src/jmcomic/jm_async_downloader.py, src/jmcomic/jm_client_impl.py
Adds decode-worker defaults, scramble-cache locking, shared file-existence checks, bounded response inspection, and future cleanup.
Path and command execution safeguards
src/jmcomic/jm_option.py, src/jmcomic/jm_plugin.py
Adds base-directory checks, refines plugin exception handling, and uses subprocess.run for command execution and password-protected archives.
Shared helpers and CLI behavior
src/jmcomic/jm_config.py, src/jmcomic/jm_exception.py, src/jmcomic/jm_toolkit.py, src/jmcomic/cli.py, src/jmcomic/jm_option.py
Centralizes album URL formatting, fixes text truncation, changes option version comparison, and handles empty CLI input.
Project metadata and documentation
pyproject.toml, README.md
Adds dependency minimum versions and an optional dependency group, and removes a version suffix from the README algorithm description.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant DownloadAPI
  participant Downloader
  participant Callback
  Caller->>DownloadAPI: request album or photo download
  DownloadAPI->>Downloader: create context and add features
  DownloadAPI->>Downloader: download entity
  DownloadAPI->>Callback: invoke optional callback
  DownloadAPI->>Caller: return DownloadResult
Loading

Possibly related PRs

Suggested reviewers: hect0x7

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 47.83% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is generic and does not clearly describe the main change in the PR. Use a concise title that names the primary fix, such as the key security or refactor changes included in this PR.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/jmcomic/api.py`:
- Around line 81-88: Update the docstrings around the batch-download API and the
additional referenced sections to avoid RUF002 violations from fullwidth
punctuation. Replace the newly introduced fullwidth punctuation with ASCII
punctuation while preserving the Chinese documentation meaning; do not alter the
API behavior.

In `@src/jmcomic/cli.py`:
- Around line 105-107: Move the empty-ID guard in main() to immediately after
parse_arg() returns, before constructing/loading the option or invoking
after_init plugins. Preserve the existing message and early return, using
album_id_list and photo_id_list from the parsed arguments.

In `@src/jmcomic/jm_client_impl.py`:
- Around line 1107-1113: Move the self.after_done_callback() invocation into the
existing finally block surrounding self.future.result() in the wrapper
completion logic. Ensure it runs after setting self.done and clearing
self.future regardless of whether result() succeeds or raises, so failed cached
wrappers are removed consistently.

In `@src/jmcomic/jm_option.py`:
- Around line 260-266: Replace the separator-based containment check in the
image-directory validation around resolved and base in src/jmcomic/jm_option.py
lines 260-266 with an os.path.commonpath([base, resolved]) comparison against
base. Apply the same containment change in the deletion-target guard in
src/jmcomic/jm_plugin.py lines 84-88, preserving rejection or skipping of paths
outside the base directory while allowing valid paths under filesystem or drive
roots.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 194b4b68-e49d-4922-87c4-a0c26766d8ef

📥 Commits

Reviewing files that changed from the base of the PR and between 2d03218 and 14550fd.

📒 Files selected for processing (11)
  • README.md
  • pyproject.toml
  • src/jmcomic/api.py
  • src/jmcomic/cli.py
  • src/jmcomic/jm_async_downloader.py
  • src/jmcomic/jm_client_impl.py
  • src/jmcomic/jm_config.py
  • src/jmcomic/jm_exception.py
  • src/jmcomic/jm_option.py
  • src/jmcomic/jm_plugin.py
  • src/jmcomic/jm_toolkit.py

Comment thread src/jmcomic/api.py
Comment on lines +81 to +88
当jm_album_id不是str或int时,视为批量下载
:param jm_album_id: 本子的禁漫车号
:param option: 下载选项
:param downloader: 下载器类
:param callback: 返回值回调函数,可以拿到 album 和 downloader
:param check_exception: 是否检查异常, 如果为True,会检查downloader是否有下载异常,并上抛PartialDownloadFailedException
:param extra: 下载特性(Feature),下载时动态挂载的附加行为上下文。会自动根据上下文(如 album/photo 来源)自适应参数行为。支持单个 Feature、FeatureChain、或列表
:return: 对于的本子实体类,下载器(如果是上述的批量情况,返回值为download_batch的返回值)
:param check_exception: 是否检查异常
:param extra: 下载特性(Feature)
:return: DownloadResult (如果是批量情况返回 BatchResult)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Avoid introducing RUF002 violations in these docstrings.

The newly added fullwidth punctuation is flagged by Ruff and can fail lint when RUF002 is enabled. Replace it with ASCII punctuation or deliberately suppress/configure this rule for Chinese documentation.

Also applies to: 107-114, 190-192, 216-218

🧰 Tools
🪛 Ruff (0.15.21)

[warning] 81-81: Docstring contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF002)


[warning] 85-85: Docstring contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF002)


[warning] 87-87: Docstring contains ambiguous (FULLWIDTH LEFT PARENTHESIS). Did you mean ( (LEFT PARENTHESIS)?

(RUF002)


[warning] 87-87: Docstring contains ambiguous (FULLWIDTH RIGHT PARENTHESIS). Did you mean ) (RIGHT PARENTHESIS)?

(RUF002)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/jmcomic/api.py` around lines 81 - 88, Update the docstrings around the
batch-download API and the additional referenced sections to avoid RUF002
violations from fullwidth punctuation. Replace the newly introduced fullwidth
punctuation with ASCII punctuation while preserving the Chinese documentation
meaning; do not alter the API behavior.

Source: Linters/SAST tools

Comment thread src/jmcomic/cli.py
Comment on lines +105 to +107
if len(self.album_id_list) == 0 and len(self.photo_id_list) == 0:
print('未指定任何 id,请提供 album 或 photo 的 id,例如: jmcomic 123')
return

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Exit before constructing the option.

This guard runs after main() loads the option and invokes after_init plugins. Move it immediately after parse_arg() so jmcomic with no IDs cannot trigger option/plugin side effects or failures.

🧰 Tools
🪛 Ruff (0.15.21)

[warning] 106-106: String contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF001)


[warning] 106-106: String contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF001)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/jmcomic/cli.py` around lines 105 - 107, Move the empty-ID guard in main()
to immediately after parse_arg() returns, before constructing/loading the option
or invoking after_init plugins. Preserve the existing message and early return,
using album_id_list and photo_id_list from the parsed arguments.

Comment on lines +1107 to 1113
try:
result = self.future.result()
self._result = result
finally:
self.done = True
self.future = None # help gc
self.after_done_callback()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Run the cache-removal callback in the finally block.

If future.result() raises, Line 1113 is skipped. The failed wrapper stays cached as done=True, so later callers return None instead of retrying or receiving the failure.

Proposed fix
         def result(self):
             if not self.done:
                 try:
                     result = self.future.result()
                     self._result = result
                 finally:
                     self.done = True
                     self.future = None  # help gc
-                self.after_done_callback()
+                    self.after_done_callback()
 
             return self._result
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try:
result = self.future.result()
self._result = result
finally:
self.done = True
self.future = None # help gc
self.after_done_callback()
try:
result = self.future.result()
self._result = result
finally:
self.done = True
self.future = None # help gc
self.after_done_callback()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/jmcomic/jm_client_impl.py` around lines 1107 - 1113, Move the
self.after_done_callback() invocation into the existing finally block
surrounding self.future.result() in the wrapper completion logic. Ensure it runs
after setting self.done and clearing self.future regardless of whether result()
succeeds or raises, so failed cached wrappers are removed consistently.

Comment thread src/jmcomic/jm_option.py
Comment on lines +260 to +266
# 路径遍历防护:确保解析后的路径仍在 base_dir 范围内
resolved = os.path.realpath(save_dir)
base = os.path.realpath(self.dir_rule.base_dir)
if not resolved.startswith(base + os.sep) and resolved != base:
raise JmcomicException(
f'路径安全校验失败: {save_dir} 不在 base_dir ({base}) 范围内'
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Handle filesystem-root base directories in both containment checks.

base + os.sep becomes // for / (and similarly doubles separators for drive roots), so valid paths below a root base are rejected or skipped. Use os.path.commonpath() for containment instead.

  • src/jmcomic/jm_option.py#L260-L266: compare os.path.commonpath([base, resolved]) with base before rejecting an image directory.
  • src/jmcomic/jm_plugin.py#L84-L88: apply the same comparison before skipping a deletion target.
🧰 Tools
🪛 Ruff (0.15.21)

[warning] 260-260: Comment contains ambiguous (FULLWIDTH COLON). Did you mean : (COLON)?

(RUF003)

📍 Affects 2 files
  • src/jmcomic/jm_option.py#L260-L266 (this comment)
  • src/jmcomic/jm_plugin.py#L84-L88
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/jmcomic/jm_option.py` around lines 260 - 266, Replace the separator-based
containment check in the image-directory validation around resolved and base in
src/jmcomic/jm_option.py lines 260-266 with an os.path.commonpath([base,
resolved]) comparison against base. Apply the same containment change in the
deletion-target guard in src/jmcomic/jm_plugin.py lines 84-88, preserving
rejection or skipping of paths outside the base directory while allowing valid
paths under filesystem or drive roots.

@hect0x7 hect0x7 closed this Jul 22, 2026
@hect0x7

hect0x7 commented Jul 22, 2026

Copy link
Copy Markdown
Owner

先去读 Contributing 文档,别把一堆ai生成的垃圾pr上来

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants