metacritic-ts 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,4 +1,5 @@
1
1
  # metacritic-ts
2
+ [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/Deadlock-too/metacritic-ts)
2
3
  [![GitHub](https://img.shields.io/github/license/Deadlock-too/metacritic-ts)](https://github.com/Deadlock-too/metacritic-ts)
3
4
  [![npm](https://img.shields.io/npm/v/metacritic-ts)](https://www.npmjs.com/package/metacritic-ts)
4
5
  [![npm](https://img.shields.io/npm/dt/metacritic-ts)](https://www.npmjs.com/package/metacritic-ts)
package/dist/index.cjs CHANGED
@@ -1,9 +1,7 @@
1
1
  "use strict";
2
- var __create = Object.create;
3
2
  var __defProp = Object.defineProperty;
4
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
6
  var __export = (target, all) => {
9
7
  for (var name in all)
@@ -17,14 +15,6 @@ var __copyProps = (to, from, except, desc) => {
17
15
  }
18
16
  return to;
19
17
  };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- // If the importer is in node compatibility mode or this is not an ESM
22
- // file that has been converted to a CommonJS file using a Babel-
23
- // compatible transform (i.e. "__esModule" has not been set), then set
24
- // "default" to the CommonJS "module.exports" for node compatibility.
25
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
- mod
27
- ));
28
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
19
 
30
20
  // src/index.ts
@@ -142,9 +132,6 @@ function mapDetailEntry(data, must) {
142
132
  };
143
133
  }
144
134
 
145
- // src/lib/service.ts
146
- var import_user_agents = __toESM(require("user-agents"), 1);
147
-
148
135
  // src/lib/types.ts
149
136
  var RecordType = /* @__PURE__ */ ((RecordType2) => {
150
137
  RecordType2[RecordType2["TVShow"] = 1] = "TVShow";
@@ -273,7 +260,7 @@ var _MetacriticService = class _MetacriticService {
273
260
  };
274
261
  }
275
262
  static getMinimalRequestHeaders() {
276
- const userAgent = new import_user_agents.default();
263
+ const userAgent = _MetacriticService.USER_AGENTS[Math.floor(Math.random() * _MetacriticService.USER_AGENTS.length)];
277
264
  return {
278
265
  "User-Agent": userAgent.toString()
279
266
  };
@@ -311,6 +298,13 @@ _MetacriticService.REFERER_HEADER = "https://www.metacritic.com/";
311
298
  _MetacriticService.HOMEPAGE_URL = "https://www.metacritic.com/";
312
299
  _MetacriticService.BASE_URL = "https://backend.metacritic.com/composer/metacritic/pages/";
313
300
  _MetacriticService.SEARCH_URL = _MetacriticService.BASE_URL + "search/";
301
+ _MetacriticService.USER_AGENTS = [
302
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
303
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
304
+ "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
305
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:127.0) Gecko/20100101 Firefox/127.0",
306
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Safari/605.1.15"
307
+ ];
314
308
  var MetacriticService = _MetacriticService;
315
309
  // Annotate the CommonJS export names for ESM import in node:
316
310
  0 && (module.exports = {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/lib/utils.ts","../src/lib/parser.ts","../src/lib/service.ts","../src/lib/types.ts"],"sourcesContent":["export { MetacriticService } from './lib/service'\r\nexport * from './lib/types'\r\n","export function getSimilarity(a: string, b: string): number {\r\n if (a === b) return 1\r\n if (!a || !b) return 0\r\n\r\n const str1 = a.toLowerCase()\r\n const str2 = b.toLowerCase()\r\n const distance = levenshteinDistance(str1, str2)\r\n const maxLength = Math.max(str1.length, str2.length)\r\n return (maxLength - distance) / maxLength\r\n}\r\n\r\nfunction levenshteinDistance(a: string, b: string): number {\r\n const matrix: number[][] = []\r\n for (let i = 0; i <= b.length; i++) {\r\n matrix[i] = [i]\r\n }\r\n for (let j = 0; j <= a.length; j++) {\r\n matrix[0][j] = j\r\n }\r\n for (let i = 1; i <= b.length; i++) {\r\n for (let j = 1; j <= a.length; j++) {\r\n if (b.charAt(i - 1) === a.charAt(j - 1)) {\r\n matrix[i][j] = matrix[i - 1][j - 1]\r\n } else {\r\n matrix[i][j] = Math.min(\r\n matrix[i - 1][j - 1] + 1,\r\n Math.min(matrix[i][j - 1] + 1, matrix[i - 1][j] + 1)\r\n )\r\n }\r\n }\r\n }\r\n return matrix[b.length][a.length]\r\n}\r\n","import { getSimilarity } from './utils'\r\nimport { MetacriticEntry, RecordType, MetacriticSearchEntry } from './types'\r\n\r\nexport function parseSearchJsonResult(jsonString: string, searchKey: string, minSimilarity: number, sortBySimilarity: boolean = true): MetacriticSearchEntry[] {\r\n try {\r\n const parsedData = JSON.parse(jsonString)\r\n const entries: MetacriticSearchEntry[] = []\r\n\r\n const itemsList = parsedData.components.find((component: any) => component.meta.componentName === 'search').data.items\r\n for (const item of itemsList) {\r\n const entry = mapSearchEntry(item, searchKey)\r\n\r\n if (entry.similarity < minSimilarity) {\r\n continue\r\n }\r\n entries.push(entry)\r\n }\r\n\r\n return sortBySimilarity ? entries.sort((a, b) => b.similarity - a.similarity) : entries\r\n } catch (error) {\r\n console.error('Error parsing JSON:', error)\r\n return []\r\n }\r\n}\r\n\r\nexport function parseDetailJsonResult(jsonString: string, must: boolean): MetacriticEntry | null {\r\n try {\r\n const parsedData = JSON.parse(jsonString)\r\n return mapDetailEntry(parsedData, must)\r\n } catch (error) {\r\n console.error('Error parsing JSON:', error)\r\n return null\r\n }\r\n}\r\n\r\nfunction mapSearchEntry(data: any, searchKey: string): MetacriticSearchEntry {\r\n return {\r\n id: data.id,\r\n recordType: data.typeId as RecordType,\r\n title: data.title,\r\n slug: data.slug,\r\n criticScore: data.criticScoreSummary.score,\r\n must: data.mustSee || data.mustWatch || data.mustPlay,\r\n similarity: getSimilarity(data.title, searchKey)\r\n }\r\n}\r\n\r\nfunction mapDetailEntry(data: any, must: boolean): MetacriticEntry {\r\n const product = data.components.find((component: any) => component.meta.componentName === 'product').data.item\r\n const criticScoreSummary = data.components.find((component: any) => component.meta.componentName === 'critic-score-summary').data.item\r\n const userScoreSummary = data.components.find((component: any) => component.meta.componentName === 'user-score-summary').data.item\r\n\r\n return {\r\n id: product.id,\r\n recordType: product.typeId as RecordType,\r\n title: product.title,\r\n slug: product.slug,\r\n must: must,\r\n userScore: {\r\n score: userScoreSummary.score,\r\n maxScore: userScoreSummary.max,\r\n sentiment: userScoreSummary.sentiment,\r\n count: {\r\n positive: userScoreSummary.positiveCount,\r\n neutral: userScoreSummary.neutralCount,\r\n negative: userScoreSummary.negativeCount,\r\n total: userScoreSummary.reviewCount,\r\n }\r\n },\r\n criticScore: {\r\n score: criticScoreSummary.score,\r\n maxScore: criticScoreSummary.max,\r\n sentiment: criticScoreSummary.sentiment,\r\n count: {\r\n positive: criticScoreSummary.positiveCount,\r\n neutral: criticScoreSummary.neutralCount,\r\n negative: criticScoreSummary.negativeCount,\r\n total: criticScoreSummary.reviewCount,\r\n }\r\n },\r\n }\r\n}\r\n","import { parseDetailJsonResult, parseSearchJsonResult } from './parser'\r\nimport UserAgent from 'user-agents'\r\nimport { MetacriticEntry, RecordType, MetacriticSearchEntry } from './types'\r\n\r\nclass SearchInformations {\r\n apiKey: string\r\n\r\n constructor(scriptContent: string) {\r\n this.apiKey = this.extractApiFromScript(scriptContent)\r\n }\r\n\r\n extractApiFromScript(scriptContent: any) {\r\n const apiKeyPattern = /apiKey=([^\"&]+)/\r\n let matches = scriptContent.match(apiKeyPattern)\r\n if (matches) {\r\n return matches[1]\r\n }\r\n }\r\n}\r\n\r\nexport class MetacriticService {\r\n private minSimilarity: number\r\n private apiKey?: string\r\n\r\n static REFERER_HEADER = 'https://www.metacritic.com/'\r\n static HOMEPAGE_URL = 'https://www.metacritic.com/'\r\n static BASE_URL = 'https://backend.metacritic.com/composer/metacritic/pages/'\r\n static SEARCH_URL = MetacriticService.BASE_URL + 'search/'\r\n\r\n constructor(minSimilarity: number = 0.5) {\r\n this.minSimilarity = minSimilarity\r\n }\r\n\r\n async search(searchKey: string, recordType?: RecordType, sortBySimilarity: boolean = true): Promise<MetacriticSearchEntry[]> {\r\n if (!searchKey) {\r\n return []\r\n }\r\n\r\n if (!this.apiKey) {\r\n const searchInfo = await MetacriticService.sendApiKeyRetrievalWebsiteRequest()\r\n if (searchInfo) {\r\n this.apiKey = searchInfo.apiKey\r\n } else {\r\n console.error('Failed to retrieve API key')\r\n return []\r\n }\r\n }\r\n\r\n const jsonResult = await MetacriticService.sendSearchWebRequest(searchKey, this.apiKey, recordType)\r\n if (jsonResult) {\r\n return parseSearchJsonResult(jsonResult, searchKey, this.minSimilarity, sortBySimilarity)\r\n }\r\n\r\n return []\r\n }\r\n\r\n async getDetail(searchKey: string, recordType: RecordType, sortBySimilarity: boolean = true): Promise<MetacriticEntry | null> {\r\n if (!searchKey) {\r\n return null\r\n }\r\n\r\n if (!this.apiKey) {\r\n const searchInfo = await MetacriticService.sendApiKeyRetrievalWebsiteRequest()\r\n if (searchInfo) {\r\n this.apiKey = searchInfo.apiKey\r\n } else {\r\n console.error('Failed to retrieve API key')\r\n return null\r\n }\r\n }\r\n\r\n const searchResult = await this.search(searchKey, recordType, sortBySimilarity)\r\n if (searchResult && searchResult.length > 0) {\r\n const detailResult = await MetacriticService.sendDetailWebRequest(searchResult[0].slug, this.apiKey, recordType)\r\n\r\n if (detailResult) {\r\n return parseDetailJsonResult(detailResult, searchResult[0].must)\r\n }\r\n }\r\n\r\n return null\r\n }\r\n\r\n static async sendSearchWebRequest(searchKey: string, apiKey: string, recordType?: RecordType) {\r\n const headers = this.getRequestHeaders()\r\n\r\n const searchUrl = new URL(MetacriticService.SEARCH_URL + encodeURI(searchKey) + '/web')\r\n\r\n searchUrl.searchParams.append('apiKey', apiKey)\r\n\r\n if (recordType) {\r\n searchUrl.searchParams.append('mcoTypeId', recordType.toString())\r\n }\r\n\r\n try {\r\n const response = await fetch(searchUrl, {\r\n headers: headers,\r\n method: 'GET',\r\n signal: AbortSignal.timeout(60000)\r\n })\r\n\r\n if (response.ok) {\r\n return await response.text()\r\n }\r\n } catch (error) {\r\n console.error('Error fetching search results:', error)\r\n }\r\n }\r\n\r\n static async sendDetailWebRequest(slug: string, apiKey: string, recordType: RecordType) {\r\n const headers = this.getRequestHeaders()\r\n\r\n let baseUrl = MetacriticService.BASE_URL\r\n switch (recordType) {\r\n case RecordType.Game:\r\n baseUrl = baseUrl + 'games/'\r\n break\r\n case RecordType.Movie:\r\n baseUrl = baseUrl + 'movies/'\r\n break\r\n case RecordType.TVShow:\r\n baseUrl = baseUrl + 'shows/'\r\n break\r\n default:\r\n console.error('Unsupported record type for detail request:', recordType)\r\n return\r\n }\r\n\r\n const detailUrl = new URL(baseUrl + encodeURI(slug) + '/web')\r\n\r\n detailUrl.searchParams.append('apiKey', apiKey)\r\n\r\n try {\r\n const response = await fetch(detailUrl, {\r\n headers: headers,\r\n method: 'GET',\r\n signal: AbortSignal.timeout(60000)\r\n })\r\n\r\n if (response.ok) {\r\n return await response.text()\r\n }\r\n } catch (error) {\r\n console.error('Error fetching detail result:', error)\r\n }\r\n }\r\n\r\n static getRequestHeaders() {\r\n const minimumHeaders = this.getMinimalRequestHeaders()\r\n return {\r\n ...minimumHeaders,\r\n 'Content-Type': 'application/json',\r\n 'Accept': '*/*',\r\n 'Referer': MetacriticService.REFERER_HEADER,\r\n }\r\n }\r\n\r\n static getMinimalRequestHeaders() {\r\n const userAgent = new UserAgent()\r\n return {\r\n 'User-Agent': userAgent.toString(),\r\n }\r\n }\r\n\r\n static async sendApiKeyRetrievalWebsiteRequest(): Promise<SearchInformations | null> {\r\n const headers = this.getMinimalRequestHeaders()\r\n try {\r\n const response = await fetch(MetacriticService.HOMEPAGE_URL, {\r\n headers: headers,\r\n signal: AbortSignal.timeout(60000)\r\n })\r\n\r\n if (response.ok) {\r\n const html = await response.text()\r\n\r\n const scriptPattern = /<script[^>]*>([\\s\\S]*?)<\\/script>/g\r\n const scripts: string[] = []\r\n let match\r\n while ((match = scriptPattern.exec(html)) !== null) {\r\n scripts.push(match[1])\r\n }\r\n\r\n for (const script of scripts) {\r\n const searchInfo = new SearchInformations(script)\r\n if (searchInfo.apiKey) {\r\n return searchInfo\r\n }\r\n }\r\n }\r\n\r\n return null\r\n } catch (error) {\r\n console.error('Error fetching website:', error)\r\n return null\r\n }\r\n }\r\n}\r\n","export enum RecordType {\r\n TVShow = 1,\r\n Movie = 2,\r\n Game = 13,\r\n}\r\n\r\nexport type MetacriticSearchEntry = {\r\n id: number\r\n recordType: RecordType\r\n title: string\r\n slug: string\r\n must: boolean\r\n criticScore: number\r\n similarity: number\r\n}\r\n\r\nexport type MetacriticEntry = {\r\n id: number\r\n recordType: RecordType\r\n title: string\r\n slug: string\r\n must: boolean\r\n userScore: Score\r\n criticScore: Score\r\n}\r\n\r\ntype Score = {\r\n score: number\r\n maxScore: number\r\n sentiment: string\r\n count: {\r\n positive: number\r\n neutral: number\r\n negative: number\r\n total: number\r\n }\r\n}\r\n"],"mappings":"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","names":["RecordType","UserAgent"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/lib/utils.ts","../src/lib/parser.ts","../src/lib/types.ts","../src/lib/service.ts"],"sourcesContent":["export { MetacriticService } from './lib/service'\r\nexport * from './lib/types'\r\n","export function getSimilarity(a: string, b: string): number {\r\n if (a === b) return 1\r\n if (!a || !b) return 0\r\n\r\n const str1 = a.toLowerCase()\r\n const str2 = b.toLowerCase()\r\n const distance = levenshteinDistance(str1, str2)\r\n const maxLength = Math.max(str1.length, str2.length)\r\n return (maxLength - distance) / maxLength\r\n}\r\n\r\nfunction levenshteinDistance(a: string, b: string): number {\r\n const matrix: number[][] = []\r\n for (let i = 0; i <= b.length; i++) {\r\n matrix[i] = [i]\r\n }\r\n for (let j = 0; j <= a.length; j++) {\r\n matrix[0][j] = j\r\n }\r\n for (let i = 1; i <= b.length; i++) {\r\n for (let j = 1; j <= a.length; j++) {\r\n if (b.charAt(i - 1) === a.charAt(j - 1)) {\r\n matrix[i][j] = matrix[i - 1][j - 1]\r\n } else {\r\n matrix[i][j] = Math.min(\r\n matrix[i - 1][j - 1] + 1,\r\n Math.min(matrix[i][j - 1] + 1, matrix[i - 1][j] + 1)\r\n )\r\n }\r\n }\r\n }\r\n return matrix[b.length][a.length]\r\n}\r\n","import { getSimilarity } from './utils'\r\nimport { MetacriticEntry, RecordType, MetacriticSearchEntry } from './types'\r\n\r\nexport function parseSearchJsonResult(jsonString: string, searchKey: string, minSimilarity: number, sortBySimilarity: boolean = true): MetacriticSearchEntry[] {\r\n try {\r\n const parsedData = JSON.parse(jsonString)\r\n const entries: MetacriticSearchEntry[] = []\r\n\r\n const itemsList = parsedData.components.find((component: any) => component.meta.componentName === 'search').data.items\r\n for (const item of itemsList) {\r\n const entry = mapSearchEntry(item, searchKey)\r\n\r\n if (entry.similarity < minSimilarity) {\r\n continue\r\n }\r\n entries.push(entry)\r\n }\r\n\r\n return sortBySimilarity ? entries.sort((a, b) => b.similarity - a.similarity) : entries\r\n } catch (error) {\r\n console.error('Error parsing JSON:', error)\r\n return []\r\n }\r\n}\r\n\r\nexport function parseDetailJsonResult(jsonString: string, must: boolean): MetacriticEntry | null {\r\n try {\r\n const parsedData = JSON.parse(jsonString)\r\n return mapDetailEntry(parsedData, must)\r\n } catch (error) {\r\n console.error('Error parsing JSON:', error)\r\n return null\r\n }\r\n}\r\n\r\nfunction mapSearchEntry(data: any, searchKey: string): MetacriticSearchEntry {\r\n return {\r\n id: data.id,\r\n recordType: data.typeId as RecordType,\r\n title: data.title,\r\n slug: data.slug,\r\n criticScore: data.criticScoreSummary.score,\r\n must: data.mustSee || data.mustWatch || data.mustPlay,\r\n similarity: getSimilarity(data.title, searchKey)\r\n }\r\n}\r\n\r\nfunction mapDetailEntry(data: any, must: boolean): MetacriticEntry {\r\n const product = data.components.find((component: any) => component.meta.componentName === 'product').data.item\r\n const criticScoreSummary = data.components.find((component: any) => component.meta.componentName === 'critic-score-summary').data.item\r\n const userScoreSummary = data.components.find((component: any) => component.meta.componentName === 'user-score-summary').data.item\r\n\r\n return {\r\n id: product.id,\r\n recordType: product.typeId as RecordType,\r\n title: product.title,\r\n slug: product.slug,\r\n must: must,\r\n userScore: {\r\n score: userScoreSummary.score,\r\n maxScore: userScoreSummary.max,\r\n sentiment: userScoreSummary.sentiment,\r\n count: {\r\n positive: userScoreSummary.positiveCount,\r\n neutral: userScoreSummary.neutralCount,\r\n negative: userScoreSummary.negativeCount,\r\n total: userScoreSummary.reviewCount,\r\n }\r\n },\r\n criticScore: {\r\n score: criticScoreSummary.score,\r\n maxScore: criticScoreSummary.max,\r\n sentiment: criticScoreSummary.sentiment,\r\n count: {\r\n positive: criticScoreSummary.positiveCount,\r\n neutral: criticScoreSummary.neutralCount,\r\n negative: criticScoreSummary.negativeCount,\r\n total: criticScoreSummary.reviewCount,\r\n }\r\n },\r\n }\r\n}\r\n","export enum RecordType {\r\n TVShow = 1,\r\n Movie = 2,\r\n Game = 13,\r\n}\r\n\r\nexport type MetacriticSearchEntry = {\r\n id: number\r\n recordType: RecordType\r\n title: string\r\n slug: string\r\n must: boolean\r\n criticScore: number\r\n similarity: number\r\n}\r\n\r\nexport type MetacriticEntry = {\r\n id: number\r\n recordType: RecordType\r\n title: string\r\n slug: string\r\n must: boolean\r\n userScore: Score\r\n criticScore: Score\r\n}\r\n\r\nexport type Score = {\r\n score: number\r\n maxScore: number\r\n sentiment: string\r\n count: {\r\n positive: number\r\n neutral: number\r\n negative: number\r\n total: number\r\n }\r\n}\r\n","import { parseDetailJsonResult, parseSearchJsonResult } from './parser'\r\nimport { MetacriticEntry, RecordType, MetacriticSearchEntry } from './types'\r\n\r\nclass SearchInformations {\r\n apiKey: string\r\n\r\n constructor(scriptContent: string) {\r\n this.apiKey = this.extractApiFromScript(scriptContent)\r\n }\r\n\r\n extractApiFromScript(scriptContent: any) {\r\n const apiKeyPattern = /apiKey=([^\"&]+)/\r\n let matches = scriptContent.match(apiKeyPattern)\r\n if (matches) {\r\n return matches[1]\r\n }\r\n }\r\n}\r\n\r\nexport class MetacriticService {\r\n private minSimilarity: number\r\n private apiKey?: string\r\n\r\n static REFERER_HEADER = 'https://www.metacritic.com/'\r\n static HOMEPAGE_URL = 'https://www.metacritic.com/'\r\n static BASE_URL = 'https://backend.metacritic.com/composer/metacritic/pages/'\r\n static SEARCH_URL = MetacriticService.BASE_URL + 'search/'\r\n\r\n static USER_AGENTS = [\r\n 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36',\r\n 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36',\r\n 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36',\r\n 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:127.0) Gecko/20100101 Firefox/127.0',\r\n 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Safari/605.1.15',\r\n ]\r\n\r\n constructor(minSimilarity: number = 0.5) {\r\n this.minSimilarity = minSimilarity\r\n }\r\n\r\n async search(searchKey: string, recordType?: RecordType, sortBySimilarity: boolean = true): Promise<MetacriticSearchEntry[]> {\r\n if (!searchKey) {\r\n return []\r\n }\r\n\r\n if (!this.apiKey) {\r\n const searchInfo = await MetacriticService.sendApiKeyRetrievalWebsiteRequest()\r\n if (searchInfo) {\r\n this.apiKey = searchInfo.apiKey\r\n } else {\r\n console.error('Failed to retrieve API key')\r\n return []\r\n }\r\n }\r\n\r\n const jsonResult = await MetacriticService.sendSearchWebRequest(searchKey, this.apiKey, recordType)\r\n if (jsonResult) {\r\n return parseSearchJsonResult(jsonResult, searchKey, this.minSimilarity, sortBySimilarity)\r\n }\r\n\r\n return []\r\n }\r\n\r\n async getDetail(searchKey: string, recordType: RecordType, sortBySimilarity: boolean = true): Promise<MetacriticEntry | null> {\r\n if (!searchKey) {\r\n return null\r\n }\r\n\r\n if (!this.apiKey) {\r\n const searchInfo = await MetacriticService.sendApiKeyRetrievalWebsiteRequest()\r\n if (searchInfo) {\r\n this.apiKey = searchInfo.apiKey\r\n } else {\r\n console.error('Failed to retrieve API key')\r\n return null\r\n }\r\n }\r\n\r\n const searchResult = await this.search(searchKey, recordType, sortBySimilarity)\r\n if (searchResult && searchResult.length > 0) {\r\n const detailResult = await MetacriticService.sendDetailWebRequest(searchResult[0].slug, this.apiKey, recordType)\r\n\r\n if (detailResult) {\r\n return parseDetailJsonResult(detailResult, searchResult[0].must)\r\n }\r\n }\r\n\r\n return null\r\n }\r\n\r\n static async sendSearchWebRequest(searchKey: string, apiKey: string, recordType?: RecordType) {\r\n const headers = this.getRequestHeaders()\r\n\r\n const searchUrl = new URL(MetacriticService.SEARCH_URL + encodeURI(searchKey) + '/web')\r\n\r\n searchUrl.searchParams.append('apiKey', apiKey)\r\n\r\n if (recordType) {\r\n searchUrl.searchParams.append('mcoTypeId', recordType.toString())\r\n }\r\n\r\n try {\r\n const response = await fetch(searchUrl, {\r\n headers: headers,\r\n method: 'GET',\r\n signal: AbortSignal.timeout(60000)\r\n })\r\n\r\n if (response.ok) {\r\n return await response.text()\r\n }\r\n } catch (error) {\r\n console.error('Error fetching search results:', error)\r\n }\r\n }\r\n\r\n static async sendDetailWebRequest(slug: string, apiKey: string, recordType: RecordType) {\r\n const headers = this.getRequestHeaders()\r\n\r\n let baseUrl = MetacriticService.BASE_URL\r\n switch (recordType) {\r\n case RecordType.Game:\r\n baseUrl = baseUrl + 'games/'\r\n break\r\n case RecordType.Movie:\r\n baseUrl = baseUrl + 'movies/'\r\n break\r\n case RecordType.TVShow:\r\n baseUrl = baseUrl + 'shows/'\r\n break\r\n default:\r\n console.error('Unsupported record type for detail request:', recordType)\r\n return\r\n }\r\n\r\n const detailUrl = new URL(baseUrl + encodeURI(slug) + '/web')\r\n\r\n detailUrl.searchParams.append('apiKey', apiKey)\r\n\r\n try {\r\n const response = await fetch(detailUrl, {\r\n headers: headers,\r\n method: 'GET',\r\n signal: AbortSignal.timeout(60000)\r\n })\r\n\r\n if (response.ok) {\r\n return await response.text()\r\n }\r\n } catch (error) {\r\n console.error('Error fetching detail result:', error)\r\n }\r\n }\r\n\r\n static getRequestHeaders() {\r\n const minimumHeaders = this.getMinimalRequestHeaders()\r\n return {\r\n ...minimumHeaders,\r\n 'Content-Type': 'application/json',\r\n 'Accept': '*/*',\r\n 'Referer': MetacriticService.REFERER_HEADER,\r\n }\r\n }\r\n\r\n static getMinimalRequestHeaders() {\r\n const userAgent = MetacriticService.USER_AGENTS[Math.floor(Math.random() * MetacriticService.USER_AGENTS.length)]\r\n return {\r\n 'User-Agent': userAgent.toString(),\r\n }\r\n }\r\n\r\n static async sendApiKeyRetrievalWebsiteRequest(): Promise<SearchInformations | null> {\r\n const headers = this.getMinimalRequestHeaders()\r\n try {\r\n const response = await fetch(MetacriticService.HOMEPAGE_URL, {\r\n headers: headers,\r\n signal: AbortSignal.timeout(60000)\r\n })\r\n\r\n if (response.ok) {\r\n const html = await response.text()\r\n\r\n const scriptPattern = /<script[^>]*>([\\s\\S]*?)<\\/script>/g\r\n const scripts: string[] = []\r\n let match\r\n while ((match = scriptPattern.exec(html)) !== null) {\r\n scripts.push(match[1])\r\n }\r\n\r\n for (const script of scripts) {\r\n const searchInfo = new SearchInformations(script)\r\n if (searchInfo.apiKey) {\r\n return searchInfo\r\n }\r\n }\r\n }\r\n\r\n return null\r\n } catch (error) {\r\n console.error('Error fetching website:', error)\r\n return null\r\n }\r\n }\r\n}\r\n"],"mappings":"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","names":["RecordType"]}
package/dist/index.d.cts CHANGED
@@ -45,6 +45,7 @@ declare class MetacriticService {
45
45
  static HOMEPAGE_URL: string;
46
46
  static BASE_URL: string;
47
47
  static SEARCH_URL: string;
48
+ static USER_AGENTS: string[];
48
49
  constructor(minSimilarity?: number);
49
50
  search(searchKey: string, recordType?: RecordType, sortBySimilarity?: boolean): Promise<MetacriticSearchEntry[]>;
50
51
  getDetail(searchKey: string, recordType: RecordType, sortBySimilarity?: boolean): Promise<MetacriticEntry | null>;
@@ -62,4 +63,4 @@ declare class MetacriticService {
62
63
  static sendApiKeyRetrievalWebsiteRequest(): Promise<SearchInformations | null>;
63
64
  }
64
65
 
65
- export { type MetacriticEntry, type MetacriticSearchEntry, MetacriticService, RecordType };
66
+ export { type MetacriticEntry, type MetacriticSearchEntry, MetacriticService, RecordType, type Score };
package/dist/index.d.ts CHANGED
@@ -45,6 +45,7 @@ declare class MetacriticService {
45
45
  static HOMEPAGE_URL: string;
46
46
  static BASE_URL: string;
47
47
  static SEARCH_URL: string;
48
+ static USER_AGENTS: string[];
48
49
  constructor(minSimilarity?: number);
49
50
  search(searchKey: string, recordType?: RecordType, sortBySimilarity?: boolean): Promise<MetacriticSearchEntry[]>;
50
51
  getDetail(searchKey: string, recordType: RecordType, sortBySimilarity?: boolean): Promise<MetacriticEntry | null>;
@@ -62,4 +63,4 @@ declare class MetacriticService {
62
63
  static sendApiKeyRetrievalWebsiteRequest(): Promise<SearchInformations | null>;
63
64
  }
64
65
 
65
- export { type MetacriticEntry, type MetacriticSearchEntry, MetacriticService, RecordType };
66
+ export { type MetacriticEntry, type MetacriticSearchEntry, MetacriticService, RecordType, type Score };
package/dist/index.js CHANGED
@@ -105,9 +105,6 @@ function mapDetailEntry(data, must) {
105
105
  };
106
106
  }
107
107
 
108
- // src/lib/service.ts
109
- import UserAgent from "user-agents";
110
-
111
108
  // src/lib/types.ts
112
109
  var RecordType = /* @__PURE__ */ ((RecordType2) => {
113
110
  RecordType2[RecordType2["TVShow"] = 1] = "TVShow";
@@ -236,7 +233,7 @@ var _MetacriticService = class _MetacriticService {
236
233
  };
237
234
  }
238
235
  static getMinimalRequestHeaders() {
239
- const userAgent = new UserAgent();
236
+ const userAgent = _MetacriticService.USER_AGENTS[Math.floor(Math.random() * _MetacriticService.USER_AGENTS.length)];
240
237
  return {
241
238
  "User-Agent": userAgent.toString()
242
239
  };
@@ -274,6 +271,13 @@ _MetacriticService.REFERER_HEADER = "https://www.metacritic.com/";
274
271
  _MetacriticService.HOMEPAGE_URL = "https://www.metacritic.com/";
275
272
  _MetacriticService.BASE_URL = "https://backend.metacritic.com/composer/metacritic/pages/";
276
273
  _MetacriticService.SEARCH_URL = _MetacriticService.BASE_URL + "search/";
274
+ _MetacriticService.USER_AGENTS = [
275
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
276
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
277
+ "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
278
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:127.0) Gecko/20100101 Firefox/127.0",
279
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Safari/605.1.15"
280
+ ];
277
281
  var MetacriticService = _MetacriticService;
278
282
  export {
279
283
  MetacriticService,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/lib/utils.ts","../src/lib/parser.ts","../src/lib/service.ts","../src/lib/types.ts"],"sourcesContent":["export function getSimilarity(a: string, b: string): number {\r\n if (a === b) return 1\r\n if (!a || !b) return 0\r\n\r\n const str1 = a.toLowerCase()\r\n const str2 = b.toLowerCase()\r\n const distance = levenshteinDistance(str1, str2)\r\n const maxLength = Math.max(str1.length, str2.length)\r\n return (maxLength - distance) / maxLength\r\n}\r\n\r\nfunction levenshteinDistance(a: string, b: string): number {\r\n const matrix: number[][] = []\r\n for (let i = 0; i <= b.length; i++) {\r\n matrix[i] = [i]\r\n }\r\n for (let j = 0; j <= a.length; j++) {\r\n matrix[0][j] = j\r\n }\r\n for (let i = 1; i <= b.length; i++) {\r\n for (let j = 1; j <= a.length; j++) {\r\n if (b.charAt(i - 1) === a.charAt(j - 1)) {\r\n matrix[i][j] = matrix[i - 1][j - 1]\r\n } else {\r\n matrix[i][j] = Math.min(\r\n matrix[i - 1][j - 1] + 1,\r\n Math.min(matrix[i][j - 1] + 1, matrix[i - 1][j] + 1)\r\n )\r\n }\r\n }\r\n }\r\n return matrix[b.length][a.length]\r\n}\r\n","import { getSimilarity } from './utils'\r\nimport { MetacriticEntry, RecordType, MetacriticSearchEntry } from './types'\r\n\r\nexport function parseSearchJsonResult(jsonString: string, searchKey: string, minSimilarity: number, sortBySimilarity: boolean = true): MetacriticSearchEntry[] {\r\n try {\r\n const parsedData = JSON.parse(jsonString)\r\n const entries: MetacriticSearchEntry[] = []\r\n\r\n const itemsList = parsedData.components.find((component: any) => component.meta.componentName === 'search').data.items\r\n for (const item of itemsList) {\r\n const entry = mapSearchEntry(item, searchKey)\r\n\r\n if (entry.similarity < minSimilarity) {\r\n continue\r\n }\r\n entries.push(entry)\r\n }\r\n\r\n return sortBySimilarity ? entries.sort((a, b) => b.similarity - a.similarity) : entries\r\n } catch (error) {\r\n console.error('Error parsing JSON:', error)\r\n return []\r\n }\r\n}\r\n\r\nexport function parseDetailJsonResult(jsonString: string, must: boolean): MetacriticEntry | null {\r\n try {\r\n const parsedData = JSON.parse(jsonString)\r\n return mapDetailEntry(parsedData, must)\r\n } catch (error) {\r\n console.error('Error parsing JSON:', error)\r\n return null\r\n }\r\n}\r\n\r\nfunction mapSearchEntry(data: any, searchKey: string): MetacriticSearchEntry {\r\n return {\r\n id: data.id,\r\n recordType: data.typeId as RecordType,\r\n title: data.title,\r\n slug: data.slug,\r\n criticScore: data.criticScoreSummary.score,\r\n must: data.mustSee || data.mustWatch || data.mustPlay,\r\n similarity: getSimilarity(data.title, searchKey)\r\n }\r\n}\r\n\r\nfunction mapDetailEntry(data: any, must: boolean): MetacriticEntry {\r\n const product = data.components.find((component: any) => component.meta.componentName === 'product').data.item\r\n const criticScoreSummary = data.components.find((component: any) => component.meta.componentName === 'critic-score-summary').data.item\r\n const userScoreSummary = data.components.find((component: any) => component.meta.componentName === 'user-score-summary').data.item\r\n\r\n return {\r\n id: product.id,\r\n recordType: product.typeId as RecordType,\r\n title: product.title,\r\n slug: product.slug,\r\n must: must,\r\n userScore: {\r\n score: userScoreSummary.score,\r\n maxScore: userScoreSummary.max,\r\n sentiment: userScoreSummary.sentiment,\r\n count: {\r\n positive: userScoreSummary.positiveCount,\r\n neutral: userScoreSummary.neutralCount,\r\n negative: userScoreSummary.negativeCount,\r\n total: userScoreSummary.reviewCount,\r\n }\r\n },\r\n criticScore: {\r\n score: criticScoreSummary.score,\r\n maxScore: criticScoreSummary.max,\r\n sentiment: criticScoreSummary.sentiment,\r\n count: {\r\n positive: criticScoreSummary.positiveCount,\r\n neutral: criticScoreSummary.neutralCount,\r\n negative: criticScoreSummary.negativeCount,\r\n total: criticScoreSummary.reviewCount,\r\n }\r\n },\r\n }\r\n}\r\n","import { parseDetailJsonResult, parseSearchJsonResult } from './parser'\r\nimport UserAgent from 'user-agents'\r\nimport { MetacriticEntry, RecordType, MetacriticSearchEntry } from './types'\r\n\r\nclass SearchInformations {\r\n apiKey: string\r\n\r\n constructor(scriptContent: string) {\r\n this.apiKey = this.extractApiFromScript(scriptContent)\r\n }\r\n\r\n extractApiFromScript(scriptContent: any) {\r\n const apiKeyPattern = /apiKey=([^\"&]+)/\r\n let matches = scriptContent.match(apiKeyPattern)\r\n if (matches) {\r\n return matches[1]\r\n }\r\n }\r\n}\r\n\r\nexport class MetacriticService {\r\n private minSimilarity: number\r\n private apiKey?: string\r\n\r\n static REFERER_HEADER = 'https://www.metacritic.com/'\r\n static HOMEPAGE_URL = 'https://www.metacritic.com/'\r\n static BASE_URL = 'https://backend.metacritic.com/composer/metacritic/pages/'\r\n static SEARCH_URL = MetacriticService.BASE_URL + 'search/'\r\n\r\n constructor(minSimilarity: number = 0.5) {\r\n this.minSimilarity = minSimilarity\r\n }\r\n\r\n async search(searchKey: string, recordType?: RecordType, sortBySimilarity: boolean = true): Promise<MetacriticSearchEntry[]> {\r\n if (!searchKey) {\r\n return []\r\n }\r\n\r\n if (!this.apiKey) {\r\n const searchInfo = await MetacriticService.sendApiKeyRetrievalWebsiteRequest()\r\n if (searchInfo) {\r\n this.apiKey = searchInfo.apiKey\r\n } else {\r\n console.error('Failed to retrieve API key')\r\n return []\r\n }\r\n }\r\n\r\n const jsonResult = await MetacriticService.sendSearchWebRequest(searchKey, this.apiKey, recordType)\r\n if (jsonResult) {\r\n return parseSearchJsonResult(jsonResult, searchKey, this.minSimilarity, sortBySimilarity)\r\n }\r\n\r\n return []\r\n }\r\n\r\n async getDetail(searchKey: string, recordType: RecordType, sortBySimilarity: boolean = true): Promise<MetacriticEntry | null> {\r\n if (!searchKey) {\r\n return null\r\n }\r\n\r\n if (!this.apiKey) {\r\n const searchInfo = await MetacriticService.sendApiKeyRetrievalWebsiteRequest()\r\n if (searchInfo) {\r\n this.apiKey = searchInfo.apiKey\r\n } else {\r\n console.error('Failed to retrieve API key')\r\n return null\r\n }\r\n }\r\n\r\n const searchResult = await this.search(searchKey, recordType, sortBySimilarity)\r\n if (searchResult && searchResult.length > 0) {\r\n const detailResult = await MetacriticService.sendDetailWebRequest(searchResult[0].slug, this.apiKey, recordType)\r\n\r\n if (detailResult) {\r\n return parseDetailJsonResult(detailResult, searchResult[0].must)\r\n }\r\n }\r\n\r\n return null\r\n }\r\n\r\n static async sendSearchWebRequest(searchKey: string, apiKey: string, recordType?: RecordType) {\r\n const headers = this.getRequestHeaders()\r\n\r\n const searchUrl = new URL(MetacriticService.SEARCH_URL + encodeURI(searchKey) + '/web')\r\n\r\n searchUrl.searchParams.append('apiKey', apiKey)\r\n\r\n if (recordType) {\r\n searchUrl.searchParams.append('mcoTypeId', recordType.toString())\r\n }\r\n\r\n try {\r\n const response = await fetch(searchUrl, {\r\n headers: headers,\r\n method: 'GET',\r\n signal: AbortSignal.timeout(60000)\r\n })\r\n\r\n if (response.ok) {\r\n return await response.text()\r\n }\r\n } catch (error) {\r\n console.error('Error fetching search results:', error)\r\n }\r\n }\r\n\r\n static async sendDetailWebRequest(slug: string, apiKey: string, recordType: RecordType) {\r\n const headers = this.getRequestHeaders()\r\n\r\n let baseUrl = MetacriticService.BASE_URL\r\n switch (recordType) {\r\n case RecordType.Game:\r\n baseUrl = baseUrl + 'games/'\r\n break\r\n case RecordType.Movie:\r\n baseUrl = baseUrl + 'movies/'\r\n break\r\n case RecordType.TVShow:\r\n baseUrl = baseUrl + 'shows/'\r\n break\r\n default:\r\n console.error('Unsupported record type for detail request:', recordType)\r\n return\r\n }\r\n\r\n const detailUrl = new URL(baseUrl + encodeURI(slug) + '/web')\r\n\r\n detailUrl.searchParams.append('apiKey', apiKey)\r\n\r\n try {\r\n const response = await fetch(detailUrl, {\r\n headers: headers,\r\n method: 'GET',\r\n signal: AbortSignal.timeout(60000)\r\n })\r\n\r\n if (response.ok) {\r\n return await response.text()\r\n }\r\n } catch (error) {\r\n console.error('Error fetching detail result:', error)\r\n }\r\n }\r\n\r\n static getRequestHeaders() {\r\n const minimumHeaders = this.getMinimalRequestHeaders()\r\n return {\r\n ...minimumHeaders,\r\n 'Content-Type': 'application/json',\r\n 'Accept': '*/*',\r\n 'Referer': MetacriticService.REFERER_HEADER,\r\n }\r\n }\r\n\r\n static getMinimalRequestHeaders() {\r\n const userAgent = new UserAgent()\r\n return {\r\n 'User-Agent': userAgent.toString(),\r\n }\r\n }\r\n\r\n static async sendApiKeyRetrievalWebsiteRequest(): Promise<SearchInformations | null> {\r\n const headers = this.getMinimalRequestHeaders()\r\n try {\r\n const response = await fetch(MetacriticService.HOMEPAGE_URL, {\r\n headers: headers,\r\n signal: AbortSignal.timeout(60000)\r\n })\r\n\r\n if (response.ok) {\r\n const html = await response.text()\r\n\r\n const scriptPattern = /<script[^>]*>([\\s\\S]*?)<\\/script>/g\r\n const scripts: string[] = []\r\n let match\r\n while ((match = scriptPattern.exec(html)) !== null) {\r\n scripts.push(match[1])\r\n }\r\n\r\n for (const script of scripts) {\r\n const searchInfo = new SearchInformations(script)\r\n if (searchInfo.apiKey) {\r\n return searchInfo\r\n }\r\n }\r\n }\r\n\r\n return null\r\n } catch (error) {\r\n console.error('Error fetching website:', error)\r\n return null\r\n }\r\n }\r\n}\r\n","export enum RecordType {\r\n TVShow = 1,\r\n Movie = 2,\r\n Game = 13,\r\n}\r\n\r\nexport type MetacriticSearchEntry = {\r\n id: number\r\n recordType: RecordType\r\n title: string\r\n slug: string\r\n must: boolean\r\n criticScore: number\r\n similarity: number\r\n}\r\n\r\nexport type MetacriticEntry = {\r\n id: number\r\n recordType: RecordType\r\n title: string\r\n slug: string\r\n must: boolean\r\n userScore: Score\r\n criticScore: Score\r\n}\r\n\r\ntype Score = {\r\n score: number\r\n maxScore: number\r\n sentiment: string\r\n count: {\r\n positive: number\r\n neutral: number\r\n negative: number\r\n total: number\r\n }\r\n}\r\n"],"mappings":"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","names":["RecordType"]}
1
+ {"version":3,"sources":["../src/lib/utils.ts","../src/lib/parser.ts","../src/lib/types.ts","../src/lib/service.ts"],"sourcesContent":["export function getSimilarity(a: string, b: string): number {\r\n if (a === b) return 1\r\n if (!a || !b) return 0\r\n\r\n const str1 = a.toLowerCase()\r\n const str2 = b.toLowerCase()\r\n const distance = levenshteinDistance(str1, str2)\r\n const maxLength = Math.max(str1.length, str2.length)\r\n return (maxLength - distance) / maxLength\r\n}\r\n\r\nfunction levenshteinDistance(a: string, b: string): number {\r\n const matrix: number[][] = []\r\n for (let i = 0; i <= b.length; i++) {\r\n matrix[i] = [i]\r\n }\r\n for (let j = 0; j <= a.length; j++) {\r\n matrix[0][j] = j\r\n }\r\n for (let i = 1; i <= b.length; i++) {\r\n for (let j = 1; j <= a.length; j++) {\r\n if (b.charAt(i - 1) === a.charAt(j - 1)) {\r\n matrix[i][j] = matrix[i - 1][j - 1]\r\n } else {\r\n matrix[i][j] = Math.min(\r\n matrix[i - 1][j - 1] + 1,\r\n Math.min(matrix[i][j - 1] + 1, matrix[i - 1][j] + 1)\r\n )\r\n }\r\n }\r\n }\r\n return matrix[b.length][a.length]\r\n}\r\n","import { getSimilarity } from './utils'\r\nimport { MetacriticEntry, RecordType, MetacriticSearchEntry } from './types'\r\n\r\nexport function parseSearchJsonResult(jsonString: string, searchKey: string, minSimilarity: number, sortBySimilarity: boolean = true): MetacriticSearchEntry[] {\r\n try {\r\n const parsedData = JSON.parse(jsonString)\r\n const entries: MetacriticSearchEntry[] = []\r\n\r\n const itemsList = parsedData.components.find((component: any) => component.meta.componentName === 'search').data.items\r\n for (const item of itemsList) {\r\n const entry = mapSearchEntry(item, searchKey)\r\n\r\n if (entry.similarity < minSimilarity) {\r\n continue\r\n }\r\n entries.push(entry)\r\n }\r\n\r\n return sortBySimilarity ? entries.sort((a, b) => b.similarity - a.similarity) : entries\r\n } catch (error) {\r\n console.error('Error parsing JSON:', error)\r\n return []\r\n }\r\n}\r\n\r\nexport function parseDetailJsonResult(jsonString: string, must: boolean): MetacriticEntry | null {\r\n try {\r\n const parsedData = JSON.parse(jsonString)\r\n return mapDetailEntry(parsedData, must)\r\n } catch (error) {\r\n console.error('Error parsing JSON:', error)\r\n return null\r\n }\r\n}\r\n\r\nfunction mapSearchEntry(data: any, searchKey: string): MetacriticSearchEntry {\r\n return {\r\n id: data.id,\r\n recordType: data.typeId as RecordType,\r\n title: data.title,\r\n slug: data.slug,\r\n criticScore: data.criticScoreSummary.score,\r\n must: data.mustSee || data.mustWatch || data.mustPlay,\r\n similarity: getSimilarity(data.title, searchKey)\r\n }\r\n}\r\n\r\nfunction mapDetailEntry(data: any, must: boolean): MetacriticEntry {\r\n const product = data.components.find((component: any) => component.meta.componentName === 'product').data.item\r\n const criticScoreSummary = data.components.find((component: any) => component.meta.componentName === 'critic-score-summary').data.item\r\n const userScoreSummary = data.components.find((component: any) => component.meta.componentName === 'user-score-summary').data.item\r\n\r\n return {\r\n id: product.id,\r\n recordType: product.typeId as RecordType,\r\n title: product.title,\r\n slug: product.slug,\r\n must: must,\r\n userScore: {\r\n score: userScoreSummary.score,\r\n maxScore: userScoreSummary.max,\r\n sentiment: userScoreSummary.sentiment,\r\n count: {\r\n positive: userScoreSummary.positiveCount,\r\n neutral: userScoreSummary.neutralCount,\r\n negative: userScoreSummary.negativeCount,\r\n total: userScoreSummary.reviewCount,\r\n }\r\n },\r\n criticScore: {\r\n score: criticScoreSummary.score,\r\n maxScore: criticScoreSummary.max,\r\n sentiment: criticScoreSummary.sentiment,\r\n count: {\r\n positive: criticScoreSummary.positiveCount,\r\n neutral: criticScoreSummary.neutralCount,\r\n negative: criticScoreSummary.negativeCount,\r\n total: criticScoreSummary.reviewCount,\r\n }\r\n },\r\n }\r\n}\r\n","export enum RecordType {\r\n TVShow = 1,\r\n Movie = 2,\r\n Game = 13,\r\n}\r\n\r\nexport type MetacriticSearchEntry = {\r\n id: number\r\n recordType: RecordType\r\n title: string\r\n slug: string\r\n must: boolean\r\n criticScore: number\r\n similarity: number\r\n}\r\n\r\nexport type MetacriticEntry = {\r\n id: number\r\n recordType: RecordType\r\n title: string\r\n slug: string\r\n must: boolean\r\n userScore: Score\r\n criticScore: Score\r\n}\r\n\r\nexport type Score = {\r\n score: number\r\n maxScore: number\r\n sentiment: string\r\n count: {\r\n positive: number\r\n neutral: number\r\n negative: number\r\n total: number\r\n }\r\n}\r\n","import { parseDetailJsonResult, parseSearchJsonResult } from './parser'\r\nimport { MetacriticEntry, RecordType, MetacriticSearchEntry } from './types'\r\n\r\nclass SearchInformations {\r\n apiKey: string\r\n\r\n constructor(scriptContent: string) {\r\n this.apiKey = this.extractApiFromScript(scriptContent)\r\n }\r\n\r\n extractApiFromScript(scriptContent: any) {\r\n const apiKeyPattern = /apiKey=([^\"&]+)/\r\n let matches = scriptContent.match(apiKeyPattern)\r\n if (matches) {\r\n return matches[1]\r\n }\r\n }\r\n}\r\n\r\nexport class MetacriticService {\r\n private minSimilarity: number\r\n private apiKey?: string\r\n\r\n static REFERER_HEADER = 'https://www.metacritic.com/'\r\n static HOMEPAGE_URL = 'https://www.metacritic.com/'\r\n static BASE_URL = 'https://backend.metacritic.com/composer/metacritic/pages/'\r\n static SEARCH_URL = MetacriticService.BASE_URL + 'search/'\r\n\r\n static USER_AGENTS = [\r\n 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36',\r\n 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36',\r\n 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36',\r\n 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:127.0) Gecko/20100101 Firefox/127.0',\r\n 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Safari/605.1.15',\r\n ]\r\n\r\n constructor(minSimilarity: number = 0.5) {\r\n this.minSimilarity = minSimilarity\r\n }\r\n\r\n async search(searchKey: string, recordType?: RecordType, sortBySimilarity: boolean = true): Promise<MetacriticSearchEntry[]> {\r\n if (!searchKey) {\r\n return []\r\n }\r\n\r\n if (!this.apiKey) {\r\n const searchInfo = await MetacriticService.sendApiKeyRetrievalWebsiteRequest()\r\n if (searchInfo) {\r\n this.apiKey = searchInfo.apiKey\r\n } else {\r\n console.error('Failed to retrieve API key')\r\n return []\r\n }\r\n }\r\n\r\n const jsonResult = await MetacriticService.sendSearchWebRequest(searchKey, this.apiKey, recordType)\r\n if (jsonResult) {\r\n return parseSearchJsonResult(jsonResult, searchKey, this.minSimilarity, sortBySimilarity)\r\n }\r\n\r\n return []\r\n }\r\n\r\n async getDetail(searchKey: string, recordType: RecordType, sortBySimilarity: boolean = true): Promise<MetacriticEntry | null> {\r\n if (!searchKey) {\r\n return null\r\n }\r\n\r\n if (!this.apiKey) {\r\n const searchInfo = await MetacriticService.sendApiKeyRetrievalWebsiteRequest()\r\n if (searchInfo) {\r\n this.apiKey = searchInfo.apiKey\r\n } else {\r\n console.error('Failed to retrieve API key')\r\n return null\r\n }\r\n }\r\n\r\n const searchResult = await this.search(searchKey, recordType, sortBySimilarity)\r\n if (searchResult && searchResult.length > 0) {\r\n const detailResult = await MetacriticService.sendDetailWebRequest(searchResult[0].slug, this.apiKey, recordType)\r\n\r\n if (detailResult) {\r\n return parseDetailJsonResult(detailResult, searchResult[0].must)\r\n }\r\n }\r\n\r\n return null\r\n }\r\n\r\n static async sendSearchWebRequest(searchKey: string, apiKey: string, recordType?: RecordType) {\r\n const headers = this.getRequestHeaders()\r\n\r\n const searchUrl = new URL(MetacriticService.SEARCH_URL + encodeURI(searchKey) + '/web')\r\n\r\n searchUrl.searchParams.append('apiKey', apiKey)\r\n\r\n if (recordType) {\r\n searchUrl.searchParams.append('mcoTypeId', recordType.toString())\r\n }\r\n\r\n try {\r\n const response = await fetch(searchUrl, {\r\n headers: headers,\r\n method: 'GET',\r\n signal: AbortSignal.timeout(60000)\r\n })\r\n\r\n if (response.ok) {\r\n return await response.text()\r\n }\r\n } catch (error) {\r\n console.error('Error fetching search results:', error)\r\n }\r\n }\r\n\r\n static async sendDetailWebRequest(slug: string, apiKey: string, recordType: RecordType) {\r\n const headers = this.getRequestHeaders()\r\n\r\n let baseUrl = MetacriticService.BASE_URL\r\n switch (recordType) {\r\n case RecordType.Game:\r\n baseUrl = baseUrl + 'games/'\r\n break\r\n case RecordType.Movie:\r\n baseUrl = baseUrl + 'movies/'\r\n break\r\n case RecordType.TVShow:\r\n baseUrl = baseUrl + 'shows/'\r\n break\r\n default:\r\n console.error('Unsupported record type for detail request:', recordType)\r\n return\r\n }\r\n\r\n const detailUrl = new URL(baseUrl + encodeURI(slug) + '/web')\r\n\r\n detailUrl.searchParams.append('apiKey', apiKey)\r\n\r\n try {\r\n const response = await fetch(detailUrl, {\r\n headers: headers,\r\n method: 'GET',\r\n signal: AbortSignal.timeout(60000)\r\n })\r\n\r\n if (response.ok) {\r\n return await response.text()\r\n }\r\n } catch (error) {\r\n console.error('Error fetching detail result:', error)\r\n }\r\n }\r\n\r\n static getRequestHeaders() {\r\n const minimumHeaders = this.getMinimalRequestHeaders()\r\n return {\r\n ...minimumHeaders,\r\n 'Content-Type': 'application/json',\r\n 'Accept': '*/*',\r\n 'Referer': MetacriticService.REFERER_HEADER,\r\n }\r\n }\r\n\r\n static getMinimalRequestHeaders() {\r\n const userAgent = MetacriticService.USER_AGENTS[Math.floor(Math.random() * MetacriticService.USER_AGENTS.length)]\r\n return {\r\n 'User-Agent': userAgent.toString(),\r\n }\r\n }\r\n\r\n static async sendApiKeyRetrievalWebsiteRequest(): Promise<SearchInformations | null> {\r\n const headers = this.getMinimalRequestHeaders()\r\n try {\r\n const response = await fetch(MetacriticService.HOMEPAGE_URL, {\r\n headers: headers,\r\n signal: AbortSignal.timeout(60000)\r\n })\r\n\r\n if (response.ok) {\r\n const html = await response.text()\r\n\r\n const scriptPattern = /<script[^>]*>([\\s\\S]*?)<\\/script>/g\r\n const scripts: string[] = []\r\n let match\r\n while ((match = scriptPattern.exec(html)) !== null) {\r\n scripts.push(match[1])\r\n }\r\n\r\n for (const script of scripts) {\r\n const searchInfo = new SearchInformations(script)\r\n if (searchInfo.apiKey) {\r\n return searchInfo\r\n }\r\n }\r\n }\r\n\r\n return null\r\n } catch (error) {\r\n console.error('Error fetching website:', error)\r\n return null\r\n }\r\n }\r\n}\r\n"],"mappings":"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","names":["RecordType"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metacritic-ts",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "TypeScript library to extrapolate data from Metacritic.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -34,9 +34,6 @@
34
34
  "bugs": {
35
35
  "url": "https://github.com/Deadlock-too/metacritic-ts/issues"
36
36
  },
37
- "dependencies": {
38
- "user-agents": "^1.1.481"
39
- },
40
37
  "devDependencies": {
41
38
  "@types/jest": "^29.5.14",
42
39
  "@types/user-agents": "^1.0.4",