Files
whattoplay/server/gog-backend.mjs

158 lines
4.3 KiB
JavaScript

/**
* GOG Backend - Unofficial GOG API Integration
* Uses Galaxy client credentials (well-known, used by lgogdownloader etc.)
*/
const CLIENT_ID = "46899977096215655";
const CLIENT_SECRET =
"9d85c43b1482497dbbce61f6e4aa173a433796eeae2ca8c5f6129f2dc4de46d9";
const REDIRECT_URI =
"https://embed.gog.com/on_login_success?origin=client";
/**
* Exchange authorization code for access + refresh tokens
* @param {string} code - Auth code from GOG login redirect
* @returns {Promise<{access_token: string, refresh_token: string, user_id: string, expires_in: number}>}
*/
export async function exchangeGogCode(code) {
if (!code) {
throw new Error("Authorization code ist erforderlich");
}
const url = new URL("https://auth.gog.com/token");
url.searchParams.set("client_id", CLIENT_ID);
url.searchParams.set("client_secret", CLIENT_SECRET);
url.searchParams.set("grant_type", "authorization_code");
url.searchParams.set("code", code);
url.searchParams.set("redirect_uri", REDIRECT_URI);
const response = await fetch(url);
if (!response.ok) {
const text = await response.text();
throw new Error(
`GOG Token Exchange Error: ${response.status} ${text}`,
);
}
const data = await response.json();
return {
access_token: data.access_token,
refresh_token: data.refresh_token,
user_id: data.user_id,
expires_in: data.expires_in,
};
}
/**
* Refresh an expired access token
* @param {string} refreshToken
* @returns {Promise<{access_token: string, refresh_token: string, expires_in: number}>}
*/
async function refreshAccessToken(refreshToken) {
const url = new URL("https://auth.gog.com/token");
url.searchParams.set("client_id", CLIENT_ID);
url.searchParams.set("client_secret", CLIENT_SECRET);
url.searchParams.set("grant_type", "refresh_token");
url.searchParams.set("refresh_token", refreshToken);
const response = await fetch(url);
if (!response.ok) {
const text = await response.text();
throw new Error(`GOG Token Refresh Error: ${response.status} ${text}`);
}
const data = await response.json();
return {
access_token: data.access_token,
refresh_token: data.refresh_token,
expires_in: data.expires_in,
};
}
/**
* Fetch all owned games from GOG
* @param {string} accessToken
* @param {string} refreshToken
* @returns {Promise<{games: Array, count: number, newAccessToken?: string, newRefreshToken?: string}>}
*/
export async function fetchGogGames(accessToken, refreshToken) {
if (!accessToken || !refreshToken) {
throw new Error("accessToken und refreshToken sind erforderlich");
}
let token = accessToken;
let newTokens = null;
// Fetch first page to get totalPages
let page = 1;
let totalPages = 1;
const allProducts = [];
while (page <= totalPages) {
const url = `https://embed.gog.com/account/getFilteredProducts?mediaType=1&page=${page}`;
let response = await fetch(url, {
headers: { Authorization: `Bearer ${token}` },
});
// Token expired — try refresh
if (response.status === 401 && !newTokens) {
console.log("[GOG] Token expired, refreshing...");
newTokens = await refreshAccessToken(refreshToken);
token = newTokens.access_token;
response = await fetch(url, {
headers: { Authorization: `Bearer ${token}` },
});
}
if (!response.ok) {
throw new Error(
`GOG API Error: ${response.status} ${response.statusText}`,
);
}
const data = await response.json();
totalPages = data.totalPages || 1;
allProducts.push(...(data.products || []));
page++;
}
// Transform to our Game format, skip products without title
const games = allProducts
.filter((product) => product.title)
.map((product) => ({
id: `gog-${product.id}`,
title: product.title,
source: "gog",
sourceId: String(product.id),
platform: "PC",
url: product.url
? `https://www.gog.com${product.url}`
: undefined,
}));
return {
games,
count: games.length,
...(newTokens && {
newAccessToken: newTokens.access_token,
newRefreshToken: newTokens.refresh_token,
}),
};
}
/**
* Returns the GOG auth URL for the user to open in their browser
*/
export function getGogAuthUrl() {
const url = new URL("https://auth.gog.com/auth");
url.searchParams.set("client_id", CLIENT_ID);
url.searchParams.set("redirect_uri", REDIRECT_URI);
url.searchParams.set("response_type", "code");
url.searchParams.set("layout", "client2");
return url.toString();
}