From a8ab4a6abd0785e33163f75ebb4d2c9f0fb34a25 Mon Sep 17 00:00:00 2001 From: Joseph Ferano Date: Thu, 24 Mar 2022 23:21:08 +0700 Subject: [PATCH] New Whitelist flow and accept invite when completing training --- Bot/Bot.fs | 6 +- Bot/DbService.fs | 7 +- Bot/GameTypes.fs | 4 +- Bot/Games/Trainer.fs | 16 ++- Bot/GuildEnvironment.fs | 5 + Bot/InviteTracker.fs | 255 +++++++++++++++++++++++++++++----------- 6 files changed, 216 insertions(+), 77 deletions(-) diff --git a/Bot/Bot.fs b/Bot/Bot.fs index 87f366c..febc661 100644 --- a/Bot/Bot.fs +++ b/Bot/Bot.fs @@ -81,10 +81,10 @@ let asdf (_ : DiscordClient) (event : DSharpPlus.EventArgs.InteractionCreateEven :> Task //hackerBattleBot.add_InteractionCreated(AsyncEventHandler(asdf)) -//if guild <> 922419263275425832uL then +if guild <> 922419263275425832uL then // Trainer.sendInitialEmbed hackerBattleBot + InviteTracker.sendInitialEmbed inviterBot -InviteTracker.sendInitialEmbed inviterBot hackerBattleBot.ConnectAsync() |> Async.AwaitTask |> Async.RunSynchronously GuildEnvironment.botUserHackerBattle <- Some hackerBattleBot.CurrentUser @@ -92,9 +92,9 @@ GuildEnvironment.botUserHackerBattle <- Some hackerBattleBot.CurrentUser storeBot.ConnectAsync() |> Async.AwaitTask |> Async.RunSynchronously GuildEnvironment.botUserArmory <- Some storeBot.CurrentUser +inviterBot.ConnectAsync() |> Async.AwaitTask |> Async.RunSynchronously //stealBot.ConnectAsync() |> Async.AwaitTask |> Async.RunSynchronously -inviterBot.ConnectAsync() |> Async.AwaitTask |> Async.RunSynchronously let rec loop areBotsRunning = diff --git a/Bot/DbService.fs b/Bot/DbService.fs index acaed00..4e8e2d5 100644 --- a/Bot/DbService.fs +++ b/Bot/DbService.fs @@ -15,6 +15,7 @@ type User = { Focus : int Charisma : int Luck : int + Active : bool } let getPlayerEvents (did : uint64) = @@ -75,7 +76,7 @@ let tryFindPlayer (discordId : uint64) = async { |> Sql.connect |> Sql.parameters [ "did", Sql.string (string discordId) ] |> Sql.query """ - SELECT discord_id, display_name, gbt, inventory, strength, focus, charisma, luck FROM "user" + SELECT discord_id, display_name, gbt, in_game, inventory, strength, focus, charisma, luck FROM "user" WHERE discord_id = @did """ |> Sql.executeAsync (fun read -> @@ -89,6 +90,7 @@ let tryFindPlayer (discordId : uint64) = async { Focus = read.intOrNone "focus" |> Option.defaultValue 0 Charisma = read.intOrNone "charisma" |> Option.defaultValue 0 Luck = read.intOrNone "luck" |> Option.defaultValue 0 + Active = read.bool "in_game" }) |> Async.AwaitTask match List.tryHead user with @@ -106,7 +108,8 @@ let tryFindPlayer (discordId : uint64) = async { Inventory = inventory Events = events Stats = { Strength = strength ; Focus = focus ; Charisma = charisma ; Luck = luck } - Bank = u.Bank } + Bank = u.Bank + Active = u.Active } with e -> printfn $"Got an error{e.Message}" return None diff --git a/Bot/GameTypes.fs b/Bot/GameTypes.fs index 17896fe..1f17d7a 100644 --- a/Bot/GameTypes.fs +++ b/Bot/GameTypes.fs @@ -174,6 +174,7 @@ type PlayerData = { Events : PlayerEvent list Stats : Stats Bank : int + Active : bool } // Achievements : string array // XP : int @@ -186,4 +187,5 @@ with member this.toDiscordPlayer = { Id = this.DiscordId ; Name = this.Name } Stats = Stats.empty // Achievements = [||] // XP = 0 - Bank = 0 } + Bank = 0 + Active = false } diff --git a/Bot/Games/Trainer.fs b/Bot/Games/Trainer.fs index 87764ff..13399c6 100644 --- a/Bot/Games/Trainer.fs +++ b/Bot/Games/Trainer.fs @@ -174,6 +174,9 @@ type the `/arsenal` command NOW""" if not completed then do! sendFollowUpMessage ctx message else + let role = ctx.GetGuild().GetRole(GuildEnvironment.roleTrainee) + do! ctx.GetDiscordMember().RevokeRoleAsync(role) |> Async.AwaitTask + do! sendFollowUpMessage ctx ($"Your training is now complete. If you want to buy more **HACKS & SHIELDS**, go to the <#{GuildEnvironment.channelArmory}> and type the `/buy-hack` and `/buy-shield` commands!") }) @@ -219,13 +222,20 @@ let handleArsenal (ctx : IDiscordContext) = PlayerInteractions.executePlayerActi do! ctx.FollowUp(embed) |> Async.AwaitTask let role = ctx.GetGuild().GetRole(GuildEnvironment.roleTrainee) - do! ctx.GetDiscordMember().RevokeRoleAsync(role) - |> Async.AwaitTask + do! ctx.GetDiscordMember().RevokeRoleAsync(role) |> Async.AwaitTask - do! Async.Sleep 2000 + let role = ctx.GetGuild().GetRole(GuildEnvironment.roleHacker) + do! ctx.GetDiscordMember().GrantRoleAsync(role) |> Async.AwaitTask + + do! InviteTracker.acceptInvite ctx player |> Async.AwaitTask + + do! Async.Sleep 1000 do! sendFollowUpMessage ctx $"Now get out of there and go hack other Degenz in the <#{GuildEnvironment.channelBattle}> channel!" else + let role = ctx.GetGuild().GetRole(GuildEnvironment.roleTrainee) + do! ctx.GetDiscordMember().RevokeRoleAsync(role) |> Async.AwaitTask + do! sendFollowUpMessage ctx ($"Your training is now complete. If you want to buy more **HACKS & SHIELDS**, go to the <#{GuildEnvironment.channelArmory}> and type the `/buy-hack` and `/buy-shield` commands!") }) diff --git a/Bot/GuildEnvironment.fs b/Bot/GuildEnvironment.fs index be9119d..902c5da 100644 --- a/Bot/GuildEnvironment.fs +++ b/Bot/GuildEnvironment.fs @@ -26,6 +26,9 @@ let channelArmory = getId "CHANNEL_ARMORY" let channelBattle = getId "CHANNEL_BATTLE" let channelWelcome = getId "CHANNEL_WELCOME" let channelWhitelist = getId "CHANNEL_WHITELIST" +let channelElite = getId "CHANNEL_ELITE" +let channelTosserTed = getId "CHANNEL_TOSSERTED" +let channelShelters = getId "CHANNEL_SHELTERS" //let channelBackAlley = getId "CHANNEL_BACKALLEY" //let channelMarket = getId "CHANNEL_MARKET" //let channelAccessoryShop = getId "CHANNEL_ACCESSORIES" @@ -33,8 +36,10 @@ let channelWhitelist = getId "CHANNEL_WHITELIST" //let channelThievery = getId "CHANNEL_THIEVERY" let botIdHackerBattle = getId "BOT_HACKER_BATTLE" let botIdArmory = getId "BOT_ARMORY" +let botIdTosserTed = getId "BOT_TOSSERTED" //let botInviter = getId "BOT_INVITER" let roleTrainee = getId "ROLE_TRAINEE" +let roleHacker = getId "ROLE_HACKER" let rolePrisoner = getId "ROLE_PRISONER" let roleWhitelist = getId "ROLE_WHITELIST" diff --git a/Bot/InviteTracker.fs b/Bot/InviteTracker.fs index d19ef63..8fb8adc 100644 --- a/Bot/InviteTracker.fs +++ b/Bot/InviteTracker.fs @@ -11,7 +11,7 @@ open Npgsql.FSharp let connStr = GuildEnvironment.connectionString let InviteRewardAmount = 100 -let WhitelistInviteRequirement = 5 +let WhitelistPrice = 1000 type Invite = { Code : string @@ -19,20 +19,18 @@ type Invite = { Count : int } -let getInvites () = async { +let private mapInvite (reader : RowReader) = { + Code = reader.string "code" + Inviter = reader.string "inviter" |> uint64 + Count = reader.int "count" +} + +let private getInvites () = async { let! invites = connStr |> Sql.connect - // TODO: Invites shouldn't expire anymore - |> Sql.query """ - SELECT code, inviter, count FROM invite - WHERE created_at > (current_timestamp at time zone 'utc') - interval '1 day' - """ - |> Sql.executeAsync (fun read -> { - Code = read.string "code" - Inviter = read.string "inviter" |> uint64 - Count = read.int "count" - }) + |> Sql.query "SELECT code, inviter, count FROM invite" + |> Sql.executeAsync mapInvite |> Async.AwaitTask return invites @@ -40,7 +38,7 @@ let getInvites () = async { |> Map.ofList } -let getInvitesFromUser discordId = async { +let private getInvitesFromUser discordId = async { let! invites = connStr |> Sql.connect @@ -61,7 +59,7 @@ let getInvitesFromUser discordId = async { |> Map.ofList } -let createInvite inviter code = +let private createInvite inviter code = connStr |> Sql.connect |> Sql.parameters [ "code" , Sql.string code ; "inviter" , Sql.string (string inviter) ] @@ -69,7 +67,7 @@ let createInvite inviter code = |> Sql.executeNonQueryAsync |> Async.AwaitTask -let addInvitedUser did code count = +let private addInvitedUser did code count = try connStr |> Sql.connect @@ -84,7 +82,7 @@ let addInvitedUser did code count = |> Async.Ignore with _ -> async.Zero () -let acceptInvite did = +let private markInvitedAccepted did = connStr |> Sql.connect |> Sql.parameters [ "did" , Sql.string (string did) ] @@ -92,7 +90,19 @@ let acceptInvite did = |> Sql.executeNonQueryAsync |> Async.AwaitTask -let removeInvitedUser did = +let private getInviteFromInvitedUser invitedUser = + connStr + |> Sql.connect + |> Sql.parameters [ "did" , Sql.string (string invitedUser) ] + |> Sql.query """ + SELECT code, inviter, count FROM invite + JOIN invited_user iu ON invite.id = iu.invite_id + WHERE iu.discord_id = @did + """ + |> Sql.executeRowAsync mapInvite + |> Async.AwaitTask + +let private removeInvitedUser did = try connStr |> Sql.connect @@ -103,7 +113,7 @@ let removeInvitedUser did = |> Async.Ignore with _ -> async.Zero () -let checkUserInvited userId = async { +let private checkUserAlreadyInvited userId = async { let! result = connStr |> Sql.connect @@ -117,7 +127,22 @@ let checkUserInvited userId = async { return List.isEmpty result |> not } -let getInviteAttributions userId = +let checkInviteAccepted (userId : uint64) = async { + try + let! result = + connStr + |> Sql.connect + |> Sql.parameters [ "did" , Sql.string (string userId) ] + |> Sql.query "SELECT accepted FROM invited_user WHERE discord_id = @did" + |> Sql.executeRowAsync (fun read -> read.bool "accepted") + |> Async.AwaitTask + return result + with ex -> + printfn "%s %u" ex.Message userId + return false +} + +let private getInviteAttributions userId = connStr |> Sql.connect |> Sql.parameters [ "did" , Sql.string (string userId) ] @@ -129,7 +154,7 @@ let getInviteAttributions userId = |> Sql.executeRowAsync (fun read -> read.int "count") |> Async.AwaitTask -let getInvitedUsers userId = +let private getInvitedUsers userId = connStr |> Sql.connect |> Sql.parameters [ "did" , Sql.string (string userId) ] @@ -141,7 +166,7 @@ let getInvitedUsers userId = |> Sql.executeAsync (fun read -> read.string "discord_id" |> uint64) |> Async.AwaitTask -let createGuildInvite (ctx : IDiscordContext) showWhitelistReward = +let private createGuildInvite (ctx : IDiscordContext) showWhitelistReward = task { let invitesRequired = 5 let rewardMsg = @@ -168,7 +193,7 @@ let createGuildInvite (ctx : IDiscordContext) showWhitelistReward = } :> Task -let listServerInvites (ctx : IDiscordContext) = task { +let private listServerInvites (ctx : IDiscordContext) = task { let! invites = ctx.GetGuild().GetInvitesAsync() let sb = StringBuilder() for invite in invites do @@ -180,7 +205,7 @@ let listServerInvites (ctx : IDiscordContext) = task { do! ctx.Respond(InteractionResponseType.ChannelMessageWithSource, msg) } -let getAttributions (ctx : IDiscordContext) userId = task { +let private getAttributions (ctx : IDiscordContext) userId = task { let! total = getInviteAttributions(userId) let msg = DiscordInteractionResponseBuilder() @@ -189,7 +214,7 @@ let getAttributions (ctx : IDiscordContext) userId = task { do! ctx.Respond(InteractionResponseType.ChannelMessageWithSource, msg) } -let getInvitedUsersForId (ctx : IDiscordContext) = task { +let private getInvitedUsersForId (ctx : IDiscordContext) = task { let! users = getInvitedUsers(ctx.GetDiscordMember().Id) let sb = StringBuilder() let mutable count = 0 @@ -213,35 +238,47 @@ let clearInvites (ctx : IDiscordContext) = task { do! invites |> Seq.map (fun invite -> invite.DeleteAsync() |> Async.AwaitTask) - |> Async.Parallel + |> Async.Sequential |> Async.Ignore } -let processNewUser (eventArgs : GuildMemberAddEventArgs) = +// Discord doesn't have any way to tell you if the user came via an invite, the only way to tell is to compare the +// cached invites in the DB to the ones in the guild and see if any has been incremented +let private processNewUser (eventArgs : GuildMemberAddEventArgs) = task { let! guildInvites = eventArgs.Guild.GetInvitesAsync() - let! cachedInvites = getInvites() + let! cachedInvites = getInvites () for invite in guildInvites do let result = cachedInvites.TryFind(invite.Code) match result with - | Some (inviter,count) -> + | Some (_,count) -> if invite.Uses > count then do! addInvitedUser eventArgs.Member.Id invite.Code invite.Uses |> Async.Ignore - let! _ = acceptInvite eventArgs.Member.Id - let! player = DbService.tryFindPlayer inviter - match player with - | Some player -> - do! DbService.updatePlayerCurrency (int InviteRewardAmount) player |> Async.Ignore - let builder = DiscordMessageBuilder() - builder.WithContent($"{eventArgs.Member.DisplayName} was recruited to the server. <@{player.DiscordId}> just earned {InviteRewardAmount} 💰$GBT for their efforts!") |> ignore - let channel = eventArgs.Guild.GetChannel(GuildEnvironment.channelEventsHackerBattle) - do! channel.SendMessageAsync(builder) - |> Async.AwaitTask - |> Async.Ignore - | None -> return () | None -> () } :> Task +let acceptInvite (ctx : IDiscordContext) (invitedPlayer : PlayerData) = + task { + match! checkInviteAccepted invitedPlayer.DiscordId with + | false -> + let! _ = markInvitedAccepted invitedPlayer.DiscordId |> Async.Ignore + try + let! inviter = getInviteFromInvitedUser invitedPlayer.DiscordId + let! player = DbService.tryFindPlayer inviter.Inviter + match player with + | Some player -> + do! DbService.updatePlayerCurrency (int InviteRewardAmount) player |> Async.Ignore + let builder = DiscordMessageBuilder() + builder.WithContent($"{invitedPlayer.Name} was recruited to the server. <@{player.DiscordId}> just earned {InviteRewardAmount} 💰$GBT for their efforts!") |> ignore + let channel = ctx.GetGuild().GetChannel(GuildEnvironment.channelEventsHackerBattle) + do! channel.SendMessageAsync(builder) + |> Async.AwaitTask + |> Async.Ignore + | None -> return () + with _ -> () + | true -> return () + } :> Task + // If we do it like this then there's an obvious exploit where the user can come and go as many times and it will keep // rewarding GBT. //let handleGuildMemberRemoved _ (eventArgs : GuildMemberRemoveEventArgs) = @@ -249,16 +286,30 @@ let processNewUser (eventArgs : GuildMemberAddEventArgs) = // do! removeInvitedUser eventArgs.Member.Id // } :> Task +//Degenz Game +//Mint Date: April 2022 +//Supply: 3,333 +//Price: 1.984 $SOL + +//Your NFT will be your In-Game Character that provides you with unique traits, and abilities in game. + let sendInitialEmbed (client : DiscordClient) = async { try let! channel = client.GetChannelAsync(GuildEnvironment.channelWhitelist) |> Async.AwaitTask let builder = DiscordMessageBuilder() let embed = DiscordEmbedBuilder() - embed.ImageUrl <- "https://securitygladiators.com/wp-content/uploads/2020/09/Whitelist-Website-Featured-Image.jpg" + embed.ImageUrl <- "https://s1.gifyu.com/images/whitelist-image-2.gif" + embed.Title <- "Degenz Game" + embed.Description <- """ + Mint Date: **April 2022** + Supply: **3,333** + Price: **1.984 $SOL** + + Your NFT will be your In-Game Character that provides you with unique traits, and abilities in game. + """ builder.AddEmbed embed |> ignore - builder.Content <- "Click on the button to get whitelist!" - let button = DiscordButtonComponent(ButtonStyle.Success, $"GimmeWhitelist", $"Gimme") :> DiscordComponent + let button = DiscordButtonComponent(ButtonStyle.Success, $"GimmeWhitelist", $"Give Me Whitelist") :> DiscordComponent builder.AddComponents [| button |] |> ignore do! channel.SendMessageAsync(builder) |> Async.AwaitTask @@ -268,40 +319,107 @@ let sendInitialEmbed (client : DiscordClient) = } |> Async.RunSynchronously type WhitelistResult = - | NotEnoughInvites of currentAmount : int - | Granted of DiscordRole + | NotInGame + | NotEnoughGBT of currentAmount : int + | Granted of PlayerData | AlreadyWhitelisted let tryGrantWhitelist (ctx : IDiscordContext) = - async { + task { let user = ctx.GetDiscordMember() - let role = ctx.GetGuild().GetRole(GuildEnvironment.roleWhitelist) - if Seq.contains role user.Roles - then return AlreadyWhitelisted - else - let! total = getInviteAttributions user.Id - if total >= WhitelistInviteRequirement then - return Granted role + match! DbService.tryFindPlayer user.Id with + | Some player -> + let role = ctx.GetGuild().GetRole(GuildEnvironment.roleWhitelist) + if player.Active then + if Seq.contains role user.Roles then + return AlreadyWhitelisted else - return NotEnoughInvites total + if int player.Bank >= WhitelistPrice then + return Granted player + else + return NotEnoughGBT (int player.Bank) + else + return NotInGame + | None -> return NotInGame } -let handleWhitelist (ctx : IDiscordContext) = +let handleGimmeWhitelist (ctx : IDiscordContext) = task { let builder = DiscordInteractionResponseBuilder().AsEphemeral(true) + do! ctx.Respond(InteractionResponseType.DeferredChannelMessageWithSource, builder) + + let builder = DiscordFollowupMessageBuilder().AsEphemeral(true) match! tryGrantWhitelist ctx with + | NotInGame -> + builder.Content <- $""" +Woah slow down buddy… You’re not even in the game yet! + +To get Whitelisted you need to buy it with **$GBT** by playing the game. + +Go to <#{GuildEnvironment.channelShelters}> NOW to get assigned a private bunk, and **JOIN THE GAME!** +""" | AlreadyWhitelisted -> - builder.Content <- "You are already whitelisted" - do! ctx.Respond(InteractionResponseType.ChannelMessageWithSource, builder) - | NotEnoughInvites total -> - do! createGuildInvite ctx true -// builder.Content <- "Testing" -// do! ctx.Respond(InteractionResponseType.ChannelMessageWithSource, builder) -// do! async.Zero() - | Granted role -> + builder.Content <- $""" +🎉 You're already WHITELISTED! + +Come hang with all the other VIP Degenz in the <#{GuildEnvironment.channelElite}> +""" + | NotEnoughGBT total -> + builder.Content <- $""" +You don't have enough **$GBT** to buy a WHITELIST spot! + +WHITELIST ain’t cheap, and looks like you're `{WhitelistPrice - total} $GBT` short. + +Go earn more $GBT, and come back when you have `{WhitelistPrice} $GBT`! + +`/recruit` other Degenz and get `{InviteRewardAmount} 💰 $GBT` for every Degen you recruit! + +`/hack` other Degenz in <#{GuildEnvironment.channelBattle}> to steal their **$GBT** + +`/toss` against <@{GuildEnvironment.botIdTosserTed}> in <#{GuildEnvironment.channelTosserTed}> to try double up! + +Good luck Degen ✊ +""" + | Granted _ -> + let button = DiscordButtonComponent(ButtonStyle.Success, $"BuyWhitelist", $"Buy Now") :> DiscordComponent + builder.AddComponents([ button ]) |> ignore + builder.Content <- """ +Look at you Degen, you played Big Brother’s games and made it out alive! + +Now you can use your $GBT to pay for one of our coveted Whitelist spots. + +Click buy now below and the role will be auto assigned to you. +""" + do! ctx.FollowUp(builder) + } :> Task + +let handleBuyWhitelist (ctx : IDiscordContext) = + task { + let builder = DiscordInteractionResponseBuilder().AsEphemeral(true) + do! ctx.Respond(InteractionResponseType.DeferredChannelMessageWithSource, builder) + + let builder = DiscordFollowupMessageBuilder().AsEphemeral(true) + match! tryGrantWhitelist ctx with + | NotInGame -> builder.Content <- $"You somehow have left the game, what exactly are you doing?" + | AlreadyWhitelisted -> + builder.Content <- $""" +🎉 You're already WHITELISTED! + +Come hang with all the other VIP Degenz in the <#{GuildEnvironment.channelElite}> +""" + | NotEnoughGBT _ -> builder.Content <- $"You somehow do not have enough $GBT, what exactly are you doing?" + | Granted player -> + let role = ctx.GetGuild().GetRole(GuildEnvironment.roleWhitelist) do! ctx.GetDiscordMember().GrantRoleAsync(role) - builder.Content <- "You have been granted whitelist" - do! ctx.Respond(InteractionResponseType.ChannelMessageWithSource, builder) + let! _ = DbService.updatePlayerCurrency -WhitelistPrice player + builder.Content <- $""" +🎉 Congratulations you’ve been WHITELISTED! + +Come hang with all the other VIP Degenz in the <#{GuildEnvironment.channelElite}> + +`/recruit` other Degenz and get 💰 $GBT {InviteRewardAmount} for every Degen you recruit! +""" + do! ctx.FollowUp(builder) } :> Task let handleCreateInvite (ctx : IDiscordContext) = @@ -333,12 +451,13 @@ let handleCreateInvite (ctx : IDiscordContext) = .AsEphemeral(true) do! ctx.FollowUp(msg) - } + } :> Task let handleButtonEvent (_ : DiscordClient) (event : ComponentInteractionCreateEventArgs) = let eventCtx = DiscordEventContext event :> IDiscordContext match event.Id with - | id when id.StartsWith("GimmeWhitelist") -> handleWhitelist eventCtx + | id when id.StartsWith("GimmeWhitelist") -> handleGimmeWhitelist eventCtx + | id when id.StartsWith("BuyWhitelist") -> handleBuyWhitelist eventCtx | id when id.StartsWith("CreateGuildInvite") -> handleCreateInvite eventCtx | _ -> task { @@ -350,7 +469,7 @@ let handleButtonEvent (_ : DiscordClient) (event : ComponentInteractionCreateEv let handleGuildMemberAdded _ (eventArgs : GuildMemberAddEventArgs) = task { - let! exists = checkUserInvited eventArgs.Member.Id + let! exists = checkUserAlreadyInvited eventArgs.Member.Id if not exists then do! processNewUser eventArgs } :> Task