@stoatx/client 0.6.4 → 0.8.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.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/client/Client.ts
2
- import { EventEmitter as EventEmitter2 } from "events";
2
+ import { EventEmitter as EventEmitter4 } from "events";
3
3
 
4
4
  // src/gateway/GatewayManager.ts
5
5
  import WebSocket from "ws";
@@ -756,6 +756,7 @@ var GatewayManager = class {
756
756
  this.client = client;
757
757
  }
758
758
  ws = null;
759
+ wsURL = "";
759
760
  pingInterval = null;
760
761
  token = null;
761
762
  reconnectAttempts = 0;
@@ -770,7 +771,7 @@ var GatewayManager = class {
770
771
  this.ws.removeAllListeners();
771
772
  this.ws = null;
772
773
  }
773
- const baseUrl = "wss://stoat.chat/events";
774
+ const baseUrl = this.wsURL ?? "wss://events.stoat.chat";
774
775
  const url = `${baseUrl}?version=1&format=json&token=${this.token}`;
775
776
  this.ws = new WebSocket(url);
776
777
  this.ws.on("open", () => {
@@ -790,6 +791,9 @@ var GatewayManager = class {
790
791
  this.client.emit("error", error);
791
792
  });
792
793
  }
794
+ setGatewayUrl(url) {
795
+ this.wsURL = url;
796
+ }
793
797
  handleMessage(rawData) {
794
798
  const payload = JSON.parse(rawData.toString());
795
799
  const eventType = payload.type;
@@ -1286,12 +1290,19 @@ var RESTManager = class {
1286
1290
  }
1287
1291
  }, 3e5).unref();
1288
1292
  }
1289
- baseURL = "https://stoat.chat/api";
1293
+ baseURL = "";
1294
+ cdnURL = "";
1290
1295
  token = null;
1291
1296
  buckets = /* @__PURE__ */ new Map();
1292
1297
  setToken(token) {
1293
1298
  this.token = token;
1294
1299
  }
1300
+ setBaseURL(baseURL) {
1301
+ this.baseURL = baseURL.replace(/\/+$/, "");
1302
+ }
1303
+ setCDNURL(cdnURL) {
1304
+ this.cdnURL = cdnURL.replace(/\/+$/, "");
1305
+ }
1295
1306
  /**
1296
1307
  * Generates a local identifier for the bucket based on method and path.
1297
1308
  */
@@ -1344,13 +1355,14 @@ var RESTManager = class {
1344
1355
  this.client.emit("debug", `Bucket [${method}:${endpoint}] exhausted. Waiting ${waitTime}ms proactively...`);
1345
1356
  await sleep(waitTime);
1346
1357
  }
1358
+ const isBodyMethod = ["post", "patch", "put"].includes(method.toLowerCase());
1347
1359
  const response = await fetch(url, {
1348
1360
  method: method.toUpperCase(),
1349
1361
  headers: {
1350
1362
  "X-Bot-Token": this.token,
1351
1363
  "Content-Type": "application/json"
1352
1364
  },
1353
- ...body !== void 0 ? { body: JSON.stringify(body) } : {}
1365
+ ...isBodyMethod ? { body: JSON.stringify(body ?? {}) } : {}
1354
1366
  });
1355
1367
  const remainingHeader = response.headers.get("x-ratelimit-remaining");
1356
1368
  const resetAfterHeader = response.headers.get("x-ratelimit-reset-after");
