294 lines
13 KiB
Forth
294 lines
13 KiB
Forth
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<DiscordUser seq> =
|
|
task {
|
|
let mutable listOfUsers = ResizeArray<DiscordUser>(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
|
|
|
|
[<SlashCommandPermissions(Permissions.Administrator)>]
|
|
[<SlashCommand("admin-invites", "Get total invites from a specific user", false)>]
|
|
member this.GetAttributions (ctx : InteractionContext, [<Option("player", "The player you want to check")>] user : DiscordUser) =
|
|
enforceAdmin (DiscordInteractionContext ctx) (InviteTracker.getInvitedUsersForId user)
|
|
|
|
[<SlashCommandPermissions(Permissions.Administrator)>]
|
|
[<SlashCommand("admin-whitelist-stock", "Set whitelist stock", false)>]
|
|
member this.SetStock (ctx : InteractionContext, [<Option("amount", "Set the amount of WL available for purchase")>] amount : int64) =
|
|
enforceAdmin (DiscordInteractionContext ctx) (Whitelist.setCurrentWhitelistStock (int amount * 1<GBT>))
|
|
|
|
[<SlashCommandPermissions(Permissions.Administrator)>]
|
|
[<SlashCommand("admin-embed-send", "Set whitelist stock", false)>]
|
|
member this.SendEmbedToChannel (ctx : InteractionContext, [<Option("embed", "Which embed to send")>] embed : InitEmbeds) =
|
|
enforceAdmin (DiscordInteractionContext ctx) (sendEmbed embed)
|
|
|
|
[<SlashCommandPermissions(Permissions.Administrator)>]
|
|
[<SlashCommand("admin-embed-update", "Set whitelist stock", false)>]
|
|
member this.UpdateEmbedInChannel (ctx : InteractionContext, [<Option("embed", "Which embed to send")>] embed : InitEmbeds) =
|
|
enforceAdmin (DiscordInteractionContext ctx) (updateEmbed embed)
|
|
|
|
[<SlashCommandPermissions(Permissions.Administrator)>]
|
|
[<SlashCommand("admin-get-msg-reactions", "Set whitelist stock", false)>]
|
|
member this.GetMessageReactions (ctx : InteractionContext,
|
|
[<Option("channel", "The channel where the message is")>] channel : DiscordChannel,
|
|
[<Option("message-id", "The ID of the message with all the reactions")>] messageId : string) =
|
|
enforceAdmin (DiscordInteractionContext ctx) (getUsersFromMessageReactions channel messageId)
|
|
|
|
[<SlashCommandPermissions(Permissions.Administrator)>]
|
|
[<SlashCommand("admin-get-invites-table", "Invites Table", false)>]
|
|
member this.GetInvitesFromReactedMessages (ctx : InteractionContext,
|
|
[<Option("channel", "The channel where the message is")>] channel : DiscordChannel,
|
|
[<Option("message-id", "The ID of the message with all the reactions")>] messageId : string) =
|
|
enforceAdmin (DiscordInteractionContext ctx) (getUserInvitesFromReactions channel messageId)
|
|
|
|
[<SlashCommand("admin-raffles-winners", "Get N Random Winners")>]
|
|
member this.GetRaffleWinners (ctx : InteractionContext, [<Option("count", "How many winners to pick")>] count : int64) =
|
|
enforceAdmin (DiscordInteractionContext ctx) (getRaffleWinners count)
|
|
|
|
// [<SlashCommand("admin-raffles-toggle", "Toggle availability of an item")>]
|
|
// member this.ToggleRaffle (ctx : InteractionContext, [<Option("enabled", "Enable or Disable?")>] enable : EnableDisable) =
|
|
// enforceAdmin (DiscordInteractionContext ctx) (toggleRaffleAvailability (enable = EnableDisable.Enable))
|
|
|
|
|
|
|
|
|