Add tests for mobygames, sgdb and ssfr service adapter

add ssfr and sgdb service test
This commit is contained in:
Georges-Antoine Assi
2025-07-22 23:35:57 -04:00
parent 71fed65f84
commit b92ecd8d13
31 changed files with 64123 additions and 22 deletions

View File

@@ -5,6 +5,7 @@ from collections.abc import AsyncIterator, Collection
from typing import Literal, cast
import aiohttp
import aiohttp.client_exceptions
import yarl
from adapters.services.steamgriddb_types import (
SGDBDimension,
@@ -58,8 +59,10 @@ class SteamGridDBService:
)
res.raise_for_status()
return await res.json()
except aiohttp.ClientResponseError as exc:
except aiohttp.client_exceptions.ClientResponseError as exc:
print(f"Request failed with status {exc.status} for URL: {url}")
if exc.status == http.HTTPStatus.UNAUTHORIZED:
print("Invalid API key or unauthorized access.")
raise SGDBInvalidAPIKeyException from exc
# Log the error and return an empty dict if the request fails with a different code
log.error(exc)
@@ -112,7 +115,8 @@ class SteamGridDBService:
if page_number is not None:
params["page"] = [str(page_number)]
url = self.url.joinpath("grids/game", str(game_id)).with_query(**params)
base_url = self.url.joinpath("grids/game", str(game_id))
url = base_url.with_query(**params) if params else base_url
response = await self._request(str(url))
if not response:
return SGDBGridList(

View File

@@ -0,0 +1,43 @@
interactions:
- request:
body: null
headers: {}
method: GET
uri: https://api.mobygames.com/v1/games?id=999999&format=normal
response:
body:
string:
'{"code":401,"error":"Authorization required","message":"You must have
an API key to use this resource."}
'
headers:
CF-RAY:
- 963b9278fab3544f-YYZ
Cf-Cache-Status:
- DYNAMIC
Connection:
- keep-alive
Content-Length:
- "105"
Content-Type:
- application/json
Date:
- Wed, 23 Jul 2025 13:37:23 GMT
Nel:
- '{"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}'
Report-To:
- '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=IREPYBRjyxV9xuhHi5Vquq%2FEtCBarJCxOMSPPc%2FGTKYYv28m%2BfL5Qalu0oL%2FpFSaMGeRRsi7lXj5fkybujCYJKBmg49O%2B26HoYlHirpw"}]}'
Server:
- cloudflare
Set-Cookie:
- session-www=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.xxxxxx.xxxxxxxxxxxxxxxxxxxxxx;
HttpOnly; SameSite=Lax; Path=/; Domain=mobygames.com
Vary:
- Cookie
WWW-Authenticate:
- Basic realm="MobyGames API key (no password)"
status:
code: 401
message: Unauthorized
version: 1

View File

@@ -0,0 +1,59 @@
interactions:
- request:
body: null
headers: {}
method: GET
uri: https://api.mobygames.com/v1/games?format=brief&limit=3
response:
body:
string:
'{"games":[{"game_id":1,"moby_url":"https://www.mobygames.com/game/1/the-x-files-game/","title":"The
X-Files Game"},{"game_id":2,"moby_url":"https://www.mobygames.com/game/2/who-framed-roger-rabbit/","title":"Who
Framed Roger Rabbit"},{"game_id":3,"moby_url":"https://www.mobygames.com/game/3/wing-commander/","title":"Wing
Commander"}]}
'
headers:
CF-RAY:
- 963b8a9f79ca36a0-YYZ
Cf-Cache-Status:
- DYNAMIC
Connection:
- keep-alive
Content-Encoding:
- br
Content-Security-Policy:
- frame-ancestors 'none';
Content-Type:
- application/json
Date:
- Wed, 23 Jul 2025 13:32:01 GMT
Nel:
- '{"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}'
Ratelimit-Limit:
- "720"
Ratelimit-Policy:
- 1;w=1, 720;w=3600
Ratelimit-Remaining:
- "314"
Ratelimit-Reset:
- "1679"
Report-To:
- '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=mcmYDbCrD3jPELg2NLm%2BjYkbXdj33QDYigpwtKqeA5jslmTS%2Fn%2BWLh1BovEFmT7XNA%2F6KZdRdXLFbX1dInGwdHUy8bZo%2B9MNYZPzOogS"}]}'
Server:
- cloudflare
Set-Cookie:
- session-www=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.xxxxxx.xxxxxxxxxxxxxxxxxxxxxx;
HttpOnly; SameSite=Lax; Path=/; Domain=mobygames.com
Transfer-Encoding:
- chunked
Vary:
- Cookie
X-Frame-Options:
- DENY
X-Xss-Protection:
- 1; mode=block
status:
code: 200
message: OK
version: 1

View File

@@ -0,0 +1,55 @@
interactions:
- request:
body: null
headers: {}
method: GET
uri: https://api.mobygames.com/v1/games?format=id&limit=5
response:
body:
string: '{"games":[1,2,3,4,5]}
'
headers:
CF-RAY:
- 963b8aced955a202-YYZ
Cf-Cache-Status:
- DYNAMIC
Connection:
- keep-alive
Content-Encoding:
- br
Content-Security-Policy:
- frame-ancestors 'none';
Content-Type:
- application/json
Date:
- Wed, 23 Jul 2025 13:32:09 GMT
Nel:
- '{"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}'
Ratelimit-Limit:
- "720"
Ratelimit-Policy:
- 1;w=1, 720;w=3600
Ratelimit-Remaining:
- "311"
Ratelimit-Reset:
- "1671"
Report-To:
- '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=7WqR9E5idJevIDcTjt%2F9tbyCEpUuxCRu1EYJXEDPq9H8sAY2WpKuB%2FNsY6GRPrwcG34ZFlJsQUGFSRSDPUWMyR2IqhDl5NLRLIK35js%2B"}]}'
Server:
- cloudflare
Set-Cookie:
- session-www=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.xxxxxx.xxxxxxxxxxxxxxxxxxxxxx;
HttpOnly; SameSite=Lax; Path=/; Domain=mobygames.com
Transfer-Encoding:
- chunked
Vary:
- Cookie
X-Frame-Options:
- DENY
X-Xss-Protection:
- 1; mode=block
status:
code: 200
message: OK
version: 1

View File

@@ -0,0 +1,118 @@
interactions:
- request:
body: null
headers: {}
method: GET
uri: https://api.mobygames.com/v1/games?format=normal&limit=2
response:
body:
string:
'{"games":[{"alternate_titles":[{"description":"Finnish title","title":"Salaiset
Kansiot"},{"description":"PSX title","title":"The X Files"},{"description":"Spanish
title","title":"The X-Files: Expediente X - El Juego"},{"description":"French
title","title":"The X-Files: Le Jeu"}],"description":"<p>As an extension of
one of the most long-running television series of all time, <em>The X-Files</em>,
play through the eyes of Special Agent Craig Willmore, a new FBI field investigator
brought in to locate missing agents Fox Mulder and Dana Scully whose last
location was the Everett, Washington, area. In this \"movie quality\" video
production, characters are played by the actors and actresses from the show,
including <a href=\"https://www.mobygames.com/person/9411/gillian-anderson/\">Gillian
Anderson</a> (Scully) and <a href=\"https://www.mobygames.com/person/9412/david-duchovny/\">David
Duchovny</a> (Mulder).</p>\n<p>As the game begins, you are given a briefing
of your mission. Gather up all state-of-the-art spy tools (night vision goggles,
a digital camera, PDA, lock picks, evidence kit, a standard issue revolver,
handcuffs and badge) and then head out to follow their trail. As you explore
the various locations, take photographs, pick up pieces of evidence and talk
with people. Use your Newton PDA to access the navigational map, to make notes
and send/receive e-mail. Trace telephone numbers, run background checks and
license plate ids and even post an All Points Bulletin on missing persons
using the computer network at your home or office. By using photo viewer software,
download field photographs to the computer where they can be enlarged and
studied more closely for clues.</p>\n<p>Features include emotion icons for
interjecting different tones during conversations (mean, humorous or technical)
which effect the answer given. An in-game hint system, Artificial Intelligence,
can be set on or off. In addition, there are multiple endings as a direct
result of actions you take.</p>","game_id":1,"genres":[{"genre_category":"Basic
Genres","genre_category_id":1,"genre_id":2,"genre_name":"Adventure"},{"genre_category":"Perspective","genre_category_id":2,"genre_id":7,"genre_name":"1st-person"},{"genre_category":"Art
Style","genre_category_id":13,"genre_id":217,"genre_name":"Live action"},{"genre_category":"Setting","genre_category_id":10,"genre_id":8,"genre_name":"Sci-fi
/ futuristic"},{"genre_category":"Narrative Theme/Topic","genre_category_id":8,"genre_id":55,"genre_name":"Detective
/ mystery"},{"genre_category":"Other Attributes","genre_category_id":6,"genre_id":82,"genre_name":"Licensed"}],"moby_score":7.1,"moby_url":"https://www.mobygames.com/game/1/the-x-files-game/","num_votes":57,"official_url":"https://www.hyperbole.com/xsite/","platforms":[{"first_release_date":"1998","platform_id":3,"platform_name":"Windows"},{"first_release_date":"1999","platform_id":6,"platform_name":"PlayStation"},{"first_release_date":"1998-06","platform_id":74,"platform_name":"Macintosh"}],"sample_cover":{"height":800,"image":"https://cdn.mobygames.com/covers/4062982-the-x-files-game-windows-front-cover.jpg","platforms":["Windows","Macintosh"],"thumbnail_image":"https://cdn.mobygames.com/872aed6c-aba4-11ed-a188-02420a00019a.webp","width":690},"sample_screenshots":[{"caption":"Outside
Agent Craig Willmore''s apartment ","height":480,"image":"https://cdn.mobygames.com/screenshots/11077669-the-x-files-game-windows-outside-agent-craig-willmores-apartment.jpg","thumbnail_image":"https://cdn.mobygames.com/93317876-ac15-11ed-b444-02420a000134.webp","width":640},{"caption":"Arriving
at Dockside Warehouse","height":480,"image":"https://cdn.mobygames.com/screenshots/11072419-the-x-files-game-windows-arriving-at-dockside-warehouse.jpg","thumbnail_image":"https://cdn.mobygames.com/916394e8-ac15-11ed-803a-02420a000131.webp","width":640},{"caption":"Boat
dock","height":480,"image":"https://cdn.mobygames.com/screenshots/11076046-the-x-files-game-windows-boat-dock.jpg","thumbnail_image":"https://cdn.mobygames.com/9a3b5ef2-ac15-11ed-b075-02420a00012f.webp","width":640},{"caption":"Medical
examiner and very dead James Wong","height":480,"image":"https://cdn.mobygames.com/screenshots/11073361-the-x-files-game-windows-medical-examiner-and-very-dead-james-wo.jpg","thumbnail_image":"https://cdn.mobygames.com/8f6b8eb6-ac15-11ed-833b-02420a000131.webp","width":640},{"caption":"Main
Menu","height":576,"image":"https://cdn.mobygames.com/screenshots/10470736-the-x-files-game-playstation-main-menu.jpg","thumbnail_image":"https://cdn.mobygames.com/6cfd26d2-ac10-11ed-803a-02420a000131.webp","width":720}],"title":"The
X-Files Game"},{"alternate_titles":[],"description":"<p>Roger Rabbit has been
framed for the murder of Marvin Acme, head of the Acme Corporation. Acme''s
will states that upon his death, Toon Town would be left to the Toons, but
the will is nowhere to be found. You have to find the will and save your wife,
Jessica, from Judge Doom and his weasels.</p>\n<p>The game takes place in
Hollywood, 1947, where Toons are alive. There are 4 levels in the game, 2
of which are driving levels (levels 1 &amp; 3). Roger and Benny the cab have
to beat the weasels to the destination, while dodging cars, trams and Judge
Doom''s dip which is scattered on the road. There are pick-ups to help you
on your way.</p>\n<p>Level 2 is the Ink &amp; Paint Club. The will is on one
of the tables. Roger has to pick up all the pieces of paper the penguin waiters
put down, whilst avoiding the alcohol and gorilla bouncer.</p>\n<p>Level 4
has you in Judge Doom''s warehouse trying to save your wife, Jessica, from
the dip truck. You have to use gags to progress and make the weasels laugh
themselves to death, literally!</p>","game_id":2,"genres":[{"genre_category":"Basic
Genres","genre_category_id":1,"genre_id":1,"genre_name":"Action"},{"genre_category":"Gameplay","genre_category_id":4,"genre_id":9,"genre_name":"Arcade"},{"genre_category":"Other
Attributes","genre_category_id":6,"genre_id":82,"genre_name":"Licensed"}],"moby_score":5.9,"moby_url":"https://www.mobygames.com/game/2/who-framed-roger-rabbit/","num_votes":34,"official_url":null,"platforms":[{"first_release_date":"1988","platform_id":2,"platform_name":"DOS"},{"first_release_date":"1988","platform_id":19,"platform_name":"Amiga"},{"first_release_date":"1988","platform_id":24,"platform_name":"Atari
ST"},{"first_release_date":"1988","platform_id":27,"platform_name":"Commodore
64"},{"first_release_date":"1988","platform_id":31,"platform_name":"Apple
II"}],"sample_cover":{"height":700,"image":"https://cdn.mobygames.com/covers/4068119-who-framed-roger-rabbit-amiga-front-cover.jpg","platforms":["Amiga"],"thumbnail_image":"https://cdn.mobygames.com/b59d6706-aba4-11ed-ba50-02420a000199.webp","width":467},"sample_screenshots":[{"caption":"But
don''t let the Waiter catch you. (CGA)","height":200,"image":"https://cdn.mobygames.com/screenshots/7288598-who-framed-roger-rabbit-dos-but-dont-let-the-waiter-catch-you-cg.png","thumbnail_image":"https://cdn.mobygames.com/272f1ce8-abf5-11ed-92cb-02420a000132.webp","width":320},{"caption":"The
map of the city and where you need to go. Your remaining lives are represented
by empty spaces for dip barrels","height":375,"image":"https://cdn.mobygames.com/screenshots/445776-who-framed-roger-rabbit-amiga-the-map-of-the-city-and-where-you-.png","thumbnail_image":"https://cdn.mobygames.com/2750450e-ab6d-11ed-b165-02420a000198.webp","width":640},{"caption":"Title
screen","height":375,"image":"https://cdn.mobygames.com/screenshots/445808-who-framed-roger-rabbit-amiga-title-screen.png","thumbnail_image":"https://cdn.mobygames.com/2762b126-ab6d-11ed-b325-02420a00019e.webp","width":640},{"caption":"Pick
up the pieces of paper and dodge the gorilla and alcohol","height":375,"image":"https://cdn.mobygames.com/screenshots/445448-who-framed-roger-rabbit-amiga-pick-up-the-pieces-of-paper-and-do.png","thumbnail_image":"https://cdn.mobygames.com/26a9c35a-ab6d-11ed-ba04-02420a00019f.webp","width":640},{"caption":"Roger
must collect all the pieces of paper from the table. Avoid the gorilla bouncer
(EGA)","height":200,"image":"https://cdn.mobygames.com/screenshots/442192-who-framed-roger-rabbit-dos-roger-must-collect-all-the-pieces-of.png","thumbnail_image":"https://cdn.mobygames.com/2039dc44-ab6d-11ed-9ab3-02420a0001a0.webp","width":320}],"title":"Who
Framed Roger Rabbit"}]}
'
headers:
CF-RAY:
- 963b8ef1eee6369c-YYZ
Cf-Cache-Status:
- DYNAMIC
Connection:
- keep-alive
Content-Encoding:
- br
Content-Security-Policy:
- frame-ancestors 'none';
Content-Type:
- application/json
Date:
- Wed, 23 Jul 2025 13:34:59 GMT
Nel:
- '{"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}'
Ratelimit-Limit:
- "720"
Ratelimit-Policy:
- 1;w=1, 720;w=3600
Ratelimit-Remaining:
- "253"
Ratelimit-Reset:
- "1501"
Report-To:
- '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=bNtxtmMrn0FmS62RuueXYOcA9z8srPXnBwUSNkIGraJq86Ly7EjaKGeapwllYC9SkAGE59wKSBWTeeqbGIXE%2BVAe%2Bs4951vfHuHTX9QO"}]}'
Server:
- cloudflare
Set-Cookie:
- session-www=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.xxxxxx.xxxxxxxxxxxxxxxxxxxxxx;
HttpOnly; SameSite=Lax; Path=/; Domain=mobygames.com
Transfer-Encoding:
- chunked
Vary:
- Cookie
X-Frame-Options:
- DENY
X-Xss-Protection:
- 1; mode=block
status:
code: 200
message: OK
version: 1

View File

@@ -0,0 +1,225 @@
interactions:
- request:
body: null
headers: {}
method: GET
uri: https://api.mobygames.com/v1/games?format=normal&limit=5
response:
body:
string:
'{"games":[{"alternate_titles":[{"description":"Finnish title","title":"Salaiset
Kansiot"},{"description":"PSX title","title":"The X Files"},{"description":"Spanish
title","title":"The X-Files: Expediente X - El Juego"},{"description":"French
title","title":"The X-Files: Le Jeu"}],"description":"<p>As an extension of
one of the most long-running television series of all time, <em>The X-Files</em>,
play through the eyes of Special Agent Craig Willmore, a new FBI field investigator
brought in to locate missing agents Fox Mulder and Dana Scully whose last
location was the Everett, Washington, area. In this \"movie quality\" video
production, characters are played by the actors and actresses from the show,
including <a href=\"https://www.mobygames.com/person/9411/gillian-anderson/\">Gillian
Anderson</a> (Scully) and <a href=\"https://www.mobygames.com/person/9412/david-duchovny/\">David
Duchovny</a> (Mulder).</p>\n<p>As the game begins, you are given a briefing
of your mission. Gather up all state-of-the-art spy tools (night vision goggles,
a digital camera, PDA, lock picks, evidence kit, a standard issue revolver,
handcuffs and badge) and then head out to follow their trail. As you explore
the various locations, take photographs, pick up pieces of evidence and talk
with people. Use your Newton PDA to access the navigational map, to make notes
and send/receive e-mail. Trace telephone numbers, run background checks and
license plate ids and even post an All Points Bulletin on missing persons
using the computer network at your home or office. By using photo viewer software,
download field photographs to the computer where they can be enlarged and
studied more closely for clues.</p>\n<p>Features include emotion icons for
interjecting different tones during conversations (mean, humorous or technical)
which effect the answer given. An in-game hint system, Artificial Intelligence,
can be set on or off. In addition, there are multiple endings as a direct
result of actions you take.</p>","game_id":1,"genres":[{"genre_category":"Basic
Genres","genre_category_id":1,"genre_id":2,"genre_name":"Adventure"},{"genre_category":"Perspective","genre_category_id":2,"genre_id":7,"genre_name":"1st-person"},{"genre_category":"Art
Style","genre_category_id":13,"genre_id":217,"genre_name":"Live action"},{"genre_category":"Setting","genre_category_id":10,"genre_id":8,"genre_name":"Sci-fi
/ futuristic"},{"genre_category":"Narrative Theme/Topic","genre_category_id":8,"genre_id":55,"genre_name":"Detective
/ mystery"},{"genre_category":"Other Attributes","genre_category_id":6,"genre_id":82,"genre_name":"Licensed"}],"moby_score":7.1,"moby_url":"https://www.mobygames.com/game/1/the-x-files-game/","num_votes":57,"official_url":"https://www.hyperbole.com/xsite/","platforms":[{"first_release_date":"1998","platform_id":3,"platform_name":"Windows"},{"first_release_date":"1999","platform_id":6,"platform_name":"PlayStation"},{"first_release_date":"1998-06","platform_id":74,"platform_name":"Macintosh"}],"sample_cover":{"height":800,"image":"https://cdn.mobygames.com/covers/4062982-the-x-files-game-windows-front-cover.jpg","platforms":["Windows","Macintosh"],"thumbnail_image":"https://cdn.mobygames.com/872aed6c-aba4-11ed-a188-02420a00019a.webp","width":690},"sample_screenshots":[{"caption":"Outside
Agent Craig Willmore''s apartment ","height":480,"image":"https://cdn.mobygames.com/screenshots/11077669-the-x-files-game-windows-outside-agent-craig-willmores-apartment.jpg","thumbnail_image":"https://cdn.mobygames.com/93317876-ac15-11ed-b444-02420a000134.webp","width":640},{"caption":"Arriving
at Dockside Warehouse","height":480,"image":"https://cdn.mobygames.com/screenshots/11072419-the-x-files-game-windows-arriving-at-dockside-warehouse.jpg","thumbnail_image":"https://cdn.mobygames.com/916394e8-ac15-11ed-803a-02420a000131.webp","width":640},{"caption":"Boat
dock","height":480,"image":"https://cdn.mobygames.com/screenshots/11076046-the-x-files-game-windows-boat-dock.jpg","thumbnail_image":"https://cdn.mobygames.com/9a3b5ef2-ac15-11ed-b075-02420a00012f.webp","width":640},{"caption":"Medical
examiner and very dead James Wong","height":480,"image":"https://cdn.mobygames.com/screenshots/11073361-the-x-files-game-windows-medical-examiner-and-very-dead-james-wo.jpg","thumbnail_image":"https://cdn.mobygames.com/8f6b8eb6-ac15-11ed-833b-02420a000131.webp","width":640},{"caption":"Main
Menu","height":576,"image":"https://cdn.mobygames.com/screenshots/10470736-the-x-files-game-playstation-main-menu.jpg","thumbnail_image":"https://cdn.mobygames.com/6cfd26d2-ac10-11ed-803a-02420a000131.webp","width":720}],"title":"The
X-Files Game"},{"alternate_titles":[],"description":"<p>Roger Rabbit has been
framed for the murder of Marvin Acme, head of the Acme Corporation. Acme''s
will states that upon his death, Toon Town would be left to the Toons, but
the will is nowhere to be found. You have to find the will and save your wife,
Jessica, from Judge Doom and his weasels.</p>\n<p>The game takes place in
Hollywood, 1947, where Toons are alive. There are 4 levels in the game, 2
of which are driving levels (levels 1 &amp; 3). Roger and Benny the cab have
to beat the weasels to the destination, while dodging cars, trams and Judge
Doom''s dip which is scattered on the road. There are pick-ups to help you
on your way.</p>\n<p>Level 2 is the Ink &amp; Paint Club. The will is on one
of the tables. Roger has to pick up all the pieces of paper the penguin waiters
put down, whilst avoiding the alcohol and gorilla bouncer.</p>\n<p>Level 4
has you in Judge Doom''s warehouse trying to save your wife, Jessica, from
the dip truck. You have to use gags to progress and make the weasels laugh
themselves to death, literally!</p>","game_id":2,"genres":[{"genre_category":"Basic
Genres","genre_category_id":1,"genre_id":1,"genre_name":"Action"},{"genre_category":"Gameplay","genre_category_id":4,"genre_id":9,"genre_name":"Arcade"},{"genre_category":"Other
Attributes","genre_category_id":6,"genre_id":82,"genre_name":"Licensed"}],"moby_score":5.9,"moby_url":"https://www.mobygames.com/game/2/who-framed-roger-rabbit/","num_votes":34,"official_url":null,"platforms":[{"first_release_date":"1988","platform_id":2,"platform_name":"DOS"},{"first_release_date":"1988","platform_id":19,"platform_name":"Amiga"},{"first_release_date":"1988","platform_id":24,"platform_name":"Atari
ST"},{"first_release_date":"1988","platform_id":27,"platform_name":"Commodore
64"},{"first_release_date":"1988","platform_id":31,"platform_name":"Apple
II"}],"sample_cover":{"height":700,"image":"https://cdn.mobygames.com/covers/4068119-who-framed-roger-rabbit-amiga-front-cover.jpg","platforms":["Amiga"],"thumbnail_image":"https://cdn.mobygames.com/b59d6706-aba4-11ed-ba50-02420a000199.webp","width":467},"sample_screenshots":[{"caption":"But
don''t let the Waiter catch you. (CGA)","height":200,"image":"https://cdn.mobygames.com/screenshots/7288598-who-framed-roger-rabbit-dos-but-dont-let-the-waiter-catch-you-cg.png","thumbnail_image":"https://cdn.mobygames.com/272f1ce8-abf5-11ed-92cb-02420a000132.webp","width":320},{"caption":"The
map of the city and where you need to go. Your remaining lives are represented
by empty spaces for dip barrels","height":375,"image":"https://cdn.mobygames.com/screenshots/445776-who-framed-roger-rabbit-amiga-the-map-of-the-city-and-where-you-.png","thumbnail_image":"https://cdn.mobygames.com/2750450e-ab6d-11ed-b165-02420a000198.webp","width":640},{"caption":"Title
screen","height":375,"image":"https://cdn.mobygames.com/screenshots/445808-who-framed-roger-rabbit-amiga-title-screen.png","thumbnail_image":"https://cdn.mobygames.com/2762b126-ab6d-11ed-b325-02420a00019e.webp","width":640},{"caption":"Pick
up the pieces of paper and dodge the gorilla and alcohol","height":375,"image":"https://cdn.mobygames.com/screenshots/445448-who-framed-roger-rabbit-amiga-pick-up-the-pieces-of-paper-and-do.png","thumbnail_image":"https://cdn.mobygames.com/26a9c35a-ab6d-11ed-ba04-02420a00019f.webp","width":640},{"caption":"Roger
must collect all the pieces of paper from the table. Avoid the gorilla bouncer
(EGA)","height":200,"image":"https://cdn.mobygames.com/screenshots/442192-who-framed-roger-rabbit-dos-roger-must-collect-all-the-pieces-of.png","thumbnail_image":"https://cdn.mobygames.com/2039dc44-ab6d-11ed-9ab3-02420a0001a0.webp","width":320}],"title":"Who
Framed Roger Rabbit"},{"alternate_titles":[{"description":"Working title","title":"Squadron"},{"description":"Common
abbreviation","title":"WC1"},{"description":"German tag-lined title","title":"Wing
Commander: Der 3D-Raumkampf-Simulator"},{"description":"Tag-lined title","title":"Wing
Commander: The 3-D Space Combat Simulator"},{"description":"Working title","title":"Wingleader"}],"description":"<p>The
Confederation have been at war with the Kilrathi for the past 20 years, and
you''re just now joining the Vega campaign. You''re a 2nd Lieutenant just
out of the Academy, with some good work under your belt. You''re posted to
Tiger''s Claw, the flagship of the Confederation Fleet. Will you help the
Confederation to victory, or go down in infamy? </p>\n<p><em>Wing Commander</em>
is a space combat simulator interspersed with shipboard dialogs. Onboard the
ship, you can save/load game, visit the bar to get the latest gossip, or go
on to the next mission briefing, and the 3D space combat part. </p>\n<p>The
3D space combat has you sitting in the cockpit, where you control the craft
like roll, turn, up/down, afterburner, as well as fire guns and launch missiles.
There are four different crafts on the Confed side, each with different flight
characteristics and armament. You will have a wingman on each mission, and
you should keep the wingman alive as the wingman will help you if you issue
the right orders. You can also taunt the enemy. You''ll be fighting several
different types of enemy fighters and capital ships, and even combat a few
Kilrathi aces. </p>\n<p>When the mission is complete, land back onboard the
ship and get ready for the next one. The campaign tree has both winning and
losing paths.</p>","game_id":3,"genres":[{"genre_category":"Basic Genres","genre_category_id":1,"genre_id":1,"genre_name":"Action"},{"genre_category":"Basic
Genres","genre_category_id":1,"genre_id":3,"genre_name":"Simulation"},{"genre_category":"Perspective","genre_category_id":2,"genre_id":7,"genre_name":"1st-person"},{"genre_category":"Vehicular
Themes","genre_category_id":11,"genre_id":151,"genre_name":"Space flight"},{"genre_category":"Vehicular
Themes","genre_category_id":11,"genre_id":159,"genre_name":"Vehicular combat"},{"genre_category":"Setting","genre_category_id":10,"genre_id":8,"genre_name":"Sci-fi
/ futuristic"}],"moby_score":7.9,"moby_url":"https://www.mobygames.com/game/3/wing-commander/","num_votes":201,"official_url":null,"platforms":[{"first_release_date":"1990","platform_id":2,"platform_name":"DOS"},{"first_release_date":"1992","platform_id":15,"platform_name":"SNES"},{"first_release_date":"1992","platform_id":19,"platform_name":"Amiga"},{"first_release_date":"1994","platform_id":20,"platform_name":"SEGA
CD"},{"first_release_date":"1992-12","platform_id":102,"platform_name":"FM
Towns"}],"sample_cover":{"height":800,"image":"https://cdn.mobygames.com/covers/6743023-wing-commander-dos-front-cover.jpg","platforms":["DOS"],"thumbnail_image":"https://cdn.mobygames.com/dad1dfd8-abf0-11ed-8a97-02420a00012d.webp","width":560},"sample_screenshots":[{"caption":"The
Officers'' Lounge","height":224,"image":"https://cdn.mobygames.com/screenshots/15762759-wing-commander-snes-the-officers-lounge.png","thumbnail_image":"https://cdn.mobygames.com/ed048970-bede-11ed-9c42-02420a000140.webp","width":256},{"caption":"Encountering
a Dralthi","height":400,"image":"https://cdn.mobygames.com/screenshots/350994-wing-commander-amiga-encountering-a-dralthi.png","thumbnail_image":"https://cdn.mobygames.com/696381aa-ab6c-11ed-bd13-02420a00019c.webp","width":640},{"caption":"red
alert! (EGA)","height":400,"image":"https://cdn.mobygames.com/screenshots/273924-wing-commander-dos-red-alert-ega.png","thumbnail_image":"https://cdn.mobygames.com/bfbe68d6-ab6b-11ed-89d0-02420a00019d.webp","width":640},{"caption":"Raptor
Cockpit","height":200,"image":"https://cdn.mobygames.com/screenshots/6756305-wing-commander-dos-raptor-cockpit.png","thumbnail_image":"https://cdn.mobygames.com/f5d649ea-abf0-11ed-902e-02420a00012d.webp","width":320},{"caption":"Rapier
Cockpit","height":200,"image":"https://cdn.mobygames.com/screenshots/6756317-wing-commander-dos-rapier-cockpit.png","thumbnail_image":"https://cdn.mobygames.com/f6a3bd4e-abf0-11ed-902e-02420a00012d.webp","width":320}],"title":"Wing
Commander"},{"alternate_titles":[{"description":"Common abbreviation","title":"SMAC"}],"description":"<p>After
the 20th century, humankind reaches its hand out across the stars. Seeking
to escape the overcrowded chaos of Earth, the United Nations builds a single
seedship, the UNS Unity, and sends her on a mission towards the Alpha Centauri
star system. After a long journey in cryogenic suspension, the Unity reaches
Alpha Centauri where the Captain is killed under mysterious circumstances.
Suspecting the motives of one another, the officers and the crew split into
7 factions, each lead with a distinct ideology and motives that they seek
to build the planet in their image...</p>\n<p><em>Sid Meier''s Alpha Centauri</em>
is best compared to <a href=\"https://www.mobygames.com/game/15/sid-meiers-civilization-ii/\">Civilization
II</a>, but features many distinct differences in gameplay and thinking. In
Civilization, the objective was to evolve a society from primitive tribes,
whereas Alpha Centauri starts with the landing of colony pods on a barren
planet with society becoming fractured. Each faction (aka nation) receives
it''s own share of the Unity''s resources and tech base. For the basics, bases
produce nutrients, materials and energy. Nutrients are required to feed to
population, Materials are used in production and energy represents the commerce
effect which can be traded to players diplomatically or spent on improvements.
The 7 factions each have their own agenda, which is determined in large part
by the Social Engineering. This enables a faction to customize its values,
earning a bonus for what it considers important and a penalty for what it
doesn''t. Social Engineering system are discovered through research, the same
as other improvements, such as structures and units.</p>\n<p>Research is divided
into 4 types of technologies, which form an intertwining tree of dependencies.
They are: Conquer (direct military applications), Explore (indirect technologies
for units and bases), Build (direct infrastructure application) and Discovery
(Science for the sake of science, indirect applications). Because of the separation,
factions can focus on what they hope the intended result of their science
will be, and can be changed at any time. To explore the planet, units are
needed. Any unit can be customized out of known technologies; consisting of
a chassis type, reactor, weapon, armor and special abilities. Each of these
components has a different expense, with untested technologies having additional
overhead (prototype). </p>\n<p>Finally, Alpha Centauri is not a desolate star
system. There is life on the planet, in the form of alien fungus that litters
the ground and strange creatures such as mindworms. Initially hostile to all
factions, this form of life holds its own secrets and effects on the world
at large.</p>","game_id":4,"genres":[{"genre_category":"Basic Genres","genre_category_id":1,"genre_id":4,"genre_name":"Strategy
/ tactics"},{"genre_category":"Perspective","genre_category_id":2,"genre_id":129,"genre_name":"Diagonal-down"},{"genre_category":"Visual
Presentation","genre_category_id":12,"genre_id":134,"genre_name":"Free camera"},{"genre_category":"Visual
Presentation","genre_category_id":12,"genre_id":25,"genre_name":"Isometric"},{"genre_category":"Pacing","genre_category_id":9,"genre_id":106,"genre_name":"Turn-based"},{"genre_category":"Gameplay","genre_category_id":4,"genre_id":142,"genre_name":"4X"},{"genre_category":"Gameplay","genre_category_id":4,"genre_id":40,"genre_name":"Managerial
/ business simulation"},{"genre_category":"Gameplay","genre_category_id":4,"genre_id":226,"genre_name":"Turn-based
strategy (TBS)"},{"genre_category":"Interface/Control","genre_category_id":7,"genre_id":173,"genre_name":"Multiple
units/characters control"},{"genre_category":"Interface/Control","genre_category_id":7,"genre_id":167,"genre_name":"Point
and select"},{"genre_category":"Setting","genre_category_id":10,"genre_id":8,"genre_name":"Sci-fi
/ futuristic"}],"moby_score":8.0,"moby_url":"https://www.mobygames.com/game/4/sid-meiers-alpha-centauri/","num_votes":172,"official_url":"https://web.archive.org/web/20021017023243/http://www.firaxis.com/smac/","platforms":[{"first_release_date":"1999-02","platform_id":3,"platform_name":"Windows"},{"first_release_date":"2000-02","platform_id":74,"platform_name":"Macintosh"}],"sample_cover":{"height":800,"image":"https://cdn.mobygames.com/covers/4024341-sid-meiers-alpha-centauri-windows-front-cover.jpg","platforms":["Windows"],"thumbnail_image":"https://cdn.mobygames.com/30989ad6-aba3-11ed-98cd-02420a00019e.webp","width":657},"sample_screenshots":[{"caption":"Governor
screen","height":600,"image":"https://cdn.mobygames.com/screenshots/10393646-sid-meiers-alpha-centauri-windows-governor-screen.jpg","thumbnail_image":"https://cdn.mobygames.com/d4034204-ac0f-11ed-aca6-02420a000132.webp","width":800},{"caption":"Moderately
developed cities","height":600,"image":"https://cdn.mobygames.com/screenshots/10232216-sid-meiers-alpha-centauri-windows-moderately-developed-cities.jpg","thumbnail_image":"https://cdn.mobygames.com/72c9f722-ac0e-11ed-9941-02420a000133.webp","width":800},{"caption":"Choosing
a faction","height":768,"image":"https://cdn.mobygames.com/screenshots/16876257-sid-meiers-alpha-centauri-windows-choosing-a-faction.png","thumbnail_image":"https://cdn.mobygames.com/a79f3e1c-c3fb-11ed-be63-02420a000121.webp","width":1024},{"caption":"Main
menu","height":768,"image":"https://cdn.mobygames.com/screenshots/16876134-sid-meiers-alpha-centauri-windows-main-menu.png","thumbnail_image":"https://cdn.mobygames.com/9809267a-c3fb-11ed-be63-02420a000121.webp","width":1024},{"caption":"Design
workshop","height":600,"image":"https://cdn.mobygames.com/screenshots/10393989-sid-meiers-alpha-centauri-windows-design-workshop.jpg","thumbnail_image":"https://cdn.mobygames.com/ce21e552-ac0f-11ed-897b-02420a00012d.webp","width":800}],"title":"Sid
Meier''s Alpha Centauri"},{"alternate_titles":[{"description":"Informal name","title":"Indy
500"}],"description":"<p>The famous Indianapolis 500 Mile race, held annually
at the Indianapolis Motor Speedway on the Memorial Day weekend, is one of
the most famous automobile racing events in North America. The event is simulated
here using 3D polygon graphics to recreate the 33 cars in the race. Players
are able to choose their car and customize it with a variety of options. Modifications
include wing down-force, tire pressures, wheel stagger (making the right-side
wheels larger, to compensate for the banked corners) and turbo output (which
provides boost, but stresses the engine and uses more fuel).</p>\n<p>After
qualifying (by performing during four laps and taking the average), players
can race over 10, 30, 60 or the full 200 laps. Lower modes remove car damage
and the ''full-course yellow'' system, the absence of these can make for repeat
carnage including traffic collisions and huge pile-ups.</p>","game_id":5,"genres":[{"genre_category":"Basic
Genres","genre_category_id":1,"genre_id":6,"genre_name":"Racing / Driving"},{"genre_category":"Basic
Genres","genre_category_id":1,"genre_id":3,"genre_name":"Simulation"},{"genre_category":"Perspective","genre_category_id":2,"genre_id":7,"genre_name":"1st-person"},{"genre_category":"Vehicular
Themes","genre_category_id":11,"genre_id":188,"genre_name":"Automobile"},{"genre_category":"Vehicular
Themes","genre_category_id":11,"genre_id":192,"genre_name":"Track racing"},{"genre_category":"Vehicular
Themes","genre_category_id":11,"genre_id":158,"genre_name":"Vehicle simulator"},{"genre_category":"Other
Attributes","genre_category_id":6,"genre_id":82,"genre_name":"Licensed"}],"moby_score":7.6,"moby_url":"https://www.mobygames.com/game/5/indianapolis-500-the-simulation/","num_votes":64,"official_url":null,"platforms":[{"first_release_date":"1989-12","platform_id":2,"platform_name":"DOS"},{"first_release_date":"1990","platform_id":19,"platform_name":"Amiga"}],"sample_cover":{"height":800,"image":"https://cdn.mobygames.com/covers/868063-indianapolis-500-the-simulation-dos-front-cover.jpg","platforms":["DOS"],"thumbnail_image":"https://cdn.mobygames.com/87cf8842-ab70-11ed-beab-02420a000199.webp","width":608},"sample_screenshots":[{"caption":"Copy
protection (Tandy)","height":200,"image":"https://cdn.mobygames.com/screenshots/9497213-indianapolis-500-the-simulation-dos-copy-protection-tandy.png","thumbnail_image":"https://cdn.mobygames.com/889a5652-ac07-11ed-9f52-02420a000130.webp","width":320},{"caption":"Behind
the wheel of Penske Chevrolet (VGA)","height":200,"image":"https://cdn.mobygames.com/screenshots/9496819-indianapolis-500-the-simulation-dos-behind-the-wheel-of-penske-c.png","thumbnail_image":"https://cdn.mobygames.com/87c9e1c0-ac07-11ed-bc39-02420a000131.webp","width":320},{"caption":"Title
Screen (VGA)","height":200,"image":"https://cdn.mobygames.com/screenshots/5487218-indianapolis-500-the-simulation-dos-title-screen-vga.png","thumbnail_image":"https://cdn.mobygames.com/d609b908-abe6-11ed-893a-02420a00012f.webp","width":320},{"caption":"Pit
stop (CGA w/Composite Monitor)","height":400,"image":"https://cdn.mobygames.com/screenshots/7045342-indianapolis-500-the-simulation-dos-pit-stop-cga-wcomposite-moni.png","thumbnail_image":"https://cdn.mobygames.com/3e2f9866-abf3-11ed-92cb-02420a000132.webp","width":640},{"caption":"We
hit the wall","height":200,"image":"https://cdn.mobygames.com/screenshots/15985099-indianapolis-500-the-simulation-amiga-we-hit-the-wall.png","thumbnail_image":"https://cdn.mobygames.com/fc99b64c-bf21-11ed-9c42-02420a000140.webp","width":320}],"title":"Indianapolis
500: The Simulation"}]}
'
headers:
CF-RAY:
- 963b898d0909a21a-YYZ
Cf-Cache-Status:
- DYNAMIC
Connection:
- keep-alive
Content-Encoding:
- br
Content-Security-Policy:
- frame-ancestors 'none';
Content-Type:
- application/json
Date:
- Wed, 23 Jul 2025 13:31:18 GMT
Nel:
- '{"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}'
Ratelimit-Limit:
- "720"
Ratelimit-Policy:
- 1;w=1, 720;w=3600
Ratelimit-Remaining:
- "323"
Ratelimit-Reset:
- "1722"
Report-To:
- '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=d3Y%2Bi5S1ZbnCplET22LftuqQSYgRqyfmNuKuCCwKhtIj5I9Bs0qDwdjS%2BewKBeoJdg%2B1clGMG83kF0Aq8Im8MfLmVaylh2F5xnval6gK"}]}'
Server:
- cloudflare
Set-Cookie:
- session-www=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.xxxxxx.xxxxxxxxxxxxxxxxxxxxxx;
HttpOnly; SameSite=Lax; Path=/; Domain=mobygames.com
Transfer-Encoding:
- chunked
Vary:
- Cookie
X-Frame-Options:
- DENY
X-Xss-Protection:
- 1; mode=block
status:
code: 200
message: OK
version: 1

View File

@@ -0,0 +1,141 @@
interactions:
- request:
body: null
headers: {}
method: GET
uri: https://api.mobygames.com/v1/games?genre=1&format=normal&limit=3
response:
body:
string:
'{"games":[{"alternate_titles":[],"description":"<p>Roger Rabbit has
been framed for the murder of Marvin Acme, head of the Acme Corporation. Acme''s
will states that upon his death, Toon Town would be left to the Toons, but
the will is nowhere to be found. You have to find the will and save your wife,
Jessica, from Judge Doom and his weasels.</p>\n<p>The game takes place in
Hollywood, 1947, where Toons are alive. There are 4 levels in the game, 2
of which are driving levels (levels 1 &amp; 3). Roger and Benny the cab have
to beat the weasels to the destination, while dodging cars, trams and Judge
Doom''s dip which is scattered on the road. There are pick-ups to help you
on your way.</p>\n<p>Level 2 is the Ink &amp; Paint Club. The will is on one
of the tables. Roger has to pick up all the pieces of paper the penguin waiters
put down, whilst avoiding the alcohol and gorilla bouncer.</p>\n<p>Level 4
has you in Judge Doom''s warehouse trying to save your wife, Jessica, from
the dip truck. You have to use gags to progress and make the weasels laugh
themselves to death, literally!</p>","game_id":2,"genres":[{"genre_category":"Basic
Genres","genre_category_id":1,"genre_id":1,"genre_name":"Action"},{"genre_category":"Gameplay","genre_category_id":4,"genre_id":9,"genre_name":"Arcade"},{"genre_category":"Other
Attributes","genre_category_id":6,"genre_id":82,"genre_name":"Licensed"}],"moby_score":5.9,"moby_url":"https://www.mobygames.com/game/2/who-framed-roger-rabbit/","num_votes":34,"official_url":null,"platforms":[{"first_release_date":"1988","platform_id":2,"platform_name":"DOS"},{"first_release_date":"1988","platform_id":19,"platform_name":"Amiga"},{"first_release_date":"1988","platform_id":24,"platform_name":"Atari
ST"},{"first_release_date":"1988","platform_id":27,"platform_name":"Commodore
64"},{"first_release_date":"1988","platform_id":31,"platform_name":"Apple
II"}],"sample_cover":{"height":700,"image":"https://cdn.mobygames.com/covers/4068119-who-framed-roger-rabbit-amiga-front-cover.jpg","platforms":["Amiga"],"thumbnail_image":"https://cdn.mobygames.com/b59d6706-aba4-11ed-ba50-02420a000199.webp","width":467},"sample_screenshots":[{"caption":"But
don''t let the Waiter catch you. (CGA)","height":200,"image":"https://cdn.mobygames.com/screenshots/7288598-who-framed-roger-rabbit-dos-but-dont-let-the-waiter-catch-you-cg.png","thumbnail_image":"https://cdn.mobygames.com/272f1ce8-abf5-11ed-92cb-02420a000132.webp","width":320},{"caption":"The
map of the city and where you need to go. Your remaining lives are represented
by empty spaces for dip barrels","height":375,"image":"https://cdn.mobygames.com/screenshots/445776-who-framed-roger-rabbit-amiga-the-map-of-the-city-and-where-you-.png","thumbnail_image":"https://cdn.mobygames.com/2750450e-ab6d-11ed-b165-02420a000198.webp","width":640},{"caption":"Title
screen","height":375,"image":"https://cdn.mobygames.com/screenshots/445808-who-framed-roger-rabbit-amiga-title-screen.png","thumbnail_image":"https://cdn.mobygames.com/2762b126-ab6d-11ed-b325-02420a00019e.webp","width":640},{"caption":"Pick
up the pieces of paper and dodge the gorilla and alcohol","height":375,"image":"https://cdn.mobygames.com/screenshots/445448-who-framed-roger-rabbit-amiga-pick-up-the-pieces-of-paper-and-do.png","thumbnail_image":"https://cdn.mobygames.com/26a9c35a-ab6d-11ed-ba04-02420a00019f.webp","width":640},{"caption":"Roger
must collect all the pieces of paper from the table. Avoid the gorilla bouncer
(EGA)","height":200,"image":"https://cdn.mobygames.com/screenshots/442192-who-framed-roger-rabbit-dos-roger-must-collect-all-the-pieces-of.png","thumbnail_image":"https://cdn.mobygames.com/2039dc44-ab6d-11ed-9ab3-02420a0001a0.webp","width":320}],"title":"Who
Framed Roger Rabbit"},{"alternate_titles":[{"description":"Working title","title":"Squadron"},{"description":"Common
abbreviation","title":"WC1"},{"description":"German tag-lined title","title":"Wing
Commander: Der 3D-Raumkampf-Simulator"},{"description":"Tag-lined title","title":"Wing
Commander: The 3-D Space Combat Simulator"},{"description":"Working title","title":"Wingleader"}],"description":"<p>The
Confederation have been at war with the Kilrathi for the past 20 years, and
you''re just now joining the Vega campaign. You''re a 2nd Lieutenant just
out of the Academy, with some good work under your belt. You''re posted to
Tiger''s Claw, the flagship of the Confederation Fleet. Will you help the
Confederation to victory, or go down in infamy? </p>\n<p><em>Wing Commander</em>
is a space combat simulator interspersed with shipboard dialogs. Onboard the
ship, you can save/load game, visit the bar to get the latest gossip, or go
on to the next mission briefing, and the 3D space combat part. </p>\n<p>The
3D space combat has you sitting in the cockpit, where you control the craft
like roll, turn, up/down, afterburner, as well as fire guns and launch missiles.
There are four different crafts on the Confed side, each with different flight
characteristics and armament. You will have a wingman on each mission, and
you should keep the wingman alive as the wingman will help you if you issue
the right orders. You can also taunt the enemy. You''ll be fighting several
different types of enemy fighters and capital ships, and even combat a few
Kilrathi aces. </p>\n<p>When the mission is complete, land back onboard the
ship and get ready for the next one. The campaign tree has both winning and
losing paths.</p>","game_id":3,"genres":[{"genre_category":"Basic Genres","genre_category_id":1,"genre_id":1,"genre_name":"Action"},{"genre_category":"Basic
Genres","genre_category_id":1,"genre_id":3,"genre_name":"Simulation"},{"genre_category":"Perspective","genre_category_id":2,"genre_id":7,"genre_name":"1st-person"},{"genre_category":"Vehicular
Themes","genre_category_id":11,"genre_id":151,"genre_name":"Space flight"},{"genre_category":"Vehicular
Themes","genre_category_id":11,"genre_id":159,"genre_name":"Vehicular combat"},{"genre_category":"Setting","genre_category_id":10,"genre_id":8,"genre_name":"Sci-fi
/ futuristic"}],"moby_score":7.9,"moby_url":"https://www.mobygames.com/game/3/wing-commander/","num_votes":201,"official_url":null,"platforms":[{"first_release_date":"1990","platform_id":2,"platform_name":"DOS"},{"first_release_date":"1992","platform_id":15,"platform_name":"SNES"},{"first_release_date":"1992","platform_id":19,"platform_name":"Amiga"},{"first_release_date":"1994","platform_id":20,"platform_name":"SEGA
CD"},{"first_release_date":"1992-12","platform_id":102,"platform_name":"FM
Towns"}],"sample_cover":{"height":800,"image":"https://cdn.mobygames.com/covers/6743023-wing-commander-dos-front-cover.jpg","platforms":["DOS"],"thumbnail_image":"https://cdn.mobygames.com/dad1dfd8-abf0-11ed-8a97-02420a00012d.webp","width":560},"sample_screenshots":[{"caption":"The
Officers'' Lounge","height":224,"image":"https://cdn.mobygames.com/screenshots/15762759-wing-commander-snes-the-officers-lounge.png","thumbnail_image":"https://cdn.mobygames.com/ed048970-bede-11ed-9c42-02420a000140.webp","width":256},{"caption":"Encountering
a Dralthi","height":400,"image":"https://cdn.mobygames.com/screenshots/350994-wing-commander-amiga-encountering-a-dralthi.png","thumbnail_image":"https://cdn.mobygames.com/696381aa-ab6c-11ed-bd13-02420a00019c.webp","width":640},{"caption":"red
alert! (EGA)","height":400,"image":"https://cdn.mobygames.com/screenshots/273924-wing-commander-dos-red-alert-ega.png","thumbnail_image":"https://cdn.mobygames.com/bfbe68d6-ab6b-11ed-89d0-02420a00019d.webp","width":640},{"caption":"Raptor
Cockpit","height":200,"image":"https://cdn.mobygames.com/screenshots/6756305-wing-commander-dos-raptor-cockpit.png","thumbnail_image":"https://cdn.mobygames.com/f5d649ea-abf0-11ed-902e-02420a00012d.webp","width":320},{"caption":"Rapier
Cockpit","height":200,"image":"https://cdn.mobygames.com/screenshots/6756317-wing-commander-dos-rapier-cockpit.png","thumbnail_image":"https://cdn.mobygames.com/f6a3bd4e-abf0-11ed-902e-02420a00012d.webp","width":320}],"title":"Wing
Commander"},{"alternate_titles":[],"description":"<p>Take control of a mechanical
digging machine as you tunnel your way through the earth, searching for valuable
gems and the even more valuable bags of gold! But watch out for Nobbins and
Hobbins, and don''t be careless enough to let the bags of gold crush you!</p>\n<p><em>Digger</em>
is an arcade game combining elements of the popular arcade games <a href=\"https://www.mobygames.com/game/139/dig-dug/\">Dig
Dug</a> and <a href=\"https://www.mobygames.com/game/9764/mr-do/\">Mr. Do!</a>.
Players control the titular ''Digger'' that can tunnel through dirt with ease.
The goal of each level is to gather up each of the gems, which allows you
to progress to the next stage. However, Nobbins and Hobbins are also lurking
within the levels - Nobbins are fairly slow, but transform into Hobbins which
are much quicker. The enemies can only chase Digger through the tunnels he
creates - they cannot dig through the dirt themselves.</p>\n<p>Digger''s defenses
consist of being able to shoot a single, rechargeable shot in the direction
he is facing with F1 (which recharges after about thirty seconds), crushing
his foes by digging underneath a gold bag and letting it plummet down, crushing
anything in its path, or by collecting the bonus cherry that sometimes appears,
causing Digger to become temporarily invincible.</p>","game_id":18,"genres":[{"genre_category":"Basic
Genres","genre_category_id":1,"genre_id":1,"genre_name":"Action"},{"genre_category":"Perspective","genre_category_id":2,"genre_id":17,"genre_name":"Side
view"},{"genre_category":"Visual Presentation","genre_category_id":12,"genre_id":133,"genre_name":"Fixed
/ flip-screen"},{"genre_category":"Gameplay","genre_category_id":4,"genre_id":9,"genre_name":"Arcade"}],"moby_score":7.2,"moby_url":"https://www.mobygames.com/game/18/digger/","num_votes":55,"official_url":null,"platforms":[{"first_release_date":"1983","platform_id":4,"platform_name":"PC
Booter"}],"sample_cover":{"height":800,"image":"https://cdn.mobygames.com/covers/3944239-digger-pc-booter-front-cover.jpg","platforms":["PC
Booter"],"thumbnail_image":"https://cdn.mobygames.com/6bec59b8-aba0-11ed-9e18-02420a00019a.webp","width":511},"sample_screenshots":[{"caption":"Level
8 (If you manage to complete this level you will return to level 6)","height":200,"image":"https://cdn.mobygames.com/screenshots/173445-digger-pc-booter-level-8-if-you-manage-to-complete-this-level-yo.png","thumbnail_image":"https://cdn.mobygames.com/f6ca57dc-ab6a-11ed-b042-02420a00019f.webp","width":320},{"caption":"Title","height":200,"image":"https://cdn.mobygames.com/screenshots/1778002-digger-pc-booter-title.png","thumbnail_image":"https://cdn.mobygames.com/27bfa1a0-ab78-11ed-837d-02420a00019b.webp","width":320},{"caption":"Level
6","height":200,"image":"https://cdn.mobygames.com/screenshots/1777003-digger-pc-booter-level-6.png","thumbnail_image":"https://cdn.mobygames.com/25bf187c-ab78-11ed-87d1-02420a000197.webp","width":320},{"caption":"Level
2","height":200,"image":"https://cdn.mobygames.com/screenshots/1778183-digger-pc-booter-level-2.png","thumbnail_image":"https://cdn.mobygames.com/280f1c76-ab78-11ed-837d-02420a00019b.webp","width":320},{"caption":"Level
3","height":200,"image":"https://cdn.mobygames.com/screenshots/1778600-digger-pc-booter-level-3.png","thumbnail_image":"https://cdn.mobygames.com/28ebf54c-ab78-11ed-837d-02420a00019b.webp","width":320}],"title":"Digger"}]}
'
headers:
CF-RAY:
- 963b92ff4e91a234-YYZ
Cf-Cache-Status:
- DYNAMIC
Connection:
- keep-alive
Content-Encoding:
- br
Content-Security-Policy:
- frame-ancestors 'none';
Content-Type:
- application/json
Date:
- Wed, 23 Jul 2025 13:37:45 GMT
Nel:
- '{"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}'
Ratelimit-Limit:
- "720"
Ratelimit-Policy:
- 1;w=1, 720;w=3600
Ratelimit-Remaining:
- "215"
Ratelimit-Reset:
- "1335"
Report-To:
- '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=BwegzCAuSTI5MVyTG8jRXPkRZIOn8xEpx4aT6R0AEQ4u2nIVDtf96724abi6IJJ9AVUudEArfXGjvcW6EeQbM7ZnHmg09vHHgEjoO7e2"}]}'
Server:
- cloudflare
Set-Cookie:
- session-www=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.xxxxxx.xxxxxxxxxxxxxxxxxxxxxx;
HttpOnly; SameSite=Lax; Path=/; Domain=mobygames.com
Transfer-Encoding:
- chunked
Vary:
- Cookie
X-Frame-Options:
- DENY
X-Xss-Protection:
- 1; mode=block
status:
code: 200
message: OK
version: 1

View File

@@ -0,0 +1,118 @@
interactions:
- request:
body: null
headers: {}
method: GET
uri: https://api.mobygames.com/v1/games?format=normal&limit=2&offset=0
response:
body:
string:
'{"games":[{"alternate_titles":[{"description":"Finnish title","title":"Salaiset
Kansiot"},{"description":"PSX title","title":"The X Files"},{"description":"Spanish
title","title":"The X-Files: Expediente X - El Juego"},{"description":"French
title","title":"The X-Files: Le Jeu"}],"description":"<p>As an extension of
one of the most long-running television series of all time, <em>The X-Files</em>,
play through the eyes of Special Agent Craig Willmore, a new FBI field investigator
brought in to locate missing agents Fox Mulder and Dana Scully whose last
location was the Everett, Washington, area. In this \"movie quality\" video
production, characters are played by the actors and actresses from the show,
including <a href=\"https://www.mobygames.com/person/9411/gillian-anderson/\">Gillian
Anderson</a> (Scully) and <a href=\"https://www.mobygames.com/person/9412/david-duchovny/\">David
Duchovny</a> (Mulder).</p>\n<p>As the game begins, you are given a briefing
of your mission. Gather up all state-of-the-art spy tools (night vision goggles,
a digital camera, PDA, lock picks, evidence kit, a standard issue revolver,
handcuffs and badge) and then head out to follow their trail. As you explore
the various locations, take photographs, pick up pieces of evidence and talk
with people. Use your Newton PDA to access the navigational map, to make notes
and send/receive e-mail. Trace telephone numbers, run background checks and
license plate ids and even post an All Points Bulletin on missing persons
using the computer network at your home or office. By using photo viewer software,
download field photographs to the computer where they can be enlarged and
studied more closely for clues.</p>\n<p>Features include emotion icons for
interjecting different tones during conversations (mean, humorous or technical)
which effect the answer given. An in-game hint system, Artificial Intelligence,
can be set on or off. In addition, there are multiple endings as a direct
result of actions you take.</p>","game_id":1,"genres":[{"genre_category":"Basic
Genres","genre_category_id":1,"genre_id":2,"genre_name":"Adventure"},{"genre_category":"Perspective","genre_category_id":2,"genre_id":7,"genre_name":"1st-person"},{"genre_category":"Art
Style","genre_category_id":13,"genre_id":217,"genre_name":"Live action"},{"genre_category":"Setting","genre_category_id":10,"genre_id":8,"genre_name":"Sci-fi
/ futuristic"},{"genre_category":"Narrative Theme/Topic","genre_category_id":8,"genre_id":55,"genre_name":"Detective
/ mystery"},{"genre_category":"Other Attributes","genre_category_id":6,"genre_id":82,"genre_name":"Licensed"}],"moby_score":7.1,"moby_url":"https://www.mobygames.com/game/1/the-x-files-game/","num_votes":57,"official_url":"https://www.hyperbole.com/xsite/","platforms":[{"first_release_date":"1998","platform_id":3,"platform_name":"Windows"},{"first_release_date":"1999","platform_id":6,"platform_name":"PlayStation"},{"first_release_date":"1998-06","platform_id":74,"platform_name":"Macintosh"}],"sample_cover":{"height":800,"image":"https://cdn.mobygames.com/covers/4062982-the-x-files-game-windows-front-cover.jpg","platforms":["Windows","Macintosh"],"thumbnail_image":"https://cdn.mobygames.com/872aed6c-aba4-11ed-a188-02420a00019a.webp","width":690},"sample_screenshots":[{"caption":"Outside
Agent Craig Willmore''s apartment ","height":480,"image":"https://cdn.mobygames.com/screenshots/11077669-the-x-files-game-windows-outside-agent-craig-willmores-apartment.jpg","thumbnail_image":"https://cdn.mobygames.com/93317876-ac15-11ed-b444-02420a000134.webp","width":640},{"caption":"Arriving
at Dockside Warehouse","height":480,"image":"https://cdn.mobygames.com/screenshots/11072419-the-x-files-game-windows-arriving-at-dockside-warehouse.jpg","thumbnail_image":"https://cdn.mobygames.com/916394e8-ac15-11ed-803a-02420a000131.webp","width":640},{"caption":"Boat
dock","height":480,"image":"https://cdn.mobygames.com/screenshots/11076046-the-x-files-game-windows-boat-dock.jpg","thumbnail_image":"https://cdn.mobygames.com/9a3b5ef2-ac15-11ed-b075-02420a00012f.webp","width":640},{"caption":"Medical
examiner and very dead James Wong","height":480,"image":"https://cdn.mobygames.com/screenshots/11073361-the-x-files-game-windows-medical-examiner-and-very-dead-james-wo.jpg","thumbnail_image":"https://cdn.mobygames.com/8f6b8eb6-ac15-11ed-833b-02420a000131.webp","width":640},{"caption":"Main
Menu","height":576,"image":"https://cdn.mobygames.com/screenshots/10470736-the-x-files-game-playstation-main-menu.jpg","thumbnail_image":"https://cdn.mobygames.com/6cfd26d2-ac10-11ed-803a-02420a000131.webp","width":720}],"title":"The
X-Files Game"},{"alternate_titles":[],"description":"<p>Roger Rabbit has been
framed for the murder of Marvin Acme, head of the Acme Corporation. Acme''s
will states that upon his death, Toon Town would be left to the Toons, but
the will is nowhere to be found. You have to find the will and save your wife,
Jessica, from Judge Doom and his weasels.</p>\n<p>The game takes place in
Hollywood, 1947, where Toons are alive. There are 4 levels in the game, 2
of which are driving levels (levels 1 &amp; 3). Roger and Benny the cab have
to beat the weasels to the destination, while dodging cars, trams and Judge
Doom''s dip which is scattered on the road. There are pick-ups to help you
on your way.</p>\n<p>Level 2 is the Ink &amp; Paint Club. The will is on one
of the tables. Roger has to pick up all the pieces of paper the penguin waiters
put down, whilst avoiding the alcohol and gorilla bouncer.</p>\n<p>Level 4
has you in Judge Doom''s warehouse trying to save your wife, Jessica, from
the dip truck. You have to use gags to progress and make the weasels laugh
themselves to death, literally!</p>","game_id":2,"genres":[{"genre_category":"Basic
Genres","genre_category_id":1,"genre_id":1,"genre_name":"Action"},{"genre_category":"Gameplay","genre_category_id":4,"genre_id":9,"genre_name":"Arcade"},{"genre_category":"Other
Attributes","genre_category_id":6,"genre_id":82,"genre_name":"Licensed"}],"moby_score":5.9,"moby_url":"https://www.mobygames.com/game/2/who-framed-roger-rabbit/","num_votes":34,"official_url":null,"platforms":[{"first_release_date":"1988","platform_id":2,"platform_name":"DOS"},{"first_release_date":"1988","platform_id":19,"platform_name":"Amiga"},{"first_release_date":"1988","platform_id":24,"platform_name":"Atari
ST"},{"first_release_date":"1988","platform_id":27,"platform_name":"Commodore
64"},{"first_release_date":"1988","platform_id":31,"platform_name":"Apple
II"}],"sample_cover":{"height":700,"image":"https://cdn.mobygames.com/covers/4068119-who-framed-roger-rabbit-amiga-front-cover.jpg","platforms":["Amiga"],"thumbnail_image":"https://cdn.mobygames.com/b59d6706-aba4-11ed-ba50-02420a000199.webp","width":467},"sample_screenshots":[{"caption":"But
don''t let the Waiter catch you. (CGA)","height":200,"image":"https://cdn.mobygames.com/screenshots/7288598-who-framed-roger-rabbit-dos-but-dont-let-the-waiter-catch-you-cg.png","thumbnail_image":"https://cdn.mobygames.com/272f1ce8-abf5-11ed-92cb-02420a000132.webp","width":320},{"caption":"The
map of the city and where you need to go. Your remaining lives are represented
by empty spaces for dip barrels","height":375,"image":"https://cdn.mobygames.com/screenshots/445776-who-framed-roger-rabbit-amiga-the-map-of-the-city-and-where-you-.png","thumbnail_image":"https://cdn.mobygames.com/2750450e-ab6d-11ed-b165-02420a000198.webp","width":640},{"caption":"Title
screen","height":375,"image":"https://cdn.mobygames.com/screenshots/445808-who-framed-roger-rabbit-amiga-title-screen.png","thumbnail_image":"https://cdn.mobygames.com/2762b126-ab6d-11ed-b325-02420a00019e.webp","width":640},{"caption":"Pick
up the pieces of paper and dodge the gorilla and alcohol","height":375,"image":"https://cdn.mobygames.com/screenshots/445448-who-framed-roger-rabbit-amiga-pick-up-the-pieces-of-paper-and-do.png","thumbnail_image":"https://cdn.mobygames.com/26a9c35a-ab6d-11ed-ba04-02420a00019f.webp","width":640},{"caption":"Roger
must collect all the pieces of paper from the table. Avoid the gorilla bouncer
(EGA)","height":200,"image":"https://cdn.mobygames.com/screenshots/442192-who-framed-roger-rabbit-dos-roger-must-collect-all-the-pieces-of.png","thumbnail_image":"https://cdn.mobygames.com/2039dc44-ab6d-11ed-9ab3-02420a0001a0.webp","width":320}],"title":"Who
Framed Roger Rabbit"}]}
'
headers:
CF-RAY:
- 963b92d7a8ada1e1-YYZ
Cf-Cache-Status:
- DYNAMIC
Connection:
- keep-alive
Content-Encoding:
- br
Content-Security-Policy:
- frame-ancestors 'none';
Content-Type:
- application/json
Date:
- Wed, 23 Jul 2025 13:37:38 GMT
Nel:
- '{"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}'
Ratelimit-Limit:
- "720"
Ratelimit-Policy:
- 1;w=1, 720;w=3600
Ratelimit-Remaining:
- "216"
Ratelimit-Reset:
- "1342"
Report-To:
- '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=cMdhBH49LSqcqJmaDvUSsqiOz5Hbb%2FavRFtM9lRILRDZj%2F5bvW8KRjLD7KB7wslOEGg0w%2BymmjYkHzgn0hLhWp%2F66peXeX5mNSPjpcrY"}]}'
Server:
- cloudflare
Set-Cookie:
- session-www=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.xxxxxx.xxxxxxxxxxxxxxxxxxxxxx;
HttpOnly; SameSite=Lax; Path=/; Domain=mobygames.com
Transfer-Encoding:
- chunked
Vary:
- Cookie
X-Frame-Options:
- DENY
X-Xss-Protection:
- 1; mode=block
status:
code: 200
message: OK
version: 1

View File

@@ -0,0 +1,158 @@
interactions:
- request:
body: null
headers: {}
method: GET
uri: https://api.mobygames.com/v1/games?platform=1&format=normal&limit=3
response:
body:
string:
'{"games":[{"alternate_titles":[{"description":"Japanese title","title":"\u014czora
no Kishi"}],"description":"<p><em>Knights of the Sky</em> is a World War I
flight combat sim where you pilot one of 20 different aircraft (each handles
differently) as you engage in simple missions or join a full campaign where
you will progress through World War I, going on a variety of missions, patrols,
and even encounter enemy aces. You can engage balloons and blimps, enemy fighters
and bombers, even strafe group targets such as supply trains. If you do really
well in the game, one of the enemy aces may drop a note to challenge you to
an aerial duel. </p>\n<p>A unique feature of the game is the capability of
modem link so you can challenge fellow modem owners to dogfights, something
that none of its contemporaries had.</p>","game_id":28,"genres":[{"genre_category":"Basic
Genres","genre_category_id":1,"genre_id":3,"genre_name":"Simulation"},{"genre_category":"Perspective","genre_category_id":2,"genre_id":7,"genre_name":"1st-person"},{"genre_category":"Perspective","genre_category_id":2,"genre_id":126,"genre_name":"3rd-person
(Other)"},{"genre_category":"Vehicular Themes","genre_category_id":11,"genre_id":19,"genre_name":"Flight
/ aviation"},{"genre_category":"Vehicular Themes","genre_category_id":11,"genre_id":159,"genre_name":"Vehicular
combat"},{"genre_category":"Setting","genre_category_id":10,"genre_id":14,"genre_name":"Historical
events"},{"genre_category":"Setting","genre_category_id":10,"genre_id":214,"genre_name":"World
War I"}],"moby_score":7.3,"moby_url":"https://www.mobygames.com/game/28/knights-of-the-sky/","num_votes":41,"official_url":null,"platforms":[{"first_release_date":"2016-07-21","platform_id":1,"platform_name":"Linux"},{"first_release_date":"1990","platform_id":2,"platform_name":"DOS"},{"first_release_date":"2016-07-21","platform_id":3,"platform_name":"Windows"},{"first_release_date":"1991","platform_id":19,"platform_name":"Amiga"},{"first_release_date":"1991","platform_id":24,"platform_name":"Atari
ST"},{"first_release_date":"2016-07-21","platform_id":74,"platform_name":"Macintosh"},{"first_release_date":"1993-06-12","platform_id":95,"platform_name":"PC-98"}],"sample_cover":{"height":800,"image":"https://cdn.mobygames.com/covers/6872241-knights-of-the-sky-dos-front-cover.jpg","platforms":["DOS"],"thumbnail_image":"https://cdn.mobygames.com/e9addfc4-abf1-11ed-b206-02420a000131.webp","width":686},"sample_screenshots":[{"caption":"Home
base desk","height":400,"image":"https://cdn.mobygames.com/screenshots/1714001-knights-of-the-sky-pc-98-home-base-desk.png","thumbnail_image":"https://cdn.mobygames.com/94e90a88-ab77-11ed-ac16-02420a000199.webp","width":640},{"caption":"Title
Screen","height":200,"image":"https://cdn.mobygames.com/screenshots/1688400-knights-of-the-sky-dos-title-screen.png","thumbnail_image":"https://cdn.mobygames.com/5a38e728-ab77-11ed-bd13-02420a00019c.webp","width":320},{"caption":"Side
View","height":200,"image":"https://cdn.mobygames.com/screenshots/1683785-knights-of-the-sky-dos-side-view.png","thumbnail_image":"https://cdn.mobygames.com/4edf93e0-ab77-11ed-81b6-02420a00019d.webp","width":320},{"caption":"He
flies straight into my crosshairs","height":200,"image":"https://cdn.mobygames.com/screenshots/15988514-knights-of-the-sky-atari-st-he-flies-straight-into-my-crosshairs.png","thumbnail_image":"https://cdn.mobygames.com/cd160442-bf22-11ed-9c42-02420a000140.webp","width":320},{"caption":"Title
screen","height":400,"image":"https://cdn.mobygames.com/screenshots/1760327-knights-of-the-sky-pc-98-title-screen.png","thumbnail_image":"https://cdn.mobygames.com/ff7a1a90-ab77-11ed-934f-02420a0001a0.webp","width":640}],"title":"Knights
of the Sky"},{"alternate_titles":[{"description":"International title","title":"Shadow
of the Comet"}],"description":"<p>In 1834, in the small New England fishing
village of Illsmouth, the distinguished British scientist Lord Boleskine lost
his mind. After studying ancient manuscripts of evil repute, he had travelled
to this place to observe the passing of Halley''s comet. What he observed
that night, however, turned him into a raving lunatic. Now, 76 years later,
Halley''s comet is coming back, and young reporter John T. Parker has travelled
to Illsmouth to try to uncover the truth in Boleskine''s wild claims, and
see the comet\nfor himself.</p>\n<p><em>Shadow of the Comet</em> is a horror
adventure game, inspired by the terrifying writings of <a href=\"https://www.mobygames.com/person/633/h-p-lovecraft/\">H.
P. Lovecraft</a>. Contrary to many adventure games from the early 90s, the
game has a keyboard driven interface with a system of actions activated either
by pressing the corresponding key (L for look, G for get, T for talk, U for
use) or selecting them from the menu activated by the TAB key. The CD release
was enhanced with a mouse-driven interface. Typical for Infogrames titles
(e.g. <a href=\"https://www.mobygames.com/game/1368/eternam/\">Eternam</a>),
the game contains vector-based cut scenes with enlarged graphics of the faces
of the speakers during dialogues.</p>\n<p>The GOG.com release of this game
includes both Floppy and CD versions of the game. The main differences are
new graphics, mouse-driven interface and full voice-acting whereas the Floppy
version doesn''t feature any voice-acting. The CD version''s launcher screen
additionally includes a <em>Museum</em> mini-game where character can walk
through a museum and look at various mystical objects and paintings inspired
by <a href=\"https://www.mobygames.com/person/633/h-p-lovecraft/\">H. P. Lovecraft</a>.</p>","game_id":132,"genres":[{"genre_category":"Basic
Genres","genre_category_id":1,"genre_id":2,"genre_name":"Adventure"},{"genre_category":"Perspective","genre_category_id":2,"genre_id":17,"genre_name":"Side
view"},{"genre_category":"Gameplay","genre_category_id":4,"genre_id":146,"genre_name":"Graphic
adventure"},{"genre_category":"Gameplay","genre_category_id":4,"genre_id":30,"genre_name":"Puzzle
elements"},{"genre_category":"Interface/Control","genre_category_id":7,"genre_id":168,"genre_name":"Direct
control"},{"genre_category":"Narrative Theme/Topic","genre_category_id":8,"genre_id":83,"genre_name":"Horror"},{"genre_category":"Other
Attributes","genre_category_id":6,"genre_id":82,"genre_name":"Licensed"}],"moby_score":7.7,"moby_url":"https://www.mobygames.com/game/132/call-of-cthulhu-shadow-of-the-comet/","num_votes":49,"official_url":"http://www.infogrames.net/shadow/","platforms":[{"first_release_date":"2015-07-21","platform_id":1,"platform_name":"Linux"},{"first_release_date":"1993","platform_id":2,"platform_name":"DOS"},{"first_release_date":"2015-07-21","platform_id":3,"platform_name":"Windows"},{"first_release_date":"1995-03-03","platform_id":95,"platform_name":"PC-98"}],"sample_cover":{"height":800,"image":"https://cdn.mobygames.com/covers/4022611-call-of-cthulhu-shadow-of-the-comet-dos-front-cover.jpg","platforms":["DOS"],"thumbnail_image":"https://cdn.mobygames.com/20f0b6b8-aba3-11ed-a519-02420a00019d.webp","width":642},"sample_screenshots":[{"caption":"Doctor
Cobble''s house","height":200,"image":"https://cdn.mobygames.com/screenshots/212030-call-of-cthulhu-shadow-of-the-comet-dos-doctor-cobbles-house.png","thumbnail_image":"https://cdn.mobygames.com/43ce164a-ab6b-11ed-8e21-02420a00019b.webp","width":320},{"caption":"Hey,
don''t you think this guy looks like George W. Bush? :)","height":400,"image":"https://cdn.mobygames.com/screenshots/16098317-call-of-cthulhu-shadow-of-the-comet-pc-98-hey-dont-you-think-thi.png","thumbnail_image":"https://cdn.mobygames.com/ab361db8-bf71-11ed-9521-02420a000152.webp","width":640},{"caption":"Title","height":200,"image":"https://cdn.mobygames.com/screenshots/194693-call-of-cthulhu-shadow-of-the-comet-dos-title.png","thumbnail_image":"https://cdn.mobygames.com/211ac36e-ab6b-11ed-a5ec-02420a00019b.webp","width":320},{"caption":"Arriving
at the sleepy town","height":400,"image":"https://cdn.mobygames.com/screenshots/16098383-call-of-cthulhu-shadow-of-the-comet-pc-98-arriving-at-the-sleepy.png","thumbnail_image":"https://cdn.mobygames.com/b153b0e8-bf71-11ed-9521-02420a000152.webp","width":640},{"caption":"The
interface looks similar to Sierra adventures, but it''s keyboard only","height":400,"image":"https://cdn.mobygames.com/screenshots/15650596-call-of-cthulhu-shadow-of-the-comet-pc-98-the-interface-looks-si.png","thumbnail_image":"https://cdn.mobygames.com/359d61aa-bdf1-11ed-b791-02420a0001bb.webp","width":640}],"title":"Call
of Cthulhu: Shadow of the Comet"},{"alternate_titles":[{"description":"Simplified
Chinese title","title":"Bantiao Ming"},{"description":"Common abbreviation","title":"HL"},{"description":"Cover
/ in-game title","title":"H\u03bblf-Life"},{"description":"Working title","title":"Quiver"}],"description":"<p>The
Black Mesa Research Facility is an ultra-secret laboratory under a government
contract to conduct top-secret and extremely volatile experiments. The scientist
Gordon Freeman is a Black Mesa employee. One morning, as usual, he pits his
way to the research facility for a run-of-the-mill experiment. However, Gordon
comes to realize that it might not be as ordinary as he thought. Odd things
happen as he makes his way to one of the Black Mesa test chambers. Even stranger
things happen when he begins to move the test sample towards the anti-mass
spectrometer.</p>\n<p>At that moment, everything goes horribly wrong. Aliens
from the dimension Xen suddenly invade the facility, injuring or killing many
of the employees. Soon afterwards, marines arrive to contain the situation
by killing the aliens as well as the surviving human witnesses. Gordon understands
what that means: he will have to fight his way through both aliens and marines
to get to the top of the Black Mesa complex and to freedom.</p>\n<p>The story
of <em>Half-Life</em> is told entirely in-game: everything is seen through
the eyes of the protagonist. Most story elements unfold via scripted sequences,
triggered by the player reaching a certain area. If other characters have
information to reveal, they address Gordon directly. The Black Mesa complex
in the game is made up of both distinct levels which progress in a linear
fashion as well as hubs where backtracking may be required to unlock further
areas.</p>\n<p>The game''s weapon arsenal mostly consists of realistic weapons
like pistols, machine guns and explosives, but there are also futuristic energy
weapons developed at Black Mesa as well as organic weapons acquired from the
invading aliens. Most weapons feature an alternate firing mode.</p>\n<p>Enemies
fall into two categories: aliens and human soldiers. While most of the aliens
are not very bright, the humans display some relatively advanced artificial
intelligence: they seek cover, retreat when hit and try to drive the player
from his cover by throwing grenades. Some of the alien enemies cannot be killed
by normal means. The environment must be used against them instead, going
with a general tendency of the game to alternate the combat with environmental
puzzles. </p>\n<p>As of the 25th Anniversary Update from 17 November 2023,
the Steam version of Half-Life includes content from <a href=\"https://www.mobygames.com/game/25393/half-life-uplink/\">Half-Life:
Uplink</a> as well as sprays and maps from <a href=\"https://www.mobygames.com/game/58630/half-life-further-data-v1/\">Half-Life:
Further Data V.1</a>.</p>","game_id":155,"genres":[{"genre_category":"Basic
Genres","genre_category_id":1,"genre_id":1,"genre_name":"Action"},{"genre_category":"Perspective","genre_category_id":2,"genre_id":7,"genre_name":"1st-person"},{"genre_category":"Gameplay","genre_category_id":4,"genre_id":22,"genre_name":"Shooter"},{"genre_category":"Interface/Control","genre_category_id":7,"genre_id":168,"genre_name":"Direct
control"},{"genre_category":"Setting","genre_category_id":10,"genre_id":207,"genre_name":"North
America"},{"genre_category":"Setting","genre_category_id":10,"genre_id":8,"genre_name":"Sci-fi
/ futuristic"},{"genre_category":"Other Attributes","genre_category_id":6,"genre_id":223,"genre_name":"Regional
differences"}],"moby_score":8.7,"moby_url":"https://www.mobygames.com/game/155/half-life/","num_votes":607,"official_url":null,"platforms":[{"first_release_date":"2013-02-06","platform_id":1,"platform_name":"Linux"},{"first_release_date":"1998-11","platform_id":3,"platform_name":"Windows"},{"first_release_date":"2013-01-25","platform_id":74,"platform_name":"Macintosh"}],"sample_cover":{"height":800,"image":"https://cdn.mobygames.com/covers/4597980-half-life-windows-front-cover.jpg","platforms":["Windows"],"thumbnail_image":"https://cdn.mobygames.com/455b871a-abb9-11ed-aecf-02420a000198.webp","width":661},"sample_screenshots":[{"caption":"Beautiful
view outside! Doesn''t look very safe, though...","height":768,"image":"https://cdn.mobygames.com/screenshots/10587038-half-life-windows-beautiful-view-outside-doesnt-look-very-safe-t.jpg","thumbnail_image":"https://cdn.mobygames.com/65e552ce-ac11-11ed-962d-02420a000135.webp","width":1024},{"caption":"Marines
incoming in an Osprey","height":360,"image":"https://cdn.mobygames.com/screenshots/12487516-half-life-windows-marines-incoming-in-an-osprey.jpg","thumbnail_image":"https://cdn.mobygames.com/7f1f0270-ac21-11ed-b57a-02420a000130.webp","width":480},{"caption":"The
title and main menu","height":800,"image":"https://cdn.mobygames.com/screenshots/16475530-half-life-linux-the-title-and-main-menu.png","thumbnail_image":"https://cdn.mobygames.com/982e3a32-c225-11ed-ab6b-02420a000194.webp","width":1000},{"caption":"Some
of the environments seem radioactive, but this is purely a stylistic choice","height":768,"image":"https://cdn.mobygames.com/screenshots/3278622-half-life-windows-some-of-the-environments-seem-radioactive-but-.jpg","thumbnail_image":"https://cdn.mobygames.com/4da4b7de-ab8c-11ed-b6ec-02420a00019b.webp","width":1024},{"caption":"The
test chamber","height":600,"image":"https://cdn.mobygames.com/screenshots/10105274-half-life-windows-the-test-chamber.jpg","thumbnail_image":"https://cdn.mobygames.com/33ea9530-ac0d-11ed-902e-02420a00012d.webp","width":800}],"title":"Half-Life"}]}
'
headers:
CF-RAY:
- 963b8a1e9f10a22e-YYZ
Cf-Cache-Status:
- DYNAMIC
Connection:
- keep-alive
Content-Encoding:
- br
Content-Security-Policy:
- frame-ancestors 'none';
Content-Type:
- application/json
Date:
- Wed, 23 Jul 2025 13:31:41 GMT
Nel:
- '{"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}'
Ratelimit-Limit:
- "720"
Ratelimit-Policy:
- 1;w=1, 720;w=3600
Ratelimit-Remaining:
- "321"
Ratelimit-Reset:
- "1699"
Report-To:
- '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=IWsWIvE1hW7HZd%2BZRMeSwQPpiMqRnoTjQg47uzY0Mowq8wqSDs36W91OmXeHSwljKzY5AuNZJ5AaIrB8OicbLvC95l2pNEvreEGh09e8"}]}'
Server:
- cloudflare
Set-Cookie:
- session-www=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.xxxxxx.xxxxxxxxxxxxxxxxxxxxxx;
HttpOnly; SameSite=Lax; Path=/; Domain=mobygames.com
Transfer-Encoding:
- chunked
Vary:
- Cookie
X-Frame-Options:
- DENY
X-Xss-Protection:
- 1; mode=block
status:
code: 200
message: OK
version: 1

View File

@@ -0,0 +1,203 @@
interactions:
- request:
body: null
headers: {}
method: GET
uri: https://api.mobygames.com/v1/games?title=Sonic&format=normal&limit=5
response:
body:
string:
'{"games":[{"alternate_titles":[],"description":"<p>The Sonic &amp;
Knuckles Collection contains three games ported from the Sega Genesis:</p>\n<ul>\n<li><a
href=\"https://www.mobygames.com/game/6612/sonic-the-hedgehog-3/\">Sonic the
Hedgehog 3</a></li>\n<li><a href=\"https://www.mobygames.com/game/6614/sonic-knuckles/\">Sonic
&amp; Knuckles</a></li>\n<li><em>Sonic 3 &amp; Knuckles</em> (<em>Sonic the
Hedgehog 3</em> running with the <em>Sonic &amp; Knuckles</em> modification
enhancement)</li>\n</ul>\n<p>In addition it contains <em>Special Stage Mode</em>,
a collection of bonus levels previously available in <em>Sonic &amp; Knuckles</em>
as \"Blue Sphere\".</p>","game_id":1156,"genres":[{"genre_category":"Basic
Genres","genre_category_id":1,"genre_id":1,"genre_name":"Action"},{"genre_category":"Basic
Genres","genre_category_id":1,"genre_id":76,"genre_name":"Compilation"},{"genre_category":"Perspective","genre_category_id":2,"genre_id":128,"genre_name":"Behind
view"},{"genre_category":"Perspective","genre_category_id":2,"genre_id":17,"genre_name":"Side
view"},{"genre_category":"Gameplay","genre_category_id":4,"genre_id":21,"genre_name":"Platform"}],"moby_score":7.6,"moby_url":"https://www.mobygames.com/game/1156/sonic-knuckles-collection/","num_votes":17,"official_url":null,"platforms":[{"first_release_date":"1996","platform_id":3,"platform_name":"Windows"}],"sample_cover":{"height":800,"image":"https://cdn.mobygames.com/covers/7129144-sonic-knuckles-collection-windows-front-cover.jpg","platforms":["Windows"],"thumbnail_image":"https://cdn.mobygames.com/e39e215a-abf3-11ed-b83f-02420a000135.webp","width":647},"sample_screenshots":[{"caption":"Knuckles
glides acroos the landscape","height":222,"image":"https://cdn.mobygames.com/screenshots/1121053-sonic-knuckles-collection-windows-knuckles-glides-acroos-the-lan.jpg","thumbnail_image":"https://cdn.mobygames.com/88e0c3e8-ab72-11ed-96f9-02420a00019a.webp","width":319},{"caption":"Sonic
goes for a run","height":224,"image":"https://cdn.mobygames.com/screenshots/1119841-sonic-knuckles-collection-windows-sonic-goes-for-a-run.jpg","thumbnail_image":"https://cdn.mobygames.com/86800456-ab72-11ed-96f9-02420a00019a.webp","width":320},{"caption":"Special
bonus stage","height":224,"image":"https://cdn.mobygames.com/screenshots/1119742-sonic-knuckles-collection-windows-special-bonus-stage.jpg","thumbnail_image":"https://cdn.mobygames.com/864afe82-ab72-11ed-96f9-02420a00019a.webp","width":317},{"caption":"Sonic
gets ready to party","height":225,"image":"https://cdn.mobygames.com/screenshots/1119619-sonic-knuckles-collection-windows-sonic-gets-ready-to-party.jpg","thumbnail_image":"https://cdn.mobygames.com/86138c36-ab72-11ed-96f9-02420a00019a.webp","width":320}],"title":"Sonic
& Knuckles Collection"},{"alternate_titles":[],"description":"<p>While on
vacation, Tails and Sonic come across a recruiting advertisement for the World
Grand Prix race. Sonic is not interested at first, but once he notices that
Dr. Robotnik will participate he decides to enter. Robotnik is only interested
in the race because he''s learned the location of the Chaos Emeralds and now
he''ll get a chance to beat Sonic while he gets them. Knuckles finds out that
Sonic will participate, so he joins him. Amy overhears Robotnik''s plan, so
she will also be racing to get the Chaos Emeralds.</p>\n<p>There are many
modes of gameplay in Sonic R:</p>\n<p>Grand Prix: You race against the rest
of the characters on each of the circuits, and get a chance to race the level''s
boss to unlock some of the extra playable characters.</p>\n<p>Time Attack:
Run on one of the tracks as fast as you can, get 5 balloons or tag 4 characters.</p>\n<p>Multi
Player: 4 players on a split-screen or over a network. You can race against
up to three players or challenge them on a balloon finding game. The screen
can be divided horizontally or vertically for 2 player games. </p>","game_id":2899,"genres":[{"genre_category":"Basic
Genres","genre_category_id":1,"genre_id":6,"genre_name":"Racing / Driving"},{"genre_category":"Perspective","genre_category_id":2,"genre_id":128,"genre_name":"Behind
view"},{"genre_category":"Art Style","genre_category_id":13,"genre_id":57,"genre_name":"Anime
/ Manga"},{"genre_category":"Gameplay","genre_category_id":4,"genre_id":9,"genre_name":"Arcade"}],"moby_score":7.0,"moby_url":"https://www.mobygames.com/game/2899/sonic-r/","num_votes":59,"official_url":null,"platforms":[{"first_release_date":"1997","platform_id":3,"platform_name":"Windows"},{"first_release_date":"1997-10-31","platform_id":23,"platform_name":"SEGA
Saturn"}],"sample_cover":{"height":686,"image":"https://cdn.mobygames.com/covers/37033-sonic-r-windows-front-cover.jpg","platforms":["Windows"],"thumbnail_image":"https://cdn.mobygames.com/1b1d2548-ab65-11ed-993c-02420a00017b.webp","width":555},"sample_screenshots":[{"caption":"Title
Screen","height":480,"image":"https://cdn.mobygames.com/screenshots/10405548-sonic-r-windows-title-screen.jpg","thumbnail_image":"https://cdn.mobygames.com/ec87b13e-ac0f-11ed-b4f3-02420a000135.webp","width":640},{"caption":"A
Waterfall","height":480,"image":"https://cdn.mobygames.com/screenshots/10255167-sonic-r-sega-saturn-a-waterfall.jpg","thumbnail_image":"https://cdn.mobygames.com/a8b9aa8a-ac0e-11ed-8ce3-02420a000131.webp","width":640},{"caption":"Entering
a Loop","height":480,"image":"https://cdn.mobygames.com/screenshots/1322722-sonic-r-sega-saturn-entering-a-loop.jpg","thumbnail_image":"https://cdn.mobygames.com/26ae5d0a-ab74-11ed-8d2b-02420a00019e.webp","width":640},{"caption":"Knuckles
in a circle of the track, facing the other side, scenery, and a locked (unlockable)
door.","height":480,"image":"https://cdn.mobygames.com/screenshots/10406232-sonic-r-windows-knuckles-in-a-circle-of-the-track-facing-the-oth.jpg","thumbnail_image":"https://cdn.mobygames.com/eac27e06-ac0f-11ed-a631-02420a000135.webp","width":640},{"caption":"Main
Menu","height":480,"image":"https://cdn.mobygames.com/screenshots/10255137-sonic-r-sega-saturn-main-menu.jpg","thumbnail_image":"https://cdn.mobygames.com/a2eb16e8-ac0e-11ed-8ce3-02420a000131.webp","width":640}],"title":"Sonic
R"},{"alternate_titles":[{"description":"Working title","title":"CD Sonic
the Hedgehog"},{"description":"In-game title","title":"Sonic the Hedgehog
CD"}],"description":"<p>Sonic the Hedgehog and his self-proclaimed girlfriend
Amy Rose travel to Never Lake, only to discover the legendary Little Planet
there, tied in chains and covered by metal. It appears that Sonic''s archenemy,
Dr. Eggman, is using the powers of the planet to manipulate the fabric of
time. He created Sonic''s evil counterpart, Metal Sonic, who kidnaps Amy and
disappears. Now the brave hedgehog must explore the Little Planet, collect
seven jewels capable of altering the passage of time, free Amy, and defeat
Metal Sonic along with his master. </p>\n<p><em>Sonic CD</em> is a fast-paced
side-scrolling platform action game, similar in gameplay to other installments
of the series. Sonic uses his patented spin attacks to destroy the doctor''s
minions and collects various items, such as protective rings, shields, and
speed shoes. His special attacks include the Spin Dash and the Super Peel
Out. A stand-out gameplay feature of this installment is Sonic''s ability
to travel to past and future versions of the stages he traverses. Depending
on the player''s action in the past version of a level, the future versions
(which contain obligatory boss enemies) will change from \"bad\" to \"good\",
having more or fewer enemies and obstacles, respectively. </p>\n<p>Time Stones
can be collected by completing special stages, in which Sonic has to shoot
UFOs within an allotted time limit. The game''s \"good\" ending can be achieved
either by collecting all the seven Time Stones or by turning all future level
versions into \"good\". The game has features that take advantage of the CD
format such as CD audio, video clips, and more levels (over fifty in total).</p>","game_id":3316,"genres":[{"genre_category":"Basic
Genres","genre_category_id":1,"genre_id":1,"genre_name":"Action"},{"genre_category":"Perspective","genre_category_id":2,"genre_id":17,"genre_name":"Side
view"},{"genre_category":"Visual Presentation","genre_category_id":12,"genre_id":131,"genre_name":"2D
scrolling"},{"genre_category":"Art Style","genre_category_id":13,"genre_id":57,"genre_name":"Anime
/ Manga"},{"genre_category":"Gameplay","genre_category_id":4,"genre_id":21,"genre_name":"Platform"}],"moby_score":8.0,"moby_url":"https://www.mobygames.com/game/3316/sonic-cd/","num_votes":101,"official_url":null,"platforms":[{"first_release_date":"1995","platform_id":3,"platform_name":"Windows"},{"first_release_date":"1993-09-23","platform_id":20,"platform_name":"SEGA
CD"}],"sample_cover":{"height":800,"image":"https://cdn.mobygames.com/covers/4219323-sonic-cd-sega-cd-front-cover.jpg","platforms":["SEGA
CD"],"thumbnail_image":"https://cdn.mobygames.com/3168cf1a-abaa-11ed-a5ec-02420a00019b.webp","width":473},"sample_screenshots":[{"caption":"Something
is wrong with the Little Planet...","height":224,"image":"https://cdn.mobygames.com/screenshots/5361222-sonic-cd-windows-something-is-wrong-with-the-little-planet.png","thumbnail_image":"https://cdn.mobygames.com/43e6cf32-abde-11ed-848a-02420a0001d8.webp","width":320},{"caption":"Good
Future","height":240,"image":"https://cdn.mobygames.com/screenshots/482217-sonic-cd-sega-cd-good-future.png","thumbnail_image":"https://cdn.mobygames.com/701f31b4-ab6d-11ed-b721-02420a00019b.webp","width":320},{"caption":"Dr.
Robotnik","height":240,"image":"https://cdn.mobygames.com/screenshots/483579-sonic-cd-sega-cd-dr-robotnik.png","thumbnail_image":"https://cdn.mobygames.com/72ef0b3a-ab6d-11ed-be6f-02420a000197.webp","width":320},{"caption":"Title
screen","height":224,"image":"https://cdn.mobygames.com/screenshots/5360658-sonic-cd-windows-title-screen.png","thumbnail_image":"https://cdn.mobygames.com/3c6cbeb0-abde-11ed-a48a-02420a0001d0.webp","width":320},{"caption":"The
first round of the adventure is called Palmtree Panic.","height":224,"image":"https://cdn.mobygames.com/screenshots/5361426-sonic-cd-windows-the-first-round-of-the-adventure-is-called-palm.png","thumbnail_image":"https://cdn.mobygames.com/46ca1574-abde-11ed-8017-02420a0001d7.webp","width":320}],"title":"Sonic
CD"},{"alternate_titles":[{"description":"European title","title":"Sonic 3D:
Flickies'' Island"}],"description":"<p>While visiting Flicky Island, Sonic
notices that the Flickies, his small, feathered friends that can travel between
parallel worlds, were captured and turned into badniks (the robot enemies
of Sonic) by Dr. Robotnik in his never-ending quest for the Chaos Emeralds.
Seven levels separate Sonic from the final showdown with Robotnik.</p>\n<p><em>Sonic
3D</em> was the last Sonic title released for the Mega Drive, the only platforming
presence of the blue blur in the Sega Saturn, and one of his rare appearances
in Personal Computers. The title, considering the era it was released, is
somewhat misleading, as instead of full blown 3D graphics it uses an isometric
view where Sonic can move not only forwards and backwards, but also left and
right. </p>\n<p>Gameplay is much slower when compared to other games in the
series, and the number of badniks is reduced to five in each section. As usual,
when Sonic destroys one badnik, an animal leaps free from its insides, but
this time Sonic has to pick him up and lead them to an interdimensional ring.
While they can be taken one by one, exploring the level with all five allow
the player to reach for otherwise inaccessible continue tokens. \nUnlike all
other previous games, time isn''t a requirement: if the player completes a
level in more than 10 minutes, it would only mean there would be no time bonus
at the end. Finally, to collect all seven Chaos Emeralds, Sonic first must
find Tails or Knuckles and offer them at least 50 rings.</p>","game_id":3494,"genres":[{"genre_category":"Basic
Genres","genre_category_id":1,"genre_id":1,"genre_name":"Action"},{"genre_category":"Perspective","genre_category_id":2,"genre_id":129,"genre_name":"Diagonal-down"},{"genre_category":"Visual
Presentation","genre_category_id":12,"genre_id":25,"genre_name":"Isometric"},{"genre_category":"Gameplay","genre_category_id":4,"genre_id":9,"genre_name":"Arcade"}],"moby_score":6.9,"moby_url":"https://www.mobygames.com/game/3494/sonic-3d-blast/","num_votes":100,"official_url":null,"platforms":[{"first_release_date":"2018-05-29","platform_id":1,"platform_name":"Linux"},{"first_release_date":"1997","platform_id":3,"platform_name":"Windows"},{"first_release_date":"1996-11","platform_id":16,"platform_name":"Genesis"},{"first_release_date":"1996-11-20","platform_id":23,"platform_name":"SEGA
Saturn"},{"first_release_date":"2018-05-29","platform_id":74,"platform_name":"Macintosh"},{"first_release_date":"2007-11-02","platform_id":82,"platform_name":"Wii"}],"sample_cover":{"height":800,"image":"https://cdn.mobygames.com/covers/4126167-sonic-3d-blast-genesis-front-cover.jpg","platforms":["Genesis"],"thumbnail_image":"https://cdn.mobygames.com/ba95aea6-aba6-11ed-8bb9-02420a000197.webp","width":565},"sample_screenshots":[{"caption":"Tails
also unlocks a Special Stage","height":240,"image":"https://cdn.mobygames.com/screenshots/2741317-sonic-3d-blast-windows-tails-also-unlocks-a-special-stage.png","thumbnail_image":"https://cdn.mobygames.com/68b6e744-ab83-11ed-bd13-02420a00019c.webp","width":320},{"caption":"that,
in the cannon, is Sonic..","height":224,"image":"https://cdn.mobygames.com/screenshots/362094-sonic-3d-blast-genesis-that-in-the-cannon-is-sonic.png","thumbnail_image":"https://cdn.mobygames.com/7f8e14c2-ab6c-11ed-b042-02420a00019f.webp","width":320},{"caption":"Panic
Puppet Zone - ring shower..","height":224,"image":"https://cdn.mobygames.com/screenshots/364951-sonic-3d-blast-genesis-panic-puppet-zone-ring-shower.png","thumbnail_image":"https://cdn.mobygames.com/854b07a8-ab6c-11ed-b042-02420a00019f.webp","width":320},{"caption":"Pause
screen, showing off a map and other information","height":240,"image":"https://cdn.mobygames.com/screenshots/3121486-sonic-3d-blast-windows-pause-screen-showing-off-a-map-and-other-.png","thumbnail_image":"https://cdn.mobygames.com/8fc35d12-ab89-11ed-8b6f-02420a00019c.webp","width":320},{"caption":"Second
boss","height":240,"image":"https://cdn.mobygames.com/screenshots/2750281-sonic-3d-blast-windows-second-boss.png","thumbnail_image":"https://cdn.mobygames.com/8d210966-ab83-11ed-b042-02420a00019f.webp","width":320}],"title":"Sonic
3D Blast"},{"alternate_titles":[{"description":"Updated Edition title","title":"Sonic
Adventure International"}],"description":"<p>Yet again Sonic and friends find
themselves wrapped up in the schemes of Dr. Robotnik. This time he plans to
exploit the power of Chaos, a malevolent being that feeds upon the power of
the Chaos Emeralds. In their own way, five heroes will do what they can to
save Station Square and, perhaps, the entire world.</p>\n<p>The player can
select from which character to play after meeting them with Sonic, and each
has its own unique version of each level. Sonic''s levels involve racing through
a level as quickly as possible, destroying any robots along the way. Tails
can hover over distances, and his levels are similar to Sonic''s except that
he must beat an opponent to the finish line. Amy can''t charge up ramps or
run as quickly as Sonic and Tails, but she does have a giant hammer she can
break obstacles with. The goal of her levels is to avoid being captured by
a robot sent after her by Dr. Robotnik. E102 Gamma is a slow, difficult to
maneuver robot, but it has guns and missiles that can lock on to several targets
at once, chaining together attacks and earning a higher score and more time
on the clock. The goal of its levels is to reach a boss enemy and destroy
it before time runs out. The final character is Big the Cat, a fisherman searching
for his lost frog. Big''s levels are almost entirely devoid of action or fighting;
he just fishes. Each character can find items that increase their abilities
as the story progresses, such as a ring for Sonic that lets him home along
lines of rings, or improved lures for Big. A final chapter wrapping up the
story is unlocked after all other characters have been completed.</p>\n<p>Additionally,
each of the characters can visit the Chao garden where they can raise baby
Chaos. By petting, feeding, and showing animals found in levels to the Chaos
they can grow stronger, and optionally be put into races against other Chaos.
As well each level in the game has additional objectives that can be completed
to acquire extra emblems, although the emblems don''t do anything in this
version of the game. Finally, at one time it was possible to go online and
download additional levels and bonuses in the game, such as a Christmas theme
for Station Square and a level sponsored by AT&amp;T, but since SegaNet shut
down this is no longer possible.</p>","game_id":3530,"genres":[{"genre_category":"Basic
Genres","genre_category_id":1,"genre_id":1,"genre_name":"Action"},{"genre_category":"Perspective","genre_category_id":2,"genre_id":128,"genre_name":"Behind
view"},{"genre_category":"Art Style","genre_category_id":13,"genre_id":57,"genre_name":"Anime
/ Manga"},{"genre_category":"Gameplay","genre_category_id":4,"genre_id":24,"genre_name":"Metroidvania"}],"moby_score":8.1,"moby_url":"https://www.mobygames.com/game/3530/sonic-adventure/","num_votes":127,"official_url":"http://web.archive.org/web/20000830142357/http://www.sega.co.jp/sonicadv/","platforms":[{"first_release_date":"1998-12-23","platform_id":8,"platform_name":"Dreamcast"},{"first_release_date":"2010-09-15","platform_id":69,"platform_name":"Xbox
360"},{"first_release_date":"2010-09-21","platform_id":81,"platform_name":"PlayStation
3"}],"sample_cover":{"height":715,"image":"https://cdn.mobygames.com/covers/36438-sonic-adventure-dreamcast-front-cover.jpg","platforms":["Dreamcast"],"thumbnail_image":"https://cdn.mobygames.com/19f9abdc-ab65-11ed-993c-02420a00017b.webp","width":715},"sample_screenshots":[{"caption":"Steep
ramp","height":480,"image":"https://cdn.mobygames.com/screenshots/10406629-sonic-adventure-dreamcast-steep-ramp.jpg","thumbnail_image":"https://cdn.mobygames.com/e82e710e-ac0f-11ed-b7c8-02420a000136.webp","width":768},{"caption":"Sonic
vs Chaos 0","height":675,"image":"https://cdn.mobygames.com/screenshots/9648175-sonic-adventure-xbox-360-sonic-vs-chaos-0.png","thumbnail_image":"https://cdn.mobygames.com/cf16b9d0-ac08-11ed-a631-02420a000135.webp","width":1200},{"caption":"Character","height":675,"image":"https://cdn.mobygames.com/screenshots/9643778-sonic-adventure-xbox-360-character.png","thumbnail_image":"https://cdn.mobygames.com/cc574494-ac08-11ed-9eb0-02420a000136.webp","width":1200},{"caption":"Aw,
Y E A H! This is happening...","height":675,"image":"https://cdn.mobygames.com/screenshots/9648160-sonic-adventure-xbox-360-aw-y-e-a-h-this-is-happening.png","thumbnail_image":"https://cdn.mobygames.com/c95f7eb4-ac08-11ed-a631-02420a000135.webp","width":1200},{"caption":"Title
Screen","height":480,"image":"https://cdn.mobygames.com/screenshots/10405243-sonic-adventure-dreamcast-title-screen.jpg","thumbnail_image":"https://cdn.mobygames.com/ed6efa4e-ac0f-11ed-887e-02420a00012e.webp","width":768}],"title":"Sonic
Adventure"}]}
'
headers:
CF-RAY:
- 963b8a678d8fea84-YYZ
Cf-Cache-Status:
- DYNAMIC
Connection:
- keep-alive
Content-Encoding:
- br
Content-Security-Policy:
- frame-ancestors 'none';
Content-Type:
- application/json
Date:
- Wed, 23 Jul 2025 13:31:53 GMT
Nel:
- '{"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}'
Ratelimit-Limit:
- "720"
Ratelimit-Policy:
- 1;w=1, 720;w=3600
Ratelimit-Remaining:
- "319"
Ratelimit-Reset:
- "1687"
Report-To:
- '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=gewoHtfpka1EZGloviSxOZNICxd%2B%2BuVobvZ47ImpJ3RGDq0Eupe3k1t2poUK7hCZpD8MnL6vL9UllErtF%2FUZ1HnWHAWZvTqeHkmK7dSl"}]}'
Server:
- cloudflare
Set-Cookie:
- session-www=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.xxxxxx.xxxxxxxxxxxxxxxxxxxxxx;
HttpOnly; SameSite=Lax; Path=/; Domain=mobygames.com
Transfer-Encoding:
- chunked
Vary:
- Cookie
X-Frame-Options:
- DENY
X-Xss-Protection:
- 1; mode=block
status:
code: 200
message: OK
version: 1

View File

@@ -93,7 +93,7 @@ interactions:
body: null
headers: {}
method: GET
uri: https://retroachievements.org/API/API_GetUserCompletionProgress.php?u=arcanecraeda&c=5
uri: https://retroachievements.org/API/API_GetUserCompletionProgress.php?u=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&c=5
response:
body:
string: '{"Count":0,"Total":0,"Results":[]}'
@@ -135,4 +135,86 @@ interactions:
status:
code: 200
message: OK
- request:
body: null
headers: {}
method: GET
uri: https://retroachievements.org/API/API_GetUserCompletionProgress.php?u=arcanecraeda&c=5
response:
body:
string: '{"message":"Unauthenticated.","errors":[{"status":419,"code":"unauthorized","title":"Unauthenticated."}]}'
headers:
Access-Control-Allow-Origin:
- "*"
CF-RAY:
- 963bc2b9193cf4cc-YYZ
Cache-Control:
- no-cache, private
Cf-Cache-Status:
- DYNAMIC
Connection:
- keep-alive
Content-Type:
- application/json
Date:
- Wed, 23 Jul 2025 14:10:19 GMT
Nel:
- '{"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}'
Report-To:
- '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=v42vzRzUlv38Qu6nfqABTwRXH9xci4Rtq02vHnmu4ziBD2DKqb1nS4qM4Y97lE3auWg7LUMH%2FpqvxzixlKcsRulm%2BSNrz6oAxnJFhIDHGxrGzQ%3D%3D"}]}'
Server:
- cloudflare
Transfer-Encoding:
- chunked
X-Robots-Tag:
- all
status:
code: 401
message: Unauthorized
- request:
body: null
headers: {}
method: GET
uri: https://retroachievements.org/API/API_GetUserCompletionProgress.php?u=arcanecraeda&c=5
response:
body:
string: '{"Count":0,"Total":0,"Results":[]}'
headers:
Access-Control-Allow-Origin:
- "*"
CF-RAY:
- 963bc80ffef5544f-YYZ
Cache-Control:
- no-cache, private
Cf-Cache-Status:
- DYNAMIC
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Type:
- application/json
Date:
- Wed, 23 Jul 2025 14:13:58 GMT
Nel:
- '{"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}'
Report-To:
- '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=V7uJ1rkaLPkSaebnfL%2FVchvTX6z%2BXIngogJGE654HkCwAGU%2FRlxFf4h301nG9sFohNl8EuyHTsfXsAjqDvi7cstOK2KUu%2FaJRm6xJGyz9HtrUQ%3D%3D"}]}'
Server:
- cloudflare
Transfer-Encoding:
- chunked
Vary:
- Accept-Encoding
X-Cache:
- MISS
X-Content-Type-Options:
- nosniff
X-Frame-Options:
- SAMEORIGIN
X-Robots-Tag:
- all
status:
code: 200
message: OK
version: 1

View File

@@ -0,0 +1,42 @@
interactions:
- request:
body: null
headers: {}
method: GET
uri: https://api.screenscraper.fr/api2/jeuInfos.php?gameid=999999
response:
body:
string: "Erreur : Jeu non trouv\xE9e ! "
headers:
Access-Control-Allow-Headers:
- X-Requested-With
Access-Control-Allow-Methods:
- GET
Access-Control-Allow-Origin:
- "*"
Cache-Control:
- no-cache, must-revalidate
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Type:
- application/json; charset=utf-8
Date:
- Wed, 23 Jul 2025 03:47:09 GMT
Expires:
- "0"
Pragma:
- no-cache
Server:
- nginx/1.14.2
Set-Cookie:
- SESSID=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Transfer-Encoding:
- chunked
Vary:
- Accept-Encoding
status:
code: 404
message: Not Found
version: 1

View File

@@ -0,0 +1,38 @@
interactions:
- request:
body: null
headers: {}
method: GET
uri: https://api.screenscraper.fr/api2/jeuInfos.php?crc=abc123&systemeid=1
response:
body:
string: "Champ crc, md5 ou sha1 erron\xE9 "
headers:
Access-Control-Allow-Headers:
- X-Requested-With
Access-Control-Allow-Methods:
- GET
Access-Control-Allow-Origin:
- "*"
Cache-Control:
- no-cache, must-revalidate
Connection:
- keep-alive
Content-Type:
- application/json; charset=utf-8
Date:
- Wed, 23 Jul 2025 03:46:42 GMT
Expires:
- "0"
Pragma:
- no-cache
Server:
- nginx/1.14.2
Set-Cookie:
- SESSID=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Transfer-Encoding:
- chunked
status:
code: 400
message: Bad Request
version: 1

View File

@@ -0,0 +1,775 @@
interactions:
- request:
body: null
headers: {}
method: GET
uri: https://api.screenscraper.fr/api2/jeuInfos.php?gameid=1
response:
body:
string:
"{\n\t\"header\" : {\n\t\t\"APIversion\" : \"2.0\",\n\t\t\"dateTime\"\
\ : \"2025-07-19\",\n\t\t\"commandRequested\" : \"https://neoclone.screenscraper.fr/api2/jeuInfos.php?gameid=1&devid=zurdi15&devpassword=xTJwoOFjOQG&output=json&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\"\
,\n\t\t\"success\": \"true\",\n\t\t\"error\": \"\"\n\t},\n\t\"response\" :\
\ {\n\t\t\"serveurs\" : {\n\t\t\t\"cpu1\": \"0\",\n\t\t\t\"cpu2\": \"0\",\n\
\t\t\t\"cpu3\": \"0\",\n\t\t\t\"cpu4\": \"0\",\n\t\t\t\"threadsmin\": \"3484\"\
,\n\t\t\t\"nbscrapeurs\": \"535\",\n\t\t\t\"apiacces\": \"7207526\",\n\t\t\
\t\"closefornomember\": \"0\",\n\t\t\t\"closeforleecher\": \"0\",\n\t\t\t\"\
maxthreadfornonmember\": \"256\",\n\t\t\t\"threadfornonmember\": \"31\",\n\
\t\t\t\"maxthreadformember\": \"4096\",\n\t\t\t\"threadformember\": \"114\"\
\n\t\t},\n\t\t\"ssuser\" : {\n\t\t\t\"id\": \"arcaneasada\",\n\t\t\t\"numid\"\
: \"27224648\",\n\t\t\t\"niveau\": \"1\",\n\t\t\t\"contribution\": \"0\",\n\
\t\t\t\"uploadsysteme\": \"0\",\n\t\t\t\"uploadinfos\": \"0\",\n\t\t\t\"romasso\"\
: \"0\",\n\t\t\t\"uploadmedia\": \"0\",\n\t\t\t\"propositionok\": \"0\",\n\
\t\t\t\"propositionko\": \"0\",\n\t\t\t\"quotarefu\": \"0\",\n\t\t\t\"maxthreads\"\
: \"1\",\n\t\t\t\"maxdownloadspeed\": \"128\",\n\t\t\t\"requeststoday\": \"\
0\",\n\t\t\t\"requestskotoday\": \"0\",\n\t\t\t\"maxrequestspermin\": \"3072\"\
,\n\t\t\t\"maxrequestsperday\": \"20000\",\n\t\t\t\"maxrequestskoperday\"\
: \"2000\",\n\t\t\t\"visites\": \"19\",\n\t\t\t\"datedernierevisite\": \"\
2025-07-22 04:21:46\",\n\t\t\t\"favregion\": \"\"\n\t\t\t},\n\t\t\"jeu\" :\
\ {\n\t\t\t\"id\": \"1\",\n\t\t\t\"romid\": \"\",\n\t\t\t\"notgame\": \"false\"\
,\n\t\t\t\"noms\": [\n\t\t\t\t{\"region\": \"ss\", \"text\": \"Battletoads\"\
},\n\t\t\t\t{\"region\": \"wor\", \"text\": \"Battletoads\"}\n\t\t\t],\n\t\
\t\t\"cloneof\": \"0\",\n\t\t\t\"systeme\": {\"id\": \"1\", \"text\": \"Megadrive\"\
},\n\t\t\t\"editeur\": {\"id\": \"1\", \"text\": \"Tradewest\"},\n\t\t\t\"\
developpeur\": {\"id\": \"2\", \"text\": \"Rareware\"},\n\t\t\t\"joueurs\"\
: {\"text\": \"1-2\"},\n\t\t\t\"note\": {\"text\": \"14\"},\n\t\t\t\"topstaff\"\
: \"0\",\n\t\t\t\"rotation\": \"0\",\n\t\t\t\"synopsis\": [\n\t\t\t\t{ \"\
langue\": \"en\", \"text\": \"The Dark Queen has attacked and kidnapped (toadnapped?)\
\ your best buddy.. To make matters worse, she nabbed the best looking girl\
\ around! So, what will you do? Cry? Whimper? Hide? Call the Starcops? NO!\
\ Cause you're a Battletoad! Battletoads don't cry, hide or call for help!\
\ And they certainly don't whimper! They get MAD then they get EVEN! So grab\
\ your blaster, hop on the Toadster and go get 'em!\\n\\nYou will control\
\ a battletoad through various levels. Some are side-scrolling beat-em-ups,\
\ other are vertical drops down a shaft, fighting as you go, others have you\
\ riding a rocket or surfboard, shooting or avoiding enemies. At the end of\
\ each area, there is a boss. In some areas, about half-way through, you may\
\ have a mini-boss.\"},\n\t\t\t\t{ \"langue\": \"fr\", \"text\": \"Battletoads\
\ sur Megadrive vous met dans la peau de Zitz, crapaud aux pouvoirs qui sortent\
\ de l'ordinaire. Votre but : sauver vos deux fr\\u00e8res enlev\\u00e9s par\
\ la Reine Noire. D\\u00e9barrassez-vous de vos adversaires \\u00e0 travers\
\ diverses phases de jeu passant par la conduite de v\\u00e9hicules, le simple\
\ combat rapproch\\u00e9 ou le jeu de plates-formes.\"},\n\t\t\t\t{ \"langue\"\
: \"de\", \"text\": \"Die Dunkle K\\u00f6nigin hat deine besten Freunde entf\\\
u00fchrt. Auf deiner Reise durch die gef\\u00e4hrliche Umgebung erwarten dich\
\ hinterlistige Fallen und gef\\u00e4hrliche Gegner, w\\u00e4hrend du dich\
\ durch unterirdische H\\u00f6hlen und gef\\u00e4hrliche Steppen bis hin zum\
\ Schattenturm durchk\\u00e4mpfst, in dem dich die Dunkle K\\u00f6nigin pers\\\
u00f6nlich erwartet.\"},\n\t\t\t\t{ \"langue\": \"es\", \"text\": \"Cuando\
\ la malvada Reina de la Oscuridad secuestra a su mejor amigo y a la Princesa\
\ Ang\\u00e9lica, es el momento de volverse loco para despu\\u00e9s desquitarse,\
\ as\\u00ed que prepara las armas nucleares porque las vas a necesitar para\
\ rescatar a tus amigos y salir con la piel verde intacta.\"},\n\t\t\t\t{\
\ \"langue\": \"it\", \"text\": \"La cattiva Regina delle Tenebre rapisce\
\ il vostro migliore amico e la bella Principessa Angelica! C'\\u00e8 veramente\
\ da IMPAZZIRE e VENDICARSI! Preparate i vostri pugni nucleari perch\\u00e9\
\ ne avrete bisogno per salvare i vostri amici e venirne fuori sani e salvi!\"\
},\n\t\t\t\t{ \"langue\": \"pt\", \"text\": \"Sapo (\\u00e9 voc\\u00ea mesmo),\
\ a Rainha das Trevas acaba de raptar a Princesa Ang\\u00e9lica e Pimple,\
\ seu melhor companheiro. A paz acabou e suas encrencas est\\u00e3o apenas\
\ come\\u00e7ando.\\nEsperando por voc\\u00ea estar\\u00e3o 12 fases de desfiladeiros\
\ intermin\\u00e1veis, t\\u00faneis aterrorizantes, infernos ardentes e muito\
\ mais (Sem mencionar em todos os inimigos que habitam estes lugares!). Os\
\ combates n\\u00e3o ser\\u00e3o somente em terra firme, voc\\u00ea ter\\\
u00e1 a oportunidade de experimentar uma Bicicleta de Corrida, um Jato Turbo\
\ e uma Prancha Espacial!!\\nE se preferir uma companhia, chame um outro sapo\
\ e fa\\u00e7a uma amea\\u00e7a dupla para a Rainha das Trevas. A situa\\\
u00e7\\u00e3o \\u00e9 esta, e apesar de preta voc\\u00ea ter\\u00e1 que fazer\
\ alguma coisa a respeito. Agora!!!\"}\n\t\t\t],\n\t\t\t\"classifications\"\
: [\n\t\t\t\t{\"type\": \"VRC\", \"text\": \"GA\"}\n\t\t\t],\n\t\t\t\"dates\"\
: [\n\t\t\t\t{ \"region\": \"fr\", \"text\": \"1993-03-23\"},\n\t\t\t\t{ \"\
region\": \"us\", \"text\": \"1991-07-13\"},\n\t\t\t\t{ \"region\": \"jp\"\
, \"text\": \"1993-03-23\"},\n\t\t\t\t{ \"region\": \"eu\", \"text\": \"1993-03-26\"\
}\n\t\t\t],\n\t\t\t\"genres\": [\n\t\t\t\t{ \"id\": \"1\",\n\t\t\t\t\"nomcourt\"\
: \"0107\",\n\t\t\t\t\"principale\": \"1\",\n\t\t\t\t\"parentid\": \"0\",\n\
\t\t\t\t\"noms\": [\n\t\t\t\t\t{ \"langue\": \"en\", \"text\": \"Beat'em Up\"\
},\n\t\t\t\t\t{ \"langue\": \"fr\", \"text\": \"Beat'em All\"},\n\t\t\t\t\t\
{ \"langue\": \"de\", \"text\": \"Beat'em Up\"},\n\t\t\t\t\t{ \"langue\":\
\ \"es\", \"text\": \"Beat'em Up\"},\n\t\t\t\t\t{ \"langue\": \"it\", \"text\"\
: \"Beat'em All\"},\n\t\t\t\t\t{ \"langue\": \"pt\", \"text\": \"Briga de\
\ rua\"}\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"modes\": [\n\t\t\t\t{\
\ \"id\": \"4\",\n\t\t\t\t\"nomcourt\": \"\",\n\t\t\t\t\"principale\": \"\
1\",\n\t\t\t\t\"parentid\": \"0\",\n\t\t\t\t\"noms\": [\n\t\t\t\t\t{ \"langue\"\
: \"en\", \"text\": \"Single-player\"},\n\t\t\t\t\t{ \"langue\": \"fr\", \"\
text\": \"Jouable en solo\"},\n\t\t\t\t\t{ \"langue\": \"de\", \"text\": \"\
Einzelspieler\"},\n\t\t\t\t\t{ \"langue\": \"es\", \"text\": \"Jugador individual\"\
},\n\t\t\t\t\t{ \"langue\": \"it\", \"text\": \"Giocatore singolo\"},\n\t\t\
\t\t\t{ \"langue\": \"pt\", \"text\": \"Jogador individual\"}\n\t\t\t\t\t\
]\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"familles\": [\n\t\t\t\t{ \"id\": \"20297\"\
,\n\t\t\t\t\"nomcourt\": \"\",\n\t\t\t\t\"principale\": \"0\",\n\t\t\t\t\"\
parentid\": \"0\",\n\t\t\t\t\"noms\": [\n\t\t\t\t\t{ \"langue\": \"fr\", \"\
text\": \"Battletoads\"}\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"themes\"\
: [\n\t\t\t\t{ \"id\": \"3\",\n\t\t\t\t\"nomcourt\": \"\",\n\t\t\t\t\"principale\"\
: \"1\",\n\t\t\t\t\"parentid\": \"0\",\n\t\t\t\t\"noms\": [\n\t\t\t\t\t{ \"\
langue\": \"fr\", \"text\": \"Animaux\"},\n\t\t\t\t\t{ \"langue\": \"de\"\
, \"text\": \"Haustiere\"},\n\t\t\t\t\t{ \"langue\": \"es\", \"text\": \"\
Animales\"}\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"medias\": [\n\t\t\t\
\t{\"type\": \"sstitle\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"url\":\
\ \"https://neoclone.screenscraper.fr/api2/mediaJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=sstitle(wor)\"\
,\n\t\t\t\t\"region\": \"wor\",\n\t\t\t\t\"crc\": \"ddf76beb\",\n\t\t\t\t\"\
md5\": \"90a26094162b11dc8f914725b5b6e67d\",\n\t\t\t\t\"sha1\": \"c38dc8d5f3af5d72c25c3704a1e02ec23cfede5f\"\
,\n\t\t\t\t\"size\": \"5617\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t{\"\
type\": \"ss\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"url\": \"https://neoclone.screenscraper.fr/api2/mediaJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=ss(wor)\"\
,\n\t\t\t\t\"region\": \"wor\",\n\t\t\t\t\"crc\": \"7c994e8a\",\n\t\t\t\t\"\
md5\": \"dbe6f3584a023a8988e6fa9ad21908d8\",\n\t\t\t\t\"sha1\": \"bb7198fcce3c7ccf77e1a83577e586ba721bddef\"\
,\n\t\t\t\t\"size\": \"16857\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t{\"\
type\": \"fanart\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"url\": \"https://neoclone.screenscraper.fr/api2/mediaJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=fanart\"\
,\n\t\t\t\t\"crc\": \"e2c71d0f\",\n\t\t\t\t\"md5\": \"631016b964745e7f9d390470e3d10ab8\"\
,\n\t\t\t\t\"sha1\": \"fa95c01f527de41e7971883f4ca94913763359f1\",\n\t\t\t\
\t\"size\": \"973047\",\n\t\t\t\t\"format\": \"jpg\"},\n\t\t\t\t{\"type\"\
: \"video\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"url\": \"https://neoclone.screenscraper.fr/api2/mediaVideoJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=video\"\
,\n\t\t\t\t\"crc\": \"40387683\",\n\t\t\t\t\"md5\": \"1551b6607157aa781e45c5d2394019eb\"\
,\n\t\t\t\t\"sha1\": \"a007fbb781840d6cbc16e7cf93ce8b1eddb8e687\",\n\t\t\t\
\t\"size\": \"6655535\",\n\t\t\t\t\"format\": \"mp4\"},\n\t\t\t\t{\"type\"\
: \"video-normalized\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"url\": \"\
https://neoclone.screenscraper.fr/api2/mediaVideoJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=video-normalized\"\
,\n\t\t\t\t\"crc\": \"b6dd91f7\",\n\t\t\t\t\"md5\": \"68983d07332efb381c691326a556bf1d\"\
,\n\t\t\t\t\"sha1\": \"7c4de9deda10dbceec3749d4248dd00bc90eaf4e\",\n\t\t\t\
\t\"size\": \"1878898\",\n\t\t\t\t\"format\": \"mp4\"},\n\t\t\t\t{\"type\"\
: \"screenmarquee\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"url\": \"https://neoclone.screenscraper.fr/api2/mediaJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=screenmarquee(wor)\"\
,\n\t\t\t\t\"region\": \"wor\",\n\t\t\t\t\"crc\": \"a757a9ee\",\n\t\t\t\t\"\
md5\": \"5d7245561ecf710f8cc979cf0700f5fc\",\n\t\t\t\t\"sha1\": \"745e7e3772879bb251319d8706fda2e62e0a29ca\"\
,\n\t\t\t\t\"size\": \"432162\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t\
{\"type\": \"screenmarqueesmall\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\
\"url\": \"https://neoclone.screenscraper.fr/api2/mediaJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=screenmarqueesmall(wor)\"\
,\n\t\t\t\t\"region\": \"wor\",\n\t\t\t\t\"crc\": \"7f85fc93\",\n\t\t\t\t\"\
md5\": \"ce81c0c3dc46a9e8f028f5a76cfcafac\",\n\t\t\t\t\"sha1\": \"4f4f0b8961670ddb264d35a5841e1fa9178b2914\"\
,\n\t\t\t\t\"size\": \"89719\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t{\"\
type\": \"manuel\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"url\": \"https://neoclone.screenscraper.fr/api2/mediaManuelJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=manuel(br)\"\
,\n\t\t\t\t\"region\": \"br\",\n\t\t\t\t\"crc\": \"45f85918\",\n\t\t\t\t\"\
md5\": \"8266356bc75fbf9de80160dfaf344637\",\n\t\t\t\t\"sha1\": \"68cff0e5da79fea0ceed988b3ddb2280d504dd0a\"\
,\n\t\t\t\t\"size\": \"3005546\",\n\t\t\t\t\"format\": \"pdf\"},\n\t\t\t\t\
{\"type\": \"manuel\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"url\": \"\
https://neoclone.screenscraper.fr/api2/mediaManuelJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=manuel(fr)\"\
,\n\t\t\t\t\"region\": \"fr\",\n\t\t\t\t\"crc\": \"5eaa6741\",\n\t\t\t\t\"\
md5\": \"2820e451731c06d70178e5378fbc5348\",\n\t\t\t\t\"sha1\": \"4d90b2cd02ee2a4b293cdbbc64bf86f621877b87\"\
,\n\t\t\t\t\"size\": \"2796200\",\n\t\t\t\t\"format\": \"pdf\"},\n\t\t\t\t\
{\"type\": \"manuel\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"url\": \"\
https://neoclone.screenscraper.fr/api2/mediaManuelJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=manuel(jp)\"\
,\n\t\t\t\t\"region\": \"jp\",\n\t\t\t\t\"crc\": \"08a3c847\",\n\t\t\t\t\"\
md5\": \"45606798e8941bccc2f3d908adf28c3e\",\n\t\t\t\t\"sha1\": \"d2eb6608fd725c4f043849f453458a8dcd21f120\"\
,\n\t\t\t\t\"size\": \"5133703\",\n\t\t\t\t\"format\": \"pdf\"},\n\t\t\t\t\
{\"type\": \"manuel\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"url\": \"\
https://neoclone.screenscraper.fr/api2/mediaManuelJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=manuel(us)\"\
,\n\t\t\t\t\"region\": \"us\",\n\t\t\t\t\"crc\": \"6139c927\",\n\t\t\t\t\"\
md5\": \"1a9776fce40d8d5e3afb4f3cc173129f\",\n\t\t\t\t\"sha1\": \"639e1c10db086d7e0be36c3cecb457bcb2e2f3f5\"\
,\n\t\t\t\t\"size\": \"2483191\",\n\t\t\t\t\"format\": \"pdf\"},\n\t\t\t\t\
{\"type\": \"steamgrid\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"url\":\
\ \"https://neoclone.screenscraper.fr/api2/mediaJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=steamgrid\"\
,\n\t\t\t\t\"crc\": \"091457a6\",\n\t\t\t\t\"md5\": \"4f6edca1f6cb9bac62d8fadd128dc817\"\
,\n\t\t\t\t\"sha1\": \"f41419ab4e54e1cff9c26d5d63cb828ee375cc47\",\n\t\t\t\
\t\"size\": \"32938\",\n\t\t\t\t\"format\": \"jpg\"},\n\t\t\t\t{\"type\":\
\ \"maps\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"url\": \"https://neoclone.screenscraper.fr/api2/mediaJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=maps(Plans(In-GameMap))\"\
,\n\t\t\t\t\"version\": \"In-GameMap\",\n\t\t\t\t\"crc\": \"f002242c\",\n\t\
\t\t\t\"md5\": \"418120091fa2fd5e01fa127e2be2747b\",\n\t\t\t\t\"sha1\": \"\
0e41b9bce09f88399c24987a9cb7bf2c74ec7ecf\",\n\t\t\t\t\"size\": \"118486\"\
,\n\t\t\t\t\"format\": \"jpg\"},\n\t\t\t\t{\"type\": \"wheel\",\n\t\t\t\t\"\
parent\": \"jeu\",\n\t\t\t\t\"url\": \"https://neoclone.screenscraper.fr/api2/mediaJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=wheel(jp)\"\
,\n\t\t\t\t\"region\": \"jp\",\n\t\t\t\t\"crc\": \"1d7d33ff\",\n\t\t\t\t\"\
md5\": \"a74e7b3bf2bc3b74a97a0a96bfcd52f2\",\n\t\t\t\t\"sha1\": \"fa205b072aa0dea362d26716fdafc54ce84ff702\"\
,\n\t\t\t\t\"size\": \"69689\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t{\"\
type\": \"wheel\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"url\": \"https://neoclone.screenscraper.fr/api2/mediaJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=wheel(wor)\"\
,\n\t\t\t\t\"region\": \"wor\",\n\t\t\t\t\"crc\": \"e970d5af\",\n\t\t\t\t\"\
md5\": \"58bff299678b2e857b8c700f2fc5d9eb\",\n\t\t\t\t\"sha1\": \"a79283e96d16eea9a12e62255ffa9d29ea1a3ebb\"\
,\n\t\t\t\t\"size\": \"265602\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t\
{\"type\": \"wheel-carbon\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"url\"\
: \"https://neoclone.screenscraper.fr/api2/mediaJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=wheel-carbon(jp)\"\
,\n\t\t\t\t\"region\": \"jp\",\n\t\t\t\t\"crc\": \"8caab1a9\",\n\t\t\t\t\"\
md5\": \"e037dff95ea877800892015825ea25d0\",\n\t\t\t\t\"sha1\": \"e190db495840236ae02fe2cb250c6c190468d1dc\"\
,\n\t\t\t\t\"size\": \"198853\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t\
{\"type\": \"wheel-carbon\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"url\"\
: \"https://neoclone.screenscraper.fr/api2/mediaJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=wheel-carbon(wor)\"\
,\n\t\t\t\t\"region\": \"wor\",\n\t\t\t\t\"crc\": \"cbea2dcf\",\n\t\t\t\t\"\
md5\": \"834c39a33123e428ca7f34d15dfbfe13\",\n\t\t\t\t\"sha1\": \"59a7c800f75ee1a9de9f1ce79bf65bbee737e460\"\
,\n\t\t\t\t\"size\": \"219591\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t\
{\"type\": \"wheel-steel\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"url\"\
: \"https://neoclone.screenscraper.fr/api2/mediaJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=wheel-steel(jp)\"\
,\n\t\t\t\t\"region\": \"jp\",\n\t\t\t\t\"crc\": \"42e226af\",\n\t\t\t\t\"\
md5\": \"707cb35881420b0294859c8440333c94\",\n\t\t\t\t\"sha1\": \"5a643a147dd5d9d9cd4df35255c1d5f80aa4c22d\"\
,\n\t\t\t\t\"size\": \"194132\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t\
{\"type\": \"wheel-steel\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"url\"\
: \"https://neoclone.screenscraper.fr/api2/mediaJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=wheel-steel(wor)\"\
,\n\t\t\t\t\"region\": \"wor\",\n\t\t\t\t\"crc\": \"0d27311d\",\n\t\t\t\t\"\
md5\": \"b2b2443758967416ab74c84e99f14fab\",\n\t\t\t\t\"sha1\": \"2a95ee0f05ea13450e74649ae239d6a9da1fb174\"\
,\n\t\t\t\t\"size\": \"213643\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t\
{\"type\": \"box-2D\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"url\": \"\
https://neoclone.screenscraper.fr/api2/mediaJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=box-2D(br)\"\
,\n\t\t\t\t\"region\": \"br\",\n\t\t\t\t\"crc\": \"89a6a850\",\n\t\t\t\t\"\
md5\": \"f14783603804a0c9cf07705b18b8d966\",\n\t\t\t\t\"sha1\": \"bab2bc4d4ea9dfd08b871560f0e8eed2b8bfa6f4\"\
,\n\t\t\t\t\"size\": \"643493\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t\
{\"type\": \"box-2D\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"url\": \"\
https://neoclone.screenscraper.fr/api2/mediaJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=box-2D(eu)\"\
,\n\t\t\t\t\"region\": \"eu\",\n\t\t\t\t\"crc\": \"158da78d\",\n\t\t\t\t\"\
md5\": \"d931115cba2c348a712b9bb7e46c56b6\",\n\t\t\t\t\"sha1\": \"aa84b394eda103fcfd1a94163283a96b7f757059\"\
,\n\t\t\t\t\"size\": \"418696\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t\
{\"type\": \"box-2D\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"url\": \"\
https://neoclone.screenscraper.fr/api2/mediaJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=box-2D(jp)\"\
,\n\t\t\t\t\"region\": \"jp\",\n\t\t\t\t\"crc\": \"0d4110f9\",\n\t\t\t\t\"\
md5\": \"62754f8a982d598d4abdad30077950c8\",\n\t\t\t\t\"sha1\": \"a111bbcbad535fef762ad37417ec99a550404a1e\"\
,\n\t\t\t\t\"size\": \"449095\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t\
{\"type\": \"box-2D\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"url\": \"\
https://neoclone.screenscraper.fr/api2/mediaJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=box-2D(ss)\"\
,\n\t\t\t\t\"region\": \"ss\",\n\t\t\t\t\"crc\": \"b69bc1c7\",\n\t\t\t\t\"\
md5\": \"5bbd87d55da17687d6930e36460734a1\",\n\t\t\t\t\"sha1\": \"8dbc8a8f1cf28b2db8868b1357937b592961e173\"\
,\n\t\t\t\t\"size\": \"331213\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t\
{\"type\": \"box-2D\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"url\": \"\
https://neoclone.screenscraper.fr/api2/mediaJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=box-2D(us)\"\
,\n\t\t\t\t\"region\": \"us\",\n\t\t\t\t\"crc\": \"2cc57d37\",\n\t\t\t\t\"\
md5\": \"b892a42f67cfa56381e8cfc15470a7b0\",\n\t\t\t\t\"sha1\": \"d9a668452f3c6a1935de47602f27c731f8fd315f\"\
,\n\t\t\t\t\"size\": \"700746\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t\
{\"type\": \"box-2D-side\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"url\"\
: \"https://neoclone.screenscraper.fr/api2/mediaJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=box-2D-side(br)\"\
,\n\t\t\t\t\"region\": \"br\",\n\t\t\t\t\"crc\": \"4b64f14e\",\n\t\t\t\t\"\
md5\": \"db67f58bb07fe6ebe28f14ca070a4c5c\",\n\t\t\t\t\"sha1\": \"f42af90b90f4e419c20868fd76715ef026a04fab\"\
,\n\t\t\t\t\"size\": \"106734\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t\
{\"type\": \"box-2D-side\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"url\"\
: \"https://neoclone.screenscraper.fr/api2/mediaJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=box-2D-side(eu)\"\
,\n\t\t\t\t\"region\": \"eu\",\n\t\t\t\t\"crc\": \"d7bfcdc6\",\n\t\t\t\t\"\
md5\": \"71a52b35dd7f2981531893fb6431c754\",\n\t\t\t\t\"sha1\": \"f57c1d55ee569cf6e6ff1a4f2e1eb460a1c7e6ad\"\
,\n\t\t\t\t\"size\": \"59777\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t{\"\
type\": \"box-2D-side\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"url\": \"\
https://neoclone.screenscraper.fr/api2/mediaJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=box-2D-side(jp)\"\
,\n\t\t\t\t\"region\": \"jp\",\n\t\t\t\t\"crc\": \"43f0d646\",\n\t\t\t\t\"\
md5\": \"5559883c733232a9fa7d8ca7dc9a4c11\",\n\t\t\t\t\"sha1\": \"1cdc8ebf2b467281021e0c821fc0703a64965ef5\"\
,\n\t\t\t\t\"size\": \"74199\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t{\"\
type\": \"box-2D-side\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"url\": \"\
https://neoclone.screenscraper.fr/api2/mediaJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=box-2D-side(ss)\"\
,\n\t\t\t\t\"region\": \"ss\",\n\t\t\t\t\"crc\": \"705df4bf\",\n\t\t\t\t\"\
md5\": \"d184f24dd7ab94655daa100fa98e9009\",\n\t\t\t\t\"sha1\": \"84a4d630b3c17bee49522e5094b1d66128c92902\"\
,\n\t\t\t\t\"size\": \"48507\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t{\"\
type\": \"box-2D-side\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"url\": \"\
https://neoclone.screenscraper.fr/api2/mediaJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=box-2D-side(us)\"\
,\n\t\t\t\t\"region\": \"us\",\n\t\t\t\t\"crc\": \"d1add4d1\",\n\t\t\t\t\"\
md5\": \"d2dd81cc702ee80b4f8cd53f525757db\",\n\t\t\t\t\"sha1\": \"a17f6feb12a43021c3ed003793f4ba7d6504d28d\"\
,\n\t\t\t\t\"size\": \"68485\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t{\"\
type\": \"box-2D-back\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"url\": \"\
https://neoclone.screenscraper.fr/api2/mediaJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=box-2D-back(br)\"\
,\n\t\t\t\t\"region\": \"br\",\n\t\t\t\t\"crc\": \"140640e2\",\n\t\t\t\t\"\
md5\": \"8ddb244d4d89bac1262c42ee6fe0960c\",\n\t\t\t\t\"sha1\": \"59747f9e1b1ab407fc30a080fa83d6da83f99ebd\"\
,\n\t\t\t\t\"size\": \"629992\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t\
{\"type\": \"box-2D-back\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"url\"\
: \"https://neoclone.screenscraper.fr/api2/mediaJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=box-2D-back(eu)\"\
,\n\t\t\t\t\"region\": \"eu\",\n\t\t\t\t\"crc\": \"f0357f29\",\n\t\t\t\t\"\
md5\": \"4802432668e00760ff160d1c46a82b60\",\n\t\t\t\t\"sha1\": \"fb1ae4a69a82731500b35b3a958b593eb91eb93f\"\
,\n\t\t\t\t\"size\": \"420565\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t\
{\"type\": \"box-2D-back\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"url\"\
: \"https://neoclone.screenscraper.fr/api2/mediaJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=box-2D-back(jp)\"\
,\n\t\t\t\t\"region\": \"jp\",\n\t\t\t\t\"crc\": \"4e921238\",\n\t\t\t\t\"\
md5\": \"91ab99cfbb4fb05fe78df9c5d73b87b7\",\n\t\t\t\t\"sha1\": \"8f4883baf133f00d20d8202a920a4eaa8e51fd7e\"\
,\n\t\t\t\t\"size\": \"374091\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t\
{\"type\": \"box-2D-back\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"url\"\
: \"https://neoclone.screenscraper.fr/api2/mediaJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=box-2D-back(ss)\"\
,\n\t\t\t\t\"region\": \"ss\",\n\t\t\t\t\"crc\": \"64feef27\",\n\t\t\t\t\"\
md5\": \"9dd426a788f74754fd4c2fd387d9a6f9\",\n\t\t\t\t\"sha1\": \"7a5d36a0da252acbf94b0400485d4d5e30b2b988\"\
,\n\t\t\t\t\"size\": \"245238\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t\
{\"type\": \"box-2D-back\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"url\"\
: \"https://neoclone.screenscraper.fr/api2/mediaJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=box-2D-back(us)\"\
,\n\t\t\t\t\"region\": \"us\",\n\t\t\t\t\"crc\": \"eb8ed430\",\n\t\t\t\t\"\
md5\": \"2076d4b97e31a9db52abed469be3c56a\",\n\t\t\t\t\"sha1\": \"a80f8b7ff5e7993cb07c2987de49bb2f43cbaa75\"\
,\n\t\t\t\t\"size\": \"492930\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t\
{\"type\": \"box-texture\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"url\"\
: \"https://neoclone.screenscraper.fr/api2/mediaJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=box-texture(br)\"\
,\n\t\t\t\t\"region\": \"br\",\n\t\t\t\t\"crc\": \"9e9a3fbb\",\n\t\t\t\t\"\
md5\": \"7c5b2a9a2904d68a3bd58351e31ceefa\",\n\t\t\t\t\"sha1\": \"32c2181b3f0e3cb02bf0f1f55547fa9f867f2e26\"\
,\n\t\t\t\t\"size\": \"1421194\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t\
{\"type\": \"box-texture\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"url\"\
: \"https://neoclone.screenscraper.fr/api2/mediaJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=box-texture(eu)\"\
,\n\t\t\t\t\"region\": \"eu\",\n\t\t\t\t\"crc\": \"ee52c689\",\n\t\t\t\t\"\
md5\": \"0bc905681226cc33e2c0d89b6a022268\",\n\t\t\t\t\"sha1\": \"65318ead086188388f4735729ed82c9ecac89f73\"\
,\n\t\t\t\t\"size\": \"941994\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t\
{\"type\": \"box-texture\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"url\"\
: \"https://neoclone.screenscraper.fr/api2/mediaJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=box-texture(jp)\"\
,\n\t\t\t\t\"region\": \"jp\",\n\t\t\t\t\"crc\": \"852340d8\",\n\t\t\t\t\"\
md5\": \"de667b0a13211a2640663239807384ee\",\n\t\t\t\t\"sha1\": \"b014eb5deb752f3332d3926f3f6efc27b41455ee\"\
,\n\t\t\t\t\"size\": \"955924\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t\
{\"type\": \"box-texture\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"url\"\
: \"https://neoclone.screenscraper.fr/api2/mediaJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=box-texture(ss)\"\
,\n\t\t\t\t\"region\": \"ss\",\n\t\t\t\t\"crc\": \"93e8091e\",\n\t\t\t\t\"\
md5\": \"ecb19b04096b70b12ff2212c4eb9fb70\",\n\t\t\t\t\"sha1\": \"9aa91cd5e8aaa8d1ed56e03a0bf91c938de41b22\"\
,\n\t\t\t\t\"size\": \"636144\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t\
{\"type\": \"box-texture\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"url\"\
: \"https://neoclone.screenscraper.fr/api2/mediaJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=box-texture(us)\"\
,\n\t\t\t\t\"region\": \"us\",\n\t\t\t\t\"crc\": \"34ede8d1\",\n\t\t\t\t\"\
md5\": \"eccf0e0c6eaad09caf48accba4a49640\",\n\t\t\t\t\"sha1\": \"287b08f503e56f7cf943c0cd250eb7d50857289b\"\
,\n\t\t\t\t\"size\": \"1306276\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t\
{\"type\": \"box-3D\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"url\": \"\
https://neoclone.screenscraper.fr/api2/mediaJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=box-3D(br)\"\
,\n\t\t\t\t\"region\": \"br\",\n\t\t\t\t\"crc\": \"bbeaf7f5\",\n\t\t\t\t\"\
md5\": \"116d15ba879f4e8525cb48976a4db36c\",\n\t\t\t\t\"sha1\": \"b3b3c808cf90b317160e2e92b94b3af0073e6360\"\
,\n\t\t\t\t\"size\": \"316050\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t\
{\"type\": \"box-3D\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"url\": \"\
https://neoclone.screenscraper.fr/api2/mediaJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=box-3D(eu)\"\
,\n\t\t\t\t\"region\": \"eu\",\n\t\t\t\t\"crc\": \"85fc0f3b\",\n\t\t\t\t\"\
md5\": \"49ccd261568517e8edff7c8709ea6113\",\n\t\t\t\t\"sha1\": \"ffabb3332ada283e38b80e37d49e813b3da75fb5\"\
,\n\t\t\t\t\"size\": \"294215\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t\
{\"type\": \"box-3D\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"url\": \"\
https://neoclone.screenscraper.fr/api2/mediaJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=box-3D(jp)\"\
,\n\t\t\t\t\"region\": \"jp\",\n\t\t\t\t\"crc\": \"83f18955\",\n\t\t\t\t\"\
md5\": \"800ed63a7da2cbdea144ce96cfb678a0\",\n\t\t\t\t\"sha1\": \"e8c0e92060f77d985e980fe379c4a93d0b7cf301\"\
,\n\t\t\t\t\"size\": \"302227\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t\
{\"type\": \"box-3D\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"url\": \"\
https://neoclone.screenscraper.fr/api2/mediaJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=box-3D(ss)\"\
,\n\t\t\t\t\"region\": \"ss\",\n\t\t\t\t\"crc\": \"63776bb6\",\n\t\t\t\t\"\
md5\": \"8fa15f192355f39c31c01d574339fbaf\",\n\t\t\t\t\"sha1\": \"22aeaaff03fc6b44829125e3ded4ad3ef04a047d\"\
,\n\t\t\t\t\"size\": \"277513\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t\
{\"type\": \"box-3D\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"url\": \"\
https://neoclone.screenscraper.fr/api2/mediaJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=box-3D(us)\"\
,\n\t\t\t\t\"region\": \"us\",\n\t\t\t\t\"crc\": \"ba99757a\",\n\t\t\t\t\"\
md5\": \"5e12aaf66a7250544611d556d0daced9\",\n\t\t\t\t\"sha1\": \"2dd36014a54e31da2c586318d481f3cfd9406f84\"\
,\n\t\t\t\t\"size\": \"334608\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t\
{\"type\": \"support-texture\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"\
url\": \"https://neoclone.screenscraper.fr/api2/mediaJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=support-texture(eu)\"\
,\n\t\t\t\t\"region\": \"eu\",\n\t\t\t\t\"crc\": \"6d8c4829\",\n\t\t\t\t\"\
md5\": \"4b7e94dc7f00c65717744122b47f5661\",\n\t\t\t\t\"sha1\": \"ae358928ab5e99c31475ff72323ea69875fa7749\"\
,\n\t\t\t\t\"size\": \"490753\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t\
{\"type\": \"support-texture\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"\
url\": \"https://neoclone.screenscraper.fr/api2/mediaJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=support-texture(jp)\"\
,\n\t\t\t\t\"region\": \"jp\",\n\t\t\t\t\"crc\": \"9c743227\",\n\t\t\t\t\"\
md5\": \"31ac29bf015704cf19e77ce1426f795e\",\n\t\t\t\t\"sha1\": \"f8cb1b37b1e468c2c1ed761d8764d28b31ddf20f\"\
,\n\t\t\t\t\"size\": \"159135\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t\
{\"type\": \"support-texture\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"\
url\": \"https://neoclone.screenscraper.fr/api2/mediaJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=support-texture(us)\"\
,\n\t\t\t\t\"region\": \"us\",\n\t\t\t\t\"crc\": \"1d6627c3\",\n\t\t\t\t\"\
md5\": \"825284e75a611da4d2e40bc89619ef75\",\n\t\t\t\t\"sha1\": \"fbfbce3c03a3ffcc6951bde45ac25ee6c3d02ecf\"\
,\n\t\t\t\t\"size\": \"146646\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t\
{\"type\": \"support-2D\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"url\"\
: \"https://neoclone.screenscraper.fr/api2/mediaJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=support-2D(eu)\"\
,\n\t\t\t\t\"region\": \"eu\",\n\t\t\t\t\"crc\": \"dc0d420e\",\n\t\t\t\t\"\
md5\": \"adac27d77f965390d57632c7fa1bbb64\",\n\t\t\t\t\"sha1\": \"6dbe02886a60576984eef2b4f4f1bd0a22c4b057\"\
,\n\t\t\t\t\"size\": \"395673\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t\
{\"type\": \"support-2D\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"url\"\
: \"https://neoclone.screenscraper.fr/api2/mediaJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=support-2D(jp)\"\
,\n\t\t\t\t\"region\": \"jp\",\n\t\t\t\t\"crc\": \"fd71b060\",\n\t\t\t\t\"\
md5\": \"4e5e6ff89c165acee93f6b75666b4a8c\",\n\t\t\t\t\"sha1\": \"ff72ce676aa4b676422c0c1fd1e272f2cbb1265f\"\
,\n\t\t\t\t\"size\": \"347279\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t\
{\"type\": \"support-2D\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"url\"\
: \"https://neoclone.screenscraper.fr/api2/mediaJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=support-2D(us)\"\
,\n\t\t\t\t\"region\": \"us\",\n\t\t\t\t\"crc\": \"a26a1d84\",\n\t\t\t\t\"\
md5\": \"62c9dffb72ec017fcd4e776d13c26acd\",\n\t\t\t\t\"sha1\": \"a5429bb3850d1276b6b264e60716d8b47f371157\"\
,\n\t\t\t\t\"size\": \"290295\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t\
{\"type\": \"bezel-16-9\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"posx\"\
: \"246\",\n\t\t\t\t\"posy\": \"2\",\n\t\t\t\t\"posw\": \"1431\",\n\t\t\t\t\
\"posh\": \"1075\",\n\t\t\t\t\"url\": \"https://neoclone.screenscraper.fr/api2/mediaJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=bezel-16-9(wor)\"\
,\n\t\t\t\t\"region\": \"wor\",\n\t\t\t\t\"crc\": \"d6baf7e9\",\n\t\t\t\t\"\
md5\": \"c769736a347a58e57d8903faf35185ea\",\n\t\t\t\t\"sha1\": \"20c8f3d56601d85c2d1d6b46bde677642d81c691\"\
,\n\t\t\t\t\"size\": \"190243\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t\
{\"type\": \"mixrbv1\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"url\": \"\
https://neoclone.screenscraper.fr/api2/mediaJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=mixrbv1(br)\"\
,\n\t\t\t\t\"region\": \"br\",\n\t\t\t\t\"crc\": \"a91f5fa2\",\n\t\t\t\t\"\
md5\": \"290cba7a3290bd9265b3e40a40891640\",\n\t\t\t\t\"sha1\": \"aaedeff48edb003e35a5d5febe2fe957e4d12e0f\"\
,\n\t\t\t\t\"size\": \"712267\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t\
{\"type\": \"mixrbv1\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"url\": \"\
https://neoclone.screenscraper.fr/api2/mediaJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=mixrbv1(eu)\"\
,\n\t\t\t\t\"region\": \"eu\",\n\t\t\t\t\"crc\": \"0c4d8b11\",\n\t\t\t\t\"\
md5\": \"df1c6a15c4372f5c7c0d9a0707ae4603\",\n\t\t\t\t\"sha1\": \"603891d8789d59376f21f4aef8be5ba1cabd5410\"\
,\n\t\t\t\t\"size\": \"706827\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t\
{\"type\": \"mixrbv1\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"url\": \"\
https://neoclone.screenscraper.fr/api2/mediaJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=mixrbv1(jp)\"\
,\n\t\t\t\t\"region\": \"jp\",\n\t\t\t\t\"crc\": \"834b485c\",\n\t\t\t\t\"\
md5\": \"53b34100f671ea3af207fd604fb1eaf8\",\n\t\t\t\t\"sha1\": \"1fed4664da917bea20ce60b130d114523a4d1d67\"\
,\n\t\t\t\t\"size\": \"654423\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t\
{\"type\": \"mixrbv1\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"url\": \"\
https://neoclone.screenscraper.fr/api2/mediaJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=mixrbv1(ss)\"\
,\n\t\t\t\t\"region\": \"ss\",\n\t\t\t\t\"crc\": \"d855f49d\",\n\t\t\t\t\"\
md5\": \"cd2507a75ddc9cbd9a0d42d479c6e798\",\n\t\t\t\t\"sha1\": \"f91704f1e12b8b7e3c6f6a68277c0a88dc60d757\"\
,\n\t\t\t\t\"size\": \"705104\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t\
{\"type\": \"mixrbv1\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"url\": \"\
https://neoclone.screenscraper.fr/api2/mediaJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=mixrbv1(us)\"\
,\n\t\t\t\t\"region\": \"us\",\n\t\t\t\t\"crc\": \"2bac7aa1\",\n\t\t\t\t\"\
md5\": \"a4df1777843dcfeb63ff69d58e269a0d\",\n\t\t\t\t\"sha1\": \"aaa0540f01e87aa2f2e8487fc2624a85b5fc1266\"\
,\n\t\t\t\t\"size\": \"726053\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t\
{\"type\": \"mixrbv1\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"url\": \"\
https://neoclone.screenscraper.fr/api2/mediaJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=mixrbv1(wor)\"\
,\n\t\t\t\t\"region\": \"wor\",\n\t\t\t\t\"crc\": \"0c4d8b11\",\n\t\t\t\t\"\
md5\": \"df1c6a15c4372f5c7c0d9a0707ae4603\",\n\t\t\t\t\"sha1\": \"603891d8789d59376f21f4aef8be5ba1cabd5410\"\
,\n\t\t\t\t\"size\": \"706827\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t\
{\"type\": \"mixrbv2\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"url\": \"\
https://neoclone.screenscraper.fr/api2/mediaJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=mixrbv2(br)\"\
,\n\t\t\t\t\"region\": \"br\",\n\t\t\t\t\"crc\": \"d4dd1722\",\n\t\t\t\t\"\
md5\": \"bad2224fda71942eb6c12a9c7c54e9d7\",\n\t\t\t\t\"sha1\": \"eda8a19573c66b550fa02b8bdc81d51c6d196163\"\
,\n\t\t\t\t\"size\": \"950327\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t\
{\"type\": \"mixrbv2\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"url\": \"\
https://neoclone.screenscraper.fr/api2/mediaJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=mixrbv2(eu)\"\
,\n\t\t\t\t\"region\": \"eu\",\n\t\t\t\t\"crc\": \"96bf48ce\",\n\t\t\t\t\"\
md5\": \"dd4f0b44587ace5799f05dfcc22ea496\",\n\t\t\t\t\"sha1\": \"20577ede69a3e6207afcdfc07af92e9f05b731d7\"\
,\n\t\t\t\t\"size\": \"945860\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t\
{\"type\": \"mixrbv2\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"url\": \"\
https://neoclone.screenscraper.fr/api2/mediaJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=mixrbv2(jp)\"\
,\n\t\t\t\t\"region\": \"jp\",\n\t\t\t\t\"crc\": \"bfe8fe40\",\n\t\t\t\t\"\
md5\": \"70a1627918029c1dd600876f0cbea898\",\n\t\t\t\t\"sha1\": \"11305b8394292a9cfbb170ef83c3937282282f8f\"\
,\n\t\t\t\t\"size\": \"903353\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t\
{\"type\": \"mixrbv2\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"url\": \"\
https://neoclone.screenscraper.fr/api2/mediaJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=mixrbv2(ss)\"\
,\n\t\t\t\t\"region\": \"ss\",\n\t\t\t\t\"crc\": \"7698c4e4\",\n\t\t\t\t\"\
md5\": \"140f1674da33eab21fdf958c36b3d889\",\n\t\t\t\t\"sha1\": \"73c7e69afb0abf49ee4c4257b33ac0a82625a7ff\"\
,\n\t\t\t\t\"size\": \"944619\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t\
{\"type\": \"mixrbv2\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"url\": \"\
https://neoclone.screenscraper.fr/api2/mediaJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=mixrbv2(us)\"\
,\n\t\t\t\t\"region\": \"us\",\n\t\t\t\t\"crc\": \"b85437a2\",\n\t\t\t\t\"\
md5\": \"50044e63bf44c36665bd409420d2920c\",\n\t\t\t\t\"sha1\": \"a2b1a5c2c2f73faf89e511e80b37718198263da6\"\
,\n\t\t\t\t\"size\": \"950374\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t\
{\"type\": \"mixrbv2\",\n\t\t\t\t\"parent\": \"jeu\",\n\t\t\t\t\"url\": \"\
https://neoclone.screenscraper.fr/api2/mediaJeu.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&systemeid=1&jeuid=1&media=mixrbv2(wor)\"\
,\n\t\t\t\t\"region\": \"wor\",\n\t\t\t\t\"crc\": \"96bf48ce\",\n\t\t\t\t\"\
md5\": \"dd4f0b44587ace5799f05dfcc22ea496\",\n\t\t\t\t\"sha1\": \"20577ede69a3e6207afcdfc07af92e9f05b731d7\"\
,\n\t\t\t\t\"size\": \"945860\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t\
{\"type\": \"pictoliste\",\n\t\t\t\t\"parent\": \"region\",\n\t\t\t\t\"url\"\
: \"https://neoclone.screenscraper.fr/api2/mediaGroup.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&groupid=57&media=pictoliste\"\
,\n\t\t\t\t\"crc\": \"3e16ef2f\",\n\t\t\t\t\"md5\": \"477d7229d6acc6f9fe10410d8523f91a\"\
,\n\t\t\t\t\"sha1\": \"99cb5eab43e90a3dd73b1034b140a8ead05c224f\",\n\t\t\t\
\t\"format\": \"png\"},\n\t\t\t\t{\"type\": \"pictomonochrome\",\n\t\t\t\t\
\"parent\": \"editeur\",\n\t\t\t\t\"url\": \"https://neoclone.screenscraper.fr/api2/mediaCompagnie.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&groupid=1&media=logo-monochrome\"\
,\n\t\t\t\t\"crc\": \"57fa0967\",\n\t\t\t\t\"md5\": \"b9431112b9c59d7f5231a25cba667d7f\"\
,\n\t\t\t\t\"sha1\": \"34a44f3308ce5e55254bd3ae695736ddefd1afd9\",\n\t\t\t\
\t\"size\": \"29109\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t{\"type\":\
\ \"pictocouleur\",\n\t\t\t\t\"parent\": \"editeur\",\n\t\t\t\t\"url\": \"\
https://neoclone.screenscraper.fr/api2/mediaCompagnie.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&groupid=1&media=wheel\"\
,\n\t\t\t\t\"crc\": \"113ab6b7\",\n\t\t\t\t\"md5\": \"ba1042f2bcde5fd495d0f9b2616a7907\"\
,\n\t\t\t\t\"sha1\": \"84e503c6943178ed1b9134e4e42d3de0d62cb202\",\n\t\t\t\
\t\"size\": \"11946\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t{\"type\":\
\ \"pictomonochrome\",\n\t\t\t\t\"parent\": \"developpeur\",\n\t\t\t\t\"url\"\
: \"https://neoclone.screenscraper.fr/api2/mediaCompagnie.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&groupid=2&media=logo-monochrome\"\
,\n\t\t\t\t\"crc\": \"420daf73\",\n\t\t\t\t\"md5\": \"70dfa243c209ca136ebb063593c2998c\"\
,\n\t\t\t\t\"sha1\": \"e32ad05386671f826f1ed828ac9bfccec2cfe9c7\",\n\t\t\t\
\t\"size\": \"9943\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t{\"type\": \"\
pictocouleur\",\n\t\t\t\t\"parent\": \"developpeur\",\n\t\t\t\t\"url\": \"\
https://neoclone.screenscraper.fr/api2/mediaCompagnie.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&groupid=2&media=wheel\"\
,\n\t\t\t\t\"crc\": \"85c43b5b\",\n\t\t\t\t\"md5\": \"4530c00e3338dd6120ad835ee53e5427\"\
,\n\t\t\t\t\"sha1\": \"f66e49db1229db033275a0b15f0f8980982a7789\",\n\t\t\t\
\t\"size\": \"35241\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t{\"type\":\
\ \"pictoliste\",\n\t\t\t\t\"parent\": \"joueurs\",\n\t\t\t\t\"url\": \"https://neoclone.screenscraper.fr/api2/mediaGroup.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&groupid=3304&media=pictoliste\"\
,\n\t\t\t\t\"crc\": \"0abdc9bf\",\n\t\t\t\t\"md5\": \"05057bd8fcfe368b23628ba314e94df0\"\
,\n\t\t\t\t\"sha1\": \"6d9b48053c720498ece35d0029621b7d25010d3b\",\n\t\t\t\
\t\"size\": \"449\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t{\"type\": \"\
pictoliste\",\n\t\t\t\t\"parent\": \"note\",\n\t\t\t\t\"url\": \"https://neoclone.screenscraper.fr/api2/mediaGroup.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&groupid=3553&media=pictoliste\"\
,\n\t\t\t\t\"crc\": \"612df705\",\n\t\t\t\t\"md5\": \"6ee8a25c785597ef3035b7139189482a\"\
,\n\t\t\t\t\"sha1\": \"570ad57ff7c5643595c1a2148bacc373f326cffe\",\n\t\t\t\
\t\"size\": \"840\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t{\"type\": \"\
pictoliste\",\n\t\t\t\t\"parent\": \"classification\",\n\t\t\t\t\"subparent\"\
: \"VRC\",\n\t\t\t\t\"url\": \"https://neoclone.screenscraper.fr/api2/mediaGroup.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&groupid=3733&media=pictoliste\"\
,\n\t\t\t\t\"crc\": \"e34ba136\",\n\t\t\t\t\"md5\": \"b481137eba34134a8178d68c6414cb32\"\
,\n\t\t\t\t\"sha1\": \"fa812a3c467414111be23bcd692aa8548bf5b461\",\n\t\t\t\
\t\"size\": \"762\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t{\"id\": \"1\"\
,\n\t\t\t\t\"parent\": \"genre\",\n\t\t\t\t\"subparent\": \"Beat'em All\"\
,\n\t\t\t\t\"type\": \"pictoliste\",\n\t\t\t\t\"url\": \"https://neoclone.screenscraper.fr/api2/mediaGroup.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&groupid=1&media=pictoliste\"\
,\n\t\t\t\t\"crc\": \"151e4161\",\n\t\t\t\t\"md5\": \"00cb0cb4842e9aba77214ca54b713d0b\"\
,\n\t\t\t\t\"sha1\": \"95b8ef111b7e5be0c1750123da620e76523dec62\",\n\t\t\t\
\t\"size\": \"1126\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t{\"id\": \"\
1\",\n\t\t\t\t\"parent\": \"genre\",\n\t\t\t\t\"subparent\": \"Beat'em All\"\
,\n\t\t\t\t\"type\": \"pictomonochrome\",\n\t\t\t\t\"url\": \"https://neoclone.screenscraper.fr/api2/mediaGroup.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&groupid=1&media=logo-monochrome\"\
,\n\t\t\t\t\"crc\": \"3974c2c4\",\n\t\t\t\t\"md5\": \"19fa1571415cc4d60d321d1c42dd45b5\"\
,\n\t\t\t\t\"sha1\": \"27a372f24bbf11f0a7050bfea0bbcab612935244\",\n\t\t\t\
\t\"size\": \"9003\",\n\t\t\t\t\"format\": \"png\"},\n\t\t\t\t{\"id\": \"\
1\",\n\t\t\t\t\"parent\": \"genre\",\n\t\t\t\t\"subparent\": \"Beat'em All\"\
,\n\t\t\t\t\"type\": \"pictomonochrome\",\n\t\t\t\t\"url\": \"https://neoclone.screenscraper.fr/api2/mediaGroup.php?devid=zurdi15&devpassword=xTJwoOFjOQG&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&groupid=1&media=logo-monochrome-svg\"\
,\n\t\t\t\t\"crc\": \"ccb72604\",\n\t\t\t\t\"md5\": \"a3cce15fcad1ab4329a718a6b0d27d7e\"\
,\n\t\t\t\t\"sha1\": \"1de1e8426d307e4cc56b909ffe353813822278e3\",\n\t\t\t\
\t\"size\": \"2918\",\n\t\t\t\t\"format\": \"svg\"}\n\t\t\t],\n\t\t\t\"roms\"\
: [\n\t\t\t\t{\n\t\t\t\t\t\"id\": \"73\",\n\t\t\t\t\t\"romsize\": \"359440\"\
,\n\t\t\t\t\t\"romfilename\": \"Battletoads (World).zip\",\n\t\t\t\t\t\"romnumsupport\"\
: \"1\",\n\t\t\t\t\t\"romtotalsupport\": \"1\",\n\t\t\t\t\t\"romcloneof\"\
: \"0\",\n\t\t\t\t\t\"romcrc\": \"BE55BF2A\",\n\t\t\t\t\t\"rommd5\": \"CA32E3BFB4507F9713F37B5D345BAAB1\"\
,\n\t\t\t\t\t\"romsha1\": \"9D3CDCCBFE2659BFB609987707B2CC2CA4317BEB\",\n\t\
\t\t\t\t\"beta\": \"0\",\n\t\t\t\t\t\"demo\": \"0\",\n\t\t\t\t\t\"proto\"\
: \"0\",\n\t\t\t\t\t\"trad\": \"0\",\n\t\t\t\t\t\"hack\": \"0\",\n\t\t\t\t\
\t\"unl\": \"0\",\n\t\t\t\t\t\"alt\": \"0\",\n\t\t\t\t\t\"best\": \"0\",\n\
\t\t\t\t\t\"netplay\": \"0\",\n\t\t\t\t\t\"regions\": {\n\t\t\t\t\t\t\"regions_id\"\
: [\"57\"],\n\t\t\t\t\t\t\"regions_shortname\": [\"wor\"],\n\t\t\t\t\t\t\"\
regions_en\": [\"World\"],\n\t\t\t\t\t\t\"regions_fr\": [\"Monde\"],\n\t\t\
\t\t\t\t\"regions_de\": [\"World\"],\n\t\t\t\t\t\t\"regions_es\": [\"Mundo\"\
],\n\t\t\t\t\t\t\"regions_pt\": [\"Mundo\"]\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\
\t\t{\n\t\t\t\t\t\"id\": \"85954\",\n\t\t\t\t\t\"romsize\": \"329618\",\n\t\
\t\t\t\t\"romfilename\": \"Battletoads (World).7z\",\n\t\t\t\t\t\"romnumsupport\"\
: \"1\",\n\t\t\t\t\t\"romtotalsupport\": \"1\",\n\t\t\t\t\t\"romcloneof\"\
: \"0\",\n\t\t\t\t\t\"romcrc\": \"74002736\",\n\t\t\t\t\t\"rommd5\": \"CBEFC8F7C7CA94942926CA75B7FFCDB5\"\
,\n\t\t\t\t\t\"romsha1\": \"\",\n\t\t\t\t\t\"beta\": \"0\",\n\t\t\t\t\t\"\
demo\": \"0\",\n\t\t\t\t\t\"proto\": \"0\",\n\t\t\t\t\t\"trad\": \"0\",\n\t\
\t\t\t\t\"hack\": \"0\",\n\t\t\t\t\t\"unl\": \"0\",\n\t\t\t\t\t\"alt\": \"\
0\",\n\t\t\t\t\t\"best\": \"0\",\n\t\t\t\t\t\"netplay\": \"0\",\n\t\t\t\t\t\
\"regions\": {\n\t\t\t\t\t\t\"regions_id\": [\"57\"],\n\t\t\t\t\t\t\"regions_shortname\"\
: [\"wor\"],\n\t\t\t\t\t\t\"regions_en\": [\"World\"],\n\t\t\t\t\t\t\"regions_fr\"\
: [\"Monde\"],\n\t\t\t\t\t\t\"regions_de\": [\"World\"],\n\t\t\t\t\t\t\"regions_es\"\
: [\"Mundo\"],\n\t\t\t\t\t\t\"regions_pt\": [\"Mundo\"]\n\t\t\t\t\t}\n\t\t\
\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"id\": \"118779\",\n\t\t\t\t\t\"romsize\": \"\
359418\",\n\t\t\t\t\t\"romfilename\": \"Battletoads (World).zip\",\n\t\t\t\
\t\t\"romnumsupport\": \"1\",\n\t\t\t\t\t\"romtotalsupport\": \"1\",\n\t\t\
\t\t\t\"romcloneof\": \"0\",\n\t\t\t\t\t\"romcrc\": \"749B17FC\",\n\t\t\t\t\
\t\"rommd5\": \"177F750741F8B44F13A2A7435F0DB6EE\",\n\t\t\t\t\t\"romsha1\"\
: \"\",\n\t\t\t\t\t\"beta\": \"0\",\n\t\t\t\t\t\"demo\": \"0\",\n\t\t\t\t\t\
\"proto\": \"0\",\n\t\t\t\t\t\"trad\": \"0\",\n\t\t\t\t\t\"hack\": \"0\",\n\
\t\t\t\t\t\"unl\": \"0\",\n\t\t\t\t\t\"alt\": \"0\",\n\t\t\t\t\t\"best\":\
\ \"0\",\n\t\t\t\t\t\"netplay\": \"0\",\n\t\t\t\t\t\"regions\": {\n\t\t\t\t\
\t\t\"regions_id\": [\"57\"],\n\t\t\t\t\t\t\"regions_shortname\": [\"wor\"\
],\n\t\t\t\t\t\t\"regions_en\": [\"World\"],\n\t\t\t\t\t\t\"regions_fr\":\
\ [\"Monde\"],\n\t\t\t\t\t\t\"regions_de\": [\"World\"],\n\t\t\t\t\t\t\"regions_es\"\
: [\"Mundo\"],\n\t\t\t\t\t\t\"regions_pt\": [\"Mundo\"]\n\t\t\t\t\t}\n\t\t\
\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"id\": \"133799\",\n\t\t\t\t\t\"romsize\": \"\
524288\",\n\t\t\t\t\t\"romfilename\": \"Battletoads (World).md\",\n\t\t\t\t\
\t\"romnumsupport\": \"1\",\n\t\t\t\t\t\"romtotalsupport\": \"1\",\n\t\t\t\
\t\t\"romcloneof\": \"0\",\n\t\t\t\t\t\"romcrc\": \"D10E103A\",\n\t\t\t\t\t\
\"rommd5\": \"F1E299D6EB40E3ECEC6460D96E1E4DC9\",\n\t\t\t\t\t\"romsha1\":\
\ \"5EF3C29B6BDD04D24552AB200D0530F647AFDB08\",\n\t\t\t\t\t\"beta\": \"0\"\
,\n\t\t\t\t\t\"demo\": \"0\",\n\t\t\t\t\t\"proto\": \"0\",\n\t\t\t\t\t\"trad\"\
: \"0\",\n\t\t\t\t\t\"hack\": \"0\",\n\t\t\t\t\t\"unl\": \"0\",\n\t\t\t\t\t\
\"alt\": \"0\",\n\t\t\t\t\t\"best\": \"0\",\n\t\t\t\t\t\"netplay\": \"0\"\
,\n\t\t\t\t\t\"regions\": {\n\t\t\t\t\t\t\"regions_id\": [\"57\"],\n\t\t\t\
\t\t\t\"regions_shortname\": [\"wor\"],\n\t\t\t\t\t\t\"regions_en\": [\"World\"\
],\n\t\t\t\t\t\t\"regions_fr\": [\"Monde\"],\n\t\t\t\t\t\t\"regions_de\":\
\ [\"World\"],\n\t\t\t\t\t\t\"regions_es\": [\"Mundo\"],\n\t\t\t\t\t\t\"regions_pt\"\
: [\"Mundo\"]\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"id\": \"203570\"\
,\n\t\t\t\t\t\"romsize\": \"358534\",\n\t\t\t\t\t\"romfilename\": \"Battletoads\
\ (W) [!].gen\",\n\t\t\t\t\t\"romnumsupport\": \"1\",\n\t\t\t\t\t\"romtotalsupport\"\
: \"1\",\n\t\t\t\t\t\"romcloneof\": \"0\",\n\t\t\t\t\t\"romcrc\": \"83C7944F\"\
,\n\t\t\t\t\t\"rommd5\": \"D7AFC4D4D8D9B90F6F268239C742924C\",\n\t\t\t\t\t\
\"romsha1\": \"\",\n\t\t\t\t\t\"beta\": \"0\",\n\t\t\t\t\t\"demo\": \"0\"\
,\n\t\t\t\t\t\"proto\": \"0\",\n\t\t\t\t\t\"trad\": \"0\",\n\t\t\t\t\t\"hack\"\
: \"0\",\n\t\t\t\t\t\"unl\": \"0\",\n\t\t\t\t\t\"alt\": \"0\",\n\t\t\t\t\t\
\"best\": \"1\",\n\t\t\t\t\t\"netplay\": \"0\",\n\t\t\t\t\t\"regions\": {\n\
\t\t\t\t\t\t\"regions_id\": [\"57\"],\n\t\t\t\t\t\t\"regions_shortname\":\
\ [\"wor\"],\n\t\t\t\t\t\t\"regions_en\": [\"World\"],\n\t\t\t\t\t\t\"regions_fr\"\
: [\"Monde\"],\n\t\t\t\t\t\t\"regions_de\": [\"World\"],\n\t\t\t\t\t\t\"regions_es\"\
: [\"Mundo\"],\n\t\t\t\t\t\t\"regions_pt\": [\"Mundo\"]\n\t\t\t\t\t}\n\t\t\
\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"id\": \"203571\",\n\t\t\t\t\t\"romsize\": \"\
358549\",\n\t\t\t\t\t\"romfilename\": \"battletoads (w) [b1].zip\",\n\t\t\t\
\t\t\"romnumsupport\": \"1\",\n\t\t\t\t\t\"romtotalsupport\": \"1\",\n\t\t\
\t\t\t\"romcloneof\": \"0\",\n\t\t\t\t\t\"romcrc\": \"CEB6B839\",\n\t\t\t\t\
\t\"rommd5\": \"D709384172D28F8563E79278BF2B7613\",\n\t\t\t\t\t\"romsha1\"\
: \"\",\n\t\t\t\t\t\"beta\": \"1\",\n\t\t\t\t\t\"demo\": \"0\",\n\t\t\t\t\t\
\"proto\": \"0\",\n\t\t\t\t\t\"trad\": \"0\",\n\t\t\t\t\t\"hack\": \"0\",\n\
\t\t\t\t\t\"unl\": \"0\",\n\t\t\t\t\t\"alt\": \"0\",\n\t\t\t\t\t\"best\":\
\ \"0\",\n\t\t\t\t\t\"netplay\": \"0\",\n\t\t\t\t\t\"regions\": {\n\t\t\t\t\
\t\t\"regions_id\": [\"57\"],\n\t\t\t\t\t\t\"regions_shortname\": [\"wor\"\
],\n\t\t\t\t\t\t\"regions_en\": [\"World\"],\n\t\t\t\t\t\t\"regions_fr\":\
\ [\"Monde\"],\n\t\t\t\t\t\t\"regions_de\": [\"World\"],\n\t\t\t\t\t\t\"regions_es\"\
: [\"Mundo\"],\n\t\t\t\t\t\t\"regions_pt\": [\"Mundo\"]\n\t\t\t\t\t}\n\t\t\
\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"id\": \"203572\",\n\t\t\t\t\t\"romsize\": \"\
358539\",\n\t\t\t\t\t\"romfilename\": \"Battletoads (W) [h1C].gen\",\n\t\t\
\t\t\t\"romnumsupport\": \"1\",\n\t\t\t\t\t\"romtotalsupport\": \"1\",\n\t\
\t\t\t\t\"romcloneof\": \"0\",\n\t\t\t\t\t\"romcrc\": \"C58567C4\",\n\t\t\t\
\t\t\"rommd5\": \"AE72B7B66A65F04312FA03D2111BB27F\",\n\t\t\t\t\t\"romsha1\"\
: \"\",\n\t\t\t\t\t\"beta\": \"0\",\n\t\t\t\t\t\"demo\": \"0\",\n\t\t\t\t\t\
\"proto\": \"0\",\n\t\t\t\t\t\"trad\": \"0\",\n\t\t\t\t\t\"hack\": \"1\",\n\
\t\t\t\t\t\"unl\": \"0\",\n\t\t\t\t\t\"alt\": \"0\",\n\t\t\t\t\t\"best\":\
\ \"0\",\n\t\t\t\t\t\"netplay\": \"0\",\n\t\t\t\t\t\"regions\": {\n\t\t\t\t\
\t\t\"regions_id\": [\"57\"],\n\t\t\t\t\t\t\"regions_shortname\": [\"wor\"\
],\n\t\t\t\t\t\t\"regions_en\": [\"World\"],\n\t\t\t\t\t\t\"regions_fr\":\
\ [\"Monde\"],\n\t\t\t\t\t\t\"regions_de\": [\"World\"],\n\t\t\t\t\t\t\"regions_es\"\
: [\"Mundo\"],\n\t\t\t\t\t\t\"regions_pt\": [\"Mundo\"]\n\t\t\t\t\t}\n\t\t\
\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"id\": \"203573\",\n\t\t\t\t\t\"romsize\": \"\
358552\",\n\t\t\t\t\t\"romfilename\": \"Battletoads (W) [p1].zip\",\n\t\t\t\
\t\t\"romnumsupport\": \"1\",\n\t\t\t\t\t\"romtotalsupport\": \"1\",\n\t\t\
\t\t\t\"romcloneof\": \"0\",\n\t\t\t\t\t\"romcrc\": \"E817C9A8\",\n\t\t\t\t\
\t\"rommd5\": \"5F303CA1FA3D7E10E35E6E9730B4E6BB\",\n\t\t\t\t\t\"romsha1\"\
: \"\",\n\t\t\t\t\t\"beta\": \"0\",\n\t\t\t\t\t\"demo\": \"0\",\n\t\t\t\t\t\
\"proto\": \"0\",\n\t\t\t\t\t\"trad\": \"0\",\n\t\t\t\t\t\"hack\": \"0\",\n\
\t\t\t\t\t\"unl\": \"0\",\n\t\t\t\t\t\"alt\": \"0\",\n\t\t\t\t\t\"best\":\
\ \"0\",\n\t\t\t\t\t\"netplay\": \"0\",\n\t\t\t\t\t\"regions\": {\n\t\t\t\t\
\t\t\"regions_id\": [\"57\"],\n\t\t\t\t\t\t\"regions_shortname\": [\"wor\"\
],\n\t\t\t\t\t\t\"regions_en\": [\"World\"],\n\t\t\t\t\t\t\"regions_fr\":\
\ [\"Monde\"],\n\t\t\t\t\t\t\"regions_de\": [\"World\"],\n\t\t\t\t\t\t\"regions_es\"\
: [\"Mundo\"],\n\t\t\t\t\t\t\"regions_pt\": [\"Mundo\"]\n\t\t\t\t\t}\n\t\t\
\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"id\": \"203574\",\n\t\t\t\t\t\"romsize\": \"\
358662\",\n\t\t\t\t\t\"romfilename\": \"Battletoads (W) [T Rus_NewGame].bin\"\
,\n\t\t\t\t\t\"romnumsupport\": \"1\",\n\t\t\t\t\t\"romtotalsupport\": \"\
1\",\n\t\t\t\t\t\"romcloneof\": \"0\",\n\t\t\t\t\t\"romcrc\": \"40E74041\"\
,\n\t\t\t\t\t\"rommd5\": \"A0F847B3507CB8919A58095EC3B16841\",\n\t\t\t\t\t\
\"romsha1\": \"\",\n\t\t\t\t\t\"beta\": \"0\",\n\t\t\t\t\t\"demo\": \"0\"\
,\n\t\t\t\t\t\"proto\": \"0\",\n\t\t\t\t\t\"trad\": \"1\",\n\t\t\t\t\t\"hack\"\
: \"0\",\n\t\t\t\t\t\"unl\": \"0\",\n\t\t\t\t\t\"alt\": \"0\",\n\t\t\t\t\t\
\"best\": \"0\",\n\t\t\t\t\t\"netplay\": \"0\",\n\t\t\t\t\t\"regions\": {\n\
\t\t\t\t\t\t\"regions_id\": [\"57\"],\n\t\t\t\t\t\t\"regions_shortname\":\
\ [\"wor\"],\n\t\t\t\t\t\t\"regions_en\": [\"World\"],\n\t\t\t\t\t\t\"regions_fr\"\
: [\"Monde\"],\n\t\t\t\t\t\t\"regions_de\": [\"World\"],\n\t\t\t\t\t\t\"regions_es\"\
: [\"Mundo\"],\n\t\t\t\t\t\t\"regions_pt\": [\"Mundo\"]\n\t\t\t\t\t}\n\t\t\
\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"id\": \"203589\",\n\t\t\t\t\t\"romsize\": \"\
9215010\",\n\t\t\t\t\t\"romfilename\": \"Battletoads RAMM (Hack).bin\",\n\t\
\t\t\t\t\"romnumsupport\": \"1\",\n\t\t\t\t\t\"romtotalsupport\": \"1\",\n\
\t\t\t\t\t\"romcloneof\": \"0\",\n\t\t\t\t\t\"romcrc\": \"A81D68E4\",\n\t\t\
\t\t\t\"rommd5\": \"EE990D0B09E6AEB71CB8547EC81BFAE5\",\n\t\t\t\t\t\"romsha1\"\
: \"\",\n\t\t\t\t\t\"beta\": \"0\",\n\t\t\t\t\t\"demo\": \"0\",\n\t\t\t\t\t\
\"proto\": \"0\",\n\t\t\t\t\t\"trad\": \"0\",\n\t\t\t\t\t\"hack\": \"1\",\n\
\t\t\t\t\t\"unl\": \"0\",\n\t\t\t\t\t\"alt\": \"0\",\n\t\t\t\t\t\"best\":\
\ \"0\",\n\t\t\t\t\t\"netplay\": \"0\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"\
id\": \"203590\",\n\t\t\t\t\t\"romsize\": \"358616\",\n\t\t\t\t\t\"romfilename\"\
: \"Battletoads Remastered (Hack).bin\",\n\t\t\t\t\t\"romnumsupport\": \"\
1\",\n\t\t\t\t\t\"romtotalsupport\": \"1\",\n\t\t\t\t\t\"romcloneof\": \"\
0\",\n\t\t\t\t\t\"romcrc\": \"6AC6502D\",\n\t\t\t\t\t\"rommd5\": \"2727700BA908BAE3B66A16F1BD2CDBF8\"\
,\n\t\t\t\t\t\"romsha1\": \"\",\n\t\t\t\t\t\"beta\": \"0\",\n\t\t\t\t\t\"\
demo\": \"0\",\n\t\t\t\t\t\"proto\": \"0\",\n\t\t\t\t\t\"trad\": \"0\",\n\t\
\t\t\t\t\"hack\": \"1\",\n\t\t\t\t\t\"unl\": \"0\",\n\t\t\t\t\t\"alt\": \"\
0\",\n\t\t\t\t\t\"best\": \"0\",\n\t\t\t\t\t\"netplay\": \"0\"\n\t\t\t\t},\n\
\t\t\t\t{\n\t\t\t\t\t\"id\": \"543806\",\n\t\t\t\t\t\"romsize\": \"373731\"\
,\n\t\t\t\t\t\"romfilename\": \"Battletoads (World).zip\",\n\t\t\t\t\t\"romnumsupport\"\
: \"1\",\n\t\t\t\t\t\"romtotalsupport\": \"1\",\n\t\t\t\t\t\"romcloneof\"\
: \"0\",\n\t\t\t\t\t\"romcrc\": \"7409C241\",\n\t\t\t\t\t\"rommd5\": \"D71124F17920403DB40D99B6A04DDAB4\"\
,\n\t\t\t\t\t\"romsha1\": \"3D58BA8A6C1662F735B6A1FA36B022D31D9A2FEE\",\n\t\
\t\t\t\t\"beta\": \"0\",\n\t\t\t\t\t\"demo\": \"0\",\n\t\t\t\t\t\"proto\"\
: \"0\",\n\t\t\t\t\t\"trad\": \"0\",\n\t\t\t\t\t\"hack\": \"0\",\n\t\t\t\t\
\t\"unl\": \"0\",\n\t\t\t\t\t\"alt\": \"0\",\n\t\t\t\t\t\"best\": \"0\",\n\
\t\t\t\t\t\"netplay\": \"0\",\n\t\t\t\t\t\"regions\": {\n\t\t\t\t\t\t\"regions_id\"\
: [\"57\"],\n\t\t\t\t\t\t\"regions_shortname\": [\"wor\"],\n\t\t\t\t\t\t\"\
regions_en\": [\"World\"],\n\t\t\t\t\t\t\"regions_fr\": [\"Monde\"],\n\t\t\
\t\t\t\t\"regions_de\": [\"World\"],\n\t\t\t\t\t\t\"regions_es\": [\"Mundo\"\
],\n\t\t\t\t\t\t\"regions_pt\": [\"Mundo\"]\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\
\t\t{\n\t\t\t\t\t\"id\": \"546554\",\n\t\t\t\t\t\"romsize\": \"359873\",\n\
\t\t\t\t\t\"romfilename\": \"Battletoads (JUE) [!].zip\",\n\t\t\t\t\t\"romnumsupport\"\
: \"1\",\n\t\t\t\t\t\"romtotalsupport\": \"1\",\n\t\t\t\t\t\"romcloneof\"\
: \"0\",\n\t\t\t\t\t\"romcrc\": \"7A2EA990\",\n\t\t\t\t\t\"rommd5\": \"55B012D3ED8335A3466F144EBE1935E4\"\
,\n\t\t\t\t\t\"romsha1\": \"8046E44E1C486B6BD470194726D71A07184E6B7F\",\n\t\
\t\t\t\t\"beta\": \"0\",\n\t\t\t\t\t\"demo\": \"0\",\n\t\t\t\t\t\"proto\"\
: \"0\",\n\t\t\t\t\t\"trad\": \"0\",\n\t\t\t\t\t\"hack\": \"0\",\n\t\t\t\t\
\t\"unl\": \"0\",\n\t\t\t\t\t\"alt\": \"0\",\n\t\t\t\t\t\"best\": \"1\",\n\
\t\t\t\t\t\"netplay\": \"0\",\n\t\t\t\t\t\"regions\": {\n\t\t\t\t\t\t\"regions_id\"\
: [\"46\",\"48\"],\n\t\t\t\t\t\t\"regions_shortname\": [\"us\",\"eu\"],\n\t\
\t\t\t\t\t\"regions_en\": [\"USA\",\"Europe\"],\n\t\t\t\t\t\t\"regions_fr\"\
: [\"USA\",\"Europe\"],\n\t\t\t\t\t\t\"regions_de\": [\"USA\",\"Europa\"],\n\
\t\t\t\t\t\t\"regions_es\": [\"USA\",\"Europa\"],\n\t\t\t\t\t\t\"regions_it\"\
: [\"USA\"],\n\t\t\t\t\t\t\"regions_pt\": [\"EUA\",\"Europa\"]\n\t\t\t\t\t\
}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"id\": \"587987\",\n\t\t\t\t\t\"romsize\"\
: \"358664\",\n\t\t\t\t\t\"romfilename\": \"Battletoads (World).zip\",\n\t\
\t\t\t\t\"romnumsupport\": \"1\",\n\t\t\t\t\t\"romtotalsupport\": \"1\",\n\
\t\t\t\t\t\"romcloneof\": \"0\",\n\t\t\t\t\t\"romcrc\": \"4B87BB34\",\n\t\t\
\t\t\t\"rommd5\": \"629DAF16944422D58C34EB27EDE88D4F\",\n\t\t\t\t\t\"romsha1\"\
: \"BF02FAE9E5AE8CEC13EBACACFCD44BE99B88C49A\",\n\t\t\t\t\t\"beta\": \"0\"\
,\n\t\t\t\t\t\"demo\": \"0\",\n\t\t\t\t\t\"proto\": \"0\",\n\t\t\t\t\t\"trad\"\
: \"0\",\n\t\t\t\t\t\"hack\": \"0\",\n\t\t\t\t\t\"unl\": \"0\",\n\t\t\t\t\t\
\"alt\": \"0\",\n\t\t\t\t\t\"best\": \"0\",\n\t\t\t\t\t\"netplay\": \"0\"\
,\n\t\t\t\t\t\"regions\": {\n\t\t\t\t\t\t\"regions_id\": [\"57\"],\n\t\t\t\
\t\t\t\"regions_shortname\": [\"wor\"],\n\t\t\t\t\t\t\"regions_en\": [\"World\"\
],\n\t\t\t\t\t\t\"regions_fr\": [\"Monde\"],\n\t\t\t\t\t\t\"regions_de\":\
\ [\"World\"],\n\t\t\t\t\t\t\"regions_es\": [\"Mundo\"],\n\t\t\t\t\t\t\"regions_pt\"\
: [\"Mundo\"]\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"id\": \"613629\"\
,\n\t\t\t\t\t\"romsize\": \"358532\",\n\t\t\t\t\t\"romfilename\": \"Battletoads\
\ (World).zip\",\n\t\t\t\t\t\"romnumsupport\": \"1\",\n\t\t\t\t\t\"romtotalsupport\"\
: \"1\",\n\t\t\t\t\t\"romcloneof\": \"0\",\n\t\t\t\t\t\"romcrc\": \"3DBC3FFF\"\
,\n\t\t\t\t\t\"rommd5\": \"A81218CDBDB3B168CB88FC4FF593FB32\",\n\t\t\t\t\t\
\"romsha1\": \"B998131C1896904A4A4B16870112D4B60413197A\",\n\t\t\t\t\t\"beta\"\
: \"0\",\n\t\t\t\t\t\"demo\": \"0\",\n\t\t\t\t\t\"proto\": \"0\",\n\t\t\t\t\
\t\"trad\": \"0\",\n\t\t\t\t\t\"hack\": \"0\",\n\t\t\t\t\t\"unl\": \"0\",\n\
\t\t\t\t\t\"alt\": \"0\",\n\t\t\t\t\t\"best\": \"0\",\n\t\t\t\t\t\"netplay\"\
: \"0\",\n\t\t\t\t\t\"regions\": {\n\t\t\t\t\t\t\"regions_id\": [\"57\"],\n\
\t\t\t\t\t\t\"regions_shortname\": [\"wor\"],\n\t\t\t\t\t\t\"regions_en\"\
: [\"World\"],\n\t\t\t\t\t\t\"regions_fr\": [\"Monde\"],\n\t\t\t\t\t\t\"regions_de\"\
: [\"World\"],\n\t\t\t\t\t\t\"regions_es\": [\"Mundo\"],\n\t\t\t\t\t\t\"regions_pt\"\
: [\"Mundo\"]\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"id\": \"613772\"\
,\n\t\t\t\t\t\"romsize\": \"359882\",\n\t\t\t\t\t\"romfilename\": \"Battletoads.zip\"\
,\n\t\t\t\t\t\"romnumsupport\": \"1\",\n\t\t\t\t\t\"romtotalsupport\": \"\
1\",\n\t\t\t\t\t\"romcloneof\": \"0\",\n\t\t\t\t\t\"romcrc\": \"C5E95063\"\
,\n\t\t\t\t\t\"rommd5\": \"D33949CC4D01F181D498E602BD01B196\",\n\t\t\t\t\t\
\"romsha1\": \"882520B3B51D799FADE6C777C714205B8FA11871\",\n\t\t\t\t\t\"beta\"\
: \"0\",\n\t\t\t\t\t\"demo\": \"0\",\n\t\t\t\t\t\"proto\": \"0\",\n\t\t\t\t\
\t\"trad\": \"0\",\n\t\t\t\t\t\"hack\": \"0\",\n\t\t\t\t\t\"unl\": \"0\",\n\
\t\t\t\t\t\"alt\": \"0\",\n\t\t\t\t\t\"best\": \"0\",\n\t\t\t\t\t\"netplay\"\
: \"0\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"id\": \"616262\",\n\t\t\t\t\t\"\
romsize\": \"524288\",\n\t\t\t\t\t\"romfilename\": \"Battletoads [T Rus_NewGame].bin\"\
,\n\t\t\t\t\t\"romnumsupport\": \"1\",\n\t\t\t\t\t\"romtotalsupport\": \"\
1\",\n\t\t\t\t\t\"romcloneof\": \"0\",\n\t\t\t\t\t\"romcrc\": \"D2859FFF\"\
,\n\t\t\t\t\t\"rommd5\": \"204014B305BB5A0EA93EFB0FCA36FEA7\",\n\t\t\t\t\t\
\"romsha1\": \"EBB9B88F1E50FE99D85B1DB5044349599BA127C0\",\n\t\t\t\t\t\"beta\"\
: \"0\",\n\t\t\t\t\t\"demo\": \"0\",\n\t\t\t\t\t\"proto\": \"0\",\n\t\t\t\t\
\t\"trad\": \"0\",\n\t\t\t\t\t\"hack\": \"0\",\n\t\t\t\t\t\"unl\": \"0\",\n\
\t\t\t\t\t\"alt\": \"0\",\n\t\t\t\t\t\"best\": \"0\",\n\t\t\t\t\t\"netplay\"\
: \"0\",\n\t\t\t\t\t\"regions\": {\n\t\t\t\t\t\t\"regions_id\": [\"57\"],\n\
\t\t\t\t\t\t\"regions_shortname\": [\"wor\"],\n\t\t\t\t\t\t\"regions_en\"\
: [\"World\"],\n\t\t\t\t\t\t\"regions_fr\": [\"Monde\"],\n\t\t\t\t\t\t\"regions_de\"\
: [\"World\"],\n\t\t\t\t\t\t\"regions_es\": [\"Mundo\"],\n\t\t\t\t\t\t\"regions_pt\"\
: [\"Mundo\"]\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"id\": \"616263\"\
,\n\t\t\t\t\t\"romsize\": \"524288\",\n\t\t\t\t\t\"romfilename\": \"Battletoads\
\ (W) [T Rus Pirate][b1].gen\",\n\t\t\t\t\t\"romnumsupport\": \"1\",\n\t\t\
\t\t\t\"romtotalsupport\": \"1\",\n\t\t\t\t\t\"romcloneof\": \"0\",\n\t\t\t\
\t\t\"romcrc\": \"D67397A8\",\n\t\t\t\t\t\"rommd5\": \"3A670957B3C409761EF042078F118D88\"\
,\n\t\t\t\t\t\"romsha1\": \"1DDD7C7F5B170E0ABAED1F27B32DBCC099BDC726\",\n\t\
\t\t\t\t\"beta\": \"0\",\n\t\t\t\t\t\"demo\": \"0\",\n\t\t\t\t\t\"proto\"\
: \"0\",\n\t\t\t\t\t\"trad\": \"0\",\n\t\t\t\t\t\"hack\": \"0\",\n\t\t\t\t\
\t\"unl\": \"0\",\n\t\t\t\t\t\"alt\": \"0\",\n\t\t\t\t\t\"best\": \"0\",\n\
\t\t\t\t\t\"netplay\": \"0\",\n\t\t\t\t\t\"regions\": {\n\t\t\t\t\t\t\"regions_id\"\
: [\"57\"],\n\t\t\t\t\t\t\"regions_shortname\": [\"wor\"],\n\t\t\t\t\t\t\"\
regions_en\": [\"World\"],\n\t\t\t\t\t\t\"regions_fr\": [\"Monde\"],\n\t\t\
\t\t\t\t\"regions_de\": [\"World\"],\n\t\t\t\t\t\t\"regions_es\": [\"Mundo\"\
],\n\t\t\t\t\t\t\"regions_pt\": [\"Mundo\"]\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\
\t\t{\n\t\t\t\t\t\"id\": \"616264\",\n\t\t\t\t\t\"romsize\": \"524288\",\n\
\t\t\t\t\t\"romfilename\": \"Battletoads (W) [T Rus Pirate].zip\",\n\t\t\t\
\t\t\"romnumsupport\": \"1\",\n\t\t\t\t\t\"romtotalsupport\": \"1\",\n\t\t\
\t\t\t\"romcloneof\": \"0\",\n\t\t\t\t\t\"romcrc\": \"2DA7DBCD\",\n\t\t\t\t\
\t\"rommd5\": \"86945FE2547CA90D0F226BEA981BAACE\",\n\t\t\t\t\t\"romsha1\"\
: \"1A351E9CD7312B3B13D7DF82F2AFCFC9AEF33CD8\",\n\t\t\t\t\t\"beta\": \"0\"\
,\n\t\t\t\t\t\"demo\": \"0\",\n\t\t\t\t\t\"proto\": \"0\",\n\t\t\t\t\t\"trad\"\
: \"0\",\n\t\t\t\t\t\"hack\": \"0\",\n\t\t\t\t\t\"unl\": \"0\",\n\t\t\t\t\t\
\"alt\": \"0\",\n\t\t\t\t\t\"best\": \"0\",\n\t\t\t\t\t\"netplay\": \"0\"\
,\n\t\t\t\t\t\"regions\": {\n\t\t\t\t\t\t\"regions_id\": [\"57\"],\n\t\t\t\
\t\t\t\"regions_shortname\": [\"wor\"],\n\t\t\t\t\t\t\"regions_en\": [\"World\"\
],\n\t\t\t\t\t\t\"regions_fr\": [\"Monde\"],\n\t\t\t\t\t\t\"regions_de\":\
\ [\"World\"],\n\t\t\t\t\t\t\"regions_es\": [\"Mundo\"],\n\t\t\t\t\t\t\"regions_pt\"\
: [\"Mundo\"]\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"id\": \"617681\"\
,\n\t\t\t\t\t\"romsize\": \"358538\",\n\t\t\t\t\t\"romfilename\": \"Battletoads\
\ (World).zip\",\n\t\t\t\t\t\"romnumsupport\": \"1\",\n\t\t\t\t\t\"romtotalsupport\"\
: \"1\",\n\t\t\t\t\t\"romcloneof\": \"0\",\n\t\t\t\t\t\"romcrc\": \"3033A130\"\
,\n\t\t\t\t\t\"rommd5\": \"3AEB63777E8953E963A1AD58AC367CA5\",\n\t\t\t\t\t\
\"romsha1\": \"C498315A6B303A0D9001FF3AE19068EBC2B62AAD\",\n\t\t\t\t\t\"beta\"\
: \"0\",\n\t\t\t\t\t\"demo\": \"0\",\n\t\t\t\t\t\"proto\": \"0\",\n\t\t\t\t\
\t\"trad\": \"0\",\n\t\t\t\t\t\"hack\": \"0\",\n\t\t\t\t\t\"unl\": \"0\",\n\
\t\t\t\t\t\"alt\": \"0\",\n\t\t\t\t\t\"best\": \"0\",\n\t\t\t\t\t\"netplay\"\
: \"0\",\n\t\t\t\t\t\"regions\": {\n\t\t\t\t\t\t\"regions_id\": [\"57\"],\n\
\t\t\t\t\t\t\"regions_shortname\": [\"wor\"],\n\t\t\t\t\t\t\"regions_en\"\
: [\"World\"],\n\t\t\t\t\t\t\"regions_fr\": [\"Monde\"],\n\t\t\t\t\t\t\"regions_de\"\
: [\"World\"],\n\t\t\t\t\t\t\"regions_es\": [\"Mundo\"],\n\t\t\t\t\t\t\"regions_pt\"\
: [\"Mundo\"]\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"id\": \"619657\"\
,\n\t\t\t\t\t\"romsize\": \"524288\",\n\t\t\t\t\t\"romfilename\": \"battle_toads.bin\"\
,\n\t\t\t\t\t\"romnumsupport\": \"1\",\n\t\t\t\t\t\"romtotalsupport\": \"\
1\",\n\t\t\t\t\t\"romcloneof\": \"0\",\n\t\t\t\t\t\"romcrc\": \"6CDBFFCD\"\
,\n\t\t\t\t\t\"rommd5\": \"31E7DF020CCBC090404800FF44410FEE\",\n\t\t\t\t\t\
\"romsha1\": \"66C4B98103EA006957481892F22B4336A240D8B6\",\n\t\t\t\t\t\"beta\"\
: \"0\",\n\t\t\t\t\t\"demo\": \"0\",\n\t\t\t\t\t\"proto\": \"0\",\n\t\t\t\t\
\t\"trad\": \"0\",\n\t\t\t\t\t\"hack\": \"1\",\n\t\t\t\t\t\"unl\": \"0\",\n\
\t\t\t\t\t\"alt\": \"0\",\n\t\t\t\t\t\"best\": \"0\",\n\t\t\t\t\t\"netplay\"\
: \"0\",\n\t\t\t\t\t\"regions\": {\n\t\t\t\t\t\t\"regions_id\": [\"57\"],\n\
\t\t\t\t\t\t\"regions_shortname\": [\"wor\"],\n\t\t\t\t\t\t\"regions_en\"\
: [\"World\"],\n\t\t\t\t\t\t\"regions_fr\": [\"Monde\"],\n\t\t\t\t\t\t\"regions_de\"\
: [\"World\"],\n\t\t\t\t\t\t\"regions_es\": [\"Mundo\"],\n\t\t\t\t\t\t\"regions_pt\"\
: [\"Mundo\"]\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"id\": \"630883\"\
,\n\t\t\t\t\t\"romsize\": \"524288\",\n\t\t\t\t\t\"romfilename\": \"Battletoads\
\ (W) [b1].gen\",\n\t\t\t\t\t\"romnumsupport\": \"1\",\n\t\t\t\t\t\"romtotalsupport\"\
: \"1\",\n\t\t\t\t\t\"romcloneof\": \"0\",\n\t\t\t\t\t\"romcrc\": \"7B6199EB\"\
,\n\t\t\t\t\t\"rommd5\": \"2E603829DE7B38475F32370A72207FCA\",\n\t\t\t\t\t\
\"romsha1\": \"053AEC63CC7AE74130404CE2557C7EFB45150FE4\",\n\t\t\t\t\t\"beta\"\
: \"0\",\n\t\t\t\t\t\"demo\": \"0\",\n\t\t\t\t\t\"proto\": \"0\",\n\t\t\t\t\
\t\"trad\": \"0\",\n\t\t\t\t\t\"hack\": \"0\",\n\t\t\t\t\t\"unl\": \"0\",\n\
\t\t\t\t\t\"alt\": \"0\",\n\t\t\t\t\t\"best\": \"0\",\n\t\t\t\t\t\"netplay\"\
: \"0\",\n\t\t\t\t\t\"regions\": {\n\t\t\t\t\t\t\"regions_id\": [\"57\"],\n\
\t\t\t\t\t\t\"regions_shortname\": [\"wor\"],\n\t\t\t\t\t\t\"regions_en\"\
: [\"World\"],\n\t\t\t\t\t\t\"regions_fr\": [\"Monde\"],\n\t\t\t\t\t\t\"regions_de\"\
: [\"World\"],\n\t\t\t\t\t\t\"regions_es\": [\"Mundo\"],\n\t\t\t\t\t\t\"regions_pt\"\
: [\"Mundo\"]\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t}\n}\n "
headers:
Access-Control-Allow-Headers:
- X-Requested-With
Access-Control-Allow-Methods:
- GET
Access-Control-Allow-Origin:
- "*"
Cache-Control:
- no-cache, must-revalidate
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Type:
- application/json; charset=utf-8
Date:
- Wed, 23 Jul 2025 03:46:44 GMT
Expires:
- "0"
Pragma:
- no-cache
Server:
- nginx/1.14.2
Set-Cookie:
- SESSID=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Transfer-Encoding:
- chunked
Vary:
- Accept-Encoding
status:
code: 200
message: OK
version: 1

View File

@@ -0,0 +1,38 @@
interactions:
- request:
body: null
headers: {}
method: GET
uri: https://api.screenscraper.fr/api2/jeuInfos.php?crc=FFFFFFFFFFFFFFFF&systemeid=999999
response:
body:
string: "Champ crc, md5 ou sha1 erron\xE9 "
headers:
Access-Control-Allow-Headers:
- X-Requested-With
Access-Control-Allow-Methods:
- GET
Access-Control-Allow-Origin:
- "*"
Cache-Control:
- no-cache, must-revalidate
Connection:
- keep-alive
Content-Type:
- application/json; charset=utf-8
Date:
- Wed, 23 Jul 2025 03:47:10 GMT
Expires:
- "0"
Pragma:
- no-cache
Server:
- nginx/1.14.2
Set-Cookie:
- SESSID=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Transfer-Encoding:
- chunked
status:
code: 400
message: Bad Request
version: 1

View File

@@ -0,0 +1,62 @@
interactions:
- request:
body: null
headers: {}
method: GET
uri: https://api.screenscraper.fr/api2/jeuRecherche.php?recherche=ZZZNonexistentGameZZZ
response:
body:
string:
"{\n\t\"header\" : {\n\t\t\"APIversion\" : \"2.0\",\n\t\t\"dateTime\"\
\ : \"2025-07-23 05:47:11\",\n\t\t\"commandRequested\" : \"https://neoclone.screenscraper.fr/api2/jeuRecherche.php?recherche=ZZZNonexistentGameZZZ&devid=zurdi15&devpassword=xTJwoOFjOQG&output=json&softname=romm&ssid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&sspassword=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\"\
,\n\t\t\"success\": \"true\",\n\t\t\"error\": \"\"\n\t},\n\t\"response\" :\
\ {\n\t\t\"serveurs\" : {\n\t\t\t\"cpu1\": \"0\",\n\t\t\t\"cpu2\": \"0\",\n\
\t\t\t\"cpu3\": \"0\",\n\t\t\t\"cpu4\": \"0\",\n\t\t\t\"threadsmin\": \"3580\"\
,\n\t\t\t\"nbscrapeurs\": \"547\",\n\t\t\t\"apiacces\": \"7207526\",\n\t\t\
\t\"closefornomember\": \"0\",\n\t\t\t\"closeforleecher\": \"0\",\n\t\t\t\"\
maxthreadfornonmember\": \"256\",\n\t\t\t\"threadfornonmember\": \"30\",\n\
\t\t\t\"maxthreadformember\": \"4096\",\n\t\t\t\"threadformember\": \"107\"\
\n\t\t},\n\t\t\"ssuser\" : {\n\t\t\t\"id\": \"arcaneasada\",\n\t\t\t\"numid\"\
: \"27224648\",\n\t\t\t\"niveau\": \"1\",\n\t\t\t\"contribution\": \"0\",\n\
\t\t\t\"uploadsysteme\": \"0\",\n\t\t\t\"uploadinfos\": \"0\",\n\t\t\t\"romasso\"\
: \"0\",\n\t\t\t\"uploadmedia\": \"0\",\n\t\t\t\"propositionok\": \"0\",\n\
\t\t\t\"propositionko\": \"0\",\n\t\t\t\"quotarefu\": \"0\",\n\t\t\t\"maxthreads\"\
: \"1\",\n\t\t\t\"maxdownloadspeed\": \"128\",\n\t\t\t\"requeststoday\": \"\
0\",\n\t\t\t\"requestskotoday\": \"0\",\n\t\t\t\"maxrequestspermin\": \"3072\"\
,\n\t\t\t\"maxrequestsperday\": \"20000\",\n\t\t\t\"maxrequestskoperday\"\
: \"2000\",\n\t\t\t\"visites\": \"19\",\n\t\t\t\"datedernierevisite\": \"\
2025-07-22 04:21:46\",\n\t\t\t\"favregion\": \"\"\n\t\t\t},\n\t\t\"jeux\"\
\ : [{\n\t\t\t}]\n\t\t}\n\t}\n "
headers:
Access-Control-Allow-Headers:
- X-Requested-With
Access-Control-Allow-Methods:
- GET
Access-Control-Allow-Origin:
- "*"
Cache-Control:
- no-cache, must-revalidate
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Type:
- application/json; charset=utf-8
Date:
- Wed, 23 Jul 2025 03:47:24 GMT
Expires:
- "0"
Pragma:
- no-cache
Server:
- nginx/1.14.2
Set-Cookie:
- SESSID=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Transfer-Encoding:
- chunked
Vary:
- Accept-Encoding
status:
code: 200
message: OK
version: 1

View File

@@ -0,0 +1,390 @@
interactions:
- request:
body: null
headers:
Accept:
- "*/*"
Accept-Encoding:
- gzip, deflate, br
Authorization:
- Bearer invalid_key
Host:
- steamgriddb.com
User-Agent:
- Python/3.13 aiohttp/3.12.14
method: GET
uri: https://steamgriddb.com/api/v2/grids/game/999999
response:
body: {}
headers:
CF-RAY:
- 963ba52e3dbe36ad-YYZ
Cf-Cache-Status:
- DYNAMIC
Connection:
- keep-alive
Content-Type:
- text/html
Date:
- Wed, 23 Jul 2025 13:50:09 GMT
Location:
- https://www.steamgriddb.com/api/v2/grids/game/999999
Nel:
- '{"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}'
Report-To:
- '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=GnRdROZEhzHeGB80TI3Xm2ysNJiSE0CRzhJq%2FOksqtw22DF8hDUZE7P3kP48HP0E1bxvLCnJmcbvckA%2FCzQpWGdkzHuyvrbOZpxltDE%3D"}]}'
Server:
- cloudflare
Transfer-Encoding:
- chunked
alt-svc:
- h3=":443"; ma=86400
status:
code: 301
message: Moved Permanently
- request:
body: null
headers:
Accept:
- "*/*"
Accept-Encoding:
- gzip, deflate, br
Authorization:
- Bearer invalid_key
Host:
- www.steamgriddb.com
User-Agent:
- Python/3.13 aiohttp/3.12.14
method: GET
uri: https://www.steamgriddb.com/api/v2/grids/game/999999
response:
body:
string: '{"success":false,"errors":["Invalid key format"]}'
headers:
CF-RAY:
- 963ba52f3bb036a7-YYZ
Cache-Control:
- no-store, no-cache, must-revalidate
Cf-Cache-Status:
- DYNAMIC
Connection:
- keep-alive
Content-Length:
- "49"
Content-Type:
- application/json
Date:
- Wed, 23 Jul 2025 13:50:09 GMT
Expires:
- Thu, 19 Nov 1981 08:52:00 GMT
Nel:
- '{"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}'
Pragma:
- no-cache
Report-To:
- '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=jriR4bDFn7rpLE4vJt%2Bh293Q8PH3vtjukPKl45PnOlrPChdPs5BcPg0M0ktbIDLZ3jHNdNDPNGonX8TgNozrLWhwpAU0tUl9gG%2BQxSdU4%2B83"}]}'
Server:
- cloudflare
Set-Cookie:
- PHPSESSID=f04g5p1qlseta58ucl8gm4m1jv; Path=/; Max-Age=604800; Expires=Wed,
30 Jul 2025 13:50:09 GMT
- PHPSESSID=deleted; Path=/; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:01 GMT
WWW-Authenticate:
- Bearer
alt-svc:
- h3=":443"; ma=86400
status:
code: 401
message: Unauthorized
- request:
body: null
headers:
Accept:
- "*/*"
Accept-Encoding:
- gzip, deflate, br
Authorization:
- Bearer invalid_key
Host:
- steamgriddb.com
User-Agent:
- Python/3.13 aiohttp/3.12.14
method: GET
uri: https://steamgriddb.com/api/v2/grids/game/999999
response:
body: {}
headers:
CF-RAY:
- 963ba53b7dc5a21d-YYZ
Cf-Cache-Status:
- DYNAMIC
Connection:
- keep-alive
Content-Type:
- text/html
Date:
- Wed, 23 Jul 2025 13:50:11 GMT
Location:
- https://www.steamgriddb.com/api/v2/grids/game/999999
Nel:
- '{"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}'
Report-To:
- '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=p6%2FEWt5LymBxMj9hVwjqB7sD8pMIhRmq8TA1AnXvzVgyMRuZE%2BlpCDtRjvPoYOYlTolBvkZ19%2F2cPEzGaoGXIwq1L5wx1sc0ol3FxPQ%3D"}]}'
Server:
- cloudflare
Transfer-Encoding:
- chunked
alt-svc:
- h3=":443"; ma=86400
status:
code: 301
message: Moved Permanently
- request:
body: null
headers:
Accept:
- "*/*"
Accept-Encoding:
- gzip, deflate, br
Authorization:
- Bearer invalid_key
Host:
- www.steamgriddb.com
User-Agent:
- Python/3.13 aiohttp/3.12.14
method: GET
uri: https://www.steamgriddb.com/api/v2/grids/game/999999
response:
body:
string: '{"success":false,"errors":["Invalid key format"]}'
headers:
CF-RAY:
- 963ba53c4fd436b4-YYZ
Cache-Control:
- no-store, no-cache, must-revalidate
Cf-Cache-Status:
- DYNAMIC
Connection:
- keep-alive
Content-Length:
- "49"
Content-Type:
- application/json
Date:
- Wed, 23 Jul 2025 13:50:11 GMT
Expires:
- Thu, 19 Nov 1981 08:52:00 GMT
Nel:
- '{"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}'
Pragma:
- no-cache
Report-To:
- '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=cCfX8aarQozXEGuHHnA9d10OfJWIekhcA3N2xsjh2s4HhGt%2Fcw42nTjgWWWLhU6CQUTwuojMjOBSN9Ry2oVmOle3VY6lqXEbOM63%2BDKxub%2Fb"}]}'
Server:
- cloudflare
Set-Cookie:
- PHPSESSID=l7n9uu15r2eolpb61s05ritb6n; Path=/; Max-Age=604800; Expires=Wed,
30 Jul 2025 13:50:11 GMT
- PHPSESSID=deleted; Path=/; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:01 GMT
WWW-Authenticate:
- Bearer
alt-svc:
- h3=":443"; ma=86400
status:
code: 401
message: Unauthorized
- request:
body: null
headers:
Accept:
- "*/*"
Accept-Encoding:
- gzip, deflate, br
Authorization:
- Bearer invalid_key
Host:
- steamgriddb.com
User-Agent:
- Python/3.13 aiohttp/3.12.14
method: GET
uri: https://steamgriddb.com/api/v2/grids/game/999999
response:
body: {}
headers:
CF-RAY:
- 963bb26c8dc536b5-YYZ
Cf-Cache-Status:
- DYNAMIC
Connection:
- keep-alive
Content-Type:
- text/html
Date:
- Wed, 23 Jul 2025 13:59:12 GMT
Location:
- https://www.steamgriddb.com/api/v2/grids/game/999999
Nel:
- '{"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}'
Report-To:
- '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=kdY8QNZlrUZJ%2BMX3%2BAFwasHcuIesOnPKfDe0nVGcuZ9FEkPZBpXvCH7TYotPu4tfNfFg7UosTXZwmnwAYTvjlc577YqgEU54whed9no%3D"}]}'
Server:
- cloudflare
Transfer-Encoding:
- chunked
alt-svc:
- h3=":443"; ma=86400
status:
code: 301
message: Moved Permanently
- request:
body: null
headers:
Accept:
- "*/*"
Accept-Encoding:
- gzip, deflate, br
Authorization:
- Bearer invalid_key
Host:
- www.steamgriddb.com
User-Agent:
- Python/3.13 aiohttp/3.12.14
method: GET
uri: https://www.steamgriddb.com/api/v2/grids/game/999999
response:
body:
string: '{"success":false,"errors":["Invalid key format"]}'
headers:
CF-RAY:
- 963bb26d888c36a4-YYZ
Cache-Control:
- no-store, no-cache, must-revalidate
Cf-Cache-Status:
- DYNAMIC
Connection:
- keep-alive
Content-Length:
- "49"
Content-Type:
- application/json
Date:
- Wed, 23 Jul 2025 13:59:12 GMT
Expires:
- Thu, 19 Nov 1981 08:52:00 GMT
Nel:
- '{"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}'
Pragma:
- no-cache
Report-To:
- '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=MRTBMv7D6B2fAnWUlyr8K11xyKQraub%2FGty8%2Bh7suSM8CkRUfRb6O%2FDRhKX4nxANUb%2BxUPc2c2O85vD1LnQCtVt%2FSPiMNVrRw6tCVDmM%2BNPH"}]}'
Server:
- cloudflare
Set-Cookie:
- PHPSESSID=7faess7v3i10vagume9rsn9j5v; Path=/; Max-Age=604800; Expires=Wed,
30 Jul 2025 13:59:12 GMT
- PHPSESSID=deleted; Path=/; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:01 GMT
WWW-Authenticate:
- Bearer
alt-svc:
- h3=":443"; ma=86400
status:
code: 401
message: Unauthorized
- request:
body: null
headers:
Accept:
- "*/*"
Accept-Encoding:
- gzip, deflate, br
Authorization:
- Bearer invalid_key
Host:
- steamgriddb.com
User-Agent:
- Python/3.13 aiohttp/3.12.14
method: GET
uri: https://steamgriddb.com/api/v2/grids/game/999999
response:
body: {}
headers:
CF-RAY:
- 963bb4d98839a211-YYZ
Cf-Cache-Status:
- DYNAMIC
Connection:
- keep-alive
Content-Type:
- text/html
Date:
- Wed, 23 Jul 2025 14:00:51 GMT
Location:
- https://www.steamgriddb.com/api/v2/grids/game/999999
Nel:
- '{"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}'
Report-To:
- '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=nfhxNJ%2FzrBrl7cItciNeWn0bangAEGR1pqqCvQ2q3V4Fu4rh6XAJcbMs454T04PriTWzyWNppJTmhAGGWKHwVoGqwGfbNu1i4AJHQKc%3D"}]}'
Server:
- cloudflare
Transfer-Encoding:
- chunked
alt-svc:
- h3=":443"; ma=86400
status:
code: 301
message: Moved Permanently
- request:
body: null
headers:
Accept:
- "*/*"
Accept-Encoding:
- gzip, deflate, br
Authorization:
- Bearer invalid_key
Host:
- www.steamgriddb.com
User-Agent:
- Python/3.13 aiohttp/3.12.14
method: GET
uri: https://www.steamgriddb.com/api/v2/grids/game/999999
response:
body:
string: '{"success":false,"errors":["Invalid key format"]}'
headers:
CF-RAY:
- 963bb4da5923a211-YYZ
Cache-Control:
- no-store, no-cache, must-revalidate
Cf-Cache-Status:
- DYNAMIC
Connection:
- keep-alive
Content-Length:
- "49"
Content-Type:
- application/json
Date:
- Wed, 23 Jul 2025 14:00:51 GMT
Expires:
- Thu, 19 Nov 1981 08:52:00 GMT
Nel:
- '{"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}'
Pragma:
- no-cache
Report-To:
- '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=j1vlbJENjs6rCUnIgwT1NyYkOlLq1htaxqRHxnVPCqQE3ROoF4F4Wk0m1JHDzj16vVGjpNKUaKzTAgfsdX11NcOFpteQGDzlJIhF3%2BbLGHrV"}]}'
Server:
- cloudflare
Set-Cookie:
- PHPSESSID=p4p1c1npililcqpr261po7n04a; Path=/; Max-Age=604800; Expires=Wed,
30 Jul 2025 14:00:51 GMT
- PHPSESSID=deleted; Path=/; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:01 GMT
WWW-Authenticate:
- Bearer
alt-svc:
- h3=":443"; ma=86400
status:
code: 401
message: Unauthorized
version: 1

View File

@@ -0,0 +1,487 @@
interactions:
- request:
body: null
headers:
Accept:
- "*/*"
Accept-Encoding:
- gzip, deflate, br
Authorization:
- Bearer xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Host:
- steamgriddb.com
User-Agent:
- Python/3.13 aiohttp/3.12.14
method: GET
uri: https://steamgriddb.com/api/v2/grids/game/999999
response:
body: {}
headers:
CF-RAY:
- 963bb3694d3753ef-YYZ
Cf-Cache-Status:
- DYNAMIC
Connection:
- keep-alive
Content-Type:
- text/html
Date:
- Wed, 23 Jul 2025 13:59:52 GMT
Location:
- https://www.steamgriddb.com/api/v2/grids/game/999999
Nel:
- '{"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}'
Report-To:
- '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=ch1dBE3F3onEyQsvqw7uMSOLJPAJUqZQuLoXkvlAAnV97ujBgHHY9aeY1WVBZ1zFpKxvWPeY0WkwRMvUL9OdRm0KKeSWLBLBR%2BBgIfM%3D"}]}'
Server:
- cloudflare
Transfer-Encoding:
- chunked
alt-svc:
- h3=":443"; ma=86400
status:
code: 301
message: Moved Permanently
- request:
body: null
headers:
Accept:
- "*/*"
Accept-Encoding:
- gzip, deflate, br
Authorization:
- Bearer xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Host:
- www.steamgriddb.com
User-Agent:
- Python/3.13 aiohttp/3.12.14
method: GET
uri: https://www.steamgriddb.com/api/v2/grids/game/999999
response:
body:
string: '{"success":false,"errors":["Invalid key format"]}'
headers:
CF-RAY:
- 963bb36a1cbea1e0-YYZ
Cache-Control:
- no-store, no-cache, must-revalidate
Cf-Cache-Status:
- DYNAMIC
Connection:
- keep-alive
Content-Length:
- "49"
Content-Type:
- application/json
Date:
- Wed, 23 Jul 2025 13:59:52 GMT
Expires:
- Thu, 19 Nov 1981 08:52:00 GMT
Nel:
- '{"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}'
Pragma:
- no-cache
Report-To:
- '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=okSHvs6tHx%2FRv2pMcOJbM19zLD%2BpTZ2IkZFNgkezH6FxtxFRjJ9vzkVLUIV0PVeIcgk4%2Bm9ULssW3m27ClxhKQ8DhH4stySQwcPyGSsZWPVh"}]}'
Server:
- cloudflare
Set-Cookie:
- PHPSESSID=i4eosftvg3bhn5j1fus4ajomdd; Path=/; Max-Age=604800; Expires=Wed,
30 Jul 2025 13:59:52 GMT
- PHPSESSID=deleted; Path=/; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:01 GMT
WWW-Authenticate:
- Bearer
alt-svc:
- h3=":443"; ma=86400
status:
code: 401
message: Unauthorized
- request:
body: null
headers:
Accept:
- "*/*"
Accept-Encoding:
- gzip, deflate, br
Authorization:
- Bearer xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Host:
- steamgriddb.com
User-Agent:
- Python/3.13 aiohttp/3.12.14
method: GET
uri: https://steamgriddb.com/api/v2/grids/game/999999
response:
body: {}
headers:
CF-RAY:
- 963bb4038de1a235-YYZ
Cf-Cache-Status:
- DYNAMIC
Connection:
- keep-alive
Content-Type:
- text/html
Date:
- Wed, 23 Jul 2025 14:00:17 GMT
Location:
- https://www.steamgriddb.com/api/v2/grids/game/999999
Nel:
- '{"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}'
Report-To:
- '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=eu0Z14X7u66upRbLT1quhB9e9riOTQ7dXdshXeGWep4Bu6MFzdREIjBS8yyOPGZdLnGp2K53Dns0SniDis4%2FSYhRKFGlQ0pa%2FS9wlAs%3D"}]}'
Server:
- cloudflare
Transfer-Encoding:
- chunked
alt-svc:
- h3=":443"; ma=86400
status:
code: 301
message: Moved Permanently
- request:
body: null
headers:
Accept:
- "*/*"
Accept-Encoding:
- gzip, deflate, br
Authorization:
- Bearer xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Host:
- www.steamgriddb.com
User-Agent:
- Python/3.13 aiohttp/3.12.14
method: GET
uri: https://www.steamgriddb.com/api/v2/grids/game/999999
response:
body:
string: '{"success":false,"status":404,"errors":["Game not found"]}'
headers:
CF-RAY:
- 963bb4046a53ab3d-YYZ
Cache-Control:
- no-store, no-cache, must-revalidate
Cf-Cache-Status:
- DYNAMIC
Connection:
- keep-alive
Content-Encoding:
- br
Content-Type:
- application/json
Date:
- Wed, 23 Jul 2025 14:00:17 GMT
Expires:
- Thu, 19 Nov 1981 08:52:00 GMT
Nel:
- '{"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}'
Pragma:
- no-cache
Report-To:
- '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=Vh3fPYAnnmgibA6aDJthL7R0mdV6qi%2F7AD7LtZ%2BWL7doKjnNkdeI%2FaEMxIG581WS7CrRnIgLJimyMs47Usr3WQS65kAt8ksFXcocO22gyu1d"}]}'
Server:
- cloudflare
Set-Cookie:
- PHPSESSID=tkpigi0q5pm95srra031mm08of; Path=/; Max-Age=604800; Expires=Wed,
30 Jul 2025 14:00:17 GMT
- PHPSESSID=deleted; Path=/; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:01 GMT
Transfer-Encoding:
- chunked
alt-svc:
- h3=":443"; ma=86400
status:
code: 404
message: Not Found
- request:
body: null
headers:
Accept:
- "*/*"
Accept-Encoding:
- gzip, deflate, br
Authorization:
- Bearer xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Host:
- steamgriddb.com
User-Agent:
- Python/3.13 aiohttp/3.12.14
method: GET
uri: https://steamgriddb.com/api/v2/grids/game/999999
response:
body: {}
headers:
CF-RAY:
- 963bb92b8947a217-YYZ
Cf-Cache-Status:
- DYNAMIC
Connection:
- keep-alive
Content-Type:
- text/html
Date:
- Wed, 23 Jul 2025 14:03:48 GMT
Location:
- https://www.steamgriddb.com/api/v2/grids/game/999999
Nel:
- '{"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}'
Report-To:
- '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=8mxiNNz85Kr44gfZgKbwlWYuDE2IvDz4m36bMoAz4ObA%2BDmmAxF8kXFsVkW1djUFbKupjS6NorJHN%2BxVUg46Pl4IoNO3OIHpBvMswBs%3D"}]}'
Server:
- cloudflare
Transfer-Encoding:
- chunked
alt-svc:
- h3=":443"; ma=86400
status:
code: 301
message: Moved Permanently
- request:
body: null
headers:
Accept:
- "*/*"
Accept-Encoding:
- gzip, deflate, br
Authorization:
- Bearer xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Host:
- www.steamgriddb.com
User-Agent:
- Python/3.13 aiohttp/3.12.14
method: GET
uri: https://www.steamgriddb.com/api/v2/grids/game/999999
response:
body:
string: '{"success":false,"errors":["Invalid key format"]}'
headers:
CF-RAY:
- 963bb92c5cd636b1-YYZ
Cache-Control:
- no-store, no-cache, must-revalidate
Cf-Cache-Status:
- DYNAMIC
Connection:
- keep-alive
Content-Length:
- "49"
Content-Type:
- application/json
Date:
- Wed, 23 Jul 2025 14:03:48 GMT
Expires:
- Thu, 19 Nov 1981 08:52:00 GMT
Nel:
- '{"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}'
Pragma:
- no-cache
Report-To:
- '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=ZeTqedMusPeyeJlzc1AsaWcnT2bohu%2FaHoTGXirK7w9%2Fgq3DZlCFeb5%2F4pYklrIkYe7LpIzUdNqD73G6YrP4DIAq4LuK0%2FBIsptN1rIRPqyG"}]}'
Server:
- cloudflare
Set-Cookie:
- PHPSESSID=l6600cbte3ak61eshc0gf1mj1i; Path=/; Max-Age=604800; Expires=Wed,
30 Jul 2025 14:03:48 GMT
- PHPSESSID=deleted; Path=/; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:01 GMT
WWW-Authenticate:
- Bearer
alt-svc:
- h3=":443"; ma=86400
status:
code: 401
message: Unauthorized
- request:
body: null
headers:
Accept:
- "*/*"
Accept-Encoding:
- gzip, deflate, br
Authorization:
- Bearer xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Host:
- steamgriddb.com
User-Agent:
- Python/3.13 aiohttp/3.12.14
method: GET
uri: https://steamgriddb.com/api/v2/grids/game/999999
response:
body: {}
headers:
CF-RAY:
- 963bb9770d0fab3b-YYZ
Cf-Cache-Status:
- DYNAMIC
Connection:
- keep-alive
Content-Type:
- text/html
Date:
- Wed, 23 Jul 2025 14:04:00 GMT
Location:
- https://www.steamgriddb.com/api/v2/grids/game/999999
Nel:
- '{"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}'
Report-To:
- '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=uvvKly%2FYGFL6vChpDFX3uMcTAsiOeGWEAql%2FZZbZe87Brq3J0klL77pgazlvmgLHfI9tJWE4bcAVF3qE5gwJMV6mdEkM6FZqL3jWpJk%3D"}]}'
Server:
- cloudflare
Transfer-Encoding:
- chunked
alt-svc:
- h3=":443"; ma=86400
status:
code: 301
message: Moved Permanently
- request:
body: null
headers:
Accept:
- "*/*"
Accept-Encoding:
- gzip, deflate, br
Authorization:
- Bearer xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Host:
- www.steamgriddb.com
User-Agent:
- Python/3.13 aiohttp/3.12.14
method: GET
uri: https://www.steamgriddb.com/api/v2/grids/game/999999
response:
body:
string: '{"success":false,"status":404,"errors":["Game not found"]}'
headers:
CF-RAY:
- 963bb97848ef53fb-YYZ
Cache-Control:
- no-store, no-cache, must-revalidate
Cf-Cache-Status:
- DYNAMIC
Connection:
- keep-alive
Content-Encoding:
- br
Content-Type:
- application/json
Date:
- Wed, 23 Jul 2025 14:04:00 GMT
Expires:
- Thu, 19 Nov 1981 08:52:00 GMT
Nel:
- '{"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}'
Pragma:
- no-cache
Report-To:
- '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=etAsRH%2FDT3yIDOQxfvpkX0at5cWVwfR1RtiAt25n44lPM9cBKxCykb9FTBUvCoLKKwTcffxA7iTCFe%2FQfsA9ORd2t8suKL53jhvxUgEKc3xu"}]}'
Server:
- cloudflare
Set-Cookie:
- PHPSESSID=r9ojdsaqa56ekpe294s5pioa7v; Path=/; Max-Age=604800; Expires=Wed,
30 Jul 2025 14:04:00 GMT
- PHPSESSID=deleted; Path=/; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:01 GMT
Transfer-Encoding:
- chunked
alt-svc:
- h3=":443"; ma=86400
status:
code: 404
message: Not Found
- request:
body: null
headers:
Accept:
- "*/*"
Accept-Encoding:
- gzip, deflate, br
Authorization:
- Bearer xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Host:
- steamgriddb.com
User-Agent:
- Python/3.13 aiohttp/3.12.14
method: GET
uri: https://steamgriddb.com/api/v2/grids/game/999999
response:
body: {}
headers:
CF-RAY:
- 963bbb53bbeda1d8-YYZ
Cf-Cache-Status:
- DYNAMIC
Connection:
- keep-alive
Content-Type:
- text/html
Date:
- Wed, 23 Jul 2025 14:05:16 GMT
Location:
- https://www.steamgriddb.com/api/v2/grids/game/999999
Nel:
- '{"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}'
Report-To:
- '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=KQhQcauvvT5L6prthgAbtFSgLD2GYR018YXcmmDHoBU9UkmQKX9VOKMq9RlfJ1I2Rzy9AI1nRex2QTUTEoSz7dpZOWrJ2o0Rk0%2BDQ28%3D"}]}'
Server:
- cloudflare
Transfer-Encoding:
- chunked
alt-svc:
- h3=":443"; ma=86400
status:
code: 301
message: Moved Permanently
- request:
body: null
headers:
Accept:
- "*/*"
Accept-Encoding:
- gzip, deflate, br
Authorization:
- Bearer xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Host:
- www.steamgriddb.com
User-Agent:
- Python/3.13 aiohttp/3.12.14
method: GET
uri: https://www.steamgriddb.com/api/v2/grids/game/999999
response:
body:
string: '{"success":false,"status":404,"errors":["Game not found"]}'
headers:
CF-RAY:
- 963bbb548a625467-YYZ
Cache-Control:
- no-store, no-cache, must-revalidate
Cf-Cache-Status:
- DYNAMIC
Connection:
- keep-alive
Content-Encoding:
- br
Content-Type:
- application/json
Date:
- Wed, 23 Jul 2025 14:05:16 GMT
Expires:
- Thu, 19 Nov 1981 08:52:00 GMT
Nel:
- '{"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}'
Pragma:
- no-cache
Report-To:
- '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=Ls7pB5VkDglRzmJm39FkeS9jFm8DleyV1fhgmtKi8VmvlUbQA6pDXIT7uDwImRttih7sW7Rp8PNHfjpFyOrGMe4Ami5KiTdPgfVlxOQVZKI2"}]}'
Server:
- cloudflare
Set-Cookie:
- PHPSESSID=8isnvjts9e1sn7a3d562d5lgd2; Path=/; Max-Age=604800; Expires=Wed,
30 Jul 2025 14:05:16 GMT
- PHPSESSID=deleted; Path=/; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:01 GMT
Transfer-Encoding:
- chunked
alt-svc:
- h3=":443"; ma=86400
status:
code: 404
message: Not Found
version: 1

View File

@@ -0,0 +1,101 @@
interactions:
- request:
body: null
headers:
Accept:
- "*/*"
Accept-Encoding:
- gzip, deflate, br
Authorization:
- Bearer xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Host:
- steamgriddb.com
User-Agent:
- Python/3.13 aiohttp/3.12.14
method: GET
uri: https://steamgriddb.com/api/v2/grids/game/1?limit=5
response:
body: {}
headers:
CF-RAY:
- 963865f18b3ca2af-YUL
Cf-Cache-Status:
- DYNAMIC
Connection:
- keep-alive
Content-Type:
- text/html
Date:
- Wed, 23 Jul 2025 04:22:42 GMT
Location:
- https://www.steamgriddb.com/api/v2/grids/game/1?limit=5
Nel:
- '{"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}'
Report-To:
- '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=myDfJHMyciw8u%2Bm4SW%2FgXNRZaNUQaPt%2Bj9JxWRpgr2kRjCy1UAGJF0S3EkpmnzdOXcJ4DVDJ%2FyHqsLVjFT1MHq2Q0yQFVTlOWopYXCU%3D"}]}'
Server:
- cloudflare
Transfer-Encoding:
- chunked
alt-svc:
- h3=":443"; ma=86400
status:
code: 301
message: Moved Permanently
- request:
body: null
headers:
Accept:
- "*/*"
Accept-Encoding:
- gzip, deflate, br
Authorization:
- Bearer xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Host:
- www.steamgriddb.com
User-Agent:
- Python/3.13 aiohttp/3.12.14
method: GET
uri: https://www.steamgriddb.com/api/v2/grids/game/1?limit=5
response:
body:
string:
'{"success":true,"page":0,"total":14,"limit":5,"data":[{"id":103281,"score":0,"style":"alternate","width":600,"height":900,"nsfw":false,"humor":false,"notes":null,"mime":"image\/png","language":"en","url":"https:\/\/cdn2.steamgriddb.com\/grid\/bafd5785e9335ccde7bcde6051b5dcf6.png","thumb":"https:\/\/cdn2.steamgriddb.com\/thumb\/bafd5785e9335ccde7bcde6051b5dcf6.png","lock":false,"epilepsy":false,"upvotes":0,"downvotes":0,"author":{"name":"duckdicks","steam64":"76561198265411778","avatar":"https:\/\/avatars.steamstatic.com\/834b570adf40c70d7d2379cf0f9332e2534dde13_medium.jpg"}},{"id":318178,"score":0,"style":"alternate","width":600,"height":900,"nsfw":false,"humor":false,"notes":null,"mime":"image\/jpeg","language":"en","url":"https:\/\/cdn2.steamgriddb.com\/grid\/fe80a3e914e35e5ba0157e1a85a3d14f.jpg","thumb":"https:\/\/cdn2.steamgriddb.com\/thumb\/fe80a3e914e35e5ba0157e1a85a3d14f.jpg","lock":false,"epilepsy":false,"upvotes":0,"downvotes":0,"author":{"name":"DebonairTBS","steam64":"76561198008715015","avatar":"https:\/\/avatars.steamstatic.com\/560614290f1bce0865ecfca921bd90ceab6d2507_medium.jpg"}},{"id":92728,"score":0,"style":"alternate","width":920,"height":430,"nsfw":false,"humor":false,"notes":null,"mime":"image\/png","language":"en","url":"https:\/\/cdn2.steamgriddb.com\/grid\/da56e25a64be3b08d6027242e1d85606.png","thumb":"https:\/\/cdn2.steamgriddb.com\/thumb\/da56e25a64be3b08d6027242e1d85606.jpg","lock":false,"epilepsy":false,"upvotes":0,"downvotes":0,"author":{"name":"ZombiJambi","steam64":"76561197965184469","avatar":"https:\/\/avatars.steamstatic.com\/7e56965baf567e68693b40eb170cb4ce75ad68ff_medium.jpg"}},{"id":25987,"score":0,"style":"alternate","width":460,"height":215,"nsfw":false,"humor":false,"notes":null,"mime":"image\/png","language":"en","url":"https:\/\/cdn2.steamgriddb.com\/grid\/cffb7924cc48c212e70437f8b32c5831.png","thumb":"https:\/\/cdn2.steamgriddb.com\/thumb\/cffb7924cc48c212e70437f8b32c5831.jpg","lock":false,"epilepsy":false,"upvotes":0,"downvotes":0,"author":{"name":"TheBoss86","steam64":"76561197988452487","avatar":"https:\/\/avatars.steamstatic.com\/e641aef239f588fc89270581be496fe91afa8c54_medium.jpg"}},{"id":410400,"score":0,"style":"alternate","width":600,"height":900,"nsfw":false,"humor":false,"notes":"Reuploaded-Template
and Art By Castcoder","mime":"image\/jpeg","language":"en","url":"https:\/\/cdn2.steamgriddb.com\/grid\/31087231e09099106762217619ccdb6e.jpg","thumb":"https:\/\/cdn2.steamgriddb.com\/thumb\/31087231e09099106762217619ccdb6e.jpg","lock":false,"epilepsy":false,"upvotes":0,"downvotes":0,"author":{"name":"FattestWrestlingFan","steam64":"76561199418084858","avatar":"https:\/\/avatars.steamstatic.com\/bd7fdd4ab203fcd4f0e93a3d2dd43ff4744b97df_medium.jpg"}}]}'
headers:
CF-RAY:
- 963865f26dcaa297-YUL
Cache-Control:
- no-store, no-cache, must-revalidate
Cf-Cache-Status:
- DYNAMIC
Connection:
- keep-alive
Content-Encoding:
- br
Content-Type:
- application/json
Date:
- Wed, 23 Jul 2025 04:22:42 GMT
Expires:
- Thu, 19 Nov 1981 08:52:00 GMT
Nel:
- '{"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}'
Pragma:
- no-cache
Report-To:
- '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=IIdVcbFwljMp8P%2FsUYL7SdhgNRM%2FICuC2Pa2G8ohVX8vm8TUZ8zLCVLSbf0qiNjfAlZ0XeiPQ66HDd2M5ABlOSSnA8Enc514ClGRi6c6sAKF"}]}'
Server:
- cloudflare
Set-Cookie:
- PHPSESSID=dtofqheldo0dcr1a7tlt0tbie3; Path=/; Max-Age=604800; Expires=Wed,
30 Jul 2025 04:22:42 GMT
- PHPSESSID=deleted; Path=/; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:01 GMT
Transfer-Encoding:
- chunked
alt-svc:
- h3=":443"; ma=86400
status:
code: 200
message: OK
version: 1

View File

@@ -0,0 +1,99 @@
interactions:
- request:
body: null
headers:
Accept:
- "*/*"
Accept-Encoding:
- gzip, deflate, br
Authorization:
- Bearer xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Host:
- steamgriddb.com
User-Agent:
- Python/3.13 aiohttp/3.12.14
method: GET
uri: https://steamgriddb.com/api/v2/grids/game/1?styles=material&limit=3
response:
body: {}
headers:
CF-RAY:
- 963865f38bbaa31d-YUL
Cf-Cache-Status:
- DYNAMIC
Connection:
- keep-alive
Content-Type:
- text/html
Date:
- Wed, 23 Jul 2025 04:22:42 GMT
Location:
- https://www.steamgriddb.com/api/v2/grids/game/1?styles=material&limit=3
Nel:
- '{"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}'
Report-To:
- '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=NySxv89agQjTn2An8NrPLzvimuTV5bVWBm8sv2zuZCfPiBUAUCxxLNyA%2F%2F5cW5BGcE3fvYWn4OX4o3qW56rh%2B6LUAhKjRCI6lxHVvlc%3D"}]}'
Server:
- cloudflare
Transfer-Encoding:
- chunked
alt-svc:
- h3=":443"; ma=86400
status:
code: 301
message: Moved Permanently
- request:
body: null
headers:
Accept:
- "*/*"
Accept-Encoding:
- gzip, deflate, br
Authorization:
- Bearer xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Host:
- www.steamgriddb.com
User-Agent:
- Python/3.13 aiohttp/3.12.14
method: GET
uri: https://www.steamgriddb.com/api/v2/grids/game/1?styles=material&limit=3
response:
body:
string: '{"success":true,"page":0,"total":0,"limit":3,"data":[]}'
headers:
CF-RAY:
- 963865f48c75a2ac-YUL
Cache-Control:
- no-store, no-cache, must-revalidate
Cf-Cache-Status:
- DYNAMIC
Connection:
- keep-alive
Content-Encoding:
- br
Content-Type:
- application/json
Date:
- Wed, 23 Jul 2025 04:22:42 GMT
Expires:
- Thu, 19 Nov 1981 08:52:00 GMT
Nel:
- '{"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}'
Pragma:
- no-cache
Report-To:
- '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=b1XJuD5Wtrw4OiT3%2BiKAWnDAkHWFMF5icQ8g5VEdm%2Fo6CCoQ0TWTP1oN1E4c2o5yJ13cmAWpic26LKkHM8x8zjt1x8ra607%2B%2BQYc%2BgJnV9DL"}]}'
Server:
- cloudflare
Set-Cookie:
- PHPSESSID=fdd0a1h1ebo3fvha468voq0e7f; Path=/; Max-Age=604800; Expires=Wed,
30 Jul 2025 04:22:42 GMT
- PHPSESSID=deleted; Path=/; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:01 GMT
Transfer-Encoding:
- chunked
alt-svc:
- h3=":443"; ma=86400
status:
code: 200
message: OK
version: 1

View File

@@ -0,0 +1,99 @@
interactions:
- request:
body: null
headers:
Accept:
- "*/*"
Accept-Encoding:
- gzip, deflate, br
Authorization:
- Bearer xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Host:
- steamgriddb.com
User-Agent:
- Python/3.13 aiohttp/3.12.14
method: GET
uri: https://steamgriddb.com/api/v2/search/autocomplete/ZZZNonexistentGameZZZ
response:
body: {}
headers:
CF-RAY:
- 963865f77a04a2d0-YUL
Cf-Cache-Status:
- DYNAMIC
Connection:
- keep-alive
Content-Type:
- text/html
Date:
- Wed, 23 Jul 2025 04:22:43 GMT
Location:
- https://www.steamgriddb.com/api/v2/search/autocomplete/ZZZNonexistentGameZZZ
Nel:
- '{"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}'
Report-To:
- '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=pYNrN%2BWi60AMMpjkoyabcuZnbHYE6nca2vcqJYqViGPwoYZ8ylsPoO%2FHG4VrXnnaBRB4FmAGoPFm1muWqdNBcxXDR%2FDhiKp%2Batb0%2B6Q%3D"}]}'
Server:
- cloudflare
Transfer-Encoding:
- chunked
alt-svc:
- h3=":443"; ma=86400
status:
code: 301
message: Moved Permanently
- request:
body: null
headers:
Accept:
- "*/*"
Accept-Encoding:
- gzip, deflate, br
Authorization:
- Bearer xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Host:
- www.steamgriddb.com
User-Agent:
- Python/3.13 aiohttp/3.12.14
method: GET
uri: https://www.steamgriddb.com/api/v2/search/autocomplete/ZZZNonexistentGameZZZ
response:
body:
string: '{"success":true,"data":[]}'
headers:
CF-RAY:
- 963865f84a46a2f4-YUL
Cache-Control:
- no-store, no-cache, must-revalidate
Cf-Cache-Status:
- DYNAMIC
Connection:
- keep-alive
Content-Encoding:
- br
Content-Type:
- application/json
Date:
- Wed, 23 Jul 2025 04:22:43 GMT
Expires:
- Thu, 19 Nov 1981 08:52:00 GMT
Nel:
- '{"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}'
Pragma:
- no-cache
Report-To:
- '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=4SLMo5kYuAK7cLrIu9U%2FQqwicvxv2EGqx5gjhBiZTUPL7jL5ra91UGH%2FsBVZqtJVlxbPLAdyHF6CWED16rcsi38tlst6KYxtH7soAZU%2Fjm2y"}]}'
Server:
- cloudflare
Set-Cookie:
- PHPSESSID=86a94v08tvbn9r0vd4m79th9b0; Path=/; Max-Age=604800; Expires=Wed,
30 Jul 2025 04:22:43 GMT
- PHPSESSID=deleted; Path=/; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:01 GMT
Transfer-Encoding:
- chunked
alt-svc:
- h3=":443"; ma=86400
status:
code: 200
message: OK
version: 1

View File

@@ -0,0 +1,109 @@
interactions:
- request:
body: null
headers:
Accept:
- "*/*"
Accept-Encoding:
- gzip, deflate, br
Authorization:
- Bearer xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Host:
- steamgriddb.com
User-Agent:
- Python/3.13 aiohttp/3.12.14
method: GET
uri: https://steamgriddb.com/api/v2/search/autocomplete/Mario
response:
body: {}
headers:
CF-RAY:
- 963865ef9e73a2c3-YUL
Cf-Cache-Status:
- DYNAMIC
Connection:
- keep-alive
Content-Type:
- text/html
Date:
- Wed, 23 Jul 2025 04:22:41 GMT
Location:
- https://www.steamgriddb.com/api/v2/search/autocomplete/Mario
Nel:
- '{"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}'
Report-To:
- '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=zkUdYCn%2Bh5KJ4%2BwxN6Ha7ns0cwyNkr5rS2BPcPlG0m5E%2FM3n6pR%2BHqSZRkui%2BytQUu0SecyyoOxMQm7SGBww3ZMpW7Tk4ygjoeb0nHA%3D"}]}'
Server:
- cloudflare
Transfer-Encoding:
- chunked
alt-svc:
- h3=":443"; ma=86400
status:
code: 301
message: Moved Permanently
- request:
body: null
headers:
Accept:
- "*/*"
Accept-Encoding:
- gzip, deflate, br
Authorization:
- Bearer xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Host:
- www.steamgriddb.com
User-Agent:
- Python/3.13 aiohttp/3.12.14
method: GET
uri: https://www.steamgriddb.com/api/v2/search/autocomplete/Mario
response:
body:
string:
'{"success":true,"data":[{"id":10,"name":"Mario Party 5","verified":true,"types":[],"release_date":1068508800},{"id":51,"name":"Mario''s
Picross","verified":true,"types":[],"release_date":795139200},{"id":1266,"name":"Mario
Bros.","verified":true,"types":["eshop"],"release_date":425001600},{"id":1681,"name":"Mario
Golf: Advance Tour","verified":true,"types":["eshop"],"release_date":1082592000},{"id":12862,"name":"Mario
Pinball Land","verified":true,"types":["eshop"],"release_date":1093478400},{"id":21164,"name":"Mario
Kart 64","verified":true,"types":["eshop"],"release_date":850521600},{"id":25624,"name":"Mario
Kart DS","verified":true,"types":["eshop"],"release_date":1131926400},{"id":25642,"name":"Mario
Tennis","verified":true,"types":["eshop"],"release_date":964137600},{"id":29847,"name":"Mario''s
Early Years: Fun with Numbers","verified":true,"types":[],"release_date":778377600},{"id":33767,"name":"Mario
Party 9","verified":true,"types":[],"release_date":1330646400}]}'
headers:
CF-RAY:
- 963865f08837a2ac-YUL
Cache-Control:
- no-store, no-cache, must-revalidate
Cf-Cache-Status:
- DYNAMIC
Connection:
- keep-alive
Content-Encoding:
- br
Content-Type:
- application/json
Date:
- Wed, 23 Jul 2025 04:22:42 GMT
Expires:
- Thu, 19 Nov 1981 08:52:00 GMT
Nel:
- '{"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}'
Pragma:
- no-cache
Report-To:
- '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=uyfaS2i0FtUqdstHe5UxIduv0T9ls74TxoL0LMh5GtHVFY78hE0CbdWngsV%2Fd3XUON1sEzJB%2FAzepsGEMh3YrB8iA%2BKxWVR6lclrfNyOJQir"}]}'
Server:
- cloudflare
Set-Cookie:
- PHPSESSID=2dlv64pgf4nk9n94g0fggujltj; Path=/; Max-Age=604800; Expires=Wed,
30 Jul 2025 04:22:42 GMT
- PHPSESSID=deleted; Path=/; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:01 GMT
Transfer-Encoding:
- chunked
alt-svc:
- h3=":443"; ma=86400
status:
code: 200
message: OK
version: 1

View File

@@ -0,0 +1,18 @@
from contextvars import ContextVar
import aiohttp
import pytest_asyncio
@pytest_asyncio.fixture
async def mock_ctx_aiohttp_session():
"""Create a real aiohttp session for integration tests."""
session = aiohttp.ClientSession()
ctx_aiohttp_session: ContextVar[aiohttp.ClientSession] = ContextVar(
"aiohttp_session"
)
ctx_aiohttp_session.set(session)
try:
yield ctx_aiohttp_session
finally:
await session.close()

View File

@@ -0,0 +1,735 @@
import asyncio
import http
import json
from unittest.mock import AsyncMock, MagicMock, patch
import aiohttp
import pytest
import yarl
from adapters.services.mobygames import (
MobyGamesService,
auth_middleware,
)
from fastapi import HTTPException, status
INVALID_GAME_ID = 999999
MockResponse = dict[str, list[dict[str, int]]]
class TestAuthMiddleware:
@patch("adapters.services.mobygames.MOBYGAMES_API_KEY", "test_api_key")
@pytest.mark.asyncio
async def test_auth_middleware_adds_api_key(self):
"""Test that auth middleware adds API key to request URL."""
# Create a real request-like object
mock_request = MagicMock()
mock_request.url = yarl.URL("https://api.mobygames.com/v1/games")
mock_handler = AsyncMock()
mock_response = MagicMock()
mock_handler.return_value = mock_response
result = await auth_middleware(mock_request, mock_handler)
# Check that the URL now contains the API key
expected_url = yarl.URL("https://api.mobygames.com/v1/games").with_query(
api_key="test_api_key"
)
assert mock_request.url == expected_url
mock_handler.assert_called_once_with(mock_request)
assert result == mock_response
@patch("adapters.services.mobygames.MOBYGAMES_API_KEY", "")
@pytest.mark.asyncio
async def test_auth_middleware_with_empty_api_key(self):
"""Test that auth middleware adds empty API key when none configured."""
mock_request = MagicMock()
mock_request.url = yarl.URL("https://api.mobygames.com/v1/games")
mock_handler = AsyncMock()
mock_response = MagicMock()
mock_handler.return_value = mock_response
result = await auth_middleware(mock_request, mock_handler)
expected_url = yarl.URL("https://api.mobygames.com/v1/games").with_query(
api_key=""
)
assert mock_request.url == expected_url
assert result == mock_response
class TestMobyGamesServiceUnit:
"""Unit tests with mocked dependencies."""
@pytest.fixture
def service(self):
"""Create a MobyGamesService instance for testing."""
return MobyGamesService()
@pytest.fixture
def service_custom_url(self):
"""Create a MobyGamesService instance with custom URL."""
return MobyGamesService("https://custom.api.com")
def test_init_default_url(self, service):
"""Test service initialization with default URL."""
assert str(service.url) == "https://api.mobygames.com/v1"
def test_init_custom_url(self, service_custom_url):
"""Test service initialization with custom URL."""
assert str(service_custom_url.url) == "https://custom.api.com"
@pytest.mark.asyncio
async def test_request_success(self, service):
"""Test successful API request."""
mock_session = AsyncMock()
mock_response = AsyncMock()
mock_response.json.return_value = {
"games": [{"game_id": 1, "title": "Test Game"}]
}
mock_response.raise_for_status.return_value = None
mock_session.get.return_value = mock_response
mock_context = MagicMock()
mock_context.get.return_value = mock_session
with patch("adapters.services.mobygames.ctx_aiohttp_session", mock_context):
result = await service._request("https://api.mobygames.com/v1/games")
assert result == {"games": [{"game_id": 1, "title": "Test Game"}]}
mock_session.get.assert_called_once()
mock_response.raise_for_status.assert_called_once()
mock_response.json.assert_called_once()
@pytest.mark.asyncio
async def test_request_connection_error(self, service):
"""Test request with connection error."""
mock_session = AsyncMock()
mock_session.get.side_effect = aiohttp.ClientConnectionError(
"Connection failed"
)
mock_context = MagicMock()
mock_context.get.return_value = mock_session
with patch("adapters.services.mobygames.ctx_aiohttp_session", mock_context):
with pytest.raises(HTTPException) as exc_info:
await service._request("https://api.mobygames.com/v1/games")
assert exc_info.value.status_code == status.HTTP_503_SERVICE_UNAVAILABLE
assert "Can't connect to MobyGames" in exc_info.value.detail
@pytest.mark.asyncio
async def test_request_timeout_with_retry(self, service):
"""Test request timeout with successful retry."""
mock_session = AsyncMock()
mock_response = AsyncMock()
mock_response.json.return_value = {"games": []}
mock_response.raise_for_status.return_value = None
# First call times out, second succeeds
mock_session.get.side_effect = [
aiohttp.ServerTimeoutError("Timeout"),
mock_response,
]
mock_context = MagicMock()
mock_context.get.return_value = mock_session
with patch("adapters.services.mobygames.ctx_aiohttp_session", mock_context):
result = await service._request("https://api.mobygames.com/v1/games")
assert result == {"games": []}
assert mock_session.get.call_count == 2
@pytest.mark.asyncio
async def test_request_unauthorized_returns_empty_dict(self, service):
"""Test that 401 Unauthorized returns empty dict."""
mock_session = AsyncMock()
unauthorized_error = aiohttp.ClientResponseError(
request_info=MagicMock(),
history=(),
status=http.HTTPStatus.UNAUTHORIZED,
)
mock_session.get.side_effect = unauthorized_error
mock_context = MagicMock()
mock_context.get.return_value = mock_session
with patch("adapters.services.mobygames.ctx_aiohttp_session", mock_context):
result = await service._request("https://api.mobygames.com/v1/games")
assert result == {}
@pytest.mark.asyncio
async def test_request_rate_limit_with_retry(self, service):
"""Test rate limit handling with retry."""
mock_session = AsyncMock()
mock_response = AsyncMock()
mock_response.json.return_value = {"games": []}
mock_response.raise_for_status.return_value = None
# First call hits rate limit, second succeeds
rate_limit_error = aiohttp.ClientResponseError(
request_info=MagicMock(),
history=(),
status=http.HTTPStatus.TOO_MANY_REQUESTS,
)
mock_session.get.side_effect = [rate_limit_error, mock_response]
mock_context = MagicMock()
mock_context.get.return_value = mock_session
with patch("adapters.services.mobygames.ctx_aiohttp_session", mock_context):
with patch("asyncio.sleep") as mock_sleep:
result = await service._request("https://api.mobygames.com/v1/games")
assert result == {
"games": []
} # First call returns empty dict, retry happens on second call
mock_sleep.assert_called_once_with(2)
@pytest.mark.asyncio
async def test_request_json_decode_error(self, service):
"""Test handling of JSON decode error."""
mock_session = AsyncMock()
mock_response = AsyncMock()
mock_response.raise_for_status.return_value = None
mock_response.json.side_effect = json.JSONDecodeError("Expecting value", "", 0)
mock_session.get.return_value = mock_response
mock_context = MagicMock()
mock_context.get.return_value = mock_session
with patch("adapters.services.mobygames.ctx_aiohttp_session", mock_context):
result = await service._request("https://api.mobygames.com/v1/games")
assert result == {}
@pytest.mark.asyncio
async def test_request_other_client_error(self, service):
"""Test handling of other client errors."""
mock_session = AsyncMock()
client_error = aiohttp.ClientResponseError(
request_info=MagicMock(),
history=(),
status=http.HTTPStatus.BAD_REQUEST,
)
mock_session.get.side_effect = client_error
mock_context = MagicMock()
mock_context.get.return_value = mock_session
with patch("adapters.services.mobygames.ctx_aiohttp_session", mock_context):
result = await service._request("https://api.mobygames.com/v1/games")
assert result == {}
@pytest.mark.asyncio
async def test_list_games_default_parameters(self, service):
"""Test list_games with default parameters."""
mock_response = {"games": [{"game_id": 1, "title": "Test Game"}]}
with patch.object(
service, "_request", return_value=mock_response
) as mock_request:
result = await service.list_games()
assert result == [{"game_id": 1, "title": "Test Game"}]
mock_request.assert_called_once()
call_args = mock_request.call_args[0][0]
assert "https://api.mobygames.com/v1/games" in call_args
@pytest.mark.asyncio
async def test_list_games_with_game_id(self, service):
"""Test list_games with specific game ID."""
mock_response = {"games": [{"game_id": 123, "title": "Specific Game"}]}
with patch.object(
service, "_request", return_value=mock_response
) as mock_request:
result = await service.list_games(game_id=123)
assert result == [{"game_id": 123, "title": "Specific Game"}]
call_args = mock_request.call_args[0][0]
assert "id=123" in call_args
@pytest.mark.asyncio
async def test_list_games_with_platform_ids(self, service):
"""Test list_games with platform IDs."""
mock_response: MockResponse = {"games": []}
with patch.object(
service, "_request", return_value=mock_response
) as mock_request:
result = await service.list_games(platform_ids=[1, 2, 3])
assert result == []
call_args = mock_request.call_args[0][0]
assert "platform=1" in call_args
assert "platform=2" in call_args
assert "platform=3" in call_args
@pytest.mark.asyncio
async def test_list_games_with_genre_ids(self, service):
"""Test list_games with genre IDs."""
mock_response: MockResponse = {"games": []}
with patch.object(
service, "_request", return_value=mock_response
) as mock_request:
result = await service.list_games(genre_ids=[5, 10])
assert result == []
call_args = mock_request.call_args[0][0]
assert "genre=5" in call_args
assert "genre=10" in call_args
@pytest.mark.asyncio
async def test_list_games_with_group_ids(self, service):
"""Test list_games with group IDs."""
mock_response: MockResponse = {"games": []}
with patch.object(
service, "_request", return_value=mock_response
) as mock_request:
result = await service.list_games(group_ids=[100, 200])
assert result == []
call_args = mock_request.call_args[0][0]
assert "group=100" in call_args
assert "group=200" in call_args
@pytest.mark.asyncio
async def test_list_games_with_title(self, service):
"""Test list_games with title search."""
mock_response: MockResponse = {"games": []}
with patch.object(
service, "_request", return_value=mock_response
) as mock_request:
result = await service.list_games(title="Sonic")
assert result == []
call_args = mock_request.call_args[0][0]
assert "title=Sonic" in call_args
@pytest.mark.asyncio
async def test_list_games_with_output_format_id(self, service):
"""Test list_games with ID output format."""
mock_response = {"games": [1, 2, 3]}
with patch.object(
service, "_request", return_value=mock_response
) as mock_request:
result = await service.list_games(output_format="id")
assert result == [1, 2, 3]
call_args = mock_request.call_args[0][0]
assert "format=id" in call_args
@pytest.mark.asyncio
async def test_list_games_with_output_format_brief(self, service):
"""Test list_games with brief output format."""
mock_response = {"games": [{"game_id": 1, "title": "Test", "moby_url": "url"}]}
with patch.object(
service, "_request", return_value=mock_response
) as mock_request:
result = await service.list_games(output_format="brief")
assert result == [{"game_id": 1, "title": "Test", "moby_url": "url"}]
call_args = mock_request.call_args[0][0]
assert "format=brief" in call_args
@pytest.mark.asyncio
async def test_list_games_with_pagination(self, service):
"""Test list_games with limit and offset."""
mock_response: MockResponse = {"games": []}
with patch.object(
service, "_request", return_value=mock_response
) as mock_request:
result = await service.list_games(limit=10, offset=20)
assert result == []
call_args = mock_request.call_args[0][0]
assert "limit=10" in call_args
assert "offset=20" in call_args
@pytest.mark.asyncio
async def test_list_games_with_all_parameters(self, service):
"""Test list_games with all parameters."""
mock_response: MockResponse = {"games": []}
with patch.object(
service, "_request", return_value=mock_response
) as mock_request:
result = await service.list_games(
game_id=1,
platform_ids=[2, 3],
genre_ids=[4, 5],
group_ids=[6, 7],
title="Test Game",
output_format="normal",
limit=5,
offset=10,
)
assert result == []
call_args = mock_request.call_args[0][0]
assert "id=1" in call_args
assert "platform=2" in call_args
assert "platform=3" in call_args
assert "genre=4" in call_args
assert "genre=5" in call_args
assert "group=6" in call_args
assert "group=7" in call_args
assert "title=Test+Game" in call_args or "title=Test%20Game" in call_args
assert "format=normal" in call_args
assert "limit=5" in call_args
assert "offset=10" in call_args
@pytest.mark.asyncio
async def test_list_games_empty_response(self, service):
"""Test list_games with empty response."""
mock_response: dict[str, int] = {}
with patch.object(service, "_request", return_value=mock_response):
result = await service.list_games()
assert result == []
@pytest.mark.asyncio
async def test_list_games_missing_games_key(self, service):
"""Test list_games with missing games key in response."""
mock_response = {"total": 0}
with patch.object(service, "_request", return_value=mock_response):
result = await service.list_games()
assert result == []
class TestMobyGamesServiceIntegration:
"""Integration tests with real API calls using VCR cassettes."""
@pytest.fixture
def service(self):
"""Create a MobyGamesService instance for integration testing."""
return MobyGamesService()
@pytest.mark.asyncio
@pytest.mark.vcr
async def test_list_games_real_api(self, service, mock_ctx_aiohttp_session):
"""Test list_games with real API call."""
with patch(
"adapters.services.mobygames.ctx_aiohttp_session", mock_ctx_aiohttp_session
):
result = await service.list_games(limit=5)
# Verify response structure
assert isinstance(result, list)
if result: # If there are games
game = result[0]
assert "game_id" in game
assert "title" in game
@pytest.mark.asyncio
@pytest.mark.vcr
async def test_list_games_with_platform_filter_real_api(
self, service, mock_ctx_aiohttp_session
):
"""Test list_games with platform filter using real API call."""
with patch(
"adapters.services.mobygames.ctx_aiohttp_session", mock_ctx_aiohttp_session
):
result = await service.list_games(platform_ids=[1], limit=3) # PC platform
# Verify response structure
assert isinstance(result, list)
assert len(result) <= 3 # Should respect limit
if result:
game = result[0]
assert "game_id" in game
assert "title" in game
@pytest.mark.asyncio
@pytest.mark.vcr
async def test_list_games_with_title_search_real_api(
self, service, mock_ctx_aiohttp_session
):
"""Test list_games with title search using real API call."""
with patch(
"adapters.services.mobygames.ctx_aiohttp_session", mock_ctx_aiohttp_session
):
result = await service.list_games(title="Sonic", limit=5)
# Verify response structure
assert isinstance(result, list)
if result:
game = result[0]
assert "game_id" in game
assert "title" in game
# Title should contain "Sonic" (case insensitive)
assert "sonic" in game["title"].lower()
@pytest.mark.asyncio
@pytest.mark.vcr
async def test_list_games_brief_format_real_api(
self, service, mock_ctx_aiohttp_session
):
"""Test list_games with brief output format using real API call."""
with patch(
"adapters.services.mobygames.ctx_aiohttp_session", mock_ctx_aiohttp_session
):
result = await service.list_games(output_format="brief", limit=3)
# Verify response structure for brief format
assert isinstance(result, list)
if result:
game = result[0]
assert "game_id" in game
assert "title" in game
assert "moby_url" in game
# Brief format should not have detailed fields
assert "description" not in game
assert "genres" not in game
@pytest.mark.asyncio
@pytest.mark.vcr
async def test_list_games_id_format_real_api(
self, service, mock_ctx_aiohttp_session
):
"""Test list_games with ID output format using real API call."""
with patch(
"adapters.services.mobygames.ctx_aiohttp_session", mock_ctx_aiohttp_session
):
result = await service.list_games(output_format="id", limit=5)
# Verify response structure for ID format
assert isinstance(result, list)
if result:
# Should return list of integers (game IDs)
assert all(isinstance(game_id, int) for game_id in result)
@pytest.mark.asyncio
@pytest.mark.vcr
async def test_list_games_normal_format_real_api(
self, service, mock_ctx_aiohttp_session
):
"""Test list_games with normal output format using real API call."""
with patch(
"adapters.services.mobygames.ctx_aiohttp_session", mock_ctx_aiohttp_session
):
result = await service.list_games(output_format="normal", limit=2)
# Verify response structure for normal format
assert isinstance(result, list)
if result:
game = result[0]
assert "game_id" in game
assert "title" in game
# Normal format should have detailed fields
expected_fields = [
"description",
"genres",
"platforms",
"moby_url",
"alternate_titles",
]
# Check that at least some detailed fields are present
assert any(field in game for field in expected_fields)
@pytest.mark.asyncio
@pytest.mark.vcr
async def test_error_handling_real_api(self, service, mock_ctx_aiohttp_session):
"""Test error handling with real API calls."""
with patch(
"adapters.services.mobygames.ctx_aiohttp_session", mock_ctx_aiohttp_session
):
with patch("adapters.services.mobygames.MOBYGAMES_API_KEY", "invalid_key"):
# This should handle the error gracefully
result = await service.list_games(game_id=INVALID_GAME_ID)
assert isinstance(result, list)
@pytest.mark.asyncio
@pytest.mark.vcr
async def test_list_games_with_pagination_real_api(
self, service, mock_ctx_aiohttp_session
):
"""Test list_games with pagination using real API call."""
with patch(
"adapters.services.mobygames.ctx_aiohttp_session", mock_ctx_aiohttp_session
):
result = await service.list_games(limit=2, offset=0)
# Verify response structure
assert isinstance(result, list)
assert len(result) <= 2 # Should respect limit
@pytest.mark.asyncio
@pytest.mark.vcr
async def test_list_games_with_genre_filter_real_api(
self, service, mock_ctx_aiohttp_session
):
"""Test list_games with genre filter using real API call."""
with patch(
"adapters.services.mobygames.ctx_aiohttp_session", mock_ctx_aiohttp_session
):
result = await service.list_games(genre_ids=[1], limit=3) # Action genre
# Verify response structure
assert isinstance(result, list)
if result:
game = result[0]
assert "game_id" in game
assert "title" in game
# Performance tests
class TestMobyGamesServicePerformance:
"""Performance tests for MobyGames service."""
@pytest.fixture
def service(self):
"""Create a MobyGamesService instance for performance testing."""
return MobyGamesService()
@pytest.mark.asyncio
async def test_concurrent_requests(self, service):
"""Test multiple concurrent API requests."""
mock_response = {"games": [{"game_id": 1, "title": "Test Game"}]}
with patch.object(
service, "_request", return_value=mock_response
) as mock_request:
# Run 5 concurrent requests
tasks = [service.list_games(limit=1) for _ in range(5)]
results = await asyncio.gather(*tasks)
# All should succeed
assert all(len(result) == 1 for result in results)
assert len(results) == 5
assert mock_request.call_count == 5
@pytest.mark.asyncio
async def test_request_timeout_handling(self, service):
"""Test handling of request timeouts."""
mock_session = AsyncMock()
# Simulate timeout on first call, success on retry
timeout_error = aiohttp.ServerTimeoutError("Request timeout")
success_response = AsyncMock()
success_response.json.return_value = {"games": []}
success_response.raise_for_status.return_value = None
mock_session.get.side_effect = [timeout_error, success_response]
mock_context = MagicMock()
mock_context.get.return_value = mock_session
with patch("adapters.services.mobygames.ctx_aiohttp_session", mock_context):
result = await service._request(
"https://api.mobygames.com/v1/games", request_timeout=1
)
assert result == {"games": []}
assert mock_session.get.call_count == 2
# Edge case tests
class TestMobyGamesServiceEdgeCases:
"""Edge case tests for MobyGames service."""
@pytest.fixture
def service(self):
"""Create a MobyGamesService instance for edge case testing."""
return MobyGamesService()
@pytest.mark.asyncio
async def test_list_games_with_empty_collections(self, service):
"""Test list_games with empty collections."""
mock_response: MockResponse = {"games": []}
with patch.object(
service, "_request", return_value=mock_response
) as mock_request:
result = await service.list_games(
platform_ids=[],
genre_ids=[],
group_ids=[],
)
assert result == []
# Empty collections should not add parameters to URL
call_args = mock_request.call_args[0][0]
assert "platform=" not in call_args
assert "genre=" not in call_args
assert "group=" not in call_args
@pytest.mark.asyncio
async def test_list_games_with_zero_limit(self, service):
"""Test list_games with zero limit."""
mock_response: MockResponse = {"games": []}
with patch.object(
service, "_request", return_value=mock_response
) as mock_request:
result = await service.list_games(limit=0)
assert result == []
call_args = mock_request.call_args[0][0]
assert "limit=0" in call_args
@pytest.mark.asyncio
async def test_list_games_with_zero_offset(self, service):
"""Test list_games with zero offset."""
mock_response: MockResponse = {"games": []}
with patch.object(
service, "_request", return_value=mock_response
) as mock_request:
result = await service.list_games(offset=0)
assert result == []
call_args = mock_request.call_args[0][0]
assert "offset=0" in call_args
@pytest.mark.asyncio
async def test_list_games_with_special_characters_in_title(self, service):
"""Test list_games with special characters in title."""
mock_response: MockResponse = {"games": []}
with patch.object(
service, "_request", return_value=mock_response
) as mock_request:
result = await service.list_games(title="Pac-Man & Ms. Pac-Man")
assert result == []
call_args = mock_request.call_args[0][0]
# URL should be properly encoded
assert "title=" in call_args
@pytest.mark.asyncio
async def test_request_with_custom_timeout(self, service):
"""Test request with custom timeout."""
mock_session = AsyncMock()
mock_response = AsyncMock()
mock_response.json.return_value = {"games": []}
mock_response.raise_for_status.return_value = None
mock_session.get.return_value = mock_response
mock_context = MagicMock()
mock_context.get.return_value = mock_session
with patch("adapters.services.mobygames.ctx_aiohttp_session", mock_context):
result = await service._request(
"https://api.mobygames.com/v1/games", request_timeout=30
)
assert result == {"games": []}
# Verify timeout was passed correctly
call_kwargs = mock_session.get.call_args[1]
assert call_kwargs["timeout"].total == 30

View File

@@ -1,9 +1,7 @@
from contextvars import ContextVar
from unittest.mock import AsyncMock, MagicMock, patch
import aiohttp
import pytest
import pytest_asyncio
import yarl
from adapters.services.retroachievements import (
RetroAchievementsService,
@@ -85,19 +83,6 @@ class TestRetroAchievementsServiceIntegration:
"""Create a RetroAchievementsService instance for integration testing."""
return RetroAchievementsService()
@pytest_asyncio.fixture
async def mock_ctx_aiohttp_session(self):
"""Create a real aiohttp session for integration tests."""
session = aiohttp.ClientSession()
ctx_aiohttp_session: ContextVar[aiohttp.ClientSession] = ContextVar(
"aiohttp_session"
)
ctx_aiohttp_session.set(session)
try:
yield ctx_aiohttp_session
finally:
await session.close()
@pytest.mark.asyncio
@pytest.mark.vcr
async def test_get_game_extended_details_real_api(
@@ -167,11 +152,11 @@ class TestRetroAchievementsServiceIntegration:
):
result = await service.get_user_completion_progress("arcanecraeda", limit=5)
# Verify response structure
assert isinstance(result, dict)
assert "Total" in result
assert "Results" in result
assert isinstance(result["Results"], list)
if result: # Non-empty response
assert "Total" in result
assert "Results" in result
assert isinstance(result["Results"], list)
@pytest.mark.asyncio
@pytest.mark.vcr

View File

@@ -0,0 +1,874 @@
import asyncio
import base64
import http
import json
from unittest.mock import AsyncMock, MagicMock, patch
import aiohttp
import pytest
import yarl
from adapters.services.screenscraper import (
LOGIN_ERROR_CHECK,
SS_DEV_ID,
SS_DEV_PASSWORD,
ScreenScraperService,
auth_middleware,
)
from fastapi import HTTPException, status
INVALID_GAME_ID = 999999
INVALID_SYSTEM_ID = 999999
class TestScreenScraperConstants:
"""Test ScreenScraper constants and configuration."""
def test_ss_dev_id_decoded(self):
"""Test that SS_DEV_ID is properly decoded."""
expected = base64.b64decode("enVyZGkxNQ==").decode()
assert SS_DEV_ID == expected
def test_ss_dev_password_decoded(self):
"""Test that SS_DEV_PASSWORD is properly decoded."""
expected = base64.b64decode("eFRKd29PRmpPUUc=").decode()
assert SS_DEV_PASSWORD == expected
def test_login_error_check_constant(self):
"""Test that LOGIN_ERROR_CHECK constant is defined."""
assert LOGIN_ERROR_CHECK == "Erreur de login"
class TestAuthMiddleware:
@patch("adapters.services.screenscraper.SCREENSCRAPER_USER", "test_user")
@patch("adapters.services.screenscraper.SCREENSCRAPER_PASSWORD", "test_pass")
@pytest.mark.asyncio
async def test_auth_middleware_adds_auth_params(self):
"""Test that auth middleware adds all required authentication parameters."""
# Create a real request-like object
mock_request = MagicMock()
mock_request.url = yarl.URL("https://api.screenscraper.fr/api2/jeuInfos.php")
mock_handler = AsyncMock()
mock_response = MagicMock()
mock_handler.return_value = mock_response
result = await auth_middleware(mock_request, mock_handler)
# Check that the URL now contains all auth parameters
expected_params = {
"devid": SS_DEV_ID,
"devpassword": SS_DEV_PASSWORD,
"output": "json",
"softname": "romm",
"ssid": "test_user",
"sspassword": "test_pass",
}
expected_url = yarl.URL(
"https://api.screenscraper.fr/api2/jeuInfos.php"
).with_query(**expected_params)
assert mock_request.url == expected_url
mock_handler.assert_called_once_with(mock_request)
assert result == mock_response
@patch("adapters.services.screenscraper.SCREENSCRAPER_USER", "")
@patch("adapters.services.screenscraper.SCREENSCRAPER_PASSWORD", "")
@pytest.mark.asyncio
async def test_auth_middleware_with_empty_credentials(self):
"""Test that auth middleware adds empty credentials when none configured."""
mock_request = MagicMock()
mock_request.url = yarl.URL("https://api.screenscraper.fr/api2/jeuInfos.php")
mock_handler = AsyncMock()
mock_response = MagicMock()
mock_handler.return_value = mock_response
result = await auth_middleware(mock_request, mock_handler)
expected_params = {
"devid": SS_DEV_ID,
"devpassword": SS_DEV_PASSWORD,
"output": "json",
"softname": "romm",
"ssid": "",
"sspassword": "",
}
expected_url = yarl.URL(
"https://api.screenscraper.fr/api2/jeuInfos.php"
).with_query(**expected_params)
assert mock_request.url == expected_url
assert result == mock_response
class TestScreenScraperServiceUnit:
"""Unit tests with mocked dependencies."""
@pytest.fixture
def service(self):
"""Create a ScreenScraperService instance for testing."""
return ScreenScraperService()
@pytest.fixture
def service_custom_url(self):
"""Create a ScreenScraperService instance with custom URL."""
return ScreenScraperService("https://custom.api.com")
def test_init_default_url(self, service):
"""Test service initialization with default URL."""
assert str(service.url) == "https://api.screenscraper.fr/api2"
def test_init_custom_url(self, service_custom_url):
"""Test service initialization with custom URL."""
assert str(service_custom_url.url) == "https://custom.api.com"
@pytest.mark.asyncio
async def test_request_success(self, service):
"""Test successful API request."""
mock_session = AsyncMock()
mock_response = AsyncMock()
mock_response.json.return_value = {"response": {"jeu": {"id": "1", "noms": []}}}
mock_response.text.return_value = '{"response": {"jeu": {"id": "1"}}}'
mock_response.raise_for_status.return_value = None
mock_session.get.return_value = mock_response
mock_context = MagicMock()
mock_context.get.return_value = mock_session
with patch("adapters.services.screenscraper.ctx_aiohttp_session", mock_context):
result = await service._request(
"https://api.screenscraper.fr/api2/jeuInfos.php"
)
assert result == {"response": {"jeu": {"id": "1", "noms": []}}}
mock_session.get.assert_called_once()
mock_response.raise_for_status.assert_called_once()
mock_response.json.assert_called_once()
@pytest.mark.asyncio
async def test_request_login_error(self, service):
"""Test request with login error in response text."""
mock_session = AsyncMock()
mock_response = AsyncMock()
mock_response.text.return_value = "Erreur de login: invalid credentials"
mock_response.raise_for_status.return_value = None
mock_session.get.return_value = mock_response
mock_context = MagicMock()
mock_context.get.return_value = mock_session
with patch("adapters.services.screenscraper.ctx_aiohttp_session", mock_context):
with pytest.raises(HTTPException) as exc_info:
await service._request("https://api.screenscraper.fr/api2/jeuInfos.php")
assert exc_info.value.status_code == status.HTTP_401_UNAUTHORIZED
assert "Invalid ScreenScraper credentials" in exc_info.value.detail
@pytest.mark.asyncio
async def test_request_connection_error(self, service):
"""Test request with connection error."""
mock_session = AsyncMock()
mock_session.get.side_effect = aiohttp.ClientConnectionError(
"Connection failed"
)
mock_context = MagicMock()
mock_context.get.return_value = mock_session
with patch("adapters.services.screenscraper.ctx_aiohttp_session", mock_context):
with pytest.raises(HTTPException) as exc_info:
await service._request("https://api.screenscraper.fr/api2/jeuInfos.php")
assert exc_info.value.status_code == status.HTTP_503_SERVICE_UNAVAILABLE
assert "Can't connect to ScreenScraper" in exc_info.value.detail
@pytest.mark.asyncio
async def test_request_timeout_with_retry(self, service):
"""Test request timeout with successful retry."""
mock_session = AsyncMock()
mock_response = AsyncMock()
mock_response.json.return_value = {"response": {"jeu": {}}}
mock_response.text.return_value = '{"response": {"jeu": {}}}'
mock_response.raise_for_status.return_value = None
# First call times out, second succeeds
mock_session.get.side_effect = [
aiohttp.ServerTimeoutError("Timeout"),
mock_response,
]
mock_context = MagicMock()
mock_context.get.return_value = mock_session
with patch("adapters.services.screenscraper.ctx_aiohttp_session", mock_context):
result = await service._request(
"https://api.screenscraper.fr/api2/jeuInfos.php"
)
assert result == {"response": {"jeu": {}}}
assert mock_session.get.call_count == 2
@pytest.mark.asyncio
async def test_request_rate_limit_with_retry(self, service):
"""Test rate limit handling with retry."""
mock_session = AsyncMock()
rate_limit_error = aiohttp.ClientResponseError(
request_info=MagicMock(),
history=(),
status=http.HTTPStatus.TOO_MANY_REQUESTS,
)
mock_session.get.side_effect = rate_limit_error
mock_context = MagicMock()
mock_context.get.return_value = mock_session
with patch("adapters.services.screenscraper.ctx_aiohttp_session", mock_context):
with patch("asyncio.sleep") as mock_sleep:
result = await service._request(
"https://api.screenscraper.fr/api2/jeuInfos.php"
)
assert result == {}
mock_sleep.assert_called_once_with(2)
@pytest.mark.asyncio
async def test_request_unauthorized_returns_empty_dict(self, service):
"""Test that unauthorized error in retry returns empty dict."""
mock_session = AsyncMock()
# First call timeout, second call unauthorized
timeout_error = aiohttp.ServerTimeoutError("Timeout")
unauthorized_error = aiohttp.ClientResponseError(
request_info=MagicMock(),
history=(),
status=http.HTTPStatus.UNAUTHORIZED,
)
mock_session.get.side_effect = [timeout_error, unauthorized_error]
mock_context = MagicMock()
mock_context.get.return_value = mock_session
with patch("adapters.services.screenscraper.ctx_aiohttp_session", mock_context):
result = await service._request(
"https://api.screenscraper.fr/api2/jeuInfos.php"
)
assert result == {}
@pytest.mark.asyncio
async def test_request_json_decode_error(self, service):
"""Test handling of JSON decode error."""
mock_session = AsyncMock()
mock_response = AsyncMock()
mock_response.text.return_value = "Valid response text"
mock_response.raise_for_status.return_value = None
mock_response.json.side_effect = json.JSONDecodeError("Expecting value", "", 0)
mock_session.get.return_value = mock_response
mock_context = MagicMock()
mock_context.get.return_value = mock_session
with patch("adapters.services.screenscraper.ctx_aiohttp_session", mock_context):
result = await service._request(
"https://api.screenscraper.fr/api2/jeuInfos.php"
)
assert result == {}
@pytest.mark.asyncio
async def test_request_other_client_error(self, service):
"""Test handling of other client errors."""
mock_session = AsyncMock()
client_error = aiohttp.ClientResponseError(
request_info=MagicMock(),
history=(),
status=http.HTTPStatus.BAD_REQUEST,
)
mock_session.get.side_effect = client_error
mock_context = MagicMock()
mock_context.get.return_value = mock_session
with patch("adapters.services.screenscraper.ctx_aiohttp_session", mock_context):
result = await service._request(
"https://api.screenscraper.fr/api2/jeuInfos.php"
)
assert result == {}
@pytest.mark.asyncio
async def test_get_game_info_with_crc(self, service):
"""Test get_game_info with CRC parameter."""
mock_response = {
"response": {
"jeu": {
"id": "1",
"noms": [{"region": "wor", "text": "Test Game"}],
"systeme": {"id": "1", "text": "NES"},
}
}
}
with patch.object(
service, "_request", return_value=mock_response
) as mock_request:
result = await service.get_game_info(crc="ABC123")
assert result is not None
assert result["id"] == "1"
call_args = mock_request.call_args[0][0]
assert "crc=ABC123" in call_args
@pytest.mark.asyncio
async def test_get_game_info_with_md5(self, service):
"""Test get_game_info with MD5 parameter."""
mock_response = {"response": {"jeu": {"id": "1"}}}
with patch.object(
service, "_request", return_value=mock_response
) as mock_request:
result = await service.get_game_info(md5="abc123def456")
assert result is not None
call_args = mock_request.call_args[0][0]
assert "md5=abc123def456" in call_args
@pytest.mark.asyncio
async def test_get_game_info_with_sha1(self, service):
"""Test get_game_info with SHA1 parameter."""
mock_response = {"response": {"jeu": {"id": "1"}}}
with patch.object(
service, "_request", return_value=mock_response
) as mock_request:
result = await service.get_game_info(sha1="abc123def456789")
assert result is not None
call_args = mock_request.call_args[0][0]
assert "sha1=abc123def456789" in call_args
@pytest.mark.asyncio
async def test_get_game_info_with_system_id(self, service):
"""Test get_game_info with system ID parameter."""
mock_response = {"response": {"jeu": {"id": "1"}}}
with patch.object(
service, "_request", return_value=mock_response
) as mock_request:
result = await service.get_game_info(system_id=1)
assert result is not None
call_args = mock_request.call_args[0][0]
assert "systemeid=1" in call_args
@pytest.mark.asyncio
async def test_get_game_info_with_rom_type(self, service):
"""Test get_game_info with ROM type parameter."""
mock_response = {"response": {"jeu": {"id": "1"}}}
with patch.object(
service, "_request", return_value=mock_response
) as mock_request:
result = await service.get_game_info(rom_type="rom")
assert result is not None
call_args = mock_request.call_args[0][0]
assert "romtype=rom" in call_args
@pytest.mark.asyncio
async def test_get_game_info_with_rom_name(self, service):
"""Test get_game_info with ROM name parameter."""
mock_response = {"response": {"jeu": {"id": "1"}}}
with patch.object(
service, "_request", return_value=mock_response
) as mock_request:
result = await service.get_game_info(rom_name="Test Game.nes")
assert result is not None
call_args = mock_request.call_args[0][0]
assert (
"romnom=Test+Game.nes" in call_args or "romnom=Test%20Game.nes" in call_args
)
@pytest.mark.asyncio
async def test_get_game_info_with_rom_size(self, service):
"""Test get_game_info with ROM size parameter."""
mock_response = {"response": {"jeu": {"id": "1"}}}
with patch.object(
service, "_request", return_value=mock_response
) as mock_request:
result = await service.get_game_info(rom_size_bytes=32768)
assert result is not None
call_args = mock_request.call_args[0][0]
assert "romtaille=32768" in call_args
@pytest.mark.asyncio
async def test_get_game_info_with_serial_number(self, service):
"""Test get_game_info with serial number parameter."""
mock_response = {"response": {"jeu": {"id": "1"}}}
with patch.object(
service, "_request", return_value=mock_response
) as mock_request:
result = await service.get_game_info(serial_number="NES-ABC-USA")
assert result is not None
call_args = mock_request.call_args[0][0]
assert "serialnum=NES-ABC-USA" in call_args
@pytest.mark.asyncio
async def test_get_game_info_with_game_id(self, service):
"""Test get_game_info with game ID parameter."""
mock_response = {"response": {"jeu": {"id": "123"}}}
with patch.object(
service, "_request", return_value=mock_response
) as mock_request:
result = await service.get_game_info(game_id=123)
assert result is not None
assert result["id"] == "123"
call_args = mock_request.call_args[0][0]
assert "gameid=123" in call_args
@pytest.mark.asyncio
async def test_get_game_info_with_all_parameters(self, service):
"""Test get_game_info with all parameters."""
mock_response = {"response": {"jeu": {"id": "1"}}}
with patch.object(
service, "_request", return_value=mock_response
) as mock_request:
result = await service.get_game_info(
crc="ABC123",
md5="md5hash",
sha1="sha1hash",
system_id=1,
rom_type="rom",
rom_name="Test Game",
rom_size_bytes=32768,
serial_number="NES-ABC-USA",
game_id=123,
)
assert result is not None
call_args = mock_request.call_args[0][0]
assert "crc=ABC123" in call_args
assert "md5=md5hash" in call_args
assert "sha1=sha1hash" in call_args
assert "systemeid=1" in call_args
assert "romtype=rom" in call_args
assert "romtaille=32768" in call_args
assert "serialnum=NES-ABC-USA" in call_args
assert "gameid=123" in call_args
@pytest.mark.asyncio
async def test_get_game_info_no_game_found(self, service):
"""Test get_game_info when no game is found."""
mock_response: dict[str, dict] = {"response": {}}
with patch.object(service, "_request", return_value=mock_response):
result = await service.get_game_info(crc="NOTFOUND")
assert result is None
@pytest.mark.asyncio
async def test_get_game_info_empty_jeu_data(self, service):
"""Test get_game_info when jeu data is empty."""
mock_response: dict[str, dict] = {"response": {"jeu": {}}}
with patch.object(service, "_request", return_value=mock_response):
result = await service.get_game_info(crc="EMPTY")
assert result is None
@pytest.mark.asyncio
async def test_search_games_basic(self, service):
"""Test search_games with basic term."""
mock_response = {
"response": {
"jeux": [
{"id": "1", "noms": [{"region": "wor", "text": "Sonic"}]},
{"id": "2", "noms": [{"region": "wor", "text": "Sonic 2"}]},
]
}
}
with patch.object(
service, "_request", return_value=mock_response
) as mock_request:
result = await service.search_games(term="Sonic")
assert len(result) == 2
assert result[0]["id"] == "1"
assert result[1]["id"] == "2"
call_args = mock_request.call_args[0][0]
assert "recherche=Sonic" in call_args
@pytest.mark.asyncio
async def test_search_games_with_system_id(self, service):
"""Test search_games with system ID filter."""
mock_response = {"response": {"jeux": [{"id": "1"}]}}
with patch.object(
service, "_request", return_value=mock_response
) as mock_request:
result = await service.search_games(term="Mario", system_id=7)
assert len(result) == 1
call_args = mock_request.call_args[0][0]
assert "recherche=Mario" in call_args
assert "systemeid=7" in call_args
@pytest.mark.asyncio
async def test_search_games_no_results(self, service):
"""Test search_games when no games are found."""
mock_response: dict[str, dict] = {"response": {"jeux": [{}]}}
with patch.object(service, "_request", return_value=mock_response):
result = await service.search_games(term="NonexistentGame")
assert result == []
@pytest.mark.asyncio
async def test_search_games_empty_response(self, service):
"""Test search_games with empty response."""
mock_response: dict[str, dict] = {"response": {}}
with patch.object(service, "_request", return_value=mock_response):
result = await service.search_games(term="Test")
assert result == []
@pytest.mark.asyncio
async def test_search_games_special_characters(self, service):
"""Test search_games with special characters in term."""
mock_response: dict[str, dict] = {"response": {"jeux": []}}
with patch.object(
service, "_request", return_value=mock_response
) as mock_request:
result = await service.search_games(term="Pac-Man & Ms. Pac-Man")
assert result == []
call_args = mock_request.call_args[0][0]
assert "recherche=" in call_args
class TestScreenScraperServiceIntegration:
"""Integration tests with real API calls using VCR cassettes."""
@pytest.fixture
def service(self):
"""Create a ScreenScraperService instance for integration testing."""
return ScreenScraperService()
@pytest.mark.asyncio
@pytest.mark.vcr
async def test_get_game_info_by_crc_real_api(
self, service, mock_ctx_aiohttp_session
):
"""Test get_game_info with CRC using real API call."""
with patch(
"adapters.services.screenscraper.ctx_aiohttp_session",
mock_ctx_aiohttp_session,
):
result = await service.get_game_info(crc="abc123", system_id=1)
# Verify response structure (might be None if game not found)
if result is not None:
assert "id" in result
assert "noms" in result
@pytest.mark.asyncio
@pytest.mark.vcr
async def test_get_game_info_by_game_id_real_api(
self, service, mock_ctx_aiohttp_session
):
"""Test get_game_info with game ID using real API call."""
with patch(
"adapters.services.screenscraper.ctx_aiohttp_session",
mock_ctx_aiohttp_session,
):
result = await service.get_game_info(game_id=1)
# Verify response structure
if result is not None:
assert "id" in result
assert "noms" in result
assert "systeme" in result
@pytest.mark.asyncio
@pytest.mark.vcr
async def test_search_games_real_api(self, service, mock_ctx_aiohttp_session):
"""Test search_games with real API call."""
with patch(
"adapters.services.screenscraper.ctx_aiohttp_session",
mock_ctx_aiohttp_session,
):
result = await service.search_games(term="Mario")
# Verify response structure
assert isinstance(result, list)
if result: # If there are games
game = result[0]
assert "id" in game
assert "noms" in game
@pytest.mark.asyncio
@pytest.mark.vcr
async def test_search_games_with_system_filter_real_api(
self, service, mock_ctx_aiohttp_session
):
"""Test search_games with system filter using real API call."""
with patch(
"adapters.services.screenscraper.ctx_aiohttp_session",
mock_ctx_aiohttp_session,
):
result = await service.search_games(term="Sonic", system_id=1)
# Verify response structure
assert isinstance(result, list)
if result:
game = result[0]
assert "id" in game
assert "noms" in game
@pytest.mark.asyncio
@pytest.mark.vcr
async def test_error_handling_real_api(self, service, mock_ctx_aiohttp_session):
"""Test error handling with real API calls."""
with patch(
"adapters.services.screenscraper.ctx_aiohttp_session",
mock_ctx_aiohttp_session,
):
with patch(
"adapters.services.screenscraper.SCREENSCRAPER_USER", "invalid_user"
):
with patch(
"adapters.services.screenscraper.SCREENSCRAPER_PASSWORD",
"invalid_pass",
):
# This should handle the error gracefully
try:
result = await service.get_game_info(game_id=INVALID_GAME_ID)
# Should either return None or handle auth error
assert result is None or isinstance(result, dict)
except HTTPException as e:
# Should be authentication error
assert e.status_code in [401, 503]
@pytest.mark.asyncio
@pytest.mark.vcr
async def test_get_game_info_not_found_real_api(
self, service, mock_ctx_aiohttp_session
):
"""Test get_game_info with non-existent game using real API call."""
with patch(
"adapters.services.screenscraper.ctx_aiohttp_session",
mock_ctx_aiohttp_session,
):
result = await service.get_game_info(
crc="FFFFFFFFFFFFFFFF", system_id=INVALID_SYSTEM_ID
)
# Should return None for non-existent game
assert result is None
@pytest.mark.asyncio
@pytest.mark.vcr
async def test_search_games_no_results_real_api(
self, service, mock_ctx_aiohttp_session
):
"""Test search_games with term that returns no results using real API call."""
with patch(
"adapters.services.screenscraper.ctx_aiohttp_session",
mock_ctx_aiohttp_session,
):
result = await service.search_games(term="ZZZNonexistentGameZZZ")
# Should return empty list for no results
assert result == []
# Performance tests
class TestScreenScraperServicePerformance:
"""Performance tests for ScreenScraper service."""
@pytest.fixture
def service(self):
"""Create a ScreenScraperService instance for performance testing."""
return ScreenScraperService()
@pytest.mark.asyncio
async def test_concurrent_requests(self, service):
"""Test multiple concurrent API requests."""
mock_response = {"response": {"jeu": {"id": "1"}}}
with patch.object(
service, "_request", return_value=mock_response
) as mock_request:
# Run 5 concurrent requests
tasks = [service.get_game_info(game_id=i) for i in range(1, 6)]
results = await asyncio.gather(*tasks)
# All should succeed
assert all(result is not None for result in results)
assert len(results) == 5
assert mock_request.call_count == 5
@pytest.mark.asyncio
async def test_request_timeout_handling(self, service):
"""Test handling of request timeouts."""
mock_session = AsyncMock()
# Simulate timeout on first call, success on retry
timeout_error = aiohttp.ServerTimeoutError("Request timeout")
success_response = AsyncMock()
success_response.json.return_value = {"response": {"jeu": {}}}
success_response.text.return_value = '{"response": {"jeu": {}}}'
success_response.raise_for_status.return_value = None
mock_session.get.side_effect = [timeout_error, success_response]
mock_context = MagicMock()
mock_context.get.return_value = mock_session
with patch("adapters.services.screenscraper.ctx_aiohttp_session", mock_context):
result = await service._request(
"https://api.screenscraper.fr/api2/jeuInfos.php", request_timeout=1
)
assert result == {"response": {"jeu": {}}}
assert mock_session.get.call_count == 2
@pytest.mark.asyncio
async def test_concurrent_search_requests(self, service):
"""Test multiple concurrent search requests."""
mock_response = {"response": {"jeux": [{"id": "1"}]}}
with patch.object(
service, "_request", return_value=mock_response
) as mock_request:
# Run 3 concurrent search requests
tasks = [
service.search_games(term="Mario"),
service.search_games(term="Sonic"),
service.search_games(term="Zelda"),
]
results = await asyncio.gather(*tasks)
# All should succeed
assert all(len(result) == 1 for result in results)
assert len(results) == 3
assert mock_request.call_count == 3
# Edge case tests
class TestScreenScraperServiceEdgeCases:
"""Edge case tests for ScreenScraper service."""
@pytest.fixture
def service(self):
"""Create a ScreenScraperService instance for edge case testing."""
return ScreenScraperService()
@pytest.mark.asyncio
async def test_get_game_info_with_zero_values(self, service):
"""Test get_game_info with zero values."""
mock_response = {"response": {"jeu": {"id": "0"}}}
with patch.object(
service, "_request", return_value=mock_response
) as mock_request:
result = await service.get_game_info(
system_id=0,
rom_size_bytes=0,
game_id=0,
)
assert result is not None
call_args = mock_request.call_args[0][0]
assert "systemeid=0" in call_args
assert "romtaille=0" in call_args
assert "gameid=0" in call_args
@pytest.mark.asyncio
async def test_search_games_empty_term(self, service):
"""Test search_games with empty term."""
mock_response: dict[str, dict] = {"response": {"jeux": []}}
with patch.object(
service, "_request", return_value=mock_response
) as mock_request:
result = await service.search_games(term="")
assert result == []
call_args = mock_request.call_args[0][0]
assert "recherche=" in call_args
@pytest.mark.asyncio
async def test_get_game_info_with_special_characters(self, service):
"""Test get_game_info with special characters in parameters."""
mock_response = {"response": {"jeu": {"id": "1"}}}
with patch.object(
service, "_request", return_value=mock_response
) as mock_request:
result = await service.get_game_info(
rom_name="Test & Game (USA).nes",
serial_number="NES-T&G-USA",
)
assert result is not None
call_args = mock_request.call_args[0][0]
# URL should be properly encoded
assert "romnom=" in call_args
assert "serialnum=" in call_args
@pytest.mark.asyncio
async def test_request_with_custom_timeout(self, service):
"""Test request with custom timeout."""
mock_session = AsyncMock()
mock_response = AsyncMock()
mock_response.json.return_value = {"response": {}}
mock_response.text.return_value = '{"response": {}}'
mock_response.raise_for_status.return_value = None
mock_session.get.return_value = mock_response
mock_context = MagicMock()
mock_context.get.return_value = mock_session
with patch("adapters.services.screenscraper.ctx_aiohttp_session", mock_context):
result = await service._request(
"https://api.screenscraper.fr/api2/jeuInfos.php", request_timeout=30
)
assert result == {"response": {}}
# Verify timeout was passed correctly
call_kwargs = mock_session.get.call_args[1]
assert call_kwargs["timeout"].total == 30
@pytest.mark.asyncio
async def test_login_error_in_retry_attempt(self, service):
"""Test login error detection in retry attempt."""
mock_session = AsyncMock()
# First call times out, second call has login error
timeout_error = aiohttp.ServerTimeoutError("Timeout")
login_error_response = AsyncMock()
login_error_response.text.return_value = "Erreur de login detected"
login_error_response.raise_for_status.return_value = None
mock_session.get.side_effect = [timeout_error, login_error_response]
mock_context = MagicMock()
mock_context.get.return_value = mock_session
with patch("adapters.services.screenscraper.ctx_aiohttp_session", mock_context):
with pytest.raises(HTTPException) as exc_info:
await service._request("https://api.screenscraper.fr/api2/jeuInfos.php")
assert exc_info.value.status_code == status.HTTP_401_UNAUTHORIZED
assert "Invalid ScreenScraper credentials" in exc_info.value.detail
assert mock_session.get.call_count == 2

View File

@@ -0,0 +1,862 @@
import asyncio
import http
import json
from unittest.mock import AsyncMock, MagicMock, patch
import aiohttp
import pytest
from adapters.services.steamgriddb import (
SteamGridDBService,
auth_middleware,
)
from adapters.services.steamgriddb_types import (
SGDBDimension,
SGDBMime,
SGDBStyle,
SGDBTag,
SGDBType,
)
from fastapi import HTTPException
INVALID_GAME_ID = 999999
class TestAuthMiddleware:
@patch("adapters.services.steamgriddb.STEAMGRIDDB_API_KEY", "test_api_key")
@pytest.mark.asyncio
async def test_auth_middleware_adds_bearer_token(self):
"""Test that auth middleware adds Bearer token to request headers."""
# Create a real request-like object
mock_request = MagicMock()
mock_request.headers = {}
mock_handler = AsyncMock()
mock_response = MagicMock()
mock_handler.return_value = mock_response
result = await auth_middleware(mock_request, mock_handler)
# Check that the Authorization header was added
assert mock_request.headers["Authorization"] == "Bearer test_api_key"
mock_handler.assert_called_once_with(mock_request)
assert result == mock_response
@patch("adapters.services.steamgriddb.STEAMGRIDDB_API_KEY", "")
@pytest.mark.asyncio
async def test_auth_middleware_with_empty_api_key(self):
"""Test that auth middleware adds empty Bearer token when none configured."""
mock_request = MagicMock()
mock_request.headers = {}
mock_handler = AsyncMock()
mock_response = MagicMock()
mock_handler.return_value = mock_response
result = await auth_middleware(mock_request, mock_handler)
assert mock_request.headers["Authorization"] == "Bearer "
assert result == mock_response
class TestSteamGridDBServiceUnit:
"""Unit tests with mocked dependencies."""
@pytest.fixture
def service(self):
"""Create a SteamGridDBService instance for testing."""
return SteamGridDBService()
@pytest.fixture
def service_custom_url(self):
"""Create a SteamGridDBService instance with custom URL."""
return SteamGridDBService("https://custom.api.com")
def test_init_default_url(self, service):
"""Test service initialization with default URL."""
assert str(service.url) == "https://steamgriddb.com/api/v2"
def test_init_custom_url(self, service_custom_url):
"""Test service initialization with custom URL."""
assert str(service_custom_url.url) == "https://custom.api.com"
@pytest.mark.asyncio
async def test_request_success(self, service):
"""Test successful API request."""
mock_session = AsyncMock()
mock_response = AsyncMock()
mock_response.json.return_value = {"data": [{"id": 1, "name": "Test Game"}]}
mock_response.raise_for_status.return_value = None
mock_session.get.return_value = mock_response
mock_context = MagicMock()
mock_context.get.return_value = mock_session
with patch("adapters.services.steamgriddb.ctx_aiohttp_session", mock_context):
result = await service._request(
"https://steamgriddb.com/api/v2/search/test"
)
assert result == {"data": [{"id": 1, "name": "Test Game"}]}
mock_session.get.assert_called_once()
mock_response.raise_for_status.assert_called_once()
mock_response.json.assert_called_once()
@pytest.mark.asyncio
async def test_request_unauthorized_raises_exception(self, service):
"""Test that 401 Unauthorized raises SGDBInvalidAPIKeyException."""
mock_session = AsyncMock()
unauthorized_error = aiohttp.ClientResponseError(
request_info=MagicMock(),
history=(),
status=http.HTTPStatus.UNAUTHORIZED,
)
mock_session.get.side_effect = unauthorized_error
mock_context = MagicMock()
mock_context.get.return_value = mock_session
with patch("adapters.services.steamgriddb.ctx_aiohttp_session", mock_context):
with pytest.raises(HTTPException) as exc_info:
await service._request("https://steamgriddb.com/api/v2/search/test")
assert exc_info.value.status_code == 401
@pytest.mark.asyncio
async def test_request_other_client_error(self, service):
"""Test handling of other client errors."""
mock_session = AsyncMock()
client_error = aiohttp.ClientResponseError(
request_info=MagicMock(),
history=(),
status=http.HTTPStatus.BAD_REQUEST,
)
mock_session.get.side_effect = client_error
mock_context = MagicMock()
mock_context.get.return_value = mock_session
with patch("adapters.services.steamgriddb.ctx_aiohttp_session", mock_context):
result = await service._request(
"https://steamgriddb.com/api/v2/search/test"
)
assert result == {}
@pytest.mark.asyncio
async def test_request_json_decode_error(self, service):
"""Test handling of JSON decode error."""
mock_session = AsyncMock()
mock_response = AsyncMock()
mock_response.raise_for_status.return_value = None
mock_response.json.side_effect = json.JSONDecodeError("Expecting value", "", 0)
mock_session.get.return_value = mock_response
mock_context = MagicMock()
mock_context.get.return_value = mock_session
with patch("adapters.services.steamgriddb.ctx_aiohttp_session", mock_context):
result = await service._request(
"https://steamgriddb.com/api/v2/search/test"
)
assert result == {}
@pytest.mark.asyncio
async def test_get_grids_for_game_basic(self, service):
"""Test get_grids_for_game with basic game ID."""
mock_response = {
"page": 0,
"total": 2,
"limit": 50,
"data": [
{
"id": 1,
"score": 100,
"style": "material",
"url": "https://example.com/grid1.png",
"thumb": "https://example.com/thumb1.png",
"tags": [],
"author": {"name": "TestUser", "steam64": "123", "avatar": ""},
},
{
"id": 2,
"score": 90,
"style": "alternate",
"url": "https://example.com/grid2.png",
"thumb": "https://example.com/thumb2.png",
"tags": ["humor"],
"author": {"name": "TestUser2", "steam64": "456", "avatar": ""},
},
],
}
with patch.object(
service, "_request", return_value=mock_response
) as mock_request:
result = await service.get_grids_for_game(123)
assert result["page"] == 0
assert result["total"] == 2
assert len(result["data"]) == 2
assert result["data"][0]["id"] == 1
call_args = mock_request.call_args[0][0]
assert "grids/game/123" in call_args
@pytest.mark.asyncio
async def test_get_grids_for_game_with_styles(self, service):
"""Test get_grids_for_game with style filters."""
mock_response = {
"page": 0,
"total": 1,
"limit": 50,
"data": [{"id": 1, "style": "material"}],
}
with patch.object(
service, "_request", return_value=mock_response
) as mock_request:
result = await service.get_grids_for_game(
123, styles=[SGDBStyle.MATERIAL, SGDBStyle.ALTERNATE]
)
assert len(result["data"]) == 1
call_args = mock_request.call_args[0][0]
assert (
"styles=material%2Calternate" in call_args
or "styles=material,alternate" in call_args
)
@pytest.mark.asyncio
async def test_get_grids_for_game_with_dimensions(self, service):
"""Test get_grids_for_game with dimension filters."""
mock_response = {"page": 0, "total": 0, "limit": 50, "data": []}
with patch.object(
service, "_request", return_value=mock_response
) as mock_request:
result = await service.get_grids_for_game(
123,
dimensions=[
SGDBDimension.STEAM_HORIZONTAL,
SGDBDimension.STEAM_VERTICAL,
],
)
assert result["data"] == []
call_args = mock_request.call_args[0][0]
assert "dimensions=" in call_args
@pytest.mark.asyncio
async def test_get_grids_for_game_with_mimes(self, service):
"""Test get_grids_for_game with MIME type filters."""
mock_response = {"page": 0, "total": 0, "limit": 50, "data": []}
with patch.object(
service, "_request", return_value=mock_response
) as mock_request:
result = await service.get_grids_for_game(
123, mimes=[SGDBMime.PNG, SGDBMime.WEBP]
)
assert result["data"] == []
call_args = mock_request.call_args[0][0]
assert "mimes=" in call_args
@pytest.mark.asyncio
async def test_get_grids_for_game_with_types(self, service):
"""Test get_grids_for_game with type filters."""
mock_response = {"page": 0, "total": 0, "limit": 50, "data": []}
with patch.object(
service, "_request", return_value=mock_response
) as mock_request:
result = await service.get_grids_for_game(
123, types=[SGDBType.STATIC, SGDBType.ANIMATED]
)
assert result["data"] == []
call_args = mock_request.call_args[0][0]
assert "types=" in call_args
@pytest.mark.asyncio
async def test_get_grids_for_game_with_tags(self, service):
"""Test get_grids_for_game with tag filters."""
mock_response = {"page": 0, "total": 0, "limit": 50, "data": []}
with patch.object(
service, "_request", return_value=mock_response
) as mock_request:
result = await service.get_grids_for_game(
123, any_of_tags=[SGDBTag.HUMOR, SGDBTag.NSFW]
)
assert result["data"] == []
call_args = mock_request.call_args[0][0]
assert "oneoftag=" in call_args
@pytest.mark.asyncio
async def test_get_grids_for_game_with_boolean_filters(self, service):
"""Test get_grids_for_game with boolean filters."""
mock_response = {"page": 0, "total": 0, "limit": 50, "data": []}
with patch.object(
service, "_request", return_value=mock_response
) as mock_request:
result = await service.get_grids_for_game(
123, is_nsfw=True, is_humor=False, is_epilepsy="any"
)
assert result["data"] == []
call_args = mock_request.call_args[0][0]
assert "nsfw=true" in call_args
assert "humor=false" in call_args
assert "epilepsy=any" in call_args
@pytest.mark.asyncio
async def test_get_grids_for_game_with_pagination(self, service):
"""Test get_grids_for_game with pagination parameters."""
mock_response = {"page": 1, "total": 100, "limit": 10, "data": []}
with patch.object(
service, "_request", return_value=mock_response
) as mock_request:
result = await service.get_grids_for_game(123, limit=10, page_number=1)
assert result["page"] == 1
assert result["limit"] == 10
call_args = mock_request.call_args[0][0]
assert "limit=10" in call_args
assert "page=1" in call_args
@pytest.mark.asyncio
async def test_get_grids_for_game_with_all_parameters(self, service):
"""Test get_grids_for_game with all parameters."""
mock_response = {"page": 0, "total": 0, "limit": 5, "data": []}
with patch.object(
service, "_request", return_value=mock_response
) as mock_request:
result = await service.get_grids_for_game(
123,
styles=[SGDBStyle.MATERIAL],
dimensions=[SGDBDimension.STEAM_HORIZONTAL],
mimes=[SGDBMime.PNG],
types=[SGDBType.STATIC],
any_of_tags=[SGDBTag.HUMOR],
is_nsfw=False,
is_humor=True,
is_epilepsy="any",
limit=5,
page_number=0,
)
assert result["data"] == []
call_args = mock_request.call_args[0][0]
assert "styles=material" in call_args
assert "dimensions=460x215" in call_args
assert "mimes=image/png" in call_args
assert "types=static" in call_args
assert "oneoftag=humor" in call_args
assert "nsfw=false" in call_args
assert "humor=true" in call_args
assert "epilepsy=any" in call_args
assert "limit=5" in call_args
assert "page=0" in call_args
@pytest.mark.asyncio
async def test_get_grids_for_game_empty_response(self, service):
"""Test get_grids_for_game with empty response."""
mock_response: dict = {}
with patch.object(service, "_request", return_value=mock_response):
result = await service.get_grids_for_game(123)
assert result["page"] == 0
assert result["total"] == 0
assert result["limit"] == 50
assert result["data"] == []
@pytest.mark.asyncio
async def test_iter_grids_for_game_single_page(self, service):
"""Test iter_grids_for_game with single page of results."""
mock_grid_1 = {"id": 1, "score": 100}
mock_grid_2 = {"id": 2, "score": 90}
mock_response = {
"page": 0,
"total": 2,
"limit": 50,
"data": [mock_grid_1, mock_grid_2],
}
with patch.object(service, "get_grids_for_game", return_value=mock_response):
results = []
async for grid in service.iter_grids_for_game(123):
results.append(grid)
assert len(results) == 2
assert results[0] == mock_grid_1
assert results[1] == mock_grid_2
@pytest.mark.asyncio
async def test_iter_grids_for_game_multiple_pages(self, service):
"""Test iter_grids_for_game with multiple pages of results."""
# First page
mock_response_1 = {
"page": 0,
"total": 75,
"limit": 50,
"data": [{"id": i} for i in range(1, 51)], # 50 items
}
# Second page
mock_response_2 = {
"page": 1,
"total": 75,
"limit": 50,
"data": [{"id": i} for i in range(51, 76)], # 25 items
}
with patch.object(
service,
"get_grids_for_game",
side_effect=[mock_response_1, mock_response_2],
) as mock_get:
results = []
async for grid in service.iter_grids_for_game(123):
results.append(grid)
assert len(results) == 75
assert results[0]["id"] == 1
assert results[49]["id"] == 50
assert results[50]["id"] == 51
assert results[74]["id"] == 75
assert mock_get.call_count == 2
@pytest.mark.asyncio
async def test_iter_grids_for_game_empty_results(self, service):
"""Test iter_grids_for_game with no results."""
mock_response = {"page": 0, "total": 0, "limit": 50, "data": []}
with patch.object(service, "get_grids_for_game", return_value=mock_response):
results = []
async for grid in service.iter_grids_for_game(123):
results.append(grid)
assert len(results) == 0
@pytest.mark.asyncio
async def test_iter_grids_for_game_with_filters(self, service):
"""Test iter_grids_for_game with filters passed through."""
mock_response = {"page": 0, "total": 1, "limit": 50, "data": [{"id": 1}]}
with patch.object(
service, "get_grids_for_game", return_value=mock_response
) as mock_get:
results = []
async for grid in service.iter_grids_for_game(
123,
styles=[SGDBStyle.MATERIAL],
is_nsfw=False,
):
results.append(grid)
assert len(results) == 1
# Verify filters were passed through
mock_get.assert_called_with(
123,
styles=[SGDBStyle.MATERIAL],
dimensions=None,
mimes=None,
types=None,
any_of_tags=None,
is_nsfw=False,
is_humor=None,
is_epilepsy=None,
limit=50,
page_number=0,
)
@pytest.mark.asyncio
async def test_search_games_basic(self, service):
"""Test search_games with basic term."""
mock_response = {
"data": [
{"id": 1, "name": "Test Game 1", "types": ["game"], "verified": True},
{"id": 2, "name": "Test Game 2", "types": ["game"], "verified": False},
]
}
with patch.object(
service, "_request", return_value=mock_response
) as mock_request:
result = await service.search_games("test")
assert len(result) == 2
assert result[0]["id"] == 1
assert result[0]["name"] == "Test Game 1"
assert result[1]["id"] == 2
call_args = mock_request.call_args[0][0]
assert "search/autocomplete/test" in call_args
@pytest.mark.asyncio
async def test_search_games_no_results(self, service):
"""Test search_games with no results."""
mock_response: dict = {"data": []}
with patch.object(service, "_request", return_value=mock_response):
result = await service.search_games("nonexistent")
assert result == []
@pytest.mark.asyncio
async def test_search_games_empty_response(self, service):
"""Test search_games with empty response."""
mock_response: dict = {}
with patch.object(service, "_request", return_value=mock_response):
result = await service.search_games("test")
assert result == []
@pytest.mark.asyncio
async def test_search_games_special_characters(self, service):
"""Test search_games with special characters in term."""
mock_response: dict = {"data": []}
with patch.object(
service, "_request", return_value=mock_response
) as mock_request:
result = await service.search_games("Pac-Man & Ms. Pac-Man")
assert result == []
call_args = mock_request.call_args[0][0]
assert "search/autocomplete/" in call_args
class TestSteamGridDBServiceIntegration:
"""Integration tests with real API calls using VCR cassettes."""
@pytest.fixture
def service(self):
"""Create a SteamGridDBService instance for integration testing."""
return SteamGridDBService()
@pytest.mark.asyncio
@pytest.mark.vcr
async def test_search_games_real_api(self, service, mock_ctx_aiohttp_session):
"""Test search_games with real API call."""
with patch(
"adapters.services.steamgriddb.ctx_aiohttp_session",
mock_ctx_aiohttp_session,
):
result = await service.search_games("Mario")
# Verify response structure
assert isinstance(result, list)
if result: # If there are games
game = result[0]
assert "id" in game
assert "name" in game
assert "types" in game
assert "verified" in game
@pytest.mark.asyncio
@pytest.mark.vcr
async def test_get_grids_for_game_real_api(self, service, mock_ctx_aiohttp_session):
"""Test get_grids_for_game with real API call."""
with patch(
"adapters.services.steamgriddb.ctx_aiohttp_session",
mock_ctx_aiohttp_session,
):
result = await service.get_grids_for_game(1, limit=5)
# Verify response structure
assert isinstance(result, dict)
assert "page" in result
assert "total" in result
assert "limit" in result
assert "data" in result
assert isinstance(result["data"], list)
if result["data"]: # If there are grids
grid = result["data"][0]
assert "id" in grid
assert "score" in grid
assert "style" in grid
assert "url" in grid
@pytest.mark.asyncio
@pytest.mark.vcr
async def test_get_grids_for_game_with_filters_real_api(
self, service, mock_ctx_aiohttp_session
):
"""Test get_grids_for_game with filters using real API call."""
with patch(
"adapters.services.steamgriddb.ctx_aiohttp_session",
mock_ctx_aiohttp_session,
):
result = await service.get_grids_for_game(
1, styles=[SGDBStyle.MATERIAL], limit=3
)
# Verify response structure
assert isinstance(result, dict)
assert len(result["data"]) <= 3 # Should respect limit
@pytest.mark.asyncio
@pytest.mark.vcr
async def test_iter_grids_for_game_real_api(
self, service, mock_ctx_aiohttp_session
):
"""Test iter_grids_for_game with real API call."""
with patch(
"adapters.services.steamgriddb.ctx_aiohttp_session",
mock_ctx_aiohttp_session,
):
results = []
count = 0
async for grid in service.iter_grids_for_game(1):
results.append(grid)
count += 1
if count >= 5: # Limit iterations for testing
break
# Verify we got results
if results:
grid = results[0]
assert isinstance(grid, dict)
assert "id" in grid
assert "score" in grid
assert "style" in grid
@pytest.mark.asyncio
@pytest.mark.vcr
async def test_error_handling_real_api(self, service, mock_ctx_aiohttp_session):
"""Test error handling with real API calls."""
with patch(
"adapters.services.steamgriddb.ctx_aiohttp_session",
mock_ctx_aiohttp_session,
):
with patch(
"adapters.services.steamgriddb.STEAMGRIDDB_API_KEY", "invalid_key"
):
# This should handle the error gracefully
try:
result = await service.get_grids_for_game(INVALID_GAME_ID)
# Should either return empty result or handle auth error
assert isinstance(result, dict)
except HTTPException as exc:
# Should be authentication error with 401 status
assert exc.status_code == 401
@pytest.mark.asyncio
@pytest.mark.vcr
async def test_search_games_no_results_real_api(
self, service, mock_ctx_aiohttp_session
):
"""Test search_games with term that returns no results using real API call."""
with patch(
"adapters.services.steamgriddb.ctx_aiohttp_session",
mock_ctx_aiohttp_session,
):
result = await service.search_games("ZZZNonexistentGameZZZ")
# Should return empty list for no results
assert result == []
# Performance tests
class TestSteamGridDBServicePerformance:
"""Performance tests for SteamGridDB service."""
@pytest.fixture
def service(self):
"""Create a SteamGridDBService instance for performance testing."""
return SteamGridDBService()
@pytest.mark.asyncio
async def test_concurrent_requests(self, service):
"""Test multiple concurrent API requests."""
mock_response = {"data": [{"id": 1, "name": "Test Game"}]}
with patch.object(
service, "_request", return_value=mock_response
) as mock_request:
# Run 5 concurrent requests
tasks = [service.search_games("test") for _ in range(5)]
results = await asyncio.gather(*tasks)
# All should succeed
assert all(len(result) == 1 for result in results)
assert len(results) == 5
assert mock_request.call_count == 5
@pytest.mark.asyncio
async def test_concurrent_grid_requests(self, service):
"""Test multiple concurrent grid requests."""
mock_response = {"page": 0, "total": 1, "limit": 50, "data": [{"id": 1}]}
with patch.object(
service, "_request", return_value=mock_response
) as mock_request:
# Run 3 concurrent grid requests
tasks = [service.get_grids_for_game(i) for i in range(1, 4)]
results = await asyncio.gather(*tasks)
# All should succeed
assert all(len(result["data"]) == 1 for result in results)
assert len(results) == 3
assert mock_request.call_count == 3
@pytest.mark.asyncio
async def test_iter_grids_pagination_performance(self, service):
"""Test performance of pagination in iter_grids_for_game."""
# Mock multiple pages
mock_responses = [
{
"page": 0,
"total": 100,
"limit": 50,
"data": [{"id": i} for i in range(1, 51)],
},
{
"page": 1,
"total": 100,
"limit": 50,
"data": [{"id": i} for i in range(51, 101)],
},
]
with patch.object(
service, "get_grids_for_game", side_effect=mock_responses
) as mock_get:
results = []
async for grid in service.iter_grids_for_game(123):
results.append(grid)
assert len(results) == 100
assert mock_get.call_count == 2
# Edge case tests
class TestSteamGridDBServiceEdgeCases:
"""Edge case tests for SteamGridDB service."""
@pytest.fixture
def service(self):
"""Create a SteamGridDBService instance for edge case testing."""
return SteamGridDBService()
@pytest.mark.asyncio
async def test_get_grids_for_game_with_empty_collections(self, service):
"""Test get_grids_for_game with empty collections."""
mock_response = {"page": 0, "total": 0, "limit": 50, "data": []}
with patch.object(
service, "_request", return_value=mock_response
) as mock_request:
result = await service.get_grids_for_game(
123,
styles=[],
dimensions=[],
mimes=[],
types=[],
any_of_tags=[],
)
assert result["data"] == []
call_args = mock_request.call_args[0][0]
# Empty collections should not add parameters to URL
assert "styles=" not in call_args
assert "dimensions=" not in call_args
assert "mimes=" not in call_args
assert "types=" not in call_args
assert "oneoftag=" not in call_args
@pytest.mark.asyncio
async def test_get_grids_for_game_with_zero_values(self, service):
"""Test get_grids_for_game with zero values."""
mock_response = {"page": 0, "total": 0, "limit": 0, "data": []}
with patch.object(
service, "_request", return_value=mock_response
) as mock_request:
result = await service.get_grids_for_game(123, limit=0, page_number=0)
assert result["data"] == []
call_args = mock_request.call_args[0][0]
assert "limit=0" in call_args
assert "page=0" in call_args
@pytest.mark.asyncio
async def test_search_games_empty_term(self, service):
"""Test search_games with empty term."""
mock_response: dict = {"data": []}
with patch.object(
service, "_request", return_value=mock_response
) as mock_request:
result = await service.search_games("")
assert result == []
call_args = mock_request.call_args[0][0]
assert "search/autocomplete/" in call_args
@pytest.mark.asyncio
async def test_request_with_custom_timeout(self, service):
"""Test request with custom timeout."""
mock_session = AsyncMock()
mock_response = AsyncMock()
mock_response.json.return_value = {"data": []}
mock_response.raise_for_status.return_value = None
mock_session.get.return_value = mock_response
mock_context = MagicMock()
mock_context.get.return_value = mock_session
with patch("adapters.services.steamgriddb.ctx_aiohttp_session", mock_context):
result = await service._request(
"https://steamgriddb.com/api/v2/search/test", request_timeout=30
)
assert result == {"data": []}
# Verify timeout was passed correctly
call_kwargs = mock_session.get.call_args[1]
assert call_kwargs["timeout"].total == 30
@pytest.mark.asyncio
async def test_iter_grids_stops_at_exact_total(self, service):
"""Test that iter_grids_for_game stops when reaching exact total."""
# Mock response where we reach exact total
mock_response = {
"page": 0,
"total": 50,
"limit": 50,
"data": [{"id": i} for i in range(1, 51)], # Exactly 50 items
}
with patch.object(
service, "get_grids_for_game", return_value=mock_response
) as mock_get:
results = []
async for grid in service.iter_grids_for_game(123):
results.append(grid)
assert len(results) == 50
assert mock_get.call_count == 1 # Should only call once
@pytest.mark.asyncio
async def test_get_grids_boolean_filter_none_values(self, service):
"""Test get_grids_for_game with None values for boolean filters."""
mock_response = {"page": 0, "total": 0, "limit": 50, "data": []}
with patch.object(
service, "_request", return_value=mock_response
) as mock_request:
result = await service.get_grids_for_game(
123, is_nsfw=None, is_humor=None, is_epilepsy=None
)
assert result["data"] == []
call_args = mock_request.call_args[0][0]
# None values should not add parameters to URL
assert "nsfw=" not in call_args
assert "humor=" not in call_args
assert "epilepsy=" not in call_args

View File

@@ -10,6 +10,10 @@ env =
IGDB_CLIENT_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
RETROACHIEVEMENTS_USERNAME=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
RETROACHIEVEMENTS_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
MOBYGAMES_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
SCREENSCRAPER_USER=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
SCREENSCRAPER_PASSWORD=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
STEAMGRIDDB_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
LAUNCHBOX_API_ENABLED=true
ROMM_AUTH_SECRET_KEY=843f6cefc5ba1430d54061301c2893be00c2aef11dae39ffec13a2af1a86e867
ENABLE_RESCAN_ON_FILESYSTEM_CHANGE=true