mirror of
https://github.com/th-ch/youtube-music.git
synced 2026-01-11 10:31:47 +00:00
feat(discord): Enhance activity updates with throttling and state management
- Implemented throttling for Discord activity updates to respect rate limits. - Added state management for song changes, pause state, and elapsed time. - Introduced buttons for YouTube Music and GitHub links in activity updates. - Improved handling of paused state and activity timeout configuration.
This commit is contained in:
@ -118,6 +118,63 @@ export const clear = () => {
|
|||||||
export const registerRefresh = (cb: () => void) => refreshCallbacks.push(cb);
|
export const registerRefresh = (cb: () => void) => refreshCallbacks.push(cb);
|
||||||
export const isConnected = () => info.rpc?.isConnected;
|
export const isConnected = () => info.rpc?.isConnected;
|
||||||
|
|
||||||
|
let lastActivitySongId: string | null = null;
|
||||||
|
let lastPausedState: boolean | null = null;
|
||||||
|
let lastElapsedSeconds: number = 0;
|
||||||
|
let updateTimeout: NodeJS.Timeout | null = null;
|
||||||
|
let lastProgressUpdate: number = 0; // timestamp of the last throttled update
|
||||||
|
|
||||||
|
const PROGRESS_THROTTLE_MS = 15000; // 15s to respect Discord's rate limit
|
||||||
|
|
||||||
|
function isSeek(oldSec: number, newSec: number) {
|
||||||
|
return Math.abs((newSec ?? 0) - (oldSec ?? 0)) > 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendActivityToDiscord(songInfo: SongInfo, config: DiscordPluginConfig) {
|
||||||
|
if (songInfo.title.length === 0 && songInfo.artist.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
info.lastSongInfo = songInfo;
|
||||||
|
clearTimeout(clearActivity);
|
||||||
|
if (!info.rpc || !info.ready) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let buttons: GatewayActivityButton[] | undefined = [];
|
||||||
|
if (config.playOnYouTubeMusic) {
|
||||||
|
buttons.push({
|
||||||
|
label: 'Play on YouTube Music',
|
||||||
|
url: songInfo.url ?? 'https://music.youtube.com',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!config.hideGitHubButton) {
|
||||||
|
buttons.push({
|
||||||
|
label: 'View App On GitHub',
|
||||||
|
url: 'https://github.com/th-ch/youtube-music',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (buttons.length === 0) {
|
||||||
|
buttons = undefined;
|
||||||
|
}
|
||||||
|
const activityInfo: SetActivity = {
|
||||||
|
type: ActivityType.Listening,
|
||||||
|
details: truncateString(songInfo.title, 128),
|
||||||
|
state: truncateString(songInfo.artist, 128),
|
||||||
|
largeImageKey: songInfo.imageSrc ?? '',
|
||||||
|
largeImageText: songInfo.album ?? '',
|
||||||
|
buttons,
|
||||||
|
};
|
||||||
|
if (songInfo.isPaused) {
|
||||||
|
activityInfo.smallImageKey = 'paused';
|
||||||
|
activityInfo.smallImageText = 'Paused';
|
||||||
|
// No timestamps when paused
|
||||||
|
} else if (!config.hideDurationLeft) {
|
||||||
|
const songStartTime = Date.now() - (songInfo.elapsedSeconds ?? 0) * 1000;
|
||||||
|
activityInfo.startTimestamp = songStartTime;
|
||||||
|
activityInfo.endTimestamp = songStartTime + songInfo.songDuration * 1000;
|
||||||
|
}
|
||||||
|
info.rpc.user?.setActivity(activityInfo).catch(console.error);
|
||||||
|
}
|
||||||
|
|
||||||
export const backend = createBackend<
|
export const backend = createBackend<
|
||||||
{
|
{
|
||||||
config?: DiscordPluginConfig;
|
config?: DiscordPluginConfig;
|
||||||
@ -134,88 +191,48 @@ export const backend = createBackend<
|
|||||||
if (songInfo.title.length === 0 && songInfo.artist.length === 0) {
|
if (songInfo.title.length === 0 && songInfo.artist.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
info.lastSongInfo = songInfo;
|
info.lastSongInfo = songInfo;
|
||||||
|
|
||||||
// Stop the clear activity timeout
|
|
||||||
clearTimeout(clearActivity);
|
clearTimeout(clearActivity);
|
||||||
|
|
||||||
// Stop early if discord connection is not ready
|
|
||||||
// do this after clearTimeout to avoid unexpected clears
|
|
||||||
if (!info.rpc || !info.ready) {
|
if (!info.rpc || !info.ready) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const now = Date.now();
|
||||||
// Clear directly if timeout is 0
|
const songChanged = songInfo.videoId !== lastActivitySongId;
|
||||||
if (
|
const pauseChanged = songInfo.isPaused !== lastPausedState;
|
||||||
songInfo.isPaused &&
|
const seeked = isSeek(lastElapsedSeconds, songInfo.elapsedSeconds ?? 0);
|
||||||
config.activityTimeoutEnabled &&
|
if (songChanged || pauseChanged || seeked) {
|
||||||
config.activityTimeoutTime === 0
|
// Immediate update
|
||||||
) {
|
if (updateTimeout) clearTimeout(updateTimeout);
|
||||||
info.rpc.user?.clearActivity().catch(console.error);
|
updateTimeout = null;
|
||||||
|
sendActivityToDiscord(songInfo, config);
|
||||||
|
lastActivitySongId = songInfo.videoId;
|
||||||
|
lastPausedState = songInfo.isPaused ?? null;
|
||||||
|
lastElapsedSeconds = songInfo.elapsedSeconds ?? 0;
|
||||||
|
lastProgressUpdate = now;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// Normal progression: throttle
|
||||||
// Song information changed, so lets update the rich presence
|
if (now - lastProgressUpdate > PROGRESS_THROTTLE_MS) {
|
||||||
// @see https://discord.com/developers/docs/topics/gateway#activity-object
|
sendActivityToDiscord(songInfo, config);
|
||||||
// not all options are transfered through https://github.com/discordjs/RPC/blob/6f83d8d812c87cb7ae22064acd132600407d7d05/src/client.js#L518-530
|
lastProgressUpdate = now;
|
||||||
const hangulFillerUnicodeCharacter = '\u3164'; // This is an empty character
|
lastElapsedSeconds = songInfo.elapsedSeconds ?? 0;
|
||||||
const paddedInfoKeys: (keyof SongInfo)[] = ['title', 'artist', 'album'];
|
} else {
|
||||||
for (const key of paddedInfoKeys) {
|
if (updateTimeout) clearTimeout(updateTimeout);
|
||||||
const keyLength = (songInfo[key] as string)?.length;
|
updateTimeout = setTimeout(() => {
|
||||||
if (keyLength < 2) {
|
sendActivityToDiscord(songInfo, config);
|
||||||
(songInfo[key] as string) += hangulFillerUnicodeCharacter.repeat(
|
lastProgressUpdate = Date.now();
|
||||||
2 - keyLength,
|
lastElapsedSeconds = songInfo.elapsedSeconds ?? 0;
|
||||||
);
|
}, PROGRESS_THROTTLE_MS - (now - lastProgressUpdate));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (songInfo.isPaused && config.activityTimeoutEnabled) {
|
||||||
// see https://github.com/th-ch/youtube-music/issues/1664
|
clearTimeout(clearActivity);
|
||||||
let buttons: GatewayActivityButton[] | undefined = [];
|
clearActivity = setTimeout(
|
||||||
if (config.playOnYouTubeMusic) {
|
() => info.rpc.user?.clearActivity().catch(console.error),
|
||||||
buttons.push({
|
config.activityTimeoutTime ?? 10_000,
|
||||||
label: 'Play on YouTube Music',
|
);
|
||||||
url: songInfo.url ?? 'https://music.youtube.com',
|
} else {
|
||||||
});
|
clearTimeout(clearActivity);
|
||||||
}
|
}
|
||||||
if (!config.hideGitHubButton) {
|
|
||||||
buttons.push({
|
|
||||||
label: 'View App On GitHub',
|
|
||||||
url: 'https://github.com/th-ch/youtube-music',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (buttons.length === 0) {
|
|
||||||
buttons = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const activityInfo: SetActivity = {
|
|
||||||
type: ActivityType.Listening,
|
|
||||||
details: truncateString(songInfo.title, 128),
|
|
||||||
state: truncateString(songInfo.artist, 128),
|
|
||||||
largeImageKey: songInfo.imageSrc ?? '',
|
|
||||||
largeImageText: songInfo.album ?? '',
|
|
||||||
buttons,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (songInfo.isPaused) {
|
|
||||||
// Add a paused icon to show that the song is paused
|
|
||||||
activityInfo.smallImageKey = 'paused';
|
|
||||||
activityInfo.smallImageText = 'Paused';
|
|
||||||
// Set start the timer so the activity gets cleared after a while if enabled
|
|
||||||
if (config.activityTimeoutEnabled) {
|
|
||||||
clearActivity = setTimeout(
|
|
||||||
() => info.rpc.user?.clearActivity().catch(console.error),
|
|
||||||
config.activityTimeoutTime ?? 10_000,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else if (!config.hideDurationLeft) {
|
|
||||||
// Add the start and end time of the song
|
|
||||||
const songStartTime = Date.now() - (songInfo.elapsedSeconds ?? 0) * 1000;
|
|
||||||
activityInfo.startTimestamp = songStartTime;
|
|
||||||
activityInfo.endTimestamp = songStartTime + songInfo.songDuration * 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
info.rpc.user?.setActivity(activityInfo).catch(console.error);
|
|
||||||
},
|
},
|
||||||
async start(ctx) {
|
async start(ctx) {
|
||||||
this.config = await ctx.getConfig();
|
this.config = await ctx.getConfig();
|
||||||
|
|||||||
Reference in New Issue
Block a user