mirror of
https://github.com/rommapp/romm.git
synced 2026-02-18 23:42:07 +01:00
Add a new service adapter for the MobyGames API, to separate concerns with RomM's handler for metadata. This adapter is agnostic to the handler and only provides methods to interact with the API, and correctly return typed responses.
685 lines
30 KiB
Python
685 lines
30 KiB
Python
import re
|
|
from typing import Final, NotRequired, TypedDict
|
|
from urllib.parse import quote
|
|
|
|
from adapters.services.mobygames import MobyGamesService
|
|
from adapters.services.mobygames_types import MobyGame
|
|
from config import MOBYGAMES_API_KEY
|
|
from unidecode import unidecode as uc
|
|
|
|
from .base_hander import (
|
|
PS2_OPL_REGEX,
|
|
SONY_SERIAL_REGEX,
|
|
SWITCH_PRODUCT_ID_REGEX,
|
|
SWITCH_TITLEDB_REGEX,
|
|
MetadataHandler,
|
|
)
|
|
|
|
# Used to display the Mobygames API status in the frontend
|
|
MOBY_API_ENABLED: Final = bool(MOBYGAMES_API_KEY)
|
|
|
|
PS1_MOBY_ID: Final = 6
|
|
PS2_MOBY_ID: Final = 7
|
|
PSP_MOBY_ID: Final = 46
|
|
SWITCH_MOBY_ID: Final = 203
|
|
ARCADE_MOBY_IDS: Final = [143, 36]
|
|
|
|
|
|
class MobyGamesPlatform(TypedDict):
|
|
slug: str
|
|
moby_id: int | None
|
|
name: NotRequired[str]
|
|
|
|
|
|
class MobyMetadataPlatform(TypedDict):
|
|
moby_id: int
|
|
name: str
|
|
|
|
|
|
class MobyMetadata(TypedDict):
|
|
moby_score: str
|
|
genres: list[str]
|
|
alternate_titles: list[str]
|
|
platforms: list[MobyMetadataPlatform]
|
|
|
|
|
|
class MobyGamesRom(TypedDict):
|
|
moby_id: int | None
|
|
name: NotRequired[str]
|
|
summary: NotRequired[str]
|
|
url_cover: NotRequired[str]
|
|
url_screenshots: NotRequired[list[str]]
|
|
moby_metadata: NotRequired[MobyMetadata]
|
|
|
|
|
|
def extract_metadata_from_moby_rom(rom: MobyGame) -> MobyMetadata:
|
|
return MobyMetadata(
|
|
{
|
|
"moby_score": str(rom.get("moby_score", "")),
|
|
"genres": [genre["genre_name"] for genre in rom.get("genres", [])],
|
|
"alternate_titles": [
|
|
alt["title"] for alt in rom.get("alternate_titles", [])
|
|
],
|
|
"platforms": [
|
|
{
|
|
"moby_id": p["platform_id"],
|
|
"name": p["platform_name"],
|
|
}
|
|
for p in rom.get("platforms", [])
|
|
],
|
|
}
|
|
)
|
|
|
|
|
|
class MobyGamesHandler(MetadataHandler):
|
|
def __init__(self) -> None:
|
|
self.moby_service = MobyGamesService()
|
|
|
|
async def _search_rom(
|
|
self, search_term: str, platform_moby_id: int
|
|
) -> MobyGame | None:
|
|
if not platform_moby_id:
|
|
return None
|
|
|
|
search_term = uc(search_term)
|
|
roms = await self.moby_service.list_games(
|
|
platform_ids=[platform_moby_id],
|
|
title=quote(search_term, safe="/ "),
|
|
)
|
|
if not roms:
|
|
return None
|
|
|
|
# Find an exact match.
|
|
search_term_casefold = search_term.casefold()
|
|
search_term_normalized = self._normalize_exact_match(search_term)
|
|
for rom in roms:
|
|
if (
|
|
rom["title"].casefold() == search_term_casefold
|
|
or self._normalize_exact_match(rom["title"]) == search_term_normalized
|
|
):
|
|
return rom
|
|
|
|
return roms[0]
|
|
|
|
def get_platform(self, slug: str) -> MobyGamesPlatform:
|
|
platform = MOBYGAMES_PLATFORM_LIST.get(slug, None)
|
|
|
|
if not platform:
|
|
return MobyGamesPlatform(moby_id=None, slug=slug)
|
|
|
|
return MobyGamesPlatform(
|
|
moby_id=platform["id"],
|
|
slug=slug,
|
|
name=platform["name"],
|
|
)
|
|
|
|
async def get_rom(self, fs_name: str, platform_moby_id: int) -> MobyGamesRom:
|
|
from handler.filesystem import fs_rom_handler
|
|
|
|
if not MOBY_API_ENABLED:
|
|
return MobyGamesRom(moby_id=None)
|
|
|
|
if not platform_moby_id:
|
|
return MobyGamesRom(moby_id=None)
|
|
|
|
search_term = fs_rom_handler.get_file_name_with_no_tags(fs_name)
|
|
fallback_rom = MobyGamesRom(moby_id=None)
|
|
|
|
# Support for PS2 OPL filename format
|
|
match = PS2_OPL_REGEX.match(fs_name)
|
|
if platform_moby_id == PS2_MOBY_ID and match:
|
|
search_term = await self._ps2_opl_format(match, search_term)
|
|
fallback_rom = MobyGamesRom(moby_id=None, name=search_term)
|
|
|
|
# Support for sony serial filename format (PS, PS3, PS3)
|
|
match = SONY_SERIAL_REGEX.search(fs_name, re.IGNORECASE)
|
|
if platform_moby_id == PS1_MOBY_ID and match:
|
|
search_term = await self._ps1_serial_format(match, search_term)
|
|
fallback_rom = MobyGamesRom(moby_id=None, name=search_term)
|
|
|
|
if platform_moby_id == PS2_MOBY_ID and match:
|
|
search_term = await self._ps2_serial_format(match, search_term)
|
|
fallback_rom = MobyGamesRom(moby_id=None, name=search_term)
|
|
|
|
if platform_moby_id == PSP_MOBY_ID and match:
|
|
search_term = await self._psp_serial_format(match, search_term)
|
|
fallback_rom = MobyGamesRom(moby_id=None, name=search_term)
|
|
|
|
# Support for switch titleID filename format
|
|
match = SWITCH_TITLEDB_REGEX.search(fs_name)
|
|
if platform_moby_id == SWITCH_MOBY_ID and match:
|
|
search_term, index_entry = await self._switch_titledb_format(
|
|
match, search_term
|
|
)
|
|
if index_entry:
|
|
fallback_rom = MobyGamesRom(
|
|
moby_id=None,
|
|
name=index_entry["name"],
|
|
summary=index_entry.get("description", ""),
|
|
url_cover=index_entry.get("iconUrl", ""),
|
|
url_screenshots=index_entry.get("screenshots", None) or [],
|
|
)
|
|
|
|
# Support for switch productID filename format
|
|
match = SWITCH_PRODUCT_ID_REGEX.search(fs_name)
|
|
if platform_moby_id == SWITCH_MOBY_ID and match:
|
|
search_term, index_entry = await self._switch_productid_format(
|
|
match, search_term
|
|
)
|
|
if index_entry:
|
|
fallback_rom = MobyGamesRom(
|
|
moby_id=None,
|
|
name=index_entry["name"],
|
|
summary=index_entry.get("description", ""),
|
|
url_cover=index_entry.get("iconUrl", ""),
|
|
url_screenshots=index_entry.get("screenshots", None) or [],
|
|
)
|
|
|
|
# Support for MAME arcade filename format
|
|
if platform_moby_id in ARCADE_MOBY_IDS:
|
|
search_term = await self._mame_format(search_term)
|
|
fallback_rom = MobyGamesRom(moby_id=None, name=search_term)
|
|
|
|
search_term = self.normalize_search_term(search_term)
|
|
res = await self._search_rom(search_term, platform_moby_id)
|
|
|
|
# Split the search term since mobygames search doesn't support special caracters
|
|
if not res and ":" in search_term:
|
|
for term in search_term.split(":")[::-1]:
|
|
res = await self._search_rom(term, platform_moby_id)
|
|
if res:
|
|
break
|
|
|
|
# Some MAME games have two titles split by a slash
|
|
if not res and "/" in search_term:
|
|
for term in search_term.split("/"):
|
|
res = await self._search_rom(term.strip(), platform_moby_id)
|
|
if res:
|
|
break
|
|
|
|
if not res:
|
|
return fallback_rom
|
|
|
|
rom = {
|
|
"moby_id": res["game_id"],
|
|
"name": res["title"],
|
|
"summary": res.get("description", ""),
|
|
"url_cover": res.get("sample_cover", {}).get("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}) # type: ignore[misc]
|
|
|
|
async def get_rom_by_id(self, moby_id: int) -> MobyGamesRom:
|
|
if not MOBY_API_ENABLED:
|
|
return MobyGamesRom(moby_id=None)
|
|
|
|
roms = await self.moby_service.list_games(game_id=moby_id)
|
|
if not roms:
|
|
return MobyGamesRom(moby_id=None)
|
|
|
|
res = roms[0]
|
|
rom = {
|
|
"moby_id": res["game_id"],
|
|
"name": res["title"],
|
|
"summary": res.get("description", None),
|
|
"url_cover": res.get("sample_cover", {}).get("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}) # type: ignore[misc]
|
|
|
|
async def get_matched_rom_by_id(self, moby_id: int) -> MobyGamesRom | None:
|
|
if not MOBY_API_ENABLED:
|
|
return None
|
|
|
|
rom = await self.get_rom_by_id(moby_id)
|
|
return rom if rom["moby_id"] else None
|
|
|
|
async def get_matched_roms_by_name(
|
|
self, search_term: str, platform_moby_id: int | None
|
|
) -> list[MobyGamesRom]:
|
|
if not MOBY_API_ENABLED:
|
|
return []
|
|
|
|
if not platform_moby_id:
|
|
return []
|
|
|
|
search_term = uc(search_term)
|
|
matched_roms = await self.moby_service.list_games(
|
|
platform_ids=[platform_moby_id],
|
|
title=quote(search_term, safe="/ "),
|
|
)
|
|
|
|
return [
|
|
MobyGamesRom(
|
|
{ # type: ignore[misc]
|
|
k: v
|
|
for k, v in {
|
|
"moby_id": rom["game_id"],
|
|
"name": rom["title"],
|
|
"summary": rom.get("description", ""),
|
|
"url_cover": rom.get("sample_cover", {}).get("image", ""),
|
|
"url_screenshots": [
|
|
s["image"] for s in rom.get("sample_screenshots", [])
|
|
],
|
|
"moby_metadata": extract_metadata_from_moby_rom(rom),
|
|
}.items()
|
|
if v
|
|
}
|
|
)
|
|
for rom in matched_roms
|
|
]
|
|
|
|
|
|
class SlugToMobyId(TypedDict):
|
|
id: int
|
|
name: str
|
|
|
|
|
|
MOBYGAMES_PLATFORM_LIST: dict[str, SlugToMobyId] = {
|
|
"1292-advanced-programmable-video-system": {
|
|
"id": 253,
|
|
"name": "1292 Advanced Programmable Video System",
|
|
},
|
|
"3do": {"id": 35, "name": "3DO"},
|
|
"abc-80": {"id": 318, "name": "ABC 80"},
|
|
"apf": {"id": 213, "name": "APF MP1000/Imagination Machine"},
|
|
"acorn-32-bit": {"id": 117, "name": "Acorn 32-bit"},
|
|
"acorn-archimedes": {"id": 117, "name": "Acorn Archimedes"}, # IGDB
|
|
"adventure-vision": {"id": 210, "name": "Adventure Vision"},
|
|
"airconsole": {"id": 305, "name": "AirConsole"},
|
|
"alice-3290": {"id": 194, "name": "Alice 32/90"},
|
|
"altair-680": {"id": 265, "name": "Altair 680"},
|
|
"altair-8800": {"id": 222, "name": "Altair 8800"},
|
|
"amazon-alexa": {"id": 237, "name": "Amazon Alexa"},
|
|
"amiga": {"id": 19, "name": "Amiga"},
|
|
"amiga-cd32": {"id": 56, "name": "Amiga CD32"},
|
|
"cpc": {"id": 60, "name": "Amstrad CPC"},
|
|
"acpc": {"id": 60, "name": "Amstrad CPC"}, # IGDB
|
|
"amstrad-pcw": {"id": 136, "name": "Amstrad PCW"},
|
|
"android": {"id": 91, "name": "Android"},
|
|
"antstream": {"id": 286, "name": "Antstream"},
|
|
"apple-i": {"id": 245, "name": "Apple I"},
|
|
"apple2": {"id": 31, "name": "Apple II"},
|
|
"appleii": {"id": 31, "name": "Apple II"}, # IGDB
|
|
"apple2gs": {"id": 51, "name": "Apple IIGD"},
|
|
"apple-iigs": {"id": 51, "name": "Apple IIGD"}, # IGDB
|
|
"arcade": {"id": 143, "name": "Arcade"},
|
|
"arcadia-2001": {"id": 162, "name": "Arcadia 2001"},
|
|
"arduboy": {"id": 215, "name": "Arduboy"},
|
|
"astral-2000": {"id": 241, "name": "Astral 2000"},
|
|
"atari-2600": {"id": 28, "name": "Atari 2600"},
|
|
"atari2600": {"id": 28, "name": "Atari 2600"}, # IGDB
|
|
"atari-5200": {"id": 33, "name": "Atari 5200"},
|
|
"atari5200": {"id": 33, "name": "Atari 5200"}, # IGDB
|
|
"atari-7800": {"id": 34, "name": "Atari 7800"},
|
|
"atari7800": {"id": 34, "name": "Atari 7800"}, # IGDB
|
|
"atari-8-bit": {"id": 39, "name": "Atari 8-bit"},
|
|
"atari8bit": {"id": 39, "name": "Atari 8-bit"}, # IGDB
|
|
"atari-st": {"id": 24, "name": "Atari ST"},
|
|
"atari-vcs": {"id": 319, "name": "Atari VCS"},
|
|
"atom": {"id": 129, "name": "Atom"},
|
|
"bbc-micro": {"id": 92, "name": "BBC Micro"},
|
|
"bbcmicro": {"id": 92, "name": "BBC Micro"}, # IGDB
|
|
"brew": {"id": 63, "name": "BREW"},
|
|
"bally-astrocade": {"id": 160, "name": "Bally Astrocade"},
|
|
"astrocade": {"id": 160, "name": "Bally Astrocade"}, # IGDB
|
|
"beos": {"id": 165, "name": "BeOS"},
|
|
"blackberry": {"id": 90, "name": "BlackBerry"},
|
|
"blacknut": {"id": 290, "name": "Blacknut"},
|
|
"blu-ray-disc-player": {"id": 168, "name": "Blu-ray Player"},
|
|
"blu-ray-player": {"id": 169, "name": "Blu-ray Player"}, # IGDB
|
|
"browser": {"id": 84, "name": "Browser"},
|
|
"bubble": {"id": 231, "name": "Bubble"},
|
|
"cd-i": {"id": 73, "name": "CD-i"},
|
|
"philips-cd-i": {"id": 73, "name": "CD-i"}, # IGDB
|
|
"cdtv": {"id": 83, "name": "CDTV"},
|
|
"commodore-cdtv": {"id": 83, "name": "CDTV"}, # IGDB
|
|
"fred-cosmac": {"id": 216, "name": "COSMAC"},
|
|
"camputers-lynx": {"id": 154, "name": "Camputers Lynx"},
|
|
"cpm": {"id": 261, "name": "CP/M"},
|
|
"casio-loopy": {"id": 124, "name": "Casio Loopy"},
|
|
"casio-pv-1000": {"id": 125, "name": "Casio PV-1000"},
|
|
"casio-programmable-calculator": {
|
|
"id": 306,
|
|
"name": "Casio Programmable Calculator",
|
|
},
|
|
"champion-2711": {"id": 298, "name": "Champion 2711"},
|
|
"channel-f": {"id": 76, "name": "Channel F"},
|
|
"fairchild-channel-f": {"id": 76, "name": "Channel F"}, # IGDB
|
|
"clickstart": {"id": 188, "name": "ClickStart"},
|
|
"colecoadam": {"id": 156, "name": "Coleco Adam"},
|
|
"colecovision": {"id": 29, "name": "ColecoVision"},
|
|
"colour-genie": {"id": 197, "name": "Colour Genie"},
|
|
"c128": {"id": 61, "name": "Commodore 128"},
|
|
"commodore-16-plus4": {"id": 115, "name": "Commodore 16, Plus/4"},
|
|
"c-plus-4": {"id": 115, "name": "Commodore Plus/4"}, # IGDB
|
|
"c16": {"id": 115, "name": "Commodore 16"}, # IGDB
|
|
"c64": {"id": 27, "name": "Commodore 64"},
|
|
"pet": {"id": 77, "name": "Commodore PET/CBM"},
|
|
"cpet": {"id": 77, "name": "Commodore PET/CBM"}, # IGDB
|
|
"compal-80": {"id": 277, "name": "Compal 80"},
|
|
"compucolor-i": {"id": 243, "name": "Compucolor I"},
|
|
"compucolor-ii": {"id": 198, "name": "Compucolor II"},
|
|
"compucorp-programmable-calculator": {
|
|
"id": 238,
|
|
"name": "Compucorp Programmable Calculator",
|
|
},
|
|
"creativision": {"id": 212, "name": "CreatiVision"},
|
|
"cybervision": {"id": 301, "name": "Cybervision"},
|
|
"dos": {"id": 2, "name": "DOS"},
|
|
"dvd-player": {"id": 166, "name": "DVD Player"},
|
|
"danger-os": {"id": 285, "name": "Danger OS"},
|
|
"dedicated-console": {"id": 204, "name": "Dedicated console"},
|
|
"dedicated-handheld": {"id": 205, "name": "Dedicated handheld"},
|
|
"didj": {"id": 184, "name": "Didj"},
|
|
"doja": {"id": 72, "name": "DoJa"},
|
|
"dragon-3264": {"id": 79, "name": "Dragon 32/64"},
|
|
"dragon-32-slash-64": {"id": 79, "name": "Dragon 32/64"}, # IGDB
|
|
"dreamcast": {"id": 8, "name": "Dreamcast"},
|
|
"dc": {"id": 8, "name": "Dreamcast"}, # IGDB
|
|
"ecd-micromind": {"id": 269, "name": "ECD Micromind"},
|
|
"electron": {"id": 93, "name": "Electron"},
|
|
"acorn-electron": {"id": 93, "name": "Electron"}, # IGDB
|
|
"enterprise": {"id": 161, "name": "Enterprise"},
|
|
"epoch-cassette-vision": {"id": 137, "name": "Epoch Cassette Vision"},
|
|
"epoch-game-pocket-computer": {"id": 139, "name": "Epoch Game Pocket Computer"},
|
|
"epoch-super-cassette-vision": {"id": 138, "name": "Epoch Super Cassette Vision"},
|
|
"evercade": {"id": 284, "name": "Evercade"},
|
|
"exen": {"id": 70, "name": "ExEn"},
|
|
"exelvision": {"id": 195, "name": "Exelvision"},
|
|
"exidy-sorcerer": {"id": 176, "name": "Exidy Sorcerer"},
|
|
"fmtowns": {"id": 102, "name": "FM Towns"},
|
|
"fm-towns": {"id": 102, "name": "FM Towns"}, # IGDB
|
|
"fm-7": {"id": 126, "name": "FM-7"},
|
|
"mobile-custom": {"id": 315, "name": "Feature phone"},
|
|
"fire-os": {"id": 159, "name": "Fire OS"},
|
|
"amazon-fire-tv": {"id": 159, "name": "Fire TV"},
|
|
"freebox": {"id": 268, "name": "Freebox"},
|
|
"g-and-w": {"id": 205, "name": "Dedicated handheld"}, # IGDB (Game & Watch)
|
|
"g-cluster": {"id": 302, "name": "G-cluster"},
|
|
"gimini": {"id": 251, "name": "GIMINI"},
|
|
"gnex": {"id": 258, "name": "GNEX"},
|
|
"gp2x": {"id": 122, "name": "GP2X"},
|
|
"gp2x-wiz": {"id": 123, "name": "GP2X Wiz"},
|
|
"gp32": {"id": 108, "name": "GP32"},
|
|
"gvm": {"id": 257, "name": "GVM"},
|
|
"galaksija": {"id": 236, "name": "Galaksija"},
|
|
"gameboy": {"id": 10, "name": "Game Boy"},
|
|
"gb": {"id": 10, "name": "Game Boy"}, # IGDB
|
|
"gameboy-advance": {"id": 12, "name": "Game Boy Advance"},
|
|
"gba": {"id": 12, "name": "Game Boy Advance"}, # IGDB
|
|
"gameboy-color": {"id": 11, "name": "Game Boy Color"},
|
|
"gbc": {"id": 11, "name": "Game Boy Color"}, # IGDB
|
|
"game-gear": {"id": 25, "name": "Game Gear"},
|
|
"gamegear": {"id": 25, "name": "Game Gear"}, # IGDB
|
|
"game-wave": {"id": 104, "name": "Game Wave"},
|
|
"game-com": {"id": 50, "name": "Game.Com"},
|
|
"game-dot-com": {"id": 50, "name": "Game.Com"}, # IGDB
|
|
"gamecube": {"id": 14, "name": "GameCube"},
|
|
"ngc": {"id": 14, "name": "GameCube"}, # IGDB
|
|
"gamestick": {"id": 155, "name": "GameStick"},
|
|
"genesis": {"id": 16, "name": "Genesis/Mega Drive"},
|
|
"genesis-slash-megadrive": {"id": 16, "name": "Genesis/Mega Drive"},
|
|
"gizmondo": {"id": 55, "name": "Gizmondo"},
|
|
"gloud": {"id": 292, "name": "Gloud"},
|
|
"glulx": {"id": 172, "name": "Glulx"},
|
|
"hd-dvd-player": {"id": 167, "name": "HD DVD Player"},
|
|
"hp-9800": {"id": 219, "name": "HP 9800"},
|
|
"hp-programmable-calculator": {"id": 234, "name": "HP Programmable Calculator"},
|
|
"heathzenith": {"id": 262, "name": "Heath/Zenith H8/H89"},
|
|
"heathkit-h11": {"id": 248, "name": "Heathkit H11"},
|
|
"hitachi-s1": {"id": 274, "name": "Hitachi S1"},
|
|
"hugo": {"id": 170, "name": "Hugo"},
|
|
"hyperscan": {"id": 192, "name": "HyperScan"},
|
|
"ibm-5100": {"id": 250, "name": "IBM 5100"},
|
|
"ideal-computer": {"id": 252, "name": "Ideal-Computer"},
|
|
"intel-8008": {"id": 224, "name": "Intel 8008"},
|
|
"intel-8080": {"id": 225, "name": "Intel 8080"},
|
|
"intel-8086": {"id": 317, "name": "Intel 8086 / 8088"},
|
|
"intellivision": {"id": 30, "name": "Intellivision"},
|
|
"interact-model-one": {"id": 295, "name": "Interact Model One"},
|
|
"interton-video-2000": {"id": 221, "name": "Interton Video 2000"},
|
|
"j2me": {"id": 64, "name": "J2ME"},
|
|
"jaguar": {"id": 17, "name": "Jaguar"},
|
|
"jolt": {"id": 247, "name": "Jolt"},
|
|
"jupiter-ace": {"id": 153, "name": "Jupiter Ace"},
|
|
"kim-1": {"id": 226, "name": "KIM-1"},
|
|
"kaios": {"id": 313, "name": "KaiOS"},
|
|
"kindle": {"id": 145, "name": "Kindle Classic"},
|
|
"laser200": {"id": 264, "name": "Laser 200"},
|
|
"laseractive": {"id": 163, "name": "LaserActive"},
|
|
"leapfrog-explorer": {"id": 185, "name": "LeapFrog Explorer"},
|
|
"leapster-explorer-slash-leadpad-explorer": {
|
|
"id": 186,
|
|
"name": "Leapster Explorer/LeapPad Explorer",
|
|
},
|
|
"leaptv": {"id": 186, "name": "LeapTV"},
|
|
"leapster": {"id": 183, "name": "Leapster"},
|
|
"linux": {"id": 1, "name": "Linux"},
|
|
"luna": {"id": 297, "name": "Luna"},
|
|
"lynx": {"id": 18, "name": "Lynx"},
|
|
"mos-technology-6502": {"id": 240, "name": "MOS Technology 6502"},
|
|
"mre": {"id": 229, "name": "MRE"},
|
|
"msx": {"id": 57, "name": "MSX"},
|
|
"macintosh": {"id": 74, "name": "Macintosh"},
|
|
"mac": {"id": 74, "name": "Macintosh"}, # IGDB
|
|
"maemo": {"id": 157, "name": "Maemo"},
|
|
"mainframe": {"id": 208, "name": "Mainframe"},
|
|
"matsushitapanasonic-jr": {"id": 307, "name": "Matsushita/Panasonic JR"},
|
|
"mattel-aquarius": {"id": 135, "name": "Mattel Aquarius"},
|
|
"meego": {"id": 158, "name": "MeeGo"},
|
|
"memotech-mtx": {"id": 148, "name": "Memotech MTX"},
|
|
"meritum": {"id": 311, "name": "Meritum"},
|
|
"microbee": {"id": 200, "name": "Microbee"},
|
|
"microtan-65": {"id": 232, "name": "Microtan 65"},
|
|
"microvision": {"id": 97, "name": "Microvision"},
|
|
"microvision--1": {"id": 97, "name": "Microvision"}, # IGDB
|
|
"mophun": {"id": 71, "name": "Mophun"},
|
|
"motorola-6800": {"id": 235, "name": "Motorola 6800"},
|
|
"motorola-68k": {"id": 275, "name": "Motorola 68k"},
|
|
"ngage": {"id": 32, "name": "N-Gage"},
|
|
"ngage2": {"id": 89, "name": "N-Gage (service)"},
|
|
"nes": {"id": 22, "name": "NES"},
|
|
"famicom": {"id": 22, "name": "Family Computer"},
|
|
"nascom": {"id": 175, "name": "Nascom"},
|
|
"neo-geo": {"id": 36, "name": "Neo Geo"},
|
|
"neogeoaes": {"id": 36, "name": "Neo Geo"}, # IGDB
|
|
"neogeomvs": {"id": 36, "name": "Neo Geo"}, # IGDB
|
|
"neo-geo-cd": {"id": 54, "name": "Neo Geo CD"},
|
|
"neo-geo-pocket": {"id": 52, "name": "Neo Geo Pocket"},
|
|
"neo-geo-pocket-color": {"id": 53, "name": "Neo Geo Pocket Color"},
|
|
"neo-geo-x": {"id": 279, "name": "Neo Geo X"},
|
|
"new-nintendo-3ds": {"id": 174, "name": "New Nintendo 3DS"},
|
|
"newbrain": {"id": 177, "name": "NewBrain"},
|
|
"newton": {"id": 207, "name": "Newton"},
|
|
"3ds": {"id": 101, "name": "Nintendo 3DS"},
|
|
"n64": {"id": 9, "name": "Nintendo 64"},
|
|
"nintendo-ds": {"id": 44, "name": "Nintendo DS"},
|
|
"nds": {"id": 44, "name": "Nintendo DS"}, # IGDB
|
|
"nintendo-dsi": {"id": 87, "name": "Nintendo DSi"},
|
|
"switch": {"id": 203, "name": "Nintendo Switch"},
|
|
"northstar": {"id": 266, "name": "North Star"},
|
|
"noval-760": {"id": 244, "name": "Noval 760"},
|
|
"nuon": {"id": 116, "name": "Nuon"},
|
|
"ooparts": {"id": 300, "name": "OOParts"},
|
|
"os2": {"id": 146, "name": "OS/2"},
|
|
"oculus-go": {"id": 218, "name": "Oculus Go"},
|
|
"odyssey": {"id": 75, "name": "Odyssey"},
|
|
"odyssey--1": {"id": 75, "name": "Odyssey"}, # IGDB
|
|
"odyssey-2": {"id": 78, "name": "Odyssey 2"},
|
|
"odyssey-2-slash-videopac-g7000": {"id": 78, "name": "Odyssey 2/Videopac G7000"},
|
|
"ohio-scientific": {"id": 178, "name": "Ohio Scientific"},
|
|
"onlive": {"id": 282, "name": "OnLive"},
|
|
"onlive-game-system": {"id": 282, "name": "OnLive Game System"}, # IGDB
|
|
"orao": {"id": 270, "name": "Orao"},
|
|
"oric": {"id": 111, "name": "Oric"},
|
|
"ouya": {"id": 144, "name": "Ouya"},
|
|
"pc-booter": {"id": 4, "name": "PC Booter"},
|
|
"pc-6001": {"id": 149, "name": "PC-6001"},
|
|
"pc-8000": {"id": 201, "name": "PC-8000"},
|
|
"pc88": {"id": 94, "name": "PC-88"},
|
|
"pc-8800-series": {"id": 94, "name": "PC-8800 Series"}, # IGDB
|
|
"pc98": {"id": 95, "name": "PC-98"},
|
|
"pc-9800-series": {"id": 95, "name": "PC-9800 Series"}, # IGDB
|
|
"pc-fx": {"id": 59, "name": "PC-FX"},
|
|
"pico": {"id": 316, "name": "PICO"},
|
|
"ps-vita": {"id": 105, "name": "PS Vita"},
|
|
"psvita": {"id": 105, "name": "PS Vita"}, # IGDB
|
|
"psp": {"id": 46, "name": "PSP"},
|
|
"palmos": {"id": 65, "name": "Palm OS"},
|
|
"palm-os": {"id": 65, "name": "Palm OS"}, # IGDB
|
|
"pandora": {"id": 308, "name": "Pandora"},
|
|
"pebble": {"id": 304, "name": "Pebble"},
|
|
"philips-vg-5000": {"id": 133, "name": "Philips VG 5000"},
|
|
"photocd": {"id": 272, "name": "Photo CD"},
|
|
"pippin": {"id": 112, "name": "Pippin"},
|
|
"playstation": {"id": 6, "name": "PlayStation"},
|
|
"ps": {"id": 6, "name": "PlayStation"}, # IGDB
|
|
"ps2": {"id": 7, "name": "PlayStation 2"},
|
|
"ps3": {"id": 81, "name": "PlayStation 3"},
|
|
"playstation-4": {"id": 141, "name": "PlayStation 4"},
|
|
"ps4--1": {"id": 141, "name": "PlayStation 4"}, # IGDB
|
|
"playstation-5": {"id": 288, "name": "PlayStation 5"},
|
|
"ps5": {"id": 288, "name": "PlayStation 5"}, # IGDB
|
|
"playstation-now": {"id": 294, "name": "PlayStation Now"},
|
|
"playdate": {"id": 303, "name": "Playdate"},
|
|
"playdia": {"id": 107, "name": "Playdia"},
|
|
"plex-arcade": {"id": 291, "name": "Plex Arcade"},
|
|
"pokitto": {"id": 230, "name": "Pokitto"},
|
|
"pokemon-mini": {"id": 152, "name": "Pokémon Mini"},
|
|
"poly-88": {"id": 249, "name": "Poly-88"},
|
|
"oculus-quest": {"id": 271, "name": "Quest"},
|
|
"rca-studio-ii": {"id": 113, "name": "RCA Studio II"},
|
|
"research-machines-380z": {"id": 309, "name": "Research Machines 380Z"},
|
|
"roku": {"id": 196, "name": "Roku"},
|
|
"sam-coupe": {"id": 120, "name": "SAM Coupé"},
|
|
"scmp": {"id": 255, "name": "SC/MP"},
|
|
"sd-200270290": {"id": 267, "name": "SD-200/270/290"},
|
|
"sega-32x": {"id": 21, "name": "SEGA 32X"},
|
|
"sega32": {"id": 21, "name": "SEGA 32X"}, # IGDB
|
|
"sega-cd": {"id": 20, "name": "SEGA CD"},
|
|
"segacd": {"id": 20, "name": "SEGA CD"}, # IGDB
|
|
"sega-master-system": {"id": 26, "name": "SEGA Master System"},
|
|
"sms": {"id": 26, "name": "SEGA Master System"}, # IGDB
|
|
"sega-pico": {"id": 103, "name": "SEGA Pico"},
|
|
"sega-saturn": {"id": 23, "name": "SEGA Saturn"},
|
|
"saturn": {"id": 23, "name": "SEGA Saturn"}, # IGDB
|
|
"sg-1000": {"id": 114, "name": "SG-1000"},
|
|
"sk-vm": {"id": 259, "name": "SK-VM"},
|
|
"smc-777": {"id": 273, "name": "SMC-777"},
|
|
"snes": {"id": 15, "name": "SNES"},
|
|
"sfam": {"id": 15, "name": "Super Famicom"},
|
|
"sri-5001000": {"id": 242, "name": "SRI-500/1000"},
|
|
"swtpc-6800": {"id": 228, "name": "SWTPC 6800"},
|
|
"sharp-mz-80b20002500": {"id": 182, "name": "Sharp MZ-80B/2000/2500"},
|
|
"sharp-mz-80k7008001500": {"id": 181, "name": "Sharp MZ-80K/700/800/1500"},
|
|
"sharp-x1": {"id": 121, "name": "Sharp X1"},
|
|
"x1": {"id": 121, "name": "Sharp X1"}, # IGDB
|
|
"sharp-x68000": {"id": 106, "name": "Sharp X68000"},
|
|
"sharp-zaurus": {"id": 202, "name": "Sharp Zaurus"},
|
|
"signetics-2650": {"id": 278, "name": "Signetics 2650"},
|
|
"sinclair-ql": {"id": 131, "name": "Sinclair QL"},
|
|
"socrates": {"id": 190, "name": "Socrates"},
|
|
"sol-20": {"id": 199, "name": "Sol-20"},
|
|
"sord-m5": {"id": 134, "name": "Sord M5"},
|
|
"spectravideo": {"id": 85, "name": "Spectravideo"},
|
|
"stadia": {"id": 281, "name": "Stadia"},
|
|
"super-acan": {"id": 110, "name": "Super A'can"},
|
|
"super-vision-8000": {"id": 296, "name": "Super Vision 8000"},
|
|
"supergrafx": {"id": 127, "name": "SuperGrafx"},
|
|
"supervision": {"id": 109, "name": "Supervision"},
|
|
"sure-shot-hd": {"id": 287, "name": "Sure Shot HD"},
|
|
"symbian": {"id": 67, "name": "Symbian"},
|
|
"tads": {"id": 171, "name": "TADS"},
|
|
"ti-programmable-calculator": {"id": 239, "name": "TI Programmable Calculator"},
|
|
"ti-994a": {"id": 47, "name": "TI-99/4A"},
|
|
"ti-99": {"id": 47, "name": "TI-99/4A"}, # IGDB
|
|
"tim": {"id": 246, "name": "TIM"},
|
|
"trs-80": {"id": 58, "name": "TRS-80"},
|
|
"trs-80-coco": {"id": 62, "name": "TRS-80 Color Computer"},
|
|
"trs-80-color-computer": {"id": 62, "name": "TRS-80 Color Computer"}, # IGDB
|
|
"trs-80-mc-10": {"id": 193, "name": "TRS-80 MC-10"},
|
|
"trs-80-model-100": {"id": 312, "name": "TRS-80 Model 100"},
|
|
"taito-x-55": {"id": 283, "name": "Taito X-55"},
|
|
"tatung-einstein": {"id": 150, "name": "Tatung Einstein"},
|
|
"tektronix-4050": {"id": 223, "name": "Tektronix 4050"},
|
|
"tele-spiel": {"id": 220, "name": "Tele-Spiel ES-2201"},
|
|
"telstar-arcade": {"id": 233, "name": "Telstar Arcade"},
|
|
"terminal": {"id": 209, "name": "Terminal"},
|
|
"thomson-mo": {"id": 147, "name": "Thomson MO"},
|
|
"thomson-mo5": {"id": 147, "name": "Thomson MO5"},
|
|
"thomson-to": {"id": 130, "name": "Thomson TO"},
|
|
"tiki-100": {"id": 263, "name": "Tiki 100"},
|
|
"timex-sinclair-2068": {"id": 173, "name": "Timex Sinclair 2068"},
|
|
"tizen": {"id": 206, "name": "Tizen"},
|
|
"tomahawk-f1": {"id": 256, "name": "Tomahawk F1"},
|
|
"tomy-tutor": {"id": 151, "name": "Tomy Tutor"},
|
|
"triton": {"id": 310, "name": "Triton"},
|
|
"turbografx-cd": {"id": 45, "name": "TurboGrafx CD"},
|
|
"turbografx-16-slash-pc-engine-cd": {"id": 45, "name": "TurboGrafx CD"},
|
|
"turbo-grafx": {"id": 40, "name": "TurboGrafx-16"},
|
|
"turbografx16--1": {"id": 40, "name": "TurboGrafx-16"}, # IGDB
|
|
"vflash": {"id": 189, "name": "V.Flash"},
|
|
"vsmile": {"id": 42, "name": "V.Smile"},
|
|
"vic-20": {"id": 43, "name": "VIC-20"},
|
|
"vis": {"id": 164, "name": "VIS"},
|
|
"vectrex": {"id": 37, "name": "Vectrex"},
|
|
"versatile": {"id": 299, "name": "Versatile"},
|
|
"videobrain": {"id": 214, "name": "VideoBrain"},
|
|
"videopac-g7400": {"id": 128, "name": "Videopac+ G7400"},
|
|
"virtual-boy": {"id": 38, "name": "Virtual Boy"},
|
|
"virtualboy": {"id": 38, "name": "Virtual Boy"},
|
|
"wipi": {"id": 260, "name": "WIPI"},
|
|
"wang2200": {"id": 217, "name": "Wang 2200"},
|
|
"wii": {"id": 82, "name": "Wii"},
|
|
"wii-u": {"id": 132, "name": "Wii U"},
|
|
"wiiu": {"id": 132, "name": "Wii U"},
|
|
"windows": {"id": 3, "name": "Windows"},
|
|
"win": {"id": 3, "name": "Windows"}, # IGDB
|
|
"win3x": {"id": 5, "name": "Windows 3.x"},
|
|
"windows-apps": {"id": 140, "name": "Windows Apps"},
|
|
"windowsmobile": {"id": 66, "name": "Windows Mobile"},
|
|
"windows-mobile": {"id": 66, "name": "Windows Mobile"}, # IGDB
|
|
"windows-phone": {"id": 98, "name": "Windows Phone"},
|
|
"winphone": {"id": 98, "name": "Windows Phone"}, # IGDB
|
|
"wonderswan": {"id": 48, "name": "WonderSwan"},
|
|
"wonderswan-color": {"id": 49, "name": "WonderSwan Color"},
|
|
"xavixport": {"id": 191, "name": "XaviXPORT"},
|
|
"xbox": {"id": 13, "name": "Xbox"},
|
|
"xbox360": {"id": 69, "name": "Xbox 360"},
|
|
"xboxcloudgaming": {"id": 293, "name": "Xbox Cloud Gaming"},
|
|
"xbox-one": {"id": 142, "name": "Xbox One"},
|
|
"xboxone": {"id": 142, "name": "Xbox One"},
|
|
"xbox-series": {"id": 289, "name": "Xbox Series"},
|
|
"series-x-s": {"id": 289, "name": "Xbox Series X/S"}, # IGDB
|
|
"xerox-alto": {"id": 254, "name": "Xerox Alto"},
|
|
"z-machine": {"id": 169, "name": "Z-machine"},
|
|
"zx-spectrum": {"id": 41, "name": "ZX Spectrum"},
|
|
"zx-spectrum-next": {"id": 280, "name": "ZX Spectrum Next"},
|
|
"zx80": {"id": 118, "name": "ZX80"},
|
|
"zx81": {"id": 119, "name": "ZX81"},
|
|
"sinclair-zx81": {"id": 119, "name": "ZX81"}, # IGDB
|
|
"zeebo": {"id": 88, "name": "Zeebo"},
|
|
"z80": {"id": 227, "name": "Zilog Z80"},
|
|
"zilog-z8000": {"id": 276, "name": "Zilog Z8000"},
|
|
"zodiac": {"id": 68, "name": "Zodiac"},
|
|
"zune": {"id": 211, "name": "Zune"},
|
|
"bada": {"id": 99, "name": "bada"},
|
|
"digiblast": {"id": 187, "name": "digiBlast"},
|
|
"ipad": {"id": 96, "name": "iPad"},
|
|
"iphone": {"id": 86, "name": "iPhone"},
|
|
"ios": {"id": 86, "name": "iOS"},
|
|
"ipod-classic": {"id": 80, "name": "iPod Classic"},
|
|
"iircade": {"id": 314, "name": "iiRcade"},
|
|
"tvos": {"id": 179, "name": "tvOS"},
|
|
"watchos": {"id": 180, "name": "watchOS"},
|
|
"webos": {"id": 100, "name": "webOS"},
|
|
}
|
|
|
|
# Reverse lookup
|
|
MOBY_ID_TO_SLUG = {v["id"]: k for k, v in MOBYGAMES_PLATFORM_LIST.items()}
|