diff options
| author | Fuwn <[email protected]> | 2026-03-01 16:20:51 -0800 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2026-03-01 16:21:02 -0800 |
| commit | eae5d24d9e79e59a19d4721caaeaa0ca650ecb33 (patch) | |
| tree | 1b685bb248e051dfa26d2bfdebe6689402dd93c5 /src/lib/Data/hololive.ts | |
| parent | chore(tooling): remove legacy eslint and prettier (diff) | |
| download | due.moe-eae5d24d9e79e59a19d4721caaeaa0ca650ecb33.tar.xz due.moe-eae5d24d9e79e59a19d4721caaeaa0ca650ecb33.zip | |
chore(biome): drop formatter style overrides
Diffstat (limited to 'src/lib/Data/hololive.ts')
| -rw-r--r-- | src/lib/Data/hololive.ts | 246 |
1 files changed, 129 insertions, 117 deletions
diff --git a/src/lib/Data/hololive.ts b/src/lib/Data/hololive.ts index 7bcb153a..b6ef637d 100644 --- a/src/lib/Data/hololive.ts +++ b/src/lib/Data/hololive.ts @@ -1,135 +1,143 @@ // https://github.com/wabilin/holo-schedule -function mapNodeList<E extends Element, T>(list: NodeListOf<E>, mapper: (ele: E) => T): T[] { - const ary: T[] = []; - list.forEach((node) => { - ary.push(mapper(node)); - }); - - return ary; +function mapNodeList<E extends Element, T>( + list: NodeListOf<E>, + mapper: (ele: E) => T, +): T[] { + const ary: T[] = []; + list.forEach((node) => { + ary.push(mapper(node)); + }); + + return ary; } function selectTrimTextContent(ele: Element, selector: string): string { - return ele.querySelector(selector)?.textContent?.trim() || ''; + return ele.querySelector(selector)?.textContent?.trim() || ""; } const livePreviewImageHosts: readonly string[] = [ - 'img.youtube.com', - 'schedule-static.hololive.tv' + "img.youtube.com", + "schedule-static.hololive.tv", ] as const; function hostOf(src: string) { - return new URL(src).host; + return new URL(src).host; } function dataFromAThumbnail(thumb: Element) { - const time = selectTrimTextContent(thumb, '.datetime'); - const name = selectTrimTextContent(thumb, '.name'); + const time = selectTrimTextContent(thumb, ".datetime"); + const name = selectTrimTextContent(thumb, ".name"); - const images = mapNodeList(thumb.querySelectorAll('img'), (img) => img.src); + const images = mapNodeList(thumb.querySelectorAll("img"), (img) => img.src); - const avatarImages = images.filter((src) => hostOf(src) === 'yt3.ggpht.com'); - const livePreviewImage = images.find((src) => livePreviewImageHosts.includes(hostOf(src))) || ''; + const avatarImages = images.filter((src) => hostOf(src) === "yt3.ggpht.com"); + const livePreviewImage = + images.find((src) => livePreviewImageHosts.includes(hostOf(src))) || ""; - return { - time, - name, - avatarImages, - livePreviewImage - }; + return { + time, + name, + avatarImages, + livePreviewImage, + }; } interface LiveBlock { - time: Date; - streamer: string; - avatarImages: string[]; - livePreviewImage: string; - link: string; - streaming: boolean; + time: Date; + streamer: string; + avatarImages: string[]; + livePreviewImage: string; + link: string; + streaming: boolean; } function parseToLiveBlocks(html: string): LiveBlock[] { - const parser = new DOMParser(); - const doc = parser.parseFromString(html, 'text/html'); - const year = new Date().getFullYear().toString(); - - const rows = doc.querySelectorAll('#all > .container > .row'); - - let date = ''; - const lives: LiveBlock[] = []; - - rows.forEach((row) => { - const dateDiv = row.querySelector('.holodule'); - if (dateDiv) { - date = dateDiv.textContent?.replace(/\s+/g, '') || ''; - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - date = date.match(/\d+\/\d+/)![0].replace('/', '-'); - - // const dateMatch = date.match(/\d+\/\d+/); - // - // date = dateMatch ? dateMatch[0].replace('/', '-') : ''; - } - - const allThumbnail = row.querySelectorAll('a.thumbnail'); - allThumbnail.forEach((t) => { - const thumbnail = t as HTMLAnchorElement; - const link = thumbnail.href; - const streaming = thumbnail.style.borderColor === 'red'; - // The dataFromAThumbnail function needs to be adjusted to work with the parsed document. - const { time, name, avatarImages, livePreviewImage } = dataFromAThumbnail(thumbnail); - - lives.push({ - link, - avatarImages, - livePreviewImage, - time: new Date(`${year}-${date}T${time}:00+09:00`), - streamer: name, - streaming - }); - }); - }); - - return lives; + const parser = new DOMParser(); + const doc = parser.parseFromString(html, "text/html"); + const year = new Date().getFullYear().toString(); + + const rows = doc.querySelectorAll("#all > .container > .row"); + + let date = ""; + const lives: LiveBlock[] = []; + + rows.forEach((row) => { + const dateDiv = row.querySelector(".holodule"); + if (dateDiv) { + date = dateDiv.textContent?.replace(/\s+/g, "") || ""; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + date = date.match(/\d+\/\d+/)![0].replace("/", "-"); + + // const dateMatch = date.match(/\d+\/\d+/); + // + // date = dateMatch ? dateMatch[0].replace('/', '-') : ''; + } + + const allThumbnail = row.querySelectorAll("a.thumbnail"); + allThumbnail.forEach((t) => { + const thumbnail = t as HTMLAnchorElement; + const link = thumbnail.href; + const streaming = thumbnail.style.borderColor === "red"; + // The dataFromAThumbnail function needs to be adjusted to work with the parsed document. + const { time, name, avatarImages, livePreviewImage } = + dataFromAThumbnail(thumbnail); + + lives.push({ + link, + avatarImages, + livePreviewImage, + time: new Date(`${year}-${date}T${time}:00+09:00`), + streamer: name, + streaming, + }); + }); + }); + + return lives; } export type StreamerImageDict = Record<string, string>; type ImageStreamerDict = Record<string, string>; -function nextStreamerImageDict(liveBlocks: LiveBlock[], oldDict: StreamerImageDict) { - const dict = { ...oldDict }; - liveBlocks.forEach(({ avatarImages: images, streamer }) => { - dict[streamer] = images[0]; - }); +function nextStreamerImageDict( + liveBlocks: LiveBlock[], + oldDict: StreamerImageDict, +) { + const dict = { ...oldDict }; + liveBlocks.forEach(({ avatarImages: images, streamer }) => { + dict[streamer] = images[0]; + }); - return dict; + return dict; } function reverseDict(dict: StreamerImageDict): ImageStreamerDict { - const reversed: ImageStreamerDict = {}; - Object.entries(dict).forEach(([streamer, img]) => { - reversed[img] = streamer; - }); + const reversed: ImageStreamerDict = {}; + Object.entries(dict).forEach(([streamer, img]) => { + reversed[img] = streamer; + }); - return reversed; + return reversed; } export interface LiveInfo { - time: Date; - link: string; - videoId: string; - streamer: string; - livePreviewImage: string; - guests: string[]; - streaming: boolean; + time: Date; + link: string; + videoId: string; + streamer: string; + livePreviewImage: string; + guests: string[]; + streaming: boolean; } interface ParseResult { - lives: LiveInfo[]; - dict: StreamerImageDict; + lives: LiveInfo[]; + dict: StreamerImageDict; } function getVideoId(link: string): string { - return link.replace('https://www.youtube.com/watch?v=', ''); + return link.replace("https://www.youtube.com/watch?v=", ""); } /** @@ -137,31 +145,35 @@ function getVideoId(link: string): string { * @param storedDict - An object stored { vtuberName: iconImageSrc } * @returns - Lives schedule and updated dict */ -export function parseScheduleHtml(html: string, storedDict: StreamerImageDict = {}): ParseResult { - const liveBlocks = parseToLiveBlocks(html); - const streamerImageDict = nextStreamerImageDict(liveBlocks, storedDict); - - const dict = reverseDict(streamerImageDict); - - const lives = liveBlocks.map((liveBlocks) => { - const { streamer, avatarImages, time, link, livePreviewImage, streaming } = liveBlocks; - - const guests = avatarImages - .splice(1) - .map((x) => dict[x]) - .filter(Boolean); - const videoId = getVideoId(link); - - return { - time, - streamer, - guests, - link, - videoId, - livePreviewImage, - streaming - }; - }); - - return { lives, dict: streamerImageDict }; +export function parseScheduleHtml( + html: string, + storedDict: StreamerImageDict = {}, +): ParseResult { + const liveBlocks = parseToLiveBlocks(html); + const streamerImageDict = nextStreamerImageDict(liveBlocks, storedDict); + + const dict = reverseDict(streamerImageDict); + + const lives = liveBlocks.map((liveBlocks) => { + const { streamer, avatarImages, time, link, livePreviewImage, streaming } = + liveBlocks; + + const guests = avatarImages + .splice(1) + .map((x) => dict[x]) + .filter(Boolean); + const videoId = getVideoId(link); + + return { + time, + streamer, + guests, + link, + videoId, + livePreviewImage, + streaming, + }; + }); + + return { lives, dict: streamerImageDict }; } |