@wumx-labs/noxaeapi-sdk 0.1.0 → 0.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/dist/index.d.ts CHANGED
@@ -33,6 +33,16 @@ declare class HttpEngine {
33
33
  request<T>(method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH", path: string, opts?: {
34
34
  body?: unknown;
35
35
  query?: Record<string, QueryValue>;
36
+ /**
37
+ * Encode `body` as `application/x-www-form-urlencoded` (Javalin's
38
+ * `ctx.formParam(...)`) instead of JSON. Most NoxAeApi endpoints
39
+ * expect form-urlencoded bodies — only the LuckPerms and NoxAuth
40
+ * routes use `ctx.bodyAsClass(...)` and need real JSON. Defaults to
41
+ * `false` (JSON) to preserve existing behavior; each module call
42
+ * site is responsible for passing `form: true` where the server
43
+ * actually expects it.
44
+ */
45
+ form?: boolean;
36
46
  }): Promise<T>;
37
47
  }
38
48
 
@@ -191,6 +201,12 @@ interface PlayerStats {
191
201
  blocksPlaced: number;
192
202
  blocksBroken: number;
193
203
  }
204
+ interface SkillInfo {
205
+ uuid: string;
206
+ /** Skill name -> level. */
207
+ skills: Record<string, number>;
208
+ powerLevel: number;
209
+ }
194
210
 
