muthera 1.0.2

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/src/index.d.ts ADDED
@@ -0,0 +1,407 @@
1
+ import { EventEmitter } from "events";
2
+ import { Collection } from "@discordjs/collection";
3
+
4
+ export declare class Track {
5
+ constructor(data: any, requester: any, node: Node);
6
+
7
+ public track: String;
8
+ public info: {
9
+ identifier: String;
10
+ seekable: Boolean;
11
+ author: String;
12
+ length: Number;
13
+ stream: Boolean;
14
+ sourceName: String;
15
+ title: String;
16
+ uri: String;
17
+ thumbnail: String | null;
18
+ requester: any;
19
+ };
20
+
21
+ public resolve(muthera: muthera): Promise<Track>;
22
+ }
23
+
24
+ export interface RestOptions {
25
+ secure: Boolean;
26
+ host: String;
27
+ port: Number;
28
+ sessionId: String;
29
+ password: String;
30
+ }
31
+
32
+ interface RequestOptions {
33
+ guildId: String;
34
+ data: any;
35
+ }
36
+
37
+ interface RestResponse {
38
+
39
+ }
40
+
41
+ export declare class Rest extends EventEmitter {
42
+ constructor(muthera: muthera, options: RestOptions);
43
+ public muthera: muthera;
44
+ public url: String
45
+ public sessionId: RestOptions["sessionId"];
46
+ public password: RestOptions["password"];
47
+ public calls: Number;
48
+
49
+ public setSessionId(sessionId: String): void;
50
+ public makeRequest(method: String, endpoint: String, body?: any): Promise<RestResponse | null>;
51
+ public getPlayers(): Promise<RestResponse | null>;
52
+ public updatePlayer(options: RequestOptions): Promise<void>;
53
+ public destroyPlayer(guildId: String): Promise<RestResponse | null>;
54
+ public getTracks(identifier: String): Promise<void>;
55
+ public decodeTrack(track: String, node?: any): Promise<void>;
56
+ public decodeTracks(tracks: any[]): Promise<void>;
57
+ public getStats(): Promise<void>;
58
+ public getInfo(): Promise<void>;
59
+ public getRoutePlannerStatus(): Promise<void>;
60
+ public getRoutePlannerAddress(address: String): Promise<void>;
61
+ public parseResponse(req: any): Promise<RestResponse | null>;
62
+ }
63
+
64
+ export declare class Queue<T> extends Array<T>{
65
+ get size(): Number;
66
+ get first(): T | null;
67
+
68
+ add(track: T): this;
69
+ remove(index: Number): T;
70
+ clear(): void;
71
+ shuffle(): void;
72
+ }
73
+
74
+ export interface PlayerOptions {
75
+ guildId: String;
76
+ textChannel?: String;
77
+ voiceChannel?: String;
78
+ deaf?: Boolean;
79
+ mute?: Boolean;
80
+ volume?: Number;
81
+ loop?: String;
82
+ }
83
+
84
+ export type LoopOption = "none" | "track" | "queue";
85
+
86
+ export declare class Player extends EventEmitter {
87
+ constructor(muthera: muthera, node: Node, options: PlayerOptions);
88
+ public muthera: muthera;
89
+ public node: Node;
90
+ public options: PlayerOptions;
91
+ public guildId: String;
92
+ public textChannel: String;
93
+ public voiceChannel: String;
94
+ public connection: Connection;
95
+ public deaf: Boolean;
96
+ public mute: Boolean;
97
+ public volume: Number;
98
+ public loop: String;
99
+ public data: {};
100
+ public queue: Queue<Track>;
101
+ public position: Number;
102
+ public current: Track;
103
+ public previous: Track | null;
104
+ public playing: Boolean;
105
+ public paused: Boolean;
106
+ public connected: Boolean;
107
+ public timestamp: Number;
108
+ public ping: Number;
109
+ public isAutoplay: Boolean;
110
+
111
+ public play(): Promise<Player>;
112
+
113
+ public autoplay(player: Player): Promise<Player>;
114
+
115
+ public connect(options?: {
116
+ guildId: String;
117
+ voiceChannel: String;
118
+ deaf?: Boolean;
119
+ mute?: Boolean;
120
+ }): void;
121
+
122
+ public stop(): Player;
123
+ public pause(toggle?: Boolean): Player;
124
+ public seek(position: Number): void;
125
+ public setVolume(volume: Number): Player;
126
+ public setLoop(mode: LoopOption): Player;
127
+ public setTextChannel(channel: String): Player;
128
+ public setVoiceChannel(channel: String, options?: {
129
+ mute?: Boolean;
130
+ deaf?: Boolean;
131
+ }): Player;
132
+
133
+ public disconnect(): Player;
134
+ public destroy(): void;
135
+ private handleEvent(payload: any): void;
136
+ private trackStart(player: Player, track: Track, payload: any): void;
137
+ private trackEnd(player: Player, track: Track, payload: any): void;
138
+ private trackError(player: Player, track: Track, payload: any): void;
139
+ private trackStuck(player: Player, track: Track, payload: any): void;
140
+ private socketClosed(player: Player, payload: any): void;
141
+ private set(key: String, value: any): void;
142
+ private get(key: String): any;
143
+ private send(data: any): void;
144
+ }
145
+
146
+ export type SearchPlatform = "ytsearch" | "ytmsearch" | "scsearch";
147
+ export type nodeResponse = {
148
+ /**
149
+ * Array of Loaded Tracks
150
+ */
151
+ tracks: Array<Track>;
152
+ /**
153
+ * Load Type - "track", "playlist", "search", "error"
154
+ */
155
+ loadType: String
156
+ /**
157
+ * Playlist Info
158
+ */
159
+ playlistInfo?: {
160
+ name: String;
161
+ selectedTrack: Number;
162
+ };
163
+ }
164
+
165
+ export type mutheraOptions = {
166
+ send: (payload: {
167
+ op: Number;
168
+ d: {
169
+ guild_id: String;
170
+ channel_id: String;
171
+ self_deaf: Boolean;
172
+ self_mute: Boolean;
173
+ }
174
+ }) => void;
175
+ defaultSearchPlatform?: SearchPlatform;
176
+ }
177
+
178
+ type k = String;
179
+ type v = any;
180
+
181
+ export declare class muthera extends EventEmitter {
182
+ constructor(client: any, nodes: {
183
+ name?: String;
184
+ host: String;
185
+ port: Number;
186
+ password: String;
187
+ secure: Boolean;
188
+ }, options: mutheraOptions);
189
+ public client: any;
190
+ public nodes: Array<LavalinkNode>;
191
+ public nodeMap: Collection<k, Node>;
192
+ public players: Collection<k, Player>;
193
+ public options: mutheraOptions;
194
+ public clientId: String;
195
+ public initiated: Boolean;
196
+ public send: mutheraOptions["send"];
197
+ public defaultSearchPlatform: String;
198
+
199
+ public readonly leastUsedNodes: Array<LavalinkNode>;
200
+
201
+ public init(clientId: String): this;
202
+
203
+ public createNode(options: any): Node;
204
+
205
+ public destroyNode(identifier: String): void;
206
+
207
+ public updateVoiceState(packet: any): void;
208
+
209
+ public fetchRegion(region: String): Array<LavalinkNode>;
210
+
211
+ public createConnection(options: {
212
+ guildId: String;
213
+ voiceChannel: String;
214
+ textChannel: String;
215
+ deaf?: Boolean;
216
+ }): Player;
217
+
218
+ public createPlayer(node: Node, options: PlayerOptions): Player;
219
+
220
+ public removeConnection(guildId: String): void;
221
+
222
+ public resolve(params: {
223
+ query: String;
224
+ source?: String;
225
+ requester: any;
226
+ }): Promise<nodeResponse>;
227
+
228
+
229
+ public get(guildId: String): Player;
230
+
231
+ public on(event: "nodeConnect", listener: (node: Node) => void): this;
232
+ public on(event: "nodeReconnect", listener: (node: Node) => void): this;
233
+ public on(event: "nodeDisconnect", listener: (node: Node, reason: String) => void): this;
234
+ public on(event: "nodeCreate", listener: (node: Node) => void): this;
235
+ public on(event: "nodeDestroy", listener: (node: Node) => void): this;
236
+ public on(event: "nodeError", listener: (node: Node, error: Error) => void): this;
237
+
238
+ public on(event: "trackStart", listener: (player: Player, track: Track, payload: any) => void): this;
239
+ public on(event: "trackEnd", listener: (player: Player, track: Track, payload: any) => void): this;
240
+ public on(event: "trackError", listener: (player: Player, track: Track, payload: any) => void): this;
241
+ public on(event: "trackStuck", listener: (player: Player, track: Track, payload: any) => void): this;
242
+
243
+ public on(event: "socketClosed", listener: (player: Player, payload: any) => void): this;
244
+
245
+ public on(event: "playerCreate", listener: (player: Player) => void): this;
246
+ public on(event: "playerDisconnect", listener: (player: Player) => void): this;
247
+ public on(event: "playerMove", listener: (player: Player, oldChannel: String, newChannel: String) => void): this;
248
+ public on(event: "playerUpdate", listener: (player: Player, payload: any) => void): this;
249
+
250
+ public on(event: "queueEnd", listener: (player: Player) => void): this;
251
+ public on(event: "debug", listener: (message: String) => void): this;
252
+ }
253
+
254
+ export type LavalinkNode = {
255
+ /**
256
+ * The name of the node
257
+ */
258
+ name?: String;
259
+ /**
260
+ * The IP of the node
261
+ */
262
+ host: String;
263
+ /**
264
+ * The port of the node
265
+ */
266
+ port: Number;
267
+ /**
268
+ * The password of the node
269
+ */
270
+ password: String;
271
+ /**
272
+ * Is node connection secured by SSL ?
273
+ */
274
+ secure: Boolean;
275
+ }
276
+
277
+ export type NodeOptions = {
278
+ /**
279
+ * The send function of the node
280
+ */
281
+ send: (payload: {
282
+ op: Number;
283
+ d: {
284
+ guild_id: String;
285
+ channel_id: String;
286
+ self_deaf: Boolean;
287
+ self_mute: Boolean;
288
+ }
289
+ }) => void;
290
+ /**
291
+ * The resume key of the node
292
+ */
293
+ resumeKey?: String;
294
+ /**
295
+ * The session id of the node
296
+ */
297
+ sessionId?: String;
298
+ /**
299
+ * The resume timeout of the node
300
+ */
301
+ resumeTimeout?: Number;
302
+ /**
303
+ * The reconnect timeout of the node
304
+ */
305
+ reconnectTimeout?: Number;
306
+ /**
307
+ * The reconnect tries of the node
308
+ */
309
+ reconnectTries?: Number;
310
+ }
311
+
312
+ export declare class Node {
313
+ constructor(muthera: muthera, node: LavalinkNode, options: NodeOptions);
314
+ public muthera: muthera;
315
+
316
+ public name: LavalinkNode["name"];
317
+ public host: LavalinkNode["host"];
318
+ public port: LavalinkNode["port"];
319
+ public password: LavalinkNode["password"];
320
+ public secure: LavalinkNode["secure"];
321
+
322
+ public rest: Rest;
323
+ public wsUrl: String;
324
+ public restUrl: String;
325
+ private ws: null;
326
+
327
+ public send: NodeOptions["send"];
328
+ public resumeKey: NodeOptions["resumeKey"];
329
+ public sessionId: NodeOptions["sessionId"];
330
+ public region: String | null;
331
+ public resumeTimeout: NodeOptions["resumeTimeout"];
332
+ public reconnectTimeout: NodeOptions["reconnectTimeout"];
333
+ public reconnectTries: NodeOptions["reconnectTries"];
334
+
335
+ public reconnectAttempt: Number;
336
+ public reconnectAttempted: Number;
337
+
338
+ public connected: Boolean;
339
+ public reconnecting: Boolean;
340
+ public stats: {
341
+ players: 0,
342
+ playingPlayers: 0,
343
+ uptime: 0,
344
+ memory: {
345
+ free: 0,
346
+ used: 0,
347
+ allocated: 0,
348
+ reservable: 0,
349
+ },
350
+ cpu: {
351
+ cores: 0,
352
+ systemLoad: 0,
353
+ lavalinkLoad: 0,
354
+ },
355
+ frameStats: {
356
+ sent: 0,
357
+ nulled: 0,
358
+ deficit: 0,
359
+ },
360
+ };
361
+
362
+ public connect(): void;
363
+ public open(): void;
364
+ public error(event: any): void;
365
+ public message(msg: any): void;
366
+ public close(event: any, reason: String): void;
367
+ public reconnect(): void;
368
+ public disconnect(): void;
369
+ readonly penalties: Number;
370
+ }
371
+
372
+ export type Voice = {
373
+ /**
374
+ * The voice session id
375
+ */
376
+ sessionId: String,
377
+ /**
378
+ * The voice event
379
+ */
380
+ event: null,
381
+ /**
382
+ * The voice endpoint
383
+ */
384
+ endpoint: String
385
+ }
386
+
387
+ export declare class Connection {
388
+ constructor(player: Player);
389
+ public player: Player;
390
+ public sessionId: String;
391
+ public voice: Voice;
392
+ public region: String;
393
+ public self_deaf: Boolean;
394
+ public self_mute: Boolean;
395
+ public voiceChannel: String;
396
+
397
+ public setServerUpdate(data: { endpoint: String; token: String }): void;
398
+
399
+ public setStateUpdate(data: {
400
+ session_id: String;
401
+ channel_id: String;
402
+ self_deaf: Boolean;
403
+ self_mute: Boolean;
404
+ }): void;
405
+
406
+ private updatePlayerVoiceData(): void;
407
+ }
package/src/index.js ADDED
@@ -0,0 +1,9 @@
1
+ const { Connection } = require("./structures/mutheraConnection");
2
+ const { Node } = require("./structures/mutheraNode");
3
+ const { Muthera } = require("./structures/Muthera");
4
+ const { Player } = require("./structures/mutheraPlayer");
5
+ const { Queue } = require("./structures/mutheraQueue");
6
+ const { Rest } = require("./structures/mutheraRest");
7
+ const { Track } = require("./structures/mutheraTrack");
8
+
9
+ module.exports = { Connection, Node, Muthera, Player, Queue, Rest, Track };
@@ -0,0 +1,191 @@
1
+ const { EventEmitter } = require("events");
2
+ const { Node } = require("./mutheraNode");
3
+ const { Player } = require("./mutheraPlayer");
4
+ const { Track } = require("./mutheraTrack");
5
+ const { Collection } = require("@discordjs/collection");
6
+
7
+ class Muthera extends EventEmitter {
8
+ constructor(client, nodes, options) {
9
+ super();
10
+ if (!client)
11
+ throw new Error("Client option must be present to initialize muthera.");
12
+ if (!nodes)
13
+ throw new Error("Node option must be present to initialize muthera.");
14
+ if (!options.send)
15
+ throw new Error("Send function must be present to initialize muthera.");
16
+
17
+ this.client = client;
18
+ this.nodes = nodes;
19
+ this.nodeMap = new Collection();
20
+ this.players = new Collection();
21
+ this.options = options;
22
+ this.clientId = null;
23
+ this.initiated = false;
24
+ this.send = options.send || null;
25
+ this.defaultSearchPlatform = options.defaultSearchPlatform || "ytsearch";
26
+ this.tracks = [];
27
+ this.loadType = null;
28
+ this.playlistInfo = null;
29
+ }
30
+
31
+ get leastUsedNodes() {
32
+ return [...this.nodeMap.values()]
33
+ .filter((node) => node.connected)
34
+ .sort((a, b) => b.rest.calls - a.rest.calls);
35
+ }
36
+
37
+ init(clientId) {
38
+ if (this.initiated) return this;
39
+ this.clientId = clientId;
40
+ this.nodes.forEach((node) => this.createNode(node));
41
+ this.initiated = true;
42
+ }
43
+
44
+ createNode(options) {
45
+ const node = new Node(this, options, this.options);
46
+ this.nodeMap.set(options.name || options.host, node);
47
+ node.connect();
48
+
49
+ this.emit("nodeCreate", node);
50
+ return node;
51
+ }
52
+
53
+ destroyNode(identifier) {
54
+ const node = this.nodeMap.get(identifier);
55
+ if (!node) return;
56
+ node.disconnect();
57
+ this.nodeMap.delete(identifier);
58
+ this.emit("nodeDestroy", node);
59
+ }
60
+
61
+ updateVoiceState(packet) {
62
+ if (!["VOICE_STATE_UPDATE", "VOICE_SERVER_UPDATE"].includes(packet.t))
63
+ return;
64
+ const player = this.players.get(packet.d.guild_id);
65
+ if (!player) return;
66
+
67
+ if (packet.t === "VOICE_SERVER_UPDATE") {
68
+ player.connection.setServerUpdate(packet.d);
69
+ } else if (packet.t === "VOICE_STATE_UPDATE") {
70
+ if (packet.d.user_id !== this.clientId) return;
71
+ player.connection.setStateUpdate(packet.d);
72
+ }
73
+ }
74
+
75
+ fetchRegion(region) {
76
+ const nodesByRegion = [...this.nodeMap.values()]
77
+ .filter(
78
+ (node) =>
79
+ node.connected && node.regions?.includes(region?.toLowerCase())
80
+ )
81
+ .sort((a, b) => {
82
+ const aLoad = a.stats.cpu
83
+ ? (a.stats.cpu.systemLoad / a.stats.cpu.cores) * 100
84
+ : 0;
85
+ const bLoad = b.stats.cpu
86
+ ? (b.stats.cpu.systemLoad / b.stats.cpu.cores) * 100
87
+ : 0;
88
+ return aLoad - bLoad;
89
+ });
90
+
91
+ return nodesByRegion;
92
+ }
93
+
94
+ createConnection(options) {
95
+ if (!this.initiated)
96
+ throw new Error("You have to initialize muthera in your event.");
97
+
98
+ const player = this.players.get(options.guildId);
99
+ if (player) return player;
100
+
101
+ if (this.leastUsedNodes.length === 0)
102
+ throw new Error("No nodes are available.");
103
+
104
+ let node;
105
+ if (options.node) {
106
+ node = this.nodeMap.get(options.node);
107
+ } else {
108
+ node = this.nodeMap.get(this.leastUsedNodes[0].name);
109
+ }
110
+
111
+ if (!node) throw new Error("No nodes are available.");
112
+
113
+ return this.createPlayer(node, options);
114
+ }
115
+
116
+ createPlayer(node, options) {
117
+ const player = new Player(this, node, options);
118
+ this.players.set(options.guildId, player);
119
+
120
+ player.connect(options);
121
+
122
+ this.emit("playerCreate", player);
123
+ return player;
124
+ }
125
+
126
+ destroyPlayer(guildId) {
127
+ const player = this.players.get(guildId);
128
+ if (!player) return;
129
+ player.destroy();
130
+ this.players.delete(guildId);
131
+
132
+ this.emit("playerDestroy", player);
133
+ }
134
+
135
+ removeConnection(guildId) {
136
+ this.players.get(guildId)?.destroy();
137
+ this.players.delete(guildId);
138
+ }
139
+
140
+ async resolve({ query, source, requester }) {
141
+ try {
142
+ if (!this.initiated)
143
+ throw new Error("You have to initialize muthera in your event.");
144
+
145
+ const sources = source || this.defaultSearchPlatform;
146
+
147
+ const node = this.leastUsedNodes[0];
148
+ if (!node) throw new Error("No nodes are available.");
149
+
150
+ const regex = /^https?:\/\//;
151
+ const identifier = regex.test(query) ? query : `${sources}:${query}`;
152
+
153
+ let response = await node.rest.makeRequest(
154
+ `GET`,
155
+ `/v4/loadtracks?identifier=${encodeURIComponent(identifier)}`
156
+ );
157
+
158
+ if (response.loadType === "track") {
159
+ this.tracks = [new Track(response.data, requester, node)];
160
+ } else if (response.loadType === "playlist") {
161
+ this.tracks = response.data.tracks.map(
162
+ (track) => new Track(track, requester, node)
163
+ );
164
+ } else if (
165
+ response.loadType === "error" ||
166
+ response.loadType === "empty"
167
+ ) {
168
+ this.tracks = null;
169
+ } else {
170
+ this.tracks = response.data.map(
171
+ (track) => new Track(track, requester, node)
172
+ );
173
+ }
174
+
175
+ this.playlistInfo = response.data.info ?? null;
176
+ this.loadType = response.loadType ?? null;
177
+ return this;
178
+ } catch (error) {
179
+ throw new Error(error);
180
+ }
181
+ }
182
+
183
+ get(guildId) {
184
+ const player = this.players.get(guildId);
185
+ if (!player)
186
+ throw new Error(`No player were found for guildId: ${guildId}.`);
187
+ return player;
188
+ }
189
+ }
190
+
191
+ module.exports = { Muthera };
@@ -0,0 +1,66 @@
1
+ class Connection {
2
+ /**
3
+ * @param {import ("../index").Player} player
4
+ */
5
+ constructor(player) {
6
+ this.player = player;
7
+ this.sessionId = null;
8
+ this.voice = {
9
+ event: null,
10
+ endpoint: null,
11
+ sessionId: null,
12
+ };
13
+ this.self_deaf = false;
14
+ this.self_mute = false;
15
+ this.region = null;
16
+ this.voiceChannel = player.voiceChannel;
17
+ }
18
+
19
+ setServerUpdate(data) {
20
+ const { endpoint, token } = data;
21
+
22
+ if (!endpoint) throw new Error("Invalid session. Please try again.");
23
+
24
+ this.voice.endpoint = endpoint;
25
+ this.voice.token = token;
26
+ this.region = endpoint.split(".").shift()?.replace(/[0-9]/g, "") || null;
27
+
28
+ setTimeout(() => {
29
+ this.player.pause(false)
30
+ }, 5000);
31
+
32
+ this.updatePlayerVoiceData();
33
+ }
34
+
35
+ setStateUpdate(data) {
36
+ const { session_id, channel_id, self_deaf, self_mute } = data;
37
+
38
+ if (
39
+ this.player.voiceChannel &&
40
+ channel_id &&
41
+ this.player.voiceChannel !== channel_id
42
+ ) {
43
+ this.player.muthera.emit("playerMove", this.player.voiceChannel, channel_id);
44
+ this.player.voiceChannel = channel_id;
45
+ this.voiceChannel = channel_id;
46
+ }
47
+
48
+ this.self_deaf = self_deaf;
49
+ this.self_mute = self_mute;
50
+ this.voice.sessionId = session_id || null;
51
+ }
52
+
53
+ updatePlayerVoiceData() {
54
+ this.player.muthera.emit(
55
+ "debug",
56
+ this.player.node.name,
57
+ `Update player data: ${JSON.stringify({ voice: this.voice })}`
58
+ );
59
+ this.player.node.rest.updatePlayer({
60
+ guildId: this.player.guildId,
61
+ data: { voice: this.voice },
62
+ });
63
+ }
64
+ }
65
+
66
+ module.exports = { Connection };