|
@@ -11,44 +11,32 @@ let mcCurrentSongTitle = "";
|
|
let mcLyricsButtonAddTries = 0;
|
|
let mcLyricsButtonAddTries = 0;
|
|
|
|
|
|
/** Adds a lyrics button to the media controls bar */
|
|
/** Adds a lyrics button to the media controls bar */
|
|
-export async function addMediaCtrlLyricsBtn(): Promise<unknown> {
|
|
|
|
|
|
+export function addMediaCtrlLyricsBtn(): void {
|
|
const likeContainer = document.querySelector(".middle-controls-buttons ytmusic-like-button-renderer#like-button-renderer") as HTMLElement;
|
|
const likeContainer = document.querySelector(".middle-controls-buttons ytmusic-like-button-renderer#like-button-renderer") as HTMLElement;
|
|
|
|
|
|
if(!likeContainer) {
|
|
if(!likeContainer) {
|
|
mcLyricsButtonAddTries++;
|
|
mcLyricsButtonAddTries++;
|
|
- if(mcLyricsButtonAddTries < triesLimit)
|
|
|
|
- return setTimeout(addMediaCtrlLyricsBtn, triesInterval); // TODO: improve this
|
|
|
|
|
|
+ if(mcLyricsButtonAddTries < triesLimit) {
|
|
|
|
+ setTimeout(addMediaCtrlLyricsBtn, triesInterval); // TODO: improve this
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
|
|
return error(`Couldn't find element to append lyrics buttons to after ${mcLyricsButtonAddTries} tries`);
|
|
return error(`Couldn't find element to append lyrics buttons to after ${mcLyricsButtonAddTries} tries`);
|
|
}
|
|
}
|
|
|
|
|
|
const songTitleElem = document.querySelector(".content-info-wrapper > yt-formatted-string") as HTMLDivElement;
|
|
const songTitleElem = document.querySelector(".content-info-wrapper > yt-formatted-string") as HTMLDivElement;
|
|
|
|
|
|
|
|
+ // run parallel without awaiting so the MutationObserver below can observe the title element in time
|
|
|
|
+ (async () => {
|
|
|
|
+ const gUrl = await getCurrentLyricsUrl();
|
|
|
|
|
|
- const gUrl = await getCurrentGeniusUrl();
|
|
|
|
-
|
|
|
|
- const linkElem = document.createElement("a");
|
|
|
|
- linkElem.id = "betterytm-lyrics-button";
|
|
|
|
- linkElem.className = "ytmusic-player-bar";
|
|
|
|
- linkElem.title = gUrl ? "Click to open this song's lyrics in a new tab" : "Loading...";
|
|
|
|
- if(gUrl)
|
|
|
|
- linkElem.href = gUrl;
|
|
|
|
- linkElem.target = "_blank";
|
|
|
|
- linkElem.rel = "noopener noreferrer";
|
|
|
|
- linkElem.style.visibility = gUrl ? "initial" : "hidden";
|
|
|
|
- linkElem.style.display = gUrl ? "inline-flex" : "none";
|
|
|
|
-
|
|
|
|
-
|
|
|
|
- const imgElem = document.createElement("img");
|
|
|
|
- imgElem.id = "betterytm-lyrics-img";
|
|
|
|
- imgElem.src = "https://raw.githubusercontent.com/Sv443/BetterYTM/main/assets/external/genius.png";
|
|
|
|
-
|
|
|
|
- linkElem.appendChild(imgElem);
|
|
|
|
|
|
+ const linkElem = getLyricsBtn(gUrl ?? undefined);
|
|
|
|
+ linkElem.id = "betterytm-lyrics-button";
|
|
|
|
|
|
- log(`Inserted lyrics button after ${mcLyricsButtonAddTries} tries:`, linkElem);
|
|
|
|
-
|
|
|
|
- insertAfter(likeContainer, linkElem);
|
|
|
|
|
|
+ log(`Inserted lyrics button after ${mcLyricsButtonAddTries} tries:`, linkElem);
|
|
|
|
|
|
|
|
+ insertAfter(likeContainer, linkElem);
|
|
|
|
+ })();
|
|
|
|
|
|
mcCurrentSongTitle = songTitleElem.title;
|
|
mcCurrentSongTitle = songTitleElem.title;
|
|
|
|
|
|
@@ -56,7 +44,7 @@ export async function addMediaCtrlLyricsBtn(): Promise<unknown> {
|
|
for await(const mut of mutations) {
|
|
for await(const mut of mutations) {
|
|
const newTitle = (mut.target as HTMLElement).title;
|
|
const newTitle = (mut.target as HTMLElement).title;
|
|
|
|
|
|
- if(newTitle != mcCurrentSongTitle && newTitle.length > 0) {
|
|
|
|
|
|
+ if(newTitle !== mcCurrentSongTitle && newTitle.length > 0) {
|
|
const lyricsBtn = document.querySelector("#betterytm-lyrics-button") as HTMLAnchorElement;
|
|
const lyricsBtn = document.querySelector("#betterytm-lyrics-button") as HTMLAnchorElement;
|
|
|
|
|
|
if(!lyricsBtn)
|
|
if(!lyricsBtn)
|
|
@@ -69,13 +57,13 @@ export async function addMediaCtrlLyricsBtn(): Promise<unknown> {
|
|
|
|
|
|
mcCurrentSongTitle = newTitle;
|
|
mcCurrentSongTitle = newTitle;
|
|
|
|
|
|
- const url = await getCurrentGeniusUrl(); // can take a second or two
|
|
|
|
|
|
+ const url = await getCurrentLyricsUrl(); // can take a second or two
|
|
if(!url)
|
|
if(!url)
|
|
continue;
|
|
continue;
|
|
|
|
|
|
lyricsBtn.href = url;
|
|
lyricsBtn.href = url;
|
|
|
|
|
|
- lyricsBtn.title = "Click to open this song's lyrics in a new tab";
|
|
|
|
|
|
+ lyricsBtn.title = "Open the current song's lyrics in a new tab";
|
|
lyricsBtn.style.cursor = "pointer";
|
|
lyricsBtn.style.cursor = "pointer";
|
|
lyricsBtn.style.visibility = "initial";
|
|
lyricsBtn.style.visibility = "initial";
|
|
lyricsBtn.style.display = "inline-flex";
|
|
lyricsBtn.style.display = "inline-flex";
|
|
@@ -90,46 +78,48 @@ export async function addMediaCtrlLyricsBtn(): Promise<unknown> {
|
|
obs.observe(songTitleElem, { attributes: true, attributeFilter: [ "title" ] });
|
|
obs.observe(songTitleElem, { attributes: true, attributeFilter: [ "title" ] });
|
|
}
|
|
}
|
|
|
|
|
|
-/** Returns the lyrics URL from genius for the current song */
|
|
|
|
-export async function getCurrentGeniusUrl() {
|
|
|
|
- try {
|
|
|
|
- // In videos the video title contains both artist and song title, in "regular" YTM songs, the video title only contains the song title
|
|
|
|
- const isVideo = typeof document.querySelector("ytmusic-player")?.getAttribute("video-mode_") === "string";
|
|
|
|
|
|
+/** Removes everything in parentheses from the passed song name */
|
|
|
|
+export function sanitizeSong(songName: string) {
|
|
|
|
+ const parensRegex = /\(.+\)/gmi;
|
|
|
|
+ const squareParensRegex = /\[.+\]/gmi;
|
|
|
|
|
|
- const songTitleElem = document.querySelector(".content-info-wrapper > yt-formatted-string") as HTMLElement;
|
|
|
|
- const songMetaElem = document.querySelector("span.subtitle > yt-formatted-string:first-child") as HTMLElement;
|
|
|
|
|
|
+ // trim right after the song name:
|
|
|
|
+ const sanitized = songName
|
|
|
|
+ .replace(parensRegex, "")
|
|
|
|
+ .replace(squareParensRegex, "");
|
|
|
|
|
|
- if(!songTitleElem || !songMetaElem || !songTitleElem.title)
|
|
|
|
- return null;
|
|
|
|
|
|
+ return sanitized.trim();
|
|
|
|
+}
|
|
|
|
|
|
- const sanitizeSongName = (songName: string) => {
|
|
|
|
- const parensRegex = /\(.+\)/gmi;
|
|
|
|
- const squareParensRegex = /\[.+\]/gmi;
|
|
|
|
|
|
+/** Removes the secondary artist (if it exists) from the passed artists string */
|
|
|
|
+export function sanitizeArtists(artists: string) {
|
|
|
|
+ artists = artists.split(/\s*\u2022\s*/gmiu)[0]; // split at • [•] character
|
|
|
|
|
|
- // trim right after the song name:
|
|
|
|
- const sanitized = songName
|
|
|
|
- .replace(parensRegex, "")
|
|
|
|
- .replace(squareParensRegex, "");
|
|
|
|
|
|
+ if(artists.match(/&/))
|
|
|
|
+ artists = artists.split(/\s*&\s*/gm)[0];
|
|
|
|
|
|
- return sanitized.trim();
|
|
|
|
- };
|
|
|
|
|
|
+ if(artists.match(/,/))
|
|
|
|
+ artists = artists.split(/,\s*/gm)[0];
|
|
|
|
|
|
- const splitArtist = (songMeta: string) => {
|
|
|
|
- songMeta = songMeta.split(/\s*\u2022\s*/gmiu)[0]; // split at bullet (• / •) character
|
|
|
|
|
|
+ return artists.trim();
|
|
|
|
+}
|
|
|
|
|
|
- if(songMeta.match(/&/))
|
|
|
|
- songMeta = songMeta.split(/\s*&\s*/gm)[0];
|
|
|
|
|
|
+/** Returns the lyrics URL from genius for the currently selected song */
|
|
|
|
+export async function getCurrentLyricsUrl() {
|
|
|
|
+ try {
|
|
|
|
+ // In videos the video title contains both artist and song title, in "regular" YTM songs, the video title only contains the song title
|
|
|
|
+ const isVideo = typeof document.querySelector("ytmusic-player")?.getAttribute("video-mode_") === "string";
|
|
|
|
|
|
- if(songMeta.match(/,/))
|
|
|
|
- songMeta = songMeta.split(/,\s*/gm)[0];
|
|
|
|
|
|
+ const songTitleElem = document.querySelector(".content-info-wrapper > yt-formatted-string") as HTMLElement;
|
|
|
|
+ const songMetaElem = document.querySelector("span.subtitle > yt-formatted-string:first-child") as HTMLElement;
|
|
|
|
|
|
- return songMeta.trim();
|
|
|
|
- };
|
|
|
|
|
|
+ if(!songTitleElem || !songMetaElem || !songTitleElem.title)
|
|
|
|
+ return null;
|
|
|
|
|
|
const songNameRaw = songTitleElem.title;
|
|
const songNameRaw = songTitleElem.title;
|
|
- const songName = sanitizeSongName(songNameRaw);
|
|
|
|
|
|
+ const songName = sanitizeSong(songNameRaw);
|
|
|
|
|
|
- const artistName = splitArtist(songMetaElem.title);
|
|
|
|
|
|
+ const artistName = sanitizeArtists(songMetaElem.title);
|
|
|
|
|
|
/** Use when the current song is not a "real YTM song" with a static background, but rather a music video */
|
|
/** Use when the current song is not a "real YTM song" with a static background, but rather a music video */
|
|
const getGeniusUrlVideo = async () => {
|
|
const getGeniusUrlVideo = async () => {
|
|
@@ -155,10 +145,10 @@ export async function getCurrentGeniusUrl() {
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
/**
|
|
- * @param artist
|
|
|
|
- * @param song
|
|
|
|
- */
|
|
|
|
-async function getGeniusUrl(artist: string, song: string): Promise<string | undefined> {
|
|
|
|
|
|
+ * @param artist
|
|
|
|
+ * @param song
|
|
|
|
+ */
|
|
|
|
+export async function getGeniusUrl(artist: string, song: string): Promise<string | undefined> {
|
|
try {
|
|
try {
|
|
const startTs = Date.now();
|
|
const startTs = Date.now();
|
|
const fetchUrl = `${geniURLSearchTopUrl}?artist=${encodeURIComponent(artist)}&song=${encodeURIComponent(song)}`;
|
|
const fetchUrl = `${geniURLSearchTopUrl}?artist=${encodeURIComponent(artist)}&song=${encodeURIComponent(song)}`;
|
|
@@ -183,3 +173,23 @@ async function getGeniusUrl(artist: string, song: string): Promise<string | unde
|
|
return undefined;
|
|
return undefined;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+export function getLyricsBtn(geniusUrl?: string, hideIfLoading = true): HTMLAnchorElement {
|
|
|
|
+ const linkElem = document.createElement("a");
|
|
|
|
+ linkElem.className = "ytmusic-player-bar bytm-generic-lyrics-btn";
|
|
|
|
+ linkElem.title = geniusUrl ? "Click to open this song's lyrics in a new tab" : "Loading...";
|
|
|
|
+ if(geniusUrl)
|
|
|
|
+ linkElem.href = geniusUrl;
|
|
|
|
+ linkElem.target = "_blank";
|
|
|
|
+ linkElem.rel = "noopener noreferrer";
|
|
|
|
+ linkElem.style.visibility = hideIfLoading && geniusUrl ? "initial" : "hidden";
|
|
|
|
+ linkElem.style.display = hideIfLoading && geniusUrl ? "inline-flex" : "none";
|
|
|
|
+
|
|
|
|
+ const imgElem = document.createElement("img");
|
|
|
|
+ imgElem.className = "betterytm-lyrics-img";
|
|
|
|
+ imgElem.src = "https://raw.githubusercontent.com/Sv443/BetterYTM/main/assets/external/genius.png";
|
|
|
|
+
|
|
|
|
+ linkElem.appendChild(imgElem);
|
|
|
|
+
|
|
|
|
+ return linkElem;
|
|
|
|
+}
|