Skip to content

feat: convert MAA GUI profiles to maa-cli task config file - #559

Open
nan-mu wants to merge 23 commits into
MaaAssistantArknights:mainfrom
nan-mu:feat/get-config-from-gui
Open

feat: convert MAA GUI profiles to maa-cli task config file#559
nan-mu wants to merge 23 commits into
MaaAssistantArknights:mainfrom
nan-mu:feat/get-config-from-gui

Conversation

@nan-mu

@nan-mu nan-mu commented Jul 10, 2026

Copy link
Copy Markdown

Summary

This PR adds GUI profile conversion to maa-cli by mapping MAA GUI TaskQueue entries into maa-cli task config shape, exposed through maa convert -g.

It lets users take an existing GUI profile node (specifically gui.new.json, located in MAA_ROOT_DIR\config for Windows MAA) and produce editable TOML/YAML/JSON task configs without hand-rewriting every field. Since this file stores the full configuration of the GUI program, converting it into a format supported by maa-cli saves significant manual migration effort.

What changed

  • add maa convert -g/--gui flag to the Convert subtask in crates/maa-cli/src/command.rs to run GUI conversion only when explicitly requested
  • add crates/maa-cli/src/config/gui.rs which contains the logic to transform specified GUI profile nodes into maa-cli v1 configs, including 9 unit tests targeting this new logic

Why

MAA GUI stores task settings in its own profile JSON shape, while maa-cli expects task configs with typed params, variants, and input templates.

Users who start from a GUI profile currently have no supported path to move those settings into maa-cli configs. This closes that gap by providing a deterministic conversion layer instead of manual migration.

User impact

This PR introduces the new -g/--gui flag. Users can now run:

maa convert -g profile.json tasks.toml
maa convert -g profile.json tasks.yaml

If the input file is not a JSON file but the flag is enabled, a notice will be printed:
The --gui flag is for converting MAA GUI profiles, which are typically JSON files; input <input file path> is not JSON

If a task within the GUI profile is disabled (IsEnable is false), the conversion layer will still process and include it in the final output, but it will emit a warning notification during the process.

Note: Whether disabled tasks should be explicitly omitted during conversion or if we can map this state into something like a task.condition is open for discussion.

Furthermore, if the GUI profile contains multiple configurations under ROOT.Configurations, the command-line interface will prompt the user to interactively select which node they want to convert:

maa convert -g crates/maa-cli/fixtures/gui/default_profile.json /tmp/y.yaml
1. Default [default]
2. Dev
3. 26/07/10 18:36:14
Please select a GUI configuration (empty for default): 1

The output is a normal maa-cli task config file that can be edited, versioned, and used with existing maa-cli workflows.

Validation

  • cargo test -p maa-cli gui::

  • 9 passed, 0 failed

  • cargo test -p maa-cli convert

  • 4 passed, 0 failed

  • Manual validation

  • cargo run -- convert -g crates/maa-cli/fixtures/gui/default_profile.json /tmp/t.toml

  • cargo run -- convert -g crates/maa-cli/fixtures/gui/default_profile.json /tmp/y.yaml

Notes

  • Most fields in the GUI profile correspond to the MaaCore integration document; the primary task of gui.rs is renaming these fields, converting specific fields into condition, and placing others into params. I have roughly checked all fields, and theoretically none should cause MaaCore errors, though I will conduct actual integration testing later today or tomorrow. If you think it is necessary, I can write a document detailing the handling of each key in the GUI profile.
  • The GUI profile attached in this commit is based on a copy from my own PC at a certain point in time, so I would be glad to test with other different GUI profiles if anyone can provide them.
  • I noticed config-v2. I am willing to continue working on this feature once config-v2 stabilizes, enabling the GUI profile to convert directly into the config-v2 format.

Summary by Sourcery

摘要 (Summary)

本 PR 为 maa-cli 增加了 GUI 配置转换功能。该功能通过将 MAA GUI 的 TaskQueue 项映射到 maa-cli 的任务配置结构中,并通过 maa convert -g 命令对外提供。

它允许用户直接使用现有的 GUI 配置文件(具体为 gui.new.json,对于 Windows MAA,该文件位于 MAA_ROOT_DIR\config)并生成可编辑的 TOML/YAML/JSON 任务配置,无需手动重写每个字段。由于该文件存储了 GUI 程序的全部配置内容,能够将其转换为 maa-cli 支持的格式可以大幅节省手动配置的时间成本。

变更内容 (What changed)

  • crates/maa-cli/src/command.rs 处的 Convert 子任务中添加一个名为 gui 的 flag,引入 maa convert -g/--gui 参数,仅在明确请求时才执行 GUI 转换。
  • 添加 crates/maa-cli/src/config/gui.rs,文件包含将所有 GUI profile 指定节点变换为 maa-cli v1 config 的逻辑,并且包含 9 个关于新逻辑的测试。

变更原因 (Why)

MAA GUI 将任务设置存储在自有的配置文件 JSON 结构中,而 maa-cli 则期望任务配置具备类型化的参数、变体和输入模板。

目前,从 GUI 配置文件开始使用的用户没有一条受支持的途径将这些设置迁移到 maa-cli 配置中。本 PR 通过提供一个确定性的转换层来填补这一空白,从而免去了手动迁移的麻烦。

用户影响 (User impact)

本 PR 引入了新的 -g/--gui 标志。用户现在可以运行:

maa convert -g profile.json tasks.toml
maa convert -g profile.json tasks.yaml

如果输入文件不是 JSON 文件但开启了该标志,程序将会输出一条提示信息:
The --gui flag is for converting MAA GUI profiles, which are typically JSON files; input <input file path> is not JSON

如果 GUI profile 中的某个任务被禁用(IsEnable 为 false),转换层在转换中仍会接受并包含它,但在过程中会输出一条警告提示。

