feat(musicControls): add lyrics custom provider settings (#931)

* feat(musicControls): add lyrics custom provider settings

* 😳
This commit is contained in:
Ape 2026-03-27 13:50:32 +01:00 • committed by GitHub
parent 0d0e749f96
commit 8ad745d5cb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 81 additions and 9 deletions

View file

@ -6,10 +6,12 @@
import { definePluginSettings } from "@api/Settings";
import { disableStyle, enableStyle } from "@api/Styles";
import { ButtonCompat } from "@components/Button";
import { HeadingSecondary } from "@components/Heading";
import { Paragraph } from "@components/Paragraph";
import { SettingsSection } from "@components/settings/tabs/plugins/components/Common";
import { makeRange, OptionType } from "@utils/types";
import { Button, MaskedLink, showToast, Toasts } from "@webpack/common";
import { MaskedLink, Select, showToast, TextInput, Toasts } from "@webpack/common";
import hoverOnlyStyle from "./hoverOnly.css?managed";
import { clearLyricsCache, removeTranslations } from "./spotify/lyrics/api";
@ -36,6 +38,46 @@ function InstallInstructions() {
);
}
function LyricsProviderSettings() {
const { store } = settings;
return (
<>
<SettingsSection name="Lyrics Provider" description="Where lyrics are fetched from.">
<Select
options={[
{ value: Provider.Lrclib, label: "LRCLIB", default: true },
{ value: Provider.Spotify, label: "Spotify (Musixmatch)" },
]}
isSelected={v => v === store.lyricsProvider}
select={v => { store.lyricsProvider = v as Provider; }}
serialize={v => v}
placeholder="Select a lyrics provider"
/>
</SettingsSection>
{store.lyricsProvider === Provider.Spotify && (
<SettingsSection
name="Spotify Lyrics API Base URL"
description="Custom instance base URL (for example: http://localhost:8080)."
>
<TextInput
type="text"
value={store.spotifyLyricsApiUrl}
onChange={v => {
store.spotifyLyricsApiUrl = v;
void clearLyricsCache();
showToast("Lyrics cache purged", Toasts.Type.SUCCESS);
}}
placeholder="https://spotify-lyrics-api-pi.vercel.app"
maxLength={null}
/>
</SettingsSection>
)}
</>
);
}
export const settings = definePluginSettings({
hoverControls: {
description: "Show controls on hover",
@ -60,9 +102,24 @@ export const settings = definePluginSettings({
description: "Where lyrics are fetched from",
type: OptionType.SELECT,
options: [
{ value: Provider.Spotify, label: "Spotify (Musixmatch)", default: true },
{ value: Provider.Lrclib, label: "LRCLIB" },
{ value: Provider.Lrclib, label: "LRCLIB", default: true },
{ value: Provider.Spotify, label: "Spotify (Musixmatch)" },
],
hidden: true,
},
spotifyLyricsApiUrl: {
type: OptionType.STRING,
description: "Spotify lyrics API base URL.",
hidden: true,
default: "https://spotify-lyrics-api-pi.vercel.app",
onChange: async () => {
await clearLyricsCache();
showToast("Lyrics cache purged", Toasts.Type.SUCCESS);
}
},
lyricsProviderSettings: {
type: OptionType.COMPONENT,
component: LyricsProviderSettings,
},
translateTo: {
description: "Translate lyrics to - Changing this will clear existing translations",
@ -102,15 +159,15 @@ export const settings = definePluginSettings({
description: "Purge the lyrics cache",
type: OptionType.COMPONENT,
component: () => (
<Button
color={Button.Colors.RED}
<ButtonCompat
color={ButtonCompat.Colors.RED}
onClick={() => {
clearLyricsCache();
showToast("Lyrics cache purged", Toasts.Type.SUCCESS);
}}
>
Purge Cache
</Button>
</ButtonCompat>
),
},
spotifySectionTitle: {

View file

@ -22,7 +22,7 @@ interface NullLyricCacheEntry {
const nullLyricCache = new Map<string, NullLyricCacheEntry>();
export const lyricFetchers = {
[Provider.Spotify]: async (track: Track) => await getLyricsSpotify(track.id),
[Provider.Spotify]: async (track: Track) => await getLyricsSpotify(track.id, settings.store.spotifyLyricsApiUrl),
[Provider.Lrclib]: getLyricsLrclib,
};

View file

@ -19,8 +19,17 @@ interface Line {
endTimeMs: string;
}
export async function getLyricsSpotify(trackId: string): Promise<LyricsData | null> {
const resp = await fetch("https://spotify-lyrics-api-pi.vercel.app/?trackid=" + trackId);
const defaultSpotifyLyricsApiUrl = "https://spotify-lyrics-api-pi.vercel.app";
function makeSpotifyLyricsApiUrl(trackId: string, customBaseUrl?: string): string {
const normalizedBaseUrl = customBaseUrl?.trim().replace(/\/+$/, "") || defaultSpotifyLyricsApiUrl;
const url = new URL(normalizedBaseUrl);
url.searchParams.set("trackid", trackId);
return url.toString();
}
export async function getLyricsSpotify(trackId: string, customBaseUrl?: string): Promise<LyricsData | null> {
const resp = await fetch(makeSpotifyLyricsApiUrl(trackId, customBaseUrl));
if (!resp.ok) return null;
let data: LyricsAPIResp;
@ -30,6 +39,8 @@ export async function getLyricsSpotify(trackId: string): Promise<LyricsData | nu
return null;
}
if (data.error || !Array.isArray(data.lines) || data.lines.length < 2) return null;
const lyrics = data.lines;
if (lyrics[0].startTimeMs === "0" && lyrics[lyrics.length - 1].startTimeMs === "0") return null;

View file

@ -1280,6 +1280,10 @@ export const EquicordDevs = Object.freeze({
name: "ScattrdBlade",
id: 678007540608532491n
},
Ape: {
name: "Ape",
id: 722832640360841308n,
},
} satisfies Record<string, Dev>);
// iife so #__PURE__ works correctly