Files

1551 lines
65 KiB
JavaScript

const express = require("express");
const app = express.Router();
const Friends = require("../model/friends");
const Profile = require("../model/profiles.js");
const profileManager = require("../structs/profile.js");
const error = require("../structs/error.js");
const functions = require("../structs/functions.js");
const { verifyToken, verifyClient } = require("../tokenManager/tokenVerify.js");
global.giftReceived = {};
app.post("/fortnite/api/game/v2/profile/*/client/SetReceiveGiftsEnabled", verifyToken, async (req, res) => {
const profiles = await Profile.findOne({ accountId: req.user.accountId });
if (!await profileManager.validateProfile(req.query.profileId, profiles)) return error.createError(
"errors.com.epicgames.modules.profiles.operation_forbidden",
`Unable to find template configuration for profile ${req.query.profileId}`,
[req.query.profileId], 12813, undefined, 403, res
);
let profile = profiles.profiles[req.query.profileId];
if (req.query.profileId != "common_core") return error.createError(
"errors.com.epicgames.modules.profiles.invalid_command",
`SetReceiveGiftsEnabled is not valid on ${req.query.profileId} profile`,
["SetReceiveGiftsEnabled",req.query.profileId], 12801, undefined, 400, res
);
const memory = functions.GetVersionInfo(req);
let ApplyProfileChanges = [];
let BaseRevision = profile.rvn;
let ProfileRevisionCheck = (memory.build >= 12.20) ? profile.commandRevision : profile.rvn;
let QueryRevision = req.query.rvn || -1;
if (typeof req.body.bReceiveGifts != "boolean") return ValidationError("bReceiveGifts", "a boolean", res);
profile.stats.attributes.allowed_to_receive_gifts = req.body.bReceiveGifts;
ApplyProfileChanges.push({
"changeType": "statModified",
"name": "allowed_to_receive_gifts",
"value": profile.stats.attributes.allowed_to_receive_gifts
});
if (ApplyProfileChanges.length > 0) {
profile.rvn += 1;
profile.commandRevision += 1;
profile.updated = new Date().toISOString();
await profiles.updateOne({ $set: { [`profiles.${req.query.profileId}`]: profile } });
}
if (QueryRevision != ProfileRevisionCheck) {
ApplyProfileChanges = [{
"changeType": "fullProfileUpdate",
"profile": profile
}];
}
res.json({
profileRevision: profile.rvn || 0,
profileId: req.query.profileId,
profileChangesBaseRevision: BaseRevision,
profileChanges: ApplyProfileChanges,
profileCommandRevision: profile.commandRevision || 0,
serverTime: new Date().toISOString(),
responseVersion: 1
});
});
app.post("/fortnite/api/game/v2/profile/*/client/GiftCatalogEntry", verifyToken, async (req, res) => {
const profiles = await Profile.findOne({ accountId: req.user.accountId });
if (!await profileManager.validateProfile(req.query.profileId, profiles)) return error.createError(
"errors.com.epicgames.modules.profiles.operation_forbidden",
`Unable to find template configuration for profile ${req.query.profileId}`,
[req.query.profileId], 12813, undefined, 403, res
);
let profile = profiles.profiles[req.query.profileId];
if (req.query.profileId != "common_core") return error.createError(
"errors.com.epicgames.modules.profiles.invalid_command",
`GiftCatalogEntry is not valid on ${req.query.profileId} profile`,
["GiftCatalogEntry",req.query.profileId], 12801, undefined, 400, res
);
const memory = functions.GetVersionInfo(req);
let Notifications = [];
let ApplyProfileChanges = [];
let BaseRevision = profile.rvn;
let ProfileRevisionCheck = (memory.build >= 12.20) ? profile.commandRevision : profile.rvn;
let QueryRevision = req.query.rvn || -1;
let validGiftBoxes = [
"GiftBox:gb_default",
"GiftBox:gb_giftwrap1",
"GiftBox:gb_giftwrap2",
"GiftBox:gb_giftwrap3"
];
let missingFields = checkFields(["offerId","receiverAccountIds","giftWrapTemplateId"], req.body);
if (missingFields.fields.length > 0) return error.createError(
"errors.com.epicgames.validation.validation_failed",
`Validation Failed. [${missingFields.fields.join(", ")}] field(s) is missing.`,
[`[${missingFields.fields.join(", ")}]`], 1040, undefined, 400, res
);
if (typeof req.body.offerId != "string") return ValidationError("offerId", "a string", res);
if (!Array.isArray(req.body.receiverAccountIds)) return ValidationError("receiverAccountIds", "an array", res);
if (typeof req.body.giftWrapTemplateId != "string") return ValidationError("giftWrapTemplateId", "a string", res);
if (typeof req.body.personalMessage != "string") return ValidationError("personalMessage", "a string", res);
if (req.body.personalMessage.length > 100) return error.createError(
"errors.com.epicgames.string.length_check",
`The personalMessage you provided is longer than 100 characters, please make sure your personal message is less than 100 characters long and try again.`,
undefined, 16027, undefined, 400, res
);
if (!validGiftBoxes.includes(req.body.giftWrapTemplateId)) return error.createError(
"errors.com.epicgames.giftbox.invalid",
`The giftbox you provided is invalid, please provide a valid giftbox and try again.`,
undefined, 16027, undefined, 400, res
);
if (req.body.receiverAccountIds.length < 1 || req.body.receiverAccountIds.length > 5) return error.createError(
"errors.com.epicgames.item.quantity.range_check",
`You need to atleast gift to 1 person and can not gift to more than 5 people.`,
undefined, 16027, undefined, 400, res
);
if (checkIfDuplicateExists(req.body.receiverAccountIds)) return error.createError(
"errors.com.epicgames.array.duplicate_found",
`There are duplicate accountIds in receiverAccountIds, please remove the duplicates and try again.`,
undefined, 16027, undefined, 400, res
);
let sender = await Friends.findOne({ accountId: req.user.accountId }).lean();
for (let receiverId of req.body.receiverAccountIds) {
if (typeof receiverId != "string") return error.createError(
"errors.com.epicgames.array.invalid_string",
`There is a non-string object inside receiverAccountIds, please provide a valid value and try again.`,
undefined, 16027, undefined, 400, res
);
if (!sender.list.accepted.find(i => i.accountId == receiverId) && receiverId != req.user.accountId) return error.createError(
"errors.com.epicgames.friends.no_relationship",
`User ${req.user.accountId} is not friends with ${receiverId}`,
[req.user.accountId,receiverId], 28004, undefined, 403, res
);
}
if (!profile.items) profile.items = {};
let findOfferId = functions.getOfferID(req.body.offerId);
if (!findOfferId) return error.createError(
"errors.com.epicgames.fortnite.id_invalid",
`Offer ID (id: '${req.body.offerId}') not found`,
[req.body.offerId], 16027, undefined, 400, res
);
switch (true) {
case /^BR(Daily|Weekly)Storefront$/.test(findOfferId.name):
if (findOfferId.offerId.prices[0].currencyType.toLowerCase() == "mtxcurrency") {
let paid = false;
let price = (findOfferId.offerId.prices[0].finalPrice) * req.body.receiverAccountIds.length;
for (let key in profile.items) {
if (!profile.items[key].templateId.toLowerCase().startsWith("currency:mtx")) continue;
let currencyPlatform = profile.items[key].attributes.platform;
if ((currencyPlatform.toLowerCase() != profile.stats.attributes.current_mtx_platform.toLowerCase()) && (currencyPlatform.toLowerCase() != "shared")) continue;
if (profile.items[key].quantity < price) return error.createError(
"errors.com.epicgames.currency.mtx.insufficient",
`You can not afford this item (${price}), you only have ${profile.items[key].quantity}.`,
[`${price}`,`${profile.items[key].quantity}`], 1040, undefined, 400, res
);
profile.items[key].quantity -= price;
ApplyProfileChanges.push({
"changeType": "itemQuantityChanged",
"itemId": key,
"quantity": profile.items[key].quantity
});
paid = true;
break;
}
if (!paid && price > 0) return error.createError(
"errors.com.epicgames.currency.mtx.insufficient",
`You can not afford this item.`,
[], 1040, undefined, 400, res
);
}
for (let receiverId of req.body.receiverAccountIds) {
const receiverProfiles = await Profile.findOne({ accountId: receiverId });
let athena = receiverProfiles.profiles["athena"];
let common_core = receiverProfiles.profiles["common_core"];
if (!athena.items) athena.items = {};
if (!common_core.stats.attributes.allowed_to_receive_gifts) return error.createError(
"errors.com.epicgames.user.gift_disabled",
`User ${receiverId} has disabled receiving gifts.`,
[receiverId], 28004, undefined, 403, res
);
for (let itemGrant of findOfferId.offerId.itemGrants) {
for (let itemId in athena.items) {
if (itemGrant.templateId.toLowerCase() == athena.items[itemId].templateId.toLowerCase()) return error.createError(
"errors.com.epicgames.modules.gamesubcatalog.purchase_not_allowed",
`User ${receiverId} already owns this item.`,
[receiverId], 28004, undefined, 403, res
);
}
}
}
for (let receiverId of req.body.receiverAccountIds) {
const receiverProfiles = await Profile.findOne({ accountId: receiverId });
let athena = receiverProfiles.profiles["athena"];
let common_core = ((receiverId == req.user.accountId) ? profile : receiverProfiles.profiles["common_core"]);
let giftBoxItemID = functions.MakeID();
let giftBoxItem = {
"templateId": req.body.giftWrapTemplateId,
"attributes": {
"fromAccountId": req.user.accountId,
"lootList": [],
"params": {
"userMessage": req.body.personalMessage
},
"level": 1,
"giftedOn": new Date().toISOString()
},
"quantity": 1
};
if (!athena.items) athena.items = {};
if (!common_core.items) common_core.items = {};
for (let value of findOfferId.offerId.itemGrants) {
const ID = functions.MakeID();
const Item = {
"templateId": value.templateId,
"attributes": {
"item_seen": false,
"variants": [],
},
"quantity": 1
};
athena.items[ID] = Item;
giftBoxItem.attributes.lootList.push({
"itemType": Item.templateId,
"itemGuid": ID,
"itemProfile": "athena",
"quantity": 1
});
}
common_core.items[giftBoxItemID] = giftBoxItem;
if (receiverId == req.user.accountId) ApplyProfileChanges.push({
"changeType": "itemAdded",
"itemId": giftBoxItemID,
"item": common_core.items[giftBoxItemID]
});
athena.rvn += 1;
athena.commandRevision += 1;
athena.updated = new Date().toISOString();
common_core.rvn += 1;
common_core.commandRevision += 1;
common_core.updated = new Date().toISOString();
await receiverProfiles.updateOne({ $set: { [`profiles.athena`]: athena, [`profiles.common_core`]: common_core } });
global.giftReceived[receiverId] = true;
functions.sendXmppMessageToId({
type: "com.epicgames.gift.received",
payload: {},
timestamp: new Date().toISOString()
}, receiverId);
}
break;
}
if (ApplyProfileChanges.length > 0 && !req.body.receiverAccountIds.includes(req.user.accountId)) {
profile.rvn += 1;
profile.commandRevision += 1;
profile.updated = new Date().toISOString();
await profiles.updateOne({ $set: { [`profiles.${req.query.profileId}`]: profile } });
}
if (QueryRevision != ProfileRevisionCheck) {
ApplyProfileChanges = [{
"changeType": "fullProfileUpdate",
"profile": profile
}];
}
res.json({
profileRevision: profile.rvn || 0,
profileId: req.query.profileId,
profileChangesBaseRevision: BaseRevision,
profileChanges: ApplyProfileChanges,
notifications: Notifications,
profileCommandRevision: profile.commandRevision || 0,
serverTime: new Date().toISOString(),
responseVersion: 1
});
});
app.post("/fortnite/api/game/v2/profile/*/client/RemoveGiftBox", verifyToken, async (req, res) => {
const profiles = await Profile.findOne({ accountId: req.user.accountId });
if (!await profileManager.validateProfile(req.query.profileId, profiles)) return error.createError(
"errors.com.epicgames.modules.profiles.operation_forbidden",
`Unable to find template configuration for profile ${req.query.profileId}`,
[req.query.profileId], 12813, undefined, 403, res
);
let profile = profiles.profiles[req.query.profileId];
if (req.query.profileId != "common_core" && req.query.profileId != "profile0") return error.createError(
"errors.com.epicgames.modules.profiles.invalid_command",
`RemoveGiftBox is not valid on ${req.query.profileId} profile`,
["RemoveGiftBox",req.query.profileId], 12801, undefined, 400, res
);
const memory = functions.GetVersionInfo(req);
let ApplyProfileChanges = [];
let BaseRevision = profile.rvn;
let ProfileRevisionCheck = (memory.build >= 12.20) ? profile.commandRevision : profile.rvn;
let QueryRevision = req.query.rvn || -1;
if (typeof req.body.giftBoxItemId == "string") {
if (!profile.items[req.body.giftBoxItemId]) return error.createError(
"errors.com.epicgames.fortnite.id_invalid",
`Item (id: '${req.body.giftBoxItemId}') not found`,
[req.body.giftBoxItemId], 16027, undefined, 400, res
);
if (!profile.items[req.body.giftBoxItemId].templateId.startsWith("GiftBox:")) return error.createError(
"errors.com.epicgames.fortnite.id_invalid",
`The specified item id is not a giftbox.`,
[req.body.giftBoxItemId], 16027, undefined, 400, res
);
delete profile.items[req.body.giftBoxItemId];
ApplyProfileChanges.push({
"changeType": "itemRemoved",
"itemId": req.body.giftBoxItemId
});
}
if (Array.isArray(req.body.giftBoxItemIds)) {
for (let giftBoxItemId of req.body.giftBoxItemIds) {
if (typeof giftBoxItemId != "string") continue;
if (!profile.items[giftBoxItemId]) continue;
if (!profile.items[giftBoxItemId].templateId.startsWith("GiftBox:")) continue;
delete profile.items[giftBoxItemId];
ApplyProfileChanges.push({
"changeType": "itemRemoved",
"itemId": giftBoxItemId
});
}
}
if (ApplyProfileChanges.length > 0) {
profile.rvn += 1;
profile.commandRevision += 1;
profile.updated = new Date().toISOString();
await profiles.updateOne({ $set: { [`profiles.${req.query.profileId}`]: profile } });
}
if (QueryRevision != ProfileRevisionCheck) {
ApplyProfileChanges = [{
"changeType": "fullProfileUpdate",
"profile": profile
}];
}
res.json({
profileRevision: profile.rvn || 0,
profileId: req.query.profileId,
profileChangesBaseRevision: BaseRevision,
profileChanges: ApplyProfileChanges,
profileCommandRevision: profile.commandRevision || 0,
serverTime: new Date().toISOString(),
responseVersion: 1
});
});
app.post("/fortnite/api/game/v2/profile/*/client/PurchaseCatalogEntry", verifyToken, async (req, res) => {
const profiles = await Profile.findOne({ accountId: req.user.accountId });
if (!await profileManager.validateProfile(req.query.profileId, profiles)) return error.createError(
"errors.com.epicgames.modules.profiles.operation_forbidden",
`Unable to find template configuration for profile ${req.query.profileId}`,
[req.query.profileId], 12813, undefined, 403, res
);
let profile = profiles.profiles[req.query.profileId];
let athena = profiles.profiles["athena"];
if (req.query.profileId != "common_core" && req.query.profileId != "profile0") return error.createError(
"errors.com.epicgames.modules.profiles.invalid_command",
`PurchaseCatalogEntry is not valid on ${req.query.profileId} profile`,
["PurchaseCatalogEntry",req.query.profileId], 12801, undefined, 400, res
);
let MultiUpdate = [{
"profileRevision": athena.rvn || 0,
"profileId": "athena",
"profileChangesBaseRevision": athena.rvn || 0,
"profileChanges": [],
"profileCommandRevision": athena.commandRevision || 0,
}];
const memory = functions.GetVersionInfo(req);
let Notifications = [];
let ApplyProfileChanges = [];
let BaseRevision = profile.rvn;
let ProfileRevisionCheck = (memory.build >= 12.20) ? profile.commandRevision : profile.rvn;
let QueryRevision = req.query.rvn || -1;
let missingFields = checkFields(["offerId"], req.body);
if (missingFields.fields.length > 0) return error.createError(
"errors.com.epicgames.validation.validation_failed",
`Validation Failed. [${missingFields.fields.join(", ")}] field(s) is missing.`,
[`[${missingFields.fields.join(", ")}]`], 1040, undefined, 400, res
);
if (typeof req.body.offerId != "string") return ValidationError("offerId", "a string", res);
if (typeof req.body.purchaseQuantity != "number") return ValidationError("purchaseQuantity", "a number", res);
if (req.body.purchaseQuantity < 1) return error.createError(
"errors.com.epicgames.validation.validation_failed",
`Validation Failed. 'purchaseQuantity' is less than 1.`,
['purchaseQuantity'], 1040, undefined, 400, res
);
if (!profile.items) profile.items = {};
if (!athena.items) athena.items = {};
let findOfferId = functions.getOfferID(req.body.offerId);
if (!findOfferId) return error.createError(
"errors.com.epicgames.fortnite.id_invalid",
`Offer ID (id: '${req.body.offerId}') not found`,
[req.body.offerId], 16027, undefined, 400, res
);
switch (true) {
case /^BR(Daily|Weekly|Season)Storefront$/.test(findOfferId.name):
Notifications.push({
"type": "CatalogPurchase",
"primary": true,
"lootResult": {
"items": []
}
});
for (let value of findOfferId.offerId.itemGrants) {
const ID = functions.MakeID();
for (let itemId in athena.items) {
if (value.templateId.toLowerCase() == athena.items[itemId].templateId.toLowerCase()) return error.createError(
"errors.com.epicgames.offer.already_owned",
`You have already bought this item before.`,
undefined, 1040, undefined, 400, res
);
}
const Item = {
"templateId": value.templateId,
"attributes": {
"item_seen": false,
"variants": [],
},
"quantity": 1
};
athena.items[ID] = Item;
MultiUpdate[0].profileChanges.push({
"changeType": "itemAdded",
"itemId": ID,
"item": athena.items[ID]
});
Notifications[0].lootResult.items.push({
"itemType": Item.templateId,
"itemGuid": ID,
"itemProfile": "athena",
"quantity": 1
});
}
if (findOfferId.offerId.prices[0].currencyType.toLowerCase() == "mtxcurrency") {
let paid = false;
for (let key in profile.items) {
if (!profile.items[key].templateId.toLowerCase().startsWith("currency:mtx")) continue;
let currencyPlatform = profile.items[key].attributes.platform;
if ((currencyPlatform.toLowerCase() != profile.stats.attributes.current_mtx_platform.toLowerCase()) && (currencyPlatform.toLowerCase() != "shared")) continue;
if (profile.items[key].quantity < findOfferId.offerId.prices[0].finalPrice) return error.createError(
"errors.com.epicgames.currency.mtx.insufficient",
`You can not afford this item (${findOfferId.offerId.prices[0].finalPrice}), you only have ${profile.items[key].quantity}.`,
[`${findOfferId.offerId.prices[0].finalPrice}`,`${profile.items[key].quantity}`], 1040, undefined, 400, res
);
profile.items[key].quantity -= findOfferId.offerId.prices[0].finalPrice;
ApplyProfileChanges.push({
"changeType": "itemQuantityChanged",
"itemId": key,
"quantity": profile.items[key].quantity
});
paid = true;
break;
}
if (!paid && findOfferId.offerId.prices[0].finalPrice > 0) return error.createError(
"errors.com.epicgames.currency.mtx.insufficient",
`You can not afford this item (${findOfferId.offerId.prices[0].finalPrice}).`,
[`${findOfferId.offerId.prices[0].finalPrice}`], 1040, undefined, 400, res
);
}
if (MultiUpdate[0].profileChanges.length > 0) {
athena.rvn += 1;
athena.commandRevision += 1;
athena.updated = new Date().toISOString();
MultiUpdate[0].profileRevision = athena.rvn;
MultiUpdate[0].profileCommandRevision = athena.commandRevision;
}
break;
}
if (ApplyProfileChanges.length > 0) {
profile.rvn += 1;
profile.commandRevision += 1;
profile.updated = new Date().toISOString();
await profiles.updateOne({ $set: { [`profiles.${req.query.profileId}`]: profile, [`profiles.athena`]: athena } });
}
if (QueryRevision != ProfileRevisionCheck) {
ApplyProfileChanges = [{
"changeType": "fullProfileUpdate",
"profile": profile
}];
}
res.json({
profileRevision: profile.rvn || 0,
profileId: req.query.profileId,
profileChangesBaseRevision: BaseRevision,
profileChanges: ApplyProfileChanges,
notifications: Notifications,
profileCommandRevision: profile.commandRevision || 0,
serverTime: new Date().toISOString(),
multiUpdate: MultiUpdate,
responseVersion: 1
});
});
app.post("/fortnite/api/game/v2/profile/*/client/MarkItemSeen", verifyToken, async (req, res) => {
const profiles = await Profile.findOne({ accountId: req.user.accountId });
if (!await profileManager.validateProfile(req.query.profileId, profiles)) return error.createError(
"errors.com.epicgames.modules.profiles.operation_forbidden",
`Unable to find template configuration for profile ${req.query.profileId}`,
[req.query.profileId], 12813, undefined, 403, res
);
let profile = profiles.profiles[req.query.profileId];
const memory = functions.GetVersionInfo(req);
if (req.query.profileId == "athena") profile.stats.attributes.season_num = memory.season;
let ApplyProfileChanges = [];
let BaseRevision = profile.rvn;
let ProfileRevisionCheck = (memory.build >= 12.20) ? profile.commandRevision : profile.rvn;
let QueryRevision = req.query.rvn || -1;
let missingFields = checkFields(["itemIds"], req.body);
if (missingFields.fields.length > 0) return error.createError(
"errors.com.epicgames.validation.validation_failed",
`Validation Failed. [${missingFields.fields.join(", ")}] field(s) is missing.`,
[`[${missingFields.fields.join(", ")}]`], 1040, undefined, 400, res
);
if (!Array.isArray(req.body.itemIds)) return ValidationError("itemIds", "an array", res);
if (!profile.items) profile.items = {};
for (let i in req.body.itemIds) {
if (!profile.items[req.body.itemIds[i]]) continue;
profile.items[req.body.itemIds[i]].attributes.item_seen = true;
ApplyProfileChanges.push({
"changeType": "itemAttrChanged",
"itemId": req.body.itemIds[i],
"attributeName": "item_seen",
"attributeValue": true
});
}
if (ApplyProfileChanges.length > 0) {
profile.rvn += 1;
profile.commandRevision += 1;
profile.updated = new Date().toISOString();
await profiles.updateOne({ $set: { [`profiles.${req.query.profileId}`]: profile } });
}
if (QueryRevision != ProfileRevisionCheck) {
ApplyProfileChanges = [{
"changeType": "fullProfileUpdate",
"profile": profile
}];
}
res.json({
profileRevision: profile.rvn || 0,
profileId: req.query.profileId,
profileChangesBaseRevision: BaseRevision,
profileChanges: ApplyProfileChanges,
profileCommandRevision: profile.commandRevision || 0,
serverTime: new Date().toISOString(),
responseVersion: 1
});
});
app.post("/fortnite/api/game/v2/profile/*/client/SetItemFavoriteStatusBatch", verifyToken, async (req, res) => {
const profiles = await Profile.findOne({ accountId: req.user.accountId });
if (!await profileManager.validateProfile(req.query.profileId, profiles)) return error.createError(
"errors.com.epicgames.modules.profiles.operation_forbidden",
`Unable to find template configuration for profile ${req.query.profileId}`,
[req.query.profileId], 12813, undefined, 403, res
);
if (req.query.profileId != "athena") return error.createError(
"errors.com.epicgames.modules.profiles.invalid_command",
`SetItemFavoriteStatusBatch is not valid on ${req.query.profileId} profile`,
["SetItemFavoriteStatusBatch",req.query.profileId], 12801, undefined, 400, res
);
let profile = profiles.profiles[req.query.profileId];
const memory = functions.GetVersionInfo(req);
if (req.query.profileId == "athena") profile.stats.attributes.season_num = memory.season;
let ApplyProfileChanges = [];
let BaseRevision = profile.rvn;
let ProfileRevisionCheck = (memory.build >= 12.20) ? profile.commandRevision : profile.rvn;
let QueryRevision = req.query.rvn || -1;
let missingFields = checkFields(["itemIds","itemFavStatus"], req.body);
if (missingFields.fields.length > 0) return error.createError(
"errors.com.epicgames.validation.validation_failed",
`Validation Failed. [${missingFields.fields.join(", ")}] field(s) is missing.`,
[`[${missingFields.fields.join(", ")}]`], 1040, undefined, 400, res
);
if (!Array.isArray(req.body.itemIds)) return ValidationError("itemIds", "an array", res);
if (!Array.isArray(req.body.itemFavStatus)) return ValidationError("itemFavStatus", "an array", res);
if (!profile.items) profile.items = {};
for (let i in req.body.itemIds) {
if (!profile.items[req.body.itemIds[i]]) continue;
if (typeof req.body.itemFavStatus[i] != "boolean") continue;
profile.items[req.body.itemIds[i]].attributes.favorite = req.body.itemFavStatus[i];
ApplyProfileChanges.push({
"changeType": "itemAttrChanged",
"itemId": req.body.itemIds[i],
"attributeName": "favorite",
"attributeValue": profile.items[req.body.itemIds[i]].attributes.favorite
});
}
if (ApplyProfileChanges.length > 0) {
profile.rvn += 1;
profile.commandRevision += 1;
profile.updated = new Date().toISOString();
await profiles.updateOne({ $set: { [`profiles.${req.query.profileId}`]: profile } });
}
if (QueryRevision != ProfileRevisionCheck) {
ApplyProfileChanges = [{
"changeType": "fullProfileUpdate",
"profile": profile
}];
}
res.json({
profileRevision: profile.rvn || 0,
profileId: req.query.profileId,
profileChangesBaseRevision: BaseRevision,
profileChanges: ApplyProfileChanges,
profileCommandRevision: profile.commandRevision || 0,
serverTime: new Date().toISOString(),
responseVersion: 1
});
});
app.post("/fortnite/api/game/v2/profile/*/client/SetBattleRoyaleBanner", verifyToken, async (req, res) => {
const profiles = await Profile.findOne({ accountId: req.user.accountId });
if (!await profileManager.validateProfile(req.query.profileId, profiles)) return error.createError(
"errors.com.epicgames.modules.profiles.operation_forbidden",
`Unable to find template configuration for profile ${req.query.profileId}`,
[req.query.profileId], 12813, undefined, 403, res
);
if (req.query.profileId != "athena") return error.createError(
"errors.com.epicgames.modules.profiles.invalid_command",
`SetBattleRoyaleBanner is not valid on ${req.query.profileId} profile`,
["SetBattleRoyaleBanner",req.query.profileId], 12801, undefined, 400, res
);
let profile = profiles.profiles[req.query.profileId];
const memory = functions.GetVersionInfo(req);
if (req.query.profileId == "athena") profile.stats.attributes.season_num = memory.season;
let ApplyProfileChanges = [];
let BaseRevision = profile.rvn;
let ProfileRevisionCheck = (memory.build >= 12.20) ? profile.commandRevision : profile.rvn;
let QueryRevision = req.query.rvn || -1;
let missingFields = checkFields(["homebaseBannerIconId","homebaseBannerColorId"], req.body);
if (missingFields.fields.length > 0) return error.createError(
"errors.com.epicgames.validation.validation_failed",
`Validation Failed. [${missingFields.fields.join(", ")}] field(s) is missing.`,
[`[${missingFields.fields.join(", ")}]`], 1040, undefined, 400, res
);
if (typeof req.body.homebaseBannerIconId != "string") return ValidationError("homebaseBannerIconId", "a string", res);
if (typeof req.body.homebaseBannerColorId != "string") return ValidationError("homebaseBannerColorId", "a string", res);
let bannerProfileId = memory.build < 3.5 ? "profile0" : "common_core";
let HomebaseBannerIconID = "";
let HomebaseBannerColorID = "";
if (!profiles.profiles[bannerProfileId].items) profiles.profiles[bannerProfileId].items = {};
for (let itemId in profiles.profiles[bannerProfileId].items) {
let templateId = profiles.profiles[bannerProfileId].items[itemId].templateId;
if (templateId.toLowerCase() == `HomebaseBannerIcon:${req.body.homebaseBannerIconId}`.toLowerCase()) { HomebaseBannerIconID = itemId; continue; }
if (templateId.toLowerCase() == `HomebaseBannerColor:${req.body.homebaseBannerColorId}`.toLowerCase()) { HomebaseBannerColorID = itemId; continue; }
if (HomebaseBannerIconID && HomebaseBannerColorID) break;
}
if (!HomebaseBannerIconID) return error.createError(
"errors.com.epicgames.fortnite.item_not_found",
`Banner template 'HomebaseBannerIcon:${req.body.homebaseBannerIconId}' not found in profile`,
[`HomebaseBannerIcon:${req.body.homebaseBannerIconId}`], 16006, undefined, 400, res
);
if (!HomebaseBannerColorID) return error.createError(
"errors.com.epicgames.fortnite.item_not_found",
`Banner template 'HomebaseBannerColor:${req.body.homebaseBannerColorId}' not found in profile`,
[`HomebaseBannerColor:${req.body.homebaseBannerColorId}`], 16006, undefined, 400, res
);
if (!profile.items) profile.items = {};
let activeLoadoutId = profile.stats.attributes.loadouts[profile.stats.attributes.active_loadout_index];
profile.stats.attributes.banner_icon = req.body.homebaseBannerIconId;
profile.stats.attributes.banner_color = req.body.homebaseBannerColorId;
profile.items[activeLoadoutId].attributes.banner_icon_template = req.body.homebaseBannerIconId;
profile.items[activeLoadoutId].attributes.banner_color_template = req.body.homebaseBannerColorId;
ApplyProfileChanges.push({
"changeType": "statModified",
"name": "banner_icon",
"value": profile.stats.attributes.banner_icon
});
ApplyProfileChanges.push({
"changeType": "statModified",
"name": "banner_color",
"value": profile.stats.attributes.banner_color
});
if (ApplyProfileChanges.length > 0) {
profile.rvn += 1;
profile.commandRevision += 1;
profile.updated = new Date().toISOString();
await profiles.updateOne({ $set: { [`profiles.${req.query.profileId}`]: profile } });
}
if (QueryRevision != ProfileRevisionCheck) {
ApplyProfileChanges = [{
"changeType": "fullProfileUpdate",
"profile": profile
}];
}
res.json({
profileRevision: profile.rvn || 0,
profileId: req.query.profileId,
profileChangesBaseRevision: BaseRevision,
profileChanges: ApplyProfileChanges,
profileCommandRevision: profile.commandRevision || 0,
serverTime: new Date().toISOString(),
responseVersion: 1
});
});
app.post("/fortnite/api/game/v2/profile/*/client/EquipBattleRoyaleCustomization", verifyToken, async (req, res) => {
const profiles = await Profile.findOne({ accountId: req.user.accountId });
if (!await profileManager.validateProfile(req.query.profileId, profiles)) return error.createError(
"errors.com.epicgames.modules.profiles.operation_forbidden",
`Unable to find template configuration for profile ${req.query.profileId}`,
[req.query.profileId], 12813, undefined, 403, res
);
if (req.query.profileId != "athena") return error.createError(
"errors.com.epicgames.modules.profiles.invalid_command",
`EquipBattleRoyaleCustomization is not valid on ${req.query.profileId} profile`,
["EquipBattleRoyaleCustomization",req.query.profileId], 12801, undefined, 400, res
);
let profile = profiles.profiles[req.query.profileId];
const memory = functions.GetVersionInfo(req);
if (req.query.profileId == "athena") profile.stats.attributes.season_num = memory.season;
let ApplyProfileChanges = [];
let BaseRevision = profile.rvn;
let ProfileRevisionCheck = (memory.build >= 12.20) ? profile.commandRevision : profile.rvn;
let QueryRevision = req.query.rvn || -1;
let specialCosmetics = [
"AthenaCharacter:cid_random",
"AthenaBackpack:bid_random",
"AthenaPickaxe:pickaxe_random",
"AthenaGlider:glider_random",
"AthenaSkyDiveContrail:trails_random",
"AthenaItemWrap:wrap_random",
"AthenaMusicPack:musicpack_random",
"AthenaLoadingScreen:lsid_random"
];
let missingFields = checkFields(["slotName"], req.body);
if (missingFields.fields.length > 0) return error.createError(
"errors.com.epicgames.validation.validation_failed",
`Validation Failed. [${missingFields.fields.join(", ")}] field(s) is missing.`,
[`[${missingFields.fields.join(", ")}]`], 1040, undefined, 400, res
);
if (typeof req.body.itemToSlot != "string") return ValidationError("itemToSlot", "a string", res);
if (typeof req.body.slotName != "string") return ValidationError("slotName", "a string", res);
if (!profile.items) profile.items = {};
if (!profile.items[req.body.itemToSlot] && req.body.itemToSlot) {
let item = req.body.itemToSlot;
if (!specialCosmetics.includes(item)) {
return error.createError(
"errors.com.epicgames.fortnite.id_invalid",
`Item (id: '${req.body.itemToSlot}') not found`,
[req.body.itemToSlot], 16027, undefined, 400, res
);
} else {
if (!item.startsWith(`Athena${req.body.slotName}:`)) return error.createError(
"errors.com.epicgames.fortnite.id_invalid",
`Cannot slot item of type ${item.split(":")[0]} in slot of category ${req.body.slotName}`,
[item.split(":")[0],req.body.slotName], 16027, undefined, 400, res
);
}
}
if (profile.items[req.body.itemToSlot]) {
if (!profile.items[req.body.itemToSlot].templateId.startsWith(`Athena${req.body.slotName}:`)) return error.createError(
"errors.com.epicgames.fortnite.id_invalid",
`Cannot slot item of type ${profile.items[req.body.itemToSlot].templateId.split(":")[0]} in slot of category ${req.body.slotName}`,
[profile.items[req.body.itemToSlot].templateId.split(":")[0],req.body.slotName], 16027, undefined, 400, res
);
let Variants = req.body.variantUpdates;
if (Array.isArray(Variants)) {
for (let i in Variants) {
if (typeof Variants[i] != "object") continue;
if (!Variants[i].channel) continue;
if (!Variants[i].active) continue;
let index = profile.items[req.body.itemToSlot].attributes.variants.findIndex(x => x.channel == Variants[i].channel);
if (index == -1) continue;
if (!profile.items[req.body.itemToSlot].attributes.variants[index].owned.includes(Variants[i].active)) continue;
profile.items[req.body.itemToSlot].attributes.variants[index].active = Variants[i].active;
}
ApplyProfileChanges.push({
"changeType": "itemAttrChanged",
"itemId": req.body.itemToSlot,
"attributeName": "variants",
"attributeValue": profile.items[req.body.itemToSlot].attributes.variants
});
}
}
let slotNames = ["Character","Backpack","Pickaxe","Glider","SkyDiveContrail","MusicPack","LoadingScreen"];
let activeLoadoutId = profile.stats.attributes.loadouts[profile.stats.attributes.active_loadout_index];
let templateId = profile.items[req.body.itemToSlot] ? profile.items[req.body.itemToSlot].templateId : req.body.itemToSlot;
switch (req.body.slotName) {
case "Dance":
if (!profile.items[activeLoadoutId].attributes.locker_slots_data.slots[req.body.slotName]) break;
if (typeof req.body.indexWithinSlot != "number") return ValidationError("indexWithinSlot", "a number", res);
if (req.body.indexWithinSlot >= 0 && req.body.indexWithinSlot <= 5) {
profile.stats.attributes.favorite_dance[req.body.indexWithinSlot] = req.body.itemToSlot;
profile.items[activeLoadoutId].attributes.locker_slots_data.slots.Dance.items[req.body.indexWithinSlot] = templateId;
ApplyProfileChanges.push({
"changeType": "statModified",
"name": "favorite_dance",
"value": profile.stats.attributes["favorite_dance"]
});
}
break;
case "ItemWrap":
if (!profile.items[activeLoadoutId].attributes.locker_slots_data.slots[req.body.slotName]) break;
if (typeof req.body.indexWithinSlot != "number") return ValidationError("indexWithinSlot", "a number", res);
switch (true) {
case req.body.indexWithinSlot >= 0 && req.body.indexWithinSlot <= 7:
profile.stats.attributes.favorite_itemwraps[req.body.indexWithinSlot] = req.body.itemToSlot;
profile.items[activeLoadoutId].attributes.locker_slots_data.slots.ItemWrap.items[req.body.indexWithinSlot] = templateId;
ApplyProfileChanges.push({
"changeType": "statModified",
"name": "favorite_itemwraps",
"value": profile.stats.attributes["favorite_itemwraps"]
});
break;
case req.body.indexWithinSlot == -1:
for (let i = 0; i < 7; i++) {
profile.stats.attributes.favorite_itemwraps[i] = req.body.itemToSlot;
profile.items[activeLoadoutId].attributes.locker_slots_data.slots.ItemWrap.items[i] = templateId;
}
ApplyProfileChanges.push({
"changeType": "statModified",
"name": "favorite_itemwraps",
"value": profile.stats.attributes["favorite_itemwraps"]
});
break;
}
break;
default:
if (!slotNames.includes(req.body.slotName)) break;
if (!profile.items[activeLoadoutId].attributes.locker_slots_data.slots[req.body.slotName]) break;
if (req.body.slotName == "Pickaxe" || req.body.slotName == "Glider") {
if (!req.body.itemToSlot) return error.createError(
"errors.com.epicgames.fortnite.id_invalid",
`${req.body.slotName} can not be empty.`,
[req.body.slotName], 16027, undefined, 400, res
);
}
profile.stats.attributes[(`favorite_${req.body.slotName}`).toLowerCase()] = req.body.itemToSlot;
profile.items[activeLoadoutId].attributes.locker_slots_data.slots[req.body.slotName].items = [templateId];
ApplyProfileChanges.push({
"changeType": "statModified",
"name": (`favorite_${req.body.slotName}`).toLowerCase(),
"value": profile.stats.attributes[(`favorite_${req.body.slotName}`).toLowerCase()]
});
break;
}
if (ApplyProfileChanges.length > 0) {
profile.rvn += 1;
profile.commandRevision += 1;
profile.updated = new Date().toISOString();
await profiles.updateOne({ $set: { [`profiles.${req.query.profileId}`]: profile } });
}
if (QueryRevision != ProfileRevisionCheck) {
ApplyProfileChanges = [{
"changeType": "fullProfileUpdate",
"profile": profile
}];
}
res.json({
profileRevision: profile.rvn || 0,
profileId: req.query.profileId,
profileChangesBaseRevision: BaseRevision,
profileChanges: ApplyProfileChanges,
profileCommandRevision: profile.commandRevision || 0,
serverTime: new Date().toISOString(),
responseVersion: 1
});
});
app.post("/fortnite/api/game/v2/profile/*/client/SetCosmeticLockerBanner", verifyToken, async (req, res) => {
const profiles = await Profile.findOne({ accountId: req.user.accountId });
if (!await profileManager.validateProfile(req.query.profileId, profiles)) return error.createError(
"errors.com.epicgames.modules.profiles.operation_forbidden",
`Unable to find template configuration for profile ${req.query.profileId}`,
[req.query.profileId], 12813, undefined, 403, res
);
if (req.query.profileId != "athena") return error.createError(
"errors.com.epicgames.modules.profiles.invalid_command",
`SetCosmeticLockerBanner is not valid on ${req.query.profileId} profile`,
["SetCosmeticLockerBanner",req.query.profileId], 12801, undefined, 400, res
);
let profile = profiles.profiles[req.query.profileId];
const memory = functions.GetVersionInfo(req);
if (req.query.profileId == "athena") profile.stats.attributes.season_num = memory.season;
let ApplyProfileChanges = [];
let BaseRevision = profile.rvn;
let ProfileRevisionCheck = (memory.build >= 12.20) ? profile.commandRevision : profile.rvn;
let QueryRevision = req.query.rvn || -1;
let missingFields = checkFields(["bannerIconTemplateName","bannerColorTemplateName","lockerItem"], req.body);
if (missingFields.fields.length > 0) return error.createError(
"errors.com.epicgames.validation.validation_failed",
`Validation Failed. [${missingFields.fields.join(", ")}] field(s) is missing.`,
[`[${missingFields.fields.join(", ")}]`], 1040, undefined, 400, res
);
if (typeof req.body.lockerItem != "string") return ValidationError("lockerItem", "a string", res);
if (typeof req.body.bannerIconTemplateName != "string") return ValidationError("bannerIconTemplateName", "a string", res);
if (typeof req.body.bannerColorTemplateName != "string") return ValidationError("bannerColorTemplateName", "a string", res);
if (!profile.items) profile.items = {};
if (!profile.items[req.body.lockerItem]) return error.createError(
"errors.com.epicgames.fortnite.id_invalid",
`Item (id: '${req.body.lockerItem}') not found`,
[req.body.lockerItem], 16027, undefined, 400, res
);
if (profile.items[req.body.lockerItem].templateId.toLowerCase() != "cosmeticlocker:cosmeticlocker_athena") return error.createError(
"errors.com.epicgames.fortnite.id_invalid",
`lockerItem id is not a cosmeticlocker`,
["lockerItem"], 16027, undefined, 400, res
);
let bannerProfileId = "common_core";
let HomebaseBannerIconID = "";
let HomebaseBannerColorID = "";
if (!profiles.profiles[bannerProfileId].items) profiles.profiles[bannerProfileId].items = {};
for (let itemId in profiles.profiles[bannerProfileId].items) {
let templateId = profiles.profiles[bannerProfileId].items[itemId].templateId;
if (templateId.toLowerCase() == `HomebaseBannerIcon:${req.body.bannerIconTemplateName}`.toLowerCase()) { HomebaseBannerIconID = itemId; continue; }
if (templateId.toLowerCase() == `HomebaseBannerColor:${req.body.bannerColorTemplateName}`.toLowerCase()) { HomebaseBannerColorID = itemId; continue; }
if (HomebaseBannerIconID && HomebaseBannerColorID) break;
}
if (!HomebaseBannerIconID) return error.createError(
"errors.com.epicgames.fortnite.item_not_found",
`Banner template 'HomebaseBannerIcon:${req.body.bannerIconTemplateName}' not found in profile`,
[`HomebaseBannerIcon:${req.body.bannerIconTemplateName}`], 16006, undefined, 400, res
);
if (!HomebaseBannerColorID) return error.createError(
"errors.com.epicgames.fortnite.item_not_found",
`Banner template 'HomebaseBannerColor:${req.body.bannerColorTemplateName}' not found in profile`,
[`HomebaseBannerColor:${req.body.bannerColorTemplateName}`], 16006, undefined, 400, res
);
profile.items[req.body.lockerItem].attributes.banner_icon_template = req.body.bannerIconTemplateName;
profile.items[req.body.lockerItem].attributes.banner_color_template = req.body.bannerColorTemplateName;
profile.stats.attributes.banner_icon = req.body.bannerIconTemplateName;
profile.stats.attributes.banner_color = req.body.bannerColorTemplateName;
ApplyProfileChanges.push({
"changeType": "itemAttrChanged",
"itemId": req.body.lockerItem,
"attributeName": "banner_icon_template",
"attributeValue": profile.items[req.body.lockerItem].attributes.banner_icon_template
});
ApplyProfileChanges.push({
"changeType": "itemAttrChanged",
"itemId": req.body.lockerItem,
"attributeName": "banner_color_template",
"attributeValue": profile.items[req.body.lockerItem].attributes.banner_color_template
});
if (ApplyProfileChanges.length > 0) {
profile.rvn += 1;
profile.commandRevision += 1;
profile.updated = new Date().toISOString();
await profiles.updateOne({ $set: { [`profiles.${req.query.profileId}`]: profile } });
}
if (QueryRevision != ProfileRevisionCheck) {
ApplyProfileChanges = [{
"changeType": "fullProfileUpdate",
"profile": profile
}];
}
res.json({
profileRevision: profile.rvn || 0,
profileId: req.query.profileId,
profileChangesBaseRevision: BaseRevision,
profileChanges: ApplyProfileChanges,
profileCommandRevision: profile.commandRevision || 0,
serverTime: new Date().toISOString(),
responseVersion: 1
});
});
app.post("/fortnite/api/game/v2/profile/*/client/SetCosmeticLockerSlot", verifyToken, async (req, res) => {
const profiles = await Profile.findOne({ accountId: req.user.accountId });
if (!await profileManager.validateProfile(req.query.profileId, profiles)) return error.createError(
"errors.com.epicgames.modules.profiles.operation_forbidden",
`Unable to find template configuration for profile ${req.query.profileId}`,
[req.query.profileId], 12813, undefined, 403, res
);
if (req.query.profileId != "athena") return error.createError(
"errors.com.epicgames.modules.profiles.invalid_command",
`SetCosmeticLockerSlot is not valid on ${req.query.profileId} profile`,
["SetCosmeticLockerSlot",req.query.profileId], 12801, undefined, 400, res
);
let profile = profiles.profiles[req.query.profileId];
const memory = functions.GetVersionInfo(req);
if (req.query.profileId == "athena") profile.stats.attributes.season_num = memory.season;
let ApplyProfileChanges = [];
let BaseRevision = profile.rvn;
let ProfileRevisionCheck = (memory.build >= 12.20) ? profile.commandRevision : profile.rvn;
let QueryRevision = req.query.rvn || -1;
let specialCosmetics = [
"AthenaCharacter:cid_random",
"AthenaBackpack:bid_random",
"AthenaPickaxe:pickaxe_random",
"AthenaGlider:glider_random",
"AthenaSkyDiveContrail:trails_random",
"AthenaItemWrap:wrap_random",
"AthenaMusicPack:musicpack_random",
"AthenaLoadingScreen:lsid_random"
];
let missingFields = checkFields(["category","lockerItem"], req.body);
if (missingFields.fields.length > 0) return error.createError(
"errors.com.epicgames.validation.validation_failed",
`Validation Failed. [${missingFields.fields.join(", ")}] field(s) is missing.`,
[`[${missingFields.fields.join(", ")}]`], 1040, undefined, 400, res
);
if (typeof req.body.itemToSlot != "string") return ValidationError("itemToSlot", "a string", res);
if (typeof req.body.slotIndex != "number") return ValidationError("slotIndex", "a number", res);
if (typeof req.body.lockerItem != "string") return ValidationError("lockerItem", "a string", res);
if (typeof req.body.category != "string") return ValidationError("category", "a string", res);
if (!profile.items) profile.items = {};
let itemToSlotID = "";
if (req.body.itemToSlot) {
for (let itemId in profile.items) {
if (profile.items[itemId].templateId.toLowerCase() == req.body.itemToSlot.toLowerCase()) { itemToSlotID = itemId; break; };
}
}
if (!profile.items[req.body.lockerItem]) return error.createError(
"errors.com.epicgames.fortnite.id_invalid",
`Item (id: '${req.body.lockerItem}') not found`,
[req.body.lockerItem], 16027, undefined, 400, res
);
if (profile.items[req.body.lockerItem].templateId.toLowerCase() != "cosmeticlocker:cosmeticlocker_athena") return error.createError(
"errors.com.epicgames.fortnite.id_invalid",
`lockerItem id is not a cosmeticlocker`,
["lockerItem"], 16027, undefined, 400, res
);
if (!profile.items[itemToSlotID] && req.body.itemToSlot) {
let item = req.body.itemToSlot;
if (!specialCosmetics.includes(item)) {
return error.createError(
"errors.com.epicgames.fortnite.id_invalid",
`Item (id: '${req.body.itemToSlot}') not found`,
[req.body.itemToSlot], 16027, undefined, 400, res
);
} else {
if (!item.startsWith(`Athena${req.body.category}:`)) return error.createError(
"errors.com.epicgames.fortnite.id_invalid",
`Cannot slot item of type ${item.split(":")[0]} in slot of category ${req.body.category}`,
[item.split(":")[0],req.body.category], 16027, undefined, 400, res
);
}
}
if (profile.items[itemToSlotID]) {
if (!profile.items[itemToSlotID].templateId.startsWith(`Athena${req.body.category}:`)) return error.createError(
"errors.com.epicgames.fortnite.id_invalid",
`Cannot slot item of type ${profile.items[itemToSlotID].templateId.split(":")[0]} in slot of category ${req.body.category}`,
[profile.items[itemToSlotID].templateId.split(":")[0],req.body.category], 16027, undefined, 400, res
);
let Variants = req.body.variantUpdates;
if (Array.isArray(Variants)) {
for (let i in Variants) {
if (typeof Variants[i] != "object") continue;
if (!Variants[i].channel) continue;
if (!Variants[i].active) continue;
let index = profile.items[itemToSlotID].attributes.variants.findIndex(x => x.channel == Variants[i].channel);
if (index == -1) continue;
if (!profile.items[itemToSlotID].attributes.variants[index].owned.includes(Variants[i].active)) continue;
profile.items[itemToSlotID].attributes.variants[index].active = Variants[i].active;
}
ApplyProfileChanges.push({
"changeType": "itemAttrChanged",
"itemId": itemToSlotID,
"attributeName": "variants",
"attributeValue": profile.items[itemToSlotID].attributes.variants
});
}
}
switch (req.body.category) {
case "Dance":
if (!profile.items[req.body.lockerItem].attributes.locker_slots_data.slots[req.body.category]) break;
if (req.body.slotIndex >= 0 && req.body.slotIndex <= 5) {
profile.items[req.body.lockerItem].attributes.locker_slots_data.slots.Dance.items[req.body.slotIndex] = req.body.itemToSlot;
profile.stats.attributes.favorite_dance[req.body.slotIndex] = itemToSlotID || req.body.itemToSlot;
ApplyProfileChanges.push({
"changeType": "itemAttrChanged",
"itemId": req.body.lockerItem,
"attributeName": "locker_slots_data",
"attributeValue": profile.items[req.body.lockerItem].attributes.locker_slots_data
});
}
break;
case "ItemWrap":
if (!profile.items[req.body.lockerItem].attributes.locker_slots_data.slots[req.body.category]) break;
switch (true) {
case req.body.slotIndex >= 0 && req.body.slotIndex <= 7:
profile.items[req.body.lockerItem].attributes.locker_slots_data.slots.ItemWrap.items[req.body.slotIndex] = req.body.itemToSlot;
profile.stats.attributes.favorite_itemwraps[req.body.slotIndex] = itemToSlotID || req.body.itemToSlot;
ApplyProfileChanges.push({
"changeType": "itemAttrChanged",
"itemId": req.body.lockerItem,
"attributeName": "locker_slots_data",
"attributeValue": profile.items[req.body.lockerItem].attributes.locker_slots_data
});
break;
case req.body.slotIndex == -1:
for (let i = 0; i < 7; i++) {
profile.items[req.body.lockerItem].attributes.locker_slots_data.slots.ItemWrap.items[i] = req.body.itemToSlot;
profile.stats.attributes.favorite_itemwraps[i] = itemToSlotID || req.body.itemToSlot;
}
ApplyProfileChanges.push({
"changeType": "itemAttrChanged",
"itemId": req.body.lockerItem,
"attributeName": "locker_slots_data",
"attributeValue": profile.items[req.body.lockerItem].attributes.locker_slots_data
});
break;
}
break;
default:
if (!profile.items[req.body.lockerItem].attributes.locker_slots_data.slots[req.body.category]) break;
if (req.body.category == "Pickaxe" || req.body.category == "Glider") {
if (!req.body.itemToSlot) return error.createError(
"errors.com.epicgames.fortnite.id_invalid",
`${req.body.category} can not be empty.`,
[req.body.category], 16027, undefined, 400, res
);
}
profile.items[req.body.lockerItem].attributes.locker_slots_data.slots[req.body.category].items = [req.body.itemToSlot];
profile.stats.attributes[(`favorite_${req.body.category}`).toLowerCase()] = itemToSlotID || req.body.itemToSlot;
ApplyProfileChanges.push({
"changeType": "itemAttrChanged",
"itemId": req.body.lockerItem,
"attributeName": "locker_slots_data",
"attributeValue": profile.items[req.body.lockerItem].attributes.locker_slots_data
});
break;
}
if (ApplyProfileChanges.length > 0) {
profile.rvn += 1;
profile.commandRevision += 1;
profile.updated = new Date().toISOString();
await profiles.updateOne({ $set: { [`profiles.${req.query.profileId}`]: profile } });
}
if (QueryRevision != ProfileRevisionCheck) {
ApplyProfileChanges = [{
"changeType": "fullProfileUpdate",
"profile": profile
}];
}
res.json({
profileRevision: profile.rvn || 0,
profileId: req.query.profileId,
profileChangesBaseRevision: BaseRevision,
profileChanges: ApplyProfileChanges,
profileCommandRevision: profile.commandRevision || 0,
serverTime: new Date().toISOString(),
responseVersion: 1
});
});
app.post("/fortnite/api/game/v2/profile/*/client/:operation", verifyToken, async (req, res) => {
const profiles = await Profile.findOne({ accountId: req.user.accountId });
if (!await profileManager.validateProfile(req.query.profileId, profiles)) return error.createError(
"errors.com.epicgames.modules.profiles.operation_forbidden",
`Unable to find template configuration for profile ${req.query.profileId}`,
[req.query.profileId], 12813, undefined, 403, res
);
let profile = profiles.profiles[req.query.profileId];
if (profile.rvn == profile.commandRevision) {
profile.rvn += 1;
if (req.query.profileId == "athena") {
if (!profile.stats.attributes.last_applied_loadout) profile.stats.attributes.last_applied_loadout = profile.stats.attributes.loadouts[0];
}
await profiles.updateOne({ $set: { [`profiles.${req.query.profileId}`]: profile } });
}
const memory = functions.GetVersionInfo(req);
if (req.query.profileId == "athena") profile.stats.attributes.season_num = memory.season;
let MultiUpdate = [];
if ((req.query.profileId == "common_core") && global.giftReceived[req.user.accountId]) {
global.giftReceived[req.user.accountId] = false;
let athena = profiles.profiles["athena"];
MultiUpdate = [{
"profileRevision": athena.rvn || 0,
"profileId": "athena",
"profileChangesBaseRevision": athena.rvn || 0,
"profileChanges": [{
"changeType": "fullProfileUpdate",
"profile": athena
}],
"profileCommandRevision": athena.commandRevision || 0,
}];
}
let ApplyProfileChanges = [];
let BaseRevision = profile.rvn;
let ProfileRevisionCheck = (memory.build >= 12.20) ? profile.commandRevision : profile.rvn;
let QueryRevision = req.query.rvn || -1;
switch (req.params.operation) {
case "QueryProfile": break;
case "ClientQuestLogin": break;
case "RefreshExpeditions": break;
case "GetMcpTimeForLogin": break;
case "IncrementNamedCounterStat": break;
case "SetHardcoreModifier": break;
case "SetMtxPlatform": break;
case "BulkEquipBattleRoyaleCustomization": break;
default:
error.createError(
"errors.com.epicgames.fortnite.operation_not_found",
`Operation ${req.params.operation} not valid`,
[req.params.operation], 16035, undefined, 404, res
);
return;
}
if (QueryRevision != ProfileRevisionCheck) {
ApplyProfileChanges = [{
"changeType": "fullProfileUpdate",
"profile": profile
}];
}
res.json({
profileRevision: profile.rvn || 0,
profileId: req.query.profileId,
profileChangesBaseRevision: BaseRevision,
profileChanges: ApplyProfileChanges,
profileCommandRevision: profile.commandRevision || 0,
serverTime: new Date().toISOString(),
multiUpdate: MultiUpdate,
responseVersion: 1
});
});
app.post("/fortnite/api/game/v2/profile/:accountId/dedicated_server/:operation", async (req, res) => {
const profiles = await Profile.findOne({ accountId: req.params.accountId }).lean();
if (!profiles) return res.status(404).json({});
if (!await profileManager.validateProfile(req.query.profileId, profiles)) return error.createError(
"errors.com.epicgames.modules.profiles.operation_forbidden",
`Unable to find template configuration for profile ${req.query.profileId}`,
[req.query.profileId], 12813, undefined, 403, res
);
let profile = profiles.profiles[req.query.profileId];
let ApplyProfileChanges = [];
let BaseRevision = profile.rvn;
let QueryRevision = req.query.rvn || -1;
if (QueryRevision != BaseRevision) {
ApplyProfileChanges = [{
"changeType": "fullProfileUpdate",
"profile": profile
}];
}
res.json({
profileRevision: profile.rvn || 0,
profileId: req.query.profileId,
profileChangesBaseRevision: BaseRevision,
profileChanges: ApplyProfileChanges,
profileCommandRevision: profile.commandRevision || 0,
serverTime: new Date().toISOString(),
responseVersion: 1
});
});
function checkFields(fields, body) {
let missingFields = { fields: [] };
fields.forEach(field => {
if (!body[field]) missingFields.fields.push(field);
});
return missingFields;
}
function ValidationError(field, type, res) {
return error.createError(
"errors.com.epicgames.validation.validation_failed",
`Validation Failed. '${field}' is not ${type}.`,
[field], 1040, undefined, 400, res
);
}
function checkIfDuplicateExists(arr) {
return new Set(arr).size !== arr.length
}
module.exports = app;