shoukaku-bun 4.2.0-b

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Luigi Colantuono
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,88 @@
1
+ ## Shoukaku-Bun
2
+
3
+ > Powerfull, Lightweight wrapper around Lavalink
4
+
5
+ [![Discord](https://img.shields.io/discord/423116740810244097?style=flat-square)](https://discordapp.com/invite/FVqbtGu)
6
+ [![npm](https://img.shields.io/npm/v/shoukaku?style=flat-square)](https://www.npmjs.com/package/shoukaku)
7
+ ![Github Stars](https://img.shields.io/github/stars/Deivu/Shoukaku?style=flat-square)
8
+ ![GitHub issues](https://img.shields.io/github/issues-raw/Deivu/Shoukaku?style=flat-square)
9
+ ![NPM](https://img.shields.io/npm/l/shoukaku?style=flat-square)
10
+
11
+ <p align="center">
12
+ <img src="https://azurlane.netojuu.com/images/thumb/d/dc/ShoukakuWeddingWithoutBG.png/767px-ShoukakuWeddingWithoutBG.png">
13
+ </p>
14
+
15
+ ### Features
16
+
17
+ - **Bun-Native**: Re-engineered to run exclusively on Bun.Purged all Node.js legacy dependencies (like `ws`).
18
+ - **Zero Latency**: Uses Bun's kernel-level WebSocket for maximum throughput.
19
+ - **Ultra Lightweight**: Optimized for minimal memory footprint (production tested at ~33MB).
20
+ - **TypeScript Native**: No build step required. Direct execution from source.
21
+ - **Stable & Updated**: Based on the rock-solid Shoukaku v4.2.0 logic.
22
+ - **Very cute (Very Important)**
23
+
24
+ ### Documentation
25
+
26
+ > https://guide.shoukaku.shipgirl.moe/
27
+
28
+ ### Installation
29
+
30
+ This is a specialized fork. Install it directly from GitHub:
31
+
32
+ ```bash
33
+ bun add github:LuigiColantuono/shoukaku-bun
34
+ ```
35
+
36
+ ### Documentation
37
+
38
+ > https://guide.shoukaku.shipgirl.moe/
39
+
40
+ ### Getting Started
41
+
42
+ > https://guide.shoukaku.shipgirl.moe/guides/1-getting-started/
43
+
44
+ ### Supported Libraries
45
+
46
+ > https://guide.shoukaku.shipgirl.moe/guides/5-connectors/
47
+
48
+ ### Example Bot
49
+
50
+ > https://github.com/Deivu/Kongou
51
+
52
+ ### Configuration Options
53
+
54
+ ```js
55
+ // Parameters for main class init, Options is the Configuration Options
56
+ new Shoukaku(new Connectors.DiscordJS(client), Nodes, Options);
57
+ ```
58
+
59
+ | Option | Type | Default | Description | Notes |
60
+ | ---------------------- | ---------------------- | -------- | ------------------------------------------------------------------------------------------------ | ------------------------ |
61
+ | resume | boolean | false | If you want to enable resuming when your connection to lavalink disconnects | |
62
+ | resumeTimeout | number | 30 | Timeout before lavalink destroys the players on a disconnect | In seconds |
63
+ | resumeByLibrary | boolean | false | If you want to force resume players no matter what even if it's not resumable by lavalink | |
64
+ | reconnectTries | number | 3 | Number of tries to reconnect to lavalink before disconnecting | |
65
+ | reconnectInterval | number | 5 | Timeout between reconnects | In seconds |
66
+ | restTimeout | number | 60 | Maximum amount of time to wait for rest lavalink api requests | In seconds |
67
+ | moveOnDisconnect | boolean | false | Whether to move players to a different lavalink node when a node disconnects | |
68
+ | userAgent | string | (auto) | Changes the user-agent used for lavalink requests | Not recommeded to change |
69
+ | structures | Object{rest?, player?} | {} | Custom structures for shoukaku to use | |
70
+ | voiceConnectionTimeout | number | 15 | Maximum amount of time to wait for a join voice channel command | In seconds |
71
+ | nodeResolver | function | function | Custom node resolver if you want to have your own method of getting the ideal node | |
72
+
73
+ ### Wrappers
74
+
75
+ | Name | Link | Description |
76
+ | -------- | --------------------------------------------- | -------------------------------------------------------- |
77
+ | Kazagumo | [Github](https://github.com/Takiyo0/Kazagumo) | A wrapper for Shoukaku that has an internal queue system |
78
+
79
+ > Open a pr if you want to add a wrapper here
80
+
81
+ ### Other Links
82
+
83
+ - [Discord](https://discord.gg/XqJw52d35R)
84
+
85
+ - [Lavalink](https://github.com/lavalink-devs/Lavalink)
86
+
87
+ ### Code made with ❤ by @ichimakase (Saya) & Luigi
88
+
package/index.ts ADDED
@@ -0,0 +1,9 @@
1
+ export * as Connectors from './src/connectors/libs';
2
+ export * as Constants from './src/Constants';
3
+ export * as Utils from './src/Utils';
4
+ export * from './src/connectors/Connector';
5
+ export * from './src/guild/Connection';
6
+ export * from './src/guild/Player';
7
+ export * from './src/node/Node';
8
+ export * from './src/node/Rest';
9
+ export * from './src/Shoukaku';
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "shoukaku-bun",
3
+ "version": "4.2.0-b",
4
+ "description": "Bun-native high-performance fork of Shoukaku. Node-dependencies purged.",
5
+ "main": "index.ts",
6
+ "module": "index.ts",
7
+ "types": "index.ts",
8
+ "files": [
9
+ "src",
10
+ "index.ts"
11
+ ],
12
+ "exports": {
13
+ ".": {
14
+ "types": "./index.ts",
15
+ "import": "./index.ts",
16
+ "require": "./index.ts"
17
+ }
18
+ },
19
+ "scripts": {
20
+ "lint": "eslint .",
21
+ "test": "bun test"
22
+ },
23
+ "keywords": [
24
+ "bot",
25
+ "music",
26
+ "lavalink",
27
+ "bun",
28
+ "high-performance",
29
+ "discord"
30
+ ],
31
+ "engines": {
32
+ "bun": "1.3.6"
33
+ },
34
+ "author": "Saya",
35
+ "contributors": [
36
+ {
37
+ "name": "Luigi Colantuono",
38
+ "url": "https://github.com/LuigiColantuono",
39
+ "info": "Bun Refactor & Optimization"
40
+ }
41
+ ],
42
+ "license": "MIT",
43
+ "homepage": "https://github.com/LuigiColantuono/shoukaku-bun",
44
+ "repository": {
45
+ "type": "git",
46
+ "url": "git+https://github.com/LuigiColantuono/shoukaku-bun.git"
47
+ },
48
+ "dependencies": {},
49
+ "devDependencies": {
50
+ "@shipgirl/eslint-config": "^0.2.2",
51
+ "@types/bun": "latest",
52
+ "eslint": "^9.39.2",
53
+ "typedoc": "^0.28.16",
54
+ "typescript": "^5.9.3"
55
+ }
56
+ }
@@ -0,0 +1,55 @@
1
+ import Info from '../package.json';
2
+ import type { NodeOption, ShoukakuOptions } from './Shoukaku';
3
+
4
+ export enum State {
5
+ CONNECTING,
6
+ CONNECTED,
7
+ DISCONNECTING,
8
+ DISCONNECTED
9
+ }
10
+
11
+ export enum VoiceState {
12
+ SESSION_READY,
13
+ SESSION_ID_MISSING,
14
+ SESSION_ENDPOINT_MISSING,
15
+ SESSION_FAILED_UPDATE
16
+ }
17
+
18
+ export enum OpCodes {
19
+ PLAYER_UPDATE = 'playerUpdate',
20
+ STATS = 'stats',
21
+ EVENT = 'event',
22
+ READY = 'ready'
23
+ }
24
+
25
+ export const Versions = {
26
+ REST_VERSION: 4,
27
+ WEBSOCKET_VERSION: 4
28
+ };
29
+
30
+ export const ShoukakuDefaults: Required<ShoukakuOptions> = {
31
+ resume: false,
32
+ resumeTimeout: 30,
33
+ resumeByLibrary: false,
34
+ reconnectTries: 3,
35
+ reconnectInterval: 5,
36
+ restTimeout: 60,
37
+ moveOnDisconnect: false,
38
+ userAgent: 'Discord Bot/unknown (https://github.com/shipgirlproject/Shoukaku.git)',
39
+ structures: {},
40
+ voiceConnectionTimeout: 15,
41
+ nodeResolver: (nodes) => [ ...nodes.values() ]
42
+ .filter(node => node.state === State.CONNECTED)
43
+ .sort((a, b) => a.penalties - b.penalties)
44
+ .shift()
45
+ };
46
+
47
+ export const ShoukakuClientInfo = `${Info.name}/${Info.version} (${Info.repository.url})`;
48
+
49
+ export const NodeDefaults: NodeOption = {
50
+ name: 'Default',
51
+ url: '',
52
+ auth: '',
53
+ secure: false,
54
+ group: undefined
55
+ };
@@ -0,0 +1,295 @@
1
+ import { ShoukakuDefaults, VoiceState } from './Constants';
2
+ import { Node } from './node/Node';
3
+ import { Connector } from './connectors/Connector';
4
+ import { Constructor, mergeDefault, TypedEventEmitter } from './Utils';
5
+ import { Player } from './guild/Player';
6
+ import { Rest } from './node/Rest';
7
+ import { Connection } from './guild/Connection';
8
+
9
+ export interface Structures {
10
+ /**
11
+ * A custom structure that extends the Rest class
12
+ */
13
+ rest?: Constructor<Rest>;
14
+ /**
15
+ * A custom structure that extends the Player class
16
+ */
17
+ player?: Constructor<Player>;
18
+ }
19
+
20
+ export interface NodeOption {
21
+ /**
22
+ * Name of the Lavalink node
23
+ */
24
+ name: string;
25
+ /**
26
+ * Lavalink node host and port without any prefix
27
+ */
28
+ url: string;
29
+ /**
30
+ * Credentials to access Lavalink
31
+ */
32
+ auth: string;
33
+ /**
34
+ * Whether to use secure protocols or not
35
+ */
36
+ secure?: boolean;
37
+ /**
38
+ * Name of the Lavalink node group
39
+ */
40
+ group?: string;
41
+ }
42
+
43
+ export interface ShoukakuOptions {
44
+ /**
45
+ * Whether to resume a connection on disconnect to Lavalink (Server Side) (Note: DOES NOT RESUME WHEN THE LAVALINK SERVER DIES)
46
+ */
47
+ resume?: boolean;
48
+ /**
49
+ * Time to wait before lavalink starts to destroy the players of the disconnected client
50
+ */
51
+ resumeTimeout?: number;
52
+ /**
53
+ * Whether to resume the players by doing it in the library side (Client Side) (Note: TRIES TO RESUME REGARDLESS OF WHAT HAPPENED ON A LAVALINK SERVER)
54
+ */
55
+ resumeByLibrary?: boolean;
56
+ /**
57
+ * Number of times to try and reconnect to Lavalink before giving up
58
+ */
59
+ reconnectTries?: number;
60
+ /**
61
+ * Timeout before trying to reconnect
62
+ */
63
+ reconnectInterval?: number;
64
+ /**
65
+ * Time to wait for a response from the Lavalink REST API before giving up
66
+ */
67
+ restTimeout?: number;
68
+ /**
69
+ * Whether to move players to a different Lavalink node when a node disconnects
70
+ */
71
+ moveOnDisconnect?: boolean;
72
+ /**
73
+ * User Agent to use when making requests to Lavalink
74
+ */
75
+ userAgent?: string;
76
+ /**
77
+ * Custom structures for shoukaku to use
78
+ */
79
+ structures?: Structures;
80
+ /**
81
+ * Timeout before abort connection
82
+ */
83
+ voiceConnectionTimeout?: number;
84
+ /**
85
+ * Node Resolver to use if you want to customize it
86
+ */
87
+ nodeResolver?: (nodes: Map<string, Node>, connection?: Connection) => Node | undefined;
88
+ }
89
+
90
+ export interface VoiceChannelOptions {
91
+ guildId: string;
92
+ shardId: number;
93
+ channelId: string;
94
+ deaf?: boolean;
95
+ mute?: boolean;
96
+ }
97
+
98
+ // Interfaces are not final, but types are, and therefore has an index signature
99
+ // https://stackoverflow.com/a/64970740
100
+ // eslint-disable-next-line @typescript-eslint/consistent-type-definitions
101
+ export type ShoukakuEvents = {
102
+ /**
103
+ * Emitted when reconnect tries are occurring and how many tries are left
104
+ * @eventProperty
105
+ */
106
+ 'reconnecting': [name: string, reconnectsLeft: number, reconnectInterval: number];
107
+ /**
108
+ * Emitted when data useful for debugging is produced
109
+ * @eventProperty
110
+ */
111
+ 'debug': [name: string, info: string];
112
+ /**
113
+ * Emitted when an error occurs
114
+ * @eventProperty
115
+ */
116
+ 'error': [name: string, error: Error];
117
+ /**
118
+ * Emitted when Shoukaku is ready to receive operations
119
+ * @eventProperty
120
+ */
121
+ 'ready': [name: string, lavalinkResume: boolean, libraryResume: boolean];
122
+ /**
123
+ * Emitted when a websocket connection to Lavalink closes
124
+ * @eventProperty
125
+ */
126
+ 'close': [name: string, code: number, reason: string];
127
+ /**
128
+ * Emitted when a websocket connection to Lavalink disconnects
129
+ * @eventProperty
130
+ */
131
+ 'disconnect': [name: string, count: number];
132
+ /**
133
+ * Emitted when a raw message is received from Lavalink
134
+ * @eventProperty
135
+ */
136
+ 'raw': [name: string, json: unknown];
137
+ };
138
+
139
+ /**
140
+ * Main Shoukaku class
141
+ */
142
+ export class Shoukaku extends TypedEventEmitter<ShoukakuEvents> {
143
+ /**
144
+ * Discord library connector
145
+ */
146
+ public readonly connector: Connector;
147
+ /**
148
+ * Shoukaku options
149
+ */
150
+ public readonly options: Required<ShoukakuOptions>;
151
+ /**
152
+ * Connected Lavalink nodes
153
+ */
154
+ public readonly nodes: Map<string, Node>;
155
+ /**
156
+ * Voice connections being handled
157
+ */
158
+ public readonly connections: Map<string, Connection>;
159
+ /**
160
+ * Players being handled
161
+ */
162
+ public readonly players: Map<string, Player>;
163
+ /**
164
+ * Shoukaku instance identifier
165
+ */
166
+ public id: string | null;
167
+ /**
168
+ * @param connector A Discord library connector
169
+ * @param nodes An array that conforms to the NodeOption type that specifies nodes to connect to
170
+ * @param options Options to pass to create this Shoukaku instance
171
+ * @param options.resume Whether to resume a connection on disconnect to Lavalink (Server Side) (Note: DOES NOT RESUME WHEN THE LAVALINK SERVER DIES)
172
+ * @param options.resumeTimeout Time to wait before lavalink starts to destroy the players of the disconnected client
173
+ * @param options.resumeByLibrary Whether to resume the players by doing it in the library side (Client Side) (Note: TRIES TO RESUME REGARDLESS OF WHAT HAPPENED ON A LAVALINK SERVER)
174
+ * @param options.reconnectTries Number of times to try and reconnect to Lavalink before giving up
175
+ * @param options.reconnectInterval Timeout before trying to reconnect
176
+ * @param options.restTimeout Time to wait for a response from the Lavalink REST API before giving up
177
+ * @param options.moveOnDisconnect Whether to move players to a different Lavalink node when a node disconnects
178
+ * @param options.userAgent User Agent to use when making requests to Lavalink
179
+ * @param options.structures Custom structures for shoukaku to use
180
+ * @param options.nodeResolver Used if you have custom lavalink node resolving
181
+ */
182
+ constructor(connector: Connector, nodes: NodeOption[], options: ShoukakuOptions = {}) {
183
+ super();
184
+ this.connector = connector.set(this);
185
+ this.options = mergeDefault<ShoukakuOptions>(ShoukakuDefaults, options);
186
+ this.nodes = new Map();
187
+ this.connections = new Map();
188
+ this.players = new Map();
189
+ this.id = null;
190
+ this.connector.listen(nodes);
191
+ }
192
+
193
+ /**
194
+ * Gets an ideal node based on the nodeResolver you provided
195
+ * @param connection Optional connection class for ideal node selection, if you use it
196
+ * @returns An ideal node for you to do things with
197
+ */
198
+ public getIdealNode(connection?: Connection): Node | undefined {
199
+ return this.options.nodeResolver(this.nodes, connection);
200
+ }
201
+
202
+ /**
203
+ * Add a Lavalink node to the pool of available nodes
204
+ * @param options.name Name of this node
205
+ * @param options.url URL of Lavalink
206
+ * @param options.auth Credentials to access Lavalink
207
+ * @param options.secure Whether to use secure protocols or not
208
+ * @param options.group Group of this node
209
+ */
210
+ public addNode(options: NodeOption): void {
211
+ const node = new Node(this, options);
212
+ node.on('debug', (...args) => this.emit('debug', node.name, ...args));
213
+ node.on('reconnecting', (...args) => this.emit('reconnecting', node.name, ...args));
214
+ node.on('error', (...args) => this.emit('error', node.name, ...args));
215
+ node.on('close', (...args) => this.emit('close', node.name, ...args));
216
+ node.on('ready', (...args) => this.emit('ready', node.name, ...args));
217
+ node.on('raw', (...args) => this.emit('raw', node.name, ...args));
218
+ node.once('disconnect', () => this.nodes.delete(node.name));
219
+ node.connect().catch((error) => this.emit('error', node.name, error as Error));
220
+ this.nodes.set(node.name, node);
221
+ }
222
+
223
+ /**
224
+ * Remove a Lavalink node from the pool of available nodes
225
+ * @param name Name of the node
226
+ * @param reason Reason of removing the node
227
+ */
228
+ public removeNode(name: string, reason = 'Remove node executed'): void {
229
+ const node = this.nodes.get(name);
230
+ if (!node) throw new Error('The node name you specified doesn\'t exist');
231
+ node.disconnect(1000, reason);
232
+ this.nodes.delete(name);
233
+ }
234
+
235
+ /**
236
+ * Joins a voice channel
237
+ * @param options.guildId GuildId in which the ChannelId of the voice channel is located
238
+ * @param options.shardId ShardId to track where this should send on sharded websockets, put 0 if you are unsharded
239
+ * @param options.channelId ChannelId of the voice channel you want to connect to
240
+ * @param options.deaf Optional boolean value to specify whether to deafen or undeafen the current bot user
241
+ * @param options.mute Optional boolean value to specify whether to mute or unmute the current bot user
242
+ * @returns The created player
243
+ */
244
+ public async joinVoiceChannel(options: VoiceChannelOptions): Promise<Player> {
245
+ if (this.connections.has(options.guildId))
246
+ throw new Error('This guild already have an existing connection');
247
+ const connection = new Connection(this, options);
248
+ this.connections.set(connection.guildId, connection);
249
+ try {
250
+ await connection.connect();
251
+ } catch (error) {
252
+ this.connections.delete(options.guildId);
253
+ throw error;
254
+ }
255
+ try {
256
+ const node = this.getIdealNode(connection);
257
+ if (!node)
258
+ throw new Error('Can\'t find any nodes to connect on');
259
+ const player = this.options.structures.player ? new this.options.structures.player(connection.guildId, node) : new Player(connection.guildId, node);
260
+ const onUpdate = (state: VoiceState) => {
261
+ if (state !== VoiceState.SESSION_READY) return;
262
+ void player.sendServerUpdate(connection);
263
+ };
264
+ await player.sendServerUpdate(connection);
265
+ connection.on('connectionUpdate', onUpdate);
266
+ this.players.set(player.guildId, player);
267
+ return player;
268
+ } catch (error) {
269
+ connection.disconnect();
270
+ this.connections.delete(options.guildId);
271
+ throw error;
272
+ }
273
+ }
274
+
275
+ /**
276
+ * Leaves a voice channel
277
+ * @param guildId The id of the guild you want to delete
278
+ * @returns The destroyed / disconnected player or undefined if none
279
+ */
280
+ public async leaveVoiceChannel(guildId: string): Promise<void> {
281
+ const connection = this.connections.get(guildId);
282
+ if (connection) {
283
+ connection.disconnect();
284
+ this.connections.delete(guildId);
285
+ }
286
+ const player = this.players.get(guildId);
287
+ if (player) {
288
+ try {
289
+ await player.destroy();
290
+ } catch { /* empty */ }
291
+ player.clean();
292
+ this.players.delete(guildId);
293
+ }
294
+ }
295
+ }
package/src/Utils.ts ADDED
@@ -0,0 +1,58 @@
1
+ import { EventEmitter } from 'node:events';
2
+
3
+ // https://stackoverflow.com/a/67244127
4
+ export abstract class TypedEventEmitter<T extends Record<string, unknown[]>> extends EventEmitter {
5
+ protected constructor() {
6
+ super();
7
+ }
8
+
9
+ on<K extends Extract<keyof T, string> | symbol>(eventName: K, listener: (...args: T[Extract<K, string>]) => void): this {
10
+ return super.on(eventName, listener);
11
+ }
12
+
13
+ once<K extends Extract<keyof T, string> | symbol>(eventName: K, listener: (...args: T[Extract<K, string>]) => void): this {
14
+ return super.once(eventName, listener);
15
+ }
16
+
17
+ off<K extends Extract<keyof T, string> | symbol>(eventName: K, listener: (...args: T[Extract<K, string>]) => void): this {
18
+ return super.off(eventName, listener);
19
+ }
20
+
21
+ emit<K extends Extract<keyof T, string> | symbol>(eventName: K, ...args: T[Extract<K, string>]): boolean {
22
+ return super.emit(eventName, ...args);
23
+ }
24
+ }
25
+
26
+ export type Constructor<T> = new (...args: unknown[]) => T;
27
+
28
+ /**
29
+ * Merge the default options to user input
30
+ * @param def Default options
31
+ * @param given User input
32
+ * @returns Merged options
33
+ */
34
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
35
+ export function mergeDefault<T extends Record<string, any>>(def: T, given: T): Required<T> {
36
+ if (!given) return def as Required<T>;
37
+ const defaultKeys: (keyof T)[] = Object.keys(def);
38
+ for (const key in given) {
39
+ if (defaultKeys.includes(key)) continue;
40
+ delete given[key];
41
+ }
42
+ for (const key of defaultKeys) {
43
+ if (def[key] === null || (typeof def[key] === 'string' && def[key].length === 0)) {
44
+ if (!given[key]) throw new Error(`${String(key)} was not found from the given options.`);
45
+ }
46
+ given[key] ??= def[key];
47
+ }
48
+ return given as Required<T>;
49
+ }
50
+
51
+ /**
52
+ * Wait for a specific amount of time (timeout)
53
+ * @param ms Time to wait in milliseconds
54
+ * @returns A promise that resolves in x seconds
55
+ */
56
+ export function wait(ms: number): Promise<void> {
57
+ return new Promise(resolve => setTimeout(resolve, ms));
58
+ }
@@ -0,0 +1,49 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access */
2
+ import { NodeDefaults } from '../Constants';
3
+ import type { ServerUpdate, StateUpdatePartial } from '../guild/Connection';
4
+ import type { NodeOption, Shoukaku } from '../Shoukaku';
5
+ import { mergeDefault } from '../Utils';
6
+
7
+ export interface ConnectorMethods {
8
+ sendPacket: any;
9
+ getId: any;
10
+ }
11
+
12
+ export const AllowedPackets = [ 'VOICE_STATE_UPDATE', 'VOICE_SERVER_UPDATE' ];
13
+
14
+ export abstract class Connector {
15
+ protected readonly client: any;
16
+ protected manager: Shoukaku | null;
17
+ constructor(client: any) {
18
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
19
+ this.client = client;
20
+ this.manager = null;
21
+ }
22
+
23
+ public set(manager: Shoukaku): Connector {
24
+ this.manager = manager;
25
+ return this;
26
+ }
27
+
28
+ protected ready(nodes: NodeOption[]): void {
29
+ this.manager!.id = this.getId();
30
+ for (const node of nodes) this.manager!.addNode(mergeDefault(NodeDefaults, node));
31
+ }
32
+
33
+ protected raw(packet: any): void {
34
+ if (!AllowedPackets.includes(packet.t as string)) return;
35
+ const guildId = packet.d.guild_id as string;
36
+ const connection = this.manager!.connections.get(guildId);
37
+ if (!connection) return;
38
+ if (packet.t === 'VOICE_SERVER_UPDATE') return connection.setServerUpdate(packet.d as ServerUpdate);
39
+ const userId = packet.d.user_id as string;
40
+ if (userId !== this.manager!.id) return;
41
+ connection.setStateUpdate(packet.d as StateUpdatePartial);
42
+ }
43
+
44
+ abstract getId(): string;
45
+
46
+ abstract sendPacket(shardId: number, payload: unknown, important: boolean): void;
47
+
48
+ abstract listen(nodes: NodeOption[]): void;
49
+ }
@@ -0,0 +1,42 @@
1
+ ## Supported Libs
2
+
3
+ > [Discord.JS](https://discord.js.org/#/) (v13.x.x & 14.x.x)
4
+
5
+ ```js
6
+ const { Shoukaku, Connectors } = require('shoukaku');
7
+ new Shoukaku(new Connectors.DiscordJS(client), servers, options);
8
+ ```
9
+
10
+ > [Eris](https://abal.moe/Eris/) (0.15.x / 0.16.x / 0.17.x)
11
+
12
+ ```js
13
+ const { Shoukaku, Connectors } = require('shoukaku');
14
+ new Shoukaku(new Connectors.Eris(client), servers, options)
15
+ ```
16
+
17
+ > [Oceanic.JS](https://oceanic.ws/) (1.0.x)
18
+
19
+ ```js
20
+ const { Shoukaku, Connectors } = require('shoukaku');
21
+ new Shoukaku(new Connectors.OceanicJS(client), servers, options)
22
+ ```
23
+
24
+ > [Seyfert](https://seyfert-docs.vercel.app/) (0.1.x)
25
+
26
+ ```js
27
+ const { Shoukaku, Connectors } = require('shoukaku');
28
+ new Shoukaku(new Connectors.Seyfert(client), servers, options)
29
+ ```
30
+
31
+
32
+ > Implement your own
33
+
34
+ ## Implementing your own
35
+
36
+ > Check **DiscordJS.ts** or **Eris.ts** inside libs folder for a detailed explanation on how to support a library
37
+
38
+ > And Submit a PR so other people don't need to do it themselves, yay!
39
+
40
+ ## Support
41
+
42
+ For questions on how to do so, just ask at my support server at [HERE](https://discord.gg/FVqbtGu) (#Development)