diff --git a/src/MaaWpfGui/Helper/CopilotHelper.cs b/src/MaaWpfGui/Helper/CopilotHelper.cs
new file mode 100644
index 0000000000..f6ae6a5980
--- /dev/null
+++ b/src/MaaWpfGui/Helper/CopilotHelper.cs
@@ -0,0 +1,229 @@
+//
+// MaaWpfGui - A part of the MaaCoreArknights project
+// Copyright (C) 2021 MistEO and Contributors
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License v3.0 only as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY
+//
+#nullable enable
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using MaaWpfGui.Constants;
+using MaaWpfGui.Models;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace MaaWpfGui.Helper;
+
+public static class CopilotHelper
+{
+ public static async Task<(PrtsStatus Status, PrtsCopilotModel? Copilot)> RequestCopilotAsync(int copilotId)
+ {
+ var jsonResponse = await Instances.HttpService.GetStringAsync(new Uri(MaaUrls.PrtsPlusCopilotGet + copilotId)) ?? string.Empty;
+ if (jsonResponse is null)
+ {
+ return (PrtsStatus.NetworkError, null);
+ }
+
+ var json = JsonConvert.DeserializeObject(jsonResponse);
+ if (json != null && json.StatusCode == 200)
+ {
+ return (PrtsStatus.Success, json);
+ }
+
+ return (PrtsStatus.NotFound, null);
+ }
+
+ public static async Task<(PrtsStatus Status, PrtsCopilotSetModel? CopilotSet)> RequestCopilotSetAsync(int copilotId)
+ {
+ var jsonResponse = await Instances.HttpService.GetStringAsync(new Uri(MaaUrls.PrtsPlusCopilotSetGet + copilotId)) ?? string.Empty;
+ if (jsonResponse is null)
+ {
+ return (PrtsStatus.NetworkError, null);
+ }
+
+ var json = JsonConvert.DeserializeObject(jsonResponse);
+ if (json != null && json.StatusCode == 200)
+ {
+ return (PrtsStatus.Success, json);
+ }
+
+ return (PrtsStatus.NotFound, null);
+ }
+
+ public static async Task RateWebJsonAsync(int copilotId, string rating)
+ {
+ var response = await Instances.HttpService.PostAsJsonAsync(new Uri(MaaUrls.PrtsPlusCopilotRating), new { id = copilotId, rating });
+ if (response == null)
+ {
+ return PrtsStatus.NetworkError;
+ }
+
+ return PrtsStatus.Success;
+ }
+
+ public enum PrtsStatus
+ {
+ ///
+ /// 成功
+ ///
+ Success,
+
+ ///
+ /// 未找到
+ ///
+ NotFound,
+
+ ///
+ /// 网络错误
+ ///
+ NetworkError,
+ }
+
+ public class PrtsCopilotModel
+ {
+ [JsonProperty("status_code")]
+ public int StatusCode { get; set; }
+
+ [JsonProperty("data")]
+ public CopilotData? Data { get; set; } = new();
+
+ public class CopilotData
+ {
+ [JsonProperty("id")]
+ public int Id { get; set; }
+
+ [JsonProperty("upload_time")]
+ public DateTime UploadTime { get; set; }
+
+ [JsonProperty("uploader_id")]
+ public string UploaderId { get; set; } = string.Empty;
+
+ [JsonProperty("uploader")]
+ public string Uploader { get; set; } = string.Empty;
+
+ [JsonProperty("views")]
+ public int Views { get; set; }
+
+ [JsonProperty("hot_score")]
+ public double HotScore { get; set; }
+
+ [JsonProperty("available")]
+ public bool Available { get; set; }
+
+ [JsonProperty("rating_level")]
+ public int RatingLevel { get; set; }
+
+ [JsonProperty("not_enough_rating")]
+ public bool NotEnoughRating { get; set; }
+
+ [JsonProperty("rating_ratio")]
+ public double RatingRatio { get; set; }
+
+ [JsonProperty("rating_type")]
+ public int RatingType { get; set; }
+
+ [JsonProperty("comments_count")]
+ public int CommentsCount { get; set; }
+
+ [JsonProperty("like")]
+ public int Like { get; set; }
+
+ [JsonProperty("dislike")]
+ public int Dislike { get; set; }
+
+ [JsonProperty("content")]
+ [JsonConverter(typeof(CopilotContentConverter))]
+ public object? Content { get; set; } = new();
+ }
+ }
+
+ public class PrtsCopilotSetModel
+ {
+ [JsonProperty("status_code")]
+ public int StatusCode { get; set; }
+
+ ///
+ /// Gets or sets 输出提示,code 200时不存在该项
+ /// e.g.: code=400, msg=作业不存在
+ ///
+ [JsonProperty("message")]
+ public string? Message { get; set; }
+
+ [JsonProperty("data")]
+ public CopilotSetData? Data { get; set; } = new();
+
+ public class CopilotSetData
+ {
+ [JsonProperty("id")]
+ public int Id { get; set; }
+
+ [JsonProperty("name")]
+ public string Name { get; set; } = string.Empty;
+
+ [JsonProperty("description")]
+ public string Description { get; set; } = string.Empty;
+
+ [JsonProperty("copilot_ids")]
+ public List CopilotIds { get; set; } = [];
+
+ [JsonProperty("creator_id")]
+ public string CreatorId { get; set; } = string.Empty;
+
+ [JsonProperty("creator")]
+ public string Creator { get; set; } = string.Empty;
+
+ [JsonProperty("create_time")]
+ public DateTime CreatedTime { get; set; }
+
+ [JsonProperty("update_time")]
+ public DateTime UpdatedTime { get; set; }
+
+ [JsonProperty("status")]
+ public string Status { get; set; } = string.Empty;
+ }
+ }
+
+ public class CopilotContentConverter : JsonConverter
+ {
+ public override bool CanConvert(Type objectType) => objectType == typeof(object);
+
+ public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
+ {
+ // 处理字符串或对象两种输入形式
+ JToken token = JToken.Load(reader);
+ if (token.Type == JTokenType.String)
+ {
+ // 如果 Content 是字符串,解析为嵌套 JSON
+ string jsonString = token.Value() ?? string.Empty;
+ token = JToken.Parse(jsonString);
+ }
+
+ if (token is JObject obj)
+ {
+ // 根据 "type" 字段判断类型
+ if (obj.TryGetValue("type", StringComparison.OrdinalIgnoreCase, out var typeToken) && typeToken.ToString() == "SSS")
+ {
+ return obj.ToObject(serializer);
+ }
+ else
+ {
+ return obj.ToObject(serializer);
+ }
+ }
+
+ throw new JsonSerializationException("Unsupported JSON structure for Content");
+ }
+
+ public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
+ {
+ writer.WriteValue(JsonConvert.SerializeObject(value));
+ }
+ }
+}
diff --git a/src/MaaWpfGui/Models/CopilotModel.cs b/src/MaaWpfGui/Models/CopilotModel.cs
new file mode 100644
index 0000000000..8667c08e6d
--- /dev/null
+++ b/src/MaaWpfGui/Models/CopilotModel.cs
@@ -0,0 +1,334 @@
+//
+// MaaWpfGui - A part of the MaaCoreArknights project
+// Copyright (C) 2021 MistEO and Contributors
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License v3.0 only as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY
+//
+#nullable enable
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using MaaWpfGui.Constants;
+using MaaWpfGui.Helper;
+using Newtonsoft.Json;
+
+namespace MaaWpfGui.Models;
+
+public class CopilotModel
+{
+ ///
+ /// Gets or sets 关卡名,必选。关卡中文名、code、stageId、levelId等,只要能保证唯一均可。
+ ///
+ [JsonProperty("stage_name")]
+ public string StageName { get; set; } = string.Empty;
+
+ [JsonProperty("opers")]
+ public List Opers { get; set; } = [];
+
+ [JsonProperty("groups")]
+ public List Groups { get; set; } = [];
+
+ ///
+ /// Gets or sets 战斗中的操作列表。有序,执行完前一个才会去执行下一个。必选
+ ///
+ [JsonProperty("actions")]
+ public List Actions { get; set; } = [];
+
+ ///
+ /// Gets or sets 最低要求 maa 版本号,必选。保留字段,暂未实现。
+ ///
+ [JsonProperty("minimum_required")]
+ public string? MinimumRequired { get; set; }
+
+ ///
+ /// Gets or sets 描述信息。
+ ///
+ [JsonProperty("doc")]
+ public Doc? Documentation { get; set; }
+
+ ///
+ /// Gets or sets 难度。可选,默认为 0
+ ///
+ [JsonProperty("difficulty")]
+ public DifficultyFlags Difficulty { get; set; }
+
+ public List<(string Output, string? Color)> Output()
+ {
+ var output = new List<(string, string?)>();
+ if (Documentation is not null)
+ {
+ var title = Documentation.Title;
+ if (!string.IsNullOrEmpty(title))
+ {
+ output.Add((title, Documentation.TitleColor ?? UiLogColor.Message));
+ }
+
+ var details = Documentation.Details;
+ if (!string.IsNullOrEmpty(details))
+ {
+ output.Add((details, Documentation.DetailsColor ?? UiLogColor.Message));
+ }
+ }
+
+ output.Add((string.Empty, null));
+ output.Add(("------------------------------------------------", null));
+ output.Add((string.Empty, null));
+ int count = 0;
+ foreach (var oper in Opers)
+ {
+ count++;
+ var localizedName = DataHelper.GetLocalizedCharacterName(oper.Name);
+ output.Add(($"{localizedName}, {LocalizationHelper.GetString("CopilotSkill")} {oper.Skill}", UiLogColor.Message));
+ }
+
+ foreach (var group in Groups)
+ {
+ count++;
+ string groupName = group.Name + ": ";
+ var operInfos = group.Opers.Select(oper => $"{DataHelper.GetLocalizedCharacterName(oper.Name)} {oper.Skill}").ToList();
+
+ output.Add((groupName + string.Join(" / ", operInfos), UiLogColor.Message));
+ }
+
+ output.Add((string.Format(LocalizationHelper.GetString("TotalOperatorsCount"), count), UiLogColor.Message));
+ return output;
+ }
+
+ public class Oper
+ {
+ ///
+ /// Gets or sets 干员名,必选。
+ ///
+ [JsonProperty("name")]
+ public string Name { get; set; } = string.Empty;
+
+ ///
+ /// Gets or sets 技能序号,可选,默认为1,取值范围 [1, 3]
+ ///
+ [JsonProperty("skill")]
+ public int Skill { get; set; } = 1;
+
+ ///
+ /// Gets or sets 技能用法。可选,默认为 0
+ ///
+ /// - 0 - 不自动使用(依赖 "actions" 字段)
+ /// - 1 - 好了就用,有多少次用多少次(例如干员 棘刺 3 技能、桃金娘 1 技能等)
+ /// - 2 - 使用 X 次(例如干员 山 2 技能用 1 次、重岳 3 技能用 5 次,通过 "skill_times" 字段设置)
+ /// - 3 - 自动判断使用时机(画饼.jpg)
+ ///
+ ///
+ [JsonProperty("skill_usage")]
+ public int SkillUsage { get; set; }
+
+ ///
+ /// Gets or sets 技能使用次数。可选,默认为 1
+ ///
+ [JsonProperty("skill_times")]
+ public int SkillTimes { get; set; } = 1;
+
+ ///
+ /// Gets or sets 练度要求。保留接口,暂未实现。可选,默认为空
+ ///
+ [JsonProperty("requirements")]
+ public Requirements? Requirements { get; set; }
+ }
+
+ public class Group
+ {
+ ///
+ /// Gets or sets 群组名,必选
+ ///
+ [JsonProperty("name")]
+ public string Name { get; set; } = string.Empty;
+
+ ///
+ /// Gets or sets 干员列表。
+ ///
+ [JsonProperty("opers")]
+ public List Opers { get; set; } = [];
+ }
+
+ public class Action
+ {
+ ///
+ /// Gets or sets 操作类型,可选,默认为 "Deploy"。
+ ///
+ /// - "Deploy" - 部署
+ /// - "Skill" - 技能
+ /// - "Retreat" - 撤退
+ /// - "SpeedUp" - 二倍速
+ /// - "BulletTime" - 子弹时间
+ /// - "SkillUsage" - 技能用法
+ /// - "Output" - 打印
+ /// - "SkillDaemon" - 摆完挂机
+ /// - "MoveCamera" - 移动镜头
+ ///
+ ///
+ [JsonProperty("type")]
+ public string Type { get; set; } = "Deploy";
+
+ ///
+ /// Gets or sets 击杀数条件,如果没达到就一直等待。可选,默认为 0,直接执行。
+ ///
+ [JsonProperty("kills")]
+ public int Kills { get; set; }
+
+ ///
+ /// Gets or sets 费用条件,如果没达到就一直等待。可选,默认为 0,直接执行。
+ ///
+ [JsonProperty("costs")]
+ public int Costs { get; set; }
+
+ ///
+ /// Gets or sets 费用变化量条件,如果没达到就一直等待。可选,默认为 0,直接执行。
+ ///
+ [JsonProperty("cost_changes")]
+ public int CostChanges { get; set; }
+
+ ///
+ /// Gets or sets CD 中干员数量条件,如果没达到就一直等待。可选,默认为 -1,不识别。
+ ///
+ [JsonProperty("cooling")]
+ public int Cooling { get; set; } = -1;
+
+ ///
+ /// Gets or sets 干员名 或 群组名, type 为 "部署" 时必选,为 "技能" | "撤退" 时可选。
+ ///
+ [JsonProperty("name")]
+ public string? Name { get; set; }
+
+ ///
+ /// Gets or sets 部署干员的位置。type 为 "部署" 时必选。type 为 "技能" | "撤退" 时可选。
+ ///
+ [JsonProperty("location")]
+ public List? Location { get; set; }
+
+ ///
+ /// Gets or sets 部署干员的干员朝向。 type 为 "部署" 时必选。
+ ///
+ /// - "Left" - 左
+ /// - "Right" - 右
+ /// - "Up" - 上
+ /// - "Down" - 下
+ /// - "None" - 无
+ ///
+ ///
+ [JsonProperty("direction")]
+ public string? Direction { get; set; }
+
+ ///
+ /// Gets or sets 修改技能用法。当 type 为 "技能用法" 时必选。
+ ///
+ [JsonProperty("skill_usage")]
+ public int SkillUsage { get; set; }
+
+ ///
+ /// Gets or sets 技能使用次数。可选,默认为 1。
+ ///
+ [JsonProperty("skill_times")]
+ public int SkillTimes { get; set; } = 1;
+
+ ///
+ /// Gets or sets 前置延时。可选,默认为 0, 单位毫秒。
+ ///
+ [JsonProperty("pre_delay")]
+ public int PreDelay { get; set; }
+
+ ///
+ /// Gets or sets 后置延时。可选,默认为 0, 单位毫秒。
+ ///
+ [JsonProperty("post_delay")]
+ public int PostDelay { get; set; }
+
+ ///
+ /// Gets or sets 移动镜头的距离。type 为 "移动镜头" 时必选。
+ ///
+ [JsonProperty("distance")]
+ public List? Distance { get; set; }
+
+ ///
+ /// Gets or sets 描述,可选。会显示在界面上,没有实际作用
+ ///
+ [JsonProperty("doc")]
+ public string? Doc { get; set; }
+
+ ///
+ /// Gets or sets 描述颜色,可选,默认灰色。会显示在界面上,没有实际作用
+ ///
+ [JsonProperty("doc_color")]
+ public string? DocColor { get; set; }
+ }
+
+ public class Requirements
+ {
+ ///
+ /// Gets or sets 精英化等级。可选,默认为 0, 不要求精英化等级。
+ ///
+ [JsonProperty("elite")]
+ public int Elite { get; set; }
+
+ ///
+ /// Gets or sets 干员等级。可选,默认为 0。
+ ///
+ [JsonProperty("level")]
+ public int Level { get; set; }
+
+ ///
+ /// Gets or sets 技能等级。可选,默认为 0。
+ ///
+ [JsonProperty("skill_level")]
+ public int SkillLevel { get; set; }
+
+ ///
+ /// Gets or sets 模组编号。可选,默认为 0。
+ ///
+ [JsonProperty("module")]
+ public int Module { get; set; }
+
+ ///
+ /// Gets or sets 潜能要求。可选,默认为 0。
+ ///
+ [JsonProperty("potentiality")]
+ public int Potentiality { get; set; }
+ }
+
+ public class Doc
+ {
+ [JsonProperty("title")]
+ public string? Title { get; set; }
+
+ [JsonProperty("title_color")]
+ public string? TitleColor { get; set; }
+
+ [JsonProperty("details")]
+ public string? Details { get; set; }
+
+ [JsonProperty("details_color")]
+ public string? DetailsColor { get; set; }
+ }
+
+ [Flags]
+ public enum DifficultyFlags
+ {
+ ///
+ /// 缺省,未设置
+ ///
+ None = 0,
+
+ ///
+ /// 普通
+ ///
+ Normal = 1,
+
+ ///
+ /// 突袭
+ ///
+ Raid = 2,
+ }
+}
diff --git a/src/MaaWpfGui/Models/SSSCopilotModel.cs b/src/MaaWpfGui/Models/SSSCopilotModel.cs
new file mode 100644
index 0000000000..9bf4b3f3d4
--- /dev/null
+++ b/src/MaaWpfGui/Models/SSSCopilotModel.cs
@@ -0,0 +1,231 @@
+//
+// MaaWpfGui - A part of the MaaCoreArknights project
+// Copyright (C) 2021 MistEO and Contributors
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License v3.0 only as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY
+//
+#nullable enable
+using System.Collections.Generic;
+using System.Linq;
+using MaaWpfGui.Constants;
+using MaaWpfGui.Helper;
+using Newtonsoft.Json;
+
+namespace MaaWpfGui.Models;
+
+public class SSSCopilotModel
+{
+ ///
+ /// Gets 协议类型,SSS 表示保全派驻,必选,不可修改
+ ///
+ [JsonProperty("type")]
+ public string Type { get; } = "SSS";
+
+ ///
+ /// Gets or sets 保全派驻地图名,必选,例:多索雷斯在建地块
+ ///
+ [JsonProperty("stage_name")]
+ public string StageName { get; set; } = string.Empty;
+
+ ///
+ /// Gets or sets 最低要求 maa 版本号,必选
+ ///
+ [JsonProperty("minimum_required")]
+ public string MinimumRequired { get; set; } = string.Empty;
+
+ ///
+ /// Gets or sets 描述,可选
+ ///
+ [JsonProperty("doc")]
+ public Doc? Documentation { get; set; }
+
+ ///
+ /// Gets or sets 开局导能元件选择,可选
+ ///
+ [JsonProperty("buff")]
+ public string? Buff { get; set; }
+
+ ///
+ /// Gets or sets 开局装备选择,横着数,可选。当前版本暂未实现,只会在界面上显示一下
+ ///
+ [JsonProperty("equipment")]
+ public List? Equipment { get; set; }
+
+ ///
+ /// Gets or sets 优选策略,可选。当前版本暂未实现,只会在界面上显示一下
+ ///
+ [JsonProperty("strategy")]
+ public string? Strategy { get; set; }
+
+ ///
+ /// Gets or sets 指定干员,可选
+ ///
+ [JsonProperty("opers")]
+ public List? Opers { get; set; }
+
+ ///
+ /// Gets or sets 剩余所需各职业人数,按费用排序随便拿,可选
+ ///
+ [JsonProperty("tool_men")]
+ public Dictionary? ToolMen { get; set; }
+
+ ///
+ /// Gets or sets 战斗开始时和战斗中途,招募干员、获取装备优先级
+ ///
+ [JsonProperty("drops")]
+ public List? Drops { get; set; }
+
+ ///
+ /// Gets or sets 黑名单,可选。在 drops 里不会选这些人。
+ ///
+ [JsonProperty("blacklist")]
+ public List? Blacklist { get; set; }
+
+ ///
+ /// Gets or sets 关卡信息
+ ///
+ [JsonProperty("stages")]
+ public List? Stages { get; set; }
+
+ public List<(string Output, string? Color)> Output()
+ {
+ var output = new List<(string, string?)>();
+
+ if (Documentation is not null)
+ {
+ var title = Documentation.Title;
+ if (!string.IsNullOrEmpty(title))
+ {
+ output.Add((title, Documentation.TitleColor ));
+ }
+
+ var details = Documentation.Details;
+ if (!string.IsNullOrEmpty(details))
+ {
+ output.Add((details, Documentation.DetailsColor ));
+ }
+ }
+
+ output.Add((string.Empty, null));
+ output.Add(("------------------------------------------------", null));
+ output.Add((string.Empty, null));
+
+ int count = 0;
+ foreach (var oper in Opers ?? [])
+ {
+ count++;
+ var localizedName = DataHelper.GetLocalizedCharacterName(oper.Name);
+ output.Add(($"{localizedName}, {LocalizationHelper.GetString("CopilotSkill")} {oper.Skill}", null));
+ }
+
+ output.Add((string.Format(LocalizationHelper.GetString("TotalOperatorsCount"), count), null));
+
+ if (Buff is not null)
+ {
+ string buffLog = LocalizationHelper.GetString("DirectiveECTerm");
+ var localizedBuffName = DataHelper.GetLocalizedCharacterName(Buff);
+ output.Add((buffLog + (string.IsNullOrEmpty(localizedBuffName) ? Buff : localizedBuffName), null));
+ }
+
+ if (ToolMen is not null)
+ {
+ string toolMenLog = LocalizationHelper.GetString("OtherOperators");
+ output.Add((toolMenLog + JsonConvert.SerializeObject(ToolMen), null));
+ }
+
+ if (Equipment is not null)
+ {
+ string equipmentLog = LocalizationHelper.GetString("InitialEquipmentHorizontal") + '\n';
+ output.Add((equipmentLog + string.Join('\n', Equipment.Chunk(4).Select(i => string.Join(",", i))), null));
+ }
+
+ if (Strategy is not null)
+ {
+ string strategyLog = LocalizationHelper.GetString("InitialStrategy");
+ output.Add((strategyLog + Strategy, null));
+ }
+
+ return output;
+ }
+
+ public class Doc
+ {
+ [JsonProperty("title")]
+ public string? Title { get; set; }
+
+ [JsonProperty("title_color")]
+ public string? TitleColor { get; set; }
+
+ [JsonProperty("details")]
+ public string? Details { get; set; }
+
+ [JsonProperty("details_color")]
+ public string? DetailsColor { get; set; }
+ }
+
+ public class Oper
+ {
+ [JsonProperty("name")]
+ public string Name { get; set; } = string.Empty;
+
+ [JsonProperty("skill")]
+ public int Skill { get; set; }
+
+ [JsonProperty("skill_usage")]
+ public int SkillUsage { get; set; }
+ }
+
+ public class Stage
+ {
+ [JsonProperty("stage_name")]
+ public string StageName { get; set; } = string.Empty;
+
+ [JsonProperty("strategies")]
+ public List? Strategies { get; set; }
+
+ [JsonProperty("draw_as_possible")]
+ public bool? DrawAsPossible { get; set; }
+
+ [JsonProperty("actions")]
+ public List? Actions { get; set; }
+
+ [JsonProperty("retry_times")]
+ public int? RetryTimes { get; set; }
+
+ public class Strategy
+ {
+ [JsonProperty("core")]
+ public string? Core { get; set; }
+
+ [JsonProperty("tool_men")]
+ public Dictionary? ToolMen { get; set; }
+
+ [JsonProperty("location")]
+ public List? Location { get; set; }
+
+ [JsonProperty("direction")]
+ public string? Direction { get; set; }
+ }
+
+ public class Action
+ {
+ [JsonProperty("type")]
+ public string Type { get; set; } = string.Empty;
+
+ [JsonProperty("name")]
+ public string? Name { get; set; }
+
+ [JsonProperty("location")]
+ public List? Location { get; set; }
+
+ [JsonProperty("kills")]
+ public int? Kills { get; set; }
+ }
+ }
+}
diff --git a/src/MaaWpfGui/ViewModels/UI/CopilotViewModel.cs b/src/MaaWpfGui/ViewModels/UI/CopilotViewModel.cs
index f3def12e4f..80e292c315 100644
--- a/src/MaaWpfGui/ViewModels/UI/CopilotViewModel.cs
+++ b/src/MaaWpfGui/ViewModels/UI/CopilotViewModel.cs
@@ -18,12 +18,14 @@ using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
+using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using MaaWpfGui.Constants;
using MaaWpfGui.Helper;
+using MaaWpfGui.Models;
using MaaWpfGui.Services;
using MaaWpfGui.States;
using Microsoft.Win32;
@@ -31,6 +33,7 @@ using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Serilog;
using Stylet;
+using static MaaWpfGui.Helper.CopilotHelper;
using DataFormats = System.Windows.Forms.DataFormats;
using Task = System.Threading.Tasks.Task;
@@ -41,18 +44,23 @@ namespace MaaWpfGui.ViewModels.UI
///
// 通过 container.Get(); 实例化或获取实例
// ReSharper disable once ClassNeverInstantiated.Global
- public class CopilotViewModel : Screen
+ public partial class CopilotViewModel : Screen
{
private readonly RunningState _runningState;
private static readonly ILogger _logger = Log.ForContext();
+ private static readonly SemaphoreSlim _semaphore = new(1, 1);
private readonly List _copilotIdList = []; // 用于保存作业列表中的作业的Id,对于同一个作业,只有都执行成功才点赞
private readonly List _recentlyRatedCopilotId = []; // TODO: 可能考虑加个持久化
private AsstTaskType _taskType = AsstTaskType.Copilot;
private const string CopilotIdPrefix = "maa://";
private const string TempCopilotFile = "cache/_temp_copilot.json";
private static readonly string[] _supportExt = [".json", ".mp4", ".m4s", ".mkv", ".flv", ".avi"];
- private const string CopilotJsonDir = "cache/copilot";
+ private const string CopilotJsonDir = "config/copilot";
private const string StageNameRegex = @"(?:[a-z]{0,3})(?:\d{0,2})-(?:(?:A|B|C|D|EX|S|TR|MO)-?)?(?:\d{1,2})(\(Raid\)(?=\.json))?";
+ private const string InvalidStageNameChars = @"[:',\.\(\)\|\[\]\?,。【】{};:]"; // 无效字符
+
+ [GeneratedRegex(InvalidStageNameChars)]
+ private static partial Regex InvalidStageNameRegex();
///
/// Gets the view models of log items.
@@ -166,7 +174,7 @@ namespace MaaWpfGui.ViewModels.UI
{
SetAndNotify(ref _filename, value);
ClearLog();
- UpdateFilename();
+ UpdateFilename(value);
}
}
@@ -230,11 +238,11 @@ namespace MaaWpfGui.ViewModels.UI
}
}
- ///
- /// Gets or sets a value indicating whether to use auto-formation.
- ///
private bool _useCopilotList;
+ ///
+ /// Gets or sets a value indicating whether 自动编队.
+ ///
public bool UseCopilotList
{
get => _useCopilotList;
@@ -260,6 +268,7 @@ namespace MaaWpfGui.ViewModels.UI
get => _copilotTaskName;
set
{
+ value = InvalidStageNameRegex().Replace(value ?? string.Empty, string.Empty).Trim();
SetAndNotify(ref _copilotTaskName, value);
}
}
@@ -365,10 +374,17 @@ namespace MaaWpfGui.ViewModels.UI
/// Paste clipboard contents.
///
// ReSharper disable once UnusedMember.Global
- public void PasteClipboardCopilotSet()
+ public async void PasteClipboardCopilotSet()
{
- IsCopilotSet = true;
- PasteClipboard();
+ StartEnabled = false;
+ UseCopilotList = true;
+ ClearLog();
+ if (Clipboard.ContainsText())
+ {
+ await GetCopilotSetAsync(Clipboard.GetText().Trim());
+ }
+
+ StartEnabled = true;
}
///
@@ -388,7 +404,7 @@ namespace MaaWpfGui.ViewModels.UI
return;
}
- Dictionary taskPairs = new();
+ CopilotId = 0;
foreach (var file in dialog.FileNames)
{
var fileInfo = new FileInfo(file);
@@ -401,62 +417,30 @@ namespace MaaWpfGui.ViewModels.UI
try
{
using var reader = new StreamReader(File.OpenRead(file));
- var jsonStr = await reader.ReadToEndAsync();
+ var str = await reader.ReadToEndAsync();
+ var payload = JsonConvert.DeserializeObject