From 6a81fc1be2da5044d7be88de9624623d80700ed4 Mon Sep 17 00:00:00 2001 From: Druidblack <70659424+Druidblack@users.noreply.github.com> Date: Thu, 9 Apr 2026 22:28:32 +0300 Subject: [PATCH 1/3] Update ymbridge.json.example --- config/ymbridge.json.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/ymbridge.json.example b/config/ymbridge.json.example index 72c02003..69cd7aee 100644 --- a/config/ymbridge.json.example +++ b/config/ymbridge.json.example @@ -5,7 +5,7 @@ "data": { "url": "http://192.168.1.161:9980", "apiKey": "change-me", - "interval": 5 + "interval": 3 } } ] -- 2.51.2 From eeb55aa02e84357ab726fb08eba1f0078403b5cb Mon Sep 17 00:00:00 2001 From: Druidblack <70659424+Druidblack@users.noreply.github.com> Date: Thu, 9 Apr 2026 22:31:18 +0300 Subject: [PATCH 2/3] added multiPlatform added multiPlatform and support for receiving data from Yandex smart speakers --- .../sources/YandexMusicBridgeSource.ts | 348 ++++++++++++------ 1 file changed, 235 insertions(+), 113 deletions(-) diff --git a/src/backend/sources/YandexMusicBridgeSource.ts b/src/backend/sources/YandexMusicBridgeSource.ts index db39a543..e1b7ce4a 100644 --- a/src/backend/sources/YandexMusicBridgeSource.ts +++ b/src/backend/sources/YandexMusicBridgeSource.ts @@ -29,15 +29,21 @@ interface BridgeTrackData { explicit?: boolean context_type?: string queue_id?: string + player_id?: string + device_id?: string + device_name?: string + platform?: string source?: string timestamp?: number } -interface BridgeNowPlayingResponse { +interface BridgePlayersResponse { ok: boolean source?: string fresh?: boolean - data?: BridgeTrackData | null + players_count?: number + primary?: BridgeTrackData | null + data?: BridgeTrackData[] | BridgeTrackData | null } interface SyntheticPlaybackState { @@ -48,14 +54,51 @@ interface SyntheticPlaybackState { reachedTrackEndAtMs?: number } + +const normalizeBridgeData = (obj: BridgeTrackData): BridgeTrackData => { + const normalized: BridgeTrackData = { ...obj }; + + if (normalized.progress_ms !== undefined && normalized.progress_ms !== null) { + normalized.progress_ms = Math.max(0, Number(normalized.progress_ms) || 0); + } + + if (normalized.duration_ms !== undefined && normalized.duration_ms !== null) { + const dur = Number(normalized.duration_ms); + normalized.duration_ms = Number.isFinite(dur) && dur > 0 ? dur : undefined; + } + + if (normalized.duration_ms !== undefined && normalized.progress_ms !== undefined) { + normalized.progress_ms = Math.min(normalized.progress_ms, normalized.duration_ms); + } + + return normalized; +} + +const getSafePositionSec = (obj: BridgeTrackData): number | undefined => { + if (obj.progress_ms === undefined || obj.progress_ms === null) { + return undefined; + } + + const progressSec = Math.max(0, obj.progress_ms / 1000); + const durationSec = obj.duration_ms !== undefined && obj.duration_ms !== null && obj.duration_ms > 0 + ? obj.duration_ms / 1000 + : undefined; + + if (obj.source === 'station-local') { + return durationSec !== undefined ? Math.min(progressSec, durationSec) : 0; + } + + return durationSec !== undefined ? Math.min(progressSec, durationSec) : progressSec; +} + export default class YandexMusicBridgeSource extends MemoryPositionalSource { declare config: YandexMusicBridgeSourceConfig; urlData!: URLData; - private syntheticPlayback?: SyntheticPlaybackState; - private lastBridgeData?: BridgeTrackData; - private lastPlay?: PlayObject; - private lastBridgeSeenAtMs?: number; + private syntheticPlaybackByPlayer = new Map(); + private lastBridgeDataByPlayer = new Map(); + private lastPlayByPlayer = new Map(); + private lastBridgeSeenAtMsByPlayer = new Map(); private keepAliveMinSec = 90; private keepAlivePaddingSec = 120; private keepAliveHardCapSec = 600; @@ -65,6 +108,7 @@ export default class YandexMusicBridgeSource extends MemoryPositionalSource { super('ymbridge', name, config, internal, emitter); this.requiresAuth = false; this.canPoll = true; + this.multiPlatform = true; } protected async doBuildInitData(): Promise { @@ -91,36 +135,68 @@ export default class YandexMusicBridgeSource extends MemoryPositionalSource { this.logger.info(`Yandex Music bridge is reachable at ${this.urlData.url.host}`); return true; } - throw new UpstreamError('Bridge health endpoint did not return JSON', {responseBody: resp.body, showStopper: true}); + throw new UpstreamError('Bridge health endpoint did not return JSON', { responseBody: resp.body, showStopper: true }); } catch (e: any) { const hint = e?.response?.text ?? e?.message ?? undefined; throw new UpstreamError(`Could not connect to Yandex Music bridge${hint !== undefined ? ` (${hint})` : ''}`, { cause: e, responseBody: e?.response?.text, showStopper: true }); } } - private async callBridge(): Promise { - const bridgeUrl = joinedUrl(this.urlData.url, 'now-playing').toString(); - const req = request.get(bridgeUrl).timeout({ response: 5000, deadline: 10000 }); + private async callBridge(): Promise { const apiKey = this.config.data?.apiKey; - if (apiKey !== undefined && apiKey.trim() !== '') { - req.set('X-API-Key', apiKey); - } + const makeReq = (path: string) => { + const req = request.get(joinedUrl(this.urlData.url, path).toString()).timeout({ response: 5000, deadline: 10000 }); + if (apiKey !== undefined && apiKey.trim() !== '') { + req.set('X-API-Key', apiKey); + } + return req; + }; + try { - const resp = await req; + const resp = await makeReq('players'); if (resp.body === undefined || typeof resp.body !== 'object') { - throw new UpstreamError('Bridge returned no JSON payload', {responseBody: resp.body, showStopper: true}); + throw new UpstreamError('Bridge returned no JSON payload', { responseBody: resp.body, showStopper: true }); } - return resp.body as BridgeNowPlayingResponse; - } catch (e) { - throw new UpstreamError('Bridge not return an expected response', {responseBody: e?.response?.text, cause: e, showStopper: true}); + return resp.body as BridgePlayersResponse; + } catch (e: any) { + if (e?.status !== 404) { + throw new UpstreamError('Bridge did not return an expected response from /players', { responseBody: e?.response?.text, cause: e, showStopper: true }); + } + const resp = await makeReq('now-playing'); + if (resp.body === undefined || typeof resp.body !== 'object') { + throw new UpstreamError('Bridge returned no JSON payload', { responseBody: resp.body, showStopper: true }); + } + const body = resp.body as any; + const single = body?.data ?? null; + return { + ok: body?.ok ?? true, + source: body?.source, + fresh: body?.fresh, + players_count: single ? 1 : 0, + primary: single, + data: single ? [single] : [], + }; } } - private resetSyntheticPlayback() { - this.syntheticPlayback = undefined; - this.lastBridgeData = undefined; - this.lastPlay = undefined; - this.lastBridgeSeenAtMs = undefined; + private getPlayerId(bridgeData: BridgeTrackData): string { + const raw = bridgeData.player_id ?? bridgeData.device_id ?? bridgeData.queue_id ?? bridgeData.track_id; + return raw !== undefined && raw !== null && `${raw}`.trim() !== '' ? `${raw}` : NO_DEVICE; + } + + private isStationLocal(bridgeData: BridgeTrackData): boolean { + return bridgeData.source === 'station-local'; + } + + private removePlayerState(playerId: string): void { + this.syntheticPlaybackByPlayer.delete(playerId); + this.lastBridgeDataByPlayer.delete(playerId); + this.lastPlayByPlayer.delete(playerId); + this.lastBridgeSeenAtMsByPlayer.delete(playerId); + } + + private clearSyntheticPlayback(playerId: string): void { + this.syntheticPlaybackByPlayer.delete(playerId); } private getSyntheticKey(bridgeData: BridgeTrackData, play: PlayObject): string { @@ -128,7 +204,7 @@ export default class YandexMusicBridgeSource extends MemoryPositionalSource { ? bridgeData.artists_list.join(',') : (bridgeData.artists ?? play.data.artists?.join(',') ?? ''); return [ - bridgeData.queue_id ?? '', + this.getPlayerId(bridgeData), bridgeData.track_id ?? '', bridgeData.title ?? play.data.track ?? '', artists, @@ -136,55 +212,58 @@ export default class YandexMusicBridgeSource extends MemoryPositionalSource { ].join('::'); } - private getPlaybackState( - bridgeData: BridgeTrackData, - play: PlayObject, - options: { keepAlive?: boolean } = {}, - ): { status: typeof REPORTED_PLAYER_STATUSES.playing, position: number } { - const { keepAlive = false } = options; + private getPlaybackState(playerId: string, bridgeData: BridgeTrackData, play: PlayObject): { status: typeof REPORTED_PLAYER_STATUSES.playing, position: number } { const now = Date.now(); const reportedPositionSec = bridgeData.progress_ms !== undefined && bridgeData.progress_ms !== null ? Math.max(0, bridgeData.progress_ms / 1000) : undefined; const durationSec = play.data.duration; + + if (this.isStationLocal(bridgeData)) { + const position = durationSec !== undefined && durationSec > 0 + ? Math.min(reportedPositionSec ?? play.meta.trackProgressPosition ?? 0, durationSec) + : (reportedPositionSec ?? play.meta.trackProgressPosition ?? 0); + this.clearSyntheticPlayback(playerId); + return { + status: REPORTED_PLAYER_STATUSES.playing, + position: Math.max(0, position), + }; + } const key = this.getSyntheticKey(bridgeData, play); + const current = this.syntheticPlaybackByPlayer.get(playerId); - if (this.syntheticPlayback === undefined || this.syntheticPlayback.key !== key) { + if (current === undefined || current.key !== key) { const initialPosition = reportedPositionSec ?? play.meta.trackProgressPosition ?? 0; - this.syntheticPlayback = { + this.syntheticPlaybackByPlayer.set(playerId, { key, lastSeenAtMs: now, lastPositionSec: initialPosition, durationSec, reachedTrackEndAtMs: durationSec !== undefined && durationSec > 0 && initialPosition >= durationSec ? now : undefined, - }; + }); return { status: REPORTED_PLAYER_STATUSES.playing, position: durationSec !== undefined && durationSec > 0 ? Math.min(initialPosition, durationSec) : initialPosition, }; } - const elapsedSec = Math.max(0, (now - this.syntheticPlayback.lastSeenAtMs) / 1000); + const elapsedSec = Math.max(0, (now - current.lastSeenAtMs) / 1000); const startingPoint = reportedPositionSec !== undefined - ? Math.max(reportedPositionSec, this.syntheticPlayback.lastPositionSec) - : this.syntheticPlayback.lastPositionSec; + ? Math.max(reportedPositionSec, current.lastPositionSec) + : current.lastPositionSec; let nextPosition = startingPoint + elapsedSec; - - if (durationSec !== undefined && durationSec > 0) { - // Never let synthetic time run past track duration. Once it reaches the end - // we will keep the player alive briefly and then emit a synthetic STOP. - if (nextPosition >= durationSec) { - nextPosition = durationSec; - if (this.syntheticPlayback.reachedTrackEndAtMs === undefined) { - this.syntheticPlayback.reachedTrackEndAtMs = now; - } + if (durationSec !== undefined && durationSec > 0 && nextPosition >= durationSec) { + nextPosition = durationSec; + if (current.reachedTrackEndAtMs === undefined) { + current.reachedTrackEndAtMs = now; } } - this.syntheticPlayback.lastSeenAtMs = now; - this.syntheticPlayback.lastPositionSec = nextPosition; - this.syntheticPlayback.durationSec = durationSec; + current.lastSeenAtMs = now; + current.lastPositionSec = nextPosition; + current.durationSec = durationSec; + this.syntheticPlaybackByPlayer.set(playerId, current); return { status: REPORTED_PLAYER_STATUSES.playing, @@ -192,14 +271,22 @@ export default class YandexMusicBridgeSource extends MemoryPositionalSource { }; } - private shouldKeepAliveSynthetic(nowMs: number = Date.now()): boolean { - if (this.syntheticPlayback === undefined || this.lastPlay === undefined || this.lastBridgeData === undefined || this.lastBridgeSeenAtMs === undefined) { + private shouldKeepAliveSynthetic(playerId: string, nowMs: number = Date.now()): boolean { + const lastBridgeData = this.lastBridgeDataByPlayer.get(playerId); + if (lastBridgeData !== undefined && this.isStationLocal(lastBridgeData)) { + return false; + } + + const synthetic = this.syntheticPlaybackByPlayer.get(playerId); + const lastPlay = this.lastPlayByPlayer.get(playerId); + const lastSeen = this.lastBridgeSeenAtMsByPlayer.get(playerId); + if (synthetic === undefined || lastPlay === undefined || lastBridgeData === undefined || lastSeen === undefined) { return false; } - const silenceSec = Math.max(0, (nowMs - this.lastBridgeSeenAtMs) / 1000); - const durationSec = this.syntheticPlayback.durationSec ?? this.lastPlay.data.duration; - const currentPosSec = this.syntheticPlayback.lastPositionSec ?? this.lastPlay.meta.trackProgressPosition ?? 0; + const silenceSec = Math.max(0, (nowMs - lastSeen) / 1000); + const durationSec = synthetic.durationSec ?? lastPlay.data.duration; + const currentPosSec = synthetic.lastPositionSec ?? lastPlay.meta.trackProgressPosition ?? 0; let allowedSilenceSec = this.keepAliveMinSec; if (durationSec !== undefined && durationSec > 0) { @@ -212,103 +299,138 @@ export default class YandexMusicBridgeSource extends MemoryPositionalSource { return true; } - this.logger.debug(`Dropping synthetic keepalive after ${silenceSec.toFixed(0)}s without bridge data (allowed ${allowedSilenceSec.toFixed(0)}s)`); + this.logger.debug(`Dropping synthetic keepalive for player ${playerId} after ${silenceSec.toFixed(0)}s without bridge data (allowed ${allowedSilenceSec.toFixed(0)}s)`); return false; } - private shouldFinalizeSyntheticTrack(nowMs: number = Date.now()): boolean { - if (this.syntheticPlayback === undefined || this.lastPlay === undefined) { + private shouldFinalizeSyntheticTrack(playerId: string, nowMs: number = Date.now()): boolean { + const synthetic = this.syntheticPlaybackByPlayer.get(playerId); + const lastPlay = this.lastPlayByPlayer.get(playerId); + if (synthetic === undefined || lastPlay === undefined) { return false; } - const durationSec = this.syntheticPlayback.durationSec ?? this.lastPlay.data.duration; + const durationSec = synthetic.durationSec ?? lastPlay.data.duration; if (durationSec === undefined || durationSec <= 0) { return false; } - if (this.syntheticPlayback.lastPositionSec < durationSec) { + if (synthetic.lastPositionSec < durationSec) { return false; } - if (this.syntheticPlayback.reachedTrackEndAtMs === undefined) { - this.syntheticPlayback.reachedTrackEndAtMs = nowMs; + if (synthetic.reachedTrackEndAtMs === undefined) { + synthetic.reachedTrackEndAtMs = nowMs; + this.syntheticPlaybackByPlayer.set(playerId, synthetic); return false; } - const sinceEndSec = Math.max(0, (nowMs - this.syntheticPlayback.reachedTrackEndAtMs) / 1000); + const sinceEndSec = Math.max(0, (nowMs - synthetic.reachedTrackEndAtMs) / 1000); return sinceEndSec >= this.postEndStopGraceSec; } - private buildStoppedState(): PlayerStateDataMaybePlay | undefined { - if (this.lastBridgeData === undefined) { + private buildStoppedState(playerId: string): PlayerStateDataMaybePlay | undefined { + const bridgeData = this.lastBridgeDataByPlayer.get(playerId); + if (bridgeData === undefined) { return undefined; } return { - platformId: [NO_DEVICE, NO_USER], - sessionId: this.lastBridgeData.queue_id ?? this.lastBridgeData.track_id, + platformId: [playerId, NO_USER], + sessionId: bridgeData.queue_id ?? bridgeData.track_id ?? playerId, status: REPORTED_PLAYER_STATUSES.stopped, }; } getRecentlyPlayed = async (options: RecentlyPlayedOptions = {}) => { const payload = await this.callBridge(); - const bridgeData = payload.data; + const rawData = payload.data; + const players = Array.isArray(rawData) + ? rawData.filter((x): x is BridgeTrackData => x !== null && x !== undefined && typeof x === 'object') + : (rawData !== undefined && rawData !== null && typeof rawData === 'object' ? [rawData as BridgeTrackData] : []); const nowMs = Date.now(); + const seenPlayerIds = new Set(); + const states: PlayerStateDataMaybePlay[] = []; + + for (const rawBridgeData of players) { + const bridgeData = normalizeBridgeData(rawBridgeData); + if (!payload.ok || bridgeData.title === undefined || bridgeData.title === null || `${bridgeData.title}`.trim() === '') { + continue; + } + const playerId = this.getPlayerId(bridgeData); + seenPlayerIds.add(playerId); + const play = formatPlayObj(bridgeData, playerId); + this.lastBridgeDataByPlayer.set(playerId, bridgeData); + this.lastPlayByPlayer.set(playerId, play); + this.lastBridgeSeenAtMsByPlayer.set(playerId, nowMs); + + if (this.isStationLocal(bridgeData) && bridgeData.paused === true) { + this.clearSyntheticPlayback(playerId); + states.push({ + platformId: [playerId, NO_USER], + sessionId: bridgeData.queue_id ?? bridgeData.track_id ?? playerId, + status: REPORTED_PLAYER_STATUSES.paused, + play, + position: getSafePositionSec(bridgeData) ?? play.meta.trackProgressPosition ?? 0, + }); + continue; + } + + const playbackState = this.getPlaybackState(playerId, bridgeData, play); - if (payload.ok && bridgeData !== undefined && bridgeData !== null && bridgeData.title) { - const play = formatPlayObj(bridgeData); - const playbackState = this.getPlaybackState(bridgeData, play); - this.lastBridgeData = bridgeData; - this.lastPlay = play; - this.lastBridgeSeenAtMs = nowMs; - - if (this.shouldFinalizeSyntheticTrack(nowMs)) { - const durationSec = this.syntheticPlayback?.durationSec ?? play.data.duration ?? 0; - this.logger.info(`Synthetic playback exceeded track duration for '${play.data.artists?.join(', ') ?? 'Unknown'} - ${play.data.track ?? 'Unknown'}'; emitting STOP after ${this.postEndStopGraceSec}s past track end at ${durationSec.toFixed(0)}s.`); - const stoppedState = this.buildStoppedState(); - this.resetSyntheticPlayback(); - return await this.processRecentPlays(stoppedState !== undefined ? [stoppedState] : []); + if (this.shouldFinalizeSyntheticTrack(playerId, nowMs)) { + const durationSec = this.syntheticPlaybackByPlayer.get(playerId)?.durationSec ?? play.data.duration ?? 0; + this.logger.info(`Synthetic playback exceeded track duration for player ${playerId} '${play.data.artists?.join(', ') ?? 'Unknown'} - ${play.data.track ?? 'Unknown'}'; emitting STOP after ${this.postEndStopGraceSec}s past track end at ${durationSec.toFixed(0)}s.`); + const stoppedState = this.buildStoppedState(playerId); + this.removePlayerState(playerId); + if (stoppedState !== undefined) { + states.push(stoppedState); + } + continue; } - const playerState: PlayerStateData = { - platformId: [NO_DEVICE, NO_USER], - sessionId: bridgeData.queue_id ?? bridgeData.track_id, + states.push({ + platformId: [playerId, NO_USER], + sessionId: bridgeData.queue_id ?? bridgeData.track_id ?? playerId, status: playbackState.status, play, position: playbackState.position, - }; - - return await this.processRecentPlays([playerState]); + }); } - if (this.shouldKeepAliveSynthetic(nowMs)) { - const bridgeDataForKeepAlive = this.lastBridgeData!; - const playForKeepAlive = this.lastPlay!; - const playbackState = this.getPlaybackState(bridgeDataForKeepAlive, playForKeepAlive, { keepAlive: true }); - - if (this.shouldFinalizeSyntheticTrack(nowMs)) { - const durationSec = this.syntheticPlayback?.durationSec ?? playForKeepAlive.data.duration ?? 0; - this.logger.info(`Synthetic keepalive exceeded track duration for '${playForKeepAlive.data.artists?.join(', ') ?? 'Unknown'} - ${playForKeepAlive.data.track ?? 'Unknown'}'; emitting STOP after ${this.postEndStopGraceSec}s past track end at ${durationSec.toFixed(0)}s.`); - const stoppedState = this.buildStoppedState(); - this.resetSyntheticPlayback(); - return await this.processRecentPlays(stoppedState !== undefined ? [stoppedState] : []); + for (const [playerId, lastPlay] of Array.from(this.lastPlayByPlayer.entries())) { + if (seenPlayerIds.has(playerId)) { + continue; } + if (this.shouldKeepAliveSynthetic(playerId, nowMs)) { + const bridgeDataForKeepAlive = this.lastBridgeDataByPlayer.get(playerId)!; + const playbackState = this.getPlaybackState(playerId, bridgeDataForKeepAlive, lastPlay); + + if (this.shouldFinalizeSyntheticTrack(playerId, nowMs)) { + const durationSec = this.syntheticPlaybackByPlayer.get(playerId)?.durationSec ?? lastPlay.data.duration ?? 0; + this.logger.info(`Synthetic keepalive exceeded track duration for player ${playerId} '${lastPlay.data.artists?.join(', ') ?? 'Unknown'} - ${lastPlay.data.track ?? 'Unknown'}'; emitting STOP after ${this.postEndStopGraceSec}s past track end at ${durationSec.toFixed(0)}s.`); + const stoppedState = this.buildStoppedState(playerId); + this.removePlayerState(playerId); + if (stoppedState !== undefined) { + states.push(stoppedState); + } + continue; + } - this.logger.trace(`Bridge returned no current track; keeping synthetic playback alive for ${playForKeepAlive.data.artists?.join(', ') ?? 'Unknown'} - ${playForKeepAlive.data.track ?? 'Unknown'}`); - - const playerState: PlayerStateData = { - platformId: [NO_DEVICE, NO_USER], - sessionId: bridgeDataForKeepAlive.queue_id ?? bridgeDataForKeepAlive.track_id, - status: REPORTED_PLAYER_STATUSES.playing, - play: playForKeepAlive, - position: playbackState.position, - }; + this.logger.trace(`Bridge returned no current track for player ${playerId}; keeping synthetic playback alive for ${lastPlay.data.artists?.join(', ') ?? 'Unknown'} - ${lastPlay.data.track ?? 'Unknown'}`); + states.push({ + platformId: [playerId, NO_USER], + sessionId: bridgeDataForKeepAlive.queue_id ?? bridgeDataForKeepAlive.track_id ?? playerId, + status: REPORTED_PLAYER_STATUSES.playing, + play: lastPlay, + position: playbackState.position, + }); + continue; + } - return await this.processRecentPlays([playerState]); + this.removePlayerState(playerId); } - this.resetSyntheticPlayback(); - return await this.processRecentPlays([]); + return await this.processRecentPlays(states); } } -const formatPlayObj = (obj: BridgeTrackData): PlayObject => { +const formatPlayObj = (obj: BridgeTrackData, playerId: string): PlayObject => { const artists = Array.isArray(obj.artists_list) && obj.artists_list.length > 0 ? obj.artists_list : (obj.artists ? obj.artists.split(/\s*,\s*/).filter(x => x.trim() !== '') : []); @@ -323,10 +445,10 @@ const formatPlayObj = (obj: BridgeTrackData): PlayObject => { : undefined, }, meta: { - trackProgressPosition: obj.progress_ms !== undefined && obj.progress_ms !== null ? obj.progress_ms / 1000 : undefined, - deviceId: NO_DEVICE, - mediaPlayerName: 'Yandex Music', - mediaPlayerVersion: 'bridge', + trackProgressPosition: getSafePositionSec(obj), + deviceId: playerId || NO_DEVICE, + mediaPlayerName: obj.device_name ?? (obj.source === 'station-local' ? 'Yandex Station' : 'Yandex Music'), + mediaPlayerVersion: obj.platform ?? (obj.source === 'station-local' ? 'station-local' : 'bridge'), comment: obj.track_id !== undefined ? `Yandex Track ${obj.track_id}` : undefined, art: obj.cover !== undefined && obj.cover !== null && obj.cover !== '' ? { album: obj.cover } : undefined, } -- 2.51.2 From 8a952cb57634756f0101e0ce4d26a363ab34a11a Mon Sep 17 00:00:00 2001 From: Druidblack <70659424+Druidblack@users.noreply.github.com> Date: Thu, 9 Apr 2026 22:32:19 +0300 Subject: [PATCH 3/3] error correction at startup. --- src/backend/sources/ScrobbleSources.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backend/sources/ScrobbleSources.ts b/src/backend/sources/ScrobbleSources.ts index b1a26a58..8a600780 100644 --- a/src/backend/sources/ScrobbleSources.ts +++ b/src/backend/sources/ScrobbleSources.ts @@ -1,7 +1,7 @@ /* eslint-disable no-case-declarations */ import { childLogger, Logger } from '@foxxmd/logging'; import EventEmitter from "events"; -import { ConfigMeta, CONFIGURE_AS, ConfigureAs, ConfigureAsSource, InternalConfig, InternalConfigOptional } from "../common/infrastructure/Atomic.js"; +import { ConfigMeta, ConfigureAs, ConfigureAsSource, InternalConfig, InternalConfigOptional } from "../common/infrastructure/Atomic.js"; import { isSourceType } from '../common/infrastructure/config/source/sources.js'; import { sourceTypes } from '../common/infrastructure/config/source/sources.js'; import { SourceType } from '../common/infrastructure/config/source/sources.js'; @@ -270,7 +270,7 @@ export default class ScrobbleSources { } for (const sourceType of sourceTypes) { - let defaultConfigureAs: ConfigureAsSource = CONFIGURE_AS.source; + let defaultConfigureAs: ConfigureAsSource = 'source'; // env builder for single user mode switch (sourceType) { case 'spotify': -- 2.51.2