SongInfo: Add Plugin
This commit is contained in:
parent
1da50a70b7
commit
83958f2135
6 changed files with 348 additions and 0 deletions
|
|
@ -58,6 +58,7 @@
|
|||
line-height: 16px;
|
||||
color: var(--white-500);
|
||||
text-align: center;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.vc-plugins-dep-name {
|
||||
|
|
|
|||
97
src/equicordplugins/songLink.desktop/Providers.ts
Normal file
97
src/equicordplugins/songLink.desktop/Providers.ts
Normal file
File diff suppressed because one or more lines are too long
62
src/equicordplugins/songLink.desktop/Settings.tsx
Normal file
62
src/equicordplugins/songLink.desktop/Settings.tsx
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 nin0
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { FormSwitch } from "@components/FormSwitch";
|
||||
import { HeadingTertiary } from "@components/Heading";
|
||||
|
||||
import { settings } from ".";
|
||||
import Providers from "./Providers";
|
||||
|
||||
export function Settings() {
|
||||
const blazinglyFastSettings = settings.use(["servicesSettings"]);
|
||||
|
||||
return <div style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 16,
|
||||
}}>
|
||||
{
|
||||
Object.entries(Providers).map(provider => <div key={provider[0]} style={{
|
||||
borderBottom: "1px solid var(--border-subtle)",
|
||||
paddingBottom: 8
|
||||
}}>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
|
||||
<img
|
||||
src={provider[1].logo}
|
||||
alt={`${provider[1].name} logo`}
|
||||
style={{ width: 16, height: 16, objectFit: "contain", display: "block" }}
|
||||
/>
|
||||
<div style={{ display: "flex", alignItems: "center" }}>
|
||||
<HeadingTertiary>{provider[1].name}</HeadingTertiary>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 4
|
||||
}}>
|
||||
<FormSwitch
|
||||
title="Show if available"
|
||||
hideBorder={true}
|
||||
key={`${provider[0]}-visible`}
|
||||
value={blazinglyFastSettings.servicesSettings[provider[0]].enabled}
|
||||
onChange={v => settings.store.servicesSettings[provider[0]].enabled = v}
|
||||
/>
|
||||
{
|
||||
/* @ts-ignore */
|
||||
provider[1].native && <FormSwitch
|
||||
title="Open in desktop app"
|
||||
hideBorder={true}
|
||||
key={`${provider[0]}-native`}
|
||||
value={blazinglyFastSettings.servicesSettings[provider[0]].openInNative}
|
||||
onChange={v => settings.store.servicesSettings[provider[0]].openInNative = v}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
</div>)
|
||||
}
|
||||
</div>;
|
||||
}
|
||||
61
src/equicordplugins/songLink.desktop/SongLinker.tsx
Normal file
61
src/equicordplugins/songLink.desktop/SongLinker.tsx
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 nin0
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { BaseText } from "@components/BaseText";
|
||||
import { findComponentByCodeLazy } from "@webpack";
|
||||
import { Button, Card, useEffect, useState } from "@webpack/common";
|
||||
|
||||
import pl, { Native, settings, SongLinkResult } from ".";
|
||||
import Providers from "./Providers";
|
||||
|
||||
const HeadphonesIcon = findComponentByCodeLazy("4.1-2.13h1.86A9");
|
||||
|
||||
export default function SongLinker({ url }: { url: string; }) {
|
||||
const [songData, setSongData] = useState<SongLinkResult>();
|
||||
|
||||
useEffect(() => {
|
||||
async function doStuff() {
|
||||
if (pl.cache[url]) return void setSongData(pl.cache[url]);
|
||||
|
||||
const sd = await Native.getTrackData(url);
|
||||
setSongData(sd);
|
||||
pl.addToCache(url, sd);
|
||||
}
|
||||
doStuff();
|
||||
});
|
||||
|
||||
return <BaseText>
|
||||
{
|
||||
songData ? <Card style={{
|
||||
padding: "10px 15px"
|
||||
}}>
|
||||
<div>
|
||||
<BaseText style={{ display: "flex", alignItems: "center", gap: "6px", fontWeight: 600, fontSize: "1.05rem" }}>
|
||||
<HeadphonesIcon /> {songData.info?.title} - {songData.info?.artist}
|
||||
</BaseText>
|
||||
<div style={{ display: "grid", gridTemplateColumns: "repeat(4, 1fr)", gap: "10px", marginTop: "10px" }}>
|
||||
{
|
||||
Object.keys(songData.links).map(service => settings.store.servicesSettings[service].enabled && <Button key={`${service}-${url}`} style={{
|
||||
width: "20px !important"
|
||||
// @ts-ignore
|
||||
}} variant="secondary" onClick={() => {
|
||||
// FIXME: fix type error
|
||||
// @ts-expect-error ???
|
||||
VencordNative.native.openExternal(settings.store.servicesSettings[service].openInNative && Providers[service].native ? songData.links[service].nativeUri : songData.links[service].url);
|
||||
}}>
|
||||
<img
|
||||
src={Providers[service].logo}
|
||||
alt={`${Providers[service]} logo`}
|
||||
style={{ width: 16, height: 16, objectFit: "contain", display: "block" }}
|
||||
/>
|
||||
</Button>)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</Card> : <BaseText>Loading song link...</BaseText>
|
||||
}
|
||||
</BaseText >;
|
||||
}
|
||||
87
src/equicordplugins/songLink.desktop/index.tsx
Normal file
87
src/equicordplugins/songLink.desktop/index.tsx
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 nin0
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType, PluginNative } from "@utils/types";
|
||||
|
||||
import Providers from "./Providers";
|
||||
import { Settings } from "./Settings";
|
||||
import SongLinker from "./SongLinker";
|
||||
|
||||
export const settings = definePluginSettings({
|
||||
servicesSettings: {
|
||||
type: OptionType.CUSTOM,
|
||||
description: "settings for services",
|
||||
default: Object.fromEntries(Object.entries(Providers).map(([name, data]) => [name, {
|
||||
enabled: true,
|
||||
// @ts-ignore
|
||||
openInNative: data.native || false
|
||||
}]))
|
||||
},
|
||||
userCountry: {
|
||||
type: OptionType.STRING,
|
||||
description: "Country used for lookup (Two letter country code)",
|
||||
default: "US"
|
||||
},
|
||||
servicesComponent: {
|
||||
type: OptionType.COMPONENT,
|
||||
component: () => <Settings />
|
||||
}
|
||||
});
|
||||
|
||||
export type SongLinkResult = {
|
||||
info?: {
|
||||
title: string;
|
||||
artist: string;
|
||||
};
|
||||
links: {
|
||||
[platform: string]: {
|
||||
url: string;
|
||||
nativeUri?: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export const Native = VencordNative.pluginHelpers.SongLink as PluginNative<typeof import("./native")>;
|
||||
|
||||
export default definePlugin({
|
||||
name: "SongLink",
|
||||
description: "Adds streaming service buttons below song links",
|
||||
authors: [Devs.nin0dev],
|
||||
settings,
|
||||
Providers,
|
||||
cache: ({} as Record<string, SongLinkResult>),
|
||||
addToCache(link, data: SongLinkResult) {
|
||||
this.cache[link] = data;
|
||||
},
|
||||
renderMessageAccessory(props: Record<string, any>) {
|
||||
const { content }: {
|
||||
content: string;
|
||||
} = props.message;
|
||||
if (!content) return;
|
||||
|
||||
const regexes = [
|
||||
/https:\/\/(?:open|play)\.spotify\.com\/track\/[a-zA-Z0-9]+/, // spotify
|
||||
/https:\/\/(music|itunes)\.apple\.com\/[a-z]{2}\/album\/\S+/, // apple music/itunes
|
||||
/https:\/\/music\.youtube\.com\/watch\?v=[0-9A-Za-z_-]+/, // yt music
|
||||
/https:\/\/tidal\.com\/track\/[0-9]+\/u/ // tidal
|
||||
];
|
||||
const musicLinks = content.match(new RegExp(regexes.map(r => r.source).join("|"), "g"));
|
||||
if (!musicLinks?.length) return;
|
||||
|
||||
return <div style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "10px",
|
||||
marginTop: "7px"
|
||||
}}>
|
||||
{
|
||||
musicLinks.map(item => <SongLinker key={item} url={item} />)
|
||||
}
|
||||
</div>;
|
||||
}
|
||||
});
|
||||
40
src/equicordplugins/songLink.desktop/native.ts
Normal file
40
src/equicordplugins/songLink.desktop/native.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 nin0
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { RendererSettings } from "@main/settings";
|
||||
|
||||
type SongLinkResult = {
|
||||
info?: {
|
||||
title: string;
|
||||
artist: string;
|
||||
};
|
||||
links: {
|
||||
[platform: string]: {
|
||||
url: string;
|
||||
nativeUri?: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export async function getTrackData(_, trackURL: string): Promise<SongLinkResult> {
|
||||
const url = new URL("https://api.song.link/v1-alpha.1/links");
|
||||
url.searchParams.set("url", trackURL);
|
||||
url.searchParams.set("userCountry", RendererSettings.store.plugins?.SongLink.userCountry || "US");
|
||||
const raw = await fetch(url.toString()).then(u => u.json());
|
||||
const [, entry]: any = Object.entries(raw.entitiesByUniqueId)
|
||||
.find(([key]) => !key.includes("YOUTUBE")) || [];
|
||||
const possibleTrackInfo = entry
|
||||
? { title: (entry.title as string), artist: (entry.artistName as string) }
|
||||
: null;
|
||||
return {
|
||||
// @ts-ignore
|
||||
info: possibleTrackInfo,
|
||||
links: Object.fromEntries(Object.entries<any>(raw.linksByPlatform).map(([name, data]) => [name, {
|
||||
url: data.url,
|
||||
nativeUri: data.nativeAppUriDesktop
|
||||
}]))
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue