jellyfin-to-m3u/lib/services/jellyfin.ts

121 lines
3.5 KiB
TypeScript
Raw Normal View History

2024-08-20 06:19:59 +00:00
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);
};