@@ -1383,7 +1395,7 @@ var RESTManager = class {
1383
1395
  */
1384
1396
  async uploadFile(tag, fileBuffer, filename) {
1385
1397
  if (!this.token) throw new Error("REST_NOT_READY: No token available.");
1386
- const url = `https://cdn.stoatusercontent.com/${tag}`;
1398
+ const url = `${this.cdnURL}/${tag}`;
1387
1399
  const formData = new FormData();
1388
1400
  formData.append("file", new Blob([fileBuffer]), filename);
1389
1401
  const response = await fetch(url, {
@@ -1886,6 +1898,9 @@ var BaseChannel = class extends Base {
1886
1898
  isGroup() {
1887
1899
  return this.type === "Group";
1888
1900
  }
1901
+ isVoice() {
1902
+ return this.isText() && this.voice !== null;
1903
+ }
1889
1904
  };
1890
1905
 
1891
1906
  // src/structures/TextChannel.ts
@@ -2196,10 +2211,21 @@ var GroupChannel = class extends BaseChannel {
2196
2211
  }
2197
2212
  };
2198
2213
 
2214
+ // src/structures/VoiceChannel.ts
2215
+ var VoiceChannel = class extends TextChannel {
2216
+ async join() {
2217
+ return this.client.voice.join(this.id, this.serverId);
2218
+ }
2219
+ async leave() {
2220
+ return this.client.voice.leave(this.id);
2221
+ }
2222
+ };
2223
+
2199
2224
  // src/utils/ChannelFactory.ts
2200
2225
  function createChannel(client, data) {
2201
2226
  switch (data.channel_type) {
2202
2227
  case "TextChannel":
2228
+ if (data.voice) return new VoiceChannel(client, data);
2203
2229
  return new TextChannel(client, data);
2204
2230
  case "DirectMessage":
2205
2231
  return new DMChannel(client, data);
@@ -3779,6 +3805,50 @@ var Emoji = class extends Base {
3779
3805
  if (data.animated !== void 0) this.animated = data.animated;
3780
3806
  if (data.nsfw !== void 0) this.nsfw = data.nsfw;
3781
3807
  }
3808
+ /**
3809
+ * Fetch this emoji from the API or resolves it from the local cache.
3810
+ * @param force Whether to skip the cache check and force a direct API request. Defaults to false.
3811
+ * @returns A promise that resolves to the fetched {@link Emoji} object.
3812
+ * @throws {Error} If the API request fails or if the emoji is detached.
3813
+ * @example
3814
+ * // Force fetch emoji to update its data
3815
+ * await emoji.fetch(true);
3816
+ */
3817
+ async fetch(force) {
3818
+ const server = this.client.servers.cache.get(this.parent.type === "Server" ? this.parent.id : "");
3819
+ if (!server) throw new Error("No server registered in EmojiManager");
3820
+ if (this.parent.type === "Detached") throw new Error("Cannot fetch a detached emoji");
3821
+ return await server.emojis.fetch(this, force);
3822
+ }
3823
+ /**
3824
+ * Deletes this emoji from the server.
3825
+ * @returns A promise that resolves when the emoji has been deleted.
3826
+ * @throws {Error} If the API request fails or if the emoji is detached.
3827
+ * @example
3828
+ * // Delete an emoji from the server
3829
+ * await emoji.delete();
3830
+ */
3831
+ async delete() {
3832
+ const server = this.client.servers.cache.get(this.parent.type === "Server" ? this.parent.id : "");
3833
+ if (!server) throw new Error("No server registered in EmojiManager");
3834
+ if (this.parent.type === "Detached") throw new Error("Emoji is already detached");
3835
+ await server.emojis.delete(this);
3836
+ }
3837
+ /**
3838
+ * Edits this emoji's properties.
3839
+ * @param options The options to edit the emoji with.
3840
+ * @returns A promise that resolves to the edited {@link Emoji} object.
3841
+ * @throws {Error} If the API request fails or if the emoji is detached.
3842
+ * @example
3843
+ * // Edit an emoji's name
3844
+ * await emoji.edit({ name: "new_name" });
3845
+ */
3846
+ async edit(options) {
3847
+ const server = this.client.servers.cache.get(this.parent.type === "Server" ? this.parent.id : "");
3848
+ if (!server) throw new Error("No server registered in EmojiManager");
3849
+ if (this.parent.type === "Detached") throw new Error("Cannot edit a detached emoji");
3850
+ return await server.emojis.edit(this, options);
3851
+ }
3782
3852
  };
3783
3853
 
3784
3854
  // src/managers/EmojiManager.ts
@@ -3792,7 +3862,7 @@ var EmojiManager = class extends BaseManager {
3792
3862
  * Tell BaseManager how to find the ID for Emojis
3793
3863
  */
3794
3864
  extractId(data) {
3795
- return data._id ?? data.id;
3865
+ return data._id;
3796
3866
  }
3797
3867
  /**
3798
3868
  * Tell BaseManager how to build an Emoji
@@ -3800,10 +3870,104 @@ var EmojiManager = class extends BaseManager {
3800
3870
  construct(data) {
3801
3871
  return new Emoji(this.client, data);
3802
3872
  }
3803
- async fetch(id) {
3873
+ /**
3874
+ * Fetch an Emoji from the API or resolves it from the local cache.
3875
+ * @param emoji The ID, mention, or {@link Emoji} object to fetch
3876
+ * @param force Whether to skip the cache check and force a direct API request. Defaults to false.
3877
+ * @returns A promise that resolves to the fetched {@link Emoji} object.
3878
+ * @throws {TypeError} If an invalid {@link EmojiResolvable} is provided.
3879
+ * @throws {Error} If the API request fails.
3880
+ * @example
3881
+ * // Fetch a channel, bypassing cache
3882
+ * const channel = await client.channels.fetch("01H...", true);
3883
+ */
3884
+ async fetch(emoji, force = false) {
3885
+ if (!force) {
3886
+ const cached = this.resolve(emoji);
3887
+ if (cached) return cached;
3888
+ }
3889
+ const id = this.resolveId(emoji);
3804
3890
  const data = await this.client.rest.get(`/custom/emoji/${id}`);
3805
3891
  return this._add(data);
3806
3892
  }
3893
+ /**
3894
+ * Resolves a {@link EmojiResolvable} to a {@link Emoji} object from the cache.
3895
+ * @param emoji The {@link EmojiResolvable} to resolve.
3896
+ * @returns The resolved {@link Emoji} object, or undefined if not found.
3897
+ */
3898
+ resolve(emoji) {
3899
+ if (emoji instanceof Emoji) return emoji;
3900
+ if (typeof emoji === "string") {
3901
+ const id = emoji.replace(/[<:>]/g, "");
3902
+ return this.cache.get(id);
3903
+ }
3904
+ return void 0;
3905
+ }
3906
+ /**
3907
+ * Extracts ID from a {@link EmojiResolvable}.
3908
+ * @param emoji The {@link EmojiResolvable} to extract the ID from.
3909
+ * @returns The extracted {@link Emoji} ID.
3910
+ * @throws {TypeError} If an invalid type is provided.
3911
+ */
3912
+ resolveId(emoji) {
3913
+ if (emoji instanceof Emoji) return emoji.id;
3914
+ if (typeof emoji === "string") {
3915
+ return emoji.replace(/:/g, "");
3916
+ }
3917
+ throw new Error("Invalid EmojiResolvable");
3918
+ }
3919
+ /**
3920
+ * Create a new emoji
3921
+ * @param options The options for creating the emoji
3922
+ * @returns A promise that resolves to the created {@link Emoji} object.
3923
+ * @throws {Error} If no server is registered in the {@link EmojiManager} or if the emoji attachment cannot be resolved.
3924
+ * @example
3925
+ * const emoji = await client.emojis.create(server, { emoji: "path/to/emoji.png", name: "myEmoji" });
3926
+ */
3927
+ async crate(options) {
3928
+ if (!this.server) throw new Error("No server registered in EmojiManager");
3929
+ const resolvedId = await resolveAttachment(this.client.rest, options.emoji, "emojis");
3930
+ if (!resolvedId) throw new Error("Failed to resolve emoji attachment");
3931
+ const payload = {
3932
+ name: options.name,
3933
+ parent: {
3934
+ type: "Server",
3935
+ id: this.server.id
3936
+ }
3937
+ };
3938
+ if (options.nsfw) payload.nsfw = options.nsfw;
3939
+ const data = await this.client.rest.put(`/custom/emoji/${resolvedId}`, payload);
3940
+ return this._add(data);
3941
+ }
3942
+ /**
3943
+ * Delete an emoji
3944
+ * @param emoji The {@link EmojiResolvable} to delete
3945
+ * @throws {Error} If no server is registered in the {@link EmojiManager}.
3946
+ * @example
3947
+ * await client.emojis.delete(emoji);
3948
+ */
3949
+ async delete(emoji) {
3950
+ const id = this.resolveId(emoji);
3951
+ await this.client.rest.delete(`/custom/emoji/${id}`);
3952
+ this.cache.delete(id);
3953
+ }
3954
+ /**
3955
+ * Edit an emoji
3956
+ * @param emoji The {@link EmojiResolvable} to edit
3957
+ * @param options The options to edit the emoji with
3958
+ * @returns A promise that resolves to the edited {@link Emoji} object.
3959
+ * @throws {Error} If no server is registered in the {@link EmojiManager}.
3960
+ * @example
3961
+ * const editedEmoji = await client.emojis.edit(emoji, { name: "newName" });
3962
+ */
3963
+ async edit(emoji, options) {
3964
+ const id = this.resolveId(emoji);
3965
+ const payload = {
3966
+ name: options.name
3967
+ };
3968
+ const data = await this.client.rest.patch(`/custom/emoji/${id}`, payload);
3969
+ return this._add(data);
3970
+ }
3807
3971
  [util11.inspect.custom]() {
3808
3972
  return this.cache;
3809
3973
  }
@@ -4207,8 +4371,257 @@ var SweeperManager = class {
4207
4371
  }
4208
4372
  };
4209
4373
 
4374
+ // src/voice/VoiceConnection.ts
4375
+ import {
4376
+ Room,
4377
+ RoomEvent,
4378
+ TrackSource,
4379
+ LocalAudioTrack,
4380
+ TrackPublishOptions,
4381
+ AudioFrame,
4382
+ AudioSource
4383
+ } from "@livekit/rtc-node";
4384
+ import { EventEmitter as EventEmitter2 } from "events";
4385
+ var SAMPLE_RATE = 48e3;
4386
+ var CHANNELS = 2;
4387
+ var SAMPLES_PER_FRAME = 960;
4388
+ var BYTES_PER_FRAME = SAMPLES_PER_FRAME * CHANNELS * 2;
4389
+ var VoiceConnection = class extends EventEmitter2 {
4390
+ channelId;
4391
+ guildId;
4392
+ room;
4393
+ _status = "connecting";
4394
+ _player = null;
4395
+ _audioSource = null;
4396
+ _audioTrack = null;
4397
+ constructor(channelId, guildId) {
4398
+ super();
4399
+ this.channelId = channelId;
4400
+ this.guildId = guildId;
4401
+ this.room = new Room();
4402
+ this.room.on(RoomEvent.Disconnected, () => {
4403
+ this._status = "disconnected";
4404
+ this._player?.removeSubscriber(this);
4405
+ this.emit("disconnect");
4406
+ });
4407
+ }
4408
+ get status() {
4409
+ return this._status;
4410
+ }
4411
+ get player() {
4412
+ return this._player;
4413
+ }
4414
+ /** @internal */
4415
+ async connect(url, token) {
4416
+ await this.room.connect(url, token);
4417
+ this._status = "ready";
4418
+ this.emit("ready");
4419
+ }
4420
+ subscribe(player) {
4421
+ if (this._player) this._player.removeSubscriber(this);
4422
+ this._player = player;
4423
+ player.addSubscriber(this);
4424
+ }
4425
+ unsubscribe() {
4426
+ this._player?.removeSubscriber(this);
4427
+ this._player = null;
4428
+ }
4429
+ async _feedStream(stream) {
4430
+ if (!this._audioSource) {
4431
+ this._audioSource = new AudioSource(SAMPLE_RATE, CHANNELS, SAMPLES_PER_FRAME);
4432
+ this._audioTrack = LocalAudioTrack.createAudioTrack("audio", this._audioSource);
4433
+ const options = new TrackPublishOptions();
4434
+ options.source = TrackSource.SOURCE_MICROPHONE;
4435
+ if (!this.room.localParticipant) {
4436
+ throw new Error("Not connected to a room");
4437
+ }
4438
+ await this.room.localParticipant.publishTrack(this._audioTrack, options);
4439
+ }
4440
+ let leftover = Buffer.alloc(0);
4441
+ for await (const chunk of stream) {
4442
+ const buf = Buffer.concat([leftover, chunk]);
4443
+ let offset = 0;
4444
+ while (offset + BYTES_PER_FRAME <= buf.length) {
4445
+ const slice = buf.subarray(offset, offset + BYTES_PER_FRAME);
4446
+ const tmp = slice.buffer.slice(slice.byteOffset, slice.byteOffset + slice.byteLength);
4447
+ const pcm = new Int16Array(tmp);
4448
+ await this._audioSource.captureFrame(new AudioFrame(pcm, SAMPLE_RATE, CHANNELS, SAMPLES_PER_FRAME));
4449
+ offset += BYTES_PER_FRAME;
4450
+ }
4451
+ leftover = buf.subarray(offset);
4452
+ }
4453
+ }
4454
+ async disconnect() {
4455
+ this._status = "disconnecting";
4456
+ this.unsubscribe();
4457
+ if (this._audioTrack) {
4458
+ await this._audioTrack.close();
4459
+ this._audioTrack = null;
4460
+ this._audioSource = null;
4461
+ }
4462
+ await this.room.disconnect();
4463
+ }
4464
+ };
4465
+
4466
+ // src/voice/VoiceManager.ts
4467
+ var VoiceManager = class {
4468
+ connections = /* @__PURE__ */ new Map();
4469
+ client;
4470
+ constructor(client) {
4471
+ this.client = client;
4472
+ }
4473
+ async join(channelId, guildId) {
4474
+ const existing = this.connections.get(channelId);
4475
+ if (existing?.status === "ready") return existing;
4476
+ const { token, url } = await this.client.rest.post(`/channels/${channelId}/join_call`);
4477
+ const connection = new VoiceConnection(channelId, guildId);
4478
+ this.connections.set(channelId, connection);
4479
+ connection.once("disconnect", () => this.connections.delete(channelId));
4480
+ await connection.connect(url, token);
4481
+ return connection;
4482
+ }
4483
+ get(channelId) {
4484
+ return this.connections.get(channelId);
4485
+ }
4486
+ async leave(channelId) {
4487
+ await this.connections.get(channelId)?.disconnect();
4488
+ }
4489
+ async leaveAll() {
4490
+ await Promise.all([...this.connections.keys()].map((id) => this.leave(id)));
4491
+ }
4492
+ };
4493
+
4494
+ // src/voice/AudioPlayer.ts
4495
+ import { EventEmitter as EventEmitter3 } from "events";
4496
+ var AudioPlayer = class extends EventEmitter3 {
4497
+ _status = "idle";
4498
+ _resource = null;
4499
+ /** Registered VoiceConnections subscribed to this player */
4500
+ subscribers = /* @__PURE__ */ new Set();
4501
+ get status() {
4502
+ return this._status;
4503
+ }
4504
+ get resource() {
4505
+ return this._resource;
4506
+ }
4507
+ play(resource) {
4508
+ this.transition("buffering");
4509
+ this._resource = resource;
4510
+ resource.stream.once("readable", () => {
4511
+ this.transition("playing");
4512
+ this.feed();
4513
+ });
4514
+ resource.stream.once("end", () => {
4515
+ this._resource = null;
4516
+ this.transition("idle");
4517
+ this.emit("idle");
4518
+ });
4519
+ resource.stream.once("error", (err) => {
4520
+ this._resource = null;
4521
+ this.transition("idle");
4522
+ this.emit("error", err);
4523
+ });
4524
+ }
4525
+ pause() {
4526
+ if (this._status !== "playing") return;
4527
+ this._resource?.stream.pause();
4528
+ this.transition("paused");
4529
+ }
4530
+ resume() {
4531
+ if (this._status !== "paused") return;
4532
+ this._resource?.stream.resume();
4533
+ this.transition("playing");
4534
+ }
4535
+ stop() {
4536
+ this._resource?.stream.destroy();
4537
+ this._resource = null;
4538
+ this.transition("stopped");
4539
+ this.transition("idle");
4540
+ this.emit("idle");
4541
+ }
4542
+ /** @internal */
4543
+ addSubscriber(conn) {
4544
+ this.subscribers.add(conn);
4545
+ }
4546
+ /** @internal */
4547
+ removeSubscriber(conn) {
4548
+ this.subscribers.delete(conn);
4549
+ }
4550
+ feed() {
4551
+ if (!this._resource) return;
4552
+ for (const conn of this.subscribers) {
4553
+ conn._feedStream(this._resource.stream);
4554
+ }
4555
+ }
4556
+ transition(next) {
4557
+ const prev = this._status;
4558
+ this._status = next;
4559
+ this.emit("stateChange", prev, next);
4560
+ }
4561
+ };
4562
+
4563
+ // src/voice/AudioResource.ts
4564
+ import { Readable } from "stream";
4565
+ import { spawn } from "child_process";
4566
+ import { PassThrough } from "stream";
4567
+ import { existsSync } from "fs";
4568
+ var AudioResource = class _AudioResource {
4569
+ stream;
4570
+ constructor(stream) {
4571
+ this.stream = stream;
4572
+ }
4573
+ static from(source, options = {}) {
4574
+ if (typeof source === "string" && !source.startsWith("http") && !existsSync(source)) {
4575
+ throw new Error(`Audio file not found: ${source}`);
4576
+ }
4577
+ const { volume = 1, inputType } = options;
4578
+ const args = [
4579
+ ...inputType ? ["-f", inputType] : [],
4580
+ "-i",
4581
+ typeof source === "string" ? source : "pipe:0",
4582
+ "-af",
4583
+ `volume=${volume}`,
4584
+ "-ar",
4585
+ "48000",
4586
+ "-ac",
4587
+ "2",
4588
+ "-f",
4589
+ "s16le",
4590
+ "-acodec",
4591
+ "pcm_s16le",
4592
+ "pipe:1"
4593
+ ];
4594
+ const ffmpeg = spawn("ffmpeg", args, { stdio: ["pipe", "pipe", "ignore"] });
4595
+ if (source instanceof Readable) {
4596
+ source.pipe(ffmpeg.stdin);
4597
+ source.once("error", () => ffmpeg.kill());
4598
+ }
4599
+ ffmpeg.stdin?.on("error", () => {
4600
+ });
4601
+ const pass = new PassThrough();
4602
+ ffmpeg.stdout.pipe(pass);
4603
+ ffmpeg.once("error", (err) => pass.destroy(err));
4604
+ ffmpeg.once("close", (code) => {
4605
+ if (code !== 0 && code !== null) {
4606
+ pass.destroy(new Error(`ffmpeg exited with code ${code}`));
4607
+ }
4608
+ });
4609
+ return new _AudioResource(pass);
4610
+ }
4611
+ };
4612
+
4210
4613
  // src/client/Client.ts
4211
- var Client = class extends EventEmitter2 {
4614
+ var Client = class extends EventEmitter4 {
4615
+ rest;
4616
+ gateway;
4617
+ channels;
4618
+ servers;
4619
+ users;
4620
+ sweepers;
4621
+ emojis;
4622
+ user = null;
4623
+ options;
4624
+ voice;
4212
4625
  constructor(options = {}) {
4213
4626
  super({ captureRejections: true });
4214
4627
  this.options = options;
@@ -4218,25 +4631,43 @@ var Client = class extends EventEmitter2 {
4218
4631
  this.servers = new ServerManager(this, options.cacheLimits?.servers);
4219
4632
  this.users = new UserManager(this, options.cacheLimits?.users);
4220
4633
  this.emojis = new EmojiManager(this, void 0, options.cacheLimits?.emojis);
4634
+ this.voice = new VoiceManager(this);
4221
4635
  this.sweepers = new SweeperManager(this, options.sweepers ?? {});
4222
4636
  }
4223
- rest;
4224
- gateway;
4225
- channels;
4226
- servers;
4227
- users;
4228
- sweepers;
4229
- emojis;
4230
- user = null;
4231
4637
  /**
4232
4638
  * Connects the bot to the Stoat Gateway
4233
4639
  */
4234
4640
  async login(token) {
4235
4641
  if (!token) throw new Error("A valid token must be provided.");
4236
- this.sweepers.start();
4642
+ const rootApiUrl = this.options.apiURL ?? "https://api.stoat.chat";
4643
+ this.rest.setBaseURL(rootApiUrl);
4237
4644
  this.rest.setToken(token);
4645
+ const configData = await this.fetchConfig(rootApiUrl);
4646
+ const finalWsUrl = this.options.overrides?.wsURL ?? configData.ws;
4647
+ if (!finalWsUrl) {
4648
+ throw new Error(
4649
+ `[Stoat Misconfiguration] The server at '${rootApiUrl}' is running, but it has no WebSocket URL configured. The server administrator needs to set their gateway config, or you must bypass it using 'options.overrides.wsURL'.`
4650
+ );
4651
+ }
4652
+ this.gateway.setGatewayUrl(finalWsUrl);
4653
+ const finalCdnUrl = this.options.overrides?.cdnURL ?? configData.features.autumn.url;
4654
+ if (!finalCdnUrl) {
4655
+ throw new Error(
4656
+ `[Stoat Misconfiguration] The server at '${rootApiUrl}' is running, but it has no CDN URL configured. The server administrator needs to set their cdn config, or you must bypass it using 'options.overrides.cdnURL'.`
4657
+ );
4658
+ }
4659
+ this.rest.setCDNURL(finalCdnUrl);
4660
+ this.sweepers.start();
4238
4661
  return this.gateway.connect(token);
4239
4662
  }
4663
+ async fetchConfig(baseURL) {
4664
+ try {
4665
+ const response = await fetch(baseURL);
4666
+ return await response.json();
4667
+ } catch (error) {
4668
+ throw new Error(`Failed to fetch ${baseURL}, make sure the instance is running.`);
4669
+ }
4670
+ }
4240
4671
  [/* @__PURE__ */ Symbol.for("nodejs.rejection")](error) {
4241
4672
  this.emit("error", error);
4242
4673
  }
@@ -4295,6 +4726,8 @@ export {
4295
4726
  API,
4296
4727
  Attachment,
4297
4728
  AttachmentBuilder,
4729
+ AudioPlayer,
4730
+ AudioResource,
4298
4731
  Base,
4299
4732
  BaseChannel,
4300
4733
  BitField,
@@ -4329,6 +4762,8 @@ export {
4329
4762
  TextChannel,
4330
4763
  UnknownChannel,
4331
4764
  User,
4332
- UserManager
4765
+ UserManager,
4766
+ VoiceConnection,
4767
+ VoiceManager
4333
4768
  };
4334
4769
  //# sourceMappingURL=index.js.map