diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 6f63919..4aa7264 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -2,6 +2,7 @@ 1. [Prerequisites](#prerequisites) 2. [Structure Overview](#overview) 3. [Coding Guidelines](#guidelines) +4. [Dev Notes](#devnotes) # Prerequisites @@ -93,4 +94,8 @@ private List FindFactors(uint number) { } private async Task ReportToCIA() { } public uint ComputeFactorial(uint number) { } public async Task ProcessPayment() { } -``` \ No newline at end of file +``` + + +# Dev Notes +1. Use `Coc.GetMember` or `Coc.TryGetMember` if you want to check whether a player tag is currently in the clan. \ No newline at end of file diff --git a/Bot/.gitignore b/Bot/.gitignore index 41ab7f6..099ff68 100644 --- a/Bot/.gitignore +++ b/Bot/.gitignore @@ -8,6 +8,8 @@ Hyperstellar.db Hyperstellar hyperstellar-token.txt +.idea + # User-specific files *.rsuser *.suo diff --git a/Bot/Discord/Cmds.cs b/Bot/Discord/Cmds.cs index 61f3fed..9b806ef 100644 --- a/Bot/Discord/Cmds.cs +++ b/Bot/Discord/Cmds.cs @@ -11,7 +11,7 @@ namespace Hyperstellar.Discord; public class Cmds : InteractionModuleBase { [RequireOwner] - [SlashCommand("shutdown", "Shuts down the bot")] + [SlashCommand("shutdown", "[Owner] Shuts down the bot")] public async Task ShutdownAsync(bool commit = true) { await RespondAsync("Ok", ephemeral: true); @@ -23,7 +23,7 @@ public async Task ShutdownAsync(bool commit = true) } [RequireOwner] - [SlashCommand("commit", "Commits db")] + [SlashCommand("commit", "[Owner] Commits db")] public async Task CommitAsync() { Db.Commit(); @@ -31,7 +31,7 @@ public async Task CommitAsync() } [RequireOwner] - [SlashCommand("admin", "Makes the Discord user an admin")] // Maybe rename to addadmin + [SlashCommand("admin", "[Owner] Makes the Discord user an admin")] // Maybe rename to addadmin public async Task AdminAsync(SocketGuildUser user) { bool success = Db.AddAdmin(user.Id); @@ -46,7 +46,7 @@ public async Task AdminAsync(SocketGuildUser user) } [RequireAdmin] - [SlashCommand("alt", "Links an alt to a main")] + [SlashCommand("alt", "[Admin] Links an alt to a main")] public async Task AltAsync(Member alt, Member main) { if (alt.CocId == main.CocId) @@ -72,8 +72,8 @@ public async Task AltAsync(Member alt, Member main) } [RequireAdmin] - [SlashCommand("link", "[Admin] Links a Discord account to a Main")] - public async Task LinkAsync(Member coc, IGuildUser discord) + [SlashCommand("discord", "[Admin] Links a Discord account to a Main")] + public async Task DiscordAsync(Member coc, IGuildUser discord) { Main? main = coc.TryToMain(); if (main == null) @@ -131,7 +131,7 @@ public async Task InfoAsync(Member clanMember) } [RequireAdmin] - [SlashCommand("alias", "Sets an alias for a Coc member")] + [SlashCommand("alias", "[Admin] Sets an alias for a Coc member")] public async Task AliasAsync(string alias, Member member) { bool success = Db.AddAlias(alias, member); diff --git a/Bot/Discord/MemberConverter.cs b/Bot/Discord/MemberConverter.cs index 75de7a9..1fbba22 100644 --- a/Bot/Discord/MemberConverter.cs +++ b/Bot/Discord/MemberConverter.cs @@ -1,7 +1,10 @@ -using Discord; +using System.Text.RegularExpressions; +using ClashOfClans.Models; +using Discord; using Discord.Interactions; using Hyperstellar.Clash; using Hyperstellar.Sql; +using Type = System.Type; namespace Hyperstellar.Discord; @@ -12,22 +15,74 @@ internal sealed class MemberConverter : TypeConverter public override Task ReadAsync(IInteractionContext context, IApplicationCommandInteractionDataOption option, IServiceProvider services) { string input = (string)option.Value; - // Console.WriteLine($"\"{input}\""); - Member? member = Db.GetMember(input); - if (member == null) + + // Check whether input matches a Discord user mention + Match dUserMentionMatch = Regexes.DiscordUserMention().Match(input); + if (dUserMentionMatch.Success) { - string? id = Coc.GetMemberId(input); - if (id != null) + // Extract uid from mention + ReadOnlySpan captured = dUserMentionMatch.ValueSpan; // <@123> + ulong uid = Convert.ToUInt64(captured[2..^1].ToString()); // 123 + + // Checks whether this Discord user is linked to a main + Main? main = Db.GetMainByDiscord(uid); + if (main == null) { - member = Db.GetMember(id); + return TypeConverters.Error("This Discord user isn't linked to any CoC account."); } + + // REMOVE THIS AFTER DB REDESIGN - SKIPPING THE CHECK BELOW BECUZ FOR NOW, IN DB = MUST BE IN CLAN + Member sqlMember = Db.GetMember(main.MainId)!; + return TypeConverters.Success(sqlMember); + + /* + // Check whether the main is still in the clan + string cocId = main.MainId; + ClanMember? member = Coc.TryGetMember(cocId); + + return member == null + ? TypeConverters.Error("The main of this Discord user isn't in the clan.") + : Task.FromResult(TypeConverterResult.FromSuccess(member)); + */ + } + + // Check whether input matches an alias + CocMemberAlias? dbAlias = Db.TryGetAlias(input); + if (dbAlias != null) + { + // Check whether the coc account of the alias is still in the clan + string aliasCocId = dbAlias.CocId; + ClanMember? aliasClanMember = Coc.TryGetMember(aliasCocId); + Member sqlMember = Db.GetMember(aliasCocId)!; + + return aliasClanMember == null + ? TypeConverters.Error("The player of this alias isn't in the clan.") + : TypeConverters.Success(sqlMember); } - return member == null - ? Task.FromResult(TypeConverterResult.FromError( - InteractionCommandError.ConvertFailed, - @$"Invalid `{option.Name}`. To specify a clan member: -* Enter his name (非速本主義Arkyo), alias (Arkyo) or ID with # (#28QL0CJV2) -* Mention his Discord (@Dim) __which will refer to his main__")) - : Task.FromResult(TypeConverterResult.FromSuccess(member)); + + // Check whether input is the name of a clan member + string? cocId = Coc.GetMemberId(input); + if (cocId != null) + { + Member sqlMember = Db.GetMember(cocId)!; + return TypeConverters.Success(sqlMember); + } + + // Check whether input is the tag of a clan member + ClanMember? member = Coc.TryGetMember(input); + if (member != null) + { + Member sqlMember = Db.GetMember(input)!; + return TypeConverters.Success(sqlMember); + } + + return TypeConverters.Error( + $""" + Invalid `{option.Name}`. To specify a clan member: + * Enter his name (非速本主義Arkyo), alias (arkyo) or ID with # (#28QL0CJV2) + * Mention his Discord (@Dim) __which will refer to his main__ + """); } + + } diff --git a/Bot/Discord/TypeConverters.cs b/Bot/Discord/TypeConverters.cs new file mode 100644 index 0000000..842a8ac --- /dev/null +++ b/Bot/Discord/TypeConverters.cs @@ -0,0 +1,13 @@ +using Discord.Interactions; + +namespace Hyperstellar.Discord; + +// Util class for other type converters in this package +internal static class TypeConverters +{ + // Shorthand for generating an error response + internal static Task Error(string reason) => Task.FromResult(TypeConverterResult.FromError(InteractionCommandError.ConvertFailed, reason)); + + // Shorthand for generating a successful conversion + internal static Task Success(object value) => Task.FromResult(TypeConverterResult.FromSuccess(value)); +} diff --git a/Bot/Regexes.cs b/Bot/Regexes.cs new file mode 100644 index 0000000..3de8d57 --- /dev/null +++ b/Bot/Regexes.cs @@ -0,0 +1,9 @@ +using System.Text.RegularExpressions; + +namespace Hyperstellar; + +internal static partial class Regexes +{ + [GeneratedRegex("^<@\\d{1,20}>$")] + internal static partial Regex DiscordUserMention(); +} diff --git a/Bot/Sql/Db.cs b/Bot/Sql/Db.cs index a888986..56cf20a 100644 --- a/Bot/Sql/Db.cs +++ b/Bot/Sql/Db.cs @@ -27,6 +27,8 @@ internal static bool AddMembers(string[] members) internal static Main? GetMain(string id) => s_db.Table
().FirstOrDefault(d => d.MainId == id); + internal static Main? GetMainByDiscord(ulong uid) => s_db.Table
().FirstOrDefault(m => m.Discord == uid); + internal static IEnumerable
GetDonations() => s_db.Table
(); internal static bool UpdateMain(Main main) => s_db.Update(main) == 1; @@ -41,11 +43,17 @@ internal static bool AddAdmin(ulong id) return count == 1; } + internal static CocMemberAlias? TryGetAlias(string alias) + { + alias = alias.ToLower(); + return s_db.Table().FirstOrDefault(a => a.Alias == alias); + } + internal static IEnumerable GetAliases() => s_db.Table(); internal static bool AddAlias(string alias, Member member) { - CocMemberAlias cocMemberAlias = new(alias, member.CocId); + CocMemberAlias cocMemberAlias = new(alias.ToLower(), member.CocId); int count = s_db.Insert(cocMemberAlias); return count == 1; }