mirror of
https://github.com/Equicord/Equicord.git
synced 2025-06-09 14:43:03 -04:00
:3
This commit is contained in:
commit
6acc198c85
2 changed files with 1 additions and 393 deletions
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
|
@ -1,4 +1,4 @@
|
|||
name: test
|
||||
name: Test
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
|
|
|
@ -1,392 +0,0 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Link } from "@components/Link";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { ApplicationAssetUtils, FluxDispatcher, Forms, React } from "@webpack/common";
|
||||
|
||||
interface ActivityAssets {
|
||||
large_image?: string;
|
||||
large_text?: string;
|
||||
small_image?: string;
|
||||
small_text?: string;
|
||||
}
|
||||
|
||||
interface Activity {
|
||||
state?: string;
|
||||
details?: string;
|
||||
timestamps?: {
|
||||
start?: number;
|
||||
end?: number;
|
||||
};
|
||||
assets?: ActivityAssets;
|
||||
buttons?: Array<string>;
|
||||
name: string;
|
||||
application_id: string;
|
||||
metadata?: {
|
||||
button_urls?: Array<string>;
|
||||
};
|
||||
type: ActivityType;
|
||||
url?: string;
|
||||
flags: number;
|
||||
}
|
||||
|
||||
interface Data {
|
||||
activity: Activity;
|
||||
pid?: number;
|
||||
socketId: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
|
||||
const enum ActivityType {
|
||||
PLAYING = 0,
|
||||
STREAMING = 1,
|
||||
LISTENING = 2,
|
||||
WATCHING = 3,
|
||||
COMPETING = 5
|
||||
}
|
||||
interface SpotifyImage {
|
||||
height: number;
|
||||
width: number;
|
||||
url: string;
|
||||
}
|
||||
|
||||
interface SpotifyElement {
|
||||
id: string;
|
||||
name: string;
|
||||
// track, album or artist
|
||||
type: string;
|
||||
// only on track
|
||||
duration?: number;
|
||||
album?: SpotifyElement;
|
||||
artists?: Array<SpotifyElement>;
|
||||
isLocal?: boolean;
|
||||
// only on album
|
||||
image?: SpotifyImage;
|
||||
// only on artist
|
||||
external_urls?: {
|
||||
spotify: string;
|
||||
};
|
||||
href?: string;
|
||||
uri?: string;
|
||||
}
|
||||
|
||||
interface SpotifyEvent {
|
||||
type: string;
|
||||
track: SpotifyElement;
|
||||
connectionId: string;
|
||||
}
|
||||
|
||||
interface SpotifyDevice {
|
||||
id: string;
|
||||
is_active: boolean;
|
||||
is_private_session: boolean;
|
||||
is_restricted: boolean;
|
||||
name: string;
|
||||
supports_volume: boolean;
|
||||
type: string;
|
||||
volume_percent: number;
|
||||
}
|
||||
|
||||
enum NonSpotifyShowOptions {
|
||||
Details,
|
||||
State,
|
||||
LargeImageText,
|
||||
SmallImageText
|
||||
}
|
||||
|
||||
const settings = definePluginSettings({
|
||||
applicationId: {
|
||||
type: OptionType.STRING,
|
||||
description: "Application ID (required)",
|
||||
isValid: (value: string) => {
|
||||
if (!value) return "Application ID is required.";
|
||||
if (value && !/^\d+$/.test(value)) return "Application ID must be a number.";
|
||||
return true;
|
||||
}
|
||||
},
|
||||
musicPlayerNames: {
|
||||
type: OptionType.STRING,
|
||||
default: "Music",
|
||||
description: "List of activity names which will get replaced by the song's title (separated by ,)",
|
||||
},
|
||||
forceListeningType: {
|
||||
type: OptionType.BOOLEAN,
|
||||
default: true,
|
||||
description: "Force all music player activities to be \"Listening to\", instead of \"Playing\"",
|
||||
},
|
||||
spotifyActivityName: {
|
||||
type: OptionType.STRING,
|
||||
default: "{{Title}}",
|
||||
description: "The activity's name (spotify)"
|
||||
},
|
||||
spotifyActivityDetails: {
|
||||
type: OptionType.STRING,
|
||||
default: "{{Title}}",
|
||||
description: "The activity's details field (first line) (spotify)"
|
||||
},
|
||||
spotifyActivityState: {
|
||||
type: OptionType.STRING,
|
||||
default: "by {{AllArtistsGrammarSeparated}}",
|
||||
description: "The activity's state field (second line) (spotify)"
|
||||
},
|
||||
spotifyActivityLargeImageText: {
|
||||
type: OptionType.STRING,
|
||||
default: "on {{Album}}",
|
||||
description: "The activity's large image text (third line and image hover text) (spotify)"
|
||||
},
|
||||
spotifyButtonOneText: {
|
||||
type: OptionType.STRING,
|
||||
default: "Open in Spotify",
|
||||
description: "The activity's first button's text (spotify)"
|
||||
},
|
||||
spotifyButtonTwoText: {
|
||||
type: OptionType.STRING,
|
||||
default: "",
|
||||
description: "The activity's second button's text (spotify)"
|
||||
},
|
||||
spotifyButtonOneLink: {
|
||||
type: OptionType.STRING,
|
||||
default: "https://open.spotify.com/track/{{Id}}",
|
||||
description: "The activity's first button's link (spotify)"
|
||||
},
|
||||
spotifyButtonTwoLink: {
|
||||
type: OptionType.STRING,
|
||||
default: "",
|
||||
description: "The activity's second button's link (spotify)"
|
||||
},
|
||||
nonSpotifyWhatToShow: {
|
||||
type: OptionType.SELECT,
|
||||
options: [
|
||||
{ label: "Details", value: NonSpotifyShowOptions.Details, default: true },
|
||||
{ label: "State", value: NonSpotifyShowOptions.State },
|
||||
{ label: "Large image text", value: NonSpotifyShowOptions.LargeImageText },
|
||||
{ label: "Small image text", value: NonSpotifyShowOptions.SmallImageText },
|
||||
],
|
||||
description: "What to show (non-spotify)"
|
||||
},
|
||||
nonSpotifyRegex: {
|
||||
type: OptionType.STRING,
|
||||
default: "(.+)",
|
||||
description: "Regex pattern to determine what to show using the first captured group (non-spotify)"
|
||||
},
|
||||
cloneSpotifyActivity: {
|
||||
type: OptionType.BOOLEAN,
|
||||
default: true,
|
||||
description: "Clone Spotify's activity to give it the custom name. Does not remove the original one.",
|
||||
restartNeeded: true
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function handleUpdate(data: Data) {
|
||||
if (data.activity === null || data.activity.state === undefined) return;
|
||||
const { nonSpotifyWhatToShow: what_to_show, nonSpotifyRegex: regex_option } = settings.store;
|
||||
|
||||
const players = settings.store.musicPlayerNames.split(",").map(x => x.trim());
|
||||
if (!players.includes(data.activity.name)) return;
|
||||
|
||||
if (settings.store.forceListeningType) {
|
||||
data.activity.type = ActivityType.LISTENING;
|
||||
}
|
||||
|
||||
let name: string = "";
|
||||
if (what_to_show === NonSpotifyShowOptions.Details && data.activity.details !== undefined) {
|
||||
name = data.activity.details;
|
||||
} else if (what_to_show === NonSpotifyShowOptions.State && data.activity.state !== undefined) {
|
||||
name = data.activity.state;
|
||||
} else if (what_to_show === NonSpotifyShowOptions.LargeImageText && data.activity.assets?.large_text !== undefined) {
|
||||
name = data.activity.assets.large_text;
|
||||
} else if (what_to_show === NonSpotifyShowOptions.State && data.activity.assets?.small_text !== undefined) {
|
||||
name = data.activity.assets.small_text;
|
||||
}
|
||||
|
||||
const regex_output = new RegExp(regex_option).exec(name);
|
||||
if (regex_output === null || regex_output[1] === undefined) {
|
||||
// no match, use the full string
|
||||
data.activity.name = name;
|
||||
} else {
|
||||
data.activity.name = regex_output[1];
|
||||
}
|
||||
}
|
||||
|
||||
var playbackStoppedTimeout: number | undefined;
|
||||
|
||||
export default definePlugin({
|
||||
name: "MusicTitleRPC",
|
||||
description: "Makes the song's title appear as the activity name when listening to music.",
|
||||
authors: [EquicordDevs.Blackilykat],
|
||||
start: () => {
|
||||
FluxDispatcher.subscribe("LOCAL_ACTIVITY_UPDATE", handleUpdate);
|
||||
},
|
||||
stop: () => {
|
||||
FluxDispatcher.unsubscribe("LOCAL_ACTIVITY_UPDATE", handleUpdate);
|
||||
},
|
||||
patches: [
|
||||
{
|
||||
find: "let{connectionId:",
|
||||
predicate: () => settings.store.cloneSpotifyActivity,
|
||||
replacement: [
|
||||
{
|
||||
match: /(?=let{connectionId:\i,track:\i}=(\i);)/,
|
||||
replace: "$self.handleSpotifySongChange($1);"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
find: "SPOTIFY_SET_DEVICES:function(",
|
||||
predicate: () => settings.store.cloneSpotifyActivity,
|
||||
replacement: [
|
||||
{
|
||||
match: /(?<=SPOTIFY_SET_DEVICES:function\((\i)\){)/,
|
||||
replace: "$self.handleSpotifyChangeDevices($1);"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
settings,
|
||||
|
||||
async handleSpotifyChangeDevices(e: { accountId: string, devices: SpotifyDevice[]; }) {
|
||||
const { devices } = e;
|
||||
let playing: boolean = false;
|
||||
devices.forEach(device => {
|
||||
if (device.is_active) playing = true;
|
||||
});
|
||||
if (!playing) {
|
||||
FluxDispatcher.dispatch({
|
||||
type: "LOCAL_ACTIVITY_UPDATE",
|
||||
activity: null,
|
||||
socketId: "MusicTitleRPC:Spotify"
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
async handleSpotifySongChange(e: SpotifyEvent) {
|
||||
const {
|
||||
applicationId: application_id,
|
||||
spotifyActivityName: activity_name,
|
||||
spotifyActivityState: activity_state,
|
||||
spotifyActivityDetails: activity_details,
|
||||
spotifyActivityLargeImageText: activity_large_image_text,
|
||||
spotifyButtonOneText: activity_button_one_text,
|
||||
spotifyButtonTwoText: activity_button_two_text,
|
||||
spotifyButtonOneLink: activity_button_one_link,
|
||||
spotifyButtonTwoLink: activity_button_two_link
|
||||
} = settings.store;
|
||||
|
||||
if (application_id === undefined) return;
|
||||
|
||||
let large_image: string | undefined = undefined;
|
||||
if (e.track.album?.image?.url !== undefined) {
|
||||
large_image = (await ApplicationAssetUtils.fetchAssetIds(application_id, [e.track.album.image.url]))[0];
|
||||
}
|
||||
const activity: Activity = {
|
||||
application_id,
|
||||
name: formatSpotifyString(activity_name, e.track),
|
||||
type: ActivityType.LISTENING,
|
||||
flags: 0,
|
||||
details: formatSpotifyString(activity_details, e.track),
|
||||
state: formatSpotifyString(activity_state, e.track),
|
||||
timestamps: {
|
||||
start: Date.now(),
|
||||
end: (Date.now() + (e.track.duration ?? 0))
|
||||
},
|
||||
assets: {
|
||||
large_image,
|
||||
large_text: formatSpotifyString(activity_large_image_text, e.track)
|
||||
},
|
||||
buttons: [],
|
||||
metadata: {
|
||||
button_urls: []
|
||||
}
|
||||
};
|
||||
if (activity_button_one_text !== "") {
|
||||
activity.buttons![0] = formatSpotifyString(activity_button_one_text, e.track);
|
||||
activity.metadata!.button_urls![0] = formatSpotifyString(activity_button_one_link, e.track);
|
||||
}
|
||||
if (activity_button_two_text !== "") {
|
||||
activity.buttons![1] = formatSpotifyString(activity_button_two_text, e.track);
|
||||
activity.metadata!.button_urls![1] = formatSpotifyString(activity_button_two_link, e.track);
|
||||
}
|
||||
|
||||
FluxDispatcher.dispatch({
|
||||
type: "LOCAL_ACTIVITY_UPDATE",
|
||||
activity: activity,
|
||||
socketId: "MusicTitleRPC:Spotify"
|
||||
});
|
||||
|
||||
if (playbackStoppedTimeout) clearTimeout(playbackStoppedTimeout);
|
||||
playbackStoppedTimeout = window.setTimeout(() => {
|
||||
FluxDispatcher.dispatch({
|
||||
type: "LOCAL_ACTIVITY_UPDATE",
|
||||
activity: null,
|
||||
socketId: "MusicTitleRPC:Spotify"
|
||||
});
|
||||
}, (e.track.duration ?? 0) + 5000);
|
||||
},
|
||||
|
||||
settingsAboutComponent: () => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Forms.FormTitle tag="h3">Getting the Application ID</Forms.FormTitle>
|
||||
<Forms.FormText variant="text-md/normal">
|
||||
To get your application ID, go to the <Link href="https://discord.com/developers/applications">Discord Developer Portal</Link> and create an application.
|
||||
</Forms.FormText>
|
||||
<Forms.FormText variant="text-md/normal">
|
||||
If you already created one for CustomRPC, you can use the same ID here.
|
||||
</Forms.FormText>
|
||||
<br />
|
||||
<Forms.FormTitle tag="h3">For spotify users</Forms.FormTitle>
|
||||
<Forms.FormText variant="text-md/normal">
|
||||
If you use spotify, make sure to disable <code>Settings</code> > <code>Connections</code> > <code>Display Spotify as your status</code>. Keeping it enabled will result in two activities showing up.
|
||||
</Forms.FormText>
|
||||
<br />
|
||||
<Forms.FormText variant="text-md/normal">
|
||||
The new activity this plugin creates will be missing some features (time bar, play on spotify and listen along). This is a compromise, not a bug.
|
||||
</Forms.FormText>
|
||||
<br />
|
||||
<Forms.FormText variant="text-md/normal">
|
||||
Text fields in settings for spotify users will have the following strings replaced:<br />
|
||||
<code>{"{{Title}}"}</code>: The song's title<br />
|
||||
<code>{"{{Album}}"}</code>: The song's album's name<br />
|
||||
<code>{"{{FirstArtist}}"}</code>: The first artist's name<br />
|
||||
<code>{"{{AllArtistsCommaSeparated}}"}</code>: Names of all artists, separated by commas (Artist1, Artist2, Artist3)<br />
|
||||
<code>{"{{AllArtistsGrammarSeparated}}"}</code>: Names of all artists, separated according to english grammar (Artist1, Artist2 and Artist3)<br />
|
||||
<code>{"{{Duration}}"}</code>: The song's duration (formatted mm:ss)<br />
|
||||
<code>{"{{Id}}"}</code>: The track id<br />
|
||||
</Forms.FormText>
|
||||
<br />
|
||||
<Forms.FormTitle tag="h3">If you don't see the activity</Forms.FormTitle>
|
||||
<Forms.FormText variant="text-md/normal">
|
||||
Make sure you enabled <code>Settings</code> > <code>Activity privacy</code> > <code>Share your detected activities with others</code>.
|
||||
</Forms.FormText>
|
||||
</React.Fragment>
|
||||
);
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
function formatSpotifyString(input: string, song: SpotifyElement): string {
|
||||
if (input === undefined) return "";
|
||||
|
||||
const durationMinutes: number = Math.trunc(song.duration! / 60000);
|
||||
const durationSeconds: number = Math.trunc(song.duration! / 1000) - durationMinutes * 60;
|
||||
return input
|
||||
.replace("{{Title}}", song.name)
|
||||
.replace("{{Album}}", song.album!.name)
|
||||
.replace("{{FirstArtist}}", song.artists![0].name)
|
||||
.replace("{{AllArtistsCommaSeparated}}", song.artists!.map(artist => artist.name).join(", "))
|
||||
.replace("{{AllArtistsGrammarSeparated}}", song.artists!
|
||||
.slice(0, song.artists!.length > 1 ? -1 : undefined)
|
||||
.map(artist => artist.name)
|
||||
.join(", ")
|
||||
+ (song.artists!.length > 1 ? ` and ${song.artists![song.artists!.length - 1].name}` : ""))
|
||||
.replace("{{Duration}}", durationMinutes + ":" + ("00" + durationSeconds).slice(-2))
|
||||
.replace("{{Id}}", song.id);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue