@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/README.md +3 -113
- package/dist/index.cjs +448 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +185 -5
- package/dist/index.d.ts +185 -5
- package/dist/index.js +453 -18
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.cjs
CHANGED
|
@@ -33,6 +33,8 @@ __export(index_exports, {
|
|
|
33
33
|
API: () => API,
|
|
34
34
|
Attachment: () => Attachment,
|
|
35
35
|
AttachmentBuilder: () => AttachmentBuilder,
|
|
36
|
+
AudioPlayer: () => AudioPlayer,
|
|
37
|
+
AudioResource: () => AudioResource,
|
|
36
38
|
Base: () => Base,
|
|
37
39
|
BaseChannel: () => BaseChannel,
|
|
38
40
|
BitField: () => BitField,
|
|
@@ -67,7 +69,9 @@ __export(index_exports, {
|
|
|
67
69
|
TextChannel: () => TextChannel,
|
|
68
70
|
UnknownChannel: () => UnknownChannel,
|
|
69
71
|
User: () => User,
|
|
70
|
-
UserManager: () => UserManager
|
|
72
|
+
UserManager: () => UserManager,
|
|
73
|
+
VoiceConnection: () => VoiceConnection,
|
|
74
|
+
VoiceManager: () => VoiceManager
|
|
71
75
|
});
|
|
72
76
|
module.exports = __toCommonJS(index_exports);
|
|
73
77
|
|
|
@@ -829,6 +833,7 @@ var GatewayManager = class {
|
|
|
829
833
|
this.client = client;
|
|
830
834
|
}
|
|
831
835
|
ws = null;
|
|
836
|
+
wsURL = "";
|
|
832
837
|
pingInterval = null;
|
|
833
838
|
token = null;
|
|
834
839
|
reconnectAttempts = 0;
|
|
@@ -843,7 +848,7 @@ var GatewayManager = class {
|
|
|
843
848
|
this.ws.removeAllListeners();
|
|
844
849
|
this.ws = null;
|
|
845
850
|
}
|
|
846
|
-
const baseUrl = "wss://stoat.chat
|
|
851
|
+
const baseUrl = this.wsURL ?? "wss://events.stoat.chat";
|
|
847
852
|
const url = `${baseUrl}?version=1&format=json&token=${this.token}`;
|
|
848
853
|
this.ws = new import_ws.default(url);
|
|
849
854
|
this.ws.on("open", () => {
|
|
@@ -863,6 +868,9 @@ var GatewayManager = class {
|
|
|
863
868
|
this.client.emit("error", error);
|
|
864
869
|
});
|
|
865
870
|
}
|
|
871
|
+
setGatewayUrl(url) {
|
|
872
|
+
this.wsURL = url;
|
|
873
|
+
}
|
|
866
874
|
handleMessage(rawData) {
|
|
867
875
|
const payload = JSON.parse(rawData.toString());
|
|
868
876
|
const eventType = payload.type;
|
|
@@ -1359,12 +1367,19 @@ var RESTManager = class {
|
|
|
1359
1367
|
}
|
|
1360
1368
|
}, 3e5).unref();
|
|
1361
1369
|
}
|
|
1362
|
-
baseURL = "
|
|
1370
|
+
baseURL = "";
|
|
1371
|
+
cdnURL = "";
|
|
1363
1372
|
token = null;
|
|
1364
1373
|
buckets = /* @__PURE__ */ new Map();
|
|
1365
1374
|
setToken(token) {
|
|
1366
1375
|
this.token = token;
|
|
1367
1376
|
}
|
|
1377
|
+
setBaseURL(baseURL) {
|
|
1378
|
+
this.baseURL = baseURL.replace(/\/+$/, "");
|
|
1379
|
+
}
|
|
1380
|
+
setCDNURL(cdnURL) {
|
|
1381
|
+
this.cdnURL = cdnURL.replace(/\/+$/, "");
|
|
1382
|
+
}
|
|
1368
1383
|
/**
|
|
1369
1384
|
* Generates a local identifier for the bucket based on method and path.
|
|
1370
1385
|
*/
|
|
@@ -1417,13 +1432,14 @@ var RESTManager = class {
|
|
|
1417
1432
|
this.client.emit("debug", `Bucket [${method}:${endpoint}] exhausted. Waiting ${waitTime}ms proactively...`);
|
|
1418
1433
|
await sleep(waitTime);
|
|
1419
1434
|
}
|
|
1435
|
+
const isBodyMethod = ["post", "patch", "put"].includes(method.toLowerCase());
|
|
1420
1436
|
const response = await fetch(url, {
|
|
1421
1437
|
method: method.toUpperCase(),
|
|
1422
1438
|
headers: {
|
|
1423
1439
|
"X-Bot-Token": this.token,
|
|
1424
1440
|
"Content-Type": "application/json"
|
|
1425
1441
|
},
|
|
1426
|
-
...
|
|
1442
|
+
...isBodyMethod ? { body: JSON.stringify(body ?? {}) } : {}
|
|
1427
1443
|
});
|
|
1428
1444
|
const remainingHeader = response.headers.get("x-ratelimit-remaining");
|
|
1429
1445
|
const resetAfterHeader = response.headers.get("x-ratelimit-reset-after");
|
|
@@ -1456,7 +1472,7 @@ var RESTManager = class {
|
|
|
1456
1472
|
*/
|
|
1457
1473
|
async uploadFile(tag, fileBuffer, filename) {
|
|
1458
1474
|
if (!this.token) throw new Error("REST_NOT_READY: No token available.");
|
|
1459
|
-
const url =
|
|
1475
|
+
const url = `${this.cdnURL}/${tag}`;
|
|
1460
1476
|
const formData = new FormData();
|
|
1461
1477
|
formData.append("file", new Blob([fileBuffer]), filename);
|
|
1462
1478
|
const response = await fetch(url, {
|
|
@@ -1959,6 +1975,9 @@ var BaseChannel = class extends Base {
|
|
|
1959
1975
|
isGroup() {
|
|
1960
1976
|
return this.type === "Group";
|
|
1961
1977
|
}
|
|
1978
|
+
isVoice() {
|
|
1979
|
+
return this.isText() && this.voice !== null;
|
|
1980
|
+
}
|
|
1962
1981
|
};
|
|
1963
1982
|
|
|
1964
1983
|
// src/structures/TextChannel.ts
|
|
@@ -2269,10 +2288,21 @@ var GroupChannel = class extends BaseChannel {
|
|
|
2269
2288
|
}
|
|
2270
2289
|
};
|
|
2271
2290
|
|
|
2291
|
+
// src/structures/VoiceChannel.ts
|
|
2292
|
+
var VoiceChannel = class extends TextChannel {
|
|
2293
|
+
async join() {
|
|
2294
|
+
return this.client.voice.join(this.id, this.serverId);
|
|
2295
|
+
}
|
|
2296
|
+
async leave() {
|
|
2297
|
+
return this.client.voice.leave(this.id);
|
|
2298
|
+
}
|
|
2299
|
+
};
|
|
2300
|
+
|
|
2272
2301
|
// src/utils/ChannelFactory.ts
|
|
2273
2302
|
function createChannel(client, data) {
|
|
2274
2303
|
switch (data.channel_type) {
|
|
2275
2304
|
case "TextChannel":
|
|
2305
|
+
if (data.voice) return new VoiceChannel(client, data);
|
|
2276
2306
|
return new TextChannel(client, data);
|
|
2277
2307
|
case "DirectMessage":
|
|
2278
2308
|
return new DMChannel(client, data);
|
|
@@ -3852,6 +3882,50 @@ var Emoji = class extends Base {
|
|
|
3852
3882
|
if (data.animated !== void 0) this.animated = data.animated;
|
|
3853
3883
|
if (data.nsfw !== void 0) this.nsfw = data.nsfw;
|
|
3854
3884
|
}
|
|
3885
|
+
/**
|
|
3886
|
+
* Fetch this emoji from the API or resolves it from the local cache.
|
|
3887
|
+
* @param force Whether to skip the cache check and force a direct API request. Defaults to false.
|
|
3888
|
+
* @returns A promise that resolves to the fetched {@link Emoji} object.
|
|
3889
|
+
* @throws {Error} If the API request fails or if the emoji is detached.
|
|
3890
|
+
* @example
|
|
3891
|
+
* // Force fetch emoji to update its data
|
|
3892
|
+
* await emoji.fetch(true);
|
|
3893
|
+
*/
|
|
3894
|
+
async fetch(force) {
|
|
3895
|
+
const server = this.client.servers.cache.get(this.parent.type === "Server" ? this.parent.id : "");
|
|
3896
|
+
if (!server) throw new Error("No server registered in EmojiManager");
|
|
3897
|
+
if (this.parent.type === "Detached") throw new Error("Cannot fetch a detached emoji");
|
|
3898
|
+
return await server.emojis.fetch(this, force);
|
|
3899
|
+
}
|
|
3900
|
+
/**
|
|
3901
|
+
* Deletes this emoji from the server.
|
|
3902
|
+
* @returns A promise that resolves when the emoji has been deleted.
|
|
3903
|
+
* @throws {Error} If the API request fails or if the emoji is detached.
|
|
3904
|
+
* @example
|
|
3905
|
+
* // Delete an emoji from the server
|
|
3906
|
+
* await emoji.delete();
|
|
3907
|
+
*/
|
|
3908
|
+
async delete() {
|
|
3909
|
+
const server = this.client.servers.cache.get(this.parent.type === "Server" ? this.parent.id : "");
|
|
3910
|
+
if (!server) throw new Error("No server registered in EmojiManager");
|
|
3911
|
+
if (this.parent.type === "Detached") throw new Error("Emoji is already detached");
|
|
3912
|
+
await server.emojis.delete(this);
|
|
3913
|
+
}
|
|
3914
|
+
/**
|
|
3915
|
+
* Edits this emoji's properties.
|
|
3916
|
+
* @param options The options to edit the emoji with.
|
|
3917
|
+
* @returns A promise that resolves to the edited {@link Emoji} object.
|
|
3918
|
+
* @throws {Error} If the API request fails or if the emoji is detached.
|
|
3919
|
+
* @example
|
|
3920
|
+
* // Edit an emoji's name
|
|
3921
|
+
* await emoji.edit({ name: "new_name" });
|
|
3922
|
+
*/
|
|
3923
|
+
async edit(options) {
|
|
3924
|
+
const server = this.client.servers.cache.get(this.parent.type === "Server" ? this.parent.id : "");
|
|
3925
|
+
if (!server) throw new Error("No server registered in EmojiManager");
|
|
3926
|
+
if (this.parent.type === "Detached") throw new Error("Cannot edit a detached emoji");
|
|
3927
|
+
return await server.emojis.edit(this, options);
|
|
3928
|
+
}
|
|
3855
3929
|
};
|
|
3856
3930
|
|
|
3857
3931
|
// src/managers/EmojiManager.ts
|
|
@@ -3865,7 +3939,7 @@ var EmojiManager = class extends BaseManager {
|
|
|
3865
3939
|
* Tell BaseManager how to find the ID for Emojis
|
|
3866
3940
|
*/
|
|
3867
3941
|
extractId(data) {
|
|
3868
|
-
return data._id
|
|
3942
|
+
return data._id;
|
|
3869
3943
|
}
|
|
3870
3944
|
/**
|
|
3871
3945
|
* Tell BaseManager how to build an Emoji
|
|
@@ -3873,10 +3947,104 @@ var EmojiManager = class extends BaseManager {
|
|
|
3873
3947
|
construct(data) {
|
|
3874
3948
|
return new Emoji(this.client, data);
|
|
3875
3949
|
}
|
|
3876
|
-
|
|
3950
|
+
/**
|
|
3951
|
+
* Fetch an Emoji from the API or resolves it from the local cache.
|
|
3952
|
+
* @param emoji The ID, mention, or {@link Emoji} object to fetch
|
|
3953
|
+
* @param force Whether to skip the cache check and force a direct API request. Defaults to false.
|
|
3954
|
+
* @returns A promise that resolves to the fetched {@link Emoji} object.
|
|
3955
|
+
* @throws {TypeError} If an invalid {@link EmojiResolvable} is provided.
|
|
3956
|
+
* @throws {Error} If the API request fails.
|
|
3957
|
+
* @example
|
|
3958
|
+
* // Fetch a channel, bypassing cache
|
|
3959
|
+
* const channel = await client.channels.fetch("01H...", true);
|
|
3960
|
+
*/
|
|
3961
|
+
async fetch(emoji, force = false) {
|
|
3962
|
+
if (!force) {
|
|
3963
|
+
const cached = this.resolve(emoji);
|
|
3964
|
+
if (cached) return cached;
|
|
3965
|
+
}
|
|
3966
|
+
const id = this.resolveId(emoji);
|
|
3877
3967
|
const data = await this.client.rest.get(`/custom/emoji/${id}`);
|
|
3878
3968
|
return this._add(data);
|
|
3879
3969
|
}
|
|
3970
|
+
/**
|
|
3971
|
+
* Resolves a {@link EmojiResolvable} to a {@link Emoji} object from the cache.
|
|
3972
|
+
* @param emoji The {@link EmojiResolvable} to resolve.
|
|
3973
|
+
* @returns The resolved {@link Emoji} object, or undefined if not found.
|
|
3974
|
+
*/
|
|
3975
|
+
resolve(emoji) {
|
|
3976
|
+
if (emoji instanceof Emoji) return emoji;
|
|
3977
|
+
if (typeof emoji === "string") {
|
|
3978
|
+
const id = emoji.replace(/[<:>]/g, "");
|
|
3979
|
+
return this.cache.get(id);
|
|
3980
|
+
}
|
|
3981
|
+
return void 0;
|
|
3982
|
+
}
|
|
3983
|
+
/**
|
|
3984
|
+
* Extracts ID from a {@link EmojiResolvable}.
|
|
3985
|
+
* @param emoji The {@link EmojiResolvable} to extract the ID from.
|
|
3986
|
+
* @returns The extracted {@link Emoji} ID.
|
|
3987
|
+
* @throws {TypeError} If an invalid type is provided.
|
|
3988
|
+
*/
|
|
3989
|
+
resolveId(emoji) {
|
|
3990
|
+
if (emoji instanceof Emoji) return emoji.id;
|
|
3991
|
+
if (typeof emoji === "string") {
|
|
3992
|
+
return emoji.replace(/:/g, "");
|
|
3993
|
+
}
|
|
3994
|
+
throw new Error("Invalid EmojiResolvable");
|
|
3995
|
+
}
|
|
3996
|
+
/**
|
|
3997
|
+
* Create a new emoji
|
|
3998
|
+
* @param options The options for creating the emoji
|
|
3999
|
+
* @returns A promise that resolves to the created {@link Emoji} object.
|
|
4000
|
+
* @throws {Error} If no server is registered in the {@link EmojiManager} or if the emoji attachment cannot be resolved.
|
|
4001
|
+
* @example
|
|
4002
|
+
* const emoji = await client.emojis.create(server, { emoji: "path/to/emoji.png", name: "myEmoji" });
|
|
4003
|
+
*/
|
|
4004
|
+
async crate(options) {
|
|
4005
|
+
if (!this.server) throw new Error("No server registered in EmojiManager");
|
|
4006
|
+
const resolvedId = await resolveAttachment(this.client.rest, options.emoji, "emojis");
|
|
4007
|
+
if (!resolvedId) throw new Error("Failed to resolve emoji attachment");
|
|
4008
|
+
const payload = {
|
|
4009
|
+
name: options.name,
|
|
4010
|
+
parent: {
|
|
4011
|
+
type: "Server",
|
|
4012
|
+
id: this.server.id
|
|
4013
|
+
}
|
|
4014
|
+
};
|
|
4015
|
+
if (options.nsfw) payload.nsfw = options.nsfw;
|
|
4016
|
+
const data = await this.client.rest.put(`/custom/emoji/${resolvedId}`, payload);
|
|
4017
|
+
return this._add(data);
|
|
4018
|
+
}
|
|
4019
|
+
/**
|
|
4020
|
+
* Delete an emoji
|
|
4021
|
+
* @param emoji The {@link EmojiResolvable} to delete
|
|
4022
|
+
* @throws {Error} If no server is registered in the {@link EmojiManager}.
|
|
4023
|
+
* @example
|
|
4024
|
+
* await client.emojis.delete(emoji);
|
|
4025
|
+
*/
|
|
4026
|
+
async delete(emoji) {
|
|
4027
|
+
const id = this.resolveId(emoji);
|
|
4028
|
+
await this.client.rest.delete(`/custom/emoji/${id}`);
|
|
4029
|
+
this.cache.delete(id);
|
|
4030
|
+
}
|
|
4031
|
+
/**
|
|
4032
|
+
* Edit an emoji
|
|
4033
|
+
* @param emoji The {@link EmojiResolvable} to edit
|
|
4034
|
+
* @param options The options to edit the emoji with
|
|
4035
|
+
* @returns A promise that resolves to the edited {@link Emoji} object.
|
|
4036
|
+
* @throws {Error} If no server is registered in the {@link EmojiManager}.
|
|
4037
|
+
* @example
|
|
4038
|
+
* const editedEmoji = await client.emojis.edit(emoji, { name: "newName" });
|
|
4039
|
+
*/
|
|
4040
|
+
async edit(emoji, options) {
|
|
4041
|
+
const id = this.resolveId(emoji);
|
|
4042
|
+
const payload = {
|
|
4043
|
+
name: options.name
|
|
4044
|
+
};
|
|
4045
|
+
const data = await this.client.rest.patch(`/custom/emoji/${id}`, payload);
|
|
4046
|
+
return this._add(data);
|
|
4047
|
+
}
|
|
3880
4048
|
[util11.inspect.custom]() {
|
|
3881
4049
|
return this.cache;
|
|
3882
4050
|
}
|
|
@@ -4280,8 +4448,249 @@ var SweeperManager = class {
|
|
|
4280
4448
|
}
|
|
4281
4449
|
};
|
|
4282
4450
|
|
|
4451
|
+
// src/voice/VoiceConnection.ts
|
|
4452
|
+
var import_rtc_node = require("@livekit/rtc-node");
|
|
4453
|
+
var import_node_events = require("events");
|
|
4454
|
+
var SAMPLE_RATE = 48e3;
|
|
4455
|
+
var CHANNELS = 2;
|
|
4456
|
+
var SAMPLES_PER_FRAME = 960;
|
|
4457
|
+
var BYTES_PER_FRAME = SAMPLES_PER_FRAME * CHANNELS * 2;
|
|
4458
|
+
var VoiceConnection = class extends import_node_events.EventEmitter {
|
|
4459
|
+
channelId;
|
|
4460
|
+
guildId;
|
|
4461
|
+
room;
|
|
4462
|
+
_status = "connecting";
|
|
4463
|
+
_player = null;
|
|
4464
|
+
_audioSource = null;
|
|
4465
|
+
_audioTrack = null;
|
|
4466
|
+
constructor(channelId, guildId) {
|
|
4467
|
+
super();
|
|
4468
|
+
this.channelId = channelId;
|
|
4469
|
+
this.guildId = guildId;
|
|
4470
|
+
this.room = new import_rtc_node.Room();
|
|
4471
|
+
this.room.on(import_rtc_node.RoomEvent.Disconnected, () => {
|
|
4472
|
+
this._status = "disconnected";
|
|
4473
|
+
this._player?.removeSubscriber(this);
|
|
4474
|
+
this.emit("disconnect");
|
|
4475
|
+
});
|
|
4476
|
+
}
|
|
4477
|
+
get status() {
|
|
4478
|
+
return this._status;
|
|
4479
|
+
}
|
|
4480
|
+
get player() {
|
|
4481
|
+
return this._player;
|
|
4482
|
+
}
|
|
4483
|
+
/** @internal */
|
|
4484
|
+
async connect(url, token) {
|
|
4485
|
+
await this.room.connect(url, token);
|
|
4486
|
+
this._status = "ready";
|
|
4487
|
+
this.emit("ready");
|
|
4488
|
+
}
|
|
4489
|
+
subscribe(player) {
|
|
4490
|
+
if (this._player) this._player.removeSubscriber(this);
|
|
4491
|
+
this._player = player;
|
|
4492
|
+
player.addSubscriber(this);
|
|
4493
|
+
}
|
|
4494
|
+
unsubscribe() {
|
|
4495
|
+
this._player?.removeSubscriber(this);
|
|
4496
|
+
this._player = null;
|
|
4497
|
+
}
|
|
4498
|
+
async _feedStream(stream) {
|
|
4499
|
+
if (!this._audioSource) {
|
|
4500
|
+
this._audioSource = new import_rtc_node.AudioSource(SAMPLE_RATE, CHANNELS, SAMPLES_PER_FRAME);
|
|
4501
|
+
this._audioTrack = import_rtc_node.LocalAudioTrack.createAudioTrack("audio", this._audioSource);
|
|
4502
|
+
const options = new import_rtc_node.TrackPublishOptions();
|
|
4503
|
+
options.source = import_rtc_node.TrackSource.SOURCE_MICROPHONE;
|
|
4504
|
+
if (!this.room.localParticipant) {
|
|
4505
|
+
throw new Error("Not connected to a room");
|
|
4506
|
+
}
|
|
4507
|
+
await this.room.localParticipant.publishTrack(this._audioTrack, options);
|
|
4508
|
+
}
|
|
4509
|
+
let leftover = Buffer.alloc(0);
|
|
4510
|
+
for await (const chunk of stream) {
|
|
4511
|
+
const buf = Buffer.concat([leftover, chunk]);
|
|
4512
|
+
let offset = 0;
|
|
4513
|
+
while (offset + BYTES_PER_FRAME <= buf.length) {
|
|
4514
|
+
const slice = buf.subarray(offset, offset + BYTES_PER_FRAME);
|
|
4515
|
+
const tmp = slice.buffer.slice(slice.byteOffset, slice.byteOffset + slice.byteLength);
|
|
4516
|
+
const pcm = new Int16Array(tmp);
|
|
4517
|
+
await this._audioSource.captureFrame(new import_rtc_node.AudioFrame(pcm, SAMPLE_RATE, CHANNELS, SAMPLES_PER_FRAME));
|
|
4518
|
+
offset += BYTES_PER_FRAME;
|
|
4519
|
+
}
|
|
4520
|
+
leftover = buf.subarray(offset);
|
|
4521
|
+
}
|
|
4522
|
+
}
|
|
4523
|
+
async disconnect() {
|
|
4524
|
+
this._status = "disconnecting";
|
|
4525
|
+
this.unsubscribe();
|
|
4526
|
+
if (this._audioTrack) {
|
|
4527
|
+
await this._audioTrack.close();
|
|
4528
|
+
this._audioTrack = null;
|
|
4529
|
+
this._audioSource = null;
|
|
4530
|
+
}
|
|
4531
|
+
await this.room.disconnect();
|
|
4532
|
+
}
|
|
4533
|
+
};
|
|
4534
|
+
|
|
4535
|
+
// src/voice/VoiceManager.ts
|
|
4536
|
+
var VoiceManager = class {
|
|
4537
|
+
connections = /* @__PURE__ */ new Map();
|
|
4538
|
+
client;
|
|
4539
|
+
constructor(client) {
|
|
4540
|
+
this.client = client;
|
|
4541
|
+
}
|
|
4542
|
+
async join(channelId, guildId) {
|
|
4543
|
+
const existing = this.connections.get(channelId);
|
|
4544
|
+
if (existing?.status === "ready") return existing;
|
|
4545
|
+
const { token, url } = await this.client.rest.post(`/channels/${channelId}/join_call`);
|
|
4546
|
+
const connection = new VoiceConnection(channelId, guildId);
|
|
4547
|
+
this.connections.set(channelId, connection);
|
|
4548
|
+
connection.once("disconnect", () => this.connections.delete(channelId));
|
|
4549
|
+
await connection.connect(url, token);
|
|
4550
|
+
return connection;
|
|
4551
|
+
}
|
|
4552
|
+
get(channelId) {
|
|
4553
|
+
return this.connections.get(channelId);
|
|
4554
|
+
}
|
|
4555
|
+
async leave(channelId) {
|
|
4556
|
+
await this.connections.get(channelId)?.disconnect();
|
|
4557
|
+
}
|
|
4558
|
+
async leaveAll() {
|
|
4559
|
+
await Promise.all([...this.connections.keys()].map((id) => this.leave(id)));
|
|
4560
|
+
}
|
|
4561
|
+
};
|
|
4562
|
+
|
|
4563
|
+
// src/voice/AudioPlayer.ts
|
|
4564
|
+
var import_node_events2 = require("events");
|
|
4565
|
+
var AudioPlayer = class extends import_node_events2.EventEmitter {
|
|
4566
|
+
_status = "idle";
|
|
4567
|
+
_resource = null;
|
|
4568
|
+
/** Registered VoiceConnections subscribed to this player */
|
|
4569
|
+
subscribers = /* @__PURE__ */ new Set();
|
|
4570
|
+
get status() {
|
|
4571
|
+
return this._status;
|
|
4572
|
+
}
|
|
4573
|
+
get resource() {
|
|
4574
|
+
return this._resource;
|
|
4575
|
+
}
|
|
4576
|
+
play(resource) {
|
|
4577
|
+
this.transition("buffering");
|
|
4578
|
+
this._resource = resource;
|
|
4579
|
+
resource.stream.once("readable", () => {
|
|
4580
|
+
this.transition("playing");
|
|
4581
|
+
this.feed();
|
|
4582
|
+
});
|
|
4583
|
+
resource.stream.once("end", () => {
|
|
4584
|
+
this._resource = null;
|
|
4585
|
+
this.transition("idle");
|
|
4586
|
+
this.emit("idle");
|
|
4587
|
+
});
|
|
4588
|
+
resource.stream.once("error", (err) => {
|
|
4589
|
+
this._resource = null;
|
|
4590
|
+
this.transition("idle");
|
|
4591
|
+
this.emit("error", err);
|
|
4592
|
+
});
|
|
4593
|
+
}
|
|
4594
|
+
pause() {
|
|
4595
|
+
if (this._status !== "playing") return;
|
|
4596
|
+
this._resource?.stream.pause();
|
|
4597
|
+
this.transition("paused");
|
|
4598
|
+
}
|
|
4599
|
+
resume() {
|
|
4600
|
+
if (this._status !== "paused") return;
|
|
4601
|
+
this._resource?.stream.resume();
|
|
4602
|
+
this.transition("playing");
|
|
4603
|
+
}
|
|
4604
|
+
stop() {
|
|
4605
|
+
this._resource?.stream.destroy();
|
|
4606
|
+
this._resource = null;
|
|
4607
|
+
this.transition("stopped");
|
|
4608
|
+
this.transition("idle");
|
|
4609
|
+
this.emit("idle");
|
|
4610
|
+
}
|
|
4611
|
+
/** @internal */
|
|
4612
|
+
addSubscriber(conn) {
|
|
4613
|
+
this.subscribers.add(conn);
|
|
4614
|
+
}
|
|
4615
|
+
/** @internal */
|
|
4616
|
+
removeSubscriber(conn) {
|
|
4617
|
+
this.subscribers.delete(conn);
|
|
4618
|
+
}
|
|
4619
|
+
feed() {
|
|
4620
|
+
if (!this._resource) return;
|
|
4621
|
+
for (const conn of this.subscribers) {
|
|
4622
|
+
conn._feedStream(this._resource.stream);
|
|
4623
|
+
}
|
|
4624
|
+
}
|
|
4625
|
+
transition(next) {
|
|
4626
|
+
const prev = this._status;
|
|
4627
|
+
this._status = next;
|
|
4628
|
+
this.emit("stateChange", prev, next);
|
|
4629
|
+
}
|
|
4630
|
+
};
|
|
4631
|
+
|
|
4632
|
+
// src/voice/AudioResource.ts
|
|
4633
|
+
var import_node_stream = require("stream");
|
|
4634
|
+
var import_node_child_process = require("child_process");
|
|
4635
|
+
var import_node_stream2 = require("stream");
|
|
4636
|
+
var import_node_fs = require("fs");
|
|
4637
|
+
var AudioResource = class _AudioResource {
|
|
4638
|
+
stream;
|
|
4639
|
+
constructor(stream) {
|
|
4640
|
+
this.stream = stream;
|
|
4641
|
+
}
|
|
4642
|
+
static from(source, options = {}) {
|
|
4643
|
+
if (typeof source === "string" && !source.startsWith("http") && !(0, import_node_fs.existsSync)(source)) {
|
|
4644
|
+
throw new Error(`Audio file not found: ${source}`);
|
|
4645
|
+
}
|
|
4646
|
+
const { volume = 1, inputType } = options;
|
|
4647
|
+
const args = [
|
|
4648
|
+
...inputType ? ["-f", inputType] : [],
|
|
4649
|
+
"-i",
|
|
4650
|
+
typeof source === "string" ? source : "pipe:0",
|
|
4651
|
+
"-af",
|
|
4652
|
+
`volume=${volume}`,
|
|
4653
|
+
"-ar",
|
|
4654
|
+
"48000",
|
|
4655
|
+
"-ac",
|
|
4656
|
+
"2",
|
|
4657
|
+
"-f",
|
|
4658
|
+
"s16le",
|
|
4659
|
+
"-acodec",
|
|
4660
|
+
"pcm_s16le",
|
|
4661
|
+
"pipe:1"
|
|
4662
|
+
];
|
|
4663
|
+
const ffmpeg = (0, import_node_child_process.spawn)("ffmpeg", args, { stdio: ["pipe", "pipe", "ignore"] });
|
|
4664
|
+
if (source instanceof import_node_stream.Readable) {
|
|
4665
|
+
source.pipe(ffmpeg.stdin);
|
|
4666
|
+
source.once("error", () => ffmpeg.kill());
|
|
4667
|
+
}
|
|
4668
|
+
ffmpeg.stdin?.on("error", () => {
|
|
4669
|
+
});
|
|
4670
|
+
const pass = new import_node_stream2.PassThrough();
|
|
4671
|
+
ffmpeg.stdout.pipe(pass);
|
|
4672
|
+
ffmpeg.once("error", (err) => pass.destroy(err));
|
|
4673
|
+
ffmpeg.once("close", (code) => {
|
|
4674
|
+
if (code !== 0 && code !== null) {
|
|
4675
|
+
pass.destroy(new Error(`ffmpeg exited with code ${code}`));
|
|
4676
|
+
}
|
|
4677
|
+
});
|
|
4678
|
+
return new _AudioResource(pass);
|
|
4679
|
+
}
|
|
4680
|
+
};
|
|
4681
|
+
|
|
4283
4682
|
// src/client/Client.ts
|
|
4284
4683
|
var Client = class extends import_events2.EventEmitter {
|
|
4684
|
+
rest;
|
|
4685
|
+
gateway;
|
|
4686
|
+
channels;
|
|
4687
|
+
servers;
|
|
4688
|
+
users;
|
|
4689
|
+
sweepers;
|
|
4690
|
+
emojis;
|
|
4691
|
+
user = null;
|
|
4692
|
+
options;
|
|
4693
|
+
voice;
|
|
4285
4694
|
constructor(options = {}) {
|
|
4286
4695
|
super({ captureRejections: true });
|
|
4287
4696
|
this.options = options;
|
|
@@ -4291,25 +4700,43 @@ var Client = class extends import_events2.EventEmitter {
|
|
|
4291
4700
|
this.servers = new ServerManager(this, options.cacheLimits?.servers);
|
|
4292
4701
|
this.users = new UserManager(this, options.cacheLimits?.users);
|
|
4293
4702
|
this.emojis = new EmojiManager(this, void 0, options.cacheLimits?.emojis);
|
|
4703
|
+
this.voice = new VoiceManager(this);
|
|
4294
4704
|
this.sweepers = new SweeperManager(this, options.sweepers ?? {});
|
|
4295
4705
|
}
|
|
4296
|
-
rest;
|
|
4297
|
-
gateway;
|
|
4298
|
-
channels;
|
|
4299
|
-
servers;
|
|
4300
|
-
users;
|
|
4301
|
-
sweepers;
|
|
4302
|
-
emojis;
|
|
4303
|
-
user = null;
|
|
4304
4706
|
/**
|
|
4305
4707
|
* Connects the bot to the Stoat Gateway
|
|
4306
4708
|
*/
|
|
4307
4709
|
async login(token) {
|
|
4308
4710
|
if (!token) throw new Error("A valid token must be provided.");
|
|
4309
|
-
this.
|
|
4711
|
+
const rootApiUrl = this.options.apiURL ?? "https://api.stoat.chat";
|
|
4712
|
+
this.rest.setBaseURL(rootApiUrl);
|
|
4310
4713
|
this.rest.setToken(token);
|
|
4714
|
+
const configData = await this.fetchConfig(rootApiUrl);
|
|
4715
|
+
const finalWsUrl = this.options.overrides?.wsURL ?? configData.ws;
|
|
4716
|
+
if (!finalWsUrl) {
|
|
4717
|
+
throw new Error(
|
|
4718
|
+
`[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'.`
|
|
4719
|
+
);
|
|
4720
|
+
}
|
|
4721
|
+
this.gateway.setGatewayUrl(finalWsUrl);
|
|
4722
|
+
const finalCdnUrl = this.options.overrides?.cdnURL ?? configData.features.autumn.url;
|
|
4723
|
+
if (!finalCdnUrl) {
|
|
4724
|
+
throw new Error(
|
|
4725
|
+
`[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'.`
|
|
4726
|
+
);
|
|
4727
|
+
}
|
|
4728
|
+
this.rest.setCDNURL(finalCdnUrl);
|
|
4729
|
+
this.sweepers.start();
|
|
4311
4730
|
return this.gateway.connect(token);
|
|
4312
4731
|
}
|
|
4732
|
+
async fetchConfig(baseURL) {
|
|
4733
|
+
try {
|
|
4734
|
+
const response = await fetch(baseURL);
|
|
4735
|
+
return await response.json();
|
|
4736
|
+
} catch (error) {
|
|
4737
|
+
throw new Error(`Failed to fetch ${baseURL}, make sure the instance is running.`);
|
|
4738
|
+
}
|
|
4739
|
+
}
|
|
4313
4740
|
[/* @__PURE__ */ Symbol.for("nodejs.rejection")](error) {
|
|
4314
4741
|
this.emit("error", error);
|
|
4315
4742
|
}
|
|
@@ -4369,6 +4796,8 @@ var API = __toESM(require("stoat-api"), 1);
|
|
|
4369
4796
|
API,
|
|
4370
4797
|
Attachment,
|
|
4371
4798
|
AttachmentBuilder,
|
|
4799
|
+
AudioPlayer,
|
|
4800
|
+
AudioResource,
|
|
4372
4801
|
Base,
|
|
4373
4802
|
BaseChannel,
|
|
4374
4803
|
BitField,
|
|
@@ -4403,6 +4832,8 @@ var API = __toESM(require("stoat-api"), 1);
|
|
|
4403
4832
|
TextChannel,
|
|
4404
4833
|
UnknownChannel,
|
|
4405
4834
|
User,
|
|
4406
|
-
UserManager
|
|
4835
|
+
UserManager,
|
|
4836
|
+
VoiceConnection,
|
|
4837
|
+
VoiceManager
|
|
4407
4838
|
});
|
|
4408
4839
|
//# sourceMappingURL=index.cjs.map
|