yukimu 1.3.0 → 2.0.8
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/package.json +49 -5
- package/.cache/replit/env/latest +0 -88
- package/.cache/replit/env/latest.json +0 -1
- package/.cache/replit/modules/nodejs-20.res +0 -1
- package/.cache/replit/modules/python-3.11.res +0 -1
- package/.cache/replit/modules/replit.res +0 -1
- package/.cache/replit/modules.stamp +0 -0
- package/.cache/replit/nix/dotreplitenv.json +0 -1
- package/.cache/replit/toolchain.json +0 -1
- package/.local/state/workflow-logs/7zVU0iVo-fBL1ccMCmELy/configure_your_app.packager.installForAll.0 +0 -9
- package/.local/state/workflow-logs/7zVU0iVo-fBL1ccMCmELy/configure_your_app.shell.exec.1 +0 -1
- package/.local/state/workflow-logs/KRgHXizaECjWI5nWtS7Dj/configure_your_app.packager.installForAll.0 +0 -1
- package/.local/state/workflow-logs/KRgHXizaECjWI5nWtS7Dj/configure_your_app.shell.exec.1 +0 -1
- package/.local/state/workflow-logs/U0AinJQVHonnwGjj0RXLn/configure_your_app.packager.installForAll.0 +0 -2
- package/.local/state/workflow-logs/jVavLOnv1MqxUvxhMmqER/configure_your_app.packager.installForAll.0 +0 -1
- package/.local/state/workflow-logs/jVavLOnv1MqxUvxhMmqER/configure_your_app.shell.exec.1 +0 -1
- package/.replit +0 -7
- package/.upm/store.json +0 -1
- package/dist/ConnectionPool.d.ts +0 -35
- package/dist/ConnectionPool.d.ts.map +0 -1
- package/dist/ConnectionPool.js +0 -111
- package/dist/ConnectionPool.js.map +0 -1
- package/dist/Constants.d.ts +0 -20
- package/dist/Constants.d.ts.map +0 -1
- package/dist/Constants.js +0 -46
- package/dist/Constants.js.map +0 -1
- package/dist/Node.d.ts +0 -48
- package/dist/Node.d.ts.map +0 -1
- package/dist/Node.js +0 -308
- package/dist/Node.js.map +0 -1
- package/dist/Player.d.ts +0 -68
- package/dist/Player.d.ts.map +0 -1
- package/dist/Player.js +0 -305
- package/dist/Player.js.map +0 -1
- package/dist/Plugin.d.ts +0 -7
- package/dist/Plugin.d.ts.map +0 -1
- package/dist/Plugin.js +0 -7
- package/dist/Plugin.js.map +0 -1
- package/dist/Queue.d.ts +0 -22
- package/dist/Queue.d.ts.map +0 -1
- package/dist/Queue.js +0 -67
- package/dist/Queue.js.map +0 -1
- package/dist/Resolver.d.ts +0 -13
- package/dist/Resolver.d.ts.map +0 -1
- package/dist/Resolver.js +0 -97
- package/dist/Resolver.js.map +0 -1
- package/dist/Rest.d.ts +0 -23
- package/dist/Rest.d.ts.map +0 -1
- package/dist/Rest.js +0 -114
- package/dist/Rest.js.map +0 -1
- package/dist/TrackCache.d.ts +0 -16
- package/dist/TrackCache.d.ts.map +0 -1
- package/dist/TrackCache.js +0 -40
- package/dist/TrackCache.js.map +0 -1
- package/dist/WsQueue.d.ts +0 -11
- package/dist/WsQueue.d.ts.map +0 -1
- package/dist/WsQueue.js +0 -58
- package/dist/WsQueue.js.map +0 -1
- package/dist/Yukimu.d.ts +0 -51
- package/dist/Yukimu.d.ts.map +0 -1
- package/dist/Yukimu.js +0 -197
- package/dist/Yukimu.js.map +0 -1
- package/dist/connector/Connector.d.ts +0 -8
- package/dist/connector/Connector.d.ts.map +0 -1
- package/dist/connector/Connector.js +0 -11
- package/dist/connector/Connector.js.map +0 -1
- package/dist/connector/DiscordJS.d.ts +0 -8
- package/dist/connector/DiscordJS.d.ts.map +0 -1
- package/dist/connector/DiscordJS.js +0 -27
- package/dist/connector/DiscordJS.js.map +0 -1
- package/dist/connector/Eris.d.ts +0 -8
- package/dist/connector/Eris.d.ts.map +0 -1
- package/dist/connector/Eris.js +0 -28
- package/dist/connector/Eris.js.map +0 -1
- package/dist/connector/Oceanic.d.ts +0 -8
- package/dist/connector/Oceanic.d.ts.map +0 -1
- package/dist/connector/Oceanic.js +0 -25
- package/dist/connector/Oceanic.js.map +0 -1
- package/dist/errors/YukimuError.d.ts +0 -15
- package/dist/errors/YukimuError.d.ts.map +0 -1
- package/dist/errors/YukimuError.js +0 -34
- package/dist/errors/YukimuError.js.map +0 -1
- package/dist/index.d.ts +0 -20
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -56
- package/dist/index.js.map +0 -1
- package/dist/plugins/AutoResume.d.ts +0 -8
- package/dist/plugins/AutoResume.d.ts.map +0 -1
- package/dist/plugins/AutoResume.js +0 -44
- package/dist/plugins/AutoResume.js.map +0 -1
- package/dist/plugins/PlayerMoved.d.ts +0 -7
- package/dist/plugins/PlayerMoved.d.ts.map +0 -1
- package/dist/plugins/PlayerMoved.js +0 -30
- package/dist/plugins/PlayerMoved.js.map +0 -1
- package/dist/types.d.ts +0 -182
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -3
- package/dist/types.js.map +0 -1
- package/src/ConnectionPool.ts +0 -114
- package/src/Constants.ts +0 -45
- package/src/Node.ts +0 -302
- package/src/Player.ts +0 -332
- package/src/Plugin.ts +0 -7
- package/src/Queue.ts +0 -66
- package/src/Resolver.ts +0 -90
- package/src/Rest.ts +0 -127
- package/src/TrackCache.ts +0 -46
- package/src/WsQueue.ts +0 -58
- package/src/Yukimu.ts +0 -213
- package/src/connector/Connector.ts +0 -13
- package/src/connector/DiscordJS.ts +0 -26
- package/src/connector/Eris.ts +0 -24
- package/src/connector/Oceanic.ts +0 -22
- package/src/errors/YukimuError.ts +0 -31
- package/src/index.ts +0 -24
- package/src/plugins/AutoResume.ts +0 -37
- package/src/plugins/PlayerMoved.ts +0 -26
- package/src/types.ts +0 -145
- package/tsconfig.json +0 -22
package/src/Constants.ts
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
export const VERSION = "1.3.0";
|
|
2
|
-
export const CLIENT_NAME = `Yukimu/${VERSION}`;
|
|
3
|
-
|
|
4
|
-
export enum State {
|
|
5
|
-
CONNECTING = 0,
|
|
6
|
-
NEARLY = 1,
|
|
7
|
-
CONNECTED = 2,
|
|
8
|
-
DISCONNECTING = 3,
|
|
9
|
-
DISCONNECTED = 4,
|
|
10
|
-
RECONNECTING = 5,
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export enum PlayerState {
|
|
14
|
-
CONNECTING = 0,
|
|
15
|
-
CONNECTED = 1,
|
|
16
|
-
DISCONNECTING = 2,
|
|
17
|
-
DISCONNECTED = 3,
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export const SOURCE_PREFIXES: Record<string, string> = {
|
|
21
|
-
youtube: "ytsearch",
|
|
22
|
-
youtubemusic: "ytmsearch",
|
|
23
|
-
spotify: "spsearch",
|
|
24
|
-
deezer: "dzsearch",
|
|
25
|
-
applemusic: "amsearch",
|
|
26
|
-
soundcloud: "scsearch",
|
|
27
|
-
tidal: "tdsearch",
|
|
28
|
-
jiosaavn: "jssearch",
|
|
29
|
-
yandexmusic: "ymsearch",
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
export const URL_PATTERNS: Record<string, RegExp[]> = {
|
|
33
|
-
youtube: [/^https?:\/\/(www\.)?(youtube\.com|youtu\.be)\/.+/, /^https?:\/\/music\.youtube\.com\/.+/],
|
|
34
|
-
spotify: [/^https?:\/\/open\.spotify\.com\/(track|album|playlist|artist)\/.+/],
|
|
35
|
-
soundcloud: [/^https?:\/\/(www\.)?soundcloud\.com\/.+/],
|
|
36
|
-
deezer: [/^https?:\/\/(www\.)?deezer\.com\/(track|album|playlist)\/.+/],
|
|
37
|
-
applemusic: [/^https?:\/\/music\.apple\.com\/.+/],
|
|
38
|
-
tidal: [/^https?:\/\/(www\.)?tidal\.com\/.+/],
|
|
39
|
-
jiosaavn: [/^https?:\/\/(www\.)?jiosaavn\.com\/.+/],
|
|
40
|
-
yandexmusic: [/^https?:\/\/music\.yandex\.(ru|com)\/.+/],
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
export const STOPPED_REASONS = new Set([
|
|
44
|
-
"replaced", "stopped", "REPLACED", "STOPPED",
|
|
45
|
-
]);
|
package/src/Node.ts
DELETED
|
@@ -1,302 +0,0 @@
|
|
|
1
|
-
import WebSocket from "ws";
|
|
2
|
-
import { Rest } from "./Rest";
|
|
3
|
-
import { WsQueue } from "./WsQueue";
|
|
4
|
-
import { State } from "./Constants";
|
|
5
|
-
import { NodeError } from "./errors/YukimuError";
|
|
6
|
-
import type { Yukimu } from "./Yukimu";
|
|
7
|
-
import type { NodeOptions, NodeStats, NodeInfo, Track, SearchResult, LavalinkException } from "./types";
|
|
8
|
-
|
|
9
|
-
const WS_CLOSE_NORMAL = 1000;
|
|
10
|
-
const HEARTBEAT_INTERVAL = 30000;
|
|
11
|
-
|
|
12
|
-
export class Node {
|
|
13
|
-
public readonly manager: Yukimu;
|
|
14
|
-
public readonly options: NodeOptions;
|
|
15
|
-
public readonly rest: Rest;
|
|
16
|
-
public readonly wsQueue: WsQueue;
|
|
17
|
-
public readonly version: 3 | 4;
|
|
18
|
-
|
|
19
|
-
public ws: WebSocket | null = null;
|
|
20
|
-
public state: State = State.DISCONNECTED;
|
|
21
|
-
public stats: NodeStats | null = null;
|
|
22
|
-
public info: NodeInfo | null = null;
|
|
23
|
-
public sessionId: string | null = null;
|
|
24
|
-
public resumed: boolean = false;
|
|
25
|
-
public penalties: number = 0;
|
|
26
|
-
public ping: number = -1;
|
|
27
|
-
|
|
28
|
-
private restQueue: Promise<any> = Promise.resolve();
|
|
29
|
-
private reconnectAttempts = 0;
|
|
30
|
-
private reconnectTimeout?: ReturnType<typeof setTimeout>;
|
|
31
|
-
private heartbeatInterval?: ReturnType<typeof setInterval>;
|
|
32
|
-
private lastHeartbeatAt = 0;
|
|
33
|
-
|
|
34
|
-
constructor(manager: Yukimu, options: NodeOptions) {
|
|
35
|
-
this.manager = manager;
|
|
36
|
-
this.options = { secure: false, retries: 5, version: 4, resumeTimeout: 60, ...options };
|
|
37
|
-
this.version = (this.options.version ?? 4) as 3 | 4;
|
|
38
|
-
this.rest = new Rest(this);
|
|
39
|
-
this.wsQueue = new WsQueue();
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
get connected(): boolean {
|
|
43
|
-
return this.state === State.CONNECTED || this.state === State.NEARLY;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
get wsUrl(): string {
|
|
47
|
-
const protocol = this.options.secure ? "wss" : "ws";
|
|
48
|
-
const base = `${protocol}://${this.options.host}:${this.options.port}`;
|
|
49
|
-
return this.version === 4 ? `${base}/v4/websocket` : base;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
get wsHeaders(): Record<string, string> {
|
|
53
|
-
const h: Record<string, string> = {
|
|
54
|
-
Authorization: this.options.password,
|
|
55
|
-
"User-Id": this.manager.options.clientId,
|
|
56
|
-
"Client-Name": "Yukimu/1.3.0",
|
|
57
|
-
};
|
|
58
|
-
if (this.version === 3) h["Num-Shards"] = "1";
|
|
59
|
-
if (this.options.resumeKey) h["Resume-Key"] = this.options.resumeKey;
|
|
60
|
-
return h;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
public calculatePenalties(): number {
|
|
64
|
-
if (!this.stats) return Infinity;
|
|
65
|
-
const cpu = Math.pow(1.05, 100 * this.stats.cpu.systemLoad) * 10 - 10;
|
|
66
|
-
const fs = this.stats.frameStats;
|
|
67
|
-
const deficit = fs ? Math.pow(1.03, 500 * fs.deficit / 3000) * 600 - 600 : 0;
|
|
68
|
-
const nulled = fs ? Math.pow(1.03, 500 * fs.nulled / 3000) * 300 - 300 : 0;
|
|
69
|
-
this.penalties = ~~(cpu + deficit + nulled + this.stats.playingPlayers);
|
|
70
|
-
return this.penalties;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
public connect(): void {
|
|
74
|
-
if (this.ws?.readyState === WebSocket.OPEN) return;
|
|
75
|
-
this.state = State.CONNECTING;
|
|
76
|
-
this.manager.emit("nodeConnecting", this);
|
|
77
|
-
this.ws = new WebSocket(this.wsUrl, { headers: this.wsHeaders });
|
|
78
|
-
this.wsQueue.setSocket(this.ws);
|
|
79
|
-
this.ws.on("open", () => this.onOpen());
|
|
80
|
-
this.ws.on("message", (d: any) => this.onMessage(d));
|
|
81
|
-
this.ws.on("close", (code: number, reason: any) => this.onClose(code, reason.toString()));
|
|
82
|
-
this.ws.on("error", (err: Error) => this.onError(err));
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
public destroy(reconnect = false): void {
|
|
86
|
-
if (this.reconnectTimeout) clearTimeout(this.reconnectTimeout);
|
|
87
|
-
if (this.heartbeatInterval) clearInterval(this.heartbeatInterval);
|
|
88
|
-
this.wsQueue.clear();
|
|
89
|
-
this.wsQueue.setSocket(null);
|
|
90
|
-
this.ws?.removeAllListeners();
|
|
91
|
-
this.ws?.close(WS_CLOSE_NORMAL, "destroy");
|
|
92
|
-
this.ws = null;
|
|
93
|
-
this.state = State.DISCONNECTED;
|
|
94
|
-
if (!reconnect) this.sessionId = null;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
private async onOpen(): Promise<void> {
|
|
98
|
-
this.state = State.NEARLY;
|
|
99
|
-
this.reconnectAttempts = 0;
|
|
100
|
-
this.manager.emit("nodeConnect", this);
|
|
101
|
-
console.log(`[Yukimu] Node "${this.options.name}" connected (v${this.version})`);
|
|
102
|
-
|
|
103
|
-
try { this.info = await this.rest.getInfo(); } catch {}
|
|
104
|
-
|
|
105
|
-
if (this.version === 4 && this.options.resumeKey) {
|
|
106
|
-
await this.rest.updateSession({ resuming: true, timeout: this.options.resumeTimeout ?? 60 }).catch(() => {});
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
if (this.version === 3) {
|
|
110
|
-
this.state = State.CONNECTED;
|
|
111
|
-
this.sessionId = this.options.resumeKey ?? `v3-${Date.now()}`;
|
|
112
|
-
this.manager.emit("nodeReady", this);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
this.startHeartbeat();
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
private onMessage(raw: any): void {
|
|
119
|
-
let payload: any;
|
|
120
|
-
try { payload = JSON.parse(raw.toString()); } catch { return; }
|
|
121
|
-
const op = payload.op as string;
|
|
122
|
-
|
|
123
|
-
if (this.version === 4) {
|
|
124
|
-
switch (op) {
|
|
125
|
-
case "ready":
|
|
126
|
-
this.state = State.CONNECTED;
|
|
127
|
-
this.sessionId = payload.sessionId;
|
|
128
|
-
this.resumed = payload.resumed ?? false;
|
|
129
|
-
this.manager.emit("nodeReady", this);
|
|
130
|
-
console.log(`[Yukimu] Node "${this.options.name}" ready | session: ${this.sessionId} | resumed: ${this.resumed}`);
|
|
131
|
-
if (this.resumed) this.reconnectPlayers();
|
|
132
|
-
break;
|
|
133
|
-
case "stats":
|
|
134
|
-
this.stats = payload;
|
|
135
|
-
this.calculatePenalties();
|
|
136
|
-
break;
|
|
137
|
-
case "playerUpdate":
|
|
138
|
-
this.handlePlayerUpdate(payload);
|
|
139
|
-
break;
|
|
140
|
-
case "event":
|
|
141
|
-
this.handleEvent(payload);
|
|
142
|
-
break;
|
|
143
|
-
}
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
switch (op) {
|
|
148
|
-
case "stats": this.stats = payload; this.calculatePenalties(); break;
|
|
149
|
-
case "playerUpdate": this.handlePlayerUpdate(payload); break;
|
|
150
|
-
case "event": this.handleEvent(payload); break;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
private onClose(code: number, reason: string): void {
|
|
155
|
-
if (this.heartbeatInterval) clearInterval(this.heartbeatInterval);
|
|
156
|
-
this.state = State.DISCONNECTED;
|
|
157
|
-
this.manager.emit("nodeDisconnect", this, code, reason);
|
|
158
|
-
console.warn(`[Yukimu] Node "${this.options.name}" disconnected (${code}): ${reason || "No reason"}`);
|
|
159
|
-
if (code !== WS_CLOSE_NORMAL) this.scheduleReconnect();
|
|
160
|
-
else this.movePlayers();
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
private onError(error: Error): void {
|
|
164
|
-
this.manager.emit("nodeError", this, error);
|
|
165
|
-
console.error(`[Yukimu] Node "${this.options.name}" error:`, error.message);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
private startHeartbeat(): void {
|
|
169
|
-
if (this.heartbeatInterval) clearInterval(this.heartbeatInterval);
|
|
170
|
-
this.heartbeatInterval = setInterval(async () => {
|
|
171
|
-
if (!this.connected) return;
|
|
172
|
-
this.lastHeartbeatAt = Date.now();
|
|
173
|
-
try {
|
|
174
|
-
this.stats = await this.rest.getStats();
|
|
175
|
-
this.ping = Date.now() - this.lastHeartbeatAt;
|
|
176
|
-
this.calculatePenalties();
|
|
177
|
-
} catch {}
|
|
178
|
-
}, HEARTBEAT_INTERVAL);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
private scheduleReconnect(): void {
|
|
182
|
-
const max = this.options.retries ?? 5;
|
|
183
|
-
if (this.reconnectAttempts >= max) {
|
|
184
|
-
this.manager.emit("nodeError", this, new NodeError(`Node "${this.options.name}" max retries reached`));
|
|
185
|
-
this.movePlayers();
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000);
|
|
189
|
-
this.reconnectAttempts++;
|
|
190
|
-
this.state = State.RECONNECTING;
|
|
191
|
-
console.log(`[Yukimu] Reconnecting "${this.options.name}" in ${delay}ms (${this.reconnectAttempts}/${max})`);
|
|
192
|
-
this.reconnectTimeout = setTimeout(() => { this.destroy(true); this.connect(); }, delay);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
private movePlayers(): void {
|
|
196
|
-
const players = [...this.manager.players.values()].filter((p: any) => p.node === this);
|
|
197
|
-
if (!players.length) return;
|
|
198
|
-
let best: Node;
|
|
199
|
-
try { best = this.manager.getBestNode(); } catch { return; }
|
|
200
|
-
if (best === this) return;
|
|
201
|
-
console.log(`[Yukimu] Moving ${players.length} players to "${best.options.name}"`);
|
|
202
|
-
for (const player of players) (player as any).moveToNode(best).catch(console.error);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
private reconnectPlayers(): void {
|
|
206
|
-
for (const player of this.manager.players.values()) {
|
|
207
|
-
if ((player as any).node === this) (player as any).checkVoiceReady();
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
private handlePlayerUpdate(payload: any): void {
|
|
212
|
-
const player = this.manager.players.get(payload.guildId);
|
|
213
|
-
if (!player) return;
|
|
214
|
-
const state = payload.state ?? {};
|
|
215
|
-
(player as any).position = state.position ?? (player as any).position;
|
|
216
|
-
(player as any).ping = state.ping ?? (player as any).ping;
|
|
217
|
-
(player as any).lastUpdated = state.time ?? Date.now();
|
|
218
|
-
this.manager.emit("playerUpdate", player);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
private handleEvent(payload: any): void {
|
|
222
|
-
const player = this.manager.players.get(payload.guildId);
|
|
223
|
-
if (!player) return;
|
|
224
|
-
|
|
225
|
-
let track: Track;
|
|
226
|
-
if (this.version === 3) {
|
|
227
|
-
track = { encoded: payload.track, info: {} as any, pluginInfo: {} };
|
|
228
|
-
} else {
|
|
229
|
-
track = payload.track;
|
|
230
|
-
if (track?.encoded) this.manager.trackCache.set(track.encoded, track);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
const p = player as any;
|
|
234
|
-
|
|
235
|
-
switch (payload.type) {
|
|
236
|
-
case "TrackStartEvent":
|
|
237
|
-
p.playing = true;
|
|
238
|
-
p.paused = false;
|
|
239
|
-
p.lastUpdated = Date.now();
|
|
240
|
-
this.manager.emit("trackStart", player, track);
|
|
241
|
-
break;
|
|
242
|
-
|
|
243
|
-
case "TrackEndEvent": {
|
|
244
|
-
const reason = payload.reason;
|
|
245
|
-
p.playing = false;
|
|
246
|
-
p.position = 0;
|
|
247
|
-
this.manager.emit("trackEnd", player, track, reason);
|
|
248
|
-
const stopped = ["replaced", "stopped", "REPLACED", "STOPPED"].includes(reason);
|
|
249
|
-
if (!stopped) {
|
|
250
|
-
if (p.loop === "track" && p.queue.current) {
|
|
251
|
-
p.play(p.queue.current).catch(() => {});
|
|
252
|
-
} else {
|
|
253
|
-
if (p.loop === "queue" && p.queue.current) p.queue.add(p.queue.current);
|
|
254
|
-
const next = p.queue.next();
|
|
255
|
-
if (next) {
|
|
256
|
-
p.play(next).catch(() => {});
|
|
257
|
-
} else {
|
|
258
|
-
this.manager.emit("queueEnd", player);
|
|
259
|
-
if (p.autoplay && track.info?.uri) {
|
|
260
|
-
this.manager.emit("autoplayRequest", player, track);
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
break;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
case "TrackExceptionEvent":
|
|
269
|
-
this.manager.emit("trackError", player, track, payload.exception);
|
|
270
|
-
break;
|
|
271
|
-
|
|
272
|
-
case "TrackStuckEvent":
|
|
273
|
-
this.manager.emit("trackStuck", player, track, payload.thresholdMs);
|
|
274
|
-
p.skip().catch(() => {});
|
|
275
|
-
break;
|
|
276
|
-
|
|
277
|
-
case "WebSocketClosedEvent":
|
|
278
|
-
this.manager.emit("socketClosed", player, payload.code, payload.reason, payload.byRemote);
|
|
279
|
-
break;
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
public send(data: Record<string, any>): Promise<void> {
|
|
284
|
-
return this.wsQueue.send(data);
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
public enqueueRest<T>(fn: () => Promise<T>): Promise<T> {
|
|
288
|
-
const result = this.restQueue.then(() => fn());
|
|
289
|
-
this.restQueue = result.catch(() => {});
|
|
290
|
-
return result;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
public async loadTracks(identifier: string): Promise<SearchResult> {
|
|
294
|
-
return this.rest.loadTracks(identifier);
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
public playerPath(guildId: string): string {
|
|
298
|
-
return this.version === 4
|
|
299
|
-
? `/sessions/${this.sessionId}/players/${guildId}`
|
|
300
|
-
: `/players/${guildId}`;
|
|
301
|
-
}
|
|
302
|
-
}
|
package/src/Player.ts
DELETED
|
@@ -1,332 +0,0 @@
|
|
|
1
|
-
import { Queue } from "./Queue";
|
|
2
|
-
import { PlayerState } from "./Constants";
|
|
3
|
-
import { PlayerError } from "./errors/YukimuError";
|
|
4
|
-
import type { Yukimu } from "./Yukimu";
|
|
5
|
-
import type { Node } from "./Node";
|
|
6
|
-
import type { Track, PlayerOptions, FilterOptions } from "./types";
|
|
7
|
-
|
|
8
|
-
export class Player {
|
|
9
|
-
public readonly manager: Yukimu;
|
|
10
|
-
public node: Node;
|
|
11
|
-
public readonly queue: Queue;
|
|
12
|
-
|
|
13
|
-
public readonly guildId: string;
|
|
14
|
-
public voiceChannelId: string | null;
|
|
15
|
-
public textChannelId?: string;
|
|
16
|
-
|
|
17
|
-
public state: PlayerState = PlayerState.DISCONNECTED;
|
|
18
|
-
public playing: boolean = false;
|
|
19
|
-
public paused: boolean = false;
|
|
20
|
-
public position: number = 0;
|
|
21
|
-
public ping: number = -1;
|
|
22
|
-
public lastUpdated: number = Date.now();
|
|
23
|
-
public volume: number;
|
|
24
|
-
public loop: "none" | "track" | "queue" = "none";
|
|
25
|
-
public autoplay: boolean = false;
|
|
26
|
-
|
|
27
|
-
public sessionId: string | null = null;
|
|
28
|
-
public voiceToken: string | null = null;
|
|
29
|
-
public voiceEndpoint: string | null = null;
|
|
30
|
-
public selfDeaf: boolean;
|
|
31
|
-
public selfMute: boolean;
|
|
32
|
-
|
|
33
|
-
public filters: FilterOptions = {};
|
|
34
|
-
public readonly data: Map<string, any> = new Map();
|
|
35
|
-
|
|
36
|
-
constructor(manager: Yukimu, node: Node, options: PlayerOptions) {
|
|
37
|
-
this.manager = manager;
|
|
38
|
-
this.node = node;
|
|
39
|
-
this.queue = new Queue();
|
|
40
|
-
this.guildId = options.guildId;
|
|
41
|
-
this.voiceChannelId = options.voiceChannelId;
|
|
42
|
-
this.textChannelId = options.textChannelId;
|
|
43
|
-
this.volume = options.volume ?? 100;
|
|
44
|
-
this.selfDeaf = options.selfDeaf ?? true;
|
|
45
|
-
this.selfMute = options.selfMute ?? false;
|
|
46
|
-
this.sendVoicePayload(options.voiceChannelId, this.selfDeaf, this.selfMute);
|
|
47
|
-
this.state = PlayerState.CONNECTING;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
get connected(): boolean { return this.state === PlayerState.CONNECTED; }
|
|
51
|
-
|
|
52
|
-
get realPosition(): number {
|
|
53
|
-
if (!this.playing || this.paused) return this.position;
|
|
54
|
-
return Math.min(this.position + (Date.now() - this.lastUpdated), this.queue.current?.info?.length ?? this.position);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
public sendVoicePayload(channelId: string | null, selfDeaf: boolean, selfMute: boolean): void {
|
|
58
|
-
this.manager.connector.sendPayload(this.guildId, {
|
|
59
|
-
op: 4,
|
|
60
|
-
d: { guild_id: this.guildId, channel_id: channelId, self_deaf: selfDeaf, self_mute: selfMute },
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
public checkVoiceReady(): void {
|
|
65
|
-
if (!this.sessionId || !this.voiceToken || !this.voiceEndpoint) return;
|
|
66
|
-
|
|
67
|
-
const voiceState: any = {
|
|
68
|
-
token: this.voiceToken,
|
|
69
|
-
endpoint: this.voiceEndpoint,
|
|
70
|
-
sessionId: this.sessionId,
|
|
71
|
-
channelId: this.voiceChannelId,
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
if (this.node.version === 4) {
|
|
75
|
-
this.node.rest.updatePlayer(this.guildId, { voice: voiceState }).catch(console.error);
|
|
76
|
-
} else {
|
|
77
|
-
this.node.send({
|
|
78
|
-
op: "voiceUpdate",
|
|
79
|
-
guildId: this.guildId,
|
|
80
|
-
sessionId: this.sessionId,
|
|
81
|
-
event: { token: this.voiceToken, guild_id: this.guildId, endpoint: this.voiceEndpoint },
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
this.state = PlayerState.CONNECTED;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
public async play(track?: Track, options?: { startTime?: number; endTime?: number; noReplace?: boolean }): Promise<void> {
|
|
89
|
-
const toPlay = track ?? this.queue.current;
|
|
90
|
-
if (!toPlay) throw new PlayerError("No track to play");
|
|
91
|
-
this.queue.current = toPlay;
|
|
92
|
-
|
|
93
|
-
if (this.node.version === 4) {
|
|
94
|
-
await this.node.rest.updatePlayer(this.guildId, {
|
|
95
|
-
track: { encoded: toPlay.encoded },
|
|
96
|
-
volume: this.volume,
|
|
97
|
-
...(options?.startTime !== undefined && { position: options.startTime }),
|
|
98
|
-
...(options?.endTime !== undefined && { endTime: options.endTime }),
|
|
99
|
-
}, options?.noReplace ?? false);
|
|
100
|
-
} else {
|
|
101
|
-
this.node.send({
|
|
102
|
-
op: "play",
|
|
103
|
-
guildId: this.guildId,
|
|
104
|
-
track: toPlay.encoded,
|
|
105
|
-
volume: String(this.volume),
|
|
106
|
-
...(options?.startTime !== undefined && { startTime: String(options.startTime) }),
|
|
107
|
-
...(options?.endTime !== undefined && { endTime: String(options.endTime) }),
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
this.playing = true;
|
|
112
|
-
this.paused = false;
|
|
113
|
-
this.lastUpdated = Date.now();
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
public async pause(state: boolean = true): Promise<void> {
|
|
117
|
-
if (this.node.version === 4) {
|
|
118
|
-
await this.node.rest.updatePlayer(this.guildId, { paused: state });
|
|
119
|
-
} else {
|
|
120
|
-
this.node.send({ op: "pause", guildId: this.guildId, pause: state });
|
|
121
|
-
}
|
|
122
|
-
this.paused = state;
|
|
123
|
-
this.playing = !state;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
public async resume(): Promise<void> { return this.pause(false); }
|
|
127
|
-
|
|
128
|
-
public async stop(): Promise<void> {
|
|
129
|
-
if (this.node.version === 4) {
|
|
130
|
-
await this.node.rest.updatePlayer(this.guildId, { track: { encoded: null } });
|
|
131
|
-
} else {
|
|
132
|
-
this.node.send({ op: "stop", guildId: this.guildId });
|
|
133
|
-
}
|
|
134
|
-
this.playing = false;
|
|
135
|
-
this.paused = false;
|
|
136
|
-
this.position = 0;
|
|
137
|
-
this.queue.current = null;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
public async skip(): Promise<Track | null> {
|
|
141
|
-
const next = this.queue.next();
|
|
142
|
-
if (next) {
|
|
143
|
-
await this.play(next);
|
|
144
|
-
} else {
|
|
145
|
-
await this.stop();
|
|
146
|
-
this.manager.emit("queueEnd", this);
|
|
147
|
-
}
|
|
148
|
-
return next;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
public async seek(position: number): Promise<void> {
|
|
152
|
-
if (!this.queue.current?.info?.isSeekable) throw new PlayerError("Current track is not seekable");
|
|
153
|
-
if (this.node.version === 4) {
|
|
154
|
-
await this.node.rest.updatePlayer(this.guildId, { position });
|
|
155
|
-
} else {
|
|
156
|
-
this.node.send({ op: "seek", guildId: this.guildId, position });
|
|
157
|
-
}
|
|
158
|
-
this.position = position;
|
|
159
|
-
this.lastUpdated = Date.now();
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
public async setVolume(volume: number): Promise<void> {
|
|
163
|
-
if (volume < 0 || volume > 1000) throw new PlayerError("Volume must be between 0 and 1000");
|
|
164
|
-
if (this.node.version === 4) {
|
|
165
|
-
await this.node.rest.updatePlayer(this.guildId, { volume });
|
|
166
|
-
} else {
|
|
167
|
-
this.node.send({ op: "volume", guildId: this.guildId, volume });
|
|
168
|
-
}
|
|
169
|
-
this.volume = volume;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
public async setFilters(filters: FilterOptions): Promise<void> {
|
|
173
|
-
this.filters = { ...this.filters, ...filters };
|
|
174
|
-
if (this.node.version === 4) {
|
|
175
|
-
await this.node.rest.updatePlayer(this.guildId, { filters: this.filters });
|
|
176
|
-
} else {
|
|
177
|
-
if (filters.equalizer) this.node.send({ op: "equalizer", guildId: this.guildId, bands: filters.equalizer });
|
|
178
|
-
const rest = { ...filters };
|
|
179
|
-
delete rest.equalizer;
|
|
180
|
-
if (Object.keys(rest).length > 0) this.node.send({ op: "filters", guildId: this.guildId, ...rest });
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
public async clearFilters(): Promise<void> {
|
|
185
|
-
this.filters = {};
|
|
186
|
-
if (this.node.version === 4) {
|
|
187
|
-
await this.node.rest.updatePlayer(this.guildId, { filters: {} });
|
|
188
|
-
} else {
|
|
189
|
-
this.node.send({ op: "filters", guildId: this.guildId });
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
public async setBassBoost(level: "off" | "low" | "medium" | "high" | "extreme"): Promise<void> {
|
|
194
|
-
const presets: Record<string, number[]> = {
|
|
195
|
-
off: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
|
196
|
-
low: [0.2,0.15,0.1,0.05,0,-0.05,0,0,0,0,0,0,0,0,0],
|
|
197
|
-
medium: [0.4,0.3,0.2,0.1,0.05,-0.05,0,0,0,0,0,0,0,0,0],
|
|
198
|
-
high: [0.6,0.5,0.4,0.25,0.1,-0.1,0,0,0,0,0,0,0,0,0],
|
|
199
|
-
extreme: [1.0,0.8,0.6,0.4,0.2,-0.2,0,0,0,0,0,0,0,0,0],
|
|
200
|
-
};
|
|
201
|
-
await this.setFilters({ equalizer: presets[level].map((gain, band) => ({ band, gain })) });
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
public async setNightcore(enabled: boolean): Promise<void> {
|
|
205
|
-
await this.setFilters({ timescale: enabled ? { speed: 1.2, pitch: 1.2, rate: 1.0 } : {} });
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
public async setVaporwave(enabled: boolean): Promise<void> {
|
|
209
|
-
await this.setFilters({ timescale: enabled ? { speed: 0.8, pitch: 0.8, rate: 1.0 } : {} });
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
public async set8D(enabled: boolean): Promise<void> {
|
|
213
|
-
await this.setFilters({ rotation: enabled ? { rotationHz: 0.2 } : {} });
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
public async setKaraoke(enabled: boolean): Promise<void> {
|
|
217
|
-
await this.setFilters({ karaoke: enabled ? { level: 1.0, monoLevel: 1.0, filterBand: 220.0, filterWidth: 100.0 } : {} });
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
public async setTremolo(enabled: boolean, frequency = 2.0, depth = 0.5): Promise<void> {
|
|
221
|
-
await this.setFilters({ tremolo: enabled ? { frequency, depth } : {} });
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
public async setVibrato(enabled: boolean, frequency = 2.0, depth = 0.5): Promise<void> {
|
|
225
|
-
await this.setFilters({ vibrato: enabled ? { frequency, depth } : {} });
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
public async setLowPass(enabled: boolean, smoothing = 20.0): Promise<void> {
|
|
229
|
-
await this.setFilters({ lowPass: enabled ? { smoothing } : {} });
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
public async add(tracks: Track | Track[], requester?: any): Promise<void> {
|
|
233
|
-
const arr = Array.isArray(tracks) ? tracks : [tracks];
|
|
234
|
-
if (requester) arr.forEach(t => { (t as any).requester = requester; });
|
|
235
|
-
if (!this.queue.current && arr.length > 0) {
|
|
236
|
-
const first = arr.shift()!;
|
|
237
|
-
this.queue.add(arr);
|
|
238
|
-
await this.play(first);
|
|
239
|
-
} else {
|
|
240
|
-
this.queue.add(arr);
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
public setLoop(mode: "none" | "track" | "queue"): void { this.loop = mode; }
|
|
245
|
-
public setAutoplay(enabled: boolean): void { this.autoplay = enabled; }
|
|
246
|
-
|
|
247
|
-
public async moveToNode(node: Node): Promise<void> {
|
|
248
|
-
if (this.node === node) return;
|
|
249
|
-
const oldNode = this.node;
|
|
250
|
-
this.node = node;
|
|
251
|
-
await oldNode.rest.destroyPlayer(this.guildId).catch(() => {});
|
|
252
|
-
if (this.queue.current) {
|
|
253
|
-
await this.play(this.queue.current, { startTime: this.realPosition });
|
|
254
|
-
}
|
|
255
|
-
if (Object.keys(this.filters).length > 0) await this.setFilters(this.filters).catch(() => {});
|
|
256
|
-
if (this.volume !== 100) await this.setVolume(this.volume).catch(() => {});
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
public async move(channelId: string): Promise<void> {
|
|
260
|
-
this.voiceChannelId = channelId;
|
|
261
|
-
this.sendVoicePayload(channelId, this.selfDeaf, this.selfMute);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
public async destroy(): Promise<void> {
|
|
265
|
-
this.state = PlayerState.DISCONNECTING;
|
|
266
|
-
this.sendVoicePayload(null, false, false);
|
|
267
|
-
await this.node.rest.destroyPlayer(this.guildId).catch(() => {});
|
|
268
|
-
this.playing = false;
|
|
269
|
-
this.paused = false;
|
|
270
|
-
this.queue.clear();
|
|
271
|
-
this.data.clear();
|
|
272
|
-
this.state = PlayerState.DISCONNECTED;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
// ─── Audio Quality Presets ────────────────────────────────────────
|
|
276
|
-
|
|
277
|
-
/** Maximum audio quality — crystal clear, enhanced bass & treble */
|
|
278
|
-
public async setHighQuality(): Promise<void> {
|
|
279
|
-
await this.setFilters({
|
|
280
|
-
equalizer: [
|
|
281
|
-
{ band: 0, gain: 0.25 }, // Sub bass boost
|
|
282
|
-
{ band: 1, gain: 0.20 }, // Bass boost
|
|
283
|
-
{ band: 2, gain: 0.15 }, // Low mid bass
|
|
284
|
-
{ band: 3, gain: 0.05 }, // Mid bass
|
|
285
|
-
{ band: 4, gain: 0.0 }, // Low mid flat
|
|
286
|
-
{ band: 5, gain: 0.0 }, // Mid flat
|
|
287
|
-
{ band: 6, gain: 0.0 }, // Upper mid flat
|
|
288
|
-
{ band: 7, gain: 0.05 }, // Presence slight boost
|
|
289
|
-
{ band: 8, gain: 0.10 }, // Upper presence
|
|
290
|
-
{ band: 9, gain: 0.15 }, // Treble boost
|
|
291
|
-
{ band: 10, gain: 0.15 }, // Upper treble
|
|
292
|
-
{ band: 11, gain: 0.10 }, // Air
|
|
293
|
-
{ band: 12, gain: 0.05 }, // Brilliance
|
|
294
|
-
{ band: 13, gain: 0.0 }, // Ultra high
|
|
295
|
-
{ band: 14, gain: 0.0 }, // Ultra high
|
|
296
|
-
],
|
|
297
|
-
lowPass: { smoothing: 5.0 }, // Very light low pass for clarity
|
|
298
|
-
});
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
/** Studio quality — flat, neutral, most accurate */
|
|
302
|
-
public async setStudioQuality(): Promise<void> {
|
|
303
|
-
await this.clearFilters();
|
|
304
|
-
await this.setFilters({
|
|
305
|
-
equalizer: Array.from({ length: 15 }, (_, i) => ({ band: i, gain: 0 })),
|
|
306
|
-
});
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
/** Concert hall effect */
|
|
310
|
-
public async setConcertHall(): Promise<void> {
|
|
311
|
-
await this.setFilters({
|
|
312
|
-
equalizer: [
|
|
313
|
-
{ band: 0, gain: 0.1 },
|
|
314
|
-
{ band: 1, gain: 0.1 },
|
|
315
|
-
{ band: 2, gain: 0.05 },
|
|
316
|
-
{ band: 3, gain: 0.0 },
|
|
317
|
-
{ band: 4, gain: -0.05 },
|
|
318
|
-
{ band: 5, gain: 0.0 },
|
|
319
|
-
{ band: 6, gain: 0.05 },
|
|
320
|
-
{ band: 7, gain: 0.1 },
|
|
321
|
-
{ band: 8, gain: 0.1 },
|
|
322
|
-
{ band: 9, gain: 0.05 },
|
|
323
|
-
{ band: 10, gain: 0.0 },
|
|
324
|
-
{ band: 11, gain: 0.0 },
|
|
325
|
-
{ band: 12, gain: 0.0 },
|
|
326
|
-
{ band: 13, gain: 0.0 },
|
|
327
|
-
{ band: 14, gain: 0.0 },
|
|
328
|
-
],
|
|
329
|
-
rotation: { rotationHz: 0.05 },
|
|
330
|
-
});
|
|
331
|
-
}
|
|
332
|
-
}
|