Skip to content

Commit

Permalink
2024.03.10
Browse files Browse the repository at this point in the history
- Add support for Mighty Infernape
- Add new command `ctc` for change trade code for users to supply their own set trade code.
- Add abuse channels and command that echoes abuse msgs such as multiple ID's found trading, etc. (Use command `aaec` to add the channel id to your abuse channel list.)
- Bump app version to 3.0
  • Loading branch information
bdawg1989 committed Oct 4, 2024
1 parent c8ae4d6 commit 66630cc
Show file tree
Hide file tree
Showing 15 changed files with 341 additions and 41 deletions.
21 changes: 10 additions & 11 deletions SysBot.Base/Util/EchoUtil.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,24 @@
using System.Collections.Generic;

namespace SysBot.Base;

public static class EchoUtil
{
public static readonly List<Action<string>> Forwarders = [];
public static readonly List<Action<string>> AbuseForwarders = [];

public static void Echo(string message)
{
foreach (var fwd in Forwarders)
{
try
{
fwd(message);
}
catch (Exception ex)
{
LogUtil.LogInfo($"Exception: {ex} occurred while trying to echo: {message} to the forwarder: {fwd}", "Echo");
LogUtil.LogSafe(ex, "Echo");
}
fwd(message);
}
}

public static void EchoAbuseMessage(string message)
{
foreach (var fwd in AbuseForwarders)
{
fwd(message);
}
LogUtil.LogInfo(message, "Echo");
}
}
96 changes: 96 additions & 0 deletions SysBot.Pokemon.Discord/Commands/Bots/QueueModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using PKHeX.Core;
using SysBot.Base;
using System;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace SysBot.Pokemon.Discord;
Expand Down Expand Up @@ -196,4 +197,99 @@ private async Task ReplyAndDeleteAsync(string message, int delaySeconds, IMessag
LogUtil.LogSafe(ex, nameof(QueueModule<T>));
}
}

[Command("changeTradeCode")]
[Alias("ctc")]
[Summary("Changes the user's trade code if trade code storage is turned on.")]
public async Task ChangeTradeCodeAsync([Summary("New 8-digit trade code")] string newCode)
{
// Delete user's message immediately to protect the trade code
await Context.Message.DeleteAsync().ConfigureAwait(false);

var userID = Context.User.Id;
var tradeCodeStorage = new TradeCodeStorage();

if (!QueueModule<T>.ValidateTradeCode(newCode, out string errorMessage))
{
await SendTemporaryMessageAsync(errorMessage).ConfigureAwait(false);
return;
}

try
{
int code = int.Parse(newCode);
var existingDetails = tradeCodeStorage.GetTradeDetails(userID);

if (existingDetails == null)
{
await SendTemporaryMessageAsync("You don't have a trade code set. Use the trade command to generate one first.").ConfigureAwait(false);
return;
}

existingDetails.Code = code;
tradeCodeStorage.UpdateTradeDetails(userID, existingDetails.OT, existingDetails.TID, existingDetails.SID);

Check warning on line 230 in SysBot.Pokemon.Discord/Commands/Bots/QueueModule.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'ot' in 'void TradeCodeStorage.UpdateTradeDetails(ulong trainerID, string ot, int tid, int sid)'.

Check warning on line 230 in SysBot.Pokemon.Discord/Commands/Bots/QueueModule.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'ot' in 'void TradeCodeStorage.UpdateTradeDetails(ulong trainerID, string ot, int tid, int sid)'.

await SendTemporaryMessageAsync("Your trade code has been successfully updated.").ConfigureAwait(false);
}
catch (Exception ex)
{
LogUtil.LogError($"Error changing trade code for user {userID}: {ex.Message}", nameof(QueueModule<T>));
await SendTemporaryMessageAsync("An error occurred while changing your trade code. Please try again later.").ConfigureAwait(false);
}
}

