Number of fixes and improvements

This commit is contained in:
Georges-Antoine Assi
2024-02-13 20:04:29 -05:00
parent ce9deb4f3c
commit edca5e6c7f
16 changed files with 128 additions and 67 deletions

View File

@@ -25,6 +25,9 @@ def upgrade() -> None:
batch_op.add_column(sa.Column("moby_id", sa.Integer(), nullable=True))
batch_op.add_column(sa.Column("moby_metadata", mysql.JSON(), nullable=True))
with op.batch_alter_table("roms", schema=None) as batch_op:
batch_op.execute("update roms set moby_metadata = '\\{\\}'")
# ### end Alembic commands ###

View File

@@ -1,27 +1,29 @@
import re
from typing import Optional
from typing import Optional, get_type_hints
from typing_extensions import NotRequired, TypedDict
from endpoints.responses.assets import SaveSchema, ScreenshotSchema, StateSchema
from fastapi import Request
from fastapi.responses import StreamingResponse
from handler import socket_handler
from handler.metadata_handler.igdb_handler import IGDBRelatedGame
from handler.metadata_handler.igdb_handler import IGDBMetadata
from handler.metadata_handler.moby_handler import MobyMetadata
from pydantic import BaseModel, computed_field, Field
from models.rom import Rom
from typing_extensions import TypedDict, NotRequired
SORT_COMPARE_REGEX = r"^([Tt]he|[Aa]|[Aa]nd)\s"
class RomMetadata(TypedDict):
expansions: NotRequired[list[IGDBRelatedGame]]
dlcs: NotRequired[list[IGDBRelatedGame]]
remasters: NotRequired[list[IGDBRelatedGame]]
remakes: NotRequired[list[IGDBRelatedGame]]
expanded_games: NotRequired[list[IGDBRelatedGame]]
ports: NotRequired[list[IGDBRelatedGame]]
similar_games: NotRequired[list[IGDBRelatedGame]]
RomIGDBMetadata = TypedDict(
"RomIGDBMetadata",
{k: NotRequired[v] for k, v in get_type_hints(IGDBMetadata).items()},
total=False,
)
RomMobyMetadata = TypedDict(
"RomMobyMetadata",
{k: NotRequired[v] for k, v in get_type_hints(MobyMetadata).items()},
total=False,
)
class RomSchema(BaseModel):
@@ -46,8 +48,6 @@ class RomSchema(BaseModel):
summary: Optional[str]
# Metadata fields
total_rating: Optional[str]
aggregated_rating: Optional[str]
first_release_date: Optional[int]
alternative_names: list[str]
genres: list[str]
@@ -55,7 +55,8 @@ class RomSchema(BaseModel):
collections: list[str]
companies: list[str]
game_modes: list[str]
igdb_metadata: Optional[RomMetadata]
igdb_metadata: Optional[RomIGDBMetadata]
moby_metadata: Optional[RomMobyMetadata]
path_cover_s: Optional[str]
path_cover_l: Optional[str]

View File

