-
{#if $current}{$current.title}{/if}
+
{#if $current}{$current.media.title}{/if}
{#if $current}shared by {$current.referer.username}{/if}
diff --git a/src/services/mastodon.js b/src/services/mastodon.js
index 4ca5657..247a1d4 100644
--- a/src/services/mastodon.js
+++ b/src/services/mastodon.js
@@ -4,11 +4,11 @@ import { urlsToMedia } from '/services/misc.js'
const LINK_RE = /<(.+?)>; rel="(\w+)"/gi
-function parseLinkHeader(link) {
- const links = {}
+function parseLinkHeader(linkHeader) {
+ const links = new Map()
- for (const [ , url, name ] of link.matchAll(LINK_RE)) {
- links[name] = url
+ for (const [ , url, name ] of linkHeader.matchAll(LINK_RE)) {
+ links.set(name, url)
}
return links
@@ -74,7 +74,7 @@ export async function* hashtagTimelineIterator (domain, hashtag) {
const response = await fetch(nextLink)
nextLink = response.headers.has('link')
- ? parseLinkHeader(response.headers.get('link')).next
+ ? parseLinkHeader(response.headers.get('link')).get('next')
: null
const statuses = await response.json()
@@ -132,15 +132,11 @@ export async function* hashtagsIterator(domain, hashtags) {
}
}
-
const processStatus = (domain, status) => ({
- title: '',
+ username: status.account.username,
+ content: status.content,
date: new Date(status.created_at),
- referer: {
- username: status.account.username,
- url: status.url,
- credentials: { type: 'mastodon', domain, id: status.id }
- },
- media: urlsToMedia(getUrls(status.content))
+ url: status.url,
+ credentials: { type: 'mastodon', domain, id: status.id }
})
diff --git a/src/services/misc.js b/src/services/misc.js
index db7e2e3..ca87ba0 100644
--- a/src/services/misc.js
+++ b/src/services/misc.js
@@ -1,21 +1,12 @@
-import { execPipe, asyncFilter, asyncMap, map, findOr } from 'iter-tools'
+import getUrls from 'get-urls'
+import { execPipe, asyncFilter, asyncMap, map, take, filter, asyncFlatMap, toArray } from 'iter-tools'
+import { share } from '/routes.js'
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
@@ -28,42 +19,15 @@ export const defer = () => {
return { resolve, reject, promise }
}
-export async function* observableToAsyncIterator(observable) {
- const buffer = [defer()]
+export const queue = () => {
+ const deferred = defer()
+ let promise = deferred.promise
- const next = value => {
- buffer[buffer.length - 1].resolve(value)
- buffer.push(defer())
+ const enqueue = f => {
+ promise = promise.then(tap(f))
}
- const complete = value => {
- buffer[buffer.length - 1].resolve(value)
- }
-
- const error = (error) => {
- buffer[buffer.length - 1].reject(error)
- }
-
- const subscription = observable.subscribe({ next, error, complete })
-
- try {
- while (true) {
- const value = await buffer[0].promise
- buffer.shift()
-
- if (buffer.length) {
- yield value
- } else {
- return value
- }
- }
- } finally {
- subscription.unsubscribe()
- }
-}
-
-export function intersection(xs, ys) {
- return xs.filter(x => ys.includes(x))
+ return { enqueue, run: deferred.resolve }
}
export const secondsToElapsedTime = (seconds) => {
@@ -79,120 +43,64 @@ export const secondsToElapsedTime = (seconds) => {
.join(':')
}
-export async function* raceIterator(iterators) {
- const values = iterators.map(iterator => iterator.next())
-
- 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
- }
-}
-
-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()
- }
-}
-
-const notKnown = cache => track => {
- if (!track) {
- console.error(`No track, should not happen here`)
- return false
- }
-
- const isKnown = (values) => {
+export async function* tracksIterator(refererGenerator, cache) {
+ const notKnow = (values) => {
if (cache.has(values)) {
console.log(`Drop already processed ${values.join(':')}`)
- return true
+ return false
} else {
cache.add(values)
- return false
+ return true
}
}
- switch (track.referer.credentials.type) {
- default:
- throw new Error()
+ try {
+ yield* execPipe(
+ refererGenerator,
+ asyncFilter(({ credentials: { domain, id } }) => notKnow(['referer', 'mastodon', domain, id])),
+ asyncFlatMap(referer => {
+ return execPipe(
+ referer.content,
+ getUrls,
+ map(url => {
+ const { hostname, pathname, searchParams } = new URL(url)
- case 'mastodon':
- if (isKnown([
- 'referer',
- 'mastodon',
- track.referer.credentials.domain,
- track.referer.credentials.id
- ])) {
- return false
- }
+ 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
+ }
+ }),
+ filter(media => media !== null),
+ map(({ url, credentials }) => ({ referer, mediaUrl: url, mediaCredentials: credentials })),
+ take(1),
+ toArray
+ )
+ }),
+ asyncFilter(({ mediaCredentials: { id }}) => notKnow(['media', 'youtube', id])),
+ asyncMap(async ({ referer, mediaUrl, mediaCredentials }) => {
+ const metadata = await fetchMetadata(mediaCredentials)
- break;
- }
-
- if (track.media == null) {
- return false
- }
-
- switch (track.media.credentials.type) {
- default:
- throw new Error()
-
- case 'youtube':
- if (isKnown([
- 'media',
- 'youtube',
- track.media.credentials.id
- ])) {
- return false
- }
-
- break
- }
-
- return true
-}
-
-const completeTrack = async track => {
- const metadata = await fetchMetadata(track.media)
- return {
- ...track,
- title: metadata.title,
- cover: metadata.thumbnail_url
+ return {
+ shareUrl: `${location.origin}${share.reverse({ domain: referer.credentials.domain, id: referer.credentials.id })}`,
+ referer,
+ media: {
+ title: metadata.title,
+ cover: metadata.thumbnail_url,
+ url: mediaUrl,
+ credentials: mediaCredentials
+ }
+ }
+ })
+ )
+ } finally {
+ refererGenerator.return()
}
}
-export const urlsToMedia = urls => {
- return execPipe(
- urls,
- map(parseSource),
- findOr(null, x => x !== null)
- )
-}
-
-const parseSource = (url) => {
- const { hostname, pathname, searchParams } = new URL(url)
-
- 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
- }
-}
-
-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())
- }
+const fetchMetadata = (credentials) => {
+ return fetch(`https://noembed.com/embed?url=https://www.youtube.com/watch?v=${credentials.id}`)
+ .then(response => response.json())
}
\ No newline at end of file
diff --git a/src/types.ts b/src/types.ts
new file mode 100644
index 0000000..96cd46c
--- /dev/null
+++ b/src/types.ts
@@ -0,0 +1,32 @@
+export type Track = {
+ referer: Referer
+ media: Media
+}
+
+export type Referer = {
+ username: string
+ content: string
+ date: Date
+ credentials: RefererCredentials
+}
+
+export type RefererCredentials = Mastodon
+
+export type Mastodon = {
+ type: 'mastodon'
+ domain: string
+ id: string
+}
+
+export type Media = {
+ title: string
+ cover: string
+ credentials: MediaCredentials
+}
+
+export type MediaCredentials = Youtube
+
+export type Youtube = {
+ type: 'youtube'
+ id: string
+}
\ No newline at end of file
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..bd0f9c0
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,13 @@
+{
+ "compilerOptions": {
+ "strict": true,
+ "lib": [
+ "dom",
+ "esnext"
+ ],
+ "downlevelIteration": true
+ },
+ "include": [
+ "src/**/*"
+ ]
+}
\ No newline at end of file