aboutsummaryrefslogtreecommitdiff
path: root/src/lib/Data/hololive.ts
diff options
context:
space:
mode:
authorFuwn <[email protected]>2026-03-01 16:20:51 -0800
committerFuwn <[email protected]>2026-03-01 16:21:02 -0800
commiteae5d24d9e79e59a19d4721caaeaa0ca650ecb33 (patch)
tree1b685bb248e051dfa26d2bfdebe6689402dd93c5 /src/lib/Data/hololive.ts
parentchore(tooling): remove legacy eslint and prettier (diff)
downloaddue.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.ts246
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 };
}