mastoradio-fork/src/services/misc.js

198 lines
4.6 KiB
JavaScript
Raw Normal View History

2020-02-15 23:12:53 +01:00
import { execPipe, asyncFilter, asyncMap, map, findOr } from 'iter-tools'
2020-01-20 03:26:18 +01:00
export const tap = f => x => {
f(x)
return x
}
export const queue = () => {
const deferred = defer()
let promise = deferred.promise
const enqueue = f => {
promise = promise.then(tap(f))
}
return { enqueue, run: deferred.resolve }
}
export const defer = () => {
let resolve
let reject
const promise = new Promise((res, rej) => {
resolve = res
reject = rej
})
return { resolve, reject, promise }
}
export async function* observableToAsyncIterator(observable) {
const buffer = [defer()]
2020-02-13 16:30:25 +01:00
const next = value => {
buffer[buffer.length - 1].resolve(value)
2020-01-20 03:26:18 +01:00
buffer.push(defer())
}
2020-02-13 16:30:25 +01:00
const complete = value => {
buffer[buffer.length - 1].resolve(value)
2020-01-20 03:26:18 +01:00
}
2020-02-13 16:30:25 +01:00
const error = (error) => {
buffer[buffer.length - 1].reject(error)
2020-01-20 03:26:18 +01:00
}
const subscription = observable.subscribe({ next, error, complete })
try {
while (true) {
2020-01-27 17:05:17 +01:00
const value = await buffer[0].promise
2020-02-13 16:30:25 +01:00
buffer.shift()
2020-01-20 03:26:18 +01:00
2020-02-13 16:30:25 +01:00
if (buffer.length) {
2020-01-20 03:26:18 +01:00
yield value
2020-02-13 16:30:25 +01:00
} else {
return value
2020-01-20 03:26:18 +01:00
}
}
} finally {
subscription.unsubscribe()
}
}
export function intersection(xs, ys) {
return xs.filter(x => ys.includes(x))
}
export const secondsToElapsedTime = (seconds) => {
const parts = [
Math.floor(seconds / 3600),
Math.floor(seconds / 60) % 60,
Math.floor(seconds) % 60
]
return parts
.filter((value, index) => value > 0 || index > 0)
.map(value => value < 10 ? '0' + value : value)
.join(':')
}
2020-02-15 23:12:53 +01:00
export async function* raceIterator(iterators) {
const values = iterators.map(iterator => iterator.next())
2020-01-20 03:26:18 +01:00
2020-02-15 23:12:53 +01:00
while (true) {
const promises = values.map((promise, index) => promise.then(result => ({ index, result })))
const { index, result: { done, value } } = await Promise.race(promises)
values[index] = iterators[index].next()
yield value
}
}
2020-02-21 23:31:30 +01:00
export async function* tracksIterator(statusesGenerator, cache) {
try {
yield* execPipe(
statusesGenerator,
asyncFilter(track => track != null), // should not be necessary
asyncFilter(notKnown(cache)),
asyncMap(completeTrack)
)
} finally {
statusesGenerator.return()
}
2020-02-16 17:02:39 +01:00
}
2020-02-15 23:12:53 +01:00
2020-02-16 17:02:39 +01:00
const notKnown = cache => track => {
if (!track) {
console.error(`No track, should not happen here`)
return false
}
2020-02-15 23:12:53 +01:00
2020-02-16 17:02:39 +01:00
const isKnown = (values) => {
if (cache.has(values)) {
console.log(`Drop already processed ${values.join(':')}`)
return true
} else {
cache.add(values)
return false
2020-02-15 23:12:53 +01:00
}
}
2020-02-16 17:02:39 +01:00
switch (track.referer.credentials.type) {
default:
throw new Error()
case 'mastodon':
if (isKnown([
'referer',
'mastodon',
track.referer.credentials.domain,
track.referer.credentials.id
])) {
return false
2020-02-15 23:12:53 +01:00
}
2020-02-16 17:02:39 +01:00
break;
2020-02-15 23:12:53 +01:00
}
2020-02-16 17:02:39 +01:00
if (track.media == null) {
return false
}
2020-02-15 23:12:53 +01:00
2020-02-16 17:02:39 +01:00
switch (track.media.credentials.type) {
default:
throw new Error()
2020-02-15 23:12:53 +01:00
2020-02-16 17:02:39 +01:00
case 'youtube':
if (isKnown([
'media',
'youtube',
track.media.credentials.id
])) {
return false
}
2020-02-15 23:12:53 +01:00
2020-02-16 17:02:39 +01:00
break
2020-02-15 23:12:53 +01:00
}
2020-02-16 17:02:39 +01:00
return true
2020-02-15 23:12:53 +01:00
}
2020-02-16 17:02:39 +01:00
const completeTrack = async track => {
const metadata = await fetchMetadata(track.media)
2020-02-16 19:54:55 +01:00
return {
...track,
title: metadata.title,
cover: metadata.thumbnail_url
}
2020-02-16 17:02:39 +01:00
}
2020-02-15 23:12:53 +01:00
2020-02-16 17:02:39 +01:00
export const urlsToMedia = urls => {
return execPipe(
2020-02-15 23:12:53 +01:00
urls,
map(parseSource),
findOr(null, x => x !== null)
2020-01-20 03:26:18 +01:00
)
2020-02-15 23:12:53 +01:00
}
2020-01-20 03:26:18 +01:00
2020-02-15 23:12:53 +01:00
const parseSource = (url) => {
const { hostname, pathname, searchParams } = new URL(url)
2020-01-20 03:26:18 +01:00
2020-02-15 23:12:53 +01:00
if (['youtube.com', 'm.youtube.com', 'music.youtube.com'].includes(hostname) && searchParams.has('v')) {
return { url, credentials: { type: 'youtube', id: searchParams.get('v') } }
} else if (hostname === 'youtu.be') {
return { url, credentials: { type: 'youtube', id: pathname.substring(1) } }
} else {
return null
2020-01-20 03:26:18 +01:00
}
}
2020-02-15 23:12:53 +01:00
const fetchMetadata = (media) => {
switch (media.credentials.type) {
case 'youtube':
return fetch(`https://noembed.com/embed?url=https://www.youtube.com/watch?v=${media.credentials.id}`)
.then(response => response.json())
}
2020-01-20 03:26:18 +01:00
}