Skip to content

refactor - #8

Merged
LevelDownRefine merged 1 commit into
mainfrom
lvdown/refator
Jul 13, 2026
Merged

LevelDownRefine merged 1 commit into
mainfrom
lvdown/refator

Conversation

@LevelDownRefine

@LevelDownRefine LevelDownRefine commented Jul 13, 2026 •

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features

    • Added a graphical configuration editor for script locations and weekly schedules.
    • Added support for configuring dungeon selections across multiple supported games.
    • Added reusable automation presets, including movement and training scripts.
    • Added mute, unmute, and automatic shutdown actions.
    • Added centralized configuration for games, dungeons, notifications, timeouts, and execution behavior.
  • Improvements

    • Improved launcher setup and portability across installation locations.
    • Moved application state and configuration files into the configuration directory.
    • Updated startup instructions and demo asset references.
  • Bug Fixes

    • Improved test execution and Python import resolution.
    • Corrected configuration path handling for bundled scripts and resources.

@coderabbitai

coderabbitai Bot commented Jul 13, 2026 •

Copy link
Copy Markdown

Review Change Stack

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 64608388-8a3b-4a2f-96c6-c3fbbcce42d5

📥 Commits

Reviewing files that changed from the base of the PR and between 0c2014d and 365847e.

⛔ Files ignored due to path filters (1)
  • assets/demo.png is excluded by !**/*.png
📒 Files selected for processing (25)
  • .github/workflows/ci.yml
  • README.md
  • config/BGI_User/KeyMouseScript/120s随机游走.json
  • config/BGI_User/KeyMouseScript/木桩.json
  • config/BGI_User/ScriptGroup/千星.json
  • config/BGI_User/ScriptGroup/锄地.json
  • config/config.yml
  • config/dungeon_list.yml
  • config/weekly_timeouts.yml
  • env.bat
  • launcher.bat
  • src/config/__init__.py
  • src/config/bgi.py
  • src/config/onedrag_ui.py
  • src/config/onedragon.py
  • src/dungeon_adapter.py
  • src/gui_launcher.py
  • src/python_script/mute.py
  • src/python_script/shutdown.py
  • src/python_script/unmute.py
  • src/utils.py
  • tests/test_bgi.py
  • tests/test_onedrag_ui.py
  • tests/test_onedragon.py
  • tests/test_utils.py

📝 Walkthrough

Walkthrough

The PR relocates configuration under config/, updates launcher and CI paths, adds configuration and BetterGI management tools, introduces game-specific dungeon configuration updates, and adds mute, unmute, shutdown, and automation configuration files.

Changes

Configuration and runtime integration

Layer / File(s) Summary
Configuration paths and data
config/config.yml, config/dungeon_list.yml, config/weekly_timeouts.yml, src/utils.py, tests/test_utils.py
Configuration paths now resolve under the project config/ directory, with new script, dungeon, and weekly timeout data.
Launcher bootstrap and entrypoints
.github/workflows/ci.yml, README.md, env.bat, launcher.bat, src/onedragon.py, src/gui_launcher.py, tests/test_onedragon.py
Launch, documentation, CI, script-copying, and GUI state paths use the src/ and config/ layout.
Configuration UI and BetterGI provisioning
src/config/onedrag_ui.py, src/config/bgi.py, config/BGI_User/*, tests/test_bgi.py, tests/test_onedrag_ui.py
A Qt editor manages script paths and weekly timeouts, while BetterGI configurations are resolved and copied into the configured user directory.
Dungeon configuration adapter
src/dungeon_adapter.py
A unified adapter loads, updates, and saves game-specific dungeon and sequence settings across JSON and YAML configurations.
Automation utility scripts
src/python_script/mute.py, src/python_script/unmute.py, src/python_script/shutdown.py
New scripts control speaker mute state and provide a timed Windows shutdown window.

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

Sequence Diagram(s)

sequenceDiagram
  participant ConfigUI
  participant config.yml
  participant weekly_timeouts.yml
  ConfigUI->>config.yml: load and save script paths
  ConfigUI->>weekly_timeouts.yml: load and save daily timeout values
Loading
sequenceDiagram
  participant set_config
  participant config.yml
  participant GameConfig
  set_config->>config.yml: resolve script configuration path
  set_config->>GameConfig: load and update dungeon settings
  set_config->>GameConfig: save changed configuration
Loading
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% 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 too vague to describe the changeset; it doesn't indicate the main refactor or affected area. Use a concise, specific title that names the primary change, such as updating config paths and adding launcher/config tooling.
✅ 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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch lvdown/refator

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: 11

🧹 Nitpick comments (3)
src/python_script/mute.py (1)

1-16: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

set_mute is duplicated verbatim in unmute.py.

Both mute.py and unmute.py define an identical set_mute function; the only difference is the argument passed in __main__. Extract the shared function into a common module (e.g., audio_utils.py) and have both scripts import it.

♻️ Suggested refactor
# src/python_script/audio_utils.py
+from pycaw.pycaw import AudioUtilities
+
+def set_mute(mute_status):
+    devices = AudioUtilities.GetSpeakers()
+    interface = devices.EndpointVolume
+    interface.SetMute(mute_status, None)
+    print(f"系统已{'静音' if mute_status else '恢复声音'}")

# src/python_script/mute.py
-from pycaw.pycaw import AudioUtilities
-
-def set_mute(mute_status):
-    devices = AudioUtilities.GetSpeakers()
-    interface = devices.EndpointVolume
-    interface.SetMute(mute_status, None)
-    print(f"系统已{'静音' if mute_status else '恢复声音'}")
-
-if __name__ == "__main__":
-    set_mute(True)
+from audio_utils import set_mute
+
+if __name__ == "__main__":
+    set_mute(True)
🤖 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/python_script/mute.py` around lines 1 - 16, Extract the shared set_mute
function from mute.py and unmute.py into a common audio_utils.py module, then
import and reuse it from both scripts. Keep each script’s __main__ entry point
responsible only for passing its appropriate mute status.
src/python_script/shutdown.py (1)

64-70: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Add error handling around the shutdown subprocess call.

If subprocess.run fails (e.g., insufficient privileges, shutdown not in PATH), the exception is unhandled and the user gets no feedback. The static-analysis CWE-78 flag is a false positive here since the command list is hardcoded with no untrusted input, but a try/except around the call would improve the user experience.

🛡️ Suggested fix
 def do_shutdown(self):
     self.close()
-    subprocess.run(
-        ["shutdown", "/s", "/t", "0"],
-        shell=False,
-        creationflags=subprocess.CREATE_NO_WINDOW
-    )
+    try:
+        subprocess.run(
+            ["shutdown", "/s", "/t", "0"],
+            shell=False,
+            creationflags=subprocess.CREATE_NO_WINDOW,
+            check=True,
+        )
+    except subprocess.CalledProcessError:
+        print("关机命令执行失败,请手动关机。")
+    except FileNotFoundError:
+        print("未找到关机程序,请手动关机。")
🤖 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/python_script/shutdown.py` around lines 64 - 70, Wrap the subprocess.run
call in do_shutdown with focused exception handling for failures launching or
executing the hardcoded shutdown command, and provide clear user feedback when
it fails. Preserve the existing command arguments, close() call, and successful
shutdown behavior.

Source: Linters/SAST tools

src/utils.py (1)

36-39: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Replace fullwidth parentheses with halfwidth in docstring.

Ruff flags RUF002: the docstring at line 37 contains fullwidth ( and ) instead of halfwidth ( and ). This is a minor style consistency issue.

♻️ Proposed fix
 def get_root_dir() -> str:
     """
     获取项目根目录
-    :return: 项目根目录(src/ 的父目录)
+    :return: 项目根目录(src/ 的父目录)
     """
     return os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
🤖 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/utils.py` around lines 36 - 39, Update the docstring associated with the
project-root directory function to replace the fullwidth parentheses in its
return description with standard halfwidth parentheses, without changing the
function behavior.

Source: Linters/SAST tools

🤖 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 `@config/config.yml`:
- Line 24: Remove all user-specific absolute values from the script_path entries
in config.yml, including the WinSa username and D:\game_helper paths. Replace
them with configurable placeholders or environment-variable references, provide
a config.yml.example template if needed, and add the real config.yml to
.gitignore so machine-specific paths and usernames are not committed.

In `@src/config/bgi.py`:
- Around line 12-18: Update get_BGI_user_dir to handle an empty YAML result by
treating it as an empty configuration, and validate that the matching script has
a non-empty script_path before deriving its directory. Skip entries with missing
or empty paths so the function never calls os.path.dirname(None) or returns a
relative User path.

In `@src/config/onedrag_ui.py`:
- Around line 149-153: Update the weekly timeout parsing in the save flow around
weekly_timeouts so empty or whitespace-only line-edit values do not call int()
and crash. Validate each le.text() value before conversion, and provide the
existing user feedback or appropriate fallback for invalid empty fields while
preserving normal integer parsing and weekly_timeouts_map assignment.
- Around line 52-66: Update load_data so yaml.safe_load for the main
configuration always produces a mapping, defaulting empty or null content to {}
before assigning self.config_data. Initialize self.weekly_timeouts_map to {}
before the missing-file early return, while preserving the existing
weekly_timeouts.yml loading behavior when the file exists.

In `@src/dungeon_adapter.py`:
- Around line 315-318: Remove the unnecessary f-string prefixes from the four
static print statements in the Star Rail configuration update paths, including
the branches around the existing “config 无需更新” and “config 已更新” messages and the
corresponding statements near the second referenced location; keep their text
and control flow unchanged.
- Around line 178-180: Update the Zenless dungeon handling in the adapter,
including the corresponding logic around the second referenced location, so
unsupported Zenless adaptation returns False rather than being treated as a
successful no-op. Preserve the existing True return for valid handlers that
return None to indicate no write is needed.
- Around line 111-120: Update the configuration-writing function containing the
ext check to validate supported extensions before opening or creating files.
Serialize the JSON/YAML data to a temporary file in the target’s directory, then
atomically replace the destination with os.replace; ensure temporary-file
cleanup on failure and preserve the existing unsupported-format return behavior.
- Line 249: Update the changed-state assignment around update_task() and
update_sequence() so both functions are evaluated before combining their boolean
results, ensuring the selected sequence is applied after a dungeon change. Apply
the same change to the corresponding occurrence near the other reported
location.
- Around line 38-41: Update the YAML and JSON loader functions used by
set_config to catch OSError, yaml.YAMLError, and json.JSONDecodeError, returning
None for unreadable or invalid configuration. In _load_config_yml and the
corresponding JSON loader, validate that parsed content is a mapping and treat
missing or empty content as invalid. Ensure set_config handles a None loader
result and returns False without allowing these errors to escape.
- Around line 379-384: Update the task-enabling logic near task_map so only the
user-selected dungeon is re-enabled after all tasks are disabled. Remove the
unconditional task_config[task_map["土"]] assignment, while preserving the
selected dungeon assignment and ensuring selecting “土” still enables that
dungeon.

In `@src/python_script/mute.py`:
- Line 1: Add pycaw as a runtime dependency in the project configuration and
regenerate the lockfile so it is recorded in uv.lock. Keep the existing
AudioUtilities import in src/python_script/mute.py unchanged.

---

Nitpick comments:
In `@src/python_script/mute.py`:
- Around line 1-16: Extract the shared set_mute function from mute.py and
unmute.py into a common audio_utils.py module, then import and reuse it from
both scripts. Keep each script’s __main__ entry point responsible only for
passing its appropriate mute status.

In `@src/python_script/shutdown.py`:
- Around line 64-70: Wrap the subprocess.run call in do_shutdown with focused
exception handling for failures launching or executing the hardcoded shutdown
command, and provide clear user feedback when it fails. Preserve the existing
command arguments, close() call, and successful shutdown behavior.

In `@src/utils.py`:
- Around line 36-39: Update the docstring associated with the project-root
directory function to replace the fullwidth parentheses in its return
description with standard halfwidth parentheses, without changing the function
behavior.
🪄 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: 64608388-8a3b-4a2f-96c6-c3fbbcce42d5

📥 Commits

Reviewing files that changed from the base of the PR and between 0c2014d and 365847e.

⛔ Files ignored due to path filters (1)
  • assets/demo.png is excluded by !**/*.png
📒 Files selected for processing (25)
  • .github/workflows/ci.yml
  • README.md
  • config/BGI_User/KeyMouseScript/120s随机游走.json
  • config/BGI_User/KeyMouseScript/木桩.json
  • config/BGI_User/ScriptGroup/千星.json
  • config/BGI_User/ScriptGroup/锄地.json
  • config/config.yml
  • config/dungeon_list.yml
  • config/weekly_timeouts.yml
  • env.bat
  • launcher.bat
  • src/config/__init__.py
  • src/config/bgi.py
  • src/config/onedrag_ui.py
  • src/config/onedragon.py
  • src/dungeon_adapter.py
  • src/gui_launcher.py
  • src/python_script/mute.py
  • src/python_script/shutdown.py
  • src/python_script/unmute.py
  • src/utils.py
  • tests/test_bgi.py
  • tests/test_onedrag_ui.py
  • tests/test_onedragon.py
  • tests/test_utils.py

@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.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 11

🧹 Nitpick comments (3)
src/python_script/mute.py (1)

1-16: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

set_mute is duplicated verbatim in unmute.py.

Both mute.py and unmute.py define an identical set_mute function; the only difference is the argument passed in __main__. Extract the shared function into a common module (e.g., audio_utils.py) and have both scripts import it.

♻️ Suggested refactor
# src/python_script/audio_utils.py
+from pycaw.pycaw import AudioUtilities
+
+def set_mute(mute_status):
+    devices = AudioUtilities.GetSpeakers()
+    interface = devices.EndpointVolume
+    interface.SetMute(mute_status, None)
+    print(f"系统已{'静音' if mute_status else '恢复声音'}")

# src/python_script/mute.py
-from pycaw.pycaw import AudioUtilities
-
-def set_mute(mute_status):
-    devices = AudioUtilities.GetSpeakers()
-    interface = devices.EndpointVolume
-    interface.SetMute(mute_status, None)
-    print(f"系统已{'静音' if mute_status else '恢复声音'}")
-
-if __name__ == "__main__":
-    set_mute(True)
+from audio_utils import set_mute
+
+if __name__ == "__main__":
+    set_mute(True)
🤖 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/python_script/mute.py` around lines 1 - 16, Extract the shared set_mute
function from mute.py and unmute.py into a common audio_utils.py module, then
import and reuse it from both scripts. Keep each script’s __main__ entry point
responsible only for passing its appropriate mute status.
src/python_script/shutdown.py (1)

64-70: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Add error handling around the shutdown subprocess call.

If subprocess.run fails (e.g., insufficient privileges, shutdown not in PATH), the exception is unhandled and the user gets no feedback. The static-analysis CWE-78 flag is a false positive here since the command list is hardcoded with no untrusted input, but a try/except around the call would improve the user experience.

🛡️ Suggested fix
 def do_shutdown(self):
     self.close()
-    subprocess.run(
-        ["shutdown", "/s", "/t", "0"],
-        shell=False,
-        creationflags=subprocess.CREATE_NO_WINDOW
-    )
+    try:
+        subprocess.run(
+            ["shutdown", "/s", "/t", "0"],
+            shell=False,
+            creationflags=subprocess.CREATE_NO_WINDOW,
+            check=True,
+        )
+    except subprocess.CalledProcessError:
+        print("关机命令执行失败,请手动关机。")
+    except FileNotFoundError:
+        print("未找到关机程序,请手动关机。")
🤖 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/python_script/shutdown.py` around lines 64 - 70, Wrap the subprocess.run
call in do_shutdown with focused exception handling for failures launching or
executing the hardcoded shutdown command, and provide clear user feedback when
it fails. Preserve the existing command arguments, close() call, and successful
shutdown behavior.

Source: Linters/SAST tools

src/utils.py (1)

36-39: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Replace fullwidth parentheses with halfwidth in docstring.

Ruff flags RUF002: the docstring at line 37 contains fullwidth ( and ) instead of halfwidth ( and ). This is a minor style consistency issue.

♻️ Proposed fix
 def get_root_dir() -> str:
     """
     获取项目根目录
-    :return: 项目根目录(src/ 的父目录)
+    :return: 项目根目录(src/ 的父目录)
     """
     return os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
🤖 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/utils.py` around lines 36 - 39, Update the docstring associated with the
project-root directory function to replace the fullwidth parentheses in its
return description with standard halfwidth parentheses, without changing the
function behavior.

Source: Linters/SAST tools

🤖 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 `@config/config.yml`:
- Line 24: Remove all user-specific absolute values from the script_path entries
in config.yml, including the WinSa username and D:\game_helper paths. Replace
them with configurable placeholders or environment-variable references, provide
a config.yml.example template if needed, and add the real config.yml to
.gitignore so machine-specific paths and usernames are not committed.

In `@src/config/bgi.py`:
- Around line 12-18: Update get_BGI_user_dir to handle an empty YAML result by
treating it as an empty configuration, and validate that the matching script has
a non-empty script_path before deriving its directory. Skip entries with missing
or empty paths so the function never calls os.path.dirname(None) or returns a
relative User path.

In `@src/config/onedrag_ui.py`:
- Around line 149-153: Update the weekly timeout parsing in the save flow around
weekly_timeouts so empty or whitespace-only line-edit values do not call int()
and crash. Validate each le.text() value before conversion, and provide the
existing user feedback or appropriate fallback for invalid empty fields while
preserving normal integer parsing and weekly_timeouts_map assignment.
- Around line 52-66: Update load_data so yaml.safe_load for the main
configuration always produces a mapping, defaulting empty or null content to {}
before assigning self.config_data. Initialize self.weekly_timeouts_map to {}
before the missing-file early return, while preserving the existing
weekly_timeouts.yml loading behavior when the file exists.

In `@src/dungeon_adapter.py`:
- Around line 315-318: Remove the unnecessary f-string prefixes from the four
static print statements in the Star Rail configuration update paths, including
the branches around the existing “config 无需更新” and “config 已更新” messages and the
corresponding statements near the second referenced location; keep their text
and control flow unchanged.
- Around line 178-180: Update the Zenless dungeon handling in the adapter,
including the corresponding logic around the second referenced location, so
unsupported Zenless adaptation returns False rather than being treated as a
successful no-op. Preserve the existing True return for valid handlers that
return None to indicate no write is needed.
- Around line 111-120: Update the configuration-writing function containing the
ext check to validate supported extensions before opening or creating files.
Serialize the JSON/YAML data to a temporary file in the target’s directory, then
atomically replace the destination with os.replace; ensure temporary-file
cleanup on failure and preserve the existing unsupported-format return behavior.
- Line 249: Update the changed-state assignment around update_task() and
update_sequence() so both functions are evaluated before combining their boolean
results, ensuring the selected sequence is applied after a dungeon change. Apply
the same change to the corresponding occurrence near the other reported
location.
- Around line 38-41: Update the YAML and JSON loader functions used by
set_config to catch OSError, yaml.YAMLError, and json.JSONDecodeError, returning
None for unreadable or invalid configuration. In _load_config_yml and the
corresponding JSON loader, validate that parsed content is a mapping and treat
missing or empty content as invalid. Ensure set_config handles a None loader
result and returns False without allowing these errors to escape.
- Around line 379-384: Update the task-enabling logic near task_map so only the
user-selected dungeon is re-enabled after all tasks are disabled. Remove the
unconditional task_config[task_map["土"]] assignment, while preserving the
selected dungeon assignment and ensuring selecting “土” still enables that
dungeon.

In `@src/python_script/mute.py`:
- Line 1: Add pycaw as a runtime dependency in the project configuration and
regenerate the lockfile so it is recorded in uv.lock. Keep the existing
AudioUtilities import in src/python_script/mute.py unchanged.

---

Nitpick comments:
In `@src/python_script/mute.py`:
- Around line 1-16: Extract the shared set_mute function from mute.py and
unmute.py into a common audio_utils.py module, then import and reuse it from
both scripts. Keep each script’s __main__ entry point responsible only for
passing its appropriate mute status.

In `@src/python_script/shutdown.py`:
- Around line 64-70: Wrap the subprocess.run call in do_shutdown with focused
exception handling for failures launching or executing the hardcoded shutdown
command, and provide clear user feedback when it fails. Preserve the existing
command arguments, close() call, and successful shutdown behavior.

In `@src/utils.py`:
- Around line 36-39: Update the docstring associated with the project-root
directory function to replace the fullwidth parentheses in its return
description with standard halfwidth parentheses, without changing the function
behavior.
🪄 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: 64608388-8a3b-4a2f-96c6-c3fbbcce42d5

📥 Commits

Reviewing files that changed from the base of the PR and between 0c2014d and 365847e.

⛔ Files ignored due to path filters (1)
  • assets/demo.png is excluded by !**/*.png
📒 Files selected for processing (25)
  • .github/workflows/ci.yml
  • README.md
  • config/BGI_User/KeyMouseScript/120s随机游走.json
  • config/BGI_User/KeyMouseScript/木桩.json
  • config/BGI_User/ScriptGroup/千星.json
  • config/BGI_User/ScriptGroup/锄地.json
  • config/config.yml
  • config/dungeon_list.yml
  • config/weekly_timeouts.yml
  • env.bat
  • launcher.bat
  • src/config/__init__.py
  • src/config/bgi.py
  • src/config/onedrag_ui.py
  • src/config/onedragon.py
  • src/dungeon_adapter.py
  • src/gui_launcher.py
  • src/python_script/mute.py
  • src/python_script/shutdown.py
  • src/python_script/unmute.py
  • src/utils.py
  • tests/test_bgi.py
  • tests/test_onedrag_ui.py
  • tests/test_onedragon.py
  • tests/test_utils.py
🛑 Comments failed to post (11)
config/config.yml (1)

24-24: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Hardcoded user-specific paths with username leakage committed to repository.

Multiple script_path entries contain absolute Windows paths specific to one user's machine, including C:\Users\WinSa\... (lines 24, 64) and D:\game_helper\... (lines 44, 84, 105, 124, 144). The username WinSa is exposed in the repository, and these paths won't work for any other user who clones the repo.

Consider using a template file (e.g., config.yml.example) with placeholder paths, or environment variable substitution, and add the real config.yml to .gitignore.

Also applies to: 44-44, 64-64, 84-84, 105-105, 124-124, 144-144

🤖 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 `@config/config.yml` at line 24, Remove all user-specific absolute values from
the script_path entries in config.yml, including the WinSa username and
D:\game_helper paths. Replace them with configurable placeholders or
environment-variable references, provide a config.yml.example template if
needed, and add the real config.yml to .gitignore so machine-specific paths and
usernames are not committed.
src/config/bgi.py (1)

12-18: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

get_BGI_user_dir crashes on empty YAML or missing script_path.

Two issues:

  1. Line 13: yaml.safe_load(f) returns None for an empty config.yml, causing AttributeError on line 14 (config_data.get(...)).
  2. Line 17: script.get('script_path') returns None when the key is absent, causing TypeError in os.path.dirname(None). If script_path is an empty string, os.path.dirname('') returns '', and os.path.join('', 'User') resolves to a relative 'User' path — shutil.copytree would then write to ./User under the current working directory.
🛡️ Proposed fix
     with open(get_config_yml_path_under_root(), 'r', encoding='utf-8') as f:
-        config_data = yaml.safe_load(f)
+        config_data = yaml.safe_load(f) or {}
         script_list = config_data.get('script_list', [])
         for script in script_list:
             if script.get('display_name') == '原神':
-                BGI_dir = os.path.dirname(script.get('script_path'))
-                return os.path.join(BGI_dir, 'User')
+                script_path = script.get('script_path')
+                if script_path:
+                    BGI_dir = os.path.dirname(script_path)
+                    return os.path.join(BGI_dir, 'User')
     return None
📝 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.

    with open(get_config_yml_path_under_root(), 'r', encoding='utf-8') as f:
        config_data = yaml.safe_load(f) or {}
        script_list = config_data.get('script_list', [])
        for script in script_list:
            if script.get('display_name') == '原神':
                script_path = script.get('script_path')
                if script_path:
                    BGI_dir = os.path.dirname(script_path)
                    return os.path.join(BGI_dir, 'User')
    return None
🤖 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/config/bgi.py` around lines 12 - 18, Update get_BGI_user_dir to handle an
empty YAML result by treating it as an empty configuration, and validate that
the matching script has a non-empty script_path before deriving its directory.
Skip entries with missing or empty paths so the function never calls
os.path.dirname(None) or returns a relative User path.
src/config/onedrag_ui.py (2)

