ryanlink 1.0.1

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 (60) hide show
  1. package/LICENSE +37 -0
  2. package/README.md +455 -0
  3. package/dist/index.d.mts +1335 -0
  4. package/dist/index.d.ts +1335 -0
  5. package/dist/index.js +4694 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/index.mjs +4604 -0
  8. package/dist/index.mjs.map +1 -0
  9. package/package.json +82 -0
  10. package/src/audio/AudioFilters.ts +316 -0
  11. package/src/audio/AudioQueue.ts +782 -0
  12. package/src/audio/AudioTrack.ts +242 -0
  13. package/src/audio/QueueController.ts +252 -0
  14. package/src/audio/TrackCollection.ts +138 -0
  15. package/src/audio/index.ts +9 -0
  16. package/src/config/defaults.ts +223 -0
  17. package/src/config/endpoints.ts +99 -0
  18. package/src/config/index.ts +9 -0
  19. package/src/config/patterns.ts +55 -0
  20. package/src/config/presets.ts +400 -0
  21. package/src/config/symbols.ts +31 -0
  22. package/src/core/PluginSystem.ts +50 -0
  23. package/src/core/RyanlinkPlayer.ts +403 -0
  24. package/src/core/index.ts +6 -0
  25. package/src/extensions/AutoplayExtension.ts +283 -0
  26. package/src/extensions/FairPlayExtension.ts +154 -0
  27. package/src/extensions/LyricsExtension.ts +187 -0
  28. package/src/extensions/PersistenceExtension.ts +182 -0
  29. package/src/extensions/SponsorBlockExtension.ts +81 -0
  30. package/src/extensions/index.ts +9 -0
  31. package/src/index.ts +19 -0
  32. package/src/lavalink/ConnectionPool.ts +326 -0
  33. package/src/lavalink/HttpClient.ts +316 -0
  34. package/src/lavalink/LavalinkConnection.ts +409 -0
  35. package/src/lavalink/index.ts +7 -0
  36. package/src/metadata.ts +88 -0
  37. package/src/types/api/Rest.ts +949 -0
  38. package/src/types/api/Websocket.ts +463 -0
  39. package/src/types/api/index.ts +6 -0
  40. package/src/types/audio/FilterManager.ts +29 -0
  41. package/src/types/audio/Queue.ts +4 -0
  42. package/src/types/audio/QueueManager.ts +30 -0
  43. package/src/types/audio/index.ts +7 -0
  44. package/src/types/common.ts +63 -0
  45. package/src/types/core/Player.ts +322 -0
  46. package/src/types/core/index.ts +5 -0
  47. package/src/types/index.ts +6 -0
  48. package/src/types/lavalink/Node.ts +173 -0
  49. package/src/types/lavalink/NodeManager.ts +34 -0
  50. package/src/types/lavalink/REST.ts +144 -0
  51. package/src/types/lavalink/index.ts +32 -0
  52. package/src/types/voice/VoiceManager.ts +176 -0
  53. package/src/types/voice/index.ts +5 -0
  54. package/src/utils/helpers.ts +169 -0
  55. package/src/utils/index.ts +6 -0
  56. package/src/utils/validators.ts +184 -0
  57. package/src/voice/RegionSelector.ts +184 -0
  58. package/src/voice/VoiceConnection.ts +451 -0
  59. package/src/voice/VoiceSession.ts +297 -0
  60. package/src/voice/index.ts +7 -0
