新增钓鱼 task,大量修改配置与格式#228
Conversation
feat: 增加 task `钓鱼 分支 2` fix: 修复部分代码 bug ci: 修改打包 Python 版本为 `3.14.5` ci: 修改 `cliff.toml` perf: 移除非必要依赖 doc: 修正 readme 中对 Python 版本的指定错误 开发: 修改并统一代码格式化器,增加 `.ruff` 用于 Python 代码规范检查
doc: update readme 开发指南 开发: 增加 toml 格式化插件,修复上次提交误删的 json 保存时自动格式化选项
Reviewer's Guide新增一个更高级的自动钓鱼任务( Fish_fork_2 自动钓鱼运行时循环时序图sequenceDiagram
participant User
participant Fish_fork_2
participant Context
participant Tasker
participant Controller
User->>Fish_fork_2: invoke run(context, argv)
Fish_fork_2->>Context: get_node_object(argv.node_name)
Context-->>Fish_fork_2: node_object.attach
Fish_fork_2->>Fish_fork_2: get_option(attach) -> Fish_option
loop fishing loop
Fish_fork_2->>Tasker: check Tasker.stopping
alt 终止时间 reached
Fish_fork_2-->>User: success True
Fish_fork_2-->>Fish_fork_2: break
end
Fish_fork_2->>Tasker: tasker.controller
Tasker-->>Fish_fork_2: Controller
Fish_fork_2->>Controller: post_screencap().get()
Controller-->>Fish_fork_2: img
Fish_fork_2->>Context: reco_上鱼(img) via run_recognition
alt 上鱼 detected
Fish_fork_2->>Controller: 溜鱼(): post_key_down(A or D)
Controller-->>Fish_fork_2: done
Fish_fork_2->>Controller: post_screencap().get()
Controller-->>Fish_fork_2: img
end
Fish_fork_2->>Context: reco_获鱼(img)
alt 获鱼 detected
Fish_fork_2->>Controller: post_click_key(VK_ESCAPE)
Controller-->>Fish_fork_2: done
Fish_fork_2-->>Fish_fork_2: continue
end
Fish_fork_2->>Context: reco_满舱_or_无饵(img)
alt 渔获已满 or 鱼饵用完
alt 卖鱼买换饵开关 == False
Fish_fork_2-->>User: success True
Fish_fork_2-->>Fish_fork_2: break
else 卖鱼买换饵开关 == True
Fish_fork_2->>Context: 卖鱼买换饵(买饵次数)
Context-->>Fish_fork_2: bool ok
alt not ok
Fish_fork_2-->>User: success False
Fish_fork_2-->>Fish_fork_2: break
end
Fish_fork_2-->>Fish_fork_2: continue
end
end
Fish_fork_2->>Context: reco_钓鱼按钮(img)
alt 钓鱼按钮 detected
Fish_fork_2->>Controller: post_click_key(F)
Controller-->>Fish_fork_2: done
Fish_fork_2->>Fish_fork_2: fail_num = 0
else none detected
Fish_fork_2->>Controller: post_click_key(F)
Fish_fork_2->>Fish_fork_2: fail_num++
alt fail_num > FISH_RECO_FIAL_NUM_MAX
Fish_fork_2-->>User: success False
Fish_fork_2-->>Fish_fork_2: break
end
end
end
File-Level Changes
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your Experience前往你的 dashboard 以:
Getting HelpOriginal review guide in EnglishReviewer's GuideAdds a new, more advanced auto fishing task (fish_fork_2) with its own pipeline and shared utilities, while broadly refactoring Python code style/type hints, updating tooling/config (ruff, CI, build scripts, package manager), and improving various custom actions and i18n/build workflows. Sequence diagram for Fish_fork_2 auto fishing runtime loopsequenceDiagram
participant User
participant Fish_fork_2
participant Context
participant Tasker
participant Controller
User->>Fish_fork_2: invoke run(context, argv)
Fish_fork_2->>Context: get_node_object(argv.node_name)
Context-->>Fish_fork_2: node_object.attach
Fish_fork_2->>Fish_fork_2: get_option(attach) -> Fish_option
loop fishing loop
Fish_fork_2->>Tasker: check Tasker.stopping
alt 终止时间 reached
Fish_fork_2-->>User: success True
Fish_fork_2-->>Fish_fork_2: break
end
Fish_fork_2->>Tasker: tasker.controller
Tasker-->>Fish_fork_2: Controller
Fish_fork_2->>Controller: post_screencap().get()
Controller-->>Fish_fork_2: img
Fish_fork_2->>Context: reco_上鱼(img) via run_recognition
alt 上鱼 detected
Fish_fork_2->>Controller: 溜鱼(): post_key_down(A or D)
Controller-->>Fish_fork_2: done
Fish_fork_2->>Controller: post_screencap().get()
Controller-->>Fish_fork_2: img
end
Fish_fork_2->>Context: reco_获鱼(img)
alt 获鱼 detected
Fish_fork_2->>Controller: post_click_key(VK_ESCAPE)
Controller-->>Fish_fork_2: done
Fish_fork_2-->>Fish_fork_2: continue
end
Fish_fork_2->>Context: reco_满舱_or_无饵(img)
alt 渔获已满 or 鱼饵用完
alt 卖鱼买换饵开关 == False
Fish_fork_2-->>User: success True
Fish_fork_2-->>Fish_fork_2: break
else 卖鱼买换饵开关 == True
Fish_fork_2->>Context: 卖鱼买换饵(买饵次数)
Context-->>Fish_fork_2: bool ok
alt not ok
Fish_fork_2-->>User: success False
Fish_fork_2-->>Fish_fork_2: break
end
Fish_fork_2-->>Fish_fork_2: continue
end
end
Fish_fork_2->>Context: reco_钓鱼按钮(img)
alt 钓鱼按钮 detected
Fish_fork_2->>Controller: post_click_key(F)
Controller-->>Fish_fork_2: done
Fish_fork_2->>Fish_fork_2: fail_num = 0
else none detected
Fish_fork_2->>Controller: post_click_key(F)
Fish_fork_2->>Fish_fork_2: fail_num++
alt fail_num > FISH_RECO_FIAL_NUM_MAX
Fish_fork_2-->>User: success False
Fish_fork_2-->>Fish_fork_2: break
end
end
end
File-Level Changes
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - 我发现了 1 个安全问题、3 个其他问题,并留下了一些总体反馈:
Security issues:
- 检测到对子进程函数
run的调用,其参数不是静态字符串。如果这些数据可能被恶意行为者控制,这可能会造成命令注入风险。请审计该调用的使用方式,确保其参数不能被外部资源控制。你可以考虑使用shlex.escape()。(link)
General comments:
- 新增的
Fish_fork_2自定义动作声明为run(...)-> bool,并且当前直接返回True/False,但 Maa 的自定义动作预期返回CustomAction.RunResult;这会破坏动作约定,应修改为始终返回RunResult(包括Manual_stop分支路径中)。 utils/py_tools.py中的辅助函数type_match在很多情况下会回退为True(并且没有正确处理get_origin为types.UnionType的X | Y联合类型),这意味着它会在类型不匹配时静默通过。建议将默认分支改为返回False,并为Union/types.UnionType添加显式处理逻辑,使检查能够真正捕获不正确的结构。
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The new `Fish_fork_2` custom action declares `run(...)-> bool` and currently returns plain `True`/`False`, but Maa custom actions are expected to return `CustomAction.RunResult`; this will break the action contract and should be updated to return `RunResult` consistently (including in the `Manual_stop` path).
- The helper `type_match` in `utils/py_tools.py` falls back to `True` for many cases (and does not correctly handle `X | Y` unions where `get_origin` is `types.UnionType`), which means it can silently accept mismatched types; consider making the default branch return `False` and adding explicit handling for `Union`/`types.UnionType` so the checks actually catch incorrect structures.
## Individual Comments
### Comment 1
<location path="agent/custom/action/AutoFish/auto_fish_fork_2.py" line_range="33" />
<code_context>
+ pass
+
+
+type Img = np.typing.NDArray
+
+
</code_context>
<issue_to_address>
**issue (bug_risk):** np is only imported under TYPE_CHECKING, so this type alias will raise NameError at import time
To avoid this, either import the type directly at runtime (e.g. `from numpy.typing import NDArray` and use `type Img = NDArray`), or move the alias into the `TYPE_CHECKING` block and only reference it in type-only contexts (e.g. `if TYPE_CHECKING` or string annotations).
</issue_to_address>
### Comment 2
<location path="agent/custom/action/AutoFish/auto_fish_fork_2.py" line_range="500-507" />
<code_context>
+FISH_RECO_FIAL_NUM_MAX = 50
+
+
+@AgentServer.custom_action("fish_fork_2")
+class Fish_fork_2(CustomAction):
+ @override
+ def run(
+ self,
+ context: Context,
+ argv: CustomAction.RunArg,
+ ) -> bool:
+ option: Fish_option = get_option(context, argv)
+ logger.debug(f"{option=}")
</code_context>
<issue_to_address>
**issue (bug_risk):** CustomAction.run should return a RunResult, not a bare bool
`Fish_fork_2.run` is typed to return `bool` and currently returns raw `True`/`False`, but callers (and the Maa API) expect `CustomAction.RunResult`. This mismatch will likely break or confuse the caller. Please update the signature to `-> CustomAction.RunResult` and return `CustomAction.RunResult(success=True/False, ...)` consistently, aligned with other custom actions.
</issue_to_address>
### Comment 3
<location path="agent/utils/py_tools.py" line_range="4-13" />
<code_context>
+def type_match[T](val: Any, t: type[T]) -> TypeGuard[T]:
</code_context>
<issue_to_address>
**suggestion (bug_risk):** type_match’s Union handling and fallback behavior are looser than the docstring suggests
The docstring advertises support for Union/PEP 604 unions and nested generics, but the implementation only checks `typing.Union` via `t_org.__name__ == "Union"`. For `T1 | T2`, `get_origin` returns `types.UnionType`, so this case is missed and the function falls through to `return True`, even when the value doesn’t match. Likewise, for generics whose origins are in `collections.abc`, once the outer `isinstance` passes, element types are never validated. This makes the function a shallow check, not the deeper validator the docstring suggests. I’d recommend either handling `types.UnionType` and relevant `collections.abc` origins explicitly, or updating the docstring to describe the current shallow behavior more accurately.
Suggested implementation:
```python
from typing import Any, TypeGuard, get_args, get_origin
from types import UnionType
from collections.abc import Mapping, Sequence, Set as AbstractSet
```
```python
def type_match[T](val: Any, t: type[T]) -> TypeGuard[T]:
"""
检查值是否匹配给定的类型(支持泛型,包含常见容器的“深度”检查)
支持的类型包括:
- 基本类型: int, str, list, dict, tuple, set
- 泛型类型: list[str], dict[str, int], tuple[int, ...]
- 联合类型: int | str, Union[int, str]
- 可选类型: Optional[str] (Union[str, None] 形式)
- 嵌套泛型: list[list[str]], dict[str, list[int]]
- collections.abc 中的常见容器: Sequence[T], Mapping[K, V], Set[T] 等
对于上述容器类型,会递归检查其内部元素是否满足对应的类型参数。
"""
# Any 始终视为匹配
if t is Any:
return True # type: ignore[return-value]
origin = get_origin(t)
args = get_args(t)
# 处理 Union / PEP 604 联合类型 (T1 | T2)
# - 对于 typing.Union[...] 和 T1 | T2,get_origin(...) 在 3.10+ 通常为 types.UnionType
# - 额外使用 isinstance(t, UnionType) 兜底 PEP 604 形式
if origin is UnionType or isinstance(t, UnionType):
# Optional[T] 等价于 Union[T, None]
for sub_t in args:
if type_match(val, sub_t):
return True # type: ignore[return-value]
return False # type: ignore[return-value]
# 无 origin,按普通类型处理
if origin is None:
try:
return isinstance(val, t) # type: ignore[arg-type,return-value]
except TypeError:
# 一些 typing 构造在某些版本下不支持直接 isinstance
return False # type: ignore[return-value]
# 处理 Sequence[T] / list[T] 等序列类型(不包括 str/bytes)
if origin in (list, Sequence):
elem_type = args[0] if args else Any
if not isinstance(val, Sequence) or isinstance(val, (str, bytes, bytearray)):
return False # type: ignore[return-value]
return all(type_match(item, elem_type) for item in val) # type: ignore[return-value]
# 处理 Set[T] / set[T]
if origin in (set, AbstractSet):
elem_type = args[0] if args else Any
if not isinstance(val, AbstractSet) or isinstance(val, (str, bytes, bytearray)):
return False # type: ignore[return-value]
return all(type_match(item, elem_type) for item in val) # type: ignore[return-value]
# 处理 Mapping[K, V] / dict[K, V]
if origin in (dict, Mapping):
key_type = args[0] if len(args) > 0 else Any
value_type = args[1] if len(args) > 1 else Any
if not isinstance(val, Mapping):
return False # type: ignore[return-value]
return all(
type_match(k, key_type) and type_match(v, value_type)
for k, v in val.items()
) # type: ignore[return-value]
# 处理 tuple[...]:
# - 固定长度: tuple[int, str]
# - 可变长度: tuple[int, ...]
if origin is tuple:
if not isinstance(val, tuple):
return False # type: ignore[return-value]
if not args:
return True # type: ignore[return-value] # 无元素约束
# 可变长度: tuple[T, ...]
if len(args) == 2 and args[1] is Ellipsis:
elem_type = args[0]
return all(type_match(item, elem_type) for item in val) # type: ignore[return-value]
# 固定长度: tuple[T1, T2, ...]
if len(val) != len(args):
return False # type: ignore[return-value]
return all(type_match(item, sub_t) for item, sub_t in zip(val, args)) # type: ignore[return-value]
# 其他有 origin 的泛型,先按其 origin 做浅检查
try:
if not isinstance(val, origin): # type: ignore[arg-type]
return False # type: ignore[return-value]
except TypeError:
# 无法用于 isinstance 的 origin
return False # type: ignore[return-value]
# 默认:对未显式支持的泛型,不做深度检查,仅检查外层类型
return True # type: ignore[return-value]
```
</issue_to_address>
### Comment 4
<location path="build.py" line_range="76" />
<code_context>
result = subprocess.run(cmd, cwd=cwd or ROOT, capture_output=True, text=True)
</code_context>
<issue_to_address>
**security (python.lang.security.audit.dangerous-subprocess-use-audit):** Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.
*Source: opengrep*
</issue_to_address>帮我变得更有用!请在每条评论上点击 👍 或 👎,我会根据你的反馈改进后续的审查。
Original comment in English
Hey - I've found 1 security issue, 3 other issues, and left some high level feedback:
Security issues:
- Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'. (link)
General comments:
- The new
Fish_fork_2custom action declaresrun(...)-> booland currently returns plainTrue/False, but Maa custom actions are expected to returnCustomAction.RunResult; this will break the action contract and should be updated to returnRunResultconsistently (including in theManual_stoppath). - The helper
type_matchinutils/py_tools.pyfalls back toTruefor many cases (and does not correctly handleX | Yunions whereget_originistypes.UnionType), which means it can silently accept mismatched types; consider making the default branch returnFalseand adding explicit handling forUnion/types.UnionTypeso the checks actually catch incorrect structures.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The new `Fish_fork_2` custom action declares `run(...)-> bool` and currently returns plain `True`/`False`, but Maa custom actions are expected to return `CustomAction.RunResult`; this will break the action contract and should be updated to return `RunResult` consistently (including in the `Manual_stop` path).
- The helper `type_match` in `utils/py_tools.py` falls back to `True` for many cases (and does not correctly handle `X | Y` unions where `get_origin` is `types.UnionType`), which means it can silently accept mismatched types; consider making the default branch return `False` and adding explicit handling for `Union`/`types.UnionType` so the checks actually catch incorrect structures.
## Individual Comments
### Comment 1
<location path="agent/custom/action/AutoFish/auto_fish_fork_2.py" line_range="33" />
<code_context>
+ pass
+
+
+type Img = np.typing.NDArray
+
+
</code_context>
<issue_to_address>
**issue (bug_risk):** np is only imported under TYPE_CHECKING, so this type alias will raise NameError at import time
To avoid this, either import the type directly at runtime (e.g. `from numpy.typing import NDArray` and use `type Img = NDArray`), or move the alias into the `TYPE_CHECKING` block and only reference it in type-only contexts (e.g. `if TYPE_CHECKING` or string annotations).
</issue_to_address>
### Comment 2
<location path="agent/custom/action/AutoFish/auto_fish_fork_2.py" line_range="500-507" />
<code_context>
+FISH_RECO_FIAL_NUM_MAX = 50
+
+
+@AgentServer.custom_action("fish_fork_2")
+class Fish_fork_2(CustomAction):
+ @override
+ def run(
+ self,
+ context: Context,
+ argv: CustomAction.RunArg,
+ ) -> bool:
+ option: Fish_option = get_option(context, argv)
+ logger.debug(f"{option=}")
</code_context>
<issue_to_address>
**issue (bug_risk):** CustomAction.run should return a RunResult, not a bare bool
`Fish_fork_2.run` is typed to return `bool` and currently returns raw `True`/`False`, but callers (and the Maa API) expect `CustomAction.RunResult`. This mismatch will likely break or confuse the caller. Please update the signature to `-> CustomAction.RunResult` and return `CustomAction.RunResult(success=True/False, ...)` consistently, aligned with other custom actions.
</issue_to_address>
### Comment 3
<location path="agent/utils/py_tools.py" line_range="4-13" />
<code_context>
+def type_match[T](val: Any, t: type[T]) -> TypeGuard[T]:
</code_context>
<issue_to_address>
**suggestion (bug_risk):** type_match’s Union handling and fallback behavior are looser than the docstring suggests
The docstring advertises support for Union/PEP 604 unions and nested generics, but the implementation only checks `typing.Union` via `t_org.__name__ == "Union"`. For `T1 | T2`, `get_origin` returns `types.UnionType`, so this case is missed and the function falls through to `return True`, even when the value doesn’t match. Likewise, for generics whose origins are in `collections.abc`, once the outer `isinstance` passes, element types are never validated. This makes the function a shallow check, not the deeper validator the docstring suggests. I’d recommend either handling `types.UnionType` and relevant `collections.abc` origins explicitly, or updating the docstring to describe the current shallow behavior more accurately.
Suggested implementation:
```python
from typing import Any, TypeGuard, get_args, get_origin
from types import UnionType
from collections.abc import Mapping, Sequence, Set as AbstractSet
```
```python
def type_match[T](val: Any, t: type[T]) -> TypeGuard[T]:
"""
检查值是否匹配给定的类型(支持泛型,包含常见容器的“深度”检查)
支持的类型包括:
- 基本类型: int, str, list, dict, tuple, set
- 泛型类型: list[str], dict[str, int], tuple[int, ...]
- 联合类型: int | str, Union[int, str]
- 可选类型: Optional[str] (Union[str, None] 形式)
- 嵌套泛型: list[list[str]], dict[str, list[int]]
- collections.abc 中的常见容器: Sequence[T], Mapping[K, V], Set[T] 等
对于上述容器类型,会递归检查其内部元素是否满足对应的类型参数。
"""
# Any 始终视为匹配
if t is Any:
return True # type: ignore[return-value]
origin = get_origin(t)
args = get_args(t)
# 处理 Union / PEP 604 联合类型 (T1 | T2)
# - 对于 typing.Union[...] 和 T1 | T2,get_origin(...) 在 3.10+ 通常为 types.UnionType
# - 额外使用 isinstance(t, UnionType) 兜底 PEP 604 形式
if origin is UnionType or isinstance(t, UnionType):
# Optional[T] 等价于 Union[T, None]
for sub_t in args:
if type_match(val, sub_t):
return True # type: ignore[return-value]
return False # type: ignore[return-value]
# 无 origin,按普通类型处理
if origin is None:
try:
return isinstance(val, t) # type: ignore[arg-type,return-value]
except TypeError:
# 一些 typing 构造在某些版本下不支持直接 isinstance
return False # type: ignore[return-value]
# 处理 Sequence[T] / list[T] 等序列类型(不包括 str/bytes)
if origin in (list, Sequence):
elem_type = args[0] if args else Any
if not isinstance(val, Sequence) or isinstance(val, (str, bytes, bytearray)):
return False # type: ignore[return-value]
return all(type_match(item, elem_type) for item in val) # type: ignore[return-value]
# 处理 Set[T] / set[T]
if origin in (set, AbstractSet):
elem_type = args[0] if args else Any
if not isinstance(val, AbstractSet) or isinstance(val, (str, bytes, bytearray)):
return False # type: ignore[return-value]
return all(type_match(item, elem_type) for item in val) # type: ignore[return-value]
# 处理 Mapping[K, V] / dict[K, V]
if origin in (dict, Mapping):
key_type = args[0] if len(args) > 0 else Any
value_type = args[1] if len(args) > 1 else Any
if not isinstance(val, Mapping):
return False # type: ignore[return-value]
return all(
type_match(k, key_type) and type_match(v, value_type)
for k, v in val.items()
) # type: ignore[return-value]
# 处理 tuple[...]:
# - 固定长度: tuple[int, str]
# - 可变长度: tuple[int, ...]
if origin is tuple:
if not isinstance(val, tuple):
return False # type: ignore[return-value]
if not args:
return True # type: ignore[return-value] # 无元素约束
# 可变长度: tuple[T, ...]
if len(args) == 2 and args[1] is Ellipsis:
elem_type = args[0]
return all(type_match(item, elem_type) for item in val) # type: ignore[return-value]
# 固定长度: tuple[T1, T2, ...]
if len(val) != len(args):
return False # type: ignore[return-value]
return all(type_match(item, sub_t) for item, sub_t in zip(val, args)) # type: ignore[return-value]
# 其他有 origin 的泛型,先按其 origin 做浅检查
try:
if not isinstance(val, origin): # type: ignore[arg-type]
return False # type: ignore[return-value]
except TypeError:
# 无法用于 isinstance 的 origin
return False # type: ignore[return-value]
# 默认:对未显式支持的泛型,不做深度检查,仅检查外层类型
return True # type: ignore[return-value]
```
</issue_to_address>
### Comment 4
<location path="build.py" line_range="76" />
<code_context>
result = subprocess.run(cmd, cwd=cwd or ROOT, capture_output=True, text=True)
</code_context>
<issue_to_address>
**security (python.lang.security.audit.dangerous-subprocess-use-audit):** Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.
*Source: opengrep*
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
|
按本仓库的
另外,当前 check 里 建议先把以上阻塞项修掉,再考虑把本 PR 里混在一起的 FishFork2 功能、全仓 Python 格式化/Ruff、pnpm/CI/build 改动拆开,降低回归面。 |
如果了解 Python 语法,而不是让 AI 根据老数据库生成判断,很容易能判定 1 2 点是错的 3 各种 OCR 也都是基于简中的,钓鱼功能也还没公开测试,各种日志也都是简中的,只有 UI 支持多语言基本毫无用处了 4 已修正 5 AI 报 6 没有必要也不会考虑分开提交 钓鱼 和 项目格式依赖修改,因为一是这两个是同一个 commit 已经难以拆开了,二是 钓鱼 功能代码量小且不与其他功能耦合,没有分开 commit 的必要 |
Summary by Sourcery
添加增强的自动钓鱼任务及其配套工具,现代化 Python 类型标注和工具链,并优化构建、CI 与开发配置。
New Features:
Enhancements:
Build:
CI:
Documentation:
Chores:
Original summary in English
Summary by Sourcery
Add an enhanced automated fishing task and supporting utilities, modernize Python typing and tooling, and refine build, CI, and development configuration.
New Features:
Enhancements:
Build:
CI:
Documentation:
Chores: