Files
youtube-music/src/plugins/discord/utils.ts
Tix bc890943ef Fix: discord activity sanitizer (#4119)
Co-authored-by: Angelos Bouklis <me@arjix.dev>
2026-01-29 18:04:31 +09:00

99 lines
3.3 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { HANGUL_FILLER } from './constants';
import { APPLICATION_NAME } from '@/i18n';
import type { GatewayActivityButton } from 'discord-api-types/v10';
import type { SongInfo } from '@/providers/song-info';
import type { DiscordPluginConfig } from './index';
/**
* Truncates a string to a specified length, adding ellipsis if truncated.
* @param str - The string to truncate.
* @param length - The maximum allowed length.
* @returns The truncated string.
*/
export const truncateString = (str: string, length: number): string => {
if (str.length > length) {
return `${str.substring(0, length - 3)}...`;
}
return str;
};
/**
* Sanitizes a string for Discord Rich Presence activity, ensuring it meets length requirements.
* @param input - The string to sanitize.
* @param fallback - A fallback string to use if the input is empty or whitespace. Defaults to 'undefined'.
* @returns The sanitized string, compliant with Discord's requirements.
*/
export function sanitizeActivityText(input: string | undefined, fallback: string = 'undefined'): string {
const text = (input && input.trim()) ? input.trim() : fallback.trim();
let safeString = truncateString(text, 128);
if (safeString.length === 0) {
return fallback;
}
if (safeString.length < 2) {
safeString = safeString.padEnd(2, ''); // change if necessary
}
return safeString;
}
/**
* Builds the array of buttons for the Discord Rich Presence activity.
* @param config - The plugin configuration.
* @param songInfo - The current song information.
* @returns An array of buttons or undefined if no buttons are configured.
*/
export const buildDiscordButtons = (
config: DiscordPluginConfig,
songInfo: SongInfo,
): GatewayActivityButton[] | undefined => {
const buttons: GatewayActivityButton[] = [];
if (
config[
'playOn\u0059\u006f\u0075\u0054\u0075\u0062\u0065\u004d\u0075\u0073\u0069\u0063'
] &&
songInfo.url
) {
buttons.push({
label: `Play on ${APPLICATION_NAME}`,
url: songInfo.url,
});
}
if (!config.hideGitHubButton) {
buttons.push({
label: 'View App On GitHub',
url: 'https://github.com/pear-devs/pear-desktop',
});
}
return buttons.length ? buttons : undefined;
};
/**
* Pads Hangul fields (title, artist, album) in SongInfo if they are less than 2 characters long.
* Discord requires fields to be at least 2 characters.
* @param songInfo - The song information object (will be mutated).
*/
export const padHangulFields = (songInfo: SongInfo): void => {
(['title', 'artist', 'album'] as const).forEach((key) => {
const value = songInfo[key];
if (typeof value === 'string' && value.length > 0 && value.length < 2) {
songInfo[key] = value + HANGUL_FILLER.repeat(2 - value.length);
}
});
};
/**
* Checks if the difference between two time values indicates a seek operation.
* @param oldSeconds - The previous elapsed time in seconds.
* @param newSeconds - The current elapsed time in seconds.
* @returns True if the time difference suggests a seek, false otherwise.
*/
export const isSeek = (oldSeconds: number, newSeconds: number): boolean => {
// Consider it a seek if the time difference is greater than 2 seconds
// (allowing for minor discrepancies in reporting)
return Math.abs(newSeconds - oldSeconds) > 2;
};