|
| 1 | +import json |
| 2 | +from typing import Final, NotRequired, TypedDict |
| 3 | + |
| 4 | +from handler.redis_handler import async_cache |
| 5 | +from tasks.update_launchbox_metadata import ( # LAUNCHBOX_PLATFORMS_KEY,; LAUNCHBOX_METADATA_IMAGE_KEY,; LAUNCHBOX_MAME_KEY,; update_launchbox_metadata_task, |
| 6 | + LAUNCHBOX_FILES_KEY, |
| 7 | + LAUNCHBOX_METADATA_DATABASE_ID_KEY, |
| 8 | + LAUNCHBOX_METADATA_NAME_KEY, |
| 9 | +) |
| 10 | + |
| 11 | +from .base_hander import MetadataHandler |
| 12 | + |
| 13 | +LAUNCHBOX_API_ENABLED: Final = True |
| 14 | + |
| 15 | + |
| 16 | +class LaunchboxPlatform(TypedDict): |
| 17 | + slug: str |
| 18 | + name: NotRequired[str] |
| 19 | + |
| 20 | + |
| 21 | +class LaunchboxMetadata(TypedDict): |
| 22 | + release_date: NotRequired[str] |
| 23 | + max_players: NotRequired[int] |
| 24 | + release_type: NotRequired[str] |
| 25 | + cooperative: NotRequired[bool] |
| 26 | + video_url: NotRequired[str] |
| 27 | + community_rating: NotRequired[float] |
| 28 | + community_rating_count: NotRequired[int] |
| 29 | + wikipedia_url: NotRequired[str] |
| 30 | + esrb: NotRequired[str] |
| 31 | + genres: NotRequired[list[str]] |
| 32 | + developer: NotRequired[str] |
| 33 | + publisher: NotRequired[str] |
| 34 | + |
| 35 | + |
| 36 | +class LaunchboxRom(TypedDict): |
| 37 | + name: NotRequired[str] |
| 38 | + summary: NotRequired[str] |
| 39 | + url_cover: NotRequired[str] |
| 40 | + url_screenshots: NotRequired[list[str]] |
| 41 | + launchbox_metadata: NotRequired[LaunchboxMetadata] |
| 42 | + |
| 43 | + |
| 44 | +def extract_metadata_from_launchbox_rom(index_entry: dict) -> LaunchboxMetadata: |
| 45 | + return LaunchboxMetadata( |
| 46 | + { |
| 47 | + "release_date": index_entry.get("ReleaseDate", ""), |
| 48 | + "max_players": index_entry.get("MaxPlayers", 0), |
| 49 | + "release_type": index_entry.get("ReleaseType", ""), |
| 50 | + "cooperative": index_entry.get("Cooperative", False), |
| 51 | + "video_url": index_entry.get("VideoURL", ""), |
| 52 | + "community_rating": index_entry.get("CommunityRating", 0), |
| 53 | + "community_rating_count": index_entry.get("CommunityRatingCount", 0), |
| 54 | + "wikipedia_url": index_entry.get("WikipediaURL", ""), |
| 55 | + "esrb": index_entry.get("ESRB", ""), |
| 56 | + "genres": index_entry.get("Genres", []), |
| 57 | + "developer": index_entry.get("Developer", ""), |
| 58 | + "publisher": index_entry.get("Publisher", ""), |
| 59 | + } |
| 60 | + ) |
| 61 | + |
| 62 | + |
| 63 | +class LaunchboxHandler(MetadataHandler): |
| 64 | + async def _search_rom(self, file_name: str) -> dict | None: |
| 65 | + file_index_entry = await async_cache.hget(LAUNCHBOX_FILES_KEY, file_name) |
| 66 | + if not file_index_entry: |
| 67 | + return None |
| 68 | + |
| 69 | + file_index_entry = json.loads(file_index_entry) |
| 70 | + metadata_name_index_entry = await async_cache.hget( |
| 71 | + LAUNCHBOX_METADATA_NAME_KEY, file_index_entry["name"] |
| 72 | + ) |
| 73 | + |
| 74 | + if not metadata_name_index_entry: |
| 75 | + return None |
| 76 | + |
| 77 | + return json.loads(metadata_name_index_entry) |
| 78 | + |
| 79 | + def get_platform(self, slug: str) -> LaunchboxPlatform: |
| 80 | + platform = SLUG_TO_LAUNCHBOX_PLATFORM_NAME.get(slug, None) |
| 81 | + |
| 82 | + if not platform: |
| 83 | + return LaunchboxPlatform(slug=slug) |
| 84 | + |
| 85 | + return LaunchboxPlatform( |
| 86 | + slug=slug, |
| 87 | + name=platform["name"], |
| 88 | + ) |
| 89 | + |
| 90 | + async def get_rom(self, fs_name: str) -> LaunchboxRom: |
| 91 | + from handler.filesystem import fs_rom_handler |
| 92 | + |
| 93 | + if not LAUNCHBOX_API_ENABLED: |
| 94 | + return LaunchboxRom() |
| 95 | + |
| 96 | + search_term = fs_rom_handler.get_file_name_with_no_extension(fs_name) |
| 97 | + fallback_rom = LaunchboxRom() |
| 98 | + |
| 99 | + index_entry = await self._search_rom(search_term) |
| 100 | + if not index_entry: |
| 101 | + return fallback_rom |
| 102 | + |
| 103 | + rom = { |
| 104 | + "name": index_entry["Name"], |
| 105 | + "summary": index_entry.get("Overview", ""), |
| 106 | + "launchbox_metadata": extract_metadata_from_launchbox_rom(index_entry), |
| 107 | + } |
| 108 | + |
| 109 | + return LaunchboxRom({k: v for k, v in rom.items() if v}) # type: ignore[misc] |
| 110 | + |
| 111 | + async def get_rom_by_id(self, database_id: int) -> LaunchboxRom: |
| 112 | + if not LAUNCHBOX_API_ENABLED: |
| 113 | + return LaunchboxRom() |
| 114 | + |
| 115 | + metadata_database_index_entry = await async_cache.hget( |
| 116 | + LAUNCHBOX_METADATA_DATABASE_ID_KEY, str(database_id) |
| 117 | + ) |
| 118 | + |
| 119 | + if not metadata_database_index_entry: |
| 120 | + return LaunchboxRom() |
| 121 | + |
| 122 | + rom = { |
| 123 | + "name": metadata_database_index_entry["Name"], |
| 124 | + "summary": metadata_database_index_entry.get("Overview", ""), |
| 125 | + "launchbox_metadata": extract_metadata_from_launchbox_rom( |
| 126 | + metadata_database_index_entry |
| 127 | + ), |
| 128 | + } |
| 129 | + |
| 130 | + return LaunchboxRom({k: v for k, v in rom.items() if v}) # type: ignore[misc] |
| 131 | + |
| 132 | + async def get_matched_rom_by_id(self, database_id: int) -> LaunchboxRom | None: |
| 133 | + if not LAUNCHBOX_API_ENABLED: |
| 134 | + return None |
| 135 | + |
| 136 | + return await self.get_rom_by_id(database_id) |
| 137 | + |
| 138 | + |
| 139 | +class SlugToLaunchboxPlatformName(TypedDict): |
| 140 | + id: int |
| 141 | + name: str |
| 142 | + |
| 143 | + |
| 144 | +SLUG_TO_LAUNCHBOX_PLATFORM_NAME = { |
| 145 | + "3do": "3DO Interactive Multiplayer", |
| 146 | + "apf": "APF Imagination Machine", |
| 147 | + "pegasus": "Aamber Pegasus", |
| 148 | + "acorn-archimedes": "Acorn Archimedes", |
| 149 | + "atom": "Acorn Atom", |
| 150 | + "acorn-electron": "Acorn Electron", |
| 151 | + "acpc": "Amstrad CPC", |
| 152 | + "gx4000": "Amstrad GX4000", |
| 153 | + "android": "Android", |
| 154 | + "bk-01": "Apogee BK-01", |
| 155 | + "apple2": "Apple II", |
| 156 | + "apple2gs": "Apple IIGS", |
| 157 | + "mac": "Apple Mac OS", |
| 158 | + "ios": "Apple iOS", |
| 159 | + "arcade": "Arcade", |
| 160 | + "atari2600": "Atari 2600", |
| 161 | + "atari5200": "Atari 5200", |
| 162 | + "atari7800": "Atari 7800", |
| 163 | + "atari800": "Atari 800", |
| 164 | + "jaguar": "Atari Jaguar", |
| 165 | + "atari-jaguar-cd": "Atari Jaguar CD", |
| 166 | + "lynx": "Atari Lynx", |
| 167 | + "atari-st": "Atari ST", |
| 168 | + "atari-xegs": "Atari XEGS", |
| 169 | + "bbcmicro": "BBC Microcomputer System", |
| 170 | + "astrocade": "Bally Astrocade", |
| 171 | + "super-vision-8000": "Bandai Super Vision 8000", |
| 172 | + "camputers-lynx": "Camputers Lynx", |
| 173 | + "casio-loopy": "Casio Loopy", |
| 174 | + "casio-pv-1000": "Casio PV-1000", |
| 175 | + "colecoadam": "Coleco ADAM", |
| 176 | + "colecovision": "ColecoVision", |
| 177 | + "c128": "Commodore 128", |
| 178 | + "c64": "Commodore 64", |
| 179 | + "amiga": "Commodore Amiga", |
| 180 | + "amiga-cd32": "Commodore Amiga CD32", |
| 181 | + "commodore-cdtv": "Commodore CDTV", |
| 182 | + "cpet": "Commodore PET", |
| 183 | + "c-plus-4": "Commodore Plus 4", |
| 184 | + "vic-20": "Commodore VIC-20", |
| 185 | + "dragon-32-slash-64": "Dragon 32/64", |
| 186 | + "colour-genie": "EACA EG2000 Colour Genie", |
| 187 | + "bk": "Elektronika BK", |
| 188 | + "arcadia-2001": "Emerson Arcadia 2001", |
| 189 | + "enterprise": "Enterprise", |
| 190 | + "adventure-vision": "Entex Adventure Vision", |
| 191 | + "epoch-game-pocket-computer": "Epoch Game Pocket Computer", |
| 192 | + "epoch-super-cassette-vision": "Epoch Super Cassette Vision", |
| 193 | + "exelvision": "Exelvision EXL 100", |
| 194 | + "exidy-sorcerer": "Exidy Sorcerer", |
| 195 | + "fairchild-channel-f": "Fairchild Channel F", |
| 196 | + "fm-towns": "Fujitsu FM Towns Marty", |
| 197 | + "fm-7": "Fujitsu FM-7", |
| 198 | + "super-acan": "Funtech Super Acan", |
| 199 | + "": "GCE Vectrex", |
| 200 | + "": "Game Wave Family Entertainment System", |
| 201 | + "": "GamePark GP32", |
| 202 | + "": "GameWave", |
| 203 | + "": "Hartung Game Master", |
| 204 | + "": "Hector HRX", |
| 205 | + "vc-4000": "Interton VC 4000", |
| 206 | + "": "Jupiter Ace", |
| 207 | + "": "Linux", |
| 208 | + "": "MS-DOS", |
| 209 | + "": "MUGEN", |
| 210 | + "": "Magnavox Odyssey", |
| 211 | + "": "Magnavox Odyssey 2", |
| 212 | + "": "Matra and Hachette Alice", |
| 213 | + "": "Mattel Aquarius", |
| 214 | + "": "Mattel HyperScan", |
| 215 | + "": "Mattel Intellivision", |
| 216 | + "": "Mega Duck", |
| 217 | + "": "Memotech MTX512", |
| 218 | + "": "Microsoft MSX", |
| 219 | + "": "Microsoft MSX2", |
| 220 | + "": "Microsoft MSX2+", |
| 221 | + "": "Microsoft Xbox", |
| 222 | + "": "Microsoft Xbox 360", |
| 223 | + "": "Microsoft Xbox One", |
| 224 | + "": "Microsoft Xbox Series X/S", |
| 225 | + "": "NEC PC-8801", |
| 226 | + "": "NEC PC-9801", |
| 227 | + "": "NEC PC-FX", |
| 228 | + "": "NEC TurboGrafx-16", |
| 229 | + "": "NEC TurboGrafx-CD", |
| 230 | + "": "Namco System 22", |
| 231 | + "": "Nintendo 3DS", |
| 232 | + "": "Nintendo 64", |
| 233 | + "": "Nintendo 64DD", |
| 234 | + "": "Nintendo DS", |
| 235 | + "": "Nintendo Entertainment System", |
| 236 | + "": "Nintendo Famicom Disk System", |
| 237 | + "": "Nintendo Game & Watch", |
| 238 | + "": "Nintendo Game Boy", |
| 239 | + "": "Nintendo Game Boy Advance", |
| 240 | + "": "Nintendo Game Boy Color", |
| 241 | + "": "Nintendo GameCube", |
| 242 | + "": "Nintendo Pokemon Mini", |
| 243 | + "": "Nintendo Satellaview", |
| 244 | + "": "Nintendo Switch", |
| 245 | + "": "Nintendo Switch 2", |
| 246 | + "": "Nintendo Virtual Boy", |
| 247 | + "": "Nintendo Wii", |
| 248 | + "": "Nintendo Wii U", |
| 249 | + "": "Nokia N-Gage", |
| 250 | + "": "Nuon", |
| 251 | + "": "OpenBOR", |
| 252 | + "": "Oric Atmos", |
| 253 | + "": "Othello Multivision", |
| 254 | + "": "Ouya", |
| 255 | + "": "PC Engine SuperGrafx", |
| 256 | + "": "PICO-8", |
| 257 | + "": "Philips CD-i", |
| 258 | + "": "Philips VG 5000", |
| 259 | + "": "Philips Videopac+", |
| 260 | + "": "Pinball", |
| 261 | + "": "RCA Studio II", |
| 262 | + "": "SAM Coupé", |
| 263 | + "": "SNK Neo Geo AES", |
| 264 | + "": "SNK Neo Geo CD", |
| 265 | + "": "SNK Neo Geo MVS", |
| 266 | + "": "SNK Neo Geo Pocket", |
| 267 | + "": "SNK Neo Geo Pocket Color", |
| 268 | + "": "Sammy Atomiswave", |
| 269 | + "": "ScummVM", |
| 270 | + "": "Sega 32X", |
| 271 | + "": "Sega CD", |
| 272 | + "": "Sega CD 32X", |
| 273 | + "": "Sega Dreamcast", |
| 274 | + "": "Sega Dreamcast VMU", |
| 275 | + "": "Sega Game Gear", |
| 276 | + "": "Sega Genesis", |
| 277 | + "": "Sega Hikaru", |
| 278 | + "": "Sega Master System", |
| 279 | + "": "Sega Model 1", |
| 280 | + "": "Sega Model 2", |
| 281 | + "": "Sega Model 3", |
| 282 | + "": "Sega Naomi", |
| 283 | + "": "Sega Naomi 2", |
| 284 | + "": "Sega Pico", |
| 285 | + "": "Sega SC-3000", |
| 286 | + "": "Sega SG-1000", |
| 287 | + "": "Sega ST-V", |
| 288 | + "": "Sega Saturn", |
| 289 | + "": "Sega System 16", |
| 290 | + "": "Sega System 32", |
| 291 | + "": "Sega Triforce", |
| 292 | + "": "Sharp MZ-2500", |
| 293 | + "": "Sharp X1", |
| 294 | + "": "Sharp X68000", |
| 295 | + "": "Sinclair ZX Spectrum", |
| 296 | + "": "Sinclair ZX-81", |
| 297 | + "": "Sony PSP", |
| 298 | + "": "Sony PSP Minis", |
| 299 | + "": "Sony Playstation", |
| 300 | + "": "Sony Playstation 2", |
| 301 | + "": "Sony Playstation 3", |
| 302 | + "": "Sony Playstation 4", |
| 303 | + "": "Sony Playstation 5", |
| 304 | + "": "Sony Playstation Vita", |
| 305 | + "": "Sony PocketStation", |
| 306 | + "": "Sord M5", |
| 307 | + "": "Spectravideo", |
| 308 | + "": "Super Nintendo Entertainment System", |
| 309 | + "": "TRS-80 Color Computer", |
| 310 | + "": "Taito Type X", |
| 311 | + "": "Tandy TRS-80", |
| 312 | + "": "Tapwave Zodiac", |
| 313 | + "": "Texas Instruments TI 99/4A", |
| 314 | + "": "Tiger Game.com", |
| 315 | + "": "Tomy Tutor", |
| 316 | + "": "VTech CreatiVision", |
| 317 | + "": "VTech Socrates", |
| 318 | + "": "VTech V.Smile", |
| 319 | + "": "Vector-06C", |
| 320 | + "": "Watara Supervision", |
| 321 | + "": "Web Browser", |
| 322 | + "": "Windows", |
| 323 | + "": "Windows 3.X", |
| 324 | + "": "WoW Action Max", |
| 325 | + "": "WonderSwan", |
| 326 | + "": "WonderSwan Color", |
| 327 | + "": "XaviXPORT", |
| 328 | + "": "ZiNc", |
| 329 | +} |
| 330 | + |
| 331 | +# Reverse lookup |
| 332 | +LAUNCHBOX_PLATFORM_NAME_TO_SLUG = { |
| 333 | + v["id"]: k for k, v in SLUG_TO_LAUNCHBOX_PLATFORM_NAME.items() |
| 334 | +} |
0 commit comments