diff --git a/src/components/App.svelte b/src/components/App.svelte index ff56c8e..32e2ae2 100644 --- a/src/components/App.svelte +++ b/src/components/App.svelte @@ -11,9 +11,12 @@
{#each $entries as entry}
$currentEntry = entry}> -
{entry.metadata.title}
- {entry.status.account.username} {entry.status.account.acct} - {entry.tags} +
{entry.data.metadata.title}
+ +
+ {entry.url} + {entry.status.account.username} {entry.status.account.acct} +
{/each}
@@ -34,13 +37,12 @@ import { playing, loading, entry as currentEntry, entries } from '/store.js' onMount(() => { - const unsub = entries.subscribe(async (xs) => { + const unsubscribe = entries.subscribe(async (xs) => { if (xs.length) { const [firstEntry] = xs currentEntry.set(firstEntry) - unsub() + unsubscribe() } - }) entries.load(7) diff --git a/src/components/Viewer.svelte b/src/components/Viewer.svelte index 9f2a632..c3b0fac 100644 --- a/src/components/Viewer.svelte +++ b/src/components/Viewer.svelte @@ -17,7 +17,7 @@ $: updateVolume($volume) const updateEntry = (entry) => { - if (player && entry) player.load(entry.id, get(playing)) + if (player && entry) player.load(entry.data.id, get(playing)) } const updatePlaying = (playing) => { @@ -37,7 +37,7 @@ width: 300, height: 300, autoplay: $playing, - controls: true, // debug only + controls: false, keyboard: false, fullscreen: false, modestBranding: true, @@ -49,7 +49,11 @@ updateVolume($volume) player.on('ended', () => entry.next()) - player.on('unplayable', () => entry.next()) + + player.on('unplayable', (...args) => { + console.log('unplayable', ...args) + entry.next() + }) }) diff --git a/src/store.js b/src/store.js index 983a3a2..b6f2e68 100644 --- a/src/store.js +++ b/src/store.js @@ -1,9 +1,9 @@ import { writable, get } from 'svelte/store' import * as util from '/util.js' -export const domain = writable('eldritch.cafe') +export const domain = writableLocalStorage('domain', 'eldritch.cafe') -export const hashtags = writable([ +export const hashtags = writableLocalStorage('hashtags', [ 'np', 'nowplaying', 'tootradio', @@ -14,7 +14,7 @@ export const playing = writable(true) export const muted = writableLocalStorage('muted', false) export const volume = writableLocalStorage('volume', 100) -export const entries = entriesStore() +export const entries = entriesStore(get(domain), get(hashtags)) export const entry = entryStore(entries) @@ -23,7 +23,7 @@ export const entry = entryStore(entries) function writableLocalStorage(key, value) { const item = JSON.parse(localStorage.getItem(key)) const store = writable(item === null ? value : item) - const unsubscribe = store.subscribe(x => localStorage.setItem(key, x)) + const unsubscribe = store.subscribe(x => localStorage.setItem(key, JSON.stringify(x))) return store } @@ -80,8 +80,8 @@ function entryStore(entries) { return { subscribe, set: select, previous, next } } -function entriesStore() { - const entriesSteam = util.statusesToEntries(util.statusesStreaming()) +function entriesStore(domain, hashtags) { + const entriesSteam = util.statusesToEntries(util.statusesStreaming(domain, hashtags)) const store = writable([]) const { update, subscribe } = store @@ -91,6 +91,7 @@ function entriesStore() { const iteratorResult = await entriesSteam.next() if (iteratorResult.value) { + // console.log(iteratorResult.value) update(entries => [...entries, iteratorResult.value]) } else { break diff --git a/src/util.js b/src/util.js index 319a920..84c1f1e 100644 --- a/src/util.js +++ b/src/util.js @@ -1,8 +1,11 @@ import getUrls from 'get-urls' import { pipe, asyncFilter, asyncMap, asyncTap, asyncTake } from 'iter-tools' +import YouTubePlayer from 'yt-player' -export async function* statusesStreaming() { - let { statuses, nextLink, previousLink } = await fetchTimeline('https://eldritch.cafe/api/v1/timelines/tag/np') +export async function* statusesStreaming(domain, [hashtag]) { + const initialLink = `https://${domain}/api/v1/timelines/tag/${hashtag}?limit=40` + + let { statuses, nextLink, previousLink } = await fetchTimeline(initialLink) yield* statuses @@ -15,50 +18,10 @@ export async function* statusesStreaming() { } export const statusesToEntries = pipe( - asyncMap(status => ({ status, urls: Array.from(getUrls(status.content)).filter(isSupportedUrl) })), - asyncFilter(entry => entry.urls.length > 0), - asyncMap(async ({ status, urls }) => { - const [url] = urls - const id = getYoutubeVideoId(url) - - const tags = intersection(status.tags.map(tag => tag.name), [ - 'np', - 'nowplaying', - 'tootradio', - 'pouetradio' - ]) - - const metadata = await fetchYoutubeMetadata(id) - - return { status, url, id, tags, metadata } - }) + asyncMap(statusToEntry), + asyncFilter(entry => entry.type !== 'unsupported') ) -function fetchYoutubeMetadata(id) { - return fetch(`https://noembed.com/embed?url=https://www.youtube.com/watch?v=${id}`) - .then(response => response.json()) - -} - -export function isSupportedUrl(urlAsString) { - const url = new URL(urlAsString) - - const hosts = [ - 'youtube.com', - 'music.youtube.com' - ] - - return hosts.includes(url.hostname) && url.searchParams.has('v') -} - -export function getYoutubeVideoId(urlAsString) { - return new URL(urlAsString).searchParams.get('v') -} - -export function intersection(xs, ys) { - return xs.filter(x => ys.includes(x)); -} - export async function fetchTimeline(url) { const urlBuilder = new URL(url) urlBuilder.searchParams.set('limit', 40) @@ -72,7 +35,7 @@ export async function fetchTimeline(url) { const LINK_RE = /<(.+?)>; rel="(\w+)"/gi -export function parseLinkHeader(link) { +function parseLinkHeader(link) { const links = {} for (const [ , url, name ] of link.matchAll(LINK_RE)) { @@ -80,4 +43,51 @@ export function parseLinkHeader(link) { } return links +} + +async function statusToEntry(status) { + const urls = getUrls(status.content) + + for await (const url of urls) { + const { type, data } = await urlToEntry(url) + + if (type !== 'unsupported') { + return { status, url, type, data } + } + } + + return { type: 'unsupported' } +} + +async function urlToEntry(urlAsString) { + const url = new URL(urlAsString) + + console.log(url.hostname) + + if (['youtube.com', 'music.youtube.com'].includes(url.hostname) && url.searchParams.has('v')) { + return await mkYoutubeEntry(url.searchParams.get('v')) + } else if (url.hostname === 'youtu.be') { + return await mkYoutubeEntry(url.pathname.substring(1)) + } else { + return { type: 'unsupported' } + } +} + +async function mkYoutubeEntry(id) { + return { + type: 'youtube', + data: { + id, + metadata: await fetchYoutubeMetadata(id) + } + } +} + +function fetchYoutubeMetadata(id) { + return fetch(`https://noembed.com/embed?url=https://www.youtube.com/watch?v=${id}`) + .then(response => response.json()) +} + +export function intersection(xs, ys) { + return xs.filter(x => ys.includes(x)) } \ No newline at end of file