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 }
+