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.
Files changed (92) hide show
  1. package/.cache/replit/env/latest +72 -72
  2. package/.cache/replit/env/latest.json +1 -1
  3. package/.cache/replit/toolchain.json +1 -1
  4. package/dist/ConnectionPool.d.ts +1 -0
  5. package/dist/ConnectionPool.d.ts.map +1 -1
  6. package/dist/ConnectionPool.js +11 -24
  7. package/dist/ConnectionPool.js.map +1 -1
  8. package/dist/Constants.d.ts +3 -49
  9. package/dist/Constants.d.ts.map +1 -1
  10. package/dist/Constants.js +6 -61
  11. package/dist/Constants.js.map +1 -1
  12. package/dist/Node.d.ts +1 -1
  13. package/dist/Node.d.ts.map +1 -1
  14. package/dist/Node.js +17 -17
  15. package/dist/Node.js.map +1 -1
  16. package/dist/Player.d.ts +9 -5
  17. package/dist/Player.d.ts.map +1 -1
  18. package/dist/Player.js +73 -54
  19. package/dist/Player.js.map +1 -1
  20. package/dist/Plugin.d.ts +0 -14
  21. package/dist/Plugin.d.ts.map +1 -1
  22. package/dist/Plugin.js +0 -12
  23. package/dist/Plugin.js.map +1 -1
  24. package/dist/Queue.d.ts +0 -1
  25. package/dist/Queue.d.ts.map +1 -1
  26. package/dist/Queue.js +7 -22
  27. package/dist/Queue.js.map +1 -1
  28. package/dist/Resolver.d.ts +1 -1
  29. package/dist/Resolver.d.ts.map +1 -1
  30. package/dist/Resolver.js +24 -10
  31. package/dist/Resolver.js.map +1 -1
  32. package/dist/Rest.d.ts +3 -4
  33. package/dist/Rest.d.ts.map +1 -1
  34. package/dist/Rest.js +26 -16
  35. package/dist/Rest.js.map +1 -1
  36. package/dist/TrackCache.d.ts +0 -5
  37. package/dist/TrackCache.d.ts.map +1 -1
  38. package/dist/TrackCache.js +6 -23
  39. package/dist/TrackCache.js.map +1 -1
  40. package/dist/WsQueue.d.ts +2 -11
  41. package/dist/WsQueue.d.ts.map +1 -1
  42. package/dist/WsQueue.js +5 -21
  43. package/dist/WsQueue.js.map +1 -1
  44. package/dist/Yukimu.d.ts +3 -12
  45. package/dist/Yukimu.d.ts.map +1 -1
  46. package/dist/Yukimu.js +2 -8
  47. package/dist/Yukimu.js.map +1 -1
  48. package/dist/connector/DiscordJS.d.ts +1 -18
  49. package/dist/connector/DiscordJS.d.ts.map +1 -1
  50. package/dist/connector/DiscordJS.js.map +1 -1
  51. package/dist/connector/Eris.d.ts +1 -16
  52. package/dist/connector/Eris.d.ts.map +1 -1
  53. package/dist/connector/Eris.js +2 -4
  54. package/dist/connector/Eris.js.map +1 -1
  55. package/dist/connector/Oceanic.d.ts +1 -13
  56. package/dist/connector/Oceanic.d.ts.map +1 -1
  57. package/dist/connector/Oceanic.js +2 -4
  58. package/dist/connector/Oceanic.js.map +1 -1
  59. package/dist/errors/YukimuError.d.ts.map +1 -1
  60. package/dist/errors/YukimuError.js +0 -1
  61. package/dist/errors/YukimuError.js.map +1 -1
  62. package/dist/index.d.ts.map +1 -1
  63. package/dist/index.js +0 -5
  64. package/dist/index.js.map +1 -1
  65. package/dist/plugins/AutoResume.d.ts +0 -13
  66. package/dist/plugins/AutoResume.d.ts.map +1 -1
  67. package/dist/plugins/AutoResume.js +10 -18
  68. package/dist/plugins/AutoResume.js.map +1 -1
  69. package/dist/plugins/PlayerMoved.js.map +1 -1
  70. package/dist/types.d.ts +25 -34
  71. package/dist/types.d.ts.map +1 -1
  72. package/package.json +2 -2
  73. package/src/ConnectionPool.ts +14 -31
  74. package/src/Constants.ts +6 -62
  75. package/src/Node.ts +44 -51
  76. package/src/Player.ts +86 -91
  77. package/src/Plugin.ts +0 -16
  78. package/src/Queue.ts +7 -32
  79. package/src/Resolver.ts +25 -14
  80. package/src/Rest.ts +34 -28
  81. package/src/TrackCache.ts +7 -30
  82. package/src/WsQueue.ts +9 -29
  83. package/src/Yukimu.ts +10 -45
  84. package/src/connector/DiscordJS.ts +5 -12
  85. package/src/connector/Eris.ts +6 -17
  86. package/src/connector/Oceanic.ts +5 -15
  87. package/src/errors/YukimuError.ts +0 -2
  88. package/src/index.ts +4 -26
  89. package/src/plugins/AutoResume.ts +13 -37
  90. package/src/plugins/PlayerMoved.ts +4 -4
  91. package/src/types.ts +26 -26
  92. package/tsconfig.json +3 -1
package/src/Constants.ts CHANGED
@@ -1,6 +1,4 @@
1
- /** Lavalink v3/v4 supported ops and constants */
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\/(browse\/)?(track|album|playlist)\/.+/],
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<unknown> = Promise.resolve();
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.2.0",
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: WebSocket.RawData) => this.onMessage(d));
81
- this.ws.on("close", (code: number, reason: Buffer) => this.onClose(code, reason.toString()));
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: WebSocket.RawData): void {
119
- let payload: Record<string, unknown>;
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 as string;
128
- this.resumed = (payload.resumed as boolean) ?? false;
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 as unknown as NodeStats;
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
- this.stats = payload as unknown as NodeStats;
150
- this.calculatePenalties();
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: Record<string, unknown>): void {
219
- const player = this.manager.players.get(payload.guildId as string);
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 as { position?: number; ping?: number; time?: number };
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: Record<string, unknown>): void {
229
- const player = this.manager.players.get(payload.guildId as string);
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 as string, info: {} as Track["info"], pluginInfo: {} };
227
+ track = { encoded: payload.track, info: {} as any, pluginInfo: {} };
235
228
  } else {
236
- track = payload.track as 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
- player.playing = true;
243
- player.paused = false;
244
- player.lastUpdated = Date.now();
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 as string;
250
- player.playing = false;
251
- player.position = 0;
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 (player.loop === "track" && player.queue.current) {
256
- player.play(player.queue.current).catch(() => {});
250
+ if (p.loop === "track" && p.queue.current) {
251
+ p.play(p.queue.current).catch(() => {});
257
252
  } else {
258
- if (player.loop === "queue" && player.queue.current) {
259
- player.queue.add(player.queue.current);
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
- player.play(next).catch(() => {});
256
+ p.play(next).catch(() => {});
264
257
  } else {
265
258
  this.manager.emit("queueEnd", player);
266
- if (player.autoplay && track.info?.uri) {
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 as LavalinkException);
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 as number);
281
- player.skip().catch(() => {});
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 as number, payload.reason as string, payload.byRemote as boolean);
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, unknown>): Promise<void> {
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, VoiceState, FilterOptions } from "./types";
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
- // ─── Getters ──────────────────────────────────────────────────────
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.length ?? this.position);
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: 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.isSeekable) throw new PlayerError("Current track is not seekable");
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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
228
- low: [0.2, 0.15, 0.1, 0.05, 0.0, -0.05, 0, 0, 0, 0, 0, 0, 0, 0, 0],
229
- medium: [0.4, 0.3, 0.2, 0.1, 0.05, -0.05, 0, 0, 0, 0, 0, 0, 0, 0, 0],
230
- high: [0.6, 0.5, 0.4, 0.25, 0.1, -0.1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
231
- extreme: [1.0, 0.8, 0.6, 0.4, 0.2, -0.2, 0, 0, 0, 0, 0, 0, 0, 0, 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 } : undefined });
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 } : undefined });
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 } : undefined });
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 } : undefined });
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 } : undefined });
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 } : undefined });
229
+ await this.setFilters({ lowPass: enabled ? { smoothing } : {} });
264
230
  }
265
231
 
266
- // ─── Queue Helpers ────────────────────────────────────────────────
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
- this.loop = mode;
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
- // Reapply filters and volume
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
  }