52-66: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

load_data is fragile against empty/missing YAML and early returns.

Two issues:

  1. Line 58: yaml.safe_load(f) returns None for an empty config.yml, causing AttributeError on line 68 (self.config_data.get(...)).
  2. Lines 53-55: When the config file is missing, load_data returns early without setting self.weekly_timeouts_map. The save button is still active, so clicking Save hits AttributeError at line 164 (yaml.dump(self.weekly_timeouts_map, ...)).
🛡️ Proposed fix
     def load_data(self):
         if not os.path.exists(self.yml_path):
             MessageBox("错误", f"找不到文件: {self.yml_path}", self).exec()
+            self.weekly_timeouts_map = {}
             return

         with open(self.yml_path, 'r', encoding='utf-8') as f:
-            self.config_data = yaml.safe_load(f)
+            self.config_data = yaml.safe_load(f) or {}
📝 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.

    def load_data(self):
        if not os.path.exists(self.yml_path):
            MessageBox("错误", f"找不到文件: {self.yml_path}", self).exec()
            self.weekly_timeouts_map = {}
            return

        with open(self.yml_path, 'r', encoding='utf-8') as f:
            self.config_data = yaml.safe_load(f) or {}

        # 从 weekly_timeouts.yml 读取每周超时配置
        weekly_timeouts_path = get_weekly_timeouts_yml_path_under_root()
        weekly_timeouts_map = {}
        if os.path.exists(weekly_timeouts_path):
            with open(weekly_timeouts_path, 'r', encoding='utf-8') as f:
                weekly_timeouts_map = yaml.safe_load(f) or {}
        self.weekly_timeouts_map = weekly_timeouts_map
