metacritic-ts
v1.3.0
Published
TypeScript library to extrapolate data from Metacritic.
Maintainers
Readme
metacritic-ts
A TypeScript library for interacting with the Metacritic website API. Easily search for games, movies and tv shows and retrieve their Metacritic ratings.
This library takes inspiration from another library of mine howlongtobeat-ts.
⚠️ Disclaimer: This library is not an official API and is not affiliated nor endorsed with Metacritic.com or Fandom Inc in any way. Please use this library responsibly and do not abuse or overload the Metacritic servers. Use at your own risk.
Features
- Search for games, movies and tv shows on Metacritic
- Retrieve rating data (critic and user scores) for games, movies and tv shows
- Resilient networking: configurable timeouts, retries with backoff,
429handling, an injectablefetchandAbortSignalsupport - Failures carry a machine-readable
kind, so you can tell a Metacritic outage from a change that broke this library - Fully typed, with a discriminated-union result type and zero
consolenoise
Installation
npm install metacritic-tsRequires Node.js 18 or newer (the library uses the global fetch). Ships both ESM and CommonJS builds.
Usage
import { MetacriticService, RecordType } from 'metacritic-ts'
const metacritic = new MetacriticService()
// Search across all record types (games, movies, tv shows).
const results = await metacritic.search('The Last of Us')
if (results.success) {
console.log(results.data) // MetacriticSearchEntry[]
} else {
console.error(results.error)
}
// Restrict to a record type via the options object.
await metacritic.search('Breaking Bad', { recordType: RecordType.TVShow })
// Fetch the full critic/user score breakdown for the best match.
const detail = await metacritic.getDetail('The Last of Us Part II', RecordType.Game)
if (detail.success && detail.data) {
console.log(detail.data.criticScore.score, detail.data.userScore.score)
}Handling failures
Every failure carries an optional kind alongside error. Branch on kind — error is prose for humans and its wording may change between releases.
const detail = await metacritic.getDetail('The Last of Us Part II', RecordType.Game)
if (!detail.success) {
switch (detail.kind) {
case 'notFound':
// Nothing is broken — Metacritic has no matching entry.
break
case 'transport':
case 'timeout':
// Could not reach Metacritic at all — a retry may well succeed.
break
case 'http':
// Metacritic answered with an error status; `status` is set.
console.error(`Metacritic returned ${detail.status}`)
break
case 'parse':
// Metacritic answered, but the response could not be read: the site has
// most likely changed shape. Please open an issue on this repo.
break
case 'aborted':
// Your own AbortSignal fired.
break
case 'input':
// The arguments were rejected — an empty key, or a record type with no
// detail endpoint.
break
}
}| kind | What happened | Where the fix lives |
| ----------- | ---------------------------------------------------------- | ------------------- |
| input | Arguments rejected | your call site |
| transport | The round trip never completed — DNS, refused, reset, TLS | the network |
| timeout | The per-request deadline elapsed | the network |
| aborted | Your AbortSignal fired | your call site |
| http | Metacritic answered with a non-2xx status (see status) | Metacritic |
| parse | The response could not be understood | this library |
| notFound | The search matched nothing, so there is no detail to fetch | nobody |
| unknown | Could not be attributed to any of the above | — |
Note that search itself never reports notFound: a search matching nothing succeeds with an empty array, and searchOne succeeds with null. Only getDetail reports it, because it has no entry to look up.
Configuration
Pass an options object to the constructor (a bare number is still accepted as minSimilarity for backwards compatibility):
import { MetacriticService, consoleLogger } from 'metacritic-ts'
const metacritic = new MetacriticService({
minSimilarity: 0.5, // min similarity threshold (0–1), clamped
timeout: 30_000, // per-request timeout in ms
retries: 2, // retry attempts on transient failures / 429 / 5xx
logger: consoleLogger, // opt in to diagnostic logging (default: silent)
// fetch: myCustomFetch, // inject a custom fetch (proxy, undici agent, …)
})
// Cancel in-flight requests.
const controller = new AbortController()
const promise = metacritic.search('Halo', { signal: controller.signal })
controller.abort()API
MetacriticService
constructor(options?: number | ScraperOptions)—ScraperOptionsextends the HTTP options (timeout,retries,retryDelay,fetch,userAgents,logger) withminSimilarity.search(searchKey, options?): Promise<SearchResult>—optionsis{ recordType?, sortBySimilarity?, signal? }.getDetail(searchKey, recordType, options?): Promise<DetailResult>—optionsis{ sortBySimilarity?, signal? }.
For
getDetail,sortBySimilarity(defaulttrue) is important: withfalse, the first API result may not be the one you are looking for.
SearchResult / DetailResult
Discriminated unions:
type FailureKind = 'input' | 'transport' | 'timeout' | 'aborted' | 'http' | 'parse' | 'notFound' | 'unknown'
type Failure = { success: false; error: string; kind?: FailureKind; status?: number }
type SearchResult = { success: true; data: MetacriticSearchEntry[] } | Failure
type DetailResult = { success: true; data: MetacriticEntry | null } | Failureerror is always present. kind is always set by this library, and status is set whenever kind is 'http'. Both are typed as optional because they come from the shared @deadlock-too/scrape-kit Failure, where they were added without breaking older producers.
RecordType
TVShow, Movie, Game.
MetacriticSearchEntry
id, recordType, title, slug, must, criticScoreValue (the critic score as a number), similarity.
MetacriticEntry
id, recordType, title, slug, must, and criticScore / userScore, each a Score:
type Score = {
score: number
maxScore: number
sentiment: string
count: { positive: number; neutral: number; negative: number; total: number }
}Development
git clone https://github.com/Deadlock-too/metacritic-ts.git
cd metacritic-ts
npm install
npm run build # build with tsup
npm test # unit tests
npm run test:integration # live API tests (hit Metacritic)
npm run test:coverage # unit tests with coverage
npm run lint # eslint
npm run format # prettierReleases are managed with Changesets: run npm run changeset to record a change; the release workflow publishes to npm once the generated version PR is merged.
Issues, Questions & Discussions
If you found a bug, report it as soon as possible creating an issue, the code is not perfect for sure, and I will be happy to fix it. If you need any new feature, or want to discuss the current implementation/features, consider opening a discussion or even propose a change with a Pull Request.
License
This project is licensed under the MIT License - see the LICENSE file for details.
