diff --git a/Bot/Analytics.fs b/Bot/Analytics.fs
new file mode 100644
index 0000000..17689ee
--- /dev/null
+++ b/Bot/Analytics.fs
@@ -0,0 +1,79 @@
+module Degenz.Analytics
+
+open Mixpanel
+
+let private mix = MixpanelClient(GuildEnvironment.tokenMixpanel)
+
+let private track eventName id data =
+ let map =
+ [ "distinct_id" , box id
+ "guild_id" , box GuildEnvironment.guildId
+ "env" , box GuildEnvironment.environment ]
+ mix.TrackAsync(eventName, data @ map |> dict) |> Async.AwaitTask |> Async.Ignore
+
+let invitedUserEntered inviteCode inviterId inviteeId inviterName inviteeName =
+ let data = [
+ "user_display_name" , inviterName
+ "invite_code" , inviteCode
+ "invitee_id" , inviteeId
+ "invitee_name" , inviteeName
+ ]
+ track "Invited User Entered" inviterId data
+
+let invitedUserAccepted inviteCode inviterId inviteeId inviterName inviteeName =
+ let data = [
+ "user_display_name" , inviterName
+ "invite_code" , inviteCode
+ "invitee_id" , inviteeId
+ "invitee_name" , inviteeName
+ ]
+ track "Invited User Accepted" inviterId data
+
+let recruitCommand origin id name channelId channelName =
+ let data = [
+ "user_display_name" , name
+ "origin" , origin
+ "channel_id" , channelId
+ "channel_name" , channelName
+ ]
+ track "Recruit Command Invoked" id data
+
+let recruitLinkButton inviteCode id name channelId channelName =
+ let data = [
+ "user_display_name" , name
+ "invite_code" , inviteCode
+ "channel_id" , channelId
+ "channel_name" , channelName
+ ]
+ track "Recruited Command Invoked" id data
+
+let recruitedCommand totalUsers id name channelId channelName =
+ let data = [
+ "user_display_name" , name
+ "total_users_at_the_time" , totalUsers
+ "channel_id" , channelId
+ "channel_name" , channelName
+ ]
+ track "Recruited Command Invoked" id data
+
+let whiteListButton availability id name =
+ let data = [
+ "user_display_name" , name
+ "availability" , availability
+ ]
+ track "Recruited Command Invoked" id data
+
+let whiteListPurchased amount id name =
+ let data = [
+ "user_display_name" , name
+ "purchase_amount" , amount
+ ]
+ track "Recruited Command Invoked" id data
+
+let trainingDojoStep step id name =
+ let data = [
+ "user_display_name" , name
+ "step" , step
+ ]
+ track "Recruited Command Invoked" id data
+
diff --git a/Bot/Bot.fs b/Bot/Bot.fs
index f1db188..b74a054 100644
--- a/Bot/Bot.fs
+++ b/Bot/Bot.fs
@@ -6,13 +6,10 @@ open DSharpPlus
open DSharpPlus.SlashCommands
open Degenz
open Emzi0767.Utilities
-open Mixpanel
//open Degenz.SlotMachine
type EmptyGlobalCommandToAvoidFamousDuplicateSlashCommandsBug() = inherit ApplicationCommandModule ()
-let mix = MixpanelClient("SOMETOKEN")
-
let guild = GuildEnvironment.guildId
let hackerBattleConfig = DiscordConfiguration()
diff --git a/Bot/Bot.fsproj b/Bot/Bot.fsproj
index e99922e..23254fe 100644
--- a/Bot/Bot.fsproj
+++ b/Bot/Bot.fsproj
@@ -17,6 +17,7 @@
+
diff --git a/Bot/Games/Trainer.fs b/Bot/Games/Trainer.fs
index 43ec543..d52a01c 100644
--- a/Bot/Games/Trainer.fs
+++ b/Bot/Games/Trainer.fs
@@ -70,6 +70,7 @@ let handleTrainerStep1 (ctx : IDiscordContext) =
.AsEphemeral(true)
do! ctx.Respond(InteractionResponseType.ChannelMessageWithSource, builder) |> Async.AwaitTask
+ do! Analytics.trainingDojoStep "LFG" (ctx.GetDiscordMember().Id) (ctx.GetDiscordMember().Username)
} |> Async.StartAsTask :> Task
let defend (ctx : IDiscordContext) =
@@ -79,6 +80,7 @@ let defend (ctx : IDiscordContext) =
let name = if System.String.IsNullOrEmpty m.Nickname then m.DisplayName else m.Nickname
let embed = Embeds.pickDefense "Trainer-2" { PlayerData.empty with Inventory = [ Shield defaultShield ] ; Name = name } true
do! ctx.FollowUp(embed) |> Async.AwaitTask
+ do! Analytics.trainingDojoStep "DefendCommand" (ctx.GetDiscordMember().Id) (ctx.GetDiscordMember().Username)
} |> Async.StartAsTask :> Task
let handleDefenseMsg hackId = {
@@ -104,6 +106,7 @@ let handleDefense (ctx : IDiscordContext) =
do! sendMessage' $"❌ HACKING FAILED!\n\n{playerName} defended hack from <@{Sensei.Id}>!"
do! Async.Sleep 1500
do! sendFollowUpMessageWithButton ctx (handleDefenseMsg defaultHack.Item.Name)
+ do! Analytics.trainingDojoStep "ShieldActivated" (ctx.GetDiscordMember().Id) (ctx.GetDiscordMember().Username)
} |> Async.StartAsTask :> Task
let handleTrainerStep3 (ctx : IDiscordContext) =
@@ -117,6 +120,7 @@ let handleTrainerStep3 (ctx : IDiscordContext) =
+ $"Type the `/hack` command now, then choose me - <@{Sensei.Id}> as your target, and select `{defaultHack.Item.Name}`")
do! ctx.Respond(InteractionResponseType.ChannelMessageWithSource, builder) |> Async.AwaitTask
+ do! Analytics.trainingDojoStep "LetsHack" (ctx.GetDiscordMember().Id) (ctx.GetDiscordMember().Username)
} |> Async.StartAsTask :> Task
let hack (target : DiscordUser) (ctx : IDiscordContext) =
@@ -138,6 +142,7 @@ let hack (target : DiscordUser) (ctx : IDiscordContext) =
.AsEphemeral(true)
do! ctx.FollowUp(builder) |> Async.AwaitTask
+ do! Analytics.trainingDojoStep "HackCommand" (ctx.GetDiscordMember().Id) (ctx.GetDiscordMember().Username)
} |> Async.StartAsTask :> Task
let handleHack (ctx : IDiscordContext) =
@@ -153,7 +158,8 @@ let handleHack (ctx : IDiscordContext) =
+ "When you **HACK** other Degenz, you **TAKE** their 💰$GBT.\n"
+ "But remember, hacks take time to recover, so use them wisely.")
- do! Async.Sleep 2000
+ do! Analytics.trainingDojoStep "HackActivated" (ctx.GetDiscordMember().Id) (ctx.GetDiscordMember().Username)
+ do! Async.Sleep 1000
let message =
$"""
@@ -181,6 +187,7 @@ type the `/arsenal` command NOW"""
do! ctx.GetDiscordMember().GrantRoleAsync(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!")
+ do! Analytics.trainingDojoStep "CompletedNoGifts" (ctx.GetDiscordMember().Id) (ctx.GetDiscordMember().Username)
})
let handleArsenal (ctx : IDiscordContext) = PlayerInteractions.executePlayerAction ctx (fun player -> async {
@@ -235,6 +242,7 @@ let handleArsenal (ctx : IDiscordContext) = PlayerInteractions.executePlayerActi
do! Async.Sleep 1000
do! sendFollowUpMessage ctx $"Now get out of there and go hack other Degenz in the <#{GuildEnvironment.channelBattle}> channel!"
+ do! Analytics.trainingDojoStep "CompletedWithGifts" (ctx.GetDiscordMember().Id) (ctx.GetDiscordMember().Username)
else
let role = ctx.GetGuild().GetRole(GuildEnvironment.roleTrainee)
do! ctx.GetDiscordMember().RevokeRoleAsync(role) |> Async.AwaitTask
diff --git a/Bot/GuildEnvironment.fs b/Bot/GuildEnvironment.fs
index 85e0ad4..7f01f0b 100644
--- a/Bot/GuildEnvironment.fs
+++ b/Bot/GuildEnvironment.fs
@@ -15,11 +15,13 @@ let getId str = getVar str |> uint64
let connectionString = (getVar "DATABASE_URL").Replace("postgresql://", "postgres://").Replace("?sslmode=require", "")
let guildId = getId "DISCORD_GUILD"
+let environment = getVar "ENVIRONMENT"
let tokenPlayerInteractions = getVar "TOKEN_PLAYER_INTERACTIONS"
let tokenSteal = getVar "TOKEN_STEAL"
let tokenHackerBattle = getVar "TOKEN_HACKER_BATTLE"
let tokenStore = getVar "TOKEN_STORE"
let tokenInviter = getVar "TOKEN_INVITER"
+let tokenMixpanel = getVar "TOKEN_MIXPANEL"
let channelEventsHackerBattle = getId "CHANNEL_EVENTS_HACKER_BATTLE"
let channelTraining = getId "CHANNEL_TRAINING"
let channelArmory = getId "CHANNEL_ARMORY"
diff --git a/Bot/InviteTracker.fs b/Bot/InviteTracker.fs
index 9d853c0..1004934 100644
--- a/Bot/InviteTracker.fs
+++ b/Bot/InviteTracker.fs
@@ -199,7 +199,7 @@ let guildInviteEmbed =
let button = DiscordButtonComponent(ButtonStyle.Success, $"CreateGuildInvite", $"GET MY UNIQUE LINK") :> DiscordComponent
builder.AddComponents [| button |]
-let private showInviteMessage (ctx : IDiscordContext) =
+let private showInviteMessage (ctx : IDiscordContext) origin =
task {
let builder = DiscordInteractionResponseBuilder().AsEphemeral(true)
do! ctx.Respond(InteractionResponseType.DeferredChannelMessageWithSource, builder)
@@ -216,6 +216,7 @@ let private showInviteMessage (ctx : IDiscordContext) =
You must **COMPLETE YOUR TRAINING FIRST!** Then you can `/recruit`...
Go to <#{GuildEnvironment.channelTraining}> now to become a **HACKER**!
"""
+ do! Analytics.recruitCommand origin player.DiscordId (ctx.GetDiscordMember().Username) (ctx.GetChannel().Id) (ctx.GetChannel().Name)
| None ->
do! sendFollowUpMessage ctx $"You're not in the game! Go to <#{GuildEnvironment.channelShelters}> NOW to get assigned a private bunk, and **JOIN THE GAME!**"
} :> Task
@@ -259,6 +260,9 @@ let private getInvitedUsersForId (ctx : IDiscordContext) = task {
.AsEphemeral(true)
.WithContent(str)
do! ctx.Respond(InteractionResponseType.ChannelMessageWithSource, msg)
+ let user = ctx.GetDiscordMember()
+ let channel = ctx.GetChannel()
+ do! Analytics.recruitedCommand total user.Id user.Username channel.Id channel.Name
}
let clearInvites (ctx : IDiscordContext) = task {
@@ -281,6 +285,7 @@ let private processNewUser (eventArgs : GuildMemberAddEventArgs) =
match result with
| Some (_,count) ->
if invite.Uses > count then
+ do! Analytics.invitedUserEntered invite.Code invite.Inviter.Id eventArgs.Member.Id invite.Inviter.Username eventArgs.Member.Username
do! addInvitedUser eventArgs.Member.Id invite.Code invite.Uses |> Async.Ignore
| None -> ()
} :> Task
@@ -291,36 +296,23 @@ let acceptInvite (ctx : IDiscordContext) (invitedPlayer : PlayerData) =
| false ->
let! _ = markInvitedAccepted invitedPlayer.DiscordId |> Async.Ignore
try
- let! inviter = getInviteFromInvitedUser invitedPlayer.DiscordId
- let! player = DbService.tryFindPlayer inviter.Inviter
+ let! invite = getInviteFromInvitedUser invitedPlayer.DiscordId
+ let! player = DbService.tryFindPlayer invite.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
+ builder.WithContent($"{invitedPlayer.Name} was recruited and is now a Degen. <@{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
+ do! Analytics.invitedUserAccepted invite.Code player.DiscordId invitedPlayer.DiscordId player.Name invitedPlayer.Name
| 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) =
-// task {
-// 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
@@ -435,7 +427,8 @@ let handleGimmeWhitelist (ctx : IDiscordContext) =
let recruitBtn = DiscordButtonComponent(ButtonStyle.Danger, $"ShowRecruitmentEmbed", $"Recruit Now") :> DiscordComponent
let builder = DiscordFollowupMessageBuilder().AsEphemeral(true)
- match! tryGrantWhitelist ctx with
+ let! availability = tryGrantWhitelist ctx
+ match availability with
| NotAHacker -> whitelistEmbed.Description <- notAHackerMsg
| NotInGame -> whitelistEmbed.Description <- notInGameMsg
| AlreadyWhitelisted ->
@@ -455,6 +448,13 @@ let handleGimmeWhitelist (ctx : IDiscordContext) =
whitelistEmbed.Description <- canBuyWhitelistMsg
builder.AddEmbed(whitelistEmbed) |> ignore
do! ctx.FollowUp(builder)
+ let availabilityStr =
+ match availability with
+ | NotEnoughGBT _ -> "NotEnoughGBT"
+ | Granted _ -> "Granted"
+ | _ -> string availability
+ let user = ctx.GetDiscordMember()
+ do! Analytics.whiteListButton availabilityStr user.Id user.Username
} :> Task
let buyWhitelistMsg = $"""
@@ -476,10 +476,18 @@ let handleBuyWhitelist (ctx : IDiscordContext) =
let builder = DiscordFollowupMessageBuilder().AsEphemeral(true)
match! tryGrantWhitelist ctx with
- | NotAHacker -> builder.Content <- $"You are somehow not a hacker anymore, what exactly are you doing?"
- | NotInGame -> builder.Content <- $"You somehow have left the game, what exactly are you doing?"
- | AlreadyWhitelisted -> builder.Content <- "🎉 You're already WHITELISTED!"
- | NotEnoughGBT _ -> builder.Content <- $"You somehow do not have enough $GBT, what exactly are you doing?"
+ | NotAHacker ->
+ builder.Content <- $"You are somehow not a hacker anymore, what exactly are you doing?"
+ do! ctx.FollowUp(builder)
+ | NotInGame ->
+ builder.Content <- $"You somehow have left the game, what exactly are you doing?"
+ do! ctx.FollowUp(builder)
+ | AlreadyWhitelisted ->
+ builder.Content <- "🎉 You're already WHITELISTED!"
+ do! ctx.FollowUp(builder)
+ | NotEnoughGBT _ ->
+ builder.Content <- $"You somehow do not have enough $GBT, what exactly are you doing?"
+ do! ctx.FollowUp(builder)
| Granted player ->
let embed = DiscordEmbedBuilder()
embed.Description <- buyWhitelistMsg
@@ -491,6 +499,7 @@ let handleBuyWhitelist (ctx : IDiscordContext) =
do! ctx.GetDiscordMember().GrantRoleAsync(role)
let! _ = DbService.updatePlayerCurrency -WhitelistPrice player
builder.AddEmbed(embed) |> ignore
+ do! ctx.FollowUp(builder)
// Send message to hall of privacy
let builder = DiscordMessageBuilder()
@@ -499,8 +508,9 @@ let handleBuyWhitelist (ctx : IDiscordContext) =
do! channel.SendMessageAsync(builder)
|> Async.AwaitTask
|> Async.Ignore
+ let user = ctx.GetDiscordMember()
+ do! Analytics.whiteListPurchased WhitelistPrice user.Id user.Username
- do! ctx.FollowUp(builder)
} :> Task
let handleCreateInvite (ctx : IDiscordContext) =
@@ -532,6 +542,7 @@ let handleCreateInvite (ctx : IDiscordContext) =
.AsEphemeral(true)
do! ctx.FollowUp(msg)
+ do! Analytics.recruitLinkButton code user.Id user.Username (ctx.GetChannel().Id) (ctx.GetChannel().Name)
} :> Task
let handleButtonEvent (_ : DiscordClient) (event : ComponentInteractionCreateEventArgs) =
@@ -540,7 +551,7 @@ let handleButtonEvent (_ : DiscordClient) (event : ComponentInteractionCreateEv
| id when id.StartsWith("GimmeWhitelist") -> handleGimmeWhitelist eventCtx
| id when id.StartsWith("BuyWhitelist") -> handleBuyWhitelist eventCtx
| id when id.StartsWith("CreateGuildInvite") -> handleCreateInvite eventCtx
- | id when id.StartsWith("ShowRecruitmentEmbed") -> showInviteMessage eventCtx
+ | id when id.StartsWith("ShowRecruitmentEmbed") -> showInviteMessage eventCtx "RecruitButton"
| _ ->
task {
let builder = DiscordInteractionResponseBuilder()
@@ -561,7 +572,7 @@ type Inviter() =
[]
member this.CreateInvite (ctx : InteractionContext) =
- showInviteMessage (DiscordInteractionContext ctx)
+ showInviteMessage (DiscordInteractionContext ctx) "RecruitCommand"
[]
member this.ListInvitedPeople (ctx : InteractionContext) =