IGDB: add DOS-only search, gameplay videos + screenshot support
Build & Deploy / build-and-deploy (push) Successful in 1m28s

- Search now filters to DOS platform only (platform ID 13)
- New fetchVideos() queries IGDB game_videos endpoint for YouTube IDs
- New fetchScreenshots() queries IGDB screenshots endpoint for images
- autoScrape and applyIgdbId both fetch videos + screenshots after match
- Upload auto-save also triggers when videos/screenshots found
- GameDetail: embed first YouTube video + scrollable screenshot gallery
- Handles missing media gracefully (section hidden entirely if neither exists)
This commit is contained in:
David Alvarez
2026-05-26 15:49:27 +02:00
parent 1f55f63b99
commit 0912e43d99
4 changed files with 189 additions and 2 deletions
+4
View File
@@ -28,6 +28,7 @@ public class Game {
private boolean hasCover;
private boolean hasScreenshots;
private List<String> screenshots;
private List<String> videos;
private boolean ready;
private Instant createdAt;
@@ -95,6 +96,9 @@ public class Game {
public List<String> getScreenshots() { return screenshots; }
public void setScreenshots(List<String> screenshots) { this.screenshots = screenshots; }
public List<String> getVideos() { return videos; }
public void setVideos(List<String> videos) { this.videos = videos; }
public boolean isReady() { return ready; }
public void setReady(boolean ready) { this.ready = ready; }
+95 -1
View File
@@ -93,11 +93,12 @@ public class IgdbService {
public List<Map<String, Object>> search(String query) throws Exception {
if (!isConfigured()) return List.of();
// Search broadly; IGDB returns relevance-sorted results
// Search limited to DOS platform (IGDB platform ID 13)
String apql = "search \"" + sanitize(query) + "\";"
+ " fields name,first_release_date,genres.name,"
+ " involved_companies.company.name,involved_companies.developer,involved_companies.publisher,"
+ " summary,cover.url,platforms;"
+ " where platforms = [13];"
+ " limit 20;";
HttpRequest req = igdbRequest("/games")
@@ -212,6 +213,57 @@ public class IgdbService {
return IMG_BASE + "/" + COVER_SIZE + "/" + thumbUrl.substring(thumbUrl.lastIndexOf('/') + 1);
}
/**
* Fetch YouTube video IDs associated with an IGDB game.
*/
public List<String> fetchVideos(int igdbId) throws Exception {
String apql = "fields video_id; where game = " + igdbId + ";";
HttpRequest req = igdbRequest("/game_videos")
.POST(HttpRequest.BodyPublishers.ofString(apql))
.build();
HttpResponse<String> res = http.send(req, HttpResponse.BodyHandlers.ofString());
if (res.statusCode() != 200) return List.of();
JsonNode results = mapper.readTree(res.body());
if (!results.isArray()) return List.of();
List<String> videos = new ArrayList<>();
for (JsonNode v : results) {
if (v.has("video_id")) {
videos.add(v.get("video_id").asText());
}
}
return videos;
}
/**
* Fetch screenshot image URLs for an IGDB game.
*/
public List<String> fetchScreenshots(int igdbId) throws Exception {
String apql = "fields url; where game = " + igdbId + ";";
HttpRequest req = igdbRequest("/screenshots")
.POST(HttpRequest.BodyPublishers.ofString(apql))
.build();
HttpResponse<String> res = http.send(req, HttpResponse.BodyHandlers.ofString());
if (res.statusCode() != 200) return List.of();
JsonNode results = mapper.readTree(res.body());
if (!results.isArray()) return List.of();
List<String> screenshots = new ArrayList<>();
for (JsonNode s : results) {
if (s.has("url")) {
String thumbUrl = s.get("url").asText();
// Use bigger size for display
String fullUrl = "https:" + thumbUrl.replace("t_thumb", "t_1080p");
screenshots.add(fullUrl);
}
}
return screenshots;
}
// ─── Auto-Scrape (called during upload) ──────────────────────
/**
@@ -242,6 +294,29 @@ public class IgdbService {
applyMatch(game, best);
LOG.info("IGDB auto-scrape: applied '" + best.get("name") + "' to '" + game.getTitle() + "'");
// Fetch videos and screenshots
Object igdbIdObj = best.get("igdb_id");
if (igdbIdObj instanceof Number igdbIdNum) {
int igdbId = igdbIdNum.intValue();
try {
List<String> videos = fetchVideos(igdbId);
if (!videos.isEmpty()) {
game.setVideos(videos);
}
} catch (Exception e) {
LOG.warning("IGDB: failed to fetch videos: " + e.getMessage());
}
try {
List<String> screenshots = fetchScreenshots(igdbId);
if (!screenshots.isEmpty()) {
game.setScreenshots(screenshots);
game.setHasScreenshots(true);
}
} catch (Exception e) {
LOG.warning("IGDB: failed to fetch screenshots: " + e.getMessage());
}
}
} catch (Exception e) {
LOG.warning("IGDB auto-scrape failed for '" + game.getTitle() + "': " + e.getMessage());
}
@@ -272,6 +347,25 @@ public class IgdbService {
}
applyMatch(game, results.get(0));
// Fetch videos and screenshots
try {
List<String> videos = fetchVideos(igdbId);
if (!videos.isEmpty()) {
game.setVideos(videos);
}
} catch (Exception e) {
LOG.warning("IGDB: failed to fetch videos for game " + igdbId + ": " + e.getMessage());
}
try {
List<String> screenshots = fetchScreenshots(igdbId);
if (!screenshots.isEmpty()) {
game.setScreenshots(screenshots);
game.setHasScreenshots(true);
}
} catch (Exception e) {
LOG.warning("IGDB: failed to fetch screenshots for game " + igdbId + ": " + e.getMessage());
}
}
/**
@@ -107,7 +107,9 @@ public class UploadResource {
// Auto-populate from IGDB (non-blocking if credentials are set)
if (igdb.isConfigured()) {
igdb.autoScrape(game);
if (game.isHasCover() || game.getYear() != null || game.getGenre() != null) {
if (game.isHasCover() || game.getYear() != null || game.getGenre() != null
|| (game.getVideos() != null && !game.getVideos().isEmpty())
|| (game.getScreenshots() != null && !game.getScreenshots().isEmpty())) {
svc.save(game); // re-save with enriched metadata
}
}