Add vbucks in store! (not real)

This commit is contained in:
Eccentric 2024-02-19 04:07:40 +00:00
parent 590917adbb
commit 749643be88
10 changed files with 420 additions and 140 deletions

View File

@ -49,6 +49,7 @@ type CS struct {
Password bool Password bool
DisableClientCredentials bool DisableClientCredentials bool
ShopSeed int ShopSeed int
EnableVBucks bool
} }
} }
@ -175,4 +176,5 @@ func LoadConfig(file []byte) {
Config.Fortnite.Password = !(cfg.Section("fortnite").Key("disable_password").MustBool(false)) Config.Fortnite.Password = !(cfg.Section("fortnite").Key("disable_password").MustBool(false))
Config.Fortnite.DisableClientCredentials = cfg.Section("fortnite").Key("disable_client_credentials").MustBool(false) Config.Fortnite.DisableClientCredentials = cfg.Section("fortnite").Key("disable_client_credentials").MustBool(false)
Config.Fortnite.ShopSeed = cfg.Section("fortnite").Key("shop_seed").MustInt(0) Config.Fortnite.ShopSeed = cfg.Section("fortnite").Key("shop_seed").MustInt(0)
Config.Fortnite.EnableVBucks = cfg.Section("fortnite").Key("enable_vbucks").MustBool(false)
} }

View File

@ -31,3 +31,15 @@ func FiberCors() fiber.Handler {
AllowHeaders: "Origin, Content-Type, Accept, Authorization, X-Requested-With", AllowHeaders: "Origin, Content-Type, Accept, Authorization, X-Requested-With",
}) })
} }
// https://github.com/gofiber/fiber/issues/510
func FiberGetQueries(c *fiber.Ctx, queryKeys ...string) map[string][]string {
argsMaps := make(map[string][]string)
for _, keys := range queryKeys {
param := c.Request().URI().QueryArgs().PeekMulti(keys)
for _, value := range param {
argsMaps[keys] = append(argsMaps[keys], string(value))
}
}
return argsMaps
}

View File

@ -80,3 +80,7 @@ disable_client_credentials=false
; each number will generate a different shop for the day ; each number will generate a different shop for the day
; the shop will stay the same for the entire day even after server restarts ; the shop will stay the same for the entire day even after server restarts
shop_seed=0 shop_seed=0
; this will enable vbucks in the store tab
; at this time it is not possible to buy vbucks
; this is only for testing purposes
enable_vbucks=true

View File

