using DeluxeBackend.Models; using DeluxeBackend.Services; using Discord; using Discord.Interactions; using Discord.WebSocket; using System.Security.Cryptography; using static DeluxeBackend.Enums; namespace DeluxeBackend.Discord.Commands { public class LinkCode : InteractionModuleBase { private readonly ILiteDbService _db; private readonly INotificationService _ws; private static readonly HashSet GameRoles = new() { 1502718783700078767, 1495587327593021490, 1495580320525844581, 1495580213529280563, 1495909230643908729, 1500323661251477635, 1495580027453046804, 1501711512996282369, 1495580006712213674, 1495580717134905457 }; public LinkCode(ILiteDbService db, INotificationService ws) { _db = db; _ws = ws; } [SlashCommand("ping", "Check bot latency")] public async Task Ping() => await RespondAsync($"{Context.Client.Latency}ms"); [SlashCommand("link", "Generate a unique code to link your Deluxe account")] public async Task LinkAccount() { await DeferAsync(ephemeral: true); if (Context.User is not SocketGuildUser guildUser) { await FollowupAsync("This command must be used within a server."); return; } if (!guildUser.Roles.Any(r => GameRoles.Contains(r.Id))) { await FollowupAsync("You do not have the required permissions.", ephemeral: true); return; } Account? account = _db.Accounts.FindOne(x => x.DiscordId == guildUser.Id); if (account != null) { var linkedEmbed = new EmbedBuilder() .WithDescription("Your Discord profile is **already linked** to a Deluxe account.") .WithColor(Color.LighterGrey) .Build(); await FollowupAsync(embed: linkedEmbed, ephemeral: true); return; } var pendingLink = _db.ActionLinks.FindOne(x => x.ExtraData!.DiscordUserId == guildUser.Id && x.ExpiresAt > DateTime.UtcNow && x.Uses < x.MaxUses); if (pendingLink != null) { long unixTime = ((DateTimeOffset)pendingLink.ExpiresAt).ToUnixTimeSeconds(); var existingEmbed = new EmbedBuilder() .WithDescription($"You already have an active code:\n\n**`{pendingLink.Code}`**\n\nIt expires **** ().") .WithColor(Color.LighterGrey) .Build(); await FollowupAsync(embed: existingEmbed, ephemeral: true); return; } string code; while (true) { code = GenerateSecureCode(8); var existing = _db.ActionLinks.FindOne(x => x.Code == code); if (existing == null) break; if (!existing.IsValid) { _db.ActionLinks.Delete(existing.Id); break; } } DateTime expiry = DateTime.UtcNow.AddMinutes(10); long expiryUnix = ((DateTimeOffset)expiry).ToUnixTimeSeconds(); var actionLink = new ActionLink { Code = code, CreatorPlayerId = 1, Description = $"Discord Link: {guildUser.Username}", ExpiresAt = expiry, Type = ActionLinkType.DiscordLink, ExtraData = new ActionLinkExtraData { DiscordUserId = guildUser.Id }, MaxUses = 1 }; _db.ActionLinks.Insert(actionLink); var embed = new EmbedBuilder() .WithDescription($"Use the code below to link your account:\n\n**`{code}`**\n\nExpires ****.") .WithColor(Color.LighterGrey) .Build(); await FollowupAsync(embed: embed, ephemeral: true); } private static string GenerateSecureCode(int length) { const string chars = "abcdefghijklmnopqrstuvwxyz0123456789"; return string.Create(length, chars, (buffer, symbols) => { for (int i = 0; i < buffer.Length; i++) { buffer[i] = symbols[RandomNumberGenerator.GetInt32(symbols.Length)]; } }); } } }