mirror of
https://github.com/zoriya/Kyoo.git
synced 2026-04-07 17:51:55 -04:00
Fix collection insertion
This commit is contained in:
parent
752f091dbf
commit
7c781e533e
@ -5,7 +5,9 @@ import { conflictUpdateAllExcept } from "~/db/utils";
|
||||
import type { SeedCollection } from "~/models/collections";
|
||||
import type { SeedMovie } from "~/models/movie";
|
||||
import type { SeedSerie } from "~/models/serie";
|
||||
import type { Original } from "~/models/utils";
|
||||
import { record } from "~/otel";
|
||||
import { uniq } from "~/utils";
|
||||
import { enqueueOptImage, flushImageQueue, type ImageTask } from "../images";
|
||||
|
||||
type ShowTrans = typeof showTranslations.$inferInsert;
|
||||
@ -13,16 +15,17 @@ type ShowTrans = typeof showTranslations.$inferInsert;
|
||||
export const insertCollection = record(
|
||||
"insertCollection",
|
||||
async (
|
||||
collection: SeedCollection | undefined,
|
||||
collection: SeedCollection | null | undefined,
|
||||
show: (
|
||||
| ({ kind: "movie" } & SeedMovie)
|
||||
| ({ kind: "serie" } & SeedSerie)
|
||||
) & {
|
||||
nextRefresh: Date;
|
||||
},
|
||||
original: Original,
|
||||
) => {
|
||||
if (!collection) return null;
|
||||
const { translations, ...col } = collection;
|
||||
const { translations, genres, ...col } = collection;
|
||||
|
||||
return await db.transaction(async (tx) => {
|
||||
const imgQueue: ImageTask[] = [];
|
||||
@ -35,7 +38,12 @@ export const insertCollection = record(
|
||||
endAir: show.kind === "movie" ? show.airDate : show.endAir,
|
||||
nextRefresh: show.nextRefresh,
|
||||
entriesCount: 0,
|
||||
original: {} as any,
|
||||
original: {
|
||||
language: original.language,
|
||||
name: original.name,
|
||||
latinName: original.latinName,
|
||||
},
|
||||
genres: uniq(show.genres),
|
||||
...col,
|
||||
})
|
||||
.onConflictDoUpdate({
|
||||
|
||||
@ -54,14 +54,29 @@ export const seedMovie = async (
|
||||
|
||||
const { translations, videos, collection, studios, staff, ...movie } = seed;
|
||||
const nextRefresh = guessNextRefresh(movie.airDate ?? new Date());
|
||||
const ori = translations[movie.originalLanguage];
|
||||
const original = ori
|
||||
? {
|
||||
...ori,
|
||||
latinName: ori.latinName ?? null,
|
||||
language: movie.originalLanguage,
|
||||
}
|
||||
: {
|
||||
name: null,
|
||||
latinName: null,
|
||||
language: movie.originalLanguage,
|
||||
};
|
||||
|
||||
const col = await insertCollection(collection, {
|
||||
kind: "movie",
|
||||
nextRefresh,
|
||||
...seed,
|
||||
});
|
||||
const col = await insertCollection(
|
||||
collection,
|
||||
{
|
||||
kind: "movie",
|
||||
nextRefresh,
|
||||
...seed,
|
||||
},
|
||||
original,
|
||||
);
|
||||
|
||||
const original = translations[movie.originalLanguage];
|
||||
const show = await insertShow(
|
||||
{
|
||||
kind: "movie",
|
||||
@ -71,17 +86,7 @@ export const seedMovie = async (
|
||||
entriesCount: 1,
|
||||
...movie,
|
||||
},
|
||||
original
|
||||
? {
|
||||
...original,
|
||||
latinName: original.latinName ?? null,
|
||||
language: movie.originalLanguage,
|
||||
}
|
||||
: {
|
||||
name: null,
|
||||
latinName: null,
|
||||
language: movie.originalLanguage,
|
||||
},
|
||||
original,
|
||||
translations,
|
||||
);
|
||||
if ("status" in show) return show;
|
||||
|
||||
@ -90,14 +90,29 @@ export const seedSerie = async (
|
||||
...serie
|
||||
} = seed;
|
||||
const nextRefresh = guessNextRefresh(serie.startAir ?? new Date());
|
||||
const ori = translations[serie.originalLanguage];
|
||||
const original = ori
|
||||
? {
|
||||
...ori,
|
||||
latinName: ori.latinName ?? null,
|
||||
language: serie.originalLanguage,
|
||||
}
|
||||
: {
|
||||
name: null,
|
||||
latinName: null,
|
||||
language: serie.originalLanguage,
|
||||
};
|
||||
|
||||
const col = await insertCollection(collection, {
|
||||
kind: "serie",
|
||||
nextRefresh,
|
||||
...seed,
|
||||
});
|
||||
const col = await insertCollection(
|
||||
collection,
|
||||
{
|
||||
kind: "serie",
|
||||
nextRefresh,
|
||||
...seed,
|
||||
},
|
||||
original,
|
||||
);
|
||||
|
||||
const original = translations[serie.originalLanguage];
|
||||
const show = await insertShow(
|
||||
{
|
||||
kind: "serie",
|
||||
@ -106,17 +121,7 @@ export const seedSerie = async (
|
||||
entriesCount: entries.length,
|
||||
...serie,
|
||||
},
|
||||
original
|
||||
? {
|
||||
...original,
|
||||
latinName: original.latinName ?? null,
|
||||
language: serie.originalLanguage,
|
||||
}
|
||||
: {
|
||||
name: null,
|
||||
latinName: null,
|
||||
language: serie.originalLanguage,
|
||||
},
|
||||
original,
|
||||
translations,
|
||||
);
|
||||
if ("status" in show) return show;
|
||||
|
||||
@ -102,7 +102,7 @@ export const SeedMovie = t.Composite([
|
||||
]),
|
||||
),
|
||||
videos: t.Optional(t.Array(t.String({ format: "uuid" }), { default: [] })),
|
||||
collection: t.Optional(SeedCollection),
|
||||
collection: t.Optional(t.Nullable(SeedCollection)),
|
||||
studios: t.Optional(t.Array(SeedStudio, { default: [] })),
|
||||
staff: t.Optional(t.Array(SeedStaff, { default: [] })),
|
||||
}),
|
||||
|
||||
@ -120,7 +120,7 @@ export const SeedSerie = t.Composite([
|
||||
seasons: t.Array(SeedSeason),
|
||||
entries: t.Array(SeedEntry),
|
||||
extras: t.Optional(t.Array(SeedExtra, { default: [] })),
|
||||
collection: t.Optional(SeedCollection),
|
||||
collection: t.Optional(t.Nullable(SeedCollection)),
|
||||
studios: t.Optional(t.Array(SeedStudio, { default: [] })),
|
||||
staff: t.Optional(t.Array(SeedStaff, { default: [] })),
|
||||
}),
|
||||
|
||||
@ -60,7 +60,7 @@ export const A = ({
|
||||
<P
|
||||
{...linkProps}
|
||||
className={cn(
|
||||
"select-text text-accent hover:underline focus:underline",
|
||||
"select-text text-accent hover:underline focus:underline dark:text-accent",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@ -10,13 +10,11 @@ export const useMobileHover = () => {
|
||||
// biome-ignore lint/correctness/useHookAtTopLevel: const condition
|
||||
useEffect(() => {
|
||||
const enableHover = () => {
|
||||
console.log("pc");
|
||||
if (preventHover) return;
|
||||
document.body.classList.remove("noHover");
|
||||
};
|
||||
|
||||
const disableHover = () => {
|
||||
console.log("mobile");
|
||||
if (hoverTimeout) clearTimeout(hoverTimeout);
|
||||
preventHover = true;
|
||||
hoverTimeout = setTimeout(() => {
|
||||
|
||||
@ -4,7 +4,6 @@ import { type ReactNode, useEffect, useMemo, useRef } from "react";
|
||||
import { Platform } from "react-native";
|
||||
import { z } from "zod/v4";
|
||||
import { Account, User } from "~/models";
|
||||
import { RetryableError } from "~/models/retryable-error";
|
||||
import { useFetch } from "~/query";
|
||||
import { AccountContext } from "./account-context";
|
||||
import { removeAccounts, updateAccount } from "./account-store";
|
||||
@ -30,9 +29,8 @@ export const AccountProvider = ({ children }: { children: ReactNode }) => {
|
||||
};
|
||||
}, [accounts]);
|
||||
|
||||
const router = useRouter();
|
||||
if (Platform.OS !== "web") {
|
||||
// biome-ignore lint/correctness/useHookAtTopLevel: static
|
||||
const router = useRouter();
|
||||
// biome-ignore lint/correctness/useHookAtTopLevel: static
|
||||
useEffect(() => {
|
||||
if (!ret.apiUrl) {
|
||||
@ -57,13 +55,13 @@ export const AccountProvider = ({ children }: { children: ReactNode }) => {
|
||||
options: {
|
||||
apiUrl: ret.apiUrl,
|
||||
authToken: ret.authToken,
|
||||
returnError: true,
|
||||
},
|
||||
});
|
||||
if (userError) {
|
||||
throw new RetryableError({
|
||||
key: "connection",
|
||||
retry: queryClient.resetQueries,
|
||||
});
|
||||
setTimeout(() => {
|
||||
router.replace("/login");
|
||||
}, 0);
|
||||
}
|
||||
// Use a ref here because we don't want the effect to trigger when the selected
|
||||
// value has changed, only when the fetch result changed
|
||||
|
||||
@ -35,7 +35,7 @@ class Serie(Model):
|
||||
translations: dict[Language, SerieTranslation] = {}
|
||||
seasons: list[Season] = []
|
||||
entries: list[Entry] = []
|
||||
extra: list[Extra] = []
|
||||
extras: list[Extra] = []
|
||||
collection: Collection | None = None
|
||||
studios: list[Studio] = []
|
||||
staff: list[Staff] = []
|
||||
|
||||
@ -3,6 +3,7 @@ from typing import override
|
||||
from langcodes import Language
|
||||
|
||||
from scanner.models.metadataid import MetadataId
|
||||
from scanner.utils import uniq_by
|
||||
|
||||
from ..models.movie import Movie, SearchMovie
|
||||
from ..models.serie import SearchSerie, Serie
|
||||
@ -52,6 +53,12 @@ class CompositeProvider(Provider):
|
||||
ret = await self._tvdb.get_serie(external_id)
|
||||
if ret is None:
|
||||
return None
|
||||
|
||||
# some series have duplicates special numbers/episode numbers, sensitize them
|
||||
ret.entries = uniq_by(
|
||||
ret.entries, lambda x: (x.season_number, x.episode_number, x.number, x.slug)
|
||||
)
|
||||
|
||||
# themoviedb has better global info than tvdb but tvdb has better entries info
|
||||
info = await self._themoviedb.get_serie(
|
||||
MetadataId.map_dict(ret.external_id), skip_entries=True
|
||||
@ -60,7 +67,7 @@ class CompositeProvider(Provider):
|
||||
return ret
|
||||
info.seasons = ret.seasons
|
||||
info.entries = ret.entries
|
||||
info.extra = ret.extra
|
||||
info.extras = ret.extras
|
||||
if ret.collection is not None:
|
||||
info.collection = ret.collection
|
||||
info.external_id = MetadataId.merge(ret.external_id, info.external_id)
|
||||
|
||||
@ -386,7 +386,7 @@ class TheMovieDatabase(Provider):
|
||||
},
|
||||
seasons=seasons,
|
||||
entries=entries,
|
||||
extra=[],
|
||||
extras=[],
|
||||
collection=None,
|
||||
studios=[self._map_studio(x) for x in serie["production_companies"]],
|
||||
# TODO: add crew
|
||||
|
||||
@ -292,8 +292,8 @@ class TVDB(Provider):
|
||||
else [],
|
||||
entries=entries,
|
||||
# TODO: map extra entries in extra instead of entries
|
||||
extra=[],
|
||||
collection=await self._get_collection(ret["list"]),
|
||||
extras=[],
|
||||
collection=await self._get_collection(ret["lists"]),
|
||||
studios=[],
|
||||
staff=[],
|
||||
)
|
||||
@ -358,7 +358,11 @@ class TVDB(Provider):
|
||||
|
||||
data = (await self._get(f"lists/{col['id']}/extended"))["data"]
|
||||
first_entity = data["entities"][0]
|
||||
kind = "movie" if "movieId" in first_entity else "series"
|
||||
kind = (
|
||||
"movie"
|
||||
if "movieId" in first_entity and first_entity["movieId"] is not None
|
||||
else "series"
|
||||
)
|
||||
show = (
|
||||
(
|
||||
await self._get(
|
||||
@ -385,15 +389,11 @@ class TVDB(Provider):
|
||||
(x["name"] for x in trans if x.get("isPrimary")), data["name"]
|
||||
),
|
||||
latin_name=None,
|
||||
description=trans.get("overview"),
|
||||
description=trans[0].get("overview"),
|
||||
tagline=None,
|
||||
aliases=[
|
||||
x["name"]
|
||||
for x in trans["aliases"]
|
||||
if x["language"] == lang and x.get("isAlias")
|
||||
],
|
||||
aliases=[x["name"] for x in trans if x.get("isAlias")],
|
||||
tags=[],
|
||||
poster=trans.get("image")
|
||||
poster=data.get("image")
|
||||
if lang == "eng"
|
||||
else self._pick_image(show["artworks"], kind, "posters", lang),
|
||||
thumbnail=self._pick_image(show["artworks"], kind, "backgrounds", lang),
|
||||
@ -618,7 +618,7 @@ class TVDB(Provider):
|
||||
None,
|
||||
),
|
||||
poster=self._pick_image(
|
||||
ret["artworks"], "episode", "posters", trans["language"]
|
||||
ret["artworks"], "movie", "posters", trans["language"]
|
||||
),
|
||||
)
|
||||
for trans in ret["translations"]["nameTranslations"]
|
||||
@ -648,9 +648,12 @@ class TVDB(Provider):
|
||||
search = await self._get(
|
||||
f"search/remoteid/{external_id[ProviderName.IMDB]}"
|
||||
)
|
||||
if len(search["data"]) > 0:
|
||||
id = search["data"][0].get("movie")["id"]
|
||||
return await self.get_movie({self.name: id})
|
||||
if search["data"] is not None and len(search["data"]) > 0:
|
||||
movie = search["data"][0].get("movie")
|
||||
mId = movie.get("id") if movie is not None else None
|
||||
if mId is None:
|
||||
return None
|
||||
return await self.get_movie({self.name: mId})
|
||||
return None
|
||||
|
||||
ret = (
|
||||
@ -675,7 +678,7 @@ class TVDB(Provider):
|
||||
status=MovieStatus.FINISHED
|
||||
if ret["status"]["name"] == "Ended"
|
||||
else MovieStatus.PLANNED,
|
||||
runtime=ret["averageRuntime"],
|
||||
runtime=ret["runtime"],
|
||||
air_date=datetime.strptime(ret["first_release"]["date"], "%Y-%m-%d").date()
|
||||
if ret.get("first_release") and ret["first_release"].get("date")
|
||||
else None,
|
||||
@ -727,7 +730,7 @@ class TVDB(Provider):
|
||||
for trans in ret["translations"]["nameTranslations"]
|
||||
if trans.get("isAlias") is None or False
|
||||
},
|
||||
collection=await self._get_collection(ret["list"]),
|
||||
collection=await self._get_collection(ret["lists"]),
|
||||
studios=[],
|
||||
staff=[],
|
||||
)
|
||||
|
||||
@ -19,6 +19,19 @@ def clean(val: str) -> str | None:
|
||||
return val or None
|
||||
|
||||
|
||||
def uniq_by[T, K](data: list[T], key: Callable[[T], K]) -> list[T]:
|
||||
seen = set()
|
||||
ret = []
|
||||
|
||||
for item in data:
|
||||
val = key(item)
|
||||
if val not in seen:
|
||||
seen.add(val)
|
||||
ret.append(item)
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
class Singleton(ABCMeta, type):
|
||||
_instances = {}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user