@ -13,10 +13,10 @@ import (
) )
var ( var (
External *ExternalDataClient DataClient *dataClient
) )
type ExternalDataClient struct { type dataClient struct {
h *http.Client h *http.Client
FortniteSets map[string]*FortniteSet `json:"sets"` FortniteSets map[string]*FortniteSet `json:"sets"`
FortniteItems map[string]*FortniteItem `json:"items"` FortniteItems map[string]*FortniteItem `json:"items"`
@ -25,10 +25,14 @@ type ExternalDataClient struct {
TypedFortniteItems map[string][]*FortniteItem `json:"-"` TypedFortniteItems map[string][]*FortniteItem `json:"-"`
TypedFortniteItemsWithDisplayAssets map[string][]*FortniteItem `json:"-"` TypedFortniteItemsWithDisplayAssets map[string][]*FortniteItem `json:"-"`
SnowVariantTokens map[string]*FortniteVariantToken `json:"variants"` SnowVariantTokens map[string]*FortniteVariantToken `json:"variants"`
StorefrontCosmeticOfferPriceLookup map[string]map[string]int `json:"-"`
StorefrontDailyItemCountLookup []struct{Season int;Items int} `json:"-"`
StorefrontWeeklySetCountLookup []struct{Season int;Sets int} `json:"-"`
StorefrontCurrencyOfferPriceLookup map[string]map[int]int `json:"-"`
} }
func NewExternalDataClient() *ExternalDataClient { func NewDataClient() *dataClient {
return &ExternalDataClient{ return &dataClient{
h: &http.Client{}, h: &http.Client{},
FortniteItems: make(map[string]*FortniteItem), FortniteItems: make(map[string]*FortniteItem),
FortniteSets: make(map[string]*FortniteSet), FortniteSets: make(map[string]*FortniteSet),
@ -37,10 +41,76 @@ func NewExternalDataClient() *ExternalDataClient {
TypedFortniteItems: make(map[string][]*FortniteItem), TypedFortniteItems: make(map[string][]*FortniteItem),
TypedFortniteItemsWithDisplayAssets: make(map[string][]*FortniteItem), TypedFortniteItemsWithDisplayAssets: make(map[string][]*FortniteItem),
SnowVariantTokens: make(map[string]*FortniteVariantToken), SnowVariantTokens: make(map[string]*FortniteVariantToken),
StorefrontDailyItemCountLookup: []struct{Season int;Items int}{
{2, 4},
{4, 6},
{13, 10},
},
StorefrontWeeklySetCountLookup: []struct{Season int;Sets int}{
{2, 2},
{4, 3},
{13, 5},
},
StorefrontCosmeticOfferPriceLookup: map[string]map[string]int{
"EFortRarity::Legendary": {
"AthenaCharacter": 2000,
"AthenaBackpack": 1500,
"AthenaPickaxe": 1500,
"AthenaGlider": 1800,
"AthenaDance": 500,
"AthenaItemWrap": 800,
},
"EFortRarity::Epic": {
"AthenaCharacter": 1500,
"AthenaBackpack": 1200,
"AthenaPickaxe": 1200,
"AthenaGlider": 1500,
"AthenaDance": 800,
"AthenaItemWrap": 800,
},
"EFortRarity::Rare": {
"AthenaCharacter": 1200,
"AthenaBackpack": 800,
"AthenaPickaxe": 800,
"AthenaGlider": 800,
"AthenaDance": 500,
"AthenaItemWrap": 600,
},
"EFortRarity::Uncommon": {
"AthenaCharacter": 800,
"AthenaBackpack": 200,
"AthenaPickaxe": 500,
"AthenaGlider": 500,
"AthenaDance": 200,
"AthenaItemWrap": 300,
},
"EFortRarity::Common": {
"AthenaCharacter": 500,
"AthenaBackpack": 200,
"AthenaPickaxe": 500,
"AthenaGlider": 500,
"AthenaDance": 200,
"AthenaItemWrap": 300,
},
},
StorefrontCurrencyOfferPriceLookup: map[string]map[int]int{
"USD": {
1000: 999,
2800: 2499,
5000: 3999,
13500: 9999,
},
"GBP": {
1000: 799,
2800: 1999,
5000: 3499,
13500: 7999,
},
},
} }
} }
func (c *ExternalDataClient) LoadExternalData() { func (c *dataClient) LoadExternalData() {
req, err := http.NewRequest("GET", "https://fortnite-api.com/v2/cosmetics/br", nil) req, err := http.NewRequest("GET", "https://fortnite-api.com/v2/cosmetics/br", nil)
if err != nil { if err != nil {
return return
@ -113,7 +183,7 @@ func (c *ExternalDataClient) LoadExternalData() {
} }
} }
func (c *ExternalDataClient) LoadItem(item *FortniteItem) { func (c *dataClient) LoadItem(item *FortniteItem) {
if item.Introduction.BackendValue > aid.Config.Fortnite.Season || item.Introduction.BackendValue == 0 { if item.Introduction.BackendValue > aid.Config.Fortnite.Season || item.Introduction.BackendValue == 0 {
return return
} }
@ -151,7 +221,7 @@ func (c *ExternalDataClient) LoadItem(item *FortniteItem) {
c.FortniteItemsWithFeaturedImage = append(c.FortniteItemsWithFeaturedImage, item) c.FortniteItemsWithFeaturedImage = append(c.FortniteItemsWithFeaturedImage, item)
} }
func (c *ExternalDataClient) AddBackpackToItem(backpack *FortniteItem) { func (c *dataClient) AddBackpackToItem(backpack *FortniteItem) {
if backpack.ItemPreviewHeroPath == "" { if backpack.ItemPreviewHeroPath == "" {
return return
} }
@ -165,7 +235,7 @@ func (c *ExternalDataClient) AddBackpackToItem(backpack *FortniteItem) {
character.Backpack = backpack character.Backpack = backpack
} }
func (c *ExternalDataClient) AddDisplayAssetToItem(displayAsset string) { func (c *dataClient) AddDisplayAssetToItem(displayAsset string) {
split := strings.Split(displayAsset, "_")[1:] split := strings.Split(displayAsset, "_")[1:]
found := c.FortniteItems[strings.Join(split[:], "_")] found := c.FortniteItems[strings.Join(split[:], "_")]
@ -185,7 +255,7 @@ func (c *ExternalDataClient) AddDisplayAssetToItem(displayAsset string) {
c.TypedFortniteItemsWithDisplayAssets[found.Type.BackendValue] = append(c.TypedFortniteItemsWithDisplayAssets[found.Type.BackendValue], found) c.TypedFortniteItemsWithDisplayAssets[found.Type.BackendValue] = append(c.TypedFortniteItemsWithDisplayAssets[found.Type.BackendValue], found)
} }
func (c *ExternalDataClient) AddNumericStylesToItem(item *FortniteItem) { func (c *dataClient) AddNumericStylesToItem(item *FortniteItem) {
ownedStyles := []FortniteVariantChannel{} ownedStyles := []FortniteVariantChannel{}
for i := 0; i < 100; i++ { for i := 0; i < 100; i++ {
ownedStyles = append(ownedStyles, FortniteVariantChannel{ ownedStyles = append(ownedStyles, FortniteVariantChannel{
@ -200,16 +270,46 @@ func (c *ExternalDataClient) AddNumericStylesToItem(item *FortniteItem) {
}) })
} }
func PreloadCosmetics() error { func (c *dataClient) GetStorefrontDailyItemCount(season int) int {
External = NewExternalDataClient() currentValue := 4
External.LoadExternalData() for _, item := range c.StorefrontDailyItemCountLookup {
if item.Season > season {
continue
}
currentValue = item.Items
}
return currentValue
}
aid.Print("(snow) " + fmt.Sprint(len(External.FortniteItems)) + " cosmetics loaded from fortnite-api.com") func (c *dataClient) GetStorefrontWeeklySetCount(season int) int {
currentValue := 2
for _, item := range c.StorefrontWeeklySetCountLookup {
if item.Season > season {
continue
}
currentValue = item.Sets
}
return currentValue
}
func (c *dataClient) GetStorefrontCosmeticOfferPrice(rarity string, type_ string) int {
return c.StorefrontCosmeticOfferPriceLookup[rarity][type_]
}
func (c *dataClient) GetStorefrontCurrencyOfferPrice(currency string, amount int) int {
return c.StorefrontCurrencyOfferPriceLookup[currency][amount]
}
func PreloadCosmetics() error {
DataClient = NewDataClient()
DataClient.LoadExternalData()
aid.Print("(snow) " + fmt.Sprint(len(DataClient.FortniteItems)) + " cosmetics loaded from fortnite-api.com")
return nil return nil
} }
func GetItemByShallowID(shallowID string) *FortniteItem { func GetItemByShallowID(shallowID string) *FortniteItem {
for _, item := range External.TypedFortniteItems["AthenaCharacter"] { for _, item := range DataClient.TypedFortniteItems["AthenaCharacter"] {
if strings.Contains(item.ID, shallowID) { if strings.Contains(item.ID, shallowID) {
return item return item
} }
@ -219,7 +319,7 @@ func GetItemByShallowID(shallowID string) *FortniteItem {
} }
func GetRandomItemWithDisplayAsset() *FortniteItem { func GetRandomItemWithDisplayAsset() *FortniteItem {
items := External.FortniteItemsWithDisplayAssets items := DataClient.FortniteItemsWithDisplayAssets
if len(items) == 0 { if len(items) == 0 {
return nil return nil
} }
@ -239,7 +339,7 @@ func GetRandomItemWithDisplayAsset() *FortniteItem {
func GetRandomItemWithDisplayAssetOfNotType(notType string) *FortniteItem { func GetRandomItemWithDisplayAssetOfNotType(notType string) *FortniteItem {
flat := []FortniteItem{} flat := []FortniteItem{}
for t, items := range External.TypedFortniteItemsWithDisplayAssets { for t, items := range DataClient.TypedFortniteItemsWithDisplayAssets {
if t == notType { if t == notType {
continue continue
} }
@ -258,7 +358,10 @@ func GetRandomItemWithDisplayAssetOfNotType(notType string) *FortniteItem {
func GetRandomSet() *FortniteSet { func GetRandomSet() *FortniteSet {
sets := []FortniteSet{} sets := []FortniteSet{}
for _, set := range External.FortniteSets { for _, set := range DataClient.FortniteSets {
if set.BackendName == "" {
continue
}
sets = append(sets, *set) sets = append(sets, *set)
} }

View File

@ -31,7 +31,7 @@ func NewFortnitePerson(displayName string, everything bool) *p.Person {
func GiveEverything(person *p.Person) { func GiveEverything(person *p.Person) {
items := make([]storage.DB_Item, 0) items := make([]storage.DB_Item, 0)
for _, item := range External.FortniteItems { for _, item := range DataClient.FortniteItems {
if strings.Contains(strings.ToLower(item.ID), "random") { if strings.Contains(strings.ToLower(item.ID), "random") {
continue continue
} }

View File

@ -1,110 +1,177 @@
package fortnite package fortnite
import ( import (
"fmt"
"math/rand" "math/rand"
"regexp" "regexp"
"time"
"github.com/ectrc/snow/aid" "github.com/ectrc/snow/aid"
"github.com/google/uuid" "github.com/google/uuid"
) )
var (
priceLookup = map[string]map[string]int{
"EFortRarity::Legendary": {
"AthenaCharacter": 2000,
"AthenaBackpack": 1500,
"AthenaPickaxe": 1500,
"AthenaGlider": 1800,
"AthenaDance": 500,
"AthenaItemWrap": 800,
},
"EFortRarity::Epic": {
"AthenaCharacter": 1500,
"AthenaBackpack": 1200,
"AthenaPickaxe": 1200,
"AthenaGlider": 1500,
"AthenaDance": 800,
"AthenaItemWrap": 800,
},
"EFortRarity::Rare": {
"AthenaCharacter": 1200,
"AthenaBackpack": 800,
"AthenaPickaxe": 800,
"AthenaGlider": 800,
"AthenaDance": 500,
"AthenaItemWrap": 600,
},
"EFortRarity::Uncommon": {
"AthenaCharacter": 800,
"AthenaBackpack": 200,
"AthenaPickaxe": 500,
"AthenaGlider": 500,
"AthenaDance": 200,
"AthenaItemWrap": 300,
},
"EFortRarity::Common": {
"AthenaCharacter": 500,
"AthenaBackpack": 200,
"AthenaPickaxe": 500,
"AthenaGlider": 500,
"AthenaDance": 200,
"AthenaItemWrap": 300,
},
}
dailyItemLookup = []struct { type FortniteCatalogCurrencyOffer struct {
Season int ID string
Items int DevName string
}{ Price struct {
{2, 4}, OriginalOffer int
{4, 6}, ExtraBonus int
{13, 10},
} }
Meta struct {
weeklySetLookup = []struct { IconSize string
Season int CurrencyAnalyticsName string
Sets int BannerOverride string
}{
{2, 2},
{4, 3},
{11, 4},
{13, 3},
} }
) Title string
Description string
func price(rarity, type_ string) int { LongDescription string
return priceLookup[rarity][type_] Priority int
} }
func dailyItems(season int) int { func NewFortniteCatalogCurrencyOffer(original, bonus int) *FortniteCatalogCurrencyOffer {
currentValue := 4 return &FortniteCatalogCurrencyOffer{
ID: "v2:/"+aid.RandomString(32),
for _, item := range dailyItemLookup { Price: struct {
if item.Season > season { OriginalOffer int
continue ExtraBonus int
} }{original, bonus},
currentValue = item.Items
} }
return currentValue
} }
func weeklySets(season int) int { func (f *FortniteCatalogCurrencyOffer) GenerateFortniteCatalogCurrencyOfferResponse() aid.JSON {
currentValue := 2 return aid.JSON{
"offerId": f.ID,
for _, set := range weeklySetLookup { "devName": f.DevName,
if set.Season > season { "offerType": "StaticPrice",
continue "prices": []aid.JSON{{
} "currencyType": "RealMoney",
"currencySubType": "",
currentValue = set.Sets "regularPrice": 0,
"dynamicRegularPrice": -1,
"finalPrice": 0,
"saleExpiration": "9999-12-31T23:59:59.999Z",
"basePrice": 0,
}},
"categories": []string{},
"dailyLimit": -1,
"weeklyLimit": -1,
"monthlyLimit": -1,
"refundable": false,
"appStoreId": []string{
"",
"app-" + f.ID,
},
"requirements": []aid.JSON{},
"metaInfo": []aid.JSON{
{
"key": "MtxQuantity",
"value": f.Price.OriginalOffer + f.Price.ExtraBonus,
},
{
"key": "MtxBonus",
"value": f.Price.ExtraBonus,
},
{
"key": "IconSize",
"value": f.Meta.IconSize,
},
{
"key": "BannerOverride",
"value": f.Meta.BannerOverride,
},
{
"Key": "CurrencyAnalyticsName",
"Value": f.Meta.CurrencyAnalyticsName,
},
},
"meta": aid.JSON{
"IconSize": f.Meta.IconSize,
"CurrencyAnalyticsName": f.Meta.CurrencyAnalyticsName,
"BannerOverride": f.Meta.BannerOverride,
"MtxQuantity": f.Price.OriginalOffer + f.Price.ExtraBonus,
"MtxBonus": f.Price.ExtraBonus,
},
"catalogGroup": "",
"catalogGroupPriority": 0,
"sortPriority": f.Priority,
"bannerOverride": f.Meta.BannerOverride,
"title": f.Title,
"shortDescription": "",
"description": f.Description,
"displayAssetPath": "/Game/Catalog/DisplayAssets/DA_" + f.Meta.CurrencyAnalyticsName + ".DA_" + f.Meta.CurrencyAnalyticsName,
"itemGrants": []aid.JSON{},
} }
return currentValue
} }
type FortniteCatalogSectionOffer struct { func (f *FortniteCatalogCurrencyOffer) GenerateFortniteCatalogBulkOfferResponse() aid.JSON{
return aid.JSON{
"id": "app-" + f.ID,
"title": f.Title,
"description": f.Description,
"longDescription": f.LongDescription,
"technicalDetails": "",
"keyImages": []aid.JSON{},
"categories": []aid.JSON{},
"namespace": "fn",
"status": "ACTIVE",
"creationDate": time.Now().Format(time.RFC3339),
"lastModifiedDate": time.Now().Format(time.RFC3339),
"customAttributes": aid.JSON{},
"internalName": f.Title,
"recurrence": "ONCE",
"items": []aid.JSON{},
"price": DataClient.GetStorefrontCurrencyOfferPrice("GBP", f.Price.OriginalOffer + f.Price.ExtraBonus),
"currentPrice": DataClient.GetStorefrontCurrencyOfferPrice("GBP", f.Price.OriginalOffer + f.Price.ExtraBonus),
"currencyCode": "GBP",
"basePrice": DataClient.GetStorefrontCurrencyOfferPrice("USD", f.Price.OriginalOffer + f.Price.ExtraBonus),
"basePriceCurrencyCode": "USD",
"recurringPrice": 0,
"freeDays": 0,
"maxBillingCycles": 0,
"seller": aid.JSON{},
"viewableDate": time.Now().Format(time.RFC3339),
"effectiveDate": time.Now().Format(time.RFC3339),
"expiryDate": "9999-12-31T23:59:59.999Z",
"vatIncluded": true,
"isCodeRedemptionOnly": false,
"isFeatured": false,
"taxSkuId": "FN_Currency",
"merchantGroup": "FN_MKT",
"priceTier": fmt.Sprintf("%d", DataClient.GetStorefrontCurrencyOfferPrice("USD", f.Price.OriginalOffer + f.Price.ExtraBonus)),
"urlSlug": "fortnite--" + f.Title,
"roleNamesToGrant": []aid.JSON{},
"tags": []aid.JSON{},
"purchaseLimit": -1,
"ignoreOrder": false,
"fulfillToGroup": false,
"fraudItemType": "V-Bucks",
"shareRevenue": false,
"offerType": "OTHERS",
"unsearchable": false,
"releaseDate": time.Now().Format(time.RFC3339),
"releaseOffer": "",
"title4Sort": f.Title,
"countriesBlacklist": []string{},
"selfRefundable": false,
"refundType": "NON_REFUNDABLE",
"pcReleaseDate": time.Now().Format(time.RFC3339),
"priceCalculationMode": "FIXED",
"assembleMode": "SINGLE",
"publisherDisplayName": "Epic Games",
"developerDisplayName": "Epic Games",
"visibilityType": "IS_LISTED",
"currencyDecimals": 2,
"allowPurchaseForPartialOwned": true,
"shareRevenueWithUnderageAffiliates": false,
"platformWhitelist": []string{},
"platformBlacklist": []string{},
"partialItemPrerequisiteCheck": false,
"upgradeMode": "UPGRADED_WITH_PRICE_FULL",
}
}
type FortniteCatalogCosmeticOffer struct {
ID string ID string
Grants []*FortniteItem Grants []*FortniteItem
TotalPrice int TotalPrice int
@ -128,11 +195,11 @@ type FortniteCatalogSectionOffer struct {
} }
} }
func NewFortniteCatalogSectionOffer() *FortniteCatalogSectionOffer { func NewFortniteCatalogSectionOffer() *FortniteCatalogCosmeticOffer {
return &FortniteCatalogSectionOffer{} return &FortniteCatalogCosmeticOffer{}
} }
func (f *FortniteCatalogSectionOffer) GenerateID() { func (f *FortniteCatalogCosmeticOffer) GenerateID() {
for _, item := range f.Grants { for _, item := range f.Grants {
f.ID += item.Type.BackendValue + ":" + item.ID + "," f.ID += item.Type.BackendValue + ":" + item.ID + ","
} }
@ -140,18 +207,18 @@ func (f *FortniteCatalogSectionOffer) GenerateID() {
f.ID = "v2:/" + aid.Hash([]byte(f.ID)) f.ID = "v2:/" + aid.Hash([]byte(f.ID))
} }
func (f *FortniteCatalogSectionOffer) GenerateTotalPrice() { func (f *FortniteCatalogCosmeticOffer) GenerateTotalPrice() {
if !f.BundleInfo.IsBundle { if !f.BundleInfo.IsBundle {
f.TotalPrice = price(f.Grants[0].Rarity.BackendValue, f.Grants[0].Type.BackendValue) f.TotalPrice = DataClient.GetStorefrontCosmeticOfferPrice(f.Grants[0].Rarity.BackendValue, f.Grants[0].Type.BackendValue)
return return
} }
for _, item := range f.Grants { for _, item := range f.Grants {
f.TotalPrice += price(item.Rarity.BackendValue, item.Rarity.BackendValue) f.TotalPrice += DataClient.GetStorefrontCosmeticOfferPrice(item.Rarity.BackendValue, item.Rarity.BackendValue)
} }
} }
func (f *FortniteCatalogSectionOffer) GenerateFortniteCatalogSectionOffer() aid.JSON { func (f *FortniteCatalogCosmeticOffer) GenerateFortniteCatalogCosmeticOfferResponse() aid.JSON {
f.GenerateTotalPrice() f.GenerateTotalPrice()
itemGrantResponse := []aid.JSON{} itemGrantResponse := []aid.JSON{}
@ -234,7 +301,7 @@ func (f *FortniteCatalogSectionOffer) GenerateFortniteCatalogSectionOffer() aid.
type FortniteCatalogSection struct { type FortniteCatalogSection struct {
Name string Name string
Offers []*FortniteCatalogSectionOffer Offers []*FortniteCatalogCosmeticOffer
} }
func NewFortniteCatalogSection(name string) *FortniteCatalogSection { func NewFortniteCatalogSection(name string) *FortniteCatalogSection {
@ -243,10 +310,10 @@ func NewFortniteCatalogSection(name string) *FortniteCatalogSection {
} }
} }
func (f *FortniteCatalogSection) GenerateFortniteCatalogSection() aid.JSON { func (f *FortniteCatalogSection) GenerateFortniteCatalogSectionResponse() aid.JSON {
catalogEntiresResponse := []aid.JSON{} catalogEntiresResponse := []aid.JSON{}
for _, offer := range f.Offers { for _, offer := range f.Offers {
catalogEntiresResponse = append(catalogEntiresResponse, offer.GenerateFortniteCatalogSectionOffer()) catalogEntiresResponse = append(catalogEntiresResponse, offer.GenerateFortniteCatalogCosmeticOfferResponse())
} }
return aid.JSON{ return aid.JSON{
@ -255,12 +322,12 @@ func (f *FortniteCatalogSection) GenerateFortniteCatalogSection() aid.JSON {
} }
} }
func (f *FortniteCatalogSection) GetGroupedOffers() map[string][]*FortniteCatalogSectionOffer { func (f *FortniteCatalogSection) GetGroupedOffers() map[string][]*FortniteCatalogCosmeticOffer {
groupedOffers := map[string][]*FortniteCatalogSectionOffer{} groupedOffers := map[string][]*FortniteCatalogCosmeticOffer{}
for _, offer := range f.Offers { for _, offer := range f.Offers {
if groupedOffers[offer.Meta.Category] == nil { if groupedOffers[offer.Meta.Category] == nil {
groupedOffers[offer.Meta.Category] = []*FortniteCatalogSectionOffer{} groupedOffers[offer.Meta.Category] = []*FortniteCatalogCosmeticOffer{}
} }
groupedOffers[offer.Meta.Category] = append(groupedOffers[offer.Meta.Category], offer) groupedOffers[offer.Meta.Category] = append(groupedOffers[offer.Meta.Category], offer)
@ -271,18 +338,33 @@ func (f *FortniteCatalogSection) GetGroupedOffers() map[string][]*FortniteCatalo
type FortniteCatalog struct { type FortniteCatalog struct {
Sections []*FortniteCatalogSection Sections []*FortniteCatalogSection
MoneyOffers []*FortniteCatalogCurrencyOffer
} }
func NewFortniteCatalog() *FortniteCatalog { func NewFortniteCatalog() *FortniteCatalog {
return &FortniteCatalog{} return &FortniteCatalog{
Sections: []*FortniteCatalogSection{},
MoneyOffers: []*FortniteCatalogCurrencyOffer{},
}
} }
func (f *FortniteCatalog) GenerateFortniteCatalog() aid.JSON { func (f *FortniteCatalog) GenerateFortniteCatalogResponse() aid.JSON {
catalogSectionsResponse := []aid.JSON{} catalogSectionsResponse := []aid.JSON{}
for _, section := range f.Sections { for _, section := range f.Sections {
catalogSectionsResponse = append(catalogSectionsResponse, section.GenerateFortniteCatalogSection()) catalogSectionsResponse = append(catalogSectionsResponse, section.GenerateFortniteCatalogSectionResponse())
} }
currencyOffersResponse := []aid.JSON{}
for _, offer := range f.MoneyOffers {
currencyOffersResponse = append(currencyOffersResponse, offer.GenerateFortniteCatalogCurrencyOfferResponse())
}
catalogSectionsResponse = append(catalogSectionsResponse, aid.JSON{
"name": "CurrencyStorefront",
"catalogEntries": currencyOffersResponse,
})
return aid.JSON{ return aid.JSON{
"storefronts": catalogSectionsResponse, "storefronts": catalogSectionsResponse,
"refreshIntervalHrs": 24, "refreshIntervalHrs": 24,
@ -291,27 +373,49 @@ func (f *FortniteCatalog) GenerateFortniteCatalog() aid.JSON {
} }
} }
func (f *FortniteCatalog) FindCosmeticOfferById(id string) *FortniteCatalogCosmeticOffer {
for _, section := range f.Sections {
for _, offer := range section.Offers {
if offer.ID == id {
return offer
}
}
}
return nil
}
func (f *FortniteCatalog) FindCurrencyOfferById(id string) *FortniteCatalogCurrencyOffer {
for _, offer := range f.MoneyOffers {
if offer.ID == id {
return offer
}
}
return nil
}
func NewRandomFortniteCatalog() *FortniteCatalog { func NewRandomFortniteCatalog() *FortniteCatalog {
aid.SetRandom(rand.New(rand.NewSource(int64(aid.Config.Fortnite.ShopSeed) + aid.CurrentDayUnix()))) aid.SetRandom(rand.New(rand.NewSource(int64(aid.Config.Fortnite.ShopSeed) + aid.CurrentDayUnix())))
catalog := NewFortniteCatalog() catalog := NewFortniteCatalog()
daily := NewFortniteCatalogSection("BRDailyStorefront") daily := NewFortniteCatalogSection("BRDailyStorefront")
for len(daily.Offers) < dailyItems(aid.Config.Fortnite.Season) { for len(daily.Offers) < DataClient.GetStorefrontDailyItemCount(aid.Config.Fortnite.Season) {
entry := newEntryFromFortniteItem(GetRandomItemWithDisplayAssetOfNotType("AthenaCharacter"), false) entry := newCosmeticOfferFromFortniteitem(GetRandomItemWithDisplayAssetOfNotType("AthenaCharacter"), false)
entry.Meta.SectionId = "Daily" entry.Meta.SectionId = "Daily"
daily.Offers = append(daily.Offers, entry) daily.Offers = append(daily.Offers, entry)
} }
catalog.Sections = append(catalog.Sections, daily) catalog.Sections = append(catalog.Sections, daily)
weekly := NewFortniteCatalogSection("BRWeeklyStorefront") weekly := NewFortniteCatalogSection("BRWeeklyStorefront")
for len(weekly.GetGroupedOffers()) < weeklySets(aid.Config.Fortnite.Season) { for len(weekly.GetGroupedOffers()) < DataClient.GetStorefrontWeeklySetCount(aid.Config.Fortnite.Season) {
set := GetRandomSet() set := GetRandomSet()
for _, item := range set.Items { for _, item := range set.Items {
if item.DisplayAssetPath == "" || item.DisplayAssetPath2 == "" { if item.DisplayAssetPath == "" || item.DisplayAssetPath2 == "" {
continue continue
} }
entry := newEntryFromFortniteItem(item, true) entry := newCosmeticOfferFromFortniteitem(item, true)
entry.Meta.Category = set.BackendName entry.Meta.Category = set.BackendName
entry.Meta.SectionId = "Featured" entry.Meta.SectionId = "Featured"
weekly.Offers = append(weekly.Offers, entry) weekly.Offers = append(weekly.Offers, entry)
@ -319,10 +423,39 @@ func NewRandomFortniteCatalog() *FortniteCatalog {
} }
catalog.Sections = append(catalog.Sections, weekly) catalog.Sections = append(catalog.Sections, weekly)
if aid.Config.Fortnite.EnableVBucks {
smallCurrencyOffer := newCurrencyOfferFromName("Small Currency Pack", 1000, 0)
smallCurrencyOffer.Meta.IconSize = "XSmall"
smallCurrencyOffer.Meta.CurrencyAnalyticsName = "MtxPack1000"
smallCurrencyOffer.Priority = -len(catalog.MoneyOffers)
catalog.MoneyOffers = append(catalog.MoneyOffers, smallCurrencyOffer)
mediumCurrencyOffer := newCurrencyOfferFromName("Medium Currency Pack", 2000, 800)
mediumCurrencyOffer.Meta.IconSize = "Small"
mediumCurrencyOffer.Meta.CurrencyAnalyticsName = "MtxPack2800"
mediumCurrencyOffer.Meta.BannerOverride = "12PercentExtra"
mediumCurrencyOffer.Priority = -len(catalog.MoneyOffers)
catalog.MoneyOffers = append(catalog.MoneyOffers, mediumCurrencyOffer)
intermediateCurrencyOffer := newCurrencyOfferFromName("Intermediate Currency Pack", 4000, 1000)
intermediateCurrencyOffer.Meta.IconSize = "Medium"
intermediateCurrencyOffer.Meta.CurrencyAnalyticsName = "MtxPack5000"
intermediateCurrencyOffer.Meta.BannerOverride = "25PercentExtra"
intermediateCurrencyOffer.Priority = -len(catalog.MoneyOffers)
catalog.MoneyOffers = append(catalog.MoneyOffers, intermediateCurrencyOffer)
jumboCurrencyOffer := newCurrencyOfferFromName("Jumbo Currency Pack", 10000, 3500)
jumboCurrencyOffer.Meta.IconSize = "XLarge"
jumboCurrencyOffer.Meta.CurrencyAnalyticsName = "MtxPack13500"
jumboCurrencyOffer.Meta.BannerOverride = "35PercentExtra"
jumboCurrencyOffer.Priority = -len(catalog.MoneyOffers)
catalog.MoneyOffers = append(catalog.MoneyOffers, jumboCurrencyOffer)
}
return catalog return catalog
} }
func newEntryFromFortniteItem(fortniteItem *FortniteItem, addAssets bool) *FortniteCatalogSectionOffer { func newCosmeticOfferFromFortniteitem(fortniteItem *FortniteItem, addAssets bool) *FortniteCatalogCosmeticOffer {
displayAsset := regexp.MustCompile(`[^/]+$`).FindString(fortniteItem.DisplayAssetPath) displayAsset := regexp.MustCompile(`[^/]+$`).FindString(fortniteItem.DisplayAssetPath)
entry := NewFortniteCatalogSectionOffer() entry := NewFortniteCatalogSectionOffer()
@ -345,16 +478,15 @@ func newEntryFromFortniteItem(fortniteItem *FortniteItem, addAssets bool) *Fortn
return entry return entry
} }
func GetOfferByOfferId(id string) *FortniteCatalogSectionOffer { func newCurrencyOfferFromName(name string, original, bonus int) *FortniteCatalogCurrencyOffer {
catalog := NewRandomFortniteCatalog() formattedPrice := aid.FormatNumber(original + bonus)
offer := NewFortniteCatalogCurrencyOffer(original, bonus)
offer.Meta.IconSize = "Small"
offer.Meta.CurrencyAnalyticsName = name
offer.DevName = name
offer.Title = formattedPrice + " V-Bucks"
offer.Description = "Buy " + formattedPrice + " Fortnite V-Bucks, the in-game currency that can be spent in Fortnite Battle Royale and Creative modes. You can purchase new customization items like Outfits, Gliders, Pickaxes, Emotes, Wraps and the latest season's Battle Pass! Gliders and Contrails may not be used in Save the World mode."
offer.LongDescription = "Buy " + formattedPrice + " Fortnite V-Bucks, the in-game currency that can be spent in Fortnite Battle Royale and Creative modes. You can purchase new customization items like Outfits, Gliders, Pickaxes, Emotes, Wraps and the latest season's Battle Pass! Gliders and Contrails may not be used in Save the World mode.\n\nAll V-Bucks purchased on the Epic Games Store are not redeemable or usable on Nintendo Switch™."
for _, section := range catalog.Sections { return offer
for _, offer := range section.Offers {
if offer.ID == id {
return offer
}
}
}
return nil
} }

View File

@ -16,7 +16,7 @@ func MiddlewareOnlyDebug(c *fiber.Ctx) error {
} }
func GetSnowPreloadedCosmetics(c *fiber.Ctx) error { func GetSnowPreloadedCosmetics(c *fiber.Ctx) error {
return c.JSON(fortnite.External) return c.JSON(fortnite.DataClient)
} }
func GetSnowCachedPlayers(c *fiber.Ctx) error { func GetSnowCachedPlayers(c *fiber.Ctx) error {
@ -43,7 +43,7 @@ func GetSnowParties(c *fiber.Ctx) error {
func GetSnowShop(c *fiber.Ctx) error { func GetSnowShop(c *fiber.Ctx) error {
shop := fortnite.NewRandomFortniteCatalog() shop := fortnite.NewRandomFortniteCatalog()
return c.JSON(shop.GenerateFortniteCatalog()) return c.JSON(shop.GenerateFortniteCatalogResponse())
} }
// //

View File

@ -1,6 +1,8 @@
package handlers package handlers
import ( import (
"strings"
"github.com/goccy/go-json" "github.com/goccy/go-json"
"github.com/ectrc/snow/aid" "github.com/ectrc/snow/aid"
@ -11,7 +13,7 @@ import (
func GetStorefrontCatalog(c *fiber.Ctx) error { func GetStorefrontCatalog(c *fiber.Ctx) error {
shop := fortnite.NewRandomFortniteCatalog() shop := fortnite.NewRandomFortniteCatalog()
return c.Status(200).JSON(shop.GenerateFortniteCatalog()) return c.Status(200).JSON(shop.GenerateFortniteCatalogResponse())
} }
func GetStorefrontKeychain(c *fiber.Ctx) error { func GetStorefrontKeychain(c *fiber.Ctx) error {
@ -23,3 +25,25 @@ func GetStorefrontKeychain(c *fiber.Ctx) error {
return c.Status(200).JSON(keychain) return c.Status(200).JSON(keychain)
} }
func GetStorefrontCatalogBulkOffers(c *fiber.Ctx) error {
shop := fortnite.NewRandomFortniteCatalog()
appStoreIdBytes := c.Request().URI().QueryArgs().PeekMulti("id")
appStoreIds := make([]string, len(appStoreIdBytes))
for i, id := range appStoreIdBytes {
appStoreIds[i] = string(id)
}
response := aid.JSON{}
for _, id := range appStoreIds {
offer := shop.FindCurrencyOfferById(strings.ReplaceAll(id, "app-", ""))
if offer == nil {
continue
}
response[id] = offer.GenerateFortniteCatalogBulkOfferResponse()
}
return c.Status(200).JSON(response)
}

View File

@ -112,6 +112,7 @@ func main() {
storefront.Use(aid.FiberLimiter(4)) storefront.Use(aid.FiberLimiter(4))
storefront.Get("/catalog", handlers.GetStorefrontCatalog) storefront.Get("/catalog", handlers.GetStorefrontCatalog)
storefront.Get("/keychain", handlers.GetStorefrontKeychain) storefront.Get("/keychain", handlers.GetStorefrontKeychain)
r.Get("/catalog/api/shared/bulk/offers", handlers.GetStorefrontCatalogBulkOffers)
matchmaking := fortnite.Group("/matchmaking") matchmaking := fortnite.Group("/matchmaking")
matchmaking.Get("/session/findPlayer/:accountId", handlers.GetMatchmakingSession) matchmaking.Get("/session/findPlayer/:accountId", handlers.GetMatchmakingSession)

View File

@ -30,7 +30,9 @@ And once battle royale is completed ...
- **Gifting** Of any item shop entry to any friend. - **Gifting** Of any item shop entry to any friend.
- **Locker Loadouts** On seasons 12 onwards, this allows for the saving and loading of multiple locker presets. - **Locker Loadouts** On seasons 12 onwards, this allows for the saving and loading of multiple locker presets.
- **Item Refunding** Of previous shop purchases, will use a refund ticket if refunded in time. - **Item Refunding** Of previous shop purchases, will use a refund ticket if refunded in time.
- **V-Bucks Purchasing** By V-Bucks and Starter Packs right from the in-game store!
- **Client Settings Storage** Uses amazon buckets to store client settings. - **Client Settings Storage** Uses amazon buckets to store client settings.
- **Giftable Bundles** Players can recieve bundles, e.g. Twitch Prime, and gift them to friends.
- **Support A Creator 5%** Use any display name and each purchase will give them 5% of the vbucks spent. - **Support A Creator 5%** Use any display name and each purchase will give them 5% of the vbucks spent.
- **Discord Bot** Very useful to control players, their inventory and their settings - **Discord Bot** Very useful to control players, their inventory and their settings