2023-11-03 23:48:50 +00:00
|
|
|
package handlers
|
|
|
|
|
|
|
|
import (
|
2023-11-09 19:35:39 +00:00
|
|
|
"fmt"
|
2023-11-05 01:58:00 +00:00
|
|
|
"strconv"
|
|
|
|
"strings"
|
2023-11-03 23:48:50 +00:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/ectrc/snow/aid"
|
2023-12-03 20:16:40 +00:00
|
|
|
"github.com/ectrc/snow/fortnite"
|
2023-11-03 23:48:50 +00:00
|
|
|
p "github.com/ectrc/snow/person"
|
|
|
|
|
|
|
|
"github.com/gofiber/fiber/v2"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
2024-01-03 19:51:46 +00:00
|
|
|
clientActions = map[string]func(c *fiber.Ctx, person *p.Person, profile *p.Profile, notifications *[]aid.JSON) error {
|
|
|
|
"QueryProfile": clientQueryProfileAction,
|
|
|
|
"ClientQuestLogin": clientQueryProfileAction,
|
|
|
|
"MarkItemSeen": clientMarkItemSeenAction,
|
|
|
|
"SetItemFavoriteStatusBatch": clientSetItemFavoriteStatusBatchAction,
|
|
|
|
"EquipBattleRoyaleCustomization": clientEquipBattleRoyaleCustomizationAction,
|
|
|
|
"SetBattleRoyaleBanner": clientSetBattleRoyaleBannerAction,
|
|
|
|
"SetCosmeticLockerSlot": clientSetCosmeticLockerSlotAction,
|
|
|
|
"SetCosmeticLockerBanner": clientSetCosmeticLockerBannerAction,
|
|
|
|
"PurchaseCatalogEntry": clientPurchaseCatalogEntryAction,
|
2023-11-03 23:48:50 +00:00
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2024-01-03 19:51:46 +00:00
|
|
|
func PostClientProfileAction(c *fiber.Ctx) error {
|
|
|
|
person := c.Locals("person").(*p.Person)
|
2023-11-03 23:48:50 +00:00
|
|
|
if person == nil {
|
|
|
|
return c.Status(404).JSON(aid.ErrorBadRequest("No Account Found"))
|
|
|
|
}
|
|
|
|
|
|
|
|
profile := person.GetProfileFromType(c.Query("profileId"))
|
2023-12-03 20:16:40 +00:00
|
|
|
if profile == nil {
|
|
|
|
return c.Status(404).JSON(aid.ErrorBadRequest("No Profile Found"))
|
|
|
|
}
|
|
|
|
defer profile.ClearProfileChanges()
|
|
|
|
|
|
|
|
profileSnapshots := map[string]*p.ProfileSnapshot{
|
|
|
|
"athena": nil,
|
|
|
|
"common_core": nil,
|
|
|
|
"common_public": nil,
|
|
|
|
}
|
|
|
|
|
|
|
|
for key := range profileSnapshots {
|
|
|
|
profileSnapshots[key] = person.GetProfileFromType(key).Snapshot()
|
|
|
|
}
|
|
|
|
|
|
|
|
notifications := []aid.JSON{}
|
|
|
|
|
2024-01-03 19:51:46 +00:00
|
|
|
action, ok := clientActions[c.Params("action")];
|
2023-12-03 20:16:40 +00:00
|
|
|
if ok && profile != nil {
|
|
|
|
if err := action(c, person, profile, ¬ifications); err != nil {
|
2023-11-09 18:53:19 +00:00
|
|
|
return c.Status(400).JSON(aid.ErrorBadRequest(err.Error()))
|
2023-11-03 23:48:50 +00:00
|
|
|
}
|
2023-12-03 20:16:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for key, profileSnapshot := range profileSnapshots {
|
|
|
|
profile := person.GetProfileFromType(key)
|
|
|
|
if profile == nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if profileSnapshot == nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
profile.Diff(profileSnapshot)
|
2023-11-03 23:48:50 +00:00
|
|
|
}
|
2023-11-05 01:58:00 +00:00
|
|
|
|
|
|
|
revision, _ := strconv.Atoi(c.Query("rvn"))
|
2023-12-03 20:16:40 +00:00
|
|
|
if revision == -1 {
|
2023-11-05 01:58:00 +00:00
|
|
|
revision = profile.Revision
|
|
|
|
}
|
|
|
|
revision++
|
2023-12-03 20:16:40 +00:00
|
|
|
profile.Revision = revision
|
|
|
|
profile.Save()
|
|
|
|
delete(profileSnapshots, profile.Type)
|
|
|
|
|
|
|
|
multiUpdate := []aid.JSON{}
|
|
|
|
for key := range profileSnapshots {
|
|
|
|
profile := person.GetProfileFromType(key)
|
|
|
|
if profile == nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
profile.Revision++
|
|
|
|
|
|
|
|
if len(profile.Changes) == 0 {
|
|
|
|
continue
|
|
|
|
}
|
2023-11-03 23:48:50 +00:00
|
|
|
|
2023-12-03 20:16:40 +00:00
|
|
|
multiUpdate = append(multiUpdate, aid.JSON{
|
|
|
|
"profileId": profile.Type,
|
|
|
|
"profileRevision": profile.Revision,
|
|
|
|
"profileCommandRevision": profile.Revision,
|
|
|
|
"profileChangesBaseRevision": profile.Revision - 1,
|
|
|
|
"profileChanges": profile.Changes,
|
|
|
|
})
|
|
|
|
|
|
|
|
profile.ClearProfileChanges()
|
|
|
|
profile.Save()
|
2023-11-20 21:20:26 +00:00
|
|
|
}
|
|
|
|
|
2023-11-03 23:48:50 +00:00
|
|
|
return c.Status(200).JSON(aid.JSON{
|
2023-11-20 21:20:26 +00:00
|
|
|
"profileId": c.Query("profileId"),
|
2024-01-03 19:51:46 +00:00
|
|
|
"profileRevision": profile.Revision,
|
|
|
|
"profileCommandRevision": profile.Revision,
|
|
|
|
"profileChangesBaseRevision": profile.Revision - 1,
|
2023-12-03 20:16:40 +00:00
|
|
|
"profileChanges": profile.Changes,
|
|
|
|
"multiUpdate": multiUpdate,
|
|
|
|
"notifications": notifications,
|
2023-11-03 23:48:50 +00:00
|
|
|
"responseVersion": 1,
|
|
|
|
"serverTime": time.Now().Format("2006-01-02T15:04:05.999Z"),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-01-03 19:51:46 +00:00
|
|
|
func clientQueryProfileAction(c *fiber.Ctx, person *p.Person, profile *p.Profile, notifications *[]aid.JSON) error {
|
2023-11-03 23:48:50 +00:00
|
|
|
profile.CreateFullProfileUpdateChange()
|
2023-11-05 01:58:00 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-01-03 19:51:46 +00:00
|
|
|
func clientMarkItemSeenAction(c *fiber.Ctx, person *p.Person, profile *p.Profile, notifications *[]aid.JSON) error {
|
2023-11-05 01:58:00 +00:00
|
|
|
var body struct {
|
|
|
|
ItemIds []string `json:"itemIds"`
|
|
|
|
}
|
|
|
|
|
|
|
|
err := c.BodyParser(&body)
|
|
|
|
if err != nil {
|
2023-11-09 19:35:39 +00:00
|
|
|
return fmt.Errorf("invalid Body")
|
2023-11-05 01:58:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, itemId := range body.ItemIds {
|
|
|
|
item := profile.Items.GetItem(itemId)
|
|
|
|
if item == nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
item.HasSeen = true
|
2023-11-05 02:43:21 +00:00
|
|
|
go item.Save()
|
2023-11-05 01:58:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-01-03 19:51:46 +00:00
|
|
|
func clientEquipBattleRoyaleCustomizationAction(c *fiber.Ctx, person *p.Person, profile *p.Profile, notifications *[]aid.JSON) error {
|
2023-11-05 01:58:00 +00:00
|
|
|
var body struct {
|
2023-11-09 18:53:19 +00:00
|
|
|
SlotName string `json:"slotName" binding:"required"`
|
2023-11-05 01:58:00 +00:00
|
|
|
ItemToSlot string `json:"itemToSlot"`
|
|
|
|
IndexWithinSlot int `json:"indexWithinSlot"`
|
|
|
|
}
|
|
|
|
|
|
|
|
err := c.BodyParser(&body)
|
|
|
|
if err != nil {
|
2023-11-09 19:35:39 +00:00
|
|
|
return fmt.Errorf("invalid Body")
|
2023-11-05 01:58:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
item := profile.Items.GetItem(body.ItemToSlot)
|
|
|
|
if item == nil {
|
2023-11-09 19:35:39 +00:00
|
|
|
if body.ItemToSlot != "" && !strings.Contains(strings.ToLower(body.ItemToSlot), "random") {
|
|
|
|
return fmt.Errorf("item not found")
|
|
|
|
}
|
|
|
|
|
|
|
|
item = &p.Item{
|
|
|
|
ID: body.ItemToSlot,
|
|
|
|
}
|
2023-11-05 01:58:00 +00:00
|
|
|
}
|
|
|
|
|
2024-01-20 23:06:36 +00:00
|
|
|
attr := profile.Attributes.GetAttributeByKey("favorite_" + strings.ReplaceAll(strings.ToLower(body.SlotName), "wrap", "wraps"))
|
2023-11-05 01:58:00 +00:00
|
|
|
if attr == nil {
|
2023-11-09 19:35:39 +00:00
|
|
|
return fmt.Errorf("attribute not found")
|
2023-11-05 01:58:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
switch body.SlotName {
|
|
|
|
case "Dance":
|
|
|
|
value := aid.JSONParse(attr.ValueJSON)
|
|
|
|
value.([]any)[body.IndexWithinSlot] = item.ID
|
|
|
|
attr.ValueJSON = aid.JSONStringify(value)
|
|
|
|
case "ItemWrap":
|
|
|
|
value := aid.JSONParse(attr.ValueJSON)
|
2024-01-20 23:06:36 +00:00
|
|
|
if body.IndexWithinSlot == -1 {
|
|
|
|
attr.ValueJSON = aid.JSONStringify([]any{item.ID,item.ID,item.ID,item.ID,item.ID,item.ID,item.ID})
|
|
|
|
break
|
|
|
|
}
|
2023-11-05 01:58:00 +00:00
|
|
|
value.([]any)[body.IndexWithinSlot] = item.ID
|
|
|
|
attr.ValueJSON = aid.JSONStringify(value)
|
|
|
|
default:
|
|
|
|
attr.ValueJSON = aid.JSONStringify(item.ID)
|
|
|
|
}
|
|
|
|
|
2023-11-09 19:35:39 +00:00
|
|
|
go attr.Save()
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-01-03 19:51:46 +00:00
|
|
|
func clientSetBattleRoyaleBannerAction(c *fiber.Ctx, person *p.Person, profile *p.Profile, notifications *[]aid.JSON) error {
|
2023-11-09 19:35:39 +00:00
|
|
|
var body struct {
|
|
|
|
HomebaseBannerColorID string `json:"homebaseBannerColorId" binding:"required"`
|
|
|
|
HomebaseBannerIconID string `json:"homebaseBannerIconId" binding:"required"`
|
|
|
|
}
|
|
|
|
|
|
|
|
err := c.BodyParser(&body)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("invalid Body")
|
|
|
|
}
|
|
|
|
|
|
|
|
colorItem := person.CommonCoreProfile.Items.GetItemByTemplateID("HomebaseBannerColor:"+body.HomebaseBannerColorID)
|
|
|
|
if colorItem == nil {
|
|
|
|
return fmt.Errorf("color item not found")
|
|
|
|
}
|
|
|
|
|
|
|
|
iconItem := person.CommonCoreProfile.Items.GetItemByTemplateID("HomebaseBannerIcon:"+body.HomebaseBannerIconID)
|
|
|
|
if iconItem == nil {
|
|
|
|
return fmt.Errorf("icon item not found")
|
|
|
|
}
|
|
|
|
|
|
|
|
iconAttr := profile.Attributes.GetAttributeByKey("banner_icon")
|
|
|
|
if iconAttr == nil {
|
|
|
|
return fmt.Errorf("icon attribute not found")
|
|
|
|
}
|
|
|
|
|
|
|
|
colorAttr := profile.Attributes.GetAttributeByKey("banner_color")
|
|
|
|
if colorAttr == nil {
|
|
|
|
return fmt.Errorf("color attribute not found")
|
|
|
|
}
|
|
|
|
|
|
|
|
iconAttr.ValueJSON = aid.JSONStringify(strings.Split(iconItem.TemplateID, ":")[1])
|
|
|
|
colorAttr.ValueJSON = aid.JSONStringify(strings.Split(colorItem.TemplateID, ":")[1])
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
iconAttr.Save()
|
|
|
|
colorAttr.Save()
|
|
|
|
}()
|
2023-11-03 23:48:50 +00:00
|
|
|
return nil
|
2023-11-20 23:20:42 +00:00
|
|
|
}
|
|
|
|
|
2024-01-03 19:51:46 +00:00
|
|
|
func clientSetItemFavoriteStatusBatchAction(c *fiber.Ctx, person *p.Person, profile *p.Profile, notifications *[]aid.JSON) error {
|
2023-11-20 23:20:42 +00:00
|
|
|
var body struct {
|
|
|
|
ItemIds []string `json:"itemIds" binding:"required"`
|
|
|
|
Favorite []bool `json:"itemFavStatus" binding:"required"`
|
|
|
|
}
|
|
|
|
|
|
|
|
err := c.BodyParser(&body)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("invalid Body")
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, itemId := range body.ItemIds {
|
|
|
|
item := profile.Items.GetItem(itemId)
|
|
|
|
if item == nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
item.Favorite = body.Favorite[i]
|
|
|
|
go item.Save()
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-01-03 19:51:46 +00:00
|
|
|
func clientSetCosmeticLockerSlotAction(c *fiber.Ctx, person *p.Person, profile *p.Profile, notifications *[]aid.JSON) error {
|
2023-11-20 23:20:42 +00:00
|
|
|
var body struct {
|
|
|
|
Category string `json:"category" binding:"required"` // item type e.g. Character
|
|
|
|
ItemToSlot string `json:"itemToSlot" binding:"required"` // template id
|
|
|
|
LockerItem string `json:"lockerItem" binding:"required"` // locker id
|
|
|
|
SlotIndex int `json:"slotIndex" binding:"required"` // index of slot
|
|
|
|
VariantUpdates []aid.JSON `json:"variantUpdates" binding:"required"` // variant updates
|
|
|
|
}
|
|
|
|
|
|
|
|
err := c.BodyParser(&body)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("invalid Body")
|
|
|
|
}
|
|
|
|
|
|
|
|
item := profile.Items.GetItemByTemplateID(body.ItemToSlot)
|
|
|
|
if item == nil {
|
|
|
|
if body.ItemToSlot != "" && !strings.Contains(strings.ToLower(body.ItemToSlot), "random") {
|
|
|
|
return fmt.Errorf("item not found")
|
|
|
|
}
|
|
|
|
|
|
|
|
item = &p.Item{
|
|
|
|
ID: body.ItemToSlot,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
currentLocker := profile.Loadouts.GetLoadout(body.LockerItem)
|
|
|
|
if currentLocker == nil {
|
|
|
|
return fmt.Errorf("current locker not found")
|
|
|
|
}
|
|
|
|
|
|
|
|
switch body.Category {
|
|
|
|
case "Character":
|
|
|
|
currentLocker.CharacterID = item.ID
|
|
|
|
case "Backpack":
|
|
|
|
currentLocker.BackpackID = item.ID
|
|
|
|
case "Pickaxe":
|
|
|
|
currentLocker.PickaxeID = item.ID
|
|
|
|
case "Glider":
|
|
|
|
currentLocker.GliderID = item.ID
|
|
|
|
case "ItemWrap":
|
2023-11-21 16:36:04 +00:00
|
|
|
defer profile.CreateLoadoutChangedChange(currentLocker, "ItemWrapID")
|
2023-11-20 23:20:42 +00:00
|
|
|
if body.SlotIndex == -1 {
|
|
|
|
for i := range currentLocker.ItemWrapID {
|
|
|
|
currentLocker.ItemWrapID[i] = item.ID
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
currentLocker.ItemWrapID[body.SlotIndex] = item.ID
|
|
|
|
case "Dance":
|
2023-11-21 16:36:04 +00:00
|
|
|
defer profile.CreateLoadoutChangedChange(currentLocker, "DanceID")
|
2023-11-20 23:20:42 +00:00
|
|
|
if body.SlotIndex == -1 {
|
|
|
|
for i := range currentLocker.DanceID {
|
|
|
|
currentLocker.DanceID[i] = item.ID
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
currentLocker.DanceID[body.SlotIndex] = item.ID
|
|
|
|
case "SkyDiveContrail":
|
|
|
|
currentLocker.ContrailID = item.ID
|
|
|
|
case "LoadingScreen":
|
|
|
|
currentLocker.LoadingScreenID = item.ID
|
|
|
|
case "MusicPack":
|
|
|
|
currentLocker.MusicPackID = item.ID
|
|
|
|
}
|
|
|
|
|
|
|
|
go currentLocker.Save()
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-01-03 19:51:46 +00:00
|
|
|
func clientSetCosmeticLockerBannerAction(c *fiber.Ctx, person *p.Person, profile *p.Profile, notifications *[]aid.JSON) error {
|
2023-11-20 23:20:42 +00:00
|
|
|
var body struct {
|
|
|
|
LockerItem string `json:"lockerItem" binding:"required"` // locker id
|
|
|
|
BannerColorTemplateName string `json:"bannerColorTemplateName" binding:"required"` // template id
|
|
|
|
BannerIconTemplateName string `json:"bannerIconTemplateName" binding:"required"` // template id
|
|
|
|
}
|
|
|
|
|
|
|
|
err := c.BodyParser(&body)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("invalid Body")
|
|
|
|
}
|
|
|
|
|
|
|
|
color := person.CommonCoreProfile.Items.GetItemByTemplateID("HomebaseBannerColor:" + body.BannerColorTemplateName)
|
|
|
|
if color == nil {
|
|
|
|
return fmt.Errorf("color item not found")
|
|
|
|
}
|
|
|
|
|
|
|
|
icon := profile.Items.GetItemByTemplateID("HomebaseBannerIcon:" + body.BannerIconTemplateName)
|
|
|
|
if icon == nil {
|
|
|
|
icon = &p.Item{
|
|
|
|
ID: body.BannerIconTemplateName,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
currentLocker := profile.Loadouts.GetLoadout(body.LockerItem)
|
|
|
|
if currentLocker == nil {
|
|
|
|
return fmt.Errorf("current locker not found")
|
|
|
|
}
|
|
|
|
|
|
|
|
currentLocker.BannerColorID = color.ID
|
|
|
|
currentLocker.BannerID = icon.ID
|
|
|
|
|
|
|
|
go currentLocker.Save()
|
2023-12-03 20:16:40 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-01-03 19:51:46 +00:00
|
|
|
func clientPurchaseCatalogEntryAction(c *fiber.Ctx, person *p.Person, profile *p.Profile, notifications *[]aid.JSON) error {
|
2023-12-03 20:16:40 +00:00
|
|
|
var body struct{
|
|
|
|
OfferID string `json:"offerId" binding:"required"`
|
|
|
|
PurchaseQuantity int `json:"purchaseQuantity" binding:"required"`
|
|
|
|
ExpectedTotalPrice int `json:"expectedTotalPrice" binding:"required"`
|
|
|
|
}
|
|
|
|
|
|
|
|
err := c.BodyParser(&body)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("invalid Body")
|
|
|
|
}
|
|
|
|
|
|
|
|
offer := fortnite.StaticCatalog.GetOfferById(body.OfferID)
|
|
|
|
if offer == nil {
|
|
|
|
return fmt.Errorf("offer not found")
|
|
|
|
}
|
|
|
|
|
|
|
|
if offer.Price != body.ExpectedTotalPrice {
|
|
|
|
return fmt.Errorf("invalid price")
|
|
|
|
}
|
|
|
|
|
|
|
|
vbucks := profile.Items.GetItemByTemplateID("Currency:MtxPurchased")
|
|
|
|
if vbucks == nil {
|
|
|
|
return fmt.Errorf("vbucks not found")
|
|
|
|
}
|
|
|
|
|
|
|
|
profile0Vbucks := person.Profile0Profile.Items.GetItemByTemplateID("Currency:MtxPurchased")
|
|
|
|
if profile0Vbucks == nil {
|
|
|
|
return fmt.Errorf("profile0vbucks not found")
|
|
|
|
}
|
|
|
|
|
|
|
|
if vbucks.Quantity < body.ExpectedTotalPrice {
|
|
|
|
return fmt.Errorf("not enough vbucks")
|
|
|
|
}
|
|
|
|
|
|
|
|
vbucks.Quantity -= body.ExpectedTotalPrice
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
profile0Vbucks.Quantity = vbucks.Quantity // for season 2 and lower
|
|
|
|
vbucks.Save()
|
|
|
|
profile0Vbucks.Save()
|
|
|
|
}()
|
|
|
|
|
|
|
|
if offer.ProfileType != "athena" {
|
2023-12-06 22:26:42 +00:00
|
|
|
return fmt.Errorf("save the world not implemeted yet")
|
2023-12-03 20:16:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
loot := []aid.JSON{}
|
|
|
|
for i := 0; i < body.PurchaseQuantity; i++ {
|
|
|
|
for _, grant := range offer.Grants {
|
|
|
|
if profile.Items.GetItemByTemplateID(grant) != nil {
|
2023-12-06 22:26:42 +00:00
|
|
|
item := profile.Items.GetItemByTemplateID(grant)
|
|
|
|
item.Quantity++
|
|
|
|
go item.Save()
|
|
|
|
|
2023-12-03 20:16:40 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
item := p.NewItem(grant, 1)
|
|
|
|
person.AthenaProfile.Items.AddItem(item)
|
|
|
|
|
|
|
|
loot = append(loot, aid.JSON{
|
|
|
|
"itemType": item.TemplateID,
|
|
|
|
"itemGuid": item.ID,
|
|
|
|
"quantity": item.Quantity,
|
|
|
|
"itemProfile": offer.ProfileType,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
*notifications = append(*notifications, aid.JSON{
|
|
|
|
"type": "CatalogPurchase",
|
|
|
|
"lootResult": aid.JSON{
|
|
|
|
"items": loot,
|
|
|
|
},
|
|
|
|
"primary": true,
|
|
|
|
})
|
|
|
|
|
2023-11-20 23:20:42 +00:00
|
|
|
return nil
|
2023-11-03 23:48:50 +00:00
|
|
|
}
|