feat: Auto-fetch game covers via Steam, Wikipedia, and CheapShark API fallback
This commit includes: - PocketBase hook to download the highest quality vertical cover images possible. - UI adjustments with blurred background fallback for non-vertical layouts. - Bugfixes in PocketBase docker startup command.
This commit is contained in:
5 files changed
+121
-23
No files matched your search
+3
-2
@@ -30,7 +30,8 @@ FROM pb_base AS production
|
||||
# Skopiuj statyczny build frontendu prosto z vite do pb_public (serwowania przez Pocketbase)
|
||||
COPY --from=build_frontend /app/dist /pb_public
|
||||
|
||||
# Skopiuj ewentualne migracje na produkcję
|
||||
# Skopiuj ewentualne migracje i hooki na produkcję
|
||||
COPY backend/pb_migrations /pb_migrations
|
||||
COPY backend/pb_hooks /pb_hooks
|
||||
|
||||
CMD ["/pocketbase", "serve", "--http=0.0.0.0:8090"]
|
||||
CMD ["/pocketbase", "serve", "--http=0.0.0.0:8090", "--hooksDir=/pb_hooks"]
|
||||
@@ -0,0 +1,111 @@
|
||||
// Pocketbase Hook fetch_covers
|
||||
console.log("POCKETBASE HOOK: loaded fetch_covers.pb.js");
|
||||
|
||||
onRecordAfterCreateRequest((e) => {
|
||||
try {
|
||||
console.log("Wyzwolono onRecordAfterCreateRequest dla " + e.record.get("title"));
|
||||
const title = e.record.get("title");
|
||||
if (!title) return;
|
||||
|
||||
// Jeśli ma już okładkę, ignorujemy
|
||||
const existingFile = e.record.get("cover_image");
|
||||
if (existingFile) return;
|
||||
|
||||
const urlArgs = encodeURIComponent(title);
|
||||
let thumbUrl = null;
|
||||
|
||||
console.log("Szukam w Steam API: " + title);
|
||||
try {
|
||||
const steamRes = $http.send({
|
||||
url: `https://store.steampowered.com/api/storesearch/?term=${urlArgs}&l=english&cc=US`,
|
||||
method: "GET"
|
||||
});
|
||||
if (steamRes.json && steamRes.json.total > 0 && steamRes.json.items.length > 0) {
|
||||
const appId = steamRes.json.items[0].id;
|
||||
// Steam serwuje kapitalne pionowe okladki library_600x900_2x (2x to wysoka rozdzielczosc)
|
||||
const steamThumb = `https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/${appId}/library_600x900_2x.jpg`;
|
||||
|
||||
try {
|
||||
const checkRes = $http.send({ url: steamThumb, method: "HEAD" });
|
||||
if (checkRes.statusCode === 200) {
|
||||
thumbUrl = steamThumb;
|
||||
console.log("Znaleziono okładke Steam dla: " + title);
|
||||
}
|
||||
} catch(err) {
|
||||
// Brak okładki 600x900, probujemy standardowej capsule
|
||||
const fallbackSteam = `https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/${appId}/capsule_616x353.jpg`;
|
||||
const checkRes2 = $http.send({ url: fallbackSteam, method: "HEAD" });
|
||||
if (checkRes2.statusCode === 200) {
|
||||
thumbUrl = fallbackSteam;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("Steam API error, pomijam:", e);
|
||||
}
|
||||
|
||||
if (!thumbUrl) {
|
||||
console.log("Szukam w Wikipedia API (fallback): " + title);
|
||||
try {
|
||||
const wikiRes = $http.send({
|
||||
url: `https://en.wikipedia.org/w/api.php?action=query&prop=pageimages&titles=${urlArgs}&pithumbsize=600&format=json`,
|
||||
method: "GET",
|
||||
headers: { "User-Agent": "Nene-Game-App/1.0" }
|
||||
});
|
||||
const pages = wikiRes.json && wikiRes.json.query && wikiRes.json.query.pages;
|
||||
if (pages) {
|
||||
const keys = Object.keys(pages);
|
||||
if (keys.length > 0 && pages[keys[0]].thumbnail) {
|
||||
thumbUrl = pages[keys[0]].thumbnail.source;
|
||||
console.log("Znaleziono okładke na Wikipedii dla: " + title);
|
||||
}
|
||||
}
|
||||
} catch(e) {
|
||||
console.log("Wikipedia API error, pomijam:", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (!thumbUrl) {
|
||||
console.log("Szukam w CheapShark API (druga deska ratunku): " + title);
|
||||
try {
|
||||
const searchRes = $http.send({
|
||||
url: `https://www.cheapshark.com/api/1.0/games?title=${urlArgs}`,
|
||||
method: "GET"
|
||||
});
|
||||
if (searchRes.json && searchRes.json.length > 0) {
|
||||
thumbUrl = searchRes.json[0].thumb;
|
||||
console.log("Znaleziono miniaturę w CheapShark dla: " + title);
|
||||
}
|
||||
} catch(e) {}
|
||||
}
|
||||
|
||||
if (thumbUrl) {
|
||||
console.log("Ostatecznie pobieram wybraną okladke z sieci...");
|
||||
const imgRes = $http.send({
|
||||
url: thumbUrl,
|
||||
method: "GET"
|
||||
});
|
||||
|
||||
const safeTitle = title.replace(/[^a-z0-9]/gi, '_').toLowerCase();
|
||||
let ext = ".jpg";
|
||||
const lowerThumbUrl = thumbUrl.toLowerCase();
|
||||
if (lowerThumbUrl.includes(".png")) ext = ".png";
|
||||
else if (lowerThumbUrl.includes(".webp")) ext = ".webp";
|
||||
else if (lowerThumbUrl.includes(".gif")) ext = ".gif";
|
||||
|
||||
const file = $filesystem.fileFromBytes(imgRes.raw, `${safeTitle}_cover${ext}`);
|
||||
|
||||
// Używamy formularza (RecordUpsertForm) aby wgrać do serwera i zlinkować
|
||||
const form = new RecordUpsertForm($app, e.record);
|
||||
form.addFiles("cover_image", file);
|
||||
form.submit();
|
||||
|
||||
console.log("Pomyslnie dodano okładkę dla: " + title);
|
||||
} else {
|
||||
console.log("Niestety w żadnym źródle (Steam, Wikipedia, CheapShark) nie znaleziono danych dla: " + title);
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.error("Generalny błąd podczas pobierania okladki: ", err);
|
||||
}
|
||||
}, "games");
|
||||
@@ -55,25 +55,6 @@ migrate((db) => {
|
||||
const dao = new Dao(db);
|
||||
dao.saveCollection(collection);
|
||||
|
||||
// Add examples:
|
||||
|
||||
const examples = [
|
||||
"Wiedźmin 3: Dziki Gon",
|
||||
"Cyberpunk 2077",
|
||||
"Red Dead Redemption 2",
|
||||
"Baldur's Gate 3",
|
||||
"GTA V",
|
||||
"Minecraft",
|
||||
"Hades",
|
||||
"Elden Ring"
|
||||
];
|
||||
|
||||
for (const title of examples) {
|
||||
const record = new Record(collection);
|
||||
record.set("title", title);
|
||||
dao.saveRecord(record);
|
||||
}
|
||||
|
||||
}, (db) => {
|
||||
const dao = new Dao(db);
|
||||
const collection = dao.findCollectionByNameOrId("games1234567890");
|
||||
|
||||
@@ -6,11 +6,13 @@ services:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
target: pb_base
|
||||
command: ["/pocketbase", "serve", "--http=0.0.0.0:8090", "--hooksDir=/pb_hooks"]
|
||||
ports:
|
||||
- "8090:8090"
|
||||
volumes:
|
||||
- ./pb_data:/pb_data
|
||||
- ./backend/pb_migrations:/pb_migrations
|
||||
- ./backend/pb_hooks:/pb_hooks
|
||||
restart: unless-stopped
|
||||
|
||||
frontend:
|
||||
|
||||
+5
-2
@@ -84,8 +84,11 @@ async function handleDraw() {
|
||||
: null;
|
||||
|
||||
card.innerHTML = `
|
||||
<div class="game-cover">
|
||||
${imageUrl ? `<img src="${imageUrl}" alt="Okładka" width="100%" height="100%" style="object-fit:cover;">` : '<span>Brak Okładki</span>'}
|
||||
<div class="game-cover" style="position: relative; overflow: hidden;">
|
||||
${imageUrl ? `
|
||||
<img src="${imageUrl}" style="position: absolute; width: 100%; height: 100%; object-fit: cover; filter: blur(12px) brightness(0.4); transform: scale(1.2); z-index: 1;">
|
||||
<img src="${imageUrl}" alt="Okładka" style="position: relative; width: 100%; height: 100%; object-fit: contain; z-index: 2; padding: 15px;">
|
||||
` : '<span style="z-index: 2; position: relative;">Brak Okładki</span>'}
|
||||
</div>
|
||||
<div class="game-info">
|
||||
<div class="game-title">${game.title}</div>
|
||||
|
||||
Reference in new issue
Block a user