erlc-v2 1.1.2 → 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 +40 -11
- package/index.d.ts +69 -19
- package/package.json +1 -1
- package/src/Client.js +71 -25
- package/src/api/LocalApiServer.js +41 -12
- package/src/util/normalize.js +62 -1
package/README.md
CHANGED
|
@@ -185,9 +185,13 @@ Core:
|
|
|
185
185
|
|
|
186
186
|
Convenience methods:
|
|
187
187
|
|
|
188
|
-
- `await client.players.list()`
|
|
189
|
-
- `await client.
|
|
190
|
-
- `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?)`
|
|
191
195
|
- `await client.staff.list()`
|
|
192
196
|
- `await client.logs.kills()`
|
|
193
197
|
- `await client.logs.joins()`
|
|
@@ -217,11 +221,35 @@ Convenience methods:
|
|
|
217
221
|
|
|
218
222
|
- `bypassCache?: boolean`
|
|
219
223
|
- `cacheTtlMs?: number`
|
|
220
|
-
- `dedupe?: boolean`
|
|
221
|
-
|
|
222
|
-
##
|
|
223
|
-
|
|
224
|
-
|
|
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:
|
|
225
253
|
|
|
226
254
|
```js
|
|
227
255
|
const car = await client.vehicles.findByPlate("LINCOLN7");
|
|
@@ -333,9 +361,10 @@ Built-in routes:
|
|
|
333
361
|
|
|
334
362
|
- `GET /erlc`
|
|
335
363
|
- `GET /erlc/health`
|
|
336
|
-
- `GET /erlc/server`
|
|
337
|
-
- `GET /erlc/players`
|
|
338
|
-
- `GET /erlc/
|
|
364
|
+
- `GET /erlc/server`
|
|
365
|
+
- `GET /erlc/players`
|
|
366
|
+
- `GET /erlc/players/:userIdOrName`
|
|
367
|
+
- `GET /erlc/vehicles`
|
|
339
368
|
- `GET /erlc/vehicles/:plate`
|
|
340
369
|
- `GET /erlc/emergency-calls`
|
|
341
370
|
- `POST /erlc/command`
|
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,11 +5,11 @@ 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
14
|
const BASE_URL = "https://api.erlc.gg";
|
|
15
15
|
const BLOCKED_COMMANDS = new Set([
|
|
@@ -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"),
|
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
|
};
|