Restructuring project for new bots

This commit is contained in:
Joseph Ferano 2022-01-15 21:13:33 +07:00
parent 5ccb4b730f
commit e40fc42482
21 changed files with 621 additions and 456 deletions

View File

@ -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 = []
[<Literal>]
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<DiscordComponent>
|> 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<DiscordComponent>
|> 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

34
Degenz.sln Normal file
View File

@ -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

View File

@ -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

290
HackerBattle/Commands.fs Normal file
View File

@ -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
[<Literal>]
// 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<Player>("players")
let tryFindPlayer (id : uint64) : Async<Player option> =
async {
let filter = Builders<Player>.Filter.Eq((fun p -> p.DiscordId), id)
let! player = players.FindAsync<Player>(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<Player>.Filter.Eq((fun p -> p.DiscordId), attacker.DiscordId)
let update = Builders<Player>.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<DiscordComponent>
|> 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<Player>.Filter.Eq((fun p -> p.DiscordId), player.DiscordId)
let update = Builders<Player>.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<DiscordComponent>
|> 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>(weapon) ; Timestamp = DateTime.UtcNow ; Target = { Id = targetId ; Name = split.[3] } }
let filter = Builders<Player>.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>(weapon) ; Timestamp = DateTime.UtcNow ; Target = { Id = targetId ; Name = split.[3] } }
let filter = Builders<Player>.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<Player>.Filter.Eq((fun p -> p.DiscordId), player.DiscordId)
let update = Builders<Player>.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

18
HackerBattle/Dockerfile Normal file
View File

@ -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"]

View File

@ -1,45 +1,45 @@
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 ]
let weapons = [| getRandom h1 ; getRandom h2 ; getRandom h3 |]
let shields = [| getRandom d1 ; getRandom d2 ; getRandom d3 |]
{ DiscordId = membr
{ 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(
@ -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,15 +70,15 @@ 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

View File

@ -3,21 +3,23 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<RootNamespace>discord_bot</RootNamespace>
<RootNamespace>hacker-game</RootNamespace>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup>
<ItemGroup>
<Content Include="challenge.jpg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include=".dockerignore" />
<Content Include="Dockerfile" />
</ItemGroup>
<ItemGroup>
<ReferencePathWithRefAssemblies Update="\home\joe\.nuget\packages\dsharpplus.slashcommands\4.2.0-nightly-01054\lib\netstandard2.0\DSharpPlus.SlashCommands.dll" />
</ItemGroup>
<ItemGroup>
<Compile Include="Types.fs" />
<Compile Include="Functions.fs" />
<Compile Include="Commands.fs" />
<Compile Include="Program.fs" />
</ItemGroup>
<Import Project=".paket\Paket.Restore.targets" />
<ItemGroup>
<ProjectReference Include="..\Shared\Shared.fsproj" />
</ItemGroup>
<Import Project="..\.paket\Paket.Restore.targets" />
</Project>

View File

@ -1,4 +1,4 @@
module Joebot.Program
module DegenzGame.Program
open System
open System.Threading.Tasks
@ -7,12 +7,13 @@ 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 ()
[<SlashCommand("redpill", "Take the redpill and become a hacker")>]
@ -46,7 +47,10 @@ client.add_ComponentInteractionCreated(AsyncEventHandler(handleButtonEvent))
let slash = client.UseSlashCommands()
slash.RegisterCommands<JoeBot>(922414052708327494uL);
// My server
slash.RegisterCommands<HackerGame>(922419263275425832uL);
// Degenz
//slash.RegisterCommands<HackerGame>(922414052708327494uL);
client.ConnectAsync ()
|> Async.AwaitTask

View File

@ -3,4 +3,5 @@ DSharpPlus
// DSharpPlus.CommandsNext
// DSharpPlus.Interactivity
DSharpPlus.SlashCommands
LiteDB.FSharp
MongoDB.Driver

11
Shared/Shared.fsproj Normal file
View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
<Compile Include="Types.fs" />
</ItemGroup>
<Import Project="..\.paket\Paket.Restore.targets" />
</Project>

67
Shared/Types.fs Normal file
View File

@ -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
[<CLIMutable>]
type DiscordPlayer = {
Id : uint64
Name : string
}
[<CLIMutable>]
type Attack = {
HackType : Weapon
Target : DiscordPlayer
Timestamp : DateTime
}
[<CLIMutable>]
type Defense = {
DefenseType : Shield
Timestamp : DateTime
}
[<CLIMutable>]
type Player = {
Id : BsonObjectId
DiscordId : uint64
Name : string
Weapons : Weapon array
Shields : Shield array
Attacks : Attack array
Defenses : Defense array
Bank : single
}

2
Shared/paket.references Normal file
View File

@ -0,0 +1,2 @@
FSharp.Core
MongoDB.Driver

25
Store/.dockerignore Normal file
View File

@ -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

18
Store/Dockerfile Normal file
View File

@ -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"]

4
Store/Program.fs Normal file
View File

@ -0,0 +1,4 @@
// For more information see https://aka.ms/fsharp-console-apps
printfn "Hello from F#"

19
Store/Store.fsproj Normal file
View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup>
<ItemGroup>
<Compile Include="Program.fs" />
</ItemGroup>
<ItemGroup>
<Content Include=".dockerignore" />
<Content Include="Dockerfile" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Shared\Shared.fsproj" />
</ItemGroup>
<Import Project="..\.paket\Paket.Restore.targets" />
</Project>

7
Store/paket.references Normal file
View File

@ -0,0 +1,7 @@
FSharp.Core
DSharpPlus
// DSharpPlus.CommandsNext
// DSharpPlus.Interactivity
DSharpPlus.SlashCommands
MongoDB.Driver

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

View File

@ -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

View File

@ -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)