120 lines
3.5 KiB
TypeScript
120 lines
3.5 KiB
TypeScript
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<AuthInfo> | 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<AuthInfo>;
|
|
};
|
|
|
|
export const getAllViews = async (creds: ServerCreds): Promise<View[]> => {
|
|
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<Playlist[]> => {
|
|
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);
|
|
};
|