195
211
  declare class PlayersModule {
196
212
  private readonly http;
@@ -205,8 +221,11 @@ declare class PlayersModule {
205
221
  getInventory(playerUuid: string, worldUuid: string): Promise<InventoryItem[]>;
206
222
  /** Kick an online player, optionally with a reason. */
207
223
  kick(uuid: string, reason?: string): Promise<void>;
208
- /** Ban a player, optionally with a reason and expiration. */
209
- ban(uuid: string, reason?: string): Promise<void>;
224
+ /**
225
+ * Ban a player, optionally with a reason and an ISO-8601 expiry
226
+ * (e.g. "2030-01-01T00:00:00Z"). Omit `expiry` for a permanent ban.
227
+ */
228
+ ban(uuid: string, reason?: string, expiry?: string): Promise<void>;
210
229
  /** Remove a player's ban. */
211
230
  unban(uuid: string): Promise<void>;
212
231
  /** Teleport a player to a location. */
@@ -247,23 +266,40 @@ declare class ServerModule {
247
266
  /** Get server info: version, MOTD, TPS, health, player counts, etc. */
248
267
  info(): Promise<ServerInfo>;
249
268
  /**
250
- * Run a console command on the server.
269
+ * Run a console command on the server, returning its console output.
251
270
  * This is a privileged endpoint — requires a write-enabled API key.
271
+ *
272
+ * `waitMs` is how long to wait for output before returning (server
273
+ * default 500ms if omitted). The server returns the joined output as a
274
+ * plain JSON string, not `{ lines }`.
252
275
  */
253
- exec(command: string): Promise<{
254
- lines: string[];
255
- }>;
276
+ exec(command: string, waitMs?: number): Promise<string>;
256
277
  /** List server operators. */
257
278
  getOps(): Promise<WhitelistEntry[]>;
258
- /** Grant operator status to a player. */
279
+ /**
280
+ * Grant operator status to a player.
281
+ *
282
+ * Server-side (`ServerApi.opPlayer`) reads `ctx.formParam("playerUuid")`,
283
+ * not "uuid" — the field name matters here.
284
+ */
259
285
  opPlayer(uuid: string): Promise<void>;
260
- /** Revoke operator status from a player. */
286
+ /**
287
+ * Revoke operator status from a player.
288
+ *
289
+ * Server-side (`ServerApi.deopPlayer`) reads this from the query string
290
+ * (`ctx.queryParam("playerUuid")`), not the request body.
291
+ */
261
292
  deopPlayer(uuid: string): Promise<void>;
262
293
  /** Get the current whitelist. */
263
294
  getWhitelist(): Promise<WhitelistEntry[]>;
264
295
  /** Add a player to the whitelist. */
265
296
  addToWhitelist(uuid: string, name?: string): Promise<void>;
266
- /** Remove a player from the whitelist. */
297
+ /**
298
+ * Remove a player from the whitelist.
299
+ *
300
+ * Server-side (`ServerApi.whitelistDelete`) reads `uuid`/`name` from the
301
+ * query string, not the request body.
302
+ */
267
303
  removeFromWhitelist(uuid: string): Promise<void>;
268
304
  /**
269
305
  * Restart the server.
@@ -293,11 +329,21 @@ declare class ServerModule {
293
329
  getScoreboard(): Promise<unknown>;
294
330
  /** Set a score for an entry on an objective. */
295
331
  setScore(objective: string, entry: string, value: number): Promise<void>;
296
- /** Reset (remove) a score for an entry on an objective. */
332
+ /**
333
+ * Reset (remove) a score for an entry on an objective.
334
+ *
335
+ * Server-side (`ServerApi.resetScore`) reads `entry` from the query
336
+ * string, not the request body.
337
+ */
297
338
  resetScore(objective: string, entry: string): Promise<void>;
298
339
  /** Broadcast a message to every player on the server. */
299
340
  broadcast(message: string): Promise<void>;
300
- /** Send a private message to a specific player. */
341
+ /**
342
+ * Send a private message to a specific player.
343
+ *
344
+ * Server-side (`ServerApi.tellPost`) reads `ctx.formParam("playerUuid")`,
345
+ * not "uuid".
346
+ */
301
347
  tell(uuid: string, message: string): Promise<void>;
302
348
  }
303
349
 
@@ -320,13 +366,16 @@ declare class WorldsModule {
320
366
  download(uuid: string): Promise<{
321
367
  url: string;
322
368
  }>;
323
- /** Set the in-game time for a world. */
369
+ /** Set the in-game time for a world (0-24000). */
324
370
  setTime(uuid: string, time: number): Promise<void>;
325
- /** Set weather (storm/thundering) for a world. */
326
- setWeather(uuid: string, weather: {
327
- storm?: boolean;
328
- thundering?: boolean;
329
- }): Promise<void>;
371
+ /**
372
+ * Set the weather for a world.
373
+ *
374
+ * Server-side (`WorldApi.setWorldWeather`) reads a single
375
+ * `ctx.formParam("weather")` enum string — "clear" | "rain" | "thunder" —
376
+ * not separate storm/thundering booleans.
377
+ */
378
+ setWeather(uuid: string, weather: "clear" | "rain" | "thunder"): Promise<void>;
330
379
  /** Get entity counts within a specific world. */
331
380
  getEntities(uuid: string): Promise<{
332
381
  world: string;
@@ -341,10 +390,14 @@ declare class PluginsModule {
341
390
  /** List all installed plugins/mods. */
342
391
  list(): Promise<Plugin[]>;
343
392
  /**
344
- * Install a plugin from a URL or identifier.
393
+ * Install a plugin by downloading it from a direct URL.
345
394
  * This is a privileged endpoint — requires a write-enabled API key.
395
+ *
396
+ * Server-side (`PluginApi.installPlugin`) reads
397
+ * `ctx.formParam("downloadUrl")`, not "source", and the request must be
398
+ * form-urlencoded.
346
399
  */
347
- install(source: string): Promise<void>;
400
+ install(downloadUrl: string): Promise<void>;
348
401
  /** Enable a plugin by name. */
349
402
  enable(name: string): Promise<void>;
350
403
  /** Disable a plugin by name. */
@@ -361,12 +414,15 @@ declare class PlaceholdersModule {
361
414
  private readonly http;
362
415
  constructor(http: HttpEngine);
363
416
  /**
364
- * Replace PlaceholderAPI-style placeholders (e.g. "%player_name%") for a
365
- * player, returning the resolved string.
417
+ * Replace PlaceholderAPI-style placeholders (e.g. "%player_name%") in
418
+ * `message` for a player, returning the resolved string.
419
+ *
420
+ * Server-side this is `PAPIApi.replacePlaceholders`, which reads
421
+ * `ctx.formParam("message")` and `ctx.formParam("uuid")` — the field is
422
+ * literally named "message", not "text", and the whole body must be
423
+ * form-urlencoded.
366
424
  */
367
- replace(uuid: string, text: string): Promise<{
368
- result: string;
369
- }>;
425
+ replace(uuid: string, message: string): Promise<string>;
370
426
  }
371
427
 
372
428
  /**
@@ -375,6 +431,10 @@ declare class PlaceholdersModule {
375
431
  * without it will fail (typically a 404). There's no separate "is this
376
432
  * available" flag from the SDK's side; check `client.plugins.list()` for
377
433
  * LuckPerms if you need to branch on it ahead of time.
434
+ *
435
+ * Unlike most other modules, these POST/DELETE bodies are sent as real
436
+ * JSON (the server reads them with `ctx.bodyAsClass(...)`, not
437
+ * `ctx.formParam(...)`) — do not add `form: true` to these calls.
378
438
  */
379
439
  declare class LuckPermsModule {
380
440
  private readonly http;
@@ -406,6 +466,10 @@ declare class LuckPermsModule {
406
466
  * Wraps the `/v1/noxauth/*` routes. These only work when `noxauth.enabled`
407
467
  * is set to true in the server's noxaeapi-config.yml and the NoxAuth plugin
408
468
  * is installed.
469
+ *
470
+ * `checkPassword`'s body is sent as real JSON (the server parses it with
471
+ * `GsonSingleton...fromJson(ctx.body(), PasswordCheckRequest.class)`, not
472
+ * `ctx.formParam(...)`) — do not add `form: true` to that call.
409
473
  */
410
474
  declare class NoxAuthModule {
411
475
  private readonly http;
@@ -416,6 +480,28 @@ declare class NoxAuthModule {
416
480
  checkPassword(name: string, password: string): Promise<PasswordCheckResult>;
417
481
  }
418
482
 
483
+ /**
484
+ * Wraps the `/v1/skills/*` routes (mcMMO / AuraSkills).
485
+ */
486
+ declare class SkillsModule {
487
+ private readonly http;
488
+ constructor(http: HttpEngine);
489
+ /**
490
+ * Get mcMMO skill levels and power level for a player.
491
+ * Only works for **online** players — mcMMO's public ExperienceAPI has
492
+ * no offline lookup, so this throws `NoxAeApiNotFoundError` if the
493
+ * player isn't currently connected, and `NoxAeApiServerError` (503)
494
+ * if mcMMO isn't loaded on the target server.
495
+ */
496
+ getMcmmoSkills(uuid: string): Promise<SkillInfo>;
497
+ /**
498
+ * Get AuraSkills skill levels and power level for a player.
499
+ * Works for offline players. Throws `NoxAeApiServerError` (503) if
500
+ * AuraSkills isn't loaded on the target server.
501
+ */
502
+ getAuraSkills(uuid: string): Promise<SkillInfo>;
503
+ }
504
+
419
505
  type NoxAeApiWsEvent = "open" | "close" | "error" | "console" | "event" | "message";
420
506
  type Listener = (payload: unknown) => void;
421
507
  interface NoxAeApiWsOptions {
@@ -475,6 +561,8 @@ declare class NoxAeApiClient {
475
561
  readonly luckperms: LuckPermsModule;
476
562
  /** Only works if `noxauth.enabled: true` is set in the server config. */
477
563
  readonly noxauth: NoxAuthModule;
564
+ /** Requires mcMMO and/or AuraSkills to be loaded on the target server. */
565
+ readonly skills: SkillsModule;
478
566
  private readonly http;
479
567
  private readonly baseUrl;
480
568
  private readonly apiKey?;
@@ -545,4 +633,4 @@ declare class NoxAeApiNetworkError extends Error {
545
633
  constructor(message: string, method: string, path: string, cause?: unknown);
546
634
  }
547
635
 
548
- export { type Advancement, type EconomyInfo, type GroupInfo, type InventoryItem, NoxAeApiClient, type NoxAeApiClientOptions, NoxAeApiError, NoxAeApiForbiddenError, NoxAeApiNetworkError, NoxAeApiNotFoundError, NoxAeApiRateLimitError, NoxAeApiServerError, NoxAeApiSocket, NoxAeApiUnauthorizedError, type NoxAeApiWsEvent, type NoxAeApiWsOptions, type NoxAuthPlayerInfo, type Objective, type OfflinePlayer, type OnlinePlayer, type PasswordCheckResult, type PermissionNode, type PlayerBalance, type PlayerStats, type Plugin, type RetryOptions, type Score, type Scoreboard, type ServerBan, type ServerHealth, type ServerInfo, type TopBalanceEntry, type WhitelistEntry, type World };
636
+ export { type Advancement, type EconomyInfo, type GroupInfo, type InventoryItem, NoxAeApiClient, type NoxAeApiClientOptions, NoxAeApiError, NoxAeApiForbiddenError, NoxAeApiNetworkError, NoxAeApiNotFoundError, NoxAeApiRateLimitError, NoxAeApiServerError, NoxAeApiSocket, NoxAeApiUnauthorizedError, type NoxAeApiWsEvent, type NoxAeApiWsOptions, type NoxAuthPlayerInfo, type Objective, type OfflinePlayer, type OnlinePlayer, type PasswordCheckResult, type PermissionNode, type PlayerBalance, type PlayerStats, type Plugin, type RetryOptions, type Score, type Scoreboard, type ServerBan, type ServerHealth, type ServerInfo, type SkillInfo, type TopBalanceEntry, type WhitelistEntry, type World };
package/dist/index.js CHANGED
@@ -105,7 +105,7 @@ var HttpEngine = class {
105
105
  this.fetchImpl = fetchImpl;
106
106
  }
107
107
  buildUrl(path, query) {
108
- const url = new URL(`${this.baseUrl}/${path.replace(/^\/+/, "")}`);
108
+ const url = new URL(`${this.baseUrl}/v1/${path.replace(/^\/+/, "")}`);
109
109
  if (query) {
110
110
  for (const [key, value] of Object.entries(query)) {
111
111
  if (value !== void 0 && value !== null) {
@@ -128,11 +128,20 @@ var HttpEngine = class {
128
128
  ...this.extraHeaders
129
129
  };
130
130
  if (this.apiKey) headers["key"] = this.apiKey;
131
- if (opts.body !== void 0) headers["Content-Type"] = "application/json";
131
+ let encodedBody;
132
+ if (opts.body !== void 0) {
133
+ if (opts.form) {
134
+ headers["Content-Type"] = "application/x-www-form-urlencoded";
135
+ encodedBody = encodeFormBody(opts.body);
136
+ } else {
137
+ headers["Content-Type"] = "application/json";
138
+ encodedBody = JSON.stringify(opts.body);
139
+ }
140
+ }
132
141
  const response = await this.fetchImpl(url, {
133
142
  method,
134
143
  headers,
135
- body: opts.body !== void 0 ? JSON.stringify(opts.body) : void 0,
144
+ body: encodedBody,
136
145
  signal: controller.signal
137
146
  });
138
147
  clearTimeout(timeout);
@@ -192,6 +201,19 @@ var HttpEngine = class {
192
201
  throw lastError instanceof Error ? lastError : new Error("Request failed after retries");
193
202
  }
194
203
  };
204
+ function encodeFormBody(body) {
205
+ const params = new URLSearchParams();
206
+ if (body && typeof body === "object") {
207
+ for (const [key, value] of Object.entries(body)) {
208
+ if (value === void 0 || value === null) continue;
209
+ params.set(
210
+ key,
211
+ typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? String(value) : JSON.stringify(value)
212
+ );
213
+ }
214
+ }
215
+ return params.toString();
216
+ }
195
217
  function safeJsonParse(text) {
196
218
  if (!text) return void 0;
197
219
  try {
@@ -236,13 +258,18 @@ var PlayersModule = class {
236
258
  /** Kick an online player, optionally with a reason. */
237
259
  kick(uuid, reason) {
238
260
  return this.http.request("POST", `players/${encodeURIComponent(uuid)}/kick`, {
239
- body: reason ? { reason } : void 0
261
+ body: reason ? { reason } : void 0,
262
+ form: true
240
263
  });
241
264
  }
242
- /** Ban a player, optionally with a reason and expiration. */
243
- ban(uuid, reason) {
265
+ /**
266
+ * Ban a player, optionally with a reason and an ISO-8601 expiry
267
+ * (e.g. "2030-01-01T00:00:00Z"). Omit `expiry` for a permanent ban.
268
+ */
269
+ ban(uuid, reason, expiry) {
244
270
  return this.http.request("POST", `players/${encodeURIComponent(uuid)}/ban`, {
245
- body: reason ? { reason } : void 0
271
+ body: reason || expiry ? { reason, expiry } : void 0,
272
+ form: true
246
273
  });
247
274
  }
248
275
  /** Remove a player's ban. */
@@ -252,13 +279,15 @@ var PlayersModule = class {
252
279
  /** Teleport a player to a location. */
253
280
  teleport(uuid, location) {
254
281
  return this.http.request("POST", `players/${encodeURIComponent(uuid)}/teleport`, {
255
- body: location
282
+ body: location,
283
+ form: true
256
284
  });
257
285
  }
258
286
  /** Change a player's gamemode. */
259
287
  setGamemode(uuid, gamemode) {
260
288
  return this.http.request("PUT", `players/${encodeURIComponent(uuid)}/gamemode`, {
261
- body: { gamemode }
289
+ body: { gamemode },
290
+ form: true
262
291
  });
263
292
  }
264
293
  /** Get kill/death/playtime/block stats for a player. */
@@ -289,11 +318,11 @@ var EconomyModule = class {
289
318
  }
290
319
  /** Pay an amount to a player (adds to their balance). */
291
320
  pay(uuid, amount) {
292
- return this.http.request("POST", "economy/pay", { body: { uuid, amount } });
321
+ return this.http.request("POST", "economy/pay", { body: { uuid, amount }, form: true });
293
322
  }
294
323
  /** Debit an amount from a player (subtracts from their balance). */
295
324
  debit(uuid, amount) {
296
- return this.http.request("POST", "economy/debit", { body: { uuid, amount } });
325
+ return this.http.request("POST", "economy/debit", { body: { uuid, amount }, form: true });
297
326
  }
298
327
  };
299
328
 
@@ -312,23 +341,40 @@ var ServerModule = class {
312
341
  return this.http.request("GET", "server");
313
342
  }
314
343
  /**
315
- * Run a console command on the server.
344
+ * Run a console command on the server, returning its console output.
316
345
  * This is a privileged endpoint — requires a write-enabled API key.
346
+ *
347
+ * `waitMs` is how long to wait for output before returning (server
348
+ * default 500ms if omitted). The server returns the joined output as a
349
+ * plain JSON string, not `{ lines }`.
317
350
  */
318
- exec(command) {
319
- return this.http.request("POST", "server/exec", { body: { command } });
351
+ exec(command, waitMs) {
352
+ return this.http.request("POST", "server/exec", {
353
+ body: { command, time: waitMs },
354
+ form: true
355
+ });
320
356
  }
321
357
  /** List server operators. */
322
358
  getOps() {
323
359
  return this.http.request("GET", "server/ops");
324
360
  }
325
- /** Grant operator status to a player. */
361
+ /**
362
+ * Grant operator status to a player.
363
+ *
364
+ * Server-side (`ServerApi.opPlayer`) reads `ctx.formParam("playerUuid")`,
365
+ * not "uuid" — the field name matters here.
366
+ */
326
367
  opPlayer(uuid) {
327
- return this.http.request("POST", "server/ops", { body: { uuid } });
368
+ return this.http.request("POST", "server/ops", { body: { playerUuid: uuid }, form: true });
328
369
  }
329
- /** Revoke operator status from a player. */
370
+ /**
371
+ * Revoke operator status from a player.
372
+ *
373
+ * Server-side (`ServerApi.deopPlayer`) reads this from the query string
374
+ * (`ctx.queryParam("playerUuid")`), not the request body.
375
+ */
330
376
  deopPlayer(uuid) {
331
- return this.http.request("DELETE", "server/ops", { body: { uuid } });
377
+ return this.http.request("DELETE", "server/ops", { query: { playerUuid: uuid } });
332
378
  }
333
379
  /** Get the current whitelist. */
334
380
  getWhitelist() {
@@ -336,11 +382,16 @@ var ServerModule = class {
336
382
  }
337
383
  /** Add a player to the whitelist. */
338
384
  addToWhitelist(uuid, name) {
339
- return this.http.request("POST", "server/whitelist", { body: { uuid, name } });
385
+ return this.http.request("POST", "server/whitelist", { body: { uuid, name }, form: true });
340
386
  }
341
- /** Remove a player from the whitelist. */
387
+ /**
388
+ * Remove a player from the whitelist.
389
+ *
390
+ * Server-side (`ServerApi.whitelistDelete`) reads `uuid`/`name` from the
391
+ * query string, not the request body.
392
+ */
342
393
  removeFromWhitelist(uuid) {
343
- return this.http.request("DELETE", "server/whitelist", { body: { uuid } });
394
+ return this.http.request("DELETE", "server/whitelist", { query: { uuid } });
344
395
  }
345
396
  /**
346
397
  * Restart the server.
@@ -363,7 +414,7 @@ var ServerModule = class {
363
414
  }
364
415
  /** Ban an IP address. */
365
416
  banIp(ip, reason) {
366
- return this.http.request("POST", "server/ban-ip", { body: { ip, reason } });
417
+ return this.http.request("POST", "server/ban-ip", { body: { ip, reason }, form: true });
367
418
  }
368
419
  /** Get a scoreboard objective's scores by objective name. */
369
420
  getObjective(name) {
@@ -376,22 +427,33 @@ var ServerModule = class {
376
427
  /** Set a score for an entry on an objective. */
377
428
  setScore(objective, entry, value) {
378
429
  return this.http.request("POST", `scoreboard/${encodeURIComponent(objective)}/score`, {
379
- body: { entry, value }
430
+ body: { entry, value },
431
+ form: true
380
432
  });
381
433
  }
382
- /** Reset (remove) a score for an entry on an objective. */
434
+ /**
435
+ * Reset (remove) a score for an entry on an objective.
436
+ *
437
+ * Server-side (`ServerApi.resetScore`) reads `entry` from the query
438
+ * string, not the request body.
439
+ */
383
440
  resetScore(objective, entry) {
384
441
  return this.http.request("DELETE", `scoreboard/${encodeURIComponent(objective)}/score`, {
385
- body: { entry }
442
+ query: { entry }
386
443
  });
387
444
  }
388
445
  /** Broadcast a message to every player on the server. */
389
446
  broadcast(message) {
390
- return this.http.request("POST", "chat/broadcast", { body: { message } });
447
+ return this.http.request("POST", "chat/broadcast", { body: { message }, form: true });
391
448
  }
392
- /** Send a private message to a specific player. */
449
+ /**
450
+ * Send a private message to a specific player.
451
+ *
452
+ * Server-side (`ServerApi.tellPost`) reads `ctx.formParam("playerUuid")`,
453
+ * not "uuid".
454
+ */
393
455
  tell(uuid, message) {
394
- return this.http.request("POST", "chat/tell", { body: { uuid, message } });
456
+ return this.http.request("POST", "chat/tell", { body: { playerUuid: uuid, message }, form: true });
395
457
  }
396
458
  };
397
459
 
@@ -425,16 +487,24 @@ var WorldsModule = class {
425
487
  download(uuid) {
426
488
  return this.http.request("GET", `worlds/${encodeURIComponent(uuid)}/download`);
427
489
  }
428
- /** Set the in-game time for a world. */
490
+ /** Set the in-game time for a world (0-24000). */
429
491
  setTime(uuid, time) {
430
492
  return this.http.request("POST", `worlds/${encodeURIComponent(uuid)}/time`, {
431
- body: { time }
493
+ body: { time },
494
+ form: true
432
495
  });
433
496
  }
434
- /** Set weather (storm/thundering) for a world. */
497
+ /**
498
+ * Set the weather for a world.
499
+ *
500
+ * Server-side (`WorldApi.setWorldWeather`) reads a single
501
+ * `ctx.formParam("weather")` enum string — "clear" | "rain" | "thunder" —
502
+ * not separate storm/thundering booleans.
503
+ */
435
504
  setWeather(uuid, weather) {
436
505
  return this.http.request("POST", `worlds/${encodeURIComponent(uuid)}/weather`, {
437
- body: weather
506
+ body: { weather },
507
+ form: true
438
508
  });
439
509
  }
440
510
  /** Get entity counts within a specific world. */
@@ -454,11 +524,15 @@ var PluginsModule = class {
454
524
  return this.http.request("GET", "plugins");
455
525
  }
456
526
  /**
457
- * Install a plugin from a URL or identifier.
527
+ * Install a plugin by downloading it from a direct URL.
458
528
  * This is a privileged endpoint — requires a write-enabled API key.
529
+ *
530
+ * Server-side (`PluginApi.installPlugin`) reads
531
+ * `ctx.formParam("downloadUrl")`, not "source", and the request must be
532
+ * form-urlencoded.
459
533
  */
460
- install(source) {
461
- return this.http.request("POST", "plugins", { body: { source } });
534
+ install(downloadUrl) {
535
+ return this.http.request("POST", "plugins", { body: { downloadUrl }, form: true });
462
536
  }
463
537
  /** Enable a plugin by name. */
464
538
  enable(name) {
@@ -487,12 +561,18 @@ var PlaceholdersModule = class {
487
561
  }
488
562
  http;
489
563
  /**
490
- * Replace PlaceholderAPI-style placeholders (e.g. "%player_name%") for a
491
- * player, returning the resolved string.
564
+ * Replace PlaceholderAPI-style placeholders (e.g. "%player_name%") in
565
+ * `message` for a player, returning the resolved string.
566
+ *
567
+ * Server-side this is `PAPIApi.replacePlaceholders`, which reads
568
+ * `ctx.formParam("message")` and `ctx.formParam("uuid")` — the field is
569
+ * literally named "message", not "text", and the whole body must be
570
+ * form-urlencoded.
492
571
  */
493
- replace(uuid, text) {
572
+ replace(uuid, message) {
494
573
  return this.http.request("POST", "placeholders/replace", {
495
- body: { uuid, text }
574
+ body: { uuid, message },
575
+ form: true
496
576
  });
497
577
  }
498
578
  };
@@ -583,9 +663,35 @@ var NoxAuthModule = class {
583
663
  }
584
664
  };
585
665
 
666
+ // src/modules/skills.ts
667
+ var SkillsModule = class {
668
+ constructor(http) {
669
+ this.http = http;
670
+ }
671
+ http;
672
+ /**
673
+ * Get mcMMO skill levels and power level for a player.
674
+ * Only works for **online** players — mcMMO's public ExperienceAPI has
675
+ * no offline lookup, so this throws `NoxAeApiNotFoundError` if the
676
+ * player isn't currently connected, and `NoxAeApiServerError` (503)
677
+ * if mcMMO isn't loaded on the target server.
678
+ */
679
+ getMcmmoSkills(uuid) {
680
+ return this.http.request("GET", `skills/mcmmo/player/${encodeURIComponent(uuid)}`);
681
+ }
682
+ /**
683
+ * Get AuraSkills skill levels and power level for a player.
684
+ * Works for offline players. Throws `NoxAeApiServerError` (503) if
685
+ * AuraSkills isn't loaded on the target server.
686
+ */
687
+ getAuraSkills(uuid) {
688
+ return this.http.request("GET", `skills/auraskills/player/${encodeURIComponent(uuid)}`);
689
+ }
690
+ };
691
+
586
692
  // src/socket.ts
587
693
  function toWsUrl(baseUrl, route, apiKey) {
588
- const url = new URL(`${baseUrl.replace(/\/+$/, "")}/${route.replace(/^\/+/, "")}`);
694
+ const url = new URL(`${baseUrl.replace(/\/+$/, "")}/v1/ws/${route.replace(/^\/+/, "")}`);
589
695
  url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
590
696
  if (apiKey) url.searchParams.set("key", apiKey);
591
697
  return url.toString();
@@ -688,6 +794,8 @@ var NoxAeApiClient = class _NoxAeApiClient {
688
794
  luckperms;
689
795
  /** Only works if `noxauth.enabled: true` is set in the server config. */
690
796
  noxauth;
797
+ /** Requires mcMMO and/or AuraSkills to be loaded on the target server. */
798
+ skills;
691
799
  http;
692
800
  baseUrl;
693
801
  apiKey;
@@ -704,6 +812,7 @@ var NoxAeApiClient = class _NoxAeApiClient {
704
812
  this.placeholders = new PlaceholdersModule(this.http);
705
813
  this.luckperms = new LuckPermsModule(this.http);
706
814
  this.noxauth = new NoxAuthModule(this.http);
815
+ this.skills = new SkillsModule(this.http);
707
816
  }
708
817
  /**
709
818
  * Build a client from environment variables: