//
// 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.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;
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;
namespace MaaWpfGui.ViewModels.UI
{
///
/// The view model of copilot.
///
// 通过 container.Get(); 实例化或获取实例
// ReSharper disable once ClassNeverInstantiated.Global
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 = "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.
///
public ObservableCollection LogItemViewModels { get; } = [];
///
/// Gets or private sets the view models of Copilot items.
///
public ObservableCollection CopilotItemViewModels { get; } = [];
///
/// Initializes a new instance of the class.
///
public CopilotViewModel()
{
DisplayName = LocalizationHelper.GetString("Copilot");
AddLog(LocalizationHelper.GetString("CopilotTip"), showTime: false);
_runningState = RunningState.Instance;
_runningState.IdleChanged += RunningState_IdleChanged;
var copilotTaskList = ConfigurationHelper.GetValue(ConfigurationKeys.CopilotTaskList, string.Empty);
if (string.IsNullOrEmpty(copilotTaskList))
{
return;
}
var list = JsonConvert.DeserializeObject>(copilotTaskList) ?? [];
for (int i = 0; i < list.Count; i++)
{
list[i].Index = i;
CopilotItemViewModels.Add(list[i]);
}
SaveCopilotTask();
}
private void RunningState_IdleChanged(object? sender, bool e)
{
Idle = e;
}
#region UI绑定及操作
#region Log
///
/// Adds log.
///
/// The content.
/// The font color.
/// The font weight.
/// Whether show time.
public void AddLog(string content, string color = UiLogColor.Trace, string weight = "Regular", bool showTime = true)
{
Execute.OnUIThread(() =>
{
LogItemViewModels.Add(new LogItemViewModel(content, color, weight, "HH':'mm':'ss", showTime: showTime));
if (showTime)
{
_logger.Information(content);
}
});
// LogItemViewModels.Insert(0, new LogItemViewModel(time + content, color, weight));
}
///
/// Clears log.
///
private void ClearLog()
{
Execute.OnUIThread(() => LogItemViewModels.Clear());
}
#endregion Log
#region 属性
private bool _idle;
///
/// Gets or sets a value indicating whether it is idle.
///
public bool Idle
{
get => _idle;
set => SetAndNotify(ref _idle, value);
}
private bool _startEnabled = true;
///
/// Gets or sets a value indicating whether the start button is enabled.
///
public bool StartEnabled
{
get => _startEnabled;
set => SetAndNotify(ref _startEnabled, value);
}
private string _filename = string.Empty;
///
/// Gets or sets the filename.
///
public string Filename
{
get => _filename;
set
{
SetAndNotify(ref _filename, value);
ClearLog();
UpdateFilename(value);
}
}
private bool _form;
///
/// Gets or sets a value indicating whether to use auto-formation.
///
public bool Form
{
get => _form;
set => SetAndNotify(ref _form, value);
}
private bool _addTrust;
///
/// Gets or sets a value indicating whether to use auto-formation.
///
public bool AddTrust
{
get => _addTrust;
set => SetAndNotify(ref _addTrust, value);
}
private bool _useSanityPotion;
public bool UseSanityPotion
{
get => _useSanityPotion;
set => SetAndNotify(ref _useSanityPotion, value);
}
private bool _addUserAdditional = Convert.ToBoolean(ConfigurationHelper.GetValue(ConfigurationKeys.CopilotAddUserAdditional, bool.FalseString));
///
/// Gets or sets a value indicating whether to use auto-formation.
///
public bool AddUserAdditional
{
get => _addUserAdditional;
set
{
SetAndNotify(ref _addUserAdditional, value);
ConfigurationHelper.SetValue(ConfigurationKeys.CopilotAddUserAdditional, value.ToString());
}
}
private string _userAdditional = ConfigurationHelper.GetValue(ConfigurationKeys.CopilotUserAdditional, string.Empty);
///
/// Gets or sets a value indicating whether to use auto-formation.
///
public string UserAdditional
{
get => _userAdditional;
set
{
SetAndNotify(ref _userAdditional, value);
ConfigurationHelper.SetValue(ConfigurationKeys.CopilotUserAdditional, value);
}
}
private bool _useCopilotList;
///
/// Gets or sets a value indicating whether 自动编队.
///
public bool UseCopilotList
{
get => _useCopilotList;
set
{
if (value)
{
_taskType = AsstTaskType.Copilot;
Form = true;
}
SetAndNotify(ref _useCopilotList, value);
}
}
///
/// Gets or sets a value indicating whether to use auto-formation.
///
private string? _copilotTaskName = string.Empty;
public string? CopilotTaskName
{
get => _copilotTaskName;
set
{
value = InvalidStageNameRegex().Replace(value ?? string.Empty, string.Empty).Trim();
SetAndNotify(ref _copilotTaskName, value);
}
}
public bool Loop { get; set; }
private int _loopTimes = int.Parse(ConfigurationHelper.GetValue(ConfigurationKeys.CopilotLoopTimes, "1"));
public int LoopTimes
{
get => _loopTimes;
set
{
SetAndNotify(ref _loopTimes, value);
ConfigurationHelper.SetValue(ConfigurationKeys.CopilotLoopTimes, value.ToString());
}
}
private string _urlText = LocalizationHelper.GetString("PrtsPlus");
///
/// Gets or private sets the UrlText.
///
public string UrlText
{
get => _urlText;
private set => SetAndNotify(ref _urlText, value);
}
private const string CopilotUiUrl = MaaUrls.PrtsPlus;
private string _copilotUrl = CopilotUiUrl;
///
/// Gets or private sets the copilot URL.
///
public string CopilotUrl
{
get => _copilotUrl;
private set
{
UrlText = value == CopilotUiUrl ? LocalizationHelper.GetString("PrtsPlus") : LocalizationHelper.GetString("VideoLink");
SetAndNotify(ref _copilotUrl, value);
}
}
private const string MapUiUrl = MaaUrls.MapPrts;
private string _mapUrl = MapUiUrl;
public string MapUrl
{
get => _mapUrl;
private set => SetAndNotify(ref _mapUrl, value);
}
private bool _couldLikeWebJson;
public bool CouldLikeWebJson
{
get => _couldLikeWebJson;
set => SetAndNotify(ref _couldLikeWebJson, value);
}
#endregion 属性
#region 方法
///
/// Selects file.
///
// ReSharper disable once UnusedMember.Global
public void SelectFile()
{
var dialog = new OpenFileDialog
{
Filter = "JSON|*.json|Video|*.mp4;*.m4s;*.mkv;*.flv;*.avi",
};
if (dialog.ShowDialog() == true)
{
Filename = dialog.FileName;
}
}
///
/// Paste clipboard contents.
///
// ReSharper disable once UnusedMember.Global
public void PasteClipboard()
{
if (Clipboard.ContainsText())
{
Filename = Clipboard.GetText().Trim();
}
else if (Clipboard.ContainsFileDropList())
{
DropFile(Clipboard.GetFileDropList()[0]);
}
}
///
/// Paste clipboard contents.
///
// ReSharper disable once UnusedMember.Global
public async void PasteClipboardCopilotSet()
{
StartEnabled = false;
UseCopilotList = true;
ClearLog();
if (Clipboard.ContainsText())
{
await GetCopilotSetAsync(Clipboard.GetText().Trim());
}
StartEnabled = true;
}
///
/// 批量导入作业
///
// ReSharper disable once UnusedMember.Global
public async void ImportFiles()
{
var dialog = new OpenFileDialog
{
Filter = "JSON|*.json",
Multiselect = true,
};
if (dialog.ShowDialog() != true)
{
return;
}
CopilotId = 0;
foreach (var file in dialog.FileNames)
{
var fileInfo = new FileInfo(file);
if (!fileInfo.Exists)
{
AddLog($"{file} not exists", showTime: false);
return;
}
try
{
using var reader = new StreamReader(File.OpenRead(file));
var str = await reader.ReadToEndAsync();
var payload = JsonConvert.DeserializeObject