mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-22 14:35:41 +00:00
db98d30a78
* fix(playback): keep browsed server when queue plays elsewhere Lucky Mix on a non-playback server now clears the old queue and pins the active server before building. Now Playing metadata uses apiForServer against the queue server instead of forcing ensurePlaybackServerActive on every activeServerId change. * docs: note PR #768 in CHANGELOG and settings credits * fix(now-playing): gate AudioMuse similar artists on playback server Match getArtistInfoForServer credentials: when browsing another server while the queue plays elsewhere, similar-artist count follows the queue server's AudioMuse flag, not the browsed server.
96 lines
3.7 KiB
TypeScript
96 lines
3.7 KiB
TypeScript
import axios from 'axios';
|
|
import md5 from 'md5';
|
|
import { version } from '../../package.json';
|
|
import { useAuthStore } from '../store/authStore';
|
|
import type { ServerProfile } from '../store/authStoreTypes';
|
|
|
|
export const SUBSONIC_CLIENT = `psysonic/${version}`;
|
|
|
|
export function secureRandomSalt(): string {
|
|
const buf = new Uint8Array(8);
|
|
crypto.getRandomValues(buf);
|
|
return Array.from(buf, b => b.toString(16).padStart(2, '0')).join('');
|
|
}
|
|
|
|
export function getAuthParams(username: string, password: string) {
|
|
const salt = secureRandomSalt();
|
|
const token = md5(password + salt);
|
|
return { u: username, t: token, s: salt, v: '1.16.1', c: SUBSONIC_CLIENT, f: 'json' };
|
|
}
|
|
|
|
export function restBaseFromUrl(serverUrl: string): string {
|
|
const base = serverUrl.startsWith('http') ? serverUrl.replace(/\/$/, '') : `http://${serverUrl.replace(/\/$/, '')}`;
|
|
return `${base}/rest`;
|
|
}
|
|
|
|
export async function apiWithCredentials<T>(
|
|
serverUrl: string,
|
|
username: string,
|
|
password: string,
|
|
endpoint: string,
|
|
extra: Record<string, unknown> = {},
|
|
timeout = 15000,
|
|
): Promise<T> {
|
|
const params = { ...getAuthParams(username, password), ...extra };
|
|
const resp = await axios.get(`${restBaseFromUrl(serverUrl)}/${endpoint}`, {
|
|
params,
|
|
paramsSerializer: { indexes: null },
|
|
timeout,
|
|
});
|
|
const data = resp.data?.['subsonic-response'];
|
|
if (!data) throw new Error('Invalid response from server (possibly not a Subsonic server)');
|
|
if (data.status !== 'ok') throw new Error(data.error?.message ?? 'Subsonic API error');
|
|
return data as T;
|
|
}
|
|
|
|
export function getClient() {
|
|
const { getBaseUrl, getActiveServer } = useAuthStore.getState();
|
|
const server = getActiveServer();
|
|
const baseUrl = getBaseUrl();
|
|
if (!baseUrl) throw new Error('No server configured');
|
|
const params = getAuthParams(server?.username ?? '', server?.password ?? '');
|
|
return { baseUrl: `${baseUrl}/rest`, params };
|
|
}
|
|
|
|
export function getServerById(serverId: string): ServerProfile | undefined {
|
|
return useAuthStore.getState().servers.find(s => s.id === serverId);
|
|
}
|
|
|
|
/** Subsonic REST call against an explicit saved server (not necessarily the active one). */
|
|
export async function apiForServer<T>(
|
|
serverId: string,
|
|
endpoint: string,
|
|
extra: Record<string, unknown> = {},
|
|
timeout = 15000,
|
|
): Promise<T> {
|
|
const server = getServerById(serverId);
|
|
if (!server) throw new Error(`Unknown server: ${serverId}`);
|
|
return apiWithCredentials(server.url, server.username, server.password, endpoint, extra, timeout);
|
|
}
|
|
|
|
export async function api<T>(endpoint: string, extra: Record<string, unknown> = {}, timeout = 15000): Promise<T> {
|
|
const { baseUrl, params } = getClient();
|
|
const resp = await axios.get(`${baseUrl}/${endpoint}`, {
|
|
params: { ...params, ...extra },
|
|
paramsSerializer: { indexes: null },
|
|
timeout,
|
|
});
|
|
const data = resp.data?.['subsonic-response'];
|
|
if (!data) throw new Error('Invalid response from server (possibly not a Subsonic server)');
|
|
if (data.status !== 'ok') throw new Error(data.error?.message ?? 'Subsonic API error');
|
|
return data as T;
|
|
}
|
|
|
|
/** Optional `musicFolderId` when the user narrowed browsing to one Subsonic library (see `getMusicFolders`). */
|
|
export function libraryFilterParams(): Record<string, string | number> {
|
|
const { activeServerId } = useAuthStore.getState();
|
|
return activeServerId ? libraryFilterParamsForServer(activeServerId) : {};
|
|
}
|
|
|
|
/** Library folder filter for an explicit saved server (e.g. Now Playing while browsing another). */
|
|
export function libraryFilterParamsForServer(serverId: string): Record<string, string | number> {
|
|
const f = useAuthStore.getState().musicLibraryFilterByServer[serverId];
|
|
if (f === undefined || f === 'all') return {};
|
|
return { musicFolderId: f };
|
|
}
|