Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Screenscraper integration #1416

Merged
merged 80 commits into from
Feb 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
80 commits
Select commit Hold shift + click to select a range
63fac78
screenscraper API test
zurdi15 Oct 25, 2024
95f0643
merge 'master' into feature/screenscraper-integration
zurdi15 Oct 25, 2024
b3a7ce3
Merge branch 'master' into feature/screenscraper-integration
zurdi15 Oct 25, 2024
3a5fd7d
Merge branch 'master' into feature/screenscraper-integration
zurdi15 Oct 25, 2024
7dfee48
added ss dev token
zurdi15 Oct 25, 2024
443def9
Merge remote-tracking branch 'origin/master' into feature/screenscrap…
zurdi15 Dec 10, 2024
374d11b
Merge remote-tracking branch 'origin/master' into feature/screenscrap…
zurdi15 Dec 26, 2024
1083dc6
Merge remote-tracking branch 'origin/master' into feature/screenscrap…
zurdi15 Dec 27, 2024
0d1b932
Merge branch 'master' into feature/screenscraper-integration
zurdi15 Jan 1, 2025
db4001d
feat: base structure for ss support
zurdi15 Jan 2, 2025
dca80cd
Merge branch 'master' into feature/screenscraper-integration
zurdi15 Jan 2, 2025
4aa2d49
feat: fully integrated screenscrapper
zurdi15 Jan 3, 2025
8ad86f2
Merge remote-tracking branch 'origin/master' into feature/screenscrap…
zurdi15 Jan 3, 2025
08de3b3
fix: typecheck
zurdi15 Jan 3, 2025
2c6a165
misc: improved manual match speed
zurdi15 Jan 3, 2025
a9f189b
Merge remote-tracking branch 'origin/master' into feature/screenscrap…
zurdi15 Jan 5, 2025
e7342ab
Merge remote-tracking branch 'origin/master' into feature/screenscrap…
zurdi15 Jan 6, 2025
d224729
fix: merge conflicts
zurdi15 Jan 6, 2025
23102b8
fix: trunk check
zurdi15 Jan 6, 2025
977d552
Merge remote-tracking branch 'origin/master' into feature/screenscrap…
zurdi15 Jan 8, 2025
a7a2e3b
Merge remote-tracking branch 'origin/master' into feature/screenscrap…
zurdi15 Jan 9, 2025
9d96501
Merge remote-tracking branch 'origin/master' into feature/screenscrap…
zurdi15 Jan 31, 2025
3a4c06f
fix: rebase migrations
zurdi15 Jan 31, 2025
a00eb1f
feat: add ScreenScrapper metadata to setup validation and improve sca…
zurdi15 Jan 31, 2025
cce279a
Merge branch 'master' into feature/screenscraper-integration
zurdi15 Feb 4, 2025
cb54ee3
fix: fixes from trunk
zurdi15 Feb 4, 2025
1089217
fix: adjust layout for CollectionInfoDrawer and improve Gallery view …
zurdi15 Feb 4, 2025
e4b24cb
Merge remote-tracking branch 'origin/master' into feature/screenscrap…
zurdi15 Feb 4, 2025
5f048a7
fix: center background image in Auth layout
zurdi15 Feb 4, 2025
402c19d
Merge remote-tracking branch 'origin/master' into feature/screenscrap…
zurdi15 Feb 4, 2025
78eb3f9
fix: adapt to new fs_name rom property
zurdi15 Feb 4, 2025
6f90916
fix: simplify scan stats display logic in Scan.vue
zurdi15 Feb 4, 2025
b550588
added png version of auth background
zurdi15 Feb 4, 2025
61dde02
Merge remote-tracking branch 'origin/master' into feature/screenscrap…
zurdi15 Feb 5, 2025
2914462
Merge remote-tracking branch 'origin/master' into feature/screenscrap…
zurdi15 Feb 5, 2025
2ee6026
Remove SCREENSCRAPER_API_KEY from configuration and handler, replace …
zurdi15 Feb 5, 2025
fc63e68
added grayscale version isotipos
zurdi15 Feb 5, 2025
8a27d62
Updated IDs for screenscraper
danblu3 Feb 5, 2025
ad179a9
Fix SLUG_TO_SS_ID declaration and update avatar style in Scan.vue
zurdi15 Feb 6, 2025
5951aec
Update platforms table with ss_id from SLUG_TO_SS_ID mapping
zurdi15 Feb 6, 2025
5caf3dd
reordered migrations to fit master branch
zurdi15 Feb 6, 2025
2f8beb6
Refactor metadata source handling to use enum for better type safety
zurdi15 Feb 6, 2025
2393e1e
Rename migration files and update platforms table with ss_id from SLU…
zurdi15 Feb 6, 2025
d96239c
Refactor scan functions to use MetadataSource enum for metadata sources
zurdi15 Feb 6, 2025
bd06865
Add support for Screenscraper ID in game details view
zurdi15 Feb 6, 2025
90435e2
Enhance game details view with improved chip layout and icons for IGD…
zurdi15 Feb 6, 2025
380a407
Refactor game details view to simplify ID display and enhance rating …
zurdi15 Feb 6, 2025
9e213bc
Refactor metadata source handling to use string types and update hear…
zurdi15 Feb 6, 2025
5b9a845
Merge branch 'master' into feature/screenscraper-integration
zurdi15 Feb 6, 2025
268bb82
Add Screenscraper ID support to PlatformSchema and update PlatformInf…
zurdi15 Feb 6, 2025
9ae3b7d
feat: added manuals to ss migration
zurdi15 Feb 6, 2025
0660502
feat: screenscraper manual support
zurdi15 Feb 6, 2025
b440846
chore: add TODO comment to extract PDF viewer to a separate component…
zurdi15 Feb 6, 2025
e08a42d
feat: refactor PDF viewer toolbar and update ID configuration
zurdi15 Feb 6, 2025
dca9c1b
feat: extracter pdf viewer to component
zurdi15 Feb 6, 2025
dcc6813
fix: DetailedRom type in PDFViewer component
zurdi15 Feb 6, 2025
22fa1f2
feat: enhance PDFViewer component with responsive toolbar adjustments
zurdi15 Feb 7, 2025
0a41a2f
fix: remove unused title-on-hover attribute in CollectionInfoDrawer c…
zurdi15 Feb 7, 2025
0cbebd6
fix: update translations for 'manual' to 'user manual' in multiple lo…
zurdi15 Feb 7, 2025
42ae385
feat: add manual upload feature in EditRom component
zurdi15 Feb 7, 2025
46678b1
feat: implement manual upload functionality for ROMs
zurdi15 Feb 7, 2025
d2c2fe6
Merge remote-tracking branch 'origin/master' into feature/screenscrap…
zurdi15 Feb 8, 2025
2876492
Add upload functionality and improve upload progress UI
zurdi15 Feb 8, 2025
e51dfc3
Merge remote-tracking branch 'origin/master' into feature/screenscrap…
zurdi15 Feb 10, 2025
d6dfc00
Refactor manual upload endpoint to improve file handling and director…
zurdi15 Feb 10, 2025
2e3858d
Merge branch 'master' into feature/screenscraper-integration
zurdi15 Feb 12, 2025
c33217b
Add ss_url_cover field to search response
zurdi15 Feb 12, 2025
8330786
Refactor API model types and add new request types for collections, f…
zurdi15 Feb 12, 2025
bd8a1f2
fix: ss migrations
zurdi15 Feb 12, 2025
81ad78b
Merge remote-tracking branch 'origin/master' into feature/screenscrap…
zurdi15 Feb 12, 2025
31fcdde
feat: enhance navigation drawers with improved layout and logout button
zurdi15 Feb 12, 2025
5733770
Merge remote-tracking branch 'origin/master' into feature/screenscrap…
zurdi15 Feb 14, 2025
b37bd53
Merge remote-tracking branch 'origin/master' into feature/screenscrap…
zurdi15 Feb 16, 2025
af1bd3e
fix: trunk fmt
zurdi15 Feb 16, 2025
a10191a
fix: rename clearAll to reset in uploadStore
zurdi15 Feb 16, 2025
c670136
Merge remote-tracking branch 'origin/master' into feature/screenscrap…
zurdi15 Feb 16, 2025
cfbcd82
fix: changes based on review
zurdi15 Feb 17, 2025
7e4ea03
Merge remote-tracking branch 'origin/master' into feature/screenscrap…
zurdi15 Feb 17, 2025
b260acb
Fix formatting issues and enhance cover image fallback logic in Game …
zurdi15 Feb 17, 2025
2570e8a
fix ts error
gantoine Feb 17, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
{
"label": "Setup testing environment",
"type": "shell",
"command": "export $(cat .env | grep DB_ROOT_PASSWD | xargs) && docker exec -i mariadb mariadb -u root -p$DB_ROOT_PASSWD < backend/romm_test/setup.sql",
"command": "export $(cat .env | grep DB_ROOT_PASSWD | xargs) && docker exec -i romm-db-dev mariadb -u root -p$DB_ROOT_PASSWD < backend/romm_test/setup.sql",
"problemMatcher": []
},
{
Expand Down
2 changes: 1 addition & 1 deletion backend/alembic/versions/0030_user_email_null.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Change empty string in users.email to NULL.
Revision ID: 951473b0c581
Revision ID: 0030_user_email_null
Revises: 0029_platforms_custom_name
Create Date: 2025-01-14 01:30:39.696257
Expand Down
46 changes: 46 additions & 0 deletions backend/alembic/versions/0035_screenscraper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"""empty message

Revision ID: 0035_screenscraper
Revises: 0034_virtual_collections_db_view
Create Date: 2025-01-02 18:58:55.557123

"""

import sqlalchemy as sa
from alembic import op
from utils.database import CustomJSON

# revision identifiers, used by Alembic.
revision = "0035_screenscraper"
down_revision = "0034_virtual_collections_db_view"
branch_labels = None
depends_on = None


def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table("platforms", schema=None) as batch_op:
batch_op.add_column(sa.Column("ss_id", sa.Integer(), nullable=True))

with op.batch_alter_table("roms", schema=None) as batch_op:
batch_op.add_column(sa.Column("ss_id", sa.Integer(), nullable=True))
batch_op.add_column(sa.Column("ss_metadata", CustomJSON(), nullable=True))
batch_op.add_column(sa.Column("url_manual", sa.Text(), nullable=True)),
batch_op.add_column(sa.Column("path_manual", sa.Text(), nullable=True)),
batch_op.create_index("idx_roms_ss_id", ["ss_id"], unique=False)
# ### end Alembic commands ###


def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table("roms", schema=None) as batch_op:
batch_op.drop_index("idx_roms_ss_id")
batch_op.drop_column("ss_id")
batch_op.drop_column("ss_metadata")
batch_op.drop_column("url_manual")
batch_op.drop_column("path_manual")
batch_op.drop_index("idx_roms_ss_id")

with op.batch_alter_table("platforms", schema=None) as batch_op:
batch_op.drop_column("ss_id")
# ### end Alembic commands ###
34 changes: 34 additions & 0 deletions backend/alembic/versions/0036_screenscraper_platforms_id.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""empty message

Revision ID: 0036_screenscraper_platforms_id
Revises: 0035_screenscraper
Create Date: 2025-01-02 18:58:55.557123

"""

import sqlalchemy as sa
from alembic import op
from handler.metadata.ss_handler import SLUG_TO_SS_ID

# revision identifiers, used by Alembic.
revision = "0036_screenscraper_platforms_id"
down_revision = "0035_screenscraper"
branch_labels = None
depends_on = None


def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
connection = op.get_bind()
for slug, ss_id in SLUG_TO_SS_ID.items():
connection.execute(
sa.text("UPDATE platforms SET ss_id = :ss_id WHERE slug = :slug"),
{"ss_id": ss_id["id"], "slug": slug},
)
# ### end Alembic commands ###


def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
pass
# ### end Alembic commands ###
4 changes: 4 additions & 0 deletions backend/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ def str_to_bool(value: str) -> bool:
"IGDB_CLIENT_SECRET", os.environ.get("CLIENT_SECRET", "")
)

# SCREENSCRAPER
SCREENSCRAPER_USER: Final = os.environ.get("SCREENSCRAPER_USER", "")
SCREENSCRAPER_PASSWORD: Final = os.environ.get("SCREENSCRAPER_PASSWORD", "")

# STEAMGRIDDB
STEAMGRIDDB_API_KEY: Final = os.environ.get("STEAMGRIDDB_API_KEY", "")

Expand Down
4 changes: 2 additions & 2 deletions backend/endpoints/collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ async def add_collection(
)
else:
path_cover_s, path_cover_l = await fs_resource_handler.get_cover(
overwrite=True,
entity=_added_collection,
overwrite=True,
url_cover=_added_collection.url_cover,
)

Expand Down Expand Up @@ -247,8 +247,8 @@ async def update_collection(
{"url_cover": data.get("url_cover", collection.url_cover)}
)
path_cover_s, path_cover_l = await fs_resource_handler.get_cover(
overwrite=True,
entity=collection,
overwrite=True,
url_cover=data.get("url_cover", ""), # type: ignore
)
cleaned_data.update(
Expand Down
6 changes: 5 additions & 1 deletion backend/endpoints/heartbeat.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from handler.metadata.igdb_handler import IGDB_API_ENABLED
from handler.metadata.moby_handler import MOBY_API_ENABLED
from handler.metadata.sgdb_handler import STEAMGRIDDB_API_ENABLED
from handler.metadata.ss_handler import SS_API_ENABLED
from utils import get_version
from utils.router import APIRouter

Expand All @@ -40,9 +41,12 @@ def heartbeat() -> HeartbeatResponse:
"SHOW_SETUP_WIZARD": len(db_user_handler.get_admin_users()) == 0,
},
"METADATA_SOURCES": {
"ANY_SOURCE_ENABLED": IGDB_API_ENABLED or MOBY_API_ENABLED,
"ANY_SOURCE_ENABLED": IGDB_API_ENABLED
or MOBY_API_ENABLED
or SS_API_ENABLED,
"IGDB_API_ENABLED": IGDB_API_ENABLED,
"MOBY_API_ENABLED": MOBY_API_ENABLED,
"SS_API_ENABLED": SS_API_ENABLED,
"STEAMGRIDDB_ENABLED": STEAMGRIDDB_API_ENABLED,
},
"FILESYSTEM": {
Expand Down
1 change: 1 addition & 0 deletions backend/endpoints/responses/heartbeat.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class MetadataSourcesDict(TypedDict):
ANY_SOURCE_ENABLED: bool
IGDB_API_ENABLED: bool
MOBY_API_ENABLED: bool
SS_API_ENABLED: bool
STEAMGRIDDB_ENABLED: bool


Expand Down
1 change: 1 addition & 0 deletions backend/endpoints/responses/platform.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class PlatformSchema(BaseModel):
igdb_id: int | None = None
sgdb_id: int | None = None
moby_id: int | None = None
ss_id: int | None = None
category: str | None = None
generation: int | None = None
family_name: str | None = None
Expand Down
13 changes: 13 additions & 0 deletions backend/endpoints/responses/rom.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from fastapi import Request
from handler.metadata.igdb_handler import IGDBMetadata
from handler.metadata.moby_handler import MobyMetadata
from handler.metadata.ss_handler import SSMetadata
from models.rom import Rom, RomFileCategory, RomUserStatus
from pydantic import computed_field

Expand All @@ -26,6 +27,11 @@
dict((k, NotRequired[v]) for k, v in get_type_hints(MobyMetadata).items()),
total=False,
)
RomSSMetadata = TypedDict( # type: ignore[misc]
"RomSSMetadata",
dict((k, NotRequired[v]) for k, v in get_type_hints(SSMetadata).items()),
total=False,
)


def rom_user_schema_factory() -> RomUserSchema:
Expand Down Expand Up @@ -120,6 +126,7 @@ class RomSchema(BaseModel):
igdb_id: int | None
sgdb_id: int | None
moby_id: int | None
ss_id: int | None

platform_id: int
platform_slug: str
Expand Down Expand Up @@ -152,10 +159,16 @@ class RomSchema(BaseModel):
age_ratings: list[str]
igdb_metadata: RomIGDBMetadata | None
moby_metadata: RomMobyMetadata | None
ss_metadata: RomSSMetadata | None

path_cover_small: str | None
path_cover_large: str | None
url_cover: str | None

has_manual: bool
path_manual: str | None
url_manual: str | None

is_unidentified: bool

revision: str | None
Expand Down
2 changes: 2 additions & 0 deletions backend/endpoints/responses/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
class SearchRomSchema(BaseModel):
igdb_id: int | None = None
moby_id: int | None = None
ss_id: int | None = None
slug: str
name: str
summary: str
igdb_url_cover: str = ""
moby_url_cover: str = ""
ss_url_cover: str = ""
platform_id: int


Expand Down
99 changes: 94 additions & 5 deletions backend/endpoints/rom.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import binascii
import os
from base64 import b64encode
from datetime import datetime, timezone
from io import BytesIO
Expand Down Expand Up @@ -31,7 +32,8 @@
from handler.database import db_platform_handler, db_rom_handler
from handler.filesystem import fs_resource_handler, fs_rom_handler
from handler.filesystem.base_handler import CoverSize
from handler.metadata import meta_igdb_handler, meta_moby_handler
from handler.metadata import meta_igdb_handler, meta_moby_handler, meta_ss_handler
from logger.formatter import highlight as hl
from logger.logger import log
from models.rom import Rom, RomFile, RomUser
from PIL import Image
Expand Down Expand Up @@ -492,16 +494,19 @@ async def update_rom(
"igdb_id": None,
"sgdb_id": None,
"moby_id": None,
"ss_id": None,
"name": rom.fs_name,
"summary": "",
"url_screenshots": [],
"path_screenshots": [],
"path_cover_s": "",
"path_cover_l": "",
"url_cover": "",
"url_manual": "",
"slug": "",
"igdb_metadata": {},
"moby_metadata": {},
"ss_metadata": {},
"revision": "",
},
)
Expand All @@ -515,6 +520,7 @@ async def update_rom(
cleaned_data: dict[str, Any] = {
"igdb_id": data.get("igdb_id", rom.igdb_id),
"moby_id": data.get("moby_id", rom.moby_id),
"ss_id": data.get("ss_id", rom.ss_id),
}

moby_id = cleaned_data["moby_id"]
Expand All @@ -527,9 +533,23 @@ async def update_rom(
)
cleaned_data.update({"path_screenshots": path_screenshots})

igdb_id = cleaned_data["igdb_id"]
if igdb_id and int(igdb_id) != rom.igdb_id:
igdb_rom = await meta_igdb_handler.get_rom_by_id(int(igdb_id))
if (
cleaned_data.get("ss_id", "")
and int(cleaned_data.get("ss_id", "")) != rom.ss_id
):
ss_rom = await meta_ss_handler.get_rom_by_id(cleaned_data["ss_id"])
cleaned_data.update(ss_rom)
path_screenshots = await fs_resource_handler.get_rom_screenshots(
rom=rom,
url_screenshots=cleaned_data.get("url_screenshots", []),
)
cleaned_data.update({"path_screenshots": path_screenshots})

if (
cleaned_data.get("igdb_id", "")
and int(cleaned_data.get("igdb_id", "")) != rom.igdb_id
):
igdb_rom = await meta_igdb_handler.get_rom_by_id(cleaned_data["igdb_id"])
cleaned_data.update(igdb_rom)
path_screenshots = await fs_resource_handler.get_rom_screenshots(
rom=rom,
Expand Down Expand Up @@ -613,14 +633,29 @@ async def update_rom(
):
cleaned_data.update({"url_cover": data.get("url_cover", rom.url_cover)})
path_cover_s, path_cover_l = await fs_resource_handler.get_cover(
overwrite=True,
entity=rom,
overwrite=True,
url_cover=str(data.get("url_cover") or ""),
)
cleaned_data.update(
{"path_cover_s": path_cover_s, "path_cover_l": path_cover_l}
)

if data.get("url_manual", "") != rom.url_manual or not (
await fs_resource_handler.manual_exists(rom)
):
cleaned_data.update({"url_manual": data.get("url_manual", rom.url_manual)})
url_manual = await fs_resource_handler.get_manual(
rom=rom,
overwrite=True,
url_manual=str(data.get("url_manual") or ""),
)
cleaned_data.update({"url_manual": url_manual})

log.debug(
f"Updating {hl(cleaned_data.get('name', ''))} [{id}] with data {cleaned_data}"
)

db_rom_handler.update_rom(id, cleaned_data)
rom = db_rom_handler.get_rom(id)
if not rom:
Expand All @@ -629,6 +664,60 @@ async def update_rom(
return DetailedRomSchema.from_orm_with_request(rom, request)


@protected_route(router.post, "/{id}/manuals", [Scope.ROMS_WRITE])
async def add_rom_manuals(request: Request, id: int):
"""Upload manuals for a rom

Args:
request (Request): Fastapi Request object

Raises:
HTTPException: No files were uploaded
"""
rom = db_rom_handler.get_rom(id)
if not rom:
raise RomNotFoundInDatabaseException(id)

filename = request.headers.get("x-upload-filename")

manuals_path = f"{RESOURCES_BASE_PATH}/{rom.fs_resources_path}/manual"
file_location = Path(f"{manuals_path}/{rom.id}.pdf")
log.info(f"Uploading {file_location}")

if not os.path.exists(manuals_path):
await Path(manuals_path).mkdir(parents=True, exist_ok=True)

parser = StreamingFormDataParser(headers=request.headers)
parser.register("x-upload-platform", NullTarget())
parser.register(filename, FileTarget(str(file_location)))

async def cleanup_partial_file():
if await file_location.exists():
await file_location.unlink()

try:
async for chunk in request.stream():
parser.data_received(chunk)
except ClientDisconnect:
log.error("Client disconnected during upload")
await cleanup_partial_file()
except Exception as exc:
log.error("Error uploading files", exc_info=exc)
await cleanup_partial_file()
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="There was an error uploading the file(s)",
) from exc

path_manual = await fs_resource_handler.get_manual(
rom=rom, overwrite=False, url_manual=None
)

db_rom_handler.update_rom(id, {"path_manual": path_manual})

return Response(status_code=status.HTTP_201_CREATED)


@protected_route(router.post, "/delete", [Scope.ROMS_WRITE])
async def delete_roms(
request: Request,
Expand Down
Loading
Loading