XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFESpaceNinjaServer

A simple server for a small space ninja game

公开
关注 0 Fork 0 Star 0
返回提交历史

XFEstudio/XFESpaceNinjaServer

feat: handle guild profile requests for old versions (#3293)

Closes #3291 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/3293 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>

267a2708
Sainan <63328889+Sainan@users.noreply.github.com>
提交于

代码差异

2 个文件 +143 -122
Modified src/controllers/dynamic/getProfileViewingDataController.ts +128 -119
@@ -5,7 +5,6 @@ import type { TInventoryDatabaseDocument } from "../../models/inventoryModels/in
5 5 import { Inventory } from "../../models/inventoryModels/inventoryModel.ts";
6 6 import { Loadout } from "../../models/inventoryModels/loadoutModel.ts";
7 7 import { Account } from "../../models/loginModel.ts";
8 import type { TStatsDatabaseDocument } from "../../models/statsModel.ts";
9 8 import { Stats } from "../../models/statsModel.ts";
10 9 import { allDailyAffiliationKeys } from "../../services/inventoryService.ts";
11 10 import type { IMongoDate, IOid } from "../../types/commonTypes.ts";
@@ -25,12 +24,54 @@ import { getJSONfromString } from "../../helpers/stringHelpers.ts";
25 24 import { ExportDojoRecipes } from "warframe-public-export-plus";
26 25 import type { IStatsClient } from "../../types/statTypes.ts";
27 26 import { toStoreItem } from "../../services/itemDataService.ts";
28 import type { FlattenMaps } from "mongoose";
29 27 import type { IEquipmentClient } from "../../types/equipmentTypes.ts";
30 28 import type { ILoadoutConfigClient } from "../../types/saveLoadoutTypes.ts";
31 29 import { skinLookupTable } from "../../helpers/skinLookupTable.ts";
30 import type { ITechProjectClient } from "../../types/guildTypes.ts";
32 31
33 const getProfileViewingDataByPlayerIdImpl = async (playerId: string): Promise<IProfileViewingData | undefined> => {
32 export const getProfileViewingDataGetController: RequestHandler = async (req, res) => {
33 if (req.query.playerId) {
34 const data = await getProfileViewingDataByPlayerId(req.query.playerId as string);
35 if (data) {
36 res.json(data);
37 } else {
38 res.status(409).send("Could not find requested account");
39 }
40 } else if (req.query.guildId) {
41 const data = await getProfileViewingDataByGuildId(req.query.guildId as string);
42 if (data) {
43 res.json(data);
44 } else {
45 res.status(409).send("Could not find guild");
46 }
47 } else {
48 res.sendStatus(400);
49 }
50 };
51
52 // For old versions, this was an authenticated POST request.
53 type IGetProfileViewingDataRequest = { AccountId: string } | { GuildId: string };
54 export const getProfileViewingDataPostController: RequestHandler = async (req, res) => {
55 const payload = getJSONfromString<IGetProfileViewingDataRequest>(String(req.body));
56 if ("GuildId" in payload) {
57 const data = await getProfileViewingDataByGuildId(payload.GuildId);
58 if (data) {
59 res.json(data);
60 } else {
61 res.status(409).send("Could not find guild");
62 }
63 } else {
64 const playerId = req.query.playerId as string; // companion app sends a POST request should be handled like a GET request
65 const data = await getProfileViewingDataByPlayerId(playerId ? playerId : payload.AccountId);
66 if (data) {
67 res.json(data);
68 } else {
69 res.status(409).send("Could not find requested account");
70 }
71 }
72 };
73
74 export const getProfileViewingDataByPlayerId = async (playerId: string): Promise<IProfileViewingData | undefined> => {
34 75 const account = await Account.findById(playerId, "DisplayName");
35 76 if (!account) {
36 77 return;
@@ -77,148 +118,116 @@ const getProfileViewingDataByPlayerIdImpl = async (playerId: string): Promise<IP
77 118 result[key] = inventory[key];
78 119 }
79 120
80 const stats = (await Stats.findOne({ accountOwnerId: account._id }))!.toJSON<Partial<TStatsDatabaseDocument>>();
81 delete stats._id;
82 delete stats.__v;
83 delete stats.accountOwnerId;
84
85 121 return {
86 122 Results: [result],
87 123 TechProjects: [],
88 124 XpComponents: [],
89 125 //XpCacheExpiryDate, some IMongoDate in the future, no clue what it's for
90 Stats: stats
126 Stats: (await Stats.findOne({ accountOwnerId: account._id }))!.toJSON() as IStatsClient
91 127 };
92 128 };
93 129
94 export const getProfileViewingDataGetController: RequestHandler = async (req, res) => {
95 if (req.query.playerId) {
96 const data = await getProfileViewingDataByPlayerIdImpl(req.query.playerId as string);
97 if (data) {
98 res.json(data);
99 } else {
100 res.status(409).send("Could not find requested account");
101 }
102 } else if (req.query.guildId) {
103 const guild = await Guild.findById(
104 req.query.guildId as string,
105 "Name Tier XP Class Emblem TechProjects ClaimedXP"
106 );
107 if (!guild) {
108 res.status(409).send("Could not find guild");
109 return;
110 }
111 const members = await GuildMember.find({ guildId: guild._id, status: 0 });
112 const results: IPlayerProfileViewingDataResult[] = [];
113 for (let i = 0; i != Math.min(4, members.length); ++i) {
114 const member = members[i];
115 const [account, inventory] = await Promise.all([
116 Account.findById(member.accountId, "DisplayName"),
117 Inventory.findOne(
118 { accountOwnerId: member.accountId },
119 "DisplayName PlayerLevel XPInfo LoadOutPresets CurrentLoadOutIds WeaponSkins Suits Pistols LongGuns Melee"
120 )
121 ]);
122 const result: IPlayerProfileViewingDataResult = {
123 AccountId: toOid(account!._id),
124 DisplayName: account!.DisplayName,
125 PlayerLevel: inventory!.PlayerLevel,
126 LoadOutInventory: {
127 WeaponSkins: [],
128 XPInfo: inventory!.XPInfo
129 }
130 };
131 await populateLoadout(inventory!, result);
132 results.push(result);
133 }
134 populateGuild(guild, results[0]);
130 export const getProfileViewingDataByGuildId = async (guildId: string): Promise<IProfileViewingData | undefined> => {
131 const guild = await Guild.findById(guildId, "Name Tier XP Class Emblem TechProjects ClaimedXP");
132 if (!guild) {
133 return;
134 }
135 const members = await GuildMember.find({ guildId: guild._id, status: 0 });
136 const results: IPlayerProfileViewingDataResult[] = [];
137 for (let i = 0; i != Math.min(4, members.length); ++i) {
138 const member = members[i];
139 const [account, inventory] = await Promise.all([
140 Account.findById(member.accountId, "DisplayName"),
141 Inventory.findOne(
142 { accountOwnerId: member.accountId },
143 "DisplayName PlayerLevel XPInfo LoadOutPresets CurrentLoadOutIds WeaponSkins Suits Pistols LongGuns Melee"
144 )
145 ]);
146 const result: IPlayerProfileViewingDataResult = {
147 AccountId: toOid(account!._id),
148 DisplayName: account!.DisplayName,
149 PlayerLevel: inventory!.PlayerLevel,
150 LoadOutInventory: {
151 WeaponSkins: [],
152 XPInfo: inventory!.XPInfo
153 }
154 };
155 await populateLoadout(inventory!, result);
156 results.push(result);
157 }
158 populateGuild(guild, results[0]);
135 159
136 const combinedStats: IStatsClient = {};
137 const statsArr = await Stats.find({ accountOwnerId: { $in: members.map(x => x.accountId) } }).lean(); // need this as POJO so Object.entries works as expected
138 for (const stats of statsArr) {
139 for (const [key, value] of Object.entries(stats)) {
140 if (typeof value == "number" && key != "__v") {
141 (combinedStats[key as keyof IStatsClient] as number | undefined) ??= 0;
142 (combinedStats[key as keyof IStatsClient] as number) += value;
143 }
160 const combinedStats: IStatsClient = {};
161 const statsArr = await Stats.find({ accountOwnerId: { $in: members.map(x => x.accountId) } }).lean(); // need this as POJO so Object.entries works as expected
162 for (const stats of statsArr) {
163 for (const [key, value] of Object.entries(stats)) {
164 if (typeof value == "number" && key != "__v") {
165 (combinedStats[key as keyof IStatsClient] as number | undefined) ??= 0;
166 (combinedStats[key as keyof IStatsClient] as number) += value;
144 167 }
145 for (const arrayName of ["Weapons", "Enemies", "Scans", "Missions", "PVP"] as const) {
146 if (stats[arrayName]) {
147 combinedStats[arrayName] ??= [];
148 for (const entry of stats[arrayName]) {
149 const combinedEntry = combinedStats[arrayName].find(x => x.type == entry.type);
150 if (combinedEntry) {
151 for (const [key, value] of Object.entries(entry)) {
152 if (typeof value == "number") {
153 (combinedEntry[key as keyof typeof combinedEntry] as unknown as
154 | number
155 | undefined) ??= 0;
156 (combinedEntry[key as keyof typeof combinedEntry] as unknown as number) += value;
157 }
168 }
169 for (const arrayName of ["Weapons", "Enemies", "Scans", "Missions", "PVP"] as const) {
170 if (stats[arrayName]) {
171 combinedStats[arrayName] ??= [];
172 for (const entry of stats[arrayName]) {
173 const combinedEntry = combinedStats[arrayName].find(x => x.type == entry.type);
174 if (combinedEntry) {
175 for (const [key, value] of Object.entries(entry)) {
176 if (typeof value == "number") {
177 (combinedEntry[key as keyof typeof combinedEntry] as unknown as number | undefined) ??=
178 0;
179 (combinedEntry[key as keyof typeof combinedEntry] as unknown as number) += value;
158 180 }
159 } else {
160 // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
161 combinedStats[arrayName].push(entry as any);
162 181 }
182 } else {
183 // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
184 combinedStats[arrayName].push(entry as any);
163 185 }
164 186 }
165 187 }
166 188 }
189 }
167 190
168 const xpComponents: IXPComponentClient[] = [];
169 if (guild.ClaimedXP) {
170 for (const componentName of guild.ClaimedXP) {
171 if (componentName.endsWith(".level")) {
172 const [key] = Object.entries(ExportDojoRecipes.rooms).find(
173 ([_key, value]) => value.resultType == componentName
174 )!;
175 xpComponents.push({
176 StoreTypeName: toStoreItem(key)
177 });
178 } else {
179 const [key] = Object.entries(ExportDojoRecipes.decos).find(
180 ([_key, value]) => value.resultType == componentName
181 )!;
182 xpComponents.push({
183 StoreTypeName: toStoreItem(key)
184 });
185 }
191 const xpComponents: IXPComponentClient[] = [];
192 if (guild.ClaimedXP) {
193 for (const componentName of guild.ClaimedXP) {
194 if (componentName.endsWith(".level")) {
195 const [key] = Object.entries(ExportDojoRecipes.rooms).find(
196 ([_key, value]) => value.resultType == componentName
197 )!;
198 xpComponents.push({
199 StoreTypeName: toStoreItem(key)
200 });
201 } else {
202 const [key] = Object.entries(ExportDojoRecipes.decos).find(
203 ([_key, value]) => value.resultType == componentName
204 )!;
205 xpComponents.push({
206 StoreTypeName: toStoreItem(key)
207 });
186 208 }
187 209 }
188
189 res.json({
190 Results: results,
191 TechProjects: guild.TechProjects,
192 XpComponents: xpComponents,
193 //XpCacheExpiryDate, some IMongoDate in the future, no clue what it's for
194 Stats: combinedStats
195 });
196 } else {
197 res.sendStatus(400);
198 210 }
199 };
200 211
201 // For old versions, this was an authenticated POST request.
202 interface IGetProfileViewingDataRequest {
203 AccountId: string;
204 }
205 export const getProfileViewingDataPostController: RequestHandler = async (req, res) => {
206 const payload = getJSONfromString<IGetProfileViewingDataRequest>(String(req.body));
207 const playerId = req.query.playerId as string; // companion app sends a POST request should be handled like a GET request
208 const data = await getProfileViewingDataByPlayerIdImpl(playerId ? playerId : payload.AccountId);
209 if (data) {
210 res.json(data);
211 } else {
212 res.status(409).send("Could not find requested account");
213 }
212 return {
213 Results: results,
214 TechProjects:
215 guild.TechProjects?.map(x => ({
216 ...x,
217 CompletionDate: x.CompletionDate ? toMongoDate(x.CompletionDate) : undefined
218 })) ?? [],
219 XpComponents: xpComponents,
220 //XpCacheExpiryDate, some IMongoDate in the future, no clue what it's for
221 Stats: combinedStats
222 };
214 223 };
215 224
216 225 interface IProfileViewingData {
217 226 Results: IPlayerProfileViewingDataResult[];
218 TechProjects: [];
219 XpComponents: [];
227 TechProjects: ITechProjectClient[];
228 XpComponents: IXPComponentClient[];
220 229 //XpCacheExpiryDate, some IMongoDate in the future, no clue what it's for
221 Stats: FlattenMaps<Partial<TStatsDatabaseDocument>>;
230 Stats: IStatsClient;
222 231 }
223 232
224 233 interface IPlayerProfileViewingDataResult extends Partial<IDailyAffiliations>, IInventoryAccolades {
Modified src/controllers/stats/viewController.ts +15 -3
@@ -2,11 +2,23 @@ import type { RequestHandler } from "express";
2 2 import { getInventory } from "../../services/inventoryService.ts";
3 3 import { getStats } from "../../services/statsService.ts";
4 4 import type { IStatsClient } from "../../types/statTypes.ts";
5 import { getProfileViewingDataByGuildId } from "../dynamic/getProfileViewingDataController.ts";
5 6
6 7 const viewController: RequestHandler = async (req, res) => {
7 const accountId = String(req.query.id ?? req.query.lookupId);
8 const inventory = await getInventory(accountId, "XPInfo");
9 const playerStats = await getStats(accountId);
8 const lookupId = String(req.query.id ?? req.query.lookupId);
9
10 if (req.query.guild == "1") {
11 const data = await getProfileViewingDataByGuildId(lookupId);
12 if (data) {
13 res.json(data.Stats);
14 } else {
15 res.status(409).send("Could not find guild");
16 }
17 return;
18 }
19
20 const inventory = await getInventory(lookupId, "XPInfo");
21 const playerStats = await getStats(lookupId);
10 22
11 23 const responseJson = playerStats.toJSON<IStatsClient>();
12 24 responseJson.Weapons ??= [];