🧰 Tools
🪛 ast-grep (0.44.1)

[warning] 56-56: File path is request-/variable-derived; validate and normalize to prevent path traversal.
Context: open(self.yml_path, 'r', encoding='utf-8')
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(open-filename-from-request)


[warning] 63-63: File path is request-/variable-derived; validate and normalize to prevent path traversal.
Context: open(weekly_timeouts_path, 'r', encoding='utf-8')
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(open-filename-from-request)

🤖 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/config/onedrag_ui.py` around lines 52 - 66, Update load_data so
yaml.safe_load for the main configuration always produces a mapping, defaulting
empty or null content to {} before assigning self.config_data. Initialize
self.weekly_timeouts_map to {} before the missing-file early return, while
preserving the existing weekly_timeouts.yml loading behavior when the file
exists.

149-153: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

int(le.text().strip()) crashes on empty timeout fields.

QIntValidator allows empty strings (returns Intermediate), so a user can clear a field and click Save. int("") raises ValueError, crashing the save operation with no user feedback.

🛡️ Proposed fix
                 for le in lineedits:
-                    val = int(le.text().strip())
+                    text = le.text().strip()
+                    val = int(text) if text else 0
                     weekly_timeouts.append(val)
📝 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.

                weekly_timeouts = []
                for le in lineedits:
                    text = le.text().strip()
                    val = int(text) if text else 0
                    weekly_timeouts.append(val)
                self.weekly_timeouts_map[display_name] = weekly_timeouts
🤖 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/config/onedrag_ui.py` around lines 149 - 153, Update the weekly timeout
parsing in the save flow around weekly_timeouts so empty or whitespace-only
line-edit values do not call int() and crash. Validate each le.text() value
before conversion, and provide the existing user feedback or appropriate
fallback for invalid empty fields while preserving normal integer parsing and
weekly_timeouts_map assignment.
src/dungeon_adapter.py (6)

