From 86354e2efe24db1627fbf4a72bddaf3758145fed Mon Sep 17 00:00:00 2001 From: Joseph Ferano Date: Tue, 11 Jan 2022 13:49:43 +0700 Subject: [PATCH] Move further code to Commands module --- Commands.fs | 258 ++++++++++++++++++++++++++++++++++++++ Joebot.fs => Functions.fs | 6 +- Program.fs | 247 +----------------------------------- discord-bot.fsproj | 3 +- 4 files changed, 268 insertions(+), 246 deletions(-) create mode 100644 Commands.fs rename Joebot.fs => Functions.fs (97%) diff --git a/Commands.fs b/Commands.fs new file mode 100644 index 0000000..ebd5bbf --- /dev/null +++ b/Commands.fs @@ -0,0 +1,258 @@ +module Joebot.Commands + +open System +open System.Threading.Tasks +open DSharpPlus +open DSharpPlus.Entities +open DSharpPlus.EventArgs +open DSharpPlus.SlashCommands +open Joebot.Types +open Joebot.Functions + +let mutable players : Player list = [] + +let addHackerRole (ctx : InteractionContext) = + async { + for role in ctx.Guild.Roles do + if role.Value.Name = "Hacker" then + do! ctx.Member.GrantRoleAsync(role.Value) + |> Async.AwaitTask + + let player = players |> List.tryFind (fun p -> int64 p.DiscordId = int64 ctx.Member.Id) + players <- + match player with + | Some _ -> players + | None -> (newPlayer ctx.Member.Id)::players + + if Option.isSome player then + do! ctx.CreateResponseAsync("Already registered as an elite haxxor", true) + |> Async.AwaitTask + else + do! ctx.CreateResponseAsync("You are now an elite haxxor", true) + |> Async.AwaitTask + + } |> Async.StartAsTask + :> Task + + +let removeHackerRole (ctx : InteractionContext) = + async { + for role in ctx.Member.Roles do + if role.Name = "Hacker" then + do! ctx.Member.RevokeRoleAsync(role) + |> Async.AwaitTask + players <- players |> List.filter (fun p -> p.DiscordId <> ctx.User.Id) + do! ctx.CreateResponseAsync("You are now lame", true) + |> Async.AwaitTask + } |> Async.StartAsTask + :> Task + +let attack (ctx : InteractionContext) (target : DiscordUser) = + // TODO: We need to check if the player has any active embed hacks going, if not they can cheat + let attacker = players |> List.tryFind (fun p -> p.DiscordId = ctx.Member.Id) + let defender = players |> List.tryFind (fun p -> p.DiscordId = target.Id) + match attacker , defender with + | Some attacker , Some defender -> + let updatedAttacks = removeExpiredActions (TimeSpan.FromMinutes(15)) (fun (atk : Attack) -> atk.Timestamp) attacker.Attacks + players <- + players + |> List.map (fun p -> if p.DiscordId = attacker.DiscordId then { p with Attacks = updatedAttacks } else p) + if updatedAttacks.Length < 2 then + async { + let builder = DiscordInteractionResponseBuilder() + builder.AddEmbed (constructEmbed "Pick the hack you wish to use.") |> ignore + + let defenderInfo = $"{defender.DiscordId}-{target.Username}" + constructButtons "Attack" defenderInfo attacker.Weapons + |> Seq.cast + |> builder.AddComponents + |> ignore + + builder.AsEphemeral true |> ignore + + do! ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder) + |> Async.AwaitTask + + } |> Async.StartAsTask + :> Task + else + async { + let builder = DiscordInteractionResponseBuilder() + let timeRemaining = TimeSpan.FromMinutes(15) - (DateTime.UtcNow - updatedAttacks.Head.Timestamp) + builder.Content <- $"You already hacked, please wait {timeRemaining.Minutes} minutes and {timeRemaining.Seconds} seconds to attempt another hack" + + builder.AsEphemeral true |> ignore + + do! ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder) + |> Async.AwaitTask + + } |> Async.StartAsTask + :> Task + | None , _ -> notYetAHackerMsg ctx + | _ , None -> createSimpleResponseAsync "Your target is not connected to the network, they must join first by using the /redpill command" ctx + +let defend (ctx : InteractionContext) = + players + |> List.tryFind (fun p -> p.DiscordId = ctx.Member.Id) + |> function + | Some player -> + async { + let updatedDefenses = removeExpiredActions (TimeSpan.FromMinutes(60)) (fun (pro : Defense) -> pro.Timestamp) player.Defenses + players <- + players + |> List.map (fun p -> if p.DiscordId = player.DiscordId then { p with Defenses = updatedDefenses } else p) + let builder = DiscordInteractionResponseBuilder() + builder.AddEmbed (constructEmbed "Pick a defense to mount for a duration of time") |> ignore + + constructButtons "Defend" (string player.DiscordId) player.Shields + |> Seq.cast + |> builder.AddComponents + |> ignore + + builder.AsEphemeral true |> ignore + + do! ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder) + |> Async.AwaitTask + + } |> Async.StartAsTask + :> Task + | None -> notYetAHackerMsg ctx + +let status (ctx : InteractionContext) = + async { + return! + match players |> List.tryFind (fun p -> p.DiscordId = ctx.Member.Id) with + | Some player -> + async { + let builder = DiscordInteractionResponseBuilder() + builder.IsEphemeral <- true + builder.Content <- $"%A{player}" + do! ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder) + |> Async.AwaitTask + } + | None -> notYetAHackerMsg ctx |> Async.AwaitTask + } |> Async.StartAsTask + :> Task + +let handleAttack (event : ComponentInteractionCreateEventArgs) = + let updatePlayer amount attack p = { + p with Attacks = attack::p.Attacks + Bank = MathF.Max(p.Bank + amount, 0f) + } + async { + let split = event.Id.Split("-") + let resultHack = Weapon.TryParse(split.[1]) + let ( resultId , targetId ) = UInt64.TryParse split.[2] + return! + match resultHack , resultId with + | Some weapon , true -> + let hackType = weapon + players + |> List.find (fun p -> p.DiscordId = targetId) + |> fun p -> p.Defenses + |> List.map (fun dfn -> dfn.DefenseType) + |> List.map (calculateDamage hackType) + |> List.contains Weak + |> function + | false -> + async { + let prize = 0.1726f + let attack = { HackType = hackType ; Timestamp = DateTime.UtcNow ; Target = { Id = targetId ; Name = split.[3] } } + players <- + players + |> List.map (fun p -> if p.DiscordId = event.User.Id then updatePlayer prize attack p else p) + + let builder = DiscordInteractionResponseBuilder() + builder.IsEphemeral <- true + builder.Content <- $"Successfully hacked {split.[3]} using {hackType}! You just won {prize} genz!" + do! event.Interaction.CreateResponseAsync(InteractionResponseType.UpdateMessage, builder) + |> Async.AwaitTask + + let builder = DiscordMessageBuilder() + builder.WithContent($"{event.User.Username} successfully hacked <@{targetId}>!") |> ignore + let battleChannel = (event.Guild.GetChannel(927449884204867664uL)) + do! battleChannel.SendMessageAsync(builder) + |> Async.AwaitTask + |> Async.Ignore + } + | true -> + async { + let builder = DiscordInteractionResponseBuilder() + let loss = -0.0623f + builder.IsEphemeral <- true + builder.Content <- $"Hack failed! {split.[3]} was able to mount a successful defense! You lost {loss} genz!" + do! event.Interaction.CreateResponseAsync(InteractionResponseType.UpdateMessage, builder) + |> Async.AwaitTask + + let attack = { HackType = hackType ; Timestamp = DateTime.UtcNow ; Target = { Id = targetId ; Name = split.[3] } } + players <- + players + |> List.map (fun p -> if p.DiscordId = event.User.Id then updatePlayer loss attack p else p) + + let builder = DiscordMessageBuilder() + builder.WithContent($"{event.User.Username} failed to hack <@{targetId}>!") |> ignore + let battleChannel = (event.Guild.GetChannel(927449884204867664uL)) + do! battleChannel.SendMessageAsync(builder) + |> Async.AwaitTask + |> Async.Ignore + } + | _ -> + async { + let builder = DiscordInteractionResponseBuilder() + builder.IsEphemeral <- true + builder.Content <- "Error parsing Button Id" + do! event.Interaction.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder) + |> Async.AwaitTask + } + } + +let handleDefense (event : ComponentInteractionCreateEventArgs) = + async { + let split = event.Id.Split("-") + return! + match Shield.TryParse(split.[1]) with + | Some shield -> + async { + let builder = DiscordInteractionResponseBuilder() + builder.IsEphemeral <- true + builder.Content <- $"Mounted a {shield} defense for 1 hour" + do! event.Interaction.CreateResponseAsync(InteractionResponseType.UpdateMessage, builder) + |> Async.AwaitTask + + let defense = { DefenseType = shield ; Timestamp = DateTime.UtcNow } + players <- + players + |> List.map (fun p -> { p with Defenses = defense::p.Defenses }) + + let builder = DiscordMessageBuilder() + builder.WithContent($"{event.User.Username} has protected their system!") |> ignore + let battleChannel = (event.Guild.GetChannel(927449884204867664uL)) + do! battleChannel.SendMessageAsync(builder) + |> Async.AwaitTask + |> Async.Ignore + } + | _ -> + async { + let builder = DiscordInteractionResponseBuilder() + builder.IsEphemeral <- true + builder.Content <- "Error parsing Button Id" + do! event.Interaction.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder) + |> Async.AwaitTask + } + } + +let handleButtonEvent (client : DiscordClient) (event : ComponentInteractionCreateEventArgs) = + async { + return! match event.Id with + | id when id.StartsWith("Attack") -> handleAttack event + | id when id.StartsWith("Defend") -> handleDefense event + | _ -> + async { + let builder = DiscordInteractionResponseBuilder() + builder.IsEphemeral <- true + builder.Content <- $"Incorrect Action identifier {event.Id}" + do! event.Interaction.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder) + |> Async.AwaitTask + } + } |> Async.StartAsTask + :> Task diff --git a/Joebot.fs b/Functions.fs similarity index 97% rename from Joebot.fs rename to Functions.fs index 12d55bc..57bf8db 100644 --- a/Joebot.fs +++ b/Functions.fs @@ -1,14 +1,14 @@ -module Joebot.Commands +module Joebot.Functions open System open System.Threading.Tasks open DSharpPlus open DSharpPlus.Entities -open DSharpPlus.EventArgs open DSharpPlus.SlashCommands -open Emzi0767.Utilities open Joebot.Types +let hackDescription = "" + let newPlayer (membr : uint64) = let h1 = [| Virus ; Ransom |] let h2 = [| DDos ; Worm |] diff --git a/Program.fs b/Program.fs index 2b66956..5723fd6 100644 --- a/Program.fs +++ b/Program.fs @@ -10,265 +10,28 @@ open Emzi0767.Utilities open Joebot.Types open Joebot.Commands -let mutable players : Player list = [] - type EmptyGlobalCommandToAvoidFamousDuplicateSlashCommandsBug() = inherit ApplicationCommandModule () type JoeBot() = inherit ApplicationCommandModule () [] - member _.AddHackerRole (ctx : InteractionContext) = - async { - for role in ctx.Guild.Roles do - if role.Value.Name = "Hacker" then - do! ctx.Member.GrantRoleAsync(role.Value) - |> Async.AwaitTask - - let player = players |> List.tryFind (fun p -> int64 p.DiscordId = int64 ctx.Member.Id) - players <- - match player with - | Some _ -> players - | None -> (newPlayer ctx.Member.Id)::players - - if Option.isSome player then - do! ctx.CreateResponseAsync("Already registered as an elite haxxor", true) - |> Async.AwaitTask - else - do! ctx.CreateResponseAsync("You are now an elite haxxor", true) - |> Async.AwaitTask - - - } |> Async.StartAsTask - :> Task + member _.AddHackerRole (ctx : InteractionContext) = Commands.addHackerRole ctx [] - member _.RemoveHackerRole (ctx : InteractionContext) = - async { - for role in ctx.Member.Roles do - if role.Name = "Hacker" then - do! ctx.Member.RevokeRoleAsync(role) - |> Async.AwaitTask - players <- players |> List.filter (fun p -> p.DiscordId <> ctx.User.Id) - do! ctx.CreateResponseAsync("You are now lame", true) - |> Async.AwaitTask - } |> Async.StartAsTask - :> Task + member _.RemoveHackerRole (ctx : InteractionContext) = Commands.removeHackerRole ctx [] member this.AttackCommand (ctx : InteractionContext, [] target : DiscordUser) = - // TODO: We need to check if the player has any active embed hacks going, if not they can cheat - let attacker = players |> List.tryFind (fun p -> p.DiscordId = ctx.Member.Id) - let defender = players |> List.tryFind (fun p -> p.DiscordId = target.Id) - match attacker , defender with - | Some attacker , Some defender -> - let updatedAttacks = removeExpiredActions (TimeSpan.FromMinutes(15)) (fun (atk : Attack) -> atk.Timestamp) attacker.Attacks - players <- - players - |> List.map (fun p -> if p.DiscordId = attacker.DiscordId then { p with Attacks = updatedAttacks } else p) - if updatedAttacks.Length < 2 then - async { - let builder = DiscordInteractionResponseBuilder() - builder.AddEmbed (constructEmbed "Pick the hack you wish to use.") |> ignore - - let defenderInfo = $"{defender.DiscordId}-{target.Username}" - constructButtons "Attack" defenderInfo attacker.Weapons - |> Seq.cast - |> builder.AddComponents - |> ignore - - builder.AsEphemeral true |> ignore - - do! ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder) - |> Async.AwaitTask - - } |> Async.StartAsTask - :> Task - else - async { - let builder = DiscordInteractionResponseBuilder() - let timeRemaining = TimeSpan.FromMinutes(15) - (DateTime.UtcNow - updatedAttacks.Head.Timestamp) - builder.Content <- $"You already hacked, please wait {timeRemaining.Minutes} minutes and {timeRemaining.Seconds} seconds to attempt another hack" - - builder.AsEphemeral true |> ignore - - do! ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder) - |> Async.AwaitTask - - } |> Async.StartAsTask - :> Task - | None , _ -> notYetAHackerMsg ctx - | _ , None -> createSimpleResponseAsync "Your target is not connected to the network, they must join first by using the /redpill command" ctx - + Commands.attack ctx target [] - member this.DefendCommand (ctx : InteractionContext) = - players - |> List.tryFind (fun p -> p.DiscordId = ctx.Member.Id) - |> function - | Some player -> - async { - let updatedDefenses = removeExpiredActions (TimeSpan.FromMinutes(60)) (fun (pro : Defense) -> pro.Timestamp) player.Defenses - players <- - players - |> List.map (fun p -> if p.DiscordId = player.DiscordId then { p with Defenses = updatedDefenses } else p) - let builder = DiscordInteractionResponseBuilder() - builder.AddEmbed (constructEmbed "Pick a defense to mount for a duration of time") |> ignore - - constructButtons "Defend" (string player.DiscordId) player.Shields - |> Seq.cast - |> builder.AddComponents - |> ignore - - builder.AsEphemeral true |> ignore - - do! ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder) - |> Async.AwaitTask - - } |> Async.StartAsTask - :> Task - | None -> notYetAHackerMsg ctx + member this.DefendCommand (ctx : InteractionContext) = Commands.defend ctx [] - member this.Status (ctx : InteractionContext) = - async { - return! - match players |> List.tryFind (fun p -> p.DiscordId = ctx.Member.Id) with - | Some player -> - async { - let builder = DiscordInteractionResponseBuilder() - builder.IsEphemeral <- true - builder.Content <- $"%A{player}" - do! ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder) - |> Async.AwaitTask - } - | None -> notYetAHackerMsg ctx |> Async.AwaitTask - } |> Async.StartAsTask - :> Task + member this.Status (ctx : InteractionContext) = Commands.status ctx -let handleAttack (event : ComponentInteractionCreateEventArgs) = - let updatePlayer amount attack p = { - p with Attacks = attack::p.Attacks - Bank = MathF.Max(p.Bank + amount, 0f) - } - async { - let split = event.Id.Split("-") - let resultHack = Weapon.TryParse(split.[1]) - let ( resultId , targetId ) = UInt64.TryParse split.[2] - return! - match resultHack , resultId with - | Some weapon , true -> - let hackType = weapon - players - |> List.find (fun p -> p.DiscordId = targetId) - |> fun p -> p.Defenses - |> List.map (fun dfn -> dfn.DefenseType) - |> List.map (calculateDamage hackType) - |> List.contains Weak - |> function - | false -> - async { - let prize = 0.1726f - let attack = { HackType = hackType ; Timestamp = DateTime.UtcNow ; Target = { Id = targetId ; Name = split.[3] } } - players <- - players - |> List.map (fun p -> if p.DiscordId = event.User.Id then updatePlayer prize attack p else p) - - let builder = DiscordInteractionResponseBuilder() - builder.IsEphemeral <- true - builder.Content <- $"Successfully hacked {split.[3]} using {hackType}! You just won {prize} genz!" - do! event.Interaction.CreateResponseAsync(InteractionResponseType.UpdateMessage, builder) - |> Async.AwaitTask - - let builder = DiscordMessageBuilder() - builder.WithContent($"{event.User.Username} successfully hacked <@{targetId}>!") |> ignore - let battleChannel = (event.Guild.GetChannel(927449884204867664uL)) - do! battleChannel.SendMessageAsync(builder) - |> Async.AwaitTask - |> Async.Ignore - } - | true -> - async { - let builder = DiscordInteractionResponseBuilder() - let loss = -0.0623f - builder.IsEphemeral <- true - builder.Content <- $"Hack failed! {split.[3]} was able to mount a successful defense! You lost {loss} genz!" - do! event.Interaction.CreateResponseAsync(InteractionResponseType.UpdateMessage, builder) - |> Async.AwaitTask - - let attack = { HackType = hackType ; Timestamp = DateTime.UtcNow ; Target = { Id = targetId ; Name = split.[3] } } - players <- - players - |> List.map (fun p -> if p.DiscordId = event.User.Id then updatePlayer loss attack p else p) - - let builder = DiscordMessageBuilder() - builder.WithContent($"{event.User.Username} failed to hack <@{targetId}>!") |> ignore - let battleChannel = (event.Guild.GetChannel(927449884204867664uL)) - do! battleChannel.SendMessageAsync(builder) - |> Async.AwaitTask - |> Async.Ignore - } - | _ -> - async { - let builder = DiscordInteractionResponseBuilder() - builder.IsEphemeral <- true - builder.Content <- "Error parsing Button Id" - do! event.Interaction.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder) - |> Async.AwaitTask - } - } - -let handleDefense (event : ComponentInteractionCreateEventArgs) = - async { - let split = event.Id.Split("-") - return! - match Shield.TryParse(split.[1]) with - | Some shield -> - async { - let builder = DiscordInteractionResponseBuilder() - builder.IsEphemeral <- true - builder.Content <- $"Mounted a {shield} defense for 1 hour" - do! event.Interaction.CreateResponseAsync(InteractionResponseType.UpdateMessage, builder) - |> Async.AwaitTask - - let defense = { DefenseType = shield ; Timestamp = DateTime.UtcNow } - players <- - players - |> List.map (fun p -> { p with Defenses = defense::p.Defenses }) - - let builder = DiscordMessageBuilder() - builder.WithContent($"{event.User.Username} has protected their system!") |> ignore - let battleChannel = (event.Guild.GetChannel(927449884204867664uL)) - do! battleChannel.SendMessageAsync(builder) - |> Async.AwaitTask - |> Async.Ignore - } - | _ -> - async { - let builder = DiscordInteractionResponseBuilder() - builder.IsEphemeral <- true - builder.Content <- "Error parsing Button Id" - do! event.Interaction.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder) - |> Async.AwaitTask - } - } - -let handleButtonEvent (client : DiscordClient) (event : ComponentInteractionCreateEventArgs) = - async { - return! match event.Id with - | id when id.StartsWith("Attack") -> handleAttack event - | id when id.StartsWith("Defend") -> handleDefense event - | _ -> - async { - let builder = DiscordInteractionResponseBuilder() - builder.IsEphemeral <- true - builder.Content <- $"Incorrect Action identifier {event.Id}" - do! event.Interaction.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder) - |> Async.AwaitTask - } - } |> Async.StartAsTask - :> Task let config = DiscordConfiguration() config.Token <- "OTIyNDIyMDIyMTI1MDEwOTU1.YcBOcw.JxfW1CSIwEO7j6RbRFCnPZ-HoTk" config.TokenType <- TokenType.Bot diff --git a/discord-bot.fsproj b/discord-bot.fsproj index 07e263b..2a6a6ee 100644 --- a/discord-bot.fsproj +++ b/discord-bot.fsproj @@ -15,7 +15,8 @@ - + +