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;
}