metacritic-ts 1.0.2 → 1.1.0

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)
@@ -38,8 +39,10 @@ async function search() {
38
39
  const results = await metacriticService.search('The Last of Us');
39
40
 
40
41
  // This will result in a list of games, movies or tv shows
41
- if (results) {
42
- console.log('Search results:', results);
42
+ if (results.success) {
43
+ console.log('Search results:', results.data);
44
+ } else {
45
+ console.error('Search failed:', results.error);
43
46
  }
44
47
  }
45
48
 
@@ -50,16 +53,18 @@ async function getDetail() {
50
53
  const metacriticService = new MetacriticService();
51
54
 
52
55
  // Get the details of a game
53
- const details = await metacriticService.getDetail('The Last of Us Part II', RecordType.GAME);
56
+ const details = await metacriticService.getDetail('The Last of Us Part II', RecordType.Game);
54
57
 
55
58
  // This will result in the details of the game, including the rating
56
- if (details) {
57
- console.log('Game details:', details);
59
+ if (details.success) {
60
+ console.log('Game details:', details.data);
61
+ } else {
62
+ console.error('Detail fetch failed:', details.error);
58
63
  }
59
64
  }
60
65
 
61
66
  await search();
62
- await getDetails();
67
+ await getDetail();
63
68
  ```
64
69
 
65
70
  ## API
@@ -74,12 +79,12 @@ The main service class for interacting with the Metacritic website.
74
79
  - `minSimilarity`: Optional parameter to set the minimum similarity threshold for search results to not be filtered out (Default: 0.5).
75
80
 
76
81
  #### Methods
77
- - `async search(searchKey: string): Promise<MetacriticSearchEntry[]>`: Searches for games, movies or tv shows matching the provided search key.
82
+ - `async search(searchKey: string): Promise<SearchResult>`: Searches for games, movies or tv shows matching the provided search key.
78
83
  - `searchKey`: The title to search for
79
84
  - `recordType`: Optional record type to adjust search behavior, it hasn't a default value and will search for all types.
80
85
  - `sortBySimilarity`: Optional boolean to sort the results by similarity to the search key (Default: true). If set to false, the results will leave to the order returned by the Metacritic API.
81
86
 
82
- - `async getDetail(title: string, recordType: RecordType): Promise<MetacriticEntry | null>`: Retrieves the details of a game, movie or tv show matching the provided title.
87
+ - `async getDetail(title: string, recordType: RecordType): Promise<DetailResult>`: Retrieves the details of a game, movie or tv show matching the provided title.
83
88
  - `title`: The title to search for
84
89
  - `recordType`: The type of record to retrieve (game, movie or tv show)
85
90
  - `sortBySimilarity`: Optional boolean to sort the results by similarity to the search key (Default: true). Pay attention that this parameter is very important for the `getDetail` method, if set to false, the results will leave to the order returned by the Metacritic API and the first result may not be the one you are looking for.
@@ -100,6 +105,12 @@ An interface representing a search entry returned by the MetacriticService.
100
105
  - `criticScore`: The critic score of the record.
101
106
  - `similarity`: A computed value that indicates how similar the record is to the search term.
102
107
 
108
+ ### `SearchResult`
109
+ An interface representing the `search` method result.
110
+ - `success`: Indicates whether the operation succeeded.
111
+ - `data`: The list of `MetacriticSearchEntry`.
112
+ - `error`: Optional error message when `success` is `false`.
113
+
103
114
  ### `MetacriticEntry`
104
115
  An interface representing a record entry returned by the MetacriticService.
105
116
  - `id`: The unique identifier for the record.
@@ -128,6 +139,12 @@ An interface representing a record entry returned by the MetacriticService.
128
139
  - `neutral`: The number of neutral reviews.
129
140
  - `total`: The total number of reviews.
130
141
 
142
+ ### `DetailResult`
143
+ An interface representing the `getDetail` method result.
144
+ - `success`: Indicates whether the operation succeeded.
145
+ - `data`: A `MetacriticEntry` when found, otherwise `null`.
146
+ - `error`: Optional error message when `success` is `false`.
147
+
131
148
  ## Development
132
149
 
133
150
  ### Prerequisites
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";
@@ -172,7 +159,11 @@ var _MetacriticService = class _MetacriticService {
172
159
  }
173
160
  async search(searchKey, recordType, sortBySimilarity = true) {
174
161
  if (!searchKey) {
175
- return [];
162
+ return {
163
+ success: false,
164
+ data: [],
165
+ error: "Search key is required"
166
+ };
176
167
  }
177
168
  if (!this.apiKey) {
178
169
  const searchInfo = await _MetacriticService.sendApiKeyRetrievalWebsiteRequest();
@@ -180,18 +171,42 @@ var _MetacriticService = class _MetacriticService {
180
171
  this.apiKey = searchInfo.apiKey;
181
172
  } else {
182
173
  console.error("Failed to retrieve API key");
183
- return [];
174
+ return {
175
+ success: false,
176
+ data: [],
177
+ error: "Failed to retrieve API key"
178
+ };
184
179
  }
185
180
  }
186
181
  const jsonResult = await _MetacriticService.sendSearchWebRequest(searchKey, this.apiKey, recordType);
187
- if (jsonResult) {
188
- return parseSearchJsonResult(jsonResult, searchKey, this.minSimilarity, sortBySimilarity);
182
+ if (!jsonResult) {
183
+ return {
184
+ success: false,
185
+ data: [],
186
+ error: "Failed to fetch search results"
187
+ };
189
188
  }
190
- return [];
189
+ try {
190
+ JSON.parse(jsonResult);
191
+ } catch (e) {
192
+ return {
193
+ success: false,
194
+ data: [],
195
+ error: "Invalid search response format"
196
+ };
197
+ }
198
+ return {
199
+ success: true,
200
+ data: parseSearchJsonResult(jsonResult, searchKey, this.minSimilarity, sortBySimilarity)
201
+ };
191
202
  }
192
203
  async getDetail(searchKey, recordType, sortBySimilarity = true) {
193
204
  if (!searchKey) {
194
- return null;
205
+ return {
206
+ success: false,
207
+ data: null,
208
+ error: "Search key is required"
209
+ };
195
210
  }
196
211
  if (!this.apiKey) {
197
212
  const searchInfo = await _MetacriticService.sendApiKeyRetrievalWebsiteRequest();
@@ -199,17 +214,57 @@ var _MetacriticService = class _MetacriticService {
199
214
  this.apiKey = searchInfo.apiKey;
200
215
  } else {
201
216
  console.error("Failed to retrieve API key");
202
- return null;
217
+ return {
218
+ success: false,
219
+ data: null,
220
+ error: "Failed to retrieve API key"
221
+ };
203
222
  }
204
223
  }
205
224
  const searchResult = await this.search(searchKey, recordType, sortBySimilarity);
206
- if (searchResult && searchResult.length > 0) {
207
- const detailResult = await _MetacriticService.sendDetailWebRequest(searchResult[0].slug, this.apiKey, recordType);
208
- if (detailResult) {
209
- return parseDetailJsonResult(detailResult, searchResult[0].must);
210
- }
225
+ if (!searchResult.success) {
226
+ return {
227
+ success: false,
228
+ data: null,
229
+ error: searchResult.error || "Failed to search detail entry"
230
+ };
211
231
  }
212
- return null;
232
+ if (searchResult.data.length === 0) {
233
+ return {
234
+ success: false,
235
+ data: null,
236
+ error: "No matching entry found"
237
+ };
238
+ }
239
+ const detailResult = await _MetacriticService.sendDetailWebRequest(searchResult.data[0].slug, this.apiKey, recordType);
240
+ if (!detailResult) {
241
+ return {
242
+ success: false,
243
+ data: null,
244
+ error: "Failed to fetch detail result"
245
+ };
246
+ }
247
+ try {
248
+ JSON.parse(detailResult);
249
+ } catch (e) {
250
+ return {
251
+ success: false,
252
+ data: null,
253
+ error: "Invalid detail response format"
254
+ };
255
+ }
256
+ const parsedDetail = parseDetailJsonResult(detailResult, searchResult.data[0].must);
257
+ if (!parsedDetail) {
258
+ return {
259
+ success: false,
260
+ data: null,
261
+ error: "Invalid detail response format"
262
+ };
263
+ }
264
+ return {
265
+ success: true,
266
+ data: parsedDetail
267
+ };
213
268
  }
214
269
  static async sendSearchWebRequest(searchKey, apiKey, recordType) {
215
270
  const headers = this.getRequestHeaders();
@@ -273,7 +328,7 @@ var _MetacriticService = class _MetacriticService {
273
328
  };
274
329
  }
275
330
  static getMinimalRequestHeaders() {
276
- const userAgent = new import_user_agents.default();
331
+ const userAgent = _MetacriticService.USER_AGENTS[Math.floor(Math.random() * _MetacriticService.USER_AGENTS.length)];
277
332
  return {
278
333
  "User-Agent": userAgent.toString()
279
334
  };
@@ -311,6 +366,13 @@ _MetacriticService.REFERER_HEADER = "https://www.metacritic.com/";
311
366
  _MetacriticService.HOMEPAGE_URL = "https://www.metacritic.com/";
312
367
  _MetacriticService.BASE_URL = "https://backend.metacritic.com/composer/metacritic/pages/";
313
368
  _MetacriticService.SEARCH_URL = _MetacriticService.BASE_URL + "search/";
369
+ _MetacriticService.USER_AGENTS = [
370
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
371
+ "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",
372
+ "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
373
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:127.0) Gecko/20100101 Firefox/127.0",
374
+ "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"
375
+ ];
314
376
  var MetacriticService = _MetacriticService;
315
377
  // Annotate the CommonJS export names for ESM import in node:
316
378
  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\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"],"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 SearchResult = {\r\n success: boolean\r\n data: MetacriticSearchEntry[]\r\n error?: string\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 DetailResult = {\r\n success: boolean\r\n data: MetacriticEntry | null\r\n error?: string\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 { DetailResult, RecordType, SearchResult } 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<SearchResult> {\r\n if (!searchKey) {\r\n return {\r\n success: false,\r\n data: [],\r\n error: 'Search key is required'\r\n }\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 success: false,\r\n data: [],\r\n error: 'Failed to retrieve API key'\r\n }\r\n }\r\n }\r\n\r\n const jsonResult = await MetacriticService.sendSearchWebRequest(searchKey, this.apiKey, recordType)\r\n if (!jsonResult) {\r\n return {\r\n success: false,\r\n data: [],\r\n error: 'Failed to fetch search results'\r\n }\r\n }\r\n\r\n try {\r\n JSON.parse(jsonResult)\r\n } catch {\r\n return {\r\n success: false,\r\n data: [],\r\n error: 'Invalid search response format'\r\n }\r\n }\r\n\r\n return {\r\n success: true,\r\n data: parseSearchJsonResult(jsonResult, searchKey, this.minSimilarity, sortBySimilarity)\r\n }\r\n }\r\n\r\n async getDetail(searchKey: string, recordType: RecordType, sortBySimilarity: boolean = true): Promise<DetailResult> {\r\n if (!searchKey) {\r\n return {\r\n success: false,\r\n data: null,\r\n error: 'Search key is required'\r\n }\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 success: false,\r\n data: null,\r\n error: 'Failed to retrieve API key'\r\n }\r\n }\r\n }\r\n\r\n const searchResult = await this.search(searchKey, recordType, sortBySimilarity)\r\n if (!searchResult.success) {\r\n return {\r\n success: false,\r\n data: null,\r\n error: searchResult.error || 'Failed to search detail entry'\r\n }\r\n }\r\n\r\n if (searchResult.data.length === 0) {\r\n return {\r\n success: false,\r\n data: null,\r\n error: 'No matching entry found'\r\n }\r\n }\r\n\r\n const detailResult = await MetacriticService.sendDetailWebRequest(searchResult.data[0].slug, this.apiKey, recordType)\r\n\r\n if (!detailResult) {\r\n return {\r\n success: false,\r\n data: null,\r\n error: 'Failed to fetch detail result'\r\n }\r\n }\r\n\r\n try {\r\n JSON.parse(detailResult)\r\n } catch {\r\n return {\r\n success: false,\r\n data: null,\r\n error: 'Invalid detail response format'\r\n }\r\n }\r\n\r\n const parsedDetail = parseDetailJsonResult(detailResult, searchResult.data[0].must)\r\n if (!parsedDetail) {\r\n return {\r\n success: false,\r\n data: null,\r\n error: 'Invalid detail response format'\r\n }\r\n }\r\n\r\n return {\r\n success: true,\r\n data: parsedDetail\r\n }\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
@@ -12,6 +12,11 @@ type MetacriticSearchEntry = {
12
12
  criticScore: number;
13
13
  similarity: number;
14
14
  };
15
+ type SearchResult = {
16
+ success: boolean;
17
+ data: MetacriticSearchEntry[];
18
+ error?: string;
19
+ };
15
20
  type MetacriticEntry = {
16
21
  id: number;
17
22
  recordType: RecordType;
@@ -21,6 +26,11 @@ type MetacriticEntry = {
21
26
  userScore: Score;
22
27
  criticScore: Score;
23
28
  };
29
+ type DetailResult = {
30
+ success: boolean;
31
+ data: MetacriticEntry | null;
32
+ error?: string;
33
+ };
24
34
  type Score = {
25
35
  score: number;
26
36
  maxScore: number;
@@ -45,9 +55,10 @@ declare class MetacriticService {
45
55
  static HOMEPAGE_URL: string;
46
56
  static BASE_URL: string;
47
57
  static SEARCH_URL: string;
58
+ static USER_AGENTS: string[];
48
59
  constructor(minSimilarity?: number);
49
- search(searchKey: string, recordType?: RecordType, sortBySimilarity?: boolean): Promise<MetacriticSearchEntry[]>;
50
- getDetail(searchKey: string, recordType: RecordType, sortBySimilarity?: boolean): Promise<MetacriticEntry | null>;
60
+ search(searchKey: string, recordType?: RecordType, sortBySimilarity?: boolean): Promise<SearchResult>;
61
+ getDetail(searchKey: string, recordType: RecordType, sortBySimilarity?: boolean): Promise<DetailResult>;
51
62
  static sendSearchWebRequest(searchKey: string, apiKey: string, recordType?: RecordType): Promise<string | undefined>;
52
63
  static sendDetailWebRequest(slug: string, apiKey: string, recordType: RecordType): Promise<string | undefined>;
53
64
  static getRequestHeaders(): {
@@ -62,4 +73,4 @@ declare class MetacriticService {
62
73
  static sendApiKeyRetrievalWebsiteRequest(): Promise<SearchInformations | null>;
63
74
  }
64
75
 
65
- export { type MetacriticEntry, type MetacriticSearchEntry, MetacriticService, RecordType, type Score };
76
+ export { type DetailResult, type MetacriticEntry, type MetacriticSearchEntry, MetacriticService, RecordType, type Score, type SearchResult };
package/dist/index.d.ts CHANGED
@@ -12,6 +12,11 @@ type MetacriticSearchEntry = {
12
12
  criticScore: number;
13
13
  similarity: number;
14
14
  };
15
+ type SearchResult = {
16
+ success: boolean;
17
+ data: MetacriticSearchEntry[];
18
+ error?: string;
19
+ };
15
20
  type MetacriticEntry = {
16
21
  id: number;
17
22
  recordType: RecordType;
@@ -21,6 +26,11 @@ type MetacriticEntry = {
21
26
  userScore: Score;
22
27
  criticScore: Score;
23
28
  };
29
+ type DetailResult = {
30
+ success: boolean;
31
+ data: MetacriticEntry | null;
32
+ error?: string;
33
+ };
24
34
  type Score = {
25
35
  score: number;
26
36
  maxScore: number;
@@ -45,9 +55,10 @@ declare class MetacriticService {
45
55
  static HOMEPAGE_URL: string;
46
56
  static BASE_URL: string;
47
57
  static SEARCH_URL: string;
58
+ static USER_AGENTS: string[];
48
59
  constructor(minSimilarity?: number);
49
- search(searchKey: string, recordType?: RecordType, sortBySimilarity?: boolean): Promise<MetacriticSearchEntry[]>;
50
- getDetail(searchKey: string, recordType: RecordType, sortBySimilarity?: boolean): Promise<MetacriticEntry | null>;
60
+ search(searchKey: string, recordType?: RecordType, sortBySimilarity?: boolean): Promise<SearchResult>;
61
+ getDetail(searchKey: string, recordType: RecordType, sortBySimilarity?: boolean): Promise<DetailResult>;
51
62
  static sendSearchWebRequest(searchKey: string, apiKey: string, recordType?: RecordType): Promise<string | undefined>;
52
63
  static sendDetailWebRequest(slug: string, apiKey: string, recordType: RecordType): Promise<string | undefined>;
53
64
  static getRequestHeaders(): {
@@ -62,4 +73,4 @@ declare class MetacriticService {
62
73
  static sendApiKeyRetrievalWebsiteRequest(): Promise<SearchInformations | null>;
63
74
  }
64
75
 
65
- export { type MetacriticEntry, type MetacriticSearchEntry, MetacriticService, RecordType, type Score };
76
+ export { type DetailResult, type MetacriticEntry, type MetacriticSearchEntry, MetacriticService, RecordType, type Score, type SearchResult };
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";
@@ -135,7 +132,11 @@ var _MetacriticService = class _MetacriticService {
135
132
  }
136
133
  async search(searchKey, recordType, sortBySimilarity = true) {
137
134
  if (!searchKey) {
138
- return [];
135
+ return {
136
+ success: false,
137
+ data: [],
138
+ error: "Search key is required"
139
+ };
139
140
  }
140
141
  if (!this.apiKey) {
141
142
  const searchInfo = await _MetacriticService.sendApiKeyRetrievalWebsiteRequest();
@@ -143,18 +144,42 @@ var _MetacriticService = class _MetacriticService {
143
144
  this.apiKey = searchInfo.apiKey;
144
145
  } else {
145
146
  console.error("Failed to retrieve API key");
146
- return [];
147
+ return {
148
+ success: false,
149
+ data: [],
150
+ error: "Failed to retrieve API key"
151
+ };
147
152
  }
148
153
  }
149
154
  const jsonResult = await _MetacriticService.sendSearchWebRequest(searchKey, this.apiKey, recordType);
150
- if (jsonResult) {
151
- return parseSearchJsonResult(jsonResult, searchKey, this.minSimilarity, sortBySimilarity);
155
+ if (!jsonResult) {
156
+ return {
157
+ success: false,
158
+ data: [],
159
+ error: "Failed to fetch search results"
160
+ };
152
161
  }
153
- return [];
162
+ try {
163
+ JSON.parse(jsonResult);
164
+ } catch (e) {
165
+ return {
166
+ success: false,
167
+ data: [],
168
+ error: "Invalid search response format"
169
+ };
170
+ }
171
+ return {
172
+ success: true,
173
+ data: parseSearchJsonResult(jsonResult, searchKey, this.minSimilarity, sortBySimilarity)
174
+ };
154
175
  }
155
176
  async getDetail(searchKey, recordType, sortBySimilarity = true) {
156
177
  if (!searchKey) {
157
- return null;
178
+ return {
179
+ success: false,
180
+ data: null,
181
+ error: "Search key is required"
182
+ };
158
183
  }
159
184
  if (!this.apiKey) {
160
185
  const searchInfo = await _MetacriticService.sendApiKeyRetrievalWebsiteRequest();
@@ -162,17 +187,57 @@ var _MetacriticService = class _MetacriticService {
162
187
  this.apiKey = searchInfo.apiKey;
163
188
  } else {
164
189
  console.error("Failed to retrieve API key");
165
- return null;
190
+ return {
191
+ success: false,
192
+ data: null,
193
+ error: "Failed to retrieve API key"
194
+ };
166
195
  }
167
196
  }
168
197
  const searchResult = await this.search(searchKey, recordType, sortBySimilarity);
169
- if (searchResult && searchResult.length > 0) {
170
- const detailResult = await _MetacriticService.sendDetailWebRequest(searchResult[0].slug, this.apiKey, recordType);
171
- if (detailResult) {
172
- return parseDetailJsonResult(detailResult, searchResult[0].must);
173
- }
198
+ if (!searchResult.success) {
199
+ return {
200
+ success: false,
201
+ data: null,
202
+ error: searchResult.error || "Failed to search detail entry"
203
+ };
174
204
  }
175
- return null;
205
+ if (searchResult.data.length === 0) {
206
+ return {
207
+ success: false,
208
+ data: null,
209
+ error: "No matching entry found"
210
+ };
211
+ }
212
+ const detailResult = await _MetacriticService.sendDetailWebRequest(searchResult.data[0].slug, this.apiKey, recordType);
213
+ if (!detailResult) {
214
+ return {
215
+ success: false,
216
+ data: null,
217
+ error: "Failed to fetch detail result"
218
+ };
219
+ }
220
+ try {
221
+ JSON.parse(detailResult);
222
+ } catch (e) {
223
+ return {
224
+ success: false,
225
+ data: null,
226
+ error: "Invalid detail response format"
227
+ };
228
+ }
229
+ const parsedDetail = parseDetailJsonResult(detailResult, searchResult.data[0].must);
230
+ if (!parsedDetail) {
231
+ return {
232
+ success: false,
233
+ data: null,
234
+ error: "Invalid detail response format"
235
+ };
236
+ }
237
+ return {
238
+ success: true,
239
+ data: parsedDetail
240
+ };
176
241
  }
177
242
  static async sendSearchWebRequest(searchKey, apiKey, recordType) {
178
243
  const headers = this.getRequestHeaders();
@@ -236,7 +301,7 @@ var _MetacriticService = class _MetacriticService {
236
301
  };
237
302
  }
238
303
  static getMinimalRequestHeaders() {
239
- const userAgent = new UserAgent();
304
+ const userAgent = _MetacriticService.USER_AGENTS[Math.floor(Math.random() * _MetacriticService.USER_AGENTS.length)];
240
305
  return {
241
306
  "User-Agent": userAgent.toString()
242
307
  };
@@ -274,6 +339,13 @@ _MetacriticService.REFERER_HEADER = "https://www.metacritic.com/";
274
339
  _MetacriticService.HOMEPAGE_URL = "https://www.metacritic.com/";
275
340
  _MetacriticService.BASE_URL = "https://backend.metacritic.com/composer/metacritic/pages/";
276
341
  _MetacriticService.SEARCH_URL = _MetacriticService.BASE_URL + "search/";
342
+ _MetacriticService.USER_AGENTS = [
343
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
344
+ "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",
345
+ "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
346
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:127.0) Gecko/20100101 Firefox/127.0",
347
+ "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"
348
+ ];
277
349
  var MetacriticService = _MetacriticService;
278
350
  export {
279
351
  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\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"],"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 SearchResult = {\r\n success: boolean\r\n data: MetacriticSearchEntry[]\r\n error?: string\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 DetailResult = {\r\n success: boolean\r\n data: MetacriticEntry | null\r\n error?: string\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 { DetailResult, RecordType, SearchResult } 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<SearchResult> {\r\n if (!searchKey) {\r\n return {\r\n success: false,\r\n data: [],\r\n error: 'Search key is required'\r\n }\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 success: false,\r\n data: [],\r\n error: 'Failed to retrieve API key'\r\n }\r\n }\r\n }\r\n\r\n const jsonResult = await MetacriticService.sendSearchWebRequest(searchKey, this.apiKey, recordType)\r\n if (!jsonResult) {\r\n return {\r\n success: false,\r\n data: [],\r\n error: 'Failed to fetch search results'\r\n }\r\n }\r\n\r\n try {\r\n JSON.parse(jsonResult)\r\n } catch {\r\n return {\r\n success: false,\r\n data: [],\r\n error: 'Invalid search response format'\r\n }\r\n }\r\n\r\n return {\r\n success: true,\r\n data: parseSearchJsonResult(jsonResult, searchKey, this.minSimilarity, sortBySimilarity)\r\n }\r\n }\r\n\r\n async getDetail(searchKey: string, recordType: RecordType, sortBySimilarity: boolean = true): Promise<DetailResult> {\r\n if (!searchKey) {\r\n return {\r\n success: false,\r\n data: null,\r\n error: 'Search key is required'\r\n }\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 success: false,\r\n data: null,\r\n error: 'Failed to retrieve API key'\r\n }\r\n }\r\n }\r\n\r\n const searchResult = await this.search(searchKey, recordType, sortBySimilarity)\r\n if (!searchResult.success) {\r\n return {\r\n success: false,\r\n data: null,\r\n error: searchResult.error || 'Failed to search detail entry'\r\n }\r\n }\r\n\r\n if (searchResult.data.length === 0) {\r\n return {\r\n success: false,\r\n data: null,\r\n error: 'No matching entry found'\r\n }\r\n }\r\n\r\n const detailResult = await MetacriticService.sendDetailWebRequest(searchResult.data[0].slug, this.apiKey, recordType)\r\n\r\n if (!detailResult) {\r\n return {\r\n success: false,\r\n data: null,\r\n error: 'Failed to fetch detail result'\r\n }\r\n }\r\n\r\n try {\r\n JSON.parse(detailResult)\r\n } catch {\r\n return {\r\n success: false,\r\n data: null,\r\n error: 'Invalid detail response format'\r\n }\r\n }\r\n\r\n const parsedDetail = parseDetailJsonResult(detailResult, searchResult.data[0].must)\r\n if (!parsedDetail) {\r\n return {\r\n success: false,\r\n data: null,\r\n error: 'Invalid detail response format'\r\n }\r\n }\r\n\r\n return {\r\n success: true,\r\n data: parsedDetail\r\n }\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.2",
3
+ "version": "1.1.0",
4
4
  "description": "TypeScript library to extrapolate data from Metacritic.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -34,12 +34,8 @@
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
- "@types/user-agents": "^1.0.4",
43
39
  "jest": "^29.7.0",
44
40
  "ts-jest": "^29.3.1",
45
41
  "ts-node": "^10.9.2",