mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-05-26 00:55:16 -07:00
fix(jellyfin): fix discovery loop, device identity, tray state, and Disc
- Derive device identity from OS hostname; remove legacy configurable client/device fields - Prevent discovery playback from reloading active item, misreporting pause state, and duplicate overlay restores - Restart stale tray discovery sessions without re-login when server drops SubMiner cast target - Sync tray discovery checkbox state on Linux after CLI/startup/remote-session changes - Stop Discord presence falling back to stream URLs; prime title before tokenized stream loads - Fix picker library discovery when log level is above info - Fix config.example.jsonc trailing commas and array formatting
This commit is contained in:
@@ -91,6 +91,22 @@ test('buildDiscordPresenceActivity shows media title regardless of style', () =>
|
||||
}
|
||||
});
|
||||
|
||||
test('buildDiscordPresenceActivity never falls back to remote stream URLs', () => {
|
||||
const payload = buildDiscordPresenceActivity(baseConfig, {
|
||||
...baseSnapshot,
|
||||
mediaTitle: null,
|
||||
mediaPath:
|
||||
'http://jellyfin.local/Videos/item-1/stream?static=true&api_key=secret-token&MediaSourceId=ms-1',
|
||||
});
|
||||
|
||||
assert.equal(payload.details, 'Unknown media');
|
||||
assert.equal(payload.state, 'Playing 01:35 / 24:10');
|
||||
const serialized = JSON.stringify(payload);
|
||||
assert.equal(serialized.includes('api_key'), false);
|
||||
assert.equal(serialized.includes('secret-token'), false);
|
||||
assert.equal(serialized.includes('/Videos/item-1/stream'), false);
|
||||
});
|
||||
|
||||
test('service deduplicates identical updates and sends changed timeline', async () => {
|
||||
const sent: DiscordActivityPayload[] = [];
|
||||
const timers = new Map<number, () => void>();
|
||||
|
||||
@@ -106,6 +106,15 @@ function basename(filePath: string | null): string {
|
||||
return parts[parts.length - 1] ?? '';
|
||||
}
|
||||
|
||||
function fallbackTitleFromMediaPath(mediaPath: string | null): string {
|
||||
const trimmed = mediaPath?.trim();
|
||||
if (!trimmed) return '';
|
||||
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed) && !trimmed.toLowerCase().startsWith('file://')) {
|
||||
return '';
|
||||
}
|
||||
return basename(trimmed).split(/[?#]/)[0] ?? '';
|
||||
}
|
||||
|
||||
function buildStatus(snapshot: DiscordPresenceSnapshot): string {
|
||||
if (!snapshot.connected || !snapshot.mediaPath) return 'Idle';
|
||||
if (snapshot.paused) return 'Paused';
|
||||
@@ -130,7 +139,10 @@ export function buildDiscordPresenceActivity(
|
||||
): DiscordActivityPayload {
|
||||
const style = resolvePresenceStyle(config.presenceStyle);
|
||||
const status = buildStatus(snapshot);
|
||||
const title = sanitizeText(snapshot.mediaTitle, basename(snapshot.mediaPath) || 'Unknown media');
|
||||
const title = sanitizeText(
|
||||
snapshot.mediaTitle,
|
||||
fallbackTitleFromMediaPath(snapshot.mediaPath) || 'Unknown media',
|
||||
);
|
||||
const details =
|
||||
snapshot.connected && snapshot.mediaPath ? trimField(title) : style.fallbackDetails;
|
||||
const timeline = `${formatClock(snapshot.currentTimeSec)} / ${formatClock(snapshot.mediaDurationSec)}`;
|
||||
|
||||
Reference in New Issue
Block a user