From 3202c01767ccc141b8b7be3a4e97da23982afedf Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 15 Mar 2026 12:38:15 +0100 Subject: [PATCH 1/8] Parse animelist.xml --- scanner/pyproject.toml | 1 + scanner/scanner/identifiers/anilist.py | 182 ++++++++++++++++++++++++ scanner/scanner/identifiers/identify.py | 3 +- scanner/scanner/models/videos.py | 1 + scanner/scanner/providers/names.py | 1 + scanner/uv.lock | 15 ++ 6 files changed, 202 insertions(+), 1 deletion(-) create mode 100644 scanner/scanner/identifiers/anilist.py diff --git a/scanner/pyproject.toml b/scanner/pyproject.toml index 2964bd29..5b9fcf31 100644 --- a/scanner/pyproject.toml +++ b/scanner/pyproject.toml @@ -18,6 +18,7 @@ dependencies = [ "opentelemetry-instrumentation-fastapi>=0.59b0", "opentelemetry-sdk>=1.38.0", "pydantic>=2.11.4", + "pydantic-xml>=2.14.0", "pyjwt[crypto]>=2.10.1", "python-slugify>=8.0.4", "watchfiles>=1.0.5", diff --git a/scanner/scanner/identifiers/anilist.py b/scanner/scanner/identifiers/anilist.py new file mode 100644 index 00000000..1a426564 --- /dev/null +++ b/scanner/scanner/identifiers/anilist.py @@ -0,0 +1,182 @@ +from __future__ import annotations + +import re +import unicodedata +from dataclasses import dataclass +from datetime import datetime, timedelta +from functools import cached_property +from logging import getLogger +from typing import Literal + +from aiohttp import ClientSession +from pydantic import field_validator +from pydantic_xml import BaseXmlModel, attr, element + +from ..cache import cache +from ..models.metadataid import EpisodeId, MetadataId +from ..models.videos import Guess +from ..providers.names import ProviderName + +logger = getLogger(__name__) + + +class AnimeTitlesDb(BaseXmlModel, tag="animetitles"): + animes: list[AnimeTitlesEntry] = element(default=[]) + + @classmethod + def get_url(cls): + return "https://raw.githubusercontent.com/Anime-Lists/anime-lists/master/animetitles.xml" + + class AnimeTitlesEntry(BaseXmlModel, tag="anime"): + aid: str = attr() + titles: list[AnimeTitle] = element(default=[]) + + class AnimeTitle( + BaseXmlModel, + tag="title", + nsmap={"xml": "http://www.w3.org/XML/1998/namespace"}, + ): + type: str = attr() + lang: str = attr(ns="xml") + text: str + + +class AnimeListDb(BaseXmlModel, tag="anime-list"): + animes: list[AnimeEntry] = element(default=[]) + + @classmethod + def get_url(cls): + return "https://raw.githubusercontent.com/Anime-Lists/anime-lists/refs/heads/master/anime-list.xml" + + class AnimeEntry(BaseXmlModel, tag="anime"): + anidbid: str = attr() + tvdbid: str | None = attr(default=None) + defaulttvdbseason: int | Literal["a"] | None = attr(default=None) + episodeoffset: int = attr(default=0) + tmdbid: str | None = attr(default=None) + imdbid: str | None = attr(default=None) + name: str | None = element(default=None) + mapping_list: MappingList | None = element(default=[]) + + @field_validator("tmdbid", "imdbid") + @classmethod + def _empty_to_none(cls, v: str | None) -> str | None: + return v or None + + class MappingList(BaseXmlModel, tag="mapping-list"): + mappings: list[EpisodeMapping] = element(default=[]) + + class EpisodeMapping(BaseXmlModel): + anidbseason: int = attr() + tvdbseason: int | None = attr(default=None) + start: int | None = attr(default=None) + end: int | None = attr(default=None) + offset: int = attr(default=0) + text: str | None = None + + @cached_property + def tvdb_mappings(self) -> dict[int, list[int]]: + if self.tvdbseason is None or not self.text: + return {} + ret = {} + for map in self.text.split(";"): + map = map.strip() + if not map or "-" not in map: + continue + [aid, tvdbids] = map.split("-", 1) + try: + ret[int(aid.strip())] = [ + int(x.strip()) for x in tvdbids.split("+") + ] + except ValueError: + continue + return ret + + +@dataclass +class AnimeListData: + fetched_at: datetime + # normalized title -> anidbid + titles: dict[str, str] = {} + # anidbid -> AnimeEntry + animes: dict[str, AnimeListDb.AnimeEntry] = {} + + +@cache(ttl=timedelta(days=30)) +async def get_data() -> AnimeListData: + logger.info("Fetching anime-lists XML databases...") + ret = AnimeListData(fetched_at=datetime.now()) + async with ClientSession() as session: + async with session.get(AnimeTitlesDb.get_url()) as resp: + resp.raise_for_status() + titles = AnimeTitlesDb.from_xml(await resp.read()) + ret.titles = { + normalize_title(title.text): x.aid + for x in titles.animes + for title in x.titles + } + async with session.get(AnimeListDb.get_url()) as resp: + resp.raise_for_status() + db = AnimeListDb.from_xml(await resp.read()) + ret.animes = {entry.anidbid: entry for entry in db.animes} + + logger.info( + "Loaded %d anime titles from animelist-xml.", + len(ret.titles), + ) + return ret + + +def normalize_title(title: str) -> str: + title = unicodedata.normalize("NFD", title) + title = "".join(c for c in title if unicodedata.category(c) != "Mn") + title = title.lower() + title = re.sub(r"[^\w\s]", "", title) + title = re.sub(r"\s+", " ", title).strip() + return title + + +async def anilist(_path: str, guess: Guess) -> Guess: + data = await get_data() + + aid = data.titles.get(guess.title) + if aid is None: + return guess + anime = data.animes.get(aid) + if anime is None: + logger.warning("AniDB id %s found in titles but not in anime-list.xml", aid) + return guess + + logger.info( + "Matched '%s' to AniDB id %s (tvdb=%s, tmdbid=%s)", + guess.title, + aid, + anime.tvdbid, + anime.tmdbid, + ) + + new_external_id = dict(guess.external_id) + new_external_id[ProviderName.ANIDB] = aid + if anime.tvdbid: + new_external_id[ProviderName.TVDB] = anime.tvdbid + if anime.tmdbid: + new_external_id[ProviderName.TMDB] = anime.tmdbid + if anime.imdbid: + new_external_id[ProviderName.IMDB] = anime.imdbid + + new_episodes: list[Guess.Episode] = [] + for ep in guess.episodes: + # TODO: implement this + ... + + return Guess( + title=guess.title, + kind=guess.kind, + extra_kind=guess.extra_kind, + years=guess.years, + episodes=new_episodes, + external_id=new_external_id, + raw=guess.raw, + from_="anilist", + history=[*guess.history, guess], + ) diff --git a/scanner/scanner/identifiers/identify.py b/scanner/scanner/identifiers/identify.py index 0c3187f4..4d2790d6 100644 --- a/scanner/scanner/identifiers/identify.py +++ b/scanner/scanner/identifiers/identify.py @@ -7,14 +7,15 @@ from typing import Callable, Literal, cast from rebulk.match import Match from ..models.videos import Guess, Video +from .anilist import anilist from .guess.guess import guessit logger = getLogger(__name__) pipeline: list[Callable[[str, Guess], Awaitable[Guess]]] = [ + anilist, # TODO: add nfo scanner # TODO: add thexem - # TODO: add anilist ] diff --git a/scanner/scanner/models/videos.py b/scanner/scanner/models/videos.py index de724b8d..a128a0fb 100644 --- a/scanner/scanner/models/videos.py +++ b/scanner/scanner/models/videos.py @@ -33,6 +33,7 @@ class Guess(Model, extra="allow"): class Episode(Model): season: int | None episode: int + external_id: dict[str, MetadataId | EpisodeId] = {} @override def __hash__(self) -> int: diff --git a/scanner/scanner/providers/names.py b/scanner/scanner/providers/names.py index 704ccf27..a50cda53 100644 --- a/scanner/scanner/providers/names.py +++ b/scanner/scanner/providers/names.py @@ -2,3 +2,4 @@ class ProviderName: TMDB = "themoviedatabase" TVDB = "tvdb" IMDB = "imdb" + ANIDB = "anidb" diff --git a/scanner/uv.lock b/scanner/uv.lock index 47446e7e..92784083 100644 --- a/scanner/uv.lock +++ b/scanner/uv.lock @@ -1259,6 +1259,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c1/60/5d4751ba3f4a40a6891f24eec885f51afd78d208498268c734e256fb13c4/pydantic_settings-2.12.0-py3-none-any.whl", hash = "sha256:fddb9fd99a5b18da837b29710391e945b1e30c135477f484084ee513adb93809", size = 51880, upload-time = "2025-11-10T14:25:45.546Z" }, ] +[[package]] +name = "pydantic-xml" +version = "2.19.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "pydantic-core" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b8/cb/5f80b61d73a8d6171ee4611bfd2b944c036c6f6e5f6e01d9fb02f29d7bfc/pydantic_xml-2.19.0.tar.gz", hash = "sha256:b7acba5a0966cbbbc9bf88d0d870b2bc875da063fe1bbe62d83939b549224730", size = 26228, upload-time = "2026-02-14T17:33:53.368Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/2d/dce0dc471fade04829c2948462d79c9bc4991305b0f73889f70c9645e540/pydantic_xml-2.19.0-py3-none-any.whl", hash = "sha256:42854bf962758bec338c112c2de984723708262793e108416f33aa4d6c11b3b4", size = 42536, upload-time = "2026-02-14T17:33:54.206Z" }, +] + [[package]] name = "pygments" version = "2.19.2" @@ -1482,6 +1495,7 @@ dependencies = [ { name = "opentelemetry-instrumentation-fastapi" }, { name = "opentelemetry-sdk" }, { name = "pydantic" }, + { name = "pydantic-xml" }, { name = "pyjwt", extra = ["crypto"] }, { name = "python-slugify" }, { name = "watchfiles" }, @@ -1502,6 +1516,7 @@ requires-dist = [ { name = "opentelemetry-instrumentation-fastapi", specifier = ">=0.59b0" }, { name = "opentelemetry-sdk", specifier = ">=1.38.0" }, { name = "pydantic", specifier = ">=2.11.4" }, + { name = "pydantic-xml", specifier = ">=2.14.0" }, { name = "pyjwt", extras = ["crypto"], specifier = ">=2.10.1" }, { name = "python-slugify", specifier = ">=8.0.4" }, { name = "watchfiles", specifier = ">=1.0.5" }, From d79a12bda8cef8af34389cb63a42c10ed4aa55ed Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 15 Mar 2026 19:54:36 +0100 Subject: [PATCH 2/8] Map anidb/tvdb season/episode numbers --- docker-compose.dev.yml | 2 +- scanner/scanner/client.py | 2 + scanner/scanner/identifiers/anilist.py | 183 ++++++++++++++++++++----- 3 files changed, 148 insertions(+), 39 deletions(-) diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 53115170..fa3124e6 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -140,7 +140,7 @@ services: path: ./scanner target: /app - action: rebuild - path: ./scanner/pyproject.toml + path: ./scanner/uv.lock transcoder: <<: *transcoder-base diff --git a/scanner/scanner/client.py b/scanner/scanner/client.py index 3738957f..67e89108 100644 --- a/scanner/scanner/client.py +++ b/scanner/scanner/client.py @@ -54,6 +54,8 @@ class KyooClient(metaclass=Singleton): return VideoInfo(**await r.json()) async def create_videos(self, videos: list[Video]) -> list[VideoCreated]: + if len(videos) == 0: + return [] async with self._client.post( "videos", data=TypeAdapter(list[Video]).dump_json(videos, by_alias=True), diff --git a/scanner/scanner/identifiers/anilist.py b/scanner/scanner/identifiers/anilist.py index 1a426564..f038ff41 100644 --- a/scanner/scanner/identifiers/anilist.py +++ b/scanner/scanner/identifiers/anilist.py @@ -2,7 +2,7 @@ from __future__ import annotations import re import unicodedata -from dataclasses import dataclass +from dataclasses import dataclass, field from datetime import datetime, timedelta from functools import cached_property from logging import getLogger @@ -10,10 +10,10 @@ from typing import Literal from aiohttp import ClientSession from pydantic import field_validator -from pydantic_xml import BaseXmlModel, attr, element +from pydantic_xml import BaseXmlModel, attr, element, wrapped from ..cache import cache -from ..models.metadataid import EpisodeId, MetadataId +from ..models.metadataid import EpisodeId from ..models.videos import Guess from ..providers.names import ProviderName @@ -53,53 +53,56 @@ class AnimeListDb(BaseXmlModel, tag="anime-list"): tvdbid: str | None = attr(default=None) defaulttvdbseason: int | Literal["a"] | None = attr(default=None) episodeoffset: int = attr(default=0) + tmdbtv: str | None = attr(default=None) tmdbid: str | None = attr(default=None) imdbid: str | None = attr(default=None) name: str | None = element(default=None) - mapping_list: MappingList | None = element(default=[]) + mappings: list[EpisodeMapping] = wrapped( + "mapping-list/mappings", element(default=[]) + ) - @field_validator("tmdbid", "imdbid") + @field_validator("tvdbid", "tmdbtv", "tmdbid", "imdbid", "defaulttvdbseason") @classmethod def _empty_to_none(cls, v: str | None) -> str | None: + # pornographic titles have this id. + if v == "hentai": + return None return v or None - class MappingList(BaseXmlModel, tag="mapping-list"): - mappings: list[EpisodeMapping] = element(default=[]) + class EpisodeMapping(BaseXmlModel): + anidbseason: int = attr() + tvdbseason: int | None = attr(default=None) + start: int | None = attr(default=None) + end: int | None = attr(default=None) + offset: int = attr(default=0) + text: str | None = None - class EpisodeMapping(BaseXmlModel): - anidbseason: int = attr() - tvdbseason: int | None = attr(default=None) - start: int | None = attr(default=None) - end: int | None = attr(default=None) - offset: int = attr(default=0) - text: str | None = None - - @cached_property - def tvdb_mappings(self) -> dict[int, list[int]]: - if self.tvdbseason is None or not self.text: - return {} - ret = {} - for map in self.text.split(";"): - map = map.strip() - if not map or "-" not in map: - continue - [aid, tvdbids] = map.split("-", 1) - try: - ret[int(aid.strip())] = [ - int(x.strip()) for x in tvdbids.split("+") - ] - except ValueError: - continue - return ret + @cached_property + def tvdb_mappings(self) -> dict[int, list[int]]: + if self.tvdbseason is None or not self.text: + return {} + ret = {} + for map in self.text.split(";"): + map = map.strip() + if not map or "-" not in map: + continue + [aid, tvdbids] = map.split("-", 1) + try: + ret[int(aid.strip())] = [ + int(x.strip()) for x in tvdbids.split("+") + ] + except ValueError: + continue + return ret @dataclass class AnimeListData: fetched_at: datetime # normalized title -> anidbid - titles: dict[str, str] = {} + titles: dict[str, str] = field(default_factory=dict) # anidbid -> AnimeEntry - animes: dict[str, AnimeListDb.AnimeEntry] = {} + animes: dict[str, AnimeListDb.AnimeEntry] = field(default_factory=dict) @cache(ttl=timedelta(days=30)) @@ -136,6 +139,62 @@ def normalize_title(title: str) -> str: return title +def anidb_to_tvdb( + anime: AnimeListDb.AnimeEntry, + anidb_ep: int, +) -> tuple[int | None, list[int]]: + for map in anime.mappings: + if map.anidbseason != 1 or map.tvdbseason is None: + continue + + # Handle mapping overrides (;anidb-tvdb; format) + if anidb_ep in map.tvdb_mappings: + tvdb_eps = map.tvdb_mappings[anidb_ep] + # Mapped to 0 means no TVDB equivalent + if tvdb_eps[0] == 0: + return (None, []) + return (map.tvdbseason, tvdb_eps) + + # Check start/end range with offset + if ( + map.start is not None + and map.end is not None + and map.start <= anidb_ep <= map.end + ): + return (map.tvdbseason, [anidb_ep + map.offset]) + + if anime.defaulttvdbseason == "a": + return (None, [anidb_ep]) + return (anime.defaulttvdbseason, [anidb_ep + anime.episodeoffset]) + + +def tvdb_to_anidb( + anime: AnimeListDb.AnimeEntry, + tvdb_season: int | None, + tvdb_ep: int, +) -> list[int]: + for map in anime.mappings: + if map.anidbseason != 1 or map.tvdbseason != tvdb_season: + continue + + # Handle mapping overrides (;anidb-tvdb; format) + overrides = [ + anidb_num + for anidb_num, tvdb_nums in map.tvdb_mappings.items() + if tvdb_ep in tvdb_nums + ] + if len(overrides): + return overrides + + # Reverse the start/end range offset + if map.start is not None and map.end is not None: + candidate = tvdb_ep - map.offset + if map.start <= candidate <= map.end: + return [candidate] + + return [tvdb_ep - anime.episodeoffset] + + async def anilist(_path: str, guess: Guess) -> Guess: data = await get_data() @@ -144,7 +203,6 @@ async def anilist(_path: str, guess: Guess) -> Guess: return guess anime = data.animes.get(aid) if anime is None: - logger.warning("AniDB id %s found in titles but not in anime-list.xml", aid) return guess logger.info( @@ -159,15 +217,64 @@ async def anilist(_path: str, guess: Guess) -> Guess: new_external_id[ProviderName.ANIDB] = aid if anime.tvdbid: new_external_id[ProviderName.TVDB] = anime.tvdbid - if anime.tmdbid: + # tmdbtv is for TV series, tmdbid is for standalone movies + if anime.tmdbtv: + new_external_id[ProviderName.TMDB] = anime.tmdbtv + elif anime.tmdbid: new_external_id[ProviderName.TMDB] = anime.tmdbid if anime.imdbid: new_external_id[ProviderName.IMDB] = anime.imdbid new_episodes: list[Guess.Episode] = [] for ep in guess.episodes: - # TODO: implement this - ... + if anime.defaulttvdbseason is None or anime.tvdbid is None: + new_episodes.append( + Guess.Episode( + season=ep.season, + episode=ep.episode, + external_id={ + ProviderName.ANIDB: EpisodeId( + serie_id=aid, + season=None, + episode=ep.episode, + ), + }, + ) + ) + continue + + # guess numbers are anidb-relative if defaulttvdbseason != 1 because + # the title already contains season information. + tvdb_season, tvdb_eps = ( + (ep.season if ep.season is not None else 1, [ep.episode]) + if anime.defaulttvdbseason == 1 + else anidb_to_tvdb(anime, ep.episode) + ) + anidb_eps = ( + tvdb_to_anidb(anime, tvdb_season, ep.episode) + if anime.defaulttvdbseason == 1 + else [ep.episode] + ) + + new_episodes += [ + Guess.Episode( + season=tvdb_season, + episode=tvdb_ep, + external_id={ + ProviderName.TVDB: EpisodeId( + serie_id=anime.tvdbid, + season=tvdb_season, + episode=tvdb_ep, + ), + ProviderName.ANIDB: EpisodeId( + serie_id=aid, + season=None, + episode=anidb_ep, + ), + }, + ) + for tvdb_ep, anidb_ep in zip(tvdb_eps, anidb_eps) + ] return Guess( title=guess.title, From 9b23d0714c76eea1c0114dd4c771319235267304 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Mon, 16 Mar 2026 15:32:11 +0100 Subject: [PATCH 3/8] Make external_ids be a list, properly handle anilist --- api/src/controllers/seed/video-links.ts | 5 +- api/src/controllers/seed/videos.ts | 3 +- api/src/db/schema/entries.ts | 28 ++-- api/src/db/schema/seasons.ts | 2 +- api/src/db/schema/utils.ts | 3 +- api/src/models/examples/bubble.ts | 20 ++- api/src/models/examples/dune-1984.ts | 20 ++- api/src/models/examples/dune-2021.ts | 20 ++- api/src/models/examples/dune-collection.ts | 10 +- api/src/models/examples/made-in-abyss.ts | 154 ++++++++++-------- api/src/models/utils/external-id.ts | 85 ++++++---- api/src/models/video.ts | 6 +- api/tests/manual.ts | 70 +++----- front/src/models/entry.ts | 29 ++-- front/src/models/season.ts | 12 +- front/src/models/utils/metadata.ts | 11 +- front/src/ui/unmatched/index.tsx | 6 +- scanner/scanner/identifiers/anilist.py | 108 ++++++++---- scanner/scanner/identifiers/identify.py | 4 +- scanner/scanner/models/collection.py | 2 +- scanner/scanner/models/entry.py | 2 +- scanner/scanner/models/metadataid.py | 20 ++- scanner/scanner/models/movie.py | 4 +- scanner/scanner/models/season.py | 2 +- scanner/scanner/models/serie.py | 4 +- scanner/scanner/models/staff.py | 2 +- scanner/scanner/models/studio.py | 2 +- scanner/scanner/models/videos.py | 1 - scanner/scanner/providers/composite.py | 5 + scanner/scanner/providers/themoviedatabase.py | 126 ++++++++------ scanner/scanner/providers/thetvdb.py | 82 ++++++---- 31 files changed, 486 insertions(+), 362 deletions(-) diff --git a/api/src/controllers/seed/video-links.ts b/api/src/controllers/seed/video-links.ts index afb20854..8142025c 100644 --- a/api/src/controllers/seed/video-links.ts +++ b/api/src/controllers/seed/video-links.ts @@ -175,7 +175,10 @@ export async function linkVideos( ), and( sql`j.entry ? 'externalId'`, - sql`j.entry->'externalId' <@ ${entriesQ.externalId}`, + sql` + (select jsonb_object_agg(key, jsonb_build_array(value)) + from jsonb_each(j.entry->'externalId')) + <@ ${entriesQ.externalId}`, ), ), ), diff --git a/api/src/controllers/seed/videos.ts b/api/src/controllers/seed/videos.ts index 12a93a4c..426f4632 100644 --- a/api/src/controllers/seed/videos.ts +++ b/api/src/controllers/seed/videos.ts @@ -20,7 +20,7 @@ import { linkVideos } from "./video-links"; const CreatedVideo = t.Object({ id: t.String({ format: "uuid" }), path: t.String({ examples: [bubbleVideo.path] }), - guess: t.Omit(Guess, ["history"]), + guess: Guess, entries: t.Array( t.Object({ slug: t.String({ format: "slug", examples: ["bubble-v2"] }), @@ -29,6 +29,7 @@ const CreatedVideo = t.Object({ }); async function createVideos(body: SeedVideo[], clearLinks: boolean) { + console.log(body.map((x) => x.guess.history)); if (body.length === 0) { return { status: 422, message: "No videos" } as const; } diff --git a/api/src/db/schema/entries.ts b/api/src/db/schema/entries.ts index 7e6228ae..6c4138c9 100644 --- a/api/src/db/schema/entries.ts +++ b/api/src/db/schema/entries.ts @@ -29,18 +29,22 @@ export const entry_extid = () => .$type< Record< string, - | { - // used for movies - dataId: string; - link: string | null; - } - | { - // used for episodes, specials & extra - serieId: string; - season: number | null; - episode: number; - link: string | null; - } + ( + | { + // used for movies + dataId: string; + link: string | null; + label?: string | null; + } + | { + // used for episodes, specials & extra + serieId: string; + season: number | null; + episode: number; + link: string | null; + label?: string | null; + } + )[] > >() .notNull() diff --git a/api/src/db/schema/seasons.ts b/api/src/db/schema/seasons.ts index f4f345f9..8237926b 100644 --- a/api/src/db/schema/seasons.ts +++ b/api/src/db/schema/seasons.ts @@ -23,7 +23,7 @@ export const season_extid = () => serieId: string; season: number; link: string | null; - } + }[] > >() .notNull() diff --git a/api/src/db/schema/utils.ts b/api/src/db/schema/utils.ts index 3f6e92e1..3ce9a680 100644 --- a/api/src/db/schema/utils.ts +++ b/api/src/db/schema/utils.ts @@ -15,7 +15,8 @@ export const externalid = () => { dataId: string; link: string | null; - } + label?: string | null; + }[] > >() .notNull() diff --git a/api/src/models/examples/bubble.ts b/api/src/models/examples/bubble.ts index ebb6e7e1..a17d674e 100644 --- a/api/src/models/examples/bubble.ts +++ b/api/src/models/examples/bubble.ts @@ -58,14 +58,18 @@ export const bubble: SeedMovie = { airDate: "2022-02-14", originalLanguage: "ja", externalId: { - themoviedatabase: { - dataId: "912598", - link: "https://www.themoviedb.org/movie/912598", - }, - imdb: { - dataId: "tt16360006", - link: "https://www.imdb.com/title/tt16360006", - }, + themoviedatabase: [ + { + dataId: "912598", + link: "https://www.themoviedb.org/movie/912598", + }, + ], + imdb: [ + { + dataId: "tt16360006", + link: "https://www.imdb.com/title/tt16360006", + }, + ], }, videos: [bubbleVideo.id], studios: [], diff --git a/api/src/models/examples/dune-1984.ts b/api/src/models/examples/dune-1984.ts index f0c2e270..6dc6b364 100644 --- a/api/src/models/examples/dune-1984.ts +++ b/api/src/models/examples/dune-1984.ts @@ -44,14 +44,18 @@ export const dune1984: SeedMovie = { airDate: "1984-12-14", originalLanguage: "en", externalId: { - themoviedatabase: { - dataId: "9495", - link: "https://www.themoviedb.org/movie/9495", - }, - imdb: { - dataId: "tt0087182", - link: "https://www.imdb.com/title/tt0087182", - }, + themoviedatabase: [ + { + dataId: "9495", + link: "https://www.themoviedb.org/movie/9495", + }, + ], + imdb: [ + { + dataId: "tt0087182", + link: "https://www.imdb.com/title/tt0087182", + }, + ], }, videos: [dune1984Video.id], studios: [], diff --git a/api/src/models/examples/dune-2021.ts b/api/src/models/examples/dune-2021.ts index ba258dfd..c5d00f5a 100644 --- a/api/src/models/examples/dune-2021.ts +++ b/api/src/models/examples/dune-2021.ts @@ -44,14 +44,18 @@ export const dune: SeedMovie = { airDate: "2021-10-22", originalLanguage: "en", externalId: { - themoviedatabase: { - dataId: "438631", - link: "https://www.themoviedb.org/movie/438631-dune", - }, - imdb: { - dataId: "tt1160419", - link: "https://www.imdb.com/title/tt1160419", - }, + themoviedatabase: [ + { + dataId: "438631", + link: "https://www.themoviedb.org/movie/438631-dune", + }, + ], + imdb: [ + { + dataId: "tt1160419", + link: "https://www.imdb.com/title/tt1160419", + }, + ], }, videos: [duneVideo.id], studios: [], diff --git a/api/src/models/examples/dune-collection.ts b/api/src/models/examples/dune-collection.ts index 4903f14b..eb6c9707 100644 --- a/api/src/models/examples/dune-collection.ts +++ b/api/src/models/examples/dune-collection.ts @@ -22,9 +22,11 @@ export const duneCollection: SeedCollection = { genres: ["adventure", "science-fiction"], rating: 80, externalId: { - themoviedatabase: { - dataId: "726871", - link: "https://www.themoviedb.org/collection/726871-dune-collection", - }, + themoviedatabase: [ + { + dataId: "726871", + link: "https://www.themoviedb.org/collection/726871-dune-collection", + }, + ], }, }; diff --git a/api/src/models/examples/made-in-abyss.ts b/api/src/models/examples/made-in-abyss.ts index 8cdbc2d0..e44f8c6b 100644 --- a/api/src/models/examples/made-in-abyss.ts +++ b/api/src/models/examples/made-in-abyss.ts @@ -109,12 +109,16 @@ export const madeInAbyss = { startAir: "2017-07-07", endAir: "2022-09-28", externalId: { - themoviedatabase: { - dataId: "72636", - link: "https://www.themoviedb.org/tv/72636", - }, - imdb: { dataId: "tt7222086", link: "https://www.imdb.com/title/tt7222086" }, - tvdb: { dataId: "326109", link: null }, + themoviedatabase: [ + { + dataId: "72636", + link: "https://www.themoviedb.org/tv/72636", + }, + ], + imdb: [ + { dataId: "tt7222086", link: "https://www.imdb.com/title/tt7222086" }, + ], + tvdb: [{ dataId: "326109", link: null }], }, seasons: [ { @@ -133,11 +137,13 @@ export const madeInAbyss = { startAir: "2017-07-07", endAir: "2017-09-29", externalId: { - themoviedatabase: { - serieId: "72636", - season: 1, - link: "https://www.themoviedb.org/tv/72636/season/1", - }, + themoviedatabase: [ + { + serieId: "72636", + season: 1, + link: "https://www.themoviedb.org/tv/72636/season/1", + }, + ], }, }, { @@ -156,11 +162,13 @@ export const madeInAbyss = { startAir: "2022-07-06", endAir: "2022-09-28", externalId: { - themoviedatabase: { - serieId: "72636", - season: 2, - link: "https://www.themoviedb.org/tv/72636/season/2", - }, + themoviedatabase: [ + { + serieId: "72636", + season: 2, + link: "https://www.themoviedb.org/tv/72636/season/2", + }, + ], }, }, ], @@ -182,12 +190,14 @@ export const madeInAbyss = { thumbnail: "https://image.tmdb.org/t/p/original/j9t1quh24suXxBetV7Q77YngID6.jpg", externalId: { - themoviedatabase: { - serieId: "72636", - season: 1, - episode: 13, - link: "https://www.themoviedb.org/tv/72636/season/1/episode/13", - }, + themoviedatabase: [ + { + serieId: "72636", + season: 1, + episode: 13, + link: "https://www.themoviedb.org/tv/72636/season/1/episode/13", + }, + ], }, videos: [madeInAbyssVideo.id], }, @@ -208,12 +218,14 @@ export const madeInAbyss = { thumbnail: "https://image.tmdb.org/t/p/original/4cMeg2ihvACsGVaSUcQJJZd96Je.jpg", externalId: { - themoviedatabase: { - serieId: "72636", - season: 0, - episode: 3, - link: "https://www.themoviedb.org/tv/72636/season/0/episode/3", - }, + themoviedatabase: [ + { + serieId: "72636", + season: 0, + episode: 3, + link: "https://www.themoviedb.org/tv/72636/season/0/episode/3", + }, + ], }, }, { @@ -235,10 +247,12 @@ export const madeInAbyss = { runtime: 105, airDate: "2020-01-17", externalId: { - themoviedatabase: { - dataId: "72636", - link: "https://www.themoviedb.org/tv/72636/season/0/episode/3", - }, + themoviedatabase: [ + { + dataId: "72636", + link: "https://www.themoviedb.org/tv/72636/season/0/episode/3", + }, + ], }, }, { @@ -258,12 +272,14 @@ export const madeInAbyss = { thumbnail: "https://image.tmdb.org/t/p/original/Tgu6E3aMf7sFHFbEIMEjetnpMi.jpg", externalId: { - themoviedatabase: { - serieId: "72636", - season: 2, - episode: 1, - link: "https://www.themoviedb.org/tv/72636/season/2/episode/1", - }, + themoviedatabase: [ + { + serieId: "72636", + season: 2, + episode: 1, + link: "https://www.themoviedb.org/tv/72636/season/2/episode/1", + }, + ], }, }, { @@ -283,12 +299,14 @@ export const madeInAbyss = { thumbnail: "https://artworks.thetvdb.com/banners/episodes/326109/6174129.jpg", externalId: { - themoviedatabase: { - serieId: "72636", - season: 2, - episode: 2, - link: "https://www.themoviedb.org/tv/72636/season/2/episode/2", - }, + themoviedatabase: [ + { + serieId: "72636", + season: 2, + episode: 2, + link: "https://www.themoviedb.org/tv/72636/season/2/episode/2", + }, + ], }, }, { @@ -308,12 +326,14 @@ export const madeInAbyss = { thumbnail: "https://artworks.thetvdb.com/banners/episodes/326109/6180539.jpg", externalId: { - themoviedatabase: { - serieId: "72636", - season: 2, - episode: 3, - link: "https://www.themoviedb.org/tv/72636/season/2/episode/4", - }, + themoviedatabase: [ + { + serieId: "72636", + season: 2, + episode: 3, + link: "https://www.themoviedb.org/tv/72636/season/2/episode/4", + }, + ], }, }, { @@ -333,12 +353,14 @@ export const madeInAbyss = { thumbnail: "https://artworks.thetvdb.com/banners/episodes/326109/6180540.jpg", externalId: { - themoviedatabase: { - serieId: "72636", - season: 2, - episode: 4, - link: "https://www.themoviedb.org/tv/72636/season/2/episode/4", - }, + themoviedatabase: [ + { + serieId: "72636", + season: 2, + episode: 4, + link: "https://www.themoviedb.org/tv/72636/season/2/episode/4", + }, + ], }, }, ], @@ -362,10 +384,12 @@ export const madeInAbyss = { }, }, externalId: { - themoviedatabase: { - dataId: "16738", - link: "https://www.themoviedb.org/company/16738", - }, + themoviedatabase: [ + { + dataId: "16738", + link: "https://www.themoviedb.org/company/16738", + }, + ], }, }, ], @@ -383,10 +407,12 @@ export const madeInAbyss = { latinName: "Mariya Ise", image: "https://cdn.myanimelist.net/images/voiceactors/2/65504.jpg", externalId: { - themoviedatabase: { - dataId: "1250465", - link: "https://www.themoviedb.org/person/1250465", - }, + themoviedatabase: [ + { + dataId: "1250465", + link: "https://www.themoviedb.org/person/1250465", + }, + ], }, }, }, diff --git a/api/src/models/utils/external-id.ts b/api/src/models/utils/external-id.ts index 39f6451e..2e98dfc5 100644 --- a/api/src/models/utils/external-id.ts +++ b/api/src/models/utils/external-id.ts @@ -4,39 +4,18 @@ import { comment } from "../../utils"; export const ExternalId = () => t.Record( t.String(), - t.Object({ - dataId: t.String(), - link: t.Nullable(t.String({ format: "uri" })), - }), + t.Array( + t.Object({ + dataId: t.String(), + link: t.Nullable(t.String({ format: "uri" })), + label: t.Optional(t.Nullable(t.String())), + }), + ), ); export const EpisodeId = t.Record( t.String(), - t.Object({ - serieId: t.String({ - descrpition: comment` - Id on the external website. - We store the serie's id because episode id are rarely stable. - `, - }), - season: t.Nullable( - t.Integer({ - description: "Null if the external website uses absolute numbering.", - }), - ), - episode: t.Integer(), - link: t.Nullable(t.String({ format: "uri" })), - }), -); -export type EpisodeId = typeof EpisodeId.static; - -export const MovieEpisodeId = t.Record( - t.String(), - t.Union([ - t.Object({ - dataId: t.String(), - link: t.Nullable(t.String({ format: "uri" })), - }), + t.Array( t.Object({ serieId: t.String({ descrpition: comment` @@ -51,21 +30,55 @@ export const MovieEpisodeId = t.Record( ), episode: t.Integer(), link: t.Nullable(t.String({ format: "uri" })), + label: t.Optional(t.Nullable(t.String())), }), - ]), + ), +); +export type EpisodeId = typeof EpisodeId.static; + +export const MovieEpisodeId = t.Record( + t.String(), + t.Array( + t.Union([ + t.Object({ + dataId: t.String(), + link: t.Nullable(t.String({ format: "uri" })), + label: t.Optional(t.Nullable(t.String())), + }), + t.Object({ + serieId: t.String({ + descrpition: comment` + Id on the external website. + We store the serie's id because episode id are rarely stable. + `, + }), + season: t.Nullable( + t.Integer({ + description: + "Null if the external website uses absolute numbering.", + }), + ), + episode: t.Integer(), + link: t.Nullable(t.String({ format: "uri" })), + label: t.Optional(t.Nullable(t.String())), + }), + ]), + ), ); export const SeasonId = t.Record( t.String(), - t.Object({ - serieId: t.String({ - descrpition: comment` + t.Array( + t.Object({ + serieId: t.String({ + descrpition: comment` Id on the external website. We store the serie's id because episode id are rarely stable. `, + }), + season: t.Integer(), + link: t.Nullable(t.String({ format: "uri" })), }), - season: t.Integer(), - link: t.Nullable(t.String({ format: "uri" })), - }), + ), ); export type SeasonId = typeof SeasonId.static; diff --git a/api/src/models/video.ts b/api/src/models/video.ts index c012f213..d3aaf5d9 100644 --- a/api/src/models/video.ts +++ b/api/src/models/video.ts @@ -88,8 +88,8 @@ export const SeedVideo = t.Object({ t.String(), t.Omit( t.Union([ - EpisodeId.patternProperties[PatternStringExact], - ExternalId().patternProperties[PatternStringExact], + EpisodeId.patternProperties[PatternStringExact].items, + ExternalId().patternProperties[PatternStringExact].items, ]), ["link"], ), @@ -154,7 +154,7 @@ registerExamples(SeedVideo, { { externalId: { themoviedatabase: { - dataId: bubble.externalId.themoviedatabase.dataId, + dataId: bubble.externalId.themoviedatabase[0].dataId, }, }, }, diff --git a/api/tests/manual.ts b/api/tests/manual.ts index f4c6d849..446e94f0 100644 --- a/api/tests/manual.ts +++ b/api/tests/manual.ts @@ -1,60 +1,30 @@ -import { db, migrate } from "~/db"; -import { profiles, shows } from "~/db/schema"; -import { bubble, madeInAbyss } from "~/models/examples"; +import { migrate } from "~/db"; import { setupLogging } from "../src/logtape"; -import { createMovie, createSerie, createVideo, getGuesses } from "./helpers"; +import { createVideo } from "./helpers"; -// test file used to run manually using `bun tests/manual.ts` -// run those before running this script -// export JWT_SECRET="this is a secret"; -// export JWT_ISSUER="https://kyoo.zoriya.dev"; +// test file used to run manually using +// `JWT_SECRET="this is a secret" JWT_ISSUER="https://kyoo.zoriya.dev" bun tests/manual.ts` await setupLogging(); await migrate(); -await db.delete(shows); -await db.delete(profiles); -const [_, ser] = await createSerie(madeInAbyss); -const [__, mov] = await createMovie(bubble); -const [resp, body] = await createVideo([ - { - guess: { - title: "mia", - episodes: [{ season: 1, episode: 13 }], - from: "test", - history: [], - }, - part: null, - path: "/video/mia s1e13.mkv", - rendering: "sha2", - version: 1, - for: [{ slug: `${madeInAbyss.slug}-s1e13` }], +const [resp, body] = await createVideo({ + guess: { + title: "mia", + episodes: [{ season: 1, episode: 13 }], + from: "test", + history: [ + { + title: "toto", + from: "tata", + }, + ], }, - { - guess: { - title: "mia", - episodes: [{ season: 2, episode: 1 }], - years: [2017], - from: "test", - history: [], - }, - part: null, - path: "/video/mia 2017 s2e1.mkv", - rendering: "sha8", - version: 1, - for: [{ slug: `${madeInAbyss.slug}-s2e1` }], - }, - { - guess: { title: "bubble", from: "test", history: [] }, - part: null, - path: "/video/bubble.mkv", - rendering: "sha5", - version: 1, - for: [{ movie: bubble.slug }], - }, -]); + part: null, + path: "/video/mia s1e13.mkv", + rendering: "sha2", + version: 1, +}); console.log(body); -const [___, ret] = await getGuesses(); -console.log(JSON.stringify(ret, undefined, 4)); process.exit(0); diff --git a/front/src/models/entry.ts b/front/src/models/entry.ts index f0698233..2690eead 100644 --- a/front/src/models/entry.ts +++ b/front/src/models/entry.ts @@ -42,12 +42,15 @@ export const Episode = Base.extend({ episodeNumber: z.int().gte(0), externalId: z.record( z.string(), - z.object({ - serieId: z.string(), - season: z.int().nullable(), - episode: z.int(), - link: z.string().nullable(), - }), + z.array( + z.object({ + serieId: z.string(), + season: z.int().nullable(), + episode: z.int(), + link: z.string().nullable(), + label: z.string().optional().nullable(), + }), + ), ), }); export type Episode = z.infer; @@ -65,12 +68,14 @@ export const Special = Base.extend({ number: z.int(), externalId: z.record( z.string(), - z.object({ - serieId: z.string(), - season: z.int().nullable(), - episode: z.int(), - link: z.string().nullable(), - }), + z.array( + z.object({ + serieId: z.string(), + season: z.int().nullable(), + episode: z.int(), + link: z.string().nullable(), + }), + ), ), }); export type Special = z.infer; diff --git a/front/src/models/season.ts b/front/src/models/season.ts index 1640b0f3..9c471985 100644 --- a/front/src/models/season.ts +++ b/front/src/models/season.ts @@ -14,11 +14,13 @@ export const Season = z.object({ endAir: zdate().nullable(), externalId: z.record( z.string(), - z.object({ - serieId: z.string(), - season: z.number(), - link: z.string().nullable(), - }), + z.array( + z.object({ + serieId: z.string(), + season: z.number(), + link: z.string().nullable(), + }), + ), ), poster: KImage.nullable(), diff --git a/front/src/models/utils/metadata.ts b/front/src/models/utils/metadata.ts index 33000bfc..5ae37164 100644 --- a/front/src/models/utils/metadata.ts +++ b/front/src/models/utils/metadata.ts @@ -2,9 +2,12 @@ import { z } from "zod/v4"; export const Metadata = z.record( z.string(), - z.object({ - dataId: z.string(), - link: z.string().nullable(), - }), + z.array( + z.object({ + dataId: z.string(), + link: z.string().nullable(), + label: z.string().optional().nullable(), + }), + ), ); export type Metadata = z.infer; diff --git a/front/src/ui/unmatched/index.tsx b/front/src/ui/unmatched/index.tsx index 0274915a..94b63433 100644 --- a/front/src/ui/unmatched/index.tsx +++ b/front/src/ui/unmatched/index.tsx @@ -259,11 +259,7 @@ export const UnmatchedPage = () => { )} Loader={() => } Divider - Empty={ - -

{t("admin.unmatched.empty")}

-
- } + Empty={

{t("admin.unmatched.empty")}

} /> ); }; diff --git a/scanner/scanner/identifiers/anilist.py b/scanner/scanner/identifiers/anilist.py index f038ff41..2f642e4b 100644 --- a/scanner/scanner/identifiers/anilist.py +++ b/scanner/scanner/identifiers/anilist.py @@ -2,6 +2,7 @@ from __future__ import annotations import re import unicodedata +from collections import defaultdict from dataclasses import dataclass, field from datetime import datetime, timedelta from functools import cached_property @@ -13,7 +14,8 @@ from pydantic import field_validator from pydantic_xml import BaseXmlModel, attr, element, wrapped from ..cache import cache -from ..models.metadataid import EpisodeId +from ..models.metadataid import EpisodeId, MetadataId, SeasonId +from ..models.serie import Serie from ..models.videos import Guess from ..providers.names import ProviderName @@ -103,6 +105,8 @@ class AnimeListData: titles: dict[str, str] = field(default_factory=dict) # anidbid -> AnimeEntry animes: dict[str, AnimeListDb.AnimeEntry] = field(default_factory=dict) + # tvdbid -> anidbid + tvdb_anidb: dict[str, list[str]] = field(default_factory=dict) @cache(ttl=timedelta(days=30)) @@ -122,6 +126,11 @@ async def get_data() -> AnimeListData: resp.raise_for_status() db = AnimeListDb.from_xml(await resp.read()) ret.animes = {entry.anidbid: entry for entry in db.animes} + ret.tvdb_anidb = defaultdict(list) + for entry in db.animes: + if not entry.tvdbid: + continue + ret.tvdb_anidb[entry.tvdbid].append(entry.anidbid) logger.info( "Loaded %d anime titles from animelist-xml.", @@ -170,7 +179,7 @@ def anidb_to_tvdb( def tvdb_to_anidb( anime: AnimeListDb.AnimeEntry, - tvdb_season: int | None, + tvdb_season: int, tvdb_ep: int, ) -> list[int]: for map in anime.mappings: @@ -195,7 +204,7 @@ def tvdb_to_anidb( return [tvdb_ep - anime.episodeoffset] -async def anilist(_path: str, guess: Guess) -> Guess: +async def identify_anilist(_path: str, guess: Guess) -> Guess: data = await get_data() aid = data.titles.get(guess.title) @@ -227,53 +236,28 @@ async def anilist(_path: str, guess: Guess) -> Guess: new_episodes: list[Guess.Episode] = [] for ep in guess.episodes: - if anime.defaulttvdbseason is None or anime.tvdbid is None: + if ( + anime.tvdbid is None + or anime.defaulttvdbseason is None + or anime.defaulttvdbseason == 1 + ): new_episodes.append( Guess.Episode( - season=ep.season, + season=ep.season or (1 if anime.defaulttvdbseason else None), episode=ep.episode, - external_id={ - ProviderName.ANIDB: EpisodeId( - serie_id=aid, - season=None, - episode=ep.episode, - ), - }, ) ) continue # guess numbers are anidb-relative if defaulttvdbseason != 1 because # the title already contains season information. - tvdb_season, tvdb_eps = ( - (ep.season if ep.season is not None else 1, [ep.episode]) - if anime.defaulttvdbseason == 1 - else anidb_to_tvdb(anime, ep.episode) - ) - anidb_eps = ( - tvdb_to_anidb(anime, tvdb_season, ep.episode) - if anime.defaulttvdbseason == 1 - else [ep.episode] - ) - + tvdb_season, tvdb_eps = anidb_to_tvdb(anime, ep.episode) new_episodes += [ Guess.Episode( season=tvdb_season, episode=tvdb_ep, - external_id={ - ProviderName.TVDB: EpisodeId( - serie_id=anime.tvdbid, - season=tvdb_season, - episode=tvdb_ep, - ), - ProviderName.ANIDB: EpisodeId( - serie_id=aid, - season=None, - episode=anidb_ep, - ), - }, ) - for tvdb_ep, anidb_ep in zip(tvdb_eps, anidb_eps) + for tvdb_ep in tvdb_eps ] return Guess( @@ -287,3 +271,55 @@ async def anilist(_path: str, guess: Guess) -> Guess: from_="anilist", history=[*guess.history, guess], ) + + +async def anilist_enrich_ids(serie: Serie): + data = await get_data() + animes = [ + data.animes[aid] + for tvdb_id in serie.external_id[ProviderName.TVDB] + for aid in data.tvdb_anidb.get(tvdb_id.data_id, []) + ] + if not animes: + return serie + + serie.external_id[ProviderName.ANIDB] = [ + MetadataId( + data_id=anime.anidbid, + link=f"https://anidb.net/anime/{anime.anidbid}", + label=anime.name, + ) + for anime in animes + ] + + for season in serie.seasons: + season.external_id[ProviderName.ANIDB] = [ + SeasonId( + serie_id=anime.anidbid, + season=1, + link=f"https://anidb.net/anime/{anime.anidbid}", + label=anime.name, + ) + for anime in animes + if anime.defaulttvdbseason == season.season_number + or anime.defaulttvdbseason == "a" + ] + + for entry in serie.entries: + season = entry.season_number or 0 + episode = entry.episode_number or entry.number + if episode is None: + continue + anime = next( + (x for x in animes if x.defaulttvdbseason == season), + next( + (x for x in animes if x.defaulttvdbseason == "a"), + animes[0], + ), + ) + adb_numbers = tvdb_to_anidb(anime, season, episode) + entry.external_id[ProviderName.ANIDB] = [ + EpisodeId(serie_id=anime.anidbid, season=1, episode=x) for x in adb_numbers + ] + + return serie diff --git a/scanner/scanner/identifiers/identify.py b/scanner/scanner/identifiers/identify.py index 4d2790d6..555f7473 100644 --- a/scanner/scanner/identifiers/identify.py +++ b/scanner/scanner/identifiers/identify.py @@ -7,13 +7,13 @@ from typing import Callable, Literal, cast from rebulk.match import Match from ..models.videos import Guess, Video -from .anilist import anilist +from .anilist import identify_anilist from .guess.guess import guessit logger = getLogger(__name__) pipeline: list[Callable[[str, Guess], Awaitable[Guess]]] = [ - anilist, + identify_anilist, # TODO: add nfo scanner # TODO: add thexem ] diff --git a/scanner/scanner/models/collection.py b/scanner/scanner/models/collection.py index 1229357b..8e602b5d 100644 --- a/scanner/scanner/models/collection.py +++ b/scanner/scanner/models/collection.py @@ -10,7 +10,7 @@ class Collection(Model): original_language: Language | None genres: list[Genre] rating: int | None - external_id: dict[str, MetadataId] + external_id: dict[str, list[MetadataId]] translations: dict[Language, CollectionTranslation] = {} diff --git a/scanner/scanner/models/entry.py b/scanner/scanner/models/entry.py index cbc0bcd9..b8f4071f 100644 --- a/scanner/scanner/models/entry.py +++ b/scanner/scanner/models/entry.py @@ -26,7 +26,7 @@ class Entry(Model): # Special-specific fields number: int | None - external_id: dict[str, MetadataId | EpisodeId] + external_id: dict[str, list[MetadataId | EpisodeId]] translations: dict[Language, EntryTranslation] = {} videos: list[str] = [] extra: dict[str, Any] = Field(exclude=True) diff --git a/scanner/scanner/models/metadataid.py b/scanner/scanner/models/metadataid.py index 00d1a16a..8cf09b2f 100644 --- a/scanner/scanner/models/metadataid.py +++ b/scanner/scanner/models/metadataid.py @@ -6,19 +6,26 @@ from ..utils import Model class MetadataId(Model): data_id: str link: str | None = None + label: str | None = None @classmethod - def map_dict(cls, self: dict[str, MetadataId]): - return {k: v.data_id for k, v in self.items()} + def map_dict(cls, self: dict[str, list[MetadataId]]): + return {k: v[0].data_id for k, v in self.items()} @classmethod def merge( - cls, self: dict[str, MetadataId], other: dict[str, MetadataId] - ) -> dict[str, MetadataId]: + cls, + self: dict[str, list[MetadataId]], + other: dict[str, list[MetadataId]], + ) -> dict[str, list[MetadataId]]: ret = other | self for k in set(self.keys()) & set(other.keys()): - if ret[k].data_id == other[k].data_id and ret[k].link is None: - ret[k].link = other[k].link + for x in ret[k]: + if x.link is not None: + continue + o = next((ox for ox in other[k] if ox.data_id == x.data_id), None) + if o: + x = o.link return ret @@ -26,6 +33,7 @@ class SeasonId(Model): serie_id: str season: int link: str | None = None + label: str | None = None class EpisodeId(Model): diff --git a/scanner/scanner/models/movie.py b/scanner/scanner/models/movie.py index ca50f205..ddf4d3fd 100644 --- a/scanner/scanner/models/movie.py +++ b/scanner/scanner/models/movie.py @@ -26,7 +26,7 @@ class Movie(Model): runtime: int | None air_date: date | None - external_id: dict[str, MetadataId] + external_id: dict[str, list[MetadataId]] translations: dict[Language, MovieTranslation] = {} collection: Collection | None = None studios: list[Studio] = [] @@ -56,4 +56,4 @@ class SearchMovie(Model): air_date: date | None poster: str | None original_language: Language | None - external_id: dict[str, MetadataId] + external_id: dict[str, list[MetadataId]] diff --git a/scanner/scanner/models/season.py b/scanner/scanner/models/season.py index 0de0c0f1..4dd29dc3 100644 --- a/scanner/scanner/models/season.py +++ b/scanner/scanner/models/season.py @@ -13,7 +13,7 @@ class Season(Model): season_number: int start_air: date | None end_air: date | None - external_id: dict[str, SeasonId] + external_id: dict[str, list[SeasonId]] translations: dict[Language, SeasonTranslation] = {} extra: dict[str, Any] = Field(exclude=True) diff --git a/scanner/scanner/models/serie.py b/scanner/scanner/models/serie.py index cdf0402c..ea58646f 100644 --- a/scanner/scanner/models/serie.py +++ b/scanner/scanner/models/serie.py @@ -31,7 +31,7 @@ class Serie(Model): start_air: date | None end_air: date | None - external_id: dict[str, MetadataId] + external_id: dict[str, list[MetadataId]] translations: dict[Language, SerieTranslation] = {} seasons: list[Season] = [] entries: list[Entry] = [] @@ -64,4 +64,4 @@ class SearchSerie(Model): end_air: date | None poster: str | None original_language: Language | None - external_id: dict[str, MetadataId] + external_id: dict[str, list[MetadataId]] diff --git a/scanner/scanner/models/staff.py b/scanner/scanner/models/staff.py index dfe82bc3..5901e413 100644 --- a/scanner/scanner/models/staff.py +++ b/scanner/scanner/models/staff.py @@ -33,4 +33,4 @@ class Person(Model): name: str latin_name: str | None image: str | None - external_id: dict[str, MetadataId] + external_id: dict[str, list[MetadataId]] diff --git a/scanner/scanner/models/studio.py b/scanner/scanner/models/studio.py index eff7840b..6cdd1a60 100644 --- a/scanner/scanner/models/studio.py +++ b/scanner/scanner/models/studio.py @@ -6,7 +6,7 @@ from .metadataid import MetadataId class Studio(Model): slug: str - external_id: dict[str, MetadataId] + external_id: dict[str, list[MetadataId]] translations: dict[str, StudioTranslation] = {} diff --git a/scanner/scanner/models/videos.py b/scanner/scanner/models/videos.py index a128a0fb..de724b8d 100644 --- a/scanner/scanner/models/videos.py +++ b/scanner/scanner/models/videos.py @@ -33,7 +33,6 @@ class Guess(Model, extra="allow"): class Episode(Model): season: int | None episode: int - external_id: dict[str, MetadataId | EpisodeId] = {} @override def __hash__(self) -> int: diff --git a/scanner/scanner/providers/composite.py b/scanner/scanner/providers/composite.py index 9b44c031..c8b5443b 100644 --- a/scanner/scanner/providers/composite.py +++ b/scanner/scanner/providers/composite.py @@ -2,7 +2,9 @@ from typing import override from langcodes import Language +from scanner.identifiers.anilist import anilist_enrich_ids from scanner.models.metadataid import MetadataId +from scanner.providers.names import ProviderName from scanner.utils import uniq_by from ..models.movie import Movie, SearchMovie @@ -59,6 +61,9 @@ class CompositeProvider(Provider): ret.entries, lambda x: (x.season_number, x.episode_number, x.number, x.slug) ) + if ProviderName.ANIDB in external_id: + ret = await anilist_enrich_ids(ret) + # 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 diff --git a/scanner/scanner/providers/themoviedatabase.py b/scanner/scanner/providers/themoviedatabase.py index cadce35c..a861ccee 100644 --- a/scanner/scanner/providers/themoviedatabase.py +++ b/scanner/scanner/providers/themoviedatabase.py @@ -127,10 +127,12 @@ class TheMovieDatabase(Provider): poster=self._map_image(x["poster_path"]), original_language=Language.get(x["original_language"]), external_id={ - self.name: MetadataId( - data_id=str(x["id"]), - link=f"https://www.themoviedb.org/movie/{x['id']}", - ) + self.name: [ + MetadataId( + data_id=str(x["id"]), + link=f"https://www.themoviedb.org/movie/{x['id']}", + ) + ] }, ) for x in search @@ -163,17 +165,21 @@ class TheMovieDatabase(Provider): else None, external_id=( { - self.name: MetadataId( - data_id=str(movie["id"]), - link=f"https://www.themoviedb.org/movie/{movie['id']}", - ) + self.name: [ + MetadataId( + data_id=str(movie["id"]), + link=f"https://www.themoviedb.org/movie/{movie['id']}", + ) + ] } | ( { - ProviderName.IMDB: MetadataId( - data_id=str(movie["imdb_id"]), - link=f"https://www.imdb.com/title/{movie['imdb_id']}", - ) + ProviderName.IMDB: [ + MetadataId( + data_id=str(movie["imdb_id"]), + link=f"https://www.imdb.com/title/{movie['imdb_id']}", + ) + ] } if movie["imdb_id"] else {} @@ -256,10 +262,12 @@ class TheMovieDatabase(Provider): poster=self._map_image(x["poster_path"]), original_language=Language.get(x["original_language"]), external_id={ - self.name: MetadataId( - data_id=str(x["id"]), - link=f"https://www.themoviedb.org/tv/{x['id']}", - ) + self.name: [ + MetadataId( + data_id=str(x["id"]), + link=f"https://www.themoviedb.org/tv/{x['id']}", + ) + ] }, ) for x in search @@ -317,27 +325,33 @@ class TheMovieDatabase(Provider): if serie["last_air_date"] else None, external_id={ - self.name: MetadataId( - data_id=str((serie["id"])), - link=f"https://www.themoviedb.org/tv/{serie['id']}", - ), + self.name: [ + MetadataId( + data_id=str((serie["id"])), + link=f"https://www.themoviedb.org/tv/{serie['id']}", + ) + ], } | ( { - ProviderName.IMDB: MetadataId( - data_id=str(serie["external_ids"]["imdb_id"]), - link=f"https://www.imdb.com/title/{serie['external_ids']['imdb_id']}", - ) + ProviderName.IMDB: [ + MetadataId( + data_id=str(serie["external_ids"]["imdb_id"]), + link=f"https://www.imdb.com/title/{serie['external_ids']['imdb_id']}", + ) + ] } if serie["external_ids"]["imdb_id"] else {} ) | ( { - ProviderName.TVDB: MetadataId( - data_id=str(serie["external_ids"]["tvdb_id"]), - link=None, - ) + ProviderName.TVDB: [ + MetadataId( + data_id=str(serie["external_ids"]["tvdb_id"]), + link=None, + ) + ] } if serie["external_ids"]["tvdb_id"] else {} @@ -408,11 +422,13 @@ class TheMovieDatabase(Provider): else None, end_air=None, external_id={ - self.name: SeasonId( - serie_id=str(serie_id), - season=season["season_number"], - link=f"https://www.themoviedb.org/tv/{serie_id}/season/{season['season_number']}", - ) + self.name: [ + SeasonId( + serie_id=str(serie_id), + season=season["season_number"], + link=f"https://www.themoviedb.org/tv/{serie_id}/season/{season['season_number']}", + ) + ] }, translations={ Language.get( @@ -543,12 +559,14 @@ class TheMovieDatabase(Provider): episode_number=episode["episode_number"], number=episode["episode_number"], external_id={ - self.name: EpisodeId( - serie_id=str(serie_id), - season=episode["season_number"], - episode=episode["episode_number"], - link=f"https://www.themoviedb.org/tv/{serie_id}/season/{episode['season_number']}/episode/{episode['episode_number']}", - ), + self.name: [ + EpisodeId( + serie_id=str(serie_id), + season=episode["season_number"], + episode=episode["episode_number"], + link=f"https://www.themoviedb.org/tv/{serie_id}/season/{episode['season_number']}/episode/{episode['episode_number']}", + ) + ], }, translations={ Language.get( @@ -583,10 +601,12 @@ class TheMovieDatabase(Provider): mean(float(x["vote_average"]) * 10 for x in collection["parts"]) ), external_id={ - self.name: MetadataId( - data_id=str(collection["id"]), - link=f"https://www.themoviedb.org/collection/{collection['id']}", - ) + self.name: [ + MetadataId( + data_id=str(collection["id"]), + link=f"https://www.themoviedb.org/collection/{collection['id']}", + ) + ] }, translations={ Language.get( @@ -685,10 +705,12 @@ class TheMovieDatabase(Provider): return Studio( slug=to_slug(company["name"]), external_id={ - self.name: MetadataId( - data_id=str(company["id"]), - link=f"https://www.themoviedb.org/company/{company['id']}", - ) + self.name: [ + MetadataId( + data_id=str(company["id"]), + link=f"https://www.themoviedb.org/company/{company['id']}", + ) + ] }, translations={ "en": StudioTranslation( @@ -714,10 +736,12 @@ class TheMovieDatabase(Provider): latin_name=person["name"], image=self._map_image(person["profile_path"]), external_id={ - self.name: MetadataId( - data_id=str(person["id"]), - link=f"https://www.themoviedb.org/person/{person['id']}", - ) + self.name: [ + MetadataId( + data_id=str(person["id"]), + link=f"https://www.themoviedb.org/person/{person['id']}", + ) + ] }, ), ) diff --git a/scanner/scanner/providers/thetvdb.py b/scanner/scanner/providers/thetvdb.py index e9032d01..e075dff0 100644 --- a/scanner/scanner/providers/thetvdb.py +++ b/scanner/scanner/providers/thetvdb.py @@ -181,10 +181,12 @@ class TVDB(Provider): poster=x["image_url"], original_language=Language.get(x["primary_language"]), external_id={ - self.name: MetadataId( - data_id=str(x["tvdb_id"]), - link=f"https://thetvdb.com/series/{x['slug']}", - ), + self.name: [ + MetadataId( + data_id=str(x["tvdb_id"]), + link=f"https://thetvdb.com/series/{x['slug']}", + ) + ], }, ) for x in ret["data"] @@ -234,10 +236,12 @@ class TVDB(Provider): start_air=datetime.strptime(ret["firstAired"], "%Y-%m-%d").date(), end_air=datetime.strptime(ret["lastAired"], "%Y-%m-%d").date(), external_id={ - self.name: MetadataId( - data_id=ret["id"], - link=f"https://thetvdb.com/series/{ret['slug']}", - ), + self.name: [ + MetadataId( + data_id=ret["id"], + link=f"https://thetvdb.com/series/{ret['slug']}", + ) + ], **self._process_remote_id(ret["remoteIds"]), }, translations={ @@ -332,7 +336,7 @@ class TVDB(Provider): def _process_remote_id( self, ids: list[dict[str, Any]] | None - ) -> dict[str, MetadataId]: + ) -> dict[str, list[MetadataId]]: # sometimes `remoteIds` is not even part of the response. if ids is None: return {} @@ -341,11 +345,11 @@ class TVDB(Provider): imdb = next((x["id"] for x in ids if x["sourceName"] == "IMDB"), None) if imdb is not None: - ret[ProviderName.IMDB] = MetadataId(data_id=imdb) + ret[ProviderName.IMDB] = [MetadataId(data_id=imdb)] tmdb = next((x["id"] for x in ids if x["sourceName"] == "TheMovieDB.com"), None) if tmdb is not None: - ret[ProviderName.TMDB] = MetadataId(data_id=tmdb) + ret[ProviderName.TMDB] = [MetadataId(data_id=tmdb)] return ret @@ -421,10 +425,12 @@ class TVDB(Provider): ], rating=None, external_id={ - self.name: MetadataId( - data_id=data["id"], - link=f"https://thetvdb.com/lists/{data['url']}", - ) + self.name: [ + MetadataId( + data_id=data["id"], + link=f"https://thetvdb.com/lists/{data['url']}", + ) + ] }, translations={ Language.get(lang): tl @@ -472,10 +478,12 @@ class TVDB(Provider): default=None, ), external_id={ - self.name: SeasonId( - serie_id=info["seriesId"], - season=info["number"], - ), + self.name: [ + SeasonId( + serie_id=info["seriesId"], + season=info["number"], + ) + ], }, translations={Language.get(lang): tl for lang, tl in zip(languages, trans)}, extra={}, @@ -520,12 +528,14 @@ class TVDB(Provider): episode_number=entry["number"], number=entry["number"], external_id={ - self.name: EpisodeId( - serie_id=str(serie_id), - season=entry["seasonNumber"], - episode=entry["number"], - link=f"https://thetvdb.com/series/{serie_id}/episodes/{entry['id']}", - ), + self.name: [ + EpisodeId( + serie_id=str(serie_id), + season=entry["seasonNumber"], + episode=entry["number"], + link=f"https://thetvdb.com/series/{serie_id}/episodes/{entry['id']}", + ) + ], }, translations={ Language.get(lang): EntryTranslation( @@ -672,11 +682,13 @@ class TVDB(Provider): for trans in ret["translations"]["nameTranslations"] if trans.get("isAlias") is None or False } - entry.external_id = { - self.name: MetadataId( - data_id=ret["id"], - link=f"https://thetvdb.com/movies/{ret['slug']}", - ), + entry.external_id = { # pyright: ignore[reportAttributeAccessIssue] + self.name: [ + MetadataId( + data_id=ret["id"], + link=f"https://thetvdb.com/movies/{ret['slug']}", + ) + ], **self._process_remote_id(ret["remoteIds"]), } @@ -731,10 +743,12 @@ class TVDB(Provider): if ret.get("first_release") and ret["first_release"].get("date") else None, external_id={ - self.name: MetadataId( - data_id=ret["id"], - link=f"https://thetvdb.com/series/{ret['slug']}", - ), + self.name: [ + MetadataId( + data_id=ret["id"], + link=f"https://thetvdb.com/series/{ret['slug']}", + ) + ], **self._process_remote_id(ret["remoteIds"]), }, translations={ From ca15f57f309ce95dcad3ee449ce144d95e525bce Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Tue, 17 Mar 2026 12:20:29 +0100 Subject: [PATCH 4/8] Fix history --- api/bun.lock | 272 +++++++++++++++++----------- api/package.json | 56 +++--- api/src/controllers/seed/videos.ts | 1 - api/src/models/video.ts | 70 ++++--- api/tests/series/get-series.test.ts | 2 - api/tests/videos/scanner.test.ts | 21 +++ 6 files changed, 258 insertions(+), 164 deletions(-) diff --git a/api/bun.lock b/api/bun.lock index 49bce478..72ba44f3 100644 --- a/api/bun.lock +++ b/api/bun.lock @@ -8,42 +8,42 @@ "@elysiajs/opentelemetry": "^1.4.10", "@elysiajs/swagger": "zoriya/elysia-swagger#build", "@kubiks/otel-drizzle": "zoriya/drizzle-otel#build", - "@logtape/elysia": "2.0.2", - "@logtape/logtape": "2.0.2", - "@logtape/otel": "2.0.2", - "@logtape/redaction": "2.0.2", + "@logtape/elysia": "2.0.4", + "@logtape/logtape": "2.0.4", + "@logtape/otel": "2.0.4", + "@logtape/redaction": "2.0.4", "@opentelemetry/api": "^1.9.0", - "@opentelemetry/api-logs": "^0.212.0", - "@opentelemetry/core": "^2.5.1", - "@opentelemetry/exporter-logs-otlp-grpc": "^0.212.0", - "@opentelemetry/exporter-logs-otlp-http": "^0.212.0", - "@opentelemetry/exporter-logs-otlp-proto": "^0.212.0", - "@opentelemetry/exporter-metrics-otlp-grpc": "^0.212.0", - "@opentelemetry/exporter-metrics-otlp-http": "^0.212.0", - "@opentelemetry/exporter-metrics-otlp-proto": "^0.212.0", - "@opentelemetry/exporter-trace-otlp-grpc": "^0.212.0", - "@opentelemetry/exporter-trace-otlp-http": "^0.212.0", - "@opentelemetry/exporter-trace-otlp-proto": "^0.212.0", - "@opentelemetry/resources": "^2.5.1", - "@opentelemetry/sdk-logs": "^0.212.0", - "@opentelemetry/sdk-metrics": "^2.5.1", - "@opentelemetry/sdk-trace-base": "^2.5.1", - "@opentelemetry/sdk-trace-node": "^2.5.1", - "@opentelemetry/semantic-conventions": "^1.39.0", - "@types/bun": "^1.3.9", + "@opentelemetry/api-logs": "^0.213.0", + "@opentelemetry/core": "^2.6.0", + "@opentelemetry/exporter-logs-otlp-grpc": "^0.213.0", + "@opentelemetry/exporter-logs-otlp-http": "^0.213.0", + "@opentelemetry/exporter-logs-otlp-proto": "^0.213.0", + "@opentelemetry/exporter-metrics-otlp-grpc": "^0.213.0", + "@opentelemetry/exporter-metrics-otlp-http": "^0.213.0", + "@opentelemetry/exporter-metrics-otlp-proto": "^0.213.0", + "@opentelemetry/exporter-trace-otlp-grpc": "^0.213.0", + "@opentelemetry/exporter-trace-otlp-http": "^0.213.0", + "@opentelemetry/exporter-trace-otlp-proto": "^0.213.0", + "@opentelemetry/resources": "^2.6.0", + "@opentelemetry/sdk-logs": "^0.213.0", + "@opentelemetry/sdk-metrics": "^2.6.0", + "@opentelemetry/sdk-trace-base": "^2.6.0", + "@opentelemetry/sdk-trace-node": "^2.6.0", + "@opentelemetry/semantic-conventions": "^1.40.0", + "@types/bun": "^1.3.10", "blurhash": "^2.0.5", "drizzle-kit": "^0.31.5", "drizzle-orm": "0.44.7", - "elysia": "^1.4.25", - "jose": "^6.1.3", - "node-addon-api": "^8.5.0", + "elysia": "^1.4.28", + "jose": "^6.2.1", + "node-addon-api": "^8.6.0", "parjs": "^1.3.9", - "pg": "^8.17.2", + "pg": "^8.20.0", "sharp": "^0.34.5", }, "devDependencies": { - "@biomejs/biome": "2.4.4", - "@types/pg": "^8.16.0", + "@biomejs/biome": "2.4.7", + "@types/pg": "^8.18.0", "typescript": "^5.9.3", }, }, @@ -52,23 +52,23 @@ "drizzle-orm@0.44.7": "patches/drizzle-orm@0.44.7.patch", }, "packages": { - "@biomejs/biome": ["@biomejs/biome@2.4.4", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.4.4", "@biomejs/cli-darwin-x64": "2.4.4", "@biomejs/cli-linux-arm64": "2.4.4", "@biomejs/cli-linux-arm64-musl": "2.4.4", "@biomejs/cli-linux-x64": "2.4.4", "@biomejs/cli-linux-x64-musl": "2.4.4", "@biomejs/cli-win32-arm64": "2.4.4", "@biomejs/cli-win32-x64": "2.4.4" }, "bin": { "biome": "bin/biome" } }, "sha512-tigwWS5KfJf0cABVd52NVaXyAVv4qpUXOWJ1rxFL8xF1RVoeS2q/LK+FHgYoKMclJCuRoCWAPy1IXaN9/mS61Q=="], + "@biomejs/biome": ["@biomejs/biome@2.4.7", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.4.7", "@biomejs/cli-darwin-x64": "2.4.7", "@biomejs/cli-linux-arm64": "2.4.7", "@biomejs/cli-linux-arm64-musl": "2.4.7", "@biomejs/cli-linux-x64": "2.4.7", "@biomejs/cli-linux-x64-musl": "2.4.7", "@biomejs/cli-win32-arm64": "2.4.7", "@biomejs/cli-win32-x64": "2.4.7" }, "bin": { "biome": "bin/biome" } }, "sha512-vXrgcmNGZ4lpdwZSpMf1hWw1aWS6B+SyeSYKTLrNsiUsAdSRN0J4d/7mF3ogJFbIwFFSOL3wT92Zzxia/d5/ng=="], - "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.4.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-jZ+Xc6qvD6tTH5jM6eKX44dcbyNqJHssfl2nnwT6vma6B1sj7ZLTGIk6N5QwVBs5xGN52r3trk5fgd3sQ9We9A=="], + "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.4.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Oo0cF5mHzmvDmTXw8XSjhCia8K6YrZnk7aCS54+/HxyMdZMruMO3nfpDsrlar/EQWe41r1qrwKiCa2QDYHDzWA=="], - "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.4.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-Dh1a/+W+SUCXhEdL7TiX3ArPTFCQKJTI1mGncZNWfO+6suk+gYA4lNyJcBB+pwvF49uw0pEbUS49BgYOY4hzUg=="], + "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.4.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-I+cOG3sd/7HdFtvDSnF9QQPrWguUH7zrkIMMykM3PtfWU9soTcS2yRb9Myq6MHmzbeCT08D1UmY+BaiMl5CcoQ=="], - "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.4.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-V/NFfbWhsUU6w+m5WYbBenlEAz8eYnSqRMDMAW3K+3v0tYVkNyZn8VU0XPxk/lOqNXLSCCrV7FmV/u3SjCBShg=="], + "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.4.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-om6FugwmibzfP/6ALj5WRDVSND4H2G9X0nkI1HZpp2ySf9lW2j0X68oQSaHEnls6666oy4KDsc5RFjT4m0kV0w=="], - "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.4.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-+sPAXq3bxmFwhVFJnSwkSF5Rw2ZAJMH3MF6C9IveAEOdSpgajPhoQhbbAK12SehN9j2QrHpk4J/cHsa/HqWaYQ=="], + "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.4.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-I2NvM9KPb09jWml93O2/5WMfNR7Lee5Latag1JThDRMURVhPX74p9UDnyTw3Ae6cE1DgXfw7sqQgX7rkvpc0vw=="], - "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.4.4", "", { "os": "linux", "cpu": "x64" }, "sha512-R4+ZCDtG9kHArasyBO+UBD6jr/FcFCTH8QkNTOCu0pRJzCWyWC4EtZa2AmUZB5h3e0jD7bRV2KvrENcf8rndBg=="], + "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.4.7", "", { "os": "linux", "cpu": "x64" }, "sha512-bV8/uo2Tj+gumnk4sUdkerWyCPRabaZdv88IpbmDWARQQoA/Q0YaqPz1a+LSEDIL7OfrnPi9Hq1Llz4ZIGyIQQ=="], - "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.4.4", "", { "os": "linux", "cpu": "x64" }, "sha512-gGvFTGpOIQDb5CQ2VC0n9Z2UEqlP46c4aNgHmAMytYieTGEcfqhfCFnhs6xjt0S3igE6q5GLuIXtdQt3Izok+g=="], + "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.4.7", "", { "os": "linux", "cpu": "x64" }, "sha512-00kx4YrBMU8374zd2wHuRV5wseh0rom5HqRND+vDldJPrWwQw+mzd/d8byI9hPx926CG+vWzq6AeiT7Yi5y59g=="], - "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.4.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-trzCqM7x+Gn832zZHgr28JoYagQNX4CZkUZhMUac2YxvvyDRLJDrb5m9IA7CaZLlX6lTQmADVfLEKP1et1Ma4Q=="], + "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.4.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-hOUHBMlFCvDhu3WCq6vaBoG0dp0LkWxSEnEEsxxXvOa9TfT6ZBnbh72A/xBM7CBYB7WgwqboetzFEVDnMxelyw=="], - "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.4.4", "", { "os": "win32", "cpu": "x64" }, "sha512-gnOHKVPFAAPrpoPt2t+Q6FZ7RPry/FDV3GcpU53P3PtLNnQjBmKyN2Vh/JtqXet+H4pme8CC76rScwdjDcT1/A=="], + "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.4.7", "", { "os": "win32", "cpu": "x64" }, "sha512-qEpGjSkPC3qX4ycbMUthXvi9CkRq7kZpkqMY1OyhmYlYLnANnooDQ7hDerM8+0NJ+DZKVnsIc07h30XOpt7LtQ=="], "@borewit/text-codec": ["@borewit/text-codec@0.2.1", "", {}, "sha512-k7vvKPbf7J2fZ5klGRD9AeKfUvojuZIQ3BT5u7Jfv+puwXkUBUT5PVyMDfJZpy30CBDXGMgw7fguK/lpOMBvgw=="], @@ -194,41 +194,41 @@ "@kubiks/otel-drizzle": ["@kubiks/otel-drizzle@github:zoriya/drizzle-otel#cc1d59b", { "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <2.0.0", "drizzle-orm": ">=0.28.0" } }, "zoriya-drizzle-otel-cc1d59b", "sha512-H8EQ2UrVrF6Qt3NIer0++QuIsvIXN8FCMnAMMOMtmy1OSJ1ViziHnEMV+5QA3zGg5G7JgbNZ7hHCZd6bEqjdJQ=="], - "@logtape/elysia": ["@logtape/elysia@2.0.2", "", { "peerDependencies": { "@logtape/logtape": "^2.0.2", "elysia": "^1.4.0" } }, "sha512-PI5/1d2h33mDmSitCJe0Rwyq7LFTc5hn/iBRE4ZQNcmbWY3Au4RYbLscVShwm4PLWVkjexwuG4rXaM5zHGtAFA=="], + "@logtape/elysia": ["@logtape/elysia@2.0.4", "", { "peerDependencies": { "@logtape/logtape": "^2.0.4", "elysia": "^1.4.0" } }, "sha512-GwbgPSmhaHOq5zBW9iZnhjA5ci4ajJdY+35uJC0uMQWvC2l+YejeNes5iBnLwggqih8dLcXrW11N9SsuCqxiyA=="], - "@logtape/logtape": ["@logtape/logtape@2.0.2", "", {}, "sha512-cveUBLbCMFkvkLycP/2vNWvywl47JcJbazHIju94/QNGboZ/jyYAgZIm0ZXezAOx3eIz8OG1EOZ5CuQP3+2FQg=="], + "@logtape/logtape": ["@logtape/logtape@2.0.4", "", {}, "sha512-Z4COeAMdedcBFuFkXaPFvDPOVuHoEom1hwNnPCIkSyojyikuNguplwPoSG+kZthWrS7GiOJo1USQyjWwIFfTKA=="], - "@logtape/otel": ["@logtape/otel@2.0.2", "", { "dependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/api-logs": "^0.208.0", "@opentelemetry/exporter-logs-otlp-grpc": "^0.208.0", "@opentelemetry/exporter-logs-otlp-http": "^0.208.0", "@opentelemetry/exporter-logs-otlp-proto": "^0.208.0", "@opentelemetry/otlp-exporter-base": "^0.208.0", "@opentelemetry/resources": "^2.2.0", "@opentelemetry/sdk-logs": "^0.208.0", "@opentelemetry/semantic-conventions": "^1.38.0" }, "peerDependencies": { "@logtape/logtape": "^2.0.2" } }, "sha512-9XBu9S2feM6qRN2G/oDFkSXpA8bEaOBJtlzkTaJ5AMipKUumGDAXXtDo/m7rc5bXMkyqUju94uaZS5p11hm+MQ=="], + "@logtape/otel": ["@logtape/otel@2.0.4", "", { "dependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/api-logs": "^0.208.0", "@opentelemetry/exporter-logs-otlp-grpc": "^0.208.0", "@opentelemetry/exporter-logs-otlp-http": "^0.208.0", "@opentelemetry/exporter-logs-otlp-proto": "^0.208.0", "@opentelemetry/otlp-exporter-base": "^0.208.0", "@opentelemetry/resources": "^2.2.0", "@opentelemetry/sdk-logs": "^0.208.0", "@opentelemetry/semantic-conventions": "^1.38.0" }, "peerDependencies": { "@logtape/logtape": "^2.0.4" } }, "sha512-ywtbgRjMqTruves8TONHF7lvlWWNQudSSlI+EUH4JJGe9NDfvvoICLICS7Lsh75zRRoo2klLe0UkAe2SxAO8Zg=="], - "@logtape/redaction": ["@logtape/redaction@2.0.2", "", { "peerDependencies": { "@logtape/logtape": "^2.0.2" } }, "sha512-NSURYmPLk2E3H1VgxSqG1P65qScVWXntChS3pvXthku6v2E2bHWp+BvDYksoA15SNT7KtTXYgfsV2O3LCMJQrw=="], + "@logtape/redaction": ["@logtape/redaction@2.0.4", "", { "peerDependencies": { "@logtape/logtape": "^2.0.4" } }, "sha512-kDMm8GapB50GIa5v64nKAGiwRAeR2sll6Q7k/nrhNHiesR1mxsDFWivFfkcYegnHVDQW9dPWbIlh0iyvi0Lrfg=="], "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], - "@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.212.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg=="], + "@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.213.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-zRM5/Qj6G84Ej3F1yt33xBVY/3tnMxtL1fiDIxYbDWYaZ/eudVw3/PBiZ8G7JwUxXxjW8gU4g6LnOyfGKYHYgw=="], - "@opentelemetry/context-async-hooks": ["@opentelemetry/context-async-hooks@2.5.1", "", { "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-MHbu8XxCHcBn6RwvCt2Vpn1WnLMNECfNKYB14LI5XypcgH4IE0/DiVifVR9tAkwPMyLXN8dOoPJfya3IryLQVw=="], + "@opentelemetry/context-async-hooks": ["@opentelemetry/context-async-hooks@2.6.0", "", { "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-L8UyDwqpTcbkIK5cgwDRDYDoEhQoj8wp8BwsO19w3LB1Z41yEQm2VJyNfAi9DrLP/YTqXqWpKHyZfR9/tFYo1Q=="], - "@opentelemetry/core": ["@opentelemetry/core@2.5.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA=="], + "@opentelemetry/core": ["@opentelemetry/core@2.6.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg=="], - "@opentelemetry/exporter-logs-otlp-grpc": ["@opentelemetry/exporter-logs-otlp-grpc@0.212.0", "", { "dependencies": { "@grpc/grpc-js": "^1.14.3", "@opentelemetry/core": "2.5.1", "@opentelemetry/otlp-exporter-base": "0.212.0", "@opentelemetry/otlp-grpc-exporter-base": "0.212.0", "@opentelemetry/otlp-transformer": "0.212.0", "@opentelemetry/sdk-logs": "0.212.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-/0bk6fQG+eSFZ4L6NlckGTgUous/ib5+OVdg0x4OdwYeHzV3lTEo3it1HgnPY6UKpmX7ki+hJvxjsOql8rCeZA=="], + "@opentelemetry/exporter-logs-otlp-grpc": ["@opentelemetry/exporter-logs-otlp-grpc@0.213.0", "", { "dependencies": { "@grpc/grpc-js": "^1.14.3", "@opentelemetry/core": "2.6.0", "@opentelemetry/otlp-exporter-base": "0.213.0", "@opentelemetry/otlp-grpc-exporter-base": "0.213.0", "@opentelemetry/otlp-transformer": "0.213.0", "@opentelemetry/sdk-logs": "0.213.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-QiRZzvayEOFnenSXi85Eorgy5WTqyNQ+E7gjl6P6r+W3IUIwAIH8A9/BgMWfP056LwmdrBL6+qvnwaIEmug6Yg=="], - "@opentelemetry/exporter-logs-otlp-http": ["@opentelemetry/exporter-logs-otlp-http@0.212.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.212.0", "@opentelemetry/core": "2.5.1", "@opentelemetry/otlp-exporter-base": "0.212.0", "@opentelemetry/otlp-transformer": "0.212.0", "@opentelemetry/sdk-logs": "0.212.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-JidJasLwG/7M9RTxV/64xotDKmFAUSBc9SNlxI32QYuUMK5rVKhHNWMPDzC7E0pCAL3cu+FyiKvsTwLi2KqPYw=="], + "@opentelemetry/exporter-logs-otlp-http": ["@opentelemetry/exporter-logs-otlp-http@0.213.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.213.0", "@opentelemetry/core": "2.6.0", "@opentelemetry/otlp-exporter-base": "0.213.0", "@opentelemetry/otlp-transformer": "0.213.0", "@opentelemetry/sdk-logs": "0.213.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-vqDVSpLp09ZzcFIdb7QZrEFPxUlO3GzdhBKLstq3jhYB5ow3+ZtV5V0ngSdi/0BZs+J5WPiN1+UDV4X5zD/GzA=="], - "@opentelemetry/exporter-logs-otlp-proto": ["@opentelemetry/exporter-logs-otlp-proto@0.212.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.212.0", "@opentelemetry/core": "2.5.1", "@opentelemetry/otlp-exporter-base": "0.212.0", "@opentelemetry/otlp-transformer": "0.212.0", "@opentelemetry/resources": "2.5.1", "@opentelemetry/sdk-logs": "0.212.0", "@opentelemetry/sdk-trace-base": "2.5.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-RpKB5UVfxc7c6Ta1UaCrxXDTQ0OD7BCGT66a97Q5zR1x3+9fw4dSaiqMXT/6FAWj2HyFbem6Rcu1UzPZikGTWQ=="], + "@opentelemetry/exporter-logs-otlp-proto": ["@opentelemetry/exporter-logs-otlp-proto@0.213.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.213.0", "@opentelemetry/core": "2.6.0", "@opentelemetry/otlp-exporter-base": "0.213.0", "@opentelemetry/otlp-transformer": "0.213.0", "@opentelemetry/resources": "2.6.0", "@opentelemetry/sdk-logs": "0.213.0", "@opentelemetry/sdk-trace-base": "2.6.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-gQk41nqfK3KhDk8jbSo3LR/fQBlV7f6Q5xRcfDmL1hZlbgXQPdVFV9/rIfYUrCoq1OM+2NnKnFfGjBt6QpLSsA=="], - "@opentelemetry/exporter-metrics-otlp-grpc": ["@opentelemetry/exporter-metrics-otlp-grpc@0.212.0", "", { "dependencies": { "@grpc/grpc-js": "^1.14.3", "@opentelemetry/core": "2.5.1", "@opentelemetry/exporter-metrics-otlp-http": "0.212.0", "@opentelemetry/otlp-exporter-base": "0.212.0", "@opentelemetry/otlp-grpc-exporter-base": "0.212.0", "@opentelemetry/otlp-transformer": "0.212.0", "@opentelemetry/resources": "2.5.1", "@opentelemetry/sdk-metrics": "2.5.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-/6Gqf9wpBq22XsomR1i0iPGnbQtCq2Vwnrq5oiDPjYSqveBdK1jtQbhGfmpK2mLLxk4cPDtD1ZEYdIou5K8EaA=="], + "@opentelemetry/exporter-metrics-otlp-grpc": ["@opentelemetry/exporter-metrics-otlp-grpc@0.213.0", "", { "dependencies": { "@grpc/grpc-js": "^1.14.3", "@opentelemetry/core": "2.6.0", "@opentelemetry/exporter-metrics-otlp-http": "0.213.0", "@opentelemetry/otlp-exporter-base": "0.213.0", "@opentelemetry/otlp-grpc-exporter-base": "0.213.0", "@opentelemetry/otlp-transformer": "0.213.0", "@opentelemetry/resources": "2.6.0", "@opentelemetry/sdk-metrics": "2.6.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-Z8gYKUAU48qwm+a1tjnGv9xbE7a5lukVIwgF6Z5i3VPXPVMe4Sjra0nN3zU7m277h+V+ZpsPGZJ2Xf0OTkL7/w=="], - "@opentelemetry/exporter-metrics-otlp-http": ["@opentelemetry/exporter-metrics-otlp-http@0.212.0", "", { "dependencies": { "@opentelemetry/core": "2.5.1", "@opentelemetry/otlp-exporter-base": "0.212.0", "@opentelemetry/otlp-transformer": "0.212.0", "@opentelemetry/resources": "2.5.1", "@opentelemetry/sdk-metrics": "2.5.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-8hgBw3aTTRpSTkU4b9MLf/2YVLnfWp+hfnLq/1Fa2cky+vx6HqTodo+Zv1GTIrAKMOOwgysOjufy0gTxngqeBg=="], + "@opentelemetry/exporter-metrics-otlp-http": ["@opentelemetry/exporter-metrics-otlp-http@0.213.0", "", { "dependencies": { "@opentelemetry/core": "2.6.0", "@opentelemetry/otlp-exporter-base": "0.213.0", "@opentelemetry/otlp-transformer": "0.213.0", "@opentelemetry/resources": "2.6.0", "@opentelemetry/sdk-metrics": "2.6.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-yw3fTIw4KQIRXC/ZyYQq5gtA3Ogfdfz/g5HVgleobQAcjUUE8Nj3spGMx8iQPp+S+u6/js7BixufRkXhzLmpJA=="], - "@opentelemetry/exporter-metrics-otlp-proto": ["@opentelemetry/exporter-metrics-otlp-proto@0.212.0", "", { "dependencies": { "@opentelemetry/core": "2.5.1", "@opentelemetry/exporter-metrics-otlp-http": "0.212.0", "@opentelemetry/otlp-exporter-base": "0.212.0", "@opentelemetry/otlp-transformer": "0.212.0", "@opentelemetry/resources": "2.5.1", "@opentelemetry/sdk-metrics": "2.5.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-C7I4WN+ghn3g7SnxXm2RK3/sRD0k/BYcXaK6lGU3yPjiM7a1M25MLuM6zY3PeVPPzzTZPfuS7+wgn/tHk768Xw=="], + "@opentelemetry/exporter-metrics-otlp-proto": ["@opentelemetry/exporter-metrics-otlp-proto@0.213.0", "", { "dependencies": { "@opentelemetry/core": "2.6.0", "@opentelemetry/exporter-metrics-otlp-http": "0.213.0", "@opentelemetry/otlp-exporter-base": "0.213.0", "@opentelemetry/otlp-transformer": "0.213.0", "@opentelemetry/resources": "2.6.0", "@opentelemetry/sdk-metrics": "2.6.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-geHF+zZaDb0/WRkJTxR8o8dG4fCWT/Wq7HBdNZCxwH5mxhwRi/5f37IDYH7nvU+dwU6IeY4Pg8TPI435JCiNkg=="], "@opentelemetry/exporter-prometheus": ["@opentelemetry/exporter-prometheus@0.200.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/resources": "2.0.0", "@opentelemetry/sdk-metrics": "2.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-ZYdlU9r0USuuYppiDyU2VFRA0kFl855ylnb3N/2aOlXrbA4PMCznen7gmPbetGQu7pz8Jbaf4fwvrDnVdQQXSw=="], - "@opentelemetry/exporter-trace-otlp-grpc": ["@opentelemetry/exporter-trace-otlp-grpc@0.212.0", "", { "dependencies": { "@grpc/grpc-js": "^1.14.3", "@opentelemetry/core": "2.5.1", "@opentelemetry/otlp-exporter-base": "0.212.0", "@opentelemetry/otlp-grpc-exporter-base": "0.212.0", "@opentelemetry/otlp-transformer": "0.212.0", "@opentelemetry/resources": "2.5.1", "@opentelemetry/sdk-trace-base": "2.5.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-9xTuYWp8ClBhljDGAoa0NSsJcsxJsC9zCFKMSZJp1Osb9pjXCMRdA6fwXtlubyqe7w8FH16EWtQNKx/FWi+Ghw=="], + "@opentelemetry/exporter-trace-otlp-grpc": ["@opentelemetry/exporter-trace-otlp-grpc@0.213.0", "", { "dependencies": { "@grpc/grpc-js": "^1.14.3", "@opentelemetry/core": "2.6.0", "@opentelemetry/otlp-exporter-base": "0.213.0", "@opentelemetry/otlp-grpc-exporter-base": "0.213.0", "@opentelemetry/otlp-transformer": "0.213.0", "@opentelemetry/resources": "2.6.0", "@opentelemetry/sdk-trace-base": "2.6.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-L8y6piP4jBIIx1Nv7/9hkx25ql6/Cro/kQrs+f9e8bPF0Ar5Dm991v7PnbtubKz6Q4fT872H56QXUWVnz/Cs4Q=="], - "@opentelemetry/exporter-trace-otlp-http": ["@opentelemetry/exporter-trace-otlp-http@0.212.0", "", { "dependencies": { "@opentelemetry/core": "2.5.1", "@opentelemetry/otlp-exporter-base": "0.212.0", "@opentelemetry/otlp-transformer": "0.212.0", "@opentelemetry/resources": "2.5.1", "@opentelemetry/sdk-trace-base": "2.5.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-v/0wMozNoiEPRolzC4YoPo4rAT0q8r7aqdnRw3Nu7IDN0CGFzNQazkfAlBJ6N5y0FYJkban7Aw5WnN73//6YlA=="], + "@opentelemetry/exporter-trace-otlp-http": ["@opentelemetry/exporter-trace-otlp-http@0.213.0", "", { "dependencies": { "@opentelemetry/core": "2.6.0", "@opentelemetry/otlp-exporter-base": "0.213.0", "@opentelemetry/otlp-transformer": "0.213.0", "@opentelemetry/resources": "2.6.0", "@opentelemetry/sdk-trace-base": "2.6.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-tnRmJD39aWrE/Sp7F6AbRNAjKHToDkAqBi6i0lESpGWz3G+f4bhVAV6mgSXH2o18lrDVJXo6jf9bAywQw43wRA=="], - "@opentelemetry/exporter-trace-otlp-proto": ["@opentelemetry/exporter-trace-otlp-proto@0.212.0", "", { "dependencies": { "@opentelemetry/core": "2.5.1", "@opentelemetry/otlp-exporter-base": "0.212.0", "@opentelemetry/otlp-transformer": "0.212.0", "@opentelemetry/resources": "2.5.1", "@opentelemetry/sdk-trace-base": "2.5.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-d1ivqPT0V+i0IVOOdzGaLqonjtlk5jYrW7ItutWzXL/Mk+PiYb59dymy/i2reot9dDnBFWfrsvxyqdutGF5Vig=="], + "@opentelemetry/exporter-trace-otlp-proto": ["@opentelemetry/exporter-trace-otlp-proto@0.213.0", "", { "dependencies": { "@opentelemetry/core": "2.6.0", "@opentelemetry/otlp-exporter-base": "0.213.0", "@opentelemetry/otlp-transformer": "0.213.0", "@opentelemetry/resources": "2.6.0", "@opentelemetry/sdk-trace-base": "2.6.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-six3vPq3sL+ge1iZOfKEg+RHuFQhGb8ZTdlvD234w/0gi8ty/qKD46qoGpKvM3amy5yYunWBKiFBW47WaVS26w=="], "@opentelemetry/exporter-zipkin": ["@opentelemetry/exporter-zipkin@2.0.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/resources": "2.0.0", "@opentelemetry/sdk-trace-base": "2.0.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0" } }, "sha512-icxaKZ+jZL/NHXX8Aru4HGsrdhK0MLcuRXkX5G5IRmCgoRLw+Br6I/nMVozX2xjGGwV7hw2g+4Slj8K7s4HbVg=="], @@ -236,27 +236,27 @@ "@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.208.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/otlp-transformer": "0.208.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-gMd39gIfVb2OgxldxUtOwGJYSH8P1kVFFlJLuut32L6KgUC4gl1dMhn+YC2mGn0bDOiQYSk/uHOdSjuKp58vvA=="], - "@opentelemetry/otlp-grpc-exporter-base": ["@opentelemetry/otlp-grpc-exporter-base@0.212.0", "", { "dependencies": { "@grpc/grpc-js": "^1.14.3", "@opentelemetry/core": "2.5.1", "@opentelemetry/otlp-exporter-base": "0.212.0", "@opentelemetry/otlp-transformer": "0.212.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-YidOSlzpsun9uw0iyIWrQp6HxpMtBlECE3tiHGAsnpEqJWbAUWcMnIffvIuvTtTQ1OyRtwwaE79dWSQ8+eiB7g=="], + "@opentelemetry/otlp-grpc-exporter-base": ["@opentelemetry/otlp-grpc-exporter-base@0.213.0", "", { "dependencies": { "@grpc/grpc-js": "^1.14.3", "@opentelemetry/core": "2.6.0", "@opentelemetry/otlp-exporter-base": "0.213.0", "@opentelemetry/otlp-transformer": "0.213.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-XgRGuLE9usFNlnw2lgMIM4HTwpcIyjdU/xPoJ8v3LbBLBfjaDkIugjc9HoWa7ZSJ/9Bhzgvm/aD0bGdYUFgnTw=="], - "@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.212.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.212.0", "@opentelemetry/core": "2.5.1", "@opentelemetry/resources": "2.5.1", "@opentelemetry/sdk-logs": "0.212.0", "@opentelemetry/sdk-metrics": "2.5.1", "@opentelemetry/sdk-trace-base": "2.5.1", "protobufjs": "8.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-bj7zYFOg6Db7NUwsRZQ/WoVXpAf41WY2gsd3kShSfdpZQDRKHWJiRZIg7A8HvWsf97wb05rMFzPbmSHyjEl9tw=="], + "@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.213.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.213.0", "@opentelemetry/core": "2.6.0", "@opentelemetry/resources": "2.6.0", "@opentelemetry/sdk-logs": "0.213.0", "@opentelemetry/sdk-metrics": "2.6.0", "@opentelemetry/sdk-trace-base": "2.6.0", "protobufjs": "^7.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-RSuAlxFFPjeK4d5Y6ps8L2WhaQI6CXWllIjvo5nkAlBpmq2XdYWEBGiAbOF4nDs8CX4QblJDv5BbMUft3sEfDw=="], "@opentelemetry/propagator-b3": ["@opentelemetry/propagator-b3@2.0.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-blx9S2EI49Ycuw6VZq+bkpaIoiJFhsDuvFGhBIoH3vJ5oYjJ2U0s3fAM5jYft99xVIAv6HqoPtlP9gpVA2IZtA=="], "@opentelemetry/propagator-jaeger": ["@opentelemetry/propagator-jaeger@2.0.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Mbm/LSFyAtQKP0AQah4AfGgsD+vsZcyreZoQ5okFBk33hU7AquU4TltgyL9dvaO8/Zkoud8/0gEvwfOZ5d7EPA=="], - "@opentelemetry/resources": ["@opentelemetry/resources@2.5.1", "", { "dependencies": { "@opentelemetry/core": "2.5.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-BViBCdE/GuXRlp9k7nS1w6wJvY5fnFX5XvuEtWsTAOQFIO89Eru7lGW3WbfbxtCuZ/GbrJfAziXG0w0dpxL7eQ=="], + "@opentelemetry/resources": ["@opentelemetry/resources@2.6.0", "", { "dependencies": { "@opentelemetry/core": "2.6.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-D4y/+OGe3JSuYUCBxtH5T9DSAWNcvCb/nQWIga8HNtXTVPQn59j0nTBAgaAXxUVBDl40mG3Tc76b46wPlZaiJQ=="], - "@opentelemetry/sdk-logs": ["@opentelemetry/sdk-logs@0.212.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.212.0", "@opentelemetry/core": "2.5.1", "@opentelemetry/resources": "2.5.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, "sha512-qglb5cqTf0mOC1sDdZ7nfrPjgmAqs2OxkzOPIf2+Rqx8yKBK0pS7wRtB1xH30rqahBIut9QJDbDePyvtyqvH/Q=="], + "@opentelemetry/sdk-logs": ["@opentelemetry/sdk-logs@0.213.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.213.0", "@opentelemetry/core": "2.6.0", "@opentelemetry/resources": "2.6.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, "sha512-00xlU3GZXo3kXKve4DLdrAL0NAFUaZ9appU/mn00S/5kSUdAvyYsORaDUfR04Mp2CLagAOhrzfUvYozY/EZX2g=="], - "@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.5.1", "", { "dependencies": { "@opentelemetry/core": "2.5.1", "@opentelemetry/resources": "2.5.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-RKMn3QKi8nE71ULUo0g/MBvq1N4icEBo7cQSKnL3URZT16/YH3nSVgWegOjwx7FRBTrjOIkMJkCUn/ZFIEfn4A=="], + "@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.6.0", "", { "dependencies": { "@opentelemetry/core": "2.6.0", "@opentelemetry/resources": "2.6.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-CicxWZxX6z35HR83jl+PLgtFgUrKRQ9LCXyxgenMnz5A1lgYWfAog7VtdOvGkJYyQgMNPhXQwkYrDLujk7z1Iw=="], "@opentelemetry/sdk-node": ["@opentelemetry/sdk-node@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@opentelemetry/core": "2.0.0", "@opentelemetry/exporter-logs-otlp-grpc": "0.200.0", "@opentelemetry/exporter-logs-otlp-http": "0.200.0", "@opentelemetry/exporter-logs-otlp-proto": "0.200.0", "@opentelemetry/exporter-metrics-otlp-grpc": "0.200.0", "@opentelemetry/exporter-metrics-otlp-http": "0.200.0", "@opentelemetry/exporter-metrics-otlp-proto": "0.200.0", "@opentelemetry/exporter-prometheus": "0.200.0", "@opentelemetry/exporter-trace-otlp-grpc": "0.200.0", "@opentelemetry/exporter-trace-otlp-http": "0.200.0", "@opentelemetry/exporter-trace-otlp-proto": "0.200.0", "@opentelemetry/exporter-zipkin": "2.0.0", "@opentelemetry/instrumentation": "0.200.0", "@opentelemetry/propagator-b3": "2.0.0", "@opentelemetry/propagator-jaeger": "2.0.0", "@opentelemetry/resources": "2.0.0", "@opentelemetry/sdk-logs": "0.200.0", "@opentelemetry/sdk-metrics": "2.0.0", "@opentelemetry/sdk-trace-base": "2.0.0", "@opentelemetry/sdk-trace-node": "2.0.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-S/YSy9GIswnhYoDor1RusNkmRughipvTCOQrlF1dzI70yQaf68qgf5WMnzUxdlCl3/et/pvaO75xfPfuEmCK5A=="], - "@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.5.1", "", { "dependencies": { "@opentelemetry/core": "2.5.1", "@opentelemetry/resources": "2.5.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-iZH3Gw8cxQn0gjpOjJMmKLd9GIaNh/E3v3ST67vyzLSxHBs14HsG4dy7jMYyC5WXGdBVEcM7U/XTF5hCQxjDMw=="], + "@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.6.0", "", { "dependencies": { "@opentelemetry/core": "2.6.0", "@opentelemetry/resources": "2.6.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-g/OZVkqlxllgFM7qMKqbPV9c1DUPhQ7d4n3pgZFcrnrNft9eJXZM2TNHTPYREJBrtNdRytYyvwjgL5geDKl3EQ=="], - "@opentelemetry/sdk-trace-node": ["@opentelemetry/sdk-trace-node@2.5.1", "", { "dependencies": { "@opentelemetry/context-async-hooks": "2.5.1", "@opentelemetry/core": "2.5.1", "@opentelemetry/sdk-trace-base": "2.5.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-9lopQ6ZoElETOEN0csgmtEV5/9C7BMfA7VtF4Jape3i954b6sTY2k3Xw3CxUTKreDck/vpAuJM+EDo4zheUw+A=="], + "@opentelemetry/sdk-trace-node": ["@opentelemetry/sdk-trace-node@2.6.0", "", { "dependencies": { "@opentelemetry/context-async-hooks": "2.6.0", "@opentelemetry/core": "2.6.0", "@opentelemetry/sdk-trace-base": "2.6.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-YhswtasmsbIGEFvLGvR9p/y3PVRTfFf+mgY8van4Ygpnv4sA3vooAjvh+qAn9PNWxs4/IwGGqiQS0PPsaRJ0vQ=="], - "@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.39.0", "", {}, "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg=="], + "@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.40.0", "", {}, "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw=="], "@protobufjs/aspromise": ["@protobufjs/aspromise@1.1.2", "", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="], @@ -290,11 +290,11 @@ "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="], - "@types/bun": ["@types/bun@1.3.9", "", { "dependencies": { "bun-types": "1.3.9" } }, "sha512-KQ571yULOdWJiMH+RIWIOZ7B2RXQGpL1YQrBtLIV3FqDcCu6FsbFUBwhdKUlCKUpS3PJDsHlJ1QKlpxoVR+xtw=="], + "@types/bun": ["@types/bun@1.3.10", "", { "dependencies": { "bun-types": "1.3.10" } }, "sha512-0+rlrUrOrTSskibryHbvQkDOWRJwJZqZlxrUs1u4oOoTln8+WIXBPmAuCF35SWB2z4Zl3E84Nl/D0P7803nigQ=="], "@types/node": ["@types/node@25.2.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-BkmoP5/FhRYek5izySdkOneRyXYN35I860MFAGupTdebyE66uZaR+bXLHq8k4DirE5DwQi3NuhvRU1jqTVwUrQ=="], - "@types/pg": ["@types/pg@8.16.0", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-RmhMd/wD+CF8Dfo+cVIy3RR5cl8CyfXQ0tGgW6XBL8L4LM/UTEbNXYRbLwU6w+CgrKBNbrQWt4FUtTfaU5jSYQ=="], + "@types/pg": ["@types/pg@8.18.0", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-gT+oueVQkqnj6ajGJXblFR4iavIXWsGAFCk3dP4Kki5+a9R4NMt0JARdk6s8cUKcfUoqP5dAtDSLU8xYUTFV+Q=="], "@types/shimmer": ["@types/shimmer@1.2.0", "", {}, "sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg=="], @@ -312,7 +312,7 @@ "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], - "bun-types": ["bun-types@1.3.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg=="], + "bun-types": ["bun-types@1.3.10", "", { "dependencies": { "@types/node": "*" } }, "sha512-tcpfCCl6XWo6nCVnpcVrxQ+9AYN1iqMIzgrSKYMB/fjLtV2eyAVEg7AxQJuCq/26R6HpKWykQXuSOq/21RYcbg=="], "char-info": ["char-info@0.3.6", "", { "dependencies": { "node-interval-tree": "^1.3.3" } }, "sha512-JskE1gqij+E0bHxs7hZt4h1xG3mvFntZGPn+jFWqImn2y/g53ugy/4J7/thcOCriiZFcLDFph4w+A0xbe2NFbg=="], @@ -330,18 +330,16 @@ "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], - "drizzle-kit": ["drizzle-kit@0.31.9", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.4", "esbuild-register": "^3.5.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-GViD3IgsXn7trFyBUUHyTFBpH/FsHTxYJ66qdbVggxef4UBPHRYxQaRzYLTuekYnk9i5FIEL9pbBIwMqX/Uwrg=="], + "drizzle-kit": ["drizzle-kit@0.31.10", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.4", "tsx": "^4.21.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-7OZcmQUrdGI+DUNNsKBn1aW8qSoKuTH7d0mYgSP8bAzdFzKoovxEFnoGQp2dVs82EOJeYycqRtciopszwUf8bw=="], "drizzle-orm": ["drizzle-orm@0.44.7", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-quIpnYznjU9lHshEOAYLoZ9s3jweleHlZIAWR/jX9gAWNg/JhQ1wj0KGRf7/Zm+obRrYd9GjPVJg790QY9N5AQ=="], - "elysia": ["elysia@1.4.25", "", { "dependencies": { "cookie": "^1.1.1", "exact-mirror": "^0.2.7", "fast-decode-uri-component": "^1.0.1", "memoirist": "^0.4.0" }, "peerDependencies": { "@sinclair/typebox": ">= 0.34.0 < 1", "@types/bun": ">= 1.2.0", "file-type": ">= 20.0.0", "openapi-types": ">= 12.0.0", "typescript": ">= 5.0.0" }, "optionalPeers": ["@types/bun", "typescript"] }, "sha512-liKjavH99Gpzrv9cDil6uYWmPuqESfPFV1FIaFSd3iNqo3y7e29sN43VxFIK8tWWnyi6eDAmi2SZk8hNAMQMyg=="], + "elysia": ["elysia@1.4.28", "", { "dependencies": { "cookie": "^1.1.1", "exact-mirror": "^0.2.7", "fast-decode-uri-component": "^1.0.1", "memoirist": "^0.4.0" }, "peerDependencies": { "@sinclair/typebox": ">= 0.34.0 < 1", "@types/bun": ">= 1.2.0", "file-type": ">= 20.0.0", "openapi-types": ">= 12.0.0", "typescript": ">= 5.0.0" }, "optionalPeers": ["@types/bun", "typescript"] }, "sha512-Vrx8sBnvq8squS/3yNBzR1jBXI+SgmnmvwawPjNuEHndUe5l1jV2Gp6JJ4ulDkEB8On6bWmmuyPpA+bq4t+WYg=="], "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], - "esbuild-register": ["esbuild-register@3.6.0", "", { "dependencies": { "debug": "^4.3.4" }, "peerDependencies": { "esbuild": ">=0.12 <1" } }, "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg=="], - "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], "exact-mirror": ["exact-mirror@0.2.7", "", { "peerDependencies": { "@sinclair/typebox": "^0.34.15" }, "optionalPeers": ["@sinclair/typebox"] }, "sha512-+MeEmDcLA4o/vjK2zujgk+1VTxPR4hdp23qLqkWfStbECtAq9gmsvQa3LW6z/0GXZyHJobrCnmy1cdeE7BjsYg=="], @@ -350,6 +348,8 @@ "file-type": ["file-type@21.3.0", "", { "dependencies": { "@tokenizer/inflate": "^0.4.1", "strtok3": "^10.3.4", "token-types": "^6.1.1", "uint8array-extras": "^1.4.0" } }, "sha512-8kPJMIGz1Yt/aPEwOsrR97ZyZaD1Iqm8PClb1nYFclUCkBi0Ma5IsYNQzvSFS9ib51lWyIw5mIT9rWzI/xjpzA=="], + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], @@ -368,7 +368,7 @@ "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], - "jose": ["jose@6.1.3", "", {}, "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ=="], + "jose": ["jose@6.2.1", "", {}, "sha512-jUaKr1yrbfaImV7R2TN/b3IcZzsw38/chqMpo2XJ7i2F8AfM/lA4G1goC3JVEwg0H7UldTmSt3P68nt31W7/mw=="], "lodash.camelcase": ["lodash.camelcase@4.3.0", "", {}, "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA=="], @@ -382,7 +382,7 @@ "nanoid": ["nanoid@5.1.6", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg=="], - "node-addon-api": ["node-addon-api@8.5.0", "", {}, "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A=="], + "node-addon-api": ["node-addon-api@8.6.0", "", {}, "sha512-gBVjCaqDlRUk0EwoPNKzIr9KkS9041G/q31IBShPs1Xz6UTA+EXdZADbzqAJQrpDRq71CIMnOP5VMut3SL0z5Q=="], "node-interval-tree": ["node-interval-tree@1.3.3", "", { "dependencies": { "shallowequal": "^1.0.2" } }, "sha512-K9vk96HdTK5fEipJwxSvIIqwTqr4e3HRJeJrNxBSeVMNSC/JWARRaX7etOLOuTmrRMeOI/K5TCJu3aWIwZiNTw=="], @@ -394,15 +394,15 @@ "pathe": ["pathe@1.1.2", "", {}, "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="], - "pg": ["pg@8.18.0", "", { "dependencies": { "pg-connection-string": "^2.11.0", "pg-pool": "^3.11.0", "pg-protocol": "^1.11.0", "pg-types": "2.2.0", "pgpass": "1.0.5" }, "optionalDependencies": { "pg-cloudflare": "^1.3.0" }, "peerDependencies": { "pg-native": ">=3.0.1" }, "optionalPeers": ["pg-native"] }, "sha512-xqrUDL1b9MbkydY/s+VZ6v+xiMUmOUk7SS9d/1kpyQxoJ6U9AO1oIJyUWVZojbfe5Cc/oluutcgFG4L9RDP1iQ=="], + "pg": ["pg@8.20.0", "", { "dependencies": { "pg-connection-string": "^2.12.0", "pg-pool": "^3.13.0", "pg-protocol": "^1.13.0", "pg-types": "2.2.0", "pgpass": "1.0.5" }, "optionalDependencies": { "pg-cloudflare": "^1.3.0" }, "peerDependencies": { "pg-native": ">=3.0.1" }, "optionalPeers": ["pg-native"] }, "sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA=="], "pg-cloudflare": ["pg-cloudflare@1.3.0", "", {}, "sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ=="], - "pg-connection-string": ["pg-connection-string@2.11.0", "", {}, "sha512-kecgoJwhOpxYU21rZjULrmrBJ698U2RxXofKVzOn5UDj61BPj/qMb7diYUR1nLScCDbrztQFl1TaQZT0t1EtzQ=="], + "pg-connection-string": ["pg-connection-string@2.12.0", "", {}, "sha512-U7qg+bpswf3Cs5xLzRqbXbQl85ng0mfSV/J0nnA31MCLgvEaAo7CIhmeyrmJpOr7o+zm0rXK+hNnT5l9RHkCkQ=="], "pg-int8": ["pg-int8@1.0.1", "", {}, "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="], - "pg-pool": ["pg-pool@3.11.0", "", { "peerDependencies": { "pg": ">=8.0" } }, "sha512-MJYfvHwtGp870aeusDh+hg9apvOe2zmpZJpyt+BMtzUWlVqbhFmMK6bOBXLBUPd7iRtIF9fZplDc7KrPN3PN7w=="], + "pg-pool": ["pg-pool@3.13.0", "", { "peerDependencies": { "pg": ">=8.0" } }, "sha512-gB+R+Xud1gLFuRD/QgOIgGOBE2KCQPaPwkzBBGC9oG69pHTkhQeIuejVIk3/cnDyX39av2AxomQiyPT13WKHQA=="], "pg-protocol": ["pg-protocol@1.11.0", "", {}, "sha512-pfsxk2M9M3BuGgDOfuy37VNRRX3jmKgMjcvAcWqNDpZSf4cUmv8HSOl5ViRQFsfARFn0KuUQTgLxVMbNq5NW3g=="], @@ -418,7 +418,7 @@ "postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="], - "protobufjs": ["protobufjs@8.0.0", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-jx6+sE9h/UryaCZhsJWbJtTEy47yXoGNYI4z8ZaRncM0zBKeRqjO2JEcOUYwrYGb1WLhXM1FfMzW3annvFv0rw=="], + "protobufjs": ["protobufjs@7.5.4", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg=="], "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], @@ -454,6 +454,8 @@ "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "tsx": ["tsx@4.21.0", "", { "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw=="], + "type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], @@ -478,8 +480,6 @@ "@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="], - "@grpc/proto-loader/protobufjs": ["protobufjs@7.5.4", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg=="], - "@logtape/otel/@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.208.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-CjruKY9V6NMssL/T1kAFgzosF1v9o6oeN+aX5JB/C/xPNtmgIJqcXHG7fA82Ou1zCpWGl4lROQUKwUNE1pMCyg=="], "@logtape/otel/@opentelemetry/exporter-logs-otlp-grpc": ["@opentelemetry/exporter-logs-otlp-grpc@0.208.0", "", { "dependencies": { "@grpc/grpc-js": "^1.7.1", "@opentelemetry/core": "2.2.0", "@opentelemetry/otlp-exporter-base": "0.208.0", "@opentelemetry/otlp-grpc-exporter-base": "0.208.0", "@opentelemetry/otlp-transformer": "0.208.0", "@opentelemetry/sdk-logs": "0.208.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-AmZDKFzbq/idME/yq68M155CJW1y056MNBekH9OZewiZKaqgwYN4VYfn3mXVPftYsfrCM2r4V6tS8H2LmfiDCg=="], @@ -488,21 +488,19 @@ "@logtape/otel/@opentelemetry/exporter-logs-otlp-proto": ["@opentelemetry/exporter-logs-otlp-proto@0.208.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.208.0", "@opentelemetry/core": "2.2.0", "@opentelemetry/otlp-exporter-base": "0.208.0", "@opentelemetry/otlp-transformer": "0.208.0", "@opentelemetry/resources": "2.2.0", "@opentelemetry/sdk-logs": "0.208.0", "@opentelemetry/sdk-trace-base": "2.2.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-Wy8dZm16AOfM7yddEzSFzutHZDZ6HspKUODSUJVjyhnZFMBojWDjSNgduyCMlw6qaxJYz0dlb0OEcb4Eme+BfQ=="], - "@logtape/otel/@opentelemetry/resources": ["@opentelemetry/resources@2.5.0", "", { "dependencies": { "@opentelemetry/core": "2.5.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-F8W52ApePshpoSrfsSk1H2yJn9aKjCrbpQF1M9Qii0GHzbfVeFUB+rc3X4aggyZD8x9Gu3Slua+s6krmq6Dt8g=="], - "@logtape/otel/@opentelemetry/sdk-logs": ["@opentelemetry/sdk-logs@0.208.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.208.0", "@opentelemetry/core": "2.2.0", "@opentelemetry/resources": "2.2.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, "sha512-QlAyL1jRpOeaqx7/leG1vJMp84g0xKP6gJmfELBpnI4O/9xPX+Hu5m1POk9Kl+veNkyth5t19hRlN6tNY1sjbA=="], - "@opentelemetry/exporter-logs-otlp-grpc/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.212.0", "", { "dependencies": { "@opentelemetry/core": "2.5.1", "@opentelemetry/otlp-transformer": "0.212.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-HoMv5pQlzbuxiMS0hN7oiUtg8RsJR5T7EhZccumIWxYfNo/f4wFc7LPDfFK6oHdG2JF/+qTocfqIHoom+7kLpw=="], + "@opentelemetry/exporter-logs-otlp-grpc/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.213.0", "", { "dependencies": { "@opentelemetry/core": "2.6.0", "@opentelemetry/otlp-transformer": "0.213.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-MegxAP1/n09Ob2dQvY5NBDVjAFkZRuKtWKxYev1R2M8hrsgXzQGkaMgoEKeUOyQ0FUyYcO29UOnYdQWmWa0PXg=="], - "@opentelemetry/exporter-logs-otlp-http/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.212.0", "", { "dependencies": { "@opentelemetry/core": "2.5.1", "@opentelemetry/otlp-transformer": "0.212.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-HoMv5pQlzbuxiMS0hN7oiUtg8RsJR5T7EhZccumIWxYfNo/f4wFc7LPDfFK6oHdG2JF/+qTocfqIHoom+7kLpw=="], + "@opentelemetry/exporter-logs-otlp-http/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.213.0", "", { "dependencies": { "@opentelemetry/core": "2.6.0", "@opentelemetry/otlp-transformer": "0.213.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-MegxAP1/n09Ob2dQvY5NBDVjAFkZRuKtWKxYev1R2M8hrsgXzQGkaMgoEKeUOyQ0FUyYcO29UOnYdQWmWa0PXg=="], - "@opentelemetry/exporter-logs-otlp-proto/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.212.0", "", { "dependencies": { "@opentelemetry/core": "2.5.1", "@opentelemetry/otlp-transformer": "0.212.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-HoMv5pQlzbuxiMS0hN7oiUtg8RsJR5T7EhZccumIWxYfNo/f4wFc7LPDfFK6oHdG2JF/+qTocfqIHoom+7kLpw=="], + "@opentelemetry/exporter-logs-otlp-proto/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.213.0", "", { "dependencies": { "@opentelemetry/core": "2.6.0", "@opentelemetry/otlp-transformer": "0.213.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-MegxAP1/n09Ob2dQvY5NBDVjAFkZRuKtWKxYev1R2M8hrsgXzQGkaMgoEKeUOyQ0FUyYcO29UOnYdQWmWa0PXg=="], - "@opentelemetry/exporter-metrics-otlp-grpc/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.212.0", "", { "dependencies": { "@opentelemetry/core": "2.5.1", "@opentelemetry/otlp-transformer": "0.212.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-HoMv5pQlzbuxiMS0hN7oiUtg8RsJR5T7EhZccumIWxYfNo/f4wFc7LPDfFK6oHdG2JF/+qTocfqIHoom+7kLpw=="], + "@opentelemetry/exporter-metrics-otlp-grpc/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.213.0", "", { "dependencies": { "@opentelemetry/core": "2.6.0", "@opentelemetry/otlp-transformer": "0.213.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-MegxAP1/n09Ob2dQvY5NBDVjAFkZRuKtWKxYev1R2M8hrsgXzQGkaMgoEKeUOyQ0FUyYcO29UOnYdQWmWa0PXg=="], - "@opentelemetry/exporter-metrics-otlp-http/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.212.0", "", { "dependencies": { "@opentelemetry/core": "2.5.1", "@opentelemetry/otlp-transformer": "0.212.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-HoMv5pQlzbuxiMS0hN7oiUtg8RsJR5T7EhZccumIWxYfNo/f4wFc7LPDfFK6oHdG2JF/+qTocfqIHoom+7kLpw=="], + "@opentelemetry/exporter-metrics-otlp-http/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.213.0", "", { "dependencies": { "@opentelemetry/core": "2.6.0", "@opentelemetry/otlp-transformer": "0.213.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-MegxAP1/n09Ob2dQvY5NBDVjAFkZRuKtWKxYev1R2M8hrsgXzQGkaMgoEKeUOyQ0FUyYcO29UOnYdQWmWa0PXg=="], - "@opentelemetry/exporter-metrics-otlp-proto/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.212.0", "", { "dependencies": { "@opentelemetry/core": "2.5.1", "@opentelemetry/otlp-transformer": "0.212.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-HoMv5pQlzbuxiMS0hN7oiUtg8RsJR5T7EhZccumIWxYfNo/f4wFc7LPDfFK6oHdG2JF/+qTocfqIHoom+7kLpw=="], + "@opentelemetry/exporter-metrics-otlp-proto/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.213.0", "", { "dependencies": { "@opentelemetry/core": "2.6.0", "@opentelemetry/otlp-transformer": "0.213.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-MegxAP1/n09Ob2dQvY5NBDVjAFkZRuKtWKxYev1R2M8hrsgXzQGkaMgoEKeUOyQ0FUyYcO29UOnYdQWmWa0PXg=="], "@opentelemetry/exporter-prometheus/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="], @@ -510,11 +508,11 @@ "@opentelemetry/exporter-prometheus/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.0.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/resources": "2.0.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-Bvy8QDjO05umd0+j+gDeWcTaVa1/R2lDj/eOvjzpm8VQj1K1vVZJuyjThpV5/lSHyYW2JaHF2IQ7Z8twJFAhjA=="], - "@opentelemetry/exporter-trace-otlp-grpc/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.212.0", "", { "dependencies": { "@opentelemetry/core": "2.5.1", "@opentelemetry/otlp-transformer": "0.212.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-HoMv5pQlzbuxiMS0hN7oiUtg8RsJR5T7EhZccumIWxYfNo/f4wFc7LPDfFK6oHdG2JF/+qTocfqIHoom+7kLpw=="], + "@opentelemetry/exporter-trace-otlp-grpc/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.213.0", "", { "dependencies": { "@opentelemetry/core": "2.6.0", "@opentelemetry/otlp-transformer": "0.213.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-MegxAP1/n09Ob2dQvY5NBDVjAFkZRuKtWKxYev1R2M8hrsgXzQGkaMgoEKeUOyQ0FUyYcO29UOnYdQWmWa0PXg=="], - "@opentelemetry/exporter-trace-otlp-http/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.212.0", "", { "dependencies": { "@opentelemetry/core": "2.5.1", "@opentelemetry/otlp-transformer": "0.212.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-HoMv5pQlzbuxiMS0hN7oiUtg8RsJR5T7EhZccumIWxYfNo/f4wFc7LPDfFK6oHdG2JF/+qTocfqIHoom+7kLpw=="], + "@opentelemetry/exporter-trace-otlp-http/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.213.0", "", { "dependencies": { "@opentelemetry/core": "2.6.0", "@opentelemetry/otlp-transformer": "0.213.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-MegxAP1/n09Ob2dQvY5NBDVjAFkZRuKtWKxYev1R2M8hrsgXzQGkaMgoEKeUOyQ0FUyYcO29UOnYdQWmWa0PXg=="], - "@opentelemetry/exporter-trace-otlp-proto/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.212.0", "", { "dependencies": { "@opentelemetry/core": "2.5.1", "@opentelemetry/otlp-transformer": "0.212.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-HoMv5pQlzbuxiMS0hN7oiUtg8RsJR5T7EhZccumIWxYfNo/f4wFc7LPDfFK6oHdG2JF/+qTocfqIHoom+7kLpw=="], + "@opentelemetry/exporter-trace-otlp-proto/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.213.0", "", { "dependencies": { "@opentelemetry/core": "2.6.0", "@opentelemetry/otlp-transformer": "0.213.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-MegxAP1/n09Ob2dQvY5NBDVjAFkZRuKtWKxYev1R2M8hrsgXzQGkaMgoEKeUOyQ0FUyYcO29UOnYdQWmWa0PXg=="], "@opentelemetry/exporter-zipkin/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="], @@ -522,13 +520,15 @@ "@opentelemetry/exporter-zipkin/@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.0.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/resources": "2.0.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-qQnYdX+ZCkonM7tA5iU4fSRsVxbFGml8jbxOgipRGMFHKaXKHQ30js03rTobYjKjIfnOsZSbHKWF0/0v0OQGfw=="], + "@opentelemetry/exporter-zipkin/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.39.0", "", {}, "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg=="], + "@opentelemetry/instrumentation/@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.200.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-IKJBQxh91qJ+3ssRly5hYEJ8NDHu9oY/B1PXVSCWf7zytmYO9RNLB0Ox9XQ/fJ8m6gY6Q6NtBWlmXfaXt5Uc4Q=="], "@opentelemetry/otlp-exporter-base/@opentelemetry/core": ["@opentelemetry/core@2.2.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw=="], "@opentelemetry/otlp-exporter-base/@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.208.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.208.0", "@opentelemetry/core": "2.2.0", "@opentelemetry/resources": "2.2.0", "@opentelemetry/sdk-logs": "0.208.0", "@opentelemetry/sdk-metrics": "2.2.0", "@opentelemetry/sdk-trace-base": "2.2.0", "protobufjs": "^7.3.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-DCFPY8C6lAQHUNkzcNT9R+qYExvsk6C5Bto2pbNxgicpcSWbe2WHShLxkOxIdNcBiYPdVHv/e7vH7K6TI+C+fQ=="], - "@opentelemetry/otlp-grpc-exporter-base/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.212.0", "", { "dependencies": { "@opentelemetry/core": "2.5.1", "@opentelemetry/otlp-transformer": "0.212.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-HoMv5pQlzbuxiMS0hN7oiUtg8RsJR5T7EhZccumIWxYfNo/f4wFc7LPDfFK6oHdG2JF/+qTocfqIHoom+7kLpw=="], + "@opentelemetry/otlp-grpc-exporter-base/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.213.0", "", { "dependencies": { "@opentelemetry/core": "2.6.0", "@opentelemetry/otlp-transformer": "0.213.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-MegxAP1/n09Ob2dQvY5NBDVjAFkZRuKtWKxYev1R2M8hrsgXzQGkaMgoEKeUOyQ0FUyYcO29UOnYdQWmWa0PXg=="], "@opentelemetry/propagator-b3/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="], @@ -566,8 +566,14 @@ "@opentelemetry/sdk-node/@opentelemetry/sdk-trace-node": ["@opentelemetry/sdk-trace-node@2.0.0", "", { "dependencies": { "@opentelemetry/context-async-hooks": "2.0.0", "@opentelemetry/core": "2.0.0", "@opentelemetry/sdk-trace-base": "2.0.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-omdilCZozUjQwY3uZRBwbaRMJ3p09l4t187Lsdf0dGMye9WKD4NGcpgZRvqhI1dwcH6og+YXQEtoO9Wx3ykilg=="], + "@opentelemetry/sdk-node/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.39.0", "", {}, "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg=="], + "@scalar/themes/@scalar/types": ["@scalar/types@0.1.7", "", { "dependencies": { "@scalar/openapi-types": "0.2.0", "@unhead/schema": "^1.11.11", "nanoid": "^5.1.5", "type-fest": "^4.20.0", "zod": "^3.23.8" } }, "sha512-irIDYzTQG2KLvFbuTI8k2Pz/R4JR+zUUSykVTbEMatkzMmVFnn1VzNSMlODbadycwZunbnL2tA27AXed9URVjw=="], + "pg/pg-protocol": ["pg-protocol@1.13.0", "", {}, "sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w=="], + + "tsx/esbuild": ["esbuild@0.27.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.4", "@esbuild/android-arm": "0.27.4", "@esbuild/android-arm64": "0.27.4", "@esbuild/android-x64": "0.27.4", "@esbuild/darwin-arm64": "0.27.4", "@esbuild/darwin-x64": "0.27.4", "@esbuild/freebsd-arm64": "0.27.4", "@esbuild/freebsd-x64": "0.27.4", "@esbuild/linux-arm": "0.27.4", "@esbuild/linux-arm64": "0.27.4", "@esbuild/linux-ia32": "0.27.4", "@esbuild/linux-loong64": "0.27.4", "@esbuild/linux-mips64el": "0.27.4", "@esbuild/linux-ppc64": "0.27.4", "@esbuild/linux-riscv64": "0.27.4", "@esbuild/linux-s390x": "0.27.4", "@esbuild/linux-x64": "0.27.4", "@esbuild/netbsd-arm64": "0.27.4", "@esbuild/netbsd-x64": "0.27.4", "@esbuild/openbsd-arm64": "0.27.4", "@esbuild/openbsd-x64": "0.27.4", "@esbuild/openharmony-arm64": "0.27.4", "@esbuild/sunos-x64": "0.27.4", "@esbuild/win32-arm64": "0.27.4", "@esbuild/win32-ia32": "0.27.4", "@esbuild/win32-x64": "0.27.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ=="], + "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="], "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="], @@ -630,12 +636,16 @@ "@logtape/otel/@opentelemetry/exporter-logs-otlp-proto/@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.2.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/resources": "2.2.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-xWQgL0Bmctsalg6PaXExmzdedSp3gyKV8mQBwK/j9VGdCDu2fmXIb2gAehBKbkXCpJ4HPkgv3QfoJWRT4dHWbw=="], - "@logtape/otel/@opentelemetry/resources/@opentelemetry/core": ["@opentelemetry/core@2.5.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ=="], - "@logtape/otel/@opentelemetry/sdk-logs/@opentelemetry/core": ["@opentelemetry/core@2.2.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw=="], "@logtape/otel/@opentelemetry/sdk-logs/@opentelemetry/resources": ["@opentelemetry/resources@2.2.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A=="], + "@opentelemetry/exporter-prometheus/@opentelemetry/core/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.39.0", "", {}, "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg=="], + + "@opentelemetry/exporter-prometheus/@opentelemetry/resources/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.39.0", "", {}, "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg=="], + + "@opentelemetry/otlp-exporter-base/@opentelemetry/core/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.39.0", "", {}, "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg=="], + "@opentelemetry/otlp-exporter-base/@opentelemetry/otlp-transformer/@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.208.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-CjruKY9V6NMssL/T1kAFgzosF1v9o6oeN+aX5JB/C/xPNtmgIJqcXHG7fA82Ou1zCpWGl4lROQUKwUNE1pMCyg=="], "@opentelemetry/otlp-exporter-base/@opentelemetry/otlp-transformer/@opentelemetry/resources": ["@opentelemetry/resources@2.2.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A=="], @@ -646,7 +656,9 @@ "@opentelemetry/otlp-exporter-base/@opentelemetry/otlp-transformer/@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.2.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/resources": "2.2.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-xWQgL0Bmctsalg6PaXExmzdedSp3gyKV8mQBwK/j9VGdCDu2fmXIb2gAehBKbkXCpJ4HPkgv3QfoJWRT4dHWbw=="], - "@opentelemetry/otlp-exporter-base/@opentelemetry/otlp-transformer/protobufjs": ["protobufjs@7.5.4", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg=="], + "@opentelemetry/propagator-b3/@opentelemetry/core/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.39.0", "", {}, "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg=="], + + "@opentelemetry/propagator-jaeger/@opentelemetry/core/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.39.0", "", {}, "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg=="], "@opentelemetry/sdk-node/@opentelemetry/exporter-logs-otlp-grpc/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.200.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/otlp-transformer": "0.200.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-IxJgA3FD7q4V6gGq4bnmQM5nTIyMDkoGFGrBrrDjB6onEiq1pafma55V+bHvGYLWvcqbBbRfezr1GED88lacEQ=="], @@ -694,13 +706,67 @@ "@scalar/themes/@scalar/types/@scalar/openapi-types": ["@scalar/openapi-types@0.2.0", "", { "dependencies": { "zod": "^3.23.8" } }, "sha512-waiKk12cRCqyUCWTOX0K1WEVX46+hVUK+zRPzAahDJ7G0TApvbNkuy5wx7aoUyEk++HHde0XuQnshXnt8jsddA=="], + "tsx/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q=="], + + "tsx/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.27.4", "", { "os": "android", "cpu": "arm" }, "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ=="], + + "tsx/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.4", "", { "os": "android", "cpu": "arm64" }, "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw=="], + + "tsx/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.27.4", "", { "os": "android", "cpu": "x64" }, "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw=="], + + "tsx/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ=="], + + "tsx/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw=="], + + "tsx/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.4", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw=="], + + "tsx/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ=="], + + "tsx/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.4", "", { "os": "linux", "cpu": "arm" }, "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg=="], + + "tsx/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA=="], + + "tsx/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.4", "", { "os": "linux", "cpu": "ia32" }, "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA=="], + + "tsx/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA=="], + + "tsx/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw=="], + + "tsx/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA=="], + + "tsx/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw=="], + + "tsx/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA=="], + + "tsx/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.4", "", { "os": "linux", "cpu": "x64" }, "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA=="], + + "tsx/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.4", "", { "os": "none", "cpu": "arm64" }, "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q=="], + + "tsx/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.4", "", { "os": "none", "cpu": "x64" }, "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg=="], + + "tsx/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.4", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow=="], + + "tsx/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.4", "", { "os": "openbsd", "cpu": "x64" }, "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ=="], + + "tsx/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.4", "", { "os": "none", "cpu": "arm64" }, "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg=="], + + "tsx/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.4", "", { "os": "sunos", "cpu": "x64" }, "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g=="], + + "tsx/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg=="], + + "tsx/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw=="], + + "tsx/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.4", "", { "os": "win32", "cpu": "x64" }, "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg=="], + + "@logtape/otel/@opentelemetry/exporter-logs-otlp-grpc/@opentelemetry/core/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.39.0", "", {}, "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg=="], + "@logtape/otel/@opentelemetry/exporter-logs-otlp-grpc/@opentelemetry/otlp-transformer/@opentelemetry/resources": ["@opentelemetry/resources@2.2.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A=="], "@logtape/otel/@opentelemetry/exporter-logs-otlp-grpc/@opentelemetry/otlp-transformer/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.2.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/resources": "2.2.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-G5KYP6+VJMZzpGipQw7Giif48h6SGQ2PFKEYCybeXJsOCB4fp8azqMAAzE5lnnHK3ZVwYQrgmFbsUJO/zOnwGw=="], "@logtape/otel/@opentelemetry/exporter-logs-otlp-grpc/@opentelemetry/otlp-transformer/@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.2.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/resources": "2.2.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-xWQgL0Bmctsalg6PaXExmzdedSp3gyKV8mQBwK/j9VGdCDu2fmXIb2gAehBKbkXCpJ4HPkgv3QfoJWRT4dHWbw=="], - "@logtape/otel/@opentelemetry/exporter-logs-otlp-grpc/@opentelemetry/otlp-transformer/protobufjs": ["protobufjs@7.5.4", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg=="], + "@logtape/otel/@opentelemetry/exporter-logs-otlp-http/@opentelemetry/core/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.39.0", "", {}, "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg=="], "@logtape/otel/@opentelemetry/exporter-logs-otlp-http/@opentelemetry/otlp-transformer/@opentelemetry/resources": ["@opentelemetry/resources@2.2.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A=="], @@ -708,28 +774,28 @@ "@logtape/otel/@opentelemetry/exporter-logs-otlp-http/@opentelemetry/otlp-transformer/@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.2.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/resources": "2.2.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-xWQgL0Bmctsalg6PaXExmzdedSp3gyKV8mQBwK/j9VGdCDu2fmXIb2gAehBKbkXCpJ4HPkgv3QfoJWRT4dHWbw=="], - "@logtape/otel/@opentelemetry/exporter-logs-otlp-http/@opentelemetry/otlp-transformer/protobufjs": ["protobufjs@7.5.4", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg=="], + "@logtape/otel/@opentelemetry/exporter-logs-otlp-proto/@opentelemetry/core/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.39.0", "", {}, "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg=="], "@logtape/otel/@opentelemetry/exporter-logs-otlp-proto/@opentelemetry/otlp-transformer/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.2.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/resources": "2.2.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-G5KYP6+VJMZzpGipQw7Giif48h6SGQ2PFKEYCybeXJsOCB4fp8azqMAAzE5lnnHK3ZVwYQrgmFbsUJO/zOnwGw=="], - "@logtape/otel/@opentelemetry/exporter-logs-otlp-proto/@opentelemetry/otlp-transformer/protobufjs": ["protobufjs@7.5.4", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg=="], + "@logtape/otel/@opentelemetry/exporter-logs-otlp-proto/@opentelemetry/resources/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.39.0", "", {}, "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg=="], - "@opentelemetry/sdk-node/@opentelemetry/exporter-logs-otlp-grpc/@opentelemetry/otlp-transformer/protobufjs": ["protobufjs@7.5.4", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg=="], + "@logtape/otel/@opentelemetry/exporter-logs-otlp-proto/@opentelemetry/sdk-trace-base/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.39.0", "", {}, "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg=="], - "@opentelemetry/sdk-node/@opentelemetry/exporter-logs-otlp-http/@opentelemetry/otlp-transformer/protobufjs": ["protobufjs@7.5.4", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg=="], + "@logtape/otel/@opentelemetry/sdk-logs/@opentelemetry/core/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.39.0", "", {}, "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg=="], - "@opentelemetry/sdk-node/@opentelemetry/exporter-logs-otlp-proto/@opentelemetry/otlp-transformer/protobufjs": ["protobufjs@7.5.4", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg=="], + "@logtape/otel/@opentelemetry/sdk-logs/@opentelemetry/resources/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.39.0", "", {}, "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg=="], - "@opentelemetry/sdk-node/@opentelemetry/exporter-metrics-otlp-grpc/@opentelemetry/otlp-transformer/protobufjs": ["protobufjs@7.5.4", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg=="], + "@opentelemetry/otlp-exporter-base/@opentelemetry/otlp-transformer/@opentelemetry/resources/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.39.0", "", {}, "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg=="], - "@opentelemetry/sdk-node/@opentelemetry/exporter-metrics-otlp-http/@opentelemetry/otlp-transformer/protobufjs": ["protobufjs@7.5.4", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg=="], + "@opentelemetry/otlp-exporter-base/@opentelemetry/otlp-transformer/@opentelemetry/sdk-trace-base/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.39.0", "", {}, "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg=="], - "@opentelemetry/sdk-node/@opentelemetry/exporter-metrics-otlp-proto/@opentelemetry/otlp-transformer/protobufjs": ["protobufjs@7.5.4", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg=="], + "@logtape/otel/@opentelemetry/exporter-logs-otlp-grpc/@opentelemetry/otlp-transformer/@opentelemetry/resources/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.39.0", "", {}, "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg=="], - "@opentelemetry/sdk-node/@opentelemetry/exporter-trace-otlp-grpc/@opentelemetry/otlp-transformer/protobufjs": ["protobufjs@7.5.4", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg=="], + "@logtape/otel/@opentelemetry/exporter-logs-otlp-grpc/@opentelemetry/otlp-transformer/@opentelemetry/sdk-trace-base/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.39.0", "", {}, "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg=="], - "@opentelemetry/sdk-node/@opentelemetry/exporter-trace-otlp-http/@opentelemetry/otlp-transformer/protobufjs": ["protobufjs@7.5.4", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg=="], + "@logtape/otel/@opentelemetry/exporter-logs-otlp-http/@opentelemetry/otlp-transformer/@opentelemetry/resources/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.39.0", "", {}, "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg=="], - "@opentelemetry/sdk-node/@opentelemetry/exporter-trace-otlp-proto/@opentelemetry/otlp-transformer/protobufjs": ["protobufjs@7.5.4", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg=="], + "@logtape/otel/@opentelemetry/exporter-logs-otlp-http/@opentelemetry/otlp-transformer/@opentelemetry/sdk-trace-base/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.39.0", "", {}, "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg=="], } } diff --git a/api/package.json b/api/package.json index c0844414..6e32ac95 100644 --- a/api/package.json +++ b/api/package.json @@ -13,42 +13,42 @@ "@elysiajs/opentelemetry": "^1.4.10", "@elysiajs/swagger": "zoriya/elysia-swagger#build", "@kubiks/otel-drizzle": "zoriya/drizzle-otel#build", - "@logtape/elysia": "2.0.2", - "@logtape/logtape": "2.0.2", - "@logtape/otel": "2.0.2", - "@logtape/redaction": "2.0.2", + "@logtape/elysia": "2.0.4", + "@logtape/logtape": "2.0.4", + "@logtape/otel": "2.0.4", + "@logtape/redaction": "2.0.4", "@opentelemetry/api": "^1.9.0", - "@opentelemetry/api-logs": "^0.212.0", - "@opentelemetry/core": "^2.5.1", - "@opentelemetry/exporter-logs-otlp-grpc": "^0.212.0", - "@opentelemetry/exporter-logs-otlp-http": "^0.212.0", - "@opentelemetry/exporter-logs-otlp-proto": "^0.212.0", - "@opentelemetry/exporter-metrics-otlp-grpc": "^0.212.0", - "@opentelemetry/exporter-metrics-otlp-http": "^0.212.0", - "@opentelemetry/exporter-metrics-otlp-proto": "^0.212.0", - "@opentelemetry/exporter-trace-otlp-grpc": "^0.212.0", - "@opentelemetry/exporter-trace-otlp-http": "^0.212.0", - "@opentelemetry/exporter-trace-otlp-proto": "^0.212.0", - "@opentelemetry/resources": "^2.5.1", - "@opentelemetry/sdk-logs": "^0.212.0", - "@opentelemetry/sdk-metrics": "^2.5.1", - "@opentelemetry/sdk-trace-base": "^2.5.1", - "@opentelemetry/sdk-trace-node": "^2.5.1", - "@opentelemetry/semantic-conventions": "^1.39.0", - "@types/bun": "^1.3.9", + "@opentelemetry/api-logs": "^0.213.0", + "@opentelemetry/core": "^2.6.0", + "@opentelemetry/exporter-logs-otlp-grpc": "^0.213.0", + "@opentelemetry/exporter-logs-otlp-http": "^0.213.0", + "@opentelemetry/exporter-logs-otlp-proto": "^0.213.0", + "@opentelemetry/exporter-metrics-otlp-grpc": "^0.213.0", + "@opentelemetry/exporter-metrics-otlp-http": "^0.213.0", + "@opentelemetry/exporter-metrics-otlp-proto": "^0.213.0", + "@opentelemetry/exporter-trace-otlp-grpc": "^0.213.0", + "@opentelemetry/exporter-trace-otlp-http": "^0.213.0", + "@opentelemetry/exporter-trace-otlp-proto": "^0.213.0", + "@opentelemetry/resources": "^2.6.0", + "@opentelemetry/sdk-logs": "^0.213.0", + "@opentelemetry/sdk-metrics": "^2.6.0", + "@opentelemetry/sdk-trace-base": "^2.6.0", + "@opentelemetry/sdk-trace-node": "^2.6.0", + "@opentelemetry/semantic-conventions": "^1.40.0", + "@types/bun": "^1.3.10", "blurhash": "^2.0.5", "drizzle-kit": "^0.31.5", "drizzle-orm": "0.44.7", - "elysia": "^1.4.25", - "jose": "^6.1.3", - "node-addon-api": "^8.5.0", + "elysia": "^1.4.28", + "jose": "^6.2.1", + "node-addon-api": "^8.6.0", "parjs": "^1.3.9", - "pg": "^8.17.2", + "pg": "^8.20.0", "sharp": "^0.34.5" }, "devDependencies": { - "@biomejs/biome": "2.4.4", - "@types/pg": "^8.16.0", + "@biomejs/biome": "2.4.7", + "@types/pg": "^8.18.0", "typescript": "^5.9.3" }, "module": "src/index.js", diff --git a/api/src/controllers/seed/videos.ts b/api/src/controllers/seed/videos.ts index 426f4632..4b95917b 100644 --- a/api/src/controllers/seed/videos.ts +++ b/api/src/controllers/seed/videos.ts @@ -29,7 +29,6 @@ const CreatedVideo = t.Object({ }); async function createVideos(body: SeedVideo[], clearLinks: boolean) { - console.log(body.map((x) => x.guess.history)); if (body.length === 0) { return { status: 422, message: "No videos" } as const; } diff --git a/api/src/models/video.ts b/api/src/models/video.ts index d3aaf5d9..77a75dc6 100644 --- a/api/src/models/video.ts +++ b/api/src/models/video.ts @@ -7,42 +7,52 @@ import { DbMetadata, EpisodeId, ExternalId, Resource } from "./utils"; const Opt = (schema: TSchema) => t.Optional(t.Nullable(schema)); -export const Guess = t.Recursive((Self) => - t.Object( - { - title: t.String(), - kind: Opt(t.UnionEnum(["episode", "movie", "extra"])), - extraKind: Opt(ExtraType), - years: Opt(t.Array(t.Integer(), { default: [] })), - episodes: Opt( - t.Array( - t.Object({ season: t.Nullable(t.Integer()), episode: t.Integer() }), - { default: [] }, - ), +// Workaround: t.Omit(Self, ["history"]) inside t.Recursive produces an empty +// object schema because Self is only a $ref at construction time (typebox bug). +// Instead, define the base properties separately and compose. +const GuessBase = t.Object( + { + title: t.String(), + kind: Opt(t.UnionEnum(["episode", "movie", "extra"])), + extraKind: Opt(ExtraType), + years: Opt(t.Array(t.Integer(), { default: [] })), + episodes: Opt( + t.Array( + t.Object({ season: t.Nullable(t.Integer()), episode: t.Integer() }), + { default: [] }, ), - externalId: Opt(t.Record(t.String(), t.String())), + ), + externalId: Opt(t.Record(t.String(), t.String())), - from: t.String({ - description: "Name of the tool that made the guess", - }), - history: t.Array(t.Omit(Self, ["history"]), { + from: t.String({ + description: "Name of the tool that made the guess", + }), + }, + { additionalProperties: true }, +); + +export const Guess = t.Composite( + [ + GuessBase, + t.Object({ + history: t.Array(GuessBase, { default: [], description: comment` - When another tool refines the guess or a user manually edit it, the history of the guesses - are kept in this \`history\` value. + When another tool refines the guess, the history of the guesses + is kept in this \`history\` value. `, }), - }, - { - additionalProperties: true, - description: comment` - Metadata guessed from the filename. Kyoo can use those informations to bypass - the scanner/metadata fetching and just register videos to movies/entries that already - exists. If Kyoo can't find a matching movie/entry, this information will be sent to - the scanner. - `, - }, - ), + }), + ], + { + additionalProperties: true, + description: comment` + Metadata guessed from the filename. Kyoo can use those informations to bypass + the scanner/metadata fetching and just register videos to movies/entries that already + exists. If Kyoo can't find a matching movie/entry, this information will be sent to + the scanner. + `, + }, ); export type Guess = typeof Guess.static; diff --git a/api/tests/series/get-series.test.ts b/api/tests/series/get-series.test.ts index e9c0cb08..0e5edfcd 100644 --- a/api/tests/series/get-series.test.ts +++ b/api/tests/series/get-series.test.ts @@ -42,11 +42,9 @@ describe("Get series", () => { ); expect(body.firstEntry.videos).toBeArrayOfSize(1); // check that it's an iso datetime - console.log(body.createdAt); expect(body.createdAt).toMatch( /\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+Z/, ); - console.log(body.firstEntry.createdAt); expect(body.firstEntry.createdAt).toMatch( /\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+Z/, ); diff --git a/api/tests/videos/scanner.test.ts b/api/tests/videos/scanner.test.ts index 3867ae44..4a424727 100644 --- a/api/tests/videos/scanner.test.ts +++ b/api/tests/videos/scanner.test.ts @@ -81,6 +81,27 @@ describe("Video seeding", () => { expect(vid!.evj[0].entry.slug).toBe(`${madeInAbyss.slug}-s1e13`); }); + it("With history", async () => { + const [resp, body] = await createVideo({ + guess: { + title: "mia", + episodes: [{ season: 1, episode: 13 }], + from: "test", + history: [{ title: "toto", from: "tata" }], + }, + part: null, + path: "/video/mia-dup-test.mkv", + rendering: "oeunounthsha2", + version: 1, + }); + + expectStatus(resp, body).toBe(201); + expect(body).toBeArrayOfSize(1); + expect(body[0].id).toBeString(); + expect(body[0].guess.history).toBeArrayOfSize(1); + expect(body[0].guess.history[0].title).toBe("toto"); + }); + it("With movie", async () => { const [resp, body] = await createVideo({ guess: { title: "bubble", from: "test", history: [] }, From 42c62c96eae763a18978de3660c287f504164903 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Tue, 17 Mar 2026 18:23:52 +0100 Subject: [PATCH 5/8] Properly handle specials and matching with anidb --- scanner/scanner/identifiers/anilist.py | 97 ++++++++++++++-------- scanner/scanner/identifiers/guess/guess.py | 2 - scanner/scanner/identifiers/identify.py | 2 +- scanner/scanner/providers/composite.py | 3 +- 4 files changed, 63 insertions(+), 41 deletions(-) diff --git a/scanner/scanner/identifiers/anilist.py b/scanner/scanner/identifiers/anilist.py index 2f642e4b..4ce0e7f2 100644 --- a/scanner/scanner/identifiers/anilist.py +++ b/scanner/scanner/identifiers/anilist.py @@ -60,16 +60,16 @@ class AnimeListDb(BaseXmlModel, tag="anime-list"): imdbid: str | None = attr(default=None) name: str | None = element(default=None) mappings: list[EpisodeMapping] = wrapped( - "mapping-list/mappings", element(default=[]) + "mapping-list", element(default=[], tag="mapping") ) @field_validator("tvdbid", "tmdbtv", "tmdbid", "imdbid", "defaulttvdbseason") @classmethod def _empty_to_none(cls, v: str | None) -> str | None: # pornographic titles have this id. - if v == "hentai": + if v == "hentai" or v == "": return None - return v or None + return v class EpisodeMapping(BaseXmlModel): anidbseason: int = attr() @@ -110,7 +110,7 @@ class AnimeListData: @cache(ttl=timedelta(days=30)) -async def get_data() -> AnimeListData: +async def get_anilist_data() -> AnimeListData: logger.info("Fetching anime-lists XML databases...") ret = AnimeListData(fetched_at=datetime.now()) async with ClientSession() as session: @@ -178,36 +178,54 @@ def anidb_to_tvdb( def tvdb_to_anidb( - anime: AnimeListDb.AnimeEntry, + animes: list[AnimeListDb.AnimeEntry], tvdb_season: int, tvdb_ep: int, -) -> list[int]: - for map in anime.mappings: - if map.anidbseason != 1 or map.tvdbseason != tvdb_season: - continue +) -> list[tuple[AnimeListDb.AnimeEntry, int, int]]: + for anime in animes: + for map in anime.mappings: + if map.tvdbseason != tvdb_season: + continue - # Handle mapping overrides (;anidb-tvdb; format) - overrides = [ - anidb_num - for anidb_num, tvdb_nums in map.tvdb_mappings.items() - if tvdb_ep in tvdb_nums - ] - if len(overrides): - return overrides + # Handle mapping overrides (;anidb-tvdb; format) + overrides = [ + anidb_num + for anidb_num, tvdb_nums in map.tvdb_mappings.items() + if tvdb_ep in tvdb_nums + ] + if len(overrides): + return [(anime, map.anidbseason, ep) for ep in overrides] - # Reverse the start/end range offset - if map.start is not None and map.end is not None: - candidate = tvdb_ep - map.offset - if map.start <= candidate <= map.end: - return [candidate] + if map.start is not None and map.end is not None: + candidate = tvdb_ep - map.offset + if map.start <= candidate <= map.end: + return [(anime, map.anidbseason, candidate)] - return [tvdb_ep - anime.episodeoffset] + seasons = sorted( + ( + x + for x in animes + if x.defaulttvdbseason == tvdb_season and x.episodeoffset < tvdb_ep + ), + key=lambda x: x.episodeoffset, + reverse=True, + ) + + fallback = next( + iter(seasons), + next( + (x for x in animes if x.defaulttvdbseason == "a"), + animes[0], + ), + ) + + return [(fallback, 1, tvdb_ep - fallback.episodeoffset)] async def identify_anilist(_path: str, guess: Guess) -> Guess: - data = await get_data() + data = await get_anilist_data() - aid = data.titles.get(guess.title) + aid = data.titles.get(normalize_title(guess.title)) if aid is None: return guess anime = data.animes.get(aid) @@ -260,9 +278,23 @@ async def identify_anilist(_path: str, guess: Guess) -> Guess: for tvdb_ep in tvdb_eps ] + kind = guess.kind + if ( + guess.kind == "movie" + and anime.tvdbid + and isinstance(anime.defaulttvdbseason, int) + ): + kind = "episode" + new_episodes.append( + Guess.Episode( + season=anime.defaulttvdbseason, + episode=1 + anime.episodeoffset, + ) + ) + return Guess( title=guess.title, - kind=guess.kind, + kind=kind, extra_kind=guess.extra_kind, years=guess.years, episodes=new_episodes, @@ -274,7 +306,7 @@ async def identify_anilist(_path: str, guess: Guess) -> Guess: async def anilist_enrich_ids(serie: Serie): - data = await get_data() + data = await get_anilist_data() animes = [ data.animes[aid] for tvdb_id in serie.external_id[ProviderName.TVDB] @@ -310,16 +342,9 @@ async def anilist_enrich_ids(serie: Serie): episode = entry.episode_number or entry.number if episode is None: continue - anime = next( - (x for x in animes if x.defaulttvdbseason == season), - next( - (x for x in animes if x.defaulttvdbseason == "a"), - animes[0], - ), - ) - adb_numbers = tvdb_to_anidb(anime, season, episode) entry.external_id[ProviderName.ANIDB] = [ - EpisodeId(serie_id=anime.anidbid, season=1, episode=x) for x in adb_numbers + EpisodeId(serie_id=anime.anidbid, season=season, episode=ep) + for anime, season, ep in tvdb_to_anidb(animes, season, episode) ] return serie diff --git a/scanner/scanner/identifiers/guess/guess.py b/scanner/scanner/identifiers/guess/guess.py index c1a2b32f..d69150b8 100644 --- a/scanner/scanner/identifiers/guess/guess.py +++ b/scanner/scanner/identifiers/guess/guess.py @@ -13,7 +13,6 @@ rblk = cast(Rebulk, default_api.rebulk).rules(rules) def guessit( name: str, *, - expected_titles: list[str] = [], extra_flags: dict[str, Any] = {}, ) -> dict[str, list[Match]]: return default_api.guessit( @@ -21,7 +20,6 @@ def guessit( { "episode_prefer_number": True, "excludes": "language", - "expected_title": expected_titles, "enforce_list": True, "advanced": True, } diff --git a/scanner/scanner/identifiers/identify.py b/scanner/scanner/identifiers/identify.py index 555f7473..2ac9ea96 100644 --- a/scanner/scanner/identifiers/identify.py +++ b/scanner/scanner/identifiers/identify.py @@ -20,7 +20,7 @@ pipeline: list[Callable[[str, Guess], Awaitable[Guess]]] = [ async def identify(path: str) -> Video: - raw = guessit(path, expected_titles=[]) + raw = guessit(path) # guessit should only return one (according to the doc) title = raw.get("title", [])[0] diff --git a/scanner/scanner/providers/composite.py b/scanner/scanner/providers/composite.py index c8b5443b..cb16da1d 100644 --- a/scanner/scanner/providers/composite.py +++ b/scanner/scanner/providers/composite.py @@ -61,8 +61,7 @@ class CompositeProvider(Provider): ret.entries, lambda x: (x.season_number, x.episode_number, x.number, x.slug) ) - if ProviderName.ANIDB in external_id: - ret = await anilist_enrich_ids(ret) + ret = await anilist_enrich_ids(ret) # themoviedb has better global info than tvdb but tvdb has better entries info info = await self._themoviedb.get_serie( From edb52be62168feab58f19b9a5ee1c75b07357b62 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Wed, 18 Mar 2026 10:12:55 +0100 Subject: [PATCH 6/8] Add popup menu for multiples external id --- front/src/components/entries/entry-box.tsx | 13 +++-- front/src/components/entries/entry-line.tsx | 12 ++-- front/src/components/items/context-menus.tsx | 14 +++-- front/src/components/items/index.ts | 4 ++ front/src/components/items/item-details.tsx | 3 + front/src/components/items/item-grid.tsx | 3 + front/src/components/items/item-list.tsx | 3 + front/src/primitives/chip.tsx | 2 + front/src/primitives/links.tsx | 5 +- front/src/ui/details/collection.tsx | 5 ++ front/src/ui/details/header.tsx | 61 +++++++++++++++----- front/src/ui/details/season.tsx | 2 +- front/src/ui/details/serie.tsx | 2 +- front/src/ui/home/news.tsx | 28 ++++++++- front/src/ui/home/nextup.tsx | 32 +++++++++- front/src/ui/home/recommended.tsx | 5 ++ 16 files changed, 157 insertions(+), 37 deletions(-) diff --git a/front/src/components/entries/entry-box.tsx b/front/src/components/entries/entry-box.tsx index 62953139..4666a43d 100644 --- a/front/src/components/entries/entry-box.tsx +++ b/front/src/components/entries/entry-box.tsx @@ -1,7 +1,7 @@ import { useState } from "react"; import { useTranslation } from "react-i18next"; import { View } from "react-native"; -import type { KImage } from "~/models"; +import type { Entry, KImage } from "~/models"; import { Image, Link, @@ -25,7 +25,8 @@ export const EntryBox = ({ thumbnail, href, watchedPercent, - videosCount, + videos, + onSelectVideos, className, ...props }: { @@ -38,7 +39,8 @@ export const EntryBox = ({ href: string; thumbnail: KImage | null; watchedPercent: number; - videosCount: number; + videos: Entry["videos"]; + onSelectVideos: () => void; className?: string; }) => { const [moreOpened, setMoreOpened] = useState(false); @@ -46,7 +48,8 @@ export const EntryBox = ({ return ( 1 ? undefined : href} + onPress={videos.length > 1 ? onSelectVideos : undefined} onLongPress={() => setMoreOpened(true)} className={cn("group w-[350px] items-center p-1 outline-0", className)} {...props} @@ -65,7 +68,7 @@ export const EntryBox = ({ kind={kind} slug={slug} serieSlug={serieSlug} - videosCount={videosCount} + videoSlug={videos.length === 1 ? videos[0].slug : null} isOpen={moreOpened} setOpen={(v) => setMoreOpened(v)} className={cn( diff --git a/front/src/components/entries/entry-line.tsx b/front/src/components/entries/entry-line.tsx index 12d7069e..d15ccdd9 100644 --- a/front/src/components/entries/entry-line.tsx +++ b/front/src/components/entries/entry-line.tsx @@ -4,7 +4,7 @@ import { useTranslation } from "react-i18next"; import { type PressableProps, View } from "react-native"; import { EntryContext } from "~/components/items/context-menus"; import { ItemProgress } from "~/components/items/item-grid"; -import type { KImage } from "~/models"; +import type { Entry, KImage } from "~/models"; import { CroppedText, Heading, @@ -37,7 +37,7 @@ export const EntryLine = ({ href, className, onSelectVideos, - videosCount, + videos, ...props }: { kind: "episode" | "movie" | "special"; @@ -55,7 +55,7 @@ export const EntryLine = ({ watchedPercent: number | null; href: string | null; onSelectVideos?: () => void; - videosCount: number; + videos: Entry["videos"]; } & PressableProps) => { const [moreOpened, setMoreOpened] = useState(false); const { t } = useTranslation(); @@ -102,7 +102,7 @@ export const EntryLine = ({ - {videosCount > 1 && ( + {videos.length > 1 && ( { e.preventDefault(); @@ -116,7 +116,7 @@ export const EntryLine = ({ className="fill-accent dark:fill-slate-400" /> - {t("show.videosCount", { number: videosCount })} + {t("show.videosCount", { number: videos.length })} )} @@ -136,7 +136,7 @@ export const EntryLine = ({ kind={kind} slug={slug} serieSlug={serieSlug} - videosCount={videosCount} + videoSlug={videos.length === 1 ? videos[0].slug : null} isOpen={moreOpened} setOpen={(v) => setMoreOpened(v)} className={cn( diff --git a/front/src/components/items/context-menus.tsx b/front/src/components/items/context-menus.tsx index 0a802c32..e007c4a7 100644 --- a/front/src/components/items/context-menus.tsx +++ b/front/src/components/items/context-menus.tsx @@ -16,14 +16,14 @@ export const EntryContext = ({ kind, slug, serieSlug, - videosCount, + videoSlug, className, ...props }: { kind: "movie" | "episode" | "special"; serieSlug: string | null; slug: string; - videosCount: number; + videoSlug: string | null; className?: string; } & Partial> & Partial>) => { @@ -50,11 +50,11 @@ export const EntryContext = ({ {/* icon={Download} */} {/* onSelect={() => downloader(type, slug)} */} {/* /> */} - {videosCount === 1 && ( + {videoSlug && ( )} @@ -64,12 +64,14 @@ export const EntryContext = ({ export const ItemContext = ({ kind, slug, + videoSlug, status, className, ...props }: { kind: "movie" | "serie"; slug: string; + videoSlug: string | null; status: WatchStatusV | null; className?: string; } & Partial> & @@ -123,7 +125,7 @@ export const ItemContext = ({ /> )} - {kind === "movie" && ( + {videoSlug && ( <> {/* )} diff --git a/front/src/components/items/index.ts b/front/src/components/items/index.ts index 69abea5a..55f76cb9 100644 --- a/front/src/components/items/index.ts +++ b/front/src/components/items/index.ts @@ -20,6 +20,10 @@ export const itemMap = ( item.kind === "movie" ? (item.watchStatus?.percent ?? null) : null, availableCount: item.kind === "serie" ? item.availableCount : null, seenCount: item.kind === "serie" ? item.watchStatus?.seenCount : null, + videoSlug: + item.kind === "movie" && item.videos?.length === 1 + ? item.videos[0].slug + : null, }); export { ItemGrid, ItemList }; diff --git a/front/src/components/items/item-details.tsx b/front/src/components/items/item-details.tsx index 23f9bd47..1ef29f96 100644 --- a/front/src/components/items/item-details.tsx +++ b/front/src/components/items/item-details.tsx @@ -33,6 +33,7 @@ export const ItemDetails = ({ watchStatus, availableCount, seenCount, + videoSlug, className, ...props }: { @@ -49,6 +50,7 @@ export const ItemDetails = ({ watchStatus: WatchStatusV | null; availableCount?: number | null; seenCount?: number | null; + videoSlug: string | null; } & ViewProps) => { const [moreOpened, setMoreOpened] = useState(false); const { t } = useTranslation(); @@ -90,6 +92,7 @@ export const ItemDetails = ({ status={watchStatus} isOpen={moreOpened} setOpen={(v) => setMoreOpened(v)} + videoSlug={videoSlug} /> )} {tagline &&

