erlc-v2 1.1.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -17
- package/index.d.ts +69 -19
- package/package.json +1 -1
- package/src/Client.js +72 -26
- package/src/api/LocalApiServer.js +41 -12
- package/src/map/renderPlayerMap.js +2 -2
- package/src/util/normalize.js +62 -1
package/README.md
CHANGED
|
@@ -2,11 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
JavaScript client for the ER:LC API v2.
|
|
4
4
|
|
|
5
|
-
Built for Node 18+.
|
|
6
|
-
|
|
7
|
-
##
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
Built for Node 18+.
|
|
6
|
+
|
|
7
|
+
## Important Upgrade Notice
|
|
8
|
+
|
|
9
|
+
Update to the newest version as soon as possible:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install erlc-v2@latest
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Older releases may still call the deprecated `api.policeroleplay.community` host. The ER:LC API is moving to `https://api.erlc.gg`, and requests to the old host may start failing on May 11, 2026.
|
|
16
|
+
|
|
17
|
+
## New Features
|
|
18
|
+
|
|
19
|
+
- `client.commands.execute()` now uses `/v2/server/command`
|
|
10
20
|
- emergency calls are supported
|
|
11
21
|
- vehicle lookup helpers are built in
|
|
12
22
|
- you can start a small local API with `api: { port: 3001 }`
|
|
@@ -175,9 +185,13 @@ Core:
|
|
|
175
185
|
|
|
176
186
|
Convenience methods:
|
|
177
187
|
|
|
178
|
-
- `await client.players.list()`
|
|
179
|
-
- `await client.
|
|
180
|
-
- `await client.
|
|
188
|
+
- `await client.players.list()`
|
|
189
|
+
- `await client.players.get(userIdOrName)`
|
|
190
|
+
- `await client.players.find(userIdOrName)`
|
|
191
|
+
- `await client.players.findById(userId)`
|
|
192
|
+
- `await client.players.findByName(username)`
|
|
193
|
+
- `await client.map.render(options?)`
|
|
194
|
+
- `await client.map.renderUser(userId, options?)`
|
|
181
195
|
- `await client.staff.list()`
|
|
182
196
|
- `await client.logs.kills()`
|
|
183
197
|
- `await client.logs.joins()`
|
|
@@ -207,11 +221,35 @@ Convenience methods:
|
|
|
207
221
|
|
|
208
222
|
- `bypassCache?: boolean`
|
|
209
223
|
- `cacheTtlMs?: number`
|
|
210
|
-
- `dedupe?: boolean`
|
|
211
|
-
|
|
212
|
-
##
|
|
213
|
-
|
|
214
|
-
|
|
224
|
+
- `dedupe?: boolean`
|
|
225
|
+
|
|
226
|
+
## Player Lookup
|
|
227
|
+
|
|
228
|
+
`client.players.list()` returns the ER:LC player objects with the original API fields still on them. The wrapper also adds easier aliases like `userId`, `username`, `team`, `wantedStars`, and `location`.
|
|
229
|
+
|
|
230
|
+
```js
|
|
231
|
+
const player = await client.players.get(123456789);
|
|
232
|
+
|
|
233
|
+
if (player?.location) {
|
|
234
|
+
console.log(player.username);
|
|
235
|
+
console.log(player.location.postalCode);
|
|
236
|
+
console.log(player.location.streetName);
|
|
237
|
+
console.log(player.location.buildingNumber);
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
The original response is still there too:
|
|
242
|
+
|
|
243
|
+
```js
|
|
244
|
+
console.log(player.Player);
|
|
245
|
+
console.log(player.Location?.PostalCode);
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Player lookup uses the same cached `Players=true` server request as `players.list()`, so calling `get()`, `findById()`, or `findByName()` right after each other will not keep refetching from `erlc.gg` while the cache entry is still fresh.
|
|
249
|
+
|
|
250
|
+
## Vehicle Search Helpers
|
|
251
|
+
|
|
252
|
+
Find one exact plate:
|
|
215
253
|
|
|
216
254
|
```js
|
|
217
255
|
const car = await client.vehicles.findByPlate("LINCOLN7");
|
|
@@ -323,9 +361,10 @@ Built-in routes:
|
|
|
323
361
|
|
|
324
362
|
- `GET /erlc`
|
|
325
363
|
- `GET /erlc/health`
|
|
326
|
-
- `GET /erlc/server`
|
|
327
|
-
- `GET /erlc/players`
|
|
328
|
-
- `GET /erlc/
|
|
364
|
+
- `GET /erlc/server`
|
|
365
|
+
- `GET /erlc/players`
|
|
366
|
+
- `GET /erlc/players/:userIdOrName`
|
|
367
|
+
- `GET /erlc/vehicles`
|
|
329
368
|
- `GET /erlc/vehicles/:plate`
|
|
330
369
|
- `GET /erlc/emergency-calls`
|
|
331
370
|
- `POST /erlc/command`
|
|
@@ -597,6 +636,6 @@ Repeated `403` responses can also trigger disconnect (`reason: "unauthorized"`).
|
|
|
597
636
|
|
|
598
637
|
## Notes
|
|
599
638
|
|
|
600
|
-
- API base URL: `https://api.
|
|
639
|
+
- API base URL: `https://api.erlc.gg`
|
|
601
640
|
- `server-key` is required for requests
|
|
602
641
|
- `Authorization` is optional (`globalKey`)
|
package/index.d.ts
CHANGED
|
@@ -107,20 +107,54 @@ export interface CommandExecuteResult {
|
|
|
107
107
|
};
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
-
export interface VehicleData {
|
|
111
|
-
Name?: string;
|
|
112
|
-
Owner?: string;
|
|
113
|
-
Plate?: string;
|
|
110
|
+
export interface VehicleData {
|
|
111
|
+
Name?: string;
|
|
112
|
+
Owner?: string;
|
|
113
|
+
Plate?: string;
|
|
114
114
|
Texture?: string | null;
|
|
115
115
|
ColorHex?: string;
|
|
116
116
|
ColorName?: string;
|
|
117
|
-
[key: string]: any;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
export interface
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
117
|
+
[key: string]: any;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export interface PlayerLocationData {
|
|
121
|
+
LocationX?: number;
|
|
122
|
+
LocationZ?: number;
|
|
123
|
+
PostalCode?: string;
|
|
124
|
+
StreetName?: string;
|
|
125
|
+
BuildingNumber?: string;
|
|
126
|
+
locationX: number | null;
|
|
127
|
+
locationZ: number | null;
|
|
128
|
+
postalCode: string | null;
|
|
129
|
+
streetName: string | null;
|
|
130
|
+
buildingNumber: string | null;
|
|
131
|
+
raw: Record<string, any>;
|
|
132
|
+
[key: string]: any;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export interface PlayerData {
|
|
136
|
+
Team?: string;
|
|
137
|
+
Player?: string;
|
|
138
|
+
Callsign?: string | null;
|
|
139
|
+
Location?: Record<string, any>;
|
|
140
|
+
Permission?: string;
|
|
141
|
+
WantedStars?: number;
|
|
142
|
+
name: string | null;
|
|
143
|
+
username: string | null;
|
|
144
|
+
userId: number | null;
|
|
145
|
+
team: string | null;
|
|
146
|
+
callsign: string | null;
|
|
147
|
+
permission: string | null;
|
|
148
|
+
wantedStars: number | null;
|
|
149
|
+
location: PlayerLocationData | null;
|
|
150
|
+
raw: Record<string, any>;
|
|
151
|
+
[key: string]: any;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export interface EmergencyCallData {
|
|
155
|
+
Team?: string;
|
|
156
|
+
Caller?: number;
|
|
157
|
+
Players?: number[];
|
|
124
158
|
Position?: number[];
|
|
125
159
|
StartedAt?: number;
|
|
126
160
|
CallNumber?: number;
|
|
@@ -220,10 +254,10 @@ export interface MapMarkerOptions {
|
|
|
220
254
|
shadow?: boolean;
|
|
221
255
|
}
|
|
222
256
|
|
|
223
|
-
export interface MapRenderOptions {
|
|
224
|
-
userId?: number | string;
|
|
225
|
-
userIds?: Array<number | string>;
|
|
226
|
-
players?: any[];
|
|
257
|
+
export interface MapRenderOptions {
|
|
258
|
+
userId?: number | string;
|
|
259
|
+
userIds?: Array<number | string>;
|
|
260
|
+
players?: PlayerData[] | any[];
|
|
227
261
|
mapUrl?: string;
|
|
228
262
|
season?: string;
|
|
229
263
|
type?: string;
|
|
@@ -277,7 +311,7 @@ export interface ServerResponse {
|
|
|
277
311
|
joinKey: string | null;
|
|
278
312
|
accVerifiedReq: string | null;
|
|
279
313
|
teamBalance: boolean | null;
|
|
280
|
-
players:
|
|
314
|
+
players: PlayerData[];
|
|
281
315
|
staff: any;
|
|
282
316
|
joinLogs: any[];
|
|
283
317
|
queue: any[];
|
|
@@ -339,9 +373,25 @@ export class Client extends EventEmitter {
|
|
|
339
373
|
requestOptions?: RequestOptions,
|
|
340
374
|
) => Promise<ServerResponse>;
|
|
341
375
|
};
|
|
342
|
-
players: {
|
|
343
|
-
list: (requestOptions?: RequestOptions) => Promise<
|
|
344
|
-
|
|
376
|
+
players: {
|
|
377
|
+
list: (requestOptions?: RequestOptions) => Promise<PlayerData[]>;
|
|
378
|
+
get: (
|
|
379
|
+
user: number | string,
|
|
380
|
+
requestOptions?: RequestOptions,
|
|
381
|
+
) => Promise<PlayerData | null>;
|
|
382
|
+
find: (
|
|
383
|
+
user: number | string,
|
|
384
|
+
requestOptions?: RequestOptions,
|
|
385
|
+
) => Promise<PlayerData | null>;
|
|
386
|
+
findById: (
|
|
387
|
+
userId: number | string,
|
|
388
|
+
requestOptions?: RequestOptions,
|
|
389
|
+
) => Promise<PlayerData | null>;
|
|
390
|
+
findByName: (
|
|
391
|
+
name: string,
|
|
392
|
+
requestOptions?: RequestOptions,
|
|
393
|
+
) => Promise<PlayerData | null>;
|
|
394
|
+
};
|
|
345
395
|
map: {
|
|
346
396
|
render: (
|
|
347
397
|
options?: MapRenderOptions,
|
package/package.json
CHANGED
package/src/Client.js
CHANGED
|
@@ -5,13 +5,13 @@ const Poller = require("./events/Poller");
|
|
|
5
5
|
const LocalApiServer = require("./api/LocalApiServer");
|
|
6
6
|
const { DEFAULT_OPTIONS, QUERY_FLAG_MAP } = require("./util/constants");
|
|
7
7
|
const { mergeOptions } = require("./util/options");
|
|
8
|
-
const { createLogger } = require("./util/logger");
|
|
9
|
-
const { normalizeServerResponse } = require("./util/normalize");
|
|
10
|
-
const { renderPlayerMap } = require("./map/renderPlayerMap");
|
|
11
|
-
const { searchVehicles, findVehicleByPlate } = require("./util/vehicleSearch");
|
|
12
|
-
const { ERLCError } = require("./errors");
|
|
8
|
+
const { createLogger } = require("./util/logger");
|
|
9
|
+
const { normalizeServerResponse } = require("./util/normalize");
|
|
10
|
+
const { renderPlayerMap } = require("./map/renderPlayerMap");
|
|
11
|
+
const { searchVehicles, findVehicleByPlate } = require("./util/vehicleSearch");
|
|
12
|
+
const { ERLCError } = require("./errors");
|
|
13
13
|
|
|
14
|
-
const BASE_URL = "https://api.
|
|
14
|
+
const BASE_URL = "https://api.erlc.gg";
|
|
15
15
|
const BLOCKED_COMMANDS = new Set([
|
|
16
16
|
":view",
|
|
17
17
|
":to",
|
|
@@ -61,15 +61,42 @@ function getCommandKeyword(command) {
|
|
|
61
61
|
return raw.split(/\s+/)[0].toLowerCase();
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
function isPromiseLike(value) {
|
|
65
|
-
return (
|
|
66
|
-
value !== null &&
|
|
67
|
-
typeof value === "object" &&
|
|
68
|
-
typeof value.then === "function"
|
|
69
|
-
);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
|
|
64
|
+
function isPromiseLike(value) {
|
|
65
|
+
return (
|
|
66
|
+
value !== null &&
|
|
67
|
+
typeof value === "object" &&
|
|
68
|
+
typeof value.then === "function"
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function low(value) {
|
|
73
|
+
return String(value ?? "").trim().toLowerCase();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function findPlayer(players, user, byName = false) {
|
|
77
|
+
if (!Array.isArray(players)) return null;
|
|
78
|
+
|
|
79
|
+
const text = low(user);
|
|
80
|
+
if (!text) return null;
|
|
81
|
+
|
|
82
|
+
const asId = Number(text);
|
|
83
|
+
const hasId = Number.isFinite(asId);
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
players.find((p) => {
|
|
87
|
+
if (!p || typeof p !== "object") return false;
|
|
88
|
+
if (!byName && hasId && Number(p.userId) === asId) return true;
|
|
89
|
+
|
|
90
|
+
const name = low(p.username ?? p.name);
|
|
91
|
+
const raw = low(p.Player);
|
|
92
|
+
if (byName) return name === text || raw === text;
|
|
93
|
+
|
|
94
|
+
return name === text || raw === text;
|
|
95
|
+
}) ?? null
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
class Client extends EventEmitter {
|
|
73
100
|
constructor(options = {}) {
|
|
74
101
|
super();
|
|
75
102
|
|
|
@@ -104,12 +131,15 @@ class Client extends EventEmitter {
|
|
|
104
131
|
this._fetchServer(flags, requestOptions),
|
|
105
132
|
};
|
|
106
133
|
|
|
107
|
-
this.players = {
|
|
108
|
-
list: (requestOptions = {}) =>
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
134
|
+
this.players = {
|
|
135
|
+
list: (requestOptions = {}) => this._listPlayers(requestOptions),
|
|
136
|
+
get: (user, requestOptions = {}) => this._getPlayer(user, requestOptions),
|
|
137
|
+
find: (user, requestOptions = {}) => this._getPlayer(user, requestOptions),
|
|
138
|
+
findById: (userId, requestOptions = {}) =>
|
|
139
|
+
this._getPlayer(userId, requestOptions),
|
|
140
|
+
findByName: (name, requestOptions = {}) =>
|
|
141
|
+
this._getPlayerByName(name, requestOptions),
|
|
142
|
+
};
|
|
113
143
|
|
|
114
144
|
this.map = {
|
|
115
145
|
render: (options = {}, requestOptions = {}) =>
|
|
@@ -340,7 +370,7 @@ class Client extends EventEmitter {
|
|
|
340
370
|
return query;
|
|
341
371
|
}
|
|
342
372
|
|
|
343
|
-
async _fetchServer(flags = {}, requestOptions = {}) {
|
|
373
|
+
async _fetchServer(flags = {}, requestOptions = {}) {
|
|
344
374
|
if (this.state.disconnected && this.state.disconnectError) {
|
|
345
375
|
throw this.state.disconnectError;
|
|
346
376
|
}
|
|
@@ -365,10 +395,26 @@ class Client extends EventEmitter {
|
|
|
365
395
|
bucket: response.bucket,
|
|
366
396
|
rateLimit: response.rateLimit,
|
|
367
397
|
};
|
|
368
|
-
return normalized;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
async
|
|
398
|
+
return normalized;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
async _listPlayers(requestOptions = {}) {
|
|
402
|
+
return this.server
|
|
403
|
+
.fetch({ players: true }, requestOptions)
|
|
404
|
+
.then((d) => d.players);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
async _getPlayer(user, requestOptions = {}) {
|
|
408
|
+
const players = await this._listPlayers(requestOptions);
|
|
409
|
+
return findPlayer(players, user);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
async _getPlayerByName(name, requestOptions = {}) {
|
|
413
|
+
const players = await this._listPlayers(requestOptions);
|
|
414
|
+
return findPlayer(players, name, true);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
async _executeCommand(command, requestOptions = {}) {
|
|
372
418
|
if (this.state.disconnected && this.state.disconnectError) {
|
|
373
419
|
throw this.state.disconnectError;
|
|
374
420
|
}
|
|
@@ -249,10 +249,10 @@ class LocalApiServer {
|
|
|
249
249
|
return;
|
|
250
250
|
}
|
|
251
251
|
|
|
252
|
-
if (req.method === "GET" && routePath === "/players") {
|
|
253
|
-
const players = await this.client.players.list({
|
|
254
|
-
bypassCache: parseBool(url.searchParams.get("bypassCache")),
|
|
255
|
-
});
|
|
252
|
+
if (req.method === "GET" && routePath === "/players") {
|
|
253
|
+
const players = await this.client.players.list({
|
|
254
|
+
bypassCache: parseBool(url.searchParams.get("bypassCache")),
|
|
255
|
+
});
|
|
256
256
|
this._recordRequest({
|
|
257
257
|
req,
|
|
258
258
|
url,
|
|
@@ -264,14 +264,43 @@ class LocalApiServer {
|
|
|
264
264
|
sendJson(res, 200, {
|
|
265
265
|
count: players.length,
|
|
266
266
|
players,
|
|
267
|
-
});
|
|
268
|
-
return;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
if (req.method === "GET" && routePath
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
|
|
267
|
+
});
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (req.method === "GET" && routePath.startsWith("/players/")) {
|
|
272
|
+
const user = decodeURIComponent(routePath.slice("/players/".length));
|
|
273
|
+
const player = await this.client.players.get(user, {
|
|
274
|
+
bypassCache: parseBool(url.searchParams.get("bypassCache")),
|
|
275
|
+
});
|
|
276
|
+
if (!player) {
|
|
277
|
+
this._recordRequest({
|
|
278
|
+
req,
|
|
279
|
+
url,
|
|
280
|
+
startedAt,
|
|
281
|
+
kind: "route",
|
|
282
|
+
status: 404,
|
|
283
|
+
note: `player=${user}`,
|
|
284
|
+
});
|
|
285
|
+
sendJson(res, 404, { message: "Player not found" });
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
this._recordRequest({
|
|
289
|
+
req,
|
|
290
|
+
url,
|
|
291
|
+
startedAt,
|
|
292
|
+
kind: "route",
|
|
293
|
+
status: 200,
|
|
294
|
+
note: `player=${user}`,
|
|
295
|
+
});
|
|
296
|
+
sendJson(res, 200, player);
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (req.method === "GET" && routePath === "/vehicles") {
|
|
301
|
+
const vehicles = await this.client.vehicles.list({
|
|
302
|
+
bypassCache: parseBool(url.searchParams.get("bypassCache")),
|
|
303
|
+
});
|
|
275
304
|
const filtered = searchVehicles(vehicles, {
|
|
276
305
|
query: url.searchParams.get("query") || url.searchParams.get("search"),
|
|
277
306
|
plate: url.searchParams.get("plate"),
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
const { ERLCError } = require("../errors");
|
|
2
2
|
|
|
3
3
|
const DEFAULT_MAP_URL =
|
|
4
|
-
"https://api.
|
|
4
|
+
"https://api.erlc.gg/maps/fall_blank.png";
|
|
5
5
|
const FIXED_MAP_SIZE = 3121;
|
|
6
|
-
const OFFICIAL_MAP_BASE_URL = "https://api.
|
|
6
|
+
const OFFICIAL_MAP_BASE_URL = "https://api.erlc.gg/maps";
|
|
7
7
|
const ROBLOX_HEADSHOT_URL = "https://thumbnails.roblox.com/v1/users/avatar";
|
|
8
8
|
const MAP_SEASON_ALIASES = {
|
|
9
9
|
fall: "fall",
|
package/src/util/normalize.js
CHANGED
|
@@ -1,3 +1,61 @@
|
|
|
1
|
+
function toUserId(value) {
|
|
2
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
3
|
+
if (typeof value !== "string") return null;
|
|
4
|
+
|
|
5
|
+
const text = value.trim();
|
|
6
|
+
if (!text) return null;
|
|
7
|
+
|
|
8
|
+
const idPart = text.includes(":") ? text.split(":").pop() : text;
|
|
9
|
+
const parsed = Number(idPart);
|
|
10
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function playerName(value) {
|
|
14
|
+
if (typeof value !== "string") return null;
|
|
15
|
+
const text = value.trim();
|
|
16
|
+
if (!text) return null;
|
|
17
|
+
return text.includes(":") ? text.slice(0, text.lastIndexOf(":")) : text;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function normalizeLocation(raw) {
|
|
21
|
+
if (!raw || typeof raw !== "object") return null;
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
...raw,
|
|
25
|
+
locationX: raw.LocationX ?? null,
|
|
26
|
+
locationZ: raw.LocationZ ?? null,
|
|
27
|
+
postalCode: raw.PostalCode ?? null,
|
|
28
|
+
streetName: raw.StreetName ?? null,
|
|
29
|
+
buildingNumber: raw.BuildingNumber ?? null,
|
|
30
|
+
raw,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function normalizePlayer(p = {}) {
|
|
35
|
+
const rawPlayer = p.Player ?? p.Name ?? null;
|
|
36
|
+
const location = normalizeLocation(p.Location);
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
...p,
|
|
40
|
+
name: playerName(rawPlayer),
|
|
41
|
+
username: playerName(rawPlayer),
|
|
42
|
+
userId: toUserId(rawPlayer),
|
|
43
|
+
team: p.Team ?? null,
|
|
44
|
+
callsign: p.Callsign ?? null,
|
|
45
|
+
permission: p.Permission ?? null,
|
|
46
|
+
wantedStars: p.WantedStars ?? null,
|
|
47
|
+
location,
|
|
48
|
+
raw: p,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function normalizePlayers(rawPlayers) {
|
|
53
|
+
if (!Array.isArray(rawPlayers)) return [];
|
|
54
|
+
return rawPlayers.map((p) =>
|
|
55
|
+
p && typeof p === "object" ? normalizePlayer(p) : p,
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
1
59
|
function normalizeServerResponse(raw = {}) {
|
|
2
60
|
return {
|
|
3
61
|
name: raw.Name ?? null,
|
|
@@ -8,7 +66,7 @@ function normalizeServerResponse(raw = {}) {
|
|
|
8
66
|
joinKey: raw.JoinKey ?? null,
|
|
9
67
|
accVerifiedReq: raw.AccVerifiedReq ?? null,
|
|
10
68
|
teamBalance: raw.TeamBalance ?? null,
|
|
11
|
-
players:
|
|
69
|
+
players: normalizePlayers(raw.Players),
|
|
12
70
|
staff: raw.Staff ?? { Admins: {}, Mods: {}, Helpers: {} },
|
|
13
71
|
joinLogs: Array.isArray(raw.JoinLogs) ? raw.JoinLogs : [],
|
|
14
72
|
queue: Array.isArray(raw.Queue) ? raw.Queue : [],
|
|
@@ -23,4 +81,7 @@ function normalizeServerResponse(raw = {}) {
|
|
|
23
81
|
|
|
24
82
|
module.exports = {
|
|
25
83
|
normalizeServerResponse,
|
|
84
|
+
normalizePlayer,
|
|
85
|
+
normalizePlayers,
|
|
86
|
+
toUserId,
|
|
26
87
|
};
|