2020-01-20 12:23:00 +01:00
|
|
|
import Observable from 'core-js-pure/features/observable'
|
2020-02-16 17:02:39 +01:00
|
|
|
import getUrls from 'get-urls'
|
|
|
|
import { observableToAsyncIterator, raceIterator, urlsToMedia } from '/services/misc.js'
|
2020-01-20 03:26:18 +01:00
|
|
|
|
|
|
|
const LINK_RE = /<(.+?)>; rel="(\w+)"/gi
|
|
|
|
|
|
|
|
function parseLinkHeader(link) {
|
|
|
|
const links = {}
|
|
|
|
|
|
|
|
for (const [ , url, name ] of link.matchAll(LINK_RE)) {
|
|
|
|
links[name] = url
|
|
|
|
}
|
|
|
|
|
|
|
|
return links
|
|
|
|
}
|
|
|
|
|
2020-02-16 17:02:39 +01:00
|
|
|
export const fetchStatus = (domain, id) => fetch(`https://${domain}/api/v1/statuses/${id}`)
|
|
|
|
.then(response => response.json())
|
|
|
|
.then(status => processStatus(domain, status))
|
2020-02-15 23:12:53 +01:00
|
|
|
|
2020-02-20 15:55:22 +01:00
|
|
|
export async function* statusIterator({ domain, id }) {
|
|
|
|
yield await fetchStatus(domain, id)
|
|
|
|
}
|
|
|
|
|
2020-02-15 23:12:53 +01:00
|
|
|
// Observable<{ domain : string, hashtag : string, status : Status}>
|
2020-01-20 03:26:18 +01:00
|
|
|
export const hashtagStreamingObservable = (domain, hashtag) => {
|
|
|
|
return new Observable(observer => {
|
2020-02-04 20:07:34 +01:00
|
|
|
const onOpen = () => {
|
|
|
|
console.log(`Streaming ${domain} #${hashtag} : open`)
|
|
|
|
}
|
|
|
|
|
|
|
|
const onStatus = event => {
|
|
|
|
const status = JSON.parse(event.data)
|
|
|
|
console.log(`Streaming ${domain} #${hashtag} : status ${status.id}`)
|
2020-02-15 23:12:53 +01:00
|
|
|
observer.next(processStatus(domain, status))
|
2020-02-04 20:07:34 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
const onError = error => {
|
|
|
|
console.error(`Streaming ${domain} #${hashtag} : error`)
|
|
|
|
console.error(error)
|
|
|
|
observer.error(error)
|
|
|
|
}
|
2020-01-20 03:26:18 +01:00
|
|
|
|
|
|
|
const eventSource = new EventSource(`https://${domain}/api/v1/streaming/hashtag?tag=${hashtag}`)
|
2020-02-04 20:07:34 +01:00
|
|
|
eventSource.addEventListener('open', onOpen)
|
2020-01-20 03:26:18 +01:00
|
|
|
eventSource.addEventListener('update', onStatus)
|
|
|
|
eventSource.addEventListener('error', onError)
|
|
|
|
|
|
|
|
return () => {
|
2020-02-04 20:07:34 +01:00
|
|
|
eventSource.removeEventListener('open', onOpen)
|
2020-01-20 03:26:18 +01:00
|
|
|
eventSource.removeEventListener('update', onStatus)
|
|
|
|
eventSource.removeEventListener('error', onError)
|
2020-02-16 17:02:39 +01:00
|
|
|
eventSource.close()
|
2020-01-20 03:26:18 +01:00
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function* hashtagTimelineIterator (domain, hashtag) {
|
|
|
|
let nextLink = `https://${domain}/api/v1/timelines/tag/${hashtag}?limit=40`
|
|
|
|
|
|
|
|
while (nextLink) {
|
|
|
|
const response = await fetch(nextLink)
|
|
|
|
|
|
|
|
nextLink = response.headers.has('link')
|
|
|
|
? parseLinkHeader(response.headers.get('link')).next
|
|
|
|
: null
|
|
|
|
|
2020-02-04 20:07:34 +01:00
|
|
|
const statuses = await response.json()
|
|
|
|
|
|
|
|
console.log(`Timeline ${domain} #${hashtag} : fetched ${statuses.length} statuses`)
|
|
|
|
|
2020-02-15 23:12:53 +01:00
|
|
|
yield* statuses.map(status => processStatus(domain, status))
|
2020-01-20 03:26:18 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-15 23:12:53 +01:00
|
|
|
export const hashtagIterator = (domain, hashtag) => {
|
|
|
|
return raceIterator([
|
|
|
|
observableToAsyncIterator(hashtagStreamingObservable(domain, hashtag)),
|
|
|
|
hashtagTimelineIterator(domain, hashtag)
|
|
|
|
])
|
2020-01-20 03:26:18 +01:00
|
|
|
}
|
|
|
|
|
2020-02-15 23:12:53 +01:00
|
|
|
export async function* hashtagsIterator (domain, hashtags) {
|
|
|
|
const iterators = hashtags.map(hashtag => hashtagIterator(domain, hashtag))
|
2020-02-14 17:49:56 +01:00
|
|
|
const values = iterators.map(iterator => iterator.next())
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
const promises = values.map((promise, index) => promise.then(result => ({ index, result })))
|
|
|
|
const promisesValues = await Promise.all(promises)
|
|
|
|
|
|
|
|
const sorted = promisesValues
|
|
|
|
.sort((a, b) =>{
|
2020-02-15 23:12:53 +01:00
|
|
|
a.result.value.date - b.result.value.date
|
2020-02-14 17:49:56 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
const { index, result: { done, value } } = sorted[0]
|
|
|
|
values[index] = iterators[index].next()
|
|
|
|
yield value
|
|
|
|
}
|
|
|
|
}
|
2020-02-15 23:12:53 +01:00
|
|
|
|
|
|
|
const processStatus = (domain, status) => ({
|
2020-02-16 17:02:39 +01:00
|
|
|
title: '',
|
2020-02-17 01:23:35 +01:00
|
|
|
date: new Date(status.created_at),
|
2020-02-15 23:12:53 +01:00
|
|
|
referer: {
|
2020-02-16 17:02:39 +01:00
|
|
|
username: status.account.username,
|
2020-02-15 23:12:53 +01:00
|
|
|
url: status.url,
|
|
|
|
credentials: { type: 'mastodon', domain, id: status.id }
|
|
|
|
},
|
2020-02-16 17:02:39 +01:00
|
|
|
media: urlsToMedia(getUrls(status.content))
|
2020-02-15 23:12:53 +01:00
|
|
|
})
|
|
|
|
|