From 6a0f142e233772c50b54257dbe83c313df4f8c4f Mon Sep 17 00:00:00 2001 From: Easy World Date: Sun, 13 Feb 2022 19:46:49 +0800 Subject: [PATCH 001/171] support QQ by Mirai.Net --- SysBot.NET.sln | 37 ++- .../PokeBotRunnerImpl.cs | 4 + .../SysBot.Pokemon.ConsoleApp.csproj | 1 + .../Helpers/MiraiQQCommandsHelper.cs | 123 ++++++++ SysBot.Pokemon.QQ/Helpers/MiraiQQQueue.cs | 20 ++ SysBot.Pokemon.QQ/Helpers/MiraiQQSettings.cs | 17 ++ .../Helpers/MiraiQQTradeNotifier.cs | 97 ++++++ SysBot.Pokemon.QQ/MiraiQQBot.cs | 282 ++++++++++++++++++ SysBot.Pokemon.QQ/SysBot.Pokemon.QQ.csproj | 20 ++ 9 files changed, 594 insertions(+), 7 deletions(-) create mode 100644 SysBot.Pokemon.QQ/Helpers/MiraiQQCommandsHelper.cs create mode 100644 SysBot.Pokemon.QQ/Helpers/MiraiQQQueue.cs create mode 100644 SysBot.Pokemon.QQ/Helpers/MiraiQQSettings.cs create mode 100644 SysBot.Pokemon.QQ/Helpers/MiraiQQTradeNotifier.cs create mode 100644 SysBot.Pokemon.QQ/MiraiQQBot.cs create mode 100644 SysBot.Pokemon.QQ/SysBot.Pokemon.QQ.csproj diff --git a/SysBot.NET.sln b/SysBot.NET.sln index 66fae9446..9977de96c 100644 --- a/SysBot.NET.sln +++ b/SysBot.NET.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29613.14 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.32126.317 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SysBot.Base", "SysBot.Base\SysBot.Base.csproj", "{850BECFD-3789-4C07-885E-0AA108C46F60}" EndProject @@ -18,11 +18,17 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SysBot.Pokemon.Twitch", "SysBot.Pokemon.Twitch\SysBot.Pokemon.Twitch.csproj", "{5CA6568C-CD16-499C-A63F-8EF86AAF4499}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SysBot.Pokemon.ConsoleApp", "SysBot.Pokemon.ConsoleApp\SysBot.Pokemon.ConsoleApp.csproj", "{C0525606-4ED4-4998-9AF3-31E2DE65717A}" + ProjectSection(ProjectDependencies) = postProject + {2CD212D9-FF32-4434-9E19-D9CCB99E6817} = {2CD212D9-FF32-4434-9E19-D9CCB99E6817} + {850BECFD-3789-4C07-885E-0AA108C46F60} = {850BECFD-3789-4C07-885E-0AA108C46F60} + EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SysBot.Pokemon.Z3", "SysBot.Pokemon.Z3\SysBot.Pokemon.Z3.csproj", "{DCC41103-1303-4CAB-AE01-5EA68317410D}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SysBot.Pokemon.YouTube", "SysBot.Pokemon.YouTube\SysBot.Pokemon.YouTube.csproj", "{958D8B0C-FF01-4BC3-84AF-8BCB80794B49}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SysBot.Pokemon.QQ", "SysBot.Pokemon.QQ\SysBot.Pokemon.QQ.csproj", "{2CD212D9-FF32-4434-9E19-D9CCB99E6817}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -69,12 +75,13 @@ Global {1D51AD25-4C03-49D3-8074-7D092168CB20}.Release|x64.Build.0 = Release|Any CPU {1D51AD25-4C03-49D3-8074-7D092168CB20}.Release|x86.ActiveCfg = Release|Any CPU {1D51AD25-4C03-49D3-8074-7D092168CB20}.Release|x86.Build.0 = Release|Any CPU - {886B1440-B333-4095-8E44-D71D043D20A9}.Debug|Any CPU.ActiveCfg = Debug|x86 + {886B1440-B333-4095-8E44-D71D043D20A9}.Debug|Any CPU.ActiveCfg = Debug|x64 + {886B1440-B333-4095-8E44-D71D043D20A9}.Debug|Any CPU.Build.0 = Debug|x64 {886B1440-B333-4095-8E44-D71D043D20A9}.Debug|x64.ActiveCfg = Debug|x64 {886B1440-B333-4095-8E44-D71D043D20A9}.Debug|x64.Build.0 = Debug|x64 {886B1440-B333-4095-8E44-D71D043D20A9}.Debug|x86.ActiveCfg = Debug|x86 {886B1440-B333-4095-8E44-D71D043D20A9}.Debug|x86.Build.0 = Debug|x86 - {886B1440-B333-4095-8E44-D71D043D20A9}.Release|Any CPU.ActiveCfg = Release|x86 + {886B1440-B333-4095-8E44-D71D043D20A9}.Release|Any CPU.ActiveCfg = Release|x64 {886B1440-B333-4095-8E44-D71D043D20A9}.Release|x64.ActiveCfg = Release|x64 {886B1440-B333-4095-8E44-D71D043D20A9}.Release|x64.Build.0 = Release|x64 {886B1440-B333-4095-8E44-D71D043D20A9}.Release|x86.ActiveCfg = Release|x86 @@ -103,22 +110,26 @@ Global {5CA6568C-CD16-499C-A63F-8EF86AAF4499}.Release|x64.Build.0 = Release|Any CPU {5CA6568C-CD16-499C-A63F-8EF86AAF4499}.Release|x86.ActiveCfg = Release|Any CPU {5CA6568C-CD16-499C-A63F-8EF86AAF4499}.Release|x86.Build.0 = Release|Any CPU - {C0525606-4ED4-4998-9AF3-31E2DE65717A}.Debug|Any CPU.ActiveCfg = Debug|x86 + {C0525606-4ED4-4998-9AF3-31E2DE65717A}.Debug|Any CPU.ActiveCfg = Debug|x64 + {C0525606-4ED4-4998-9AF3-31E2DE65717A}.Debug|Any CPU.Build.0 = Debug|x64 {C0525606-4ED4-4998-9AF3-31E2DE65717A}.Debug|x64.ActiveCfg = Debug|x64 {C0525606-4ED4-4998-9AF3-31E2DE65717A}.Debug|x64.Build.0 = Debug|x64 {C0525606-4ED4-4998-9AF3-31E2DE65717A}.Debug|x86.ActiveCfg = Debug|x86 {C0525606-4ED4-4998-9AF3-31E2DE65717A}.Debug|x86.Build.0 = Debug|x86 - {C0525606-4ED4-4998-9AF3-31E2DE65717A}.Release|Any CPU.ActiveCfg = Release|x86 + {C0525606-4ED4-4998-9AF3-31E2DE65717A}.Release|Any CPU.ActiveCfg = Release|x64 + {C0525606-4ED4-4998-9AF3-31E2DE65717A}.Release|Any CPU.Build.0 = Release|x64 {C0525606-4ED4-4998-9AF3-31E2DE65717A}.Release|x64.ActiveCfg = Release|x64 {C0525606-4ED4-4998-9AF3-31E2DE65717A}.Release|x64.Build.0 = Release|x64 {C0525606-4ED4-4998-9AF3-31E2DE65717A}.Release|x86.ActiveCfg = Release|x86 {C0525606-4ED4-4998-9AF3-31E2DE65717A}.Release|x86.Build.0 = Release|x86 - {DCC41103-1303-4CAB-AE01-5EA68317410D}.Debug|Any CPU.ActiveCfg = Debug|x86 + {DCC41103-1303-4CAB-AE01-5EA68317410D}.Debug|Any CPU.ActiveCfg = Debug|x64 + {DCC41103-1303-4CAB-AE01-5EA68317410D}.Debug|Any CPU.Build.0 = Debug|x64 {DCC41103-1303-4CAB-AE01-5EA68317410D}.Debug|x64.ActiveCfg = Debug|x64 {DCC41103-1303-4CAB-AE01-5EA68317410D}.Debug|x64.Build.0 = Debug|x64 {DCC41103-1303-4CAB-AE01-5EA68317410D}.Debug|x86.ActiveCfg = Debug|x86 {DCC41103-1303-4CAB-AE01-5EA68317410D}.Debug|x86.Build.0 = Debug|x86 {DCC41103-1303-4CAB-AE01-5EA68317410D}.Release|Any CPU.ActiveCfg = Release|x86 + {DCC41103-1303-4CAB-AE01-5EA68317410D}.Release|Any CPU.Build.0 = Release|x86 {DCC41103-1303-4CAB-AE01-5EA68317410D}.Release|x64.ActiveCfg = Release|x64 {DCC41103-1303-4CAB-AE01-5EA68317410D}.Release|x64.Build.0 = Release|x64 {DCC41103-1303-4CAB-AE01-5EA68317410D}.Release|x86.ActiveCfg = Release|x86 @@ -135,6 +146,18 @@ Global {958D8B0C-FF01-4BC3-84AF-8BCB80794B49}.Release|x64.Build.0 = Release|Any CPU {958D8B0C-FF01-4BC3-84AF-8BCB80794B49}.Release|x86.ActiveCfg = Release|Any CPU {958D8B0C-FF01-4BC3-84AF-8BCB80794B49}.Release|x86.Build.0 = Release|Any CPU + {2CD212D9-FF32-4434-9E19-D9CCB99E6817}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2CD212D9-FF32-4434-9E19-D9CCB99E6817}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2CD212D9-FF32-4434-9E19-D9CCB99E6817}.Debug|x64.ActiveCfg = Debug|Any CPU + {2CD212D9-FF32-4434-9E19-D9CCB99E6817}.Debug|x64.Build.0 = Debug|Any CPU + {2CD212D9-FF32-4434-9E19-D9CCB99E6817}.Debug|x86.ActiveCfg = Debug|Any CPU + {2CD212D9-FF32-4434-9E19-D9CCB99E6817}.Debug|x86.Build.0 = Debug|Any CPU + {2CD212D9-FF32-4434-9E19-D9CCB99E6817}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2CD212D9-FF32-4434-9E19-D9CCB99E6817}.Release|Any CPU.Build.0 = Release|Any CPU + {2CD212D9-FF32-4434-9E19-D9CCB99E6817}.Release|x64.ActiveCfg = Release|Any CPU + {2CD212D9-FF32-4434-9E19-D9CCB99E6817}.Release|x64.Build.0 = Release|Any CPU + {2CD212D9-FF32-4434-9E19-D9CCB99E6817}.Release|x86.ActiveCfg = Release|Any CPU + {2CD212D9-FF32-4434-9E19-D9CCB99E6817}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/SysBot.Pokemon.ConsoleApp/PokeBotRunnerImpl.cs b/SysBot.Pokemon.ConsoleApp/PokeBotRunnerImpl.cs index 782e36262..4a70ce35d 100644 --- a/SysBot.Pokemon.ConsoleApp/PokeBotRunnerImpl.cs +++ b/SysBot.Pokemon.ConsoleApp/PokeBotRunnerImpl.cs @@ -3,6 +3,7 @@ using SysBot.Pokemon.Twitch; using System.Threading; using System.Threading.Tasks; +using SysBot.Pokemon.QQ; namespace SysBot.Pokemon.ConsoleApp { @@ -20,6 +21,9 @@ protected override void AddIntegrations() { AddDiscordBot(Hub.Config.Discord); AddTwitchBot(Hub.Config.Twitch); + //add qq bot + var qqBot = new MiraiQQBot(Hub, "机器人启动成功"); + qqBot.StartingDistribution(); } private void AddTwitchBot(TwitchSettings config) diff --git a/SysBot.Pokemon.ConsoleApp/SysBot.Pokemon.ConsoleApp.csproj b/SysBot.Pokemon.ConsoleApp/SysBot.Pokemon.ConsoleApp.csproj index f3d3d5b75..39aeab023 100644 --- a/SysBot.Pokemon.ConsoleApp/SysBot.Pokemon.ConsoleApp.csproj +++ b/SysBot.Pokemon.ConsoleApp/SysBot.Pokemon.ConsoleApp.csproj @@ -12,6 +12,7 @@ + diff --git a/SysBot.Pokemon.QQ/Helpers/MiraiQQCommandsHelper.cs b/SysBot.Pokemon.QQ/Helpers/MiraiQQCommandsHelper.cs new file mode 100644 index 000000000..15b3386dd --- /dev/null +++ b/SysBot.Pokemon.QQ/Helpers/MiraiQQCommandsHelper.cs @@ -0,0 +1,123 @@ +using System; +using PKHeX.Core; +using SysBot.Base; +using SysBot.Pokemon; + +namespace SysBot.Pokemon.QQ +{ + public class MiraiQQCommandsHelper where T : PKM, new() + { + public static bool AddToWaitingList(string setstring, string username, ulong mUserId, out string msg) + { + if (!MiraiQQBot.Info.GetCanQueue()) + { + msg = "Sorry, I am not currently accepting queue requests!"; + return false; + } + + var set = ShowdownUtil.ConvertToShowdown(setstring); + if (set == null) + { + msg = $"Skipping trade, @{username}: Empty nickname provided for the species."; + return false; + } + + var template = AutoLegalityWrapper.GetTemplate(set); + if (template.Species < 1) + { + msg = + $"Skipping trade, @{username}: Please read what you are supposed to type as the command argument."; + return false; + } + + if (set.InvalidLines.Count != 0) + { + msg = + $"Skipping trade, @{username}: Unable to parse Showdown Set:\n{string.Join("\n", set.InvalidLines)}"; + return false; + } + + try + { + var sav = AutoLegalityWrapper.GetTrainerInfo(); + var pkm = sav.GetLegal(template, out var result); + + if (!pkm.CanBeTraded()) + { + msg = $"Skipping trade, @{username}: Provided Pokemon content is blocked from trading!"; + return false; + } + + if (pkm is T pk) + { + var valid = new LegalityAnalysis(pkm).Valid; + if (valid) + { + var tq = new MiraiQQQueue(pk, new PokeTradeTrainerInfo(username, mUserId), mUserId); + MiraiQQBot.QueuePool.RemoveAll(z => z.QQ == mUserId); // remove old requests if any + MiraiQQBot.QueuePool.Add(tq); + msg = + $"@{username} - added to the waiting list. Your request from the waiting list will be removed if you are too slow!"; + return true; + } + } + + var reason = result == "Timeout" ? "Set took too long to generate." : "Unable to legalize the Pokemon."; + msg = $"Skipping trade, @{username}: {reason}"; + } +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception ex) +#pragma warning restore CA1031 // Do not catch general exception types + { + LogUtil.LogSafe(ex, nameof(MiraiQQCommandsHelper)); + msg = $"Skipping trade, @{username}: An unexpected problem occurred."; + } + + return false; + } + + public static bool AddToWaitingList(PKM pkm, string username, ulong mUserId, out string msg) + { + if (!MiraiQQBot.Info.GetCanQueue()) + { + msg = "Sorry, I am not currently accepting queue requests!"; + return false; + } + + try + { + if (!pkm.CanBeTraded()) + { + msg = $"Skipping trade, @{username}: Provided Pokemon content is blocked from trading!"; + return false; + } + + if (pkm is T pk) + { + var valid = new LegalityAnalysis(pkm).Valid; + if (valid) + { + var tq = new MiraiQQQueue(pk, new PokeTradeTrainerInfo(username, mUserId), mUserId); + MiraiQQBot.QueuePool.RemoveAll(z => z.QQ == mUserId); // remove old requests if any + MiraiQQBot.QueuePool.Add(tq); + msg = + $"@{username} - added to the waiting list. Your request from the waiting list will be removed if you are too slow!"; + return true; + } + } + + var reason = "Unable to legalize the Pokemon."; + msg = $"Skipping trade, @{username}: {reason}"; + } +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception ex) +#pragma warning restore CA1031 // Do not catch general exception types + { + LogUtil.LogSafe(ex, nameof(MiraiQQCommandsHelper)); + msg = $"Skipping trade, @{username}: An unexpected problem occurred."; + } + + return false; + } + } +} \ No newline at end of file diff --git a/SysBot.Pokemon.QQ/Helpers/MiraiQQQueue.cs b/SysBot.Pokemon.QQ/Helpers/MiraiQQQueue.cs new file mode 100644 index 000000000..a9c4fcfdb --- /dev/null +++ b/SysBot.Pokemon.QQ/Helpers/MiraiQQQueue.cs @@ -0,0 +1,20 @@ +using PKHeX.Core; +using SysBot.Pokemon; + +namespace SysBot.Pokemon.QQ +{ + public class MiraiQQQueue where T : PKM, new() + { + public T Pokemon { get; } + public PokeTradeTrainerInfo Trainer { get; } + public ulong QQ { get; } + public string DisplayName => Trainer.TrainerName; + + public MiraiQQQueue(T pkm, PokeTradeTrainerInfo trainer, ulong qq) + { + Pokemon = pkm; + Trainer = trainer; + QQ = qq; + } + } +} \ No newline at end of file diff --git a/SysBot.Pokemon.QQ/Helpers/MiraiQQSettings.cs b/SysBot.Pokemon.QQ/Helpers/MiraiQQSettings.cs new file mode 100644 index 000000000..36a0f6721 --- /dev/null +++ b/SysBot.Pokemon.QQ/Helpers/MiraiQQSettings.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace SysBot.Pokemon.QQ +{ + public class MiraiQQSettings + { + public string Address { get; set; } = string.Empty; + + public string QQ { get; set; } = string.Empty; + + public string VerifyKey { get; set; } = string.Empty; + + public string GroupId { get; set; } = string.Empty; + } +} diff --git a/SysBot.Pokemon.QQ/Helpers/MiraiQQTradeNotifier.cs b/SysBot.Pokemon.QQ/Helpers/MiraiQQTradeNotifier.cs new file mode 100644 index 000000000..2731ce0b6 --- /dev/null +++ b/SysBot.Pokemon.QQ/Helpers/MiraiQQTradeNotifier.cs @@ -0,0 +1,97 @@ +using Mirai.Net.Sessions.Http.Managers; +using PKHeX.Core; +using SysBot.Base; +using SysBot.Pokemon; +using System; +using System.Linq; + +namespace SysBot.Pokemon.QQ +{ + public class MiraiQQTradeNotifier : IPokeTradeNotifier where T : PKM, new() + { + private T Data { get; } + private PokeTradeTrainerInfo Info { get; } + private int Code { get; } + private string Username { get; } + + private string GroupId { get; } + + public MiraiQQTradeNotifier(T data, PokeTradeTrainerInfo info, int code, string username, string groupId) + { + Data = data; + Info = info; + Code = code; + Username = username; + GroupId = groupId; + LogUtil.LogText($"Created trade details for {Username} - {Code}"); + } + + public Action>? OnFinish { private get; set; } + + public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, string message) + { + LogUtil.LogText(message); + SendMessage($"@{info.Trainer.TrainerName}: {message}"); + } + + public void TradeCanceled(PokeRoutineExecutor routine, PokeTradeDetail info, PokeTradeResult msg) + { + OnFinish?.Invoke(routine); + var line = $"@{info.Trainer.TrainerName}: Trade canceled, {msg}"; + LogUtil.LogText(line); + SendMessage(line); + } + + public void TradeFinished(PokeRoutineExecutor routine, PokeTradeDetail info, T result) + { + OnFinish?.Invoke(routine); + var tradedToUser = Data.Species; + var message = $"@{info.Trainer.TrainerName}: " + (tradedToUser != 0 + ? $"Trade finished. Enjoy your {(Species) tradedToUser}!" + : "Trade finished!"); + LogUtil.LogText(message); + SendMessage(message); + } + + public void TradeInitialize(PokeRoutineExecutor routine, PokeTradeDetail info) + { + var receive = Data.Species == 0 ? string.Empty : $" ({Data.Nickname})"; + var msg = + $"@{info.Trainer.TrainerName} (ID: {info.ID}): Initializing trade{receive} with you. Please be ready."; + msg += $" Your trade code is: {info.Code:0000 0000}"; + LogUtil.LogText(msg); + SendMessage(msg); + } + + public void TradeSearching(PokeRoutineExecutor routine, PokeTradeDetail info) + { + var name = Info.TrainerName; + var trainer = string.IsNullOrEmpty(name) ? string.Empty : $", @{name}"; + var message = $"I'm waiting for you{trainer}! My IGN is {routine.InGameName}."; + message += $" Your trade code is: {info.Code:0000 0000}"; + LogUtil.LogText(message); + SendMessage($"@{info.Trainer.TrainerName} {message}"); + } + + public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, PokeTradeSummary message) + { + var msg = message.Summary; + if (message.Details.Count > 0) + msg += ", " + string.Join(", ", message.Details.Select(z => $"{z.Heading}: {z.Detail}")); + LogUtil.LogText(msg); + SendMessage(msg); + } + + public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, T result, string message) + { + var msg = $"Details for {result.FileName}: " + message; + LogUtil.LogText(msg); + SendMessage(msg); + } + + private void SendMessage(string message) + { + MessageManager.SendGroupMessageAsync(GroupId, message); + } + } +} \ No newline at end of file diff --git a/SysBot.Pokemon.QQ/MiraiQQBot.cs b/SysBot.Pokemon.QQ/MiraiQQBot.cs new file mode 100644 index 000000000..15c5aad29 --- /dev/null +++ b/SysBot.Pokemon.QQ/MiraiQQBot.cs @@ -0,0 +1,282 @@ +using Mirai.Net.Data.Messages.Concretes; +using Mirai.Net.Data.Messages.Receivers; +using Mirai.Net.Sessions; +using Mirai.Net.Sessions.Http.Managers; +using Newtonsoft.Json; +using PKHeX.Core; +using SysBot.Base; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reactive.Linq; +using System.Threading.Tasks; + +namespace SysBot.Pokemon.QQ +{ + public class MiraiQQBot where T : PKM, new() + { + private const string ConfigPath = "miraiQQconfig.json"; + private static MiraiQQSettings DefaultSettings = new(); + private static PokeTradeHub Hub = default!; + + private readonly MiraiBot client; + private static string GroupId; + private static string WelcomeString = "欢迎使用SysBot.NET"; + internal static TradeQueueInfo Info => Hub.Queues.Info; + internal static readonly List> QueuePool = new(); + + public MiraiQQSettings ReadConfig() + { + if (!File.Exists(ConfigPath)) + { + return DefaultSettings; + } + + try + { + var lines = File.ReadAllText(ConfigPath); + var cfg = JsonConvert.DeserializeObject(lines) ?? DefaultSettings; + return cfg; + } +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception) +#pragma warning restore CA1031 // Do not catch general exception types + { + LogUtil.LogError("miraiQQ config error: {}", "MiraiQQBot"); + return DefaultSettings; + } + } + + public MiraiQQBot(PokeTradeHub hub, string message) + { + Hub = hub; + WelcomeString = message; + var cfg = ReadConfig(); + + client = new MiraiBot + { + Address = cfg.Address, + QQ = cfg.QQ, + VerifyKey = cfg.VerifyKey + }; + GroupId = cfg.GroupId; + client.MessageReceived.OfType() + .Subscribe(receiver => + { + var senderQQ = receiver.Sender.Id; + var groupId = receiver.Sender.Group.Id; + if (groupId != GroupId || senderQQ == client.QQ) + return; + + HandleFileUpload(receiver); + var response = HandleCommand(receiver); + if (response.Length == 0) + return; + }); + } + + public void StartingDistribution() + { + Task.Run(async () => + { + await client.LaunchAsync(); + await MessageManager.SendGroupMessageAsync(GroupId, WelcomeString); + if (typeof(T) == typeof(PK8)) + { + await MessageManager.SendGroupMessageAsync(GroupId, "当前版本为剑盾"); + } + else if (typeof(T) == typeof(PB8)) + { + await MessageManager.SendGroupMessageAsync(GroupId, "当前版本为晶灿钻石明亮珍珠"); + } + else if(typeof(T) == typeof(PA8)) + { + await MessageManager.SendGroupMessageAsync(GroupId, "当前版本为阿尔宙斯"); + } + }); + } + + // todo: revise + private string HandleFileUpload(GroupMessageReceiver receiver) + { + var senderQQ = receiver.Sender.Id; + var groupId = receiver.Sender.Group.Id; + var qqMsg = string.Empty; + + var fileMessages = receiver.MessageChain.OfType(); + + if (fileMessages.Any()) + { + LogUtil.LogText("In file module"); + var file = fileMessages.First(); + var fileName = file.Name; + string operationType; + if (typeof(T) == typeof(PK8) && + fileName.EndsWith(".pk8", StringComparison.OrdinalIgnoreCase)) operationType = "pk8"; + else if (typeof(T) == typeof(PB8) && + fileName.EndsWith(".pb8", StringComparison.OrdinalIgnoreCase)) + operationType = "pb8"; + else if (typeof(T) == typeof(PA8) && + fileName.EndsWith(".pa8", StringComparison.OrdinalIgnoreCase)) + operationType = "pa8"; + else return qqMsg; + + PKM pkm; + try + { + var f = FileManager.GetFileAsync(groupId, file.FileId, true).Result; + + string url = f.DownloadInfo.Url; + byte[] data = new System.Net.WebClient().DownloadData(url); + switch (operationType) + { + case "pk8" or "pb8" when data.Length != 344: + MessageManager.SendGroupMessageAsync("非法文件"); + return qqMsg; + case "pa8" when data.Length != 376: + MessageManager.SendGroupMessageAsync("非法文件"); + return qqMsg; + } + + switch (operationType) + { + case "pk8": + pkm = new PK8(data); + break; + case "pb8": + pkm = new PB8(data); + break; + case "pa8": + pkm = new PA8(data); + break; + default: return qqMsg; + } + + LogUtil.LogText($"operationType:{operationType}"); + FileManager.DeleteFileAsync(groupId, file.FileId); + } + catch (Exception ex) + { + LogUtil.LogText(ex.ToString()); + return qqMsg; + } + + MessageManager.SendGroupMessageAsync(receiver.Sender.Name + " 上传了 " + fileName + " 文件"); + var _ = MiraiQQCommandsHelper.AddToWaitingList(pkm, receiver.Sender.Name, + ulong.Parse(senderQQ), out string msg); + if (_) + { + GetUserFromQueueAndGenerateCodeToTrade(senderQQ); + msg = string.Empty; + } + else + { + MessageManager.SendGroupMessageAsync(msg); + } + } + + return qqMsg; + } + + private string HandleCommand(GroupMessageReceiver receiver) + { + string qqMsg = string.Empty; + try + { + qqMsg = receiver.MessageChain.OfType().First().Text; + } + catch + { + } + + LogUtil.LogText($"debug qqMsg:{qqMsg}"); + var split = qqMsg.Split('\n'); + string c = ""; + string args = ""; + string nickName = receiver.Sender.Name; + string qq = receiver.Sender.Id; + if (split.Length > 0) + { + c = split[0]; + args = qqMsg.Substring(qqMsg.IndexOf('\n') + 1); + } + + switch (c) + { + case "$trade": + var _ = MiraiQQCommandsHelper.AddToWaitingList(args, nickName, ulong.Parse(qq), out string msg); + if (_) + { + GetUserFromQueueAndGenerateCodeToTrade(qq); + msg = string.Empty; + } + + return msg; + + + default: return string.Empty; + } + } + + private void GetUserFromQueueAndGenerateCodeToTrade(string qq) + { + var user = QueuePool.FindLast(q => q.QQ == ulong.Parse(qq)); + + //LogUtil.LogInfo("QueuePool.ToString" + QueuePool.ToString(), "debug"); + if (user == null) + return; + QueuePool.Remove(user); + + Random rnd = new(); + try + { + int code = rnd.Next(0, 9999_9999); //Util.ToInt32(msg); + var _ = AddToTradeQueue(user.Pokemon, code, user.QQ, user.DisplayName, RequestSignificance.Favored, + PokeRoutineType.LinkTrade, out string message); + MessageManager.SendGroupMessageAsync(GroupId, message); + } +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception ex) +#pragma warning restore CA1031 // Do not catch general exception types + { + LogUtil.LogSafe(ex, nameof(MiraiQQBot)); + LogUtil.LogError($"{ex.Message}", nameof(MiraiQQBot)); + } + } + + private bool AddToTradeQueue(T pk, int code, ulong qq, string displayName, RequestSignificance sig, + PokeRoutineType type, out string msg) + { + // var user = e.WhisperMessage.UserId; + var userID = qq; + var name = displayName; + + var trainer = new PokeTradeTrainerInfo(name, userID); + var notifier = new MiraiQQTradeNotifier(pk, trainer, code, name, GroupId); + var tt = type == PokeRoutineType.SeedCheck ? PokeTradeType.Seed : PokeTradeType.Specific; + var detail = new PokeTradeDetail(pk, trainer, notifier, tt, code, sig == RequestSignificance.Favored); + var trade = new TradeEntry(detail, userID, type, name); + + var added = Info.AddToTradeQueue(trade, userID, sig == RequestSignificance.Owner); + + if (added == QueueResultAdd.AlreadyInQueue) + { + msg = $"@{name}: Sorry, you are already in the queue."; + return false; + } + + var position = Info.CheckPosition(userID, type); + msg = $"@{name}: Added to the {type} queue, unique ID: {detail.ID}. Current Position: {position.Position}"; + + var botct = Info.Hub.Bots.Count; + if (position.Position > botct) + { + var eta = Info.Hub.Config.Queues.EstimateDelay(position.Position, botct); + msg += $". Estimated: {eta:F1} minutes."; + } + + return true; + } + } +} \ No newline at end of file diff --git a/SysBot.Pokemon.QQ/SysBot.Pokemon.QQ.csproj b/SysBot.Pokemon.QQ/SysBot.Pokemon.QQ.csproj new file mode 100644 index 000000000..c85e95a8d --- /dev/null +++ b/SysBot.Pokemon.QQ/SysBot.Pokemon.QQ.csproj @@ -0,0 +1,20 @@ + + + + netstandard2.0 + AnyCPU + enable + 9 + + + + + + + + + + + + + \ No newline at end of file From 6a7c956d572a06dd690c96dc875e39b421ef83fd Mon Sep 17 00:00:00 2001 From: Easy World Date: Sun, 13 Feb 2022 20:15:08 +0800 Subject: [PATCH 002/171] Create README.md --- SysBot.Pokemon.QQ/README.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 SysBot.Pokemon.QQ/README.md diff --git a/SysBot.Pokemon.QQ/README.md b/SysBot.Pokemon.QQ/README.md new file mode 100644 index 000000000..e23f3efc1 --- /dev/null +++ b/SysBot.Pokemon.QQ/README.md @@ -0,0 +1,29 @@ +# SysBot.Pokemon.QQ +![License](https://img.shields.io/badge/License-AGPLv3-blue.svg) +Most codes are based on [SysBot.Pokemon.Twitch](https://github.com/kwsch/SysBot.NET/tree/master/SysBot.Pokemon.Twitch) + +Uses Mirai.Net as a dependency via Nuget. + +## Support QQ by mirai.net: +- Support [ALM-Showdown-Sets](https://github.com/architdate/PKHeX-Plugins/wiki/ALM-Showdown-Sets) +- Support PK8 PB8 PA8 file upload + +## Usage +- Build SysBot.Pokemon.ConsoleApp +- Download [MCL](https://github.com/iTXTech/mirai-console-loader) and add mirai plugin [mirai-api-http](https://github.com/project-mirai/mirai-api-http) +- Config mirai-api-http by [this](https://github.com/project-mirai/mirai-api-http/blob/master/README.md), get the VerifyKey of your config +- Start [MCL](https://github.com/iTXTech/mirai-console-loader) and check if it works +- Use the miraiQQconfig template and replace your QQ number and your trade QQ group number, save as miraiQQconfig.json to the folder of your `SysBot.Pokemon.ConsoleApp.exe` +- Run the `SysBot.Pokemon.ConsoleApp.exe` +- Send `$trade` and press `ctrl+enter` to the next line, then parse your ALM-Showdown-Sets before sending your QQ message +- Or you can upload a PK8/PB8/PA8 file directly + +## miraiQQconfig template +``` +{ + "Address": "localhost:8080", + "QQ": "your QQ number", + "VerifyKey": "INITKEY6Ipj3WbA", + "GroupId": "trade QQ group number" +} +``` From 4c1fd2591d58538ab25dcd4733d9a8b09dccb0e0 Mon Sep 17 00:00:00 2001 From: Easy World Date: Sun, 13 Feb 2022 20:16:31 +0800 Subject: [PATCH 003/171] Update README.md --- SysBot.Pokemon.QQ/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SysBot.Pokemon.QQ/README.md b/SysBot.Pokemon.QQ/README.md index e23f3efc1..535e3ba43 100644 --- a/SysBot.Pokemon.QQ/README.md +++ b/SysBot.Pokemon.QQ/README.md @@ -1,8 +1,9 @@ # SysBot.Pokemon.QQ ![License](https://img.shields.io/badge/License-AGPLv3-blue.svg) + Most codes are based on [SysBot.Pokemon.Twitch](https://github.com/kwsch/SysBot.NET/tree/master/SysBot.Pokemon.Twitch) -Uses Mirai.Net as a dependency via Nuget. +Uses [Mirai.Net](https://github.com/SinoAHpx/Mirai.Net) as a dependency via Nuget. ## Support QQ by mirai.net: - Support [ALM-Showdown-Sets](https://github.com/architdate/PKHeX-Plugins/wiki/ALM-Showdown-Sets) From 421b4fcba76e44ccd55a0d8b77da80d869ef52ca Mon Sep 17 00:00:00 2001 From: Easy World Date: Sun, 13 Feb 2022 20:29:37 +0800 Subject: [PATCH 004/171] Support WinForms --- SysBot.Pokemon.ConsoleApp/PokeBotRunnerImpl.cs | 11 +++++++++-- SysBot.Pokemon.QQ/MiraiQQBot.cs | 7 ++++++- SysBot.Pokemon.WinForms/PokeBotRunnerImpl.cs | 10 ++++++++++ .../SysBot.Pokemon.WinForms.csproj | 1 + 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/SysBot.Pokemon.ConsoleApp/PokeBotRunnerImpl.cs b/SysBot.Pokemon.ConsoleApp/PokeBotRunnerImpl.cs index 4a70ce35d..205b2922e 100644 --- a/SysBot.Pokemon.ConsoleApp/PokeBotRunnerImpl.cs +++ b/SysBot.Pokemon.ConsoleApp/PokeBotRunnerImpl.cs @@ -22,8 +22,7 @@ protected override void AddIntegrations() AddDiscordBot(Hub.Config.Discord); AddTwitchBot(Hub.Config.Twitch); //add qq bot - var qqBot = new MiraiQQBot(Hub, "机器人启动成功"); - qqBot.StartingDistribution(); + AddQQBot(); } private void AddTwitchBot(TwitchSettings config) @@ -54,5 +53,13 @@ private void AddDiscordBot(DiscordSettings config) var bot = new SysCord(this); Task.Run(() => bot.MainAsync(token, CancellationToken.None), CancellationToken.None); } + + private void AddQQBot() + { + if (!MiraiQQBot.HasConfig()) return; + //add qq bot + var qqBot = new MiraiQQBot(Hub, "机器人启动成功"); + qqBot.StartingDistribution(); + } } } diff --git a/SysBot.Pokemon.QQ/MiraiQQBot.cs b/SysBot.Pokemon.QQ/MiraiQQBot.cs index 15c5aad29..8de5f59b7 100644 --- a/SysBot.Pokemon.QQ/MiraiQQBot.cs +++ b/SysBot.Pokemon.QQ/MiraiQQBot.cs @@ -26,9 +26,14 @@ namespace SysBot.Pokemon.QQ internal static TradeQueueInfo Info => Hub.Queues.Info; internal static readonly List> QueuePool = new(); + public static bool HasConfig() + { + return File.Exists(ConfigPath); + } + public MiraiQQSettings ReadConfig() { - if (!File.Exists(ConfigPath)) + if (!HasConfig()) { return DefaultSettings; } diff --git a/SysBot.Pokemon.WinForms/PokeBotRunnerImpl.cs b/SysBot.Pokemon.WinForms/PokeBotRunnerImpl.cs index 035a37c3e..8835a39bf 100644 --- a/SysBot.Pokemon.WinForms/PokeBotRunnerImpl.cs +++ b/SysBot.Pokemon.WinForms/PokeBotRunnerImpl.cs @@ -5,6 +5,7 @@ using SysBot.Pokemon.YouTube; using System.Threading; using System.Threading.Tasks; +using SysBot.Pokemon.QQ; namespace SysBot.Pokemon { @@ -24,6 +25,7 @@ protected override void AddIntegrations() AddDiscordBot(Hub.Config.Discord.Token); AddTwitchBot(Hub.Config.Twitch); AddYouTubeBot(Hub.Config.YouTube); + AddQQBot(); } private void AddTwitchBot(TwitchSettings config) @@ -71,5 +73,13 @@ private void AddDiscordBot(string apiToken) var bot = new SysCord(this); Task.Run(() => bot.MainAsync(apiToken, CancellationToken.None)); } + + private void AddQQBot() + { + if (!MiraiQQBot.HasConfig()) return; + //add qq bot + var qqBot = new MiraiQQBot(Hub, "机器人启动成功"); + qqBot.StartingDistribution(); + } } } diff --git a/SysBot.Pokemon.WinForms/SysBot.Pokemon.WinForms.csproj b/SysBot.Pokemon.WinForms/SysBot.Pokemon.WinForms.csproj index 73f2f1313..1277fd1e2 100644 --- a/SysBot.Pokemon.WinForms/SysBot.Pokemon.WinForms.csproj +++ b/SysBot.Pokemon.WinForms/SysBot.Pokemon.WinForms.csproj @@ -28,6 +28,7 @@ + From 7eaae845eb62a9c62ff80eeb9fecc635fc8f0ff9 Mon Sep 17 00:00:00 2001 From: Easy World Date: Sun, 13 Feb 2022 20:39:13 +0800 Subject: [PATCH 005/171] Update README.md --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 303cb3c9f..80f82707f 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,14 @@ The driving force to develop this project is automated bots for Nintendo Switch Uses [Discord.Net](https://github.com/discord-net/Discord.Net) , [TwitchLib](https://github.com/TwitchLib/TwitchLib) and [StreamingClientLibary](https://github.com/SaviorXTanren/StreamingClientLibrary) as a dependency via Nuget. +## SysBot.Pokemon.QQ: +- Support [ALM-Showdown-Sets](https://github.com/architdate/PKHeX-Plugins/wiki/ALM-Showdown-Sets) +- Support PK8 PB8 PA8 file upload + +Most codes are based on [SysBot.Pokemon.Twitch](https://github.com/kwsch/SysBot.NET/tree/master/SysBot.Pokemon.Twitch) + +Uses [Mirai.Net](https://github.com/SinoAHpx/Mirai.Net) as a dependency via Nuget. + ## Other Dependencies Pokémon API logic is provided by [PKHeX](https://github.com/kwsch/PKHeX/), and template generation is provided by [AutoMod](https://github.com/architdate/PKHeX-Plugins/). From 98d8ea595578f9d2f70f3ebee4673b69ca656429 Mon Sep 17 00:00:00 2001 From: Easy World Date: Sun, 13 Feb 2022 20:51:19 +0800 Subject: [PATCH 006/171] Update README.md --- SysBot.Pokemon.QQ/README.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/SysBot.Pokemon.QQ/README.md b/SysBot.Pokemon.QQ/README.md index 535e3ba43..7990c660f 100644 --- a/SysBot.Pokemon.QQ/README.md +++ b/SysBot.Pokemon.QQ/README.md @@ -10,21 +10,22 @@ Uses [Mirai.Net](https://github.com/SinoAHpx/Mirai.Net) as a dependency via Nuge - Support PK8 PB8 PA8 file upload ## Usage -- Build SysBot.Pokemon.ConsoleApp +- Build SysBot.Pokemon.ConsoleApp or SysBot.Pokemon.WinForms +- Setup by this [wiki](https://github.com/kwsch/SysBot.NET/wiki/Bot-Startup-Details) - Download [MCL](https://github.com/iTXTech/mirai-console-loader) and add mirai plugin [mirai-api-http](https://github.com/project-mirai/mirai-api-http) -- Config mirai-api-http by [this](https://github.com/project-mirai/mirai-api-http/blob/master/README.md), get the VerifyKey of your config +- Config mirai-api-http by [this](https://github.com/project-mirai/mirai-api-http/blob/master/README.md) - Start [MCL](https://github.com/iTXTech/mirai-console-loader) and check if it works -- Use the miraiQQconfig template and replace your QQ number and your trade QQ group number, save as miraiQQconfig.json to the folder of your `SysBot.Pokemon.ConsoleApp.exe` -- Run the `SysBot.Pokemon.ConsoleApp.exe` -- Send `$trade` and press `ctrl+enter` to the next line, then parse your ALM-Showdown-Sets before sending your QQ message +- Use the miraiQQconfig template and replace your QQ number and your trade QQ group number, save as miraiQQconfig.json to the folder of your `SysBot.Pokemon.ConsoleApp.exe` or `SysBot.exe` +- Run the `SysBot.Pokemon.ConsoleApp.exe` or `SysBot.exe` +- Send `$trade` and press `ctrl+enter` to the next line, then paste your ALM-Showdown-Sets before sending your QQ message - Or you can upload a PK8/PB8/PA8 file directly ## miraiQQconfig template ``` { - "Address": "localhost:8080", + "Address": "ip and port of you mirai-api-http config", "QQ": "your QQ number", - "VerifyKey": "INITKEY6Ipj3WbA", + "VerifyKey": "VerifyKey of your mirai-api-http config", "GroupId": "trade QQ group number" } ``` From 9b8958aa4fbbec92f6b5ee022dd33773f17c9018 Mon Sep 17 00:00:00 2001 From: Easy World Date: Sun, 13 Feb 2022 21:59:53 +0800 Subject: [PATCH 007/171] Add bot alive message --- SysBot.Pokemon.QQ/Helpers/MiraiQQSettings.cs | 2 ++ SysBot.Pokemon.QQ/MiraiQQBot.cs | 33 +++++++++++++------- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/SysBot.Pokemon.QQ/Helpers/MiraiQQSettings.cs b/SysBot.Pokemon.QQ/Helpers/MiraiQQSettings.cs index 36a0f6721..930166c61 100644 --- a/SysBot.Pokemon.QQ/Helpers/MiraiQQSettings.cs +++ b/SysBot.Pokemon.QQ/Helpers/MiraiQQSettings.cs @@ -13,5 +13,7 @@ public class MiraiQQSettings public string VerifyKey { get; set; } = string.Empty; public string GroupId { get; set; } = string.Empty; + + public string AliveMsg { get; set; } = "hello"; } } diff --git a/SysBot.Pokemon.QQ/MiraiQQBot.cs b/SysBot.Pokemon.QQ/MiraiQQBot.cs index 8de5f59b7..686d291d4 100644 --- a/SysBot.Pokemon.QQ/MiraiQQBot.cs +++ b/SysBot.Pokemon.QQ/MiraiQQBot.cs @@ -74,7 +74,17 @@ public MiraiQQBot(PokeTradeHub hub, string message) if (groupId != GroupId || senderQQ == client.QQ) return; - HandleFileUpload(receiver); + if (cfg.AliveMsg == receiver.MessageChain.OfType()?.First()?.Text) + { + MessageManager.SendGroupMessageAsync(cfg.AliveMsg); + return; + } + + if (HandleFileUpload(receiver)) + { + return; + } + var response = HandleCommand(receiver); if (response.Length == 0) return; @@ -95,7 +105,7 @@ public void StartingDistribution() { await MessageManager.SendGroupMessageAsync(GroupId, "当前版本为晶灿钻石明亮珍珠"); } - else if(typeof(T) == typeof(PA8)) + else if (typeof(T) == typeof(PA8)) { await MessageManager.SendGroupMessageAsync(GroupId, "当前版本为阿尔宙斯"); } @@ -103,11 +113,11 @@ public void StartingDistribution() } // todo: revise - private string HandleFileUpload(GroupMessageReceiver receiver) + private bool HandleFileUpload(GroupMessageReceiver receiver) { var senderQQ = receiver.Sender.Id; var groupId = receiver.Sender.Group.Id; - var qqMsg = string.Empty; + var result = false; var fileMessages = receiver.MessageChain.OfType(); @@ -125,7 +135,7 @@ private string HandleFileUpload(GroupMessageReceiver receiver) else if (typeof(T) == typeof(PA8) && fileName.EndsWith(".pa8", StringComparison.OrdinalIgnoreCase)) operationType = "pa8"; - else return qqMsg; + else return result; PKM pkm; try @@ -138,10 +148,10 @@ private string HandleFileUpload(GroupMessageReceiver receiver) { case "pk8" or "pb8" when data.Length != 344: MessageManager.SendGroupMessageAsync("非法文件"); - return qqMsg; + return result; case "pa8" when data.Length != 376: MessageManager.SendGroupMessageAsync("非法文件"); - return qqMsg; + return result; } switch (operationType) @@ -155,7 +165,7 @@ private string HandleFileUpload(GroupMessageReceiver receiver) case "pa8": pkm = new PA8(data); break; - default: return qqMsg; + default: return result; } LogUtil.LogText($"operationType:{operationType}"); @@ -164,7 +174,7 @@ private string HandleFileUpload(GroupMessageReceiver receiver) catch (Exception ex) { LogUtil.LogText(ex.ToString()); - return qqMsg; + return result; } MessageManager.SendGroupMessageAsync(receiver.Sender.Name + " 上传了 " + fileName + " 文件"); @@ -173,15 +183,16 @@ private string HandleFileUpload(GroupMessageReceiver receiver) if (_) { GetUserFromQueueAndGenerateCodeToTrade(senderQQ); - msg = string.Empty; + return true; } else { MessageManager.SendGroupMessageAsync(msg); + return false; } } - return qqMsg; + return false; } private string HandleCommand(GroupMessageReceiver receiver) From 5a4cbed010e61b92a1a10bfe3bc9388173309ab1 Mon Sep 17 00:00:00 2001 From: Easy World Date: Sun, 13 Feb 2022 22:40:29 +0800 Subject: [PATCH 008/171] Bug fix --- SysBot.Pokemon.QQ/MiraiQQBot.cs | 2 +- SysBot.Pokemon.QQ/SysBot.Pokemon.QQ.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SysBot.Pokemon.QQ/MiraiQQBot.cs b/SysBot.Pokemon.QQ/MiraiQQBot.cs index 686d291d4..ddb6b8d8a 100644 --- a/SysBot.Pokemon.QQ/MiraiQQBot.cs +++ b/SysBot.Pokemon.QQ/MiraiQQBot.cs @@ -76,7 +76,7 @@ public MiraiQQBot(PokeTradeHub hub, string message) if (cfg.AliveMsg == receiver.MessageChain.OfType()?.First()?.Text) { - MessageManager.SendGroupMessageAsync(cfg.AliveMsg); + MessageManager.SendGroupMessageAsync(groupId, cfg.AliveMsg); return; } diff --git a/SysBot.Pokemon.QQ/SysBot.Pokemon.QQ.csproj b/SysBot.Pokemon.QQ/SysBot.Pokemon.QQ.csproj index c85e95a8d..903031ded 100644 --- a/SysBot.Pokemon.QQ/SysBot.Pokemon.QQ.csproj +++ b/SysBot.Pokemon.QQ/SysBot.Pokemon.QQ.csproj @@ -9,7 +9,7 @@ - + From d0d788438b87abe143441e0cc50521867bebd3d0 Mon Sep 17 00:00:00 2001 From: Easy World Date: Mon, 14 Feb 2022 20:40:04 +0800 Subject: [PATCH 009/171] Update MiraiQQBot.cs Bug Fix --- SysBot.Pokemon.QQ/MiraiQQBot.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SysBot.Pokemon.QQ/MiraiQQBot.cs b/SysBot.Pokemon.QQ/MiraiQQBot.cs index ddb6b8d8a..0ef44b6da 100644 --- a/SysBot.Pokemon.QQ/MiraiQQBot.cs +++ b/SysBot.Pokemon.QQ/MiraiQQBot.cs @@ -74,7 +74,7 @@ public MiraiQQBot(PokeTradeHub hub, string message) if (groupId != GroupId || senderQQ == client.QQ) return; - if (cfg.AliveMsg == receiver.MessageChain.OfType()?.First()?.Text) + if (cfg.AliveMsg == receiver.MessageChain.OfType()?.FirstOrDefault()?.Text) { MessageManager.SendGroupMessageAsync(groupId, cfg.AliveMsg); return; From c4cdc7d2d0883d57467899a5c56586ee59787f7d Mon Sep 17 00:00:00 2001 From: Easy World Date: Mon, 14 Feb 2022 20:48:23 +0800 Subject: [PATCH 010/171] Update README.md --- SysBot.Pokemon.QQ/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SysBot.Pokemon.QQ/README.md b/SysBot.Pokemon.QQ/README.md index 7990c660f..e0cfdf9e8 100644 --- a/SysBot.Pokemon.QQ/README.md +++ b/SysBot.Pokemon.QQ/README.md @@ -18,14 +18,14 @@ Uses [Mirai.Net](https://github.com/SinoAHpx/Mirai.Net) as a dependency via Nuge - Use the miraiQQconfig template and replace your QQ number and your trade QQ group number, save as miraiQQconfig.json to the folder of your `SysBot.Pokemon.ConsoleApp.exe` or `SysBot.exe` - Run the `SysBot.Pokemon.ConsoleApp.exe` or `SysBot.exe` - Send `$trade` and press `ctrl+enter` to the next line, then paste your ALM-Showdown-Sets before sending your QQ message -- Or you can upload a PK8/PB8/PA8 file directly +- Or you can upload a PK8/PB8/PA8 file directly(Do not forget to set your bot as administrator in your QQ group) ## miraiQQconfig template ``` { - "Address": "ip and port of you mirai-api-http config", + "Address": "localhost:8080", "QQ": "your QQ number", - "VerifyKey": "VerifyKey of your mirai-api-http config", + "VerifyKey": "1234567890", "GroupId": "trade QQ group number" } ``` From 260bb80f57d719cf4e970f91db93056105f2edae Mon Sep 17 00:00:00 2001 From: Easy World Date: Mon, 14 Feb 2022 22:46:24 +0800 Subject: [PATCH 011/171] Update MiraiQQBot.cs SendGroupMessage fix --- SysBot.Pokemon.QQ/MiraiQQBot.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SysBot.Pokemon.QQ/MiraiQQBot.cs b/SysBot.Pokemon.QQ/MiraiQQBot.cs index 0ef44b6da..d220d4295 100644 --- a/SysBot.Pokemon.QQ/MiraiQQBot.cs +++ b/SysBot.Pokemon.QQ/MiraiQQBot.cs @@ -147,10 +147,10 @@ private bool HandleFileUpload(GroupMessageReceiver receiver) switch (operationType) { case "pk8" or "pb8" when data.Length != 344: - MessageManager.SendGroupMessageAsync("非法文件"); + MessageManager.SendGroupMessageAsync(groupId, "非法文件"); return result; case "pa8" when data.Length != 376: - MessageManager.SendGroupMessageAsync("非法文件"); + MessageManager.SendGroupMessageAsync(groupId, "非法文件"); return result; } @@ -177,7 +177,7 @@ private bool HandleFileUpload(GroupMessageReceiver receiver) return result; } - MessageManager.SendGroupMessageAsync(receiver.Sender.Name + " 上传了 " + fileName + " 文件"); + MessageManager.SendGroupMessageAsync(groupId, receiver.Sender.Name + " 上传了 " + fileName + " 文件"); var _ = MiraiQQCommandsHelper.AddToWaitingList(pkm, receiver.Sender.Name, ulong.Parse(senderQQ), out string msg); if (_) @@ -187,7 +187,7 @@ private bool HandleFileUpload(GroupMessageReceiver receiver) } else { - MessageManager.SendGroupMessageAsync(msg); + MessageManager.SendGroupMessageAsync(groupId, msg); return false; } } From 794b1af7c37b831c8b957e24dc6bf81cf7306fd8 Mon Sep 17 00:00:00 2001 From: Easy World Date: Tue, 15 Feb 2022 15:32:38 +0800 Subject: [PATCH 012/171] Create dotnet.yml --- .github/workflows/dotnet.yml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .github/workflows/dotnet.yml diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml new file mode 100644 index 000000000..a73cc8bca --- /dev/null +++ b/.github/workflows/dotnet.yml @@ -0,0 +1,25 @@ +name: .NET + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Setup .NET + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 5.0.x + - name: Restore dependencies + run: dotnet restore + - name: Build + run: dotnet build --no-restore + - name: Test + run: dotnet test --no-build --verbosity normal From 5108c0bb1283c55b0642ccf53313b6c4e8c98c54 Mon Sep 17 00:00:00 2001 From: Easy World Date: Tue, 15 Feb 2022 15:37:08 +0800 Subject: [PATCH 013/171] Update dotnet.yml --- .github/workflows/dotnet.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index a73cc8bca..681317759 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -9,7 +9,7 @@ on: jobs: build: - runs-on: ubuntu-latest + runs-on: windows-latest steps: - uses: actions/checkout@v2 From 6c728f8db235f31f4e1ac0b35bbee6266f4ed091 Mon Sep 17 00:00:00 2001 From: Easy World Date: Tue, 15 Feb 2022 15:45:27 +0800 Subject: [PATCH 014/171] Update dotnet.yml --- .github/workflows/dotnet.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 681317759..edb31b3d3 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -16,7 +16,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v1 with: - dotnet-version: 5.0.x + dotnet-version: 4.7.x - name: Restore dependencies run: dotnet restore - name: Build From 40f965a324b6bfdcf36753137bf42ae48a80e292 Mon Sep 17 00:00:00 2001 From: Easy World Date: Tue, 15 Feb 2022 15:52:56 +0800 Subject: [PATCH 015/171] Delete .github/workflows directory --- .github/workflows/dotnet.yml | 25 ------------------------- 1 file changed, 25 deletions(-) delete mode 100644 .github/workflows/dotnet.yml diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml deleted file mode 100644 index edb31b3d3..000000000 --- a/.github/workflows/dotnet.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: .NET - -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] - -jobs: - build: - - runs-on: windows-latest - - steps: - - uses: actions/checkout@v2 - - name: Setup .NET - uses: actions/setup-dotnet@v1 - with: - dotnet-version: 4.7.x - - name: Restore dependencies - run: dotnet restore - - name: Build - run: dotnet build --no-restore - - name: Test - run: dotnet test --no-build --verbosity normal From 2d2f2ad560142d6d55e7bd993566ce6955ca9bce Mon Sep 17 00:00:00 2001 From: Easy World Date: Tue, 15 Feb 2022 16:10:05 +0800 Subject: [PATCH 016/171] Update MiraiQQBot.cs --- SysBot.Pokemon.QQ/MiraiQQBot.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SysBot.Pokemon.QQ/MiraiQQBot.cs b/SysBot.Pokemon.QQ/MiraiQQBot.cs index d220d4295..ac1813f0d 100644 --- a/SysBot.Pokemon.QQ/MiraiQQBot.cs +++ b/SysBot.Pokemon.QQ/MiraiQQBot.cs @@ -250,7 +250,7 @@ private void GetUserFromQueueAndGenerateCodeToTrade(string qq) int code = rnd.Next(0, 9999_9999); //Util.ToInt32(msg); var _ = AddToTradeQueue(user.Pokemon, code, user.QQ, user.DisplayName, RequestSignificance.Favored, PokeRoutineType.LinkTrade, out string message); - MessageManager.SendGroupMessageAsync(GroupId, message); + MessageManager.SendTempMessageAsync(qq, GroupId, message); } #pragma warning disable CA1031 // Do not catch general exception types catch (Exception ex) @@ -295,4 +295,4 @@ private bool AddToTradeQueue(T pk, int code, ulong qq, string displayName, Reque return true; } } -} \ No newline at end of file +} From 81dc24353aad771687b67bf938faa229e0ad9048 Mon Sep 17 00:00:00 2001 From: Easy World Date: Tue, 15 Feb 2022 17:47:01 +0800 Subject: [PATCH 017/171] Update MiraiQQBot.cs --- SysBot.Pokemon.QQ/MiraiQQBot.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SysBot.Pokemon.QQ/MiraiQQBot.cs b/SysBot.Pokemon.QQ/MiraiQQBot.cs index ac1813f0d..0e7ed9cfc 100644 --- a/SysBot.Pokemon.QQ/MiraiQQBot.cs +++ b/SysBot.Pokemon.QQ/MiraiQQBot.cs @@ -250,7 +250,8 @@ private void GetUserFromQueueAndGenerateCodeToTrade(string qq) int code = rnd.Next(0, 9999_9999); //Util.ToInt32(msg); var _ = AddToTradeQueue(user.Pokemon, code, user.QQ, user.DisplayName, RequestSignificance.Favored, PokeRoutineType.LinkTrade, out string message); - MessageManager.SendTempMessageAsync(qq, GroupId, message); + //MessageManager.SendTempMessageAsync(qq, GroupId, message); + MessageManager.SendGroupMessageAsync(GroupId, message); } #pragma warning disable CA1031 // Do not catch general exception types catch (Exception ex) From 14cb97c23cac490de0c9d1300967162fe56c7255 Mon Sep 17 00:00:00 2001 From: Easy World Date: Tue, 15 Feb 2022 18:59:33 +0800 Subject: [PATCH 018/171] Update MiraiQQTradeNotifier.cs SendWhisperMessage for trade code --- SysBot.Pokemon.QQ/Helpers/MiraiQQTradeNotifier.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/SysBot.Pokemon.QQ/Helpers/MiraiQQTradeNotifier.cs b/SysBot.Pokemon.QQ/Helpers/MiraiQQTradeNotifier.cs index 2731ce0b6..1079f4bb7 100644 --- a/SysBot.Pokemon.QQ/Helpers/MiraiQQTradeNotifier.cs +++ b/SysBot.Pokemon.QQ/Helpers/MiraiQQTradeNotifier.cs @@ -60,7 +60,7 @@ public void TradeInitialize(PokeRoutineExecutor routine, PokeTradeDetail i $"@{info.Trainer.TrainerName} (ID: {info.ID}): Initializing trade{receive} with you. Please be ready."; msg += $" Your trade code is: {info.Code:0000 0000}"; LogUtil.LogText(msg); - SendMessage(msg); + SendWhisperMessage(info.Trainer.ID.ToString(), msg); } public void TradeSearching(PokeRoutineExecutor routine, PokeTradeDetail info) @@ -70,7 +70,7 @@ public void TradeSearching(PokeRoutineExecutor routine, PokeTradeDetail in var message = $"I'm waiting for you{trainer}! My IGN is {routine.InGameName}."; message += $" Your trade code is: {info.Code:0000 0000}"; LogUtil.LogText(message); - SendMessage($"@{info.Trainer.TrainerName} {message}"); + SendWhisperMessage(info.Trainer.ID.ToString(), $"@{info.Trainer.TrainerName} {message}"); } public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, PokeTradeSummary message) @@ -93,5 +93,10 @@ private void SendMessage(string message) { MessageManager.SendGroupMessageAsync(GroupId, message); } + + private void SendWhisperMessage(string qq, string message) + { + MessageManager.SendTempMessageAsync(qq, GroupId, message); + } } -} \ No newline at end of file +} From ac94de186d19376dbf37adc6c5797cd183bc9e28 Mon Sep 17 00:00:00 2001 From: Easy World Date: Tue, 15 Feb 2022 19:37:21 +0800 Subject: [PATCH 019/171] Update MiraiQQTradeNotifier.cs Revert --- SysBot.Pokemon.QQ/Helpers/MiraiQQTradeNotifier.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SysBot.Pokemon.QQ/Helpers/MiraiQQTradeNotifier.cs b/SysBot.Pokemon.QQ/Helpers/MiraiQQTradeNotifier.cs index 1079f4bb7..6f81c4cd0 100644 --- a/SysBot.Pokemon.QQ/Helpers/MiraiQQTradeNotifier.cs +++ b/SysBot.Pokemon.QQ/Helpers/MiraiQQTradeNotifier.cs @@ -60,7 +60,7 @@ public void TradeInitialize(PokeRoutineExecutor routine, PokeTradeDetail i $"@{info.Trainer.TrainerName} (ID: {info.ID}): Initializing trade{receive} with you. Please be ready."; msg += $" Your trade code is: {info.Code:0000 0000}"; LogUtil.LogText(msg); - SendWhisperMessage(info.Trainer.ID.ToString(), msg); + SendMessage(msg); } public void TradeSearching(PokeRoutineExecutor routine, PokeTradeDetail info) @@ -70,7 +70,7 @@ public void TradeSearching(PokeRoutineExecutor routine, PokeTradeDetail in var message = $"I'm waiting for you{trainer}! My IGN is {routine.InGameName}."; message += $" Your trade code is: {info.Code:0000 0000}"; LogUtil.LogText(message); - SendWhisperMessage(info.Trainer.ID.ToString(), $"@{info.Trainer.TrainerName} {message}"); + SendMessage($"@{info.Trainer.TrainerName} {message}"); } public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, PokeTradeSummary message) From 0af0e4661c25fbdd616b404f5c053ed61a2fb41d Mon Sep 17 00:00:00 2001 From: Easy World Date: Tue, 15 Feb 2022 23:58:43 +0800 Subject: [PATCH 020/171] Migrate config to config.json, support trade partner info override - Migrate config to config.json - Support trade partner info override --- .../PokeBotRunnerImpl.cs | 13 ++-- SysBot.Pokemon.QQ/MiraiQQBot.cs | 75 ++++++------------- SysBot.Pokemon.WinForms/PokeBotRunnerImpl.cs | 24 ++++-- SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs | 34 +++++++++ .../Settings/Integrations/QQSettings.cs | 39 ++++++++++ SysBot.Pokemon/TradeHub/PokeTradeHubConfig.cs | 4 + 6 files changed, 122 insertions(+), 67 deletions(-) create mode 100644 SysBot.Pokemon/Settings/Integrations/QQSettings.cs diff --git a/SysBot.Pokemon.ConsoleApp/PokeBotRunnerImpl.cs b/SysBot.Pokemon.ConsoleApp/PokeBotRunnerImpl.cs index 205b2922e..40165b8bd 100644 --- a/SysBot.Pokemon.ConsoleApp/PokeBotRunnerImpl.cs +++ b/SysBot.Pokemon.ConsoleApp/PokeBotRunnerImpl.cs @@ -16,13 +16,14 @@ public PokeBotRunnerImpl(PokeTradeHub hub, BotFactory fac) : base(hub, fac public PokeBotRunnerImpl(PokeTradeHubConfig config, BotFactory fac) : base(config, fac) { } private static TwitchBot? Twitch; + private MiraiQQBot? QQ; protected override void AddIntegrations() { AddDiscordBot(Hub.Config.Discord); AddTwitchBot(Hub.Config.Twitch); //add qq bot - AddQQBot(); + AddQQBot(Hub.Config.QQ); } private void AddTwitchBot(TwitchSettings config) @@ -54,12 +55,14 @@ private void AddDiscordBot(DiscordSettings config) Task.Run(() => bot.MainAsync(token, CancellationToken.None), CancellationToken.None); } - private void AddQQBot() + private void AddQQBot(QQSettings config) { - if (!MiraiQQBot.HasConfig()) return; + if (string.IsNullOrWhiteSpace(config.VerifyKey) || string.IsNullOrWhiteSpace(config.Address)) return; + if (string.IsNullOrWhiteSpace(config.QQ) || string.IsNullOrWhiteSpace(config.GroupId)) return; + if (QQ != null) return; //add qq bot - var qqBot = new MiraiQQBot(Hub, "机器人启动成功"); - qqBot.StartingDistribution(); + QQ = new MiraiQQBot(config, Hub); + QQ.StartingDistribution(); } } } diff --git a/SysBot.Pokemon.QQ/MiraiQQBot.cs b/SysBot.Pokemon.QQ/MiraiQQBot.cs index 0e7ed9cfc..b4c8196cb 100644 --- a/SysBot.Pokemon.QQ/MiraiQQBot.cs +++ b/SysBot.Pokemon.QQ/MiraiQQBot.cs @@ -16,56 +16,26 @@ namespace SysBot.Pokemon.QQ { public class MiraiQQBot where T : PKM, new() { - private const string ConfigPath = "miraiQQconfig.json"; - private static MiraiQQSettings DefaultSettings = new(); private static PokeTradeHub Hub = default!; - private readonly MiraiBot client; - private static string GroupId; - private static string WelcomeString = "欢迎使用SysBot.NET"; internal static TradeQueueInfo Info => Hub.Queues.Info; internal static readonly List> QueuePool = new(); + private readonly MiraiBot client; + private readonly string GroupId; + private readonly QQSettings Settings; - public static bool HasConfig() - { - return File.Exists(ConfigPath); - } - - public MiraiQQSettings ReadConfig() - { - if (!HasConfig()) - { - return DefaultSettings; - } - - try - { - var lines = File.ReadAllText(ConfigPath); - var cfg = JsonConvert.DeserializeObject(lines) ?? DefaultSettings; - return cfg; - } -#pragma warning disable CA1031 // Do not catch general exception types - catch (Exception) -#pragma warning restore CA1031 // Do not catch general exception types - { - LogUtil.LogError("miraiQQ config error: {}", "MiraiQQBot"); - return DefaultSettings; - } - } - - public MiraiQQBot(PokeTradeHub hub, string message) + public MiraiQQBot(QQSettings settings, PokeTradeHub hub) { Hub = hub; - WelcomeString = message; - var cfg = ReadConfig(); + Settings = settings; client = new MiraiBot { - Address = cfg.Address, - QQ = cfg.QQ, - VerifyKey = cfg.VerifyKey + Address = settings.Address, + QQ = settings.QQ, + VerifyKey = settings.VerifyKey }; - GroupId = cfg.GroupId; + GroupId = settings.GroupId; client.MessageReceived.OfType() .Subscribe(receiver => { @@ -74,9 +44,9 @@ public MiraiQQBot(PokeTradeHub hub, string message) if (groupId != GroupId || senderQQ == client.QQ) return; - if (cfg.AliveMsg == receiver.MessageChain.OfType()?.FirstOrDefault()?.Text) + if (settings.AliveMsg == receiver.MessageChain.OfType()?.FirstOrDefault()?.Text) { - MessageManager.SendGroupMessageAsync(groupId, cfg.AliveMsg); + MessageManager.SendGroupMessageAsync(groupId, settings.AliveMsg); return; } @@ -85,9 +55,7 @@ public MiraiQQBot(PokeTradeHub hub, string message) return; } - var response = HandleCommand(receiver); - if (response.Length == 0) - return; + HandleCommand(receiver); }); } @@ -96,7 +64,8 @@ public void StartingDistribution() Task.Run(async () => { await client.LaunchAsync(); - await MessageManager.SendGroupMessageAsync(GroupId, WelcomeString); + await MessageManager.SendGroupMessageAsync(GroupId, Settings.MessageStart); + await Task.Delay(1_000).ConfigureAwait(false); if (typeof(T) == typeof(PK8)) { await MessageManager.SendGroupMessageAsync(GroupId, "当前版本为剑盾"); @@ -195,15 +164,16 @@ private bool HandleFileUpload(GroupMessageReceiver receiver) return false; } - private string HandleCommand(GroupMessageReceiver receiver) + private void HandleCommand(GroupMessageReceiver receiver) { - string qqMsg = string.Empty; + string qqMsg; try { qqMsg = receiver.MessageChain.OfType().First().Text; } catch { + return; } LogUtil.LogText($"debug qqMsg:{qqMsg}"); @@ -215,7 +185,7 @@ private string HandleCommand(GroupMessageReceiver receiver) if (split.Length > 0) { c = split[0]; - args = qqMsg.Substring(qqMsg.IndexOf('\n') + 1); + args = qqMsg[(qqMsg.IndexOf('\n') + 1)..]; } switch (c) @@ -225,13 +195,10 @@ private string HandleCommand(GroupMessageReceiver receiver) if (_) { GetUserFromQueueAndGenerateCodeToTrade(qq); - msg = string.Empty; } - return msg; - - - default: return string.Empty; + MessageManager.SendGroupMessageAsync(GroupId, msg); + break; } } @@ -296,4 +263,4 @@ private bool AddToTradeQueue(T pk, int code, ulong qq, string displayName, Reque return true; } } -} +} \ No newline at end of file diff --git a/SysBot.Pokemon.WinForms/PokeBotRunnerImpl.cs b/SysBot.Pokemon.WinForms/PokeBotRunnerImpl.cs index 8835a39bf..fd770e161 100644 --- a/SysBot.Pokemon.WinForms/PokeBotRunnerImpl.cs +++ b/SysBot.Pokemon.WinForms/PokeBotRunnerImpl.cs @@ -14,18 +14,24 @@ namespace SysBot.Pokemon /// public class PokeBotRunnerImpl : PokeBotRunner where T : PKM, new() { - public PokeBotRunnerImpl(PokeTradeHub hub, BotFactory fac) : base(hub, fac) { } - public PokeBotRunnerImpl(PokeTradeHubConfig config, BotFactory fac) : base(config, fac) { } + public PokeBotRunnerImpl(PokeTradeHub hub, BotFactory fac) : base(hub, fac) + { + } + + public PokeBotRunnerImpl(PokeTradeHubConfig config, BotFactory fac) : base(config, fac) + { + } private TwitchBot? Twitch; private YouTubeBot? YouTube; + private MiraiQQBot? QQ; protected override void AddIntegrations() { AddDiscordBot(Hub.Config.Discord.Token); AddTwitchBot(Hub.Config.Twitch); AddYouTubeBot(Hub.Config.YouTube); - AddQQBot(); + AddQQBot(Hub.Config.QQ); } private void AddTwitchBot(TwitchSettings config) @@ -74,12 +80,14 @@ private void AddDiscordBot(string apiToken) Task.Run(() => bot.MainAsync(apiToken, CancellationToken.None)); } - private void AddQQBot() + private void AddQQBot(QQSettings config) { - if (!MiraiQQBot.HasConfig()) return; + if (string.IsNullOrWhiteSpace(config.VerifyKey) || string.IsNullOrWhiteSpace(config.Address)) return; + if (string.IsNullOrWhiteSpace(config.QQ) || string.IsNullOrWhiteSpace(config.GroupId)) return; + if (QQ != null) return; //add qq bot - var qqBot = new MiraiQQBot(Hub, "机器人启动成功"); - qqBot.StartingDistribution(); + QQ = new MiraiQQBot(config, Hub); + QQ.StartingDistribution(); } } -} +} \ No newline at end of file diff --git a/SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs b/SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs index d12d1113b..b3a97620b 100644 --- a/SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs +++ b/SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs @@ -324,6 +324,11 @@ private async Task PerformLinkCodeTrade(SAV8LA sav, PokeTradeDe return update; } + if (Hub.Config.QQ.UseTradePartnerInfo) + { + await SetBoxPkmWithSwappedIDDetailsPLA(toSend, offered, sav, token); + } + Log("Confirming trade."); var tradeResult = await ConfirmAndStartTrading(poke, token).ConfigureAwait(false); if (tradeResult != PokeTradeResult.Success) @@ -792,5 +797,34 @@ private PokeTradeResult CheckPartnerReputation(PokeTradeDetail poke, ulong Comment = $"Added automatically on {DateTime.Now:yyyy.MM.dd-hh:mm:ss} ({comment})", }; + // based on https://github.com/Muchacho13Scripts/SysBot.NET/commit/f7879386f33bcdbd95c7a56e7add897273867106 + private async Task SetBoxPkmWithSwappedIDDetailsPLA(PA8 toSend, PA8 offered, SAV8LA sav, CancellationToken token) + { + var cln = (PA8)toSend.Clone(); + cln.OT_Gender = offered.Gender; + cln.TrainerID7 = offered.TrainerID7; + cln.TrainerSID7 = offered.TrainerSID7; + cln.Language = offered.Language; + cln.OT_Name = offered.OT_Name; + cln.ClearNickname(); + + if (toSend.IsShiny) + cln.SetShiny(); + + cln.RefreshChecksum(); + + var tradela = new LegalityAnalysis(cln); + if (tradela.Valid) + { + Log($"Pokemon is valid, use trade partnerInfo"); + await SetBoxPokemonAbsolute(BoxStartOffset, cln, token, sav).ConfigureAwait(false); + } + else + { + Log($"Pokemon not valid, do nothing to trade Pokemon"); + } + + return tradela.Valid; + } } } diff --git a/SysBot.Pokemon/Settings/Integrations/QQSettings.cs b/SysBot.Pokemon/Settings/Integrations/QQSettings.cs new file mode 100644 index 000000000..7d81d28f4 --- /dev/null +++ b/SysBot.Pokemon/Settings/Integrations/QQSettings.cs @@ -0,0 +1,39 @@ +using System; +using System.ComponentModel; +using System.Linq; + +namespace SysBot.Pokemon +{ + public class QQSettings + { + private const string Startup = nameof(Startup); + private const string Operation = nameof(Operation); + private const string Messages = nameof(Messages); + public override string ToString() => "QQ Integration Settings"; + + // Startup + + [Category(Startup), Description("Mirai Bot Address")] + public string Address { get; set; } = string.Empty; + + [Category(Startup), Description("Mirai Bot VerifyKey")] + public string VerifyKey { get; set; } = string.Empty; + + [Category(Startup), Description("QQ number of your Bot")] + public string QQ { get; set; } = string.Empty; + + [Category(Startup), Description("QQ Group to Send Messages To")] + public string GroupId { get; set; } = string.Empty; + + [Category(Startup), Description("Message to test Bot alive")] + public string AliveMsg { get; set; } = "hello"; + + [Category(Operation), Description("Message sent when the Barrier is released.")] + public string MessageStart { get; set; } = string.Empty; + + // Operation + + [Category(Operation), Description("Use TradePartnerInfo")] + public bool UseTradePartnerInfo { get; set; } = false; + } +} diff --git a/SysBot.Pokemon/TradeHub/PokeTradeHubConfig.cs b/SysBot.Pokemon/TradeHub/PokeTradeHubConfig.cs index ac7929e66..ee009cb17 100644 --- a/SysBot.Pokemon/TradeHub/PokeTradeHubConfig.cs +++ b/SysBot.Pokemon/TradeHub/PokeTradeHubConfig.cs @@ -85,5 +85,9 @@ public sealed class PokeTradeHubConfig : BaseConfig [Category(Integration), Description("Allows favored users to join the queue with a more favorable position than unfavored users.")] [TypeConverter(typeof(ExpandableObjectConverter))] public FavoredPrioritySettings Favoritism { get; set; } = new(); + + [Category(Integration)] + [TypeConverter(typeof(ExpandableObjectConverter))] + public QQSettings QQ { get; set; } = new(); } } \ No newline at end of file From 36feae2a3c1c8417608a118624fe4ffd83f1adb7 Mon Sep 17 00:00:00 2001 From: Easy World Date: Wed, 16 Feb 2022 00:09:49 +0800 Subject: [PATCH 021/171] Update README.md --- SysBot.Pokemon.QQ/README.md | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/SysBot.Pokemon.QQ/README.md b/SysBot.Pokemon.QQ/README.md index e0cfdf9e8..abccf5221 100644 --- a/SysBot.Pokemon.QQ/README.md +++ b/SysBot.Pokemon.QQ/README.md @@ -10,22 +10,17 @@ Uses [Mirai.Net](https://github.com/SinoAHpx/Mirai.Net) as a dependency via Nuge - Support PK8 PB8 PA8 file upload ## Usage -- Build SysBot.Pokemon.ConsoleApp or SysBot.Pokemon.WinForms -- Setup by this [wiki](https://github.com/kwsch/SysBot.NET/wiki/Bot-Startup-Details) +- Build SysBot.Pokemon.WinForms +- Setup your switch **with CFW** by this [wiki](https://github.com/kwsch/SysBot.NET/wiki/Bot-Startup-Details) - Download [MCL](https://github.com/iTXTech/mirai-console-loader) and add mirai plugin [mirai-api-http](https://github.com/project-mirai/mirai-api-http) - Config mirai-api-http by [this](https://github.com/project-mirai/mirai-api-http/blob/master/README.md) - Start [MCL](https://github.com/iTXTech/mirai-console-loader) and check if it works -- Use the miraiQQconfig template and replace your QQ number and your trade QQ group number, save as miraiQQconfig.json to the folder of your `SysBot.Pokemon.ConsoleApp.exe` or `SysBot.exe` -- Run the `SysBot.Pokemon.ConsoleApp.exe` or `SysBot.exe` +- Run `SysBot.exe` and add your mirai config in `Hub` tab +![config template](https://user-images.githubusercontent.com/8280291/154100318-408b2b80-25aa-4c0b-aaec-4556fd0b07e0.png) +- Press `Start` Button - Send `$trade` and press `ctrl+enter` to the next line, then paste your ALM-Showdown-Sets before sending your QQ message +![image](https://user-images.githubusercontent.com/8280291/154101006-8bf68c61-76e3-4e00-bad5-9d692c5aabe2.png) - Or you can upload a PK8/PB8/PA8 file directly(Do not forget to set your bot as administrator in your QQ group) -## miraiQQconfig template -``` -{ - "Address": "localhost:8080", - "QQ": "your QQ number", - "VerifyKey": "1234567890", - "GroupId": "trade QQ group number" -} -``` + + From f3c83de272dbf9047920d31d8897195ffdb5e39a Mon Sep 17 00:00:00 2001 From: Easy World Date: Wed, 16 Feb 2022 23:56:01 +0800 Subject: [PATCH 022/171] Less message --- .../Helpers/MiraiQQTradeNotifier.cs | 24 +++++---- SysBot.Pokemon.QQ/MiraiQQBot.cs | 51 +++++++++++-------- SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs | 2 +- 3 files changed, 45 insertions(+), 32 deletions(-) diff --git a/SysBot.Pokemon.QQ/Helpers/MiraiQQTradeNotifier.cs b/SysBot.Pokemon.QQ/Helpers/MiraiQQTradeNotifier.cs index 6f81c4cd0..50ff2c7d5 100644 --- a/SysBot.Pokemon.QQ/Helpers/MiraiQQTradeNotifier.cs +++ b/SysBot.Pokemon.QQ/Helpers/MiraiQQTradeNotifier.cs @@ -4,6 +4,9 @@ using SysBot.Pokemon; using System; using System.Linq; +using Mirai.Net.Data.Messages; +using Mirai.Net.Data.Messages.Concretes; +using Mirai.Net.Utils.Scaffolds; namespace SysBot.Pokemon.QQ { @@ -31,7 +34,7 @@ public MiraiQQTradeNotifier(T data, PokeTradeTrainerInfo info, int code, string public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, string message) { LogUtil.LogText(message); - SendMessage($"@{info.Trainer.TrainerName}: {message}"); + //SendMessage($"@{info.Trainer.TrainerName}: {message}"); } public void TradeCanceled(PokeRoutineExecutor routine, PokeTradeDetail info, PokeTradeResult msg) @@ -39,7 +42,7 @@ public void TradeCanceled(PokeRoutineExecutor routine, PokeTradeDetail inf OnFinish?.Invoke(routine); var line = $"@{info.Trainer.TrainerName}: Trade canceled, {msg}"; LogUtil.LogText(line); - SendMessage(line); + SendMessage(new AtMessage($"{info.Trainer.ID}").Append(" 取消")); } public void TradeFinished(PokeRoutineExecutor routine, PokeTradeDetail info, T result) @@ -50,7 +53,7 @@ public void TradeFinished(PokeRoutineExecutor routine, PokeTradeDetail inf ? $"Trade finished. Enjoy your {(Species) tradedToUser}!" : "Trade finished!"); LogUtil.LogText(message); - SendMessage(message); + SendMessage(new AtMessage($"{info.Trainer.ID}").Append(" 完成")); } public void TradeInitialize(PokeRoutineExecutor routine, PokeTradeDetail info) @@ -60,7 +63,7 @@ public void TradeInitialize(PokeRoutineExecutor routine, PokeTradeDetail i $"@{info.Trainer.TrainerName} (ID: {info.ID}): Initializing trade{receive} with you. Please be ready."; msg += $" Your trade code is: {info.Code:0000 0000}"; LogUtil.LogText(msg); - SendMessage(msg); + SendMessage(new AtMessage($"{info.Trainer.ID}").Append($" 准备交换\n连接密码:{info.Code:0000 0000}\n我的名字:{routine.InGameName}")); } public void TradeSearching(PokeRoutineExecutor routine, PokeTradeDetail info) @@ -70,7 +73,7 @@ public void TradeSearching(PokeRoutineExecutor routine, PokeTradeDetail in var message = $"I'm waiting for you{trainer}! My IGN is {routine.InGameName}."; message += $" Your trade code is: {info.Code:0000 0000}"; LogUtil.LogText(message); - SendMessage($"@{info.Trainer.TrainerName} {message}"); + SendMessage(new AtMessage($"{info.Trainer.ID}").Append($" 寻找中")); } public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, PokeTradeSummary message) @@ -91,12 +94,13 @@ public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail private void SendMessage(string message) { - MessageManager.SendGroupMessageAsync(GroupId, message); + var _ = MessageManager.SendGroupMessageAsync(GroupId, message).Result; + LogUtil.LogInfo($"msgId:{_} {message}", "debug"); } - - private void SendWhisperMessage(string qq, string message) + + private void SendMessage(MessageBase[] message) { - MessageManager.SendTempMessageAsync(qq, GroupId, message); + var _ = MessageManager.SendGroupMessageAsync(GroupId, message).Result; } } -} +} \ No newline at end of file diff --git a/SysBot.Pokemon.QQ/MiraiQQBot.cs b/SysBot.Pokemon.QQ/MiraiQQBot.cs index b4c8196cb..1e2352bca 100644 --- a/SysBot.Pokemon.QQ/MiraiQQBot.cs +++ b/SysBot.Pokemon.QQ/MiraiQQBot.cs @@ -11,6 +11,7 @@ using System.Linq; using System.Reactive.Linq; using System.Threading.Tasks; +using Mirai.Net.Utils.Scaffolds; namespace SysBot.Pokemon.QQ { @@ -37,7 +38,7 @@ public MiraiQQBot(QQSettings settings, PokeTradeHub hub) }; GroupId = settings.GroupId; client.MessageReceived.OfType() - .Subscribe(receiver => + .Subscribe(async receiver => { var senderQQ = receiver.Sender.Id; var groupId = receiver.Sender.Group.Id; @@ -46,16 +47,16 @@ public MiraiQQBot(QQSettings settings, PokeTradeHub hub) if (settings.AliveMsg == receiver.MessageChain.OfType()?.FirstOrDefault()?.Text) { - MessageManager.SendGroupMessageAsync(groupId, settings.AliveMsg); + await MessageManager.SendGroupMessageAsync(groupId, settings.AliveMsg); return; } - if (HandleFileUpload(receiver)) + if (await HandleFileUpload(receiver)) { return; } - HandleCommand(receiver); + await HandleCommand(receiver); }); } @@ -64,8 +65,12 @@ public void StartingDistribution() Task.Run(async () => { await client.LaunchAsync(); - await MessageManager.SendGroupMessageAsync(GroupId, Settings.MessageStart); - await Task.Delay(1_000).ConfigureAwait(false); + if (!string.IsNullOrWhiteSpace(Settings.MessageStart)) + { + await MessageManager.SendGroupMessageAsync(GroupId, Settings.MessageStart); + await Task.Delay(1_000).ConfigureAwait(false); + } + if (typeof(T) == typeof(PK8)) { await MessageManager.SendGroupMessageAsync(GroupId, "当前版本为剑盾"); @@ -78,11 +83,13 @@ public void StartingDistribution() { await MessageManager.SendGroupMessageAsync(GroupId, "当前版本为阿尔宙斯"); } + + await Task.Delay(1_000).ConfigureAwait(false); }); } // todo: revise - private bool HandleFileUpload(GroupMessageReceiver receiver) + private async Task HandleFileUpload(GroupMessageReceiver receiver) { var senderQQ = receiver.Sender.Id; var groupId = receiver.Sender.Group.Id; @@ -109,17 +116,17 @@ private bool HandleFileUpload(GroupMessageReceiver receiver) PKM pkm; try { - var f = FileManager.GetFileAsync(groupId, file.FileId, true).Result; + var f = await FileManager.GetFileAsync(groupId, file.FileId, true); string url = f.DownloadInfo.Url; byte[] data = new System.Net.WebClient().DownloadData(url); switch (operationType) { case "pk8" or "pb8" when data.Length != 344: - MessageManager.SendGroupMessageAsync(groupId, "非法文件"); + await MessageManager.SendGroupMessageAsync(groupId, "非法文件"); return result; case "pa8" when data.Length != 376: - MessageManager.SendGroupMessageAsync(groupId, "非法文件"); + await MessageManager.SendGroupMessageAsync(groupId, "非法文件"); return result; } @@ -138,7 +145,7 @@ private bool HandleFileUpload(GroupMessageReceiver receiver) } LogUtil.LogText($"operationType:{operationType}"); - FileManager.DeleteFileAsync(groupId, file.FileId); + await FileManager.DeleteFileAsync(groupId, file.FileId); } catch (Exception ex) { @@ -146,17 +153,17 @@ private bool HandleFileUpload(GroupMessageReceiver receiver) return result; } - MessageManager.SendGroupMessageAsync(groupId, receiver.Sender.Name + " 上传了 " + fileName + " 文件"); + //MessageManager.SendGroupMessageAsync(groupId, receiver.Sender.Name + " 上传了 " + fileName + " 文件"); var _ = MiraiQQCommandsHelper.AddToWaitingList(pkm, receiver.Sender.Name, ulong.Parse(senderQQ), out string msg); if (_) { - GetUserFromQueueAndGenerateCodeToTrade(senderQQ); + await GetUserFromQueueAndGenerateCodeToTrade(senderQQ); return true; } else { - MessageManager.SendGroupMessageAsync(groupId, msg); + await MessageManager.SendGroupMessageAsync(groupId, new AtMessage(senderQQ).Append(" 宝可梦信息异常")); return false; } } @@ -164,7 +171,7 @@ private bool HandleFileUpload(GroupMessageReceiver receiver) return false; } - private void HandleCommand(GroupMessageReceiver receiver) + private async Task HandleCommand(GroupMessageReceiver receiver) { string qqMsg; try @@ -194,15 +201,18 @@ private void HandleCommand(GroupMessageReceiver receiver) var _ = MiraiQQCommandsHelper.AddToWaitingList(args, nickName, ulong.Parse(qq), out string msg); if (_) { - GetUserFromQueueAndGenerateCodeToTrade(qq); + await GetUserFromQueueAndGenerateCodeToTrade(qq); + } + else + { + await MessageManager.SendGroupMessageAsync(GroupId, new AtMessage(qq).Append(" 宝可梦信息异常")); } - MessageManager.SendGroupMessageAsync(GroupId, msg); break; } } - private void GetUserFromQueueAndGenerateCodeToTrade(string qq) + private async Task GetUserFromQueueAndGenerateCodeToTrade(string qq) { var user = QueuePool.FindLast(q => q.QQ == ulong.Parse(qq)); @@ -217,8 +227,8 @@ private void GetUserFromQueueAndGenerateCodeToTrade(string qq) int code = rnd.Next(0, 9999_9999); //Util.ToInt32(msg); var _ = AddToTradeQueue(user.Pokemon, code, user.QQ, user.DisplayName, RequestSignificance.Favored, PokeRoutineType.LinkTrade, out string message); - //MessageManager.SendTempMessageAsync(qq, GroupId, message); - MessageManager.SendGroupMessageAsync(GroupId, message); + if (!_) + await MessageManager.SendGroupMessageAsync(GroupId, new AtMessage(qq).Append(" 已在队列中")); } #pragma warning disable CA1031 // Do not catch general exception types catch (Exception ex) @@ -232,7 +242,6 @@ private void GetUserFromQueueAndGenerateCodeToTrade(string qq) private bool AddToTradeQueue(T pk, int code, ulong qq, string displayName, RequestSignificance sig, PokeRoutineType type, out string msg) { - // var user = e.WhisperMessage.UserId; var userID = qq; var name = displayName; diff --git a/SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs b/SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs index b3a97620b..db72a4da5 100644 --- a/SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs +++ b/SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs @@ -801,7 +801,7 @@ private PokeTradeResult CheckPartnerReputation(PokeTradeDetail poke, ulong private async Task SetBoxPkmWithSwappedIDDetailsPLA(PA8 toSend, PA8 offered, SAV8LA sav, CancellationToken token) { var cln = (PA8)toSend.Clone(); - cln.OT_Gender = offered.Gender; + cln.OT_Gender = offered.OT_Gender; cln.TrainerID7 = offered.TrainerID7; cln.TrainerSID7 = offered.TrainerSID7; cln.Language = offered.Language; From 1ae508e4e386d243cc2e8ccc81cfc06e69f3d617 Mon Sep 17 00:00:00 2001 From: Easy World Date: Thu, 17 Feb 2022 21:49:02 +0800 Subject: [PATCH 023/171] Message adjust --- SysBot.Pokemon.QQ/MiraiQQBot.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/SysBot.Pokemon.QQ/MiraiQQBot.cs b/SysBot.Pokemon.QQ/MiraiQQBot.cs index 1e2352bca..c8287b916 100644 --- a/SysBot.Pokemon.QQ/MiraiQQBot.cs +++ b/SysBot.Pokemon.QQ/MiraiQQBot.cs @@ -229,6 +229,8 @@ private async Task GetUserFromQueueAndGenerateCodeToTrade(string qq) PokeRoutineType.LinkTrade, out string message); if (!_) await MessageManager.SendGroupMessageAsync(GroupId, new AtMessage(qq).Append(" 已在队列中")); + else + await MessageManager.SendGroupMessageAsync(GroupId, new AtMessage(qq).Append(message)); } #pragma warning disable CA1031 // Do not catch general exception types catch (Exception ex) @@ -260,13 +262,15 @@ private bool AddToTradeQueue(T pk, int code, ulong qq, string displayName, Reque } var position = Info.CheckPosition(userID, type); - msg = $"@{name}: Added to the {type} queue, unique ID: {detail.ID}. Current Position: {position.Position}"; + //msg = $"@{name}: Added to the {type} queue, unique ID: {detail.ID}. Current Position: {position.Position}"; + msg = $" 你在第{position.Position}位"; var botct = Info.Hub.Bots.Count; if (position.Position > botct) { var eta = Info.Hub.Config.Queues.EstimateDelay(position.Position, botct); - msg += $". Estimated: {eta:F1} minutes."; + //msg += $". Estimated: {eta:F1} minutes."; + msg += $", 需等待约{eta:F1}分钟"; } return true; From a72eebdafdc433899c75ba62b99a80e0a8b576f0 Mon Sep 17 00:00:00 2001 From: Easy World Date: Fri, 4 Mar 2022 21:00:26 +0800 Subject: [PATCH 024/171] Minor update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update UseTradePartnerInfo Config, by using trade trainer info instead of trade pokemon's OT info * Support Random EC config * Support Chinese words to showdown(eg: 异色头目怕寂寞母怪力) --- .../Helpers/MiraiQQTradeNotifier.cs | 5 +- .../Helpers/ShowdownTranslator.cs | 93 +++++++ SysBot.Pokemon.QQ/MiraiQQBot.cs | 235 +++++++++++------- SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs | 26 +- SysBot.Pokemon/LA/BotTrade/TradePartnerLA.cs | 13 +- .../Settings/Integrations/QQSettings.cs | 3 + 6 files changed, 274 insertions(+), 101 deletions(-) create mode 100644 SysBot.Pokemon.QQ/Helpers/ShowdownTranslator.cs diff --git a/SysBot.Pokemon.QQ/Helpers/MiraiQQTradeNotifier.cs b/SysBot.Pokemon.QQ/Helpers/MiraiQQTradeNotifier.cs index 50ff2c7d5..b55896ccf 100644 --- a/SysBot.Pokemon.QQ/Helpers/MiraiQQTradeNotifier.cs +++ b/SysBot.Pokemon.QQ/Helpers/MiraiQQTradeNotifier.cs @@ -63,7 +63,10 @@ public void TradeInitialize(PokeRoutineExecutor routine, PokeTradeDetail i $"@{info.Trainer.TrainerName} (ID: {info.ID}): Initializing trade{receive} with you. Please be ready."; msg += $" Your trade code is: {info.Code:0000 0000}"; LogUtil.LogText(msg); - SendMessage(new AtMessage($"{info.Trainer.ID}").Append($" 准备交换\n连接密码:{info.Code:0000 0000}\n我的名字:{routine.InGameName}")); + SendMessage(MiraiQQBot.TradeCodeDictionary.ContainsKey(info.Trainer.ID.ToString()) + ? new AtMessage($"{info.Trainer.ID}").Append($" 准备交换\n连接密码是你私信我的\n我的名字:{routine.InGameName}") + : new AtMessage($"{info.Trainer.ID}").Append( + $" 准备交换\n连接密码:{info.Code:0000 0000}\n我的名字:{routine.InGameName}")); } public void TradeSearching(PokeRoutineExecutor routine, PokeTradeDetail info) diff --git a/SysBot.Pokemon.QQ/Helpers/ShowdownTranslator.cs b/SysBot.Pokemon.QQ/Helpers/ShowdownTranslator.cs new file mode 100644 index 000000000..c2950c2b8 --- /dev/null +++ b/SysBot.Pokemon.QQ/Helpers/ShowdownTranslator.cs @@ -0,0 +1,93 @@ +using PKHeX.Core; +using System.Text.RegularExpressions; + +namespace SysBot.Pokemon.QQ +{ + public class ShowdownTranslator + { + public static string Chinese2Showdown(string zh) + { + string result = ""; + var gameStrings = GameInfo.GetStrings("zh"); + var gameStringsEn = GameInfo.GetStrings("en"); + int candidateSpecieNo = 0; + int candidateSpecieStringLength = 0; + for (int i = 0; i < gameStrings.Species.Count; i++) + { + if (zh.Contains(gameStrings.Species[i]) && gameStrings.Species[i].Length > candidateSpecieStringLength) + { + candidateSpecieNo = i; + candidateSpecieStringLength = gameStrings.Species[i].Length; + } + } + + if (candidateSpecieNo > 0) + { + result += gameStringsEn.Species[candidateSpecieNo]; + zh = zh.Replace(gameStrings.Species[candidateSpecieNo], ""); + } + else + { + return result; + } + + if (Regex.IsMatch(zh, "[A-Z?!?!]形态")) + { + string formsUnown = Regex.Match(zh, "([A-Z?!?!])形态").Groups?[1]?.Value ?? ""; + result += $"-{formsUnown}"; + zh = Regex.Replace(zh, "[A-Z?!?!]形态", ""); + } + + for (int i = 0; i < gameStrings.forms.Length; i++) + { + if (gameStrings.forms[i].Length == 0) continue; + if (!zh.Contains(gameStrings.forms[i] + "形态")) continue; + result += $"-{gameStringsEn.forms[i]}"; + zh = zh.Replace(gameStrings.forms[i] + "形态", ""); + break; + } + + if (zh.Contains("公")) + { + result += " (M)"; + zh = zh.Replace("公", ""); + } + else if (zh.Contains("母")) + { + result += " (F)"; + zh = zh.Replace("母", ""); + } + + if (Regex.IsMatch(zh, "\\d{1,3}级")) + { + string level = Regex.Match(zh, "(\\d{1,3})级").Groups?[1]?.Value ?? "100"; + result += $"\nLevel: {level}"; + zh = Regex.Replace(zh, "\\d{1,3}级", ""); + } + + if (zh.Contains("异色")) + { + result += "\nShiny: Yes"; + zh = zh.Replace("异色", ""); + } + + if (zh.Contains("头目")) + { + result += "\nAlpha: Yes"; + zh = zh.Replace("头目", ""); + } + + for (int i = 0; i < gameStrings.Natures.Count; i++) + { + if (zh.Contains(gameStrings.Natures[i])) + { + result += $"\n{gameStringsEn.Natures[i]} Nature"; + zh = zh.Replace(gameStrings.Natures[i], ""); + break; + } + } + + return result; + } + } +} \ No newline at end of file diff --git a/SysBot.Pokemon.QQ/MiraiQQBot.cs b/SysBot.Pokemon.QQ/MiraiQQBot.cs index c8287b916..c905a07c2 100644 --- a/SysBot.Pokemon.QQ/MiraiQQBot.cs +++ b/SysBot.Pokemon.QQ/MiraiQQBot.cs @@ -6,12 +6,15 @@ using PKHeX.Core; using SysBot.Base; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reactive.Linq; using System.Threading.Tasks; using Mirai.Net.Utils.Scaffolds; +using System.Text.RegularExpressions; +using Mirai.Net.Data.Events.Concretes.Group; namespace SysBot.Pokemon.QQ { @@ -21,50 +24,57 @@ namespace SysBot.Pokemon.QQ internal static TradeQueueInfo Info => Hub.Queues.Info; internal static readonly List> QueuePool = new(); - private readonly MiraiBot client; + private readonly MiraiBot Client; private readonly string GroupId; private readonly QQSettings Settings; + // concurrent? + internal static ConcurrentDictionary TradeCodeDictionary = new(); public MiraiQQBot(QQSettings settings, PokeTradeHub hub) { Hub = hub; Settings = settings; - client = new MiraiBot + Client = new MiraiBot { Address = settings.Address, QQ = settings.QQ, VerifyKey = settings.VerifyKey }; GroupId = settings.GroupId; - client.MessageReceived.OfType() + Client.MessageReceived.OfType() .Subscribe(async receiver => { - var senderQQ = receiver.Sender.Id; - var groupId = receiver.Sender.Group.Id; - if (groupId != GroupId || senderQQ == client.QQ) + if (IsBotOrNotTargetGroup(receiver)) return; - if (settings.AliveMsg == receiver.MessageChain.OfType()?.FirstOrDefault()?.Text) - { - await MessageManager.SendGroupMessageAsync(groupId, settings.AliveMsg); - return; - } + await HandleAliveMessage(receiver); + await HandleFileUpload(receiver); + await HandleCommand(receiver); + await HandlePokemonName(receiver); + await HandleCancel(receiver); + }); - if (await HandleFileUpload(receiver)) + Client.MessageReceived.OfType() + .Subscribe(receiver => + { + var tradeCode = receiver.MessageChain.OfType()?.FirstOrDefault()?.Text ?? ""; + if (Regex.IsMatch(tradeCode, "\\d{8}")) { - return; + TradeCodeDictionary[receiver.Sender.Id] = int.Parse(tradeCode); } - - await HandleCommand(receiver); }); + Client.EventReceived.OfType() + .Subscribe(receiver => { Info.ClearTrade(ulong.Parse(receiver.Member.Id)); }); + Client.EventReceived.OfType() + .Subscribe(receiver => { Info.ClearTrade(ulong.Parse(receiver.Member.Id)); }); } public void StartingDistribution() { Task.Run(async () => { - await client.LaunchAsync(); + await Client.LaunchAsync(); if (!string.IsNullOrWhiteSpace(Settings.MessageStart)) { await MessageManager.SendGroupMessageAsync(GroupId, Settings.MessageStart); @@ -88,87 +98,118 @@ public void StartingDistribution() }); } - // todo: revise - private async Task HandleFileUpload(GroupMessageReceiver receiver) + private bool IsBotOrNotTargetGroup(GroupMessageReceiver receiver) { - var senderQQ = receiver.Sender.Id; - var groupId = receiver.Sender.Group.Id; - var result = false; + return receiver.Sender.Group.Id != GroupId || receiver.Sender.Id == Settings.QQ; + } - var fileMessages = receiver.MessageChain.OfType(); + private async Task HandleAliveMessage(GroupMessageReceiver receiver) + { + if (Settings.AliveMsg == receiver.MessageChain.OfType()?.FirstOrDefault()?.Text) + { + await MessageManager.SendGroupMessageAsync(receiver.Sender.Group.Id, Settings.AliveMsg); + return; + } + } - if (fileMessages.Any()) + private async Task HandleCancel(GroupMessageReceiver receiver) + { + if (receiver.MessageChain.OfType().All(x => x.Target != Settings.QQ)) return; + bool isCancelMsg = (receiver.MessageChain.OfType()?.First()?.Text ?? "").Trim() + .StartsWith("取消"); + if (!isCancelMsg) return; + var result = Info.ClearTrade(ulong.Parse(receiver.Sender.Id)); + await receiver.SendMessageAsync(GetClearTradeMessage(result)); + } + + private static string GetClearTradeMessage(QueueResultRemove result) + { + return result switch { - LogUtil.LogText("In file module"); - var file = fileMessages.First(); - var fileName = file.Name; - string operationType; - if (typeof(T) == typeof(PK8) && - fileName.EndsWith(".pk8", StringComparison.OrdinalIgnoreCase)) operationType = "pk8"; - else if (typeof(T) == typeof(PB8) && - fileName.EndsWith(".pb8", StringComparison.OrdinalIgnoreCase)) - operationType = "pb8"; - else if (typeof(T) == typeof(PA8) && - fileName.EndsWith(".pa8", StringComparison.OrdinalIgnoreCase)) - operationType = "pa8"; - else return result; - - PKM pkm; - try - { - var f = await FileManager.GetFileAsync(groupId, file.FileId, true); + QueueResultRemove.CurrentlyProcessing => "你正在交换中", + QueueResultRemove.CurrentlyProcessingRemoved => "正在删除", + QueueResultRemove.Removed => "已删除", + _ => "你不在队列里", + }; + } - string url = f.DownloadInfo.Url; - byte[] data = new System.Net.WebClient().DownloadData(url); - switch (operationType) - { - case "pk8" or "pb8" when data.Length != 344: - await MessageManager.SendGroupMessageAsync(groupId, "非法文件"); - return result; - case "pa8" when data.Length != 376: - await MessageManager.SendGroupMessageAsync(groupId, "非法文件"); - return result; - } + private async Task HandlePokemonName(GroupMessageReceiver receiver) + { + if (receiver.MessageChain.OfType().All(x => x.Target != Settings.QQ)) return; + var text = receiver.MessageChain.OfType()?.First()?.Text ?? ""; + if (string.IsNullOrWhiteSpace(text)) return; + string ps = ShowdownTranslator.Chinese2Showdown(text); + if (string.IsNullOrWhiteSpace(ps)) return; + LogUtil.LogInfo($"code\n{ps}", "ps"); + var _ = MiraiQQCommandsHelper.AddToWaitingList(ps, receiver.Sender.Name, + ulong.Parse(receiver.Sender.Id), out string msg); + + await ProcessAddWaitingListResult(_, msg, receiver.Sender.Id); + } - switch (operationType) - { - case "pk8": - pkm = new PK8(data); - break; - case "pb8": - pkm = new PB8(data); - break; - case "pa8": - pkm = new PA8(data); - break; - default: return result; - } + private async Task HandleFileUpload(GroupMessageReceiver receiver) + { + var senderQQ = receiver.Sender.Id; + var groupId = receiver.Sender.Group.Id; - LogUtil.LogText($"operationType:{operationType}"); - await FileManager.DeleteFileAsync(groupId, file.FileId); - } - catch (Exception ex) - { - LogUtil.LogText(ex.ToString()); - return result; - } + var fileMessage = receiver.MessageChain.OfType()?.FirstOrDefault(); + if (fileMessage == null) return; + LogUtil.LogText("In file module"); + var fileName = fileMessage.Name; + string operationType; + if (typeof(T) == typeof(PK8) && + fileName.EndsWith(".pk8", StringComparison.OrdinalIgnoreCase)) operationType = "pk8"; + else if (typeof(T) == typeof(PB8) && + fileName.EndsWith(".pb8", StringComparison.OrdinalIgnoreCase)) + operationType = "pb8"; + else if (typeof(T) == typeof(PA8) && + fileName.EndsWith(".pa8", StringComparison.OrdinalIgnoreCase)) + operationType = "pa8"; + else return; + + PKM pkm; + try + { + var f = await FileManager.GetFileAsync(groupId, fileMessage.FileId, true); - //MessageManager.SendGroupMessageAsync(groupId, receiver.Sender.Name + " 上传了 " + fileName + " 文件"); - var _ = MiraiQQCommandsHelper.AddToWaitingList(pkm, receiver.Sender.Name, - ulong.Parse(senderQQ), out string msg); - if (_) + string url = f.DownloadInfo.Url; + byte[] data = new System.Net.WebClient().DownloadData(url); + switch (operationType) { - await GetUserFromQueueAndGenerateCodeToTrade(senderQQ); - return true; + case "pk8" or "pb8" when data.Length != 344: + await MessageManager.SendGroupMessageAsync(groupId, "非法文件"); + return; + case "pa8" when data.Length != 376: + await MessageManager.SendGroupMessageAsync(groupId, "非法文件"); + return; } - else + + switch (operationType) { - await MessageManager.SendGroupMessageAsync(groupId, new AtMessage(senderQQ).Append(" 宝可梦信息异常")); - return false; + case "pk8": + pkm = new PK8(data); + break; + case "pb8": + pkm = new PB8(data); + break; + case "pa8": + pkm = new PA8(data); + break; + default: return; } + + LogUtil.LogText($"operationType:{operationType}"); + await FileManager.DeleteFileAsync(groupId, fileMessage.FileId); + } + catch (Exception ex) + { + LogUtil.LogText(ex.ToString()); + return; } - return false; + var _ = MiraiQQCommandsHelper.AddToWaitingList(pkm, receiver.Sender.Name, + ulong.Parse(senderQQ), out string msg); + await ProcessAddWaitingListResult(_, msg, senderQQ); } private async Task HandleCommand(GroupMessageReceiver receiver) @@ -198,33 +239,45 @@ private async Task HandleCommand(GroupMessageReceiver receiver) switch (c) { case "$trade": - var _ = MiraiQQCommandsHelper.AddToWaitingList(args, nickName, ulong.Parse(qq), out string msg); - if (_) + try { - await GetUserFromQueueAndGenerateCodeToTrade(qq); + await receiver.RecallAsync(); } - else + catch (Exception) { - await MessageManager.SendGroupMessageAsync(GroupId, new AtMessage(qq).Append(" 宝可梦信息异常")); + LogUtil.LogError("recall failed", "mirai"); } + var _ = MiraiQQCommandsHelper.AddToWaitingList(args, nickName, ulong.Parse(qq), out string msg); + await ProcessAddWaitingListResult(_, msg, qq); break; } } + private async Task ProcessAddWaitingListResult(bool success, string msg, string qq) + { + if (success) + await GetUserFromQueueAndGenerateCodeToTrade(qq); + else + { + LogUtil.LogError(msg, "trade"); + await MessageManager.SendGroupMessageAsync(GroupId, new AtMessage(qq).Append(" 宝可梦信息异常")); + } + } + private async Task GetUserFromQueueAndGenerateCodeToTrade(string qq) { var user = QueuePool.FindLast(q => q.QQ == ulong.Parse(qq)); - //LogUtil.LogInfo("QueuePool.ToString" + QueuePool.ToString(), "debug"); if (user == null) return; QueuePool.Remove(user); - Random rnd = new(); try { - int code = rnd.Next(0, 9999_9999); //Util.ToInt32(msg); + int code = TradeCodeDictionary.ContainsKey(qq) + ? TradeCodeDictionary[qq] + : Info.GetRandomTradeCode(); //Util.ToInt32(msg); var _ = AddToTradeQueue(user.Pokemon, code, user.QQ, user.DisplayName, RequestSignificance.Favored, PokeRoutineType.LinkTrade, out string message); if (!_) diff --git a/SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs b/SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs index db72a4da5..8c5b57161 100644 --- a/SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs +++ b/SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs @@ -276,6 +276,7 @@ private async Task PerformLinkCodeTrade(SAV8LA sav, PokeTradeDe var tradePartner = await GetTradePartnerInfo(token).ConfigureAwait(false); var trainerNID = await GetTradePartnerNID(TradePartnerNIDOffset, token).ConfigureAwait(false); + tradePartner.NID = trainerNID; RecordUtil.Record($"Initiating\t{trainerNID:X16}\t{tradePartner.TrainerName}\t{poke.Trainer.TrainerName}\t{poke.Trainer.ID}\t{poke.ID}\t{toSend.EncryptionConstant:X8}"); Log($"Found Link Trade partner: {tradePartner.TrainerName}-{tradePartner.TID7} (ID: {trainerNID})"); @@ -324,9 +325,14 @@ private async Task PerformLinkCodeTrade(SAV8LA sav, PokeTradeDe return update; } + if (Hub.Config.QQ.RandomEC) + { + toSend.SetRandomEC(); + } + if (Hub.Config.QQ.UseTradePartnerInfo) { - await SetBoxPkmWithSwappedIDDetailsPLA(toSend, offered, sav, token); + await SetBoxPkmWithSwappedIDDetailsPLA(toSend, tradePartner, sav, token); } Log("Confirming trade."); @@ -554,7 +560,10 @@ private async Task GetTradePartnerInfo(CancellationToken token) { var id = await SwitchConnection.PointerPeek(4, Offsets.LinkTradePartnerTIDPointer, token).ConfigureAwait(false); var name = await SwitchConnection.PointerPeek(TradePartnerLA.MaxByteLengthStringObject, Offsets.LinkTradePartnerNamePointer, token).ConfigureAwait(false); - return new TradePartnerLA(id, name); + var traderOffset = await SwitchConnection.PointerAll(Offsets.LinkTradePartnerTIDPointer, token).ConfigureAwait(false); + var idbytes = await SwitchConnection.ReadBytesAbsoluteAsync(traderOffset + 0x04, 4, token).ConfigureAwait(false); + + return new TradePartnerLA(id, name, idbytes); } protected virtual async Task<(PA8 toSend, PokeTradeResult check)> GetEntityToSend(SAV8LA sav, PokeTradeDetail poke, PA8 offered, PA8 toSend, PartnerDataHolder partnerID, CancellationToken token) @@ -798,14 +807,15 @@ private PokeTradeResult CheckPartnerReputation(PokeTradeDetail poke, ulong }; // based on https://github.com/Muchacho13Scripts/SysBot.NET/commit/f7879386f33bcdbd95c7a56e7add897273867106 - private async Task SetBoxPkmWithSwappedIDDetailsPLA(PA8 toSend, PA8 offered, SAV8LA sav, CancellationToken token) + // and https://github.com/berichan/SysBot.PLA/commit/84042d4716007dc6ff3100ad4be4a483d622ccf8 + private async Task SetBoxPkmWithSwappedIDDetailsPLA(PA8 toSend, TradePartnerLA tradePartner, SAV8LA sav, CancellationToken token) { var cln = (PA8)toSend.Clone(); - cln.OT_Gender = offered.OT_Gender; - cln.TrainerID7 = offered.TrainerID7; - cln.TrainerSID7 = offered.TrainerSID7; - cln.Language = offered.Language; - cln.OT_Name = offered.OT_Name; + cln.OT_Gender = tradePartner.Gender; + cln.TrainerID7 = int.Parse(tradePartner.TID7); + cln.TrainerSID7 = int.Parse(tradePartner.SID7); + cln.Language = tradePartner.Language; + cln.OT_Name = tradePartner.TrainerName; cln.ClearNickname(); if (toSend.IsShiny) diff --git a/SysBot.Pokemon/LA/BotTrade/TradePartnerLA.cs b/SysBot.Pokemon/LA/BotTrade/TradePartnerLA.cs index fffc92ba5..17643ba13 100644 --- a/SysBot.Pokemon/LA/BotTrade/TradePartnerLA.cs +++ b/SysBot.Pokemon/LA/BotTrade/TradePartnerLA.cs @@ -10,7 +10,14 @@ public sealed class TradePartnerLA public string SID7 { get; } public string TrainerName { get; } - public TradePartnerLA(byte[] TIDSID, byte[] trainerNameObject) + // based on https://github.com/berichan/SysBot.PLA/commit/8196b11a48e66d1ef3fa6c9c8f36c9bcc6cf96e7 + public byte Game { get; } + public byte Language { get; } + public byte Gender { get; } + + public ulong NID { get; set; } + + public TradePartnerLA(byte[] TIDSID, byte[] trainerNameObject, byte[] idbytes) { Debug.Assert(TIDSID.Length == 4); var tidsid = BitConverter.ToUInt32(TIDSID, 0); @@ -18,6 +25,10 @@ public TradePartnerLA(byte[] TIDSID, byte[] trainerNameObject) SID7 = $"{tidsid / 1_000_000:0000}"; TrainerName = StringConverter8.GetString(trainerNameObject); + + Game = idbytes[0]; + Gender = idbytes[1]; + Language = idbytes[3]; } public const int MaxByteLengthStringObject = 0x26; diff --git a/SysBot.Pokemon/Settings/Integrations/QQSettings.cs b/SysBot.Pokemon/Settings/Integrations/QQSettings.cs index 7d81d28f4..0e1bfacfe 100644 --- a/SysBot.Pokemon/Settings/Integrations/QQSettings.cs +++ b/SysBot.Pokemon/Settings/Integrations/QQSettings.cs @@ -35,5 +35,8 @@ public class QQSettings [Category(Operation), Description("Use TradePartnerInfo")] public bool UseTradePartnerInfo { get; set; } = false; + + [Category(Operation), Description("Random EC")] + public bool RandomEC { get; set; } = false; } } From 448c695c21a30cc1f647328eb48e9f6ba720bd5a Mon Sep 17 00:00:00 2001 From: Easy World Date: Fri, 4 Mar 2022 21:13:07 +0800 Subject: [PATCH 025/171] Update PKHeX.Core --- SysBot.Pokemon.QQ/SysBot.Pokemon.QQ.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SysBot.Pokemon.QQ/SysBot.Pokemon.QQ.csproj b/SysBot.Pokemon.QQ/SysBot.Pokemon.QQ.csproj index 903031ded..8ec8dafc9 100644 --- a/SysBot.Pokemon.QQ/SysBot.Pokemon.QQ.csproj +++ b/SysBot.Pokemon.QQ/SysBot.Pokemon.QQ.csproj @@ -9,7 +9,7 @@ - + From ba458070b59edef8e66677997f27baaca8af61ec Mon Sep 17 00:00:00 2001 From: Easy World Date: Sun, 6 Mar 2022 22:51:59 +0800 Subject: [PATCH 026/171] Minor clean Waiting for PKHeX.Core.AutoMod.dll update --- .../PokeBotRunnerImpl.cs | 1 - .../Helpers/ShowdownTranslator.cs | 33 ++++--- SysBot.Pokemon.QQ/MiraiQQBot.cs | 98 +++++++++++++------ SysBot.Pokemon.WinForms/PokeBotRunnerImpl.cs | 1 - 4 files changed, 87 insertions(+), 46 deletions(-) diff --git a/SysBot.Pokemon.ConsoleApp/PokeBotRunnerImpl.cs b/SysBot.Pokemon.ConsoleApp/PokeBotRunnerImpl.cs index 40165b8bd..e1f6c357e 100644 --- a/SysBot.Pokemon.ConsoleApp/PokeBotRunnerImpl.cs +++ b/SysBot.Pokemon.ConsoleApp/PokeBotRunnerImpl.cs @@ -62,7 +62,6 @@ private void AddQQBot(QQSettings config) if (QQ != null) return; //add qq bot QQ = new MiraiQQBot(config, Hub); - QQ.StartingDistribution(); } } } diff --git a/SysBot.Pokemon.QQ/Helpers/ShowdownTranslator.cs b/SysBot.Pokemon.QQ/Helpers/ShowdownTranslator.cs index c2950c2b8..b447491a8 100644 --- a/SysBot.Pokemon.QQ/Helpers/ShowdownTranslator.cs +++ b/SysBot.Pokemon.QQ/Helpers/ShowdownTranslator.cs @@ -5,26 +5,27 @@ namespace SysBot.Pokemon.QQ { public class ShowdownTranslator { + public static GameStrings GameStrings = GameInfo.GetStrings("zh"); + public static GameStrings GameStringsEn = GameInfo.GetStrings("en"); public static string Chinese2Showdown(string zh) { string result = ""; - var gameStrings = GameInfo.GetStrings("zh"); - var gameStringsEn = GameInfo.GetStrings("en"); + int candidateSpecieNo = 0; int candidateSpecieStringLength = 0; - for (int i = 0; i < gameStrings.Species.Count; i++) + for (int i = 0; i < GameStrings.Species.Count; i++) { - if (zh.Contains(gameStrings.Species[i]) && gameStrings.Species[i].Length > candidateSpecieStringLength) + if (zh.Contains(GameStrings.Species[i]) && GameStrings.Species[i].Length > candidateSpecieStringLength) { candidateSpecieNo = i; - candidateSpecieStringLength = gameStrings.Species[i].Length; + candidateSpecieStringLength = GameStrings.Species[i].Length; } } if (candidateSpecieNo > 0) { - result += gameStringsEn.Species[candidateSpecieNo]; - zh = zh.Replace(gameStrings.Species[candidateSpecieNo], ""); + result += GameStringsEn.Species[candidateSpecieNo]; + zh = zh.Replace(GameStrings.Species[candidateSpecieNo], ""); } else { @@ -38,12 +39,12 @@ public static string Chinese2Showdown(string zh) zh = Regex.Replace(zh, "[A-Z?!?!]形态", ""); } - for (int i = 0; i < gameStrings.forms.Length; i++) + for (int i = 0; i < GameStrings.forms.Length; i++) { - if (gameStrings.forms[i].Length == 0) continue; - if (!zh.Contains(gameStrings.forms[i] + "形态")) continue; - result += $"-{gameStringsEn.forms[i]}"; - zh = zh.Replace(gameStrings.forms[i] + "形态", ""); + if (GameStrings.forms[i].Length == 0) continue; + if (!zh.Contains(GameStrings.forms[i] + "形态")) continue; + result += $"-{GameStringsEn.forms[i]}"; + zh = zh.Replace(GameStrings.forms[i] + "形态", ""); break; } @@ -77,12 +78,12 @@ public static string Chinese2Showdown(string zh) zh = zh.Replace("头目", ""); } - for (int i = 0; i < gameStrings.Natures.Count; i++) + for (int i = 0; i < GameStrings.Natures.Count; i++) { - if (zh.Contains(gameStrings.Natures[i])) + if (zh.Contains(GameStrings.Natures[i])) { - result += $"\n{gameStringsEn.Natures[i]} Nature"; - zh = zh.Replace(gameStrings.Natures[i], ""); + result += $"\n{GameStringsEn.Natures[i]} Nature"; + zh = zh.Replace(GameStrings.Natures[i], ""); break; } } diff --git a/SysBot.Pokemon.QQ/MiraiQQBot.cs b/SysBot.Pokemon.QQ/MiraiQQBot.cs index c905a07c2..b9000628e 100644 --- a/SysBot.Pokemon.QQ/MiraiQQBot.cs +++ b/SysBot.Pokemon.QQ/MiraiQQBot.cs @@ -2,7 +2,6 @@ using Mirai.Net.Data.Messages.Receivers; using Mirai.Net.Sessions; using Mirai.Net.Sessions.Http.Managers; -using Newtonsoft.Json; using PKHeX.Core; using SysBot.Base; using System; @@ -26,7 +25,9 @@ namespace SysBot.Pokemon.QQ internal static readonly List> QueuePool = new(); private readonly MiraiBot Client; private readonly string GroupId; + private readonly QQSettings Settings; + // concurrent? internal static ConcurrentDictionary TradeCodeDictionary = new(); @@ -53,6 +54,7 @@ public MiraiQQBot(QQSettings settings, PokeTradeHub hub) await HandleCommand(receiver); await HandlePokemonName(receiver); await HandleCancel(receiver); + await HandlePosition(receiver); }); Client.MessageReceived.OfType() @@ -68,36 +70,48 @@ public MiraiQQBot(QQSettings settings, PokeTradeHub hub) .Subscribe(receiver => { Info.ClearTrade(ulong.Parse(receiver.Member.Id)); }); Client.EventReceived.OfType() .Subscribe(receiver => { Info.ClearTrade(ulong.Parse(receiver.Member.Id)); }); - } - public void StartingDistribution() - { Task.Run(async () => { - await Client.LaunchAsync(); - if (!string.IsNullOrWhiteSpace(Settings.MessageStart)) + try { - await MessageManager.SendGroupMessageAsync(GroupId, Settings.MessageStart); - await Task.Delay(1_000).ConfigureAwait(false); - } + await Client.LaunchAsync(); - if (typeof(T) == typeof(PK8)) - { - await MessageManager.SendGroupMessageAsync(GroupId, "当前版本为剑盾"); - } - else if (typeof(T) == typeof(PB8)) - { - await MessageManager.SendGroupMessageAsync(GroupId, "当前版本为晶灿钻石明亮珍珠"); + if (!string.IsNullOrWhiteSpace(Settings.MessageStart)) + { + await MessageManager.SendGroupMessageAsync(GroupId, Settings.MessageStart); + await Task.Delay(1_000).ConfigureAwait(false); + } + + if (typeof(T) == typeof(PK8)) + { + await MessageManager.SendGroupMessageAsync(GroupId, "当前版本为剑盾"); + } + else if (typeof(T) == typeof(PB8)) + { + await MessageManager.SendGroupMessageAsync(GroupId, "当前版本为晶灿钻石明亮珍珠"); + } + else if (typeof(T) == typeof(PA8)) + { + await MessageManager.SendGroupMessageAsync(GroupId, "当前版本为阿尔宙斯"); + } + + await Task.Delay(1_000).ConfigureAwait(false); } - else if (typeof(T) == typeof(PA8)) +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception ex) +#pragma warning restore CA1031 // Do not catch general exception types { - await MessageManager.SendGroupMessageAsync(GroupId, "当前版本为阿尔宙斯"); + LogUtil.LogError(ex.Message, nameof(MiraiQQBot)); } - - await Task.Delay(1_000).ConfigureAwait(false); }); } + void Dispose() + { + Client.Dispose(); + } + private bool IsBotOrNotTargetGroup(GroupMessageReceiver receiver) { return receiver.Sender.Group.Id != GroupId || receiver.Sender.Id == Settings.QQ; @@ -119,7 +133,30 @@ private async Task HandleCancel(GroupMessageReceiver receiver) .StartsWith("取消"); if (!isCancelMsg) return; var result = Info.ClearTrade(ulong.Parse(receiver.Sender.Id)); - await receiver.SendMessageAsync(GetClearTradeMessage(result)); + await receiver.SendMessageAsync( + new AtMessage(receiver.Sender.Id).Append($" {GetClearTradeMessage(result)}")); + } + + private async Task HandlePosition(GroupMessageReceiver receiver) + { + if (receiver.MessageChain.OfType().All(x => x.Target != Settings.QQ)) return; + bool isPositionMsg = (receiver.MessageChain.OfType()?.First()?.Text ?? "").Trim() + .StartsWith("位置"); + if (!isPositionMsg) return; + var result = Info.CheckPosition(ulong.Parse(receiver.Sender.Id)); + await receiver.SendMessageAsync( + new AtMessage(receiver.Sender.Id).Append($" {GetQueueCheckResultMessage(result)}")); + } + + public string GetQueueCheckResultMessage(QueueCheckResult result) + { + if (!result.InQueue || result.Detail is null) + return "你不在队列里"; + var msg = $"你在第{result.Position}位"; + var pk = result.Detail.Trade.TradeData; + if (pk.Species != 0) + msg += $",交换宝可梦:{ShowdownTranslator.GameStrings.Species[result.Detail.Trade.TradeData.Species]}"; + return msg; } private static string GetClearTradeMessage(QueueResultRemove result) @@ -140,7 +177,7 @@ private async Task HandlePokemonName(GroupMessageReceiver receiver) if (string.IsNullOrWhiteSpace(text)) return; string ps = ShowdownTranslator.Chinese2Showdown(text); if (string.IsNullOrWhiteSpace(ps)) return; - LogUtil.LogInfo($"code\n{ps}", "ps"); + LogUtil.LogInfo($"code\n{ps}", "HandlePokemonName"); var _ = MiraiQQCommandsHelper.AddToWaitingList(ps, receiver.Sender.Name, ulong.Parse(receiver.Sender.Id), out string msg); @@ -154,7 +191,7 @@ private async Task HandleFileUpload(GroupMessageReceiver receiver) var fileMessage = receiver.MessageChain.OfType()?.FirstOrDefault(); if (fileMessage == null) return; - LogUtil.LogText("In file module"); + LogUtil.LogInfo("In file module", "HandleFileUpload"); var fileName = fileMessage.Name; string operationType; if (typeof(T) == typeof(PK8) && @@ -198,12 +235,13 @@ private async Task HandleFileUpload(GroupMessageReceiver receiver) default: return; } - LogUtil.LogText($"operationType:{operationType}"); + LogUtil.LogInfo($"operationType:{operationType}", "HandleFileUpload"); await FileManager.DeleteFileAsync(groupId, fileMessage.FileId); } catch (Exception ex) { - LogUtil.LogText(ex.ToString()); + LogUtil.LogSafe(ex, "HandleFileUpload"); + LogUtil.LogError(ex.Message, "HandleFileUpload"); return; } @@ -224,7 +262,7 @@ private async Task HandleCommand(GroupMessageReceiver receiver) return; } - LogUtil.LogText($"debug qqMsg:{qqMsg}"); + LogUtil.LogInfo($"qqMsg:{qqMsg}", "HandleCommand"); var split = qqMsg.Split('\n'); string c = ""; string args = ""; @@ -243,9 +281,10 @@ private async Task HandleCommand(GroupMessageReceiver receiver) { await receiver.RecallAsync(); } - catch (Exception) + catch (Exception ex) { - LogUtil.LogError("recall failed", "mirai"); + LogUtil.LogSafe(ex, "mirai"); + LogUtil.LogError($"{ex.Message}", "mirai"); } var _ = MiraiQQCommandsHelper.AddToWaitingList(args, nickName, ulong.Parse(qq), out string msg); @@ -257,7 +296,10 @@ private async Task HandleCommand(GroupMessageReceiver receiver) private async Task ProcessAddWaitingListResult(bool success, string msg, string qq) { if (success) + { + LogUtil.LogInfo(msg, "trade"); await GetUserFromQueueAndGenerateCodeToTrade(qq); + } else { LogUtil.LogError(msg, "trade"); diff --git a/SysBot.Pokemon.WinForms/PokeBotRunnerImpl.cs b/SysBot.Pokemon.WinForms/PokeBotRunnerImpl.cs index fd770e161..d3cc9f9f2 100644 --- a/SysBot.Pokemon.WinForms/PokeBotRunnerImpl.cs +++ b/SysBot.Pokemon.WinForms/PokeBotRunnerImpl.cs @@ -87,7 +87,6 @@ private void AddQQBot(QQSettings config) if (QQ != null) return; //add qq bot QQ = new MiraiQQBot(config, Hub); - QQ.StartingDistribution(); } } } \ No newline at end of file From 9f99954706f4dd167490effbdf844910e7b6b1c5 Mon Sep 17 00:00:00 2001 From: Easy World Date: Mon, 7 Mar 2022 21:58:26 +0800 Subject: [PATCH 027/171] Update MiraiQQBot.cs --- SysBot.Pokemon.QQ/MiraiQQBot.cs | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/SysBot.Pokemon.QQ/MiraiQQBot.cs b/SysBot.Pokemon.QQ/MiraiQQBot.cs index b9000628e..2742004fa 100644 --- a/SysBot.Pokemon.QQ/MiraiQQBot.cs +++ b/SysBot.Pokemon.QQ/MiraiQQBot.cs @@ -46,15 +46,23 @@ public MiraiQQBot(QQSettings settings, PokeTradeHub hub) Client.MessageReceived.OfType() .Subscribe(async receiver => { - if (IsBotOrNotTargetGroup(receiver)) - return; - - await HandleAliveMessage(receiver); - await HandleFileUpload(receiver); - await HandleCommand(receiver); - await HandlePokemonName(receiver); - await HandleCancel(receiver); - await HandlePosition(receiver); + try + { + if (IsBotOrNotTargetGroup(receiver)) + return; + + await HandleAliveMessage(receiver); + await HandleFileUpload(receiver); + await HandleCommand(receiver); + await HandlePokemonName(receiver); + await HandleCancel(receiver); + await HandlePosition(receiver); + } + catch (Exception ex) + { + LogUtil.LogSafe(ex, "MiraiMain"); + LogUtil.LogError(ex.Message, "MiraiMain"); + } }); Client.MessageReceived.OfType() @@ -107,11 +115,6 @@ public MiraiQQBot(QQSettings settings, PokeTradeHub hub) }); } - void Dispose() - { - Client.Dispose(); - } - private bool IsBotOrNotTargetGroup(GroupMessageReceiver receiver) { return receiver.Sender.Group.Id != GroupId || receiver.Sender.Id == Settings.QQ; @@ -262,6 +265,8 @@ private async Task HandleCommand(GroupMessageReceiver receiver) return; } + if (string.IsNullOrWhiteSpace(qqMsg) || !qqMsg.Trim().StartsWith("$trade")) return; + LogUtil.LogInfo($"qqMsg:{qqMsg}", "HandleCommand"); var split = qqMsg.Split('\n'); string c = ""; From d0a681949b79602d670710e52a3db9ece7140c49 Mon Sep 17 00:00:00 2001 From: Easy World Date: Sat, 12 Mar 2022 18:52:06 +0800 Subject: [PATCH 028/171] Bilibili danmu bot --- SysBot.NET.sln | 20 +- SysBot.Pokemon.Bilibili/BilibiliLiveBot.cs | 175 ++++++++++++++++++ .../BilibiliTradeNotifier.cs | 86 +++++++++ SysBot.Pokemon.Bilibili/ShowdownTranslator.cs | 94 ++++++++++ .../SysBot.Pokemon.Bilibili.csproj | 18 ++ .../PokeBotRunnerImpl.cs | 10 + .../SysBot.Pokemon.ConsoleApp.csproj | 1 + SysBot.Pokemon.WinForms/PokeBotRunnerImpl.cs | 10 + .../SysBot.Pokemon.WinForms.csproj | 1 + .../Settings/Integrations/BilibiliSettings.cs | 21 +++ SysBot.Pokemon/TradeHub/PokeTradeHubConfig.cs | 4 + SysBot.Pokemon/deps/PKHeX.Core.AutoMod.dll | Bin 125952 -> 116224 bytes 12 files changed, 439 insertions(+), 1 deletion(-) create mode 100644 SysBot.Pokemon.Bilibili/BilibiliLiveBot.cs create mode 100644 SysBot.Pokemon.Bilibili/BilibiliTradeNotifier.cs create mode 100644 SysBot.Pokemon.Bilibili/ShowdownTranslator.cs create mode 100644 SysBot.Pokemon.Bilibili/SysBot.Pokemon.Bilibili.csproj create mode 100644 SysBot.Pokemon/Settings/Integrations/BilibiliSettings.cs diff --git a/SysBot.NET.sln b/SysBot.NET.sln index 9977de96c..a01187e81 100644 --- a/SysBot.NET.sln +++ b/SysBot.NET.sln @@ -27,7 +27,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SysBot.Pokemon.Z3", "SysBot EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SysBot.Pokemon.YouTube", "SysBot.Pokemon.YouTube\SysBot.Pokemon.YouTube.csproj", "{958D8B0C-FF01-4BC3-84AF-8BCB80794B49}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SysBot.Pokemon.QQ", "SysBot.Pokemon.QQ\SysBot.Pokemon.QQ.csproj", "{2CD212D9-FF32-4434-9E19-D9CCB99E6817}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SysBot.Pokemon.QQ", "SysBot.Pokemon.QQ\SysBot.Pokemon.QQ.csproj", "{2CD212D9-FF32-4434-9E19-D9CCB99E6817}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SysBot.Pokemon.Bilibili", "SysBot.Pokemon.Bilibili\SysBot.Pokemon.Bilibili.csproj", "{310FFBCD-05DA-4CA3-B785-08B2158ABEBC}" + ProjectSection(ProjectDependencies) = postProject + {2B520A84-74AF-40EC-9B10-A9FCF950D02E} = {2B520A84-74AF-40EC-9B10-A9FCF950D02E} + {850BECFD-3789-4C07-885E-0AA108C46F60} = {850BECFD-3789-4C07-885E-0AA108C46F60} + EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -158,6 +164,18 @@ Global {2CD212D9-FF32-4434-9E19-D9CCB99E6817}.Release|x64.Build.0 = Release|Any CPU {2CD212D9-FF32-4434-9E19-D9CCB99E6817}.Release|x86.ActiveCfg = Release|Any CPU {2CD212D9-FF32-4434-9E19-D9CCB99E6817}.Release|x86.Build.0 = Release|Any CPU + {310FFBCD-05DA-4CA3-B785-08B2158ABEBC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {310FFBCD-05DA-4CA3-B785-08B2158ABEBC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {310FFBCD-05DA-4CA3-B785-08B2158ABEBC}.Debug|x64.ActiveCfg = Debug|Any CPU + {310FFBCD-05DA-4CA3-B785-08B2158ABEBC}.Debug|x64.Build.0 = Debug|Any CPU + {310FFBCD-05DA-4CA3-B785-08B2158ABEBC}.Debug|x86.ActiveCfg = Debug|Any CPU + {310FFBCD-05DA-4CA3-B785-08B2158ABEBC}.Debug|x86.Build.0 = Debug|Any CPU + {310FFBCD-05DA-4CA3-B785-08B2158ABEBC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {310FFBCD-05DA-4CA3-B785-08B2158ABEBC}.Release|Any CPU.Build.0 = Release|Any CPU + {310FFBCD-05DA-4CA3-B785-08B2158ABEBC}.Release|x64.ActiveCfg = Release|Any CPU + {310FFBCD-05DA-4CA3-B785-08B2158ABEBC}.Release|x64.Build.0 = Release|Any CPU + {310FFBCD-05DA-4CA3-B785-08B2158ABEBC}.Release|x86.ActiveCfg = Release|Any CPU + {310FFBCD-05DA-4CA3-B785-08B2158ABEBC}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/SysBot.Pokemon.Bilibili/BilibiliLiveBot.cs b/SysBot.Pokemon.Bilibili/BilibiliLiveBot.cs new file mode 100644 index 000000000..3ba2757f7 --- /dev/null +++ b/SysBot.Pokemon.Bilibili/BilibiliLiveBot.cs @@ -0,0 +1,175 @@ +using System; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using PKHeX.Core; +using SysBot.Base; + +namespace SysBot.Pokemon.Bilibili +{ + public class BilibiliLiveBot where T : PKM, new() + { + // private readonly BilibiliSettings Settings; + private static PokeTradeHub Hub = default!; + internal static TradeQueueInfo Info => Hub.Queues.Info; + + private static readonly string DefaultUserName = "b站用户"; + private static readonly string LogIdentity = "b站直播"; + private static readonly ulong DefaultUserId = 2333; + + public BilibiliLiveBot(BilibiliSettings settings, PokeTradeHub hub) + { + Hub = hub; + File.WriteAllText(@"msg.txt", "等待命令"); + LogUtil.LogInfo($"日志路径:{System.Environment.CurrentDirectory}\\msg.txt", LogIdentity); + Task.Run(() => + { + var currentDanmu = ""; + while (true) + { + Thread.Sleep(1000); + var date = DateTime.Now.ToString("yyyyMMdd"); + string last = ""; + try + { + var path = settings.LogUrl; + if (settings.LogUrl.EndsWith("/") || settings.LogUrl.EndsWith("\\")) + { + path.Remove(path.Length - 1); + } + + last = File.ReadLines(@$"{path}\Log-{settings.RoomId}-{date}.txt") + .Last(); + } + catch (Exception ex) + { + LogUtil.LogSafe(ex, LogIdentity); + } + + if (string.IsNullOrWhiteSpace(last) || currentDanmu == last) continue; + currentDanmu = last; + + + string[] split = last.Split(':'); + if (split.Length != 2 || Info.Count != 0) continue; + var showdown = ShowdownTranslator.Chinese2Showdown(split[1]); + if (showdown.Length <= 0) continue; + LogUtil.LogInfo($"收到命令\n{showdown}", LogIdentity); + var _ = CheckAndGetPkm(showdown, DefaultUserName, out var msg, out var pkm); + if (!_) continue; + var code = Info.GetRandomTradeCode(); + File.WriteAllText(@"msg.txt", + $"派送:{ShowdownTranslator.GameStrings.Species[pkm.Species]}\n密码:{code:0000 0000}"); + var __ = AddToTradeQueue(pkm, code, DefaultUserId, DefaultUserName, RequestSignificance.Favored, + PokeRoutineType.LinkTrade, out string message); + } + }); + } + + + public static bool CheckAndGetPkm(string setstring, string username, out string msg, out T outPkm) + { + outPkm = new T(); + if (!BilibiliLiveBot.Info.GetCanQueue()) + { + msg = "Sorry, I am not currently accepting queue requests!"; + return false; + } + + var set = ShowdownUtil.ConvertToShowdown(setstring); + if (set == null) + { + msg = $"Skipping trade, @{username}: Empty nickname provided for the species."; + return false; + } + + var template = AutoLegalityWrapper.GetTemplate(set); + if (template.Species < 1) + { + msg = + $"Skipping trade, @{username}: Please read what you are supposed to type as the command argument."; + return false; + } + + if (set.InvalidLines.Count != 0) + { + msg = + $"Skipping trade, @{username}: Unable to parse Showdown Set:\n{string.Join("\n", set.InvalidLines)}"; + return false; + } + + try + { + var sav = AutoLegalityWrapper.GetTrainerInfo(); + var pkm = sav.GetLegal(template, out var result); + + if (!pkm.CanBeTraded()) + { + msg = $"Skipping trade, @{username}: Provided Pokemon content is blocked from trading!"; + return false; + } + + if (pkm is T pk) + { + var valid = new LegalityAnalysis(pkm).Valid; + if (valid) + { + outPkm = pk; + + msg = + $"@{username} - added to the waiting list. Your request from the waiting list will be removed if you are too slow!"; + return true; + } + } + + var reason = result == "Timeout" + ? "Set took too long to generate." + : "Unable to legalize the Pokemon."; + msg = $"Skipping trade, @{username}: {reason}"; + } +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception ex) +#pragma warning restore CA1031 // Do not catch general exception types + { + LogUtil.LogSafe(ex, nameof(BilibiliLiveBot)); + msg = $"Skipping trade, @{username}: An unexpected problem occurred."; + } + + return false; + } + + private static bool AddToTradeQueue(T pk, int code, ulong userId, string name, RequestSignificance sig, + PokeRoutineType type, out string msg) + { + var trainer = new PokeTradeTrainerInfo(name, userId); + var notifier = new BilibiliTradeNotifier(pk, trainer, code, name); + var tt = type == PokeRoutineType.SeedCheck ? PokeTradeType.Seed : PokeTradeType.Specific; + var detail = + new PokeTradeDetail(pk, trainer, notifier, tt, code, sig == RequestSignificance.Favored); + var trade = new TradeEntry(detail, userId, type, name); + + var added = Info.AddToTradeQueue(trade, userId, sig == RequestSignificance.Owner); + + if (added == QueueResultAdd.AlreadyInQueue) + { + msg = $"@{name}: Sorry, you are already in the queue."; + return false; + } + + var position = Info.CheckPosition(userId, type); + //msg = $"@{name}: Added to the {type} queue, unique ID: {detail.ID}. Current Position: {position.Position}"; + msg = $" 你在第{position.Position}位"; + + var botct = Info.Hub.Bots.Count; + if (position.Position > botct) + { + var eta = Info.Hub.Config.Queues.EstimateDelay(position.Position, botct); + //msg += $". Estimated: {eta:F1} minutes."; + msg += $", 需等待约{eta:F1}分钟"; + } + + return true; + } + } +} \ No newline at end of file diff --git a/SysBot.Pokemon.Bilibili/BilibiliTradeNotifier.cs b/SysBot.Pokemon.Bilibili/BilibiliTradeNotifier.cs new file mode 100644 index 000000000..9000998c2 --- /dev/null +++ b/SysBot.Pokemon.Bilibili/BilibiliTradeNotifier.cs @@ -0,0 +1,86 @@ +using PKHeX.Core; +using SysBot.Base; +using SysBot.Pokemon; +using System; +using System.Linq; +using System.IO; + +namespace SysBot.Pokemon.Bilibili +{ + public class BilibiliTradeNotifier : IPokeTradeNotifier where T : PKM, new() + { + private T Data { get; } + private PokeTradeTrainerInfo Info { get; } + private int Code { get; } + private string Username { get; } + + public BilibiliTradeNotifier(T data, PokeTradeTrainerInfo info, int code, string username) + { + Data = data; + Info = info; + Code = code; + Username = username; + LogUtil.LogText($"Created trade details for {Username} - {Code}"); + } + + public Action>? OnFinish { private get; set; } + + public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, string message) + { + LogUtil.LogText(message); + } + + public void TradeCanceled(PokeRoutineExecutor routine, PokeTradeDetail info, PokeTradeResult msg) + { + OnFinish?.Invoke(routine); + var line = $"@{info.Trainer.TrainerName}: Trade canceled, {msg}"; + LogUtil.LogText(line); + File.WriteAllText(@"msg.txt", $"等待命令"); + } + + public void TradeFinished(PokeRoutineExecutor routine, PokeTradeDetail info, T result) + { + OnFinish?.Invoke(routine); + var tradedToUser = Data.Species; + var message = $"@{info.Trainer.TrainerName}: " + (tradedToUser != 0 + ? $"Trade finished. Enjoy your {(Species) tradedToUser}!" + : "Trade finished!"); + LogUtil.LogText(message); + File.WriteAllText(@"msg.txt", $"等待命令"); + } + + public void TradeInitialize(PokeRoutineExecutor routine, PokeTradeDetail info) + { + var receive = Data.Species == 0 ? string.Empty : $" ({Data.Nickname})"; + var msg = + $"@{info.Trainer.TrainerName} (ID: {info.ID}): Initializing trade{receive} with you. Please be ready."; + msg += $" Your trade code is: {info.Code:0000 0000}"; + LogUtil.LogText(msg); + File.WriteAllText("msg.txt", $"派送:{ShowdownTranslator.GameStrings.Species[Data.Species]}\n密码:{info.Code:0000 0000}\n状态:初始化"); + } + + public void TradeSearching(PokeRoutineExecutor routine, PokeTradeDetail info) + { + var name = Info.TrainerName; + var trainer = string.IsNullOrEmpty(name) ? string.Empty : $", @{name}"; + var message = $"I'm waiting for you{trainer}! My IGN is {routine.InGameName}."; + message += $" Your trade code is: {info.Code:0000 0000}"; + LogUtil.LogText(message); + File.WriteAllText("msg.txt", $"派送:{ShowdownTranslator.GameStrings.Species[Data.Species]}\n密码:{info.Code:0000 0000}\n状态:搜索中"); + } + + public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, PokeTradeSummary message) + { + var msg = message.Summary; + if (message.Details.Count > 0) + msg += ", " + string.Join(", ", message.Details.Select(z => $"{z.Heading}: {z.Detail}")); + LogUtil.LogText(msg); + } + + public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, T result, string message) + { + var msg = $"Details for {result.FileName}: " + message; + LogUtil.LogText(msg); + } + } +} \ No newline at end of file diff --git a/SysBot.Pokemon.Bilibili/ShowdownTranslator.cs b/SysBot.Pokemon.Bilibili/ShowdownTranslator.cs new file mode 100644 index 000000000..82fb9b359 --- /dev/null +++ b/SysBot.Pokemon.Bilibili/ShowdownTranslator.cs @@ -0,0 +1,94 @@ +using PKHeX.Core; +using System.Text.RegularExpressions; + +namespace SysBot.Pokemon.Bilibili +{ + public class ShowdownTranslator + { + public static GameStrings GameStrings = GameInfo.GetStrings("zh"); + public static GameStrings GameStringsEn = GameInfo.GetStrings("en"); + public static string Chinese2Showdown(string zh) + { + string result = ""; + + int candidateSpecieNo = 0; + int candidateSpecieStringLength = 0; + for (int i = 0; i < GameStrings.Species.Count; i++) + { + if (zh.Contains(GameStrings.Species[i]) && GameStrings.Species[i].Length > candidateSpecieStringLength) + { + candidateSpecieNo = i; + candidateSpecieStringLength = GameStrings.Species[i].Length; + } + } + + if (candidateSpecieNo > 0) + { + result += GameStringsEn.Species[candidateSpecieNo]; + zh = zh.Replace(GameStrings.Species[candidateSpecieNo], ""); + } + else + { + return result; + } + + if (Regex.IsMatch(zh, "[A-Z?!?!]形态")) + { + string formsUnown = Regex.Match(zh, "([A-Z?!?!])形态").Groups?[1]?.Value ?? ""; + result += $"-{formsUnown}"; + zh = Regex.Replace(zh, "[A-Z?!?!]形态", ""); + } + + for (int i = 0; i < GameStrings.forms.Length; i++) + { + if (GameStrings.forms[i].Length == 0) continue; + if (!zh.Contains(GameStrings.forms[i] + "形态")) continue; + result += $"-{GameStringsEn.forms[i]}"; + zh = zh.Replace(GameStrings.forms[i] + "形态", ""); + break; + } + + if (zh.Contains("公")) + { + result += " (M)"; + zh = zh.Replace("公", ""); + } + else if (zh.Contains("母")) + { + result += " (F)"; + zh = zh.Replace("母", ""); + } + + if (Regex.IsMatch(zh, "\\d{1,3}级")) + { + string level = Regex.Match(zh, "(\\d{1,3})级").Groups?[1]?.Value ?? "100"; + result += $"\nLevel: {level}"; + zh = Regex.Replace(zh, "\\d{1,3}级", ""); + } + + if (zh.Contains("异色")) + { + result += "\nShiny: Yes"; + zh = zh.Replace("异色", ""); + } + + if (zh.Contains("头目")) + { + result += "\nAlpha: Yes"; + zh = zh.Replace("头目", ""); + } + + for (int i = 0; i < GameStrings.Natures.Count; i++) + { + if (zh.Contains(GameStrings.Natures[i])) + { + result += $"\n{GameStringsEn.Natures[i]} Nature"; + zh = zh.Replace(GameStrings.Natures[i], ""); + break; + } + } + + return result; + } + } +} \ No newline at end of file diff --git a/SysBot.Pokemon.Bilibili/SysBot.Pokemon.Bilibili.csproj b/SysBot.Pokemon.Bilibili/SysBot.Pokemon.Bilibili.csproj new file mode 100644 index 000000000..608e1e32d --- /dev/null +++ b/SysBot.Pokemon.Bilibili/SysBot.Pokemon.Bilibili.csproj @@ -0,0 +1,18 @@ + + + netstandard2.0 + AnyCPU + enable + 9 + + + + + + + + + + + + \ No newline at end of file diff --git a/SysBot.Pokemon.ConsoleApp/PokeBotRunnerImpl.cs b/SysBot.Pokemon.ConsoleApp/PokeBotRunnerImpl.cs index e1f6c357e..306fd0dcd 100644 --- a/SysBot.Pokemon.ConsoleApp/PokeBotRunnerImpl.cs +++ b/SysBot.Pokemon.ConsoleApp/PokeBotRunnerImpl.cs @@ -3,6 +3,7 @@ using SysBot.Pokemon.Twitch; using System.Threading; using System.Threading.Tasks; +using SysBot.Pokemon.Bilibili; using SysBot.Pokemon.QQ; namespace SysBot.Pokemon.ConsoleApp @@ -17,6 +18,7 @@ public PokeBotRunnerImpl(PokeTradeHubConfig config, BotFactory fac) : base(co private static TwitchBot? Twitch; private MiraiQQBot? QQ; + private BilibiliLiveBot? Bilibili; protected override void AddIntegrations() { @@ -24,6 +26,7 @@ protected override void AddIntegrations() AddTwitchBot(Hub.Config.Twitch); //add qq bot AddQQBot(Hub.Config.QQ); + AddBilibiliBot(Hub.Config.Bilibili); } private void AddTwitchBot(TwitchSettings config) @@ -63,5 +66,12 @@ private void AddQQBot(QQSettings config) //add qq bot QQ = new MiraiQQBot(config, Hub); } + + private void AddBilibiliBot(BilibiliSettings config) + { + if (string.IsNullOrWhiteSpace(config.LogUrl) || config.RoomId == 0) return; + if (Bilibili != null) return; + Bilibili = new BilibiliLiveBot(config, Hub); + } } } diff --git a/SysBot.Pokemon.ConsoleApp/SysBot.Pokemon.ConsoleApp.csproj b/SysBot.Pokemon.ConsoleApp/SysBot.Pokemon.ConsoleApp.csproj index 3a8950414..db09e8ad3 100644 --- a/SysBot.Pokemon.ConsoleApp/SysBot.Pokemon.ConsoleApp.csproj +++ b/SysBot.Pokemon.ConsoleApp/SysBot.Pokemon.ConsoleApp.csproj @@ -11,6 +11,7 @@ + diff --git a/SysBot.Pokemon.WinForms/PokeBotRunnerImpl.cs b/SysBot.Pokemon.WinForms/PokeBotRunnerImpl.cs index d3cc9f9f2..59a55ff87 100644 --- a/SysBot.Pokemon.WinForms/PokeBotRunnerImpl.cs +++ b/SysBot.Pokemon.WinForms/PokeBotRunnerImpl.cs @@ -5,6 +5,7 @@ using SysBot.Pokemon.YouTube; using System.Threading; using System.Threading.Tasks; +using SysBot.Pokemon.Bilibili; using SysBot.Pokemon.QQ; namespace SysBot.Pokemon @@ -25,6 +26,7 @@ public PokeBotRunnerImpl(PokeTradeHubConfig config, BotFactory fac) : base(co private TwitchBot? Twitch; private YouTubeBot? YouTube; private MiraiQQBot? QQ; + private BilibiliLiveBot? Bilibili; protected override void AddIntegrations() { @@ -32,6 +34,7 @@ protected override void AddIntegrations() AddTwitchBot(Hub.Config.Twitch); AddYouTubeBot(Hub.Config.YouTube); AddQQBot(Hub.Config.QQ); + AddBilibiliBot(Hub.Config.Bilibili); } private void AddTwitchBot(TwitchSettings config) @@ -88,5 +91,12 @@ private void AddQQBot(QQSettings config) //add qq bot QQ = new MiraiQQBot(config, Hub); } + + private void AddBilibiliBot(BilibiliSettings config) + { + if (string.IsNullOrWhiteSpace(config.LogUrl) || config.RoomId == 0) return; + if (Bilibili != null) return; + Bilibili = new BilibiliLiveBot(config, Hub); + } } } \ No newline at end of file diff --git a/SysBot.Pokemon.WinForms/SysBot.Pokemon.WinForms.csproj b/SysBot.Pokemon.WinForms/SysBot.Pokemon.WinForms.csproj index d7d1096cb..a4d31175c 100644 --- a/SysBot.Pokemon.WinForms/SysBot.Pokemon.WinForms.csproj +++ b/SysBot.Pokemon.WinForms/SysBot.Pokemon.WinForms.csproj @@ -27,6 +27,7 @@ + diff --git a/SysBot.Pokemon/Settings/Integrations/BilibiliSettings.cs b/SysBot.Pokemon/Settings/Integrations/BilibiliSettings.cs new file mode 100644 index 000000000..a91a0876d --- /dev/null +++ b/SysBot.Pokemon/Settings/Integrations/BilibiliSettings.cs @@ -0,0 +1,21 @@ +using System; +using System.ComponentModel; +using System.Linq; + +namespace SysBot.Pokemon +{ + public class BilibiliSettings + { + private const string Startup = nameof(Startup); + + public override string ToString() => "Bilibili Integration Settings"; + + // Startup + + [Category(Startup), Description("B站彈幕姬日志目录")] + public string LogUrl { get; set; } = string.Empty; + + [Category(Startup), Description("直播间ID")] + public int RoomId { get; set; } = 0; + } +} diff --git a/SysBot.Pokemon/TradeHub/PokeTradeHubConfig.cs b/SysBot.Pokemon/TradeHub/PokeTradeHubConfig.cs index ee009cb17..b07b14c57 100644 --- a/SysBot.Pokemon/TradeHub/PokeTradeHubConfig.cs +++ b/SysBot.Pokemon/TradeHub/PokeTradeHubConfig.cs @@ -89,5 +89,9 @@ public sealed class PokeTradeHubConfig : BaseConfig [Category(Integration)] [TypeConverter(typeof(ExpandableObjectConverter))] public QQSettings QQ { get; set; } = new(); + + [Category(Integration)] + [TypeConverter(typeof(ExpandableObjectConverter))] + public BilibiliSettings Bilibili { get; set; } = new(); } } \ No newline at end of file diff --git a/SysBot.Pokemon/deps/PKHeX.Core.AutoMod.dll b/SysBot.Pokemon/deps/PKHeX.Core.AutoMod.dll index ccef12236254da3c8f595aa58cf323e84b5df03a..666d18ebc100e395ee01441e240b2066aaad766b 100644 GIT binary patch literal 116224 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

W?15e|(TYLI4+lm;jk?=Okb_q)VuL&|?4hs$Q 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 X-%bVF>BOfFe#d+AHy!v6VBr4)SRBhV literal 125952 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 From 971ad70647375d3b0b2fd8d250e1efe5cd5c9c74 Mon Sep 17 00:00:00 2001 From: Easy World Date: Sat, 12 Mar 2022 19:22:30 +0800 Subject: [PATCH 029/171] Create README.md --- SysBot.Pokemon.Bilibili/README.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 SysBot.Pokemon.Bilibili/README.md diff --git a/SysBot.Pokemon.Bilibili/README.md b/SysBot.Pokemon.Bilibili/README.md new file mode 100644 index 000000000..eb9fd754d --- /dev/null +++ b/SysBot.Pokemon.Bilibili/README.md @@ -0,0 +1,7 @@ +# Bilibili直播弹幕配送 +## 使用方法 +1. 下载[B站弹幕姬](https://www.danmuji.org/),并打开 +2. 下载[弹幕日志插件](https://www.danmuji.org/plugins/DanmuLog),下载后dll文件放入**外挂程式资料夹**![image](https://user-images.githubusercontent.com/8280291/158015907-d66c262a-4608-494f-ae3b-c6af537562d7.png) +3. 把上一步放dll文件的目录加上\DanmuLog 填入下面配置中(举个例子:假如电脑用户名叫user1,那么通常的路径是`C:\Users\user1\Documents\弹幕姬\Plugins\DanmuLog`) +4. 将路径和直播间的号码填入配置中![image](https://user-images.githubusercontent.com/8280291/158015848-92b9511f-9ea7-4583-9e5c-97428e3327c9.png) +5. 启动本项目 From ac74e845ec80fe09fee0fdd4446596b6df6be767 Mon Sep 17 00:00:00 2001 From: Easy World Date: Sun, 13 Mar 2022 21:17:20 +0800 Subject: [PATCH 030/171] =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=89=91=E7=9B=BE?= =?UTF-8?q?=E8=87=AAID?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SysBot.Pokemon/SWSH/BotTrade/PokeTradeBot.cs | 36 ++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/SysBot.Pokemon/SWSH/BotTrade/PokeTradeBot.cs b/SysBot.Pokemon/SWSH/BotTrade/PokeTradeBot.cs index 8729a868c..c870da598 100644 --- a/SysBot.Pokemon/SWSH/BotTrade/PokeTradeBot.cs +++ b/SysBot.Pokemon/SWSH/BotTrade/PokeTradeBot.cs @@ -325,6 +325,11 @@ private async Task PerformLinkCodeTrade(SAV8SWSH sav, PokeTrade return PokeTradeResult.RecoverOpenBox; } + if (Hub.Config.QQ.UseTradePartnerInfo) + { + await SetPkmWithSwappedIDDetails(toSend, trainerName, sav, token); + } + // Confirm Box 1 Slot 1 if (poke.Type == PokeTradeType.Specific) { @@ -389,6 +394,37 @@ private async Task PerformLinkCodeTrade(SAV8SWSH sav, PokeTrade return PokeTradeResult.Success; } + private async Task SetPkmWithSwappedIDDetails(PK8 toSend, string trainerName, SAV8SWSH sav, CancellationToken token) + { + var data = await Connection.ReadBytesAsync(LinkTradePartnerNameOffset - 0x8, 8, token).ConfigureAwait(false); + var tidsid = BitConverter.ToInt32(data, 0); + var cln = (PK8)toSend.Clone(); + cln.OT_Gender = data[6]; + cln.TrainerID7 = tidsid % 1_000_000; + cln.TrainerSID7 = tidsid / 1_000_000; + cln.Language = data[5]; + cln.OT_Name = trainerName; + cln.ClearNickname(); + + if (toSend.IsShiny) + cln.SetShiny(); + + cln.RefreshChecksum(); + + var tradeswsh = new LegalityAnalysis(cln); + if (tradeswsh.Valid) + { + Log($"Pokemon is valid, use trade partnerInfo"); + await SetBoxPokemon(cln, InjectBox, InjectSlot, token, sav).ConfigureAwait(false); + } + else + { + Log($"Pokemon not valid, do nothing to trade Pokemon"); + } + + return tradeswsh.Valid; + } + private async Task CheckPartnerReputation(PokeTradeDetail poke, ulong TrainerNID, string TrainerName, CancellationToken token) { bool quit = false; From 9682a7ee692183640ad9ad49f249e69e525b5b0f Mon Sep 17 00:00:00 2001 From: Easy World Date: Thu, 17 Mar 2022 22:40:42 +0800 Subject: [PATCH 031/171] =?UTF-8?q?=E6=94=AF=E6=8C=81dodo=E6=9C=BA?= =?UTF-8?q?=E5=99=A8=E4=BA=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SysBot.NET.sln | 16 +- SysBot.Pokemon.Dodo/DodoBot.cs | 66 ++++++ SysBot.Pokemon.Dodo/DodoHelper.cs | 135 +++++++++++ SysBot.Pokemon.Dodo/DodoTradeNotifier.cs | 89 +++++++ SysBot.Pokemon.Dodo/PokemonProcessService.cs | 220 ++++++++++++++++++ SysBot.Pokemon.Dodo/ShowdownTranslator.cs | 94 ++++++++ .../SysBot.Pokemon.Dodo.csproj | 16 ++ SysBot.Pokemon.WinForms/PokeBotRunnerImpl.cs | 10 + .../SysBot.Pokemon.WinForms.csproj | 1 + .../Settings/Integrations/DodoSettings.cs | 24 ++ SysBot.Pokemon/TradeHub/PokeTradeHubConfig.cs | 4 + 11 files changed, 674 insertions(+), 1 deletion(-) create mode 100644 SysBot.Pokemon.Dodo/DodoBot.cs create mode 100644 SysBot.Pokemon.Dodo/DodoHelper.cs create mode 100644 SysBot.Pokemon.Dodo/DodoTradeNotifier.cs create mode 100644 SysBot.Pokemon.Dodo/PokemonProcessService.cs create mode 100644 SysBot.Pokemon.Dodo/ShowdownTranslator.cs create mode 100644 SysBot.Pokemon.Dodo/SysBot.Pokemon.Dodo.csproj create mode 100644 SysBot.Pokemon/Settings/Integrations/DodoSettings.cs diff --git a/SysBot.NET.sln b/SysBot.NET.sln index a01187e81..1f5b2d8d0 100644 --- a/SysBot.NET.sln +++ b/SysBot.NET.sln @@ -29,12 +29,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SysBot.Pokemon.YouTube", "S EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SysBot.Pokemon.QQ", "SysBot.Pokemon.QQ\SysBot.Pokemon.QQ.csproj", "{2CD212D9-FF32-4434-9E19-D9CCB99E6817}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SysBot.Pokemon.Bilibili", "SysBot.Pokemon.Bilibili\SysBot.Pokemon.Bilibili.csproj", "{310FFBCD-05DA-4CA3-B785-08B2158ABEBC}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SysBot.Pokemon.Bilibili", "SysBot.Pokemon.Bilibili\SysBot.Pokemon.Bilibili.csproj", "{310FFBCD-05DA-4CA3-B785-08B2158ABEBC}" ProjectSection(ProjectDependencies) = postProject {2B520A84-74AF-40EC-9B10-A9FCF950D02E} = {2B520A84-74AF-40EC-9B10-A9FCF950D02E} {850BECFD-3789-4C07-885E-0AA108C46F60} = {850BECFD-3789-4C07-885E-0AA108C46F60} EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SysBot.Pokemon.Dodo", "SysBot.Pokemon.Dodo\SysBot.Pokemon.Dodo.csproj", "{BE43311D-4571-466E-B3CB-3FD658EFC159}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -176,6 +178,18 @@ Global {310FFBCD-05DA-4CA3-B785-08B2158ABEBC}.Release|x64.Build.0 = Release|Any CPU {310FFBCD-05DA-4CA3-B785-08B2158ABEBC}.Release|x86.ActiveCfg = Release|Any CPU {310FFBCD-05DA-4CA3-B785-08B2158ABEBC}.Release|x86.Build.0 = Release|Any CPU + {BE43311D-4571-466E-B3CB-3FD658EFC159}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BE43311D-4571-466E-B3CB-3FD658EFC159}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BE43311D-4571-466E-B3CB-3FD658EFC159}.Debug|x64.ActiveCfg = Debug|Any CPU + {BE43311D-4571-466E-B3CB-3FD658EFC159}.Debug|x64.Build.0 = Debug|Any CPU + {BE43311D-4571-466E-B3CB-3FD658EFC159}.Debug|x86.ActiveCfg = Debug|Any CPU + {BE43311D-4571-466E-B3CB-3FD658EFC159}.Debug|x86.Build.0 = Debug|Any CPU + {BE43311D-4571-466E-B3CB-3FD658EFC159}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BE43311D-4571-466E-B3CB-3FD658EFC159}.Release|Any CPU.Build.0 = Release|Any CPU + {BE43311D-4571-466E-B3CB-3FD658EFC159}.Release|x64.ActiveCfg = Release|Any CPU + {BE43311D-4571-466E-B3CB-3FD658EFC159}.Release|x64.Build.0 = Release|Any CPU + {BE43311D-4571-466E-B3CB-3FD658EFC159}.Release|x86.ActiveCfg = Release|Any CPU + {BE43311D-4571-466E-B3CB-3FD658EFC159}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/SysBot.Pokemon.Dodo/DodoBot.cs b/SysBot.Pokemon.Dodo/DodoBot.cs new file mode 100644 index 000000000..7833524b7 --- /dev/null +++ b/SysBot.Pokemon.Dodo/DodoBot.cs @@ -0,0 +1,66 @@ +using System.Threading.Tasks; +using DoDo.Open.Sdk.Models; +using DoDo.Open.Sdk.Models.Channels; +using DoDo.Open.Sdk.Models.Messages; +using DoDo.Open.Sdk.Services; +using PKHeX.Core; + +namespace SysBot.Pokemon.Dodo +{ + public class DodoBot where T : PKM, new() + { + private static PokeTradeHub Hub = default!; + internal static TradeQueueInfo Info => Hub.Queues.Info; + + public static OpenApiService OpenApiService; + + public DodoBot(DodoSettings settings, PokeTradeHub hub) + { + Hub = hub; + + //开放接口服务 + OpenApiService = new OpenApiService(new OpenApiOptions + { + BaseApi = settings.BaseApi, + ClientId = settings.ClientId, + Token = settings.Token + }); + //事件处理服务,可自定义,只要继承EventProcessService抽象类即可 + var eventProcessService = new PokemonProcessService(OpenApiService); + //开放事件服务 + var openEventService = new OpenEventService(OpenApiService, eventProcessService, new OpenEventOptions + { + IsReconnect = true, + IsAsync = true + }); + //接收事件消息 + Task.Run(async () => { await openEventService.ReceiveAsync(); }); + } + + public static void SendChannelMessage(string message, string channelId) + { + if (string.IsNullOrEmpty(message)) return; + OpenApiService.SetChannelMessageSend(new SetChannelMessageSendInput + { + ChannelId = channelId, + MessageBody = new MessageBodyText + { + Content = message + } + }); + } + + public static void SendChannelAtMessage(ulong atDodoId, string message, string channelId) + { + if (string.IsNullOrEmpty(message)) return; + OpenApiService.SetChannelMessageSend(new SetChannelMessageSendInput + { + ChannelId = channelId, + MessageBody = new MessageBodyText + { + Content = $"<@!{atDodoId}> {message}" + } + }); + } + } +} \ No newline at end of file diff --git a/SysBot.Pokemon.Dodo/DodoHelper.cs b/SysBot.Pokemon.Dodo/DodoHelper.cs new file mode 100644 index 000000000..25f3fde42 --- /dev/null +++ b/SysBot.Pokemon.Dodo/DodoHelper.cs @@ -0,0 +1,135 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using PKHeX.Core; +using SysBot.Base; + +namespace SysBot.Pokemon.Dodo +{ + public class DodoHelper where T : PKM, new() + { + public static void dummy(string ps, string dodoId, string nickName, string channelId) + { + var _ = CheckAndGetPkm(ps, dodoId, out var msg, out var pkm); + if (!_) + { + DodoBot.SendChannelMessage(msg, channelId); + return; + } + + var code = DodoBot.Info.GetRandomTradeCode(); + var text = $"派送:{ShowdownTranslator.GameStrings.Species[pkm.Species]}\n密码:{code:0000 0000}"; + + var __ = AddToTradeQueue(pkm, code, ulong.Parse(dodoId), nickName, channelId, + PokeRoutineType.LinkTrade, out string message); + DodoBot.SendChannelMessage(message, channelId); + } + + + public static bool CheckAndGetPkm(string setstring, string username, out string msg, out T outPkm) + { + outPkm = new T(); + if (!DodoBot.Info.GetCanQueue()) + { + msg = "Sorry, I am not currently accepting queue requests!"; + return false; + } + + var set = ShowdownUtil.ConvertToShowdown(setstring); + if (set == null) + { + msg = $"Skipping trade, @{username}: Empty nickname provided for the species."; + return false; + } + + var template = AutoLegalityWrapper.GetTemplate(set); + if (template.Species < 1) + { + msg = + $"Skipping trade, @{username}: Please read what you are supposed to type as the command argument."; + return false; + } + + if (set.InvalidLines.Count != 0) + { + msg = + $"Skipping trade, @{username}: Unable to parse Showdown Set:\n{string.Join("\n", set.InvalidLines)}"; + return false; + } + + try + { + var sav = AutoLegalityWrapper.GetTrainerInfo(); + var pkm = sav.GetLegal(template, out var result); + + if (!pkm.CanBeTraded()) + { + msg = $"Skipping trade, @{username}: Provided Pokemon content is blocked from trading!"; + return false; + } + + if (pkm is T pk) + { + var valid = new LegalityAnalysis(pkm).Valid; + if (valid) + { + outPkm = pk; + + msg = + $"@{username} - added to the waiting list. Your request from the waiting list will be removed if you are too slow!"; + return true; + } + } + + var reason = result == "Timeout" + ? "Set took too long to generate." + : "Unable to legalize the Pokemon."; + msg = $"Skipping trade, @{username}: {reason}"; + } +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception ex) +#pragma warning restore CA1031 // Do not catch general exception types + { + LogUtil.LogSafe(ex, nameof(DodoBot)); + msg = $"Skipping trade, @{username}: An unexpected problem occurred."; + } + + return false; + } + + private static bool AddToTradeQueue(T pk, int code, ulong userId, string name, string channelId, + PokeRoutineType type, out string msg) + { + var trainer = new PokeTradeTrainerInfo(name, userId); + var notifier = new DodoTradeNotifier(pk, trainer, code, name, channelId); + var tt = type == PokeRoutineType.SeedCheck ? PokeTradeType.Seed : PokeTradeType.Specific; + var detail = + new PokeTradeDetail(pk, trainer, notifier, tt, code, true); + var trade = new TradeEntry(detail, userId, type, name); + + var added = DodoBot.Info.AddToTradeQueue(trade, userId, false); + + if (added == QueueResultAdd.AlreadyInQueue) + { + msg = $"<@!{userId}> 你已经在队列中,请不要重复发送"; + return false; + } + + var position = DodoBot.Info.CheckPosition(userId, type); + //msg = $"@{name}: Added to the {type} queue, unique ID: {detail.ID}. Current Position: {position.Position}"; + msg = $" 你在第{position.Position}位"; + + var botct = DodoBot.Info.Hub.Bots.Count; + if (position.Position > botct) + { + var eta = DodoBot.Info.Hub.Config.Queues.EstimateDelay(position.Position, botct); + //msg += $". Estimated: {eta:F1} minutes."; + msg += $", 需等待约{eta:F1}分钟"; + } + + return true; + } + } +} \ No newline at end of file diff --git a/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs b/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs new file mode 100644 index 000000000..02c0ecd24 --- /dev/null +++ b/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs @@ -0,0 +1,89 @@ +using PKHeX.Core; +using SysBot.Base; +using System; +using System.Linq; + +namespace SysBot.Pokemon.Dodo +{ + public class DodoTradeNotifier : IPokeTradeNotifier where T : PKM, new() + { + private T Data { get; } + private PokeTradeTrainerInfo Info { get; } + private int Code { get; } + private string Username { get; } + + private string ChannelId { get; } + + public DodoTradeNotifier(T data, PokeTradeTrainerInfo info, int code, string username, string channelId) + { + Data = data; + Info = info; + Code = code; + Username = username; + ChannelId = channelId; + LogUtil.LogText($"Created trade details for {Username} - {Code}"); + } + + public Action>? OnFinish { private get; set; } + + public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, string message) + { + LogUtil.LogText(message); + } + + public void TradeCanceled(PokeRoutineExecutor routine, PokeTradeDetail info, PokeTradeResult msg) + { + OnFinish?.Invoke(routine); + var line = $"@{info.Trainer.TrainerName}: Trade canceled, {msg}"; + LogUtil.LogText(line); + DodoBot.SendChannelAtMessage(info.Trainer.ID, "取消", ChannelId); + } + + public void TradeFinished(PokeRoutineExecutor routine, PokeTradeDetail info, T result) + { + OnFinish?.Invoke(routine); + var tradedToUser = Data.Species; + var message = $"@{info.Trainer.TrainerName}: " + (tradedToUser != 0 + ? $"Trade finished. Enjoy your {(Species) tradedToUser}!" + : "Trade finished!"); + LogUtil.LogText(message); + DodoBot.SendChannelAtMessage(info.Trainer.ID, "完成", ChannelId); + } + + public void TradeInitialize(PokeRoutineExecutor routine, PokeTradeDetail info) + { + var receive = Data.Species == 0 ? string.Empty : $" ({Data.Nickname})"; + var msg = + $"@{info.Trainer.TrainerName} (ID: {info.ID}): Initializing trade{receive} with you. Please be ready."; + msg += $" Your trade code is: {info.Code:0000 0000}"; + LogUtil.LogText(msg); + var text = $"\n派送:{ShowdownTranslator.GameStrings.Species[Data.Species]}\n密码:{info.Code:0000 0000}\n状态:初始化"; + DodoBot.SendChannelAtMessage(info.Trainer.ID, text, ChannelId); + } + + public void TradeSearching(PokeRoutineExecutor routine, PokeTradeDetail info) + { + var name = Info.TrainerName; + var trainer = string.IsNullOrEmpty(name) ? string.Empty : $", @{name}"; + var message = $"I'm waiting for you{trainer}! My IGN is {routine.InGameName}."; + message += $" Your trade code is: {info.Code:0000 0000}"; + LogUtil.LogText(message); + var text = $"派送:{ShowdownTranslator.GameStrings.Species[Data.Species]}\n密码:{info.Code:0000 0000}\n状态:搜索中"; + DodoBot.SendChannelMessage(text, ChannelId); + } + + public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, PokeTradeSummary message) + { + var msg = message.Summary; + if (message.Details.Count > 0) + msg += ", " + string.Join(", ", message.Details.Select(z => $"{z.Heading}: {z.Detail}")); + LogUtil.LogText(msg); + } + + public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, T result, string message) + { + var msg = $"Details for {result.FileName}: " + message; + LogUtil.LogText(msg); + } + } +} \ No newline at end of file diff --git a/SysBot.Pokemon.Dodo/PokemonProcessService.cs b/SysBot.Pokemon.Dodo/PokemonProcessService.cs new file mode 100644 index 000000000..263a557d2 --- /dev/null +++ b/SysBot.Pokemon.Dodo/PokemonProcessService.cs @@ -0,0 +1,220 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using DoDo.Open.Sdk.Models.Bots; +using DoDo.Open.Sdk.Models.Channels; +using DoDo.Open.Sdk.Models.Events; +using DoDo.Open.Sdk.Models.Islands; +using DoDo.Open.Sdk.Models.Members; +using DoDo.Open.Sdk.Models.Messages; +using DoDo.Open.Sdk.Models.Personals; +using DoDo.Open.Sdk.Models.Resources; +using DoDo.Open.Sdk.Models.Roles; +using DoDo.Open.Sdk.Models.WebSockets; +using DoDo.Open.Sdk.Services; +using PKHeX.Core; +using SysBot.Base; + +namespace SysBot.Pokemon.Dodo +{ + public class PokemonProcessService : EventProcessService where TP : PKM, new() + { + private readonly OpenApiService _openApiService; + private static string LogIdentity = "DodoBot"; + private static string BotDodoId = "0"; + private static string Welcome = "at我并尝试对我说:\n头目异色怕寂寞性格98级母怪力\n六尾阿罗拉的样子形态\n未知图腾Y形态"; + + public PokemonProcessService(OpenApiService openApiService) + { + _openApiService = openApiService; + var output = _openApiService.GetBotInfo(new GetBotInfoInput()); + if (output != null) + { + BotDodoId = output.DodoId; + } + } + + public override void Connected(string message) + { + Console.WriteLine($"{message}\n"); + } + + public override void Disconnected(string message) + { + Console.WriteLine($"{message}\n"); + } + + public override void Reconnected(string message) + { + Console.WriteLine($"{message}\n"); + } + + public override void Exception(string message) + { + Console.WriteLine($"{message}\n"); + } + + public override void PersonalMessageEvent( + EventSubjectOutput>> input) + { + var eventBody = input.Data.EventBody; + + if (eventBody.MessageBody is MessageBodyText messageBodyText) + { + var messageBody = messageBodyText; + + _openApiService.SetPersonalMessageSend(new SetPersonalMessageSendInput + { + DoDoId = eventBody.DodoId, + MessageBody = new MessageBodyText + { + Content = "触发个人消息事件-文本" + } + }); + + _openApiService.SetPersonalMessageSend(new SetPersonalMessageSendInput + { + DoDoId = eventBody.DodoId, + MessageBody = messageBody + }); + } + else if (eventBody.MessageBody is MessageBodyPicture messageBodyPicture) + { + var messageBody = messageBodyPicture; + + _openApiService.SetPersonalMessageSend(new SetPersonalMessageSendInput + { + DoDoId = eventBody.DodoId, + MessageBody = new MessageBodyText + { + Content = "触发个人消息事件-图片" + } + }); + + _openApiService.SetPersonalMessageSend(new SetPersonalMessageSendInput + { + DoDoId = eventBody.DodoId, + MessageBody = messageBody + }); + } + else if (eventBody.MessageBody is MessageBodyVideo messageBodyVideo) + { + var messageBody = messageBodyVideo; + + _openApiService.SetPersonalMessageSend(new SetPersonalMessageSendInput + { + DoDoId = eventBody.DodoId, + MessageBody = new MessageBodyText + { + Content = "触发个人消息事件-视频" + } + }); + + _openApiService.SetPersonalMessageSend(new SetPersonalMessageSendInput + { + DoDoId = eventBody.DodoId, + MessageBody = messageBody + }); + } + } + + public override void ChannelMessageEvent( + EventSubjectOutput>> input) + { + var eventBody = input.Data.EventBody; + + if (eventBody.MessageBody is MessageBodyText messageBodyText) + { + var messageBody = messageBodyText; + + var content = messageBody.Content; + + Console.WriteLine($"\n【{content}】"); + LogUtil.LogInfo($"{eventBody.Personal.NickName}({eventBody.DodoId}):{content}", LogIdentity); + if (!content.Contains($"<@!{BotDodoId}>")) return; + //DodoBot.SendChannelMessage($"{eventBody.DodoId} {eventBody.Personal.NickName}", eventBody.ChannelId); + var ps = ShowdownTranslator.Chinese2Showdown(content); + if (!string.IsNullOrWhiteSpace(ps)) + { + LogUtil.LogInfo($"收到命令\n{ps}", LogIdentity); + DodoHelper.dummy(ps, eventBody.DodoId, eventBody.Personal.NickName, eventBody.ChannelId); + } + else if (content.Contains("取消")) + { + var result = DodoBot.Info.ClearTrade(ulong.Parse(eventBody.DodoId)); + DodoBot.SendChannelAtMessage(ulong.Parse(eventBody.DodoId), $" {GetClearTradeMessage(result)}", + eventBody.ChannelId); + } + else if (content.Contains("位置")) + { + var result = DodoBot.Info.CheckPosition(ulong.Parse(eventBody.DodoId)); + DodoBot.SendChannelAtMessage(ulong.Parse(eventBody.DodoId), + $" {GetQueueCheckResultMessage(result)}", + eventBody.ChannelId); + } + else + { + DodoBot.SendChannelMessage($"{Welcome}", eventBody.ChannelId); + } + } + } + + public string GetQueueCheckResultMessage(QueueCheckResult result) + { + if (!result.InQueue || result.Detail is null) + return "你不在队列里"; + var msg = $"你在第{result.Position}位"; + var pk = result.Detail.Trade.TradeData; + if (pk.Species != 0) + msg += $",交换宝可梦:{ShowdownTranslator.GameStrings.Species[result.Detail.Trade.TradeData.Species]}"; + return msg; + } + + private static string GetClearTradeMessage(QueueResultRemove result) + { + return result switch + { + QueueResultRemove.CurrentlyProcessing => "你正在交换中", + QueueResultRemove.CurrentlyProcessingRemoved => "正在删除", + QueueResultRemove.Removed => "已删除", + _ => "你不在队列里", + }; + } + + public override void MessageReactionEvent( + EventSubjectOutput> input) + { + var eventBody = input.Data.EventBody; + + _openApiService.SetChannelMessageSend(new SetChannelMessageSendInput + { + ChannelId = eventBody.ChannelId, + MessageBody = new MessageBodyText + { + Content = "触发消息反应事件" + } + }); + + var reply = ""; + + reply += $"反应对象类型:{eventBody.ReactionTarget.Type}\n"; + reply += $"反应对象ID:{eventBody.ReactionTarget.Id}\n"; + reply += $"反应表情类型:{eventBody.ReactionEmoji.Type}\n"; + reply += $"反应表情ID:{eventBody.ReactionEmoji.Id}\n"; + reply += $"反应类型:{eventBody.ReactionType}\n"; + + _openApiService.SetChannelMessageSend(new SetChannelMessageSendInput + { + ChannelId = eventBody.ChannelId, + MessageBody = new MessageBodyText + { + Content = reply + } + }); + } + } +} \ No newline at end of file diff --git a/SysBot.Pokemon.Dodo/ShowdownTranslator.cs b/SysBot.Pokemon.Dodo/ShowdownTranslator.cs new file mode 100644 index 000000000..45db9dca5 --- /dev/null +++ b/SysBot.Pokemon.Dodo/ShowdownTranslator.cs @@ -0,0 +1,94 @@ +using PKHeX.Core; +using System.Text.RegularExpressions; + +namespace SysBot.Pokemon.Dodo +{ + public class ShowdownTranslator + { + public static GameStrings GameStrings = GameInfo.GetStrings("zh"); + public static GameStrings GameStringsEn = GameInfo.GetStrings("en"); + public static string Chinese2Showdown(string zh) + { + string result = ""; + + int candidateSpecieNo = 0; + int candidateSpecieStringLength = 0; + for (int i = 0; i < GameStrings.Species.Count; i++) + { + if (zh.Contains(GameStrings.Species[i]) && GameStrings.Species[i].Length > candidateSpecieStringLength) + { + candidateSpecieNo = i; + candidateSpecieStringLength = GameStrings.Species[i].Length; + } + } + + if (candidateSpecieNo > 0) + { + result += GameStringsEn.Species[candidateSpecieNo]; + zh = zh.Replace(GameStrings.Species[candidateSpecieNo], ""); + } + else + { + return result; + } + + if (Regex.IsMatch(zh, "[A-Z?!?!]形态")) + { + string formsUnown = Regex.Match(zh, "([A-Z?!?!])形态").Groups?[1]?.Value ?? ""; + result += $"-{formsUnown}"; + zh = Regex.Replace(zh, "[A-Z?!?!]形态", ""); + } + + for (int i = 0; i < GameStrings.forms.Length; i++) + { + if (GameStrings.forms[i].Length == 0) continue; + if (!zh.Contains(GameStrings.forms[i])) continue; + result += $"-{GameStringsEn.forms[i]}"; + zh = zh.Replace(GameStrings.forms[i], ""); + break; + } + + if (zh.Contains("公")) + { + result += " (M)"; + zh = zh.Replace("公", ""); + } + else if (zh.Contains("母")) + { + result += " (F)"; + zh = zh.Replace("母", ""); + } + + if (Regex.IsMatch(zh, "\\d{1,3}级")) + { + string level = Regex.Match(zh, "(\\d{1,3})级").Groups?[1]?.Value ?? "100"; + result += $"\nLevel: {level}"; + zh = Regex.Replace(zh, "\\d{1,3}级", ""); + } + + if (zh.Contains("异色")) + { + result += "\nShiny: Yes"; + zh = zh.Replace("异色", ""); + } + + if (zh.Contains("头目")) + { + result += "\nAlpha: Yes"; + zh = zh.Replace("头目", ""); + } + + for (int i = 0; i < GameStrings.Natures.Count; i++) + { + if (zh.Contains(GameStrings.Natures[i])) + { + result += $"\n{GameStringsEn.Natures[i]} Nature"; + zh = zh.Replace(GameStrings.Natures[i], ""); + break; + } + } + + return result; + } + } +} \ No newline at end of file diff --git a/SysBot.Pokemon.Dodo/SysBot.Pokemon.Dodo.csproj b/SysBot.Pokemon.Dodo/SysBot.Pokemon.Dodo.csproj new file mode 100644 index 000000000..8571902e8 --- /dev/null +++ b/SysBot.Pokemon.Dodo/SysBot.Pokemon.Dodo.csproj @@ -0,0 +1,16 @@ + + + + netstandard2.0 + 9 + + + + + + + + + + + \ No newline at end of file diff --git a/SysBot.Pokemon.WinForms/PokeBotRunnerImpl.cs b/SysBot.Pokemon.WinForms/PokeBotRunnerImpl.cs index 59a55ff87..855f0b70a 100644 --- a/SysBot.Pokemon.WinForms/PokeBotRunnerImpl.cs +++ b/SysBot.Pokemon.WinForms/PokeBotRunnerImpl.cs @@ -6,6 +6,7 @@ using System.Threading; using System.Threading.Tasks; using SysBot.Pokemon.Bilibili; +using SysBot.Pokemon.Dodo; using SysBot.Pokemon.QQ; namespace SysBot.Pokemon @@ -27,6 +28,7 @@ public PokeBotRunnerImpl(PokeTradeHubConfig config, BotFactory fac) : base(co private YouTubeBot? YouTube; private MiraiQQBot? QQ; private BilibiliLiveBot? Bilibili; + private DodoBot? Dodo; protected override void AddIntegrations() { @@ -35,6 +37,7 @@ protected override void AddIntegrations() AddYouTubeBot(Hub.Config.YouTube); AddQQBot(Hub.Config.QQ); AddBilibiliBot(Hub.Config.Bilibili); + AddDodoBot(Hub.Config.Dodo); } private void AddTwitchBot(TwitchSettings config) @@ -98,5 +101,12 @@ private void AddBilibiliBot(BilibiliSettings config) if (Bilibili != null) return; Bilibili = new BilibiliLiveBot(config, Hub); } + + private void AddDodoBot(DodoSettings config) + { + if (string.IsNullOrWhiteSpace(config.BaseApi) || string.IsNullOrWhiteSpace(config.ClientId) || string.IsNullOrWhiteSpace(config.Token)) return; + if (Dodo != null) return; + Dodo = new DodoBot(config, Hub); + } } } \ No newline at end of file diff --git a/SysBot.Pokemon.WinForms/SysBot.Pokemon.WinForms.csproj b/SysBot.Pokemon.WinForms/SysBot.Pokemon.WinForms.csproj index a4d31175c..711966ee5 100644 --- a/SysBot.Pokemon.WinForms/SysBot.Pokemon.WinForms.csproj +++ b/SysBot.Pokemon.WinForms/SysBot.Pokemon.WinForms.csproj @@ -29,6 +29,7 @@ + diff --git a/SysBot.Pokemon/Settings/Integrations/DodoSettings.cs b/SysBot.Pokemon/Settings/Integrations/DodoSettings.cs new file mode 100644 index 000000000..f2ccd471f --- /dev/null +++ b/SysBot.Pokemon/Settings/Integrations/DodoSettings.cs @@ -0,0 +1,24 @@ +using System; +using System.ComponentModel; +using System.Linq; + +namespace SysBot.Pokemon +{ + public class DodoSettings + { + private const string Startup = nameof(Startup); + + public override string ToString() => "Dodo Integration Settings"; + + // Startup + + [Category(Startup), Description("接口地址")] + public string BaseApi { get; set; } = "https://botopen.imdodo.com"; + + [Category(Startup), Description("机器人唯一标识")] + public string ClientId { get; set; } = string.Empty; + + [Category(Startup), Description("机器人鉴权Token")] + public string Token { get; set; } = string.Empty; + } +} diff --git a/SysBot.Pokemon/TradeHub/PokeTradeHubConfig.cs b/SysBot.Pokemon/TradeHub/PokeTradeHubConfig.cs index b07b14c57..6ab6d5b1c 100644 --- a/SysBot.Pokemon/TradeHub/PokeTradeHubConfig.cs +++ b/SysBot.Pokemon/TradeHub/PokeTradeHubConfig.cs @@ -93,5 +93,9 @@ public sealed class PokeTradeHubConfig : BaseConfig [Category(Integration)] [TypeConverter(typeof(ExpandableObjectConverter))] public BilibiliSettings Bilibili { get; set; } = new(); + + [Category(Integration)] + [TypeConverter(typeof(ExpandableObjectConverter))] + public DodoSettings Dodo { get; set; } = new(); } } \ No newline at end of file From 8254d2b03d5427381a2784ad377048be0db51aa8 Mon Sep 17 00:00:00 2001 From: Easy World Date: Sat, 19 Mar 2022 13:34:11 +0800 Subject: [PATCH 032/171] =?UTF-8?q?=E6=94=AF=E6=8C=81=E7=A7=81=E4=BF=A1?= =?UTF-8?q?=EF=BC=8C=E6=94=AF=E6=8C=81=E6=8C=87=E5=AE=9A=E5=93=8D=E5=BA=94?= =?UTF-8?q?=E9=A2=91=E9=81=93id?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SysBot.Pokemon.Dodo/DodoBot.cs | 16 +- SysBot.Pokemon.Dodo/DodoHelper.cs | 8 +- SysBot.Pokemon.Dodo/DodoTradeNotifier.cs | 6 +- SysBot.Pokemon.Dodo/PokemonProcessService.cs | 149 +++++------------- .../Settings/Integrations/DodoSettings.cs | 5 +- 5 files changed, 66 insertions(+), 118 deletions(-) diff --git a/SysBot.Pokemon.Dodo/DodoBot.cs b/SysBot.Pokemon.Dodo/DodoBot.cs index 7833524b7..25c27e470 100644 --- a/SysBot.Pokemon.Dodo/DodoBot.cs +++ b/SysBot.Pokemon.Dodo/DodoBot.cs @@ -2,6 +2,7 @@ using DoDo.Open.Sdk.Models; using DoDo.Open.Sdk.Models.Channels; using DoDo.Open.Sdk.Models.Messages; +using DoDo.Open.Sdk.Models.Personals; using DoDo.Open.Sdk.Services; using PKHeX.Core; @@ -26,7 +27,7 @@ public DodoBot(DodoSettings settings, PokeTradeHub hub) Token = settings.Token }); //事件处理服务,可自定义,只要继承EventProcessService抽象类即可 - var eventProcessService = new PokemonProcessService(OpenApiService); + var eventProcessService = new PokemonProcessService(OpenApiService, settings.ChannelId); //开放事件服务 var openEventService = new OpenEventService(OpenApiService, eventProcessService, new OpenEventOptions { @@ -62,5 +63,18 @@ public static void SendChannelAtMessage(ulong atDodoId, string message, string c } }); } + + public static void SendPersonalMessage(string dodoId, string message) + { + if (string.IsNullOrEmpty(message)) return; + OpenApiService.SetPersonalMessageSend(new SetPersonalMessageSendInput + { + DoDoId = dodoId, + MessageBody = new MessageBodyText + { + Content = message + } + }); + } } } \ No newline at end of file diff --git a/SysBot.Pokemon.Dodo/DodoHelper.cs b/SysBot.Pokemon.Dodo/DodoHelper.cs index 25f3fde42..806ad900b 100644 --- a/SysBot.Pokemon.Dodo/DodoHelper.cs +++ b/SysBot.Pokemon.Dodo/DodoHelper.cs @@ -10,7 +10,7 @@ namespace SysBot.Pokemon.Dodo { public class DodoHelper where T : PKM, new() { - public static void dummy(string ps, string dodoId, string nickName, string channelId) + public static void StartTrade(string ps, string dodoId, string nickName, string channelId) { var _ = CheckAndGetPkm(ps, dodoId, out var msg, out var pkm); if (!_) @@ -84,9 +84,9 @@ public static bool CheckAndGetPkm(string setstring, string username, out string } var reason = result == "Timeout" - ? "Set took too long to generate." - : "Unable to legalize the Pokemon."; - msg = $"Skipping trade, @{username}: {reason}"; + ? "宝可梦创造超时" + : "我没办法创造非法宝可梦"; + msg = $": {reason}"; } #pragma warning disable CA1031 // Do not catch general exception types catch (Exception ex) diff --git a/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs b/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs index 02c0ecd24..65b265b8b 100644 --- a/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs +++ b/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs @@ -57,8 +57,9 @@ public void TradeInitialize(PokeRoutineExecutor routine, PokeTradeDetail i $"@{info.Trainer.TrainerName} (ID: {info.ID}): Initializing trade{receive} with you. Please be ready."; msg += $" Your trade code is: {info.Code:0000 0000}"; LogUtil.LogText(msg); - var text = $"\n派送:{ShowdownTranslator.GameStrings.Species[Data.Species]}\n密码:{info.Code:0000 0000}\n状态:初始化"; + var text = $"\n派送:{ShowdownTranslator.GameStrings.Species[Data.Species]}\n密码:见私信\n状态:初始化"; DodoBot.SendChannelAtMessage(info.Trainer.ID, text, ChannelId); + DodoBot.SendPersonalMessage(info.Trainer.ID.ToString(), $"{info.Code:0000 0000}"); } public void TradeSearching(PokeRoutineExecutor routine, PokeTradeDetail info) @@ -68,8 +69,9 @@ public void TradeSearching(PokeRoutineExecutor routine, PokeTradeDetail in var message = $"I'm waiting for you{trainer}! My IGN is {routine.InGameName}."; message += $" Your trade code is: {info.Code:0000 0000}"; LogUtil.LogText(message); - var text = $"派送:{ShowdownTranslator.GameStrings.Species[Data.Species]}\n密码:{info.Code:0000 0000}\n状态:搜索中"; + var text = $"派送:{ShowdownTranslator.GameStrings.Species[Data.Species]}\n密码:见私信\n状态:搜索中"; DodoBot.SendChannelMessage(text, ChannelId); + //DodoBot.SendPersonalMessage(info.Trainer.ID.ToString(), $"{info.Code:0000 0000}"); } public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, PokeTradeSummary message) diff --git a/SysBot.Pokemon.Dodo/PokemonProcessService.cs b/SysBot.Pokemon.Dodo/PokemonProcessService.cs index 263a557d2..4de2395ff 100644 --- a/SysBot.Pokemon.Dodo/PokemonProcessService.cs +++ b/SysBot.Pokemon.Dodo/PokemonProcessService.cs @@ -24,18 +24,21 @@ namespace SysBot.Pokemon.Dodo public class PokemonProcessService : EventProcessService where TP : PKM, new() { private readonly OpenApiService _openApiService; - private static string LogIdentity = "DodoBot"; - private static string BotDodoId = "0"; - private static string Welcome = "at我并尝试对我说:\n头目异色怕寂寞性格98级母怪力\n六尾阿罗拉的样子形态\n未知图腾Y形态"; + private static readonly string LogIdentity = "DodoBot"; + private static readonly string Welcome = "at我并尝试对我说:\n头目异色怕寂寞性格98级母怪力\n六尾阿罗拉的样子形态\n未知图腾Y形态"; + private readonly string _channelId; + private readonly string _botDodoId; - public PokemonProcessService(OpenApiService openApiService) + public PokemonProcessService(OpenApiService openApiService, string channelId) { _openApiService = openApiService; var output = _openApiService.GetBotInfo(new GetBotInfoInput()); if (output != null) { - BotDodoId = output.DodoId; + _botDodoId = output.DodoId; } + + _channelId = channelId; } public override void Connected(string message) @@ -65,60 +68,14 @@ public override void PersonalMessageEvent( if (eventBody.MessageBody is MessageBodyText messageBodyText) { - var messageBody = messageBodyText; - _openApiService.SetPersonalMessageSend(new SetPersonalMessageSendInput { DoDoId = eventBody.DodoId, MessageBody = new MessageBodyText { - Content = "触发个人消息事件-文本" + Content = "你好" } }); - - _openApiService.SetPersonalMessageSend(new SetPersonalMessageSendInput - { - DoDoId = eventBody.DodoId, - MessageBody = messageBody - }); - } - else if (eventBody.MessageBody is MessageBodyPicture messageBodyPicture) - { - var messageBody = messageBodyPicture; - - _openApiService.SetPersonalMessageSend(new SetPersonalMessageSendInput - { - DoDoId = eventBody.DodoId, - MessageBody = new MessageBodyText - { - Content = "触发个人消息事件-图片" - } - }); - - _openApiService.SetPersonalMessageSend(new SetPersonalMessageSendInput - { - DoDoId = eventBody.DodoId, - MessageBody = messageBody - }); - } - else if (eventBody.MessageBody is MessageBodyVideo messageBodyVideo) - { - var messageBody = messageBodyVideo; - - _openApiService.SetPersonalMessageSend(new SetPersonalMessageSendInput - { - DoDoId = eventBody.DodoId, - MessageBody = new MessageBodyText - { - Content = "触发个人消息事件-视频" - } - }); - - _openApiService.SetPersonalMessageSend(new SetPersonalMessageSendInput - { - DoDoId = eventBody.DodoId, - MessageBody = messageBody - }); } } @@ -126,40 +83,38 @@ public override void ChannelMessageEvent( EventSubjectOutput>> input) { var eventBody = input.Data.EventBody; + if (!string.IsNullOrWhiteSpace(_channelId) && eventBody.ChannelId != _channelId) return; - if (eventBody.MessageBody is MessageBodyText messageBodyText) - { - var messageBody = messageBodyText; + if (eventBody.MessageBody is not MessageBodyText messageBodyText) return; - var content = messageBody.Content; + var content = messageBodyText.Content; - Console.WriteLine($"\n【{content}】"); - LogUtil.LogInfo($"{eventBody.Personal.NickName}({eventBody.DodoId}):{content}", LogIdentity); - if (!content.Contains($"<@!{BotDodoId}>")) return; - //DodoBot.SendChannelMessage($"{eventBody.DodoId} {eventBody.Personal.NickName}", eventBody.ChannelId); - var ps = ShowdownTranslator.Chinese2Showdown(content); - if (!string.IsNullOrWhiteSpace(ps)) - { - LogUtil.LogInfo($"收到命令\n{ps}", LogIdentity); - DodoHelper.dummy(ps, eventBody.DodoId, eventBody.Personal.NickName, eventBody.ChannelId); - } - else if (content.Contains("取消")) - { - var result = DodoBot.Info.ClearTrade(ulong.Parse(eventBody.DodoId)); - DodoBot.SendChannelAtMessage(ulong.Parse(eventBody.DodoId), $" {GetClearTradeMessage(result)}", - eventBody.ChannelId); - } - else if (content.Contains("位置")) - { - var result = DodoBot.Info.CheckPosition(ulong.Parse(eventBody.DodoId)); - DodoBot.SendChannelAtMessage(ulong.Parse(eventBody.DodoId), - $" {GetQueueCheckResultMessage(result)}", - eventBody.ChannelId); - } - else - { - DodoBot.SendChannelMessage($"{Welcome}", eventBody.ChannelId); - } + Console.WriteLine($"\n【{content}】"); + LogUtil.LogInfo($"{eventBody.Personal.NickName}({eventBody.DodoId}):{content}", LogIdentity); + if (!content.Contains($"<@!{_botDodoId}>")) return; + //DodoBot.SendChannelMessage($"{eventBody.DodoId} {eventBody.Personal.NickName}", eventBody.ChannelId); + var ps = ShowdownTranslator.Chinese2Showdown(content); + if (!string.IsNullOrWhiteSpace(ps)) + { + LogUtil.LogInfo($"收到命令\n{ps}", LogIdentity); + DodoHelper.StartTrade(ps, eventBody.DodoId, eventBody.Personal.NickName, eventBody.ChannelId); + } + else if (content.Contains("取消")) + { + var result = DodoBot.Info.ClearTrade(ulong.Parse(eventBody.DodoId)); + DodoBot.SendChannelAtMessage(ulong.Parse(eventBody.DodoId), $" {GetClearTradeMessage(result)}", + eventBody.ChannelId); + } + else if (content.Contains("位置")) + { + var result = DodoBot.Info.CheckPosition(ulong.Parse(eventBody.DodoId)); + DodoBot.SendChannelAtMessage(ulong.Parse(eventBody.DodoId), + $" {GetQueueCheckResultMessage(result)}", + eventBody.ChannelId); + } + else + { + DodoBot.SendChannelMessage($"{Welcome}", eventBody.ChannelId); } } @@ -188,33 +143,7 @@ private static string GetClearTradeMessage(QueueResultRemove result) public override void MessageReactionEvent( EventSubjectOutput> input) { - var eventBody = input.Data.EventBody; - - _openApiService.SetChannelMessageSend(new SetChannelMessageSendInput - { - ChannelId = eventBody.ChannelId, - MessageBody = new MessageBodyText - { - Content = "触发消息反应事件" - } - }); - - var reply = ""; - - reply += $"反应对象类型:{eventBody.ReactionTarget.Type}\n"; - reply += $"反应对象ID:{eventBody.ReactionTarget.Id}\n"; - reply += $"反应表情类型:{eventBody.ReactionEmoji.Type}\n"; - reply += $"反应表情ID:{eventBody.ReactionEmoji.Id}\n"; - reply += $"反应类型:{eventBody.ReactionType}\n"; - - _openApiService.SetChannelMessageSend(new SetChannelMessageSendInput - { - ChannelId = eventBody.ChannelId, - MessageBody = new MessageBodyText - { - Content = reply - } - }); + // Do nothing } } } \ No newline at end of file diff --git a/SysBot.Pokemon/Settings/Integrations/DodoSettings.cs b/SysBot.Pokemon/Settings/Integrations/DodoSettings.cs index f2ccd471f..36da802b6 100644 --- a/SysBot.Pokemon/Settings/Integrations/DodoSettings.cs +++ b/SysBot.Pokemon/Settings/Integrations/DodoSettings.cs @@ -20,5 +20,8 @@ public class DodoSettings [Category(Startup), Description("机器人鉴权Token")] public string Token { get; set; } = string.Empty; + + [Category(Startup), Description("机器人响应频道id")] + public string ChannelId { get; set; } = string.Empty; } -} +} \ No newline at end of file From 7de089fbb59d1e99f6bc331370ac683b1e3f3dbe Mon Sep 17 00:00:00 2001 From: Easy World Date: Sat, 19 Mar 2022 14:22:46 +0800 Subject: [PATCH 033/171] remove dependency --- SysBot.Pokemon.Bilibili/SysBot.Pokemon.Bilibili.csproj | 4 ---- SysBot.Pokemon.QQ/SysBot.Pokemon.QQ.csproj | 1 - 2 files changed, 5 deletions(-) diff --git a/SysBot.Pokemon.Bilibili/SysBot.Pokemon.Bilibili.csproj b/SysBot.Pokemon.Bilibili/SysBot.Pokemon.Bilibili.csproj index 608e1e32d..2c5772d4f 100644 --- a/SysBot.Pokemon.Bilibili/SysBot.Pokemon.Bilibili.csproj +++ b/SysBot.Pokemon.Bilibili/SysBot.Pokemon.Bilibili.csproj @@ -6,10 +6,6 @@ 9 - - - - diff --git a/SysBot.Pokemon.QQ/SysBot.Pokemon.QQ.csproj b/SysBot.Pokemon.QQ/SysBot.Pokemon.QQ.csproj index 8ec8dafc9..0dae64136 100644 --- a/SysBot.Pokemon.QQ/SysBot.Pokemon.QQ.csproj +++ b/SysBot.Pokemon.QQ/SysBot.Pokemon.QQ.csproj @@ -9,7 +9,6 @@ - From 6f6ed18870d6868c195b4f6fe7fc0cf0de8a997d Mon Sep 17 00:00:00 2001 From: Easy World Date: Sat, 19 Mar 2022 22:44:53 +0800 Subject: [PATCH 034/171] Minor tweak MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 自id配置放入LegalitySettings * ShowdownTranslator支持球种 * ShowdownTranslator放入SysBot.Pokemon * Dodo文案优化 --- SysBot.Pokemon.Bilibili/BilibiliLiveBot.cs | 4 +- .../BilibiliTradeNotifier.cs | 6 +- SysBot.Pokemon.Bilibili/ShowdownTranslator.cs | 94 ------------------- .../SysBot.Pokemon.Bilibili.csproj | 2 +- SysBot.Pokemon.Dodo/DodoHelper.cs | 18 ++-- SysBot.Pokemon.Dodo/DodoTradeNotifier.cs | 8 +- SysBot.Pokemon.Dodo/PokemonProcessService.cs | 29 +++--- SysBot.Pokemon.QQ/Helpers/MiraiQQSettings.cs | 19 ---- .../Helpers/ShowdownTranslator.cs | 94 ------------------- SysBot.Pokemon.QQ/MiraiQQBot.cs | 5 +- .../Helpers}/ShowdownTranslator.cs | 17 +++- SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs | 7 +- SysBot.Pokemon/SWSH/BotTrade/PokeTradeBot.cs | 2 +- .../Settings/Integrations/QQSettings.cs | 10 +- SysBot.Pokemon/Settings/LegalitySettings.cs | 3 + 15 files changed, 55 insertions(+), 263 deletions(-) delete mode 100644 SysBot.Pokemon.Bilibili/ShowdownTranslator.cs delete mode 100644 SysBot.Pokemon.QQ/Helpers/MiraiQQSettings.cs delete mode 100644 SysBot.Pokemon.QQ/Helpers/ShowdownTranslator.cs rename {SysBot.Pokemon.Dodo => SysBot.Pokemon/Helpers}/ShowdownTranslator.cs (80%) diff --git a/SysBot.Pokemon.Bilibili/BilibiliLiveBot.cs b/SysBot.Pokemon.Bilibili/BilibiliLiveBot.cs index 3ba2757f7..3cd5afde0 100644 --- a/SysBot.Pokemon.Bilibili/BilibiliLiveBot.cs +++ b/SysBot.Pokemon.Bilibili/BilibiliLiveBot.cs @@ -53,14 +53,14 @@ public BilibiliLiveBot(BilibiliSettings settings, PokeTradeHub hub) string[] split = last.Split(':'); if (split.Length != 2 || Info.Count != 0) continue; - var showdown = ShowdownTranslator.Chinese2Showdown(split[1]); + var showdown = ShowdownTranslator.Chinese2Showdown(split[1]); if (showdown.Length <= 0) continue; LogUtil.LogInfo($"收到命令\n{showdown}", LogIdentity); var _ = CheckAndGetPkm(showdown, DefaultUserName, out var msg, out var pkm); if (!_) continue; var code = Info.GetRandomTradeCode(); File.WriteAllText(@"msg.txt", - $"派送:{ShowdownTranslator.GameStrings.Species[pkm.Species]}\n密码:{code:0000 0000}"); + $"派送:{ShowdownTranslator.GameStrings.Species[pkm.Species]}\n密码:{code:0000 0000}"); var __ = AddToTradeQueue(pkm, code, DefaultUserId, DefaultUserName, RequestSignificance.Favored, PokeRoutineType.LinkTrade, out string message); } diff --git a/SysBot.Pokemon.Bilibili/BilibiliTradeNotifier.cs b/SysBot.Pokemon.Bilibili/BilibiliTradeNotifier.cs index 9000998c2..045f39363 100644 --- a/SysBot.Pokemon.Bilibili/BilibiliTradeNotifier.cs +++ b/SysBot.Pokemon.Bilibili/BilibiliTradeNotifier.cs @@ -56,7 +56,8 @@ public void TradeInitialize(PokeRoutineExecutor routine, PokeTradeDetail i $"@{info.Trainer.TrainerName} (ID: {info.ID}): Initializing trade{receive} with you. Please be ready."; msg += $" Your trade code is: {info.Code:0000 0000}"; LogUtil.LogText(msg); - File.WriteAllText("msg.txt", $"派送:{ShowdownTranslator.GameStrings.Species[Data.Species]}\n密码:{info.Code:0000 0000}\n状态:初始化"); + File.WriteAllText("msg.txt", + $"派送:{ShowdownTranslator.GameStrings.Species[Data.Species]}\n密码:{info.Code:0000 0000}\n状态:初始化"); } public void TradeSearching(PokeRoutineExecutor routine, PokeTradeDetail info) @@ -66,7 +67,8 @@ public void TradeSearching(PokeRoutineExecutor routine, PokeTradeDetail in var message = $"I'm waiting for you{trainer}! My IGN is {routine.InGameName}."; message += $" Your trade code is: {info.Code:0000 0000}"; LogUtil.LogText(message); - File.WriteAllText("msg.txt", $"派送:{ShowdownTranslator.GameStrings.Species[Data.Species]}\n密码:{info.Code:0000 0000}\n状态:搜索中"); + File.WriteAllText("msg.txt", + $"派送:{ShowdownTranslator.GameStrings.Species[Data.Species]}\n密码:{info.Code:0000 0000}\n状态:搜索中"); } public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, PokeTradeSummary message) diff --git a/SysBot.Pokemon.Bilibili/ShowdownTranslator.cs b/SysBot.Pokemon.Bilibili/ShowdownTranslator.cs deleted file mode 100644 index 82fb9b359..000000000 --- a/SysBot.Pokemon.Bilibili/ShowdownTranslator.cs +++ /dev/null @@ -1,94 +0,0 @@ -using PKHeX.Core; -using System.Text.RegularExpressions; - -namespace SysBot.Pokemon.Bilibili -{ - public class ShowdownTranslator - { - public static GameStrings GameStrings = GameInfo.GetStrings("zh"); - public static GameStrings GameStringsEn = GameInfo.GetStrings("en"); - public static string Chinese2Showdown(string zh) - { - string result = ""; - - int candidateSpecieNo = 0; - int candidateSpecieStringLength = 0; - for (int i = 0; i < GameStrings.Species.Count; i++) - { - if (zh.Contains(GameStrings.Species[i]) && GameStrings.Species[i].Length > candidateSpecieStringLength) - { - candidateSpecieNo = i; - candidateSpecieStringLength = GameStrings.Species[i].Length; - } - } - - if (candidateSpecieNo > 0) - { - result += GameStringsEn.Species[candidateSpecieNo]; - zh = zh.Replace(GameStrings.Species[candidateSpecieNo], ""); - } - else - { - return result; - } - - if (Regex.IsMatch(zh, "[A-Z?!?!]形态")) - { - string formsUnown = Regex.Match(zh, "([A-Z?!?!])形态").Groups?[1]?.Value ?? ""; - result += $"-{formsUnown}"; - zh = Regex.Replace(zh, "[A-Z?!?!]形态", ""); - } - - for (int i = 0; i < GameStrings.forms.Length; i++) - { - if (GameStrings.forms[i].Length == 0) continue; - if (!zh.Contains(GameStrings.forms[i] + "形态")) continue; - result += $"-{GameStringsEn.forms[i]}"; - zh = zh.Replace(GameStrings.forms[i] + "形态", ""); - break; - } - - if (zh.Contains("公")) - { - result += " (M)"; - zh = zh.Replace("公", ""); - } - else if (zh.Contains("母")) - { - result += " (F)"; - zh = zh.Replace("母", ""); - } - - if (Regex.IsMatch(zh, "\\d{1,3}级")) - { - string level = Regex.Match(zh, "(\\d{1,3})级").Groups?[1]?.Value ?? "100"; - result += $"\nLevel: {level}"; - zh = Regex.Replace(zh, "\\d{1,3}级", ""); - } - - if (zh.Contains("异色")) - { - result += "\nShiny: Yes"; - zh = zh.Replace("异色", ""); - } - - if (zh.Contains("头目")) - { - result += "\nAlpha: Yes"; - zh = zh.Replace("头目", ""); - } - - for (int i = 0; i < GameStrings.Natures.Count; i++) - { - if (zh.Contains(GameStrings.Natures[i])) - { - result += $"\n{GameStringsEn.Natures[i]} Nature"; - zh = zh.Replace(GameStrings.Natures[i], ""); - break; - } - } - - return result; - } - } -} \ No newline at end of file diff --git a/SysBot.Pokemon.Bilibili/SysBot.Pokemon.Bilibili.csproj b/SysBot.Pokemon.Bilibili/SysBot.Pokemon.Bilibili.csproj index 2c5772d4f..402730e08 100644 --- a/SysBot.Pokemon.Bilibili/SysBot.Pokemon.Bilibili.csproj +++ b/SysBot.Pokemon.Bilibili/SysBot.Pokemon.Bilibili.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 AnyCPU diff --git a/SysBot.Pokemon.Dodo/DodoHelper.cs b/SysBot.Pokemon.Dodo/DodoHelper.cs index 806ad900b..030423ecd 100644 --- a/SysBot.Pokemon.Dodo/DodoHelper.cs +++ b/SysBot.Pokemon.Dodo/DodoHelper.cs @@ -20,7 +20,7 @@ public static void StartTrade(string ps, string dodoId, string nickName, string } var code = DodoBot.Info.GetRandomTradeCode(); - var text = $"派送:{ShowdownTranslator.GameStrings.Species[pkm.Species]}\n密码:{code:0000 0000}"; + var text = $"派送:{ShowdownTranslator.GameStrings.Species[pkm.Species]}\n密码:{code:0000 0000}"; var __ = AddToTradeQueue(pkm, code, ulong.Parse(dodoId), nickName, channelId, PokeRoutineType.LinkTrade, out string message); @@ -33,14 +33,14 @@ public static bool CheckAndGetPkm(string setstring, string username, out string outPkm = new T(); if (!DodoBot.Info.GetCanQueue()) { - msg = "Sorry, I am not currently accepting queue requests!"; + msg = "对不起, 我不再接受队列请求!"; return false; } var set = ShowdownUtil.ConvertToShowdown(setstring); if (set == null) { - msg = $"Skipping trade, @{username}: Empty nickname provided for the species."; + msg = $"取消派送, <@!{username}>: 宝可梦昵称为空."; return false; } @@ -48,14 +48,14 @@ public static bool CheckAndGetPkm(string setstring, string username, out string if (template.Species < 1) { msg = - $"Skipping trade, @{username}: Please read what you are supposed to type as the command argument."; + $"取消派送, <@!{username}>: 请使用正确的Showdown Set代码"; return false; } if (set.InvalidLines.Count != 0) { msg = - $"Skipping trade, @{username}: Unable to parse Showdown Set:\n{string.Join("\n", set.InvalidLines)}"; + $"取消派送, <@!{username}>: 非法的Showdown Set代码:\n{string.Join("\n", set.InvalidLines)}"; return false; } @@ -66,7 +66,7 @@ public static bool CheckAndGetPkm(string setstring, string username, out string if (!pkm.CanBeTraded()) { - msg = $"Skipping trade, @{username}: Provided Pokemon content is blocked from trading!"; + msg = $"取消派送, <@!{username}>: 官方禁止该宝可梦交易!"; return false; } @@ -78,7 +78,7 @@ public static bool CheckAndGetPkm(string setstring, string username, out string outPkm = pk; msg = - $"@{username} - added to the waiting list. Your request from the waiting list will be removed if you are too slow!"; + $"<@!{username}> - 已加入等待队列. 如果你选宝可梦的速度太慢,你的派送请求将被取消!"; return true; } } @@ -86,14 +86,14 @@ public static bool CheckAndGetPkm(string setstring, string username, out string var reason = result == "Timeout" ? "宝可梦创造超时" : "我没办法创造非法宝可梦"; - msg = $": {reason}"; + msg = $"<@!{username}>: {reason}"; } #pragma warning disable CA1031 // Do not catch general exception types catch (Exception ex) #pragma warning restore CA1031 // Do not catch general exception types { LogUtil.LogSafe(ex, nameof(DodoBot)); - msg = $"Skipping trade, @{username}: An unexpected problem occurred."; + msg = $"取消派送, <@!{username}>: 发生了一个错误"; } return false; diff --git a/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs b/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs index 65b265b8b..0a81a7468 100644 --- a/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs +++ b/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs @@ -24,7 +24,7 @@ public DodoTradeNotifier(T data, PokeTradeTrainerInfo info, int code, string use LogUtil.LogText($"Created trade details for {Username} - {Code}"); } - public Action>? OnFinish { private get; set; } + public Action> OnFinish { private get; set; } public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, string message) { @@ -57,9 +57,9 @@ public void TradeInitialize(PokeRoutineExecutor routine, PokeTradeDetail i $"@{info.Trainer.TrainerName} (ID: {info.ID}): Initializing trade{receive} with you. Please be ready."; msg += $" Your trade code is: {info.Code:0000 0000}"; LogUtil.LogText(msg); - var text = $"\n派送:{ShowdownTranslator.GameStrings.Species[Data.Species]}\n密码:见私信\n状态:初始化"; + var text = $"\n派送:{ShowdownTranslator.GameStrings.Species[Data.Species]}\n密码:见私信\n状态:初始化"; DodoBot.SendChannelAtMessage(info.Trainer.ID, text, ChannelId); - DodoBot.SendPersonalMessage(info.Trainer.ID.ToString(), $"{info.Code:0000 0000}"); + DodoBot.SendPersonalMessage(info.Trainer.ID.ToString(), $"派送:{ShowdownTranslator.GameStrings.Species[Data.Species]}\n密码:{info.Code:0000 0000}"); } public void TradeSearching(PokeRoutineExecutor routine, PokeTradeDetail info) @@ -69,7 +69,7 @@ public void TradeSearching(PokeRoutineExecutor routine, PokeTradeDetail in var message = $"I'm waiting for you{trainer}! My IGN is {routine.InGameName}."; message += $" Your trade code is: {info.Code:0000 0000}"; LogUtil.LogText(message); - var text = $"派送:{ShowdownTranslator.GameStrings.Species[Data.Species]}\n密码:见私信\n状态:搜索中"; + var text = $"派送:{ShowdownTranslator.GameStrings.Species[Data.Species]}\n密码:见私信\n状态:搜索中"; DodoBot.SendChannelMessage(text, ChannelId); //DodoBot.SendPersonalMessage(info.Trainer.ID.ToString(), $"{info.Code:0000 0000}"); } diff --git a/SysBot.Pokemon.Dodo/PokemonProcessService.cs b/SysBot.Pokemon.Dodo/PokemonProcessService.cs index 4de2395ff..fc37f472c 100644 --- a/SysBot.Pokemon.Dodo/PokemonProcessService.cs +++ b/SysBot.Pokemon.Dodo/PokemonProcessService.cs @@ -1,20 +1,8 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; using DoDo.Open.Sdk.Models.Bots; -using DoDo.Open.Sdk.Models.Channels; using DoDo.Open.Sdk.Models.Events; -using DoDo.Open.Sdk.Models.Islands; -using DoDo.Open.Sdk.Models.Members; using DoDo.Open.Sdk.Models.Messages; using DoDo.Open.Sdk.Models.Personals; -using DoDo.Open.Sdk.Models.Resources; -using DoDo.Open.Sdk.Models.Roles; -using DoDo.Open.Sdk.Models.WebSockets; using DoDo.Open.Sdk.Services; using PKHeX.Core; using SysBot.Base; @@ -25,7 +13,7 @@ namespace SysBot.Pokemon.Dodo { private readonly OpenApiService _openApiService; private static readonly string LogIdentity = "DodoBot"; - private static readonly string Welcome = "at我并尝试对我说:\n头目异色怕寂寞性格98级母怪力\n六尾阿罗拉的样子形态\n未知图腾Y形态"; + private static readonly string Welcome = "at我并尝试对我说:\n头目异色怕寂寞性格98级母怪力\n飞羽球六尾阿罗拉的样子形态\n未知图腾Y形态"; private readonly string _channelId; private readonly string _botDodoId; @@ -89,11 +77,18 @@ public override void ChannelMessageEvent( var content = messageBodyText.Content; - Console.WriteLine($"\n【{content}】"); LogUtil.LogInfo($"{eventBody.Personal.NickName}({eventBody.DodoId}):{content}", LogIdentity); if (!content.Contains($"<@!{_botDodoId}>")) return; - //DodoBot.SendChannelMessage($"{eventBody.DodoId} {eventBody.Personal.NickName}", eventBody.ChannelId); - var ps = ShowdownTranslator.Chinese2Showdown(content); + + content = content.Substring(content.IndexOf('>') + 1); + if (content.Trim().StartsWith("trade")) + { + content = content.Replace("trade", ""); + DodoHelper.StartTrade(content, eventBody.DodoId, eventBody.Personal.NickName, eventBody.ChannelId); + return; + } + + var ps = ShowdownTranslator.Chinese2Showdown(content); if (!string.IsNullOrWhiteSpace(ps)) { LogUtil.LogInfo($"收到命令\n{ps}", LogIdentity); @@ -125,7 +120,7 @@ public string GetQueueCheckResultMessage(QueueCheckResult result) var msg = $"你在第{result.Position}位"; var pk = result.Detail.Trade.TradeData; if (pk.Species != 0) - msg += $",交换宝可梦:{ShowdownTranslator.GameStrings.Species[result.Detail.Trade.TradeData.Species]}"; + msg += $",交换宝可梦:{ShowdownTranslator.GameStrings.Species[result.Detail.Trade.TradeData.Species]}"; return msg; } diff --git a/SysBot.Pokemon.QQ/Helpers/MiraiQQSettings.cs b/SysBot.Pokemon.QQ/Helpers/MiraiQQSettings.cs deleted file mode 100644 index 930166c61..000000000 --- a/SysBot.Pokemon.QQ/Helpers/MiraiQQSettings.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace SysBot.Pokemon.QQ -{ - public class MiraiQQSettings - { - public string Address { get; set; } = string.Empty; - - public string QQ { get; set; } = string.Empty; - - public string VerifyKey { get; set; } = string.Empty; - - public string GroupId { get; set; } = string.Empty; - - public string AliveMsg { get; set; } = "hello"; - } -} diff --git a/SysBot.Pokemon.QQ/Helpers/ShowdownTranslator.cs b/SysBot.Pokemon.QQ/Helpers/ShowdownTranslator.cs deleted file mode 100644 index b447491a8..000000000 --- a/SysBot.Pokemon.QQ/Helpers/ShowdownTranslator.cs +++ /dev/null @@ -1,94 +0,0 @@ -using PKHeX.Core; -using System.Text.RegularExpressions; - -namespace SysBot.Pokemon.QQ -{ - public class ShowdownTranslator - { - public static GameStrings GameStrings = GameInfo.GetStrings("zh"); - public static GameStrings GameStringsEn = GameInfo.GetStrings("en"); - public static string Chinese2Showdown(string zh) - { - string result = ""; - - int candidateSpecieNo = 0; - int candidateSpecieStringLength = 0; - for (int i = 0; i < GameStrings.Species.Count; i++) - { - if (zh.Contains(GameStrings.Species[i]) && GameStrings.Species[i].Length > candidateSpecieStringLength) - { - candidateSpecieNo = i; - candidateSpecieStringLength = GameStrings.Species[i].Length; - } - } - - if (candidateSpecieNo > 0) - { - result += GameStringsEn.Species[candidateSpecieNo]; - zh = zh.Replace(GameStrings.Species[candidateSpecieNo], ""); - } - else - { - return result; - } - - if (Regex.IsMatch(zh, "[A-Z?!?!]形态")) - { - string formsUnown = Regex.Match(zh, "([A-Z?!?!])形态").Groups?[1]?.Value ?? ""; - result += $"-{formsUnown}"; - zh = Regex.Replace(zh, "[A-Z?!?!]形态", ""); - } - - for (int i = 0; i < GameStrings.forms.Length; i++) - { - if (GameStrings.forms[i].Length == 0) continue; - if (!zh.Contains(GameStrings.forms[i] + "形态")) continue; - result += $"-{GameStringsEn.forms[i]}"; - zh = zh.Replace(GameStrings.forms[i] + "形态", ""); - break; - } - - if (zh.Contains("公")) - { - result += " (M)"; - zh = zh.Replace("公", ""); - } - else if (zh.Contains("母")) - { - result += " (F)"; - zh = zh.Replace("母", ""); - } - - if (Regex.IsMatch(zh, "\\d{1,3}级")) - { - string level = Regex.Match(zh, "(\\d{1,3})级").Groups?[1]?.Value ?? "100"; - result += $"\nLevel: {level}"; - zh = Regex.Replace(zh, "\\d{1,3}级", ""); - } - - if (zh.Contains("异色")) - { - result += "\nShiny: Yes"; - zh = zh.Replace("异色", ""); - } - - if (zh.Contains("头目")) - { - result += "\nAlpha: Yes"; - zh = zh.Replace("头目", ""); - } - - for (int i = 0; i < GameStrings.Natures.Count; i++) - { - if (zh.Contains(GameStrings.Natures[i])) - { - result += $"\n{GameStringsEn.Natures[i]} Nature"; - zh = zh.Replace(GameStrings.Natures[i], ""); - break; - } - } - - return result; - } - } -} \ No newline at end of file diff --git a/SysBot.Pokemon.QQ/MiraiQQBot.cs b/SysBot.Pokemon.QQ/MiraiQQBot.cs index 2742004fa..bafeada69 100644 --- a/SysBot.Pokemon.QQ/MiraiQQBot.cs +++ b/SysBot.Pokemon.QQ/MiraiQQBot.cs @@ -7,7 +7,6 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Reactive.Linq; using System.Threading.Tasks; @@ -158,7 +157,7 @@ public string GetQueueCheckResultMessage(QueueCheckResult result) var msg = $"你在第{result.Position}位"; var pk = result.Detail.Trade.TradeData; if (pk.Species != 0) - msg += $",交换宝可梦:{ShowdownTranslator.GameStrings.Species[result.Detail.Trade.TradeData.Species]}"; + msg += $",交换宝可梦:{ShowdownTranslator.GameStrings.Species[result.Detail.Trade.TradeData.Species]}"; return msg; } @@ -178,7 +177,7 @@ private async Task HandlePokemonName(GroupMessageReceiver receiver) if (receiver.MessageChain.OfType().All(x => x.Target != Settings.QQ)) return; var text = receiver.MessageChain.OfType()?.First()?.Text ?? ""; if (string.IsNullOrWhiteSpace(text)) return; - string ps = ShowdownTranslator.Chinese2Showdown(text); + string ps = ShowdownTranslator.Chinese2Showdown(text); if (string.IsNullOrWhiteSpace(ps)) return; LogUtil.LogInfo($"code\n{ps}", "HandlePokemonName"); var _ = MiraiQQCommandsHelper.AddToWaitingList(ps, receiver.Sender.Name, diff --git a/SysBot.Pokemon.Dodo/ShowdownTranslator.cs b/SysBot.Pokemon/Helpers/ShowdownTranslator.cs similarity index 80% rename from SysBot.Pokemon.Dodo/ShowdownTranslator.cs rename to SysBot.Pokemon/Helpers/ShowdownTranslator.cs index 45db9dca5..bb126fc08 100644 --- a/SysBot.Pokemon.Dodo/ShowdownTranslator.cs +++ b/SysBot.Pokemon/Helpers/ShowdownTranslator.cs @@ -1,9 +1,9 @@ using PKHeX.Core; using System.Text.RegularExpressions; -namespace SysBot.Pokemon.Dodo +namespace SysBot.Pokemon { - public class ShowdownTranslator + public class ShowdownTranslator where T : PKM { public static GameStrings GameStrings = GameInfo.GetStrings("zh"); public static GameStrings GameStringsEn = GameInfo.GetStrings("en"); @@ -35,6 +35,8 @@ public static string Chinese2Showdown(string zh) if (Regex.IsMatch(zh, "[A-Z?!?!]形态")) { string formsUnown = Regex.Match(zh, "([A-Z?!?!])形态").Groups?[1]?.Value ?? ""; + if (formsUnown == "?") formsUnown = "?"; + else if (formsUnown == "!") formsUnown = "!"; result += $"-{formsUnown}"; zh = Regex.Replace(zh, "[A-Z?!?!]形态", ""); } @@ -88,6 +90,17 @@ public static string Chinese2Showdown(string zh) } } + for (int i = 0; i < GameStrings.balllist.Length; i++) + { + if (GameStrings.balllist[i].Length == 0) continue; + if (!zh.Contains(GameStrings.balllist[i])) continue; + var ballStr = GameStringsEn.balllist[i]; + if (typeof(T) == typeof(PA8) && ballStr is "Poké Ball" or "Great Ball" or "Ultra Ball") ballStr = "LA" + ballStr; + result += $"\nBall: {ballStr}"; + zh = zh.Replace(GameStrings.balllist[i], ""); + break; + } + return result; } } diff --git a/SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs b/SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs index 8c5b57161..c7fa6477d 100644 --- a/SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs +++ b/SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs @@ -325,12 +325,7 @@ private async Task PerformLinkCodeTrade(SAV8LA sav, PokeTradeDe return update; } - if (Hub.Config.QQ.RandomEC) - { - toSend.SetRandomEC(); - } - - if (Hub.Config.QQ.UseTradePartnerInfo) + if (Hub.Config.Legality.UseTradePartnerInfo) { await SetBoxPkmWithSwappedIDDetailsPLA(toSend, tradePartner, sav, token); } diff --git a/SysBot.Pokemon/SWSH/BotTrade/PokeTradeBot.cs b/SysBot.Pokemon/SWSH/BotTrade/PokeTradeBot.cs index c870da598..d316a2a8d 100644 --- a/SysBot.Pokemon/SWSH/BotTrade/PokeTradeBot.cs +++ b/SysBot.Pokemon/SWSH/BotTrade/PokeTradeBot.cs @@ -325,7 +325,7 @@ private async Task PerformLinkCodeTrade(SAV8SWSH sav, PokeTrade return PokeTradeResult.RecoverOpenBox; } - if (Hub.Config.QQ.UseTradePartnerInfo) + if (Hub.Config.Legality.UseTradePartnerInfo) { await SetPkmWithSwappedIDDetails(toSend, trainerName, sav, token); } diff --git a/SysBot.Pokemon/Settings/Integrations/QQSettings.cs b/SysBot.Pokemon/Settings/Integrations/QQSettings.cs index 0e1bfacfe..f4ad08f41 100644 --- a/SysBot.Pokemon/Settings/Integrations/QQSettings.cs +++ b/SysBot.Pokemon/Settings/Integrations/QQSettings.cs @@ -30,13 +30,5 @@ public class QQSettings [Category(Operation), Description("Message sent when the Barrier is released.")] public string MessageStart { get; set; } = string.Empty; - - // Operation - - [Category(Operation), Description("Use TradePartnerInfo")] - public bool UseTradePartnerInfo { get; set; } = false; - - [Category(Operation), Description("Random EC")] - public bool RandomEC { get; set; } = false; } -} +} \ No newline at end of file diff --git a/SysBot.Pokemon/Settings/LegalitySettings.cs b/SysBot.Pokemon/Settings/LegalitySettings.cs index 4c7de18ff..785e0450c 100644 --- a/SysBot.Pokemon/Settings/LegalitySettings.cs +++ b/SysBot.Pokemon/Settings/LegalitySettings.cs @@ -65,5 +65,8 @@ public string GenerateOT [Category(Misc), Description("Zero out HOME tracker regardless of current tracker value. Applies to user requested PKM files as well.")] public bool ResetHOMETracker { get; set; } = true; + + [Category(Misc), Description("Override trainer data by trade partner")] + public bool UseTradePartnerInfo { get; set; } = false; } } From a66dccb441ff7f8acc1e90fec7be246beb2a60aa Mon Sep 17 00:00:00 2001 From: Easy World Date: Tue, 29 Mar 2022 20:20:22 +0800 Subject: [PATCH 035/171] Dodo support file upload --- SysBot.Pokemon.Dodo/DodoBot.cs | 30 +++++++++++- SysBot.Pokemon.Dodo/DodoHelper.cs | 7 ++- SysBot.Pokemon.Dodo/PokemonProcessService.cs | 47 +++++++++++++++++++ .../SysBot.Pokemon.Dodo.csproj | 2 +- 4 files changed, 81 insertions(+), 5 deletions(-) diff --git a/SysBot.Pokemon.Dodo/DodoBot.cs b/SysBot.Pokemon.Dodo/DodoBot.cs index 25c27e470..7db8b00fa 100644 --- a/SysBot.Pokemon.Dodo/DodoBot.cs +++ b/SysBot.Pokemon.Dodo/DodoBot.cs @@ -15,10 +15,12 @@ namespace SysBot.Pokemon.Dodo public static OpenApiService OpenApiService; + private DodoSettings Settings; + public DodoBot(DodoSettings settings, PokeTradeHub hub) { Hub = hub; - + Settings = settings; //开放接口服务 OpenApiService = new OpenApiService(new OpenApiOptions { @@ -35,7 +37,31 @@ public DodoBot(DodoSettings settings, PokeTradeHub hub) IsAsync = true }); //接收事件消息 - Task.Run(async () => { await openEventService.ReceiveAsync(); }); + Task.Run(async () => + { + StartDistribution(); + await openEventService.ReceiveAsync(); + }); + } + + public void StartDistribution() + { + var channelId = Settings.ChannelId; + if (string.IsNullOrWhiteSpace(channelId)) return; + SendChannelMessage("开始派送", channelId); + Task.Delay(1_000).ConfigureAwait(false); + if (typeof(T) == typeof(PK8)) + { + SendChannelMessage("当前版本为剑盾", channelId); + } + else if (typeof(T) == typeof(PB8)) + { + SendChannelMessage("当前版本为晶灿钻石明亮珍珠", channelId); + } + else if (typeof(T) == typeof(PA8)) + { + SendChannelMessage("当前版本为阿尔宙斯", channelId); + } } public static void SendChannelMessage(string message, string channelId) diff --git a/SysBot.Pokemon.Dodo/DodoHelper.cs b/SysBot.Pokemon.Dodo/DodoHelper.cs index 030423ecd..f12e017bd 100644 --- a/SysBot.Pokemon.Dodo/DodoHelper.cs +++ b/SysBot.Pokemon.Dodo/DodoHelper.cs @@ -19,9 +19,12 @@ public static void StartTrade(string ps, string dodoId, string nickName, string return; } - var code = DodoBot.Info.GetRandomTradeCode(); - var text = $"派送:{ShowdownTranslator.GameStrings.Species[pkm.Species]}\n密码:{code:0000 0000}"; + StartTrade(pkm, dodoId, nickName, channelId); + } + public static void StartTrade(T pkm, string dodoId, string nickName, string channelId) + { + var code = DodoBot.Info.GetRandomTradeCode(); var __ = AddToTradeQueue(pkm, code, ulong.Parse(dodoId), nickName, channelId, PokeRoutineType.LinkTrade, out string message); DodoBot.SendChannelMessage(message, channelId); diff --git a/SysBot.Pokemon.Dodo/PokemonProcessService.cs b/SysBot.Pokemon.Dodo/PokemonProcessService.cs index fc37f472c..59093ab02 100644 --- a/SysBot.Pokemon.Dodo/PokemonProcessService.cs +++ b/SysBot.Pokemon.Dodo/PokemonProcessService.cs @@ -1,4 +1,5 @@ using System; +using System.Net; using DoDo.Open.Sdk.Models.Bots; using DoDo.Open.Sdk.Models.Events; using DoDo.Open.Sdk.Models.Messages; @@ -73,6 +74,22 @@ public override void ChannelMessageEvent( var eventBody = input.Data.EventBody; if (!string.IsNullOrWhiteSpace(_channelId) && eventBody.ChannelId != _channelId) return; + if (eventBody.MessageBody is MessageBodyFile messageBodyFile) + { + if (!ValidFileSize(messageBodyFile.Size ?? 0) || !ValidFileName(messageBodyFile.Name)) + { + DodoBot.SendChannelMessage("非法文件", eventBody.ChannelId); + return; + } + var p = GetPKM(new WebClient().DownloadData(messageBodyFile.Url)); + if (p is TP pkm) + { + DodoHelper.StartTrade(pkm, eventBody.DodoId, eventBody.Personal.NickName, eventBody.ChannelId); + } + + return; + } + if (eventBody.MessageBody is not MessageBodyText messageBodyText) return; var content = messageBodyText.Content; @@ -135,6 +152,36 @@ private static string GetClearTradeMessage(QueueResultRemove result) }; } + private static bool ValidFileSize(long size) + { + if (typeof(TP) == typeof(PK8) || typeof(TP) == typeof(PB8)) + { + return size == 344; + } + + if (typeof(TP) == typeof(PA8)) + { + return size == 376; + } + + return false; + } + + private static bool ValidFileName(string fileName) + { + return (typeof(TP) == typeof(PK8) && fileName.EndsWith("pk8", StringComparison.OrdinalIgnoreCase) + || typeof(TP) == typeof(PB8) && fileName.EndsWith("pb8", StringComparison.OrdinalIgnoreCase) + || typeof(TP) == typeof(PA8) && fileName.EndsWith("pa8", StringComparison.OrdinalIgnoreCase)); + } + + private static PKM GetPKM(byte[] bytes) + { + if (typeof(TP) == typeof(PK8)) return new PK8(bytes); + if (typeof(TP) == typeof(PB8)) return new PB8(bytes); + if (typeof(TP) == typeof(PA8)) return new PA8(bytes); + return null; + } + public override void MessageReactionEvent( EventSubjectOutput> input) { diff --git a/SysBot.Pokemon.Dodo/SysBot.Pokemon.Dodo.csproj b/SysBot.Pokemon.Dodo/SysBot.Pokemon.Dodo.csproj index 8571902e8..7cdf36920 100644 --- a/SysBot.Pokemon.Dodo/SysBot.Pokemon.Dodo.csproj +++ b/SysBot.Pokemon.Dodo/SysBot.Pokemon.Dodo.csproj @@ -6,7 +6,7 @@ - + From 7b3c5fb55e532f8c4c27b8760fb5e538f9231ba5 Mon Sep 17 00:00:00 2001 From: Easy World Date: Thu, 31 Mar 2022 20:40:57 +0800 Subject: [PATCH 036/171] Fix Null --- SysBot.Pokemon.QQ/MiraiQQBot.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SysBot.Pokemon.QQ/MiraiQQBot.cs b/SysBot.Pokemon.QQ/MiraiQQBot.cs index bafeada69..4c55daae5 100644 --- a/SysBot.Pokemon.QQ/MiraiQQBot.cs +++ b/SysBot.Pokemon.QQ/MiraiQQBot.cs @@ -131,7 +131,7 @@ private async Task HandleAliveMessage(GroupMessageReceiver receiver) private async Task HandleCancel(GroupMessageReceiver receiver) { if (receiver.MessageChain.OfType().All(x => x.Target != Settings.QQ)) return; - bool isCancelMsg = (receiver.MessageChain.OfType()?.First()?.Text ?? "").Trim() + bool isCancelMsg = (receiver.MessageChain.OfType()?.FirstOrDefault()?.Text ?? "").Trim() .StartsWith("取消"); if (!isCancelMsg) return; var result = Info.ClearTrade(ulong.Parse(receiver.Sender.Id)); @@ -142,7 +142,7 @@ await receiver.SendMessageAsync( private async Task HandlePosition(GroupMessageReceiver receiver) { if (receiver.MessageChain.OfType().All(x => x.Target != Settings.QQ)) return; - bool isPositionMsg = (receiver.MessageChain.OfType()?.First()?.Text ?? "").Trim() + bool isPositionMsg = (receiver.MessageChain.OfType()?.FirstOrDefault()?.Text ?? "").Trim() .StartsWith("位置"); if (!isPositionMsg) return; var result = Info.CheckPosition(ulong.Parse(receiver.Sender.Id)); @@ -175,7 +175,7 @@ private static string GetClearTradeMessage(QueueResultRemove result) private async Task HandlePokemonName(GroupMessageReceiver receiver) { if (receiver.MessageChain.OfType().All(x => x.Target != Settings.QQ)) return; - var text = receiver.MessageChain.OfType()?.First()?.Text ?? ""; + var text = receiver.MessageChain.OfType()?.FirstOrDefault()?.Text ?? ""; if (string.IsNullOrWhiteSpace(text)) return; string ps = ShowdownTranslator.Chinese2Showdown(text); if (string.IsNullOrWhiteSpace(ps)) return; @@ -257,7 +257,7 @@ private async Task HandleCommand(GroupMessageReceiver receiver) string qqMsg; try { - qqMsg = receiver.MessageChain.OfType().First().Text; + qqMsg = receiver.MessageChain.OfType().FirstOrDefault()?.Text ?? ""; } catch { From 184e2202791bd6bd253948a04bdedc673d3a138b Mon Sep 17 00:00:00 2001 From: Easy World Date: Mon, 4 Apr 2022 16:13:30 +0800 Subject: [PATCH 037/171] =?UTF-8?q?dodo=E6=94=AF=E6=8C=81dump?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SysBot.Pokemon.Dodo/DodoHelper.cs | 10 +++++++++- SysBot.Pokemon.Dodo/DodoTradeNotifier.cs | 9 ++++++++- SysBot.Pokemon.Dodo/PokemonProcessService.cs | 5 +++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/SysBot.Pokemon.Dodo/DodoHelper.cs b/SysBot.Pokemon.Dodo/DodoHelper.cs index f12e017bd..bafffa028 100644 --- a/SysBot.Pokemon.Dodo/DodoHelper.cs +++ b/SysBot.Pokemon.Dodo/DodoHelper.cs @@ -30,6 +30,14 @@ public static void StartTrade(T pkm, string dodoId, string nickName, string chan DodoBot.SendChannelMessage(message, channelId); } + public static void StartDump(string dodoId, string nickName, string channelId) + { + var code = DodoBot.Info.GetRandomTradeCode(); + var __ = AddToTradeQueue(new T(), code, ulong.Parse(dodoId), nickName, channelId, + PokeRoutineType.Dump, out string message); + DodoBot.SendChannelMessage(message, channelId); + } + public static bool CheckAndGetPkm(string setstring, string username, out string msg, out T outPkm) { @@ -107,7 +115,7 @@ private static bool AddToTradeQueue(T pk, int code, ulong userId, string name, s { var trainer = new PokeTradeTrainerInfo(name, userId); var notifier = new DodoTradeNotifier(pk, trainer, code, name, channelId); - var tt = type == PokeRoutineType.SeedCheck ? PokeTradeType.Seed : PokeTradeType.Specific; + var tt = type == PokeRoutineType.SeedCheck ? PokeTradeType.Seed : (type == PokeRoutineType.Dump ? PokeTradeType.Dump : PokeTradeType.Specific); var detail = new PokeTradeDetail(pk, trainer, notifier, tt, code, true); var trade = new TradeEntry(detail, userId, type, name); diff --git a/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs b/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs index 0a81a7468..d9b518dcf 100644 --- a/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs +++ b/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs @@ -59,7 +59,8 @@ public void TradeInitialize(PokeRoutineExecutor routine, PokeTradeDetail i LogUtil.LogText(msg); var text = $"\n派送:{ShowdownTranslator.GameStrings.Species[Data.Species]}\n密码:见私信\n状态:初始化"; DodoBot.SendChannelAtMessage(info.Trainer.ID, text, ChannelId); - DodoBot.SendPersonalMessage(info.Trainer.ID.ToString(), $"派送:{ShowdownTranslator.GameStrings.Species[Data.Species]}\n密码:{info.Code:0000 0000}"); + DodoBot.SendPersonalMessage(info.Trainer.ID.ToString(), + $"派送:{ShowdownTranslator.GameStrings.Species[Data.Species]}\n密码:{info.Code:0000 0000}"); } public void TradeSearching(PokeRoutineExecutor routine, PokeTradeDetail info) @@ -86,6 +87,12 @@ public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail { var msg = $"Details for {result.FileName}: " + message; LogUtil.LogText(msg); + if (result.Species != 0 && info.Type == PokeTradeType.Dump) + { + var text = + $"species:{result.Species}\npid:{result.PID}\nec:{result.EncryptionConstant}\nIVs:{string.Join(",", result.IVs)}"; + DodoBot.SendChannelMessage(text, ChannelId); + } } } } \ No newline at end of file diff --git a/SysBot.Pokemon.Dodo/PokemonProcessService.cs b/SysBot.Pokemon.Dodo/PokemonProcessService.cs index 59093ab02..6bbe686f4 100644 --- a/SysBot.Pokemon.Dodo/PokemonProcessService.cs +++ b/SysBot.Pokemon.Dodo/PokemonProcessService.cs @@ -103,6 +103,11 @@ public override void ChannelMessageEvent( content = content.Replace("trade", ""); DodoHelper.StartTrade(content, eventBody.DodoId, eventBody.Personal.NickName, eventBody.ChannelId); return; + } + else if (content.Trim().StartsWith("dump")) + { + DodoHelper.StartDump(eventBody.DodoId, eventBody.Personal.NickName, eventBody.ChannelId); + return; } var ps = ShowdownTranslator.Chinese2Showdown(content); From 01dd0602e63ab1ca3c8fef81a03c96e46f9160b0 Mon Sep 17 00:00:00 2001 From: Easy World Date: Tue, 12 Jul 2022 21:09:38 +0800 Subject: [PATCH 038/171] Update PokeTradeBot.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix SWSH tid&sid calculate problem. Thanks 齿轮 --- SysBot.Pokemon/SWSH/BotTrade/PokeTradeBot.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SysBot.Pokemon/SWSH/BotTrade/PokeTradeBot.cs b/SysBot.Pokemon/SWSH/BotTrade/PokeTradeBot.cs index ffb9bb59b..a5f801d84 100644 --- a/SysBot.Pokemon/SWSH/BotTrade/PokeTradeBot.cs +++ b/SysBot.Pokemon/SWSH/BotTrade/PokeTradeBot.cs @@ -398,11 +398,11 @@ private async Task PerformLinkCodeTrade(SAV8SWSH sav, PokeTrade private async Task SetPkmWithSwappedIDDetails(PK8 toSend, string trainerName, SAV8SWSH sav, CancellationToken token) { var data = await Connection.ReadBytesAsync(LinkTradePartnerNameOffset - 0x8, 8, token).ConfigureAwait(false); - var tidsid = BitConverter.ToInt32(data, 0); + var tidsid = BitConverter.ToUInt32(data, 0); var cln = (PK8)toSend.Clone(); cln.OT_Gender = data[6]; - cln.TrainerID7 = tidsid % 1_000_000; - cln.TrainerSID7 = tidsid / 1_000_000; + cln.TrainerID7 = (int)(tidsid % 1_000_000); + cln.TrainerSID7 = (int)(tidsid / 1_000_000); cln.Language = data[5]; cln.OT_Name = trainerName; cln.ClearNickname(); From 46c2d207230053e0368c5614e21c19e20493a5f7 Mon Sep 17 00:00:00 2001 From: Easy World Date: Sat, 24 Dec 2022 23:23:10 +0800 Subject: [PATCH 039/171] Tauros-Paldea Form support --- SysBot.Pokemon.Bilibili/BilibiliLiveBot.cs | 2 +- .../BilibiliTradeNotifier.cs | 4 +- SysBot.Pokemon.Dodo/DodoTradeNotifier.cs | 6 +- SysBot.Pokemon.Dodo/PokemonProcessService.cs | 2 +- SysBot.Pokemon.QQ/MiraiQQBot.cs | 2 +- SysBot.Pokemon/Helpers/ShowdownTranslator.cs | 274 ++++++++++++++++-- SysBot.Tests/TranslatorTests.cs | 19 ++ 7 files changed, 275 insertions(+), 34 deletions(-) create mode 100644 SysBot.Tests/TranslatorTests.cs diff --git a/SysBot.Pokemon.Bilibili/BilibiliLiveBot.cs b/SysBot.Pokemon.Bilibili/BilibiliLiveBot.cs index 3cd5afde0..aee10df99 100644 --- a/SysBot.Pokemon.Bilibili/BilibiliLiveBot.cs +++ b/SysBot.Pokemon.Bilibili/BilibiliLiveBot.cs @@ -60,7 +60,7 @@ public BilibiliLiveBot(BilibiliSettings settings, PokeTradeHub hub) if (!_) continue; var code = Info.GetRandomTradeCode(); File.WriteAllText(@"msg.txt", - $"派送:{ShowdownTranslator.GameStrings.Species[pkm.Species]}\n密码:{code:0000 0000}"); + $"派送:{ShowdownTranslator.GameStringsZh.Species[pkm.Species]}\n密码:{code:0000 0000}"); var __ = AddToTradeQueue(pkm, code, DefaultUserId, DefaultUserName, RequestSignificance.Favored, PokeRoutineType.LinkTrade, out string message); } diff --git a/SysBot.Pokemon.Bilibili/BilibiliTradeNotifier.cs b/SysBot.Pokemon.Bilibili/BilibiliTradeNotifier.cs index 045f39363..9d432d208 100644 --- a/SysBot.Pokemon.Bilibili/BilibiliTradeNotifier.cs +++ b/SysBot.Pokemon.Bilibili/BilibiliTradeNotifier.cs @@ -57,7 +57,7 @@ public void TradeInitialize(PokeRoutineExecutor routine, PokeTradeDetail i msg += $" Your trade code is: {info.Code:0000 0000}"; LogUtil.LogText(msg); File.WriteAllText("msg.txt", - $"派送:{ShowdownTranslator.GameStrings.Species[Data.Species]}\n密码:{info.Code:0000 0000}\n状态:初始化"); + $"派送:{ShowdownTranslator.GameStringsZh.Species[Data.Species]}\n密码:{info.Code:0000 0000}\n状态:初始化"); } public void TradeSearching(PokeRoutineExecutor routine, PokeTradeDetail info) @@ -68,7 +68,7 @@ public void TradeSearching(PokeRoutineExecutor routine, PokeTradeDetail in message += $" Your trade code is: {info.Code:0000 0000}"; LogUtil.LogText(message); File.WriteAllText("msg.txt", - $"派送:{ShowdownTranslator.GameStrings.Species[Data.Species]}\n密码:{info.Code:0000 0000}\n状态:搜索中"); + $"派送:{ShowdownTranslator.GameStringsZh.Species[Data.Species]}\n密码:{info.Code:0000 0000}\n状态:搜索中"); } public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, PokeTradeSummary message) diff --git a/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs b/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs index d9b518dcf..fc89085a3 100644 --- a/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs +++ b/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs @@ -57,10 +57,10 @@ public void TradeInitialize(PokeRoutineExecutor routine, PokeTradeDetail i $"@{info.Trainer.TrainerName} (ID: {info.ID}): Initializing trade{receive} with you. Please be ready."; msg += $" Your trade code is: {info.Code:0000 0000}"; LogUtil.LogText(msg); - var text = $"\n派送:{ShowdownTranslator.GameStrings.Species[Data.Species]}\n密码:见私信\n状态:初始化"; + var text = $"\n派送:{ShowdownTranslator.GameStringsZh.Species[Data.Species]}\n密码:见私信\n状态:初始化"; DodoBot.SendChannelAtMessage(info.Trainer.ID, text, ChannelId); DodoBot.SendPersonalMessage(info.Trainer.ID.ToString(), - $"派送:{ShowdownTranslator.GameStrings.Species[Data.Species]}\n密码:{info.Code:0000 0000}"); + $"派送:{ShowdownTranslator.GameStringsZh.Species[Data.Species]}\n密码:{info.Code:0000 0000}"); } public void TradeSearching(PokeRoutineExecutor routine, PokeTradeDetail info) @@ -70,7 +70,7 @@ public void TradeSearching(PokeRoutineExecutor routine, PokeTradeDetail in var message = $"I'm waiting for you{trainer}! My IGN is {routine.InGameName}."; message += $" Your trade code is: {info.Code:0000 0000}"; LogUtil.LogText(message); - var text = $"派送:{ShowdownTranslator.GameStrings.Species[Data.Species]}\n密码:见私信\n状态:搜索中"; + var text = $"派送:{ShowdownTranslator.GameStringsZh.Species[Data.Species]}\n密码:见私信\n状态:搜索中"; DodoBot.SendChannelMessage(text, ChannelId); //DodoBot.SendPersonalMessage(info.Trainer.ID.ToString(), $"{info.Code:0000 0000}"); } diff --git a/SysBot.Pokemon.Dodo/PokemonProcessService.cs b/SysBot.Pokemon.Dodo/PokemonProcessService.cs index 6bbe686f4..ba7dee670 100644 --- a/SysBot.Pokemon.Dodo/PokemonProcessService.cs +++ b/SysBot.Pokemon.Dodo/PokemonProcessService.cs @@ -142,7 +142,7 @@ public string GetQueueCheckResultMessage(QueueCheckResult result) var msg = $"你在第{result.Position}位"; var pk = result.Detail.Trade.TradeData; if (pk.Species != 0) - msg += $",交换宝可梦:{ShowdownTranslator.GameStrings.Species[result.Detail.Trade.TradeData.Species]}"; + msg += $",交换宝可梦:{ShowdownTranslator.GameStringsZh.Species[result.Detail.Trade.TradeData.Species]}"; return msg; } diff --git a/SysBot.Pokemon.QQ/MiraiQQBot.cs b/SysBot.Pokemon.QQ/MiraiQQBot.cs index 4c55daae5..e9b78d1ee 100644 --- a/SysBot.Pokemon.QQ/MiraiQQBot.cs +++ b/SysBot.Pokemon.QQ/MiraiQQBot.cs @@ -157,7 +157,7 @@ public string GetQueueCheckResultMessage(QueueCheckResult result) var msg = $"你在第{result.Position}位"; var pk = result.Detail.Trade.TradeData; if (pk.Species != 0) - msg += $",交换宝可梦:{ShowdownTranslator.GameStrings.Species[result.Detail.Trade.TradeData.Species]}"; + msg += $",交换宝可梦:{ShowdownTranslator.GameStringsZh.Species[result.Detail.Trade.TradeData.Species]}"; return msg; } diff --git a/SysBot.Pokemon/Helpers/ShowdownTranslator.cs b/SysBot.Pokemon/Helpers/ShowdownTranslator.cs index bb126fc08..79c46cb0f 100644 --- a/SysBot.Pokemon/Helpers/ShowdownTranslator.cs +++ b/SysBot.Pokemon/Helpers/ShowdownTranslator.cs @@ -5,33 +5,42 @@ namespace SysBot.Pokemon { public class ShowdownTranslator where T : PKM { - public static GameStrings GameStrings = GameInfo.GetStrings("zh"); + public static GameStrings GameStringsZh = GameInfo.GetStrings("zh"); public static GameStrings GameStringsEn = GameInfo.GetStrings("en"); public static string Chinese2Showdown(string zh) { string result = ""; - + + // 添加宝可梦 int candidateSpecieNo = 0; int candidateSpecieStringLength = 0; - for (int i = 0; i < GameStrings.Species.Count; i++) + for (int i = 0; i < GameStringsZh.Species.Count; i++) { - if (zh.Contains(GameStrings.Species[i]) && GameStrings.Species[i].Length > candidateSpecieStringLength) + if (zh.Contains(GameStringsZh.Species[i]) && GameStringsZh.Species[i].Length > candidateSpecieStringLength) { candidateSpecieNo = i; - candidateSpecieStringLength = GameStrings.Species[i].Length; + candidateSpecieStringLength = GameStringsZh.Species[i].Length; } } if (candidateSpecieNo > 0) { - result += GameStringsEn.Species[candidateSpecieNo]; - zh = zh.Replace(GameStrings.Species[candidateSpecieNo], ""); + if (candidateSpecieNo == 29) result = "Nidoran-F"; + else if (candidateSpecieNo == 32) result = "Nidoran-M"; + else result += GameStringsEn.Species[candidateSpecieNo]; + + zh = zh.Replace(GameStringsZh.Species[candidateSpecieNo], ""); + + // 特殊性别差异 + // 29-尼多兰F,32-尼多朗M,678-超能妙喵F,876-爱管侍F,902-幽尾玄鱼F, 916-飘香豚 + if ((candidateSpecieNo is 678 or 876 or 902 or 916) && zh.Contains("母")) result += "-F"; } else { return result; } + // 识别未知图腾 if (Regex.IsMatch(zh, "[A-Z?!?!]形态")) { string formsUnown = Regex.Match(zh, "([A-Z?!?!])形态").Groups?[1]?.Value ?? ""; @@ -41,15 +50,31 @@ public static string Chinese2Showdown(string zh) zh = Regex.Replace(zh, "[A-Z?!?!]形态", ""); } - for (int i = 0; i < GameStrings.forms.Length; i++) + // 识别地区形态 + if (zh.Contains("帕底亚的样子(火)形态")) { - if (GameStrings.forms[i].Length == 0) continue; - if (!zh.Contains(GameStrings.forms[i])) continue; - result += $"-{GameStringsEn.forms[i]}"; - zh = zh.Replace(GameStrings.forms[i], ""); - break; + result += $"-Paldea-Fire"; + zh = zh.Replace("帕底亚的样子(火)形态", ""); + } + else if (zh.Contains("帕底亚的样子(水)形态")) + { + result += $"-Paldea-Water"; + zh = zh.Replace("帕底亚的样子(水)形态", ""); + } + else + { + for (int i = 0; i < GameStringsZh.forms.Length; i++) + { + if (GameStringsZh.forms[i].Length == 0) continue; + if (!zh.Contains(GameStringsZh.forms[i] + "形态")) continue; + result += $"-{GameStringsEn.forms[i]}"; + zh = zh.Replace(GameStringsZh.forms[i] + "形态", ""); + break; + } } + + // 添加性别 if (zh.Contains("公")) { result += " (M)"; @@ -61,6 +86,31 @@ public static string Chinese2Showdown(string zh) zh = zh.Replace("母", ""); } + // 添加持有物 + if (zh.Contains("持有")) + { + for (int i = 0; i < GameStringsZh.Item.Count; i++) + { + if (GameStringsZh.Item[i].Length == 0) continue; + if (!zh.Contains("持有" + GameStringsZh.Item[i])) continue; + result += $" @ {GameStringsEn.Item[i]}"; + zh = zh.Replace("持有" + GameStringsZh.Item[i], ""); + break; + } + } + else if (zh.Contains("携带")) + { + for (int i = 0; i < GameStringsZh.Item.Count; i++) + { + if (GameStringsZh.Item[i].Length == 0) continue; + if (!zh.Contains("携带" + GameStringsZh.Item[i])) continue; + result += $" @ {GameStringsEn.Item[i]}"; + zh = zh.Replace("携带" + GameStringsZh.Item[i], ""); + break; + } + } + + // 添加等级 if (Regex.IsMatch(zh, "\\d{1,3}级")) { string level = Regex.Match(zh, "(\\d{1,3})级").Groups?[1]?.Value ?? "100"; @@ -68,38 +118,210 @@ public static string Chinese2Showdown(string zh) zh = Regex.Replace(zh, "\\d{1,3}级", ""); } + // 添加超极巨化 + if (typeof(T) == typeof(PK8) && zh.Contains("超极巨")) + { + result += "\nGigantamax: Yes"; + zh = zh.Replace("超极巨", ""); + } + + // 添加异色 if (zh.Contains("异色")) { result += "\nShiny: Yes"; zh = zh.Replace("异色", ""); } + else if (zh.Contains("闪光")) + { + result += "\nShiny: Yes"; + zh = zh.Replace("闪光", ""); + } + else if (zh.Contains("星闪")) + { + result += "\nShiny: Star"; + zh = zh.Replace("星闪", ""); + } + else if (zh.Contains("方闪")) + { + result += "\nShiny: Square"; + zh = zh.Replace("方闪", ""); + } - if (zh.Contains("头目")) + // 添加头目 + if (typeof(T) == typeof(PA8) && zh.Contains("头目")) { result += "\nAlpha: Yes"; zh = zh.Replace("头目", ""); } - for (int i = 0; i < GameStrings.Natures.Count; i++) + // 添加球种 + for (int i = 0; i < GameStringsZh.balllist.Length; i++) + { + if (GameStringsZh.balllist[i].Length == 0) continue; + if (!zh.Contains(GameStringsZh.balllist[i])) continue; + var ballStr = GameStringsEn.balllist[i]; + if (typeof(T) == typeof(PA8) && ballStr is "Poké Ball" or "Great Ball" or "Ultra Ball") ballStr = "LA" + ballStr; + result += $"\nBall: {ballStr}"; + zh = zh.Replace(GameStringsZh.balllist[i], ""); + break; + } + + // 添加特性 + for (int i = 0; i < GameStringsZh.Ability.Count; i++) + { + if (GameStringsZh.Ability[i].Length == 0) continue; + if (!zh.Contains(GameStringsZh.Ability[i] + "特性")) continue; + result += $"\nAbility: {GameStringsEn.Ability[i]}"; + zh = zh.Replace(GameStringsZh.Ability[i] + "特性", ""); + break; + } + + // 添加性格 + for (int i = 0; i < GameStringsZh.Natures.Count; i++) + { + if (GameStringsZh.Natures[i].Length == 0) continue; + if (!zh.Contains(GameStringsZh.Natures[i])) continue; + result += $"\n{GameStringsEn.Natures[i]} Nature"; + zh = zh.Replace(GameStringsZh.Natures[i], ""); + break; + } + + // 添加个体值 + if (zh.ToUpper().Contains("6V")) { - if (zh.Contains(GameStrings.Natures[i])) + result += "\nIVs: 31 HP / 31 Atk / 31 Def / 31 SpA / 31 SpD / 31 Spe"; + zh = zh.Replace("6V", ""); + } + else if (zh.ToUpper().Contains("5V0A")) + { + result += "\nIVs: 31 HP / 0 Atk / 31 Def / 31 SpA / 31 SpD / 31 Spe"; + zh = zh.Replace("5V0A", ""); + } + else if (zh.ToUpper().Contains("5V0S")) + { + result += "\nIVs: 31 HP / 31 Atk / 31 Def / 31 SpA / 31 SpD / 0 Spe"; + zh = zh.Replace("5V0S", ""); + } + else if (zh.ToUpper().Contains("4V0A0S")) + { + result += "\nIVs: 31 HP / 0 Atk / 31 Def / 31 SpA / 31 SpD / 0 Spe"; + zh = zh.Replace("4V0A0S", ""); + } + + // 添加努力值 + if (zh.Contains("努力值")) + { + result += "\nEVs: "; + zh = zh.Replace("努力值", ""); + if (Regex.IsMatch(zh, "\\d{1,3}生命")) + { + string value = Regex.Match(zh, "(\\d{1,3})生命").Groups?[1]?.Value ?? ""; + result += $"{value} HP / "; + zh = Regex.Replace(zh, "\\d{1,3}生命", ""); + } + else if (Regex.IsMatch(zh, "\\d{1,3}Hp")) + { + string value = Regex.Match(zh, "(\\d{1,3})Hp").Groups?[1]?.Value ?? ""; + result += $"{value} HP / "; + zh = Regex.Replace(zh, "\\d{1,3}Hp", ""); + } + + if (Regex.IsMatch(zh, "\\d{1,3}攻击")) + { + string value = Regex.Match(zh, "(\\d{1,3})攻击").Groups?[1]?.Value ?? ""; + result += $"{value} Atk / "; + zh = Regex.Replace(zh, "\\d{1,3}攻击", ""); + } + else if (Regex.IsMatch(zh, "\\d{1,3}Atk")) + { + string value = Regex.Match(zh, "(\\d{1,3})Atk").Groups?[1]?.Value ?? ""; + result += $"{value} Atk / "; + zh = Regex.Replace(zh, "\\d{1,3}Atk", ""); + } + + if (Regex.IsMatch(zh, "\\d{1,3}防御")) + { + string value = Regex.Match(zh, "(\\d{1,3})防御").Groups?[1]?.Value ?? ""; + result += $"{value} Def / "; + zh = Regex.Replace(zh, "\\d{1,3}防御", ""); + } + else if (Regex.IsMatch(zh, "\\d{1,3}Def")) + { + string value = Regex.Match(zh, "(\\d{1,3})Def").Groups?[1]?.Value ?? ""; + result += $"{value} Def / "; + zh = Regex.Replace(zh, "\\d{1,3}Def", ""); + } + + if (Regex.IsMatch(zh, "\\d{1,3}特攻")) + { + string value = Regex.Match(zh, "(\\d{1,3})特攻").Groups?[1]?.Value ?? ""; + result += $"{value} SpA / "; + zh = Regex.Replace(zh, "\\d{1,3}特攻", ""); + } + else if (Regex.IsMatch(zh, "\\d{1,3}SpA")) + { + string value = Regex.Match(zh, "(\\d{1,3})SpA").Groups?[1]?.Value ?? ""; + result += $"{value} SpA / "; + zh = Regex.Replace(zh, "\\d{1,3}SpA", ""); + } + + if (Regex.IsMatch(zh, "\\d{1,3}特防")) + { + string value = Regex.Match(zh, "(\\d{1,3})特防").Groups?[1]?.Value ?? ""; + result += $"{value} SpD / "; + zh = Regex.Replace(zh, "\\d{1,3}特防", ""); + } + else if (Regex.IsMatch(zh, "\\d{1,3}SpD")) + { + string value = Regex.Match(zh, "(\\d{1,3})SpD").Groups?[1]?.Value ?? ""; + result += $"{value} SpD / "; + zh = Regex.Replace(zh, "\\d{1,3}SpD", ""); + } + if (Regex.IsMatch(zh, "\\d{1,3}速度")) + { + string value = Regex.Match(zh, "(\\d{1,3})速度").Groups?[1]?.Value ?? ""; + result += $"{value} Spe"; + zh = Regex.Replace(zh, "\\d{1,3}速度", ""); + } + else if (Regex.IsMatch(zh, "\\d{1,3}Spe")) + { + string value = Regex.Match(zh, "(\\d{1,3})Spe").Groups?[1]?.Value ?? ""; + result += $"{value} Spe"; + zh = Regex.Replace(zh, "\\d{1,3}Spe", ""); + } + if (result.EndsWith("/ ")) { - result += $"\n{GameStringsEn.Natures[i]} Nature"; - zh = zh.Replace(GameStrings.Natures[i], ""); + result = result.Substring(0, result.Length - 2); + } + } + + // 添加太晶属性 + if (typeof(T) == typeof(PK9)) + { + for (int i = 0; i < GameStringsZh.Types.Count; i++) + { + if (GameStringsZh.Types[i].Length == 0) continue; + if (!zh.Contains("太晶" + GameStringsZh.Types[i])) continue; + result += $"\nTera Type: {GameStringsEn.Types[i]}"; + zh = zh.Replace("太晶" + GameStringsZh.Types[i], ""); break; } } - for (int i = 0; i < GameStrings.balllist.Length; i++) + // 添加技能 + zh += "-"; + for (int moveCount = 0; moveCount < 4; moveCount++) { - if (GameStrings.balllist[i].Length == 0) continue; - if (!zh.Contains(GameStrings.balllist[i])) continue; - var ballStr = GameStringsEn.balllist[i]; - if (typeof(T) == typeof(PA8) && ballStr is "Poké Ball" or "Great Ball" or "Ultra Ball") ballStr = "LA" + ballStr; - result += $"\nBall: {ballStr}"; - zh = zh.Replace(GameStrings.balllist[i], ""); - break; + for (int i = 0; i < GameStringsZh.Move.Count; i++) + { + if (GameStringsZh.Move[i].Length == 0) continue; + if (!zh.Contains("-" + GameStringsZh.Move[i] + "-")) continue; + result += $"\n-{GameStringsEn.Move[i]}"; + zh = zh.Replace("-" + GameStringsZh.Move[i], ""); + break; + } } + zh = zh.Replace("-", ""); return result; } diff --git a/SysBot.Tests/TranslatorTests.cs b/SysBot.Tests/TranslatorTests.cs new file mode 100644 index 000000000..e6ee00bab --- /dev/null +++ b/SysBot.Tests/TranslatorTests.cs @@ -0,0 +1,19 @@ +using FluentAssertions; +using PKHeX.Core; +using SysBot.Pokemon; +using Xunit; + +namespace SysBot.Tests +{ + public class TranslatorTests + { + [Theory] + [InlineData("公肯泰罗帕底亚的样子(火)形态", "Tauros-Paldea-Fire (M)")] + public void TestForm(string input, string output) + { + var result = ShowdownTranslator.Chinese2Showdown(input); + result.Should().Be(output); + } + + } +} From 51453c9b128fa831f2275c7db29b743f9942926d Mon Sep 17 00:00:00 2001 From: Easy World Date: Tue, 27 Dec 2022 15:24:19 +0800 Subject: [PATCH 040/171] Use trade partner info for SV --- SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs | 50 +++++++++++++++++++- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs index 27d7620b2..cc3e74f0c 100644 --- a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs +++ b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs @@ -317,7 +317,8 @@ private async Task PerformLinkCodeTrade(SAV9SV sav, PokeTradeDe } await Task.Delay(2_000, token).ConfigureAwait(false); - var tradePartner = await GetTradePartnerInfo(token).ConfigureAwait(false); + var tradePartnerFullInfo = await GetTradePartnerFullInfo(token).ConfigureAwait(false); + var tradePartner = new TradePartnerSV(tradePartnerFullInfo); var trainerNID = await GetTradePartnerNID(TradePartnerNIDOffset, token).ConfigureAwait(false); RecordUtil.Record($"Initiating\t{trainerNID:X16}\t{tradePartner.TrainerName}\t{poke.Trainer.TrainerName}\t{poke.Trainer.ID}\t{poke.ID}\t{toSend.EncryptionConstant:X8}"); Log($"Found Link Trade partner: {tradePartner.TrainerName}-{tradePartner.TID7} (ID: {trainerNID})"); @@ -330,6 +331,11 @@ private async Task PerformLinkCodeTrade(SAV9SV sav, PokeTradeDe return partnerCheck; } + if (Hub.Config.Legality.UseTradePartnerInfo) + { + await SetBoxPkmWithSwappedIDDetailsSV(toSend, tradePartnerFullInfo, sav, token); + } + poke.SendNotification(this, $"Found Link Trade partner: {tradePartner.TrainerName}. Waiting for a Pokémon..."); if (poke.Type == PokeTradeType.Dump) @@ -753,12 +759,17 @@ private async Task ProcessDumpTradeAsync(PokeTradeDetail d } private async Task GetTradePartnerInfo(CancellationToken token) + { + return new TradePartnerSV(await GetTradePartnerFullInfo(token)); + } + + private async Task GetTradePartnerFullInfo(CancellationToken token) { // We're able to see both users' MyStatus, but one of them will be ourselves. var trader_info = await GetTradePartnerMyStatus(Offsets.Trader1MyStatusPointer, token).ConfigureAwait(false); if (trader_info.OT == OT && trader_info.DisplaySID == DisplaySID && trader_info.DisplayTID == DisplayTID) // This one matches ourselves. trader_info = await GetTradePartnerMyStatus(Offsets.Trader2MyStatusPointer, token).ConfigureAwait(false); - return new TradePartnerSV(trader_info); + return trader_info; } protected virtual async Task<(PK9 toSend, PokeTradeResult check)> GetEntityToSend(SAV9SV sav, PokeTradeDetail poke, PK9 offered, byte[] oldEC, PK9 toSend, PartnerDataHolder partnerID, CancellationToken token) @@ -1005,5 +1016,40 @@ private PokeTradeResult CheckPartnerReputation(PokeTradeDetail poke, ulong Name = name, Comment = $"Added automatically on {DateTime.Now:yyyy.MM.dd-hh:mm:ss} ({comment})", }; + + private async Task SetBoxPkmWithSwappedIDDetailsSV(PK9 toSend, TradeMyStatus tradePartner, SAV9SV sav, CancellationToken token) + { + if (toSend.Species == (ushort)Species.Ditto) + { + Log($"Do nothing to trade Pokemon, since pokemon is Ditto"); + return false; + } + var cln = (PK9)toSend.Clone(); + cln.OT_Gender = tradePartner.Gender; + cln.TrainerID7 = (int)Math.Abs(tradePartner.DisplayTID); + cln.TrainerSID7 = (int)Math.Abs(tradePartner.DisplaySID); + cln.Language = tradePartner.Language; + cln.OT_Name = tradePartner.OT; + cln.Version = tradePartner.Game; + cln.ClearNickname(); + + if (toSend.IsShiny) + cln.SetShiny(); + + cln.RefreshChecksum(); + + var tradeSV = new LegalityAnalysis(cln); + if (tradeSV.Valid) + { + Log($"Pokemon is valid, use trade partnerInfo"); + await SetBoxPokemonAbsolute(BoxStartOffset, toSend, token, sav).ConfigureAwait(false); + } + else + { + Log($"Pokemon not valid, do nothing to trade Pokemon"); + } + + return tradeSV.Valid; + } } } From 37e37d27da9f1548d90cdbf4c063c6ac3185cee7 Mon Sep 17 00:00:00 2001 From: Easy World Date: Wed, 28 Dec 2022 09:30:27 +0800 Subject: [PATCH 041/171] Fix wrong inject object --- SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs index cc3e74f0c..58ef1cf4b 100644 --- a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs +++ b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs @@ -1042,7 +1042,7 @@ private async Task SetBoxPkmWithSwappedIDDetailsSV(PK9 toSend, TradeMyStat if (tradeSV.Valid) { Log($"Pokemon is valid, use trade partnerInfo"); - await SetBoxPokemonAbsolute(BoxStartOffset, toSend, token, sav).ConfigureAwait(false); + await SetBoxPokemonAbsolute(BoxStartOffset, cln, token, sav).ConfigureAwait(false); } else { From 173eece30e7710f6a85395435fa0ee16606b5c20 Mon Sep 17 00:00:00 2001 From: Easy World Date: Wed, 28 Dec 2022 14:14:35 +0800 Subject: [PATCH 042/171] QQ and Dodo file support --- SysBot.Pokemon.Dodo/PokemonProcessService.cs | 6 ++++-- SysBot.Pokemon.QQ/MiraiQQBot.cs | 12 +++++++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/SysBot.Pokemon.Dodo/PokemonProcessService.cs b/SysBot.Pokemon.Dodo/PokemonProcessService.cs index ba7dee670..80b7c8891 100644 --- a/SysBot.Pokemon.Dodo/PokemonProcessService.cs +++ b/SysBot.Pokemon.Dodo/PokemonProcessService.cs @@ -159,7 +159,7 @@ private static string GetClearTradeMessage(QueueResultRemove result) private static bool ValidFileSize(long size) { - if (typeof(TP) == typeof(PK8) || typeof(TP) == typeof(PB8)) + if (typeof(TP) == typeof(PK8) || typeof(TP) == typeof(PB8) || typeof(TP) == typeof(PK9)) { return size == 344; } @@ -176,7 +176,8 @@ private static bool ValidFileName(string fileName) { return (typeof(TP) == typeof(PK8) && fileName.EndsWith("pk8", StringComparison.OrdinalIgnoreCase) || typeof(TP) == typeof(PB8) && fileName.EndsWith("pb8", StringComparison.OrdinalIgnoreCase) - || typeof(TP) == typeof(PA8) && fileName.EndsWith("pa8", StringComparison.OrdinalIgnoreCase)); + || typeof(TP) == typeof(PA8) && fileName.EndsWith("pa8", StringComparison.OrdinalIgnoreCase) + || typeof(TP) == typeof(PK9) && fileName.EndsWith("pk9", StringComparison.OrdinalIgnoreCase)); } private static PKM GetPKM(byte[] bytes) @@ -184,6 +185,7 @@ private static PKM GetPKM(byte[] bytes) if (typeof(TP) == typeof(PK8)) return new PK8(bytes); if (typeof(TP) == typeof(PB8)) return new PB8(bytes); if (typeof(TP) == typeof(PA8)) return new PA8(bytes); + if (typeof(TP) == typeof(PK9)) return new PK9(bytes); return null; } diff --git a/SysBot.Pokemon.QQ/MiraiQQBot.cs b/SysBot.Pokemon.QQ/MiraiQQBot.cs index e9b78d1ee..7af79c324 100644 --- a/SysBot.Pokemon.QQ/MiraiQQBot.cs +++ b/SysBot.Pokemon.QQ/MiraiQQBot.cs @@ -102,6 +102,10 @@ public MiraiQQBot(QQSettings settings, PokeTradeHub hub) { await MessageManager.SendGroupMessageAsync(GroupId, "当前版本为阿尔宙斯"); } + else if (typeof(T) == typeof(PK9)) + { + await MessageManager.SendGroupMessageAsync(GroupId, "当前版本为朱紫"); + } await Task.Delay(1_000).ConfigureAwait(false); } @@ -204,6 +208,9 @@ private async Task HandleFileUpload(GroupMessageReceiver receiver) else if (typeof(T) == typeof(PA8) && fileName.EndsWith(".pa8", StringComparison.OrdinalIgnoreCase)) operationType = "pa8"; + else if (typeof(T) == typeof(PK9) && + fileName.EndsWith(".pk9", StringComparison.OrdinalIgnoreCase)) + operationType = "pk9"; else return; PKM pkm; @@ -215,7 +222,7 @@ private async Task HandleFileUpload(GroupMessageReceiver receiver) byte[] data = new System.Net.WebClient().DownloadData(url); switch (operationType) { - case "pk8" or "pb8" when data.Length != 344: + case "pk8" or "pb8" or "pk9" when data.Length != 344: await MessageManager.SendGroupMessageAsync(groupId, "非法文件"); return; case "pa8" when data.Length != 376: @@ -234,6 +241,9 @@ private async Task HandleFileUpload(GroupMessageReceiver receiver) case "pa8": pkm = new PA8(data); break; + case "pk9": + pkm = new PK9(data); + break; default: return; } From bafb566625888b5d525ceecb80b915281a873e23 Mon Sep 17 00:00:00 2001 From: Lusamine <30205550+Lusamine@users.noreply.github.com> Date: Wed, 28 Dec 2022 00:56:03 -0600 Subject: [PATCH 043/171] Try-catch connect/disconnect attempts --- .../Switch/Wireless/SwitchSocketAsync.cs | 46 +++++++++++++++++-- .../Switch/Wireless/SwitchSocketSync.cs | 11 ++++- 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/SysBot.Base/Connection/Switch/Wireless/SwitchSocketAsync.cs b/SysBot.Base/Connection/Switch/Wireless/SwitchSocketAsync.cs index 53e363f58..e8c3e4147 100644 --- a/SysBot.Base/Connection/Switch/Wireless/SwitchSocketAsync.cs +++ b/SysBot.Base/Connection/Switch/Wireless/SwitchSocketAsync.cs @@ -29,7 +29,16 @@ public override void Connect() } Log("Connecting to device..."); - Connection.Connect(Info.IP, Info.Port); + try + { + Connection.Connect(Info.IP, Info.Port); + } + catch (Exception e) + { + Log($"Connect failed: {e.Message}"); + Connected = false; + return; + } Connected = true; Log("Connected!"); Label = Name; @@ -47,7 +56,16 @@ public override void Reset() foreach (IPAddress adr in address) { IPEndPoint ep = new(adr, Info.Port); - Connection.BeginConnect(ep, ConnectCallback, Connection); + try + { + Connection.BeginConnect(ep, ConnectCallback, Connection); + } + catch (Exception e) + { + Log($"Reset connection failed: {e.Message}"); + Connected = false; + return; + } Connected = true; Log("Connected!"); } @@ -56,8 +74,17 @@ public override void Reset() public override void Disconnect() { Log("Disconnecting from device..."); - Connection.Shutdown(SocketShutdown.Both); - Connection.BeginDisconnect(true, DisconnectCallback, Connection); + try + { + Connection.Shutdown(SocketShutdown.Both); + Connection.BeginDisconnect(true, DisconnectCallback, Connection); + } + catch (Exception e) + { + Log($"Disconnect failed: {e.Message}"); + Connected = false; + return; + } Connected = false; Log("Disconnected! Resetting Socket."); InitializeSocket(); @@ -69,7 +96,16 @@ public void ConnectCallback(IAsyncResult ar) { // Complete the connection request. Socket client = (Socket)ar.AsyncState; - client.EndConnect(ar); + try + { + client.EndConnect(ar); + } + catch (Exception e) + { + Log($"ConnectCallback failed: {e.Message}"); + Connected = false; + return; + } // Signal that the connection is complete. connectionDone.Set(); diff --git a/SysBot.Base/Connection/Switch/Wireless/SwitchSocketSync.cs b/SysBot.Base/Connection/Switch/Wireless/SwitchSocketSync.cs index 12a0afe0e..b9bd3d4a5 100644 --- a/SysBot.Base/Connection/Switch/Wireless/SwitchSocketSync.cs +++ b/SysBot.Base/Connection/Switch/Wireless/SwitchSocketSync.cs @@ -17,7 +17,16 @@ public SwitchSocketSync(IWirelessConnectionConfig cfg) : base(cfg) { } public override void Connect() { Log("Connecting to device..."); - Connection.Connect(Info.IP, Info.Port); + try + { + Connection.Connect(Info.IP, Info.Port); + } + catch (Exception e) + { + Log($"Connect failed: {e.Message}"); + Connected = false; + return; + } Connected = true; Log("Connected!"); } From e17aa5a40397912f5485339a47c2f4696af1cb37 Mon Sep 17 00:00:00 2001 From: Easy World Date: Wed, 28 Dec 2022 15:06:30 +0800 Subject: [PATCH 044/171] Update README.md --- README.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 80f82707f..c2e7c7e61 100644 --- a/README.md +++ b/README.md @@ -44,12 +44,21 @@ Uses [Discord.Net](https://github.com/discord-net/Discord.Net) , [TwitchLib](htt ## SysBot.Pokemon.QQ: - Support [ALM-Showdown-Sets](https://github.com/architdate/PKHeX-Plugins/wiki/ALM-Showdown-Sets) -- Support PK8 PB8 PA8 file upload +- Support PK8 PB8 PA8 PK9 file upload Most codes are based on [SysBot.Pokemon.Twitch](https://github.com/kwsch/SysBot.NET/tree/master/SysBot.Pokemon.Twitch) Uses [Mirai.Net](https://github.com/SinoAHpx/Mirai.Net) as a dependency via Nuget. +## SysBot.Pokemon.Dodo: +- Support [ALM-Showdown-Sets](https://github.com/architdate/PKHeX-Plugins/wiki/ALM-Showdown-Sets) +- Support PK8 PB8 PA8 PK9 file upload +- Support Customized Chinese to ALM-Showdown-Sets translation + +Most codes are based on [SysBot.Pokemon.Twitch](https://github.com/kwsch/SysBot.NET/tree/master/SysBot.Pokemon.Twitch) + +Uses [dodo-open-net](https://github.com/dodo-open/dodo-open-net) as a dependency via Nuget. + ## Other Dependencies Pokémon API logic is provided by [PKHeX](https://github.com/kwsch/PKHeX/), and template generation is provided by [AutoMod](https://github.com/architdate/PKHeX-Plugins/). From 47ace69cf22d9340fc93b842be3d44b3bc8592af Mon Sep 17 00:00:00 2001 From: Easy World Date: Wed, 28 Dec 2022 15:10:22 +0800 Subject: [PATCH 045/171] Create README.md --- SysBot.Pokemon.Dodo/README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 SysBot.Pokemon.Dodo/README.md diff --git a/SysBot.Pokemon.Dodo/README.md b/SysBot.Pokemon.Dodo/README.md new file mode 100644 index 000000000..86b55e8c1 --- /dev/null +++ b/SysBot.Pokemon.Dodo/README.md @@ -0,0 +1,14 @@ +# SysBot.Pokemon.Dodo +![License](https://img.shields.io/badge/License-AGPLv3-blue.svg) + +Most codes are based on [SysBot.Pokemon.Twitch](https://github.com/kwsch/SysBot.NET/tree/master/SysBot.Pokemon.Twitch) + +Uses [dodo-open-net](https://github.com/dodo-open/dodo-open-net) as a dependency via Nuget. + +## Support Dodo by [dodo-open-net](https://github.com/dodo-open/dodo-open-net): +- Support [ALM-Showdown-Sets](https://github.com/architdate/PKHeX-Plugins/wiki/ALM-Showdown-Sets) +- Support PK8 PB8 PA8 PK9 file upload +- Support Customized Chinese to ALM-Showdown-Sets translation + +## Usage +See https://docs.qq.com/doc/DSVVZZk9saUNTeHNn From d046b5364a27f095760ff61ffaafecff80d5db35 Mon Sep 17 00:00:00 2001 From: Easy World Date: Sun, 1 Jan 2023 18:11:11 +0800 Subject: [PATCH 046/171] Init message for SV --- SysBot.Pokemon.Dodo/DodoBot.cs | 4 ++++ SysBot.Pokemon.Dodo/PokemonProcessService.cs | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/SysBot.Pokemon.Dodo/DodoBot.cs b/SysBot.Pokemon.Dodo/DodoBot.cs index 7db8b00fa..c63e27fd3 100644 --- a/SysBot.Pokemon.Dodo/DodoBot.cs +++ b/SysBot.Pokemon.Dodo/DodoBot.cs @@ -62,6 +62,10 @@ public void StartDistribution() { SendChannelMessage("当前版本为阿尔宙斯", channelId); } + else if (typeof(T) == typeof(PK9)) + { + SendChannelMessage("当前版本为朱紫", channelId); + } } public static void SendChannelMessage(string message, string channelId) diff --git a/SysBot.Pokemon.Dodo/PokemonProcessService.cs b/SysBot.Pokemon.Dodo/PokemonProcessService.cs index 80b7c8891..8898bfa26 100644 --- a/SysBot.Pokemon.Dodo/PokemonProcessService.cs +++ b/SysBot.Pokemon.Dodo/PokemonProcessService.cs @@ -14,7 +14,7 @@ namespace SysBot.Pokemon.Dodo { private readonly OpenApiService _openApiService; private static readonly string LogIdentity = "DodoBot"; - private static readonly string Welcome = "at我并尝试对我说:\n头目异色怕寂寞性格98级母怪力\n飞羽球六尾阿罗拉的样子形态\n未知图腾Y形态"; + private static readonly string Welcome = "at我并尝试对我说:\n皮卡丘\ntrade ps代码\n或者直接拖一个文件进来"; private readonly string _channelId; private readonly string _botDodoId; From 0f719594ea361da5fd4a5b2923db3ead9285b13c Mon Sep 17 00:00:00 2001 From: Easy World Date: Sun, 1 Jan 2023 22:07:57 +0800 Subject: [PATCH 047/171] Fix missing project include --- SysBot.Pokemon.ConsoleApp/SysBot.Pokemon.ConsoleApp.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/SysBot.Pokemon.ConsoleApp/SysBot.Pokemon.ConsoleApp.csproj b/SysBot.Pokemon.ConsoleApp/SysBot.Pokemon.ConsoleApp.csproj index 054f29c85..f5d22692b 100644 --- a/SysBot.Pokemon.ConsoleApp/SysBot.Pokemon.ConsoleApp.csproj +++ b/SysBot.Pokemon.ConsoleApp/SysBot.Pokemon.ConsoleApp.csproj @@ -11,6 +11,7 @@ + From 7039a0c2b203760e7bae5f1193637d524197e12d Mon Sep 17 00:00:00 2001 From: Easy World Date: Wed, 4 Jan 2023 23:34:50 +0800 Subject: [PATCH 048/171] Update ShowdownTranslator.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 添加控制体型功能 使用指令: 体型XXXL/XXL/XL/L/AV/S/XS/XXS/XXXS ## 增加LA、SV全回忆技能功能 SV全技能输入指令: 全技能 全招式 Co-Authored-By: Eelen <37260264+ppllouf@users.noreply.github.com> --- SysBot.Pokemon/Helpers/ShowdownTranslator.cs | 174 +++++++++++++++++-- 1 file changed, 163 insertions(+), 11 deletions(-) diff --git a/SysBot.Pokemon/Helpers/ShowdownTranslator.cs b/SysBot.Pokemon/Helpers/ShowdownTranslator.cs index 79c46cb0f..7be218c30 100644 --- a/SysBot.Pokemon/Helpers/ShowdownTranslator.cs +++ b/SysBot.Pokemon/Helpers/ShowdownTranslator.cs @@ -55,12 +55,12 @@ public static string Chinese2Showdown(string zh) { result += $"-Paldea-Fire"; zh = zh.Replace("帕底亚的样子(火)形态", ""); - } + } else if (zh.Contains("帕底亚的样子(水)形态")) { result += $"-Paldea-Water"; zh = zh.Replace("帕底亚的样子(水)形态", ""); - } + } else { for (int i = 0; i < GameStringsZh.forms.Length; i++) @@ -72,7 +72,6 @@ public static string Chinese2Showdown(string zh) break; } } - // 添加性别 if (zh.Contains("公")) @@ -187,25 +186,40 @@ public static string Chinese2Showdown(string zh) } // 添加个体值 - if (zh.ToUpper().Contains("6V")) + if (zh.ToUpper().Contains("6V"))//默认 { result += "\nIVs: 31 HP / 31 Atk / 31 Def / 31 SpA / 31 SpD / 31 Spe"; - zh = zh.Replace("6V", ""); + zh = Regex.Replace(zh, "6V|6v", ""); } else if (zh.ToUpper().Contains("5V0A")) { result += "\nIVs: 31 HP / 0 Atk / 31 Def / 31 SpA / 31 SpD / 31 Spe"; - zh = zh.Replace("5V0A", ""); + zh = Regex.Replace(zh, "5V0A|5v0a", ""); + } + else if (zh.ToUpper().Contains("5V0攻")) + { + result += "\nIVs: 31 HP / 0 Atk / 31 Def / 31 SpA / 31 SpD / 31 Spe"; + zh = Regex.Replace(zh, "5V0攻|5v0攻", ""); } else if (zh.ToUpper().Contains("5V0S")) { result += "\nIVs: 31 HP / 31 Atk / 31 Def / 31 SpA / 31 SpD / 0 Spe"; - zh = zh.Replace("5V0S", ""); + zh = Regex.Replace(zh, "5V0S|5v0s", ""); + } + else if (zh.ToUpper().Contains("5V0速")) + { + result += "\nIVs: 31 HP / 31 Atk / 31 Def / 31 SpA / 31 SpD / 0 Spe"; + zh = Regex.Replace(zh, "5V0速|5v0速", ""); } else if (zh.ToUpper().Contains("4V0A0S")) { result += "\nIVs: 31 HP / 0 Atk / 31 Def / 31 SpA / 31 SpD / 0 Spe"; - zh = zh.Replace("4V0A0S", ""); + zh = Regex.Replace(zh, "4V0A0S|4v0a0s", ""); + } + else if (zh.ToUpper().Contains("4V0攻0速")) + { + result += "\nIVs: 31 HP / 0 Atk / 31 Def / 31 SpA / 31 SpD / 0 Spe"; + zh = Regex.Replace(zh, "4V0攻0速|4v0攻0速", ""); } // 添加努力值 @@ -308,20 +322,158 @@ public static string Chinese2Showdown(string zh) } } + //体型大小并添加证章 + if (typeof(T) == typeof(PK9) && zh.Contains("体型")) + { + if (zh.Contains("XXXL"))//255 + { + result += $"\n.Scale=255\n.RibbonMarkJumbo=True"; + zh = zh.Replace("XXXL", ""); + } + else if (zh.Contains("XXL"))//242-254 + { + result += $"\n.Scale=$242,254"; + zh = zh.Replace("XXL", ""); + } + else if (zh.Contains("XL"))//196-241 + { + result += $"\n.Scale=$196,241"; + zh = zh.Replace("XL", ""); + } + else if (zh.Contains("L"))//161-195 + { + result += $"\n.Scale=$161,195"; + zh = zh.Replace("L", ""); + } + else if (zh.Contains("AV"))//100-160 + { + result += $"\n.Scale=$100,160"; + zh = zh.Replace("AV", ""); + } + else if (zh.Contains("S"))//61-99 + { + result += $"\n.Scale=$61,99"; + zh = zh.Replace("S", ""); + } + else if (zh.Contains("XS"))//31-60 + { + result += $"\n.Scale=$31,60"; + zh = zh.Replace("XS", ""); + } + else if (zh.Contains("XXS"))//1-30 + { + result += $"\n.Scale=$1,30"; + zh = zh.Replace("XXS", ""); + } + else if (zh.Contains("XXXS"))//0 + { + result += $"\n.Scale=0\n.RibbonMarkMini=True"; + zh = zh.Replace("XXXS", ""); + } + zh = zh.Replace("体型", ""); + } + //补充后天获得的全奖章 + if (typeof(T) == typeof(PK9) && zh.Contains("全奖章")) + { + result += "\n.Ribbons=$suggestAll\n.RibbonMarkPartner=True\n.RibbonMarkGourmand=True"; + zh = zh.Replace("全奖章", ""); + } + //为野生宝可梦添加证章 + //if (typeof(T) == typeof(PK9) && zh.Contains("野生"))(此条为其他大佬加入野生宝可梦功能时启用) + if (typeof(T) == typeof(PK9)) + { + if (zh.Contains("未知之证")) { result += "\n.RibbonMarkRare=True"; } + else if (zh.Contains("大个子之证")) { result += "\n.Scale=255\n.RibbonMarkJumbo=True"; } + else if (zh.Contains("小不点之证")) { result += "\n.Scale=0\n.RibbonMarkMini=True"; } + else if (zh.Contains("命运之证")) { result += "\n.RibbonMarkDestiny=True"; } + else if (zh.Contains("暴雪之证")) { result += "\n.RibbonMarkBlizzard=True"; } + else if (zh.Contains("阴云之证")) { result += "\n.RibbonMarkCloudy=True"; } + else if (zh.Contains("正午之证")) { result += "\n.RibbonMarkLunchtime=True"; } + else if (zh.Contains("浓雾之证")) { result += "\n.RibbonMarkMisty=True"; } + else if (zh.Contains("降雨之证")) { result += "\n.RibbonMarkRainy=True"; } + else if (zh.Contains("沙尘之证")) { result += "\n.RibbonMarkSandstorm=True"; } + else if (zh.Contains("午夜之证")) { result += "\n.RibbonMarkSleepyTime=True"; } + else if (zh.Contains("降雪之证")) { result += "\n.RibbonMarkSnowy=True"; } + else if (zh.Contains("落雷之证")) { result += "\n.RibbonMarkStormy=True"; } + else if (zh.Contains("干燥之证")) { result += "\n.RibbonMarkDry=True"; } + else if (zh.Contains("黄昏之证")) { result += "\n.RibbonMarkDusk=True"; } + else if (zh.Contains("拂晓之证")) { result += "\n.RibbonMarkDawn=True"; } + else if (zh.Contains("上钩之证")) { result += "\n.RibbonMarkFishing=True"; } + else if (zh.Contains("咖喱之证")) { result += "\n.RibbonMarkCurry=True"; } + else if (zh.Contains("无虑之证")) { result += "\n.RibbonMarkAbsentMinded=True"; } + else if (zh.Contains("愤怒之证")) { result += "\n.RibbonMarkAngry=True"; } + else if (zh.Contains("冷静之证")) { result += "\n.RibbonMarkCalmness=True"; } + else if (zh.Contains("领袖之证")) { result += "\n.RibbonMarkCharismatic=True"; } + else if (zh.Contains("狡猾之证")) { result += "\n.RibbonMarkCrafty=True"; } + else if (zh.Contains("期待之证")) { result += "\n.RibbonMarkExcited=True"; } + else if (zh.Contains("本能之证")) { result += "\n.RibbonMarkFerocious=True"; } + else if (zh.Contains("动摇之证")) { result += "\n.RibbonMarkFlustered=True"; } + else if (zh.Contains("木讷之证")) { result += "\n.RibbonMarkHumble=True"; } + else if (zh.Contains("理性之证")) { result += "\n.RibbonMarkIntellectual=True"; } + else if (zh.Contains("热情之证")) { result += "\n.RibbonMarkIntense=True"; } + else if (zh.Contains("捡拾之证")) { result += "\n.RibbonMarkItemfinder=True"; } + else if (zh.Contains("紧张之证")) { result += "\n.RibbonMarkJittery=True"; } + else if (zh.Contains("幸福之证")) { result += "\n.RibbonMarkJoyful=True"; } + else if (zh.Contains("优雅之证")) { result += "\n.RibbonMarkKindly=True"; } + else if (zh.Contains("激动之证")) { result += "\n.RibbonMarkPeeved=True"; } + else if (zh.Contains("自信之证")) { result += "\n.RibbonMarkPrideful=True"; } + else if (zh.Contains("昂扬之证")) { result += "\n.RibbonMarkPumpedUp=True"; } + else if (zh.Contains("未知之证")) { result += "\n.RibbonMarkRare=True"; } + else if (zh.Contains("淘气之证")) { result += "\n.RibbonMarkRowdy=True"; } + else if (zh.Contains("凶悍之证")) { result += "\n.RibbonMarkScowling=True"; } + else if (zh.Contains("不振之证")) { result += "\n.RibbonMarkSlump=True"; } + else if (zh.Contains("微笑之证")) { result += "\n.RibbonMarkSmiley=True"; } + else if (zh.Contains("悲伤之证")) { result += "\n.RibbonMarkTeary=True"; } + else if (zh.Contains("不纯之证")) { result += "\n.RibbonMarkThorny=True"; } + else if (zh.Contains("偶遇之证")) { result += "\n.RibbonMarkUncommon=True"; } + else if (zh.Contains("自卑之证")) { result += "\n.RibbonMarkUnsure=True"; } + else if (zh.Contains("爽快之证")) { result += "\n.RibbonMarkUpbeat=True"; } + else if (zh.Contains("活力之证")) { result += "\n.RibbonMarkVigor=True"; } + else if (zh.Contains("倦怠之证")) { result += "\n.RibbonMarkZeroEnergy=True"; } + else if (zh.Contains("疏忽之证")) { result += "\n.RibbonMarkZonedOut=True"; } + } + + //添加全回忆技能 + if (typeof(T) == typeof(PK9) || typeof(T) == typeof(PK8)) + { + if (zh.Contains("全技能")) + { + result += "\n.RelearnMoves=$suggestAll"; + zh = zh.Replace("全技能", ""); + + } + else if (zh.Contains("全招式")) + { + result += "\n.RelearnMoves=$suggestAll"; + zh = zh.Replace("全招式", ""); + } + } + if (typeof(T) == typeof(PA8)) + { + if (zh.Contains("全技能")) + { + result += "\n.MoveMastery=$suggestAll"; + zh = zh.Replace("全技能", ""); + } + else if (zh.Contains("全招式")) + { + result += "\n.MoveMastery=$suggestAll"; + zh = zh.Replace("全招式", ""); + } + } + // 添加技能 - zh += "-"; for (int moveCount = 0; moveCount < 4; moveCount++) { for (int i = 0; i < GameStringsZh.Move.Count; i++) { if (GameStringsZh.Move[i].Length == 0) continue; - if (!zh.Contains("-" + GameStringsZh.Move[i] + "-")) continue; + if (!zh.Contains("-" + GameStringsZh.Move[i])) continue; result += $"\n-{GameStringsEn.Move[i]}"; zh = zh.Replace("-" + GameStringsZh.Move[i], ""); break; } } - zh = zh.Replace("-", ""); return result; } From 5b97fa3a309d866dbcea437b1a1c8a26f0f86508 Mon Sep 17 00:00:00 2001 From: Easy World Date: Thu, 5 Jan 2023 02:43:20 +0800 Subject: [PATCH 049/171] File trade update may cause invalid pokemon in older pkhex.core version --- SysBot.Pokemon.Dodo/DodoHelper.cs | 53 ++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/SysBot.Pokemon.Dodo/DodoHelper.cs b/SysBot.Pokemon.Dodo/DodoHelper.cs index bafffa028..1f99a61df 100644 --- a/SysBot.Pokemon.Dodo/DodoHelper.cs +++ b/SysBot.Pokemon.Dodo/DodoHelper.cs @@ -19,10 +19,22 @@ public static void StartTrade(string ps, string dodoId, string nickName, string return; } - StartTrade(pkm, dodoId, nickName, channelId); + StartTradeWithoutCheck(pkm, dodoId, nickName, channelId); } public static void StartTrade(T pkm, string dodoId, string nickName, string channelId) + { + var _ = CheckPkm(pkm, dodoId, out var msg); + if (!_) + { + DodoBot.SendChannelMessage(msg, channelId); + return; + } + + StartTradeWithoutCheck(pkm, dodoId, nickName, channelId); + } + + public static void StartTradeWithoutCheck(T pkm, string dodoId, string nickName, string channelId) { var code = DodoBot.Info.GetRandomTradeCode(); var __ = AddToTradeQueue(pkm, code, ulong.Parse(dodoId), nickName, channelId, @@ -38,6 +50,45 @@ public static void StartDump(string dodoId, string nickName, string channelId) DodoBot.SendChannelMessage(message, channelId); } + public static bool CheckPkm(T pkm, string username, out string msg) + { + if (!DodoBot.Info.GetCanQueue()) + { + msg = "对不起, 我不再接受队列请求!"; + return false; + } + try + { + if (!pkm.CanBeTraded()) + { + msg = $"取消派送, <@!{username}>: 官方禁止该宝可梦交易!"; + return false; + } + + if (pkm is T pk) + { + var valid = new LegalityAnalysis(pkm).Valid; + if (valid) + { + msg = + $"<@!{username}> - 已加入等待队列. 如果你选宝可梦的速度太慢,你的派送请求将被取消!"; + return true; + } + } + + var reason = "我没办法创造非法宝可梦"; + msg = $"<@!{username}>: {reason}"; + } +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception ex) +#pragma warning restore CA1031 // Do not catch general exception types + { + LogUtil.LogSafe(ex, nameof(DodoBot)); + msg = $"取消派送, <@!{username}>: 发生了一个错误"; + } + + return false; + } public static bool CheckAndGetPkm(string setstring, string username, out string msg, out T outPkm) { From dcb42c812831dbc6897d49266491816e44351a76 Mon Sep 17 00:00:00 2001 From: Easy World Date: Fri, 6 Jan 2023 00:02:16 +0800 Subject: [PATCH 050/171] Fix default value of variety resources --- SysBot.NET.sln | 16 +++++++++++++++- SysBot.Pokemon/Helpers/ShowdownTranslator.cs | 10 +++++----- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/SysBot.NET.sln b/SysBot.NET.sln index b2a429f9d..82ca38204 100644 --- a/SysBot.NET.sln +++ b/SysBot.NET.sln @@ -35,7 +35,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SysBot.Pokemon.Bilibili", " {850BECFD-3789-4C07-885E-0AA108C46F60} = {850BECFD-3789-4C07-885E-0AA108C46F60} EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SysBot.Pokemon.Dodo", "SysBot.Pokemon.Dodo\SysBot.Pokemon.Dodo.csproj", "{BE43311D-4571-466E-B3CB-3FD658EFC159}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SysBot.Pokemon.Dodo", "SysBot.Pokemon.Dodo\SysBot.Pokemon.Dodo.csproj", "{BE43311D-4571-466E-B3CB-3FD658EFC159}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SysBot.Pokemon.Kook", "SysBot.Pokemon.Kook\SysBot.Pokemon.Kook.csproj", "{BDD41990-5801-4C75-A08C-C248BC627D3E}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -187,6 +189,18 @@ Global {BE43311D-4571-466E-B3CB-3FD658EFC159}.Release|x64.Build.0 = Release|Any CPU {BE43311D-4571-466E-B3CB-3FD658EFC159}.Release|x86.ActiveCfg = Release|Any CPU {BE43311D-4571-466E-B3CB-3FD658EFC159}.Release|x86.Build.0 = Release|Any CPU + {BDD41990-5801-4C75-A08C-C248BC627D3E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BDD41990-5801-4C75-A08C-C248BC627D3E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BDD41990-5801-4C75-A08C-C248BC627D3E}.Debug|x64.ActiveCfg = Debug|Any CPU + {BDD41990-5801-4C75-A08C-C248BC627D3E}.Debug|x64.Build.0 = Debug|Any CPU + {BDD41990-5801-4C75-A08C-C248BC627D3E}.Debug|x86.ActiveCfg = Debug|Any CPU + {BDD41990-5801-4C75-A08C-C248BC627D3E}.Debug|x86.Build.0 = Debug|Any CPU + {BDD41990-5801-4C75-A08C-C248BC627D3E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BDD41990-5801-4C75-A08C-C248BC627D3E}.Release|Any CPU.Build.0 = Release|Any CPU + {BDD41990-5801-4C75-A08C-C248BC627D3E}.Release|x64.ActiveCfg = Release|Any CPU + {BDD41990-5801-4C75-A08C-C248BC627D3E}.Release|x64.Build.0 = Release|Any CPU + {BDD41990-5801-4C75-A08C-C248BC627D3E}.Release|x86.ActiveCfg = Release|Any CPU + {BDD41990-5801-4C75-A08C-C248BC627D3E}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/SysBot.Pokemon/Helpers/ShowdownTranslator.cs b/SysBot.Pokemon/Helpers/ShowdownTranslator.cs index 7be218c30..1cd1bc86d 100644 --- a/SysBot.Pokemon/Helpers/ShowdownTranslator.cs +++ b/SysBot.Pokemon/Helpers/ShowdownTranslator.cs @@ -14,7 +14,7 @@ public static string Chinese2Showdown(string zh) // 添加宝可梦 int candidateSpecieNo = 0; int candidateSpecieStringLength = 0; - for (int i = 0; i < GameStringsZh.Species.Count; i++) + for (int i = 1; i < GameStringsZh.Species.Count; i++) { if (zh.Contains(GameStringsZh.Species[i]) && GameStringsZh.Species[i].Length > candidateSpecieStringLength) { @@ -88,7 +88,7 @@ public static string Chinese2Showdown(string zh) // 添加持有物 if (zh.Contains("持有")) { - for (int i = 0; i < GameStringsZh.Item.Count; i++) + for (int i = 1; i < GameStringsZh.Item.Count; i++) { if (GameStringsZh.Item[i].Length == 0) continue; if (!zh.Contains("持有" + GameStringsZh.Item[i])) continue; @@ -99,7 +99,7 @@ public static string Chinese2Showdown(string zh) } else if (zh.Contains("携带")) { - for (int i = 0; i < GameStringsZh.Item.Count; i++) + for (int i = 1; i < GameStringsZh.Item.Count; i++) { if (GameStringsZh.Item[i].Length == 0) continue; if (!zh.Contains("携带" + GameStringsZh.Item[i])) continue; @@ -154,7 +154,7 @@ public static string Chinese2Showdown(string zh) } // 添加球种 - for (int i = 0; i < GameStringsZh.balllist.Length; i++) + for (int i = 1; i < GameStringsZh.balllist.Length; i++) { if (GameStringsZh.balllist[i].Length == 0) continue; if (!zh.Contains(GameStringsZh.balllist[i])) continue; @@ -166,7 +166,7 @@ public static string Chinese2Showdown(string zh) } // 添加特性 - for (int i = 0; i < GameStringsZh.Ability.Count; i++) + for (int i = 1; i < GameStringsZh.Ability.Count; i++) { if (GameStringsZh.Ability[i].Length == 0) continue; if (!zh.Contains(GameStringsZh.Ability[i] + "特性")) continue; From cdb0192b0d0ad9242cdc675afa183bd3280977d2 Mon Sep 17 00:00:00 2001 From: Easy World Date: Fri, 6 Jan 2023 00:11:31 +0800 Subject: [PATCH 051/171] Kook init --- SysBot.Pokemon.Kook/KookBot.cs | 46 +++++++++++++++++++ .../SysBot.Pokemon.Kook.csproj | 17 +++++++ .../Settings/Integrations/KookSettings.cs | 17 +++++++ 3 files changed, 80 insertions(+) create mode 100644 SysBot.Pokemon.Kook/KookBot.cs create mode 100644 SysBot.Pokemon.Kook/SysBot.Pokemon.Kook.csproj create mode 100644 SysBot.Pokemon/Settings/Integrations/KookSettings.cs diff --git a/SysBot.Pokemon.Kook/KookBot.cs b/SysBot.Pokemon.Kook/KookBot.cs new file mode 100644 index 000000000..2f181cbe8 --- /dev/null +++ b/SysBot.Pokemon.Kook/KookBot.cs @@ -0,0 +1,46 @@ +using Kook.Rest; +using Kook; +using Kook.WebSocket; +using NLog.Fluent; +using PKHeX.Core; +using System; +using System.Threading.Tasks; +using SysBot.Base; + +namespace SysBot.Pokemon.Kook +{ + public class KookBot where T : PKM, new() + { + private static PokeTradeHub Hub = default!; + internal static TradeQueueInfo Info => Hub.Queues.Info; + + private KookSocketClient _client; + private KookSettings Settings; + + public KookBot(KookSettings settings, PokeTradeHub hub) + { + Hub = hub; + Settings = settings; + + _client = new KookSocketClient(); + + _client.Log += message => + { + LogUtil.LogText($"{message}"); + return Task.CompletedTask; + }; + Task.Run(async () => + { + await _client.LoginAsync(TokenType.Bot, Settings.Token); + await _client.StartAsync(); + _client.Connected += () => + { + LogUtil.LogInfo("Kook Bot is connected!", nameof(KookBot)); + return Task.CompletedTask; + }; + //_client.MessageReceived += + }); + + } + } +} diff --git a/SysBot.Pokemon.Kook/SysBot.Pokemon.Kook.csproj b/SysBot.Pokemon.Kook/SysBot.Pokemon.Kook.csproj new file mode 100644 index 000000000..c8914a1ba --- /dev/null +++ b/SysBot.Pokemon.Kook/SysBot.Pokemon.Kook.csproj @@ -0,0 +1,17 @@ + + + + netstandard2.0 + 9 + + + + + + + + + + + + diff --git a/SysBot.Pokemon/Settings/Integrations/KookSettings.cs b/SysBot.Pokemon/Settings/Integrations/KookSettings.cs new file mode 100644 index 000000000..94ff7bfc8 --- /dev/null +++ b/SysBot.Pokemon/Settings/Integrations/KookSettings.cs @@ -0,0 +1,17 @@ +using System; +using System.ComponentModel; +using System.Linq; + +namespace SysBot.Pokemon +{ + public class KookSettings + { + private const string Startup = nameof(Startup); + + public override string ToString() => "Kook Integration Settings"; + + // Startup + [Category(Startup), Description("机器人鉴权Token")] + public string Token { get; set; } = string.Empty; + } +} \ No newline at end of file From 66ca39b769f3257e92aa1117951607046926af7f Mon Sep 17 00:00:00 2001 From: Easy World Date: Wed, 11 Jan 2023 23:42:06 +0800 Subject: [PATCH 052/171] Remove useless code --- SysBot.Pokemon/Helpers/ShowdownTranslator.cs | 128 ++----------------- 1 file changed, 8 insertions(+), 120 deletions(-) diff --git a/SysBot.Pokemon/Helpers/ShowdownTranslator.cs b/SysBot.Pokemon/Helpers/ShowdownTranslator.cs index 1cd1bc86d..0ae6173a8 100644 --- a/SysBot.Pokemon/Helpers/ShowdownTranslator.cs +++ b/SysBot.Pokemon/Helpers/ShowdownTranslator.cs @@ -322,115 +322,16 @@ public static string Chinese2Showdown(string zh) } } - //体型大小并添加证章 - if (typeof(T) == typeof(PK9) && zh.Contains("体型")) + // 体型大小并添加证章 + if (typeof(T) == typeof(PK9) && zh.Contains("大个子")) { - if (zh.Contains("XXXL"))//255 - { - result += $"\n.Scale=255\n.RibbonMarkJumbo=True"; - zh = zh.Replace("XXXL", ""); - } - else if (zh.Contains("XXL"))//242-254 - { - result += $"\n.Scale=$242,254"; - zh = zh.Replace("XXL", ""); - } - else if (zh.Contains("XL"))//196-241 - { - result += $"\n.Scale=$196,241"; - zh = zh.Replace("XL", ""); - } - else if (zh.Contains("L"))//161-195 - { - result += $"\n.Scale=$161,195"; - zh = zh.Replace("L", ""); - } - else if (zh.Contains("AV"))//100-160 - { - result += $"\n.Scale=$100,160"; - zh = zh.Replace("AV", ""); - } - else if (zh.Contains("S"))//61-99 - { - result += $"\n.Scale=$61,99"; - zh = zh.Replace("S", ""); - } - else if (zh.Contains("XS"))//31-60 - { - result += $"\n.Scale=$31,60"; - zh = zh.Replace("XS", ""); - } - else if (zh.Contains("XXS"))//1-30 - { - result += $"\n.Scale=$1,30"; - zh = zh.Replace("XXS", ""); - } - else if (zh.Contains("XXXS"))//0 - { - result += $"\n.Scale=0\n.RibbonMarkMini=True"; - zh = zh.Replace("XXXS", ""); - } - zh = zh.Replace("体型", ""); - } - //补充后天获得的全奖章 - if (typeof(T) == typeof(PK9) && zh.Contains("全奖章")) - { - result += "\n.Ribbons=$suggestAll\n.RibbonMarkPartner=True\n.RibbonMarkGourmand=True"; - zh = zh.Replace("全奖章", ""); - } - //为野生宝可梦添加证章 - //if (typeof(T) == typeof(PK9) && zh.Contains("野生"))(此条为其他大佬加入野生宝可梦功能时启用) - if (typeof(T) == typeof(PK9)) + result += $"\n.Scale=255\n.RibbonMarkJumbo=True"; + zh = zh.Replace("大个子", ""); + } + else if (typeof(T) == typeof(PK9) && zh.Contains("小不点")) { - if (zh.Contains("未知之证")) { result += "\n.RibbonMarkRare=True"; } - else if (zh.Contains("大个子之证")) { result += "\n.Scale=255\n.RibbonMarkJumbo=True"; } - else if (zh.Contains("小不点之证")) { result += "\n.Scale=0\n.RibbonMarkMini=True"; } - else if (zh.Contains("命运之证")) { result += "\n.RibbonMarkDestiny=True"; } - else if (zh.Contains("暴雪之证")) { result += "\n.RibbonMarkBlizzard=True"; } - else if (zh.Contains("阴云之证")) { result += "\n.RibbonMarkCloudy=True"; } - else if (zh.Contains("正午之证")) { result += "\n.RibbonMarkLunchtime=True"; } - else if (zh.Contains("浓雾之证")) { result += "\n.RibbonMarkMisty=True"; } - else if (zh.Contains("降雨之证")) { result += "\n.RibbonMarkRainy=True"; } - else if (zh.Contains("沙尘之证")) { result += "\n.RibbonMarkSandstorm=True"; } - else if (zh.Contains("午夜之证")) { result += "\n.RibbonMarkSleepyTime=True"; } - else if (zh.Contains("降雪之证")) { result += "\n.RibbonMarkSnowy=True"; } - else if (zh.Contains("落雷之证")) { result += "\n.RibbonMarkStormy=True"; } - else if (zh.Contains("干燥之证")) { result += "\n.RibbonMarkDry=True"; } - else if (zh.Contains("黄昏之证")) { result += "\n.RibbonMarkDusk=True"; } - else if (zh.Contains("拂晓之证")) { result += "\n.RibbonMarkDawn=True"; } - else if (zh.Contains("上钩之证")) { result += "\n.RibbonMarkFishing=True"; } - else if (zh.Contains("咖喱之证")) { result += "\n.RibbonMarkCurry=True"; } - else if (zh.Contains("无虑之证")) { result += "\n.RibbonMarkAbsentMinded=True"; } - else if (zh.Contains("愤怒之证")) { result += "\n.RibbonMarkAngry=True"; } - else if (zh.Contains("冷静之证")) { result += "\n.RibbonMarkCalmness=True"; } - else if (zh.Contains("领袖之证")) { result += "\n.RibbonMarkCharismatic=True"; } - else if (zh.Contains("狡猾之证")) { result += "\n.RibbonMarkCrafty=True"; } - else if (zh.Contains("期待之证")) { result += "\n.RibbonMarkExcited=True"; } - else if (zh.Contains("本能之证")) { result += "\n.RibbonMarkFerocious=True"; } - else if (zh.Contains("动摇之证")) { result += "\n.RibbonMarkFlustered=True"; } - else if (zh.Contains("木讷之证")) { result += "\n.RibbonMarkHumble=True"; } - else if (zh.Contains("理性之证")) { result += "\n.RibbonMarkIntellectual=True"; } - else if (zh.Contains("热情之证")) { result += "\n.RibbonMarkIntense=True"; } - else if (zh.Contains("捡拾之证")) { result += "\n.RibbonMarkItemfinder=True"; } - else if (zh.Contains("紧张之证")) { result += "\n.RibbonMarkJittery=True"; } - else if (zh.Contains("幸福之证")) { result += "\n.RibbonMarkJoyful=True"; } - else if (zh.Contains("优雅之证")) { result += "\n.RibbonMarkKindly=True"; } - else if (zh.Contains("激动之证")) { result += "\n.RibbonMarkPeeved=True"; } - else if (zh.Contains("自信之证")) { result += "\n.RibbonMarkPrideful=True"; } - else if (zh.Contains("昂扬之证")) { result += "\n.RibbonMarkPumpedUp=True"; } - else if (zh.Contains("未知之证")) { result += "\n.RibbonMarkRare=True"; } - else if (zh.Contains("淘气之证")) { result += "\n.RibbonMarkRowdy=True"; } - else if (zh.Contains("凶悍之证")) { result += "\n.RibbonMarkScowling=True"; } - else if (zh.Contains("不振之证")) { result += "\n.RibbonMarkSlump=True"; } - else if (zh.Contains("微笑之证")) { result += "\n.RibbonMarkSmiley=True"; } - else if (zh.Contains("悲伤之证")) { result += "\n.RibbonMarkTeary=True"; } - else if (zh.Contains("不纯之证")) { result += "\n.RibbonMarkThorny=True"; } - else if (zh.Contains("偶遇之证")) { result += "\n.RibbonMarkUncommon=True"; } - else if (zh.Contains("自卑之证")) { result += "\n.RibbonMarkUnsure=True"; } - else if (zh.Contains("爽快之证")) { result += "\n.RibbonMarkUpbeat=True"; } - else if (zh.Contains("活力之证")) { result += "\n.RibbonMarkVigor=True"; } - else if (zh.Contains("倦怠之证")) { result += "\n.RibbonMarkZeroEnergy=True"; } - else if (zh.Contains("疏忽之证")) { result += "\n.RibbonMarkZonedOut=True"; } + result += $"\n.Scale=0\n.RibbonMarkMini=True"; + zh = zh.Replace("小不点", ""); } //添加全回忆技能 @@ -462,19 +363,6 @@ public static string Chinese2Showdown(string zh) } } - // 添加技能 - for (int moveCount = 0; moveCount < 4; moveCount++) - { - for (int i = 0; i < GameStringsZh.Move.Count; i++) - { - if (GameStringsZh.Move[i].Length == 0) continue; - if (!zh.Contains("-" + GameStringsZh.Move[i])) continue; - result += $"\n-{GameStringsEn.Move[i]}"; - zh = zh.Replace("-" + GameStringsZh.Move[i], ""); - break; - } - } - return result; } } From cfa6f4f4c6178d4811848aa21feb0462ad6aaf69 Mon Sep 17 00:00:00 2001 From: Easy World Date: Fri, 27 Jan 2023 12:56:22 +0800 Subject: [PATCH 053/171] Send tid&sid when connected --- SysBot.Pokemon.Discord/SysBot.Pokemon.Discord.csproj | 1 - SysBot.Pokemon.Dodo/DodoTradeNotifier.cs | 9 +++++++++ SysBot.Pokemon.Twitch/SysBot.Pokemon.Twitch.csproj | 1 - SysBot.Pokemon.WinForms/SysBot.Pokemon.WinForms.csproj | 1 - SysBot.Pokemon.YouTube/SysBot.Pokemon.YouTube.csproj | 1 - SysBot.Pokemon.Z3/SysBot.Pokemon.Z3.csproj | 1 - SysBot.Pokemon/Helpers/ShowdownTranslator.cs | 6 ++++++ SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs | 2 +- SysBot.Pokemon/SysBot.Pokemon.csproj | 2 +- 9 files changed, 17 insertions(+), 7 deletions(-) diff --git a/SysBot.Pokemon.Discord/SysBot.Pokemon.Discord.csproj b/SysBot.Pokemon.Discord/SysBot.Pokemon.Discord.csproj index b03094db4..cda6c0684 100644 --- a/SysBot.Pokemon.Discord/SysBot.Pokemon.Discord.csproj +++ b/SysBot.Pokemon.Discord/SysBot.Pokemon.Discord.csproj @@ -11,7 +11,6 @@ - diff --git a/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs b/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs index fc89085a3..fa2727b06 100644 --- a/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs +++ b/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs @@ -2,6 +2,7 @@ using SysBot.Base; using System; using System.Linq; +using System.Text.RegularExpressions; namespace SysBot.Pokemon.Dodo { @@ -29,6 +30,14 @@ public DodoTradeNotifier(T data, PokeTradeTrainerInfo info, int code, string use public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, string message) { LogUtil.LogText(message); + if (message.Contains("Found Trading Partner:")) + { + Regex regex = new Regex("TID: (\\d+)"); + string tid = regex.Match(message).Groups[1].ToString(); + regex = new Regex("SID: (\\d+)"); + string sid = regex.Match(message).Groups[1].ToString(); + DodoBot.SendPersonalMessage(info.Trainer.ID.ToString(), $"找到你了,你的SID7:{sid},TID7:{tid}"); + } } public void TradeCanceled(PokeRoutineExecutor routine, PokeTradeDetail info, PokeTradeResult msg) diff --git a/SysBot.Pokemon.Twitch/SysBot.Pokemon.Twitch.csproj b/SysBot.Pokemon.Twitch/SysBot.Pokemon.Twitch.csproj index fe66ad248..1dd96ee20 100644 --- a/SysBot.Pokemon.Twitch/SysBot.Pokemon.Twitch.csproj +++ b/SysBot.Pokemon.Twitch/SysBot.Pokemon.Twitch.csproj @@ -8,7 +8,6 @@ - diff --git a/SysBot.Pokemon.WinForms/SysBot.Pokemon.WinForms.csproj b/SysBot.Pokemon.WinForms/SysBot.Pokemon.WinForms.csproj index 84f4bb4df..b0093cbc7 100644 --- a/SysBot.Pokemon.WinForms/SysBot.Pokemon.WinForms.csproj +++ b/SysBot.Pokemon.WinForms/SysBot.Pokemon.WinForms.csproj @@ -21,7 +21,6 @@ - diff --git a/SysBot.Pokemon.YouTube/SysBot.Pokemon.YouTube.csproj b/SysBot.Pokemon.YouTube/SysBot.Pokemon.YouTube.csproj index 0ea1dae50..8156a6233 100644 --- a/SysBot.Pokemon.YouTube/SysBot.Pokemon.YouTube.csproj +++ b/SysBot.Pokemon.YouTube/SysBot.Pokemon.YouTube.csproj @@ -13,7 +13,6 @@ - diff --git a/SysBot.Pokemon.Z3/SysBot.Pokemon.Z3.csproj b/SysBot.Pokemon.Z3/SysBot.Pokemon.Z3.csproj index 1a05cd994..ea2abb1c4 100644 --- a/SysBot.Pokemon.Z3/SysBot.Pokemon.Z3.csproj +++ b/SysBot.Pokemon.Z3/SysBot.Pokemon.Z3.csproj @@ -8,7 +8,6 @@ - diff --git a/SysBot.Pokemon/Helpers/ShowdownTranslator.cs b/SysBot.Pokemon/Helpers/ShowdownTranslator.cs index 0ae6173a8..c248997c0 100644 --- a/SysBot.Pokemon/Helpers/ShowdownTranslator.cs +++ b/SysBot.Pokemon/Helpers/ShowdownTranslator.cs @@ -363,6 +363,12 @@ public static string Chinese2Showdown(string zh) } } + if (candidateSpecieNo == (int)Species.Ditto && zh.Contains("异国")) + { + result += "\nLanguage: Italian"; + zh = zh.Replace("异国", ""); + } + return result; } } diff --git a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs index 15af52c57..3fa1a35f3 100644 --- a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs +++ b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs @@ -363,7 +363,7 @@ private async Task PerformLinkCodeTrade(SAV9SV sav, PokeTradeDe return PokeTradeResult.TrainerTooSlow; } - poke.SendNotification(this, $"Found Link Trade partner: {tradePartner.TrainerName}. Waiting for a Pokémon..."); + poke.SendNotification(this, $"Found Trading Partner: {tradePartner.TrainerName}. TID: {tradePartner.TID7} SID: {tradePartner.SID7} Waiting for a Pokémon..."); if (poke.Type == PokeTradeType.Dump) { diff --git a/SysBot.Pokemon/SysBot.Pokemon.csproj b/SysBot.Pokemon/SysBot.Pokemon.csproj index 01b5a195a..22c759fef 100644 --- a/SysBot.Pokemon/SysBot.Pokemon.csproj +++ b/SysBot.Pokemon/SysBot.Pokemon.csproj @@ -8,7 +8,7 @@ - + From add9aa1091a55656d05ff386b9820158a52229eb Mon Sep 17 00:00:00 2001 From: Easy World Date: Fri, 27 Jan 2023 13:23:53 +0800 Subject: [PATCH 054/171] .net 7 --- SysBot.Pokemon.Bilibili/SysBot.Pokemon.Bilibili.csproj | 4 ++-- SysBot.Pokemon.ConsoleApp/SysBot.Pokemon.ConsoleApp.csproj | 4 ++-- SysBot.Pokemon.Discord/SysBot.Pokemon.Discord.csproj | 4 ++-- SysBot.Pokemon.Dodo/SysBot.Pokemon.Dodo.csproj | 4 ++-- SysBot.Pokemon.Kook/SysBot.Pokemon.Kook.csproj | 4 ++-- SysBot.Pokemon.QQ/SysBot.Pokemon.QQ.csproj | 4 ++-- SysBot.Pokemon.Twitch/SysBot.Pokemon.Twitch.csproj | 4 ++-- SysBot.Pokemon.WinForms/SysBot.Pokemon.WinForms.csproj | 4 ++-- SysBot.Pokemon.YouTube/SysBot.Pokemon.YouTube.csproj | 4 ++-- SysBot.Pokemon.Z3/SysBot.Pokemon.Z3.csproj | 4 ++-- SysBot.Pokemon/LA/PokeRoutineExecutor8LA.cs | 2 +- SysBot.Pokemon/SV/PokeRoutineExecutor9SV.cs | 2 +- SysBot.Pokemon/SysBot.Pokemon.csproj | 4 ++-- SysBot.Tests/SysBot.Tests.csproj | 4 ++-- 14 files changed, 26 insertions(+), 26 deletions(-) diff --git a/SysBot.Pokemon.Bilibili/SysBot.Pokemon.Bilibili.csproj b/SysBot.Pokemon.Bilibili/SysBot.Pokemon.Bilibili.csproj index 402730e08..e92e81033 100644 --- a/SysBot.Pokemon.Bilibili/SysBot.Pokemon.Bilibili.csproj +++ b/SysBot.Pokemon.Bilibili/SysBot.Pokemon.Bilibili.csproj @@ -1,9 +1,9 @@  - netstandard2.0 + net7.0 AnyCPU enable - 9 + 11 diff --git a/SysBot.Pokemon.ConsoleApp/SysBot.Pokemon.ConsoleApp.csproj b/SysBot.Pokemon.ConsoleApp/SysBot.Pokemon.ConsoleApp.csproj index f5d22692b..763a4078e 100644 --- a/SysBot.Pokemon.ConsoleApp/SysBot.Pokemon.ConsoleApp.csproj +++ b/SysBot.Pokemon.ConsoleApp/SysBot.Pokemon.ConsoleApp.csproj @@ -2,8 +2,8 @@ Exe - net6.0;net47 - 9 + net7.0 + 11 enable x64 SysBot.Pokemon.ConsoleApp.Program diff --git a/SysBot.Pokemon.Discord/SysBot.Pokemon.Discord.csproj b/SysBot.Pokemon.Discord/SysBot.Pokemon.Discord.csproj index cda6c0684..5a3746589 100644 --- a/SysBot.Pokemon.Discord/SysBot.Pokemon.Discord.csproj +++ b/SysBot.Pokemon.Discord/SysBot.Pokemon.Discord.csproj @@ -1,8 +1,8 @@  - netstandard2.0;net47 - 9 + net7.0 + 11 enable AnyCPU diff --git a/SysBot.Pokemon.Dodo/SysBot.Pokemon.Dodo.csproj b/SysBot.Pokemon.Dodo/SysBot.Pokemon.Dodo.csproj index 7cdf36920..248f9872a 100644 --- a/SysBot.Pokemon.Dodo/SysBot.Pokemon.Dodo.csproj +++ b/SysBot.Pokemon.Dodo/SysBot.Pokemon.Dodo.csproj @@ -1,8 +1,8 @@ - netstandard2.0 - 9 + net7.0 + 11 diff --git a/SysBot.Pokemon.Kook/SysBot.Pokemon.Kook.csproj b/SysBot.Pokemon.Kook/SysBot.Pokemon.Kook.csproj index c8914a1ba..dbb529dc8 100644 --- a/SysBot.Pokemon.Kook/SysBot.Pokemon.Kook.csproj +++ b/SysBot.Pokemon.Kook/SysBot.Pokemon.Kook.csproj @@ -1,8 +1,8 @@  - netstandard2.0 - 9 + net7.0 + 11 diff --git a/SysBot.Pokemon.QQ/SysBot.Pokemon.QQ.csproj b/SysBot.Pokemon.QQ/SysBot.Pokemon.QQ.csproj index 0dae64136..63a6b4516 100644 --- a/SysBot.Pokemon.QQ/SysBot.Pokemon.QQ.csproj +++ b/SysBot.Pokemon.QQ/SysBot.Pokemon.QQ.csproj @@ -1,10 +1,10 @@ - netstandard2.0 + net7.0 AnyCPU enable - 9 + 11 diff --git a/SysBot.Pokemon.Twitch/SysBot.Pokemon.Twitch.csproj b/SysBot.Pokemon.Twitch/SysBot.Pokemon.Twitch.csproj index 1dd96ee20..85d011ebe 100644 --- a/SysBot.Pokemon.Twitch/SysBot.Pokemon.Twitch.csproj +++ b/SysBot.Pokemon.Twitch/SysBot.Pokemon.Twitch.csproj @@ -1,10 +1,10 @@  - netstandard2.0 + net7.0 AnyCPU enable - 9 + 11 diff --git a/SysBot.Pokemon.WinForms/SysBot.Pokemon.WinForms.csproj b/SysBot.Pokemon.WinForms/SysBot.Pokemon.WinForms.csproj index b0093cbc7..22163482a 100644 --- a/SysBot.Pokemon.WinForms/SysBot.Pokemon.WinForms.csproj +++ b/SysBot.Pokemon.WinForms/SysBot.Pokemon.WinForms.csproj @@ -2,7 +2,7 @@ WinExe - net47;net6.0-windows + net7.0-windows true SysBot.Pokemon.WinForms Project Pokémon @@ -12,7 +12,7 @@ icon.ico SysBot.Pokemon.WinForms.Program SysBot - 9 + 11 01.00.00 enable x64 diff --git a/SysBot.Pokemon.YouTube/SysBot.Pokemon.YouTube.csproj b/SysBot.Pokemon.YouTube/SysBot.Pokemon.YouTube.csproj index 8156a6233..07dab3d03 100644 --- a/SysBot.Pokemon.YouTube/SysBot.Pokemon.YouTube.csproj +++ b/SysBot.Pokemon.YouTube/SysBot.Pokemon.YouTube.csproj @@ -1,10 +1,10 @@  - netstandard2.0 + net7.0 AnyCPU enable - 9 + 11 diff --git a/SysBot.Pokemon.Z3/SysBot.Pokemon.Z3.csproj b/SysBot.Pokemon.Z3/SysBot.Pokemon.Z3.csproj index ea2abb1c4..6b61abaf7 100644 --- a/SysBot.Pokemon.Z3/SysBot.Pokemon.Z3.csproj +++ b/SysBot.Pokemon.Z3/SysBot.Pokemon.Z3.csproj @@ -1,8 +1,8 @@  - netstandard2.0; - 9 + net7.0 + 11 enable x64;x86 diff --git a/SysBot.Pokemon/LA/PokeRoutineExecutor8LA.cs b/SysBot.Pokemon/LA/PokeRoutineExecutor8LA.cs index 493893f7e..ce715d997 100644 --- a/SysBot.Pokemon/LA/PokeRoutineExecutor8LA.cs +++ b/SysBot.Pokemon/LA/PokeRoutineExecutor8LA.cs @@ -61,7 +61,7 @@ public async Task SetBoxPokemonAbsolute(ulong offset, PA8 pkm, CancellationToken public async Task SetCurrentBox(byte box, CancellationToken token) { - await SwitchConnection.PointerPoke(BitConverter.GetBytes(box), Offsets.CurrentBoxPointer, token).ConfigureAwait(false); + await SwitchConnection.PointerPoke(BitConverter.GetBytes((short)box), Offsets.CurrentBoxPointer, token).ConfigureAwait(false); } public async Task GetCurrentBox(CancellationToken token) diff --git a/SysBot.Pokemon/SV/PokeRoutineExecutor9SV.cs b/SysBot.Pokemon/SV/PokeRoutineExecutor9SV.cs index 15b33e11f..985cf7419 100644 --- a/SysBot.Pokemon/SV/PokeRoutineExecutor9SV.cs +++ b/SysBot.Pokemon/SV/PokeRoutineExecutor9SV.cs @@ -61,7 +61,7 @@ public async Task SetBoxPokemonAbsolute(ulong offset, PK9 pkm, CancellationToken public async Task SetCurrentBox(byte box, CancellationToken token) { - await SwitchConnection.PointerPoke(BitConverter.GetBytes(box), Offsets.CurrentBoxPointer, token).ConfigureAwait(false); + await SwitchConnection.PointerPoke(BitConverter.GetBytes((short)box), Offsets.CurrentBoxPointer, token).ConfigureAwait(false); } public async Task GetCurrentBox(CancellationToken token) diff --git a/SysBot.Pokemon/SysBot.Pokemon.csproj b/SysBot.Pokemon/SysBot.Pokemon.csproj index 22c759fef..d789b0e21 100644 --- a/SysBot.Pokemon/SysBot.Pokemon.csproj +++ b/SysBot.Pokemon/SysBot.Pokemon.csproj @@ -1,8 +1,8 @@  - netstandard2.0 - 9 + net7.0 + 11 enable AnyCPU diff --git a/SysBot.Tests/SysBot.Tests.csproj b/SysBot.Tests/SysBot.Tests.csproj index 047fca07f..026461dd4 100644 --- a/SysBot.Tests/SysBot.Tests.csproj +++ b/SysBot.Tests/SysBot.Tests.csproj @@ -1,8 +1,8 @@  - net6.0 - 9 + net7.0 + 11 enable false From 4dba3b546c9769629b2c7323ac27d063593dbc69 Mon Sep 17 00:00:00 2001 From: Easy World Date: Fri, 27 Jan 2023 13:47:42 +0800 Subject: [PATCH 055/171] .net7 support --- .../Commands/Extra/BatchEditingModule.cs | 2 +- SysBot.Pokemon/BDSP/PokeRoutineExecutor8BS.cs | 4 ++-- SysBot.Pokemon/Helpers/AutoLegalityWrapper.cs | 4 ++-- SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs | 4 ++-- SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs | 8 ++++---- SysBot.Pokemon/SWSH/BotTrade/PokeTradeBot.cs | 4 ++-- SysBot.Pokemon/SysBot.Pokemon.csproj | 2 +- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/SysBot.Pokemon.Discord/Commands/Extra/BatchEditingModule.cs b/SysBot.Pokemon.Discord/Commands/Extra/BatchEditingModule.cs index 5beac9d6f..d70560ae5 100644 --- a/SysBot.Pokemon.Discord/Commands/Extra/BatchEditingModule.cs +++ b/SysBot.Pokemon.Discord/Commands/Extra/BatchEditingModule.cs @@ -44,7 +44,7 @@ await ReplyAsync($"Invalid Lines Detected:\r\n{Format.Code(string.Join(Environme private static bool IsValidInstructionSet(List split, out List invalid) { invalid = new List(); - var set = new StringInstructionSet(split); + var set = new StringInstructionSet(split.ToArray()); foreach (var s in set.Filters.Concat(set.Instructions)) { var type = BatchEditing.GetPropertyType(s.PropertyName); diff --git a/SysBot.Pokemon/BDSP/PokeRoutineExecutor8BS.cs b/SysBot.Pokemon/BDSP/PokeRoutineExecutor8BS.cs index 3800c2e36..98281826c 100644 --- a/SysBot.Pokemon/BDSP/PokeRoutineExecutor8BS.cs +++ b/SysBot.Pokemon/BDSP/PokeRoutineExecutor8BS.cs @@ -93,8 +93,8 @@ public async Task GetFakeTrainerSAV(CancellationToken token) // Set the TID, SID, and Language var id = await SwitchConnection.PointerPeek(4, Offsets.MyStatusTIDPointer, token).ConfigureAwait(false); - info.TID = BitConverter.ToUInt16(id, 0); - info.SID = BitConverter.ToUInt16(id, 2); + info.TID16 = BitConverter.ToUInt16(id, 0); + info.SID16 = BitConverter.ToUInt16(id, 2); var lang = await SwitchConnection.PointerPeek(1, Offsets.ConfigLanguagePointer, token).ConfigureAwait(false); sav.Language = lang[0]; diff --git a/SysBot.Pokemon/Helpers/AutoLegalityWrapper.cs b/SysBot.Pokemon/Helpers/AutoLegalityWrapper.cs index 637e584ab..48a02437f 100644 --- a/SysBot.Pokemon/Helpers/AutoLegalityWrapper.cs +++ b/SysBot.Pokemon/Helpers/AutoLegalityWrapper.cs @@ -62,8 +62,8 @@ private static void InitializeTrainerDatabase(LegalitySettings cfg) var fallback = new SimpleTrainerInfo(v) { Language = lang, - TID = TID, - SID = SID, + TID16 = (ushort)TID, + SID16 = (ushort)SID, OT = OT, Generation = i, }; diff --git a/SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs b/SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs index 1e270a3ae..7405198da 100644 --- a/SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs +++ b/SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs @@ -831,8 +831,8 @@ private async Task SetBoxPkmWithSwappedIDDetailsPLA(PA8 toSend, TradePartn { var cln = (PA8)toSend.Clone(); cln.OT_Gender = tradePartner.Gender; - cln.TrainerID7 = int.Parse(tradePartner.TID7); - cln.TrainerSID7 = int.Parse(tradePartner.SID7); + cln.TrainerTID7 = uint.Parse(tradePartner.TID7); + cln.TrainerSID7 = uint.Parse(tradePartner.SID7); cln.Language = tradePartner.Language; cln.OT_Name = tradePartner.TrainerName; cln.ClearNickname(); diff --git a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs index 3fa1a35f3..106e7801e 100644 --- a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs +++ b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs @@ -79,8 +79,8 @@ public override async Task MainLoop(CancellationToken token) Log("Identifying trainer data of the host console."); var sav = await IdentifyTrainer(token).ConfigureAwait(false); OT = sav.OT; - DisplaySID = sav.DisplaySID; - DisplayTID = sav.DisplayTID; + DisplaySID = (int)sav.DisplaySID; + DisplayTID = (int)sav.DisplayTID; RecentTrainerCache.SetRecentTrainer(sav); await InitializeSessionOffsets(token).ConfigureAwait(false); @@ -1056,8 +1056,8 @@ private async Task SetBoxPkmWithSwappedIDDetailsSV(PK9 toSend, TradeMyStat } var cln = (PK9)toSend.Clone(); cln.OT_Gender = tradePartner.Gender; - cln.TrainerID7 = (int)Math.Abs(tradePartner.DisplayTID); - cln.TrainerSID7 = (int)Math.Abs(tradePartner.DisplaySID); + cln.TrainerTID7 = (uint)Math.Abs(tradePartner.DisplayTID); + cln.TrainerSID7 = (uint)Math.Abs(tradePartner.DisplaySID); cln.Language = tradePartner.Language; cln.OT_Name = tradePartner.OT; cln.Version = tradePartner.Game; diff --git a/SysBot.Pokemon/SWSH/BotTrade/PokeTradeBot.cs b/SysBot.Pokemon/SWSH/BotTrade/PokeTradeBot.cs index 37fe05336..c77817589 100644 --- a/SysBot.Pokemon/SWSH/BotTrade/PokeTradeBot.cs +++ b/SysBot.Pokemon/SWSH/BotTrade/PokeTradeBot.cs @@ -415,8 +415,8 @@ private async Task SetPkmWithSwappedIDDetails(PK8 toSend, string trainerNa var tidsid = BitConverter.ToUInt32(data, 0); var cln = (PK8)toSend.Clone(); cln.OT_Gender = data[6]; - cln.TrainerID7 = (int)(tidsid % 1_000_000); - cln.TrainerSID7 = (int)(tidsid / 1_000_000); + cln.TrainerTID7 = (uint)(tidsid % 1_000_000); + cln.TrainerSID7 = (uint)(tidsid / 1_000_000); cln.Language = data[5]; cln.OT_Name = trainerName; cln.ClearNickname(); diff --git a/SysBot.Pokemon/SysBot.Pokemon.csproj b/SysBot.Pokemon/SysBot.Pokemon.csproj index d789b0e21..706a06ca8 100644 --- a/SysBot.Pokemon/SysBot.Pokemon.csproj +++ b/SysBot.Pokemon/SysBot.Pokemon.csproj @@ -8,7 +8,7 @@ - + From aa2aa212b389cb1a2972ba679257115478ad6b8d Mon Sep 17 00:00:00 2001 From: Easy World Date: Fri, 27 Jan 2023 14:10:37 +0800 Subject: [PATCH 056/171] Update PKHeX.Core.AutoMod.dll --- SysBot.Pokemon/deps/PKHeX.Core.AutoMod.dll | Bin 130048 -> 121856 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/SysBot.Pokemon/deps/PKHeX.Core.AutoMod.dll b/SysBot.Pokemon/deps/PKHeX.Core.AutoMod.dll index 8a48614f16913a87dacd01c8405ba46b1bd5467f..21d75057abea31b2ce1b268e09e0d498ab78ac0b 100644 GIT binary patch literal 121856 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 O(x3mo8~;Kv@P7fL+kPAX literal 130048 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 Date: Fri, 27 Jan 2023 14:26:40 +0800 Subject: [PATCH 057/171] use PKHeX.Core 23.01.26 --- SysBot.Pokemon/SysBot.Pokemon.csproj | 2 +- SysBot.Pokemon/deps/PKHeX.Core.AutoMod.dll | Bin 121856 -> 121856 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/SysBot.Pokemon/SysBot.Pokemon.csproj b/SysBot.Pokemon/SysBot.Pokemon.csproj index 706a06ca8..6feae08e8 100644 --- a/SysBot.Pokemon/SysBot.Pokemon.csproj +++ b/SysBot.Pokemon/SysBot.Pokemon.csproj @@ -8,7 +8,7 @@ - + diff --git a/SysBot.Pokemon/deps/PKHeX.Core.AutoMod.dll b/SysBot.Pokemon/deps/PKHeX.Core.AutoMod.dll index 21d75057abea31b2ce1b268e09e0d498ab78ac0b..643f43abc5d0034c1189b58679eeeee2a6b3b0ce 100644 GIT binary patch delta 16222 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 delta 16173 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 From 1a9042b04c7840baf0cd727a1e0b42fb9e6991e0 Mon Sep 17 00:00:00 2001 From: Easy World Date: Fri, 27 Jan 2023 15:01:57 +0800 Subject: [PATCH 058/171] Update PKHeX.Core.AutoMod.dll --- SysBot.Pokemon/deps/PKHeX.Core.AutoMod.dll | Bin 121856 -> 121856 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/SysBot.Pokemon/deps/PKHeX.Core.AutoMod.dll b/SysBot.Pokemon/deps/PKHeX.Core.AutoMod.dll index 643f43abc5d0034c1189b58679eeeee2a6b3b0ce..963cd745e3ebdd3a4be43286414d97c1a3c8e1b1 100644 GIT binary patch delta 45598 zcmbTf33wDm*EU?$lj)gE7Lt%{vdx5qFj*i834uU>gdGf!um>Ro6a_?vB!KJ_L=YYU zjjc!&6k&99NL31-Q%6AdjSgODKSB6%#fxI z_p-K(b)0_d)~$^7-^8r5^JD8DcmK)xiS>({gsS|T^~*gqHhyV+qns@keofn(cQ?Pb zzD52oR{qy|SKBU}|Gob1cI^Wc#@D~taiqdWXx2_emJr3F_3GqVKdIAsg;#17U9)@z zv-fc3P`o9GMDeCTdiO%%-3Jkr8LN0d`5$J_Jaj6^mVu7uSl9(5BMBW=Vk)|Z09)}d z9{*l4{c8b?>e&GNd(5OC1MHg#(dK!G+56Fu#d|IEb1Tpf=|jN{sgem@INq z$6w&Z`=xfGTWda33+o;o@)aq9AXJ<8l$O_hV%M)h9O~@P84GCIJSJ`SX84W0_fK#e0UzdJ+w)dhP@FexqIJo=|uWFw3T>o<}f}#yBP;*F7O8oL(ZeMe$r-U(gftbhb95SF-{imJt?;Tb+zW#hb@kmSlyf77?@u%g&LZdVZ!C zWpW+tLAETLP4WDqo#>UUUV+|oRlC)z1^-oRaYrI#=^d$%)pxwv_!^AOBNflnh<8Re z8qI53e(93<-=Me5J|=T{ROHe}x%^!_RXRw$4twpNT4C>0{+CwS+sSWeQ+p?OxB+#V z;#rQ`vLaNyTF*r=uVHzHERnDfaLxAo;J2yXzqRANR|Vcg6(JFd=ax3V&vAt-+K9fL zOB4k&!t)}Wop~W@O;d$U^pY+!L)XTUw!L4A4v}wcUXWT-A+*}Lz8z6lN?Gd`dR=Q^ z&w{x@7V@-5!>Y%ES~J=zF|%5t%Zlf>^$W_za{ilkwS1BjyM@E^0SaPreuG?jKGd;Q z^YvTGRV}*zWNy3POr&{z4K;J&GBU)+$p$+gH*9&1HIy`59EnbIqrhg|Mq@@g) zj%+?RAhu~RJfm;&;F!g4VK|#ci{M^}@rFRJ+*D7YSZ%2G<$y$AC^Sv$PSDhDhPmwV zK!gM?X>LP4(ILzW>Q`X&)1qhkFxWA7Y~C=G)Op~Nu`#)khG&e~)2L*qWkxnyz)Y`M zNBIgz(TwPfP8LObU|=eb(3THu8-uOgLF-rXuA?4$cSEY4NbT~#f5k!4=NoF7DKb4OP9#z(aynyZhEH#zq_#HK=x1*xiRwq+&=R z57o3G`O&|l{ibzaQ{*lhgeEBFP0>yb$+dqD6E?UbT69I8hc-b^DvU2Qjj6F%*hIxk z8(T~ZTa6Pe(^~UA((6gpsw;*@Vv;*D;&ZGF`!5@YUe{s*pRPzvN`t}f9Zrk28K9>* znvAg6y~vEiDt~qvwaz8OueZ-LipAYms1J8Ut^Vu5UruMYs|7q!Y>34LNwBD~M|M67=Tk>Xu3#=@Uuc zlJt4HJ|8d516;YJC+uyd>kA0SX|E1TQ>k`OYwg&u>$Wz4inmZZHoT6v)dp8igxph^ z=xYakb0sK}m@BVe65C0;$ACE&Ixaq1_}%xw&<`@T5! z{FI{v_un6{c9^|B5^(t_H)?W^i&tk)Y#Rl|*Q4TMP@h9hSo}7&IM45x3GVtFOAKs{ z?uvtR{mgjvvnReH#kR5W>ZE5c5*#mVp;P0Nk2{(qQAo0irm-&9b9y~t6&qpF!qC;R zg+CE2zAs+8br&-6_uyTPh!^ zCn6=MT2^KQo*omYcI??d5Af2Ac-8V(yL`YibthO4-&fKn764XHic@>_scQ>Z zHX%;^d}oIu!0o7E(yP1!U^WJzeW&3a0S}C(R9_xVX_|Ia^s8vxH>I+R!{PXIO=;|>;CuIq#St+u1qZVZ(}_SD`x zRhnfK?%vflf|S0;?4bm=kD>NX3wcWvagU{-FONpnKE0D%hK(f`%F-kACy`?LSgLxP zkY5XVOGx)P(w7KHvo4=mIo#LY6R#FVNz`YQ!yM z@qJX!NFgzW)S4;l$d6{)cat{>cu$nt=;ls>l=^ndcL^=VX~e5v5lkLWRbV-(Q@_Lo zP0wiCoo<52w)XsZ3IfYX`#_+}h%xucd1R)f4t@6#f)k-V8uJW6nxq-k?-D#PI!;Zy z@&m!Ld*alu?`%s;^Sx>D>iFK}1ecGBSI_ONCAj~7ii76pg+8y6B6I>NvV|<0Kz*Jl zaJ`T;S7WXnr1f2fj@!pHw*q1>&>DFuU@TxQTOdp)t*=0|drTCl?A|!-G2J>QUfr@( z2}a>}N5&PsyLM2>Kh|Khe+}wu`M)mDJ!gJOKm{Qahlo-()^u->kYKDOYE9&AxIm|z()maS50}L>1xbTS`$6{ z+sTbm;fZt*+%DYC2+3=xNd1Ha>%C$s(}FtuXCrb$!ZlwFg_t}(URxm$4N#jG!pTfK zNof;&Fre{>SDmgX0nyAyJ)#NHK5#rZh9J$1ZHJr$X(DeJ5HB3=jZ^zi6Oh(bb>~FV z(z4xpTtJ#H-}iKrw$UUS=IlunZJChOLQ^j=hEGlWIfEQ&^pD@4PY`omYc!cUfSpXs z76{WyD--AzhWGxr`jR7ECki456U64JT22ikNE7$-k(C6$nC!C1E5b(CJ*1-L-|w?( zf;5+R?#J5eM9yCI$FRtxH%Zsia#_FYglBYjFT3QWV zKQEz$ft@z|tA9U%8BbHIu1udf3m-^EJ+$?9?1C#}u|^%~(N)094^VzWr;^MTvP{To zA!)?k{Z2U2H82bxS5X|@)P9~yS;BUug-s(wGp#C&lbI$&RgQqPsQFL<>2f+?xPX`( zYRUou-=pfDRW_Y6X`FK%Bg%#`pEtHs_-Yod6sfHcX1?-koL&CSF|1f)w)s0@7x^ScjC_F400u3oqU&AdUC)@dDB; zn3XBu>$9nG8lo+_;%A`<^N~7NNb2tSzQUUZ=EFM$#K36ld=we&5z7xc5eyo-a0ovSpHDwiYr0hrngjP z3`F(bU3gob(W0g^J`mKFXeo2Lgm!{CLz+8lljq#aztT?4>BUcJ6CZ4%^?LB7$S!dC zOn-Onf0wK&bJGI5!q7ZHot>fO&P^XKJ~jCrs9JRfCcpl^JcI66;e&?wtL+YJ-5}UJ zJ%GGDA!=44CQmOi42Hp#B^s<7LO8kBsco9uy3ZZ3(x+R zyo%-nV9#hVo1*g$Cd}fo8q*N?{dB9%6GtCBhQQ!Zy%pNdd7WAg)ybWBso8|cY7Rhn zhk@dg>TtYxD@lwXQAJ{;)_Q)2E~5aml*o+PccLlPdlzVs&3iXqnnP>zjs}YH-UI2q z7Xr5}#^5DOv1dePbgJoW#So6ww#-l9fy}L`{3eGW{(h6j@KqU9OJtm zs-`hc?*w{{HC~;@Yh0|Zk2hWu;&h%EqrWC4=&!Co2^@x}D=6A91xr(iG=&iIzad?~0YBoD#?`xZYe$-SpmzHGMn)z)WKT4OiXGoCglPF7q<`T4!ab`=9BU70xV`(Y# zmM1~J44{<+1=6{-1Z`v!g-Jdm+_tgT2 zW^1qnL!?uM1ViOG+$jSXCR-aWL8SyEWT`5Dvm^azl-OoOU(ocyt1B&G9}gi{?|ySTAAfZGLFd-Op&R1KvpzWHa1QAOqV`0 zWQ8;Ry`Ck#XUp=w1S9|MI_#iE3HKae!`1yDu;J~V3v8s<{SdIxrW;Q^n9;0zzRBld zU|Ht^U|HuvU>TIp{V!7q+;#W2un1U|UTnfkOg@hS%P8paiNy85vbD#7We;%g#ow~~ z2_imYFL6H!CYxIdEIafRu&j2O$)^EWR*PHc{<65c&5R**<7T)YKWoCz0ULGt+|S=8 z?iD7y5?J=(1z?#mH|}ry4KD%9)?Nmd5v?-eSAb;?Rs;Jh!i{j|^Rrh?Zm$8$kzNZd z<9Hp|U*HWh!Hr*k%HA|#+{E^$26ub>cs(%HB=&#z27}SX!2OPyYy^>!;3XTW*` zJ|z5eKLa@eBGVafvlAX<+zt>=Ap9jxnlRnGHa0rAyAM?|@~U-|KpP{#^i(OXs4gzz@JO89xHcKygFY zAJAnJ{t4JWa6g+0`~oZ+xB@J*devtx@vEuCHDKB2-%KTb2bLlI0WACTZCu$XXw30r|>J*o-Y@L@q&A`8$vc?2|i0 zOAv|kfMuomz%r+;Ot=78R@&O+-v(F)S_mwA+!k2Y)9$v9&y6Q#eghtu`f-QbPVSDt zvZ7)WE-~Rwz_Q1kfn{)AfMsX8ntZwmAN})}yE}-Cx`zq(1eS$+ne=y<^ra@;8(0R> z$AtR=%Nbh+EQ2ej^(PDT1D1{T2bSp?V8R1UK7&knFtDs=2(WCR0$6qo_sabB-w90X zRjj{ZCIjxG`BPm9EQ1(f(pLe?N=KS}Mghx)?*f*cx!Z(C1Ix1a(E5`N;Dn05z!+dT z-eZBK&p2S2#`{b@<4yQ}6P{qgUSQeqL|{EzzNYSK5ZOSD-_X=uYbr3wgeRNu6kxe( z9x&mlCOi#TW@0+9Y-REMOVwY+#uQnk!OY2P{)P2UwPU(1hm#%f=ou z;d#Ka9^ZTr8QsGsyugGP0?Ug2Wx|h`@FHM&0a$FpOHBAtU>WFRI@YiM^(MpPCajt8 z6DIs5u{x>dKMgF)KErYSmt?uA@UtfT9I%Y~d0<)T3KL!l?7v97 zU@GvU3BP12@G>w(E!N*E5SiasOn5c0zt%M%GK^PE_%&b|(pnRK9a!e&4PZHo)|v2| z)PHGs3kKQ1+rYBY^(MRlSXTOu32y|JqrVB*AN6K4*#aW7{Vp*2tdIYDIuRc~wwmxZ zQ-SwQ`t86n)DKK}hY5dZ!aIRwcXpZZZtB0x{6{cI{4ucX=N@1g^}rzeoq0*?tcv|8$Jyz13F{!KWox|11y6(2P`{w9#~Hcz5n^vRN^~R ziSJGL0UA_h^ zyY`z2|8By6nDD=WW!|p?8};~ba`tuuf0^(N6aE`mHg*$O25}2m_L3>LF$b1DIDX{E zIAZ6oCqQ81{f`wy_7cY_{HeA9%PEVKb^gZiFxcN42e1q-2v`mQPSW}72{GYNlYb+u ze}APo7UvHF=iK~J<8iYe(-4DRaL>+${vXS)0i;53)N01kEe#}cQx{8_=F zF8`dsX)eDWhr0Y2C%gQOwFZ`*#Mv&tPa$x;eE-uHL{1zW^zvgI?($cJvt9lkcQENY zn*8w`)L$0Q_WYf}gFHX(3@j(CuM3EOhsGm2|KM~pVLXxZ>+wv^kMU^kc40i7^Y;_a z;`~l{5a(|i&)@vH>jNAk`;W(Q{+RGU&X4g_&X4hI&Og2S1Iwi1nVcWv3AVq1K_)$( z)%o>!Jm<&03X>Ba)%l~w^E!W}cv$D}0iM|T1Hywl|KN-OmK!S`iU~hp!c&21V-@?)G!VH=rfHvBIWe#C?q znfw<6>z5VmKTAO5uKB15KL#v&R&TjE3s|2IJ7R9DpPK4bi z{1LG1@y8~-2UvDsuLzX>0p^(WKyDGaj52Z8;Ae+Wc&;jjsR1}vx7=O%mv zSf=GDu&nf$2_Ki3-hZC}kzF`x!e5y1m%y^puT1!q34aYN$KbRHp8=M=JZr+=0Q>Jh zo&%BjJr69S{uWp^{2j1AE#I5T1rXVVizfVo3I7Nz!?+|d_5U)6Oz=-8{4=l&=@%2e z0xT1M)r5Zqmi@nG!oLB_?)+}Te*pVr!~X`6_&TtR^iNZ(x63Zki0Y zfMrF@aytqRtS1%wui__7-S}pNziR=&vO88_8J%kKw*kv2>?RxtEW7LgmMba~u?a_-Fb?wh(-;jb>-WWg$cSQ17~cx< zJH-LZAmV{#!wJB$_ldx=$4MsaGGVvLKN%QV!TyI{HUW{9rkDz(0?SC7ns6GhY`B@p z-vcaDoo>Pzz_J6Gz_QLP3+{i&F>Vfn>_H1t;cOGeF*Sd~xhCAwg!6!9M)OU$6|ii$ z09XV{>#wyWz-@qK&v4?>kK3AXJ75`fkqNgq`E&r5o#_ZHvs-M!CBXjA|D8Z&if{n% z_Im09BE#+qENkj!!re`{2e7QDrwR8m;X8o!I7qE=tZWA7D!uJ5nnQ$+#EIY>JGuDL1nSAa8PB6}2xyPFf_nQh# z0G45UO+FKWWy94b{~D9N)})_g!jpmJ1exLkkt_BAlVK{b%lVc$M>(7%QGQ_1O{1iDE z>tY$OjCPAj|E|gZ6O(?w3BPB;TY+Vod`CfK!ylNOb^y!19R`*IeF#_vu@hJ>+Fd5R z!sN5kgkLb>O~B}we*M|36LBqi)r4O&71(Xke`LaYfMuY|fn}i2n((KIpLty{>e-T9H^Z>BGri~yn zq?dqY7%!XfDihvo^518|Zvx8^e+yXV=sENC_jwp(18)G!kzHrPZ<`9QH{tJrWgag8 z%PyY)mOc9dSa#+Lu*e-97N{$2(TQ(&w=%h)(ywu^Fvwm#Y->No6j>5a#AM@eO)Ciwbh^w zH9BcAxime5d*IwRK?s$OG zn3<${r#`;<6+UhK>djT0PuBw9O=@hVc4k0i26-~@bp@Merq=e|Ui`E+@7;XJci(-u zXco+k9gQ1jMuj^9!y9|r<6G3;*-(Zzj;Uze7&802xWzcn#Ty5y_!1Dl4UvhSgyYn| zPn-2#ZZ4jVIhxkNA;8lXWovOe&Y{k*z|n)dagOFyH6e&LJP6;_#zR~DPKI{+y~J?b zoI@#`3=8mdKq-$!3)z~;XK0yQo%|cE>((ZGt~O?C+fdxAb9m=Lq=g52=W1`lJVV>J zwSdpp__oL>>Rd*6FzUv`UbR;D)-tx;7d6vw!P8#9<<)KJs^N5UTQ~g7hx`3bkaxX5 zq~LY|JnoeRs9{eUcz6i44_dJZhf8^QhAZv}4;_`*07?;jqNyr75$Q0E!5J-8p}sF@ccq4ychvNMY>*K&5IK@Q%T&Y#uh z>`dU#Y0vHKWy4)Re0_bT_VdozFx=^Lh_CtUB2|mo)q=mMb%T6Ko4o76%9r8rS#2{+ z7EcuNiGsM*=Lo9BO+H7k#f$rUj>Z9*1HuEmxV`6SY_)}3y@fzt+~IRHR-+r+GIRAZ z;a2>Z0`B!WvYd))Csw;w8@PK?7u@G_;0rWw;03=?lFJ#-8tigL*RF#CKjZK;Ex|>W|a2wtxkxlbd5oM&7i9T1RAs+r$Wn-yHD> zv;Kvr+IO|TKh6@tHCF;N0=>nE$cQBm3dN%TT#ctUCXVMlt$a^xP7G>qTGxn{at5^4 z#z7H5dJVx5!8fCFu$6}gX9h#L6`r-fY7Kk3aEJEwp2WE6PeZOFcDagE8K#AF1}uP4}C&K4oF(=fYj-tAiUC)@FJG(Ue54;i$d zWV8LL`IP(S_!XNzv|1vOcicI}FD_--ytr-Z2)2v(EFt)r3*0$%+~fcoHQ)Z8t#^Tr zYe}(LyfpV6_$8lVh^A?DE}$jayBo?hT7U}6#m108-x>3Mq}|$|#&>CH2fE%#vo$cf z7VFe8qbEdGu*DYW#SK?n`-V7dF++lE7H?PNt>s!Z!}x(Hix;N=9AG%{Out@)Te8G( zs)E>$WE+IvlhHmpkc@BrT{zGt6*p=f8A;HQt(P)mJJR9Br53;Ivrn3|E}!;*`{Gag z@lUifpSm+}=hopl01>Rrz%lXwyXRAoXzxMD#sT;dltTcNl*3xa!R{?S2h$x}*dl{Y ztw48m8tug$S}b1tO3G0J)(ETTnD+d^Zv43R-N82egyuSwz)xyLhe`r*3sw(Qn|~;! zy9jr-#fzJ<4p(f8Kpn;AVI_S96@In`KMm6YUoym9Sw|Ex3+Y|@me6_UH>?4P8_`~5LazN&d zx~mQ+A#1+Fv9Y)X?9iRkEUww!GvrP=*VY|Q4yEQ5%d9gfr+Ch4UmsqEi^Bt-Wrc*n z4VP#v5Nqu@?aj}o^YdEf=PmeIZRqDYF(T>$MSOG8;u(qY)~j6kd9wK7q>OGB@nbAC zhx`$WCQp5y!oSt{ktoC#bL5Fo+(~xS;SRDDOU!_ZjcmEVFRu8AJX9B(6!g-R!8veP)Me04*aq+vYdo$_#S! z41{_Sx)6Uenxyw9D5GyGNGXmzsda(8+?;fAL9%#%hEqmMgZx5df{8R&EExv9LbevQ zI0dEFg&=-CPxl{lMu}eaQD%feD=s&d!``dfGsn`~d+uf|Wj13^;5BCyaQ{g)V<+5O zi+-|QA2D_z6uN=^+1jIwg+Z6nePGWnc-;#Akj9TUa~9%2N-t2}Zt#AW69Q>+Q za=fdreMhM9r?LlIZ|JSEMr}wg5%SA|cD)<3BiLtER^EDJTa`_2^F|kyooG$+Ng>}B zGF#}mz^eYlkX+wte5!!A|Cq>!wuFKVm0Ms7jjqDoN9`gi4LBAdP1T$-P}kUUQT6L*aj> z3u(6ZB+Ypn`M;5v(<+pG0;|u#lr%E;ZbCAzBgv0Oken~7d!~x;o+y&5o09x9j^tM< zBnuFhgY870P<04ZeXE2tzjq>8Ka^yCKFL#qNj3zM+&+wCNdlyg-J3(C0KIXrSEETz z64EZ3{1%ZqShNVPMrh`VAbN|2j|$Hh?c}p-5Xo(@s;s-6LWvF`%`@dBUl&=4k0iXf zhc6CIZ?i;$13IeM}UH5t=Qc-=jqitj(xws+c7ov?p995}w(ZaIXTAh22QH zMOZ-?8kHqiQHSqt>LbI2Y?2>gq*c~OH2*{Q&xfmQtmw_DVzP#adGLpr8Q%+eRLBN2 z7Qu!GM|O9xOGvWH@pRC5N3&o;r2P%7R1mS{GYGxC{ieR7SQi!jMiP=xAhybiL%>R`^>J9TiVqFPLKJ>j07kVigP)^1U9Uc}2({ z$j8Te+(D{nA@f85kHFJ_F$9=y4wf$Z@lY6fo)IDDie^r`NwZLleUX?AhlS>_7|=@s z9}|Ppdmv5N^p;fH6j64Kn0dZW#M1g2Qe~cEGXE%+%^neBJT?sndl9L2uw*d}Ga*%W zN^B#^B2Pi(WNj^iwuuhxs3e~fu{8}sV=5blS%nD>f0eDxqx{VqN^-of<>)33_Oe)! zm)lYEOGW=)6w$41LsgFx@$MJB|4qmwv1f1$naVnfwZ1-*JZ&O~Hp2jzJVyk(oDHXdUdf!1f`9wfZi{9HsSn)y@ize?DGD~E! zy^xQ`QVlOcsw`Iw-`k>wvts@q5%cUPA%jPX`5%JSnZ%k8poDw`iR~5zRQA^p8nZ0X zzi%Pp!?Bxd`ZP88NctTmPG!upD1 z?twQ3_^`n6635=p`L0_WYd`F}ZqdxredvH__CXhti(Ao>D-shYB997xQvCUF3|SCW z7lBI=sw-*T*1>|rT!|V*nv(7m(5)`ineJk9+k@?kuCwQ(N#YY9L>OD@E0|hd=%+v9 z(n$mR0%8Shov8C(5zHOzYr#r8knIk3Ua%p(ANPUwX1@qJT2#}AT@{u7 zSx&A4*>%_$8za~tyosS+j1tw{$((|1??j%%SRxqSB=sgXT(B_KN93uBr3%l-%E&gJ z6${VNu$6aZlUZj@$#@~3axsN<7p`A7CD&03YKk*B^5ADkKv&klpLu|U?k$pD4Q1>{>X6ZWNf1K5s{7-qZ&3qGY=;rVXtij~*D>1~QV!m|6Z|yBNTKn~E|3-Rifqbi>8~DvrzM`^^qSKhQ z?LNr$oexBZv#&E&VG{LdL2^_U$pr$tg|rBnCww|&lJ)1TLnw7Xc-|EH#m!0oX^R8V zsyaONK=h!x#`#AeUBgMfltQvm4#@?+K1cjc`Bf)KHJs!l*(B$b2rQ%qnad8f{R)k} z_ z4}$z<5Xl6A&j_3$@J)fAi=sMHi$}!xa@q1$cfmEZ=&G8_HbspA?i)$6!+_19x$Ni4 z`_aJK&ScH$Rs;Q*;XcUi1LwtHmZV=*!`Z|Pl7T`l6q<5@n+klvL)LKu?-kf4@E0U~ z>*WGP95M5pu5lnJ<2+0LuBx55;mNX{miX>SaMRIEN0gUCQDlJCNgJwm`5ojSK z8WWSW0J#ijuVa|<_|f1MF?qbdgXFZvFGD^durE9MRgm#<>mZLcCi4$YlF{KCAZx=9 zMCb7h;iRcyTObDq5uP1S(qSd}Lj=i10!NFoaUrD1j3aqK=rbD;?jStFVhOhnB>B2e z7-}Nl2l+|hHKK~(7&0FevUAu@vPO|+RT6ooCVd21E-HFmG!ret`aX$#+Kb?ph_W9? zlIDj{s=?zTJVw;NUHIfOAF0-ak{m56?Hxl^-(jWRhY0;qk*M*FK7ocMkY_(P$vZ_a z_n{wYti0I>$fJWUVwv65_E1bTTLf9aCPse&9IBj#d{n&%xz_S4WTfRrjLJ5Yg!u5% zxe^-%%NrgATvIRta@-JGtjcB-kpAD@gMdFA9tC-($QjF7a&QXda+?RTQ*cY@t4oVw zsj;rH(X5R3g8UQ>M6>QTYV4l@4TQDYG8Cn@7GeHVvAdvZP{xQVs)5(5Qy_mI?h~4Y zz+dns0xtue$I04N#gBfnCwm>jvZO$DTz{vrx!5!fKv!;r5Aor+Ld zyDotkhzCX_`G-Tt$GdHUHNP@6nu7QfRh02tkn@E6g4>;>4|hhhacVT=dbR{%b*ywb z%iQ#yXMSv1DP2s5=*$PY8+4-ISm4BWpZtt$OZ?0V_N-t_?Ml-kc)nz?sHVkWYYmps zugIye^#<$MuNdq-gT2_H$Z2688qD`@hhoru26gl=at5%&221W=40h6B@0Arft?Z1! zj+GUIT`<@)u;EsP!Cr$+Wq%mV+oZ^8V>b;}*Q6Ls#pQ;vrS>_#mPJlG3o+;uEsMdT z40ccFuFgP~V6eK*y}(ipmfpF@>0p@#>(IFvEYDz5a*CWmtgXQw%_#=!WU$G-i=4r% zr@5Q9BEyv!NGs&vMh6%2HSvJX;4+-$?hyPOd$o$j--diG_r z@y&V#UpL+w(mia>laF279{m5@$RM^XRtjo(7*YkzEpWDZAC|WoI(0 z!ZQk_hDCW^cBZh^2HOo=Q+C5(Lttyhp1@6h^89S@>&|o*iN|~tOtUWUI5Tl6)>%lu z_npnzYCN(fTUC$ImX_@4<~sYkY`@c&$KDilDVx^z2<9$pp&NUZy~8vwvr_q-C*k*&R z8a~iDp5@SuNaW)4o{z>&WHWRYJFfJlxLWpxPf(40ue@fjWt$D=w7q7Z%)Sx~BZq9g(1@yux3yP-E5O@5OtcdQ< zQZPfZ;+L_94EERHw(-l^8Nrsa8^gNBKhF~G&^=!m)Ia`3HdtpO(3e@2U`yGvJzixm zvt0(;GxF~ESJ(-o+{{+h@vpJ`-Z*7R4LU~6h<}3>^`T0avKj5?$G^p1E7O};f@(Ih z{emrJ<%zF5H#2*=Zu9nNh<}&m8|*;4oz|^vwO~ux!P4sZZR|!rz0T{UE8;(3E&A*1 zX2)0AhpdX256utlH`=m`O~TE11S5K}oAnr?yZ%tTCVn>?FIYW$Is1t7W46_>-K?mK z+r!S|V;F^9Sh6mDA6qm`XZKXS7yl_cZ?K(xM_Udv1qVFHb57z%@t?6&!F)^E<{_WP zA7cYX=*Ab@j<%d+lML3Q@Obb};H!3FK7K^-Bciq&oX@Z3>GuX^*{DLOmHAc5J z>6M>=|5?Uh&CA*)*m?d~-F9tYU7UjtGuX7^&I!SMk-^saKtuQjgKA)*{J3BmtHz$t zh@UsuVf8h8I1e62m1?Z7rFTMOUMkpp`_a^aU;_*`ujf#(VFsJhZUorf1{*eTG}w59 zbu9Pgut>g4r+6zeEF_W#-$zZ*`xI|N6kn|~wzeuoiRF3Y$)-=zSl(T*`S$t_Z@|{q zU{80Ll@QB^80<#=9>vM44CZL%bvXH0gZ-8BkS`&QR~yux`zY9SgDoHVOhP<=&|s@a zt^j+)U=O6`umr9dY)-0;C-CJ4tF6kxU5~XoV*|=pB_#6I_lquxP9*W869f})#C%Eo zyrAC&UhBP$CGktT(a!oLTk*Wmu-PkTSX}&X!xmY&F2TiBFIDqhV18bT;^v`(&9|52 zg)ujeF>FOWRIV_X!^%=%zD{VZJe`XjzZNGzM@h=S9>Gr$9&Kb6$`6B|F^B)b{z4;px zn)7Rh?Y+{4mKOYm!7d6GFi{_-&V4W7{|X5fY`(pB-)#vkc+^C?{;>J>6(u_pvU#Fm ze6?f`Sen634^L5Yc#dFmFljzb$l=4PMLDoz33)uMMzXIHTJiCM)w7+Y-zK!??;7lp zzLygU`31q|h>6&azf&u&fAuWC*>4H$`2Jd|W)AD2B-+~ZGlDH;b)C-&qWAJ7YaJEU9U9lWo>)>c&{_U1$UY(!!o?wP84uB{rK*q3KblWcrq89!mL zsNAWE{kR=xacJv)KV@O!K)zbArK~bWr=t4%|yW}N>(IRa?dQC zZAe>{ID+>OOy72@_z|74=25RFR&l(k!CyVt#>BgLq`|bb_Y?2oQw3Yfh7bEFaV+0s z*fJc4636pj1@qOj=w^o!@8=0(Cg@E};2oZ)N@))q7BYeN7VNt~En*w<@}UNMHX_;T z<@XrO2irtmYp{i|P2@g<1v!!|)%*#AMLN#0YQ9xxzQ75Ck0sXdU54?^loX|g9~NxB zeN##ptKp{&8*L`F{F1?@wj_4lV1MNhQ&)(v?e<(NSfpUz1!j!g!6xw{gB6V24OV3^ zU#m_#aDVn8gBEw%4Yt}~cfdAE?R*rxEG4OZD@6?=f+Fc|HR5AeX141Y^mZ2nh? z5AXzoO(;5-IF(oV*_FiU{E%SCwhwQEXYw{L=moE~V@Wgl34{HVYE7EO>t57tKc&Vb z&Ec#4EInx+|IT1@d$mnkz<)JZyY4T=;fH}<(#y?BE>2p=%LJ=uk0*CcdW3H$d%1TVb$teCAoq51Bld@Xbf{CSqGvBrW0gV>+9VR+aQ9 zFE&_I$MH#z@f8N!UNkM~aX$9A@U(xK;Ply+@`(ohC4nU^rIXNuGI6!#fyQVL6Y zia%_y^OR{f~zO=L|sQOR`5`RHNdu#M;UB0Y%952XYA4P z1xYXPvQt#9o{jDMc+$&!$k#ec>-}odtNe`4#QW^E+&AX5Xo3az*`9>+u4i=iWS3y; z8+?Jm`sD3NTE|n*l1-l_Z}EwOEoHeyGeh3uPZ`Vw_BQ{(V2x7r;Vz#+AFe8LZschOd!nistcAgzN-c73;;jt!Qfe_+d!4bsVaJj-^D={V zA8{&a3!iDQb(QCn-s3m?>`Kx$o_bCM!`4;aOxn(`o%4x4v3HUiyLR)%=XGPBUd>$x z_zFKOaUJ0o1gmF#dJS}a$%`-O<$4s4u$<%j4K~3u)^(o0bWyj>?pWjcor;`+CgxyqKhe&!1dbUBB>O1k>2>Y>n$GziF@-b&cy9 z557dzXzb&lHLl-zoM5zBuXFvI=Ln_`)1SP^Z{zGw-bJvvELPd%`jeM}q5pGPgSyT2 z7r);yZnW%l-Qbf9mSoxE`kT)ZOuu(`lg~43y#fxpZt`U&+bzDruw@2sbKT;-yP+~7r@6Ymm z!*NbZ+5eNye$Pt>`$90ioK^Wou(@n>z)qJ{xhj~R8CAI{m``_A74>JTW-hDXU%FH! z&0ts2dz;eIU}d(QF1yl3Fuj^UrHf#CErCj@;W@){z!9kQGkH3cN|UEUxkvZ(u@{wZ zTn^=7lWUNonOuXEXLMJ#O--}~DXUGM!O8}cXRxx>@I1#I!NJPMCeIM%bCYL?@|EG~ zTg?+~AMI48Yw}7&1J0uwz(Q9P5m}23sZ^(JIlAV;y5ol)nNOE z=YS2nLbc3cx9q|u*xA6*cYf~*SL$@ed-{fO7NI+GD0xDnpovNlV_|_Yj|!|4me_!X$A|h9dI}mpTYhPI^c*? z78tA|RQ0*zmDdewi3oBhD4Pv-4T~#L`O08(0}r~A6#nb&UbvJH!Sr0XlqkXGvU6;C zgiC2^unj?1Biu?mlV=m9vte5me8AyrqEs04y^w>h6lI*jo(enYN>yeUtgP`tS5sw? z!CFKfbfqcJ8mw>BL02>74TC)#ebD7mwi_&o9dxBDhYV&3Kj_L(&Khh(#6ef4Pq`|n z-is{dPs8{)dXJOR*Qmd`t+^5=*j%>Awl$);Qev`YD?JU{Yay*I*~)!_>BE$xJYe$7 zQGAByeLUO^w#cAMKy#G_gZ-OFyIU&n2&Pw)r|b|+4<=99Yj{plo1#R3==HtYYt;jKlo9*zf9ChQZ0 znz681J*fd|%!)^Y)C{?j48z&ujH<9O3)>=6C-qd3-aoz4S5d?5QWDD!OfS8y(OW#5 zjq6_k+nrBMQp4uLvY5F6Jrn<_Syd&=Dr}Vq^v8i=VF7HPzfRIqCpYxC?TG>M8BNmo zQUBgyQ4Dlc;RoGc!rG5u6)?Svss9si$svs{^;xYC*3h=pnVvb6gEk$>=YI~F1^I^6$C5qL ztg!Mv)Z?Q~sZWO>WieSbX=oG`qdFZA9z><8OKIjO4x;~MyKqog7#_zB3k$QcMOXzi zNofu}Cfd?V(MrL$97p0YimX(h6g34@(SOtbchA&f(vLv63YsLWPe$`B(Q6hxW*tP% z3?HAE`M0N@KA$cBbP_&sSP`^3!osYqW!wMR3(YO@6j6bHT)X~@&;MS-f3f;*Ukd+Ab^5B^ zSVAG@iquJso&!p?e$o00dw|3=_6pPURE+E@#?>Ti6qVAKrIeWem;t8klh(0Z&G+3w zSWnc_!L(h_)*;hKdNbziwAdf@S)w=a7_I^e%NJ8v-*#x}TEz91vy&;<{}r*CElj`Q zJ|#9?-AUKzOObkMVWY*xSMTwBv4jdm|2jJ;IJz!y_Me*R*pw7z_Mxe#VCLahu4J)l zadD){sL#*;`;oSF55t#BXt4AyarE*wjuSh6H zp_~=7go04mWHI$RRw}?7utqqJ;*fr^$X3TnG5?8J*b7LJe53JJs)9qs<^q&<3!8&* z1H=Y}HzNp66+4HGy@i=?XZY_h{SDmSJ#W`Z{-=g1%y#?oP4oZ%-Bm6#N}VDWp+%I! z^$-!_EDRYbHKmBpm2bTnS#rtRKZud#w8}tRKSNDIy`c}PG z3}TU3mip#nmg2fFmYG%miSgh1A~=0B`A_SAYHVWc|5HDspguVgA9nxK@_(zLcMhE0 zuv5C01{2n6&@XfUEA|=JSF;lp)Bp50C7iYBcDqvD^S@;<9EZZgahD;SMd6g^(kK91F>DU!$xv3a19kN>kVlg>FUdQ0Nxab!a#n89@560f%s3q&k4^heWc! z?<@u$Lw7u?1OCRHjszB8I*?5Zh%c>VS5N?YQFQ~`-E{?PVEa0)!eM%P&e6b*b+WQR zYe>IWS-3TA)Z5U|bH=&|EMb}fG&+k#RD*$&pN@(2s$3kIG<4fLp@fkmX8 zV{2IFzP=00X+HqeckmtT zY&Xtwt<*rFl`1WIp?SGg4lB0~cHYOYFnZFN9ze2z zAzZxU51`m9tPfPy!c0#-8(4Y?l4f<6&*p)A%Yc)>TSqM9(~!((A;;jncLVcvU&X@t zieam8;=dr7`b1BO*9du*hXk%d5F_wV`6A;5yJ5X+@G4eq9hmzGFEYBbQ^`TW8ZrQUj?Rde9Cd*&+xMB!@dlU+J1cZ&h4w(jaT z1^BmNvms}u&4XM%;t{2sFCW>U?6n*#*}?W&Dl2y&IC@yU#!8yEte0_4V62Znf~WV@ z{KZ6yot{^3v39Q7qwKIgQ1z*TuO1BjT3HDB9jdd7k^8PQ_4N<@&zUE!zZE(xD|z?) z2+JBqkCz)*p@$Nio|t4gYfbFd#L~d%IoU<)lpY?-c($uwYs(}N^c>`}n`MsTTheX- zoYq%Rfo_Qv!1P@HinU*l5ti%LD`lfCGewUZ7(LZ$V9%ikTwR?z2Tjr=gk>*(e&{*O z=dH>!cc@({0X#KVPlqJymQ`~51D3ofh z2Xv+MJ4-q6CCf>EBn^|1Pwc*fC9C5TuUf9~Swn7Erdc{CD*+8GH_>+vRky?Q{aiJ@ zZ)iYC06kC-S4bABlk!Ig6$QLTsSeK*HcOb`i+$uCl7>JcsJFSHQG#eTi9Xna5 z)@JXuu2DZ7e$ZO2Y{-tlFj+CTFeSxQjm^Jot@iOVI8KqQzL0hm(JjUbo~FchyJ5wT zucUHyj&iOy%?5h7+`#BDas#^*hBcxF_j0RyRa#&T><&!HIf|n!TYbss>*5X_)dogK zQ5IS|7p=nFdN6O4TFq&vspk3)xHyGs_PMb<8dwX&wNRlaybWT~9adfO^}f z&~xC03Oy=5YaySLYKCW;de%bLvlgOw?29{Y-%(k+yg8k}N zc5&D-$m2!l)rHu$uOh^HUk7S_eQXZf#i|SKwg&ce2P?a%#tsj+T~SxIbJ`YSx+MZf zq&Xq?4RqSBtAF5(9JhU1nF)Lrc)!@bj)?8cX`5A8YIEB7i2k;8>)yeYwp|E?qyCv` zHMU{E^OUdHMC_aC)_Zb&tKd{U{7u_=;dDtDu3=nH+O~)#=d>*7bJ})8=o?tbAdc^8 z(H?in@Htia6UxSy{$&f`bWIDugP0taYbh&@wC7qf+lK)+G>fz6+WJ-|L*wqAq7>Q= z=26Ew4Q}qUm)I^0Z)NXkTV7Ob545hxyW4(Agc8Xqh)bfe1Wxf@Q8ywV<+gh(U$d9n zPM5r9Z(wvZC(t^x)m!#y3Tb+Y2ohM$*<) z-`$EQMh(HMn6Je0d7F^Hv9=G|ga?*b{ucNL;A-2OL!$#5*g9-PJ=OY@#K38`@g184 zhV!bT9c-a3E+sv1m{^3v@UuF_*t}`FUFVPG<*@5~dY|mT8|?4GHmHHlKwal!p~(_m z!W3>9hAsL2ysm)@eb(!3dk5Cr(wp@U%;zVE%nDp%`?l}D0^hRz)b}YAwv}VRW{qf&HFBvnLmewx=Z|o|e`c;07GiCiyzndXcEJfqhxJIqjo90fsNzF`du?l)?Zh&p16U1=tcPuDJDv+XZ2P9;MaYjJ zyCA)s;Q&oqYfGuX10ZW`r9v}IB>%c~dv=nel+z{WtnK%1-5qCbXDdn_)t03Ea!9)3 zRCBuzyLz?dSjsp@1B;EP6}hYmJ+>r^tH4uMhM9-!$|IP zW2>>$H{q<_a-a|k*z!2G>ITcpkaTH i28`>NTZD?H0?Y`Qv(CWj*J; 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 X)f!zN$-k<~nxT!xNqJ7LFVX)FIwwMd delta 45808 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

>6S5gJCcRX2Bd7 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 From 4a9bdc2e72f84e1f78f32d67b637284ddbcd07cc Mon Sep 17 00:00:00 2001 From: Easy World Date: Fri, 27 Jan 2023 15:32:20 +0800 Subject: [PATCH 059/171] Compile error fix --- SysBot.Pokemon.Discord/Commands/Extra/BatchEditingModule.cs | 2 +- SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs | 4 ++-- SysBot.Pokemon/SWSH/BotTrade/PokeTradeBot.cs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/SysBot.Pokemon.Discord/Commands/Extra/BatchEditingModule.cs b/SysBot.Pokemon.Discord/Commands/Extra/BatchEditingModule.cs index cd47a53a4..02fa06a70 100644 --- a/SysBot.Pokemon.Discord/Commands/Extra/BatchEditingModule.cs +++ b/SysBot.Pokemon.Discord/Commands/Extra/BatchEditingModule.cs @@ -43,7 +43,7 @@ await ReplyAsync($"Invalid Lines Detected:\r\n{Format.Code(string.Join(Environme private static bool IsValidInstructionSet(ReadOnlySpan split, out List invalid) { invalid = new List(); - var set = new StringInstructionSet(split.ToArray()); + var set = new StringInstructionSet(split); foreach (var s in set.Filters.Concat(set.Instructions)) { var type = BatchEditing.GetPropertyType(s.PropertyName); diff --git a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs index 24a8e031d..08ac45bf1 100644 --- a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs +++ b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs @@ -79,8 +79,8 @@ public override async Task MainLoop(CancellationToken token) Log("Identifying trainer data of the host console."); var sav = await IdentifyTrainer(token).ConfigureAwait(false); OT = sav.OT; - DisplaySID = (int)sav.DisplaySID; - DisplayTID = (int)sav.DisplayTID; + DisplaySID = sav.DisplaySID; + DisplayTID = sav.DisplayTID; RecentTrainerCache.SetRecentTrainer(sav); await InitializeSessionOffsets(token).ConfigureAwait(false); diff --git a/SysBot.Pokemon/SWSH/BotTrade/PokeTradeBot.cs b/SysBot.Pokemon/SWSH/BotTrade/PokeTradeBot.cs index b13f86357..d785a0abd 100644 --- a/SysBot.Pokemon/SWSH/BotTrade/PokeTradeBot.cs +++ b/SysBot.Pokemon/SWSH/BotTrade/PokeTradeBot.cs @@ -416,8 +416,8 @@ private async Task SetPkmWithSwappedIDDetails(PK8 toSend, string trainerNa var tidsid = BitConverter.ToUInt32(data, 0); var cln = (PK8)toSend.Clone(); cln.OT_Gender = data[6]; - cln.TrainerTID7 = (uint)(tidsid % 1_000_000); - cln.TrainerSID7 = (uint)(tidsid / 1_000_000); + cln.TrainerTID7 = tidsid % 1_000_000; + cln.TrainerSID7 = tidsid / 1_000_000; cln.Language = data[5]; cln.OT_Name = trainerName; cln.ClearNickname(); From 69d358a0f949b65ddf27069b2781af712d79336e Mon Sep 17 00:00:00 2001 From: Easy World Date: Sat, 28 Jan 2023 16:40:12 +0800 Subject: [PATCH 060/171] use form dict, have fun --- SysBot.Pokemon/Helpers/ShowdownTranslator.cs | 217 ++++++++++++++++--- 1 file changed, 188 insertions(+), 29 deletions(-) diff --git a/SysBot.Pokemon/Helpers/ShowdownTranslator.cs b/SysBot.Pokemon/Helpers/ShowdownTranslator.cs index c248997c0..8878f17a4 100644 --- a/SysBot.Pokemon/Helpers/ShowdownTranslator.cs +++ b/SysBot.Pokemon/Helpers/ShowdownTranslator.cs @@ -1,5 +1,6 @@ using PKHeX.Core; using System.Text.RegularExpressions; +using System.Collections.Generic; namespace SysBot.Pokemon { @@ -40,37 +41,13 @@ public static string Chinese2Showdown(string zh) return result; } - // 识别未知图腾 - if (Regex.IsMatch(zh, "[A-Z?!?!]形态")) - { - string formsUnown = Regex.Match(zh, "([A-Z?!?!])形态").Groups?[1]?.Value ?? ""; - if (formsUnown == "?") formsUnown = "?"; - else if (formsUnown == "!") formsUnown = "!"; - result += $"-{formsUnown}"; - zh = Regex.Replace(zh, "[A-Z?!?!]形态", ""); - } - // 识别地区形态 - if (zh.Contains("帕底亚的样子(火)形态")) - { - result += $"-Paldea-Fire"; - zh = zh.Replace("帕底亚的样子(火)形态", ""); - } - else if (zh.Contains("帕底亚的样子(水)形态")) - { - result += $"-Paldea-Water"; - zh = zh.Replace("帕底亚的样子(水)形态", ""); - } - else + foreach (var s in formDict) { - for (int i = 0; i < GameStringsZh.forms.Length; i++) - { - if (GameStringsZh.forms[i].Length == 0) continue; - if (!zh.Contains(GameStringsZh.forms[i] + "形态")) continue; - result += $"-{GameStringsEn.forms[i]}"; - zh = zh.Replace(GameStringsZh.forms[i] + "形态", ""); - break; - } + if (!zh.Contains(s.Key + "形态")) continue; + result += $"-{s.Value}"; + zh = zh.Replace(s.Key + "形态", ""); + break; } // 添加性别 @@ -371,5 +348,187 @@ public static string Chinese2Showdown(string zh) return result; } + + #region 形态中文ps字典 + public static Dictionary formDict = new Dictionary { + {"阿罗拉","Alola"}, + {"初始","Original"}, + {"丰缘","Hoenn"}, + {"神奥","Sinnoh"}, + {"合众","Unova"}, + {"卡洛斯","Kalos"}, + {"就决定是你了","Partner"}, + {"搭档","Starter"}, + {"世界","World"}, + {"摇滚巨星","Rock-Star"}, + {"贵妇","Belle"}, + {"流行偶像","Pop-Star"}, + {"博士","PhD"}, + {"面罩摔角手","Libre"}, + {"换装","Cosplay"}, + {"伽勒尔","Galar"}, + {"洗翠","Hisui"}, + {"帕底亚的样子斗战种","Paldea-Combat"}, + {"帕底亚的样子火炽种","Paldea-Blaze"}, + {"帕底亚的样子水澜种","Paldea-Aqua"}, + {"刺刺耳","Spiky-eared"}, + {"帕底亚","Paldea"}, + {"B","B"}, + {"C","C"}, + {"D","D"}, + {"E","E"}, + {"F","F"}, + {"G","G"}, + {"H","H"}, + {"I","I"}, + {"J","J"}, + {"K","K"}, + {"L","L"}, + {"M","M"}, + {"N","N"}, + {"O","O"}, + {"P","P"}, + {"Q","Q"}, + {"R","R"}, + {"S","S"}, + {"T","T"}, + {"U","U"}, + {"V","V"}, + {"W","W"}, + {"X","X"}, + {"Y","Y"}, + {"Z","Z"}, + {"!","Exclamation"}, + {"?","Question"}, + {"太阳的样子","Sunny"}, + {"雨水的样子","Rainy"}, + {"雪云的样子","Snowy"}, + {"原始回归的样子","Primal"}, + {"攻击形态","Attack"}, + {"防御形态","Defense"}, + {"速度形态","Speed"}, + {"砂土蓑衣","Sandy"}, + {"垃圾蓑衣","Trash"}, + {"晴天形态","Sunshine"}, + {"东海","East"}, + {"加热","Heat"}, + {"清洗","Wash"}, + {"结冰","Frost"}, + {"旋转","Fan"}, + {"切割","Mow"}, + {"起源","Origin"}, + {"天空","Sky"}, + {"格斗","Fighting"}, + {"飞行","Flying"}, + {"毒","Poison"}, + {"地面","Ground"}, + {"岩石","Rock"}, + {"虫","Bug"}, + {"幽灵","Ghost"}, + {"钢","Steel"}, + {"火","Fire"}, + {"水","Water"}, + {"草","Grass"}, + {"电","Electric"}, + {"超能力","Psychic"}, + {"冰","Ice"}, + {"龙","Dragon"}, + {"恶","Dark"}, + {"妖精","Fairy"}, + {"蓝条纹的样子","Blue-Striped"}, + {"白条纹的样子","White-Striped"}, + {"夏天的样子","Summer"}, + {"秋天的样子","Autumn"}, + {"冬天的样子","Winter"}, + {"灵兽形态","Therian"}, + {"暗黑","White"}, + {"焰白","Black"}, + {"觉悟的样子","Resolute"}, + {"舞步形态","Pirouette"}, + {"水流卡带","Douse"}, + {"闪电卡带","Shock"}, + {"火焰卡带","Burn"}, + {"冰冻卡带","Chill"}, + {"小智版","Ash"}, + {"冰雪花纹","Icy Snow"}, + {"雪国花纹","Polar"}, + {"雪原花纹","Tundra"}, + {"大陆花纹","Continental"}, + {"庭院花纹","Garden"}, + {"高雅花纹","Elegant"}, + {"摩登花纹","Modern"}, + {"大海花纹","Marine"}, + {"群鸟花纹","Archipelago"}, + {"荒野花纹","High Plains"}, + {"沙尘花纹","Sandstorm"}, + {"大河花纹","River"}, + {"骤雨花纹","Monsoon"}, + {"热带草原花纹","Savanna"}, + {"太阳花纹","Sun"}, + {"大洋花纹","Ocean"}, + {"热带雨林花纹","Jungle"}, + {"幻彩花纹","Fancy"}, + {"球球花纹","Pokeball"}, + {"蓝花","Blue"}, + {"橙花","Orange"}, + {"白花","White"}, + {"黄花","Yellow"}, + {"永恒","Eternal"}, + {"心形造型","Heart"}, + {"星形造型","Star"}, + {"菱形造型","Diamond"}, + {"淑女造型","Debutante"}, + {"贵妇造型","Matron"}, + {"绅士造型","Dandy"}, + {"女王造型","La Reine"}, + {"歌舞伎造型","Kabuki"}, + {"国王造型","Pharaoh"}, + {"小尺寸","Small"}, + {"大尺寸","Large"}, + {"特大尺寸","Super"}, + {"解放","Unbound"}, + {"啪滋啪滋风格","Pom-Pom"}, + {"呼拉呼拉风格","Pa'u"}, + {"轻盈轻盈风格","Sensu"}, + {"黑夜的样子","Midnight"}, + {"黄昏的样子","Dusk"}, + {"流星的样子","Meteor"}, + {"橙色核心","Orange"}, + {"黄色核心","Yellow"}, + {"绿色核心","Green"}, + {"浅蓝色核心","Blue"}, + {"蓝色核心","Indigo"}, + {"紫色核心","Violet"}, + {"黄昏之鬃","Dusk-Mane"}, + {"拂晓之翼","Dawn-Wings"}, + {"究极","Ultra"}, + {"500年前的颜色","Original"}, + {"低调的样子","Low-Key"}, + {"真品","Antique"}, + {"奶香红钻","Ruby-Cream"}, + {"奶香抹茶","Matcha-Cream"}, + {"奶香薄荷","Mint-Cream"}, + {"奶香柠檬","Lemon-Cream"}, + {"奶香雪盐","Salted-Cream"}, + {"红钻综合","Ruby-Swirl"}, + {"焦糖综合","Caramel-Swirl"}, + {"三色综合","Rainbow-Swirl"}, + {"剑之王","Crowned"}, + {"盾之王","Crowned"}, + {"无极巨化","Eternamax"}, + {"连击流","Rapid-Strike"}, + {"阿爸","Dada"}, + {"骑白马的样子","Ice"}, + {"骑黑马的样子","Shadow"}, + {"四只家庭","Four"}, + {"蓝羽毛","Blue"}, + {"黄羽毛","Yellow"}, + {"白羽毛","White"}, + {"下垂姿势","Droopy"}, + {"平挺姿势","Stretchy"}, + {"三节形态","Three-Segment"}, + }; + #endregion + } } \ No newline at end of file From d6befd201aef0fd868bf55da6821b36bed7d0476 Mon Sep 17 00:00:00 2001 From: Easy World Date: Sat, 28 Jan 2023 16:49:57 +0800 Subject: [PATCH 061/171] =?UTF-8?q?remove=20redundant=20=E5=BD=A2=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SysBot.Pokemon/Helpers/ShowdownTranslator.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/SysBot.Pokemon/Helpers/ShowdownTranslator.cs b/SysBot.Pokemon/Helpers/ShowdownTranslator.cs index 8878f17a4..6248737fc 100644 --- a/SysBot.Pokemon/Helpers/ShowdownTranslator.cs +++ b/SysBot.Pokemon/Helpers/ShowdownTranslator.cs @@ -44,9 +44,10 @@ public static string Chinese2Showdown(string zh) // 识别地区形态 foreach (var s in formDict) { - if (!zh.Contains(s.Key + "形态")) continue; + var searchKey = s.Key.EndsWith("形态") ? s.Key : s.Key + "形态"; + if (!zh.Contains(searchKey)) continue; result += $"-{s.Value}"; - zh = zh.Replace(s.Key + "形态", ""); + zh = zh.Replace(searchKey, ""); break; } @@ -349,7 +350,7 @@ public static string Chinese2Showdown(string zh) return result; } - #region 形态中文ps字典 + #region 形态中文ps字典,感谢ppllouf public static Dictionary formDict = new Dictionary { {"阿罗拉","Alola"}, {"初始","Original"}, From 26e7269bf5a9f99222f7f00c1a707b7d82167108 Mon Sep 17 00:00:00 2001 From: Easy World Date: Sun, 29 Jan 2023 23:58:25 +0800 Subject: [PATCH 062/171] Update Dodo SDK to 1.7.0 --- SysBot.Pokemon.Dodo/DodoBot.cs | 17 +++++-- SysBot.Pokemon.Dodo/PokemonProcessService.cs | 46 ++++++++----------- .../SysBot.Pokemon.Dodo.csproj | 4 +- 3 files changed, 34 insertions(+), 33 deletions(-) diff --git a/SysBot.Pokemon.Dodo/DodoBot.cs b/SysBot.Pokemon.Dodo/DodoBot.cs index c63e27fd3..0fcf334f7 100644 --- a/SysBot.Pokemon.Dodo/DodoBot.cs +++ b/SysBot.Pokemon.Dodo/DodoBot.cs @@ -1,6 +1,8 @@ -using System.Threading.Tasks; +using System.Linq; +using System.Threading.Tasks; using DoDo.Open.Sdk.Models; -using DoDo.Open.Sdk.Models.Channels; +using DoDo.Open.Sdk.Models.ChannelMessages; +using DoDo.Open.Sdk.Models.Islands; using DoDo.Open.Sdk.Models.Messages; using DoDo.Open.Sdk.Models.Personals; using DoDo.Open.Sdk.Services; @@ -15,7 +17,7 @@ namespace SysBot.Pokemon.Dodo public static OpenApiService OpenApiService; - private DodoSettings Settings; + private static DodoSettings Settings; public DodoBot(DodoSettings settings, PokeTradeHub hub) { @@ -94,12 +96,17 @@ public static void SendChannelAtMessage(ulong atDodoId, string message, string c }); } - public static void SendPersonalMessage(string dodoId, string message) + public static void SendPersonalMessage(string dodoId, string message, string islandSourceId = "") { if (string.IsNullOrEmpty(message)) return; + if (string.IsNullOrWhiteSpace(islandSourceId)) + { + islandSourceId = OpenApiService.GetIslandList(new GetIslandListInput()).FirstOrDefault().IslandSourceId ?? ""; + } OpenApiService.SetPersonalMessageSend(new SetPersonalMessageSendInput { - DoDoId = dodoId, + IslandSourceId = islandSourceId, + DodoSourceId = dodoId, MessageBody = new MessageBodyText { Content = message diff --git a/SysBot.Pokemon.Dodo/PokemonProcessService.cs b/SysBot.Pokemon.Dodo/PokemonProcessService.cs index 8898bfa26..5d4b290fa 100644 --- a/SysBot.Pokemon.Dodo/PokemonProcessService.cs +++ b/SysBot.Pokemon.Dodo/PokemonProcessService.cs @@ -1,5 +1,6 @@ using System; using System.Net; +using System.Net.Http; using DoDo.Open.Sdk.Models.Bots; using DoDo.Open.Sdk.Models.Events; using DoDo.Open.Sdk.Models.Messages; @@ -16,17 +17,11 @@ namespace SysBot.Pokemon.Dodo private static readonly string LogIdentity = "DodoBot"; private static readonly string Welcome = "at我并尝试对我说:\n皮卡丘\ntrade ps代码\n或者直接拖一个文件进来"; private readonly string _channelId; - private readonly string _botDodoId; + private string _botDodoSourceId; public PokemonProcessService(OpenApiService openApiService, string channelId) { _openApiService = openApiService; - var output = _openApiService.GetBotInfo(new GetBotInfoInput()); - if (output != null) - { - _botDodoId = output.DodoId; - } - _channelId = channelId; } @@ -57,14 +52,7 @@ public override void PersonalMessageEvent( if (eventBody.MessageBody is MessageBodyText messageBodyText) { - _openApiService.SetPersonalMessageSend(new SetPersonalMessageSendInput - { - DoDoId = eventBody.DodoId, - MessageBody = new MessageBodyText - { - Content = "你好" - } - }); + DodoBot.SendPersonalMessage(eventBody.DodoSourceId, $"你好", eventBody.IslandSourceId); } } @@ -81,10 +69,12 @@ public override void ChannelMessageEvent( DodoBot.SendChannelMessage("非法文件", eventBody.ChannelId); return; } - var p = GetPKM(new WebClient().DownloadData(messageBodyFile.Url)); + using var client = new HttpClient(); + var downloadBytes = client.GetByteArrayAsync(messageBodyFile.Url).Result; + var p = GetPKM(downloadBytes); if (p is TP pkm) { - DodoHelper.StartTrade(pkm, eventBody.DodoId, eventBody.Personal.NickName, eventBody.ChannelId); + DodoHelper.StartTrade(pkm, eventBody.DodoSourceId, eventBody.Personal.NickName, eventBody.ChannelId); } return; @@ -94,19 +84,23 @@ public override void ChannelMessageEvent( var content = messageBodyText.Content; - LogUtil.LogInfo($"{eventBody.Personal.NickName}({eventBody.DodoId}):{content}", LogIdentity); - if (!content.Contains($"<@!{_botDodoId}>")) return; + LogUtil.LogInfo($"{eventBody.Personal.NickName}({eventBody.DodoSourceId}):{content}", LogIdentity); + if (_botDodoSourceId == null) + { + _botDodoSourceId = _openApiService.GetBotInfo(new GetBotInfoInput()).DodoSourceId; + } + if (!content.Contains($"<@!{_botDodoSourceId}>")) return; content = content.Substring(content.IndexOf('>') + 1); if (content.Trim().StartsWith("trade")) { content = content.Replace("trade", ""); - DodoHelper.StartTrade(content, eventBody.DodoId, eventBody.Personal.NickName, eventBody.ChannelId); + DodoHelper.StartTrade(content, eventBody.DodoSourceId, eventBody.Personal.NickName, eventBody.ChannelId); return; } else if (content.Trim().StartsWith("dump")) { - DodoHelper.StartDump(eventBody.DodoId, eventBody.Personal.NickName, eventBody.ChannelId); + DodoHelper.StartDump(eventBody.DodoSourceId, eventBody.Personal.NickName, eventBody.ChannelId); return; } @@ -114,18 +108,18 @@ public override void ChannelMessageEvent( if (!string.IsNullOrWhiteSpace(ps)) { LogUtil.LogInfo($"收到命令\n{ps}", LogIdentity); - DodoHelper.StartTrade(ps, eventBody.DodoId, eventBody.Personal.NickName, eventBody.ChannelId); + DodoHelper.StartTrade(ps, eventBody.DodoSourceId, eventBody.Personal.NickName, eventBody.ChannelId); } else if (content.Contains("取消")) { - var result = DodoBot.Info.ClearTrade(ulong.Parse(eventBody.DodoId)); - DodoBot.SendChannelAtMessage(ulong.Parse(eventBody.DodoId), $" {GetClearTradeMessage(result)}", + var result = DodoBot.Info.ClearTrade(ulong.Parse(eventBody.DodoSourceId)); + DodoBot.SendChannelAtMessage(ulong.Parse(eventBody.DodoSourceId), $" {GetClearTradeMessage(result)}", eventBody.ChannelId); } else if (content.Contains("位置")) { - var result = DodoBot.Info.CheckPosition(ulong.Parse(eventBody.DodoId)); - DodoBot.SendChannelAtMessage(ulong.Parse(eventBody.DodoId), + var result = DodoBot.Info.CheckPosition(ulong.Parse(eventBody.DodoSourceId)); + DodoBot.SendChannelAtMessage(ulong.Parse(eventBody.DodoSourceId), $" {GetQueueCheckResultMessage(result)}", eventBody.ChannelId); } diff --git a/SysBot.Pokemon.Dodo/SysBot.Pokemon.Dodo.csproj b/SysBot.Pokemon.Dodo/SysBot.Pokemon.Dodo.csproj index 248f9872a..e0113ed08 100644 --- a/SysBot.Pokemon.Dodo/SysBot.Pokemon.Dodo.csproj +++ b/SysBot.Pokemon.Dodo/SysBot.Pokemon.Dodo.csproj @@ -1,4 +1,4 @@ - + net7.0 @@ -6,7 +6,7 @@ - + From 79aaf4d832532cb21113a607cc63e81b93d542f9 Mon Sep 17 00:00:00 2001 From: Easy World Date: Mon, 30 Jan 2023 00:24:35 +0800 Subject: [PATCH 063/171] Dump support isShiny --- SysBot.Pokemon.Dodo/DodoTradeNotifier.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs b/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs index fa2727b06..def472873 100644 --- a/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs +++ b/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs @@ -99,7 +99,7 @@ public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail if (result.Species != 0 && info.Type == PokeTradeType.Dump) { var text = - $"species:{result.Species}\npid:{result.PID}\nec:{result.EncryptionConstant}\nIVs:{string.Join(",", result.IVs)}"; + $"species:{result.Species}\npid:{result.PID}\nec:{result.EncryptionConstant}\nIVs:{string.Join(",", result.IVs)}\nisShiny:{result.IsShiny}"; DodoBot.SendChannelMessage(text, ChannelId); } } From 78fa6995c15d62568be9ce703f5752dac7c9c21d Mon Sep 17 00:00:00 2001 From: Easy World Date: Mon, 30 Jan 2023 14:00:00 +0800 Subject: [PATCH 064/171] fix islandSourceId problem --- SysBot.Pokemon.Dodo/DodoHelper.cs | 15 ++++++++------- SysBot.Pokemon.Dodo/DodoTradeNotifier.cs | 3 ++- SysBot.Pokemon.Dodo/PokemonProcessService.cs | 6 +++--- SysBot.Pokemon/TradeHub/PokeTradeDetail.cs | 4 ++++ 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/SysBot.Pokemon.Dodo/DodoHelper.cs b/SysBot.Pokemon.Dodo/DodoHelper.cs index 1f99a61df..4647301a3 100644 --- a/SysBot.Pokemon.Dodo/DodoHelper.cs +++ b/SysBot.Pokemon.Dodo/DodoHelper.cs @@ -10,7 +10,7 @@ namespace SysBot.Pokemon.Dodo { public class DodoHelper where T : PKM, new() { - public static void StartTrade(string ps, string dodoId, string nickName, string channelId) + public static void StartTrade(string ps, string dodoId, string nickName, string channelId, string islandSourceId) { var _ = CheckAndGetPkm(ps, dodoId, out var msg, out var pkm); if (!_) @@ -19,10 +19,10 @@ public static void StartTrade(string ps, string dodoId, string nickName, string return; } - StartTradeWithoutCheck(pkm, dodoId, nickName, channelId); + StartTradeWithoutCheck(pkm, dodoId, nickName, channelId, islandSourceId); } - public static void StartTrade(T pkm, string dodoId, string nickName, string channelId) + public static void StartTrade(T pkm, string dodoId, string nickName, string channelId, string islandSourceId) { var _ = CheckPkm(pkm, dodoId, out var msg); if (!_) @@ -31,13 +31,13 @@ public static void StartTrade(T pkm, string dodoId, string nickName, string chan return; } - StartTradeWithoutCheck(pkm, dodoId, nickName, channelId); + StartTradeWithoutCheck(pkm, dodoId, nickName, channelId, islandSourceId); } - public static void StartTradeWithoutCheck(T pkm, string dodoId, string nickName, string channelId) + public static void StartTradeWithoutCheck(T pkm, string dodoId, string nickName, string channelId, string islandSourceId) { var code = DodoBot.Info.GetRandomTradeCode(); - var __ = AddToTradeQueue(pkm, code, ulong.Parse(dodoId), nickName, channelId, + var __ = AddToTradeQueue(pkm, code, ulong.Parse(dodoId), nickName, channelId, islandSourceId PokeRoutineType.LinkTrade, out string message); DodoBot.SendChannelMessage(message, channelId); } @@ -161,7 +161,7 @@ public static bool CheckAndGetPkm(string setstring, string username, out string return false; } - private static bool AddToTradeQueue(T pk, int code, ulong userId, string name, string channelId, + private static bool AddToTradeQueue(T pk, int code, ulong userId, string name, string channelId, string islandSourceId PokeRoutineType type, out string msg) { var trainer = new PokeTradeTrainerInfo(name, userId); @@ -169,6 +169,7 @@ private static bool AddToTradeQueue(T pk, int code, ulong userId, string name, s var tt = type == PokeRoutineType.SeedCheck ? PokeTradeType.Seed : (type == PokeRoutineType.Dump ? PokeTradeType.Dump : PokeTradeType.Specific); var detail = new PokeTradeDetail(pk, trainer, notifier, tt, code, true); + detail.Context.Add("islandSourceId", islandSourceId); var trade = new TradeEntry(detail, userId, type, name); var added = DodoBot.Info.AddToTradeQueue(trade, userId, false); diff --git a/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs b/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs index def472873..d01276a21 100644 --- a/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs +++ b/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs @@ -69,7 +69,8 @@ public void TradeInitialize(PokeRoutineExecutor routine, PokeTradeDetail i var text = $"\n派送:{ShowdownTranslator.GameStringsZh.Species[Data.Species]}\n密码:见私信\n状态:初始化"; DodoBot.SendChannelAtMessage(info.Trainer.ID, text, ChannelId); DodoBot.SendPersonalMessage(info.Trainer.ID.ToString(), - $"派送:{ShowdownTranslator.GameStringsZh.Species[Data.Species]}\n密码:{info.Code:0000 0000}"); + $"派送:{ShowdownTranslator.GameStringsZh.Species[Data.Species]}\n密码:{info.Code:0000 0000}", + info.Context.GetValueOrDefault("islandSourceId", "")); } public void TradeSearching(PokeRoutineExecutor routine, PokeTradeDetail info) diff --git a/SysBot.Pokemon.Dodo/PokemonProcessService.cs b/SysBot.Pokemon.Dodo/PokemonProcessService.cs index 5d4b290fa..a604e84cf 100644 --- a/SysBot.Pokemon.Dodo/PokemonProcessService.cs +++ b/SysBot.Pokemon.Dodo/PokemonProcessService.cs @@ -74,7 +74,7 @@ public override void ChannelMessageEvent( var p = GetPKM(downloadBytes); if (p is TP pkm) { - DodoHelper.StartTrade(pkm, eventBody.DodoSourceId, eventBody.Personal.NickName, eventBody.ChannelId); + DodoHelper.StartTrade(pkm, eventBody.DodoSourceId, eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId); } return; @@ -95,7 +95,7 @@ public override void ChannelMessageEvent( if (content.Trim().StartsWith("trade")) { content = content.Replace("trade", ""); - DodoHelper.StartTrade(content, eventBody.DodoSourceId, eventBody.Personal.NickName, eventBody.ChannelId); + DodoHelper.StartTrade(content, eventBody.DodoSourceId, eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId); return; } else if (content.Trim().StartsWith("dump")) @@ -108,7 +108,7 @@ public override void ChannelMessageEvent( if (!string.IsNullOrWhiteSpace(ps)) { LogUtil.LogInfo($"收到命令\n{ps}", LogIdentity); - DodoHelper.StartTrade(ps, eventBody.DodoSourceId, eventBody.Personal.NickName, eventBody.ChannelId); + DodoHelper.StartTrade(ps, eventBody.DodoSourceId, eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId); } else if (content.Contains("取消")) { diff --git a/SysBot.Pokemon/TradeHub/PokeTradeDetail.cs b/SysBot.Pokemon/TradeHub/PokeTradeDetail.cs index c4c5ec2f2..6075d3b1e 100644 --- a/SysBot.Pokemon/TradeHub/PokeTradeDetail.cs +++ b/SysBot.Pokemon/TradeHub/PokeTradeDetail.cs @@ -1,6 +1,7 @@ using PKHeX.Core; using System; using System.Threading; +using System.Collections.Generic; namespace SysBot.Pokemon { @@ -43,6 +44,9 @@ namespace SysBot.Pokemon ///

Indicates if the trade data is currently being traded. public bool IsProcessing; + /// Customized trade parameters. + public Dictionary Context = new(); + public PokeTradeDetail(TPoke pkm, PokeTradeTrainerInfo info, IPokeTradeNotifier notifier, PokeTradeType type, int code, bool favored = false) { ID = Interlocked.Increment(ref CreatedCount) % 3000; From 89db2e74c99dd3d3398b7bbf611269b947fffe94 Mon Sep 17 00:00:00 2001 From: Easy World Date: Mon, 30 Jan 2023 14:06:17 +0800 Subject: [PATCH 065/171] add missing parameter --- SysBot.Pokemon.Dodo/DodoTradeNotifier.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs b/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs index d01276a21..73ad6c203 100644 --- a/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs +++ b/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs @@ -36,7 +36,8 @@ public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail string tid = regex.Match(message).Groups[1].ToString(); regex = new Regex("SID: (\\d+)"); string sid = regex.Match(message).Groups[1].ToString(); - DodoBot.SendPersonalMessage(info.Trainer.ID.ToString(), $"找到你了,你的SID7:{sid},TID7:{tid}"); + DodoBot.SendPersonalMessage(info.Trainer.ID.ToString(), $"找到你了,你的SID7:{sid},TID7:{tid}", + info.Context.GetValueOrDefault("islandSourceId", "")); } } From b1fccbc30a54919881ea557a1ac470964049df0c Mon Sep 17 00:00:00 2001 From: Easy World Date: Mon, 30 Jan 2023 14:17:06 +0800 Subject: [PATCH 066/171] use DodoTradeNotifier instead of PokeTradeDetail --- SysBot.Pokemon.Dodo/DodoHelper.cs | 3 +-- SysBot.Pokemon.Dodo/DodoTradeNotifier.cs | 11 +++++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/SysBot.Pokemon.Dodo/DodoHelper.cs b/SysBot.Pokemon.Dodo/DodoHelper.cs index 4647301a3..6660390b6 100644 --- a/SysBot.Pokemon.Dodo/DodoHelper.cs +++ b/SysBot.Pokemon.Dodo/DodoHelper.cs @@ -165,11 +165,10 @@ private static bool AddToTradeQueue(T pk, int code, ulong userId, string name, s PokeRoutineType type, out string msg) { var trainer = new PokeTradeTrainerInfo(name, userId); - var notifier = new DodoTradeNotifier(pk, trainer, code, name, channelId); + var notifier = new DodoTradeNotifier(pk, trainer, code, name, channelId, islandSourceId); var tt = type == PokeRoutineType.SeedCheck ? PokeTradeType.Seed : (type == PokeRoutineType.Dump ? PokeTradeType.Dump : PokeTradeType.Specific); var detail = new PokeTradeDetail(pk, trainer, notifier, tt, code, true); - detail.Context.Add("islandSourceId", islandSourceId); var trade = new TradeEntry(detail, userId, type, name); var added = DodoBot.Info.AddToTradeQueue(trade, userId, false); diff --git a/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs b/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs index 73ad6c203..1b2faac4a 100644 --- a/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs +++ b/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs @@ -14,14 +14,17 @@ namespace SysBot.Pokemon.Dodo private string Username { get; } private string ChannelId { get; } + private string IslandSourceId { get; } - public DodoTradeNotifier(T data, PokeTradeTrainerInfo info, int code, string username, string channelId) + public DodoTradeNotifier(T data, PokeTradeTrainerInfo info, int code, string username, string channelId, + string islandSourceId) { Data = data; Info = info; Code = code; Username = username; ChannelId = channelId; + IslandSourceId = islandSourceId; LogUtil.LogText($"Created trade details for {Username} - {Code}"); } @@ -37,7 +40,7 @@ public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail regex = new Regex("SID: (\\d+)"); string sid = regex.Match(message).Groups[1].ToString(); DodoBot.SendPersonalMessage(info.Trainer.ID.ToString(), $"找到你了,你的SID7:{sid},TID7:{tid}", - info.Context.GetValueOrDefault("islandSourceId", "")); + IslandSourceId); } } @@ -71,7 +74,7 @@ public void TradeInitialize(PokeRoutineExecutor routine, PokeTradeDetail i DodoBot.SendChannelAtMessage(info.Trainer.ID, text, ChannelId); DodoBot.SendPersonalMessage(info.Trainer.ID.ToString(), $"派送:{ShowdownTranslator.GameStringsZh.Species[Data.Species]}\n密码:{info.Code:0000 0000}", - info.Context.GetValueOrDefault("islandSourceId", "")); + IslandSourceId); } public void TradeSearching(PokeRoutineExecutor routine, PokeTradeDetail info) @@ -83,7 +86,7 @@ public void TradeSearching(PokeRoutineExecutor routine, PokeTradeDetail in LogUtil.LogText(message); var text = $"派送:{ShowdownTranslator.GameStringsZh.Species[Data.Species]}\n密码:见私信\n状态:搜索中"; DodoBot.SendChannelMessage(text, ChannelId); - //DodoBot.SendPersonalMessage(info.Trainer.ID.ToString(), $"{info.Code:0000 0000}"); + //DodoBot.SendPersonalMessage(info.Trainer.ID.ToString(), $"{info.Code:0000 0000}", IslandSourceId); } public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, PokeTradeSummary message) From 016d44a2f93e958fe39f7b48381e694fa9068c48 Mon Sep 17 00:00:00 2001 From: Easy World Date: Mon, 30 Jan 2023 15:36:36 +0800 Subject: [PATCH 067/171] =?UTF-8?q?=E5=BC=82=E5=9B=BD=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SysBot.Pokemon.Dodo/DodoHelper.cs | 15 ++++++++++----- SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs | 16 ++++++++++++++-- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/SysBot.Pokemon.Dodo/DodoHelper.cs b/SysBot.Pokemon.Dodo/DodoHelper.cs index 6660390b6..2fd5e6e43 100644 --- a/SysBot.Pokemon.Dodo/DodoHelper.cs +++ b/SysBot.Pokemon.Dodo/DodoHelper.cs @@ -18,8 +18,9 @@ public static void StartTrade(string ps, string dodoId, string nickName, string DodoBot.SendChannelMessage(msg, channelId); return; } + var foreign = ps.Contains("Language: "); - StartTradeWithoutCheck(pkm, dodoId, nickName, channelId, islandSourceId); + StartTradeWithoutCheck(pkm, dodoId, nickName, channelId, islandSourceId, foreign); } public static void StartTrade(T pkm, string dodoId, string nickName, string channelId, string islandSourceId) @@ -34,10 +35,10 @@ public static void StartTrade(T pkm, string dodoId, string nickName, string chan StartTradeWithoutCheck(pkm, dodoId, nickName, channelId, islandSourceId); } - public static void StartTradeWithoutCheck(T pkm, string dodoId, string nickName, string channelId, string islandSourceId) + public static void StartTradeWithoutCheck(T pkm, string dodoId, string nickName, string channelId, string islandSourceId, bool foreign = false) { var code = DodoBot.Info.GetRandomTradeCode(); - var __ = AddToTradeQueue(pkm, code, ulong.Parse(dodoId), nickName, channelId, islandSourceId + var __ = AddToTradeQueue(pkm, code, ulong.Parse(dodoId), nickName, channelId, islandSourceId, foreign PokeRoutineType.LinkTrade, out string message); DodoBot.SendChannelMessage(message, channelId); } @@ -45,7 +46,7 @@ public static void StartTradeWithoutCheck(T pkm, string dodoId, string nickName, public static void StartDump(string dodoId, string nickName, string channelId) { var code = DodoBot.Info.GetRandomTradeCode(); - var __ = AddToTradeQueue(new T(), code, ulong.Parse(dodoId), nickName, channelId, + var __ = AddToTradeQueue(new T(), code, ulong.Parse(dodoId), nickName, channelId, "", false PokeRoutineType.Dump, out string message); DodoBot.SendChannelMessage(message, channelId); } @@ -161,7 +162,7 @@ public static bool CheckAndGetPkm(string setstring, string username, out string return false; } - private static bool AddToTradeQueue(T pk, int code, ulong userId, string name, string channelId, string islandSourceId + private static bool AddToTradeQueue(T pk, int code, ulong userId, string name, string channelId, string islandSourceId, bool foreign PokeRoutineType type, out string msg) { var trainer = new PokeTradeTrainerInfo(name, userId); @@ -169,6 +170,10 @@ private static bool AddToTradeQueue(T pk, int code, ulong userId, string name, s var tt = type == PokeRoutineType.SeedCheck ? PokeTradeType.Seed : (type == PokeRoutineType.Dump ? PokeTradeType.Dump : PokeTradeType.Specific); var detail = new PokeTradeDetail(pk, trainer, notifier, tt, code, true); + if (foreign) + { + detail.Context.Add("异国", "true"); + } var trade = new TradeEntry(detail, userId, type, name); var added = DodoBot.Info.AddToTradeQueue(trade, userId, false); diff --git a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs index b7402c890..893bdad7d 100644 --- a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs +++ b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs @@ -351,7 +351,8 @@ private async Task PerformLinkCodeTrade(SAV9SV sav, PokeTradeDe return partnerCheck; } - if (Hub.Config.Legality.UseTradePartnerInfo) + var needUseTradePartnerInfo = poke.Context.GetValueOrDefault("异国", "false") == "false"; + if (Hub.Config.Legality.UseTradePartnerInfo && needUseTradePartnerInfo) { await SetBoxPkmWithSwappedIDDetailsSV(toSend, tradePartnerFullInfo, sav, token); } @@ -1070,7 +1071,18 @@ private async Task SetBoxPkmWithSwappedIDDetailsSV(PK9 toSend, TradeMyStat cln.TrainerSID7 = (uint)Math.Abs(tradePartner.DisplaySID); cln.Language = tradePartner.Language; cln.OT_Name = tradePartner.OT; - cln.Version = tradePartner.Game; + if (toSend.Species == Species.Koraidon) + { + cln.Version = GameVersion.SL; + } + else if (toSend.Species == Species.Miraidon) + { + cln.Version = GameVersion.VL; + } + else + { + cln.Version = tradePartner.Game; + } cln.ClearNickname(); if (toSend.IsShiny) From 2aeeb5a0add72a16539b19f14283b90fe272f74d Mon Sep 17 00:00:00 2001 From: Easy World Date: Mon, 30 Jan 2023 15:53:40 +0800 Subject: [PATCH 068/171] teracave fix --- SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs index 893bdad7d..8203c051f 100644 --- a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs +++ b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs @@ -1071,13 +1071,17 @@ private async Task SetBoxPkmWithSwappedIDDetailsSV(PK9 toSend, TradeMyStat cln.TrainerSID7 = (uint)Math.Abs(tradePartner.DisplaySID); cln.Language = tradePartner.Language; cln.OT_Name = tradePartner.OT; - if (toSend.Species == Species.Koraidon) + + // copied from https://github.com/Wanghaoran86/TransFireBot/commit/f7c5b39ce2952818177a97babb8b3df027e673fb + if (toSend.Species == (ushort)Species.Koraidon) { cln.Version = GameVersion.SL; + Log("故勒顿,强制修改版本为朱"); } - else if (toSend.Species == Species.Miraidon) + else if (toSend.Species == (ushort)Species.Miraidon) { cln.Version = GameVersion.VL; + Log("密勒顿,强制修改版本为紫"); } else { @@ -1085,6 +1089,12 @@ private async Task SetBoxPkmWithSwappedIDDetailsSV(PK9 toSend, TradeMyStat } cln.ClearNickname(); + // thanks @Wanghaoran86 + if (toSend.Met_Location == Locations.TeraCavern9 && toSend.IsShiny) + { + cln.PID = (((uint)(cln.TID16 ^ cln.SID16) ^ (cln.PID & 0xFFFF) ^ 1u) << 16) | (cln.PID & 0xFFFF); + } + if (toSend.IsShiny) cln.SetShiny(); From 8e79a97a172d6b034fc82514097e9823eeebd1ec Mon Sep 17 00:00:00 2001 From: Easy World Date: Mon, 30 Jan 2023 20:09:26 +0800 Subject: [PATCH 069/171] compile fix --- SysBot.Pokemon.Dodo/DodoHelper.cs | 6 +++--- SysBot.Pokemon/Helpers/ShowdownTranslator.cs | 9 +++++---- SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs | 5 +++-- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/SysBot.Pokemon.Dodo/DodoHelper.cs b/SysBot.Pokemon.Dodo/DodoHelper.cs index 2fd5e6e43..ab273a027 100644 --- a/SysBot.Pokemon.Dodo/DodoHelper.cs +++ b/SysBot.Pokemon.Dodo/DodoHelper.cs @@ -38,7 +38,7 @@ public static void StartTrade(T pkm, string dodoId, string nickName, string chan public static void StartTradeWithoutCheck(T pkm, string dodoId, string nickName, string channelId, string islandSourceId, bool foreign = false) { var code = DodoBot.Info.GetRandomTradeCode(); - var __ = AddToTradeQueue(pkm, code, ulong.Parse(dodoId), nickName, channelId, islandSourceId, foreign + var __ = AddToTradeQueue(pkm, code, ulong.Parse(dodoId), nickName, channelId, islandSourceId, foreign, PokeRoutineType.LinkTrade, out string message); DodoBot.SendChannelMessage(message, channelId); } @@ -46,7 +46,7 @@ public static void StartTradeWithoutCheck(T pkm, string dodoId, string nickName, public static void StartDump(string dodoId, string nickName, string channelId) { var code = DodoBot.Info.GetRandomTradeCode(); - var __ = AddToTradeQueue(new T(), code, ulong.Parse(dodoId), nickName, channelId, "", false + var __ = AddToTradeQueue(new T(), code, ulong.Parse(dodoId), nickName, channelId, "", false, PokeRoutineType.Dump, out string message); DodoBot.SendChannelMessage(message, channelId); } @@ -162,7 +162,7 @@ public static bool CheckAndGetPkm(string setstring, string username, out string return false; } - private static bool AddToTradeQueue(T pk, int code, ulong userId, string name, string channelId, string islandSourceId, bool foreign + private static bool AddToTradeQueue(T pk, int code, ulong userId, string name, string channelId, string islandSourceId, bool foreign, PokeRoutineType type, out string msg) { var trainer = new PokeTradeTrainerInfo(name, userId); diff --git a/SysBot.Pokemon/Helpers/ShowdownTranslator.cs b/SysBot.Pokemon/Helpers/ShowdownTranslator.cs index 6248737fc..dfba65678 100644 --- a/SysBot.Pokemon/Helpers/ShowdownTranslator.cs +++ b/SysBot.Pokemon/Helpers/ShowdownTranslator.cs @@ -26,15 +26,16 @@ public static string Chinese2Showdown(string zh) if (candidateSpecieNo > 0) { - if (candidateSpecieNo == 29) result = "Nidoran-F"; - else if (candidateSpecieNo == 32) result = "Nidoran-M"; + if (candidateSpecieNo == (int)Species.NidoranF) result = "Nidoran-F"; + else if (candidateSpecieNo == (int)Species.NidoranM) result = "Nidoran-M"; else result += GameStringsEn.Species[candidateSpecieNo]; zh = zh.Replace(GameStringsZh.Species[candidateSpecieNo], ""); // 特殊性别差异 // 29-尼多兰F,32-尼多朗M,678-超能妙喵F,876-爱管侍F,902-幽尾玄鱼F, 916-飘香豚 - if ((candidateSpecieNo is 678 or 876 or 902 or 916) && zh.Contains("母")) result += "-F"; + if (((Species)candidateSpecieNo is Species.Meowstic or Species.Indeedee or Species.Basculegion or Species.Oinkologne) + && zh.Contains("母")) result += "-F"; } else { @@ -341,7 +342,7 @@ public static string Chinese2Showdown(string zh) } } - if (candidateSpecieNo == (int)Species.Ditto && zh.Contains("异国")) + if (zh.Contains("异国")) { result += "\nLanguage: Italian"; zh = zh.Replace("异国", ""); diff --git a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs index 8203c051f..402647e97 100644 --- a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs +++ b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using static SysBot.Base.SwitchButton; using static SysBot.Pokemon.PokeDataOffsetsSV; +using System.Collections.Generic; namespace SysBot.Pokemon { @@ -1075,12 +1076,12 @@ private async Task SetBoxPkmWithSwappedIDDetailsSV(PK9 toSend, TradeMyStat // copied from https://github.com/Wanghaoran86/TransFireBot/commit/f7c5b39ce2952818177a97babb8b3df027e673fb if (toSend.Species == (ushort)Species.Koraidon) { - cln.Version = GameVersion.SL; + cln.Version = (int)GameVersion.SL; Log("故勒顿,强制修改版本为朱"); } else if (toSend.Species == (ushort)Species.Miraidon) { - cln.Version = GameVersion.VL; + cln.Version = (int)GameVersion.VL; Log("密勒顿,强制修改版本为紫"); } else From 04e5be42610f058999dff4321b948c647f4b0ace Mon Sep 17 00:00:00 2001 From: Easy World Date: Mon, 30 Jan 2023 20:22:01 +0800 Subject: [PATCH 070/171] Update PokeTradeBotSV.cs --- SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs index 402647e97..31050f30d 100644 --- a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs +++ b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs @@ -1066,7 +1066,7 @@ private async Task SetBoxPkmWithSwappedIDDetailsSV(PK9 toSend, TradeMyStat Log($"Do nothing to trade Pokemon, since pokemon is Ditto"); return false; } - var cln = (PK9)toSend.Clone(); + var cln = toSend.Clone(); cln.OT_Gender = tradePartner.Gender; cln.TrainerTID7 = (uint)Math.Abs(tradePartner.DisplayTID); cln.TrainerSID7 = (uint)Math.Abs(tradePartner.DisplaySID); From fc90dc791f50808b3fd0becf5bf978cf04e6f7ee Mon Sep 17 00:00:00 2001 From: Easy World Date: Mon, 30 Jan 2023 20:24:56 +0800 Subject: [PATCH 071/171] Update PokeTradeBotSV.cs --- SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs index 7714300e0..3d233b5bc 100644 --- a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs +++ b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs @@ -1095,9 +1095,10 @@ private async Task SetBoxPkmWithSwappedIDDetailsSV(PK9 toSend, TradeMyStat { cln.PID = (((uint)(cln.TID16 ^ cln.SID16) ^ (cln.PID & 0xFFFF) ^ 1u) << 16) | (cln.PID & 0xFFFF); } - - if (toSend.IsShiny) + else if (toSend.IsShiny) + { cln.SetShiny(); + } cln.RefreshChecksum(); From a9a68b9bb22f0a1ba7715b6690d87d2655a3c9b5 Mon Sep 17 00:00:00 2001 From: Easy World Date: Thu, 2 Feb 2023 17:48:23 +0800 Subject: [PATCH 072/171] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=8A=80=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SysBot.Pokemon/Helpers/ShowdownTranslator.cs | 22 ++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/SysBot.Pokemon/Helpers/ShowdownTranslator.cs b/SysBot.Pokemon/Helpers/ShowdownTranslator.cs index dfba65678..c39fc48ee 100644 --- a/SysBot.Pokemon/Helpers/ShowdownTranslator.cs +++ b/SysBot.Pokemon/Helpers/ShowdownTranslator.cs @@ -301,6 +301,28 @@ public static string Chinese2Showdown(string zh) } } + // 添加技能 + for (int moveCount = 0; moveCount < 4; moveCount++) + { + int candidateIndex = -1; + int candidateLength = 0; + for (int i = 0; i < GameStringsZh.Move.Count; i++) + { + if (GameStringsZh.Move[i].Length == 0) continue; + if (!zh.Contains("-" + GameStringsZh.Move[i])) continue; + // 吸取 吸取拳 + if (candidateIndex == -1 || GameStringsZh.Move[i].Length > candidateLength) + { + candidateIndex = i; + candidateLength = GameStringsZh.Move[i].Length; + } + } + if (candidateIndex == -1) continue; + + result += $"\n-{GameStringsEn.Move[candidateIndex]}"; + zh = zh.Replace("-" + GameStringsZh.Move[candidateIndex], ""); + } + // 体型大小并添加证章 if (typeof(T) == typeof(PK9) && zh.Contains("大个子")) { From 8743a9675a06d120fa3adccbc4adf5cf2c29a2b5 Mon Sep 17 00:00:00 2001 From: Easy World Date: Thu, 2 Feb 2023 17:54:54 +0800 Subject: [PATCH 073/171] =?UTF-8?q?=E5=85=A8=E5=A5=96=E7=AB=A0=E6=94=AF?= =?UTF-8?q?=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SysBot.Pokemon/Helpers/ShowdownTranslator.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/SysBot.Pokemon/Helpers/ShowdownTranslator.cs b/SysBot.Pokemon/Helpers/ShowdownTranslator.cs index c39fc48ee..469344585 100644 --- a/SysBot.Pokemon/Helpers/ShowdownTranslator.cs +++ b/SysBot.Pokemon/Helpers/ShowdownTranslator.cs @@ -323,6 +323,12 @@ public static string Chinese2Showdown(string zh) zh = zh.Replace("-" + GameStringsZh.Move[candidateIndex], ""); } + // 补充后天获得的全奖章 注意开启Legality=>AllowBatchCommands + if (typeof(T) == typeof(PK9) && zh.Contains("全奖章")) + { + result += "\n.Ribbons=$suggestAll\n.RibbonMarkPartner=True\n.RibbonMarkGourmand=True"; + zh = zh.Replace("全奖章", ""); + } // 体型大小并添加证章 if (typeof(T) == typeof(PK9) && zh.Contains("大个子")) { @@ -350,7 +356,7 @@ public static string Chinese2Showdown(string zh) zh = zh.Replace("全招式", ""); } } - if (typeof(T) == typeof(PA8)) + else if (typeof(T) == typeof(PA8)) { if (zh.Contains("全技能")) { From bfaac0eb5e24e19916bda5cd913b7332797aace9 Mon Sep 17 00:00:00 2001 From: Easy World Date: Thu, 2 Feb 2023 23:58:17 +0800 Subject: [PATCH 074/171] SV support batch trade --- SysBot.Pokemon.Dodo/DodoHelper.cs | 51 +++++++- SysBot.Pokemon.Dodo/DodoTradeNotifier.cs | 10 ++ SysBot.Pokemon.Dodo/PokemonProcessService.cs | 5 + SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs | 117 ++++++++++--------- SysBot.Pokemon/TradeHub/PokeTradeDetail.cs | 2 +- 5 files changed, 129 insertions(+), 56 deletions(-) diff --git a/SysBot.Pokemon.Dodo/DodoHelper.cs b/SysBot.Pokemon.Dodo/DodoHelper.cs index ab273a027..1f7c87913 100644 --- a/SysBot.Pokemon.Dodo/DodoHelper.cs +++ b/SysBot.Pokemon.Dodo/DodoHelper.cs @@ -35,6 +35,45 @@ public static void StartTrade(T pkm, string dodoId, string nickName, string chan StartTradeWithoutCheck(pkm, dodoId, nickName, channelId, islandSourceId); } + public static void StartTradeMulti(string chinesePsRaw, string dodoId, string nickName, string channelId, string islandSourceId) + { + var chinesePss = chinesePsRaw.Split('+').ToList(); + List msgs = new List(); + List pkms = new List(); + List foreignList = new List(); + int invalidCount = 0; + for (var i = 0; i < chinesePss.Count; i++) + { + var ps = ShowdownTranslator.Chinese2Showdown(chinesePss[i]); + var _ = CheckAndGetPkm(ps, dodoId, out var msg, out var pkm); + if (!_) + { + LogUtil.LogInfo($"批量第{i+1}只宝可梦有问题:{msg}", nameof(DodoHelper)); + invalidCount++; + } + else + { + LogUtil.LogInfo($"批量第{i+1}只:\n{ps}", nameof(DodoHelper)); + foreignList.Add(ps.Contains("Language: ")); + pkms.Add(pkm); + } + } + if (invalidCount == chinesePss.Count) + { + DodoBot.SendChannelMessage("一个都不合法,换个屁", channelId); + return; + } + else if (invalidCount != 0) + { + DodoBot.SendChannelMessage($"期望交换的{chinesePss.Count}只宝可梦中,有{invalidCount}只不合法,仅交换合法的{pkms.Count}只", channelId); + } + + var code = DodoBot.Info.GetRandomTradeCode(); + var __ = AddToTradeQueue(pkms, code, ulong.Parse(dodoId), nickName, channelId, islandSourceId, foreignList, + PokeRoutineType.LinkTrade, out string message); + DodoBot.SendChannelMessage(message, channelId); + } + public static void StartTradeWithoutCheck(T pkm, string dodoId, string nickName, string channelId, string islandSourceId, bool foreign = false) { var code = DodoBot.Info.GetRandomTradeCode(); @@ -165,14 +204,22 @@ public static bool CheckAndGetPkm(string setstring, string username, out string private static bool AddToTradeQueue(T pk, int code, ulong userId, string name, string channelId, string islandSourceId, bool foreign, PokeRoutineType type, out string msg) { + return AddToTradeQueue(new List { pk }, code, userId, name, channelId, islandSourceId, new List{ foreign }, type, out msg); + } + + private static bool AddToTradeQueue(List pks, int code, ulong userId, string name, string channelId, string islandSourceId, List foreignList, + PokeRoutineType type, out string msg) + { + T pk = pks.FirstOrDefault(); var trainer = new PokeTradeTrainerInfo(name, userId); var notifier = new DodoTradeNotifier(pk, trainer, code, name, channelId, islandSourceId); var tt = type == PokeRoutineType.SeedCheck ? PokeTradeType.Seed : (type == PokeRoutineType.Dump ? PokeTradeType.Dump : PokeTradeType.Specific); var detail = new PokeTradeDetail(pk, trainer, notifier, tt, code, true); - if (foreign) + detail.Context.Add("异国", foreignList); + if (pks.Count > 0) { - detail.Context.Add("异国", "true"); + detail.Context.Add("批量", pks); } var trade = new TradeEntry(detail, userId, type, name); diff --git a/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs b/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs index 1b2faac4a..559af4a7c 100644 --- a/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs +++ b/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs @@ -1,6 +1,7 @@ using PKHeX.Core; using SysBot.Base; using System; +using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; @@ -42,6 +43,10 @@ public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail DodoBot.SendPersonalMessage(info.Trainer.ID.ToString(), $"找到你了,你的SID7:{sid},TID7:{tid}", IslandSourceId); } + else if (message.StartsWith("批量")) + { + DodoBot.SendPersonalMessage(info.Trainer.ID.ToString(), message, IslandSourceId); + } } public void TradeCanceled(PokeRoutineExecutor routine, PokeTradeDetail info, PokeTradeResult msg) @@ -71,6 +76,11 @@ public void TradeInitialize(PokeRoutineExecutor routine, PokeTradeDetail i msg += $" Your trade code is: {info.Code:0000 0000}"; LogUtil.LogText(msg); var text = $"\n派送:{ShowdownTranslator.GameStringsZh.Species[Data.Species]}\n密码:见私信\n状态:初始化"; + List batchPK9s = (List)info.Context.GetValueOrDefault("批量", new List()); + if (batchPK9s.Count > 0) + { + text = $"\n批量派送{batchPK9s.Count}只宝可梦\n密码:见私信\n状态:初始化"; + } DodoBot.SendChannelAtMessage(info.Trainer.ID, text, ChannelId); DodoBot.SendPersonalMessage(info.Trainer.ID.ToString(), $"派送:{ShowdownTranslator.GameStringsZh.Species[Data.Species]}\n密码:{info.Code:0000 0000}", diff --git a/SysBot.Pokemon.Dodo/PokemonProcessService.cs b/SysBot.Pokemon.Dodo/PokemonProcessService.cs index a604e84cf..787834d06 100644 --- a/SysBot.Pokemon.Dodo/PokemonProcessService.cs +++ b/SysBot.Pokemon.Dodo/PokemonProcessService.cs @@ -103,6 +103,11 @@ public override void ChannelMessageEvent( DodoHelper.StartDump(eventBody.DodoSourceId, eventBody.Personal.NickName, eventBody.ChannelId); return; } + else if (typeof(TP) == typeof(PK9) && content.Trim().StartsWith("批量"))// 仅SV支持批量,其他偷懒还没写 + { + DodoHelper.StartTradeMulti(content.Replace("批量", "").Trim(), eventBody.DodoSourceId, eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId); + return; + } var ps = ShowdownTranslator.Chinese2Showdown(content); if (!string.IsNullOrWhiteSpace(ps)) diff --git a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs index 3d233b5bc..de602c6e7 100644 --- a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs +++ b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs @@ -352,11 +352,6 @@ private async Task PerformLinkCodeTrade(SAV9SV sav, PokeTradeDe return partnerCheck; } - var needUseTradePartnerInfo = poke.Context.GetValueOrDefault("异国", "false") == "false"; - if (Hub.Config.Legality.UseTradePartnerInfo && needUseTradePartnerInfo) - { - await SetBoxPkmWithSwappedIDDetailsSV(toSend, tradePartnerFullInfo, sav, token); - } // Hard check to verify that the offset changed from the last thing offered from the previous trade. // This is because box opening times can vary per person, the offset persists between trades, and can also change offset between trades. var tradeOffered = await ReadUntilChanged(TradePartnerOfferedOffset, lastOffered, 10_000, 0_500, false, true, token).ConfigureAwait(false); @@ -374,62 +369,78 @@ private async Task PerformLinkCodeTrade(SAV9SV sav, PokeTradeDe await ExitTradeToPortal(false, token).ConfigureAwait(false); return result; } - - // Wait for user input... - var offered = await ReadUntilPresent(TradePartnerOfferedOffset, 25_000, 1_000, BoxFormatSlotSize, token).ConfigureAwait(false); - var oldEC = await SwitchConnection.ReadBytesAbsoluteAsync(TradePartnerOfferedOffset, 8, token).ConfigureAwait(false); - if (offered == null || offered.Species < 1 || !offered.ChecksumValid) - { - Log("Trade ended because a valid Pokémon was not offered."); - await ExitTradeToPortal(false, token).ConfigureAwait(false); - return PokeTradeResult.TrainerTooSlow; - } - - PokeTradeResult update; - var trainer = new PartnerDataHolder(0, tradePartner.TrainerName, tradePartner.TID7); - (toSend, update) = await GetEntityToSend(sav, poke, offered, oldEC, toSend, trainer, token).ConfigureAwait(false); - if (update != PokeTradeResult.Success) + List batchPK9s = (List)poke.Context.GetValueOrDefault("批量", new List { toSend }); + List foreignList = (List)poke.Context.GetValueOrDefault("异国", new List()); + for (var i = 0; i < batchPK9s.Count; i++) { - await ExitTradeToPortal(false, token).ConfigureAwait(false); - return update; - } + var pk9 = batchPK9s[i]; + if (batchPK9s.Count > 1) poke.SendNotification(this, $"批量:等待交换第{i+1}个宝可梦{ShowdownTranslator.GameStringsZh.Species[pk9.Species]}"); + + var needUseTradePartnerInfo = !foreignList[i]; + if (Hub.Config.Legality.UseTradePartnerInfo && needUseTradePartnerInfo) + { + await SetBoxPkmWithSwappedIDDetailsSV(toSend, tradePartnerFullInfo, sav, token); + } + else if (pk9.Species != 0) + { + await SetBoxPokemonAbsolute(BoxStartOffset, pk9, token, sav).ConfigureAwait(false); + } + // Wait for user input... + var offered = await ReadUntilPresent(TradePartnerOfferedOffset, 25_000, 1_000, BoxFormatSlotSize, token).ConfigureAwait(false); + var oldEC = await SwitchConnection.ReadBytesAbsoluteAsync(TradePartnerOfferedOffset, 8, token).ConfigureAwait(false); + if (offered == null || offered.Species < 1 || !offered.ChecksumValid) + { + Log("Trade ended because a valid Pokémon was not offered."); + await ExitTradeToPortal(false, token).ConfigureAwait(false); + return PokeTradeResult.TrainerTooSlow; + } - Log("Confirming trade."); - var tradeResult = await ConfirmAndStartTrading(poke, token).ConfigureAwait(false); - if (tradeResult != PokeTradeResult.Success) - { - await ExitTradeToPortal(false, token).ConfigureAwait(false); - return tradeResult; - } + PokeTradeResult update; + var trainer = new PartnerDataHolder(0, tradePartner.TrainerName, tradePartner.TID7); + (toSend, update) = await GetEntityToSend(sav, poke, offered, oldEC, toSend, trainer, token).ConfigureAwait(false); + if (update != PokeTradeResult.Success) + { + await ExitTradeToPortal(false, token).ConfigureAwait(false); + return update; + } - if (token.IsCancellationRequested) - { - StartFromOverworld = true; - LastTradeDistributionFixed = false; - await ExitTradeToPortal(false, token).ConfigureAwait(false); - return PokeTradeResult.RoutineCancel; - } + Log("Confirming trade."); + var tradeResult = await ConfirmAndStartTrading(poke, token).ConfigureAwait(false); + if (tradeResult != PokeTradeResult.Success) + { + await ExitTradeToPortal(false, token).ConfigureAwait(false); + return tradeResult; + } - // Trade was Successful! - var received = await ReadPokemon(BoxStartOffset, BoxFormatSlotSize, token).ConfigureAwait(false); - // Pokémon in b1s1 is same as the one they were supposed to receive (was never sent). - if (SearchUtil.HashByDetails(received) == SearchUtil.HashByDetails(toSend) && received.Checksum == toSend.Checksum) - { - Log("User did not complete the trade."); - await ExitTradeToPortal(false, token).ConfigureAwait(false); - return PokeTradeResult.TrainerTooSlow; - } + if (token.IsCancellationRequested) + { + StartFromOverworld = true; + LastTradeDistributionFixed = false; + await ExitTradeToPortal(false, token).ConfigureAwait(false); + return PokeTradeResult.RoutineCancel; + } - // As long as we got rid of our inject in b1s1, assume the trade went through. - Log("User completed the trade."); - poke.TradeFinished(this, received); + // Trade was Successful! + var received = await ReadPokemon(BoxStartOffset, BoxFormatSlotSize, token).ConfigureAwait(false); + // Pokémon in b1s1 is same as the one they were supposed to receive (was never sent). + if (SearchUtil.HashByDetails(received) == SearchUtil.HashByDetails(toSend) && received.Checksum == toSend.Checksum) + { + Log("User did not complete the trade."); + await ExitTradeToPortal(false, token).ConfigureAwait(false); + return PokeTradeResult.TrainerTooSlow; + } - // Only log if we completed the trade. - UpdateCountsAndExport(poke, received, toSend); + // As long as we got rid of our inject in b1s1, assume the trade went through. + Log("User completed the trade."); + poke.TradeFinished(this, received); - // Sometimes they offered another mon, so store that immediately upon leaving Union Room. - lastOffered = await SwitchConnection.ReadBytesAbsoluteAsync(TradePartnerOfferedOffset, 8, token).ConfigureAwait(false); + // Only log if we completed the trade. + UpdateCountsAndExport(poke, received, toSend); + // Sometimes they offered another mon, so store that immediately upon leaving Union Room. + lastOffered = await SwitchConnection.ReadBytesAbsoluteAsync(TradePartnerOfferedOffset, 8, token).ConfigureAwait(false); + } + await ExitTradeToPortal(false, token).ConfigureAwait(false); return PokeTradeResult.Success; } diff --git a/SysBot.Pokemon/TradeHub/PokeTradeDetail.cs b/SysBot.Pokemon/TradeHub/PokeTradeDetail.cs index 6075d3b1e..0f721c6c0 100644 --- a/SysBot.Pokemon/TradeHub/PokeTradeDetail.cs +++ b/SysBot.Pokemon/TradeHub/PokeTradeDetail.cs @@ -45,7 +45,7 @@ namespace SysBot.Pokemon public bool IsProcessing; /// Customized trade parameters. - public Dictionary Context = new(); + public Dictionary Context = new(); public PokeTradeDetail(TPoke pkm, PokeTradeTrainerInfo info, IPokeTradeNotifier notifier, PokeTradeType type, int code, bool favored = false) { From 0f5afe3527718d3b98de137139e846f1cd4c1b42 Mon Sep 17 00:00:00 2001 From: Easy World Date: Fri, 3 Feb 2023 00:45:15 +0800 Subject: [PATCH 075/171] =?UTF-8?q?=E6=89=B9=E9=87=8F=E6=9C=80=E5=A4=A7?= =?UTF-8?q?=E6=95=B0=E9=87=8F=E6=94=AF=E6=8C=81=E9=85=8D=E7=BD=AE=EF=BC=8C?= =?UTF-8?q?=E4=B8=BA0=E6=97=B6=E5=85=B3=E9=97=AD=E6=89=B9=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SysBot.Pokemon.Dodo/DodoHelper.cs | 11 +++++++++++ SysBot.Pokemon/SWSH/BotTrade/TradeSettings.cs | 2 ++ 2 files changed, 13 insertions(+) diff --git a/SysBot.Pokemon.Dodo/DodoHelper.cs b/SysBot.Pokemon.Dodo/DodoHelper.cs index 1f7c87913..eac591efa 100644 --- a/SysBot.Pokemon.Dodo/DodoHelper.cs +++ b/SysBot.Pokemon.Dodo/DodoHelper.cs @@ -38,6 +38,17 @@ public static void StartTrade(T pkm, string dodoId, string nickName, string chan public static void StartTradeMulti(string chinesePsRaw, string dodoId, string nickName, string channelId, string islandSourceId) { var chinesePss = chinesePsRaw.Split('+').ToList(); + var maxQuantityPerTrade = DodoBot.Info.Hub.Config.Trade.MaxQuantityPerTrade; + if (maxQuantityPerTrade == 0) + { + DodoBot.SendChannelMessage("批量未开启", channelId); + return; + } + else if (chinesePss.Count > maxQuantityPerTrade) + { + DodoBot.SendChannelMessage($"批量交换宝可梦数量应小于等于{maxQuantityPerTrade}", channelId); + return; + } List msgs = new List(); List pkms = new List(); List foreignList = new List(); diff --git a/SysBot.Pokemon/SWSH/BotTrade/TradeSettings.cs b/SysBot.Pokemon/SWSH/BotTrade/TradeSettings.cs index d07346023..cc737f8f7 100644 --- a/SysBot.Pokemon/SWSH/BotTrade/TradeSettings.cs +++ b/SysBot.Pokemon/SWSH/BotTrade/TradeSettings.cs @@ -38,6 +38,8 @@ public class TradeSettings : IBotStateSettings, ICountSettings [Category(TradeConfig), Description("When enabled, the screen will be turned off during normal bot loop operation to save power.")] public bool ScreenOff { get; set; } + [Category(TradeCode), Description("Maximum quantity per trade when using batch mode. Batch mode will be closed if this configuration is zero")] + public int MaxQuantityPerTrade { get; set; } = 0; /// /// Gets a random trade code based on the range settings. /// From f3a62fa5a871469282c80c2564977edcea8ab1db Mon Sep 17 00:00:00 2001 From: Easy World Date: Fri, 3 Feb 2023 01:11:00 +0800 Subject: [PATCH 076/171] =?UTF-8?q?=E6=96=87=E6=A1=88=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SysBot.Pokemon.Dodo/DodoTradeNotifier.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs b/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs index 559af4a7c..a0197fe0d 100644 --- a/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs +++ b/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs @@ -77,7 +77,7 @@ public void TradeInitialize(PokeRoutineExecutor routine, PokeTradeDetail i LogUtil.LogText(msg); var text = $"\n派送:{ShowdownTranslator.GameStringsZh.Species[Data.Species]}\n密码:见私信\n状态:初始化"; List batchPK9s = (List)info.Context.GetValueOrDefault("批量", new List()); - if (batchPK9s.Count > 0) + if (batchPK9s.Count > 1) { text = $"\n批量派送{batchPK9s.Count}只宝可梦\n密码:见私信\n状态:初始化"; } From 63a0d6755c799f2dda7df45df29bbefb857bbe0a Mon Sep 17 00:00:00 2001 From: DanbyD <122141417+DanbyD@users.noreply.github.com> Date: Fri, 3 Feb 2023 09:05:00 +0800 Subject: [PATCH 077/171] Add files via upload --- SysBot.Pokemon/Queues/TradeQueueInfo.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/SysBot.Pokemon/Queues/TradeQueueInfo.cs b/SysBot.Pokemon/Queues/TradeQueueInfo.cs index dbd9fa736..ebb5bde99 100644 --- a/SysBot.Pokemon/Queues/TradeQueueInfo.cs +++ b/SysBot.Pokemon/Queues/TradeQueueInfo.cs @@ -155,6 +155,13 @@ public IEnumerable GetUserList(string fmt) { return UsersInQueue.Select(z => string.Format(fmt, z.Trade.ID, z.Trade.Code, z.Trade.Type, z.Username, (Species)z.Trade.TradeData.Species)); } + } + public IEnumerable GetUserIdList(int count) + { + lock (_sync) + { + return UsersInQueue.Take(count).Select(z => z.Trade.Trainer.ID); + } } public IList> GetIsUserQueued(Func, bool> match) From 5735fbdb8cf5edf0e69ae1668b236bf4e474f281 Mon Sep 17 00:00:00 2001 From: Easy World Date: Fri, 3 Feb 2023 09:06:58 +0800 Subject: [PATCH 078/171] Update PokeTradeBotSV.cs --- SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs index de602c6e7..49ca1e246 100644 --- a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs +++ b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs @@ -379,7 +379,7 @@ private async Task PerformLinkCodeTrade(SAV9SV sav, PokeTradeDe var needUseTradePartnerInfo = !foreignList[i]; if (Hub.Config.Legality.UseTradePartnerInfo && needUseTradePartnerInfo) { - await SetBoxPkmWithSwappedIDDetailsSV(toSend, tradePartnerFullInfo, sav, token); + await SetBoxPkmWithSwappedIDDetailsSV(pk9, tradePartnerFullInfo, sav, token); } else if (pk9.Species != 0) { From fc789fce048c6a20407e0ef38a4cdea68cca1817 Mon Sep 17 00:00:00 2001 From: Easy World Date: Fri, 3 Feb 2023 10:06:39 +0800 Subject: [PATCH 079/171] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=BC=82=E5=9B=BD?= =?UTF-8?q?=E6=95=B0=E7=BB=84=E9=97=AE=E9=A2=98=EF=BC=8C=E8=B0=83=E6=95=B4?= =?UTF-8?q?=E6=89=B9=E9=87=8F=E9=80=9A=E7=9F=A5=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 新增了一些功能,引入了一些bug --- SysBot.Pokemon.Dodo/DodoTradeNotifier.cs | 2 +- SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs | 41 ++++++++++---------- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs b/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs index a0197fe0d..09ab9ae25 100644 --- a/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs +++ b/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs @@ -45,7 +45,7 @@ public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail } else if (message.StartsWith("批量")) { - DodoBot.SendPersonalMessage(info.Trainer.ID.ToString(), message, IslandSourceId); + DodoBot.SendChannelMessage(message, ChannelId); } } diff --git a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs index 49ca1e246..8ed4582c5 100644 --- a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs +++ b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs @@ -370,7 +370,8 @@ private async Task PerformLinkCodeTrade(SAV9SV sav, PokeTradeDe return result; } List batchPK9s = (List)poke.Context.GetValueOrDefault("批量", new List { toSend }); - List foreignList = (List)poke.Context.GetValueOrDefault("异国", new List()); + List foreignList = (List)poke.Context.GetValueOrDefault("异国", new List { false }); + LogUtil.LogInfo($"count:{batchPK9s.Count}, foreignList:{String.Join(',', foreignList)}", nameof(PokeTradeBotSV)); for (var i = 0; i < batchPK9s.Count; i++) { var pk9 = batchPK9s[i]; @@ -411,6 +412,7 @@ private async Task PerformLinkCodeTrade(SAV9SV sav, PokeTradeDe await ExitTradeToPortal(false, token).ConfigureAwait(false); return tradeResult; } + if (batchPK9s.Count > 1) poke.SendNotification(this, $"批量:第{i + 1}个宝可梦{ShowdownTranslator.GameStringsZh.Species[pk9.Species]}交换完成"); if (token.IsCancellationRequested) { @@ -419,27 +421,26 @@ private async Task PerformLinkCodeTrade(SAV9SV sav, PokeTradeDe await ExitTradeToPortal(false, token).ConfigureAwait(false); return PokeTradeResult.RoutineCancel; } + } + // Trade was Successful! + var received = await ReadPokemon(BoxStartOffset, BoxFormatSlotSize, token).ConfigureAwait(false); + // Pokémon in b1s1 is same as the one they were supposed to receive (was never sent). + if (SearchUtil.HashByDetails(received) == SearchUtil.HashByDetails(toSend) && received.Checksum == toSend.Checksum) + { + Log("User did not complete the trade."); + await ExitTradeToPortal(false, token).ConfigureAwait(false); + return PokeTradeResult.TrainerTooSlow; + } + + // As long as we got rid of our inject in b1s1, assume the trade went through. + Log("User completed the trade."); + poke.TradeFinished(this, received); - // Trade was Successful! - var received = await ReadPokemon(BoxStartOffset, BoxFormatSlotSize, token).ConfigureAwait(false); - // Pokémon in b1s1 is same as the one they were supposed to receive (was never sent). - if (SearchUtil.HashByDetails(received) == SearchUtil.HashByDetails(toSend) && received.Checksum == toSend.Checksum) - { - Log("User did not complete the trade."); - await ExitTradeToPortal(false, token).ConfigureAwait(false); - return PokeTradeResult.TrainerTooSlow; - } - - // As long as we got rid of our inject in b1s1, assume the trade went through. - Log("User completed the trade."); - poke.TradeFinished(this, received); - - // Only log if we completed the trade. - UpdateCountsAndExport(poke, received, toSend); + // Only log if we completed the trade. + UpdateCountsAndExport(poke, received, toSend); - // Sometimes they offered another mon, so store that immediately upon leaving Union Room. - lastOffered = await SwitchConnection.ReadBytesAbsoluteAsync(TradePartnerOfferedOffset, 8, token).ConfigureAwait(false); - } + // Sometimes they offered another mon, so store that immediately upon leaving Union Room. + lastOffered = await SwitchConnection.ReadBytesAbsoluteAsync(TradePartnerOfferedOffset, 8, token).ConfigureAwait(false); await ExitTradeToPortal(false, token).ConfigureAwait(false); return PokeTradeResult.Success; From 1d07728e427d25bfd9b2a7ddd3f0d5b669936dcf Mon Sep 17 00:00:00 2001 From: DanbyD <122141417+DanbyD@users.noreply.github.com> Date: Fri, 3 Feb 2023 13:04:48 +0800 Subject: [PATCH 080/171] Add files via upload --- SysBot.Pokemon.Dodo/DodoTradeNotifier.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs b/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs index 09ab9ae25..8d8d9d2e0 100644 --- a/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs +++ b/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs @@ -54,7 +54,12 @@ public void TradeCanceled(PokeRoutineExecutor routine, PokeTradeDetail inf OnFinish?.Invoke(routine); var line = $"@{info.Trainer.TrainerName}: Trade canceled, {msg}"; LogUtil.LogText(line); - DodoBot.SendChannelAtMessage(info.Trainer.ID, "取消", ChannelId); + DodoBot.SendChannelAtMessage(info.Trainer.ID, "取消", ChannelId); + var waitUserIds = DodoBot.Info.GetUserIdList(4).ToList(); + for (int i = 1; i < waitUserIds.Count(); i++) + { + DodoBot.SendChannelAtMessage(waitUserIds[i], $"你在第{i + 1}位,还有{i}个以后就到你了!\n", ChannelId); + } } public void TradeFinished(PokeRoutineExecutor routine, PokeTradeDetail info, T result) @@ -65,7 +70,12 @@ public void TradeFinished(PokeRoutineExecutor routine, PokeTradeDetail inf ? $"Trade finished. Enjoy your {(Species) tradedToUser}!" : "Trade finished!"); LogUtil.LogText(message); - DodoBot.SendChannelAtMessage(info.Trainer.ID, "完成", ChannelId); + DodoBot.SendChannelAtMessage(info.Trainer.ID, "完成", ChannelId); + var waitUserIds = DodoBot.Info.GetUserIdList(4).ToList(); + for (int i = 1; i < waitUserIds.Count(); i++) + { + DodoBot.SendChannelAtMessage(waitUserIds[i], $"你在第{i + 1}位,还有{i}个以后就到你了!\n", ChannelId); + } } public void TradeInitialize(PokeRoutineExecutor routine, PokeTradeDetail info) From 2818d15edde512fb44112c6da7722c504234f2a1 Mon Sep 17 00:00:00 2001 From: Easy World Date: Sun, 5 Feb 2023 23:27:44 +0800 Subject: [PATCH 081/171] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index c2e7c7e61..34b0905d5 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,8 @@ Most codes are based on [SysBot.Pokemon.Twitch](https://github.com/kwsch/SysBot. Uses [Mirai.Net](https://github.com/SinoAHpx/Mirai.Net) as a dependency via Nuget. +Document: [搭建指南](https://github.com/easyworld/SysBot.NET/tree/master/SysBot.Pokemon.QQ), [命令指南](https://docs.qq.com/doc/DSVlldkxMSW92VXZF) + ## SysBot.Pokemon.Dodo: - Support [ALM-Showdown-Sets](https://github.com/architdate/PKHeX-Plugins/wiki/ALM-Showdown-Sets) - Support PK8 PB8 PA8 PK9 file upload @@ -59,6 +61,8 @@ Most codes are based on [SysBot.Pokemon.Twitch](https://github.com/kwsch/SysBot. Uses [dodo-open-net](https://github.com/dodo-open/dodo-open-net) as a dependency via Nuget. +Document: [搭建指南](https://docs.qq.com/doc/DSVVZZk9saUNTeHNn), [命令指南](https://docs.qq.com/doc/DSVlldkxMSW92VXZF) + ## Other Dependencies Pokémon API logic is provided by [PKHeX](https://github.com/kwsch/PKHeX/), and template generation is provided by [AutoMod](https://github.com/architdate/PKHeX-Plugins/). From a88f3db7cdde3fc92fb536623c2c6d77eeb8eadc Mon Sep 17 00:00:00 2001 From: Easy World Date: Mon, 6 Feb 2023 23:12:14 +0800 Subject: [PATCH 082/171] =?UTF-8?q?MaxQuantityPerTrade=E6=94=B9=E4=B8=BAMa?= =?UTF-8?q?xPkmsPerTrade?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SysBot.Pokemon.Dodo/DodoHelper.cs | 25 +++++++++++-------- SysBot.Pokemon/SWSH/BotTrade/TradeSettings.cs | 4 +-- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/SysBot.Pokemon.Dodo/DodoHelper.cs b/SysBot.Pokemon.Dodo/DodoHelper.cs index eac591efa..dbab62cff 100644 --- a/SysBot.Pokemon.Dodo/DodoHelper.cs +++ b/SysBot.Pokemon.Dodo/DodoHelper.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; using PKHeX.Core; using SysBot.Base; @@ -38,20 +36,20 @@ public static void StartTrade(T pkm, string dodoId, string nickName, string chan public static void StartTradeMulti(string chinesePsRaw, string dodoId, string nickName, string channelId, string islandSourceId) { var chinesePss = chinesePsRaw.Split('+').ToList(); - var maxQuantityPerTrade = DodoBot.Info.Hub.Config.Trade.MaxQuantityPerTrade; - if (maxQuantityPerTrade == 0) + var MaxPkmsPerTrade = DodoBot.Info.Hub.Config.Trade.MaxPkmsPerTrade; + if (MaxPkmsPerTrade <= 1) { - DodoBot.SendChannelMessage("批量未开启", channelId); + DodoBot.SendChannelMessage("请联系群主将trade/MaxPkmsPerTrade配置改为大于1", channelId); return; } - else if (chinesePss.Count > maxQuantityPerTrade) + else if (chinesePss.Count > MaxPkmsPerTrade) { - DodoBot.SendChannelMessage($"批量交换宝可梦数量应小于等于{maxQuantityPerTrade}", channelId); + DodoBot.SendChannelMessage($"批量交换宝可梦数量应小于等于{MaxPkmsPerTrade}", channelId); return; } - List msgs = new List(); - List pkms = new List(); - List foreignList = new List(); + List msgs = new(); + List pkms = new(); + List foreignList = new(); int invalidCount = 0; for (var i = 0; i < chinesePss.Count; i++) { @@ -221,7 +219,12 @@ private static bool AddToTradeQueue(T pk, int code, ulong userId, string name, s private static bool AddToTradeQueue(List pks, int code, ulong userId, string name, string channelId, string islandSourceId, List foreignList, PokeRoutineType type, out string msg) { - T pk = pks.FirstOrDefault(); + if (pks == null || pks.Count == 0) + { + msg = $"宝可梦数据为空"; + return false; + } + T pk = pks.First(); var trainer = new PokeTradeTrainerInfo(name, userId); var notifier = new DodoTradeNotifier(pk, trainer, code, name, channelId, islandSourceId); var tt = type == PokeRoutineType.SeedCheck ? PokeTradeType.Seed : (type == PokeRoutineType.Dump ? PokeTradeType.Dump : PokeTradeType.Specific); diff --git a/SysBot.Pokemon/SWSH/BotTrade/TradeSettings.cs b/SysBot.Pokemon/SWSH/BotTrade/TradeSettings.cs index cc737f8f7..1ab3c11d2 100644 --- a/SysBot.Pokemon/SWSH/BotTrade/TradeSettings.cs +++ b/SysBot.Pokemon/SWSH/BotTrade/TradeSettings.cs @@ -38,8 +38,8 @@ public class TradeSettings : IBotStateSettings, ICountSettings [Category(TradeConfig), Description("When enabled, the screen will be turned off during normal bot loop operation to save power.")] public bool ScreenOff { get; set; } - [Category(TradeCode), Description("Maximum quantity per trade when using batch mode. Batch mode will be closed if this configuration is zero")] - public int MaxQuantityPerTrade { get; set; } = 0; + [Category(TradeCode), Description("Maximum pokemons of single trade. Batch mode will be closed if this configuration is less than 1")] + public int MaxPkmsPerTrade { get; set; } = 1; /// /// Gets a random trade code based on the range settings. /// From 81f5a7fcfe3f0528b28c084aadfcbb62ef087391 Mon Sep 17 00:00:00 2001 From: Easy World Date: Mon, 6 Feb 2023 23:12:44 +0800 Subject: [PATCH 083/171] remove compile warning --- SysBot.Pokemon.Dodo/DodoBot.cs | 6 +++--- SysBot.Pokemon.Dodo/DodoTradeNotifier.cs | 2 +- SysBot.Pokemon.Dodo/PokemonProcessService.cs | 10 +++++----- SysBot.Pokemon.QQ/MiraiQQBot.cs | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/SysBot.Pokemon.Dodo/DodoBot.cs b/SysBot.Pokemon.Dodo/DodoBot.cs index 0fcf334f7..37ec2206d 100644 --- a/SysBot.Pokemon.Dodo/DodoBot.cs +++ b/SysBot.Pokemon.Dodo/DodoBot.cs @@ -15,9 +15,9 @@ namespace SysBot.Pokemon.Dodo private static PokeTradeHub Hub = default!; internal static TradeQueueInfo Info => Hub.Queues.Info; - public static OpenApiService OpenApiService; + public static OpenApiService OpenApiService = default!; - private static DodoSettings Settings; + private static DodoSettings Settings = default!; public DodoBot(DodoSettings settings, PokeTradeHub hub) { @@ -101,7 +101,7 @@ public static void SendPersonalMessage(string dodoId, string message, string isl if (string.IsNullOrEmpty(message)) return; if (string.IsNullOrWhiteSpace(islandSourceId)) { - islandSourceId = OpenApiService.GetIslandList(new GetIslandListInput()).FirstOrDefault().IslandSourceId ?? ""; + islandSourceId = OpenApiService.GetIslandList(new GetIslandListInput()).FirstOrDefault()?.IslandSourceId ?? ""; } OpenApiService.SetPersonalMessageSend(new SetPersonalMessageSendInput { diff --git a/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs b/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs index 8d8d9d2e0..a8681b484 100644 --- a/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs +++ b/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs @@ -29,7 +29,7 @@ public DodoTradeNotifier(T data, PokeTradeTrainerInfo info, int code, string use LogUtil.LogText($"Created trade details for {Username} - {Code}"); } - public Action> OnFinish { private get; set; } + public Action>? OnFinish { private get; set; } public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, string message) { diff --git a/SysBot.Pokemon.Dodo/PokemonProcessService.cs b/SysBot.Pokemon.Dodo/PokemonProcessService.cs index 787834d06..13d9e0b2d 100644 --- a/SysBot.Pokemon.Dodo/PokemonProcessService.cs +++ b/SysBot.Pokemon.Dodo/PokemonProcessService.cs @@ -17,7 +17,7 @@ namespace SysBot.Pokemon.Dodo private static readonly string LogIdentity = "DodoBot"; private static readonly string Welcome = "at我并尝试对我说:\n皮卡丘\ntrade ps代码\n或者直接拖一个文件进来"; private readonly string _channelId; - private string _botDodoSourceId; + private string _botDodoSourceId = default!; public PokemonProcessService(OpenApiService openApiService, string channelId) { @@ -179,12 +179,12 @@ private static bool ValidFileName(string fileName) || typeof(TP) == typeof(PK9) && fileName.EndsWith("pk9", StringComparison.OrdinalIgnoreCase)); } - private static PKM GetPKM(byte[] bytes) + private static PKM? GetPKM(byte[] bytes) { if (typeof(TP) == typeof(PK8)) return new PK8(bytes); - if (typeof(TP) == typeof(PB8)) return new PB8(bytes); - if (typeof(TP) == typeof(PA8)) return new PA8(bytes); - if (typeof(TP) == typeof(PK9)) return new PK9(bytes); + else if (typeof(TP) == typeof(PB8)) return new PB8(bytes); + else if (typeof(TP) == typeof(PA8)) return new PA8(bytes); + else if (typeof(TP) == typeof(PK9)) return new PK9(bytes); return null; } diff --git a/SysBot.Pokemon.QQ/MiraiQQBot.cs b/SysBot.Pokemon.QQ/MiraiQQBot.cs index 7af79c324..ce85c943a 100644 --- a/SysBot.Pokemon.QQ/MiraiQQBot.cs +++ b/SysBot.Pokemon.QQ/MiraiQQBot.cs @@ -13,6 +13,7 @@ using Mirai.Net.Utils.Scaffolds; using System.Text.RegularExpressions; using Mirai.Net.Data.Events.Concretes.Group; +using System.Net.Http; namespace SysBot.Pokemon.QQ { @@ -217,9 +218,8 @@ private async Task HandleFileUpload(GroupMessageReceiver receiver) try { var f = await FileManager.GetFileAsync(groupId, fileMessage.FileId, true); - - string url = f.DownloadInfo.Url; - byte[] data = new System.Net.WebClient().DownloadData(url); + using var client = new HttpClient(); + byte[] data = client.GetByteArrayAsync(f.DownloadInfo.Url).Result; switch (operationType) { case "pk8" or "pb8" or "pk9" when data.Length != 344: From d7519406c9a06c0a4fe1eecbe335564d78ac76fa Mon Sep 17 00:00:00 2001 From: Easy World Date: Mon, 6 Feb 2023 23:36:01 +0800 Subject: [PATCH 084/171] Update DodoHelper.cs --- SysBot.Pokemon.Dodo/DodoHelper.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/SysBot.Pokemon.Dodo/DodoHelper.cs b/SysBot.Pokemon.Dodo/DodoHelper.cs index dbab62cff..827d54c24 100644 --- a/SysBot.Pokemon.Dodo/DodoHelper.cs +++ b/SysBot.Pokemon.Dodo/DodoHelper.cs @@ -76,6 +76,10 @@ public static void StartTradeMulti(string chinesePsRaw, string dodoId, string ni { DodoBot.SendChannelMessage($"期望交换的{chinesePss.Count}只宝可梦中,有{invalidCount}只不合法,仅交换合法的{pkms.Count}只", channelId); } + else + { + DodoBot.SendChannelMessage($"开始批量交换{pkms.Count}只宝可梦", channelId); + } var code = DodoBot.Info.GetRandomTradeCode(); var __ = AddToTradeQueue(pkms, code, ulong.Parse(dodoId), nickName, channelId, islandSourceId, foreignList, From 2d4db7c6d4debdc25a720062f5c7e86b8752f1f4 Mon Sep 17 00:00:00 2001 From: Easy World Date: Mon, 6 Feb 2023 23:55:06 +0800 Subject: [PATCH 085/171] Revert "Update DodoHelper.cs" This reverts commit d7519406c9a06c0a4fe1eecbe335564d78ac76fa. --- SysBot.Pokemon.Dodo/DodoHelper.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/SysBot.Pokemon.Dodo/DodoHelper.cs b/SysBot.Pokemon.Dodo/DodoHelper.cs index 827d54c24..dbab62cff 100644 --- a/SysBot.Pokemon.Dodo/DodoHelper.cs +++ b/SysBot.Pokemon.Dodo/DodoHelper.cs @@ -76,10 +76,6 @@ public static void StartTradeMulti(string chinesePsRaw, string dodoId, string ni { DodoBot.SendChannelMessage($"期望交换的{chinesePss.Count}只宝可梦中,有{invalidCount}只不合法,仅交换合法的{pkms.Count}只", channelId); } - else - { - DodoBot.SendChannelMessage($"开始批量交换{pkms.Count}只宝可梦", channelId); - } var code = DodoBot.Info.GetRandomTradeCode(); var __ = AddToTradeQueue(pkms, code, ulong.Parse(dodoId), nickName, channelId, islandSourceId, foreignList, From ffc6cdb1f4e3d958c28f4f6ddc87faa8d81bad81 Mon Sep 17 00:00:00 2001 From: Easy World Date: Tue, 7 Feb 2023 22:08:41 +0800 Subject: [PATCH 086/171] Update DodoTradeNotifier.cs --- SysBot.Pokemon.Dodo/DodoTradeNotifier.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs b/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs index a8681b484..585e1884c 100644 --- a/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs +++ b/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs @@ -105,6 +105,11 @@ public void TradeSearching(PokeRoutineExecutor routine, PokeTradeDetail in message += $" Your trade code is: {info.Code:0000 0000}"; LogUtil.LogText(message); var text = $"派送:{ShowdownTranslator.GameStringsZh.Species[Data.Species]}\n密码:见私信\n状态:搜索中"; + List batchPK9s = (List)info.Context.GetValueOrDefault("批量", new List()); + if (batchPK9s.Count > 1) + { + text = $"批量派送{batchPK9s.Count}只宝可梦\n密码:见私信\n状态:搜索中"; + } DodoBot.SendChannelMessage(text, ChannelId); //DodoBot.SendPersonalMessage(info.Trainer.ID.ToString(), $"{info.Code:0000 0000}", IslandSourceId); } From 8246126cd6b23718673d6cf349e93b31600867d8 Mon Sep 17 00:00:00 2001 From: Easy World Date: Tue, 7 Feb 2023 22:24:05 +0800 Subject: [PATCH 087/171] Update PokeTradeBotSV.cs --- SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs | 27 +++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs index 8ed4582c5..96075cfd8 100644 --- a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs +++ b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs @@ -371,6 +371,7 @@ private async Task PerformLinkCodeTrade(SAV9SV sav, PokeTradeDe } List batchPK9s = (List)poke.Context.GetValueOrDefault("批量", new List { toSend }); List foreignList = (List)poke.Context.GetValueOrDefault("异国", new List { false }); + PK9 received = default!; LogUtil.LogInfo($"count:{batchPK9s.Count}, foreignList:{String.Join(',', foreignList)}", nameof(PokeTradeBotSV)); for (var i = 0; i < batchPK9s.Count; i++) { @@ -398,7 +399,7 @@ private async Task PerformLinkCodeTrade(SAV9SV sav, PokeTradeDe PokeTradeResult update; var trainer = new PartnerDataHolder(0, tradePartner.TrainerName, tradePartner.TID7); - (toSend, update) = await GetEntityToSend(sav, poke, offered, oldEC, toSend, trainer, token).ConfigureAwait(false); + (pk9, update) = await GetEntityToSend(sav, poke, offered, oldEC, pk9, trainer, token).ConfigureAwait(false); if (update != PokeTradeResult.Success) { await ExitTradeToPortal(false, token).ConfigureAwait(false); @@ -421,23 +422,25 @@ private async Task PerformLinkCodeTrade(SAV9SV sav, PokeTradeDe await ExitTradeToPortal(false, token).ConfigureAwait(false); return PokeTradeResult.RoutineCancel; } - } - // Trade was Successful! - var received = await ReadPokemon(BoxStartOffset, BoxFormatSlotSize, token).ConfigureAwait(false); - // Pokémon in b1s1 is same as the one they were supposed to receive (was never sent). - if (SearchUtil.HashByDetails(received) == SearchUtil.HashByDetails(toSend) && received.Checksum == toSend.Checksum) - { - Log("User did not complete the trade."); - await ExitTradeToPortal(false, token).ConfigureAwait(false); - return PokeTradeResult.TrainerTooSlow; + + // Trade was Successful! + received = await ReadPokemon(BoxStartOffset, BoxFormatSlotSize, token).ConfigureAwait(false); + // Pokémon in b1s1 is same as the one they were supposed to receive (was never sent). + if (SearchUtil.HashByDetails(received) == SearchUtil.HashByDetails(pk9) && received.Checksum == pk9.Checksum) + { + Log("User did not complete the trade."); + await ExitTradeToPortal(false, token).ConfigureAwait(false); + return PokeTradeResult.TrainerTooSlow; + } + + // Only log if we completed the trade. + UpdateCountsAndExport(poke, received, pk9); } // As long as we got rid of our inject in b1s1, assume the trade went through. Log("User completed the trade."); poke.TradeFinished(this, received); - // Only log if we completed the trade. - UpdateCountsAndExport(poke, received, toSend); // Sometimes they offered another mon, so store that immediately upon leaving Union Room. lastOffered = await SwitchConnection.ReadBytesAbsoluteAsync(TradePartnerOfferedOffset, 8, token).ConfigureAwait(false); From 4f83d0c781fb9a2c61a94385cc789c78e009f761 Mon Sep 17 00:00:00 2001 From: Easy World Date: Tue, 7 Feb 2023 23:30:23 +0800 Subject: [PATCH 088/171] Update PokeTradeBotSV.cs --- SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs index 96075cfd8..bffe5a523 100644 --- a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs +++ b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs @@ -376,6 +376,10 @@ private async Task PerformLinkCodeTrade(SAV9SV sav, PokeTradeDe for (var i = 0; i < batchPK9s.Count; i++) { var pk9 = batchPK9s[i]; + if (i > 0 && pk9.Species != 0) + { + await SetBoxPokemonAbsolute(BoxStartOffset, pk9, token, sav).ConfigureAwait(false); + } if (batchPK9s.Count > 1) poke.SendNotification(this, $"批量:等待交换第{i+1}个宝可梦{ShowdownTranslator.GameStringsZh.Species[pk9.Species]}"); var needUseTradePartnerInfo = !foreignList[i]; @@ -383,10 +387,7 @@ private async Task PerformLinkCodeTrade(SAV9SV sav, PokeTradeDe { await SetBoxPkmWithSwappedIDDetailsSV(pk9, tradePartnerFullInfo, sav, token); } - else if (pk9.Species != 0) - { - await SetBoxPokemonAbsolute(BoxStartOffset, pk9, token, sav).ConfigureAwait(false); - } + // Wait for user input... var offered = await ReadUntilPresent(TradePartnerOfferedOffset, 25_000, 1_000, BoxFormatSlotSize, token).ConfigureAwait(false); var oldEC = await SwitchConnection.ReadBytesAbsoluteAsync(TradePartnerOfferedOffset, 8, token).ConfigureAwait(false); From 73a90c8b2aae490a4e9bc481022e6e2a67c43b39 Mon Sep 17 00:00:00 2001 From: Easy World Date: Wed, 8 Feb 2023 20:53:09 +0800 Subject: [PATCH 089/171] Update DodoTradeNotifier.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复了一个bug --- SysBot.Pokemon.Dodo/DodoTradeNotifier.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs b/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs index 585e1884c..914a15573 100644 --- a/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs +++ b/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs @@ -86,10 +86,10 @@ public void TradeInitialize(PokeRoutineExecutor routine, PokeTradeDetail i msg += $" Your trade code is: {info.Code:0000 0000}"; LogUtil.LogText(msg); var text = $"\n派送:{ShowdownTranslator.GameStringsZh.Species[Data.Species]}\n密码:见私信\n状态:初始化"; - List batchPK9s = (List)info.Context.GetValueOrDefault("批量", new List()); - if (batchPK9s.Count > 1) + List batchPKMs = (List)info.Context.GetValueOrDefault("批量", new List()); + if (batchPKMs.Count > 1) { - text = $"\n批量派送{batchPK9s.Count}只宝可梦\n密码:见私信\n状态:初始化"; + text = $"\n批量派送{batchPKMs.Count}只宝可梦\n密码:见私信\n状态:初始化"; } DodoBot.SendChannelAtMessage(info.Trainer.ID, text, ChannelId); DodoBot.SendPersonalMessage(info.Trainer.ID.ToString(), @@ -105,10 +105,10 @@ public void TradeSearching(PokeRoutineExecutor routine, PokeTradeDetail in message += $" Your trade code is: {info.Code:0000 0000}"; LogUtil.LogText(message); var text = $"派送:{ShowdownTranslator.GameStringsZh.Species[Data.Species]}\n密码:见私信\n状态:搜索中"; - List batchPK9s = (List)info.Context.GetValueOrDefault("批量", new List()); - if (batchPK9s.Count > 1) + List batchPKMs = (List)info.Context.GetValueOrDefault("批量", new List()); + if (batchPKMs.Count > 1) { - text = $"批量派送{batchPK9s.Count}只宝可梦\n密码:见私信\n状态:搜索中"; + text = $"批量派送{batchPKMs.Count}只宝可梦\n密码:见私信\n状态:搜索中"; } DodoBot.SendChannelMessage(text, ChannelId); //DodoBot.SendPersonalMessage(info.Trainer.ID.ToString(), $"{info.Code:0000 0000}", IslandSourceId); From 5d8a484d12ec7fe053cf63bdf29ce4de99e24ab6 Mon Sep 17 00:00:00 2001 From: Easy World Date: Wed, 8 Feb 2023 22:24:19 +0800 Subject: [PATCH 090/171] Update PokemonProcessService.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 直接使用+就行了,不用加批量两个字了 --- SysBot.Pokemon.Dodo/PokemonProcessService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SysBot.Pokemon.Dodo/PokemonProcessService.cs b/SysBot.Pokemon.Dodo/PokemonProcessService.cs index 13d9e0b2d..85abc4bc6 100644 --- a/SysBot.Pokemon.Dodo/PokemonProcessService.cs +++ b/SysBot.Pokemon.Dodo/PokemonProcessService.cs @@ -103,9 +103,9 @@ public override void ChannelMessageEvent( DodoHelper.StartDump(eventBody.DodoSourceId, eventBody.Personal.NickName, eventBody.ChannelId); return; } - else if (typeof(TP) == typeof(PK9) && content.Trim().StartsWith("批量"))// 仅SV支持批量,其他偷懒还没写 + else if (typeof(TP) == typeof(PK9) && content.Trim().Contains("+"))// 仅SV支持批量,其他偷懒还没写 { - DodoHelper.StartTradeMulti(content.Replace("批量", "").Trim(), eventBody.DodoSourceId, eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId); + DodoHelper.StartTradeMulti(content.Trim(), eventBody.DodoSourceId, eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId); return; } From 052aa57929b2b420c90eaab5b6a14b3d33137650 Mon Sep 17 00:00:00 2001 From: Easy World Date: Thu, 9 Feb 2023 23:17:01 +0800 Subject: [PATCH 091/171] =?UTF-8?q?=E6=94=AF=E6=8C=81ps=E9=98=9F=E4=BC=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit at机器人后,输入“队伍”,然后粘贴ps队伍 --- SysBot.Pokemon.Dodo/DodoHelper.cs | 52 +++++++++++++++++++- SysBot.Pokemon.Dodo/PokemonProcessService.cs | 7 ++- 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/SysBot.Pokemon.Dodo/DodoHelper.cs b/SysBot.Pokemon.Dodo/DodoHelper.cs index dbab62cff..520e07a5c 100644 --- a/SysBot.Pokemon.Dodo/DodoHelper.cs +++ b/SysBot.Pokemon.Dodo/DodoHelper.cs @@ -33,7 +33,7 @@ public static void StartTrade(T pkm, string dodoId, string nickName, string chan StartTradeWithoutCheck(pkm, dodoId, nickName, channelId, islandSourceId); } - public static void StartTradeMulti(string chinesePsRaw, string dodoId, string nickName, string channelId, string islandSourceId) + public static void StartTradeMultiChinese(string chinesePsRaw, string dodoId, string nickName, string channelId, string islandSourceId) { var chinesePss = chinesePsRaw.Split('+').ToList(); var MaxPkmsPerTrade = DodoBot.Info.Hub.Config.Trade.MaxPkmsPerTrade; @@ -83,6 +83,56 @@ public static void StartTradeMulti(string chinesePsRaw, string dodoId, string ni DodoBot.SendChannelMessage(message, channelId); } + public static void StartTradeMultiPs(string pssRaw, string dodoId, string nickName, string channelId, string islandSourceId) + { + var psArray = pssRaw.Split("\n\n").ToList(); + var MaxPkmsPerTrade = DodoBot.Info.Hub.Config.Trade.MaxPkmsPerTrade; + if (MaxPkmsPerTrade <= 1) + { + DodoBot.SendChannelMessage("请联系群主将trade/MaxPkmsPerTrade配置改为大于1", channelId); + return; + } + else if (psArray.Count > MaxPkmsPerTrade) + { + DodoBot.SendChannelMessage($"批量交换宝可梦数量应小于等于{MaxPkmsPerTrade}", channelId); + return; + } + List msgs = new(); + List pkms = new(); + List foreignList = new(); + int invalidCount = 0; + for (var i = 0; i < psArray.Count; i++) + { + var ps = psArray[i]; + var _ = CheckAndGetPkm(ps, dodoId, out var msg, out var pkm); + if (!_) + { + LogUtil.LogInfo($"批量第{i + 1}只宝可梦有问题:{msg}", nameof(DodoHelper)); + invalidCount++; + } + else + { + LogUtil.LogInfo($"批量第{i + 1}只:\n{ps}", nameof(DodoHelper)); + foreignList.Add(ps.Contains("Language: ")); + pkms.Add(pkm); + } + } + if (invalidCount == psArray.Count) + { + DodoBot.SendChannelMessage("一个都不合法,换个屁", channelId); + return; + } + else if (invalidCount != 0) + { + DodoBot.SendChannelMessage($"期望交换的{psArray.Count}只宝可梦中,有{invalidCount}只不合法,仅交换合法的{pkms.Count}只", channelId); + } + + var code = DodoBot.Info.GetRandomTradeCode(); + var __ = AddToTradeQueue(pkms, code, ulong.Parse(dodoId), nickName, channelId, islandSourceId, foreignList, + PokeRoutineType.LinkTrade, out string message); + DodoBot.SendChannelMessage(message, channelId); + } + public static void StartTradeWithoutCheck(T pkm, string dodoId, string nickName, string channelId, string islandSourceId, bool foreign = false) { var code = DodoBot.Info.GetRandomTradeCode(); diff --git a/SysBot.Pokemon.Dodo/PokemonProcessService.cs b/SysBot.Pokemon.Dodo/PokemonProcessService.cs index 85abc4bc6..c1283f78f 100644 --- a/SysBot.Pokemon.Dodo/PokemonProcessService.cs +++ b/SysBot.Pokemon.Dodo/PokemonProcessService.cs @@ -105,7 +105,12 @@ public override void ChannelMessageEvent( } else if (typeof(TP) == typeof(PK9) && content.Trim().Contains("+"))// 仅SV支持批量,其他偷懒还没写 { - DodoHelper.StartTradeMulti(content.Trim(), eventBody.DodoSourceId, eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId); + DodoHelper.StartTradeMultiChinese(content.Trim(), eventBody.DodoSourceId, eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId); + return; + } + else if (typeof(TP) == typeof(PK9) && content.Trim().Contains("队伍"))// 仅SV支持批量,其他偷懒还没写 + { + DodoHelper.StartTradeMultiPs(content.Replace("队伍", "").Trim(), eventBody.DodoSourceId, eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId); return; } From 1bc6c7e2809920dee3e2a51c01fa724cdc0a4ade Mon Sep 17 00:00:00 2001 From: Easy World Date: Sun, 12 Feb 2023 18:32:26 +0800 Subject: [PATCH 092/171] Update PokeTradeBotSV.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复批量等待时机错误问题 --- SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs index bffe5a523..efa8f123d 100644 --- a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs +++ b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs @@ -387,7 +387,18 @@ private async Task PerformLinkCodeTrade(SAV9SV sav, PokeTradeDe { await SetBoxPkmWithSwappedIDDetailsSV(pk9, tradePartnerFullInfo, sav, token); } - + + if (i > 0) + { + tradeOffered = await ReadUntilChanged(TradePartnerOfferedOffset, lastOffered, 25_000, 0_500, false, true, token).ConfigureAwait(false); + if (!tradeOffered) + { + await ExitTradeToPortal(false, token).ConfigureAwait(false); + return PokeTradeResult.TrainerTooSlow; + } + + } + // Wait for user input... var offered = await ReadUntilPresent(TradePartnerOfferedOffset, 25_000, 1_000, BoxFormatSlotSize, token).ConfigureAwait(false); var oldEC = await SwitchConnection.ReadBytesAbsoluteAsync(TradePartnerOfferedOffset, 8, token).ConfigureAwait(false); @@ -436,6 +447,7 @@ private async Task PerformLinkCodeTrade(SAV9SV sav, PokeTradeDe // Only log if we completed the trade. UpdateCountsAndExport(poke, received, pk9); + lastOffered = await SwitchConnection.ReadBytesAbsoluteAsync(TradePartnerOfferedOffset, 8, token).ConfigureAwait(false); } // As long as we got rid of our inject in b1s1, assume the trade went through. From dbb084ab87eb68202e8737c80d3587a82a1ac4bd Mon Sep 17 00:00:00 2001 From: Easy World Date: Sun, 12 Feb 2023 21:16:59 +0800 Subject: [PATCH 093/171] Update ShowdownTranslator.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增指定语言规则 --- SysBot.Pokemon/Helpers/ShowdownTranslator.cs | 55 +++++++++++--------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/SysBot.Pokemon/Helpers/ShowdownTranslator.cs b/SysBot.Pokemon/Helpers/ShowdownTranslator.cs index 469344585..0505caf52 100644 --- a/SysBot.Pokemon/Helpers/ShowdownTranslator.cs +++ b/SysBot.Pokemon/Helpers/ShowdownTranslator.cs @@ -301,28 +301,6 @@ public static string Chinese2Showdown(string zh) } } - // 添加技能 - for (int moveCount = 0; moveCount < 4; moveCount++) - { - int candidateIndex = -1; - int candidateLength = 0; - for (int i = 0; i < GameStringsZh.Move.Count; i++) - { - if (GameStringsZh.Move[i].Length == 0) continue; - if (!zh.Contains("-" + GameStringsZh.Move[i])) continue; - // 吸取 吸取拳 - if (candidateIndex == -1 || GameStringsZh.Move[i].Length > candidateLength) - { - candidateIndex = i; - candidateLength = GameStringsZh.Move[i].Length; - } - } - if (candidateIndex == -1) continue; - - result += $"\n-{GameStringsEn.Move[candidateIndex]}"; - zh = zh.Replace("-" + GameStringsZh.Move[candidateIndex], ""); - } - // 补充后天获得的全奖章 注意开启Legality=>AllowBatchCommands if (typeof(T) == typeof(PK9) && zh.Contains("全奖章")) { @@ -370,10 +348,37 @@ public static string Chinese2Showdown(string zh) } } - if (zh.Contains("异国")) + if (zh.Contains("异国")) { result += "\nLanguage: Italian"; zh = zh.Replace("异国", ""); } + else if (zh.Contains("日语")) { result += "\nLanguage: Japanese"; zh = zh.Replace("日语", ""); } + else if (zh.Contains("英语")) { result += "\nLanguage: English"; zh = zh.Replace("英语", ""); } + else if (zh.Contains("法语")) { result += "\nLanguage: French"; zh = zh.Replace("法语", ""); } + else if (zh.Contains("意大利语")) { result += "\nLanguage: Italian"; zh = zh.Replace("意大利语", ""); } + else if (zh.Contains("德语")) { result += "\nLanguage: German"; zh = zh.Replace("德语", ""); } + else if (zh.Contains("西班牙语")) { result += "\nLanguage: Spanish"; zh = zh.Replace("西班牙语", ""); } + else if (zh.Contains("韩语")) { result += "\nLanguage: Korean"; zh = zh.Replace("韩语", ""); } + else if (zh.Contains("简体中文")) { result += "\nLanguage: ChineseS"; zh = zh.Replace("简体中文", ""); } + else if (zh.Contains("繁体中文")) { result += "\nLanguage: ChineseT"; zh = zh.Replace("繁体中文", ""); } + + // 添加技能 原因:PKHeX.Core.ShowdownSet#ParseLines中,若招式数满足4个则不再解析,所以招式文本应放在最后 + for (int moveCount = 0; moveCount < 4; moveCount++) { - result += "\nLanguage: Italian"; - zh = zh.Replace("异国", ""); + int candidateIndex = -1; + int candidateLength = 0; + for (int i = 0; i < GameStringsZh.Move.Count; i++) + { + if (GameStringsZh.Move[i].Length == 0) continue; + if (!zh.Contains("-" + GameStringsZh.Move[i])) continue; + // 吸取 吸取拳 + if (candidateIndex == -1 || GameStringsZh.Move[i].Length > candidateLength) + { + candidateIndex = i; + candidateLength = GameStringsZh.Move[i].Length; + } + } + if (candidateIndex == -1) continue; + + result += $"\n-{GameStringsEn.Move[candidateIndex]}"; + zh = zh.Replace("-" + GameStringsZh.Move[candidateIndex], ""); } return result; From bbe1fdab113b312169e6be06aa8f6df470585149 Mon Sep 17 00:00:00 2001 From: Easy World Date: Wed, 15 Mar 2023 21:57:10 +0800 Subject: [PATCH 094/171] Variable name optimization --- SysBot.Pokemon.Dodo/DodoHelper.cs | 18 ++++---- SysBot.Pokemon.Dodo/DodoTradeNotifier.cs | 4 +- SysBot.Pokemon.Kook/KookBot.cs | 46 ------------------- .../SysBot.Pokemon.Kook.csproj | 17 ------- SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs | 8 ++-- 5 files changed, 15 insertions(+), 78 deletions(-) delete mode 100644 SysBot.Pokemon.Kook/KookBot.cs delete mode 100644 SysBot.Pokemon.Kook/SysBot.Pokemon.Kook.csproj diff --git a/SysBot.Pokemon.Dodo/DodoHelper.cs b/SysBot.Pokemon.Dodo/DodoHelper.cs index 520e07a5c..ea7fd318d 100644 --- a/SysBot.Pokemon.Dodo/DodoHelper.cs +++ b/SysBot.Pokemon.Dodo/DodoHelper.cs @@ -49,7 +49,7 @@ public static void StartTradeMultiChinese(string chinesePsRaw, string dodoId, st } List msgs = new(); List pkms = new(); - List foreignList = new(); + List skipAutoOTList = new(); int invalidCount = 0; for (var i = 0; i < chinesePss.Count; i++) { @@ -63,7 +63,7 @@ public static void StartTradeMultiChinese(string chinesePsRaw, string dodoId, st else { LogUtil.LogInfo($"批量第{i+1}只:\n{ps}", nameof(DodoHelper)); - foreignList.Add(ps.Contains("Language: ")); + skipAutoOTList.Add(ps.Contains("Language: ")); pkms.Add(pkm); } } @@ -78,7 +78,7 @@ public static void StartTradeMultiChinese(string chinesePsRaw, string dodoId, st } var code = DodoBot.Info.GetRandomTradeCode(); - var __ = AddToTradeQueue(pkms, code, ulong.Parse(dodoId), nickName, channelId, islandSourceId, foreignList, + var __ = AddToTradeQueue(pkms, code, ulong.Parse(dodoId), nickName, channelId, islandSourceId, skipAutoOTList, PokeRoutineType.LinkTrade, out string message); DodoBot.SendChannelMessage(message, channelId); } @@ -99,7 +99,7 @@ public static void StartTradeMultiPs(string pssRaw, string dodoId, string nickNa } List msgs = new(); List pkms = new(); - List foreignList = new(); + List skipAutoOTList = new(); int invalidCount = 0; for (var i = 0; i < psArray.Count; i++) { @@ -113,7 +113,7 @@ public static void StartTradeMultiPs(string pssRaw, string dodoId, string nickNa else { LogUtil.LogInfo($"批量第{i + 1}只:\n{ps}", nameof(DodoHelper)); - foreignList.Add(ps.Contains("Language: ")); + skipAutoOTList.Add(ps.Contains("Language: ")); pkms.Add(pkm); } } @@ -128,7 +128,7 @@ public static void StartTradeMultiPs(string pssRaw, string dodoId, string nickNa } var code = DodoBot.Info.GetRandomTradeCode(); - var __ = AddToTradeQueue(pkms, code, ulong.Parse(dodoId), nickName, channelId, islandSourceId, foreignList, + var __ = AddToTradeQueue(pkms, code, ulong.Parse(dodoId), nickName, channelId, islandSourceId, skipAutoOTList, PokeRoutineType.LinkTrade, out string message); DodoBot.SendChannelMessage(message, channelId); } @@ -266,7 +266,7 @@ private static bool AddToTradeQueue(T pk, int code, ulong userId, string name, s return AddToTradeQueue(new List { pk }, code, userId, name, channelId, islandSourceId, new List{ foreign }, type, out msg); } - private static bool AddToTradeQueue(List pks, int code, ulong userId, string name, string channelId, string islandSourceId, List foreignList, + private static bool AddToTradeQueue(List pks, int code, ulong userId, string name, string channelId, string islandSourceId, List skipAutoOTList, PokeRoutineType type, out string msg) { if (pks == null || pks.Count == 0) @@ -280,10 +280,10 @@ private static bool AddToTradeQueue(List pks, int code, ulong userId, string var tt = type == PokeRoutineType.SeedCheck ? PokeTradeType.Seed : (type == PokeRoutineType.Dump ? PokeTradeType.Dump : PokeTradeType.Specific); var detail = new PokeTradeDetail(pk, trainer, notifier, tt, code, true); - detail.Context.Add("异国", foreignList); + detail.Context.Add("skipAutoOTList", skipAutoOTList); if (pks.Count > 0) { - detail.Context.Add("批量", pks); + detail.Context.Add("batch", pks); } var trade = new TradeEntry(detail, userId, type, name); diff --git a/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs b/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs index 914a15573..d34a6e039 100644 --- a/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs +++ b/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs @@ -86,7 +86,7 @@ public void TradeInitialize(PokeRoutineExecutor routine, PokeTradeDetail i msg += $" Your trade code is: {info.Code:0000 0000}"; LogUtil.LogText(msg); var text = $"\n派送:{ShowdownTranslator.GameStringsZh.Species[Data.Species]}\n密码:见私信\n状态:初始化"; - List batchPKMs = (List)info.Context.GetValueOrDefault("批量", new List()); + List batchPKMs = (List)info.Context.GetValueOrDefault("batch", new List()); if (batchPKMs.Count > 1) { text = $"\n批量派送{batchPKMs.Count}只宝可梦\n密码:见私信\n状态:初始化"; @@ -105,7 +105,7 @@ public void TradeSearching(PokeRoutineExecutor routine, PokeTradeDetail in message += $" Your trade code is: {info.Code:0000 0000}"; LogUtil.LogText(message); var text = $"派送:{ShowdownTranslator.GameStringsZh.Species[Data.Species]}\n密码:见私信\n状态:搜索中"; - List batchPKMs = (List)info.Context.GetValueOrDefault("批量", new List()); + List batchPKMs = (List)info.Context.GetValueOrDefault("batch", new List()); if (batchPKMs.Count > 1) { text = $"批量派送{batchPKMs.Count}只宝可梦\n密码:见私信\n状态:搜索中"; diff --git a/SysBot.Pokemon.Kook/KookBot.cs b/SysBot.Pokemon.Kook/KookBot.cs deleted file mode 100644 index 2f181cbe8..000000000 --- a/SysBot.Pokemon.Kook/KookBot.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Kook.Rest; -using Kook; -using Kook.WebSocket; -using NLog.Fluent; -using PKHeX.Core; -using System; -using System.Threading.Tasks; -using SysBot.Base; - -namespace SysBot.Pokemon.Kook -{ - public class KookBot where T : PKM, new() - { - private static PokeTradeHub Hub = default!; - internal static TradeQueueInfo Info => Hub.Queues.Info; - - private KookSocketClient _client; - private KookSettings Settings; - - public KookBot(KookSettings settings, PokeTradeHub hub) - { - Hub = hub; - Settings = settings; - - _client = new KookSocketClient(); - - _client.Log += message => - { - LogUtil.LogText($"{message}"); - return Task.CompletedTask; - }; - Task.Run(async () => - { - await _client.LoginAsync(TokenType.Bot, Settings.Token); - await _client.StartAsync(); - _client.Connected += () => - { - LogUtil.LogInfo("Kook Bot is connected!", nameof(KookBot)); - return Task.CompletedTask; - }; - //_client.MessageReceived += - }); - - } - } -} diff --git a/SysBot.Pokemon.Kook/SysBot.Pokemon.Kook.csproj b/SysBot.Pokemon.Kook/SysBot.Pokemon.Kook.csproj deleted file mode 100644 index dbb529dc8..000000000 --- a/SysBot.Pokemon.Kook/SysBot.Pokemon.Kook.csproj +++ /dev/null @@ -1,17 +0,0 @@ - - - - net7.0 - 11 - - - - - - - - - - - - diff --git a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs index 8f4dea9fa..c213512af 100644 --- a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs +++ b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs @@ -369,10 +369,10 @@ private async Task PerformLinkCodeTrade(SAV9SV sav, PokeTradeDe await ExitTradeToPortal(false, token).ConfigureAwait(false); return result; } - List batchPK9s = (List)poke.Context.GetValueOrDefault("批量", new List { toSend }); - List foreignList = (List)poke.Context.GetValueOrDefault("异国", new List { false }); + List batchPK9s = (List)poke.Context.GetValueOrDefault("batch", new List { toSend }); + List skipAutoOTList = (List)poke.Context.GetValueOrDefault("skipAutoOTList", new List { false }); PK9 received = default!; - LogUtil.LogInfo($"count:{batchPK9s.Count}, foreignList:{String.Join(',', foreignList)}", nameof(PokeTradeBotSV)); + LogUtil.LogInfo($"count:{batchPK9s.Count}, skipAutoOTList:{String.Join(',', skipAutoOTList)}", nameof(PokeTradeBotSV)); for (var i = 0; i < batchPK9s.Count; i++) { var pk9 = batchPK9s[i]; @@ -382,7 +382,7 @@ private async Task PerformLinkCodeTrade(SAV9SV sav, PokeTradeDe } if (batchPK9s.Count > 1) poke.SendNotification(this, $"批量:等待交换第{i+1}个宝可梦{ShowdownTranslator.GameStringsZh.Species[pk9.Species]}"); - var needUseTradePartnerInfo = !foreignList[i]; + var needUseTradePartnerInfo = !skipAutoOTList[i]; if (Hub.Config.Legality.UseTradePartnerInfo && needUseTradePartnerInfo) { await SetBoxPkmWithSwappedIDDetailsSV(pk9, tradePartnerFullInfo, sav, token); From 390863db02739dcca78b0dfd721c92532378ed88 Mon Sep 17 00:00:00 2001 From: Easy World Date: Wed, 15 Mar 2023 21:58:30 +0800 Subject: [PATCH 095/171] remove Kook --- SysBot.NET.sln | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/SysBot.NET.sln b/SysBot.NET.sln index 69bbed15b..126d7814c 100644 --- a/SysBot.NET.sln +++ b/SysBot.NET.sln @@ -37,8 +37,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SysBot.Pokemon.Bilibili", " EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SysBot.Pokemon.Dodo", "SysBot.Pokemon.Dodo\SysBot.Pokemon.Dodo.csproj", "{BE43311D-4571-466E-B3CB-3FD658EFC159}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SysBot.Pokemon.Kook", "SysBot.Pokemon.Kook\SysBot.Pokemon.Kook.csproj", "{BDD41990-5801-4C75-A08C-C248BC627D3E}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -187,18 +185,6 @@ Global {BE43311D-4571-466E-B3CB-3FD658EFC159}.Release|x64.Build.0 = Release|Any CPU {BE43311D-4571-466E-B3CB-3FD658EFC159}.Release|x86.ActiveCfg = Release|Any CPU {BE43311D-4571-466E-B3CB-3FD658EFC159}.Release|x86.Build.0 = Release|Any CPU - {BDD41990-5801-4C75-A08C-C248BC627D3E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BDD41990-5801-4C75-A08C-C248BC627D3E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BDD41990-5801-4C75-A08C-C248BC627D3E}.Debug|x64.ActiveCfg = Debug|Any CPU - {BDD41990-5801-4C75-A08C-C248BC627D3E}.Debug|x64.Build.0 = Debug|Any CPU - {BDD41990-5801-4C75-A08C-C248BC627D3E}.Debug|x86.ActiveCfg = Debug|Any CPU - {BDD41990-5801-4C75-A08C-C248BC627D3E}.Debug|x86.Build.0 = Debug|Any CPU - {BDD41990-5801-4C75-A08C-C248BC627D3E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BDD41990-5801-4C75-A08C-C248BC627D3E}.Release|Any CPU.Build.0 = Release|Any CPU - {BDD41990-5801-4C75-A08C-C248BC627D3E}.Release|x64.ActiveCfg = Release|Any CPU - {BDD41990-5801-4C75-A08C-C248BC627D3E}.Release|x64.Build.0 = Release|Any CPU - {BDD41990-5801-4C75-A08C-C248BC627D3E}.Release|x86.ActiveCfg = Release|Any CPU - {BDD41990-5801-4C75-A08C-C248BC627D3E}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From d7c047df3c66215a39be02852770364497a66d78 Mon Sep 17 00:00:00 2001 From: Easy World Date: Thu, 16 Mar 2023 13:46:29 +0800 Subject: [PATCH 096/171] General system stability improvements to enhance the user's experience --- SysBot.Pokemon/BDSP/PokeRoutineExecutor8BS.cs | 4 ++-- SysBot.Pokemon/LA/PokeRoutineExecutor8LA.cs | 4 ++-- SysBot.Pokemon/SV/PokeRoutineExecutor9SV.cs | 4 ++-- SysBot.Pokemon/SWSH/BotTrade/TradeSettings.cs | 4 ++-- SysBot.Pokemon/SWSH/PokeRoutineExecutor8.cs | 4 ++-- SysBot.Pokemon/Settings/LegalitySettings.cs | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/SysBot.Pokemon/BDSP/PokeRoutineExecutor8BS.cs b/SysBot.Pokemon/BDSP/PokeRoutineExecutor8BS.cs index 05ad20292..ca5479071 100644 --- a/SysBot.Pokemon/BDSP/PokeRoutineExecutor8BS.cs +++ b/SysBot.Pokemon/BDSP/PokeRoutineExecutor8BS.cs @@ -75,9 +75,9 @@ public async Task IdentifyTrainer(CancellationToken token) InitSaveData(sav); if (!IsValidTrainerData()) - throw new Exception("Trainer data is not valid. Refer to the SysBot.NET wiki for bad or no trainer data."); + throw new Exception("训练家数据无效。请参考https://docs.qq.com/doc/DSWNwV1pLVnZudnJF"); if (await GetTextSpeed(token).ConfigureAwait(false) < TextSpeedOption.Fast) - throw new Exception("Text speed should be set to FAST. Fix this for correct operation."); + throw new Exception("请在游戏中设置文本速度为快速,然后重启机器人"); return sav; } diff --git a/SysBot.Pokemon/LA/PokeRoutineExecutor8LA.cs b/SysBot.Pokemon/LA/PokeRoutineExecutor8LA.cs index 10bd9bcfd..576cf1164 100644 --- a/SysBot.Pokemon/LA/PokeRoutineExecutor8LA.cs +++ b/SysBot.Pokemon/LA/PokeRoutineExecutor8LA.cs @@ -81,9 +81,9 @@ public async Task IdentifyTrainer(CancellationToken token) InitSaveData(sav); if (!IsValidTrainerData()) - throw new Exception("Trainer data is not valid. Refer to the SysBot.NET wiki for bad or no trainer data."); + throw new Exception("训练家数据无效。请参考https://docs.qq.com/doc/DSWNwV1pLVnZudnJF"); if (await GetTextSpeed(token).ConfigureAwait(false) < TextSpeedOption.Fast) - throw new Exception("Text speed should be set to FAST. Fix this for correct operation."); + throw new Exception("请在游戏中设置文本速度为快速,然后重启机器人"); return sav; } diff --git a/SysBot.Pokemon/SV/PokeRoutineExecutor9SV.cs b/SysBot.Pokemon/SV/PokeRoutineExecutor9SV.cs index 9eda70332..4c862d65b 100644 --- a/SysBot.Pokemon/SV/PokeRoutineExecutor9SV.cs +++ b/SysBot.Pokemon/SV/PokeRoutineExecutor9SV.cs @@ -81,9 +81,9 @@ public async Task IdentifyTrainer(CancellationToken token) InitSaveData(sav); if (!IsValidTrainerData()) - throw new Exception("Trainer data is not valid. Refer to the SysBot.NET wiki for bad or no trainer data."); + throw new Exception("训练家数据无效。请参考https://docs.qq.com/doc/DSWNwV1pLVnZudnJF"); if (await GetTextSpeed(token).ConfigureAwait(false) < TextSpeedOption.Fast) - throw new Exception("Text speed should be set to FAST. Fix this for correct operation."); + throw new Exception("请在游戏中设置文本速度为快速,然后重启机器人"); return sav; } diff --git a/SysBot.Pokemon/SWSH/BotTrade/TradeSettings.cs b/SysBot.Pokemon/SWSH/BotTrade/TradeSettings.cs index 1ab3c11d2..a5bc6d161 100644 --- a/SysBot.Pokemon/SWSH/BotTrade/TradeSettings.cs +++ b/SysBot.Pokemon/SWSH/BotTrade/TradeSettings.cs @@ -21,10 +21,10 @@ public class TradeSettings : IBotStateSettings, ICountSettings public int MaxTradeConfirmTime { get; set; } = 25; [Category(TradeCode), Description("Minimum Link Code.")] - public int MinTradeCode { get; set; } = 8180; + public int MinTradeCode { get; set; } = 0; [Category(TradeCode), Description("Maximum Link Code.")] - public int MaxTradeCode { get; set; } = 8199; + public int MaxTradeCode { get; set; } = 9999_9999; [Category(Dumping), Description("Dump Trade: Dumping routine will stop after a maximum number of dumps from a single user.")] public int MaxDumpsPerTrade { get; set; } = 20; diff --git a/SysBot.Pokemon/SWSH/PokeRoutineExecutor8.cs b/SysBot.Pokemon/SWSH/PokeRoutineExecutor8.cs index 6d782fcc0..5d5ef75f3 100644 --- a/SysBot.Pokemon/SWSH/PokeRoutineExecutor8.cs +++ b/SysBot.Pokemon/SWSH/PokeRoutineExecutor8.cs @@ -90,9 +90,9 @@ public async Task IdentifyTrainer(CancellationToken token) InitSaveData(sav); if (!IsValidTrainerData()) - throw new Exception("Trainer data is not valid. Refer to the SysBot.NET wiki for bad or no trainer data."); + throw new Exception("训练家数据无效。请参考https://docs.qq.com/doc/DSWNwV1pLVnZudnJF"); if (await GetTextSpeed(token).ConfigureAwait(false) < TextSpeedOption.Fast) - throw new Exception("Text speed should be set to FAST. Fix this for correct operation."); + throw new Exception("请在游戏中设置文本速度为快速,然后重启机器人"); return sav; } diff --git a/SysBot.Pokemon/Settings/LegalitySettings.cs b/SysBot.Pokemon/Settings/LegalitySettings.cs index 2bc81e30f..d8e5c8968 100644 --- a/SysBot.Pokemon/Settings/LegalitySettings.cs +++ b/SysBot.Pokemon/Settings/LegalitySettings.cs @@ -65,7 +65,7 @@ public string GenerateOT public bool AllowTrainerDataOverride { get; set; } [Category(Generate), Description("Allow users to submit further customization with Batch Editor commands.")] - public bool AllowBatchCommands { get; set; } + public bool AllowBatchCommands { get; set; } = true; [Category(Generate), Description("Maximum time in seconds to spend when generating a set before canceling. This prevents difficult sets from freezing the bot.")] public int Timeout { get; set; } = 15; @@ -76,6 +76,6 @@ public string GenerateOT public bool ResetHOMETracker { get; set; } = true; [Category(Misc), Description("Override trainer data by trade partner")] - public bool UseTradePartnerInfo { get; set; } = false; + public bool UseTradePartnerInfo { get; set; } = true; } } From edd27636c611920e8c9346e09e6f52626fe244a7 Mon Sep 17 00:00:00 2001 From: Easy World Date: Thu, 16 Mar 2023 15:11:59 +0800 Subject: [PATCH 097/171] Update ShowdownTranslator.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pa'u -> Pa’u --- SysBot.Pokemon/Helpers/ShowdownTranslator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SysBot.Pokemon/Helpers/ShowdownTranslator.cs b/SysBot.Pokemon/Helpers/ShowdownTranslator.cs index 0505caf52..709ca86b2 100644 --- a/SysBot.Pokemon/Helpers/ShowdownTranslator.cs +++ b/SysBot.Pokemon/Helpers/ShowdownTranslator.cs @@ -523,7 +523,7 @@ public static string Chinese2Showdown(string zh) {"特大尺寸","Super"}, {"解放","Unbound"}, {"啪滋啪滋风格","Pom-Pom"}, - {"呼拉呼拉风格","Pa'u"}, + {"呼拉呼拉风格","Pa’u"}, {"轻盈轻盈风格","Sensu"}, {"黑夜的样子","Midnight"}, {"黄昏的样子","Dusk"}, @@ -566,4 +566,4 @@ public static string Chinese2Showdown(string zh) #endregion } -} \ No newline at end of file +} From 44aff68c45442d0f8464b90a141542ffec2306da Mon Sep 17 00:00:00 2001 From: Easy World Date: Sun, 19 Mar 2023 23:16:34 +0800 Subject: [PATCH 098/171] QQbot refactor --- SysBot.Pokemon.Dodo/DodoHelper.cs | 2 +- .../Helpers/MiraiQQCommandsHelper.cs | 123 ------ SysBot.Pokemon.QQ/Helpers/MiraiQQQueue.cs | 20 - SysBot.Pokemon.QQ/MiraiQQBot.cs | 335 ++-------------- SysBot.Pokemon.QQ/MiraiQQHelper.cs | 361 ++++++++++++++++++ .../{Helpers => }/MiraiQQTradeNotifier.cs | 32 +- SysBot.Pokemon.QQ/Modules/AliveModule.cs | 29 ++ SysBot.Pokemon.QQ/Modules/CommandModule.cs | 59 +++ SysBot.Pokemon.QQ/Modules/FileModule.cs | 114 ++++++ SysBot.Pokemon.QQ/Modules/PsModule.cs | 91 +++++ SysBot.Pokemon.QQ/SysBot.Pokemon.QQ.csproj | 2 +- SysBot.Tests/TranslatorTests.cs | 24 ++ 12 files changed, 711 insertions(+), 481 deletions(-) delete mode 100644 SysBot.Pokemon.QQ/Helpers/MiraiQQCommandsHelper.cs delete mode 100644 SysBot.Pokemon.QQ/Helpers/MiraiQQQueue.cs create mode 100644 SysBot.Pokemon.QQ/MiraiQQHelper.cs rename SysBot.Pokemon.QQ/{Helpers => }/MiraiQQTradeNotifier.cs (72%) create mode 100644 SysBot.Pokemon.QQ/Modules/AliveModule.cs create mode 100644 SysBot.Pokemon.QQ/Modules/CommandModule.cs create mode 100644 SysBot.Pokemon.QQ/Modules/FileModule.cs create mode 100644 SysBot.Pokemon.QQ/Modules/PsModule.cs diff --git a/SysBot.Pokemon.Dodo/DodoHelper.cs b/SysBot.Pokemon.Dodo/DodoHelper.cs index ea7fd318d..740a36230 100644 --- a/SysBot.Pokemon.Dodo/DodoHelper.cs +++ b/SysBot.Pokemon.Dodo/DodoHelper.cs @@ -182,7 +182,7 @@ public static bool CheckPkm(T pkm, string username, out string msg) catch (Exception ex) #pragma warning restore CA1031 // Do not catch general exception types { - LogUtil.LogSafe(ex, nameof(DodoBot)); + LogUtil.LogSafe(ex, nameof(DodoHelper)); msg = $"取消派送, <@!{username}>: 发生了一个错误"; } diff --git a/SysBot.Pokemon.QQ/Helpers/MiraiQQCommandsHelper.cs b/SysBot.Pokemon.QQ/Helpers/MiraiQQCommandsHelper.cs deleted file mode 100644 index 15b3386dd..000000000 --- a/SysBot.Pokemon.QQ/Helpers/MiraiQQCommandsHelper.cs +++ /dev/null @@ -1,123 +0,0 @@ -using System; -using PKHeX.Core; -using SysBot.Base; -using SysBot.Pokemon; - -namespace SysBot.Pokemon.QQ -{ - public class MiraiQQCommandsHelper where T : PKM, new() - { - public static bool AddToWaitingList(string setstring, string username, ulong mUserId, out string msg) - { - if (!MiraiQQBot.Info.GetCanQueue()) - { - msg = "Sorry, I am not currently accepting queue requests!"; - return false; - } - - var set = ShowdownUtil.ConvertToShowdown(setstring); - if (set == null) - { - msg = $"Skipping trade, @{username}: Empty nickname provided for the species."; - return false; - } - - var template = AutoLegalityWrapper.GetTemplate(set); - if (template.Species < 1) - { - msg = - $"Skipping trade, @{username}: Please read what you are supposed to type as the command argument."; - return false; - } - - if (set.InvalidLines.Count != 0) - { - msg = - $"Skipping trade, @{username}: Unable to parse Showdown Set:\n{string.Join("\n", set.InvalidLines)}"; - return false; - } - - try - { - var sav = AutoLegalityWrapper.GetTrainerInfo(); - var pkm = sav.GetLegal(template, out var result); - - if (!pkm.CanBeTraded()) - { - msg = $"Skipping trade, @{username}: Provided Pokemon content is blocked from trading!"; - return false; - } - - if (pkm is T pk) - { - var valid = new LegalityAnalysis(pkm).Valid; - if (valid) - { - var tq = new MiraiQQQueue(pk, new PokeTradeTrainerInfo(username, mUserId), mUserId); - MiraiQQBot.QueuePool.RemoveAll(z => z.QQ == mUserId); // remove old requests if any - MiraiQQBot.QueuePool.Add(tq); - msg = - $"@{username} - added to the waiting list. Your request from the waiting list will be removed if you are too slow!"; - return true; - } - } - - var reason = result == "Timeout" ? "Set took too long to generate." : "Unable to legalize the Pokemon."; - msg = $"Skipping trade, @{username}: {reason}"; - } -#pragma warning disable CA1031 // Do not catch general exception types - catch (Exception ex) -#pragma warning restore CA1031 // Do not catch general exception types - { - LogUtil.LogSafe(ex, nameof(MiraiQQCommandsHelper)); - msg = $"Skipping trade, @{username}: An unexpected problem occurred."; - } - - return false; - } - - public static bool AddToWaitingList(PKM pkm, string username, ulong mUserId, out string msg) - { - if (!MiraiQQBot.Info.GetCanQueue()) - { - msg = "Sorry, I am not currently accepting queue requests!"; - return false; - } - - try - { - if (!pkm.CanBeTraded()) - { - msg = $"Skipping trade, @{username}: Provided Pokemon content is blocked from trading!"; - return false; - } - - if (pkm is T pk) - { - var valid = new LegalityAnalysis(pkm).Valid; - if (valid) - { - var tq = new MiraiQQQueue(pk, new PokeTradeTrainerInfo(username, mUserId), mUserId); - MiraiQQBot.QueuePool.RemoveAll(z => z.QQ == mUserId); // remove old requests if any - MiraiQQBot.QueuePool.Add(tq); - msg = - $"@{username} - added to the waiting list. Your request from the waiting list will be removed if you are too slow!"; - return true; - } - } - - var reason = "Unable to legalize the Pokemon."; - msg = $"Skipping trade, @{username}: {reason}"; - } -#pragma warning disable CA1031 // Do not catch general exception types - catch (Exception ex) -#pragma warning restore CA1031 // Do not catch general exception types - { - LogUtil.LogSafe(ex, nameof(MiraiQQCommandsHelper)); - msg = $"Skipping trade, @{username}: An unexpected problem occurred."; - } - - return false; - } - } -} \ No newline at end of file diff --git a/SysBot.Pokemon.QQ/Helpers/MiraiQQQueue.cs b/SysBot.Pokemon.QQ/Helpers/MiraiQQQueue.cs deleted file mode 100644 index a9c4fcfdb..000000000 --- a/SysBot.Pokemon.QQ/Helpers/MiraiQQQueue.cs +++ /dev/null @@ -1,20 +0,0 @@ -using PKHeX.Core; -using SysBot.Pokemon; - -namespace SysBot.Pokemon.QQ -{ - public class MiraiQQQueue where T : PKM, new() - { - public T Pokemon { get; } - public PokeTradeTrainerInfo Trainer { get; } - public ulong QQ { get; } - public string DisplayName => Trainer.TrainerName; - - public MiraiQQQueue(T pkm, PokeTradeTrainerInfo trainer, ulong qq) - { - Pokemon = pkm; - Trainer = trainer; - QQ = qq; - } - } -} \ No newline at end of file diff --git a/SysBot.Pokemon.QQ/MiraiQQBot.cs b/SysBot.Pokemon.QQ/MiraiQQBot.cs index ce85c943a..a3668ae02 100644 --- a/SysBot.Pokemon.QQ/MiraiQQBot.cs +++ b/SysBot.Pokemon.QQ/MiraiQQBot.cs @@ -1,19 +1,13 @@ -using Mirai.Net.Data.Messages.Concretes; -using Mirai.Net.Data.Messages.Receivers; +using Mirai.Net.Data.Messages; +using Mirai.Net.Modules; using Mirai.Net.Sessions; using Mirai.Net.Sessions.Http.Managers; +using Mirai.Net.Utils.Scaffolds; using PKHeX.Core; using SysBot.Base; using System; -using System.Collections.Concurrent; using System.Collections.Generic; -using System.Linq; -using System.Reactive.Linq; using System.Threading.Tasks; -using Mirai.Net.Utils.Scaffolds; -using System.Text.RegularExpressions; -using Mirai.Net.Data.Events.Concretes.Group; -using System.Net.Http; namespace SysBot.Pokemon.QQ { @@ -22,63 +16,30 @@ namespace SysBot.Pokemon.QQ private static PokeTradeHub Hub = default!; internal static TradeQueueInfo Info => Hub.Queues.Info; - internal static readonly List> QueuePool = new(); private readonly MiraiBot Client; - private readonly string GroupId; - - private readonly QQSettings Settings; - // concurrent? - internal static ConcurrentDictionary TradeCodeDictionary = new(); + internal static QQSettings Settings = default!; public MiraiQQBot(QQSettings settings, PokeTradeHub hub) { - Hub = hub; Settings = settings; - + Hub = hub; Client = new MiraiBot { Address = settings.Address, QQ = settings.QQ, VerifyKey = settings.VerifyKey }; - GroupId = settings.GroupId; - Client.MessageReceived.OfType() - .Subscribe(async receiver => - { - try - { - if (IsBotOrNotTargetGroup(receiver)) - return; - - await HandleAliveMessage(receiver); - await HandleFileUpload(receiver); - await HandleCommand(receiver); - await HandlePokemonName(receiver); - await HandleCancel(receiver); - await HandlePosition(receiver); - } - catch (Exception ex) - { - LogUtil.LogSafe(ex, "MiraiMain"); - LogUtil.LogError(ex.Message, "MiraiMain"); - } - }); - - Client.MessageReceived.OfType() - .Subscribe(receiver => - { - var tradeCode = receiver.MessageChain.OfType()?.FirstOrDefault()?.Text ?? ""; - if (Regex.IsMatch(tradeCode, "\\d{8}")) - { - TradeCodeDictionary[receiver.Sender.Id] = int.Parse(tradeCode); - } - }); - Client.EventReceived.OfType() - .Subscribe(receiver => { Info.ClearTrade(ulong.Parse(receiver.Member.Id)); }); - Client.EventReceived.OfType() - .Subscribe(receiver => { Info.ClearTrade(ulong.Parse(receiver.Member.Id)); }); + var modules = new List() + { + new AliveModule(), + new CommandModule(), + new FileModule(), + new PsModule() + }; + Client.MessageReceived.SubscribeGroupMessage(receiver => { if (receiver.GroupId == Settings.GroupId) modules.Raise(receiver); }); + var GroupId = Settings.GroupId; Task.Run(async () => { try @@ -119,270 +80,22 @@ public MiraiQQBot(QQSettings settings, PokeTradeHub hub) }); } - private bool IsBotOrNotTargetGroup(GroupMessageReceiver receiver) - { - return receiver.Sender.Group.Id != GroupId || receiver.Sender.Id == Settings.QQ; - } - - private async Task HandleAliveMessage(GroupMessageReceiver receiver) - { - if (Settings.AliveMsg == receiver.MessageChain.OfType()?.FirstOrDefault()?.Text) - { - await MessageManager.SendGroupMessageAsync(receiver.Sender.Group.Id, Settings.AliveMsg); - return; - } - } - - private async Task HandleCancel(GroupMessageReceiver receiver) - { - if (receiver.MessageChain.OfType().All(x => x.Target != Settings.QQ)) return; - bool isCancelMsg = (receiver.MessageChain.OfType()?.FirstOrDefault()?.Text ?? "").Trim() - .StartsWith("取消"); - if (!isCancelMsg) return; - var result = Info.ClearTrade(ulong.Parse(receiver.Sender.Id)); - await receiver.SendMessageAsync( - new AtMessage(receiver.Sender.Id).Append($" {GetClearTradeMessage(result)}")); - } - - private async Task HandlePosition(GroupMessageReceiver receiver) - { - if (receiver.MessageChain.OfType().All(x => x.Target != Settings.QQ)) return; - bool isPositionMsg = (receiver.MessageChain.OfType()?.FirstOrDefault()?.Text ?? "").Trim() - .StartsWith("位置"); - if (!isPositionMsg) return; - var result = Info.CheckPosition(ulong.Parse(receiver.Sender.Id)); - await receiver.SendMessageAsync( - new AtMessage(receiver.Sender.Id).Append($" {GetQueueCheckResultMessage(result)}")); - } - - public string GetQueueCheckResultMessage(QueueCheckResult result) - { - if (!result.InQueue || result.Detail is null) - return "你不在队列里"; - var msg = $"你在第{result.Position}位"; - var pk = result.Detail.Trade.TradeData; - if (pk.Species != 0) - msg += $",交换宝可梦:{ShowdownTranslator.GameStringsZh.Species[result.Detail.Trade.TradeData.Species]}"; - return msg; - } - - private static string GetClearTradeMessage(QueueResultRemove result) - { - return result switch - { - QueueResultRemove.CurrentlyProcessing => "你正在交换中", - QueueResultRemove.CurrentlyProcessingRemoved => "正在删除", - QueueResultRemove.Removed => "已删除", - _ => "你不在队列里", - }; - } - - private async Task HandlePokemonName(GroupMessageReceiver receiver) - { - if (receiver.MessageChain.OfType().All(x => x.Target != Settings.QQ)) return; - var text = receiver.MessageChain.OfType()?.FirstOrDefault()?.Text ?? ""; - if (string.IsNullOrWhiteSpace(text)) return; - string ps = ShowdownTranslator.Chinese2Showdown(text); - if (string.IsNullOrWhiteSpace(ps)) return; - LogUtil.LogInfo($"code\n{ps}", "HandlePokemonName"); - var _ = MiraiQQCommandsHelper.AddToWaitingList(ps, receiver.Sender.Name, - ulong.Parse(receiver.Sender.Id), out string msg); - - await ProcessAddWaitingListResult(_, msg, receiver.Sender.Id); - } - - private async Task HandleFileUpload(GroupMessageReceiver receiver) - { - var senderQQ = receiver.Sender.Id; - var groupId = receiver.Sender.Group.Id; - - var fileMessage = receiver.MessageChain.OfType()?.FirstOrDefault(); - if (fileMessage == null) return; - LogUtil.LogInfo("In file module", "HandleFileUpload"); - var fileName = fileMessage.Name; - string operationType; - if (typeof(T) == typeof(PK8) && - fileName.EndsWith(".pk8", StringComparison.OrdinalIgnoreCase)) operationType = "pk8"; - else if (typeof(T) == typeof(PB8) && - fileName.EndsWith(".pb8", StringComparison.OrdinalIgnoreCase)) - operationType = "pb8"; - else if (typeof(T) == typeof(PA8) && - fileName.EndsWith(".pa8", StringComparison.OrdinalIgnoreCase)) - operationType = "pa8"; - else if (typeof(T) == typeof(PK9) && - fileName.EndsWith(".pk9", StringComparison.OrdinalIgnoreCase)) - operationType = "pk9"; - else return; - - PKM pkm; - try - { - var f = await FileManager.GetFileAsync(groupId, fileMessage.FileId, true); - using var client = new HttpClient(); - byte[] data = client.GetByteArrayAsync(f.DownloadInfo.Url).Result; - switch (operationType) - { - case "pk8" or "pb8" or "pk9" when data.Length != 344: - await MessageManager.SendGroupMessageAsync(groupId, "非法文件"); - return; - case "pa8" when data.Length != 376: - await MessageManager.SendGroupMessageAsync(groupId, "非法文件"); - return; - } - - switch (operationType) - { - case "pk8": - pkm = new PK8(data); - break; - case "pb8": - pkm = new PB8(data); - break; - case "pa8": - pkm = new PA8(data); - break; - case "pk9": - pkm = new PK9(data); - break; - default: return; - } - - LogUtil.LogInfo($"operationType:{operationType}", "HandleFileUpload"); - await FileManager.DeleteFileAsync(groupId, fileMessage.FileId); - } - catch (Exception ex) - { - LogUtil.LogSafe(ex, "HandleFileUpload"); - LogUtil.LogError(ex.Message, "HandleFileUpload"); - return; - } - - var _ = MiraiQQCommandsHelper.AddToWaitingList(pkm, receiver.Sender.Name, - ulong.Parse(senderQQ), out string msg); - await ProcessAddWaitingListResult(_, msg, senderQQ); - } - - private async Task HandleCommand(GroupMessageReceiver receiver) - { - string qqMsg; - try - { - qqMsg = receiver.MessageChain.OfType().FirstOrDefault()?.Text ?? ""; - } - catch - { - return; - } - - if (string.IsNullOrWhiteSpace(qqMsg) || !qqMsg.Trim().StartsWith("$trade")) return; - - LogUtil.LogInfo($"qqMsg:{qqMsg}", "HandleCommand"); - var split = qqMsg.Split('\n'); - string c = ""; - string args = ""; - string nickName = receiver.Sender.Name; - string qq = receiver.Sender.Id; - if (split.Length > 0) - { - c = split[0]; - args = qqMsg[(qqMsg.IndexOf('\n') + 1)..]; - } - - switch (c) - { - case "$trade": - try - { - await receiver.RecallAsync(); - } - catch (Exception ex) - { - LogUtil.LogSafe(ex, "mirai"); - LogUtil.LogError($"{ex.Message}", "mirai"); - } - - var _ = MiraiQQCommandsHelper.AddToWaitingList(args, nickName, ulong.Parse(qq), out string msg); - await ProcessAddWaitingListResult(_, msg, qq); - break; - } - } - - private async Task ProcessAddWaitingListResult(bool success, string msg, string qq) + public async static void SendGroupMessage(MessageChain mc) { - if (success) - { - LogUtil.LogInfo(msg, "trade"); - await GetUserFromQueueAndGenerateCodeToTrade(qq); - } - else - { - LogUtil.LogError(msg, "trade"); - await MessageManager.SendGroupMessageAsync(GroupId, new AtMessage(qq).Append(" 宝可梦信息异常")); - } + if (string.IsNullOrEmpty(Settings.GroupId)) return; + await MessageManager.SendGroupMessageAsync(Settings.GroupId, mc); } - private async Task GetUserFromQueueAndGenerateCodeToTrade(string qq) + public async static void SendFriendMessage(string friendId, MessageChain mc) { - var user = QueuePool.FindLast(q => q.QQ == ulong.Parse(qq)); - - if (user == null) - return; - QueuePool.Remove(user); - - try - { - int code = TradeCodeDictionary.ContainsKey(qq) - ? TradeCodeDictionary[qq] - : Info.GetRandomTradeCode(); //Util.ToInt32(msg); - var _ = AddToTradeQueue(user.Pokemon, code, user.QQ, user.DisplayName, RequestSignificance.Favored, - PokeRoutineType.LinkTrade, out string message); - if (!_) - await MessageManager.SendGroupMessageAsync(GroupId, new AtMessage(qq).Append(" 已在队列中")); - else - await MessageManager.SendGroupMessageAsync(GroupId, new AtMessage(qq).Append(message)); - } -#pragma warning disable CA1031 // Do not catch general exception types - catch (Exception ex) -#pragma warning restore CA1031 // Do not catch general exception types - { - LogUtil.LogSafe(ex, nameof(MiraiQQBot)); - LogUtil.LogError($"{ex.Message}", nameof(MiraiQQBot)); - } + if (string.IsNullOrEmpty(friendId) || string.IsNullOrEmpty(Settings.GroupId)) return; + await MessageManager.SendFriendMessageAsync(friendId, mc); } - private bool AddToTradeQueue(T pk, int code, ulong qq, string displayName, RequestSignificance sig, - PokeRoutineType type, out string msg) + public async static void SendTempMessage(string friendId, MessageChain mc) { - var userID = qq; - var name = displayName; - - var trainer = new PokeTradeTrainerInfo(name, userID); - var notifier = new MiraiQQTradeNotifier(pk, trainer, code, name, GroupId); - var tt = type == PokeRoutineType.SeedCheck ? PokeTradeType.Seed : PokeTradeType.Specific; - var detail = new PokeTradeDetail(pk, trainer, notifier, tt, code, sig == RequestSignificance.Favored); - var trade = new TradeEntry(detail, userID, type, name); - - var added = Info.AddToTradeQueue(trade, userID, sig == RequestSignificance.Owner); - - if (added == QueueResultAdd.AlreadyInQueue) - { - msg = $"@{name}: Sorry, you are already in the queue."; - return false; - } - - var position = Info.CheckPosition(userID, type); - //msg = $"@{name}: Added to the {type} queue, unique ID: {detail.ID}. Current Position: {position.Position}"; - msg = $" 你在第{position.Position}位"; - - var botct = Info.Hub.Bots.Count; - if (position.Position > botct) - { - var eta = Info.Hub.Config.Queues.EstimateDelay(position.Position, botct); - //msg += $". Estimated: {eta:F1} minutes."; - msg += $", 需等待约{eta:F1}分钟"; - } - - return true; + if (string.IsNullOrEmpty(friendId) || string.IsNullOrEmpty(Settings.GroupId)) return; + await MessageManager.SendTempMessageAsync(friendId, Settings.GroupId, mc); } } -} \ No newline at end of file +} diff --git a/SysBot.Pokemon.QQ/MiraiQQHelper.cs b/SysBot.Pokemon.QQ/MiraiQQHelper.cs new file mode 100644 index 000000000..eaecc6f11 --- /dev/null +++ b/SysBot.Pokemon.QQ/MiraiQQHelper.cs @@ -0,0 +1,361 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Manganese.Array; +using Mirai.Net.Utils.Scaffolds; +using PKHeX.Core; +using SysBot.Base; + +namespace SysBot.Pokemon.QQ +{ + public class MiraiQQHelper where T : PKM, new() + { + public static void StartTrade(string ps, string qq, string nickName, string groupId) + { + var _ = CheckAndGetPkm(ps, qq, out var msg, out var pkm); + if (!_) + { + MiraiQQBot.SendGroupMessage(new MessageChainBuilder().At(qq).Plain(msg).Build()); + return; + } + var foreign = ps.Contains("Language: "); + + StartTradeWithoutCheck(pkm, qq, nickName, groupId, foreign); + } + + public static void StartTrade(T pkm, string qq, string nickName, string groupId) + { + var _ = CheckPkm(pkm, qq, out var msg); + if (!_) + { + MiraiQQBot.SendGroupMessage(new MessageChainBuilder().At(qq).Plain(msg).Build()); + return; + } + + StartTradeWithoutCheck(pkm, qq, nickName, groupId); + } + + public static void StartTradeMultiChinese(string chinesePsRaw, string qq, string nickName, string groupId) + { + var chinesePss = chinesePsRaw.Split('+').ToList(); + var MaxPkmsPerTrade = MiraiQQBot.Info.Hub.Config.Trade.MaxPkmsPerTrade; + if (MaxPkmsPerTrade <= 1) + { + MiraiQQBot.SendGroupMessage("请联系群主将trade/MaxPkmsPerTrade配置改为大于1"); + return; + } + else if (chinesePss.Count > MaxPkmsPerTrade) + { + MiraiQQBot.SendGroupMessage($"批量交换宝可梦数量应小于等于{MaxPkmsPerTrade}"); + return; + } + List msgs = new(); + List pkms = new(); + List skipAutoOTList = new(); + int invalidCount = 0; + for (var i = 0; i < chinesePss.Count; i++) + { + var ps = ShowdownTranslator.Chinese2Showdown(chinesePss[i]); + var _ = CheckAndGetPkm(ps, qq, out var msg, out var pkm); + if (!_) + { + LogUtil.LogInfo($"批量第{i + 1}只宝可梦有问题:{msg}", nameof(MiraiQQHelper)); + invalidCount++; + } + else + { + LogUtil.LogInfo($"批量第{i + 1}只:\n{ps}", nameof(MiraiQQHelper)); + skipAutoOTList.Add(ps.Contains("Language: ")); + pkms.Add(pkm); + } + } + if (invalidCount == chinesePss.Count) + { + MiraiQQBot.SendGroupMessage("一个都不合法,换个屁"); + return; + } + else if (invalidCount != 0) + { + MiraiQQBot.SendGroupMessage($"期望交换的{chinesePss.Count}只宝可梦中,有{invalidCount}只不合法,仅交换合法的{pkms.Count}只"); + } + + var code = MiraiQQBot.Info.GetRandomTradeCode(); + var __ = AddToTradeQueue(pkms, code, ulong.Parse(qq), nickName, groupId, skipAutoOTList, + PokeRoutineType.LinkTrade, out string message); + MiraiQQBot.SendGroupMessage(message); + } + + public static void StartTradeMulti(List rawPkms, string qq, string nickName, string groupId) + { + var MaxPkmsPerTrade = MiraiQQBot.Info.Hub.Config.Trade.MaxPkmsPerTrade; + if (MaxPkmsPerTrade <= 1) + { + MiraiQQBot.SendGroupMessage("请联系群主将trade/MaxPkmsPerTrade配置改为大于1"); + return; + } + else if (rawPkms.Count > MaxPkmsPerTrade) + { + MiraiQQBot.SendGroupMessage($"批量交换宝可梦数量应小于等于{MaxPkmsPerTrade}"); + return; + } + List pkms = new(); + List skipAutoOTList = new(); + int invalidCount = 0; + for (var i = 0; i < rawPkms.Count; i++) + { + var _ = CheckPkm(rawPkms[i], qq, out var msg); + if (!_) + { + LogUtil.LogInfo($"批量第{i + 1}只宝可梦有问题:{msg}", nameof(MiraiQQHelper)); + invalidCount++; + } + else + { + LogUtil.LogInfo($"批量第{i + 1}只:\n{GameInfo.GetStrings("zh").Species[rawPkms[i].Species]}", nameof(MiraiQQHelper)); + skipAutoOTList.Add(false); + pkms.Add(rawPkms[i]); + } + } + if (invalidCount == rawPkms.Count) + { + MiraiQQBot.SendGroupMessage("一个都不合法,换个屁"); + return; + } + else if (invalidCount != 0) + { + MiraiQQBot.SendGroupMessage($"期望交换的{rawPkms.Count}只宝可梦中,有{invalidCount}只不合法,仅交换合法的{pkms.Count}只"); + } + + var code = MiraiQQBot.Info.GetRandomTradeCode(); + var __ = AddToTradeQueue(pkms, code, ulong.Parse(qq), nickName, groupId, skipAutoOTList, + PokeRoutineType.LinkTrade, out string message); + MiraiQQBot.SendGroupMessage(message); + } + + public static void StartTradeMultiPs(string pssRaw, string qq, string nickName, string groupId) + { + var psArray = pssRaw.Split("\n\n").ToList(); + var MaxPkmsPerTrade = MiraiQQBot.Info.Hub.Config.Trade.MaxPkmsPerTrade; + if (MaxPkmsPerTrade <= 1) + { + MiraiQQBot.SendGroupMessage("请联系群主将trade/MaxPkmsPerTrade配置改为大于1"); + return; + } + else if (psArray.Count > MaxPkmsPerTrade) + { + MiraiQQBot.SendGroupMessage($"批量交换宝可梦数量应小于等于{MaxPkmsPerTrade}"); + return; + } + List pkms = new(); + List skipAutoOTList = new(); + int invalidCount = 0; + for (var i = 0; i < psArray.Count; i++) + { + var ps = psArray[i]; + var _ = CheckAndGetPkm(ps, qq, out var msg, out var pkm); + if (!_) + { + LogUtil.LogInfo($"批量第{i + 1}只宝可梦有问题:{msg}", nameof(MiraiQQHelper)); + invalidCount++; + } + else + { + LogUtil.LogInfo($"批量第{i + 1}只:\n{ps}", nameof(MiraiQQHelper)); + skipAutoOTList.Add(ps.Contains("Language: ")); + pkms.Add(pkm); + } + } + if (invalidCount == psArray.Count) + { + MiraiQQBot.SendGroupMessage("一个都不合法,换个屁"); + return; + } + else if (invalidCount != 0) + { + MiraiQQBot.SendGroupMessage($"期望交换的{psArray.Count}只宝可梦中,有{invalidCount}只不合法,仅交换合法的{pkms.Count}只"); + } + + var code = MiraiQQBot.Info.GetRandomTradeCode(); + var __ = AddToTradeQueue(pkms, code, ulong.Parse(qq), nickName, groupId, skipAutoOTList, + PokeRoutineType.LinkTrade, out string message); + MiraiQQBot.SendGroupMessage(message); + } + + public static void StartTradeWithoutCheck(T pkm, string qq, string nickName, string groupId, bool foreign = false) + { + var code = MiraiQQBot.Info.GetRandomTradeCode(); + var __ = AddToTradeQueue(pkm, code, ulong.Parse(qq), nickName, groupId, foreign, + PokeRoutineType.LinkTrade, out string message); + MiraiQQBot.SendGroupMessage(message); + } + + public static void StartDump(string qq, string nickName, string groupId) + { + var code = MiraiQQBot.Info.GetRandomTradeCode(); + var __ = AddToTradeQueue(new T(), code, ulong.Parse(qq), nickName, groupId, false, + PokeRoutineType.Dump, out string message); + MiraiQQBot.SendGroupMessage(message); + } + + public static bool CheckPkm(T pkm, string username, out string msg) + { + if (!MiraiQQBot.Info.GetCanQueue()) + { + msg = "对不起, 我不再接受队列请求!"; + return false; + } + try + { + if (!pkm.CanBeTraded()) + { + msg = $"取消派送, 官方禁止该宝可梦交易!"; + return false; + } + + if (pkm is T pk) + { + var valid = new LegalityAnalysis(pkm).Valid; + if (valid) + { + msg = + $"已加入等待队列. 如果你选宝可梦的速度太慢,你的派送请求将被取消!"; + return true; + } + } + + var reason = "我没办法创造非法宝可梦"; + msg = $"{reason}"; + } +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception ex) +#pragma warning restore CA1031 // Do not catch general exception types + { + LogUtil.LogSafe(ex, nameof(MiraiQQBot)); + msg = $"取消派送, 发生了一个错误"; + } + + return false; + } + + public static bool CheckAndGetPkm(string setstring, string username, out string msg, out T outPkm) + { + outPkm = new T(); + if (!MiraiQQBot.Info.GetCanQueue()) + { + msg = "对不起, 我不再接受队列请求!"; + return false; + } + + var set = ShowdownUtil.ConvertToShowdown(setstring); + if (set == null) + { + msg = $"取消派送, 宝可梦昵称为空."; + return false; + } + + var template = AutoLegalityWrapper.GetTemplate(set); + if (template.Species < 1) + { + msg = + $"取消派送, 请使用正确的Showdown Set代码"; + return false; + } + + if (set.InvalidLines.Count != 0) + { + msg = + $"取消派送, 非法的Showdown Set代码:\n{string.Join("\n", set.InvalidLines)}"; + return false; + } + + try + { + var sav = AutoLegalityWrapper.GetTrainerInfo(); + var pkm = sav.GetLegal(template, out var result); + + if (!pkm.CanBeTraded()) + { + msg = $"取消派送, 官方禁止该宝可梦交易!"; + return false; + } + + if (pkm is T pk) + { + var valid = new LegalityAnalysis(pkm).Valid; + if (valid) + { + outPkm = pk; + + msg = + $"已加入等待队列. 如果你选宝可梦的速度太慢,你的派送请求将被取消!"; + return true; + } + } + + var reason = result == "Timeout" + ? "宝可梦创造超时" + : "我没办法创造非法宝可梦"; + msg = $"{reason}"; + } +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception ex) +#pragma warning restore CA1031 // Do not catch general exception types + { + LogUtil.LogSafe(ex, nameof(MiraiQQBot)); + msg = $"取消派送, 发生了一个错误"; + } + + return false; + } + + private static bool AddToTradeQueue(T pk, int code, ulong userId, string name, string groupId, bool foreign, + PokeRoutineType type, out string msg) + { + return AddToTradeQueue(new List { pk }, code, userId, name, groupId, new List { foreign }, type, out msg); + } + + private static bool AddToTradeQueue(List pks, int code, ulong userId, string name, string groupId, List skipAutoOTList, + PokeRoutineType type, out string msg) + { + if (pks == null || pks.Count == 0) + { + msg = $"宝可梦数据为空"; + return false; + } + T pk = pks.First(); + var trainer = new PokeTradeTrainerInfo(name, userId); + var notifier = new MiraiQQTradeNotifier(pk, trainer, code, name, groupId); + var tt = type == PokeRoutineType.SeedCheck ? PokeTradeType.Seed : (type == PokeRoutineType.Dump ? PokeTradeType.Dump : PokeTradeType.Specific); + var detail = + new PokeTradeDetail(pk, trainer, notifier, tt, code, true); + detail.Context.Add("skipAutoOTList", skipAutoOTList); + if (pks.Count > 0) + { + detail.Context.Add("batch", pks); + } + var trade = new TradeEntry(detail, userId, type, name); + + var added = MiraiQQBot.Info.AddToTradeQueue(trade, userId, false); + + if (added == QueueResultAdd.AlreadyInQueue) + { + msg = $"你已经在队列中,请不要重复发送"; + return false; + } + + var position = MiraiQQBot.Info.CheckPosition(userId, type); + //msg = $"@{name}: Added to the {type} queue, unique ID: {detail.ID}. Current Position: {position.Position}"; + msg = $" 你在第{position.Position}位"; + + var botct = MiraiQQBot.Info.Hub.Bots.Count; + if (position.Position > botct) + { + var eta = MiraiQQBot.Info.Hub.Config.Queues.EstimateDelay(position.Position, botct); + //msg += $". Estimated: {eta:F1} minutes."; + msg += $", 需等待约{eta:F1}分钟"; + } + + return true; + } + } +} \ No newline at end of file diff --git a/SysBot.Pokemon.QQ/Helpers/MiraiQQTradeNotifier.cs b/SysBot.Pokemon.QQ/MiraiQQTradeNotifier.cs similarity index 72% rename from SysBot.Pokemon.QQ/Helpers/MiraiQQTradeNotifier.cs rename to SysBot.Pokemon.QQ/MiraiQQTradeNotifier.cs index b55896ccf..11e3fd635 100644 --- a/SysBot.Pokemon.QQ/Helpers/MiraiQQTradeNotifier.cs +++ b/SysBot.Pokemon.QQ/MiraiQQTradeNotifier.cs @@ -1,11 +1,7 @@ -using Mirai.Net.Sessions.Http.Managers; -using PKHeX.Core; +using PKHeX.Core; using SysBot.Base; -using SysBot.Pokemon; using System; using System.Linq; -using Mirai.Net.Data.Messages; -using Mirai.Net.Data.Messages.Concretes; using Mirai.Net.Utils.Scaffolds; namespace SysBot.Pokemon.QQ @@ -42,7 +38,7 @@ public void TradeCanceled(PokeRoutineExecutor routine, PokeTradeDetail inf OnFinish?.Invoke(routine); var line = $"@{info.Trainer.TrainerName}: Trade canceled, {msg}"; LogUtil.LogText(line); - SendMessage(new AtMessage($"{info.Trainer.ID}").Append(" 取消")); + MiraiQQBot.SendGroupMessage(new MessageChainBuilder().At($"{info.Trainer.ID}").Plain(" 取消").Build()); } public void TradeFinished(PokeRoutineExecutor routine, PokeTradeDetail info, T result) @@ -53,7 +49,7 @@ public void TradeFinished(PokeRoutineExecutor routine, PokeTradeDetail inf ? $"Trade finished. Enjoy your {(Species) tradedToUser}!" : "Trade finished!"); LogUtil.LogText(message); - SendMessage(new AtMessage($"{info.Trainer.ID}").Append(" 完成")); + MiraiQQBot.SendGroupMessage(new MessageChainBuilder().At($"{info.Trainer.ID}").Plain(" 完成").Build()); } public void TradeInitialize(PokeRoutineExecutor routine, PokeTradeDetail info) @@ -63,10 +59,7 @@ public void TradeInitialize(PokeRoutineExecutor routine, PokeTradeDetail i $"@{info.Trainer.TrainerName} (ID: {info.ID}): Initializing trade{receive} with you. Please be ready."; msg += $" Your trade code is: {info.Code:0000 0000}"; LogUtil.LogText(msg); - SendMessage(MiraiQQBot.TradeCodeDictionary.ContainsKey(info.Trainer.ID.ToString()) - ? new AtMessage($"{info.Trainer.ID}").Append($" 准备交换\n连接密码是你私信我的\n我的名字:{routine.InGameName}") - : new AtMessage($"{info.Trainer.ID}").Append( - $" 准备交换\n连接密码:{info.Code:0000 0000}\n我的名字:{routine.InGameName}")); + MiraiQQBot.SendGroupMessage(new MessageChainBuilder().At($"{info.Trainer.ID}").Plain($" 准备交换\n连接密码:{info.Code:0000 0000}\n我的名字:{routine.InGameName}").Build()); } public void TradeSearching(PokeRoutineExecutor routine, PokeTradeDetail info) @@ -76,7 +69,7 @@ public void TradeSearching(PokeRoutineExecutor routine, PokeTradeDetail in var message = $"I'm waiting for you{trainer}! My IGN is {routine.InGameName}."; message += $" Your trade code is: {info.Code:0000 0000}"; LogUtil.LogText(message); - SendMessage(new AtMessage($"{info.Trainer.ID}").Append($" 寻找中")); + MiraiQQBot.SendGroupMessage(new MessageChainBuilder().At($"{info.Trainer.ID}").Plain($" 寻找中").Build()); } public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, PokeTradeSummary message) @@ -85,25 +78,14 @@ public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail if (message.Details.Count > 0) msg += ", " + string.Join(", ", message.Details.Select(z => $"{z.Heading}: {z.Detail}")); LogUtil.LogText(msg); - SendMessage(msg); + MiraiQQBot.SendGroupMessage(msg); } public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, T result, string message) { var msg = $"Details for {result.FileName}: " + message; LogUtil.LogText(msg); - SendMessage(msg); - } - - private void SendMessage(string message) - { - var _ = MessageManager.SendGroupMessageAsync(GroupId, message).Result; - LogUtil.LogInfo($"msgId:{_} {message}", "debug"); - } - - private void SendMessage(MessageBase[] message) - { - var _ = MessageManager.SendGroupMessageAsync(GroupId, message).Result; + MiraiQQBot.SendGroupMessage(msg); } } } \ No newline at end of file diff --git a/SysBot.Pokemon.QQ/Modules/AliveModule.cs b/SysBot.Pokemon.QQ/Modules/AliveModule.cs new file mode 100644 index 000000000..46c6ed29c --- /dev/null +++ b/SysBot.Pokemon.QQ/Modules/AliveModule.cs @@ -0,0 +1,29 @@ +using Mirai.Net.Data.Messages; +using Mirai.Net.Data.Messages.Concretes; +using Mirai.Net.Data.Messages.Receivers; +using Mirai.Net.Modules; +using Mirai.Net.Sessions.Http.Managers; +using Mirai.Net.Utils.Scaffolds; +using PKHeX.Core; +using System.Linq; +using System.Reactive.Linq; + +namespace SysBot.Pokemon.QQ +{ + public class AliveModule : IModule where T : PKM, new() + { + public bool? IsEnable { get; set; } = true; + + public async void Execute(MessageReceiverBase @base) + { + QQSettings settings = MiraiQQBot.Settings; + + var receiver = @base.Concretize(); + if (settings.AliveMsg == receiver.MessageChain.OfType()?.FirstOrDefault()?.Text) + { + await MessageManager.SendGroupMessageAsync(receiver.Sender.Group.Id, settings.AliveMsg); + return; + } + } + } +} diff --git a/SysBot.Pokemon.QQ/Modules/CommandModule.cs b/SysBot.Pokemon.QQ/Modules/CommandModule.cs new file mode 100644 index 000000000..2b94e80f1 --- /dev/null +++ b/SysBot.Pokemon.QQ/Modules/CommandModule.cs @@ -0,0 +1,59 @@ +using Mirai.Net.Data.Messages; +using Mirai.Net.Data.Messages.Concretes; +using Mirai.Net.Data.Messages.Receivers; +using Mirai.Net.Modules; +using Mirai.Net.Utils.Scaffolds; +using PKHeX.Core; +using System.Linq; +using System.Reactive.Linq; + +namespace SysBot.Pokemon.QQ +{ + public class CommandModule : IModule where T : PKM, new() + { + public bool? IsEnable { get; set; } = true; + + public void Execute(MessageReceiverBase @base) + { + var receiver = @base.Concretize(); + QQSettings settings = MiraiQQBot.Settings; + + if (receiver.MessageChain.OfType().All(x => x.Target != settings.QQ)) return; + + var text = receiver.MessageChain.OfType()?.FirstOrDefault()?.Text ?? ""; + if (string.IsNullOrWhiteSpace(text)) return; + if (text.Trim().StartsWith("取消")) + { + var result = MiraiQQBot.Info.ClearTrade(ulong.Parse(receiver.Sender.Id)); + MiraiQQBot.SendGroupMessage(new MessageChainBuilder().At(receiver.Sender.Id).Plain($" {GetClearTradeMessage(result)}").Build()); + } + else if (text.Trim().StartsWith("位置")) + { + var result = MiraiQQBot.Info.CheckPosition(ulong.Parse(receiver.Sender.Id)); + MiraiQQBot.SendGroupMessage(new MessageChainBuilder().At(receiver.Sender.Id).Plain($" {GetQueueCheckResultMessage(result)}").Build()); + } + } + + public static string GetQueueCheckResultMessage(QueueCheckResult result) + { + if (!result.InQueue || result.Detail is null) + return "你不在队列里"; + var msg = $"你在第{result.Position}位"; + var pk = result.Detail.Trade.TradeData; + if (pk.Species != 0) + msg += $",交换宝可梦:{ShowdownTranslator.GameStringsZh.Species[result.Detail.Trade.TradeData.Species]}"; + return msg; + } + + private static string GetClearTradeMessage(QueueResultRemove result) + { + return result switch + { + QueueResultRemove.CurrentlyProcessing => "你正在交换中", + QueueResultRemove.CurrentlyProcessingRemoved => "正在删除", + QueueResultRemove.Removed => "已删除", + _ => "你不在队列里", + }; + } + } +} diff --git a/SysBot.Pokemon.QQ/Modules/FileModule.cs b/SysBot.Pokemon.QQ/Modules/FileModule.cs new file mode 100644 index 000000000..538014202 --- /dev/null +++ b/SysBot.Pokemon.QQ/Modules/FileModule.cs @@ -0,0 +1,114 @@ +using Mirai.Net.Data.Messages; +using Mirai.Net.Data.Messages.Concretes; +using Mirai.Net.Data.Messages.Receivers; +using Mirai.Net.Modules; +using Mirai.Net.Sessions.Http.Managers; +using Mirai.Net.Utils.Scaffolds; +using PKHeX.Core; +using SysBot.Base; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Reactive.Linq; + +namespace SysBot.Pokemon.QQ +{ + public class FileModule : IModule where T : PKM, new() + { + public bool? IsEnable { get; set; } = true; + + public async void Execute(MessageReceiverBase @base) + { + QQSettings settings = MiraiQQBot.Settings; + + var receiver = @base.Concretize(); + + var senderQQ = receiver.Sender.Id; + var nickname = receiver.Sender.Name; + var groupId = receiver.Sender.Group.Id; + + var fileMessage = receiver.MessageChain.OfType()?.FirstOrDefault(); + if (fileMessage == null) return; + LogUtil.LogInfo("In file module", nameof(FileModule)); + var fileName = fileMessage.Name; + string operationType; + if (typeof(T) == typeof(PK8) && fileName.EndsWith(".pk8", StringComparison.OrdinalIgnoreCase)) + operationType = "pk8"; + else if (typeof(T) == typeof(PB8) && fileName.EndsWith(".pb8", StringComparison.OrdinalIgnoreCase)) + operationType = "pb8"; + else if (typeof(T) == typeof(PA8) && fileName.EndsWith(".pa8", StringComparison.OrdinalIgnoreCase)) + operationType = "pa8"; + else if (typeof(T) == typeof(PK9) && fileName.EndsWith(".pk9", StringComparison.OrdinalIgnoreCase)) + operationType = "pk9"; + else if (typeof(T) == typeof(PK9) && fileName.EndsWith(".bin", StringComparison.OrdinalIgnoreCase)) + operationType = "bin"; + else return; + + PKM pkm = default!; + List pkms = new(); + try + { + var f = await FileManager.GetFileAsync(groupId, fileMessage.FileId, true); + using var client = new HttpClient(); + byte[] data = client.GetByteArrayAsync(f.DownloadInfo.Url).Result; + switch (operationType) + { + case "pk8" or "pb8" or "pk9" when data.Length != 344: + await MessageManager.SendGroupMessageAsync(groupId, "非法文件"); + return; + case "pa8" when data.Length != 376: + await MessageManager.SendGroupMessageAsync(groupId, "非法文件"); + return; + } + + switch (operationType) + { + case "pk8": + pkm = new PK8(data); + break; + case "pb8": + pkm = new PB8(data); + break; + case "pa8": + pkm = new PA8(data); + break; + case "pk9": + pkm = new PK9(data); + break; + case "bin": + pkms = bin2List(data); + break; + default: return; + } + + LogUtil.LogInfo($"operationType:{operationType}", nameof(FileModule)); + await FileManager.DeleteFileAsync(groupId, fileMessage.FileId); + } + catch (Exception ex) + { + LogUtil.LogError(ex.Message, nameof(FileModule)); + return; + } + if (pkms != null && pkms.Count > 0) + MiraiQQHelper.StartTradeMulti(pkms, senderQQ, nickname, groupId); + else + MiraiQQHelper.StartTrade((T)pkm, senderQQ, nickname, groupId); + } + + private static List bin2List(byte[] bb) + { + int count = 344; + int times = bb.Length % count == 0 ? (bb.Length / count) : (bb.Length / count + 1); + List pkms = new(); + for (var i = 0; i < times; i++) + { + int start = i * count; + int end = (start + count) > bb.Length ? bb.Length : (start + count); + PK9 pk9 = new(bb[start..end]); + if (pk9.Valid && pk9.Species > 0) pkms.Add(pk9); + } + return pkms; + } + } +} diff --git a/SysBot.Pokemon.QQ/Modules/PsModule.cs b/SysBot.Pokemon.QQ/Modules/PsModule.cs new file mode 100644 index 000000000..a49900adf --- /dev/null +++ b/SysBot.Pokemon.QQ/Modules/PsModule.cs @@ -0,0 +1,91 @@ +using Mirai.Net.Data.Messages; +using Mirai.Net.Data.Messages.Concretes; +using Mirai.Net.Data.Messages.Receivers; +using Mirai.Net.Modules; +using Mirai.Net.Sessions.Http.Managers; +using Mirai.Net.Utils.Scaffolds; +using PKHeX.Core; +using SysBot.Base; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Reactive.Linq; +using System.Reflection; + +namespace SysBot.Pokemon.QQ +{ + public class PsModule : IModule where T : PKM, new() + { + public bool? IsEnable { get; set; } = true; + + public void Execute(MessageReceiverBase @base) + { + var receiver = @base.Concretize(); + QQSettings settings = MiraiQQBot.Settings; + + if (receiver.MessageChain.OfType().All(x => x.Target != settings.QQ)) return; + + var text = receiver.MessageChain.OfType()?.FirstOrDefault()?.Text ?? ""; + if (string.IsNullOrWhiteSpace(text)) return; + var qq = receiver.Sender.Id; + var nickName = receiver.Sender.Name; + var groupId = receiver.GroupId; + // 中英文判断 + if (IsChinesePS(text)) + ProcessChinesePS(text, qq, nickName, groupId); + else if (IsPS(text)) + ProcessPS(text, qq, nickName, groupId); + } + + private void ProcessPS(string text, string qq, string nickName, string groupId) + { + LogUtil.LogInfo($"收到ps代码:\n{text}", nameof(PsModule)); + var pss = text.Split("\n\n"); + if (pss.Length > 1) + MiraiQQHelper.StartTradeMultiPs(text, qq, nickName, groupId); + else + MiraiQQHelper.StartTrade(text, qq, nickName, groupId); + } + + private void ProcessChinesePS(string text, string qq, string nickName, string groupId) + { + LogUtil.LogInfo($"收到中文ps代码:\n{text}", nameof(PsModule)); + var pss = text.Split("\n\n"); + if (pss.Length > 1) + MiraiQQHelper.StartTradeMultiChinese(text, qq, nickName, groupId); + else + { + string ps = ShowdownTranslator.Chinese2Showdown(text); + LogUtil.LogInfo($"中文转换后ps代码:\n{ps}", nameof(PsModule)); + MiraiQQHelper.StartTrade(ps, qq, nickName, groupId); + } + + } + + private static bool IsChinesePS(string str) + { + var gameStrings = ShowdownTranslator.GameStringsZh; + for (int i = 1; i < gameStrings.Species.Count; i++) + { + if (str.Contains(gameStrings.Species[i])) + { + return true; + } + } + return false; + } + private static bool IsPS(string str) + { + var gameStrings = ShowdownTranslator.GameStringsEn; + for (int i = 1; i < gameStrings.Species.Count; i++) + { + if (str.Contains(gameStrings.Species[i])) + { + return true; + } + } + return false; + } + } +} diff --git a/SysBot.Pokemon.QQ/SysBot.Pokemon.QQ.csproj b/SysBot.Pokemon.QQ/SysBot.Pokemon.QQ.csproj index 63a6b4516..43efb1d08 100644 --- a/SysBot.Pokemon.QQ/SysBot.Pokemon.QQ.csproj +++ b/SysBot.Pokemon.QQ/SysBot.Pokemon.QQ.csproj @@ -8,7 +8,7 @@ - + diff --git a/SysBot.Tests/TranslatorTests.cs b/SysBot.Tests/TranslatorTests.cs index e6ee00bab..e7f04fbf8 100644 --- a/SysBot.Tests/TranslatorTests.cs +++ b/SysBot.Tests/TranslatorTests.cs @@ -1,12 +1,15 @@ using FluentAssertions; using PKHeX.Core; using SysBot.Pokemon; +using System.Diagnostics; using Xunit; namespace SysBot.Tests { public class TranslatorTests { + static TranslatorTests() => AutoLegalityWrapper.EnsureInitialized(new LegalitySettings()); + [Theory] [InlineData("公肯泰罗帕底亚的样子(火)形态", "Tauros-Paldea-Fire (M)")] public void TestForm(string input, string output) @@ -15,5 +18,26 @@ public void TestForm(string input, string output) result.Should().Be(output); } + [Theory] + [InlineData("皮卡丘")] + [InlineData("木木枭")] + public void TestLegal(string input) + { + var setstring = ShowdownTranslator.Chinese2Showdown(input); + var set = ShowdownUtil.ConvertToShowdown(setstring); + set.Should().NotBeNull(); + var template = AutoLegalityWrapper.GetTemplate(set); + template.Species.Should().BeGreaterThan(0); + var sav = AutoLegalityWrapper.GetTrainerInfo(); + var pkm = sav.GetLegal(template, out var result); + Trace.WriteLine(result.ToString()); + + pkm.CanBeTraded().Should().BeTrue(); + (pkm is PK9).Should().BeTrue(); + var valid = new LegalityAnalysis(pkm).Valid; + valid.Should().BeTrue(); + } + } + } From e21fbe77e5b3bcfe02cf54d6eb3c16a3d208d456 Mon Sep 17 00:00:00 2001 From: Easy World Date: Sun, 19 Mar 2023 23:23:19 +0800 Subject: [PATCH 099/171] fix wrong character --- SysBot.Pokemon.QQ/Modules/PsModule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SysBot.Pokemon.QQ/Modules/PsModule.cs b/SysBot.Pokemon.QQ/Modules/PsModule.cs index a49900adf..78ad318a2 100644 --- a/SysBot.Pokemon.QQ/Modules/PsModule.cs +++ b/SysBot.Pokemon.QQ/Modules/PsModule.cs @@ -51,7 +51,7 @@ private void ProcessPS(string text, string qq, string nickName, string groupId) private void ProcessChinesePS(string text, string qq, string nickName, string groupId) { LogUtil.LogInfo($"收到中文ps代码:\n{text}", nameof(PsModule)); - var pss = text.Split("\n\n"); + var pss = text.Split("+"); if (pss.Length > 1) MiraiQQHelper.StartTradeMultiChinese(text, qq, nickName, groupId); else From 6b5a60efaf2475fc9d99eedc4db79b7aefbb5d80 Mon Sep 17 00:00:00 2001 From: Easy World Date: Tue, 21 Mar 2023 21:34:19 +0800 Subject: [PATCH 100/171] =?UTF-8?q?=E9=87=8E=E8=9B=AE=E9=B2=88=E9=B1=BC?= =?UTF-8?q?=E5=BD=A2=E6=80=81=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SysBot.Pokemon/Helpers/ShowdownTranslator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SysBot.Pokemon/Helpers/ShowdownTranslator.cs b/SysBot.Pokemon/Helpers/ShowdownTranslator.cs index 709ca86b2..a78698790 100644 --- a/SysBot.Pokemon/Helpers/ShowdownTranslator.cs +++ b/SysBot.Pokemon/Helpers/ShowdownTranslator.cs @@ -470,8 +470,8 @@ public static string Chinese2Showdown(string zh) {"龙","Dragon"}, {"恶","Dark"}, {"妖精","Fairy"}, - {"蓝条纹的样子","Blue-Striped"}, - {"白条纹的样子","White-Striped"}, + {"蓝条纹的样子","Blue"}, + {"白条纹的样子","White"}, {"夏天的样子","Summer"}, {"秋天的样子","Autumn"}, {"冬天的样子","Winter"}, From 903fe5465286a8f90d1043228c5ece866acf6512 Mon Sep 17 00:00:00 2001 From: Easy World Date: Sun, 26 Mar 2023 21:43:46 +0800 Subject: [PATCH 101/171] minor tweak --- SysBot.Pokemon.Dodo/DodoTrade.cs | 30 ++ SysBot.Pokemon.Dodo/DodoTradeNotifier.cs | 260 ++++++------- SysBot.Pokemon.Dodo/PokemonProcessService.cs | 44 ++- SysBot.Pokemon/Helpers/AbstractTrade.cs | 370 +++++++++++++++++++ SysBot.Pokemon/Helpers/ShowdownTranslator.cs | 13 + 5 files changed, 564 insertions(+), 153 deletions(-) create mode 100644 SysBot.Pokemon.Dodo/DodoTrade.cs create mode 100644 SysBot.Pokemon/Helpers/AbstractTrade.cs diff --git a/SysBot.Pokemon.Dodo/DodoTrade.cs b/SysBot.Pokemon.Dodo/DodoTrade.cs new file mode 100644 index 000000000..efe3332b9 --- /dev/null +++ b/SysBot.Pokemon.Dodo/DodoTrade.cs @@ -0,0 +1,30 @@ +using PKHeX.Core; +using SysBot.Pokemon.Helpers; +using System; + +namespace SysBot.Pokemon.Dodo +{ + public class DodoTrade : AbstractTrade where T : PKM, new() + { + private readonly string channelId = default!; + private readonly string islandSourceId = default!; + + public DodoTrade(ulong dodoId, string nickName, string channelId, string islandSourceId) + { + SetPokeTradeTrainerInfo(new PokeTradeTrainerInfo(nickName, dodoId)); + SetTradeQueueInfo(DodoBot.Info); + this.channelId = channelId; + this.islandSourceId = islandSourceId; + } + + public override IPokeTradeNotifier GetPokeTradeNotifier(T pkm, int code) + { + return new DodoTradeNotifier(pkm, userInfo, code, userInfo.ID.ToString(), channelId, islandSourceId); + } + + public override void SendMessage(string message) + { + DodoBot.SendChannelAtMessage(userInfo.ID, message, channelId); + } + } +} diff --git a/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs b/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs index d34a6e039..c6d339bba 100644 --- a/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs +++ b/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs @@ -1,137 +1,137 @@ -using PKHeX.Core; -using SysBot.Base; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text.RegularExpressions; - -namespace SysBot.Pokemon.Dodo -{ - public class DodoTradeNotifier : IPokeTradeNotifier where T : PKM, new() - { - private T Data { get; } - private PokeTradeTrainerInfo Info { get; } - private int Code { get; } - private string Username { get; } - - private string ChannelId { get; } - private string IslandSourceId { get; } - - public DodoTradeNotifier(T data, PokeTradeTrainerInfo info, int code, string username, string channelId, - string islandSourceId) - { - Data = data; - Info = info; - Code = code; - Username = username; - ChannelId = channelId; - IslandSourceId = islandSourceId; - LogUtil.LogText($"Created trade details for {Username} - {Code}"); - } - - public Action>? OnFinish { private get; set; } - - public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, string message) - { - LogUtil.LogText(message); - if (message.Contains("Found Trading Partner:")) - { - Regex regex = new Regex("TID: (\\d+)"); - string tid = regex.Match(message).Groups[1].ToString(); - regex = new Regex("SID: (\\d+)"); - string sid = regex.Match(message).Groups[1].ToString(); - DodoBot.SendPersonalMessage(info.Trainer.ID.ToString(), $"找到你了,你的SID7:{sid},TID7:{tid}", - IslandSourceId); - } - else if (message.StartsWith("批量")) - { - DodoBot.SendChannelMessage(message, ChannelId); - } - } - - public void TradeCanceled(PokeRoutineExecutor routine, PokeTradeDetail info, PokeTradeResult msg) - { - OnFinish?.Invoke(routine); - var line = $"@{info.Trainer.TrainerName}: Trade canceled, {msg}"; - LogUtil.LogText(line); +using PKHeX.Core; +using SysBot.Base; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; + +namespace SysBot.Pokemon.Dodo +{ + public class DodoTradeNotifier : IPokeTradeNotifier where T : PKM, new() + { + private T Data { get; } + private PokeTradeTrainerInfo Info { get; } + private int Code { get; } + private string Username { get; } + + private string ChannelId { get; } + private string IslandSourceId { get; } + + public DodoTradeNotifier(T data, PokeTradeTrainerInfo info, int code, string username, string channelId, + string islandSourceId) + { + Data = data; + Info = info; + Code = code; + Username = username; + ChannelId = channelId; + IslandSourceId = islandSourceId; + LogUtil.LogText($"Created trade details for {Username} - {Code}"); + } + + public Action>? OnFinish { private get; set; } + + public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, string message) + { + LogUtil.LogText(message); + if (message.Contains("Found Trading Partner:")) + { + Regex regex = new Regex("TID: (\\d+)"); + string tid = regex.Match(message).Groups[1].ToString(); + regex = new Regex("SID: (\\d+)"); + string sid = regex.Match(message).Groups[1].ToString(); + DodoBot.SendPersonalMessage(info.Trainer.ID.ToString(), $"找到你了,你的SID7:{sid},TID7:{tid}", + IslandSourceId); + } + else if (message.StartsWith("批量")) + { + DodoBot.SendChannelMessage(message, ChannelId); + } + } + + public void TradeCanceled(PokeRoutineExecutor routine, PokeTradeDetail info, PokeTradeResult msg) + { + OnFinish?.Invoke(routine); + var line = $"@{info.Trainer.TrainerName}: Trade canceled, {msg}"; + LogUtil.LogText(line); DodoBot.SendChannelAtMessage(info.Trainer.ID, "取消", ChannelId); - var waitUserIds = DodoBot.Info.GetUserIdList(4).ToList(); - for (int i = 1; i < waitUserIds.Count(); i++) - { - DodoBot.SendChannelAtMessage(waitUserIds[i], $"你在第{i + 1}位,还有{i}个以后就到你了!\n", ChannelId); - } - } - - public void TradeFinished(PokeRoutineExecutor routine, PokeTradeDetail info, T result) - { - OnFinish?.Invoke(routine); - var tradedToUser = Data.Species; - var message = $"@{info.Trainer.TrainerName}: " + (tradedToUser != 0 - ? $"Trade finished. Enjoy your {(Species) tradedToUser}!" - : "Trade finished!"); - LogUtil.LogText(message); + var waitUserIds = DodoBot.Info.GetUserIdList(4).ToList(); + for (int i = 1; i < waitUserIds.Count(); i++) + { + DodoBot.SendChannelAtMessage(waitUserIds[i], $"你在第{i + 1}位,还有{i}个以后就到你了!\n", ChannelId); + } + } + + public void TradeFinished(PokeRoutineExecutor routine, PokeTradeDetail info, T result) + { + OnFinish?.Invoke(routine); + var tradedToUser = Data.Species; + var message = $"@{info.Trainer.TrainerName}: " + (tradedToUser != 0 + ? $"Trade finished. Enjoy your {(Species) tradedToUser}!" + : "Trade finished!"); + LogUtil.LogText(message); DodoBot.SendChannelAtMessage(info.Trainer.ID, "完成", ChannelId); var waitUserIds = DodoBot.Info.GetUserIdList(4).ToList(); for (int i = 1; i < waitUserIds.Count(); i++) { DodoBot.SendChannelAtMessage(waitUserIds[i], $"你在第{i + 1}位,还有{i}个以后就到你了!\n", ChannelId); - } - } - - public void TradeInitialize(PokeRoutineExecutor routine, PokeTradeDetail info) - { - var receive = Data.Species == 0 ? string.Empty : $" ({Data.Nickname})"; - var msg = - $"@{info.Trainer.TrainerName} (ID: {info.ID}): Initializing trade{receive} with you. Please be ready."; - msg += $" Your trade code is: {info.Code:0000 0000}"; - LogUtil.LogText(msg); - var text = $"\n派送:{ShowdownTranslator.GameStringsZh.Species[Data.Species]}\n密码:见私信\n状态:初始化"; - List batchPKMs = (List)info.Context.GetValueOrDefault("batch", new List()); - if (batchPKMs.Count > 1) - { - text = $"\n批量派送{batchPKMs.Count}只宝可梦\n密码:见私信\n状态:初始化"; - } - DodoBot.SendChannelAtMessage(info.Trainer.ID, text, ChannelId); - DodoBot.SendPersonalMessage(info.Trainer.ID.ToString(), - $"派送:{ShowdownTranslator.GameStringsZh.Species[Data.Species]}\n密码:{info.Code:0000 0000}", - IslandSourceId); - } - - public void TradeSearching(PokeRoutineExecutor routine, PokeTradeDetail info) - { - var name = Info.TrainerName; - var trainer = string.IsNullOrEmpty(name) ? string.Empty : $", @{name}"; - var message = $"I'm waiting for you{trainer}! My IGN is {routine.InGameName}."; - message += $" Your trade code is: {info.Code:0000 0000}"; - LogUtil.LogText(message); - var text = $"派送:{ShowdownTranslator.GameStringsZh.Species[Data.Species]}\n密码:见私信\n状态:搜索中"; - List batchPKMs = (List)info.Context.GetValueOrDefault("batch", new List()); - if (batchPKMs.Count > 1) - { - text = $"批量派送{batchPKMs.Count}只宝可梦\n密码:见私信\n状态:搜索中"; - } - DodoBot.SendChannelMessage(text, ChannelId); - //DodoBot.SendPersonalMessage(info.Trainer.ID.ToString(), $"{info.Code:0000 0000}", IslandSourceId); - } - - public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, PokeTradeSummary message) - { - var msg = message.Summary; - if (message.Details.Count > 0) - msg += ", " + string.Join(", ", message.Details.Select(z => $"{z.Heading}: {z.Detail}")); - LogUtil.LogText(msg); - } - - public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, T result, string message) - { - var msg = $"Details for {result.FileName}: " + message; - LogUtil.LogText(msg); - if (result.Species != 0 && info.Type == PokeTradeType.Dump) - { - var text = - $"species:{result.Species}\npid:{result.PID}\nec:{result.EncryptionConstant}\nIVs:{string.Join(",", result.IVs)}\nisShiny:{result.IsShiny}"; - DodoBot.SendChannelMessage(text, ChannelId); - } - } - } + } + } + + public void TradeInitialize(PokeRoutineExecutor routine, PokeTradeDetail info) + { + var receive = Data.Species == 0 ? string.Empty : $" ({Data.Nickname})"; + var msg = + $"@{info.Trainer.TrainerName} (ID: {info.ID}): Initializing trade{receive} with you. Please be ready."; + msg += $" Your trade code is: {info.Code:0000 0000}"; + LogUtil.LogText(msg); + var text = $"\n派送:{ShowdownTranslator.GameStringsZh.Species[Data.Species]}\n密码:见私信\n状态:初始化"; + List batchPKMs = (List)info.Context.GetValueOrDefault("batch", new List()); + if (batchPKMs.Count > 1) + { + text = $"\n批量派送{batchPKMs.Count}只宝可梦\n密码:见私信\n状态:初始化"; + } + DodoBot.SendChannelAtMessage(info.Trainer.ID, text, ChannelId); + DodoBot.SendPersonalMessage(info.Trainer.ID.ToString(), + $"派送:{ShowdownTranslator.GameStringsZh.Species[Data.Species]}\n密码:{info.Code:0000 0000}", + IslandSourceId); + } + + public void TradeSearching(PokeRoutineExecutor routine, PokeTradeDetail info) + { + var name = Info.TrainerName; + var trainer = string.IsNullOrEmpty(name) ? string.Empty : $", @{name}"; + var message = $"I'm waiting for you{trainer}! My IGN is {routine.InGameName}."; + message += $" Your trade code is: {info.Code:0000 0000}"; + LogUtil.LogText(message); + var text = $"派送:{ShowdownTranslator.GameStringsZh.Species[Data.Species]}\n密码:见私信\n状态:搜索中"; + List batchPKMs = (List)info.Context.GetValueOrDefault("batch", new List()); + if (batchPKMs.Count > 1) + { + text = $"批量派送{batchPKMs.Count}只宝可梦\n密码:见私信\n状态:搜索中"; + } + DodoBot.SendChannelMessage(text, ChannelId); + //DodoBot.SendPersonalMessage(info.Trainer.ID.ToString(), $"{info.Code:0000 0000}", IslandSourceId); + } + + public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, PokeTradeSummary message) + { + var msg = message.Summary; + if (message.Details.Count > 0) + msg += ", " + string.Join(", ", message.Details.Select(z => $"{z.Heading}: {z.Detail}")); + LogUtil.LogText(msg); + } + + public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, T result, string message) + { + var msg = $"Details for {result.FileName}: " + message; + LogUtil.LogText(msg); + if (result.Species != 0 && info.Type == PokeTradeType.Dump) + { + var text = + $"species:{result.Species}\npid:{result.PID}\nec:{result.EncryptionConstant}\nIVs:{string.Join(",", result.IVs)}\nisShiny:{result.IsShiny}"; + DodoBot.SendChannelMessage(text, ChannelId); + } + } + } } \ No newline at end of file diff --git a/SysBot.Pokemon.Dodo/PokemonProcessService.cs b/SysBot.Pokemon.Dodo/PokemonProcessService.cs index c1283f78f..cad6ef128 100644 --- a/SysBot.Pokemon.Dodo/PokemonProcessService.cs +++ b/SysBot.Pokemon.Dodo/PokemonProcessService.cs @@ -74,7 +74,8 @@ public override void ChannelMessageEvent( var p = GetPKM(downloadBytes); if (p is TP pkm) { - DodoHelper.StartTrade(pkm, eventBody.DodoSourceId, eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId); + //DodoHelper.StartTrade(pkm, eventBody.DodoSourceId, eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId); + new DodoTrade(ulong.Parse(eventBody.DodoSourceId), eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId).StartTradePKM(pkm); } return; @@ -92,25 +93,28 @@ public override void ChannelMessageEvent( if (!content.Contains($"<@!{_botDodoSourceId}>")) return; content = content.Substring(content.IndexOf('>') + 1); - if (content.Trim().StartsWith("trade")) + if (typeof(TP) == typeof(PK9) && content.Contains("\n\n") && ShowdownTranslator.IsPS(content))// 仅SV支持批量,其他偷懒还没写 { - content = content.Replace("trade", ""); - DodoHelper.StartTrade(content, eventBody.DodoSourceId, eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId); + //DodoHelper.StartTradeMultiPs(content.Trim(), eventBody.DodoSourceId, eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId); + new DodoTrade(ulong.Parse(eventBody.DodoSourceId), eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId).StartTradeMultiPs(content.Trim()); return; - } - else if (content.Trim().StartsWith("dump")) + } + else if (ShowdownTranslator.IsPS(content)) { - DodoHelper.StartDump(eventBody.DodoSourceId, eventBody.Personal.NickName, eventBody.ChannelId); + //DodoHelper.StartTrade(content.Trim(), eventBody.DodoSourceId, eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId); + new DodoTrade(ulong.Parse(eventBody.DodoSourceId), eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId).StartTradePs(content.Trim()); return; } - else if (typeof(TP) == typeof(PK9) && content.Trim().Contains("+"))// 仅SV支持批量,其他偷懒还没写 + else if (content.Trim().StartsWith("dump")) { - DodoHelper.StartTradeMultiChinese(content.Trim(), eventBody.DodoSourceId, eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId); + //DodoHelper.StartDump(eventBody.DodoSourceId, eventBody.Personal.NickName, eventBody.ChannelId); + new DodoTrade(ulong.Parse(eventBody.DodoSourceId), eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId).StartDump(); return; } - else if (typeof(TP) == typeof(PK9) && content.Trim().Contains("队伍"))// 仅SV支持批量,其他偷懒还没写 + else if (typeof(TP) == typeof(PK9) && content.Trim().Contains('+'))// 仅SV支持批量,其他偷懒还没写 { - DodoHelper.StartTradeMultiPs(content.Replace("队伍", "").Trim(), eventBody.DodoSourceId, eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId); + //DodoHelper.StartTradeMultiChinese(content.Trim(), eventBody.DodoSourceId, eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId); + new DodoTrade(ulong.Parse(eventBody.DodoSourceId), eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId).StartTradeMultiChinesePs(content.Trim()); return; } @@ -118,7 +122,8 @@ public override void ChannelMessageEvent( if (!string.IsNullOrWhiteSpace(ps)) { LogUtil.LogInfo($"收到命令\n{ps}", LogIdentity); - DodoHelper.StartTrade(ps, eventBody.DodoSourceId, eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId); + //DodoHelper.StartTrade(ps, eventBody.DodoSourceId, eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId); + new DodoTrade(ulong.Parse(eventBody.DodoSourceId), eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId).StartTradePs(ps); } else if (content.Contains("取消")) { @@ -163,17 +168,9 @@ private static string GetClearTradeMessage(QueueResultRemove result) private static bool ValidFileSize(long size) { - if (typeof(TP) == typeof(PK8) || typeof(TP) == typeof(PB8) || typeof(TP) == typeof(PK9)) - { - return size == 344; - } - - if (typeof(TP) == typeof(PA8)) - { - return size == 376; - } - - return false; + if (typeof(TP) == typeof(PK8) || typeof(TP) == typeof(PB8) || typeof(TP) == typeof(PK9)) return size == 344; + else if (typeof(TP) == typeof(PA8)) return size == 376; + else return false; } private static bool ValidFileName(string fileName) @@ -198,5 +195,6 @@ public override void MessageReactionEvent( { // Do nothing } + } } \ No newline at end of file diff --git a/SysBot.Pokemon/Helpers/AbstractTrade.cs b/SysBot.Pokemon/Helpers/AbstractTrade.cs new file mode 100644 index 000000000..10ca5eb01 --- /dev/null +++ b/SysBot.Pokemon/Helpers/AbstractTrade.cs @@ -0,0 +1,370 @@ +using PKHeX.Core; +using SysBot.Base; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace SysBot.Pokemon.Helpers +{ + /// + /// 宝可梦抽象交易类 + /// 本类需要实现SendMessage,同时也要实现一个多参数的构造函数, + /// 参数应该包括该类型机器人发送消息的相关信息,以便SendMessage使用 + /// 注意在实现抽象类的构造方法里一定要调用SetPokeTradeTrainerInfo和SetTradeQueueInfo + /// + /// + public abstract class AbstractTrade where T : PKM, new() + { + public abstract void SendMessage(string message);//完善此方法以实现发送消息功能 + public abstract IPokeTradeNotifier GetPokeTradeNotifier(T pkm, int code);//完善此方法以实现消息通知功能 + protected PokeTradeTrainerInfo userInfo = default!; + private TradeQueueInfo queueInfo = default!; + + public void SetPokeTradeTrainerInfo(PokeTradeTrainerInfo pokeTradeTrainerInfo) + { + userInfo = pokeTradeTrainerInfo; + } + + public void SetTradeQueueInfo(TradeQueueInfo queueInfo) + { + this.queueInfo = queueInfo; + } + + public void StartTradePs(string ps) + { + var _ = CheckAndGetPkm(ps, out var msg, out var pkm); + if (!_) + { + SendMessage(msg); + return; + } + var foreign = ps.Contains("Language: "); + StartTradeWithoutCheck(pkm, foreign); + } + public void StartTradeChinesePs(string chinesePs) + { + var ps = ShowdownTranslator.Chinese2Showdown(chinesePs); + StartTradePs(ps); + } + public void StartTradePKM(T pkm) + { + var _ = CheckPkm(pkm, out var msg); + if (!_) + { + SendMessage(msg); + return; + } + + StartTradeWithoutCheck(pkm); + } + public void StartTradeMultiPs(string pss) + { + var psList = pss.Split("\n\n").ToList(); + if (!JudgeMultiNum(psList.Count)) return; + + var pkms = GetPKMsFromPsList(psList, isChinesePS: false, out int invalidCount, out List skipAutoOTList); + + if (!JudgeInvalidCount(invalidCount, psList.Count)) return; + + var code = queueInfo.GetRandomTradeCode(); + var __ = AddToTradeQueue(pkms, code, skipAutoOTList, + PokeRoutineType.LinkTrade, out string message); + SendMessage(message); + } + + public void StartTradeMultiChinesePs(string chinesePssString) + { + var chinesePsList = chinesePssString.Split('+').ToList(); + if (!JudgeMultiNum(chinesePsList.Count)) return; + + List pkms = GetPKMsFromPsList(chinesePsList, true, out int invalidCount, out List skipAutoOTList); + + if (!JudgeInvalidCount(invalidCount, chinesePsList.Count)) return; + + var code = queueInfo.GetRandomTradeCode(); + var __ = AddToTradeQueue(pkms, code, skipAutoOTList, + PokeRoutineType.LinkTrade, out string message); + SendMessage(message); + } + + public void StartTradeMultiPKM(List rawPkms) + { + if (!JudgeMultiNum(rawPkms.Count)) return; + + List pkms = new(); + List skipAutoOTList = new(); + int invalidCount = 0; + for (var i = 0; i < rawPkms.Count; i++) + { + var _ = CheckPkm(rawPkms[i], out var msg); + if (!_) + { + LogUtil.LogInfo($"批量第{i + 1}只宝可梦有问题:{msg}", nameof(AbstractTrade)); + invalidCount++; + } + else + { + LogUtil.LogInfo($"批量第{i + 1}只:\n{GameInfo.GetStrings("zh").Species[rawPkms[i].Species]}", nameof(AbstractTrade)); + skipAutoOTList.Add(false); + pkms.Add(rawPkms[i]); + } + } + + if (!JudgeInvalidCount(invalidCount, rawPkms.Count)) return; + + var code = queueInfo.GetRandomTradeCode(); + var __ = AddToTradeQueue(pkms, code, skipAutoOTList, + PokeRoutineType.LinkTrade, out string message); + SendMessage(message); + } + /// + /// 根据pokemon showdown代码生成对应版本的PKM文件 + /// + /// ps代码 + /// 是否是中文ps + /// 不合法的宝可梦数量 + /// 需要跳过自id的列表 + /// + private List GetPKMsFromPsList(List psList, bool isChinesePS, out int invalidCount, out List skipAutoOTList) + { + List pkms = new(); + skipAutoOTList = new List(); + invalidCount = 0; + for (var i = 0; i < psList.Count; i++) + { + var ps = isChinesePS ? ShowdownTranslator.Chinese2Showdown(psList[i]) : psList[i]; + var _ = CheckAndGetPkm(ps, out var msg, out var pkm); + if (!_) + { + LogUtil.LogInfo($"批量第{i + 1}只宝可梦有问题:{msg}", nameof(AbstractTrade)); + invalidCount++; + } + else + { + LogUtil.LogInfo($"批量第{i + 1}只:\n{ps}", nameof(AbstractTrade)); + skipAutoOTList.Add(ps.Contains("Language: ")); + pkms.Add(pkm); + } + } + return pkms; + } + /// + /// 判断是否符合批量规则 + /// + /// 待计算的数量 + /// + private bool JudgeMultiNum(int multiNum) + { + var maxPkmsPerTrade = queueInfo.Hub.Config.Trade.MaxPkmsPerTrade; + if (maxPkmsPerTrade <= 1) + { + SendMessage("请联系群主将trade/MaxPkmsPerTrade配置改为大于1"); + return false; + } + else if (multiNum > maxPkmsPerTrade) + { + SendMessage($"批量交换宝可梦数量应小于等于{maxPkmsPerTrade}"); + return false; + } + return true; + } + /// + /// 判断无效数量 + /// + /// + /// + /// + private bool JudgeInvalidCount(int invalidCount, int totalCount) + { + if (invalidCount == totalCount) + { + SendMessage("一个都不合法,换个屁"); + return false; + } + else if (invalidCount != 0) + { + SendMessage($"期望交换的{totalCount}只宝可梦中,有{invalidCount}只不合法,仅交换合法的{totalCount - invalidCount}只"); + } + return true; + } + + public void StartTradeWithoutCheck(T pkm, bool foreign = false) + { + var code = queueInfo.GetRandomTradeCode(); + var __ = AddToTradeQueue(pkm, code, foreign, + PokeRoutineType.LinkTrade, out string message); + SendMessage(message); + } + + public void StartDump() + { + var code = queueInfo.GetRandomTradeCode(); + var __ = AddToTradeQueue(new T(), code, false, + PokeRoutineType.Dump, out string message); + SendMessage(message); + } + public bool CheckPkm(T pkm, out string msg) + { + if (!queueInfo.GetCanQueue()) + { + msg = "对不起, 我不再接受队列请求!"; + return false; + } + + try + { + if (!pkm.CanBeTraded()) + { + msg = $"取消派送, 官方禁止该宝可梦交易!"; + return false; + } + + if (pkm is T pk) + { + var valid = new LegalityAnalysis(pkm).Valid; + if (valid) + { + msg = + $"已加入等待队列. 如果你选宝可梦的速度太慢,你的派送请求将被取消!"; + return true; + } + } + + var reason = "我没办法创造非法宝可梦"; + msg = $"{reason}"; + } + catch (Exception ex) + { + LogUtil.LogSafe(ex, nameof(AbstractTrade)); + msg = $"取消派送, 发生了一个错误"; + } + + return false; + } + + public bool CheckAndGetPkm(string setstring, out string msg, out T outPkm) + { + outPkm = new T(); + if (!queueInfo.GetCanQueue()) + { + msg = "对不起, 我不再接受队列请求!"; + return false; + } + + var set = ShowdownUtil.ConvertToShowdown(setstring); + if (set == null) + { + msg = $"取消派送, 宝可梦昵称为空."; + return false; + } + + var template = AutoLegalityWrapper.GetTemplate(set); + if (template.Species < 1) + { + msg = + $"取消派送, 请使用正确的Showdown Set代码"; + return false; + } + + try + { + var sav = AutoLegalityWrapper.GetTrainerInfo(); + GenerationFix(sav); + var pkm = sav.GetLegal(template, out var result); + + if (!pkm.CanBeTraded()) + { + msg = $"取消派送, 官方禁止该宝可梦交易!"; + return false; + } + + if (pkm is T pk) + { + var valid = new LegalityAnalysis(pkm).Valid; + if (valid) + { + outPkm = pk; + + msg = + $"已加入等待队列. 如果你选宝可梦的速度太慢,你的派送请求将被取消!"; + return true; + } + } + + var reason = result == "Timeout" + ? "宝可梦创造超时" + : "我没办法创造非法宝可梦"; + msg = $"{reason}"; + } + catch (Exception ex) + { + LogUtil.LogSafe(ex, nameof(AbstractTrade)); + msg = $"取消派送, 发生了一个错误"; + } + + return false; + } + + private static void GenerationFix(ITrainerInfo sav) + { + if (typeof(T) == typeof(PK8) || typeof(T) == typeof(PB8) || typeof(T) == typeof(PA8)) + { + var a = sav.GetType().GetProperty("Generation"); + a?.SetValue(sav, 8); + } + } + + private bool AddToTradeQueue(T pk, int code, bool skipAutoOT, + PokeRoutineType type, out string msg) + { + return AddToTradeQueue(new List { pk }, code, new List { skipAutoOT }, type, out msg); + } + + private bool AddToTradeQueue(List pks, int code, List skipAutoOTList, + PokeRoutineType type, out string msg) + { + if (pks == null || pks.Count == 0) + { + msg = $"宝可梦数据为空"; + return false; + } + T pk = pks.First(); + var trainer = userInfo; + var notifier = GetPokeTradeNotifier(pk, code); + var tt = type == PokeRoutineType.SeedCheck + ? PokeTradeType.Seed + : (type == PokeRoutineType.Dump ? PokeTradeType.Dump : PokeTradeType.Specific); + var detail = + new PokeTradeDetail(pk, trainer, notifier, tt, code, true); + detail.Context.Add("skipAutoOTList", skipAutoOTList); + if (pks.Count > 0) + { + detail.Context.Add("batch", pks); + } + var trade = new TradeEntry(detail, userInfo.ID, type, userInfo.TrainerName); + + var added = queueInfo.AddToTradeQueue(trade, userInfo.ID, false); + + if (added == QueueResultAdd.AlreadyInQueue) + { + msg = $"你已经在队列中,请不要重复发送"; + return false; + } + + var position = queueInfo.CheckPosition(userInfo.ID, type); + //msg = $"@{name}: Added to the {type} queue, unique ID: {detail.ID}. Current Position: {position.Position}"; + msg = $"你在第{position.Position}位"; + + var botct = queueInfo.Hub.Bots.Count; + if (position.Position > botct) + { + var eta = queueInfo.Hub.Config.Queues.EstimateDelay(position.Position, botct); + //msg += $". Estimated: {eta:F1} minutes."; + msg += $", 需等待约{eta:F1}分钟"; + } + + return true; + } + + } +} diff --git a/SysBot.Pokemon/Helpers/ShowdownTranslator.cs b/SysBot.Pokemon/Helpers/ShowdownTranslator.cs index a78698790..4421e00ea 100644 --- a/SysBot.Pokemon/Helpers/ShowdownTranslator.cs +++ b/SysBot.Pokemon/Helpers/ShowdownTranslator.cs @@ -384,6 +384,19 @@ public static string Chinese2Showdown(string zh) return result; } + public static bool IsPS(string str) + { + var gameStrings = GameStringsEn; + for (int i = 1; i < gameStrings.Species.Count; i++) + { + if (str.Contains(gameStrings.Species[i])) + { + return true; + } + } + return false; + } + #region 形态中文ps字典,感谢ppllouf public static Dictionary formDict = new Dictionary { {"阿罗拉","Alola"}, From c006a7330a9f5722ed0e633b6c211fc223490a0c Mon Sep 17 00:00:00 2001 From: Easy World Date: Mon, 27 Mar 2023 23:07:06 +0800 Subject: [PATCH 102/171] ShowdownTranslator refactor --- SysBot.Pokemon/Helpers/ShowdownTranslator.cs | 417 +++--------------- .../Helpers/ShowdownTranslatorDictionary.cs | 236 ++++++++++ 2 files changed, 294 insertions(+), 359 deletions(-) create mode 100644 SysBot.Pokemon/Helpers/ShowdownTranslatorDictionary.cs diff --git a/SysBot.Pokemon/Helpers/ShowdownTranslator.cs b/SysBot.Pokemon/Helpers/ShowdownTranslator.cs index 4421e00ea..efa47b023 100644 --- a/SysBot.Pokemon/Helpers/ShowdownTranslator.cs +++ b/SysBot.Pokemon/Helpers/ShowdownTranslator.cs @@ -1,6 +1,8 @@ using PKHeX.Core; using System.Text.RegularExpressions; using System.Collections.Generic; +using System.Linq; +using System.Text; namespace SysBot.Pokemon { @@ -43,7 +45,7 @@ public static string Chinese2Showdown(string zh) } // 识别地区形态 - foreach (var s in formDict) + foreach (var s in ShowdownTranslatorDictionary.formDict) { var searchKey = s.Key.EndsWith("形态") ? s.Key : s.Key + "形态"; if (!zh.Contains(searchKey)) continue; @@ -104,25 +106,14 @@ public static string Chinese2Showdown(string zh) } // 添加异色 - if (zh.Contains("异色")) + foreach (string key in ShowdownTranslatorDictionary.shinyTypes.Keys) { - result += "\nShiny: Yes"; - zh = zh.Replace("异色", ""); - } - else if (zh.Contains("闪光")) - { - result += "\nShiny: Yes"; - zh = zh.Replace("闪光", ""); - } - else if (zh.Contains("星闪")) - { - result += "\nShiny: Star"; - zh = zh.Replace("星闪", ""); - } - else if (zh.Contains("方闪")) - { - result += "\nShiny: Square"; - zh = zh.Replace("方闪", ""); + if (zh.Contains(key)) + { + result += ShowdownTranslatorDictionary.shinyTypes[key]; + zh = zh.Replace(key, ""); + break; + } } // 添加头目 @@ -165,127 +156,46 @@ public static string Chinese2Showdown(string zh) } // 添加个体值 - if (zh.ToUpper().Contains("6V"))//默认 - { - result += "\nIVs: 31 HP / 31 Atk / 31 Def / 31 SpA / 31 SpD / 31 Spe"; - zh = Regex.Replace(zh, "6V|6v", ""); - } - else if (zh.ToUpper().Contains("5V0A")) - { - result += "\nIVs: 31 HP / 0 Atk / 31 Def / 31 SpA / 31 SpD / 31 Spe"; - zh = Regex.Replace(zh, "5V0A|5v0a", ""); - } - else if (zh.ToUpper().Contains("5V0攻")) - { - result += "\nIVs: 31 HP / 0 Atk / 31 Def / 31 SpA / 31 SpD / 31 Spe"; - zh = Regex.Replace(zh, "5V0攻|5v0攻", ""); - } - else if (zh.ToUpper().Contains("5V0S")) - { - result += "\nIVs: 31 HP / 31 Atk / 31 Def / 31 SpA / 31 SpD / 0 Spe"; - zh = Regex.Replace(zh, "5V0S|5v0s", ""); - } - else if (zh.ToUpper().Contains("5V0速")) - { - result += "\nIVs: 31 HP / 31 Atk / 31 Def / 31 SpA / 31 SpD / 0 Spe"; - zh = Regex.Replace(zh, "5V0速|5v0速", ""); - } - else if (zh.ToUpper().Contains("4V0A0S")) - { - result += "\nIVs: 31 HP / 0 Atk / 31 Def / 31 SpA / 31 SpD / 0 Spe"; - zh = Regex.Replace(zh, "4V0A0S|4v0a0s", ""); - } - else if (zh.ToUpper().Contains("4V0攻0速")) + foreach (string key in ShowdownTranslatorDictionary.ivCombos.Keys) { - result += "\nIVs: 31 HP / 0 Atk / 31 Def / 31 SpA / 31 SpD / 0 Spe"; - zh = Regex.Replace(zh, "4V0攻0速|4v0攻0速", ""); + if (zh.ToUpper().Contains(key)) + { + result += "\nIVs: " + ShowdownTranslatorDictionary.ivCombos[key]; + zh = Regex.Replace(zh, key, "", RegexOptions.IgnoreCase); + break; + } } // 添加努力值 if (zh.Contains("努力值")) { - result += "\nEVs: "; + StringBuilder sb = new(); + sb.Append("\nEVs: "); zh = zh.Replace("努力值", ""); - if (Regex.IsMatch(zh, "\\d{1,3}生命")) - { - string value = Regex.Match(zh, "(\\d{1,3})生命").Groups?[1]?.Value ?? ""; - result += $"{value} HP / "; - zh = Regex.Replace(zh, "\\d{1,3}生命", ""); - } - else if (Regex.IsMatch(zh, "\\d{1,3}Hp")) - { - string value = Regex.Match(zh, "(\\d{1,3})Hp").Groups?[1]?.Value ?? ""; - result += $"{value} HP / "; - zh = Regex.Replace(zh, "\\d{1,3}Hp", ""); - } - if (Regex.IsMatch(zh, "\\d{1,3}攻击")) - { - string value = Regex.Match(zh, "(\\d{1,3})攻击").Groups?[1]?.Value ?? ""; - result += $"{value} Atk / "; - zh = Regex.Replace(zh, "\\d{1,3}攻击", ""); - } - else if (Regex.IsMatch(zh, "\\d{1,3}Atk")) + foreach (var stat in ShowdownTranslatorDictionary.statsDict) { - string value = Regex.Match(zh, "(\\d{1,3})Atk").Groups?[1]?.Value ?? ""; - result += $"{value} Atk / "; - zh = Regex.Replace(zh, "\\d{1,3}Atk", ""); - } - - if (Regex.IsMatch(zh, "\\d{1,3}防御")) - { - string value = Regex.Match(zh, "(\\d{1,3})防御").Groups?[1]?.Value ?? ""; - result += $"{value} Def / "; - zh = Regex.Replace(zh, "\\d{1,3}防御", ""); - } - else if (Regex.IsMatch(zh, "\\d{1,3}Def")) - { - string value = Regex.Match(zh, "(\\d{1,3})Def").Groups?[1]?.Value ?? ""; - result += $"{value} Def / "; - zh = Regex.Replace(zh, "\\d{1,3}Def", ""); + string regexPattern = $@"\d{{1,3}}{stat.Key}"; + if (Regex.IsMatch(zh, regexPattern)) + { + string value = Regex.Match(zh, $@"(\d{{1,3}}){stat.Key}").Groups[1].Value; + sb.Append($"{value} {stat.Value} / "); + zh = Regex.Replace(zh, regexPattern, ""); + } + else if (Regex.IsMatch(zh, $@"\d{{1,3}}{stat.Value}")) + { + string value = Regex.Match(zh, $@"(\d{{1,3}}){stat.Value}").Groups?[1]?.Value ?? ""; + sb.Append($"{value} {stat.Value} / "); + zh = Regex.Replace(zh, $@"\d{{1,3}}{stat.Value}", ""); + } } - if (Regex.IsMatch(zh, "\\d{1,3}特攻")) - { - string value = Regex.Match(zh, "(\\d{1,3})特攻").Groups?[1]?.Value ?? ""; - result += $"{value} SpA / "; - zh = Regex.Replace(zh, "\\d{1,3}特攻", ""); - } - else if (Regex.IsMatch(zh, "\\d{1,3}SpA")) + if (sb.ToString().EndsWith("/ ")) { - string value = Regex.Match(zh, "(\\d{1,3})SpA").Groups?[1]?.Value ?? ""; - result += $"{value} SpA / "; - zh = Regex.Replace(zh, "\\d{1,3}SpA", ""); + sb.Remove(sb.Length - 2, 2); } - if (Regex.IsMatch(zh, "\\d{1,3}特防")) - { - string value = Regex.Match(zh, "(\\d{1,3})特防").Groups?[1]?.Value ?? ""; - result += $"{value} SpD / "; - zh = Regex.Replace(zh, "\\d{1,3}特防", ""); - } - else if (Regex.IsMatch(zh, "\\d{1,3}SpD")) - { - string value = Regex.Match(zh, "(\\d{1,3})SpD").Groups?[1]?.Value ?? ""; - result += $"{value} SpD / "; - zh = Regex.Replace(zh, "\\d{1,3}SpD", ""); - } - if (Regex.IsMatch(zh, "\\d{1,3}速度")) - { - string value = Regex.Match(zh, "(\\d{1,3})速度").Groups?[1]?.Value ?? ""; - result += $"{value} Spe"; - zh = Regex.Replace(zh, "\\d{1,3}速度", ""); - } - else if (Regex.IsMatch(zh, "\\d{1,3}Spe")) - { - string value = Regex.Match(zh, "(\\d{1,3})Spe").Groups?[1]?.Value ?? ""; - result += $"{value} Spe"; - zh = Regex.Replace(zh, "\\d{1,3}Spe", ""); - } - if (result.EndsWith("/ ")) - { - result = result.Substring(0, result.Length - 2); - } + result += sb.ToString(); } // 添加太晶属性 @@ -322,261 +232,50 @@ public static string Chinese2Showdown(string zh) //添加全回忆技能 if (typeof(T) == typeof(PK9) || typeof(T) == typeof(PK8)) { - if (zh.Contains("全技能")) + if (Regex.IsMatch(zh, "全[技招]式")) { result += "\n.RelearnMoves=$suggestAll"; - zh = zh.Replace("全技能", ""); - - } - else if (zh.Contains("全招式")) - { - result += "\n.RelearnMoves=$suggestAll"; - zh = zh.Replace("全招式", ""); + zh = Regex.Replace(zh, "全[技招]式", ""); } } else if (typeof(T) == typeof(PA8)) { - if (zh.Contains("全技能")) + if (Regex.IsMatch(zh, "全[技招]式")) { result += "\n.MoveMastery=$suggestAll"; - zh = zh.Replace("全技能", ""); - } - else if (zh.Contains("全招式")) - { - result += "\n.MoveMastery=$suggestAll"; - zh = zh.Replace("全招式", ""); + zh = Regex.Replace(zh, "全[技招]式", ""); } } - if (zh.Contains("异国")) { result += "\nLanguage: Italian"; zh = zh.Replace("异国", ""); } - else if (zh.Contains("日语")) { result += "\nLanguage: Japanese"; zh = zh.Replace("日语", ""); } - else if (zh.Contains("英语")) { result += "\nLanguage: English"; zh = zh.Replace("英语", ""); } - else if (zh.Contains("法语")) { result += "\nLanguage: French"; zh = zh.Replace("法语", ""); } - else if (zh.Contains("意大利语")) { result += "\nLanguage: Italian"; zh = zh.Replace("意大利语", ""); } - else if (zh.Contains("德语")) { result += "\nLanguage: German"; zh = zh.Replace("德语", ""); } - else if (zh.Contains("西班牙语")) { result += "\nLanguage: Spanish"; zh = zh.Replace("西班牙语", ""); } - else if (zh.Contains("韩语")) { result += "\nLanguage: Korean"; zh = zh.Replace("韩语", ""); } - else if (zh.Contains("简体中文")) { result += "\nLanguage: ChineseS"; zh = zh.Replace("简体中文", ""); } - else if (zh.Contains("繁体中文")) { result += "\nLanguage: ChineseT"; zh = zh.Replace("繁体中文", ""); } + // 语言 + var lang = ShowdownTranslatorDictionary.languages.Keys.FirstOrDefault(zh.Contains); + if (!string.IsNullOrEmpty(lang)) + { + result += $"\nLanguage: {ShowdownTranslatorDictionary.languages[lang]}"; + zh = zh.Replace(lang, ""); + } // 添加技能 原因:PKHeX.Core.ShowdownSet#ParseLines中,若招式数满足4个则不再解析,所以招式文本应放在最后 for (int moveCount = 0; moveCount < 4; moveCount++) { - int candidateIndex = -1; - int candidateLength = 0; - for (int i = 0; i < GameStringsZh.Move.Count; i++) - { - if (GameStringsZh.Move[i].Length == 0) continue; - if (!zh.Contains("-" + GameStringsZh.Move[i])) continue; - // 吸取 吸取拳 - if (candidateIndex == -1 || GameStringsZh.Move[i].Length > candidateLength) - { - candidateIndex = i; - candidateLength = GameStringsZh.Move[i].Length; - } - } - if (candidateIndex == -1) continue; + var candidate = GameStringsZh.Move + .Where(move => move.Length > 0 && zh.Contains("-" + move)) + .OrderByDescending(move => move.Length) + .FirstOrDefault(); + + if (candidate == null) continue; + int candidateIndex = GameStringsZh.Move + .Select((move, index) => new { Move = move, Index = index }) + .FirstOrDefault(item => item.Move == candidate)?.Index ?? -1; result += $"\n-{GameStringsEn.Move[candidateIndex]}"; - zh = zh.Replace("-" + GameStringsZh.Move[candidateIndex], ""); + zh = zh.Replace("-" + candidate, ""); } return result; } - public static bool IsPS(string str) - { - var gameStrings = GameStringsEn; - for (int i = 1; i < gameStrings.Species.Count; i++) - { - if (str.Contains(gameStrings.Species[i])) - { - return true; - } - } - return false; - } + public static bool IsPS(string str) => GameStringsEn.Species.Skip(1).Any(str.Contains); - #region 形态中文ps字典,感谢ppllouf - public static Dictionary formDict = new Dictionary { - {"阿罗拉","Alola"}, - {"初始","Original"}, - {"丰缘","Hoenn"}, - {"神奥","Sinnoh"}, - {"合众","Unova"}, - {"卡洛斯","Kalos"}, - {"就决定是你了","Partner"}, - {"搭档","Starter"}, - {"世界","World"}, - {"摇滚巨星","Rock-Star"}, - {"贵妇","Belle"}, - {"流行偶像","Pop-Star"}, - {"博士","PhD"}, - {"面罩摔角手","Libre"}, - {"换装","Cosplay"}, - {"伽勒尔","Galar"}, - {"洗翠","Hisui"}, - {"帕底亚的样子斗战种","Paldea-Combat"}, - {"帕底亚的样子火炽种","Paldea-Blaze"}, - {"帕底亚的样子水澜种","Paldea-Aqua"}, - {"刺刺耳","Spiky-eared"}, - {"帕底亚","Paldea"}, - {"B","B"}, - {"C","C"}, - {"D","D"}, - {"E","E"}, - {"F","F"}, - {"G","G"}, - {"H","H"}, - {"I","I"}, - {"J","J"}, - {"K","K"}, - {"L","L"}, - {"M","M"}, - {"N","N"}, - {"O","O"}, - {"P","P"}, - {"Q","Q"}, - {"R","R"}, - {"S","S"}, - {"T","T"}, - {"U","U"}, - {"V","V"}, - {"W","W"}, - {"X","X"}, - {"Y","Y"}, - {"Z","Z"}, - {"!","Exclamation"}, - {"?","Question"}, - {"太阳的样子","Sunny"}, - {"雨水的样子","Rainy"}, - {"雪云的样子","Snowy"}, - {"原始回归的样子","Primal"}, - {"攻击形态","Attack"}, - {"防御形态","Defense"}, - {"速度形态","Speed"}, - {"砂土蓑衣","Sandy"}, - {"垃圾蓑衣","Trash"}, - {"晴天形态","Sunshine"}, - {"东海","East"}, - {"加热","Heat"}, - {"清洗","Wash"}, - {"结冰","Frost"}, - {"旋转","Fan"}, - {"切割","Mow"}, - {"起源","Origin"}, - {"天空","Sky"}, - {"格斗","Fighting"}, - {"飞行","Flying"}, - {"毒","Poison"}, - {"地面","Ground"}, - {"岩石","Rock"}, - {"虫","Bug"}, - {"幽灵","Ghost"}, - {"钢","Steel"}, - {"火","Fire"}, - {"水","Water"}, - {"草","Grass"}, - {"电","Electric"}, - {"超能力","Psychic"}, - {"冰","Ice"}, - {"龙","Dragon"}, - {"恶","Dark"}, - {"妖精","Fairy"}, - {"蓝条纹的样子","Blue"}, - {"白条纹的样子","White"}, - {"夏天的样子","Summer"}, - {"秋天的样子","Autumn"}, - {"冬天的样子","Winter"}, - {"灵兽形态","Therian"}, - {"暗黑","White"}, - {"焰白","Black"}, - {"觉悟的样子","Resolute"}, - {"舞步形态","Pirouette"}, - {"水流卡带","Douse"}, - {"闪电卡带","Shock"}, - {"火焰卡带","Burn"}, - {"冰冻卡带","Chill"}, - {"小智版","Ash"}, - {"冰雪花纹","Icy Snow"}, - {"雪国花纹","Polar"}, - {"雪原花纹","Tundra"}, - {"大陆花纹","Continental"}, - {"庭院花纹","Garden"}, - {"高雅花纹","Elegant"}, - {"摩登花纹","Modern"}, - {"大海花纹","Marine"}, - {"群鸟花纹","Archipelago"}, - {"荒野花纹","High Plains"}, - {"沙尘花纹","Sandstorm"}, - {"大河花纹","River"}, - {"骤雨花纹","Monsoon"}, - {"热带草原花纹","Savanna"}, - {"太阳花纹","Sun"}, - {"大洋花纹","Ocean"}, - {"热带雨林花纹","Jungle"}, - {"幻彩花纹","Fancy"}, - {"球球花纹","Pokeball"}, - {"蓝花","Blue"}, - {"橙花","Orange"}, - {"白花","White"}, - {"黄花","Yellow"}, - {"永恒","Eternal"}, - {"心形造型","Heart"}, - {"星形造型","Star"}, - {"菱形造型","Diamond"}, - {"淑女造型","Debutante"}, - {"贵妇造型","Matron"}, - {"绅士造型","Dandy"}, - {"女王造型","La Reine"}, - {"歌舞伎造型","Kabuki"}, - {"国王造型","Pharaoh"}, - {"小尺寸","Small"}, - {"大尺寸","Large"}, - {"特大尺寸","Super"}, - {"解放","Unbound"}, - {"啪滋啪滋风格","Pom-Pom"}, - {"呼拉呼拉风格","Pa’u"}, - {"轻盈轻盈风格","Sensu"}, - {"黑夜的样子","Midnight"}, - {"黄昏的样子","Dusk"}, - {"流星的样子","Meteor"}, - {"橙色核心","Orange"}, - {"黄色核心","Yellow"}, - {"绿色核心","Green"}, - {"浅蓝色核心","Blue"}, - {"蓝色核心","Indigo"}, - {"紫色核心","Violet"}, - {"黄昏之鬃","Dusk-Mane"}, - {"拂晓之翼","Dawn-Wings"}, - {"究极","Ultra"}, - {"500年前的颜色","Original"}, - {"低调的样子","Low-Key"}, - {"真品","Antique"}, - {"奶香红钻","Ruby-Cream"}, - {"奶香抹茶","Matcha-Cream"}, - {"奶香薄荷","Mint-Cream"}, - {"奶香柠檬","Lemon-Cream"}, - {"奶香雪盐","Salted-Cream"}, - {"红钻综合","Ruby-Swirl"}, - {"焦糖综合","Caramel-Swirl"}, - {"三色综合","Rainbow-Swirl"}, - {"剑之王","Crowned"}, - {"盾之王","Crowned"}, - {"无极巨化","Eternamax"}, - {"连击流","Rapid-Strike"}, - {"阿爸","Dada"}, - {"骑白马的样子","Ice"}, - {"骑黑马的样子","Shadow"}, - {"四只家庭","Four"}, - {"蓝羽毛","Blue"}, - {"黄羽毛","Yellow"}, - {"白羽毛","White"}, - {"下垂姿势","Droopy"}, - {"平挺姿势","Stretchy"}, - {"三节形态","Three-Segment"}, - }; - #endregion - } } diff --git a/SysBot.Pokemon/Helpers/ShowdownTranslatorDictionary.cs b/SysBot.Pokemon/Helpers/ShowdownTranslatorDictionary.cs new file mode 100644 index 000000000..7ce36762a --- /dev/null +++ b/SysBot.Pokemon/Helpers/ShowdownTranslatorDictionary.cs @@ -0,0 +1,236 @@ +using System.Collections.Generic; + +namespace SysBot.Pokemon +{ + public class ShowdownTranslatorDictionary + { + + // 添加异色 + public static Dictionary shinyTypes = new Dictionary + { + {"异色", "\nShiny: Yes"}, + {"闪光", "\nShiny: Yes"}, + {"星闪", "\nShiny: Star"}, + {"方闪", "\nShiny: Square"} + }; + + // 添加个体值 + public static Dictionary ivCombos = new() + { + {"6V", "31 HP / 31 Atk / 31 Def / 31 SpA / 31 SpD / 31 Spe"}, + {"5V0A", "31 HP / 0 Atk / 31 Def / 31 SpA / 31 SpD / 31 Spe"}, + {"5V0攻", "31 HP / 0 Atk / 31 Def / 31 SpA / 31 SpD / 31 Spe"}, + {"5V0S", "31 HP / 31 Atk / 31 Def / 31 SpA / 31 SpD / 0 Spe"}, + {"5V0速", "31 HP / 31 Atk / 31 Def / 31 SpA / 31 SpD / 0 Spe"}, + {"4V0A0S", "31 HP / 0 Atk / 31 Def / 31 SpA / 31 SpD / 0 Spe"}, + {"4V0攻0速", "31 HP / 0 Atk / 31 Def / 31 SpA / 31 SpD / 0 Spe"} + }; + + public static Dictionary statsDict = new() + { + { "生命", "HP" }, + { "攻击", "Atk" }, + { "防御", "Def" }, + { "特攻", "SpA" }, + { "特防", "SpD" }, + { "速度", "Spe" } + }; + + + public static Dictionary languages = new Dictionary + { + { "异国", "Italian" }, + { "日语", "Japanese" }, + { "英语", "English" }, + { "法语", "French" }, + { "意大利语", "Italian" }, + { "德语", "German" }, + { "西班牙语", "Spanish" }, + { "韩语", "Korean" }, + { "简体中文", "ChineseS" }, + { "繁体中文", "ChineseT" } + }; + + #region 形态中文ps字典,感谢ppllouf + public static Dictionary formDict = new Dictionary { + {"阿罗拉","Alola"}, + {"初始","Original"}, + {"丰缘","Hoenn"}, + {"神奥","Sinnoh"}, + {"合众","Unova"}, + {"卡洛斯","Kalos"}, + {"就决定是你了","Partner"}, + {"搭档","Starter"}, + {"世界","World"}, + {"摇滚巨星","Rock-Star"}, + {"贵妇","Belle"}, + {"流行偶像","Pop-Star"}, + {"博士","PhD"}, + {"面罩摔角手","Libre"}, + {"换装","Cosplay"}, + {"伽勒尔","Galar"}, + {"洗翠","Hisui"}, + {"帕底亚的样子斗战种","Paldea-Combat"}, + {"帕底亚的样子火炽种","Paldea-Blaze"}, + {"帕底亚的样子水澜种","Paldea-Aqua"}, + {"刺刺耳","Spiky-eared"}, + {"帕底亚","Paldea"}, + {"B","B"}, + {"C","C"}, + {"D","D"}, + {"E","E"}, + {"F","F"}, + {"G","G"}, + {"H","H"}, + {"I","I"}, + {"J","J"}, + {"K","K"}, + {"L","L"}, + {"M","M"}, + {"N","N"}, + {"O","O"}, + {"P","P"}, + {"Q","Q"}, + {"R","R"}, + {"S","S"}, + {"T","T"}, + {"U","U"}, + {"V","V"}, + {"W","W"}, + {"X","X"}, + {"Y","Y"}, + {"Z","Z"}, + {"!","Exclamation"}, + {"?","Question"}, + {"太阳的样子","Sunny"}, + {"雨水的样子","Rainy"}, + {"雪云的样子","Snowy"}, + {"原始回归的样子","Primal"}, + {"攻击形态","Attack"}, + {"防御形态","Defense"}, + {"速度形态","Speed"}, + {"砂土蓑衣","Sandy"}, + {"垃圾蓑衣","Trash"}, + {"晴天形态","Sunshine"}, + {"东海","East"}, + {"加热","Heat"}, + {"清洗","Wash"}, + {"结冰","Frost"}, + {"旋转","Fan"}, + {"切割","Mow"}, + {"起源","Origin"}, + {"天空","Sky"}, + {"格斗","Fighting"}, + {"飞行","Flying"}, + {"毒","Poison"}, + {"地面","Ground"}, + {"岩石","Rock"}, + {"虫","Bug"}, + {"幽灵","Ghost"}, + {"钢","Steel"}, + {"火","Fire"}, + {"水","Water"}, + {"草","Grass"}, + {"电","Electric"}, + {"超能力","Psychic"}, + {"冰","Ice"}, + {"龙","Dragon"}, + {"恶","Dark"}, + {"妖精","Fairy"}, + {"蓝条纹的样子","Blue"}, + {"白条纹的样子","White"}, + {"夏天的样子","Summer"}, + {"秋天的样子","Autumn"}, + {"冬天的样子","Winter"}, + {"灵兽形态","Therian"}, + {"暗黑","White"}, + {"焰白","Black"}, + {"觉悟的样子","Resolute"}, + {"舞步形态","Pirouette"}, + {"水流卡带","Douse"}, + {"闪电卡带","Shock"}, + {"火焰卡带","Burn"}, + {"冰冻卡带","Chill"}, + {"小智版","Ash"}, + {"冰雪花纹","Icy Snow"}, + {"雪国花纹","Polar"}, + {"雪原花纹","Tundra"}, + {"大陆花纹","Continental"}, + {"庭院花纹","Garden"}, + {"高雅花纹","Elegant"}, + {"摩登花纹","Modern"}, + {"大海花纹","Marine"}, + {"群鸟花纹","Archipelago"}, + {"荒野花纹","High Plains"}, + {"沙尘花纹","Sandstorm"}, + {"大河花纹","River"}, + {"骤雨花纹","Monsoon"}, + {"热带草原花纹","Savanna"}, + {"太阳花纹","Sun"}, + {"大洋花纹","Ocean"}, + {"热带雨林花纹","Jungle"}, + {"幻彩花纹","Fancy"}, + {"球球花纹","Pokeball"}, + {"蓝花","Blue"}, + {"橙花","Orange"}, + {"白花","White"}, + {"黄花","Yellow"}, + {"永恒","Eternal"}, + {"心形造型","Heart"}, + {"星形造型","Star"}, + {"菱形造型","Diamond"}, + {"淑女造型","Debutante"}, + {"贵妇造型","Matron"}, + {"绅士造型","Dandy"}, + {"女王造型","La Reine"}, + {"歌舞伎造型","Kabuki"}, + {"国王造型","Pharaoh"}, + {"小尺寸","Small"}, + {"大尺寸","Large"}, + {"特大尺寸","Super"}, + {"解放","Unbound"}, + {"啪滋啪滋风格","Pom-Pom"}, + {"呼拉呼拉风格","Pa’u"}, + {"轻盈轻盈风格","Sensu"}, + {"黑夜的样子","Midnight"}, + {"黄昏的样子","Dusk"}, + {"流星的样子","Meteor"}, + {"橙色核心","Orange"}, + {"黄色核心","Yellow"}, + {"绿色核心","Green"}, + {"浅蓝色核心","Blue"}, + {"蓝色核心","Indigo"}, + {"紫色核心","Violet"}, + {"黄昏之鬃","Dusk-Mane"}, + {"拂晓之翼","Dawn-Wings"}, + {"究极","Ultra"}, + {"500年前的颜色","Original"}, + {"低调的样子","Low-Key"}, + {"真品","Antique"}, + {"奶香红钻","Ruby-Cream"}, + {"奶香抹茶","Matcha-Cream"}, + {"奶香薄荷","Mint-Cream"}, + {"奶香柠檬","Lemon-Cream"}, + {"奶香雪盐","Salted-Cream"}, + {"红钻综合","Ruby-Swirl"}, + {"焦糖综合","Caramel-Swirl"}, + {"三色综合","Rainbow-Swirl"}, + {"剑之王","Crowned"}, + {"盾之王","Crowned"}, + {"无极巨化","Eternamax"}, + {"连击流","Rapid-Strike"}, + {"阿爸","Dada"}, + {"骑白马的样子","Ice"}, + {"骑黑马的样子","Shadow"}, + {"四只家庭","Four"}, + {"蓝羽毛","Blue"}, + {"黄羽毛","Yellow"}, + {"白羽毛","White"}, + {"下垂姿势","Droopy"}, + {"平挺姿势","Stretchy"}, + {"三节形态","Three-Segment"}, + }; + #endregion + + } +} From 938450de117a82444f3ad00d26eec2aac3a5e08c Mon Sep 17 00:00:00 2001 From: Easy World Date: Wed, 29 Mar 2023 12:19:09 +0800 Subject: [PATCH 103/171] Vivillon fix --- SysBot.Pokemon.Dodo/DodoHelper.cs | 313 ------------------ SysBot.Pokemon.QQ/Modules/FileModule.cs | 13 + .../Helpers/ShowdownTranslatorDictionary.cs | 3 +- 3 files changed, 15 insertions(+), 314 deletions(-) delete mode 100644 SysBot.Pokemon.Dodo/DodoHelper.cs diff --git a/SysBot.Pokemon.Dodo/DodoHelper.cs b/SysBot.Pokemon.Dodo/DodoHelper.cs deleted file mode 100644 index 740a36230..000000000 --- a/SysBot.Pokemon.Dodo/DodoHelper.cs +++ /dev/null @@ -1,313 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using PKHeX.Core; -using SysBot.Base; - -namespace SysBot.Pokemon.Dodo -{ - public class DodoHelper where T : PKM, new() - { - public static void StartTrade(string ps, string dodoId, string nickName, string channelId, string islandSourceId) - { - var _ = CheckAndGetPkm(ps, dodoId, out var msg, out var pkm); - if (!_) - { - DodoBot.SendChannelMessage(msg, channelId); - return; - } - var foreign = ps.Contains("Language: "); - - StartTradeWithoutCheck(pkm, dodoId, nickName, channelId, islandSourceId, foreign); - } - - public static void StartTrade(T pkm, string dodoId, string nickName, string channelId, string islandSourceId) - { - var _ = CheckPkm(pkm, dodoId, out var msg); - if (!_) - { - DodoBot.SendChannelMessage(msg, channelId); - return; - } - - StartTradeWithoutCheck(pkm, dodoId, nickName, channelId, islandSourceId); - } - - public static void StartTradeMultiChinese(string chinesePsRaw, string dodoId, string nickName, string channelId, string islandSourceId) - { - var chinesePss = chinesePsRaw.Split('+').ToList(); - var MaxPkmsPerTrade = DodoBot.Info.Hub.Config.Trade.MaxPkmsPerTrade; - if (MaxPkmsPerTrade <= 1) - { - DodoBot.SendChannelMessage("请联系群主将trade/MaxPkmsPerTrade配置改为大于1", channelId); - return; - } - else if (chinesePss.Count > MaxPkmsPerTrade) - { - DodoBot.SendChannelMessage($"批量交换宝可梦数量应小于等于{MaxPkmsPerTrade}", channelId); - return; - } - List msgs = new(); - List pkms = new(); - List skipAutoOTList = new(); - int invalidCount = 0; - for (var i = 0; i < chinesePss.Count; i++) - { - var ps = ShowdownTranslator.Chinese2Showdown(chinesePss[i]); - var _ = CheckAndGetPkm(ps, dodoId, out var msg, out var pkm); - if (!_) - { - LogUtil.LogInfo($"批量第{i+1}只宝可梦有问题:{msg}", nameof(DodoHelper)); - invalidCount++; - } - else - { - LogUtil.LogInfo($"批量第{i+1}只:\n{ps}", nameof(DodoHelper)); - skipAutoOTList.Add(ps.Contains("Language: ")); - pkms.Add(pkm); - } - } - if (invalidCount == chinesePss.Count) - { - DodoBot.SendChannelMessage("一个都不合法,换个屁", channelId); - return; - } - else if (invalidCount != 0) - { - DodoBot.SendChannelMessage($"期望交换的{chinesePss.Count}只宝可梦中,有{invalidCount}只不合法,仅交换合法的{pkms.Count}只", channelId); - } - - var code = DodoBot.Info.GetRandomTradeCode(); - var __ = AddToTradeQueue(pkms, code, ulong.Parse(dodoId), nickName, channelId, islandSourceId, skipAutoOTList, - PokeRoutineType.LinkTrade, out string message); - DodoBot.SendChannelMessage(message, channelId); - } - - public static void StartTradeMultiPs(string pssRaw, string dodoId, string nickName, string channelId, string islandSourceId) - { - var psArray = pssRaw.Split("\n\n").ToList(); - var MaxPkmsPerTrade = DodoBot.Info.Hub.Config.Trade.MaxPkmsPerTrade; - if (MaxPkmsPerTrade <= 1) - { - DodoBot.SendChannelMessage("请联系群主将trade/MaxPkmsPerTrade配置改为大于1", channelId); - return; - } - else if (psArray.Count > MaxPkmsPerTrade) - { - DodoBot.SendChannelMessage($"批量交换宝可梦数量应小于等于{MaxPkmsPerTrade}", channelId); - return; - } - List msgs = new(); - List pkms = new(); - List skipAutoOTList = new(); - int invalidCount = 0; - for (var i = 0; i < psArray.Count; i++) - { - var ps = psArray[i]; - var _ = CheckAndGetPkm(ps, dodoId, out var msg, out var pkm); - if (!_) - { - LogUtil.LogInfo($"批量第{i + 1}只宝可梦有问题:{msg}", nameof(DodoHelper)); - invalidCount++; - } - else - { - LogUtil.LogInfo($"批量第{i + 1}只:\n{ps}", nameof(DodoHelper)); - skipAutoOTList.Add(ps.Contains("Language: ")); - pkms.Add(pkm); - } - } - if (invalidCount == psArray.Count) - { - DodoBot.SendChannelMessage("一个都不合法,换个屁", channelId); - return; - } - else if (invalidCount != 0) - { - DodoBot.SendChannelMessage($"期望交换的{psArray.Count}只宝可梦中,有{invalidCount}只不合法,仅交换合法的{pkms.Count}只", channelId); - } - - var code = DodoBot.Info.GetRandomTradeCode(); - var __ = AddToTradeQueue(pkms, code, ulong.Parse(dodoId), nickName, channelId, islandSourceId, skipAutoOTList, - PokeRoutineType.LinkTrade, out string message); - DodoBot.SendChannelMessage(message, channelId); - } - - public static void StartTradeWithoutCheck(T pkm, string dodoId, string nickName, string channelId, string islandSourceId, bool foreign = false) - { - var code = DodoBot.Info.GetRandomTradeCode(); - var __ = AddToTradeQueue(pkm, code, ulong.Parse(dodoId), nickName, channelId, islandSourceId, foreign, - PokeRoutineType.LinkTrade, out string message); - DodoBot.SendChannelMessage(message, channelId); - } - - public static void StartDump(string dodoId, string nickName, string channelId) - { - var code = DodoBot.Info.GetRandomTradeCode(); - var __ = AddToTradeQueue(new T(), code, ulong.Parse(dodoId), nickName, channelId, "", false, - PokeRoutineType.Dump, out string message); - DodoBot.SendChannelMessage(message, channelId); - } - - public static bool CheckPkm(T pkm, string username, out string msg) - { - if (!DodoBot.Info.GetCanQueue()) - { - msg = "对不起, 我不再接受队列请求!"; - return false; - } - try - { - if (!pkm.CanBeTraded()) - { - msg = $"取消派送, <@!{username}>: 官方禁止该宝可梦交易!"; - return false; - } - - if (pkm is T pk) - { - var valid = new LegalityAnalysis(pkm).Valid; - if (valid) - { - msg = - $"<@!{username}> - 已加入等待队列. 如果你选宝可梦的速度太慢,你的派送请求将被取消!"; - return true; - } - } - - var reason = "我没办法创造非法宝可梦"; - msg = $"<@!{username}>: {reason}"; - } -#pragma warning disable CA1031 // Do not catch general exception types - catch (Exception ex) -#pragma warning restore CA1031 // Do not catch general exception types - { - LogUtil.LogSafe(ex, nameof(DodoHelper)); - msg = $"取消派送, <@!{username}>: 发生了一个错误"; - } - - return false; - } - - public static bool CheckAndGetPkm(string setstring, string username, out string msg, out T outPkm) - { - outPkm = new T(); - if (!DodoBot.Info.GetCanQueue()) - { - msg = "对不起, 我不再接受队列请求!"; - return false; - } - - var set = ShowdownUtil.ConvertToShowdown(setstring); - if (set == null) - { - msg = $"取消派送, <@!{username}>: 宝可梦昵称为空."; - return false; - } - - var template = AutoLegalityWrapper.GetTemplate(set); - if (template.Species < 1) - { - msg = - $"取消派送, <@!{username}>: 请使用正确的Showdown Set代码"; - return false; - } - - if (set.InvalidLines.Count != 0) - { - msg = - $"取消派送, <@!{username}>: 非法的Showdown Set代码:\n{string.Join("\n", set.InvalidLines)}"; - return false; - } - - try - { - var sav = AutoLegalityWrapper.GetTrainerInfo(); - var pkm = sav.GetLegal(template, out var result); - - if (!pkm.CanBeTraded()) - { - msg = $"取消派送, <@!{username}>: 官方禁止该宝可梦交易!"; - return false; - } - - if (pkm is T pk) - { - var valid = new LegalityAnalysis(pkm).Valid; - if (valid) - { - outPkm = pk; - - msg = - $"<@!{username}> - 已加入等待队列. 如果你选宝可梦的速度太慢,你的派送请求将被取消!"; - return true; - } - } - - var reason = result == "Timeout" - ? "宝可梦创造超时" - : "我没办法创造非法宝可梦"; - msg = $"<@!{username}>: {reason}"; - } -#pragma warning disable CA1031 // Do not catch general exception types - catch (Exception ex) -#pragma warning restore CA1031 // Do not catch general exception types - { - LogUtil.LogSafe(ex, nameof(DodoBot)); - msg = $"取消派送, <@!{username}>: 发生了一个错误"; - } - - return false; - } - - private static bool AddToTradeQueue(T pk, int code, ulong userId, string name, string channelId, string islandSourceId, bool foreign, - PokeRoutineType type, out string msg) - { - return AddToTradeQueue(new List { pk }, code, userId, name, channelId, islandSourceId, new List{ foreign }, type, out msg); - } - - private static bool AddToTradeQueue(List pks, int code, ulong userId, string name, string channelId, string islandSourceId, List skipAutoOTList, - PokeRoutineType type, out string msg) - { - if (pks == null || pks.Count == 0) - { - msg = $"宝可梦数据为空"; - return false; - } - T pk = pks.First(); - var trainer = new PokeTradeTrainerInfo(name, userId); - var notifier = new DodoTradeNotifier(pk, trainer, code, name, channelId, islandSourceId); - var tt = type == PokeRoutineType.SeedCheck ? PokeTradeType.Seed : (type == PokeRoutineType.Dump ? PokeTradeType.Dump : PokeTradeType.Specific); - var detail = - new PokeTradeDetail(pk, trainer, notifier, tt, code, true); - detail.Context.Add("skipAutoOTList", skipAutoOTList); - if (pks.Count > 0) - { - detail.Context.Add("batch", pks); - } - var trade = new TradeEntry(detail, userId, type, name); - - var added = DodoBot.Info.AddToTradeQueue(trade, userId, false); - - if (added == QueueResultAdd.AlreadyInQueue) - { - msg = $"<@!{userId}> 你已经在队列中,请不要重复发送"; - return false; - } - - var position = DodoBot.Info.CheckPosition(userId, type); - //msg = $"@{name}: Added to the {type} queue, unique ID: {detail.ID}. Current Position: {position.Position}"; - msg = $" 你在第{position.Position}位"; - - var botct = DodoBot.Info.Hub.Bots.Count; - if (position.Position > botct) - { - var eta = DodoBot.Info.Hub.Config.Queues.EstimateDelay(position.Position, botct); - //msg += $". Estimated: {eta:F1} minutes."; - msg += $", 需等待约{eta:F1}分钟"; - } - - return true; - } - } -} \ No newline at end of file diff --git a/SysBot.Pokemon.QQ/Modules/FileModule.cs b/SysBot.Pokemon.QQ/Modules/FileModule.cs index 538014202..68689d59c 100644 --- a/SysBot.Pokemon.QQ/Modules/FileModule.cs +++ b/SysBot.Pokemon.QQ/Modules/FileModule.cs @@ -110,5 +110,18 @@ private static List bin2List(byte[] bb) } return pkms; } + + private static List bin2List(byte[] bb, int size) + { + int times = bb.Length % size == 0 ? (bb.Length / size) : (bb.Length / size + 1); + List pkmBytes = new(); + for (var i = 0; i < times; i++) + { + int start = i * size; + int end = (start + size) > bb.Length ? bb.Length : (start + size); + pkmBytes.Add(bb[start..end]); + } + return pkmBytes; + } } } diff --git a/SysBot.Pokemon/Helpers/ShowdownTranslatorDictionary.cs b/SysBot.Pokemon/Helpers/ShowdownTranslatorDictionary.cs index 7ce36762a..791f0b50b 100644 --- a/SysBot.Pokemon/Helpers/ShowdownTranslatorDictionary.cs +++ b/SysBot.Pokemon/Helpers/ShowdownTranslatorDictionary.cs @@ -158,9 +158,10 @@ public class ShowdownTranslatorDictionary {"大陆花纹","Continental"}, {"庭院花纹","Garden"}, {"高雅花纹","Elegant"}, + {"花园花纹","Meadow"}, {"摩登花纹","Modern"}, {"大海花纹","Marine"}, - {"群鸟花纹","Archipelago"}, + {"群岛花纹","Archipelago"}, {"荒野花纹","High Plains"}, {"沙尘花纹","Sandstorm"}, {"大河花纹","River"}, From 895458e357ebd47506252b24b9f40254e83e2845 Mon Sep 17 00:00:00 2001 From: Easy World Date: Wed, 29 Mar 2023 21:17:06 +0800 Subject: [PATCH 104/171] ShowdownTranslator tweak --- SysBot.Pokemon/Helpers/ShowdownTranslator.cs | 74 ++++++------------- .../Helpers/ShowdownTranslatorDictionary.cs | 3 + 2 files changed, 27 insertions(+), 50 deletions(-) diff --git a/SysBot.Pokemon/Helpers/ShowdownTranslator.cs b/SysBot.Pokemon/Helpers/ShowdownTranslator.cs index efa47b023..c1bc1948e 100644 --- a/SysBot.Pokemon/Helpers/ShowdownTranslator.cs +++ b/SysBot.Pokemon/Helpers/ShowdownTranslator.cs @@ -15,34 +15,24 @@ public static string Chinese2Showdown(string zh) string result = ""; // 添加宝可梦 - int candidateSpecieNo = 0; - int candidateSpecieStringLength = 0; - for (int i = 1; i < GameStringsZh.Species.Count; i++) - { - if (zh.Contains(GameStringsZh.Species[i]) && GameStringsZh.Species[i].Length > candidateSpecieStringLength) - { - candidateSpecieNo = i; - candidateSpecieStringLength = GameStringsZh.Species[i].Length; - } - } + int specieNo = GameStringsZh.Species.Skip(1).Select((s, index) => new { Species = s, Index = index + 1 }) + .Where(s => zh.Contains(s.Species)).OrderByDescending(s => s.Species.Length).FirstOrDefault()?.Index ?? -1; - if (candidateSpecieNo > 0) + if (specieNo <= 0) return result; + result = specieNo switch { - if (candidateSpecieNo == (int)Species.NidoranF) result = "Nidoran-F"; - else if (candidateSpecieNo == (int)Species.NidoranM) result = "Nidoran-M"; - else result += GameStringsEn.Species[candidateSpecieNo]; + (int)Species.NidoranF => "Nidoran-F", + (int)Species.NidoranM => "Nidoran-M", + _ => GameStringsEn.Species[specieNo], + }; - zh = zh.Replace(GameStringsZh.Species[candidateSpecieNo], ""); + zh = zh.Replace(GameStringsZh.Species[specieNo], ""); + + // 特殊性别差异 + // 29-尼多兰F,32-尼多朗M,678-超能妙喵F,876-爱管侍F,902-幽尾玄鱼F, 916-飘香豚 + if (((Species)specieNo is Species.Meowstic or Species.Indeedee or Species.Basculegion or Species.Oinkologne) + && zh.Contains("母")) result += "-F"; - // 特殊性别差异 - // 29-尼多兰F,32-尼多朗M,678-超能妙喵F,876-爱管侍F,902-幽尾玄鱼F, 916-飘香豚 - if (((Species)candidateSpecieNo is Species.Meowstic or Species.Indeedee or Species.Basculegion or Species.Oinkologne) - && zh.Contains("母")) result += "-F"; - } - else - { - return result; - } // 识别地区形态 foreach (var s in ShowdownTranslatorDictionary.formDict) @@ -67,25 +57,15 @@ public static string Chinese2Showdown(string zh) } // 添加持有物 - if (zh.Contains("持有")) + foreach (var holdItemKeyword in ShowdownTranslatorDictionary.holdItemKeywords) { + if (!zh.Contains(holdItemKeyword)) continue; for (int i = 1; i < GameStringsZh.Item.Count; i++) { if (GameStringsZh.Item[i].Length == 0) continue; - if (!zh.Contains("持有" + GameStringsZh.Item[i])) continue; + if (!zh.Contains(holdItemKeyword + GameStringsZh.Item[i])) continue; result += $" @ {GameStringsEn.Item[i]}"; - zh = zh.Replace("持有" + GameStringsZh.Item[i], ""); - break; - } - } - else if (zh.Contains("携带")) - { - for (int i = 1; i < GameStringsZh.Item.Count; i++) - { - if (GameStringsZh.Item[i].Length == 0) continue; - if (!zh.Contains("携带" + GameStringsZh.Item[i])) continue; - result += $" @ {GameStringsEn.Item[i]}"; - zh = zh.Replace("携带" + GameStringsZh.Item[i], ""); + zh = zh.Replace(holdItemKeyword + GameStringsZh.Item[i], ""); break; } } @@ -222,7 +202,7 @@ public static string Chinese2Showdown(string zh) { result += $"\n.Scale=255\n.RibbonMarkJumbo=True"; zh = zh.Replace("大个子", ""); - } + } else if (typeof(T) == typeof(PK9) && zh.Contains("小不点")) { result += $"\n.Scale=0\n.RibbonMarkMini=True"; @@ -258,18 +238,12 @@ public static string Chinese2Showdown(string zh) // 添加技能 原因:PKHeX.Core.ShowdownSet#ParseLines中,若招式数满足4个则不再解析,所以招式文本应放在最后 for (int moveCount = 0; moveCount < 4; moveCount++) { - var candidate = GameStringsZh.Move - .Where(move => move.Length > 0 && zh.Contains("-" + move)) - .OrderByDescending(move => move.Length) - .FirstOrDefault(); - - if (candidate == null) continue; - - int candidateIndex = GameStringsZh.Move - .Select((move, index) => new { Move = move, Index = index }) - .FirstOrDefault(item => item.Move == candidate)?.Index ?? -1; + var candidateIndex = GameStringsZh.Move.Select((move, index) => new { Move = move, Index = index }) + .Where(move => move.Move.Length > 0 && zh.Contains("-" + move.Move)) + .OrderByDescending(move => move.Move.Length).FirstOrDefault()?.Index ?? -1; + if (candidateIndex < 0) continue; result += $"\n-{GameStringsEn.Move[candidateIndex]}"; - zh = zh.Replace("-" + candidate, ""); + zh = zh.Replace("-" + GameStringsZh.Move[candidateIndex], ""); } return result; diff --git a/SysBot.Pokemon/Helpers/ShowdownTranslatorDictionary.cs b/SysBot.Pokemon/Helpers/ShowdownTranslatorDictionary.cs index 791f0b50b..b5f20a50c 100644 --- a/SysBot.Pokemon/Helpers/ShowdownTranslatorDictionary.cs +++ b/SysBot.Pokemon/Helpers/ShowdownTranslatorDictionary.cs @@ -5,6 +5,9 @@ namespace SysBot.Pokemon public class ShowdownTranslatorDictionary { + // 添加持有物 + public static List holdItemKeywords = new List { "持有", "携带" }; + // 添加异色 public static Dictionary shinyTypes = new Dictionary { From 1463e72c454ea3b92365a65fd872fdd6373742db Mon Sep 17 00:00:00 2001 From: Easy World Date: Wed, 5 Apr 2023 18:48:41 +0800 Subject: [PATCH 105/171] qq refactor --- SysBot.Pokemon.QQ/MiraiQQHelper.cs | 361 ---------------------- SysBot.Pokemon.QQ/MiraiQQTrade.cs | 33 ++ SysBot.Pokemon.QQ/MiraiQQTradeNotifier.cs | 39 ++- SysBot.Pokemon.QQ/Modules/FileModule.cs | 22 +- SysBot.Pokemon.QQ/Modules/PsModule.cs | 15 +- SysBot.Pokemon/Helpers/AbstractTrade.cs | 157 +++++++--- 6 files changed, 207 insertions(+), 420 deletions(-) delete mode 100644 SysBot.Pokemon.QQ/MiraiQQHelper.cs create mode 100644 SysBot.Pokemon.QQ/MiraiQQTrade.cs diff --git a/SysBot.Pokemon.QQ/MiraiQQHelper.cs b/SysBot.Pokemon.QQ/MiraiQQHelper.cs deleted file mode 100644 index eaecc6f11..000000000 --- a/SysBot.Pokemon.QQ/MiraiQQHelper.cs +++ /dev/null @@ -1,361 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Manganese.Array; -using Mirai.Net.Utils.Scaffolds; -using PKHeX.Core; -using SysBot.Base; - -namespace SysBot.Pokemon.QQ -{ - public class MiraiQQHelper where T : PKM, new() - { - public static void StartTrade(string ps, string qq, string nickName, string groupId) - { - var _ = CheckAndGetPkm(ps, qq, out var msg, out var pkm); - if (!_) - { - MiraiQQBot.SendGroupMessage(new MessageChainBuilder().At(qq).Plain(msg).Build()); - return; - } - var foreign = ps.Contains("Language: "); - - StartTradeWithoutCheck(pkm, qq, nickName, groupId, foreign); - } - - public static void StartTrade(T pkm, string qq, string nickName, string groupId) - { - var _ = CheckPkm(pkm, qq, out var msg); - if (!_) - { - MiraiQQBot.SendGroupMessage(new MessageChainBuilder().At(qq).Plain(msg).Build()); - return; - } - - StartTradeWithoutCheck(pkm, qq, nickName, groupId); - } - - public static void StartTradeMultiChinese(string chinesePsRaw, string qq, string nickName, string groupId) - { - var chinesePss = chinesePsRaw.Split('+').ToList(); - var MaxPkmsPerTrade = MiraiQQBot.Info.Hub.Config.Trade.MaxPkmsPerTrade; - if (MaxPkmsPerTrade <= 1) - { - MiraiQQBot.SendGroupMessage("请联系群主将trade/MaxPkmsPerTrade配置改为大于1"); - return; - } - else if (chinesePss.Count > MaxPkmsPerTrade) - { - MiraiQQBot.SendGroupMessage($"批量交换宝可梦数量应小于等于{MaxPkmsPerTrade}"); - return; - } - List msgs = new(); - List pkms = new(); - List skipAutoOTList = new(); - int invalidCount = 0; - for (var i = 0; i < chinesePss.Count; i++) - { - var ps = ShowdownTranslator.Chinese2Showdown(chinesePss[i]); - var _ = CheckAndGetPkm(ps, qq, out var msg, out var pkm); - if (!_) - { - LogUtil.LogInfo($"批量第{i + 1}只宝可梦有问题:{msg}", nameof(MiraiQQHelper)); - invalidCount++; - } - else - { - LogUtil.LogInfo($"批量第{i + 1}只:\n{ps}", nameof(MiraiQQHelper)); - skipAutoOTList.Add(ps.Contains("Language: ")); - pkms.Add(pkm); - } - } - if (invalidCount == chinesePss.Count) - { - MiraiQQBot.SendGroupMessage("一个都不合法,换个屁"); - return; - } - else if (invalidCount != 0) - { - MiraiQQBot.SendGroupMessage($"期望交换的{chinesePss.Count}只宝可梦中,有{invalidCount}只不合法,仅交换合法的{pkms.Count}只"); - } - - var code = MiraiQQBot.Info.GetRandomTradeCode(); - var __ = AddToTradeQueue(pkms, code, ulong.Parse(qq), nickName, groupId, skipAutoOTList, - PokeRoutineType.LinkTrade, out string message); - MiraiQQBot.SendGroupMessage(message); - } - - public static void StartTradeMulti(List rawPkms, string qq, string nickName, string groupId) - { - var MaxPkmsPerTrade = MiraiQQBot.Info.Hub.Config.Trade.MaxPkmsPerTrade; - if (MaxPkmsPerTrade <= 1) - { - MiraiQQBot.SendGroupMessage("请联系群主将trade/MaxPkmsPerTrade配置改为大于1"); - return; - } - else if (rawPkms.Count > MaxPkmsPerTrade) - { - MiraiQQBot.SendGroupMessage($"批量交换宝可梦数量应小于等于{MaxPkmsPerTrade}"); - return; - } - List pkms = new(); - List skipAutoOTList = new(); - int invalidCount = 0; - for (var i = 0; i < rawPkms.Count; i++) - { - var _ = CheckPkm(rawPkms[i], qq, out var msg); - if (!_) - { - LogUtil.LogInfo($"批量第{i + 1}只宝可梦有问题:{msg}", nameof(MiraiQQHelper)); - invalidCount++; - } - else - { - LogUtil.LogInfo($"批量第{i + 1}只:\n{GameInfo.GetStrings("zh").Species[rawPkms[i].Species]}", nameof(MiraiQQHelper)); - skipAutoOTList.Add(false); - pkms.Add(rawPkms[i]); - } - } - if (invalidCount == rawPkms.Count) - { - MiraiQQBot.SendGroupMessage("一个都不合法,换个屁"); - return; - } - else if (invalidCount != 0) - { - MiraiQQBot.SendGroupMessage($"期望交换的{rawPkms.Count}只宝可梦中,有{invalidCount}只不合法,仅交换合法的{pkms.Count}只"); - } - - var code = MiraiQQBot.Info.GetRandomTradeCode(); - var __ = AddToTradeQueue(pkms, code, ulong.Parse(qq), nickName, groupId, skipAutoOTList, - PokeRoutineType.LinkTrade, out string message); - MiraiQQBot.SendGroupMessage(message); - } - - public static void StartTradeMultiPs(string pssRaw, string qq, string nickName, string groupId) - { - var psArray = pssRaw.Split("\n\n").ToList(); - var MaxPkmsPerTrade = MiraiQQBot.Info.Hub.Config.Trade.MaxPkmsPerTrade; - if (MaxPkmsPerTrade <= 1) - { - MiraiQQBot.SendGroupMessage("请联系群主将trade/MaxPkmsPerTrade配置改为大于1"); - return; - } - else if (psArray.Count > MaxPkmsPerTrade) - { - MiraiQQBot.SendGroupMessage($"批量交换宝可梦数量应小于等于{MaxPkmsPerTrade}"); - return; - } - List pkms = new(); - List skipAutoOTList = new(); - int invalidCount = 0; - for (var i = 0; i < psArray.Count; i++) - { - var ps = psArray[i]; - var _ = CheckAndGetPkm(ps, qq, out var msg, out var pkm); - if (!_) - { - LogUtil.LogInfo($"批量第{i + 1}只宝可梦有问题:{msg}", nameof(MiraiQQHelper)); - invalidCount++; - } - else - { - LogUtil.LogInfo($"批量第{i + 1}只:\n{ps}", nameof(MiraiQQHelper)); - skipAutoOTList.Add(ps.Contains("Language: ")); - pkms.Add(pkm); - } - } - if (invalidCount == psArray.Count) - { - MiraiQQBot.SendGroupMessage("一个都不合法,换个屁"); - return; - } - else if (invalidCount != 0) - { - MiraiQQBot.SendGroupMessage($"期望交换的{psArray.Count}只宝可梦中,有{invalidCount}只不合法,仅交换合法的{pkms.Count}只"); - } - - var code = MiraiQQBot.Info.GetRandomTradeCode(); - var __ = AddToTradeQueue(pkms, code, ulong.Parse(qq), nickName, groupId, skipAutoOTList, - PokeRoutineType.LinkTrade, out string message); - MiraiQQBot.SendGroupMessage(message); - } - - public static void StartTradeWithoutCheck(T pkm, string qq, string nickName, string groupId, bool foreign = false) - { - var code = MiraiQQBot.Info.GetRandomTradeCode(); - var __ = AddToTradeQueue(pkm, code, ulong.Parse(qq), nickName, groupId, foreign, - PokeRoutineType.LinkTrade, out string message); - MiraiQQBot.SendGroupMessage(message); - } - - public static void StartDump(string qq, string nickName, string groupId) - { - var code = MiraiQQBot.Info.GetRandomTradeCode(); - var __ = AddToTradeQueue(new T(), code, ulong.Parse(qq), nickName, groupId, false, - PokeRoutineType.Dump, out string message); - MiraiQQBot.SendGroupMessage(message); - } - - public static bool CheckPkm(T pkm, string username, out string msg) - { - if (!MiraiQQBot.Info.GetCanQueue()) - { - msg = "对不起, 我不再接受队列请求!"; - return false; - } - try - { - if (!pkm.CanBeTraded()) - { - msg = $"取消派送, 官方禁止该宝可梦交易!"; - return false; - } - - if (pkm is T pk) - { - var valid = new LegalityAnalysis(pkm).Valid; - if (valid) - { - msg = - $"已加入等待队列. 如果你选宝可梦的速度太慢,你的派送请求将被取消!"; - return true; - } - } - - var reason = "我没办法创造非法宝可梦"; - msg = $"{reason}"; - } -#pragma warning disable CA1031 // Do not catch general exception types - catch (Exception ex) -#pragma warning restore CA1031 // Do not catch general exception types - { - LogUtil.LogSafe(ex, nameof(MiraiQQBot)); - msg = $"取消派送, 发生了一个错误"; - } - - return false; - } - - public static bool CheckAndGetPkm(string setstring, string username, out string msg, out T outPkm) - { - outPkm = new T(); - if (!MiraiQQBot.Info.GetCanQueue()) - { - msg = "对不起, 我不再接受队列请求!"; - return false; - } - - var set = ShowdownUtil.ConvertToShowdown(setstring); - if (set == null) - { - msg = $"取消派送, 宝可梦昵称为空."; - return false; - } - - var template = AutoLegalityWrapper.GetTemplate(set); - if (template.Species < 1) - { - msg = - $"取消派送, 请使用正确的Showdown Set代码"; - return false; - } - - if (set.InvalidLines.Count != 0) - { - msg = - $"取消派送, 非法的Showdown Set代码:\n{string.Join("\n", set.InvalidLines)}"; - return false; - } - - try - { - var sav = AutoLegalityWrapper.GetTrainerInfo(); - var pkm = sav.GetLegal(template, out var result); - - if (!pkm.CanBeTraded()) - { - msg = $"取消派送, 官方禁止该宝可梦交易!"; - return false; - } - - if (pkm is T pk) - { - var valid = new LegalityAnalysis(pkm).Valid; - if (valid) - { - outPkm = pk; - - msg = - $"已加入等待队列. 如果你选宝可梦的速度太慢,你的派送请求将被取消!"; - return true; - } - } - - var reason = result == "Timeout" - ? "宝可梦创造超时" - : "我没办法创造非法宝可梦"; - msg = $"{reason}"; - } -#pragma warning disable CA1031 // Do not catch general exception types - catch (Exception ex) -#pragma warning restore CA1031 // Do not catch general exception types - { - LogUtil.LogSafe(ex, nameof(MiraiQQBot)); - msg = $"取消派送, 发生了一个错误"; - } - - return false; - } - - private static bool AddToTradeQueue(T pk, int code, ulong userId, string name, string groupId, bool foreign, - PokeRoutineType type, out string msg) - { - return AddToTradeQueue(new List { pk }, code, userId, name, groupId, new List { foreign }, type, out msg); - } - - private static bool AddToTradeQueue(List pks, int code, ulong userId, string name, string groupId, List skipAutoOTList, - PokeRoutineType type, out string msg) - { - if (pks == null || pks.Count == 0) - { - msg = $"宝可梦数据为空"; - return false; - } - T pk = pks.First(); - var trainer = new PokeTradeTrainerInfo(name, userId); - var notifier = new MiraiQQTradeNotifier(pk, trainer, code, name, groupId); - var tt = type == PokeRoutineType.SeedCheck ? PokeTradeType.Seed : (type == PokeRoutineType.Dump ? PokeTradeType.Dump : PokeTradeType.Specific); - var detail = - new PokeTradeDetail(pk, trainer, notifier, tt, code, true); - detail.Context.Add("skipAutoOTList", skipAutoOTList); - if (pks.Count > 0) - { - detail.Context.Add("batch", pks); - } - var trade = new TradeEntry(detail, userId, type, name); - - var added = MiraiQQBot.Info.AddToTradeQueue(trade, userId, false); - - if (added == QueueResultAdd.AlreadyInQueue) - { - msg = $"你已经在队列中,请不要重复发送"; - return false; - } - - var position = MiraiQQBot.Info.CheckPosition(userId, type); - //msg = $"@{name}: Added to the {type} queue, unique ID: {detail.ID}. Current Position: {position.Position}"; - msg = $" 你在第{position.Position}位"; - - var botct = MiraiQQBot.Info.Hub.Bots.Count; - if (position.Position > botct) - { - var eta = MiraiQQBot.Info.Hub.Config.Queues.EstimateDelay(position.Position, botct); - //msg += $". Estimated: {eta:F1} minutes."; - msg += $", 需等待约{eta:F1}分钟"; - } - - return true; - } - } -} \ No newline at end of file diff --git a/SysBot.Pokemon.QQ/MiraiQQTrade.cs b/SysBot.Pokemon.QQ/MiraiQQTrade.cs new file mode 100644 index 000000000..d284b4024 --- /dev/null +++ b/SysBot.Pokemon.QQ/MiraiQQTrade.cs @@ -0,0 +1,33 @@ +using Mirai.Net.Utils.Scaffolds; +using PKHeX.Core; +using SysBot.Pokemon.Helpers; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Linq; + +namespace SysBot.Pokemon.QQ +{ + public class MiraiQQTrade : AbstractTrade where T : PKM, new() + { + private readonly string GroupId = default!; + + public MiraiQQTrade(string qq, string nickName) + { + SetPokeTradeTrainerInfo(new PokeTradeTrainerInfo(nickName, ulong.Parse(qq))); + SetTradeQueueInfo(MiraiQQBot.Info); + GroupId = MiraiQQBot.Settings.GroupId; + } + public override IPokeTradeNotifier GetPokeTradeNotifier(T pkm, int code) + { + return new MiraiQQTradeNotifier(pkm, userInfo, code, userInfo.TrainerName, GroupId); + } + + public override void SendMessage(string message) + { + MiraiQQBot.SendGroupMessage(new MessageChainBuilder().At(userInfo.ID.ToString()).Plain(message).Build()); + } + } +} diff --git a/SysBot.Pokemon.QQ/MiraiQQTradeNotifier.cs b/SysBot.Pokemon.QQ/MiraiQQTradeNotifier.cs index 11e3fd635..1b96bdc99 100644 --- a/SysBot.Pokemon.QQ/MiraiQQTradeNotifier.cs +++ b/SysBot.Pokemon.QQ/MiraiQQTradeNotifier.cs @@ -3,6 +3,9 @@ using System; using System.Linq; using Mirai.Net.Utils.Scaffolds; +using System.Text.RegularExpressions; +using System.Threading.Channels; +using System.Collections.Generic; namespace SysBot.Pokemon.QQ { @@ -30,7 +33,18 @@ public MiraiQQTradeNotifier(T data, PokeTradeTrainerInfo info, int code, string public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, string message) { LogUtil.LogText(message); - //SendMessage($"@{info.Trainer.TrainerName}: {message}"); + if (message.Contains("Found Trading Partner:")) + { + Regex regex = new Regex("TID: (\\d+)"); + string tid = regex.Match(message).Groups[1].ToString(); + regex = new Regex("SID: (\\d+)"); + string sid = regex.Match(message).Groups[1].ToString(); + MiraiQQBot.SendGroupMessage(new MessageChainBuilder().Plain($"找到你了,你的SID7:{sid},TID7:{tid}").Build()); + } + else if (message.StartsWith("批量")) + { + MiraiQQBot.SendGroupMessage(new MessageChainBuilder().Plain(message).Build()); + } } public void TradeCanceled(PokeRoutineExecutor routine, PokeTradeDetail info, PokeTradeResult msg) @@ -59,7 +73,13 @@ public void TradeInitialize(PokeRoutineExecutor routine, PokeTradeDetail i $"@{info.Trainer.TrainerName} (ID: {info.ID}): Initializing trade{receive} with you. Please be ready."; msg += $" Your trade code is: {info.Code:0000 0000}"; LogUtil.LogText(msg); - MiraiQQBot.SendGroupMessage(new MessageChainBuilder().At($"{info.Trainer.ID}").Plain($" 准备交换\n连接密码:{info.Code:0000 0000}\n我的名字:{routine.InGameName}").Build()); + var text = $"\n派送:{ShowdownTranslator.GameStringsZh.Species[Data.Species]}\n密码:{info.Code:0000 0000}\n状态:初始化"; + List batchPKMs = (List)info.Context.GetValueOrDefault("batch", new List()); + if (batchPKMs.Count > 1) + { + text = $"\n批量派送{batchPKMs.Count}只宝可梦\n密码:{info.Code:0000 0000}\n状态:初始化"; + } + MiraiQQBot.SendGroupMessage(new MessageChainBuilder().At($"{info.Trainer.ID}").Plain(text).Build()); } public void TradeSearching(PokeRoutineExecutor routine, PokeTradeDetail info) @@ -69,7 +89,13 @@ public void TradeSearching(PokeRoutineExecutor routine, PokeTradeDetail in var message = $"I'm waiting for you{trainer}! My IGN is {routine.InGameName}."; message += $" Your trade code is: {info.Code:0000 0000}"; LogUtil.LogText(message); - MiraiQQBot.SendGroupMessage(new MessageChainBuilder().At($"{info.Trainer.ID}").Plain($" 寻找中").Build()); + var text = $"派送:{ShowdownTranslator.GameStringsZh.Species[Data.Species]}\n密码:{info.Code:0000 0000}\n状态:搜索中"; + List batchPKMs = (List)info.Context.GetValueOrDefault("batch", new List()); + if (batchPKMs.Count > 1) + { + text = $"批量派送{batchPKMs.Count}只宝可梦\n密码:{info.Code:0000 0000}\n状态:搜索中"; + } + MiraiQQBot.SendGroupMessage(new MessageChainBuilder().At($"{info.Trainer.ID}").Plain(text).Build()); } public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, PokeTradeSummary message) @@ -85,7 +111,12 @@ public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail { var msg = $"Details for {result.FileName}: " + message; LogUtil.LogText(msg); - MiraiQQBot.SendGroupMessage(msg); + if (result.Species != 0 && info.Type == PokeTradeType.Dump) + { + var text = + $"species:{result.Species}\npid:{result.PID}\nec:{result.EncryptionConstant}\nIVs:{string.Join(",", result.IVs)}\nisShiny:{result.IsShiny}"; + MiraiQQBot.SendGroupMessage(text); + } } } } \ No newline at end of file diff --git a/SysBot.Pokemon.QQ/Modules/FileModule.cs b/SysBot.Pokemon.QQ/Modules/FileModule.cs index 68689d59c..e7b7fc4c3 100644 --- a/SysBot.Pokemon.QQ/Modules/FileModule.cs +++ b/SysBot.Pokemon.QQ/Modules/FileModule.cs @@ -11,6 +11,7 @@ using System.Linq; using System.Net.Http; using System.Reactive.Linq; +using System.Reflection; namespace SysBot.Pokemon.QQ { @@ -91,9 +92,9 @@ public async void Execute(MessageReceiverBase @base) return; } if (pkms != null && pkms.Count > 0) - MiraiQQHelper.StartTradeMulti(pkms, senderQQ, nickname, groupId); + new MiraiQQTrade(senderQQ, nickname).StartTradeMultiPKM(pkms); else - MiraiQQHelper.StartTrade((T)pkm, senderQQ, nickname, groupId); + new MiraiQQTrade(senderQQ, nickname).StartTradePKM((T)pkm); } private static List bin2List(byte[] bb) @@ -111,17 +112,28 @@ private static List bin2List(byte[] bb) return pkms; } - private static List bin2List(byte[] bb, int size) + private static List bin2List(byte[] bb, int size) where TP : PKM, new() { int times = bb.Length % size == 0 ? (bb.Length / size) : (bb.Length / size + 1); - List pkmBytes = new(); + List pkmBytes = new(); for (var i = 0; i < times; i++) { int start = i * size; int end = (start + size) > bb.Length ? bb.Length : (start + size); - pkmBytes.Add(bb[start..end]); + var tp = CreateInstance(bb[start..end]); + if (tp != null) pkmBytes.Add(tp); } return pkmBytes; } + + private static TP? CreateInstance(byte[] bytes) where TP : new() + { + // 获取泛型类型定义 + var typeDef = typeof(TP).GetGenericTypeDefinition(); + + // 创建泛型类型实例 + var type = typeDef.MakeGenericType(typeof(byte)); + return (TP)Activator.CreateInstance(type, bytes); + } } } diff --git a/SysBot.Pokemon.QQ/Modules/PsModule.cs b/SysBot.Pokemon.QQ/Modules/PsModule.cs index 78ad318a2..bde1297f1 100644 --- a/SysBot.Pokemon.QQ/Modules/PsModule.cs +++ b/SysBot.Pokemon.QQ/Modules/PsModule.cs @@ -2,7 +2,6 @@ using Mirai.Net.Data.Messages.Concretes; using Mirai.Net.Data.Messages.Receivers; using Mirai.Net.Modules; -using Mirai.Net.Sessions.Http.Managers; using Mirai.Net.Utils.Scaffolds; using PKHeX.Core; using SysBot.Base; @@ -42,10 +41,10 @@ private void ProcessPS(string text, string qq, string nickName, string groupId) { LogUtil.LogInfo($"收到ps代码:\n{text}", nameof(PsModule)); var pss = text.Split("\n\n"); - if (pss.Length > 1) - MiraiQQHelper.StartTradeMultiPs(text, qq, nickName, groupId); + if (pss.Length > 1) + new MiraiQQTrade(qq, nickName).StartTradeMultiPs(text); else - MiraiQQHelper.StartTrade(text, qq, nickName, groupId); + new MiraiQQTrade(qq, nickName).StartTradePs(text); } private void ProcessChinesePS(string text, string qq, string nickName, string groupId) @@ -53,14 +52,12 @@ private void ProcessChinesePS(string text, string qq, string nickName, string gr LogUtil.LogInfo($"收到中文ps代码:\n{text}", nameof(PsModule)); var pss = text.Split("+"); if (pss.Length > 1) - MiraiQQHelper.StartTradeMultiChinese(text, qq, nickName, groupId); + new MiraiQQTrade(qq, nickName).StartTradeMultiChinesePs(text); else { - string ps = ShowdownTranslator.Chinese2Showdown(text); - LogUtil.LogInfo($"中文转换后ps代码:\n{ps}", nameof(PsModule)); - MiraiQQHelper.StartTrade(ps, qq, nickName, groupId); + new MiraiQQTrade(qq, nickName).StartTradeChinesePs(text); } - + } private static bool IsChinesePS(string str) diff --git a/SysBot.Pokemon/Helpers/AbstractTrade.cs b/SysBot.Pokemon/Helpers/AbstractTrade.cs index 10ca5eb01..abfe303f9 100644 --- a/SysBot.Pokemon/Helpers/AbstractTrade.cs +++ b/SysBot.Pokemon/Helpers/AbstractTrade.cs @@ -1,4 +1,5 @@ using PKHeX.Core; +using PKHeX.Core.AutoMod; using SysBot.Base; using System; using System.Collections.Generic; @@ -44,6 +45,7 @@ public void StartTradePs(string ps) public void StartTradeChinesePs(string chinesePs) { var ps = ShowdownTranslator.Chinese2Showdown(chinesePs); + LogUtil.LogInfo($"中文转换后ps代码:\n{ps}", nameof(AbstractTrade)); StartTradePs(ps); } public void StartTradePKM(T pkm) @@ -203,14 +205,9 @@ public void StartDump() PokeRoutineType.Dump, out string message); SendMessage(message); } - public bool CheckPkm(T pkm, out string msg) - { - if (!queueInfo.GetCanQueue()) - { - msg = "对不起, 我不再接受队列请求!"; - return false; - } + public bool Check(T pkm, out string msg) + { try { if (!pkm.CanBeTraded()) @@ -218,18 +215,15 @@ public bool CheckPkm(T pkm, out string msg) msg = $"取消派送, 官方禁止该宝可梦交易!"; return false; } - if (pkm is T pk) { var valid = new LegalityAnalysis(pkm).Valid; if (valid) { - msg = - $"已加入等待队列. 如果你选宝可梦的速度太慢,你的派送请求将被取消!"; + msg = $"已加入等待队列. 如果你选宝可梦的速度太慢,你的派送请求将被取消!"; return true; } } - var reason = "我没办法创造非法宝可梦"; msg = $"{reason}"; } @@ -238,9 +232,17 @@ public bool CheckPkm(T pkm, out string msg) LogUtil.LogSafe(ex, nameof(AbstractTrade)); msg = $"取消派送, 发生了一个错误"; } - return false; } + public bool CheckPkm(T pkm, out string msg) + { + if (!queueInfo.GetCanQueue()) + { + msg = "对不起, 我不再接受队列请求!"; + return false; + } + return Check(pkm, out msg); + } public bool CheckAndGetPkm(string setstring, out string msg, out T outPkm) { @@ -271,30 +273,8 @@ public bool CheckAndGetPkm(string setstring, out string msg, out T outPkm) var sav = AutoLegalityWrapper.GetTrainerInfo(); GenerationFix(sav); var pkm = sav.GetLegal(template, out var result); - - if (!pkm.CanBeTraded()) - { - msg = $"取消派送, 官方禁止该宝可梦交易!"; - return false; - } - - if (pkm is T pk) - { - var valid = new LegalityAnalysis(pkm).Valid; - if (valid) - { - outPkm = pk; - - msg = - $"已加入等待队列. 如果你选宝可梦的速度太慢,你的派送请求将被取消!"; - return true; - } - } - - var reason = result == "Timeout" - ? "宝可梦创造超时" - : "我没办法创造非法宝可梦"; - msg = $"{reason}"; + if (pkm.Nickname.ToLower() == "egg" && Breeding.CanHatchAsEgg(pkm.Species)) EggTrade(pkm, template); + if (Check(outPkm, out msg)) outPkm = (T)pkm; } catch (Exception ex) { @@ -307,11 +287,7 @@ public bool CheckAndGetPkm(string setstring, out string msg, out T outPkm) private static void GenerationFix(ITrainerInfo sav) { - if (typeof(T) == typeof(PK8) || typeof(T) == typeof(PB8) || typeof(T) == typeof(PA8)) - { - var a = sav.GetType().GetProperty("Generation"); - a?.SetValue(sav, 8); - } + if (typeof(T) == typeof(PK8) || typeof(T) == typeof(PB8) || typeof(T) == typeof(PA8)) sav.GetType().GetProperty("Generation")?.SetValue(sav, 8); } private bool AddToTradeQueue(T pk, int code, bool skipAutoOT, @@ -366,5 +342,104 @@ private bool AddToTradeQueue(List pks, int code, List skipAutoOTList, return true; } + // https://github.com/Koi-3088/ForkBot.NET/blob/KoiTest/SysBot.Pokemon/Helpers/TradeExtensions.cs + public static void EggTrade(PKM pk, IBattleTemplate template) + { + pk.IsNicknamed = true; + pk.Nickname = pk.Language switch + { + 1 => "タマゴ", + 3 => "Œuf", + 4 => "Uovo", + 5 => "Ei", + 7 => "Huevo", + 8 => "알", + 9 or 10 => "蛋", + _ => "Egg", + }; + + pk.IsEgg = true; + pk.Egg_Location = pk switch + { + PB8 => 60010, + PK9 => 30023, + _ => 60002, //PK8 + }; + + pk.HeldItem = 0; + pk.CurrentLevel = 1; + pk.EXP = 0; + pk.Met_Level = 1; + pk.Met_Location = pk switch + { + PB8 => 65535, + PK9 => 0, + _ => 30002, //PK8 + }; + + pk.CurrentHandler = 0; + pk.OT_Friendship = 1; + pk.HT_Name = ""; + pk.HT_Friendship = 0; + pk.ClearMemories(); + pk.StatNature = pk.Nature; + pk.SetEVs(new int[] { 0, 0, 0, 0, 0, 0 }); + + pk.SetMarking(0, 0); + pk.SetMarking(1, 0); + pk.SetMarking(2, 0); + pk.SetMarking(3, 0); + pk.SetMarking(4, 0); + pk.SetMarking(5, 0); + + pk.ClearRelearnMoves(); + + if (pk is PK8 pk8) + { + pk8.HT_Language = 0; + pk8.HT_Gender = 0; + pk8.HT_Memory = 0; + pk8.HT_Feeling = 0; + pk8.HT_Intensity = 0; + pk8.DynamaxLevel = pk8.GetSuggestedDynamaxLevel(pk8, 0); + } + else if (pk is PB8 pb8) + { + pb8.HT_Language = 0; + pb8.HT_Gender = 0; + pb8.HT_Memory = 0; + pb8.HT_Feeling = 0; + pb8.HT_Intensity = 0; + pb8.DynamaxLevel = pb8.GetSuggestedDynamaxLevel(pb8, 0); + } + else if (pk is PK9 pk9) + { + pk9.HT_Language = 0; + pk9.HT_Gender = 0; + pk9.HT_Memory = 0; + pk9.HT_Feeling = 0; + pk9.HT_Intensity = 0; + pk9.Obedience_Level = 1; + pk9.Version = 0; + pk9.BattleVersion = 0; + pk9.TeraTypeOverride = (MoveType)19; + } + + var la = new LegalityAnalysis(pk); + var enc = la.EncounterMatch; + pk.CurrentFriendship = enc is EncounterStatic s ? s.EggCycles : pk.PersonalInfo.HatchCycles; + + Span relearn = stackalloc ushort[4]; + la.GetSuggestedRelearnMoves(relearn, enc); + pk.SetRelearnMoves(relearn); + + pk.SetSuggestedMoves(); + + pk.Move1_PPUps = pk.Move2_PPUps = pk.Move3_PPUps = pk.Move4_PPUps = 0; + pk.SetMaximumPPCurrent(pk.Moves); + pk.SetSuggestedHyperTrainingData(); + pk.SetSuggestedRibbons(template, enc); + } + } } From 59fc42087efebc267bd04e83d4b79225e0ccb670 Mon Sep 17 00:00:00 2001 From: Easy World Date: Thu, 6 Apr 2023 20:34:21 +0800 Subject: [PATCH 106/171] Check pkm bug fix --- SysBot.Pokemon/Helpers/AbstractTrade.cs | 7 +++++-- SysBot.Pokemon/deps/PKHeX.Core.AutoMod.dll | Bin 128000 -> 128512 bytes 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/SysBot.Pokemon/Helpers/AbstractTrade.cs b/SysBot.Pokemon/Helpers/AbstractTrade.cs index abfe303f9..17a003d93 100644 --- a/SysBot.Pokemon/Helpers/AbstractTrade.cs +++ b/SysBot.Pokemon/Helpers/AbstractTrade.cs @@ -217,13 +217,16 @@ public bool Check(T pkm, out string msg) } if (pkm is T pk) { - var valid = new LegalityAnalysis(pkm).Valid; + var la = new LegalityAnalysis(pkm); + var valid = la.Valid; if (valid) { msg = $"已加入等待队列. 如果你选宝可梦的速度太慢,你的派送请求将被取消!"; return true; } + LogUtil.LogInfo($"非法原因:\n{la.Report()}", nameof(AbstractTrade)); } + LogUtil.LogInfo($"pkm type:{pkm.GetType()}, T:{typeof(T)}", nameof(AbstractTrade)); var reason = "我没办法创造非法宝可梦"; msg = $"{reason}"; } @@ -274,7 +277,7 @@ public bool CheckAndGetPkm(string setstring, out string msg, out T outPkm) GenerationFix(sav); var pkm = sav.GetLegal(template, out var result); if (pkm.Nickname.ToLower() == "egg" && Breeding.CanHatchAsEgg(pkm.Species)) EggTrade(pkm, template); - if (Check(outPkm, out msg)) outPkm = (T)pkm; + if (Check((T)pkm, out msg)) outPkm = (T)pkm; } catch (Exception ex) { diff --git a/SysBot.Pokemon/deps/PKHeX.Core.AutoMod.dll b/SysBot.Pokemon/deps/PKHeX.Core.AutoMod.dll index 80d8b2c428ba77b76acd95b590fa9aa84ede2799..8169b0c4c6cba1c774c7e251e959c82ed2ffc2bd 100644 GIT binary patch delta 56498 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 delta 56038 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 From 02ebbd56150bd2fe8b1edaa406c620abbcdc6392 Mon Sep 17 00:00:00 2001 From: Easy World Date: Thu, 6 Apr 2023 22:52:51 +0800 Subject: [PATCH 107/171] pkm bug fix --- SysBot.Pokemon/Helpers/AbstractTrade.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/SysBot.Pokemon/Helpers/AbstractTrade.cs b/SysBot.Pokemon/Helpers/AbstractTrade.cs index 17a003d93..cbdd6e1e3 100644 --- a/SysBot.Pokemon/Helpers/AbstractTrade.cs +++ b/SysBot.Pokemon/Helpers/AbstractTrade.cs @@ -277,7 +277,11 @@ public bool CheckAndGetPkm(string setstring, out string msg, out T outPkm) GenerationFix(sav); var pkm = sav.GetLegal(template, out var result); if (pkm.Nickname.ToLower() == "egg" && Breeding.CanHatchAsEgg(pkm.Species)) EggTrade(pkm, template); - if (Check((T)pkm, out msg)) outPkm = (T)pkm; + if (Check((T)pkm, out msg)) + { + outPkm = (T)pkm; + return true; + } } catch (Exception ex) { From f48bd23ea4e25f6a7e0b9a06db43a7d6f7285f45 Mon Sep 17 00:00:00 2001 From: Easy World Date: Sun, 9 Apr 2023 21:49:50 +0800 Subject: [PATCH 108/171] update test --- SysBot.Tests/TranslatorTests.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/SysBot.Tests/TranslatorTests.cs b/SysBot.Tests/TranslatorTests.cs index e7f04fbf8..748e96709 100644 --- a/SysBot.Tests/TranslatorTests.cs +++ b/SysBot.Tests/TranslatorTests.cs @@ -11,8 +11,8 @@ public class TranslatorTests static TranslatorTests() => AutoLegalityWrapper.EnsureInitialized(new LegalitySettings()); [Theory] - [InlineData("公肯泰罗帕底亚的样子(火)形态", "Tauros-Paldea-Fire (M)")] - public void TestForm(string input, string output) + [InlineData("公肯泰罗携带大师球6V异色努力值252生命全招式异国-泰山压顶", "Tauros (M) @ Master Ball\nShiny: Yes\nIVs: 31 HP / 31 Atk / 31 Def / 31 SpA / 31 SpD / 31 Spe\nEVs: 252 HP \n.RelearnMoves=$suggestAll\nLanguage: Italian\n-Body Slam")] + public void TestTrans(string input, string output) { var result = ShowdownTranslator.Chinese2Showdown(input); result.Should().Be(output); @@ -21,6 +21,8 @@ public void TestForm(string input, string output) [Theory] [InlineData("皮卡丘")] [InlineData("木木枭")] + [InlineData("彩粉蝶-冰雪花纹")] + [InlineData("大剑鬼")] public void TestLegal(string input) { var setstring = ShowdownTranslator.Chinese2Showdown(input); From b1f537ec0c997bc7022af0842b067850fe0243f6 Mon Sep 17 00:00:00 2001 From: Easy World Date: Sun, 9 Apr 2023 21:50:11 +0800 Subject: [PATCH 109/171] dodo withdraw --- SysBot.Pokemon.Dodo/DodoBot.cs | 2 +- SysBot.Pokemon.Dodo/PokemonProcessService.cs | 29 ++++++++++++------- .../Settings/Integrations/DodoSettings.cs | 3 ++ 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/SysBot.Pokemon.Dodo/DodoBot.cs b/SysBot.Pokemon.Dodo/DodoBot.cs index 37ec2206d..a29d29be5 100644 --- a/SysBot.Pokemon.Dodo/DodoBot.cs +++ b/SysBot.Pokemon.Dodo/DodoBot.cs @@ -31,7 +31,7 @@ public DodoBot(DodoSettings settings, PokeTradeHub hub) Token = settings.Token }); //事件处理服务,可自定义,只要继承EventProcessService抽象类即可 - var eventProcessService = new PokemonProcessService(OpenApiService, settings.ChannelId); + var eventProcessService = new PokemonProcessService(OpenApiService, settings); //开放事件服务 var openEventService = new OpenEventService(OpenApiService, eventProcessService, new OpenEventOptions { diff --git a/SysBot.Pokemon.Dodo/PokemonProcessService.cs b/SysBot.Pokemon.Dodo/PokemonProcessService.cs index cad6ef128..f7ece2e59 100644 --- a/SysBot.Pokemon.Dodo/PokemonProcessService.cs +++ b/SysBot.Pokemon.Dodo/PokemonProcessService.cs @@ -1,10 +1,9 @@ using System; -using System.Net; using System.Net.Http; using DoDo.Open.Sdk.Models.Bots; +using DoDo.Open.Sdk.Models.ChannelMessages; using DoDo.Open.Sdk.Models.Events; using DoDo.Open.Sdk.Models.Messages; -using DoDo.Open.Sdk.Models.Personals; using DoDo.Open.Sdk.Services; using PKHeX.Core; using SysBot.Base; @@ -17,12 +16,14 @@ namespace SysBot.Pokemon.Dodo private static readonly string LogIdentity = "DodoBot"; private static readonly string Welcome = "at我并尝试对我说:\n皮卡丘\ntrade ps代码\n或者直接拖一个文件进来"; private readonly string _channelId; + private DodoSettings _dodoSettings; private string _botDodoSourceId = default!; - public PokemonProcessService(OpenApiService openApiService, string channelId) + public PokemonProcessService(OpenApiService openApiService, DodoSettings settings) { _openApiService = openApiService; - _channelId = channelId; + _channelId = settings.ChannelId; + _dodoSettings = settings; } public override void Connected(string message) @@ -74,7 +75,7 @@ public override void ChannelMessageEvent( var p = GetPKM(downloadBytes); if (p is TP pkm) { - //DodoHelper.StartTrade(pkm, eventBody.DodoSourceId, eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId); + ProcessWithdraw(eventBody.MessageId); new DodoTrade(ulong.Parse(eventBody.DodoSourceId), eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId).StartTradePKM(pkm); } @@ -95,25 +96,25 @@ public override void ChannelMessageEvent( content = content.Substring(content.IndexOf('>') + 1); if (typeof(TP) == typeof(PK9) && content.Contains("\n\n") && ShowdownTranslator.IsPS(content))// 仅SV支持批量,其他偷懒还没写 { - //DodoHelper.StartTradeMultiPs(content.Trim(), eventBody.DodoSourceId, eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId); + ProcessWithdraw(eventBody.MessageId); new DodoTrade(ulong.Parse(eventBody.DodoSourceId), eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId).StartTradeMultiPs(content.Trim()); return; } else if (ShowdownTranslator.IsPS(content)) { - //DodoHelper.StartTrade(content.Trim(), eventBody.DodoSourceId, eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId); + ProcessWithdraw(eventBody.MessageId); new DodoTrade(ulong.Parse(eventBody.DodoSourceId), eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId).StartTradePs(content.Trim()); return; } else if (content.Trim().StartsWith("dump")) { - //DodoHelper.StartDump(eventBody.DodoSourceId, eventBody.Personal.NickName, eventBody.ChannelId); + ProcessWithdraw(eventBody.MessageId); new DodoTrade(ulong.Parse(eventBody.DodoSourceId), eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId).StartDump(); return; } else if (typeof(TP) == typeof(PK9) && content.Trim().Contains('+'))// 仅SV支持批量,其他偷懒还没写 { - //DodoHelper.StartTradeMultiChinese(content.Trim(), eventBody.DodoSourceId, eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId); + ProcessWithdraw(eventBody.MessageId); new DodoTrade(ulong.Parse(eventBody.DodoSourceId), eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId).StartTradeMultiChinesePs(content.Trim()); return; } @@ -122,7 +123,7 @@ public override void ChannelMessageEvent( if (!string.IsNullOrWhiteSpace(ps)) { LogUtil.LogInfo($"收到命令\n{ps}", LogIdentity); - //DodoHelper.StartTrade(ps, eventBody.DodoSourceId, eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId); + ProcessWithdraw(eventBody.MessageId); new DodoTrade(ulong.Parse(eventBody.DodoSourceId), eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId).StartTradePs(ps); } else if (content.Contains("取消")) @@ -190,6 +191,14 @@ private static bool ValidFileName(string fileName) return null; } + private void ProcessWithdraw(string messageId) + { + if (_dodoSettings.WithdrawTradeMessage) + { + DodoBot.OpenApiService.SetChannelMessageWithdraw(new SetChannelMessageWithdrawInput() { MessageId = messageId }, true); + } + } + public override void MessageReactionEvent( EventSubjectOutput> input) { diff --git a/SysBot.Pokemon/Settings/Integrations/DodoSettings.cs b/SysBot.Pokemon/Settings/Integrations/DodoSettings.cs index 36da802b6..9ce4a5576 100644 --- a/SysBot.Pokemon/Settings/Integrations/DodoSettings.cs +++ b/SysBot.Pokemon/Settings/Integrations/DodoSettings.cs @@ -23,5 +23,8 @@ public class DodoSettings [Category(Startup), Description("机器人响应频道id")] public string ChannelId { get; set; } = string.Empty; + + [Category(Startup), Description("是否撤回交换消息")] + public bool WithdrawTradeMessage { get; set; } = false; } } \ No newline at end of file From 829ccc7877505388deaac313da0d44de34628b36 Mon Sep 17 00:00:00 2001 From: Easy World Date: Fri, 14 Apr 2023 00:26:49 +0800 Subject: [PATCH 110/171] Update PokeTradeBot.cs fix missing variable --- SysBot.Pokemon/SWSH/BotTrade/PokeTradeBot.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SysBot.Pokemon/SWSH/BotTrade/PokeTradeBot.cs b/SysBot.Pokemon/SWSH/BotTrade/PokeTradeBot.cs index 150af8d8d..e150197d0 100644 --- a/SysBot.Pokemon/SWSH/BotTrade/PokeTradeBot.cs +++ b/SysBot.Pokemon/SWSH/BotTrade/PokeTradeBot.cs @@ -432,7 +432,7 @@ private async Task SetPkmWithSwappedIDDetails(PK8 toSend, string trainerNa if (tradeswsh.Valid) { Log($"Pokemon is valid, use trade partnerInfo"); - await SetBoxPokemon(cln, InjectBox, InjectSlot, token, sav).ConfigureAwait(false); + await SetBoxPokemon(cln, 0, 0, token, sav).ConfigureAwait(false); } else { From 05361a41765ccd8d40e83b9223267fe69d97dcaa Mon Sep 17 00:00:00 2001 From: Easy World Date: Sun, 16 Apr 2023 18:43:56 +0800 Subject: [PATCH 111/171] file trade refactor --- SysBot.Pokemon.Dodo/PokemonProcessService.cs | 43 ++------ SysBot.Pokemon.QQ/Modules/FileModule.cs | 101 +++---------------- SysBot.Pokemon/Helpers/AbstractTrade.cs | 2 +- SysBot.Pokemon/Helpers/FileTradeHelper.cs | 67 ++++++++++++ SysBot.Tests/TranslatorTests.cs | 1 + 5 files changed, 93 insertions(+), 121 deletions(-) create mode 100644 SysBot.Pokemon/Helpers/FileTradeHelper.cs diff --git a/SysBot.Pokemon.Dodo/PokemonProcessService.cs b/SysBot.Pokemon.Dodo/PokemonProcessService.cs index f7ece2e59..1faf3466a 100644 --- a/SysBot.Pokemon.Dodo/PokemonProcessService.cs +++ b/SysBot.Pokemon.Dodo/PokemonProcessService.cs @@ -7,6 +7,7 @@ using DoDo.Open.Sdk.Services; using PKHeX.Core; using SysBot.Base; +using SysBot.Pokemon.Helpers; namespace SysBot.Pokemon.Dodo { @@ -65,20 +66,22 @@ public override void ChannelMessageEvent( if (eventBody.MessageBody is MessageBodyFile messageBodyFile) { - if (!ValidFileSize(messageBodyFile.Size ?? 0) || !ValidFileName(messageBodyFile.Name)) + if (!FileTradeHelper.ValidFileSize(messageBodyFile.Size ?? 0) && !FileTradeHelper.ValidFileName(messageBodyFile.Name)) { + ProcessWithdraw(eventBody.MessageId); DodoBot.SendChannelMessage("非法文件", eventBody.ChannelId); return; } using var client = new HttpClient(); var downloadBytes = client.GetByteArrayAsync(messageBodyFile.Url).Result; - var p = GetPKM(downloadBytes); - if (p is TP pkm) - { - ProcessWithdraw(eventBody.MessageId); - new DodoTrade(ulong.Parse(eventBody.DodoSourceId), eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId).StartTradePKM(pkm); - } - + var pkms = FileTradeHelper.Bin2List(downloadBytes); + ProcessWithdraw(eventBody.MessageId); + if (pkms.Count == 1) + new DodoTrade(ulong.Parse(eventBody.DodoSourceId), eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId).StartTradePKM(pkms[0]); + else if (pkms.Count is > 1 and <= 960) + new DodoTrade(ulong.Parse(eventBody.DodoSourceId), eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId).StartTradeMultiPKM(pkms); + else + DodoBot.SendChannelMessage("文件内容不正确", eventBody.ChannelId); return; } @@ -167,30 +170,6 @@ private static string GetClearTradeMessage(QueueResultRemove result) }; } - private static bool ValidFileSize(long size) - { - if (typeof(TP) == typeof(PK8) || typeof(TP) == typeof(PB8) || typeof(TP) == typeof(PK9)) return size == 344; - else if (typeof(TP) == typeof(PA8)) return size == 376; - else return false; - } - - private static bool ValidFileName(string fileName) - { - return (typeof(TP) == typeof(PK8) && fileName.EndsWith("pk8", StringComparison.OrdinalIgnoreCase) - || typeof(TP) == typeof(PB8) && fileName.EndsWith("pb8", StringComparison.OrdinalIgnoreCase) - || typeof(TP) == typeof(PA8) && fileName.EndsWith("pa8", StringComparison.OrdinalIgnoreCase) - || typeof(TP) == typeof(PK9) && fileName.EndsWith("pk9", StringComparison.OrdinalIgnoreCase)); - } - - private static PKM? GetPKM(byte[] bytes) - { - if (typeof(TP) == typeof(PK8)) return new PK8(bytes); - else if (typeof(TP) == typeof(PB8)) return new PB8(bytes); - else if (typeof(TP) == typeof(PA8)) return new PA8(bytes); - else if (typeof(TP) == typeof(PK9)) return new PK9(bytes); - return null; - } - private void ProcessWithdraw(string messageId) { if (_dodoSettings.WithdrawTradeMessage) diff --git a/SysBot.Pokemon.QQ/Modules/FileModule.cs b/SysBot.Pokemon.QQ/Modules/FileModule.cs index e7b7fc4c3..82a9fd107 100644 --- a/SysBot.Pokemon.QQ/Modules/FileModule.cs +++ b/SysBot.Pokemon.QQ/Modules/FileModule.cs @@ -6,12 +6,12 @@ using Mirai.Net.Utils.Scaffolds; using PKHeX.Core; using SysBot.Base; +using SysBot.Pokemon.Helpers; using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Reactive.Linq; -using System.Reflection; namespace SysBot.Pokemon.QQ { @@ -33,57 +33,19 @@ public async void Execute(MessageReceiverBase @base) if (fileMessage == null) return; LogUtil.LogInfo("In file module", nameof(FileModule)); var fileName = fileMessage.Name; - string operationType; - if (typeof(T) == typeof(PK8) && fileName.EndsWith(".pk8", StringComparison.OrdinalIgnoreCase)) - operationType = "pk8"; - else if (typeof(T) == typeof(PB8) && fileName.EndsWith(".pb8", StringComparison.OrdinalIgnoreCase)) - operationType = "pb8"; - else if (typeof(T) == typeof(PA8) && fileName.EndsWith(".pa8", StringComparison.OrdinalIgnoreCase)) - operationType = "pa8"; - else if (typeof(T) == typeof(PK9) && fileName.EndsWith(".pk9", StringComparison.OrdinalIgnoreCase)) - operationType = "pk9"; - else if (typeof(T) == typeof(PK9) && fileName.EndsWith(".bin", StringComparison.OrdinalIgnoreCase)) - operationType = "bin"; - else return; + if (!FileTradeHelper.ValidFileName(fileName) && !FileTradeHelper.ValidFileSize(fileMessage.Size)) + { + await MessageManager.SendGroupMessageAsync(groupId, "非法文件"); + return; + } - PKM pkm = default!; - List pkms = new(); + List pkms = default!; try { var f = await FileManager.GetFileAsync(groupId, fileMessage.FileId, true); using var client = new HttpClient(); byte[] data = client.GetByteArrayAsync(f.DownloadInfo.Url).Result; - switch (operationType) - { - case "pk8" or "pb8" or "pk9" when data.Length != 344: - await MessageManager.SendGroupMessageAsync(groupId, "非法文件"); - return; - case "pa8" when data.Length != 376: - await MessageManager.SendGroupMessageAsync(groupId, "非法文件"); - return; - } - - switch (operationType) - { - case "pk8": - pkm = new PK8(data); - break; - case "pb8": - pkm = new PB8(data); - break; - case "pa8": - pkm = new PA8(data); - break; - case "pk9": - pkm = new PK9(data); - break; - case "bin": - pkms = bin2List(data); - break; - default: return; - } - - LogUtil.LogInfo($"operationType:{operationType}", nameof(FileModule)); + pkms = FileTradeHelper.Bin2List(data); await FileManager.DeleteFileAsync(groupId, fileMessage.FileId); } catch (Exception ex) @@ -91,49 +53,12 @@ public async void Execute(MessageReceiverBase @base) LogUtil.LogError(ex.Message, nameof(FileModule)); return; } - if (pkms != null && pkms.Count > 0) - new MiraiQQTrade(senderQQ, nickname).StartTradeMultiPKM(pkms); + if (pkms.Count is > 1 and <= 960) + new MiraiQQTrade(senderQQ, nickname).StartTradeMultiPKM(pkms); + else if (pkms.Count == 1) + new MiraiQQTrade(senderQQ, nickname).StartTradePKM(pkms[0]); else - new MiraiQQTrade(senderQQ, nickname).StartTradePKM((T)pkm); - } - - private static List bin2List(byte[] bb) - { - int count = 344; - int times = bb.Length % count == 0 ? (bb.Length / count) : (bb.Length / count + 1); - List pkms = new(); - for (var i = 0; i < times; i++) - { - int start = i * count; - int end = (start + count) > bb.Length ? bb.Length : (start + count); - PK9 pk9 = new(bb[start..end]); - if (pk9.Valid && pk9.Species > 0) pkms.Add(pk9); - } - return pkms; - } - - private static List bin2List(byte[] bb, int size) where TP : PKM, new() - { - int times = bb.Length % size == 0 ? (bb.Length / size) : (bb.Length / size + 1); - List pkmBytes = new(); - for (var i = 0; i < times; i++) - { - int start = i * size; - int end = (start + size) > bb.Length ? bb.Length : (start + size); - var tp = CreateInstance(bb[start..end]); - if (tp != null) pkmBytes.Add(tp); - } - return pkmBytes; - } - - private static TP? CreateInstance(byte[] bytes) where TP : new() - { - // 获取泛型类型定义 - var typeDef = typeof(TP).GetGenericTypeDefinition(); - - // 创建泛型类型实例 - var type = typeDef.MakeGenericType(typeof(byte)); - return (TP)Activator.CreateInstance(type, bytes); + await MessageManager.SendGroupMessageAsync(groupId, "文件内容不正确"); } } } diff --git a/SysBot.Pokemon/Helpers/AbstractTrade.cs b/SysBot.Pokemon/Helpers/AbstractTrade.cs index cbdd6e1e3..7aabfe382 100644 --- a/SysBot.Pokemon/Helpers/AbstractTrade.cs +++ b/SysBot.Pokemon/Helpers/AbstractTrade.cs @@ -106,7 +106,7 @@ public void StartTradeMultiPKM(List rawPkms) } else { - LogUtil.LogInfo($"批量第{i + 1}只:\n{GameInfo.GetStrings("zh").Species[rawPkms[i].Species]}", nameof(AbstractTrade)); + LogUtil.LogInfo($"批量第{i + 1}只:{GameInfo.GetStrings("zh").Species[rawPkms[i].Species]}", nameof(AbstractTrade)); skipAutoOTList.Add(false); pkms.Add(rawPkms[i]); } diff --git a/SysBot.Pokemon/Helpers/FileTradeHelper.cs b/SysBot.Pokemon/Helpers/FileTradeHelper.cs new file mode 100644 index 000000000..5b96d6480 --- /dev/null +++ b/SysBot.Pokemon/Helpers/FileTradeHelper.cs @@ -0,0 +1,67 @@ +using PKHeX.Core; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace SysBot.Pokemon.Helpers +{ + ///

+ /// 宝可梦文件交易帮助类 + /// + /// + public class FileTradeHelper where T : PKM, new() + { + /// + /// 将bin文件转换成对应版本的PKM list + /// + /// + /// + public static List Bin2List(byte[] bb) + { + int size = pkmSize[typeof(T)]; + int times = bb.Length % size == 0 ? (bb.Length / size) : (bb.Length / size + 1); + List pkmBytes = new(); + for (var i = 0; i < times; i++) + { + int start = i * size; + int end = (start + size) > bb.Length ? bb.Length : (start + size); + var tp = GetPKM(bb[start..end]); + if (tp != null && tp.Species > 0 && tp.Valid && tp is T pkm) pkmBytes.Add(pkm); + } + return pkmBytes; + } + /// + /// 文件名称是否有效 + /// + /// + /// + public static bool ValidFileName(string fileName) + { + string ext = fileName?.Split('.').Last().ToLower() ?? ""; + return ext == typeof(T).Name.ToLower() || ext == "bin"; + } + /// + /// 文件大小是否有效 + /// + /// + /// + public static bool ValidFileSize(long size) => size == pkmSize[typeof(T)]; + + static PKM? GetPKM(byte[] ba) => typeof(T) switch + { + Type t when t == typeof(PK8) => new PK8(ba), + Type t when t == typeof(PB8) => new PB8(ba), + Type t when t == typeof(PA8) => new PA8(ba), + Type t when t == typeof(PK9) => new PK9(ba), + _ => null + }; + static readonly Dictionary pkmSize = new() + { + { typeof(PK8), 344 }, + { typeof(PB8), 344 }, + { typeof(PA8), 376 }, + { typeof(PK9), 344 } + }; + + } +} diff --git a/SysBot.Tests/TranslatorTests.cs b/SysBot.Tests/TranslatorTests.cs index 748e96709..f84508bc8 100644 --- a/SysBot.Tests/TranslatorTests.cs +++ b/SysBot.Tests/TranslatorTests.cs @@ -23,6 +23,7 @@ public void TestTrans(string input, string output) [InlineData("木木枭")] [InlineData("彩粉蝶-冰雪花纹")] [InlineData("大剑鬼")] + [InlineData("火暴兽")] public void TestLegal(string input) { var setstring = ShowdownTranslator.Chinese2Showdown(input); From 9d0da7b82a22e6ff23c8815f8b3b84d95f51ee7a Mon Sep 17 00:00:00 2001 From: Easy World Date: Sun, 16 Apr 2023 18:55:24 +0800 Subject: [PATCH 112/171] Fix: file size and file name judge bug --- SysBot.Pokemon.Dodo/PokemonProcessService.cs | 2 +- SysBot.Pokemon.QQ/Modules/FileModule.cs | 2 +- SysBot.Pokemon/Helpers/FileTradeHelper.cs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/SysBot.Pokemon.Dodo/PokemonProcessService.cs b/SysBot.Pokemon.Dodo/PokemonProcessService.cs index 1faf3466a..14fdf60c2 100644 --- a/SysBot.Pokemon.Dodo/PokemonProcessService.cs +++ b/SysBot.Pokemon.Dodo/PokemonProcessService.cs @@ -66,7 +66,7 @@ public override void ChannelMessageEvent( if (eventBody.MessageBody is MessageBodyFile messageBodyFile) { - if (!FileTradeHelper.ValidFileSize(messageBodyFile.Size ?? 0) && !FileTradeHelper.ValidFileName(messageBodyFile.Name)) + if (!FileTradeHelper.ValidFileSize(messageBodyFile.Size ?? 0) || !FileTradeHelper.ValidFileName(messageBodyFile.Name)) { ProcessWithdraw(eventBody.MessageId); DodoBot.SendChannelMessage("非法文件", eventBody.ChannelId); diff --git a/SysBot.Pokemon.QQ/Modules/FileModule.cs b/SysBot.Pokemon.QQ/Modules/FileModule.cs index 82a9fd107..493ef5f8f 100644 --- a/SysBot.Pokemon.QQ/Modules/FileModule.cs +++ b/SysBot.Pokemon.QQ/Modules/FileModule.cs @@ -33,7 +33,7 @@ public async void Execute(MessageReceiverBase @base) if (fileMessage == null) return; LogUtil.LogInfo("In file module", nameof(FileModule)); var fileName = fileMessage.Name; - if (!FileTradeHelper.ValidFileName(fileName) && !FileTradeHelper.ValidFileSize(fileMessage.Size)) + if (!FileTradeHelper.ValidFileName(fileName) || !FileTradeHelper.ValidFileSize(fileMessage.Size)) { await MessageManager.SendGroupMessageAsync(groupId, "非法文件"); return; diff --git a/SysBot.Pokemon/Helpers/FileTradeHelper.cs b/SysBot.Pokemon/Helpers/FileTradeHelper.cs index 5b96d6480..c76e035bd 100644 --- a/SysBot.Pokemon/Helpers/FileTradeHelper.cs +++ b/SysBot.Pokemon/Helpers/FileTradeHelper.cs @@ -38,14 +38,14 @@ public static List Bin2List(byte[] bb) public static bool ValidFileName(string fileName) { string ext = fileName?.Split('.').Last().ToLower() ?? ""; - return ext == typeof(T).Name.ToLower() || ext == "bin"; + return (ext == typeof(T).Name.ToLower()) || (ext == "bin"); } /// /// 文件大小是否有效 /// /// /// - public static bool ValidFileSize(long size) => size == pkmSize[typeof(T)]; + public static bool ValidFileSize(long size) => (size == pkmSize[typeof(T)]) || (size % pkmSize[typeof(T)] == 0); static PKM? GetPKM(byte[] ba) => typeof(T) switch { From 8d297b68bf2c97baf43ef8043c9a195098e508ac Mon Sep 17 00:00:00 2001 From: Easy World Date: Sun, 16 Apr 2023 19:55:01 +0800 Subject: [PATCH 113/171] Fix: file size problem --- SysBot.Pokemon.Dodo/PokemonProcessService.cs | 2 +- SysBot.Pokemon.QQ/Modules/FileModule.cs | 2 +- SysBot.Pokemon/Helpers/FileTradeHelper.cs | 23 +++++++++- SysBot.Tests/FileTradeHelperTests.cs | 45 ++++++++++++++++++++ 4 files changed, 68 insertions(+), 4 deletions(-) create mode 100644 SysBot.Tests/FileTradeHelperTests.cs diff --git a/SysBot.Pokemon.Dodo/PokemonProcessService.cs b/SysBot.Pokemon.Dodo/PokemonProcessService.cs index 14fdf60c2..526f01ab2 100644 --- a/SysBot.Pokemon.Dodo/PokemonProcessService.cs +++ b/SysBot.Pokemon.Dodo/PokemonProcessService.cs @@ -78,7 +78,7 @@ public override void ChannelMessageEvent( ProcessWithdraw(eventBody.MessageId); if (pkms.Count == 1) new DodoTrade(ulong.Parse(eventBody.DodoSourceId), eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId).StartTradePKM(pkms[0]); - else if (pkms.Count is > 1 and <= 960) + else if (pkms.Count > 1 && pkms.Count <= FileTradeHelper.MaxCountInBin) new DodoTrade(ulong.Parse(eventBody.DodoSourceId), eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId).StartTradeMultiPKM(pkms); else DodoBot.SendChannelMessage("文件内容不正确", eventBody.ChannelId); diff --git a/SysBot.Pokemon.QQ/Modules/FileModule.cs b/SysBot.Pokemon.QQ/Modules/FileModule.cs index 493ef5f8f..3e5d57632 100644 --- a/SysBot.Pokemon.QQ/Modules/FileModule.cs +++ b/SysBot.Pokemon.QQ/Modules/FileModule.cs @@ -53,7 +53,7 @@ public async void Execute(MessageReceiverBase @base) LogUtil.LogError(ex.Message, nameof(FileModule)); return; } - if (pkms.Count is > 1 and <= 960) + if (pkms.Count > 1 && pkms.Count <= FileTradeHelper.MaxCountInBin) new MiraiQQTrade(senderQQ, nickname).StartTradeMultiPKM(pkms); else if (pkms.Count == 1) new MiraiQQTrade(senderQQ, nickname).StartTradePKM(pkms[0]); diff --git a/SysBot.Pokemon/Helpers/FileTradeHelper.cs b/SysBot.Pokemon/Helpers/FileTradeHelper.cs index c76e035bd..5ff6037bc 100644 --- a/SysBot.Pokemon/Helpers/FileTradeHelper.cs +++ b/SysBot.Pokemon/Helpers/FileTradeHelper.cs @@ -45,7 +45,10 @@ public static bool ValidFileName(string fileName) /// /// /// - public static bool ValidFileSize(long size) => (size == pkmSize[typeof(T)]) || (size % pkmSize[typeof(T)] == 0); + public static bool ValidFileSize(long size) => ValidPKMFileSize(size) || ValidBinFileSize(size); + public static bool ValidPKMFileSize(long size) => size == pkmSize[typeof(T)]; + public static bool ValidBinFileSize(long size) => (size > 0 && (size <= MaxCountInBin * pkmSizeInBin[typeof(T)]) && (size % pkmSizeInBin[typeof(T)] == 0)); + public static int MaxCountInBin => maxCountInBin[typeof(T)]; static PKM? GetPKM(byte[] ba) => typeof(T) switch { @@ -62,6 +65,22 @@ public static bool ValidFileName(string fileName) { typeof(PA8), 376 }, { typeof(PK9), 344 } }; - + + static readonly Dictionary pkmSizeInBin = new() + { + { typeof(PK8), 344 }, + { typeof(PB8), 344 }, + { typeof(PA8), 360 }, + { typeof(PK9), 344 } + }; + + static readonly Dictionary maxCountInBin = new() + { + { typeof(PK8), 960 }, + { typeof(PB8), 1200 }, + { typeof(PA8), 960 }, + { typeof(PK9), 960 } + }; + } } diff --git a/SysBot.Tests/FileTradeHelperTests.cs b/SysBot.Tests/FileTradeHelperTests.cs new file mode 100644 index 000000000..9b9eb4d74 --- /dev/null +++ b/SysBot.Tests/FileTradeHelperTests.cs @@ -0,0 +1,45 @@ +using FluentAssertions; +using PKHeX.Core; +using SysBot.Pokemon.Helpers; +using Xunit; + +namespace SysBot.Tests +{ + public class FileTradeHelperTests + { + + [Fact] + public void TestValidFileSize() + { + FileTradeHelper.ValidFileSize(344).Should().Be(true); + FileTradeHelper.ValidFileSize(0).Should().Be(false); + FileTradeHelper.ValidFileSize(345).Should().Be(false); + FileTradeHelper.ValidFileSize(344 * 10).Should().Be(true); + FileTradeHelper.ValidFileSize(344 * 960).Should().Be(true); + FileTradeHelper.ValidFileSize(344 * 961).Should().Be(false); + + FileTradeHelper.ValidFileSize(376).Should().Be(true); + FileTradeHelper.ValidFileSize(0).Should().Be(false); + FileTradeHelper.ValidFileSize(375).Should().Be(false); + FileTradeHelper.ValidFileSize(360 * 10).Should().Be(true); + FileTradeHelper.ValidFileSize(360 * 960).Should().Be(true); + FileTradeHelper.ValidFileSize(360 * 961).Should().Be(false); + + FileTradeHelper.ValidFileSize(344).Should().Be(true); + FileTradeHelper.ValidFileSize(0).Should().Be(false); + FileTradeHelper.ValidFileSize(345).Should().Be(false); + FileTradeHelper.ValidFileSize(344 * 10).Should().Be(true); + FileTradeHelper.ValidFileSize(344 * 1200).Should().Be(true); + FileTradeHelper.ValidFileSize(344 * 1201).Should().Be(false); + + FileTradeHelper.ValidFileSize(344).Should().Be(true); + FileTradeHelper.ValidFileSize(0).Should().Be(false); + FileTradeHelper.ValidFileSize(345).Should().Be(false); + FileTradeHelper.ValidFileSize(344 * 10).Should().Be(true); + FileTradeHelper.ValidFileSize(344 * 960).Should().Be(true); + FileTradeHelper.ValidFileSize(344 * 961).Should().Be(false); + } + + } + +} From 36511fd3ef2f88db5ca52f231c1d107db047ee8a Mon Sep 17 00:00:00 2001 From: Easy World Date: Sun, 16 Apr 2023 21:16:06 +0800 Subject: [PATCH 114/171] Update FileTradeHelper.cs --- SysBot.Pokemon/Helpers/FileTradeHelper.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SysBot.Pokemon/Helpers/FileTradeHelper.cs b/SysBot.Pokemon/Helpers/FileTradeHelper.cs index 5ff6037bc..487d1c196 100644 --- a/SysBot.Pokemon/Helpers/FileTradeHelper.cs +++ b/SysBot.Pokemon/Helpers/FileTradeHelper.cs @@ -18,7 +18,7 @@ namespace SysBot.Pokemon.Helpers /// public static List Bin2List(byte[] bb) { - int size = pkmSize[typeof(T)]; + int size = pkmSizeInBin[typeof(T)]; int times = bb.Length % size == 0 ? (bb.Length / size) : (bb.Length / size + 1); List pkmBytes = new(); for (var i = 0; i < times; i++) @@ -47,7 +47,7 @@ public static bool ValidFileName(string fileName) /// public static bool ValidFileSize(long size) => ValidPKMFileSize(size) || ValidBinFileSize(size); public static bool ValidPKMFileSize(long size) => size == pkmSize[typeof(T)]; - public static bool ValidBinFileSize(long size) => (size > 0 && (size <= MaxCountInBin * pkmSizeInBin[typeof(T)]) && (size % pkmSizeInBin[typeof(T)] == 0)); + public static bool ValidBinFileSize(long size) => (size > 0) && (size <= MaxCountInBin * pkmSizeInBin[typeof(T)]) && (size % pkmSizeInBin[typeof(T)] == 0); public static int MaxCountInBin => maxCountInBin[typeof(T)]; static PKM? GetPKM(byte[] ba) => typeof(T) switch From eab711ec02cd246a0f456dacc37c8922f8d22de1 Mon Sep 17 00:00:00 2001 From: Easy World Date: Mon, 17 Apr 2023 21:11:04 +0800 Subject: [PATCH 115/171] Egg support --- SysBot.Pokemon.Dodo/PokemonProcessService.cs | 2 +- SysBot.Pokemon/Helpers/ShowdownTranslator.cs | 7 +++++++ SysBot.Tests/TranslatorTests.cs | 10 ++++++++-- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/SysBot.Pokemon.Dodo/PokemonProcessService.cs b/SysBot.Pokemon.Dodo/PokemonProcessService.cs index 526f01ab2..72c995c46 100644 --- a/SysBot.Pokemon.Dodo/PokemonProcessService.cs +++ b/SysBot.Pokemon.Dodo/PokemonProcessService.cs @@ -15,7 +15,7 @@ namespace SysBot.Pokemon.Dodo { private readonly OpenApiService _openApiService; private static readonly string LogIdentity = "DodoBot"; - private static readonly string Welcome = "at我并尝试对我说:\n皮卡丘\ntrade ps代码\n或者直接拖一个文件进来"; + private static readonly string Welcome = "at我并尝试对我说:\n皮卡丘\nps代码\n或者直接拖一个文件进来"; private readonly string _channelId; private DodoSettings _dodoSettings; private string _botDodoSourceId = default!; diff --git a/SysBot.Pokemon/Helpers/ShowdownTranslator.cs b/SysBot.Pokemon/Helpers/ShowdownTranslator.cs index c1bc1948e..1e8dabc55 100644 --- a/SysBot.Pokemon/Helpers/ShowdownTranslator.cs +++ b/SysBot.Pokemon/Helpers/ShowdownTranslator.cs @@ -44,6 +44,13 @@ public static string Chinese2Showdown(string zh) break; } + // 识别蛋 + if (zh.Contains("的蛋")) + { + result = $"Egg ({result})"; + zh = zh.Replace("的蛋", ""); + } + // 添加性别 if (zh.Contains("公")) { diff --git a/SysBot.Tests/TranslatorTests.cs b/SysBot.Tests/TranslatorTests.cs index f84508bc8..e751f7c9d 100644 --- a/SysBot.Tests/TranslatorTests.cs +++ b/SysBot.Tests/TranslatorTests.cs @@ -1,6 +1,7 @@ using FluentAssertions; using PKHeX.Core; using SysBot.Pokemon; +using SysBot.Pokemon.Helpers; using System.Diagnostics; using Xunit; @@ -22,6 +23,7 @@ public void TestTrans(string input, string output) [InlineData("皮卡丘")] [InlineData("木木枭")] [InlineData("彩粉蝶-冰雪花纹")] + [InlineData("公小火龙的蛋")] [InlineData("大剑鬼")] [InlineData("火暴兽")] public void TestLegal(string input) @@ -35,10 +37,14 @@ public void TestLegal(string input) var pkm = sav.GetLegal(template, out var result); Trace.WriteLine(result.ToString()); + if (pkm.Nickname.ToLower() == "egg" && Breeding.CanHatchAsEgg(pkm.Species)) AbstractTrade.EggTrade(pkm, template); + pkm.CanBeTraded().Should().BeTrue(); (pkm is PK9).Should().BeTrue(); - var valid = new LegalityAnalysis(pkm).Valid; - valid.Should().BeTrue(); + var la = new LegalityAnalysis(pkm); + if (!la.Valid) + Trace.WriteLine(la.Report()); + la.Valid.Should().BeTrue(); } } From d7d6ff05ad5eaf81a0eda6003b15dcbf8607cb3f Mon Sep 17 00:00:00 2001 From: xiaolong11123 Date: Fri, 21 Apr 2023 01:32:28 +0800 Subject: [PATCH 116/171] =?UTF-8?q?=E6=97=A0=E7=94=A8=E7=9A=84=E6=9B=B4?= =?UTF-8?q?=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs index c213512af..bc80dd69d 100644 --- a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs +++ b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs @@ -380,7 +380,7 @@ private async Task PerformLinkCodeTrade(SAV9SV sav, PokeTradeDe { await SetBoxPokemonAbsolute(BoxStartOffset, pk9, token, sav).ConfigureAwait(false); } - if (batchPK9s.Count > 1) poke.SendNotification(this, $"批量:等待交换第{i+1}个宝可梦{ShowdownTranslator.GameStringsZh.Species[pk9.Species]}"); + if (batchPK9s.Count > 1) poke.SendNotification(this, $"批量:等待交换第{i+1}只宝可梦{ShowdownTranslator.GameStringsZh.Species[pk9.Species]}"); var needUseTradePartnerInfo = !skipAutoOTList[i]; if (Hub.Config.Legality.UseTradePartnerInfo && needUseTradePartnerInfo) @@ -425,7 +425,7 @@ private async Task PerformLinkCodeTrade(SAV9SV sav, PokeTradeDe await ExitTradeToPortal(false, token).ConfigureAwait(false); return tradeResult; } - if (batchPK9s.Count > 1) poke.SendNotification(this, $"批量:第{i + 1}个宝可梦{ShowdownTranslator.GameStringsZh.Species[pk9.Species]}交换完成"); + if (batchPK9s.Count > 1) poke.SendNotification(this, $"批量:第{i + 1}只宝可梦{ShowdownTranslator.GameStringsZh.Species[pk9.Species]}交换完成"); if (token.IsCancellationRequested) { From 0820275459a7f7f3501d3c2add0282fa7177c375 Mon Sep 17 00:00:00 2001 From: Easy World Date: Fri, 21 Apr 2023 21:28:55 +0800 Subject: [PATCH 117/171] =?UTF-8?q?Fix:=20=E5=85=A8=E6=8A=80=E8=83=BD?= =?UTF-8?q?=E4=B8=8D=E7=94=9F=E6=95=88=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SysBot.Pokemon/Helpers/ShowdownTranslator.cs | 8 +-- .../Helpers/ShowdownTranslatorDictionary.cs | 52 +++++++++++++++++++ 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/SysBot.Pokemon/Helpers/ShowdownTranslator.cs b/SysBot.Pokemon/Helpers/ShowdownTranslator.cs index 1e8dabc55..ffdc34091 100644 --- a/SysBot.Pokemon/Helpers/ShowdownTranslator.cs +++ b/SysBot.Pokemon/Helpers/ShowdownTranslator.cs @@ -219,18 +219,18 @@ public static string Chinese2Showdown(string zh) //添加全回忆技能 if (typeof(T) == typeof(PK9) || typeof(T) == typeof(PK8)) { - if (Regex.IsMatch(zh, "全[技招]式")) + if (Regex.IsMatch(zh, "全技能|全招式")) { result += "\n.RelearnMoves=$suggestAll"; - zh = Regex.Replace(zh, "全[技招]式", ""); + zh = Regex.Replace(zh, "全技能|全招式", ""); } } else if (typeof(T) == typeof(PA8)) { - if (Regex.IsMatch(zh, "全[技招]式")) + if (Regex.IsMatch(zh, "全技能|全招式")) { result += "\n.MoveMastery=$suggestAll"; - zh = Regex.Replace(zh, "全[技招]式", ""); + zh = Regex.Replace(zh, "全技能|全招式", ""); } } diff --git a/SysBot.Pokemon/Helpers/ShowdownTranslatorDictionary.cs b/SysBot.Pokemon/Helpers/ShowdownTranslatorDictionary.cs index b5f20a50c..e93493f7b 100644 --- a/SysBot.Pokemon/Helpers/ShowdownTranslatorDictionary.cs +++ b/SysBot.Pokemon/Helpers/ShowdownTranslatorDictionary.cs @@ -39,6 +39,58 @@ public class ShowdownTranslatorDictionary { "速度", "Spe" } }; + public static Dictionary ribbonMarks = new() + { + { "最强之证", "\n.RibbonMarkMightiest=True" }, + { "未知之证", "\n.RibbonMarkRare=True" }, + { "命运之证", "\n.RibbonMarkDestiny=True" }, + { "暴雪之证", "\n.RibbonMarkBlizzard=True" }, + { "阴云之证", "\n.RibbonMarkCloudy=True" }, + { "正午之证", "\n.RibbonMarkLunchtime=True" }, + { "浓雾之证", "\n.RibbonMarkMisty=True" }, + { "降雨之证", "\n.RibbonMarkRainy=True" }, + { "沙尘之证", "\n.RibbonMarkSandstorm=True" }, + { "午夜之证", "\n.RibbonMarkSleepyTime=True" }, + { "降雪之证", "\n.RibbonMarkSnowy=True" }, + { "落雷之证", "\n.RibbonMarkStormy=True" }, + { "干燥之证", "\n.RibbonMarkDry=True" }, + { "黄昏之证", "\n.RibbonMarkDusk=True" }, + { "拂晓之证", "\n.RibbonMarkDawn=True" }, + { "上钩之证", "\n.RibbonMarkFishing=True" }, + { "咖喱之证", "\n.RibbonMarkCurry=True" }, + { "无虑之证", "\n.RibbonMarkAbsentMinded=True" }, + { "愤怒之证", "\n.RibbonMarkAngry=True" }, + { "冷静之证", "\n.RibbonMarkCalmness=True" }, + { "领袖之证", "\n.RibbonMarkCharismatic=True" }, + { "狡猾之证", "\n.RibbonMarkCrafty=True" }, + { "期待之证", "\n.RibbonMarkExcited=True" }, + { "本能之证", "\n.RibbonMarkFerocious=True" }, + { "动摇之证", "\n.RibbonMarkFlustered=True" }, + { "木讷之证", "\n.RibbonMarkHumble=True" }, + { "理性之证", "\n.RibbonMarkIntellectual=True" }, + { "热情之证", "\n.RibbonMarkIntense=True" }, + { "捡拾之证", "\n.RibbonMarkItemfinder=True" }, + { "紧张之证", "\n.RibbonMarkJittery=True" }, + { "幸福之证", "\n.RibbonMarkJoyful=True" }, + { "优雅之证", "\n.RibbonMarkKindly=True" }, + { "激动之证", "\n.RibbonMarkPeeved=True" }, + { "自信之证", "\n.RibbonMarkPrideful=True" }, + { "昂扬之证", "\n.RibbonMarkPumpedUp=True" }, + { "淘气之证", "\n.RibbonMarkRowdy=True" }, + { "凶悍之证", "\n.RibbonMarkScowling=True" }, + { "不振之证", "\n.RibbonMarkSlump=True" }, + { "微笑之证", "\n.RibbonMarkSmiley=True" }, + { "悲伤之证", "\n.RibbonMarkTeary=True" }, + { "不纯之证", "\n.RibbonMarkThorny=True" }, + { "偶遇之证", "\n.RibbonMarkUncommon=True" }, + { "自卑之证", "\n.RibbonMarkUnsure=True" }, + { "爽快之证", "\n.RibbonMarkUpbeat=True" }, + { "活力之证", "\n.RibbonMarkVigor=True" }, + { "倦怠之证", "\n.RibbonMarkZeroEnergy=True" }, + { "疏忽之证", "\n.RibbonMarkZonedOut=True" }, + { "宝主之证", "\n.RibbonMarkTitan=True" } + }; + public static Dictionary languages = new Dictionary { From 1e54423f811e6b21c1481caecf7e929a04089866 Mon Sep 17 00:00:00 2001 From: Easy World Date: Sat, 22 Apr 2023 15:37:39 +0800 Subject: [PATCH 118/171] feat: add dodo in ConsoleApp --- SysBot.Pokemon.ConsoleApp/PokeBotRunnerImpl.cs | 10 ++++++++++ .../SysBot.Pokemon.ConsoleApp.csproj | 1 + .../Settings/Integrations/KookSettings.cs | 17 ----------------- 3 files changed, 11 insertions(+), 17 deletions(-) delete mode 100644 SysBot.Pokemon/Settings/Integrations/KookSettings.cs diff --git a/SysBot.Pokemon.ConsoleApp/PokeBotRunnerImpl.cs b/SysBot.Pokemon.ConsoleApp/PokeBotRunnerImpl.cs index 306fd0dcd..0d9ca04fc 100644 --- a/SysBot.Pokemon.ConsoleApp/PokeBotRunnerImpl.cs +++ b/SysBot.Pokemon.ConsoleApp/PokeBotRunnerImpl.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using SysBot.Pokemon.Bilibili; using SysBot.Pokemon.QQ; +using SysBot.Pokemon.Dodo; namespace SysBot.Pokemon.ConsoleApp { @@ -19,6 +20,7 @@ public PokeBotRunnerImpl(PokeTradeHubConfig config, BotFactory fac) : base(co private static TwitchBot? Twitch; private MiraiQQBot? QQ; private BilibiliLiveBot? Bilibili; + private DodoBot? Dodo; protected override void AddIntegrations() { @@ -27,6 +29,7 @@ protected override void AddIntegrations() //add qq bot AddQQBot(Hub.Config.QQ); AddBilibiliBot(Hub.Config.Bilibili); + AddDodoBot(Hub.Config.Dodo); } private void AddTwitchBot(TwitchSettings config) @@ -73,5 +76,12 @@ private void AddBilibiliBot(BilibiliSettings config) if (Bilibili != null) return; Bilibili = new BilibiliLiveBot(config, Hub); } + + private void AddDodoBot(DodoSettings config) + { + if (string.IsNullOrWhiteSpace(config.BaseApi) || string.IsNullOrWhiteSpace(config.ClientId) || string.IsNullOrWhiteSpace(config.Token)) return; + if (Dodo != null) return; + Dodo = new DodoBot(config, Hub); + } } } diff --git a/SysBot.Pokemon.ConsoleApp/SysBot.Pokemon.ConsoleApp.csproj b/SysBot.Pokemon.ConsoleApp/SysBot.Pokemon.ConsoleApp.csproj index fef39d053..6f527fa73 100644 --- a/SysBot.Pokemon.ConsoleApp/SysBot.Pokemon.ConsoleApp.csproj +++ b/SysBot.Pokemon.ConsoleApp/SysBot.Pokemon.ConsoleApp.csproj @@ -9,6 +9,7 @@ + diff --git a/SysBot.Pokemon/Settings/Integrations/KookSettings.cs b/SysBot.Pokemon/Settings/Integrations/KookSettings.cs deleted file mode 100644 index 94ff7bfc8..000000000 --- a/SysBot.Pokemon/Settings/Integrations/KookSettings.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.ComponentModel; -using System.Linq; - -namespace SysBot.Pokemon -{ - public class KookSettings - { - private const string Startup = nameof(Startup); - - public override string ToString() => "Kook Integration Settings"; - - // Startup - [Category(Startup), Description("机器人鉴权Token")] - public string Token { get; set; } = string.Empty; - } -} \ No newline at end of file From 3625db56cd6c6755abdd9f3de495719b083cfb7e Mon Sep 17 00:00:00 2001 From: Easy World Date: Tue, 25 Apr 2023 12:57:55 +0800 Subject: [PATCH 119/171] Fix pa8 size problem --- SysBot.Pokemon/Helpers/FileTradeHelper.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/SysBot.Pokemon/Helpers/FileTradeHelper.cs b/SysBot.Pokemon/Helpers/FileTradeHelper.cs index 487d1c196..f8e71a0bb 100644 --- a/SysBot.Pokemon/Helpers/FileTradeHelper.cs +++ b/SysBot.Pokemon/Helpers/FileTradeHelper.cs @@ -18,6 +18,11 @@ namespace SysBot.Pokemon.Helpers /// public static List Bin2List(byte[] bb) { + if (pkmSize[typeof(T)] == bb.Length) + { + var tp = GetPKM(bb); + if (tp != null && tp.Species > 0 && tp.Valid && tp is T pkm) return new List() { pkm }; + } int size = pkmSizeInBin[typeof(T)]; int times = bb.Length % size == 0 ? (bb.Length / size) : (bb.Length / size + 1); List pkmBytes = new(); From db03695f5d9ec3b1093f2fb7ab5c8342ee92062f Mon Sep 17 00:00:00 2001 From: Easy World Date: Tue, 23 May 2023 23:45:59 +0800 Subject: [PATCH 120/171] update --- SysBot.NET.sln | 4 +- SysBot.Pokemon/SWSH/BotTrade/PokeTradeBot.cs | 110 ------------------- 2 files changed, 2 insertions(+), 112 deletions(-) diff --git a/SysBot.NET.sln b/SysBot.NET.sln index 126d7814c..e49fcbbea 100644 --- a/SysBot.NET.sln +++ b/SysBot.NET.sln @@ -88,7 +88,7 @@ Global {886B1440-B333-4095-8E44-D71D043D20A9}.Debug|x64.Build.0 = Debug|x64 {886B1440-B333-4095-8E44-D71D043D20A9}.Debug|x86.ActiveCfg = Debug|x86 {886B1440-B333-4095-8E44-D71D043D20A9}.Debug|x86.Build.0 = Debug|x86 - {886B1440-B333-4095-8E44-D71D043D20A9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {886B1440-B333-4095-8E44-D71D043D20A9}.Release|Any CPU.ActiveCfg = Release|x64 {886B1440-B333-4095-8E44-D71D043D20A9}.Release|x64.ActiveCfg = Release|x64 {886B1440-B333-4095-8E44-D71D043D20A9}.Release|x64.Build.0 = Release|x64 {886B1440-B333-4095-8E44-D71D043D20A9}.Release|x86.ActiveCfg = Release|x86 @@ -122,7 +122,7 @@ Global {C0525606-4ED4-4998-9AF3-31E2DE65717A}.Debug|x64.Build.0 = Debug|x64 {C0525606-4ED4-4998-9AF3-31E2DE65717A}.Debug|x86.ActiveCfg = Debug|x86 {C0525606-4ED4-4998-9AF3-31E2DE65717A}.Debug|x86.Build.0 = Debug|x86 - {C0525606-4ED4-4998-9AF3-31E2DE65717A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C0525606-4ED4-4998-9AF3-31E2DE65717A}.Release|Any CPU.ActiveCfg = Release|x64 {C0525606-4ED4-4998-9AF3-31E2DE65717A}.Release|x64.ActiveCfg = Release|x64 {C0525606-4ED4-4998-9AF3-31E2DE65717A}.Release|x64.Build.0 = Release|x64 {C0525606-4ED4-4998-9AF3-31E2DE65717A}.Release|x86.ActiveCfg = Release|x86 diff --git a/SysBot.Pokemon/SWSH/BotTrade/PokeTradeBot.cs b/SysBot.Pokemon/SWSH/BotTrade/PokeTradeBot.cs index 0ae11de5f..c9b79b850 100644 --- a/SysBot.Pokemon/SWSH/BotTrade/PokeTradeBot.cs +++ b/SysBot.Pokemon/SWSH/BotTrade/PokeTradeBot.cs @@ -438,116 +438,6 @@ private async Task SetPkmWithSwappedIDDetails(PK8 toSend, string trainerNa return tradeswsh.Valid; } - private async Task CheckPartnerReputation(PokeTradeDetail poke, ulong TrainerNID, string TrainerName, CancellationToken token) - { - bool quit = false; - var user = poke.Trainer; - var isDistribution = poke.Type == PokeTradeType.Random; - var useridmsg = isDistribution ? "" : $" ({user.ID})"; - var list = isDistribution ? PreviousUsersDistribution : PreviousUsers; - - var cooldown = list.TryGetPrevious(TrainerNID); - if (cooldown != null) - { - var delta = DateTime.Now - cooldown.Time; - Log($"Last saw {user.TrainerName} {delta.TotalMinutes:F1} minutes ago (OT: {TrainerName})."); - - var cd = AbuseSettings.TradeCooldown; - if (cd != 0 && TimeSpan.FromMinutes(cd) > delta) - { - poke.Notifier.SendNotification(this, poke, "You have ignored the trade cooldown set by the bot owner. The owner has been notified."); - var msg = $"Found {user.TrainerName}{useridmsg} ignoring the {cd} minute trade cooldown. Last encountered {delta.TotalMinutes:F1} minutes ago."; - if (AbuseSettings.EchoNintendoOnlineIDCooldown) - msg += $"\nID: {TrainerNID}"; - if (!string.IsNullOrWhiteSpace(AbuseSettings.CooldownAbuseEchoMention)) - msg = $"{AbuseSettings.CooldownAbuseEchoMention} {msg}"; - EchoUtil.Echo(msg); - quit = true; - } - } - - if (!isDistribution) - { - var previousEncounter = EncounteredUsers.TryRegister(poke.Trainer.ID, TrainerName, poke.Trainer.ID); - if (previousEncounter != null && previousEncounter.Name != TrainerName) - { - if (AbuseSettings.TradeAbuseAction != TradeAbuseAction.Ignore) - { - if (AbuseSettings.TradeAbuseAction == TradeAbuseAction.BlockAndQuit) - { - await BlockUser(token).ConfigureAwait(false); - if (AbuseSettings.BanIDWhenBlockingUser) - { - AbuseSettings.BannedIDs.AddIfNew(new[] { GetReference(TrainerName, TrainerNID, "in-game block for sending to multiple in-game players") }); - Log($"Added {TrainerNID} to the BannedIDs list."); - } - } - quit = true; - } - - var msg = $"Found {user.TrainerName}{useridmsg} sending to multiple in-game players. Previous OT: {previousEncounter.Name}, Current OT: {TrainerName}"; - if (AbuseSettings.EchoNintendoOnlineIDMultiRecipients) - msg += $"\nID: {TrainerNID}"; - if (!string.IsNullOrWhiteSpace(AbuseSettings.MultiRecipientEchoMention)) - msg = $"{AbuseSettings.MultiRecipientEchoMention} {msg}"; - EchoUtil.Echo(msg); - } - } - - if (quit) - return PokeTradeResult.SuspiciousActivity; - - // Try registering the partner in our list of recently seen. - // Get back the details of their previous interaction. - var previous = isDistribution - ? list.TryRegister(TrainerNID, TrainerName) - : list.TryRegister(TrainerNID, TrainerName, poke.Trainer.ID); - if (previous != null && previous.NetworkID == TrainerNID && previous.RemoteID != user.ID && !isDistribution) - { - var delta = DateTime.Now - previous.Time; - if (delta < TimeSpan.FromMinutes(AbuseSettings.TradeAbuseExpiration) && AbuseSettings.TradeAbuseAction != TradeAbuseAction.Ignore) - { - if (AbuseSettings.TradeAbuseAction == TradeAbuseAction.BlockAndQuit) - { - await BlockUser(token).ConfigureAwait(false); - if (AbuseSettings.BanIDWhenBlockingUser) - { - AbuseSettings.BannedIDs.AddIfNew(new[] { GetReference(TrainerName, TrainerNID, "in-game block for multiple accounts") }); - Log($"Added {TrainerNID} to the BannedIDs list."); - } - } - quit = true; - } - - var msg = $"Found {user.TrainerName}{useridmsg} using multiple accounts.\nPreviously encountered {previous.Name} ({previous.RemoteID}) {delta.TotalMinutes:F1} minutes ago on OT: {TrainerName}."; - if (AbuseSettings.EchoNintendoOnlineIDMulti) - msg += $"\nID: {TrainerNID}"; - if (!string.IsNullOrWhiteSpace(AbuseSettings.MultiAbuseEchoMention)) - msg = $"{AbuseSettings.MultiAbuseEchoMention} {msg}"; - EchoUtil.Echo(msg); - } - - if (quit) - return PokeTradeResult.SuspiciousActivity; - - var entry = AbuseSettings.BannedIDs.List.Find(z => z.ID == TrainerNID); - if (entry != null) - { - if (AbuseSettings.BlockDetectedBannedUser) - await BlockUser(token).ConfigureAwait(false); - - var msg = $"{user.TrainerName}{useridmsg} is a banned user, and was encountered in-game using OT: {TrainerName}."; - if (!string.IsNullOrWhiteSpace(entry.Comment)) - msg += $"\nUser was banned for: {entry.Comment}"; - if (!string.IsNullOrWhiteSpace(AbuseSettings.BannedIDMatchEchoMention)) - msg = $"{AbuseSettings.BannedIDMatchEchoMention} {msg}"; - EchoUtil.Echo(msg); - return PokeTradeResult.SuspiciousActivity; - } - - return PokeTradeResult.Success; - } - private static RemoteControlAccess GetReference(string name, ulong id, string comment) => new() { ID = id, From 0e43176fb139a79773570247f4071d4ceb5795cf Mon Sep 17 00:00:00 2001 From: Easy World Date: Thu, 25 May 2023 22:43:49 +0800 Subject: [PATCH 121/171] Update PKHeX.Core.AutoMod.dll --- SysBot.Pokemon/deps/PKHeX.Core.AutoMod.dll | Bin 124928 -> 124928 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/SysBot.Pokemon/deps/PKHeX.Core.AutoMod.dll b/SysBot.Pokemon/deps/PKHeX.Core.AutoMod.dll index c3ea6a92a3332780830c33f631c9225f0e39697f..20b80d4dc3def6370cabb78e2ecd6f04ba2f7bfd 100644 GIT binary patch delta 25950 zcmbWf30zcV7e9WUJ9B4&VTZwC8}`MOeNoY2Q&9;~R6x{0P;)8GElKl&m}y!`m`hed znrT{UWNKM2q`6h5W{a(mW?5ERR$8XNbI!e}_5R=Y|NegU@jK^x&Uwyrp7q|j_Ze!A z8*7dmS1i#VEjzuretD3XQNLxR2x$-^Mh?9qptvDS>e^5$V#MKwTA|p6UKK$94ZwdA z$S~^&yZGmw9&}xHjyMi!nHnm==WQhJl^4-9u2AW=H5|F~o*8Jki-M!Wv%f+&MWM zb)BWy2>N()W+Z+@1=xe8MUh~$2dhFSdx$F7eeL1Xq8d7N$rjNKqq_7LF%28Kq!?n+ zJ2tN2i!MQZyP#O)uAj-D#%W!V>ZXyshh*z)wOHM{C_3}BZm5Worgg`UnO_1JeDw2r zpgb+UA){-El7OO@9Gi{?FN1%QSsvo0pO#2UL#HGccYOynnx`e9*2gG$$zDdQby_kq zlYfcD>|f%BokP|3oqglR4JAKYp5ID_u zF!t|f^1qev|Ia1!n=)B(y+p)MI;Z>U*I% z)=$-szD?g7^<7fDh282OtA(q$&HUer#U^E9VQJduR3(YAeGpt`JdEWHv3*hQj1$c# zUJpt58AlT?*3rYdQuEE0R^MKZ9AYE>t=vv3 z=W3YoF#2*RZmJ>H3!^qK8UBW3&}P`+k^mEldc(|3kClcrEHCLMdNjXUViO{>;Z*5R z#F(-;k=a~SHcKbs8xD>>K9F0io8hnPXqZkLJRPV0Oq9)A7pSGHn-Ou#&8UvC=4Oe_ zY-k&kCej-^jZN|6nV>UGbK>-gO~f?4(XCctMz*Vg=Syb8oFm0Cv|1%;+Vhy{$(iIF8!W)!Doxvp8jHU)Z5w{#HgM_4W8UN& z8;ZQ!BwCxe)SDkanlFT-`OCLMv7;Y*Hx@IGdv~+NlQRchY#|LPU^4Bew@V4};{hUu z&6+Xk?uj#TR>I5g0Q7c8-SA;>@e=?~)J60ko}Wv`PjT;TX#MN~WnY%Ms~Vs+}f7wh_Ea8;gL@yHr<|P0Am`NeUeEI@6!`QM3}n9xWJ+$ zE)Y%nFIiJyAkCNx()^S|rG}N0=0+Le)d7V0;bgU%DZ-`xr1^F*X*!w6|Emytn&J+2 zHusMq^RfuSm$C?-8b>&86ydr`!Y)?A-!Tan_#u!mAc}An<9pa90~EMZ=$CUz|6Lwi zM-v97628pINiHOBAB(8K>|i3lB@o78Yh1v~pYT(zY>FSrHalT4C-(tsPH_^;xZ-~7 z|GSBN9xWxbU}Frhgj=b&k!Dvh;pf~d3w%j#>q9=uARhH<(=Wvac*#P!M`AY`AbJ3e z)|^zrqbW3&1%1h9nCbA4nZk^hRRc_6pJvn;;ERO2aDHsT8DoIga5&td-9p`djGN?( zy=VY;?CGtLg45OjV+T{Ix0C^|_IH62Z!(`7N%FW}l-*Z2axRcwMA!opa)%#q)NxvI ziqUExM3U9goA4v=g%&7@-IMv$wEh47&QLidq`8JUE~cy90D&U_cg)f*B_g#!(+$%V@9h$9~d4@wiw zaH!zbG_Eg4;5Z5XB-?0RDA1Fa#}|W0b(HaAUTkCUAWb*MrCg8v@-$(eY|Jb??<0G)lP%8gus zhm}0f@fz{tcAsER!OicmLza1Wvj zwDXitRp);(E-eFmlKdWSJ_oS{23V6z)qKM<;9xRo#^oGz;ws=3QDh*^H!2dyb!gJ}56=#A_G#ZZE0#f{9L&gX3 z@|%gWo>?k09e(AhMccrtu!MZH7(ig8oW!t%rNRe>vaMzW*#eM}E!#=904Qc#UMbna zkde(%PPPb8%GkK0fQ+%AvT}BfgUM(E$Y9nP=3;uD@LL&q_JBralQW5>!WInb2?goI zdckgvxsH<=0_T{0=^)z>xWKGn2(h7XMNwTj%rN+agZd69V;P2|>K7Ih8wH(^dBT)D zVonHSTMrI14x-sMf_r8>bYu4XF!H4}hI`Hp6ZVF;&cz}AbS}`t9z*svSftr)-*o7{ zhKDp=&Nq2tNyE}_0=pc-E9O%1JG`J;xU3|QVNE@7oN-9S4z1LC-we~DcM``nUBb7k zl@Y^!wnjs*-oGLKtKU_t7kG4S5nk{{55kwa5ng9`6=OByaP|rAo?`TZsxIX72y4c7 zzlITVx|5~{Yjo_N8-LYmFszHZY8_GYM+*3ls4+STzfvOUhatRX{GY}KNkZJPwf)Ct!z~-L> z-6KN~pC7J-V~Dz(&i<)jDj|7gVNaC3%KP|-!IduAh%MdwAr9#_5HUQy&_5am6t{?I zP!I(?*{#H1KybIwh_kwmL;NaUk9Y`sML08=I&Rm0zT|U_*MK> z1wSS-*%?m-ma{pXH7|03i`{-hpKD#Iz#R5jz?!eR&hig~)7?W5e?%1E=+P6=(4A6$ zttiDXGWnJ92&1r4&^6gB(DjA1m52p> zQj7@@U|oyyOTL6tN18k^XwlQyTPsFxM2$0l3*tEED~Rh$_F;!tcc%H#)P=Ao0(^aAAzp1qGFNfLeBKJrfz`&L>*Tg5=aJ%f$$j*!X^%EG-`xXJSm#3C!n zwVlZG6FXsv{V&8^H5%8YxGLln7)p2#XL^GJb@n4og$D&HXX_}g<`?$q31kiB6xVXl zH~cB|Lo)TmB6qU(;ntt=kUAxaD-`@_EcNrZ!rI=(PF8pm zF%{modZ9c=SP?fELJ@zIIwLNVk~$fnE`5c+0Ro-<5VJ=0L5;lOO4@rn444ttU zRq)T`9!AY^-D7C&-ggDg|M#uyP~)j<_n5DMu46yIyy{{|ey8XQtl$u$fIRcpD3hm((QsqTg-+3Mfid2B0j=L8 zrxzo>#a~>BrLZOzU3Z(`f~3G#8|l-V!OE;`@iLFg`Z(B zqL0PH#{m8N_rzh!8&^ofr||fx#5nN0w*g`*dRnQPGz>9J^hVslD5N|NcL!=7*A3&e zR`?GIr5m8dohBC2O;E{Ab`sh2n~a{^DmNL~9?_UGu~0_(6w_6vqUhP^x=~|Q(bdSd zX)Jg6Y?}nl8Y>-MjqEjzg=fvS>EKO`Wn@(&JF2lhBWBz5a6)6lM^qy_t+5Z3VY6*A zv}yF>uxe!AYHSzU@HvFW-a?xJe%DxW;%u7{u4`;UVl^^D7~p@aOndU2MiaPc>_pxI zn+g0h_CmyLn;C*M_C|zKjWklDU6W_q@QH-R`X*N+OVU_bzdD;M^wij_eoK&LYb?0m zY?}r0HI~?~8d;IXWK7fzN;DRNiMoMPW0Qx>R&4GtQKPj(s*z3WV9vFE9xz8^4?7>R zc|eWIAU(ao<_V!u++NsM^^DCAmNKgcOWanQKWt{!07cH7wob4EZ;b%z;qc%CwjhX& zRoRAq$83rnYBV}G?zAlo%JIo7b|S>beQk?^;~MLSECxE!6I!(W7W=g=4yI~sDcZV# zzKd$xQuc$b8!Td04@v!hwe^5+H5N1cx-9{GyOC!-)D4=VOI6^yM*WK|0cqgdT@ACn zw@*L@R57cEs$ro4Jt4A(TCjh=&HHHiYW=1J z41?(zWLpJqMb8fy3B7x(o(KFE2b95+8Uz0o0p;MIsd_HSd?H{h;CB%GuO3E3J&o)g zrLRis9a{scAk?8UuhiWEcS3oV%JwAuD_|1LWwr`@98+{tAU<2QeOPoPU@GWyh^>Mz za!&-@4KHgfGH;4*8aVjbm zuNu1dC)NN1rNgFbNY~f}!(r1rC}*|`?oa$J;C@)6F=_O*fCbQ`GSdsT*)|0>YwRss zHL@L==a4KZumKzcc3~Fw&u)yW;vc~#lb`E?5rVmnUunZg;_&CJkD;BJO6`U(84O|U( zBBhZzS2jNI33yp!C(C9AJ`IuhO@nOjXOGu4!477t;Oy81fzQAN%{Hg>;lSr0<_`Ac zw6{PHW(^P}0(`eXrp9I)0(`f^AYux3aJ(hJ_XQ|t<0@zwv>UcVi^h&uJsr3cPBE*8 zg=wJyyC5DP9#b;jHSGCd4zVhsOpT z2|NJh%#?b#p811(5RPlE#|lFO4ngh+wcwS)cwW?}m3^GqZY_HGGQh7`!%; zE9CaR2k-oiwcvL@O072Mbl`h%mRUU{C-?-MfaK9^Q{Y1Rk${s>K1Ovtp7UkkDbQD_ zY(nMtfgeM;#!e5NqWcu?)7Y+%-vU2_CpETm)b+qNxLm20JKcMV?hN==scdF1W6)XH zsIfEoM*_Y9&+)2F`KxG(?mW?!un=5$0;_VDWCa z1g#o#8{!xA3mn(ju3W#MUt#4$wOj?(@H<>&Ru3oggM+R>`JH4#rsPKkU4;pgRM(}^ zodd4H8jbx{*)8ZgT+`USXcMAwvg&!)P)mSLSf;4#Wbu&zStK*7hXB8{Afu?#Z0GzO zL1yup#>OSz5#%PEQ`K_%1f{dlQ!KwrrGpAa1bK;r2Q{X&Wmu^PLS`npSmNYL1(#7#kw zB12=%i93;HYiwiH>p@XspvHDoy@_m?#&$);L$o4BY4l%Fz9L#wX>4X?Jj94uDucU= z4+q7F=`(J%B33-bFQn8vzgV%2**UYV=qSXBmsOi7swhv66|ZZyKb-^RIPsQdGgh1k ziWAB)%@~+6L39=$YR1?UPv|VZ)NEr1HA5Hijb@uXNJe&1vl+6Rp{ux}*?h8PWMUR2 zdd|GDsu>>~nVBs%?WmHGd26B!~jdR+=Uw8=={@B~K8EVvJ_no9qdRV&W{m{+%;FpM4CH z#5B#=oP846T&-YpYBMB@8qIbnRYtZ{vn{BIhZIq-*;Z8eiWKpLW*gx+2C3p1%{I|- z64_SGmX%tgPZQ0v`1-flbVusPL1|*2W_-19l`dTz(Ac-k-qBd;&|e{4oMN`vR5|oZ ze53lg#`@)c9h4!y*4U8T?~whdv6IdTqNljRtQObE#h{)dat>erXbN2k$`l_XRliC= za9@#pFR^-fv(OlvB^GL|X{bkVj@ZGhmX}kWxPPwdIX>1uI8Ur(Rtxt^RmT2eGqY9j zdv-|h0AZ;n&sFg7(5T>nVhJ$?Y0IE4!GpxBn(;?xy`eyStT8A}4!%QlzE3R(rP;xS zB3)zM^9zEDMB_ZwR#Z|JTrB+OtL)IINx>sTjK;QA&JQjT@i*DR;8Fqilc%x@Td^d# zOqdp^1-Df`8aztu(^yO5y5P~`0?!h>X^Pwy>=esuRL{JWZNZh|ipHezuLVyKEw!rc zM$Z1=Nuqe6%D#v>5gN*3B0!8oS@+BFqNIW=FKBF6=mOg&u}fpK zG3aw*pT-`iy}&67qEpdig=a5mg3DJ+lBKhs-hm0p)ZBJBK`w^Ce~;yCFR?YeIogsy0dY?H$=@j)wQNitJ@pmIgQ|f%z z#(p0AQ%H*l{f0eFOYoV{n<7SIYw?-To1%xt9;}>g+b>czwz9GsSs#sk5H;I&K;(VH z*S}S!ucE4v7HCF+1>X|I8uP@0Z;5i1f!EmILJo@Q8uJ_XSI8mpgvOq!(A$rQJvW($ z{iqmlo)WExZ^J|E?}-!4R>8dmUF{!I(m77Va|En+XIY<=7a z`w!x*#(vHkYriN?{Xn)=U>-Nc{)@2usIrVL!5Y(N&9(m~US>vH@B{WM;wUq9 zQT`!5B&KN3?*0&8u(1wzu7~Y^h;KFaf}z3wr!fCSRn);1X|4U5@YLAj($n_8gpHYc zA9!6vYPP@h&)TnxHGi`;YPOmBt!QgzrlJ)fHzt%2G zA2L%S80ak8KB!^ca<7$^R!6oZ}YTB z&uN~K#ww#ldigd_H>st=Q^BBa(qYZjMVer7lit70)m{3s!xcTcsA#2oZYwkE?_m$3Q%%^Y+_(|V0Q&aPkeqmMz z&*~c#yPqU}r3zJ}RWdWHgIvtpD)}=zV7{~LBk-3ZnH_}a%n8C@ny%VJXvqh5f9V*r zRi+24KC^d{KGE1bWC7Aety~)3Uj#`*f1~;ig4^(Ob|px1s#LfQ|K1)fozR$opY0*i zX=aP)4jGyJ`>jOnQu^PRgIOJ%(BH7zr4nZ9WDAw5Zu1P4rfQzPdP8WaA}zSh7$z;& zjE@@E+QX!6%+xU9(w^Hq!=)C@GYO|`xO7lsQ;gBB5z;#vn_!7{jg(Gl?6|vIXq0rd z!{+TB8Z9aME7YbsNRqeOW29J(T`+I8$4Ujv)av7;QfBHf#Yq*K=QTLy9VgwRv0zK2 zPiN`D+dR8Uk7_m#w`kX{(z6ysoR(rg3 zUSlu$ZM7#ze`xGS>sEWBZUP&jF}?!VWu`BRqA(@ zuYYxL9=F6)srWWynpCb8+-&^OJ574{Hd}_YMzi_4XS!rapEFbYsi$=QHqV~YCC#&3 zbPDY$UDw##$a+bxfAICM4(w8JXm2Uz56W8&lPM)JQ!~w!dNHem^}6uTOsVf}wmwpS z&9=ul!O};<<1O$>um2U5|J_yf40O^j1?tiPbog$Nj?=iudV0$E3X-ZMy@Yy_v&^?N z8wQg9^b$``z2;e#OP<@50y2~%5io;8TT{3&hLG@;8(DL)upTG`2ED2O-{kInJK_j@ zjrUJEYZQ*Z4mRPlUOiVP<4aVkhFl3f@icg6Zdr$hLQoPq_U{P#K1R4zszdH*Kz-pY zjaqhk0@b)J6~|w`El@`|wcTnr-pZOf{8k{8XQ%aVKBKp6iH4K05el z&@BxOlWJ9HEUDQ3v<{U7lQ26S#H3P=RJbF>s~8l|wX>Dd(!&g`pKg_cFk&O%3}vfhOJV zESjYMo}AJ#mgF=vjskUrAelx^(q@AycPXX5SyD(zUn-&B%(#_$dK%!Pv7Vkrc#1nV zYZT3!R*t5YqDJaqb>^*@YQ1$zItBXwFx(uWjwG}K?7t@a){=WMno|5|EQNLyk^D-| zf0Nbu)G?D3DsX!ryo;mTF>yNd|J3}u&rYn8RuH9iXYarJ3p`0fwWyu1%Ids9na=9n z%BW*e#o$x@j&8qY?U+`#x00+K5pIpH1Paf2N=Ti;9X{$>xaEoWKPPaUBzPB52S*U~ zV7YBAPzW73Vmqqg*@;zB-$<}{T!+4+eYYpn(aGfb=WWaHwtiJ^YkfzKVfcQBWST@B zGHKMT!?MAeKr4%E%=Q=cMA5d4T7q;hc&*+fVd877|YZ=aeNxS59 z9j^xcyKBcpTX}Py3!oLRq67=?Ai1NrXp^B0r=u@Pf4hu>%VpKMLaE^Vr>n9A63=9H zx1trS!{as-+8>8T;x(-U_0nwSAya)+jk+MItvYDr%e30=<-CscIEU~`??TDZ6$HN- z-qI+VYVz2BI?)7_a01>cwuCREG$qwZ`hP!g*#kX21r7=|Slyqb?lT6m-?9-j*|KHo8 zZjg5mC%qcFW4oX$Mee`(|9w9HGunT*^8Jqli8y<8VCOwfou8^^!$?~F5m=?nH)nVN z(@AZ5iu;MiSb_>34w?bfv9~m|%GFDD$Aw$nRsWF??f)^E>%lWx8qigspxszk$LC96GZ55)NVUf&s)2v(s74FPIQn?LRWPQ zQrE&Q*Z<_%kp%UV#LMshF9~&6IbB8DAZa1}eRKU!J)y3CHN;8o_6fYG)cxajRJZoq zHUBOT`2j0dHyJ#BH=gAG1+#gT|GVe%^!hMg+*5A1gz~`V@%3^O-@x83zs_?>;`?Xy zt{{&G|Mm#~yG%C@LV+P>>dhOLTg|z>FaCF#JUfP$L&KQ-TQSwN)ZSN<&Ku_irLg+A zRrKQrOI|?7>uiVu!Vp~`0nrmO7_%7j8H*T887mkkG2VkHU@4*>G)WT+BjAKY57$me z^ia*LW2>I@YxV7E0-E&n5XTqVlrat@eepR>lfDg~&jdkY;c(cZPsHaj)f}`5ejV@v zG{N$`X8f%|1ipxAf;D-Vt{gLbH-yR2K_sCDD&Q(zLN?JVJBM%ICKwJfSQ(L4bYyxUczMfiEEl;j^ z7_Ic?wh3Bt1k}nsi`NJ*ae5^F#zKB*+y+sL-M$-fEgrFNf>#GNgH5a$+l)V#SQbu6 z&|B`EjORs#xfzpqKdTv(OW>V;63yMqnxS5Xq_0J;*7^m#|HM9+JT4GtJq=1R_d$Y? z@fCcjJg+KKN)U&8O%Pk;^JzIKe>!-eRL*7V<);3n(o`P4dil!m5`(Gk1xe8>53|jVRrPky;Ua75qT<}bGSzw!P5uT;-e7ConE(}kaH@( zkWS0@R-ToXiqg@SrFz6`7|xf+_2+)nR$YG?uF73{hw8S7?y0f5oj`BLn_zMr_3P-6 zWSuY+DJzce+iPHq_cmM%Y z!M0`@!uo%V{_Xu_D3#rYU)R~>_P{Bqp(Lt>n##pOqrMbnH+{Rf9)nX)Y#n$EiVTe* zKKiSobd(aTU#nXa9-(i7@gWy6bQYdQs4(;$+Cx7{Pp|)N65%AnsMN*!4CzZe!%~XH z2s6eWgA5$q&H8p&72Be}$B;JsfPN=Xk5(JjM78N_4N+BJ>uqA}pkwGy16PE-`-8sT zu+zmThe9gQqW=@C+l3)=W&L3Ft*|6VMyqCZK~NrGnmn zHUZ%lLw3>-d8eUl+%$PP)MEE8hx}d+sK9Efz-f<&}ZJMbhNF+7G?Z5R~UDxbi_56Y7;$}z+h@ww-%u@Rdnhuz6BA@; zg2!=9)=C2``O$`@QlF?Uh^E!A2Z>-MrJ&GYHqsJlf@lZT@IJ1MrBdjyF@|kg{+6t} zF$SH8vB{HC>M=I&6++$N-j1aCl7oZM1Vxxdy+p5=n|Mp|GI|HCGt^7;nz>%07t!?+ zy{b0r$;W0KrNnJ9nDt~f>&a}^llf(?__VZmz(Io$6AQmE>;qf50}en~`gOwrI5E~_ zJOKH9t;TxUI62B2sbnjrr`1Jv|kNb^PD6f-Racn%{6gY8*%aFa2b{aF} zDbCl7pRwmz)?dKUI%{m@)e@(BvFJPFW!B>?D-rM+Z$@V|pHC{TV%fw(gXtP=a|&Do zxs^|l(-0Z6qvKyYdCRcL3$iUPUZziCf57d44uUnY)n*FKw!7F&BeJ6q zdm@IJ8qzu=whoAQ>5Z)s&|7SZ$#ltOc`)Mr#$47Ei-|+9$8Cl?5~ia0%J58=1XIGm z#}VHvf5N3+C#SAQq>D*4U#IGIzEOKHIoBYX6l*F4tksR={r&Bb*Ih0d?g}}u`u9)~ zqv(1cgI3+$xZk$wY7w{S_Vl{w(xjuUYKv|&%d~mXRraz=f??`F;aVy`h}Y(P(_2YF zu1)X=o;<_NvR5?9mr&lRvk%3uK$!iPa*=6cVL#V$9G!DAO{`(AM|ABGxE1Q24<|gi z`l&GC@3SiLB$VpuoLH%8u4B6EY2C!thr&cJUSoa} zY&ul-mupu+w;EO0*l?URbSN!DZtd%B!4AQzY`tzEcIi%2d}$BMR6!flEc9H5JFX$U zz_Oh8nWbV`*#t{1uf}@WY@ce`YwADjK}KbzrPXxbuyvND(wX7UTaK6xm+Y|2=Mal{ zMc4C6U(VHM$cxH;u$(Ym$#HdC$y3@3^`UMVcvmbSL%%g8-mRYB7(OXxjB~(~BEMIu zo0sl<+8DRX9Op9c6{Yg&+-JEK6v0E0qt8I_S1dOQ`?2k5SW*O}BF!`&1dX7@o~Bx^FR@&31RUiO8gQ zunB*}m)U&ftJP&`Sc#10sll>C>R*#GnVSH;=tEZdiTD~sT z@&&1u#}}){%Tg_mcr9O?Fb%vbI&GArW}7dWmh|oKVV2vn3Oz3ImEtoV-_NiL-1%|Q z-fuk1nzO7q%Nk*(Yl_(%Shm8WR?la?>gA1jTRk%5dCr|4r+K6%8B6;(jb7%<$!~dh zncqx#%-v>QQrhZfGtVjd#N&dXJLSuQzAeOYxH$7;eSYysFt1Pg-QynPnWAeRrSfZ| zB+m@Jnv!8BJ-TQ2G3ISGr4OK2NbRMq*31Hsh8Ta4tZJ__1Fh=(t+B&7p^85 zyu5TNL*2bBthY$#v+l?1=fSaoUabb#xr*fs z`GMRV@7`F8!8Da?$&h`^9`sh|3bxXFDt?iShgs+&3uV|#Wr7}H@z#hYSJt7eV*hvrLwhT70T2f8`)YahxOjzvrUuT%69t* zMeZ8)rjL-5$Kl;PtcgD4^D-B1;&s?{Evu^3ddHSF=nP z@mB3t^>DVA?-s+pY+7RaoB-dKv3lBUM)t)E8_tzD-&X#n*b20&TJf1HE-1R880QratBz+IqL zekJo6UxEIgB06)w@;xmTj{6odzVfo~DQVj%cfZdhdqKG0S?N{uKL8t)*c2p_@cwQe zkof|eFSGeF>kq()0eAZyfSKj%qys?T^s9AIgJ=6y>!`}TlI!Sse)&4Dv|7JXxh~-$ zza}%Ch$xlc9lO$RlJU-%wWv?(y~*!MoZ9yrw_qR8PUxNxkGrCKg0j{yl-5l;_@Uf9uo|KTB`&_aMEhR zZ^4TZOJEsdIjlf*!sCcl@C4#i_PmQdr?clQ_MFR}^Vze8Jy)Qo30AVt8uob-eI$Gi zamtDh=EQodg0GatGw3v2)LOmq3lt?$Ehvct;#tJQ;(5e(#CF8v;$^g+VCyNd7d0P? zHxNG)`w`p3VZ^iIUBrtR$AnwdV)U1#Q;25iq!r%}B6{PY8>%G=^(F-hm=v*7DVil% zi-ZzC)PgwHbYsk5%w)_*i~?^cx5YqbXbSHRuGsnA!3SOq?GAykA29;HLhJ(HBPJ;j z7uFsBq(v5D4%~q_2u33gWBn+`O1L*H2c}@49KgTbW6k3VhA~#MzLNEoFca+I<5^$J z`Z^7%R2^FTg+IX7Cf092R2Ew|aN!MTzAOA$Ha9c2uv07Jeh7y>Vf$IXpWE~n%kML` zu}?cA2uf5i>IAhul;to+2V)jvj=-2oSdO6X%3)V08!8xU7;72p$Th5vJ?q%>0hSxt z+QhhleKxRu1N%ISf16~St%a?vjQiPVKl|)wALT73ZES95Ji|_BSpPML0EyegsFNra zokU5?jG?R#V~k*H1nZ+19jwn{%wcN|>vJUPn>>Yylg*WEu4GLmnigO19Rprj9jrtZ8IT3*&y)?`Qpfj?>2aGmN0;wDg>iehQxXv_`PX!I;IE z!{}tJWUSFsLUk-}U~FN{ewNQL3YkMQMlj|uRx;Mg0q}EJ9m^XS_aox`Kf?t7Y#vr1 z3><7Gn;hll?0U)I$}lsbs8UY-H;OmRneEWqH3L9Aoas$mO;ph&cb- z*xb(MGmK#5RE(U8F`Pz@HDRoYV05r1i{%_fCu=HLu4JrXO)blHj7^Li7+V>Y{ru6+ zc!p7!xMIdI#t6nN#vH~9#!ALo#wNyA#&$+9b2H2oEtKUjmK`i-vFucsR4}Pwxt8Td z^E^<(8d=lCnpQ?|;c$#`F4%!#aV$GrXr4PVW(D>+gNUA8QiI`aHr5tmTMRr88^Dqgl%Ej=|P?~jE#&fjIAD2ww)0? zU1|OcPf~> z(89)BBg9wSaz_S#j=y-3YKeFu4TE2v6Zns&|AS=L=XiE zW6WZ#V60_agoslwi1xw8AgZ{D^({fJD7UiQ#&SE$5KMXz%*nAF#y_N-*jh20u4=kTrI=iL0O?6x6_MF>JH$V4u_X+Mx+}FE*=6=OJ z%A>nSs>cYAF+)72dMxl*?6J&at;c$gXFZO3bnz_ktnj?YbCc&T&rdxIyehnEyw-V* z@m}w}&}XU7F`rQ1c;9s2@xFKaF7U1QeZu!e-xYpO`EB(xSiP;W)?uqNGei&N&ghs> zfYHMVGqVUwM-W~fMtFpAaw5qe~L2jM#Qzm`w(18Ib>CzJoqoJELlr;=u61;t$CP)Ic< zbqV6+3DH05Rx0-;Q|ONp3A3vx za*sShE91ugq*ogd8$x~gN@SM__>mexWN#?pvPifQUzWFxeh%fZF*^~bmhIYI*h{>tQ*w-(jqeyw89z1tZ1gnMo7S4zOy%ag z&AZG$m{VPHT!y-oyYzK+vGla$THdm}WBJ-*b1QOt(e1cfpnIIV;(o~8&10O$9FK22 ze(<>0^EJ<7o>s2}ujyWMy_R|Xp;9LB_9~S>5@ZC)(#OEO%aVrc!+y+j>7ho>p zi?9N5JFG$60Xq{xBj68@E833TM{)_>EiPvSW4Bs?@Ys`67zPc*6^p;rT_>if@?5a1o?Z!_x zeTg|(KhbTrRD=H(F^+@@fe3W#)f)hD{omOggBOCKvehyS)qvC;?n zTF+p8joUByZ#UGqx#?>Zw_lO`j{k!7qrA3Dk9+NgUHS_Ab=hwGw*`YeA|1p(CKu&B zTyls%g+m0&CEj892X}tgAA|_mAzbzC-n+peKO#BgO!*$4?O3U+{w1G5GJS&Y2l`0& zCfVWFBs=}4h|7MnC5!cUeT20FBCJ2jB4YQE9N`)5Q8<0pq`M~FH$G>^q*>!!98u}IdR?s; zwA*E&7?|RDLaux9>asC~jT58C@Bi}Q?YpaXi*%#O#i%z)-nCh~8{ZJ#!fXObco%su ztl0h1zl68VkeHH?oRqQq=*J>c_?eoDmx3r5BH?J(;lEi|@K1)W4*o>=%fF-e`|6BO zL_|`n?S9+C-D?YLzJL4c;$7GG3-rwCSw4K`_!%?H$5+g(9$TI}t#bBV+~{Z3^wYT}LJA(zONJ!KSUvV@u|=AdvD@pM_({~q8~+c${`@`w delta 25899 zcmbWf30zg>_BXzsedfaf&S5@Ghna&Tv!a5GDh>f63W$0TG))tyO0?RLLs{09Fo&Bd zC#^I!H7xYzEl3+QGqt>BSwRjBSIe$?(t*?KJ^onj@uN%@rbEY-w&3vOV>>0Qxr! ze>t}$?zX6Cf$2)?%qP-uq>=#Q@l(I=cX~x%pT^f=SecpxjGiXqI|p1_e07 zkU7H<>Kq6GzYDFam*~+vwnwg4M7OrLMemGke!ho8L^Z$Nqep5q8hS|182Wf~UMzmZ z20KFQ;z+PN!W1FM5v~XhKSxwuTysp%ToK=VXV0O+*}Su7nmz%&ojsbr>lrHdLb1e4 zJC8pNb-j^l>d4+#viAtE1bFt8HKw{gsEC#7`r^mDF9Gzv+C_;d*CinYBuT+NB%9Vp z?{tw#XVcqLq>vsqU0pJoZ8~RvRNM4{DJEU9Ow)EsV)`Oz6Qyr zvl#*c>e7%I1IsO@z;aKMlF;CLZrr$mW$Mz&)7aC~$LQ>bVt{iJ#z)e>`FO7&`=6|V zf3W(gR-;-rj*Io_Jxf6l!TQO%<78aK3KBp>4bPh%^&wU)r>z&ys_rUq&o2Z3e_?bZS%9%qR z`noUxGfonzE*ynA2ZCPXjKB{`qt`mCF<_6@X?;=+BEI#Rv@7ABYGyY$v*rzBf{bc& zGn-!@GbvIv*GM-*iR@fWZ5zfr!tg^r?)vNw?ubt!-t$JZ@byf+JaPCHRFAx3P z$I+Kn&aBqvvUeoCGJ%?1<-sDcc~SXjk=S~)+%7~`^EVZv5eHT#h^*ErmGdH?1OI2_f8t=pb_AN4Rtu!=HBmrVK(&A8|rXIm``Jju1TXXMqgLv{}7|m z6^8k5bX}7|Idk3Iem7InX_cPRVO^~?^@@X-JN>4Ekkpyy%xoQ2`+d1F9<%A8pBLYn z^>>XBJz8^L&-KvrOv!3~^w1bDTAGqn_ae67#Ju?C3x|SmC(#{#(uWMr2vql;PHPJn zaqHH@g+k=CUVblvdfV@PC+3;?{uYas^8}5vh(-~x>Z_-AN=d0x0HRCh&z(N&&UrZd zVDAxtL0+gUD}^hc0$5QOJ+!PaA3wF90kkxqJiSzI$N5SB^spl1NU_?urgN<;T41%`x7=+ zuFN&VvLWB4n?WD_os1v8g+wm*`z~D%C(_CMMGDDrSy>n&+87dVh9cCM;c6`DzhuoQ zJ!vLaljd?Bl^Rz?8qIjZ--ZzuMv*nZL=mnICC!DAqzN*T|5xGPrORI6U~_srnIDTL zd^LyAJ(+MShuc^~a?b$5^Kpb1LkNR0Av4Tp{1Druhf!V>dT&1Izb_zMFoE!V8sXkb zLdi_nItfvRd%}pEOCs!ztuccwknl6EY=%F{b_d}YPVOXY4sa5Wam7Q}|0g5)G*=M* zf>u2|!mX4&NwaSZVHfwxgMK8R%_1K;fJeRF_|q6YylkP|o!E_f=rN2&D+h-|4^O4h zSe6VXpHkzI;(5Yz6wQe`_Gv|p9^OytkMm=G1`XL8I2>NkWub0A!A%OnUetpZ_VhN$ zz-g<8nvqoMkUZ@5p=K!eA@g_RNbWt5vik~0&J2S}2vaa2FSv-Ku7`b`VjTCuM=@mm znEgNI-de~li{Sp)#C;R%B%i+~6OPO#l+p>G@Fk4vFVn}}JOKka(}Os2diZrR^~AH> z$y0e~4&w~a!(TWF+la$1oT}Wu1$h+V8=fmC7~f>v#W(^h^MSW5rNhk-fwkx%iTk?& zhei(*Q&!6OvGR_6X$<4{(Cri{*G3|GsW?4lTtd7s7V?t2Jcx>Y zHs}Cd@ONZ;>c{7#Wm_R-rg9x9a84go;HBB8% zawf+~3M4s<)`bjNyga@dL8_ySr+Kl}+(DWo#>ctJpjy(Da)>Tw#HA)nj# zwUvB;eg4iVrbdzG2G4+nJSPTm^>M71i+FT%n3$CjDyNybk-PLdhd9kvQw#+PMbtw} zE_bgNX*zg-e&E!H;r4=)mj_}J`%h^!`+;O zi{td+VGZz?$tj9+iE)vf%*$gwYmRVo7Tz*X;zGq;!I!Gs!WDQ2kpD?uBX(~0N!GV< zLZ5K!XLDMccv-%~co%PCix_{8Ci4Zg=Dq(liXGQhr#y7aZZC zTwX%WID{J%U17@t2?hQKVTHNG>XB zF_(*BJ}>A8S7&=2|+@xYJ^0XF!( zXS=Y$j=qHM-h>xfUdlL)u^;O#ebNjzC}sbNy~uM+pC2$navzezS@SQJQxY!(==D#> zUI-Z5@K)MoL=P9?S8@z}Jf1}OatUEu=H;8N{cCALe(}Pf zB~92{tH!NCO>NczYMs5Gvl?IzXx{DiG^8jV^6F= zw7~~Fh1;+%Z6LqTvv&!S6p`h5I50)*Hxr)r{Q&VdmY)px2<1s3rx3G)E(D~A3_tSu zJeaWF_66cUY@`qLAz7P`ZL~F zQBDmb&9X?sgWS{_fi<* zV^Um@9`5MBI8YA`R|aCi_$1Us*Wl`hiTO#W8BmQ|4UFqg=opcP)_tzQh+_xm2MKu8 zatGouLn-1m%Y+~qe@894Gl;63jTP(?_aJ@0QigZal5^ zVC+t1=Ea&(wX6K>(o~lf^j+i|724ZQv)=vFovp) z$tK$c$Tu0;o=}-QqezGJ8K$d^*>Tg*b+gJ!<7Oh;uCife)9ezos%%2pOk}UAEG}o7 zT?22bEHh^&vcoFN9Xrjgh2tu#7&{Z$DV3d)OQ+d&(5ccZr8ALzqq2Qy)58xcJA^hp z{G>7|Ynt5vzpKnIYbG*%B;dc*#{C6V1|xW?>}0_lyAk|Vwj+9)-2|a3dp%m7i8Mx~ zKc!5w;}Z#$nNw#XOHtXxA@|xnV1UXN3|WLMS7nhyrr9k}sIv4SGm(|3>`Kx!yC;;Z z%rki=GMCC`6;G4xUT~*Mmln@NR@cp3EBw9TUX?Yu8tvZDpfJ$%UuL&LL@c)#+H0S( z`@?c(P4J6zgFO(oFl&Y~*JgVV?7~|vfF?LHvdta}PN%|N7}8;v9nhfCI}?uBBcTeP zuwo~IBjFQ!EF4o=8nSo@q9?IvJLmkwo&Ymdwg7ECLEBTY?WpXu_l70RnxNm%Z|r^H z8lxHz~90y7oQ(#*$$CjtHrg8!OeY-|&R$~H z=3LY*g)Q__37KxJ6tWC1F>B&v9)-w4wi*9|d%~mORM}?S6CQ;`g~6GDo*|FH{F}@# zWCd(dnJqIcHqpIWlAoj6q6$@m*zk$X}sFW&fz08nOYb z_?rgVKF*z@al?9MtKpkTb3&elPgL8&iiVIE!1oUJ<>8z!1sGp zr{#tY(f8|+xr3R{R7)xr9@epcO>Kk zIL53AqLcK&$01?@+hn*@wLka-44bI9ew242Seb`REy%&tR&` zUJ3s;_U{pD5XH<^!+HOdP?LC9WxZ2#LOsREnM#z)iE(J$wq6eTos zlw`tko(zo=7L~n}{)H4R{8Y9-eU~{}gsE&!^19F%5v#HXlQ$#lqp~fvyFz0{y2|#{ zzJhG9%3g_0gg99YRp}eCej-j3tL(m-M2HtIg~8l0uZ6~os=2pX;S|f~G2=Twr&!DE zyeX{YFgQhvVlyU`6zH5{t7^O98m>zayH%S<^})~tA^%M^o==@BdWbhw+w%(<=5OPp10fw?+l7ggJq+E#pUbXB$OsnsF-UA0{v+zNe! zUY@UX_iuxBNWGbzHx0$s_Z5Mvt)#|J^c4}RZF6BOBnqc$Yc13vOHyt2eyxxsGE`ek zKOM4M)pj2?Fj)*&ZA-9$$)a>VU;oaVUdlZRDPp{8Y|A}?tX3`fV0tU0ikYhI$#fmE zdsN#))rpWM9#Czos{KToSf<)4vyVc$ctW+!$UcE=jcT)`H)#8bjq~~Xx6Eiye=oG3 z*rFO=FIuhXFLtTy0<*m;n=tw+^cU^SmKpCHeIm5KIHa=S`Jdq1)Z;1}lm9ugPgV90 z*Hke;oMqOCbLLFw0AanCuYdH*!#_ha#X+RXuM(F-2aAaNh&936ML&n;h*>IoZnPAZ zC)P7-?s!UrEA9jZbSg6>v6=`8bB1UD&h1p>xV#OlGR#rYLY>d#{ zudw&VSA~rgzAD>QQx{e)LT|Er!YafCX7Xxm#iFoEapeJ};I5j7!p4g&4=U`j5-w)S zwlhOqP?!uW{SSoA5MhhSwF%jguvwy)%C^UU6gEe!WVRYgCY=tOFFI9Qv-y13eZpgj zQZC(jK5T(VRhV)EC&q3j8x8L&&w4S1*?H5yeGbDyF;`_W_!j6EiU(Et6&e?b$5eK~ zr$DzztWnu~bE@Wkv0Y^km@mQo;+V=ZD=&mSAU;vq_c2q&1L7RBWyY&9R(L>MR&BJG zJSgBfO6|PqdpU)ur%LA}6ANax%=lok4w+N6rPm$>Srn@*xAp|G`6~N9|0pzwwJN)w ze*)Qlm1zr(;-ltHl?4=>pa;*-E7i{$D!0Nz!m3i*ARiK8%vQtUw10&?BobBjR@U{f z#bW+VW(Z#@&N5R*^kFgb1q!+v(z52*9~Ku>)+;U`{1LHgi(=~)=L}yV_TOX!UnP81b|rI~eU<2cla++85)I6nVBe65;cG;@%AOpu%(_lo zP#DjIb;9_f67$PJQ^MDY6)M|~zd6Z&75kYgt$12&!BY*?io@wM!=Dz{RQ7QEyzuoR z5l=XfZEcPm{)||!vb4b~!Z(U3r`cxo4zDt75;Ij66F$ejNz7N-x|k}%X0b?RFUQQW zZgz=Zvi5ksE}B&~7ug$PzsmkS>8tQ|VfmUp zjVth((3`?nWzXO$X1yj7JN$#QyBP9`ZoM+QKd5b(4UV71M=IN)U*`BlTvgdMX@%og@vF+7lA0X93EdA=q4EUqcVT6=7=F{Pb^I=t z{K2+DwawFRK-)%UvQqGdctJI;vMh7l5U;8%-D{Zxqz;u?Y|9)%`aorm`Ydxu(x)nW z%XgVWBYnfHOLVvdXr=W(QfggdOIiZ57nv#b>7AK23hbD(X(*297SPU-x3WrhhV5U@Il6+O$Yg(7VBqcIaYA{QKR8M^V zNvFs1Ro727b`P^O{5Dq)X{_o>o*vRf)iYSL&EX-*w|QEm$8PhqNX@EejKO8FNG-Q{ zdP>{7J!K5)DeX~R%~FNMQ+nezS1;+kZddg5l0H&B&k2{oOZrh|$MoAA-qJ6(d0HjS zWu<2(YL_{zQW!Jp866&=?9Z%Atm`KW@R4S#w8NE%Y|IrZ*a)R2wlV86jhwj4;Ug_j zn1~rKz*mx0+glS0kgZT{>+krR!%zA^Wr0N#!C(5AnKCf`(wEE@!*kkYvcq4xc$+am zx~dxUG4B9LbCr@gXqs91F$79hW^cofgQki=sYy8Lny6ZM$q%>DG{>8Au5h<-@ zri6)-Hs9tMC2dzdQ*g>gNiVBxhQaO;E$vm=REymsMrv2tF)w^%ES>AN`RF3zB>9p` zQ*;{~@si&)YSUu4VA|ktN*TJy{_W=eY#q*1Epuh8L>AWcwNm?hA+hcxdt&tB3Z z)#mMK_vj@xsVv58gQK_ftgO?#Yym}ycEW=bQ{rR3{;{aXy*;+B{$W#4A( zCk;~zZZUNE^pomuvt>w2RGXhyqB%o4#7ya@0n*9aJO@ahtDaTDBVvH`oyy)rHce@2Yq@Ty*gNj{X1|d^bqPX?}~hT6OpelBy)VgnE*5Ot&;I3@88j>NchT)ti!Od7AVB+`u|Q&9^4&A;A^};%LU_c{59|*KI_$T zWjcI`O4X1np%o8u_u-axYbXRIr(yr@pdVv|Tcx_??gl(nbW5X@os>j1-j|N!ue&W! zcQ~cpN;lrhnmYVeAjRj!o5@M=VvW_RfdkyhXRznGqmhpWKEr3<-5MGuMV4tSsn`?! zx>XXCV|E(&R~qF=g}YPy27^jKxofzGv~aK5Pq)faT1x2d$`s83zgzm-=8%kS`r{;` zH9)Rod9?PqNPce!;fJho$NpK)OD2ti$|O|=Zu}r>*gHuy>0ZmBN&273$=zc~PB{q_ zs5=D7G;)$U8x*;31+{)f5hZ=KoW25MtFT)2@Yy7*)d17DV++R9yxGOklv3144NT6w z6;rAA4!J)C`u{N89HH(c)B^0kKKIs=dnS%j{A?11&MzVPZ+ZWltjwqGnIuzz+xuWY zj&AqJb?g7E`A?r$uu57%lvc{1Kl%$iNkg?Lov+Bsyg`}H>fOqydr|eor~2L9e#_cD zt!{56S-T_L8eIt%^PH!IlquZpqpXEno_PP$j^iZ3enbuIMbyH_>9+@^Nz~Wbt>M{; zRZ`zb@aW`jeRunAPpG?-$@9i-%kQ>+m2Yc(caE|6eure5MBOrJl&oX9Z>SG6>PRa+ zbZZNefaLDJ{G*j(zJSBgU6*{TE3gF^y4yf{4R}^ke~ib@6ubdTJjJ>z-i4cYcUnp{ zJxc!=L8vJzQ&Pb{EMt2ADFLnN?v7PD@EFhJDV5au${zQ>U#Q5xd+Gk~o`1CB{QrL` zMx~Th?#ibW>B~m9G$bp-r(D>!;hrI>7dd6#DE;JJiSu7lFFA=*ln{S(?Vf0BZ_aZ( ztbr?DK?#=KL2`F*(I!J1PIq6D{&rbF7?)M%3Z;VcpRURh=y@h9yA`cq4Ia0l(1AEK z60d0uD3@kW9x}y8(I^X&+Ny!ge3@3-J&vy_Mw~+uY&TPKbOjM`n?_buvnKu7i6)?g z6Yy5C6?_?`DXC1-|NDU(GTdqvI1toeWt)e4yTN+yB$|bw|!maG8e@ck;Kdf%Ia4_{DZr_j2D%%f837B)_%L@Kjosmc$cSaGI;!M z3d#Qs4&hb)pPu8^9QPN2^6iEg9@j#?T)xQnueZw`JeBl(`>fm)6!O^L9^t>2>Apcg zv23EwykWW3n%leLzn96gdvul1lM8RfRMJv9UrD-fvJEQ025Zg-hV+msgV>RP+#yN-rmLvLuTbfZ64aX&Vx^`Tmr)nk*TeYNLr`_I9 zfLlvXas1${JTV(d9zLjXYtP~XnovkDDub=sbbKIF&q3XAeb^3g!^(nI{IbFK_$CIA zwiIBxx}Ifi5UJ}itB!<%9;oSxod0Omvpw*9g`Z zSBOL4KOBF1)KLY6Itp5%`z>mMaKl)9g}K@JklDCnj$ajo9sNh>zeIdpEp3ycO(X)udMZzQkiulmxxz-p%-}s5Z4?5+CQZ zg8U7E-}Alp|%=^>fpNW-vc8wTgCM>r)D?M>v1Zo(`VA} z9Gs<@&-pILZXBsuF8w9563x$5k?X?nNhs5a0-L^m#1xHP?>%M)zQd(i5~+W`tWlGs z4^LTv)zE8jH_!|I4zV%HPjrZPA_;prXyEn@r0Um4C&D4|8@}*!L#vE$$`1+eAtzu7 zyfJJ_Av&ng z*~)&`*mYeYGf+cG)C(n*D@6t^zC|wb)OLxB@i_Iw&f!O)MBftbtGzBJjF-c->ojYl zqP1?A5`GCoTktqSwLWijU+r`)z5lmMgwyqL>C3bk(iuF&Qh~(?GbbH|3>@7p+Adh? zY}d}w4=g*V-3`>E_4>83o!UlyLhU)NUDS*?ivBckCD^+cwN3inW`izLI-C|S>(=QT zs}gl?{f1#_I&2Ldzi>le>_9hUVJdE*H^FY8SH5nblOh#@UVyrRaI4-krC7IHKXG!M zZUv0Q?p*Nw{g-+-I|C{5hpJl zegY2ZS0mQzN5pjKj$`6)>!xFrqll}jKGbc-GG8K|%{kASRP62NYA))kwKUjnpp!5i z`tK4i>$>!BxvuN#rQ{?hjzuq=G;a7ljeHKL1?uYsZ4V{-*WyFB-Ml5)3_hVx>zgEc&)g)@o9HHq-c_5l}|aeGm5^@?}xCeY&ZxP`u(my2qz~Q4F_S=-~dCDE_iqk zLj`WmX$Cj=;YQ(@?I<6?>hU`^xQWyoYEgb#>cA1Q!bebx`&@?Zbn*^EhOW-lW;o5B zXIXy%$E?%P!D}W#^K!{~!xh%!Tq_rFnzy90s?QhISFvn*kt7>`<=qaS8{>5<2d`ft z#vF}%uodNf&PZdmVMw(THIs%<71IrkY19H+WvX$G;Y-&bW4*y=@KB>&H#%)1o+_)y zb>fDHqIfuPP|k94T0v=@zt`q#|T+jRj`I*jWK;RAm% zmT0cj(l&H#xDLJ*l4mPaYYq*FFl{xwIv~bWt+~u{C(65J!RCN~Vs)?r>B#3Y*b z8P4XUne3u93pZy&dCVYFEibrQ_;q|XZq76fg_N6?h~J^uT#{=FDs(>&nr-Sb6guxU{b;x``e{=y(UVr&b%U{VCz3bDzkvlJ zOYy2F-Q}dK+tg8sU@{&Yeay5QUSH!IArfR|^ZcUdYm<;@yK_TE|DUi}Xt55nqw`<8 zdCRct9?HF8vKbFKFXDDUC&AoowHsq{eav=aWo{5+7Gk7vV!trNw}wTV2VrXj3=&Vp zCz|tF9*J0Q$Y;$MF{2oJ+^)||nuO+G%M#5=#>H2t?`Hy@pVTB9)%9gT-kL6hEU5~9s>emh}ZSwoHw8`TwUd&Wct$TgsG0b?$NZL8pjHEUU zmvPE$<;x2WF79Y1EuFS8Y3W>z$@pI7C68W$ZY^rDaZxx+=oDIp?ybQ(3tlbos@kNv z1G{jyF|{JfGE>k-G#@>m#vS%}zii72-cgo|zf@LO8hQ0K=}eAEmVL$%r3)G5hb3m>< zhR(?)XlS>;0t@jrGyylLRi19}cP8Q;@!_-rydzkcw8yhT_ulw@p4-)gMofMWHEVL| z_H;{@%dl0yHScH7t@>|rg_m9Ykemp1@fG48_TSIgi0O{-YScb3w8X2TQA?+!8ntvM z)S;y_S^Knf`YK6GFQ4pnkUO9D!KGf5^v}!3s57EZzErZ8hOMU`C^1=;7U4W(8W$O{b*b< zc!0M_cQz;2`$xVeoaXU8jaA?tkBhY4aF#V^S#y>(!bDdOlgUxJ*c)r1Ltag~Ed?9A zGjy_Rv-c?;sp*CZSycv`>5bIA-ZsDk0J*C|+DhSb@pNx29WC7yB z;rNj2TddNGcs8%n`YU%&hR&;Mvv-&Fnbd{WLet8E25X6FVZkzMh3WbH6^J8Cp0v^^ zZ?tNqvpIjWS{Sw12XwT7+Px1Brd+q$G#R6X&BA(%bTOwMuat);dD%Mj|42!&W#FKB z$u_!;>~D*as7<{jYEvo}h!WFHD~29L>%_{FjHhjbIM5(zTk=I)r0$hrKiTq8a}m+2 zOz$(2OO27}s#c}Szh{p%)v}zSTb`fpGYD&uj5E2G3|&y=LLZr~SP%Qm#9tf}VLrMF zxL=~Lq}?xl7_b+SUg9p{5c4Ja7TSD?UV5%z&HWO6ZS8)EUhS?@s~J=?$ag)*sn7+N zFGrdBV>4STbQcD$_T8?^w#pZMg{HvB0Io}Sr z7JeA^bFqWmPe-@~CS6#n-NU4#i*|>4YkDMC>$g?ko=Z#2nCI!Y2dk&eX8d5hkl|bj z_3Pl@3_E~U6~?CpMYj%v{q7fZFMB_33HaMF#F_A}7e{EQAFcB{{08wA22;fD?SLoa z?!!R6CnthdT+CS|?UO#kU7%6dnE8~SK>wqNwfQIgPD!PcKSNBd`P%OzY1eq+e_Dzj z4tIfD^UOYz;*uXacb8awqhU9 zPWW45H13MOCCZ!qck92;+U8GWr~j|grSY#L#wEY&uho#O)sVaowv~P9Uuc?z z`{6#5IpLze1=}jXqM^AG%5tcNzNHwWp}7*Op_kyjP)~P8!LAVj*R`vLjSE;WRSc{` zd~kB2Y+lcc#|XtRKR|*Kcq_n&e>9;Uu^b*mtb)aeE_ej779K~O$)0z!=WO% zcf~V^N5r#;$HWV0JSL-zNq~yzzcrV z0vO>s%0>{n&#-gm=%`mb^_`Tjp8Cd)sDoHNwIG2ty%{qYGZ_mJW5EZC@GF5mU{zEf z@W9UR1HP~$q7Q_?tBBEX60s+IftVuW7cTqY->^tU%!3@n5m1a+%KGt)HE>sC9?ZZ% zd0<8?;0j6^Ygk{y`Wl!AK2cLx-^lvKDpIM%Xw8UP%2qe)HzCUP0h_q+CN#UEo?~+> zV>>%_Fdl#?*cN$!^#{03Z?XI#V<-D`F@m5(@fS5jjiA;?upG&l&6vZOCorZQnJ1{b z^4QhIhHAzJ#zw})So-;KATv-iG7~KKSok!Z)a-u|7|t zzA2EIxY%67<{H-2uz5PlyCWOe+QPVreKxVrRw)=RMYglPlkp5&&#)EntB^PZ8ctV3 z=|-?7nl;g^iPp%JMK&Aq*pSDDJPqmdHB^C%tu<_|VQT|x7PDqCYg$;-&Uk?J2UvfA z<8-qA3?pbcEiEUcogrg2(X7g5%wf!9bTQU2HfSlK#Vl`PY-h~@md`K>9fxL&X3S%( zVQka|!@0=CEN@~wfQa+|3=_~(1wzju7$f!6`H?I~Gv+YnF}m2lT2DPx&6*m<#f&X% z-NbS`%N;Bq&_`j+0~oo;eh3lge>%_u-wUV7t7#9g@qS|cCp;R*uuEk zizaM4%Pw#7Y+!6*Y-jB7rm|g(VD+H+FRY}BKvEwN$#OQ!F2)ANB~}XE!g4!fCnMOX zR0Lx-ql>Yfv6B&e*oQHjv4ydnP{u#~z-Go4-x(OLow1V<{74hwN6RLHWf#lU zEH|*+$Z`wIZkF3w?qIo7X41t3>=atqd23`jlI3icb69q<+`!n#niiJZ89N!fSP#LR zC}U)BF#Hmk&2mmK#don>9W0Zffk`7fwXp1Fxt-+>F3`zx7t0XBiH2~ZEJw1O&2kRQ zE|#lVZeY2QWjA97V^@fejJb$V3KYqh!&uGO$hZU%r(P)SgDs&{v77bnp&lrAu-wUV z7t0VvdJ)FSu^h>AHp?!?#;{<}N41AN4RTv#2a;bRyBI|{$*yn?9Zn$*BI4AGpzS3m zf;82Pjf`%_4#qA<5y=%tl7A%2*(~RR>rLhE`JpqudZnZEcC2f!NO2$p~?ziD1lT zbTKwCwlKCcb}~Xd`!i-Ux)>W6TNv9JI~l>r{xW}LGrAZX7+ahkSUvP$C&p|>7h?nC zk{;Bi7M9x?52EJbs7{ulCq;;0%w}{kwlKCcLNDe1rx&@p7#kQnd((95?9)t1B$C|D z7?DJBeiF@z29^)9+{rQ|lhu_>r5YGJ8P6t@KctX8f-#%XmC{W6Kcv!FLMpeFJF`KakQO1@jhW=#EW^^$&Ft#wZ zGj=lKZxIxT(Z$%p*vWV{gV)PI^4UC)j0=sMjoXZ8jK3QnFg;}Y(PT2uH9u%xYd&ax z*ZjTt7qf>)xJQh~NRKj)N{_oe?)7N)_{8I!M}Q^NGSV`|a+hU}WsPO0dLMv!~Ikr&op7BCjXCKJfadSCDt4cY=4JcS*7LB=0%i_1+J9uke1-d#(51 zy~C~9)-l#v>pJTe>rrdAZH#T6?Fn0n&yzm)_}=f^;p^)c?bp+<+^^bij^7f$$Nk)X zi~U#nZ}7kFuL}qX7_z1$L-du$#RW$IRF@Ivp?BjzDKoJ(k*Lim?#!j0??!$~gg zNBBr8`M;8PAL8nC(yXnfn2%)3q)JF%gxFQoh!|T;7*o0ou{{4V#GhSMSnEsGU-BV* z!n_hOr*aiyVC=0_3Q{Tbv&n=*YAJF`0bwxXmZ79q8sH44zI-(%T*i;|Xd?R}2p@|f z{4I>Im9dCj|HAS=7?UE&dWrqrJaFr4sH@M%5(crnYrR+P<>guvvY=d}4ii`_%cY_u1@o$mf!e)3?$03EyXYKllBo?={)?ci(G%7Jr9- zj{k7~SN)Irdj$9f#0I1VWCoO97w{0wMqCV!ATEKmh)ZD?;xgEa_%L)LJ_6q$F30i0 zKbZzWh>w92aRnqIJ`TeWpMYbCe}QihR|5PZ@MugBqWmO8;72p`M_dKPh)=;Z#MRJ% zxCYiDu7zEQ>)>6)zd|SC({K%OJ>cVMcm|w^8z2#JBMd`q0T-eh79c(gD-k!rTExw; z3-LMFi}*aWBfcQRyZEsMIuW)+&nJMjqGpS_en@A^l-eRv%4&t*&TAle^wGo*kEZ98&AlJFfT zy`sI3fBG1AN}n6YNh8+$m@9mzk1%;?uUpz+gvnDo!W5>h^t?}+hd=fBQ|Vca`hnVi zdbYtO&l%!vsD?l<4{fa1ebRegcWa%}3;45Ba!Mz)^Q~dpd7hW?rw!(LdgA9*{P_ug z!nAkTc1n+|xjRn;Rs{Ksld{DxB3p#$M)*YHU)%Xf`!+=DvW18CBcC?N);%F*>oRq< zzB{oB5AAclBXsn6+4rP2(95k$@ptP={bz`;{qK`*`2VC02&jgDfFE=LZJ*}}Yh2s! z(?zj<&E2!bWmC?~nRWM#nm+IDw&8Pxq-&dfubAXvy>rU^Nh5QsXU;5`RYS%#hZ{w4 z2x)KnSI@7R2J^Z#ZMKI*X_|HY$W<9_jnzeuMr1jMLf2}h<8f6A}nU#9z1&_MA&l6>)pPS>46 z+o?5WpNii-I4gf$_pO8ItSh}1PhKNAUU tb3YU7_05S=M4Q)FVu;2g2~r^uf6^d%&7^Z;t28E~&30b=Aes^l{|BV&YRdos From 5e9e77f757d7f105e5f3a938f7bcdfe42747aa53 Mon Sep 17 00:00:00 2001 From: Lusamine <30205550+Lusamine@users.noreply.github.com> Date: Thu, 25 May 2023 17:20:32 -0500 Subject: [PATCH 122/171] Label Raid and Encounter bots with -SWSH suffix --- .../{EncounterBotDog.cs => EncounterBotDogSWSH.cs} | 4 ++-- .../{EncounterBotEgg.cs => EncounterBotEggSWSH.cs} | 4 ++-- ...counterBotFossil.cs => EncounterBotFossilSWSH.cs} | 0 .../{EncounterBotLine.cs => EncounterBotLineSWSH.cs} | 4 ++-- ...EncounterBotReset.cs => EncounterBotResetSWSH.cs} | 4 ++-- .../{EncounterBot.cs => EncounterBotSWSH.cs} | 0 SysBot.Pokemon/SWSH/BotFactory8SWSH.cs | 12 ++++++------ .../SWSH/BotRaid/{RaidBot.cs => RaidBotSWSH.cs} | 6 +++--- 8 files changed, 17 insertions(+), 17 deletions(-) rename SysBot.Pokemon/SWSH/BotEncounter/{EncounterBotDog.cs => EncounterBotDogSWSH.cs} (93%) rename SysBot.Pokemon/SWSH/BotEncounter/{EncounterBotEgg.cs => EncounterBotEggSWSH.cs} (96%) rename SysBot.Pokemon/SWSH/BotEncounter/EncounterBotFossil/{EncounterBotFossil.cs => EncounterBotFossilSWSH.cs} (100%) rename SysBot.Pokemon/SWSH/BotEncounter/{EncounterBotLine.cs => EncounterBotLineSWSH.cs} (96%) rename SysBot.Pokemon/SWSH/BotEncounter/{EncounterBotReset.cs => EncounterBotResetSWSH.cs} (93%) rename SysBot.Pokemon/SWSH/BotEncounter/{EncounterBot.cs => EncounterBotSWSH.cs} (100%) rename SysBot.Pokemon/SWSH/BotRaid/{RaidBot.cs => RaidBotSWSH.cs} (98%) diff --git a/SysBot.Pokemon/SWSH/BotEncounter/EncounterBotDog.cs b/SysBot.Pokemon/SWSH/BotEncounter/EncounterBotDogSWSH.cs similarity index 93% rename from SysBot.Pokemon/SWSH/BotEncounter/EncounterBotDog.cs rename to SysBot.Pokemon/SWSH/BotEncounter/EncounterBotDogSWSH.cs index c5ffd4c5a..1903f29fe 100644 --- a/SysBot.Pokemon/SWSH/BotEncounter/EncounterBotDog.cs +++ b/SysBot.Pokemon/SWSH/BotEncounter/EncounterBotDogSWSH.cs @@ -7,9 +7,9 @@ namespace SysBot.Pokemon { - public sealed class EncounterBotDog : EncounterBot + public sealed class EncounterBotDogSWSH : EncounterBotSWSH { - public EncounterBotDog(PokeBotState cfg, PokeTradeHub hub) : base(cfg, hub) + public EncounterBotDogSWSH(PokeBotState cfg, PokeTradeHub hub) : base(cfg, hub) { } diff --git a/SysBot.Pokemon/SWSH/BotEncounter/EncounterBotEgg.cs b/SysBot.Pokemon/SWSH/BotEncounter/EncounterBotEggSWSH.cs similarity index 96% rename from SysBot.Pokemon/SWSH/BotEncounter/EncounterBotEgg.cs rename to SysBot.Pokemon/SWSH/BotEncounter/EncounterBotEggSWSH.cs index a82f99003..ce73d5617 100644 --- a/SysBot.Pokemon/SWSH/BotEncounter/EncounterBotEgg.cs +++ b/SysBot.Pokemon/SWSH/BotEncounter/EncounterBotEggSWSH.cs @@ -7,11 +7,11 @@ namespace SysBot.Pokemon { - public class EncounterBotEgg : EncounterBot + public class EncounterBotEggSWSH : EncounterBotSWSH { private readonly IDumper DumpSetting; - public EncounterBotEgg(PokeBotState cfg, PokeTradeHub hub) : base(cfg, hub) + public EncounterBotEggSWSH(PokeBotState cfg, PokeTradeHub hub) : base(cfg, hub) { DumpSetting = Hub.Config.Folder; } diff --git a/SysBot.Pokemon/SWSH/BotEncounter/EncounterBotFossil/EncounterBotFossil.cs b/SysBot.Pokemon/SWSH/BotEncounter/EncounterBotFossil/EncounterBotFossilSWSH.cs similarity index 100% rename from SysBot.Pokemon/SWSH/BotEncounter/EncounterBotFossil/EncounterBotFossil.cs rename to SysBot.Pokemon/SWSH/BotEncounter/EncounterBotFossil/EncounterBotFossilSWSH.cs diff --git a/SysBot.Pokemon/SWSH/BotEncounter/EncounterBotLine.cs b/SysBot.Pokemon/SWSH/BotEncounter/EncounterBotLineSWSH.cs similarity index 96% rename from SysBot.Pokemon/SWSH/BotEncounter/EncounterBotLine.cs rename to SysBot.Pokemon/SWSH/BotEncounter/EncounterBotLineSWSH.cs index 586540d1e..2b187b768 100644 --- a/SysBot.Pokemon/SWSH/BotEncounter/EncounterBotLine.cs +++ b/SysBot.Pokemon/SWSH/BotEncounter/EncounterBotLineSWSH.cs @@ -6,9 +6,9 @@ namespace SysBot.Pokemon { - public sealed class EncounterBotLine : EncounterBot + public sealed class EncounterBotLineSWSH : EncounterBotSWSH { - public EncounterBotLine(PokeBotState cfg, PokeTradeHub hub) : base(cfg, hub) + public EncounterBotLineSWSH(PokeBotState cfg, PokeTradeHub hub) : base(cfg, hub) { } diff --git a/SysBot.Pokemon/SWSH/BotEncounter/EncounterBotReset.cs b/SysBot.Pokemon/SWSH/BotEncounter/EncounterBotResetSWSH.cs similarity index 93% rename from SysBot.Pokemon/SWSH/BotEncounter/EncounterBotReset.cs rename to SysBot.Pokemon/SWSH/BotEncounter/EncounterBotResetSWSH.cs index 1a994b89e..06232bb90 100644 --- a/SysBot.Pokemon/SWSH/BotEncounter/EncounterBotReset.cs +++ b/SysBot.Pokemon/SWSH/BotEncounter/EncounterBotResetSWSH.cs @@ -8,9 +8,9 @@ namespace SysBot.Pokemon { - public sealed class EncounterBotReset : EncounterBot + public sealed class EncounterBotResetSWSH : EncounterBotSWSH { - public EncounterBotReset(PokeBotState cfg, PokeTradeHub hub) : base(cfg, hub) + public EncounterBotResetSWSH(PokeBotState cfg, PokeTradeHub hub) : base(cfg, hub) { } diff --git a/SysBot.Pokemon/SWSH/BotEncounter/EncounterBot.cs b/SysBot.Pokemon/SWSH/BotEncounter/EncounterBotSWSH.cs similarity index 100% rename from SysBot.Pokemon/SWSH/BotEncounter/EncounterBot.cs rename to SysBot.Pokemon/SWSH/BotEncounter/EncounterBotSWSH.cs diff --git a/SysBot.Pokemon/SWSH/BotFactory8SWSH.cs b/SysBot.Pokemon/SWSH/BotFactory8SWSH.cs index 4d6b4478c..95fff828f 100644 --- a/SysBot.Pokemon/SWSH/BotFactory8SWSH.cs +++ b/SysBot.Pokemon/SWSH/BotFactory8SWSH.cs @@ -15,12 +15,12 @@ or PokeRoutineType.Dump or PokeRoutineType.SeedCheck => new PokeTradeBotSWSH(Hub, cfg), - PokeRoutineType.RaidBot => new RaidBot(cfg, Hub), - PokeRoutineType.EncounterLine => new EncounterBotLine(cfg, Hub), - PokeRoutineType.EggFetch => new EncounterBotEgg(cfg, Hub), - PokeRoutineType.FossilBot => new EncounterBotFossil(cfg, Hub), - PokeRoutineType.Reset => new EncounterBotReset(cfg, Hub), - PokeRoutineType.DogBot => new EncounterBotDog(cfg, Hub), + PokeRoutineType.RaidBot => new RaidBotSWSH(cfg, Hub), + PokeRoutineType.EncounterLine => new EncounterBotLineSWSH(cfg, Hub), + PokeRoutineType.EggFetch => new EncounterBotEggSWSH(cfg, Hub), + PokeRoutineType.FossilBot => new EncounterBotFossilSWSH(cfg, Hub), + PokeRoutineType.Reset => new EncounterBotResetSWSH(cfg, Hub), + PokeRoutineType.DogBot => new EncounterBotDogSWSH(cfg, Hub), PokeRoutineType.RemoteControl => new RemoteControlBotSWSH(cfg), _ => throw new ArgumentException(nameof(cfg.NextRoutineType)), diff --git a/SysBot.Pokemon/SWSH/BotRaid/RaidBot.cs b/SysBot.Pokemon/SWSH/BotRaid/RaidBotSWSH.cs similarity index 98% rename from SysBot.Pokemon/SWSH/BotRaid/RaidBot.cs rename to SysBot.Pokemon/SWSH/BotRaid/RaidBotSWSH.cs index f921221d9..0c57a592d 100644 --- a/SysBot.Pokemon/SWSH/BotRaid/RaidBot.cs +++ b/SysBot.Pokemon/SWSH/BotRaid/RaidBotSWSH.cs @@ -8,13 +8,13 @@ namespace SysBot.Pokemon { - public class RaidBot : PokeRoutineExecutor8SWSH, ICountBot + public class RaidBotSWSH : PokeRoutineExecutor8SWSH, ICountBot { private readonly PokeTradeHub Hub; private readonly RaidSettings Settings; public ICountSettings Counts => Settings; - public RaidBot(PokeBotState cfg, PokeTradeHub hub) : base(cfg) + public RaidBotSWSH(PokeBotState cfg, PokeTradeHub hub) : base(cfg) { Hub = hub; Settings = hub.Config.RaidSWSH; @@ -53,7 +53,7 @@ public override async Task MainLoop(CancellationToken token) Log(e.Message); } - Log($"Ending {nameof(RaidBot)} loop."); + Log($"Ending {nameof(RaidBotSWSH)} loop."); await HardStop().ConfigureAwait(false); } From 34f775acd1dcc747cafe46b59db3d0a3cc2d06b2 Mon Sep 17 00:00:00 2001 From: Easy World Date: Sat, 3 Jun 2023 12:51:51 +0800 Subject: [PATCH 123/171] fix merge problem --- SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs | 106 ------------------- SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs | 106 ------------------- 2 files changed, 212 deletions(-) diff --git a/SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs b/SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs index 5c1296651..d97f43b41 100644 --- a/SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs +++ b/SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs @@ -727,112 +727,6 @@ private void UpdateBarrier(bool shouldWait) } } - private PokeTradeResult CheckPartnerReputation(PokeTradeDetail poke, ulong TrainerNID, string TrainerName) - { - bool quit = false; - var user = poke.Trainer; - var isDistribution = poke.Type == PokeTradeType.Random; - var useridmsg = isDistribution ? "" : $" ({user.ID})"; - var list = isDistribution ? PreviousUsersDistribution : PreviousUsers; - - var cooldown = list.TryGetPrevious(TrainerNID); - if (cooldown != null) - { - var delta = DateTime.Now - cooldown.Time; - Log($"Last saw {user.TrainerName} {delta.TotalMinutes:F1} minutes ago (OT: {TrainerName})."); - - var cd = AbuseSettings.TradeCooldown; - if (cd != 0 && TimeSpan.FromMinutes(cd) > delta) - { - poke.Notifier.SendNotification(this, poke, "You have ignored the trade cooldown set by the bot owner. The owner has been notified."); - var msg = $"Found {user.TrainerName}{useridmsg} ignoring the {cd} minute trade cooldown. Last encountered {delta.TotalMinutes:F1} minutes ago."; - if (AbuseSettings.EchoNintendoOnlineIDCooldown) - msg += $"\nID: {TrainerNID}"; - if (!string.IsNullOrWhiteSpace(AbuseSettings.CooldownAbuseEchoMention)) - msg = $"{AbuseSettings.CooldownAbuseEchoMention} {msg}"; - EchoUtil.Echo(msg); - quit = true; - } - } - - if (!isDistribution) - { - var previousEncounter = EncounteredUsers.TryRegister(poke.Trainer.ID, TrainerName, poke.Trainer.ID); - if (previousEncounter != null && previousEncounter.Name != TrainerName) - { - if (AbuseSettings.TradeAbuseAction != TradeAbuseAction.Ignore) - { - if (AbuseSettings.TradeAbuseAction == TradeAbuseAction.BlockAndQuit) - { - AbuseSettings.BannedIDs.AddIfNew(new[] { GetReference(TrainerName, TrainerNID, "in-game block for sending to multiple in-game players") }); - Log($"Added {TrainerNID} to the BannedIDs list."); - } - quit = true; - } - - var msg = $"Found {user.TrainerName}{useridmsg} sending to multiple in-game players. Previous OT: {previousEncounter.Name}, Current OT: {TrainerName}"; - if (AbuseSettings.EchoNintendoOnlineIDMultiRecipients) - msg += $"\nID: {TrainerNID}"; - if (!string.IsNullOrWhiteSpace(AbuseSettings.MultiRecipientEchoMention)) - msg = $"{AbuseSettings.MultiRecipientEchoMention} {msg}"; - EchoUtil.Echo(msg); - } - } - - if (quit) - return PokeTradeResult.SuspiciousActivity; - - // Try registering the partner in our list of recently seen. - // Get back the details of their previous interaction. - var previous = isDistribution - ? list.TryRegister(TrainerNID, TrainerName) - : list.TryRegister(TrainerNID, TrainerName, poke.Trainer.ID); - if (previous != null && previous.NetworkID == TrainerNID && previous.RemoteID != user.ID && !isDistribution) - { - var delta = DateTime.Now - previous.Time; - if (delta < TimeSpan.FromMinutes(AbuseSettings.TradeAbuseExpiration) && AbuseSettings.TradeAbuseAction != TradeAbuseAction.Ignore) - { - if (AbuseSettings.TradeAbuseAction == TradeAbuseAction.BlockAndQuit) - { - AbuseSettings.BannedIDs.AddIfNew(new[] { GetReference(TrainerName, TrainerNID, "in-game block for multiple accounts") }); - Log($"Added {TrainerNID} to the BannedIDs list."); - } - quit = true; - } - - var msg = $"Found {user.TrainerName}{useridmsg} using multiple accounts.\nPreviously encountered {previous.Name} ({previous.RemoteID}) {delta.TotalMinutes:F1} minutes ago on OT: {TrainerName}."; - if (AbuseSettings.EchoNintendoOnlineIDMulti) - msg += $"\nID: {TrainerNID}"; - if (!string.IsNullOrWhiteSpace(AbuseSettings.MultiAbuseEchoMention)) - msg = $"{AbuseSettings.MultiAbuseEchoMention} {msg}"; - EchoUtil.Echo(msg); - } - - if (quit) - return PokeTradeResult.SuspiciousActivity; - - var entry = AbuseSettings.BannedIDs.List.Find(z => z.ID == TrainerNID); - if (entry != null) - { - var msg = $"{user.TrainerName}{useridmsg} is a banned user, and was encountered in-game using OT: {TrainerName}."; - if (!string.IsNullOrWhiteSpace(entry.Comment)) - msg += $"\nUser was banned for: {entry.Comment}"; - if (!string.IsNullOrWhiteSpace(AbuseSettings.BannedIDMatchEchoMention)) - msg = $"{AbuseSettings.BannedIDMatchEchoMention} {msg}"; - EchoUtil.Echo(msg); - return PokeTradeResult.SuspiciousActivity; - } - - return PokeTradeResult.Success; - } - - private static RemoteControlAccess GetReference(string name, ulong id, string comment) => new() - { - ID = id, - Name = name, - Comment = $"Added automatically on {DateTime.Now:yyyy.MM.dd-hh:mm:ss} ({comment})", - }; - // based on https://github.com/Muchacho13Scripts/SysBot.NET/commit/f7879386f33bcdbd95c7a56e7add897273867106 // and https://github.com/berichan/SysBot.PLA/commit/84042d4716007dc6ff3100ad4be4a483d622ccf8 private async Task SetBoxPkmWithSwappedIDDetailsPLA(PA8 toSend, TradePartnerLA tradePartner, SAV8LA sav, CancellationToken token) diff --git a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs index 3b9686fbb..987832fa2 100644 --- a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs +++ b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs @@ -994,112 +994,6 @@ private void UpdateBarrier(bool shouldWait) } } - private PokeTradeResult CheckPartnerReputation(PokeTradeDetail poke, ulong TrainerNID, string TrainerName) - { - bool quit = false; - var user = poke.Trainer; - var isDistribution = poke.Type == PokeTradeType.Random; - var useridmsg = isDistribution ? "" : $" ({user.ID})"; - var list = isDistribution ? PreviousUsersDistribution : PreviousUsers; - - var cooldown = list.TryGetPrevious(TrainerNID); - if (cooldown != null) - { - var delta = DateTime.Now - cooldown.Time; - Log($"Last saw {user.TrainerName} {delta.TotalMinutes:F1} minutes ago (OT: {TrainerName})."); - - var cd = AbuseSettings.TradeCooldown; - if (cd != 0 && TimeSpan.FromMinutes(cd) > delta) - { - poke.Notifier.SendNotification(this, poke, "You have ignored the trade cooldown set by the bot owner. The owner has been notified."); - var msg = $"Found {user.TrainerName}{useridmsg} ignoring the {cd} minute trade cooldown. Last encountered {delta.TotalMinutes:F1} minutes ago."; - if (AbuseSettings.EchoNintendoOnlineIDCooldown) - msg += $"\nID: {TrainerNID}"; - if (!string.IsNullOrWhiteSpace(AbuseSettings.CooldownAbuseEchoMention)) - msg = $"{AbuseSettings.CooldownAbuseEchoMention} {msg}"; - EchoUtil.Echo(msg); - quit = true; - } - } - - if (!isDistribution) - { - var previousEncounter = EncounteredUsers.TryRegister(poke.Trainer.ID, TrainerName, poke.Trainer.ID); - if (previousEncounter != null && previousEncounter.Name != TrainerName) - { - if (AbuseSettings.TradeAbuseAction != TradeAbuseAction.Ignore) - { - if (AbuseSettings.TradeAbuseAction == TradeAbuseAction.BlockAndQuit) - { - AbuseSettings.BannedIDs.AddIfNew(new[] { GetReference(TrainerName, TrainerNID, "in-game block for sending to multiple in-game players") }); - Log($"Added {TrainerNID} to the BannedIDs list."); - } - quit = true; - } - - var msg = $"Found {user.TrainerName}{useridmsg} sending to multiple in-game players. Previous OT: {previousEncounter.Name}, Current OT: {TrainerName}"; - if (AbuseSettings.EchoNintendoOnlineIDMultiRecipients) - msg += $"\nID: {TrainerNID}"; - if (!string.IsNullOrWhiteSpace(AbuseSettings.MultiRecipientEchoMention)) - msg = $"{AbuseSettings.MultiRecipientEchoMention} {msg}"; - EchoUtil.Echo(msg); - } - } - - if (quit) - return PokeTradeResult.SuspiciousActivity; - - // Try registering the partner in our list of recently seen. - // Get back the details of their previous interaction. - var previous = isDistribution - ? list.TryRegister(TrainerNID, TrainerName) - : list.TryRegister(TrainerNID, TrainerName, poke.Trainer.ID); - if (previous != null && previous.NetworkID == TrainerNID && previous.RemoteID != user.ID && !isDistribution) - { - var delta = DateTime.Now - previous.Time; - if (delta < TimeSpan.FromMinutes(AbuseSettings.TradeAbuseExpiration) && AbuseSettings.TradeAbuseAction != TradeAbuseAction.Ignore) - { - if (AbuseSettings.TradeAbuseAction == TradeAbuseAction.BlockAndQuit) - { - AbuseSettings.BannedIDs.AddIfNew(new[] { GetReference(TrainerName, TrainerNID, "in-game block for multiple accounts") }); - Log($"Added {TrainerNID} to the BannedIDs list."); - } - quit = true; - } - - var msg = $"Found {user.TrainerName}{useridmsg} using multiple accounts.\nPreviously encountered {previous.Name} ({previous.RemoteID}) {delta.TotalMinutes:F1} minutes ago on OT: {TrainerName}."; - if (AbuseSettings.EchoNintendoOnlineIDMulti) - msg += $"\nID: {TrainerNID}"; - if (!string.IsNullOrWhiteSpace(AbuseSettings.MultiAbuseEchoMention)) - msg = $"{AbuseSettings.MultiAbuseEchoMention} {msg}"; - EchoUtil.Echo(msg); - } - - if (quit) - return PokeTradeResult.SuspiciousActivity; - - var entry = AbuseSettings.BannedIDs.List.Find(z => z.ID == TrainerNID); - if (entry != null) - { - var msg = $"{user.TrainerName}{useridmsg} is a banned user, and was encountered in-game using OT: {TrainerName}."; - if (!string.IsNullOrWhiteSpace(entry.Comment)) - msg += $"\nUser was banned for: {entry.Comment}"; - if (!string.IsNullOrWhiteSpace(AbuseSettings.BannedIDMatchEchoMention)) - msg = $"{AbuseSettings.BannedIDMatchEchoMention} {msg}"; - EchoUtil.Echo(msg); - return PokeTradeResult.SuspiciousActivity; - } - - return PokeTradeResult.Success; - } - - private static RemoteControlAccess GetReference(string name, ulong id, string comment) => new() - { - ID = id, - Name = name, - Comment = $"Added automatically on {DateTime.Now:yyyy.MM.dd-hh:mm:ss} ({comment})", - }; - private async Task SetBoxPkmWithSwappedIDDetailsSV(PK9 toSend, TradeMyStatus tradePartner, SAV9SV sav, CancellationToken token) { if (toSend.Species == (ushort)Species.Ditto) From 01549b229e3897f082713c6d7a26d512d50853e6 Mon Sep 17 00:00:00 2001 From: Easy World Date: Sat, 3 Jun 2023 13:11:45 +0800 Subject: [PATCH 124/171] Update ShowdownTranslatorDictionary.cs --- SysBot.Pokemon/Helpers/ShowdownTranslatorDictionary.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/SysBot.Pokemon/Helpers/ShowdownTranslatorDictionary.cs b/SysBot.Pokemon/Helpers/ShowdownTranslatorDictionary.cs index e93493f7b..b1a20c7ba 100644 --- a/SysBot.Pokemon/Helpers/ShowdownTranslatorDictionary.cs +++ b/SysBot.Pokemon/Helpers/ShowdownTranslatorDictionary.cs @@ -285,6 +285,7 @@ public class ShowdownTranslatorDictionary {"下垂姿势","Droopy"}, {"平挺姿势","Stretchy"}, {"三节形态","Three-Segment"}, + {"徒步","Roaming"}, }; #endregion From 362749c12616c7fd04256356e8a829f62821a9c5 Mon Sep 17 00:00:00 2001 From: Easy World Date: Thu, 7 Sep 2023 23:16:14 +0800 Subject: [PATCH 125/171] fix: compile error --- SysBot.Pokemon/Helpers/AbstractTrade.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SysBot.Pokemon/Helpers/AbstractTrade.cs b/SysBot.Pokemon/Helpers/AbstractTrade.cs index 7aabfe382..ae8a3e798 100644 --- a/SysBot.Pokemon/Helpers/AbstractTrade.cs +++ b/SysBot.Pokemon/Helpers/AbstractTrade.cs @@ -434,7 +434,7 @@ public static void EggTrade(PKM pk, IBattleTemplate template) var la = new LegalityAnalysis(pk); var enc = la.EncounterMatch; - pk.CurrentFriendship = enc is EncounterStatic s ? s.EggCycles : pk.PersonalInfo.HatchCycles; + pk.CurrentFriendship = enc is IHatchCycle s ? s.EggCycles : pk.PersonalInfo.HatchCycles; Span relearn = stackalloc ushort[4]; la.GetSuggestedRelearnMoves(relearn, enc); From e8f0beb6fa9d2238ee862da1109f0d6a312c9be3 Mon Sep 17 00:00:00 2001 From: Easy World Date: Mon, 18 Sep 2023 13:21:10 +0800 Subject: [PATCH 126/171] Update ShowdownTranslatorDictionary.cs --- SysBot.Pokemon/Helpers/ShowdownTranslatorDictionary.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/SysBot.Pokemon/Helpers/ShowdownTranslatorDictionary.cs b/SysBot.Pokemon/Helpers/ShowdownTranslatorDictionary.cs index b1a20c7ba..5e39817e6 100644 --- a/SysBot.Pokemon/Helpers/ShowdownTranslatorDictionary.cs +++ b/SysBot.Pokemon/Helpers/ShowdownTranslatorDictionary.cs @@ -286,6 +286,12 @@ public class ShowdownTranslatorDictionary {"平挺姿势","Stretchy"}, {"三节形态","Three-Segment"}, {"徒步","Roaming"}, + {"赫月","Bloodmoon" }, + {"高档货的样子","Artisan"}, + {"杰作的样子","Masterpiece"}, + {"水井面具","Wellspring"}, + {"火灶面具","Hearthflame"}, + {"础石面具","Cornerstone"}, }; #endregion From 6c1ff73bf0733d2c968f1d4fcc5ddffbf9569e68 Mon Sep 17 00:00:00 2001 From: Easy World Date: Tue, 26 Sep 2023 14:12:01 +0800 Subject: [PATCH 127/171] Match new version --- SysBot.Pokemon/Helpers/AbstractTrade.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SysBot.Pokemon/Helpers/AbstractTrade.cs b/SysBot.Pokemon/Helpers/AbstractTrade.cs index ae8a3e798..0eda36dd5 100644 --- a/SysBot.Pokemon/Helpers/AbstractTrade.cs +++ b/SysBot.Pokemon/Helpers/AbstractTrade.cs @@ -445,7 +445,7 @@ public static void EggTrade(PKM pk, IBattleTemplate template) pk.Move1_PPUps = pk.Move2_PPUps = pk.Move3_PPUps = pk.Move4_PPUps = 0; pk.SetMaximumPPCurrent(pk.Moves); pk.SetSuggestedHyperTrainingData(); - pk.SetSuggestedRibbons(template, enc); + pk.SetSuggestedRibbons(template, enc, true, new List()); } } From 852cb8daf0dbca0fe7524a221fc33e8f7e2168e4 Mon Sep 17 00:00:00 2001 From: Easy World Date: Tue, 24 Oct 2023 08:10:28 +0800 Subject: [PATCH 128/171] Fix SetSuggestedRibbons API --- SysBot.Pokemon/Helpers/AbstractTrade.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SysBot.Pokemon/Helpers/AbstractTrade.cs b/SysBot.Pokemon/Helpers/AbstractTrade.cs index 0eda36dd5..20a5f2727 100644 --- a/SysBot.Pokemon/Helpers/AbstractTrade.cs +++ b/SysBot.Pokemon/Helpers/AbstractTrade.cs @@ -445,7 +445,7 @@ public static void EggTrade(PKM pk, IBattleTemplate template) pk.Move1_PPUps = pk.Move2_PPUps = pk.Move3_PPUps = pk.Move4_PPUps = 0; pk.SetMaximumPPCurrent(pk.Moves); pk.SetSuggestedHyperTrainingData(); - pk.SetSuggestedRibbons(template, enc, true, new List()); + pk.SetSuggestedRibbons(template, enc, true, APILegality.TracebackHandlerType.GetTracebackHandler()); } } From d253855a648a5e5c815d966b53bce94e529d2f18 Mon Sep 17 00:00:00 2001 From: Easy World Date: Thu, 21 Dec 2023 16:29:18 +0800 Subject: [PATCH 129/171] Merge fix --- .../SysBot.Pokemon.Bilibili.csproj | 8 +-- .../SysBot.Pokemon.Dodo.csproj | 7 +-- SysBot.Pokemon.QQ/SysBot.Pokemon.QQ.csproj | 7 --- SysBot.Pokemon/Helpers/AbstractTrade.cs | 4 +- SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs | 53 ++++++++++--------- .../SWSH/BotTrade/PokeTradeBotSWSH.cs | 4 +- 6 files changed, 33 insertions(+), 50 deletions(-) diff --git a/SysBot.Pokemon.Bilibili/SysBot.Pokemon.Bilibili.csproj b/SysBot.Pokemon.Bilibili/SysBot.Pokemon.Bilibili.csproj index e92e81033..8c50a41f4 100644 --- a/SysBot.Pokemon.Bilibili/SysBot.Pokemon.Bilibili.csproj +++ b/SysBot.Pokemon.Bilibili/SysBot.Pokemon.Bilibili.csproj @@ -1,10 +1,4 @@ - - - net7.0 - AnyCPU - enable - 11 - + diff --git a/SysBot.Pokemon.Dodo/SysBot.Pokemon.Dodo.csproj b/SysBot.Pokemon.Dodo/SysBot.Pokemon.Dodo.csproj index e0113ed08..cd0bce8d0 100644 --- a/SysBot.Pokemon.Dodo/SysBot.Pokemon.Dodo.csproj +++ b/SysBot.Pokemon.Dodo/SysBot.Pokemon.Dodo.csproj @@ -1,9 +1,4 @@ - - - - net7.0 - 11 - + diff --git a/SysBot.Pokemon.QQ/SysBot.Pokemon.QQ.csproj b/SysBot.Pokemon.QQ/SysBot.Pokemon.QQ.csproj index 43efb1d08..9172484bb 100644 --- a/SysBot.Pokemon.QQ/SysBot.Pokemon.QQ.csproj +++ b/SysBot.Pokemon.QQ/SysBot.Pokemon.QQ.csproj @@ -1,12 +1,5 @@ - - net7.0 - AnyCPU - enable - 11 - - diff --git a/SysBot.Pokemon/Helpers/AbstractTrade.cs b/SysBot.Pokemon/Helpers/AbstractTrade.cs index 20a5f2727..ff98fc92f 100644 --- a/SysBot.Pokemon/Helpers/AbstractTrade.cs +++ b/SysBot.Pokemon/Helpers/AbstractTrade.cs @@ -1,4 +1,4 @@ -using PKHeX.Core; +using PKHeX.Core; using PKHeX.Core.AutoMod; using SysBot.Base; using System; @@ -445,7 +445,7 @@ public static void EggTrade(PKM pk, IBattleTemplate template) pk.Move1_PPUps = pk.Move2_PPUps = pk.Move3_PPUps = pk.Move4_PPUps = 0; pk.SetMaximumPPCurrent(pk.Moves); pk.SetSuggestedHyperTrainingData(); - pk.SetSuggestedRibbons(template, enc, true, APILegality.TracebackHandlerType.GetTracebackHandler()); + pk.SetSuggestedRibbons(template, enc, true); } } diff --git a/SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs b/SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs index 2223902c8..5618f53d6 100644 --- a/SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs +++ b/SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs @@ -552,6 +552,7 @@ private async Task GetTradePartnerInfo(CancellationToken token) var name = await SwitchConnection.PointerPeek(TradePartnerLA.MaxByteLengthStringObject, Offsets.LinkTradePartnerNamePointer, token).ConfigureAwait(false); var traderOffset = await SwitchConnection.PointerAll(Offsets.LinkTradePartnerTIDPointer, token).ConfigureAwait(false); var idbytes = await SwitchConnection.ReadBytesAbsoluteAsync(traderOffset + 0x04, 4, token).ConfigureAwait(false); + return new TradePartnerLA(id, name, idbytes); } @@ -713,36 +714,36 @@ private void UpdateBarrier(bool shouldWait) Hub.BotSync.Barrier.RemoveParticipant(); Log($"Left the Barrier. Count: {Hub.BotSync.Barrier.ParticipantCount}"); } + } - // based on https://github.com/Muchacho13Scripts/SysBot.NET/commit/f7879386f33bcdbd95c7a56e7add897273867106 - // and https://github.com/berichan/SysBot.PLA/commit/84042d4716007dc6ff3100ad4be4a483d622ccf8 - private async Task SetBoxPkmWithSwappedIDDetailsPLA(PA8 toSend, TradePartnerLA tradePartner, SAV8LA sav, CancellationToken token) - { - var cln = (PA8)toSend.Clone(); - cln.OT_Gender = tradePartner.Gender; - cln.TrainerTID7 = uint.Parse(tradePartner.TID7); - cln.TrainerSID7 = uint.Parse(tradePartner.SID7); - cln.Language = tradePartner.Language; - cln.OT_Name = tradePartner.TrainerName; - cln.ClearNickname(); + // based on https://github.com/Muchacho13Scripts/SysBot.NET/commit/f7879386f33bcdbd95c7a56e7add897273867106 + // and https://github.com/berichan/SysBot.PLA/commit/84042d4716007dc6ff3100ad4be4a483d622ccf8 + private async Task SetBoxPkmWithSwappedIDDetailsPLA(PA8 toSend, TradePartnerLA tradePartner, SAV8LA sav, CancellationToken token) + { + var cln = (PA8)toSend.Clone(); + cln.OT_Gender = tradePartner.Gender; + cln.TrainerTID7 = uint.Parse(tradePartner.TID7); + cln.TrainerSID7 = uint.Parse(tradePartner.SID7); + cln.Language = tradePartner.Language; + cln.OT_Name = tradePartner.TrainerName; + cln.ClearNickname(); - if (toSend.IsShiny) - cln.SetShiny(); + if (toSend.IsShiny) + cln.SetShiny(); - cln.RefreshChecksum(); + cln.RefreshChecksum(); - var tradela = new LegalityAnalysis(cln); - if (tradela.Valid) - { - Log($"Pokemon is valid, use trade partnerInfo"); - await SetBoxPokemonAbsolute(BoxStartOffset, cln, token, sav).ConfigureAwait(false); - } - else - { - Log($"Pokemon not valid, do nothing to trade Pokemon"); - } - - return tradela.Valid; + var tradela = new LegalityAnalysis(cln); + if (tradela.Valid) + { + Log($"Pokemon is valid, use trade partnerInfo"); + await SetBoxPokemonAbsolute(BoxStartOffset, cln, token, sav).ConfigureAwait(false); } + else + { + Log($"Pokemon not valid, do nothing to trade Pokemon"); + } + + return tradela.Valid; } } diff --git a/SysBot.Pokemon/SWSH/BotTrade/PokeTradeBotSWSH.cs b/SysBot.Pokemon/SWSH/BotTrade/PokeTradeBotSWSH.cs index 85230e7c0..b26dfe890 100644 --- a/SysBot.Pokemon/SWSH/BotTrade/PokeTradeBotSWSH.cs +++ b/SysBot.Pokemon/SWSH/BotTrade/PokeTradeBotSWSH.cs @@ -321,7 +321,7 @@ private async Task PerformLinkCodeTrade(SAV8SWSH sav, PokeTrade return PokeTradeResult.RecoverOpenBox; } - if (Hub.Config.Legality.UseTradePartnerInfo) + if (hub.Config.Legality.UseTradePartnerInfo) { await SetPkmWithSwappedIDDetails(toSend, trainerName, sav, token); } @@ -998,4 +998,4 @@ private async Task SetPkmWithSwappedIDDetails(PK8 toSend, string trainerNa return tradeswsh.Valid; } -} \ No newline at end of file +} From f660781e7ed0783792aa81a6e55f69b76f392f2f Mon Sep 17 00:00:00 2001 From: xiaolong11123 Date: Fri, 22 Dec 2023 17:19:04 +0800 Subject: [PATCH 130/171] =?UTF-8?q?Fix=20New=20Paradox=5FPok=C3=A9mon=20do?= =?UTF-8?q?=20not=20use=20trade=20partner=20info=20for=20SV?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs index b67a83593..9a7ab797b 100644 --- a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs +++ b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs @@ -998,15 +998,15 @@ private async Task SetBoxPkmWithSwappedIDDetailsSV(PK9 toSend, TradeMyStat cln.OT_Name = tradePartner.OT; // copied from https://github.com/Wanghaoran86/TransFireBot/commit/f7c5b39ce2952818177a97babb8b3df027e673fb - if (toSend.Species == (ushort)Species.Koraidon) + if (toSend.Species == (ushort)Species.Koraidon || toSend.Species == (ushort)Species.GougingFire || toSend.Species == (ushort)Species.RagingBolt) { cln.Version = (int)GameVersion.SL; - Log("故勒顿,强制修改版本为朱"); + Log("朱版本限定宝可梦,强制修改版本为朱"); } - else if (toSend.Species == (ushort)Species.Miraidon) + else if (toSend.Species == (ushort)Species.Miraidon || toSend.Species == (ushort)Species.IronCrown || toSend.Species == (ushort)Species.IronBoulder) { cln.Version = (int)GameVersion.VL; - Log("密勒顿,强制修改版本为紫"); + Log("紫版本限定宝可梦,强制修改版本为紫"); } else { From 86145f5fef4a229570bc44d27212c5eb89612124 Mon Sep 17 00:00:00 2001 From: Easy World Date: Fri, 22 Dec 2023 17:53:28 +0800 Subject: [PATCH 131/171] Update PokeTradeBotSV.cs --- SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs | 34 ++++++++++++-------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs index 9a7ab797b..68d2061d8 100644 --- a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs +++ b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs @@ -998,20 +998,28 @@ private async Task SetBoxPkmWithSwappedIDDetailsSV(PK9 toSend, TradeMyStat cln.OT_Name = tradePartner.OT; // copied from https://github.com/Wanghaoran86/TransFireBot/commit/f7c5b39ce2952818177a97babb8b3df027e673fb - if (toSend.Species == (ushort)Species.Koraidon || toSend.Species == (ushort)Species.GougingFire || toSend.Species == (ushort)Species.RagingBolt) + ushort species = toSend.Species; + GameVersion version; + switch (species) { - cln.Version = (int)GameVersion.SL; - Log("朱版本限定宝可梦,强制修改版本为朱"); - } - else if (toSend.Species == (ushort)Species.Miraidon || toSend.Species == (ushort)Species.IronCrown || toSend.Species == (ushort)Species.IronBoulder) - { - cln.Version = (int)GameVersion.VL; - Log("紫版本限定宝可梦,强制修改版本为紫"); - } - else - { - cln.Version = tradePartner.Game; + case (ushort)Species.Koraidon: + case (ushort)Species.GougingFire: + case (ushort)Species.RagingBolt: + version = GameVersion.SL; + Log("朱版本限定宝可梦,强制修改版本为朱"); + break; + case (ushort)Species.Miraidon: + case (ushort)Species.IronCrown: + case (ushort)Species.IronBoulder: + version = GameVersion.VL; + Log("紫版本限定宝可梦,强制修改版本为紫"); + break; + default: + version = tradePartner.Game; + break; } + cln.Version = (int)version; + cln.ClearNickname(); // thanks @Wanghaoran86 @@ -1039,4 +1047,4 @@ private async Task SetBoxPkmWithSwappedIDDetailsSV(PK9 toSend, TradeMyStat return tradeSV.Valid; } -} \ No newline at end of file +} From c612962252da48d46253188529d2da33e4043d41 Mon Sep 17 00:00:00 2001 From: Easy World Date: Tue, 2 Jan 2024 21:35:46 +0800 Subject: [PATCH 132/171] Fix compile error --- SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs index 68d2061d8..3dc1010b4 100644 --- a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs +++ b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs @@ -1015,7 +1015,7 @@ private async Task SetBoxPkmWithSwappedIDDetailsSV(PK9 toSend, TradeMyStat Log("紫版本限定宝可梦,强制修改版本为紫"); break; default: - version = tradePartner.Game; + version = (GameVersion)tradePartner.Game; break; } cln.Version = (int)version; From a5f8a692740ab1194444e69979182ee8f0a14c37 Mon Sep 17 00:00:00 2001 From: Easy World Date: Sun, 14 Jan 2024 21:15:37 +0800 Subject: [PATCH 133/171] Fix compile error --- SysBot.Pokemon/Helpers/AbstractTrade.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/SysBot.Pokemon/Helpers/AbstractTrade.cs b/SysBot.Pokemon/Helpers/AbstractTrade.cs index ff98fc92f..494d69833 100644 --- a/SysBot.Pokemon/Helpers/AbstractTrade.cs +++ b/SysBot.Pokemon/Helpers/AbstractTrade.cs @@ -392,12 +392,7 @@ public static void EggTrade(PKM pk, IBattleTemplate template) pk.StatNature = pk.Nature; pk.SetEVs(new int[] { 0, 0, 0, 0, 0, 0 }); - pk.SetMarking(0, 0); - pk.SetMarking(1, 0); - pk.SetMarking(2, 0); - pk.SetMarking(3, 0); - pk.SetMarking(4, 0); - pk.SetMarking(5, 0); + MarkingApplicator.SetMarkings(pk); pk.ClearRelearnMoves(); From 59d12cfc2e913fd33fa4ac0f0bf79d15a1fdff4c Mon Sep 17 00:00:00 2001 From: Easy World Date: Mon, 26 Feb 2024 10:49:55 +0800 Subject: [PATCH 134/171] Match new version of PKHeX.core --- SysBot.Pokemon/Helpers/AbstractTrade.cs | 42 +++++++++---------- SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs | 4 +- SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs | 8 ++-- .../SWSH/BotTrade/PokeTradeBotSWSH.cs | 4 +- 4 files changed, 29 insertions(+), 29 deletions(-) diff --git a/SysBot.Pokemon/Helpers/AbstractTrade.cs b/SysBot.Pokemon/Helpers/AbstractTrade.cs index 494d69833..08aa15fc5 100644 --- a/SysBot.Pokemon/Helpers/AbstractTrade.cs +++ b/SysBot.Pokemon/Helpers/AbstractTrade.cs @@ -366,7 +366,7 @@ public static void EggTrade(PKM pk, IBattleTemplate template) }; pk.IsEgg = true; - pk.Egg_Location = pk switch + pk.EggLocation = pk switch { PB8 => 60010, PK9 => 30023, @@ -376,8 +376,8 @@ public static void EggTrade(PKM pk, IBattleTemplate template) pk.HeldItem = 0; pk.CurrentLevel = 1; pk.EXP = 0; - pk.Met_Level = 1; - pk.Met_Location = pk switch + pk.MetLevel = 1; + pk.MetLocation = pk switch { PB8 => 65535, PK9 => 0, @@ -385,9 +385,9 @@ public static void EggTrade(PKM pk, IBattleTemplate template) }; pk.CurrentHandler = 0; - pk.OT_Friendship = 1; - pk.HT_Name = ""; - pk.HT_Friendship = 0; + pk.OriginalTrainerFriendship = 1; + pk.HandlingTrainerName = ""; + pk.HandlingTrainerFriendship = 0; pk.ClearMemories(); pk.StatNature = pk.Nature; pk.SetEVs(new int[] { 0, 0, 0, 0, 0, 0 }); @@ -398,29 +398,29 @@ public static void EggTrade(PKM pk, IBattleTemplate template) if (pk is PK8 pk8) { - pk8.HT_Language = 0; - pk8.HT_Gender = 0; - pk8.HT_Memory = 0; - pk8.HT_Feeling = 0; - pk8.HT_Intensity = 0; + pk8.HandlingTrainerLanguage = 0; + pk8.HandlingTrainerGender = 0; + pk8.HandlingTrainerMemory = 0; + pk8.HandlingTrainerMemoryFeeling = 0; + pk8.HandlingTrainerMemoryIntensity = 0; pk8.DynamaxLevel = pk8.GetSuggestedDynamaxLevel(pk8, 0); } else if (pk is PB8 pb8) { - pb8.HT_Language = 0; - pb8.HT_Gender = 0; - pb8.HT_Memory = 0; - pb8.HT_Feeling = 0; - pb8.HT_Intensity = 0; + pb8.HandlingTrainerLanguage = 0; + pb8.HandlingTrainerGender = 0; + pb8.HandlingTrainerMemory = 0; + pb8.HandlingTrainerMemoryFeeling = 0; + pb8.HandlingTrainerMemoryIntensity = 0; pb8.DynamaxLevel = pb8.GetSuggestedDynamaxLevel(pb8, 0); } else if (pk is PK9 pk9) { - pk9.HT_Language = 0; - pk9.HT_Gender = 0; - pk9.HT_Memory = 0; - pk9.HT_Feeling = 0; - pk9.HT_Intensity = 0; + pk9.HandlingTrainerLanguage = 0; + pk9.HandlingTrainerGender = 0; + pk9.HandlingTrainerMemory = 0; + pk9.HandlingTrainerMemoryFeeling = 0; + pk9.HandlingTrainerMemoryIntensity = 0; pk9.Obedience_Level = 1; pk9.Version = 0; pk9.BattleVersion = 0; diff --git a/SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs b/SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs index 522c7782c..299af74f5 100644 --- a/SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs +++ b/SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs @@ -721,11 +721,11 @@ private void UpdateBarrier(bool shouldWait) private async Task SetBoxPkmWithSwappedIDDetailsPLA(PA8 toSend, TradePartnerLA tradePartner, SAV8LA sav, CancellationToken token) { var cln = (PA8)toSend.Clone(); - cln.OT_Gender = tradePartner.Gender; + cln.OriginalTrainerGender = tradePartner.Gender; cln.TrainerTID7 = uint.Parse(tradePartner.TID7); cln.TrainerSID7 = uint.Parse(tradePartner.SID7); cln.Language = tradePartner.Language; - cln.OT_Name = tradePartner.TrainerName; + cln.OriginalTrainerName = tradePartner.TrainerName; cln.ClearNickname(); if (toSend.IsShiny) diff --git a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs index 0505d8037..415432a57 100644 --- a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs +++ b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs @@ -991,11 +991,11 @@ private async Task SetBoxPkmWithSwappedIDDetailsSV(PK9 toSend, TradeMyStat return false; } var cln = toSend.Clone(); - cln.OT_Gender = tradePartner.Gender; + cln.OriginalTrainerGender = (byte)tradePartner.Gender; cln.TrainerTID7 = (uint)Math.Abs(tradePartner.DisplayTID); cln.TrainerSID7 = (uint)Math.Abs(tradePartner.DisplaySID); cln.Language = tradePartner.Language; - cln.OT_Name = tradePartner.OT; + cln.OriginalTrainerName = tradePartner.OT; // copied from https://github.com/Wanghaoran86/TransFireBot/commit/f7c5b39ce2952818177a97babb8b3df027e673fb ushort species = toSend.Species; @@ -1018,12 +1018,12 @@ private async Task SetBoxPkmWithSwappedIDDetailsSV(PK9 toSend, TradeMyStat version = (GameVersion)tradePartner.Game; break; } - cln.Version = (int)version; + cln.Version = version; cln.ClearNickname(); // thanks @Wanghaoran86 - if (toSend.Met_Location == Locations.TeraCavern9 && toSend.IsShiny) + if (toSend.MetLocation == Locations.TeraCavern9 && toSend.IsShiny) { cln.PID = (((uint)(cln.TID16 ^ cln.SID16) ^ (cln.PID & 0xFFFF) ^ 1u) << 16) | (cln.PID & 0xFFFF); } diff --git a/SysBot.Pokemon/SWSH/BotTrade/PokeTradeBotSWSH.cs b/SysBot.Pokemon/SWSH/BotTrade/PokeTradeBotSWSH.cs index ce54511c5..1b456870a 100644 --- a/SysBot.Pokemon/SWSH/BotTrade/PokeTradeBotSWSH.cs +++ b/SysBot.Pokemon/SWSH/BotTrade/PokeTradeBotSWSH.cs @@ -973,11 +973,11 @@ private async Task SetPkmWithSwappedIDDetails(PK8 toSend, string trainerNa var data = await Connection.ReadBytesAsync(LinkTradePartnerNameOffset - 0x8, 8, token).ConfigureAwait(false); var tidsid = BitConverter.ToUInt32(data, 0); var cln = (PK8)toSend.Clone(); - cln.OT_Gender = data[6]; + cln.OriginalTrainerGender = data[6]; cln.TrainerTID7 = tidsid % 1_000_000; cln.TrainerSID7 = tidsid / 1_000_000; cln.Language = data[5]; - cln.OT_Name = trainerName; + cln.OriginalTrainerName = trainerName; cln.ClearNickname(); if (toSend.IsShiny) From 293b2987e5bbe6d2d3efbe941fc1ef004a250fb0 Mon Sep 17 00:00:00 2001 From: Easy World Date: Sun, 31 Mar 2024 21:49:42 +0800 Subject: [PATCH 135/171] Remove GenerationFix --- SysBot.Pokemon/Helpers/AbstractTrade.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/SysBot.Pokemon/Helpers/AbstractTrade.cs b/SysBot.Pokemon/Helpers/AbstractTrade.cs index 08aa15fc5..e602e088f 100644 --- a/SysBot.Pokemon/Helpers/AbstractTrade.cs +++ b/SysBot.Pokemon/Helpers/AbstractTrade.cs @@ -274,7 +274,6 @@ public bool CheckAndGetPkm(string setstring, out string msg, out T outPkm) try { var sav = AutoLegalityWrapper.GetTrainerInfo(); - GenerationFix(sav); var pkm = sav.GetLegal(template, out var result); if (pkm.Nickname.ToLower() == "egg" && Breeding.CanHatchAsEgg(pkm.Species)) EggTrade(pkm, template); if (Check((T)pkm, out msg)) @@ -292,11 +291,6 @@ public bool CheckAndGetPkm(string setstring, out string msg, out T outPkm) return false; } - private static void GenerationFix(ITrainerInfo sav) - { - if (typeof(T) == typeof(PK8) || typeof(T) == typeof(PB8) || typeof(T) == typeof(PA8)) sav.GetType().GetProperty("Generation")?.SetValue(sav, 8); - } - private bool AddToTradeQueue(T pk, int code, bool skipAutoOT, PokeRoutineType type, out string msg) { From f34cd365dbcd2a1d1a13c61c9a2c24fe328daaf7 Mon Sep 17 00:00:00 2001 From: Easy World Date: Wed, 5 Jun 2024 21:23:23 +0800 Subject: [PATCH 136/171] Fix compile error --- SysBot.Pokemon/Helpers/AbstractTrade.cs | 2 +- SysBot.Tests/TranslatorTests.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/SysBot.Pokemon/Helpers/AbstractTrade.cs b/SysBot.Pokemon/Helpers/AbstractTrade.cs index e602e088f..57eac86e5 100644 --- a/SysBot.Pokemon/Helpers/AbstractTrade.cs +++ b/SysBot.Pokemon/Helpers/AbstractTrade.cs @@ -415,7 +415,7 @@ public static void EggTrade(PKM pk, IBattleTemplate template) pk9.HandlingTrainerMemory = 0; pk9.HandlingTrainerMemoryFeeling = 0; pk9.HandlingTrainerMemoryIntensity = 0; - pk9.Obedience_Level = 1; + pk9.ObedienceLevel = 1; pk9.Version = 0; pk9.BattleVersion = 0; pk9.TeraTypeOverride = (MoveType)19; diff --git a/SysBot.Tests/TranslatorTests.cs b/SysBot.Tests/TranslatorTests.cs index e751f7c9d..4429f5511 100644 --- a/SysBot.Tests/TranslatorTests.cs +++ b/SysBot.Tests/TranslatorTests.cs @@ -1,4 +1,4 @@ -using FluentAssertions; +using FluentAssertions; using PKHeX.Core; using SysBot.Pokemon; using SysBot.Pokemon.Helpers; @@ -9,7 +9,7 @@ namespace SysBot.Tests { public class TranslatorTests { - static TranslatorTests() => AutoLegalityWrapper.EnsureInitialized(new LegalitySettings()); + static TranslatorTests() => AutoLegalityWrapper.EnsureInitialized(new Pokemon.LegalitySettings()); [Theory] [InlineData("公肯泰罗携带大师球6V异色努力值252生命全招式异国-泰山压顶", "Tauros (M) @ Master Ball\nShiny: Yes\nIVs: 31 HP / 31 Atk / 31 Def / 31 SpA / 31 SpD / 31 Spe\nEVs: 252 HP \n.RelearnMoves=$suggestAll\nLanguage: Italian\n-Body Slam")] From 9a4444d297aa24cfac8659b2d605617bf5612b10 Mon Sep 17 00:00:00 2001 From: Easy World Date: Thu, 4 Jul 2024 21:00:23 +0800 Subject: [PATCH 137/171] TrashBytes tweak --- SysBot.Pokemon/Helpers/AbstractTrade.cs | 1 + SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/SysBot.Pokemon/Helpers/AbstractTrade.cs b/SysBot.Pokemon/Helpers/AbstractTrade.cs index 57eac86e5..ec1cdf3aa 100644 --- a/SysBot.Pokemon/Helpers/AbstractTrade.cs +++ b/SysBot.Pokemon/Helpers/AbstractTrade.cs @@ -347,6 +347,7 @@ private bool AddToTradeQueue(List pks, int code, List skipAutoOTList, public static void EggTrade(PKM pk, IBattleTemplate template) { pk.IsNicknamed = true; + pk.NicknameTrash.Clear(); pk.Nickname = pk.Language switch { 1 => "タマゴ", diff --git a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs index 415432a57..9c9271c9c 100644 --- a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs +++ b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs @@ -995,6 +995,7 @@ private async Task SetBoxPkmWithSwappedIDDetailsSV(PK9 toSend, TradeMyStat cln.TrainerTID7 = (uint)Math.Abs(tradePartner.DisplayTID); cln.TrainerSID7 = (uint)Math.Abs(tradePartner.DisplaySID); cln.Language = tradePartner.Language; + cln.OriginalTrainerTrash.Clear(); cln.OriginalTrainerName = tradePartner.OT; // copied from https://github.com/Wanghaoran86/TransFireBot/commit/f7c5b39ce2952818177a97babb8b3df027e673fb From 7ae5867ae5fab9c5af34ab4d2def78bcc1f3ebb0 Mon Sep 17 00:00:00 2001 From: Easy World Date: Thu, 4 Jul 2024 23:10:03 +0800 Subject: [PATCH 138/171] Fix unexpected nickname --- SysBot.Pokemon/Helpers/AbstractTrade.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SysBot.Pokemon/Helpers/AbstractTrade.cs b/SysBot.Pokemon/Helpers/AbstractTrade.cs index ec1cdf3aa..214d24157 100644 --- a/SysBot.Pokemon/Helpers/AbstractTrade.cs +++ b/SysBot.Pokemon/Helpers/AbstractTrade.cs @@ -347,7 +347,7 @@ private bool AddToTradeQueue(List pks, int code, List skipAutoOTList, public static void EggTrade(PKM pk, IBattleTemplate template) { pk.IsNicknamed = true; - pk.NicknameTrash.Clear(); + pk.Nickname = SpeciesName.GetSpeciesNameGeneration(pk.Species, pk.Language, pk.Format); pk.Nickname = pk.Language switch { 1 => "タマゴ", From a97645530944497b11e730104344b40e5daa7e9d Mon Sep 17 00:00:00 2001 From: Easy World Date: Sun, 7 Jul 2024 21:19:57 +0800 Subject: [PATCH 139/171] Egg needs to retain the nickname --- SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs index 9c9271c9c..fcd136d1f 100644 --- a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs +++ b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs @@ -1021,8 +1021,12 @@ private async Task SetBoxPkmWithSwappedIDDetailsSV(PK9 toSend, TradeMyStat } cln.Version = version; - cln.ClearNickname(); - + // egg needs to retain the nickname + if (!cln.IsEgg) + { + cln.ClearNickname(); + } + // thanks @Wanghaoran86 if (toSend.MetLocation == Locations.TeraCavern9 && toSend.IsShiny) { From 0e6d86867232fe7467115fb83cc43af07f818305 Mon Sep 17 00:00:00 2001 From: Easy World Date: Wed, 18 Sep 2024 21:49:52 +0800 Subject: [PATCH 140/171] Use new language code --- SysBot.Pokemon/Helpers/AbstractTrade.cs | 2 +- SysBot.Pokemon/Helpers/ShowdownTranslator.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SysBot.Pokemon/Helpers/AbstractTrade.cs b/SysBot.Pokemon/Helpers/AbstractTrade.cs index 214d24157..04a1dcebb 100644 --- a/SysBot.Pokemon/Helpers/AbstractTrade.cs +++ b/SysBot.Pokemon/Helpers/AbstractTrade.cs @@ -106,7 +106,7 @@ public void StartTradeMultiPKM(List rawPkms) } else { - LogUtil.LogInfo($"批量第{i + 1}只:{GameInfo.GetStrings("zh").Species[rawPkms[i].Species]}", nameof(AbstractTrade)); + LogUtil.LogInfo($"批量第{i + 1}只:{GameInfo.GetStrings("zh-Hans").Species[rawPkms[i].Species]}", nameof(AbstractTrade)); skipAutoOTList.Add(false); pkms.Add(rawPkms[i]); } diff --git a/SysBot.Pokemon/Helpers/ShowdownTranslator.cs b/SysBot.Pokemon/Helpers/ShowdownTranslator.cs index ffdc34091..2726fe073 100644 --- a/SysBot.Pokemon/Helpers/ShowdownTranslator.cs +++ b/SysBot.Pokemon/Helpers/ShowdownTranslator.cs @@ -8,7 +8,7 @@ namespace SysBot.Pokemon { public class ShowdownTranslator where T : PKM { - public static GameStrings GameStringsZh = GameInfo.GetStrings("zh"); + public static GameStrings GameStringsZh = GameInfo.GetStrings("zh-Hans"); public static GameStrings GameStringsEn = GameInfo.GetStrings("en"); public static string Chinese2Showdown(string zh) { From 6874bbed1525a03b2e22afbfbaa2eed59db359c8 Mon Sep 17 00:00:00 2001 From: Easy World Date: Tue, 26 Nov 2024 21:48:48 +0800 Subject: [PATCH 141/171] using file scoped namespaces --- SysBot.Pokemon.Bilibili/BilibiliLiveBot.cs | 260 ++++--- .../BilibiliTradeNotifier.cs | 140 ++-- SysBot.Pokemon.Dodo/DodoBot.cs | 175 +++-- SysBot.Pokemon.Dodo/DodoTrade.cs | 43 +- SysBot.Pokemon.Dodo/DodoTradeNotifier.cs | 211 +++--- SysBot.Pokemon.Dodo/PokemonProcessService.cs | 292 ++++--- SysBot.Pokemon.QQ/MiraiQQBot.cs | 146 ++-- SysBot.Pokemon.QQ/MiraiQQTrade.cs | 43 +- SysBot.Pokemon.QQ/MiraiQQTradeNotifier.cs | 186 +++-- SysBot.Pokemon.QQ/Modules/AliveModule.cs | 24 +- SysBot.Pokemon.QQ/Modules/CommandModule.cs | 80 +- SysBot.Pokemon.QQ/Modules/FileModule.cs | 81 +- SysBot.Pokemon.QQ/Modules/PsModule.cs | 112 ++- SysBot.Pokemon/Helpers/AbstractTrade.cs | 710 +++++++++--------- SysBot.Pokemon/Helpers/FileTradeHelper.cs | 146 ++-- SysBot.Pokemon/Helpers/ShowdownTranslator.cs | 414 +++++----- .../Helpers/ShowdownTranslatorDictionary.cs | 572 +++++++------- SysBot.Tests/FileTradeHelperTests.cs | 64 +- SysBot.Tests/TranslatorTests.cs | 75 +- 19 files changed, 1869 insertions(+), 1905 deletions(-) diff --git a/SysBot.Pokemon.Bilibili/BilibiliLiveBot.cs b/SysBot.Pokemon.Bilibili/BilibiliLiveBot.cs index aee10df99..2d5ec6a12 100644 --- a/SysBot.Pokemon.Bilibili/BilibiliLiveBot.cs +++ b/SysBot.Pokemon.Bilibili/BilibiliLiveBot.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Linq; using System.Threading; @@ -6,170 +6,168 @@ using PKHeX.Core; using SysBot.Base; -namespace SysBot.Pokemon.Bilibili +namespace SysBot.Pokemon.Bilibili; +public class BilibiliLiveBot where T : PKM, new() { - public class BilibiliLiveBot where T : PKM, new() - { - // private readonly BilibiliSettings Settings; - private static PokeTradeHub Hub = default!; - internal static TradeQueueInfo Info => Hub.Queues.Info; + // private readonly BilibiliSettings Settings; + private static PokeTradeHub Hub = default!; + internal static TradeQueueInfo Info => Hub.Queues.Info; - private static readonly string DefaultUserName = "b站用户"; - private static readonly string LogIdentity = "b站直播"; - private static readonly ulong DefaultUserId = 2333; + private static readonly string DefaultUserName = "b站用户"; + private static readonly string LogIdentity = "b站直播"; + private static readonly ulong DefaultUserId = 2333; - public BilibiliLiveBot(BilibiliSettings settings, PokeTradeHub hub) + public BilibiliLiveBot(BilibiliSettings settings, PokeTradeHub hub) + { + Hub = hub; + File.WriteAllText(@"msg.txt", "等待命令"); + LogUtil.LogInfo($"日志路径:{System.Environment.CurrentDirectory}\\msg.txt", LogIdentity); + Task.Run(() => { - Hub = hub; - File.WriteAllText(@"msg.txt", "等待命令"); - LogUtil.LogInfo($"日志路径:{System.Environment.CurrentDirectory}\\msg.txt", LogIdentity); - Task.Run(() => + var currentDanmu = ""; + while (true) { - var currentDanmu = ""; - while (true) + Thread.Sleep(1000); + var date = DateTime.Now.ToString("yyyyMMdd"); + string last = ""; + try { - Thread.Sleep(1000); - var date = DateTime.Now.ToString("yyyyMMdd"); - string last = ""; - try - { - var path = settings.LogUrl; - if (settings.LogUrl.EndsWith("/") || settings.LogUrl.EndsWith("\\")) - { - path.Remove(path.Length - 1); - } - - last = File.ReadLines(@$"{path}\Log-{settings.RoomId}-{date}.txt") - .Last(); - } - catch (Exception ex) + var path = settings.LogUrl; + if (settings.LogUrl.EndsWith("/") || settings.LogUrl.EndsWith("\\")) { - LogUtil.LogSafe(ex, LogIdentity); + path.Remove(path.Length - 1); } - if (string.IsNullOrWhiteSpace(last) || currentDanmu == last) continue; - currentDanmu = last; - - - string[] split = last.Split(':'); - if (split.Length != 2 || Info.Count != 0) continue; - var showdown = ShowdownTranslator.Chinese2Showdown(split[1]); - if (showdown.Length <= 0) continue; - LogUtil.LogInfo($"收到命令\n{showdown}", LogIdentity); - var _ = CheckAndGetPkm(showdown, DefaultUserName, out var msg, out var pkm); - if (!_) continue; - var code = Info.GetRandomTradeCode(); - File.WriteAllText(@"msg.txt", - $"派送:{ShowdownTranslator.GameStringsZh.Species[pkm.Species]}\n密码:{code:0000 0000}"); - var __ = AddToTradeQueue(pkm, code, DefaultUserId, DefaultUserName, RequestSignificance.Favored, - PokeRoutineType.LinkTrade, out string message); + last = File.ReadLines(@$"{path}\Log-{settings.RoomId}-{date}.txt") + .Last(); } - }); + catch (Exception ex) + { + LogUtil.LogSafe(ex, LogIdentity); + } + + if (string.IsNullOrWhiteSpace(last) || currentDanmu == last) continue; + currentDanmu = last; + + + string[] split = last.Split(':'); + if (split.Length != 2 || Info.Count != 0) continue; + var showdown = ShowdownTranslator.Chinese2Showdown(split[1]); + if (showdown.Length <= 0) continue; + LogUtil.LogInfo($"收到命令\n{showdown}", LogIdentity); + var _ = CheckAndGetPkm(showdown, DefaultUserName, out var msg, out var pkm); + if (!_) continue; + var code = Info.GetRandomTradeCode(); + File.WriteAllText(@"msg.txt", + $"派送:{ShowdownTranslator.GameStringsZh.Species[pkm.Species]}\n密码:{code:0000 0000}"); + var __ = AddToTradeQueue(pkm, code, DefaultUserId, DefaultUserName, RequestSignificance.Favored, + PokeRoutineType.LinkTrade, out string message); + } + }); + } + + + public static bool CheckAndGetPkm(string setstring, string username, out string msg, out T outPkm) + { + outPkm = new T(); + if (!BilibiliLiveBot.Info.GetCanQueue()) + { + msg = "Sorry, I am not currently accepting queue requests!"; + return false; } + var set = ShowdownUtil.ConvertToShowdown(setstring); + if (set == null) + { + msg = $"Skipping trade, @{username}: Empty nickname provided for the species."; + return false; + } - public static bool CheckAndGetPkm(string setstring, string username, out string msg, out T outPkm) + var template = AutoLegalityWrapper.GetTemplate(set); + if (template.Species < 1) { - outPkm = new T(); - if (!BilibiliLiveBot.Info.GetCanQueue()) - { - msg = "Sorry, I am not currently accepting queue requests!"; - return false; - } + msg = + $"Skipping trade, @{username}: Please read what you are supposed to type as the command argument."; + return false; + } - var set = ShowdownUtil.ConvertToShowdown(setstring); - if (set == null) - { - msg = $"Skipping trade, @{username}: Empty nickname provided for the species."; - return false; - } + if (set.InvalidLines.Count != 0) + { + msg = + $"Skipping trade, @{username}: Unable to parse Showdown Set:\n{string.Join("\n", set.InvalidLines)}"; + return false; + } - var template = AutoLegalityWrapper.GetTemplate(set); - if (template.Species < 1) - { - msg = - $"Skipping trade, @{username}: Please read what you are supposed to type as the command argument."; - return false; - } + try + { + var sav = AutoLegalityWrapper.GetTrainerInfo(); + var pkm = sav.GetLegal(template, out var result); - if (set.InvalidLines.Count != 0) + if (!pkm.CanBeTraded()) { - msg = - $"Skipping trade, @{username}: Unable to parse Showdown Set:\n{string.Join("\n", set.InvalidLines)}"; + msg = $"Skipping trade, @{username}: Provided Pokemon content is blocked from trading!"; return false; } - try + if (pkm is T pk) { - var sav = AutoLegalityWrapper.GetTrainerInfo(); - var pkm = sav.GetLegal(template, out var result); - - if (!pkm.CanBeTraded()) - { - msg = $"Skipping trade, @{username}: Provided Pokemon content is blocked from trading!"; - return false; - } - - if (pkm is T pk) + var valid = new LegalityAnalysis(pkm).Valid; + if (valid) { - var valid = new LegalityAnalysis(pkm).Valid; - if (valid) - { - outPkm = pk; + outPkm = pk; - msg = - $"@{username} - added to the waiting list. Your request from the waiting list will be removed if you are too slow!"; - return true; - } + msg = + $"@{username} - added to the waiting list. Your request from the waiting list will be removed if you are too slow!"; + return true; } - - var reason = result == "Timeout" - ? "Set took too long to generate." - : "Unable to legalize the Pokemon."; - msg = $"Skipping trade, @{username}: {reason}"; } + + var reason = result == "Timeout" + ? "Set took too long to generate." + : "Unable to legalize the Pokemon."; + msg = $"Skipping trade, @{username}: {reason}"; + } #pragma warning disable CA1031 // Do not catch general exception types - catch (Exception ex) + catch (Exception ex) #pragma warning restore CA1031 // Do not catch general exception types - { - LogUtil.LogSafe(ex, nameof(BilibiliLiveBot)); - msg = $"Skipping trade, @{username}: An unexpected problem occurred."; - } - - return false; + { + LogUtil.LogSafe(ex, nameof(BilibiliLiveBot)); + msg = $"Skipping trade, @{username}: An unexpected problem occurred."; } - private static bool AddToTradeQueue(T pk, int code, ulong userId, string name, RequestSignificance sig, - PokeRoutineType type, out string msg) - { - var trainer = new PokeTradeTrainerInfo(name, userId); - var notifier = new BilibiliTradeNotifier(pk, trainer, code, name); - var tt = type == PokeRoutineType.SeedCheck ? PokeTradeType.Seed : PokeTradeType.Specific; - var detail = - new PokeTradeDetail(pk, trainer, notifier, tt, code, sig == RequestSignificance.Favored); - var trade = new TradeEntry(detail, userId, type, name); + return false; + } - var added = Info.AddToTradeQueue(trade, userId, sig == RequestSignificance.Owner); + private static bool AddToTradeQueue(T pk, int code, ulong userId, string name, RequestSignificance sig, + PokeRoutineType type, out string msg) + { + var trainer = new PokeTradeTrainerInfo(name, userId); + var notifier = new BilibiliTradeNotifier(pk, trainer, code, name); + var tt = type == PokeRoutineType.SeedCheck ? PokeTradeType.Seed : PokeTradeType.Specific; + var detail = + new PokeTradeDetail(pk, trainer, notifier, tt, code, sig == RequestSignificance.Favored); + var trade = new TradeEntry(detail, userId, type, name); - if (added == QueueResultAdd.AlreadyInQueue) - { - msg = $"@{name}: Sorry, you are already in the queue."; - return false; - } + var added = Info.AddToTradeQueue(trade, userId, sig == RequestSignificance.Owner); - var position = Info.CheckPosition(userId, type); - //msg = $"@{name}: Added to the {type} queue, unique ID: {detail.ID}. Current Position: {position.Position}"; - msg = $" 你在第{position.Position}位"; + if (added == QueueResultAdd.AlreadyInQueue) + { + msg = $"@{name}: Sorry, you are already in the queue."; + return false; + } - var botct = Info.Hub.Bots.Count; - if (position.Position > botct) - { - var eta = Info.Hub.Config.Queues.EstimateDelay(position.Position, botct); - //msg += $". Estimated: {eta:F1} minutes."; - msg += $", 需等待约{eta:F1}分钟"; - } + var position = Info.CheckPosition(userId, type); + //msg = $"@{name}: Added to the {type} queue, unique ID: {detail.ID}. Current Position: {position.Position}"; + msg = $" 你在第{position.Position}位"; - return true; + var botct = Info.Hub.Bots.Count; + if (position.Position > botct) + { + var eta = Info.Hub.Config.Queues.EstimateDelay(position.Position, botct); + //msg += $". Estimated: {eta:F1} minutes."; + msg += $", 需等待约{eta:F1}分钟"; } + + return true; } -} \ No newline at end of file +} diff --git a/SysBot.Pokemon.Bilibili/BilibiliTradeNotifier.cs b/SysBot.Pokemon.Bilibili/BilibiliTradeNotifier.cs index 9d432d208..a6a248f4d 100644 --- a/SysBot.Pokemon.Bilibili/BilibiliTradeNotifier.cs +++ b/SysBot.Pokemon.Bilibili/BilibiliTradeNotifier.cs @@ -1,88 +1,86 @@ -using PKHeX.Core; +using PKHeX.Core; using SysBot.Base; using SysBot.Pokemon; using System; using System.Linq; using System.IO; -namespace SysBot.Pokemon.Bilibili +namespace SysBot.Pokemon.Bilibili; +public class BilibiliTradeNotifier : IPokeTradeNotifier where T : PKM, new() { - public class BilibiliTradeNotifier : IPokeTradeNotifier where T : PKM, new() - { - private T Data { get; } - private PokeTradeTrainerInfo Info { get; } - private int Code { get; } - private string Username { get; } + private T Data { get; } + private PokeTradeTrainerInfo Info { get; } + private int Code { get; } + private string Username { get; } - public BilibiliTradeNotifier(T data, PokeTradeTrainerInfo info, int code, string username) - { - Data = data; - Info = info; - Code = code; - Username = username; - LogUtil.LogText($"Created trade details for {Username} - {Code}"); - } + public BilibiliTradeNotifier(T data, PokeTradeTrainerInfo info, int code, string username) + { + Data = data; + Info = info; + Code = code; + Username = username; + LogUtil.LogText($"Created trade details for {Username} - {Code}"); + } - public Action>? OnFinish { private get; set; } + public Action>? OnFinish { private get; set; } - public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, string message) - { - LogUtil.LogText(message); - } + public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, string message) + { + LogUtil.LogText(message); + } - public void TradeCanceled(PokeRoutineExecutor routine, PokeTradeDetail info, PokeTradeResult msg) - { - OnFinish?.Invoke(routine); - var line = $"@{info.Trainer.TrainerName}: Trade canceled, {msg}"; - LogUtil.LogText(line); - File.WriteAllText(@"msg.txt", $"等待命令"); - } + public void TradeCanceled(PokeRoutineExecutor routine, PokeTradeDetail info, PokeTradeResult msg) + { + OnFinish?.Invoke(routine); + var line = $"@{info.Trainer.TrainerName}: Trade canceled, {msg}"; + LogUtil.LogText(line); + File.WriteAllText(@"msg.txt", $"等待命令"); + } - public void TradeFinished(PokeRoutineExecutor routine, PokeTradeDetail info, T result) - { - OnFinish?.Invoke(routine); - var tradedToUser = Data.Species; - var message = $"@{info.Trainer.TrainerName}: " + (tradedToUser != 0 - ? $"Trade finished. Enjoy your {(Species) tradedToUser}!" - : "Trade finished!"); - LogUtil.LogText(message); - File.WriteAllText(@"msg.txt", $"等待命令"); - } + public void TradeFinished(PokeRoutineExecutor routine, PokeTradeDetail info, T result) + { + OnFinish?.Invoke(routine); + var tradedToUser = Data.Species; + var message = $"@{info.Trainer.TrainerName}: " + (tradedToUser != 0 + ? $"Trade finished. Enjoy your {(Species) tradedToUser}!" + : "Trade finished!"); + LogUtil.LogText(message); + File.WriteAllText(@"msg.txt", $"等待命令"); + } - public void TradeInitialize(PokeRoutineExecutor routine, PokeTradeDetail info) - { - var receive = Data.Species == 0 ? string.Empty : $" ({Data.Nickname})"; - var msg = - $"@{info.Trainer.TrainerName} (ID: {info.ID}): Initializing trade{receive} with you. Please be ready."; - msg += $" Your trade code is: {info.Code:0000 0000}"; - LogUtil.LogText(msg); - File.WriteAllText("msg.txt", - $"派送:{ShowdownTranslator.GameStringsZh.Species[Data.Species]}\n密码:{info.Code:0000 0000}\n状态:初始化"); - } + public void TradeInitialize(PokeRoutineExecutor routine, PokeTradeDetail info) + { + var receive = Data.Species == 0 ? string.Empty : $" ({Data.Nickname})"; + var msg = + $"@{info.Trainer.TrainerName} (ID: {info.ID}): Initializing trade{receive} with you. Please be ready."; + msg += $" Your trade code is: {info.Code:0000 0000}"; + LogUtil.LogText(msg); + File.WriteAllText("msg.txt", + $"派送:{ShowdownTranslator.GameStringsZh.Species[Data.Species]}\n密码:{info.Code:0000 0000}\n状态:初始化"); + } - public void TradeSearching(PokeRoutineExecutor routine, PokeTradeDetail info) - { - var name = Info.TrainerName; - var trainer = string.IsNullOrEmpty(name) ? string.Empty : $", @{name}"; - var message = $"I'm waiting for you{trainer}! My IGN is {routine.InGameName}."; - message += $" Your trade code is: {info.Code:0000 0000}"; - LogUtil.LogText(message); - File.WriteAllText("msg.txt", - $"派送:{ShowdownTranslator.GameStringsZh.Species[Data.Species]}\n密码:{info.Code:0000 0000}\n状态:搜索中"); - } + public void TradeSearching(PokeRoutineExecutor routine, PokeTradeDetail info) + { + var name = Info.TrainerName; + var trainer = string.IsNullOrEmpty(name) ? string.Empty : $", @{name}"; + var message = $"I'm waiting for you{trainer}! My IGN is {routine.InGameName}."; + message += $" Your trade code is: {info.Code:0000 0000}"; + LogUtil.LogText(message); + File.WriteAllText("msg.txt", + $"派送:{ShowdownTranslator.GameStringsZh.Species[Data.Species]}\n密码:{info.Code:0000 0000}\n状态:搜索中"); + } - public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, PokeTradeSummary message) - { - var msg = message.Summary; - if (message.Details.Count > 0) - msg += ", " + string.Join(", ", message.Details.Select(z => $"{z.Heading}: {z.Detail}")); - LogUtil.LogText(msg); - } + public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, PokeTradeSummary message) + { + var msg = message.Summary; + if (message.Details.Count > 0) + msg += ", " + string.Join(", ", message.Details.Select(z => $"{z.Heading}: {z.Detail}")); + LogUtil.LogText(msg); + } - public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, T result, string message) - { - var msg = $"Details for {result.FileName}: " + message; - LogUtil.LogText(msg); - } + public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, T result, string message) + { + var msg = $"Details for {result.FileName}: " + message; + LogUtil.LogText(msg); } -} \ No newline at end of file +} diff --git a/SysBot.Pokemon.Dodo/DodoBot.cs b/SysBot.Pokemon.Dodo/DodoBot.cs index a29d29be5..c8e75e020 100644 --- a/SysBot.Pokemon.Dodo/DodoBot.cs +++ b/SysBot.Pokemon.Dodo/DodoBot.cs @@ -1,4 +1,4 @@ -using System.Linq; +using System.Linq; using System.Threading.Tasks; using DoDo.Open.Sdk.Models; using DoDo.Open.Sdk.Models.ChannelMessages; @@ -8,110 +8,109 @@ using DoDo.Open.Sdk.Services; using PKHeX.Core; -namespace SysBot.Pokemon.Dodo +namespace SysBot.Pokemon.Dodo; + +public class DodoBot where T : PKM, new() { - public class DodoBot where T : PKM, new() - { - private static PokeTradeHub Hub = default!; - internal static TradeQueueInfo Info => Hub.Queues.Info; + private static PokeTradeHub Hub = default!; + internal static TradeQueueInfo Info => Hub.Queues.Info; - public static OpenApiService OpenApiService = default!; + public static OpenApiService OpenApiService = default!; - private static DodoSettings Settings = default!; + private static DodoSettings Settings = default!; - public DodoBot(DodoSettings settings, PokeTradeHub hub) + public DodoBot(DodoSettings settings, PokeTradeHub hub) + { + Hub = hub; + Settings = settings; + //开放接口服务 + OpenApiService = new OpenApiService(new OpenApiOptions { - Hub = hub; - Settings = settings; - //开放接口服务 - OpenApiService = new OpenApiService(new OpenApiOptions - { - BaseApi = settings.BaseApi, - ClientId = settings.ClientId, - Token = settings.Token - }); - //事件处理服务,可自定义,只要继承EventProcessService抽象类即可 - var eventProcessService = new PokemonProcessService(OpenApiService, settings); - //开放事件服务 - var openEventService = new OpenEventService(OpenApiService, eventProcessService, new OpenEventOptions - { - IsReconnect = true, - IsAsync = true - }); - //接收事件消息 - Task.Run(async () => - { - StartDistribution(); - await openEventService.ReceiveAsync(); - }); + BaseApi = settings.BaseApi, + ClientId = settings.ClientId, + Token = settings.Token + }); + //事件处理服务,可自定义,只要继承EventProcessService抽象类即可 + var eventProcessService = new PokemonProcessService(OpenApiService, settings); + //开放事件服务 + var openEventService = new OpenEventService(OpenApiService, eventProcessService, new OpenEventOptions + { + IsReconnect = true, + IsAsync = true + }); + //接收事件消息 + Task.Run(async () => + { + StartDistribution(); + await openEventService.ReceiveAsync(); + }); + } + + public void StartDistribution() + { + var channelId = Settings.ChannelId; + if (string.IsNullOrWhiteSpace(channelId)) return; + SendChannelMessage("开始派送", channelId); + Task.Delay(1_000).ConfigureAwait(false); + if (typeof(T) == typeof(PK8)) + { + SendChannelMessage("当前版本为剑盾", channelId); } + else if (typeof(T) == typeof(PB8)) + { + SendChannelMessage("当前版本为晶灿钻石明亮珍珠", channelId); + } + else if (typeof(T) == typeof(PA8)) + { + SendChannelMessage("当前版本为阿尔宙斯", channelId); + } + else if (typeof(T) == typeof(PK9)) + { + SendChannelMessage("当前版本为朱紫", channelId); + } + } - public void StartDistribution() + public static void SendChannelMessage(string message, string channelId) + { + if (string.IsNullOrEmpty(message)) return; + OpenApiService.SetChannelMessageSend(new SetChannelMessageSendInput { - var channelId = Settings.ChannelId; - if (string.IsNullOrWhiteSpace(channelId)) return; - SendChannelMessage("开始派送", channelId); - Task.Delay(1_000).ConfigureAwait(false); - if (typeof(T) == typeof(PK8)) - { - SendChannelMessage("当前版本为剑盾", channelId); - } - else if (typeof(T) == typeof(PB8)) - { - SendChannelMessage("当前版本为晶灿钻石明亮珍珠", channelId); - } - else if (typeof(T) == typeof(PA8)) + ChannelId = channelId, + MessageBody = new MessageBodyText { - SendChannelMessage("当前版本为阿尔宙斯", channelId); + Content = message } - else if (typeof(T) == typeof(PK9)) - { - SendChannelMessage("当前版本为朱紫", channelId); - } - } + }); + } - public static void SendChannelMessage(string message, string channelId) + public static void SendChannelAtMessage(ulong atDodoId, string message, string channelId) + { + if (string.IsNullOrEmpty(message)) return; + OpenApiService.SetChannelMessageSend(new SetChannelMessageSendInput { - if (string.IsNullOrEmpty(message)) return; - OpenApiService.SetChannelMessageSend(new SetChannelMessageSendInput + ChannelId = channelId, + MessageBody = new MessageBodyText { - ChannelId = channelId, - MessageBody = new MessageBodyText - { - Content = message - } - }); - } + Content = $"<@!{atDodoId}> {message}" + } + }); + } - public static void SendChannelAtMessage(ulong atDodoId, string message, string channelId) + public static void SendPersonalMessage(string dodoId, string message, string islandSourceId = "") + { + if (string.IsNullOrEmpty(message)) return; + if (string.IsNullOrWhiteSpace(islandSourceId)) { - if (string.IsNullOrEmpty(message)) return; - OpenApiService.SetChannelMessageSend(new SetChannelMessageSendInput - { - ChannelId = channelId, - MessageBody = new MessageBodyText - { - Content = $"<@!{atDodoId}> {message}" - } - }); + islandSourceId = OpenApiService.GetIslandList(new GetIslandListInput()).FirstOrDefault()?.IslandSourceId ?? ""; } - - public static void SendPersonalMessage(string dodoId, string message, string islandSourceId = "") + OpenApiService.SetPersonalMessageSend(new SetPersonalMessageSendInput { - if (string.IsNullOrEmpty(message)) return; - if (string.IsNullOrWhiteSpace(islandSourceId)) + IslandSourceId = islandSourceId, + DodoSourceId = dodoId, + MessageBody = new MessageBodyText { - islandSourceId = OpenApiService.GetIslandList(new GetIslandListInput()).FirstOrDefault()?.IslandSourceId ?? ""; + Content = message } - OpenApiService.SetPersonalMessageSend(new SetPersonalMessageSendInput - { - IslandSourceId = islandSourceId, - DodoSourceId = dodoId, - MessageBody = new MessageBodyText - { - Content = message - } - }); - } + }); } -} \ No newline at end of file +} diff --git a/SysBot.Pokemon.Dodo/DodoTrade.cs b/SysBot.Pokemon.Dodo/DodoTrade.cs index efe3332b9..0e2b5024c 100644 --- a/SysBot.Pokemon.Dodo/DodoTrade.cs +++ b/SysBot.Pokemon.Dodo/DodoTrade.cs @@ -1,30 +1,29 @@ -using PKHeX.Core; +using PKHeX.Core; using SysBot.Pokemon.Helpers; -using System; -namespace SysBot.Pokemon.Dodo +namespace SysBot.Pokemon.Dodo; + +public class DodoTrade : AbstractTrade where T : PKM, new() { - public class DodoTrade : AbstractTrade where T : PKM, new() - { - private readonly string channelId = default!; - private readonly string islandSourceId = default!; + private readonly string channelId = default!; + private readonly string islandSourceId = default!; - public DodoTrade(ulong dodoId, string nickName, string channelId, string islandSourceId) - { - SetPokeTradeTrainerInfo(new PokeTradeTrainerInfo(nickName, dodoId)); - SetTradeQueueInfo(DodoBot.Info); - this.channelId = channelId; - this.islandSourceId = islandSourceId; - } + public DodoTrade(ulong dodoId, string nickName, string channelId, string islandSourceId) + { + SetPokeTradeTrainerInfo(new PokeTradeTrainerInfo(nickName, dodoId)); + SetTradeQueueInfo(DodoBot.Info); + this.channelId = channelId; + this.islandSourceId = islandSourceId; + } - public override IPokeTradeNotifier GetPokeTradeNotifier(T pkm, int code) - { - return new DodoTradeNotifier(pkm, userInfo, code, userInfo.ID.ToString(), channelId, islandSourceId); - } + public override IPokeTradeNotifier GetPokeTradeNotifier(T pkm, int code) + { + return new DodoTradeNotifier(pkm, userInfo, code, userInfo.ID.ToString(), channelId, islandSourceId); + } - public override void SendMessage(string message) - { - DodoBot.SendChannelAtMessage(userInfo.ID, message, channelId); - } + public override void SendMessage(string message) + { + DodoBot.SendChannelAtMessage(userInfo.ID, message, channelId); } } + diff --git a/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs b/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs index c6d339bba..a93249a7d 100644 --- a/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs +++ b/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs @@ -1,137 +1,136 @@ -using PKHeX.Core; +using PKHeX.Core; using SysBot.Base; using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; -namespace SysBot.Pokemon.Dodo +namespace SysBot.Pokemon.Dodo; + +public class DodoTradeNotifier : IPokeTradeNotifier where T : PKM, new() { - public class DodoTradeNotifier : IPokeTradeNotifier where T : PKM, new() - { - private T Data { get; } - private PokeTradeTrainerInfo Info { get; } - private int Code { get; } - private string Username { get; } + private T Data { get; } + private PokeTradeTrainerInfo Info { get; } + private int Code { get; } + private string Username { get; } - private string ChannelId { get; } - private string IslandSourceId { get; } + private string ChannelId { get; } + private string IslandSourceId { get; } - public DodoTradeNotifier(T data, PokeTradeTrainerInfo info, int code, string username, string channelId, - string islandSourceId) - { - Data = data; - Info = info; - Code = code; - Username = username; - ChannelId = channelId; - IslandSourceId = islandSourceId; - LogUtil.LogText($"Created trade details for {Username} - {Code}"); - } + public DodoTradeNotifier(T data, PokeTradeTrainerInfo info, int code, string username, string channelId, + string islandSourceId) + { + Data = data; + Info = info; + Code = code; + Username = username; + ChannelId = channelId; + IslandSourceId = islandSourceId; + LogUtil.LogText($"Created trade details for {Username} - {Code}"); + } - public Action>? OnFinish { private get; set; } + public Action>? OnFinish { private get; set; } - public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, string message) + public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, string message) + { + LogUtil.LogText(message); + if (message.Contains("Found Trading Partner:")) { - LogUtil.LogText(message); - if (message.Contains("Found Trading Partner:")) - { - Regex regex = new Regex("TID: (\\d+)"); - string tid = regex.Match(message).Groups[1].ToString(); - regex = new Regex("SID: (\\d+)"); - string sid = regex.Match(message).Groups[1].ToString(); - DodoBot.SendPersonalMessage(info.Trainer.ID.ToString(), $"找到你了,你的SID7:{sid},TID7:{tid}", - IslandSourceId); - } - else if (message.StartsWith("批量")) - { - DodoBot.SendChannelMessage(message, ChannelId); - } + Regex regex = new Regex("TID: (\\d+)"); + string tid = regex.Match(message).Groups[1].ToString(); + regex = new Regex("SID: (\\d+)"); + string sid = regex.Match(message).Groups[1].ToString(); + DodoBot.SendPersonalMessage(info.Trainer.ID.ToString(), $"找到你了,你的SID7:{sid},TID7:{tid}", + IslandSourceId); } - - public void TradeCanceled(PokeRoutineExecutor routine, PokeTradeDetail info, PokeTradeResult msg) + else if (message.StartsWith("批量")) { - OnFinish?.Invoke(routine); - var line = $"@{info.Trainer.TrainerName}: Trade canceled, {msg}"; - LogUtil.LogText(line); - DodoBot.SendChannelAtMessage(info.Trainer.ID, "取消", ChannelId); - var waitUserIds = DodoBot.Info.GetUserIdList(4).ToList(); - for (int i = 1; i < waitUserIds.Count(); i++) - { - DodoBot.SendChannelAtMessage(waitUserIds[i], $"你在第{i + 1}位,还有{i}个以后就到你了!\n", ChannelId); - } + DodoBot.SendChannelMessage(message, ChannelId); } + } - public void TradeFinished(PokeRoutineExecutor routine, PokeTradeDetail info, T result) + public void TradeCanceled(PokeRoutineExecutor routine, PokeTradeDetail info, PokeTradeResult msg) + { + OnFinish?.Invoke(routine); + var line = $"@{info.Trainer.TrainerName}: Trade canceled, {msg}"; + LogUtil.LogText(line); + DodoBot.SendChannelAtMessage(info.Trainer.ID, "取消", ChannelId); + var waitUserIds = DodoBot.Info.GetUserIdList(4).ToList(); + for (int i = 1; i < waitUserIds.Count(); i++) { - OnFinish?.Invoke(routine); - var tradedToUser = Data.Species; - var message = $"@{info.Trainer.TrainerName}: " + (tradedToUser != 0 - ? $"Trade finished. Enjoy your {(Species) tradedToUser}!" - : "Trade finished!"); - LogUtil.LogText(message); - DodoBot.SendChannelAtMessage(info.Trainer.ID, "完成", ChannelId); - var waitUserIds = DodoBot.Info.GetUserIdList(4).ToList(); - for (int i = 1; i < waitUserIds.Count(); i++) - { - DodoBot.SendChannelAtMessage(waitUserIds[i], $"你在第{i + 1}位,还有{i}个以后就到你了!\n", ChannelId); - } + DodoBot.SendChannelAtMessage(waitUserIds[i], $"你在第{i + 1}位,还有{i}个以后就到你了!\n", ChannelId); } + } - public void TradeInitialize(PokeRoutineExecutor routine, PokeTradeDetail info) + public void TradeFinished(PokeRoutineExecutor routine, PokeTradeDetail info, T result) + { + OnFinish?.Invoke(routine); + var tradedToUser = Data.Species; + var message = $"@{info.Trainer.TrainerName}: " + (tradedToUser != 0 + ? $"Trade finished. Enjoy your {(Species) tradedToUser}!" + : "Trade finished!"); + LogUtil.LogText(message); + DodoBot.SendChannelAtMessage(info.Trainer.ID, "完成", ChannelId); + var waitUserIds = DodoBot.Info.GetUserIdList(4).ToList(); + for (int i = 1; i < waitUserIds.Count(); i++) { - var receive = Data.Species == 0 ? string.Empty : $" ({Data.Nickname})"; - var msg = - $"@{info.Trainer.TrainerName} (ID: {info.ID}): Initializing trade{receive} with you. Please be ready."; - msg += $" Your trade code is: {info.Code:0000 0000}"; - LogUtil.LogText(msg); - var text = $"\n派送:{ShowdownTranslator.GameStringsZh.Species[Data.Species]}\n密码:见私信\n状态:初始化"; - List batchPKMs = (List)info.Context.GetValueOrDefault("batch", new List()); - if (batchPKMs.Count > 1) - { - text = $"\n批量派送{batchPKMs.Count}只宝可梦\n密码:见私信\n状态:初始化"; - } - DodoBot.SendChannelAtMessage(info.Trainer.ID, text, ChannelId); - DodoBot.SendPersonalMessage(info.Trainer.ID.ToString(), - $"派送:{ShowdownTranslator.GameStringsZh.Species[Data.Species]}\n密码:{info.Code:0000 0000}", - IslandSourceId); + DodoBot.SendChannelAtMessage(waitUserIds[i], $"你在第{i + 1}位,还有{i}个以后就到你了!\n", ChannelId); } + } - public void TradeSearching(PokeRoutineExecutor routine, PokeTradeDetail info) + public void TradeInitialize(PokeRoutineExecutor routine, PokeTradeDetail info) + { + var receive = Data.Species == 0 ? string.Empty : $" ({Data.Nickname})"; + var msg = + $"@{info.Trainer.TrainerName} (ID: {info.ID}): Initializing trade{receive} with you. Please be ready."; + msg += $" Your trade code is: {info.Code:0000 0000}"; + LogUtil.LogText(msg); + var text = $"\n派送:{ShowdownTranslator.GameStringsZh.Species[Data.Species]}\n密码:见私信\n状态:初始化"; + List batchPKMs = (List)info.Context.GetValueOrDefault("batch", new List()); + if (batchPKMs.Count > 1) { - var name = Info.TrainerName; - var trainer = string.IsNullOrEmpty(name) ? string.Empty : $", @{name}"; - var message = $"I'm waiting for you{trainer}! My IGN is {routine.InGameName}."; - message += $" Your trade code is: {info.Code:0000 0000}"; - LogUtil.LogText(message); - var text = $"派送:{ShowdownTranslator.GameStringsZh.Species[Data.Species]}\n密码:见私信\n状态:搜索中"; - List batchPKMs = (List)info.Context.GetValueOrDefault("batch", new List()); - if (batchPKMs.Count > 1) - { - text = $"批量派送{batchPKMs.Count}只宝可梦\n密码:见私信\n状态:搜索中"; - } - DodoBot.SendChannelMessage(text, ChannelId); - //DodoBot.SendPersonalMessage(info.Trainer.ID.ToString(), $"{info.Code:0000 0000}", IslandSourceId); + text = $"\n批量派送{batchPKMs.Count}只宝可梦\n密码:见私信\n状态:初始化"; } + DodoBot.SendChannelAtMessage(info.Trainer.ID, text, ChannelId); + DodoBot.SendPersonalMessage(info.Trainer.ID.ToString(), + $"派送:{ShowdownTranslator.GameStringsZh.Species[Data.Species]}\n密码:{info.Code:0000 0000}", + IslandSourceId); + } - public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, PokeTradeSummary message) + public void TradeSearching(PokeRoutineExecutor routine, PokeTradeDetail info) + { + var name = Info.TrainerName; + var trainer = string.IsNullOrEmpty(name) ? string.Empty : $", @{name}"; + var message = $"I'm waiting for you{trainer}! My IGN is {routine.InGameName}."; + message += $" Your trade code is: {info.Code:0000 0000}"; + LogUtil.LogText(message); + var text = $"派送:{ShowdownTranslator.GameStringsZh.Species[Data.Species]}\n密码:见私信\n状态:搜索中"; + List batchPKMs = (List)info.Context.GetValueOrDefault("batch", new List()); + if (batchPKMs.Count > 1) { - var msg = message.Summary; - if (message.Details.Count > 0) - msg += ", " + string.Join(", ", message.Details.Select(z => $"{z.Heading}: {z.Detail}")); - LogUtil.LogText(msg); + text = $"批量派送{batchPKMs.Count}只宝可梦\n密码:见私信\n状态:搜索中"; } + DodoBot.SendChannelMessage(text, ChannelId); + //DodoBot.SendPersonalMessage(info.Trainer.ID.ToString(), $"{info.Code:0000 0000}", IslandSourceId); + } + + public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, PokeTradeSummary message) + { + var msg = message.Summary; + if (message.Details.Count > 0) + msg += ", " + string.Join(", ", message.Details.Select(z => $"{z.Heading}: {z.Detail}")); + LogUtil.LogText(msg); + } - public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, T result, string message) + public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, T result, string message) + { + var msg = $"Details for {result.FileName}: " + message; + LogUtil.LogText(msg); + if (result.Species != 0 && info.Type == PokeTradeType.Dump) { - var msg = $"Details for {result.FileName}: " + message; - LogUtil.LogText(msg); - if (result.Species != 0 && info.Type == PokeTradeType.Dump) - { - var text = - $"species:{result.Species}\npid:{result.PID}\nec:{result.EncryptionConstant}\nIVs:{string.Join(",", result.IVs)}\nisShiny:{result.IsShiny}"; - DodoBot.SendChannelMessage(text, ChannelId); - } + var text = + $"species:{result.Species}\npid:{result.PID}\nec:{result.EncryptionConstant}\nIVs:{string.Join(",", result.IVs)}\nisShiny:{result.IsShiny}"; + DodoBot.SendChannelMessage(text, ChannelId); } } -} \ No newline at end of file +} diff --git a/SysBot.Pokemon.Dodo/PokemonProcessService.cs b/SysBot.Pokemon.Dodo/PokemonProcessService.cs index 72c995c46..69a6cd67d 100644 --- a/SysBot.Pokemon.Dodo/PokemonProcessService.cs +++ b/SysBot.Pokemon.Dodo/PokemonProcessService.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Net.Http; using DoDo.Open.Sdk.Models.Bots; using DoDo.Open.Sdk.Models.ChannelMessages; @@ -9,180 +9,178 @@ using SysBot.Base; using SysBot.Pokemon.Helpers; -namespace SysBot.Pokemon.Dodo +namespace SysBot.Pokemon.Dodo; +public class PokemonProcessService : EventProcessService where TP : PKM, new() { - public class PokemonProcessService : EventProcessService where TP : PKM, new() + private readonly OpenApiService _openApiService; + private static readonly string LogIdentity = "DodoBot"; + private static readonly string Welcome = "at我并尝试对我说:\n皮卡丘\nps代码\n或者直接拖一个文件进来"; + private readonly string _channelId; + private DodoSettings _dodoSettings; + private string _botDodoSourceId = default!; + + public PokemonProcessService(OpenApiService openApiService, DodoSettings settings) { - private readonly OpenApiService _openApiService; - private static readonly string LogIdentity = "DodoBot"; - private static readonly string Welcome = "at我并尝试对我说:\n皮卡丘\nps代码\n或者直接拖一个文件进来"; - private readonly string _channelId; - private DodoSettings _dodoSettings; - private string _botDodoSourceId = default!; - - public PokemonProcessService(OpenApiService openApiService, DodoSettings settings) - { - _openApiService = openApiService; - _channelId = settings.ChannelId; - _dodoSettings = settings; - } + _openApiService = openApiService; + _channelId = settings.ChannelId; + _dodoSettings = settings; + } - public override void Connected(string message) - { - Console.WriteLine($"{message}\n"); - } + public override void Connected(string message) + { + Console.WriteLine($"{message}\n"); + } - public override void Disconnected(string message) - { - Console.WriteLine($"{message}\n"); - } + public override void Disconnected(string message) + { + Console.WriteLine($"{message}\n"); + } - public override void Reconnected(string message) - { - Console.WriteLine($"{message}\n"); - } + public override void Reconnected(string message) + { + Console.WriteLine($"{message}\n"); + } - public override void Exception(string message) - { - Console.WriteLine($"{message}\n"); - } + public override void Exception(string message) + { + Console.WriteLine($"{message}\n"); + } - public override void PersonalMessageEvent( - EventSubjectOutput>> input) - { - var eventBody = input.Data.EventBody; + public override void PersonalMessageEvent( + EventSubjectOutput>> input) + { + var eventBody = input.Data.EventBody; - if (eventBody.MessageBody is MessageBodyText messageBodyText) - { - DodoBot.SendPersonalMessage(eventBody.DodoSourceId, $"你好", eventBody.IslandSourceId); - } + if (eventBody.MessageBody is MessageBodyText messageBodyText) + { + DodoBot.SendPersonalMessage(eventBody.DodoSourceId, $"你好", eventBody.IslandSourceId); } + } - public override void ChannelMessageEvent( - EventSubjectOutput>> input) - { - var eventBody = input.Data.EventBody; - if (!string.IsNullOrWhiteSpace(_channelId) && eventBody.ChannelId != _channelId) return; + public override void ChannelMessageEvent( + EventSubjectOutput>> input) + { + var eventBody = input.Data.EventBody; + if (!string.IsNullOrWhiteSpace(_channelId) && eventBody.ChannelId != _channelId) return; - if (eventBody.MessageBody is MessageBodyFile messageBodyFile) + if (eventBody.MessageBody is MessageBodyFile messageBodyFile) + { + if (!FileTradeHelper.ValidFileSize(messageBodyFile.Size ?? 0) || !FileTradeHelper.ValidFileName(messageBodyFile.Name)) { - if (!FileTradeHelper.ValidFileSize(messageBodyFile.Size ?? 0) || !FileTradeHelper.ValidFileName(messageBodyFile.Name)) - { - ProcessWithdraw(eventBody.MessageId); - DodoBot.SendChannelMessage("非法文件", eventBody.ChannelId); - return; - } - using var client = new HttpClient(); - var downloadBytes = client.GetByteArrayAsync(messageBodyFile.Url).Result; - var pkms = FileTradeHelper.Bin2List(downloadBytes); ProcessWithdraw(eventBody.MessageId); - if (pkms.Count == 1) - new DodoTrade(ulong.Parse(eventBody.DodoSourceId), eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId).StartTradePKM(pkms[0]); - else if (pkms.Count > 1 && pkms.Count <= FileTradeHelper.MaxCountInBin) - new DodoTrade(ulong.Parse(eventBody.DodoSourceId), eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId).StartTradeMultiPKM(pkms); - else - DodoBot.SendChannelMessage("文件内容不正确", eventBody.ChannelId); + DodoBot.SendChannelMessage("非法文件", eventBody.ChannelId); return; } + using var client = new HttpClient(); + var downloadBytes = client.GetByteArrayAsync(messageBodyFile.Url).Result; + var pkms = FileTradeHelper.Bin2List(downloadBytes); + ProcessWithdraw(eventBody.MessageId); + if (pkms.Count == 1) + new DodoTrade(ulong.Parse(eventBody.DodoSourceId), eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId).StartTradePKM(pkms[0]); + else if (pkms.Count > 1 && pkms.Count <= FileTradeHelper.MaxCountInBin) + new DodoTrade(ulong.Parse(eventBody.DodoSourceId), eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId).StartTradeMultiPKM(pkms); + else + DodoBot.SendChannelMessage("文件内容不正确", eventBody.ChannelId); + return; + } - if (eventBody.MessageBody is not MessageBodyText messageBodyText) return; - - var content = messageBodyText.Content; + if (eventBody.MessageBody is not MessageBodyText messageBodyText) return; - LogUtil.LogInfo($"{eventBody.Personal.NickName}({eventBody.DodoSourceId}):{content}", LogIdentity); - if (_botDodoSourceId == null) - { - _botDodoSourceId = _openApiService.GetBotInfo(new GetBotInfoInput()).DodoSourceId; - } - if (!content.Contains($"<@!{_botDodoSourceId}>")) return; + var content = messageBodyText.Content; - content = content.Substring(content.IndexOf('>') + 1); - if (typeof(TP) == typeof(PK9) && content.Contains("\n\n") && ShowdownTranslator.IsPS(content))// 仅SV支持批量,其他偷懒还没写 - { - ProcessWithdraw(eventBody.MessageId); - new DodoTrade(ulong.Parse(eventBody.DodoSourceId), eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId).StartTradeMultiPs(content.Trim()); - return; - } - else if (ShowdownTranslator.IsPS(content)) - { - ProcessWithdraw(eventBody.MessageId); - new DodoTrade(ulong.Parse(eventBody.DodoSourceId), eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId).StartTradePs(content.Trim()); - return; - } - else if (content.Trim().StartsWith("dump")) - { - ProcessWithdraw(eventBody.MessageId); - new DodoTrade(ulong.Parse(eventBody.DodoSourceId), eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId).StartDump(); - return; - } - else if (typeof(TP) == typeof(PK9) && content.Trim().Contains('+'))// 仅SV支持批量,其他偷懒还没写 - { - ProcessWithdraw(eventBody.MessageId); - new DodoTrade(ulong.Parse(eventBody.DodoSourceId), eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId).StartTradeMultiChinesePs(content.Trim()); - return; - } - - var ps = ShowdownTranslator.Chinese2Showdown(content); - if (!string.IsNullOrWhiteSpace(ps)) - { - LogUtil.LogInfo($"收到命令\n{ps}", LogIdentity); - ProcessWithdraw(eventBody.MessageId); - new DodoTrade(ulong.Parse(eventBody.DodoSourceId), eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId).StartTradePs(ps); - } - else if (content.Contains("取消")) - { - var result = DodoBot.Info.ClearTrade(ulong.Parse(eventBody.DodoSourceId)); - DodoBot.SendChannelAtMessage(ulong.Parse(eventBody.DodoSourceId), $" {GetClearTradeMessage(result)}", - eventBody.ChannelId); - } - else if (content.Contains("位置")) - { - var result = DodoBot.Info.CheckPosition(ulong.Parse(eventBody.DodoSourceId)); - DodoBot.SendChannelAtMessage(ulong.Parse(eventBody.DodoSourceId), - $" {GetQueueCheckResultMessage(result)}", - eventBody.ChannelId); - } - else - { - DodoBot.SendChannelMessage($"{Welcome}", eventBody.ChannelId); - } + LogUtil.LogInfo($"{eventBody.Personal.NickName}({eventBody.DodoSourceId}):{content}", LogIdentity); + if (_botDodoSourceId == null) + { + _botDodoSourceId = _openApiService.GetBotInfo(new GetBotInfoInput()).DodoSourceId; } + if (!content.Contains($"<@!{_botDodoSourceId}>")) return; - public string GetQueueCheckResultMessage(QueueCheckResult result) + content = content.Substring(content.IndexOf('>') + 1); + if (typeof(TP) == typeof(PK9) && content.Contains("\n\n") && ShowdownTranslator.IsPS(content))// 仅SV支持批量,其他偷懒还没写 { - if (!result.InQueue || result.Detail is null) - return "你不在队列里"; - var msg = $"你在第{result.Position}位"; - var pk = result.Detail.Trade.TradeData; - if (pk.Species != 0) - msg += $",交换宝可梦:{ShowdownTranslator.GameStringsZh.Species[result.Detail.Trade.TradeData.Species]}"; - return msg; + ProcessWithdraw(eventBody.MessageId); + new DodoTrade(ulong.Parse(eventBody.DodoSourceId), eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId).StartTradeMultiPs(content.Trim()); + return; } - - private static string GetClearTradeMessage(QueueResultRemove result) + else if (ShowdownTranslator.IsPS(content)) { - return result switch - { - QueueResultRemove.CurrentlyProcessing => "你正在交换中", - QueueResultRemove.CurrentlyProcessingRemoved => "正在删除", - QueueResultRemove.Removed => "已删除", - _ => "你不在队列里", - }; + ProcessWithdraw(eventBody.MessageId); + new DodoTrade(ulong.Parse(eventBody.DodoSourceId), eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId).StartTradePs(content.Trim()); + return; } - - private void ProcessWithdraw(string messageId) + else if (content.Trim().StartsWith("dump")) { - if (_dodoSettings.WithdrawTradeMessage) - { - DodoBot.OpenApiService.SetChannelMessageWithdraw(new SetChannelMessageWithdrawInput() { MessageId = messageId }, true); - } + ProcessWithdraw(eventBody.MessageId); + new DodoTrade(ulong.Parse(eventBody.DodoSourceId), eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId).StartDump(); + return; + } + else if (typeof(TP) == typeof(PK9) && content.Trim().Contains('+'))// 仅SV支持批量,其他偷懒还没写 + { + ProcessWithdraw(eventBody.MessageId); + new DodoTrade(ulong.Parse(eventBody.DodoSourceId), eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId).StartTradeMultiChinesePs(content.Trim()); + return; } - public override void MessageReactionEvent( - EventSubjectOutput> input) + var ps = ShowdownTranslator.Chinese2Showdown(content); + if (!string.IsNullOrWhiteSpace(ps)) + { + LogUtil.LogInfo($"收到命令\n{ps}", LogIdentity); + ProcessWithdraw(eventBody.MessageId); + new DodoTrade(ulong.Parse(eventBody.DodoSourceId), eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId).StartTradePs(ps); + } + else if (content.Contains("取消")) { - // Do nothing + var result = DodoBot.Info.ClearTrade(ulong.Parse(eventBody.DodoSourceId)); + DodoBot.SendChannelAtMessage(ulong.Parse(eventBody.DodoSourceId), $" {GetClearTradeMessage(result)}", + eventBody.ChannelId); } + else if (content.Contains("位置")) + { + var result = DodoBot.Info.CheckPosition(ulong.Parse(eventBody.DodoSourceId)); + DodoBot.SendChannelAtMessage(ulong.Parse(eventBody.DodoSourceId), + $" {GetQueueCheckResultMessage(result)}", + eventBody.ChannelId); + } + else + { + DodoBot.SendChannelMessage($"{Welcome}", eventBody.ChannelId); + } + } + public string GetQueueCheckResultMessage(QueueCheckResult result) + { + if (!result.InQueue || result.Detail is null) + return "你不在队列里"; + var msg = $"你在第{result.Position}位"; + var pk = result.Detail.Trade.TradeData; + if (pk.Species != 0) + msg += $",交换宝可梦:{ShowdownTranslator.GameStringsZh.Species[result.Detail.Trade.TradeData.Species]}"; + return msg; } -} \ No newline at end of file + + private static string GetClearTradeMessage(QueueResultRemove result) + { + return result switch + { + QueueResultRemove.CurrentlyProcessing => "你正在交换中", + QueueResultRemove.CurrentlyProcessingRemoved => "正在删除", + QueueResultRemove.Removed => "已删除", + _ => "你不在队列里", + }; + } + + private void ProcessWithdraw(string messageId) + { + if (_dodoSettings.WithdrawTradeMessage) + { + DodoBot.OpenApiService.SetChannelMessageWithdraw(new SetChannelMessageWithdrawInput() { MessageId = messageId }, true); + } + } + + public override void MessageReactionEvent( + EventSubjectOutput> input) + { + // Do nothing + } + +} diff --git a/SysBot.Pokemon.QQ/MiraiQQBot.cs b/SysBot.Pokemon.QQ/MiraiQQBot.cs index a3668ae02..ab91b4e49 100644 --- a/SysBot.Pokemon.QQ/MiraiQQBot.cs +++ b/SysBot.Pokemon.QQ/MiraiQQBot.cs @@ -1,4 +1,4 @@ -using Mirai.Net.Data.Messages; +using Mirai.Net.Data.Messages; using Mirai.Net.Modules; using Mirai.Net.Sessions; using Mirai.Net.Sessions.Http.Managers; @@ -9,93 +9,91 @@ using System.Collections.Generic; using System.Threading.Tasks; -namespace SysBot.Pokemon.QQ +namespace SysBot.Pokemon.QQ; +public class MiraiQQBot where T : PKM, new() { - public class MiraiQQBot where T : PKM, new() - { - private static PokeTradeHub Hub = default!; + private static PokeTradeHub Hub = default!; - internal static TradeQueueInfo Info => Hub.Queues.Info; - private readonly MiraiBot Client; + internal static TradeQueueInfo Info => Hub.Queues.Info; + private readonly MiraiBot Client; - internal static QQSettings Settings = default!; + internal static QQSettings Settings = default!; - public MiraiQQBot(QQSettings settings, PokeTradeHub hub) + public MiraiQQBot(QQSettings settings, PokeTradeHub hub) + { + Settings = settings; + Hub = hub; + Client = new MiraiBot { - Settings = settings; - Hub = hub; - Client = new MiraiBot - { - Address = settings.Address, - QQ = settings.QQ, - VerifyKey = settings.VerifyKey - }; + Address = settings.Address, + QQ = settings.QQ, + VerifyKey = settings.VerifyKey + }; - var modules = new List() - { - new AliveModule(), - new CommandModule(), - new FileModule(), - new PsModule() - }; - Client.MessageReceived.SubscribeGroupMessage(receiver => { if (receiver.GroupId == Settings.GroupId) modules.Raise(receiver); }); - var GroupId = Settings.GroupId; - Task.Run(async () => + var modules = new List() + { + new AliveModule(), + new CommandModule(), + new FileModule(), + new PsModule() + }; + Client.MessageReceived.SubscribeGroupMessage(receiver => { if (receiver.GroupId == Settings.GroupId) modules.Raise(receiver); }); + var GroupId = Settings.GroupId; + Task.Run(async () => + { + try { - try - { - await Client.LaunchAsync(); - - if (!string.IsNullOrWhiteSpace(Settings.MessageStart)) - { - await MessageManager.SendGroupMessageAsync(GroupId, Settings.MessageStart); - await Task.Delay(1_000).ConfigureAwait(false); - } - - if (typeof(T) == typeof(PK8)) - { - await MessageManager.SendGroupMessageAsync(GroupId, "当前版本为剑盾"); - } - else if (typeof(T) == typeof(PB8)) - { - await MessageManager.SendGroupMessageAsync(GroupId, "当前版本为晶灿钻石明亮珍珠"); - } - else if (typeof(T) == typeof(PA8)) - { - await MessageManager.SendGroupMessageAsync(GroupId, "当前版本为阿尔宙斯"); - } - else if (typeof(T) == typeof(PK9)) - { - await MessageManager.SendGroupMessageAsync(GroupId, "当前版本为朱紫"); - } + await Client.LaunchAsync(); + if (!string.IsNullOrWhiteSpace(Settings.MessageStart)) + { + await MessageManager.SendGroupMessageAsync(GroupId, Settings.MessageStart); await Task.Delay(1_000).ConfigureAwait(false); } -#pragma warning disable CA1031 // Do not catch general exception types - catch (Exception ex) -#pragma warning restore CA1031 // Do not catch general exception types + + if (typeof(T) == typeof(PK8)) + { + await MessageManager.SendGroupMessageAsync(GroupId, "当前版本为剑盾"); + } + else if (typeof(T) == typeof(PB8)) + { + await MessageManager.SendGroupMessageAsync(GroupId, "当前版本为晶灿钻石明亮珍珠"); + } + else if (typeof(T) == typeof(PA8)) + { + await MessageManager.SendGroupMessageAsync(GroupId, "当前版本为阿尔宙斯"); + } + else if (typeof(T) == typeof(PK9)) { - LogUtil.LogError(ex.Message, nameof(MiraiQQBot)); + await MessageManager.SendGroupMessageAsync(GroupId, "当前版本为朱紫"); } - }); - } - public async static void SendGroupMessage(MessageChain mc) - { - if (string.IsNullOrEmpty(Settings.GroupId)) return; - await MessageManager.SendGroupMessageAsync(Settings.GroupId, mc); - } + await Task.Delay(1_000).ConfigureAwait(false); + } +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception ex) +#pragma warning restore CA1031 // Do not catch general exception types + { + LogUtil.LogError(ex.Message, nameof(MiraiQQBot)); + } + }); + } - public async static void SendFriendMessage(string friendId, MessageChain mc) - { - if (string.IsNullOrEmpty(friendId) || string.IsNullOrEmpty(Settings.GroupId)) return; - await MessageManager.SendFriendMessageAsync(friendId, mc); - } + public async static void SendGroupMessage(MessageChain mc) + { + if (string.IsNullOrEmpty(Settings.GroupId)) return; + await MessageManager.SendGroupMessageAsync(Settings.GroupId, mc); + } - public async static void SendTempMessage(string friendId, MessageChain mc) - { - if (string.IsNullOrEmpty(friendId) || string.IsNullOrEmpty(Settings.GroupId)) return; - await MessageManager.SendTempMessageAsync(friendId, Settings.GroupId, mc); - } + public async static void SendFriendMessage(string friendId, MessageChain mc) + { + if (string.IsNullOrEmpty(friendId) || string.IsNullOrEmpty(Settings.GroupId)) return; + await MessageManager.SendFriendMessageAsync(friendId, mc); + } + + public async static void SendTempMessage(string friendId, MessageChain mc) + { + if (string.IsNullOrEmpty(friendId) || string.IsNullOrEmpty(Settings.GroupId)) return; + await MessageManager.SendTempMessageAsync(friendId, Settings.GroupId, mc); } } diff --git a/SysBot.Pokemon.QQ/MiraiQQTrade.cs b/SysBot.Pokemon.QQ/MiraiQQTrade.cs index d284b4024..b4fffca0a 100644 --- a/SysBot.Pokemon.QQ/MiraiQQTrade.cs +++ b/SysBot.Pokemon.QQ/MiraiQQTrade.cs @@ -1,33 +1,26 @@ -using Mirai.Net.Utils.Scaffolds; +using Mirai.Net.Utils.Scaffolds; using PKHeX.Core; using SysBot.Pokemon.Helpers; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Xml.Linq; -namespace SysBot.Pokemon.QQ +namespace SysBot.Pokemon.QQ; + +public class MiraiQQTrade : AbstractTrade where T : PKM, new() { - public class MiraiQQTrade : AbstractTrade where T : PKM, new() - { - private readonly string GroupId = default!; + private readonly string GroupId = default!; - public MiraiQQTrade(string qq, string nickName) - { - SetPokeTradeTrainerInfo(new PokeTradeTrainerInfo(nickName, ulong.Parse(qq))); - SetTradeQueueInfo(MiraiQQBot.Info); - GroupId = MiraiQQBot.Settings.GroupId; - } - public override IPokeTradeNotifier GetPokeTradeNotifier(T pkm, int code) - { - return new MiraiQQTradeNotifier(pkm, userInfo, code, userInfo.TrainerName, GroupId); - } + public MiraiQQTrade(string qq, string nickName) + { + SetPokeTradeTrainerInfo(new PokeTradeTrainerInfo(nickName, ulong.Parse(qq))); + SetTradeQueueInfo(MiraiQQBot.Info); + GroupId = MiraiQQBot.Settings.GroupId; + } + public override IPokeTradeNotifier GetPokeTradeNotifier(T pkm, int code) + { + return new MiraiQQTradeNotifier(pkm, userInfo, code, userInfo.TrainerName, GroupId); + } - public override void SendMessage(string message) - { - MiraiQQBot.SendGroupMessage(new MessageChainBuilder().At(userInfo.ID.ToString()).Plain(message).Build()); - } + public override void SendMessage(string message) + { + MiraiQQBot.SendGroupMessage(new MessageChainBuilder().At(userInfo.ID.ToString()).Plain(message).Build()); } } diff --git a/SysBot.Pokemon.QQ/MiraiQQTradeNotifier.cs b/SysBot.Pokemon.QQ/MiraiQQTradeNotifier.cs index 1b96bdc99..ab4c8b7a9 100644 --- a/SysBot.Pokemon.QQ/MiraiQQTradeNotifier.cs +++ b/SysBot.Pokemon.QQ/MiraiQQTradeNotifier.cs @@ -1,122 +1,120 @@ -using PKHeX.Core; +using PKHeX.Core; using SysBot.Base; using System; using System.Linq; using Mirai.Net.Utils.Scaffolds; using System.Text.RegularExpressions; -using System.Threading.Channels; using System.Collections.Generic; -namespace SysBot.Pokemon.QQ +namespace SysBot.Pokemon.QQ; + +public class MiraiQQTradeNotifier : IPokeTradeNotifier where T : PKM, new() { - public class MiraiQQTradeNotifier : IPokeTradeNotifier where T : PKM, new() - { - private T Data { get; } - private PokeTradeTrainerInfo Info { get; } - private int Code { get; } - private string Username { get; } + private T Data { get; } + private PokeTradeTrainerInfo Info { get; } + private int Code { get; } + private string Username { get; } - private string GroupId { get; } + private string GroupId { get; } - public MiraiQQTradeNotifier(T data, PokeTradeTrainerInfo info, int code, string username, string groupId) - { - Data = data; - Info = info; - Code = code; - Username = username; - GroupId = groupId; - LogUtil.LogText($"Created trade details for {Username} - {Code}"); - } + public MiraiQQTradeNotifier(T data, PokeTradeTrainerInfo info, int code, string username, string groupId) + { + Data = data; + Info = info; + Code = code; + Username = username; + GroupId = groupId; + LogUtil.LogText($"Created trade details for {Username} - {Code}"); + } - public Action>? OnFinish { private get; set; } + public Action>? OnFinish { private get; set; } - public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, string message) + public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, string message) + { + LogUtil.LogText(message); + if (message.Contains("Found Trading Partner:")) { - LogUtil.LogText(message); - if (message.Contains("Found Trading Partner:")) - { - Regex regex = new Regex("TID: (\\d+)"); - string tid = regex.Match(message).Groups[1].ToString(); - regex = new Regex("SID: (\\d+)"); - string sid = regex.Match(message).Groups[1].ToString(); - MiraiQQBot.SendGroupMessage(new MessageChainBuilder().Plain($"找到你了,你的SID7:{sid},TID7:{tid}").Build()); - } - else if (message.StartsWith("批量")) - { - MiraiQQBot.SendGroupMessage(new MessageChainBuilder().Plain(message).Build()); - } + Regex regex = new Regex("TID: (\\d+)"); + string tid = regex.Match(message).Groups[1].ToString(); + regex = new Regex("SID: (\\d+)"); + string sid = regex.Match(message).Groups[1].ToString(); + MiraiQQBot.SendGroupMessage(new MessageChainBuilder().Plain($"找到你了,你的SID7:{sid},TID7:{tid}").Build()); } - - public void TradeCanceled(PokeRoutineExecutor routine, PokeTradeDetail info, PokeTradeResult msg) + else if (message.StartsWith("批量")) { - OnFinish?.Invoke(routine); - var line = $"@{info.Trainer.TrainerName}: Trade canceled, {msg}"; - LogUtil.LogText(line); - MiraiQQBot.SendGroupMessage(new MessageChainBuilder().At($"{info.Trainer.ID}").Plain(" 取消").Build()); + MiraiQQBot.SendGroupMessage(new MessageChainBuilder().Plain(message).Build()); } + } - public void TradeFinished(PokeRoutineExecutor routine, PokeTradeDetail info, T result) - { - OnFinish?.Invoke(routine); - var tradedToUser = Data.Species; - var message = $"@{info.Trainer.TrainerName}: " + (tradedToUser != 0 - ? $"Trade finished. Enjoy your {(Species) tradedToUser}!" - : "Trade finished!"); - LogUtil.LogText(message); - MiraiQQBot.SendGroupMessage(new MessageChainBuilder().At($"{info.Trainer.ID}").Plain(" 完成").Build()); - } + public void TradeCanceled(PokeRoutineExecutor routine, PokeTradeDetail info, PokeTradeResult msg) + { + OnFinish?.Invoke(routine); + var line = $"@{info.Trainer.TrainerName}: Trade canceled, {msg}"; + LogUtil.LogText(line); + MiraiQQBot.SendGroupMessage(new MessageChainBuilder().At($"{info.Trainer.ID}").Plain(" 取消").Build()); + } - public void TradeInitialize(PokeRoutineExecutor routine, PokeTradeDetail info) - { - var receive = Data.Species == 0 ? string.Empty : $" ({Data.Nickname})"; - var msg = - $"@{info.Trainer.TrainerName} (ID: {info.ID}): Initializing trade{receive} with you. Please be ready."; - msg += $" Your trade code is: {info.Code:0000 0000}"; - LogUtil.LogText(msg); - var text = $"\n派送:{ShowdownTranslator.GameStringsZh.Species[Data.Species]}\n密码:{info.Code:0000 0000}\n状态:初始化"; - List batchPKMs = (List)info.Context.GetValueOrDefault("batch", new List()); - if (batchPKMs.Count > 1) - { - text = $"\n批量派送{batchPKMs.Count}只宝可梦\n密码:{info.Code:0000 0000}\n状态:初始化"; - } - MiraiQQBot.SendGroupMessage(new MessageChainBuilder().At($"{info.Trainer.ID}").Plain(text).Build()); - } + public void TradeFinished(PokeRoutineExecutor routine, PokeTradeDetail info, T result) + { + OnFinish?.Invoke(routine); + var tradedToUser = Data.Species; + var message = $"@{info.Trainer.TrainerName}: " + (tradedToUser != 0 + ? $"Trade finished. Enjoy your {(Species) tradedToUser}!" + : "Trade finished!"); + LogUtil.LogText(message); + MiraiQQBot.SendGroupMessage(new MessageChainBuilder().At($"{info.Trainer.ID}").Plain(" 完成").Build()); + } - public void TradeSearching(PokeRoutineExecutor routine, PokeTradeDetail info) + public void TradeInitialize(PokeRoutineExecutor routine, PokeTradeDetail info) + { + var receive = Data.Species == 0 ? string.Empty : $" ({Data.Nickname})"; + var msg = + $"@{info.Trainer.TrainerName} (ID: {info.ID}): Initializing trade{receive} with you. Please be ready."; + msg += $" Your trade code is: {info.Code:0000 0000}"; + LogUtil.LogText(msg); + var text = $"\n派送:{ShowdownTranslator.GameStringsZh.Species[Data.Species]}\n密码:{info.Code:0000 0000}\n状态:初始化"; + List batchPKMs = (List)info.Context.GetValueOrDefault("batch", new List()); + if (batchPKMs.Count > 1) { - var name = Info.TrainerName; - var trainer = string.IsNullOrEmpty(name) ? string.Empty : $", @{name}"; - var message = $"I'm waiting for you{trainer}! My IGN is {routine.InGameName}."; - message += $" Your trade code is: {info.Code:0000 0000}"; - LogUtil.LogText(message); - var text = $"派送:{ShowdownTranslator.GameStringsZh.Species[Data.Species]}\n密码:{info.Code:0000 0000}\n状态:搜索中"; - List batchPKMs = (List)info.Context.GetValueOrDefault("batch", new List()); - if (batchPKMs.Count > 1) - { - text = $"批量派送{batchPKMs.Count}只宝可梦\n密码:{info.Code:0000 0000}\n状态:搜索中"; - } - MiraiQQBot.SendGroupMessage(new MessageChainBuilder().At($"{info.Trainer.ID}").Plain(text).Build()); + text = $"\n批量派送{batchPKMs.Count}只宝可梦\n密码:{info.Code:0000 0000}\n状态:初始化"; } + MiraiQQBot.SendGroupMessage(new MessageChainBuilder().At($"{info.Trainer.ID}").Plain(text).Build()); + } - public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, PokeTradeSummary message) + public void TradeSearching(PokeRoutineExecutor routine, PokeTradeDetail info) + { + var name = Info.TrainerName; + var trainer = string.IsNullOrEmpty(name) ? string.Empty : $", @{name}"; + var message = $"I'm waiting for you{trainer}! My IGN is {routine.InGameName}."; + message += $" Your trade code is: {info.Code:0000 0000}"; + LogUtil.LogText(message); + var text = $"派送:{ShowdownTranslator.GameStringsZh.Species[Data.Species]}\n密码:{info.Code:0000 0000}\n状态:搜索中"; + List batchPKMs = (List)info.Context.GetValueOrDefault("batch", new List()); + if (batchPKMs.Count > 1) { - var msg = message.Summary; - if (message.Details.Count > 0) - msg += ", " + string.Join(", ", message.Details.Select(z => $"{z.Heading}: {z.Detail}")); - LogUtil.LogText(msg); - MiraiQQBot.SendGroupMessage(msg); + text = $"批量派送{batchPKMs.Count}只宝可梦\n密码:{info.Code:0000 0000}\n状态:搜索中"; } + MiraiQQBot.SendGroupMessage(new MessageChainBuilder().At($"{info.Trainer.ID}").Plain(text).Build()); + } + + public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, PokeTradeSummary message) + { + var msg = message.Summary; + if (message.Details.Count > 0) + msg += ", " + string.Join(", ", message.Details.Select(z => $"{z.Heading}: {z.Detail}")); + LogUtil.LogText(msg); + MiraiQQBot.SendGroupMessage(msg); + } - public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, T result, string message) + public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, T result, string message) + { + var msg = $"Details for {result.FileName}: " + message; + LogUtil.LogText(msg); + if (result.Species != 0 && info.Type == PokeTradeType.Dump) { - var msg = $"Details for {result.FileName}: " + message; - LogUtil.LogText(msg); - if (result.Species != 0 && info.Type == PokeTradeType.Dump) - { - var text = - $"species:{result.Species}\npid:{result.PID}\nec:{result.EncryptionConstant}\nIVs:{string.Join(",", result.IVs)}\nisShiny:{result.IsShiny}"; - MiraiQQBot.SendGroupMessage(text); - } + var text = + $"species:{result.Species}\npid:{result.PID}\nec:{result.EncryptionConstant}\nIVs:{string.Join(",", result.IVs)}\nisShiny:{result.IsShiny}"; + MiraiQQBot.SendGroupMessage(text); } } -} \ No newline at end of file +} diff --git a/SysBot.Pokemon.QQ/Modules/AliveModule.cs b/SysBot.Pokemon.QQ/Modules/AliveModule.cs index 46c6ed29c..8d264c573 100644 --- a/SysBot.Pokemon.QQ/Modules/AliveModule.cs +++ b/SysBot.Pokemon.QQ/Modules/AliveModule.cs @@ -1,4 +1,4 @@ -using Mirai.Net.Data.Messages; +using Mirai.Net.Data.Messages; using Mirai.Net.Data.Messages.Concretes; using Mirai.Net.Data.Messages.Receivers; using Mirai.Net.Modules; @@ -8,22 +8,20 @@ using System.Linq; using System.Reactive.Linq; -namespace SysBot.Pokemon.QQ +namespace SysBot.Pokemon.QQ; +public class AliveModule : IModule where T : PKM, new() { - public class AliveModule : IModule where T : PKM, new() + public bool? IsEnable { get; set; } = true; + + public async void Execute(MessageReceiverBase @base) { - public bool? IsEnable { get; set; } = true; + QQSettings settings = MiraiQQBot.Settings; - public async void Execute(MessageReceiverBase @base) + var receiver = @base.Concretize(); + if (settings.AliveMsg == receiver.MessageChain.OfType()?.FirstOrDefault()?.Text) { - QQSettings settings = MiraiQQBot.Settings; - - var receiver = @base.Concretize(); - if (settings.AliveMsg == receiver.MessageChain.OfType()?.FirstOrDefault()?.Text) - { - await MessageManager.SendGroupMessageAsync(receiver.Sender.Group.Id, settings.AliveMsg); - return; - } + await MessageManager.SendGroupMessageAsync(receiver.Sender.Group.Id, settings.AliveMsg); + return; } } } diff --git a/SysBot.Pokemon.QQ/Modules/CommandModule.cs b/SysBot.Pokemon.QQ/Modules/CommandModule.cs index 2b94e80f1..ad3a74c6d 100644 --- a/SysBot.Pokemon.QQ/Modules/CommandModule.cs +++ b/SysBot.Pokemon.QQ/Modules/CommandModule.cs @@ -1,4 +1,4 @@ -using Mirai.Net.Data.Messages; +using Mirai.Net.Data.Messages; using Mirai.Net.Data.Messages.Concretes; using Mirai.Net.Data.Messages.Receivers; using Mirai.Net.Modules; @@ -7,53 +7,53 @@ using System.Linq; using System.Reactive.Linq; -namespace SysBot.Pokemon.QQ +namespace SysBot.Pokemon.QQ; + +public class CommandModule : IModule where T : PKM, new() { - public class CommandModule : IModule where T : PKM, new() + public bool? IsEnable { get; set; } = true; + + public void Execute(MessageReceiverBase @base) { - public bool? IsEnable { get; set; } = true; + var receiver = @base.Concretize(); + QQSettings settings = MiraiQQBot.Settings; + + if (receiver.MessageChain.OfType().All(x => x.Target != settings.QQ)) return; - public void Execute(MessageReceiverBase @base) + var text = receiver.MessageChain.OfType()?.FirstOrDefault()?.Text ?? ""; + if (string.IsNullOrWhiteSpace(text)) return; + if (text.Trim().StartsWith("取消")) { - var receiver = @base.Concretize(); - QQSettings settings = MiraiQQBot.Settings; - - if (receiver.MessageChain.OfType().All(x => x.Target != settings.QQ)) return; - - var text = receiver.MessageChain.OfType()?.FirstOrDefault()?.Text ?? ""; - if (string.IsNullOrWhiteSpace(text)) return; - if (text.Trim().StartsWith("取消")) - { - var result = MiraiQQBot.Info.ClearTrade(ulong.Parse(receiver.Sender.Id)); - MiraiQQBot.SendGroupMessage(new MessageChainBuilder().At(receiver.Sender.Id).Plain($" {GetClearTradeMessage(result)}").Build()); - } - else if (text.Trim().StartsWith("位置")) - { - var result = MiraiQQBot.Info.CheckPosition(ulong.Parse(receiver.Sender.Id)); - MiraiQQBot.SendGroupMessage(new MessageChainBuilder().At(receiver.Sender.Id).Plain($" {GetQueueCheckResultMessage(result)}").Build()); - } + var result = MiraiQQBot.Info.ClearTrade(ulong.Parse(receiver.Sender.Id)); + MiraiQQBot.SendGroupMessage(new MessageChainBuilder().At(receiver.Sender.Id).Plain($" {GetClearTradeMessage(result)}").Build()); } - - public static string GetQueueCheckResultMessage(QueueCheckResult result) + else if (text.Trim().StartsWith("位置")) { - if (!result.InQueue || result.Detail is null) - return "你不在队列里"; - var msg = $"你在第{result.Position}位"; - var pk = result.Detail.Trade.TradeData; - if (pk.Species != 0) - msg += $",交换宝可梦:{ShowdownTranslator.GameStringsZh.Species[result.Detail.Trade.TradeData.Species]}"; - return msg; + var result = MiraiQQBot.Info.CheckPosition(ulong.Parse(receiver.Sender.Id)); + MiraiQQBot.SendGroupMessage(new MessageChainBuilder().At(receiver.Sender.Id).Plain($" {GetQueueCheckResultMessage(result)}").Build()); } + } - private static string GetClearTradeMessage(QueueResultRemove result) + public static string GetQueueCheckResultMessage(QueueCheckResult result) + { + if (!result.InQueue || result.Detail is null) + return "你不在队列里"; + var msg = $"你在第{result.Position}位"; + var pk = result.Detail.Trade.TradeData; + if (pk.Species != 0) + msg += $",交换宝可梦:{ShowdownTranslator.GameStringsZh.Species[result.Detail.Trade.TradeData.Species]}"; + return msg; + } + + private static string GetClearTradeMessage(QueueResultRemove result) + { + return result switch { - return result switch - { - QueueResultRemove.CurrentlyProcessing => "你正在交换中", - QueueResultRemove.CurrentlyProcessingRemoved => "正在删除", - QueueResultRemove.Removed => "已删除", - _ => "你不在队列里", - }; - } + QueueResultRemove.CurrentlyProcessing => "你正在交换中", + QueueResultRemove.CurrentlyProcessingRemoved => "正在删除", + QueueResultRemove.Removed => "已删除", + _ => "你不在队列里", + }; } } + diff --git a/SysBot.Pokemon.QQ/Modules/FileModule.cs b/SysBot.Pokemon.QQ/Modules/FileModule.cs index 3e5d57632..638b18985 100644 --- a/SysBot.Pokemon.QQ/Modules/FileModule.cs +++ b/SysBot.Pokemon.QQ/Modules/FileModule.cs @@ -1,4 +1,4 @@ -using Mirai.Net.Data.Messages; +using Mirai.Net.Data.Messages; using Mirai.Net.Data.Messages.Concretes; using Mirai.Net.Data.Messages.Receivers; using Mirai.Net.Modules; @@ -13,52 +13,51 @@ using System.Net.Http; using System.Reactive.Linq; -namespace SysBot.Pokemon.QQ +namespace SysBot.Pokemon.QQ; + +public class FileModule : IModule where T : PKM, new() { - public class FileModule : IModule where T : PKM, new() - { - public bool? IsEnable { get; set; } = true; + public bool? IsEnable { get; set; } = true; - public async void Execute(MessageReceiverBase @base) - { - QQSettings settings = MiraiQQBot.Settings; + public async void Execute(MessageReceiverBase @base) + { + QQSettings settings = MiraiQQBot.Settings; - var receiver = @base.Concretize(); + var receiver = @base.Concretize(); - var senderQQ = receiver.Sender.Id; - var nickname = receiver.Sender.Name; - var groupId = receiver.Sender.Group.Id; + var senderQQ = receiver.Sender.Id; + var nickname = receiver.Sender.Name; + var groupId = receiver.Sender.Group.Id; - var fileMessage = receiver.MessageChain.OfType()?.FirstOrDefault(); - if (fileMessage == null) return; - LogUtil.LogInfo("In file module", nameof(FileModule)); - var fileName = fileMessage.Name; - if (!FileTradeHelper.ValidFileName(fileName) || !FileTradeHelper.ValidFileSize(fileMessage.Size)) - { - await MessageManager.SendGroupMessageAsync(groupId, "非法文件"); - return; - } + var fileMessage = receiver.MessageChain.OfType()?.FirstOrDefault(); + if (fileMessage == null) return; + LogUtil.LogInfo("In file module", nameof(FileModule)); + var fileName = fileMessage.Name; + if (!FileTradeHelper.ValidFileName(fileName) || !FileTradeHelper.ValidFileSize(fileMessage.Size)) + { + await MessageManager.SendGroupMessageAsync(groupId, "非法文件"); + return; + } - List pkms = default!; - try - { - var f = await FileManager.GetFileAsync(groupId, fileMessage.FileId, true); - using var client = new HttpClient(); - byte[] data = client.GetByteArrayAsync(f.DownloadInfo.Url).Result; - pkms = FileTradeHelper.Bin2List(data); - await FileManager.DeleteFileAsync(groupId, fileMessage.FileId); - } - catch (Exception ex) - { - LogUtil.LogError(ex.Message, nameof(FileModule)); - return; - } - if (pkms.Count > 1 && pkms.Count <= FileTradeHelper.MaxCountInBin) - new MiraiQQTrade(senderQQ, nickname).StartTradeMultiPKM(pkms); - else if (pkms.Count == 1) - new MiraiQQTrade(senderQQ, nickname).StartTradePKM(pkms[0]); - else - await MessageManager.SendGroupMessageAsync(groupId, "文件内容不正确"); + List pkms = default!; + try + { + var f = await FileManager.GetFileAsync(groupId, fileMessage.FileId, true); + using var client = new HttpClient(); + byte[] data = client.GetByteArrayAsync(f.DownloadInfo.Url).Result; + pkms = FileTradeHelper.Bin2List(data); + await FileManager.DeleteFileAsync(groupId, fileMessage.FileId); + } + catch (Exception ex) + { + LogUtil.LogError(ex.Message, nameof(FileModule)); + return; } + if (pkms.Count > 1 && pkms.Count <= FileTradeHelper.MaxCountInBin) + new MiraiQQTrade(senderQQ, nickname).StartTradeMultiPKM(pkms); + else if (pkms.Count == 1) + new MiraiQQTrade(senderQQ, nickname).StartTradePKM(pkms[0]); + else + await MessageManager.SendGroupMessageAsync(groupId, "文件内容不正确"); } } diff --git a/SysBot.Pokemon.QQ/Modules/PsModule.cs b/SysBot.Pokemon.QQ/Modules/PsModule.cs index bde1297f1..187b0b895 100644 --- a/SysBot.Pokemon.QQ/Modules/PsModule.cs +++ b/SysBot.Pokemon.QQ/Modules/PsModule.cs @@ -1,88 +1,82 @@ -using Mirai.Net.Data.Messages; +using Mirai.Net.Data.Messages; using Mirai.Net.Data.Messages.Concretes; using Mirai.Net.Data.Messages.Receivers; using Mirai.Net.Modules; using Mirai.Net.Utils.Scaffolds; using PKHeX.Core; using SysBot.Base; -using System; -using System.Collections.Generic; using System.Linq; -using System.Net.Http; using System.Reactive.Linq; -using System.Reflection; -namespace SysBot.Pokemon.QQ +namespace SysBot.Pokemon.QQ; +public class PsModule : IModule where T : PKM, new() { - public class PsModule : IModule where T : PKM, new() + public bool? IsEnable { get; set; } = true; + + public void Execute(MessageReceiverBase @base) { - public bool? IsEnable { get; set; } = true; + var receiver = @base.Concretize(); + QQSettings settings = MiraiQQBot.Settings; - public void Execute(MessageReceiverBase @base) - { - var receiver = @base.Concretize(); - QQSettings settings = MiraiQQBot.Settings; + if (receiver.MessageChain.OfType().All(x => x.Target != settings.QQ)) return; - if (receiver.MessageChain.OfType().All(x => x.Target != settings.QQ)) return; + var text = receiver.MessageChain.OfType()?.FirstOrDefault()?.Text ?? ""; + if (string.IsNullOrWhiteSpace(text)) return; + var qq = receiver.Sender.Id; + var nickName = receiver.Sender.Name; + var groupId = receiver.GroupId; + // 中英文判断 + if (IsChinesePS(text)) + ProcessChinesePS(text, qq, nickName, groupId); + else if (IsPS(text)) + ProcessPS(text, qq, nickName, groupId); + } - var text = receiver.MessageChain.OfType()?.FirstOrDefault()?.Text ?? ""; - if (string.IsNullOrWhiteSpace(text)) return; - var qq = receiver.Sender.Id; - var nickName = receiver.Sender.Name; - var groupId = receiver.GroupId; - // 中英文判断 - if (IsChinesePS(text)) - ProcessChinesePS(text, qq, nickName, groupId); - else if (IsPS(text)) - ProcessPS(text, qq, nickName, groupId); - } + private void ProcessPS(string text, string qq, string nickName, string groupId) + { + LogUtil.LogInfo($"收到ps代码:\n{text}", nameof(PsModule)); + var pss = text.Split("\n\n"); + if (pss.Length > 1) + new MiraiQQTrade(qq, nickName).StartTradeMultiPs(text); + else + new MiraiQQTrade(qq, nickName).StartTradePs(text); + } - private void ProcessPS(string text, string qq, string nickName, string groupId) + private void ProcessChinesePS(string text, string qq, string nickName, string groupId) + { + LogUtil.LogInfo($"收到中文ps代码:\n{text}", nameof(PsModule)); + var pss = text.Split("+"); + if (pss.Length > 1) + new MiraiQQTrade(qq, nickName).StartTradeMultiChinesePs(text); + else { - LogUtil.LogInfo($"收到ps代码:\n{text}", nameof(PsModule)); - var pss = text.Split("\n\n"); - if (pss.Length > 1) - new MiraiQQTrade(qq, nickName).StartTradeMultiPs(text); - else - new MiraiQQTrade(qq, nickName).StartTradePs(text); + new MiraiQQTrade(qq, nickName).StartTradeChinesePs(text); } - private void ProcessChinesePS(string text, string qq, string nickName, string groupId) - { - LogUtil.LogInfo($"收到中文ps代码:\n{text}", nameof(PsModule)); - var pss = text.Split("+"); - if (pss.Length > 1) - new MiraiQQTrade(qq, nickName).StartTradeMultiChinesePs(text); - else - { - new MiraiQQTrade(qq, nickName).StartTradeChinesePs(text); - } - - } + } - private static bool IsChinesePS(string str) + private static bool IsChinesePS(string str) + { + var gameStrings = ShowdownTranslator.GameStringsZh; + for (int i = 1; i < gameStrings.Species.Count; i++) { - var gameStrings = ShowdownTranslator.GameStringsZh; - for (int i = 1; i < gameStrings.Species.Count; i++) + if (str.Contains(gameStrings.Species[i])) { - if (str.Contains(gameStrings.Species[i])) - { - return true; - } + return true; } - return false; } - private static bool IsPS(string str) + return false; + } + private static bool IsPS(string str) + { + var gameStrings = ShowdownTranslator.GameStringsEn; + for (int i = 1; i < gameStrings.Species.Count; i++) { - var gameStrings = ShowdownTranslator.GameStringsEn; - for (int i = 1; i < gameStrings.Species.Count; i++) + if (str.Contains(gameStrings.Species[i])) { - if (str.Contains(gameStrings.Species[i])) - { - return true; - } + return true; } - return false; } + return false; } } diff --git a/SysBot.Pokemon/Helpers/AbstractTrade.cs b/SysBot.Pokemon/Helpers/AbstractTrade.cs index 04a1dcebb..b1cd25ed5 100644 --- a/SysBot.Pokemon/Helpers/AbstractTrade.cs +++ b/SysBot.Pokemon/Helpers/AbstractTrade.cs @@ -5,438 +5,436 @@ using System.Collections.Generic; using System.Linq; -namespace SysBot.Pokemon.Helpers +namespace SysBot.Pokemon.Helpers; +/// +/// 宝可梦抽象交易类 +/// 本类需要实现SendMessage,同时也要实现一个多参数的构造函数, +/// 参数应该包括该类型机器人发送消息的相关信息,以便SendMessage使用 +/// 注意在实现抽象类的构造方法里一定要调用SetPokeTradeTrainerInfo和SetTradeQueueInfo +/// +/// +public abstract class AbstractTrade where T : PKM, new() { - /// - /// 宝可梦抽象交易类 - /// 本类需要实现SendMessage,同时也要实现一个多参数的构造函数, - /// 参数应该包括该类型机器人发送消息的相关信息,以便SendMessage使用 - /// 注意在实现抽象类的构造方法里一定要调用SetPokeTradeTrainerInfo和SetTradeQueueInfo - /// - /// - public abstract class AbstractTrade where T : PKM, new() - { - public abstract void SendMessage(string message);//完善此方法以实现发送消息功能 - public abstract IPokeTradeNotifier GetPokeTradeNotifier(T pkm, int code);//完善此方法以实现消息通知功能 - protected PokeTradeTrainerInfo userInfo = default!; - private TradeQueueInfo queueInfo = default!; + public abstract void SendMessage(string message);//完善此方法以实现发送消息功能 + public abstract IPokeTradeNotifier GetPokeTradeNotifier(T pkm, int code);//完善此方法以实现消息通知功能 + protected PokeTradeTrainerInfo userInfo = default!; + private TradeQueueInfo queueInfo = default!; - public void SetPokeTradeTrainerInfo(PokeTradeTrainerInfo pokeTradeTrainerInfo) - { - userInfo = pokeTradeTrainerInfo; - } + public void SetPokeTradeTrainerInfo(PokeTradeTrainerInfo pokeTradeTrainerInfo) + { + userInfo = pokeTradeTrainerInfo; + } - public void SetTradeQueueInfo(TradeQueueInfo queueInfo) - { - this.queueInfo = queueInfo; - } + public void SetTradeQueueInfo(TradeQueueInfo queueInfo) + { + this.queueInfo = queueInfo; + } - public void StartTradePs(string ps) + public void StartTradePs(string ps) + { + var _ = CheckAndGetPkm(ps, out var msg, out var pkm); + if (!_) { - var _ = CheckAndGetPkm(ps, out var msg, out var pkm); - if (!_) - { - SendMessage(msg); - return; - } - var foreign = ps.Contains("Language: "); - StartTradeWithoutCheck(pkm, foreign); + SendMessage(msg); + return; } - public void StartTradeChinesePs(string chinesePs) + var foreign = ps.Contains("Language: "); + StartTradeWithoutCheck(pkm, foreign); + } + public void StartTradeChinesePs(string chinesePs) + { + var ps = ShowdownTranslator.Chinese2Showdown(chinesePs); + LogUtil.LogInfo($"中文转换后ps代码:\n{ps}", nameof(AbstractTrade)); + StartTradePs(ps); + } + public void StartTradePKM(T pkm) + { + var _ = CheckPkm(pkm, out var msg); + if (!_) { - var ps = ShowdownTranslator.Chinese2Showdown(chinesePs); - LogUtil.LogInfo($"中文转换后ps代码:\n{ps}", nameof(AbstractTrade)); - StartTradePs(ps); + SendMessage(msg); + return; } - public void StartTradePKM(T pkm) - { - var _ = CheckPkm(pkm, out var msg); - if (!_) - { - SendMessage(msg); - return; - } - - StartTradeWithoutCheck(pkm); - } - public void StartTradeMultiPs(string pss) - { - var psList = pss.Split("\n\n").ToList(); - if (!JudgeMultiNum(psList.Count)) return; - - var pkms = GetPKMsFromPsList(psList, isChinesePS: false, out int invalidCount, out List skipAutoOTList); - if (!JudgeInvalidCount(invalidCount, psList.Count)) return; + StartTradeWithoutCheck(pkm); + } + public void StartTradeMultiPs(string pss) + { + var psList = pss.Split("\n\n").ToList(); + if (!JudgeMultiNum(psList.Count)) return; - var code = queueInfo.GetRandomTradeCode(); - var __ = AddToTradeQueue(pkms, code, skipAutoOTList, - PokeRoutineType.LinkTrade, out string message); - SendMessage(message); - } + var pkms = GetPKMsFromPsList(psList, isChinesePS: false, out int invalidCount, out List skipAutoOTList); - public void StartTradeMultiChinesePs(string chinesePssString) - { - var chinesePsList = chinesePssString.Split('+').ToList(); - if (!JudgeMultiNum(chinesePsList.Count)) return; + if (!JudgeInvalidCount(invalidCount, psList.Count)) return; - List pkms = GetPKMsFromPsList(chinesePsList, true, out int invalidCount, out List skipAutoOTList); + var code = queueInfo.GetRandomTradeCode(); + var __ = AddToTradeQueue(pkms, code, skipAutoOTList, + PokeRoutineType.LinkTrade, out string message); + SendMessage(message); + } - if (!JudgeInvalidCount(invalidCount, chinesePsList.Count)) return; + public void StartTradeMultiChinesePs(string chinesePssString) + { + var chinesePsList = chinesePssString.Split('+').ToList(); + if (!JudgeMultiNum(chinesePsList.Count)) return; - var code = queueInfo.GetRandomTradeCode(); - var __ = AddToTradeQueue(pkms, code, skipAutoOTList, - PokeRoutineType.LinkTrade, out string message); - SendMessage(message); - } + List pkms = GetPKMsFromPsList(chinesePsList, true, out int invalidCount, out List skipAutoOTList); - public void StartTradeMultiPKM(List rawPkms) - { - if (!JudgeMultiNum(rawPkms.Count)) return; + if (!JudgeInvalidCount(invalidCount, chinesePsList.Count)) return; - List pkms = new(); - List skipAutoOTList = new(); - int invalidCount = 0; - for (var i = 0; i < rawPkms.Count; i++) - { - var _ = CheckPkm(rawPkms[i], out var msg); - if (!_) - { - LogUtil.LogInfo($"批量第{i + 1}只宝可梦有问题:{msg}", nameof(AbstractTrade)); - invalidCount++; - } - else - { - LogUtil.LogInfo($"批量第{i + 1}只:{GameInfo.GetStrings("zh-Hans").Species[rawPkms[i].Species]}", nameof(AbstractTrade)); - skipAutoOTList.Add(false); - pkms.Add(rawPkms[i]); - } - } + var code = queueInfo.GetRandomTradeCode(); + var __ = AddToTradeQueue(pkms, code, skipAutoOTList, + PokeRoutineType.LinkTrade, out string message); + SendMessage(message); + } - if (!JudgeInvalidCount(invalidCount, rawPkms.Count)) return; + public void StartTradeMultiPKM(List rawPkms) + { + if (!JudgeMultiNum(rawPkms.Count)) return; - var code = queueInfo.GetRandomTradeCode(); - var __ = AddToTradeQueue(pkms, code, skipAutoOTList, - PokeRoutineType.LinkTrade, out string message); - SendMessage(message); - } - /// - /// 根据pokemon showdown代码生成对应版本的PKM文件 - /// - /// ps代码 - /// 是否是中文ps - /// 不合法的宝可梦数量 - /// 需要跳过自id的列表 - /// - private List GetPKMsFromPsList(List psList, bool isChinesePS, out int invalidCount, out List skipAutoOTList) + List pkms = new(); + List skipAutoOTList = new(); + int invalidCount = 0; + for (var i = 0; i < rawPkms.Count; i++) { - List pkms = new(); - skipAutoOTList = new List(); - invalidCount = 0; - for (var i = 0; i < psList.Count; i++) + var _ = CheckPkm(rawPkms[i], out var msg); + if (!_) { - var ps = isChinesePS ? ShowdownTranslator.Chinese2Showdown(psList[i]) : psList[i]; - var _ = CheckAndGetPkm(ps, out var msg, out var pkm); - if (!_) - { - LogUtil.LogInfo($"批量第{i + 1}只宝可梦有问题:{msg}", nameof(AbstractTrade)); - invalidCount++; - } - else - { - LogUtil.LogInfo($"批量第{i + 1}只:\n{ps}", nameof(AbstractTrade)); - skipAutoOTList.Add(ps.Contains("Language: ")); - pkms.Add(pkm); - } + LogUtil.LogInfo($"批量第{i + 1}只宝可梦有问题:{msg}", nameof(AbstractTrade)); + invalidCount++; } - return pkms; - } - /// - /// 判断是否符合批量规则 - /// - /// 待计算的数量 - /// - private bool JudgeMultiNum(int multiNum) - { - var maxPkmsPerTrade = queueInfo.Hub.Config.Trade.MaxPkmsPerTrade; - if (maxPkmsPerTrade <= 1) + else { - SendMessage("请联系群主将trade/MaxPkmsPerTrade配置改为大于1"); - return false; + LogUtil.LogInfo($"批量第{i + 1}只:{GameInfo.GetStrings("zh-Hans").Species[rawPkms[i].Species]}", nameof(AbstractTrade)); + skipAutoOTList.Add(false); + pkms.Add(rawPkms[i]); } - else if (multiNum > maxPkmsPerTrade) - { - SendMessage($"批量交换宝可梦数量应小于等于{maxPkmsPerTrade}"); - return false; - } - return true; } - /// - /// 判断无效数量 - /// - /// - /// - /// - private bool JudgeInvalidCount(int invalidCount, int totalCount) + + if (!JudgeInvalidCount(invalidCount, rawPkms.Count)) return; + + var code = queueInfo.GetRandomTradeCode(); + var __ = AddToTradeQueue(pkms, code, skipAutoOTList, + PokeRoutineType.LinkTrade, out string message); + SendMessage(message); + } + /// + /// 根据pokemon showdown代码生成对应版本的PKM文件 + /// + /// ps代码 + /// 是否是中文ps + /// 不合法的宝可梦数量 + /// 需要跳过自id的列表 + /// + private List GetPKMsFromPsList(List psList, bool isChinesePS, out int invalidCount, out List skipAutoOTList) + { + List pkms = new(); + skipAutoOTList = new List(); + invalidCount = 0; + for (var i = 0; i < psList.Count; i++) { - if (invalidCount == totalCount) + var ps = isChinesePS ? ShowdownTranslator.Chinese2Showdown(psList[i]) : psList[i]; + var _ = CheckAndGetPkm(ps, out var msg, out var pkm); + if (!_) { - SendMessage("一个都不合法,换个屁"); - return false; + LogUtil.LogInfo($"批量第{i + 1}只宝可梦有问题:{msg}", nameof(AbstractTrade)); + invalidCount++; } - else if (invalidCount != 0) + else { - SendMessage($"期望交换的{totalCount}只宝可梦中,有{invalidCount}只不合法,仅交换合法的{totalCount - invalidCount}只"); + LogUtil.LogInfo($"批量第{i + 1}只:\n{ps}", nameof(AbstractTrade)); + skipAutoOTList.Add(ps.Contains("Language: ")); + pkms.Add(pkm); } - return true; } - - public void StartTradeWithoutCheck(T pkm, bool foreign = false) + return pkms; + } + /// + /// 判断是否符合批量规则 + /// + /// 待计算的数量 + /// + private bool JudgeMultiNum(int multiNum) + { + var maxPkmsPerTrade = queueInfo.Hub.Config.Trade.MaxPkmsPerTrade; + if (maxPkmsPerTrade <= 1) { - var code = queueInfo.GetRandomTradeCode(); - var __ = AddToTradeQueue(pkm, code, foreign, - PokeRoutineType.LinkTrade, out string message); - SendMessage(message); + SendMessage("请联系群主将trade/MaxPkmsPerTrade配置改为大于1"); + return false; } - - public void StartDump() + else if (multiNum > maxPkmsPerTrade) { - var code = queueInfo.GetRandomTradeCode(); - var __ = AddToTradeQueue(new T(), code, false, - PokeRoutineType.Dump, out string message); - SendMessage(message); + SendMessage($"批量交换宝可梦数量应小于等于{maxPkmsPerTrade}"); + return false; } - - public bool Check(T pkm, out string msg) + return true; + } + /// + /// 判断无效数量 + /// + /// + /// + /// + private bool JudgeInvalidCount(int invalidCount, int totalCount) + { + if (invalidCount == totalCount) { - try - { - if (!pkm.CanBeTraded()) - { - msg = $"取消派送, 官方禁止该宝可梦交易!"; - return false; - } - if (pkm is T pk) - { - var la = new LegalityAnalysis(pkm); - var valid = la.Valid; - if (valid) - { - msg = $"已加入等待队列. 如果你选宝可梦的速度太慢,你的派送请求将被取消!"; - return true; - } - LogUtil.LogInfo($"非法原因:\n{la.Report()}", nameof(AbstractTrade)); - } - LogUtil.LogInfo($"pkm type:{pkm.GetType()}, T:{typeof(T)}", nameof(AbstractTrade)); - var reason = "我没办法创造非法宝可梦"; - msg = $"{reason}"; - } - catch (Exception ex) - { - LogUtil.LogSafe(ex, nameof(AbstractTrade)); - msg = $"取消派送, 发生了一个错误"; - } + SendMessage("一个都不合法,换个屁"); return false; } - public bool CheckPkm(T pkm, out string msg) + else if (invalidCount != 0) { - if (!queueInfo.GetCanQueue()) - { - msg = "对不起, 我不再接受队列请求!"; - return false; - } - return Check(pkm, out msg); + SendMessage($"期望交换的{totalCount}只宝可梦中,有{invalidCount}只不合法,仅交换合法的{totalCount - invalidCount}只"); } + return true; + } - public bool CheckAndGetPkm(string setstring, out string msg, out T outPkm) - { - outPkm = new T(); - if (!queueInfo.GetCanQueue()) - { - msg = "对不起, 我不再接受队列请求!"; - return false; - } + public void StartTradeWithoutCheck(T pkm, bool foreign = false) + { + var code = queueInfo.GetRandomTradeCode(); + var __ = AddToTradeQueue(pkm, code, foreign, + PokeRoutineType.LinkTrade, out string message); + SendMessage(message); + } - var set = ShowdownUtil.ConvertToShowdown(setstring); - if (set == null) - { - msg = $"取消派送, 宝可梦昵称为空."; - return false; - } + public void StartDump() + { + var code = queueInfo.GetRandomTradeCode(); + var __ = AddToTradeQueue(new T(), code, false, + PokeRoutineType.Dump, out string message); + SendMessage(message); + } - var template = AutoLegalityWrapper.GetTemplate(set); - if (template.Species < 1) + public bool Check(T pkm, out string msg) + { + try + { + if (!pkm.CanBeTraded()) { - msg = - $"取消派送, 请使用正确的Showdown Set代码"; + msg = $"取消派送, 官方禁止该宝可梦交易!"; return false; } - - try + if (pkm is T pk) { - var sav = AutoLegalityWrapper.GetTrainerInfo(); - var pkm = sav.GetLegal(template, out var result); - if (pkm.Nickname.ToLower() == "egg" && Breeding.CanHatchAsEgg(pkm.Species)) EggTrade(pkm, template); - if (Check((T)pkm, out msg)) + var la = new LegalityAnalysis(pkm); + var valid = la.Valid; + if (valid) { - outPkm = (T)pkm; + msg = $"已加入等待队列. 如果你选宝可梦的速度太慢,你的派送请求将被取消!"; return true; } + LogUtil.LogInfo($"非法原因:\n{la.Report()}", nameof(AbstractTrade)); } - catch (Exception ex) - { - LogUtil.LogSafe(ex, nameof(AbstractTrade)); - msg = $"取消派送, 发生了一个错误"; - } - + LogUtil.LogInfo($"pkm type:{pkm.GetType()}, T:{typeof(T)}", nameof(AbstractTrade)); + var reason = "我没办法创造非法宝可梦"; + msg = $"{reason}"; + } + catch (Exception ex) + { + LogUtil.LogSafe(ex, nameof(AbstractTrade)); + msg = $"取消派送, 发生了一个错误"; + } + return false; + } + public bool CheckPkm(T pkm, out string msg) + { + if (!queueInfo.GetCanQueue()) + { + msg = "对不起, 我不再接受队列请求!"; return false; } + return Check(pkm, out msg); + } - private bool AddToTradeQueue(T pk, int code, bool skipAutoOT, - PokeRoutineType type, out string msg) + public bool CheckAndGetPkm(string setstring, out string msg, out T outPkm) + { + outPkm = new T(); + if (!queueInfo.GetCanQueue()) { - return AddToTradeQueue(new List { pk }, code, new List { skipAutoOT }, type, out msg); + msg = "对不起, 我不再接受队列请求!"; + return false; } - private bool AddToTradeQueue(List pks, int code, List skipAutoOTList, - PokeRoutineType type, out string msg) + var set = ShowdownUtil.ConvertToShowdown(setstring); + if (set == null) { - if (pks == null || pks.Count == 0) - { - msg = $"宝可梦数据为空"; - return false; - } - T pk = pks.First(); - var trainer = userInfo; - var notifier = GetPokeTradeNotifier(pk, code); - var tt = type == PokeRoutineType.SeedCheck - ? PokeTradeType.Seed - : (type == PokeRoutineType.Dump ? PokeTradeType.Dump : PokeTradeType.Specific); - var detail = - new PokeTradeDetail(pk, trainer, notifier, tt, code, true); - detail.Context.Add("skipAutoOTList", skipAutoOTList); - if (pks.Count > 0) - { - detail.Context.Add("batch", pks); - } - var trade = new TradeEntry(detail, userInfo.ID, type, userInfo.TrainerName); + msg = $"取消派送, 宝可梦昵称为空."; + return false; + } - var added = queueInfo.AddToTradeQueue(trade, userInfo.ID, false); + var template = AutoLegalityWrapper.GetTemplate(set); + if (template.Species < 1) + { + msg = + $"取消派送, 请使用正确的Showdown Set代码"; + return false; + } - if (added == QueueResultAdd.AlreadyInQueue) + try + { + var sav = AutoLegalityWrapper.GetTrainerInfo(); + var pkm = sav.GetLegal(template, out var result); + if (pkm.Nickname.ToLower() == "egg" && Breeding.CanHatchAsEgg(pkm.Species)) EggTrade(pkm, template); + if (Check((T)pkm, out msg)) { - msg = $"你已经在队列中,请不要重复发送"; - return false; + outPkm = (T)pkm; + return true; } + } + catch (Exception ex) + { + LogUtil.LogSafe(ex, nameof(AbstractTrade)); + msg = $"取消派送, 发生了一个错误"; + } - var position = queueInfo.CheckPosition(userInfo.ID, type); - //msg = $"@{name}: Added to the {type} queue, unique ID: {detail.ID}. Current Position: {position.Position}"; - msg = $"你在第{position.Position}位"; + return false; + } - var botct = queueInfo.Hub.Bots.Count; - if (position.Position > botct) - { - var eta = queueInfo.Hub.Config.Queues.EstimateDelay(position.Position, botct); - //msg += $". Estimated: {eta:F1} minutes."; - msg += $", 需等待约{eta:F1}分钟"; - } + private bool AddToTradeQueue(T pk, int code, bool skipAutoOT, + PokeRoutineType type, out string msg) + { + return AddToTradeQueue(new List { pk }, code, new List { skipAutoOT }, type, out msg); + } - return true; + private bool AddToTradeQueue(List pks, int code, List skipAutoOTList, + PokeRoutineType type, out string msg) + { + if (pks == null || pks.Count == 0) + { + msg = $"宝可梦数据为空"; + return false; + } + T pk = pks.First(); + var trainer = userInfo; + var notifier = GetPokeTradeNotifier(pk, code); + var tt = type == PokeRoutineType.SeedCheck + ? PokeTradeType.Seed + : (type == PokeRoutineType.Dump ? PokeTradeType.Dump : PokeTradeType.Specific); + var detail = + new PokeTradeDetail(pk, trainer, notifier, tt, code, true); + detail.Context.Add("skipAutoOTList", skipAutoOTList); + if (pks.Count > 0) + { + detail.Context.Add("batch", pks); } + var trade = new TradeEntry(detail, userInfo.ID, type, userInfo.TrainerName); - // https://github.com/Koi-3088/ForkBot.NET/blob/KoiTest/SysBot.Pokemon/Helpers/TradeExtensions.cs - public static void EggTrade(PKM pk, IBattleTemplate template) + var added = queueInfo.AddToTradeQueue(trade, userInfo.ID, false); + + if (added == QueueResultAdd.AlreadyInQueue) { - pk.IsNicknamed = true; - pk.Nickname = SpeciesName.GetSpeciesNameGeneration(pk.Species, pk.Language, pk.Format); - pk.Nickname = pk.Language switch - { - 1 => "タマゴ", - 3 => "Œuf", - 4 => "Uovo", - 5 => "Ei", - 7 => "Huevo", - 8 => "알", - 9 or 10 => "蛋", - _ => "Egg", - }; - - pk.IsEgg = true; - pk.EggLocation = pk switch - { - PB8 => 60010, - PK9 => 30023, - _ => 60002, //PK8 - }; - - pk.HeldItem = 0; - pk.CurrentLevel = 1; - pk.EXP = 0; - pk.MetLevel = 1; - pk.MetLocation = pk switch - { - PB8 => 65535, - PK9 => 0, - _ => 30002, //PK8 - }; + msg = $"你已经在队列中,请不要重复发送"; + return false; + } - pk.CurrentHandler = 0; - pk.OriginalTrainerFriendship = 1; - pk.HandlingTrainerName = ""; - pk.HandlingTrainerFriendship = 0; - pk.ClearMemories(); - pk.StatNature = pk.Nature; - pk.SetEVs(new int[] { 0, 0, 0, 0, 0, 0 }); + var position = queueInfo.CheckPosition(userInfo.ID, type); + //msg = $"@{name}: Added to the {type} queue, unique ID: {detail.ID}. Current Position: {position.Position}"; + msg = $"你在第{position.Position}位"; - MarkingApplicator.SetMarkings(pk); + var botct = queueInfo.Hub.Bots.Count; + if (position.Position > botct) + { + var eta = queueInfo.Hub.Config.Queues.EstimateDelay(position.Position, botct); + //msg += $". Estimated: {eta:F1} minutes."; + msg += $", 需等待约{eta:F1}分钟"; + } - pk.ClearRelearnMoves(); + return true; + } - if (pk is PK8 pk8) - { - pk8.HandlingTrainerLanguage = 0; - pk8.HandlingTrainerGender = 0; - pk8.HandlingTrainerMemory = 0; - pk8.HandlingTrainerMemoryFeeling = 0; - pk8.HandlingTrainerMemoryIntensity = 0; - pk8.DynamaxLevel = pk8.GetSuggestedDynamaxLevel(pk8, 0); - } - else if (pk is PB8 pb8) - { - pb8.HandlingTrainerLanguage = 0; - pb8.HandlingTrainerGender = 0; - pb8.HandlingTrainerMemory = 0; - pb8.HandlingTrainerMemoryFeeling = 0; - pb8.HandlingTrainerMemoryIntensity = 0; - pb8.DynamaxLevel = pb8.GetSuggestedDynamaxLevel(pb8, 0); - } - else if (pk is PK9 pk9) - { - pk9.HandlingTrainerLanguage = 0; - pk9.HandlingTrainerGender = 0; - pk9.HandlingTrainerMemory = 0; - pk9.HandlingTrainerMemoryFeeling = 0; - pk9.HandlingTrainerMemoryIntensity = 0; - pk9.ObedienceLevel = 1; - pk9.Version = 0; - pk9.BattleVersion = 0; - pk9.TeraTypeOverride = (MoveType)19; - } + // https://github.com/Koi-3088/ForkBot.NET/blob/KoiTest/SysBot.Pokemon/Helpers/TradeExtensions.cs + public static void EggTrade(PKM pk, IBattleTemplate template) + { + pk.IsNicknamed = true; + pk.Nickname = SpeciesName.GetSpeciesNameGeneration(pk.Species, pk.Language, pk.Format); + pk.Nickname = pk.Language switch + { + 1 => "タマゴ", + 3 => "Œuf", + 4 => "Uovo", + 5 => "Ei", + 7 => "Huevo", + 8 => "알", + 9 or 10 => "蛋", + _ => "Egg", + }; + + pk.IsEgg = true; + pk.EggLocation = pk switch + { + PB8 => 60010, + PK9 => 30023, + _ => 60002, //PK8 + }; + + pk.HeldItem = 0; + pk.CurrentLevel = 1; + pk.EXP = 0; + pk.MetLevel = 1; + pk.MetLocation = pk switch + { + PB8 => 65535, + PK9 => 0, + _ => 30002, //PK8 + }; - var la = new LegalityAnalysis(pk); - var enc = la.EncounterMatch; - pk.CurrentFriendship = enc is IHatchCycle s ? s.EggCycles : pk.PersonalInfo.HatchCycles; + pk.CurrentHandler = 0; + pk.OriginalTrainerFriendship = 1; + pk.HandlingTrainerName = ""; + pk.HandlingTrainerFriendship = 0; + pk.ClearMemories(); + pk.StatNature = pk.Nature; + pk.SetEVs(new int[] { 0, 0, 0, 0, 0, 0 }); - Span relearn = stackalloc ushort[4]; - la.GetSuggestedRelearnMoves(relearn, enc); - pk.SetRelearnMoves(relearn); + MarkingApplicator.SetMarkings(pk); - pk.SetSuggestedMoves(); + pk.ClearRelearnMoves(); - pk.Move1_PPUps = pk.Move2_PPUps = pk.Move3_PPUps = pk.Move4_PPUps = 0; - pk.SetMaximumPPCurrent(pk.Moves); - pk.SetSuggestedHyperTrainingData(); - pk.SetSuggestedRibbons(template, enc, true); + if (pk is PK8 pk8) + { + pk8.HandlingTrainerLanguage = 0; + pk8.HandlingTrainerGender = 0; + pk8.HandlingTrainerMemory = 0; + pk8.HandlingTrainerMemoryFeeling = 0; + pk8.HandlingTrainerMemoryIntensity = 0; + pk8.DynamaxLevel = pk8.GetSuggestedDynamaxLevel(pk8, 0); + } + else if (pk is PB8 pb8) + { + pb8.HandlingTrainerLanguage = 0; + pb8.HandlingTrainerGender = 0; + pb8.HandlingTrainerMemory = 0; + pb8.HandlingTrainerMemoryFeeling = 0; + pb8.HandlingTrainerMemoryIntensity = 0; + pb8.DynamaxLevel = pb8.GetSuggestedDynamaxLevel(pb8, 0); + } + else if (pk is PK9 pk9) + { + pk9.HandlingTrainerLanguage = 0; + pk9.HandlingTrainerGender = 0; + pk9.HandlingTrainerMemory = 0; + pk9.HandlingTrainerMemoryFeeling = 0; + pk9.HandlingTrainerMemoryIntensity = 0; + pk9.ObedienceLevel = 1; + pk9.Version = 0; + pk9.BattleVersion = 0; + pk9.TeraTypeOverride = (MoveType)19; } + var la = new LegalityAnalysis(pk); + var enc = la.EncounterMatch; + pk.CurrentFriendship = enc is IHatchCycle s ? s.EggCycles : pk.PersonalInfo.HatchCycles; + + Span relearn = stackalloc ushort[4]; + la.GetSuggestedRelearnMoves(relearn, enc); + pk.SetRelearnMoves(relearn); + + pk.SetSuggestedMoves(); + + pk.Move1_PPUps = pk.Move2_PPUps = pk.Move3_PPUps = pk.Move4_PPUps = 0; + pk.SetMaximumPPCurrent(pk.Moves); + pk.SetSuggestedHyperTrainingData(); + pk.SetSuggestedRibbons(template, enc, true); } + } diff --git a/SysBot.Pokemon/Helpers/FileTradeHelper.cs b/SysBot.Pokemon/Helpers/FileTradeHelper.cs index f8e71a0bb..4d6bb7aea 100644 --- a/SysBot.Pokemon/Helpers/FileTradeHelper.cs +++ b/SysBot.Pokemon/Helpers/FileTradeHelper.cs @@ -1,91 +1,89 @@ -using PKHeX.Core; +using PKHeX.Core; using System; using System.Collections.Generic; using System.Linq; -namespace SysBot.Pokemon.Helpers +namespace SysBot.Pokemon.Helpers; +/// +/// 宝可梦文件交易帮助类 +/// +/// +public class FileTradeHelper where T : PKM, new() { /// - /// 宝可梦文件交易帮助类 + /// 将bin文件转换成对应版本的PKM list /// - /// - public class FileTradeHelper where T : PKM, new() + /// + /// + public static List Bin2List(byte[] bb) { - /// - /// 将bin文件转换成对应版本的PKM list - /// - /// - /// - public static List Bin2List(byte[] bb) + if (pkmSize[typeof(T)] == bb.Length) { - if (pkmSize[typeof(T)] == bb.Length) - { - var tp = GetPKM(bb); - if (tp != null && tp.Species > 0 && tp.Valid && tp is T pkm) return new List() { pkm }; - } - int size = pkmSizeInBin[typeof(T)]; - int times = bb.Length % size == 0 ? (bb.Length / size) : (bb.Length / size + 1); - List pkmBytes = new(); - for (var i = 0; i < times; i++) - { - int start = i * size; - int end = (start + size) > bb.Length ? bb.Length : (start + size); - var tp = GetPKM(bb[start..end]); - if (tp != null && tp.Species > 0 && tp.Valid && tp is T pkm) pkmBytes.Add(pkm); - } - return pkmBytes; + var tp = GetPKM(bb); + if (tp != null && tp.Species > 0 && tp.Valid && tp is T pkm) return new List() { pkm }; } - /// - /// 文件名称是否有效 - /// - /// - /// - public static bool ValidFileName(string fileName) + int size = pkmSizeInBin[typeof(T)]; + int times = bb.Length % size == 0 ? (bb.Length / size) : (bb.Length / size + 1); + List pkmBytes = new(); + for (var i = 0; i < times; i++) { - string ext = fileName?.Split('.').Last().ToLower() ?? ""; - return (ext == typeof(T).Name.ToLower()) || (ext == "bin"); + int start = i * size; + int end = (start + size) > bb.Length ? bb.Length : (start + size); + var tp = GetPKM(bb[start..end]); + if (tp != null && tp.Species > 0 && tp.Valid && tp is T pkm) pkmBytes.Add(pkm); } - /// - /// 文件大小是否有效 - /// - /// - /// - public static bool ValidFileSize(long size) => ValidPKMFileSize(size) || ValidBinFileSize(size); - public static bool ValidPKMFileSize(long size) => size == pkmSize[typeof(T)]; - public static bool ValidBinFileSize(long size) => (size > 0) && (size <= MaxCountInBin * pkmSizeInBin[typeof(T)]) && (size % pkmSizeInBin[typeof(T)] == 0); - public static int MaxCountInBin => maxCountInBin[typeof(T)]; + return pkmBytes; + } + /// + /// 文件名称是否有效 + /// + /// + /// + public static bool ValidFileName(string fileName) + { + string ext = fileName?.Split('.').Last().ToLower() ?? ""; + return (ext == typeof(T).Name.ToLower()) || (ext == "bin"); + } + /// + /// 文件大小是否有效 + /// + /// + /// + public static bool ValidFileSize(long size) => ValidPKMFileSize(size) || ValidBinFileSize(size); + public static bool ValidPKMFileSize(long size) => size == pkmSize[typeof(T)]; + public static bool ValidBinFileSize(long size) => (size > 0) && (size <= MaxCountInBin * pkmSizeInBin[typeof(T)]) && (size % pkmSizeInBin[typeof(T)] == 0); + public static int MaxCountInBin => maxCountInBin[typeof(T)]; - static PKM? GetPKM(byte[] ba) => typeof(T) switch - { - Type t when t == typeof(PK8) => new PK8(ba), - Type t when t == typeof(PB8) => new PB8(ba), - Type t when t == typeof(PA8) => new PA8(ba), - Type t when t == typeof(PK9) => new PK9(ba), - _ => null - }; - static readonly Dictionary pkmSize = new() - { - { typeof(PK8), 344 }, - { typeof(PB8), 344 }, - { typeof(PA8), 376 }, - { typeof(PK9), 344 } - }; + static PKM? GetPKM(byte[] ba) => typeof(T) switch + { + Type t when t == typeof(PK8) => new PK8(ba), + Type t when t == typeof(PB8) => new PB8(ba), + Type t when t == typeof(PA8) => new PA8(ba), + Type t when t == typeof(PK9) => new PK9(ba), + _ => null + }; + static readonly Dictionary pkmSize = new() + { + { typeof(PK8), 344 }, + { typeof(PB8), 344 }, + { typeof(PA8), 376 }, + { typeof(PK9), 344 } + }; - static readonly Dictionary pkmSizeInBin = new() - { - { typeof(PK8), 344 }, - { typeof(PB8), 344 }, - { typeof(PA8), 360 }, - { typeof(PK9), 344 } - }; + static readonly Dictionary pkmSizeInBin = new() + { + { typeof(PK8), 344 }, + { typeof(PB8), 344 }, + { typeof(PA8), 360 }, + { typeof(PK9), 344 } + }; - static readonly Dictionary maxCountInBin = new() - { - { typeof(PK8), 960 }, - { typeof(PB8), 1200 }, - { typeof(PA8), 960 }, - { typeof(PK9), 960 } - }; + static readonly Dictionary maxCountInBin = new() + { + { typeof(PK8), 960 }, + { typeof(PB8), 1200 }, + { typeof(PA8), 960 }, + { typeof(PK9), 960 } + }; - } } diff --git a/SysBot.Pokemon/Helpers/ShowdownTranslator.cs b/SysBot.Pokemon/Helpers/ShowdownTranslator.cs index 2726fe073..c7c692da5 100644 --- a/SysBot.Pokemon/Helpers/ShowdownTranslator.cs +++ b/SysBot.Pokemon/Helpers/ShowdownTranslator.cs @@ -1,262 +1,262 @@ -using PKHeX.Core; +using PKHeX.Core; using System.Text.RegularExpressions; -using System.Collections.Generic; using System.Linq; using System.Text; -namespace SysBot.Pokemon +namespace SysBot.Pokemon; +public class ShowdownTranslator where T : PKM { - public class ShowdownTranslator where T : PKM + public static GameStrings GameStringsZh = GameInfo.GetStrings("zh-Hans"); + public static GameStrings GameStringsEn = GameInfo.GetStrings("en"); + public static string Chinese2Showdown(string zh) { - public static GameStrings GameStringsZh = GameInfo.GetStrings("zh-Hans"); - public static GameStrings GameStringsEn = GameInfo.GetStrings("en"); - public static string Chinese2Showdown(string zh) - { - string result = ""; + string result = ""; - // 添加宝可梦 - int specieNo = GameStringsZh.Species.Skip(1).Select((s, index) => new { Species = s, Index = index + 1 }) - .Where(s => zh.Contains(s.Species)).OrderByDescending(s => s.Species.Length).FirstOrDefault()?.Index ?? -1; + // 添加宝可梦 + int specieNo = GameStringsZh.Species.Skip(1).Select((s, index) => new { Species = s, Index = index + 1 }) + .Where(s => zh.Contains(s.Species)).OrderByDescending(s => s.Species.Length).FirstOrDefault()?.Index ?? -1; - if (specieNo <= 0) return result; - result = specieNo switch - { - (int)Species.NidoranF => "Nidoran-F", - (int)Species.NidoranM => "Nidoran-M", - _ => GameStringsEn.Species[specieNo], - }; + if (specieNo <= 0) return result; + result = specieNo switch + { + (int)Species.NidoranF => "Nidoran-F", + (int)Species.NidoranM => "Nidoran-M", + _ => GameStringsEn.Species[specieNo], + }; - zh = zh.Replace(GameStringsZh.Species[specieNo], ""); + zh = zh.Replace(GameStringsZh.Species[specieNo], ""); - // 特殊性别差异 - // 29-尼多兰F,32-尼多朗M,678-超能妙喵F,876-爱管侍F,902-幽尾玄鱼F, 916-飘香豚 - if (((Species)specieNo is Species.Meowstic or Species.Indeedee or Species.Basculegion or Species.Oinkologne) - && zh.Contains("母")) result += "-F"; + // 特殊性别差异 + // 29-尼多兰F,32-尼多朗M,678-超能妙喵F,876-爱管侍F,902-幽尾玄鱼F, 916-飘香豚 + if (((Species)specieNo is Species.Meowstic or Species.Indeedee or Species.Basculegion or Species.Oinkologne) + && zh.Contains('母')) + { + result += "-F"; + } - // 识别地区形态 - foreach (var s in ShowdownTranslatorDictionary.formDict) - { - var searchKey = s.Key.EndsWith("形态") ? s.Key : s.Key + "形态"; - if (!zh.Contains(searchKey)) continue; - result += $"-{s.Value}"; - zh = zh.Replace(searchKey, ""); - break; - } + // 识别地区形态 + foreach (var s in ShowdownTranslatorDictionary.formDict) + { + var searchKey = s.Key.EndsWith("形态") ? s.Key : s.Key + "形态"; + if (!zh.Contains(searchKey)) continue; + result += $"-{s.Value}"; + zh = zh.Replace(searchKey, ""); + break; + } - // 识别蛋 - if (zh.Contains("的蛋")) - { - result = $"Egg ({result})"; - zh = zh.Replace("的蛋", ""); - } + // 识别蛋 + if (zh.Contains("的蛋")) + { + result = $"Egg ({result})"; + zh = zh.Replace("的蛋", ""); + } - // 添加性别 - if (zh.Contains("公")) - { - result += " (M)"; - zh = zh.Replace("公", ""); - } - else if (zh.Contains("母")) - { - result += " (F)"; - zh = zh.Replace("母", ""); - } + // 添加性别 + if (zh.Contains('公')) + { + result += " (M)"; + zh = zh.Replace("公", ""); + } + else if (zh.Contains('母')) + { + result += " (F)"; + zh = zh.Replace("母", ""); + } - // 添加持有物 - foreach (var holdItemKeyword in ShowdownTranslatorDictionary.holdItemKeywords) + // 添加持有物 + foreach (var holdItemKeyword in ShowdownTranslatorDictionary.holdItemKeywords) + { + if (!zh.Contains(holdItemKeyword)) continue; + for (int i = 1; i < GameStringsZh.Item.Count; i++) { - if (!zh.Contains(holdItemKeyword)) continue; - for (int i = 1; i < GameStringsZh.Item.Count; i++) - { - if (GameStringsZh.Item[i].Length == 0) continue; - if (!zh.Contains(holdItemKeyword + GameStringsZh.Item[i])) continue; - result += $" @ {GameStringsEn.Item[i]}"; - zh = zh.Replace(holdItemKeyword + GameStringsZh.Item[i], ""); - break; - } + if (GameStringsZh.Item[i].Length == 0) continue; + if (!zh.Contains(holdItemKeyword + GameStringsZh.Item[i])) continue; + result += $" @ {GameStringsEn.Item[i]}"; + zh = zh.Replace(holdItemKeyword + GameStringsZh.Item[i], ""); + break; } + } - // 添加等级 - if (Regex.IsMatch(zh, "\\d{1,3}级")) - { - string level = Regex.Match(zh, "(\\d{1,3})级").Groups?[1]?.Value ?? "100"; - result += $"\nLevel: {level}"; - zh = Regex.Replace(zh, "\\d{1,3}级", ""); - } + // 添加等级 + if (Regex.IsMatch(zh, "\\d{1,3}级")) + { + string level = Regex.Match(zh, "(\\d{1,3})级").Groups?[1]?.Value ?? "100"; + result += $"\nLevel: {level}"; + zh = Regex.Replace(zh, "\\d{1,3}级", ""); + } - // 添加超极巨化 - if (typeof(T) == typeof(PK8) && zh.Contains("超极巨")) - { - result += "\nGigantamax: Yes"; - zh = zh.Replace("超极巨", ""); - } + // 添加超极巨化 + if (typeof(T) == typeof(PK8) && zh.Contains("超极巨")) + { + result += "\nGigantamax: Yes"; + zh = zh.Replace("超极巨", ""); + } - // 添加异色 - foreach (string key in ShowdownTranslatorDictionary.shinyTypes.Keys) + // 添加异色 + foreach (string key in ShowdownTranslatorDictionary.shinyTypes.Keys) + { + if (zh.Contains(key)) { - if (zh.Contains(key)) - { - result += ShowdownTranslatorDictionary.shinyTypes[key]; - zh = zh.Replace(key, ""); - break; - } + result += ShowdownTranslatorDictionary.shinyTypes[key]; + zh = zh.Replace(key, ""); + break; } + } - // 添加头目 - if (typeof(T) == typeof(PA8) && zh.Contains("头目")) - { - result += "\nAlpha: Yes"; - zh = zh.Replace("头目", ""); - } + // 添加头目 + if (typeof(T) == typeof(PA8) && zh.Contains("头目")) + { + result += "\nAlpha: Yes"; + zh = zh.Replace("头目", ""); + } - // 添加球种 - for (int i = 1; i < GameStringsZh.balllist.Length; i++) - { - if (GameStringsZh.balllist[i].Length == 0) continue; - if (!zh.Contains(GameStringsZh.balllist[i])) continue; - var ballStr = GameStringsEn.balllist[i]; - if (typeof(T) == typeof(PA8) && ballStr is "Poké Ball" or "Great Ball" or "Ultra Ball") ballStr = "LA" + ballStr; - result += $"\nBall: {ballStr}"; - zh = zh.Replace(GameStringsZh.balllist[i], ""); - break; - } + // 添加球种 + for (int i = 1; i < GameStringsZh.balllist.Length; i++) + { + if (GameStringsZh.balllist[i].Length == 0) continue; + if (!zh.Contains(GameStringsZh.balllist[i])) continue; + var ballStr = GameStringsEn.balllist[i]; + if (typeof(T) == typeof(PA8) && ballStr is "Poké Ball" or "Great Ball" or "Ultra Ball") ballStr = "LA" + ballStr; + result += $"\nBall: {ballStr}"; + zh = zh.Replace(GameStringsZh.balllist[i], ""); + break; + } - // 添加特性 - for (int i = 1; i < GameStringsZh.Ability.Count; i++) - { - if (GameStringsZh.Ability[i].Length == 0) continue; - if (!zh.Contains(GameStringsZh.Ability[i] + "特性")) continue; - result += $"\nAbility: {GameStringsEn.Ability[i]}"; - zh = zh.Replace(GameStringsZh.Ability[i] + "特性", ""); - break; - } + // 添加特性 + for (int i = 1; i < GameStringsZh.Ability.Count; i++) + { + if (GameStringsZh.Ability[i].Length == 0) continue; + if (!zh.Contains(GameStringsZh.Ability[i] + "特性")) continue; + result += $"\nAbility: {GameStringsEn.Ability[i]}"; + zh = zh.Replace(GameStringsZh.Ability[i] + "特性", ""); + break; + } - // 添加性格 - for (int i = 0; i < GameStringsZh.Natures.Count; i++) + // 添加性格 + for (int i = 0; i < GameStringsZh.Natures.Count; i++) + { + if (GameStringsZh.Natures[i].Length == 0) continue; + if (!zh.Contains(GameStringsZh.Natures[i])) continue; + result += $"\n{GameStringsEn.Natures[i]} Nature"; + zh = zh.Replace(GameStringsZh.Natures[i], ""); + break; + } + + // 添加个体值 + foreach (string key in ShowdownTranslatorDictionary.ivCombos.Keys) + { + if (zh.ToUpper().Contains(key)) { - if (GameStringsZh.Natures[i].Length == 0) continue; - if (!zh.Contains(GameStringsZh.Natures[i])) continue; - result += $"\n{GameStringsEn.Natures[i]} Nature"; - zh = zh.Replace(GameStringsZh.Natures[i], ""); + result += "\nIVs: " + ShowdownTranslatorDictionary.ivCombos[key]; + zh = Regex.Replace(zh, key, "", RegexOptions.IgnoreCase); break; } + } - // 添加个体值 - foreach (string key in ShowdownTranslatorDictionary.ivCombos.Keys) - { - if (zh.ToUpper().Contains(key)) - { - result += "\nIVs: " + ShowdownTranslatorDictionary.ivCombos[key]; - zh = Regex.Replace(zh, key, "", RegexOptions.IgnoreCase); - break; - } - } + // 添加努力值 + if (zh.Contains("努力值")) + { + StringBuilder sb = new(); + sb.Append("\nEVs: "); + zh = zh.Replace("努力值", ""); - // 添加努力值 - if (zh.Contains("努力值")) + foreach (var stat in ShowdownTranslatorDictionary.statsDict) { - StringBuilder sb = new(); - sb.Append("\nEVs: "); - zh = zh.Replace("努力值", ""); - - foreach (var stat in ShowdownTranslatorDictionary.statsDict) + string regexPattern = $@"\d{{1,3}}{stat.Key}"; + if (Regex.IsMatch(zh, regexPattern)) { - string regexPattern = $@"\d{{1,3}}{stat.Key}"; - if (Regex.IsMatch(zh, regexPattern)) - { - string value = Regex.Match(zh, $@"(\d{{1,3}}){stat.Key}").Groups[1].Value; - sb.Append($"{value} {stat.Value} / "); - zh = Regex.Replace(zh, regexPattern, ""); - } - else if (Regex.IsMatch(zh, $@"\d{{1,3}}{stat.Value}")) - { - string value = Regex.Match(zh, $@"(\d{{1,3}}){stat.Value}").Groups?[1]?.Value ?? ""; - sb.Append($"{value} {stat.Value} / "); - zh = Regex.Replace(zh, $@"\d{{1,3}}{stat.Value}", ""); - } + string value = Regex.Match(zh, $@"(\d{{1,3}}){stat.Key}").Groups[1].Value; + sb.Append($"{value} {stat.Value} / "); + zh = Regex.Replace(zh, regexPattern, ""); } - - if (sb.ToString().EndsWith("/ ")) + else if (Regex.IsMatch(zh, $@"\d{{1,3}}{stat.Value}")) { - sb.Remove(sb.Length - 2, 2); + string value = Regex.Match(zh, $@"(\d{{1,3}}){stat.Value}").Groups?[1]?.Value ?? ""; + sb.Append($"{value} {stat.Value} / "); + zh = Regex.Replace(zh, $@"\d{{1,3}}{stat.Value}", ""); } - - result += sb.ToString(); } - // 添加太晶属性 - if (typeof(T) == typeof(PK9)) + if (sb.ToString().EndsWith("/ ")) { - for (int i = 0; i < GameStringsZh.Types.Count; i++) - { - if (GameStringsZh.Types[i].Length == 0) continue; - if (!zh.Contains("太晶" + GameStringsZh.Types[i])) continue; - result += $"\nTera Type: {GameStringsEn.Types[i]}"; - zh = zh.Replace("太晶" + GameStringsZh.Types[i], ""); - break; - } + sb.Remove(sb.Length - 2, 2); } - // 补充后天获得的全奖章 注意开启Legality=>AllowBatchCommands - if (typeof(T) == typeof(PK9) && zh.Contains("全奖章")) - { - result += "\n.Ribbons=$suggestAll\n.RibbonMarkPartner=True\n.RibbonMarkGourmand=True"; - zh = zh.Replace("全奖章", ""); - } - // 体型大小并添加证章 - if (typeof(T) == typeof(PK9) && zh.Contains("大个子")) - { - result += $"\n.Scale=255\n.RibbonMarkJumbo=True"; - zh = zh.Replace("大个子", ""); - } - else if (typeof(T) == typeof(PK9) && zh.Contains("小不点")) - { - result += $"\n.Scale=0\n.RibbonMarkMini=True"; - zh = zh.Replace("小不点", ""); - } + result += sb.ToString(); + } - //添加全回忆技能 - if (typeof(T) == typeof(PK9) || typeof(T) == typeof(PK8)) - { - if (Regex.IsMatch(zh, "全技能|全招式")) - { - result += "\n.RelearnMoves=$suggestAll"; - zh = Regex.Replace(zh, "全技能|全招式", ""); - } - } - else if (typeof(T) == typeof(PA8)) + // 添加太晶属性 + if (typeof(T) == typeof(PK9)) + { + for (int i = 0; i < GameStringsZh.Types.Count; i++) { - if (Regex.IsMatch(zh, "全技能|全招式")) - { - result += "\n.MoveMastery=$suggestAll"; - zh = Regex.Replace(zh, "全技能|全招式", ""); - } + if (GameStringsZh.Types[i].Length == 0) continue; + if (!zh.Contains("太晶" + GameStringsZh.Types[i])) continue; + result += $"\nTera Type: {GameStringsEn.Types[i]}"; + zh = zh.Replace("太晶" + GameStringsZh.Types[i], ""); + break; } + } - // 语言 - var lang = ShowdownTranslatorDictionary.languages.Keys.FirstOrDefault(zh.Contains); - if (!string.IsNullOrEmpty(lang)) + // 补充后天获得的全奖章 注意开启Legality=>AllowBatchCommands + if (typeof(T) == typeof(PK9) && zh.Contains("全奖章")) + { + result += "\n.Ribbons=$suggestAll\n.RibbonMarkPartner=True\n.RibbonMarkGourmand=True"; + zh = zh.Replace("全奖章", ""); + } + // 体型大小并添加证章 + if (typeof(T) == typeof(PK9) && zh.Contains("大个子")) + { + result += $"\n.Scale=255\n.RibbonMarkJumbo=True"; + zh = zh.Replace("大个子", ""); + } + else if (typeof(T) == typeof(PK9) && zh.Contains("小不点")) + { + result += $"\n.Scale=0\n.RibbonMarkMini=True"; + zh = zh.Replace("小不点", ""); + } + + //添加全回忆技能 + if (typeof(T) == typeof(PK9) || typeof(T) == typeof(PK8)) + { + if (Regex.IsMatch(zh, "全技能|全招式")) { - result += $"\nLanguage: {ShowdownTranslatorDictionary.languages[lang]}"; - zh = zh.Replace(lang, ""); + result += "\n.RelearnMoves=$suggestAll"; + zh = Regex.Replace(zh, "全技能|全招式", ""); } - - // 添加技能 原因:PKHeX.Core.ShowdownSet#ParseLines中,若招式数满足4个则不再解析,所以招式文本应放在最后 - for (int moveCount = 0; moveCount < 4; moveCount++) + } + else if (typeof(T) == typeof(PA8)) + { + if (Regex.IsMatch(zh, "全技能|全招式")) { - var candidateIndex = GameStringsZh.Move.Select((move, index) => new { Move = move, Index = index }) - .Where(move => move.Move.Length > 0 && zh.Contains("-" + move.Move)) - .OrderByDescending(move => move.Move.Length).FirstOrDefault()?.Index ?? -1; - if (candidateIndex < 0) continue; - result += $"\n-{GameStringsEn.Move[candidateIndex]}"; - zh = zh.Replace("-" + GameStringsZh.Move[candidateIndex], ""); + result += "\n.MoveMastery=$suggestAll"; + zh = Regex.Replace(zh, "全技能|全招式", ""); } + } - return result; + // 语言 + var lang = ShowdownTranslatorDictionary.languages.Keys.FirstOrDefault(zh.Contains); + if (!string.IsNullOrEmpty(lang)) + { + result += $"\nLanguage: {ShowdownTranslatorDictionary.languages[lang]}"; + zh = zh.Replace(lang, ""); } - public static bool IsPS(string str) => GameStringsEn.Species.Skip(1).Any(str.Contains); + // 添加技能 原因:PKHeX.Core.ShowdownSet#ParseLines中,若招式数满足4个则不再解析,所以招式文本应放在最后 + for (int moveCount = 0; moveCount < 4; moveCount++) + { + var candidateIndex = GameStringsZh.Move.Select((move, index) => new { Move = move, Index = index }) + .Where(move => move.Move.Length > 0 && zh.Contains("-" + move.Move)) + .OrderByDescending(move => move.Move.Length).FirstOrDefault()?.Index ?? -1; + if (candidateIndex < 0) continue; + result += $"\n-{GameStringsEn.Move[candidateIndex]}"; + zh = zh.Replace("-" + GameStringsZh.Move[candidateIndex], ""); + } + return result; } + + public static bool IsPS(string str) => GameStringsEn.Species.Skip(1).Any(str.Contains); + } diff --git a/SysBot.Pokemon/Helpers/ShowdownTranslatorDictionary.cs b/SysBot.Pokemon/Helpers/ShowdownTranslatorDictionary.cs index 5e39817e6..8d9da25d8 100644 --- a/SysBot.Pokemon/Helpers/ShowdownTranslatorDictionary.cs +++ b/SysBot.Pokemon/Helpers/ShowdownTranslatorDictionary.cs @@ -1,299 +1,297 @@ -using System.Collections.Generic; +using System.Collections.Generic; -namespace SysBot.Pokemon +namespace SysBot.Pokemon; +public class ShowdownTranslatorDictionary { - public class ShowdownTranslatorDictionary - { - // 添加持有物 - public static List holdItemKeywords = new List { "持有", "携带" }; + // 添加持有物 + public static List holdItemKeywords = new List { "持有", "携带" }; - // 添加异色 - public static Dictionary shinyTypes = new Dictionary - { - {"异色", "\nShiny: Yes"}, - {"闪光", "\nShiny: Yes"}, - {"星闪", "\nShiny: Star"}, - {"方闪", "\nShiny: Square"} - }; + // 添加异色 + public static Dictionary shinyTypes = new Dictionary + { + {"异色", "\nShiny: Yes"}, + {"闪光", "\nShiny: Yes"}, + {"星闪", "\nShiny: Star"}, + {"方闪", "\nShiny: Square"} + }; - // 添加个体值 - public static Dictionary ivCombos = new() - { - {"6V", "31 HP / 31 Atk / 31 Def / 31 SpA / 31 SpD / 31 Spe"}, - {"5V0A", "31 HP / 0 Atk / 31 Def / 31 SpA / 31 SpD / 31 Spe"}, - {"5V0攻", "31 HP / 0 Atk / 31 Def / 31 SpA / 31 SpD / 31 Spe"}, - {"5V0S", "31 HP / 31 Atk / 31 Def / 31 SpA / 31 SpD / 0 Spe"}, - {"5V0速", "31 HP / 31 Atk / 31 Def / 31 SpA / 31 SpD / 0 Spe"}, - {"4V0A0S", "31 HP / 0 Atk / 31 Def / 31 SpA / 31 SpD / 0 Spe"}, - {"4V0攻0速", "31 HP / 0 Atk / 31 Def / 31 SpA / 31 SpD / 0 Spe"} - }; + // 添加个体值 + public static Dictionary ivCombos = new() + { + {"6V", "31 HP / 31 Atk / 31 Def / 31 SpA / 31 SpD / 31 Spe"}, + {"5V0A", "31 HP / 0 Atk / 31 Def / 31 SpA / 31 SpD / 31 Spe"}, + {"5V0攻", "31 HP / 0 Atk / 31 Def / 31 SpA / 31 SpD / 31 Spe"}, + {"5V0S", "31 HP / 31 Atk / 31 Def / 31 SpA / 31 SpD / 0 Spe"}, + {"5V0速", "31 HP / 31 Atk / 31 Def / 31 SpA / 31 SpD / 0 Spe"}, + {"4V0A0S", "31 HP / 0 Atk / 31 Def / 31 SpA / 31 SpD / 0 Spe"}, + {"4V0攻0速", "31 HP / 0 Atk / 31 Def / 31 SpA / 31 SpD / 0 Spe"} + }; - public static Dictionary statsDict = new() - { - { "生命", "HP" }, - { "攻击", "Atk" }, - { "防御", "Def" }, - { "特攻", "SpA" }, - { "特防", "SpD" }, - { "速度", "Spe" } - }; + public static Dictionary statsDict = new() + { + { "生命", "HP" }, + { "攻击", "Atk" }, + { "防御", "Def" }, + { "特攻", "SpA" }, + { "特防", "SpD" }, + { "速度", "Spe" } + }; - public static Dictionary ribbonMarks = new() - { - { "最强之证", "\n.RibbonMarkMightiest=True" }, - { "未知之证", "\n.RibbonMarkRare=True" }, - { "命运之证", "\n.RibbonMarkDestiny=True" }, - { "暴雪之证", "\n.RibbonMarkBlizzard=True" }, - { "阴云之证", "\n.RibbonMarkCloudy=True" }, - { "正午之证", "\n.RibbonMarkLunchtime=True" }, - { "浓雾之证", "\n.RibbonMarkMisty=True" }, - { "降雨之证", "\n.RibbonMarkRainy=True" }, - { "沙尘之证", "\n.RibbonMarkSandstorm=True" }, - { "午夜之证", "\n.RibbonMarkSleepyTime=True" }, - { "降雪之证", "\n.RibbonMarkSnowy=True" }, - { "落雷之证", "\n.RibbonMarkStormy=True" }, - { "干燥之证", "\n.RibbonMarkDry=True" }, - { "黄昏之证", "\n.RibbonMarkDusk=True" }, - { "拂晓之证", "\n.RibbonMarkDawn=True" }, - { "上钩之证", "\n.RibbonMarkFishing=True" }, - { "咖喱之证", "\n.RibbonMarkCurry=True" }, - { "无虑之证", "\n.RibbonMarkAbsentMinded=True" }, - { "愤怒之证", "\n.RibbonMarkAngry=True" }, - { "冷静之证", "\n.RibbonMarkCalmness=True" }, - { "领袖之证", "\n.RibbonMarkCharismatic=True" }, - { "狡猾之证", "\n.RibbonMarkCrafty=True" }, - { "期待之证", "\n.RibbonMarkExcited=True" }, - { "本能之证", "\n.RibbonMarkFerocious=True" }, - { "动摇之证", "\n.RibbonMarkFlustered=True" }, - { "木讷之证", "\n.RibbonMarkHumble=True" }, - { "理性之证", "\n.RibbonMarkIntellectual=True" }, - { "热情之证", "\n.RibbonMarkIntense=True" }, - { "捡拾之证", "\n.RibbonMarkItemfinder=True" }, - { "紧张之证", "\n.RibbonMarkJittery=True" }, - { "幸福之证", "\n.RibbonMarkJoyful=True" }, - { "优雅之证", "\n.RibbonMarkKindly=True" }, - { "激动之证", "\n.RibbonMarkPeeved=True" }, - { "自信之证", "\n.RibbonMarkPrideful=True" }, - { "昂扬之证", "\n.RibbonMarkPumpedUp=True" }, - { "淘气之证", "\n.RibbonMarkRowdy=True" }, - { "凶悍之证", "\n.RibbonMarkScowling=True" }, - { "不振之证", "\n.RibbonMarkSlump=True" }, - { "微笑之证", "\n.RibbonMarkSmiley=True" }, - { "悲伤之证", "\n.RibbonMarkTeary=True" }, - { "不纯之证", "\n.RibbonMarkThorny=True" }, - { "偶遇之证", "\n.RibbonMarkUncommon=True" }, - { "自卑之证", "\n.RibbonMarkUnsure=True" }, - { "爽快之证", "\n.RibbonMarkUpbeat=True" }, - { "活力之证", "\n.RibbonMarkVigor=True" }, - { "倦怠之证", "\n.RibbonMarkZeroEnergy=True" }, - { "疏忽之证", "\n.RibbonMarkZonedOut=True" }, - { "宝主之证", "\n.RibbonMarkTitan=True" } - }; + public static Dictionary ribbonMarks = new() + { + { "最强之证", "\n.RibbonMarkMightiest=True" }, + { "未知之证", "\n.RibbonMarkRare=True" }, + { "命运之证", "\n.RibbonMarkDestiny=True" }, + { "暴雪之证", "\n.RibbonMarkBlizzard=True" }, + { "阴云之证", "\n.RibbonMarkCloudy=True" }, + { "正午之证", "\n.RibbonMarkLunchtime=True" }, + { "浓雾之证", "\n.RibbonMarkMisty=True" }, + { "降雨之证", "\n.RibbonMarkRainy=True" }, + { "沙尘之证", "\n.RibbonMarkSandstorm=True" }, + { "午夜之证", "\n.RibbonMarkSleepyTime=True" }, + { "降雪之证", "\n.RibbonMarkSnowy=True" }, + { "落雷之证", "\n.RibbonMarkStormy=True" }, + { "干燥之证", "\n.RibbonMarkDry=True" }, + { "黄昏之证", "\n.RibbonMarkDusk=True" }, + { "拂晓之证", "\n.RibbonMarkDawn=True" }, + { "上钩之证", "\n.RibbonMarkFishing=True" }, + { "咖喱之证", "\n.RibbonMarkCurry=True" }, + { "无虑之证", "\n.RibbonMarkAbsentMinded=True" }, + { "愤怒之证", "\n.RibbonMarkAngry=True" }, + { "冷静之证", "\n.RibbonMarkCalmness=True" }, + { "领袖之证", "\n.RibbonMarkCharismatic=True" }, + { "狡猾之证", "\n.RibbonMarkCrafty=True" }, + { "期待之证", "\n.RibbonMarkExcited=True" }, + { "本能之证", "\n.RibbonMarkFerocious=True" }, + { "动摇之证", "\n.RibbonMarkFlustered=True" }, + { "木讷之证", "\n.RibbonMarkHumble=True" }, + { "理性之证", "\n.RibbonMarkIntellectual=True" }, + { "热情之证", "\n.RibbonMarkIntense=True" }, + { "捡拾之证", "\n.RibbonMarkItemfinder=True" }, + { "紧张之证", "\n.RibbonMarkJittery=True" }, + { "幸福之证", "\n.RibbonMarkJoyful=True" }, + { "优雅之证", "\n.RibbonMarkKindly=True" }, + { "激动之证", "\n.RibbonMarkPeeved=True" }, + { "自信之证", "\n.RibbonMarkPrideful=True" }, + { "昂扬之证", "\n.RibbonMarkPumpedUp=True" }, + { "淘气之证", "\n.RibbonMarkRowdy=True" }, + { "凶悍之证", "\n.RibbonMarkScowling=True" }, + { "不振之证", "\n.RibbonMarkSlump=True" }, + { "微笑之证", "\n.RibbonMarkSmiley=True" }, + { "悲伤之证", "\n.RibbonMarkTeary=True" }, + { "不纯之证", "\n.RibbonMarkThorny=True" }, + { "偶遇之证", "\n.RibbonMarkUncommon=True" }, + { "自卑之证", "\n.RibbonMarkUnsure=True" }, + { "爽快之证", "\n.RibbonMarkUpbeat=True" }, + { "活力之证", "\n.RibbonMarkVigor=True" }, + { "倦怠之证", "\n.RibbonMarkZeroEnergy=True" }, + { "疏忽之证", "\n.RibbonMarkZonedOut=True" }, + { "宝主之证", "\n.RibbonMarkTitan=True" } + }; - public static Dictionary languages = new Dictionary - { - { "异国", "Italian" }, - { "日语", "Japanese" }, - { "英语", "English" }, - { "法语", "French" }, - { "意大利语", "Italian" }, - { "德语", "German" }, - { "西班牙语", "Spanish" }, - { "韩语", "Korean" }, - { "简体中文", "ChineseS" }, - { "繁体中文", "ChineseT" } - }; + public static Dictionary languages = new Dictionary + { + { "异国", "Italian" }, + { "日语", "Japanese" }, + { "英语", "English" }, + { "法语", "French" }, + { "意大利语", "Italian" }, + { "德语", "German" }, + { "西班牙语", "Spanish" }, + { "韩语", "Korean" }, + { "简体中文", "ChineseS" }, + { "繁体中文", "ChineseT" } + }; - #region 形态中文ps字典,感谢ppllouf - public static Dictionary formDict = new Dictionary { - {"阿罗拉","Alola"}, - {"初始","Original"}, - {"丰缘","Hoenn"}, - {"神奥","Sinnoh"}, - {"合众","Unova"}, - {"卡洛斯","Kalos"}, - {"就决定是你了","Partner"}, - {"搭档","Starter"}, - {"世界","World"}, - {"摇滚巨星","Rock-Star"}, - {"贵妇","Belle"}, - {"流行偶像","Pop-Star"}, - {"博士","PhD"}, - {"面罩摔角手","Libre"}, - {"换装","Cosplay"}, - {"伽勒尔","Galar"}, - {"洗翠","Hisui"}, - {"帕底亚的样子斗战种","Paldea-Combat"}, - {"帕底亚的样子火炽种","Paldea-Blaze"}, - {"帕底亚的样子水澜种","Paldea-Aqua"}, - {"刺刺耳","Spiky-eared"}, - {"帕底亚","Paldea"}, - {"B","B"}, - {"C","C"}, - {"D","D"}, - {"E","E"}, - {"F","F"}, - {"G","G"}, - {"H","H"}, - {"I","I"}, - {"J","J"}, - {"K","K"}, - {"L","L"}, - {"M","M"}, - {"N","N"}, - {"O","O"}, - {"P","P"}, - {"Q","Q"}, - {"R","R"}, - {"S","S"}, - {"T","T"}, - {"U","U"}, - {"V","V"}, - {"W","W"}, - {"X","X"}, - {"Y","Y"}, - {"Z","Z"}, - {"!","Exclamation"}, - {"?","Question"}, - {"太阳的样子","Sunny"}, - {"雨水的样子","Rainy"}, - {"雪云的样子","Snowy"}, - {"原始回归的样子","Primal"}, - {"攻击形态","Attack"}, - {"防御形态","Defense"}, - {"速度形态","Speed"}, - {"砂土蓑衣","Sandy"}, - {"垃圾蓑衣","Trash"}, - {"晴天形态","Sunshine"}, - {"东海","East"}, - {"加热","Heat"}, - {"清洗","Wash"}, - {"结冰","Frost"}, - {"旋转","Fan"}, - {"切割","Mow"}, - {"起源","Origin"}, - {"天空","Sky"}, - {"格斗","Fighting"}, - {"飞行","Flying"}, - {"毒","Poison"}, - {"地面","Ground"}, - {"岩石","Rock"}, - {"虫","Bug"}, - {"幽灵","Ghost"}, - {"钢","Steel"}, - {"火","Fire"}, - {"水","Water"}, - {"草","Grass"}, - {"电","Electric"}, - {"超能力","Psychic"}, - {"冰","Ice"}, - {"龙","Dragon"}, - {"恶","Dark"}, - {"妖精","Fairy"}, - {"蓝条纹的样子","Blue"}, - {"白条纹的样子","White"}, - {"夏天的样子","Summer"}, - {"秋天的样子","Autumn"}, - {"冬天的样子","Winter"}, - {"灵兽形态","Therian"}, - {"暗黑","White"}, - {"焰白","Black"}, - {"觉悟的样子","Resolute"}, - {"舞步形态","Pirouette"}, - {"水流卡带","Douse"}, - {"闪电卡带","Shock"}, - {"火焰卡带","Burn"}, - {"冰冻卡带","Chill"}, - {"小智版","Ash"}, - {"冰雪花纹","Icy Snow"}, - {"雪国花纹","Polar"}, - {"雪原花纹","Tundra"}, - {"大陆花纹","Continental"}, - {"庭院花纹","Garden"}, - {"高雅花纹","Elegant"}, - {"花园花纹","Meadow"}, - {"摩登花纹","Modern"}, - {"大海花纹","Marine"}, - {"群岛花纹","Archipelago"}, - {"荒野花纹","High Plains"}, - {"沙尘花纹","Sandstorm"}, - {"大河花纹","River"}, - {"骤雨花纹","Monsoon"}, - {"热带草原花纹","Savanna"}, - {"太阳花纹","Sun"}, - {"大洋花纹","Ocean"}, - {"热带雨林花纹","Jungle"}, - {"幻彩花纹","Fancy"}, - {"球球花纹","Pokeball"}, - {"蓝花","Blue"}, - {"橙花","Orange"}, - {"白花","White"}, - {"黄花","Yellow"}, - {"永恒","Eternal"}, - {"心形造型","Heart"}, - {"星形造型","Star"}, - {"菱形造型","Diamond"}, - {"淑女造型","Debutante"}, - {"贵妇造型","Matron"}, - {"绅士造型","Dandy"}, - {"女王造型","La Reine"}, - {"歌舞伎造型","Kabuki"}, - {"国王造型","Pharaoh"}, - {"小尺寸","Small"}, - {"大尺寸","Large"}, - {"特大尺寸","Super"}, - {"解放","Unbound"}, - {"啪滋啪滋风格","Pom-Pom"}, - {"呼拉呼拉风格","Pa’u"}, - {"轻盈轻盈风格","Sensu"}, - {"黑夜的样子","Midnight"}, - {"黄昏的样子","Dusk"}, - {"流星的样子","Meteor"}, - {"橙色核心","Orange"}, - {"黄色核心","Yellow"}, - {"绿色核心","Green"}, - {"浅蓝色核心","Blue"}, - {"蓝色核心","Indigo"}, - {"紫色核心","Violet"}, - {"黄昏之鬃","Dusk-Mane"}, - {"拂晓之翼","Dawn-Wings"}, - {"究极","Ultra"}, - {"500年前的颜色","Original"}, - {"低调的样子","Low-Key"}, - {"真品","Antique"}, - {"奶香红钻","Ruby-Cream"}, - {"奶香抹茶","Matcha-Cream"}, - {"奶香薄荷","Mint-Cream"}, - {"奶香柠檬","Lemon-Cream"}, - {"奶香雪盐","Salted-Cream"}, - {"红钻综合","Ruby-Swirl"}, - {"焦糖综合","Caramel-Swirl"}, - {"三色综合","Rainbow-Swirl"}, - {"剑之王","Crowned"}, - {"盾之王","Crowned"}, - {"无极巨化","Eternamax"}, - {"连击流","Rapid-Strike"}, - {"阿爸","Dada"}, - {"骑白马的样子","Ice"}, - {"骑黑马的样子","Shadow"}, - {"四只家庭","Four"}, - {"蓝羽毛","Blue"}, - {"黄羽毛","Yellow"}, - {"白羽毛","White"}, - {"下垂姿势","Droopy"}, - {"平挺姿势","Stretchy"}, - {"三节形态","Three-Segment"}, - {"徒步","Roaming"}, - {"赫月","Bloodmoon" }, - {"高档货的样子","Artisan"}, - {"杰作的样子","Masterpiece"}, - {"水井面具","Wellspring"}, - {"火灶面具","Hearthflame"}, - {"础石面具","Cornerstone"}, - }; - #endregion + #region 形态中文ps字典,感谢ppllouf + public static Dictionary formDict = new Dictionary { + {"阿罗拉","Alola"}, + {"初始","Original"}, + {"丰缘","Hoenn"}, + {"神奥","Sinnoh"}, + {"合众","Unova"}, + {"卡洛斯","Kalos"}, + {"就决定是你了","Partner"}, + {"搭档","Starter"}, + {"世界","World"}, + {"摇滚巨星","Rock-Star"}, + {"贵妇","Belle"}, + {"流行偶像","Pop-Star"}, + {"博士","PhD"}, + {"面罩摔角手","Libre"}, + {"换装","Cosplay"}, + {"伽勒尔","Galar"}, + {"洗翠","Hisui"}, + {"帕底亚的样子斗战种","Paldea-Combat"}, + {"帕底亚的样子火炽种","Paldea-Blaze"}, + {"帕底亚的样子水澜种","Paldea-Aqua"}, + {"刺刺耳","Spiky-eared"}, + {"帕底亚","Paldea"}, + {"B","B"}, + {"C","C"}, + {"D","D"}, + {"E","E"}, + {"F","F"}, + {"G","G"}, + {"H","H"}, + {"I","I"}, + {"J","J"}, + {"K","K"}, + {"L","L"}, + {"M","M"}, + {"N","N"}, + {"O","O"}, + {"P","P"}, + {"Q","Q"}, + {"R","R"}, + {"S","S"}, + {"T","T"}, + {"U","U"}, + {"V","V"}, + {"W","W"}, + {"X","X"}, + {"Y","Y"}, + {"Z","Z"}, + {"!","Exclamation"}, + {"?","Question"}, + {"太阳的样子","Sunny"}, + {"雨水的样子","Rainy"}, + {"雪云的样子","Snowy"}, + {"原始回归的样子","Primal"}, + {"攻击形态","Attack"}, + {"防御形态","Defense"}, + {"速度形态","Speed"}, + {"砂土蓑衣","Sandy"}, + {"垃圾蓑衣","Trash"}, + {"晴天形态","Sunshine"}, + {"东海","East"}, + {"加热","Heat"}, + {"清洗","Wash"}, + {"结冰","Frost"}, + {"旋转","Fan"}, + {"切割","Mow"}, + {"起源","Origin"}, + {"天空","Sky"}, + {"格斗","Fighting"}, + {"飞行","Flying"}, + {"毒","Poison"}, + {"地面","Ground"}, + {"岩石","Rock"}, + {"虫","Bug"}, + {"幽灵","Ghost"}, + {"钢","Steel"}, + {"火","Fire"}, + {"水","Water"}, + {"草","Grass"}, + {"电","Electric"}, + {"超能力","Psychic"}, + {"冰","Ice"}, + {"龙","Dragon"}, + {"恶","Dark"}, + {"妖精","Fairy"}, + {"蓝条纹的样子","Blue"}, + {"白条纹的样子","White"}, + {"夏天的样子","Summer"}, + {"秋天的样子","Autumn"}, + {"冬天的样子","Winter"}, + {"灵兽形态","Therian"}, + {"暗黑","White"}, + {"焰白","Black"}, + {"觉悟的样子","Resolute"}, + {"舞步形态","Pirouette"}, + {"水流卡带","Douse"}, + {"闪电卡带","Shock"}, + {"火焰卡带","Burn"}, + {"冰冻卡带","Chill"}, + {"小智版","Ash"}, + {"冰雪花纹","Icy Snow"}, + {"雪国花纹","Polar"}, + {"雪原花纹","Tundra"}, + {"大陆花纹","Continental"}, + {"庭院花纹","Garden"}, + {"高雅花纹","Elegant"}, + {"花园花纹","Meadow"}, + {"摩登花纹","Modern"}, + {"大海花纹","Marine"}, + {"群岛花纹","Archipelago"}, + {"荒野花纹","High Plains"}, + {"沙尘花纹","Sandstorm"}, + {"大河花纹","River"}, + {"骤雨花纹","Monsoon"}, + {"热带草原花纹","Savanna"}, + {"太阳花纹","Sun"}, + {"大洋花纹","Ocean"}, + {"热带雨林花纹","Jungle"}, + {"幻彩花纹","Fancy"}, + {"球球花纹","Pokeball"}, + {"蓝花","Blue"}, + {"橙花","Orange"}, + {"白花","White"}, + {"黄花","Yellow"}, + {"永恒","Eternal"}, + {"心形造型","Heart"}, + {"星形造型","Star"}, + {"菱形造型","Diamond"}, + {"淑女造型","Debutante"}, + {"贵妇造型","Matron"}, + {"绅士造型","Dandy"}, + {"女王造型","La Reine"}, + {"歌舞伎造型","Kabuki"}, + {"国王造型","Pharaoh"}, + {"小尺寸","Small"}, + {"大尺寸","Large"}, + {"特大尺寸","Super"}, + {"解放","Unbound"}, + {"啪滋啪滋风格","Pom-Pom"}, + {"呼拉呼拉风格","Pa’u"}, + {"轻盈轻盈风格","Sensu"}, + {"黑夜的样子","Midnight"}, + {"黄昏的样子","Dusk"}, + {"流星的样子","Meteor"}, + {"橙色核心","Orange"}, + {"黄色核心","Yellow"}, + {"绿色核心","Green"}, + {"浅蓝色核心","Blue"}, + {"蓝色核心","Indigo"}, + {"紫色核心","Violet"}, + {"黄昏之鬃","Dusk-Mane"}, + {"拂晓之翼","Dawn-Wings"}, + {"究极","Ultra"}, + {"500年前的颜色","Original"}, + {"低调的样子","Low-Key"}, + {"真品","Antique"}, + {"奶香红钻","Ruby-Cream"}, + {"奶香抹茶","Matcha-Cream"}, + {"奶香薄荷","Mint-Cream"}, + {"奶香柠檬","Lemon-Cream"}, + {"奶香雪盐","Salted-Cream"}, + {"红钻综合","Ruby-Swirl"}, + {"焦糖综合","Caramel-Swirl"}, + {"三色综合","Rainbow-Swirl"}, + {"剑之王","Crowned"}, + {"盾之王","Crowned"}, + {"无极巨化","Eternamax"}, + {"连击流","Rapid-Strike"}, + {"阿爸","Dada"}, + {"骑白马的样子","Ice"}, + {"骑黑马的样子","Shadow"}, + {"四只家庭","Four"}, + {"蓝羽毛","Blue"}, + {"黄羽毛","Yellow"}, + {"白羽毛","White"}, + {"下垂姿势","Droopy"}, + {"平挺姿势","Stretchy"}, + {"三节形态","Three-Segment"}, + {"徒步","Roaming"}, + {"赫月","Bloodmoon" }, + {"高档货的样子","Artisan"}, + {"杰作的样子","Masterpiece"}, + {"水井面具","Wellspring"}, + {"火灶面具","Hearthflame"}, + {"础石面具","Cornerstone"}, + }; + #endregion - } } diff --git a/SysBot.Tests/FileTradeHelperTests.cs b/SysBot.Tests/FileTradeHelperTests.cs index 9b9eb4d74..7cf2daafe 100644 --- a/SysBot.Tests/FileTradeHelperTests.cs +++ b/SysBot.Tests/FileTradeHelperTests.cs @@ -1,45 +1,43 @@ -using FluentAssertions; +using FluentAssertions; using PKHeX.Core; using SysBot.Pokemon.Helpers; using Xunit; -namespace SysBot.Tests -{ - public class FileTradeHelperTests - { +namespace SysBot.Tests; - [Fact] - public void TestValidFileSize() - { - FileTradeHelper.ValidFileSize(344).Should().Be(true); - FileTradeHelper.ValidFileSize(0).Should().Be(false); - FileTradeHelper.ValidFileSize(345).Should().Be(false); - FileTradeHelper.ValidFileSize(344 * 10).Should().Be(true); - FileTradeHelper.ValidFileSize(344 * 960).Should().Be(true); - FileTradeHelper.ValidFileSize(344 * 961).Should().Be(false); +public class FileTradeHelperTests +{ - FileTradeHelper.ValidFileSize(376).Should().Be(true); - FileTradeHelper.ValidFileSize(0).Should().Be(false); - FileTradeHelper.ValidFileSize(375).Should().Be(false); - FileTradeHelper.ValidFileSize(360 * 10).Should().Be(true); - FileTradeHelper.ValidFileSize(360 * 960).Should().Be(true); - FileTradeHelper.ValidFileSize(360 * 961).Should().Be(false); + [Fact] + public void TestValidFileSize() + { + FileTradeHelper.ValidFileSize(344).Should().Be(true); + FileTradeHelper.ValidFileSize(0).Should().Be(false); + FileTradeHelper.ValidFileSize(345).Should().Be(false); + FileTradeHelper.ValidFileSize(344 * 10).Should().Be(true); + FileTradeHelper.ValidFileSize(344 * 960).Should().Be(true); + FileTradeHelper.ValidFileSize(344 * 961).Should().Be(false); - FileTradeHelper.ValidFileSize(344).Should().Be(true); - FileTradeHelper.ValidFileSize(0).Should().Be(false); - FileTradeHelper.ValidFileSize(345).Should().Be(false); - FileTradeHelper.ValidFileSize(344 * 10).Should().Be(true); - FileTradeHelper.ValidFileSize(344 * 1200).Should().Be(true); - FileTradeHelper.ValidFileSize(344 * 1201).Should().Be(false); + FileTradeHelper.ValidFileSize(376).Should().Be(true); + FileTradeHelper.ValidFileSize(0).Should().Be(false); + FileTradeHelper.ValidFileSize(375).Should().Be(false); + FileTradeHelper.ValidFileSize(360 * 10).Should().Be(true); + FileTradeHelper.ValidFileSize(360 * 960).Should().Be(true); + FileTradeHelper.ValidFileSize(360 * 961).Should().Be(false); - FileTradeHelper.ValidFileSize(344).Should().Be(true); - FileTradeHelper.ValidFileSize(0).Should().Be(false); - FileTradeHelper.ValidFileSize(345).Should().Be(false); - FileTradeHelper.ValidFileSize(344 * 10).Should().Be(true); - FileTradeHelper.ValidFileSize(344 * 960).Should().Be(true); - FileTradeHelper.ValidFileSize(344 * 961).Should().Be(false); - } + FileTradeHelper.ValidFileSize(344).Should().Be(true); + FileTradeHelper.ValidFileSize(0).Should().Be(false); + FileTradeHelper.ValidFileSize(345).Should().Be(false); + FileTradeHelper.ValidFileSize(344 * 10).Should().Be(true); + FileTradeHelper.ValidFileSize(344 * 1200).Should().Be(true); + FileTradeHelper.ValidFileSize(344 * 1201).Should().Be(false); + FileTradeHelper.ValidFileSize(344).Should().Be(true); + FileTradeHelper.ValidFileSize(0).Should().Be(false); + FileTradeHelper.ValidFileSize(345).Should().Be(false); + FileTradeHelper.ValidFileSize(344 * 10).Should().Be(true); + FileTradeHelper.ValidFileSize(344 * 960).Should().Be(true); + FileTradeHelper.ValidFileSize(344 * 961).Should().Be(false); } } diff --git a/SysBot.Tests/TranslatorTests.cs b/SysBot.Tests/TranslatorTests.cs index 4429f5511..92b4cbb52 100644 --- a/SysBot.Tests/TranslatorTests.cs +++ b/SysBot.Tests/TranslatorTests.cs @@ -5,48 +5,49 @@ using System.Diagnostics; using Xunit; -namespace SysBot.Tests -{ - public class TranslatorTests - { - static TranslatorTests() => AutoLegalityWrapper.EnsureInitialized(new Pokemon.LegalitySettings()); +namespace SysBot.Tests; - [Theory] - [InlineData("公肯泰罗携带大师球6V异色努力值252生命全招式异国-泰山压顶", "Tauros (M) @ Master Ball\nShiny: Yes\nIVs: 31 HP / 31 Atk / 31 Def / 31 SpA / 31 SpD / 31 Spe\nEVs: 252 HP \n.RelearnMoves=$suggestAll\nLanguage: Italian\n-Body Slam")] - public void TestTrans(string input, string output) - { - var result = ShowdownTranslator.Chinese2Showdown(input); - result.Should().Be(output); - } +public class TranslatorTests +{ + static TranslatorTests() => AutoLegalityWrapper.EnsureInitialized(new Pokemon.LegalitySettings()); - [Theory] - [InlineData("皮卡丘")] - [InlineData("木木枭")] - [InlineData("彩粉蝶-冰雪花纹")] - [InlineData("公小火龙的蛋")] - [InlineData("大剑鬼")] - [InlineData("火暴兽")] - public void TestLegal(string input) - { - var setstring = ShowdownTranslator.Chinese2Showdown(input); - var set = ShowdownUtil.ConvertToShowdown(setstring); - set.Should().NotBeNull(); - var template = AutoLegalityWrapper.GetTemplate(set); - template.Species.Should().BeGreaterThan(0); - var sav = AutoLegalityWrapper.GetTrainerInfo(); - var pkm = sav.GetLegal(template, out var result); - Trace.WriteLine(result.ToString()); + [Theory] + [InlineData("公肯泰罗携带大师球6V异色努力值252生命全招式异国-泰山压顶", "Tauros (M) @ Master Ball\nShiny: Yes\nIVs: 31 HP / 31 Atk / 31 Def / 31 SpA / 31 SpD / 31 Spe\nEVs: 252 HP \n.RelearnMoves=$suggestAll\nLanguage: Italian\n-Body Slam")] + public void TestTrans(string input, string output) + { + var result = ShowdownTranslator.Chinese2Showdown(input); + result.Should().Be(output); + } - if (pkm.Nickname.ToLower() == "egg" && Breeding.CanHatchAsEgg(pkm.Species)) AbstractTrade.EggTrade(pkm, template); + [Theory] + [InlineData("皮卡丘")] + [InlineData("木木枭")] + [InlineData("彩粉蝶-冰雪花纹")] + [InlineData("公小火龙的蛋")] + [InlineData("大剑鬼")] + [InlineData("火暴兽")] + [InlineData("千面避役")] + [InlineData("泪眼蜥")] + [InlineData("布里卡隆")] + public void TestLegal(string input) + { + var setstring = ShowdownTranslator.Chinese2Showdown(input); + var set = ShowdownUtil.ConvertToShowdown(setstring); + set.Should().NotBeNull(); + var template = AutoLegalityWrapper.GetTemplate(set); + template.Species.Should().BeGreaterThan(0); + var sav = AutoLegalityWrapper.GetTrainerInfo(); + var pkm = sav.GetLegal(template, out var result); + Trace.WriteLine(result.ToString()); - pkm.CanBeTraded().Should().BeTrue(); - (pkm is PK9).Should().BeTrue(); - var la = new LegalityAnalysis(pkm); - if (!la.Valid) - Trace.WriteLine(la.Report()); - la.Valid.Should().BeTrue(); - } + if (pkm.Nickname.ToLower() == "egg" && Breeding.CanHatchAsEgg(pkm.Species)) AbstractTrade.EggTrade(pkm, template); + pkm.CanBeTraded().Should().BeTrue(); + (pkm is PK9).Should().BeTrue(); + var la = new LegalityAnalysis(pkm); + if (!la.Valid) + Trace.WriteLine(la.Report()); + la.Valid.Should().BeTrue(); } } From 5491c6a452b335f63ea5f6c7de6a2cf447d03fe8 Mon Sep 17 00:00:00 2001 From: Easy World Date: Mon, 23 Dec 2024 17:00:44 +0800 Subject: [PATCH 142/171] Update PokeTradeBotSV.cs --- SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs index 8d1eee418..de999067f 100644 --- a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs +++ b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs @@ -1030,7 +1030,7 @@ private async Task SetBoxPkmWithSwappedIDDetailsSV(PK9 toSend, TradeMyStat // thanks @Wanghaoran86 if (toSend.MetLocation == Locations.TeraCavern9 && toSend.IsShiny) { - cln.PID = (((uint)(cln.TID16 ^ cln.SID16) ^ (cln.PID & 0xFFFF) ^ 1u) << 16) | (cln.PID & 0xFFFF); + cln.PID = (((uint)(cln.TID16 ^ cln.SID16) ^ (cln.PID & 0xFFFF) ^ toSend.ShinyXor) << 16) | (cln.PID & 0xFFFF); } else if (toSend.IsShiny) { From d2a431ec40d3196baeb4c4502f1772705d197893 Mon Sep 17 00:00:00 2001 From: Easy World Date: Tue, 14 Jan 2025 22:14:35 +0800 Subject: [PATCH 143/171] add batch trade for test --- .../BDSP/BotTrade/PokeTradeBotBS.cs | 98 +++++++++++++++++++ SysBot.Pokemon/Helpers/AbstractTrade.cs | 22 ++++- SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs | 70 ++++++++++++- SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs | 5 +- .../SWSH/BotTrade/PokeTradeBotSWSH.cs | 75 +++++++++++++- 5 files changed, 264 insertions(+), 6 deletions(-) diff --git a/SysBot.Pokemon/BDSP/BotTrade/PokeTradeBotBS.cs b/SysBot.Pokemon/BDSP/BotTrade/PokeTradeBotBS.cs index f80a0c700..208a431c5 100644 --- a/SysBot.Pokemon/BDSP/BotTrade/PokeTradeBotBS.cs +++ b/SysBot.Pokemon/BDSP/BotTrade/PokeTradeBotBS.cs @@ -1,6 +1,7 @@ using PKHeX.Core; using PKHeX.Core.Searching; using SysBot.Base; +using SysBot.Pokemon.Helpers; using System; using System.Linq; using System.Net.Sockets; @@ -301,6 +302,9 @@ private async Task PerformLinkCodeTrade(SAV8BS sav, PokeTradeDe await Task.Delay(2_000 + Hub.Config.Timings.ExtraTimeOpenBox, token).ConfigureAwait(false); + if (Hub.Config.Legality.UseTradePartnerInfo && !AbstractTrade.GetSkipAutoOTListFromPokeTradeDetail(poke).First()) + await ApplyAutoOT(toSend, sav, poke, tradePartner, token).ConfigureAwait(false); + // Confirm Box 1 Slot 1 if (poke.Type == PokeTradeType.Specific) { @@ -348,6 +352,10 @@ private async Task PerformLinkCodeTrade(SAV8BS sav, PokeTradeDe return PokeTradeResult.TrainerTooSlow; } + var batchTradeResult = await PerformRemainingLinkCodeTrades(sav, poke, tradePartner, token); + if (batchTradeResult != PokeTradeResult.Success) + return batchTradeResult; + // As long as we got rid of our inject in b1s1, assume the trade went through. Log("User completed the trade."); poke.TradeFinished(this, received); @@ -780,4 +788,94 @@ private void UpdateBarrier(bool shouldWait) Log($"Left the Barrier. Count: {Hub.BotSync.Barrier.ParticipantCount}"); } } + + // based on https://github.dev/bdawg1989/SysBot + private async Task ApplyAutoOT(PB8 toSend, SAV8BS sav, PokeTradeDetail poke, TradePartnerBS tradePartner, CancellationToken token) + { + if (token.IsCancellationRequested) return false; + + if (toSend is IHomeTrack pk && pk.HasTracker) + { + Log("Home tracker detected. Can't apply AutoOT."); + return false; + } + // Current handler cannot be past gen OT + if (toSend.Generation != toSend.Format) + { + Log("Cannot apply Partner details: Current handler cannot be different gen OT."); + return false; + } + var cln = toSend.Clone(); + cln.TrainerTID7 = uint.Parse(tradePartner.TID7); + cln.TrainerSID7 = uint.Parse(tradePartner.SID7); + cln.OriginalTrainerName = tradePartner.TrainerName; + if (!toSend.IsNicknamed) + cln.ClearNickname(); + if (toSend.IsShiny) + cln.PID = (uint)((cln.TID16 ^ cln.SID16 ^ (cln.PID & 0xFFFF) ^ toSend.ShinyXor) << 16) | (cln.PID & 0xFFFF); + if (!toSend.ChecksumValid) + cln.RefreshChecksum(); + var tradeBS = new LegalityAnalysis(cln); + if (tradeBS.Valid) + { + Log("Pokemon is valid with Trade Partner Info applied. Swapping details."); + await SetBoxPokemonAbsolute(BoxStartOffset, cln, token, sav).ConfigureAwait(false); + } + else + { + Log("Pokemon not valid after using Trade Partner Info."); + } + return tradeBS.Valid; + } + + private async Task PerformRemainingLinkCodeTrades(SAV8BS sav, PokeTradeDetail poke, TradePartnerBS tradePartner, CancellationToken token) + { + var pkms = AbstractTrade.GetPKMsFromPokeTradeDetail(poke).Skip(1); + var skipAutoOTList = AbstractTrade.GetSkipAutoOTListFromPokeTradeDetail(poke).Skip(1).ToList(); + foreach (var (toSend, index) in pkms.Select((value, i) => (value, i))) + { + Log($"Processing remaining Pokémon {index + 1} of {pkms.Count()} in batch trade."); + // Wait for user input... Needs to be different from the previously offered Pokémon. + var tradeOffered = await ReadUntilChanged(LinkTradePokemonOffset, lastOffered, 25_000, 1_000, false, true, token).ConfigureAwait(false); + if (!tradeOffered) + return PokeTradeResult.TrainerTooSlow; + + // If we detected a change, they offered something. + var offered = await ReadPokemon(LinkTradePokemonOffset, BoxFormatSlotSize, token).ConfigureAwait(false); + if (offered.Species == 0 || !offered.ChecksumValid) + return PokeTradeResult.TrainerTooSlow; + lastOffered = await SwitchConnection.ReadBytesAbsoluteAsync(LinkTradePokemonOffset, 8, token).ConfigureAwait(false); + + var trainer = new PartnerDataHolder(0, tradePartner.TrainerName, tradePartner.TID7); + + if (Hub.Config.Legality.UseTradePartnerInfo && !skipAutoOTList[index]) + await ApplyAutoOT(toSend, sav, poke, tradePartner, token); + else + await SetBoxPokemonAbsolute(BoxStartOffset, toSend, token, sav).ConfigureAwait(false); + + // Confirm Box 1 Slot 1 + if (poke.Type == PokeTradeType.Specific) + { + for (int i = 0; i < 5; i++) + await Click(A, 0_500, token).ConfigureAwait(false); + } + + var tradeResult = await ConfirmAndStartTrading(poke, token).ConfigureAwait(false); + if (tradeResult != PokeTradeResult.Success) + return tradeResult; + + if (token.IsCancellationRequested) + return PokeTradeResult.RoutineCancel; + + // Trade was Successful! + var received = await ReadPokemon(BoxStartOffset, BoxFormatSlotSize, token).ConfigureAwait(false); + // Pokémon in b1s1 is same as the one they were supposed to receive (was never sent). + if (SearchUtil.HashByDetails(received) == SearchUtil.HashByDetails(toSend) && received.Checksum == toSend.Checksum) + { + Log("User did not complete the trade."); + return PokeTradeResult.TrainerTooSlow; + } + } + return PokeTradeResult.Success; + } } diff --git a/SysBot.Pokemon/Helpers/AbstractTrade.cs b/SysBot.Pokemon/Helpers/AbstractTrade.cs index b1cd25ed5..6d57619fa 100644 --- a/SysBot.Pokemon/Helpers/AbstractTrade.cs +++ b/SysBot.Pokemon/Helpers/AbstractTrade.cs @@ -290,10 +290,28 @@ public bool CheckAndGetPkm(string setstring, out string msg, out T outPkm) return false; } + public static List GetPKMsFromPokeTradeDetail(PokeTradeDetail detail) + { + if (detail.Context.TryGetValue("batch", out object? value)) + { + return (List)value; + } + return [detail.TradeData]; + } + + public static List GetSkipAutoOTListFromPokeTradeDetail(PokeTradeDetail detail) + { + if (detail.Context.TryGetValue("skipAutoOTList", out object? value)) + { + return (List)value; + } + return [false]; + } + private bool AddToTradeQueue(T pk, int code, bool skipAutoOT, PokeRoutineType type, out string msg) { - return AddToTradeQueue(new List { pk }, code, new List { skipAutoOT }, type, out msg); + return AddToTradeQueue([pk], code, [skipAutoOT], type, out msg); } private bool AddToTradeQueue(List pks, int code, List skipAutoOTList, @@ -384,7 +402,7 @@ public static void EggTrade(PKM pk, IBattleTemplate template) pk.HandlingTrainerFriendship = 0; pk.ClearMemories(); pk.StatNature = pk.Nature; - pk.SetEVs(new int[] { 0, 0, 0, 0, 0, 0 }); + pk.SetEVs([0, 0, 0, 0, 0, 0]); MarkingApplicator.SetMarkings(pk); diff --git a/SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs b/SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs index 0c03df93e..3883cbfb6 100644 --- a/SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs +++ b/SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs @@ -1,6 +1,7 @@ using PKHeX.Core; using PKHeX.Core.Searching; using SysBot.Base; +using SysBot.Pokemon.Helpers; using System; using System.Linq; using System.Net.Sockets; @@ -320,7 +321,7 @@ private async Task PerformLinkCodeTrade(SAV8LA sav, PokeTradeDe return update; } - if (Hub.Config.Legality.UseTradePartnerInfo) + if (Hub.Config.Legality.UseTradePartnerInfo && !AbstractTrade.GetSkipAutoOTListFromPokeTradeDetail(poke).First()) { await SetBoxPkmWithSwappedIDDetailsPLA(toSend, tradePartner, sav, token); } @@ -351,6 +352,10 @@ private async Task PerformLinkCodeTrade(SAV8LA sav, PokeTradeDe return PokeTradeResult.TrainerTooSlow; } + var batchTradeResult = await PerformRemainingLinkCodeTrades(sav, poke, tradePartner, token); + if (batchTradeResult != PokeTradeResult.Success) + return batchTradeResult; + // As long as we got rid of our inject in b1s1, assume the trade went through. Log("User completed the trade."); poke.TradeFinished(this, received); @@ -746,4 +751,67 @@ private async Task SetBoxPkmWithSwappedIDDetailsPLA(PA8 toSend, TradePartn return tradela.Valid; } + + private async Task PerformRemainingLinkCodeTrades(SAV8LA sav, PokeTradeDetail poke, TradePartnerLA tradePartner, CancellationToken token) + { + var pkms = AbstractTrade.GetPKMsFromPokeTradeDetail(poke).Skip(1); + var skipAutoOTList = AbstractTrade.GetSkipAutoOTListFromPokeTradeDetail(poke).Skip(1).ToList(); + foreach (var (toSend, index) in pkms.Select((value, i) => (value, i))) + { + Log($"Processing remaining Pokémon {index + 1} of {pkms.Count()} in batch trade."); + // Watch their status to indicate they have offered a Pokémon as well. + var offering = await ReadUntilChanged(TradePartnerOfferedOffset, [0x3], 25_000, 1_000, true, true, token).ConfigureAwait(false); + if (!offering) + { + await ExitTrade(false, token).ConfigureAwait(false); + return PokeTradeResult.TrainerTooSlow; + } + + Log("Checking offered Pokémon."); + // If we got to here, we can read their offered Pokémon. + + // Wait for user input... Needs to be different from the previously offered Pokémon. + var offered = await ReadUntilPresentPointer(Offsets.LinkTradePartnerPokemonPointer, 3_000, 0_050, BoxFormatSlotSize, token).ConfigureAwait(false); + if (offered == null || offered.Species == 0 || !offered.ChecksumValid) + { + Log("Trade ended because trainer offer was rescinded too quickly."); + await ExitTrade(false, token).ConfigureAwait(false); + return PokeTradeResult.TrainerOfferCanceledQuick; + } + + var trainer = new PartnerDataHolder(0, tradePartner.TrainerName, tradePartner.TID7); + + if (Hub.Config.Legality.UseTradePartnerInfo && !skipAutoOTList[index]) + { + await SetBoxPkmWithSwappedIDDetailsPLA(toSend, tradePartner, sav, token); + } + + Log("Confirming trade."); + var tradeResult = await ConfirmAndStartTrading(poke, token).ConfigureAwait(false); + if (tradeResult != PokeTradeResult.Success) + { + if (tradeResult == PokeTradeResult.TrainerLeft) + Log("Trade canceled because trainer left the trade."); + await ExitTrade(false, token).ConfigureAwait(false); + return tradeResult; + } + + if (token.IsCancellationRequested) + { + await ExitTrade(false, token).ConfigureAwait(false); + return PokeTradeResult.RoutineCancel; + } + + // Trade was Successful! + var received = await ReadPokemon(BoxStartOffset, BoxFormatSlotSize, token).ConfigureAwait(false); + // Pokémon in b1s1 is same as the one they were supposed to receive (was never sent). + if (SearchUtil.HashByDetails(received) == SearchUtil.HashByDetails(toSend) && received.Checksum == toSend.Checksum) + { + Log("User did not complete the trade."); + await ExitTrade(false, token).ConfigureAwait(false); + return PokeTradeResult.TrainerTooSlow; + } + } + return PokeTradeResult.Success; + } } diff --git a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs index de999067f..6ade796d0 100644 --- a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs +++ b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs @@ -9,6 +9,7 @@ using static SysBot.Base.SwitchButton; using static SysBot.Pokemon.PokeDataOffsetsSV; using System.Collections.Generic; +using SysBot.Pokemon.Helpers; namespace SysBot.Pokemon; @@ -356,8 +357,8 @@ private async Task PerformLinkCodeTrade(SAV9SV sav, PokeTradeDe await ExitTradeToPortal(false, token).ConfigureAwait(false); return result; } - List batchPK9s = (List)poke.Context.GetValueOrDefault("batch", new List { toSend }); - List skipAutoOTList = (List)poke.Context.GetValueOrDefault("skipAutoOTList", new List { false }); + List batchPK9s = AbstractTrade.GetPKMsFromPokeTradeDetail(poke); + List skipAutoOTList = AbstractTrade.GetSkipAutoOTListFromPokeTradeDetail(poke); PK9 received = default!; LogUtil.LogInfo($"count:{batchPK9s.Count}, skipAutoOTList:{String.Join(',', skipAutoOTList)}", nameof(PokeTradeBotSV)); for (var i = 0; i < batchPK9s.Count; i++) diff --git a/SysBot.Pokemon/SWSH/BotTrade/PokeTradeBotSWSH.cs b/SysBot.Pokemon/SWSH/BotTrade/PokeTradeBotSWSH.cs index 95e60cc95..f76d1234c 100644 --- a/SysBot.Pokemon/SWSH/BotTrade/PokeTradeBotSWSH.cs +++ b/SysBot.Pokemon/SWSH/BotTrade/PokeTradeBotSWSH.cs @@ -1,7 +1,9 @@ using PKHeX.Core; using PKHeX.Core.Searching; using SysBot.Base; +using SysBot.Pokemon.Helpers; using System; +using System.Collections.Generic; using System.Linq; using System.Net.Sockets; using System.Threading; @@ -321,7 +323,7 @@ private async Task PerformLinkCodeTrade(SAV8SWSH sav, PokeTrade return PokeTradeResult.RecoverOpenBox; } - if (hub.Config.Legality.UseTradePartnerInfo) + if (hub.Config.Legality.UseTradePartnerInfo && !AbstractTrade.GetSkipAutoOTListFromPokeTradeDetail(poke).First()) { await SetPkmWithSwappedIDDetails(toSend, trainerName, sav, token); } @@ -384,6 +386,9 @@ private async Task PerformLinkCodeTrade(SAV8SWSH sav, PokeTrade await ExitTrade(false, token).ConfigureAwait(false); return PokeTradeResult.TrainerTooSlow; } + var batchTradeResult = await PerformRemainingLinkCodeTrades(trainer, sav, poke, token); + if (batchTradeResult != PokeTradeResult.Success) + return batchTradeResult; // As long as we got rid of our inject in b1s1, assume the trade went through. Log("User completed the trade."); @@ -998,4 +1003,72 @@ private async Task SetPkmWithSwappedIDDetails(PK8 toSend, string trainerNa return tradeswsh.Valid; } + + /// + /// Processes the remaining Pokémon in a batch Link Code trade, starting from the second Pokémon. + /// The first Pokémon is assumed to have already been handled separately. + /// + private async Task PerformRemainingLinkCodeTrades(PartnerDataHolder trainer, SAV8SWSH sav, PokeTradeDetail poke, CancellationToken token) + { + var pkms = AbstractTrade.GetPKMsFromPokeTradeDetail(poke).Skip(1); + var skipAutoOTList = AbstractTrade.GetSkipAutoOTListFromPokeTradeDetail(poke).Skip(1).ToList(); + foreach (var (toSend, index) in pkms.Select((value, i) => (value, i))) + { + Log($"Processing remaining Pokémon {index + 1} of {pkms.Count()} in batch trade."); + if (toSend.Species != 0 && hub.Config.Legality.UseTradePartnerInfo && !skipAutoOTList[index]) + { + await SetPkmWithSwappedIDDetails(toSend, trainer.TrainerName, sav, token); + } + else if (toSend.Species != 0) + { + await SetBoxPokemon(toSend, 0, 0, token, sav).ConfigureAwait(false); + } + // Wait for User Input... + var offered = await ReadUntilPresent(LinkTradePartnerPokemonOffset, 25_000, 1_000, BoxFormatSlotSize, token).ConfigureAwait(false); + var oldEC = await Connection.ReadBytesAsync(LinkTradePartnerPokemonOffset, 4, token).ConfigureAwait(false); + if (offered is null) + { + await ExitSeedCheckTrade(token).ConfigureAwait(false); + return PokeTradeResult.TrainerTooSlow; + } + + // Confirm Box 1 Slot 1 + if (poke.Type == PokeTradeType.Specific) + { + for (int i = 0; i < 5; i++) + await Click(A, 0_500, token).ConfigureAwait(false); + } + + if (poke.Type == PokeTradeType.Seed) + { + // Immediately exit, we aren't trading anything. + return await EndSeedCheckTradeAsync(poke, offered, token).ConfigureAwait(false); + } + + var tradeResult = await ConfirmAndStartTrading(poke, token).ConfigureAwait(false); + if (tradeResult != PokeTradeResult.Success) + { + await ExitTrade(false, token).ConfigureAwait(false); + return tradeResult; + } + + if (token.IsCancellationRequested) + { + await ExitTrade(false, token).ConfigureAwait(false); + return PokeTradeResult.RoutineCancel; + } + + // Trade was Successful! + var received = await ReadBoxPokemon(0, 0, token).ConfigureAwait(false); + // Pokémon in b1s1 is same as the one they were supposed to receive (was never sent). + if (SearchUtil.HashByDetails(received) == SearchUtil.HashByDetails(toSend) && received.Checksum == toSend.Checksum) + { + Log("User did not complete the trade."); + RecordUtil.Record($"Cancelled\t{trainer.TrainerOnlineID:X16}\t{trainer.TrainerName}\t{poke.Trainer.TrainerName}\\t{poke.ID}\t{toSend.EncryptionConstant:X8}\t{offered.EncryptionConstant:X8}"); + await ExitTrade(false, token).ConfigureAwait(false); + return PokeTradeResult.TrainerTooSlow; + } + } + return PokeTradeResult.Success; + } } From 49cd6a2e2811da8b8105943d71b11b9f110b282d Mon Sep 17 00:00:00 2001 From: Easy World Date: Sat, 18 Jan 2025 22:35:12 +0800 Subject: [PATCH 144/171] fix batch trade bug --- SysBot.Pokemon.Dodo/PokemonProcessService.cs | 4 ++-- SysBot.Pokemon/BDSP/BotTrade/PokeTradeBotBS.cs | 7 ------- SysBot.Pokemon/Helpers/AbstractTrade.cs | 2 +- SysBot.Pokemon/SWSH/BotTrade/PokeTradeBotSWSH.cs | 7 ------- 4 files changed, 3 insertions(+), 17 deletions(-) diff --git a/SysBot.Pokemon.Dodo/PokemonProcessService.cs b/SysBot.Pokemon.Dodo/PokemonProcessService.cs index 69a6cd67d..208728972 100644 --- a/SysBot.Pokemon.Dodo/PokemonProcessService.cs +++ b/SysBot.Pokemon.Dodo/PokemonProcessService.cs @@ -96,7 +96,7 @@ public override void ChannelMessageEvent( if (!content.Contains($"<@!{_botDodoSourceId}>")) return; content = content.Substring(content.IndexOf('>') + 1); - if (typeof(TP) == typeof(PK9) && content.Contains("\n\n") && ShowdownTranslator.IsPS(content))// 仅SV支持批量,其他偷懒还没写 + if (content.Contains("\n\n") && ShowdownTranslator.IsPS(content)) { ProcessWithdraw(eventBody.MessageId); new DodoTrade(ulong.Parse(eventBody.DodoSourceId), eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId).StartTradeMultiPs(content.Trim()); @@ -114,7 +114,7 @@ public override void ChannelMessageEvent( new DodoTrade(ulong.Parse(eventBody.DodoSourceId), eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId).StartDump(); return; } - else if (typeof(TP) == typeof(PK9) && content.Trim().Contains('+'))// 仅SV支持批量,其他偷懒还没写 + else if (content.Trim().Contains('+')) { ProcessWithdraw(eventBody.MessageId); new DodoTrade(ulong.Parse(eventBody.DodoSourceId), eventBody.Personal.NickName, eventBody.ChannelId, eventBody.IslandSourceId).StartTradeMultiChinesePs(content.Trim()); diff --git a/SysBot.Pokemon/BDSP/BotTrade/PokeTradeBotBS.cs b/SysBot.Pokemon/BDSP/BotTrade/PokeTradeBotBS.cs index 208a431c5..4043f1efd 100644 --- a/SysBot.Pokemon/BDSP/BotTrade/PokeTradeBotBS.cs +++ b/SysBot.Pokemon/BDSP/BotTrade/PokeTradeBotBS.cs @@ -853,13 +853,6 @@ private async Task PerformRemainingLinkCodeTrades(SAV8BS sav, P else await SetBoxPokemonAbsolute(BoxStartOffset, toSend, token, sav).ConfigureAwait(false); - // Confirm Box 1 Slot 1 - if (poke.Type == PokeTradeType.Specific) - { - for (int i = 0; i < 5; i++) - await Click(A, 0_500, token).ConfigureAwait(false); - } - var tradeResult = await ConfirmAndStartTrading(poke, token).ConfigureAwait(false); if (tradeResult != PokeTradeResult.Success) return tradeResult; diff --git a/SysBot.Pokemon/Helpers/AbstractTrade.cs b/SysBot.Pokemon/Helpers/AbstractTrade.cs index 6d57619fa..e5e46bef5 100644 --- a/SysBot.Pokemon/Helpers/AbstractTrade.cs +++ b/SysBot.Pokemon/Helpers/AbstractTrade.cs @@ -179,7 +179,7 @@ private bool JudgeInvalidCount(int invalidCount, int totalCount) { if (invalidCount == totalCount) { - SendMessage("一个都不合法,换个屁"); + SendMessage("一个都不合法,请检查"); return false; } else if (invalidCount != 0) diff --git a/SysBot.Pokemon/SWSH/BotTrade/PokeTradeBotSWSH.cs b/SysBot.Pokemon/SWSH/BotTrade/PokeTradeBotSWSH.cs index f76d1234c..c6e9739e4 100644 --- a/SysBot.Pokemon/SWSH/BotTrade/PokeTradeBotSWSH.cs +++ b/SysBot.Pokemon/SWSH/BotTrade/PokeTradeBotSWSH.cs @@ -1032,13 +1032,6 @@ private async Task PerformRemainingLinkCodeTrades(PartnerDataHo return PokeTradeResult.TrainerTooSlow; } - // Confirm Box 1 Slot 1 - if (poke.Type == PokeTradeType.Specific) - { - for (int i = 0; i < 5; i++) - await Click(A, 0_500, token).ConfigureAwait(false); - } - if (poke.Type == PokeTradeType.Seed) { // Immediately exit, we aren't trading anything. From a975b48432925ff9263a3894b2f93d11fab07645 Mon Sep 17 00:00:00 2001 From: Easy World Date: Tue, 4 Feb 2025 22:18:34 +0800 Subject: [PATCH 145/171] Generate skipAutoOTList size dynamically based on PKM count Update GetSkipAutoOTListFromPokeTradeDetail to generate the skipAutoOTList dynamically based on the count of PKMs returned by GetPKMsFromPokeTradeDetail. Replaced the hardcoded [false] with a flexible implementation using Enumerable.Repeat. --- SysBot.Pokemon/Helpers/AbstractTrade.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SysBot.Pokemon/Helpers/AbstractTrade.cs b/SysBot.Pokemon/Helpers/AbstractTrade.cs index e5e46bef5..de2fb9c5e 100644 --- a/SysBot.Pokemon/Helpers/AbstractTrade.cs +++ b/SysBot.Pokemon/Helpers/AbstractTrade.cs @@ -305,7 +305,8 @@ public static List GetSkipAutoOTListFromPokeTradeDetail(PokeTradeDetail { return (List)value; } - return [false]; + int count = GetPKMsFromPokeTradeDetail(detail).Count; + return Enumerable.Repeat(false, count).ToList(); } private bool AddToTradeQueue(T pk, int code, bool skipAutoOT, From 3a6a9bd5d32a1fdfab5a0768adc37291958643bd Mon Sep 17 00:00:00 2001 From: Lusamine <30205550+Lusamine@users.noreply.github.com> Date: Wed, 19 Mar 2025 09:07:23 -0500 Subject: [PATCH 146/171] Remove double HardStop on all bots --- SysBot.Pokemon/BDSP/BotRemoteControl/RemoteControlBotBS.cs | 3 +-- SysBot.Pokemon/BDSP/BotTrade/PokeTradeBotBS.cs | 1 - SysBot.Pokemon/LA/BotRemoteControl/RemoteControlBotLA.cs | 3 +-- SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs | 1 - SysBot.Pokemon/SV/BotRemoteControl/RemoteControlBotSV.cs | 3 +-- SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs | 1 - SysBot.Pokemon/SWSH/BotEncounter/EncounterBotSWSH.cs | 1 - SysBot.Pokemon/SWSH/BotRaid/RaidBotSWSH.cs | 1 - SysBot.Pokemon/SWSH/BotRemoteControl/RemoteControlBotSWSH.cs | 3 +-- SysBot.Pokemon/SWSH/BotTrade/PokeTradeBotSWSH.cs | 1 - 10 files changed, 4 insertions(+), 14 deletions(-) diff --git a/SysBot.Pokemon/BDSP/BotRemoteControl/RemoteControlBotBS.cs b/SysBot.Pokemon/BDSP/BotRemoteControl/RemoteControlBotBS.cs index e9b92ca33..acfe22d4f 100644 --- a/SysBot.Pokemon/BDSP/BotRemoteControl/RemoteControlBotBS.cs +++ b/SysBot.Pokemon/BDSP/BotRemoteControl/RemoteControlBotBS.cs @@ -1,4 +1,4 @@ -using SysBot.Base; +using SysBot.Base; using System; using System.Threading; using System.Threading.Tasks; @@ -28,7 +28,6 @@ public override async Task MainLoop(CancellationToken token) } Log($"Ending {nameof(RemoteControlBotBS)} loop."); - await HardStop().ConfigureAwait(false); } public override async Task HardStop() diff --git a/SysBot.Pokemon/BDSP/BotTrade/PokeTradeBotBS.cs b/SysBot.Pokemon/BDSP/BotTrade/PokeTradeBotBS.cs index fd816cac1..ecdc76656 100644 --- a/SysBot.Pokemon/BDSP/BotTrade/PokeTradeBotBS.cs +++ b/SysBot.Pokemon/BDSP/BotTrade/PokeTradeBotBS.cs @@ -67,7 +67,6 @@ public override async Task MainLoop(CancellationToken token) } Log($"Ending {nameof(PokeTradeBotBS)} loop."); - await HardStop().ConfigureAwait(false); } public override Task HardStop() diff --git a/SysBot.Pokemon/LA/BotRemoteControl/RemoteControlBotLA.cs b/SysBot.Pokemon/LA/BotRemoteControl/RemoteControlBotLA.cs index bf220827d..fcee78ff6 100644 --- a/SysBot.Pokemon/LA/BotRemoteControl/RemoteControlBotLA.cs +++ b/SysBot.Pokemon/LA/BotRemoteControl/RemoteControlBotLA.cs @@ -1,4 +1,4 @@ -using SysBot.Base; +using SysBot.Base; using System; using System.Threading; using System.Threading.Tasks; @@ -28,7 +28,6 @@ public override async Task MainLoop(CancellationToken token) } Log($"Ending {nameof(RemoteControlBotLA)} loop."); - await HardStop().ConfigureAwait(false); } public override async Task HardStop() diff --git a/SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs b/SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs index 54d3bc12e..b870785ba 100644 --- a/SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs +++ b/SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs @@ -64,7 +64,6 @@ public override async Task MainLoop(CancellationToken token) } Log($"Ending {nameof(PokeTradeBotLA)} loop."); - await HardStop().ConfigureAwait(false); } public override Task HardStop() diff --git a/SysBot.Pokemon/SV/BotRemoteControl/RemoteControlBotSV.cs b/SysBot.Pokemon/SV/BotRemoteControl/RemoteControlBotSV.cs index 40780c61c..bcebacb11 100644 --- a/SysBot.Pokemon/SV/BotRemoteControl/RemoteControlBotSV.cs +++ b/SysBot.Pokemon/SV/BotRemoteControl/RemoteControlBotSV.cs @@ -1,4 +1,4 @@ -using SysBot.Base; +using SysBot.Base; using System; using System.Threading; using System.Threading.Tasks; @@ -28,7 +28,6 @@ public override async Task MainLoop(CancellationToken token) } Log($"Ending {nameof(RemoteControlBotSV)} loop."); - await HardStop().ConfigureAwait(false); } public override async Task HardStop() diff --git a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs index 483c8fd63..c136e9737 100644 --- a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs +++ b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs @@ -83,7 +83,6 @@ public override async Task MainLoop(CancellationToken token) } Log($"Ending {nameof(PokeTradeBotSV)} loop."); - await HardStop().ConfigureAwait(false); } public override Task HardStop() diff --git a/SysBot.Pokemon/SWSH/BotEncounter/EncounterBotSWSH.cs b/SysBot.Pokemon/SWSH/BotEncounter/EncounterBotSWSH.cs index 34107cd4b..dd10e1038 100644 --- a/SysBot.Pokemon/SWSH/BotEncounter/EncounterBotSWSH.cs +++ b/SysBot.Pokemon/SWSH/BotEncounter/EncounterBotSWSH.cs @@ -57,7 +57,6 @@ public override async Task MainLoop(CancellationToken token) } Log($"Ending {GetType().Name} loop."); - await HardStop().ConfigureAwait(false); } public override async Task HardStop() diff --git a/SysBot.Pokemon/SWSH/BotRaid/RaidBotSWSH.cs b/SysBot.Pokemon/SWSH/BotRaid/RaidBotSWSH.cs index d07f42eb5..9acebb7c8 100644 --- a/SysBot.Pokemon/SWSH/BotRaid/RaidBotSWSH.cs +++ b/SysBot.Pokemon/SWSH/BotRaid/RaidBotSWSH.cs @@ -47,7 +47,6 @@ public override async Task MainLoop(CancellationToken token) } Log($"Ending {nameof(RaidBotSWSH)} loop."); - await HardStop().ConfigureAwait(false); } private async Task InnerLoop(CancellationToken token) diff --git a/SysBot.Pokemon/SWSH/BotRemoteControl/RemoteControlBotSWSH.cs b/SysBot.Pokemon/SWSH/BotRemoteControl/RemoteControlBotSWSH.cs index 1ff0e984b..564bdcf80 100644 --- a/SysBot.Pokemon/SWSH/BotRemoteControl/RemoteControlBotSWSH.cs +++ b/SysBot.Pokemon/SWSH/BotRemoteControl/RemoteControlBotSWSH.cs @@ -1,4 +1,4 @@ -using SysBot.Base; +using SysBot.Base; using System; using System.Threading; using System.Threading.Tasks; @@ -28,7 +28,6 @@ public override async Task MainLoop(CancellationToken token) } Log($"Ending {nameof(RemoteControlBotSWSH)} loop."); - await HardStop().ConfigureAwait(false); } public override async Task HardStop() diff --git a/SysBot.Pokemon/SWSH/BotTrade/PokeTradeBotSWSH.cs b/SysBot.Pokemon/SWSH/BotTrade/PokeTradeBotSWSH.cs index dab6398d6..2ee690002 100644 --- a/SysBot.Pokemon/SWSH/BotTrade/PokeTradeBotSWSH.cs +++ b/SysBot.Pokemon/SWSH/BotTrade/PokeTradeBotSWSH.cs @@ -58,7 +58,6 @@ public override async Task MainLoop(CancellationToken token) } Log($"Ending {nameof(PokeTradeBotSWSH)} loop."); - await HardStop().ConfigureAwait(false); } public override Task HardStop() From 4788a318aff4961fff872cdc03d8fae9621530b2 Mon Sep 17 00:00:00 2001 From: Easy World Date: Tue, 3 Jun 2025 13:14:48 +0800 Subject: [PATCH 147/171] kook support Copy code from SysBot.Pokemon.Discord --- SysBot.NET.sln | 6 + .../PokeBotRunnerImpl.cs | 12 +- .../SysBot.Pokemon.ConsoleApp.csproj | 1 + .../Commands/Bots/CloneModule.cs | 59 ++++ .../Commands/Bots/DumpModule.cs | 59 ++++ .../Commands/Bots/QueueModule.cs | 146 ++++++++++ .../Commands/Bots/RemoteControlModule.cs | 145 ++++++++++ .../Commands/Bots/SeedCheckModule.cs | 84 ++++++ .../Commands/Bots/TradeModule.cs | 248 ++++++++++++++++ .../Commands/Extra/BatchEditingModule.cs | 54 ++++ .../Commands/Extra/LegalityCheckModule.cs | 49 ++++ .../Commands/Extra/LegalizerModule.cs | 33 +++ .../Commands/General/HelloModule.cs | 18 ++ .../Commands/General/HelpModule.cs | 102 +++++++ .../Commands/General/InfoModule.cs | 79 ++++++ .../Commands/General/PingModule.cs | 14 + .../Commands/Management/BotModule.cs | 124 ++++++++ .../Commands/Management/EchoModule.cs | 119 ++++++++ .../Commands/Management/EncounterModule.cs | 27 ++ .../Commands/Management/HubModule.cs | 87 ++++++ .../Commands/Management/LogModule.cs | 104 +++++++ .../Commands/Management/OwnerModule.cs | 128 +++++++++ .../Commands/Management/PoolModule.cs | 51 ++++ .../Commands/Management/SudoModule.cs | 205 ++++++++++++++ .../Commands/Management/TradeStartModule.cs | 124 ++++++++ .../Helpers/AutoLegalityExtensionsKook.cs | 96 +++++++ SysBot.Pokemon.Kook/Helpers/ChannelAction.cs | 10 + SysBot.Pokemon.Kook/Helpers/ChannelLogger.cs | 26 ++ SysBot.Pokemon.Kook/Helpers/KookManager.cs | 59 ++++ .../Helpers/KookTradeNotifier.cs | 86 ++++++ SysBot.Pokemon.Kook/Helpers/NetUtil.cs | 49 ++++ SysBot.Pokemon.Kook/Helpers/QueueHelper.cs | 141 +++++++++ .../Helpers/RequireOwnerAttribute.cs | 22 ++ .../Helpers/RequireQueueRoleAttribute.cs | 41 +++ .../Helpers/RequireRoleAccessAttribute.cs | 37 +++ .../Helpers/RequireRoleAttribute.cs | 31 ++ .../Helpers/RequireSudoAttribute.cs | 28 ++ .../Helpers/ReusableActions.cs | 88 ++++++ SysBot.Pokemon.Kook/KookBot.cs | 268 ++++++++++++++++++ .../SysBot.Pokemon.Kook.csproj | 17 ++ SysBot.Pokemon.WinForms/PokeBotRunnerImpl.cs | 19 +- .../SysBot.Pokemon.WinForms.csproj | 1 + .../Settings/Integrations/BilibiliSettings.cs | 23 +- .../Settings/Integrations/DodoSettings.cs | 37 ++- .../Settings/Integrations/KookSettings.cs | 88 ++++++ SysBot.Pokemon/TradeHub/PokeTradeHubConfig.cs | 6 +- 46 files changed, 3211 insertions(+), 40 deletions(-) create mode 100644 SysBot.Pokemon.Kook/Commands/Bots/CloneModule.cs create mode 100644 SysBot.Pokemon.Kook/Commands/Bots/DumpModule.cs create mode 100644 SysBot.Pokemon.Kook/Commands/Bots/QueueModule.cs create mode 100644 SysBot.Pokemon.Kook/Commands/Bots/RemoteControlModule.cs create mode 100644 SysBot.Pokemon.Kook/Commands/Bots/SeedCheckModule.cs create mode 100644 SysBot.Pokemon.Kook/Commands/Bots/TradeModule.cs create mode 100644 SysBot.Pokemon.Kook/Commands/Extra/BatchEditingModule.cs create mode 100644 SysBot.Pokemon.Kook/Commands/Extra/LegalityCheckModule.cs create mode 100644 SysBot.Pokemon.Kook/Commands/Extra/LegalizerModule.cs create mode 100644 SysBot.Pokemon.Kook/Commands/General/HelloModule.cs create mode 100644 SysBot.Pokemon.Kook/Commands/General/HelpModule.cs create mode 100644 SysBot.Pokemon.Kook/Commands/General/InfoModule.cs create mode 100644 SysBot.Pokemon.Kook/Commands/General/PingModule.cs create mode 100644 SysBot.Pokemon.Kook/Commands/Management/BotModule.cs create mode 100644 SysBot.Pokemon.Kook/Commands/Management/EchoModule.cs create mode 100644 SysBot.Pokemon.Kook/Commands/Management/EncounterModule.cs create mode 100644 SysBot.Pokemon.Kook/Commands/Management/HubModule.cs create mode 100644 SysBot.Pokemon.Kook/Commands/Management/LogModule.cs create mode 100644 SysBot.Pokemon.Kook/Commands/Management/OwnerModule.cs create mode 100644 SysBot.Pokemon.Kook/Commands/Management/PoolModule.cs create mode 100644 SysBot.Pokemon.Kook/Commands/Management/SudoModule.cs create mode 100644 SysBot.Pokemon.Kook/Commands/Management/TradeStartModule.cs create mode 100644 SysBot.Pokemon.Kook/Helpers/AutoLegalityExtensionsKook.cs create mode 100644 SysBot.Pokemon.Kook/Helpers/ChannelAction.cs create mode 100644 SysBot.Pokemon.Kook/Helpers/ChannelLogger.cs create mode 100644 SysBot.Pokemon.Kook/Helpers/KookManager.cs create mode 100644 SysBot.Pokemon.Kook/Helpers/KookTradeNotifier.cs create mode 100644 SysBot.Pokemon.Kook/Helpers/NetUtil.cs create mode 100644 SysBot.Pokemon.Kook/Helpers/QueueHelper.cs create mode 100644 SysBot.Pokemon.Kook/Helpers/RequireOwnerAttribute.cs create mode 100644 SysBot.Pokemon.Kook/Helpers/RequireQueueRoleAttribute.cs create mode 100644 SysBot.Pokemon.Kook/Helpers/RequireRoleAccessAttribute.cs create mode 100644 SysBot.Pokemon.Kook/Helpers/RequireRoleAttribute.cs create mode 100644 SysBot.Pokemon.Kook/Helpers/RequireSudoAttribute.cs create mode 100644 SysBot.Pokemon.Kook/Helpers/ReusableActions.cs create mode 100644 SysBot.Pokemon.Kook/KookBot.cs create mode 100644 SysBot.Pokemon.Kook/SysBot.Pokemon.Kook.csproj create mode 100644 SysBot.Pokemon/Settings/Integrations/KookSettings.cs diff --git a/SysBot.NET.sln b/SysBot.NET.sln index 6f1f6df91..52f56c219 100644 --- a/SysBot.NET.sln +++ b/SysBot.NET.sln @@ -29,6 +29,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SysBot.Pokemon.Bilibili", " EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SysBot.Pokemon.Dodo", "SysBot.Pokemon.Dodo\SysBot.Pokemon.Dodo.csproj", "{BE43311D-4571-466E-B3CB-3FD658EFC159}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SysBot.Pokemon.Kook", "SysBot.Pokemon.Kook\SysBot.Pokemon.Kook.csproj", "{6F77F72F-9700-4CEE-9C41-46539C176269}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -83,6 +85,10 @@ Global {BE43311D-4571-466E-B3CB-3FD658EFC159}.Debug|Any CPU.Build.0 = Debug|Any CPU {BE43311D-4571-466E-B3CB-3FD658EFC159}.Release|Any CPU.ActiveCfg = Release|Any CPU {BE43311D-4571-466E-B3CB-3FD658EFC159}.Release|Any CPU.Build.0 = Release|Any CPU + {6F77F72F-9700-4CEE-9C41-46539C176269}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6F77F72F-9700-4CEE-9C41-46539C176269}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6F77F72F-9700-4CEE-9C41-46539C176269}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6F77F72F-9700-4CEE-9C41-46539C176269}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/SysBot.Pokemon.ConsoleApp/PokeBotRunnerImpl.cs b/SysBot.Pokemon.ConsoleApp/PokeBotRunnerImpl.cs index 0451f2dfd..47350ed30 100644 --- a/SysBot.Pokemon.ConsoleApp/PokeBotRunnerImpl.cs +++ b/SysBot.Pokemon.ConsoleApp/PokeBotRunnerImpl.cs @@ -1,4 +1,4 @@ -using PKHeX.Core; +using PKHeX.Core; using SysBot.Pokemon.Discord; using SysBot.Pokemon.Twitch; using System.Threading; @@ -6,6 +6,7 @@ using SysBot.Pokemon.Bilibili; using SysBot.Pokemon.QQ; using SysBot.Pokemon.Dodo; +using SysBot.Pokemon.Kook; namespace SysBot.Pokemon.ConsoleApp; @@ -29,6 +30,7 @@ protected override void AddIntegrations() AddQQBot(Hub.Config.QQ); AddBilibiliBot(Hub.Config.Bilibili); AddDodoBot(Hub.Config.Dodo); + AddKookBot(Hub.Config.Kook.Token); } private void AddTwitchBot(TwitchSettings config) @@ -82,4 +84,12 @@ private void AddDodoBot(DodoSettings config) if (Dodo != null) return; Dodo = new DodoBot(config, Hub); } + + private void AddKookBot(string apiToken) + { + if (string.IsNullOrWhiteSpace(apiToken)) + return; + var bot = new KookBot(this); + Task.Run(() => bot.MainAsync(apiToken, CancellationToken.None), CancellationToken.None); + } } diff --git a/SysBot.Pokemon.ConsoleApp/SysBot.Pokemon.ConsoleApp.csproj b/SysBot.Pokemon.ConsoleApp/SysBot.Pokemon.ConsoleApp.csproj index 04de99c5c..83f16f461 100644 --- a/SysBot.Pokemon.ConsoleApp/SysBot.Pokemon.ConsoleApp.csproj +++ b/SysBot.Pokemon.ConsoleApp/SysBot.Pokemon.ConsoleApp.csproj @@ -10,6 +10,7 @@ + diff --git a/SysBot.Pokemon.Kook/Commands/Bots/CloneModule.cs b/SysBot.Pokemon.Kook/Commands/Bots/CloneModule.cs new file mode 100644 index 000000000..87fbfbd70 --- /dev/null +++ b/SysBot.Pokemon.Kook/Commands/Bots/CloneModule.cs @@ -0,0 +1,59 @@ +using Kook; +using Kook.Commands; +using PKHeX.Core; +using System.Threading.Tasks; + +namespace SysBot.Pokemon.Kook; + +[Summary("Queues new Clone trades")] +public class CloneModule : ModuleBase where T : PKM, new() +{ + private static TradeQueueInfo Info => KookBot.Runner.Hub.Queues.Info; + + [Command("clone")] + [Alias("c")] + [Summary("Clones the Pokémon you show via Link Trade.")] + [RequireQueueRole(nameof(KookManager.RolesClone))] + public Task CloneAsync(int code) + { + var sig = Context.User.GetFavor(); + return QueueHelper.AddToQueueAsync(Context, code, Context.User.Username, sig, new T(), PokeRoutineType.Clone, PokeTradeType.Clone); + } + + [Command("clone")] + [Alias("c")] + [Summary("Clones the Pokémon you show via Link Trade.")] + [RequireQueueRole(nameof(KookManager.RolesClone))] + public Task CloneAsync([Summary("Trade Code")][Remainder] string code) + { + int tradeCode = Util.ToInt32(code); + var sig = Context.User.GetFavor(); + return QueueHelper.AddToQueueAsync(Context, tradeCode == 0 ? Info.GetRandomTradeCode() : tradeCode, Context.User.Username, sig, new T(), PokeRoutineType.Clone, PokeTradeType.Clone); + } + + [Command("clone")] + [Alias("c")] + [Summary("Clones the Pokémon you show via Link Trade.")] + [RequireQueueRole(nameof(KookManager.RolesClone))] + public Task CloneAsync() + { + var code = Info.GetRandomTradeCode(); + return CloneAsync(code); + } + + [Command("cloneList")] + [Alias("cl", "cq")] + [Summary("Prints the users in the Clone queue.")] + [RequireSudo] + public async Task GetListAsync() + { + string msg = Info.GetTradeList(PokeRoutineType.Clone); + + var card = new CardBuilder() + .AddModule(new SectionModuleBuilder().WithText("These are the users who are currently waiting:")) + .AddModule(new SectionModuleBuilder().WithText("Pending Trades")) + .AddModule(new SectionModuleBuilder().WithText(msg)) + .Build(); + await ReplyCardAsync(card).ConfigureAwait(false); + } +} diff --git a/SysBot.Pokemon.Kook/Commands/Bots/DumpModule.cs b/SysBot.Pokemon.Kook/Commands/Bots/DumpModule.cs new file mode 100644 index 000000000..2a30e2738 --- /dev/null +++ b/SysBot.Pokemon.Kook/Commands/Bots/DumpModule.cs @@ -0,0 +1,59 @@ +using Kook; +using Kook.Commands; +using PKHeX.Core; +using System.Threading.Tasks; + +namespace SysBot.Pokemon.Kook; + +[Summary("Queues new Dump trades")] +public class DumpModule : ModuleBase where T : PKM, new() +{ + private static TradeQueueInfo Info => KookBot.Runner.Hub.Queues.Info; + + [Command("dump")] + [Alias("d")] + [Summary("Dumps the Pokémon you show via Link Trade.")] + [RequireQueueRole(nameof(KookManager.RolesDump))] + public Task DumpAsync(int code) + { + var sig = Context.User.GetFavor(); + return QueueHelper.AddToQueueAsync(Context, code, Context.User.Username, sig, new T(), PokeRoutineType.Dump, PokeTradeType.Dump); + } + + [Command("dump")] + [Alias("d")] + [Summary("Dumps the Pokémon you show via Link Trade.")] + [RequireQueueRole(nameof(KookManager.RolesDump))] + public Task DumpAsync([Summary("Trade Code")][Remainder] string code) + { + int tradeCode = Util.ToInt32(code); + var sig = Context.User.GetFavor(); + return QueueHelper.AddToQueueAsync(Context, tradeCode == 0 ? Info.GetRandomTradeCode() : tradeCode, Context.User.Username, sig, new T(), PokeRoutineType.Dump, PokeTradeType.Dump); + } + + [Command("dump")] + [Alias("d")] + [Summary("Dumps the Pokémon you show via Link Trade.")] + [RequireQueueRole(nameof(KookManager.RolesDump))] + public Task DumpAsync() + { + var code = Info.GetRandomTradeCode(); + return DumpAsync(code); + } + + [Command("dumpList")] + [Alias("dl", "dq")] + [Summary("Prints the users in the Dump queue.")] + [RequireSudo] + public async Task GetListAsync() + { + string msg = Info.GetTradeList(PokeRoutineType.Dump); + + var card = new CardBuilder() + .AddModule(new SectionModuleBuilder().WithText("These are the users who are currently waiting:")) + .AddModule(new SectionModuleBuilder().WithText("Pending Trades")) + .AddModule(new SectionModuleBuilder().WithText(msg)) + .Build(); + await ReplyCardAsync(card).ConfigureAwait(false); + } +} diff --git a/SysBot.Pokemon.Kook/Commands/Bots/QueueModule.cs b/SysBot.Pokemon.Kook/Commands/Bots/QueueModule.cs new file mode 100644 index 000000000..1df66cfb3 --- /dev/null +++ b/SysBot.Pokemon.Kook/Commands/Bots/QueueModule.cs @@ -0,0 +1,146 @@ +using Kook; +using Kook.Commands; +using PKHeX.Core; +using System.Threading.Tasks; + +namespace SysBot.Pokemon.Kook; + +[Summary("Clears and toggles Queue features.")] +public class QueueModule : ModuleBase where T : PKM, new() +{ + private static TradeQueueInfo Info => KookBot.Runner.Hub.Queues.Info; + + [Command("queueStatus")] + [Alias("qs", "ts")] + [Summary("Checks the user's position in the queue.")] + public async Task GetTradePositionAsync() + { + var msg = $"{Context.User.KMarkdownMention}" + " - " + Info.GetPositionString(Context.User.Id); + await ReplyTextAsync(msg).ConfigureAwait(false); + } + + [Command("queueClear")] + [Alias("qc", "tc")] + [Summary("Clears the user from the trade queues. Will not remove a user if they are being processed.")] + public async Task ClearTradeAsync() + { + string msg = ClearTrade(); + await ReplyTextAsync(msg).ConfigureAwait(false); + } + + [Command("queueClearUser")] + [Alias("qcu", "tcu")] + [Summary("Clears the user from the trade queues. Will not remove a user if they are being processed.")] + [RequireSudo] + public async Task ClearTradeUserAsync([Summary("Kook user ID")] ulong id) + { + string msg = ClearTrade(id); + await ReplyTextAsync(msg).ConfigureAwait(false); + } + + [Command("queueClearUser")] + [Alias("qcu", "tcu")] + [Summary("Clears the user from the trade queues. Will not remove a user if they are being processed.")] + [RequireSudo] + public async Task ClearTradeUserAsync([Summary("Username of the person to clear")] string _) + { + foreach (var user in Context.Message.MentionedUsers) + { + string msg = ClearTrade(user.Id); + await ReplyTextAsync(msg).ConfigureAwait(false); + } + } + + [Command("queueClearUser")] + [Alias("qcu", "tcu")] + [Summary("Clears the user from the trade queues. Will not remove a user if they are being processed.")] + [RequireSudo] + public async Task ClearTradeUserAsync() + { + var users = Context.Message.MentionedUsers; + if (users.Count == 0) + { + await ReplyTextAsync("No users mentioned").ConfigureAwait(false); + return; + } + foreach (var u in users) + await ClearTradeUserAsync(u.Id).ConfigureAwait(false); + } + + [Command("queueClearAll")] + [Alias("qca", "tca")] + [Summary("Clears all users from the trade queues.")] + [RequireSudo] + public async Task ClearAllTradesAsync() + { + Info.ClearAllQueues(); + await ReplyTextAsync("Cleared all in the queue.").ConfigureAwait(false); + } + + [Command("queueToggle")] + [Alias("qt", "tt")] + [Summary("Toggles on/off the ability to join the trade queue.")] + [RequireSudo] + public Task ToggleQueueTradeAsync() + { + var state = Info.ToggleQueue(); + var msg = state + ? "Users are now able to join the trade queue." + : "Changed queue settings: **Users CANNOT join the queue until it is turned back on.**"; + + return Context.Channel.EchoAndReply(msg); + } + + [Command("queueMode")] + [Alias("qm")] + [Summary("Changes how queueing is controlled (manual/threshold/interval).")] + [RequireSudo] + public async Task ChangeQueueModeAsync([Summary("Queue mode")] QueueOpening mode) + { + KookBot.Runner.Hub.Config.Queues.QueueToggleMode = mode; + await ReplyTextAsync($"Changed queue mode to {mode}.").ConfigureAwait(false); + } + + [Command("queueList")] + [Alias("ql")] + [Summary("Private messages the list of users in the queue.")] + [RequireSudo] + public async Task ListUserQueue() + { + var lines = KookBot.Runner.Hub.Queues.Info.GetUserList("(ID {0}) - Code: {1} - {2} - {3}"); + var msg = string.Join("\n", lines); + if (msg.Length < 3) + await ReplyTextAsync("Queue list is empty.").ConfigureAwait(false); + else + await Context.User.SendTextAsync(msg).ConfigureAwait(false); + } + + private string ClearTrade() + { + var userID = Context.User.Id; + return ClearTrade(userID); + } + + //private static string ClearTrade(string username) + //{ + // var result = Info.ClearTrade(username); + // return GetClearTradeMessage(result); + //} + + private static string ClearTrade(ulong userID) + { + var result = Info.ClearTrade(userID); + return GetClearTradeMessage(result); + } + + private static string GetClearTradeMessage(QueueResultRemove result) + { + return result switch + { + QueueResultRemove.CurrentlyProcessing => "Looks like you're currently being processed! Did not remove from all queues.", + QueueResultRemove.CurrentlyProcessingRemoved => "Looks like you're currently being processed!", + QueueResultRemove.Removed => "Removed you from the queue.", + _ => "Sorry, you are not currently in the queue.", + }; + } +} diff --git a/SysBot.Pokemon.Kook/Commands/Bots/RemoteControlModule.cs b/SysBot.Pokemon.Kook/Commands/Bots/RemoteControlModule.cs new file mode 100644 index 000000000..37f643d3d --- /dev/null +++ b/SysBot.Pokemon.Kook/Commands/Bots/RemoteControlModule.cs @@ -0,0 +1,145 @@ +using Kook.Commands; +using PKHeX.Core; +using SysBot.Base; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace SysBot.Pokemon.Kook; + +[Summary("Remotely controls a bot.")] +public class RemoteControlModule : ModuleBase where T : PKM, new() +{ + [Command("click")] + [Summary("Clicks the specified button.")] + [RequireRoleAccess(nameof(KookManager.RolesRemoteControl))] + public async Task ClickAsync(SwitchButton b) + { + var bot = KookBot.Runner.Bots.Find(z => IsRemoteControlBot(z.Bot)); + if (bot == null) + { + await ReplyTextAsync($"No bot is available to execute your command: {b}").ConfigureAwait(false); + return; + } + + await ClickAsyncImpl(b, bot).ConfigureAwait(false); + } + + [Command("click")] + [Summary("Clicks the specified button.")] + [RequireSudo] + public async Task ClickAsync(string ip, SwitchButton b) + { + var bot = KookBot.Runner.GetBot(ip); + if (bot == null) + { + await ReplyTextAsync($"No bot is available to execute your command: {b}").ConfigureAwait(false); + return; + } + + await ClickAsyncImpl(b, bot).ConfigureAwait(false); + } + + [Command("setStick")] + [Summary("Sets the stick to the specified position.")] + [RequireRoleAccess(nameof(KookManager.RolesRemoteControl))] + public async Task SetStickAsync(SwitchStick s, short x, short y, ushort ms = 1_000) + { + var bot = KookBot.Runner.Bots.Find(z => IsRemoteControlBot(z.Bot)); + if (bot == null) + { + await ReplyTextAsync($"No bot is available to execute your command: {s}").ConfigureAwait(false); + return; + } + + await SetStickAsyncImpl(s, x, y, ms, bot).ConfigureAwait(false); + } + + [Command("setStick")] + [Summary("Sets the stick to the specified position.")] + [RequireSudo] + public async Task SetStickAsync(string ip, SwitchStick s, short x, short y, ushort ms = 1_000) + { + var bot = KookBot.Runner.GetBot(ip); + if (bot == null) + { + await ReplyTextAsync($"No bot has that IP address ({ip}).").ConfigureAwait(false); + return; + } + + await SetStickAsyncImpl(s, x, y, ms, bot).ConfigureAwait(false); + } + + [Command("setScreenOn")] + [Alias("screenOn", "scrOn")] + [Summary("Turns the screen on")] + [RequireSudo] + public Task SetScreenOnAsync([Remainder] string ip) + { + return SetScreen(true, ip); + } + + [Command("setScreenOff")] + [Alias("screenOff", "scrOff")] + [Summary("Turns the screen off")] + [RequireSudo] + public Task SetScreenOffAsync([Remainder] string ip) + { + return SetScreen(false, ip); + } + + private async Task SetScreen(bool on, string ip) + { + var bot = GetBot(ip); + if (bot == null) + { + await ReplyTextAsync($"No bot has that IP address ({ip}).").ConfigureAwait(false); + return; + } + + var b = bot.Bot; + var crlf = b is SwitchRoutineExecutor { UseCRLF: true }; + await b.Connection.SendAsync(SwitchCommand.SetScreen(on ? ScreenState.On : ScreenState.Off, crlf), CancellationToken.None).ConfigureAwait(false); + await ReplyTextAsync("Screen state set to: " + (on ? "On" : "Off")).ConfigureAwait(false); + } + + private static BotSource? GetBot(string ip) + { + var r = KookBot.Runner; + return r.GetBot(ip) ?? r.Bots.Find(x => x.IsRunning); // safe fallback for users who mistype IP address for single bot instances + } + + private async Task ClickAsyncImpl(SwitchButton button, BotSource bot) + { + if (!Enum.IsDefined(button)) + { + await ReplyTextAsync($"Unknown button value: {button}").ConfigureAwait(false); + return; + } + + var b = bot.Bot; + var crlf = b is SwitchRoutineExecutor { UseCRLF: true }; + await b.Connection.SendAsync(SwitchCommand.Click(button, crlf), CancellationToken.None).ConfigureAwait(false); + await ReplyTextAsync($"{b.Connection.Name} has performed: {button}").ConfigureAwait(false); + } + + private async Task SetStickAsyncImpl(SwitchStick s, short x, short y, ushort ms, BotSource bot) + { + if (!Enum.IsDefined(s)) + { + await ReplyTextAsync($"Unknown stick: {s}").ConfigureAwait(false); + return; + } + + var b = bot.Bot; + var crlf = b is SwitchRoutineExecutor { UseCRLF: true }; + await b.Connection.SendAsync(SwitchCommand.SetStick(s, x, y, crlf), CancellationToken.None).ConfigureAwait(false); + await ReplyTextAsync($"{b.Connection.Name} has performed: {s}").ConfigureAwait(false); + await Task.Delay(ms).ConfigureAwait(false); + await b.Connection.SendAsync(SwitchCommand.ResetStick(s, crlf), CancellationToken.None).ConfigureAwait(false); + await ReplyTextAsync($"{b.Connection.Name} has reset the stick position.").ConfigureAwait(false); + } + + private static bool IsRemoteControlBot(RoutineExecutor botstate) + => botstate is RemoteControlBotSWSH or RemoteControlBotBS or RemoteControlBotLA or RemoteControlBotSV; +} diff --git a/SysBot.Pokemon.Kook/Commands/Bots/SeedCheckModule.cs b/SysBot.Pokemon.Kook/Commands/Bots/SeedCheckModule.cs new file mode 100644 index 000000000..8e3822c53 --- /dev/null +++ b/SysBot.Pokemon.Kook/Commands/Bots/SeedCheckModule.cs @@ -0,0 +1,84 @@ +using Kook; +using Kook.Commands; +using PKHeX.Core; +using System.Threading.Tasks; + +namespace SysBot.Pokemon.Kook; + +[Summary("Queues new Seed Check trades")] +public class SeedCheckModule : ModuleBase where T : PKM, new() +{ + private static TradeQueueInfo Info => KookBot.Runner.Hub.Queues.Info; + + [Command("seedCheck")] + [Alias("checkMySeed", "checkSeed", "seed", "s", "sc")] + [Summary("Checks the seed for a Pokémon.")] + [RequireQueueRole(nameof(KookManager.RolesSeed))] + public Task SeedCheckAsync(int code) + { + var sig = Context.User.GetFavor(); + return QueueHelper.AddToQueueAsync(Context, code, Context.User.Username, sig, new T(), PokeRoutineType.SeedCheck, PokeTradeType.Seed); + } + + [Command("seedCheck")] + [Alias("checkMySeed", "checkSeed", "seed", "s", "sc")] + [Summary("Checks the seed for a Pokémon.")] + [RequireQueueRole(nameof(KookManager.RolesSeed))] + public Task SeedCheckAsync([Summary("Trade Code")][Remainder] string code) + { + int tradeCode = Util.ToInt32(code); + var sig = Context.User.GetFavor(); + return QueueHelper.AddToQueueAsync(Context, tradeCode == 0 ? Info.GetRandomTradeCode() : tradeCode, Context.User.Username, sig, new T(), PokeRoutineType.SeedCheck, PokeTradeType.Seed); + } + + [Command("seedCheck")] + [Alias("checkMySeed", "checkSeed", "seed", "s", "sc")] + [Summary("Checks the seed for a Pokémon.")] + [RequireQueueRole(nameof(KookManager.RolesSeed))] + public Task SeedCheckAsync() + { + var code = Info.GetRandomTradeCode(); + return SeedCheckAsync(code); + } + + [Command("seedList")] + [Alias("sl", "scq", "seedCheckQueue", "seedQueue", "seedList")] + [Summary("Prints the users in the Seed Check queue.")] + [RequireSudo] + public async Task GetSeedListAsync() + { + string msg = Info.GetTradeList(PokeRoutineType.SeedCheck); + + var card = new CardBuilder() + .AddModule(new SectionModuleBuilder().WithText("These are the users who are currently waiting:")) + .AddModule(new SectionModuleBuilder().WithText("Pending Trades")) + .AddModule(new SectionModuleBuilder().WithText(msg)) + .Build(); + await ReplyCardAsync(card).ConfigureAwait(false); + } + + [Command("findFrame")] + [Alias("ff", "getFrameData")] + [Summary("Prints the next shiny frame from the provided seed.")] + public async Task FindFrameAsync([Remainder] string seedString) + { + var me = KookBot.Runner; + var hub = me.Hub; + + seedString = seedString.ToLower(); + if (seedString.StartsWith("0x")) + seedString = seedString[2..]; + + var seed = Util.GetHexValue64(seedString); + + var r = new SeedSearchResult(Z3SearchResult.Success, seed, -1, hub.Config.SeedCheckSWSH.ResultDisplayMode); + var msg = r.ToString(); + + var card = new CardBuilder() + .AddModule(new SectionModuleBuilder().WithText($"Here are the details for `{seed:X16}`:")) + .AddModule(new SectionModuleBuilder().WithText($"Seed: {seed:X16}")) + .AddModule(new SectionModuleBuilder().WithText(msg)) + .Build(); + await ReplyCardAsync(card).ConfigureAwait(false); + } +} diff --git a/SysBot.Pokemon.Kook/Commands/Bots/TradeModule.cs b/SysBot.Pokemon.Kook/Commands/Bots/TradeModule.cs new file mode 100644 index 000000000..eae8d281a --- /dev/null +++ b/SysBot.Pokemon.Kook/Commands/Bots/TradeModule.cs @@ -0,0 +1,248 @@ +using Kook; +using Kook.Commands; +using Kook.WebSocket; +using PKHeX.Core; +using SysBot.Base; +using System; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SysBot.Pokemon.Kook; + +[Summary("Queues new Link Code trades")] +public class TradeModule : ModuleBase where T : PKM, new() +{ + private static TradeQueueInfo Info => KookBot.Runner.Hub.Queues.Info; + + [Command("tradeList")] + [Alias("tl")] + [Summary("Prints the users in the trade queues.")] + [RequireSudo] + public async Task GetTradeListAsync() + { + string msg = Info.GetTradeList(PokeRoutineType.LinkTrade); + + var card = new CardBuilder() + .AddModule(new SectionModuleBuilder().WithText("These are the users who are currently waiting:")) + .AddModule(new SectionModuleBuilder().WithText("Pending Trades")) + .AddModule(new SectionModuleBuilder().WithText(msg)) + .Build(); + await ReplyCardAsync(card).ConfigureAwait(false); + } + + [Command("trade")] + [Alias("t")] + [Summary("Makes the bot trade you the provided Pokémon file.")] + [RequireQueueRole(nameof(KookManager.RolesTrade))] + public Task TradeAsyncAttach([Summary("Trade Code")] int code) + { + var sig = Context.User.GetFavor(); + return TradeAsyncAttach(code, sig, Context.User); + } + + [Command("trade")] + [Alias("t")] + [Summary("Makes the bot trade you a Pokémon converted from the provided Showdown Set.")] + [RequireQueueRole(nameof(KookManager.RolesTrade))] + public async Task TradeAsync([Summary("Trade Code")] int code, [Summary("Showdown Set")][Remainder] string content) + { + if (ShowdownTranslator.GameStringsZh.Species.Skip(1).Any(s => content.Contains(s))) + { + // If the content contains a Chinese Showdown Set, translate it to English. + content = ShowdownTranslator.Chinese2Showdown(content); + } + else + { + content = ReusableActions.StripCodeBlock(content); + } + + var set = new ShowdownSet(content); + var template = AutoLegalityWrapper.GetTemplate(set); + if (set.InvalidLines.Count != 0 || set.Species is 0) + { + var sb = new StringBuilder(128); + sb.AppendLine("Unable to parse Showdown Set."); + if (set.InvalidLines.Count != 0) + { + sb.AppendLine("Invalid lines detected:\n```"); + foreach (var line in set.InvalidLines) + sb.AppendLine(line); + sb.AppendLine("```"); + } + if (set.Species is 0) + sb.AppendLine("Species could not be identified. Check your spelling."); + + var msg = sb.ToString(); + await ReplyTextAsync(msg).ConfigureAwait(false); + return; + } + + try + { + var sav = AutoLegalityWrapper.GetTrainerInfo(); + var pkm = sav.GetLegal(template, out var result); + var la = new LegalityAnalysis(pkm); + var spec = GameInfo.Strings.Species[template.Species]; + pkm = EntityConverter.ConvertToType(pkm, typeof(T), out _) ?? pkm; + if (pkm is not T pk || !la.Valid) + { + var reason = result switch + { + "Timeout" => $"That {spec} set took too long to generate.", + "VersionMismatch" => "Request refused: PKHeX and Auto-Legality Mod version mismatch.", + _ => $"I wasn't able to create a {spec} from that set.", + }; + var imsg = $"Oops! {reason}"; + if (result == "Failed") + imsg += $"\n{AutoLegalityWrapper.GetLegalizationHint(template, sav, pkm)}"; + await ReplyTextAsync(imsg).ConfigureAwait(false); + return; + } + pk.ResetPartyStats(); + + var sig = Context.User.GetFavor(); + await AddTradeToQueueAsync(code, Context.User.Username, pk, sig, Context.User).ConfigureAwait(false); + } + catch (Exception ex) + { + LogUtil.LogSafe(ex, nameof(TradeModule)); + var msg = $"Oops! An unexpected problem happened with this Showdown Set:\n```{string.Join("\n", set.GetSetLines())}```"; + await ReplyTextAsync(msg).ConfigureAwait(false); + } + } + + [Command("trade")] + [Alias("t")] + [Summary("Makes the bot trade you a Pokémon converted from the provided Showdown Set.")] + [RequireQueueRole(nameof(KookManager.RolesTrade))] + public Task TradeAsync([Summary("Showdown Set")][Remainder] string content) + { + var code = Info.GetRandomTradeCode(); + return TradeAsync(code, content); + } + + [Command("trade")] + [Alias("t")] + [Summary("Makes the bot trade you the attached file.")] + [RequireQueueRole(nameof(KookManager.RolesTrade))] + public Task TradeAsyncAttach() + { + var code = Info.GetRandomTradeCode(); + return TradeAsyncAttach(code); + } + + [Command("banTrade")] + [Alias("bt")] + [RequireSudo] + public async Task BanTradeAsync([Summary("Online ID")] ulong nnid, string comment) + { + KookBotSettings.HubConfig.TradeAbuse.BannedIDs.AddIfNew([GetReference(nnid, comment)]); + await ReplyTextAsync("Done.").ConfigureAwait(false); + } + + private RemoteControlAccess GetReference(ulong id, string comment) => new() + { + ID = id, + Name = id.ToString(), + Comment = $"Added by {Context.User.Username} on {DateTime.Now:yyyy.MM.dd-hh:mm:ss} ({comment})", + }; + + [Command("tradeUser")] + [Alias("tu", "tradeOther")] + [Summary("Makes the bot trade the mentioned user the attached file.")] + [RequireSudo] + public async Task TradeAsyncAttachUser([Summary("Trade Code")] int code, [Remainder] string _) + { + if (Context.Message.MentionedUsers.Count > 1) + { + await ReplyTextAsync("Too many mentions. Queue one user at a time.").ConfigureAwait(false); + return; + } + + if (Context.Message.MentionedUsers.Count == 0) + { + await ReplyTextAsync("A user must be mentioned in order to do this.").ConfigureAwait(false); + return; + } + + var usr = Context.Message.MentionedUsers.ElementAt(0); + var sig = usr.GetFavor(); + await TradeAsyncAttach(code, sig, usr).ConfigureAwait(false); + } + + [Command("tradeUser")] + [Alias("tu", "tradeOther")] + [Summary("Makes the bot trade the mentioned user the attached file.")] + [RequireSudo] + public Task TradeAsyncAttachUser([Remainder] string _) + { + var code = Info.GetRandomTradeCode(); + return TradeAsyncAttachUser(code, _); + } + + private async Task TradeAsyncAttach(int code, RequestSignificance sig, SocketUser usr) + { + var attachment = Context.Message.Attachments.FirstOrDefault(); + if (attachment == default) + { + await ReplyTextAsync("No attachment provided!").ConfigureAwait(false); + return; + } + + var att = await NetUtil.DownloadPKMAsync(attachment).ConfigureAwait(false); + var pk = GetRequest(att); + if (pk == null) + { + await ReplyTextAsync("Attachment provided is not compatible with this module!").ConfigureAwait(false); + return; + } + + await AddTradeToQueueAsync(code, usr.Username, pk, sig, usr).ConfigureAwait(false); + } + + private static T? GetRequest(Download dl) + { + if (!dl.Success) + return null; + return dl.Data switch + { + null => null, + T pk => pk, + _ => EntityConverter.ConvertToType(dl.Data, typeof(T), out _) as T, + }; + } + + private async Task AddTradeToQueueAsync(int code, string trainerName, T pk, RequestSignificance sig, SocketUser usr) + { + if (!pk.CanBeTraded()) + { + // Disallow anything that cannot be traded from the game (e.g. Fusions). + await ReplyTextAsync("Provided Pokémon content is blocked from trading!").ConfigureAwait(false); + return; + } + + var cfg = Info.Hub.Config.Trade; + var la = new LegalityAnalysis(pk); + if (!la.Valid) + { + // Disallow trading illegal Pokémon. + await ReplyTextAsync($"{typeof(T).Name} attachment is not legal, and cannot be traded!").ConfigureAwait(false); + return; + } + if (cfg.DisallowNonNatives && (la.EncounterOriginal.Context != pk.Context || pk.GO)) + { + // Allow the owner to prevent trading entities that require a HOME Tracker even if the file has one already. + await ReplyTextAsync($"{typeof(T).Name} attachment is not native, and cannot be traded!").ConfigureAwait(false); + return; + } + if (cfg.DisallowTracked && pk is IHomeTrack { HasTracker: true }) + { + // Allow the owner to prevent trading entities that already have a HOME Tracker. + await ReplyTextAsync($"{typeof(T).Name} attachment is tracked by HOME, and cannot be traded!").ConfigureAwait(false); + return; + } + + await QueueHelper.AddToQueueAsync(Context, code, trainerName, sig, pk, PokeRoutineType.LinkTrade, PokeTradeType.Specific, usr).ConfigureAwait(false); + } +} diff --git a/SysBot.Pokemon.Kook/Commands/Extra/BatchEditingModule.cs b/SysBot.Pokemon.Kook/Commands/Extra/BatchEditingModule.cs new file mode 100644 index 000000000..21a88a60f --- /dev/null +++ b/SysBot.Pokemon.Kook/Commands/Extra/BatchEditingModule.cs @@ -0,0 +1,54 @@ +using Kook; +using Kook.Commands; +using PKHeX.Core; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace SysBot.Pokemon.Kook; + +// ReSharper disable once UnusedType.Global +public class BatchEditingModule : ModuleBase +{ + [Command("batchInfo"), Alias("bei")] + [Summary("Tries to get info about the requested property.")] + public async Task GetBatchInfo(string propertyName) + { + if (BatchEditing.TryGetPropertyType(propertyName, out var result)) + await ReplyTextAsync($"{propertyName}: {result}").ConfigureAwait(false); + else + await ReplyTextAsync($"Unable to find info for {propertyName}.").ConfigureAwait(false); + } + + [Command("batchValidate"), Alias("bev")] + [Summary("Tries to get info about the requested property.")] + public async Task ValidateBatchInfo(string instructions) + { + bool valid = IsValidInstructionSet(instructions, out var invalid); + + if (!valid) + { + var msg = invalid.Select(z => $"{z.PropertyName}, {z.PropertyValue}"); + await ReplyTextAsync($"Invalid Lines Detected:\r\n{Format.Code(string.Join(Environment.NewLine, msg))}") + .ConfigureAwait(false); + } + else + { + await ReplyTextAsync($"{invalid.Count} line(s) are invalid.").ConfigureAwait(false); + } + } + + private static bool IsValidInstructionSet(ReadOnlySpan split, out List invalid) + { + invalid = []; + var set = new StringInstructionSet(split); + foreach (var s in set.Filters.Concat(set.Instructions)) + { + if (!BatchEditing.TryGetPropertyType(s.PropertyName, out _)) + invalid.Add(s); + } + + return invalid.Count == 0; + } +} diff --git a/SysBot.Pokemon.Kook/Commands/Extra/LegalityCheckModule.cs b/SysBot.Pokemon.Kook/Commands/Extra/LegalityCheckModule.cs new file mode 100644 index 000000000..8d4f2afbd --- /dev/null +++ b/SysBot.Pokemon.Kook/Commands/Extra/LegalityCheckModule.cs @@ -0,0 +1,49 @@ +using Kook; +using Kook.Commands; +using PKHeX.Core; +using System.Threading.Tasks; + +namespace SysBot.Pokemon.Kook; + +public class LegalityCheckModule : ModuleBase +{ + [Command("lc"), Alias("check", "validate", "verify")] + [Summary("Verifies the attachment for legality.")] + public async Task LegalityCheck() + { + var attachments = Context.Message.Attachments; + foreach (var att in attachments) + await LegalityCheck(att, false).ConfigureAwait(false); + } + + [Command("lcv"), Alias("verbose")] + [Summary("Verifies the attachment for legality with a verbose output.")] + public async Task LegalityCheckVerbose() + { + var attachments = Context.Message.Attachments; + foreach (var att in attachments) + await LegalityCheck(att, true).ConfigureAwait(false); + } + + private async Task LegalityCheck(IAttachment att, bool verbose) + { + var download = await NetUtil.DownloadPKMAsync(att).ConfigureAwait(false); + if (!download.Success) + { + await ReplyTextAsync(download.ErrorMessage?? "No error message").ConfigureAwait(false); + return; + } + + var pkm = download.Data!; + var la = new LegalityAnalysis(pkm); + + var card = new CardBuilder() + .AddModule(new SectionModuleBuilder().WithText("Here's the legality report!")) + .AddModule(new SectionModuleBuilder().WithText($"Legality Report for {download.SanitizedFileName}:")) + .AddModule(new SectionModuleBuilder().WithText(la.Valid ? "Valid" : "Invalid")) + .AddModule(new SectionModuleBuilder().WithText(la.Report(verbose))) + .Build(); + + await ReplyCardAsync(card).ConfigureAwait(false); + } +} diff --git a/SysBot.Pokemon.Kook/Commands/Extra/LegalizerModule.cs b/SysBot.Pokemon.Kook/Commands/Extra/LegalizerModule.cs new file mode 100644 index 000000000..f570727a2 --- /dev/null +++ b/SysBot.Pokemon.Kook/Commands/Extra/LegalizerModule.cs @@ -0,0 +1,33 @@ +using Kook.Commands; +using PKHeX.Core; +using System.Threading.Tasks; + +namespace SysBot.Pokemon.Kook; + +public class LegalizerModule : ModuleBase where T : PKM, new() +{ + [Command("legalize"), Alias("alm")] + [Summary("Tries to legalize the attached pkm data.")] + public async Task LegalizeAsync() + { + var attachments = Context.Message.Attachments; + foreach (var att in attachments) + await Context.Channel.ReplyWithLegalizedSetAsync(att).ConfigureAwait(false); + } + + [Command("convert"), Alias("showdown")] + [Summary("Tries to convert the Showdown Set to pkm data.")] + [Priority(1)] + public Task ConvertShowdown([Summary("Generation/Format")] byte gen, [Remainder][Summary("Showdown Set")] string content) + { + return Context.Channel.ReplyWithLegalizedSetAsync(content, gen); + } + + [Command("convert"), Alias("showdown")] + [Summary("Tries to convert the Showdown Set to pkm data.")] + [Priority(0)] + public Task ConvertShowdown([Remainder][Summary("Showdown Set")] string content) + { + return Context.Channel.ReplyWithLegalizedSetAsync(content); + } +} diff --git a/SysBot.Pokemon.Kook/Commands/General/HelloModule.cs b/SysBot.Pokemon.Kook/Commands/General/HelloModule.cs new file mode 100644 index 000000000..6583e0799 --- /dev/null +++ b/SysBot.Pokemon.Kook/Commands/General/HelloModule.cs @@ -0,0 +1,18 @@ +using Kook; +using Kook.Commands; +using System.Threading.Tasks; + +namespace SysBot.Pokemon.Kook; + +public class HelloModule : ModuleBase +{ + [Command("hello")] + [Alias("hi")] + [Summary("Say hello to the bot and get a response.")] + public async Task PingAsync() + { + var str = KookBotSettings.Settings.HelloResponse; + var msg = string.Format(str, $"{Context.User.KMarkdownMention}"); + await ReplyTextAsync(msg).ConfigureAwait(false); + } +} diff --git a/SysBot.Pokemon.Kook/Commands/General/HelpModule.cs b/SysBot.Pokemon.Kook/Commands/General/HelpModule.cs new file mode 100644 index 000000000..d8beb52be --- /dev/null +++ b/SysBot.Pokemon.Kook/Commands/General/HelpModule.cs @@ -0,0 +1,102 @@ +using Kook; +using Kook.Commands; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace SysBot.Pokemon.Kook; + +public class HelpModule(CommandService Service) : ModuleBase +{ + [Command("help")] + [Summary("Lists available commands.")] + public async Task HelpAsync() + { + var builder = new CardBuilder() + .AddModule(new SectionModuleBuilder().WithText("Help has arrived!")) + .AddModule(new SectionModuleBuilder().WithText("These are the commands you can use:")); + + var mgr = KookBotSettings.Manager; + //var app = await Context.Client.GetApplicationInfoAsync().ConfigureAwait(false); + //var owner = app.Owner.Id; + var uid = Context.User.Id; + + foreach (var module in Service.Modules) + { + string? description = null; + HashSet mentioned = []; + foreach (var cmd in module.Commands) + { + var name = cmd.Name; + if (mentioned.Contains(name)) + continue; + //if (cmd.Attributes.Any(z => z is RequireOwnerAttribute) && owner != uid) + // continue; + if (cmd.Attributes.Any(z => z is RequireSudoAttribute) && !mgr.CanUseSudo(uid)) + continue; + + mentioned.Add(name); + var result = await cmd.CheckPreconditionsAsync(Context).ConfigureAwait(false); + if (result.IsSuccess) + description += $"{cmd.Aliases[0]}\n"; + } + if (string.IsNullOrWhiteSpace(description)) + continue; + + var moduleName = module.Name; + var gen = moduleName.IndexOf('`'); + if (gen != -1) + moduleName = moduleName[..gen]; + + builder.AddModule(new SectionModuleBuilder().WithText($"**{moduleName}**\n{description}")); + } + + await ReplyCardAsync(builder.Build()).ConfigureAwait(false); + } + + [Command("help")] + [Summary("Lists information about a specific command.")] + public async Task HelpAsync([Summary("The command you want help for")] string command) + { + var result = Service.Search(Context, command); + + if (!result.IsSuccess) + { + await ReplyTextAsync($"Sorry, I couldn't find a command like **{command}**.").ConfigureAwait(false); + return; + } + + var builder = new CardBuilder() + .AddModule(new SectionModuleBuilder().WithText("Help has arrived!")) + .AddModule(new SectionModuleBuilder().WithText($"Here are some commands like **{command}**:")); + + foreach (var match in result.Commands) + { + var cmd = match.Command; + + builder.AddModule(new SectionModuleBuilder().WithText($"**{string.Join(", ", cmd.Aliases)}**\n{GetCommandSummary(cmd)}")); + } + + await ReplyCardAsync(builder.Build()).ConfigureAwait(false); + } + + private static string GetCommandSummary(CommandInfo cmd) + { + return $"Summary: {cmd.Summary}\nParameters: {GetParameterSummary(cmd.Parameters)}"; + } + + private static string GetParameterSummary(IReadOnlyList p) + { + if (p.Count == 0) + return "None"; + return $"{p.Count}\n- " + string.Join("\n- ", p.Select(GetParameterSummary)); + } + + private static string GetParameterSummary(ParameterInfo z) + { + var result = z.Name; + if (!string.IsNullOrWhiteSpace(z.Summary)) + result += $" ({z.Summary})"; + return result; + } +} diff --git a/SysBot.Pokemon.Kook/Commands/General/InfoModule.cs b/SysBot.Pokemon.Kook/Commands/General/InfoModule.cs new file mode 100644 index 000000000..e49326fb3 --- /dev/null +++ b/SysBot.Pokemon.Kook/Commands/General/InfoModule.cs @@ -0,0 +1,79 @@ +using Kook; +using Kook.Commands; +using System; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Threading.Tasks; + +namespace SysBot.Pokemon.Kook; + +// src: https://github.com/foxbot/patek/blob/master/src/Patek/Modules/InfoModule.cs +// ISC License (ISC) +// Copyright 2017, Christopher F. +public class InfoModule : ModuleBase +{ + private const string detail = "I am an open-source Kook bot powered by PKHeX.Core and other open-source software."; + private const string repo = "https://github.com/kwsch/SysBot.NET"; + + [Command("info")] + [Alias("about", "whoami", "owner")] + public async Task InfoAsync() + { + //var app = await Context.Client.GetApplicationInfoAsync().ConfigureAwait(false); + + var builder = new CardBuilder() + .AddModule(new SectionModuleBuilder().WithText("Here's some information about me!")) + .AddModule(new SectionModuleBuilder().WithText(detail)); + + builder.AddModule(new SectionModuleBuilder().WithText("**Info**")) + .AddModule(new SectionModuleBuilder().WithText( + $"- [Source Code]({repo})\n" + + //$"- {Format.Bold("Owner")}: {app.Owner} ({app.Owner.Id})\n" + + $"- {Format.Bold("Library")}: Kook.Net ({KookConfig.Version})\n" + + $"- {Format.Bold("Uptime")}: {GetUptime()}\n" + + $"- {Format.Bold("Runtime")}: {RuntimeInformation.FrameworkDescription} {RuntimeInformation.ProcessArchitecture} " + + $"({RuntimeInformation.OSDescription} {RuntimeInformation.OSArchitecture})\n" + + $"- {Format.Bold("Buildtime")}: {GetVersionInfo("SysBot.Base", false)}\n" + + $"- {Format.Bold("Core Version")}: {GetVersionInfo("PKHeX.Core")}\n" + + $"- {Format.Bold("AutoLegality Version")}: {GetVersionInfo("PKHeX.Core.AutoMod")}\n" + )); + + builder.AddModule(new SectionModuleBuilder().WithText("**Stats**")) + .AddModule(new SectionModuleBuilder().WithText( + $"- {Format.Bold("Heap Size")}: {GetHeapSize()}MiB\n" + + $"- {Format.Bold("Guilds")}: {Context.Client.Guilds.Count}\n" + + $"- {Format.Bold("Channels")}: {Context.Client.Guilds.Sum(g => g.Channels.Count)}\n" + + $"- {Format.Bold("Users")}: {Context.Client.Guilds.Sum(g => g.MemberCount)}\n" + )); + + await ReplyCardAsync(builder.Build()).ConfigureAwait(false); + } + + private static string GetUptime() => (DateTime.Now - Process.GetCurrentProcess().StartTime).ToString(@"dd\.hh\:mm\:ss"); + private static string GetHeapSize() => Math.Round(GC.GetTotalMemory(true) / (1024.0 * 1024.0), 2).ToString(CultureInfo.CurrentCulture); + + private static string GetVersionInfo(string assemblyName, bool inclVersion = true) + { + const string _default = "Unknown"; + var assemblies = AppDomain.CurrentDomain.GetAssemblies(); + var assembly = Array.Find(assemblies, x => x.GetName().Name == assemblyName); + + var attribute = assembly?.GetCustomAttribute(); + if (attribute is null) + return _default; + + var info = attribute.InformationalVersion; + var split = info.Split('+'); + if (split.Length < 2) + return _default; + + var version = split[0]; + var revision = split[1]; + if (DateTime.TryParseExact(revision, "yyMMddHHmmss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var buildTime)) + return (inclVersion ? $"{version} " : "") + $@"{buildTime:yy-MM-dd\.hh\:mm}"; + return !inclVersion ? _default : version; + } +} diff --git a/SysBot.Pokemon.Kook/Commands/General/PingModule.cs b/SysBot.Pokemon.Kook/Commands/General/PingModule.cs new file mode 100644 index 000000000..3ea0fb03c --- /dev/null +++ b/SysBot.Pokemon.Kook/Commands/General/PingModule.cs @@ -0,0 +1,14 @@ +using Kook.Commands; +using System.Threading.Tasks; + +namespace SysBot.Pokemon.Kook; + +public class PingModule : ModuleBase +{ + [Command("ping")] + [Summary("Makes the bot respond, indicating that it is running.")] + public async Task PingAsync() + { + await ReplyTextAsync("Pong!").ConfigureAwait(false); + } +} diff --git a/SysBot.Pokemon.Kook/Commands/Management/BotModule.cs b/SysBot.Pokemon.Kook/Commands/Management/BotModule.cs new file mode 100644 index 000000000..a3bc6e761 --- /dev/null +++ b/SysBot.Pokemon.Kook/Commands/Management/BotModule.cs @@ -0,0 +1,124 @@ +using Kook; +using Kook.Commands; +using PKHeX.Core; +using System.Text; +using System.Threading.Tasks; + +namespace SysBot.Pokemon.Kook; + +// ReSharper disable once UnusedType.Global +public class BotModule : ModuleBase where T : PKM, new() +{ + [Command("botStatus")] + [Summary("Gets the status of the bots.")] + [RequireSudo] + public async Task GetStatusAsync() + { + var me = KookBot.Runner; + var sb = new StringBuilder(); + foreach (var bot in me.Bots) + { + if (bot.Bot is not PokeRoutineExecutorBase b) + continue; + sb.AppendLine(GetDetailedSummary(b)); + } + if (sb.Length == 0) + { + await ReplyTextAsync("No bots configured.").ConfigureAwait(false); + return; + } + await ReplyTextAsync(Format.Code(sb.ToString())).ConfigureAwait(false); + } + + private static string GetDetailedSummary(TBot z) where TBot: PokeRoutineExecutorBase + { + return $"- {z.Connection.Name} | {z.Connection.Label} - {z.Config.CurrentRoutineType} ~ {z.LastTime:hh:mm:ss} | {z.LastLogged}"; + } + + [Command("botStart")] + [Summary("Starts a bot by IP address/port.")] + [RequireSudo] + public async Task StartBotAsync(string ip) + { + var bot = KookBot.Runner.GetBot(ip); + if (bot == null) + { + await ReplyTextAsync($"No bot has that IP address ({ip}).").ConfigureAwait(false); + return; + } + + bot.Start(); + await Context.Channel.EchoAndReply($"The bot at {ip} ({bot.Bot.Connection.Label}) has been commanded to Start.").ConfigureAwait(false); + } + + [Command("botStop")] + [Summary("Stops a bot by IP address/port.")] + [RequireSudo] + public async Task StopBotAsync(string ip) + { + var bot = KookBot.Runner.GetBot(ip); + if (bot == null) + { + await ReplyTextAsync($"No bot has that IP address ({ip}).").ConfigureAwait(false); + return; + } + + bot.Stop(); + await Context.Channel.EchoAndReply($"The bot at {ip} ({bot.Bot.Connection.Label}) has been commanded to Stop.").ConfigureAwait(false); + } + + [Command("botIdle")] + [Alias("botPause")] + [Summary("Commands a bot to Idle by IP address/port.")] + [RequireSudo] + public async Task IdleBotAsync(string ip) + { + var bot = KookBot.Runner.GetBot(ip); + if (bot == null) + { + await ReplyTextAsync($"No bot has that IP address ({ip}).").ConfigureAwait(false); + return; + } + + bot.Pause(); + await Context.Channel.EchoAndReply($"The bot at {ip} ({bot.Bot.Connection.Label}) has been commanded to Idle.").ConfigureAwait(false); + } + + [Command("botChange")] + [Summary("Changes the routine of a bot (trades).")] + [RequireSudo] + public async Task ChangeTaskAsync(string ip, [Summary("Routine enum name")] PokeRoutineType task) + { + var bot = KookBot.Runner.GetBot(ip); + if (bot == null) + { + await ReplyTextAsync($"No bot has that IP address ({ip}).").ConfigureAwait(false); + return; + } + + bot.Bot.Config.Initialize(task); + await Context.Channel.EchoAndReply($"The bot at {ip} ({bot.Bot.Connection.Label}) has been commanded to do {task} as its next task.").ConfigureAwait(false); + } + + [Command("botRestart")] + [Summary("Restarts the bot(s) by IP address(es), separated by commas.")] + [RequireSudo] + public async Task RestartBotAsync(string ipAddressesCommaSeparated) + { + var ips = ipAddressesCommaSeparated.Split(','); + foreach (var ip in ips) + { + var bot = KookBot.Runner.GetBot(ip); + if (bot == null) + { + await ReplyTextAsync($"No bot has that IP address ({ip}).").ConfigureAwait(false); + return; + } + + var c = bot.Bot.Connection; + c.Reset(); + bot.Start(); + await Context.Channel.EchoAndReply($"The bot at {ip} ({c.Label}) has been commanded to Restart.").ConfigureAwait(false); + } + } +} diff --git a/SysBot.Pokemon.Kook/Commands/Management/EchoModule.cs b/SysBot.Pokemon.Kook/Commands/Management/EchoModule.cs new file mode 100644 index 000000000..d58e5fc6f --- /dev/null +++ b/SysBot.Pokemon.Kook/Commands/Management/EchoModule.cs @@ -0,0 +1,119 @@ +using Kook; +using Kook.Commands; +using Kook.WebSocket; +using SysBot.Base; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace SysBot.Pokemon.Kook; + +public class EchoModule : ModuleBase +{ + private class EchoChannel(ulong ChannelId, string ChannelName, Action Action) + { + public readonly ulong ChannelID = ChannelId; + public readonly string ChannelName = ChannelName; + public readonly Action Action = Action; + } + + private static readonly Dictionary Channels = []; + + public static void RestoreChannels(KookSocketClient Kook, KookSettings cfg) + { + foreach (var ch in cfg.EchoChannels) + { + if (Kook.GetChannel(ch.ID) is ISocketMessageChannel c) + AddEchoChannel(c, ch.ID); + } + + EchoUtil.Echo("Added echo notification to Kook channel(s) on Bot startup."); + } + + [Command("echoHere")] + [Summary("Makes the echo special messages to the channel.")] + [RequireSudo] + public async Task AddEchoAsync() + { + var c = Context.Channel; + var cid = c.Id; + if (Channels.TryGetValue(cid, out _)) + { + await ReplyTextAsync("Already notifying here.").ConfigureAwait(false); + return; + } + + AddEchoChannel(c, cid); + + // Add to Kook global loggers (saves on program close) + KookBotSettings.Settings.EchoChannels.AddIfNew([GetReference(Context.Channel)]); + await ReplyTextAsync("Added Echo output to this channel!").ConfigureAwait(false); + } + + private static void AddEchoChannel(ISocketMessageChannel c, ulong cid) + { + void Echo(string msg) => c.SendTextAsync(msg); + + Action l = Echo; + EchoUtil.Forwarders.Add(l); + var entry = new EchoChannel(cid, c.Name, l); + Channels.Add(cid, entry); + } + + public static bool IsEchoChannel(ISocketMessageChannel c) + { + var cid = c.Id; + return Channels.TryGetValue(cid, out _); + } + + [Command("echoInfo")] + [Summary("Dumps the special message (Echo) settings.")] + [RequireSudo] + public async Task DumpEchoInfoAsync() + { + foreach (var c in Channels) + await ReplyTextAsync($"{c.Key} - {c.Value}").ConfigureAwait(false); + } + + [Command("echoClear")] + [Summary("Clears the special message echo settings in that specific channel.")] + [RequireSudo] + public async Task ClearEchosAsync() + { + var id = Context.Channel.Id; + if (!Channels.TryGetValue(id, out var echo)) + { + await ReplyTextAsync("Not echoing in this channel.").ConfigureAwait(false); + return; + } + EchoUtil.Forwarders.Remove(echo.Action); + Channels.Remove(Context.Channel.Id); + KookBotSettings.Settings.EchoChannels.RemoveAll(z => z.ID == id); + await ReplyTextAsync($"Echoes cleared from channel: {Context.Channel.Name}").ConfigureAwait(false); + } + + [Command("echoClearAll")] + [Summary("Clears all the special message Echo channel settings.")] + [RequireSudo] + public async Task ClearEchosAllAsync() + { + foreach (var l in Channels) + { + var entry = l.Value; + await ReplyTextAsync($"Echoing cleared from {entry.ChannelName} ({entry.ChannelID}!").ConfigureAwait(false); + EchoUtil.Forwarders.Remove(entry.Action); + } + EchoUtil.Forwarders.RemoveAll(y => Channels.Select(x => x.Value.Action).Contains(y)); + Channels.Clear(); + KookBotSettings.Settings.EchoChannels.Clear(); + await ReplyTextAsync("Echoes cleared from all channels!").ConfigureAwait(false); + } + + private RemoteControlAccess GetReference(IChannel channel) => new() + { + ID = channel.Id, + Name = channel.Name, + Comment = $"Added by {Context.User.Username} on {DateTime.Now:yyyy.MM.dd-hh:mm:ss}", + }; +} diff --git a/SysBot.Pokemon.Kook/Commands/Management/EncounterModule.cs b/SysBot.Pokemon.Kook/Commands/Management/EncounterModule.cs new file mode 100644 index 000000000..e229c7fb6 --- /dev/null +++ b/SysBot.Pokemon.Kook/Commands/Management/EncounterModule.cs @@ -0,0 +1,27 @@ +using Kook.Commands; +using PKHeX.Core; +using System.Linq; +using System.Threading.Tasks; + +namespace SysBot.Pokemon.Kook; + +public class EchoModule : ModuleBase where T : PKM, new() +{ + [Command("toss")] + [Summary("Makes all bots that are currently waiting for a go-ahead continue operation.")] + [RequireSudo] + public async Task TossAsync(string name = "") + { + var bots = KookBot.Runner.Bots.Select(z => z.Bot); + foreach (var b in bots) + { + if (b is not IEncounterBot x) + continue; + if (!b.Connection.Name.Contains(name) && !b.Connection.Label.Contains(name)) + continue; + x.Acknowledge(); + } + + await ReplyTextAsync("Done.").ConfigureAwait(false); + } +} diff --git a/SysBot.Pokemon.Kook/Commands/Management/HubModule.cs b/SysBot.Pokemon.Kook/Commands/Management/HubModule.cs new file mode 100644 index 000000000..ac4e333a8 --- /dev/null +++ b/SysBot.Pokemon.Kook/Commands/Management/HubModule.cs @@ -0,0 +1,87 @@ +using Kook; +using Kook.Commands; +using PKHeX.Core; +using SysBot.Base; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace SysBot.Pokemon.Kook; + +public class HubModule : ModuleBase where T : PKM, new() +{ + [Command("status")] + [Alias("stats")] + [Summary("Gets the status of the bot environment.")] + public async Task GetStatusAsync() + { + var me = KookBot.Runner; + var hub = me.Hub; + + var builder = new CardBuilder(); + + var runner = KookBot.Runner; + var allBots = runner.Bots.ConvertAll(z => z.Bot); + var botCount = allBots.Count; + builder.AddModule(new SectionModuleBuilder().WithText("Bot Status")) + .AddModule(new SectionModuleBuilder().WithText("**Summary**")) + .AddModule(new SectionModuleBuilder().WithText($"Bot Count: {botCount}\n" + + $"Bot State: {SummarizeBots(allBots)}\n" + + $"Pool Count: {hub.Ledy.Pool.Count}\n")); + + var bots = allBots.OfType(); + var lines = bots.SelectMany(z => z.Counts.GetNonZeroCounts()).Distinct(); + var msg = string.Join("\n", lines); + if (string.IsNullOrWhiteSpace(msg)) + msg = "Nothing counted yet!"; + builder.AddModule(new SectionModuleBuilder().WithText("**Counts**")) + .AddModule(new SectionModuleBuilder().WithText(msg)); + + var queues = hub.Queues.AllQueues; + int count = 0; + foreach (var q in queues) + { + var c = q.Count; + if (c == 0) + continue; + + var nextMsg = GetNextName(q); + builder.AddModule(new SectionModuleBuilder().WithText($"**{q.Type} Queue**")) + .AddModule(new SectionModuleBuilder().WithText($"Next: {nextMsg}\n" + + $"Count: {c}")); + count += c; + } + + if (count == 0) + { + builder.AddModule(new SectionModuleBuilder().WithText("**Queues are empty.**")) + .AddModule(new SectionModuleBuilder().WithText("Nobody in line!")); + } + + await ReplyCardAsync(builder.Build()).ConfigureAwait(false); + } + + private static string GetNextName(PokeTradeQueue q) + { + var next = q.TryPeek(out var detail, out _); + if (!next) + return "None!"; + + var name = detail.Trainer.TrainerName; + + // show detail of trade if possible + var nick = detail.TradeData.Nickname; + if (!string.IsNullOrEmpty(nick)) + name += $" - {nick}"; + return name; + } + + private static string SummarizeBots(IReadOnlyCollection> bots) + { + if (bots.Count == 0) + return "No bots configured."; + var summaries = bots.Select(z => $"- {z.GetSummary()}"); + return Environment.NewLine + string.Join(Environment.NewLine, summaries); + } +} diff --git a/SysBot.Pokemon.Kook/Commands/Management/LogModule.cs b/SysBot.Pokemon.Kook/Commands/Management/LogModule.cs new file mode 100644 index 000000000..5d411f0f3 --- /dev/null +++ b/SysBot.Pokemon.Kook/Commands/Management/LogModule.cs @@ -0,0 +1,104 @@ +using Kook; +using Kook.Commands; +using Kook.WebSocket; +using SysBot.Base; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace SysBot.Pokemon.Kook; + +public class LogModule : ModuleBase +{ + private static readonly Dictionary Channels = []; + + public static void RestoreLogging(KookSocketClient Kook, KookSettings settings) + { + foreach (var ch in settings.LoggingChannels) + { + if (Kook.GetChannel(ch.ID) is ISocketMessageChannel c) + AddLogChannel(c, ch.ID); + } + + LogUtil.LogInfo("Added logging to Kook channel(s) on Bot startup.", "Kook"); + } + + [Command("logHere")] + [Summary("Makes the bot log to the channel.")] + [RequireSudo] + public async Task AddLogAsync() + { + var c = Context.Channel; + var cid = c.Id; + if (Channels.TryGetValue(cid, out _)) + { + await ReplyTextAsync("Already logging here.").ConfigureAwait(false); + return; + } + + AddLogChannel(c, cid); + + // Add to Kook global loggers (saves on program close) + KookBotSettings.Settings.LoggingChannels.AddIfNew([GetReference(Context.Channel)]); + await ReplyTextAsync("Added logging output to this channel!").ConfigureAwait(false); + } + + private static void AddLogChannel(ISocketMessageChannel c, ulong cid) + { + var logger = new ChannelLogger(cid, c); + LogUtil.Forwarders.Add(logger); + Channels.Add(cid, logger); + } + + [Command("logInfo")] + [Summary("Dumps the logging settings.")] + [RequireSudo] + public async Task DumpLogInfoAsync() + { + foreach (var c in Channels) + await ReplyTextAsync($"{c.Key} - {c.Value}").ConfigureAwait(false); + } + + [Command("logClear")] + [Summary("Clears the logging settings in that specific channel.")] + [RequireSudo] + public async Task ClearLogsAsync() + { + var id = Context.Channel.Id; + if (!Channels.TryGetValue(id, out var log)) + { + await ReplyTextAsync("Not echoing in this channel.").ConfigureAwait(false); + return; + } + LogUtil.Forwarders.Remove(log); + Channels.Remove(Context.Channel.Id); + KookBotSettings.Settings.LoggingChannels.RemoveAll(z => z.ID == id); + await ReplyTextAsync($"Logging cleared from channel: {Context.Channel.Name}").ConfigureAwait(false); + } + + [Command("logClearAll")] + [Summary("Clears all the logging settings.")] + [RequireSudo] + public async Task ClearLogsAllAsync() + { + foreach (var l in Channels) + { + var entry = l.Value; + await ReplyTextAsync($"Logging cleared from {entry.ChannelName} ({entry.ChannelID}!").ConfigureAwait(false); + LogUtil.Forwarders.Remove(entry); + } + + LogUtil.Forwarders.RemoveAll(y => Channels.Select(z => z.Value).Contains(y)); + Channels.Clear(); + KookBotSettings.Settings.LoggingChannels.Clear(); + await ReplyTextAsync("Logging cleared from all channels!").ConfigureAwait(false); + } + + private RemoteControlAccess GetReference(IChannel channel) => new() + { + ID = channel.Id, + Name = channel.Name, + Comment = $"Added by {Context.User.Username} on {DateTime.Now:yyyy.MM.dd-hh:mm:ss}", + }; +} diff --git a/SysBot.Pokemon.Kook/Commands/Management/OwnerModule.cs b/SysBot.Pokemon.Kook/Commands/Management/OwnerModule.cs new file mode 100644 index 000000000..02f0f478e --- /dev/null +++ b/SysBot.Pokemon.Kook/Commands/Management/OwnerModule.cs @@ -0,0 +1,128 @@ +using Kook; +using Kook.Commands; +using PKHeX.Core; +using System; +using System.Linq; +using System.Threading.Tasks; + +namespace SysBot.Pokemon.Kook; + +public class OwnerModule : SudoModule where T : PKM, new() +{ + [Command("addSudo")] + [Summary("Adds mentioned user to global sudo")] + [RequireOwner] + // ReSharper disable once UnusedParameter.Global + public async Task SudoUsers([Remainder] string _) + { + var users = Context.Message.MentionedUsers; + var objects = users.Select(GetReference); + KookBotSettings.Settings.GlobalSudoList.AddIfNew(objects); + await ReplyTextAsync("Done.").ConfigureAwait(false); + } + + [Command("removeSudo")] + [Summary("Removes mentioned user from global sudo")] + [RequireOwner] + // ReSharper disable once UnusedParameter.Global + public async Task RemoveSudoUsers([Remainder] string _) + { + var users = Context.Message.MentionedUsers; + var objects = users.Select(GetReference); + KookBotSettings.Settings.GlobalSudoList.RemoveAll(z => objects.Any(o => o.ID == z.ID)); + await ReplyTextAsync("Done.").ConfigureAwait(false); + } + + [Command("addChannel")] + [Summary("Adds a channel to the list of channels that are accepting commands.")] + [RequireOwner] + // ReSharper disable once UnusedParameter.Global + public async Task AddChannel() + { + var obj = GetReference(Context.Message.Channel); + KookBotSettings.Settings.ChannelWhitelist.AddIfNew([obj]); + await ReplyTextAsync("Done.").ConfigureAwait(false); + } + + [Command("removeChannel")] + [Summary("Removes a channel from the list of channels that are accepting commands.")] + [RequireOwner] + // ReSharper disable once UnusedParameter.Global + public async Task RemoveChannel() + { + var obj = GetReference(Context.Message.Channel); + KookBotSettings.Settings.ChannelWhitelist.RemoveAll(z => z.ID == obj.ID); + await ReplyTextAsync("Done.").ConfigureAwait(false); + } + + [Command("leave")] + [Alias("bye")] + [Summary("Leaves the current server.")] + [RequireOwner] + // ReSharper disable once UnusedParameter.Global + public async Task Leave() + { + await ReplyTextAsync("Goodbye.").ConfigureAwait(false); + await Context.Guild.LeaveAsync().ConfigureAwait(false); + } + + [Command("leaveguild")] + [Alias("lg")] + [Summary("Leaves guild based on supplied ID.")] + [RequireOwner] + // ReSharper disable once UnusedParameter.Global + public async Task LeaveGuild(string userInput) + { + if (!ulong.TryParse(userInput, out ulong id)) + { + await ReplyTextAsync("Please provide a valid Guild ID.").ConfigureAwait(false); + return; + } + + var guild = Context.Client.Guilds.FirstOrDefault(x => x.Id == id); + if (guild is null) + { + await ReplyTextAsync($"Provided input ({userInput}) is not a valid guild ID or the bot is not in the specified guild.").ConfigureAwait(false); + return; + } + + await ReplyTextAsync($"Leaving {guild}.").ConfigureAwait(false); + await guild.LeaveAsync().ConfigureAwait(false); + } + + [Command("leaveall")] + [Summary("Leaves all servers the bot is currently in.")] + [RequireOwner] + // ReSharper disable once UnusedParameter.Global + public async Task LeaveAll() + { + await ReplyTextAsync("Leaving all servers.").ConfigureAwait(false); + foreach (var guild in Context.Client.Guilds) + await guild.LeaveAsync().ConfigureAwait(false); + } + + [Command("sudoku")] + [Alias("kill", "shutdown")] + [Summary("Causes the entire process to end itself!")] + [RequireOwner] + // ReSharper disable once UnusedParameter.Global + public async Task ExitProgram() + { + await Context.Channel.EchoAndReply("Shutting down... goodbye! **Bot services are going offline.**").ConfigureAwait(false); + Environment.Exit(0); + } + + private RemoteControlAccess GetReference(IUser channel) => new() + { + ID = channel.Id, + Name = channel.Username, + Comment = $"Added by {Context.User.Username} on {DateTime.Now:yyyy.MM.dd-hh:mm:ss}", + }; + + private RemoteControlAccess GetReference(IChannel channel) => new() + { + ID = channel.Id, + Name = channel.Name, + Comment = $"Added by {Context.User.Username} on {DateTime.Now:yyyy.MM.dd-hh:mm:ss}", + }; +} diff --git a/SysBot.Pokemon.Kook/Commands/Management/PoolModule.cs b/SysBot.Pokemon.Kook/Commands/Management/PoolModule.cs new file mode 100644 index 000000000..046ff0ad3 --- /dev/null +++ b/SysBot.Pokemon.Kook/Commands/Management/PoolModule.cs @@ -0,0 +1,51 @@ +using Kook; +using Kook.Commands; +using PKHeX.Core; +using System.Linq; +using System.Threading.Tasks; + +namespace SysBot.Pokemon.Kook; + +[Summary("Distribution Pool Module")] +public class PoolModule : ModuleBase where T : PKM, new() +{ + [Command("poolReload")] + [Summary("Reloads the bot pool from the setting's folder.")] + [RequireSudo] + public async Task ReloadPoolAsync() + { + var me = KookBot.Runner; + var hub = me.Hub; + + var pool = hub.Ledy.Pool.Reload(hub.Config.Folder.DistributeFolder); + if (!pool) + await ReplyTextAsync("Failed to reload from folder.").ConfigureAwait(false); + else + await ReplyTextAsync($"Reloaded from folder. Pool count: {hub.Ledy.Pool.Count}").ConfigureAwait(false); + } + + [Command("pool")] + [Summary("Displays the details of Pokémon files in the random pool.")] + public async Task DisplayPoolCountAsync() + { + var me = KookBot.Runner; + var hub = me.Hub; + var pool = hub.Ledy.Pool; + var count = pool.Count; + if (count is > 0 and < 20) + { + var lines = pool.Files.Select((z, i) => $"{i + 1:00}: {z.Key} = {(Species)z.Value.RequestInfo.Species}"); + var msg = string.Join("\n", lines); + + var card = new CardBuilder().AddModule(new SectionModuleBuilder().WithText("Pool Details")) + .AddModule(new SectionModuleBuilder().WithText($"Count: {count}")) + .AddModule(new SectionModuleBuilder().WithText(msg)) + .Build(); + await ReplyCardAsync(card).ConfigureAwait(false); + } + else + { + await ReplyTextAsync($"Pool Count: {count}").ConfigureAwait(false); + } + } +} diff --git a/SysBot.Pokemon.Kook/Commands/Management/SudoModule.cs b/SysBot.Pokemon.Kook/Commands/Management/SudoModule.cs new file mode 100644 index 000000000..3769add32 --- /dev/null +++ b/SysBot.Pokemon.Kook/Commands/Management/SudoModule.cs @@ -0,0 +1,205 @@ +using Kook; +using Kook.Commands; +using PKHeX.Core; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace SysBot.Pokemon.Kook; + +public class SudoModule : ModuleBase where T : PKM, new() +{ + [Command("blacklist")] + [Summary("Blacklists a mentioned Kook user.")] + [RequireSudo] + // ReSharper disable once UnusedParameter.Global + public async Task BlackListUsers([Remainder] string _) + { + var users = Context.Message.MentionedUsers; + var objects = users.Select(GetReference); + KookBotSettings.Settings.UserBlacklist.AddIfNew(objects); + await ReplyTextAsync("Done.").ConfigureAwait(false); + } + + [Command("blacklistComment")] + [Summary("Adds a comment for a blacklisted Kook user ID.")] + [RequireSudo] + // ReSharper disable once UnusedParameter.Global + public async Task BlackListUsers(ulong id, [Remainder] string comment) + { + var obj = KookBotSettings.Settings.UserBlacklist.List.Find(z => z.ID == id); + if (obj is null) + { + await ReplyTextAsync($"Unable to find a user with that ID ({id}).").ConfigureAwait(false); + return; + } + + var oldComment = obj.Comment; + obj.Comment = comment; + await ReplyTextAsync($"Done. Changed existing comment ({oldComment}) to ({comment}).").ConfigureAwait(false); + } + + [Command("unblacklist")] + [Summary("Removes a mentioned Kook user from the blacklist.")] + [RequireSudo] + // ReSharper disable once UnusedParameter.Global + public async Task UnBlackListUsers([Remainder] string _) + { + var users = Context.Message.MentionedUsers; + var objects = users.Select(GetReference); + KookBotSettings.Settings.UserBlacklist.RemoveAll(z => objects.Any(o => o.ID == z.ID)); + await ReplyTextAsync("Done.").ConfigureAwait(false); + } + + [Command("blacklistId")] + [Summary("Blacklists Kook user IDs. (Useful if user is not in the server).")] + [RequireSudo] + public async Task BlackListIDs([Summary("Comma Separated Kook IDs")][Remainder] string content) + { + var IDs = GetIDs(content); + var objects = IDs.Select(GetReference); + KookBotSettings.Settings.UserBlacklist.AddIfNew(objects); + await ReplyTextAsync("Done.").ConfigureAwait(false); + } + + [Command("unBlacklistId")] + [Summary("Removes Kook user IDs from the blacklist. (Useful if user is not in the server).")] + [RequireSudo] + public async Task UnBlackListIDs([Summary("Comma Separated Kook IDs")][Remainder] string content) + { + var IDs = GetIDs(content); + KookBotSettings.Settings.UserBlacklist.RemoveAll(z => IDs.Any(o => o == z.ID)); + await ReplyTextAsync("Done.").ConfigureAwait(false); + } + + [Command("blacklistSummary")] + [Alias("printBlacklist", "blacklistPrint")] + [Summary("Prints the list of blacklisted Kook users.")] + [RequireSudo] + public async Task PrintBlacklist() + { + var lines = KookBotSettings.Settings.UserBlacklist.Summarize(); + var msg = string.Join("\n", lines); + await ReplyTextAsync(Format.Code(msg)).ConfigureAwait(false); + } + + [Command("banID")] + [Summary("Bans online user IDs.")] + [RequireSudo] + public async Task BanOnlineIDs([Summary("Comma Separated Online IDs")][Remainder] string content) + { + var IDs = GetIDs(content); + var objects = IDs.Select(GetReference); + + var me = KookBot.Runner; + var hub = me.Hub; + hub.Config.TradeAbuse.BannedIDs.AddIfNew(objects); + await ReplyTextAsync("Done.").ConfigureAwait(false); + } + + [Command("bannedIDComment")] + [Summary("Adds a comment for a banned online user ID.")] + [RequireSudo] + public async Task BanOnlineIDs(ulong id, [Remainder] string comment) + { + var me = KookBot.Runner; + var hub = me.Hub; + var obj = hub.Config.TradeAbuse.BannedIDs.List.Find(z => z.ID == id); + if (obj is null) + { + await ReplyTextAsync($"Unable to find a user with that online ID ({id}).").ConfigureAwait(false); + return; + } + + var oldComment = obj.Comment; + obj.Comment = comment; + await ReplyTextAsync($"Done. Changed existing comment ({oldComment}) to ({comment}).").ConfigureAwait(false); + } + + [Command("unbanID")] + [Summary("Bans online user IDs.")] + [RequireSudo] + public async Task UnBanOnlineIDs([Summary("Comma Separated Online IDs")][Remainder] string content) + { + var IDs = GetIDs(content); + var me = KookBot.Runner; + var hub = me.Hub; + hub.Config.TradeAbuse.BannedIDs.RemoveAll(z => IDs.Any(o => o == z.ID)); + await ReplyTextAsync("Done.").ConfigureAwait(false); + } + + [Command("bannedIDSummary")] + [Alias("printBannedID", "bannedIDPrint")] + [Summary("Prints the list of banned online IDs.")] + [RequireSudo] + public async Task PrintBannedOnlineIDs() + { + var me = KookBot.Runner; + var hub = me.Hub; + var lines = hub.Config.TradeAbuse.BannedIDs.Summarize(); + var msg = string.Join("\n", lines); + await ReplyTextAsync(Format.Code(msg)).ConfigureAwait(false); + } + + [Command("forgetUser")] + [Alias("forget")] + [Summary("Forgets users that were previously encountered.")] + [RequireSudo] + public async Task ForgetPreviousUser([Summary("Comma Separated Online IDs")][Remainder] string content) + { + var IDs = GetIDs(content); + foreach (var ID in IDs) + { + PokeRoutineExecutorBase.PreviousUsers.RemoveAllNID(ID); + PokeRoutineExecutorBase.PreviousUsersDistribution.RemoveAllNID(ID); + } + await ReplyTextAsync("Done.").ConfigureAwait(false); + } + + [Command("previousUserSummary")] + [Alias("prevUsers")] + [Summary("Prints a list of previously encountered users.")] + [RequireSudo] + public async Task PrintPreviousUsers() + { + bool found = false; + var lines = PokeRoutineExecutorBase.PreviousUsers.Summarize().ToList(); + if (lines.Count != 0) + { + found = true; + var msg = "Previous Users:\n" + string.Join("\n", lines); + await ReplyTextAsync(Format.Code(msg)).ConfigureAwait(false); + } + + lines = PokeRoutineExecutorBase.PreviousUsersDistribution.Summarize().ToList(); + if (lines.Count != 0) + { + found = true; + var msg = "Previous Distribution Users:\n" + string.Join("\n", lines); + await ReplyTextAsync(Format.Code(msg)).ConfigureAwait(false); + } + if (!found) + await ReplyTextAsync("No previous users found.").ConfigureAwait(false); + } + + private RemoteControlAccess GetReference(IUser channel) => new() + { + ID = channel.Id, + Name = channel.Username, + Comment = $"Added by {Context.User.Username} on {DateTime.Now:yyyy.MM.dd-hh:mm:ss}", + }; + + private RemoteControlAccess GetReference(ulong id) => new() + { + ID = id, + Name = "Manual", + Comment = $"Added by {Context.User.Username} on {DateTime.Now:yyyy.MM.dd-hh:mm:ss}", + }; + + protected static IEnumerable GetIDs(string content) + { + return content.Split([",", ", ", " "], StringSplitOptions.RemoveEmptyEntries) + .Select(z => ulong.TryParse(z, out var x) ? x : 0).Where(z => z != 0); + } +} diff --git a/SysBot.Pokemon.Kook/Commands/Management/TradeStartModule.cs b/SysBot.Pokemon.Kook/Commands/Management/TradeStartModule.cs new file mode 100644 index 000000000..5dfab1763 --- /dev/null +++ b/SysBot.Pokemon.Kook/Commands/Management/TradeStartModule.cs @@ -0,0 +1,124 @@ +using Kook; +using Kook.Commands; +using Kook.WebSocket; +using PKHeX.Core; +using SysBot.Base; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace SysBot.Pokemon.Kook; + +public class TradeStartModule : ModuleBase where T : PKM, new() +{ + private class TradeStartAction(ulong ChannelId, Action> messager, string channel) + : ChannelAction>(ChannelId, messager, channel); + + private static readonly Dictionary Channels = []; + + private static void Remove(TradeStartAction entry) + { + Channels.Remove(entry.ChannelID); + KookBot.Runner.Hub.Queues.Forwarders.Remove(entry.Action); + } + +#pragma warning disable RCS1158 // Static member in generic type should use a type parameter. + public static void RestoreTradeStarting(KookSocketClient Kook) + { + var cfg = KookBotSettings.Settings; + foreach (var ch in cfg.TradeStartingChannels) + { + if (Kook.GetChannel(ch.ID) is ISocketMessageChannel c) + AddLogChannel(c, ch.ID); + } + + LogUtil.LogInfo("Added Trade Start Notification to Kook channel(s) on Bot startup.", "Kook"); + } + + public static bool IsStartChannel(ulong cid) +#pragma warning restore RCS1158 // Static member in generic type should use a type parameter. + { + return Channels.TryGetValue(cid, out _); + } + + [Command("startHere")] + [Summary("Makes the bot log trade starts to the channel.")] + [RequireSudo] + public async Task AddLogAsync() + { + var c = Context.Channel; + var cid = c.Id; + if (Channels.TryGetValue(cid, out _)) + { + await ReplyTextAsync("Already logging here.").ConfigureAwait(false); + return; + } + + AddLogChannel(c, cid); + + // Add to Kook global loggers (saves on program close) + KookBotSettings.Settings.TradeStartingChannels.AddIfNew([GetReference(Context.Channel)]); + await ReplyTextAsync("Added Start Notification output to this channel!").ConfigureAwait(false); + } + + private static void AddLogChannel(ISocketMessageChannel c, ulong cid) + { + void Logger(PokeRoutineExecutorBase bot, PokeTradeDetail detail) + { + if (detail.Type == PokeTradeType.Random) + return; + c.SendTextAsync(GetMessage(bot, detail)); + } + + Action> l = Logger; + KookBot.Runner.Hub.Queues.Forwarders.Add(l); + static string GetMessage(PokeRoutineExecutorBase bot, PokeTradeDetail detail) => $"> [{DateTime.Now:hh:mm:ss}] - {bot.Connection.Label} is now trading (ID {detail.ID}) {detail.Trainer.TrainerName}"; + + var entry = new TradeStartAction(cid, l, c.Name); + Channels.Add(cid, entry); + } + + [Command("startInfo")] + [Summary("Dumps the Start Notification settings.")] + [RequireSudo] + public async Task DumpLogInfoAsync() + { + foreach (var c in Channels) + await ReplyTextAsync($"{c.Key} - {c.Value}").ConfigureAwait(false); + } + + [Command("startClear")] + [Summary("Clears the Start Notification settings in that specific channel.")] + [RequireSudo] + public async Task ClearLogsAsync() + { + var cfg = KookBotSettings.Settings; + if (Channels.TryGetValue(Context.Channel.Id, out var entry)) + Remove(entry); + cfg.TradeStartingChannels.RemoveAll(z => z.ID == Context.Channel.Id); + await ReplyTextAsync($"Start Notifications cleared from channel: {Context.Channel.Name}").ConfigureAwait(false); + } + + [Command("startClearAll")] + [Summary("Clears all the Start Notification settings.")] + [RequireSudo] + public async Task ClearLogsAllAsync() + { + foreach (var l in Channels) + { + var entry = l.Value; + await ReplyTextAsync($"Logging cleared from {entry.ChannelName} ({entry.ChannelID}!").ConfigureAwait(false); + KookBot.Runner.Hub.Queues.Forwarders.Remove(entry.Action); + } + Channels.Clear(); + KookBotSettings.Settings.TradeStartingChannels.Clear(); + await ReplyTextAsync("Start Notifications cleared from all channels!").ConfigureAwait(false); + } + + private RemoteControlAccess GetReference(IChannel channel) => new() + { + ID = channel.Id, + Name = channel.Name, + Comment = $"Added by {Context.User.Username} on {DateTime.Now:yyyy.MM.dd-hh:mm:ss}", + }; +} diff --git a/SysBot.Pokemon.Kook/Helpers/AutoLegalityExtensionsKook.cs b/SysBot.Pokemon.Kook/Helpers/AutoLegalityExtensionsKook.cs new file mode 100644 index 000000000..37b8962a9 --- /dev/null +++ b/SysBot.Pokemon.Kook/Helpers/AutoLegalityExtensionsKook.cs @@ -0,0 +1,96 @@ +using Kook; +using Kook.WebSocket; +using PKHeX.Core; +using SysBot.Base; +using System; +using System.Threading.Tasks; + +namespace SysBot.Pokemon.Kook; + +public static class AutoLegalityExtensionsKook +{ + public static async Task ReplyWithLegalizedSetAsync(this ISocketMessageChannel channel, ITrainerInfo sav, ShowdownSet set) + { + if (set.Species == 0) + { + await channel.SendTextAsync("Oops! I wasn't able to interpret your message! If you intended to convert something, please double check what you're pasting!").ConfigureAwait(false); + return; + } + + try + { + var template = AutoLegalityWrapper.GetTemplate(set); + var pkm = sav.GetLegal(template, out var result); + var la = new LegalityAnalysis(pkm); + var spec = GameInfo.Strings.Species[template.Species]; + if (!la.Valid) + { + var reason = result switch + { + "Timeout" => $"That {spec} set took too long to generate.", + "VersionMismatch" => "Request refused: PKHeX and Auto-Legality Mod version mismatch.", + _ => $"I wasn't able to create a {spec} from that set.", + }; + var imsg = $"Oops! {reason}"; + if (result == "Failed") + imsg += $"\n{AutoLegalityWrapper.GetLegalizationHint(template, sav, pkm)}"; + await channel.SendTextAsync(imsg).ConfigureAwait(false); + return; + } + + var msg = $"Here's your ({result}) legalized PKM for {spec} ({la.EncounterOriginal.Name})!"; + await channel.SendPKMAsync(pkm, msg + $"\n{ReusableActions.GetFormattedShowdownText(pkm)}").ConfigureAwait(false); + } + catch (Exception ex) + { + LogUtil.LogSafe(ex, nameof(AutoLegalityExtensionsKook)); + var msg = $"Oops! An unexpected problem happened with this Showdown Set:\n```{string.Join("\n", set.GetSetLines())}```"; + await channel.SendTextAsync(msg).ConfigureAwait(false); + } + } + + public static Task ReplyWithLegalizedSetAsync(this ISocketMessageChannel channel, string content, byte gen) + { + content = ReusableActions.StripCodeBlock(content); + var set = new ShowdownSet(content); + var sav = AutoLegalityWrapper.GetTrainerInfo(gen); + return channel.ReplyWithLegalizedSetAsync(sav, set); + } + + public static Task ReplyWithLegalizedSetAsync(this ISocketMessageChannel channel, string content) where T : PKM, new() + { + content = ReusableActions.StripCodeBlock(content); + var set = new ShowdownSet(content); + var sav = AutoLegalityWrapper.GetTrainerInfo(); + return channel.ReplyWithLegalizedSetAsync(sav, set); + } + + public static async Task ReplyWithLegalizedSetAsync(this ISocketMessageChannel channel, IAttachment att) + { + var download = await NetUtil.DownloadPKMAsync(att).ConfigureAwait(false); + if (!download.Success) + { + await channel.SendTextAsync(download.ErrorMessage).ConfigureAwait(false); + return; + } + + var pkm = download.Data!; + if (new LegalityAnalysis(pkm).Valid) + { + await channel.SendTextAsync($"{download.SanitizedFileName}: Already legal.").ConfigureAwait(false); + return; + } + + var legal = pkm.LegalizePokemon(); + if (!new LegalityAnalysis(legal).Valid) + { + await channel.SendTextAsync($"{download.SanitizedFileName}: Unable to legalize.").ConfigureAwait(false); + return; + } + + legal.RefreshChecksum(); + + var msg = $"Here's your legalized PKM for {download.SanitizedFileName}!\n{ReusableActions.GetFormattedShowdownText(legal)}"; + await channel.SendPKMAsync(legal, msg).ConfigureAwait(false); + } +} diff --git a/SysBot.Pokemon.Kook/Helpers/ChannelAction.cs b/SysBot.Pokemon.Kook/Helpers/ChannelAction.cs new file mode 100644 index 000000000..0c5a759b0 --- /dev/null +++ b/SysBot.Pokemon.Kook/Helpers/ChannelAction.cs @@ -0,0 +1,10 @@ +using System; + +namespace SysBot.Pokemon.Kook; + +public class ChannelAction(ulong ChannelID, Action Messager, string ChannelName) +{ + public readonly ulong ChannelID = ChannelID; + public readonly string ChannelName = ChannelName; + public readonly Action Action = Messager; +} diff --git a/SysBot.Pokemon.Kook/Helpers/ChannelLogger.cs b/SysBot.Pokemon.Kook/Helpers/ChannelLogger.cs new file mode 100644 index 000000000..82d24e4f4 --- /dev/null +++ b/SysBot.Pokemon.Kook/Helpers/ChannelLogger.cs @@ -0,0 +1,26 @@ +using System; +using Kook.WebSocket; +using SysBot.Base; + +namespace SysBot.Pokemon.Kook; + +public class ChannelLogger(ulong ChannelID, ISocketMessageChannel Channel) : ILogForwarder +{ + public ulong ChannelID { get; } = ChannelID; + public string ChannelName => Channel.Name; + + public void Forward(string message, string identity) + { + try + { + var text = GetMessage(message, identity); + Channel.SendTextAsync(text); + } + catch (Exception ex) + { + LogUtil.LogSafe(ex, identity); + } + } + private static string GetMessage(ReadOnlySpan msg, string identity) + => $"> [{DateTime.Now:hh:mm:ss}] - {identity}: {msg}"; +} diff --git a/SysBot.Pokemon.Kook/Helpers/KookManager.cs b/SysBot.Pokemon.Kook/Helpers/KookManager.cs new file mode 100644 index 000000000..9a9f4ad8b --- /dev/null +++ b/SysBot.Pokemon.Kook/Helpers/KookManager.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace SysBot.Pokemon.Kook; + +public class KookManager(KookSettings Config) +{ + public readonly KookSettings Config = Config; + public ulong Owner { get; internal set; } + + public RemoteControlAccessList BlacklistedUsers => Config.UserBlacklist; + public RemoteControlAccessList WhitelistedChannels => Config.ChannelWhitelist; + + public RemoteControlAccessList SudoKook => Config.GlobalSudoList; + public RemoteControlAccessList SudoRoles => Config.RoleSudo; + public RemoteControlAccessList FavoredRoles => Config.RoleFavored; + + public RemoteControlAccessList RolesClone => Config.RoleCanClone; + public RemoteControlAccessList RolesTrade => Config.RoleCanTrade; + public RemoteControlAccessList RolesSeed => Config.RoleCanSeedCheck; + public RemoteControlAccessList RolesDump => Config.RoleCanDump; + public RemoteControlAccessList RolesRemoteControl => Config.RoleRemoteControl; + + public bool CanUseSudo(ulong uid) => SudoKook.Contains(uid); + public bool CanUseSudo(IEnumerable roles) => roles.Any(SudoRoles.Contains); + + public bool CanUseCommandChannel(ulong channel) => (WhitelistedChannels.List.Count == 0 && WhitelistedChannels.AllowIfEmpty) || WhitelistedChannels.Contains(channel); + public bool CanUseCommandUser(ulong uid) => !BlacklistedUsers.Contains(uid); + + public RequestSignificance GetSignificance(IEnumerable roles) + { + var result = RequestSignificance.None; + foreach (var r in roles) + { + if (SudoRoles.Contains(r)) + result = RequestSignificance.Favored; + if (FavoredRoles.Contains(r)) + result = RequestSignificance.Favored; + } + return result; + } + + public bool GetHasRoleAccess(string type, IEnumerable roles) + { + var set = GetSet(type); + return set is { AllowIfEmpty: true, List.Count: 0 } || roles.Any(set.Contains); + } + + private RemoteControlAccessList GetSet(string type) => type switch + { + nameof(RolesClone) => RolesClone, + nameof(RolesTrade) => RolesTrade, + nameof(RolesSeed) => RolesSeed, + nameof(RolesDump) => RolesDump, + nameof(RolesRemoteControl) => RolesRemoteControl, + _ => throw new ArgumentOutOfRangeException(nameof(type)), + }; +} diff --git a/SysBot.Pokemon.Kook/Helpers/KookTradeNotifier.cs b/SysBot.Pokemon.Kook/Helpers/KookTradeNotifier.cs new file mode 100644 index 000000000..92e32e629 --- /dev/null +++ b/SysBot.Pokemon.Kook/Helpers/KookTradeNotifier.cs @@ -0,0 +1,86 @@ +using Kook; +using Kook.WebSocket; +using PKHeX.Core; +using SysBot.Pokemon.Kook; +using System; +using System.Linq; + +namespace SysBot.Pokemon.Kook; + +public class KookTradeNotifier(T Data, PokeTradeTrainerInfo Info, int Code, SocketUser Trader) + : IPokeTradeNotifier + where T : PKM, new() +{ + private T Data { get; } = Data; + private PokeTradeTrainerInfo Info { get; } = Info; + private int Code { get; } = Code; + private SocketUser Trader { get; } = Trader; + public Action>? OnFinish { private get; set; } + public readonly PokeTradeHub Hub = KookBot.Runner.Hub; + + public void TradeInitialize(PokeRoutineExecutor routine, PokeTradeDetail info) + { + var receive = Data.Species == 0 ? string.Empty : $" ({Data.Nickname})"; + Trader.SendTextAsync($"Initializing trade{receive}. Please be ready. Your code is **{Code:0000 0000}**.").ConfigureAwait(false); + } + + public void TradeSearching(PokeRoutineExecutor routine, PokeTradeDetail info) + { + var name = Info.TrainerName; + var trainer = string.IsNullOrEmpty(name) ? string.Empty : $", {name}"; + Trader.SendTextAsync($"I'm waiting for you{trainer}! Your code is **{Code:0000 0000}**. My IGN is **{routine.InGameName}**.").ConfigureAwait(false); + } + + public void TradeCanceled(PokeRoutineExecutor routine, PokeTradeDetail info, PokeTradeResult msg) + { + OnFinish?.Invoke(routine); + Trader.SendTextAsync($"Trade canceled: {msg}").ConfigureAwait(false); + } + + public void TradeFinished(PokeRoutineExecutor routine, PokeTradeDetail info, T result) + { + OnFinish?.Invoke(routine); + var tradedToUser = Data.Species; + var message = tradedToUser != 0 ? $"Trade finished. Enjoy your {(Species)tradedToUser}!" : "Trade finished!"; + Trader.SendTextAsync(message).ConfigureAwait(false); + if (result.Species != 0 && Hub.Config.Kook.ReturnPKMs) + Trader.SendPKMAsync(result, "Here's what you traded me!").ConfigureAwait(false); + } + + public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, string message) + { + Trader.SendTextAsync(message).ConfigureAwait(false); + } + + public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, PokeTradeSummary message) + { + if (message.ExtraInfo is SeedSearchResult r) + { + SendNotificationZ3(r); + return; + } + + var msg = message.Summary; + if (message.Details.Count > 0) + msg += ", " + string.Join(", ", message.Details.Select(z => $"{z.Heading}: {z.Detail}")); + Trader.SendTextAsync(msg).ConfigureAwait(false); + } + + public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, T result, string message) + { + if (result.Species != 0 && (Hub.Config.Kook.ReturnPKMs || info.Type == PokeTradeType.Dump)) + Trader.SendPKMAsync(result, message).ConfigureAwait(false); + } + + private void SendNotificationZ3(SeedSearchResult r) + { + var lines = r.ToString(); + + var card = new CardBuilder() + .AddModule(new SectionModuleBuilder().WithText($"Here are the details for `{r.Seed:X16}`:")) + .AddModule(new SectionModuleBuilder().WithText($"Seed: {r.Seed:X16}")) + .AddModule(new SectionModuleBuilder().WithText(lines)) + .Build(); + Trader.SendCardAsync(card).ConfigureAwait(false); + } +} diff --git a/SysBot.Pokemon.Kook/Helpers/NetUtil.cs b/SysBot.Pokemon.Kook/Helpers/NetUtil.cs new file mode 100644 index 000000000..4c22eaee0 --- /dev/null +++ b/SysBot.Pokemon.Kook/Helpers/NetUtil.cs @@ -0,0 +1,49 @@ +using Kook; +using PKHeX.Core; +using System.Net.Http; +using System.Threading.Tasks; + +namespace SysBot.Pokemon.Kook; + +public static class NetUtil +{ + public static async Task DownloadFromUrlAsync(string url) + { + using var client = new HttpClient(); + return await client.GetByteArrayAsync(url).ConfigureAwait(false); + } + + public static async Task> DownloadPKMAsync(IAttachment att) + { + var result = new Download { SanitizedFileName = Format.Sanitize(att.Filename) }; + if (!EntityDetection.IsSizePlausible(att.Size ?? 0)) + { + result.ErrorMessage = $"{result.SanitizedFileName}: Invalid size."; + return result; + } + + string url = att.Url; + + // Download the resource and load the bytes into a buffer. + var buffer = await DownloadFromUrlAsync(url).ConfigureAwait(false); + var prefer = EntityFileExtension.GetContextFromExtension(result.SanitizedFileName); + var pkm = EntityFormat.GetFromBytes(buffer, prefer); + if (pkm == null) + { + result.ErrorMessage = $"{result.SanitizedFileName}: Invalid pkm attachment."; + return result; + } + + result.Data = pkm; + result.Success = true; + return result; + } +} + +public sealed class Download where T : class +{ + public bool Success; + public T? Data; + public string? SanitizedFileName; + public string? ErrorMessage; +} diff --git a/SysBot.Pokemon.Kook/Helpers/QueueHelper.cs b/SysBot.Pokemon.Kook/Helpers/QueueHelper.cs new file mode 100644 index 000000000..d34546d02 --- /dev/null +++ b/SysBot.Pokemon.Kook/Helpers/QueueHelper.cs @@ -0,0 +1,141 @@ +using Kook; +using Kook.Commands; +using Kook.Net; +using Kook.WebSocket; +using PKHeX.Core; +using System.Threading.Tasks; + +namespace SysBot.Pokemon.Kook; + +public static class QueueHelper where T : PKM, new() +{ + private const uint MaxTradeCode = 9999_9999; + + public static async Task AddToQueueAsync(SocketCommandContext context, int code, string trainer, RequestSignificance sig, T trade, PokeRoutineType routine, PokeTradeType type, SocketUser trader) + { + if ((uint)code > MaxTradeCode) + { + await context.Channel.SendTextAsync("Trade code should be 00000000-99999999!").ConfigureAwait(false); + return; + } + + try + { + const string helper = "I've added you to the queue! I'll message you here when your trade is starting."; + var test = await trader.SendTextAsync(helper).ConfigureAwait(false); + + // Try adding + var result = AddToTradeQueue(context, trade, code, trainer, sig, routine, type, trader, out var msg); + + // Notify in channel + await context.Channel.SendTextAsync(msg).ConfigureAwait(false); + // Notify in PM to mirror what is said in the channel. + await trader.SendTextAsync($"{msg}\nYour trade code will be **{code:0000 0000}**.").ConfigureAwait(false); + + // Clean Up + if (result) + { + // Delete the user's join message for privacy + if (!context.IsPrivate) + await context.Message.DeleteAsync(RequestOptions.Default).ConfigureAwait(false); + } + else + { + // Delete our "I'm adding you!", and send the same message that we sent to the general channel. + // KOOK.Net 中,无法删除私信消息,直接忽略此步骤 + // await test.DeleteAsync().ConfigureAwait(false); + } + } + catch (HttpException ex) + { + await HandleKookExceptionAsync(context, trader, ex).ConfigureAwait(false); + } + } + + public static Task AddToQueueAsync(SocketCommandContext context, int code, string trainer, RequestSignificance sig, T trade, PokeRoutineType routine, PokeTradeType type) + { + return AddToQueueAsync(context, code, trainer, sig, trade, routine, type, context.User); + } + + private static bool AddToTradeQueue(SocketCommandContext context, T pk, int code, string trainerName, RequestSignificance sig, PokeRoutineType type, PokeTradeType t, SocketUser trader, out string msg) + { + var user = trader; + var userID = user.Id; + var name = user.Username; + + var trainer = new PokeTradeTrainerInfo(trainerName, userID); + var notifier = new KookTradeNotifier(pk, trainer, code, user); + var detail = new PokeTradeDetail(pk, trainer, notifier, t, code, sig == RequestSignificance.Favored); + var trade = new TradeEntry(detail, userID, type, name); + + var hub = KookBot.Runner.Hub; + var Info = hub.Queues.Info; + var added = Info.AddToTradeQueue(trade, userID, sig == RequestSignificance.Owner); + + if (added == QueueResultAdd.AlreadyInQueue) + { + msg = "Sorry, you are already in the queue."; + return false; + } + + var position = Info.CheckPosition(userID, type); + + var ticketID = ""; + if (TradeStartModule.IsStartChannel(context.Channel.Id)) + ticketID = $", unique ID: {detail.ID}"; + + var pokeName = ""; + if (t == PokeTradeType.Specific && pk.Species != 0) + pokeName = $" Receiving: {GameInfo.GetStrings("en").Species[pk.Species]}."; + msg = $"{user.KMarkdownMention} - Added to the {type} queue{ticketID}. Current Position: {position.Position}.{pokeName}"; + + var botct = Info.Hub.Bots.Count; + if (position.Position > botct) + { + var eta = Info.Hub.Config.Queues.EstimateDelay(position.Position, botct); + msg += $" Estimated: {eta:F1} minutes."; + } + return true; + } + + private static async Task HandleKookExceptionAsync(SocketCommandContext context, SocketUser trader, HttpException ex) + { + string message = string.Empty; + switch (ex.KookCode) + { + case KookErrorCode.MissingPermissions: + { + // Check if the exception was raised due to missing "Send Messages" or "Manage Messages" permissions. Nag the bot owner if so. + var permissions = context.Guild.CurrentUser.GetPermissions(context.Channel as IGuildChannel); + if (!permissions.SendMessages) + { + // Nag the owner in logs. + message = "You must grant me \"Send Messages\" permissions!"; + Base.LogUtil.LogError(message, "QueueHelper"); + return; + } + if (!permissions.ManageMessages) + { + //var app = await context.Client.GetApplicationInfoAsync().ConfigureAwait(false); + //var owner = app.Owner.Id; + var owner = KookBotSettings.Manager.Owner; + message = $"{MentionUtils.KMarkdownMentionUser(owner)} You must grant me \"Manage Messages\" permissions!"; + } + } + break; + //case KookErrorCode.CannotSendMessageToUser: + //{ + // // The user either has DMs turned off, or Kook thinks they do. + // message = context.User == trader ? "You must enable private messages in order to be queued!" : "The mentioned user must enable private messages in order for them to be queued!"; + //} + // break; + default: + { + // Send a generic error message. + message = ex.KookCode != null ? $"Kook error {(int)ex.KookCode}: {ex.Reason}" : $"Http error {(int)ex.HttpCode}: {ex.Message}"; + } + break; + } + await context.Channel.SendTextAsync(message).ConfigureAwait(false); + } +} diff --git a/SysBot.Pokemon.Kook/Helpers/RequireOwnerAttribute.cs b/SysBot.Pokemon.Kook/Helpers/RequireOwnerAttribute.cs new file mode 100644 index 000000000..47a3eea75 --- /dev/null +++ b/SysBot.Pokemon.Kook/Helpers/RequireOwnerAttribute.cs @@ -0,0 +1,22 @@ +using Kook.Commands; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SysBot.Pokemon.Kook; + +public class RequireOwnerAttribute : PreconditionAttribute +{ + public override async Task CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services) + { + // 你的Bot主人ID(可配置到配置文件) + ulong ownerId = KookBotSettings.Manager.Owner; // 替换为你的 KOOK 用户ID + + if (context.User.Id == ownerId) + return PreconditionResult.FromSuccess(); + else + return PreconditionResult.FromError("只有Bot主人可以使用此命令。"); + } +} diff --git a/SysBot.Pokemon.Kook/Helpers/RequireQueueRoleAttribute.cs b/SysBot.Pokemon.Kook/Helpers/RequireQueueRoleAttribute.cs new file mode 100644 index 000000000..a5af1ff56 --- /dev/null +++ b/SysBot.Pokemon.Kook/Helpers/RequireQueueRoleAttribute.cs @@ -0,0 +1,41 @@ +using Kook.Commands; +using Kook.WebSocket; +using System; +using System.Linq; +using System.Threading.Tasks; + +namespace SysBot.Pokemon.Kook; + +/// +/// Same as with extra consideration for bots accepting Queue requests. +/// +public sealed class RequireQueueRoleAttribute(string RoleName) : PreconditionAttribute +{ + // Create a field to store the specified name + + // Create a constructor so the name can be specified + + public override Task CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services) + { + var mgr = KookBotSettings.Manager; + if (mgr.Config.AllowGlobalSudo && mgr.CanUseSudo(context.User.Id)) + return Task.FromResult(PreconditionResult.FromSuccess()); + + // Check if this user is a Guild User, which is the only context where roles exist + if (context.User is not SocketGuildUser gUser) + return Task.FromResult(PreconditionResult.FromError("You must be sending the message from a guild to run this command.")); + + var roles = gUser.Roles; + if (mgr.CanUseSudo(roles.Select(z => z.Name))) + return Task.FromResult(PreconditionResult.FromSuccess()); + + bool canQueue = KookBotSettings.HubConfig.Queues.CanQueue; + if (!canQueue) + return Task.FromResult(PreconditionResult.FromError("Sorry, I am not currently accepting queue requests!")); + + if (!mgr.GetHasRoleAccess(RoleName, roles.Select(z => z.Name))) + return Task.FromResult(PreconditionResult.FromError("You do not have the required role to run this command.")); + + return Task.FromResult(PreconditionResult.FromSuccess()); + } +} diff --git a/SysBot.Pokemon.Kook/Helpers/RequireRoleAccessAttribute.cs b/SysBot.Pokemon.Kook/Helpers/RequireRoleAccessAttribute.cs new file mode 100644 index 000000000..432815687 --- /dev/null +++ b/SysBot.Pokemon.Kook/Helpers/RequireRoleAccessAttribute.cs @@ -0,0 +1,37 @@ +using Kook.Commands; +using Kook.WebSocket; +using System; +using System.Linq; +using System.Threading.Tasks; + +namespace SysBot.Pokemon.Kook; + +/// +/// Requires an assigned role in order to accept commands. Can be used by sudo users if satisfied. +/// +public sealed class RequireRoleAccessAttribute(string RoleName) : PreconditionAttribute +{ + // Create a field to store the specified name + + // Create a constructor so the name can be specified + + public override Task CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services) + { + var mgr = KookBotSettings.Manager; + if (mgr.Config.AllowGlobalSudo && mgr.CanUseSudo(context.User.Id)) + return Task.FromResult(PreconditionResult.FromSuccess()); + + // Check if this user is a Guild User, which is the only context where roles exist + if (context.User is not SocketGuildUser gUser) + return Task.FromResult(PreconditionResult.FromError("You must be sending the message from a guild to run this command.")); + + var roles = gUser.Roles; + if (mgr.CanUseSudo(roles.Select(z => z.Name))) + return Task.FromResult(PreconditionResult.FromSuccess()); + + if (!mgr.GetHasRoleAccess(RoleName, roles.Select(z => z.Name))) + return Task.FromResult(PreconditionResult.FromError("You do not have the required role to run this command.")); + + return Task.FromResult(PreconditionResult.FromSuccess()); + } +} diff --git a/SysBot.Pokemon.Kook/Helpers/RequireRoleAttribute.cs b/SysBot.Pokemon.Kook/Helpers/RequireRoleAttribute.cs new file mode 100644 index 000000000..9d8d10d68 --- /dev/null +++ b/SysBot.Pokemon.Kook/Helpers/RequireRoleAttribute.cs @@ -0,0 +1,31 @@ +using Kook.Commands; +using Kook.WebSocket; +using System; +using System.Linq; +using System.Threading.Tasks; + +namespace SysBot.Pokemon.Kook; + +public sealed class RequireRoleAttribute(string RoleName) : PreconditionAttribute +{ + // Create a field to store the specified name + + // Create a constructor so the name can be specified + + // Override the CheckPermissions method + public override Task CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services) + { + // Since no async work is done, the result has to be wrapped with `Task.FromResult` to avoid compiler errors + + // Check if this user is a Guild User, which is the only context where roles exist + if (context.User is not SocketGuildUser gUser) + return Task.FromResult(PreconditionResult.FromError("You must be in a guild to run this command.")); + + // If this command was executed by a user with the appropriate role, return a success + if (gUser.Roles.Any(r => r.Name == RoleName)) + return Task.FromResult(PreconditionResult.FromSuccess()); + + // Since it wasn't, fail + return Task.FromResult(PreconditionResult.FromError($"You must have a role named {RoleName} to run this command.")); + } +} diff --git a/SysBot.Pokemon.Kook/Helpers/RequireSudoAttribute.cs b/SysBot.Pokemon.Kook/Helpers/RequireSudoAttribute.cs new file mode 100644 index 000000000..c7b239fdf --- /dev/null +++ b/SysBot.Pokemon.Kook/Helpers/RequireSudoAttribute.cs @@ -0,0 +1,28 @@ +using Kook.Commands; +using Kook.WebSocket; +using System; +using System.Linq; +using System.Threading.Tasks; + +namespace SysBot.Pokemon.Kook; + +public sealed class RequireSudoAttribute : PreconditionAttribute +{ + // Override the CheckPermissions method + public override Task CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services) + { + var mgr = KookBotSettings.Manager; + if (mgr.Config.AllowGlobalSudo && mgr.CanUseSudo(context.User.Id)) + return Task.FromResult(PreconditionResult.FromSuccess()); + + // Check if this user is a Guild User, which is the only context where roles exist + if (context.User is not SocketGuildUser gUser) + return Task.FromResult(PreconditionResult.FromError("You must be in a guild to run this command.")); + + if (mgr.CanUseSudo(gUser.Roles.Select(z => z.Name))) + return Task.FromResult(PreconditionResult.FromSuccess()); + + // Since it wasn't, fail + return Task.FromResult(PreconditionResult.FromError("You are not permitted to run this command.")); + } +} diff --git a/SysBot.Pokemon.Kook/Helpers/ReusableActions.cs b/SysBot.Pokemon.Kook/Helpers/ReusableActions.cs new file mode 100644 index 000000000..ad8f1f711 --- /dev/null +++ b/SysBot.Pokemon.Kook/Helpers/ReusableActions.cs @@ -0,0 +1,88 @@ +using Kook; +using Kook.WebSocket; +using PKHeX.Core; +using SysBot.Base; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace SysBot.Pokemon.Kook; + +public static class ReusableActions +{ + public static async Task SendPKMAsync(this IMessageChannel channel, PKM pkm, string msg = "") + { + var tmp = Path.Combine(Path.GetTempPath(), Util.CleanFileName(pkm.FileName)); + await File.WriteAllBytesAsync(tmp, pkm.DecryptedPartyData); + await channel.SendFileAsync(tmp, msg).ConfigureAwait(false); + File.Delete(tmp); + } + + public static async Task SendPKMAsync(this IUser user, PKM pkm, string msg = "") + { + var tmp = Path.Combine(Path.GetTempPath(), Util.CleanFileName(pkm.FileName)); + await File.WriteAllBytesAsync(tmp, pkm.DecryptedPartyData); + await user.SendFileAsync(tmp, msg).ConfigureAwait(false); + File.Delete(tmp); + } + + public static async Task RepostPKMAsShowdownAsync(this ISocketMessageChannel channel, IAttachment att) + { + if (!EntityDetection.IsSizePlausible(att.Size??0)) + return; + var result = await NetUtil.DownloadPKMAsync(att).ConfigureAwait(false); + if (!result.Success) + return; + + var pkm = result.Data!; + await channel.SendPKMAsShowdownSetAsync(pkm).ConfigureAwait(false); + } + + public static RequestSignificance GetFavor(this IUser user) + { + var mgr = KookBotSettings.Manager; + if (user.Id == mgr.Owner) + return RequestSignificance.Owner; + if (mgr.CanUseSudo(user.Id)) + return RequestSignificance.Favored; + if (user is SocketGuildUser g) + return mgr.GetSignificance(g.Roles.Select(z => z.Name)); + return RequestSignificance.None; + } + + public static async Task EchoAndReply(this ISocketMessageChannel channel, string msg) + { + // Announce it in the channel the command was entered only if it's not already an echo channel. + EchoUtil.Echo(msg); + if (!EchoModule.IsEchoChannel(channel)) + await channel.SendTextAsync(msg).ConfigureAwait(false); + } + + public static async Task SendPKMAsShowdownSetAsync(this ISocketMessageChannel channel, PKM pkm) + { + var txt = GetFormattedShowdownText(pkm); + await channel.SendTextAsync(txt).ConfigureAwait(false); + } + + public static string GetFormattedShowdownText(PKM pkm) + { + var showdown = ShowdownParsing.GetShowdownText(pkm); + return Format.Code(showdown); + } + + private static readonly string[] separator = [ ",", ", ", " " ]; + + public static IReadOnlyList GetListFromString(string str) + { + // Extract comma separated list + return str.Split(separator, StringSplitOptions.RemoveEmptyEntries); + } + + public static string StripCodeBlock(string str) => str + .Replace("`\n", "") + .Replace("\n`", "") + .Replace("`", "") + .Trim(); +} diff --git a/SysBot.Pokemon.Kook/KookBot.cs b/SysBot.Pokemon.Kook/KookBot.cs new file mode 100644 index 000000000..09083106d --- /dev/null +++ b/SysBot.Pokemon.Kook/KookBot.cs @@ -0,0 +1,268 @@ +using Kook; +using Kook.Commands; +using Kook.WebSocket; +using Microsoft.Extensions.DependencyInjection; +using PKHeX.Core; +using SysBot.Base; +using System.Reflection; +using System.Text.RegularExpressions; + +namespace SysBot.Pokemon.Kook; + +public static class KookBotSettings +{ + public static KookManager Manager { get; internal set; } = default!; + public static KookSettings Settings => Manager.Config; + public static PokeTradeHubConfig HubConfig { get; internal set; } = default!; +} + +public sealed class KookBot where T : PKM, new() +{ + public static PokeBotRunner Runner { get; private set; } = default!; + private readonly KookSocketClient _client; + private readonly KookManager Manager; + public readonly PokeTradeHub Hub; + + private readonly CommandService _commands; + private readonly IServiceProvider _services; + private bool MessageChannelsLoaded { get; set; } + public KookBot(PokeBotRunner runner) { + Runner = runner; + Hub = runner.Hub; + Manager = new KookManager(Hub.Config.Kook); + + KookBotSettings.Manager = Manager; + KookBotSettings.HubConfig = Hub.Config; + + _client = new KookSocketClient(new KookSocketConfig + { + LogLevel = LogSeverity.Info, + MessageCacheSize = 1000, // Adjust as needed + AlwaysDownloadUsers = true, // Download user data for commands + DefaultRetryMode = RetryMode.AlwaysRetry, + }); + + _commands = new CommandService(new CommandServiceConfig + { + DefaultRunMode = Hub.Config.Kook.AsyncCommands ? RunMode.Async : RunMode.Sync, + LogLevel = LogSeverity.Info, + CaseSensitiveCommands = false, + }); + + _client.Log += Log; + _commands.Log += Log; + + _services = ConfigServices(); + } + + private static ServiceProvider ConfigServices() + { + var map = new ServiceCollection(); + return map.BuildServiceProvider(); + } + + private static Task Log(LogMessage msg) + { + var text = $"[{msg.Severity,8}] {msg.Source}: {msg.Message} {msg.Exception}"; + Console.ForegroundColor = GetTextColor(msg.Severity); + Console.WriteLine($"{DateTime.Now,-19} {text}"); + Console.ResetColor(); + + LogUtil.LogText($"KookBot: {text}"); + + return Task.CompletedTask; + } + + private static ConsoleColor GetTextColor(LogSeverity sv) => sv switch + { + LogSeverity.Critical => ConsoleColor.Red, + LogSeverity.Error => ConsoleColor.Red, + + LogSeverity.Warning => ConsoleColor.Yellow, + LogSeverity.Info => ConsoleColor.White, + + LogSeverity.Verbose => ConsoleColor.DarkGray, + LogSeverity.Debug => ConsoleColor.DarkGray, + _ => Console.ForegroundColor, + }; + + public async Task MainAsync(string apiToken, CancellationToken token) + { + await InitCommands().ConfigureAwait(false); + + await _client.LoginAsync(TokenType.Bot, apiToken).ConfigureAwait(false); + await _client.StartAsync().ConfigureAwait(false); + LogUtil.LogInfo("Kook Bot started successfully.", "KookBot"); + + var guilds = await _client.Rest.GetGuildsAsync().ConfigureAwait(false); + if (guilds.Count != 0) + { + var guild = guilds.First(); + var owner = await guild.GetOwnerAsync(); + Manager.Owner = owner.Id; + LogUtil.LogInfo($"Set owner {owner.Id} from guild: {guild.Name} (ID: {guild.Id})", "KookBot"); + } + else + { + LogUtil.LogError("No guilds found. Ensure the bot has been added to at least one guild.", "KookBot"); + } + // Wait infinitely so your bot actually stays connected. + await MonitorLogIntervalAsync(token).ConfigureAwait(false); + } + + public async Task InitCommands() + { + var assembly = Assembly.GetExecutingAssembly(); + + await _commands.AddModulesAsync(assembly, _services).ConfigureAwait(false); + var genericTypes = assembly.DefinedTypes.Where(z => z.IsSubclassOf(typeof(ModuleBase)) && z.IsGenericType); + foreach (var t in genericTypes) + { + var genModule = t.MakeGenericType(typeof(T)); + try + { + await _commands.AddModuleAsync(genModule, _services).ConfigureAwait(false); + } catch (Exception ex) + { + LogUtil.LogError($"Failed to add module {genModule.Name}: {ex.Message}", "KookBot"); + // Optionally, you can log the exception or handle it as needed + } + } + var modules = _commands.Modules.ToList(); + + var blacklist = Hub.Config.Kook.ModuleBlacklist + .Replace("Module", "").Split(',', StringSplitOptions.RemoveEmptyEntries) + .Select(z => z.Trim()).ToList(); + + foreach (var module in modules) + { + var name = module.Name; + name = name.Replace("Module", ""); + var gen = name.IndexOf('`'); + if (gen != -1) + name = name[..gen]; + if (blacklist.Any(z => z.Equals(name, StringComparison.OrdinalIgnoreCase))) + await _commands.RemoveModuleAsync(module).ConfigureAwait(false); + } + + // Subscribe a handler to see if a message invokes a command. + _client.Ready += LoadLoggingAndEcho; + _client.MessageReceived += HandleMessageAsync; + } + + private async Task HandleMessageAsync(SocketMessage arg, SocketGuildUser user, SocketTextChannel channel) + { + // Bail out if it's a System Message. + if (arg is not SocketUserMessage msg) + return; + + // We don't want the bot to respond to itself or other bots. + if (msg.Author.Id == _client.CurrentUser?.Id || (msg.Author.IsBot ?? false)) + return; + + // Create a number to track where the prefix ends and the command begins + int pos = 0; + if (msg.HasStringPrefix(Hub.Config.Kook.CommandPrefix, ref pos)) + { + bool handled = await TryHandleCommandAsync(msg, pos).ConfigureAwait(false); + if (handled) + return; + } + await TryHandleMessageAsync(msg).ConfigureAwait(false); + } + + private async Task TryHandleMessageAsync(SocketUserMessage msg) + { + // should this be a service? + if (msg.Attachments.Count > 0) + { + var mgr = Manager; + var cfg = mgr.Config; + if (cfg.ConvertPKMToShowdownSet && (cfg.ConvertPKMReplyAnyChannel || mgr.CanUseCommandChannel(msg.Channel.Id))) + { + foreach (var att in msg.Attachments) + await msg.Channel.RepostPKMAsShowdownAsync(att).ConfigureAwait(false); + } + } + if (msg.MentionedUserIds.Where(id => id ==_client.CurrentUser?.Id).Any()) + { + string commandPrefix = Manager.Config.CommandPrefix; + await msg.Channel.SendTextAsync($"Please use {commandPrefix}help for help"); + } + } + + private async Task TryHandleCommandAsync(SocketUserMessage msg, int pos) + { + // Create a Command Context. + var context = new SocketCommandContext(_client, msg); + + // Check Permission + var mgr = Manager; + if (!mgr.CanUseCommandUser(msg.Author.Id)) + { + await msg.Channel.SendTextAsync("You are not permitted to use this command.").ConfigureAwait(false); + return true; + } + if (!mgr.CanUseCommandChannel(msg.Channel.Id) && msg.Author.Id != mgr.Owner) + { + if (Hub.Config.Kook.ReplyCannotUseCommandInChannel) + await msg.Channel.SendTextAsync("You can't use that command here.").ConfigureAwait(false); + return true; + } + + // Execute the command. (result does not indicate a return value, + // rather an object stating if the command executed successfully). + var guild = msg.Channel is SocketGuildChannel g ? g.Guild.Name : "Unknown Guild"; + await Log(new LogMessage(LogSeverity.Info, "Command", $"Executing command from {guild}#{msg.Channel.Name}:@{msg.Author.Username}. Content: {msg}. id: {msg.Author.IdentifyNumber}")).ConfigureAwait(false); + var result = await _commands.ExecuteAsync(context, pos, _services).ConfigureAwait(false); + + if (result.Error == CommandError.UnknownCommand) + return false; + + // Uncomment the following lines if you want the bot + // to send a message if it failed. + // This does not catch errors from commands with 'RunMode.Async', + // subscribe a handler for '_commands.CommandExecuted' to see those. + if (!result.IsSuccess) + await msg.Channel.SendTextAsync(result.ErrorReason??"No Error Reason").ConfigureAwait(false); + return true; + } + + private async Task MonitorLogIntervalAsync(CancellationToken token) + { + const int Interval = 20; // seconds + // Check datetime for update + while (!token.IsCancellationRequested) + { + var time = DateTime.Now; + var lastLogged = LogUtil.LastLogged; + + var delta = time - lastLogged; + var gap = TimeSpan.FromSeconds(Interval) - delta; + + if (gap <= TimeSpan.Zero) + { + await Task.Delay(2_000, token).ConfigureAwait(false); + continue; + } + await Task.Delay(gap, token).ConfigureAwait(false); + } + } + private async Task LoadLoggingAndEcho() + { + if (MessageChannelsLoaded) + return; + + // Restore Echoes + EchoModule.RestoreChannels(_client, Hub.Config.Kook); + + // Restore Logging + LogModule.RestoreLogging(_client, Hub.Config.Kook); + TradeStartModule.RestoreTradeStarting(_client); + + // Don't let it load more than once in case of Kook hiccups. + await Log(new LogMessage(LogSeverity.Info, "LoadLoggingAndEcho()", "Logging and Echo channels loaded!")).ConfigureAwait(false); + MessageChannelsLoaded = true; + } +} + diff --git a/SysBot.Pokemon.Kook/SysBot.Pokemon.Kook.csproj b/SysBot.Pokemon.Kook/SysBot.Pokemon.Kook.csproj new file mode 100644 index 000000000..fecc8a551 --- /dev/null +++ b/SysBot.Pokemon.Kook/SysBot.Pokemon.Kook.csproj @@ -0,0 +1,17 @@ + + + + net9.0 + enable + enable + + + + + + + + + + + diff --git a/SysBot.Pokemon.WinForms/PokeBotRunnerImpl.cs b/SysBot.Pokemon.WinForms/PokeBotRunnerImpl.cs index e17e015e3..7fa94ae7e 100644 --- a/SysBot.Pokemon.WinForms/PokeBotRunnerImpl.cs +++ b/SysBot.Pokemon.WinForms/PokeBotRunnerImpl.cs @@ -1,13 +1,14 @@ -using PKHeX.Core; +using PKHeX.Core; +using SysBot.Pokemon.Bilibili; using SysBot.Pokemon.Discord; +using SysBot.Pokemon.Dodo; +using SysBot.Pokemon.Kook; +using SysBot.Pokemon.QQ; using SysBot.Pokemon.Twitch; using SysBot.Pokemon.WinForms; using SysBot.Pokemon.YouTube; using System.Threading; using System.Threading.Tasks; -using SysBot.Pokemon.Bilibili; -using SysBot.Pokemon.Dodo; -using SysBot.Pokemon.QQ; namespace SysBot.Pokemon; @@ -33,6 +34,7 @@ protected override void AddIntegrations() AddQQBot(Hub.Config.QQ); AddBilibiliBot(Hub.Config.Bilibili); AddDodoBot(Hub.Config.Dodo); + AddKookBot(Hub.Config.Kook.Token); } private void AddTwitchBot(TwitchSettings config) @@ -103,4 +105,11 @@ private void AddDodoBot(DodoSettings config) if (Dodo != null) return; Dodo = new DodoBot(config, Hub); } -} \ No newline at end of file + private void AddKookBot(string apiToken) + { + if (string.IsNullOrWhiteSpace(apiToken)) + return; + var bot = new KookBot(this); + Task.Run(() => bot.MainAsync(apiToken, CancellationToken.None), CancellationToken.None); + } +} diff --git a/SysBot.Pokemon.WinForms/SysBot.Pokemon.WinForms.csproj b/SysBot.Pokemon.WinForms/SysBot.Pokemon.WinForms.csproj index 8c3ae6ccc..08384d7d3 100644 --- a/SysBot.Pokemon.WinForms/SysBot.Pokemon.WinForms.csproj +++ b/SysBot.Pokemon.WinForms/SysBot.Pokemon.WinForms.csproj @@ -35,6 +35,7 @@ + diff --git a/SysBot.Pokemon/Settings/Integrations/BilibiliSettings.cs b/SysBot.Pokemon/Settings/Integrations/BilibiliSettings.cs index a91a0876d..20199790a 100644 --- a/SysBot.Pokemon/Settings/Integrations/BilibiliSettings.cs +++ b/SysBot.Pokemon/Settings/Integrations/BilibiliSettings.cs @@ -1,21 +1,18 @@ -using System; using System.ComponentModel; -using System.Linq; -namespace SysBot.Pokemon +namespace SysBot.Pokemon; + +public class BilibiliSettings { - public class BilibiliSettings - { - private const string Startup = nameof(Startup); + private const string Startup = nameof(Startup); - public override string ToString() => "Bilibili Integration Settings"; + public override string ToString() => "Bilibili Integration Settings"; - // Startup + // Startup - [Category(Startup), Description("B站彈幕姬日志目录")] - public string LogUrl { get; set; } = string.Empty; + [Category(Startup), Description("B站彈幕姬日志目录")] + public string LogUrl { get; set; } = string.Empty; - [Category(Startup), Description("直播间ID")] - public int RoomId { get; set; } = 0; - } + [Category(Startup), Description("直播间ID")] + public int RoomId { get; set; } = 0; } diff --git a/SysBot.Pokemon/Settings/Integrations/DodoSettings.cs b/SysBot.Pokemon/Settings/Integrations/DodoSettings.cs index 9ce4a5576..96d4b3d5e 100644 --- a/SysBot.Pokemon/Settings/Integrations/DodoSettings.cs +++ b/SysBot.Pokemon/Settings/Integrations/DodoSettings.cs @@ -1,30 +1,27 @@ -using System; using System.ComponentModel; -using System.Linq; -namespace SysBot.Pokemon +namespace SysBot.Pokemon; + +public class DodoSettings { - public class DodoSettings - { - private const string Startup = nameof(Startup); + private const string Startup = nameof(Startup); - public override string ToString() => "Dodo Integration Settings"; + public override string ToString() => "Dodo Integration Settings"; - // Startup + // Startup - [Category(Startup), Description("接口地址")] - public string BaseApi { get; set; } = "https://botopen.imdodo.com"; + [Category(Startup), Description("接口地址")] + public string BaseApi { get; set; } = "https://botopen.imdodo.com"; - [Category(Startup), Description("机器人唯一标识")] - public string ClientId { get; set; } = string.Empty; + [Category(Startup), Description("机器人唯一标识")] + public string ClientId { get; set; } = string.Empty; - [Category(Startup), Description("机器人鉴权Token")] - public string Token { get; set; } = string.Empty; + [Category(Startup), Description("机器人鉴权Token")] + public string Token { get; set; } = string.Empty; - [Category(Startup), Description("机器人响应频道id")] - public string ChannelId { get; set; } = string.Empty; + [Category(Startup), Description("机器人响应频道id")] + public string ChannelId { get; set; } = string.Empty; - [Category(Startup), Description("是否撤回交换消息")] - public bool WithdrawTradeMessage { get; set; } = false; - } -} \ No newline at end of file + [Category(Startup), Description("是否撤回交换消息")] + public bool WithdrawTradeMessage { get; set; } = false; +} diff --git a/SysBot.Pokemon/Settings/Integrations/KookSettings.cs b/SysBot.Pokemon/Settings/Integrations/KookSettings.cs new file mode 100644 index 000000000..f4255dd6c --- /dev/null +++ b/SysBot.Pokemon/Settings/Integrations/KookSettings.cs @@ -0,0 +1,88 @@ +using System.ComponentModel; + +namespace SysBot.Pokemon; + +public class KookSettings +{ + private const string Startup = nameof(Startup); + private const string Operation = nameof(Operation); + private const string Channels = nameof(Channels); + private const string Roles = nameof(Roles); + private const string Users = nameof(Users); + public override string ToString() => "Kook Integration Settings"; + + // Startup + + [Category(Startup), Description("Bot login token.")] + public string Token { get; set; } = string.Empty; + + [Category(Startup), Description("Bot command prefix.")] + public string CommandPrefix { get; set; } = "$"; + + [Category(Startup), Description("List of modules that will not be loaded when the bot is started (comma separated).")] + public string ModuleBlacklist { get; set; } = string.Empty; + + [Category(Startup), Description("Toggle to handle commands asynchronously or synchronously.")] + public bool AsyncCommands { get; set; } + + [Category(Operation), Description("Custom message the bot will reply with when a user says hello to it. Use string formatting to mention the user in the reply.")] + public string HelloResponse { get; set; } = "Hi {0}!"; + + // Whitelists + + [Category(Roles), Description("Users with this role are allowed to enter the Trade queue.")] + public RemoteControlAccessList RoleCanTrade { get; set; } = new() { AllowIfEmpty = false }; + + [Category(Roles), Description("Users with this role are allowed to enter the Seed Check queue.")] + public RemoteControlAccessList RoleCanSeedCheck { get; set; } = new() { AllowIfEmpty = false }; + + [Category(Roles), Description("Users with this role are allowed to enter the Clone queue.")] + public RemoteControlAccessList RoleCanClone { get; set; } = new() { AllowIfEmpty = false }; + + [Category(Roles), Description("Users with this role are allowed to enter the Dump queue.")] + public RemoteControlAccessList RoleCanDump { get; set; } = new() { AllowIfEmpty = false }; + + [Category(Roles), Description("Users with this role are allowed to remotely control the console (if running as Remote Control Bot.")] + public RemoteControlAccessList RoleRemoteControl { get; set; } = new() { AllowIfEmpty = false }; + + [Category(Roles), Description("Users with this role are allowed to bypass command restrictions.")] + public RemoteControlAccessList RoleSudo { get; set; } = new() { AllowIfEmpty = false }; + + // Operation + + [Category(Roles), Description("Users with this role are allowed to join the queue with a better position.")] + public RemoteControlAccessList RoleFavored { get; set; } = new() { AllowIfEmpty = false }; + + [Category(Users), Description("Users with these user IDs cannot use the bot.")] + public RemoteControlAccessList UserBlacklist { get; set; } = new(); + + [Category(Channels), Description("Channels with these IDs are the only channels where the bot acknowledges commands.")] + public RemoteControlAccessList ChannelWhitelist { get; set; } = new(); + + [Category(Users), Description("Comma separated Discord user IDs that will have sudo access to the Bot Hub.")] + public RemoteControlAccessList GlobalSudoList { get; set; } = new(); + + [Category(Users), Description("Disabling this will remove global sudo support.")] + public bool AllowGlobalSudo { get; set; } = true; + + [Category(Channels), Description("Channel IDs that will echo the log bot data.")] + public RemoteControlAccessList LoggingChannels { get; set; } = new(); + + [Category(Channels), Description("Logger channels that will log trade start messages.")] + public RemoteControlAccessList TradeStartingChannels { get; set; } = new(); + + [Category(Channels), Description("Echo channels that will log special messages.")] + public RemoteControlAccessList EchoChannels { get; set; } = new(); + + [Category(Operation), Description("Returns PKMs of Pokémon shown in the trade to the user.")] + public bool ReturnPKMs { get; set; } = true; + + [Category(Operation), Description("Replies to users if they are not allowed to use a given command in the channel. When false, the bot will silently ignore them instead.")] + public bool ReplyCannotUseCommandInChannel { get; set; } = true; + + [Category(Operation), Description("Bot listens to channel messages to reply with a ShowdownSet whenever a PKM file is attached (not with a command).")] + public bool ConvertPKMToShowdownSet { get; set; } = true; + + [Category(Operation), Description("Bot can reply with a ShowdownSet in Any channel the bot can see, instead of only channels the bot has been whitelisted to run in. Only make this true if you want the bot to serve more utility in non-bot channels.")] + public bool ConvertPKMReplyAnyChannel { get; set; } +} diff --git a/SysBot.Pokemon/TradeHub/PokeTradeHubConfig.cs b/SysBot.Pokemon/TradeHub/PokeTradeHubConfig.cs index 7bbd78822..bc2387dc2 100644 --- a/SysBot.Pokemon/TradeHub/PokeTradeHubConfig.cs +++ b/SysBot.Pokemon/TradeHub/PokeTradeHubConfig.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; // ReSharper disable AutoPropertyCanBeMadeGetOnly.Global @@ -86,4 +86,8 @@ public sealed class PokeTradeHubConfig : BaseConfig [Category(Integration)] [TypeConverter(typeof(ExpandableObjectConverter))] public DodoSettings Dodo { get; set; } = new(); + + [Category(Integration)] + [TypeConverter(typeof(ExpandableObjectConverter))] + public KookSettings Kook { get; set; } = new(); } From 1c8c1d85c498cd366686afac26add2b64c989156 Mon Sep 17 00:00:00 2001 From: Easy World Date: Tue, 3 Jun 2025 13:23:34 +0800 Subject: [PATCH 148/171] Update RequireOwnerAttribute.cs --- SysBot.Pokemon.Kook/Helpers/RequireOwnerAttribute.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/SysBot.Pokemon.Kook/Helpers/RequireOwnerAttribute.cs b/SysBot.Pokemon.Kook/Helpers/RequireOwnerAttribute.cs index 47a3eea75..be83ad435 100644 --- a/SysBot.Pokemon.Kook/Helpers/RequireOwnerAttribute.cs +++ b/SysBot.Pokemon.Kook/Helpers/RequireOwnerAttribute.cs @@ -9,14 +9,13 @@ namespace SysBot.Pokemon.Kook; public class RequireOwnerAttribute : PreconditionAttribute { - public override async Task CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services) + public override Task CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services) { - // 你的Bot主人ID(可配置到配置文件) - ulong ownerId = KookBotSettings.Manager.Owner; // 替换为你的 KOOK 用户ID + ulong ownerId = KookBotSettings.Manager.Owner; if (context.User.Id == ownerId) - return PreconditionResult.FromSuccess(); + return Task.FromResult(PreconditionResult.FromSuccess()); else - return PreconditionResult.FromError("只有Bot主人可以使用此命令。"); + return Task.FromResult(PreconditionResult.FromError("只有Bot主人可以使用此命令。")); } } From 68f8f10e9b1f58217c80884dc47e7624c4496cc5 Mon Sep 17 00:00:00 2001 From: Easy World Date: Tue, 3 Jun 2025 22:36:10 +0800 Subject: [PATCH 149/171] Use file-scoped namespace --- .../Settings/Integrations/BilibiliSettings.cs | 23 ++++++------ .../Settings/Integrations/DodoSettings.cs | 37 +++++++++---------- 2 files changed, 29 insertions(+), 31 deletions(-) diff --git a/SysBot.Pokemon/Settings/Integrations/BilibiliSettings.cs b/SysBot.Pokemon/Settings/Integrations/BilibiliSettings.cs index a91a0876d..19115cadd 100644 --- a/SysBot.Pokemon/Settings/Integrations/BilibiliSettings.cs +++ b/SysBot.Pokemon/Settings/Integrations/BilibiliSettings.cs @@ -1,21 +1,20 @@ -using System; +using System; using System.ComponentModel; using System.Linq; -namespace SysBot.Pokemon +namespace SysBot.Pokemon; + +public class BilibiliSettings { - public class BilibiliSettings - { - private const string Startup = nameof(Startup); + private const string Startup = nameof(Startup); - public override string ToString() => "Bilibili Integration Settings"; + public override string ToString() => "Bilibili Integration Settings"; - // Startup + // Startup - [Category(Startup), Description("B站彈幕姬日志目录")] - public string LogUrl { get; set; } = string.Empty; + [Category(Startup), Description("B站彈幕姬日志目录")] + public string LogUrl { get; set; } = string.Empty; - [Category(Startup), Description("直播间ID")] - public int RoomId { get; set; } = 0; - } + [Category(Startup), Description("直播间ID")] + public int RoomId { get; set; } = 0; } diff --git a/SysBot.Pokemon/Settings/Integrations/DodoSettings.cs b/SysBot.Pokemon/Settings/Integrations/DodoSettings.cs index 9ce4a5576..d49783e7b 100644 --- a/SysBot.Pokemon/Settings/Integrations/DodoSettings.cs +++ b/SysBot.Pokemon/Settings/Integrations/DodoSettings.cs @@ -1,30 +1,29 @@ -using System; +using System; using System.ComponentModel; using System.Linq; -namespace SysBot.Pokemon +namespace SysBot.Pokemon; + +public class DodoSettings { - public class DodoSettings - { - private const string Startup = nameof(Startup); + private const string Startup = nameof(Startup); - public override string ToString() => "Dodo Integration Settings"; + public override string ToString() => "Dodo Integration Settings"; - // Startup + // Startup - [Category(Startup), Description("接口地址")] - public string BaseApi { get; set; } = "https://botopen.imdodo.com"; + [Category(Startup), Description("接口地址")] + public string BaseApi { get; set; } = "https://botopen.imdodo.com"; - [Category(Startup), Description("机器人唯一标识")] - public string ClientId { get; set; } = string.Empty; + [Category(Startup), Description("机器人唯一标识")] + public string ClientId { get; set; } = string.Empty; - [Category(Startup), Description("机器人鉴权Token")] - public string Token { get; set; } = string.Empty; + [Category(Startup), Description("机器人鉴权Token")] + public string Token { get; set; } = string.Empty; - [Category(Startup), Description("机器人响应频道id")] - public string ChannelId { get; set; } = string.Empty; + [Category(Startup), Description("机器人响应频道id")] + public string ChannelId { get; set; } = string.Empty; - [Category(Startup), Description("是否撤回交换消息")] - public bool WithdrawTradeMessage { get; set; } = false; - } -} \ No newline at end of file + [Category(Startup), Description("是否撤回交换消息")] + public bool WithdrawTradeMessage { get; set; } = false; +} From 19c4dab2262a58a006ce35c71e0ec886f190ddce Mon Sep 17 00:00:00 2001 From: Easy World Date: Tue, 3 Jun 2025 22:37:22 +0800 Subject: [PATCH 150/171] Update QQSettings.cs --- .../Settings/Integrations/QQSettings.cs | 45 +++++++++---------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/SysBot.Pokemon/Settings/Integrations/QQSettings.cs b/SysBot.Pokemon/Settings/Integrations/QQSettings.cs index f4ad08f41..b474904f2 100644 --- a/SysBot.Pokemon/Settings/Integrations/QQSettings.cs +++ b/SysBot.Pokemon/Settings/Integrations/QQSettings.cs @@ -1,34 +1,33 @@ -using System; +using System; using System.ComponentModel; using System.Linq; -namespace SysBot.Pokemon +namespace SysBot.Pokemon; + +public class QQSettings { - public class QQSettings - { - private const string Startup = nameof(Startup); - private const string Operation = nameof(Operation); - private const string Messages = nameof(Messages); - public override string ToString() => "QQ Integration Settings"; + private const string Startup = nameof(Startup); + private const string Operation = nameof(Operation); + private const string Messages = nameof(Messages); + public override string ToString() => "QQ Integration Settings"; - // Startup + // Startup - [Category(Startup), Description("Mirai Bot Address")] - public string Address { get; set; } = string.Empty; + [Category(Startup), Description("Mirai Bot Address")] + public string Address { get; set; } = string.Empty; - [Category(Startup), Description("Mirai Bot VerifyKey")] - public string VerifyKey { get; set; } = string.Empty; + [Category(Startup), Description("Mirai Bot VerifyKey")] + public string VerifyKey { get; set; } = string.Empty; - [Category(Startup), Description("QQ number of your Bot")] - public string QQ { get; set; } = string.Empty; + [Category(Startup), Description("QQ number of your Bot")] + public string QQ { get; set; } = string.Empty; - [Category(Startup), Description("QQ Group to Send Messages To")] - public string GroupId { get; set; } = string.Empty; + [Category(Startup), Description("QQ Group to Send Messages To")] + public string GroupId { get; set; } = string.Empty; - [Category(Startup), Description("Message to test Bot alive")] - public string AliveMsg { get; set; } = "hello"; + [Category(Startup), Description("Message to test Bot alive")] + public string AliveMsg { get; set; } = "hello"; - [Category(Operation), Description("Message sent when the Barrier is released.")] - public string MessageStart { get; set; } = string.Empty; - } -} \ No newline at end of file + [Category(Operation), Description("Message sent when the Barrier is released.")] + public string MessageStart { get; set; } = string.Empty; +} From 0c7e94facdfec1870802a01f7312c4781acd62d6 Mon Sep 17 00:00:00 2001 From: Easy World Date: Wed, 4 Jun 2025 08:46:42 +0800 Subject: [PATCH 151/171] Bold fix --- SysBot.Pokemon.Kook/Commands/Bots/QueueModule.cs | 2 +- SysBot.Pokemon.Kook/Commands/General/HelpModule.cs | 8 ++++---- SysBot.Pokemon.Kook/Commands/General/InfoModule.cs | 4 ++-- SysBot.Pokemon.Kook/Commands/Management/HubModule.cs | 8 ++++---- SysBot.Pokemon.Kook/Commands/Management/OwnerModule.cs | 2 +- SysBot.Pokemon.Kook/Helpers/KookTradeNotifier.cs | 4 ++-- SysBot.Pokemon.Kook/Helpers/QueueHelper.cs | 2 +- SysBot.Pokemon.Kook/Helpers/RequireOwnerAttribute.cs | 2 +- 8 files changed, 16 insertions(+), 16 deletions(-) diff --git a/SysBot.Pokemon.Kook/Commands/Bots/QueueModule.cs b/SysBot.Pokemon.Kook/Commands/Bots/QueueModule.cs index 1df66cfb3..541bbce34 100644 --- a/SysBot.Pokemon.Kook/Commands/Bots/QueueModule.cs +++ b/SysBot.Pokemon.Kook/Commands/Bots/QueueModule.cs @@ -86,7 +86,7 @@ public Task ToggleQueueTradeAsync() var state = Info.ToggleQueue(); var msg = state ? "Users are now able to join the trade queue." - : "Changed queue settings: **Users CANNOT join the queue until it is turned back on.**"; + : $"Changed queue settings: {Format.Bold($"Users CANNOT join the queue until it is turned back on.")}"; return Context.Channel.EchoAndReply(msg); } diff --git a/SysBot.Pokemon.Kook/Commands/General/HelpModule.cs b/SysBot.Pokemon.Kook/Commands/General/HelpModule.cs index d8beb52be..e533b0925 100644 --- a/SysBot.Pokemon.Kook/Commands/General/HelpModule.cs +++ b/SysBot.Pokemon.Kook/Commands/General/HelpModule.cs @@ -48,7 +48,7 @@ public async Task HelpAsync() if (gen != -1) moduleName = moduleName[..gen]; - builder.AddModule(new SectionModuleBuilder().WithText($"**{moduleName}**\n{description}")); + builder.AddModule(new SectionModuleBuilder().WithText(new KMarkdownElementBuilder($"**{moduleName}**\n{description}"))); } await ReplyCardAsync(builder.Build()).ConfigureAwait(false); @@ -62,19 +62,19 @@ public async Task HelpAsync([Summary("The command you want help for")] string co if (!result.IsSuccess) { - await ReplyTextAsync($"Sorry, I couldn't find a command like **{command}**.").ConfigureAwait(false); + await ReplyTextAsync($"Sorry, I couldn't find a command like {Format.Bold($"{command}")}.").ConfigureAwait(false); return; } var builder = new CardBuilder() .AddModule(new SectionModuleBuilder().WithText("Help has arrived!")) - .AddModule(new SectionModuleBuilder().WithText($"Here are some commands like **{command}**:")); + .AddModule(new SectionModuleBuilder().WithText(new KMarkdownElementBuilder($"Here are some commands like **{command}**:"))); foreach (var match in result.Commands) { var cmd = match.Command; - builder.AddModule(new SectionModuleBuilder().WithText($"**{string.Join(", ", cmd.Aliases)}**\n{GetCommandSummary(cmd)}")); + builder.AddModule(new SectionModuleBuilder().WithText(new KMarkdownElementBuilder($"**{string.Join(", ", cmd.Aliases)}**\n{GetCommandSummary(cmd)}"))); } await ReplyCardAsync(builder.Build()).ConfigureAwait(false); diff --git a/SysBot.Pokemon.Kook/Commands/General/InfoModule.cs b/SysBot.Pokemon.Kook/Commands/General/InfoModule.cs index e49326fb3..4775c9d58 100644 --- a/SysBot.Pokemon.Kook/Commands/General/InfoModule.cs +++ b/SysBot.Pokemon.Kook/Commands/General/InfoModule.cs @@ -28,7 +28,7 @@ public async Task InfoAsync() .AddModule(new SectionModuleBuilder().WithText("Here's some information about me!")) .AddModule(new SectionModuleBuilder().WithText(detail)); - builder.AddModule(new SectionModuleBuilder().WithText("**Info**")) + builder.AddModule(new SectionModuleBuilder().WithText(new KMarkdownElementBuilder("**Info**"))) .AddModule(new SectionModuleBuilder().WithText( $"- [Source Code]({repo})\n" + //$"- {Format.Bold("Owner")}: {app.Owner} ({app.Owner.Id})\n" + @@ -41,7 +41,7 @@ public async Task InfoAsync() $"- {Format.Bold("AutoLegality Version")}: {GetVersionInfo("PKHeX.Core.AutoMod")}\n" )); - builder.AddModule(new SectionModuleBuilder().WithText("**Stats**")) + builder.AddModule(new SectionModuleBuilder().WithText(new KMarkdownElementBuilder("**Stats**"))) .AddModule(new SectionModuleBuilder().WithText( $"- {Format.Bold("Heap Size")}: {GetHeapSize()}MiB\n" + $"- {Format.Bold("Guilds")}: {Context.Client.Guilds.Count}\n" + diff --git a/SysBot.Pokemon.Kook/Commands/Management/HubModule.cs b/SysBot.Pokemon.Kook/Commands/Management/HubModule.cs index ac4e333a8..2d656df38 100644 --- a/SysBot.Pokemon.Kook/Commands/Management/HubModule.cs +++ b/SysBot.Pokemon.Kook/Commands/Management/HubModule.cs @@ -25,7 +25,7 @@ public async Task GetStatusAsync() var allBots = runner.Bots.ConvertAll(z => z.Bot); var botCount = allBots.Count; builder.AddModule(new SectionModuleBuilder().WithText("Bot Status")) - .AddModule(new SectionModuleBuilder().WithText("**Summary**")) + .AddModule(new SectionModuleBuilder().WithText(new KMarkdownElementBuilder("**Summary**"))) .AddModule(new SectionModuleBuilder().WithText($"Bot Count: {botCount}\n" + $"Bot State: {SummarizeBots(allBots)}\n" + $"Pool Count: {hub.Ledy.Pool.Count}\n")); @@ -35,7 +35,7 @@ public async Task GetStatusAsync() var msg = string.Join("\n", lines); if (string.IsNullOrWhiteSpace(msg)) msg = "Nothing counted yet!"; - builder.AddModule(new SectionModuleBuilder().WithText("**Counts**")) + builder.AddModule(new SectionModuleBuilder().WithText(new KMarkdownElementBuilder("**Counts**"))) .AddModule(new SectionModuleBuilder().WithText(msg)); var queues = hub.Queues.AllQueues; @@ -47,7 +47,7 @@ public async Task GetStatusAsync() continue; var nextMsg = GetNextName(q); - builder.AddModule(new SectionModuleBuilder().WithText($"**{q.Type} Queue**")) + builder.AddModule(new SectionModuleBuilder().WithText(new KMarkdownElementBuilder($"**{q.Type} Queue**"))) .AddModule(new SectionModuleBuilder().WithText($"Next: {nextMsg}\n" + $"Count: {c}")); count += c; @@ -55,7 +55,7 @@ public async Task GetStatusAsync() if (count == 0) { - builder.AddModule(new SectionModuleBuilder().WithText("**Queues are empty.**")) + builder.AddModule(new SectionModuleBuilder().WithText(new KMarkdownElementBuilder("**Queues are empty.**"))) .AddModule(new SectionModuleBuilder().WithText("Nobody in line!")); } diff --git a/SysBot.Pokemon.Kook/Commands/Management/OwnerModule.cs b/SysBot.Pokemon.Kook/Commands/Management/OwnerModule.cs index 02f0f478e..dc8d9b091 100644 --- a/SysBot.Pokemon.Kook/Commands/Management/OwnerModule.cs +++ b/SysBot.Pokemon.Kook/Commands/Management/OwnerModule.cs @@ -108,7 +108,7 @@ public async Task LeaveAll() // ReSharper disable once UnusedParameter.Global public async Task ExitProgram() { - await Context.Channel.EchoAndReply("Shutting down... goodbye! **Bot services are going offline.**").ConfigureAwait(false); + await Context.Channel.EchoAndReply($"Shutting down... goodbye! {Format.Bold("Bot services are going offline.")}").ConfigureAwait(false); Environment.Exit(0); } diff --git a/SysBot.Pokemon.Kook/Helpers/KookTradeNotifier.cs b/SysBot.Pokemon.Kook/Helpers/KookTradeNotifier.cs index 92e32e629..e05f624aa 100644 --- a/SysBot.Pokemon.Kook/Helpers/KookTradeNotifier.cs +++ b/SysBot.Pokemon.Kook/Helpers/KookTradeNotifier.cs @@ -21,14 +21,14 @@ public class KookTradeNotifier(T Data, PokeTradeTrainerInfo Info, int Code, S public void TradeInitialize(PokeRoutineExecutor routine, PokeTradeDetail info) { var receive = Data.Species == 0 ? string.Empty : $" ({Data.Nickname})"; - Trader.SendTextAsync($"Initializing trade{receive}. Please be ready. Your code is **{Code:0000 0000}**.").ConfigureAwait(false); + Trader.SendTextAsync($"Initializing trade{receive}. Please be ready. Your code is {Format.Bold($"{Code: 0000 0000}")}.").ConfigureAwait(false); } public void TradeSearching(PokeRoutineExecutor routine, PokeTradeDetail info) { var name = Info.TrainerName; var trainer = string.IsNullOrEmpty(name) ? string.Empty : $", {name}"; - Trader.SendTextAsync($"I'm waiting for you{trainer}! Your code is **{Code:0000 0000}**. My IGN is **{routine.InGameName}**.").ConfigureAwait(false); + Trader.SendTextAsync($"I'm waiting for you{trainer}! Your code is {Format.Bold($"{Code:0000 0000}")}. My IGN is {Format.Bold($"{routine.InGameName}")}.").ConfigureAwait(false); } public void TradeCanceled(PokeRoutineExecutor routine, PokeTradeDetail info, PokeTradeResult msg) diff --git a/SysBot.Pokemon.Kook/Helpers/QueueHelper.cs b/SysBot.Pokemon.Kook/Helpers/QueueHelper.cs index d34546d02..734c04701 100644 --- a/SysBot.Pokemon.Kook/Helpers/QueueHelper.cs +++ b/SysBot.Pokemon.Kook/Helpers/QueueHelper.cs @@ -30,7 +30,7 @@ public static async Task AddToQueueAsync(SocketCommandContext context, int code, // Notify in channel await context.Channel.SendTextAsync(msg).ConfigureAwait(false); // Notify in PM to mirror what is said in the channel. - await trader.SendTextAsync($"{msg}\nYour trade code will be **{code:0000 0000}**.").ConfigureAwait(false); + await trader.SendTextAsync($"{msg}\nYour trade code will be {Format.Bold($"{code:0000 0000}")}.").ConfigureAwait(false); // Clean Up if (result) diff --git a/SysBot.Pokemon.Kook/Helpers/RequireOwnerAttribute.cs b/SysBot.Pokemon.Kook/Helpers/RequireOwnerAttribute.cs index be83ad435..41f74a801 100644 --- a/SysBot.Pokemon.Kook/Helpers/RequireOwnerAttribute.cs +++ b/SysBot.Pokemon.Kook/Helpers/RequireOwnerAttribute.cs @@ -16,6 +16,6 @@ public override Task CheckPermissionsAsync(ICommandContext c if (context.User.Id == ownerId) return Task.FromResult(PreconditionResult.FromSuccess()); else - return Task.FromResult(PreconditionResult.FromError("只有Bot主人可以使用此命令。")); + return Task.FromResult(PreconditionResult.FromError("Only the Bot owner can use this command.")); } } From 9d224eb96cbc1285111030bdd352f0a836edc0ac Mon Sep 17 00:00:00 2001 From: Easy World Date: Wed, 18 Jun 2025 10:48:59 +0800 Subject: [PATCH 152/171] Update ReusableActions.cs --- SysBot.Pokemon.Kook/Helpers/ReusableActions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SysBot.Pokemon.Kook/Helpers/ReusableActions.cs b/SysBot.Pokemon.Kook/Helpers/ReusableActions.cs index ad8f1f711..0660a741e 100644 --- a/SysBot.Pokemon.Kook/Helpers/ReusableActions.cs +++ b/SysBot.Pokemon.Kook/Helpers/ReusableActions.cs @@ -14,7 +14,7 @@ public static class ReusableActions { public static async Task SendPKMAsync(this IMessageChannel channel, PKM pkm, string msg = "") { - var tmp = Path.Combine(Path.GetTempPath(), Util.CleanFileName(pkm.FileName)); + var tmp = Path.Combine(Path.GetTempPath(), PathUtil.CleanFileName(pkm.FileName)); await File.WriteAllBytesAsync(tmp, pkm.DecryptedPartyData); await channel.SendFileAsync(tmp, msg).ConfigureAwait(false); File.Delete(tmp); @@ -22,7 +22,7 @@ public static async Task SendPKMAsync(this IMessageChannel channel, PKM pkm, str public static async Task SendPKMAsync(this IUser user, PKM pkm, string msg = "") { - var tmp = Path.Combine(Path.GetTempPath(), Util.CleanFileName(pkm.FileName)); + var tmp = Path.Combine(Path.GetTempPath(), PathUtil.CleanFileName(pkm.FileName)); await File.WriteAllBytesAsync(tmp, pkm.DecryptedPartyData); await user.SendFileAsync(tmp, msg).ConfigureAwait(false); File.Delete(tmp); From 60ec2b1870644aeacae7f337575d2e0fad3a17c4 Mon Sep 17 00:00:00 2001 From: Easy World Date: Fri, 27 Jun 2025 16:20:30 +0800 Subject: [PATCH 153/171] Update README.md --- README.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6a925de8c..14e386958 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,8 @@ Uses [Mirai.Net](https://github.com/SinoAHpx/Mirai.Net) as a dependency via Nuge Document: [搭建指南](https://github.com/easyworld/SysBot.NET/tree/master/SysBot.Pokemon.QQ), [命令指南](https://docs.qq.com/doc/DSVlldkxMSW92VXZF) -## SysBot.Pokemon.Dodo: +## SysBot.Pokemon.Dodo (Dead): +**Notice: The Dodo platform was discontinued in June 15th 2025. All related features of this project are no longer functional. This repository is now archived for code reference only—none of the bot functionalities or commands are available anymore.** - Support [ALM-Showdown-Sets](https://github.com/architdate/PKHeX-Plugins/wiki/ALM-Showdown-Sets) - Support PK8 PB8 PA8 PK9 file upload - Support Customized Chinese to ALM-Showdown-Sets translation @@ -63,6 +64,14 @@ Uses [dodo-open-net](https://github.com/dodo-open/dodo-open-net) as a dependency Document: [搭建指南](https://docs.qq.com/doc/DSVVZZk9saUNTeHNn), [命令指南](https://docs.qq.com/doc/DSVlldkxMSW92VXZF) +## SysBot.Pokemon.Kook: +**SysBot.Pokemon.Kook** is a complete port of the `SysBot.Pokemon.Discord` project, fully compatible with the [Kook](https://www.kookapp.cn/) (formerly known as Kaiheila) platform. + +- 100% feature parity with SysBot.Pokemon.Discord. +- Seamless integration with the Kook chat platform, supporting all bot commands and functionalities available on Discord. +- Allows Pokémon trading, file uploads (PK8, PB8, etc.), and advanced automation features. +- Easily configurable and extensible for the Kook ecosystem. + ## Other Dependencies Pokémon API logic is provided by [PKHeX](https://github.com/kwsch/PKHeX/), and template generation is provided by [Auto-Legality Mod](https://github.com/architdate/PKHeX-Plugins/). Current template generation uses [@santacrab2](https://www.github.com/santacrab2)'s [Auto-Legality Mod fork](https://github.com/santacrab2/PKHeX-Plugins). From cd691f42e0a3ba3f7cc0df3c561cac80e5c1d7c0 Mon Sep 17 00:00:00 2001 From: Easy World Date: Fri, 18 Jul 2025 12:52:45 +0800 Subject: [PATCH 154/171] Bump Kook.Net package to version 0.9.11 Updated the Kook.Net NuGet package from version 0.9.9 to 0.9.11 in the SysBot.Pokemon.Kook project file to use the latest features and bug fixes. --- SysBot.Pokemon.Kook/SysBot.Pokemon.Kook.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SysBot.Pokemon.Kook/SysBot.Pokemon.Kook.csproj b/SysBot.Pokemon.Kook/SysBot.Pokemon.Kook.csproj index fecc8a551..d87ff30f2 100644 --- a/SysBot.Pokemon.Kook/SysBot.Pokemon.Kook.csproj +++ b/SysBot.Pokemon.Kook/SysBot.Pokemon.Kook.csproj @@ -7,7 +7,7 @@ - + From ca4bc08a4d1c6608cdf507f71e8512c172295d79 Mon Sep 17 00:00:00 2001 From: Easy World Date: Fri, 26 Sep 2025 20:09:25 +0800 Subject: [PATCH 155/171] Improve error reporting and egg met date handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enhanced the error reporting in TradeModule to humanize invalid lines using localization. Updated AbstractTrade to set EggMetDate and handle MetDate fields correctly for unhatched eggs based on MetLocation. Added more test cases to TranslatorTests for additional Chinese Pokémon names. --- .../Commands/Bots/TradeModule.cs | 11 ++++++++--- SysBot.Pokemon/Helpers/AbstractTrade.cs | 17 +++++++++++++++++ SysBot.Tests/TranslatorTests.cs | 4 ++++ 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/SysBot.Pokemon.Kook/Commands/Bots/TradeModule.cs b/SysBot.Pokemon.Kook/Commands/Bots/TradeModule.cs index eae8d281a..1c50f80ea 100644 --- a/SysBot.Pokemon.Kook/Commands/Bots/TradeModule.cs +++ b/SysBot.Pokemon.Kook/Commands/Bots/TradeModule.cs @@ -63,11 +63,16 @@ public async Task TradeAsync([Summary("Trade Code")] int code, [Summary("Showdow { var sb = new StringBuilder(128); sb.AppendLine("Unable to parse Showdown Set."); - if (set.InvalidLines.Count != 0) + var invalidlines = set.InvalidLines; + if (invalidlines.Count != 0) { + var localization = BattleTemplateParseErrorLocalization.Get(); sb.AppendLine("Invalid lines detected:\n```"); - foreach (var line in set.InvalidLines) - sb.AppendLine(line); + foreach (var line in invalidlines) + { + var error = line.Humanize(localization); + sb.AppendLine(error); + } sb.AppendLine("```"); } if (set.Species is 0) diff --git a/SysBot.Pokemon/Helpers/AbstractTrade.cs b/SysBot.Pokemon/Helpers/AbstractTrade.cs index de2fb9c5e..3a0c59de7 100644 --- a/SysBot.Pokemon/Helpers/AbstractTrade.cs +++ b/SysBot.Pokemon/Helpers/AbstractTrade.cs @@ -386,6 +386,7 @@ public static void EggTrade(PKM pk, IBattleTemplate template) _ => 60002, //PK8 }; + pk.EggMetDate = DateOnly.FromDateTime(DateTime.Now); pk.HeldItem = 0; pk.CurrentLevel = 1; pk.EXP = 0; @@ -397,8 +398,24 @@ public static void EggTrade(PKM pk, IBattleTemplate template) _ => 30002, //PK8 }; + // Set MetDate based on MetLocation + // For unhatched eggs: + // - PK9 (SV): MetLocation 0 requires MetDate fields to be 0 + // - PB8 (BDSP): MetLocation 65535 requires MetDate fields to be 0 + // - PK8 (SwSh): MetLocation 30002 can have a valid MetDate + if (pk.MetLocation == 0 || pk.MetLocation == 65535) + { + pk.MetYear = 0; + pk.MetMonth = 0; + pk.MetDay = 0; + } + else + { + pk.MetDate = pk.EggMetDate; + } pk.CurrentHandler = 0; pk.OriginalTrainerFriendship = 1; + pk.HandlingTrainerTrash.Clear(); pk.HandlingTrainerName = ""; pk.HandlingTrainerFriendship = 0; pk.ClearMemories(); diff --git a/SysBot.Tests/TranslatorTests.cs b/SysBot.Tests/TranslatorTests.cs index 92b4cbb52..f4accc1fc 100644 --- a/SysBot.Tests/TranslatorTests.cs +++ b/SysBot.Tests/TranslatorTests.cs @@ -29,6 +29,10 @@ public void TestTrans(string input, string output) [InlineData("千面避役")] [InlineData("泪眼蜥")] [InlineData("布里卡隆")] + [InlineData("异色烈空坐")] + [InlineData("等级球呆火鳄")] + [InlineData("异色古剑豹")] + [InlineData("异色故勒顿")] public void TestLegal(string input) { var setstring = ShowdownTranslator.Chinese2Showdown(input); From 2862bd7e350d72aff256dd294119155fdf465d51 Mon Sep 17 00:00:00 2001 From: Easy World Date: Mon, 17 Nov 2025 09:30:36 +0800 Subject: [PATCH 156/171] Fix trade partner status offset in batch trade MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces TradePartnerOfferedOffset with TradePartnerStatusOffset when checking if the trade partner has offered a Pokémon during batch trades. This ensures the correct memory offset is used for status detection. --- SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs b/SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs index 77bec7f32..f41d97e2e 100644 --- a/SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs +++ b/SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs @@ -768,7 +768,7 @@ private async Task PerformRemainingLinkCodeTrades(SAV8LA sav, P { Log($"Processing remaining Pokémon {index + 1} of {pkms.Count()} in batch trade."); // Watch their status to indicate they have offered a Pokémon as well. - var offering = await ReadUntilChanged(TradePartnerOfferedOffset, [0x3], 25_000, 1_000, true, true, token).ConfigureAwait(false); + var offering = await ReadUntilChanged(TradePartnerStatusOffset, [0x3], 25_000, 1_000, true, true, token).ConfigureAwait(false); if (!offering) { await ExitTrade(false, token).ConfigureAwait(false); From 3807f8d9728525d8f710c5a833e6a67d1eec0e3b Mon Sep 17 00:00:00 2001 From: Easy World Date: Tue, 18 Nov 2025 13:56:03 +0800 Subject: [PATCH 157/171] Add PA9 support and enhance multi-trade logic Introduces PA9 handling across helpers and trade logic, including size and bin definitions. Refactors multi-trade support in PokeTradeBotLZA for batch trading, adds partner info (gender, language), and implements auto OT swap for PA9 trades. Removes redundant version messages in DodoBot and MiraiQQBot, and updates ShowdownTranslator to support PA9 Alpha detection. --- SysBot.Pokemon.Dodo/DodoBot.cs | 16 -- SysBot.Pokemon.QQ/MiraiQQBot.cs | 17 -- SysBot.Pokemon/Helpers/AbstractTrade.cs | 10 +- SysBot.Pokemon/Helpers/FileTradeHelper.cs | 10 +- SysBot.Pokemon/Helpers/ShowdownTranslator.cs | 2 +- .../LZA/BotTrade/PokeTradeBotLZA.cs | 251 ++++++++++++++---- .../LZA/BotTrade/TradePartnerLZA.cs | 11 +- 7 files changed, 216 insertions(+), 101 deletions(-) diff --git a/SysBot.Pokemon.Dodo/DodoBot.cs b/SysBot.Pokemon.Dodo/DodoBot.cs index c8e75e020..bc0085323 100644 --- a/SysBot.Pokemon.Dodo/DodoBot.cs +++ b/SysBot.Pokemon.Dodo/DodoBot.cs @@ -52,22 +52,6 @@ public void StartDistribution() if (string.IsNullOrWhiteSpace(channelId)) return; SendChannelMessage("开始派送", channelId); Task.Delay(1_000).ConfigureAwait(false); - if (typeof(T) == typeof(PK8)) - { - SendChannelMessage("当前版本为剑盾", channelId); - } - else if (typeof(T) == typeof(PB8)) - { - SendChannelMessage("当前版本为晶灿钻石明亮珍珠", channelId); - } - else if (typeof(T) == typeof(PA8)) - { - SendChannelMessage("当前版本为阿尔宙斯", channelId); - } - else if (typeof(T) == typeof(PK9)) - { - SendChannelMessage("当前版本为朱紫", channelId); - } } public static void SendChannelMessage(string message, string channelId) diff --git a/SysBot.Pokemon.QQ/MiraiQQBot.cs b/SysBot.Pokemon.QQ/MiraiQQBot.cs index ab91b4e49..4062b845d 100644 --- a/SysBot.Pokemon.QQ/MiraiQQBot.cs +++ b/SysBot.Pokemon.QQ/MiraiQQBot.cs @@ -51,23 +51,6 @@ public MiraiQQBot(QQSettings settings, PokeTradeHub hub) await Task.Delay(1_000).ConfigureAwait(false); } - if (typeof(T) == typeof(PK8)) - { - await MessageManager.SendGroupMessageAsync(GroupId, "当前版本为剑盾"); - } - else if (typeof(T) == typeof(PB8)) - { - await MessageManager.SendGroupMessageAsync(GroupId, "当前版本为晶灿钻石明亮珍珠"); - } - else if (typeof(T) == typeof(PA8)) - { - await MessageManager.SendGroupMessageAsync(GroupId, "当前版本为阿尔宙斯"); - } - else if (typeof(T) == typeof(PK9)) - { - await MessageManager.SendGroupMessageAsync(GroupId, "当前版本为朱紫"); - } - await Task.Delay(1_000).ConfigureAwait(false); } #pragma warning disable CA1031 // Do not catch general exception types diff --git a/SysBot.Pokemon/Helpers/AbstractTrade.cs b/SysBot.Pokemon/Helpers/AbstractTrade.cs index 3a0c59de7..8a36fe16c 100644 --- a/SysBot.Pokemon/Helpers/AbstractTrade.cs +++ b/SysBot.Pokemon/Helpers/AbstractTrade.cs @@ -92,8 +92,8 @@ public void StartTradeMultiPKM(List rawPkms) { if (!JudgeMultiNum(rawPkms.Count)) return; - List pkms = new(); - List skipAutoOTList = new(); + List pkms = []; + List skipAutoOTList = []; int invalidCount = 0; for (var i = 0; i < rawPkms.Count; i++) { @@ -128,8 +128,8 @@ public void StartTradeMultiPKM(List rawPkms) /// private List GetPKMsFromPsList(List psList, bool isChinesePS, out int invalidCount, out List skipAutoOTList) { - List pkms = new(); - skipAutoOTList = new List(); + List pkms = []; + skipAutoOTList = []; invalidCount = 0; for (var i = 0; i < psList.Count; i++) { @@ -306,7 +306,7 @@ public static List GetSkipAutoOTListFromPokeTradeDetail(PokeTradeDetail return (List)value; } int count = GetPKMsFromPokeTradeDetail(detail).Count; - return Enumerable.Repeat(false, count).ToList(); + return [.. Enumerable.Repeat(false, count)]; } private bool AddToTradeQueue(T pk, int code, bool skipAutoOT, diff --git a/SysBot.Pokemon/Helpers/FileTradeHelper.cs b/SysBot.Pokemon/Helpers/FileTradeHelper.cs index 4d6bb7aea..3df50acc1 100644 --- a/SysBot.Pokemon/Helpers/FileTradeHelper.cs +++ b/SysBot.Pokemon/Helpers/FileTradeHelper.cs @@ -60,6 +60,7 @@ public static bool ValidFileName(string fileName) Type t when t == typeof(PB8) => new PB8(ba), Type t when t == typeof(PA8) => new PA8(ba), Type t when t == typeof(PK9) => new PK9(ba), + Type t when t == typeof(PA9) => new PA9(ba), _ => null }; static readonly Dictionary pkmSize = new() @@ -67,7 +68,8 @@ public static bool ValidFileName(string fileName) { typeof(PK8), 344 }, { typeof(PB8), 344 }, { typeof(PA8), 376 }, - { typeof(PK9), 344 } + { typeof(PK9), 344 }, + { typeof(PA9), 344 } }; static readonly Dictionary pkmSizeInBin = new() @@ -75,7 +77,8 @@ public static bool ValidFileName(string fileName) { typeof(PK8), 344 }, { typeof(PB8), 344 }, { typeof(PA8), 360 }, - { typeof(PK9), 344 } + { typeof(PK9), 344 }, + { typeof(PA9), 408 } }; static readonly Dictionary maxCountInBin = new() @@ -83,7 +86,8 @@ public static bool ValidFileName(string fileName) { typeof(PK8), 960 }, { typeof(PB8), 1200 }, { typeof(PA8), 960 }, - { typeof(PK9), 960 } + { typeof(PK9), 960 }, + { typeof(PA9), 960 } }; } diff --git a/SysBot.Pokemon/Helpers/ShowdownTranslator.cs b/SysBot.Pokemon/Helpers/ShowdownTranslator.cs index c7c692da5..84ed22079 100644 --- a/SysBot.Pokemon/Helpers/ShowdownTranslator.cs +++ b/SysBot.Pokemon/Helpers/ShowdownTranslator.cs @@ -105,7 +105,7 @@ public static string Chinese2Showdown(string zh) } // 添加头目 - if (typeof(T) == typeof(PA8) && zh.Contains("头目")) + if ((typeof(T) == typeof(PA8) || typeof(T) == typeof(PA9)) && zh.Contains("头目")) { result += "\nAlpha: Yes"; zh = zh.Replace("头目", ""); diff --git a/SysBot.Pokemon/LZA/BotTrade/PokeTradeBotLZA.cs b/SysBot.Pokemon/LZA/BotTrade/PokeTradeBotLZA.cs index 968df0657..96e268080 100644 --- a/SysBot.Pokemon/LZA/BotTrade/PokeTradeBotLZA.cs +++ b/SysBot.Pokemon/LZA/BotTrade/PokeTradeBotLZA.cs @@ -1,7 +1,9 @@ using PKHeX.Core; using PKHeX.Core.Searching; using SysBot.Base; +using SysBot.Pokemon.Helpers; using System; +using System.Collections.Generic; using System.Linq; using System.Net.Sockets; using System.Threading; @@ -323,6 +325,11 @@ private async Task PerformLinkCodeTrade(SAV9ZA sav, PokeTradeDe return partnerCheck; } + if (Hub.Config.Legality.UseTradePartnerInfo && !AbstractTrade.GetSkipAutoOTListFromPokeTradeDetail(poke).First()) + { + await SetBoxPkmWithSwappedIDDetailsLZA(toSend, tradePartner, sav, token); + } + poke.SendNotification(this, $"Found Link Trade partner: {tradePartner.TrainerName}. Waiting for a Pokémon..."); if (poke.Type == PokeTradeType.Dump) @@ -332,76 +339,98 @@ private async Task PerformLinkCodeTrade(SAV9ZA sav, PokeTradeDe return result; } - // Watch their status to indicate they have offered a Pokémon as well. - var offering = await ReadUntilChanged(TradePartnerStatusOffset, [0x3], 25_000, 1_000, true, true, token).ConfigureAwait(false); - if (!offering) + // multi trade support, need more tests + List batchPA9s = AbstractTrade.GetPKMsFromPokeTradeDetail(poke); + List skipAutoOTList = AbstractTrade.GetSkipAutoOTListFromPokeTradeDetail(poke); + PA9 received = default!; + LogUtil.LogInfo($"count:{batchPA9s.Count}, skipAutoOTList:{String.Join(',', skipAutoOTList)}", nameof(PokeTradeBotLZA)); + for (var i = 0; i < batchPA9s.Count; i++) { - await ResetToLinkPlay(token).ConfigureAwait(false); - return PokeTradeResult.TrainerTooSlow; - } + toSend = batchPA9s[i]; + if (i > 0 && toSend.Species != 0) + { + if (Hub.Config.Legality.UseTradePartnerInfo && !skipAutoOTList[i]) + { + await SetBoxPkmWithSwappedIDDetailsLZA(toSend, tradePartner, sav, token); + } + else + { + await SetBoxPokemonAbsolute(BoxStartOffset, toSend, token, sav).ConfigureAwait(false); + } + await Task.Delay(1_000, token).ConfigureAwait(false); // give some time for the game to process the new Pokémon + } + if (batchPA9s.Count > 1) poke.SendNotification(this, $"批量:等待交换第{i + 1}只宝可梦{ShowdownTranslator.GameStringsZh.Species[toSend.Species]}"); - Log("Checking offered Pokémon."); - // If we got to here, we can read their offered Pokémon. + // Watch their status to indicate they have offered a Pokémon as well. + var offering = await ReadUntilChanged(TradePartnerStatusOffset, [0x3], 25_000, 1_000, true, true, token).ConfigureAwait(false); + if (!offering) + { + await ResetToLinkPlay(token).ConfigureAwait(false); + return PokeTradeResult.TrainerTooSlow; + } - // Wait for user input... Needs to be different from the previously offered Pokémon. - var offered = await ReadUntilPresentPointer(Offsets.LinkTradePartnerPokemonPointer, 3_000, 0_050, BoxFormatSlotSize, token).ConfigureAwait(false); - if (offered == null || offered.Species == 0 || !offered.ChecksumValid) - { - Log("Trade ended because trainer offer was rescinded too quickly."); - await ResetToLinkPlay(token).ConfigureAwait(false); - return PokeTradeResult.TrainerOfferCanceledQuick; - } - offered.Heal(); - offered.RefreshChecksum(); + Log("Checking offered Pokémon."); + // If we got to here, we can read their offered Pokémon. - var trainer = new PartnerDataHolder(0, tradePartner.TrainerName, tradePartner.TID7); - (toSend, PokeTradeResult update) = await GetEntityToSend(sav, poke, offered, toSend, trainer, token).ConfigureAwait(false); - if (update != PokeTradeResult.Success) - { - await ResetToLinkPlay(token).ConfigureAwait(false); - return update; - } + // Wait for user input... Needs to be different from the previously offered Pokémon. + var offered = await ReadUntilPresentPointer(Offsets.LinkTradePartnerPokemonPointer, 3_000, 0_050, BoxFormatSlotSize, token).ConfigureAwait(false); + if (offered == null || offered.Species == 0 || !offered.ChecksumValid) + { + Log("Trade ended because trainer offer was rescinded too quickly."); + await ResetToLinkPlay(token).ConfigureAwait(false); + return PokeTradeResult.TrainerOfferCanceledQuick; + } + offered.Heal(); + offered.RefreshChecksum(); - if (Hub.Config.Trade.DisallowTradeEvolve && TradeEvolutions.WillTradeEvolve(offered.Species, offered.Form, offered.HeldItem, toSend.Species)) - { - Log("Trade cancelled because trainer offered a Pokémon that would evolve upon trade."); - await ResetToLinkPlay(token).ConfigureAwait(false); - return PokeTradeResult.TradeEvolveNotAllowed; - } + var trainer = new PartnerDataHolder(0, tradePartner.TrainerName, tradePartner.TID7); + (toSend, PokeTradeResult update) = await GetEntityToSend(sav, poke, offered, toSend, trainer, token).ConfigureAwait(false); + if (update != PokeTradeResult.Success) + { + await ResetToLinkPlay(token).ConfigureAwait(false); + return update; + } - Log("Confirming trade."); - var tradeResult = await ConfirmAndStartTrading(poke, token).ConfigureAwait(false); - if (tradeResult != PokeTradeResult.Success) - { - if (tradeResult == PokeTradeResult.TrainerLeft) - Log("Trade canceled because trainer left the trade."); - await ResetToLinkPlay(token).ConfigureAwait(false); - return tradeResult; - } + if (Hub.Config.Trade.DisallowTradeEvolve && TradeEvolutions.WillTradeEvolve(offered.Species, offered.Form, offered.HeldItem, toSend.Species)) + { + Log("Trade cancelled because trainer offered a Pokémon that would evolve upon trade."); + await ResetToLinkPlay(token).ConfigureAwait(false); + return PokeTradeResult.TradeEvolveNotAllowed; + } - if (token.IsCancellationRequested) - { - await ResetToOverworld(token).ConfigureAwait(false); - return PokeTradeResult.RoutineCancel; - } + Log("Confirming trade."); + var tradeResult = await ConfirmAndStartTrading(poke, token).ConfigureAwait(false); + if (tradeResult != PokeTradeResult.Success) + { + if (tradeResult == PokeTradeResult.TrainerLeft) + Log("Trade canceled because trainer left the trade."); + await ResetToLinkPlay(token).ConfigureAwait(false); + return tradeResult; + } - // Trade was successful! - var received = await ReadPokemon(BoxStartOffset, BoxFormatSlotSize, token).ConfigureAwait(false); - // Pokémon in b1s1 is same as the one they were supposed to receive (was never sent). - if (SearchUtil.HashByDetails(received) == SearchUtil.HashByDetails(toSend) && received.Checksum == toSend.Checksum) - { - Log("User did not complete the trade."); - await ResetToLinkPlay(token).ConfigureAwait(false); - return PokeTradeResult.TrainerTooSlow; + if (token.IsCancellationRequested) + { + await ResetToOverworld(token).ConfigureAwait(false); + return PokeTradeResult.RoutineCancel; + } + + // Trade was successful! + received = await ReadPokemon(BoxStartOffset, BoxFormatSlotSize, token).ConfigureAwait(false); + // Pokémon in b1s1 is same as the one they were supposed to receive (was never sent). + if (SearchUtil.HashByDetails(received) == SearchUtil.HashByDetails(toSend) && received.Checksum == toSend.Checksum) + { + Log("User did not complete the trade."); + await ResetToLinkPlay(token).ConfigureAwait(false); + return PokeTradeResult.TrainerTooSlow; + } + // Only log if we completed the trade. + UpdateCountsAndExport(poke, received, toSend); } // As long as we got rid of our inject in b1s1, assume the trade went through. Log("User completed the trade."); poke.TradeFinished(this, received); - // Only log if we completed the trade. - UpdateCountsAndExport(poke, received, toSend); - // Log for Trade Abuse tracking. LogSuccessfulTrades(poke, tradePartner.NID, tradePartner.TrainerName); @@ -713,15 +742,19 @@ private async Task GetTradePartnerInfo(CancellationToken token) { // Data is loaded here, so we can read TID and OT from here. var tid = chunk.AsSpan(0x44, 4).ToArray(); + var gender = chunk[0x48]; + var language = chunk[0x49]; var name = chunk.AsSpan(0x4C, TradePartnerLZA.MaxByteLengthStringObject).ToArray(); - return new TradePartnerLZA(nid, tid, name); + return new TradePartnerLZA(nid, tid, name, gender, language); } // Data is not loaded at the expected place, so we have to read TID and OT from the fallback location. { chunk = await SwitchConnection.ReadBytesAbsoluteAsync(TradePartnerTIDOffset + FallBackTradePartnerDataShift, 34, token).ConfigureAwait(false); var tid = chunk.AsSpan(0, 4).ToArray(); + var gender = chunk[4]; + var language = chunk[5]; var name = chunk.AsSpan(0x8, TradePartnerLZA.MaxByteLengthStringObject).ToArray(); - return new TradePartnerLZA(nid, tid, name); + return new TradePartnerLZA(nid, tid, name, gender, language); } } @@ -886,4 +919,106 @@ private void UpdateBarrier(bool shouldWait) Log($"Left the Barrier. Count: {Hub.BotSync.Barrier.ParticipantCount}"); } } + // based on https://github.com/hexbyt3/PokeBot/blob/master/SysBot.Pokemon/PLZA/BotTrade/PokeTradeBotPLZA.cs + private async Task SetBoxPkmWithSwappedIDDetailsLZA(PA9 toSend, TradePartnerLZA tradePartner, SAV9ZA sav, CancellationToken token) + { + // Sanity check: if trade partner OT is empty, skip AutoOT + if (string.IsNullOrWhiteSpace(tradePartner.TrainerName)) + { + return; + } + + if (toSend.Version == GameVersion.GO) + { + var goClone = toSend.Clone(); + goClone.OriginalTrainerName = tradePartner.TrainerName; + + ClearOTTrash(goClone, tradePartner); + + if (!toSend.ChecksumValid) + goClone.RefreshChecksum(); + + await SetBoxPokemonAbsolute(BoxStartOffset, goClone, token, sav).ConfigureAwait(false); + return; + } + + if (toSend is IHomeTrack pk && pk.HasTracker) + { + return; + } + + if (toSend.Generation != toSend.Format) + { + return; + } + + bool isMysteryGift = toSend.FatefulEncounter; + var cln = toSend.Clone(); + + // Apply trainer info (OT, TID, SID, Gender) + ApplyTrainerInfo(cln, tradePartner); + + if (!isMysteryGift) + { + // Validate language ID - if invalid, default to English (2) + int language = tradePartner.Language; + if (language < 1 || language > 12) // Valid language IDs are 1-12 + language = 2; // English + cln.Language = language; + } + + ClearOTTrash(cln, tradePartner); + + // Hard-code version to ZA since PLZA only has one game version + cln.Version = GameVersion.ZA; + + // Set nickname to species name in the Pokemon's language using PKHeX's method + // This properly handles generation-specific formatting and language-specific names + if (!toSend.IsNicknamed) + cln.ClearNickname(); + + // Clear handler info - make it look like trade partner is OT and never traded it + cln.CurrentHandler = 0; // 0 = OT is current handler + + if (toSend.IsShiny) + cln.PID = (uint)((cln.TID16 ^ cln.SID16 ^ (cln.PID & 0xFFFF) ^ toSend.ShinyXor) << 16) | (cln.PID & 0xFFFF); + + cln.RefreshChecksum(); + + var tradeSV = new LegalityAnalysis(cln); + + if (tradeSV.Valid) + { + // Don't pass sav - we've already set handler info and don't want UpdateHandler to overwrite it + await SetBoxPokemonAbsolute(BoxStartOffset, cln, token, null).ConfigureAwait(false); + return; + } + } + + private static void ClearOTTrash(PA9 pokemon, TradePartnerLZA tradePartner) + { + Span trash = pokemon.OriginalTrainerTrash; + trash.Clear(); + string name = tradePartner.TrainerName; + int maxLength = trash.Length / 2; + int actualLength = Math.Min(name.Length, maxLength); + for (int i = 0; i < actualLength; i++) + { + char value = name[i]; + trash[i * 2] = (byte)value; + trash[(i * 2) + 1] = (byte)(value >> 8); + } + if (actualLength < maxLength) + { + trash[actualLength * 2] = 0x00; + trash[(actualLength * 2) + 1] = 0x00; + } + } + private static void ApplyTrainerInfo(PA9 pokemon, TradePartnerLZA partner) + { + pokemon.OriginalTrainerGender = (byte)partner.Gender; + pokemon.TrainerTID7 = uint.Parse(partner.TID7); + pokemon.TrainerSID7 = uint.Parse(partner.SID7); + pokemon.OriginalTrainerName = partner.TrainerName; + } } diff --git a/SysBot.Pokemon/LZA/BotTrade/TradePartnerLZA.cs b/SysBot.Pokemon/LZA/BotTrade/TradePartnerLZA.cs index 4555f2ce5..6be49ece8 100644 --- a/SysBot.Pokemon/LZA/BotTrade/TradePartnerLZA.cs +++ b/SysBot.Pokemon/LZA/BotTrade/TradePartnerLZA.cs @@ -11,7 +11,13 @@ public sealed class TradePartnerLZA public string SID7 { get; } public string TrainerName { get; } - public TradePartnerLZA(ulong ID, byte[] TIDSID, byte[] trainerNameObject) + public int Game => (int)GameVersion.ZA; + + public int Gender { get; } + + public int Language { get; } + + public TradePartnerLZA(ulong ID, byte[] TIDSID, byte[] trainerNameObject, int gender, int language) { NID = ID; @@ -21,6 +27,9 @@ public TradePartnerLZA(ulong ID, byte[] TIDSID, byte[] trainerNameObject) SID7 = $"{tidsid / 1_000_000:0000}"; TrainerName = StringConverter8.GetString(trainerNameObject); + + Gender = gender; + Language = language; } public const int MaxByteLengthStringObject = 26; From b3e91be998b1c351568595a0ff65e09ad2c0cdc9 Mon Sep 17 00:00:00 2001 From: Easy World Date: Tue, 18 Nov 2025 14:04:41 +0800 Subject: [PATCH 158/171] Update Kook.Net package to version 0.10.3 Upgraded Kook.Net dependency from 0.9.11 to 0.10.3 in the project file to ensure compatibility with the latest features and bug fixes. --- SysBot.Pokemon.Kook/SysBot.Pokemon.Kook.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SysBot.Pokemon.Kook/SysBot.Pokemon.Kook.csproj b/SysBot.Pokemon.Kook/SysBot.Pokemon.Kook.csproj index d87ff30f2..17fb0e6e0 100644 --- a/SysBot.Pokemon.Kook/SysBot.Pokemon.Kook.csproj +++ b/SysBot.Pokemon.Kook/SysBot.Pokemon.Kook.csproj @@ -7,7 +7,7 @@ - + From 5964ccb1068021bf54016e238ffe81a0be28d624 Mon Sep 17 00:00:00 2001 From: Easy World Date: Tue, 18 Nov 2025 22:42:10 +0800 Subject: [PATCH 159/171] Add bulk PKM file download and improve helper usage Introduces DownloadPKMsAsync to NetUtil for downloading and processing multiple PKM files from attachments using reflection and FileTradeHelper. Refactors FileTradeHelper to use collection expressions for improved readability and updates TradeModule to accept a Uri parameter for file trading. --- .../Commands/Bots/TradeModule.cs | 2 +- SysBot.Pokemon.Kook/Helpers/NetUtil.cs | 108 ++++++++++++++++++ SysBot.Pokemon/Helpers/FileTradeHelper.cs | 4 +- 3 files changed, 111 insertions(+), 3 deletions(-) diff --git a/SysBot.Pokemon.Kook/Commands/Bots/TradeModule.cs b/SysBot.Pokemon.Kook/Commands/Bots/TradeModule.cs index 1c50f80ea..948a115e1 100644 --- a/SysBot.Pokemon.Kook/Commands/Bots/TradeModule.cs +++ b/SysBot.Pokemon.Kook/Commands/Bots/TradeModule.cs @@ -131,7 +131,7 @@ public Task TradeAsync([Summary("Showdown Set")][Remainder] string content) [Alias("t")] [Summary("Makes the bot trade you the attached file.")] [RequireQueueRole(nameof(KookManager.RolesTrade))] - public Task TradeAsyncAttach() + public Task TradeAsyncAttach(Uri file) { var code = Info.GetRandomTradeCode(); return TradeAsyncAttach(code); diff --git a/SysBot.Pokemon.Kook/Helpers/NetUtil.cs b/SysBot.Pokemon.Kook/Helpers/NetUtil.cs index 4c22eaee0..78da4d4ba 100644 --- a/SysBot.Pokemon.Kook/Helpers/NetUtil.cs +++ b/SysBot.Pokemon.Kook/Helpers/NetUtil.cs @@ -1,6 +1,10 @@ using Kook; using PKHeX.Core; +using SysBot.Pokemon.Helpers; +using System; +using System.Collections; using System.Net.Http; +using System.Reflection; using System.Threading.Tasks; namespace SysBot.Pokemon.Kook; @@ -13,6 +17,110 @@ public static async Task DownloadFromUrlAsync(string url) return await client.GetByteArrayAsync(url).ConfigureAwait(false); } + private static readonly HashSet SupportedTypes = + [ + typeof(PK8), typeof(PA8), typeof(PB8), typeof(PK9), typeof(PA9), + ]; + + public static async Task>> DownloadPKMsAsync(IAttachment att, Type type) + { + // 统一使用本地函数生成错误结果 + static Download> Error(string name, string msg) + { + return new() + { + SanitizedFileName = name, + ErrorMessage = $"{name}: {msg}" + }; + } + + var fileName = Format.Sanitize(att.Filename); + if (string.IsNullOrWhiteSpace(fileName)) + { + return new Download> + { + SanitizedFileName = "unknown", + ErrorMessage = "Invalid filename." + }; + } + + if (!SupportedTypes.Contains(type)) + return Error(fileName, "Unsupported PKM type."); + + var url = att.Url; + + byte[]? buffer; + try + { + buffer = await DownloadFromUrlAsync(url).ConfigureAwait(false); + } + catch (Exception ex) + { + return Error(fileName, $"Failed to download file: {ex.Message}"); + } + + if (buffer is null || buffer.Length == 0) + return Error(fileName, "Empty or failed download."); + + try + { + // 构造泛型类型 FileTradeHelper + var helperType = typeof(FileTradeHelper<>).MakeGenericType(type); + + // 取得 ValidBinFileSize(int length) 静态方法 + var validMethod = helperType.GetMethod( + "ValidBinFileSize", + BindingFlags.Static | BindingFlags.Public, + binder: null, + types: [typeof(int)], + modifiers: null + ); + if (validMethod is null) + return Error(fileName, "Helper type missing ValidBinFileSize method."); + + // 取得 Bin2List(byte[]) 静态方法 + var bin2ListMethod = helperType.GetMethod( + "Bin2List", + BindingFlags.Static | BindingFlags.Public, + binder: null, + types: [typeof(byte[])], + modifiers: null + ); + if (bin2ListMethod is null) + return Error(fileName, "Helper type missing Bin2List method."); + + // 调用 ValidBinFileSize + if (validMethod.Invoke(null, [buffer.Length]) is not bool valid) + return Error(fileName, "Invalid return type from ValidBinFileSize."); + + if (!valid) + return Error(fileName, "Invalid .bin attachment size."); + + // 调用 Bin2List(byte[]),应返回 IEnumerable / List + var listObj = bin2ListMethod.Invoke(null, [buffer]); + if (listObj is not IEnumerable enumerable) + return Error(fileName, "Unexpected return type from Bin2List."); + + var pkms = enumerable.Cast().ToList(); + + return new Download> + { + SanitizedFileName = fileName, + Data = pkms, + Success = true + }; + } + catch (TargetInvocationException ex) + { + // 反射内层异常 + return Error(fileName, $"Exception while processing file (inner): {ex.InnerException?.Message ?? ex.Message}"); + } + catch (Exception ex) + { + return Error(fileName, $"Exception while processing file: {ex.Message}"); + } + } + public static async Task> DownloadPKMAsync(IAttachment att) { var result = new Download { SanitizedFileName = Format.Sanitize(att.Filename) }; diff --git a/SysBot.Pokemon/Helpers/FileTradeHelper.cs b/SysBot.Pokemon/Helpers/FileTradeHelper.cs index 3df50acc1..152ae941c 100644 --- a/SysBot.Pokemon/Helpers/FileTradeHelper.cs +++ b/SysBot.Pokemon/Helpers/FileTradeHelper.cs @@ -20,11 +20,11 @@ public static List Bin2List(byte[] bb) if (pkmSize[typeof(T)] == bb.Length) { var tp = GetPKM(bb); - if (tp != null && tp.Species > 0 && tp.Valid && tp is T pkm) return new List() { pkm }; + if (tp != null && tp.Species > 0 && tp.Valid && tp is T pkm) return [pkm]; } int size = pkmSizeInBin[typeof(T)]; int times = bb.Length % size == 0 ? (bb.Length / size) : (bb.Length / size + 1); - List pkmBytes = new(); + List pkmBytes = []; for (var i = 0; i < times; i++) { int start = i * size; From a18fb50f05c07d19c17cf68f6037b3306c34bf4d Mon Sep 17 00:00:00 2001 From: Easy World Date: Wed, 19 Nov 2025 14:49:19 +0800 Subject: [PATCH 160/171] Include TID and SID in trade partner notifications Updated trade partner notification messages across all bot trade implementations to include both Trainer ID (TID) and Secret ID (SID) for improved clarity. Refactored SWSH bot to retrieve both TID and SID together and adjusted related logging for consistency. --- SysBot.Pokemon.Dodo/DodoTradeNotifier.cs | 2 +- SysBot.Pokemon/BDSP/BotTrade/PokeTradeBotBS.cs | 2 +- SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs | 2 +- SysBot.Pokemon/LZA/BotTrade/PokeTradeBotLZA.cs | 2 +- SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs | 2 +- .../SWSH/BotTrade/PokeTradeBotSWSH.cs | 17 +++++++++++------ 6 files changed, 16 insertions(+), 11 deletions(-) diff --git a/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs b/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs index a93249a7d..55024dbb8 100644 --- a/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs +++ b/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs @@ -34,7 +34,7 @@ public DodoTradeNotifier(T data, PokeTradeTrainerInfo info, int code, string use public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, string message) { LogUtil.LogText(message); - if (message.Contains("Found Trading Partner:")) + if (message.Contains("Found Link Trade partner:")) { Regex regex = new Regex("TID: (\\d+)"); string tid = regex.Match(message).Groups[1].ToString(); diff --git a/SysBot.Pokemon/BDSP/BotTrade/PokeTradeBotBS.cs b/SysBot.Pokemon/BDSP/BotTrade/PokeTradeBotBS.cs index 6ac021bd2..0092d45ac 100644 --- a/SysBot.Pokemon/BDSP/BotTrade/PokeTradeBotBS.cs +++ b/SysBot.Pokemon/BDSP/BotTrade/PokeTradeBotBS.cs @@ -311,7 +311,7 @@ private async Task PerformLinkCodeTrade(SAV8BS sav, PokeTradeDe await Click(A, 0_500, token).ConfigureAwait(false); } - poke.SendNotification(this, $"Found Link Trade partner: {tradePartner.TrainerName}. Waiting for a Pokémon..."); + poke.SendNotification(this, $"Found Link Trade partner: {tradePartner.TrainerName} TID: {tradePartner.TID7} SID: {tradePartner.SID7}. Waiting for a Pokémon..."); // Requires at least one trade for this pointer to make sense, so cache it here. LinkTradePokemonOffset = await SwitchConnection.PointerAll(Offsets.LinkTradePartnerPokemonPointer, token).ConfigureAwait(false); diff --git a/SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs b/SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs index f41d97e2e..01799d931 100644 --- a/SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs +++ b/SysBot.Pokemon/LA/BotTrade/PokeTradeBotLA.cs @@ -283,7 +283,7 @@ private async Task PerformLinkCodeTrade(SAV8LA sav, PokeTradeDe return partnerCheck; } - poke.SendNotification(this, $"Found Link Trade partner: {tradePartner.TrainerName}. Waiting for a Pokémon..."); + poke.SendNotification(this, $"Found Link Trade partner: {tradePartner.TrainerName} TID: {tradePartner.TID7} SID: {tradePartner.SID7}. Waiting for a Pokémon..."); if (poke.Type == PokeTradeType.Dump) { diff --git a/SysBot.Pokemon/LZA/BotTrade/PokeTradeBotLZA.cs b/SysBot.Pokemon/LZA/BotTrade/PokeTradeBotLZA.cs index 96e268080..9d1f46b6e 100644 --- a/SysBot.Pokemon/LZA/BotTrade/PokeTradeBotLZA.cs +++ b/SysBot.Pokemon/LZA/BotTrade/PokeTradeBotLZA.cs @@ -330,7 +330,7 @@ private async Task PerformLinkCodeTrade(SAV9ZA sav, PokeTradeDe await SetBoxPkmWithSwappedIDDetailsLZA(toSend, tradePartner, sav, token); } - poke.SendNotification(this, $"Found Link Trade partner: {tradePartner.TrainerName}. Waiting for a Pokémon..."); + poke.SendNotification(this, $"Found Link Trade partner: {tradePartner.TrainerName} TID: {tradePartner.TID7} SID: {tradePartner.SID7}. Waiting for a Pokémon..."); if (poke.Type == PokeTradeType.Dump) { diff --git a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs index 7117c236c..4741ca721 100644 --- a/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs +++ b/SysBot.Pokemon/SV/BotTrade/PokeTradeBotSV.cs @@ -348,7 +348,7 @@ private async Task PerformLinkCodeTrade(SAV9SV sav, PokeTradeDe return PokeTradeResult.TrainerTooSlow; } - poke.SendNotification(this, $"Found Link Trade partner: {tradePartner.TrainerName}. TID: {tradePartner.TID7} SID: {tradePartner.SID7} Waiting for a Pokémon..."); + poke.SendNotification(this, $"Found Link Trade partner: {tradePartner.TrainerName} TID: {tradePartner.TID7} SID: {tradePartner.SID7} Waiting for a Pokémon..."); if (poke.Type == PokeTradeType.Dump) { diff --git a/SysBot.Pokemon/SWSH/BotTrade/PokeTradeBotSWSH.cs b/SysBot.Pokemon/SWSH/BotTrade/PokeTradeBotSWSH.cs index 7a1c320cf..d1c4d082d 100644 --- a/SysBot.Pokemon/SWSH/BotTrade/PokeTradeBotSWSH.cs +++ b/SysBot.Pokemon/SWSH/BotTrade/PokeTradeBotSWSH.cs @@ -304,7 +304,9 @@ private async Task PerformLinkCodeTrade(SAV8SWSH sav, PokeTrade await Task.Delay(5_500 + hub.Config.Timings.ExtraTimeOpenBox, token).ConfigureAwait(false); // necessary delay to get to the box properly var trainerName = await GetTradePartnerName(TradeMethod.LinkTrade, token).ConfigureAwait(false); - var trainerTID = await GetTradePartnerTID7(TradeMethod.LinkTrade, token).ConfigureAwait(false); + var trainerTIDSID = await GetTradePartnerTIDSID7(TradeMethod.LinkTrade, token).ConfigureAwait(false); + var trainerTID = trainerTIDSID.Item1; + var trainerSID = trainerTIDSID.Item2; var trainerNID = await GetTradePartnerNID(token).ConfigureAwait(false); RecordUtil.Record($"Initiating\t{trainerNID:X16}\t{trainerName}\t{poke.Trainer.TrainerName}\t{poke.Trainer.ID}\t{poke.ID}\t{toSend.EncryptionConstant:X8}"); Log($"Found Link Trade partner: {trainerName}-{trainerTID} (ID: {trainerNID})"); @@ -334,7 +336,7 @@ private async Task PerformLinkCodeTrade(SAV8SWSH sav, PokeTrade await Click(A, 0_500, token).ConfigureAwait(false); } - poke.SendNotification(this, $"Found Link Trade partner: {trainerName}. Waiting for a Pokémon..."); + poke.SendNotification(this, $"Found Link Trade partner: {trainerName} TID: {trainerTID} SID: {trainerSID}. Waiting for a Pokémon..."); if (poke.Type == PokeTradeType.Dump) return await ProcessDumpTradeAsync(poke, token).ConfigureAwait(false); @@ -742,10 +744,12 @@ private async Task PerformSurpriseTrade(SAV8SWSH sav, PK8 pkm, await Task.Delay(7_000, token).ConfigureAwait(false); var TrainerName = await GetTradePartnerName(TradeMethod.SurpriseTrade, token).ConfigureAwait(false); - var TrainerTID = await GetTradePartnerTID7(TradeMethod.SurpriseTrade, token).ConfigureAwait(false); + var TrainerTIDSID = await GetTradePartnerTIDSID7(TradeMethod.SurpriseTrade, token).ConfigureAwait(false); + var TrainerTID = TrainerTIDSID.Item1; + var TrainerSID = TrainerTIDSID.Item2; var SurprisePoke = await ReadSurpriseTradePokemon(token).ConfigureAwait(false); - Log($"Found Surprise Trade partner: {TrainerName}-{TrainerTID}, Pokémon: {(Species)SurprisePoke.Species}"); + Log($"Found Surprise Trade partner: {TrainerName}-{TrainerTID}-{TrainerSID}, Pokémon: {(Species)SurprisePoke.Species}"); // Clear out the received trade data; we want to skip the trade animation. // The box slot locks have been removed prior to searching. @@ -964,14 +968,15 @@ private async Task GetTradePartnerName(TradeMethod tradeMethod, Cancella return StringConverter8.GetString(data); } - private async Task GetTradePartnerTID7(TradeMethod tradeMethod, CancellationToken token) + private async Task<(string,string)> GetTradePartnerTIDSID7(TradeMethod tradeMethod, CancellationToken token) { var ofs = GetTrainerTIDSIDOffset(tradeMethod); var data = await Connection.ReadBytesAsync(ofs, 8, token).ConfigureAwait(false); var tidsid = BitConverter.ToUInt32(data, 0); var tid7 = $"{tidsid % 1_000_000:000000}"; - return tid7; + var sid7 = $"{tidsid / 1_000_000:000000}"; + return (tid7,sid7); } public async Task GetTradePartnerNID(CancellationToken token) From 956c6569a0ab25fd6371ccf9ea7f7b16f1c62243 Mon Sep 17 00:00:00 2001 From: Easy World Date: Sun, 23 Nov 2025 21:03:49 +0800 Subject: [PATCH 161/171] Update PKHeX.Core.AutoMod.dll dependency Replaces the PKHeX.Core.AutoMod.dll binary with a newer version to incorporate upstream changes or bug fixes. --- SysBot.Pokemon/deps/PKHeX.Core.AutoMod.dll | Bin 165888 -> 153600 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/SysBot.Pokemon/deps/PKHeX.Core.AutoMod.dll b/SysBot.Pokemon/deps/PKHeX.Core.AutoMod.dll index 63506768a5890960874c99ec051e1500e3481b06..636bd8e47e0b589cb0d3ccbad66eb299ca3b8a88 100644 GIT binary patch literal 153600 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

-/// Same as with extra consideration for bots accepting Queue requests. -/// -public sealed class RequireQueueRoleAttribute(string RoleName) : PreconditionAttribute -{ - // Create a field to store the specified name - - // Create a constructor so the name can be specified - - public override Task CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services) - { - var mgr = KookBotSettings.Manager; - if (mgr.Config.AllowGlobalSudo && mgr.CanUseSudo(context.User.Id)) - return Task.FromResult(PreconditionResult.FromSuccess()); - - // Check if this user is a Guild User, which is the only context where roles exist - if (context.User is not SocketGuildUser gUser) - return Task.FromResult(PreconditionResult.FromError("You must be sending the message from a guild to run this command.")); - - var roles = gUser.Roles; - if (mgr.CanUseSudo(roles.Select(z => z.Name))) - return Task.FromResult(PreconditionResult.FromSuccess()); - - bool canQueue = KookBotSettings.HubConfig.Queues.CanQueue; - if (!canQueue) - return Task.FromResult(PreconditionResult.FromError("Sorry, I am not currently accepting queue requests!")); - - if (!mgr.GetHasRoleAccess(RoleName, roles.Select(z => z.Name))) - return Task.FromResult(PreconditionResult.FromError("You do not have the required role to run this command.")); - - return Task.FromResult(PreconditionResult.FromSuccess()); - } -} diff --git a/SysBot.Pokemon.Kook/Helpers/RequireRoleAccessAttribute.cs b/SysBot.Pokemon.Kook/Helpers/RequireRoleAccessAttribute.cs deleted file mode 100644 index 432815687..000000000 --- a/SysBot.Pokemon.Kook/Helpers/RequireRoleAccessAttribute.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Kook.Commands; -using Kook.WebSocket; -using System; -using System.Linq; -using System.Threading.Tasks; - -namespace SysBot.Pokemon.Kook; - -/// -/// Requires an assigned role in order to accept commands. Can be used by sudo users if satisfied. -/// -public sealed class RequireRoleAccessAttribute(string RoleName) : PreconditionAttribute -{ - // Create a field to store the specified name - - // Create a constructor so the name can be specified - - public override Task CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services) - { - var mgr = KookBotSettings.Manager; - if (mgr.Config.AllowGlobalSudo && mgr.CanUseSudo(context.User.Id)) - return Task.FromResult(PreconditionResult.FromSuccess()); - - // Check if this user is a Guild User, which is the only context where roles exist - if (context.User is not SocketGuildUser gUser) - return Task.FromResult(PreconditionResult.FromError("You must be sending the message from a guild to run this command.")); - - var roles = gUser.Roles; - if (mgr.CanUseSudo(roles.Select(z => z.Name))) - return Task.FromResult(PreconditionResult.FromSuccess()); - - if (!mgr.GetHasRoleAccess(RoleName, roles.Select(z => z.Name))) - return Task.FromResult(PreconditionResult.FromError("You do not have the required role to run this command.")); - - return Task.FromResult(PreconditionResult.FromSuccess()); - } -} diff --git a/SysBot.Pokemon.Kook/Helpers/RequireRoleAttribute.cs b/SysBot.Pokemon.Kook/Helpers/RequireRoleAttribute.cs deleted file mode 100644 index 9d8d10d68..000000000 --- a/SysBot.Pokemon.Kook/Helpers/RequireRoleAttribute.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Kook.Commands; -using Kook.WebSocket; -using System; -using System.Linq; -using System.Threading.Tasks; - -namespace SysBot.Pokemon.Kook; - -public sealed class RequireRoleAttribute(string RoleName) : PreconditionAttribute -{ - // Create a field to store the specified name - - // Create a constructor so the name can be specified - - // Override the CheckPermissions method - public override Task CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services) - { - // Since no async work is done, the result has to be wrapped with `Task.FromResult` to avoid compiler errors - - // Check if this user is a Guild User, which is the only context where roles exist - if (context.User is not SocketGuildUser gUser) - return Task.FromResult(PreconditionResult.FromError("You must be in a guild to run this command.")); - - // If this command was executed by a user with the appropriate role, return a success - if (gUser.Roles.Any(r => r.Name == RoleName)) - return Task.FromResult(PreconditionResult.FromSuccess()); - - // Since it wasn't, fail - return Task.FromResult(PreconditionResult.FromError($"You must have a role named {RoleName} to run this command.")); - } -} diff --git a/SysBot.Pokemon.Kook/Helpers/RequireSudoAttribute.cs b/SysBot.Pokemon.Kook/Helpers/RequireSudoAttribute.cs deleted file mode 100644 index c7b239fdf..000000000 --- a/SysBot.Pokemon.Kook/Helpers/RequireSudoAttribute.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Kook.Commands; -using Kook.WebSocket; -using System; -using System.Linq; -using System.Threading.Tasks; - -namespace SysBot.Pokemon.Kook; - -public sealed class RequireSudoAttribute : PreconditionAttribute -{ - // Override the CheckPermissions method - public override Task CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services) - { - var mgr = KookBotSettings.Manager; - if (mgr.Config.AllowGlobalSudo && mgr.CanUseSudo(context.User.Id)) - return Task.FromResult(PreconditionResult.FromSuccess()); - - // Check if this user is a Guild User, which is the only context where roles exist - if (context.User is not SocketGuildUser gUser) - return Task.FromResult(PreconditionResult.FromError("You must be in a guild to run this command.")); - - if (mgr.CanUseSudo(gUser.Roles.Select(z => z.Name))) - return Task.FromResult(PreconditionResult.FromSuccess()); - - // Since it wasn't, fail - return Task.FromResult(PreconditionResult.FromError("You are not permitted to run this command.")); - } -} diff --git a/SysBot.Pokemon.Kook/Helpers/ReusableActions.cs b/SysBot.Pokemon.Kook/Helpers/ReusableActions.cs deleted file mode 100644 index 0660a741e..000000000 --- a/SysBot.Pokemon.Kook/Helpers/ReusableActions.cs +++ /dev/null @@ -1,88 +0,0 @@ -using Kook; -using Kook.WebSocket; -using PKHeX.Core; -using SysBot.Base; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; - -namespace SysBot.Pokemon.Kook; - -public static class ReusableActions -{ - public static async Task SendPKMAsync(this IMessageChannel channel, PKM pkm, string msg = "") - { - var tmp = Path.Combine(Path.GetTempPath(), PathUtil.CleanFileName(pkm.FileName)); - await File.WriteAllBytesAsync(tmp, pkm.DecryptedPartyData); - await channel.SendFileAsync(tmp, msg).ConfigureAwait(false); - File.Delete(tmp); - } - - public static async Task SendPKMAsync(this IUser user, PKM pkm, string msg = "") - { - var tmp = Path.Combine(Path.GetTempPath(), PathUtil.CleanFileName(pkm.FileName)); - await File.WriteAllBytesAsync(tmp, pkm.DecryptedPartyData); - await user.SendFileAsync(tmp, msg).ConfigureAwait(false); - File.Delete(tmp); - } - - public static async Task RepostPKMAsShowdownAsync(this ISocketMessageChannel channel, IAttachment att) - { - if (!EntityDetection.IsSizePlausible(att.Size??0)) - return; - var result = await NetUtil.DownloadPKMAsync(att).ConfigureAwait(false); - if (!result.Success) - return; - - var pkm = result.Data!; - await channel.SendPKMAsShowdownSetAsync(pkm).ConfigureAwait(false); - } - - public static RequestSignificance GetFavor(this IUser user) - { - var mgr = KookBotSettings.Manager; - if (user.Id == mgr.Owner) - return RequestSignificance.Owner; - if (mgr.CanUseSudo(user.Id)) - return RequestSignificance.Favored; - if (user is SocketGuildUser g) - return mgr.GetSignificance(g.Roles.Select(z => z.Name)); - return RequestSignificance.None; - } - - public static async Task EchoAndReply(this ISocketMessageChannel channel, string msg) - { - // Announce it in the channel the command was entered only if it's not already an echo channel. - EchoUtil.Echo(msg); - if (!EchoModule.IsEchoChannel(channel)) - await channel.SendTextAsync(msg).ConfigureAwait(false); - } - - public static async Task SendPKMAsShowdownSetAsync(this ISocketMessageChannel channel, PKM pkm) - { - var txt = GetFormattedShowdownText(pkm); - await channel.SendTextAsync(txt).ConfigureAwait(false); - } - - public static string GetFormattedShowdownText(PKM pkm) - { - var showdown = ShowdownParsing.GetShowdownText(pkm); - return Format.Code(showdown); - } - - private static readonly string[] separator = [ ",", ", ", " " ]; - - public static IReadOnlyList GetListFromString(string str) - { - // Extract comma separated list - return str.Split(separator, StringSplitOptions.RemoveEmptyEntries); - } - - public static string StripCodeBlock(string str) => str - .Replace("`\n", "") - .Replace("\n`", "") - .Replace("`", "") - .Trim(); -} diff --git a/SysBot.Pokemon.Kook/KookBot.cs b/SysBot.Pokemon.Kook/KookBot.cs index 09083106d..835d93677 100644 --- a/SysBot.Pokemon.Kook/KookBot.cs +++ b/SysBot.Pokemon.Kook/KookBot.cs @@ -1,64 +1,49 @@ -using Kook; using Kook.Commands; +using Kook; using Kook.WebSocket; -using Microsoft.Extensions.DependencyInjection; using PKHeX.Core; using SysBot.Base; -using System.Reflection; -using System.Text.RegularExpressions; +using System.Collections.Generic; +using System.Threading.Tasks; +using System; +using System.Threading; +using System.Linq; namespace SysBot.Pokemon.Kook; -public static class KookBotSettings +public class KookBot where T : PKM, new() { - public static KookManager Manager { get; internal set; } = default!; - public static KookSettings Settings => Manager.Config; - public static PokeTradeHubConfig HubConfig { get; internal set; } = default!; -} + private static PokeTradeHub Hub = default!; + internal static TradeQueueInfo Info => Hub.Queues.Info; -public sealed class KookBot where T : PKM, new() -{ - public static PokeBotRunner Runner { get; private set; } = default!; private readonly KookSocketClient _client; - private readonly KookManager Manager; - public readonly PokeTradeHub Hub; - private readonly CommandService _commands; - private readonly IServiceProvider _services; - private bool MessageChannelsLoaded { get; set; } - public KookBot(PokeBotRunner runner) { - Runner = runner; - Hub = runner.Hub; - Manager = new KookManager(Hub.Config.Kook); + internal static KookSettings Settings = default!; - KookBotSettings.Manager = Manager; - KookBotSettings.HubConfig = Hub.Config; + private readonly List Modules = []; + public KookBot(PokeBotRunner runner) + { + Hub = runner.Hub; + + Settings = Hub.Config.Kook; _client = new KookSocketClient(new KookSocketConfig { LogLevel = LogSeverity.Info, MessageCacheSize = 1000, // Adjust as needed - AlwaysDownloadUsers = true, // Download user data for commands + AlwaysDownloadUsers = false, // Download user data for commands DefaultRetryMode = RetryMode.AlwaysRetry, }); - _commands = new CommandService(new CommandServiceConfig - { - DefaultRunMode = Hub.Config.Kook.AsyncCommands ? RunMode.Async : RunMode.Sync, - LogLevel = LogSeverity.Info, - CaseSensitiveCommands = false, - }); - _client.Log += Log; - _commands.Log += Log; - _services = ConfigServices(); - } - - private static ServiceProvider ConfigServices() - { - var map = new ServiceCollection(); - return map.BuildServiceProvider(); + Modules = + [ + new AliveModule(), + new CommandModule(), + new FileModule(), + new PsModule() + ]; } private static Task Log(LogMessage msg) @@ -88,144 +73,65 @@ private static Task Log(LogMessage msg) public async Task MainAsync(string apiToken, CancellationToken token) { - await InitCommands().ConfigureAwait(false); + _client.MessageReceived += HandleMessageAsync; await _client.LoginAsync(TokenType.Bot, apiToken).ConfigureAwait(false); await _client.StartAsync().ConfigureAwait(false); LogUtil.LogInfo("Kook Bot started successfully.", "KookBot"); - - var guilds = await _client.Rest.GetGuildsAsync().ConfigureAwait(false); - if (guilds.Count != 0) - { - var guild = guilds.First(); - var owner = await guild.GetOwnerAsync(); - Manager.Owner = owner.Id; - LogUtil.LogInfo($"Set owner {owner.Id} from guild: {guild.Name} (ID: {guild.Id})", "KookBot"); - } - else + var channel = await _client.GetChannelAsync(Settings.ChannelId).ConfigureAwait(false); + if (channel is ITextChannel textChannel) { - LogUtil.LogError("No guilds found. Ensure the bot has been added to at least one guild.", "KookBot"); - } - // Wait infinitely so your bot actually stays connected. - await MonitorLogIntervalAsync(token).ConfigureAwait(false); - } - - public async Task InitCommands() - { - var assembly = Assembly.GetExecutingAssembly(); - - await _commands.AddModulesAsync(assembly, _services).ConfigureAwait(false); - var genericTypes = assembly.DefinedTypes.Where(z => z.IsSubclassOf(typeof(ModuleBase)) && z.IsGenericType); - foreach (var t in genericTypes) - { - var genModule = t.MakeGenericType(typeof(T)); - try + if (!string.IsNullOrWhiteSpace(Settings.MessageStart)) { - await _commands.AddModuleAsync(genModule, _services).ConfigureAwait(false); - } catch (Exception ex) + // Send a start message to the channel + await textChannel.SendTextAsync(Settings.MessageStart).ConfigureAwait(false); + await Task.Delay(1_000, token).ConfigureAwait(false); + } + if (typeof(T) == typeof(PK8)) { - LogUtil.LogError($"Failed to add module {genModule.Name}: {ex.Message}", "KookBot"); - // Optionally, you can log the exception or handle it as needed + await textChannel.SendTextAsync("当前版本为剑盾"); } + else if (typeof(T) == typeof(PB8)) + { + await textChannel.SendTextAsync("当前版本为晶灿钻石明亮珍珠"); + } + else if (typeof(T) == typeof(PA8)) + { + await textChannel.SendTextAsync("当前版本为阿尔宙斯"); + } + else if (typeof(T) == typeof(PK9)) + { + await textChannel.SendTextAsync("当前版本为朱紫"); + } + else if (typeof(T) == typeof(PA9)) + { + await textChannel.SendTextAsync("当前版本为Z-A"); + } + await Task.Delay(1_000, token).ConfigureAwait(false); } - var modules = _commands.Modules.ToList(); - - var blacklist = Hub.Config.Kook.ModuleBlacklist - .Replace("Module", "").Split(',', StringSplitOptions.RemoveEmptyEntries) - .Select(z => z.Trim()).ToList(); - - foreach (var module in modules) - { - var name = module.Name; - name = name.Replace("Module", ""); - var gen = name.IndexOf('`'); - if (gen != -1) - name = name[..gen]; - if (blacklist.Any(z => z.Equals(name, StringComparison.OrdinalIgnoreCase))) - await _commands.RemoveModuleAsync(module).ConfigureAwait(false); - } - - // Subscribe a handler to see if a message invokes a command. - _client.Ready += LoadLoggingAndEcho; - _client.MessageReceived += HandleMessageAsync; + + // Wait infinitely so your bot actually stays connected. + await MonitorLogIntervalAsync(token).ConfigureAwait(false); } private async Task HandleMessageAsync(SocketMessage arg, SocketGuildUser user, SocketTextChannel channel) { - // Bail out if it's a System Message. if (arg is not SocketUserMessage msg) return; - - // We don't want the bot to respond to itself or other bots. + // Ignore messages from the bot itself or other bots if (msg.Author.Id == _client.CurrentUser?.Id || (msg.Author.IsBot ?? false)) return; - - // Create a number to track where the prefix ends and the command begins - int pos = 0; - if (msg.HasStringPrefix(Hub.Config.Kook.CommandPrefix, ref pos)) - { - bool handled = await TryHandleCommandAsync(msg, pos).ConfigureAwait(false); - if (handled) - return; - } - await TryHandleMessageAsync(msg).ConfigureAwait(false); - } - - private async Task TryHandleMessageAsync(SocketUserMessage msg) - { - // should this be a service? - if (msg.Attachments.Count > 0) - { - var mgr = Manager; - var cfg = mgr.Config; - if (cfg.ConvertPKMToShowdownSet && (cfg.ConvertPKMReplyAnyChannel || mgr.CanUseCommandChannel(msg.Channel.Id))) - { - foreach (var att in msg.Attachments) - await msg.Channel.RepostPKMAsShowdownAsync(att).ConfigureAwait(false); - } - } - if (msg.MentionedUserIds.Where(id => id ==_client.CurrentUser?.Id).Any()) - { - string commandPrefix = Manager.Config.CommandPrefix; - await msg.Channel.SendTextAsync($"Please use {commandPrefix}help for help"); - } - } - - private async Task TryHandleCommandAsync(SocketUserMessage msg, int pos) - { - // Create a Command Context. - var context = new SocketCommandContext(_client, msg); - - // Check Permission - var mgr = Manager; - if (!mgr.CanUseCommandUser(msg.Author.Id)) - { - await msg.Channel.SendTextAsync("You are not permitted to use this command.").ConfigureAwait(false); - return true; - } - if (!mgr.CanUseCommandChannel(msg.Channel.Id) && msg.Author.Id != mgr.Owner) + // Ignore messages from other channels + if (msg.Channel.Id != Settings.ChannelId) + return; + if (msg.MentionedUserIds.Where(id => id == _client.CurrentUser?.Id).Any() || msg.Attachments.Count > 0) { - if (Hub.Config.Kook.ReplyCannotUseCommandInChannel) - await msg.Channel.SendTextAsync("You can't use that command here.").ConfigureAwait(false); - return true; + // only respond to commands if the user is not a bot + LogUtil.LogInfo($"Received command from {user.Username} in channel {channel.Name}: {msg.Content}", "KookBot"); + // Create a Command Context. + var context = new SocketCommandContext(_client, msg); + await Modules.Raise(context); } - - // Execute the command. (result does not indicate a return value, - // rather an object stating if the command executed successfully). - var guild = msg.Channel is SocketGuildChannel g ? g.Guild.Name : "Unknown Guild"; - await Log(new LogMessage(LogSeverity.Info, "Command", $"Executing command from {guild}#{msg.Channel.Name}:@{msg.Author.Username}. Content: {msg}. id: {msg.Author.IdentifyNumber}")).ConfigureAwait(false); - var result = await _commands.ExecuteAsync(context, pos, _services).ConfigureAwait(false); - - if (result.Error == CommandError.UnknownCommand) - return false; - - // Uncomment the following lines if you want the bot - // to send a message if it failed. - // This does not catch errors from commands with 'RunMode.Async', - // subscribe a handler for '_commands.CommandExecuted' to see those. - if (!result.IsSuccess) - await msg.Channel.SendTextAsync(result.ErrorReason??"No Error Reason").ConfigureAwait(false); - return true; } private async Task MonitorLogIntervalAsync(CancellationToken token) @@ -248,21 +154,4 @@ private async Task MonitorLogIntervalAsync(CancellationToken token) await Task.Delay(gap, token).ConfigureAwait(false); } } - private async Task LoadLoggingAndEcho() - { - if (MessageChannelsLoaded) - return; - - // Restore Echoes - EchoModule.RestoreChannels(_client, Hub.Config.Kook); - - // Restore Logging - LogModule.RestoreLogging(_client, Hub.Config.Kook); - TradeStartModule.RestoreTradeStarting(_client); - - // Don't let it load more than once in case of Kook hiccups. - await Log(new LogMessage(LogSeverity.Info, "LoadLoggingAndEcho()", "Logging and Echo channels loaded!")).ConfigureAwait(false); - MessageChannelsLoaded = true; - } } - diff --git a/SysBot.Pokemon.Kook/KookMessageUtil.cs b/SysBot.Pokemon.Kook/KookMessageUtil.cs new file mode 100644 index 000000000..11e1c2c1b --- /dev/null +++ b/SysBot.Pokemon.Kook/KookMessageUtil.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace SysBot.Pokemon.Kook; + +public class KookMessageUtil +{ + /// + /// 去除消息中的mention信息、多余空格和特殊字符 + /// + public static string SanitizeMessage(string message) + { + if (string.IsNullOrWhiteSpace(message)) + return string.Empty; + // Remove any leading or trailing whitespace + message = message.Trim(); + // Replace multiple spaces with a single space + message = System.Text.RegularExpressions.Regex.Replace(message, @"\(met\)\d+\(met\)", "").Trim(); + message = Regex.Replace(message, @"\\(.)", "$1"); + return message; + } + + /// 判断字符串是否为JSON数组格式 + public static bool IsJsonArray(string str) + { + if (string.IsNullOrWhiteSpace(str)) + return false; + str = str.Trim(); + return str.StartsWith('[') && str.EndsWith(']'); + } +} diff --git a/SysBot.Pokemon.Kook/KookTrade.cs b/SysBot.Pokemon.Kook/KookTrade.cs new file mode 100644 index 000000000..0be82d817 --- /dev/null +++ b/SysBot.Pokemon.Kook/KookTrade.cs @@ -0,0 +1,25 @@ +using Kook.Commands; +using PKHeX.Core; +using SysBot.Pokemon.Helpers; + +namespace SysBot.Pokemon.Kook; + +public class KookTrade : AbstractTrade where T : PKM, new() +{ + private readonly SocketCommandContext Context = default!; + + public KookTrade(SocketCommandContext context) + { + Context = context; + SetPokeTradeTrainerInfo(new PokeTradeTrainerInfo(Context.User.Username, Context.User.Id)); + SetTradeQueueInfo(KookBot.Info); + } + public override IPokeTradeNotifier GetPokeTradeNotifier(T pkm, int code) + { + return new KookTradeNotifier(pkm, userInfo, code, Context); + } + public override void SendMessage(string message) + { + Context.Channel.SendTextAsync($"{Context.User.KMarkdownMention} {message}"); + } +} diff --git a/SysBot.Pokemon.Kook/KookTradeNotifier.cs b/SysBot.Pokemon.Kook/KookTradeNotifier.cs new file mode 100644 index 000000000..d13a88cd2 --- /dev/null +++ b/SysBot.Pokemon.Kook/KookTradeNotifier.cs @@ -0,0 +1,115 @@ +using Kook; +using Kook.Commands; +using PKHeX.Core; +using SysBot.Base; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace SysBot.Pokemon.Kook; + +public class KookTradeNotifier : IPokeTradeNotifier where T : PKM, new() +{ + private T Data { get; } + private PokeTradeTrainerInfo Info { get; } + private int Code { get; } + + private SocketCommandContext Context = default!; + public KookTradeNotifier(T data, PokeTradeTrainerInfo info, int code, SocketCommandContext context) + { + this.Data = data; + Info = info; + Code = code; + Context = context; + LogUtil.LogText($"Created trade details for {context.User.Username} - {Code}"); + } + public Action>? OnFinish { private get; set; } + public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, string message) + { + LogUtil.LogText(message); + if (message.Contains("Found Trading Partner:")) + { + Regex regex = new Regex("TID: (\\d+)"); + string tid = regex.Match(message).Groups[1].ToString(); + regex = new Regex("SID: (\\d+)"); + string sid = regex.Match(message).Groups[1].ToString(); + Context.User.SendTextAsync($"找到你了,你的SID7:{sid},TID7:{tid}"); + } + else if (message.StartsWith("批量")) + { + Context.Channel.SendTextAsync(message); + } + } + public void TradeCanceled(PokeRoutineExecutor routine, PokeTradeDetail info, PokeTradeResult msg) + { + OnFinish?.Invoke(routine); + var line = $"@{info.Trainer.TrainerName}: Trade canceled, {msg}"; + LogUtil.LogText(line); + Context.Channel.SendTextAsync($"{Context.User.KMarkdownMention} 取消, {msg}"); + } + public void TradeFinished(PokeRoutineExecutor routine, PokeTradeDetail info, T result) + { + OnFinish?.Invoke(routine); + var message = $"{Context.User.KMarkdownMention} 交换成功"; + LogUtil.LogText(message); + Context.Channel.SendTextAsync(message); + } + + public void TradeInitialize(PokeRoutineExecutor routine, PokeTradeDetail info) + { + var receive = Data.Species == 0 ? string.Empty : $" ({Data.Nickname})"; + var msg = + $"@{info.Trainer.TrainerName} (ID: {info.ID}): Initializing trade{receive} with you. Please be ready."; + msg += $" Your trade code is: {info.Code:0000 0000}"; + LogUtil.LogText(msg); + var text = $"\n派送:{ShowdownTranslator.GameStringsZh.Species[Data.Species]}\n密码:见私信\n状态:初始化"; + List batchPKMs = (List)info.Context.GetValueOrDefault("batch", new List()); + if (batchPKMs.Count > 1) + { + text = $"\n批量派送{batchPKMs.Count}只宝可梦\n密码:见私信\n状态:初始化"; + } + Context.Channel.SendTextAsync($"{Context.User.KMarkdownMention} {text}"); + Context.User.SendTextAsync($"派送:{ShowdownTranslator.GameStringsZh.Species[Data.Species]}\n密码:{info.Code:0000 0000}"); + } + + public void TradeSearching(PokeRoutineExecutor routine, PokeTradeDetail info) + { + var name = Info.TrainerName; + var trainer = string.IsNullOrEmpty(name) ? string.Empty : $", @{name}"; + var message = $"I'm waiting for you{trainer}! My IGN is {routine.InGameName}."; + message += $" Your trade code is: {info.Code:0000 0000}"; + LogUtil.LogText(message); + var text = $"派送:{ShowdownTranslator.GameStringsZh.Species[Data.Species]}\n密码:见私信\n状态:搜索中"; + List batchPKMs = (List)info.Context.GetValueOrDefault("batch", new List()); + if (batchPKMs.Count > 1) + { + text = $"批量派送{batchPKMs.Count}只宝可梦\n密码:见私信\n状态:搜索中"; + } + Context.Channel.SendTextAsync(text); + } + + public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, PokeTradeSummary message) + { + var msg = message.Summary; + if (message.Details.Count > 0) + msg += ", " + string.Join(", ", message.Details.Select(z => $"{z.Heading}: {z.Detail}")); + LogUtil.LogText(msg); + } + + public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, T result, string message) + { + var msg = $"Details for {result.FileName}: " + message; + LogUtil.LogText(msg); + if (result.Species != 0 && info.Type == PokeTradeType.Dump) + { + Span ivs = stackalloc int[6]; + result.GetIVs(ivs); + var text = + $"species:{result.Species}\npid:{result.PID}\nec:{result.EncryptionConstant}\nIVs:{string.Join(",", ivs.ToArray())}\nisShiny:{result.IsShiny}"; + Context.Channel.SendTextAsync(text); + } + } +} diff --git a/SysBot.Pokemon.Kook/Modules/AliveModule.cs b/SysBot.Pokemon.Kook/Modules/AliveModule.cs new file mode 100644 index 000000000..e2a8cd9ee --- /dev/null +++ b/SysBot.Pokemon.Kook/Modules/AliveModule.cs @@ -0,0 +1,26 @@ +using Kook.Commands; +using PKHeX.Core; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SysBot.Pokemon.Kook; + +public class AliveModule : IModule where T : PKM, new() +{ + public bool? IsEnable { get; set; } = true; + + public async Task ExecuteAsync(SocketCommandContext context) + { + KookSettings settings = KookBot.Settings; + + var text = KookMessageUtil.SanitizeMessage(context.Message.Content); + if (settings.AliveMsg == text) + { + await context.Channel.SendTextAsync(settings.AliveMsg); + return; + } + } +} diff --git a/SysBot.Pokemon.Kook/Modules/CommandModule.cs b/SysBot.Pokemon.Kook/Modules/CommandModule.cs new file mode 100644 index 000000000..4cf320500 --- /dev/null +++ b/SysBot.Pokemon.Kook/Modules/CommandModule.cs @@ -0,0 +1,47 @@ +using Kook.Commands; +using PKHeX.Core; +using System.Threading.Tasks; + +namespace SysBot.Pokemon.Kook; + +public class CommandModule : IModule where T : PKM, new() +{ + public bool? IsEnable { get; set; } = true; + + public async Task ExecuteAsync(SocketCommandContext context) + { + var text = KookMessageUtil.SanitizeMessage(context.Message.Content); + if (string.IsNullOrWhiteSpace(text)) return; + if (text.Trim().StartsWith("取消")) + { + var result = KookBot.Info.ClearTrade(context.User.Id); + await context.Channel.SendTextAsync($"{context.User.KMarkdownMention} {GetClearTradeMessage(result)}"); + } + else if (text.Trim().StartsWith("位置")) + { + var result = KookBot.Info.CheckPosition(context.User.Id); + await context.Channel.SendTextAsync($"{context.User.KMarkdownMention} {GetQueueCheckResultMessage(result)}"); + } + } + private static string GetQueueCheckResultMessage(QueueCheckResult result) + { + if (!result.InQueue || result.Detail is null) + return "你不在队列里"; + var msg = $"你在第{result.Position}位"; + var pk = result.Detail.Trade.TradeData; + if (pk.Species != 0) + msg += $",交换宝可梦:{ShowdownTranslator.GameStringsZh.Species[result.Detail.Trade.TradeData.Species]}"; + return msg; + } + + private static string GetClearTradeMessage(QueueResultRemove result) + { + return result switch + { + QueueResultRemove.CurrentlyProcessing => "你正在交换中", + QueueResultRemove.CurrentlyProcessingRemoved => "正在删除", + QueueResultRemove.Removed => "已删除", + _ => "你不在队列里", + }; + } +} diff --git a/SysBot.Pokemon.Kook/Modules/FileModule.cs b/SysBot.Pokemon.Kook/Modules/FileModule.cs new file mode 100644 index 000000000..833ee36f0 --- /dev/null +++ b/SysBot.Pokemon.Kook/Modules/FileModule.cs @@ -0,0 +1,53 @@ +using Kook.Commands; +using PKHeX.Core; +using SysBot.Base; +using SysBot.Pokemon.Helpers; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; + +namespace SysBot.Pokemon.Kook; + +public class FileModule : IModule where T : PKM, new() +{ + public bool? IsEnable { get; set; } = true; + + public async Task ExecuteAsync(SocketCommandContext context) + { + if (context.Message.Attachments.Count == 0 || !KookMessageUtil.IsJsonArray(context.Message.Content)) + { + return; + } + + LogUtil.LogInfo("In file module", nameof(FileModule)); + var attachment = context.Message.Attachments.First(); + var fileName = attachment.Filename ?? ""; + var fileSize = attachment.Size ?? 0; + if (!FileTradeHelper.ValidFileName(fileName) || !FileTradeHelper.ValidFileSize(fileSize)) + { + await context.Channel.SendTextAsync("非法文件"); + return; + } + + List pkms = default!; + try + { + using var client = new HttpClient(); + byte[] data = client.GetByteArrayAsync(attachment.Url).Result; + pkms = FileTradeHelper.Bin2List(data); + } + catch (Exception ex) + { + LogUtil.LogError(ex.Message, nameof(FileModule)); + return; + } + if (pkms.Count > 1 && pkms.Count <= FileTradeHelper.MaxCountInBin) + new KookTrade(context).StartTradeMultiPKM(pkms); + else if (pkms.Count == 1) + new KookTrade(context).StartTradePKM(pkms[0]); + else + await context.Channel.SendTextAsync("文件内容不正确"); + } +} diff --git a/SysBot.Pokemon.Kook/Modules/IModule.cs b/SysBot.Pokemon.Kook/Modules/IModule.cs new file mode 100644 index 000000000..3fa169705 --- /dev/null +++ b/SysBot.Pokemon.Kook/Modules/IModule.cs @@ -0,0 +1,24 @@ +using Kook.Commands; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SysBot.Pokemon.Kook; + +public interface IModule +{ + // + // 摘要: + // 是否启用模块 + bool? IsEnable { get; set; } + + // + // 摘要: + // 执行器 + // + // 参数: + // context: + Task ExecuteAsync(SocketCommandContext context); +} diff --git a/SysBot.Pokemon.Kook/Modules/ModuleScaffold.cs b/SysBot.Pokemon.Kook/Modules/ModuleScaffold.cs new file mode 100644 index 000000000..8ef0856cd --- /dev/null +++ b/SysBot.Pokemon.Kook/Modules/ModuleScaffold.cs @@ -0,0 +1,27 @@ +using Kook.Commands; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace SysBot.Pokemon.Kook; + +public static class ModuleScaffold +{ + // + // 摘要: + // 传播订阅到模块 + // + // 参数: + // modules: + // + // base: + public static async Task Raise(this List modules, SocketCommandContext context) + { + foreach (IModule module in modules) + { + if (module.IsEnable ?? true) + { + await module.ExecuteAsync(context); + } + } + } +} diff --git a/SysBot.Pokemon.Kook/Modules/PsModule.cs b/SysBot.Pokemon.Kook/Modules/PsModule.cs new file mode 100644 index 000000000..1a7fcf5cf --- /dev/null +++ b/SysBot.Pokemon.Kook/Modules/PsModule.cs @@ -0,0 +1,81 @@ +using Kook; +using Kook.Commands; +using PKHeX.Core; +using SysBot.Base; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SysBot.Pokemon.Kook; + +public class PsModule : IModule where T : PKM, new() +{ + public bool? IsEnable { get; set; } = true; + + public Task ExecuteAsync(SocketCommandContext context) + { + if (KookMessageUtil.IsJsonArray(context.Message.Content)) return Task.CompletedTask; + + var text = KookMessageUtil.SanitizeMessage(context.Message.Content); + if (string.IsNullOrWhiteSpace(text)) return Task.CompletedTask; + // 中英文判断 + if (IsChinesePS(text)) + ProcessChinesePS(text, context); + else if (IsPS(text)) + ProcessPS(text, context); + + return Task.CompletedTask; + } + + private void ProcessPS(string text, SocketCommandContext context) + { + LogUtil.LogInfo($"收到ps代码:\n{text}", nameof(PsModule)); + var pss = text.Split("\n\n"); + if (pss.Length > 1) + new KookTrade(context).StartTradeMultiPs(text); + else + new KookTrade(context).StartTradePs(text); + } + + private void ProcessChinesePS(string text, SocketCommandContext context) + { + LogUtil.LogInfo($"收到中文ps代码:\n{text}", nameof(PsModule)); + var pss = text.Split("+"); + if (pss.Length > 1) + { + new KookTrade(context).StartTradeMultiChinesePs(text); + } + else + { + new KookTrade(context).StartTradeChinesePs(text); + } + + } + + private static bool IsChinesePS(string str) + { + var gameStrings = ShowdownTranslator.GameStringsZh; + for (int i = 1; i < gameStrings.Species.Count; i++) + { + if (str.Contains(gameStrings.Species[i])) + { + return true; + } + } + return false; + } + private static bool IsPS(string str) + { + var gameStrings = ShowdownTranslator.GameStringsEn; + for (int i = 1; i < gameStrings.Species.Count; i++) + { + if (str.Contains(gameStrings.Species[i])) + { + return true; + } + } + return false; + } +} diff --git a/SysBot.Pokemon.Kook/SysBot.Pokemon.Kook.csproj b/SysBot.Pokemon.Kook/SysBot.Pokemon.Kook.csproj index bf2c833a1..ab233991f 100644 --- a/SysBot.Pokemon.Kook/SysBot.Pokemon.Kook.csproj +++ b/SysBot.Pokemon.Kook/SysBot.Pokemon.Kook.csproj @@ -1,14 +1,7 @@  - - enable - enable - - - - diff --git a/SysBot.Pokemon.QQ/MiraiQQTradeNotifier.cs b/SysBot.Pokemon.QQ/MiraiQQTradeNotifier.cs index ab4c8b7a9..2f326851a 100644 --- a/SysBot.Pokemon.QQ/MiraiQQTradeNotifier.cs +++ b/SysBot.Pokemon.QQ/MiraiQQTradeNotifier.cs @@ -112,8 +112,10 @@ public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail LogUtil.LogText(msg); if (result.Species != 0 && info.Type == PokeTradeType.Dump) { + Span ivs = stackalloc int[6]; + result.GetIVs(ivs); var text = - $"species:{result.Species}\npid:{result.PID}\nec:{result.EncryptionConstant}\nIVs:{string.Join(",", result.IVs)}\nisShiny:{result.IsShiny}"; + $"species:{result.Species}\npid:{result.PID}\nec:{result.EncryptionConstant}\nIVs:{string.Join(",", ivs.ToArray())}\nisShiny:{result.IsShiny}"; MiraiQQBot.SendGroupMessage(text); } } diff --git a/SysBot.Pokemon/Settings/Integrations/KookSettings.cs b/SysBot.Pokemon/Settings/Integrations/KookSettings.cs index f4255dd6c..7847ae2d9 100644 --- a/SysBot.Pokemon/Settings/Integrations/KookSettings.cs +++ b/SysBot.Pokemon/Settings/Integrations/KookSettings.cs @@ -6,83 +6,20 @@ public class KookSettings { private const string Startup = nameof(Startup); private const string Operation = nameof(Operation); - private const string Channels = nameof(Channels); - private const string Roles = nameof(Roles); - private const string Users = nameof(Users); + private const string Messages = nameof(Messages); public override string ToString() => "Kook Integration Settings"; // Startup - [Category(Startup), Description("Bot login token.")] + [Category(Startup), Description("Kook Bot Token")] public string Token { get; set; } = string.Empty; - [Category(Startup), Description("Bot command prefix.")] - public string CommandPrefix { get; set; } = "$"; + [Category(Startup), Description("Response Channel ID")] + public ulong ChannelId { get; set; } = 0; - [Category(Startup), Description("List of modules that will not be loaded when the bot is started (comma separated).")] - public string ModuleBlacklist { get; set; } = string.Empty; + [Category(Startup), Description("Message to test Bot alive")] + public string AliveMsg { get; set; } = "hello"; - [Category(Startup), Description("Toggle to handle commands asynchronously or synchronously.")] - public bool AsyncCommands { get; set; } - - [Category(Operation), Description("Custom message the bot will reply with when a user says hello to it. Use string formatting to mention the user in the reply.")] - public string HelloResponse { get; set; } = "Hi {0}!"; - - // Whitelists - - [Category(Roles), Description("Users with this role are allowed to enter the Trade queue.")] - public RemoteControlAccessList RoleCanTrade { get; set; } = new() { AllowIfEmpty = false }; - - [Category(Roles), Description("Users with this role are allowed to enter the Seed Check queue.")] - public RemoteControlAccessList RoleCanSeedCheck { get; set; } = new() { AllowIfEmpty = false }; - - [Category(Roles), Description("Users with this role are allowed to enter the Clone queue.")] - public RemoteControlAccessList RoleCanClone { get; set; } = new() { AllowIfEmpty = false }; - - [Category(Roles), Description("Users with this role are allowed to enter the Dump queue.")] - public RemoteControlAccessList RoleCanDump { get; set; } = new() { AllowIfEmpty = false }; - - [Category(Roles), Description("Users with this role are allowed to remotely control the console (if running as Remote Control Bot.")] - public RemoteControlAccessList RoleRemoteControl { get; set; } = new() { AllowIfEmpty = false }; - - [Category(Roles), Description("Users with this role are allowed to bypass command restrictions.")] - public RemoteControlAccessList RoleSudo { get; set; } = new() { AllowIfEmpty = false }; - - // Operation - - [Category(Roles), Description("Users with this role are allowed to join the queue with a better position.")] - public RemoteControlAccessList RoleFavored { get; set; } = new() { AllowIfEmpty = false }; - - [Category(Users), Description("Users with these user IDs cannot use the bot.")] - public RemoteControlAccessList UserBlacklist { get; set; } = new(); - - [Category(Channels), Description("Channels with these IDs are the only channels where the bot acknowledges commands.")] - public RemoteControlAccessList ChannelWhitelist { get; set; } = new(); - - [Category(Users), Description("Comma separated Discord user IDs that will have sudo access to the Bot Hub.")] - public RemoteControlAccessList GlobalSudoList { get; set; } = new(); - - [Category(Users), Description("Disabling this will remove global sudo support.")] - public bool AllowGlobalSudo { get; set; } = true; - - [Category(Channels), Description("Channel IDs that will echo the log bot data.")] - public RemoteControlAccessList LoggingChannels { get; set; } = new(); - - [Category(Channels), Description("Logger channels that will log trade start messages.")] - public RemoteControlAccessList TradeStartingChannels { get; set; } = new(); - - [Category(Channels), Description("Echo channels that will log special messages.")] - public RemoteControlAccessList EchoChannels { get; set; } = new(); - - [Category(Operation), Description("Returns PKMs of Pokémon shown in the trade to the user.")] - public bool ReturnPKMs { get; set; } = true; - - [Category(Operation), Description("Replies to users if they are not allowed to use a given command in the channel. When false, the bot will silently ignore them instead.")] - public bool ReplyCannotUseCommandInChannel { get; set; } = true; - - [Category(Operation), Description("Bot listens to channel messages to reply with a ShowdownSet whenever a PKM file is attached (not with a command).")] - public bool ConvertPKMToShowdownSet { get; set; } = true; - - [Category(Operation), Description("Bot can reply with a ShowdownSet in Any channel the bot can see, instead of only channels the bot has been whitelisted to run in. Only make this true if you want the bot to serve more utility in non-bot channels.")] - public bool ConvertPKMReplyAnyChannel { get; set; } + [Category(Operation), Description("Message sent when the Barrier is released.")] + public string MessageStart { get; set; } = string.Empty; } From 47289e45483516bc496a2c8d0e46370b82755e68 Mon Sep 17 00:00:00 2001 From: Easy World Date: Sun, 11 Jan 2026 19:23:38 +0800 Subject: [PATCH 166/171] Add PA9 file upload support in README Updated file upload support to include PA9. --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1341714dc..20817f986 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ Document: [搭建指南](https://github.com/easyworld/SysBot.NET/tree/master/Sys ## SysBot.Pokemon.Dodo (Dead): **Notice: The Dodo platform was discontinued in June 15th 2025. All related features of this project are no longer functional. This repository is now archived for code reference only—none of the bot functionalities or commands are available anymore.** - Support [ALM-Showdown-Sets](https://github.com/architdate/PKHeX-Plugins/wiki/ALM-Showdown-Sets) -- Support PK8 PB8 PA8 PK9 file upload +- Support PK8 PB8 PA8 PK9 PA9 file upload - Support Customized Chinese to ALM-Showdown-Sets translation Most codes are based on [SysBot.Pokemon.Twitch](https://github.com/kwsch/SysBot.NET/tree/master/SysBot.Pokemon.Twitch) @@ -65,10 +65,10 @@ Uses [dodo-open-net](https://github.com/dodo-open/dodo-open-net) as a dependency Document: [搭建指南](https://docs.qq.com/doc/DSVVZZk9saUNTeHNn), [命令指南](https://docs.qq.com/doc/DSVlldkxMSW92VXZF) ## SysBot.Pokemon.Kook: -**SysBot.Pokemon.Kook** is a complete port of the `SysBot.Pokemon.Discord` project, fully compatible with the [Kook](https://www.kookapp.cn/) (formerly known as Kaiheila) platform. +**SysBot.Pokemon.Kook** is fully compatible with the [Kook](https://www.kookapp.cn/) (formerly known as Kaiheila) platform. - Support [ALM-Showdown-Sets](https://github.com/architdate/PKHeX-Plugins/wiki/ALM-Showdown-Sets) -- Support PK8 PB8 PA8 PK9 file upload +- Support PK8 PB8 PA8 PK9 PA9 file upload - Support [Customized Chinese](https://docs.qq.com/doc/DSVlldkxMSW92VXZF) to ALM-Showdown-Sets translation - Easily configurable and extensible for the Kook ecosystem. From c7f39c4bcdc07a6ebce718ba4e139716428e07e8 Mon Sep 17 00:00:00 2001 From: Easy World Date: Tue, 10 Feb 2026 10:41:13 +0800 Subject: [PATCH 167/171] Translate README content to Chinese and update links --- README.md | 104 +++++++++++++++++++++++++++--------------------------- 1 file changed, 52 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index 20817f986..dad10a56f 100644 --- a/README.md +++ b/README.md @@ -1,81 +1,81 @@ # SysBot.NET -![License](https://img.shields.io/badge/License-AGPLv3-blue.svg) +![License](https://pfst.cf2.poecdn.net/base/image/373bd76ec7034dbe3c0220acf29365286b74b5dd1c19dbb63781975bf273392a?pmaid=567453342) -## Support Discord: +## 支持用 Discord(Support Discord) -For support on setting up your own instance of SysBot.NET, feel free to join the discord! (Beware of un-official discords who claim to be official) +如果你需要在搭建自己的 SysBot.NET 实例时获得帮助,欢迎加入 Discord 获取支持!(请注意:有些非官方 Discord 可能冒充官方,请务必辨别) [](https://discord.gg/tDMvSRv) -[sys-botbase](https://github.com/olliz0r/sys-botbase) client for remote control automation of Nintendo Switch consoles. +[sys-botbase](https://github.com/olliz0r/sys-botbase) 的客户端,用于对任天堂 Switch 主机进行远程控制与自动化操作。 -## SysBot.Base: -- Base logic library to be built upon in game-specific projects. -- Contains a synchronous and asynchronous Bot connection class to interact with sys-botbase. +## SysBot.Base: +- 可供各游戏专用项目构建与扩展的基础逻辑库。 +- 包含用于与 sys-botbase 交互的同步与异步 Bot 连接类。 -## SysBot.Tests: -- Unit Tests for ensuring logic behaves as intended :) +## SysBot.Tests: +- 单元测试,用于确保逻辑行为符合预期 :) -# Example Implementations +# 示例实现(Example Implementations) -The driving force to develop this project is automated bots for Nintendo Switch Pokémon games. An example implementation is provided in this repo to demonstrate interesting tasks this framework is capable of performing. Refer to the [Wiki](https://github.com/kwsch/SysBot.NET/wiki) for more details on the supported Pokémon features. +开发这个项目的主要动力是为任天堂 Switch 的宝可梦游戏实现自动化机器人。本仓库提供了一个示例实现,用于展示该框架能够完成的一些有趣任务。更多已支持的宝可梦功能请参考 [Wiki](https://github.com/kwsch/SysBot.NET/wiki)。 -## SysBot.Pokemon: -- Class library using SysBot.Base to contain logic related to creating & running Sword/Shield bots. +## SysBot.Pokemon: +- 使用 SysBot.Base 的类库,包含与创建和运行《宝可梦 剑/盾》机器人相关的逻辑。 -## SysBot.Pokemon.WinForms: -- Simple GUI Launcher for adding, starting, and stopping Pokémon bots (as described above). -- Configuration of program settings is performed in-app and is saved as a local json file. +## SysBot.Pokemon.WinForms: +- 一个简单的 GUI 启动器,用于添加、启动与停止宝可梦机器人(如上所述)。 +- 程序设置可在应用内配置,并保存为本地 JSON 文件。 -## SysBot.Pokemon.Discord: -- Discord interface for remotely interacting with the WinForms GUI. -- Provide a discord login token and the Roles that are allowed to interact with your bots. -- Commands are provided to manage & join the distribution queue. +## SysBot.Pokemon.Discord: +- 用于远程与 WinForms GUI 交互的 Discord 接口。 +- 需要提供 Discord 登录 Token,以及允许与机器人交互的角色(Roles)。 +- 提供用于管理与加入派送队列(distribution queue)的命令。 -## SysBot.Pokemon.Twitch: -- Twitch.tv interface for remotely announcing when the distribution starts. -- Provide a Twitch login token, username, and channel for login. +## SysBot.Pokemon.Twitch: +- Twitch.tv 接口,用于在派送开始时进行远程公告等操作。 +- 需要提供 Twitch 登录 Token、用户名与要登录的频道。 -## SysBot.Pokemon.YouTube: -- YouTube.com interface for remotely announcing when the distribution starts. -- Provide a YouTube login ClientID, ClientSecret, and ChannelID for login. +## SysBot.Pokemon.YouTube: +- YouTube.com 接口,用于在派送开始时进行远程公告等操作。 +- 需要提供 YouTube 登录用的 ClientID、ClientSecret 与 ChannelID。 -Uses [Discord.Net](https://github.com/discord-net/Discord.Net) , [TwitchLib](https://github.com/TwitchLib/TwitchLib) and [StreamingClientLibary](https://github.com/SaviorXTanren/StreamingClientLibrary) as a dependency via Nuget. +通过 NuGet 依赖使用 [Discord.Net](https://github.com/discord-net/Discord.Net)、[TwitchLib](https://github.com/TwitchLib/TwitchLib) 和 [StreamingClientLibary](https://github.com/SaviorXTanren/StreamingClientLibrary)。 -## SysBot.Pokemon.QQ: -- Support [ALM-Showdown-Sets](https://github.com/architdate/PKHeX-Plugins/wiki/ALM-Showdown-Sets) -- Support PK8 PB8 PA8 PK9 file upload +## SysBot.Pokemon.QQ: +- 支持 [ALM-Showdown-Sets](https://github.com/architdate/PKHeX-Plugins/wiki/ALM-Showdown-Sets) +- 支持 PK8 / PB8 / PA8 / PK9 文件上传 -Most codes are based on [SysBot.Pokemon.Twitch](https://github.com/kwsch/SysBot.NET/tree/master/SysBot.Pokemon.Twitch) +大部分代码基于 [SysBot.Pokemon.Twitch](https://github.com/kwsch/SysBot.NET/tree/master/SysBot.Pokemon.Twitch)。 -Uses [Mirai.Net](https://github.com/SinoAHpx/Mirai.Net) as a dependency via Nuget. +通过 NuGet 依赖使用 [Mirai.Net](https://github.com/SinoAHpx/Mirai.Net)。 -Document: [搭建指南](https://github.com/easyworld/SysBot.NET/tree/master/SysBot.Pokemon.QQ), [命令指南](https://docs.qq.com/doc/DSVlldkxMSW92VXZF) +文档:[搭建指南](https://github.com/easyworld/SysBot.NET/tree/master/SysBot.Pokemon.QQ)、[命令指南](https://docs.qq.com/doc/DSVlldkxMSW92VXZF) -## SysBot.Pokemon.Dodo (Dead): -**Notice: The Dodo platform was discontinued in June 15th 2025. All related features of this project are no longer functional. This repository is now archived for code reference only—none of the bot functionalities or commands are available anymore.** -- Support [ALM-Showdown-Sets](https://github.com/architdate/PKHeX-Plugins/wiki/ALM-Showdown-Sets) -- Support PK8 PB8 PA8 PK9 PA9 file upload -- Support Customized Chinese to ALM-Showdown-Sets translation +## SysBot.Pokemon.Dodo(已失效 / Dead): +**注意:Dodo 平台已于 2025 年 6 月 15 日停止服务。本项目与 Dodo 平台相关的功能均已无法使用。此仓库现仅作为代码参考用途归档——所有机器人功能与命令均不再可用。** +- 支持 [ALM-Showdown-Sets](https://github.com/architdate/PKHeX-Plugins/wiki/ALM-Showdown-Sets) +- 支持 PK8 / PB8 / PA8 / PK9 / PA9 文件上传 +- 支持将自定义中文内容翻译为 ALM-Showdown-Sets 格式 -Most codes are based on [SysBot.Pokemon.Twitch](https://github.com/kwsch/SysBot.NET/tree/master/SysBot.Pokemon.Twitch) +大部分代码基于 [SysBot.Pokemon.Twitch](https://github.com/kwsch/SysBot.NET/tree/master/SysBot.Pokemon.Twitch)。 -Uses [dodo-open-net](https://github.com/dodo-open/dodo-open-net) as a dependency via Nuget. +通过 NuGet 依赖使用 [dodo-open-net](https://github.com/dodo-open/dodo-open-net)。 -Document: [搭建指南](https://docs.qq.com/doc/DSVVZZk9saUNTeHNn), [命令指南](https://docs.qq.com/doc/DSVlldkxMSW92VXZF) +文档:[搭建指南](https://docs.qq.com/doc/DSVVZZk9saUNTeHNn)、[命令指南](https://docs.qq.com/doc/DSVlldkxMSW92VXZF) -## SysBot.Pokemon.Kook: -**SysBot.Pokemon.Kook** is fully compatible with the [Kook](https://www.kookapp.cn/) (formerly known as Kaiheila) platform. +## SysBot.Pokemon.Kook: +**SysBot.Pokemon.Kook** 与 [Kook](https://www.kookapp.cn/)(原开黑啦)平台完全兼容。 -- Support [ALM-Showdown-Sets](https://github.com/architdate/PKHeX-Plugins/wiki/ALM-Showdown-Sets) -- Support PK8 PB8 PA8 PK9 PA9 file upload -- Support [Customized Chinese](https://docs.qq.com/doc/DSVlldkxMSW92VXZF) to ALM-Showdown-Sets translation -- Easily configurable and extensible for the Kook ecosystem. +- 支持 [ALM-Showdown-Sets](https://github.com/architdate/PKHeX-Plugins/wiki/ALM-Showdown-Sets) +- 支持 PK8 / PB8 / PA8 / PK9 / PA9 文件上传 +- 支持将[自定义中文](https://docs.qq.com/doc/DSVlldkxMSW92VXZF)翻译为 ALM-Showdown-Sets 格式 +- 易于配置与扩展,适配 Kook 生态 -Document: [搭建指南](https://docs.qq.com/doc/DSWpDQnBwSXRGSGR3), [命令指南](https://docs.qq.com/doc/DSVlldkxMSW92VXZF) +文档:[搭建指南](https://docs.qq.com/doc/DSWpDQnBwSXRGSGR3)、[命令指南](https://docs.qq.com/doc/DSVlldkxMSW92VXZF) -## Other Dependencies -Pokémon API logic is provided by [PKHeX](https://github.com/kwsch/PKHeX/), and template generation is provided by [Auto-Legality Mod](https://github.com/architdate/PKHeX-Plugins/). Current template generation uses [@santacrab2](https://www.github.com/santacrab2)'s [Auto-Legality Mod fork](https://github.com/santacrab2/PKHeX-Plugins). +## 其他依赖(Other Dependencies) +宝可梦相关的 API 逻辑由 [PKHeX](https://github.com/kwsch/PKHeX/) 提供,模板生成由 [Auto-Legality Mod](https://github.com/architdate/PKHeX-Plugins/) 提供。目前的模板生成使用 [@santacrab2](https://www.github.com/santacrab2) 的 [Auto-Legality Mod 分支(fork)](https://github.com/santacrab2/PKHeX-Plugins)。 -# License -Refer to the `License.md` for details regarding licensing. +# 许可证(License) +有关许可协议的详细信息,请参阅 `License.md`。 From 5445f909b66cc35d3c0c27f776bba0c592e217f3 Mon Sep 17 00:00:00 2001 From: Easy World Date: Thu, 26 Mar 2026 10:16:06 +0800 Subject: [PATCH 168/171] Update license badge in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dad10a56f..ec8cb1049 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # SysBot.NET -![License](https://pfst.cf2.poecdn.net/base/image/373bd76ec7034dbe3c0220acf29365286b74b5dd1c19dbb63781975bf273392a?pmaid=567453342) +![License](https://img.shields.io/badge/License-AGPLv3-blue.svg) ## 支持用 Discord(Support Discord) From e7c83231d617c6dcb8b225d2954fca1b00922c75 Mon Sep 17 00:00:00 2001 From: Easy World Date: Thu, 16 Apr 2026 10:12:11 +0800 Subject: [PATCH 169/171] Remove empty lines from Showdown sets Call a new RemoveEmptyLines helper when assembling the final ShowdownSet to strip blank or whitespace-only lines. Adds System and System.Linq usings and implements RemoveEmptyLines which guards against empty input, splits the text into lines, filters out empty/whitespace lines, and rejoins them with CRLF to produce a clean output. --- SysBot.Pokemon/Helpers/ShowdownUtil.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/SysBot.Pokemon/Helpers/ShowdownUtil.cs b/SysBot.Pokemon/Helpers/ShowdownUtil.cs index dfe2462a7..559b7858f 100644 --- a/SysBot.Pokemon/Helpers/ShowdownUtil.cs +++ b/SysBot.Pokemon/Helpers/ShowdownUtil.cs @@ -1,4 +1,6 @@ -using PKHeX.Core; +using PKHeX.Core; +using System; +using System.Linq; namespace SysBot.Pokemon; @@ -29,9 +31,20 @@ public static class ShowdownUtil setstring = setstring.Replace(i, $"\r\n{i}"); } - var finalset = restorenick + setstring; + var finalset = RemoveEmptyLines(restorenick + setstring); return new ShowdownSet(finalset); } + private static string RemoveEmptyLines(string text) + { + if (string.IsNullOrWhiteSpace(text)) + return string.Empty; + + // 按行拆分 → 跳过空行/空白行 → 重新拼接 + var lines = text.Split(["\r\n", "\n"], StringSplitOptions.None) + .Where(line => !string.IsNullOrWhiteSpace(line)); + + return string.Join("\r\n", lines); + } private static readonly string[] splittables = [ From 3e8eb081ed770474c77e1a9bdb53f386b72c5c9d Mon Sep 17 00:00:00 2001 From: Easy World Date: Fri, 8 May 2026 10:58:14 +0800 Subject: [PATCH 170/171] Add legality tests for SWSH, SV and ZA MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce new TranslatorTests covering multiple generations/formats: add TestLegalSWSH (PK8) that accepts Chinese or Showdown input, converts to showdown, retrieves templates via AutoLegalityWrapper, handles egg trades, runs legality analysis and asserts shiny status. Update existing TestLegal to support SV (PK9) as TestSVLegal with IsPS detection so inputs can be raw PS or Chinese names, and add additional InlineData cases (including a full Showdown block). Extend ZA tests with an extra case and assert shiny when input contains "异色". Overall this ensures translator + legality checks work across SWSH, SV and ZA workflows. --- SysBot.Tests/TranslatorTests.cs | 43 +++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/SysBot.Tests/TranslatorTests.cs b/SysBot.Tests/TranslatorTests.cs index 741294a9c..869adc7bb 100644 --- a/SysBot.Tests/TranslatorTests.cs +++ b/SysBot.Tests/TranslatorTests.cs @@ -20,6 +20,35 @@ public void TestTrans(string input, string output) result.Should().Be(output); } + [Theory] + [InlineData("异色月亮球喷火龙")] + public void TestLegalSWSH(string input) + { + var setstring = input; + if (!ShowdownTranslator.IsPS(input)) + { + setstring = ShowdownTranslator.Chinese2Showdown(input); + } + + var set = ShowdownUtil.ConvertToShowdown(setstring); + set.Should().NotBeNull(); + var template = AutoLegalityWrapper.GetTemplate(set); + template.Species.Should().BeGreaterThan(0); + var sav = AutoLegalityWrapper.GetTrainerInfo(); + var pkm = sav.GetLegal(template, out var result); + Trace.WriteLine(result.ToString()); + + if (pkm.Nickname.ToLower() == "egg" && Breeding.CanHatchAsEgg(pkm.Species)) AbstractTrade.EggTrade(pkm, template); + + (pkm is PK8).Should().BeTrue(); + var la = new LegalityAnalysis(pkm); + if (!la.Valid) + Trace.WriteLine(la.Report()); + pkm.CanBeTraded(la.EncounterOriginal).Should().BeTrue(); + la.Valid.Should().BeTrue(); + pkm.IsShiny.Should().BeTrue(); + } + [Theory] [InlineData("皮卡丘")] [InlineData("木木枭")] @@ -34,9 +63,16 @@ public void TestTrans(string input, string output) [InlineData("等级球呆火鳄")] [InlineData("异色古剑豹")] [InlineData("异色故勒顿")] - public void TestLegal(string input) + [InlineData("皮卡丘 大师球 胆小 全技能")] + [InlineData("Sinistcha-Masterpiece @ Master Ball\r\nShiny: Yes\r\nAbility: Hospitality\r\nHardy Nature\r\nEVs: 0 HP / 0 Atk / 0 Def / 0 SpA / 0 SpD / 0 Spe \r\nIVs: 31 HP / 31 Atk / 31 Def / 31 SpA / 31 SpD / 31 Spe \r\nLanguage: ChineseS\r\n")] + public void TestSVLegal(string input) { - var setstring = ShowdownTranslator.Chinese2Showdown(input); + var setstring = input; + if (!ShowdownTranslator.IsPS(input)) + { + setstring = ShowdownTranslator.Chinese2Showdown(input); + } + var set = ShowdownUtil.ConvertToShowdown(setstring); set.Should().NotBeNull(); var template = AutoLegalityWrapper.GetTemplate(set); @@ -58,6 +94,7 @@ public void TestLegal(string input) [Theory] [InlineData("梦境球皮卡丘")] + [InlineData("异色波尔凯尼恩")] public void TestLegalZA(string input) { var setstring = ShowdownTranslator.Chinese2Showdown(input); @@ -77,6 +114,8 @@ public void TestLegalZA(string input) Trace.WriteLine(la.Report()); pkm.CanBeTraded(la.EncounterOriginal).Should().BeTrue(); la.Valid.Should().BeTrue(); + if (input.Contains("异色")) + pkm.IsShiny.Should().BeTrue(); } [Theory] From bddd0e68ae00126de5ecca8ac642446441e5257c Mon Sep 17 00:00:00 2001 From: Easy World Date: Tue, 21 Jul 2026 15:45:10 +0800 Subject: [PATCH 171/171] Update StatNature to StatAlignment Fix property reference in AbstractTrade to use StatAlignment instead of StatNature for setting Pokemon stat nature alignment. --- SysBot.Pokemon/Helpers/AbstractTrade.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SysBot.Pokemon/Helpers/AbstractTrade.cs b/SysBot.Pokemon/Helpers/AbstractTrade.cs index 083f163a5..f247e8983 100644 --- a/SysBot.Pokemon/Helpers/AbstractTrade.cs +++ b/SysBot.Pokemon/Helpers/AbstractTrade.cs @@ -422,7 +422,7 @@ public static void EggTrade(PKM pk, IBattleTemplate template) pk.HandlingTrainerName = ""; pk.HandlingTrainerFriendship = 0; pk.ClearMemories(); - pk.StatNature = pk.Nature; + pk.StatAlignment = pk.Nature; pk.SetEVs([0, 0, 0, 0, 0, 0]); MarkingApplicator.SetMarkings(pk);

gb4*yqm(x(7D-VTRbXE|sB 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%0A-U@>QfNJE#)vxhx7ax~lJG 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%0DMDiH%smQUdG9Sng&DMHO;lu<; zc+^}s89nitPAH-TE3*@&mzWYq&?j3QeDu+d+S$}R$o6~yt45&8!jWSCtYgv=p_S&$5UZb|?lzO+utX7N2CH;W zx<^>0du)wlJiG~kIAsU~D`tSs*zO@e%|k1m9%+6W9SCz8L28zM+o%23-xk3a06~$8 zmIMnW3l({efv^emm}b?ad5y3Z?=b6PY~?#6%`Gz$lNrKlW!h+JAD$n-!fCIlG-d=g zIjiGqO0)rA?(mzZFDTUf;-%h1`Gx|f<`%=_$XO;&(J_{_ltY-C{m+xKrh7!&bdQpH zo{Da2o%PPCP4*O~0De|b4NmOoTyH@x-D7O!Ys+W3)DYK7^yw{UraHNcWJ7Vy#LRIN zVh$HCSU`G+!%^$see&RY`tYdL%V||NfQ$^ zjz4t{6KshH#kLP|U=R{*4xteW6#|{DEk{F?$NWlc!NzZt#j*v_t`#S<+#;$aOMVl~ z-6UlSj?J02B*_3-zj^Zya^h4=v8|{$b%n*_FgV@kO`y8Zn<7<4mb8hMf-bU-pRzV^piT!;;$vUj|T3Bi{Y+c@Zz^?Z{o#ii(b+H zu}2Nos?FEL#&6b)Z~Y_9?7#!&Z59pdE`F z=B<$Pf3&&bU&k0%uh?^$?^)xWk&(TAoZm6{j_BAvVC{bZJZOOJ@@vOG_d^%#zFQpi zBHulahHlR;&tGbw9wU4|$oC9&XBN~^Y?lQ z84cI*X9f6kTZep(KTC6PdVR^E`<{5{^ZQB$-S_zP)3EX-TCIQbk%!zpJGWi8`36_w z^VK~iKAQ8;@BZq8zdpF-htt2;d%p9z`T2fQ>G1^+F?dqZ$t&f(I zKJH3>!a+|`d@bt}0!LQmR^Z!NUd^nS5JMQ&gGM97(4lL?u;#3cd!tb!R?=*82-l>n z1}`;uFega@!s*1cc*v7;S^*%OD)3j5~)z?IHrmn@q{W+)kK)G z_vh>qp{VmXWWc;?s;N`V%xE2k2~j7>3?)2N)(F50Z>5GjGD$w51Qlu=P4epv0xh+= zk&z*pDihWmSPoIT&XpmV7X39l;Al8*wW!&w)1l6@04fKOx*n2jax6!mgl&Rm4Ynu- z(Mc2t>WbJ3COrei-9v<&foMn|oyBV~C!mQO0H7na@?`{+%>^@L3F|>jT^K|})gnVO zU{x3!;M6b@0euc->Bh7Z8r3;>5TZeodDGV-DewWrhf+|w?kDVOF&G4OXu}HF1;$`z zb(RgZ04$DZm}-NE61Gv0kKwEzL-W`(xYigC4&`aoy09itS%-V5Wy)eQF)&kPtfmmG z!}Ac60hzW9R~L~rQ^C>UHq5F=u{f#H45_m_$f!4L%t)F}9a2j1a%mU}ke^DD5tn)c zNcD(kI3X0I(a@|FH4+>xg*Y4dh>V5@8#uuPMOaJ7L^1ZZjLwmXlN*H)iEWrHP&8^R zajV1n(3-u3;FTEQ}XS_Aj3`riX+0CxU&!TJsC#gKm1+UT7o{@*6|kZ3^55&BlYeAO>jd0jSR z8~_h}F7^Azi+&|ZyE_d|HLiY*ddTS)-};^MLVVvXyc?ii(4WVBz?=USsqnha>7|ex z`KRj*Ts0fvv5bDApnmVEcem+RwevH2H{-A8m!I~lY2dL31>XEGWksncp^;{$_eAjv zSvSu2J{$M0vRuDJ*0>ctl=Bq*K%L5bqu zvE+M_c*ytN(h=!@AGfU%aVNjjwoXsO{#m$bS@|A#p77B<;h7sQf8q^qZ^5p-)|nc!*^kGBbDi<3bRnt`>E+1m)&O;2kR9-hWHdul$p` z*;dv%d~=^wy1dWo)7by}pYbJ9W~L|YWp&4;c^XuAe2FgBpt|Er44Vek9bd{uL3PKM z7<+}PJHC{Qg6fVh(Y*>)cYLW31=Ss2Dn>zd$Cvt}pt|Erbd#o4cYLWF1=Ss28i<1G zjxP;HL3PKMpe9YL?)Z`y1=T&*Kism4Aw*pakK|`$rLVTqH*HxHn_O!R6{lCc`F`7S zi|#;X@wcS4G7A)I_veOJoNC!Cx7J!YCp?P8{_lkR-CX!1+{NDrZcw(|B_E#&Iw9j$ zq=f2#H_$3Oc6f}Gd@MhflktyJw=%dgLJ1Szp#O5o&;i<`{xK&F>3$o0s4$yP#y6`S|eV;U2#i{!pG`|85}-YrZZzR75~+V5W^?JHScAzUlIvCe70ys>ax zLgSWK3NIGl`pSC$5yDSMC32)q+@QN9KN>JB$Yr@Mx4Ijgg zWVpfG(3-HkHQq#cg~VUUk41tt{#D`=$hf~HPI@(Q)xEsES|y41Cj9+@Sl;*oGqSJU zkB4IW+IEnwSN*`LRejHIRno&K6u7sy_x^(nKZ&cGyiL`O-bT%&+VjmMp;CSOY*wuY1`{0$STHRXN>WAXl;;p%Xxzy)vxgxxd2N9A;$q_x6WW?xl}SE|XhE7_v$K ziQu$~{(1_40HpHUylr5<5ln9~V|(Kf6&3wi9o@+VCURf3;Wb*rUL$-G=`-Pz`Bh#l z8Ep5qw+<$;GD4hP0HYp?y4QaSQ9^GAcn46{HYuxNatRj!SyrKXpm$*FddX!;BBHVW z7phP_$UA7)jYRs(0wHiyH!vB&;@GDGsvhhe-1Z_s_%vc_E4Ebh5bqF57mA`IUaDQ} zO`4u9ZIJU#{-(cC^Y?wsyKV$yEGTO;VeGy5zpjTes^Ocwnuj$%b{9&OFpR zw8acI=^k7pb^qw-{LPZ?u*zXk0zWI{Tsdrxez?wm7C-{tx|;uNy#6gFyb9<)MMLG*ILgaC+wlV(pN6DhMm#*TM0S3 zI{GX0*ifHkv*kC)Xn zvJSTY9XiO-q}=q28Y}Zn`1q8)_;+E=&f>%3WET7SaI%XI&UAH?ZM~Sb38!QN;4O}8 z<+BsjOt5IHKu~nU+W_?EMmG<+3tP4QOfQ`lbyvh*sF7a`3!V?tLM+K`#Hq<(NI5kIM)AuJ`TqRdLVylmUy zE66i^B|k_Hft8;Izq%=7HE9$CMq^VMAEbC7~d!LOoB|8{;Be^1uFM{KoFZ}+jbwF)$#Fgnu? zj&*lQ!R{`_fr+{-;e)ck-XX~$w7}2tQ_Tlw`BeiRO2GLZaL44a^nJ4HS=Lu%rF{)~ zO)U$=FT_dWY%Q%)z8sD3?2xIE|60;au4JUUi(au66qbIlI_HNaXQ}1XvOxT#GUY;J zhtCuXs{LO7{`b-0R$=Lfs}nO{;=}^!Oi7lsthHm z|51*4uQYkPmoHm|#&**}WXiaOKU&?wzHSQz@_*gJN~Q8Y)`u33|LD_iC|2tF1Wy>Lc?9|9#}yE@X9W-5unJ zc|S3_xcd9Sqk=kEDe%f?4KswQ}btRLge!HkA|EOlV%BziOgqqG%y`iN4E3({9C1CcOTjMC!Z2 zng1|9)ncXSBfqf>)qbYPJdvr-Sk~P&f1^dV?ZaJ_DDi0k=qgX?Q_CG;xw(i|kiGW~` zsh5IjXW?KoK2s`tuB=aWD4x0}C_&jefWcGUV!&by$$jB;mW zoo-RkpWE*&?4VW9e1AUtG-c+H&@wrtcooK^9gLmm`eY6rYLF}W@G~GcWv8@~F;qrV za`keD0f9O$lvegTAuL4%V;O~b2E95#Xrtf}1pjmV2#U-m8K@=GVY*`M;TfG2y;pa& zCDMyR!;L~b$STMk5qBZ%-xZP{n~f@fa4WgX$|AWpz5I9#f`A1il8}J1R$VDVc!zC0 zNKF|y&ne4VQdsz{kmnN%3rKc-?duAHo#|F)o5Q0CM=Bp^3oQ|)~V3;!kVj)HDI+6M+FLR}}q zBtI`b%FKEn;e*gL!N@dx58)ZRt8N2jf++c`N71rtAv&Q`X_W}h(pKtd4Ja)YCE78K zsyL=G%uQ{a9pszvbgQ2|N%%!d?l%h&dPBHE(Xy5LtLPt3psYd3Zy?iMMT)lnWl3Z^ zl|KI~_?+;o{AT@a3>n_FDvH$q%8QA)enxY|NYQDs5B(V+NONA9zYo0V;< zW+&3O>z}0|{~79JeYPq9vp$nY+`9j?D8j*2P7s$SvtJa3poz@n`unIq)-HV)jxg1(ZWvy1Pa*61 z-QZ6xOKdQ)^lx3@$z7nt7E##9{w!mSGQJhfbKz(u@77>MWmB|DW>p=glI=h?mYwmx zPX7I_{|y|tg>b0Ow}H42@cqR35Ab7$-{g12j^s<$KK5Hq_$`5h44K?Q49w+t0y5== zUo;(xn>i0&m2FOnm-&lVDlxNX@I*WO4#;#(&}P8 zMi{-V;el{42;WnLttR2ROs zVU0KjKwvjo;SUv~XQNDh%1rAN8$k>HkH89lEU1>$&$ga~XShojYgS~5vV$v450?w2 zeDgmU=9wD%>*niid`p~Guk-(d zJXuoTPAQvWxy>KcOD9Tol!C@{SN4U2CHz{^nctFOQhAc-FS49K_j=f1jg8tn8uOJ`G)`N$V zvv4}`<$}<>OyN?#fLKswM`{HJISXeinYfL}`uMB#_X2LNEq^D&yY&3k9|DYWO zYZ{UPkt4wElu!?vXu|3`_+2sug?5pn#3hB!I&-WugVFFiSr+pstnNO z$4Wg1&7raE>!ipF0k)kwbQ2KMv!8f3BM-9}bi4tbsos!(8&NVHWs23jK~Ge1<0~T# z*}abY2MMWI!B(?1=QM7jHN;sP=hpFwu}n{ybQO->>>K(a{6R-FuE7RFH(6_t9LAJx zGTEAcuv9^I5!udPvh;Pa*6%ahias-~SnB0c5J%ZsV%`-8`&MdADx{n*37kMHLuO;K znycjV>(0CSjy)FM^SY<{Oh;Jt$hAJxu~-`H%U=_vW>(6|rJOa}`FRHMD4otu5?&K> z0>HUOoY6Jt0lNNfl|D-?BvaXA&$IdSf|IREkxhS*N&`twR&aoolvox1#>`{*tf7k^r5NRTJqVWV6+^TjaJ#UAW2o6o zQ8{MLb~du+SDX>QDoC~J`k*5Nx#nse@2jeN0`&IOiFK^8w~%A!_IHz5CYa@|r)SrD z>yzFaCNriP=gIB&)T(<0nuui+;)J%S1O=?cqO#frUdK4aw;7INYAB)0cQFuHGk4NE&C_EwIq$$ej zTdG@ajm(H*vDK=;Zt`PMmFt&OgJ4}HKiV7iR;LfluF>Np!S?#=mzo6#QE3fjaYz6M z^-8Th$h_2qEm|`b^}?1!+#VdQwuAL`ekN=Dj8#hX*kz`;>P%(EUn{w^qcYmnGCX6F zsMY|isq8oK(s@Ew9IVrSs1Biva@YjWZRk!YzIlplBg$kSwQhGnz{t4%Iw^%L{XOtB z#%6}RIFPIv9Nv>Z`wPpqy@9Ff=5W1~;cX806c>Kbya}MGI)UXRaQ5zS>VHPrpeWTX zx?_KC6DDRBxLI$uU3E-^u88}1`?S{v`_hZE;a()d@o_47e{Y=X)&P?TZ=Y~lK>LKj zDp0vr>*ASHr7hmpumzHpzotL%-1^y57$0Tjpnuz&|N8L5xoc$@k|DpAo=gJLtuot_ z2w}8c0|SoOGbz)81~F#Yof%G2Nj8arfgJaj5&N%~$a#KL{>WMnF)_wvZD5cfDOfoY z`C$^8AylmNx!0~{>~^Iu;_MNF?u}5kVd)ux)lree-}wKUjU8^1`s5uI?GY_FreMX; z(`b0F9#yn(gDqaZF|=hmm=<^A9>E^!x=1dnChZrI7n!4-50GY1oyvdtdbQNq`7joi zYzuTQl}Ber8Q}6bvj;vaWrI6hul5JSh7rcup=OL!I$nP%uUm!&XP&~N4S%-v1iI!w zm6g_TQc=RMgY?)1`)CA$k<%+~J@JhuzOlqNp7R@oBRq0Fk z?ocqWDkuze$S|mg4U2RlY}mvq8yzDE#%uH=Gp-m~!4cVa=W6|(uadyg^NA8C;de2kT2(xE<0%)SgMy5Zo=D)w z8O&-PE+MEKP!ysAiUJ1|`JMv`)R+5EEkr6;4=Oyg&3Jo{DvS!aKR7Wyn%PB`Gbs9x z$2FYSKpJQu?5FKaquY_pNGUkM z4)-OeA{rK9E+I!uXkFr6kKfVYstp!O2T3^Ws=b8CFWeuzu04Wdy_Lmft;(mW-J%KD z_h41wMtZKi?HaWOF-B*T?L3Jzj_&iTIi0_`9`*oFv-ad~AO3l5C2qo#M)%R!V;2407lAYnScq&}NNw0X2NHrTy&J7&k*aAs zP1(irSzP+DstgmOLt9QS4+Jo^ED+Iu1+7Y?AY|<`Iv81kMXizTJd?)69WmzS&hZZ> zt(@)$7sLi2vR&HMEf5t_uQ`O%El?m@pmHx8rTCQgxv+xzIlE17BUv!H(VY~S+(_5$ zm)ki5fly@cipVDWSJ6owLS15;K=!VvO?D8o!6>C5g2_U7C=seOnZ>~IFaqPVhzTNL z(nb_32UvDDhAS+z)By>MwqkF!(_W^oTubql;MQmBfU zES6B`jGB|ccx@3R)k@LuBP9#f9WGM8hUuT9D4h!O>!UTc22Rw6Tq{6p-}cNfIz~I%mCz zv1;m^R<#LZG?3Hd6PgL(MtsdKbsh_%(fF;^!)p&+b0yOl^G}xSyvz_#6;QG|?Tm7H zYXg1q7ijKw_TH!oBAxSg(OP!UI3}9sA%&^tdH)V)5}bb+c3Yh7$9oE8pnT4D-UrAY ziDE|h=N~~ARWqGWb<@-}$*GdWBgsvgMAAsqhR{&O4Y+$K-6ar3*k<6 zy-awKcvPo<2<5M*d?65fVrDR~?6ot){5A$SdZ^cG_4$iIE(W9ZT4QtMFOAk3S4Dnh zsjlY5;Yv5J`pg<~8HbCrsj??$#)1t!sWx;oo|vdLpGC?z@1Am!yt5W+I9VpV`7E4~ zMQ!nCuthhs2*GAtdl6N4#?3DN5{G_Rab4k`mr!xP295XUY8;UPPH=| zhJ&nsIT5A&RJ**v-6eUB&i@kAi|8EP5uRX8$yl2!0USR(M!KV7`B&ifbCh-^uF(&| zO{@)4N0fim(Z34FBBG{L5M4lqS|fBztt;Y;ETT-vxK9xb@~cx(6Y!bWWT|}nVDj8c z@@)6y$(C30WTz{6vcZ+i*cX$TfXEEhBbiAc%1m^Uuma1Myb6++cCOu6KZB8<-PoI% zlHs_sVEd1wbf{e@!p$$Df6qAdXi<9f4xzL>d8<`kf9}?HDK>1fZx8x%=CHZCDQCSJ z&Wpy=voiJ5ZT}jwWYGNMNY4s%Do%8xL0LY%dYV97*34@`^+SG=jP+dTcD-THpIiDV z_35!2%t*D)Bk|O-K=e95e{TLdX+u7|9!KQ@R*aHV1-sG?NvOl=q;X_L(gVt3*)d{I z!Q5QPbiN30wesN{Ij~}6yu4!!@bMv;I>y9ab~@oaD5#JejQ`lIVIohge*=-bLlpJ` z(qqbTqq!2P<2uV0K_PrRV4#;%6}3cbPZ9Y96A4umXGaJ>(S#wOSb58S$Sbv_6eO!+ zPz6bP6z3SpiMdcjx)2>K%_}lm6JBAVA7!H~lX7a(Po3)KKndT%%cB)k4pBVn^JH?Z zVrS^@!eg5A6nx=g9Mr`zYL>qwpc@TH8#-Yc$_9q>i9)5aG7=!KbAYnkgTy9821b;{ zc^Yz)0WClVT1!#?iOgrN#@9rDxDyR)D$ZQ?p_)H1`E{^HF@1%lHxu zRxfv{OMW7E3jbo~ALV<_5sadoA&YH>*e6k@-58$X_C{k3l-yWb3vv1IHwO1+F&UgSWNs2brZ#%<*oAF;j*uNP3 z{#^KLnp4V;&2WPJpVG4axxYYA^DhMD$z}!V(#~e`o8Ql@eJ=uGkyT07z8P_x#f{>O zEVjgXihx-AP?53rNx;926w}-v%vcXFskca8TQslCBK@1O2Hf_byGy(n!@!2!VEsVW z311?WeJMYzd$Y2@yi6h{$k%7D9rS1CUqQqv0~zDy!dDVB$yPFuSe}~%oy#e)R>BCZ z^Jn@+ZtI1w0!?-=c6d9!dBhF5w&(7;gHZTtey~B+9b}#x2c131VD5xI&eLBb@YnK# zz1yAQf2{EN*Woj_M1<5>+|^1G)qM7Qu0_mSh-v@i!NH2RQbR2;5!wA( z*(hC=bXAj%b)p`nt9H{>SEZ|rH1-*nlj~w-WG4m(FKJ!r~&{KVLQ+CqiK5bRdso0HL-Q;P{?cIgjeosof zHzhn%`s3|nY@3FQ*MA2;#kn)c!S&yXU-f?(>k#S>UBIs*!7{$d%K}hlaveinMsR=` zzf003qNn2yD;J9F`po*YRrta_hR0k5`r-x{0z76fahye8oRP&Tah@U|cua%Vn?R3z z&8)Qc)qY)AWIE_x4)#1il|(+%`99RgP9iH;IAczT7zE46=Gc}BGbC>GnJh@ye$`?6vVCfpNv7WylB>!1p z`0tipmR#6dt32^H>I8$4HHF=l*xnz71M`0mn5usxSFe&;89`v0@G$4?e32x^e5_ry z!+!;@je1B{bFHe;`=E4V87Xj{?g57wbU3g1sa zJC?2?n>;k~0iw9?P(PcD!n+8_Qq;#(}c!;958QC<&JKWp3!!Y%87>_W)TrKL%iG zSs;E>md+f*UPUvw5AiLPSIs$Pq)CSwYct#hWIc)oUBo2r$N8Tifk9PW!wD>o2N?SL z4)t)tPXeF8DpFjr5f0!a`$o;|M~WCsS+93=%O>6$E)~;&Y-JkM*8CWpGwP zM*gQIBV*ww3x8r*7AK>Qq6FXn4#t=^Ni zkPgbmgJI1@(yoKO0dH`o;0?1cC=>N9QNTR1@-F{=gq%{9Ey6-?kTc4E%DvRteHK+&-2V#2}ztM>U zLz7sf32!VQKOxROszq8)>KTmKg?+BsGLW3yL=-P9Yg26Gwkk{7FI=?eXeP0CWB;Qs zL_tx1zWXIKTTCVGq_PB)Qi z7ve<1&ob6)*w!Cp#`b73-wh{>_?i=*Mfx7&dvj^oXwY@A``IxHi_kzRVjy3DXy{cs zP$llQSyensrzG=jKw3z52tqlh7FA^!FcN!oESGP&a=ay!FB4BX^F)ALn`X<4oa}jx zP4RJ9oLOR@ZYm&YrH`;1@Sg;qHmaXo{vz-e&?xAQuV@LEZfUY6&{tog55h0Yz-jO+ z68x$JA)g-ps1xNNOxuVSHlaLHlq_+FLmd71c3JGslc+Cg5*UBgb5D(o;aOjFk>PgZS`1DbAcy zCIL}8-41PhEo`erGbJg)!P0Ju zHjNIyjyL=UKmPsv)Xy>c&cTf9KR{SGj4)$)cEK5~a`c@w+BrurDC%LIoGZDru3Tl3 zu@+0QY=<1J>;en1c~zv~?Gb*H`~(5(*!`Jl0^cr^g;O-)H)~bzCI}dGwQfKW$ug!h zp}Ij2i8gRHnKq&&1sGji)mA2IDEreb{(Z zKjiJ{O@-g0ZY0kxJY8z6stJVJZ$~wjw?>XWrl_DRps+9pG;;Iy|;?qCZ21cGP%5P95$dlXl*SLtG-YRG3fX zB3pGiusD&s+0CJJrya;asP(UhE0h8zoz%nsAwX!ta3TLXU`)84GcH*R4>9?Os8)2* z1BkrJeX?PmO8OCT{*U>QlmEW5+&VOC=Mzb{gLI}Cy|kQbQfUkRwGq38CM-dW-Y`#) z{+0u}LyhcbGHu-G|AcJ04rOV7%M?t%X zO5F`huf5pl2IY3dS z0G&P+DCPIRoBH%#b%D0I>e76e#`?dMGNPjQAW=1gQb=|zmkZO)QM*1pxIT$3Wvn$p zgZCdK+gRnP7kTH24MOo4xt$mY)j)ym`@L+0#QsBeCxen%7a5^WwkS&dnOm=lHYv~J z?BWsaPx`67+3hh&*nf5V50>^HsO=Yszh_h#krs|mwY^$)y>d_MF-O$vI`+cLnE7qA zvWJ=D2s6u~VUX=D{K4IYf{@*o9sUM4nunr-+MF(B(8U_c|E;95{hLS$2N>+V7u4OX zzypiBe#g(!w$;r#q}wckXtQ(`W-e-$MsBzPnmUXwoj(G6bfSv~gB*1nTEZh^;*fKp zK+dXyes`DP=z-P}#rJNFcEspY%L3^pD&?AILo?y;scCCC%0U<6w?kmc`pXf@A3)gv zB?U8_Zv2q|(wp+se7+X`iLjLyJ>7rd6a4`l8xvPf(V+-9WBZc@CBCViOhkt4W@0L2 zaxevnO}0?nsHtB*{BN>Xs;ZfhH1W|sfj7Zq*419z-r{9?$m@XBU2C#@Qw+!9y!oB5AD||h^n*&$xmrE`;3*852*$dBP zYH=Xaa`Y1U(m|`sI$X+n83Q%7ED(P$GkxLdsK>&;2!R9@IONBKLw=i|@UId#J#E=z z;X~?`vw{Pt$B@>;3I9f{6aJlFe?<^%{|Pz=eFjG6@4^eIk=fl+FEQ*vs!+RwjP)w0 z{0PlQl{R{ia|>~1-4Kz!Txh}%tgRahO<2Bl&JkIP{vm(;gwBfoZY9j(qnI;$>xGm; z^V!T*u|6m?vkpFttqaY+;M?w!sv`@KDa8(X{Dj@4tTLDbV&uhgCfwL;_#Pr}ghY|} z^J4xa$Z%iNhy>u|B~Hclj)>xRt7+2IXW8OO8r_gy$9~vt{=r4_n(f>vcq)!@=UdM~ zWt%r(E8OJIL44!7$h%;-YH^+@7$nc;?<6NXIM-HRgn0a;ApYJUZnm4^ixAhF>|`R# zZnF}##>ece7@?@H)t0@IbtL0tkL@&zd1BWqLSl(Ok3UZC`sMhJQ^J8pf#bW0VvucA zyT^B`%X%)DM@a%THffYMS8c3;a&}{U%S>n?E9}V$f;+R^3g_->B<1f1+-)`73oto4;0fsQEK>^UWWqYfdcHjPgWo3@<{p z^H>EG{VYG$^vu0qM@J*Cg9WO~d=nINwDdtF&fEumZM`7Zgo9XH_sTWKwvXWx;YOJB zb|Ekc2zdJ>Kb?#C$?0o@+Xo_`u}nM6iGuf%W@fkE37ZK*ad$cXPIerj1_xg*Y|BL=>@3zS%hSnr5C7TnW1rkf`w460##8qrRxW<=@c zix|mSu793D?)4d-I@4RLB3#S$ap7CWG_8z2F0xFFO^=oDoJx)1n{W-Ay-`8=YLC78 z#c&aMYj`If?kio~`0t%up4HRlcE)otn|GTuefeTz%-;Dafk@^ z1z-q(7+k7URf%wX455k}I)9|$ndni8jCo9g{lgzcx_zW-wi>0)`pXn$b?(x{+BT7+ zI6@71)>gOeB72<>q1U2X2{}ZA-V-CEe_zNms;uAIMpeB`s^;asuq^SN6Qv;C1y5|^ zFg>_Y6*wZrLv{tovmVoSTMt=kJ_p-Y;B~?N5hra$0s9& zwoEUkB0OrHv@fF3H_B?!du2Q9-mFfGpI*A@X-DLh4R=q?BClxwBCpJ&$SV};^8#3O z88JI9&+};|WaydFvTGIfShn+C%FT|Y;HuIR?N_!VI;$w{5NYc>t7}X!8f_DZFr-{F z`+FklJ9`3+#$^{@8-)iozpF2ff+qW`FGjf+6`?ma>pN%j-rCyLT|Ju}6rkC4oiAg( zfSNu4E*TT`r9gEy?ajCG# z?dz#8%ev^2F1|WrxXHB_XRp4vF?2~Byy%+lTK7aIJWCill-4@4U`=iuHJ3OV{WB;N zWp?rIBJ5GdR+f`&=SUJcEe_#+1+Xz-K*G_oodA$gMC-A*E-Tx4MY2Z&O`xlPHk$`w zQfwepv!3TTXhK@l`FCcjy@2SCX3OmGA};k=03usD3ojD(A@+>i-wt^*O%EJuGp7X| zXW>Lt4{a`KpCs-UX90@q(*uaDT>oAxxG2)wlLRIu02AU(kgWUtX==!^#!H21V`;oBT>> zord5{wE7n!Iwa2+{eh(N#8`L5i4O*%w<7KaJrX`z1J3H}1JM%^BF{7`)l3tmj8)Ax z9}>sS$jcJ@bA$!(*vQ^%JvC@gHje8J4mOVO2?}o(_OcX-FRV`0HAa###3r+>xlE=P zcx#(CiOUtRNDLUd%s^u0p2#;b4sV>vEGIuNb8d{*T@fWR%8gy{VRpDDRaI8Q!xnK$ zRTo|$#dQ-#aZ&b@mqlUrNSt!$QmQe!oK~?bW|H?rWYZC@g^9%OP=^Y&OEiS`0z#KQ z(9oK27ETs|lNVI5nJH>hlGQ?ZALa#~yF1(-s!pW@=>zracrwtQ93d42uL;=Pp zj}Bj;7u2p)dEh8Hu<;e01XVTmE>w?)8Ux9PHe@Z?%$$_Mp;8?3WjnHKTBA>}w7?e* zh&I{B&|TD}ACo4C9-`znKTkoGzM4M+=%|Wa^Ecu-*2g@es5h&2hnpo)j98Bm>yJdl z{I82R8Y7NA5)r$IQ~dP42Qai<1ymIBN4auB2L7JV~<4KR}htN+hR{7TQF!q zR48mk8V`Xj=KAABw?|4&1=pbWVfQx~Xi#p#zYIxrO0`9V+7Q z&ri5bs$?&4f-WZ#Yn%se97T@A%b~+q&svS`z@}aIVZ%xgttRx zN?d@3;mYB*&@g&>ig7~p)dC$Iv?DUWsu|2vAmIq}-y~x&Q31#KoZ5wcFM{q!@`(oD zrpQG)kX+Nvc z2cm)6%kvc9fp}r!^lSSFyc~ohoVHKEdn{hefX={sAzl?T&Li*&383d{Um83fO94t| z{2%A~M@t=oOOvQ`a#7UDNz{?~D2hE{lyJ7-AEODUlc=Vbsr$!jB&Wm?9%quTDBO2OENFT9dk(VVK?;#$*t=(ti|Prl2+-jIHH9NF~;9b>_yr^)8&%kX#rAtpgU zvBeSTW7W%;!XKk+;Bs+R8P!CZsPVjeM&);VJqD#1jzqds3=i5l;ji;$ewI$8cs}qE*QBUV&p$Huje>i!;-1pQWtn(n4#DEmsfu`7f*4$_jpzCG z)4OqJF5LqW5r?Iu>4su(s2X+=cV;(kM&E}a?yPRy_|n+~h&!hn=gyx?;K}B4zymTv zoht6RgKe;9LCY*xJ@rH0n&D*NlF$LRrjtj_sdE?)u_R zjbS>U0O#7#eMyy-_rql40-~j4&GW)2uoE}DC<YWN0llAC*^{WNdBgOSot0 zJc*txT)0^2%E*ek^t=reXMvV<8 z@M5ivBIm+OB(PHg=H4m|K}eRa>fT!wwQ?bE?yUx`NTI?kUUf}1+0Kbbom^2>ntdFV zM&;)998Q$-Vr1`MDt*F!_A*@VNFBxwRSoeD#k1lvHRF>@f%xc#o1_%WA(%-P!A;GbvmKYaB-@x*$jb30(}G}wM#tt4T(x(QO)GB7)v%g$G6vkLVJJE|3s9zd`n_QGItKXlngPJ@ujoMu{ zusm4-SRydGV-Z$E3Et4>#&Pt-zm5;9u&6*;2OHCBu9HDAY4ZpHoiypFl)bs+H037K zDsU;-iKOj|kj=0R#xwjwlw1l9Z)`Lm9iPE?s((a*dm)2sy7mC`>TzUXZU~p+nSKpX ziLJAYBWty+(@%^0nW_!JS+4~TCln`|M_IYIW3gxm{vAE|6Z9}Fv3LV&?Y`)Q6_e)c zBrWvf==|Gh(!E;KwQ6R0Y7e-p4}Lj0e-?#A8p@`8>qce6x^*vh8b$?Q&2X#Qzg}xL z*yqpT5gnmSrji(T8%VH&>dynKx^lFReJ)nOA*Z%-dyg|NLiKP#Gf9rc-SF{5pdn(5 zD({7j*$c0szDAG|#ay2G_XJ5Jbn2+-%SGBQbGV1?m^m*Gp9qrwB*mP$m-Rb3JfLNg zj@f0wfjqmuB`YT${S|9vWIJn(8`ON3Jh9HU>*x4SCWB+}TFlHTBn9B%OX?Z*5gkS8|q7?ujZdZ|2e9jsQawgZ0KzNPFzQCdxh_ks5nL@ zH@joK%B3q3pY0eI!GkY-E$o06RR1C$9A8k^6F%`Z~= zlw-cTw1nRxAbO|p#wZ}Y7A{ADBP4K>1YqNf(lO+=38#{4aA)gg3a#YkzJwiBbY}&b zSk#2_jVNwkvxj&p*wmWrTEYQNlL4dlpDpUl=57o zn}i1*G2AHn3<62YJxLsWs#WHx3!xihy9A|GZJJu z*pp>Ge7@#ltBJ@2w*p@v)kR7S{1@Sye=$FcO4(4)5VU9Q(Rb)&<{pmhmDWP1mkFKv zw}A8fhj@?oc7QRZ0W70NKEi!#$XxAt-O{$(WU*WZ%r6> zz*C=H3;n$kAJ;gHN3G&iA*e>&!gmKX;j6&1)S4VjNi3JjV7lg$BvIhgt25=@s;KkI)*V*cQ#v zs&&|zRzJnFme{gZTUY#pdd$T2aLi|9lqTg_^B}HFDPwALO1=ExnyC0Mt|~q=dKCaLj-j)5$Yjg zYaF2-BG4@v4E5}5`|qYmbMr>!IJ#es_dR`gG`Q5F$;twFV@4;wnUA)Qu2=}_Kt4S4 z1{u+8e%BktD z%dn-l%rib4K&<~7^05(_AnZZ19g$$!0;yoZHJ)G>CioYC=f8*Ma&VO8)>+19*Xa81 zjbqNT2_|{OPKU(WRU>L?ac*lj2RcL$%c-va79nhQR9LFtPI6^%{Y>Ok* zLj?N+lSDm491usShlm5?2=x$gP#mEiA`sF{9_k_DkT^m;MC^zo)I-FfafEt^I4q7( z4-tpQ5$Yl0h&Vz$L>w7MsD}u4i^|20j@)Bn1oeP$Y#gDUNn6;3+#}6cOdE?O!?Vqo zdj+0|PL5uN_dgDR0x7f4@2`0~z@ymjP+9~bCPB}@6<#jiDQd0$^P zPro(JS8&YB*+r-K4Fgf{kAU_g;NhM8xMoWgoM#)njH|1g|6|Rk2Qj>xn5cvOFW^Vn z!wSEiZn+Z~pP^6k40ZnY30Kwen>K62d9-0j+hE?H$oM~#LWDl(>*%x4d=rV*v0$Uh zD%tvsNlYvAZR1t zX_&W^Zp-N_ZM?{pd$m52kSFn{NQ{UGyKH}#tKSg|*Zu#=q`g+l;Oy%^Y5y@WHnlW3 zV9@G$h$KyreDA1{8fylRN0w1;&2;=AT zZf0A}Md(NXJj2xzxCHge5M~yHpC|1GW0+p%{jx6*G%_Q}ud4G%hH&?hI90tjRg$Sl zl|mD?wT`w5O<0ck-V!U@PFV_IGO9C_I~8jAa3;4dh%>z)pQ;e8w2XH~Kdz@^VTYd} zzA-R+xPGk5C~s%>i2`hHu>MoqUUzbqw`o5Ha+6(>HH$j+Z?Z9dI=0J=lgB~G@Fh5w4TO>k|22&V_^fN;j z*@l~Di_NtdL4S{C?>1HUziL`#S}u0S(kJqrVpl#j)72rDslxR-X5rUJ(&C#ep8+1f zW6&gUJn3;Wcx=-BRqAQ?Oz&{76h^PcT;@idsRZBCqNGg_0$ON@UR;mJan~gKf4O@`8@; zH~98NY?C+_$^`!o1HxxW2Idx$&@7?FBy<3wXC$E`^xalB{HC;@D}AcJD+Y@m>MH;3 z((zfZRO33@rCzBNJcj6U99@>^_cBcxn) zBse2qQ!9y5!MNP1IONbl>)brHH#`uc4RzLT>GvWNu=@f#e_yPsT)TyL?@-|qS-8)r zEKjMEspX9ro_INfFt2lT_!OJ%|6vddBL&Bj&jubp$V(+&W)xQOZE8Pyj{hB;xwl9U zB%GI-Be+d)VL^5Fi-L|1Yc-kL3sOg050N?Wl_O*B_MOUnGMAC5 z=^87C9ij_WLWx457jltbJ?Hlu2?f(SdadG)3yq+sE>_&RuSgevAHRhzOHYdZADB@1 zL);Bf684bYG~Zm6W+!Nf%5%RWHprT}`e<29^yKj8o*Z0xB$f@NyQ;umY$w~TV=jdq zi$6yHm9_rFcs^eG%Iw)K+nj#9iJs~YF3ssjj7x5POusN+##H#nR9{~F&Fd$E{hts# zM&2~0MowP8ax9;iqkEjWL8-^HOdub=4NnZ}+56`7i@WWYJjfKz<>F_z>|Hm3u6j7+ z(>B})8JR7*Yt9Q?^EqWH03EF6{0>Re7|kc-~LvFdaBzE{L6%s4&F{ zm|39{H_ubX9qy!XAY0+b98}5~_YtT?2iOYflkf8-d{gB1Q|LiHLBf+!=;B$>>i9Xb zlw#$ZNPJLPfINroc77@Sz3@5)p_CVkTh?czo!1XIbHnu%ksN+Tjz0USK-BSO-Oq== zAd)XPn;dn`oXUs4l(KK+$0G0_5?GD`|0w}o=aM@9DgymlK);Cszr~f^CGx*`!{0^m zzt{M+5-;BH4^jLdB|g*GECBI`e~JMAB>@#qc%Ldu7qWuEokVWVSZwCyyMgTE%rL;H z*>L9Uaz}Q<8UMA6NYs`7pUJ3bV-xTgB3-pS)NB7kR^ymVbPR7&#D>x4`N)i24jvoj z-{MOlI?OVl^%<;^yzFFr5h%9!X8Li)s4V|Ej1a53S~3)|WP2{*ag*zDW&};BSh*jr;}2jZoNpUSEa5cU4{= zhQYTpi8~|l9hbuz&pdJ?v#rgG;Mnu(Q#Yh&S9pafQX{%^)h;XLVatwL0F;Z)|CKtc z-ISNbq(5eCVgv9(s`M0UDHZ3e|A+$NxauYR>`&#Jk@9^PeRFzG+`ah=T8*v6;Pl4c z#o(kdZp$@F#o)-sVZ~rab5VQIMug{sM;6zsXw9*F;hu-+vO;!96nPj@-8?eJiKOT*w%pGv zyCbXYV1}tT^NTe^E2|8zufV3 z#D3LUmO44|Ei#_ZtDUYxweUDG+Ai7pi95_IyZv@f-DF{mduT!2}UNXjX@KPtdbkUCKwr_HpV1qdSCkDPUeyH z{rn15DY`cz&1KfiKTN1T(&u~Ss7o}jo{@u<5#x)dyP&G#}f>?JAKXO6zpjbX1&!TvsQ$xsaYL<)At)X%*b z_MsH)&xK!9V%V2cuy3}yun(tTUwYNks&TrHreHsQ{QU{+RVmm(OYa?y(|scatG@2z z2@IXbs{UNMZrezl?)DVy&JP^HEp{`0pGv`AxXG!z_L4fEf0f6zqn#_8k<%UYvp*c<2QO$FTRLU>Cftc6bcCBL&-f|H6?A3p!*`55-v6zrKBwmv?FeI*5Z z&+eZ;J%(|pvZ^lEz4pdu#IUcWU}t`8-eJ};f-*W9wBiOPm*75q2=E1M2*va~no~I{#%1vNt`uM_|a|tXxPhXH9NMLC? z_OIdiEaI2xxtP6WeF96blc&C^3rqLWIU{=~>C!areD%BpmR_3%`!7#m>3RB`n{P;9 z>GA5k@dXJi&7Vh3enSFF^R~P4Ur1o@PmR~*#Rn2tdi{CHO+QIsX*&MQoqtbY={0m8 zE87?Mb$U+i+vg^*G(Y+9PxnY*>2>Qv@7S5Z(mXM9(KQJyy&k;qvgaqT^qPIg1Ggox z^cu4~{gwolo>M=(>|F^gz240|z6(pQuRmS5D@m8;LxU@yN?>WexBd1{C$O|!wezjt zO<JX`6bUt zU}=7G?%mHxU}@fcfBjVnEG;8UUw?N3ORqm0e)r1+mR>6!Ja1bfi|$VK+2c+)C4r@7 zwI6+IK7plWydS)EA%Uf3grA@I)&%y(RC%ZU_vaE=TJ~K&aww6D(`)wU+H7s3@k`4c z?|At{0!zyX6W1J_z|wL@{f}QuVE3l#nZL4~jmx_&1$%#Xdjd<#%-5d(ganpeL;LP| zAQ$JE-h;gJh+pSpSb8ly@y#a|Vpw|p`Or)4QVdJ)7w&%hndKOkmZy7V=Cqvp`S1UF zO`I+*`;OfC)U6S0`6T)!UhmTD!GA(9aUN;f*}x7uhNahv$!~OF={fxFLU#>K^QhkS zHNB49Fx^d;UPnHCvy;>%y&jx_-CbPI^xU`pHIu;7>v`{*o#rRUoR&}0rD@I|E+(+_ zx^+XYl)%#bopU)m$OJHf9@y${_fu;HLNA4X(lS@C z9G6~SKXKr6k}l1o*8ZjoOY_oiUb9bvpbtFBOf1d5GBWeD1@Znupnm&#` zq6g{Aq))0jvx&rj3;M9-R*=H2UAVB>UYp8r#jV_2H+-Tl)pEWO4I{jLj3 z)9Md@--V^u-k1NS3rnws7jc&>u1lKdU&Ph27?xgR`p)gb(mdmIEXU(?>Gfd!v%0YK zdQdEP*X8uuyLNpSmY&1C>v@{*-QVt}OV7m{kL<$Ivc>?lZr#5NOUpn99p8ne*Mkq=+J&X({>R?jg{67M&Np}U2x&RzkS}y$>GiI3MOVj= zUVGO*rwdE3KNr5fE6b$UgORUxVQHD<(D_2rXKC5u-b1=_M_R`FX{Recq}SeiPOm0; zrq`cueY-0oq}Q0?y}RpqT1NQe#qBuHOB!1-FD8%xvG9hojHO#^4;y0A1q z$rrk?G(S0_)P<$#@$cm>EKSFwgI!p9zU^JirrkiJazP*2Q7nWXszBJo~rRn&`Cv{tm zd1;!vu?Lo>)$Q*|(k-7xd!`TKLfF}O;;Sb!_f|M1_4_#5k!vUd(o19N*<$-@A|khu zr)TCuPKW1hFrOR)&6395?rP;f}{fFS%&i*cy*p<{e+>zM>=cf&JWm@<8FE zGJaKD3ilnUM4a{I9NbQCGZk>6CqiNvCgWYiqXjXBV6xhi>Pk8M<*c?(&wiiA`J~A& zdfda9ez^K7M^w*A>iKjzGhKy?vr#?86^a`A>+eJ+GB7I_8hfu#~$eHbH^~`qeD-3U#VGK67 zbAL$u4Ace&h>wUinjvp`0&Oft9<@f(yoPMcx|zIu7Yt|ap(HtWz)C_nNr_FF8bUTknP4%8hQ;?vjU{t+q0j6^ z6T7D{s`Sfx0lYxwVo0}An@bn-u3ofp(8SJerB+LqgD4R7Gkq{2cv(!yYrjb2{iGLf z?LRE`;a;@HKSZnNdP?Ofqkqtd1AEcd|3kD3deN}O#>}VUuGB7A-G=%!5AymxV9bZ6 z&$OMHL7VT2IAK7PF+84rou7v^Y#-}=-Y&-5-203(-f*s8lvw3QA(#D&XHW0T+rG-H zB=PivWVYF91ty^rN^-vhVr#S~sbezW@`Aj?fB_S1W*&1Im&%yi^jt3zR;N7Cn%)qR z>nYP)ZoG@=v3V{u6!`@1PT`A__OiO;OBY2!b;p-3j)Lkk=N)v6l09zh*TpyHE`?aX&;_~F@{fK6Y?$ceU{AGQ`H{O zUOZ0h0o`5V$VE)K{vpP(Zft{hn%Lm63Kiu;f`Ot(*d9gp$5lAWL_SItrgD*aoe#@% zje+}=zeW7UR?p|vSJ#;8aphWTVzT+aNQkMfxn3=OhYYO${0cRkYP%cMoO$^ix@^8m z)bhFbmL%v8_hQUF0iyKT5EPfs1K5}~)o2uqCEv32d81__C!;tSj>b9@w>FBCk(zrp zdFg03HzTtyUqoDU|4sO0jF%*Te}CF7YrzC{rb^bcb-eu=6)+}&c5sY6_d+tN`*k9* zvGzxCxw9OO;g*AuLO716npu)2p<$mO#H$;uEXDSZ9_;5MnuW;vm2!-IXxmzMVr(C3 zY{YJnavgs=)hTL5d{1hPlp`;lVwsr9m4j>f7N@yjTXWk5JL~TYc97%ylKbH)2Pg8( zozNl`SF_PfrW|a`^0H@m1+jdzl3NwX9Yrt4MHW|4ceY0!C99y1l;)ngtT6}CFVB(s zS@QC>D|TW{u@hTM{f@#;EP296?N)~qGcoik#VrqrF*+|M&~~AV26;VK@-mHd!bvhI zx>N1^2Dps0BeqAf)!yrQIv)D?iDSM?E!JfFn$#iBfv}Qq(NNgb0Qt+hUd6pt)5@2X zur>1!OY)YnJ{I`%1wPNq`eJXm2hQ9}80WdS@e{6>%&;!s6E~dVhfkPp5dU8C!&6Sh z(yF`t`DAQ!*1iXtv6XV@dFus_Ue0a#q#RTFr-_)`-HrXHrSqN`4Jxmy1RrBE^p&$t zJ2$R_E#Dp&J4ECm=tpEAor|Sl!k*uohS2!wf6^Wv+gNAvLbSXD5?9euJw#j@N2rI0 z%i;+2e3JKx7m)(F;9B3wEk+-yPj1wXYJjUo5*XJ2_mWhC+$HE*hI**G&<%ZPAy0b{ zMPt9{tIpQkAL*>fx*cH;{^>?9P3_CA06%Wy=YRGM@Rd3FPij$ z3Q#GD#jN5d1C>>L!yvroGy-Y^c_#Ije|`WNm@y7pc%wz!#LD<;?AfcLWt3M%w>1wL zx^M>X+0&Oe3nT1Kc+1LpgLt%D7IJfM4LZ_)J!LyeW#L9rw~mhDrF2J$&BSkt;-z>; zY0kut`rYy!rKAwz5xIf+Df5wa*SbyU$Y*sAmX2r1jlK;b3$HG+Q_B+T=Yjjj%g+h? z$SY~HJ3tUJr^^>pY=-3CWAK{(gAc-{i$4Bw2blelZC#{KPoJh%1`; zy1&(aj5R17@uDR-MrIdx=rYE4LgG6~YIUXz8hAo?>;>g5O6GN|;P4qwW_M)X&DE;y zKKZ%xsLzT;zl^t4;j4q&2(}7r9L3YFDuAWL(Xf%OVmZZ;@BdmaCI5M@%fLbHMaU;( zEj@+`axvf{5lXg`jeint!bH{?<~KZmnD9V;Og&&mR%G=&Sdy|7 z6s_UngW4$R3sy+0z|EBfJXiaNNM<|uS(ZZQFQmw2=^vpTTWzObPQE^mUdY_A{X;=L zYWmO{UkOgr&TvibFk(!9WvmWU_!Zh;jb?OV%G_>+Cvq7ox)wEL*>k^SN_C$Yk&6N2 z<<3J?&Yt_@Dp31;doEM#MQ$H!&kd}O>DY6%)iE3Fxjk3M{2$)_13b#2djrO2W@{?h zBqSk}gb2wdyUB)XAoN~BZ=o0IZDB(ZmV{7kpdyNh4F#+yDi%;w6j4OMuGml%#R4db z1x5Vsb7r;#ec#`E{jdM`<;u=8_c>?!nfg34ld1yEYf}EX4P;(cS&BWYBQE_&!2u*O z8917YcFmQqU4{mRc4oM6V%hDk7;Ndn)e-;>1CpKCwb2DRkrJ02=ZZ1z++9I&cY}fR zz(RhJ%Zl#AxD1(^fa{2i1;YW#^J&Ng+x8gv@F z)QR)7RQhz2usjC1){Hn$0~HuiL!Lgv$902oxY_4vGT2cJeBZ%YVT5c)IvUOlTtoX3k{9+o!j zg|QAa61@;Md_n+;wUoVadO0Q}AUOuoknim{N7i`0_yFe)9|&DPPC7#kn-_j6Y#qQF zus-_6UVs5#U3YJ|$J7jqJUlhyXJ!?&j3GZWtEL;s&&;YB2J$nrYNmnw%&eMaAU`v! zW*f-Q%qqWu{LHMHV<0~>tL7TW&&;ZTf&9#@sxXkBnN{-)<=>SJRE>$n!5uEk?N^A8-VT~j!U+rVaM-4lGBkRwN5LpWtorWpAUY}r<0k> zUF?2n0#sio8T&LbU9#&~(UmDU5=a9}jFE7-aZX&Udj(>d;zVl|K+A^+4F*vkWk8#s z`_gfR#G%B~4Gi&aKRIxfw4=^|*2q$7n`F3ALvWX?tG0VNM1*@5r5}j00}c}4KnES; z9s-if;T}p6KpA*wmz;kX$>AatDj=?Rd;vpaR|Z!-zG@-E?yBS83V$kxK*$~>)8qnO z{E+I3K|hLva_tr6@XS9PMy~Oh5yBNWBy7AO~afzx&E6=-c@ z-53&h_&$cVZmggoJuy`S5hx!Dj1$mt62N67I|3m!t|pFqFkGymWw4O1n2u{-r`3#@ zQJFCaMj!r?f}1WQiKpv8({R!rwL6^n7*`!Wb^{GY3a(Q2rWmIGE=bVERXS{mVn4lR z>~W(qw6ua!9k-@4QXSICAeJZD!Hk*_W7^D`p3t2$@orpOKxN-4k;gN@WvD*A@&*v~!)G zbVZ|`FA{I&M)_fw9$kJDLkVqMO&l(9%HkYX4m@5I?J@Ry3RePdGJ-ESm_FKvLr zL30&SB2O!po_kRwOeHgQZ}`7dHY{Pd3XleB*DuF&Gm?Y8mexWh(OSr2G%Y>jg~jO} zf(nIx9_vPQ-yIKUl&7uLKL)Cih9hjyEPCUuf!k$hs%WJ%Kaw43>DYq*8MSH-ryG+7 zOjRBJvD74l7gVTaYq}2Ouo&?Y;(&^TH6!j7!gqu?ZE5e|yOn5luR=D^prDrG z_M02Ngp!r;0hDp1vDCaLDP^@KT8$7bd-GMq6yJ*R;5UYLpfB)<#_n=F_`Kz0Ych@w zFp}dNs+)e38wDK*rIP3?xhXITu zEw1D(imFTtv4(RulZ1hYr!VBw_^#Uoviw4Cc8~_C6N=?U4^FYNa7NfTAGkbXjZbl& z#fjD!b4#u>y*%D&7&w>-$u7Jb$Dn#Tg@Z_Ogu}oNN8}YJjrLfF4xY2QoN=DqL|n)m zx_Xz-1a;XX{$iM3_>dmj(z*szSZqPMD^Rxf*r*fU6Jp|!XH$@yR1^;PRLWP2g9}fe z(T*s%r-6!1iV09fJ~^f-w&HW`q21w)=_qXOe#%kD?Ljhe1qeU=E0Ft$HYz%4DJf;F z(~T8O4^9fM??kg)5w1~aK5=?bh>3mUs0g&iwia=xzrX^FCaY=a7I>Fz)5tbI4z}rJ zdxvZ^Rtu!W!$!ljz!hYhMYf;GMuW4!lL@fVxGc~!5jGl<1#Ti6jm83}$VLOP!1!9Q z(HJc77TIX{73i7-n;v=T`NBhp7m^;{cZ(bnNC$5jrtxSxezX>ewpRG?yA(fYH}AJ> zk7sxM`r+ppoz*nfGcFsQ0!B&Y*-2`Gdf|s53a@2*#swZkz99O6=lNM4c#og`1IPH;Bk&tPI|Ljqk}C;Z!q4JBD}K5I zUHO?E7{t$vz%+g)1y=AAr`+(10?3h_%yZ>bLTJt+8tYo#R+Sg`!>Wx;3`H6*S`?q zq?e=Ns-#T5oYJ%i21aHScoj}?)g^(&B9z1;5?Ty?<7(o#@eawu$;_-;YBG?Y_$Gl9 zeTU9E=%ZEqg>>If>8>{E#*~T@j4k6xusujmP8OeNP>BINBEN)$^b$3lb5J?;Vk*7L ztDJr@bu*AXc<2np+{TK0qL@VT31@NC%qQ}@ zYBUUsL&@*!;Q~i`aNL}f%50VW3dNh_c9d*I zBy`5v5HTGj$b z{We8p>k_#a_{pABl~oQtE_+p3(`tB5?E&>~4^VrcH8-AGB4<_uc+tZHi+6ZeCnQqM zFBi`^{OCzPM3;ZaZTFq1DRFcd$Y!>r;mmTj;NTLpDPZx!9{G>63ejkfFhSDD4B@1= zq%w(v=EON@Si-}aMpObR6!ZSN@H{$$(BM01kq<*KlH=1-Y#AvrbVXx~$A;rG0(7-ET>fB zetR?GCOH~A@nT&njtiNTm8)MuTlj^mpWpuD9*6~KO3gcWbEOi$bWf5zh(tVakUHfI z9q2Z{F(xZ~HzY-YMscw*esrE#D)HDT5Pj4$(&OO1b~~qs7B6CB6K%M7jmM?;z`Fs7 zw*LBkADy(8m1qOD`=LX$Y_bp!BY-4u!$)~!6_|hsjGZWt{Fl;b8fQblK=IEeV9maV zG9L2QY;@bCqbPzHDgrEt@O2V(a4(M*#vzaRsvy?i7hzEsM+|M(a~8c!lpQS9IqW}}>I-x4K(f8FWZ-$oV!%&-{aL`H8DUg(Xv?dX+_z+b8 zQYuAscGP`XxL47l?t2je-y`ZwO-U^I9CcIaBV^rs(N1S2r1`7iG;LG7H5-Rmie`2j z4r?UeX~_=9k;_ypgF~}pBLX-gn*?a}4FZm^Kq8LD#$}WgL%xXG3+RP{P@L)&;o8hH zSoqXXd}G66S;-NNOy_F~Tu$zv`3!Borvt~=bcD`eT`AEv)>}*GWR@>{MJuh&g&fhb z*4mMiS1D2}mLY8kF*sK81p4O2{9@Su60A7GkFMY8jR>I%v_$e!Y!=*mU`wStz3`68Wg&_*@3LQ3Dw0J$-n;>0(9sM`v#hbdPoa^>6m(^x!)vRCCfTW z37QAYS-f~XrEpMWB5F{ge)sq_BKR=tI{QPnRmC~7Z$Tlq$aZ8Sb)u*v-X>(9B3lQt zZ3GW3RC`YkNA?|56X+C}?5$+ObvlmhH_4XZDX`UTX0v7`Hm&c;vU#nRdQDP2X}I~< zX3L7fZQF6StQ2m56FgJxHhtnNw>H-F1kX?#wJ9_oqow*D_N+QZ32tbN-Oeiuw1z|t zjz{49M%;OKD+-9RzP=A@nQ)1Js2*t_wH@T4O@uBTpmx49(z1io3>BgIHNB(cONAWq zB{Z&}hS{2Z6%x{-kv01SD5dL>PgLVMA6k-)*hLFUz68UXZKH-@9FZ~T5~Vj9X$-zZ zDW+h8FT_LYYa#VbV{JI(5s|ig>^6SVs`7FuVPQPJv*U=ktT=+z;$8tFAMF~)C+*wZ zx4~7MXQ|DN@c}FgZ0XHx7I;|?=V?Vdj?Mnrj>X<*kTV`f)*S7Jr~5yo`dN)s9184W zFZxHA36Y3l_4Uu-sy;o#1hs(KT-QaX&~cnS)k< zsAHk)+tU22;2;-Y_C&t5836wt)jEk?m9&mcFKuwLP}{Uvlw%`-vwH8R>IP46+9NB9 z&1H@E)JJtK$9G5R32M8XTm`&| zPJP8ya24eqbdBPwJS~(nl_o3buXT4A6D1bXSLI2x;8Uk75HwHc^s<%3{}lW;ak(Dt z1?r!ppcguao(O_R$nJ-bAav4ov9`9tCd5vPU%j<>BOqh5GWep{AB~#FWyLf8oG`ka zcz!>IV`r`IEJQB_2c=SDiYqAC`xHk>eRwEh(C;Ez{2J}8lvunDb&r8gn_HXzKv}1y z;1KLt)XbsNaK-Ar54QNI-VcY}`LEK2LYK$mCfmJm$tPP|_-#WQTo1snFlBVFK6FbaQ9(B6x$FY-0KBGykI$iv(8!Ix${I_aL?yRK*6@z_qrm zg{|sZ7>au`_jRydJMHb11g!T`anPFs3mX>ihMk-znlzyJQ*6{|-H zP-|Et!-V=YOu*_s-_o3itU_i5;fWqCFr@K9&6v``q#P^_1X2={6ZJ@dmeFgu;sGN= z0d)46@RIvb+;b(Gg#s|6foxZ{H^CJjH5@>gP!viC(#7L|hOUMv)^Fh&1sh-S7zIiz z>-Yt$(b#$Gz_JDwUj2@DHS*tx?9l6IykI^Z2O4XM81rNaE(HHgkR^4nr(lJgdrew6 zq=jwUr}Ggeh}5V0@kRijK!gt4cEt`t-NxHiR7Ndu_ov+{hDVcfMW8l~E;hhpU_|*} zhb}Z!2{Z`}LsHP>yKg2JxEaa4ku0uS$$44cdin&pyg+Xfy%O0XlJn^P<)2~gMfVF} zO*$_ytOhszBJQLb+>DF3^J{SHT*O^fgPVB~_of=$x)*V`MRDENAi_K*OR1H)b`E_V zk%Y2N;;RqR^{ttKN6Eo0NWFg(42-R6CA+iY%%^4Uz*7_zGTR644b*L;w> z;Z+{IMhq=Z;AF~F-3DK@wu9G_v=l+kB1;jgE?$ZtpQ^C`A$lnS@Bcze5yXis+0eMA zIpXyM;zd^^*2WM%8E55nMP0&zX_~ux$kJ0=vYNWf$Y$3IY}7B(8tRj9j}r>lqI&Z; zu6D;J`pAoOTYsq#T4PMoeVhH-X-z*0tuJ1!f-B*YrBx6bXyrTOlzT6NqF%(T?>Yot z1B<5+mg4h%D^by`#)?(qkQI6svXWn_;#O#BB{e;vdr)xcFm%y>8|ZHI;V2EhxdCx( zi&-1~cr@QQ#afBZ9Q`=iD2Cd$B5YgEw#sFAlwg`AEV>PeQ&Cj}b9u~~sA=U|P)Tl- z!@m=KgbEUH??DVI$x}^r=sQ}T1vOr25fvmh?jIpZ3Jr96xH}Mm;)u-w7_rk-rgF80 z_`HkZ6$H~OtLdSA)5YT6Np2}sxY!3)Ue~1Z#Na}IFN$|Tv<~cTWpTd2qG*U@4ZHz|X?#OQYyibE zCoVm{1lw-W#yiu8Z%T*>oPvP=4r*bnF#$*I5J2DL?I1KI(|;%U%;d%I@)R5*n&?VE zvz_R|kI$WT)f&!+&SvF=CZus#2LGO#q(_i0o^0AmwnDX3kTTB^sE@$v`ZvRCUBBs) zJ1kqTNR4wy5vO9=j_geF^L0h-D5^v$8dGv3vmbcn%!yJom+b3Jo%(-MG`nOm zI4W=#%7^cIXq^LGGKq=zhl#a`*q@1yhKYiRrA*u%CSrUeN^+U_LYPSFwIwblz8WUJ zO2qRpRN$>JaSsu(egfi$VdA|+e4B~K!o(YhNNubNd>baB9T6q>G4V`@SQsqXz?5@g z%3OY9uL3g7q`GjdXTA!=gkd>Wqyw%n?CBY%0(HW$lcz_ZAy6!yv-sH|u$!MH0b4rJ zivt7r=?>h^&+Ndr{LBcn$RK`FU>QI4_JQ!*D!p!47qNYs+eLaUjVYg9zf7|8+7Tw%HOXbq8tvbLN=Va3EDGT~dt8$7G(xVNk5L6i+UXi$)fQcjpWaqx zjV?-1p@uholeu^$#o-7Xg7>U~y!NT@Y>2~Z+E%#2rmM+lOK{I{;8Tb>c;U?>ExLV3 zaoVyb>V}z?HH+x7k^1Hg=lH-^)GVlS?_u4{U>?v>Q*T=OIt9yi?N?w3PqN6I(bz)H5NnE?b}>{D=<7 z>2&W#;Z@BG4Hhe4xnRfj#`cPPsT8V-;~tMl`tJj#WAE`M!IeVot&Q%t7pTAV9#nV3 ze2E5@Z{mf%US|MSU0Pw&FeRs)KGV(g-w$tSC5LbC!kO{Ri?b;#Ke%iTmrZxnxCb=6 zu45CY5DqGS=nexFXo7U;{s;Ydd|E=ubC7VxY)Y|WL>_1r=BB$JpiF*{j0s2tP9dk4 zB6QjLT)MYo6R&Q%UqT+F#Msd_Jp@VCM&;?{iV5_FPj&qd6Or`H3(_QzP#QBLVQdO1 zciN@^-lol=6&X|z@{T(*Ph#Zqbc}hgu=CKCu6v|$3>JR$x|8$TY3*BK1pw>m$l-(n zq=@DW7*GqO4r6mwxe$dREQO9Lyey+Vl-J>b?+A&^g-r`*SP&a49BF|{L=dAebVv5P z9qECqqG3kh=4eVWeCX-2s4(#q_sw2Y`ki|b14K3`;?QtI|W_Bteu?DzuXNEJ2Yer5q$?HUS#L5WsiRcuUfm2T*4?i(d(6nyutBPy%m(5MNRc z9;v|hQOp;K`8PBFW|#DKO}~}84I_Kno8zh$GIYBAc2y0wW=UJBX&>5%0k;BRi|m{|KCi-Z3bV zjrb%%CsbCdehEHJzXT@_qF>}wTT2z@1=1f2H>ZX5_0@(v_w^=N0!t}E51=pOw?yf9 zxzG^vjAsxf|DzPBHE;_)Om(u!y+DG;ule*IkVH~&Nhm*YD^s01?$z)s8FwIDjVCTd z(hl{=g zfd5H6(1zerYD_R`wS|{&(bZ$LShy^5F*WTa+>7|_R9IL`!cyJ)O!$Hin8T;4)HwA*Z8sa_N7?JVId-x{!T(Z78+V_^i4&0^XSOFywB9br3a1J zmqGDY(X5g<$f&^cq}lhPAoyDF)`!WpZha2bHx!l9mtDp0E?t(&L63S3f?*3#1k?M+QuuE&kf zsY>y?6u)Np`SA1Lmxtda_+{Z&7r%5B=uB?EM{UY1WJJ4}8VXjKC>=)(QN= z&&j<2j&&ywx5sd%fpSlR_FHKA3wLjZ&=f-L!UvsH zz6ei{m>@kX_7R9pFNjSmh*bpxEt5v4o$vTbyT}jY)y2{5bWvP5>KBLKD>@tn9`x0rroc32Gbep`TzSFOx@wv%kayb!PGtM6%MsINxifKWq~u8Mn^N~ z`x`zRz=5boox!9x{r?Ount zoZjImkNFhIVT1TeL=={1WnN1X-|@+3VsJ6 zcO#OpJ3?V3=VHiJBUF8sOK7kDl~)PeizH;uMd=uYbR77%bYOyCO9jz6nRFx^ZK;AM z0X0ztPr;rnQ)@&d;i!w^5$y_}%!K05qo=?*eo=9NkEh{_h41D=Wi{p9O9g)bnYYAP z+=nRp3(#c#2pS4EEJid89Eb*weqxVesTB@OfQ%*MkpG=j@N8I!DjJQS8YBIf5W6Ze zun4GxPFXZPT!$;q zfvVhxQ5uzh!ivnOi~hG7c>*g`m-w;vtn+$%ej?}!zs#r~VWN97#8C$yMr8lQ6VLWW zNX#erZaB5liWMe2Z2IBUj~M;H0Frp|`jJ47tP32m0)02BPpBrmbqHaX3e{HIslMX- zc-jYH;u+`Yia8la(2A#>A{FCF^i-9g?ZzippZ0;cs?A>G9c>VA5|Hs1*u)%5NRP@t$h&lwN{{{YV;USAM@SkW3URYButzsxQVG#!(p@ zrTO2a)NsCE?34}%iiP8n6hspBo9rq`q3{7{wh}aNAX6^!yXTMu6W)a9{E-rWqh{l; z47IEl)1NzqDCC4c<#NA;=tS~9D%Y`W5nD;BKwNt2TxYGwI>5-2nbJ=SwKPl)?H7y4LUG?(ekN zqd0S`A^GtMjX68kL*kHA^fw0zMn9*9B4D&lD+|7c)@7f8%o7C>CPjppoHotGScfzz zG};P5_)BBlk$4^s>wVURW*I6-)t4(WKEqDqoWPzYM_NTTVv5;u@GtNysUTp->W;ON z6d!iXAgz^D5U^XwPL;q;hl#r3GSY<5oO=Z{M%kCyv4Lu>W)TBBol#p8|aw3A!_S%_uEi`ig!p4Qo+PvjeG}(pA(>ptFVhoYaCli5mwI1GcdO_5R9D6(vNP?2T8!bLWNi>wC)79=i;j2;(i2SP>0PT|{U z@U0R#Kn*87DzSP>oeh(M3^+>qRUj>0Ia1?3eF@p3qpDR3XZJZ+r|5`ItyJAqIy8fW zaoH;ylCR+?+M!i5If~oZBjVzl*d_FgQY#%tyH=|#@*zlO+|eFdfhhk-fjA$H0y#-F z*kNiV(UBC+*HoP_&LS+QX+0|VAB4l|Bs$_FF4CJE{4nYj3-P;HZ>|FEdi;Elxma&# zZDS1R@P@w6$wBP&#u$6(m=!E{;>vjD@b|&ykQF1l1GNih{sk`~hrOwV)C^6rr@F_Z zDd1(<@Svz2#)?=qx0;WfYqnJw8i}`9hZ{r0v{jd@3N)Yg%6yPiP@8$MS5kCC1UC^V z8l(jzbHUyu)aJO;P_X{bjj|piW$nQ23k3pY_0S_hEy=|bx@vKwdV0iBzX}ouib{_Q zwJSo3%FdvuUyz~}kmDekaYq|Molw@yPu1~dPqC`2OBb8>{)HVWPdz?&h(~vch8LF= zwdL?xyv_OJMSv!MMo{&|s-IPVg;t4%0x>FLlkDzsD1@5H@65?3Kal+9^g!|p@#N&s z=6bLPWubz^L=Mm+DhJqQCrklDba&uVsnNHGUFso)8Yc> z(EV1dgCZ@CJL=XM$lj;wgrsUeId;g$^&vehoL-@52qo+T34zVSfjC4c-axV(l5&h=S9-Gx!C9-frimd&04q zh87HICYxr#=g9>{AbtxX4*RKL|G&9WbE?@Yi1$iX|7_$8re!FSLhOIw;|DM|??H*^ zR3j1OJHo5zO{}gpSWPYbaW22@fds~z_G+}mQ8|DqJGv(NEIX1b(^aq+g!G)yiv!FX zhiB~J+Kf3$@30J3f;vKH^h(gbuJg!PIL)J=~EnEjkNM zB4lr|p;Mq1J{Qr?^bA9%P%S|Yo9HHqSSuP$K|dLuXR?=-l(jf5nmnr{19oy+Nrosm zjCY0ESUMkux~xS5UBj5g-Q~n0>CmD=Sb4@+^x21}IP3aDgkKCoLnk06)dN6?Xd{+? zUVw%cU=0&zd12rjiv=XAb2PS2M_#>)x@z%12L{%cYHW&)c+yGniBftggNhr3p$Nv} zE<_vo-wDMROZPFycc~7iai#7 z*u#5yXwYTjZyH0KbC|>O3^$JV+r&H4o1se?2UmQmD+-EV<~FoDc~g!vxE6)3H{=Az zla$$z6KBt!garY7CqujOY>s*(-i}M7-AZ`tSTY;kUquy?loBJNJTFYLyPPh)BUdSfGv z($QN7Tv*EZG79H4q_@x$!Mxq4E^uhK4?C-|+XowzkZJTv%!SWlDYiKK7bbb4bE+F6 zZ>(sZ?e48qbud4|_p?EIcrz6r&ESf;U|))CsCWmlaL++e)5bIUgiU$6h0{%+t0fwj zG43Cb0HkkH#5ezsOo|i|Wex4XRh~3_^F^71ZITX5qwzIb>y?;ByKhB=(#!QJ5|LW< zoz^JDab)FchAp~5D48LVwOO*3GR2FqsPaZB9qHU26fpW=qjQ)MMs$ug5|rEtz6wr_ zrkk~lRm{;Y{#_TR!BtonNrC@W7pId{NEe~AB{UVQSWY_U2nCIgrbFt1u`jy_sR!^3 z@*$)iAskWT^GHXiLa~lO7v837O*(?l z+FHL09f=g_10l7bptV}m4ygr`NG<-M255UMVjL<7zEUxIFbD3#Z4dMSO0I%)L8MDG zuEZ#hU*PcTTmA94*_@35YF{*}aeUa6-j<26X8;ZzI$dZWuRu0bSAZ0WcT*lLS@_eI z2z8(e&WD(FQ(z=II17K8S^(mLe-NWyJ)S`gqgRjg3M)1>YtlNii-Pv>}5;BMiWkK)a;-ke*%eYz};+8E~hW{t3YJhvr`n zyYp^OOap*FWHX8^g?=N9VR$aWa~*_!$B)+T5mP>Q2>auCu@~jXr(Z0_^ALXUUd+R< z8-CA*alKE`6-IG$pl*z=M(fxrkyExn9 z*WuWB&*q!m$$pB>RZO|ijVQNv8w$5g*`GVue5)JzwlUxwY!}sRZwH_4;!BvZqG!Uy z;zTj6{QWM8;<*lFe$koCyF6qjjUw}hu4H<^wTqX#I0q++Cs^{;;Y6t&Pd@bbk$rz3 z@_BhYh4nkr$H$P{DV1bSok22R3?Uyp*+jWHk<3rh$gDS?%;6Oz^WnIblM}@swTbfL z2%;2o{`5~EpI`4mJ{%iK+=mN@+ksQzX348L^!vKEnUN?~j3=3#ZX|;}%y6|Rk0{Aw z$!s}*%-aT%>0V0a>j*tj9I7UBW&<({i^=@BkbKx$n|ye0C{boDBXfOyGP^AxvkT|j zr<{|c*zH#VqMv6|W|BRR^WmN>vj55ET+WB_Y$|pY!?|%DA);L7lKCFTwmp}Ei+xC_ zNAY=(a05YIE-O?Y?_bBKByL;iDfz0dSk6oPaM6PzRO!pBK;&TWQ z`q{1>bag;KqAcgKeq{p1@B+ttHkWTYm)AGb-|sR^Y@A3j{0?pt#bnlpM|+ZdyG~>_ z=Mrqth3u8AlaEX$`(eZ|QCvHY%mgllgQ;XM<$T-Ii|luE4u8m66oVW~q&Y9lLq%kE zX;0><{$vi}vOC8zf3exSF40$yBJ&uRcpr}cLv4w27n?5Df!(m%#gWdGZ}nOJ57ta| z8PhpGy+g=V`)V?WFD0`lo0qcr7xE!le3ksv=$&%+ICK-(F-3LiLKR7-u*{-iWS@l^ zhx>$?63cqk4O(Fr2M0FurioYQPaT~mPPdyn+Agjh)68oZpR+#vQbjGpp>it6a?~K? z%oy_L{r2QGW3Y2@rkD}m@p8lpF?mMm5;>DR5&JQoVjr9%sN7$vA|G}I$$lSdw_R-F zI=%+35=ACzccR$K_B)r8y@0j1YBt%ga~E z%q+5Z4wC(QE|HUL-dsRFjAb8&xX2!0joQYPHC!&1K15&4deRVfyXc3uC{gt5K=fWL zIh4&&tlN(!QYa5{DK~9SGAoc9&@pZeZtF+(sT}h=vdI1!>&$u9z{JKx*~Tr&y{vBy zxRv;xTd;U;4L%u5G6Cp7qIiJyXCb?NPx}eIwTm;TJBi|}B_wmE37N;)&tKRa&$<+o zOY~i=!)MsOliR2TT!J0gx0hM-|KeP7ar{5vw0zHT`-@ZEbrJcqnPnU-(~avw-&yZ> zX&^42O))vmdb=N{UBvYz$^%|<^*Btsz%4VNI5=lMV$H8Nm$=V#B=Z$c=>()JQGA3P zh9t-B2@ZWFbJw#ukm;wn)IMf?-oqh2(}`P~1!V5-(9D}Co}5VjyvT8$#WC5PLzJ!T z&tAklQOxCB^>B-EoWqLGAi9^$-?*NRPaw*Cj``c%o;2b3zrkg68T+<0NHWiKCUX;) z_!CUG`iXLG5aqz_ern(5p*>6#9k{JN$>rj7lT3e3drvOA1lEU^oWph5^fPxMVrUmD zyQibAyPxCVuaMln$7$?ZkL=UfOy=}n!ls|O)odQ*9H?gc;7dq;5=_*0)}@EJkLb%W zU(BUbm+dXsY{wck1a-tNa{QFfW4TPP^^rd-kRrRdf?NAX*tZFYp)5JtJw_i>z(#_l=J&qm$V~>u% z4o45mxv_gFQ7#!nx!Ph1#bk7^_q*hXV88UqvEq|i)XroMp}uJyVw)>Q_TMriSH#TU zg8qFXw*$_p6cgJhYUP@68@0JT`FXG%mDjcK$1X-AMRqZ3XuC4B675J{ppB)mS9La- zG}c^(1?i`jm9_24NMdL{)VvR&COCtLc&Xh8U5Z^G6y7?H zOPM&$Xw?AXm5E;%4PkeEg+)?ac6On-3>NV~N#cbWBv}qbp&yw}Xt?kJC5d<0mkFYS zi~-6oD{H$-O%Pp}_X_hSi@}VR4MJv@ofR560lKF-0ZViKblrx9;~xB^Is z@0qtiT*Wav+nHjvP^@Lik;Npr7>MGzWg?+!@d!@>u0k1g#T`X210{(c;_yA4`am3H zl$AidBR~qgGZ=Jt6(5T4 z{t7}fF+A^zyv5BYKxdivgNS4Fxs`Zl1n#Q=uRZ(nGhRdzdMuWBzlaP*2k@0vSMjUJ zWb|t)q2EM3Mq99m*H!#38Zi1RkNE97eCX+P)7s~Ek`xwKHOQF!exF4UqtyWAi*$~}Ux|0@ce$1F3# z`>=+)986gwJnTF(h;oTWXk7(`;1Y$GcC@%e%Th9bEg^FO+Y{M5)0!x2SY~{y9dR!4 zZJUveT;f8ju`sV_Mcm2E9mkTxniIv{54DQ=s$v{6HmcIgV3^;=&7^Tv{EV17_)u{0DR74N~6;`5U08%fc;4a@$b4rBnZM16 zZ`@McI%h~$6VV=ejvjX&nOk8tldmS#Zrn`n5t%S2rI2|Kn-?x2d+Q7`uV_r>jD}=x zXA@@y!TyjW%4jE@?Ph|KOuWV-5;d732~rjz{z=B{GNp|u);vV`Nju@-R$viuAAWM5b>ALfC&WPhgt znOCx(MLA^mv*}@3G9+A_!- zlBwMuf&E}I`S8a?N|(;VGZVkK$j=Cu;_y9O?Mv1^{1_$qN7x_AF|^ommRu`S0i_Cd(MER5L@wJJvLp98`bBSE6T?LSI zS~Con{P%%!AhtQo-Amf$5~Vxrzgo*6bK?XmeKOO;Gs8wT#w8w?56#ULXBU!rU2rI5 zendM)u13Pu`q*(WAIKuTZ6-)3@2av`LOJ$y6Vec(koVjG*km+gqt^%r%yNk>b6jQ4T3pz}fB4wT?9gP!y41R875 z@fka?ae1;q=Vt5#nr%?Kfje?7V!lB`2JQr^G$?!4j$Er)Y0#y!b^@(2=+v|wxi+!h zAm{X*K(`un2t2#E+n`UU?F4$jpc~rl$i;U|25oJ(6KJR?dXlENI9=x$KXlGC{^(a)f6`8#rx#Z-;N-(7zO+D@N) zigluFf+a6qT$Zm<%AC@iy5dSk8-!=MD=$m9@lJ^(S2xSdYbeGsS}&dpHq6TrYYH{b zUS5=!D{k{?blLcJd2TU>=ltu$PtD5mykcWh;%yKQG#j2*EKVA<7pSQyqgOcK%>imI z9ychi`S84!VtaGV`zSCWuS9HYL1>+5IDAH4Yw?Xi@6MQ?*G6=?RP%h(ugL2p<}unJ zs>`m;>mm*qo`2e{d0oZH5-mBj{QkV2Vhz9N+8{;`Id1DMQd?`@-~LDQ%EWeq9?990 z*H4^mt9iXA9k&e@8SONBukf|JA)sw0gxdNV;EU3; z{RpiSei+Q||R*rsj#61NXXb6y9=nC(g}0?7l_p)X2F7t;X%*X@hp5)wo@}WF*sif8@SH z#1Gf*(t00r-zi2Jw5s@&`)+ZcK}mi7bZ-@pkJ6H3MmNlVP|O>xQHKh5{zGD~K@S%< z&EFy7#}IFw=upuy|535hAa6y#{3k>PEfatj7(60>m&hJRXoK(tJLc~e;|%IQcT)bd z;vq)sI0v5>PcT|9R#;YLJumhd^hC_6tQW*v1`T(u%GxJBXS6}=AN!cxFD@T1@V7y< z={i6E74hz68r`_OI{%<}WRgbL_P-|okQg zZ!vDUMhWdlQ z69-5AlAj=Vh0*!^METWfE%~MI{ydkgcZEj%me~qY$NGV98iCh~EE8lVknCQlkvWgA=2LVDLy2&4RVj43Dm}*$$jPm zbus7-|3aWLgZ3~QY|wW9vG^8pltG)Ow-znrl^SKdIdM^)7P9zy4i9K~K}$L621eqy z>eZ^1EL*S9ioQ1#w36={^hVE(1*LNEjl|QM+*VFywA$$%IMmiw&M~OPz^w&s;>sWi5XNXuClti&~40 z@^OQHEy@LY)}W_*w-%k`euG}@oeOl>pl#KyMQ8bmMqLtmQVKC5AV#|9)8}R~p{z{oT~u7$CneyuvPT*aph)46kFCKMDrQpAD~~vb7i_|1`W+mAOFHTXZ@6iS!MY z@r+hG;|J8nyN23^H@5ThYKW|Fcr!ci2Ff+O{7KDhLuHXcqZzd{sC4Rga;R))+;yG$ zcfnBE-JqUBZJuGWpFx9$#sZZav|-6=RW8RdS_XYd@|4ThH-)sPj%TF2{?-s`=ou}4 zXS7Z+PD?rfEmdMEMq@WnzeJSL$T>4WkX> z?!kRLQ{;WQs&7yTQoWev_Kv=X#e<|JeSKQTQzUcyt_P$ zW%@RaeyiT;St7H;Xt$?QmfaKLJ@2WKGw%(dmpx0R_dbnUw13C5Ozyp(&^l33c-*s6 z-u8gz&FysDbA{~kphhc~|LnP1y0>fO9QvnctvtYJo!C`hdasqK4-t=0qIaEaZcufT zH1CbF+@Ldo2Hu~)=)F}=HApq>?Y&LDs}YZa zZkNjrlH_V`cW;;L7%g+I2)ruqkhg1|^T_gom^!XCDDgVWUGl8qEl+%3-X$|%=`ZLw7z2)P@!Uv85>8k9S%w%8`?y-qRvI;IJD_sA{=bq4PqIoqH`!}rU3 zWzZmMY44R+GJ08jRkm8)E8k_bOtkk;^xi9fJWPIWaN3s7@ZKlS8+2~j4DSOn`3>T| zEJn9mE4Is0gPxvoKTuCbU(2UwRCphf%NVW38{S3Uhvij_sD@Vo-D!A-11r5dZj{lOG$j z47|tWagD^Ieedw@lx^PR^Z{-6J|VN;BBXofr{uegHi)FYV=Yg~vbVM5&Aw;6yW~km z>%`UN&sq1#l6OM9?cQhQ#4tMKeO@j%X!(Rsyf4V>4XQupxr`U(W`pirl3(XVxtGy8 zG2HWocb{~>tNnbbEWgf6ayz4SVrtiKz5C_wj8;2yX11ocv+t4QYG<>VKYCx5F$V2M z@AQgv8T4}XkKR{gra|$(*5ZK7F{qC3NACgY(FmndTf8bq8Q!hs_sWBEqCq>#fA=1g za~NrTc}>Q@PoZmlc}+gai1bAkzAj%h=-e_{cv!y9NbAd+vgiYH_p%^;c~kaaMEVj_ z_?FzoXoE-?SF7-CIqC>WZV*rP*^&E>Tp30U3g3~(8Lboh$~}eeOYesyi9VqG`^+P9 zsz%&~9FZ#-Z4hTCG%Y+LH*21A!KhY+AIirK@13$PK)-*a!@H+T|H6;toR2liXfmqs zWBC%Jb>dvpS%sg-MxSV2!+!G$Kb313trM5_TTysS9x>>K8CMm4A-$iH8U$M?5(R-^m3A z9rBE|d?%|6y3w7J@V#7R(02D&%lC4vL7#YY5>Cn+4La=|YdI-zGiV{=a!PJ7Xf5J$ zNzG}dxf zrW&*tY5zsmHRv$X{)=p+kr*}Ofx=(q`7g;Ata%K1yzrc?KCaQS%U>xxFZ+L`(LG(i zER?Fp*BX5^^{+y^vYueXS{JJ}Gs5c6DUVRX@{Zc&`d_%s;%}K zG^?tAQJVVIpcgBL6=f*v_c{$PR!%O;RG(_(Y`C~Er@lIFP_xCSbL*>OMd5BSJX(o$!LSvFnD88 zWA*qCnzzDpcadAI{87iO)r1{IK6QlAI?-yvo}w1&rJuEA!{)CRwNbmyYV_!!cZ)hG z=daq`_^L08x~Ou422T67sGHhlQ1-wdin^=LziG*DW}YwVsmjC1>g%O`Kd*TYA(zV3 z^?z{)tUZ0zn;MD6ft;egIHHAktDWCthS^WG(TK-H{nX~a*_~+Kv6ipD`km1-@m4!2 z2C8x;!CWW0dGhNFR7p0CnvBf!4N@frO_`G88>S98G;it9BHu_=?9}L|sh9f3DsPNN z11ELxjZ+5oHNTEuy=c%E zwzT`pR^;ztjtLmj{$v66}^aa#VgMJ-ym9IigH0bKy8+|yloe_;OKNJhqaz@AG zf{9baoDjRu|(}N=(<67 zh$?l^pi{kCi)wYqpmg-w)#^=+pqN?fC=4VuvFGqF}(p;64y zfv<^c)kcFR4EjY}r|vY8O?sclPLN#&9bf#5xIsN{(EUi!jp}7aRDN50H>pn<={ds9 z>RZio&T3i^bF(^Q(4wY8yf>>q3|c++LElEDaK;DKv2}AF0g7j&M>@Btaz=WjbBlVA z5sh?q`!=bk3_7=Lx9>J}$hf1C@f|7w2Q!35I(MjgjA*2@&v&O<$cRS92Ys8>Qbsf~ zeiP^}!#f=Kz;~D0$w-e)x2Wee50X-BQ3n{U5I4sh^KDV54LXu~%(qpYGpJVP3Ewt# z-k_JQr+xRR1~@uHh!tW{>}lV<%5BiYuG7BzR8xb3>a6d6Rcg?jn6thIR2PGOvK;e0 zsLB{o&P%afl^b4`^Pq3LO0C2BX?WR;R)`-Q#M@YdcbDM}b({uoM-=ZNwaf6Xv>)?5 zq+T;XQb2qoVq;3TOz?5+)%r#)p-`sP)1tvc{R@P7C`cO zwdRtDvFRCkZBa$zws~PF~w@+n| zy}4_@I&Dz>xU;^O)gK0(aGmwNqU?DT-U?9}*W7hL#T#TzKI?l`Wf)Yu_F3OS)ySaT zN~OG}3K;3QysnxVUaF(H>vh$Sk&g8tH8M)_keX;Dlda8Nht%{a$-`sf8Jz;nisBv$qH=}rOsSgeBY4F}sCm6jH zLn}OQtFw%BEBvqy@twV(_#;)sNayv( zs&$m)$EvfDygjx_@yDvSL62u0^L?U58PrZ3^L?t8M!7qxt~9(Zj$^)~>K21m)jj6> zOg&)GBD8~_t7i;qR_~ban0noyd9{!EzEGbU^ke;FzAx2jgFZ<=<~y$bHmH~LnC~l< zfOBX_2RmmT^L?%A8ZJnC~0amXXehZ&f$L%SAlDRf8GnocK(UNq(=^7|ByHZHm8FHyTtL*QWTSy3L>gv2BV^sjX4&POC=@ z?<$8)oL27@MoR1lb(GNxaZU2Jq6qIt)ripwaTnbEs80GKyff+-MrdE)?u^PV zj_`g`UPdd#Y`FVLZDgd={@$Vgu2>Qwx*dOC`CR_!x9<>+2~Rvj_u{kZPM zzp7JV$q%s?_Af@qVyI0%r(&_M7VUgY*M;4HQZ65R$IhuA07O5YX#98^kY|* zME6W1dj1@ulRwJHY)FVudW|8;p7SC;9QP|oo<`^r-v*K^3X9@EG88Mx_7HvhXrhxp z(e(e#u47C7hhiRWrw}b7dp!A1bQ{OV&gmujq$DM#REOM#e9*2)-WryqRLq7yS|%h( zG8WFm5JkJKZcA>{yHJRc_=IG%E1esZ^Zyt9NOe+@MO-~6Dak6XYZ8&yB|Fo$%sEgSp)kmA({8eDV`TtXS5Hb$rgN7N?iKpMv2#o zF1GJ)M-*Ml?ixt;P;OA_#+Om7653JfqPfxK{(7^B59C&IDJ#|)i&)5|MsvBj zf%4#k}4p*UQ{c|tArKf?{l zKhT$QQ?uEv0uq8q}DO5^T+*^ zmO;qpP+Ll^gau!P6J={5rAt$?P?{Ji^}U#)TQEwIMRbSm;p=El@8(G)`BWms_C~IK zn>jb4DQRc}YswHMGz!`_H{$jTr(P$(N0;gPIVAHU z_mA5CGTZ-|-r}h7>i?dukGa=Ah89wZZ5}GMQ?Q3L`Nxj`4{L7%9!GJl4OjOp(rB?P z?>my^1>kg%d%|Ai_9X8rLjGDv{*AT78oOeAt8ZF%odywBE^Q_KmZ3w zFeJGW#ut>ZIY7QRAwdex;tRRC4%x_d|M#5k8Lj5#f4ILs;jDNIp@@=sutx= zZ7PXBa-9#&H+w7b2PHhoF4C%NS&|QxqJ~_Glv4cdR}`kBHK1n5qZsn0^(1tV^q%AltF__vpej`HJnz;jR@PHCmfkqivz$^hY@0zXme$A%mg6%{+!NBNQO%@hE);*P)Qq0< zy&B5#D|k~Q4r&jwMHU8s@L4)(G`vpI978r5zAPxz z_$M)K;KemE#UCCzIt9uL=ffC{TxMm;Fjg)`Oe5t%S`s%Zl-GUSdxwsZe_f^-vie4a zw4srXVZ$uv@$v`O7yk+x^*r?1C%K0Im1m7ORqLrvMq@0JuK#o^T*9+91F6?x6b#|~ z>p6aM%&%WfDG!bF|EE^z*=}yrp(z-&QA__fw2{KEc)W~GzuGgH`)K+J)_wGpEFR0W zQJS_?=i`g-`QnJ2FTRYc8`s0Qj>{87Tucz(#cwaJ)42L@HR5^=*90*cA6+ZPj-O&N z9p{gWam~T~TwDuqU50BJ?(F!s_c&aA(sFz$X}LI!-(JZn&PXm?9$a2rD!$XP75uFT z-zpS*e~s3J3V}9!Go0vRU=F^nf!&=V$fz=wF>Yk6VT>}y8TSHh_^YtAH>s2HFc2*T zoPqCJyxCABjsPW;@685$h|}_B!(!nA7KtDb_Lb#Mc)f17&}zse@f-NcZnqqPSL}An zZ{S6nCGKn3$|*<1Vat73&p5(Qbt67l>6eQdtia+MBA}}UrBf{$vC>*CD4lBY9#&SX#j{vZtrm}CCG{$f zf0cL?E2me9o3UbgmADjt9_A`RDL3$2*Vvn`&5@$^VBL5rZm-=xMd}kjyRsWd`x4JE z_K7FAJ}$M}-^RIwK0!ORT{hZ#+$U(y@$I5j>ytX|j%}|?7tE(@1 zR?xob+wIRO1E6U?a0)H;Cw$>#$MSzjN9=n$@HtX&=F3OyZ)%Hy8_Fx>vx0V1yKIzW zp%lEaSuT{eD{ZnW{WW$2=z6RxAGeR&ag*F9QrIVb!oIiSPH{%i9?CvJdnZrYufFoL z2t9|IKg6}sYky+g5qYEZz__o-efHY$Bl21M(eUH)5&7{AC*_Sc+T%Q6Zx6o&d@|f8 z_u4<2@lE z9%DL~^;FFY>k;(Tko9qJ{v<2do zY|*SQZ#gY}EPW0b&-x5bSU6;Xy^n=*)51H&-mJEc1Ge_8;~h8Kisi-o?zb%hK4j|? z-kM(YXT9bLTVK|qb-gy1TpsSTofWjpJ|R}7-a?!it*50kStZw=v%SkTb~dXak!SC; z|7rD9dmoR2PW%4KIiP=lv2a$<9{F?TvvdMLm1iubJ~KxrI(!+ql?$%{pLTQi3EH>aC-N5l2uNpA{vf`) z3A<3TsSFM&dc(DdS--@F(tc_&VZ&vFWhI1X7ZSGN(>C4QqYh_3UYp8xNdI-s<-&of z?g2de{xy@uk?e7LC-~G}ePSt2YV-*@uW>}altSeo@Pz#R@IBcl)sw~Fxqtp$JWv)9@$9dSdlTtAv7=Wyk)68!m)QeZl-fWRrPhZ&e_A@3 z{ma#p9eu(Vz6?mGW7_Qt4=#6fORI18f%6c0R8T6rYO|v^yC}ZhF$t1{Wa*`wu66Wf zKeFWz;?S6Cx8E1~vZIjOqEI%Zw6c_1aDOdH?7M3xiwiRC7w=;!dM5kQ`Oi3JNngxA z1$0e(8ThNoKX6=-X&3ngnf9PxkZHHML$c@t4u|Ai{D#dT)m%n+?IOb07ZPgKA0V`T z3E`&}6E<8%SXM%Kb|GQgq7NKrt(5!O?04K*IcKeu%ULU>f7VKQ&BA-l$eG7)*JsJO zQq568*W@gbp2MhLA}veZnB$S2j~>ohAt`MUp-I2dpU%;wA7B4n;Q9?e$O&fczxsDM z=d=G=;mk!(U`@0#<;+)fk?7#2aikRBflc8B?SAVCC`dhn= z`rF6ZzuR^-LO)(#pIa@F7QT=@Bbfwez_ma~jsmqEG@VN76R+!^$#ppX3+MOBc@IVd za+E~S{$DqLDfa~MNyPlgTxyFYkjaoanh9mZ_)t}Ehz_oY8 z{_7i#<{jcP+`*yuN@QJJU@dUovVGV0^B!b=jbr7tKg}z&AKp2Tciuvy_=H`w{%2mF zIMsr0_DE!b++jYObMU{B=6wx+%{#_*c#Pll5T_G$gl?RYA9wsFG&i48SdhQh@z{Z7 z`R$G+%U9;N+n)@z<#$W^_1q7aTrOT^d430%@NrK64$kWtPHk`Y2bZ17?{xgB^p*U* z+4nDbJ^yx-o?r4-{$ZwrlD7B#{O_3WySL+7#}S9C_MiD>HX6T2vLkJmj%&BC(3g)3 zN(a|3#T&JiTrRpDUoE?G-0gDf%_MspuCd{x?fS$!#dPbYvPZ_9aI7x-`nc)V15C$3 zOZHn2_#7u4`}RFKu1~xRsk=AZxAa@%dL1=YFO2J!9;|>~a2#Fo;yBV`uZ&w{4MnMy z@4#8EV(a?Q<>G|>Pj!$XB_xUzNyW ze3e__Rc?h>rPBvrF9=$-7;J2@5_+LeTpzPRN7Ko=v#jsh?H^RVTd*kS*9wgYI%C(z zp{|@woBvec%K63i|0#Hz%XucNY2_v3gE=2;R>r%s?r*zhe2uj<90Srjw%gz8Naog9 zk0q(z=(J9spmRBWqIf=WzTW{W%la4AdOH?&jfW(G9zX8**_B@#Uz79c*f+*UbE+1; zI6j^;Y2mBm`^2sJ^lZ^Y!dE5}&Ybpy?RM)*{B@VZ)~ByHEgiA`7xLy1bN*t&ipvPyC4{FI64ox7 zEV`|mk&9}hlX~4bvp3#Vc-%_o^afzZ9zk9;IQ9w&yyOj=D(mjtN(xw$DP_5*Evk zv_3o`n04R6r%?uy8`G^HV8%Dynv(+N-XDK?!lK-|`0pkpc%)pgCQ^T&Alcqg7JBZdF zPyCR_-QQWF_K6qu_a@fJ)%)(k84Z%{AM$uSku7cc2mYQBS%z_>JORhJsXTcy#hC>D zTeexSvLrE2CTU-6D-S0p726&tyG@=a(-=Qte|cs5q?6L5?GH_yC6jEYbZ(jy<@s;a z@>Zw|Y0}BC6ZXRu&rLd!tyI1?X^Bi0!V;M*1S-P^cCzrNO+I1&_ARLslRc;tvX_n?d~tHr^1Ce`0pAMAQ%=|ms_~}Thbvy2bTWJQ=Ikjavu~*` zm~tZf=5?^s>~C$KKc!oGc{Q|#^a$g!mdnKonfj#0X_lKQf2Fm4N}=sDHTR<@;G0ND z;c&$drc}#hU02H&R`!Fo-TWb^Jbmhk?1JirQhIVoPxJw%&x?;c-v|Bto)4zcJha+g$aBUc_VEYurg!J! zZ|qHvI|3DR^N+KC=6LQu)J4;e=l&t|pLr*8ckF$8+=<-DyVg$c&0V}_%k)Ds`FZY; z$=*9|`*}zE^y9WUTkf0wIFIP#w)*HvaBkl58j!5Jlbq&B+bzw%ntmp?(e>ft`53>&F@B53nSPww%j!fmNuoF z{a)e+Q=%3c9n?3rO?{Wg)T@w1Hc^~cb^Uv@it`|6W_`$>hY#8Fuqf}@D|2S2#M#a2 z>;!Wt$91!+YJJhdA@`MxyoT>VZ zaqv#q!@pnNGkXQfe}C2_PUmCWp@qHDERL_{eSOCVv*$5=#Qqrms&1cX*&qp;U&Q9T z&Ev68+yHxnHz#=8=EDlUktAek?Se=I*a=Yr@wcsb6>lE02>V9g~T z+s>AHpl$c&L)YWa8DS*apT@Tl3+>wzn=h$xd}~$BB^&eJxFL#|q|r`h@2Wm<$;s^X z)g-B&tiJV(=8Q$?h|Y{FyKz*kz!idHcFp>+M~a%irb z*WFMQ&s&-5F4~*-m%Wb`1#?awJb|ak!X#gWTy!$~h3c13^DnPFi}WAZaht4hNj_w2 zQ?rm%v2s7uxb3JvtS;8zm0ed{4sUIJakb^GgAq|}d7$hDaAZsC0~VG)%-L(% z3ETUuMBWugR?GVAxtPm1m(Ha=_NloibMEfkH1~+(h1%_NRVjDpp}D$KY%OR&Wb+E(+B^CIE@`yC5J3DhrPo>bJ%+=6#rfe z&19EgR@+eWAafpMPJ+`(a5@Q2r_B0X>T4xs)|={{0a9uATHbD(A|2y*ImYjDjNj!# zF17NaJj_Q*PCD+|_lJ^6+@=j?&Cu81vPGp>Ixic(WCgfQ_~%U!HO#q6tN^D@sK5ph z0VbH=!ZA~bnJxBnoC6%EgX7%6aX!y+?&UZSAdW2ZrH%6}_)D|vfF7v=&sIy6?>33@ zy-K3gZUkonzN)4?*bF?)*vI%9;~BaH9CWw#u*&2BxQn*^K znr9PH-~^n5qP!jk%2*qr&=br*1I`5aHi-XQ3-M#tDquI`!$6xDuxlXHj!eMaLf`8E#a8+nJ(oxs)-ac%rV0p8s?Zf({&stY9hs~=a}^z8e{C> z&`uL6&aKSvVotY-#6QZ<9_7%78GAUi*F=hQisPK(&{JHJ(@ghsoB-rk2h+DQ z-Np1#PT?qXj&cgen0}bChvS^$IH#ENlI2S%&uOOnIrM$TUouBnxwO_2Ddo0VskBAR zDK?R6uY~FO99qhBm}#A{o;e*>df!`^?qd2V(>>OE#60B`(*q`QJ2HI^DQtJ2vmQmA z2pgqfu~B)7P2}8}R&5khHEH69nU0!BoO-4^O(f1OOm~||oTE(lnn;{eOux@)v2*Sw zQfLX&rFJTt%Cu@HewgW~iNvXAx`ShOGTq5BZ(+LIM2dNo=^l>R%XBZtJjHZB^9Pt7 zVE+3|i!3UGltm?!vM3!3(?!fLX1bX9B}|txUu9ZlewgVx=0};1GQXbbIAaHgb~4?` zp|>#IZ6cSS>0T3wbBgH!6N&Ra(^58KrfNt?M{uJ%@IH{|%*s`5nx^ zh51JrdzjOU6pk)9#T=Yi1pPkKB8SVCL!2U}i2%raPJL;+Wmc>1Iw3b9$N6%bb4Z3@~SaInp>T!#GO0nCW7sOUH4(%u$(B z$DAm0qRfdi-NF1$raPJ6#hh;DbTg-iIlbek-TRq7H|{ZUquWtH97h3_VO{}oRHoGe zDo#t~F@fVSmNIrR_AvG{iiz|tVj{<6EM-p5M0)prMlp%# zBF1@>s3fJ6NWzpdr;f3Mv5T?C;NZ{EA79WjnbTnuQ#c*QQpP&Q4#q=MDCI7udl>r} z#Z-QZv2H4j$3s&&Ce!`Q7t=TnV<}@DV+UgwV-I6LqnOU|8A}=K7&{od7<(A|8O02a z&sfS>$JoKx#n{8x&p5y+X3|pzIx*-NAGx)7^}{j023)Y))r3wO}#RrA(_#M;SYq z)5&xf(_OQv4BgD>nN2;apK*XWVh(YnIh-=nrHm?b>X`0e>|{>Q9Ll|)QCvcF5o6~i zltMRS&n1**FVh2zQW1p~GpdYH#?GRTMBLrYbWaiWBB_`-#l;j~V~jGVld-Fq--Y=- zO!qSmFh`opxiG4XQN~WjZpL0lsf1&eP+n1{I~ltfdl?59rFj&`I*;Qstuh^D>|{>Y zJStTWV=wdj=g~;)XU+g~#C+n2`P?7oQ))#_7c*VTp{2}GnWF)ZE~sOE9rL5i?_j!% zv70$PO!qR~kND8_Op67a*80+i!nXY4uGN*&-&IRoFV1B?>BM2*&GR2id;9hXuGJDKicx|``9rhA$0XL^8Xv54Pj z5x)`B#Z1>R-ND$kh=y2ZZI1j~^2fzliBlMs*3LpaIe1OQ^IR zjGfHyV!C??<#mkX^f0HFL;IN?U|KBYH(kna%5*7X9b*S$Cx>=1-OY3l)4fY6{g)W~ znKQtl=a?gwamvd$Wu}XnE@fI>M$gtU9c6w8)16FrG2P8^j)Ak?eQX)^lOE>xGQXec z0giu;Xv7b@s6l&G! zeT$gxVC-i+6e0dG#-0eLvxcyvjMDFCoOcD6+Z0Gm3Tmri`VG zb&MVBI0dG=7)3edQpecA*u~h-DArSG5o0N19b?0KE)UaPj6IC~jG}^{Vyt7dZs4A_ zf%x;7E@fI{x{m2XprMN-6`LVG!^>OTvQRjrg_WcN0#WyzCjo-QW-HdSAD#RA-4r{P{ z(eic6QSJ`&kZnr&Rd)n4-`%l|HZ2^0S{j>IC_9a;>vc8w~aP~*p za~+>@eAaQ)@rL6g$H$I>oOwCd<{Zm;Cubn%PdQ)9eJuBzxzFW(kei)1HBZTltXgYc8n7Rj)Ex##RW$S-Y)o6fpvV&_|oxf$L|^c?)YDi z|6qKuu(YtH@Ic|A!tTPp!k-jgGGXb2)e~ALv`@Hu!ebLoPUxBNqX|mAp0;<|P1Ek1_N8fGo%ZCkkEZ=&+Op|4O+PyQo6}#J{?_!LOfR0X zY{tqNYiC?N7BWH<~1{Kn)#WTCuTl2>-4O@&zd#6VfMAN56pgM z_RF)g=S-e6Z;ofqsyVmJxo6IkbN*w_n{$3P=aEZZxTNQjzDs^~NlsBg(X^u0qC-XB zE9xtHqv*Y&4~pg%f4caN;$Ia1uJ|9tljqKyTReB+T-V&_++A~Do4cXp){<|OoGSTg z$=^x};7g}=ohY)g|2_w&4syj*wC^08S(q=zi_38m;Zq2`0wEP*8d`rkT7I^OBE~M9 z8rY4tPvYFcKA~c*VJpr82=U_b^*BlKk3EDnn+QMENSJpm;Xs`5(N%;Owr&Kzbn|B5 zhtph}Xa#MJ5#GG~2H?(U z2k_eUw*ud4{v5D}@u}wfK|gfuSAY+0Ito0l9S81S{Uq=UH$M+7T27(1a>C;sgir1K zci_|;PXWIf>I1%0`x@}St9}Ih!p(%vjXwdt#_=~?P4sI0=fK4(#ZPS}j;_&Jx!Z4~ zG#7FFZ-##ZdQI%Nz^^kF#eWZa>y|$Q`w!5w%ACIgKiz6`37k940lqkc%5w+fZQ}~S ziM37y9$RXZjZ3m^Iyk#mQ`w&EBu?vA!ZnOf*AQ*I(N~)0Am*2sQj5GW<5JMKGWru# z&hPUZ&E`6Jn$x`gswIf?odpzsHa~S{G0{(5O8EOFgoinF4deTaC5+cHUb=|l{F>u? zmJ$6!eo86#BV~VrTK4-LgtNIvJ-uxO@vo=W|7!=mV^KT3m)%Vz?6_V<=)=o&;44=U z=lPomf3u(Bv@W{>{O6dymHBI!W7Ox33f1R*dn*+3arF6bPfj4K~>9uTW-q5w3+73cz-#E~%83PBfQ ztVtMi6G2bJn8PoT z<0=6?7bxSJ53Iu9;-yn73qfxai$K=^WpRU84E&;43hc%R$GJ^`zfdTS3ODeW@B$yk zU*MI+BRDZ6i?2aW$oT8LE0MyJKv_H`R)anPl*Kn8A0+WKP{t{RFz9E2I4>&b?+bq$ zh%W_*HK0!dW%0Zy1OB_f%9-c^%A!}S2mK;Y7N^7p&@TaHahf&Le*k6iGTufKudr_F zV~zBEAkMx*GD+CUx)tA|QH&bQkEkK=cafFmRr9 z4{*73AJ8d%0iiCSEEMSoXg5&CD)E;+s5?=wm1}NjNKy-sH1L6#) zbQE+sP!?B84+CS;*TC5c#CLY2`tv|q1AiIx-9TB~gWZynxEF|%pVALN-w(t& zYUwr54+62KExiGJRXPKFO?nIXy7Uh4P3iw2&KaOAeuy(E=z-Etf$vH`1O5zus|R0c zkp_V8N$&%HDg6@he+87q2hy*BA4=!J`7ID%R+2sd{X3v6=F7hYE|h-<#Qrwmo$^P( zf0O?RS#ZB)HSht;I^Y*A>wyniHUJ;8Yy@^$s(@d$Yz7{)Yz01IsR2G}xf1ww%hkZg zEw#WWEK%T7mU`efEivFT7P1b$Wr+iyv+MyrZ)pU6$I=Y^o@Fnv$C3oTXlVn!WVsIb zAC`9DE0!C8-?!WdeARLj@O4Wk@J*}*7KIPn6^#aSSeqe<)2&}Yz z3b@I-S~$fPYZ$oAszZ8R1==O90qqgjg7%06Xs>7i?G>${w~86IAaIuL)4(}4H?YX& z2hN4!b%|AwIVo`fr`VnrFNjl+8mM3CdFgl3EctGExuwnGur9V%TDMv6w|>Ps-?qf| zN84iidiy`^N3#}Xznxv@c-|4niROGJ=Sw+X&(U%dxi{tBn;Xdcbl#Wpp2>S5uQ%_l zJZt{b`7h-ESAI>wD+LAPS5AC<;bwJGIOwof@b z<&~-Hr|qBCH|?j>;?ws||L62+Gb(0mp7EI(FVAqyTsCvx%t^CW%}UHl&bn<@^X&U) zzdQTf?2l)E?vm1?Ym257Z!K;v?k(;sK2!XY;$Ig}D=99ytfZ;r`I1M!fW9r>1l}!v z47^A51Md~T0p2J68+gC?3-Akq{#5t_A{%%_j0b*EOnVT0UK9Zz6qf?OES$iHL;&~| zQ3~u5Yk}RO68KfI4R}=40gs8@z=uT(@DZ^e_%+c1d{le}cwBrQ_;v9B@G;Q^d|W&V z{Dyc6_=I>4_@w9oJ|$iOo)B*WzbSqUd|LDapAo+SJ}dqk_$~1l;J0D$klx7#o)qJO z&x>im7eo>8JK|E{cZC!9JrMx@yC?;P=G?z#oV%;H%CfSuN1I12LH{R_fm-NhV$OT7bZqN`6GNAVr0B;i&`O=FL+(NAhOQo4Ru}Ppc%}T*#vmT=Q_v$F%_0LR^>PT7>H|T#Ion!L=0EGF;1X zIdQpgDX_KNxIDPLxO{kHKdu1mt{|?5`h-(wBDqPjLHsjiYi=-v}D-{pp zTMPnu7{51|zfYLI???v!u;sEk3GohFe$htp?=kQ1!TqyhihNS&^0VR!`2`V|3#5&f z7sLeXN@;=hq*!2mL3piU&{yKxg}+9Bz39lgUfhQ3j;yeBca}wdF6(Z~PqROdt6si4 z>qhyY<7K%j=NZ4 zyDYkVqpZu{wm&@U1$$e;%eH@?b((&0y@u;q)bC@qhi1QT@1OK%yF8~bi{AIEx$CoH zMUUBHMKxJh$fxaB$S>o%DeFtcomnS}Z^6}xydKM{%6T&Dxw%hfxl5kRQgK~Z@^aRZ zlAmQgG`rKXc?!OYg}K6SF-K^mrd<(EG&jXo%@Zru#8Tz4UC~6OwYjl6*4UDWref9c z##nP(N=>#m)vs!(t#!GaV#TUpZLQZ=OJr3nr8Y$q?a6p@)s74|hR9w-Muee>QshiR zPT}^1oX&t#*TX^87k2t1xYx9ZFC1~|9#24txb;v-jkqE{&FONx!)|BL?{PZAK`jvU z>xxsjoB?mx>roX~*y+=QVW%q`P`m+WPzeY0AQBBkoT}gF_j$u=NcV-jJ}neb{GqVw z_W3opQ|yYRY9r09^|A8UzF5NLbna|!^*TjzY<5r!`@D*(1pV$vFcMM1Zg0dJ@jC-y zpRNWpB<~MvUfqxKhP@Gw#}(2vsD9NM z@OU)UqePsZfZG%CXu6^}BYHT5BwS8490`Oy?toJ$L6=AKt8Ufl_XZ+hXJ3r|?BwevhUpisEwyf@;|1Ruv_r zs7}SDD1ngEtK$`2L8swgL!ls&b!o1E?sj|et)NK6A42iNt`Kqv<3)Xua9D{1 z{8YnXzu)Il{UNV2681zwPMS&!Eto^ex5%956Dz5q$*LHzwDgNt%gBeE*tQQ$wtk zQXlgHmusvn6@yD9G+(bGnrdr}aUsWgz~yoi8)HP>7mX*PI}$N;$HrB=YHQ1qm94R4 ztSMF2v?kgZtJ+$%_J9$4WW^}yG(yn?kKL+NYrJU}B~lq}O~wvzIMa5yTqs%_V~4O9ZWT2&mPB(^@I5j9joR%1{cNg zGZl#^QnA)p1JzY|Jjq2eLPl1X$;FfWAPTrCwky_zmo_BWShHv$ff`IvvKTp6&or$-Bg z-65AZtNNjY{h>hEB~-5x3@VCOLpHcqoPL)l;#53Pyb(1J za6wx_*9HBm8i{x!y5EEBb+60i*Az{OXf9E?&Xv|$&RUSDrHd#%Gai%?D0J3LTc+EI zI!Y$=f72Ktt*VkVhM6j~1gAPiA2WhR3NnK`rbH*9k%;Xd6zOIj5Xz(S80_LkC}|Lg zV8pwK$Trg=`*KY4a!iqHJ_JDviiZC%;S1hH<=lvOfuKvLmCk>dpfdxNpplVLn`4el zBg&{+l55+{rv{;dhrA%s7fEgE&b%;uJFhr(N zvD^5ORqe1Xni3FpYHMq>-B;%lk+!CK{8Y3h*a87rrZ=@U!bX5SLU$WlVGG3?hBNiO zZPAn&19rg9_%2vF4b{=)9`(9tJRJffrXgMrX^(UetKN{mq3AB$Q(XLPibWeXG$q=r zTB1$3RqIm_*&xco&4~nTL-Q^jZ5WqD9Hj#Ta(8n>s4bpAQSmU9E)h?)Q}ae!nUyIT z-=$wM)lAVVW3fHv>lutPG9nPLV%7Dvwdt&@AhHuN-H^9tgtBy?d7r*rA8TxGZKsl? zbq0D#L(Ii<4zhb(Jk202Vo;kP$D^%H2BT#U-ai#GgYkiree*d9|>|7jOlFn(B3f@CICJ*r|vW z>FM20=rm(`rwGlh2i&@%`n_)57xw5XgmKXCgxwzrs1dIkav?Jp1g^^iqu=fG1Qji) zdBPBSm|nS%Dtg{fbqH2jB{Xp}ULsw1h3lbAb1Pg8Oi)|3rDA1Uz$}9S&PHlgEY^Vg zP0=R$sczm})|3Jf-v{vcBd(wx@jAV}NC;ICP&_24HD^Ez>!^r`Kj@42pm}r`rbPZQ z{)a+%cRyy8KEyRF??fWgL`kXxzan(ZUsOeNVLlpkDsI1D)BIkS+wV~^FIAl$OlXv# zJFIAtNJOQXW=K~(m<2++_%Sqs*ND>@rI#ZG_TVW3Wo6?6FHBnM^IDHL2l>@ zHRwStM#6f~<7RQgCaE3sXF(6{p|rf7I$c%#`q)ahvsO*ScQvj2q)5uY5XbGwMDk(o z8}WH`FKJ%g>4E+X`J8S|b9r20Ul1KC7(_u`dPw!_5q}_nUhDU%n%kp2!JgdQc4ngAuRW?FmF&a094bEug!?F3pL0){#p{^#uK4FDAZzG`BB|+EmCHfL8Ve zeX8PAX}TKlz}&zb8-2m6f^Y={6%o=Eci5vjox0{% zoF0E5toedEERvwd??FM_L9{#U2-p|_U)bkFyJAwT_~DR%Ef7&iz|nN5y18kjCUNO=boL&A{Pm2!Sne(lsEW ziAKys%^Oo0f!z?p%-rN&%veFRi>N4sjF1xGq{sX)3wH>AY(0xtPjW#?!zqR~Q^b~N zqAgb420NvWq)BUYTMMK&=ykDn4kPoNg3znA;j!^LRd+b7 z!?A=R5K-VSLIPobD5z-xc#!a{4u6=-qr3fZEa{p%?AIbe^g_r3Ps9(2p}Tbzej_-) zRF4WDja#6Xs}wV+g&@!(I@~fyGN^kY;Nim3gNp8Qd%|H&4Z@NQ20|X65_Z9JL!Pxz z(EUl`MEE@3h}(lv>S85jZ?3ScCKF)x6T9t1~qA{OMR?uOS4)kDrEINtDjAq|6335Q^1yJ2Lz#Wq|# zkYFN%x>%nyLdO^_=F>rX7=3g2fB>(atzP&<$dI6p4<6fUa2ht6_ZTRck(xIb>H0?U zLlq=AV~uODsb{;5F6|!LrQIXU3O8%fA^p@X%Bt$4iC6(YFF z`2NDG$K^qni})bn!vUx)SR6j|a{&541S%RTECBDW&kd;zXQmGmJeB5Dsv3Y}Qg>+{ zkASq-Fl54L_lOJH*bTcT;Kl%eCozaQo);qlIwlnID8Wb=x)*Lw(z8$`US08eh8qz+ z4;vBqF^mWh7gv$4wEFW1Ke=(?J&i*0r!W#5EdckPX4(?9&v4glsKSRg}5ev>({S0jCXP!*|s3x=CGqOt!hExCX2$${) zLWH>CO;@~LH{LelfdIg?Hvm7h)9H7r9-UM;Rg53i<;8%;SaxX`yb+HV{(sm^K?oyn z02)64f4Am>fyRa=Y}AMw-7BI)nfqZ`Mih6*uZ58|=H;;B6rUHCD$E^~Bv%l#c9$1| zHSAPfo*)VqM9&OkDFB*1;Pq>8th*pnBM@&;?nnlSB4$SfE_ zQ--vlKcK@)p~C}#@#Ar7cs-45Nx#n*^a|waf&CazLkiiNPRPcvPob>gM2PtOWKW`w zLhy9>AQ9m%!IBB=J1i^Ec=3vGFo@1hV?)=y=%1QPhc0oV%3=G$B=TcPAc9xZoNfkgiva6P9vI{p*a57&d7)|i7}px6PeH#AQ!@-WC5%}zJbErnFI)i(TDbT; zkgWkxsp1L+sKqq$MmN{*Np1}X1jd|7(^;Pr3uCm*1;Yu8h_uM%)5vtfnD<~Zr|BLS zCLS1Nm=^e`FQEi}Eg)dIxLiR*cdIVA3?oo%F!*4ALEHHt$zaihA;Huj>^#f?-N69N zKNVUHww8uwLMw2ohV`R>fDfQyE8H8EWm?&m0JS~c(e#+sYl%fYjqP-h7im0%jA*>T z$Zi$D=H_;_r6tk6Hrm=y-`oHj0+~=|0UptC30{|oC6fjxvyKxO+3W$>*6|%XnrUf; zT!pybfa%e7&8>+B_JCr28NQvN1%Txd5O6lYut3H6gRqkIfJgDTpe`_*_rgUR2*3~F ziDne5wc5vQEZrP+)x)CJPpU-F~-UgV2Q@#cVAQz$A`VQXv7bIO*|t zoMFrhF=f`>D$+s|kRR6_6mECK4N=L82TQe1m@i@UF$E?&%#=Vt)5BP*hCskP6iSf# zrZWf!gX)1Z#P11;aMMy^eXjzz%9e8^Tu#=0%#E9)vUwin8STXsUj9xHS%EPdpkN zZ1`$;T87*j;$;W}>!Gm*BtY>tMdKhd9uw}QK|xj~&6k2BJHe2s>*Kq2r-s~PaTm8M zuu#~Rh^-PUDqG|GFc7iWV>sBjcWAK`7Dkh+gd6J0qq-si4U-*AN;DTlM8p-ss0zbG zgGPr&ge-xH2*(thG;nu0BQ&E@(R4mlxczWPz-6Ur@D}M#bO0C>m~6QqUV?7j3ztPW zlGZTsu#v$_v!#FvizNh+qL3>K#w->t zU?~T5NIV4zYLtK&%0S$%{JY2A`Fl|&ks0LWXu#%utT^PbTW|}a5vAF{?1i0M3Pw6MNMIHU*$&eO+aEl*bt{mmaJOMg0tPI`sIHQ@t}LrqqYl?O zf$~8HDHysZn*l#%Nv!We&O{Tqfz+f`ax-*O47&^JW5j`+Nw$; zin@hK%A?F6=|PhmrVfMKhZl|R=`s9Zvq})!#pw%&Ay@*69>Vm+9SoCKiQKLbG0>8( zApS#bg_RKc4=Tpzh7XO}OlU#awzzSrv~9%gg%c2qQ4lql`@3jv0rMF12>8utQ;1XZ zV7Ux-BiRLzi-C}EDSkLI;SI%NnKJ-e6dnRN>tLsoyHat(sKq1>p1gn_fcL=%O95)j z8}^|mp*2DR?gse$f?l^0#Pmx?fv`WvrFbwy$G#revOX9X3dCWUc7+6VuYzqd*s4Pl zNZ7d{LDNEP_i>}&xqNsDIN{JO;7Ns1?+d~634ehKD*<@~VRd`pu)y9L@}K&h@VkWt zHseGTnuVgrKuPfu4TX2`%mMVQ-aOE}Yw$WDkgq}Q(DNs2ch6kA_lQz~%e!bC@d`QYavzc|J=oFL%4 zFvo>*Q4|EgnE^KsIpWb4C?|@H$&`+6kLd{fGBD#2%j@&PxNu?VG~|V68v_gy3M-HB zNxNMUI6XW8n5d9yp0KWJf}Jf+zotScL5E-;4`yagOcbzYiU}+xn2O5<$BqvJ+JpIr z5AJKoQ7;+?p8k*~D#IGTt6E;UR;?|o*rJw~X*?W;b9h+kTlDf_amt5rC{9Q%A0A~G zgJMvG^&7V6Rl`ym&E>ciYI#jn*@&nknG~~XZCORluoQ=LhN~_Q%qf0t`lTUEziZMx z!n`2^i^#Ghx3{r3$P8oqhiJVC+bG(JG8Hjx>%bI;`WHtcnhF?(Sp_oPg-q`H=>Dpf zSbaQ}1Oo*acAqJ8x?37$WqD+_5eXq9eHKCn*CYoYC)dt4DyzAVp=x)uq4_#u84GA( zPznrK^e*g=gCtV1?FwC(rVr3>@OopQLmI#_0WFNZVK8lBzQ7IObfbrot)vT>K3>dx z-B_A(V@eys9yroID)a${1O_9P$zAB25%|@EI%X*_ov_f4Ni!zDT1e+ws;bn(W#}54 zE7UD&S-Bc2*Ns`^7<{VLwPl*7SJbMZvhuR(no*B^LNGnOp`yHIWD>*k{p1*whMGt; zU$=I{dZV1&HU`PM!gwog5QA%yIt@oJRyDAGp@*=(gC#5&7~u#kRRwzs;6T$na2Eu8 ze(VnPL})i3tsRlxjOeb2z!peyP++t+S+QA*nzE;dnJWw-OCBr%h+!iXV z9;_=t9Ko9b6GLDpgzAQ*#ws6liwn*t#CJkgkN|;Q0(TwUHclrLh)>u3BnE2el4|n9g}!1BF)!IbMab4t|7Z3 z8QZ+6T&2P?V~9{~XrLeLDXNdHB@>Iu}a*2mf z^-V@nV+PO@Yu8j&iOMzAB20_+?CgQ$URy27(vBYy-k|BVRb}ORMKu?4LpA4)A7lF) zh&3$c%VSNuQoC_W37f7HGxFfwpv2}y-N0s!U8Qaz^`dWK?*NS%=%2DJ0@f_p1|3iG z{y*Xl_0j~Z1{qZMbU&nrY~l1tn$=csQY)$=`X&~VMpKVwn|)$%b4GF*53YFqGTutD zqI%V`)b4ol%B$1eXUwSS#W>nb@yU;Hs_7*6(PCe1?WhhNrJXFMstZQ>mDEJb?kK;7 zK~bfbB?oEL+hCN;O_ZP7(2|NbVy2s(LbGZor5#OVSeg7X9JF$Vt~V#&B7rir7$ws8 zr&^;T#z@;&;f;z?*Qko_!&*~o6kBy#nMXz4oQw@+B}cQk%!#3qz!`drq(ja7IYoq| z$p*?bQ+UL0hy`{GGO!wk+cHB?4E5gZ5Xl`78}%Z&yZJiU5=~^q;N1Y2*_Su(ir0Ta zi;L!6v8H-4%-P8sg4CoTJlXw8wUHq*yW;3YX@?_)W|-AuoLdmd)_QJxbNA=^Xj60- zs&+S|b3<7JN}s5$jbn!d+A+hVM9mGVi1JC@d$1!@kA*NFE4I;HDR38exXn!|s^SI_ z-W{vo!)7IU*#st`v4$j3Mm)9&cgAs_gg+$7R0JLoifa7SHew4=1-efd)7Z>&74iB# zP3TN`j`JJ5FT?h%J=k1>5bOcL&ya05Ro6vZTEHqxrk4RkINB77W%>n&ZHnz}qphuo zR<^6DnJ6mkrdWM*D?EGHAd6mV@bpBqg=iK*1}ltO$0911D^2qbY~JqEAb9uK=B8wH zXKX_gl`RpY=tgx6-mk#`NpDgYa93i#K5d!uY z6soNGI^2xWzC*U3ErXXG9c_1Oa}!4Y{~FtDxuH)GiyN7XwzrM<*0Y>3xIEry6LUIJ zrpC#0nr2eP^OJm5VicGB7-?oDRYYr3`qr4Tr0+6`ry1$rOa?q*F}SE`bO%&zS<^n6 z?GDO>G$U>Xv>-5vD}`$XTQkFs)vVf6TN}bFVjD&zj>ZrxO!da=Ko7*R&dPe;cXHZ>h1c=Tjvbfk+PN zDWhD+j+53HV?NzDn#)>jG`k$?E{T4|n#Md1GCCHSVvyN`XI4hNCVCH_|F~!pk=8i0 zU~+f7Wpwg}0vOH5q6_vgrP_Im_vm1=$BqurahPaS3F(Bx+Hz<^MBOI+`#Nmd( z9>Y-&lVg^;;21&1`I}Ks7(+;pVv5>|J~zgL7{lhtg|P$4#u$DiYK(YjE?!N8GNaBj zMvSti`b1j;pXVDRlqFAs%~oTPbj&zd+vRg@V+5ML;G*Ff9e>dnWXrdWk+Qy@PFs;g ziKa%k2zmsoaJKWt2sX|Oj}E|4Pr*nXBOuzeCcZ1$l%g>_I+#Vx7;Mw2Iy%Jk-;55R zxs_RK>0UoNlHtW2#iw~G&Yv}zmM(gwX>;<#8}q>WX0R&Pt&MG4hS^bUnRx_K3_m}^ zMz7iV44;!|oakS$pH!to9wcajT=O#b>9 z&VtbDe04MC3RTT*#3v(`NAn0hX3nM|-$n+&cErKC-C<}mVb+M=+S=U83}e!VBN{XV zf+qV&Z^oQcQhw9H^pg}f-vR`a1%wbVIh@dr#fgG$#iW9xUl&96zSQ!*{w{;Oy_yUR`RzoMMbNKUB@&u z{0<5&Q^|isdJq;tJq8Qb6TDMqkYyU$ zTy<%yo20BUATu1iCr`OIp!#<~^rjyJKSQH@8Ja90Lo{cyVqw5{ND1d3s8ke<37n~F zWWxABfH6Snd^9u7@mk%?ce`8oXDgg2h5^C1REZ-d2ZpR0K4BO!ERFN$B=uU17F}RSOzIxyEe|k`XO3cWs?z$8sD}*+!4(I&DYlP(kg`$j zY4wxtwPk~1beTcMXa$)ojUI;6o)W)JC-GK8=`c;CHc>2N zW-7{tOH3-(DBme^gI6{!RELQD3=3{F{yGeHd+i`6Tx+Pc`8w7FJa~+yEN(F4e#I(R zZ7mNaV?+vc$=2&=Y_j(elDrif9sM27J2iz*OYFcoKv5BEOSMK5s4%ual4%t>7{kb( zG|(U~eT;RGGpdk-L21!g9mmXTD0_rl7z~075yfn7NXKSFmxuc#xOQj*1m+Y99CmdU_(P^W z!Vg?DWlC~rL}PA(EGlT6Bq%dJu!Y|-(#X6{dfBoJ9}n8UVdq$JjW9yRF7tqqJHNt?jf*MxeL;7T7nWixa>)H#@nUimL&&yv=r8|yFJY? zMm(ozlE~VmGKVtFdz#0jU(i@JLnjsNM+&uTv1C1bQN)3mA=7G?Sm9hX`g=eu|A%{9 z#0r-Yk%r;mXC~5lnPYUBDYhtO4a<%Rn)dNWiDwidV0oGO`0-?8`>8%Vv;KKDQswg=Sj<`&>= zXe!1oo%K{LMjNJAs126+UhCsYt~oq_(G_n%>*C7>87|%t`?=F`U`n-I#|cuMK`t4u zOLOn^@?KgB(pMIkPn{xdO2d3XOys|tr_Up-8Dl1mFq4n9C8OjR{`Htd^L-03(KT4U ztc@miZjCn#;Y3JxF^BRs%zcqys~;n)y{rjs0m%;q0ma&y;>oX3JKHL8c=tLa)e;xY zJFXqM!5qt=#^->|E{|2ps7Dtz;N1tIvBXQ~7=ya$^5kym*!Pd4` z>c{%NW;`y|#sDl{P+RHmTO^T0ayJ5u-6QF{GB%+qo1x@sZJNhr+WJq=6Nh)947b^w zUeelkrMd5e-Zn!wpwnhBD8v;T< zJu)*=U>Vgxav7;(ykUZi$UY8!1A{_Y23-*A<4pz^m1@pvjeDw4*t3HVXKoM^c0A2p zGc1a1F7XdOFpN<)bg6)gHH?u&0>c<6WM+|hSiqosW!XcYt`lZEZ%)M%f~F`;q??Ox zHn%ju!e!D}gh%J$I~FKRWej|tCSy1-cWrYcZTrB^<5s>;HynCUv%x`ikTgniFpk1u z?KhgJz>YI24T&zbbr(MAf~9$4VABWxa{pqgx*7XVFj`XWd~0Z0IQvZThjVD4se(ze zax*eP92y01HDVhK?M}#qkHMzaO|RK=Ji~o3m=ZY-_zprazO$Xl9hBi{&+4)Uupvql zqOCO{P_I$QJnXjPLZZhiFw`5g5k6y=p(t>VVX+@Q)cv~j4*+y=p&f*-Dp57rci6AGF=s=tvS~lH)Y9ch{Hw>fEpXTp;XZi+mioZd*>fyXLa89^WGo(-n*;4 z+WW2+kdee9EE^13u0UY0jcp-;B!iGZ0xTQCtfVVxjaJhB0%RBa-Ybbh8xqrmBu?Wd zG!hJ$&xC1dsx~xn8z!TsOzTeEx1aBG&imf?u7pBT_pjvc z-uFE}pL5Q0p7Z=V=Y4NBT=A<6mhim*tGHO+-kD?YFrIb`h{@|^s-GDv25D#DO+MnE zy!6il6f?Y9ZDqhdODz=l!;=@+5)KO&yr&A$!v$4uzKy3`qG^FR&S9y13z&Tz=JW4? zlpbg+HoPp*ojCc}c4(f(zVGPtaoDw?W6QA<2r!!+345n^{8+%d04!M0H92XeQsA%P zR}m6Gno@(%&r;}+H%;9h@AEwsqDLSC<6}mEJ~S^!g7w7q5iZ)*%{SPMsUNa)sV6?D0+@ly;+cYez(~8 zU6!ixWnpzB3!6e6)I&Qgm}RJfhXdEv`KByGwJ)0CY1Y5rs#w_5Y?dOp(BNRAU|x`D ztY)xWfx1Liz8iAIWzbQnBt)iML$~8?Kk90WC~_R`Ut;8O==C3q)MvinA2T z%7Lr1#3ChwhxL=kfz7GBVv4aJze$ysQqQDz6rmL3w4xKt4<5%bDW+#{wNlH1VRE3~ zkwO#*fv3ooGMG__9r0c>!Mi|nSmuRQXE9`bYr%+x>x4I~72md|MQQHGf>w^VRxgzu zNHNn!XJTnx8lT@c1ba^%Z2MH0#oncp+Da z=`sD3zJBf5LuN7U#xKc6pNLIzE{pnsn2TqA2CWy?YcF2Yu9Qtmw%al&^p>a6DXhw{ z3nb1g2FU>kn{%|L#f9xB#BGk9+IQlTYDix<(ecr(Cr-Xig@UgnE2SkRSDshWER=8j zSxhvR>57&h$zuVK;u)XCFgp&k=PkH1@Oz8+SN9fEs8@{2;FI->Pdo!FJg_0M<@lio zL~DJz49^-#XAOA9yT5Dtq&s@jc|~ydzWxywD^iFqFqA@6BQp&lKl7q_UY4Rd%l6-v zfynHoFs4GT>b`xh&3AU%#tQ)dX2Sx6#o3c?+Y%R9=NSp0l22(9TD>iBhQPS&HC4*$ z)3i@IqW4$1Y44GoZ_j1m*v`tseL^anz3(R)b&1lmy8WyLe@1`Gw%vF=j$b0opie;F z&f6HhM^k%DA;CKaLxLcqAROAnBwPqFwN`lQnabE4{vV>?#y<3aW_1{ zvAKCpEQEmXXn5-*=eQRid+dOJ1*G)?MycYO$`A}PMutj11v0gR)me(|UxtY2EI0;3 z3bTVq5K9*9Abtx~ME`frgzSq@U;*$l`YP19cn(d6GR9aS!VsnT5b}PE5b+#VFOHz6 z8cBBU#k_Qem%)@w)gDas2SnVyJ8`H0=zzZoo{nC!bWB3K6N_xUAv^4Wg`L|V1sR0% zh#`(8XOSJa-Pml-N z4YuIP4<|<7zJQtzdP7!6)3q!K#ac%@41Hm2fm>xU8-m~T&XS-Qs341N&v~X>^w=?I zUIYQUAXM|T;)^*Yh?c)zm1=;sv+{y~H!3?T=K6cKxghs0M-i)6jPiJn-#o=Xn4m4S zJkNT2HyA#=)PGf`nnzCSvFXfzsU%I%?1|$Vem`AOhcP^X1xaT;iT2~thjm)d?LEnR zO%$}61U-}|{`JEack(*ekZl!x%&V#U z!j`5fK6Xe69(3?1UPA3a9>jPREWOGW_%h9q{TwA)meD_-wXAe8il-3ur;wr*QiyuN zTuAWCxsYJ(GyR1g+3RKt2@CC$xZl)Sd{uaUeE3qE7yXky5)~l#&t9+Krs;dgL`&}g zUe--YFK>MI1jivfw+rLQY0HdcN{aCbDzef-38H+(B3hcIV9O((IOpd_hjVa`wo(BP>iwk~qK;V%v#%3exNL4Y=-t zn5`1H!Xe&qYl(gVg^1RSf?z(R=yq5>F*|Ij*cN2$QMOACQr$m=xou{eXK;8eK1oMl zUuN+^(!A&;9Jlr2SBBtVvP$DHm_FuDFD^KBAPZ*(fWnA>cNu4_2^kCk2wihF| zrA_<`^0TdbxUmijNbBdQ-a@8Y2NTC!N+Il*xIfKgJc@1>h1r^U>aIga+FPd=hyeS}_OYcS(`+&Dh>+shV_qSJQxrOcckJMj$;yxl214kr z!#Jd}2-S1kZJ$1VV0wP3064rx)@I7qpUsmG6>El+uwFcy9ZbvXfPWvRLwU=E*I9;{ z8%Uw(K%Axr;`nwh_LB`Z>vF9LNf$O6#J|q~$39@2L99kfCa*2iK&o|QFx3xC=zA2W2c60NdNTlQm5+yz&=*y3_8_F+8RLR2eb}LkjhkPy9 z!9CZ7tmwtEgeNbvU_csu9uqvTbwS}6qgimw9D-1TgEXz+4-Ol0x>+J8^PQ26&KjD-aOoW^ZUVFJs zf)Fb_%wVKae7YgX9eJI--6DMt$z|6?LX2q8dCT+G$53iKY_iOwn6P5Mv)ZE`e+s_9V77QBlSi^+ zTzLX^gPgo9w&0k}*G?Z3HXny@-J}2cH-00jq4U9n{&56A81FLU#5+5E&*T+h_0d1+ zCAc~A#xftz6np(x-V65BK`|U2v(1vUJcG5mEGOt!Y)zdM92M*vdOjWo@9j6Al&hLq z;$i;~5o^GovCIq5H8XD!zlAhr0&*`TnDi5Jtq0y3(HkJ0SW+7yW$%M@yN~z7ck_c% zdB!K&^C$Aiz9;+PyE)jIXB^JpOdmQlFX%g2>iRiC_{j94EzW$w7W|G2IB;2wC{^~* z3@W$Z4>YkHbXat|4}1y>jF!ai@m%bN{_IGVo%%qBWuaS8 zxjOHl{awtAgRFl$N<Z z^E_z-TCeNWxe;t?vW!a$$wC@{UvuV-3Bkg%WCg!tQKVt>bGF;-c78J~RE2>|>$K9nC*;@B zVqWg43aRujgqX#C+Lqaqc&zw!iI6n!Vd%{q(5ENz;|J$Y+0Gbbj^#-50>gmQE>EV7 zYzyGROq#t&740f)J*MMs*4D?CFb;L#$L$NJN&BY~3rpxWNgSlEmGn;5V(x)rwhg>r z`mW*{se^tG=3?B>M4wLF z6lz?;TNSLpi$N+tn7Tx&up7vq6){Qf#)5 zn5AVV59Vx^^`Q^n^w?tu-yYeLDIr(Q7ELr*mQJztkF|v5j;$l>S}(O;Jh}_(I-Ghj zT9Qx@B^!x{@TI7t&r-dSfD`LtGnr43Hkx!U=N;A?+=&(NotYA|^C5H9`4tfKnJf*2 zE-&`Z2Vv8vSL^&a26D-|#liFo%9G)}jy4pVH573_IIZTv6jjuk_4zFBJ{Cka)^C*w z#)?lk_;$l7c1iEudEmr}iw?V3PSEqR7Lk4<_Z-(tp*d5X*o0ww;$lU!6Z@8rY(uYv zAW=ssZKgpM;~8vRXgo6D&(U~a1CkEMt=uZ8EcmwZU+VR-3yYlW=OU_C7Xmj#9m|*I zy?6vD5pWhP12?bJwMy~Q?hH6$|F;uz;%8T4c!Tj@!=0zHXG>7>`QE?wqr03TsBT)Lv?gUiYl=sF*bT$vK54vgRiu;8@(#(FsS zI6irsA#(_MYgdo?W{eHX%%UPrermu+e4{=*)^%GRo=65D-wd{ zmT{kUe9FVlt$fI}9S4LQj~2P(&Rz8Yp#%IWw#!YE_qf~YmblNmQ_j^Fxi3-2t!%j| z*Z;c6jlACam#yj6>DEuQqLI6SaFV}i=SJ^z$A~Qvn{jSr7h#+{f;O^;{6oa&oxA-$ z?u%{`*vGB(5qFf>g26aQ>Lj&~b6q4|G4=5&`rvNdN%|8&I!1{@;>#p8T zS+zNDX=?un_4bmo=srNmUB{0gxSfOsOSY|bUv9ydEZ^u{^#;}1FzKE&nAzxyqs)5l zE*DN_=#7UZOfPDjmw(dj3+X;>(!;-ASQSPz$Gou6JxW`jbla@wJbEAMSg*^EI(Pji z>30#smneUwFfb3~dH0yj`Vo4v$UH4F+rQEJ!`7d){uSS-xs4Kn2Q_!FO;G_L;1~nNfnJOaX$hCw{i;*ywJwP@*Y}SFC|uwX#GDu6#GRx?pa>WShT3hVvHGcNEE?zVbmJS;g zd2S|NGD-Y^`xLq2R9c}&Nm-)L;!c7n8dS{#+$W8K{dzH;wqXyYA17yUWo#ow6d<~S zZsJMz8S1)mzp6Z?O@c+zCd6?(ZZ^4F3HMVymI|rkDl5$0>~6|RzTej`u1l5JM%&`y z$LPhWeC>#Dh89+;p5`pXFIT)s5g~oG(g!IaOS7_t-tU6e-^VOGNUMUl z5!&;#wCJMF?@MTY+NM#6ywTkPTWtaITK6!iV&f+e6(Xbi43(RLX#OB6zQnC=vs3IT zCU>*Dj?fmSMH9m-WB49GD2gBio8O}a{>?@P*P5{8o$Yi|gBF7lq4`NVNce<7+X}&o z@rxbaNX<+&;HP`}y9s!@-%P!|jNmZec_0n8=5FkZW+6a=>;$N|7nnz=kuk@O^yUO( zjz+hz5TqQOq@=rgI*e$>mPa-vH?piWmB*dC#xD;|?O|%oFC!>hs8janr`fplctXj#XAa+U(MmB_ z5vx=vm~GwDhD6K9NCAhV!nKq#u`#!DJ98w=YaWP??zUO;YLXk>YwS=n39GK>!lo)! z&P`lQm2|MdlaxLP+{1hiG6o4k%`J8YDWc&EnWGkkmXOb1vKmr`#Mq{xT~RRtzw`5I zTKbwN7V*x$mZpUwtxQ9c_9HMEKbE#P2u{Rst-Ff_BN@Cz$5d7jKLc?;4e5Rcf_|F# zNnlBnk>;$9T&#r!76~1E)Yp|>r^O}>*AI5q;CgtM8e~xm_T@A^T694FNCy3#LQr1| z^%t`G(vObw=QVPTNt0aEzSV2VAF-Vbud~D?rLF}RVlYZCO1P{gVbbYu^uPTgO4O@Fl&=>qr_^)_#9|3bTg-LlkWlHNv;2aqx0t zu+3})|KHlnSFb0T{s=GRZExRiPv#QjvWCTBbs-h7~F$+ zQOqklccZV}gOYF;R?}8;mAb`rsY!GNkt=wZJRPG+tz%mka-_LENs*l?( z#ZB<*OBUY=Oj%j{exkMUAgR{g=)?4AJAW3N*k$$ix%aZ}q>8Crv|9DHSsw&ly}1+k zJJG|p()Lbb(v;N)`WNqJ*Er3KbT&Pn{;l-q)01h-bhVq&5N|<;<5!B^?R5A~ z5X^6;(Y@`~;g1=*cQHVI8_C@dlJ_#O2hpY;#;LO3eZrQ>L0dG3jf`f{!5(LVXPJ6A zhUS=B3DhOn$&-lKQ)suJbyvHOx@+9K-3Ip~?mg~(u&?WIBV3OLcn1sfX`B>~y1(Kc z!Jzt}TMv>yuarF zX??x*FIwMdeY5rJt>3@{@vmFoZvAHKw_5+E^>16>ZT(K`-?jcj>vvoKv30KXpIX1y z`Y)|NXnn8sUt9kTf5n@vKWhE=)*s{9da3n`tuMF!ZtE+p|J-_`_4}ATYn>3h=mrq`y|rSD6xPj5&!rc>#SX)E27-jv>) zZccAWZ%yBy-j?2;ejweF-kEMqx21QbccWW9dEVz3Hy>zI1oGC%r#?Al;kp zOCL-hN*_)?p6*XSk^V>;rITqnt)$iT!|6~uoYvBM`jK=b9ZlD!pG+T3KbYQ;K9YVY zeOG#AT1w+|O}Z{!kv7t+(v@jc-N>{?qZ^s(7AUtEt3guqh^icooq=yS}DTG=RS$V~^S}cxl!z+xm zQZ<#7UQfCx9;UzCtHWx)ywOE#*Fy^Hjqoxr$_ydqznQzZp6(wHccpJ8erLEp5bjF9CDgk$+~05a zJ6Yl$-fbc7@At6m-91Oa2OFg}vo|syPukxU@ji`A89$#TS0=+#+mUaHys&t; z+Yb$v7|su?Uxygq4-3`-u=>M-B}V$gf~8sh;lcXh;eG*;aY4UWTe7F*IrJ<3lUl`P zzN~yvtGLzzD@_(n;0gGR)|2`~&`6|#h~8{f!%Z%p^GO7eyXt9HsA@|a_WI27+858U z+E-?^y{B8=?;@YNFxAFFkG!oaF6A}(!bn%%$H>I5Jr3mQ`zUd@@?INE{p9@nGd+L955d1B%uhx6D1<)#N>hX!J8A8X!ceUQ#5yW-CGM9We>`krXw zZt~`VEoXxEZB|d>M8YZJoe1B9bKxYJ*bAf!N{vgq_4^NkzY(g6E=W5}ns;sW+c?B5 z91bnW%?2jN?FPXl|Jl-0HX!Z{qq$Q-`HsT8^ zJANpH9t-_pT;vKOuqO_M&|}0sEIge!qNc(567pMf3cPF7Q(<2t)M&Cj3%N9v3T8&+ zH}GX#c@`&>#w$7(UL>F8sH1iCY#-W)yw=!fpd8r}(RgN!_IFC6rccw12s-;SvDbrK zbr-W6Rk!LoB3GmO)izD(;e0I7$}OY{-`<_zM((x#%4h4?V-TMaH*%NDzLl?J8hHKm zXysOVs96(iMp?CWcwJ^!%Ptz+OYl}vW|6zmJvKto0)1zXweO*FBjN_41p0ytPrC@( zdzJS~apBFo@Z|XW1f`fUqZyCojVSO+i$-_Y*pE8lw?U&6 zR}j@n=kvaZ!Tl3gy%XKgt(F7iDM|^ivh;E&P^9dmI}MVYv~IQMK;E~mk;;;jp5d-~ zyBxKS{Zy3}D&M*|i;Gq3Fn6zQ`unyJ2TN7Csa?6{+>QLaJ)@O}P`uOA-VAF}Jj(Y& z{tjuyvRB=N=FvM<{S4W@iS1&D3O)bSY+Cx&f-cX=EOaOT!$XAoJIjoiePUs9Fg@FW z*Q^M?2J1%I*P0Wx{|?G;eEagEUD2zbJ%i4UQnpJzmA5hlZoN}}7wtl|6BjGx%19pf zcxT6$Dlp`_s2Mr|VLekDpe z&r-1zPb8feqGYZz>Ba?VjS{*f)F+)~jU#KQ1u&O%e{D#>y0215Aa?kBb6)`2tpK4G zPnM$O^x7ItuAxt*sJ4cnT}lc?p^b4z_0t<7yLVpWPD|s|C-piX^Vbq30JGNTb-z#Q zkic36u2f(Q#GFe`Z&F83-@>(d`u5K0cw5r>g=BMcRVi6&?oUpwZSHSA9fsKKoK4zE z=ebg8qS<*qic8tY;3aeX&8WbOx_m+R&KFC~&Pz$#3f7a(mq(J$SCYQ1PEE-${NLNa(ZX;>5(z!*v7WbRfn13*EKJ%l+-uywdRV|V|jik zX8G$}sY3C&@^I96*BIDHPTwD`)AV=k28eZT9X6ps0?D1{)U(b@^m=s2MWv+kCc%b{ z3ycj>^R=i^b)|Zv#!QuxwhiU<%nDbjj*T==&y2-mo;;_8m+1;XV#XOIz5UH;Dvnr1 zurwm!4syGkjp#?`uSkpYjmD6)PNo^WMa&SOT_h7xE_xB(?e zyLtM#(vYF2RvT(X5d#Kw&EC}vrlI+!Yf)n?t}*FJ=j)o#ZveH%ES!&;oww?f#F7~% z0Ys3enXLO@eI>Ozuk!tF3HmFQs&Sd?$Kq8<=SzkndI3H1UrIJ7H~3(4Td7ndXtZIs zb5H~^-9)H`=&1A44aJ;(sp==2LQE9bQK(^UK8s9nbAR2Yn-#EsNJXGW03$JJzWsoAA(w)CQrc!-3g ztER@|k>m!#bpeN?bVoxeTLMH^0H)hax)UPKcrt39ejO6k{T%lg0*;fm)^$7@3#B%w zW_Puge0O!ywk9`0Ka~|I%bJ97#}Y5cFm8x2X%ENHc6V*L#wQDm#&OcUI_YkpQ!(Bf zI=BIX?7RsL626*+Q}YGjAfDmClVK}!s~B7NI^%ukB0t%2NhOG}Xrq-;t)5MHJeK#! z3XHpu1=p;@^^)$!I4&vV5ma+!`kTy@nC=(~yF0$7l-vOBYR&E~!d^o-?A|U^8bD3l zIp)KzwUY3ibnj?(w{T~rcitp=SBWX3#Pd?|3l>F!M0TjRB$p0r&__kL~<@`Z+(#u8Y2)OQCgF}u(~DfF)dfsu7SswdrP zRvQxWe!D(s*ZuI(nxDdRehNMH)mdj{IB9ZbBV6pPm7V^0d|SOf;v1?4TqNCgvpZ9- z*Q(9#EHg#JC8IiKm;e`2=!5872&L2ri?2Ip}U-AofSB~ zC7UoNMNA@qtxuMrzwu}s-UWyIN&5UeEcsdg4+}Xnr)U#FVObIuBn9Q+*?6)7jy$jN z^=uSk`OnhCi@GBa2uk_V^bjCTnxnE=g;_B=oOFIw(bug63a)~vZW6f?15#Ih)pSn! zh$^QF-?Y?kj;ixNC$SNgz$)cuvB42K0+I&PBGa;I`c_ zGr_PVV@-a6ev7u?vB(nxVe(CaOUh(~%;yTz_F@D;p4%{e@K*9ljT%zdIC=NWBc54W zJLn@1(#@f<$RV&Kd#7RJHNK6~P(#z#jaI}E9^@e6dvg>$?#%P>IWk0}2*oqcn_xiA zfn8P6RJw0M8^(;zv(TH}^Hd!cqdoIwMH)z$TvjG+lSXf8de>+Grg5J6ik85fA@@75 ze9cDNko=vG##bbrZ>!rZ!{!RO-&^P;&`Mj&gD(C6T=tMco0H9X0Od9hLr&7Gx^KY_ zO!h-KLhCE6uQ8*r2m3mu;V0Q#_3csoHPx(nwDn$~9_Z`6U_7iY>}LvPqtOsm(<^#p zghrn1=w+?WGcUtY&`m4UZAdAN8jYHyF_Pt3lLYOg_jSX`ZyPRt zo8_wEcb=8-d6t=O#tbE}8`Wz|uF;9cyW$lwDqbKk&wN;o!q|wY7W4_k$~cS^f)s{@ zm+w=bJC+Vg0o>*r@MKt)g1@HAw}z3=@FG6d;edf=Z$scly$zB(#34m!;~GsSa)OWD ztGZEJ|1wSVfv7;*oF6fP_0mIMttOrCCcQfU;#+r7&aP6PtV$L6vNgUDO;=gHQ36S? zS*p;bI*VmUHej)6{LBO@033S^z^Ia9coGFeamCmxt0k^iSo|?H ztM%&p;-(>zQm8g-W35;MM%SJ}m^wxhVneiL4a72yq2AcozSk7RAk7#mA>!keD%|;^ zht@n>ABeM*r_}Dh*MtL!3zYCh-)Lyn*UY+LBoZS!#gCxR-r5ohPoVXsl`#`k#V+Ar z600WDrv|?VRS3Gc)SRv{<~UwyT@-KhnquONyJH3BNwN%VjO$$U?A5fz3|(ECh*!$C z(X0k#gNfV-)znFrL_fP>bG*LU`2)mT(%TfQWr{HMqxza4rw$69J_y!f-MvmQ3WVhB zb&av8f*vCMhnVS3y-jExs36V#W1tk7cA5df;=6Oz`Y^K;8iIG$Z;B@&!K8OPt=s{B zNqXpqgpF)_JF7`=ms(Vx^pQdV-3>`JN@jpf<^YCTg~&>=ltHRqfzsc*%jQB0_<7V4 zA6dZ~_aX8ngwO7hW{H(=i{O4*&^&d%Aj6@O^d7``wJ8JMOEe<(Jx$sbn1Lvmy+@PY zH0&D{A?eAM6`PNevk%fe-AZCgy_p)87h2lc{c)8ciyxeQRO{p{^GmpG;Ta3hT6j(l zm*ngchOq9ZjE9_k+9F+%Mj12+1#)IIO=n1Yv!)>aQhJ#8{v&Y7gw)#}l#kV9Wd6jAlMnoaQsX3n@ z9PU+2bdeJbYb+SUXXx1Lw%8kZE3zge3#>7?lf-QA`5{-9z!L8d8piOj?$`CaJbuOi`w@kKW#hwo=)Hg)PTc<+yGE0fSRzaCo{t*7Yu=mm zeUr=L1}}*tHyY#M-3xFvAwk}G9n~i3zHC!nYKTa?FXKXC%(=gSJk45TWn39Z_0*|kkO2s0j1NV< z5l&P|4awAQl;gQ0Wu^fcK=!7!^}Z^-qW4v1w*kF>Rr*5rEf#|DJ|y;9xDqZ9vEowc zvUpe{ZXI)?RxghUcfX;SDVj`HmuOPzM2%I&AO;EOvO6W@1ZnW zvh82?9Yc8*mA?$V$mHRMN37b2`7k~SI(md8#7MZp>=4OH8#&7*+P9IhFi~O$g8td zl}2r{j0#MIRm7E9e&xNW44xh2^b56wR+Bspr-biSc?~0cy9$lV;cT7%mFxf$$PuyS zQnCa21T~!f3P!_9=+;)V#bvXb(I=0FHv^YgUQDT|nCc^P2LxR!2@c0zT~5auovg(U zUh)H&Eu+B~Nns>m=W8pB>m%!cR2zmTCcUqf;MjD+0?;U4_p$M)E}xT6iYuVyQQV{i zsO6`p3Z?<`yUhmbQPIu3WwaY?<7ek+h&$0-!P9a1*>gW=>dE|4nD{%HV zfl~*K3Yl4^l(tE)C7k2dE1XY~l+Pj0W*P|Z&Yu8B1eVOKGTaT)D6)@5jq@K(snJ+% zY>4G-g;3AAW3?iUF~KYip4L43dJ?RdU}QFcK>wh9)7q^HGL6^-DvG9XrpQj-8buV( z_*KR%(MzKi4H*?dXi?u)rtj;lfTbCOIb$O6wZ@2s`Fhejm-JrK(mDHvgj}P6rO7(Q zB=4QG%^V9^fj7-ku&md#mX-6GEWr#6*azkt#+8*vOHe}t9?L9AOM-Agx#)mf6Wzrm zm3oO(k`iSW3;{Fg0xz(exUq^{!5kQ-a^G`^S<~mGAxU32&%XxPMT>irFx`dx35%z4 z)p);e4C8!F!XKOWyRrv+=XHGsqH${}XYLMI2-dOZ0|c&&SFk={i_U5jHqLpDR_R z@bg~fAWS8AoWJ6-ye~!v9BUX(--11dZ7ItK?`Q#Y0K(zeTqUQYL>t2x{ye%j>f@jFe`3540j#l&27+-xt@E@spQ)$F|@k^9W(YGY{u z1zu_qk?>+uOA3aZ2Q~8}C|RVXlu_vg0#=hb2Tf*0t@1keZmGM{3xMQT?_7vzN+2tt z)FVa$=86l(Ole^U4u**&Ru?#Axs3W!u4QbrTtk>i#~l|lYc!u(O>0PPSzR7x;VMd_6_$yH3no`gmNf)9LQouH zX3&%;NjG|HE!Sq^8z@c9Y;z#SJY;)pn!XfM5d&@USPf&Xrkedn?|y;KIKT!z zjDcyWxOU)u{G+hJ{vjT}yrnIaht@VE#0BSFu1ds*i&sQ~wjnSF*_(x@VSw54=)N`L z3_0}KjJ)J@y2KA!RkbkFp8+G)tB!+LJ6&@@>D!9$TT6R<#S7vHxarv$-v+leC zDjY!5@}3Qe7cyPUV53IMYhvF5iY|csReZ}@8AAR(b4yLS!85lLS^=gpR`@55<=pIk zXsvW)ELFUF5CTm|sgZrb+KFQ@!iTQEFCH;v#5V>?{IJCM&*3$qIW!fYSS+{(YVW>0 z(L6mGG6^y8#!?>eSZSHtr08v!{ZH%S5hE}?7_*S}auosXQ&Rgz&mvxqO-=IYxk4a&Z!b55M z7jLmB~qcU4u7Eb?az#-zq#@C`zstEVvkRk zk}lALt0=L8R^>_`aHY>Wc4SFDbw6f_)%@sPnU+2zJl*6zH7=u$qjs4 zd}nY_bzZ5+7}sUgw((4yq4Fwa$t*w2&(aW%Ej)asp$agj;kLkB%l30wdjMu`0IvF8AuPM~6nd zi&WkIyhZVn;?GjXE3%XJheV`kmdG|BJ)7k_r^Z-3tQRSjES-?RVJZN=YBA_;PH6D^ zUP}KN4d9$Y&sv79CTj;`?L14@Ixnfn8(A)Ns}4y_s@88P-IE#VehWwC8FJG3ovfSe z4_GbvuS0g)V`+B1@Z-FS2_@sETGoY)zKgmP5by z17(`RqRh=%D~&1y#aTd+orB^^SYiA9r2sm4fF z)ftW^;2fMybUukPM4GG6btq~P3!oOgiRA)Zg_>@N`A`+ryn%N8lEghJ+sw{r$fT_J zQdD3s*A{IuV|#W2f(a<6Ct#Z0O)wpH0j<3HLV5Yh;a0miF`(#}GVLe=JP1XnZk@%k ztV}6rQkpb)LA3K96h9@Ev2&JxwKv;v+EC`dVjuY*!(K7EWgbRB5n5n=I{r4B%wf>v zu`%h$Vys#AM8*{#NtM68ntFvp$1`AS!$`AL}eo z=a?M>PGs0@BG^^ZhHL~b*H^78(;`->wJ~uOx1%fAR4x!*bJzO(9QBljDF}4 z8@_CuY+_3M%n@a)vMx`W-~6FSscBPxwJE~*Y$yzBL@?-|a-v`Xco(;*F;GBnqxQ-m z7?P|_*_5#?Q+5`H1EHZ9OIHnmV}HKpJe({GnQN!ScFiBiZfWU)kAf3`uVj0YXd5eW znHlbzy%$i1+3GX@(&gQvB&? za)Yhn%~dg;WY(^m4WDdN4^kHJ?=kRfJnhP7i zjihDQ8D&v8x1^ycO^TdMn2z(;5gI z^2U%lDdDVp(g41hbQ;YzsytCy2G` z00raEO-mR80I*uYYu#r<>UGkLkKmn>g;N_Q>dioZA|5kMjBZUvw`)1kxgKLZ2qr zDiUq3g8!;a(4R@=V9X?=s+AcW-$tc|at5;eB2w&8gctf3nedk&#VXfICUBXlazQ3s ztNxg7RSUsJ`C1NPT+4ye$gZO*6Brpau_3N?vE?igYN(oWt3+yitClrKsG`!-T-aRW z5_5sq*L+Wu#4`?D+7zv;QtwrwXq2G@EUD(=;VjmDvr=OX=(3+0Ybw|3S%qt5;HesM z&Pj109yu5%pLJ4V_mwVtcz*gMzx18|Of)}La^lH_h;p$hxOYVEimChV+NT#T^J3u} z9`f&%yyJ=6r&<7Lth#%icO~9VDZH0G{G5+-!;!1nPu=mP zQRIeix@Bt9rm0Q0PVr>TyCOG{#jn5VmQ9;B-Fnlen{VB$N0uX3YV#1%2P1dug;1vo z@A|y{L%b_<;Kx}$eCV;q-p^yv_eO5}JE&`~|I}|)EPHXYz1Uf>Hf^G>w?*#eKMh!T z+PU=4Yf5&XE9hX zl9^clVG3k3%akE)$Pzb1GHBc=Zv&x{ro51oM$B2=SLNu%wmK0&lbu#$J=b@w;emS zi(d(LvkM35Uwi7%F`nVGPr3mphK?Jfo1#{(Nk&!{z|4*Xxw8Mr9N^8m4$f z9kqizY%~#9TA6;7qM<4sj-nyBB2}uiNI9wwadJ>YD$zVc2bo;TRn#z{3Z~AUv)oRc zZf9^RqMqUtU~ih$P{H*cF|M+5DkL*9O5<$CVWq`F3TJbNBKn2yQf47CNIrB4ER@+@ z=4m*5H06q+ktP|c&Pdxw;1DHrj~>uDTGEY1HBI2MTCZ2Az%h6}s#~t;6m%VbG<9m2 z4H`AQg%=17(Lc7ZS$S%qn%TL2bC3>i;@hH98qppWLP9!=vlrmtn%gF+)Js@HggD^L%)3|T}b z0tRceRaXZq&^*Yb!4g`9nyU!O8mZuLjDUVJ?cji`hhuj-quP)$RWzIms1(QL!qx*_ z0R3bVta7Og0jW~uEH;P&HENo%rC!&eODvz{n1e5}3?dtiiVSbgt*^PVgN z9|op^;UntLhiMQ3I4cs$2^>LfiC^i~xDC8Q=_co2<>X)3q*pWXu?|>$E{b2I|0?&F zpT7VYh)mhI!nw}iV9gWG8;UMWSGgU0KmH3P_drIJ#2+Z5$h(K!US8Stkh{xX3bvCM zgxyD6?@zm%us&a}{L%jrha7RMiH+s=k612JkGtr-_}NAz0;=8!aW_xl>kR}uc@|!8 zL(u2lR;&LqRK3R_ds^QLxL>aPBY;Nq&c7K#??Si`(vMjgz17P9ZFUEQ14@olH+%nq z-dn(LJJ7}~{m|!PPr+aCtUTrJGdNkhdMCkx(>p)(rix;J-zmIX!CsV)<00Vn-zA`a z-Q;fJ^=O;;)Ad%a8jbpKIrW4=J@v0Qd+OZ^{Sm#B_NRFM-`<-6JZxv+_1~8uOq~Qr z8kycA$a@mpB;SXu-Q6KwZ=_JW89o&A82z)h@8?Hv#L!y_^fnWIg(!#i&gW;3yNq!R z!dZ^h Date: Tue, 2 Dec 2025 11:52:47 +0800 Subject: [PATCH 162/171] Refactor trade legality checks to use LegalityAnalysis MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updated trade modules and helpers to use LegalityAnalysis for legality validation and to pass EncounterOriginal to CanBeTraded. Improved error handling and messaging for invalid or blocked Pokémon trades. Added new tests for legality checks in TranslatorTests. --- SysBot.Pokemon.Bilibili/BilibiliLiveBot.cs | 32 +++++++------- .../Commands/Bots/TradeModule.cs | 18 ++++---- SysBot.Pokemon/Helpers/AbstractTrade.cs | 29 +++++++------ SysBot.Tests/TranslatorTests.cs | 43 ++++++++++++++++++- 4 files changed, 82 insertions(+), 40 deletions(-) diff --git a/SysBot.Pokemon.Bilibili/BilibiliLiveBot.cs b/SysBot.Pokemon.Bilibili/BilibiliLiveBot.cs index 2d5ec6a12..206d877b9 100644 --- a/SysBot.Pokemon.Bilibili/BilibiliLiveBot.cs +++ b/SysBot.Pokemon.Bilibili/BilibiliLiveBot.cs @@ -103,29 +103,27 @@ public static bool CheckAndGetPkm(string setstring, string username, out string var sav = AutoLegalityWrapper.GetTrainerInfo(); var pkm = sav.GetLegal(template, out var result); - if (!pkm.CanBeTraded()) + if (pkm == null) { - msg = $"Skipping trade, @{username}: Provided Pokemon content is blocked from trading!"; + msg = $"Skipping trade, @{username}: Unkown Pokemon data."; return false; } - - if (pkm is T pk) + var la = new LegalityAnalysis(pkm); + if (!la.Valid) { - var valid = new LegalityAnalysis(pkm).Valid; - if (valid) - { - outPkm = pk; - - msg = - $"@{username} - added to the waiting list. Your request from the waiting list will be removed if you are too slow!"; - return true; - } + msg = $"Skipping trade, @{username}: Unable to legalize the Pokemon."; + return false; } + if (!pkm.CanBeTraded(la.EncounterOriginal)) + { + msg = $"Skipping trade, @{username}: Provided Pokemon content is blocked from trading!"; + return false; + } + + outPkm = (T)pkm; - var reason = result == "Timeout" - ? "Set took too long to generate." - : "Unable to legalize the Pokemon."; - msg = $"Skipping trade, @{username}: {reason}"; + msg = $"@{username} - added to the waiting list. Your request from the waiting list will be removed if you are too slow!"; + return true; } #pragma warning disable CA1031 // Do not catch general exception types catch (Exception ex) diff --git a/SysBot.Pokemon.Kook/Commands/Bots/TradeModule.cs b/SysBot.Pokemon.Kook/Commands/Bots/TradeModule.cs index 948a115e1..446509bf1 100644 --- a/SysBot.Pokemon.Kook/Commands/Bots/TradeModule.cs +++ b/SysBot.Pokemon.Kook/Commands/Bots/TradeModule.cs @@ -220,14 +220,6 @@ private async Task TradeAsyncAttach(int code, RequestSignificance sig, SocketUse private async Task AddTradeToQueueAsync(int code, string trainerName, T pk, RequestSignificance sig, SocketUser usr) { - if (!pk.CanBeTraded()) - { - // Disallow anything that cannot be traded from the game (e.g. Fusions). - await ReplyTextAsync("Provided Pokémon content is blocked from trading!").ConfigureAwait(false); - return; - } - - var cfg = Info.Hub.Config.Trade; var la = new LegalityAnalysis(pk); if (!la.Valid) { @@ -235,7 +227,15 @@ private async Task AddTradeToQueueAsync(int code, string trainerName, T pk, Requ await ReplyTextAsync($"{typeof(T).Name} attachment is not legal, and cannot be traded!").ConfigureAwait(false); return; } - if (cfg.DisallowNonNatives && (la.EncounterOriginal.Context != pk.Context || pk.GO)) + var enc = la.EncounterOriginal; + if (!pk.CanBeTraded(enc)) + { + // Disallow anything that cannot be traded from the game (e.g. Fusions). + await ReplyTextAsync("Provided Pokémon content is blocked from trading!").ConfigureAwait(false); + return; + } + var cfg = Info.Hub.Config.Trade; + if (cfg.DisallowNonNatives && (enc.Context != pk.Context || pk.GO)) { // Allow the owner to prevent trading entities that require a HOME Tracker even if the file has one already. await ReplyTextAsync($"{typeof(T).Name} attachment is not native, and cannot be traded!").ConfigureAwait(false); diff --git a/SysBot.Pokemon/Helpers/AbstractTrade.cs b/SysBot.Pokemon/Helpers/AbstractTrade.cs index 8a36fe16c..083f163a5 100644 --- a/SysBot.Pokemon/Helpers/AbstractTrade.cs +++ b/SysBot.Pokemon/Helpers/AbstractTrade.cs @@ -209,25 +209,28 @@ public bool Check(T pkm, out string msg) { try { - if (!pkm.CanBeTraded()) + if (pkm is null) { - msg = $"取消派送, 官方禁止该宝可梦交易!"; + msg = $"取消派送, 宝可梦数据有误!"; return false; } - if (pkm is T pk) + var la = new LegalityAnalysis(pkm); + var valid = la.Valid; + if (!valid) { - var la = new LegalityAnalysis(pkm); - var valid = la.Valid; - if (valid) - { - msg = $"已加入等待队列. 如果你选宝可梦的速度太慢,你的派送请求将被取消!"; - return true; - } LogUtil.LogInfo($"非法原因:\n{la.Report()}", nameof(AbstractTrade)); + msg = $"取消派送, 宝可梦不合法!\n原因:\n{la.Report()}"; + return false; } - LogUtil.LogInfo($"pkm type:{pkm.GetType()}, T:{typeof(T)}", nameof(AbstractTrade)); - var reason = "我没办法创造非法宝可梦"; - msg = $"{reason}"; + var enc = la.EncounterOriginal; + if (!pkm.CanBeTraded(enc)) + { + msg = $"取消派送, 官方禁止该宝可梦交易!"; + return false; + } + + msg = $"已加入等待队列. 如果你选宝可梦的速度太慢,你的派送请求将被取消!"; + return true; } catch (Exception ex) { diff --git a/SysBot.Tests/TranslatorTests.cs b/SysBot.Tests/TranslatorTests.cs index f4accc1fc..741294a9c 100644 --- a/SysBot.Tests/TranslatorTests.cs +++ b/SysBot.Tests/TranslatorTests.cs @@ -3,6 +3,7 @@ using SysBot.Pokemon; using SysBot.Pokemon.Helpers; using System.Diagnostics; +using System.IO; using Xunit; namespace SysBot.Tests; @@ -46,12 +47,52 @@ public void TestLegal(string input) if (pkm.Nickname.ToLower() == "egg" && Breeding.CanHatchAsEgg(pkm.Species)) AbstractTrade.EggTrade(pkm, template); - pkm.CanBeTraded().Should().BeTrue(); + (pkm is PK9).Should().BeTrue(); var la = new LegalityAnalysis(pkm); if (!la.Valid) Trace.WriteLine(la.Report()); + pkm.CanBeTraded(la.EncounterOriginal).Should().BeTrue(); la.Valid.Should().BeTrue(); } + [Theory] + [InlineData("梦境球皮卡丘")] + public void TestLegalZA(string input) + { + var setstring = ShowdownTranslator.Chinese2Showdown(input); + var set = ShowdownUtil.ConvertToShowdown(setstring); + set.Should().NotBeNull(); + var template = AutoLegalityWrapper.GetTemplate(set); + template.Species.Should().BeGreaterThan(0); + var sav = AutoLegalityWrapper.GetTrainerInfo(); + var pkm = sav.GetLegal(template, out var result); + Trace.WriteLine(result.ToString()); + + if (pkm.Nickname.ToLower() == "egg" && Breeding.CanHatchAsEgg(pkm.Species)) AbstractTrade.EggTrade(pkm, template); + + (pkm is PA9).Should().BeTrue(); + var la = new LegalityAnalysis(pkm); + if (!la.Valid) + Trace.WriteLine(la.Report()); + pkm.CanBeTraded(la.EncounterOriginal).Should().BeTrue(); + la.Valid.Should().BeTrue(); + } + + [Theory] + [InlineData("C:\\Users\\easyworld\\Downloads\\boxdata Box 2.bin")] + public void TestLegalZAFile(string file) + { + var bytes = File.ReadAllBytes(file); + FileTradeHelper.Bin2List(bytes).ForEach(pkm => + { + pkm.Should().NotBeNull(); + (pkm is PA9).Should().BeTrue(); + var la = new LegalityAnalysis(pkm); + if (!la.Valid) + Trace.WriteLine(la.Report()); + pkm.CanBeTraded(la.EncounterOriginal).Should().BeTrue(); + la.Valid.Should().BeTrue(); + }); + } } From 82aba03a11fd6f19555614c49702ae358fe21ba5 Mon Sep 17 00:00:00 2001 From: Easy World Date: Wed, 17 Dec 2025 21:13:47 +0800 Subject: [PATCH 163/171] Fix category attribute for MaxPkmsPerTrade setting Corrected the Category attribute for the MaxPkmsPerTrade property from 'TradeCode' to 'TradeConfig' to ensure proper grouping in configuration UIs. --- SysBot.Pokemon/Settings/TradeSettings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SysBot.Pokemon/Settings/TradeSettings.cs b/SysBot.Pokemon/Settings/TradeSettings.cs index d07411c77..1269ffb7f 100644 --- a/SysBot.Pokemon/Settings/TradeSettings.cs +++ b/SysBot.Pokemon/Settings/TradeSettings.cs @@ -38,7 +38,7 @@ public class TradeSettings : IBotStateSettings, ICountSettings [Category(TradeConfig), Description("When enabled, the screen will be turned off during normal bot loop operation to save power.")] public bool ScreenOff { get; set; } - [Category(TradeCode), Description("Maximum pokemons of single trade. Batch mode will be closed if this configuration is less than 1")] + [Category(TradeConfig), Description("Maximum pokemons of single trade. Batch mode will be closed if this configuration is less than 1")] public int MaxPkmsPerTrade { get; set; } = 1; [Category(TradeConfig), Description("When enabled, disallows requesting Pokémon from outside of their original context.")] From c9491fffac25d8b2d0a5751323562cb286e50750 Mon Sep 17 00:00:00 2001 From: Easy World Date: Sat, 10 Jan 2026 22:59:27 +0800 Subject: [PATCH 164/171] Add new projects and update Kook project dependencies Added SysBot.Pokemon.QQ, SysBot.Pokemon.Bilibili, SysBot.Pokemon.Dodo, and SysBot.Pokemon.Kook projects to the solution. Updated Kook.Net and Microsoft.Extensions.* package versions in SysBot.Pokemon.Kook.csproj. --- SysBot.NET.slnx | 4 ++++ SysBot.Pokemon.Kook/SysBot.Pokemon.Kook.csproj | 7 +++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/SysBot.NET.slnx b/SysBot.NET.slnx index 888a1bdcd..5f5b54683 100644 --- a/SysBot.NET.slnx +++ b/SysBot.NET.slnx @@ -6,6 +6,10 @@ + + + + diff --git a/SysBot.Pokemon.Kook/SysBot.Pokemon.Kook.csproj b/SysBot.Pokemon.Kook/SysBot.Pokemon.Kook.csproj index 17fb0e6e0..bf2c833a1 100644 --- a/SysBot.Pokemon.Kook/SysBot.Pokemon.Kook.csproj +++ b/SysBot.Pokemon.Kook/SysBot.Pokemon.Kook.csproj @@ -1,15 +1,14 @@  - net9.0 enable enable - - - + + + From 4ec959894d3f14c689d980f07ee0b5bd76d2ed6e Mon Sep 17 00:00:00 2001 From: Easy World Date: Sun, 11 Jan 2026 09:46:56 +0800 Subject: [PATCH 165/171] Refactor Kook command modules and update features Removed all Kook command modules from the Commands directory and introduced new module scaffolding under SysBot.Pokemon.Kook/Modules. Updated DodoTradeNotifier to fix IV reporting. Updated README with new Kook features and documentation links. Adjusted project files and settings to reflect the new modular structure. --- README.md | 8 +- SysBot.Pokemon.Dodo/DodoTradeNotifier.cs | 4 +- .../Commands/Bots/CloneModule.cs | 59 ---- .../Commands/Bots/DumpModule.cs | 59 ---- .../Commands/Bots/QueueModule.cs | 146 ---------- .../Commands/Bots/RemoteControlModule.cs | 145 ---------- .../Commands/Bots/SeedCheckModule.cs | 84 ------ .../Commands/Bots/TradeModule.cs | 253 ------------------ .../Commands/Extra/BatchEditingModule.cs | 54 ---- .../Commands/Extra/LegalityCheckModule.cs | 49 ---- .../Commands/Extra/LegalizerModule.cs | 33 --- .../Commands/General/HelloModule.cs | 18 -- .../Commands/General/HelpModule.cs | 102 ------- .../Commands/General/InfoModule.cs | 79 ------ .../Commands/General/PingModule.cs | 14 - .../Commands/Management/BotModule.cs | 124 --------- .../Commands/Management/EchoModule.cs | 119 -------- .../Commands/Management/EncounterModule.cs | 27 -- .../Commands/Management/HubModule.cs | 87 ------ .../Commands/Management/LogModule.cs | 104 ------- .../Commands/Management/OwnerModule.cs | 128 --------- .../Commands/Management/PoolModule.cs | 51 ---- .../Commands/Management/SudoModule.cs | 205 -------------- .../Commands/Management/TradeStartModule.cs | 124 --------- .../Helpers/AutoLegalityExtensionsKook.cs | 96 ------- SysBot.Pokemon.Kook/Helpers/ChannelAction.cs | 10 - SysBot.Pokemon.Kook/Helpers/ChannelLogger.cs | 26 -- SysBot.Pokemon.Kook/Helpers/KookManager.cs | 59 ---- .../Helpers/KookTradeNotifier.cs | 86 ------ SysBot.Pokemon.Kook/Helpers/NetUtil.cs | 157 ----------- SysBot.Pokemon.Kook/Helpers/QueueHelper.cs | 141 ---------- .../Helpers/RequireOwnerAttribute.cs | 21 -- .../Helpers/RequireQueueRoleAttribute.cs | 41 --- .../Helpers/RequireRoleAccessAttribute.cs | 37 --- .../Helpers/RequireRoleAttribute.cs | 31 --- .../Helpers/RequireSudoAttribute.cs | 28 -- .../Helpers/ReusableActions.cs | 88 ------ SysBot.Pokemon.Kook/KookBot.cs | 239 +++++------------ SysBot.Pokemon.Kook/KookMessageUtil.cs | 35 +++ SysBot.Pokemon.Kook/KookTrade.cs | 25 ++ SysBot.Pokemon.Kook/KookTradeNotifier.cs | 115 ++++++++ SysBot.Pokemon.Kook/Modules/AliveModule.cs | 26 ++ SysBot.Pokemon.Kook/Modules/CommandModule.cs | 47 ++++ SysBot.Pokemon.Kook/Modules/FileModule.cs | 53 ++++ SysBot.Pokemon.Kook/Modules/IModule.cs | 24 ++ SysBot.Pokemon.Kook/Modules/ModuleScaffold.cs | 27 ++ SysBot.Pokemon.Kook/Modules/PsModule.cs | 81 ++++++ .../SysBot.Pokemon.Kook.csproj | 7 - SysBot.Pokemon.QQ/MiraiQQTradeNotifier.cs | 4 +- .../Settings/Integrations/KookSettings.cs | 79 +----- 50 files changed, 516 insertions(+), 3143 deletions(-) delete mode 100644 SysBot.Pokemon.Kook/Commands/Bots/CloneModule.cs delete mode 100644 SysBot.Pokemon.Kook/Commands/Bots/DumpModule.cs delete mode 100644 SysBot.Pokemon.Kook/Commands/Bots/QueueModule.cs delete mode 100644 SysBot.Pokemon.Kook/Commands/Bots/RemoteControlModule.cs delete mode 100644 SysBot.Pokemon.Kook/Commands/Bots/SeedCheckModule.cs delete mode 100644 SysBot.Pokemon.Kook/Commands/Bots/TradeModule.cs delete mode 100644 SysBot.Pokemon.Kook/Commands/Extra/BatchEditingModule.cs delete mode 100644 SysBot.Pokemon.Kook/Commands/Extra/LegalityCheckModule.cs delete mode 100644 SysBot.Pokemon.Kook/Commands/Extra/LegalizerModule.cs delete mode 100644 SysBot.Pokemon.Kook/Commands/General/HelloModule.cs delete mode 100644 SysBot.Pokemon.Kook/Commands/General/HelpModule.cs delete mode 100644 SysBot.Pokemon.Kook/Commands/General/InfoModule.cs delete mode 100644 SysBot.Pokemon.Kook/Commands/General/PingModule.cs delete mode 100644 SysBot.Pokemon.Kook/Commands/Management/BotModule.cs delete mode 100644 SysBot.Pokemon.Kook/Commands/Management/EchoModule.cs delete mode 100644 SysBot.Pokemon.Kook/Commands/Management/EncounterModule.cs delete mode 100644 SysBot.Pokemon.Kook/Commands/Management/HubModule.cs delete mode 100644 SysBot.Pokemon.Kook/Commands/Management/LogModule.cs delete mode 100644 SysBot.Pokemon.Kook/Commands/Management/OwnerModule.cs delete mode 100644 SysBot.Pokemon.Kook/Commands/Management/PoolModule.cs delete mode 100644 SysBot.Pokemon.Kook/Commands/Management/SudoModule.cs delete mode 100644 SysBot.Pokemon.Kook/Commands/Management/TradeStartModule.cs delete mode 100644 SysBot.Pokemon.Kook/Helpers/AutoLegalityExtensionsKook.cs delete mode 100644 SysBot.Pokemon.Kook/Helpers/ChannelAction.cs delete mode 100644 SysBot.Pokemon.Kook/Helpers/ChannelLogger.cs delete mode 100644 SysBot.Pokemon.Kook/Helpers/KookManager.cs delete mode 100644 SysBot.Pokemon.Kook/Helpers/KookTradeNotifier.cs delete mode 100644 SysBot.Pokemon.Kook/Helpers/NetUtil.cs delete mode 100644 SysBot.Pokemon.Kook/Helpers/QueueHelper.cs delete mode 100644 SysBot.Pokemon.Kook/Helpers/RequireOwnerAttribute.cs delete mode 100644 SysBot.Pokemon.Kook/Helpers/RequireQueueRoleAttribute.cs delete mode 100644 SysBot.Pokemon.Kook/Helpers/RequireRoleAccessAttribute.cs delete mode 100644 SysBot.Pokemon.Kook/Helpers/RequireRoleAttribute.cs delete mode 100644 SysBot.Pokemon.Kook/Helpers/RequireSudoAttribute.cs delete mode 100644 SysBot.Pokemon.Kook/Helpers/ReusableActions.cs create mode 100644 SysBot.Pokemon.Kook/KookMessageUtil.cs create mode 100644 SysBot.Pokemon.Kook/KookTrade.cs create mode 100644 SysBot.Pokemon.Kook/KookTradeNotifier.cs create mode 100644 SysBot.Pokemon.Kook/Modules/AliveModule.cs create mode 100644 SysBot.Pokemon.Kook/Modules/CommandModule.cs create mode 100644 SysBot.Pokemon.Kook/Modules/FileModule.cs create mode 100644 SysBot.Pokemon.Kook/Modules/IModule.cs create mode 100644 SysBot.Pokemon.Kook/Modules/ModuleScaffold.cs create mode 100644 SysBot.Pokemon.Kook/Modules/PsModule.cs diff --git a/README.md b/README.md index 14e386958..1341714dc 100644 --- a/README.md +++ b/README.md @@ -67,11 +67,13 @@ Document: [搭建指南](https://docs.qq.com/doc/DSVVZZk9saUNTeHNn), [命令指 ## SysBot.Pokemon.Kook: **SysBot.Pokemon.Kook** is a complete port of the `SysBot.Pokemon.Discord` project, fully compatible with the [Kook](https://www.kookapp.cn/) (formerly known as Kaiheila) platform. -- 100% feature parity with SysBot.Pokemon.Discord. -- Seamless integration with the Kook chat platform, supporting all bot commands and functionalities available on Discord. -- Allows Pokémon trading, file uploads (PK8, PB8, etc.), and advanced automation features. +- Support [ALM-Showdown-Sets](https://github.com/architdate/PKHeX-Plugins/wiki/ALM-Showdown-Sets) +- Support PK8 PB8 PA8 PK9 file upload +- Support [Customized Chinese](https://docs.qq.com/doc/DSVlldkxMSW92VXZF) to ALM-Showdown-Sets translation - Easily configurable and extensible for the Kook ecosystem. +Document: [搭建指南](https://docs.qq.com/doc/DSWpDQnBwSXRGSGR3), [命令指南](https://docs.qq.com/doc/DSVlldkxMSW92VXZF) + ## Other Dependencies Pokémon API logic is provided by [PKHeX](https://github.com/kwsch/PKHeX/), and template generation is provided by [Auto-Legality Mod](https://github.com/architdate/PKHeX-Plugins/). Current template generation uses [@santacrab2](https://www.github.com/santacrab2)'s [Auto-Legality Mod fork](https://github.com/santacrab2/PKHeX-Plugins). diff --git a/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs b/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs index 55024dbb8..c0df3c349 100644 --- a/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs +++ b/SysBot.Pokemon.Dodo/DodoTradeNotifier.cs @@ -128,8 +128,10 @@ public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail LogUtil.LogText(msg); if (result.Species != 0 && info.Type == PokeTradeType.Dump) { + Span ivs = stackalloc int[6]; + result.GetIVs(ivs); var text = - $"species:{result.Species}\npid:{result.PID}\nec:{result.EncryptionConstant}\nIVs:{string.Join(",", result.IVs)}\nisShiny:{result.IsShiny}"; + $"species:{result.Species}\npid:{result.PID}\nec:{result.EncryptionConstant}\nIVs:{string.Join(",", ivs.ToArray())}\nisShiny:{result.IsShiny}"; DodoBot.SendChannelMessage(text, ChannelId); } } diff --git a/SysBot.Pokemon.Kook/Commands/Bots/CloneModule.cs b/SysBot.Pokemon.Kook/Commands/Bots/CloneModule.cs deleted file mode 100644 index 87fbfbd70..000000000 --- a/SysBot.Pokemon.Kook/Commands/Bots/CloneModule.cs +++ /dev/null @@ -1,59 +0,0 @@ -using Kook; -using Kook.Commands; -using PKHeX.Core; -using System.Threading.Tasks; - -namespace SysBot.Pokemon.Kook; - -[Summary("Queues new Clone trades")] -public class CloneModule : ModuleBase where T : PKM, new() -{ - private static TradeQueueInfo Info => KookBot.Runner.Hub.Queues.Info; - - [Command("clone")] - [Alias("c")] - [Summary("Clones the Pokémon you show via Link Trade.")] - [RequireQueueRole(nameof(KookManager.RolesClone))] - public Task CloneAsync(int code) - { - var sig = Context.User.GetFavor(); - return QueueHelper.AddToQueueAsync(Context, code, Context.User.Username, sig, new T(), PokeRoutineType.Clone, PokeTradeType.Clone); - } - - [Command("clone")] - [Alias("c")] - [Summary("Clones the Pokémon you show via Link Trade.")] - [RequireQueueRole(nameof(KookManager.RolesClone))] - public Task CloneAsync([Summary("Trade Code")][Remainder] string code) - { - int tradeCode = Util.ToInt32(code); - var sig = Context.User.GetFavor(); - return QueueHelper.AddToQueueAsync(Context, tradeCode == 0 ? Info.GetRandomTradeCode() : tradeCode, Context.User.Username, sig, new T(), PokeRoutineType.Clone, PokeTradeType.Clone); - } - - [Command("clone")] - [Alias("c")] - [Summary("Clones the Pokémon you show via Link Trade.")] - [RequireQueueRole(nameof(KookManager.RolesClone))] - public Task CloneAsync() - { - var code = Info.GetRandomTradeCode(); - return CloneAsync(code); - } - - [Command("cloneList")] - [Alias("cl", "cq")] - [Summary("Prints the users in the Clone queue.")] - [RequireSudo] - public async Task GetListAsync() - { - string msg = Info.GetTradeList(PokeRoutineType.Clone); - - var card = new CardBuilder() - .AddModule(new SectionModuleBuilder().WithText("These are the users who are currently waiting:")) - .AddModule(new SectionModuleBuilder().WithText("Pending Trades")) - .AddModule(new SectionModuleBuilder().WithText(msg)) - .Build(); - await ReplyCardAsync(card).ConfigureAwait(false); - } -} diff --git a/SysBot.Pokemon.Kook/Commands/Bots/DumpModule.cs b/SysBot.Pokemon.Kook/Commands/Bots/DumpModule.cs deleted file mode 100644 index 2a30e2738..000000000 --- a/SysBot.Pokemon.Kook/Commands/Bots/DumpModule.cs +++ /dev/null @@ -1,59 +0,0 @@ -using Kook; -using Kook.Commands; -using PKHeX.Core; -using System.Threading.Tasks; - -namespace SysBot.Pokemon.Kook; - -[Summary("Queues new Dump trades")] -public class DumpModule : ModuleBase where T : PKM, new() -{ - private static TradeQueueInfo Info => KookBot.Runner.Hub.Queues.Info; - - [Command("dump")] - [Alias("d")] - [Summary("Dumps the Pokémon you show via Link Trade.")] - [RequireQueueRole(nameof(KookManager.RolesDump))] - public Task DumpAsync(int code) - { - var sig = Context.User.GetFavor(); - return QueueHelper.AddToQueueAsync(Context, code, Context.User.Username, sig, new T(), PokeRoutineType.Dump, PokeTradeType.Dump); - } - - [Command("dump")] - [Alias("d")] - [Summary("Dumps the Pokémon you show via Link Trade.")] - [RequireQueueRole(nameof(KookManager.RolesDump))] - public Task DumpAsync([Summary("Trade Code")][Remainder] string code) - { - int tradeCode = Util.ToInt32(code); - var sig = Context.User.GetFavor(); - return QueueHelper.AddToQueueAsync(Context, tradeCode == 0 ? Info.GetRandomTradeCode() : tradeCode, Context.User.Username, sig, new T(), PokeRoutineType.Dump, PokeTradeType.Dump); - } - - [Command("dump")] - [Alias("d")] - [Summary("Dumps the Pokémon you show via Link Trade.")] - [RequireQueueRole(nameof(KookManager.RolesDump))] - public Task DumpAsync() - { - var code = Info.GetRandomTradeCode(); - return DumpAsync(code); - } - - [Command("dumpList")] - [Alias("dl", "dq")] - [Summary("Prints the users in the Dump queue.")] - [RequireSudo] - public async Task GetListAsync() - { - string msg = Info.GetTradeList(PokeRoutineType.Dump); - - var card = new CardBuilder() - .AddModule(new SectionModuleBuilder().WithText("These are the users who are currently waiting:")) - .AddModule(new SectionModuleBuilder().WithText("Pending Trades")) - .AddModule(new SectionModuleBuilder().WithText(msg)) - .Build(); - await ReplyCardAsync(card).ConfigureAwait(false); - } -} diff --git a/SysBot.Pokemon.Kook/Commands/Bots/QueueModule.cs b/SysBot.Pokemon.Kook/Commands/Bots/QueueModule.cs deleted file mode 100644 index 541bbce34..000000000 --- a/SysBot.Pokemon.Kook/Commands/Bots/QueueModule.cs +++ /dev/null @@ -1,146 +0,0 @@ -using Kook; -using Kook.Commands; -using PKHeX.Core; -using System.Threading.Tasks; - -namespace SysBot.Pokemon.Kook; - -[Summary("Clears and toggles Queue features.")] -public class QueueModule : ModuleBase where T : PKM, new() -{ - private static TradeQueueInfo Info => KookBot.Runner.Hub.Queues.Info; - - [Command("queueStatus")] - [Alias("qs", "ts")] - [Summary("Checks the user's position in the queue.")] - public async Task GetTradePositionAsync() - { - var msg = $"{Context.User.KMarkdownMention}" + " - " + Info.GetPositionString(Context.User.Id); - await ReplyTextAsync(msg).ConfigureAwait(false); - } - - [Command("queueClear")] - [Alias("qc", "tc")] - [Summary("Clears the user from the trade queues. Will not remove a user if they are being processed.")] - public async Task ClearTradeAsync() - { - string msg = ClearTrade(); - await ReplyTextAsync(msg).ConfigureAwait(false); - } - - [Command("queueClearUser")] - [Alias("qcu", "tcu")] - [Summary("Clears the user from the trade queues. Will not remove a user if they are being processed.")] - [RequireSudo] - public async Task ClearTradeUserAsync([Summary("Kook user ID")] ulong id) - { - string msg = ClearTrade(id); - await ReplyTextAsync(msg).ConfigureAwait(false); - } - - [Command("queueClearUser")] - [Alias("qcu", "tcu")] - [Summary("Clears the user from the trade queues. Will not remove a user if they are being processed.")] - [RequireSudo] - public async Task ClearTradeUserAsync([Summary("Username of the person to clear")] string _) - { - foreach (var user in Context.Message.MentionedUsers) - { - string msg = ClearTrade(user.Id); - await ReplyTextAsync(msg).ConfigureAwait(false); - } - } - - [Command("queueClearUser")] - [Alias("qcu", "tcu")] - [Summary("Clears the user from the trade queues. Will not remove a user if they are being processed.")] - [RequireSudo] - public async Task ClearTradeUserAsync() - { - var users = Context.Message.MentionedUsers; - if (users.Count == 0) - { - await ReplyTextAsync("No users mentioned").ConfigureAwait(false); - return; - } - foreach (var u in users) - await ClearTradeUserAsync(u.Id).ConfigureAwait(false); - } - - [Command("queueClearAll")] - [Alias("qca", "tca")] - [Summary("Clears all users from the trade queues.")] - [RequireSudo] - public async Task ClearAllTradesAsync() - { - Info.ClearAllQueues(); - await ReplyTextAsync("Cleared all in the queue.").ConfigureAwait(false); - } - - [Command("queueToggle")] - [Alias("qt", "tt")] - [Summary("Toggles on/off the ability to join the trade queue.")] - [RequireSudo] - public Task ToggleQueueTradeAsync() - { - var state = Info.ToggleQueue(); - var msg = state - ? "Users are now able to join the trade queue." - : $"Changed queue settings: {Format.Bold($"Users CANNOT join the queue until it is turned back on.")}"; - - return Context.Channel.EchoAndReply(msg); - } - - [Command("queueMode")] - [Alias("qm")] - [Summary("Changes how queueing is controlled (manual/threshold/interval).")] - [RequireSudo] - public async Task ChangeQueueModeAsync([Summary("Queue mode")] QueueOpening mode) - { - KookBot.Runner.Hub.Config.Queues.QueueToggleMode = mode; - await ReplyTextAsync($"Changed queue mode to {mode}.").ConfigureAwait(false); - } - - [Command("queueList")] - [Alias("ql")] - [Summary("Private messages the list of users in the queue.")] - [RequireSudo] - public async Task ListUserQueue() - { - var lines = KookBot.Runner.Hub.Queues.Info.GetUserList("(ID {0}) - Code: {1} - {2} - {3}"); - var msg = string.Join("\n", lines); - if (msg.Length < 3) - await ReplyTextAsync("Queue list is empty.").ConfigureAwait(false); - else - await Context.User.SendTextAsync(msg).ConfigureAwait(false); - } - - private string ClearTrade() - { - var userID = Context.User.Id; - return ClearTrade(userID); - } - - //private static string ClearTrade(string username) - //{ - // var result = Info.ClearTrade(username); - // return GetClearTradeMessage(result); - //} - - private static string ClearTrade(ulong userID) - { - var result = Info.ClearTrade(userID); - return GetClearTradeMessage(result); - } - - private static string GetClearTradeMessage(QueueResultRemove result) - { - return result switch - { - QueueResultRemove.CurrentlyProcessing => "Looks like you're currently being processed! Did not remove from all queues.", - QueueResultRemove.CurrentlyProcessingRemoved => "Looks like you're currently being processed!", - QueueResultRemove.Removed => "Removed you from the queue.", - _ => "Sorry, you are not currently in the queue.", - }; - } -} diff --git a/SysBot.Pokemon.Kook/Commands/Bots/RemoteControlModule.cs b/SysBot.Pokemon.Kook/Commands/Bots/RemoteControlModule.cs deleted file mode 100644 index 37f643d3d..000000000 --- a/SysBot.Pokemon.Kook/Commands/Bots/RemoteControlModule.cs +++ /dev/null @@ -1,145 +0,0 @@ -using Kook.Commands; -using PKHeX.Core; -using SysBot.Base; -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace SysBot.Pokemon.Kook; - -[Summary("Remotely controls a bot.")] -public class RemoteControlModule : ModuleBase where T : PKM, new() -{ - [Command("click")] - [Summary("Clicks the specified button.")] - [RequireRoleAccess(nameof(KookManager.RolesRemoteControl))] - public async Task ClickAsync(SwitchButton b) - { - var bot = KookBot.Runner.Bots.Find(z => IsRemoteControlBot(z.Bot)); - if (bot == null) - { - await ReplyTextAsync($"No bot is available to execute your command: {b}").ConfigureAwait(false); - return; - } - - await ClickAsyncImpl(b, bot).ConfigureAwait(false); - } - - [Command("click")] - [Summary("Clicks the specified button.")] - [RequireSudo] - public async Task ClickAsync(string ip, SwitchButton b) - { - var bot = KookBot.Runner.GetBot(ip); - if (bot == null) - { - await ReplyTextAsync($"No bot is available to execute your command: {b}").ConfigureAwait(false); - return; - } - - await ClickAsyncImpl(b, bot).ConfigureAwait(false); - } - - [Command("setStick")] - [Summary("Sets the stick to the specified position.")] - [RequireRoleAccess(nameof(KookManager.RolesRemoteControl))] - public async Task SetStickAsync(SwitchStick s, short x, short y, ushort ms = 1_000) - { - var bot = KookBot.Runner.Bots.Find(z => IsRemoteControlBot(z.Bot)); - if (bot == null) - { - await ReplyTextAsync($"No bot is available to execute your command: {s}").ConfigureAwait(false); - return; - } - - await SetStickAsyncImpl(s, x, y, ms, bot).ConfigureAwait(false); - } - - [Command("setStick")] - [Summary("Sets the stick to the specified position.")] - [RequireSudo] - public async Task SetStickAsync(string ip, SwitchStick s, short x, short y, ushort ms = 1_000) - { - var bot = KookBot.Runner.GetBot(ip); - if (bot == null) - { - await ReplyTextAsync($"No bot has that IP address ({ip}).").ConfigureAwait(false); - return; - } - - await SetStickAsyncImpl(s, x, y, ms, bot).ConfigureAwait(false); - } - - [Command("setScreenOn")] - [Alias("screenOn", "scrOn")] - [Summary("Turns the screen on")] - [RequireSudo] - public Task SetScreenOnAsync([Remainder] string ip) - { - return SetScreen(true, ip); - } - - [Command("setScreenOff")] - [Alias("screenOff", "scrOff")] - [Summary("Turns the screen off")] - [RequireSudo] - public Task SetScreenOffAsync([Remainder] string ip) - { - return SetScreen(false, ip); - } - - private async Task SetScreen(bool on, string ip) - { - var bot = GetBot(ip); - if (bot == null) - { - await ReplyTextAsync($"No bot has that IP address ({ip}).").ConfigureAwait(false); - return; - } - - var b = bot.Bot; - var crlf = b is SwitchRoutineExecutor { UseCRLF: true }; - await b.Connection.SendAsync(SwitchCommand.SetScreen(on ? ScreenState.On : ScreenState.Off, crlf), CancellationToken.None).ConfigureAwait(false); - await ReplyTextAsync("Screen state set to: " + (on ? "On" : "Off")).ConfigureAwait(false); - } - - private static BotSource? GetBot(string ip) - { - var r = KookBot.Runner; - return r.GetBot(ip) ?? r.Bots.Find(x => x.IsRunning); // safe fallback for users who mistype IP address for single bot instances - } - - private async Task ClickAsyncImpl(SwitchButton button, BotSource bot) - { - if (!Enum.IsDefined(button)) - { - await ReplyTextAsync($"Unknown button value: {button}").ConfigureAwait(false); - return; - } - - var b = bot.Bot; - var crlf = b is SwitchRoutineExecutor { UseCRLF: true }; - await b.Connection.SendAsync(SwitchCommand.Click(button, crlf), CancellationToken.None).ConfigureAwait(false); - await ReplyTextAsync($"{b.Connection.Name} has performed: {button}").ConfigureAwait(false); - } - - private async Task SetStickAsyncImpl(SwitchStick s, short x, short y, ushort ms, BotSource bot) - { - if (!Enum.IsDefined(s)) - { - await ReplyTextAsync($"Unknown stick: {s}").ConfigureAwait(false); - return; - } - - var b = bot.Bot; - var crlf = b is SwitchRoutineExecutor { UseCRLF: true }; - await b.Connection.SendAsync(SwitchCommand.SetStick(s, x, y, crlf), CancellationToken.None).ConfigureAwait(false); - await ReplyTextAsync($"{b.Connection.Name} has performed: {s}").ConfigureAwait(false); - await Task.Delay(ms).ConfigureAwait(false); - await b.Connection.SendAsync(SwitchCommand.ResetStick(s, crlf), CancellationToken.None).ConfigureAwait(false); - await ReplyTextAsync($"{b.Connection.Name} has reset the stick position.").ConfigureAwait(false); - } - - private static bool IsRemoteControlBot(RoutineExecutor botstate) - => botstate is RemoteControlBotSWSH or RemoteControlBotBS or RemoteControlBotLA or RemoteControlBotSV; -} diff --git a/SysBot.Pokemon.Kook/Commands/Bots/SeedCheckModule.cs b/SysBot.Pokemon.Kook/Commands/Bots/SeedCheckModule.cs deleted file mode 100644 index 8e3822c53..000000000 --- a/SysBot.Pokemon.Kook/Commands/Bots/SeedCheckModule.cs +++ /dev/null @@ -1,84 +0,0 @@ -using Kook; -using Kook.Commands; -using PKHeX.Core; -using System.Threading.Tasks; - -namespace SysBot.Pokemon.Kook; - -[Summary("Queues new Seed Check trades")] -public class SeedCheckModule : ModuleBase where T : PKM, new() -{ - private static TradeQueueInfo Info => KookBot.Runner.Hub.Queues.Info; - - [Command("seedCheck")] - [Alias("checkMySeed", "checkSeed", "seed", "s", "sc")] - [Summary("Checks the seed for a Pokémon.")] - [RequireQueueRole(nameof(KookManager.RolesSeed))] - public Task SeedCheckAsync(int code) - { - var sig = Context.User.GetFavor(); - return QueueHelper.AddToQueueAsync(Context, code, Context.User.Username, sig, new T(), PokeRoutineType.SeedCheck, PokeTradeType.Seed); - } - - [Command("seedCheck")] - [Alias("checkMySeed", "checkSeed", "seed", "s", "sc")] - [Summary("Checks the seed for a Pokémon.")] - [RequireQueueRole(nameof(KookManager.RolesSeed))] - public Task SeedCheckAsync([Summary("Trade Code")][Remainder] string code) - { - int tradeCode = Util.ToInt32(code); - var sig = Context.User.GetFavor(); - return QueueHelper.AddToQueueAsync(Context, tradeCode == 0 ? Info.GetRandomTradeCode() : tradeCode, Context.User.Username, sig, new T(), PokeRoutineType.SeedCheck, PokeTradeType.Seed); - } - - [Command("seedCheck")] - [Alias("checkMySeed", "checkSeed", "seed", "s", "sc")] - [Summary("Checks the seed for a Pokémon.")] - [RequireQueueRole(nameof(KookManager.RolesSeed))] - public Task SeedCheckAsync() - { - var code = Info.GetRandomTradeCode(); - return SeedCheckAsync(code); - } - - [Command("seedList")] - [Alias("sl", "scq", "seedCheckQueue", "seedQueue", "seedList")] - [Summary("Prints the users in the Seed Check queue.")] - [RequireSudo] - public async Task GetSeedListAsync() - { - string msg = Info.GetTradeList(PokeRoutineType.SeedCheck); - - var card = new CardBuilder() - .AddModule(new SectionModuleBuilder().WithText("These are the users who are currently waiting:")) - .AddModule(new SectionModuleBuilder().WithText("Pending Trades")) - .AddModule(new SectionModuleBuilder().WithText(msg)) - .Build(); - await ReplyCardAsync(card).ConfigureAwait(false); - } - - [Command("findFrame")] - [Alias("ff", "getFrameData")] - [Summary("Prints the next shiny frame from the provided seed.")] - public async Task FindFrameAsync([Remainder] string seedString) - { - var me = KookBot.Runner; - var hub = me.Hub; - - seedString = seedString.ToLower(); - if (seedString.StartsWith("0x")) - seedString = seedString[2..]; - - var seed = Util.GetHexValue64(seedString); - - var r = new SeedSearchResult(Z3SearchResult.Success, seed, -1, hub.Config.SeedCheckSWSH.ResultDisplayMode); - var msg = r.ToString(); - - var card = new CardBuilder() - .AddModule(new SectionModuleBuilder().WithText($"Here are the details for `{seed:X16}`:")) - .AddModule(new SectionModuleBuilder().WithText($"Seed: {seed:X16}")) - .AddModule(new SectionModuleBuilder().WithText(msg)) - .Build(); - await ReplyCardAsync(card).ConfigureAwait(false); - } -} diff --git a/SysBot.Pokemon.Kook/Commands/Bots/TradeModule.cs b/SysBot.Pokemon.Kook/Commands/Bots/TradeModule.cs deleted file mode 100644 index 446509bf1..000000000 --- a/SysBot.Pokemon.Kook/Commands/Bots/TradeModule.cs +++ /dev/null @@ -1,253 +0,0 @@ -using Kook; -using Kook.Commands; -using Kook.WebSocket; -using PKHeX.Core; -using SysBot.Base; -using System; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace SysBot.Pokemon.Kook; - -[Summary("Queues new Link Code trades")] -public class TradeModule : ModuleBase where T : PKM, new() -{ - private static TradeQueueInfo Info => KookBot.Runner.Hub.Queues.Info; - - [Command("tradeList")] - [Alias("tl")] - [Summary("Prints the users in the trade queues.")] - [RequireSudo] - public async Task GetTradeListAsync() - { - string msg = Info.GetTradeList(PokeRoutineType.LinkTrade); - - var card = new CardBuilder() - .AddModule(new SectionModuleBuilder().WithText("These are the users who are currently waiting:")) - .AddModule(new SectionModuleBuilder().WithText("Pending Trades")) - .AddModule(new SectionModuleBuilder().WithText(msg)) - .Build(); - await ReplyCardAsync(card).ConfigureAwait(false); - } - - [Command("trade")] - [Alias("t")] - [Summary("Makes the bot trade you the provided Pokémon file.")] - [RequireQueueRole(nameof(KookManager.RolesTrade))] - public Task TradeAsyncAttach([Summary("Trade Code")] int code) - { - var sig = Context.User.GetFavor(); - return TradeAsyncAttach(code, sig, Context.User); - } - - [Command("trade")] - [Alias("t")] - [Summary("Makes the bot trade you a Pokémon converted from the provided Showdown Set.")] - [RequireQueueRole(nameof(KookManager.RolesTrade))] - public async Task TradeAsync([Summary("Trade Code")] int code, [Summary("Showdown Set")][Remainder] string content) - { - if (ShowdownTranslator.GameStringsZh.Species.Skip(1).Any(s => content.Contains(s))) - { - // If the content contains a Chinese Showdown Set, translate it to English. - content = ShowdownTranslator.Chinese2Showdown(content); - } - else - { - content = ReusableActions.StripCodeBlock(content); - } - - var set = new ShowdownSet(content); - var template = AutoLegalityWrapper.GetTemplate(set); - if (set.InvalidLines.Count != 0 || set.Species is 0) - { - var sb = new StringBuilder(128); - sb.AppendLine("Unable to parse Showdown Set."); - var invalidlines = set.InvalidLines; - if (invalidlines.Count != 0) - { - var localization = BattleTemplateParseErrorLocalization.Get(); - sb.AppendLine("Invalid lines detected:\n```"); - foreach (var line in invalidlines) - { - var error = line.Humanize(localization); - sb.AppendLine(error); - } - sb.AppendLine("```"); - } - if (set.Species is 0) - sb.AppendLine("Species could not be identified. Check your spelling."); - - var msg = sb.ToString(); - await ReplyTextAsync(msg).ConfigureAwait(false); - return; - } - - try - { - var sav = AutoLegalityWrapper.GetTrainerInfo(); - var pkm = sav.GetLegal(template, out var result); - var la = new LegalityAnalysis(pkm); - var spec = GameInfo.Strings.Species[template.Species]; - pkm = EntityConverter.ConvertToType(pkm, typeof(T), out _) ?? pkm; - if (pkm is not T pk || !la.Valid) - { - var reason = result switch - { - "Timeout" => $"That {spec} set took too long to generate.", - "VersionMismatch" => "Request refused: PKHeX and Auto-Legality Mod version mismatch.", - _ => $"I wasn't able to create a {spec} from that set.", - }; - var imsg = $"Oops! {reason}"; - if (result == "Failed") - imsg += $"\n{AutoLegalityWrapper.GetLegalizationHint(template, sav, pkm)}"; - await ReplyTextAsync(imsg).ConfigureAwait(false); - return; - } - pk.ResetPartyStats(); - - var sig = Context.User.GetFavor(); - await AddTradeToQueueAsync(code, Context.User.Username, pk, sig, Context.User).ConfigureAwait(false); - } - catch (Exception ex) - { - LogUtil.LogSafe(ex, nameof(TradeModule)); - var msg = $"Oops! An unexpected problem happened with this Showdown Set:\n```{string.Join("\n", set.GetSetLines())}```"; - await ReplyTextAsync(msg).ConfigureAwait(false); - } - } - - [Command("trade")] - [Alias("t")] - [Summary("Makes the bot trade you a Pokémon converted from the provided Showdown Set.")] - [RequireQueueRole(nameof(KookManager.RolesTrade))] - public Task TradeAsync([Summary("Showdown Set")][Remainder] string content) - { - var code = Info.GetRandomTradeCode(); - return TradeAsync(code, content); - } - - [Command("trade")] - [Alias("t")] - [Summary("Makes the bot trade you the attached file.")] - [RequireQueueRole(nameof(KookManager.RolesTrade))] - public Task TradeAsyncAttach(Uri file) - { - var code = Info.GetRandomTradeCode(); - return TradeAsyncAttach(code); - } - - [Command("banTrade")] - [Alias("bt")] - [RequireSudo] - public async Task BanTradeAsync([Summary("Online ID")] ulong nnid, string comment) - { - KookBotSettings.HubConfig.TradeAbuse.BannedIDs.AddIfNew([GetReference(nnid, comment)]); - await ReplyTextAsync("Done.").ConfigureAwait(false); - } - - private RemoteControlAccess GetReference(ulong id, string comment) => new() - { - ID = id, - Name = id.ToString(), - Comment = $"Added by {Context.User.Username} on {DateTime.Now:yyyy.MM.dd-hh:mm:ss} ({comment})", - }; - - [Command("tradeUser")] - [Alias("tu", "tradeOther")] - [Summary("Makes the bot trade the mentioned user the attached file.")] - [RequireSudo] - public async Task TradeAsyncAttachUser([Summary("Trade Code")] int code, [Remainder] string _) - { - if (Context.Message.MentionedUsers.Count > 1) - { - await ReplyTextAsync("Too many mentions. Queue one user at a time.").ConfigureAwait(false); - return; - } - - if (Context.Message.MentionedUsers.Count == 0) - { - await ReplyTextAsync("A user must be mentioned in order to do this.").ConfigureAwait(false); - return; - } - - var usr = Context.Message.MentionedUsers.ElementAt(0); - var sig = usr.GetFavor(); - await TradeAsyncAttach(code, sig, usr).ConfigureAwait(false); - } - - [Command("tradeUser")] - [Alias("tu", "tradeOther")] - [Summary("Makes the bot trade the mentioned user the attached file.")] - [RequireSudo] - public Task TradeAsyncAttachUser([Remainder] string _) - { - var code = Info.GetRandomTradeCode(); - return TradeAsyncAttachUser(code, _); - } - - private async Task TradeAsyncAttach(int code, RequestSignificance sig, SocketUser usr) - { - var attachment = Context.Message.Attachments.FirstOrDefault(); - if (attachment == default) - { - await ReplyTextAsync("No attachment provided!").ConfigureAwait(false); - return; - } - - var att = await NetUtil.DownloadPKMAsync(attachment).ConfigureAwait(false); - var pk = GetRequest(att); - if (pk == null) - { - await ReplyTextAsync("Attachment provided is not compatible with this module!").ConfigureAwait(false); - return; - } - - await AddTradeToQueueAsync(code, usr.Username, pk, sig, usr).ConfigureAwait(false); - } - - private static T? GetRequest(Download dl) - { - if (!dl.Success) - return null; - return dl.Data switch - { - null => null, - T pk => pk, - _ => EntityConverter.ConvertToType(dl.Data, typeof(T), out _) as T, - }; - } - - private async Task AddTradeToQueueAsync(int code, string trainerName, T pk, RequestSignificance sig, SocketUser usr) - { - var la = new LegalityAnalysis(pk); - if (!la.Valid) - { - // Disallow trading illegal Pokémon. - await ReplyTextAsync($"{typeof(T).Name} attachment is not legal, and cannot be traded!").ConfigureAwait(false); - return; - } - var enc = la.EncounterOriginal; - if (!pk.CanBeTraded(enc)) - { - // Disallow anything that cannot be traded from the game (e.g. Fusions). - await ReplyTextAsync("Provided Pokémon content is blocked from trading!").ConfigureAwait(false); - return; - } - var cfg = Info.Hub.Config.Trade; - if (cfg.DisallowNonNatives && (enc.Context != pk.Context || pk.GO)) - { - // Allow the owner to prevent trading entities that require a HOME Tracker even if the file has one already. - await ReplyTextAsync($"{typeof(T).Name} attachment is not native, and cannot be traded!").ConfigureAwait(false); - return; - } - if (cfg.DisallowTracked && pk is IHomeTrack { HasTracker: true }) - { - // Allow the owner to prevent trading entities that already have a HOME Tracker. - await ReplyTextAsync($"{typeof(T).Name} attachment is tracked by HOME, and cannot be traded!").ConfigureAwait(false); - return; - } - - await QueueHelper.AddToQueueAsync(Context, code, trainerName, sig, pk, PokeRoutineType.LinkTrade, PokeTradeType.Specific, usr).ConfigureAwait(false); - } -} diff --git a/SysBot.Pokemon.Kook/Commands/Extra/BatchEditingModule.cs b/SysBot.Pokemon.Kook/Commands/Extra/BatchEditingModule.cs deleted file mode 100644 index 21a88a60f..000000000 --- a/SysBot.Pokemon.Kook/Commands/Extra/BatchEditingModule.cs +++ /dev/null @@ -1,54 +0,0 @@ -using Kook; -using Kook.Commands; -using PKHeX.Core; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace SysBot.Pokemon.Kook; - -// ReSharper disable once UnusedType.Global -public class BatchEditingModule : ModuleBase -{ - [Command("batchInfo"), Alias("bei")] - [Summary("Tries to get info about the requested property.")] - public async Task GetBatchInfo(string propertyName) - { - if (BatchEditing.TryGetPropertyType(propertyName, out var result)) - await ReplyTextAsync($"{propertyName}: {result}").ConfigureAwait(false); - else - await ReplyTextAsync($"Unable to find info for {propertyName}.").ConfigureAwait(false); - } - - [Command("batchValidate"), Alias("bev")] - [Summary("Tries to get info about the requested property.")] - public async Task ValidateBatchInfo(string instructions) - { - bool valid = IsValidInstructionSet(instructions, out var invalid); - - if (!valid) - { - var msg = invalid.Select(z => $"{z.PropertyName}, {z.PropertyValue}"); - await ReplyTextAsync($"Invalid Lines Detected:\r\n{Format.Code(string.Join(Environment.NewLine, msg))}") - .ConfigureAwait(false); - } - else - { - await ReplyTextAsync($"{invalid.Count} line(s) are invalid.").ConfigureAwait(false); - } - } - - private static bool IsValidInstructionSet(ReadOnlySpan split, out List invalid) - { - invalid = []; - var set = new StringInstructionSet(split); - foreach (var s in set.Filters.Concat(set.Instructions)) - { - if (!BatchEditing.TryGetPropertyType(s.PropertyName, out _)) - invalid.Add(s); - } - - return invalid.Count == 0; - } -} diff --git a/SysBot.Pokemon.Kook/Commands/Extra/LegalityCheckModule.cs b/SysBot.Pokemon.Kook/Commands/Extra/LegalityCheckModule.cs deleted file mode 100644 index 8d4f2afbd..000000000 --- a/SysBot.Pokemon.Kook/Commands/Extra/LegalityCheckModule.cs +++ /dev/null @@ -1,49 +0,0 @@ -using Kook; -using Kook.Commands; -using PKHeX.Core; -using System.Threading.Tasks; - -namespace SysBot.Pokemon.Kook; - -public class LegalityCheckModule : ModuleBase -{ - [Command("lc"), Alias("check", "validate", "verify")] - [Summary("Verifies the attachment for legality.")] - public async Task LegalityCheck() - { - var attachments = Context.Message.Attachments; - foreach (var att in attachments) - await LegalityCheck(att, false).ConfigureAwait(false); - } - - [Command("lcv"), Alias("verbose")] - [Summary("Verifies the attachment for legality with a verbose output.")] - public async Task LegalityCheckVerbose() - { - var attachments = Context.Message.Attachments; - foreach (var att in attachments) - await LegalityCheck(att, true).ConfigureAwait(false); - } - - private async Task LegalityCheck(IAttachment att, bool verbose) - { - var download = await NetUtil.DownloadPKMAsync(att).ConfigureAwait(false); - if (!download.Success) - { - await ReplyTextAsync(download.ErrorMessage?? "No error message").ConfigureAwait(false); - return; - } - - var pkm = download.Data!; - var la = new LegalityAnalysis(pkm); - - var card = new CardBuilder() - .AddModule(new SectionModuleBuilder().WithText("Here's the legality report!")) - .AddModule(new SectionModuleBuilder().WithText($"Legality Report for {download.SanitizedFileName}:")) - .AddModule(new SectionModuleBuilder().WithText(la.Valid ? "Valid" : "Invalid")) - .AddModule(new SectionModuleBuilder().WithText(la.Report(verbose))) - .Build(); - - await ReplyCardAsync(card).ConfigureAwait(false); - } -} diff --git a/SysBot.Pokemon.Kook/Commands/Extra/LegalizerModule.cs b/SysBot.Pokemon.Kook/Commands/Extra/LegalizerModule.cs deleted file mode 100644 index f570727a2..000000000 --- a/SysBot.Pokemon.Kook/Commands/Extra/LegalizerModule.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Kook.Commands; -using PKHeX.Core; -using System.Threading.Tasks; - -namespace SysBot.Pokemon.Kook; - -public class LegalizerModule : ModuleBase where T : PKM, new() -{ - [Command("legalize"), Alias("alm")] - [Summary("Tries to legalize the attached pkm data.")] - public async Task LegalizeAsync() - { - var attachments = Context.Message.Attachments; - foreach (var att in attachments) - await Context.Channel.ReplyWithLegalizedSetAsync(att).ConfigureAwait(false); - } - - [Command("convert"), Alias("showdown")] - [Summary("Tries to convert the Showdown Set to pkm data.")] - [Priority(1)] - public Task ConvertShowdown([Summary("Generation/Format")] byte gen, [Remainder][Summary("Showdown Set")] string content) - { - return Context.Channel.ReplyWithLegalizedSetAsync(content, gen); - } - - [Command("convert"), Alias("showdown")] - [Summary("Tries to convert the Showdown Set to pkm data.")] - [Priority(0)] - public Task ConvertShowdown([Remainder][Summary("Showdown Set")] string content) - { - return Context.Channel.ReplyWithLegalizedSetAsync(content); - } -} diff --git a/SysBot.Pokemon.Kook/Commands/General/HelloModule.cs b/SysBot.Pokemon.Kook/Commands/General/HelloModule.cs deleted file mode 100644 index 6583e0799..000000000 --- a/SysBot.Pokemon.Kook/Commands/General/HelloModule.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Kook; -using Kook.Commands; -using System.Threading.Tasks; - -namespace SysBot.Pokemon.Kook; - -public class HelloModule : ModuleBase -{ - [Command("hello")] - [Alias("hi")] - [Summary("Say hello to the bot and get a response.")] - public async Task PingAsync() - { - var str = KookBotSettings.Settings.HelloResponse; - var msg = string.Format(str, $"{Context.User.KMarkdownMention}"); - await ReplyTextAsync(msg).ConfigureAwait(false); - } -} diff --git a/SysBot.Pokemon.Kook/Commands/General/HelpModule.cs b/SysBot.Pokemon.Kook/Commands/General/HelpModule.cs deleted file mode 100644 index e533b0925..000000000 --- a/SysBot.Pokemon.Kook/Commands/General/HelpModule.cs +++ /dev/null @@ -1,102 +0,0 @@ -using Kook; -using Kook.Commands; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace SysBot.Pokemon.Kook; - -public class HelpModule(CommandService Service) : ModuleBase -{ - [Command("help")] - [Summary("Lists available commands.")] - public async Task HelpAsync() - { - var builder = new CardBuilder() - .AddModule(new SectionModuleBuilder().WithText("Help has arrived!")) - .AddModule(new SectionModuleBuilder().WithText("These are the commands you can use:")); - - var mgr = KookBotSettings.Manager; - //var app = await Context.Client.GetApplicationInfoAsync().ConfigureAwait(false); - //var owner = app.Owner.Id; - var uid = Context.User.Id; - - foreach (var module in Service.Modules) - { - string? description = null; - HashSet mentioned = []; - foreach (var cmd in module.Commands) - { - var name = cmd.Name; - if (mentioned.Contains(name)) - continue; - //if (cmd.Attributes.Any(z => z is RequireOwnerAttribute) && owner != uid) - // continue; - if (cmd.Attributes.Any(z => z is RequireSudoAttribute) && !mgr.CanUseSudo(uid)) - continue; - - mentioned.Add(name); - var result = await cmd.CheckPreconditionsAsync(Context).ConfigureAwait(false); - if (result.IsSuccess) - description += $"{cmd.Aliases[0]}\n"; - } - if (string.IsNullOrWhiteSpace(description)) - continue; - - var moduleName = module.Name; - var gen = moduleName.IndexOf('`'); - if (gen != -1) - moduleName = moduleName[..gen]; - - builder.AddModule(new SectionModuleBuilder().WithText(new KMarkdownElementBuilder($"**{moduleName}**\n{description}"))); - } - - await ReplyCardAsync(builder.Build()).ConfigureAwait(false); - } - - [Command("help")] - [Summary("Lists information about a specific command.")] - public async Task HelpAsync([Summary("The command you want help for")] string command) - { - var result = Service.Search(Context, command); - - if (!result.IsSuccess) - { - await ReplyTextAsync($"Sorry, I couldn't find a command like {Format.Bold($"{command}")}.").ConfigureAwait(false); - return; - } - - var builder = new CardBuilder() - .AddModule(new SectionModuleBuilder().WithText("Help has arrived!")) - .AddModule(new SectionModuleBuilder().WithText(new KMarkdownElementBuilder($"Here are some commands like **{command}**:"))); - - foreach (var match in result.Commands) - { - var cmd = match.Command; - - builder.AddModule(new SectionModuleBuilder().WithText(new KMarkdownElementBuilder($"**{string.Join(", ", cmd.Aliases)}**\n{GetCommandSummary(cmd)}"))); - } - - await ReplyCardAsync(builder.Build()).ConfigureAwait(false); - } - - private static string GetCommandSummary(CommandInfo cmd) - { - return $"Summary: {cmd.Summary}\nParameters: {GetParameterSummary(cmd.Parameters)}"; - } - - private static string GetParameterSummary(IReadOnlyList p) - { - if (p.Count == 0) - return "None"; - return $"{p.Count}\n- " + string.Join("\n- ", p.Select(GetParameterSummary)); - } - - private static string GetParameterSummary(ParameterInfo z) - { - var result = z.Name; - if (!string.IsNullOrWhiteSpace(z.Summary)) - result += $" ({z.Summary})"; - return result; - } -} diff --git a/SysBot.Pokemon.Kook/Commands/General/InfoModule.cs b/SysBot.Pokemon.Kook/Commands/General/InfoModule.cs deleted file mode 100644 index 4775c9d58..000000000 --- a/SysBot.Pokemon.Kook/Commands/General/InfoModule.cs +++ /dev/null @@ -1,79 +0,0 @@ -using Kook; -using Kook.Commands; -using System; -using System.Diagnostics; -using System.Globalization; -using System.Linq; -using System.Reflection; -using System.Runtime.InteropServices; -using System.Threading.Tasks; - -namespace SysBot.Pokemon.Kook; - -// src: https://github.com/foxbot/patek/blob/master/src/Patek/Modules/InfoModule.cs -// ISC License (ISC) -// Copyright 2017, Christopher F. -public class InfoModule : ModuleBase -{ - private const string detail = "I am an open-source Kook bot powered by PKHeX.Core and other open-source software."; - private const string repo = "https://github.com/kwsch/SysBot.NET"; - - [Command("info")] - [Alias("about", "whoami", "owner")] - public async Task InfoAsync() - { - //var app = await Context.Client.GetApplicationInfoAsync().ConfigureAwait(false); - - var builder = new CardBuilder() - .AddModule(new SectionModuleBuilder().WithText("Here's some information about me!")) - .AddModule(new SectionModuleBuilder().WithText(detail)); - - builder.AddModule(new SectionModuleBuilder().WithText(new KMarkdownElementBuilder("**Info**"))) - .AddModule(new SectionModuleBuilder().WithText( - $"- [Source Code]({repo})\n" + - //$"- {Format.Bold("Owner")}: {app.Owner} ({app.Owner.Id})\n" + - $"- {Format.Bold("Library")}: Kook.Net ({KookConfig.Version})\n" + - $"- {Format.Bold("Uptime")}: {GetUptime()}\n" + - $"- {Format.Bold("Runtime")}: {RuntimeInformation.FrameworkDescription} {RuntimeInformation.ProcessArchitecture} " + - $"({RuntimeInformation.OSDescription} {RuntimeInformation.OSArchitecture})\n" + - $"- {Format.Bold("Buildtime")}: {GetVersionInfo("SysBot.Base", false)}\n" + - $"- {Format.Bold("Core Version")}: {GetVersionInfo("PKHeX.Core")}\n" + - $"- {Format.Bold("AutoLegality Version")}: {GetVersionInfo("PKHeX.Core.AutoMod")}\n" - )); - - builder.AddModule(new SectionModuleBuilder().WithText(new KMarkdownElementBuilder("**Stats**"))) - .AddModule(new SectionModuleBuilder().WithText( - $"- {Format.Bold("Heap Size")}: {GetHeapSize()}MiB\n" + - $"- {Format.Bold("Guilds")}: {Context.Client.Guilds.Count}\n" + - $"- {Format.Bold("Channels")}: {Context.Client.Guilds.Sum(g => g.Channels.Count)}\n" + - $"- {Format.Bold("Users")}: {Context.Client.Guilds.Sum(g => g.MemberCount)}\n" - )); - - await ReplyCardAsync(builder.Build()).ConfigureAwait(false); - } - - private static string GetUptime() => (DateTime.Now - Process.GetCurrentProcess().StartTime).ToString(@"dd\.hh\:mm\:ss"); - private static string GetHeapSize() => Math.Round(GC.GetTotalMemory(true) / (1024.0 * 1024.0), 2).ToString(CultureInfo.CurrentCulture); - - private static string GetVersionInfo(string assemblyName, bool inclVersion = true) - { - const string _default = "Unknown"; - var assemblies = AppDomain.CurrentDomain.GetAssemblies(); - var assembly = Array.Find(assemblies, x => x.GetName().Name == assemblyName); - - var attribute = assembly?.GetCustomAttribute(); - if (attribute is null) - return _default; - - var info = attribute.InformationalVersion; - var split = info.Split('+'); - if (split.Length < 2) - return _default; - - var version = split[0]; - var revision = split[1]; - if (DateTime.TryParseExact(revision, "yyMMddHHmmss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var buildTime)) - return (inclVersion ? $"{version} " : "") + $@"{buildTime:yy-MM-dd\.hh\:mm}"; - return !inclVersion ? _default : version; - } -} diff --git a/SysBot.Pokemon.Kook/Commands/General/PingModule.cs b/SysBot.Pokemon.Kook/Commands/General/PingModule.cs deleted file mode 100644 index 3ea0fb03c..000000000 --- a/SysBot.Pokemon.Kook/Commands/General/PingModule.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Kook.Commands; -using System.Threading.Tasks; - -namespace SysBot.Pokemon.Kook; - -public class PingModule : ModuleBase -{ - [Command("ping")] - [Summary("Makes the bot respond, indicating that it is running.")] - public async Task PingAsync() - { - await ReplyTextAsync("Pong!").ConfigureAwait(false); - } -} diff --git a/SysBot.Pokemon.Kook/Commands/Management/BotModule.cs b/SysBot.Pokemon.Kook/Commands/Management/BotModule.cs deleted file mode 100644 index a3bc6e761..000000000 --- a/SysBot.Pokemon.Kook/Commands/Management/BotModule.cs +++ /dev/null @@ -1,124 +0,0 @@ -using Kook; -using Kook.Commands; -using PKHeX.Core; -using System.Text; -using System.Threading.Tasks; - -namespace SysBot.Pokemon.Kook; - -// ReSharper disable once UnusedType.Global -public class BotModule : ModuleBase where T : PKM, new() -{ - [Command("botStatus")] - [Summary("Gets the status of the bots.")] - [RequireSudo] - public async Task GetStatusAsync() - { - var me = KookBot.Runner; - var sb = new StringBuilder(); - foreach (var bot in me.Bots) - { - if (bot.Bot is not PokeRoutineExecutorBase b) - continue; - sb.AppendLine(GetDetailedSummary(b)); - } - if (sb.Length == 0) - { - await ReplyTextAsync("No bots configured.").ConfigureAwait(false); - return; - } - await ReplyTextAsync(Format.Code(sb.ToString())).ConfigureAwait(false); - } - - private static string GetDetailedSummary(TBot z) where TBot: PokeRoutineExecutorBase - { - return $"- {z.Connection.Name} | {z.Connection.Label} - {z.Config.CurrentRoutineType} ~ {z.LastTime:hh:mm:ss} | {z.LastLogged}"; - } - - [Command("botStart")] - [Summary("Starts a bot by IP address/port.")] - [RequireSudo] - public async Task StartBotAsync(string ip) - { - var bot = KookBot.Runner.GetBot(ip); - if (bot == null) - { - await ReplyTextAsync($"No bot has that IP address ({ip}).").ConfigureAwait(false); - return; - } - - bot.Start(); - await Context.Channel.EchoAndReply($"The bot at {ip} ({bot.Bot.Connection.Label}) has been commanded to Start.").ConfigureAwait(false); - } - - [Command("botStop")] - [Summary("Stops a bot by IP address/port.")] - [RequireSudo] - public async Task StopBotAsync(string ip) - { - var bot = KookBot.Runner.GetBot(ip); - if (bot == null) - { - await ReplyTextAsync($"No bot has that IP address ({ip}).").ConfigureAwait(false); - return; - } - - bot.Stop(); - await Context.Channel.EchoAndReply($"The bot at {ip} ({bot.Bot.Connection.Label}) has been commanded to Stop.").ConfigureAwait(false); - } - - [Command("botIdle")] - [Alias("botPause")] - [Summary("Commands a bot to Idle by IP address/port.")] - [RequireSudo] - public async Task IdleBotAsync(string ip) - { - var bot = KookBot.Runner.GetBot(ip); - if (bot == null) - { - await ReplyTextAsync($"No bot has that IP address ({ip}).").ConfigureAwait(false); - return; - } - - bot.Pause(); - await Context.Channel.EchoAndReply($"The bot at {ip} ({bot.Bot.Connection.Label}) has been commanded to Idle.").ConfigureAwait(false); - } - - [Command("botChange")] - [Summary("Changes the routine of a bot (trades).")] - [RequireSudo] - public async Task ChangeTaskAsync(string ip, [Summary("Routine enum name")] PokeRoutineType task) - { - var bot = KookBot.Runner.GetBot(ip); - if (bot == null) - { - await ReplyTextAsync($"No bot has that IP address ({ip}).").ConfigureAwait(false); - return; - } - - bot.Bot.Config.Initialize(task); - await Context.Channel.EchoAndReply($"The bot at {ip} ({bot.Bot.Connection.Label}) has been commanded to do {task} as its next task.").ConfigureAwait(false); - } - - [Command("botRestart")] - [Summary("Restarts the bot(s) by IP address(es), separated by commas.")] - [RequireSudo] - public async Task RestartBotAsync(string ipAddressesCommaSeparated) - { - var ips = ipAddressesCommaSeparated.Split(','); - foreach (var ip in ips) - { - var bot = KookBot.Runner.GetBot(ip); - if (bot == null) - { - await ReplyTextAsync($"No bot has that IP address ({ip}).").ConfigureAwait(false); - return; - } - - var c = bot.Bot.Connection; - c.Reset(); - bot.Start(); - await Context.Channel.EchoAndReply($"The bot at {ip} ({c.Label}) has been commanded to Restart.").ConfigureAwait(false); - } - } -} diff --git a/SysBot.Pokemon.Kook/Commands/Management/EchoModule.cs b/SysBot.Pokemon.Kook/Commands/Management/EchoModule.cs deleted file mode 100644 index d58e5fc6f..000000000 --- a/SysBot.Pokemon.Kook/Commands/Management/EchoModule.cs +++ /dev/null @@ -1,119 +0,0 @@ -using Kook; -using Kook.Commands; -using Kook.WebSocket; -using SysBot.Base; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace SysBot.Pokemon.Kook; - -public class EchoModule : ModuleBase -{ - private class EchoChannel(ulong ChannelId, string ChannelName, Action Action) - { - public readonly ulong ChannelID = ChannelId; - public readonly string ChannelName = ChannelName; - public readonly Action Action = Action; - } - - private static readonly Dictionary Channels = []; - - public static void RestoreChannels(KookSocketClient Kook, KookSettings cfg) - { - foreach (var ch in cfg.EchoChannels) - { - if (Kook.GetChannel(ch.ID) is ISocketMessageChannel c) - AddEchoChannel(c, ch.ID); - } - - EchoUtil.Echo("Added echo notification to Kook channel(s) on Bot startup."); - } - - [Command("echoHere")] - [Summary("Makes the echo special messages to the channel.")] - [RequireSudo] - public async Task AddEchoAsync() - { - var c = Context.Channel; - var cid = c.Id; - if (Channels.TryGetValue(cid, out _)) - { - await ReplyTextAsync("Already notifying here.").ConfigureAwait(false); - return; - } - - AddEchoChannel(c, cid); - - // Add to Kook global loggers (saves on program close) - KookBotSettings.Settings.EchoChannels.AddIfNew([GetReference(Context.Channel)]); - await ReplyTextAsync("Added Echo output to this channel!").ConfigureAwait(false); - } - - private static void AddEchoChannel(ISocketMessageChannel c, ulong cid) - { - void Echo(string msg) => c.SendTextAsync(msg); - - Action l = Echo; - EchoUtil.Forwarders.Add(l); - var entry = new EchoChannel(cid, c.Name, l); - Channels.Add(cid, entry); - } - - public static bool IsEchoChannel(ISocketMessageChannel c) - { - var cid = c.Id; - return Channels.TryGetValue(cid, out _); - } - - [Command("echoInfo")] - [Summary("Dumps the special message (Echo) settings.")] - [RequireSudo] - public async Task DumpEchoInfoAsync() - { - foreach (var c in Channels) - await ReplyTextAsync($"{c.Key} - {c.Value}").ConfigureAwait(false); - } - - [Command("echoClear")] - [Summary("Clears the special message echo settings in that specific channel.")] - [RequireSudo] - public async Task ClearEchosAsync() - { - var id = Context.Channel.Id; - if (!Channels.TryGetValue(id, out var echo)) - { - await ReplyTextAsync("Not echoing in this channel.").ConfigureAwait(false); - return; - } - EchoUtil.Forwarders.Remove(echo.Action); - Channels.Remove(Context.Channel.Id); - KookBotSettings.Settings.EchoChannels.RemoveAll(z => z.ID == id); - await ReplyTextAsync($"Echoes cleared from channel: {Context.Channel.Name}").ConfigureAwait(false); - } - - [Command("echoClearAll")] - [Summary("Clears all the special message Echo channel settings.")] - [RequireSudo] - public async Task ClearEchosAllAsync() - { - foreach (var l in Channels) - { - var entry = l.Value; - await ReplyTextAsync($"Echoing cleared from {entry.ChannelName} ({entry.ChannelID}!").ConfigureAwait(false); - EchoUtil.Forwarders.Remove(entry.Action); - } - EchoUtil.Forwarders.RemoveAll(y => Channels.Select(x => x.Value.Action).Contains(y)); - Channels.Clear(); - KookBotSettings.Settings.EchoChannels.Clear(); - await ReplyTextAsync("Echoes cleared from all channels!").ConfigureAwait(false); - } - - private RemoteControlAccess GetReference(IChannel channel) => new() - { - ID = channel.Id, - Name = channel.Name, - Comment = $"Added by {Context.User.Username} on {DateTime.Now:yyyy.MM.dd-hh:mm:ss}", - }; -} diff --git a/SysBot.Pokemon.Kook/Commands/Management/EncounterModule.cs b/SysBot.Pokemon.Kook/Commands/Management/EncounterModule.cs deleted file mode 100644 index e229c7fb6..000000000 --- a/SysBot.Pokemon.Kook/Commands/Management/EncounterModule.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Kook.Commands; -using PKHeX.Core; -using System.Linq; -using System.Threading.Tasks; - -namespace SysBot.Pokemon.Kook; - -public class EchoModule : ModuleBase where T : PKM, new() -{ - [Command("toss")] - [Summary("Makes all bots that are currently waiting for a go-ahead continue operation.")] - [RequireSudo] - public async Task TossAsync(string name = "") - { - var bots = KookBot.Runner.Bots.Select(z => z.Bot); - foreach (var b in bots) - { - if (b is not IEncounterBot x) - continue; - if (!b.Connection.Name.Contains(name) && !b.Connection.Label.Contains(name)) - continue; - x.Acknowledge(); - } - - await ReplyTextAsync("Done.").ConfigureAwait(false); - } -} diff --git a/SysBot.Pokemon.Kook/Commands/Management/HubModule.cs b/SysBot.Pokemon.Kook/Commands/Management/HubModule.cs deleted file mode 100644 index 2d656df38..000000000 --- a/SysBot.Pokemon.Kook/Commands/Management/HubModule.cs +++ /dev/null @@ -1,87 +0,0 @@ -using Kook; -using Kook.Commands; -using PKHeX.Core; -using SysBot.Base; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace SysBot.Pokemon.Kook; - -public class HubModule : ModuleBase where T : PKM, new() -{ - [Command("status")] - [Alias("stats")] - [Summary("Gets the status of the bot environment.")] - public async Task GetStatusAsync() - { - var me = KookBot.Runner; - var hub = me.Hub; - - var builder = new CardBuilder(); - - var runner = KookBot.Runner; - var allBots = runner.Bots.ConvertAll(z => z.Bot); - var botCount = allBots.Count; - builder.AddModule(new SectionModuleBuilder().WithText("Bot Status")) - .AddModule(new SectionModuleBuilder().WithText(new KMarkdownElementBuilder("**Summary**"))) - .AddModule(new SectionModuleBuilder().WithText($"Bot Count: {botCount}\n" + - $"Bot State: {SummarizeBots(allBots)}\n" + - $"Pool Count: {hub.Ledy.Pool.Count}\n")); - - var bots = allBots.OfType(); - var lines = bots.SelectMany(z => z.Counts.GetNonZeroCounts()).Distinct(); - var msg = string.Join("\n", lines); - if (string.IsNullOrWhiteSpace(msg)) - msg = "Nothing counted yet!"; - builder.AddModule(new SectionModuleBuilder().WithText(new KMarkdownElementBuilder("**Counts**"))) - .AddModule(new SectionModuleBuilder().WithText(msg)); - - var queues = hub.Queues.AllQueues; - int count = 0; - foreach (var q in queues) - { - var c = q.Count; - if (c == 0) - continue; - - var nextMsg = GetNextName(q); - builder.AddModule(new SectionModuleBuilder().WithText(new KMarkdownElementBuilder($"**{q.Type} Queue**"))) - .AddModule(new SectionModuleBuilder().WithText($"Next: {nextMsg}\n" + - $"Count: {c}")); - count += c; - } - - if (count == 0) - { - builder.AddModule(new SectionModuleBuilder().WithText(new KMarkdownElementBuilder("**Queues are empty.**"))) - .AddModule(new SectionModuleBuilder().WithText("Nobody in line!")); - } - - await ReplyCardAsync(builder.Build()).ConfigureAwait(false); - } - - private static string GetNextName(PokeTradeQueue q) - { - var next = q.TryPeek(out var detail, out _); - if (!next) - return "None!"; - - var name = detail.Trainer.TrainerName; - - // show detail of trade if possible - var nick = detail.TradeData.Nickname; - if (!string.IsNullOrEmpty(nick)) - name += $" - {nick}"; - return name; - } - - private static string SummarizeBots(IReadOnlyCollection> bots) - { - if (bots.Count == 0) - return "No bots configured."; - var summaries = bots.Select(z => $"- {z.GetSummary()}"); - return Environment.NewLine + string.Join(Environment.NewLine, summaries); - } -} diff --git a/SysBot.Pokemon.Kook/Commands/Management/LogModule.cs b/SysBot.Pokemon.Kook/Commands/Management/LogModule.cs deleted file mode 100644 index 5d411f0f3..000000000 --- a/SysBot.Pokemon.Kook/Commands/Management/LogModule.cs +++ /dev/null @@ -1,104 +0,0 @@ -using Kook; -using Kook.Commands; -using Kook.WebSocket; -using SysBot.Base; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace SysBot.Pokemon.Kook; - -public class LogModule : ModuleBase -{ - private static readonly Dictionary Channels = []; - - public static void RestoreLogging(KookSocketClient Kook, KookSettings settings) - { - foreach (var ch in settings.LoggingChannels) - { - if (Kook.GetChannel(ch.ID) is ISocketMessageChannel c) - AddLogChannel(c, ch.ID); - } - - LogUtil.LogInfo("Added logging to Kook channel(s) on Bot startup.", "Kook"); - } - - [Command("logHere")] - [Summary("Makes the bot log to the channel.")] - [RequireSudo] - public async Task AddLogAsync() - { - var c = Context.Channel; - var cid = c.Id; - if (Channels.TryGetValue(cid, out _)) - { - await ReplyTextAsync("Already logging here.").ConfigureAwait(false); - return; - } - - AddLogChannel(c, cid); - - // Add to Kook global loggers (saves on program close) - KookBotSettings.Settings.LoggingChannels.AddIfNew([GetReference(Context.Channel)]); - await ReplyTextAsync("Added logging output to this channel!").ConfigureAwait(false); - } - - private static void AddLogChannel(ISocketMessageChannel c, ulong cid) - { - var logger = new ChannelLogger(cid, c); - LogUtil.Forwarders.Add(logger); - Channels.Add(cid, logger); - } - - [Command("logInfo")] - [Summary("Dumps the logging settings.")] - [RequireSudo] - public async Task DumpLogInfoAsync() - { - foreach (var c in Channels) - await ReplyTextAsync($"{c.Key} - {c.Value}").ConfigureAwait(false); - } - - [Command("logClear")] - [Summary("Clears the logging settings in that specific channel.")] - [RequireSudo] - public async Task ClearLogsAsync() - { - var id = Context.Channel.Id; - if (!Channels.TryGetValue(id, out var log)) - { - await ReplyTextAsync("Not echoing in this channel.").ConfigureAwait(false); - return; - } - LogUtil.Forwarders.Remove(log); - Channels.Remove(Context.Channel.Id); - KookBotSettings.Settings.LoggingChannels.RemoveAll(z => z.ID == id); - await ReplyTextAsync($"Logging cleared from channel: {Context.Channel.Name}").ConfigureAwait(false); - } - - [Command("logClearAll")] - [Summary("Clears all the logging settings.")] - [RequireSudo] - public async Task ClearLogsAllAsync() - { - foreach (var l in Channels) - { - var entry = l.Value; - await ReplyTextAsync($"Logging cleared from {entry.ChannelName} ({entry.ChannelID}!").ConfigureAwait(false); - LogUtil.Forwarders.Remove(entry); - } - - LogUtil.Forwarders.RemoveAll(y => Channels.Select(z => z.Value).Contains(y)); - Channels.Clear(); - KookBotSettings.Settings.LoggingChannels.Clear(); - await ReplyTextAsync("Logging cleared from all channels!").ConfigureAwait(false); - } - - private RemoteControlAccess GetReference(IChannel channel) => new() - { - ID = channel.Id, - Name = channel.Name, - Comment = $"Added by {Context.User.Username} on {DateTime.Now:yyyy.MM.dd-hh:mm:ss}", - }; -} diff --git a/SysBot.Pokemon.Kook/Commands/Management/OwnerModule.cs b/SysBot.Pokemon.Kook/Commands/Management/OwnerModule.cs deleted file mode 100644 index dc8d9b091..000000000 --- a/SysBot.Pokemon.Kook/Commands/Management/OwnerModule.cs +++ /dev/null @@ -1,128 +0,0 @@ -using Kook; -using Kook.Commands; -using PKHeX.Core; -using System; -using System.Linq; -using System.Threading.Tasks; - -namespace SysBot.Pokemon.Kook; - -public class OwnerModule : SudoModule where T : PKM, new() -{ - [Command("addSudo")] - [Summary("Adds mentioned user to global sudo")] - [RequireOwner] - // ReSharper disable once UnusedParameter.Global - public async Task SudoUsers([Remainder] string _) - { - var users = Context.Message.MentionedUsers; - var objects = users.Select(GetReference); - KookBotSettings.Settings.GlobalSudoList.AddIfNew(objects); - await ReplyTextAsync("Done.").ConfigureAwait(false); - } - - [Command("removeSudo")] - [Summary("Removes mentioned user from global sudo")] - [RequireOwner] - // ReSharper disable once UnusedParameter.Global - public async Task RemoveSudoUsers([Remainder] string _) - { - var users = Context.Message.MentionedUsers; - var objects = users.Select(GetReference); - KookBotSettings.Settings.GlobalSudoList.RemoveAll(z => objects.Any(o => o.ID == z.ID)); - await ReplyTextAsync("Done.").ConfigureAwait(false); - } - - [Command("addChannel")] - [Summary("Adds a channel to the list of channels that are accepting commands.")] - [RequireOwner] - // ReSharper disable once UnusedParameter.Global - public async Task AddChannel() - { - var obj = GetReference(Context.Message.Channel); - KookBotSettings.Settings.ChannelWhitelist.AddIfNew([obj]); - await ReplyTextAsync("Done.").ConfigureAwait(false); - } - - [Command("removeChannel")] - [Summary("Removes a channel from the list of channels that are accepting commands.")] - [RequireOwner] - // ReSharper disable once UnusedParameter.Global - public async Task RemoveChannel() - { - var obj = GetReference(Context.Message.Channel); - KookBotSettings.Settings.ChannelWhitelist.RemoveAll(z => z.ID == obj.ID); - await ReplyTextAsync("Done.").ConfigureAwait(false); - } - - [Command("leave")] - [Alias("bye")] - [Summary("Leaves the current server.")] - [RequireOwner] - // ReSharper disable once UnusedParameter.Global - public async Task Leave() - { - await ReplyTextAsync("Goodbye.").ConfigureAwait(false); - await Context.Guild.LeaveAsync().ConfigureAwait(false); - } - - [Command("leaveguild")] - [Alias("lg")] - [Summary("Leaves guild based on supplied ID.")] - [RequireOwner] - // ReSharper disable once UnusedParameter.Global - public async Task LeaveGuild(string userInput) - { - if (!ulong.TryParse(userInput, out ulong id)) - { - await ReplyTextAsync("Please provide a valid Guild ID.").ConfigureAwait(false); - return; - } - - var guild = Context.Client.Guilds.FirstOrDefault(x => x.Id == id); - if (guild is null) - { - await ReplyTextAsync($"Provided input ({userInput}) is not a valid guild ID or the bot is not in the specified guild.").ConfigureAwait(false); - return; - } - - await ReplyTextAsync($"Leaving {guild}.").ConfigureAwait(false); - await guild.LeaveAsync().ConfigureAwait(false); - } - - [Command("leaveall")] - [Summary("Leaves all servers the bot is currently in.")] - [RequireOwner] - // ReSharper disable once UnusedParameter.Global - public async Task LeaveAll() - { - await ReplyTextAsync("Leaving all servers.").ConfigureAwait(false); - foreach (var guild in Context.Client.Guilds) - await guild.LeaveAsync().ConfigureAwait(false); - } - - [Command("sudoku")] - [Alias("kill", "shutdown")] - [Summary("Causes the entire process to end itself!")] - [RequireOwner] - // ReSharper disable once UnusedParameter.Global - public async Task ExitProgram() - { - await Context.Channel.EchoAndReply($"Shutting down... goodbye! {Format.Bold("Bot services are going offline.")}").ConfigureAwait(false); - Environment.Exit(0); - } - - private RemoteControlAccess GetReference(IUser channel) => new() - { - ID = channel.Id, - Name = channel.Username, - Comment = $"Added by {Context.User.Username} on {DateTime.Now:yyyy.MM.dd-hh:mm:ss}", - }; - - private RemoteControlAccess GetReference(IChannel channel) => new() - { - ID = channel.Id, - Name = channel.Name, - Comment = $"Added by {Context.User.Username} on {DateTime.Now:yyyy.MM.dd-hh:mm:ss}", - }; -} diff --git a/SysBot.Pokemon.Kook/Commands/Management/PoolModule.cs b/SysBot.Pokemon.Kook/Commands/Management/PoolModule.cs deleted file mode 100644 index 046ff0ad3..000000000 --- a/SysBot.Pokemon.Kook/Commands/Management/PoolModule.cs +++ /dev/null @@ -1,51 +0,0 @@ -using Kook; -using Kook.Commands; -using PKHeX.Core; -using System.Linq; -using System.Threading.Tasks; - -namespace SysBot.Pokemon.Kook; - -[Summary("Distribution Pool Module")] -public class PoolModule : ModuleBase where T : PKM, new() -{ - [Command("poolReload")] - [Summary("Reloads the bot pool from the setting's folder.")] - [RequireSudo] - public async Task ReloadPoolAsync() - { - var me = KookBot.Runner; - var hub = me.Hub; - - var pool = hub.Ledy.Pool.Reload(hub.Config.Folder.DistributeFolder); - if (!pool) - await ReplyTextAsync("Failed to reload from folder.").ConfigureAwait(false); - else - await ReplyTextAsync($"Reloaded from folder. Pool count: {hub.Ledy.Pool.Count}").ConfigureAwait(false); - } - - [Command("pool")] - [Summary("Displays the details of Pokémon files in the random pool.")] - public async Task DisplayPoolCountAsync() - { - var me = KookBot.Runner; - var hub = me.Hub; - var pool = hub.Ledy.Pool; - var count = pool.Count; - if (count is > 0 and < 20) - { - var lines = pool.Files.Select((z, i) => $"{i + 1:00}: {z.Key} = {(Species)z.Value.RequestInfo.Species}"); - var msg = string.Join("\n", lines); - - var card = new CardBuilder().AddModule(new SectionModuleBuilder().WithText("Pool Details")) - .AddModule(new SectionModuleBuilder().WithText($"Count: {count}")) - .AddModule(new SectionModuleBuilder().WithText(msg)) - .Build(); - await ReplyCardAsync(card).ConfigureAwait(false); - } - else - { - await ReplyTextAsync($"Pool Count: {count}").ConfigureAwait(false); - } - } -} diff --git a/SysBot.Pokemon.Kook/Commands/Management/SudoModule.cs b/SysBot.Pokemon.Kook/Commands/Management/SudoModule.cs deleted file mode 100644 index 3769add32..000000000 --- a/SysBot.Pokemon.Kook/Commands/Management/SudoModule.cs +++ /dev/null @@ -1,205 +0,0 @@ -using Kook; -using Kook.Commands; -using PKHeX.Core; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace SysBot.Pokemon.Kook; - -public class SudoModule : ModuleBase where T : PKM, new() -{ - [Command("blacklist")] - [Summary("Blacklists a mentioned Kook user.")] - [RequireSudo] - // ReSharper disable once UnusedParameter.Global - public async Task BlackListUsers([Remainder] string _) - { - var users = Context.Message.MentionedUsers; - var objects = users.Select(GetReference); - KookBotSettings.Settings.UserBlacklist.AddIfNew(objects); - await ReplyTextAsync("Done.").ConfigureAwait(false); - } - - [Command("blacklistComment")] - [Summary("Adds a comment for a blacklisted Kook user ID.")] - [RequireSudo] - // ReSharper disable once UnusedParameter.Global - public async Task BlackListUsers(ulong id, [Remainder] string comment) - { - var obj = KookBotSettings.Settings.UserBlacklist.List.Find(z => z.ID == id); - if (obj is null) - { - await ReplyTextAsync($"Unable to find a user with that ID ({id}).").ConfigureAwait(false); - return; - } - - var oldComment = obj.Comment; - obj.Comment = comment; - await ReplyTextAsync($"Done. Changed existing comment ({oldComment}) to ({comment}).").ConfigureAwait(false); - } - - [Command("unblacklist")] - [Summary("Removes a mentioned Kook user from the blacklist.")] - [RequireSudo] - // ReSharper disable once UnusedParameter.Global - public async Task UnBlackListUsers([Remainder] string _) - { - var users = Context.Message.MentionedUsers; - var objects = users.Select(GetReference); - KookBotSettings.Settings.UserBlacklist.RemoveAll(z => objects.Any(o => o.ID == z.ID)); - await ReplyTextAsync("Done.").ConfigureAwait(false); - } - - [Command("blacklistId")] - [Summary("Blacklists Kook user IDs. (Useful if user is not in the server).")] - [RequireSudo] - public async Task BlackListIDs([Summary("Comma Separated Kook IDs")][Remainder] string content) - { - var IDs = GetIDs(content); - var objects = IDs.Select(GetReference); - KookBotSettings.Settings.UserBlacklist.AddIfNew(objects); - await ReplyTextAsync("Done.").ConfigureAwait(false); - } - - [Command("unBlacklistId")] - [Summary("Removes Kook user IDs from the blacklist. (Useful if user is not in the server).")] - [RequireSudo] - public async Task UnBlackListIDs([Summary("Comma Separated Kook IDs")][Remainder] string content) - { - var IDs = GetIDs(content); - KookBotSettings.Settings.UserBlacklist.RemoveAll(z => IDs.Any(o => o == z.ID)); - await ReplyTextAsync("Done.").ConfigureAwait(false); - } - - [Command("blacklistSummary")] - [Alias("printBlacklist", "blacklistPrint")] - [Summary("Prints the list of blacklisted Kook users.")] - [RequireSudo] - public async Task PrintBlacklist() - { - var lines = KookBotSettings.Settings.UserBlacklist.Summarize(); - var msg = string.Join("\n", lines); - await ReplyTextAsync(Format.Code(msg)).ConfigureAwait(false); - } - - [Command("banID")] - [Summary("Bans online user IDs.")] - [RequireSudo] - public async Task BanOnlineIDs([Summary("Comma Separated Online IDs")][Remainder] string content) - { - var IDs = GetIDs(content); - var objects = IDs.Select(GetReference); - - var me = KookBot.Runner; - var hub = me.Hub; - hub.Config.TradeAbuse.BannedIDs.AddIfNew(objects); - await ReplyTextAsync("Done.").ConfigureAwait(false); - } - - [Command("bannedIDComment")] - [Summary("Adds a comment for a banned online user ID.")] - [RequireSudo] - public async Task BanOnlineIDs(ulong id, [Remainder] string comment) - { - var me = KookBot.Runner; - var hub = me.Hub; - var obj = hub.Config.TradeAbuse.BannedIDs.List.Find(z => z.ID == id); - if (obj is null) - { - await ReplyTextAsync($"Unable to find a user with that online ID ({id}).").ConfigureAwait(false); - return; - } - - var oldComment = obj.Comment; - obj.Comment = comment; - await ReplyTextAsync($"Done. Changed existing comment ({oldComment}) to ({comment}).").ConfigureAwait(false); - } - - [Command("unbanID")] - [Summary("Bans online user IDs.")] - [RequireSudo] - public async Task UnBanOnlineIDs([Summary("Comma Separated Online IDs")][Remainder] string content) - { - var IDs = GetIDs(content); - var me = KookBot.Runner; - var hub = me.Hub; - hub.Config.TradeAbuse.BannedIDs.RemoveAll(z => IDs.Any(o => o == z.ID)); - await ReplyTextAsync("Done.").ConfigureAwait(false); - } - - [Command("bannedIDSummary")] - [Alias("printBannedID", "bannedIDPrint")] - [Summary("Prints the list of banned online IDs.")] - [RequireSudo] - public async Task PrintBannedOnlineIDs() - { - var me = KookBot.Runner; - var hub = me.Hub; - var lines = hub.Config.TradeAbuse.BannedIDs.Summarize(); - var msg = string.Join("\n", lines); - await ReplyTextAsync(Format.Code(msg)).ConfigureAwait(false); - } - - [Command("forgetUser")] - [Alias("forget")] - [Summary("Forgets users that were previously encountered.")] - [RequireSudo] - public async Task ForgetPreviousUser([Summary("Comma Separated Online IDs")][Remainder] string content) - { - var IDs = GetIDs(content); - foreach (var ID in IDs) - { - PokeRoutineExecutorBase.PreviousUsers.RemoveAllNID(ID); - PokeRoutineExecutorBase.PreviousUsersDistribution.RemoveAllNID(ID); - } - await ReplyTextAsync("Done.").ConfigureAwait(false); - } - - [Command("previousUserSummary")] - [Alias("prevUsers")] - [Summary("Prints a list of previously encountered users.")] - [RequireSudo] - public async Task PrintPreviousUsers() - { - bool found = false; - var lines = PokeRoutineExecutorBase.PreviousUsers.Summarize().ToList(); - if (lines.Count != 0) - { - found = true; - var msg = "Previous Users:\n" + string.Join("\n", lines); - await ReplyTextAsync(Format.Code(msg)).ConfigureAwait(false); - } - - lines = PokeRoutineExecutorBase.PreviousUsersDistribution.Summarize().ToList(); - if (lines.Count != 0) - { - found = true; - var msg = "Previous Distribution Users:\n" + string.Join("\n", lines); - await ReplyTextAsync(Format.Code(msg)).ConfigureAwait(false); - } - if (!found) - await ReplyTextAsync("No previous users found.").ConfigureAwait(false); - } - - private RemoteControlAccess GetReference(IUser channel) => new() - { - ID = channel.Id, - Name = channel.Username, - Comment = $"Added by {Context.User.Username} on {DateTime.Now:yyyy.MM.dd-hh:mm:ss}", - }; - - private RemoteControlAccess GetReference(ulong id) => new() - { - ID = id, - Name = "Manual", - Comment = $"Added by {Context.User.Username} on {DateTime.Now:yyyy.MM.dd-hh:mm:ss}", - }; - - protected static IEnumerable GetIDs(string content) - { - return content.Split([",", ", ", " "], StringSplitOptions.RemoveEmptyEntries) - .Select(z => ulong.TryParse(z, out var x) ? x : 0).Where(z => z != 0); - } -} diff --git a/SysBot.Pokemon.Kook/Commands/Management/TradeStartModule.cs b/SysBot.Pokemon.Kook/Commands/Management/TradeStartModule.cs deleted file mode 100644 index 5dfab1763..000000000 --- a/SysBot.Pokemon.Kook/Commands/Management/TradeStartModule.cs +++ /dev/null @@ -1,124 +0,0 @@ -using Kook; -using Kook.Commands; -using Kook.WebSocket; -using PKHeX.Core; -using SysBot.Base; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace SysBot.Pokemon.Kook; - -public class TradeStartModule : ModuleBase where T : PKM, new() -{ - private class TradeStartAction(ulong ChannelId, Action> messager, string channel) - : ChannelAction>(ChannelId, messager, channel); - - private static readonly Dictionary Channels = []; - - private static void Remove(TradeStartAction entry) - { - Channels.Remove(entry.ChannelID); - KookBot.Runner.Hub.Queues.Forwarders.Remove(entry.Action); - } - -#pragma warning disable RCS1158 // Static member in generic type should use a type parameter. - public static void RestoreTradeStarting(KookSocketClient Kook) - { - var cfg = KookBotSettings.Settings; - foreach (var ch in cfg.TradeStartingChannels) - { - if (Kook.GetChannel(ch.ID) is ISocketMessageChannel c) - AddLogChannel(c, ch.ID); - } - - LogUtil.LogInfo("Added Trade Start Notification to Kook channel(s) on Bot startup.", "Kook"); - } - - public static bool IsStartChannel(ulong cid) -#pragma warning restore RCS1158 // Static member in generic type should use a type parameter. - { - return Channels.TryGetValue(cid, out _); - } - - [Command("startHere")] - [Summary("Makes the bot log trade starts to the channel.")] - [RequireSudo] - public async Task AddLogAsync() - { - var c = Context.Channel; - var cid = c.Id; - if (Channels.TryGetValue(cid, out _)) - { - await ReplyTextAsync("Already logging here.").ConfigureAwait(false); - return; - } - - AddLogChannel(c, cid); - - // Add to Kook global loggers (saves on program close) - KookBotSettings.Settings.TradeStartingChannels.AddIfNew([GetReference(Context.Channel)]); - await ReplyTextAsync("Added Start Notification output to this channel!").ConfigureAwait(false); - } - - private static void AddLogChannel(ISocketMessageChannel c, ulong cid) - { - void Logger(PokeRoutineExecutorBase bot, PokeTradeDetail detail) - { - if (detail.Type == PokeTradeType.Random) - return; - c.SendTextAsync(GetMessage(bot, detail)); - } - - Action> l = Logger; - KookBot.Runner.Hub.Queues.Forwarders.Add(l); - static string GetMessage(PokeRoutineExecutorBase bot, PokeTradeDetail detail) => $"> [{DateTime.Now:hh:mm:ss}] - {bot.Connection.Label} is now trading (ID {detail.ID}) {detail.Trainer.TrainerName}"; - - var entry = new TradeStartAction(cid, l, c.Name); - Channels.Add(cid, entry); - } - - [Command("startInfo")] - [Summary("Dumps the Start Notification settings.")] - [RequireSudo] - public async Task DumpLogInfoAsync() - { - foreach (var c in Channels) - await ReplyTextAsync($"{c.Key} - {c.Value}").ConfigureAwait(false); - } - - [Command("startClear")] - [Summary("Clears the Start Notification settings in that specific channel.")] - [RequireSudo] - public async Task ClearLogsAsync() - { - var cfg = KookBotSettings.Settings; - if (Channels.TryGetValue(Context.Channel.Id, out var entry)) - Remove(entry); - cfg.TradeStartingChannels.RemoveAll(z => z.ID == Context.Channel.Id); - await ReplyTextAsync($"Start Notifications cleared from channel: {Context.Channel.Name}").ConfigureAwait(false); - } - - [Command("startClearAll")] - [Summary("Clears all the Start Notification settings.")] - [RequireSudo] - public async Task ClearLogsAllAsync() - { - foreach (var l in Channels) - { - var entry = l.Value; - await ReplyTextAsync($"Logging cleared from {entry.ChannelName} ({entry.ChannelID}!").ConfigureAwait(false); - KookBot.Runner.Hub.Queues.Forwarders.Remove(entry.Action); - } - Channels.Clear(); - KookBotSettings.Settings.TradeStartingChannels.Clear(); - await ReplyTextAsync("Start Notifications cleared from all channels!").ConfigureAwait(false); - } - - private RemoteControlAccess GetReference(IChannel channel) => new() - { - ID = channel.Id, - Name = channel.Name, - Comment = $"Added by {Context.User.Username} on {DateTime.Now:yyyy.MM.dd-hh:mm:ss}", - }; -} diff --git a/SysBot.Pokemon.Kook/Helpers/AutoLegalityExtensionsKook.cs b/SysBot.Pokemon.Kook/Helpers/AutoLegalityExtensionsKook.cs deleted file mode 100644 index 37b8962a9..000000000 --- a/SysBot.Pokemon.Kook/Helpers/AutoLegalityExtensionsKook.cs +++ /dev/null @@ -1,96 +0,0 @@ -using Kook; -using Kook.WebSocket; -using PKHeX.Core; -using SysBot.Base; -using System; -using System.Threading.Tasks; - -namespace SysBot.Pokemon.Kook; - -public static class AutoLegalityExtensionsKook -{ - public static async Task ReplyWithLegalizedSetAsync(this ISocketMessageChannel channel, ITrainerInfo sav, ShowdownSet set) - { - if (set.Species == 0) - { - await channel.SendTextAsync("Oops! I wasn't able to interpret your message! If you intended to convert something, please double check what you're pasting!").ConfigureAwait(false); - return; - } - - try - { - var template = AutoLegalityWrapper.GetTemplate(set); - var pkm = sav.GetLegal(template, out var result); - var la = new LegalityAnalysis(pkm); - var spec = GameInfo.Strings.Species[template.Species]; - if (!la.Valid) - { - var reason = result switch - { - "Timeout" => $"That {spec} set took too long to generate.", - "VersionMismatch" => "Request refused: PKHeX and Auto-Legality Mod version mismatch.", - _ => $"I wasn't able to create a {spec} from that set.", - }; - var imsg = $"Oops! {reason}"; - if (result == "Failed") - imsg += $"\n{AutoLegalityWrapper.GetLegalizationHint(template, sav, pkm)}"; - await channel.SendTextAsync(imsg).ConfigureAwait(false); - return; - } - - var msg = $"Here's your ({result}) legalized PKM for {spec} ({la.EncounterOriginal.Name})!"; - await channel.SendPKMAsync(pkm, msg + $"\n{ReusableActions.GetFormattedShowdownText(pkm)}").ConfigureAwait(false); - } - catch (Exception ex) - { - LogUtil.LogSafe(ex, nameof(AutoLegalityExtensionsKook)); - var msg = $"Oops! An unexpected problem happened with this Showdown Set:\n```{string.Join("\n", set.GetSetLines())}```"; - await channel.SendTextAsync(msg).ConfigureAwait(false); - } - } - - public static Task ReplyWithLegalizedSetAsync(this ISocketMessageChannel channel, string content, byte gen) - { - content = ReusableActions.StripCodeBlock(content); - var set = new ShowdownSet(content); - var sav = AutoLegalityWrapper.GetTrainerInfo(gen); - return channel.ReplyWithLegalizedSetAsync(sav, set); - } - - public static Task ReplyWithLegalizedSetAsync(this ISocketMessageChannel channel, string content) where T : PKM, new() - { - content = ReusableActions.StripCodeBlock(content); - var set = new ShowdownSet(content); - var sav = AutoLegalityWrapper.GetTrainerInfo(); - return channel.ReplyWithLegalizedSetAsync(sav, set); - } - - public static async Task ReplyWithLegalizedSetAsync(this ISocketMessageChannel channel, IAttachment att) - { - var download = await NetUtil.DownloadPKMAsync(att).ConfigureAwait(false); - if (!download.Success) - { - await channel.SendTextAsync(download.ErrorMessage).ConfigureAwait(false); - return; - } - - var pkm = download.Data!; - if (new LegalityAnalysis(pkm).Valid) - { - await channel.SendTextAsync($"{download.SanitizedFileName}: Already legal.").ConfigureAwait(false); - return; - } - - var legal = pkm.LegalizePokemon(); - if (!new LegalityAnalysis(legal).Valid) - { - await channel.SendTextAsync($"{download.SanitizedFileName}: Unable to legalize.").ConfigureAwait(false); - return; - } - - legal.RefreshChecksum(); - - var msg = $"Here's your legalized PKM for {download.SanitizedFileName}!\n{ReusableActions.GetFormattedShowdownText(legal)}"; - await channel.SendPKMAsync(legal, msg).ConfigureAwait(false); - } -} diff --git a/SysBot.Pokemon.Kook/Helpers/ChannelAction.cs b/SysBot.Pokemon.Kook/Helpers/ChannelAction.cs deleted file mode 100644 index 0c5a759b0..000000000 --- a/SysBot.Pokemon.Kook/Helpers/ChannelAction.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; - -namespace SysBot.Pokemon.Kook; - -public class ChannelAction(ulong ChannelID, Action Messager, string ChannelName) -{ - public readonly ulong ChannelID = ChannelID; - public readonly string ChannelName = ChannelName; - public readonly Action Action = Messager; -} diff --git a/SysBot.Pokemon.Kook/Helpers/ChannelLogger.cs b/SysBot.Pokemon.Kook/Helpers/ChannelLogger.cs deleted file mode 100644 index 82d24e4f4..000000000 --- a/SysBot.Pokemon.Kook/Helpers/ChannelLogger.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using Kook.WebSocket; -using SysBot.Base; - -namespace SysBot.Pokemon.Kook; - -public class ChannelLogger(ulong ChannelID, ISocketMessageChannel Channel) : ILogForwarder -{ - public ulong ChannelID { get; } = ChannelID; - public string ChannelName => Channel.Name; - - public void Forward(string message, string identity) - { - try - { - var text = GetMessage(message, identity); - Channel.SendTextAsync(text); - } - catch (Exception ex) - { - LogUtil.LogSafe(ex, identity); - } - } - private static string GetMessage(ReadOnlySpan msg, string identity) - => $"> [{DateTime.Now:hh:mm:ss}] - {identity}: {msg}"; -} diff --git a/SysBot.Pokemon.Kook/Helpers/KookManager.cs b/SysBot.Pokemon.Kook/Helpers/KookManager.cs deleted file mode 100644 index 9a9f4ad8b..000000000 --- a/SysBot.Pokemon.Kook/Helpers/KookManager.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace SysBot.Pokemon.Kook; - -public class KookManager(KookSettings Config) -{ - public readonly KookSettings Config = Config; - public ulong Owner { get; internal set; } - - public RemoteControlAccessList BlacklistedUsers => Config.UserBlacklist; - public RemoteControlAccessList WhitelistedChannels => Config.ChannelWhitelist; - - public RemoteControlAccessList SudoKook => Config.GlobalSudoList; - public RemoteControlAccessList SudoRoles => Config.RoleSudo; - public RemoteControlAccessList FavoredRoles => Config.RoleFavored; - - public RemoteControlAccessList RolesClone => Config.RoleCanClone; - public RemoteControlAccessList RolesTrade => Config.RoleCanTrade; - public RemoteControlAccessList RolesSeed => Config.RoleCanSeedCheck; - public RemoteControlAccessList RolesDump => Config.RoleCanDump; - public RemoteControlAccessList RolesRemoteControl => Config.RoleRemoteControl; - - public bool CanUseSudo(ulong uid) => SudoKook.Contains(uid); - public bool CanUseSudo(IEnumerable roles) => roles.Any(SudoRoles.Contains); - - public bool CanUseCommandChannel(ulong channel) => (WhitelistedChannels.List.Count == 0 && WhitelistedChannels.AllowIfEmpty) || WhitelistedChannels.Contains(channel); - public bool CanUseCommandUser(ulong uid) => !BlacklistedUsers.Contains(uid); - - public RequestSignificance GetSignificance(IEnumerable roles) - { - var result = RequestSignificance.None; - foreach (var r in roles) - { - if (SudoRoles.Contains(r)) - result = RequestSignificance.Favored; - if (FavoredRoles.Contains(r)) - result = RequestSignificance.Favored; - } - return result; - } - - public bool GetHasRoleAccess(string type, IEnumerable roles) - { - var set = GetSet(type); - return set is { AllowIfEmpty: true, List.Count: 0 } || roles.Any(set.Contains); - } - - private RemoteControlAccessList GetSet(string type) => type switch - { - nameof(RolesClone) => RolesClone, - nameof(RolesTrade) => RolesTrade, - nameof(RolesSeed) => RolesSeed, - nameof(RolesDump) => RolesDump, - nameof(RolesRemoteControl) => RolesRemoteControl, - _ => throw new ArgumentOutOfRangeException(nameof(type)), - }; -} diff --git a/SysBot.Pokemon.Kook/Helpers/KookTradeNotifier.cs b/SysBot.Pokemon.Kook/Helpers/KookTradeNotifier.cs deleted file mode 100644 index e05f624aa..000000000 --- a/SysBot.Pokemon.Kook/Helpers/KookTradeNotifier.cs +++ /dev/null @@ -1,86 +0,0 @@ -using Kook; -using Kook.WebSocket; -using PKHeX.Core; -using SysBot.Pokemon.Kook; -using System; -using System.Linq; - -namespace SysBot.Pokemon.Kook; - -public class KookTradeNotifier(T Data, PokeTradeTrainerInfo Info, int Code, SocketUser Trader) - : IPokeTradeNotifier - where T : PKM, new() -{ - private T Data { get; } = Data; - private PokeTradeTrainerInfo Info { get; } = Info; - private int Code { get; } = Code; - private SocketUser Trader { get; } = Trader; - public Action>? OnFinish { private get; set; } - public readonly PokeTradeHub Hub = KookBot.Runner.Hub; - - public void TradeInitialize(PokeRoutineExecutor routine, PokeTradeDetail info) - { - var receive = Data.Species == 0 ? string.Empty : $" ({Data.Nickname})"; - Trader.SendTextAsync($"Initializing trade{receive}. Please be ready. Your code is {Format.Bold($"{Code: 0000 0000}")}.").ConfigureAwait(false); - } - - public void TradeSearching(PokeRoutineExecutor routine, PokeTradeDetail info) - { - var name = Info.TrainerName; - var trainer = string.IsNullOrEmpty(name) ? string.Empty : $", {name}"; - Trader.SendTextAsync($"I'm waiting for you{trainer}! Your code is {Format.Bold($"{Code:0000 0000}")}. My IGN is {Format.Bold($"{routine.InGameName}")}.").ConfigureAwait(false); - } - - public void TradeCanceled(PokeRoutineExecutor routine, PokeTradeDetail info, PokeTradeResult msg) - { - OnFinish?.Invoke(routine); - Trader.SendTextAsync($"Trade canceled: {msg}").ConfigureAwait(false); - } - - public void TradeFinished(PokeRoutineExecutor routine, PokeTradeDetail info, T result) - { - OnFinish?.Invoke(routine); - var tradedToUser = Data.Species; - var message = tradedToUser != 0 ? $"Trade finished. Enjoy your {(Species)tradedToUser}!" : "Trade finished!"; - Trader.SendTextAsync(message).ConfigureAwait(false); - if (result.Species != 0 && Hub.Config.Kook.ReturnPKMs) - Trader.SendPKMAsync(result, "Here's what you traded me!").ConfigureAwait(false); - } - - public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, string message) - { - Trader.SendTextAsync(message).ConfigureAwait(false); - } - - public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, PokeTradeSummary message) - { - if (message.ExtraInfo is SeedSearchResult r) - { - SendNotificationZ3(r); - return; - } - - var msg = message.Summary; - if (message.Details.Count > 0) - msg += ", " + string.Join(", ", message.Details.Select(z => $"{z.Heading}: {z.Detail}")); - Trader.SendTextAsync(msg).ConfigureAwait(false); - } - - public void SendNotification(PokeRoutineExecutor routine, PokeTradeDetail info, T result, string message) - { - if (result.Species != 0 && (Hub.Config.Kook.ReturnPKMs || info.Type == PokeTradeType.Dump)) - Trader.SendPKMAsync(result, message).ConfigureAwait(false); - } - - private void SendNotificationZ3(SeedSearchResult r) - { - var lines = r.ToString(); - - var card = new CardBuilder() - .AddModule(new SectionModuleBuilder().WithText($"Here are the details for `{r.Seed:X16}`:")) - .AddModule(new SectionModuleBuilder().WithText($"Seed: {r.Seed:X16}")) - .AddModule(new SectionModuleBuilder().WithText(lines)) - .Build(); - Trader.SendCardAsync(card).ConfigureAwait(false); - } -} diff --git a/SysBot.Pokemon.Kook/Helpers/NetUtil.cs b/SysBot.Pokemon.Kook/Helpers/NetUtil.cs deleted file mode 100644 index 78da4d4ba..000000000 --- a/SysBot.Pokemon.Kook/Helpers/NetUtil.cs +++ /dev/null @@ -1,157 +0,0 @@ -using Kook; -using PKHeX.Core; -using SysBot.Pokemon.Helpers; -using System; -using System.Collections; -using System.Net.Http; -using System.Reflection; -using System.Threading.Tasks; - -namespace SysBot.Pokemon.Kook; - -public static class NetUtil -{ - public static async Task DownloadFromUrlAsync(string url) - { - using var client = new HttpClient(); - return await client.GetByteArrayAsync(url).ConfigureAwait(false); - } - - private static readonly HashSet SupportedTypes = - [ - typeof(PK8), typeof(PA8), typeof(PB8), typeof(PK9), typeof(PA9), - ]; - - public static async Task>> DownloadPKMsAsync(IAttachment att, Type type) - { - // 统一使用本地函数生成错误结果 - static Download> Error(string name, string msg) - { - return new() - { - SanitizedFileName = name, - ErrorMessage = $"{name}: {msg}" - }; - } - - var fileName = Format.Sanitize(att.Filename); - if (string.IsNullOrWhiteSpace(fileName)) - { - return new Download> - { - SanitizedFileName = "unknown", - ErrorMessage = "Invalid filename." - }; - } - - if (!SupportedTypes.Contains(type)) - return Error(fileName, "Unsupported PKM type."); - - var url = att.Url; - - byte[]? buffer; - try - { - buffer = await DownloadFromUrlAsync(url).ConfigureAwait(false); - } - catch (Exception ex) - { - return Error(fileName, $"Failed to download file: {ex.Message}"); - } - - if (buffer is null || buffer.Length == 0) - return Error(fileName, "Empty or failed download."); - - try - { - // 构造泛型类型 FileTradeHelper - var helperType = typeof(FileTradeHelper<>).MakeGenericType(type); - - // 取得 ValidBinFileSize(int length) 静态方法 - var validMethod = helperType.GetMethod( - "ValidBinFileSize", - BindingFlags.Static | BindingFlags.Public, - binder: null, - types: [typeof(int)], - modifiers: null - ); - if (validMethod is null) - return Error(fileName, "Helper type missing ValidBinFileSize method."); - - // 取得 Bin2List(byte[]) 静态方法 - var bin2ListMethod = helperType.GetMethod( - "Bin2List", - BindingFlags.Static | BindingFlags.Public, - binder: null, - types: [typeof(byte[])], - modifiers: null - ); - if (bin2ListMethod is null) - return Error(fileName, "Helper type missing Bin2List method."); - - // 调用 ValidBinFileSize - if (validMethod.Invoke(null, [buffer.Length]) is not bool valid) - return Error(fileName, "Invalid return type from ValidBinFileSize."); - - if (!valid) - return Error(fileName, "Invalid .bin attachment size."); - - // 调用 Bin2List(byte[]),应返回 IEnumerable / List - var listObj = bin2ListMethod.Invoke(null, [buffer]); - if (listObj is not IEnumerable enumerable) - return Error(fileName, "Unexpected return type from Bin2List."); - - var pkms = enumerable.Cast().ToList(); - - return new Download> - { - SanitizedFileName = fileName, - Data = pkms, - Success = true - }; - } - catch (TargetInvocationException ex) - { - // 反射内层异常 - return Error(fileName, $"Exception while processing file (inner): {ex.InnerException?.Message ?? ex.Message}"); - } - catch (Exception ex) - { - return Error(fileName, $"Exception while processing file: {ex.Message}"); - } - } - - public static async Task> DownloadPKMAsync(IAttachment att) - { - var result = new Download { SanitizedFileName = Format.Sanitize(att.Filename) }; - if (!EntityDetection.IsSizePlausible(att.Size ?? 0)) - { - result.ErrorMessage = $"{result.SanitizedFileName}: Invalid size."; - return result; - } - - string url = att.Url; - - // Download the resource and load the bytes into a buffer. - var buffer = await DownloadFromUrlAsync(url).ConfigureAwait(false); - var prefer = EntityFileExtension.GetContextFromExtension(result.SanitizedFileName); - var pkm = EntityFormat.GetFromBytes(buffer, prefer); - if (pkm == null) - { - result.ErrorMessage = $"{result.SanitizedFileName}: Invalid pkm attachment."; - return result; - } - - result.Data = pkm; - result.Success = true; - return result; - } -} - -public sealed class Download where T : class -{ - public bool Success; - public T? Data; - public string? SanitizedFileName; - public string? ErrorMessage; -} diff --git a/SysBot.Pokemon.Kook/Helpers/QueueHelper.cs b/SysBot.Pokemon.Kook/Helpers/QueueHelper.cs deleted file mode 100644 index 734c04701..000000000 --- a/SysBot.Pokemon.Kook/Helpers/QueueHelper.cs +++ /dev/null @@ -1,141 +0,0 @@ -using Kook; -using Kook.Commands; -using Kook.Net; -using Kook.WebSocket; -using PKHeX.Core; -using System.Threading.Tasks; - -namespace SysBot.Pokemon.Kook; - -public static class QueueHelper where T : PKM, new() -{ - private const uint MaxTradeCode = 9999_9999; - - public static async Task AddToQueueAsync(SocketCommandContext context, int code, string trainer, RequestSignificance sig, T trade, PokeRoutineType routine, PokeTradeType type, SocketUser trader) - { - if ((uint)code > MaxTradeCode) - { - await context.Channel.SendTextAsync("Trade code should be 00000000-99999999!").ConfigureAwait(false); - return; - } - - try - { - const string helper = "I've added you to the queue! I'll message you here when your trade is starting."; - var test = await trader.SendTextAsync(helper).ConfigureAwait(false); - - // Try adding - var result = AddToTradeQueue(context, trade, code, trainer, sig, routine, type, trader, out var msg); - - // Notify in channel - await context.Channel.SendTextAsync(msg).ConfigureAwait(false); - // Notify in PM to mirror what is said in the channel. - await trader.SendTextAsync($"{msg}\nYour trade code will be {Format.Bold($"{code:0000 0000}")}.").ConfigureAwait(false); - - // Clean Up - if (result) - { - // Delete the user's join message for privacy - if (!context.IsPrivate) - await context.Message.DeleteAsync(RequestOptions.Default).ConfigureAwait(false); - } - else - { - // Delete our "I'm adding you!", and send the same message that we sent to the general channel. - // KOOK.Net 中,无法删除私信消息,直接忽略此步骤 - // await test.DeleteAsync().ConfigureAwait(false); - } - } - catch (HttpException ex) - { - await HandleKookExceptionAsync(context, trader, ex).ConfigureAwait(false); - } - } - - public static Task AddToQueueAsync(SocketCommandContext context, int code, string trainer, RequestSignificance sig, T trade, PokeRoutineType routine, PokeTradeType type) - { - return AddToQueueAsync(context, code, trainer, sig, trade, routine, type, context.User); - } - - private static bool AddToTradeQueue(SocketCommandContext context, T pk, int code, string trainerName, RequestSignificance sig, PokeRoutineType type, PokeTradeType t, SocketUser trader, out string msg) - { - var user = trader; - var userID = user.Id; - var name = user.Username; - - var trainer = new PokeTradeTrainerInfo(trainerName, userID); - var notifier = new KookTradeNotifier(pk, trainer, code, user); - var detail = new PokeTradeDetail(pk, trainer, notifier, t, code, sig == RequestSignificance.Favored); - var trade = new TradeEntry(detail, userID, type, name); - - var hub = KookBot.Runner.Hub; - var Info = hub.Queues.Info; - var added = Info.AddToTradeQueue(trade, userID, sig == RequestSignificance.Owner); - - if (added == QueueResultAdd.AlreadyInQueue) - { - msg = "Sorry, you are already in the queue."; - return false; - } - - var position = Info.CheckPosition(userID, type); - - var ticketID = ""; - if (TradeStartModule.IsStartChannel(context.Channel.Id)) - ticketID = $", unique ID: {detail.ID}"; - - var pokeName = ""; - if (t == PokeTradeType.Specific && pk.Species != 0) - pokeName = $" Receiving: {GameInfo.GetStrings("en").Species[pk.Species]}."; - msg = $"{user.KMarkdownMention} - Added to the {type} queue{ticketID}. Current Position: {position.Position}.{pokeName}"; - - var botct = Info.Hub.Bots.Count; - if (position.Position > botct) - { - var eta = Info.Hub.Config.Queues.EstimateDelay(position.Position, botct); - msg += $" Estimated: {eta:F1} minutes."; - } - return true; - } - - private static async Task HandleKookExceptionAsync(SocketCommandContext context, SocketUser trader, HttpException ex) - { - string message = string.Empty; - switch (ex.KookCode) - { - case KookErrorCode.MissingPermissions: - { - // Check if the exception was raised due to missing "Send Messages" or "Manage Messages" permissions. Nag the bot owner if so. - var permissions = context.Guild.CurrentUser.GetPermissions(context.Channel as IGuildChannel); - if (!permissions.SendMessages) - { - // Nag the owner in logs. - message = "You must grant me \"Send Messages\" permissions!"; - Base.LogUtil.LogError(message, "QueueHelper"); - return; - } - if (!permissions.ManageMessages) - { - //var app = await context.Client.GetApplicationInfoAsync().ConfigureAwait(false); - //var owner = app.Owner.Id; - var owner = KookBotSettings.Manager.Owner; - message = $"{MentionUtils.KMarkdownMentionUser(owner)} You must grant me \"Manage Messages\" permissions!"; - } - } - break; - //case KookErrorCode.CannotSendMessageToUser: - //{ - // // The user either has DMs turned off, or Kook thinks they do. - // message = context.User == trader ? "You must enable private messages in order to be queued!" : "The mentioned user must enable private messages in order for them to be queued!"; - //} - // break; - default: - { - // Send a generic error message. - message = ex.KookCode != null ? $"Kook error {(int)ex.KookCode}: {ex.Reason}" : $"Http error {(int)ex.HttpCode}: {ex.Message}"; - } - break; - } - await context.Channel.SendTextAsync(message).ConfigureAwait(false); - } -} diff --git a/SysBot.Pokemon.Kook/Helpers/RequireOwnerAttribute.cs b/SysBot.Pokemon.Kook/Helpers/RequireOwnerAttribute.cs deleted file mode 100644 index 41f74a801..000000000 --- a/SysBot.Pokemon.Kook/Helpers/RequireOwnerAttribute.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Kook.Commands; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace SysBot.Pokemon.Kook; - -public class RequireOwnerAttribute : PreconditionAttribute -{ - public override Task CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services) - { - ulong ownerId = KookBotSettings.Manager.Owner; - - if (context.User.Id == ownerId) - return Task.FromResult(PreconditionResult.FromSuccess()); - else - return Task.FromResult(PreconditionResult.FromError("Only the Bot owner can use this command.")); - } -} diff --git a/SysBot.Pokemon.Kook/Helpers/RequireQueueRoleAttribute.cs b/SysBot.Pokemon.Kook/Helpers/RequireQueueRoleAttribute.cs deleted file mode 100644 index a5af1ff56..000000000 --- a/SysBot.Pokemon.Kook/Helpers/RequireQueueRoleAttribute.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Kook.Commands; -using Kook.WebSocket; -using System; -using System.Linq; -using System.Threading.Tasks; - -namespace SysBot.Pokemon.Kook; - -///