@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/README.md CHANGED
@@ -69,6 +69,23 @@ try {
69
69
  }
70
70
  ```
71
71
 
72
+ ## Request encoding
73
+
74
+ The server is a Javalin app, and most endpoints read their body with
75
+ `ctx.formParam(...)` — i.e. `application/x-www-form-urlencoded` — rather
76
+ than JSON. The SDK follows the same split:
77
+
78
+ - **Form-urlencoded**: everything in `economy`, `players`, `server`
79
+ (except `luckperms`/`noxauth`), `worlds`, `plugins`, and `placeholders`.
80
+ - **JSON**: `client.luckperms.*` and `client.noxauth.checkPassword` only —
81
+ these are read server-side with `ctx.bodyAsClass(...)`.
82
+
83
+ If you're adding a new SDK method, check which one the corresponding
84
+ Javalin handler uses before wiring it up, and pass `form: true` to
85
+ `http.request(...)` if it's form-urlencoded (this is also the more common
86
+ case). Getting this wrong won't throw a type error — the request just
87
+ silently sends the wrong content type and the server won't see the field.
88
+
72
89
  ## Optional modules
73
90
 
74
91
  Some modules only work depending on the target server's setup:
@@ -97,4 +114,4 @@ new NoxAeApiClient({
97
114
 
98
115
  ## License
99
116
 
100
- MIT
117
+ MIT
package/dist/index.cjs CHANGED
@@ -107,7 +107,7 @@ var HttpEngine = class {
107
107
  this.fetchImpl = fetchImpl;
108
108
  }
109
109
  buildUrl(path, query) {
110
- const url = new URL(`${this.baseUrl}/${path.replace(/^\/+/, "")}`);
110
+ const url = new URL(`${this.baseUrl}/v1/${path.replace(/^\/+/, "")}`);
111
111
  if (query) {
112
112
  for (const [key, value] of Object.entries(query)) {
113
113
  if (value !== void 0 && value !== null) {
@@ -130,11 +130,20 @@ var HttpEngine = class {
130
130
  ...this.extraHeaders
131
131
  };
132
132
  if (this.apiKey) headers["key"] = this.apiKey;
133
- if (opts.body !== void 0) headers["Content-Type"] = "application/json";
133
+ let encodedBody;
134
+ if (opts.body !== void 0) {
135
+ if (opts.form) {
136
+ headers["Content-Type"] = "application/x-www-form-urlencoded";
137
+ encodedBody = encodeFormBody(opts.body);
138
+ } else {
139
+ headers["Content-Type"] = "application/json";
140
+ encodedBody = JSON.stringify(opts.body);
141
+ }
142
+ }
134
143
  const response = await this.fetchImpl(url, {
135
144
  method,
136
145
  headers,
137
- body: opts.body !== void 0 ? JSON.stringify(opts.body) : void 0,
146
+ body: encodedBody,
138
147
  signal: controller.signal
139
148
  });
140
149
  clearTimeout(timeout);
@@ -194,6 +203,19 @@ var HttpEngine = class {
194
203
  throw lastError instanceof Error ? lastError : new Error("Request failed after retries");
195
204
  }
196
205
  };
206
+ function encodeFormBody(body) {
207
+ const params = new URLSearchParams();
208
+ if (body && typeof body === "object") {
209
+ for (const [key, value] of Object.entries(body)) {
210
+ if (value === void 0 || value === null) continue;
211
+ params.set(
212
+ key,
213
+ typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? String(value) : JSON.stringify(value)
214
+ );
215
+ }
216
+ }
217
+ return params.toString();
218
+ }
197
219
  function safeJsonParse(text) {
198
220
  if (!text) return void 0;
199
221
  try {
@@ -238,13 +260,18 @@ var PlayersModule = class {
238
260
  /** Kick an online player, optionally with a reason. */
239
261
  kick(uuid, reason) {
240
262
  return this.http.request("POST", `players/${encodeURIComponent(uuid)}/kick`, {
241
- body: reason ? { reason } : void 0
263
+ body: reason ? { reason } : void 0,
264
+ form: true
242
265
  });
243
266
  }
244
- /** Ban a player, optionally with a reason and expiration. */
245
- ban(uuid, reason) {
267
+ /**
268
+ * Ban a player, optionally with a reason and an ISO-8601 expiry
269
+ * (e.g. "2030-01-01T00:00:00Z"). Omit `expiry` for a permanent ban.
270
+ */
271
+ ban(uuid, reason, expiry) {
246
272
  return this.http.request("POST", `players/${encodeURIComponent(uuid)}/ban`, {
247
- body: reason ? { reason } : void 0
273
+ body: reason || expiry ? { reason, expiry } : void 0,
274
+ form: true
248
275
  });
249
276
  }
250
277
  /** Remove a player's ban. */
@@ -254,13 +281,15 @@ var PlayersModule = class {
254
281
  /** Teleport a player to a location. */
255
282
  teleport(uuid, location) {
256
283
  return this.http.request("POST", `players/${encodeURIComponent(uuid)}/teleport`, {
257
- body: location
284
+ body: location,
285
+ form: true
258
286
  });
259
287
  }
260
288
  /** Change a player's gamemode. */
261
289
  setGamemode(uuid, gamemode) {
262
290
  return this.http.request("PUT", `players/${encodeURIComponent(uuid)}/gamemode`, {
263
- body: { gamemode }
291
+ body: { gamemode },
292
+ form: true
264
293
  });
265
294
  }
266
295
  /** Get kill/death/playtime/block stats for a player. */
@@ -291,11 +320,11 @@ var EconomyModule = class {
291
320
  }
292
321
  /** Pay an amount to a player (adds to their balance). */
293
322
  pay(uuid, amount) {
294
- return this.http.request("POST", "economy/pay", { body: { uuid, amount } });
323
+ return this.http.request("POST", "economy/pay", { body: { uuid, amount }, form: true });
295
324
  }
296
325
  /** Debit an amount from a player (subtracts from their balance). */
297
326
  debit(uuid, amount) {
298
- return this.http.request("POST", "economy/debit", { body: { uuid, amount } });
327
+ return this.http.request("POST", "economy/debit", { body: { uuid, amount }, form: true });
299
328
  }
300
329
  };
301
330
 
@@ -314,23 +343,40 @@ var ServerModule = class {
314
343
  return this.http.request("GET", "server");
315
344
  }
316
345
  /**
317
- * Run a console command on the server.
346
+ * Run a console command on the server, returning its console output.
318
347
  * This is a privileged endpoint — requires a write-enabled API key.
348
+ *
349
+ * `waitMs` is how long to wait for output before returning (server
350
+ * default 500ms if omitted). The server returns the joined output as a
351
+ * plain JSON string, not `{ lines }`.
319
352
  */
320
- exec(command) {
321
- return this.http.request("POST", "server/exec", { body: { command } });
353
+ exec(command, waitMs) {
354
+ return this.http.request("POST", "server/exec", {
355
+ body: { command, time: waitMs },
356
+ form: true
357
+ });
322
358
  }
323
359
  /** List server operators. */
324
360
  getOps() {
325
361
  return this.http.request("GET", "server/ops");
326
362
  }
327
- /** Grant operator status to a player. */
363
+ /**
364
+ * Grant operator status to a player.
365
+ *
366
+ * Server-side (`ServerApi.opPlayer`) reads `ctx.formParam("playerUuid")`,
367
+ * not "uuid" — the field name matters here.
368
+ */
328
369
  opPlayer(uuid) {
329
- return this.http.request("POST", "server/ops", { body: { uuid } });
370
+ return this.http.request("POST", "server/ops", { body: { playerUuid: uuid }, form: true });
330
371
  }
331
- /** Revoke operator status from a player. */
372
+ /**
373
+ * Revoke operator status from a player.
374
+ *
375
+ * Server-side (`ServerApi.deopPlayer`) reads this from the query string
376
+ * (`ctx.queryParam("playerUuid")`), not the request body.
377
+ */
332
378
  deopPlayer(uuid) {
333
- return this.http.request("DELETE", "server/ops", { body: { uuid } });
379
+ return this.http.request("DELETE", "server/ops", { query: { playerUuid: uuid } });
334
380
  }
335
381
  /** Get the current whitelist. */
336
382
  getWhitelist() {
@@ -338,11 +384,16 @@ var ServerModule = class {
338
384
  }
339
385
  /** Add a player to the whitelist. */
340
386
  addToWhitelist(uuid, name) {
341
- return this.http.request("POST", "server/whitelist", { body: { uuid, name } });
387
+ return this.http.request("POST", "server/whitelist", { body: { uuid, name }, form: true });
342
388
  }
343
- /** Remove a player from the whitelist. */
389
+ /**
390
+ * Remove a player from the whitelist.
391
+ *
392
+ * Server-side (`ServerApi.whitelistDelete`) reads `uuid`/`name` from the
393
+ * query string, not the request body.
394
+ */
344
395
  removeFromWhitelist(uuid) {
345
- return this.http.request("DELETE", "server/whitelist", { body: { uuid } });
396
+ return this.http.request("DELETE", "server/whitelist", { query: { uuid } });
346
397
  }
347
398
  /**
348
399
  * Restart the server.
@@ -365,7 +416,7 @@ var ServerModule = class {
365
416
  }
366
417
  /** Ban an IP address. */
367
418
  banIp(ip, reason) {
368
- return this.http.request("POST", "server/ban-ip", { body: { ip, reason } });
419
+ return this.http.request("POST", "server/ban-ip", { body: { ip, reason }, form: true });
369
420
  }
370
421
  /** Get a scoreboard objective's scores by objective name. */
371
422
  getObjective(name) {
@@ -378,22 +429,33 @@ var ServerModule = class {
378
429
  /** Set a score for an entry on an objective. */
379
430
  setScore(objective, entry, value) {
380
431
  return this.http.request("POST", `scoreboard/${encodeURIComponent(objective)}/score`, {
381
- body: { entry, value }
432
+ body: { entry, value },
433
+ form: true
382
434
  });
383
435
  }
384
- /** Reset (remove) a score for an entry on an objective. */
436
+ /**
437
+ * Reset (remove) a score for an entry on an objective.
438
+ *
439
+ * Server-side (`ServerApi.resetScore`) reads `entry` from the query
440
+ * string, not the request body.
441
+ */
385
442
  resetScore(objective, entry) {
386
443
  return this.http.request("DELETE", `scoreboard/${encodeURIComponent(objective)}/score`, {
387
- body: { entry }
444
+ query: { entry }
388
445
  });
389
446
  }
390
447
  /** Broadcast a message to every player on the server. */
391
448
  broadcast(message) {
392
- return this.http.request("POST", "chat/broadcast", { body: { message } });
449
+ return this.http.request("POST", "chat/broadcast", { body: { message }, form: true });
393
450
  }
394
- /** Send a private message to a specific player. */
451
+ /**
452
+ * Send a private message to a specific player.
453
+ *
454
+ * Server-side (`ServerApi.tellPost`) reads `ctx.formParam("playerUuid")`,
455
+ * not "uuid".
456
+ */
395
457
  tell(uuid, message) {
396
- return this.http.request("POST", "chat/tell", { body: { uuid, message } });
458
+ return this.http.request("POST", "chat/tell", { body: { playerUuid: uuid, message }, form: true });
397
459
  }
398
460
  };
399
461
 
@@ -427,16 +489,24 @@ var WorldsModule = class {
427
489
  download(uuid) {
428
490
  return this.http.request("GET", `worlds/${encodeURIComponent(uuid)}/download`);
429
491
  }
430
- /** Set the in-game time for a world. */
492
+ /** Set the in-game time for a world (0-24000). */
431
493
  setTime(uuid, time) {
432
494
  return this.http.request("POST", `worlds/${encodeURIComponent(uuid)}/time`, {
433
- body: { time }
495
+ body: { time },
496
+ form: true
434
497
  });
435
498
  }
436
- /** Set weather (storm/thundering) for a world. */
499
+ /**
500
+ * Set the weather for a world.
501
+ *
502
+ * Server-side (`WorldApi.setWorldWeather`) reads a single
503
+ * `ctx.formParam("weather")` enum string — "clear" | "rain" | "thunder" —
504
+ * not separate storm/thundering booleans.
505
+ */
437
506
  setWeather(uuid, weather) {
438
507
  return this.http.request("POST", `worlds/${encodeURIComponent(uuid)}/weather`, {
439
- body: weather
508
+ body: { weather },
509
+ form: true
440
510
  });
441
511
  }
442
512
  /** Get entity counts within a specific world. */
@@ -456,11 +526,15 @@ var PluginsModule = class {
456
526
  return this.http.request("GET", "plugins");
457
527
  }
458
528
  /**
459
- * Install a plugin from a URL or identifier.
529
+ * Install a plugin by downloading it from a direct URL.
460
530
  * This is a privileged endpoint — requires a write-enabled API key.
531
+ *
532
+ * Server-side (`PluginApi.installPlugin`) reads
533
+ * `ctx.formParam("downloadUrl")`, not "source", and the request must be
534
+ * form-urlencoded.
461
535
  */
462
- install(source) {
463
- return this.http.request("POST", "plugins", { body: { source } });
536
+ install(downloadUrl) {
537
+ return this.http.request("POST", "plugins", { body: { downloadUrl }, form: true });
464
538
  }
465
539
  /** Enable a plugin by name. */
466
540
  enable(name) {
@@ -489,12 +563,18 @@ var PlaceholdersModule = class {
489
563
  }
490
564
  http;
491
565
  /**
492
- * Replace PlaceholderAPI-style placeholders (e.g. "%player_name%") for a
493
- * player, returning the resolved string.
566
+ * Replace PlaceholderAPI-style placeholders (e.g. "%player_name%") in
567
+ * `message` for a player, returning the resolved string.
568
+ *
569
+ * Server-side this is `PAPIApi.replacePlaceholders`, which reads
570
+ * `ctx.formParam("message")` and `ctx.formParam("uuid")` — the field is
571
+ * literally named "message", not "text", and the whole body must be
572
+ * form-urlencoded.
494
573
  */
495
- replace(uuid, text) {
574
+ replace(uuid, message) {
496
575
  return this.http.request("POST", "placeholders/replace", {
497
- body: { uuid, text }
576
+ body: { uuid, message },
577
+ form: true
498
578
  });
499
579
  }
500
580
  };
@@ -585,9 +665,35 @@ var NoxAuthModule = class {
585
665
  }
586
666
  };
587
667
 
668
+ // src/modules/skills.ts
669
+ var SkillsModule = class {
670
+ constructor(http) {
671
+ this.http = http;
672
+ }
673
+ http;
674
+ /**
675
+ * Get mcMMO skill levels and power level for a player.
676
+ * Only works for **online** players — mcMMO's public ExperienceAPI has
677
+ * no offline lookup, so this throws `NoxAeApiNotFoundError` if the
678
+ * player isn't currently connected, and `NoxAeApiServerError` (503)
679
+ * if mcMMO isn't loaded on the target server.
680
+ */
681
+ getMcmmoSkills(uuid) {
682
+ return this.http.request("GET", `skills/mcmmo/player/${encodeURIComponent(uuid)}`);
683
+ }
684
+ /**
685
+ * Get AuraSkills skill levels and power level for a player.
686
+ * Works for offline players. Throws `NoxAeApiServerError` (503) if
687
+ * AuraSkills isn't loaded on the target server.
688
+ */
689
+ getAuraSkills(uuid) {
690
+ return this.http.request("GET", `skills/auraskills/player/${encodeURIComponent(uuid)}`);
691
+ }
692
+ };
693
+
588
694
  // src/socket.ts
589
695
  function toWsUrl(baseUrl, route, apiKey) {
590
- const url = new URL(`${baseUrl.replace(/\/+$/, "")}/${route.replace(/^\/+/, "")}`);
696
+ const url = new URL(`${baseUrl.replace(/\/+$/, "")}/v1/ws/${route.replace(/^\/+/, "")}`);
591
697
  url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
592
698
  if (apiKey) url.searchParams.set("key", apiKey);
593
699
  return url.toString();
@@ -690,6 +796,8 @@ var NoxAeApiClient = class _NoxAeApiClient {
690
796
  luckperms;
691
797
  /** Only works if `noxauth.enabled: true` is set in the server config. */
692
798
  noxauth;
799
+ /** Requires mcMMO and/or AuraSkills to be loaded on the target server. */
800
+ skills;
693
801
  http;
694
802
  baseUrl;
695
803
  apiKey;
@@ -706,6 +814,7 @@ var NoxAeApiClient = class _NoxAeApiClient {
706
814
  this.placeholders = new PlaceholdersModule(this.http);
707
815
  this.luckperms = new LuckPermsModule(this.http);
708
816
  this.noxauth = new NoxAuthModule(this.http);
817
+ this.skills = new SkillsModule(this.http);
709
818
  }
710
819
  /**
711
820
  * Build a client from environment variables: