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/Queue.ts
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import type { Track } from "./types";
|
|
2
|
-
|
|
3
|
-
export class Queue {
|
|
4
|
-
public current: Track | null = null;
|
|
5
|
-
public previous: Track[] = [];
|
|
6
|
-
private tracks: Track[] = [];
|
|
7
|
-
|
|
8
|
-
public add(tracks: Track | Track[], position?: number): void {
|
|
9
|
-
const arr = Array.isArray(tracks) ? tracks : [tracks];
|
|
10
|
-
if (position !== undefined) this.tracks.splice(position, 0, ...arr);
|
|
11
|
-
else this.tracks.push(...arr);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
public next(): Track | null {
|
|
15
|
-
if (this.current) {
|
|
16
|
-
this.previous.unshift(this.current);
|
|
17
|
-
if (this.previous.length > 10) this.previous.pop();
|
|
18
|
-
}
|
|
19
|
-
this.current = this.tracks.shift() ?? null;
|
|
20
|
-
return this.current;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
public remove(index: number): Track | null {
|
|
24
|
-
if (index < 0 || index >= this.tracks.length) return null;
|
|
25
|
-
const [removed] = this.tracks.splice(index, 1);
|
|
26
|
-
return removed;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
public removeRange(start: number, end: number): Track[] {
|
|
30
|
-
return this.tracks.splice(start, end - start);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
public clear(): void {
|
|
34
|
-
this.tracks = [];
|
|
35
|
-
this.current = null;
|
|
36
|
-
this.previous = [];
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
public shuffle(): void {
|
|
40
|
-
for (let i = this.tracks.length - 1; i > 0; i--) {
|
|
41
|
-
const j = Math.floor(Math.random() * (i + 1));
|
|
42
|
-
[this.tracks[i], this.tracks[j]] = [this.tracks[j], this.tracks[i]];
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
public move(from: number, to: number): void {
|
|
47
|
-
if (from < 0 || to < 0 || from >= this.tracks.length || to >= this.tracks.length) return;
|
|
48
|
-
const [track] = this.tracks.splice(from, 1);
|
|
49
|
-
this.tracks.splice(to, 0, track);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
public skipto(index: number): Track | null {
|
|
53
|
-
if (index < 0 || index >= this.tracks.length) return null;
|
|
54
|
-
this.tracks.splice(0, index);
|
|
55
|
-
return this.next();
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
public peek(count: number = 10): Track[] { return this.tracks.slice(0, count); }
|
|
59
|
-
public find(predicate: (track: Track) => boolean): Track | undefined { return this.tracks.find(predicate); }
|
|
60
|
-
public filter(predicate: (track: Track) => boolean): Track[] { return this.tracks.filter(predicate); }
|
|
61
|
-
|
|
62
|
-
get size(): number { return this.tracks.length; }
|
|
63
|
-
get isEmpty(): boolean { return this.tracks.length === 0; }
|
|
64
|
-
get totalDuration(): number { return this.tracks.reduce((acc, t) => acc + (t.info?.length ?? 0), 0); }
|
|
65
|
-
get list(): ReadonlyArray<Track> { return this.tracks; }
|
|
66
|
-
}
|
package/src/Resolver.ts
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import { SOURCE_PREFIXES, URL_PATTERNS } from "./Constants";
|
|
2
|
-
import type { Yukimu } from "./Yukimu";
|
|
3
|
-
import type { SearchResult, SearchSource, Track } from "./types";
|
|
4
|
-
|
|
5
|
-
export class Resolver {
|
|
6
|
-
private manager: Yukimu;
|
|
7
|
-
private spotifyToken: string | null = null;
|
|
8
|
-
private spotifyExpiry: number = 0;
|
|
9
|
-
|
|
10
|
-
constructor(manager: Yukimu) { this.manager = manager; }
|
|
11
|
-
|
|
12
|
-
public async resolve(query: string, source: SearchSource, requester?: any): Promise<SearchResult> {
|
|
13
|
-
const node = this.manager.getBestNode();
|
|
14
|
-
const isUrl = /^https?:\/\//.test(query);
|
|
15
|
-
const identifier = isUrl ? query : `${SOURCE_PREFIXES[source] ?? "ytsearch"}:${query}`;
|
|
16
|
-
|
|
17
|
-
let result: SearchResult;
|
|
18
|
-
try {
|
|
19
|
-
result = await node.loadTracks(identifier);
|
|
20
|
-
} catch (err: any) {
|
|
21
|
-
console.error("[Resolver] loadTracks error:", err.message);
|
|
22
|
-
return { loadType: "error", tracks: [] };
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
if (!result) return { loadType: "empty", tracks: [] };
|
|
26
|
-
if (!result.tracks) result.tracks = [];
|
|
27
|
-
|
|
28
|
-
// Attach requester to all tracks
|
|
29
|
-
if (requester && result.tracks.length > 0) {
|
|
30
|
-
result.tracks.forEach((t: any) => { t.requester = requester; });
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Spotify fallback — if LavaSrc not installed
|
|
34
|
-
if ((result.loadType === "error" || result.loadType === "empty") && isUrl) {
|
|
35
|
-
const spotifyMatch = query.match(/spotify\.com\/track\/([a-zA-Z0-9]+)/);
|
|
36
|
-
if (spotifyMatch) {
|
|
37
|
-
const meta = await this.resolveSpotifyMeta(spotifyMatch[1]);
|
|
38
|
-
if (meta) return this.resolve(`${meta.title} ${meta.artist}`, "youtube", requester);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Cache tracks
|
|
43
|
-
if (result.tracks.length > 0) {
|
|
44
|
-
for (const track of result.tracks) {
|
|
45
|
-
if (track?.encoded) this.manager.trackCache.set(track.encoded, track);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return result;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
public detectSource(url: string): SearchSource | null {
|
|
53
|
-
for (const [source, patterns] of Object.entries(URL_PATTERNS)) {
|
|
54
|
-
if (patterns.some(p => p.test(url))) return source as SearchSource;
|
|
55
|
-
}
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
private async getSpotifyToken(): Promise<string | null> {
|
|
60
|
-
const opts = this.manager.options.spotify;
|
|
61
|
-
if (!opts) return null;
|
|
62
|
-
if (this.spotifyToken && Date.now() < this.spotifyExpiry) return this.spotifyToken;
|
|
63
|
-
try {
|
|
64
|
-
const res = await (globalThis as any).fetch("https://accounts.spotify.com/api/token", {
|
|
65
|
-
method: "POST",
|
|
66
|
-
headers: {
|
|
67
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
68
|
-
Authorization: `Basic ${Buffer.from(`${opts.clientId}:${opts.clientSecret}`).toString("base64")}`,
|
|
69
|
-
},
|
|
70
|
-
body: "grant_type=client_credentials",
|
|
71
|
-
});
|
|
72
|
-
const data = await res.json();
|
|
73
|
-
this.spotifyToken = data.access_token;
|
|
74
|
-
this.spotifyExpiry = Date.now() + data.expires_in * 1000 - 5000;
|
|
75
|
-
return this.spotifyToken;
|
|
76
|
-
} catch { return null; }
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
private async resolveSpotifyMeta(trackId: string): Promise<{ title: string; artist: string } | null> {
|
|
80
|
-
const token = await this.getSpotifyToken();
|
|
81
|
-
if (!token) return null;
|
|
82
|
-
try {
|
|
83
|
-
const res = await (globalThis as any).fetch(`https://api.spotify.com/v1/tracks/${trackId}`, {
|
|
84
|
-
headers: { Authorization: `Bearer ${token}` },
|
|
85
|
-
});
|
|
86
|
-
const data = await res.json();
|
|
87
|
-
return { title: data.name, artist: data.artists[0]?.name ?? "Unknown" };
|
|
88
|
-
} catch { return null; }
|
|
89
|
-
}
|
|
90
|
-
}
|
package/src/Rest.ts
DELETED
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
import { RestError } from "./errors/YukimuError";
|
|
2
|
-
import type { Node } from "./Node";
|
|
3
|
-
import type { SearchResult, Track, NodeStats, NodeInfo } from "./types";
|
|
4
|
-
|
|
5
|
-
const TIMEOUT_MS = 30000;
|
|
6
|
-
|
|
7
|
-
export class Rest {
|
|
8
|
-
private node: Node;
|
|
9
|
-
|
|
10
|
-
constructor(node: Node) { this.node = node; }
|
|
11
|
-
|
|
12
|
-
get baseUrl(): string {
|
|
13
|
-
const protocol = this.node.options.secure ? "https" : "http";
|
|
14
|
-
return `${protocol}://${this.node.options.host}:${this.node.options.port}`;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
get prefix(): string { return this.node.version === 4 ? "/v4" : ""; }
|
|
18
|
-
|
|
19
|
-
get headers(): Record<string, string> {
|
|
20
|
-
return {
|
|
21
|
-
Authorization: this.node.options.password,
|
|
22
|
-
"User-Id": this.node.manager.options.clientId,
|
|
23
|
-
"Client-Name": "Yukimu/1.3.0",
|
|
24
|
-
"Content-Type": "application/json",
|
|
25
|
-
...(this.node.version === 3 ? { "Num-Shards": "1" } : {}),
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
public async request<T = any>(method: string, path: string, body?: any): Promise<T> {
|
|
30
|
-
const url = `${this.baseUrl}${this.prefix}${path}`;
|
|
31
|
-
const controller = new AbortController();
|
|
32
|
-
const timeout = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
|
33
|
-
|
|
34
|
-
try {
|
|
35
|
-
const res = await (globalThis as any).fetch(url, {
|
|
36
|
-
method,
|
|
37
|
-
headers: this.headers,
|
|
38
|
-
body: body ? JSON.stringify(body) : undefined,
|
|
39
|
-
signal: controller.signal,
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
clearTimeout(timeout);
|
|
43
|
-
|
|
44
|
-
if (!res.ok) {
|
|
45
|
-
const text = await res.text().catch(() => "Unknown error");
|
|
46
|
-
throw new RestError(`REST ${res.status} on ${method} ${path}: ${text}`, res.status, path);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (res.status === 204) return undefined as T;
|
|
50
|
-
|
|
51
|
-
const text = await res.text();
|
|
52
|
-
let parsed: any;
|
|
53
|
-
try { parsed = JSON.parse(text); } catch { return null as T; }
|
|
54
|
-
|
|
55
|
-
// Normalize Lavalink v4.2+ response — uses "data" instead of "tracks"
|
|
56
|
-
if (parsed && parsed.data !== undefined && !parsed.tracks) {
|
|
57
|
-
parsed.tracks = Array.isArray(parsed.data) ? parsed.data : [parsed.data];
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return parsed as T;
|
|
61
|
-
} catch (err: any) {
|
|
62
|
-
clearTimeout(timeout);
|
|
63
|
-
if (err.name === "AbortError") throw new RestError(`REST timeout on ${method} ${path}`, 408, path);
|
|
64
|
-
throw err;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
public async loadTracks(identifier: string): Promise<SearchResult> {
|
|
69
|
-
const raw = await this.request<any>("GET", `/loadtracks?identifier=${encodeURIComponent(identifier)}`);
|
|
70
|
-
if (!raw) return { loadType: "empty", tracks: [] };
|
|
71
|
-
return this.node.version === 3 ? this.normalizeV3(raw) : raw;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
public async decodeTrack(encoded: string): Promise<Track> {
|
|
75
|
-
return this.request("GET", `/decodetrack?encodedTrack=${encodeURIComponent(encoded)}`);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
public async decodeTracks(encoded: string[]): Promise<Track[]> {
|
|
79
|
-
return this.request("POST", `/decodetracks`, encoded);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
public async getInfo(): Promise<NodeInfo> {
|
|
83
|
-
return this.request("GET", "/info");
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
public async getStats(): Promise<NodeStats> {
|
|
87
|
-
return this.request("GET", "/stats");
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
public async updatePlayer(guildId: string, body: any, noReplace = false): Promise<any> {
|
|
91
|
-
const path = this.node.version === 4
|
|
92
|
-
? `/sessions/${this.node.sessionId}/players/${guildId}?noReplace=${noReplace}`
|
|
93
|
-
: `/players/${guildId}`;
|
|
94
|
-
return this.request("PATCH", path, body);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
public async destroyPlayer(guildId: string): Promise<void> {
|
|
98
|
-
const path = this.node.version === 4
|
|
99
|
-
? `/sessions/${this.node.sessionId}/players/${guildId}`
|
|
100
|
-
: `/players/${guildId}`;
|
|
101
|
-
await this.request("DELETE", path).catch(() => {});
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
public async updateSession(body: { resuming?: boolean; timeout?: number }): Promise<any> {
|
|
105
|
-
if (this.node.version !== 4) return;
|
|
106
|
-
return this.request("PATCH", `/sessions/${this.node.sessionId}`, body);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
private normalizeV3(raw: any): SearchResult {
|
|
110
|
-
const loadType = (raw?.loadType ?? "").toLowerCase();
|
|
111
|
-
const norm = (tracks: any[]): Track[] =>
|
|
112
|
-
(tracks ?? []).map((t: any) => ({
|
|
113
|
-
encoded: t.track ?? t.encoded,
|
|
114
|
-
info: t.info,
|
|
115
|
-
pluginInfo: t.pluginInfo ?? {},
|
|
116
|
-
}));
|
|
117
|
-
|
|
118
|
-
switch (loadType) {
|
|
119
|
-
case "track_loaded": return { loadType: "track", tracks: norm(raw.tracks) };
|
|
120
|
-
case "playlist_loaded": return { loadType: "playlist", tracks: norm(raw.tracks), playlistInfo: raw.playlistInfo };
|
|
121
|
-
case "search_result": return { loadType: "search", tracks: norm(raw.tracks) };
|
|
122
|
-
case "no_matches": return { loadType: "empty", tracks: [] };
|
|
123
|
-
case "load_failed": return { loadType: "error", tracks: [], exception: raw.exception };
|
|
124
|
-
default: return { loadType: "empty", tracks: [] };
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}
|
package/src/TrackCache.ts
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import type { Track } from "./types";
|
|
2
|
-
|
|
3
|
-
interface CacheEntry {
|
|
4
|
-
track: Track;
|
|
5
|
-
expiresAt: number;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export class TrackCache {
|
|
9
|
-
private cache: Map<string, CacheEntry> = new Map();
|
|
10
|
-
private readonly ttl: number;
|
|
11
|
-
private sweepInterval: ReturnType<typeof setInterval>;
|
|
12
|
-
|
|
13
|
-
constructor(ttlMs: number = 3600000) {
|
|
14
|
-
this.ttl = ttlMs;
|
|
15
|
-
this.sweepInterval = setInterval(() => this.sweep(), 600000);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
public set(encoded: string, track: Track): void {
|
|
19
|
-
this.cache.set(encoded, { track, expiresAt: Date.now() + this.ttl });
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
public get(encoded: string): Track | null {
|
|
23
|
-
const entry = this.cache.get(encoded);
|
|
24
|
-
if (!entry) return null;
|
|
25
|
-
if (Date.now() > entry.expiresAt) { this.cache.delete(encoded); return null; }
|
|
26
|
-
return entry.track;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
public has(encoded: string): boolean { return this.get(encoded) !== null; }
|
|
30
|
-
public delete(encoded: string): void { this.cache.delete(encoded); }
|
|
31
|
-
public clear(): void { this.cache.clear(); }
|
|
32
|
-
|
|
33
|
-
private sweep(): void {
|
|
34
|
-
const now = Date.now();
|
|
35
|
-
for (const [key, entry] of this.cache) {
|
|
36
|
-
if (now > entry.expiresAt) this.cache.delete(key);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
public destroy(): void {
|
|
41
|
-
clearInterval(this.sweepInterval);
|
|
42
|
-
this.cache.clear();
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
get size(): number { return this.cache.size; }
|
|
46
|
-
}
|
package/src/WsQueue.ts
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import WebSocket from "ws";
|
|
2
|
-
|
|
3
|
-
interface QueuedMessage {
|
|
4
|
-
data: string;
|
|
5
|
-
resolve: () => void;
|
|
6
|
-
reject: (err: Error) => void;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export class WsQueue {
|
|
10
|
-
private queue: QueuedMessage[] = [];
|
|
11
|
-
private ws: WebSocket | null = null;
|
|
12
|
-
|
|
13
|
-
public setSocket(ws: WebSocket | null): void {
|
|
14
|
-
this.ws = ws;
|
|
15
|
-
if (ws) {
|
|
16
|
-
ws.on("open", () => this.flush());
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
public send(data: Record<string, any>): Promise<void> {
|
|
21
|
-
return new Promise((resolve, reject) => {
|
|
22
|
-
const serialized = JSON.stringify(data);
|
|
23
|
-
if (this.ws?.readyState === 1) {
|
|
24
|
-
this.ws.send(serialized, (err: any) => {
|
|
25
|
-
if (err) reject(err);
|
|
26
|
-
else resolve();
|
|
27
|
-
});
|
|
28
|
-
} else {
|
|
29
|
-
this.queue.push({ data: serialized, resolve, reject });
|
|
30
|
-
}
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
public flush(): void {
|
|
35
|
-
if (!this.ws) return;
|
|
36
|
-
const toSend = [...this.queue];
|
|
37
|
-
this.queue = [];
|
|
38
|
-
for (const msg of toSend) {
|
|
39
|
-
if (this.ws.readyState !== 1) {
|
|
40
|
-
this.queue.unshift(msg);
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
this.ws.send(msg.data, (err: any) => {
|
|
44
|
-
if (err) msg.reject(err);
|
|
45
|
-
else msg.resolve();
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
public clear(): void {
|
|
51
|
-
for (const msg of this.queue) {
|
|
52
|
-
msg.reject(new Error("WsQueue cleared"));
|
|
53
|
-
}
|
|
54
|
-
this.queue = [];
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
get size(): number { return this.queue.length; }
|
|
58
|
-
}
|
package/src/Yukimu.ts
DELETED
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
import { EventEmitter } from "events";
|
|
2
|
-
import { Node } from "./Node";
|
|
3
|
-
import { Player } from "./Player";
|
|
4
|
-
import { Resolver } from "./Resolver";
|
|
5
|
-
import { ConnectionPool } from "./ConnectionPool";
|
|
6
|
-
import { TrackCache } from "./TrackCache";
|
|
7
|
-
import { URL_PATTERNS } from "./Constants";
|
|
8
|
-
import { YukimuError } from "./errors/YukimuError";
|
|
9
|
-
import type { Plugin } from "./Plugin";
|
|
10
|
-
import type { Connector } from "./connector/Connector";
|
|
11
|
-
import type { YukimuOptions, NodeOptions, PlayerOptions, YukimuEvents, SearchSource, SearchResult } from "./types";
|
|
12
|
-
|
|
13
|
-
export class Yukimu extends EventEmitter {
|
|
14
|
-
public readonly options: YukimuOptions;
|
|
15
|
-
public readonly nodes: Map<string, Node> = new Map();
|
|
16
|
-
public readonly players: Map<string, Player> = new Map();
|
|
17
|
-
public readonly connector: Connector;
|
|
18
|
-
public readonly resolver: Resolver;
|
|
19
|
-
public readonly pool: ConnectionPool;
|
|
20
|
-
public readonly trackCache: TrackCache;
|
|
21
|
-
private readonly plugins: Map<string, Plugin> = new Map();
|
|
22
|
-
public nodeResolver?: (nodes: Map<string, Node>) => Node | undefined;
|
|
23
|
-
|
|
24
|
-
constructor(options: YukimuOptions, connector: Connector, nodes: NodeOptions[], plugins: Plugin[] = []) {
|
|
25
|
-
super();
|
|
26
|
-
this.options = { moveOnDisconnect: true, voiceConnectionTimeout: 15000, ...options };
|
|
27
|
-
this.connector = connector.set(this);
|
|
28
|
-
this.resolver = new Resolver(this);
|
|
29
|
-
this.pool = new ConnectionPool(this);
|
|
30
|
-
this.trackCache = new TrackCache();
|
|
31
|
-
for (const plugin of plugins) this.use(plugin);
|
|
32
|
-
this.connector.listen();
|
|
33
|
-
for (const node of nodes) this.addNode(node);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// ─── Plugin System ────────────────────────────────────────────────
|
|
37
|
-
public use(plugin: Plugin): this {
|
|
38
|
-
if (this.plugins.has(plugin.name)) throw new YukimuError(`Plugin "${plugin.name}" already loaded`);
|
|
39
|
-
plugin.load(this);
|
|
40
|
-
this.plugins.set(plugin.name, plugin);
|
|
41
|
-
console.log(`[Yukimu] Plugin "${plugin.name}" loaded`);
|
|
42
|
-
return this;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
public unuse(name: string): this {
|
|
46
|
-
const plugin = this.plugins.get(name);
|
|
47
|
-
if (!plugin) throw new YukimuError(`Plugin "${name}" not found`);
|
|
48
|
-
plugin.unload?.(this);
|
|
49
|
-
this.plugins.delete(name);
|
|
50
|
-
return this;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
public getPlugin<T extends Plugin>(name: string): T | undefined {
|
|
54
|
-
return this.plugins.get(name) as T;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// ─── Node Management ──────────────────────────────────────────────
|
|
58
|
-
public addNode(options: NodeOptions): Node {
|
|
59
|
-
if (this.nodes.has(options.name)) throw new YukimuError(`Node "${options.name}" already exists`);
|
|
60
|
-
const node = new Node(this, options);
|
|
61
|
-
this.nodes.set(options.name, node);
|
|
62
|
-
node.connect();
|
|
63
|
-
return node;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
public removeNode(name: string): void {
|
|
67
|
-
const node = this.nodes.get(name);
|
|
68
|
-
if (!node) throw new YukimuError(`Node "${name}" not found`);
|
|
69
|
-
node.destroy();
|
|
70
|
-
this.nodes.delete(name);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
public getNode(name?: string): Node {
|
|
74
|
-
if (name) {
|
|
75
|
-
const node = this.nodes.get(name);
|
|
76
|
-
if (!node) throw new YukimuError(`Node "${name}" not found`);
|
|
77
|
-
return node;
|
|
78
|
-
}
|
|
79
|
-
return this.getBestNode();
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
public getBestNode(): Node {
|
|
83
|
-
const connected = [...this.nodes.values()].filter(n => n.connected);
|
|
84
|
-
if (!connected.length) throw new YukimuError("No connected Lavalink nodes available");
|
|
85
|
-
if (this.nodeResolver) {
|
|
86
|
-
const resolved = this.nodeResolver(this.nodes);
|
|
87
|
-
if (resolved?.connected) return resolved;
|
|
88
|
-
}
|
|
89
|
-
return connected.sort((a, b) => a.calculatePenalties() - b.calculatePenalties())[0];
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// ─── Player Management ────────────────────────────────────────────
|
|
93
|
-
public createPlayer(options: PlayerOptions): Player {
|
|
94
|
-
const existing = this.players.get(options.guildId);
|
|
95
|
-
if (existing) return existing;
|
|
96
|
-
const node = options.nodeName ? this.getNode(options.nodeName) : this.getBestNode();
|
|
97
|
-
const player = new Player(this, node, options);
|
|
98
|
-
this.players.set(options.guildId, player);
|
|
99
|
-
this.pool.add(options.guildId, options.voiceChannelId);
|
|
100
|
-
this.emit("playerCreate", player);
|
|
101
|
-
return player;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
public getPlayer(guildId: string): Player | null {
|
|
105
|
-
return this.players.get(guildId) ?? null;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
public async destroyPlayer(guildId: string): Promise<void> {
|
|
109
|
-
const player = this.players.get(guildId);
|
|
110
|
-
if (!player) return;
|
|
111
|
-
await player.destroy();
|
|
112
|
-
this.players.delete(guildId);
|
|
113
|
-
this.pool.remove(guildId);
|
|
114
|
-
this.emit("playerDestroy", player);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// ─── Search ───────────────────────────────────────────────────────
|
|
118
|
-
public async search(query: string, options?: { source?: SearchSource; requester?: any }): Promise<SearchResult> {
|
|
119
|
-
return this.resolver.resolve(
|
|
120
|
-
query,
|
|
121
|
-
options?.source ?? this.options.defaultSource ?? "youtube",
|
|
122
|
-
options?.requester
|
|
123
|
-
);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// ─── Voice Gateway ────────────────────────────────────────────────
|
|
127
|
-
public handleVoiceStateUpdate(data: any): void {
|
|
128
|
-
if (data.user_id !== this.options.clientId) return;
|
|
129
|
-
if (!data.guild_id) return;
|
|
130
|
-
|
|
131
|
-
const player = this.players.get(data.guild_id);
|
|
132
|
-
|
|
133
|
-
if (!data.channel_id) {
|
|
134
|
-
this.pool.remove(data.guild_id);
|
|
135
|
-
if (player && this.options.moveOnDisconnect) {
|
|
136
|
-
this.destroyPlayer(data.guild_id).catch(() => {});
|
|
137
|
-
}
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
this.pool.updateSession(data.guild_id, data.session_id, data.channel_id);
|
|
142
|
-
|
|
143
|
-
if (player) {
|
|
144
|
-
const oldChannel = player.voiceChannelId;
|
|
145
|
-
player.voiceChannelId = data.channel_id;
|
|
146
|
-
player.sessionId = data.session_id;
|
|
147
|
-
|
|
148
|
-
if (oldChannel && oldChannel !== data.channel_id) {
|
|
149
|
-
this.emit("playerMove", player, oldChannel, data.channel_id);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
if (this.pool.isReady(data.guild_id)) {
|
|
153
|
-
const vs = this.pool.getVoiceState(data.guild_id)!;
|
|
154
|
-
player.voiceToken = vs.token;
|
|
155
|
-
player.voiceEndpoint = vs.endpoint;
|
|
156
|
-
player.checkVoiceReady();
|
|
157
|
-
this.pool.markConnected(data.guild_id);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
public handleVoiceServerUpdate(data: any): void {
|
|
163
|
-
if (!data.endpoint) return;
|
|
164
|
-
this.pool.updateServer(data.guild_id, data.token, data.endpoint);
|
|
165
|
-
const player = this.players.get(data.guild_id);
|
|
166
|
-
if (!player) return;
|
|
167
|
-
player.voiceToken = data.token;
|
|
168
|
-
player.voiceEndpoint = data.endpoint;
|
|
169
|
-
if (this.pool.isReady(data.guild_id)) {
|
|
170
|
-
player.checkVoiceReady();
|
|
171
|
-
this.pool.markConnected(data.guild_id);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// ─── Utility ─────────────────────────────────────────────────────
|
|
176
|
-
public detectSource(url: string): SearchSource | null {
|
|
177
|
-
for (const [source, patterns] of Object.entries(URL_PATTERNS)) {
|
|
178
|
-
if (patterns.some(p => p.test(url))) return source as SearchSource;
|
|
179
|
-
}
|
|
180
|
-
return null;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
public getStats() {
|
|
184
|
-
const nodes = [...this.nodes.values()];
|
|
185
|
-
return {
|
|
186
|
-
nodes: nodes.length,
|
|
187
|
-
connectedNodes: nodes.filter(n => n.connected).length,
|
|
188
|
-
players: this.players.size,
|
|
189
|
-
playingPlayers: [...this.players.values()].filter(p => p.playing).length,
|
|
190
|
-
cachedTracks: this.trackCache.size,
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
public destroy(): void {
|
|
195
|
-
for (const node of this.nodes.values()) node.destroy();
|
|
196
|
-
this.trackCache.destroy();
|
|
197
|
-
this.nodes.clear();
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// ─── Typed EventEmitter ───────────────────────────────────────────
|
|
201
|
-
public on<K extends keyof YukimuEvents>(event: K, listener: (...args: YukimuEvents[K]) => void): this {
|
|
202
|
-
return super.on(event, listener as any);
|
|
203
|
-
}
|
|
204
|
-
public once<K extends keyof YukimuEvents>(event: K, listener: (...args: YukimuEvents[K]) => void): this {
|
|
205
|
-
return super.once(event, listener as any);
|
|
206
|
-
}
|
|
207
|
-
public off<K extends keyof YukimuEvents>(event: K, listener: (...args: YukimuEvents[K]) => void): this {
|
|
208
|
-
return super.off(event, listener as any);
|
|
209
|
-
}
|
|
210
|
-
public emit<K extends keyof YukimuEvents>(event: K, ...args: YukimuEvents[K]): boolean {
|
|
211
|
-
return super.emit(event, ...args);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { Yukimu } from "../Yukimu";
|
|
2
|
-
|
|
3
|
-
export abstract class Connector {
|
|
4
|
-
protected manager!: Yukimu;
|
|
5
|
-
|
|
6
|
-
public set(manager: Yukimu): this {
|
|
7
|
-
this.manager = manager;
|
|
8
|
-
return this;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
public abstract listen(): void;
|
|
12
|
-
public abstract sendPayload(guildId: string, payload: unknown): void;
|
|
13
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { Connector } from "./Connector";
|
|
2
|
-
|
|
3
|
-
export class DiscordJS extends Connector {
|
|
4
|
-
private client: any;
|
|
5
|
-
|
|
6
|
-
constructor(client: any) {
|
|
7
|
-
super();
|
|
8
|
-
this.client = client;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
public listen(): void {
|
|
12
|
-
this.client.on("raw", (packet: any) => {
|
|
13
|
-
if (packet.t === "VOICE_STATE_UPDATE") {
|
|
14
|
-
this.manager.handleVoiceStateUpdate(packet.d);
|
|
15
|
-
}
|
|
16
|
-
if (packet.t === "VOICE_SERVER_UPDATE") {
|
|
17
|
-
this.manager.handleVoiceServerUpdate(packet.d);
|
|
18
|
-
}
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
public sendPayload(guildId: string, payload: unknown): void {
|
|
23
|
-
const guild = this.client.guilds.cache.get(guildId);
|
|
24
|
-
if (guild) guild.shard.send(payload);
|
|
25
|
-
}
|
|
26
|
-
}
|
package/src/connector/Eris.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { Connector } from "./Connector";
|
|
2
|
-
|
|
3
|
-
export class Eris extends Connector {
|
|
4
|
-
private client: any;
|
|
5
|
-
|
|
6
|
-
constructor(client: any) {
|
|
7
|
-
super();
|
|
8
|
-
this.client = client;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
public listen(): void {
|
|
12
|
-
this.client.on("rawWS", (packet: any) => {
|
|
13
|
-
if (packet.t === "VOICE_STATE_UPDATE") this.manager.handleVoiceStateUpdate(packet.d);
|
|
14
|
-
if (packet.t === "VOICE_SERVER_UPDATE") this.manager.handleVoiceServerUpdate(packet.d);
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
public sendPayload(guildId: string, payload: unknown): void {
|
|
19
|
-
const guild = this.client.guilds.get(guildId);
|
|
20
|
-
if (!guild) return;
|
|
21
|
-
const shard = this.client.shards.get(guild.shard.id);
|
|
22
|
-
if (shard) shard.sendWS(4, (payload as any).d);
|
|
23
|
-
}
|
|
24
|
-
}
|
package/src/connector/Oceanic.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { Connector } from "./Connector";
|
|
2
|
-
|
|
3
|
-
export class Oceanic extends Connector {
|
|
4
|
-
private client: any;
|
|
5
|
-
|
|
6
|
-
constructor(client: any) {
|
|
7
|
-
super();
|
|
8
|
-
this.client = client;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
public listen(): void {
|
|
12
|
-
this.client.on("packet", (packet: any) => {
|
|
13
|
-
if (packet.t === "VOICE_STATE_UPDATE") this.manager.handleVoiceStateUpdate(packet.d);
|
|
14
|
-
if (packet.t === "VOICE_SERVER_UPDATE") this.manager.handleVoiceServerUpdate(packet.d);
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
public sendPayload(guildId: string, payload: unknown): void {
|
|
19
|
-
const guild = this.client.guilds.get(guildId);
|
|
20
|
-
if (guild) guild.shard.send(payload);
|
|
21
|
-
}
|
|
22
|
-
}
|