made some nice progress today!
This commit is contained in:
parent
78b9857f57
commit
7c5d2a25ff
15 changed files with 1065 additions and 330 deletions
|
@ -5,14 +5,27 @@ import createClient from "openapi-fetch";
|
|||
import { query } from "@solidjs/router";
|
||||
import { Entry } from "../types";
|
||||
|
||||
type ItemImageType = "Primary" | "Art" | "Backdrop" | "Banner" | "Logo" | "Thumb" | "Disc" | "Box" | "Screenshot" | "Menu" | "Chapter" | "BoxRear" | "Profile";
|
||||
type ItemImageType =
|
||||
| "Primary"
|
||||
| "Art"
|
||||
| "Backdrop"
|
||||
| "Banner"
|
||||
| "Logo"
|
||||
| "Thumb"
|
||||
| "Disc"
|
||||
| "Box"
|
||||
| "Screenshot"
|
||||
| "Menu"
|
||||
| "Chapter"
|
||||
| "BoxRear"
|
||||
| "Profile";
|
||||
|
||||
const baseUrl = process.env.JELLYFIN_BASE_URL;
|
||||
const client = createClient<paths>({
|
||||
baseUrl,
|
||||
headers: {
|
||||
'Authorization': `MediaBrowser DeviceId="Streamarr", Token="${process.env.JELLYFIN_API_KEY}"`,
|
||||
'Content-Type': 'application/json; profile="CamelCase"',
|
||||
Authorization: `MediaBrowser DeviceId="Streamarr", Token="${process.env.JELLYFIN_API_KEY}"`,
|
||||
"Content-Type": 'application/json; profile="CamelCase"',
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -21,21 +34,24 @@ export const TEST = query(async () => {
|
|||
const itemId = "919dfa97-e4da-d275-8a92-5d056e590a28";
|
||||
const seriesId = "5230ddbcd-9400-733d-c07e-5b8cb7a4f49";
|
||||
|
||||
const { data: seriesData } = await client.GET("/UserItems/{itemId}/UserData", {
|
||||
params: {
|
||||
path: { itemId: seriesId },
|
||||
query: { userId }
|
||||
}
|
||||
});
|
||||
const { data: seriesData } = await client.GET(
|
||||
"/UserItems/{itemId}/UserData",
|
||||
{
|
||||
params: {
|
||||
path: { itemId: seriesId },
|
||||
query: { userId },
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const { data: epData } = await client.GET("/UserItems/{itemId}/UserData", {
|
||||
params: {
|
||||
path: { itemId },
|
||||
query: { userId }
|
||||
}
|
||||
query: { userId },
|
||||
},
|
||||
});
|
||||
|
||||
console.log(seriesData, epData)
|
||||
console.log(seriesData, epData);
|
||||
}, "jellyfin.TEST");
|
||||
|
||||
export const getCurrentUser = query(async () => {
|
||||
|
@ -43,8 +59,6 @@ export const getCurrentUser = query(async () => {
|
|||
params: {},
|
||||
});
|
||||
|
||||
console.log(data, error, response)
|
||||
|
||||
return data;
|
||||
}, "jellyfin.getCurrentUser");
|
||||
|
||||
|
@ -56,138 +70,170 @@ export const listUsers = query(async () => {
|
|||
return data ?? [];
|
||||
}, "jellyfin.listUsers");
|
||||
|
||||
export const listItems = query(async (userId: string): Promise<Entry[] | undefined> => {
|
||||
const { data, error } = await client.GET("/Items", {
|
||||
params: {
|
||||
query: {
|
||||
userId,
|
||||
hasTmdbInfo: true,
|
||||
recursive: true,
|
||||
includeItemTypes: ["Movie", "Series"],
|
||||
fields: [
|
||||
"ProviderIds",
|
||||
"Genres",
|
||||
"DateLastMediaAdded",
|
||||
"DateCreated",
|
||||
"MediaSources",
|
||||
],
|
||||
export const listItems = query(
|
||||
async (userId: string): Promise<Entry[] | undefined> => {
|
||||
const { data, error } = await client.GET("/Items", {
|
||||
params: {
|
||||
query: {
|
||||
userId,
|
||||
hasTmdbInfo: true,
|
||||
recursive: true,
|
||||
includeItemTypes: ["Movie", "Series"],
|
||||
fields: [
|
||||
"ProviderIds",
|
||||
"Genres",
|
||||
"DateLastMediaAdded",
|
||||
"DateCreated",
|
||||
"MediaSources",
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
if (data === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return (
|
||||
data.Items?.map((item) => ({
|
||||
// id: item.Id!,
|
||||
id: item.ProviderIds!["Tmdb"]!,
|
||||
title: item.Name!,
|
||||
thumbnail: new URL(`/Items/${item.Id!}/Images/Primary`, baseUrl), //await getItemImage(data.Id!, 'Primary'),
|
||||
})) ?? []
|
||||
);
|
||||
},
|
||||
"jellyfin.listItems",
|
||||
);
|
||||
|
||||
export const getRandomItem = query(
|
||||
async (userId: string): Promise<Entry | undefined> =>
|
||||
getRandomItems(userId, 1).then((items) => items?.at(0)),
|
||||
"jellyfin.listRandomItem",
|
||||
);
|
||||
|
||||
export const getRandomItems = query(
|
||||
async (userId: string, limit: number = 10): Promise<Entry[]> => {
|
||||
const { data, error } = await client.GET("/Items", {
|
||||
params: {
|
||||
query: {
|
||||
userId,
|
||||
hasTmdbInfo: true,
|
||||
recursive: true,
|
||||
limit,
|
||||
sortBy: ["Random"],
|
||||
includeItemTypes: ["Movie", "Series"],
|
||||
imageTypes: ["Primary", "Backdrop", "Thumb"],
|
||||
fields: [
|
||||
"ProviderIds",
|
||||
"Genres",
|
||||
"DateLastMediaAdded",
|
||||
"DateCreated",
|
||||
"MediaSources",
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
data?.Items?.map((item) => ({
|
||||
// id: item.Id!,
|
||||
id: item.ProviderIds!["Tmdb"]!,
|
||||
title: item.Name!,
|
||||
thumbnail: new URL(`/Items/${item.Id!}/Images/Primary`, baseUrl), //await getItemImage(data.Id!, 'Primary'),
|
||||
image: new URL(`/Items/${item.Id!}/Images/Backdrop`, baseUrl), //await getItemImage(data.Id!, 'Primary'),
|
||||
})) ?? []
|
||||
);
|
||||
},
|
||||
"jellyfin.listRandomItems",
|
||||
);
|
||||
|
||||
export const getItem = query(
|
||||
async (userId: string, itemId: string): Promise<Entry | undefined> => {
|
||||
const { data, error } = await client.GET("/Items/{itemId}", {
|
||||
params: {
|
||||
path: {
|
||||
itemId,
|
||||
},
|
||||
query: {
|
||||
userId,
|
||||
hasTmdbInfo: true,
|
||||
recursive: true,
|
||||
includeItemTypes: ["Movie", "Series"],
|
||||
fields: [
|
||||
"ProviderIds",
|
||||
"Genres",
|
||||
"DateLastMediaAdded",
|
||||
"DateCreated",
|
||||
"MediaSources",
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (data === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
// id: data.Id!,
|
||||
id: data.ProviderIds!["Tmdb"]!,
|
||||
title: data.Name!,
|
||||
overview: data.Overview!,
|
||||
thumbnail: new URL(`/Items/${itemId}/Images/Primary`, baseUrl), //await getItemImage(data.Id!, 'Primary'),
|
||||
image: new URL(`/Items/${itemId}/Images/Backdrop`, baseUrl),
|
||||
// ...data,
|
||||
};
|
||||
},
|
||||
"jellyfin.getItem",
|
||||
);
|
||||
|
||||
export const getItemImage = query(
|
||||
async (
|
||||
itemId: string,
|
||||
imageType: ItemImageType,
|
||||
): Promise<any | undefined> => {
|
||||
const { data, error } = await client.GET(
|
||||
"/Items/{itemId}/Images/{imageType}",
|
||||
{
|
||||
parseAs: "blob",
|
||||
params: {
|
||||
path: {
|
||||
itemId,
|
||||
imageType,
|
||||
},
|
||||
query: {},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
return data;
|
||||
},
|
||||
"jellyfin.getItemImage",
|
||||
);
|
||||
|
||||
export const getItemPlaybackInfo = query(
|
||||
async (userId: string, itemId: string): Promise<any | undefined> => {
|
||||
const { data, error, response } = await client.GET(
|
||||
"/Items/{itemId}/PlaybackInfo",
|
||||
{
|
||||
parseAs: "text",
|
||||
|
||||
params: {
|
||||
path: {
|
||||
itemId,
|
||||
},
|
||||
query: {
|
||||
userId,
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
if (data === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return data.Items?.map(item => ({
|
||||
id: item.Id!,
|
||||
title: item.Name!,
|
||||
thumbnail: new URL(`/Items/${item.Id!}/Images/Primary`, baseUrl), //await getItemImage(data.Id!, 'Primary'),
|
||||
})) ?? [];
|
||||
}, "jellyfin.listItems");
|
||||
|
||||
export const getRandomItem = query(async (userId: string): Promise<Entry | undefined> => getRandomItems(userId, 1).then(items => items?.at(0)), "jellyfin.listRandomItem");
|
||||
|
||||
export const getRandomItems = query(async (userId: string, limit: number = 10): Promise<Entry[]> => {
|
||||
const { data, error } = await client.GET("/Items", {
|
||||
params: {
|
||||
query: {
|
||||
userId,
|
||||
hasTmdbInfo: true,
|
||||
recursive: true,
|
||||
limit,
|
||||
sortBy: ["Random"],
|
||||
includeItemTypes: ["Movie", "Series"],
|
||||
imageTypes: ["Primary", "Backdrop", "Thumb"],
|
||||
fields: [
|
||||
"ProviderIds",
|
||||
"Genres",
|
||||
"DateLastMediaAdded",
|
||||
"DateCreated",
|
||||
"MediaSources",
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return data?.Items?.map(item => ({
|
||||
id: item.Id!,
|
||||
title: item.Name!,
|
||||
thumbnail: new URL(`/Items/${item.Id!}/Images/Primary`, baseUrl), //await getItemImage(data.Id!, 'Primary'),
|
||||
image: new URL(`/Items/${item.Id!}/Images/Backdrop`, baseUrl), //await getItemImage(data.Id!, 'Primary'),
|
||||
})) ?? [];
|
||||
}, "jellyfin.listRandomItems");
|
||||
|
||||
export const getItem = query(async (userId: string, itemId: string): Promise<Entry | undefined> => {
|
||||
console.log('baseUrl', baseUrl);
|
||||
|
||||
const { data, error } = await client.GET("/Items/{itemId}", {
|
||||
params: {
|
||||
path: {
|
||||
itemId,
|
||||
},
|
||||
query: {
|
||||
userId,
|
||||
hasTmdbInfo: true,
|
||||
recursive: true,
|
||||
includeItemTypes: ["Movie", "Series"],
|
||||
fields: [
|
||||
"ProviderIds",
|
||||
"Genres",
|
||||
"DateLastMediaAdded",
|
||||
"DateCreated",
|
||||
"MediaSources",
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (data === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
id: data.Id!,
|
||||
title: data.Name!,
|
||||
synopsis: data.Overview!,
|
||||
thumbnail: new URL(`/Items/${itemId}/Images/Primary`, baseUrl), //await getItemImage(data.Id!, 'Primary'),
|
||||
image: new URL(`/Items/${itemId}/Images/Backdrop`, baseUrl),
|
||||
// ...data,
|
||||
};
|
||||
}, "jellyfin.getItem");
|
||||
|
||||
export const getItemImage = query(async (itemId: string, imageType: ItemImageType): Promise<any | undefined> => {
|
||||
const { data, error } = await client.GET("/Items/{itemId}/Images/{imageType}", {
|
||||
parseAs: 'blob',
|
||||
params: {
|
||||
path: {
|
||||
itemId,
|
||||
imageType
|
||||
},
|
||||
query: {
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return data;
|
||||
}, "jellyfin.getItemImage");
|
||||
|
||||
export const getItemPlaybackInfo = query(async (userId: string, itemId: string): Promise<any | undefined> => {
|
||||
const { data, error, response } = await client.GET("/Items/{itemId}/PlaybackInfo", {
|
||||
parseAs: 'text',
|
||||
|
||||
params: {
|
||||
path: {
|
||||
itemId,
|
||||
},
|
||||
query: {
|
||||
userId,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return undefined;
|
||||
}, "jellyfin.getItemPlaybackInfo");
|
||||
},
|
||||
"jellyfin.getItemPlaybackInfo",
|
||||
);
|
||||
|
||||
export const queryItems = query(async () => {
|
||||
const { data, error } = await client.GET("/Items", {
|
||||
|
@ -204,35 +250,57 @@ export const queryItems = query(async () => {
|
|||
});
|
||||
|
||||
console.log(data);
|
||||
}, "jellyfin.queryItems");
|
||||
|
||||
}, 'jellyfin.queryItems');
|
||||
|
||||
export const getContinueWatching = query(async (userId: string): Promise<Entry[]> => {
|
||||
const { data, error } = await client.GET("/UserItems/Resume", {
|
||||
params: {
|
||||
query: {
|
||||
userId,
|
||||
mediaTypes: ["Video"],
|
||||
// fields: ["ProviderIds", "Genres"],
|
||||
// includeItemTypes: ["Series", "Movie"]
|
||||
export const getContinueWatching = query(
|
||||
async (userId: string): Promise<Entry[]> => {
|
||||
const { data, error } = await client.GET("/UserItems/Resume", {
|
||||
params: {
|
||||
query: {
|
||||
userId,
|
||||
mediaTypes: ["Video"],
|
||||
// fields: ["ProviderIds", "Genres"],
|
||||
// includeItemTypes: ["Series", "Movie"]
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
if (Array.isArray(data?.Items) !== true) {
|
||||
return [];
|
||||
if (Array.isArray(data?.Items) !== true) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const uniqueIds = new Set<string>(
|
||||
data.Items.map((item) =>
|
||||
item.Type === "Episode" ? item.SeriesId! : item.Id!,
|
||||
),
|
||||
);
|
||||
const results = await Promise.allSettled(
|
||||
uniqueIds
|
||||
.values()
|
||||
.map((id) => getItem(userId, id))
|
||||
.toArray(),
|
||||
);
|
||||
|
||||
assertNoErrors(results);
|
||||
|
||||
return results
|
||||
.filter(
|
||||
(result): result is PromiseFulfilledResult<Entry> =>
|
||||
result.value !== undefined,
|
||||
)
|
||||
.map(({ value }) => value);
|
||||
},
|
||||
"jellyfin.continueWatching",
|
||||
);
|
||||
|
||||
function assertNoErrors<T>(
|
||||
results: PromiseSettledResult<T>[],
|
||||
): asserts results is PromiseFulfilledResult<T>[] {
|
||||
if (results.some(({ status }) => status !== "fulfilled")) {
|
||||
throw new Error("one or more promices failed", {
|
||||
cause: results
|
||||
.filter((r): r is PromiseRejectedResult => r.status === "rejected")
|
||||
.map((r) => r.reason),
|
||||
});
|
||||
}
|
||||
|
||||
const uniqueIds = new Set<string>(data.Items.map(item => item.Type === 'Episode' ? item.SeriesId! : item.Id!));
|
||||
const results = await Promise.allSettled(uniqueIds.values().map(id => getItem(userId, id)).toArray());
|
||||
|
||||
assertNoErrors(results);
|
||||
|
||||
return results.filter((result): result is PromiseFulfilledResult<Entry> => result.value !== undefined).map(({ value }) => value);
|
||||
}, "jellyfin.continueWatching");
|
||||
|
||||
function assertNoErrors<T>(results: PromiseSettledResult<T>[]): asserts results is PromiseFulfilledResult<T>[] {
|
||||
if (results.some(({ status }) => status !== 'fulfilled')) {
|
||||
throw new Error('one or more promices failed', { cause: results.filter((r): r is PromiseRejectedResult => r.status === 'rejected').map(r => r.reason) });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
287
src/features/content/apis/tmdb.not.generated.ts
Normal file
287
src/features/content/apis/tmdb.not.generated.ts
Normal file
|
@ -0,0 +1,287 @@
|
|||
export interface paths {
|
||||
"/4/account/{account_object_id}/movie/recommendations": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get: operations["GetMovieRecommendations"];
|
||||
put?: never;
|
||||
post?: never;
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/3/movie/{movie_id}": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get: operations["GetMovieById"];
|
||||
put?: never;
|
||||
post?: never;
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/3/series/{series_id}": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get: operations["GetSeriesById"];
|
||||
put?: never;
|
||||
post?: never;
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/3/discover/movie": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get: operations["GetDiscovery_Movie"];
|
||||
put?: never;
|
||||
post?: never;
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/3/discover/tv": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get: operations["GetDiscovery_Serie"];
|
||||
put?: never;
|
||||
post?: never;
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
}
|
||||
export type webhooks = Record<string, never>;
|
||||
export interface components {
|
||||
schemas: {
|
||||
paginatedQueryResult: {
|
||||
page: number;
|
||||
results: components["schemas"]["entry"][];
|
||||
total_pages: number;
|
||||
total_results: number;
|
||||
};
|
||||
entry: {
|
||||
backdrop_path: string;
|
||||
id: number;
|
||||
title: string;
|
||||
overview: string;
|
||||
poster_path: string;
|
||||
media_type: string;
|
||||
adult: boolean;
|
||||
original_language: string;
|
||||
gerne_ids: number[];
|
||||
popularity: number;
|
||||
release_date: string;
|
||||
video: boolean;
|
||||
vote_average: number;
|
||||
vote_count: number;
|
||||
};
|
||||
};
|
||||
responses: never;
|
||||
parameters: never;
|
||||
requestBodies: never;
|
||||
headers: never;
|
||||
pathItems: never;
|
||||
}
|
||||
export type $defs = Record<string, never>;
|
||||
export interface operations {
|
||||
GetMovieRecommendations: {
|
||||
parameters: {
|
||||
query?: {
|
||||
page?: number;
|
||||
language?: string;
|
||||
};
|
||||
header?: never;
|
||||
path: {
|
||||
account_object_id: string;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["paginatedQueryResult"];
|
||||
};
|
||||
};
|
||||
401: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
403: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
};
|
||||
};
|
||||
GetDiscovery_Movie: {
|
||||
parameters: {
|
||||
query?: {
|
||||
page?: number;
|
||||
language?: string;
|
||||
};
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["paginatedQueryResult"];
|
||||
};
|
||||
};
|
||||
401: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
403: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
};
|
||||
};
|
||||
GetDiscovery_Serie: {
|
||||
parameters: {
|
||||
query?: {
|
||||
page?: number;
|
||||
language?: string;
|
||||
};
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["paginatedQueryResult"];
|
||||
};
|
||||
};
|
||||
401: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
403: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
};
|
||||
};
|
||||
GetMovieById: {
|
||||
parameters: {
|
||||
query?: {};
|
||||
header?: never;
|
||||
path: {
|
||||
movie_id: string;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["entry"];
|
||||
};
|
||||
};
|
||||
401: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
403: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
};
|
||||
};
|
||||
GetSeriesById: {
|
||||
parameters: {
|
||||
query?: {};
|
||||
header?: never;
|
||||
path: {
|
||||
series_id: string;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["entry"];
|
||||
};
|
||||
};
|
||||
401: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
403: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
91
src/features/content/apis/tmdb.ts
Normal file
91
src/features/content/apis/tmdb.ts
Normal file
|
@ -0,0 +1,91 @@
|
|||
"use server";
|
||||
|
||||
import createClient from "openapi-fetch";
|
||||
import { query } from "@solidjs/router";
|
||||
import { Entry } from "../types";
|
||||
import { paths } from "./tmdb.not.generated";
|
||||
|
||||
const baseUrl = process.env.TMDB_BASE_URL;
|
||||
const client = createClient<paths>({
|
||||
baseUrl,
|
||||
headers: {
|
||||
Authorization: `Bearer ${process.env.TMDB_TOKEN}`,
|
||||
"Content-Type": "application/json;",
|
||||
},
|
||||
});
|
||||
|
||||
export const getEntry = query(
|
||||
async (id: string): Promise<Entry | undefined> => {
|
||||
const { data } = await client.GET("/3/movie/{movie_id}", {
|
||||
params: {
|
||||
path: {
|
||||
movie_id: id,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (data === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
id: String(data.id),
|
||||
title: data.title,
|
||||
overview: data.overview,
|
||||
thumbnail: `http://image.tmdb.org/t/p/w342${data.poster_path}`,
|
||||
image: `http://image.tmdb.org/t/p/original${data.backdrop_path}`,
|
||||
};
|
||||
},
|
||||
"tmdb.getEntry",
|
||||
);
|
||||
|
||||
export const getRecommendations = query(async (): Promise<Entry[]> => {
|
||||
const account_object_id = "6668b76e419b28ec1a1c5aab";
|
||||
|
||||
const { data } = await client.GET(
|
||||
"/4/account/{account_object_id}/movie/recommendations",
|
||||
{
|
||||
params: {
|
||||
path: { account_object_id },
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
if (data === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return data?.results.map(
|
||||
({ id, title, overview, poster_path, backdrop_path }) => ({
|
||||
id: String(id),
|
||||
title,
|
||||
overview,
|
||||
thumbnail: `http://image.tmdb.org/t/p/w342${poster_path}`,
|
||||
image: `http://image.tmdb.org/t/p/original${backdrop_path}`,
|
||||
}),
|
||||
);
|
||||
}, "tmdb.getRecommendations");
|
||||
|
||||
export const getDiscovery = query(async (): Promise<Entry[]> => {
|
||||
const [{ data: movies }, { data: series }] = await Promise.all([
|
||||
client.GET("/3/discover/movie"),
|
||||
client.GET("/3/discover/movie"),
|
||||
]);
|
||||
|
||||
if (movies === undefined || series === undefined) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// console.log({ movies: movies.results.length, series: series.results.length });
|
||||
|
||||
return movies?.results
|
||||
.slice(0, 9)
|
||||
.concat(series?.results.slice(0, 9))
|
||||
.map(({ id, title, overview, poster_path, backdrop_path }) => ({
|
||||
id: String(id),
|
||||
title,
|
||||
overview,
|
||||
thumbnail: `http://image.tmdb.org/t/p/w342${poster_path}`,
|
||||
image: `http://image.tmdb.org/t/p/original${backdrop_path}`,
|
||||
}));
|
||||
}, "tmdb.getDiscovery");
|
|
@ -4,6 +4,11 @@ import type { Category, Entry } from "./types";
|
|||
import { query } from "@solidjs/router";
|
||||
import { entries } from "./data";
|
||||
import { getContinueWatching, getItem, getRandomItems } from "./apis/jellyfin";
|
||||
import {
|
||||
getDiscovery,
|
||||
getRecommendations,
|
||||
getEntry as getTmdbEntry,
|
||||
} from "./apis/tmdb";
|
||||
|
||||
const jellyfinUserId = "a9c51af84bf54578a99ab4dd0ebf0763";
|
||||
|
||||
|
@ -13,65 +18,19 @@ export const getHighlights = () => getContinueWatching(jellyfinUserId);
|
|||
export const listCategories = query(async (): Promise<Category[]> => {
|
||||
return [
|
||||
// { label: "Continue", entries: await getContinueWatching(jellyfinUserId) },
|
||||
{
|
||||
label: "Recommendations (For you?)",
|
||||
entries: await getRecommendations(),
|
||||
},
|
||||
{ label: "Discover", entries: await getDiscovery() },
|
||||
{ label: "Random", entries: await getRandomItems(jellyfinUserId) },
|
||||
{
|
||||
label: "Popular",
|
||||
entries: [
|
||||
entries.get("1")!,
|
||||
entries.get("2")!,
|
||||
entries.get("3")!,
|
||||
entries.get("4")!,
|
||||
entries.get("1")!,
|
||||
entries.get("2")!,
|
||||
entries.get("3")!,
|
||||
entries.get("4")!,
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Drama",
|
||||
entries: [
|
||||
entries.get("5")!,
|
||||
entries.get("6")!,
|
||||
entries.get("7")!,
|
||||
entries.get("8")!,
|
||||
entries.get("1")!,
|
||||
entries.get("2")!,
|
||||
entries.get("3")!,
|
||||
entries.get("4")!,
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Now streaming",
|
||||
entries: [
|
||||
entries.get("1")!,
|
||||
entries.get("2")!,
|
||||
entries.get("3")!,
|
||||
entries.get("4")!,
|
||||
entries.get("1")!,
|
||||
entries.get("2")!,
|
||||
entries.get("3")!,
|
||||
entries.get("4")!,
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Sci-Fi & Fantasy",
|
||||
entries: [
|
||||
entries.get("9")!,
|
||||
entries.get("11")!,
|
||||
entries.get("12")!,
|
||||
entries.get("13")!,
|
||||
entries.get("1")!,
|
||||
entries.get("2")!,
|
||||
entries.get("3")!,
|
||||
entries.get("4")!,
|
||||
],
|
||||
},
|
||||
];
|
||||
}, "series.categories.list");
|
||||
|
||||
export const getEntry = query(
|
||||
async (id: Entry["id"]): Promise<Entry | undefined> => {
|
||||
return getItem(jellyfinUserId, id);
|
||||
return getTmdbEntry(id);
|
||||
// return getItem(jellyfinUserId, id);
|
||||
},
|
||||
"series.get",
|
||||
);
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
export interface Category {
|
||||
label: string;
|
||||
entries: Entry[];
|
||||
|
@ -7,7 +6,7 @@ export interface Category {
|
|||
export interface Entry {
|
||||
id: string;
|
||||
title: string;
|
||||
synopsis?: string;
|
||||
overview?: string;
|
||||
releaseDate?: string;
|
||||
sources?: Entry.Source[];
|
||||
thumbnail?: URL | string;
|
||||
|
@ -30,4 +29,3 @@ export namespace Entry {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
grid: 100% / 100%;
|
||||
place-items: start center;
|
||||
position: relative;
|
||||
inline-size: clamp(15em, 20vw, 30em);
|
||||
inline-size: clamp(15em, 15cqi, 25em);
|
||||
aspect-ratio: var(--ratio-portrait);
|
||||
transform: translateY(calc(-2 * var(--padding)));
|
||||
z-index: 1;
|
||||
|
@ -21,20 +21,28 @@
|
|||
z-index: 1;
|
||||
|
||||
box-shadow: var(--shadow-2);
|
||||
background:
|
||||
radial-gradient(circle at 25% 30%, #7772, #7774 1em, transparent 1em),
|
||||
radial-gradient(circle at 85% 15%, #7772, #7774 1em, transparent 1em),
|
||||
linear-gradient(165deg, transparent 60%, #555 60%, #333),
|
||||
radial-gradient(
|
||||
ellipse 5em 2.25em at 0.5em calc(50% - 1em), #333 100%, transparent 100%
|
||||
),
|
||||
radial-gradient(
|
||||
ellipse 5em 2.25em at calc(100% - 0.5em) calc(50% + 1em), #555 100%, transparent 100%
|
||||
),
|
||||
linear-gradient(to bottom, #333 50%, #555 50%);
|
||||
background: radial-gradient(
|
||||
circle at 25% 30%,
|
||||
#7772,
|
||||
#7774 1em,
|
||||
transparent 1em
|
||||
),
|
||||
radial-gradient(circle at 85% 15%, #7772, #7774 1em, transparent 1em),
|
||||
linear-gradient(165deg, transparent 60%, #555 60%, #333),
|
||||
radial-gradient(
|
||||
ellipse 5em 2.25em at 0.5em calc(50% - 1em),
|
||||
#333 100%,
|
||||
transparent 100%
|
||||
),
|
||||
radial-gradient(
|
||||
ellipse 5em 2.25em at calc(100% - 0.5em) calc(50% + 1em),
|
||||
#555 100%,
|
||||
transparent 100%
|
||||
),
|
||||
linear-gradient(to bottom, #333 50%, #555 50%);
|
||||
|
||||
transform-origin: 50% 0;
|
||||
transform: scale(1.1) translateY(calc(-4 * var(--padding)));
|
||||
transform: scale(1.15) translateY(calc(-4 * var(--padding)));
|
||||
|
||||
user-select: none;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue