From 58b24241bdc2e708e761d1639d7242216b17c28e Mon Sep 17 00:00:00 2001
From: Fuwn <contact@fuwn.me>
Date: Thu, 15 Feb 2024 10:50:38 -0800
Subject: feat(hololive): push pinned streamers to top

---
 src/routes/hololive/+page.svelte | 61 +++++++++++++++++++++++++++-------------
 1 file changed, 42 insertions(+), 19 deletions(-)

(limited to 'src/routes/hololive/+page.svelte')

diff --git a/src/routes/hololive/+page.svelte b/src/routes/hololive/+page.svelte
index aace56a3..dca0b14a 100644
--- a/src/routes/hololive/+page.svelte
+++ b/src/routes/hololive/+page.svelte
@@ -65,6 +65,24 @@
 				'Content-Type': 'application/json'
 			}
 		}).then(getPinnedStreams);
+
+	const getClosestUpcomingPinnedStreams = (schedule: ParseResult) => {
+		const now = Date.now();
+		let closestUpcomingPinnedStreams = new Map();
+
+		schedule.lives.forEach((live) => {
+			const liveTime = new Date(live.time).getTime();
+
+			if (liveTime > now && pinnedStreams.includes(live.streamer)) {
+				const existing = closestUpcomingPinnedStreams.get(live.streamer);
+
+				if (!existing || liveTime < new Date(existing.time).getTime())
+					closestUpcomingPinnedStreams.set(live.streamer, live);
+			}
+		});
+
+		return closestUpcomingPinnedStreams;
+	};
 </script>
 
 <HeadTitle route="hololive Schedule" path="/hololive" />
@@ -81,6 +99,7 @@
 			<Skeleton grid={true} count={100} width="49%" height="16.25em" />
 		{:then untypedSchedule}
 			{@const schedule = typeSchedule(parseScheduleHtml(untypedSchedule))}
+			{@const closestUpcomingPinnedStreams = getClosestUpcomingPinnedStreams(schedule)}
 
 			{#if schedule.lives.length === 0}
 				<Message message="No upcoming streams." loader="ripple" />
@@ -89,32 +108,36 @@
 			<div class="container">
 				{#each schedule.lives
 					.filter((live) => {
-						const time = new Date(live.time);
+						try {
+							$locale().hololive.dateFormatter(new Date(live.time));
 
-						return time.getTime() > Date.now() - 12 * 60 * 60 * 1000 || time.getTime() > Date.now() || live.streaming;
+							return true;
+						} catch {
+							return false;
+						}
 					})
 					.sort((a, b) => {
-						if (pinnedStreams.includes(a.streamer) && a.streaming) {
-							return -1;
-						} else if (pinnedStreams.includes(b.streamer) && b.streaming) {
-							return 1;
-						}
+						const now = Date.now();
+						const aTime = new Date(a.time).getTime();
+						const bTime = new Date(b.time).getTime();
+						const aIsLive = a.streaming;
+						const bIsLive = b.streaming;
+						const aIsClosestPinned = closestUpcomingPinnedStreams.get(a.streamer) === a;
+						const bIsClosestPinned = closestUpcomingPinnedStreams.get(b.streamer) === b;
 
-						if (a.streaming && !b.streaming) {
-							return -1;
-						} else if (!a.streaming && b.streaming) {
-							return 1;
-						}
+						if (aIsLive && pinnedStreams.includes(a.streamer)) return -1;
+						if (bIsLive && pinnedStreams.includes(b.streamer)) return 1;
 
-						const aTime = new Date(a.time).getTime();
+						if (aIsLive && !bIsLive) return -1;
+						if (bIsLive && !aIsLive) return 1;
 
-						if (aTime < Date.now() && new Date(b.time).getTime() > Date.now()) {
-							return 1;
-						} else if (aTime > Date.now() && new Date(b.time).getTime() < Date.now()) {
-							return -1;
-						}
+						if (aIsClosestPinned && !bIsClosestPinned) return -1;
+						if (bIsClosestPinned && !aIsClosestPinned) return 1;
+
+						if (aTime > now && !(aTime < now && !aIsLive)) return -1;
+						if (bTime > now && !(bTime < now && !bIsLive)) return 1;
 
-						return aTime - new Date(b.time).getTime();
+						return bTime - aTime;
 					}) as live}
 					<div class="stream card">
 						{#if $identity.id !== -2}
-- 
cgit v1.2.3

