@stoatx/client 0.7.0 → 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.d.cts CHANGED
@@ -4,6 +4,8 @@ import * as stoatApi from 'stoat-api';
4
4
  export { stoatApi as API };
5
5
  import * as util from 'node:util';
6
6
  import util__default from 'node:util';
7
+ import { Readable } from 'node:stream';
8
+ import { EventEmitter as EventEmitter$1 } from 'node:events';
7
9
 
8
10
  declare class GatewayManager {
9
11
  private client;
@@ -1142,6 +1144,82 @@ declare class MessageCollector extends Collector<string, Message> {
1142
1144
  endReason(): string | null;
1143
1145
  }
1144
1146
 
1147
+ interface AudioResourceOptions {
1148
+ volume?: number;
1149
+ inputType?: string;
1150
+ }
1151
+ type AudioSource = string | Readable;
1152
+ declare class AudioResource {
1153
+ readonly stream: Readable;
1154
+ private constructor();
1155
+ static from(source: AudioSource, options?: AudioResourceOptions): AudioResource;
1156
+ }
1157
+
1158
+ type AudioPlayerStatus = "idle" | "buffering" | "playing" | "paused" | "stopped";
1159
+ interface AudioPlayerEvents {
1160
+ stateChange: [oldStatus: AudioPlayerStatus, newStatus: AudioPlayerStatus];
1161
+ idle: [];
1162
+ error: [error: Error];
1163
+ }
1164
+ declare class AudioPlayer extends EventEmitter$1<AudioPlayerEvents> {
1165
+ private _status;
1166
+ private _resource;
1167
+ /** Registered VoiceConnections subscribed to this player */
1168
+ private readonly subscribers;
1169
+ get status(): AudioPlayerStatus;
1170
+ get resource(): AudioResource | null;
1171
+ play(resource: AudioResource): void;
1172
+ pause(): void;
1173
+ resume(): void;
1174
+ stop(): void;
1175
+ /** @internal */
1176
+ addSubscriber(conn: VoiceConnection): void;
1177
+ /** @internal */
1178
+ removeSubscriber(conn: VoiceConnection): void;
1179
+ private feed;
1180
+ private transition;
1181
+ }
1182
+
1183
+ interface VoiceConnectionEvents {
1184
+ ready: [];
1185
+ disconnect: [];
1186
+ error: [error: Error];
1187
+ }
1188
+ type VoiceConnectionStatus = "connecting" | "ready" | "disconnecting" | "disconnected";
1189
+ declare class VoiceConnection extends EventEmitter$1<VoiceConnectionEvents> {
1190
+ readonly channelId: string;
1191
+ readonly guildId: string | undefined;
1192
+ private readonly room;
1193
+ private _status;
1194
+ private _player;
1195
+ private _audioSource;
1196
+ private _audioTrack;
1197
+ constructor(channelId: string, guildId?: string);
1198
+ get status(): VoiceConnectionStatus;
1199
+ get player(): AudioPlayer | null;
1200
+ /** @internal */
1201
+ connect(url: string, token: string): Promise<void>;
1202
+ subscribe(player: AudioPlayer): void;
1203
+ unsubscribe(): void;
1204
+ _feedStream(stream: Readable): Promise<void>;
1205
+ disconnect(): Promise<void>;
1206
+ }
1207
+
1208
+ declare class VoiceManager {
1209
+ private readonly connections;
1210
+ private readonly client;
1211
+ constructor(client: Client);
1212
+ join(channelId: string, guildId?: string): Promise<VoiceConnection>;
1213
+ get(channelId: string): VoiceConnection | undefined;
1214
+ leave(channelId: string): Promise<void>;
1215
+ leaveAll(): Promise<void>;
1216
+ }
1217
+
1218
+ declare class VoiceChannel extends TextChannel {
1219
+ join(): Promise<VoiceConnection>;
1220
+ leave(): Promise<void>;
1221
+ }
1222
+
1145
1223
  type ChannelType = "SavedMessages" | "DirectMessage" | "Group" | "TextChannel";
1146
1224
  interface ChannelCreateOptions {
1147
1225
  name: string;
@@ -1249,6 +1327,7 @@ declare abstract class BaseChannel extends Base {
1249
1327
  isText(): this is TextChannel;
1250
1328
  isDM(): this is DMChannel;
1251
1329
  isGroup(): this is GroupChannel;
1330
+ isVoice(): this is VoiceChannel;
1252
1331
  }
1253
1332
 
1254
1333
  declare class ServerChannelManager {
@@ -2241,6 +2320,7 @@ declare class Client extends EventEmitter {
2241
2320
  emojis: EmojiManager;
2242
2321
  user: ClientUser | null;
2243
2322
  options: ClientOptions;
2323
+ readonly voice: VoiceManager;
2244
2324
  constructor(options?: ClientOptions);
2245
2325
  /**
2246
2326
  * Connects the bot to the Stoat Gateway
@@ -2259,4 +2339,4 @@ declare class UnknownChannel extends BaseChannel {
2259
2339
  constructor(client: Client, data: Channel);
2260
2340
  }
2261
2341
 
2262
- export { Attachment, AttachmentBuilder, type AttachmentMetadata, Base, BaseChannel, BitField, type BitFieldResolvable, type BotInformation, type CDNTag, type ChannelCreateOptions, type ChannelEditOptions, ChannelManager, type ChannelResolvable, type ChannelRolePermissionOptions, type ChannelType, Client, type ClientEvents, type ClientOptions, ClientUser, Collection, Collector, type CollectorOptions, DMChannel, EmbedBuilder, Emoji, type EmojiCreateOptions, type EmojiEditOptions, EmojiManager, type EmojiParent, type EmojiResolvable, type FetchMembersOptions, GatewayManager, GroupChannel, type Interaction, type Masquerade, Member, type MemberBanOptions, type MemberEditOptions, MemberManager, type MemberResolvable, Message, MessageCollector, type MessageCollectorOptions, type MessageFetchOptions, MessageManager, type MessageOptions, MessageReaction, type MessageResolvable, PermissionFlags, type PermissionResolvable, type PermissionString, Permissions, RESTManager, type RawDMChannel, type RawGroupChannel, type RawTextChannel, ReactionCollector, type ReactionCollectorOptions, type ReplyIntent, Role, type RoleCreateOptions, type RoleEditOptions, RoleManager, type RolePermissionOptions, type RoleResolvable, Server, ServerChannelManager, type ServerEditOptions, ServerManager, StoatAPIError, SweeperManager, type SweeperOptions, TextChannel, type TextEmbedData, UnknownChannel, User, type UserEditOptions, UserManager, type UserPresence, type UserProfile, type UserRelationShip, type UserResolvable, type UserStatus };
2342
+ export { Attachment, AttachmentBuilder, type AttachmentMetadata, AudioPlayer, type AudioPlayerEvents, type AudioPlayerStatus, AudioResource, type AudioResourceOptions, type AudioSource, Base, BaseChannel, BitField, type BitFieldResolvable, type BotInformation, type CDNTag, type ChannelCreateOptions, type ChannelEditOptions, ChannelManager, type ChannelResolvable, type ChannelRolePermissionOptions, type ChannelType, Client, type ClientEvents, type ClientOptions, ClientUser, Collection, Collector, type CollectorOptions, DMChannel, EmbedBuilder, Emoji, type EmojiCreateOptions, type EmojiEditOptions, EmojiManager, type EmojiParent, type EmojiResolvable, type FetchMembersOptions, GatewayManager, GroupChannel, type Interaction, type Masquerade, Member, type MemberBanOptions, type MemberEditOptions, MemberManager, type MemberResolvable, Message, MessageCollector, type MessageCollectorOptions, type MessageFetchOptions, MessageManager, type MessageOptions, MessageReaction, type MessageResolvable, PermissionFlags, type PermissionResolvable, type PermissionString, Permissions, RESTManager, type RawDMChannel, type RawGroupChannel, type RawTextChannel, ReactionCollector, type ReactionCollectorOptions, type ReplyIntent, Role, type RoleCreateOptions, type RoleEditOptions, RoleManager, type RolePermissionOptions, type RoleResolvable, Server, ServerChannelManager, type ServerEditOptions, ServerManager, StoatAPIError, SweeperManager, type SweeperOptions, TextChannel, type TextEmbedData, UnknownChannel, User, type UserEditOptions, UserManager, type UserPresence, type UserProfile, type UserRelationShip, type UserResolvable, type UserStatus, VoiceConnection, type VoiceConnectionEvents, type VoiceConnectionStatus, VoiceManager };
package/dist/index.d.ts CHANGED
@@ -4,6 +4,8 @@ import * as stoatApi from 'stoat-api';
4
4
  export { stoatApi as API };
5
5
  import * as util from 'node:util';
6
6
  import util__default from 'node:util';
7
+ import { Readable } from 'node:stream';
8
+ import { EventEmitter as EventEmitter$1 } from 'node:events';
7
9
 
8
10
  declare class GatewayManager {
9
11
  private client;
@@ -1142,6 +1144,82 @@ declare class MessageCollector extends Collector<string, Message> {
1142
1144
  endReason(): string | null;
1143
1145
  }
1144
1146
 
1147
+ interface AudioResourceOptions {
1148
+ volume?: number;
1149
+ inputType?: string;
1150
+ }
1151
+ type AudioSource = string | Readable;
1152
+ declare class AudioResource {
1153
+ readonly stream: Readable;
1154
+ private constructor();
1155
+ static from(source: AudioSource, options?: AudioResourceOptions): AudioResource;
1156
+ }
1157
+
1158
+ type AudioPlayerStatus = "idle" | "buffering" | "playing" | "paused" | "stopped";
1159
+ interface AudioPlayerEvents {
1160
+ stateChange: [oldStatus: AudioPlayerStatus, newStatus: AudioPlayerStatus];
1161
+ idle: [];
1162
+ error: [error: Error];
1163
+ }
1164
+ declare class AudioPlayer extends EventEmitter$1<AudioPlayerEvents> {
1165
+ private _status;
1166
+ private _resource;
1167
+ /** Registered VoiceConnections subscribed to this player */
1168
+ private readonly subscribers;
1169
+ get status(): AudioPlayerStatus;
1170
+ get resource(): AudioResource | null;
1171
+ play(resource: AudioResource): void;
1172
+ pause(): void;
1173
+ resume(): void;
1174
+ stop(): void;
1175
+ /** @internal */
1176
+ addSubscriber(conn: VoiceConnection): void;
1177
+ /** @internal */
1178
+ removeSubscriber(conn: VoiceConnection): void;
1179
+ private feed;
1180
+ private transition;
1181
+ }
1182
+
1183
+ interface VoiceConnectionEvents {
1184
+ ready: [];
1185
+ disconnect: [];
1186
+ error: [error: Error];
1187
+ }
1188
+ type VoiceConnectionStatus = "connecting" | "ready" | "disconnecting" | "disconnected";
1189
+ declare class VoiceConnection extends EventEmitter$1<VoiceConnectionEvents> {
1190
+ readonly channelId: string;
1191
+ readonly guildId: string | undefined;
1192
+ private readonly room;
1193
+ private _status;
1194
+ private _player;
1195
+ private _audioSource;
1196
+ private _audioTrack;
1197
+ constructor(channelId: string, guildId?: string);
1198
+ get status(): VoiceConnectionStatus;
1199
+ get player(): AudioPlayer | null;
1200
+ /** @internal */
1201
+ connect(url: string, token: string): Promise<void>;
1202
+ subscribe(player: AudioPlayer): void;
1203
+ unsubscribe(): void;
1204
+ _feedStream(stream: Readable): Promise<void>;
1205
+ disconnect(): Promise<void>;
1206
+ }
1207
+
1208
+ declare class VoiceManager {
1209
+ private readonly connections;
1210
+ private readonly client;
1211
+ constructor(client: Client);
1212
+ join(channelId: string, guildId?: string): Promise<VoiceConnection>;
1213
+ get(channelId: string): VoiceConnection | undefined;
1214
+ leave(channelId: string): Promise<void>;
1215
+ leaveAll(): Promise<void>;
1216
+ }
1217
+
1218
+ declare class VoiceChannel extends TextChannel {
1219
+ join(): Promise<VoiceConnection>;
1220
+ leave(): Promise<void>;
1221
+ }
1222
+
1145
1223
  type ChannelType = "SavedMessages" | "DirectMessage" | "Group" | "TextChannel";
1146
1224
  interface ChannelCreateOptions {
1147
1225
  name: string;
@@ -1249,6 +1327,7 @@ declare abstract class BaseChannel extends Base {
1249
1327
  isText(): this is TextChannel;
1250
1328
  isDM(): this is DMChannel;
1251
1329
  isGroup(): this is GroupChannel;
1330
+ isVoice(): this is VoiceChannel;
1252
1331
  }
1253
1332
 
1254
1333
  declare class ServerChannelManager {
@@ -2241,6 +2320,7 @@ declare class Client extends EventEmitter {
2241
2320
  emojis: EmojiManager;
2242
2321
  user: ClientUser | null;
2243
2322
  options: ClientOptions;
2323
+ readonly voice: VoiceManager;
2244
2324
  constructor(options?: ClientOptions);
2245
2325
  /**
2246
2326
  * Connects the bot to the Stoat Gateway
@@ -2259,4 +2339,4 @@ declare class UnknownChannel extends BaseChannel {
2259
2339
  constructor(client: Client, data: Channel);
2260
2340
  }
2261
2341
 
2262
- export { Attachment, AttachmentBuilder, type AttachmentMetadata, Base, BaseChannel, BitField, type BitFieldResolvable, type BotInformation, type CDNTag, type ChannelCreateOptions, type ChannelEditOptions, ChannelManager, type ChannelResolvable, type ChannelRolePermissionOptions, type ChannelType, Client, type ClientEvents, type ClientOptions, ClientUser, Collection, Collector, type CollectorOptions, DMChannel, EmbedBuilder, Emoji, type EmojiCreateOptions, type EmojiEditOptions, EmojiManager, type EmojiParent, type EmojiResolvable, type FetchMembersOptions, GatewayManager, GroupChannel, type Interaction, type Masquerade, Member, type MemberBanOptions, type MemberEditOptions, MemberManager, type MemberResolvable, Message, MessageCollector, type MessageCollectorOptions, type MessageFetchOptions, MessageManager, type MessageOptions, MessageReaction, type MessageResolvable, PermissionFlags, type PermissionResolvable, type PermissionString, Permissions, RESTManager, type RawDMChannel, type RawGroupChannel, type RawTextChannel, ReactionCollector, type ReactionCollectorOptions, type ReplyIntent, Role, type RoleCreateOptions, type RoleEditOptions, RoleManager, type RolePermissionOptions, type RoleResolvable, Server, ServerChannelManager, type ServerEditOptions, ServerManager, StoatAPIError, SweeperManager, type SweeperOptions, TextChannel, type TextEmbedData, UnknownChannel, User, type UserEditOptions, UserManager, type UserPresence, type UserProfile, type UserRelationShip, type UserResolvable, type UserStatus };
2342
+ export { Attachment, AttachmentBuilder, type AttachmentMetadata, AudioPlayer, type AudioPlayerEvents, type AudioPlayerStatus, AudioResource, type AudioResourceOptions, type AudioSource, Base, BaseChannel, BitField, type BitFieldResolvable, type BotInformation, type CDNTag, type ChannelCreateOptions, type ChannelEditOptions, ChannelManager, type ChannelResolvable, type ChannelRolePermissionOptions, type ChannelType, Client, type ClientEvents, type ClientOptions, ClientUser, Collection, Collector, type CollectorOptions, DMChannel, EmbedBuilder, Emoji, type EmojiCreateOptions, type EmojiEditOptions, EmojiManager, type EmojiParent, type EmojiResolvable, type FetchMembersOptions, GatewayManager, GroupChannel, type Interaction, type Masquerade, Member, type MemberBanOptions, type MemberEditOptions, MemberManager, type MemberResolvable, Message, MessageCollector, type MessageCollectorOptions, type MessageFetchOptions, MessageManager, type MessageOptions, MessageReaction, type MessageResolvable, PermissionFlags, type PermissionResolvable, type PermissionString, Permissions, RESTManager, type RawDMChannel, type RawGroupChannel, type RawTextChannel, ReactionCollector, type ReactionCollectorOptions, type ReplyIntent, Role, type RoleCreateOptions, type RoleEditOptions, RoleManager, type RolePermissionOptions, type RoleResolvable, Server, ServerChannelManager, type ServerEditOptions, ServerManager, StoatAPIError, SweeperManager, type SweeperOptions, TextChannel, type TextEmbedData, UnknownChannel, User, type UserEditOptions, UserManager, type UserPresence, type UserProfile, type UserRelationShip, type UserResolvable, type UserStatus, VoiceConnection, type VoiceConnectionEvents, type VoiceConnectionStatus, VoiceManager };
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";
@@ -1355,13 +1355,14 @@ var RESTManager = class {
1355
1355
  this.client.emit("debug", `Bucket [${method}:${endpoint}] exhausted. Waiting ${waitTime}ms proactively...`);
1356
1356
  await sleep(waitTime);
1357
1357
  }
1358
+ const isBodyMethod = ["post", "patch", "put"].includes(method.toLowerCase());
1358
1359
  const response = await fetch(url, {
1359
1360
  method: method.toUpperCase(),
1360
1361
  headers: {
1361
1362
  "X-Bot-Token": this.token,
1362
1363
  "Content-Type": "application/json"
1363
1364
  },
1364
- ...body !== void 0 ? { body: JSON.stringify(body) } : {}
1365
+ ...isBodyMethod ? { body: JSON.stringify(body ?? {}) } : {}
1365
1366
  });
1366
1367
  const remainingHeader = response.headers.get("x-ratelimit-remaining");
1367
1368
  const resetAfterHeader = response.headers.get("x-ratelimit-reset-after");
@@ -1897,6 +1898,9 @@ var BaseChannel = class extends Base {
1897
1898
  isGroup() {
1898
1899
  return this.type === "Group";
1899
1900
  }
1901
+ isVoice() {
1902
+ return this.isText() && this.voice !== null;
1903
+ }
1900
1904
  };
1901
1905
 
1902
1906
  // src/structures/TextChannel.ts
@@ -2207,10 +2211,21 @@ var GroupChannel = class extends BaseChannel {
2207
2211
  }
2208
2212
  };
2209
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
+
2210
2224
  // src/utils/ChannelFactory.ts
2211
2225
  function createChannel(client, data) {
2212
2226
  switch (data.channel_type) {
2213
2227
  case "TextChannel":
2228
+ if (data.voice) return new VoiceChannel(client, data);
2214
2229
  return new TextChannel(client, data);
2215
2230
  case "DirectMessage":
2216
2231
  return new DMChannel(client, data);
@@ -4356,8 +4371,247 @@ var SweeperManager = class {
4356
4371
  }
4357
4372
  };
4358
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
+
4359
4613
  // src/client/Client.ts
4360
- var Client = class extends EventEmitter2 {
4614
+ var Client = class extends EventEmitter4 {
4361
4615
  rest;
4362
4616
  gateway;
4363
4617
  channels;
@@ -4367,6 +4621,7 @@ var Client = class extends EventEmitter2 {
4367
4621
  emojis;
4368
4622
  user = null;
4369
4623
  options;
4624
+ voice;
4370
4625
  constructor(options = {}) {
4371
4626
  super({ captureRejections: true });
4372
4627
  this.options = options;
@@ -4376,6 +4631,7 @@ var Client = class extends EventEmitter2 {
4376
4631
  this.servers = new ServerManager(this, options.cacheLimits?.servers);
4377
4632
  this.users = new UserManager(this, options.cacheLimits?.users);
4378
4633
  this.emojis = new EmojiManager(this, void 0, options.cacheLimits?.emojis);
4634
+ this.voice = new VoiceManager(this);
4379
4635
  this.sweepers = new SweeperManager(this, options.sweepers ?? {});
4380
4636
  }
4381
4637
  /**
@@ -4470,6 +4726,8 @@ export {
4470
4726
  API,
4471
4727
  Attachment,
4472
4728
  AttachmentBuilder,
4729
+ AudioPlayer,
4730
+ AudioResource,
4473
4731
  Base,
4474
4732
  BaseChannel,
4475
4733
  BitField,
@@ -4504,6 +4762,8 @@ export {
4504
4762
  TextChannel,
4505
4763
  UnknownChannel,
4506
4764
  User,
4507
- UserManager
4765
+ UserManager,
4766
+ VoiceConnection,
4767
+ VoiceManager
4508
4768
  };
4509
4769
  //# sourceMappingURL=index.js.map