diff --git a/Bot/Bot.fsproj b/Bot/Bot.fsproj index 2def2ea..073b522 100644 --- a/Bot/Bot.fsproj +++ b/Bot/Bot.fsproj @@ -13,17 +13,19 @@ - + + + - - - - - - + + + + + + diff --git a/Bot/Game.fs b/Bot/Game.fs deleted file mode 100644 index 0b85933..0000000 --- a/Bot/Game.fs +++ /dev/null @@ -1,337 +0,0 @@ -namespace Degenz - -open System -open System.Threading.Tasks -open DSharpPlus -open DSharpPlus.Entities -open DSharpPlus.EventArgs -open DSharpPlus.SlashCommands -open Newtonsoft.Json - -[] -module Types = - - [] - type mins - - [] - type GBT - - type HackId = - | Virus = 0 - | RemoteAccess = 1 - | Worm = 2 - - type ShieldId = - | Firewall = 6 - | Encryption = 7 - | Cypher = 8 - - type ItemType = - | Hack = 0 - | Shield = 1 - | Food = 1 - - type ItemAttributes = { - Sell : bool - Buy : bool - Consume : bool - Drop : bool - } - with static member empty = { Sell = false ; Buy = false ; Consume = false ; Drop = false } - - type Item = { - Id : int - Name : string - Price : int - Type : ItemType - Power : int - Class : int - Cooldown : int - Attributes : ItemAttributes - } - with static member empty = - { Id = -1 - Name = "None" - Price = 0 - Type = ItemType.Hack - Power = 0 - Class = -1 - Cooldown = 0 - Attributes = ItemAttributes.empty } - - type HackResult = - | Strong - | Weak - - [] - type DiscordPlayer = { Id: uint64; Name: string } - with static member empty = { Id = 0uL ; Name = "None" } - - type HackEvent = { - IsInstigator : bool - Adversary : DiscordPlayer - Success : bool - HackId : int - } - - type PlayerEventType = - | Hacking of HackEvent - | Shielding of shieldId : int - | Stealing of instigator : bool * adversary : DiscordPlayer - | Imprison - - type PlayerEvent = - { Type : PlayerEventType - Cooldown : int - Timestamp : DateTime } - - type PlayerTraits = { - Strength : int - Focus : int - Luck : int - Charisma : int - } - with static member empty = { Strength = 0 ; Focus = 0 ; Luck = 0 ; Charisma = 0 } - - [] - type PlayerData = { - DiscordId : uint64 - Name : string - Inventory : Item array - Events : PlayerEvent array - Traits : PlayerTraits - Bank : int - } -// Achievements : string array -// XP : int - with member this.basicPlayer = { Id = this.DiscordId ; Name = this.Name } - static member empty = - { DiscordId = 0uL - Name = "None" - Inventory = [||] - Events = [||] - Traits = PlayerTraits.empty -// Achievements = [||] -// XP = 0 - Bank = 0 } - -module Armory = - let battleItems = - let file = System.IO.File.ReadAllText("Items.json") -// let file = System.IO.File.ReadAllText("Bot/Items.json") - JsonConvert.DeserializeObject(file) - - let hacks = battleItems |> Array.filter (fun bi -> match bi.Type with ItemType.Hack -> true | _ -> false) - let shields = battleItems |> Array.filter (fun bi -> match bi.Type with ItemType.Shield -> true | _ -> false) - - let getItem itemId = battleItems |> Array.find (fun w -> w.Id = itemId) - -module Messaging = - type InteractiveMessage = { - ButtonId : string - ButtonText : string - Message : string - } - - type DiscordContext = - | Interaction of InteractionContext - | Event of ComponentInteractionCreateEventArgs - - type IDiscordContext = - abstract member Respond : InteractionResponseType -> Task - abstract member Respond : InteractionResponseType * DiscordInteractionResponseBuilder -> Task - abstract member FollowUp : DiscordFollowupMessageBuilder -> Task - abstract member GetDiscordMember : unit -> DiscordMember - abstract member GetGuild : unit -> DiscordGuild - abstract member GetInteractionId : unit -> string - abstract member GetChannel : unit -> DiscordChannel - abstract member GetContext : unit -> obj - - type DiscordInteractionContext(ctx : InteractionContext) = - interface IDiscordContext with - member this.Respond responseType = - async { - do! ctx.Interaction.CreateResponseAsync(responseType) |> Async.AwaitTask - } |> Async.StartAsTask :> Task - member this.Respond (responseType, builder) = - async { - do! ctx.Interaction.CreateResponseAsync(responseType, builder) |> Async.AwaitTask - } |> Async.StartAsTask :> Task - member this.FollowUp(builder) = - async { - do! ctx.Interaction.CreateFollowupMessageAsync(builder) |> Async.AwaitTask |> Async.Ignore - } |> Async.StartAsTask :> Task - member this.GetDiscordMember() = ctx.Member - member this.GetGuild() = ctx.Guild - member this.GetInteractionId() = string ctx.InteractionId - member this.GetChannel() = ctx.Channel - member this.GetContext() = ctx - - type DiscordEventContext(ctx : ComponentInteractionCreateEventArgs) = - interface IDiscordContext with - member this.Respond responseType = - async { - do! ctx.Interaction.CreateResponseAsync(responseType) |> Async.AwaitTask - } |> Async.StartAsTask :> Task - member this.Respond (responseType, builder) = - async { - do! ctx.Interaction.CreateResponseAsync(responseType, builder) |> Async.AwaitTask - } |> Async.StartAsTask :> Task - member this.FollowUp(builder) = - async { - do! ctx.Interaction.CreateFollowupMessageAsync(builder) |> Async.AwaitTask |> Async.Ignore - } |> Async.StartAsTask :> Task - member this.GetDiscordMember() = ctx.User :?> DiscordMember - member this.GetGuild() = ctx.Guild - member this.GetInteractionId() = ctx.Id - member this.GetChannel() = ctx.Channel - member this.GetContext() = ctx - - let getTimeText isCooldown (timespan : TimeSpan) timestamp = - let span = - if isCooldown - then timespan - (DateTime.UtcNow - timestamp) - else (DateTime.UtcNow - timestamp) - let plural amount = if amount = 1 then "" else "s" - let ``and`` = if span.Hours > 0 then "and " else "" - let hours = if span.Hours > 0 then $"{span.Hours} hour{plural span.Hours} {``and``}" else String.Empty - let totalMins = span.Minutes - let minutes = if totalMins > 0 then $"{totalMins} minute{plural totalMins}" else "1 minute" - $"{hours}{minutes}" - - let getShortTimeText (timespan : TimeSpan) timestamp = - let remaining = timespan - (DateTime.UtcNow - timestamp) - let hours = if remaining.Hours > 0 then $"{remaining.Hours}h " else String.Empty - let minutesRemaining = if remaining.Hours = 0 then remaining.Minutes + 1 else remaining.Minutes - $"{hours}{minutesRemaining}min" - - let defer (ctx: IDiscordContext) = async { - let builder = DiscordInteractionResponseBuilder() - builder.IsEphemeral <- true - do! ctx.Respond(InteractionResponseType.DeferredChannelMessageWithSource, builder) |> Async.AwaitTask - } - - let sendSimpleResponse (ctx: IDiscordContext) msg = - async { - let builder = DiscordInteractionResponseBuilder() - builder.Content <- msg - builder.AsEphemeral true |> ignore - do! ctx.Respond(InteractionResponseType.ChannelMessageWithSource, builder) |> Async.AwaitTask - } - - let sendFollowUpMessage (ctx : IDiscordContext) msg = - async { - let builder = DiscordFollowupMessageBuilder() - builder.IsEphemeral <- true - builder.Content <- msg - do! ctx.FollowUp(builder) |> Async.AwaitTask - } - - let sendFollowUpEmbed (ctx : IDiscordContext) embed = - async { - let builder = - DiscordFollowupMessageBuilder() - .AsEphemeral(true) - .AddEmbed(embed) - do! ctx.FollowUp(builder) |> Async.AwaitTask - } - - let sendFollowUpMessageWithButton (ctx : IDiscordContext) interactiveMessage = - async { - let builder = DiscordFollowupMessageBuilder() - let button = DiscordButtonComponent(ButtonStyle.Success, interactiveMessage.ButtonId, interactiveMessage.ButtonText) :> DiscordComponent - builder.AddComponents [| button |] |> ignore - builder.IsEphemeral <- true - builder.Content <- interactiveMessage.Message - do! ctx.FollowUp(builder) |> Async.AwaitTask - } - - let updateMessageWithGreyedOutButtons (ctx : IDiscordContext) interactiveMessage = - async { - let builder = DiscordInteractionResponseBuilder() - let button = DiscordButtonComponent(ButtonStyle.Success, interactiveMessage.ButtonId, interactiveMessage.ButtonText, true) :> DiscordComponent - builder.AddComponents [| button |] |> ignore - builder.IsEphemeral <- true - builder.Content <- interactiveMessage.Message - do! ctx.Respond(InteractionResponseType.UpdateMessage, builder) |> Async.AwaitTask - } - - let handleResultWithResponse ctx fn (player : Result) = - match player with - | Ok p -> fn p - | Error e -> async { do! sendFollowUpMessage ctx e } - -open Messaging - -module Game = - let SameTargetAttackCooldown = System.TimeSpan.FromHours(1) - - let getClassButtonColor = function - | 0 -> ButtonStyle.Danger - | 1 -> ButtonStyle.Primary - | _ -> ButtonStyle.Success - - let getClassEmbedColor = function - | 0 -> DiscordColor.Red - | 1 -> DiscordColor.Blurple - | _ -> DiscordColor.Green - - let getGoodAgainst = function - | 0 -> ( ShieldId.Firewall , HackId.Virus ) - | 1 -> ( ShieldId.Encryption , HackId.RemoteAccess ) - | _ -> ( ShieldId.Cypher , HackId.Worm ) - -module Player = - let getItems itemType (player : PlayerData) = player.Inventory |> Array.filter (fun i -> i.Type = itemType) - let getHacks (player : PlayerData) = getItems ItemType.Hack player - let getShields (player : PlayerData) = getItems ItemType.Shield player - let getHackEvents player = - player.Events - |> Array.filter (fun act -> match act.Type with PlayerEventType.Hacking h -> h.IsInstigator | _ -> false) - let getShieldEvents player = - player.Events - |> Array.filter (fun act -> match act.Type with PlayerEventType.Shielding _ -> true | _ -> false) - - let removeExpiredActions player = - let actions = - player.Events - |> Array.filter (fun (act : PlayerEvent) -> - let cooldown = System.TimeSpan.FromMinutes(int act.Cooldown) - System.DateTime.UtcNow - act.Timestamp < cooldown) - { player with Events = actions } - - let modifyBank (player : PlayerData) amount = { player with Bank = max (player.Bank + amount) 0 } - -module Arsenal = - let battleItemFormat (items : Item array) = - match items with - | [||] -> "None" - | _ -> items |> Array.toList |> List.map (fun i -> i.Name) |> String.concat ", " - - let actionFormat (actions : PlayerEvent array) = - match actions with - | [||] -> "None" - | acts -> - acts - |> Array.map (fun act -> - match act.Type with - | Hacking h -> - let item = Armory.getItem h.HackId - let cooldown = Messaging.getTimeText false Game.SameTargetAttackCooldown act.Timestamp - $"Hacked {h.Adversary.Name} with {item.Name} {cooldown} ago" - | Shielding id -> - let item = Armory.getItem id - let cooldown = Messaging.getTimeText true (System.TimeSpan.FromMinutes(int act.Cooldown)) act.Timestamp - $"{item.Name} Shield active for {cooldown}" - | _ -> "") - |> Array.filter (System.String.IsNullOrWhiteSpace >> not) - |> String.concat "\n" - - let statusFormat p = - let hacks = Player.getHackEvents p - $"**Hacks:** {Player.getHacks p |> battleItemFormat}\n - **Shields:** {Player.getShields p |> battleItemFormat}\n - **Hack Attacks:**\n{ hacks |> Array.take (min hacks.Length 10) |> actionFormat}\n - **Active Shields:**\n{Player.getShieldEvents p |> actionFormat}" - diff --git a/Bot/GameHelpers.fs b/Bot/GameHelpers.fs new file mode 100644 index 0000000..3d494ea --- /dev/null +++ b/Bot/GameHelpers.fs @@ -0,0 +1,88 @@ +namespace Degenz + +open DSharpPlus +open DSharpPlus.Entities +open Newtonsoft.Json + +module Armory = + let battleItems = + let file = System.IO.File.ReadAllText("Items.json") +// let file = System.IO.File.ReadAllText("Bot/Items.json") + JsonConvert.DeserializeObject(file) + + let hacks = battleItems |> Array.filter (fun bi -> match bi.Type with ItemType.Hack -> true | _ -> false) + let shields = battleItems |> Array.filter (fun bi -> match bi.Type with ItemType.Shield -> true | _ -> false) + + let getItem itemId = battleItems |> Array.find (fun w -> w.Id = itemId) + +module WeaponClass = + let SameTargetAttackCooldown = System.TimeSpan.FromHours(1) + + let getClassButtonColor = function + | 0 -> ButtonStyle.Danger + | 1 -> ButtonStyle.Primary + | _ -> ButtonStyle.Success + + let getClassEmbedColor = function + | 0 -> DiscordColor.Red + | 1 -> DiscordColor.Blurple + | _ -> DiscordColor.Green + + let getGoodAgainst = function + | 0 -> ( ShieldId.Firewall , HackId.Virus ) + | 1 -> ( ShieldId.Encryption , HackId.RemoteAccess ) + | _ -> ( ShieldId.Cypher , HackId.Worm ) + +module Player = + let getItems itemType (player : PlayerData) = player.Inventory |> Array.filter (fun i -> i.Type = itemType) + let getHacks (player : PlayerData) = getItems ItemType.Hack player + let getShields (player : PlayerData) = getItems ItemType.Shield player + let getHackEvents player = + player.Events + |> Array.filter (fun act -> match act.Type with PlayerEventType.Hacking h -> h.IsInstigator | _ -> false) + let getShieldEvents player = + player.Events + |> Array.filter (fun act -> match act.Type with PlayerEventType.Shielding _ -> true | _ -> false) + + let removeExpiredActions player = + let actions = + player.Events + |> Array.filter (fun (act : PlayerEvent) -> + let cooldown = System.TimeSpan.FromMinutes(int act.Cooldown) + System.DateTime.UtcNow - act.Timestamp < cooldown) + { player with Events = actions } + + let modifyBank (player : PlayerData) amount = { player with Bank = max (player.Bank + amount) 0 } + +module Arsenal = + let battleItemFormat (items : Item array) = + match items with + | [||] -> "None" + | _ -> items |> Array.toList |> List.map (fun i -> i.Name) |> String.concat ", " + + let actionFormat (actions : PlayerEvent array) = + match actions with + | [||] -> "None" + | acts -> + acts + |> Array.map (fun act -> + match act.Type with + | Hacking h -> + let item = Armory.getItem h.HackId + let cooldown = Messaging.getTimeText false WeaponClass.SameTargetAttackCooldown act.Timestamp + $"Hacked {h.Adversary.Name} with {item.Name} {cooldown} ago" + | Shielding id -> + let item = Armory.getItem id + let cooldown = Messaging.getTimeText true (System.TimeSpan.FromMinutes(int act.Cooldown)) act.Timestamp + $"{item.Name} Shield active for {cooldown}" + | _ -> "") + |> Array.filter (System.String.IsNullOrWhiteSpace >> not) + |> String.concat "\n" + + let statusFormat p = + let hacks = Player.getHackEvents p + $"**Hacks:** {Player.getHacks p |> battleItemFormat}\n + **Shields:** {Player.getShields p |> battleItemFormat}\n + **Hack Attacks:**\n{ hacks |> Array.take (min hacks.Length 10) |> actionFormat}\n + **Active Shields:**\n{Player.getShieldEvents p |> actionFormat}" + diff --git a/Bot/GameTypes.fs b/Bot/GameTypes.fs new file mode 100644 index 0000000..6942873 --- /dev/null +++ b/Bot/GameTypes.fs @@ -0,0 +1,117 @@ +namespace Degenz + +open System +open System.Threading.Tasks +open DSharpPlus +open DSharpPlus.Entities +open DSharpPlus.EventArgs +open DSharpPlus.SlashCommands +open Newtonsoft.Json + +[] +module Types = + + [] + type mins + + [] + type GBT + + type HackId = + | Virus = 0 + | RemoteAccess = 1 + | Worm = 2 + + type ShieldId = + | Firewall = 6 + | Encryption = 7 + | Cypher = 8 + + type ItemType = + | Hack = 0 + | Shield = 1 + | Food = 1 + + type ItemAttributes = { + Sell : bool + Buy : bool + Consume : bool + Drop : bool + } + with static member empty = { Sell = false ; Buy = false ; Consume = false ; Drop = false } + + type Item = { + Id : int + Name : string + Price : int + Type : ItemType + Power : int + Class : int + Cooldown : int + Attributes : ItemAttributes + } + with static member empty = + { Id = -1 + Name = "None" + Price = 0 + Type = ItemType.Hack + Power = 0 + Class = -1 + Cooldown = 0 + Attributes = ItemAttributes.empty } + + type HackResult = + | Strong + | Weak + + [] + type DiscordPlayer = { Id: uint64; Name: string } + with static member empty = { Id = 0uL ; Name = "None" } + + type HackEvent = { + IsInstigator : bool + Adversary : DiscordPlayer + Success : bool + HackId : int + } + + type PlayerEventType = + | Hacking of HackEvent + | Shielding of shieldId : int + | Stealing of instigator : bool * adversary : DiscordPlayer + | Imprison + + type PlayerEvent = + { Type : PlayerEventType + Cooldown : int + Timestamp : DateTime } + + type PlayerTraits = { + Strength : int + Focus : int + Luck : int + Charisma : int + } + with static member empty = { Strength = 0 ; Focus = 0 ; Luck = 0 ; Charisma = 0 } + + [] + type PlayerData = { + DiscordId : uint64 + Name : string + Inventory : Item array + Events : PlayerEvent array + Traits : PlayerTraits + Bank : int + } +// Achievements : string array +// XP : int + with member this.basicPlayer = { Id = this.DiscordId ; Name = this.Name } + static member empty = + { DiscordId = 0uL + Name = "None" + Inventory = [||] + Events = [||] + Traits = PlayerTraits.empty +// Achievements = [||] +// XP = 0 + Bank = 0 } diff --git a/Bot/HackerBattle.fs b/Bot/Games/HackerBattle.fs similarity index 100% rename from Bot/HackerBattle.fs rename to Bot/Games/HackerBattle.fs diff --git a/Bot/RockPaperScissors.fs b/Bot/Games/RockPaperScissors.fs similarity index 100% rename from Bot/RockPaperScissors.fs rename to Bot/Games/RockPaperScissors.fs diff --git a/Bot/SlotMachine.fs b/Bot/Games/SlotMachine.fs similarity index 100% rename from Bot/SlotMachine.fs rename to Bot/Games/SlotMachine.fs diff --git a/Bot/Store.fs b/Bot/Games/Store.fs similarity index 100% rename from Bot/Store.fs rename to Bot/Games/Store.fs diff --git a/Bot/Thief.fs b/Bot/Games/Thief.fs similarity index 100% rename from Bot/Thief.fs rename to Bot/Games/Thief.fs diff --git a/Bot/Trainer.fs b/Bot/Games/Trainer.fs similarity index 100% rename from Bot/Trainer.fs rename to Bot/Games/Trainer.fs diff --git a/Bot/Messaging.fs b/Bot/Messaging.fs new file mode 100644 index 0000000..9a0bfd9 --- /dev/null +++ b/Bot/Messaging.fs @@ -0,0 +1,143 @@ +module Degenz.Messaging + +open System +open System.Threading.Tasks +open DSharpPlus +open DSharpPlus.Entities +open DSharpPlus.EventArgs +open DSharpPlus.SlashCommands + +type InteractiveMessage = { + ButtonId : string + ButtonText : string + Message : string +} + +type DiscordContext = + | Interaction of InteractionContext + | Event of ComponentInteractionCreateEventArgs + +type IDiscordContext = + abstract member Respond : InteractionResponseType -> Task + abstract member Respond : InteractionResponseType * DiscordInteractionResponseBuilder -> Task + abstract member FollowUp : DiscordFollowupMessageBuilder -> Task + abstract member GetDiscordMember : unit -> DiscordMember + abstract member GetGuild : unit -> DiscordGuild + abstract member GetInteractionId : unit -> string + abstract member GetChannel : unit -> DiscordChannel + abstract member GetContext : unit -> obj + +type DiscordInteractionContext(ctx : InteractionContext) = + interface IDiscordContext with + member this.Respond responseType = + async { + do! ctx.Interaction.CreateResponseAsync(responseType) |> Async.AwaitTask + } |> Async.StartAsTask :> Task + member this.Respond (responseType, builder) = + async { + do! ctx.Interaction.CreateResponseAsync(responseType, builder) |> Async.AwaitTask + } |> Async.StartAsTask :> Task + member this.FollowUp(builder) = + async { + do! ctx.Interaction.CreateFollowupMessageAsync(builder) |> Async.AwaitTask |> Async.Ignore + } |> Async.StartAsTask :> Task + member this.GetDiscordMember() = ctx.Member + member this.GetGuild() = ctx.Guild + member this.GetInteractionId() = string ctx.InteractionId + member this.GetChannel() = ctx.Channel + member this.GetContext() = ctx + +type DiscordEventContext(ctx : ComponentInteractionCreateEventArgs) = + interface IDiscordContext with + member this.Respond responseType = + async { + do! ctx.Interaction.CreateResponseAsync(responseType) |> Async.AwaitTask + } |> Async.StartAsTask :> Task + member this.Respond (responseType, builder) = + async { + do! ctx.Interaction.CreateResponseAsync(responseType, builder) |> Async.AwaitTask + } |> Async.StartAsTask :> Task + member this.FollowUp(builder) = + async { + do! ctx.Interaction.CreateFollowupMessageAsync(builder) |> Async.AwaitTask |> Async.Ignore + } |> Async.StartAsTask :> Task + member this.GetDiscordMember() = ctx.User :?> DiscordMember + member this.GetGuild() = ctx.Guild + member this.GetInteractionId() = ctx.Id + member this.GetChannel() = ctx.Channel + member this.GetContext() = ctx + +let getTimeText isCooldown (timespan : TimeSpan) timestamp = + let span = + if isCooldown + then timespan - (DateTime.UtcNow - timestamp) + else (DateTime.UtcNow - timestamp) + let plural amount = if amount = 1 then "" else "s" + let ``and`` = if span.Hours > 0 then "and " else "" + let hours = if span.Hours > 0 then $"{span.Hours} hour{plural span.Hours} {``and``}" else String.Empty + let totalMins = span.Minutes + let minutes = if totalMins > 0 then $"{totalMins} minute{plural totalMins}" else "1 minute" + $"{hours}{minutes}" + +let getShortTimeText (timespan : TimeSpan) timestamp = + let remaining = timespan - (DateTime.UtcNow - timestamp) + let hours = if remaining.Hours > 0 then $"{remaining.Hours}h " else String.Empty + let minutesRemaining = if remaining.Hours = 0 then remaining.Minutes + 1 else remaining.Minutes + $"{hours}{minutesRemaining}min" + +let defer (ctx: IDiscordContext) = async { + let builder = DiscordInteractionResponseBuilder() + builder.IsEphemeral <- true + do! ctx.Respond(InteractionResponseType.DeferredChannelMessageWithSource, builder) |> Async.AwaitTask +} + +let sendSimpleResponse (ctx: IDiscordContext) msg = + async { + let builder = DiscordInteractionResponseBuilder() + builder.Content <- msg + builder.AsEphemeral true |> ignore + do! ctx.Respond(InteractionResponseType.ChannelMessageWithSource, builder) |> Async.AwaitTask + } + +let sendFollowUpMessage (ctx : IDiscordContext) msg = + async { + let builder = DiscordFollowupMessageBuilder() + builder.IsEphemeral <- true + builder.Content <- msg + do! ctx.FollowUp(builder) |> Async.AwaitTask + } + +let sendFollowUpEmbed (ctx : IDiscordContext) embed = + async { + let builder = + DiscordFollowupMessageBuilder() + .AsEphemeral(true) + .AddEmbed(embed) + do! ctx.FollowUp(builder) |> Async.AwaitTask + } + +let sendFollowUpMessageWithButton (ctx : IDiscordContext) interactiveMessage = + async { + let builder = DiscordFollowupMessageBuilder() + let button = DiscordButtonComponent(ButtonStyle.Success, interactiveMessage.ButtonId, interactiveMessage.ButtonText) :> DiscordComponent + builder.AddComponents [| button |] |> ignore + builder.IsEphemeral <- true + builder.Content <- interactiveMessage.Message + do! ctx.FollowUp(builder) |> Async.AwaitTask + } + +let updateMessageWithGreyedOutButtons (ctx : IDiscordContext) interactiveMessage = + async { + let builder = DiscordInteractionResponseBuilder() + let button = DiscordButtonComponent(ButtonStyle.Success, interactiveMessage.ButtonId, interactiveMessage.ButtonText, true) :> DiscordComponent + builder.AddComponents [| button |] |> ignore + builder.IsEphemeral <- true + builder.Content <- interactiveMessage.Message + do! ctx.Respond(InteractionResponseType.UpdateMessage, builder) |> Async.AwaitTask + } + +let handleResultWithResponse ctx fn (player : Result) = + match player with + | Ok p -> fn p + | Error e -> async { do! sendFollowUpMessage ctx e } +