@@ -13,7 +13,6 @@ router = APIRouter()
async def search_rom(
request: Request,
rom_id: str,
source: str,
search_term: str = None,
search_by: str = "name",
search_extended: bool = False,

View File

@@ -6,7 +6,7 @@ from sqlalchemy.orm import Session
class DBRomsHandler(DBHandler):
def _filter(data: Select[Rom], platform_id: int | None, search_term: str):
def _filter(self, data: Select[Rom], platform_id: int | None, search_term: str):
if platform_id:
data = data.filter_by(platform_id=platform_id)
@@ -20,7 +20,7 @@ class DBRomsHandler(DBHandler):
return data
def _order(data: Select[Rom], order_by: str, order_dir: str):
def _order(self, data: Select[Rom], order_by: str, order_dir: str):
if order_by == "id":
_column = Rom.id
else:

View File

@@ -40,7 +40,7 @@ class MetadataHandler:
@staticmethod
def _normalize_cover_url(url: str) -> str:
return f"https:{url.replace('https:', '')}"
async def _ps2_opl_format(self, match: re.Match[str], search_term: str) -> str:
serial_code = match.group(1)
@@ -52,7 +52,9 @@ class MetadataHandler:
return search_term
async def _switch_titledb_format(self, match: re.Match[str], search_term: str) -> str:
async def _switch_titledb_format(
self, match: re.Match[str], search_term: str
) -> str:
titledb_index = {}
title_id = match.group(1)
@@ -74,7 +76,9 @@ class MetadataHandler:
return search_term
async def _switch_productid_format(self, match: re.Match[str], search_term: str) -> str:
async def _switch_productid_format(
self, match: re.Match[str], search_term: str
) -> str:
product_id_index = {}
product_id = match.group(1)

View File

@@ -2,7 +2,8 @@ import functools
import re
import sys
import time
from typing import Final, Optional, TypedDict, NotRequired
from typing import Final, Optional
from typing_extensions import NotRequired, TypedDict
import pydash
import requests
@@ -29,7 +30,6 @@ ARCADE_IGDB_IDS: Final = [52, 79, 80]
class IGDBPlatform(TypedDict):
igdb_id: int
slug: str
name: NotRequired[str]
@@ -190,19 +190,22 @@ class IGDBHandler(MetadataHandler):
return res.json()
def _search_rom(
self, search_term: str, platform_idgb_id: int, category: int = 0
self, search_term: str, platform_igdb_id: int, category: int = 0
) -> dict | None:
if not platform_igdb_id:
return None
search_term = uc(search_term)
category_filter: str = f"& category={category}" if category else ""
roms = self._request(
self.games_endpoint,
data=f'search "{search_term}"; fields {",".join(self.games_fields)}; where platforms=[{platform_idgb_id}] {category_filter};',
data=f'search "{search_term}"; fields {",".join(self.games_fields)}; where platforms=[{platform_igdb_id}] {category_filter};',
)
if not roms:
roms = self._request(
self.search_endpoint,
data=f'fields {",".join(self.search_fields)}; where game.platforms=[{platform_idgb_id}] & (name ~ *"{search_term}"* | alternative_name ~ *"{search_term}"*);',
data=f'fields {",".join(self.search_fields)}; where game.platforms=[{platform_igdb_id}] & (name ~ *"{search_term}"* | alternative_name ~ *"{search_term}"*);',
)
if roms:
roms = self._request(
@@ -250,36 +253,39 @@ class IGDBHandler(MetadataHandler):
return IGDBPlatform(igdb_id=None, slug=slug)
@check_twitch_token
async def get_rom(self, file_name: str, platform_idgb_id: int) -> IGDBRom:
async def get_rom(self, file_name: str, platform_igdb_id: int) -> IGDBRom:
from handler import fs_rom_handler
if not platform_igdb_id:
return IGDBRom(igdb_id=None)
search_term = fs_rom_handler.get_file_name_with_no_tags(file_name)
# Support for PS2 OPL filename format
match = re.match(PS2_OPL_REGEX, file_name)
if platform_idgb_id == PS2_IGDB_ID and match:
if platform_igdb_id == PS2_IGDB_ID and match:
search_term = await self._ps2_opl_format(match, search_term)
# Support for switch titleID filename format
match = re.search(SWITCH_TITLEDB_REGEX, file_name)
if platform_idgb_id == SWITCH_IGDB_ID and match:
if platform_igdb_id == SWITCH_IGDB_ID and match:
search_term = await self._switch_titledb_format(match, search_term)
# Support for switch productID filename format
match = re.search(SWITCH_PRODUCT_ID_REGEX, file_name)
if platform_idgb_id == SWITCH_IGDB_ID and match:
if platform_igdb_id == SWITCH_IGDB_ID and match:
search_term = await self._switch_productid_format(match, search_term)
# Support for MAME arcade filename format
if platform_idgb_id in ARCADE_IGDB_IDS:
if platform_igdb_id in ARCADE_IGDB_IDS:
search_term = await self._mame_format(search_term)
search_term = self._normalize_search_term(search_term)
search_term = self.normalize_search_term(search_term)
rom = (
self._search_rom(search_term, platform_idgb_id, MAIN_GAME_CATEGORY)
or self._search_rom(search_term, platform_idgb_id, EXPANDED_GAME_CATEGORY)
or self._search_rom(search_term, platform_idgb_id)
self._search_rom(search_term, platform_igdb_id, MAIN_GAME_CATEGORY)
or self._search_rom(search_term, platform_igdb_id, EXPANDED_GAME_CATEGORY)
or self._search_rom(search_term, platform_igdb_id)
)
if not rom:
@@ -332,22 +338,22 @@ class IGDBHandler(MetadataHandler):
@check_twitch_token
def get_matched_roms_by_name(
self, search_term: str, platform_idgb_id: int, search_extended: bool = False
self, search_term: str, platform_igdb_id: int, search_extended: bool = False
) -> list[IGDBRom]:
if not platform_idgb_id:
if not platform_igdb_id:
return []
search_term = uc(search_term)
matched_roms = self._request(
self.games_endpoint,
data=f'search "{search_term}"; fields {",".join(self.games_fields)}; where platforms=[{platform_idgb_id}];',
data=f'search "{search_term}"; fields {",".join(self.games_fields)}; where platforms=[{platform_igdb_id}];',
)
if not matched_roms or search_extended:
log.info("Extended searching...")
alternative_matched_roms = self._request(
self.search_endpoint,
data=f'fields {",".join(self.search_fields)}; where game.platforms=[{platform_idgb_id}] & (name ~ *"{search_term}"* | alternative_name ~ *"{search_term}"*);',
data=f'fields {",".join(self.search_fields)}; where game.platforms=[{platform_igdb_id}] & (name ~ *"{search_term}"* | alternative_name ~ *"{search_term}"*);',
)
if alternative_matched_roms:

View File

@@ -4,10 +4,12 @@ import yarl
import re
import time
from config import MOBYGAMES_API_KEY
from typing import Final, TypedDict, NotRequired, Optional
from typing import Final, Optional
from typing_extensions import NotRequired, TypedDict
from requests.exceptions import HTTPError, Timeout
from logger.logger import log
from unidecode import unidecode as uc
from urllib.parse import quote_plus
from . import (
MetadataHandler,
@@ -23,7 +25,6 @@ ARCADE_MOBY_IDS: Final = [143, 36]
class MobyGamesPlatform(TypedDict):
moby_id: int
slug: str
name: NotRequired[str]
@@ -53,7 +54,6 @@ def extract_metadata_from_moby_rom(rom: dict) -> MobyMetadata:
"platforms": [
{
"moby_id": p["platform_id"],
"slug": MOBY_ID_TO_SLUG[p["platform_id"]],
"name": p["platform_name"],
}
for p in rom.get("platforms", [])
@@ -95,8 +95,13 @@ class MobyGamesHandler(MetadataHandler):
return res.json()
def _search_rom(self, search_term: str, platform_moby_id: int) -> dict | None:
if not platform_moby_id:
return None
search_term = uc(search_term)
url = yarl.URL(self.games_url).with_query(
platform=[platform_moby_id or 0], title=search_term
platform=[platform_moby_id or 0],
title=quote_plus(search_term),
)
roms = self._request(str(url)).get("games", [])
@@ -121,6 +126,9 @@ class MobyGamesHandler(MetadataHandler):
async def get_rom(self, file_name: str, platform_moby_id: int) -> MobyGamesRom:
from handler import fs_rom_handler
if not platform_moby_id:
return MobyGamesRom(moby_id=None)
search_term = fs_rom_handler.get_file_name_with_no_tags(file_name)
# Support for PS2 OPL filename format
@@ -143,7 +151,7 @@ class MobyGamesHandler(MetadataHandler):
search_term = await self._mame_format(search_term)
search_term = self.normalize_search_term(search_term)
res = self._search_rom(uc(search_term), platform_moby_id)
res = self._search_rom(search_term, platform_moby_id)
if not res:
return MobyGamesRom(moby_id=None)
@@ -155,6 +163,7 @@ class MobyGamesHandler(MetadataHandler):
"summary": res.get("description", ""),
"url_cover": res.get("sample_cover.image", ""),
"url_screenshots": [s["image"] for s in res.get("sample_screenshots", [])],
"moby_metadata": extract_metadata_from_moby_rom(res),
}
return MobyGamesRom({k: v for k, v in rom.items() if v})
@@ -174,6 +183,7 @@ class MobyGamesHandler(MetadataHandler):
"summary": res.get("description", None),
"url_cover": res.get("sample_cover.image", None),
"url_screenshots": [s["image"] for s in res.get("sample_screenshots", [])],
"moby_metadata": extract_metadata_from_moby_rom(res),
}
return MobyGamesRom({k: v for k, v in rom.items() if v})
@@ -187,8 +197,9 @@ class MobyGamesHandler(MetadataHandler):
if not platform_moby_id:
return []
search_term = uc(search_term)
url = yarl.URL(self.games_url).with_query(
platform=[platform_moby_id or 0], title=search_term
platform=[platform_moby_id or 0], title=quote_plus(search_term)
)
matched_roms = self._request(str(url))["games"]
@@ -205,6 +216,7 @@ class MobyGamesHandler(MetadataHandler):
"url_screenshots": [
s["image"] for s in rom.get("sample_screenshots", [])
],
"moby_metadata": extract_metadata_from_moby_rom(rom),
}.items()
if v
}