38-41: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Keep unreadable configuration within set_config’s failure contract.

Missing, malformed, empty, or non-mapping YAML/JSON can raise here before set_config reaches its try blocks, despite its documented bool result. Catch OSError, yaml.YAMLError, and json.JSONDecodeError in the loaders; validate config.yml is a mapping; return None so set_config returns False cleanly.

Also applies to: 90-95, 165-169

🧰 Tools
🪛 ast-grep (0.44.1)

[warning] 39-39: File path is request-/variable-derived; validate and normalize to prevent path traversal.
Context: open(get_config_yml_path_under_root(), 'r', encoding='utf-8')
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(open-filename-from-request)

🤖 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/dungeon_adapter.py` around lines 38 - 41, Update the YAML and JSON loader
functions used by set_config to catch OSError, yaml.YAMLError, and
json.JSONDecodeError, returning None for unreadable or invalid configuration. In
_load_config_yml and the corresponding JSON loader, validate that parsed content
is a mapping and treat missing or empty content as invalid. Ensure set_config
handles a None loader result and returns False without allowing these errors to
escape.

111-120: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Write configuration atomically.

Opening the live file in write mode can leave it empty or partially written if serialization, disk I/O, or the process fails. Serialize to a temporary file in the same directory, then replace the target with os.replace; validate the extension before creating any file.

🧰 Tools
🪛 ast-grep (0.44.1)

[warning] 111-111: File path is request-/variable-derived; validate and normalize to prevent path traversal.
Context: open(path, 'w', encoding='utf-8')
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(open-filename-from-request)

🤖 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/dungeon_adapter.py` around lines 111 - 120, Update the
configuration-writing function containing the ext check to validate supported
extensions before opening or creating files. Serialize the JSON/YAML data to a
temporary file in the target’s directory, then atomically replace the
destination with os.replace; ensure temporary-file cleanup on failure and
preserve the existing unsupported-format return behavior.

178-180: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not report Zenless configuration as successful.

Selecting a Zenless dungeon currently performs no update but returns True. Reserve None for valid no-op updates; signal unsupported adaptation so callers receive False.

Proposed fix
 def _apply_zenless(config: dict, dungeon_name: str, sequence: str | None = None) -> dict | None:
-    # TODO: 适配绝区零的副本配置
-    print(f"[dungeon_adapter][Zenless] 待适配: {dungeon_name}")
-    return None
+    raise NotImplementedError(
+        f"[dungeon_adapter][Zenless] 未适配副本配置: {dungeon_name}"
+    )

Also applies to: 296-300

🧰 Tools
🪛 Ruff (0.15.20)

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

(RUF003)


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

(RUF003)

🤖 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/dungeon_adapter.py` around lines 178 - 180, Update the Zenless dungeon
handling in the adapter, including the corresponding logic around the second
referenced location, so unsupported Zenless adaptation returns False rather than
being treated as a successful no-op. Preserve the existing True return for valid
handlers that return None to indicate no write is needed.

249-249: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Apply the selected sequence after changing the dungeon.

update_task() or update_sequence() short-circuits, so a newly selected dungeon is saved without its selected sequence. Evaluate both updates before combining their results.

Proposed fix
-    changed = update_task() or update_sequence()
+    task_changed = update_task()
+    sequence_changed = update_sequence()
+    changed = task_changed or sequence_changed

Also applies to: 354-354

🤖 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/dungeon_adapter.py` at line 249, Update the changed-state assignment
around update_task() and update_sequence() so both functions are evaluated
before combining their boolean results, ensuring the selected sequence is
applied after a dungeon change. Apply the same change to the corresponding
occurrence near the other reported location.

