diff --git a/Commands.fs b/Commands.fs deleted file mode 100644 index 2fad0bd..0000000 --- a/Commands.fs +++ /dev/null @@ -1,287 +0,0 @@ -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 battleChannel = 930363007781978142uL - -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.Username 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(5)) (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 timestamp = updatedAttacks |> List.rev |> List.head |> fun a -> a.Timestamp // This should be the next expiring timestamp - let timeRemaining = TimeSpan.FromMinutes(15) - (DateTime.UtcNow - timestamp) - builder.Content <- $"No more hacks available, 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) - if updatedDefenses.Length < 2 then - 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 - else - let builder = DiscordInteractionResponseBuilder() - let timestamp = updatedDefenses |> List.rev |> List.head |> fun a -> a.Timestamp // This should be the next expiring timestamp - let timeRemaining = TimeSpan.FromMinutes(15) - (DateTime.UtcNow - timestamp) - builder.Content <- $"Cannot add new defense, please wait {timeRemaining.Minutes} minutes and {timeRemaining.Seconds} seconds to add another defense" - - 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 <- Functions.statusFormat player - do! ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder) - |> Async.AwaitTask - } - | None -> notYetAHackerMsg ctx |> Async.AwaitTask - } |> Async.StartAsTask - :> Task - -let leaderboard (ctx : InteractionContext) = - async { - let builder = DiscordInteractionResponseBuilder() - builder.IsEphemeral <- true - let content = - players - |> List.sortByDescending (fun p -> p.Bank) - |> List.mapi (fun i p -> $"{i + 1}. {p.Bank} {p.Name}") - |> String.concat "\n" - builder.Content <- if not <| String.IsNullOrEmpty content then content else "There are no active hackers" - do! ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder) - |> 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 channel = (event.Guild.GetChannel(battleChannel)) - do! channel.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 channel = (event.Guild.GetChannel(battleChannel)) - do! channel.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 channel = (event.Guild.GetChannel(battleChannel)) - do! channel.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/Degenz.sln b/Degenz.sln new file mode 100644 index 0000000..ec17336 --- /dev/null +++ b/Degenz.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30114.105 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "HackerBattle", "HackerBattle\HackerBattle.fsproj", "{2A437756-3D5D-467D-9497-DF9789DB99CC}" +EndProject +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Shared", "Shared\Shared.fsproj", "{5F34C24E-BA4E-4E57-9141-812775687360}" +EndProject +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Store", "Store\Store.fsproj", "{CD88B0A6-DE42-4087-9B33-48FF84201633}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {2A437756-3D5D-467D-9497-DF9789DB99CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2A437756-3D5D-467D-9497-DF9789DB99CC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2A437756-3D5D-467D-9497-DF9789DB99CC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2A437756-3D5D-467D-9497-DF9789DB99CC}.Release|Any CPU.Build.0 = Release|Any CPU + {5F34C24E-BA4E-4E57-9141-812775687360}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5F34C24E-BA4E-4E57-9141-812775687360}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5F34C24E-BA4E-4E57-9141-812775687360}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5F34C24E-BA4E-4E57-9141-812775687360}.Release|Any CPU.Build.0 = Release|Any CPU + {CD88B0A6-DE42-4087-9B33-48FF84201633}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CD88B0A6-DE42-4087-9B33-48FF84201633}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CD88B0A6-DE42-4087-9B33-48FF84201633}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CD88B0A6-DE42-4087-9B33-48FF84201633}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/HackerBattle/.dockerignore b/HackerBattle/.dockerignore new file mode 100644 index 0000000..38bece4 --- /dev/null +++ b/HackerBattle/.dockerignore @@ -0,0 +1,25 @@ +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/.idea +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/azds.yaml +**/bin +**/charts +**/docker-compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +LICENSE +README.md \ No newline at end of file diff --git a/HackerBattle/Commands.fs b/HackerBattle/Commands.fs new file mode 100644 index 0000000..57983d2 --- /dev/null +++ b/HackerBattle/Commands.fs @@ -0,0 +1,290 @@ +module DegenzGame.Commands + +open System +open System.Threading.Tasks +open DSharpPlus +open DSharpPlus.Entities +open DSharpPlus.EventArgs +open DSharpPlus.SlashCommands +open DegenzGame.Types +open DegenzGame.Functions +open MongoDB.Driver + +[] +// Degenz Server +//let battleChannel = 930363007781978142uL +// My server +let battleChannel = 927449884204867664uL + +let mongo = MongoClient("mongodb://localhost:27017") +let db = mongo.GetDatabase("degenz-game") +let players = db.GetCollection("players") + +let tryFindPlayer (id : uint64) : Async = + async { + let filter = Builders.Filter.Eq((fun p -> p.DiscordId), id) + let! player = players.FindAsync(filter) |> Async.AwaitTask + return match player.ToEnumerable() |> Seq.toList with + | [] -> None + | p::_ -> Some p + } + +let addHackerRole (ctx : InteractionContext) = + async { + let! player = tryFindPlayer ctx.Member.Id + let! newPlayer = + match player with + | Some _ -> async.Return false + | None -> + async { + let p = (newPlayer ctx.Member.Username ctx.Member.Id) + do! players.InsertOneAsync p |> Async.AwaitTask + + for role in ctx.Guild.Roles do + if role.Value.Name = "Hacker" then + do! ctx.Member.GrantRoleAsync(role.Value) + |> Async.AwaitTask + + return true + } + + if newPlayer then + do! ctx.CreateResponseAsync("You are now an elite haxxor", true) + |> Async.AwaitTask + else + do! ctx.CreateResponseAsync("Already registered as 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 + // TODO: Check the result of this delete operation + let! _ = players.DeleteOneAsync (fun p -> p.DiscordId = ctx.Member.Id) |> Async.AwaitTask + do! ctx.CreateResponseAsync("You are now lame", true) + |> Async.AwaitTask + } |> Async.StartAsTask + :> Task + +let attack (ctx : InteractionContext) (target : DiscordUser) = + async { + // TODO: We need to check if the player has any active embed hacks going, if not they can cheat + let! attacker = tryFindPlayer ctx.Member.Id + let! defender = tryFindPlayer target.Id + match attacker , defender with + | Some attacker , Some defender -> + let updatedAttacks = removeExpiredActions (TimeSpan.FromMinutes(5)) (fun (atk : Attack) -> atk.Timestamp) attacker.Attacks + let filter = Builders.Filter.Eq((fun p -> p.DiscordId), attacker.DiscordId) + let update = Builders.Update.Set((fun p -> p.Attacks), updatedAttacks) + let! _ = players.UpdateOneAsync(filter, update) |> Async.AwaitTask + if updatedAttacks.Length < 2 then + 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 + + else + let builder = DiscordInteractionResponseBuilder() + let timestamp = updatedAttacks |> Array.rev |> Array.head |> fun a -> a.Timestamp // This should be the next expiring timestamp + let timeRemaining = TimeSpan.FromMinutes(15) - (DateTime.UtcNow - timestamp) + builder.Content <- $"No more hacks available, 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 + + | None , _ -> do! notYetAHackerMsg ctx + | _ , None -> do! createSimpleResponseAsync "Your target is not connected to the network, they must join first by using the /redpill command" ctx + } |> Async.StartAsTask + :> Task + +let defend (ctx : InteractionContext) = + async { + let! player = tryFindPlayer ctx.Member.Id + match player with + | Some player -> + let updatedDefenses = removeExpiredActions (TimeSpan.FromMinutes(60)) (fun (pro : Defense) -> pro.Timestamp) player.Defenses + let filter = Builders.Filter.Eq((fun p -> p.DiscordId), player.DiscordId) + let update = Builders.Update.Set((fun p -> p.Defenses), updatedDefenses) + let! _ = players.UpdateOneAsync(filter, update) |> Async.AwaitTask + if updatedDefenses.Length < 2 then + 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 + else + let builder = DiscordInteractionResponseBuilder() + let timestamp = updatedDefenses |> Array.rev |> Array.head |> fun a -> a.Timestamp // This should be the next expiring timestamp + let timeRemaining = TimeSpan.FromMinutes(15) - (DateTime.UtcNow - timestamp) + builder.Content <- $"Cannot add new defense, please wait {timeRemaining.Minutes} minutes and {timeRemaining.Seconds} seconds to add another defense" + + builder.AsEphemeral true |> ignore + + do! ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder) + |> Async.AwaitTask + | None -> do! notYetAHackerMsg ctx + } |> Async.StartAsTask + :> Task + +let status (ctx : InteractionContext) = + async { + let! player = tryFindPlayer ctx.Member.Id + match player with + | Some p -> + let builder = DiscordInteractionResponseBuilder() + builder.IsEphemeral <- true + builder.Content <- Functions.statusFormat p + do! ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder) + |> Async.AwaitTask + | None -> do! notYetAHackerMsg ctx + } |> Async.StartAsTask + :> Task + +let leaderboard (ctx : InteractionContext) = + async { + let builder = DiscordInteractionResponseBuilder() + builder.IsEphemeral <- true + + let! leaders = players.Find(fun _ -> true).SortBy(fun p -> p.Bank).Limit(10).ToListAsync() |> Async.AwaitTask + let content = + leaders.ToArray() + |> Array.mapi (fun i p -> $"{i + 1}. {p.Bank} {p.Name}") + |> String.concat "\n" + builder.Content <- if not <| String.IsNullOrEmpty content then content else "There are no active hackers" + do! ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder) + |> Async.AwaitTask + } |> Async.StartAsTask + :> Task + +let handleAttack (event : ComponentInteractionCreateEventArgs) = + let updatePlayer amount attack p = + { p with Attacks = Array.append [| attack |] p.Attacks ; Bank = MathF.Max(p.Bank + amount, 0f) } + async { + let split = event.Id.Split("-") + let ( resultHack , weapon ) = Weapon.TryParse(split.[1]) + let ( resultId , targetId ) = UInt64.TryParse split.[2] + let! resultPlayer = tryFindPlayer event.User.Id + let! resultTarget = tryFindPlayer targetId + match resultPlayer , resultTarget , resultHack , resultId with + | Some player , Some target , true , true -> + let wasSuccessfulHack = + target.Defenses + |> Seq.toArray + |> Array.map (fun dfn -> int dfn.DefenseType) + |> Array.map (calculateDamage weapon) + |> Array.contains Weak + match wasSuccessfulHack with + | false -> + let prize = 0.1726f + let attack = { HackType = enum(weapon) ; Timestamp = DateTime.UtcNow ; Target = { Id = targetId ; Name = split.[3] } } + let filter = Builders.Filter.Eq((fun p -> p.DiscordId), player.DiscordId) + let! _ = players.ReplaceOneAsync(filter, updatePlayer prize attack player) |> Async.AwaitTask + + let builder = DiscordInteractionResponseBuilder() + builder.IsEphemeral <- true + builder.Content <- $"Successfully hacked {split.[3]} using {weapon}! 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 channel = (event.Guild.GetChannel(battleChannel)) + do! channel.SendMessageAsync(builder) + |> Async.AwaitTask + |> Async.Ignore + | true -> + 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 = enum(weapon) ; Timestamp = DateTime.UtcNow ; Target = { Id = targetId ; Name = split.[3] } } + let filter = Builders.Filter.Eq((fun p -> p.DiscordId), player.DiscordId) + let! _ = players.ReplaceOneAsync(filter, updatePlayer loss attack player) |> Async.AwaitTask + + let builder = DiscordMessageBuilder() + builder.WithContent($"{event.User.Username} failed to hack <@{targetId}>!") |> ignore + let channel = (event.Guild.GetChannel(battleChannel)) + do! channel.SendMessageAsync(builder) + |> Async.AwaitTask + |> Async.Ignore + | _ -> + let builder = DiscordInteractionResponseBuilder() + builder.IsEphemeral <- true + builder.Content <- "Error occurred processing attack" + do! event.Interaction.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder) + |> Async.AwaitTask + } + +let handleDefense (event : ComponentInteractionCreateEventArgs) = + async { + let split = event.Id.Split("-") + let ( shieldResult , shield ) = Shield.TryParse(split.[1]) + let! playerResult = tryFindPlayer event.User.Id + match playerResult , shieldResult with + | Some player , true -> + 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 } + let filter = Builders.Filter.Eq((fun p -> p.DiscordId), player.DiscordId) + let update = Builders.Update.Set((fun p -> p.Defenses), Array.append [| defense |] player.Defenses ) + let! _ = players.UpdateOneAsync(filter, update) |> Async.AwaitTask + + let builder = DiscordMessageBuilder() + builder.WithContent($"{event.User.Username} has protected their system!") |> ignore + let channel = event.Guild.Channels.Values |> Seq.find (fun c -> c.Name = "battle-1") + do! channel.SendMessageAsync(builder) + |> Async.AwaitTask + |> Async.Ignore + | _ -> + 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/HackerBattle/Dockerfile b/HackerBattle/Dockerfile new file mode 100644 index 0000000..e7a72b7 --- /dev/null +++ b/HackerBattle/Dockerfile @@ -0,0 +1,18 @@ +FROM mcr.microsoft.com/dotnet/runtime:6.0 AS base +WORKDIR /app + +FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build +WORKDIR /src +COPY ["HackerBattle/HackerBattle.fsproj", "HackerBattle/"] +RUN dotnet restore "HackerBattle/HackerBattle.fsproj" +COPY . . +WORKDIR "/src/HackerBattle" +RUN dotnet build "HackerBattle.fsproj" -c Release -o /app/build + +FROM build AS publish +RUN dotnet publish "HackerBattle.fsproj" -c Release -o /app/publish + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "HackerBattle.dll"] diff --git a/Functions.fs b/HackerBattle/Functions.fs similarity index 66% rename from Functions.fs rename to HackerBattle/Functions.fs index 831dadf..6ac32be 100644 --- a/Functions.fs +++ b/HackerBattle/Functions.fs @@ -1,52 +1,52 @@ -module Joebot.Functions +module DegenzGame.Functions open System -open System.Threading.Tasks open DSharpPlus open DSharpPlus.Entities open DSharpPlus.SlashCommands -open Joebot.Types +open DegenzGame.Types +open MongoDB.Bson let hackDescription = "" - + let statusFormat player = $"Hack Inventory: {player.Weapons} Shield Inventory: {player.Shields} -Active Hacks: {player.Attacks} -Active Defenses: {player.Defenses} +Active Hacks: {player.Attacks |> Array.toList} +Active Defenses: {player.Defenses |> Array.toList} Bank: {player.Bank}" - let newPlayer nickname (membr : uint64) = - let h1 = [| Virus ; Ransom |] - let h2 = [| DDos ; Worm |] - let h3 = [| Crack ; Injection |] - let d1 = [| Firewall ; PortScan |] - let d2 = [| Encryption ; Cypher |] - let d3 = [| Hardening ; Sanitation |] - + let h1 = [| Weapon.Virus ; Weapon.Ransom |] + let h2 = [| Weapon.DDos ; Weapon.Worm |] + let h3 = [| Weapon.Crack ; Weapon.Injection |] + let d1 = [| Shield.Firewall ; Shield.PortScan |] + let d2 = [| Shield.Encryption ; Shield.Cypher |] + let d3 = [| Shield.Hardening ; Shield.Sanitation |] + let rand = System.Random(System.Guid.NewGuid().GetHashCode()) let getRandom (actions : 'a array) = actions.[rand.Next(0,2)] - - let weapons = [ getRandom h1 ; getRandom h2 ; getRandom h3 ] - let shields = [ getRandom d1 ; getRandom d2 ; getRandom d3 ] - - { DiscordId = membr + + let weapons = [| getRandom h1 ; getRandom h2 ; getRandom h3 |] + let shields = [| getRandom d1 ; getRandom d2 ; getRandom d3 |] + + { Id = BsonObjectId(ObjectId.GenerateNewId()) + DiscordId = membr Name = nickname Weapons = weapons Shields = shields - Attacks = [] - Defenses = [] + Attacks = [||] + Defenses = [||] Bank = 0f } -let constructButtons (actionType : string) (playerInfo : string) (weapons : 'a list) = +let constructButtons (actionType : string) (playerInfo : string) (weapons : 'a array) = weapons |> Seq.map (fun hack -> DiscordButtonComponent( ButtonStyle.Primary, $"{actionType}-{hack}-{playerInfo}", $"{hack}")) - + let createSimpleResponseAsync msg (ctx : InteractionContext) = async { let builder = DiscordInteractionResponseBuilder() @@ -54,14 +54,13 @@ let createSimpleResponseAsync msg (ctx : InteractionContext) = builder.AsEphemeral true |> ignore do! ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder) |> Async.AwaitTask - } |> Async.StartAsTask - :> Task + } let notYetAHackerMsg = createSimpleResponseAsync "You are not currently a hacker, first use the /redpill command to become one" let removeExpiredActions timespan (timestamp : 'a -> DateTime) actions = actions - |> List.filter (fun act -> + |> Array.filter (fun act -> if DateTime.UtcNow - (timestamp act) < timespan then true else false) @@ -71,16 +70,16 @@ let constructEmbed message = builder.Color <- Optional(DiscordColor.PhthaloGreen) builder.Description <- message let author = DiscordEmbedBuilder.EmbedAuthor() - author.Name <- "Joebot Pro" + author.Name <- "Degenz Hacker Game" author.Url <- "https://twitter.com/degenzgame" author.IconUrl <- "https://pbs.twimg.com/profile_images/1473192843359309825/cqjm0VQ4_400x400.jpg" builder.Author <- author builder.Build() - -let calculateDamage (hack : IClass) (protection : IClass) = - let hackClass = hack.GetClass() - let protectionClass = protection.GetClass() + +let calculateDamage (hack : int) (shield : int) = + let hackClass = getClass hack + let protectionClass = getClass shield match hackClass , protectionClass with | h , p when h = p -> Weak | _ -> Strong - + diff --git a/discord-bot.fsproj b/HackerBattle/HackerBattle.fsproj similarity index 62% rename from discord-bot.fsproj rename to HackerBattle/HackerBattle.fsproj index 2a6a6ee..d52adce 100644 --- a/discord-bot.fsproj +++ b/HackerBattle/HackerBattle.fsproj @@ -3,21 +3,23 @@ Exe net6.0 - discord_bot + hacker-game + Linux - - PreserveNewest - + + - - + + + + \ No newline at end of file diff --git a/Program.fs b/HackerBattle/Program.fs similarity index 88% rename from Program.fs rename to HackerBattle/Program.fs index feb5c8f..0a6d931 100644 --- a/Program.fs +++ b/HackerBattle/Program.fs @@ -1,4 +1,4 @@ -module Joebot.Program +module DegenzGame.Program open System open System.Threading.Tasks @@ -7,30 +7,31 @@ open DSharpPlus.Entities open DSharpPlus.EventArgs open DSharpPlus.SlashCommands open Emzi0767.Utilities -open Joebot.Types -open Joebot.Commands +open DegenzGame.Types +open DegenzGame.Commands +open MongoDB.Driver type EmptyGlobalCommandToAvoidFamousDuplicateSlashCommandsBug() = inherit ApplicationCommandModule () -type JoeBot() = +type HackerGame() = inherit ApplicationCommandModule () - + [] member _.AddHackerRole (ctx : InteractionContext) = Commands.addHackerRole ctx - + [] member _.RemoveHackerRole (ctx : InteractionContext) = Commands.removeHackerRole ctx - + [] member this.AttackCommand (ctx : InteractionContext, [] target : DiscordUser) = Commands.attack ctx target - + [] member this.DefendCommand (ctx : InteractionContext) = Commands.defend ctx - + [] member this.Status (ctx : InteractionContext) = Commands.status ctx - + [] member this.Leaderboard (ctx : InteractionContext) = Commands.leaderboard ctx @@ -46,7 +47,10 @@ client.add_ComponentInteractionCreated(AsyncEventHandler(handleButtonEvent)) let slash = client.UseSlashCommands() -slash.RegisterCommands(922414052708327494uL); +// My server +slash.RegisterCommands(922419263275425832uL); +// Degenz +//slash.RegisterCommands(922414052708327494uL); client.ConnectAsync () |> Async.AwaitTask diff --git a/paket.references b/HackerBattle/paket.references similarity index 86% rename from paket.references rename to HackerBattle/paket.references index 9c14efd..5753ebc 100644 --- a/paket.references +++ b/HackerBattle/paket.references @@ -3,4 +3,5 @@ DSharpPlus // DSharpPlus.CommandsNext // DSharpPlus.Interactivity DSharpPlus.SlashCommands -LiteDB.FSharp + +MongoDB.Driver diff --git a/Shared/Shared.fsproj b/Shared/Shared.fsproj new file mode 100644 index 0000000..47c6c0a --- /dev/null +++ b/Shared/Shared.fsproj @@ -0,0 +1,11 @@ + + + + net6.0 + true + + + + + + \ No newline at end of file diff --git a/Shared/Types.fs b/Shared/Types.fs new file mode 100644 index 0000000..9b9650b --- /dev/null +++ b/Shared/Types.fs @@ -0,0 +1,67 @@ +module DegenzGame.Types + +open System +open MongoDB.Bson + +type ActionClass = + | Network + | Exploit + | Penetration + +type Weapon = + | Virus = 0 + | Ransom = 1 + | Worm = 2 + | DDos = 3 + | Crack = 4 + | Injection = 5 + +type Shield = + | Firewall = 0 + | PortScan = 1 + | Encryption = 2 + | Hardening = 4 + | Sanitation = 5 + | Cypher = 3 + +let getClass = function + | 0 | 1 -> Network + | 2 | 3 -> Exploit + | 4 | _ -> Penetration + +type HackResult = + | Strong + | Weak + +[] +type DiscordPlayer = { + Id : uint64 + Name : string +} + +[] +type Attack = { + HackType : Weapon + Target : DiscordPlayer + Timestamp : DateTime +} + +[] +type Defense = { + DefenseType : Shield + Timestamp : DateTime +} + +[] +type Player = { + Id : BsonObjectId + DiscordId : uint64 + Name : string + Weapons : Weapon array + Shields : Shield array + Attacks : Attack array + Defenses : Defense array + Bank : single +} + + diff --git a/Shared/paket.references b/Shared/paket.references new file mode 100644 index 0000000..7e4dfad --- /dev/null +++ b/Shared/paket.references @@ -0,0 +1,2 @@ +FSharp.Core +MongoDB.Driver diff --git a/Store/.dockerignore b/Store/.dockerignore new file mode 100644 index 0000000..38bece4 --- /dev/null +++ b/Store/.dockerignore @@ -0,0 +1,25 @@ +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/.idea +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/azds.yaml +**/bin +**/charts +**/docker-compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +LICENSE +README.md \ No newline at end of file diff --git a/Store/Dockerfile b/Store/Dockerfile new file mode 100644 index 0000000..603db69 --- /dev/null +++ b/Store/Dockerfile @@ -0,0 +1,18 @@ +FROM mcr.microsoft.com/dotnet/runtime:6.0 AS base +WORKDIR /app + +FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build +WORKDIR /src +COPY ["Store/Store.fsproj", "Store/"] +RUN dotnet restore "Store/Store.fsproj" +COPY . . +WORKDIR "/src/Store" +RUN dotnet build "Store.fsproj" -c Release -o /app/build + +FROM build AS publish +RUN dotnet publish "Store.fsproj" -c Release -o /app/publish + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "Store.dll"] diff --git a/Store/Program.fs b/Store/Program.fs new file mode 100644 index 0000000..f115430 --- /dev/null +++ b/Store/Program.fs @@ -0,0 +1,4 @@ + + +// For more information see https://aka.ms/fsharp-console-apps +printfn "Hello from F#" \ No newline at end of file diff --git a/Store/Store.fsproj b/Store/Store.fsproj new file mode 100644 index 0000000..e69619e --- /dev/null +++ b/Store/Store.fsproj @@ -0,0 +1,19 @@ + + + + Exe + net6.0 + Linux + + + + + + + + + + + + + \ No newline at end of file diff --git a/Store/paket.references b/Store/paket.references new file mode 100644 index 0000000..5753ebc --- /dev/null +++ b/Store/paket.references @@ -0,0 +1,7 @@ +FSharp.Core +DSharpPlus +// DSharpPlus.CommandsNext +// DSharpPlus.Interactivity +DSharpPlus.SlashCommands + +MongoDB.Driver diff --git a/Types.fs b/Types.fs deleted file mode 100644 index 4717a3d..0000000 --- a/Types.fs +++ /dev/null @@ -1,88 +0,0 @@ -module Joebot.Types - -open System - -type ActionClass = - | Network - | Exploit - | Penetration - -type IClass = abstract GetClass : unit -> ActionClass - -type Weapon = - | Virus - | Ransom - | Worm - | DDos - | Crack - | Injection - interface IClass with - member this.GetClass () = - match this with - | Virus | Ransom -> Exploit - | DDos | Worm -> Network - | Crack | Injection -> Penetration - static member TryParse weapon = - match weapon with - | "Virus" -> Some Virus - | "Ransom" -> Some Ransom - | "Worm" -> Some Worm - | "DDos" -> Some DDos - | "Crack" -> Some Crack - | "Injection" -> Some Injection - | _ -> None - -type Shield = - | Firewall - | PortScan - | Encryption - | Cypher - | Hardening - | Sanitation - interface IClass with - member this.GetClass () = - match this with - | Firewall | PortScan -> Network - | Encryption | Cypher -> Exploit - | Hardening | Sanitation -> Penetration - static member TryParse shield = - match shield with - | "Firewall" -> Some Firewall - | "PortScan" -> Some PortScan - | "Encryption" -> Some Encryption - | "Cypher" -> Some Cypher - | "Hardening" -> Some Hardening - | "Sanitation" -> Some Sanitation - | _ -> None - -type HackResult = - | Strong - | Weak - -type DiscordPlayer = { - Id : uint64 - Name : string -} - -type Attack = { - HackType : Weapon - Target : DiscordPlayer - Timestamp : DateTime -} - -type Defense = { - DefenseType : Shield - Timestamp : DateTime -} - -type Player = { - DiscordId : uint64 - Name : string - Weapons : Weapon list - Shields : Shield list - Attacks : Attack list - Defenses : Defense list - Bank : single -} - - diff --git a/challenge.jpg b/challenge.jpg deleted file mode 100644 index bce5e38..0000000 Binary files a/challenge.jpg and /dev/null differ diff --git a/paket.dependencies b/paket.dependencies index a935bee..f9261fa 100644 --- a/paket.dependencies +++ b/paket.dependencies @@ -10,4 +10,4 @@ source https://nuget.emzi0767.com/api/v3/index.json nuget DSharpPlus >= 4.2.0-nightly-01054 nuget DSharpPlus.SlashCommands >= 4.2.0-nightly-01054 -nuget LiteDB.FSharp 2.16.0 +nuget MongoDB.Driver diff --git a/paket.lock b/paket.lock index f161594..ac4ebf6 100644 --- a/paket.lock +++ b/paket.lock @@ -1,35 +1,16 @@ STORAGE: NONE RESTRICTION: || (== net6.0) (== netstandard2.0) (== netstandard2.1) NUGET - remote: https://nuget.emzi0767.com/api/v3/index.json - DSharpPlus (4.2.0-nightly-01054) - Emzi0767.Common (>= 2.6.2) - Microsoft.Extensions.Logging.Abstractions (>= 5.0) - Newtonsoft.Json (>= 13.0.1) - System.Memory (>= 4.5.4) - System.Net.Http (>= 4.3.4) - System.Net.WebSockets (>= 4.3) - System.Net.WebSockets.Client (>= 4.3.2) - System.Runtime.InteropServices.RuntimeInformation (>= 4.3) - System.Threading.Channels (>= 5.0) - DSharpPlus.SlashCommands (4.2.0-nightly-01054) - DSharpPlus (>= 4.2.0-nightly-01054) - Microsoft.Extensions.DependencyInjection (>= 5.0.1) remote: https://api.nuget.org/v3/index.json + DnsClient (1.5) + Microsoft.Win32.Registry (>= 5.0) + System.Buffers (>= 4.5.1) - restriction: || (&& (== net6.0) (>= net471)) (&& (== net6.0) (< netstandard2.0)) (&& (== net6.0) (< netstandard2.1)) (== netstandard2.0) (&& (== netstandard2.1) (>= net471)) (&& (== netstandard2.1) (< netstandard2.0)) Emzi0767.Common (2.6.2) System.Collections.Immutable (>= 5.0) System.Memory (>= 4.5.4) System.Runtime.CompilerServices.Unsafe (>= 5.0) System.ValueTuple (>= 4.5) FSharp.Core (6.0.1) - LiteDB (4.1.4) - System.Reflection (>= 4.3) - System.Reflection.TypeExtensions (>= 4.3) - LiteDB.FSharp (2.16) - FSharp.Core (>= 4.7.2) - LiteDB (>= 4.1.4 < 5.0) - Newtonsoft.Json (>= 13.0.1) - TypeShape (>= 9.0) Microsoft.Bcl.AsyncInterfaces (6.0) - restriction: || (&& (== net6.0) (>= net461)) (&& (== net6.0) (< netstandard2.1)) (== netstandard2.0) (&& (== netstandard2.1) (>= net461)) System.Threading.Tasks.Extensions (>= 4.5.4) - restriction: || (&& (== net6.0) (>= net461)) (&& (== net6.0) (< netstandard2.1)) (== netstandard2.0) (&& (== netstandard2.1) (>= net461)) Microsoft.Extensions.DependencyInjection (6.0) @@ -49,6 +30,24 @@ NUGET Microsoft.NETCore.Platforms (>= 1.1) Microsoft.NETCore.Targets (>= 1.1) System.Runtime (>= 4.3) + Microsoft.Win32.Registry (5.0) + System.Buffers (>= 4.5.1) - restriction: || (&& (== net6.0) (>= monoandroid) (< netstandard1.3)) (&& (== net6.0) (>= monotouch)) (&& (== net6.0) (< netcoreapp2.0)) (&& (== net6.0) (>= xamarinios)) (&& (== net6.0) (>= xamarinmac)) (&& (== net6.0) (>= xamarintvos)) (&& (== net6.0) (>= xamarinwatchos)) (== netstandard2.0) (== netstandard2.1) + System.Memory (>= 4.5.4) - restriction: || (&& (== net6.0) (< netcoreapp2.0)) (&& (== net6.0) (< netcoreapp2.1)) (&& (== net6.0) (>= uap10.1)) (== netstandard2.0) (== netstandard2.1) + System.Security.AccessControl (>= 5.0) + System.Security.Principal.Windows (>= 5.0) + MongoDB.Bson (2.14.1) + System.Runtime.CompilerServices.Unsafe (>= 5.0) + MongoDB.Driver (2.14.1) + MongoDB.Bson (>= 2.14.1) + MongoDB.Driver.Core (>= 2.14.1) + MongoDB.Libmongocrypt (>= 1.3) + MongoDB.Driver.Core (2.14.1) + DnsClient (>= 1.4) + MongoDB.Bson (>= 2.14.1) + MongoDB.Libmongocrypt (>= 1.3) + SharpCompress (>= 0.30.1) + System.Buffers (>= 4.5.1) + MongoDB.Libmongocrypt (1.3) Newtonsoft.Json (13.0.1) runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl (4.3.3) runtime.debian.9-x64.runtime.native.System.Security.Cryptography.OpenSsl (4.3.3) @@ -93,7 +92,10 @@ NUGET runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl (4.3.3) runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl (4.3.3) runtime.ubuntu.18.04-x64.runtime.native.System.Security.Cryptography.OpenSsl (4.3.3) - System.Buffers (4.5.1) - restriction: || (&& (== net6.0) (>= net461)) (== netstandard2.0) (== netstandard2.1) + SharpCompress (0.30.1) + System.Memory (>= 4.5.4) - restriction: || (&& (== net6.0) (>= net461)) (&& (== net6.0) (< netstandard2.1)) (== netstandard2.0) (&& (== netstandard2.1) (>= net461)) + System.Text.Encoding.CodePages (>= 5.0) - restriction: || (&& (== net6.0) (>= net461)) (&& (== net6.0) (< netcoreapp3.1)) (&& (== net6.0) (< netstandard2.1)) (== netstandard2.0) (== netstandard2.1) + System.Buffers (4.5.1) System.Collections (4.3) Microsoft.NETCore.Platforms (>= 1.1) Microsoft.NETCore.Targets (>= 1.1) @@ -294,9 +296,6 @@ NUGET System.IO (>= 4.3) System.Reflection.Primitives (>= 4.3) System.Runtime (>= 4.3) - System.Reflection.Emit.ILGeneration (4.7) - restriction: || (&& (== net6.0) (< netcoreapp2.0) (< netstandard2.1)) (&& (== net6.0) (< netstandard2.0)) (&& (== net6.0) (< portable-net45+wp8)) (&& (== net6.0) (>= uap10.1)) (== netstandard2.0) (&& (== netstandard2.1) (< netstandard2.0)) (&& (== netstandard2.1) (< portable-net45+wp8)) (&& (== netstandard2.1) (>= uap10.1)) - System.Reflection.Emit.LightWeight (4.7) - restriction: || (&& (== net6.0) (< netcoreapp3.1)) (== netstandard2.0) (== netstandard2.1) - System.Reflection.Emit.ILGeneration (>= 4.7) - restriction: || (&& (== net6.0) (< netcoreapp2.0) (< netstandard2.1)) (&& (== net6.0) (< netstandard2.0)) (&& (== net6.0) (< portable-net45+wp8)) (&& (== net6.0) (>= uap10.1)) (== netstandard2.0) (&& (== netstandard2.1) (< netstandard2.0)) (&& (== netstandard2.1) (< portable-net45+wp8)) (&& (== netstandard2.1) (>= uap10.1)) System.Reflection.Extensions (4.3) Microsoft.NETCore.Platforms (>= 1.1) Microsoft.NETCore.Targets (>= 1.1) @@ -306,7 +305,6 @@ NUGET Microsoft.NETCore.Platforms (>= 1.1) Microsoft.NETCore.Targets (>= 1.1) System.Runtime (>= 4.3) - System.Reflection.TypeExtensions (4.7) System.Resources.ResourceManager (4.3) Microsoft.NETCore.Platforms (>= 1.1) Microsoft.NETCore.Targets (>= 1.1) @@ -345,6 +343,8 @@ NUGET System.Resources.ResourceManager (>= 4.3) System.Runtime (>= 4.3) System.Runtime.Extensions (>= 4.3) + System.Security.AccessControl (6.0) + System.Security.Principal.Windows (>= 5.0) - restriction: || (&& (== net6.0) (>= net461)) (== netstandard2.0) (== netstandard2.1) System.Security.Claims (4.3) System.Collections (>= 4.3) System.Globalization (>= 4.3) @@ -440,6 +440,9 @@ NUGET Microsoft.NETCore.Platforms (>= 1.1) Microsoft.NETCore.Targets (>= 1.1) System.Runtime (>= 4.3) + System.Text.Encoding.CodePages (6.0) - restriction: || (&& (== net6.0) (>= net461)) (&& (== net6.0) (< netcoreapp3.1)) (&& (== net6.0) (< netstandard2.1)) (== netstandard2.0) (== netstandard2.1) + System.Memory (>= 4.5.4) - restriction: || (&& (== net6.0) (>= net461)) (&& (== net6.0) (< netcoreapp3.1)) (== netstandard2.0) (== netstandard2.1) + System.Runtime.CompilerServices.Unsafe (>= 6.0) System.Text.Encoding.Extensions (4.3) Microsoft.NETCore.Platforms (>= 1.1) Microsoft.NETCore.Targets (>= 1.1) @@ -464,6 +467,17 @@ NUGET Microsoft.NETCore.Targets (>= 1.1) System.Runtime (>= 4.3) System.ValueTuple (4.5) - TypeShape (10.0) - FSharp.Core (>= 4.5.4) - System.Reflection.Emit.LightWeight (>= 4.7) - restriction: || (&& (== net6.0) (< netcoreapp3.1)) (== netstandard2.0) (== netstandard2.1) + remote: https://nuget.emzi0767.com/api/v3/index.json + DSharpPlus (4.2.0-nightly-01059) + Emzi0767.Common (>= 2.6.2) + Microsoft.Extensions.Logging.Abstractions (>= 5.0) + Newtonsoft.Json (>= 13.0.1) + System.Memory (>= 4.5.4) + System.Net.Http (>= 4.3.4) + System.Net.WebSockets (>= 4.3) + System.Net.WebSockets.Client (>= 4.3.2) + System.Runtime.InteropServices.RuntimeInformation (>= 4.3) + System.Threading.Channels (>= 5.0) + DSharpPlus.SlashCommands (4.2.0-nightly-01059) + DSharpPlus (>= 4.2.0-nightly-01059) + Microsoft.Extensions.DependencyInjection (>= 5.0.1)