注意: 关于禁用任务应当直接忽略还是映射到类似 task.condition 的属性中,目前仍开放讨论。

此外,如果 GUI profile 中包含多个配置(指 ROOT.Configurations 有多个子项),命令行界面将会提示用户交互式地选择需要被转换的节点:

maa convert -g crates/maa-cli/fixtures/gui/default_profile.json /tmp/y.yaml
1. Default [default]
2. Dev
3. 26/07/10 18:36:14
Please select a GUI configuration (empty for default): 1

输出是一个正常的 maa-cli 任务配置文件,可以进行编辑、版本控制,并用于现有的 maa-cli 工作流中。

验证 (Validation)

  • cargo test -p maa-cli gui::

  • 9 通过, 0 失败

  • cargo test -p maa-cli convert

  • 4 通过, 0 失败

  • 手动验证 (Manual validation)

  • cargo run -- convert -g crates/maa-cli/fixtures/gui/default_profile.json /tmp/t.toml

  • cargo run -- convert -g crates/maa-cli/fixtures/gui/default_profile.json /tmp/y.yaml

注意事项 (Notes)

  • 对于 GUI profile 中的大部分字段都可以在 MaaCore 集成文档中找到对应,gui.rs 的主要任务就是将 these 字段重命名。并且特定字段转换为 condition,其他放到 params。我粗略的检查过所有字段,这些理论上都不会引起 MaaCore 报错,但今天稍晚或是明天我才会实际进行测试。如果你们认为有必要,我可以去写一个文档讲述对 GUI profile 每个键的处理细节。
  • 本次提交中附上的 GUI profile 是我自己在某个时间从我电脑上复制的,所以如果有人能提供其他不一样的 GUI profile 我很乐意去测试。
  • 我注意到了 config-v2。我愿意在 config-v2 稳定后为这个功能继续编写代码,让 GUI profile 直接转为 config-v2 格式的配置文件。

Summary by Sourcery

在 maa-cli 中新增支持,通过新的 CLI 标志将 MAA GUI 配置文件转换为 maa-cli 任务配置文件。

新功能:

  • maa convert 命令引入 -g/--gui 标志,用于触发 GUI 配置转换。
  • 支持将 MAA GUI 的 TaskQueue 条目转换为 maa-cli v1 任务配置结构,包括对多配置文件(multi-configuration profiles)的交互式选择处理。

增强:

  • 当在非 JSON 输入上使用 --gui 标志以及在转换被禁用的 GUI 任务时发出警告,以改进用户反馈。
  • 添加专门用于 GUI 的转换模块以及相关的样例和测试,用于确保将 GUI 配置字段正确映射到 maa-cli 配置中。

测试:

  • 添加单元测试,覆盖配置选择、GUI 到配置的转换、新标志的 CLI 解析,以及默认 GUI 配置文件的端到端转换。
Original summary in English

Summary by Sourcery

Add support in maa-cli to convert MAA GUI profiles into maa-cli task configuration files via a new CLI flag.

New Features:

  • Introduce a -g/--gui flag to the maa convert command to trigger GUI profile conversion.
  • Support converting MAA GUI TaskQueue entries into maa-cli v1 task config structures, including handling multi-configuration profiles with interactive selection.

Enhancements:

  • Warn when the --gui flag is used with non-JSON inputs and when disabled GUI tasks are converted, improving user feedback.
  • Add a GUI-specific conversion module and associated fixtures and tests to ensure correct mapping of GUI profile fields into maa-cli configs.

Tests:

  • Add unit tests for configuration selection, GUI-to-config conversion, CLI parsing of the new flag, and end-to-end conversion of a default GUI profile.

nan-mu and others added 4 commits July 9, 2026 02:53
Introduce gui::convert for TaskQueue profiles with StartUp and Fight
mappings, plus a default GUI profile fixture for regression tests.

Co-authored-by: Cursor <cursoragent@cursor.com>
Wire gui::convert behind convert -g, map Infrast/Recruit/Mall/Award/
Roguelike/Reclamation from GUI TaskQueue, and align StartUp output with
daily.json input templates.

Co-authored-by: Cursor <cursoragent@cursor.com>
The --gui flag targets MAA GUI profile JSON files; warn users when the input uses another format.

Co-authored-by: Cursor <cursoragent@cursor.com>
Prompt users to pick a configuration when converting GUI profiles with multiple entries; use Current as the default in batch mode.

Co-authored-by: Cursor <cursoragent@cursor.com>

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

Hey - 我发现了 1 个问题,并给出了一些总体反馈:

  • GUI 转换辅助函数在每个子模块中都重新实现了大量几乎相同的测试工具(临时文件读写、JSON 加载/存储);建议将这些工具集中到一个共享的测试辅助模块中,以减少重复并让测试更易维护。
  • convert_task 中,未知的 GUI $type 条目会被静默丢弃;可以考虑记录日志或以其他方式暴露被跳过的任务类型,这样用户就能理解为什么某些 GUI 任务没有出现在生成的配置中。
  • 通过 SelectD 的交互式 select_configuration 提示在非交互环境(例如自动化场景)中会阻塞;建议增加一个标志或基于环境变量的覆盖机制,当存在多个配置项时可以以非交互方式选择配置。
给 AI Agent 的提示
请根据以下代码审查评论进行修改:

## 总体评论
- GUI 转换辅助函数在每个子模块中都重新实现了大量几乎相同的测试工具(临时文件读写、JSON 加载/存储);建议将这些工具集中到一个共享的测试辅助模块中,以减少重复并让测试更易维护。
-`convert_task` 中,未知的 GUI `$type` 条目会被静默丢弃;可以考虑记录日志或以其他方式暴露被跳过的任务类型,这样用户就能理解为什么某些 GUI 任务没有出现在生成的配置中。
- 通过 `SelectD` 的交互式 `select_configuration` 提示在非交互环境(例如自动化场景)中会阻塞;建议增加一个标志或基于环境变量的覆盖机制,当存在多个配置项时可以以非交互方式选择配置。

