feat(discord): centralize activityInfo creation and simplify elapsedSeconds handling

- Extracted activityInfo object construction into the buildActivityInfo helper to remove code duplication in presence updates.

- Removed unnecessary nullish coalescing (?? 0)
This commit is contained in:
MindLated
2025-05-12 13:17:13 +02:00
parent 42cb9b0ea8
commit 981a7f319e

View File

@ -92,48 +92,55 @@ let lastElapsedSeconds: number = 0;
let lastProgressUpdate: number = 0; let lastProgressUpdate: number = 0;
const PROGRESS_THROTTLE_MS = 15000; const PROGRESS_THROTTLE_MS = 15000;
/** function buildActivityInfo(
* Main Discord presence update logic. songInfo: SongInfo,
* Handles throttling, snapshotting, and timer management to avoid spamming Discord's API. config: DiscordPluginConfig,
* - Throttling: Ensures updates are not sent more than once every PROGRESS_THROTTLE_MS ms. pausedKey: 'smallImageKey' | 'largeImageKey' = 'smallImageKey',
* - Snapshot: If an update is requested too soon, schedules a delayed update with a snapshot of the current song state. ): SetActivity {
* - Timers: Uses TimerManager to ensure only one timer per type is active and to clean up properly. padHangulFields(songInfo);
*/ const activityInfo: SetActivity = {
type: ActivityType.Listening,
details: truncateString(songInfo.title, 128),
state: truncateString(songInfo.artist, 128),
largeImageKey: songInfo.imageSrc ?? '',
largeImageText: songInfo.album ?? '',
buttons: buildDiscordButtons(config, songInfo),
};
if (songInfo.isPaused) {
activityInfo[pausedKey] = 'paused';
if (pausedKey === 'smallImageKey') {
activityInfo.smallImageText = 'Paused';
} else {
activityInfo.largeImageText = 'Paused';
}
} else if (!config.hideDurationLeft) {
// Set start/end timestamps for progress bar
const songStartTime = Date.now() - (songInfo.elapsedSeconds ?? 0) * 1000;
activityInfo.startTimestamp = songStartTime;
activityInfo.endTimestamp = songStartTime + songInfo.songDuration * 1000;
}
return activityInfo;
}
function updateDiscordRichPresence( function updateDiscordRichPresence(
songInfo: SongInfo, songInfo: SongInfo,
config: DiscordPluginConfig, config: DiscordPluginConfig,
) { ) {
if (songInfo.title.length === 0 && songInfo.artist.length === 0) return; if (songInfo.title.length === 0 && songInfo.artist.length === 0) return;
discordState.lastSongInfo = songInfo; discordState.lastSongInfo = songInfo;
// Always clear any pending activity-clear timer before updating presence
TimerManager.clear('clearActivity'); TimerManager.clear('clearActivity');
if (!discordState.rpc || !discordState.ready) return; if (!discordState.rpc || !discordState.ready) return;
const now = Date.now(); const now = Date.now();
const songChanged = songInfo.videoId !== lastActivitySongId; const songChanged = songInfo.videoId !== lastActivitySongId;
const pauseChanged = songInfo.isPaused !== lastPausedState; const pauseChanged = songInfo.isPaused !== lastPausedState;
const seeked = isSeek(lastElapsedSeconds, songInfo.elapsedSeconds ?? 0); const seeked = isSeek(lastElapsedSeconds, songInfo.elapsedSeconds ?? 0);
// If the song changed, pause state changed, or user seeked, update immediately and reset throttle
if (songChanged || pauseChanged || seeked) { if (songChanged || pauseChanged || seeked) {
// Cancel any pending throttled update
TimerManager.clear('updateTimeout'); TimerManager.clear('updateTimeout');
padHangulFields(songInfo); const activityInfo = buildActivityInfo(
const activityInfo: SetActivity = { songInfo,
type: ActivityType.Listening, config,
details: truncateString(songInfo.title, 128), songInfo.isPaused ? 'largeImageKey' : 'smallImageKey',
state: truncateString(songInfo.artist, 128), );
largeImageKey: songInfo.imageSrc ?? '',
largeImageText: songInfo.album ?? '',
buttons: buildDiscordButtons(config, songInfo),
};
if (songInfo.isPaused) {
activityInfo.smallImageKey = 'paused';
activityInfo.smallImageText = 'Paused';
} else if (!config.hideDurationLeft) {
// Set start/end timestamps for progress bar
const songStartTime = Date.now() - (songInfo.elapsedSeconds ?? 0) * 1000;
activityInfo.startTimestamp = songStartTime;
activityInfo.endTimestamp = songStartTime + songInfo.songDuration * 1000;
}
discordState.rpc.user?.setActivity(activityInfo).catch(console.error); discordState.rpc.user?.setActivity(activityInfo).catch(console.error);
lastActivitySongId = songInfo.videoId; lastActivitySongId = songInfo.videoId;
if (typeof songInfo.isPaused === 'boolean') { if (typeof songInfo.isPaused === 'boolean') {
@ -144,62 +151,27 @@ function updateDiscordRichPresence(
setActivityTimeoutCentral(songInfo.isPaused, config); setActivityTimeoutCentral(songInfo.isPaused, config);
return; return;
} }
// Throttling: Only allow a full update if enough time has passed
if (now - lastProgressUpdate > PROGRESS_THROTTLE_MS) { if (now - lastProgressUpdate > PROGRESS_THROTTLE_MS) {
padHangulFields(songInfo); const activityInfo = buildActivityInfo(
const activityInfo: SetActivity = { songInfo,
type: ActivityType.Listening, config,
details: truncateString(songInfo.title, 128), songInfo.isPaused ? 'largeImageKey' : 'smallImageKey',
state: truncateString(songInfo.artist, 128), );
largeImageKey: songInfo.imageSrc ?? '',
largeImageText: songInfo.album ?? '',
buttons: buildDiscordButtons(config, songInfo),
};
if (songInfo.isPaused) {
activityInfo.smallImageKey = 'paused';
activityInfo.smallImageText = 'Paused';
} else if (!config.hideDurationLeft) {
// Set start/end timestamps for progress bar
const songStartTime = Date.now() - (songInfo.elapsedSeconds ?? 0) * 1000;
activityInfo.startTimestamp = songStartTime;
activityInfo.endTimestamp = songStartTime + songInfo.songDuration * 1000;
}
discordState.rpc.user?.setActivity(activityInfo).catch(console.error); discordState.rpc.user?.setActivity(activityInfo).catch(console.error);
lastProgressUpdate = now; lastProgressUpdate = now;
lastElapsedSeconds = songInfo.elapsedSeconds ?? 0; lastElapsedSeconds = songInfo.elapsedSeconds ?? 0;
setActivityTimeoutCentral(songInfo.isPaused, config); setActivityTimeoutCentral(songInfo.isPaused, config);
} else { } else {
// Snapshot logic: If throttled, schedule a delayed update with a snapshot of the current song state
TimerManager.clear('updateTimeout'); TimerManager.clear('updateTimeout');
const songInfoSnapshot = { ...songInfo }; const songInfoSnapshot = { ...songInfo };
TimerManager.set( TimerManager.set(
'updateTimeout', 'updateTimeout',
() => { () => {
// Only send if the global state still matches the snapshot
if ( if (
discordState.lastSongInfo?.videoId === songInfoSnapshot.videoId && discordState.lastSongInfo?.videoId === songInfoSnapshot.videoId &&
discordState.lastSongInfo?.isPaused === songInfoSnapshot.isPaused discordState.lastSongInfo?.isPaused === songInfoSnapshot.isPaused
) { ) {
padHangulFields(songInfoSnapshot); const activityInfo = buildActivityInfo(songInfoSnapshot, config);
const activityInfo: SetActivity = {
type: ActivityType.Listening,
details: truncateString(songInfoSnapshot.title, 128),
state: truncateString(songInfoSnapshot.artist, 128),
largeImageKey: songInfoSnapshot.imageSrc ?? '',
largeImageText: songInfoSnapshot.album ?? '',
buttons: buildDiscordButtons(config, songInfoSnapshot),
};
if (songInfoSnapshot.isPaused) {
activityInfo.smallImageKey = 'paused';
activityInfo.smallImageText = 'Paused';
} else if (!config.hideDurationLeft) {
// Set start/end timestamps for progress bar
const songStartTime =
Date.now() - (songInfoSnapshot.elapsedSeconds ?? 0) * 1000;
activityInfo.startTimestamp = songStartTime;
activityInfo.endTimestamp =
songStartTime + songInfoSnapshot.songDuration * 1000;
}
discordState.rpc.user?.setActivity(activityInfo).catch(console.error); discordState.rpc.user?.setActivity(activityInfo).catch(console.error);
lastProgressUpdate = Date.now(); lastProgressUpdate = Date.now();
lastElapsedSeconds = songInfoSnapshot.elapsedSeconds ?? 0; lastElapsedSeconds = songInfoSnapshot.elapsedSeconds ?? 0;
@ -306,7 +278,7 @@ export const isConnected = () => discordState.rpc?.isConnected;
const refreshCallbacks: (() => void)[] = []; const refreshCallbacks: (() => void)[] = [];
function isSeek(oldSec: number, newSec: number) { function isSeek(oldSec: number, newSec: number) {
return Math.abs((newSec ?? 0) - (oldSec ?? 0)) > 2; return Math.abs(newSec - oldSec) > 2;
} }
export const backend = createBackend< export const backend = createBackend<