import { Developer, MetadataSource, Publisher } from "@prisma/client"; import { MetadataProvider } from "."; import { GameMetadataSearchResult, _FetchGameMetadataParams, GameMetadata, _FetchPublisherMetadataParams, PublisherMetadata, _FetchDeveloperMetadataParams, DeveloperMetadata, } from "./types"; import axios, { AxiosRequestConfig } from "axios"; import moment from "moment"; import TurndownService from "turndown"; interface GiantBombResponseType { error: "OK" | string; limit: number; offset: number; number_of_page_results: number; number_of_total_results: number; status_code: number; results: T; version: string; } interface GameSearchResult { guid: string; name: string; deck: string; original_release_date?: string; expected_release_year?: number; image?: { icon_url: string; }; } interface GameResult { guid: string; name: string; deck: string; description?: string; developers: Array<{ id: number; name: string }>; publishers: Array<{ id: number; name: string }>; number_of_user_reviews: number; // Doesn't provide an actual rating, so kinda useless image: { icon_url: string; screen_large_url: string; }; images: Array<{ tags: string; // If it's "All Images", art, otherwise screenshot original: string; }>; } interface CompanySearchResult { guid: string; deck: string | null; description: string | null; name: string; website: string | null; image: { icon_url: string; screen_large_url: string; }; } export class GiantBombProvider implements MetadataProvider { private apikey: string; private turndown: TurndownService; constructor() { const apikey = process.env.GIANT_BOMB_API_KEY; if (!apikey) throw new Error("No GIANT_BOMB_API_KEY in environment"); this.apikey = apikey; this.turndown = new TurndownService(); this.turndown.addRule("remove-links", { filter: ["a"], replacement: function (content) { return content; }, }); } private async request( resource: string, url: string, query: { [key: string]: string | Array }, options?: AxiosRequestConfig ) { const queryOptions = { ...query, api_key: this.apikey, format: "json" }; const queryString = Object.entries(queryOptions) .map(([key, value]) => { if (Array.isArray(value)) { return `${key}=${value.map(encodeURIComponent).join(",")}`; } return `${key}=${encodeURIComponent(value)}`; }) .join("&"); const finalURL = `https://www.giantbomb.com/api/${resource}/${url}?${queryString}`; const overlay: AxiosRequestConfig = { url: finalURL, baseURL: "", }; const response = await axios.request>( Object.assign({}, options, overlay) ); return response; } id() { return "giantbomb"; } name() { return "GiantBomb"; } source() { return MetadataSource.GiantBomb; } async search(query: string): Promise { const results = await this.request>("search", "", { query: query, resources: ["game"], }); const mapped = results.data.results.map((result) => { const date = (result.original_release_date ? moment(result.original_release_date).year() : result.expected_release_year) ?? 0; const metadata: GameMetadataSearchResult = { id: result.guid, name: result.name, icon: result.image?.icon_url ?? "", description: result.deck, year: date, }; return metadata; }); return mapped; } async fetchGame({ id, publisher, developer, createObject, }: _FetchGameMetadataParams): Promise { const result = await this.request("game", id, {}); const gameData = result.data.results; const longDescription = gameData.description ? this.turndown.turndown(gameData.description) : gameData.deck; const publishers: Publisher[] = []; for (const pub of gameData.publishers) { publishers.push(await publisher(pub.name)); } const developers: Developer[] = []; for (const dev of gameData.developers) { developers.push(await developer(dev.name)); } const icon = createObject(gameData.image.icon_url); const banner = createObject(gameData.image.screen_large_url); const imageURLs: string[] = gameData.images.map((e) => e.original); const images = [banner, ...imageURLs.map(createObject)]; const metadata: GameMetadata = { id: gameData.guid, name: gameData.name, shortDescription: gameData.deck, description: longDescription, reviewCount: 0, reviewRating: 0, publishers, developers, icon, bannerId: banner, coverId: images[1], images, }; return metadata; } async fetchPublisher({ query, createObject, }: _FetchPublisherMetadataParams): Promise { const results = await this.request>( "search", "", { query, resources: "company" } ); // Find the right entry const company = results.data.results.find((e) => e.name == query) ?? results.data.results.at(0); if (!company) throw new Error(`No results for "${query}"`); const longDescription = company.description ? this.turndown.turndown(company.description) : company.deck; const metadata: PublisherMetadata = { id: company.guid, name: company.name, shortDescription: company.deck ?? "", description: longDescription ?? "", website: company.website ?? "", logo: createObject(company.image.icon_url), banner: createObject(company.image.screen_large_url), }; return metadata; } async fetchDeveloper( params: _FetchDeveloperMetadataParams ): Promise { return await this.fetchPublisher(params); } }