forked from Mastodon/mastoradio-la-radio-di-mastodon
quick url matcher/transformer refact
This commit is contained in:
parent
87c0398f0a
commit
e6e88fddd5
|
@ -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)
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
13
src/store.js
13
src/store.js
|
@ -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
|
||||||
|
|
100
src/util.js
100
src/util.js
|
@ -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))
|
||||||
|
}
|
Loading…
Reference in New Issue