{tagline}

} diff --git a/front/src/components/items/item-grid.tsx b/front/src/components/items/item-grid.tsx index ff744a76..41037465 100644 --- a/front/src/components/items/item-grid.tsx +++ b/front/src/components/items/item-grid.tsx @@ -39,6 +39,7 @@ export const ItemGrid = ({ watchPercent, availableCount, seenCount, + videoSlug, horizontal = false, className, ...props @@ -53,6 +54,7 @@ export const ItemGrid = ({ kind: "movie" | "serie" | "collection"; availableCount?: number | null; seenCount?: number | null; + videoSlug: string | null; horizontal?: boolean; className?: string; }) => { @@ -89,6 +91,7 @@ export const ItemGrid = ({ setMoreOpened(v)} diff --git a/front/src/components/items/item-list.tsx b/front/src/components/items/item-list.tsx index ce50162f..8b3ae20e 100644 --- a/front/src/components/items/item-list.tsx +++ b/front/src/components/items/item-list.tsx @@ -27,6 +27,7 @@ export const ItemList = ({ watchStatus, availableCount, seenCount, + videoSlug, className, ...props }: { @@ -39,6 +40,7 @@ export const ItemList = ({ banner: KImage | null; watchStatus: WatchStatusV | null; availableCount?: number | null; + videoSlug: string | null; seenCount?: number | null; className?: string; }) => { @@ -75,6 +77,7 @@ export const ItemList = ({ setMoreOpened(v)} diff --git a/front/src/primitives/chip.tsx b/front/src/primitives/chip.tsx index cbbe2c78..f085c805 100644 --- a/front/src/primitives/chip.tsx +++ b/front/src/primitives/chip.tsx @@ -1,3 +1,4 @@ +import type { GestureResponderEvent } from "react-native"; import { View } from "react-native"; import { cn } from "~/utils"; import { Link } from "./links"; @@ -21,6 +22,7 @@ export const Chip = ({ href: string | null; replace?: boolean; target?: string; + onPress?: (e: GestureResponderEvent) => void; className?: string; }) => { return ( diff --git a/front/src/primitives/links.tsx b/front/src/primitives/links.tsx index a1f27ce0..f91fb3bf 100644 --- a/front/src/primitives/links.tsx +++ b/front/src/primitives/links.tsx @@ -96,6 +96,7 @@ export const Link = ({ href, replace, children, + disabled, ...props }: { href?: string | null; @@ -109,10 +110,10 @@ export const Link = ({ { - if (!href) return; props?.onPress?.(e); + if (!href) return; if (e?.defaultPrevented) return; else linkProps.onPress?.(e); }} diff --git a/front/src/ui/details/collection.tsx b/front/src/ui/details/collection.tsx index 307cce17..713b69a9 100644 --- a/front/src/ui/details/collection.tsx +++ b/front/src/ui/details/collection.tsx @@ -45,6 +45,11 @@ export const CollectionDetails = () => { description={item.description} genres={item.genres} playHref={item.kind !== "collection" ? item.playHref : null} + videoSlug={ + item.kind === "movie" && item.videos?.length === 1 + ? item.videos[0].slug + : null + } /> )} Loader={() => } diff --git a/front/src/ui/details/header.tsx b/front/src/ui/details/header.tsx index 95793248..c54eee8e 100644 --- a/front/src/ui/details/header.tsx +++ b/front/src/ui/details/header.tsx @@ -37,6 +37,7 @@ import { Link, Menu, P, + Popup, Poster, Skeleton, tooltip, @@ -291,6 +292,50 @@ TitleLine.Loader = ({ ); }; +const ExternalIdChip = ({ + name, + items, +}: { + name: string; + items: Metadata[string]; +}) => { + const [setPopup, closePopup] = usePopup(); + + const withLinks = items.filter((x) => x.link); + if (withLinks.length === 0) return null; + + return ( + + setPopup( + + {withLinks + .sort((a, b) => + (a.label ?? a.link!).localeCompare(b.label ?? b.link!), + ) + .map((x) => ( + + {x.label ?? x.link} + + ))} + , + ) + } + /> + ); +}; + const Description = ({ description, tags, @@ -364,19 +409,9 @@ const Description = ({ )}

{t("show.links")}:

- {Object.entries(externalIds) - .filter(([_, data]) => data.link) - .map(([name, data]) => ( - - ))} + {Object.entries(externalIds).map(([name, items]) => ( + + ))}
); diff --git a/front/src/ui/details/season.tsx b/front/src/ui/details/season.tsx index c76b74a3..a749fe90 100644 --- a/front/src/ui/details/season.tsx +++ b/front/src/ui/details/season.tsx @@ -142,7 +142,7 @@ export const EntryList = ({ diff --git a/front/src/ui/home/news.tsx b/front/src/ui/home/news.tsx index 30af6dab..ff56ae46 100644 --- a/front/src/ui/home/news.tsx +++ b/front/src/ui/home/news.tsx @@ -7,6 +7,25 @@ import { Header } from "./genre"; export const NewsList = () => { const { t } = useTranslation(); + const [setPopup, closePopup] = usePopup(); + + const openEntrySelect = useCallback( + (entry: { + displayNumber: string; + name: string | null; + videos: Entry["videos"]; + }) => { + setPopup( + , + ); + }, + [setPopup, closePopup], + ); return ( <> @@ -25,7 +44,14 @@ export const NewsList = () => { thumbnail={item.thumbnail ?? item.show!.thumbnail} href={item.href ?? "#"} watchedPercent={item.progress.percent} - videosCount={item.videos.length} + videos={item.videos} + onSelectVideos={() => + openEntrySelect({ + displayNumber: entryDisplayNumber(item), + name: item.name, + videos: item.videos, + }) + } /> )} Loader={EntryBox.Loader} diff --git a/front/src/ui/home/nextup.tsx b/front/src/ui/home/nextup.tsx index 5fbeded1..e961ffa0 100644 --- a/front/src/ui/home/nextup.tsx +++ b/front/src/ui/home/nextup.tsx @@ -1,9 +1,11 @@ +import { useCallback } from "react"; import { useTranslation } from "react-i18next"; import { View } from "react-native"; import { EntryBox, entryDisplayNumber } from "~/components/entries"; +import { EntrySelect } from "~/components/entries/select"; import { ItemGrid } from "~/components/items"; import { Entry } from "~/models"; -import { Button, Link, P } from "~/primitives"; +import { Button, Link, P, usePopup } from "~/primitives"; import { useAccount } from "~/providers/account-context"; import { InfiniteFetch, type QueryIdentifier } from "~/query"; import { EmptyView } from "~/ui/empty-view"; @@ -12,6 +14,25 @@ import { Header } from "./genre"; export const NextupList = () => { const { t } = useTranslation(); const account = useAccount(); + const [setPopup, closePopup] = usePopup(); + + const openEntrySelect = useCallback( + (entry: { + displayNumber: string; + name: string | null; + videos: Entry["videos"]; + }) => { + setPopup( + , + ); + }, + [setPopup, closePopup], + ); if (!account) { return ( @@ -47,7 +68,14 @@ export const NextupList = () => { thumbnail={item.thumbnail ?? item.show!.thumbnail} href={item.href ?? "#"} watchedPercent={item.progress.percent} - videosCount={item.videos.length} + videos={item.videos} + onSelectVideos={() => + openEntrySelect({ + displayNumber: entryDisplayNumber(item), + name: item.name, + videos: item.videos, + }) + } /> )} Loader={EntryBox.Loader} diff --git a/front/src/ui/home/recommended.tsx b/front/src/ui/home/recommended.tsx index 7d55ce56..bfee0ade 100644 --- a/front/src/ui/home/recommended.tsx +++ b/front/src/ui/home/recommended.tsx @@ -56,6 +56,11 @@ export const Recommended = () => { seenCount={ item.kind === "serie" ? item.watchStatus?.seenCount : null } + videoSlug={ + item.kind === "movie" && item.videos?.length === 1 + ? item.videos[0].slug + : null + } /> ); })} From d3ca4da16638bc4e019d563f5c4914f5aa4dfe87 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Wed, 18 Mar 2026 12:16:13 +0100 Subject: [PATCH 7/8] Handle expected_titles in guessit --- scanner/scanner/identifiers/anilist.py | 6 +- scanner/scanner/identifiers/guess/guess.py | 2 + scanner/scanner/identifiers/guess/rules.py | 89 ++++++++++++++++++++++ scanner/scanner/identifiers/identify.py | 7 +- 4 files changed, 101 insertions(+), 3 deletions(-) diff --git a/scanner/scanner/identifiers/anilist.py b/scanner/scanner/identifiers/anilist.py index 4ce0e7f2..ee10afd3 100644 --- a/scanner/scanner/identifiers/anilist.py +++ b/scanner/scanner/identifiers/anilist.py @@ -113,7 +113,11 @@ class AnimeListData: async def get_anilist_data() -> AnimeListData: logger.info("Fetching anime-lists XML databases...") ret = AnimeListData(fetched_at=datetime.now()) - async with ClientSession() as session: + async with ClientSession( + headers={ + "User-Agent": "kyoo scanner v5", + }, + ) as session: async with session.get(AnimeTitlesDb.get_url()) as resp: resp.raise_for_status() titles = AnimeTitlesDb.from_xml(await resp.read()) diff --git a/scanner/scanner/identifiers/guess/guess.py b/scanner/scanner/identifiers/guess/guess.py index d69150b8..2b86ba63 100644 --- a/scanner/scanner/identifiers/guess/guess.py +++ b/scanner/scanner/identifiers/guess/guess.py @@ -13,6 +13,7 @@ rblk = cast(Rebulk, default_api.rebulk).rules(rules) def guessit( name: str, *, + expected_titles: list[str] = [], extra_flags: dict[str, Any] = {}, ) -> dict[str, list[Match]]: return default_api.guessit( @@ -20,6 +21,7 @@ def guessit( { "episode_prefer_number": True, "excludes": "language", + "expected_titles": expected_titles, "enforce_list": True, "advanced": True, } diff --git a/scanner/scanner/identifiers/guess/rules.py b/scanner/scanner/identifiers/guess/rules.py index 9c38c0b9..7a55a655 100644 --- a/scanner/scanner/identifiers/guess/rules.py +++ b/scanner/scanner/identifiers/guess/rules.py @@ -76,6 +76,95 @@ class UnlistTitles(Rule): return [titles, [title]] +class ExpectedTitles(Rule): + """Fix both alternate names and seasons that are known titles but parsed differently by guessit + + Example: "JoJo's Bizarre Adventure - Diamond is Unbreakable - 12.mkv" + Default: + ```json + { + "title": "JoJo's Bizarre Adventure", + "alternative_title": "Diamond is Unbreakable", + "episode": 12, + } + ``` + Expected: + ```json + { + "title": "JoJo's Bizarre Adventure - Diamond is Unbreakable", + "episode": 12, + } + ``` + + Or + Example: 'Owarimonogatari S2 E15.mkv' + Default: + ```json + { + "title": "Owarimonogatari", + "season": 2, + "episode": 15 + } + ``` + Expected: + ```json + { + "title": "Owarimonogatari S2", + "episode": 15 + } + ``` + """ + + priority = POST_PROCESS + consequence = [RemoveMatch, AppendMatch] + + @override + def when(self, matches: Matches, context) -> Any: + from ..anilist import normalize_title + + titles: list[Match] = matches.named("title", lambda m: m.tagged("title")) # type: ignore + + if not titles or not context["expected_titles"]: + return + title = titles[0] + + # Greedily collect all adjacent matches that could be part of the title + absorbed: list[Match] = [] + current = title + while True: + nmatch: list[Match] = matches.next(current) + if not nmatch or not ( + nmatch[0].tagged("title") + or nmatch[0].named("season") + or nmatch[0].named("part") + ): + break + absorbed.append(nmatch[0]) + current = nmatch[0] + if not absorbed: + return + + # Try longest combined title first, then progressively shorter ones + for end in range(len(absorbed), 0, -1): + candidate_matches = absorbed[:end] + + mtitle = f"{title.value}" + prev = title + for m in candidate_matches: + holes: list[Match] = matches.holes(prev.end, m.start) # type: ignore + hole = "".join( + f" {h.value}" if h.value != "-" else " - " for h in holes + ) + mtitle = f"{mtitle}{hole}{m.value}" + prev = m + + if normalize_title(mtitle) in context["expected_titles"]: + new_title = copy(title) + new_title.end = candidate_matches[-1].end + new_title.value = mtitle + return [[title] + candidate_matches, [new_title]] + + class MultipleSeasonRule(Rule): """Understand `abcd Season 2 - 5.mkv` as S2E5 diff --git a/scanner/scanner/identifiers/identify.py b/scanner/scanner/identifiers/identify.py index 2ac9ea96..41cc7db5 100644 --- a/scanner/scanner/identifiers/identify.py +++ b/scanner/scanner/identifiers/identify.py @@ -7,7 +7,7 @@ from typing import Callable, Literal, cast from rebulk.match import Match from ..models.videos import Guess, Video -from .anilist import identify_anilist +from .anilist import get_anilist_data, identify_anilist from .guess.guess import guessit logger = getLogger(__name__) @@ -20,7 +20,10 @@ pipeline: list[Callable[[str, Guess], Awaitable[Guess]]] = [ async def identify(path: str) -> Video: - raw = guessit(path) + raw = guessit( + path, + expected_titles=list((await get_anilist_data()).titles.keys()), + ) # guessit should only return one (according to the doc) title = raw.get("title", [])[0] From 6d9bccef13d089361475e4b3fbbf18d484dd8d60 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Wed, 18 Mar 2026 15:02:14 +0100 Subject: [PATCH 8/8] Last anilist cleanups --- front/src/models/entry.ts | 21 +++++++++++-- front/src/ui/home/news.tsx | 3 ++ front/src/ui/unmatched/index.tsx | 1 - scanner/scanner/identifiers/anilist.py | 41 ++++++++++++++++++-------- 4 files changed, 51 insertions(+), 15 deletions(-) diff --git a/front/src/models/entry.ts b/front/src/models/entry.ts index 2690eead..59816537 100644 --- a/front/src/models/entry.ts +++ b/front/src/models/entry.ts @@ -1,7 +1,6 @@ import { z } from "zod/v4"; import { Show } from "./show"; import { KImage } from "./utils/images"; -import { Metadata } from "./utils/metadata"; import { zdate } from "./utils/utils"; const Base = z.object({ @@ -59,7 +58,25 @@ export const MovieEntry = Base.extend({ kind: z.literal("movie"), tagline: z.string().nullable(), poster: KImage.nullable(), - externalId: Metadata, + externalId: z.record( + z.string(), + z.array( + z.union([ + z.object({ + serieId: z.string(), + season: z.int().nullable(), + episode: z.int(), + link: z.string().nullable(), + label: z.string().optional().nullable(), + }), + z.object({ + dataId: z.string(), + link: z.string().nullable(), + label: z.string().optional().nullable(), + }), + ]), + ), + ), }); export type MovieEntry = z.infer; diff --git a/front/src/ui/home/news.tsx b/front/src/ui/home/news.tsx index ff56ae46..5d352ea5 100644 --- a/front/src/ui/home/news.tsx +++ b/front/src/ui/home/news.tsx @@ -1,6 +1,9 @@ +import { useCallback } from "react"; import { useTranslation } from "react-i18next"; import { EntryBox, entryDisplayNumber } from "~/components/entries"; +import { EntrySelect } from "~/components/entries/select"; import { Entry } from "~/models"; +import { usePopup } from "~/primitives"; import { InfiniteFetch, type QueryIdentifier } from "~/query"; import { EmptyView } from "~/ui/empty-view"; import { Header } from "./genre"; diff --git a/front/src/ui/unmatched/index.tsx b/front/src/ui/unmatched/index.tsx index 94b63433..b5b1b04b 100644 --- a/front/src/ui/unmatched/index.tsx +++ b/front/src/ui/unmatched/index.tsx @@ -10,7 +10,6 @@ import { z } from "zod/v4"; import { ScanRequest, Video } from "~/models"; import { Button, - Container, DottedSeparator, HR, IconButton, diff --git a/scanner/scanner/identifiers/anilist.py b/scanner/scanner/identifiers/anilist.py index ee10afd3..248d0123 100644 --- a/scanner/scanner/identifiers/anilist.py +++ b/scanner/scanner/identifiers/anilist.py @@ -236,6 +236,22 @@ async def identify_anilist(_path: str, guess: Guess) -> Guess: if anime is None: return guess + new_external_id = dict(guess.external_id) + new_external_id[ProviderName.ANIDB] = aid + if anime.tvdbid: + new_external_id[ProviderName.TVDB] = anime.tvdbid + # tmdbtv is for TV series, tmdbid is for standalone movies + if anime.tmdbtv: + new_external_id[ProviderName.TMDB] = anime.tmdbtv + elif anime.tmdbid and "," not in anime.tmdbid: + new_external_id[ProviderName.TMDB] = anime.tmdbid + if anime.imdbid and "," not in anime.imdbid: + new_external_id[ProviderName.IMDB] = anime.imdbid + + # if we don't have a single external id, skip it and use the normal flow + if len(new_external_id) == 1: + return guess + logger.info( "Matched '%s' to AniDB id %s (tvdb=%s, tmdbid=%s)", guess.title, @@ -244,17 +260,18 @@ async def identify_anilist(_path: str, guess: Guess) -> Guess: anime.tmdbid, ) - new_external_id = dict(guess.external_id) - new_external_id[ProviderName.ANIDB] = aid - if anime.tvdbid: - new_external_id[ProviderName.TVDB] = anime.tvdbid - # tmdbtv is for TV series, tmdbid is for standalone movies - if anime.tmdbtv: - new_external_id[ProviderName.TMDB] = anime.tmdbtv - elif anime.tmdbid: - new_external_id[ProviderName.TMDB] = anime.tmdbid - if anime.imdbid: - new_external_id[ProviderName.IMDB] = anime.imdbid + animes = ( + [data.animes[id] for id in data.tvdb_anidb.get(anime.tvdbid, [])] + if anime.tvdbid + else [] + ) + new_title = next( + (x.name for x in animes if x.defaulttvdbseason == 1), + next( + (x.name for x in animes if x.defaulttvdbseason == "a"), + anime.name, + ), + ) new_episodes: list[Guess.Episode] = [] for ep in guess.episodes: @@ -297,7 +314,7 @@ async def identify_anilist(_path: str, guess: Guess) -> Guess: ) return Guess( - title=guess.title, + title=new_title or guess.title, kind=kind, extra_kind=guess.extra_kind, years=guess.years,