mcp-metacritic 1.2.0 → 2.0.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/dist/mc/client.js CHANGED
@@ -1,5 +1,8 @@
1
+ // src/config.ts
2
+ import process from "process";
3
+
1
4
  // src/version.ts
2
- var PKG_VERSION = "1.2.0";
5
+ var PKG_VERSION = "2.0.0";
3
6
  var REPO_URL = "https://github.com/smeet666/mcp-metacritic";
4
7
 
5
8
  // src/config.ts
@@ -23,7 +26,9 @@ var MAX_ALLOWED_INTERVAL_MS = 6e4;
23
26
  var LOG_LEVELS = ["silent", "error", "info", "debug"];
24
27
  function readNumber(name, env, range) {
25
28
  const raw = env[name];
26
- if (raw === void 0 || raw.trim() === "") return range.fallback;
29
+ if (raw === void 0 || raw.trim() === "") {
30
+ return range.fallback;
31
+ }
27
32
  const parsed = Number(raw);
28
33
  if (!Number.isFinite(parsed)) {
29
34
  warn(`${name}="${raw}" is not a number, using ${range.fallback}`);
@@ -44,7 +49,9 @@ function warn(message) {
44
49
  }
45
50
  function readInterval(env) {
46
51
  const raw = env.MC_MIN_INTERVAL_MS;
47
- if (raw === void 0 || raw.trim() === "") return DEFAULTS.minIntervalMs;
52
+ if (raw === void 0 || raw.trim() === "") {
53
+ return DEFAULTS.minIntervalMs;
54
+ }
48
55
  const parsed = Number(raw);
49
56
  if (!Number.isFinite(parsed)) {
50
57
  warn(`MC_MIN_INTERVAL_MS="${raw}" is not a number, using ${DEFAULTS.minIntervalMs}ms`);
@@ -133,7 +140,9 @@ var TtlLruCache = class {
133
140
  store = /* @__PURE__ */ new Map();
134
141
  get(key) {
135
142
  const entry = this.store.get(key);
136
- if (!entry) return void 0;
143
+ if (!entry) {
144
+ return void 0;
145
+ }
137
146
  if (Date.now() >= entry.expiresAt) {
138
147
  this.store.delete(key);
139
148
  return void 0;
@@ -143,12 +152,16 @@ var TtlLruCache = class {
143
152
  return entry.value;
144
153
  }
145
154
  set(key, value) {
146
- if (this.maxEntries <= 0 || this.ttlMs <= 0) return;
155
+ if (this.maxEntries <= 0 || this.ttlMs <= 0) {
156
+ return;
157
+ }
147
158
  this.store.delete(key);
148
159
  this.store.set(key, { value, expiresAt: Date.now() + this.ttlMs });
149
160
  while (this.store.size > this.maxEntries) {
150
161
  const oldest = this.store.keys().next();
151
- if (oldest.done) break;
162
+ if (oldest.done) {
163
+ break;
164
+ }
152
165
  this.store.delete(oldest.value);
153
166
  }
154
167
  }
@@ -163,7 +176,7 @@ var TtlLruCache = class {
163
176
  // src/errors.ts
164
177
  var McError = class extends Error {
165
178
  constructor(code, message, details = {}) {
166
- super(message);
179
+ super(message, details.cause === void 0 ? void 0 : { cause: details.cause });
167
180
  this.code = code;
168
181
  this.details = details;
169
182
  this.name = "McError";
@@ -193,11 +206,11 @@ function rateLimited(url, retryAfterMs) {
193
206
  }
194
207
  );
195
208
  }
196
- function parseFailure(url, what) {
209
+ function parseFailure(url, what, cause) {
197
210
  return new McError(
198
211
  "parse_failure",
199
212
  `Metacritic answered, but the response did not have the expected shape (${what}).`,
200
- { url, hint: `Please report this, with the request you made, at ${ISSUES_URL}` }
213
+ { url, hint: `Please report this, with the request you made, at ${ISSUES_URL}`, cause }
201
214
  );
202
215
  }
203
216
  function upstreamError(url, status) {
@@ -265,10 +278,14 @@ var RateLimiter = class {
265
278
  this.intervalMs = Math.max(this.baseIntervalMs, Math.floor(this.intervalMs * 0.75));
266
279
  }
267
280
  async waitForSlot() {
268
- if (this.intervalMs === 0 || this.lastStart === 0) return;
281
+ if (this.intervalMs === 0 || this.lastStart === 0) {
282
+ return;
283
+ }
269
284
  const elapsed = Date.now() - this.lastStart;
270
285
  const remaining = Math.min(this.intervalMs, this.intervalMs - elapsed);
271
- if (remaining > 0) await sleep(remaining);
286
+ if (remaining > 0) {
287
+ await sleep(remaining);
288
+ }
272
289
  }
273
290
  };
274
291
  function sleep(ms) {
@@ -283,10 +300,57 @@ function backoffDelay(attempt, random = Math.random) {
283
300
  const capped = Math.min(BACKOFF_MAX_MS, BACKOFF_BASE_MS * BACKOFF_FACTOR ** attempt);
284
301
  return Math.round(capped * (0.5 + random() * 0.5));
285
302
  }
303
+ function readAnswer(url, status, body, retryAfterMs, ownGuessMs) {
304
+ if (status === 429 || status === 503 || status === 403) {
305
+ return {
306
+ kind: "again",
307
+ error: rateLimited(url, retryAfterMs ?? ownGuessMs),
308
+ // A server that says when to come back knows better than our own guess.
309
+ waitMs: retryAfterMs,
310
+ penalise: true,
311
+ because: `refused with ${status}`
312
+ };
313
+ }
314
+ if (status >= 500) {
315
+ return {
316
+ kind: "again",
317
+ error: upstreamError(url, status),
318
+ waitMs: null,
319
+ penalise: false,
320
+ because: `status ${status}`
321
+ };
322
+ }
323
+ if (status === 404) {
324
+ return { kind: "refused", error: notFound(url, "that request") };
325
+ }
326
+ if (status >= 400) {
327
+ return { kind: "refused", error: upstreamError(url, status) };
328
+ }
329
+ const trimmed = body.trim();
330
+ if (trimmed === "") {
331
+ return {
332
+ kind: "again",
333
+ error: rateLimited(url, ownGuessMs),
334
+ waitMs: null,
335
+ penalise: true,
336
+ because: "empty body, treated as a refusal"
337
+ };
338
+ }
339
+ if (!(trimmed.startsWith("{") || trimmed.startsWith("["))) {
340
+ return {
341
+ kind: "again",
342
+ error: parseFailure(url, "the body is not JSON"),
343
+ waitMs: retryAfterMs,
344
+ penalise: true,
345
+ because: "non-JSON body"
346
+ };
347
+ }
348
+ return { kind: "usable" };
349
+ }
286
350
  async function fetchText(url, deps) {
287
351
  const { config, limiter, logger } = deps;
288
352
  const doFetch = deps.fetchImpl ?? fetch;
289
- return limiter.schedule(async () => {
353
+ return await limiter.schedule(async () => {
290
354
  let lastError;
291
355
  let askedWaitMs = null;
292
356
  for (let attempt = 0; attempt <= config.maxRetries; attempt += 1) {
@@ -317,33 +381,17 @@ async function fetchText(url, deps) {
317
381
  logger.debug(`${lastError.code} for ${url}: ${lastError.message}`);
318
382
  continue;
319
383
  }
320
- if (status === 429 || status === 503 || status === 403) {
321
- limiter.penalize();
322
- askedWaitMs = retryAfterMs;
323
- lastError = rateLimited(url, retryAfterMs ?? backoffDelay(attempt));
324
- logger.info(
325
- `refused on ${url} with ${status}, interval now ${limiter.currentIntervalMs}ms`
326
- );
327
- continue;
328
- }
329
- if (status >= 500) {
330
- lastError = upstreamError(url, status);
331
- continue;
384
+ const verdict = readAnswer(url, status, body, retryAfterMs, backoffDelay(attempt));
385
+ if (verdict.kind === "refused") {
386
+ throw verdict.error;
332
387
  }
333
- if (status === 404) throw notFound(url, "that request");
334
- if (status >= 400) throw upstreamError(url, status);
335
- const trimmed = body.trim();
336
- if (trimmed === "") {
337
- limiter.penalize();
338
- lastError = rateLimited(url, backoffDelay(attempt));
339
- logger.info(`empty body on ${url}, treating as a refusal`);
340
- continue;
341
- }
342
- if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) {
343
- limiter.penalize();
344
- lastError = parseFailure(url, "the body is not JSON");
345
- askedWaitMs = retryAfterMs;
346
- logger.info(`non-JSON body on ${url}, retrying`);
388
+ if (verdict.kind === "again") {
389
+ if (verdict.penalise) {
390
+ limiter.penalize();
391
+ logger.info(`${verdict.because} on ${url}, interval now ${limiter.currentIntervalMs}ms`);
392
+ }
393
+ askedWaitMs = verdict.waitMs;
394
+ lastError = verdict.error;
347
395
  continue;
348
396
  }
349
397
  limiter.relax();
@@ -353,15 +401,23 @@ async function fetchText(url, deps) {
353
401
  });
354
402
  }
355
403
  function parseRetryAfter(raw) {
356
- if (!raw) return null;
404
+ if (!raw) {
405
+ return null;
406
+ }
357
407
  const seconds = Number(raw.trim());
358
- if (Number.isFinite(seconds) && seconds >= 0) return Math.round(seconds * 1e3);
408
+ if (Number.isFinite(seconds) && seconds >= 0) {
409
+ return Math.round(seconds * 1e3);
410
+ }
359
411
  const when = Date.parse(raw);
360
- if (Number.isNaN(when)) return null;
412
+ if (Number.isNaN(when)) {
413
+ return null;
414
+ }
361
415
  return Math.max(0, when - Date.now());
362
416
  }
363
417
  function asTransportError(error, url) {
364
- if (error instanceof McError) return error;
418
+ if (error instanceof McError) {
419
+ return error;
420
+ }
365
421
  const name = error instanceof Error ? error.name : "";
366
422
  if (name === "TimeoutError" || name === "AbortError") {
367
423
  return new McError("timeout", "Metacritic did not answer in time.", {
@@ -419,10 +475,14 @@ var FIELD = {
419
475
  };
420
476
 
421
477
  // src/mc/urls.ts
478
+ var SLUG = /^[a-z0-9][a-z0-9-]*$/i;
479
+ var IMDB_ID = /^tt\d+$/;
422
480
  function build(path, params) {
423
481
  const query = new URLSearchParams();
424
482
  for (const [key, value] of Object.entries(params)) {
425
- if (value !== void 0 && value !== "") query.set(key, String(value));
483
+ if (value !== void 0 && value !== "") {
484
+ query.set(key, String(value));
485
+ }
426
486
  }
427
487
  const suffix = query.toString();
428
488
  return suffix ? `${API_BASE}${path}?${suffix}` : `${API_BASE}${path}`;
@@ -432,7 +492,7 @@ function checkSlug(slug) {
432
492
  if (trimmed === "") {
433
493
  throw invalidInput("The slug is empty.", "Take it from a search_titles result.");
434
494
  }
435
- if (!/^[a-z0-9][a-z0-9-]*$/i.test(trimmed)) {
495
+ if (!SLUG.test(trimmed)) {
436
496
  throw invalidInput(
437
497
  `"${slug}" does not look like a Metacritic slug.`,
438
498
  "Slugs are lowercase words joined by hyphens, such as the-matrix. Use search_titles to get one."
@@ -475,7 +535,7 @@ function reviewsUrl(options) {
475
535
  }
476
536
  function watchUrl(imdbId, kind) {
477
537
  const trimmed = imdbId.trim();
478
- if (!/^tt\d+$/.test(trimmed)) {
538
+ if (!IMDB_ID.test(trimmed)) {
479
539
  throw invalidInput(
480
540
  `"${imdbId}" is not an IMDb id.`,
481
541
  "IMDb ids look like tt0133093. get_title returns one as imdb_id."
@@ -494,38 +554,51 @@ function bucketFor(sentiment) {
494
554
  }
495
555
 
496
556
  // src/mc/parse.ts
557
+ var ABSOLUTE_URL = /^https?:\/\//i;
497
558
  var isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
498
559
  function envelope(raw, url, what) {
499
560
  let body;
500
561
  try {
501
562
  body = JSON.parse(raw);
502
- } catch {
503
- throw parseFailure(url, "the body is not valid JSON");
563
+ } catch (cause) {
564
+ throw parseFailure(url, "the body is not valid JSON", cause);
565
+ }
566
+ if (!isObject(body)) {
567
+ throw parseFailure(url, "the body is not an object");
504
568
  }
505
- if (!isObject(body)) throw parseFailure(url, "the body is not an object");
506
569
  const errors = body[FIELD.errors];
507
570
  if (Array.isArray(errors) && errors.length > 0) {
508
571
  const first = isObject(errors[0]) ? errors[0] : {};
509
- if (first.code === 404) throw notFound(url, what);
572
+ if (first.code === 404) {
573
+ throw notFound(url, what);
574
+ }
510
575
  throw parseFailure(
511
576
  url,
512
577
  `the site reported ${String(first.reason ?? first.code ?? "an error")}`
513
578
  );
514
579
  }
515
580
  const data = body[FIELD.data];
516
- if (!isObject(data)) throw parseFailure(url, "the response carries no data object");
581
+ if (!isObject(data)) {
582
+ throw parseFailure(url, "the response carries no data object");
583
+ }
517
584
  return data;
518
585
  }
519
586
  var str = (value) => {
520
- if (typeof value !== "string") return null;
587
+ if (typeof value !== "string") {
588
+ return null;
589
+ }
521
590
  const trimmed = value.trim();
522
591
  return trimmed === "" ? null : trimmed;
523
592
  };
524
593
  var num = (value) => {
525
- if (typeof value === "number" && Number.isFinite(value)) return value;
594
+ if (typeof value === "number" && Number.isFinite(value)) {
595
+ return value;
596
+ }
526
597
  if (typeof value === "string") {
527
598
  const parsed = Number(value);
528
- if (Number.isFinite(parsed)) return parsed;
599
+ if (Number.isFinite(parsed)) {
600
+ return parsed;
601
+ }
529
602
  }
530
603
  return null;
531
604
  };
@@ -539,18 +612,28 @@ var aggregate = (value) => {
539
612
  };
540
613
  function kindOf(value) {
541
614
  const type = str(value);
542
- if (type === SEARCH_TYPE.movie) return "movie";
543
- if (type === SEARCH_TYPE.show) return "show";
544
- if (type === SEARCH_TYPE.game) return "game";
615
+ if (type === SEARCH_TYPE.movie) {
616
+ return "movie";
617
+ }
618
+ if (type === SEARCH_TYPE.show) {
619
+ return "show";
620
+ }
621
+ if (type === SEARCH_TYPE.game) {
622
+ return "game";
623
+ }
545
624
  return null;
546
625
  }
547
626
  function toSummary(node) {
548
- if (!isObject(node)) return null;
627
+ if (!isObject(node)) {
628
+ return null;
629
+ }
549
630
  const id = intOf(node.id);
550
631
  const slug = str(node.slug);
551
632
  const title = str(node.title);
552
633
  const kind = kindOf(node.type);
553
- if (id === null || !slug || !title || !kind) return null;
634
+ if (id === null || !slug || !title || !kind) {
635
+ return null;
636
+ }
554
637
  const critic = isObject(node.criticScoreSummary) ? node.criticScoreSummary : {};
555
638
  const userSummary = isObject(node.userScoreSummary) ? node.userScoreSummary : {};
556
639
  const userObject = isObject(node.userScore) ? node.userScore : {};
@@ -571,11 +654,15 @@ function toSummary(node) {
571
654
  function parseTitlePage(raw, url, what) {
572
655
  const data = envelope(raw, url, what);
573
656
  const items = data[FIELD.items];
574
- if (!Array.isArray(items)) throw parseFailure(url, "the response carries no items array");
657
+ if (!Array.isArray(items)) {
658
+ throw parseFailure(url, "the response carries no items array");
659
+ }
575
660
  const titles = [];
576
661
  for (const node of items) {
577
662
  const row = toSummary(node);
578
- if (row) titles.push(row);
663
+ if (row) {
664
+ titles.push(row);
665
+ }
579
666
  }
580
667
  if (items.length > 0 && titles.length === 0) {
581
668
  throw parseFailure(url, `${items.length} rows but none could be read`);
@@ -588,35 +675,51 @@ function parseTitlePage(raw, url, what) {
588
675
  }
589
676
  function toCompanies(value) {
590
677
  const source = isObject(value) ? value.companies : value;
591
- if (!Array.isArray(source)) return [];
678
+ if (!Array.isArray(source)) {
679
+ return [];
680
+ }
592
681
  const seen = /* @__PURE__ */ new Set();
593
682
  const out = [];
594
683
  for (const node of source) {
595
684
  const name = isObject(node) ? str(node.name) : str(node);
596
- if (!name) continue;
685
+ if (!name) {
686
+ continue;
687
+ }
597
688
  const key = name.toLowerCase();
598
- if (seen.has(key)) continue;
689
+ if (seen.has(key)) {
690
+ continue;
691
+ }
599
692
  seen.add(key);
600
693
  out.push({ name, id: isObject(node) ? intOf(node.id) : null });
601
694
  }
602
695
  return out;
603
696
  }
604
697
  function toStringList(value) {
605
- if (!Array.isArray(value)) return [];
698
+ if (!Array.isArray(value)) {
699
+ return [];
700
+ }
606
701
  const out = [];
607
702
  for (const node of value) {
608
703
  const name = isObject(node) ? str(node.name) : str(node);
609
- if (name) out.push(name);
704
+ if (name) {
705
+ out.push(name);
706
+ }
610
707
  }
611
708
  return out;
612
709
  }
613
710
  function toAwards(value) {
614
- if (!Array.isArray(value)) return [];
711
+ if (!Array.isArray(value)) {
712
+ return [];
713
+ }
615
714
  const out = [];
616
715
  for (const node of value) {
617
- if (!isObject(node)) continue;
716
+ if (!isObject(node)) {
717
+ continue;
718
+ }
618
719
  const ceremony = str(node.awardEvent);
619
- if (!ceremony) continue;
720
+ if (!ceremony) {
721
+ continue;
722
+ }
620
723
  out.push({
621
724
  ceremony,
622
725
  wins: intOf(node.wins),
@@ -628,10 +731,14 @@ function toAwards(value) {
628
731
  function parseDetail(raw, url, kind, slug) {
629
732
  const data = envelope(raw, url, `${kind} "${slug}"`);
630
733
  const item = data[FIELD.item];
631
- if (!isObject(item)) throw parseFailure(url, "the response carries no item object");
734
+ if (!isObject(item)) {
735
+ throw parseFailure(url, "the response carries no item object");
736
+ }
632
737
  const id = intOf(item.id);
633
738
  const title = str(item.title);
634
- if (id === null || !title) throw parseFailure(url, "the entry has no id or no title");
739
+ if (id === null || !title) {
740
+ throw parseFailure(url, "the entry has no id or no title");
741
+ }
635
742
  const critic = isObject(item.criticScoreSummary) ? item.criticScoreSummary : {};
636
743
  return {
637
744
  id,
@@ -659,9 +766,13 @@ function parseDetail(raw, url, kind, slug) {
659
766
  function parseScore(raw, url, what) {
660
767
  const data = envelope(raw, url, what);
661
768
  const item = data[FIELD.item];
662
- if (!isObject(item)) throw parseFailure(url, "the response carries no score object");
769
+ if (!isObject(item)) {
770
+ throw parseFailure(url, "the response carries no score object");
771
+ }
663
772
  const max = num(item.max);
664
- if (max === null) throw parseFailure(url, "the score carries no scale");
773
+ if (max === null) {
774
+ throw parseFailure(url, "the score carries no scale");
775
+ }
665
776
  return {
666
777
  score: aggregate(item.score),
667
778
  max,
@@ -674,7 +785,9 @@ function parseScore(raw, url, what) {
674
785
  }
675
786
  function reviewBucket(data, url, sentiment) {
676
787
  const item = data[FIELD.item];
677
- if (!isObject(item)) throw parseFailure(url, "the response carries no reviews object");
788
+ if (!isObject(item)) {
789
+ throw parseFailure(url, "the response carries no reviews object");
790
+ }
678
791
  const bucket = item[bucketFor(sentiment)];
679
792
  if (!Array.isArray(bucket)) {
680
793
  throw parseFailure(url, `there is no "${bucketFor(sentiment)}" group of reviews`);
@@ -686,10 +799,14 @@ function parseCriticReviews(raw, url, what, sentiment) {
686
799
  const bucket = reviewBucket(data, url, sentiment);
687
800
  const reviews = [];
688
801
  for (const node of bucket) {
689
- if (!isObject(node)) continue;
802
+ if (!isObject(node)) {
803
+ continue;
804
+ }
690
805
  const quote = str(node.quote);
691
806
  const publication = str(node.publicationName);
692
- if (!quote || !publication) continue;
807
+ if (!(quote && publication)) {
808
+ continue;
809
+ }
693
810
  reviews.push({
694
811
  quote,
695
812
  score: num(node.score),
@@ -713,9 +830,13 @@ function parseUserReviews(raw, url, what, sentiment) {
713
830
  const bucket = reviewBucket(data, url, sentiment);
714
831
  const reviews = [];
715
832
  for (const node of bucket) {
716
- if (!isObject(node)) continue;
833
+ if (!isObject(node)) {
834
+ continue;
835
+ }
717
836
  const quote = str(node.quote);
718
- if (!quote) continue;
837
+ if (!quote) {
838
+ continue;
839
+ }
719
840
  reviews.push({ quote, score: num(node.score), date: str(node.date) });
720
841
  }
721
842
  if (bucket.length > 0 && reviews.length === 0) {
@@ -728,10 +849,14 @@ function parseUserReviews(raw, url, what, sentiment) {
728
849
  };
729
850
  }
730
851
  function unwrapLink(link) {
731
- if (!link) return null;
852
+ if (!link) {
853
+ return null;
854
+ }
732
855
  try {
733
856
  const target = new URL(link).searchParams.get("r");
734
- if (target && /^https?:\/\//i.test(target)) return target;
857
+ if (target && ABSOLUTE_URL.test(target)) {
858
+ return target;
859
+ }
735
860
  } catch {
736
861
  }
737
862
  return link;
@@ -739,14 +864,22 @@ function unwrapLink(link) {
739
864
  function parseOffers(raw, url, what) {
740
865
  const data = envelope(raw, url, what);
741
866
  const item = data[FIELD.item];
742
- if (!isObject(item)) throw parseFailure(url, "the response carries no offers object");
867
+ if (!isObject(item)) {
868
+ throw parseFailure(url, "the response carries no offers object");
869
+ }
743
870
  const offers = [];
744
871
  for (const [group, value] of Object.entries(item)) {
745
- if (!Array.isArray(value)) continue;
872
+ if (!Array.isArray(value)) {
873
+ continue;
874
+ }
746
875
  for (const node of value) {
747
- if (!isObject(node)) continue;
876
+ if (!isObject(node)) {
877
+ continue;
878
+ }
748
879
  const provider = str(node.providerName);
749
- if (!provider) continue;
880
+ if (!provider) {
881
+ continue;
882
+ }
750
883
  offers.push({ provider, kind: group, url: unwrapLink(str(node.link)) });
751
884
  }
752
885
  }
@@ -754,8 +887,9 @@ function parseOffers(raw, url, what) {
754
887
  }
755
888
 
756
889
  // src/mc/client.ts
890
+ var BROWSER_IDENTITY = /mozilla\/|applewebkit|chrome\/|safari\/|gecko/i;
757
891
  function withGuarantees(config) {
758
- const userAgent = /mozilla\/|applewebkit|chrome\/|safari\/|gecko/i.test(config.userAgent) ? `${config.userAgent} ${DEFAULT_USER_AGENT}` : config.userAgent;
892
+ const userAgent = BROWSER_IDENTITY.test(config.userAgent) ? `${config.userAgent} ${DEFAULT_USER_AGENT}` : config.userAgent;
759
893
  return {
760
894
  ...config,
761
895
  userAgent,
@@ -797,7 +931,7 @@ var McClient = class {
797
931
  */
798
932
  async search(query, limit, offset) {
799
933
  const url = searchUrl(query, limit, offset);
800
- return this.fetchParsed(
934
+ return await this.fetchParsed(
801
935
  url,
802
936
  this.catalogueCache,
803
937
  (body) => parseTitlePage(body, url, `the query "${query}"`)
@@ -805,7 +939,7 @@ var McClient = class {
805
939
  }
806
940
  async browse(options) {
807
941
  const url = browseUrl(options);
808
- return this.fetchParsed(
942
+ return await this.fetchParsed(
809
943
  url,
810
944
  this.catalogueCache,
811
945
  (body) => parseTitlePage(body, url, `that ${options.kind} listing`)
@@ -814,7 +948,7 @@ var McClient = class {
814
948
  async getDetail(kind, slug) {
815
949
  const url = detailUrl(kind, slug);
816
950
  const clean = slug.trim();
817
- return this.fetchParsed(
951
+ return await this.fetchParsed(
818
952
  url,
819
953
  this.catalogueCache,
820
954
  (body) => parseDetail(body, url, kind, clean)
@@ -822,7 +956,7 @@ var McClient = class {
822
956
  }
823
957
  async getScore(kind, slug, source) {
824
958
  const url = scoreUrl(kind, slug, source);
825
- return this.fetchParsed(
959
+ return await this.fetchParsed(
826
960
  url,
827
961
  this.scoresCache,
828
962
  (body) => parseScore(body, url, `the ${source} score of ${kind} "${slug}"`)
@@ -830,7 +964,7 @@ var McClient = class {
830
964
  }
831
965
  async getCriticReviews(options) {
832
966
  const url = reviewsUrl({ ...options, source: "critic" });
833
- return this.fetchParsed(
967
+ return await this.fetchParsed(
834
968
  url,
835
969
  this.scoresCache,
836
970
  (body) => parseCriticReviews(
@@ -844,7 +978,7 @@ var McClient = class {
844
978
  }
845
979
  async getUserReviews(options) {
846
980
  const url = reviewsUrl({ ...options, source: "user" });
847
- return this.fetchParsed(
981
+ return await this.fetchParsed(
848
982
  url,
849
983
  this.scoresCache,
850
984
  (body) => parseUserReviews(
@@ -858,7 +992,7 @@ var McClient = class {
858
992
  }
859
993
  async getWatchOffers(imdbId, kind) {
860
994
  const url = watchUrl(imdbId, kind);
861
- return this.fetchParsed(
995
+ return await this.fetchParsed(
862
996
  url,
863
997
  this.scoresCache,
864
998
  (body) => parseOffers(body, url, `streaming offers for ${imdbId}`)