@@ -0,0 +1,154 @@
1
+ import { PlayerPlugin } from "../core/PluginSystem";
2
+ import type { Player } from "../core/RyanlinkPlayer";
3
+ import type { Track } from "../audio/AudioTrack";
4
+
5
+ interface PlayerWithGet extends Player {
6
+ get?<T>(key: string): T;
7
+ }
8
+
9
+ export interface FairPlayPluginEvents {
10
+ /**
11
+ * Emitted when fair play reorders the queue
12
+ */
13
+ fairPlayApplied: [player: Player, guildId: string, trackCount: number];
14
+ }
15
+
16
+ export interface FairPlayConfig {
17
+ enabled: boolean;
18
+ /** Minimum tracks before fair play kicks in */
19
+ minTracks: number;
20
+ /** Maximum consecutive tracks from same requester */
21
+ maxConsecutive: number;
22
+ }
23
+
24
+ /**
25
+ * Fair Play Plugin - Distributes tracks fairly among requesters
26
+ * Prevents one user from dominating the queue
27
+ */
28
+ export class FairPlayPlugin extends PlayerPlugin<FairPlayPluginEvents & Record<string, unknown[]>> {
29
+ readonly name = "fairplay" as const;
30
+
31
+ #player!: Player;
32
+ config: FairPlayConfig = {
33
+ enabled: true,
34
+ minTracks: 5,
35
+ maxConsecutive: 3,
36
+ };
37
+
38
+ init(player: Player): void {
39
+ this.#player = player;
40
+
41
+ const config = (player as PlayerWithGet).get?.("fairplay_config");
42
+ if (config) {
43
+ this.config = { ...this.config, ...config };
44
+ }
45
+
46
+ // Apply fair play when tracks are added
47
+ player.on("trackAdd", (p, g, t) => {
48
+ void this.#handleTrackAdd(p, g, t);
49
+ });
50
+ }
51
+
52
+ #handleTrackAdd(_player: Player, guildId: string, _tracks: Track[]): void {
53
+ if (!this.config.enabled) {
54
+ return;
55
+ }
56
+
57
+ const queue = this.#player.queues.get(guildId);
58
+ if (!queue || queue.length < this.config.minTracks) {
59
+ return;
60
+ }
61
+
62
+ this.applyFairPlay(queue.guildId);
63
+ }
64
+
65
+ /**
66
+ * Apply fair play algorithm to a queue
67
+ */
68
+ applyFairPlay(guildId: string): void {
69
+ const queue = this.#player.queues.get(guildId);
70
+ if (!queue) {
71
+ return;
72
+ }
73
+
74
+ const playerConfig = (this.#player as PlayerWithGet).get?.("fairplay_config");
75
+ const config = playerConfig ? { ...this.config, ...playerConfig } : this.config;
76
+
77
+ if (queue.length < config.minTracks) {
78
+ return;
79
+ }
80
+
81
+ const tracks = queue.tracks;
82
+ const fairQueue: Track[] = [];
83
+ const requesterQueues = new Map<string, Track[]>();
84
+
85
+ // Group tracks by requester
86
+ for (const track of tracks) {
87
+ const requesterId = this.#getRequesterId(track);
88
+ let requesterQueue = requesterQueues.get(requesterId);
89
+ if (!requesterQueue) {
90
+ requesterQueue = [];
91
+ requesterQueues.set(requesterId, requesterQueue);
92
+ }
93
+ requesterQueue.push(track);
94
+ }
95
+
96
+ // Distribute tracks fairly
97
+ let hasMore = true;
98
+ let consecutiveCount = 0;
99
+ let lastRequesterId: string | null = null;
100
+
101
+ while (hasMore) {
102
+ hasMore = false;
103
+
104
+ for (const [requesterId, requesterTracks] of requesterQueues) {
105
+ if (requesterTracks.length === 0) {
106
+ continue;
107
+ }
108
+
109
+ hasMore = true;
110
+
111
+ // Check if we need to switch requester
112
+ if (lastRequesterId === requesterId) {
113
+ consecutiveCount++;
114
+ if (consecutiveCount >= config.maxConsecutive) {
115
+ // Skip this requester for now
116
+ continue;
117
+ }
118
+ } else {
119
+ consecutiveCount = 1;
120
+ lastRequesterId = requesterId;
121
+ }
122
+
123
+ // Add track from this requester
124
+ const track = requesterTracks.shift();
125
+ if (track) {
126
+ fairQueue.push(track);
127
+ }
128
+ }
129
+
130
+ // Reset if we've gone through all requesters
131
+ if (!hasMore) {
132
+ lastRequesterId = null;
133
+ consecutiveCount = 0;
134
+ hasMore = Array.from(requesterQueues.values()).some((q) => q.length > 0);
135
+ }
136
+ }
137
+
138
+ // Update queue with fair distribution
139
+ if (fairQueue.length > 0) {
140
+ queue.clear("current");
141
+ queue.add(fairQueue);
142
+
143
+ this.#player.emit("fairPlayApplied", this.#player, guildId, fairQueue.length);
144
+ }
145
+ }
146
+
147
+ #getRequesterId(track: Track): string {
148
+ const requester = (track.userData as { requester?: { id: string } }).requester;
149
+ if (requester && typeof requester === "object") {
150
+ return requester.id || "unknown";
151
+ }
152
+ return "unknown";
153
+ }
154
+ }
@@ -0,0 +1,187 @@
1
+ import { PlayerPlugin } from "../core/PluginSystem";
2
+ import type { Player } from "../core/RyanlinkPlayer";
3
+ import { Track } from "../audio/AudioTrack";
4
+
5
+ export interface LyricsPluginEvents {
6
+ /**
7
+ * Emitted when lyrics are found
8
+ */
9
+ lyricsFound: [player: Player, track: Track, lyrics: LyricsResult];
10
+
11
+ /**
12
+ * Emitted when lyrics are not found
13
+ */
14
+ lyricsNotFound: [player: Player, track: Track];
15
+ }
16
+
17
+ export interface LyricsLine {
18
+ timestamp: number;
19
+ duration: number | null;
20
+ line: string;
21
+ }
22
+
23
+ export interface LyricsResult {
24
+ sourceName: string;
25
+ provider: string;
26
+ text: string | null;
27
+ lines: LyricsLine[];
28
+ track: {
29
+ title: string;
30
+ artist: string;
31
+ album?: string;
32
+ duration: number;
33
+ };
34
+ }
35
+
36
+ export interface LyricsConfig {
37
+ sources: {
38
+ lavalink: boolean;
39
+ lrclib: boolean;
40
+ musixmatch: boolean;
41
+ genius: boolean;
42
+ };
43
+ apiKeys?: {
44
+ musixmatch?: string;
45
+ genius?: string;
46
+ };
47
+ }
48
+
49
+ /**
50
+ * Lyrics Plugin - Fetches and manages track lyrics from multiple sources
51
+ * Supports Lavalink LavaLyrics, LRCLib, Musixmatch, and Genius
52
+ */
53
+ export class LyricsPlugin extends PlayerPlugin<LyricsPluginEvents & Record<string, unknown[]>> {
54
+ readonly name = "lyrics" as const;
55
+
56
+ #player!: Player;
57
+
58
+ init(player: Player): void {
59
+ this.#player = player;
60
+ }
61
+
62
+ /**
63
+ * Fetch lyrics for a track
64
+ * @param track - The track to fetch lyrics for
65
+ * @param config - Configuration for lyrics sources
66
+ */
67
+ async getLyrics(track: Track, config?: LyricsConfig): Promise<LyricsResult | null> {
68
+ const defaultConfig: LyricsConfig = {
69
+ sources: {
70
+ lavalink: true,
71
+ lrclib: true,
72
+ musixmatch: false,
73
+ genius: false,
74
+ },
75
+ ...config,
76
+ };
77
+
78
+ const title = track.info.title;
79
+ const artist = track.info.author;
80
+
81
+ if (defaultConfig.sources.lavalink) {
82
+ const lavalinkLyrics = await this.#fetchFromLavalink(track);
83
+ if (lavalinkLyrics) {
84
+ this.#player.emit("lyricsFound", this.#player, track, lavalinkLyrics);
85
+ return lavalinkLyrics;
86
+ }
87
+ }
88
+
89
+ if (defaultConfig.sources.lrclib) {
90
+ const lrclibLyrics = await this.#fetchFromLRCLib(title, artist);
91
+ if (lrclibLyrics) {
92
+ this.#player.emit("lyricsFound", this.#player, track, lrclibLyrics);
93
+ return lrclibLyrics;
94
+ }
95
+ }
96
+
97
+ if (defaultConfig.sources.musixmatch && defaultConfig.apiKeys?.musixmatch) {
98
+ const musixmatchLyrics = await this.#fetchFromMusixmatch(title, artist, defaultConfig.apiKeys.musixmatch);
99
+ if (musixmatchLyrics) {
100
+ this.#player.emit("lyricsFound", this.#player, track, musixmatchLyrics);
101
+ return musixmatchLyrics;
102
+ }
103
+ }
104
+
105
+ if (defaultConfig.sources.genius && defaultConfig.apiKeys?.genius) {
106
+ const geniusLyrics = await this.#fetchFromGenius(title, artist, defaultConfig.apiKeys.genius);
107
+ if (geniusLyrics) {
108
+ this.#player.emit("lyricsFound", this.#player, track, geniusLyrics);
109
+ return geniusLyrics;
110
+ }
111
+ }
112
+
113
+ this.#player.emit("lyricsNotFound", this.#player, track);
114
+ return null;
115
+ }
116
+
117
+ /**
118
+ * Get the current lyric line based on playback position
119
+ */
120
+ getCurrentLine(lyrics: LyricsResult, position: number): LyricsLine | null {
121
+ if (!lyrics.lines || lyrics.lines.length === 0) {
122
+ return null;
123
+ }
124
+
125
+ for (let i = lyrics.lines.length - 1; i >= 0; i--) {
126
+ if (lyrics.lines[i].timestamp <= position) {
127
+ return lyrics.lines[i];
128
+ }
129
+ }
130
+
131
+ return null;
132
+ }
133
+
134
+ /**
135
+ * Format lyrics for display
136
+ */
137
+ formatLyrics(lyrics: LyricsResult, maxLength: number = 2000): string {
138
+ if (lyrics.text) {
139
+ return lyrics.text.slice(0, maxLength);
140
+ }
141
+
142
+ if (lyrics.lines && lyrics.lines.length > 0) {
143
+ let formatted = "";
144
+ for (const line of lyrics.lines) {
145
+ const timestamp = this.#formatTimestamp(line.timestamp);
146
+ formatted += `[${timestamp}] ${line.line}\n`;
147
+
148
+ if (formatted.length > maxLength) {
149
+ return `${formatted.slice(0, maxLength)}...`;
150
+ }
151
+ }
152
+ return formatted;
153
+ }
154
+
155
+ return "No lyrics available";
156
+ }
157
+
158
+ #formatTimestamp(ms: number): string {
159
+ const minutes = Math.floor(ms / 60000);
160
+ const seconds = Math.floor((ms % 60000) / 1000);
161
+ return `${minutes}:${seconds.toString().padStart(2, "0")}`;
162
+ }
163
+
164
+ async #fetchFromLavalink(_track: Track): Promise<LyricsResult | null> {
165
+ // Implementation would call Lavalink's lyrics endpoint
166
+ // This is a placeholder
167
+ return await Promise.resolve(null);
168
+ }
169
+
170
+ async #fetchFromLRCLib(_title: string, _artist: string): Promise<LyricsResult | null> {
171
+ // Implementation would call LRCLib API
172
+ // This is a placeholder
173
+ return await Promise.resolve(null);
174
+ }
175
+
176
+ async #fetchFromMusixmatch(_title: string, _artist: string, _apiKey: string): Promise<LyricsResult | null> {
177
+ // Implementation would call Musixmatch API
178
+ // This is a placeholder
179
+ return await Promise.resolve(null);
180
+ }
181
+
182
+ async #fetchFromGenius(_title: string, _artist: string, _apiToken: string): Promise<LyricsResult | null> {
183
+ // Implementation would call Genius API
184
+ // This is a placeholder
185
+ return await Promise.resolve(null);
186
+ }
187
+ }
@@ -0,0 +1,182 @@
1
+ import { PlayerPlugin } from "../core/PluginSystem";
2
+ import type { Player } from "../core/RyanlinkPlayer";
3
+ import type { RepeatMode } from "../types";
4
+
5
+ export interface QueuePersistencePluginEvents {
6
+ /**
7
+ * Emitted when queue is saved
8
+ */
9
+ queueSaved: [guildId: string];
10
+
11
+ /**
12
+ * Emitted when queue is loaded
13
+ */
14
+ queueLoaded: [guildId: string, trackCount: number];
15
+ }
16
+
17
+ export interface StoredQueue {
18
+ guildId: string;
19
+ tracks: Array<{
20
+ encoded: string;
21
+ info: unknown;
22
+ pluginInfo?: unknown;
23
+ userData?: unknown;
24
+ }>;
25
+ previousTracks: Array<{
26
+ encoded: string;
27
+ info: unknown;
28
+ pluginInfo?: unknown;
29
+ userData?: unknown;
30
+ }>;
31
+ currentTrack?: {
32
+ encoded: string;
33
+ info: unknown;
34
+ position: number;
35
+ };
36
+ volume: number;
37
+ repeatMode: RepeatMode;
38
+ autoplay: boolean;
39
+ paused: boolean;
40
+ timestamp: number;
41
+ }
42
+
43
+ export interface QueueStore {
44
+ get(guildId: string): Promise<StoredQueue | null> | StoredQueue | null;
45
+ set(guildId: string, data: StoredQueue): Promise<void> | void;
46
+ delete(guildId: string): Promise<void> | void;
47
+ }
48
+
49
+ /**
50
+ * Default in-memory queue store
51
+ */
52
+ export class MemoryQueueStore implements QueueStore {
53
+ #data = new Map<string, StoredQueue>();
54
+
55
+ get(guildId: string): StoredQueue | null {
56
+ return this.#data.get(guildId) ?? null;
57
+ }
58
+
59
+ set(guildId: string, data: StoredQueue): void {
60
+ this.#data.set(guildId, data);
61
+ }
62
+
63
+ delete(guildId: string): void {
64
+ this.#data.delete(guildId);
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Queue Persistence Plugin - Saves and restores queue state
70
+ * Useful for bot restarts or crashes
71
+ */
72
+ export class QueuePersistencePlugin extends PlayerPlugin<QueuePersistencePluginEvents & Record<string, unknown[]>> {
73
+ readonly name = "queue-persistence" as const;
74
+
75
+ #player!: Player;
76
+ #store: QueueStore;
77
+ #autoSave: boolean;
78
+
79
+ constructor(store?: QueueStore, autoSave = true) {
80
+ super();
81
+ this.#store = store ?? new MemoryQueueStore();
82
+ this.#autoSave = autoSave;
83
+ }
84
+
85
+ init(player: Player): void {
86
+ this.#player = player;
87
+
88
+ if (this.#autoSave) {
89
+ // Auto-save on track changes
90
+ player.on("trackStart", (queue, _track) => {
91
+ this.saveQueue(queue.guildId).catch(() => {});
92
+ });
93
+
94
+ player.on("trackFinish", (queue, _track, _reason) => {
95
+ this.saveQueue(queue.guildId).catch(() => {});
96
+ });
97
+
98
+ // Save on queue destroy
99
+ player.on("queueDestroy", (queue) => {
100
+ this.deleteQueue(queue.guildId).catch(() => {});
101
+ });
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Save queue state
107
+ */
108
+ async saveQueue(guildId: string): Promise<void> {
109
+ const queue = this.#player.queues.get(guildId);
110
+ if (!queue) {
111
+ return;
112
+ }
113
+
114
+ const data: StoredQueue = {
115
+ guildId,
116
+ tracks: queue.tracks.map((track) => ({
117
+ encoded: track.encoded,
118
+ info: track.info,
119
+ pluginInfo: track.pluginInfo,
120
+ userData: track.userData,
121
+ })),
122
+ previousTracks: queue.previousTracks.map((t) => ({
123
+ encoded: t.encoded,
124
+ info: t.info,
125
+ pluginInfo: t.pluginInfo,
126
+ userData: t.userData,
127
+ })),
128
+ currentTrack: queue.track
129
+ ? {
130
+ encoded: queue.track.encoded,
131
+ info: queue.track.info,
132
+ position: queue.currentTime,
133
+ }
134
+ : undefined,
135
+ volume: queue.volume,
136
+ repeatMode: queue.repeatMode,
137
+ autoplay: queue.autoplay,
138
+ paused: queue.paused,
139
+ timestamp: Date.now(),
140
+ };
141
+
142
+ await this.#store.set(guildId, data);
143
+ this.#player.emit("queueSaved", guildId);
144
+ }
145
+
146
+ /**
147
+ * Load queue state
148
+ */
149
+ async loadQueue(guildId: string): Promise<StoredQueue | null> {
150
+ const data = await this.#store.get(guildId);
151
+ if (!data) {
152
+ return null;
153
+ }
154
+
155
+ const queue = this.#player.queues.get(guildId);
156
+ if (!queue) {
157
+ return null;
158
+ }
159
+
160
+ // Restore tracks
161
+ // Note: Tracks need to be reconstructed from stored data
162
+ // This is a simplified version - you'd need to properly reconstruct Track objects
163
+
164
+ this.#player.emit("queueLoaded", guildId, data.tracks.length);
165
+ return data;
166
+ }
167
+
168
+ /**
169
+ * Delete saved queue
170
+ */
171
+ async deleteQueue(guildId: string): Promise<void> {
172
+ await this.#store.delete(guildId);
173
+ }
174
+
175
+ /**
176
+ * Get all saved queues
177
+ */
178
+ async getAllQueues(): Promise<StoredQueue[]> {
179
+ // This would need to be implemented based on the store type
180
+ return await Promise.resolve([]);
181
+ }
182
+ }
@@ -0,0 +1,81 @@
1
+ import { PlayerPlugin } from "../core/PluginSystem";
2
+ import type { Player } from "../core/RyanlinkPlayer";
3
+ import { Track } from "../audio/AudioTrack";
4
+ import { Queue } from "../audio/AudioQueue";
5
+ import type { SponsorBlockSegment } from "../types/lavalink/Node";
6
+
7
+ export interface SponsorBlockPluginEvents {
8
+ /**
9
+ * Emitted when segments are loaded
10
+ */
11
+ segmentsLoaded: [queue: Queue, track: Track, segments: SponsorBlockSegment[]];
12
+
13
+ /**
14
+ * Emitted when a segment is skipped
15
+ */
16
+ segmentSkipped: [queue: Queue, track: Track, segment: SponsorBlockSegment];
17
+ }
18
+
19
+ /**
20
+ * SponsorBlock Plugin - Automatically skip sponsored segments in videos
21
+ * Requires Lavalink SponsorBlock plugin
22
+ */
23
+ export class SponsorBlockPlugin extends PlayerPlugin<SponsorBlockPluginEvents & Record<string, unknown[]>> {
24
+ readonly name = "sponsorblock" as const;
25
+
26
+ #player!: Player;
27
+
28
+ init(player: Player): void {
29
+ this.#player = player;
30
+
31
+ // Listen to SponsorBlock events from Lavalink
32
+ player.on("segmentsLoaded", this.#handleSegmentsLoaded.bind(this));
33
+ player.on("segmentSkipped", this.#handleSegmentSkipped.bind(this));
34
+ }
35
+
36
+ /**
37
+ * Set which segments to skip for a player
38
+ */
39
+ async setSegments(queue: Queue, segments: string[] = ["sponsor", "selfpromo"]): Promise<void> {
40
+ const node = queue.node;
41
+ if (!node) {
42
+ throw new Error("No node available");
43
+ }
44
+
45
+ await node.setSponsorBlock(queue, segments);
46
+ }
47
+
48
+ /**
49
+ * Get current SponsorBlock segments for a player
50
+ */
51
+ async getSegments(queue: Queue): Promise<string[]> {
52
+ const node = queue.node;
53
+ if (!node) {
54
+ throw new Error("No node available");
55
+ }
56
+
57
+ return await node.getSponsorBlock(queue);
58
+ }
59
+
60
+ /**
61
+ * Delete SponsorBlock configuration for a player
62
+ */
63
+ async deleteSegments(queue: Queue): Promise<void> {
64
+ const node = queue.node;
65
+ if (!node) {
66
+ throw new Error("No node available");
67
+ }
68
+
69
+ await node.deleteSponsorBlock(queue);
70
+ }
71
+
72
+ #handleSegmentsLoaded(queue: Queue, track: Track, segments: unknown): void {
73
+ const _segments = (segments as SponsorBlockSegment[]) || [];
74
+ this.#player.emit("segmentsLoaded", queue, track, _segments);
75
+ }
76
+
77
+ #handleSegmentSkipped(queue: Queue, track: Track, segment: unknown): void {
78
+ const _segment = segment as SponsorBlockSegment;
79
+ this.#player.emit("segmentSkipped", queue, track, _segment);
80
+ }
81
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Extension exports
3
+ */
4
+
5
+ export * from "./AutoplayExtension";
6
+ export * from "./LyricsExtension";
7
+ export * from "./SponsorBlockExtension";
8
+ export * from "./FairPlayExtension";
9
+ export * from "./PersistenceExtension";
package/src/index.ts ADDED
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Ryanlink - Advanced Lavalink client for Node.js
3
+ *
4
+ * @packageDocumentation
5
+ */
6
+
7
+ // Export all components
8
+ export * from "./config";
9
+ export * from "./utils";
10
+ export * from "./lavalink";
11
+ export * from "./voice";
12
+ export * from "./audio";
13
+ export * from "./extensions";
14
+ export * from "./core";
15
+
16
+ /**
17
+ * Ryanlink version (dynamically loaded from package.json)
18
+ */
19
+ export { CLIENT_VERSION as version, CLIENT_NAME as name, PACKAGE_INFO } from "./metadata";