module Degenz.Admin open System open System.IO open System.Reflection open System.Threading.Tasks open DSharpPlus open DSharpPlus.Entities open DSharpPlus.EventArgs open DSharpPlus.SlashCommands open Degenz.Messaging open Npgsql.FSharp type InitEmbeds = | Dojo = 0 | Whitelist = 1 | Slots = 2 | JpegStore = 3 let handleGuildDownloadReady (_ : DiscordClient) (event : GuildDownloadCompletedEventArgs) = task { let ( _ , guild ) = event.Guilds.TryGetValue(GuildEnvironment.guildId) let! commands = guild.GetApplicationCommandsAsync() let ( _ , adminRole ) = guild.Roles.TryGetValue(GuildEnvironment.roleAdmin) let permission = DiscordApplicationCommandPermission(adminRole, true) let commands = commands |> Seq.map (fun com -> DiscordGuildApplicationCommandPermissions(com.Id, [ permission ])) do! guild.BatchEditApplicationCommandPermissionsAsync(commands) |> Async.AwaitTask |> Async.Ignore return () } :> Task let sendEmbed embed (ctx : IDiscordContext) = task { match embed with | InitEmbeds.Dojo -> Trainer.sendInitialEmbed ctx | InitEmbeds.Whitelist -> InviteTracker.sendInitialEmbed ctx | InitEmbeds.Slots -> SlotMachine.sendInitialEmbedFromSlashCommand ctx | InitEmbeds.JpegStore -> Store.sendInitialEmbed ctx | _ -> () do! Messaging.sendSimpleResponse ctx "Sent!" } :> Task let getAllReactions (msg : DiscordMessage) (ctx : IDiscordContext) : Task = task { let mutable listOfUsers = ResizeArray(512) let mutable emojiCount = 0 for reaction in msg.Reactions do let mutable count = 0 for r in 0..reaction.Count / 100 do let rs = if count > 0 then msg.GetReactionsAsync(reaction.Emoji, 100, listOfUsers.[listOfUsers.Count - 1].Id) |> Async.AwaitTask |> Async.RunSynchronously else msg.GetReactionsAsync(reaction.Emoji, 100) |> Async.AwaitTask |> Async.RunSynchronously listOfUsers.AddRange(rs) count <- count + 1 let builder = DiscordFollowupMessageBuilder().AsEphemeral(true) builder.Content <- $"Reading {emojiCount + 1} out of {msg.Reactions.Count}: {reaction.Emoji.Name} with {reaction.Count} reactions" do! ctx.FollowUp builder do! Async.Sleep 1800 emojiCount <- emojiCount + 1 return listOfUsers } let getCsvFromUsersList users = users |> List.ofSeq |> List.map (fun (user : DiscordUser) -> {| Id = user.Id ; FullName = $"{user.Username}#{user.Discriminator}" |}) |> List.distinctBy (fun u -> u.Id) |> List.map (fun user -> $"{user.Id},{user.FullName}") |> String.concat "\n" |> (+) "Discord Id Num,Username\n" let getCsvFromTotalInvites (users : {| Username : string ; DiscordId : uint64 ; TotalInvites : int |} seq) = users |> Seq.map (fun user -> $"{user.DiscordId},{user.Username},{user.TotalInvites}") |> String.concat "\n" |> (+) "Discord Id Num,Username,Total Invites\n" let getUserInvites userIds = GuildEnvironment.connectionString |> Sql.connect |> Sql.parameters [ "dids" , Sql.stringArray (userIds |> Seq.toArray) ] |> Sql.query """ SELECT usr.display_name, inviter, count(invite_id) AS total_invites FROM invite JOIN invited_user iu ON invite.id = iu.invite_id JOIN "user" usr ON invite.inviter = usr.discord_id WHERE iu.accepted = true AND inviter = ANY (@dids) GROUP BY inviter, usr.display_name ORDER BY total_invites DESC; """ |> Sql.executeAsync (fun read -> {| Username = read.string "display_name" ; DiscordId = read.string "inviter" |> uint64 ; TotalInvites = read.int "total_invites" |}) |> Async.AwaitTask let getUsersFromMessageReactions (channel : DiscordChannel) (message : string) (ctx : IDiscordContext) = task { do! Messaging.defer ctx let ( result , mId ) = UInt64.TryParse(message) if result then let! msg = channel.GetMessageAsync(message |> uint64) |> Async.AwaitTask let! reactions = getAllReactions msg ctx let csv = getCsvFromUsersList reactions let builder = DiscordFollowupMessageBuilder().AsEphemeral(true) use ms = new MemoryStream(Text.Encoding.ASCII.GetBytes(csv)) :> Stream let dict = Collections.Generic.Dictionary() dict.Add("users.csv", ms) builder.AddFiles(dict) |> ignore do! ctx.FollowUp(builder) else do! Messaging.sendSimpleResponse ctx $"The message ID provided was not a valid: {message}" } :> Task let getUserInvitesFromReactions (channel : DiscordChannel) (message : string) (ctx : IDiscordContext) = task { do! Messaging.defer ctx let! msg = channel.GetMessageAsync(message |> uint64) |> Async.AwaitTask let! users = getAllReactions msg ctx try let! invites = getUserInvites (users |> Seq.map (fun u -> string u.Id)) let csv = getCsvFromTotalInvites (invites) let builder = DiscordFollowupMessageBuilder().AsEphemeral(true) use ms = new MemoryStream(Text.Encoding.ASCII.GetBytes(csv)) :> Stream let dict = Collections.Generic.Dictionary() dict.Add("users.csv", ms) builder.AddFiles(dict) |> ignore do! ctx.FollowUp(builder) with ex -> printfn $"exception {ex.Message}" } :> Task type AdminBot() = inherit ApplicationCommandModule () let enforceAdmin (ctx : IDiscordContext) (adminFn : IDiscordContext -> Task) = let isAdmin = Seq.exists (fun (role : DiscordRole) -> role.Id = GuildEnvironment.roleAdmin) (ctx.GetDiscordMember().Roles) if isAdmin then adminFn ctx else Messaging.sendSimpleResponse ctx $"You are not admin" |> Async.StartAsTask :> Task [] [] member this.GetAttributions (ctx : InteractionContext, [] user : DiscordUser) = enforceAdmin (DiscordInteractionContext ctx) (InviteTracker.getInvitedUsersForId user) [] [] member this.SetStock (ctx : InteractionContext, [] amount : int64) = enforceAdmin (DiscordInteractionContext ctx) (InviteTracker.setCurrentWhitelistStock (int amount)) [] [] member this.SendEmbedToChannel (ctx : InteractionContext, [] embed : InitEmbeds) = enforceAdmin (DiscordInteractionContext ctx) (sendEmbed embed) [] [] member this.GetMessageReactions (ctx : InteractionContext, [] channel : DiscordChannel, [] messageId : string) = enforceAdmin (DiscordInteractionContext ctx) (getUsersFromMessageReactions channel messageId) [] [] member this.GetInvitesFromReactedMessages (ctx : InteractionContext, [] channel : DiscordChannel, [] messageId : string) = enforceAdmin (DiscordInteractionContext ctx) (getUserInvitesFromReactions channel messageId)