remove hartbeat from bh endpoint

This commit is contained in:
Georges-Antoine Assi
2025-09-20 17:57:38 -04:00
parent e9f7da09f9
commit 8c33a95637
19 changed files with 210 additions and 56 deletions

View File

@@ -106,6 +106,19 @@ class MobyGamesService:
log.error("Error decoding JSON response from ScreenScraper: %s", exc)
return {}
async def list_groups(self, limit: int | None = None) -> list[dict]:
"""Retrieve a list of groups.
Reference: https://www.mobygames.com/info/api/#groups
"""
params: dict[str, list[str]] = {}
if limit is not None:
params["limit"] = [str(limit)]
url = self.url.joinpath("groups").with_query(**params)
response = await self._request(str(url))
return response.get("groups", [])
@overload
async def list_games(
self,

View File

@@ -129,6 +129,14 @@ class ScreenScraperService:
log.error("Error decoding JSON response from ScreenScraper: %s", exc)
return {}
async def get_infra_info(self) -> dict:
"""Retrieve information about the infrastructure.
Reference: https://api.screenscraper.fr/webapi2.php#infraInfos
"""
url = self.url.joinpath("ssinfraInfos.php")
return await self._request(str(url))
async def get_game_info(
self,
*,

View File

@@ -1,4 +1,4 @@
import asyncio
from fastapi import HTTPException
from config import (
DISABLE_EMULATOR_JS,
@@ -33,6 +33,7 @@ from handler.metadata import (
meta_ss_handler,
meta_tgdb_handler,
)
from handler.scan_handler import MetadataSource
from utils import get_version
from utils.router import APIRouter
@@ -48,13 +49,17 @@ async def heartbeat() -> HeartbeatResponse:
Returns:
HeartbeatReturn: TypedDict structure with all the defined values in the HeartbeatReturn class.
"""
# Run async operations in parallel
igdb_heartbeat, flashpoint_heartbeat, fs_platforms = await asyncio.gather(
meta_igdb_handler.heartbeat(),
meta_flashpoint_handler.heartbeat(),
fs_platform_handler.get_platforms(),
)
igdb_enabled = meta_igdb_handler.is_enabled()
flashpoint_enabled = meta_flashpoint_handler.is_enabled()
ss_enabled = meta_ss_handler.is_enabled()
moby_enabled = meta_moby_handler.is_enabled()
ra_enabled = meta_ra_handler.is_enabled()
sgdb_enabled = meta_sgdb_handler.is_enabled()
launchbox_enabled = meta_launchbox_handler.is_enabled()
hasheous_enabled = meta_hasheous_handler.is_enabled()
playmatch_enabled = meta_playmatch_handler.is_enabled()
hltb_enabled = meta_hltb_handler.is_enabled()
tgdb_enabled = meta_tgdb_handler.is_enabled()
return {
"SYSTEM": {
@@ -63,32 +68,30 @@ async def heartbeat() -> HeartbeatResponse:
},
"METADATA_SOURCES": {
"ANY_SOURCE_ENABLED": (
meta_igdb_handler.is_enabled()
or meta_ss_handler.is_enabled()
or meta_moby_handler.is_enabled()
or meta_ra_handler.is_enabled()
or meta_launchbox_handler.is_enabled()
or meta_hasheous_handler.is_enabled()
or meta_tgdb_handler.is_enabled()
or meta_flashpoint_handler.is_enabled()
or meta_hltb_handler.is_enabled()
igdb_enabled
or ss_enabled
or moby_enabled
or ra_enabled
or launchbox_enabled
or hasheous_enabled
or tgdb_enabled
or flashpoint_enabled
or hltb_enabled
),
"IGDB_API_ENABLED": meta_igdb_handler.is_enabled(),
"IGDB_API_HEARTBEAT": igdb_heartbeat,
"SS_API_ENABLED": meta_ss_handler.is_enabled(),
"MOBY_API_ENABLED": meta_moby_handler.is_enabled(),
"STEAMGRIDDB_API_ENABLED": meta_sgdb_handler.is_enabled(),
"RA_API_ENABLED": meta_ra_handler.is_enabled(),
"LAUNCHBOX_API_ENABLED": meta_launchbox_handler.is_enabled(),
"HASHEOUS_API_ENABLED": meta_hasheous_handler.is_enabled(),
"PLAYMATCH_API_ENABLED": meta_playmatch_handler.is_enabled(),
"TGDB_API_ENABLED": meta_tgdb_handler.is_enabled(),
"FLASHPOINT_API_ENABLED": meta_flashpoint_handler.is_enabled(),
"FLASHPOINT_API_HEARTBEAT": flashpoint_heartbeat,
"HLTB_API_ENABLED": meta_hltb_handler.is_enabled(),
"IGDB_API_ENABLED": igdb_enabled,
"SS_API_ENABLED": ss_enabled,
"MOBY_API_ENABLED": moby_enabled,
"STEAMGRIDDB_API_ENABLED": sgdb_enabled,
"RA_API_ENABLED": ra_enabled,
"LAUNCHBOX_API_ENABLED": launchbox_enabled,
"HASHEOUS_API_ENABLED": hasheous_enabled,
"PLAYMATCH_API_ENABLED": playmatch_enabled,
"TGDB_API_ENABLED": tgdb_enabled,
"FLASHPOINT_API_ENABLED": flashpoint_enabled,
"HLTB_API_ENABLED": hltb_enabled,
},
"FILESYSTEM": {
"FS_PLATFORMS": fs_platforms,
"FS_PLATFORMS": await fs_platform_handler.get_platforms(),
},
"EMULATION": {
"DISABLE_EMULATOR_JS": DISABLE_EMULATOR_JS,
@@ -114,3 +117,34 @@ async def heartbeat() -> HeartbeatResponse:
"SCHEDULED_CONVERT_IMAGES_TO_WEBP_CRON": SCHEDULED_CONVERT_IMAGES_TO_WEBP_CRON,
},
}
@router.get("/heartbeat/metadata")
async def metadata_heartbeat(metadata_source: str) -> bool:
"""Endpoint to return the heartbeat of the metadata sources"""
try:
metadata_source = MetadataSource(metadata_source)
except ValueError as e:
raise HTTPException(status_code=400, detail="Invalid metadata source") from e
match metadata_source:
case MetadataSource.IGDB:
return await meta_igdb_handler.heartbeat()
case MetadataSource.MOBY:
return await meta_moby_handler.heartbeat()
case MetadataSource.SS:
return await meta_ss_handler.heartbeat()
case MetadataSource.RA:
return await meta_ra_handler.heartbeat()
case MetadataSource.LB:
return await meta_launchbox_handler.heartbeat()
case MetadataSource.HASHEOUS:
return await meta_hasheous_handler.heartbeat()
case MetadataSource.TGDB:
return await meta_tgdb_handler.heartbeat()
case MetadataSource.SGDB:
return await meta_sgdb_handler.heartbeat()
case MetadataSource.FLASHPOINT:
return await meta_flashpoint_handler.heartbeat()
case MetadataSource.HLTB:
return await meta_hltb_handler.heartbeat()

View File

@@ -9,17 +9,15 @@ class SystemDict(TypedDict):
class MetadataSourcesDict(TypedDict):
ANY_SOURCE_ENABLED: bool
IGDB_API_ENABLED: bool
IGDB_API_HEARTBEAT: bool
MOBY_API_ENABLED: bool
SS_API_ENABLED: bool
MOBY_API_ENABLED: bool
STEAMGRIDDB_API_ENABLED: bool
RA_API_ENABLED: bool
LAUNCHBOX_API_ENABLED: bool
PLAYMATCH_API_ENABLED: bool
HASHEOUS_API_ENABLED: bool
PLAYMATCH_API_ENABLED: bool
TGDB_API_ENABLED: bool
FLASHPOINT_API_ENABLED: bool
FLASHPOINT_API_HEARTBEAT: bool
HLTB_API_ENABLED: bool

View File

@@ -153,7 +153,12 @@ class FlashpointHandler(MetadataHandler):
return False
# make a request to the Flashpoint API to check if the API is working
response = await self._request(self.platforms_url, {})
try:
response = await self._request(self.platforms_url, {})
except Exception as e:
log.error("Error checking Flashpoint API: %s", e)
return False
return bool(response)
async def search_games(self, search_term: str) -> list[FlashpointGame]:

View File

@@ -129,6 +129,21 @@ class HasheousHandler(MetadataHandler):
"""Return whether this metadata handler is enabled."""
return HASHEOUS_API_ENABLED
async def heartbeat(self) -> bool:
return True
# if not self.is_enabled():
# return False
# # make a request to the Hasheous API to check if the API is working
# try:
# response = await self._request(self.platform_endpoint, "GET")
# except Exception as e:
# log.error("Error checking Hasheous API: %s", e)
# return False
# return bool(response)
async def _request(
self,
url: str,

View File

@@ -178,6 +178,23 @@ class HowLongToBeatHandler(MetadataHandler):
def is_enabled(cls) -> bool:
return HLTB_API_ENABLED
async def heartbeat(self) -> bool:
return True
# if not self.is_enabled():
# return False
# # make a request to the HLTB API to check if the API is working
# try:
# async with ctx_httpx_client() as client:
# response = await client.get(self.search_url, params={"q": "test"})
# response.raise_for_status()
# except Exception as e:
# log.error("Error checking HLTB API: %s", e)
# return False
# return True
@staticmethod
def extract_hltb_id_from_filename(fs_name: str) -> int | None:
"""Extract HLTB ID from filename tag like (hltb-12345)."""

View File

@@ -314,10 +314,14 @@ class IGDBHandler(MetadataHandler):
return False
# make a request to the IGDB API to check if the API is working
roms = await self.igdb_service.list_games(
fields=["id"],
limit=1,
)
try:
roms = await self.igdb_service.list_games(
fields=["id"],
limit=1,
)
except Exception as e:
log.error("Error checking IGDB API: %s", e)
return False
return bool(roms)

View File

@@ -127,6 +127,9 @@ class LaunchboxHandler(MetadataHandler):
def is_enabled(cls) -> bool:
return LAUNCHBOX_API_ENABLED
async def heartbeat(self) -> bool:
return self.is_enabled()
@staticmethod
def extract_launchbox_id_from_filename(fs_name: str) -> int | None:
"""Extract LaunchBox ID from filename tag like (launchbox-12345)."""

View File

@@ -82,6 +82,18 @@ class MobyGamesHandler(MetadataHandler):
def is_enabled(cls) -> bool:
return bool(MOBYGAMES_API_KEY)
async def heartbeat(self) -> bool:
if not self.is_enabled():
return False
try:
response = await self.moby_service.list_groups(limit=1)
except Exception as e:
log.error("Error checking MobyGames API: %s", e)
return False
return bool(response)
@staticmethod
def extract_mobygames_id_from_filename(fs_name: str) -> int | None:
"""Extract MobyGames ID from filename tag like (moby-12345)."""

View File

@@ -53,6 +53,21 @@ class PlaymatchHandler(MetadataHandler):
def is_enabled(cls) -> bool:
return PLAYMATCH_API_ENABLED
async def heartbeat(self) -> bool:
return True
# if not self.is_enabled():
# return False
# # make a request to the Playmatch API to check if the API is working
# try:
# response = await self._request(self.identify_url, {"hashes": ["test"]})
# except Exception as e:
# log.error("Error checking Playmatch API: %s", e)
# return False
# return bool(response)
async def _request(self, url: str, query: dict) -> dict:
"""
Sends a Request to Playmatch API.

View File

@@ -132,6 +132,21 @@ class RAHandler(MetadataHandler):
def is_enabled(cls) -> bool:
return bool(RETROACHIEVEMENTS_API_KEY)
async def heartbeat(self) -> bool:
return True
# if not self.is_enabled():
# return False
# # make a request to the RetroAchievements API to check if the API is working
# try:
# response = await self.ra_service.get_console_ids()
# except Exception as e:
# log.error("Error checking RetroAchievements API: %s", e)
# return False
# return bool(response)
@staticmethod
def extract_ra_id_from_filename(fs_name: str) -> int | None:
"""Extract RetroAchievements ID from filename tag like (ra-12345)."""

View File

@@ -34,6 +34,21 @@ class SGDBBaseHandler(MetadataHandler):
def is_enabled(cls) -> bool:
return bool(STEAMGRIDDB_API_KEY)
async def heartbeat(self) -> bool:
return True
# if not self.is_enabled():
# return False
# # make a request to the SteamGridDB API to check if the API is working
# try:
# response = await self.sgdb_service.get_platforms()
# except Exception as e:
# log.error("Error checking SteamGridDB API: %s", e)
# return False
# return bool(response)
async def get_rom_by_id(self, sgdb_id: int) -> SGDBRom:
"""Get ROM details by SteamGridDB ID."""
if not self.is_enabled():

View File

@@ -282,6 +282,18 @@ class SSHandler(MetadataHandler):
def is_enabled(cls) -> bool:
return bool(SCREENSCRAPER_USER and SCREENSCRAPER_PASSWORD)
async def heartbeat(self) -> bool:
if not self.is_enabled():
return False
try:
response = await self.ss_service.get_infra_info()
except Exception as e:
log.error("Error checking ScreenScraper API: %s", e)
return False
return bool(response.get("response", {}))
@staticmethod
def extract_ss_id_from_filename(fs_name: str) -> int | None:
"""Extract ScreenScraper ID from filename tag like (ss-12345)."""

View File

@@ -28,6 +28,9 @@ class TGDBHandler(MetadataHandler):
def is_enabled(cls) -> bool:
return TGDB_API_ENABLED
async def heartbeat(self) -> bool:
return self.is_enabled()
def get_platform(self, slug: str) -> TGDBPlatform:
if slug not in TGDB_PLATFORM_LIST:
return TGDBPlatform(tgdb_id=None, slug=slug)

View File

@@ -27,7 +27,6 @@ def test_heartbeat(client):
metadata = heartbeat["METADATA_SOURCES"]
assert isinstance(metadata["ANY_SOURCE_ENABLED"], bool)
assert isinstance(metadata["IGDB_API_ENABLED"], bool)
assert isinstance(metadata["IGDB_API_HEARTBEAT"], bool)
assert isinstance(metadata["MOBY_API_ENABLED"], bool)
assert isinstance(metadata["SS_API_ENABLED"], bool)
assert isinstance(metadata["STEAMGRIDDB_API_ENABLED"], bool)
@@ -37,7 +36,6 @@ def test_heartbeat(client):
assert isinstance(metadata["HASHEOUS_API_ENABLED"], bool)
assert isinstance(metadata["TGDB_API_ENABLED"], bool)
assert isinstance(metadata["FLASHPOINT_API_ENABLED"], bool)
assert isinstance(metadata["FLASHPOINT_API_HEARTBEAT"], bool)
assert "FILESYSTEM" in heartbeat
filesystem = heartbeat["FILESYSTEM"]

View File

@@ -5,7 +5,6 @@
export type MetadataSourcesDict = {
ANY_SOURCE_ENABLED: boolean;
IGDB_API_ENABLED: boolean;
IGDB_API_HEARTBEAT: boolean;
MOBY_API_ENABLED: boolean;
SS_API_ENABLED: boolean;
STEAMGRIDDB_API_ENABLED: boolean;
@@ -15,7 +14,6 @@ export type MetadataSourcesDict = {
HASHEOUS_API_ENABLED: boolean;
TGDB_API_ENABLED: boolean;
FLASHPOINT_API_ENABLED: boolean;
FLASHPOINT_API_HEARTBEAT: boolean;
HLTB_API_ENABLED: boolean;
};

View File

@@ -19,7 +19,6 @@ const defaultHeartbeat: Heartbeat = {
METADATA_SOURCES: {
ANY_SOURCE_ENABLED: false,
IGDB_API_ENABLED: false,
IGDB_API_HEARTBEAT: false,
SS_API_ENABLED: false,
MOBY_API_ENABLED: false,
RA_API_ENABLED: false,
@@ -29,7 +28,6 @@ const defaultHeartbeat: Heartbeat = {
HASHEOUS_API_ENABLED: false,
TGDB_API_ENABLED: false,
FLASHPOINT_API_ENABLED: false,
FLASHPOINT_API_HEARTBEAT: false,
HLTB_API_ENABLED: false,
},
FILESYSTEM: {

View File

@@ -1,8 +1,7 @@
<script setup lang="ts">
import { computed, inject } from "vue";
import { computed } from "vue";
import { useI18n } from "vue-i18n";
import storeHeartbeat from "@/stores/heartbeat";
import type { Events } from "@/types/emitter";
const { t } = useI18n();
const heartbeat = storeHeartbeat();
@@ -14,7 +13,6 @@ const metadataOptions = computed(() => [
value: "igdb",
logo_path: "/assets/scrappers/igdb.png",
disabled: !heartbeat.value.METADATA_SOURCES?.IGDB_API_ENABLED,
heartbeat: heartbeat.value.METADATA_SOURCES?.IGDB_API_HEARTBEAT,
},
{
name: "MobyGames",
@@ -51,7 +49,6 @@ const metadataOptions = computed(() => [
value: "flashpoint",
logo_path: "/assets/scrappers/flashpoint.png",
disabled: !heartbeat.value.METADATA_SOURCES?.FLASHPOINT_API_ENABLED,
heartbeat: heartbeat.value.METADATA_SOURCES?.FLASHPOINT_API_HEARTBEAT,
},
{
name: "HowLongToBeat",
@@ -110,12 +107,6 @@ const metadataOptions = computed(() => [
class="mr-1"
size="small"
/>
<v-icon
v-if="source.heartbeat"
icon="mdi-check"
class="mr-1"
size="small"
/>
{{
source.disabled ? t("common.disabled") : t("common.enabled")
}}