315-318: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove the empty f-string prefixes.

These four statements trigger Ruff F541 errors.

Proposed fix
-        print(f"[dungeon_adapter][Star Rail] config 无需更新")
+        print("[dungeon_adapter][Star Rail] config 无需更新")
...
-    print(f"[dungeon_adapter][Star Rail] config 已更新")
+    print("[dungeon_adapter][Star Rail] config 已更新")
...
-        print(f"[dungeon_adapter][Arknights] config 无需更新")
+        print("[dungeon_adapter][Arknights] config 无需更新")
...
-    print(f"[dungeon_adapter][Arknights] config 已更新")
+    print("[dungeon_adapter][Arknights] config 已更新")

Also applies to: 391-394

🧰 Tools
🪛 Ruff (0.15.20)

[error] 315-315: f-string without any placeholders

Remove extraneous f prefix

(F541)


[error] 318-318: f-string without any placeholders

Remove extraneous f prefix

(F541)

🤖 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/dungeon_adapter.py` around lines 315 - 318, Remove the unnecessary
f-string prefixes from the four static print statements in the Star Rail
configuration update paths, including the branches around the existing “config
无需更新” and “config 已更新” messages and the corresponding statements near the second
referenced location; keep their text and control flow unchanged.

Source: Linters/SAST tools


379-384: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Enable only the dungeon selected by the user.

For any selection other than “土”, Line 384 re-enables a second task after the loop disables all alternatives. The automation will run both the chosen dungeon and “土”.

Proposed fix
         task_config[task_map[dungeon_name]]["IsEnable"] = True
-        task_config[task_map["土"]]["IsEnable"] = True
         print(f"enable task: {dungeon_name}")
📝 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.

        # disable other tasks
        for key in task_map:
            task_config[task_map[key]]["IsEnable"] = False
        
        task_config[task_map[dungeon_name]]["IsEnable"] = True
🤖 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/dungeon_adapter.py` around lines 379 - 384, Update the task-enabling
logic near task_map so only the user-selected dungeon is re-enabled after all
tasks are disabled. Remove the unconditional task_config[task_map["土"]]
assignment, while preserving the selected dungeon assignment and ensuring
selecting “土” still enables that dungeon.
src/python_script/mute.py (1)

1-1: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if pycaw is declared in project dependencies
rg -n 'pycaw' pyproject.toml requirements*.txt setup.cfg setup.py 2>/dev/null || echo "pycaw not found in dependency files"

Repository: LevelDownRefine/OneDragon-Helper

Length of output: 206


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Files in repo root that look like dependency manifests:\n'
git ls-files | rg '(^|/)(pyproject\.toml|requirements[^/]*\.txt|setup\.cfg|setup\.py|Pipfile|poetry\.lock|uv\.lock|pdm\.toml|conda\.ya?ml|environment\.ya?ml)$' || true

printf '\n--- src/python_script/mute.py ---\n'
wc -l src/python_script/mute.py
cat -n src/python_script/mute.py

printf '\n--- dependency manifests mentioning pycaw (if any) ---\n'
rg -n 'pycaw' . -g 'pyproject.toml' -g 'requirements*.txt' -g 'setup.cfg' -g 'setup.py' -g 'Pipfile' -g 'poetry.lock' -g 'uv.lock' -g 'pdm.toml' -g 'conda*.yml' -g 'environment*.yml' 2>/dev/null || true

Repository: LevelDownRefine/OneDragon-Helper

Length of output: 862


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- pyproject.toml ---'
wc -l pyproject.toml
cat -n pyproject.toml

echo
echo '--- uv.lock pycaw mentions ---'
rg -n -C 2 'pycaw|pycaw.pycaw|win32|platform' uv.lock pyproject.toml || true

Repository: LevelDownRefine/OneDragon-Helper

Length of output: 50389


Declare pycaw as a dependency. src/python_script/mute.py:1 imports pycaw.pycaw at module import time, but pycaw is not listed in pyproject.toml/uv.lock, so a clean install will fail before this script runs.

🤖 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/python_script/mute.py` at line 1, Add pycaw as a runtime dependency in
the project configuration and regenerate the lockfile so it is recorded in
uv.lock. Keep the existing AudioUtilities import in src/python_script/mute.py
unchanged.

@LevelDownRefine
LevelDownRefine merged commit 42a00dd into main Jul 13, 2026
2 checks passed
@LevelDownRefine
LevelDownRefine deleted the lvdown/refator branch July 13, 2026 05:27
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.

1 participant