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 error
|
||||
let response
|
||||
@ -206,9 +206,11 @@ export const createPosts = async (channel, source, fromDate, reducerCallback) =>
|
||||
}
|
||||
|
||||
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 link = item.querySelector('link').textContent
|
||||
let link = item.querySelector('link')?.textContent
|
||||
let guid = item.querySelector('guid')?.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