import { hostname, userInfo } from 'node:os'; import { Readable } from 'stream'; interface AuthInfo { token: string; userId: string; authHeaderValue: string; } export interface ServerCreds { server: string; user: string; password: string; } interface View { Id: string; CollectionType: string; } export interface Playlist { Name: string; Id: string; MediaType: string; songlist: SongMeta[]; } export interface SongMeta { Id: string; Name: string; Path: string; } let tokenPromise: Promise | undefined = undefined; const getAuthHeaderValue = (token?: string) => { let val = `MediaBrowser Client="JF-to-M3U",Device="CLI",DeviceId="${ userInfo().username }-${hostname()}",Version="0.0.1"`; if (token) { val += `,Token="${token}"`; } return val; }; const getAuthInfo = async ({ server, user, password }: ServerCreds) => { if (!tokenPromise) { const url = `${server}/Users/authenticatebyname`; console.log('URL: ', url); const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: getAuthHeaderValue(), }, body: JSON.stringify({ Username: user, Pw: password, }), }); if (!response.ok) { console.error('Error authenticating to server: ', await response.text()); console.log('Exiting due unrecoverable error'); process.exit(); } const authInfo = await response.json(); const token = authInfo.AccessToken; const userId = authInfo.User.Id; const authHeaderValue = getAuthHeaderValue(token); tokenPromise = Promise.resolve({ token, authHeaderValue, userId }); } return tokenPromise as Promise; }; export const getAllViews = async (creds: ServerCreds): Promise => { const auth = await getAuthInfo(creds); const url = `${creds.server}/UserViews?userId=${auth.userId}`; const response = await fetch(url, { headers: { Authorization: auth.authHeaderValue } }); return (await response.json()).Items as View[]; }; export const getItemsByParent = async (creds: ServerCreds, parentId: string) => { const auth = await getAuthInfo(creds); const url = `${creds.server}/Items?userId=${auth.userId}&parentId=${parentId}`; const response = await fetch(url, { headers: { Authorization: auth.authHeaderValue } }); return (await response.json()).Items; }; export const getAllPlaylists = async (creds: ServerCreds): Promise => { const auth = await getAuthInfo(creds); const views = await getAllViews(creds); const musicViews = views.filter(c => c.CollectionType === 'music'); const playlistViews = views.filter(c => c.CollectionType === 'playlists'); const playlists: Playlist[] = ( await Promise.all(playlistViews.map(c => getItemsByParent(creds, c.Id))) ).flat(Infinity); return playlists.filter(p => p.MediaType === 'Audio'); }; export const getSonglistForPlaylist = async (creds: ServerCreds, id: string) => { const auth = await getAuthInfo(creds); const response = await fetch( `${creds.server}/Playlists/${id}/Items?userId=${auth.userId}&fields=Path`, { headers: { Authorization: auth.authHeaderValue }, }, ); return (await response.json()).Items as SongMeta[]; }; export const getSongFileBuffer = async (creds: ServerCreds, id: string) => { const auth = await getAuthInfo(creds); const response = await fetch(`${creds.server}/Items/${id}/File?userId=${auth.userId}`, { headers: { Authorization: auth.authHeaderValue }, }); return Readable.fromWeb(response.body); };