mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-03-03 06:22:41 -08:00
Jellyfin and Subsync Fixes (#13)
This commit is contained in:
@@ -111,6 +111,7 @@ test('composeJellyfinRuntimeHandlers returns callable jellyfin runtime handlers'
|
||||
listJellyfinLibraries: async () => [],
|
||||
listJellyfinItems: async () => [],
|
||||
listJellyfinSubtitleTracks: async () => [],
|
||||
writeJellyfinPreviewAuth: () => {},
|
||||
logInfo: () => {},
|
||||
},
|
||||
handleJellyfinPlayCommandMainDeps: {
|
||||
|
||||
@@ -24,6 +24,7 @@ test('list handler no-ops when no list command is set', async () => {
|
||||
listJellyfinLibraries: async () => [],
|
||||
listJellyfinItems: async () => [],
|
||||
listJellyfinSubtitleTracks: async () => [],
|
||||
writeJellyfinPreviewAuth: () => {},
|
||||
logInfo: () => {},
|
||||
});
|
||||
|
||||
@@ -47,6 +48,7 @@ test('list handler logs libraries', async () => {
|
||||
listJellyfinLibraries: async () => [{ id: 'lib1', name: 'Anime', collectionType: 'tvshows' }],
|
||||
listJellyfinItems: async () => [],
|
||||
listJellyfinSubtitleTracks: async () => [],
|
||||
writeJellyfinPreviewAuth: () => {},
|
||||
logInfo: (message) => logs.push(message),
|
||||
});
|
||||
|
||||
@@ -67,14 +69,19 @@ test('list handler logs libraries', async () => {
|
||||
|
||||
test('list handler resolves items using default library id', async () => {
|
||||
let usedLibraryId = '';
|
||||
let usedRecursive: boolean | undefined;
|
||||
let usedIncludeItemTypes: string | undefined;
|
||||
const logs: string[] = [];
|
||||
const handler = createHandleJellyfinListCommands({
|
||||
listJellyfinLibraries: async () => [],
|
||||
listJellyfinItems: async (_session, _clientInfo, params) => {
|
||||
usedLibraryId = params.libraryId;
|
||||
usedRecursive = params.recursive;
|
||||
usedIncludeItemTypes = params.includeItemTypes;
|
||||
return [{ id: 'item1', title: 'Episode 1', type: 'Episode' }];
|
||||
},
|
||||
listJellyfinSubtitleTracks: async () => [],
|
||||
writeJellyfinPreviewAuth: () => {},
|
||||
logInfo: (message) => logs.push(message),
|
||||
});
|
||||
|
||||
@@ -86,6 +93,8 @@ test('list handler resolves items using default library id', async () => {
|
||||
jellyfinLibraryId: '',
|
||||
jellyfinSearch: 'episode',
|
||||
jellyfinLimit: 10,
|
||||
jellyfinRecursive: false,
|
||||
jellyfinIncludeItemTypes: 'Series,Movie,Folder',
|
||||
} as never,
|
||||
session: baseSession,
|
||||
clientInfo: baseClientInfo,
|
||||
@@ -96,6 +105,8 @@ test('list handler resolves items using default library id', async () => {
|
||||
|
||||
assert.equal(handled, true);
|
||||
assert.equal(usedLibraryId, 'default-lib');
|
||||
assert.equal(usedRecursive, false);
|
||||
assert.equal(usedIncludeItemTypes, 'Series,Movie,Folder');
|
||||
assert.ok(logs.some((line) => line.includes('Jellyfin item: Episode 1 [item1] (Episode)')));
|
||||
});
|
||||
|
||||
@@ -104,6 +115,7 @@ test('list handler throws when items command has no library id', async () => {
|
||||
listJellyfinLibraries: async () => [],
|
||||
listJellyfinItems: async () => [],
|
||||
listJellyfinSubtitleTracks: async () => [],
|
||||
writeJellyfinPreviewAuth: () => {},
|
||||
logInfo: () => {},
|
||||
});
|
||||
|
||||
@@ -132,6 +144,7 @@ test('list handler logs subtitle urls only when requested', async () => {
|
||||
{ index: 1, deliveryUrl: 'http://localhost/sub1.srt', language: 'eng' },
|
||||
{ index: 2, language: 'jpn' },
|
||||
],
|
||||
writeJellyfinPreviewAuth: () => {},
|
||||
logInfo: (message) => logs.push(message),
|
||||
});
|
||||
|
||||
@@ -157,6 +170,7 @@ test('list handler throws when subtitle command has no item id', async () => {
|
||||
listJellyfinLibraries: async () => [],
|
||||
listJellyfinItems: async () => [],
|
||||
listJellyfinSubtitleTracks: async () => [],
|
||||
writeJellyfinPreviewAuth: () => {},
|
||||
logInfo: () => {},
|
||||
});
|
||||
|
||||
@@ -174,3 +188,65 @@ test('list handler throws when subtitle command has no item id', async () => {
|
||||
/Missing --jellyfin-item-id/,
|
||||
);
|
||||
});
|
||||
|
||||
test('list handler writes preview auth payload to response path', async () => {
|
||||
const writes: Array<{
|
||||
path: string;
|
||||
payload: { serverUrl: string; accessToken: string; userId: string };
|
||||
}> = [];
|
||||
const logs: string[] = [];
|
||||
const handler = createHandleJellyfinListCommands({
|
||||
listJellyfinLibraries: async () => [],
|
||||
listJellyfinItems: async () => [],
|
||||
listJellyfinSubtitleTracks: async () => [],
|
||||
writeJellyfinPreviewAuth: (responsePath, payload) => {
|
||||
writes.push({ path: responsePath, payload });
|
||||
},
|
||||
logInfo: (message) => logs.push(message),
|
||||
});
|
||||
|
||||
const handled = await handler({
|
||||
args: {
|
||||
jellyfinPreviewAuth: true,
|
||||
jellyfinResponsePath: '/tmp/subminer-preview-auth.json',
|
||||
} as never,
|
||||
session: baseSession,
|
||||
clientInfo: baseClientInfo,
|
||||
jellyfinConfig: baseConfig,
|
||||
});
|
||||
|
||||
assert.equal(handled, true);
|
||||
assert.deepEqual(writes, [
|
||||
{
|
||||
path: '/tmp/subminer-preview-auth.json',
|
||||
payload: {
|
||||
serverUrl: baseSession.serverUrl,
|
||||
accessToken: baseSession.accessToken,
|
||||
userId: baseSession.userId,
|
||||
},
|
||||
},
|
||||
]);
|
||||
assert.deepEqual(logs, ['Jellyfin preview auth written.']);
|
||||
});
|
||||
|
||||
test('list handler throws when preview auth command has no response path', async () => {
|
||||
const handler = createHandleJellyfinListCommands({
|
||||
listJellyfinLibraries: async () => [],
|
||||
listJellyfinItems: async () => [],
|
||||
listJellyfinSubtitleTracks: async () => [],
|
||||
writeJellyfinPreviewAuth: () => {},
|
||||
logInfo: () => {},
|
||||
});
|
||||
|
||||
await assert.rejects(
|
||||
handler({
|
||||
args: {
|
||||
jellyfinPreviewAuth: true,
|
||||
} as never,
|
||||
session: baseSession,
|
||||
clientInfo: baseClientInfo,
|
||||
jellyfinConfig: baseConfig,
|
||||
}),
|
||||
/Missing --jellyfin-response-path/,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -17,6 +17,12 @@ type JellyfinConfig = {
|
||||
defaultLibraryId: string;
|
||||
};
|
||||
|
||||
type JellyfinPreviewAuthPayload = {
|
||||
serverUrl: string;
|
||||
accessToken: string;
|
||||
userId: string;
|
||||
};
|
||||
|
||||
export function createHandleJellyfinListCommands(deps: {
|
||||
listJellyfinLibraries: (
|
||||
session: JellyfinSession,
|
||||
@@ -25,7 +31,13 @@ export function createHandleJellyfinListCommands(deps: {
|
||||
listJellyfinItems: (
|
||||
session: JellyfinSession,
|
||||
clientInfo: JellyfinClientInfo,
|
||||
params: { libraryId: string; searchTerm?: string; limit: number },
|
||||
params: {
|
||||
libraryId: string;
|
||||
searchTerm?: string;
|
||||
limit: number;
|
||||
recursive?: boolean;
|
||||
includeItemTypes?: string;
|
||||
},
|
||||
) => Promise<Array<{ id: string; title: string; type: string }>>;
|
||||
listJellyfinSubtitleTracks: (
|
||||
session: JellyfinSession,
|
||||
@@ -42,8 +54,9 @@ export function createHandleJellyfinListCommands(deps: {
|
||||
isForced?: boolean;
|
||||
isExternal?: boolean;
|
||||
deliveryUrl?: string | null;
|
||||
}>
|
||||
}>
|
||||
>;
|
||||
writeJellyfinPreviewAuth: (responsePath: string, payload: JellyfinPreviewAuthPayload) => void;
|
||||
logInfo: (message: string) => void;
|
||||
}) {
|
||||
return async (params: {
|
||||
@@ -54,6 +67,20 @@ export function createHandleJellyfinListCommands(deps: {
|
||||
}): Promise<boolean> => {
|
||||
const { args, session, clientInfo, jellyfinConfig } = params;
|
||||
|
||||
if (args.jellyfinPreviewAuth) {
|
||||
const responsePath = args.jellyfinResponsePath?.trim();
|
||||
if (!responsePath) {
|
||||
throw new Error('Missing --jellyfin-response-path for --jellyfin-preview-auth.');
|
||||
}
|
||||
deps.writeJellyfinPreviewAuth(responsePath, {
|
||||
serverUrl: session.serverUrl,
|
||||
accessToken: session.accessToken,
|
||||
userId: session.userId,
|
||||
});
|
||||
deps.logInfo('Jellyfin preview auth written.');
|
||||
return true;
|
||||
}
|
||||
|
||||
if (args.jellyfinLibraries) {
|
||||
const libraries = await deps.listJellyfinLibraries(session, clientInfo);
|
||||
if (libraries.length === 0) {
|
||||
@@ -79,6 +106,8 @@ export function createHandleJellyfinListCommands(deps: {
|
||||
libraryId,
|
||||
searchTerm: args.jellyfinSearch,
|
||||
limit: args.jellyfinLimit ?? 100,
|
||||
recursive: args.jellyfinRecursive,
|
||||
includeItemTypes: args.jellyfinIncludeItemTypes,
|
||||
});
|
||||
if (items.length === 0) {
|
||||
deps.logInfo('No Jellyfin items found for the selected library/search.');
|
||||
|
||||
@@ -31,6 +31,10 @@ test('jellyfin auth commands main deps builder maps callbacks', async () => {
|
||||
|
||||
test('jellyfin list commands main deps builder maps callbacks', async () => {
|
||||
const calls: string[] = [];
|
||||
const writes: Array<{
|
||||
responsePath: string;
|
||||
payload: { serverUrl: string; accessToken: string; userId: string };
|
||||
}> = [];
|
||||
const deps = createBuildHandleJellyfinListCommandsMainDepsHandler({
|
||||
listJellyfinLibraries: async () => {
|
||||
calls.push('libraries');
|
||||
@@ -44,14 +48,32 @@ test('jellyfin list commands main deps builder maps callbacks', async () => {
|
||||
calls.push('subtitles');
|
||||
return [];
|
||||
},
|
||||
writeJellyfinPreviewAuth: (responsePath, payload) => {
|
||||
writes.push({ responsePath, payload });
|
||||
},
|
||||
logInfo: (message) => calls.push(`info:${message}`),
|
||||
})();
|
||||
|
||||
await deps.listJellyfinLibraries({} as never, {} as never);
|
||||
await deps.listJellyfinItems({} as never, {} as never, { libraryId: '', limit: 1 });
|
||||
await deps.listJellyfinSubtitleTracks({} as never, {} as never, 'id');
|
||||
deps.writeJellyfinPreviewAuth('/tmp/jellyfin-preview.json', {
|
||||
serverUrl: 'https://example.test',
|
||||
accessToken: 'token',
|
||||
userId: 'user-id',
|
||||
});
|
||||
deps.logInfo('done');
|
||||
assert.deepEqual(calls, ['libraries', 'items', 'subtitles', 'info:done']);
|
||||
assert.deepEqual(writes, [
|
||||
{
|
||||
responsePath: '/tmp/jellyfin-preview.json',
|
||||
payload: {
|
||||
serverUrl: 'https://example.test',
|
||||
accessToken: 'token',
|
||||
userId: 'user-id',
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('jellyfin play command main deps builder maps callbacks', async () => {
|
||||
|
||||
@@ -32,6 +32,8 @@ export function createBuildHandleJellyfinListCommandsMainDepsHandler(
|
||||
deps.listJellyfinItems(session, clientInfo, params),
|
||||
listJellyfinSubtitleTracks: (session, clientInfo, itemId) =>
|
||||
deps.listJellyfinSubtitleTracks(session, clientInfo, itemId),
|
||||
writeJellyfinPreviewAuth: (responsePath, payload) =>
|
||||
deps.writeJellyfinPreviewAuth(responsePath, payload),
|
||||
logInfo: (message: string) => deps.logInfo(message),
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user