从固定点(commit、branch、tag 或 merge-base)开始,沿两条轴线审查变更——规范(代码是否遵循仓库文档化的编码规范?)和规格(代码是否与原始 issue/PRD 的要求一致?)。两条审查线在并行子 agent 中运行,并以并排方式报告结果。当用户想审查一个分支、PR、进行中的变更,或要求"从 X 开始审查"时使用。
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Add, refactor, or review AUTO-MAS game community sign-in (game sign) code, including the provider registry in app/tools/game_sign.py, platform adapters for Skland/Miyoushe/Kuro/Taygedo, credential encryption and one-time login routes, sign-in locks and trigger paths, result and notification contracts, the GameSign account-group config, and the frontend gamesign views.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
실행 점수 미검증
설치 방법 보기설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.
签到是凭据驱动的批量外部请求,不是脚本适配。它的风险集中在三处:用户凭据、上游风控、重复触发。改动时先确认属于哪一层:
app/tools/<platform>.py):单个社区的登录、角色发现、签到请求、风控退避。上游 API 知识来自被致谢的第三方项目,文件头的 AGPL/致谢声明必须保留。app/tools/game_sign.py):账号遍历、平台注册表、并发、锁、日期标记、凭据回写、结果归一。app/tools/game_sign_notify.py、game_sign_result.py、API、前端):结果展示与通知。新增或修改平台时,只应动第 1 层加注册表一行。若发现必须改编排层才能容纳新平台,先停下来确认是不是抽象放错了位置。
app/tools/game_sign.py 的 _GAME_SIGN_PROVIDERS 是不可变元组,每项 _GameSignProvider(frozen=True) 声明五件事:
| 字段 | 含义 |
|---|---|
token_field | GameSignAccountGroup 上的凭据字段名,同时是「该平台是否已配置」的判据 |
log_name | 日志与错误文案中的平台名 |
runner | async (token, account_name, account_uid) -> _ProviderRun |
resolve_platforms | 由凭据推导本次涉及的平台名元组 |
error_game | 平台名 → 失败结果里的 game 字段 |
当前四项:SklandToken/森空岛、MiyousheToken/米游社、KuroToken/库街区、TaygedoToken/塔吉多。
GAME_SIGN_TOKEN_FIELDS 由注册表派生,has_game_sign_credentials() 又由它派生。不要另写一份平台或字段清单:调度台跳过账号、前端判空、通知分组都应回到注册表。
只有塔吉多使用动态 resolve_platforms(_resolve_taygedo_platforms):一份凭据可能同时覆盖塔吉多与云异环两个平台名。其余三家用 _fixed_platforms(...) 返回单元素元组。新增「一份凭据对多平台」的社区时照塔吉多写,不要在 runner 里硬编码平台名。
| 文件 | 职责 |
|---|---|
app/tools/game_sign.py | 注册表、锁、编排、结果归一、format_sign_results / merge_sign_results |
app/tools/skland.py | 森空岛:凭据校验、角色发现、签到、账号密码登录;绑定列表与玩家整表数据(player/info)的公共拉取(fetch_skland_bindings / fetch_skland_player_info),供签到与养成练度源共用 |
app/tools/skland_response.py | 森空岛响应形状判定(如 is_skland_already_signed),与请求逻辑分离 |
app/tools/miyoushe.py | 米游社:DS 签名、多游戏签到 |
app/tools/miyoushe_qr.py | 米游社扫码登录状态机 |
app/tools/kuro.py | 库街区 |
app/tools/taygedo.py | 塔吉多 + 云异环:账号密码登录、Token 刷新、凭据序列化 |
app/tools/game_sign_result.py | 结果构建辅助(SKLAND_GAME_MAPPING、build_skland_sign_results) |
app/tools/game_sign_notify.py | 通知文案格式化、平台排序、任务摘要挂载、多渠道推送与重试 |
app/api/tools.py | 手动签到、账号组 CRUD、塔吉多/森空岛一次性登录 |
app/api/qr_login.py | 米游社扫码登录三步路由 |
app/core/timer.py | 自动签到触发、任务前置签到、全局日期标记 |
app/tools/__init__.py 的 __all__ 只有四项:run_all_sign_in、format_sign_results、login_skland_with_password、skland_sign_in。其余符号按需从具体模块导入,且多数调用点使用函数内延迟导入以避免启动期循环依赖;沿用这个写法,不要为了「统一出口」把签到模块提到包顶层导入。
| 位置 | 内容 |
|---|---|
frontend/src/views/gamesign/index.vue | 页面容器 |
frontend/src/views/gamesign/TabGameSign.vue | 账号组列表、凭据录入、扫码弹窗、结果展示 |
frontend/src/views/gamesign/useGameSignApi.ts | 仅包装生成的 Service.*,无业务判断 |
useGameSignApi.ts 是薄封装:每个方法一行转发到 @/api 的生成客户端。新增路由时在此加一行并从返回对象导出,不要在此写状态、缓存或错误吞掉逻辑。
按此顺序,每步都能独立自查:
app/tools/<platform>.py。对外只暴露一个「跑一次签到」的入口与必要的凭据解析/校验函数。若参考了第三方项目的接口知识,按现有模块的写法在文件头加致谢与许可声明。app/models/config.py 的 GameSignAccountGroup 增加 <Name>Token,section 固定为 "GameSignAccount",validator 必须是 EncryptValidator(),并在字段上方写 ## GameSignAccount - <说明> (DPAPI 加密) 注释。app/models/schema.py 的 GameSignAccountGroupConfig 增加同名可选字段。这是 API 出入口,字段名与配置项保持一致。game_sign.py 加 _run_<platform>_provider(),返回 _ProviderRun(results=..., platforms=..., credential_updates=...)。_GAME_SIGN_PROVIDERS 追加一项。GAME_SIGN_TOKEN_FIELDS、has_game_sign_credentials()、并发执行、凭据回写、日期标记全部自动生效。game_sign_notify.py 的 _PLATFORM_ORDER 追加平台名。缺失时该平台会落到排序尾部而不是报错,容易漏掉。TabGameSign.vue 加录入入口。禁止手改 frontend/src/api/**。签到是本仓少数直接持有用户账号密码的路径,规则从严:
EncryptValidator()(DPAPI 加密落盘)。新增凭据字段没有例外。SecretStr,用 .get_secret_value() 取值,不落盘、不进日志。except Exception 分支禁止 exc_info / logger.exception / 回显上游响应,只返回固定文案。参见 login_taygedo / login_skland 的注释:避免密码、请求对象或上游响应进入日志。ValueError 才允许把消息带给用户(凭据不完整、格式错误等可预期原因);其他异常统一返回泛化文案。accessToken/refreshToken/uid,森空岛校验 oauthToken/token/cred),缺字段视为登录失败。_ProviderRun.credential_updates:编排层在并发完成后统一比对新旧值并写回,值未变则跳过。不要在 runner 内部直接写 account.set()。_read_game_sign_token():它容忍旧账号对象缺少新增字段(AttributeError / KeyError 均返回空串)。直接 account.get(...) 会在旧配置上炸。两把锁,语义不同,不要合并:
| 锁 | 覆盖范围 | 违反时 |
|---|---|---|
_game_sign_flow_lock(game_sign_flow() 上下文管理器) | 签到请求 加 结果落盘。通知在锁外发送。 | 直接抛 GameSignInProgressError,不排队等待 |
_game_sign_lock(run_all_sign_in 内部) | 全局签到执行,配合 _game_sign_lock_owner ContextVar 支持同任务嵌套重入 | 同上 |
两把锁都是快速失败而非等待:if lock.locked(): raise。API 层把 GameSignInProgressError 映射为 code=409;timer 层降级为 logger.info 后跳过本次触发。新增触发入口时必须选一种处理,不要静默 await 到锁释放。
森空岛另有模块级 _skland_sign_lock(公共别名 skland_sign_lock,skland.py):签到全流程与养成练度源(app/task/MAA/tools/cultivate/skland.py)共用,互斥签名 token 的两个独立轮换写者。练度源的凭据回写走注入的 save_credential 回调(core/config.py),不进 _ProviderRun.credential_updates——它不是注册表 runner;改这两条链路时必须保住这把互斥锁,否则会把对方刚回写的 token 作废(community_sign.py 收尾回写前重读存量、发现已被轮换即跳过,也是防同一件事)。
通知必须在流程锁外发送。慢渠道会阻塞后续操作,这是锁边界画在落盘之后的原因。
日期标记有两层,不要混用:
GameSignAccount.LastSignDate:_run_all_sign_in 内按账号写。自动模式(force=False)即使失败也标记当天,避免后续 MAS 任务反复请求上游;手动模式(force=True)仅在该账号所有已配置平台完成后才标记。GameSign.LastSignDate:由调用方在所有启用且有凭据的账号都完成后才写。多账号串行签到可能跨越 0 点,写入时重新取当前日期而不是复用循环开始时的 today。改这段逻辑要保留这个行为。
编排层的 results 是 list[dict],键固定:
account 账号名,失败结果为 "账号名/平台名"
account_uid 账号组 UUID 字符串,由编排层统一覆盖
game 游戏名
platform 平台名
status "成功" / "已签到" / "失败"
reward 奖励文案
reason 失败原因
两个内部标记:
_notification_only:占位结果(如「未获取到可签到角色」),进通知但被 format_sign_results 过滤,不进前端列表。_completed:完成态旁路标记,参与「是否全部完成」判定。成功判据在三处重复出现(_all_enabled_platforms_signed、manual_game_sign、game_sign_notify._SUCCESS_STATUSES):status in ("成功", "已签到") or _completed。改成功语义时必须同步这三处。
_decorate_provider_run() 负责归一:补 account 与 account_uid、为没有结果的平台补占位。runner 返回的结果不必自己填 account_uid。
format_sign_results() 输出 {platform: [{account_alias, account_uid, games: [...]}]};merge_sign_results() 按 account_uid 替换受影响账号,避免旧成功状态遮蔽新失败结果(replace 参数已退化为兼容占位,两条路径行为相同)。
_is_expected_provider_exception() 是唯一的分级依据,_run_provider() 据此选择 logger.warning 还是 logger.exception:
warning + 带消息给用户):ValueError、httpx.HTTPError、TimeoutError、ConnectionError;以及消息中含 token/cookie/凭据/登录/风控/请求/接口/网络/offline/timeout/timed out 的 RuntimeError。exception + 只给用户泛化文案):其余全部。凭据失效和上游风控是正常运行状态,不应刷栈。新增平台若抛自定义异常,让它继承 ValueError 或在消息里带上述关键词,不要扩大 _is_expected_provider_exception 的类型白名单来将就实现。
| 入口 | 特点 |
|---|---|
POST /api/tools/sign(manual_game_sign) | force=True;通知走 _dispatch_community_notification,asyncio.shield + 1.1s(NOTIFICATION_FAST_PATH_WAIT_SECONDS)超时后转后台,不阻塞响应;渠道级失败另有系统提示兜底 |
timer._execute_game_sign(source=...) | force=False;source 在 _TASK_GAME_SIGN_SOURCES 内时结果交由任务完成通知消费,其余自动来源单独推送 |
timer.try_game_sign_for_task | MAS 任务前置签到 |
GameSign.NotifyEnabled 是通知总开关,两条路径都要检查。后台通知任务必须持有强引用(_PENDING_COMMUNITY_NOTIFICATIONS)并挂 add_done_callback,否则任务可能被 GC;渠道级失败由 push_community_notification 另发一条系统提示(Config.push_system_notice),不只写日志。
check_community_system_time() 只告警不阻断,且不占签到锁(在 run_community_sign_in 里以独立 task 起、finally 里取消)。时间源不可用时静默 debug。被取消的那一次不写检查缓存(只在跑完后打点),否则接下来一个检查周期都会命中缓存、偏差提示不再出现。不要把它改成阻断条件。
app/api/qr_login.py 把 QrCreateOut / QrCheckIn / QrCheckOut / QrSaveIn 定义在路由文件内,其余签到 schema 都在 app/models/schema.py。新增签到 schema 放 schema.py。tools.py 的账号组路由用 tags=["GameSign"],手动签到用 tags=["Action"],qr_login.py 用中文 tags=["扫码登录"]。沿用所在文件的既有 tag,不要顺手改动已生成客户端的方法名。qr_login.py 的 QrSaveIn 用 snake_case(account_uid),tools.py 的账号组请求用 camelCase(accountId)。这是既有契约,改名会破坏生成客户端。GameSign_WindowStart / WindowEnd / ScheduledRun / ScheduledTime 是保留读取历史配置的旧字段,不参与调度。不要据此推断当前调度行为。EncryptValidator(),schema 侧同名字段已加SecretStr,异常分支未记录堆栈或上游响应credential_updates 回写,runner 内未直接写配置_read_game_sign_token(),兼容缺字段的旧账号GameSignInProgressError(409 或跳过),未静默等待锁_all_enabled_platforms_signed、manual_game_sign、_SUCCESS_STATUSES)保持一致LastSignDate 语义未混用;跨 0 点重取日期的行为保留_PLATFORM_ORDERwarning,未因新异常类型扩大白名单_notification_only 结果未进前端列表frontend/src/api/**;后端 schema 变更后已提示重新生成按 tests/AGENTS.md,开发时在 tests/tools/ 下编写签到测试用于本地验证(test_game_sign.py、test_game_sign_notification.py、test_miyoushe_qr.py、test_miyoushe_retry.py、test_contracts.py 等)。
提交或提 PR 时,测试文件的取舍见根目录 AGENTS.md「分支与 PR」。
前端改动只运行实际受影响的 *.test.ts。有缺口就在结果里说明,不编造验证结果。
useGameSignApi.ts 里写业务判断或错误吞掉逻辑。_check_system_time() 变成阻断条件,也不要让它占用签到锁。if。需要平台差异时加 provider 字段。아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.
로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.
아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.
从固定点(commit、branch、tag 或 merge-base)开始,沿两条轴线审查变更——规范(代码是否遵循仓库文档化的编码规范?)和规格(代码是否与原始 issue/PRD 的要求一致?)。两条审查线在并行子 agent 中运行,并以并排方式报告结果。当用户想审查一个分支、PR、进行中的变更,或要求"从 X 开始审查"时使用。
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
一场无休止的盘问,用来打磨你的计划或设计。
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
对用户的计划、决策或想法进行层层盘问。当用户想要压力测试自己的思考,或使用任何 'grill' 触发短语时使用。
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Define backend API contract standards for FastAPI services. Use when adding or refactoring HTTP/WebSocket endpoints in app/api, designing request/response schemas in app/models/schema.py, standardizing status/error contracts, and maintaining backward compatibility for clients.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Use when an AUTO-MAS PR is red in CI, when reproducing a dev-PR gate locally before pushing, when a workflow needs a skip-changelog or changelog-maintenance label, or when editing .github/workflows, .github/actions, scripts/changelog.py or scripts/check_reverts.py. Covers every gate that runs on a PR to dev and the exact local command for each.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Use when implementing, fixing, refactoring, or reviewing non-generated AUTO-MAS code, or when preparing code-style guidance, comments, docstrings, version notes, or Conventional Commit wording. Skip read-only diagnosis, explanation, exploration, and planning unless code conventions are requested.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.