Skip to content

Commit 0934791

Browse files
authored
Merge pull request #48 from appujet/main
Added Live Lyrics Events and Added JioSaavn in DefaultSources
2 parents e3dc7ad + 027cea7 commit 0934791

File tree

9 files changed

+350
-12
lines changed

9 files changed

+350
-12
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@ testBot/node_modules
55
testBot/src
66
.vscode
77
bun.lockb
8-
package-lock.json
8+
package-lock.json

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,4 +71,4 @@
7171
"node": ">=18.0.0",
7272
"bun": ">=1.0.0"
7373
}
74-
}
74+
}

src/structures/LavalinkManagerStatics.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,12 @@ export const DefaultSources: Record<SearchPlatform, LavalinkSearchPlatform | Cli
6666
"http": "http",
6767
"https": "https",
6868
"link": "link",
69-
"uri": "uri"
69+
"uri": "uri",
70+
// jiosaavn
71+
"jiosaavn": "jssearch",
72+
"js": "jssearch",
73+
"jssearch": "jssearch",
74+
"jsrec": "jsrec"
7075
}
7176

7277
/** Lavalink Plugins definiton */
@@ -75,7 +80,9 @@ export const LavalinkPlugins = {
7580
LavaSrc: "lavasrc-plugin",
7681
GoogleCloudTTS: "tts-plugin",
7782
LavaSearch: "lavasearch-plugin",
78-
LavalinkFilterPlugin: "lavalink-filter-plugin"
83+
Jiosaavn_Plugin: "jiosaavn-plugin",
84+
LavalinkFilterPlugin: "lavalink-filter-plugin",
85+
JavaTimedLyricsPlugin: "java-lyrics-plugin"
7986
}
8087