View File

@@ -118,18 +118,6 @@ class Rom(BaseModel):
).all()
# Metadata fields
@property
def total_rating(self) -> str:
return (
self.igdb_metadata.get("total_rating", None)
or self.moby_metadata.get("moby_score", None)
or ""
)
@property
def aggregated_rating(self) -> str:
return self.igdb_metadata.get("aggregated_rating", "")
@property
def alternative_names(self) -> list[str]:
return (

View File

@@ -15,11 +15,14 @@ export type { ConfigResponse } from './models/ConfigResponse';
export type { CursorPage_RomSchema_ } from './models/CursorPage_RomSchema_';
export type { HeartbeatResponse } from './models/HeartbeatResponse';
export type { HTTPValidationError } from './models/HTTPValidationError';
export type { IGDBPlatform } from './models/IGDBPlatform';
export type { IGDBRelatedGame } from './models/IGDBRelatedGame';
export type { MessageResponse } from './models/MessageResponse';
export type { MobyGamesPlatform } from './models/MobyGamesPlatform';
export type { PlatformSchema } from './models/PlatformSchema';
export type { Role } from './models/Role';
export type { RomMetadata } from './models/RomMetadata';
export type { RomIGDBMetadata } from './models/RomIGDBMetadata';
export type { RomMobyMetadata } from './models/RomMobyMetadata';
export type { RomSchema } from './models/RomSchema';
export type { SaveSchema } from './models/SaveSchema';
export type { SchedulerDict } from './models/SchedulerDict';

View File

@@ -0,0 +1,10 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type IGDBPlatform = {
igdb_id: number;
name?: string;
};

View File

@@ -0,0 +1,10 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type MobyGamesPlatform = {
moby_id: number;
name?: string;
};

View File

@@ -3,9 +3,20 @@
/* tslint:disable */
/* eslint-disable */
import type { IGDBPlatform } from './IGDBPlatform';
import type { IGDBRelatedGame } from './IGDBRelatedGame';
export type RomMetadata = {
export type RomIGDBMetadata = {
total_rating?: string;
aggregated_rating?: string;
first_release_date?: (number | null);
genres?: Array<string>;
franchises?: Array<string>;
alternative_names?: Array<string>;
collections?: Array<string>;
companies?: Array<string>;
game_modes?: Array<string>;
platforms?: Array<IGDBPlatform>;
expansions?: Array<IGDBRelatedGame>;
dlcs?: Array<IGDBRelatedGame>;
remasters?: Array<IGDBRelatedGame>;

View File

@@ -0,0 +1,14 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { MobyGamesPlatform } from './MobyGamesPlatform';
export type RomMobyMetadata = {
moby_score?: string;
genres?: Array<string>;
alternate_titles?: Array<string>;
platforms?: Array<MobyGamesPlatform>;
};

View File

@@ -3,7 +3,8 @@
/* tslint:disable */
/* eslint-disable */
import type { RomMetadata } from './RomMetadata';
import type { RomIGDBMetadata } from './RomIGDBMetadata';
import type { RomMobyMetadata } from './RomMobyMetadata';
import type { SaveSchema } from './SaveSchema';
import type { ScreenshotSchema } from './ScreenshotSchema';
import type { StateSchema } from './StateSchema';
@@ -25,8 +26,6 @@ export type RomSchema = {
name: (string | null);
slug: (string | null);
summary: (string | null);
total_rating: (string | null);
aggregated_rating: (string | null);
first_release_date: (number | null);
alternative_names: Array<string>;
genres: Array<string>;
@@ -34,7 +33,8 @@ export type RomSchema = {
collections: Array<string>;
companies: Array<string>;
game_modes: Array<string>;
igdb_metadata: (RomMetadata | null);
igdb_metadata: (RomIGDBMetadata | null);
moby_metadata: (RomMobyMetadata | null);
path_cover_s: (string | null);
path_cover_l: (string | null);
has_cover: boolean;

View File

@@ -49,7 +49,7 @@ defineProps<{ rom: Rom }>();
</v-col>
<v-col class="text-center">
<v-chip variant="text" label>
{{ rom.total_rating }}
{{ rom.igdb_metadata?.total_rating }}
</v-chip>
</v-col>
</v-row>
@@ -80,7 +80,7 @@ defineProps<{ rom: Rom }>();
</v-col>
<v-col class="text-center">
<v-chip variant="text" label>
{{ rom.total_rating }}
{{ rom.moby_metadata?.moby_score }}
</v-chip>
</v-col>
</v-row>

View File

@@ -90,7 +90,7 @@ const { smAndDown } = useDisplay();
<v-divider class="mx-2 border-opacity-25" vertical />
<span>ID: {{ rom.igdb_id }}</span>
<v-divider class="mx-2 border-opacity-25" vertical />
<span>Rating: {{ rom.total_rating }}</span>
<span>Rating: {{ rom.igdb_metadata?.total_rating }}</span>
</v-chip>
</a>
</v-col>
@@ -112,7 +112,7 @@ const { smAndDown } = useDisplay();
<v-divider class="mx-2 border-opacity-25" vertical />
<span>ID: {{ rom.moby_id }}</span>
<v-divider class="mx-2 border-opacity-25" vertical />
<span>Rating: {{ rom.total_rating }}</span>
<span>Rating: {{ rom.moby_metadata?.moby_score }}</span>
</v-chip>
</a>
</v-col>