Skip to content

Commit 374c228

Browse files
committed
refactor(ototoy): switch to deno-dom
1 parent e3abdae commit 374c228

File tree

2 files changed

+45
-34
lines changed

2 files changed

+45
-34
lines changed

deno.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
},
1313
"imports": {
1414
"@/": "./",
15+
"@b-fuze/deno-dom": "jsr:@b-fuze/deno-dom@^0.1.56",
1516
"@deno/gfm": "jsr:@deno/gfm@^0.8.0",
1617
"@kellnerd/musicbrainz": "jsr:@kellnerd/musicbrainz@^0.4.1",
1718
"@std/collections": "jsr:@std/collections@^1.0.10",
@@ -20,9 +21,7 @@
2021
"@std/testing": "jsr:@std/testing@^1.0.9",
2122
"@std/uuid": "jsr:@std/uuid@^1.0.6",
2223
"$fresh/": "https://deno.land/x/fresh@1.6.8/",
23-
"@types/jsdom": "npm:@types/jsdom@^27.0.0",
2424
"fresh/": "https://deno.land/x/fresh@1.6.8/",
25-
"jsdom": "npm:jsdom@^27.2.0",
2625
"lande": "https://esm.sh/lande@1.0.10",
2726
"preact": "https://esm.sh/preact@10.19.6",
2827
"preact/": "https://esm.sh/preact@10.19.6/",

providers/Ototoy/mod.ts

Lines changed: 44 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ import { type CacheEntry, MetadataProvider, ReleaseLookup } from '@/providers/ba
1313
import { DurationPrecision, FeatureQuality, FeatureQualityMap } from '@/providers/features.ts';
1414
import { parseISODateTime, PartialDate } from '@/utils/date.ts';
1515
import { ProviderError, ResponseError } from '@/utils/errors.ts';
16-
// @deno-types="npm:@types/jsdom"
17-
import { JSDOM } from 'jsdom';
16+
import { DOMParser, HTMLDocument } from '@b-fuze/deno-dom';
1817
import { parseDuration } from '../../utils/time.ts';
1918

2019
export default class OtotoyProvider extends MetadataProvider {
@@ -30,7 +29,7 @@ export default class OtotoyProvider extends MetadataProvider {
3029
pathname: '/_/default/a/:id',
3130
});
3231

33-
readonly entityPathPattern = /\/_\/default\/(?:a|p)\/(\d+)$/;
32+
readonly entityPathPattern = /\/_\/default\/[ap]\/(\d+)$/;
3433

3534
readonly labelUrlPattern = new URLPattern({
3635
hostname: this.supportedUrls.hostname,
@@ -107,19 +106,22 @@ export default class OtotoyProvider extends MetadataProvider {
107106
}
108107

109108
scrapePackage(html: string, webUrl: URL): PackagePage {
110-
const { document } = (new JSDOM(html)).window;
109+
const doc = new DOMParser().parseFromString(html, 'text/html');
110+
if (!doc) {
111+
throw new ResponseError(this.name, `Failed to parse HTML`, webUrl);
112+
}
111113

112-
const thumbUrl = this.parseAlbumArtwork(document);
114+
const thumbUrl = this.parseAlbumArtwork(doc);
113115
if (!thumbUrl) {
114116
throw new ResponseError(this.name, `Failed to extract album thumbnail`, webUrl);
115117
}
116118

117-
const albumMeta = this.parseAlbumMeta(document);
119+
const albumMeta = this.parseAlbumMeta(doc);
118120
if (!albumMeta) {
119121
throw new ResponseError(this.name, `Failed to extract album metadata`, webUrl);
120122
}
121123

122-
const trackList = this.parseTracklist(document);
124+
const trackList = this.parseTracklist(doc);
123125
if (!trackList) {
124126
throw new ResponseError(this.name, `Failed to extract tracklist`, webUrl);
125127
}
@@ -142,11 +144,11 @@ export default class OtotoyProvider extends MetadataProvider {
142144
// </div>
143145
//
144146
// This is just the small thumbnail, the full size image comes from getArtwork()
145-
parseAlbumArtwork(doc: Document): string | undefined {
146-
const imageElement = doc.querySelector<HTMLImageElement>('div.album-artwork img.photo');
147+
parseAlbumArtwork(doc: HTMLDocument): string | undefined {
148+
const imageElement = doc.querySelector('div.album-artwork img.photo');
147149
if (!imageElement) return undefined;
148150

149-
return imageElement.src;
151+
return imageElement.getAttribute('src') || undefined;
150152
}
151153

152154
// The format is as follows:
@@ -169,16 +171,15 @@ export default class OtotoyProvider extends MetadataProvider {
169171
// </table>
170172
//
171173
// NOTE: `disc-row` is optional
172-
parseTracklist(doc: Document): Track[] | undefined {
173-
const trackListRows = doc.querySelectorAll<HTMLTableRowElement>('#tracklist tbody tr');
174+
parseTracklist(doc: HTMLDocument): Track[] | undefined {
175+
const trackListRows = doc.querySelectorAll('#tracklist tbody tr');
174176

175177
let currentDisc = null;
176178
const tracks: Track[] = [];
177179

178180
for (const trackRow of trackListRows) {
179181
if (trackRow.classList.contains('disc-row')) {
180-
const discText = trackRow.textContent || '';
181-
const match = discText.match(/\d+/);
182+
const match = trackRow.textContent.match(/\d+/);
182183

183184
if (match) {
184185
currentDisc = parseInt(match[0], 10);
@@ -190,17 +191,21 @@ export default class OtotoyProvider extends MetadataProvider {
190191
const trackNumberCell = trackRow.querySelector('.num');
191192
if (!trackNumberCell) continue;
192193

193-
const trackNumber = trackNumberCell.textContent.trim();
194+
const trackNumber = trackNumberCell.textContent?.trim() ?? '';
195+
if (!trackNumber) return undefined;
194196

195197
const titleSpan = trackRow.querySelector("td.item span[id^='title-']");
196198
if (!titleSpan) return undefined;
197199

198-
const title = titleSpan.textContent.trim();
200+
const title = titleSpan.textContent?.trim() ?? '';
201+
if (!title) return undefined;
199202

200203
const durationCell = trackRow.querySelectorAll('td')[3];
201204
if (!durationCell) continue;
202205

203-
const duration = durationCell.textContent.trim();
206+
const duration = durationCell.textContent?.trim() ?? '';
207+
if (!duration) return undefined;
208+
204209
tracks.push({
205210
title: title,
206211
discNumber: currentDisc ?? undefined,
@@ -236,39 +241,43 @@ export default class OtotoyProvider extends MetadataProvider {
236241
// * The release can have an "original" release date, a platform release date, or both. "Release date" is the preferred date.
237242
// * In the case that only one date is present, sometimes "Original" is used, sometimes not. Whatever's available will
238243
// be used.
239-
parseAlbumMeta(doc: Document): AlbumMeta | undefined {
240-
const albumMetadata = doc.querySelector<HTMLDivElement>('div.album-meta-data');
244+
parseAlbumMeta(doc: HTMLDocument): AlbumMeta | undefined {
245+
const albumMetadata = doc.querySelector('div.album-meta-data');
241246
if (!albumMetadata) return undefined;
242247

243-
const titleHeading = albumMetadata.querySelector<HTMLHeadingElement>('h1.album-title');
248+
const titleHeading = albumMetadata.querySelector('h1.album-title');
244249
if (!titleHeading) return undefined;
245250

246-
const title = titleHeading.textContent;
251+
const title = titleHeading.textContent?.trim();
252+
if (!title) return undefined;
247253

248-
const artistSpans = albumMetadata.querySelectorAll<HTMLSpanElement>('p.album-artist > span.album-artist');
254+
const artistSpans = Array.from(albumMetadata.querySelectorAll('p.album-artist > span.album-artist'));
249255
if (artistSpans.length === 0) return undefined;
250256

251257
const artists: Artist[] = [];
252258
for (const span of artistSpans) {
253-
const anchor = span.querySelector<HTMLAnchorElement>('a');
259+
const anchor = span.querySelector('a');
254260
if (!anchor) return undefined;
255261

256-
const id = anchor.href.match(this.entityPathPattern)?.[1];
262+
const id = anchor.getAttribute('href')?.match(this.entityPathPattern)?.[1];
257263
if (!id) return undefined;
258264

265+
const name = span.textContent?.trim();
266+
if (!name) return undefined;
267+
259268
artists.push({
260-
name: span.textContent?.trim(),
269+
name,
261270
id,
262271
});
263272
}
264273

265-
const details = albumMetadata.querySelector<HTMLDivElement>('div.detail');
274+
const details = albumMetadata.querySelector('div.detail');
266275
if (!details) return undefined;
267276

268277
let releaseDate: string | undefined;
269278
let originalReleaseDate: string | undefined;
270279

271-
const releaseElements = details.querySelectorAll<HTMLParagraphElement>('p.release-day');
280+
const releaseElements = details.querySelectorAll('p.release-day');
272281

273282
releaseElements.forEach((el) => {
274283
const text = el.textContent?.trim();
@@ -290,21 +299,24 @@ export default class OtotoyProvider extends MetadataProvider {
290299
releaseDate,
291300
};
292301

293-
const labelAnchor = details.querySelector<HTMLAnchorElement>('p.label-name > a');
302+
const labelAnchor = details.querySelector('p.label-name > a');
294303
if (!labelAnchor) return albumMeta;
295304

296-
const catalogIdParagraph = details.querySelector<HTMLParagraphElement>('p.catalog-id');
305+
const catalogIdParagraph = details.querySelector('p.catalog-id');
297306

298307
let catalogNumber = undefined;
299308
if (catalogIdParagraph) {
300-
catalogNumber = catalogIdParagraph.textContent.trim().match(/^Catalog number: (.*?)$/)?.[1];
309+
catalogNumber = catalogIdParagraph.textContent?.trim().match(/^Catalog number: (.*?)$/)?.[1];
301310
}
302311

303-
const labelId = labelAnchor.href.match(this.labelPathPattern)?.[1];
312+
const labelId = labelAnchor.getAttribute('href')?.match(this.labelPathPattern)?.[1];
304313
if (!labelId) return undefined;
305314

315+
const labelName = labelAnchor.textContent?.trim();
316+
if (!labelName) return undefined;
317+
306318
albumMeta.label = {
307-
name: labelAnchor.textContent,
319+
name: labelName,
308320
id: labelId,
309321
catalogNumber,
310322
};

0 commit comments

Comments
 (0)