8188
/** Lavalink Sources regexes for url validations */
@@ -120,6 +127,9 @@ export const SourceLinksRegexes: Record<SourcesRegex, RegExp> = {
120127

121128
appleMusic: /https?:\/\/?(?:www\.)?music\.apple\.com\/(\S+)/,
122129

130+
/** From jiosaavn-plugin */
131+
jiosaavn: /(https?:\/\/)(www\.)?jiosaavn\.com\/(?<type>song|album|featured|artist)\/([a-zA-Z0-9-_\/,]+)/,
132+
123133
/** FROM DUNCTE BOT PLUGIN */
124134
tiktok: /https:\/\/www\.tiktok\.com\//,
125135
mixcloud: /https:\/\/www\.mixcloud\.com\//,

src/structures/Node.ts

Lines changed: 155 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ import { NodeSymbol, queueTrackEnd } from "./Utils";
77
import type { Player } from "./Player";
88
import type { DestroyReasonsType } from "./Types/Player";
99
import type { LavalinkTrack, PluginInfo, Track } from "./Types/Track";
10-
import type { Base64, InvalidLavalinkRestRequest, LavalinkPlayer, LavaSearchQuery, LavaSearchResponse, LoadTypes, PlayerEvents, PlayerEventType, PlayerUpdateInfo, RoutePlanner, SearchQuery, SearchResult, Session, SponsorBlockChaptersLoaded, SponsorBlockChapterStarted, SponsorBlockSegmentSkipped, SponsorBlockSegmentsLoaded, TrackEndEvent, TrackExceptionEvent, TrackStartEvent, TrackStuckEvent, WebSocketClosedEvent } from "./Types/Utils";
10+
import type { Base64, InvalidLavalinkRestRequest, LavalinkPlayer, LavaSearchQuery, LavaSearchResponse, LoadTypes, LyricsFoundEvent, LyricsLineEvent, LyricsNotFoundEvent, PlayerEvents, PlayerEventType, PlayerUpdateInfo, RoutePlanner, SearchQuery, SearchResult, Session, SponsorBlockChaptersLoaded, SponsorBlockChapterStarted, SponsorBlockSegmentSkipped, SponsorBlockSegmentsLoaded, TrackEndEvent, TrackExceptionEvent, TrackStartEvent, TrackStuckEvent, WebSocketClosedEvent } from "./Types/Utils";
1111
import type { NodeManager } from "./NodeManager";
1212

1313
import type {
14-
BaseNodeStats, LavalinkInfo, LavalinkNodeOptions, ModifyRequest, NodeStats, SponsorBlockSegment
14+
BaseNodeStats, LavalinkInfo, LavalinkNodeOptions, LyricsResult, ModifyRequest, NodeStats, SponsorBlockSegment
1515
} from "./Types/Node";
1616
/**
1717
* Lavalink Node creator class
@@ -637,6 +637,124 @@ export class LavalinkNode {
637637
}
638638
}
639639

640+
lyrics = {
641+
/**
642+
* Get the lyrics of a track
643+
* @param track the track to get the lyrics for
644+
* @param skipTrackSource wether to skip the track source or not
645+
* @returns the lyrics of the track
646+
* @example
647+
*
648+
* ```ts
649+
* const lyrics = await player.node.lyrics.get(track, true);
650+
* // use it of player instead:
651+
* // const lyrics = await player.getLyrics(track, true);
652+
* ```
653+
*/
654+
get: async (track: Track, skipTrackSource: boolean = false) => {
655+
if (!this.sessionId) throw new Error("the Lavalink-Node is either not ready, or not up to date!");
656+
657+
if(
658+
!this.info.plugins.find(v => v.name === "lavalyrics-plugin")
659+
) throw new RangeError(`there is no lavalyrics-plugin available in the lavalink node (required for lyrics): ${this.id}`);
660+
661+
if(
662+
!this.info.plugins.find(v => v.name === "lavasrc-plugin") &&
663+
!this.info.plugins.find(v => v.name === "java-lyrics-plugin")
664+
) throw new RangeError(`there is no lyrics source (via lavasrc-plugin / java-lyrics-plugin) available in the lavalink node (required for lyrics): ${this.id}`);
665+
666+
const url = `/lyrics?track=${track.encoded}&skipTrackSource=${skipTrackSource}`;
667+
return (await this.request(url)) as LyricsResult | null;
668+
},
669+
670+
/**
671+
* Get the lyrics of the current playing track
672+
*
673+
* @param guildId the guild id of the player
674+
* @param skipTrackSource wether to skip the track source or not
675+
* @returns the lyrics of the current playing track
676+
* @example
677+
* ```ts
678+
* const lyrics = await player.node.lyrics.getCurrent(guildId);
679+
* // use it of player instead:
680+
* // const lyrics = await player.getCurrentLyrics();
681+
* ```
682+
*/
683+
getCurrent: async (guildId: string, skipTrackSource: boolean = false) => {
684+
if (!this.sessionId) throw new Error("the Lavalink-Node is either not ready, or not up to date!");
685+
686+
if(
687+
!this.info.plugins.find(v => v.name === "lavalyrics-plugin")
688+
) throw new RangeError(`there is no lavalyrics-plugin available in the lavalink node (required for lyrics): ${this.id}`);
689+
690+
if(
691+
!this.info.plugins.find(v => v.name === "lavasrc-plugin") &&
692+
!this.info.plugins.find(v => v.name === "java-lyrics-plugin")
693+
) throw new RangeError(`there is no lyrics source (via lavasrc-plugin / java-lyrics-plugin) available in the lavalink node (required for lyrics): ${this.id}`);
694+
695+
const url = `/sessions/${this.sessionId}/players/${guildId}/track/lyrics?skipTrackSource=${skipTrackSource}`;
696+
return (await this.request(url)) as LyricsResult | null;
697+
},
698+
699+
/**
700+
* subscribe to lyrics updates for a guild
701+
* @param guildId the guild id of the player
702+
* @returns request data of the request
703+
*
704+
* @example
705+
* ```ts
706+
* await player.node.lyrics.subscribe(guildId);
707+
* // use it of player instead:
708+
* // const lyrics = await player.subscribeLyrics();
709+
* ```
710+
*/
711+
712+
subscribe: async (guildId: string) => {
713+
if (!this.sessionId) throw new Error("the Lavalink-Node is either not ready, or not up to date!");
714+
715+
if(
716+
!this.info.plugins.find(v => v.name === "lavalyrics-plugin")
717+
) throw new RangeError(`there is no lavalyrics-plugin available in the lavalink node (required for lyrics): ${this.id}`);
718+
719+
if(
720+
!this.info.plugins.find(v => v.name === "lavasrc-plugin") &&
721+
!this.info.plugins.find(v => v.name === "java-lyrics-plugin")
722+
) throw new RangeError(`there is no lyrics source (via lavasrc-plugin / java-lyrics-plugin) available in the lavalink node (required for lyrics): ${this.id}`);
723+
724+
return await this.request(`/sessions/${this.sessionId}/players/${guildId}/lyrics/subscribe`, (options) => {
725+
options.method = "POST";
726+
}).catch(() => { });
727+
},
728+
/**
729+
* unsubscribe from lyrics updates for a guild
730+
* @param guildId the guild id of the player
731+
* @returns request data of the request
732+
*
733+
* @example
734+
* ```ts
735+
* await player.node.lyrics.unsubscribe(guildId);
736+
* // use it of player instead:
737+
* // const lyrics = await player.unsubscribeLyrics();
738+
* ```
739+
*/
740+
unsubscribe: async (guildId: string) => {
741+
if (!this.sessionId) throw new Error("the Lavalink-Node is either not ready, or not up to date!");
742+
743+
if(
744+
!this.info.plugins.find(v => v.name === "lavalyrics-plugin")
745+
) throw new RangeError(`there is no lavalyrics-plugin available in the lavalink node (required for lyrics): ${this.id}`);
746+
747+
if(
748+
!this.info.plugins.find(v => v.name === "lavasrc-plugin") &&
749+
!this.info.plugins.find(v => v.name === "java-lyrics-plugin")
750+
) throw new RangeError(`there is no lyrics source (via lavasrc-plugin / java-lyrics-plugin) available in the lavalink node (required for lyrics): ${this.id}`);
751+
752+
return await this.request(`/sessions/${this.sessionId}/players/${guildId}/lyrics/unsubscribe`, (options) => {
753+
options.method = "DELETE";
754+
}).catch(() => { });
755+
},
756+
};
757+
640758
/**
641759
* Request Lavalink statistics.
642760
* @returns the lavalink node stats
@@ -1017,6 +1135,9 @@ export class LavalinkNode {
10171135
case "SegmentSkipped": this.SponsorBlockSegmentSkipped(player, player.queue.current as Track, payload); break;
10181136
case "ChaptersLoaded": this.SponsorBlockChaptersLoaded(player, player.queue.current as Track, payload); break;
10191137
case "ChapterStarted": this.SponsorBlockChapterStarted(player, player.queue.current as Track, payload); break;
1138+
case "LyricsLineEvent": this.LyricsLine(player, player.queue.current as Track, payload); break;
1139+
case "LyricsFoundEvent": this.LyricsFound(player, player.queue.current as Track, payload); break;
1140+
case "LyricsNotFoundEvent": this.LyricsNotFound(player, player.queue.current as Track, payload); break;
10201141
default: this.NodeManager.emit("error", this, new Error(`Node#event unknown event '${(payload as PlayerEventType & PlayerEvents).type}'.`), (payload as PlayerEventType & PlayerEvents)); break;
10211142
}
10221143
return;
@@ -1369,4 +1490,36 @@ export class LavalinkNode {
13691490

13701491
return this.NodeManager.LavalinkManager.emit("queueEnd", player, track, payload);
13711492
}
1493+
1494+
/**
1495+
* Emitted whenever a line of lyrics gets emitted
1496+
* @event
1497+
* @param {Player} player The player that emitted the event
1498+
* @param {Track} track The track that emitted the event
1499+
* @param {LyricsLineEvent} payload The payload of the event
1500+
*/
1501+
private LyricsLine(player: Player, track: Track, payload: LyricsLineEvent) {
1502+
return this.NodeManager.LavalinkManager.emit("LyricsLine", player, track, payload);
1503+
}
1504+
1505+
/**
1506+
* Emitted whenever the lyrics for a track got found
1507+
* @event
1508+
* @param {Player} player The player that emitted the event
1509+
* @param {Track} track The track that emitted the event
1510+
* @param {LyricsFoundEvent} payload The payload of the event
1511+
*/
1512+
private LyricsFound(player: Player, track: Track, payload: LyricsFoundEvent) {
1513+
return this.NodeManager.LavalinkManager.emit("LyricsFound", player, track, payload);
1514+
}
1515+
/**
1516+
* Emitted whenever the lyrics for a track got not found
1517+
* @event
1518+
* @param {Player} player The player that emitted the event
1519+
* @param {Track} track The track that emitted the event
1520+
* @param {LyricsNotFoundEvent} payload The payload of the event
1521+
*/
1522+
private LyricsNotFound(player: Player, track: Track, payload: LyricsNotFoundEvent) {
1523+
return this.NodeManager.LavalinkManager.emit("LyricsNotFound", player, track, payload);
1524+
}
13721525
}

src/structures/Player.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -636,6 +636,60 @@ export class Player {
636636
return this;
637637
}
638638

639+
/**
640+
* Get the current lyrics of the track currently playing on the guild
641+
* @param guildId The guild id to get the current lyrics for
642+
* @param skipTrackSource If true, it will not try to get the lyrics from the track source
643+
* @returns The current lyrics
644+
* @example
645+
* ```ts
646+
* const lyrics = await player.getCurrentLyrics();
647+
* ```
648+
*/
649+
public async getCurrentLyrics(skipTrackSource?: boolean) {
650+
return await this.node.lyrics.getCurrent(this.guildId, skipTrackSource);
651+
}
652+
653+
/**
654+
* Get the lyrics of a specific track
655+
* @param track The track to get the lyrics for
656+
* @param skipTrackSource If true, it will not try to get the lyrics from the track source
657+
* @returns The lyrics of the track
658+
* @example
659+
* ```ts
660+
* const lyrics = await player.getLyrics(player.queue.tracks[0], true);
661+
* ```
662+
*/
663+
public async getLyrics(track: Track, skipTrackSource?: boolean) {
664+
return await this.node.lyrics.get(track, skipTrackSource);
665+
}
666+
667+
/**
668+
* Subscribe to the lyrics event on a specific guild to active live lyrics events
669+
* @param guildId The guild id to subscribe to
670+
* @returns The unsubscribe function
671+
* @example
672+
* ```ts
673+
* const lyrics = await player.subscribeLyrics();
674+
* ```
675+
*/
676+
public subscribeLyrics() {
677+
return this.node.lyrics.subscribe(this.guildId);
678+
}
679+
680+
/**
681+
* Unsubscribe from the lyrics event on a specific guild to disable live lyrics events
682+
* @param guildId The guild id to unsubscribe from
683+
* @returns The unsubscribe function
684+
* @example
685+
* ```ts
686+
* const lyrics = await player.unsubscribeLyrics();
687+
* ```
688+
*/
689+
public unsubscribeLyrics(guildId: string) {
690+
return this.node.lyrics.unsubscribe(guildId);
691+
}
692+
639693
/**
640694
* Move the player on a different Audio-Node
641695
* @param newNode New Node / New Node Id

src/structures/Types/Manager.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import type { LavalinkNodeOptions } from "./Node";
55
import type { DestroyReasonsType, PlayerJson } from "./Player";
66
import type { ManagerQueueOptions } from "./Queue";
77
import type { Track, UnresolvedTrack } from "./Track";
8-
import type { GuildShardPayload, SearchPlatform, SponsorBlockChaptersLoaded, SponsorBlockChapterStarted, SponsorBlockSegmentSkipped, SponsorBlockSegmentsLoaded, TrackExceptionEvent, TrackEndEvent, TrackStuckEvent, WebSocketClosedEvent, TrackStartEvent } from "./Utils";
8+
import type { GuildShardPayload, SearchPlatform, SponsorBlockChaptersLoaded, SponsorBlockChapterStarted, SponsorBlockSegmentSkipped, SponsorBlockSegmentsLoaded, TrackExceptionEvent, TrackEndEvent, TrackStuckEvent, WebSocketClosedEvent, TrackStartEvent, LyricsFoundEvent, LyricsNotFoundEvent, LyricsLineEvent } from "./Utils";
99

1010
/**
1111
* The events from the lavalink Manager
@@ -117,7 +117,28 @@ export interface LavalinkManagerEvents {
117117
*
118118
* @event Manager#debug
119119
*/
120-
"debug": (eventKey: DebugEvents, eventData: { message: string, state: "log" | "warn" | "error", error?: Error|string, functionLayer: string }) => void;
120+
"debug": (eventKey: DebugEvents, eventData: { message: string, state: "log" | "warn" | "error", error?: Error | string, functionLayer: string }) => void;
121+
122+
/**
123+
* Emitted when a Lyrics line is received
124+
* @link https://github.com/topi314/LavaLyrics
125+
* @event Manager#LyricsLine
126+
*/
127+
"LyricsLine": (player: Player, track: Track | UnresolvedTrack | null, payload: LyricsLineEvent) => void;
128+
129+
/**
130+
* Emitted when a Lyrics is found
131+
* @link https://github.com/topi314/LavaLyrics
132+
* @event Manager#LyricsFound
133+
*/
134+
"LyricsFound": (player: Player, track: Track | UnresolvedTrack | null, payload: LyricsFoundEvent) => void;
135+
136+
/**
137+
* Emitted when a Lyrics is not found
138+
* @link https://github.com/topi314/LavaLyrics
139+
* @event Manager#LyricsNotFound
140+
*/
141+
"LyricsNotFound": (player: Player, track: Track | UnresolvedTrack | null, payload: LyricsNotFoundEvent) => void;
121142
}
122143
/**
123144
* The Bot client Options needed for the manager

src/structures/Types/Node.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type internal from "stream";
22
import type { LavalinkNode } from "../Node";
33
import type { DestroyReasonsType } from "./Player";
44
import type { InvalidLavalinkRestRequest, LavalinkPlayer } from "./Utils";
5+
import { PluginInfo } from "./Track";
56

67
/** Ability to manipulate fetch requests */
78
export type ModifyRequest = (options: RequestInit & { path: string, extraQueryUrlParams?: URLSearchParams }) => void;
@@ -166,6 +167,29 @@ export interface PluginObject {
166167
version: string;
167168
}
168169

170+
export interface LyricsResult {
171+
/**The name of the source */
172+
sourceName: string;
173+
/**The name of the provider */
174+
provider: string;
175+
/**The result text */
176+
text: string | null;
177+
/**The lyrics lines */
178+
lines: LyricsLine[];
179+
/**Information about the plugin */
180+
plugin: PluginInfo;
181+
}
182+
183+
export interface LyricsLine {
184+
/**The millisecond timestamp */
185+
timestamp: number;
186+
/**The line duration in milliseconds */
187+
duration: number | null;
188+
/**The line text */
189+
line: string;
190+
/**Information about the plugin */
191+
plugin: PluginInfo;
192+
}
169193
export type LavalinkNodeIdentifier = string;
170194

171195
export interface NodeManagerEvents {

0 commit comments

Comments
 (0)