diff --git a/BetterGenshinImpact/Core/Script/Group/ScriptGroupProject.cs b/BetterGenshinImpact/Core/Script/Group/ScriptGroupProject.cs index a9a3168e..da6c0b9c 100644 --- a/BetterGenshinImpact/Core/Script/Group/ScriptGroupProject.cs +++ b/BetterGenshinImpact/Core/Script/Group/ScriptGroupProject.cs @@ -12,6 +12,7 @@ using System.Dynamic; using System.IO; using System.Text.Json.Serialization; using System.Threading.Tasks; +using BetterGenshinImpact.GameTask.Shell; namespace BetterGenshinImpact.Core.Script.Group; @@ -112,6 +113,11 @@ public partial class ScriptGroupProject : ObservableObject return new ScriptGroupProject(name, name, "KeyMouse"); } + public static ScriptGroupProject BuildShellProject(string command) + { + return new ScriptGroupProject(command, command, "Shell"); + } + public static ScriptGroupProject BuildPathingProject(string name, string folder) { return new ScriptGroupProject(name, folder, "Pathing"); @@ -165,6 +171,15 @@ public partial class ScriptGroupProject : ObservableObject } await pathingTask.Pathing(task); } + if (Type == "Shell") + { + var task = ShellExecutor.BuildFromShellName(Name); + await task.Execute(); + } + else + { + throw new Exception("不支持的脚本类型"); + } } partial void OnTypeChanged(string value) @@ -189,7 +204,8 @@ public class ScriptGroupProjectExtensions { { "Javascript", "JS脚本" }, { "KeyMouse", "键鼠脚本" }, - { "Pathing", "路径追踪" } + { "Pathing", "路径追踪" }, + { "Shell", "Shell" } }; public static readonly Dictionary StatusDescriptions = new() diff --git a/BetterGenshinImpact/GameTask/Shell/ShellExecutor.cs b/BetterGenshinImpact/GameTask/Shell/ShellExecutor.cs new file mode 100644 index 00000000..d66868da --- /dev/null +++ b/BetterGenshinImpact/GameTask/Shell/ShellExecutor.cs @@ -0,0 +1,121 @@ +using System; +using System.Diagnostics; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Threading; +using System.Threading.Tasks; +using BetterGenshinImpact.GameTask.Common; +using Microsoft.Extensions.Logging; + +namespace BetterGenshinImpact.GameTask.Shell; + +[Serializable] +public class ShellExecutor +{ + private string command = string.Empty; + private int maxWaitSeconds = 60; + private bool noWindow = true; + private bool output = true; + + public static readonly JsonSerializerOptions JsonOptions = new() + { + NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals, + Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + AllowTrailingCommas = true, + ReadCommentHandling = JsonCommentHandling.Skip, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull + }; + + + public async Task Execute(CancellationToken ct = default) + { + if (string.IsNullOrEmpty(command)) + { + TaskControl.Logger.LogWarning("无法执行Shell: Shell为空"); + return; + } + + var cmd = new Process(); + cmd.StartInfo.FileName = "cmd.exe"; + cmd.StartInfo.Arguments = "/k @echo off"; + cmd.StartInfo.RedirectStandardInput = true; + cmd.StartInfo.RedirectStandardOutput = true; + cmd.StartInfo.CreateNoWindow = noWindow; + cmd.StartInfo.UseShellExecute = false; + if (ct.IsCancellationRequested) + { + TaskControl.Logger.LogError("shell {Shell} 被取消", command); + } + + TaskControl.Logger.LogInformation("执行shell:{Shell},超时时间为 {Wait} 秒", command, maxWaitSeconds); + var timeoutSignal = new CancellationTokenSource(TimeSpan.FromSeconds(maxWaitSeconds)); + var mixedToken = CancellationTokenSource.CreateLinkedTokenSource(ct, timeoutSignal.Token).Token; + + cmd.Start(); + var outputShell = ""; + var outputText = ""; + var cmdCanceled = false; + try + { + await cmd.StandardInput.WriteLineAsync(command.AsMemory(), mixedToken); + await cmd.StandardInput.FlushAsync(mixedToken); + cmd.StandardInput.Close(); + await cmd.WaitForExitAsync(mixedToken); + if (output) + { + outputShell = await cmd.StandardOutput.ReadLineAsync(mixedToken) ?? ""; + outputText = await cmd.StandardOutput.ReadToEndAsync(mixedToken); + } + } + catch (OperationCanceledException) + { + cmdCanceled = true; + } + + if (!cmd.HasExited || cmdCanceled) + { + cmd.Kill(); + if (ct.IsCancellationRequested) + { + TaskControl.Logger.LogError("shell {Shell} 被取消", command); + } + else if (timeoutSignal.IsCancellationRequested) + { + TaskControl.Logger.LogWarning("shell {Shell} 超时", command); + } + else + { + TaskControl.Logger.LogWarning("shell {Shell} 出现异常输出,可能未能成功执行。", command); + } + } + + if (output) + { + TaskControl.Logger.LogInformation("shell {End} 运行结束,输出:{Output}", outputShell, outputText); + } + else + { + TaskControl.Logger.LogInformation("shell {End} 运行结束", command); + } + + SystemControl.ActivateWindow(); + } + + public static ShellExecutor BuildFromShellName(string name) + { + var obj = new ShellExecutor + { + command = name + }; + return obj; + } + + public static ShellExecutor BuildFromJson(string json) + { + // 留给以后玩 + var task = JsonSerializer.Deserialize(json, JsonOptions) ?? + throw new Exception("Failed to deserialize ShellExecutorTask"); + return task; + } +} \ No newline at end of file diff --git a/BetterGenshinImpact/Service/ScriptService.cs b/BetterGenshinImpact/Service/ScriptService.cs index 51323dd1..e171b00d 100644 --- a/BetterGenshinImpact/Service/ScriptService.cs +++ b/BetterGenshinImpact/Service/ScriptService.cs @@ -183,6 +183,13 @@ public partial class ScriptService : IScriptService list.Add(newProject); hasTimer = true; } + else if (project.Type == "Shell") + { + var newProject = ScriptGroupProject.BuildShellProject(project.Name); + CopyProjectProperties(project, newProject); + list.Add(newProject); + hasTimer = true; + } } return list; @@ -234,6 +241,10 @@ public partial class ScriptService : IScriptService _logger.LogInformation("→ 开始执行路径追踪任务: {Name}", project.Name); await project.Run(); } + else if (project.Type == "Shell"){ + _logger.LogInformation("→ 开始执行shell: {Name}", project.Name); + await project.Run(); + } } private async Task> ReadCodeList(List list) diff --git a/BetterGenshinImpact/View/Pages/ScriptControlPage.xaml b/BetterGenshinImpact/View/Pages/ScriptControlPage.xaml index 6c615f85..9068ce98 100644 --- a/BetterGenshinImpact/View/Pages/ScriptControlPage.xaml +++ b/BetterGenshinImpact/View/Pages/ScriptControlPage.xaml @@ -206,6 +206,7 @@ + @@ -305,6 +306,7 @@ + diff --git a/BetterGenshinImpact/ViewModel/Pages/ScriptControlViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/ScriptControlViewModel.cs index 23693df7..88a2fb01 100644 --- a/BetterGenshinImpact/ViewModel/Pages/ScriptControlViewModel.cs +++ b/BetterGenshinImpact/ViewModel/Pages/ScriptControlViewModel.cs @@ -633,6 +633,15 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware } } + [RelayCommand] + private void OnAddShell() + { + var str = PromptDialog.Prompt("执行shell是非常危险的,请不要输入你不认识的东西。\n 可能会导致安全问题并破坏你的系统。","请输入需要执行的shell"); + if (!string.IsNullOrEmpty(str)) + { + SelectedScriptGroup?.AddProject(ScriptGroupProject.BuildShellProject(str)); + } + } [RelayCommand] private void OnAddPathing()