diff options
| author | Fuwn <[email protected]> | 2024-02-07 01:07:34 -0800 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2024-02-07 01:07:34 -0800 |
| commit | b2cd85ceddb1d39924b858cb198a9784954fd4c5 (patch) | |
| tree | 62c7afc68bbdf06da7221d0763db0c03bc28fcfa /src | |
| parent | fix(match): skip specials and ovas in schedule (diff) | |
| download | due.moe-b2cd85ceddb1d39924b858cb198a9784954fd4c5.tar.xz due.moe-b2cd85ceddb1d39924b858cb198a9784954fd4c5.zip | |
feat(hololive): hololive schedule
Diffstat (limited to 'src')
| -rw-r--r-- | src/app.html | 2 | ||||
| -rw-r--r-- | src/lib/Loading/Message.svelte | 2 | ||||
| -rw-r--r-- | src/lib/Locale/english.ts | 3 | ||||
| -rw-r--r-- | src/lib/Locale/japanese.ts | 3 | ||||
| -rw-r--r-- | src/lib/Locale/layout.ts | 1 | ||||
| -rw-r--r-- | src/routes/+layout.svelte | 5 | ||||
| -rw-r--r-- | src/routes/api/hololive/+server.ts | 10 | ||||
| -rw-r--r-- | src/routes/hololive/+page.svelte | 127 |
8 files changed, 148 insertions, 5 deletions
diff --git a/src/app.html b/src/app.html index 293aa41b..5a221a19 100644 --- a/src/app.html +++ b/src/app.html @@ -79,6 +79,8 @@ ></script> <script> + var global = global || window; + aoButa = ''; document.addEventListener('DOMContentLoaded', () => { diff --git a/src/lib/Loading/Message.svelte b/src/lib/Loading/Message.svelte index 758c4039..247ad3b3 100644 --- a/src/lib/Loading/Message.svelte +++ b/src/lib/Loading/Message.svelte @@ -20,7 +20,7 @@ {/if} {#if message} - <p /> + <br /> {message} {/if} diff --git a/src/lib/Locale/english.ts b/src/lib/Locale/english.ts index fe449d34..5477632c 100644 --- a/src/lib/Locale/english.ts +++ b/src/lib/Locale/english.ts @@ -11,7 +11,8 @@ const English: Locale = { profile: 'Profile', logIn: 'Log in with AniList', logOut: 'Log out', - schedule: 'Schedule' + schedule: 'Schedule', + hololive: 'hololive' }, settings: { fields: { diff --git a/src/lib/Locale/japanese.ts b/src/lib/Locale/japanese.ts index 7326a3a0..09b7dc32 100644 --- a/src/lib/Locale/japanese.ts +++ b/src/lib/Locale/japanese.ts @@ -11,7 +11,8 @@ const Japanese: Locale = { profile: 'プロフィール', logIn: 'AniListでログインする', logOut: 'ログアウト', - schedule: 'スケジュール' + schedule: 'スケジュール', + hololive: 'ホロライブ' }, settings: { fields: { diff --git a/src/lib/Locale/layout.ts b/src/lib/Locale/layout.ts index d2af0a4c..e1c81e0f 100644 --- a/src/lib/Locale/layout.ts +++ b/src/lib/Locale/layout.ts @@ -16,6 +16,7 @@ export interface Locale { logIn: LocaleValue; logOut: LocaleValue; schedule: LocaleValue; + hololive: LocaleValue; }; settings: { fields: { diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index e8fb9f9b..cc09bb94 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -113,8 +113,9 @@ <Dropdown items={[ { name: $locale().navigation.subtitleSchedule, url: root('/schedule') }, - { name: $locale().navigation.newReleases, url: root('/updates') }, - { name: $locale().tools.tool.characterBirthdays.short, url: root('/birthdays') } + { name: $locale().navigation.hololive, url: root('/hololive') }, + { name: $locale().tools.tool.characterBirthdays.short, url: root('/birthdays') }, + { name: $locale().navigation.newReleases, url: root('/updates') } ]} title={$locale().navigation.schedule} /> diff --git a/src/routes/api/hololive/+server.ts b/src/routes/api/hololive/+server.ts new file mode 100644 index 00000000..331d776a --- /dev/null +++ b/src/routes/api/hololive/+server.ts @@ -0,0 +1,10 @@ +import parseScheduleHtml from 'holo-schedule'; +import getScheduleHtml from 'holo-schedule/lib/getScheduleHtml'; + +export const GET = async () => + Response.json(parseScheduleHtml(await getScheduleHtml()), { + headers: { + 'Access-Control-Allow-Origin': 'https://due.moe', + 'Cache-Control': 'public, max-age=300, s-maxage=300' + } + }); diff --git a/src/routes/hololive/+page.svelte b/src/routes/hololive/+page.svelte new file mode 100644 index 00000000..bd49bbf8 --- /dev/null +++ b/src/routes/hololive/+page.svelte @@ -0,0 +1,127 @@ +<script lang="ts"> + import { onMount } from 'svelte'; + import Message from '$lib/Loading/Message.svelte'; + import Skeleton from '$lib/Loading/Skeleton.svelte'; + + interface ParseResult { + lives: { + time: Date; + link: string; + videoId: string; + streamer: string; + livePreviewImage: string; + guests: string[]; + streaming: boolean; + }[]; + dict: Record<string, string>; + } + + let schedulePromise: Promise<Response>; + + onMount(() => (schedulePromise = fetch('/api/hololive'))); + + const typeSchedule = (schedule: any) => schedule as ParseResult; +</script> + +{#await schedulePromise} + <Message message="Loading schedule ..." /> + + <Skeleton grid={true} count={100} width="49%" height="16.25em" /> +{:then scheduleResponse} + {#if scheduleResponse} + {#await scheduleResponse.json()} + <Message message="Parsing schedule ..." /> + + <Skeleton grid={true} count={100} width="49%" height="16.25em" /> + {:then untypedSchedule} + {@const schedule = typeSchedule(untypedSchedule)} + + {#if schedule.lives.length === 0} + <Message message="No upcoming streams." /> + {/if} + + <div class="container"> + {#each schedule.lives + .filter((live) => { + const time = new Date(live.time); + + return time.getTime() > Date.now() - 12 * 60 * 60 * 1000 || time.getTime() > Date.now() || live.streaming; + }) + .sort((a, b) => { + if (a.streaming && !b.streaming) return -1; + + if (!a.streaming && b.streaming) return 1; + + return new Date(a.time).getTime() - new Date(b.time).getTime(); + }) as live} + <div class="stream card"> + <p> + [{#if live.streaming} + <b class="live">LIVE</b>{:else} + <span class="upcoming">Upcoming</span>{/if}] + <b>{live.streamer}</b> <span class="opaque">|</span> + {new Date(live.time).toLocaleString()} + {#if live.guests.length > 0} + <br /> + <small> + With {live.guests.join(', ').replace(/, ([^,]+)$/, ', & $1')} + </small> + {/if} + </p> + + <a href={live.link} class="preview"> + <img src={live.livePreviewImage} alt="Stream Preview" /> + </a> + </div> + {/each} + </div> + {:catch} + <Message message="Could not parse schedule. Please try again later." loader="ripple" /> + {/await} + {:else} + <Message message="Loading schedule ..." /> + + <Skeleton grid={true} count={100} width="49%" height="16.25em" /> + {/if} +{:catch} + <Message message="Could not load schedule. Please try again later." loader="ripple" /> +{/await} + +<style lang="scss"> + .preview { + // margin: 0.15rem; + + img { + border-radius: 8px; + height: 20vh; + transition: transform 0.3s ease; + } + + &:hover { + img { + position: relative; + z-index: 2; + transition: transform 0.3s ease; + transform: scale(1.05); + } + } + + display: flex; + justify-content: center; + align-items: center; + } + + .live { + color: var(--base0F); + } + + .upcoming { + color: var(--base0C); + } + + .container { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(22.5em, 1fr)); + gap: 0.5em; + } +</style> |