Files
romm/backend/endpoints/responses/feeds.py
Georges-Antoine Assi d0a71ae5fe add new endpoints for pkgj
2026-02-03 16:22:39 -05:00

287 lines
6.7 KiB
Python

from datetime import datetime
from typing import Annotated, Any, Final, NotRequired, TypedDict
from pydantic import BaseModel, BeforeValidator, Field, field_validator
from handler.metadata.base_handler import UniversalPlatformSlug as UPS
from tasks.scheduled.update_switch_titledb import TITLEDB_REGION_LIST
from utils.database import safe_int
WEBRCADE_SUPPORTED_PLATFORM_SLUGS = frozenset(
(
UPS._3DO,
UPS.ARCADE,
UPS.ATARI2600,
UPS.ATARI5200,
UPS.ATARI7800,
UPS.C64,
UPS.COLECOVISION,
UPS.DOS,
UPS.GAMEGEAR,
UPS.GB,
UPS.GBA,
UPS.GBC,
UPS.GENESIS,
UPS.LYNX,
UPS.N64,
UPS.NEO_GEO_CD,
UPS.NEO_GEO_POCKET,
UPS.NEO_GEO_POCKET_COLOR,
UPS.NEOGEOAES,
UPS.NEOGEOMVS,
UPS.NES,
UPS.PC_FX,
UPS.PSX,
UPS.SEGACD,
UPS.SG1000,
UPS.SMS,
UPS.SNES,
UPS.SUPERGRAFX,
UPS.TURBOGRAFX_CD,
UPS.TG16,
UPS.VIRTUALBOY,
UPS.WONDERSWAN,
UPS.WONDERSWAN_COLOR,
)
)
WEBRCADE_SLUG_TO_TYPE_MAP = {
UPS.ATARI2600: "2600",
UPS.ATARI5200: "5200",
UPS.ATARI7800: "7800",
UPS.C64: "commodore-c64",
UPS.LYNX: "lnx",
UPS.TG16: "pce",
UPS.TURBOGRAFX_CD: "pce",
UPS.SUPERGRAFX: "sgx",
UPS.PC_FX: "pcfx",
UPS.VIRTUALBOY: "vb",
UPS.GAMEGEAR: "gg",
UPS.NEOGEOAES: "neogeo",
UPS.NEOGEOMVS: "neogeo",
UPS.NEO_GEO_CD: "neogeocd",
UPS.NEO_GEO_POCKET: "ngp",
UPS.NEO_GEO_POCKET_COLOR: "ngc",
}
# Webrcade feed format
# Source: https://docs.webrcade.com/feeds/format/
class WebrcadeFeedItemPropsSchema(TypedDict):
rom: str
class WebrcadeFeedItemSchema(TypedDict):
title: str
longTitle: NotRequired[str]
description: NotRequired[str]
type: str
thumbnail: NotRequired[str]
background: NotRequired[str]
props: WebrcadeFeedItemPropsSchema
class WebrcadeFeedCategorySchema(TypedDict):
title: str
longTitle: NotRequired[str]
background: NotRequired[str]
thumbnail: NotRequired[str]
description: NotRequired[str]
items: list[WebrcadeFeedItemSchema]
class WebrcadeFeedSchema(TypedDict):
title: str
longTitle: NotRequired[str]
description: NotRequired[str]
thumbnail: NotRequired[str]
background: NotRequired[str]
categories: list[WebrcadeFeedCategorySchema]
# Tinfoil feed format
# Source: https://blawar.github.io/tinfoil/custom_index/
UNIX_EPOCH_START_DATE: Final = 19700101
def coerce_to_string(value: Any) -> str:
"""Coerce value to string, returning empty string for None."""
return "" if value is None else str(value)
def coerce_to_int(value: Any) -> int:
"""Coerce value to int, returning 0 for None/empty values."""
return safe_int(value, default=0)
# Annotated types for cleaner field definitions
StringField = Annotated[str, BeforeValidator(coerce_to_string)]
IntField = Annotated[int, BeforeValidator(coerce_to_int)]
class TinfoilFeedFileSchema(TypedDict):
url: str
size: int
class TinfoilFeedTitleDBSchema(BaseModel):
"""Schema for Tinfoil feed title database entries.
This schema handles the conversion and validation of game metadata
for the Tinfoil custom index format.
"""
id: StringField = Field(default="")
name: StringField = Field(default="")
description: StringField = Field(default="")
region: StringField = Field(default="US")
publisher: StringField = Field(default="")
releaseDate: IntField = Field(default=UNIX_EPOCH_START_DATE)
size: IntField = Field(default=0, ge=0)
version: IntField = Field(default=0, ge=0)
rating: IntField = Field(default=0, ge=0, le=100)
rank: IntField = Field(default=0, ge=0)
@field_validator("region")
def validate_region(cls, v: str) -> str:
if v not in TITLEDB_REGION_LIST:
return "US"
return v
@field_validator("releaseDate")
def validate_release_date(cls, v: int) -> int:
if v < UNIX_EPOCH_START_DATE:
return UNIX_EPOCH_START_DATE
return v
class TinfoilFeedSchema(TypedDict):
files: list[TinfoilFeedFileSchema]
directories: list[str]
titledb: NotRequired[dict[str, dict]] # dict after .model_dump()
success: NotRequired[str]
error: NotRequired[str]
# PKGi PS3 feed format
# Source: https://github.com/bucanero/pkgi-ps3
class PKGiFeedPS3ItemSchema(BaseModel):
"""Schema for PKGi PS3 feed items.
Follows the PKGi database format:
contentid,type,name,description,rap,url,size,checksum
"""
contentid: str
type: int
name: str
description: str
rap: str
url: str
size: int
checksum: str
# PKGi PSP feed format
# Source: https://github.com/bucanero/pkgi-psp
class PKGiFeedPSPItemSchema(PKGiFeedPS3ItemSchema):
"""Schema for PKGi PSP feed items.
Follows the PKGi database format:
contentid,type,name,description,rap,url,size,checksum
"""
pass
# PKGi PS Vita feed format
# Source: https://github.com/mmozeiko/pkgi
class PKGiFeedPSVitaItemSchema(BaseModel):
"""Schema for PKGi PS Vita feed items.
Follows the PKGi database format:
contentid,flags,name,name2,zrif,url,size,checksum
"""
contentid: str
flags: int
name: str
name2: str
zrif: str
url: str
size: int
checksum: str
# FPKGi feed format
# Source: https://github.com/ItsJokerZz/FPKGi
class FPKGiFeedItemSchema(BaseModel):
name: str
size: int
title_id: str
region: str | None
version: str | None
release: str | None
min_fw: str | None
cover_url: str | None
# Kekatsu DS feed format
# Source: https://github.com/cavv-dev/Kekatsu-DS
class KekatsuDSItemSchema(BaseModel):
"""Schema for Kekatsu DS feed items.
Follows the Kekatsu DS database format:
title,platform,region,version,author,download_url,filename,size,box_art_url[,extract_items...]
"""
title: str
platform: str
region: str
version: str
author: str
download_url: str
filename: str
size: int
box_art_url: str
# Pkgj feed formats
# Source: https://github.com/rommapp/romm/issues/2899
class PkgjBaseItemSchema(BaseModel):
title_id: str
region: str
name: str
download_link: str
content_id: str
last_modified: datetime
file_size: int
sha_256: str
class PkgjPSPGamesItemSchema(PkgjBaseItemSchema):
type: str
rap: str
download_rap_file: str
class PkgjPSPDlcsItemSchema(PkgjBaseItemSchema):
rap: str
download_rap_file: str
class PkgjPSVGamesItemSchema(PkgjBaseItemSchema):
zrif: str
original_name: str
required_fw: str
app_version: str
class PkgjPSVDlcsItemSchema(PkgjBaseItemSchema):
zrif: str
class PkgjPSXGamesItemSchema(PkgjBaseItemSchema):
original_name: str