## 具体评论

### 评论 1
<location path="crates/maa-cli/src/config/gui.rs" line_range="71-80" />
<code_context>
+    Ok(object!("tasks" => tasks??))
+}
+
+fn convert_task(task: &MAAValue) -> Result<Option<MAAValue>> {
+    // GUI task discriminator, e.g. "FightTask"
+    let type_tag = task
+        .get("$type")
+        .and_then(|v| v.as_str())
+        .context("GUI task missing $type")?;
+    match type_tag {
+        "StartUpTask" => start_up::convert_start_up_task(task),
+        "FightTask" => fight::convert_fight_task(task),
+        "InfrastTask" => infrast::convert_infrast_task(task),
+        "RecruitTask" => recruit::convert_recruit_task(task),
+        "MallTask" => mall::convert_mall_task(task),
+        "AwardTask" => award::convert_award_task(task),
+        "RoguelikeTask" => roguelike::convert_roguelike_task(task),
+        "ReclamationTask" => reclamation::convert_reclamation_task(task),
+        _ => Ok(None),
+    }
</code_context>
<issue_to_address>
**suggestion (bug_risk):** 静默丢弃未知的 GUI 任务类型可能会隐藏配置问题;建议至少记录一条日志。

在 `convert_task` 中,未匹配到的 `$type` 会返回 `Ok(None)`,因此相关任务会被静默丢弃:

```rust
match type_tag {
    // ...
    "ReclamationTask" => reclamation::convert_reclamation_task(task),
    _ => Ok(None),
}
```
为避免隐藏配置错误或新增加的任务类型,请至少增加一条 `log::warn!`,包含 `$type`(以及可能的标识符如 `Name`)。你可以暂时保留当前宽松的行为,之后再通过一个标志在“警告”与“未知类型视为硬错误”之间切换。

建议实现:

```rust
fn convert_task(task: &MAAValue) -> Result<Option<MAAValue>> {
    // GUI task discriminator, e.g. "FightTask"
    let type_tag = task
        .get("$type")
        .and_then(|v| v.as_str())
        .context("GUI task missing $type")?;
    match type_tag {
        "StartUpTask" => start_up::convert_start_up_task(task),
        "FightTask" => fight::convert_fight_task(task),
        "InfrastTask" => infrast::convert_infrast_task(task),
        "RecruitTask" => recruit::convert_recruit_task(task),
        "MallTask" => mall::convert_mall_task(task),
        "AwardTask" => award::convert_award_task(task),
        "RoguelikeTask" => roguelike::convert_roguelike_task(task),
        "ReclamationTask" => reclamation::convert_reclamation_task(task),
        _ => {
            let name = task
                .get("Name")
                .and_then(|v| v.as_str())
                .unwrap_or("<unknown>");
            log::warn!(
                "Unknown GUI task type `{}` (Name: `{}`); skipping task",
                type_tag,
                name
            );
            Ok(None)
        }
    }
}

```

如果本模块尚未使用 `log` crate,请确保:
1.`Cargo.toml` 中已添加 `log` 依赖(很可能已经存在)。
2. 在可执行程序中初始化了某个日志实现(例如 `env_logger``tracing` 的桥接等),以确保 `log::warn!` 实际能输出日志。

由于函数签名与返回行为保持不变(未知类型仍返回 `Ok(None)`),因此不需要对调用方做进一步修改。
</issue_to_address>

Sourcery 对开源项目是免费的——如果你觉得这些审查有帮助,请考虑分享 ✨
帮我变得更有用!请对每条评论点击 👍 或 👎,我会根据你的反馈改进后续审查。
Original comment in English

Hey - I've found 1 issue, and left some high level feedback:

  • The GUI conversion helpers reimplement a lot of near-identical test utilities (temp file write/read, JSON load/store) in each submodule; consider centralizing these into a shared test helper to reduce repetition and keep the tests easier to maintain.
  • Unknown GUI $type entries are silently dropped in convert_task; it might be helpful to log or otherwise surface that a task type was skipped so users understand why some GUI tasks did not appear in the generated config.
  • The interactive select_configuration prompt via SelectD will block in non-interactive contexts (e.g. automation); consider adding a flag or environment-based override to select a configuration non-interactively when multiple entries exist.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The GUI conversion helpers reimplement a lot of near-identical test utilities (temp file write/read, JSON load/store) in each submodule; consider centralizing these into a shared test helper to reduce repetition and keep the tests easier to maintain.
- Unknown GUI `$type` entries are silently dropped in `convert_task`; it might be helpful to log or otherwise surface that a task type was skipped so users understand why some GUI tasks did not appear in the generated config.
- The interactive `select_configuration` prompt via `SelectD` will block in non-interactive contexts (e.g. automation); consider adding a flag or environment-based override to select a configuration non-interactively when multiple entries exist.

## Individual Comments

### Comment 1
<location path="crates/maa-cli/src/config/gui.rs" line_range="71-80" />
<code_context>
+    Ok(object!("tasks" => tasks??))
+}
+
+fn convert_task(task: &MAAValue) -> Result<Option<MAAValue>> {
+    // GUI task discriminator, e.g. "FightTask"
+    let type_tag = task
+        .get("$type")
+        .and_then(|v| v.as_str())
+        .context("GUI task missing $type")?;
+    match type_tag {
+        "StartUpTask" => start_up::convert_start_up_task(task),
+        "FightTask" => fight::convert_fight_task(task),
+        "InfrastTask" => infrast::convert_infrast_task(task),
+        "RecruitTask" => recruit::convert_recruit_task(task),
+        "MallTask" => mall::convert_mall_task(task),
+        "AwardTask" => award::convert_award_task(task),
+        "RoguelikeTask" => roguelike::convert_roguelike_task(task),
+        "ReclamationTask" => reclamation::convert_reclamation_task(task),
+        _ => Ok(None),
+    }
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Silently dropping unknown GUI task types may hide configuration issues; consider at least logging them.

In `convert_task`, unmatched `$type` values return `Ok(None)`, so those tasks are dropped silently:

```rust
match type_tag {
    // ...
    "ReclamationTask" => reclamation::convert_reclamation_task(task),
    _ => Ok(None),
}
```
To avoid hiding misconfigured or new task types, please add at least a `log::warn!` including the `$type` (and possibly an identifier like `Name`). You can keep the tolerant behavior now and later introduce a flag if you want to switch between warning and hard error for unknown types.

Suggested implementation:

```rust
fn convert_task(task: &MAAValue) -> Result<Option<MAAValue>> {
    // GUI task discriminator, e.g. "FightTask"
    let type_tag = task
        .get("$type")
        .and_then(|v| v.as_str())
        .context("GUI task missing $type")?;
    match type_tag {
        "StartUpTask" => start_up::convert_start_up_task(task),
        "FightTask" => fight::convert_fight_task(task),
        "InfrastTask" => infrast::convert_infrast_task(task),
        "RecruitTask" => recruit::convert_recruit_task(task),
        "MallTask" => mall::convert_mall_task(task),
        "AwardTask" => award::convert_award_task(task),
        "RoguelikeTask" => roguelike::convert_roguelike_task(task),
        "ReclamationTask" => reclamation::convert_reclamation_task(task),
        _ => {
            let name = task
                .get("Name")
                .and_then(|v| v.as_str())
                .unwrap_or("<unknown>");
            log::warn!(
                "Unknown GUI task type `{}` (Name: `{}`); skipping task",
                type_tag,
                name
            );
            Ok(None)
        }
    }
}

```

If the `log` crate is not yet used in this module, ensure that:
1. The `log` dependency is present in `Cargo.toml` (likely already is).
2. A logger implementation (e.g. `env_logger`, `tracing` bridge, etc.) is initialized in your binary so that `log::warn!` actually emits output.

No further changes to callers are required since the function signature and return behavior remain the same (`Ok(None)` for unknown types).
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread crates/maa-cli/src/config/gui.rs Outdated
- warn when skipping unsupported GUI task $type values
- add --gui-config flag and MAA_GUI_CONFIG env for non-interactive
  multi-profile selection
- merge pick_configuration into select_configuration
- consolidate per-task tests into gui::tests with gui_task_test! macro
@nan-mu

nan-mu commented Jul 13, 2026

Copy link
Copy Markdown
Author

本次修改采纳了 @sourcery-ai 的部分优化建议:
新增了一个接受字符串的 gui_config 标志,允许动态指定特定的配置文件,从而避免潜在的阻塞问题。
对于测试,将原 gui.rs 文件中的所有测试用例统一迁移至 maa::config::gui::tests 模块下。

English version

This PR incorporates several optimization suggestions from @sourcery-ai:

Added a gui_config flag that accepts a string to specify particular configuration files, preventing potential blocking issues.
Moved all test cases originally in gui.rs to the maa::config::gui::tests module to improve code organization.

@nan-mu

nan-mu commented Jul 22, 2026

Copy link
Copy Markdown
Author

Tested on my local environment, everything works as expected.

在我的设备上进行了测试,运行正常。

Test Logs / 测试日志
nan@nanmus-Mac-mini ~/L/A/c/c/profiles [1]> maa run daliy --log-file
Whether to start the game [Y/n]: 
1. Official [default]
2. YoStarEN
3. YoStarJP
Please select a client type (empty for default): 
Summary
----------------------------------------
[StartUp] 14:31:20 - 14:32:32 (1m 11s) Completed
----------------------------------------
[日常经验本] 14:32:33 - 14:39:34 (7m 1s) Completed
Fight LS-6 3 times, drops:
1. 中级作战记录 × 2, 高级作战记录 × 4, 龙门币 × 432
2. 中级作战记录 × 2, 高级作战记录 × 4, 龙门币 × 432
3. 中级作战记录 × 2, 高级作战记录 × 4, 龙门币 × 432
total drops: 中级作战记录 × 6, 高级作战记录 × 12, 龙门币 × 1296
----------------------------------------
[] 14:39:35 - 14:50:02 (10m 26s) Completed
Mfg(PureGold) with operators: unknown
Mfg(PureGold) with operators: unknown
Mfg(CombatRecord) with operators: unknown
Mfg(CombatRecord) with operators: unknown
Trade(Money) with operators: unknown
Trade(Money) with operators: unknown
----------------------------------------
[] 14:50:02 - 14:51:56 (1m 53s) Completed
Detected tags:
1. ★★★ 狙击干员, 近战位, 远程位, 新手, 输出, Refreshed
2. ★★★★ 支援, 新手, 位移, 近卫干员, 快速复活, Recruited
3. ★★★ 医疗干员, 先锋干员, 近战位, 输出, 生存, Refreshed
4. ★★★ 近卫干员, 狙击干员, 辅助干员, 远程位, 群攻, Recruited
5. ★★★ 医疗干员, 辅助干员, 防护, 狙击干员, 先锋干员, Recruited
6. ★★★ 近卫干员, 狙击干员, 辅助干员, 先锋干员, 新手, Recruited
Recruited 4 times
Refreshed 2 times
----------------------------------------
[] 14:51:57 - 14:55:42 (3m 45s) Completed
----------------------------------------
[] 14:55:42 - 14:56:23 (40s) Completed
Converted config / 转换的配置
[[tasks]]
type = "StartUp"

[tasks.params.client_type]
alternatives = [
    "Official",
    "YoStarEN",
    "YoStarJP",
]
description = "a client type"

[tasks.params.client_type.deps]
start_game_enabled = true

[tasks.params.start_game_enabled]
default = true
description = "start the game"

[[tasks]]
type = "Fight"
name = "日常经验本"
strategy = "merge"

[[tasks.variants]]

[tasks.variants.condition]
type = "Weekday"
weekdays = [
    "Mon",
    "Wed",
    "Fri",
]

[tasks.variants.params]
stage = "LS-6"
medicine_expire_days = 2

[[tasks]]
type = "Fight"
name = "日常龙门币"
strategy = "merge"

[[tasks.variants]]

[tasks.variants.condition]
type = "Weekday"
weekdays = [
    "Tue",
    "Thu",
    "Sat",
]

[tasks.variants.params]
stage = "CE-6"
medicine_expire_days = 2

[[tasks]]
type = "Infrast"
name = ""

[tasks.params]
mode = 0
facility = [
    "Mfg",
    "Trade",
    "Control",
    "Power",
    "Reception",
    "Office",
    "Dorm",
    "Processing",
    "Training",
]
drones = "Money"
threshold = 0.3
replenish = true
dorm_notstationed_enabled = true
dorm_trust_enabled = true
reception_message_board = true
reception_clue_exchange = true
reception_send_clue = true

[[tasks]]
type = "Recruit"
name = ""

[tasks.params]
times = 4
extra_tags_mode = 0
refresh = true
expedite = true
select = [
    5,
    4,
    3,
]
confirm = [
    5,
    4,
    3,
]

[tasks.params.recruitment_time]
4 = 540
3 = 540

[[tasks]]
type = "Mall"
name = ""

[tasks.params]
shopping = true
credit_fight = false
formation_index = 0
visit_friends = true
buy_first = [
    "加急许可",
    "招聘许可",
]
blacklist = [
    "",
    "家具",
]
force_shopping_if_credit_full = true
only_buy_discount = false
reserve_max_credit = false

[[tasks]]
type = "Award"
name = ""

[tasks.params]
award = true
mail = true
recruit = false
orundum = true
mining = true
specialaccess = true

@codecov

codecov Bot commented Jul 27, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 47.70477% with 581 lines in your changes missing coverage. Please review.
✅ Project coverage is 68.99%. Comparing base (aebb5e9) to head (1e972d0).

Files with missing lines Patch % Lines
crates/maa-cli/src/config/migrate/wpf/fight.rs 23.17% 173 Missing and 6 partials ⚠️
crates/maa-cli/src/config/migrate/wpf/mod.rs 51.48% 83 Missing and 15 partials ⚠️
crates/maa-cli/src/config/migrate/wpf/recruit.rs 0.00% 59 Missing ⚠️
crates/maa-cli/src/config/migrate/wpf/roguelike.rs 0.00% 54 Missing ⚠️
crates/maa-cli/src/config/migrate/wpf/infrast.rs 86.62% 26 Missing and 16 partials ⚠️
...ates/maa-cli/src/config/migrate/wpf/reclamation.rs 0.00% 34 Missing ⚠️
crates/maa-cli/src/config/migrate/wpf/start_up.rs 0.00% 33 Missing ⚠️
crates/maa-cli/src/config/migrate/wpf/mall.rs 0.00% 32 Missing ⚠️
crates/maa-cli/src/activity.rs 57.89% 14 Missing and 2 partials ⚠️
crates/maa-cli/src/config/migrate/mod.rs 73.77% 13 Missing and 3 partials ⚠️
... and 4 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #559      +/-   ##
==========================================
- Coverage   72.36%   68.99%   -3.38%     
==========================================
  Files          72       82      +10     
  Lines        6804     7869    +1065     
  Branches     6804     7869    +1065     
==========================================
+ Hits         4924     5429     +505     
- Misses       1537     2059     +522     
- Partials      343      381      +38     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@wangl-cc wangl-cc left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

感谢贡献,我大概看了一下这个 PR。这个功能的方向是有价值的,不过还有几个需要修正的问题。

  1. 目前 maa convert 的职责是保持数据结构不变,只在 TOML、YAML 和 JSON 之间转换序列化格式;这里实际上是在解析 MAA WPF GUI 的配置结构,将 TaskQueue 中的任务和字段映射成 maa-cli v1 task config。转换过程中还会选择 GUI configuration、重组字段,并可能跳过不支持的任务,因此更接近一次有损的语义迁移。

    我倾向于将命令调整为:

    maa migrate wpf <input> [output]

    这样可以清楚地区分:

    • convert:同一数据结构之间的 TOML/YAML/JSON 格式转换;
    • migrate:从外部配置模型迁移到 maa-cli 配置模型;
    • import:将已有配置安装到 maa-cli 的配置目录。
  2. 当前迁移是有损的,部分影响执行的参数没有处理,但用户无法知道具体丢失了什么。第一版不一定支持所有字段,但是应该在汇总被跳过的任务和字段。否则生成的文件可以解析,执行行为可能与原 GUI 配置不同。

  3. 禁用任务会在迁移后变成可执行任务。当前 IsEnable = false 只会产生 warning,任务仍然会写入,而且没有保留禁用语义。这可能导致用户迁移后意外执行原本关闭的肉鸽、生息演算等任务。

@wangl-cc

Copy link
Copy Markdown
Member

此外,你可以需要 rebase 或者 merge 一下 main,CI 里面有很多在 main 里面修复的问题。

nan-mu added 4 commits July 28, 2026 01:39
Retarget GUI migration to maa migrate wpf and the newer gui.new.json
shape from maa v6.16.3. StartUp now maps RuntimeSettings ClientType /
StartGame and conditional AccountName; other task types remain as-is
for follow-up.

zh: 按 maa v6.16.3 重构 WPF 迁移,并完成 StartUp 相关逻辑
Wire UseWeeklySchedule and UseOptionalStage into Fight variants
Map medicine, stone, times, drops, series, and expiring medicine into shared params
Derive per-stage Always/Weekday/OnSideStory conditions via StageActivityV2
Document the Fight migration rules in zh-CN migrate.md

从 WPF 战斗任务迁移关卡变体与开放条件,并补充中文文档
Add maa migrate wpf for semantic migration from GUI TaskQueue
Preserve disabled tasks with Never conditions and report skipped fields
Map Fight stage variants, shared params, and open-day conditions
Reject unsupported custom Infrast plans instead of silently dropping them
Document WPF task mappings in zh-CN migrate.md

将 WPF GUI 任务迁移为 maa-cli 配置,并补充跳过汇总与中文文档
@nan-mu

nan-mu commented Aug 3, 2026

Copy link
Copy Markdown
Author

抱歉鸽了一段时间。现在接口都和您描述的一样了,然后我才发现gui有一些选择关卡的逻辑,补充了这部分。下面是ai的总结:

对审查意见的回应

1. convert vs migrate

认同原先放在 convert 下语义不准确。现已拆分为:

命令 职责
maa convert 同一数据结构下的 TOML / YAML / JSON 序列化格式转换
maa migrate wpf <input> [output] 从 WPF GUI 配置模型做有损语义迁移,映射到 maa-cli task config
maa import 将已有配置安装进 maa-cli 配置目录

迁移流程包括:多配置选择(--config / MAA_GUI_CONFIG)、TaskQueue 任务与字段映射、跳过不支持的任务类型,因此不再伪装成无损格式转换。

2. 有损迁移需要可见汇总

第一版不要求覆盖全部 GUI 字段,但迁移结束会输出摘要,包括:

  • 被跳过的任务类型(及可选名称)
  • 有实际取值却未映射的字段
  • IsEnable = false 而被保留为禁用的任务

这样生成的配置即使可解析,用户也能对照摘要检查与原 GUI 行为的差异。

3. 禁用任务不能变成可执行

IsEnable = false 的任务仍会写入结果,但会附带永不满足的条件(Not { Always };若已有 variants 则与原条件 And),保证迁移后不会意外跑起原本关闭的肉鸽、生息演算等。同时在摘要中标记为 disabled,便于核对。

本 PR 主要能力

  • StartUp / Fight / Infrast / Recruit / Mall / Award / Roguelike / Reclamation 字段映射
  • Fight:按 UseWeeklySchedule × UseOptionalStage 生成变体;可选关卡推导 Weekday / Always / OnSideStory;理智药 / 源石等公共参数写入
  • Infrast:自定义基建(Mode = Custom 或非空 Filename)直接报错,避免静默丢失
  • 文档:docs/zh-CN/migrate.md 按任务类型说明映射规则与已知限制

@nan-mu

nan-mu commented Aug 3, 2026

Copy link
Copy Markdown
Author

然后我还没测试,可能明天吧。跑粥的实体机炸了...

nan-mu added 2 commits August 4, 2026 15:07
Use if-let for single-pattern StartGame matching
Prefer slice to_vec for Weekday condition weekdays

修复 WPF 迁移中的 clippy 警告
Skip gui.new.json so upstream GUI field names do not fail spellcheck
Keep CI typos/rustfmt checks green for the migrate branch

对 typos 排除 WPF GUI 示例文件使其通过并让 rustfmt 检查通过
@nan-mu
nan-mu requested a review from wangl-cc August 5, 2026 10:33
@wangl-cc

wangl-cc commented Aug 5, 2026

Copy link
Copy Markdown
Member

感谢贡献,但是现在这个版本未来的维护成本太高了,wpf.rs 已经两千行了,而且大量是 let Some(xxx) = xxx.get(xxx) 这种做法。考虑到 gui 的配置本身可能没有稳定、版本化的兼容性保证,所以我更希望他更结构化一点,比如这样:

#[derive(Deserialize)]
#[serde(rename_all = "PascalCase")]
struct WpfFightTask {
    name: Option<String>,
    is_enable: Option<bool>,

	// Other known fields ...
	
	#[serde(flatten)]
	unknown: BTreeMap<String, serde_json::Value>,
}

enum WpfTask {
    StartUp(WpfStartUpTask),
    Fight(WpfFightTask),
    Infrast(WpfInfrastTask),
    Recruit(WpfRecruitTask),
    // ...
    Unsupported {
        type_tag: String,
        raw: serde_json::Value,
    },
}

然后直接使用 serde 来反序列化整个 gui.json,这样相对更好维护一点。

然后直接对这些是 struct 来实现

trait ToCliconfig {}

impl ToCliConfig for WpfTask {}

这样的做法,应该会更清晰一点。

nan-mu added 6 commits August 6, 2026 13:57
zh: 删除default_profile.json example
Move the CLI entry into wpf.rs and parse profiles as serde_json::Value first
Drop --format and MAA_GUI_CONFIG; default to adjacent .toml when output is omitted
Require .json input and take the selected configuration by ownership

重构 WPF 迁移入口并简化配置选择与输出路径
注释旧逻辑,为迁移做准备
Build CLI Fight output from FightCliTask/FightParams and From/TryFrom instead of MAAValue insert trees.
Serialize Condition/TimeOffset/ClientType so weekly and stage open conditions stay typed.
Remove the legacy MAAValue Fight path and temporary parity tests until a unified suite lands.

将 WPF Fight 迁移改为原型结构体序列化,并移除旧路径与临时测试
Migrate remaining Infrast/Recruit/Mall/Award/Roguelike/Reclamation via Wpf*/Cli* structs and From/TryFrom.
Reuse Fight/StartUp patterns with Gui.RuntimeSettings for StartUp and shared unknown-field reporting.
Drop legacy MAAValue insert paths; keep Unsupported types skipped in the migration summary.

完成 WPF GUI 任务迁移的结构化实现,并覆盖剩余任务类型
Put shared Fight options on task params so variants only select stages.
Drop ForceRefresh→expedite mapping and warn on unsupported/dangerous recruit settings.
Add zh-CN WPF migrate docs and refresh the example output.

修正 WPF 理智作战/公招迁移并补充中文文档
@nan-mu

nan-mu commented Aug 8, 2026

Copy link
Copy Markdown
Author

现在所有转换都通过类型了,路径是json->wpf类型->cli类型。然后ToCliconfig 没实现,wpf到cli用TryFrom。感觉专门写一个ToCliconfig 没啥必要?然后之前那个2k行其实是包含测试的,现在这个版本1.6k行不包含,如果这版没问题我再写测试。虽然代码更多了,但有一说一确实靠结构体约束转换行为更容易维护。

文档删除了一些细节,现在主要讲需要特殊处理的部分。

这是ai总结:

针对维护性反馈的重构说明

本段改动按该方向重写了 WPF GUI → maa-cli 的迁移路径:不再用大量 let Some(x) = v.get(...) 手写解析,改为 serde 结构化反序列化 + 按任务类型的转换

范围

基线 b0370e8999bb0b55aba66f7ce5c6b40e4e47af6f
当前 63c823f833e3d150aca999158eafd72dff2423b8
体量 约 −2256 行净删(13 files;wpf.rs 约 2008 → 1601 行)

设计对齐

审核建议的核心是:

  1. 用带 #[serde(rename_all = "PascalCase")] / #[serde(flatten)] unknown 的 struct 描述 GUI 任务
  2. 用 enum 按 $type 分派
  3. 对 struct 做「转成 cli 配置」的清晰实现,而不是在巨型函数里插 MAAValue

当前实现基本按此落地,细节如下。

入口与整体反序列化

  • 选定 profile 后,整份配置反序列化为 WpfConfiguration { TaskQueue, Gui }
  • TaskQueue 元素是 #[serde(tag = "$type")] enum WpfTask,已知类型映射到各子模块的 Wpf*Task,未知 $typeUnsupported 并记入摘要后跳过

按任务分模块

wpf.rs 拆成 start_up / fight / infrast / recruit / mall / award / roguelike / reclamation:每个模块内是 Wpf*Task(Deserialize)+ Cli*Task / Cli*Params(Serialize)+ TryFrom / From / to_maa_value()

未单独引入名为 ToCliConfig 的 trait,而是用 TryFrom / From + serialize_to_maa_value 表达同一意图:先落到可序列化的 CLI 原型,再转成 MAAValue 写出。未知字段用 #[serde(flatten)] unknown 收集,经 report_unknown_fields 进入 MigrationSummary

相关支撑改动

  • Condition / TimeOffset / ClientType 补上 Serialize(及合理的 skip_serializing_if),以便 Fight 等变体条件能类型化写出,而不再手搓 condition 对象树。

CLI / 入口行为(顺带简化)

  • maa migrate wpf:去掉 --formatMAA_GUI_CONFIG;格式由输出扩展名决定;未指定输出时默认写到同名 .toml
  • 输入要求为 .json;禁用任务改为写出 params.enable = false(不再用 never-true condition)
  • 迁移入口逻辑收拢到 wpf.rsmigrate/mod.rs 只保留摘要与 re-export

映射与语义修正(相对旧实现)

在结构化重写之外,也修正了若干迁移语义,并写入 docs/zh-CN/wpf.md

  1. Fight:药品 / 源石 / 次数 / 掉落 / 过期药等共享项放在任务级 params;变体只负责关卡与开放条件(Weekday / Always / OnSideStory,可与周计划 And
  2. RecruitForceRefresh 不再映射为 expedite(GUI 与 MaaCore 语义不同);开启时记跳过并警告;Level6Choose 有危险行为警告
  3. StartUpclient_type / start_game_enabled 来自 Gui.RuntimeSettings,而非 StartUp 任务本体
  4. Infrast Custom:自定义基建计划仍不支持,明确报错

文档与示例

  • 新增 docs/zh-CN/wpf.md(用法、任务映射、有损摘要说明)
  • 刷新 config_examples/wpf/;删除体积很大的 fixtures/gui/default_profile.json 示例

提交序列(便于按步 review)

  1. d217b28 — 删除过大的 default profile 示例
  2. bdcb6f8 — 重构迁移入口与配置选择
  3. 507f189 — 为结构化迁移做准备
  4. dd2433d — Fight 先改为 typed Serialize 原型路径
  5. 7742a44 — 其余任务类型完成结构化迁移
  6. 63c823f — 修正 Fight/Recruit 语义并补中文文档

建议审核关注点

  • 结构化方向是否符合预期(serde 输入 + CLI 原型 Serialize + TryFrom,而非手写 get
  • 未引入 trait ToCliConfig、改用 TryFrom/to_maa_value 是否可接受(若希望统一 trait,可再抽一层薄封装)
  • Fight 共享 params vs variants、Recruit ForceRefresh、禁用任务 enable=false 等行为是否符合产品预期
  • GUI 无稳定 schema 下,unknown + 跳过摘要是否足够应对字段演进

Change the series field in the fight configuration from an optional type to a required i32
@nan-mu

nan-mu commented Aug 18, 2026

Copy link
Copy Markdown
Author

@wangl-cc 要不你看看这个写法有没有什么问题?没问题我就继续了,虽然好像只有单元测试了。

@wangl-cc

Copy link
Copy Markdown
Member

@wangl-cc 要不你看看这个写法有没有什么问题?没问题我就继续了,虽然好像只有单元测试了。

不好意思,我最近比较忙,我尽快今晚或者明天抽空看一下。

Read the schedule JSON during `maa migrate wpf` and emit plan_index variants.
Add `--custom-schedule` to override Filename when the profile is already custom.
Expand `~` in schedule paths; fail if the override is set on a non-custom profile.
从 WPF 迁移自定义基建排班,支持 --custom-schedule 覆盖排班文件。

@wangl-cc wangl-cc left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

感谢根据我的建议进行的重构,改成 Typed Serde 确实好一点。我大概看了一下,提出了几个我注意到的意见。不过具体每个每个 task 我还没有来的及看完,我让 AI 看了一下。他提出来几个意见:

  • WpfFightTask 虽然使用 flatten 收集了未知字段,但迁移时只调用了 report_disabled,没有调用未知字段报告逻辑。当前示例中的 UseCustomAnnihilation = true 和 AnnihilationStage 会被静默丢弃,迁移摘要中没有提示。这应该是 typed 重构过程中遗漏的调用,请补充报告并增加回归测试。

  • 可选活动关卡生成的 OnSideStory 条件仍然不够准确。迁移时会从活动数据中收集所有历史活动关卡,但运行时的 OnSideStory 只判断当前是否有任意 SideStory 开放。因此另一个活动开放时,已经关闭的活动关卡也可能满足条件。这里需要保留关卡与具体活动之间的关联,或者暂时不要将这类关卡转换成通用的 OnSideStory。

  • 未支持任务使用 #[serde(other)] Unsupported 后,原始 $type 和 Name 会丢失。例如 UserDataUpdateTask 在摘要中只会显示为 Unsupported。建议保留真实任务类型和名称,否则用户无法判断具体丢失了哪个任务。

这里面第二条可能目前也没办法直接匹配,CLI 没有类似的功能,所以可以先不管。

Comment thread crates/maa-cli/src/command.rs Outdated
output: Option<PathBuf>,
/// Select a named configuration when migrating a multi-profile GUI export
#[arg(long)]
config: Option<String>,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

这个叫 profile_name 是不是更好一点?

}

/// Print a user-facing summary of lossy migration decisions to stderr.
pub fn print(&self) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

这个方法可能作为 Display 的实现会更好一点

Comment on lines +65 to +66
let Some(configurations) = input.get_mut("Configurations") else {
bail!("GUI profile missing Configurations");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

这里应该可以直接 get_mut(xx).context(msg). 其他地方的 let Some(xxx) = xxx else { bail } 也应该改一下。

}
}

mod fight {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

我更倾向于每一个 mod 放在一个单独的文件,而不是全都挤在这个文件里面。加上测试之后可能 4000 行打不住。

Comment on lines +31 to +32
let value = BufReader::new(std::fs::File::open(file).context("Trying to open wpf profile")?);
let value = serde_json::from_reader(value)?;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

最外层的 WpfConfig 也可以结构化一下,而不是先反序列化成 json value。此外也可以验证一下 config_version 如果 Wpf 的配置 bump 了可能就不匹配。类似于这样:

#[derive(Debug, Deserialize)]
#[serde(rename_all = "PascalCase")]
struct WpfProfile {
    config_version: u32,
    #[serde(default)]
    current: Option<String>,
    configurations: BTreeMap<String, WpfConfiguration>,
    #[serde(flatten)]
    unknown: Map<String, Value>,
}

"`maa migrate wpf` expected a MAA GUI profile (typically .json); input {file:?} is not a JSON file"
);

let value = BufReader::new(std::fs::File::open(file).context("Trying to open wpf profile")?);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

没有必要 buffer reader,File 本身就可以 read。

Split `migrate/wpf.rs` into per-task modules under `migrate/wpf/`.
Read side-story expire time from StageActivityV2.json at migrate time
instead of emitting OnSideStory; fail after hot-update if the activity
file cannot be read. Rename migrate `--config` to `--profile-name`.

将 WPF 活动关迁移为 DateTime 截止时间,并拆分 migrate/wpf 模块。
@nan-mu

nan-mu commented Aug 20, 2026

Copy link
Copy Markdown
Author

ok,这些都做完了,测试也覆盖了,大概率没问题。但最近黑流树海的肉鸽选项在beta版的wpf更新了,等他们把协议文档补充后我会新增上去。到时候再合?

以下是ai总结:

Inline comments

  1. --config--profile-name:已改。
  2. MigrationSummaryDisplay:已实现 impl fmt::Display,入口用 eprint!("{summary}")
  3. get_mut(...).context(...)select_configuration 已改成 .remove(...).with_context(...);迁移路径里原先那类 let Some = ... else { bail } 也清掉了。
  4. 每个 task 单独文件wpf.rs 已拆成 migrate/wpf/{mod,start_up,fight,infrast,recruit,mall,award,roguelike,reclamation}.rs
  5. 最外层 WpfProfile 结构化 + config_version:已按你给的形状反序列化,并校验 ConfigVersion == 1,不匹配直接报错。
  6. 去掉多余 BufReader:已改为直接 File + from_reader

AI 补充的三点

  1. Fight 未知字段 / UseCustomAnnihilation:已接上 report_unknown_fields;自定义剿灭会映射到 AnnihilationStage,并补了回归测试。
  2. OnSideStory 不够准:不再写成 OnSideStory。迁移时从本地 StageActivityV2.json 读该关卡所属活动的截止时间,写成 DateTime { end, timezone }。活动文件读失败会先尝试热更新,仍失败则报错(避免瞎编条件)。示例里 TO-5 现为 end = "2026-08-22T03:59:59"
  3. Unsupported 丢掉 $type/Name:已保留 type_tag + name,摘要里会显示真实类型名。
    自定义基建排班(--custom-schedule / plan_index)也已经在前面 commit 里落地了。

nan-mu added 3 commits August 21, 2026 07:10
Add comprehensive tests for Fight, Mall, Reclamation, Recruit, and Roguelike tasks, ensuring correct mapping of parameters and handling of optional fields. Validate behavior for shared parameters, stage plans, and error handling for invalid configurations. Improve overall test coverage for WPF migration functionality.
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