yukimu 1.2.0 → 1.3.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/.cache/replit/env/latest +72 -72
- package/.cache/replit/env/latest.json +1 -1
- package/.cache/replit/toolchain.json +1 -1
- package/dist/ConnectionPool.d.ts +1 -0
- package/dist/ConnectionPool.d.ts.map +1 -1
- package/dist/ConnectionPool.js +11 -24
- package/dist/ConnectionPool.js.map +1 -1
- package/dist/Constants.d.ts +3 -49
- package/dist/Constants.d.ts.map +1 -1
- package/dist/Constants.js +6 -61
- package/dist/Constants.js.map +1 -1
- package/dist/Node.d.ts +1 -1
- package/dist/Node.d.ts.map +1 -1
- package/dist/Node.js +17 -17
- package/dist/Node.js.map +1 -1
- package/dist/Player.d.ts +9 -5
- package/dist/Player.d.ts.map +1 -1
- package/dist/Player.js +73 -54
- package/dist/Player.js.map +1 -1
- package/dist/Plugin.d.ts +0 -14
- package/dist/Plugin.d.ts.map +1 -1
- package/dist/Plugin.js +0 -12
- package/dist/Plugin.js.map +1 -1
- package/dist/Queue.d.ts +0 -1
- package/dist/Queue.d.ts.map +1 -1
- package/dist/Queue.js +7 -22
- package/dist/Queue.js.map +1 -1
- package/dist/Resolver.d.ts +1 -1
- package/dist/Resolver.d.ts.map +1 -1
- package/dist/Resolver.js +24 -10
- package/dist/Resolver.js.map +1 -1
- package/dist/Rest.d.ts +3 -4
- package/dist/Rest.d.ts.map +1 -1
- package/dist/Rest.js +26 -16
- package/dist/Rest.js.map +1 -1
- package/dist/TrackCache.d.ts +0 -5
- package/dist/TrackCache.d.ts.map +1 -1
- package/dist/TrackCache.js +6 -23
- package/dist/TrackCache.js.map +1 -1
- package/dist/WsQueue.d.ts +2 -11
- package/dist/WsQueue.d.ts.map +1 -1
- package/dist/WsQueue.js +5 -21
- package/dist/WsQueue.js.map +1 -1
- package/dist/Yukimu.d.ts +3 -12
- package/dist/Yukimu.d.ts.map +1 -1
- package/dist/Yukimu.js +2 -8
- package/dist/Yukimu.js.map +1 -1
- package/dist/connector/DiscordJS.d.ts +1 -18
- package/dist/connector/DiscordJS.d.ts.map +1 -1
- package/dist/connector/DiscordJS.js.map +1 -1
- package/dist/connector/Eris.d.ts +1 -16
- package/dist/connector/Eris.d.ts.map +1 -1
- package/dist/connector/Eris.js +2 -4
- package/dist/connector/Eris.js.map +1 -1
- package/dist/connector/Oceanic.d.ts +1 -13
- package/dist/connector/Oceanic.d.ts.map +1 -1
- package/dist/connector/Oceanic.js +2 -4
- package/dist/connector/Oceanic.js.map +1 -1
- package/dist/errors/YukimuError.d.ts.map +1 -1
- package/dist/errors/YukimuError.js +0 -1
- package/dist/errors/YukimuError.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -5
- package/dist/index.js.map +1 -1
- package/dist/plugins/AutoResume.d.ts +0 -13
- package/dist/plugins/AutoResume.d.ts.map +1 -1
- package/dist/plugins/AutoResume.js +10 -18
- package/dist/plugins/AutoResume.js.map +1 -1
- package/dist/plugins/PlayerMoved.js.map +1 -1
- package/dist/types.d.ts +25 -34
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/ConnectionPool.ts +14 -31
- package/src/Constants.ts +6 -62
- package/src/Node.ts +44 -51
- package/src/Player.ts +86 -91
- package/src/Plugin.ts +0 -16
- package/src/Queue.ts +7 -32
- package/src/Resolver.ts +25 -14
- package/src/Rest.ts +34 -28
- package/src/TrackCache.ts +7 -30
- package/src/WsQueue.ts +9 -29
- package/src/Yukimu.ts +10 -45
- package/src/connector/DiscordJS.ts +5 -12
- package/src/connector/Eris.ts +6 -17
- package/src/connector/Oceanic.ts +5 -15
- package/src/errors/YukimuError.ts +0 -2
- package/src/index.ts +4 -26
- package/src/plugins/AutoResume.ts +13 -37
- package/src/plugins/PlayerMoved.ts +4 -4
- package/src/types.ts +26 -26
- package/tsconfig.json +3 -1
package/src/Constants.ts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export const VERSION = "2.0.0";
|
|
1
|
+
export const VERSION = "1.3.0";
|
|
4
2
|
export const CLIENT_NAME = `Yukimu/${VERSION}`;
|
|
5
3
|
|
|
6
4
|
export enum State {
|
|
@@ -19,63 +17,6 @@ export enum PlayerState {
|
|
|
19
17
|
DISCONNECTED = 3,
|
|
20
18
|
}
|
|
21
19
|
|
|
22
|
-
export enum OpCodes {
|
|
23
|
-
// v3 WebSocket ops
|
|
24
|
-
PLAY = "play",
|
|
25
|
-
STOP = "stop",
|
|
26
|
-
PAUSE = "pause",
|
|
27
|
-
SEEK = "seek",
|
|
28
|
-
VOLUME = "volume",
|
|
29
|
-
FILTERS = "filters",
|
|
30
|
-
DESTROY = "destroy",
|
|
31
|
-
VOICE_UPDATE = "voiceUpdate",
|
|
32
|
-
EQUALIZER = "equalizer",
|
|
33
|
-
// Incoming
|
|
34
|
-
STATS = "stats",
|
|
35
|
-
PLAYER_UPDATE = "playerUpdate",
|
|
36
|
-
EVENT = "event",
|
|
37
|
-
READY = "ready",
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export enum LoadType {
|
|
41
|
-
TRACK = "track",
|
|
42
|
-
PLAYLIST = "playlist",
|
|
43
|
-
SEARCH = "search",
|
|
44
|
-
EMPTY = "empty",
|
|
45
|
-
ERROR = "error",
|
|
46
|
-
// v3 equivalents (normalized internally)
|
|
47
|
-
TRACK_LOADED = "track_loaded",
|
|
48
|
-
PLAYLIST_LOADED = "playlist_loaded",
|
|
49
|
-
SEARCH_RESULT = "search_result",
|
|
50
|
-
NO_MATCHES = "no_matches",
|
|
51
|
-
LOAD_FAILED = "load_failed",
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export enum EventType {
|
|
55
|
-
TRACK_START = "TrackStartEvent",
|
|
56
|
-
TRACK_END = "TrackEndEvent",
|
|
57
|
-
TRACK_EXCEPTION = "TrackExceptionEvent",
|
|
58
|
-
TRACK_STUCK = "TrackStuckEvent",
|
|
59
|
-
WS_CLOSED = "WebSocketClosedEvent",
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export enum TrackEndReason {
|
|
63
|
-
FINISHED = "finished",
|
|
64
|
-
LOAD_FAILED = "loadFailed",
|
|
65
|
-
STOPPED = "stopped",
|
|
66
|
-
REPLACED = "replaced",
|
|
67
|
-
CLEANUP = "cleanup",
|
|
68
|
-
// v3
|
|
69
|
-
FINISHED_V3 = "FINISHED",
|
|
70
|
-
STOPPED_V3 = "STOPPED",
|
|
71
|
-
REPLACED_V3 = "REPLACED",
|
|
72
|
-
CLEANUP_V3 = "CLEANUP",
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export const STOPPED_REASONS = new Set([
|
|
76
|
-
"replaced", "stopped", "REPLACED", "STOPPED",
|
|
77
|
-
]);
|
|
78
|
-
|
|
79
20
|
export const SOURCE_PREFIXES: Record<string, string> = {
|
|
80
21
|
youtube: "ytsearch",
|
|
81
22
|
youtubemusic: "ytmsearch",
|
|
@@ -86,7 +27,6 @@ export const SOURCE_PREFIXES: Record<string, string> = {
|
|
|
86
27
|
tidal: "tdsearch",
|
|
87
28
|
jiosaavn: "jssearch",
|
|
88
29
|
yandexmusic: "ymsearch",
|
|
89
|
-
flowery: "ftts",
|
|
90
30
|
};
|
|
91
31
|
|
|
92
32
|
export const URL_PATTERNS: Record<string, RegExp[]> = {
|
|
@@ -95,7 +35,11 @@ export const URL_PATTERNS: Record<string, RegExp[]> = {
|
|
|
95
35
|
soundcloud: [/^https?:\/\/(www\.)?soundcloud\.com\/.+/],
|
|
96
36
|
deezer: [/^https?:\/\/(www\.)?deezer\.com\/(track|album|playlist)\/.+/],
|
|
97
37
|
applemusic: [/^https?:\/\/music\.apple\.com\/.+/],
|
|
98
|
-
tidal: [/^https?:\/\/(www\.)?tidal\.com
|
|
38
|
+
tidal: [/^https?:\/\/(www\.)?tidal\.com\/.+/],
|
|
99
39
|
jiosaavn: [/^https?:\/\/(www\.)?jiosaavn\.com\/.+/],
|
|
100
40
|
yandexmusic: [/^https?:\/\/music\.yandex\.(ru|com)\/.+/],
|
|
101
41
|
};
|
|
42
|
+
|
|
43
|
+
export const STOPPED_REASONS = new Set([
|
|
44
|
+
"replaced", "stopped", "REPLACED", "STOPPED",
|
|
45
|
+
]);
|
package/src/Node.ts
CHANGED
|
@@ -25,7 +25,7 @@ export class Node {
|
|
|
25
25
|
public penalties: number = 0;
|
|
26
26
|
public ping: number = -1;
|
|
27
27
|
|
|
28
|
-
private restQueue: Promise<
|
|
28
|
+
private restQueue: Promise<any> = Promise.resolve();
|
|
29
29
|
private reconnectAttempts = 0;
|
|
30
30
|
private reconnectTimeout?: ReturnType<typeof setTimeout>;
|
|
31
31
|
private heartbeatInterval?: ReturnType<typeof setInterval>;
|
|
@@ -53,7 +53,7 @@ export class Node {
|
|
|
53
53
|
const h: Record<string, string> = {
|
|
54
54
|
Authorization: this.options.password,
|
|
55
55
|
"User-Id": this.manager.options.clientId,
|
|
56
|
-
"Client-Name": "Yukimu/1.
|
|
56
|
+
"Client-Name": "Yukimu/1.3.0",
|
|
57
57
|
};
|
|
58
58
|
if (this.version === 3) h["Num-Shards"] = "1";
|
|
59
59
|
if (this.options.resumeKey) h["Resume-Key"] = this.options.resumeKey;
|
|
@@ -77,8 +77,8 @@ export class Node {
|
|
|
77
77
|
this.ws = new WebSocket(this.wsUrl, { headers: this.wsHeaders });
|
|
78
78
|
this.wsQueue.setSocket(this.ws);
|
|
79
79
|
this.ws.on("open", () => this.onOpen());
|
|
80
|
-
this.ws.on("message", (d:
|
|
81
|
-
this.ws.on("close", (code: number, reason:
|
|
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
82
|
this.ws.on("error", (err: Error) => this.onError(err));
|
|
83
83
|
}
|
|
84
84
|
|
|
@@ -115,8 +115,8 @@ export class Node {
|
|
|
115
115
|
this.startHeartbeat();
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
private onMessage(raw:
|
|
119
|
-
let payload:
|
|
118
|
+
private onMessage(raw: any): void {
|
|
119
|
+
let payload: any;
|
|
120
120
|
try { payload = JSON.parse(raw.toString()); } catch { return; }
|
|
121
121
|
const op = payload.op as string;
|
|
122
122
|
|
|
@@ -124,14 +124,14 @@ export class Node {
|
|
|
124
124
|
switch (op) {
|
|
125
125
|
case "ready":
|
|
126
126
|
this.state = State.CONNECTED;
|
|
127
|
-
this.sessionId = payload.sessionId
|
|
128
|
-
this.resumed =
|
|
127
|
+
this.sessionId = payload.sessionId;
|
|
128
|
+
this.resumed = payload.resumed ?? false;
|
|
129
129
|
this.manager.emit("nodeReady", this);
|
|
130
130
|
console.log(`[Yukimu] Node "${this.options.name}" ready | session: ${this.sessionId} | resumed: ${this.resumed}`);
|
|
131
131
|
if (this.resumed) this.reconnectPlayers();
|
|
132
132
|
break;
|
|
133
133
|
case "stats":
|
|
134
|
-
this.stats = payload
|
|
134
|
+
this.stats = payload;
|
|
135
135
|
this.calculatePenalties();
|
|
136
136
|
break;
|
|
137
137
|
case "playerUpdate":
|
|
@@ -145,16 +145,9 @@ export class Node {
|
|
|
145
145
|
}
|
|
146
146
|
|
|
147
147
|
switch (op) {
|
|
148
|
-
case "stats":
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
break;
|
|
152
|
-
case "playerUpdate":
|
|
153
|
-
this.handlePlayerUpdate(payload);
|
|
154
|
-
break;
|
|
155
|
-
case "event":
|
|
156
|
-
this.handleEvent(payload);
|
|
157
|
-
break;
|
|
148
|
+
case "stats": this.stats = payload; this.calculatePenalties(); break;
|
|
149
|
+
case "playerUpdate": this.handlePlayerUpdate(payload); break;
|
|
150
|
+
case "event": this.handleEvent(payload); break;
|
|
158
151
|
}
|
|
159
152
|
}
|
|
160
153
|
|
|
@@ -200,70 +193,70 @@ export class Node {
|
|
|
200
193
|
}
|
|
201
194
|
|
|
202
195
|
private movePlayers(): void {
|
|
203
|
-
const players = [...this.manager.players.values()].filter(p => p.node === this);
|
|
196
|
+
const players = [...this.manager.players.values()].filter((p: any) => p.node === this);
|
|
204
197
|
if (!players.length) return;
|
|
205
198
|
let best: Node;
|
|
206
199
|
try { best = this.manager.getBestNode(); } catch { return; }
|
|
207
200
|
if (best === this) return;
|
|
208
201
|
console.log(`[Yukimu] Moving ${players.length} players to "${best.options.name}"`);
|
|
209
|
-
for (const player of players) player.moveToNode(best).catch(console.error);
|
|
202
|
+
for (const player of players) (player as any).moveToNode(best).catch(console.error);
|
|
210
203
|
}
|
|
211
204
|
|
|
212
205
|
private reconnectPlayers(): void {
|
|
213
206
|
for (const player of this.manager.players.values()) {
|
|
214
|
-
if (player.node === this) player.checkVoiceReady();
|
|
207
|
+
if ((player as any).node === this) (player as any).checkVoiceReady();
|
|
215
208
|
}
|
|
216
209
|
}
|
|
217
210
|
|
|
218
|
-
private handlePlayerUpdate(payload:
|
|
219
|
-
const player = this.manager.players.get(payload.guildId
|
|
211
|
+
private handlePlayerUpdate(payload: any): void {
|
|
212
|
+
const player = this.manager.players.get(payload.guildId);
|
|
220
213
|
if (!player) return;
|
|
221
|
-
const state = payload.state
|
|
222
|
-
player.position = state.position ?? player.position;
|
|
223
|
-
player.ping = state.ping ?? player.ping;
|
|
224
|
-
player.lastUpdated = state.time ?? Date.now();
|
|
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();
|
|
225
218
|
this.manager.emit("playerUpdate", player);
|
|
226
219
|
}
|
|
227
220
|
|
|
228
|
-
private handleEvent(payload:
|
|
229
|
-
const player = this.manager.players.get(payload.guildId
|
|
221
|
+
private handleEvent(payload: any): void {
|
|
222
|
+
const player = this.manager.players.get(payload.guildId);
|
|
230
223
|
if (!player) return;
|
|
231
224
|
|
|
232
225
|
let track: Track;
|
|
233
226
|
if (this.version === 3) {
|
|
234
|
-
track = { encoded: payload.track
|
|
227
|
+
track = { encoded: payload.track, info: {} as any, pluginInfo: {} };
|
|
235
228
|
} else {
|
|
236
|
-
track = payload.track
|
|
229
|
+
track = payload.track;
|
|
237
230
|
if (track?.encoded) this.manager.trackCache.set(track.encoded, track);
|
|
238
231
|
}
|
|
239
232
|
|
|
233
|
+
const p = player as any;
|
|
234
|
+
|
|
240
235
|
switch (payload.type) {
|
|
241
236
|
case "TrackStartEvent":
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
237
|
+
p.playing = true;
|
|
238
|
+
p.paused = false;
|
|
239
|
+
p.lastUpdated = Date.now();
|
|
245
240
|
this.manager.emit("trackStart", player, track);
|
|
246
241
|
break;
|
|
247
242
|
|
|
248
243
|
case "TrackEndEvent": {
|
|
249
|
-
const reason = payload.reason
|
|
250
|
-
|
|
251
|
-
|
|
244
|
+
const reason = payload.reason;
|
|
245
|
+
p.playing = false;
|
|
246
|
+
p.position = 0;
|
|
252
247
|
this.manager.emit("trackEnd", player, track, reason);
|
|
253
248
|
const stopped = ["replaced", "stopped", "REPLACED", "STOPPED"].includes(reason);
|
|
254
249
|
if (!stopped) {
|
|
255
|
-
if (
|
|
256
|
-
|
|
250
|
+
if (p.loop === "track" && p.queue.current) {
|
|
251
|
+
p.play(p.queue.current).catch(() => {});
|
|
257
252
|
} else {
|
|
258
|
-
if (
|
|
259
|
-
|
|
260
|
-
}
|
|
261
|
-
const next = player.queue.next();
|
|
253
|
+
if (p.loop === "queue" && p.queue.current) p.queue.add(p.queue.current);
|
|
254
|
+
const next = p.queue.next();
|
|
262
255
|
if (next) {
|
|
263
|
-
|
|
256
|
+
p.play(next).catch(() => {});
|
|
264
257
|
} else {
|
|
265
258
|
this.manager.emit("queueEnd", player);
|
|
266
|
-
if (
|
|
259
|
+
if (p.autoplay && track.info?.uri) {
|
|
267
260
|
this.manager.emit("autoplayRequest", player, track);
|
|
268
261
|
}
|
|
269
262
|
}
|
|
@@ -273,21 +266,21 @@ export class Node {
|
|
|
273
266
|
}
|
|
274
267
|
|
|
275
268
|
case "TrackExceptionEvent":
|
|
276
|
-
this.manager.emit("trackError", player, track, payload.exception
|
|
269
|
+
this.manager.emit("trackError", player, track, payload.exception);
|
|
277
270
|
break;
|
|
278
271
|
|
|
279
272
|
case "TrackStuckEvent":
|
|
280
|
-
this.manager.emit("trackStuck", player, track, payload.thresholdMs
|
|
281
|
-
|
|
273
|
+
this.manager.emit("trackStuck", player, track, payload.thresholdMs);
|
|
274
|
+
p.skip().catch(() => {});
|
|
282
275
|
break;
|
|
283
276
|
|
|
284
277
|
case "WebSocketClosedEvent":
|
|
285
|
-
this.manager.emit("socketClosed", player, payload.code
|
|
278
|
+
this.manager.emit("socketClosed", player, payload.code, payload.reason, payload.byRemote);
|
|
286
279
|
break;
|
|
287
280
|
}
|
|
288
281
|
}
|
|
289
282
|
|
|
290
|
-
public send(data: Record<string,
|
|
283
|
+
public send(data: Record<string, any>): Promise<void> {
|
|
291
284
|
return this.wsQueue.send(data);
|
|
292
285
|
}
|
|
293
286
|
|
package/src/Player.ts
CHANGED
|
@@ -3,19 +3,17 @@ import { PlayerState } from "./Constants";
|
|
|
3
3
|
import { PlayerError } from "./errors/YukimuError";
|
|
4
4
|
import type { Yukimu } from "./Yukimu";
|
|
5
5
|
import type { Node } from "./Node";
|
|
6
|
-
import type { Track, PlayerOptions,
|
|
6
|
+
import type { Track, PlayerOptions, FilterOptions } from "./types";
|
|
7
7
|
|
|
8
8
|
export class Player {
|
|
9
9
|
public readonly manager: Yukimu;
|
|
10
10
|
public node: Node;
|
|
11
11
|
public readonly queue: Queue;
|
|
12
12
|
|
|
13
|
-
// IDs
|
|
14
13
|
public readonly guildId: string;
|
|
15
14
|
public voiceChannelId: string | null;
|
|
16
15
|
public textChannelId?: string;
|
|
17
16
|
|
|
18
|
-
// State
|
|
19
17
|
public state: PlayerState = PlayerState.DISCONNECTED;
|
|
20
18
|
public playing: boolean = false;
|
|
21
19
|
public paused: boolean = false;
|
|
@@ -24,53 +22,38 @@ export class Player {
|
|
|
24
22
|
public lastUpdated: number = Date.now();
|
|
25
23
|
public volume: number;
|
|
26
24
|
public loop: "none" | "track" | "queue" = "none";
|
|
25
|
+
public autoplay: boolean = false;
|
|
27
26
|
|
|
28
|
-
// Voice
|
|
29
27
|
public sessionId: string | null = null;
|
|
30
28
|
public voiceToken: string | null = null;
|
|
31
29
|
public voiceEndpoint: string | null = null;
|
|
32
30
|
public selfDeaf: boolean;
|
|
33
31
|
public selfMute: boolean;
|
|
34
32
|
|
|
35
|
-
// Filters
|
|
36
33
|
public filters: FilterOptions = {};
|
|
37
|
-
|
|
38
|
-
// Data store (like Kazagumo's player.data)
|
|
39
|
-
public readonly data: Map<string, unknown> = new Map();
|
|
40
|
-
|
|
41
|
-
// Autoplay
|
|
42
|
-
public autoplay: boolean = false;
|
|
34
|
+
public readonly data: Map<string, any> = new Map();
|
|
43
35
|
|
|
44
36
|
constructor(manager: Yukimu, node: Node, options: PlayerOptions) {
|
|
45
37
|
this.manager = manager;
|
|
46
38
|
this.node = node;
|
|
47
39
|
this.queue = new Queue();
|
|
48
|
-
|
|
49
40
|
this.guildId = options.guildId;
|
|
50
41
|
this.voiceChannelId = options.voiceChannelId;
|
|
51
42
|
this.textChannelId = options.textChannelId;
|
|
52
43
|
this.volume = options.volume ?? 100;
|
|
53
44
|
this.selfDeaf = options.selfDeaf ?? true;
|
|
54
45
|
this.selfMute = options.selfMute ?? false;
|
|
55
|
-
|
|
56
46
|
this.sendVoicePayload(options.voiceChannelId, this.selfDeaf, this.selfMute);
|
|
57
47
|
this.state = PlayerState.CONNECTING;
|
|
58
48
|
}
|
|
59
49
|
|
|
60
|
-
|
|
50
|
+
get connected(): boolean { return this.state === PlayerState.CONNECTED; }
|
|
61
51
|
|
|
62
|
-
get connected(): boolean {
|
|
63
|
-
return this.state === PlayerState.CONNECTED;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/** Real-time position estimate */
|
|
67
52
|
get realPosition(): number {
|
|
68
53
|
if (!this.playing || this.paused) return this.position;
|
|
69
|
-
return Math.min(this.position + (Date.now() - this.lastUpdated), this.queue.current?.info
|
|
54
|
+
return Math.min(this.position + (Date.now() - this.lastUpdated), this.queue.current?.info?.length ?? this.position);
|
|
70
55
|
}
|
|
71
56
|
|
|
72
|
-
// ─── Voice ───────────────────────────────────────────────────────
|
|
73
|
-
|
|
74
57
|
public sendVoicePayload(channelId: string | null, selfDeaf: boolean, selfMute: boolean): void {
|
|
75
58
|
this.manager.connector.sendPayload(this.guildId, {
|
|
76
59
|
op: 4,
|
|
@@ -81,10 +64,11 @@ export class Player {
|
|
|
81
64
|
public checkVoiceReady(): void {
|
|
82
65
|
if (!this.sessionId || !this.voiceToken || !this.voiceEndpoint) return;
|
|
83
66
|
|
|
84
|
-
const voiceState:
|
|
67
|
+
const voiceState: any = {
|
|
85
68
|
token: this.voiceToken,
|
|
86
69
|
endpoint: this.voiceEndpoint,
|
|
87
70
|
sessionId: this.sessionId,
|
|
71
|
+
channelId: this.voiceChannelId,
|
|
88
72
|
};
|
|
89
73
|
|
|
90
74
|
if (this.node.version === 4) {
|
|
@@ -101,12 +85,9 @@ export class Player {
|
|
|
101
85
|
this.state = PlayerState.CONNECTED;
|
|
102
86
|
}
|
|
103
87
|
|
|
104
|
-
// ─── Playback ─────────────────────────────────────────────────────
|
|
105
|
-
|
|
106
88
|
public async play(track?: Track, options?: { startTime?: number; endTime?: number; noReplace?: boolean }): Promise<void> {
|
|
107
89
|
const toPlay = track ?? this.queue.current;
|
|
108
90
|
if (!toPlay) throw new PlayerError("No track to play");
|
|
109
|
-
|
|
110
91
|
this.queue.current = toPlay;
|
|
111
92
|
|
|
112
93
|
if (this.node.version === 4) {
|
|
@@ -124,7 +105,6 @@ export class Player {
|
|
|
124
105
|
volume: String(this.volume),
|
|
125
106
|
...(options?.startTime !== undefined && { startTime: String(options.startTime) }),
|
|
126
107
|
...(options?.endTime !== undefined && { endTime: String(options.endTime) }),
|
|
127
|
-
...(options?.noReplace !== undefined && { noReplace: options.noReplace }),
|
|
128
108
|
});
|
|
129
109
|
}
|
|
130
110
|
|
|
@@ -169,10 +149,7 @@ export class Player {
|
|
|
169
149
|
}
|
|
170
150
|
|
|
171
151
|
public async seek(position: number): Promise<void> {
|
|
172
|
-
if (!this.queue.current?.info
|
|
173
|
-
if (position < 0 || position > (this.queue.current.info.length ?? Infinity)) {
|
|
174
|
-
throw new PlayerError(`Seek position out of range: ${position}`);
|
|
175
|
-
}
|
|
152
|
+
if (!this.queue.current?.info?.isSeekable) throw new PlayerError("Current track is not seekable");
|
|
176
153
|
if (this.node.version === 4) {
|
|
177
154
|
await this.node.rest.updatePlayer(this.guildId, { position });
|
|
178
155
|
} else {
|
|
@@ -192,22 +169,15 @@ export class Player {
|
|
|
192
169
|
this.volume = volume;
|
|
193
170
|
}
|
|
194
171
|
|
|
195
|
-
// ─── Filters ──────────────────────────────────────────────────────
|
|
196
|
-
|
|
197
172
|
public async setFilters(filters: FilterOptions): Promise<void> {
|
|
198
173
|
this.filters = { ...this.filters, ...filters };
|
|
199
|
-
|
|
200
174
|
if (this.node.version === 4) {
|
|
201
175
|
await this.node.rest.updatePlayer(this.guildId, { filters: this.filters });
|
|
202
176
|
} else {
|
|
203
|
-
if (filters.equalizer) {
|
|
204
|
-
this.node.send({ op: "equalizer", guildId: this.guildId, bands: filters.equalizer });
|
|
205
|
-
}
|
|
177
|
+
if (filters.equalizer) this.node.send({ op: "equalizer", guildId: this.guildId, bands: filters.equalizer });
|
|
206
178
|
const rest = { ...filters };
|
|
207
179
|
delete rest.equalizer;
|
|
208
|
-
if (Object.keys(rest).length > 0) {
|
|
209
|
-
this.node.send({ op: "filters", guildId: this.guildId, ...rest });
|
|
210
|
-
}
|
|
180
|
+
if (Object.keys(rest).length > 0) this.node.send({ op: "filters", guildId: this.guildId, ...rest });
|
|
211
181
|
}
|
|
212
182
|
}
|
|
213
183
|
|
|
@@ -220,57 +190,48 @@ export class Player {
|
|
|
220
190
|
}
|
|
221
191
|
}
|
|
222
192
|
|
|
223
|
-
// ─── Preset Filters ───────────────────────────────────────────────
|
|
224
|
-
|
|
225
193
|
public async setBassBoost(level: "off" | "low" | "medium" | "high" | "extreme"): Promise<void> {
|
|
226
194
|
const presets: Record<string, number[]> = {
|
|
227
|
-
off: [0,
|
|
228
|
-
low: [0.2,
|
|
229
|
-
medium: [0.4,
|
|
230
|
-
high: [0.6,
|
|
231
|
-
extreme: [1.0,
|
|
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],
|
|
232
200
|
};
|
|
233
201
|
await this.setFilters({ equalizer: presets[level].map((gain, band) => ({ band, gain })) });
|
|
234
202
|
}
|
|
235
203
|
|
|
236
204
|
public async setNightcore(enabled: boolean): Promise<void> {
|
|
237
|
-
await this.setFilters({ timescale: enabled ? { speed: 1.2, pitch: 1.2, rate: 1.0 } :
|
|
205
|
+
await this.setFilters({ timescale: enabled ? { speed: 1.2, pitch: 1.2, rate: 1.0 } : {} });
|
|
238
206
|
}
|
|
239
207
|
|
|
240
208
|
public async setVaporwave(enabled: boolean): Promise<void> {
|
|
241
|
-
await this.setFilters({ timescale: enabled ? { speed: 0.8, pitch: 0.8, rate: 1.0 } :
|
|
209
|
+
await this.setFilters({ timescale: enabled ? { speed: 0.8, pitch: 0.8, rate: 1.0 } : {} });
|
|
242
210
|
}
|
|
243
211
|
|
|
244
212
|
public async set8D(enabled: boolean): Promise<void> {
|
|
245
|
-
await this.setFilters({ rotation: enabled ? { rotationHz: 0.2 } :
|
|
213
|
+
await this.setFilters({ rotation: enabled ? { rotationHz: 0.2 } : {} });
|
|
246
214
|
}
|
|
247
215
|
|
|
248
216
|
public async setKaraoke(enabled: boolean): Promise<void> {
|
|
249
|
-
await this.setFilters({
|
|
250
|
-
karaoke: enabled ? { level: 1.0, monoLevel: 1.0, filterBand: 220.0, filterWidth: 100.0 } : undefined,
|
|
251
|
-
});
|
|
217
|
+
await this.setFilters({ karaoke: enabled ? { level: 1.0, monoLevel: 1.0, filterBand: 220.0, filterWidth: 100.0 } : {} });
|
|
252
218
|
}
|
|
253
219
|
|
|
254
220
|
public async setTremolo(enabled: boolean, frequency = 2.0, depth = 0.5): Promise<void> {
|
|
255
|
-
await this.setFilters({ tremolo: enabled ? { frequency, depth } :
|
|
221
|
+
await this.setFilters({ tremolo: enabled ? { frequency, depth } : {} });
|
|
256
222
|
}
|
|
257
223
|
|
|
258
224
|
public async setVibrato(enabled: boolean, frequency = 2.0, depth = 0.5): Promise<void> {
|
|
259
|
-
await this.setFilters({ vibrato: enabled ? { frequency, depth } :
|
|
225
|
+
await this.setFilters({ vibrato: enabled ? { frequency, depth } : {} });
|
|
260
226
|
}
|
|
261
227
|
|
|
262
228
|
public async setLowPass(enabled: boolean, smoothing = 20.0): Promise<void> {
|
|
263
|
-
await this.setFilters({ lowPass: enabled ? { smoothing } :
|
|
229
|
+
await this.setFilters({ lowPass: enabled ? { smoothing } : {} });
|
|
264
230
|
}
|
|
265
231
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
public async add(tracks: Track | Track[], requester?: unknown): Promise<void> {
|
|
232
|
+
public async add(tracks: Track | Track[], requester?: any): Promise<void> {
|
|
269
233
|
const arr = Array.isArray(tracks) ? tracks : [tracks];
|
|
270
|
-
|
|
271
|
-
// Attach requester to each track
|
|
272
234
|
if (requester) arr.forEach(t => { (t as any).requester = requester; });
|
|
273
|
-
|
|
274
235
|
if (!this.queue.current && arr.length > 0) {
|
|
275
236
|
const first = arr.shift()!;
|
|
276
237
|
this.queue.add(arr);
|
|
@@ -280,58 +241,92 @@ export class Player {
|
|
|
280
241
|
}
|
|
281
242
|
}
|
|
282
243
|
|
|
283
|
-
public setLoop(mode: "none" | "track" | "queue"): void {
|
|
284
|
-
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
public setAutoplay(enabled: boolean): void {
|
|
288
|
-
this.autoplay = enabled;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// ─── Node Moving ──────────────────────────────────────────────────
|
|
244
|
+
public setLoop(mode: "none" | "track" | "queue"): void { this.loop = mode; }
|
|
245
|
+
public setAutoplay(enabled: boolean): void { this.autoplay = enabled; }
|
|
292
246
|
|
|
293
|
-
/** Move player to a different Lavalink node */
|
|
294
247
|
public async moveToNode(node: Node): Promise<void> {
|
|
295
248
|
if (this.node === node) return;
|
|
296
249
|
const oldNode = this.node;
|
|
297
250
|
this.node = node;
|
|
298
|
-
|
|
299
|
-
// Destroy on old node
|
|
300
251
|
await oldNode.rest.destroyPlayer(this.guildId).catch(() => {});
|
|
301
|
-
|
|
302
|
-
// Recreate on new node with current state
|
|
303
252
|
if (this.queue.current) {
|
|
304
253
|
await this.play(this.queue.current, { startTime: this.realPosition });
|
|
305
254
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
if (Object.keys(this.filters).length > 0) {
|
|
309
|
-
await this.setFilters(this.filters).catch(() => {});
|
|
310
|
-
}
|
|
311
|
-
if (this.volume !== 100) {
|
|
312
|
-
await this.setVolume(this.volume).catch(() => {});
|
|
313
|
-
}
|
|
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(() => {});
|
|
314
257
|
}
|
|
315
258
|
|
|
316
|
-
// ─── Voice Channel Moves ──────────────────────────────────────────
|
|
317
|
-
|
|
318
259
|
public async move(channelId: string): Promise<void> {
|
|
319
260
|
this.voiceChannelId = channelId;
|
|
320
261
|
this.sendVoicePayload(channelId, this.selfDeaf, this.selfMute);
|
|
321
262
|
}
|
|
322
263
|
|
|
323
|
-
// ─── Destroy ──────────────────────────────────────────────────────
|
|
324
|
-
|
|
325
264
|
public async destroy(): Promise<void> {
|
|
326
265
|
this.state = PlayerState.DISCONNECTING;
|
|
327
266
|
this.sendVoicePayload(null, false, false);
|
|
328
|
-
|
|
329
267
|
await this.node.rest.destroyPlayer(this.guildId).catch(() => {});
|
|
330
|
-
|
|
331
268
|
this.playing = false;
|
|
332
269
|
this.paused = false;
|
|
333
270
|
this.queue.clear();
|
|
334
271
|
this.data.clear();
|
|
335
272
|
this.state = PlayerState.DISCONNECTED;
|
|
336
273
|
}
|
|
337
|
-
|
|
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
|
+
}
|
package/src/Plugin.ts
CHANGED
|
@@ -1,23 +1,7 @@
|
|
|
1
1
|
import type { Yukimu } from "./Yukimu";
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* Base Plugin class — extend this to create Yukimu plugins.
|
|
5
|
-
* Similar to Kazagumo's plugin system.
|
|
6
|
-
*
|
|
7
|
-
* Example:
|
|
8
|
-
* class MyPlugin extends Plugin {
|
|
9
|
-
* public name = "MyPlugin";
|
|
10
|
-
* public load(manager: Yukimu) {
|
|
11
|
-
* manager.on("trackStart", (player, track) => { ... });
|
|
12
|
-
* }
|
|
13
|
-
* }
|
|
14
|
-
*/
|
|
15
3
|
export abstract class Plugin {
|
|
16
4
|
public abstract name: string;
|
|
17
|
-
|
|
18
|
-
/** Called when plugin is loaded into Yukimu */
|
|
19
5
|
public abstract load(manager: Yukimu): void;
|
|
20
|
-
|
|
21
|
-
/** Called when plugin is unloaded */
|
|
22
6
|
public unload?(manager: Yukimu): void;
|
|
23
7
|
}
|