module Degenz.Admin open System open System.IO 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 | Recruit = 2 | Slots = 3 | JpegStore = 4 | Armory = 5 | Wallet = 6 type EnableDisable = | Enable = 0 | Disable = 1 let handleGuildDownloadReady _ (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 -> Whitelist.sendInitialEmbed ctx | InitEmbeds.Recruit -> InviteTracker.sendInitialEmbed ctx | InitEmbeds.Slots -> SlotMachine.sendInitialEmbedFromSlashCommand ctx | InitEmbeds.JpegStore -> Store.sendBackalleyEmbed ctx | InitEmbeds.Armory -> Store.sendArmoryEmbed ctx | InitEmbeds.Wallet -> InviteTracker.sendSubmitEmbed ctx | _ -> () do! Messaging.sendSimpleResponse ctx "Sent!" } :> Task let updateEmbed embed (ctx : IDiscordContext) = task { match embed with | InitEmbeds.Dojo -> Trainer.sendInitialEmbed ctx | InitEmbeds.Whitelist -> Whitelist.sendInitialEmbed ctx | InitEmbeds.Recruit -> InviteTracker.sendInitialEmbed ctx | InitEmbeds.Slots -> SlotMachine.sendInitialEmbedFromSlashCommand ctx | InitEmbeds.JpegStore -> Store.sendBackalleyEmbed ctx | InitEmbeds.Armory -> Store.sendArmoryEmbed ctx | InitEmbeds.Wallet -> InviteTracker.sendSubmitEmbed 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 getCsvFromRaffleTickets (users : {| Id : uint64 ; Name : string ; TicketsOwned : int |} seq) = users |> Seq.map (fun user -> $"{user.Id},{user.Name},{user.TicketsOwned}") |> String.concat "\n" |> (+) "Discord Id Num,Username,Tickets Owned\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 let getRaffleWinners count (ctx : IDiscordContext) = task { do! Messaging.defer ctx let! raffleItems = [ "BACKALLEY1" ; "BACKALLEY2" ; "BACKALLEY3" ] |> List.map DbService.getStoreItems |> Async.Parallel let buttons = raffleItems |> Seq.concat |> Seq.map (fun item -> DiscordButtonComponent(ButtonStyle.Primary, $"GetRaffleWinner-{item.Item.Id}-{count}", $"{item.Item.Name}") :> DiscordComponent) let builder = DiscordFollowupMessageBuilder().AsEphemeral(true) buttons |> Seq.chunkBySize 5 |> Seq.iter (fun btns -> builder.AddComponents(btns) |> ignore) builder.Content <- "Click on the respective item" do! ctx.FollowUp builder } :> Task let pickRaffleWinners (ctx : IDiscordContext) = task { do! Messaging.defer ctx let tokens = ctx.GetInteractionId().Split('-') let itemId = tokens.[1] let count = int tokens.[2] let! winners = DbService.connStr |> Sql.connect |> Sql.parameters [ "iid", Sql.string itemId ; "winner_count" , Sql.int count] |> Sql.query """ SELECT discord_id, count(*) AS tickets_owned, display_name FROM "user" INNER JOIN inventory_item ii ON "user".id = ii.user_id WHERE ii.item_id = @iid GROUP BY discord_id, display_name ORDER BY random() LIMIT @winner_count; """ |> Sql.executeAsync (fun read -> {| Id = read.string "discord_id" |> uint64 Name = read.string "display_name" TicketsOwned = read.int "tickets_owned"|}) |> Async.AwaitTask let csv = getCsvFromRaffleTickets winners 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 } :> Task let toggleRaffleAvailability enable (ctx : IDiscordContext) = task { do! Messaging.defer ctx } :> Task let handleButtonEvent _ (event : ComponentInteractionCreateEventArgs) = let eventCtx = DiscordEventContext event :> IDiscordContext match event.Id with | id when id.StartsWith("GetRaffleWinner") -> pickRaffleWinners eventCtx | _ -> task { let builder = DiscordInteractionResponseBuilder() builder.IsEphemeral <- true builder.Content <- $"Incorrect Action identifier {eventCtx.GetInteractionId()}" do! eventCtx.Respond(InteractionResponseType.ChannelMessageWithSource, builder) |> Async.AwaitTask } 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) (Whitelist.setCurrentWhitelistStock (int amount * 1)) [] [] member this.SendEmbedToChannel (ctx : InteractionContext, [] embed : InitEmbeds) = enforceAdmin (DiscordInteractionContext ctx) (sendEmbed embed) [] [] member this.UpdateEmbedInChannel (ctx : InteractionContext, [] embed : InitEmbeds) = enforceAdmin (DiscordInteractionContext ctx) (updateEmbed 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) [] member this.GetRaffleWinners (ctx : InteractionContext, [] count : int64) = enforceAdmin (DiscordInteractionContext ctx) (getRaffleWinners count) // [] // member this.ToggleRaffle (ctx : InteractionContext, [] enable : EnableDisable) = // enforceAdmin (DiscordInteractionContext ctx) (toggleRaffleAvailability (enable = EnableDisable.Enable))