private async Task SendTemporaryMessageAsync(string message)
{
var sentMessage = await ReplyAsync(message).ConfigureAwait(false);
_ = Task.Run(async () =>
{
await Task.Delay(TimeSpan.FromSeconds(10));
await sentMessage.DeleteAsync().ConfigureAwait(false);
});
}

private static bool ValidateTradeCode(string code, out string errorMessage)
{
errorMessage = string.Empty;

if (code.Length != 8)
{
errorMessage = "Trade code must be exactly 8 digits long.";
return false;
}

if (!Regex.IsMatch(code, @"^\d{8}$"))
{
errorMessage = "Trade code must contain only digits.";
return false;
}

if (QueueModule<T>.IsEasilyGuessableCode(code))
{
errorMessage = "Trade code is too easy to guess. Please choose a more complex code.";
return false;
}

return true;
}

private static bool IsEasilyGuessableCode(string code)
{
string[] easyPatterns = [
@"^(\d)\1{7}$", // All same digits (e.g., 11111111)
@"^12345678$", // Ascending sequence
@"^87654321$", // Descending sequence
@"^(?:01234567|12345678|23456789)$" // Other common sequences
];

foreach (var pattern in easyPatterns)
{
if (Regex.IsMatch(code, pattern))
{
return true;
}
}

return false;
}
}
78 changes: 77 additions & 1 deletion SysBot.Pokemon.Discord/Commands/Management/EchoModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ private class EncounterEchoChannel(ulong channelId, string channelName, Action<s

private static readonly Dictionary<ulong, EncounterEchoChannel> EncounterChannels = [];

private static readonly Dictionary<ulong, EchoChannel> AbuseChannels = [];

public static void RestoreChannels(DiscordSocketClient discord, DiscordSettings cfg)
{
Settings = cfg;
Expand All @@ -72,8 +74,82 @@ public static void RestoreChannels(DiscordSocketClient discord, DiscordSettings
if (discord.GetChannel(ch.ID) is ISocketMessageChannel c)
AddEchoChannel(c, ch.ID);
}
foreach (var ch in cfg.AbuseLogChannels)
{
if (discord.GetChannel(ch.ID) is ISocketMessageChannel c)
AddAbuseEchoChannel(c, ch.ID);
}
}

[Command("AddAbuseEchoChannel")]
[Alias("aaec")]
[Summary("Makes the bot post abuse logs to the channel.")]
[RequireSudo]
public async Task AddAbuseEchoAsync()
{
var c = Context.Channel;
var cid = c.Id;
if (AbuseChannels.TryGetValue(cid, out _))
{
await ReplyAsync("Already logging abuse in this channel.").ConfigureAwait(false);
return;
}

AddAbuseEchoChannel(c, cid);
SysCordSettings.Settings.AbuseLogChannels.AddIfNew([GetReference(Context.Channel)]);
await ReplyAsync("Added Abuse Log output to this channel!").ConfigureAwait(false);
}

private static void AddAbuseEchoChannel(ISocketMessageChannel c, ulong cid)
{
async void l(string msg) => await SendMessageWithRetry(c, msg).ConfigureAwait(false);
EchoUtil.AbuseForwarders.Add(l);
var entry = new EchoChannel(cid, c.Name, l, null);

Check warning on line 107 in SysBot.Pokemon.Discord/Commands/Management/EchoModule.cs

View workflow job for this annotation

GitHub Actions / build

Cannot convert null literal to non-nullable reference type.
AbuseChannels.Add(cid, entry);
}

public static bool IsAbuseEchoChannel(ISocketMessageChannel c)
{
var cid = c.Id;
return AbuseChannels.TryGetValue(cid, out _);
}

[Command("RemoveAbuseEchoChannel")]
[Alias("raec")]
[Summary("Removes the abuse logging from the channel.")]
[RequireSudo]
public async Task RemoveAbuseEchoAsync()
{
var id = Context.Channel.Id;
if (!AbuseChannels.TryGetValue(id, out var echo))
{
await ReplyAsync("Not logging abuse in this channel.").ConfigureAwait(false);
return;
}
AbuseChannels.Remove(id);
SysCordSettings.Settings.AbuseLogChannels.RemoveAll(z => z.ID == id);
await ReplyAsync($"Abuse logging removed from channel: {Context.Channel.Name}").ConfigureAwait(false);
}

[Command("ListAbuseEchoChannels")]
[Alias("laec")]
[Summary("Lists all channels where abuse logging is enabled.")]
[RequireSudo]
public async Task ListAbuseEchoChannelsAsync()
{
if (AbuseChannels.Count == 0)
{
await ReplyAsync("No channels are currently set up for abuse logging.").ConfigureAwait(false);
return;
}

var response = "Abuse logging is enabled in the following channels:\n";
foreach (var channel in AbuseChannels.Values)
{
response += $"- {channel.ChannelName} (ID: {channel.ChannelID})\n";
}

// EchoUtil.Echo("Added echo notification to Discord channel(s) on Bot startup.");
await ReplyAsync(response).ConfigureAwait(false);
}

[Command("Announce", RunMode = RunMode.Async)]
Expand Down
2 changes: 1 addition & 1 deletion SysBot.Pokemon.Discord/Helpers/ReusableActions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public static async Task EchoAndReply(this ISocketMessageChannel channel, string
{
// 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))
if (!EchoModule.IsEchoChannel(channel) && !EchoModule.IsAbuseEchoChannel(channel))
await channel.SendMessageAsync(msg).ConfigureAwait(false);
}

Expand Down
97 changes: 90 additions & 7 deletions SysBot.Pokemon/Actions/PokeRoutineExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using static SysBot.Base.SwitchButton;
using System.Threading;
using System.Threading.Tasks;

Expand Down Expand Up @@ -140,18 +141,28 @@ public async Task VerifyBotbaseVersion(CancellationToken token)

// Tesla Menu
// dmnt used for cheats
protected PokeTradeResult CheckPartnerReputation(PokeRoutineExecutor<T> bot, PokeTradeDetail<T> poke, ulong TrainerNID, string TrainerName,
protected async Task<PokeTradeResult> CheckPartnerReputation(PokeRoutineExecutor<T> bot, PokeTradeDetail<T> poke, ulong TrainerNID, string TrainerName,
TradeAbuseSettings AbuseSettings, 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;

// Matches to a list of banned NIDs, in case the user ever manages to enter a trade.
var entry = AbuseSettings.BannedIDs.List.Find(z => z.ID == TrainerNID);
if (entry != null)
{
if (AbuseSettings.BlockDetectedBannedUser && bot is PokeRoutineExecutor8SWSH)
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.EchoAbuseMessage(msg);
return PokeTradeResult.SuspiciousActivity;
}

Expand All @@ -163,22 +174,74 @@ protected PokeTradeResult CheckPartnerReputation(PokeRoutineExecutor<T> bot, Pok
Log($"Last traded with {user.TrainerName} {delta.TotalMinutes:F1} minutes ago (OT: {TrainerName}).");

// Allows setting a cooldown for repeat trades. If the same user is encountered within the cooldown period for the same trade type, the user is warned and the trade will be ignored.
var cd = AbuseSettings.TradeCooldown; // Time they must wait before trading again.
var cd = AbuseSettings.TradeCooldown; // Time they must wait before trading again.
if (cd != 0 && TimeSpan.FromMinutes(cd) > delta)
{
Log($"Found {user.TrainerName} ignoring the {cd} minute trade cooldown. Last encountered {delta.TotalMinutes:F1} minutes ago.");
var wait = TimeSpan.FromMinutes(cd) - delta;
poke.Notifier.SendNotification(bot, poke, $"You are still on trade cooldown, and cannot trade for another {wait.TotalMinutes:F1} minute(s).");
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.EchoAbuseMessage(msg);
return PokeTradeResult.SuspiciousActivity;
}

// For distribution trades only, flag users using multiple Discord/Twitch accounts to send to the same in-game player within the TradeAbuseExpiration time limit.
// For non-Distribution trades, flag users using multiple Discord/Twitch accounts to send to the same in-game player within a time limit.
// This is usually to evade a ban or a trade cooldown.
if (isDistribution && previous.NetworkID == TrainerNID && previous.RemoteID != user.ID)
if (!isDistribution && previous.NetworkID == TrainerNID && previous.RemoteID != user.ID)
{
if (delta < TimeSpan.FromMinutes(AbuseSettings.TradeAbuseExpiration) && AbuseSettings.TradeAbuseAction != TradeAbuseAction.Ignore)
{
if (AbuseSettings.TradeAbuseAction == TradeAbuseAction.BlockAndQuit)
{
await BlockUser(token).ConfigureAwait(false);
if (AbuseSettings.BanIDWhenBlockingUser || bot is not PokeRoutineExecutor8SWSH) // Only ban ID if blocking in SWSH, always in other games.
{
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 traded with {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.EchoAbuseMessage(msg);
}
}

// For non-Distribution trades, we can optionally flag users sending to multiple in-game players.
// Can trigger if the user gets sniped, but can also catch abusers sending to many people.
if (!isDistribution)
{
var previous_remote = PreviousUsers.TryGetPreviousRemoteID(poke.Trainer.ID);
if (previous_remote != null && previous_remote.Name != TrainerName)
{
if (delta < TimeSpan.FromMinutes(AbuseSettings.TradeAbuseExpiration))
if (AbuseSettings.TradeAbuseAction != TradeAbuseAction.Ignore)
{
if (AbuseSettings.TradeAbuseAction == TradeAbuseAction.BlockAndQuit)
{
await BlockUser(token).ConfigureAwait(false);
if (AbuseSettings.BanIDWhenBlockingUser || bot is not PokeRoutineExecutor8SWSH) // Only ban ID if blocking in SWSH, always in other games.
{
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;
Log($"Found {user.TrainerName} using multiple accounts.\nPreviously traded with {previous.Name} ({previous.RemoteID}) {delta.TotalMinutes:F1} minutes ago on OT: {TrainerName}.");
}

var msg = $"Found {user.TrainerName}{useridmsg} sending to multiple in-game players. Previous OT: {previous_remote.Name}, Current OT: {TrainerName}";
if (AbuseSettings.EchoNintendoOnlineIDMultiRecipients)
msg += $"\nID: {TrainerNID}";
if (!string.IsNullOrWhiteSpace(AbuseSettings.MultiRecipientEchoMention))
msg = $"{AbuseSettings.MultiRecipientEchoMention} {msg}";
EchoUtil.Echo(msg);
}
}

Expand All @@ -193,4 +256,24 @@ protected PokeTradeResult CheckPartnerReputation(PokeRoutineExecutor<T> bot, Pok
var solved = await SwitchConnection.PointerAll(jumps, token).ConfigureAwait(false);
return (solved != 0, solved);
}

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})",
};

// Blocks a user from the box during in-game trades (SWSH).
private async Task BlockUser(CancellationToken token)
{
Log("Blocking user in-game...");
await PressAndHold(RSTICK, 0_750, 0, token).ConfigureAwait(false);
await Click(DUP, 0_300, token).ConfigureAwait(false);
await Click(A, 1_300, token).ConfigureAwait(false);
await Click(A, 1_300, token).ConfigureAwait(false);
await Click(DUP, 0_300, token).ConfigureAwait(false);
await Click(A, 1_100, token).ConfigureAwait(false);
await Click(A, 1_100, token).ConfigureAwait(false);
}
}
Loading

0 comments on commit 66630cc

Please sign in to comment.