Merge branch 'master' into dev

This commit is contained in:
Joseph Ferano 2022-06-19 21:34:33 +07:00
commit 36e6225201
4 changed files with 87 additions and 49 deletions

View File

@ -105,7 +105,7 @@ let getStoreItems (storeId : string) =
|> Sql.connect |> Sql.connect
|> Sql.parameters [ "sid", Sql.string storeId ] |> Sql.parameters [ "sid", Sql.string storeId ]
|> Sql.query """ |> Sql.query """
SELECT store_id,stock,available,limit_stock,i.id,name,description,icon_url,image_url,category,require_role,require_invites, SELECT store_id,stock,available,limit_stock,i.id,name,description,icon_url,image_url,category,require_role,require_invites,sale_end,
buy_price,sell_price,rate_limit,expiration,drop_chance,can_trade,can_consume,attack_power,defense_power,class_name,max_stack,mods buy_price,sell_price,rate_limit,expiration,drop_chance,can_trade,can_consume,attack_power,defense_power,class_name,max_stack,mods
FROM store_item FROM store_item
JOIN item i on store_item.item_id = i.id JOIN item i on store_item.item_id = i.id
@ -116,6 +116,7 @@ let getStoreItems (storeId : string) =
Stock = reader.int "stock" Stock = reader.int "stock"
LimitStock = reader.bool "limit_stock" LimitStock = reader.bool "limit_stock"
Available = reader.bool "available" Available = reader.bool "available"
SaleEnd = reader.int64OrNone "sale_end"
TotalSold = None TotalSold = None
RequiresInvites = reader.intOrNone "require_invites" RequiresInvites = reader.intOrNone "require_invites"
RequiresRole = reader.stringOrNone "require_role" |> Option.map uint64 RequiresRole = reader.stringOrNone "require_role" |> Option.map uint64
@ -127,7 +128,7 @@ let getAllActiveStoreItems () =
connStr connStr
|> Sql.connect |> Sql.connect
|> Sql.query """ |> Sql.query """
SELECT store_id,stock,available,limit_stock,i.id,name,description,icon_url,image_url,category,require_role,require_invites, SELECT store_id,stock,available,limit_stock,i.id,name,description,icon_url,image_url,category,require_role,require_invites,sale_end,
buy_price,sell_price,rate_limit,expiration,drop_chance,can_trade,can_consume,attack_power,defense_power,class_name,max_stack,mods buy_price,sell_price,rate_limit,expiration,drop_chance,can_trade,can_consume,attack_power,defense_power,class_name,max_stack,mods
FROM store_item FROM store_item
JOIN item i on store_item.item_id = i.id JOIN item i on store_item.item_id = i.id
@ -138,6 +139,7 @@ let getAllActiveStoreItems () =
Stock = reader.int "stock" Stock = reader.int "stock"
LimitStock = reader.bool "limit_stock" LimitStock = reader.bool "limit_stock"
Available = reader.bool "available" Available = reader.bool "available"
SaleEnd = reader.int64OrNone "sale_end"
TotalSold = None TotalSold = None
RequiresInvites = reader.intOrNone "require_invites" RequiresInvites = reader.intOrNone "require_invites"
RequiresRole = reader.stringOrNone "require_role" |> Option.map uint64 RequiresRole = reader.stringOrNone "require_role" |> Option.map uint64
@ -151,7 +153,7 @@ let getRafflesWithPurchases storeId =
|> Sql.parameters [ "sid" , Sql.string storeId ] |> Sql.parameters [ "sid" , Sql.string storeId ]
|> Sql.query """ |> Sql.query """
WITH raffles AS WITH raffles AS
(SELECT store_id,stock,available,limit_stock,i.id AS raffle_id,name,description,icon_url,image_url,category,require_role,require_invites, (SELECT store_id,stock,available,limit_stock,i.id AS raffle_id,name,description,icon_url,image_url,category,require_role,require_invites,sale_end,
buy_price,sell_price,rate_limit,expiration,drop_chance,can_trade,can_consume,attack_power,defense_power,class_name,max_stack,mods buy_price,sell_price,rate_limit,expiration,drop_chance,can_trade,can_consume,attack_power,defense_power,class_name,max_stack,mods
FROM store_item FROM store_item
JOIN item i on store_item.item_id = i.id JOIN item i on store_item.item_id = i.id
@ -166,6 +168,7 @@ FULL JOIN (SELECT item_id, count(*) AS total FROM inventory_item
Stock = reader.int "stock" Stock = reader.int "stock"
LimitStock = reader.bool "limit_stock" LimitStock = reader.bool "limit_stock"
Available = reader.bool "available" Available = reader.bool "available"
SaleEnd = reader.int64OrNone "sale_end"
TotalSold = reader.intOrNone "total" TotalSold = reader.intOrNone "total"
RequiresInvites = reader.intOrNone "require_invites" RequiresInvites = reader.intOrNone "require_invites"
RequiresRole = reader.stringOrNone "require_role" |> Option.map uint64 RequiresRole = reader.stringOrNone "require_role" |> Option.map uint64
@ -178,7 +181,7 @@ let getStoreItemBySymbol (itemSymbol : string) =
|> Sql.connect |> Sql.connect
|> Sql.parameters [ "iid", Sql.string itemSymbol ] |> Sql.parameters [ "iid", Sql.string itemSymbol ]
|> Sql.query """ |> Sql.query """
SELECT store_id,stock,available,limit_stock,i.id,name,description,icon_url,image_url,category,require_role,require_invites, SELECT store_id,stock,available,limit_stock,i.id,name,description,icon_url,image_url,category,require_role,require_invites,sale_end,
buy_price,sell_price,rate_limit,expiration,drop_chance,can_trade,can_consume,attack_power,defense_power,class_name,max_stack,mods buy_price,sell_price,rate_limit,expiration,drop_chance,can_trade,can_consume,attack_power,defense_power,class_name,max_stack,mods
FROM store_item FROM store_item
JOIN item i on store_item.item_id = i.id JOIN item i on store_item.item_id = i.id
@ -189,6 +192,7 @@ let getStoreItemBySymbol (itemSymbol : string) =
Stock = reader.int "stock" Stock = reader.int "stock"
LimitStock = reader.bool "limit_stock" LimitStock = reader.bool "limit_stock"
Available = reader.bool "available" Available = reader.bool "available"
SaleEnd = reader.int64OrNone "sale_end"
TotalSold = None TotalSold = None
RequiresInvites = reader.intOrNone "require_invites" RequiresInvites = reader.intOrNone "require_invites"
RequiresRole = reader.stringOrNone "require_role" |> Option.map uint64 RequiresRole = reader.stringOrNone "require_role" |> Option.map uint64

View File

@ -144,6 +144,7 @@ type StoreItem = {
LimitStock : bool LimitStock : bool
Available : bool Available : bool
TotalSold : int option TotalSold : int option
SaleEnd : int64 option
RequiresRole : uint64 option RequiresRole : uint64 option
RequiresInvites : int option RequiresInvites : int option
Item : Item Item : Item

View File

@ -53,13 +53,20 @@ let checkDoesntExceedStackCap (item : Item) player =
| _ , Some _ -> "You already own this item" |> embedWithError | _ , Some _ -> "You already own this item" |> embedWithError
| _ -> Ok () | _ -> Ok ()
let checkItemSaleStillActive (item : StoreItem) =
match item.SaleEnd with
| Some time ->
let date = DateTimeOffset.FromUnixTimeSeconds(time).DateTime.ToUniversalTime()
if DateTime.UtcNow < date then Ok () else $"Sale for {item.Item.Name} has already ended!" |> embedWithError
| None -> Ok ()
let checkSoldItemAlready (item : Item) player = let checkSoldItemAlready (item : Item) player =
if player.Inventory |> List.exists (fun i -> item.Id = i.Id) if player.Inventory |> List.exists (fun i -> item.Id = i.Id)
then Ok () then Ok ()
else $"{item.Name} not found in your inventory! Looks like you sold it already." else $"{item.Name} not found in your inventory! Looks like you sold it already."
|> embedWithError |> embedWithError
let checkHasItemsInArsenal itemType items player = let checkHasItemsInArsenal itemType items =
if List.isEmpty items |> not if List.isEmpty items |> not
then Ok () then Ok ()
else $"You currently have no {itemType} in your arsenal to sell!" else $"You currently have no {itemType} in your arsenal to sell!"
@ -125,6 +132,13 @@ let getItemEmbeds owned (items : StoreItem list) =
| _ -> ()) | _ -> ())
// if item.Item.Type = ItemType.Whitelist then // if item.Item.Type = ItemType.Whitelist then
// embed.AddField("Mint Allowance", (if item.Item.Id = "WHITEOG" then 2 else 1) |> string, true) |> ignore // embed.AddField("Mint Allowance", (if item.Item.Id = "WHITEOG" then 2 else 1) |> string, true) |> ignore
item.SaleEnd |> Option.iter (fun time ->
let date = DateTimeOffset.FromUnixTimeSeconds(time).DateTime.ToUniversalTime()
if DateTime.UtcNow < date then
embed.AddField("⏰ Closes", $"<t:{time}:R>", true) |> ignore
else
embed.AddField("🚫 Closed", $"<t:{time}:R>", true) |> ignore)
item.TotalSold |> Option.iter (fun total -> embed.AddField("Total Sold", string total, true) |> ignore) item.TotalSold |> Option.iter (fun total -> embed.AddField("Total Sold", string total, true) |> ignore)
embed.Color <- WeaponClass.getClassEmbedColor item.Item embed.Color <- WeaponClass.getClassEmbedColor item.Item
embed.Title <- titleText embed.Title <- titleText
@ -142,12 +156,19 @@ let getBuyItemsEmbed storeId player (storeInventory : StoreItem list) =
storeInventory storeInventory
|> List.map (fun item -> |> List.map (fun item ->
let owned = player.Inventory |> List.exists (fun i -> i.Id = item.Item.Id) let owned = player.Inventory |> List.exists (fun i -> i.Id = item.Item.Id)
let saleStillOngoing =
match item.SaleEnd with
| Some time ->
let date = DateTimeOffset.FromUnixTimeSeconds(time).DateTime.ToUniversalTime()
DateTime.UtcNow < date
| None -> true
let inStock = item.Available && (item.Stock > 0 || item.LimitStock = false) let inStock = item.Available && (item.Stock > 0 || item.LimitStock = false)
match owned , inStock with match owned , inStock , saleStillOngoing with
| _ , false -> | _ , false , _ ->
let msg = if item.Available then "Out of Stock" else "Unavailable" let msg = if item.Available then "Out of Stock" else "Unavailable"
DiscordButtonComponent(WeaponClass.getClassButtonColor item.Item, $"Buy-{item.Item.Id}-{storeId}", $"{item.Item.Name} ({msg})", true) DiscordButtonComponent(WeaponClass.getClassButtonColor item.Item, $"Buy-{item.Item.Id}-{storeId}", $"{item.Item.Name} ({msg})", true)
| false , true -> DiscordButtonComponent(WeaponClass.getClassButtonColor item.Item, $"Buy-{item.Item.Id}-{storeId}", $"Buy {item.Item.Name}") | false , true , true -> DiscordButtonComponent(WeaponClass.getClassButtonColor item.Item, $"Buy-{item.Item.Id}-{storeId}", $"Buy {item.Item.Name}")
| _ , _ , false -> DiscordButtonComponent(WeaponClass.getClassButtonColor item.Item, $"Buy-{item.Item.Id}-{storeId}", $"Closed {item.Item.Name}", true)
| _ -> | _ ->
match checkDoesntExceedStackCap item.Item player with match checkDoesntExceedStackCap item.Item player with
| Ok _ -> DiscordButtonComponent(WeaponClass.getClassButtonColor item.Item, $"Buy-{item.Item.Id}-{storeId}", $"Buy {item.Item.Name}") | Ok _ -> DiscordButtonComponent(WeaponClass.getClassButtonColor item.Item, $"Buy-{item.Item.Id}-{storeId}", $"Buy {item.Item.Name}")
@ -170,7 +191,7 @@ let purchaseItemEmbed quantity (item : Item) =
match item.Type with match item.Type with
| ItemType.Jpeg -> | ItemType.Jpeg ->
let itemName = item.Name.Replace("🎟️", "") let itemName = item.Name.Replace("🎟️", "")
embed.Description <- $"Congratulations! You are in the draw for the {itemName}.\n\nThe winner will be announced soon in <#{GuildEnvironment.channelGiveaway}>" embed.Description <- $"Congratulations! You are in the draw for the {itemName}.\n\nWinners announced in <#{GuildEnvironment.channelGiveaway}>"
embed.ImageUrl <- item.ImageUrl embed.ImageUrl <- item.ImageUrl
embed.Thumbnail <- DiscordEmbedBuilder.EmbedThumbnail() embed.Thumbnail <- DiscordEmbedBuilder.EmbedThumbnail()
embed.Thumbnail.Url <- item.IconUrl embed.Thumbnail.Url <- item.IconUrl
@ -261,7 +282,7 @@ let buyForPlayer storeId player (filterBy : ItemType option) (ctx : IDiscordCont
let sell itemType getItems (ctx : IDiscordContext) = let sell itemType getItems (ctx : IDiscordContext) =
executePlayerAction ctx (fun player -> async { executePlayerAction ctx (fun player -> async {
let items = getItems player.Inventory let items = getItems player.Inventory
match checkHasItemsInArsenal itemType items player with match checkHasItemsInArsenal itemType items with
| Ok _ -> let itemStore = getSellEmbed items | Ok _ -> let itemStore = getSellEmbed items
do! ctx.FollowUp(itemStore) |> Async.AwaitTask do! ctx.FollowUp(itemStore) |> Async.AwaitTask
| Error e -> do! ctx.FollowUp e |> Async.AwaitTask | Error e -> do! ctx.FollowUp e |> Async.AwaitTask
@ -335,6 +356,7 @@ let handleBuyItem (dispatch : IDiscordContext -> Task) (ctx : IDiscordContext) i
let storeItem = storeInventory |> List.find (fun si -> si.Item.Id = itemId) let storeItem = storeInventory |> List.find (fun si -> si.Item.Id = itemId)
do! checkHasSufficientFunds storeItem.Item player do! checkHasSufficientFunds storeItem.Item player
do! checkHasStock storeItem do! checkHasStock storeItem
do! checkItemSaleStillActive storeItem
do! checkDoesntExceedStackCap storeItem.Item player do! checkDoesntExceedStackCap storeItem.Item player
do! checkHasRequiredRole storeItem (ctx.GetDiscordMember()) do! checkHasRequiredRole storeItem (ctx.GetDiscordMember())
do! checkHasRequiredInvites storeItem player do! checkHasRequiredInvites storeItem player

View File

@ -68,20 +68,26 @@ let private createInvite inviter code =
|> Sql.executeNonQueryAsync |> Sql.executeNonQueryAsync
|> Async.AwaitTask |> Async.AwaitTask
let private addInvitedUser did code count = let private addInvitedUser did inviterId code =
try connStr
connStr |> Sql.connect
|> Sql.connect |> Sql.parameters [ "@code" , Sql.string code ; "@did" , Sql.string (string did) ; "@iid" , Sql.string (string inviterId) ]
|> Sql.executeTransactionAsync [ |> Sql.query """
""" INSERT INTO invited_user (inviter_id, discord_id, invite_id)
INSERT INTO invited_user (discord_id, invite_id) VALUES (@iid, @did, (SELECT id FROM invite WHERE code = @code))
VALUES (@did, (SELECT id FROM invite WHERE code = @code)); """
""" , [ [ "@code" , Sql.string code ; "@did" , Sql.string (string did) ] ] |> Sql.executeNonQueryAsync
"UPDATE invite SET count = @count WHERE code = @code" , [ [ "count" , Sql.int count ; "code" , Sql.string code ] ] |> Async.AwaitTask
] |> Async.Ignore
|> Async.AwaitTask
|> Async.Ignore let private updateInviteCount code count =
with _ -> async.Zero () connStr
|> Sql.connect
|> Sql.parameters [ "count" , Sql.int count ; "code" , Sql.string code ]
|> Sql.query "UPDATE invite SET count = @count WHERE code = @code"
|> Sql.executeNonQueryAsync
|> Async.AwaitTask
|> Async.Ignore
let private markInvitedAccepted did = let private markInvitedAccepted did =
connStr connStr
@ -129,18 +135,14 @@ let private checkUserAlreadyInvited userId = async {
} }
let checkInviteAccepted (userId : uint64) = async { let checkInviteAccepted (userId : uint64) = async {
try
let! result = let! result =
connStr connStr
|> Sql.connect |> Sql.connect
|> Sql.parameters [ "did" , Sql.string (string userId) ] |> Sql.parameters [ "did" , Sql.string (string userId) ]
|> Sql.query "SELECT accepted FROM invited_user WHERE discord_id = @did" |> Sql.query "SELECT accepted FROM invited_user WHERE discord_id = @did"
|> Sql.executeRowAsync (fun read -> read.bool "accepted") |> Sql.executeAsync (fun read -> read.bool "accepted")
|> Async.AwaitTask |> Async.AwaitTask
return result return List.tryHead result |> Option.defaultValue false
with ex ->
printfn "%s %u" ex.Message userId
return false
} }
let private getInviteAttributions userId = let private getInviteAttributions userId =
@ -279,10 +281,20 @@ let private processNewUser (eventArgs : GuildMemberAddEventArgs) =
for invite in guildInvites do for invite in guildInvites do
let result = cachedInvites.TryFind(invite.Code) let result = cachedInvites.TryFind(invite.Code)
match result with match result with
| Some (_,count) -> | Some (inviterId,count) ->
if invite.Uses > count then if invite.Uses > count then
do! addInvitedUser eventArgs.Member.Id invite.Code invite.Uses |> Async.Ignore do! updateInviteCount invite.Code invite.Uses
do! Analytics.invitedUserEntered invite.Code invite.Inviter.Id eventArgs.Member.Id invite.Inviter.Username eventArgs.Member.Username try
match! checkUserAlreadyInvited eventArgs.Member.Id with
| false ->
do! addInvitedUser eventArgs.Member.Id inviterId invite.Code |> Async.Ignore
match! DbService.tryFindPlayer inviterId with
| Some inviter ->
do! Analytics.invitedUserEntered invite.Code inviter.DiscordId eventArgs.Member.Id inviter.Name eventArgs.Member.Username
| None ->
do! Analytics.invitedUserEntered invite.Code inviterId eventArgs.Member.Id "Unknown" eventArgs.Member.Username
| true -> ()
with ex -> printfn $"Tried to add existing user {eventArgs.Member.Id}:{eventArgs.Member.Username} to invites: {ex.Message}"
| None -> () | None -> ()
} :> Task } :> Task
@ -290,7 +302,7 @@ let acceptInvite (guild : DiscordGuild) (user : DiscordMember) =
task { task {
match! checkInviteAccepted user.Id with match! checkInviteAccepted user.Id with
| false -> | false ->
let! _ = markInvitedAccepted user.Id |> Async.Ignore do! markInvitedAccepted user.Id |> Async.Ignore
try try
let! invite = getInviteFromInvitedUser user.Id let! invite = getInviteFromInvitedUser user.Id
let! player = DbService.tryFindPlayer invite.Inviter let! player = DbService.tryFindPlayer invite.Inviter
@ -310,18 +322,20 @@ let acceptInvite (guild : DiscordGuild) (user : DiscordMember) =
let role3x = guild.Roles.TryGetValue(GuildEnvironment.roleRecruiter3x) |> snd let role3x = guild.Roles.TryGetValue(GuildEnvironment.roleRecruiter3x) |> snd
let role2x = guild.Roles.TryGetValue(GuildEnvironment.roleRecruiter2x) |> snd let role2x = guild.Roles.TryGetValue(GuildEnvironment.roleRecruiter2x) |> snd
let role1x = guild.Roles.TryGetValue(GuildEnvironment.roleRecruiter1x) |> snd let role1x = guild.Roles.TryGetValue(GuildEnvironment.roleRecruiter1x) |> snd
match invite.Count with let! playerMember = guild.GetMemberAsync(invite.Inviter)
| count when count > 10 -> let! totalInvites = getInvitedUserCount player.DiscordId
do! [ user.GrantRoleAsync(role3x) ; user.RevokeRoleAsync(role2x) ; user.RevokeRoleAsync(role1x) ] if totalInvites >= 10 then
do! [ playerMember.GrantRoleAsync(role3x) ; playerMember.RevokeRoleAsync(role2x) ; playerMember.RevokeRoleAsync(role1x) ]
|> List.map Async.AwaitTask |> List.map Async.AwaitTask
|> Async.Parallel |> Async.Parallel
|> Async.Ignore |> Async.Ignore
| count when count > 5 -> elif totalInvites >= 5 then
do! [ user.GrantRoleAsync(role2x) ; user.RevokeRoleAsync(role1x) ] do! [ playerMember.GrantRoleAsync(role2x) ; playerMember.RevokeRoleAsync(role1x) ]
|> List.map Async.AwaitTask |> List.map Async.AwaitTask
|> Async.Parallel |> Async.Parallel
|> Async.Ignore |> Async.Ignore
| _ -> do! user.GrantRoleAsync(role1x) else
do! playerMember.GrantRoleAsync(role1x)
do! Analytics.invitedUserAccepted invite.Code player.DiscordId user.Id player.Name user.Username do! Analytics.invitedUserAccepted invite.Code player.DiscordId user.Id player.Name user.Username
| None -> return () | None -> return ()
with _ -> () with _ -> ()
@ -343,6 +357,9 @@ let sendInitialEmbed (ctx : IDiscordContext) =
**__Bonus__** **__Bonus__**
💰 Earn an extra {InviteRewardAmount} $GBT for every invite! 💰 Earn an extra {InviteRewardAmount} $GBT for every invite!
<:purple_fist:986685279031152650> <@#{GuildEnvironment.roleRecruiter1x}> role if you invite 1 or more Degen
<:red_fist:986685280868249690> <@#{GuildEnvironment.roleRecruiter2x}> role is you invite 5 or more Degen
<:gold_fist:986685276942377052> <@#{GuildEnvironment.roleRecruiter3x}> role is you invite 10 or more Degen
**Every invite increases your chances of winning* **Every invite increases your chances of winning*
""" """
@ -370,7 +387,7 @@ let showWalletStatus (ctx : IDiscordContext) =
try try
match! getWalletAddress player.DiscordId with match! getWalletAddress player.DiscordId with
| Some address -> do! Messaging.sendFollowUpMessage ctx $""" | Some address -> do! Messaging.sendFollowUpMessage ctx $"""
🚀 __Mint Date:__ Mid June 🚀 __Mint Date:__ June 20th
__Status:__ We have successfully received your wallet address: {address}""" __Status:__ We have successfully received your wallet address: {address}"""
| None -> do! Messaging.sendFollowUpMessage ctx "You haven't submitted a wallet yet. Type `/submit`, paste your **Solana Wallet Address**, then press enter" | None -> do! Messaging.sendFollowUpMessage ctx "You haven't submitted a wallet yet. Type `/submit`, paste your **Solana Wallet Address**, then press enter"
with ex -> with ex ->
@ -473,12 +490,7 @@ let handleMessageCreated _ (event : MessageCreateEventArgs) =
do! event.Message.DeleteAsync() do! event.Message.DeleteAsync()
} :> Task } :> Task
let handleGuildMemberAdded _ (eventArgs : GuildMemberAddEventArgs) = let handleGuildMemberAdded _ (eventArgs : GuildMemberAddEventArgs) = processNewUser eventArgs
task {
let! exists = checkUserAlreadyInvited eventArgs.Member.Id
if not exists then
do! processNewUser eventArgs
} :> Task
let submitAddress (address : string) (ctx : IDiscordContext) = let submitAddress (address : string) (ctx : IDiscordContext) =
PlayerInteractions.executePlayerAction ctx (fun player -> async { PlayerInteractions.executePlayerAction ctx (fun player -> async {
@ -504,10 +516,9 @@ let submitAddress (address : string) (ctx : IDiscordContext) =
do! user.GrantRoleAsync(role) |> Async.AwaitTask do! user.GrantRoleAsync(role) |> Async.AwaitTask
let role = ctx.GetGuild().GetRole(GuildEnvironment.roleWhiteOGPending) let role = ctx.GetGuild().GetRole(GuildEnvironment.roleWhiteOGPending)
do! user.RevokeRoleAsync(role) |> Async.AwaitTask do! user.RevokeRoleAsync(role) |> Async.AwaitTask
do! Messaging.sendFollowUpMessage ctx $""" do! Messaging.sendFollowUpMessage ctx $"""
🚀 __Mint Date:__ Mid June 🚀 __Mint Date:__ June 20th
{msg} {address} {msg} {address}
Keep an eye on <#{GuildEnvironment.channelAnnouncements}> for updates.""" Keep an eye on <#{GuildEnvironment.channelAnnouncements}> for updates."""