Files
romm/backend/handler/metadata/hasheous_handler.py
Georges-Antoine Assi 5b3b146039 add log statements
2025-09-21 13:34:03 -04:00

1965 lines
58 KiB
Python

import json
from datetime import datetime
from typing import Any, NotRequired, TypedDict
import httpx
import pydash
from fastapi import HTTPException, status
from config import DEV_MODE, HASHEOUS_API_ENABLED
from logger.logger import log
from models.rom import RomFile
from utils import get_version
from utils.context import ctx_httpx_client
from .base_handler import BaseRom, MetadataHandler
from .base_handler import UniversalPlatformSlug as UPS
from .giantbomb_handler import GiantBombMetadata
from .igdb_handler import (
IGDB_AGE_RATINGS,
IGDBMetadata,
IGDBMetadataPlatform,
)
from .ra_handler import RAMetadata
class HasheousMetadata(TypedDict):
tosec_match: bool
mame_arcade_match: bool
mame_mess_match: bool
nointro_match: bool
redump_match: bool
whdload_match: bool
ra_match: bool
fbneo_match: bool
class HasheousPlatform(TypedDict):
slug: str
hasheous_id: int | None
name: NotRequired[str]
igdb_id: NotRequired[int | None]
tgdb_id: NotRequired[int | None]
ra_id: NotRequired[int | None]
giantbomb_id: NotRequired[int | None]
class HasheousRom(BaseRom):
hasheous_id: int | None
igdb_id: NotRequired[int | None]
slug: NotRequired[str]
igdb_metadata: NotRequired[IGDBMetadata]
ra_id: NotRequired[int | None]
ra_metadata: NotRequired[RAMetadata]
tgdb_id: NotRequired[int | None]
giantbomb_id: NotRequired[int | None]
giantbomb_metadata: NotRequired[GiantBombMetadata]
hasheous_metadata: NotRequired[HasheousMetadata]
ACCEPTABLE_FILE_EXTENSIONS_BY_PLATFORM_SLUG = {"dc": ["cue"]}
def extract_metadata_from_igdb_rom(rom: dict[str, Any]) -> IGDBMetadata:
return IGDBMetadata(
{
"youtube_video_id": (
list(rom["videos"].values())[0]["video_id"]
if rom.get("videos")
else None
),
"total_rating": str(round(rom.get("total_rating", 0.0), 2)),
"aggregated_rating": str(round(rom.get("aggregated_rating", 0.0), 2)),
"first_release_date": (
int(
datetime.fromisoformat(
rom["first_release_date"].replace("Z", "+00:00")
).timestamp()
)
if rom.get("first_release_date")
else None
),
"genres": pydash.map_(rom.get("genres", {}), "name"),
"franchises": pydash.compact(
[rom.get("franchise.name", None)]
+ pydash.map_(rom.get("franchises", {}), "name")
),
"alternative_names": pydash.map_(rom.get("alternative_names", {}), "name"),
"collections": pydash.map_(rom.get("collections", {}), "name"),
"game_modes": pydash.map_(rom.get("game_modes", {}), "name"),
"companies": pydash.compact(
pydash.map_(rom.get("involved_companies", {}), "company.name")
),
"platforms": [
IGDBMetadataPlatform(igdb_id=p.get("id", ""), name=p.get("name", ""))
for p in pydash.map_(rom.get("platforms", {}))
],
"age_ratings": [
IGDB_AGE_RATINGS[r]
for r in pydash.map_(rom.get("age_ratings", {}), "rating_category")
if r in IGDB_AGE_RATINGS
],
"expansions": [],
"dlcs": [],
"ports": [],
"remakes": [],
"remasters": [],
"similar_games": [],
"expanded_games": [],
}
)
# {
# "deck": "A top-down isometric helicopter shoot 'em up originally for the Sega Genesis, which was later ported to a variety of platforms. It is best known for its open-ended mission design and was followed by several sequels.",
# "description": "<h2>Overview</h2><p>Desert Strike: Return to the Gulf is an isometric helicopter shoot 'em up from Electronic Arts. The player is a fighter pilot who must take down mission-critical targets across a number of maps. The player is free to pursue these missions in any order, and must also keep an eye on the fuel, damage, and ammo gauges.</p><p>Desert Strike is the first of the prolific <a href=\"/strike-series/3025-143/\" data-ref-id=\"3025-143\">Strike</a> series, and was followed with <a href=\"/jungle-strike/3030-2180/\" data-ref-id=\"3030-2180\">Jungle Strike</a> and <a href=\"/urban-strike/3030-9336/\" data-ref-id=\"3030-9336\">Urban Strike</a> on the Genesis. Two more games, <a href=\"/soviet-strike/3030-2422/\" data-ref-id=\"3030-2422\">Soviet Strike</a> and <a href=\"/nuclear-strike/3030-11731/\" data-ref-id=\"3030-11731\">Nuclear Strike</a>, were released later for 32-bit systems.</p><h2>Story</h2><p>A year after the Gulf War, General Ibn Kilbaba takes over a small <a href=\"/unnamed-middle-eastern-location/3035-179/\" data-ref-id=\"3035-179\">Arab emirate</a> and plans to start World War III. Using an <a href=\"/ah-64-apache/3055-2463/\" data-ref-id=\"3055-2463\">AH-64 Apache</a>, the player must open the way for ground troops and finally take on the \"Madman\" himself.</p><h2>Gameplay</h2><p>The game is played from an isometric perspective in open levels that allow free movement in all directions by scrolling the screen with the movement of the helicopter. Each level consists of many varying objectives that range anywhere from destroying enemy bases and vehicles to capturing enemy troops or rescuing friendly ones. While bases and vehicles are simply destroyed, both friendly and enemy troops must be taken back to base for extraction. The AH-64 Apache has limited cargo space, so multiple trips to and from the base may be necessary. These objectives aren't always linear, and can often be tackled in whatever order the player chooses. This combination of free movement and non-linear structure separated Desert Strike from many of the other contemporary shooters.</p><p>There are three weapons of varying strength and usefulness available to the AH-64 Apache: machine guns, hydra missiles, and hellfire missiles, which increase in strength respectively. Each of these weapons has a limited number of ammo which can only be replenished by picking up ammo crates on the mission or by resupplying back at the base. Similarly, the AH-64 Apache only has a limited amount of fuel that will drain slowly over the course of each level. If the fuel runs out, the helicopter crashes and the player loses a life. Refueling works exactly the same as restocking ammo.</p><p>Lives are lost when either the AH-64 Apache takes too much damage and is destroyed, or when it runs out of fuel. After three lives have been lost, the game is over. Due to the nature of the game's freedom, each level requires a certain amount of planning and strategy in order to complete all of the objectives while still having enough fuel, ammo, and health to survive.</p><h2>Ports</h2><p>Due to its popularity on the <a href=\"/sega-genesis/3055-4659/\" data-ref-id=\"3055-4659\">Sega Genesis</a> in 1992, the game was then ported to the <a href=\"/amiga/3045-1/\" data-ref-id=\"3045-1\">Amiga</a>, <a href=\"/sega-master-system/3045-8/\" data-ref-id=\"3045-8\">Master System</a>, and <a href=\"/snes/3045-9/\" data-ref-id=\"3045-9\">SNES</a> in the same year. Two years later in 1994, it was released on the <a href=\"/pc/3045-94/\" data-ref-id=\"3045-94\">PC</a>. It was also ported to most handheld systems such as the <a href=\"/lynx/3045-7/\" data-ref-id=\"3045-7\">Lynx</a> in 1993, <a href=\"/game-gear/3045-5/\" data-ref-id=\"3045-5\">Game Gear</a> in 1994, <a href=\"/game-boy/3045-3/\" data-ref-id=\"3045-3\">Game Boy</a> in 1995, <a href=\"/game-boy-advance/3045-4/\" data-ref-id=\"3045-4\">Game Boy Advance</a> in 2002, and finally the <a href=\"/psp/3045-18/\" data-ref-id=\"3045-18\">PSP</a> in 2006 as part of <a href=\"/ea-replay/3030-10965/\" data-ref-id=\"3030-10965\">EA Replay</a>.</p>",
# "expected_release_year": "1992",
# "guid": "3030-1",
# "id": 1,
# "image": {
# "icon_url": "/api/v1/MetadataProxy/GiantBomb/a/uploads/square_avatar/9/93770/2370498-genesis_desertstrike_2__1_.jpg",
# "medium_url": "/api/v1/MetadataProxy/GiantBomb/a/uploads/scale_medium/9/93770/2370498-genesis_desertstrike_2__1_.jpg",
# "screen_url": "/api/v1/MetadataProxy/GiantBomb/a/uploads/screen_medium/9/93770/2370498-genesis_desertstrike_2__1_.jpg",
# "screen_large_url": "/api/v1/MetadataProxy/GiantBomb/a/uploads/screen_kubrick/9/93770/2370498-genesis_desertstrike_2__1_.jpg",
# "small_url": "/api/v1/MetadataProxy/GiantBomb/a/uploads/scale_small/9/93770/2370498-genesis_desertstrike_2__1_.jpg",
# "super_url": "/api/v1/MetadataProxy/GiantBomb/a/uploads/scale_large/9/93770/2370498-genesis_desertstrike_2__1_.jpg",
# "thumb_url": "/api/v1/MetadataProxy/GiantBomb/a/uploads/scale_avatar/9/93770/2370498-genesis_desertstrike_2__1_.jpg",
# "tiny_url": "/api/v1/MetadataProxy/GiantBomb/a/uploads/square_mini/9/93770/2370498-genesis_desertstrike_2__1_.jpg",
# "original_url": "/api/v1/MetadataProxy/GiantBomb/a/uploads/original/9/93770/2370498-genesis_desertstrike_2__1_.jpg"
# },
# "name": "Desert Strike: Return to the Gulf",
# "number_of_user_reviews": "0",
# "original_game_rating": [
# {
# "api_detail_url": "https://www.giantbomb.com/api/game_rating/3065-9/",
# "id": 9,
# "name": "ESRB: K-A"
# },
# {
# "api_detail_url": "https://www.giantbomb.com/api/game_rating/3065-6/",
# "id": 6,
# "name": "ESRB: E"
# }
# ],
# "site_detail_url": "https://www.giantbomb.com/desert-strike-return-to-the-gulf/3030-1/"
# }
def extract_metadata_from_giantbomb_rom(rom: dict) -> GiantBombMetadata:
print(f"GIANTBOMB ROM: {rom}")
return GiantBombMetadata(
{
"guid": rom.get("guid", ""),
"alternative_names": pydash.map_(rom.get("aliases", {}), "name"),
"deck": rom.get("deck", ""),
"description": rom.get("description", ""),
"first_release_date": rom.get("first_release_date", ""),
"image": rom.get("image", {}),
"age_ratings": pydash.map_(rom.get("original_game_rating", {}), "name"),
"site_url": rom.get("site_detail_url", ""),
}
)
class HasheousHandler(MetadataHandler):
def __init__(self) -> None:
self.BASE_URL = (
"https://beta.hasheous.org/api/v1"
if DEV_MODE
else "https://hasheous.org/api/v1"
)
self.platform_endpoint = f"{self.BASE_URL}/Lookup/Platforms"
self.games_endpoint = f"{self.BASE_URL}/Lookup/ByHash"
self.proxy_igdb_game_endpoint = f"{self.BASE_URL}/MetadataProxy/IGDB/Game"
self.proxy_igdb_cover_endpoint = f"{self.BASE_URL}/MetadataProxy/IGDB/Cover"
self.proxy_ra_game_endpoint = f"{self.BASE_URL}/MetadataProxy/RA/Game"
self.proxy_giantbomb_game_endpoint = (
f"{self.BASE_URL}/MetadataProxy/GiantBomb/game"
)
self.app_api_key = (
"UUvh9ef_CddMM4xXO1iqxl9FqEt764v33LU-UiGFc0P34odXjMP9M6MTeE4JZRxZ"
if DEV_MODE
else "JNoFBA-jEh4HbxuxEHM6MVzydKoAXs9eCcp2dvcg5LRCnpp312voiWmjuaIssSzS"
)
@classmethod
def is_enabled(cls) -> bool:
return HASHEOUS_API_ENABLED
async def _request(
self,
url: str,
method: str = "POST",
params: dict | None = None,
data: dict | None = None,
) -> dict:
httpx_client = ctx_httpx_client.get()
# Normalize method to uppercase
method = method.upper()
if method not in ["GET", "POST"]:
raise ValueError(f"Unsupported HTTP method: {method}")
try:
log.debug(
"API request: Method=%s, URL=%s, Params=%s, Data=%s",
method,
url,
params,
data,
)
# Prepare request kwargs
request_kwargs = {
"url": url,
"params": params,
"headers": {
"Content-Type": "application/json-patch+json",
"User-Agent": f"RomM/{get_version()}",
"X-Client-API-Key": self.app_api_key,
},
"timeout": 120,
}
# Add method-specific parameters
if method == "POST":
request_kwargs["json"] = data
# Make the request
res = await httpx_client.request(method, **request_kwargs)
res.raise_for_status()
return res.json()
except httpx.HTTPStatusError as exc:
# Check if its a 404 error
if exc.response.status_code == status.HTTP_404_NOT_FOUND:
log.debug("Game not found in Hasheous API")
return {}
log.error(
"Hasheous API returned an error: %s %s",
exc.response.status_code,
exc.response.text,
)
pass
except httpx.NetworkError as exc:
log.critical("Connection error: can't connect to Hasheous")
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Can't connect to Hasheous, check your internet connection",
) from exc
except json.decoder.JSONDecodeError as exc:
# Log the error and return an empty dict if the response is not valid JSON
log.error(exc)
return {}
except httpx.TimeoutException:
pass
return {}
def get_platform(self, slug: str) -> HasheousPlatform:
if slug not in HASHEOUS_PLATFORM_LIST:
return HasheousPlatform(hasheous_id=None, slug=slug)
platform = HASHEOUS_PLATFORM_LIST[UPS(slug)]
return HasheousPlatform(
hasheous_id=platform["id"],
slug=slug,
name=platform["name"],
igdb_id=platform["igdb_id"],
tgdb_id=platform["tgdb_id"],
ra_id=platform["ra_id"],
giantbomb_id=platform["giantbomb_id"],
)
async def lookup_rom(self, platform_slug: str, files: list[RomFile]) -> HasheousRom:
fallback_rom = HasheousRom(
hasheous_id=None, igdb_id=None, tgdb_id=None, ra_id=None
)
if not self.is_enabled():
return fallback_rom
filtered_files = [
file
for file in files
if file.file_size_bytes is not None
and file.file_size_bytes > 0
and (
file.file_extension
in ACCEPTABLE_FILE_EXTENSIONS_BY_PLATFORM_SLUG[platform_slug]
if platform_slug in ACCEPTABLE_FILE_EXTENSIONS_BY_PLATFORM_SLUG
else True
)
]
first_file = max(filtered_files, key=lambda f: f.file_size_bytes, default=None)
if first_file is None:
return fallback_rom
md5_hash = first_file.md5_hash
sha1_hash = first_file.sha1_hash
crc_hash = first_file.crc_hash
if not (md5_hash or sha1_hash or crc_hash):
log.warning(
"No hashes provided for Hasheous lookup. "
"At least one of md5_hash, sha1_hash, or crc_hash is required."
)
return fallback_rom
data = {}
if md5_hash:
data["mD5"] = md5_hash
if sha1_hash:
data["shA1"] = sha1_hash
if crc_hash:
data["crc"] = crc_hash
hasheous_game = await self._request(
self.games_endpoint,
params={
"returnAllSources": "true",
"returnFields": "Signatures, Metadata, Attributes",
},
data=data,
)
if not hasheous_game:
return fallback_rom
metadata = hasheous_game.get("metadata", [])
attributes = hasheous_game.get("attributes", [])
signatures = hasheous_game.get("signatures", {}).keys()
igdb_id = None
tgdb_id = None
ra_id = None
giantbomb_id = None
for meta in metadata:
if meta["source"] == "IGDB":
try:
# TEMP: Hasheous is slowly replacing slugs with IDs
igdb_id = int(meta["immutableId"])
except (ValueError, TypeError):
log.debug(
f"Found an IGDB slug instead of an ID: {meta['immutableId']}"
)
pass
elif meta["source"] == "TheGamesDB":
tgdb_id = meta["immutableId"]
elif meta["source"] == "RetroAchievements":
ra_id = meta["immutableId"]
elif meta["source"] == "GiantBomb":
giantbomb_id = meta["immutableId"]
url_cover = ""
for attr in attributes:
if attr["attributeName"] == "Logo":
url_cover = f"https://hasheous.org{attr['link']}"
break
return HasheousRom(
hasheous_id=hasheous_game["id"],
name=hasheous_game.get("name", ""),
igdb_id=int(igdb_id) if igdb_id else None,
tgdb_id=int(tgdb_id) if tgdb_id else None,
ra_id=int(ra_id) if ra_id else None,
giantbomb_id=int(giantbomb_id) if giantbomb_id else None,
url_cover=url_cover,
hasheous_metadata=HasheousMetadata(
tosec_match="TOSEC" in signatures,
mame_arcade_match="MAMEArcade" in signatures,
mame_mess_match="MAMEMess" in signatures,
nointro_match="NoIntros" in signatures,
redump_match="Redump" in signatures,
whdload_match="WHDLoad" in signatures,
ra_match="RetroAchievements" in signatures,
fbneo_match="FBNeo" in signatures,
),
)
async def get_igdb_game(self, hasheous_rom: HasheousRom) -> HasheousRom:
if not self.is_enabled():
return hasheous_rom
igdb_id = hasheous_rom.get("igdb_id", None)
if igdb_id is None:
log.info("No IGDB ID provided for Hasheous IGDB game lookup.")
return hasheous_rom
igdb_game = await self._request(
self.proxy_igdb_game_endpoint,
params={
"Id": igdb_id,
"expandColumns": "age_ratings, alternative_names, collections, cover, dlcs, expanded_games, franchise, franchises, game_modes, genres, involved_companies, platforms, ports, remakes, screenshots, similar_games, videos",
},
method="GET",
)
if not igdb_game:
log.debug(f"No Hasheous game found for IGDB ID {igdb_id}.")
return hasheous_rom
return HasheousRom(
{
**hasheous_rom,
"slug": igdb_game.get("slug") or hasheous_rom.get("slug") or "",
"name": igdb_game.get("name") or hasheous_rom.get("name") or "",
"summary": igdb_game.get("summary", ""),
"url_cover": self.normalize_cover_url(
pydash.get(igdb_game, "cover.url", "")
).replace("t_thumb", "t_1080p")
or hasheous_rom.get("url_cover", ""),
"url_screenshots": [
self.normalize_cover_url(s.get("url", "")).replace(
"t_thumb", "t_720p"
)
for s in pydash.get(igdb_game, "screenshots", {}).values()
]
or hasheous_rom.get("url_screenshots", []),
"igdb_metadata": extract_metadata_from_igdb_rom(igdb_game),
}
)
async def get_giantbomb_game(self, hasheous_rom: HasheousRom) -> HasheousRom:
if not self.is_enabled():
return hasheous_rom
giantbomb_id = hasheous_rom.get("giantbomb_id", None)
if giantbomb_id is None:
log.info("No GiantBomb ID provided for Hasheous GiantBomb game lookup.")
return hasheous_rom
giantbomb_game = await self._request(
f"{self.proxy_giantbomb_game_endpoint}/3030-{giantbomb_id}",
params={"field_list": "*", "format": "json"},
method="GET",
)
if not giantbomb_game:
log.debug(f"No Hasheous game found for GiantBomb GUID 3030-{giantbomb_id}.")
return hasheous_rom
log.info(f"GiantBomb game: {giantbomb_game}")
return HasheousRom(
{
**hasheous_rom,
"giantbomb_metadata": extract_metadata_from_giantbomb_rom(
giantbomb_game
),
}
)
async def get_ra_game(self, hasheous_rom: HasheousRom) -> HasheousRom:
if not self.is_enabled():
return hasheous_rom
ra_id = hasheous_rom.get("ra_id", None)
if ra_id is None:
log.info("No RA ID provided for Hasheous RA game lookup.")
return hasheous_rom
ra_game = await self._request(
self.proxy_ra_game_endpoint,
params={"Id": ra_id},
method="GET",
)
if not ra_game:
log.debug(f"No Hasheous game found for RA ID {ra_id}.")
return hasheous_rom
return hasheous_rom
class SlugToHasheousId(TypedDict):
id: int
name: str
igdb_id: int | None
igdb_slug: str | None
tgdb_id: int | None
ra_id: int | None
giantbomb_id: int | None
giantbomb_slug: str | None
HASHEOUS_PLATFORM_LIST: dict[UPS, SlugToHasheousId] = {
UPS._3DO: {
"id": 161825,
"igdb_id": 50,
"igdb_slug": "3do",
"name": "3DO Interactive Multiplayer",
"ra_id": 43,
"tgdb_id": 25,
"giantbomb_id": 26,
"giantbomb_slug": "3do",
},
UPS.N3DS: {
"id": 62,
"igdb_id": 37,
"igdb_slug": "3ds",
"name": "Nintendo 3DS",
"ra_id": 62,
"tgdb_id": 4912,
"giantbomb_id": 117,
"giantbomb_slug": "nintendo-3ds",
},
UPS.N64DD: {
"id": 65,
"igdb_id": 416,
"igdb_slug": "64dd",
"name": "Nintendo 64DD",
"ra_id": None,
"tgdb_id": None,
"giantbomb_id": 101,
"giantbomb_slug": "nintendo-64dd",
},
UPS.APF: {
"id": 61862,
"igdb_id": None,
"igdb_slug": None,
"name": "APF M-1000",
"ra_id": None,
"tgdb_id": 4969,
"giantbomb_id": None,
"giantbomb_slug": None,
},
UPS.C64: {
"id": 1,
"igdb_id": 15,
"igdb_slug": None,
"name": "Commodore 64",
"ra_id": 30,
"tgdb_id": 40,
"giantbomb_id": 14,
"giantbomb_slug": None,
},
UPS.ARCADIA_2001: {
"id": 322291,
"igdb_id": None,
"igdb_slug": None,
"name": "Emerson Arcadia 2001",
"ra_id": None,
"tgdb_id": 4963,
"giantbomb_id": None,
"giantbomb_slug": None,
},
UPS.EPOCH_SUPER_CASSETTE_VISION: {
"id": 299046,
"igdb_id": 376,
"igdb_slug": None,
"name": "Epoch Super Cassette Vision",
"ra_id": None,
"tgdb_id": 4966,
"giantbomb_id": None,
"giantbomb_slug": None,
},
UPS.EXIDY_SORCERER: {
"id": 299154,
"igdb_id": 236,
"igdb_slug": None,
"name": "Exidy Sorcerer",
"ra_id": None,
"tgdb_id": None,
"giantbomb_id": None,
"giantbomb_slug": None,
},
UPS.FDS: {
"id": 69,
"igdb_id": 51,
"igdb_slug": None,
"name": "Family Computer Disk System",
"ra_id": None,
"tgdb_id": None,
"giantbomb_id": 91,
"giantbomb_slug": None,
},
UPS.MEMOTECH_MTX: {
"id": 338636,
"igdb_id": None,
"igdb_slug": None,
"name": "Memotech MTX",
"ra_id": None,
"tgdb_id": None,
"giantbomb_id": 206,
"giantbomb_slug": None,
},
UPS.G_AND_W: {
"id": 273860,
"igdb_id": 307,
"igdb_slug": None,
"name": "Nintendo Game & Watch",
"ra_id": None,
"tgdb_id": None,
"giantbomb_id": None,
"giantbomb_slug": None,
},
UPS.PC_8800_SERIES: {
"id": 311550,
"igdb_id": None,
"igdb_slug": None,
"name": "PC-8000/8800",
"ra_id": 47,
"tgdb_id": None,
"giantbomb_id": None,
"giantbomb_slug": None,
},
UPS.LASERACTIVE: {
"id": 261429,
"igdb_id": None,
"igdb_slug": None,
"name": "Pioneer LaserActive",
"ra_id": None,
"tgdb_id": 4975,
"giantbomb_id": 92,
"giantbomb_slug": None,
},
UPS.SG1000: {
"id": 83,
"igdb_id": 84,
"igdb_slug": None,
"name": "Sega SG-1000",
"ra_id": 33,
"tgdb_id": 4949,
"giantbomb_id": 141,
"giantbomb_slug": None,
},
UPS.SORD_M5: {
"id": 279962,
"igdb_id": None,
"igdb_slug": None,
"name": "Sord M5",
"ra_id": None,
"tgdb_id": None,
"giantbomb_id": 193,
"giantbomb_slug": None,
},
UPS.ACORN_ARCHIMEDES: {
"id": 24,
"igdb_id": 116,
"igdb_slug": "acorn-archimedes",
"name": "Acorn Archimedes",
"ra_id": None,
"tgdb_id": 4944,
"giantbomb_id": 125,
"giantbomb_slug": "acorn-archimedes",
},
UPS.ACORN_ELECTRON: {
"id": 25,
"igdb_id": 134,
"igdb_slug": "acorn-electron",
"name": "Acorn Electron",
"ra_id": None,
"tgdb_id": 4954,
"giantbomb_id": None,
"giantbomb_slug": None,
},
UPS.ACPC: {
"id": 28,
"igdb_id": 25,
"igdb_slug": "acpc",
"name": "Amstrad CPC",
"ra_id": 37,
"tgdb_id": 4914,
"giantbomb_id": 11,
"giantbomb_slug": "amstrad-cpc",
},
UPS.ACTION_MAX: {
"id": 232983,
"igdb_id": None,
"igdb_slug": "",
"name": "Action Max",
"ra_id": None,
"tgdb_id": 4976,
"giantbomb_id": 148,
"giantbomb_slug": "action-max",
},
UPS.ADVENTURE_VISION: {
"id": 234388,
"igdb_id": None,
"igdb_slug": "",
"name": "Entex Adventure Vision",
"ra_id": None,
"tgdb_id": 4974,
"giantbomb_id": 93,
"giantbomb_slug": "adventure-vision",
},
UPS.ALTAIR_8800: {
"id": 234456,
"igdb_id": None,
"igdb_slug": "",
"name": "MITS Altair 8800",
"ra_id": None,
"tgdb_id": None,
"giantbomb_id": None,
"giantbomb_slug": None,
},
UPS.AMIGA: {
"id": 3,
"igdb_id": 16,
"igdb_slug": "amiga",
"name": "Commodore Amiga",
"ra_id": 35,
"tgdb_id": 4911,
"giantbomb_id": 1,
"giantbomb_slug": "amiga",
},
UPS.AMIGA_CD32: {
"id": 161823,
"igdb_id": 114,
"igdb_slug": "amiga-cd32",
"name": "Commodore CD32",
"ra_id": None,
"tgdb_id": 4947,
"giantbomb_id": 39,
"giantbomb_slug": "amiga-cd32",
},
UPS.AMSTRAD_GX4000: {
"id": 61540,
"igdb_id": 506,
"igdb_slug": "amstrad-gx4000",
"name": "Amstrad GX4000",
"ra_id": None,
"tgdb_id": 4999,
"giantbomb_id": None,
"giantbomb_slug": None,
},
UPS.AMSTRAD_PCW: {
"id": 29,
"igdb_id": 154,
"igdb_slug": "amstrad-pcw",
"name": "Amstrad PCW",
"ra_id": None,
"tgdb_id": None,
"giantbomb_id": 197,
"giantbomb_slug": "amstrad-pcw",
},
UPS.APF: {
"id": 61738,
"igdb_id": None,
"igdb_slug": "",
"name": "APF Imagination Machine",
"ra_id": None,
"tgdb_id": None,
"giantbomb_id": 190,
"giantbomb_slug": "apf-mp-1000",
},
UPS.APPLE: {
"id": 61885,
"igdb_id": None,
"igdb_slug": "",
"name": "Apple I",
"ra_id": None,
"tgdb_id": None,
"giantbomb_id": None,
"giantbomb_slug": None,
},
UPS.APPLE_IIGS: {
"id": 21,
"igdb_id": 115,
"igdb_slug": "apple-iigs",
"name": "Apple IIGS",
"ra_id": None,
"tgdb_id": None,
"giantbomb_id": 38,
"giantbomb_slug": "apple-iigs",
},
UPS.APPLE_LISA: {
"id": 69659,
"igdb_id": None,
"igdb_slug": "",
"name": "Apple Lisa",
"ra_id": None,
"tgdb_id": None,
"giantbomb_id": None,
"giantbomb_slug": None,
},
UPS.APPLE_PIPPIN: {
"id": 22,
"igdb_id": 476,
"igdb_slug": "apple-pippin",
"name": "Apple Pippin",
"ra_id": None,
"tgdb_id": 5001,
"giantbomb_id": 102,
"giantbomb_slug": None,
},
UPS.APPLEII: {
"id": 20,
"igdb_id": 75,
"igdb_slug": "appleii",
"name": "Apple II",
"ra_id": 38,
"tgdb_id": 4942,
"giantbomb_id": 12,
"giantbomb_slug": "apple-ii",
},
UPS.APPLEIII: {
"id": 63154,
"igdb_id": None,
"igdb_slug": "",
"name": "Apple III",
"ra_id": None,
"tgdb_id": None,
"giantbomb_id": None,
"giantbomb_slug": None,
},
UPS.AQUARIUS: {
"id": 51,
"igdb_id": None,
"igdb_slug": "",
"name": "Mattel Aquarius",
"ra_id": None,
"tgdb_id": 4989,
"giantbomb_id": 100,
"giantbomb_slug": "aquarius",
},
UPS.ARCADE: {
"id": 178,
"igdb_id": 52,
"igdb_slug": "arcade",
"name": "Arcade",
"ra_id": 27,
"tgdb_id": 23,
"giantbomb_id": 84,
"giantbomb_slug": "arcade",
},
UPS.ARDUBOY: {
"id": 244294,
"igdb_id": 438,
"igdb_slug": "arduboy",
"name": "Arduboy",
"ra_id": None,
"tgdb_id": None,
"giantbomb_id": None,
"giantbomb_slug": None,
},
UPS.ASTROCADE: {
"id": 31,
"igdb_id": 91,
"igdb_slug": "astrocade",
"name": "Bally Astrocade",
"ra_id": None,
"tgdb_id": 4968,
"giantbomb_id": 120,
"giantbomb_slug": "bally-astrocade",
},
UPS.ATARI_ST: {
"id": 15,
"igdb_id": 63,
"igdb_slug": "atari-st",
"name": "Atari ST/STE",
"ra_id": 36,
"tgdb_id": 4937,
"giantbomb_id": 13,
"giantbomb_slug": "atari-st",
},
UPS.ATARI2600: {
"id": 12,
"igdb_id": 59,
"igdb_slug": "atari2600",
"name": "Atari 2600",
"ra_id": 25,
"tgdb_id": 22,
"giantbomb_id": 40,
"giantbomb_slug": "atari-2600",
},
UPS.ATARI5200: {
"id": 17,
"igdb_id": 66,
"igdb_slug": "atari5200",
"name": "Atari 5200",
"ra_id": 50,
"tgdb_id": 26,
"giantbomb_id": 67,
"giantbomb_slug": "atari-5200",
},
UPS.ATARI7800: {
"id": 16,
"igdb_id": 60,
"igdb_slug": "atari7800",
"name": "Atari 7800",
"ra_id": 51,
"tgdb_id": 27,
"giantbomb_id": 70,
"giantbomb_slug": "atari-7800",
},
UPS.ATARI8BIT: {
"id": 18,
"igdb_id": 65,
"igdb_slug": "atari8bit",
"name": "Atari 8-bit",
"ra_id": None,
"tgdb_id": None,
"giantbomb_id": 24,
"giantbomb_slug": "atari-8-bit",
},
UPS.ATOM: {
"id": 55099,
"igdb_id": None,
"igdb_slug": "",
"name": "Acorn Atom",
"ra_id": None,
"tgdb_id": 5014,
"giantbomb_id": None,
"giantbomb_slug": None,
},
UPS.BBCMICRO: {
"id": 26,
"igdb_id": 69,
"igdb_slug": "bbcmicro",
"name": "BBC Micro",
"ra_id": None,
"tgdb_id": 5013,
"giantbomb_id": 110,
"giantbomb_slug": "bbc-micro",
},
UPS.BEENA: {
"id": 82,
"igdb_id": None,
"igdb_slug": "",
"name": "Sega Advanced Pico Beena",
"ra_id": None,
"tgdb_id": None,
"giantbomb_id": 174,
"giantbomb_slug": None,
},
UPS.BIT_90: {
"id": 97614,
"igdb_id": None,
"igdb_slug": "",
"name": "Bit Corporation BIT 90",
"ra_id": None,
"tgdb_id": None,
"giantbomb_id": None,
"giantbomb_slug": None,
},
UPS.C_PLUS_4: {
"id": 7,
"igdb_id": 94,
"igdb_slug": "c-plus-4",
"name": "Commodore Plus/4",
"ra_id": None,
"tgdb_id": 5007,
"giantbomb_id": None,
"giantbomb_slug": None,
},
UPS.C128: {
"id": 8,
"igdb_id": None,
"igdb_slug": "",
"name": "Commodore 128",
"ra_id": None,
"tgdb_id": 4946,
"giantbomb_id": 58,
"giantbomb_slug": "commodore-128",
},
UPS.C16: {
"id": 6,
"igdb_id": 93,
"igdb_slug": "c16",
"name": "Commodore 16",
"ra_id": None,
"tgdb_id": 5006,
"giantbomb_id": 150,
"giantbomb_slug": "commodore-16",
},
UPS.C64: {
"id": 5,
"igdb_id": 15,
"igdb_slug": "c64",
"name": "Commodore MAX",
"ra_id": None,
"tgdb_id": 40,
"giantbomb_id": 14,
"giantbomb_slug": "commodore-64",
},
UPS.CAMPUTERS_LYNX: {
"id": 97720,
"igdb_id": None,
"igdb_slug": "",
"name": "Camputers Lynx",
"ra_id": None,
"tgdb_id": None,
"giantbomb_id": None,
"giantbomb_slug": None,
},
UPS.CASIO_CFX_9850: {
"id": 97839,
"igdb_id": None,
"igdb_slug": "",
"name": "Casio CFX-9850",
"ra_id": None,
"tgdb_id": None,
"giantbomb_id": None,
"giantbomb_slug": None,
},
UPS.CASIO_FP_1000: {
"id": 98757,
"igdb_id": None,
"igdb_slug": "",
"name": "Casio FP-1000 & FP-1100",
"ra_id": None,
"tgdb_id": None,
"giantbomb_id": None,
"giantbomb_slug": None,
},
UPS.CASIO_LOOPY: {
"id": 37,
"igdb_id": 380,
"igdb_slug": "casio-loopy",
"name": "Casio Loopy",
"ra_id": None,
"tgdb_id": 4991,
"giantbomb_id": 126,
"giantbomb_slug": "casio-loopy",
},
UPS.CASIO_PB_1000: {
"id": 98771,
"igdb_id": None,
"igdb_slug": "",
"name": "Casio PB-1000",
"ra_id": None,
"tgdb_id": None,
"giantbomb_id": None,
"giantbomb_slug": None,
},
UPS.CASIO_PV_1000: {
"id": 98793,
"igdb_id": None,
"igdb_slug": "",
"name": "Casio PV-1000",
"ra_id": None,
"tgdb_id": 4964,
"giantbomb_id": 149,
"giantbomb_slug": "casio-pv-1000",
},
UPS.CASIO_PV_2000: {
"id": 98811,
"igdb_id": None,
"igdb_slug": "",
"name": "Casio PV-2000",
"ra_id": None,
"tgdb_id": None,
"giantbomb_id": 187,
"giantbomb_slug": "casio-pv-2000",
},
UPS.COLECOVISION: {
"id": 39,
"igdb_id": 68,
"igdb_slug": "colecovision",
"name": "ColecoVision",
"ra_id": 44,
"tgdb_id": 31,
"giantbomb_id": 47,
"giantbomb_slug": "colecovision",
},
UPS.COMMANDER_X16: {
"id": 54769,
"igdb_id": None,
"igdb_slug": "",
"name": "8-Bit Productions Commander X16",
"ra_id": None,
"tgdb_id": None,
"giantbomb_id": None,
"giantbomb_slug": None,
},
UPS.COMMODORE_CDTV: {
"id": 9,
"igdb_id": 158,
"igdb_slug": "commodore-cdtv",
"name": "Commodore CDTV",
"ra_id": None,
"tgdb_id": None,
"giantbomb_id": 142,
"giantbomb_slug": "commodore-cdtv",
},
UPS.CPET: {
"id": 10,
"igdb_id": 90,
"igdb_slug": "cpet",
"name": "Commodore PET",
"ra_id": None,
"tgdb_id": 5008,
"giantbomb_id": 62,
"giantbomb_slug": "commodore-petcbm",
},
UPS.DC: {
"id": 54694,
"igdb_id": 23,
"igdb_slug": "dc",
"name": "Sega Dreamcast",
"ra_id": 40,
"tgdb_id": 16,
"giantbomb_id": 37,
"giantbomb_slug": "dreamcast",
},
UPS.DOS: {
"id": 233075,
"igdb_id": 13,
"igdb_slug": "dos",
"name": "Microsoft DOS",
"ra_id": None,
"tgdb_id": 1,
"giantbomb_id": None,
"giantbomb_slug": None,
},
UPS.EXCALIBUR_64: {
"id": 97612,
"igdb_id": None,
"igdb_slug": "",
"name": "BGR Computers Excalibur 64",
"ra_id": None,
"tgdb_id": None,
"giantbomb_id": None,
"giantbomb_slug": None,
},
UPS.FAIRCHILD_CHANNEL_F: {
"id": 43,
"igdb_id": 127,
"igdb_slug": "fairchild-channel-f",
"name": "Fairchild Channel F",
"ra_id": 57,
"tgdb_id": 4928,
"giantbomb_id": 66,
"giantbomb_slug": "channel-f",
},
UPS.FDS: {
"id": 54692,
"igdb_id": 51,
"igdb_slug": "fds",
"name": "Nintendo Famicom Disk System",
"ra_id": None,
"tgdb_id": 4936,
"giantbomb_id": 91,
"giantbomb_slug": "famicom-disk-system",
},
UPS.FM_TOWNS: {
"id": 238902,
"igdb_id": 118,
"igdb_slug": "fm-towns",
"name": "Fujitsu - FM Towns",
"ra_id": None,
"tgdb_id": None,
"giantbomb_id": 108,
"giantbomb_slug": "fm-towns",
},
UPS.GAMATE: {
"id": 97616,
"igdb_id": 378,
"igdb_slug": "gamate",
"name": "Bit Corporation Gamate",
"ra_id": None,
"tgdb_id": None,
"giantbomb_id": 166,
"giantbomb_slug": "gamate",
},
UPS.GAMEGEAR: {
"id": 84,
"igdb_id": 35,
"igdb_slug": "gamegear",
"name": "Sega Game Gear",
"ra_id": 15,
"tgdb_id": 20,
"giantbomb_id": 5,
"giantbomb_slug": "game-gear",
},
UPS.GB: {
"id": 70,
"igdb_id": 33,
"igdb_slug": "gb",
"name": "Nintendo GameBoy",
"ra_id": 4,
"tgdb_id": 4,
"giantbomb_id": 3,
"giantbomb_slug": "game-boy",
},
UPS.GBA: {
"id": 71,
"igdb_id": 24,
"igdb_slug": "gba",
"name": "Nintendo Game Boy Advance",
"ra_id": 5,
"tgdb_id": 5,
"giantbomb_id": 4,
"giantbomb_slug": "game-boy-advance",
},
UPS.GBC: {
"id": 72,
"igdb_id": 22,
"igdb_slug": "gbc",
"name": "Nintendo Game Boy Color",
"ra_id": 6,
"tgdb_id": 41,
"giantbomb_id": 57,
"giantbomb_slug": "game-boy-color",
},
UPS.GENESIS: {
"id": 86,
"igdb_id": 29,
"igdb_slug": "genesis-slash-megadrive",
"name": "Sega Mega Drive / Genesis",
"ra_id": 1,
"tgdb_id": 18,
"giantbomb_id": 6,
"giantbomb_slug": "genesis",
},
UPS.INTELLIVISION: {
"id": 52,
"igdb_id": 67,
"igdb_slug": "intellivision",
"name": "Mattel Intellivision",
"ra_id": 45,
"tgdb_id": 32,
"giantbomb_id": 51,
"giantbomb_slug": "intellivision",
},
UPS.JAGUAR: {
"id": 13,
"igdb_id": 62,
"igdb_slug": "jaguar",
"name": "Atari Jaguar",
"ra_id": 17,
"tgdb_id": 28,
"giantbomb_id": 28,
"giantbomb_slug": "jaguar",
},
UPS.LINUX: {
"id": 233076,
"igdb_id": 3,
"igdb_slug": "linux",
"name": "Linux",
"ra_id": None,
"tgdb_id": None,
"giantbomb_id": 152,
"giantbomb_slug": "linux",
},
UPS.LYNX: {
"id": 14,
"igdb_id": 61,
"igdb_slug": "lynx",
"name": "Atari Lynx",
"ra_id": 13,
"tgdb_id": 4924,
"giantbomb_id": 7,
"giantbomb_slug": "atari-lynx",
},
UPS.MAC: {
"id": 30,
"igdb_id": 14,
"igdb_slug": "mac",
"name": "Apple Mac",
"ra_id": None,
"tgdb_id": 37,
"giantbomb_id": 17,
"giantbomb_slug": "mac",
},
UPS.MICROBEE: {
"id": 69714,
"igdb_id": None,
"igdb_slug": "",
"name": "Applied Technology MicroBee",
"ra_id": None,
"tgdb_id": None,
"giantbomb_id": 168,
"giantbomb_slug": "micro-bee",
},
UPS.MSX: {
"id": 53,
"igdb_id": 27,
"igdb_slug": "msx",
"name": "MSX",
"ra_id": 29,
"tgdb_id": 4929,
"giantbomb_id": 15,
"giantbomb_slug": "msx",
},
UPS.MSX2: {
"id": 54,
"igdb_id": 53,
"igdb_slug": "msx2",
"name": "MSX 2",
"ra_id": None,
"tgdb_id": None,
"giantbomb_id": None,
"giantbomb_slug": None,
},
UPS.MULTIVISION: {
"id": 52922,
"igdb_id": None,
"igdb_slug": "",
"name": "Tsukuda Original Othello Multivision",
"ra_id": None,
"tgdb_id": None,
"giantbomb_id": None,
"giantbomb_slug": None,
},
UPS.N64: {
"id": 64,
"igdb_id": 4,
"igdb_slug": "n64",
"name": "Nintendo 64",
"ra_id": 2,
"tgdb_id": 3,
"giantbomb_id": 43,
"giantbomb_slug": "nintendo-64",
},
UPS.NDS: {
"id": 66,
"igdb_id": 20,
"igdb_slug": "nds",
"name": "Nintendo DS",
"ra_id": 18,
"tgdb_id": 8,
"giantbomb_id": 52,
"giantbomb_slug": "nintendo-ds",
},
UPS.NEC_PC_6000_SERIES: {
"id": 58,
"igdb_id": 157,
"igdb_slug": "nec-pc-6000-series",
"name": "NEC PC-6000",
"ra_id": None,
"tgdb_id": None,
"giantbomb_id": None,
"giantbomb_slug": None,
},
UPS.NEO_GEO_CD: {
"id": 161829,
"igdb_id": 136,
"igdb_slug": "neo-geo-cd",
"name": "Neo Geo CD",
"ra_id": 56,
"tgdb_id": 4956,
"giantbomb_id": 167,
"giantbomb_slug": "neo-geo-cd",
},
UPS.NEO_GEO_POCKET: {
"id": 97,
"igdb_id": 119,
"igdb_slug": "neo-geo-pocket",
"name": "Neo Geo Pocket",
"ra_id": 14,
"tgdb_id": 4922,
"giantbomb_id": 80,
"giantbomb_slug": "neo-geo-pocket",
},
UPS.NEO_GEO_POCKET_COLOR: {
"id": 98,
"igdb_id": 120,
"igdb_slug": "neo-geo-pocket-color",
"name": "Neo Geo Pocket Color",
"ra_id": None,
"tgdb_id": 4923,
"giantbomb_id": 81,
"giantbomb_slug": "neo-geo-pocket-color",
},
UPS.NEOGEOAES: {
"id": 96,
"igdb_id": 80,
"igdb_slug": "neogeoaes",
"name": "Neo Geo",
"ra_id": None,
"tgdb_id": 24,
"giantbomb_id": 25,
"giantbomb_slug": "neo-geo",
},
UPS.NES: {
"id": 68,
"igdb_id": 18,
"igdb_slug": "nes",
"name": "Nintendo Entertainment System",
"ra_id": 7,
"tgdb_id": 7,
"giantbomb_id": 21,
"giantbomb_slug": "nintendo-entertainment-system",
},
UPS.NEW_NINTENDON3DS: {
"id": 63,
"igdb_id": 137,
"igdb_slug": "new-nintendo-3ds",
"name": "Nintendo New 3DS",
"ra_id": None,
"tgdb_id": None,
"giantbomb_id": 156,
"giantbomb_slug": "new-nintendo-3ds",
},
UPS.NGC: {
"id": 73,
"igdb_id": 21,
"igdb_slug": "ngc",
"name": "Nintendo GameCube",
"ra_id": 16,
"tgdb_id": 2,
"giantbomb_id": 23,
"giantbomb_slug": "gamecube",
},
UPS.NINTENDO_DSI: {
"id": 67,
"igdb_id": 159,
"igdb_slug": "nintendo-dsi",
"name": "Nintendo DSi",
"ra_id": 78,
"tgdb_id": None,
"giantbomb_id": None,
"giantbomb_slug": None,
},
UPS.ODYSSEY: {
"id": 48,
"igdb_id": 88,
"igdb_slug": "odyssey--1",
"name": "Magnavox Odyssey",
"ra_id": None,
"tgdb_id": 4961,
"giantbomb_id": 74,
"giantbomb_slug": "odyssey",
},
UPS.ODYSSEY_2_SLASH_VIDEOPAC_G7000: {
"id": 49,
"igdb_id": 133,
"igdb_slug": "odyssey-2-slash-videopac-g7000",
"name": "Magnavox Odyssey 2",
"ra_id": 23,
"tgdb_id": 4927,
"giantbomb_id": 60,
"giantbomb_slug": None,
},
UPS.PC_8800_SERIES: {
"id": 57,
"igdb_id": 125,
"igdb_slug": "pc-8800-series",
"name": "NEC PC-8800",
"ra_id": None,
"tgdb_id": 4933,
"giantbomb_id": 47,
"giantbomb_slug": "nec-pc-8800-series",
},
UPS.PC_9800_SERIES: {
"id": 59,
"igdb_id": 149,
"igdb_slug": "pc-9800-series",
"name": "NEC PC-9000",
"ra_id": None,
"tgdb_id": None,
"giantbomb_id": 112,
"giantbomb_slug": "nec-pc-9801",
},
UPS.PC_JR: {
"id": 233269,
"igdb_id": None,
"igdb_slug": "",
"name": "IBM PCjr",
"ra_id": None,
"tgdb_id": None,
"giantbomb_id": None,
"giantbomb_slug": None,
},
UPS.PHILIPS_CD_I: {
"id": 161827,
"igdb_id": 117,
"igdb_slug": "philips-cd-i",
"name": "Philips CD-i",
"ra_id": 42,
"tgdb_id": 4917,
"giantbomb_id": 27,
"giantbomb_slug": "cd-i",
},
UPS.POCKET_CHALLENGE_V2: {
"id": 97550,
"igdb_id": None,
"igdb_slug": "",
"name": "Benesse Pocket Challenge V2",
"ra_id": None,
"tgdb_id": None,
"giantbomb_id": None,
"giantbomb_slug": None,
},
UPS.POCKET_CHALLENGE_W: {
"id": 97577,
"igdb_id": None,
"igdb_slug": "",
"name": "Benesse Pocket Challenge W",
"ra_id": None,
"tgdb_id": None,
"giantbomb_id": None,
"giantbomb_slug": None,
},
UPS.POCKETSTATION: {
"id": 103,
"igdb_id": 441,
"igdb_slug": "pocketstation",
"name": "Sony PocketStation",
"ra_id": None,
"tgdb_id": None,
"giantbomb_id": None,
"giantbomb_slug": None,
},
UPS.POKEMON_MINI: {
"id": 244733,
"igdb_id": 166,
"igdb_slug": "pokemon-mini",
"name": "Nintendo Pokemon Mini",
"ra_id": 24,
"tgdb_id": None,
"giantbomb_id": 134,
"giantbomb_slug": "pokemon-mini",
},
UPS.PS2: {
"id": 101,
"igdb_id": 8,
"igdb_slug": "ps2",
"name": "Sony PlayStation 2",
"ra_id": 21,
"tgdb_id": 11,
"giantbomb_id": 19,
"giantbomb_slug": "playstation-2",
},
UPS.PS3: {
"id": 161830,
"igdb_id": 9,
"igdb_slug": "ps3",
"name": "Sony Playstation 3",
"ra_id": None,
"tgdb_id": 12,
"giantbomb_id": 35,
"giantbomb_slug": "playstation-3",
},
UPS.PS4: {
"id": 232986,
"igdb_id": 48,
"igdb_slug": "ps4--1",
"name": "Sony Playstation 4",
"ra_id": None,
"tgdb_id": 4919,
"giantbomb_id": 146,
"giantbomb_slug": "playstation-4",
},
UPS.PS5: {
"id": 232987,
"igdb_id": 167,
"igdb_slug": "ps5",
"name": "Sony Playstation 5",
"ra_id": None,
"tgdb_id": 4980,
"giantbomb_id": 176,
"giantbomb_slug": "playstation-5",
},
UPS.PSP: {
"id": 161831,
"igdb_id": 38,
"igdb_slug": "psp",
"name": "Sony Playstation Portable",
"ra_id": 41,
"tgdb_id": 13,
"giantbomb_id": 18,
"giantbomb_slug": "playstation-portable",
},
UPS.PSVITA: {
"id": 102,
"igdb_id": 46,
"igdb_slug": "psvita",
"name": "Sony PlayStation Vita",
"ra_id": None,
"tgdb_id": 39,
"giantbomb_id": 129,
"giantbomb_slug": "playstation-vita",
},
UPS.PSX: {
"id": 100,
"igdb_id": 7,
"igdb_slug": "ps",
"name": "Sony PlayStation",
"ra_id": 12,
"tgdb_id": 10,
"giantbomb_id": 22,
"giantbomb_slug": "playstation",
},
UPS.RCA_STUDIO_II: {
"id": 234745,
"igdb_id": None,
"igdb_slug": "",
"name": "RCA Studio II",
"ra_id": None,
"tgdb_id": 4967,
"giantbomb_id": 131,
"giantbomb_slug": "rca-studio-ii",
},
UPS.SATURN: {
"id": 54695,
"igdb_id": 32,
"igdb_slug": "saturn",
"name": "Sega Saturn",
"ra_id": 39,
"tgdb_id": 17,
"giantbomb_id": 42,
"giantbomb_slug": "saturn",
},
UPS.SC3000: {
"id": 52165,
"igdb_id": None,
"igdb_slug": "",
"name": "Sega Computer 3000",
"ra_id": None,
"tgdb_id": None,
"giantbomb_id": None,
"giantbomb_slug": None,
},
UPS.SEGA_PICO: {
"id": 81,
"igdb_id": 339,
"igdb_slug": "sega-pico",
"name": "Sega Pico",
"ra_id": 68,
"tgdb_id": 4958,
"giantbomb_id": 105,
"giantbomb_slug": "sega-pico",
},
UPS.SEGA32X: {
"id": 80,
"igdb_id": 30,
"igdb_slug": "sega32",
"name": "Sega 32X",
"ra_id": 10,
"tgdb_id": 33,
"giantbomb_id": 31,
"giantbomb_slug": "sega-32x",
},
UPS.SEGACD: {
"id": 161828,
"igdb_id": 78,
"igdb_slug": "",
"name": "Sega Mega CD / Sega CD",
"ra_id": 9,
"tgdb_id": 21,
"giantbomb_id": 29,
"giantbomb_slug": "sega-cd",
},
UPS.SERIES_X_S: {
"id": 232984,
"igdb_id": 169,
"igdb_slug": "series-x-s",
"name": "Microsoft Xbox Series X",
"ra_id": None,
"tgdb_id": 4981,
"giantbomb_id": 179,
"giantbomb_slug": "xbox-series-xs",
},
UPS.SFAM: {
"id": 233081,
"igdb_id": 58,
"igdb_slug": "sfam",
"name": "Super Famicom",
"ra_id": None,
"tgdb_id": None,
"giantbomb_id": None,
"giantbomb_slug": None,
},
UPS.SG1000: {
"id": 244470,
"igdb_id": 84,
"igdb_slug": "sg1000",
"name": "SG-1000",
"ra_id": 33,
"tgdb_id": None,
"giantbomb_id": 141,
"giantbomb_slug": "sega-sg-1000",
},
UPS.SHARP_X68000: {
"id": 90,
"igdb_id": 121,
"igdb_slug": "sharp-x68000",
"name": "Sharp X68000",
"ra_id": 52,
"tgdb_id": 4931,
"giantbomb_id": 95,
"giantbomb_slug": "sharp-x68000",
},
UPS.SINCLAIR_QL: {
"id": 92,
"igdb_id": 406,
"igdb_slug": "sinclair-ql",
"name": "Sinclair QL",
"ra_id": None,
"tgdb_id": 5020,
"giantbomb_id": None,
"giantbomb_slug": None,
},
UPS.SMS: {
"id": 85,
"igdb_id": 64,
"igdb_slug": "sms",
"name": "Sega Master System",
"ra_id": 11,
"tgdb_id": 35,
"giantbomb_id": 8,
"giantbomb_slug": "sega-master-system",
},
UPS.SNES: {
"id": 74,
"igdb_id": 19,
"igdb_slug": "snes",
"name": "Super Nintendo Entertainment System",
"ra_id": 3,
"tgdb_id": 6,
"giantbomb_id": 9,
"giantbomb_slug": "super-nintendo-entertainment-system",
},
UPS.SUPER_VISION_8000: {
"id": 97267,
"igdb_id": None,
"igdb_slug": "",
"name": "Bandai Super Vision 8000",
"ra_id": None,
"tgdb_id": None,
"giantbomb_id": None,
"giantbomb_slug": None,
},
UPS.SUPERVISION: {
"id": 244828,
"igdb_id": 415,
"igdb_slug": "watara-slash-quickshot-supervision",
"name": "Watara Supervision",
"ra_id": 63,
"tgdb_id": 4959,
"giantbomb_id": 147,
"giantbomb_slug": "watara-supervision",
},
UPS.SWITCH: {
"id": 233067,
"igdb_id": 130,
"igdb_slug": "switch",
"name": "Nintendo Switch",
"ra_id": None,
"tgdb_id": 4971,
"giantbomb_id": 157,
"giantbomb_slug": "nintendo-switch",
},
UPS.TG16: {
"id": 245372,
"igdb_id": 86,
"igdb_slug": "turbografx16--1",
"name": "TurboGrafx-16/PC Engine",
"ra_id": 8,
"tgdb_id": 34,
"giantbomb_id": 55,
"giantbomb_slug": "turbografx-16",
},
UPS.TI_82: {
"id": 47973,
"igdb_id": None,
"igdb_slug": "",
"name": "Texas Instruments TI-82",
"ra_id": None,
"tgdb_id": None,
"giantbomb_id": None,
"giantbomb_slug": None,
},
UPS.TI_83: {
"id": 243852,
"igdb_id": None,
"igdb_slug": "",
"name": "Texas Instruments TI-83",
"ra_id": None,
"tgdb_id": None,
"giantbomb_id": None,
"giantbomb_slug": None,
},
UPS.TRS_80: {
"id": 105,
"igdb_id": 126,
"igdb_slug": "trs-80",
"name": "Tandy/RadioShack TRS-80",
"ra_id": None,
"tgdb_id": None,
"giantbomb_id": 63,
"giantbomb_slug": "trs-80",
},
UPS.TRS_80_COLOR_COMPUTER: {
"id": 106,
"igdb_id": 151,
"igdb_slug": "trs-80-color-computer",
"name": "Tandy/RadioShack TRS-80 Color Computer",
"ra_id": None,
"tgdb_id": 4941,
"giantbomb_id": 68,
"giantbomb_slug": "trs-80-coco",
},
UPS.TURBOGRAFX_CD: {
"id": 247350,
"igdb_id": 150,
"igdb_slug": "turbografx-16-slash-pc-engine-cd",
"name": "Turbografx-16/PC Engine CD",
"ra_id": None,
"tgdb_id": 4955,
"giantbomb_id": 53,
"giantbomb_slug": "turbografx-cd",
},
UPS.VECTREX: {
"id": 45,
"igdb_id": 70,
"igdb_slug": "vectrex",
"name": "Vectrex",
"ra_id": 46,
"tgdb_id": 4939,
"giantbomb_id": 76,
"giantbomb_slug": "vectrex",
},
UPS.VIC_20: {
"id": 4,
"igdb_id": 71,
"igdb_slug": "vic-20",
"name": "Commodore VIC20",
"ra_id": None,
"tgdb_id": 4945,
"giantbomb_id": 30,
"giantbomb_slug": "vic-20",
},
UPS.VIRTUALBOY: {
"id": 75,
"igdb_id": 87,
"igdb_slug": "virtualboy",
"name": "Nintendo Virtual Boy",
"ra_id": 28,
"tgdb_id": 4918,
"giantbomb_id": 79,
"giantbomb_slug": "virtual-boy",
},
UPS.WII: {
"id": 76,
"igdb_id": 5,
"igdb_slug": "wii",
"name": "Nintendo Wii",
"ra_id": 19,
"tgdb_id": 9,
"giantbomb_id": 36,
"giantbomb_slug": "wii",
},
UPS.WIIU: {
"id": 77,
"igdb_id": 41,
"igdb_slug": "wiiu",
"name": "Nintendo WiiU",
"ra_id": None,
"tgdb_id": 38,
"giantbomb_id": 139,
"giantbomb_slug": "wii-u",
},
UPS.WIN: {
"id": 233074,
"igdb_id": 6,
"igdb_slug": "win",
"name": "Microsoft Windows",
"ra_id": None,
"tgdb_id": 1,
"giantbomb_id": 94,
"giantbomb_slug": "pc",
},
UPS.WONDERSWAN: {
"id": 34,
"igdb_id": 57,
"igdb_slug": "wonderswan",
"name": "Bandai WonderSwan",
"ra_id": 53,
"tgdb_id": 4925,
"giantbomb_id": 65,
"giantbomb_slug": "wonderswan",
},
UPS.WONDERSWAN_COLOR: {
"id": 35,
"igdb_id": 123,
"igdb_slug": "wonderswan-color",
"name": "Bandai WonderSwan Color",
"ra_id": None,
"tgdb_id": 4926,
"giantbomb_id": 54,
"giantbomb_slug": "wonderswan-color",
},
UPS.X1: {
"id": 89,
"igdb_id": 77,
"igdb_slug": "x1",
"name": "Sharp X1",
"ra_id": 64,
"tgdb_id": 4977,
"giantbomb_id": 113,
"giantbomb_slug": "sharp-x1",
},
UPS.XBOX: {
"id": 54696,
"igdb_id": 11,
"igdb_slug": "xbox",
"name": "Microsoft Xbox",
"ra_id": None,
"tgdb_id": 14,
"giantbomb_id": 32,
"giantbomb_slug": "xbox",
},
UPS.XBOX360: {
"id": 54697,
"igdb_id": 12,
"igdb_slug": "xbox360",
"name": "Microsoft Xbox 360",
"ra_id": None,
"tgdb_id": 15,
"giantbomb_id": 20,
"giantbomb_slug": "xbox-360",
},
UPS.XBOXONE: {
"id": 161824,
"igdb_id": 49,
"igdb_slug": "xboxone",
"name": "Microsoft Xbox One",
"ra_id": None,
"tgdb_id": 4920,
"giantbomb_id": 145,
"giantbomb_slug": "xbox-one",
},
UPS.Z88: {
"id": 97718,
"igdb_id": None,
"igdb_slug": "",
"name": "Cambridge Computer Z88",
"ra_id": None,
"tgdb_id": None,
"giantbomb_id": None,
"giantbomb_slug": None,
},
UPS.ZX80: {
"id": 232985,
"igdb_id": None,
"igdb_slug": "",
"name": "Sinclair ZX80",
"ra_id": None,
"tgdb_id": 5009,
"giantbomb_id": None,
"giantbomb_slug": None,
},
UPS.ZX81: {
"id": 94,
"igdb_id": 373,
"igdb_slug": "sinclair-zx81",
"name": "Sinclair ZX81",
"ra_id": None,
"tgdb_id": 5010,
"giantbomb_id": None,
"giantbomb_slug": None,
},
UPS.ZXS: {
"id": 93,
"igdb_id": 26,
"igdb_slug": "zxs",
"name": "Sinclair ZX Spectrum",
"ra_id": 59,
"tgdb_id": 4913,
"giantbomb_id": 16,
"giantbomb_slug": "zx-spectrum",
},
}