quick url matcher/transformer refact

This commit is contained in:
wryk 2020-01-10 14:30:17 +01:00
parent 87c0398f0a
commit e6e88fddd5
4 changed files with 77 additions and 60 deletions

View File

@ -11,9 +11,12 @@
<div> <div>
{#each $entries as entry} {#each $entries as entry}
<div class="entry" class:active={entry === $currentEntry} on:click={() => $currentEntry = entry}> <div class="entry" class:active={entry === $currentEntry} on:click={() => $currentEntry = entry}>
<div>{entry.metadata.title}</div> <div>{entry.data.metadata.title}</div>
<div>
<small>{entry.url}</small>
<b>{entry.status.account.username} <small>{entry.status.account.acct}</small></b> <b>{entry.status.account.username} <small>{entry.status.account.acct}</small></b>
<small>{entry.tags}</small> </div>
</div> </div>
{/each} {/each}
</div> </div>
@ -34,13 +37,12 @@
import { playing, loading, entry as currentEntry, entries } from '/store.js' import { playing, loading, entry as currentEntry, entries } from '/store.js'
onMount(() => { onMount(() => {
const unsub = entries.subscribe(async (xs) => { const unsubscribe = entries.subscribe(async (xs) => {
if (xs.length) { if (xs.length) {
const [firstEntry] = xs const [firstEntry] = xs
currentEntry.set(firstEntry) currentEntry.set(firstEntry)
unsub() unsubscribe()
} }
}) })
entries.load(7) entries.load(7)

View File

@ -17,7 +17,7 @@
$: updateVolume($volume) $: updateVolume($volume)
const updateEntry = (entry) => { 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) => { const updatePlaying = (playing) => {
@ -37,7 +37,7 @@
width: 300, width: 300,
height: 300, height: 300,
autoplay: $playing, autoplay: $playing,
controls: true, // debug only controls: false,
keyboard: false, keyboard: false,
fullscreen: false, fullscreen: false,
modestBranding: true, modestBranding: true,
@ -49,7 +49,11 @@
updateVolume($volume) updateVolume($volume)
player.on('ended', () => entry.next()) player.on('ended', () => entry.next())
player.on('unplayable', () => entry.next())
player.on('unplayable', (...args) => {
console.log('unplayable', ...args)
entry.next()
})
}) })
</script> </script>

View File

@ -1,9 +1,9 @@
import { writable, get } from 'svelte/store' import { writable, get } from 'svelte/store'
import * as util from '/util.js' 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', 'np',
'nowplaying', 'nowplaying',
'tootradio', 'tootradio',
@ -14,7 +14,7 @@ export const playing = writable(true)
export const muted = writableLocalStorage('muted', false) export const muted = writableLocalStorage('muted', false)
export const volume = writableLocalStorage('volume', 100) export const volume = writableLocalStorage('volume', 100)
export const entries = entriesStore() export const entries = entriesStore(get(domain), get(hashtags))
export const entry = entryStore(entries) export const entry = entryStore(entries)
@ -23,7 +23,7 @@ export const entry = entryStore(entries)
function writableLocalStorage(key, value) { function writableLocalStorage(key, value) {
const item = JSON.parse(localStorage.getItem(key)) const item = JSON.parse(localStorage.getItem(key))
const store = writable(item === null ? value : item) 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 return store
} }
@ -80,8 +80,8 @@ function entryStore(entries) {
return { subscribe, set: select, previous, next } return { subscribe, set: select, previous, next }
} }
function entriesStore() { function entriesStore(domain, hashtags) {
const entriesSteam = util.statusesToEntries(util.statusesStreaming()) const entriesSteam = util.statusesToEntries(util.statusesStreaming(domain, hashtags))
const store = writable([]) const store = writable([])
const { update, subscribe } = store const { update, subscribe } = store
@ -91,6 +91,7 @@ function entriesStore() {
const iteratorResult = await entriesSteam.next() const iteratorResult = await entriesSteam.next()
if (iteratorResult.value) { if (iteratorResult.value) {
// console.log(iteratorResult.value)
update(entries => [...entries, iteratorResult.value]) update(entries => [...entries, iteratorResult.value])
} else { } else {
break break

View File

@ -1,8 +1,11 @@
import getUrls from 'get-urls' import getUrls from 'get-urls'
import { pipe, asyncFilter, asyncMap, asyncTap, asyncTake } from 'iter-tools' import { pipe, asyncFilter, asyncMap, asyncTap, asyncTake } from 'iter-tools'
import YouTubePlayer from 'yt-player'
export async function* statusesStreaming() { export async function* statusesStreaming(domain, [hashtag]) {
let { statuses, nextLink, previousLink } = await fetchTimeline('https://eldritch.cafe/api/v1/timelines/tag/np') const initialLink = `https://${domain}/api/v1/timelines/tag/${hashtag}?limit=40`
let { statuses, nextLink, previousLink } = await fetchTimeline(initialLink)
yield* statuses yield* statuses
@ -15,50 +18,10 @@ export async function* statusesStreaming() {
} }
export const statusesToEntries = pipe( export const statusesToEntries = pipe(
asyncMap(status => ({ status, urls: Array.from(getUrls(status.content)).filter(isSupportedUrl) })), asyncMap(statusToEntry),
asyncFilter(entry => entry.urls.length > 0), asyncFilter(entry => entry.type !== 'unsupported')
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 }
})
) )
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) { export async function fetchTimeline(url) {
const urlBuilder = new URL(url) const urlBuilder = new URL(url)
urlBuilder.searchParams.set('limit', 40) urlBuilder.searchParams.set('limit', 40)
@ -72,7 +35,7 @@ export async function fetchTimeline(url) {
const LINK_RE = /<(.+?)>; rel="(\w+)"/gi const LINK_RE = /<(.+?)>; rel="(\w+)"/gi
export function parseLinkHeader(link) { function parseLinkHeader(link) {
const links = {} const links = {}
for (const [ , url, name ] of link.matchAll(LINK_RE)) { for (const [ , url, name ] of link.matchAll(LINK_RE)) {
@ -81,3 +44,50 @@ export function parseLinkHeader(link) {
return links 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))
}