Bluesky support
This commit is contained in:
parent
80a082fd45
commit
694184d728
8
lib.js
8
lib.js
@ -121,7 +121,7 @@ export const retryDelayedFetch = async (url, options, courtesyWait, retryAttempt
|
|||||||
// | `-. `-.
|
// | `-. `-.
|
||||||
// ' `-' `-'
|
// ' `-' `-'
|
||||||
|
|
||||||
export async function fetchChannel(source) {
|
export const fetchChannel = async (source) => {
|
||||||
let { hostname } = source
|
let { hostname } = source
|
||||||
let error
|
let error
|
||||||
let response
|
let response
|
||||||
@ -206,9 +206,11 @@ export const createPosts = async (channel, source, fromDate, reducerCallback) =>
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const createPost = (item, source) => {
|
export const createPost = (item, source) => {
|
||||||
let description = new JSDOM(item.querySelector('description').textContent).window.document
|
let description = item.querySelector('description')
|
||||||
|
description = description === null ? '' : new JSDOM(description.textContent).window.document
|
||||||
|
|
||||||
let date = readPubDate(item.querySelector('pubDate'))
|
let date = readPubDate(item.querySelector('pubDate'))
|
||||||
let link = item.querySelector('link').textContent
|
let link = item.querySelector('link')?.textContent
|
||||||
let guid = item.querySelector('guid')?.textContent
|
let guid = item.querySelector('guid')?.textContent
|
||||||
let title = item.querySelector('title')?.textContent
|
let title = item.querySelector('title')?.textContent
|
||||||
|
|
||||||
|
225
platforms/bluesky.js
Normal file
225
platforms/bluesky.js
Normal file
@ -0,0 +1,225 @@
|
|||||||
|
import Path from "path"
|
||||||
|
import { createSource, createSourceOptions, downloadImages, fetchChannel, getPostIdFromPathname, isUnset } from "../lib.js"
|
||||||
|
import { channel } from "diagnostics_channel"
|
||||||
|
|
||||||
|
class BlueskyError extends Error {}
|
||||||
|
|
||||||
|
let bluesky = {}
|
||||||
|
|
||||||
|
bluesky.createEndpoint = (domain, method) => {
|
||||||
|
let endpoint = new URL('https://' + domain)
|
||||||
|
endpoint.pathname = Path.join('xrpc', method)
|
||||||
|
|
||||||
|
return endpoint
|
||||||
|
}
|
||||||
|
|
||||||
|
bluesky.createHeaders = (client) => {
|
||||||
|
return {
|
||||||
|
// 'Accept': 'application/json',
|
||||||
|
'Authorization': 'Bearer ' + client.accessJwt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bluesky.handleRequest = response => {
|
||||||
|
return response
|
||||||
|
.then(response => {
|
||||||
|
response = response.json()
|
||||||
|
|
||||||
|
if(response.error) {
|
||||||
|
throw new BlueskyError(`${response.error}: ${response.message}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
throw new BlueskyError(err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
bluesky.login = async (handle, password, domain = 'bsky.social') => {
|
||||||
|
let body = { identifier: handle, password }
|
||||||
|
let endpoint = bluesky.createEndpoint(domain, 'com.atproto.server.createSession')
|
||||||
|
|
||||||
|
let { accessJwt, refreshJwt } = await bluesky.handleRequest(
|
||||||
|
fetch(endpoint.href, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
body: JSON.stringify(body)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
domain,
|
||||||
|
accessJwt,
|
||||||
|
refreshJwt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getPostUriChunks = channel => {
|
||||||
|
let guids = channel.querySelectorAll('guid')
|
||||||
|
let storedUris = new Set()
|
||||||
|
let chunks = []
|
||||||
|
let uris = []
|
||||||
|
let index = 0
|
||||||
|
|
||||||
|
for(let guid of guids) {
|
||||||
|
let uri = guid?.textContent
|
||||||
|
|
||||||
|
if(!uri || storedUris.has(uri))
|
||||||
|
continue
|
||||||
|
|
||||||
|
if(index !== 0 && index % 25 === 0) {
|
||||||
|
chunks.push(uris)
|
||||||
|
uris = []
|
||||||
|
}
|
||||||
|
|
||||||
|
uris.push(uri)
|
||||||
|
storedUris.add(uri)
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
|
||||||
|
chunks.push(uris)
|
||||||
|
return chunks
|
||||||
|
}
|
||||||
|
|
||||||
|
bluesky.fetchAnnotations = async (client, channel) => {
|
||||||
|
let chunks = getPostUriChunks(channel)
|
||||||
|
let annotations = new Map()
|
||||||
|
|
||||||
|
for(let uris of chunks) {
|
||||||
|
let endpoint = bluesky.createEndpoint(client.domain, 'app.bsky.feed.getPosts')
|
||||||
|
for(let uri of uris)
|
||||||
|
endpoint.searchParams.append('uris', uri)
|
||||||
|
|
||||||
|
let { posts } = await bluesky.handleRequest(
|
||||||
|
fetch(endpoint.href, {
|
||||||
|
headers: bluesky.createHeaders(client)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
for(let post of posts) {
|
||||||
|
annotations.set(post.uri, post)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return annotations
|
||||||
|
}
|
||||||
|
|
||||||
|
export default bluesky
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bluesky.createSource = (handle, options = {}, client, postReducerCallback, cache) => {
|
||||||
|
let startOfServerHostname = handle.indexOf('.')
|
||||||
|
let user = handle.slice(0, startOfServerHostname)
|
||||||
|
let serverHostname = handle.slice(startOfServerHostname + 1)
|
||||||
|
|
||||||
|
let source = {
|
||||||
|
type: 'bluesky',
|
||||||
|
description: `Aggregate feed for @${user} on bluesky`,
|
||||||
|
hostname: 'bsky.app',
|
||||||
|
pathname: 'profile/' + handle + "/rss",
|
||||||
|
name: `${serverHostname}-${user}`,
|
||||||
|
displayName: handle,
|
||||||
|
user,
|
||||||
|
handle,
|
||||||
|
postsToPopulate: new Map(),
|
||||||
|
postsChecked: new Set(),
|
||||||
|
...createSourceOptions(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
return createSource(
|
||||||
|
source,
|
||||||
|
isUnset(client) ? fetchChannel : bluesky.fetchAnnotatedChannelFor(client),
|
||||||
|
postReducerCallback,
|
||||||
|
cache
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
bluesky.fetchAnnotatedChannelFor = (client) => async (source) => {
|
||||||
|
let channel = await fetchChannel(source)
|
||||||
|
source.postAnnotations = await bluesky.fetchAnnotations(client, channel)
|
||||||
|
|
||||||
|
return channel
|
||||||
|
}
|
||||||
|
|
||||||
|
bluesky.isRepost = (post) => {
|
||||||
|
let annotation = post.source.postAnnotations.get(post.guid)
|
||||||
|
let beenChecked = post.source.postsChecked.has(post.guid)
|
||||||
|
|
||||||
|
post.source.postsChecked.add(post.guid)
|
||||||
|
return annotation ?
|
||||||
|
beenChecked && annotation.author.handle === post.source.handle :
|
||||||
|
beenChecked
|
||||||
|
}
|
||||||
|
|
||||||
|
bluesky.pullImages = async (post, view, discardPostIfNoImages) => {
|
||||||
|
let annotation = post.source.postAnnotations.get(post.guid)
|
||||||
|
let noImages = isUnset(annotation.embed)
|
||||||
|
|| annotation.embed.type === 'app.bsky.embed.images#view'
|
||||||
|
|
||||||
|
if(isUnset(annotation) || noImages) {
|
||||||
|
return discardPostIfNoImages ? null : post
|
||||||
|
}
|
||||||
|
|
||||||
|
post.images = await downloadImages(
|
||||||
|
annotation.embed.images.map(image => image.fullsize),
|
||||||
|
post.source,
|
||||||
|
getPostIdFromPathname(post),
|
||||||
|
view
|
||||||
|
)
|
||||||
|
return post
|
||||||
|
}
|
||||||
|
|
||||||
|
// export const bluesky = {
|
||||||
|
// login(identifier, password) {
|
||||||
|
|
||||||
|
// },
|
||||||
|
|
||||||
|
// createSource(usertag, options, postReducerCallback) {
|
||||||
|
// let startOfServerHostname = usertag.indexOf('.')
|
||||||
|
// let user = usertag.slice(0, startOfServerHostname)
|
||||||
|
// let serverHostname = usertag.slice(startOfServerHostname + 1)
|
||||||
|
|
||||||
|
// let source = {
|
||||||
|
// type: 'bluesky',
|
||||||
|
// description: `Aggregate feed for @${user} on bluesky`,
|
||||||
|
// hostname: 'bsky.app',
|
||||||
|
// pathname: 'profile/' + usertag + "/rss",
|
||||||
|
// name: `${serverHostname}-${user}`,
|
||||||
|
// displayName: usertag,
|
||||||
|
// user,
|
||||||
|
// postsToPopulate: new Map(),
|
||||||
|
// ...createSourceOptions(options)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return createSource(source, fetchRss, postReducerCallback)
|
||||||
|
// },
|
||||||
|
|
||||||
|
// isRepost(post) {
|
||||||
|
// // Bluesky's rss does not provide retweets/retoots
|
||||||
|
// return false
|
||||||
|
// },
|
||||||
|
|
||||||
|
// async pullImages(post, view, discardPostIfNoImages) {
|
||||||
|
// let getPostsEndpoint = new URL('https://bsky.social/xrpc/app.bsky.feed.getPosts')
|
||||||
|
|
||||||
|
// getPostsEndpoint.searchParams.set('uris', [ post.guid ])
|
||||||
|
// console.log(getPostsEndpoint)
|
||||||
|
|
||||||
|
// // fetch(getPostsEndpoint)
|
||||||
|
|
||||||
|
// // post.source.postsToPopulate.set(post.guid, {
|
||||||
|
// // discardPostIfNoImages,
|
||||||
|
// // view,
|
||||||
|
// // post
|
||||||
|
// // })
|
||||||
|
// },
|
||||||
|
|
||||||
|
// populatePosts(source) {
|
||||||
|
|
||||||
|
// console.log(getPostsEndpoint)
|
||||||
|
// }
|
||||||
|
// }
|
Loading…
x
Reference in New Issue
Block a user