kopia lustrzana https://github.com/cloudflare/wildebeest
Merge pull request #141 from cloudflare/sven/MOW-87
MOW-87: implement remote followers/followingpull/142/head
commit
c15f6bec5e
|
@ -1,24 +1,35 @@
|
|||
import type { Actor } from 'wildebeest/backend/src/activitypub/actors'
|
||||
import type { OrderedCollection } from 'wildebeest/backend/src/activitypub/core'
|
||||
import * as actors from 'wildebeest/backend/src/activitypub/actors'
|
||||
import type { OrderedCollection } from 'wildebeest/backend/src/activitypub/objects/collection'
|
||||
import { getMetadata, loadItems } from 'wildebeest/backend/src/activitypub/objects/collection'
|
||||
|
||||
const headers = {
|
||||
accept: 'application/activity+json',
|
||||
export async function countFollowing(actor: Actor): Promise<number> {
|
||||
const collection = await getMetadata(actor.following)
|
||||
return collection.totalItems
|
||||
}
|
||||
|
||||
export async function getFollowingMetadata(actor: Actor): Promise<OrderedCollection<unknown>> {
|
||||
const res = await fetch(actor.following, { headers })
|
||||
if (!res.ok) {
|
||||
throw new Error(`${actor.following} returned ${res.status}`)
|
||||
}
|
||||
|
||||
return res.json<OrderedCollection<unknown>>()
|
||||
export async function countFollowers(actor: Actor): Promise<number> {
|
||||
const collection = await getMetadata(actor.followers)
|
||||
return collection.totalItems
|
||||
}
|
||||
|
||||
export async function getFollowersMetadata(actor: Actor): Promise<OrderedCollection<unknown>> {
|
||||
const res = await fetch(actor.followers, { headers })
|
||||
if (!res.ok) {
|
||||
throw new Error(`${actor.followers} returned ${res.status}`)
|
||||
}
|
||||
|
||||
return res.json<OrderedCollection<unknown>>()
|
||||
export async function getFollowers(actor: Actor): Promise<OrderedCollection<string>> {
|
||||
const collection = await getMetadata(actor.followers)
|
||||
collection.items = await loadItems<string>(collection)
|
||||
return collection
|
||||
}
|
||||
|
||||
export async function getFollowing(actor: Actor): Promise<OrderedCollection<string>> {
|
||||
const collection = await getMetadata(actor.following)
|
||||
collection.items = await loadItems<string>(collection)
|
||||
return collection
|
||||
}
|
||||
|
||||
export async function loadActors(db: D1Database, collection: OrderedCollection<string>): Promise<Array<Actor>> {
|
||||
const promises = collection.items.map((item) => {
|
||||
const actorId = new URL(item)
|
||||
return actors.getAndCache(actorId, db)
|
||||
})
|
||||
|
||||
return Promise.all(promises)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import type { Object } from 'wildebeest/backend/src/activitypub/objects'
|
||||
import type { Activity } from 'wildebeest/backend/src/activitypub/activities'
|
||||
import type { Actor } from 'wildebeest/backend/src/activitypub/actors'
|
||||
import type { OrderedCollection, OrderedCollectionPage } from 'wildebeest/backend/src/activitypub/core'
|
||||
import type { OrderedCollection } from 'wildebeest/backend/src/activitypub/objects/collection'
|
||||
import { getMetadata, loadItems } from 'wildebeest/backend/src/activitypub/objects/collection'
|
||||
import { PUBLIC_GROUP } from 'wildebeest/backend/src/activitypub/activities'
|
||||
|
||||
export async function addObjectInOutbox(
|
||||
|
@ -30,35 +31,14 @@ export async function addObjectInOutbox(
|
|||
}
|
||||
}
|
||||
|
||||
const headers = {
|
||||
accept: 'application/activity+json',
|
||||
}
|
||||
|
||||
export async function getMetadata(actor: Actor): Promise<OrderedCollection<unknown>> {
|
||||
const res = await fetch(actor.outbox, { headers })
|
||||
if (!res.ok) {
|
||||
throw new Error(`${actor.outbox} returned ${res.status}`)
|
||||
}
|
||||
|
||||
return res.json<OrderedCollection<unknown>>()
|
||||
}
|
||||
|
||||
export async function get(actor: Actor): Promise<OrderedCollection<Activity>> {
|
||||
const collection = await getMetadata(actor)
|
||||
const collection = await getMetadata(actor.outbox)
|
||||
collection.items = await loadItems(collection, 20)
|
||||
|
||||
return collection
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
async function loadItems<T>(collection: OrderedCollection<T>, max: number): Promise<Array<T>> {
|
||||
// FIXME: implement max and multi page support
|
||||
|
||||
const res = await fetch(collection.first, { headers })
|
||||
if (!res.ok) {
|
||||
throw new Error(`${collection.first} returned ${res.status}`)
|
||||
}
|
||||
|
||||
const data = await res.json<OrderedCollectionPage<T>>()
|
||||
return data.orderedItems
|
||||
export async function countStatuses(actor: Actor): Promise<number> {
|
||||
const metadata = await getMetadata(actor.outbox)
|
||||
return metadata.totalItems
|
||||
}
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
import type { Object } from 'wildebeest/backend/src/activitypub/objects'
|
||||
|
||||
export interface Collection<T> extends Object {
|
||||
totalItems: number
|
||||
current?: string
|
||||
first: URL
|
||||
last: URL
|
||||
items: Array<T>
|
||||
}
|
||||
|
||||
export interface OrderedCollection<T> extends Collection<T> {}
|
||||
|
||||
export interface OrderedCollectionPage<T> extends Object {
|
||||
orderedItems: Array<T>
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
import type { Object } from 'wildebeest/backend/src/activitypub/objects'
|
||||
|
||||
export interface Collection<T> extends Object {
|
||||
totalItems: number
|
||||
current?: string
|
||||
first: URL
|
||||
last: URL
|
||||
items: Array<T>
|
||||
}
|
||||
|
||||
export interface OrderedCollection<T> extends Collection<T> {}
|
||||
|
||||
export interface OrderedCollectionPage<T> extends Object {
|
||||
orderedItems: Array<T>
|
||||
}
|
||||
|
||||
const headers = {
|
||||
accept: 'application/activity+json',
|
||||
}
|
||||
|
||||
export async function getMetadata(url: URL): Promise<OrderedCollection<any>> {
|
||||
const res = await fetch(url, { headers })
|
||||
if (!res.ok) {
|
||||
throw new Error(`${url} returned ${res.status}`)
|
||||
}
|
||||
|
||||
return res.json<OrderedCollection<any>>()
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export async function loadItems<T>(collection: OrderedCollection<T>, max?: number): Promise<Array<T>> {
|
||||
// FIXME: implement max and multi page support
|
||||
|
||||
const res = await fetch(collection.first, { headers })
|
||||
if (!res.ok) {
|
||||
throw new Error(`${collection.first} returned ${res.status}`)
|
||||
}
|
||||
|
||||
const data = await res.json<OrderedCollectionPage<T>>()
|
||||
return data.orderedItems
|
||||
}
|
|
@ -42,14 +42,14 @@ function toMastodonAccount(acct: string, res: Actor): MastodonAccount {
|
|||
// Load an external user, using ActivityPub queries, and return it as a MastodonAccount
|
||||
export async function loadExternalMastodonAccount(
|
||||
acct: string,
|
||||
res: Actor,
|
||||
actor: Actor,
|
||||
loadStats: boolean = false
|
||||
): Promise<MastodonAccount> {
|
||||
const account = toMastodonAccount(acct, res)
|
||||
const account = toMastodonAccount(acct, actor)
|
||||
if (loadStats === true) {
|
||||
account.statuses_count = (await apOutbox.getMetadata(res)).totalItems
|
||||
account.followers_count = (await apFollow.getFollowersMetadata(res)).totalItems
|
||||
account.following_count = (await apFollow.getFollowingMetadata(res)).totalItems
|
||||
account.statuses_count = await apOutbox.countStatuses(actor)
|
||||
account.followers_count = await apFollow.countFollowers(actor)
|
||||
account.following_count = await apFollow.countFollowing(actor)
|
||||
}
|
||||
return account
|
||||
}
|
||||
|
|
|
@ -638,10 +638,82 @@ describe('Mastodon APIs', () => {
|
|||
|
||||
test('get remote actor followers', async () => {
|
||||
const db = await makeDB()
|
||||
const connectedActor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com')
|
||||
const actorA = await createPerson(domain, db, userKEK, 'a@cloudflare.com')
|
||||
|
||||
globalThis.fetch = async (input: any) => {
|
||||
if (input.toString() === 'https://example.com/.well-known/webfinger?resource=acct%3Asven%40example.com') {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
links: [
|
||||
{
|
||||
rel: 'self',
|
||||
type: 'application/activity+json',
|
||||
href: 'https://example.com/users/sven',
|
||||
},
|
||||
],
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
if (input.toString() === 'https://example.com/users/sven') {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
id: 'https://example.com/users/sven',
|
||||
type: 'Person',
|
||||
followers: 'https://example.com/users/sven/followers',
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
if (input.toString() === 'https://example.com/users/sven/followers') {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
id: 'https://example.com/users/sven/followers',
|
||||
type: 'OrderedCollection',
|
||||
totalItems: 3,
|
||||
first: 'https://example.com/users/sven/followers/1',
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
if (input.toString() === 'https://example.com/users/sven/followers/1') {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
id: 'https://example.com/users/sven/followers/1',
|
||||
type: 'OrderedCollectionPage',
|
||||
totalItems: 3,
|
||||
partOf: 'https://example.com/users/sven/followers',
|
||||
orderedItems: [
|
||||
actorA.id.toString(), // local user
|
||||
'https://example.com/users/b', // remote user
|
||||
],
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
if (input.toString() === 'https://example.com/users/b') {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
id: 'https://example.com/users/b',
|
||||
type: 'Person',
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
throw new Error('unexpected request to ' + input)
|
||||
}
|
||||
|
||||
const req = new Request(`https://${domain}`)
|
||||
const res = await accounts_followers.handleRequest(req, db, 'sven@example.com', connectedActor)
|
||||
assert.equal(res.status, 403)
|
||||
const res = await accounts_followers.handleRequest(req, db, 'sven@example.com')
|
||||
assert.equal(res.status, 200)
|
||||
|
||||
const data = await res.json<Array<any>>()
|
||||
assert.equal(data.length, 2)
|
||||
|
||||
assert.equal(data[0].acct, 'a@cloudflare.com')
|
||||
assert.equal(data[1].acct, 'b@example.com')
|
||||
})
|
||||
|
||||
test('get local actor followers', async () => {
|
||||
|
@ -664,9 +736,8 @@ describe('Mastodon APIs', () => {
|
|||
await addFollowing(db, actor2, actor, 'sven@' + domain)
|
||||
await acceptFollowing(db, actor2, actor)
|
||||
|
||||
const connectedActor = actor
|
||||
const req = new Request(`https://${domain}`)
|
||||
const res = await accounts_followers.handleRequest(req, db, 'sven', connectedActor)
|
||||
const res = await accounts_followers.handleRequest(req, db, 'sven')
|
||||
assert.equal(res.status, 200)
|
||||
|
||||
const data = await res.json<Array<any>>()
|
||||
|
@ -693,9 +764,8 @@ describe('Mastodon APIs', () => {
|
|||
await addFollowing(db, actor, actor2, 'sven@' + domain)
|
||||
await acceptFollowing(db, actor, actor2)
|
||||
|
||||
const connectedActor = actor
|
||||
const req = new Request(`https://${domain}`)
|
||||
const res = await accounts_following.handleRequest(req, db, 'sven', connectedActor)
|
||||
const res = await accounts_following.handleRequest(req, db, 'sven')
|
||||
assert.equal(res.status, 200)
|
||||
|
||||
const data = await res.json<Array<any>>()
|
||||
|
@ -704,11 +774,82 @@ describe('Mastodon APIs', () => {
|
|||
|
||||
test('get remote actor following', async () => {
|
||||
const db = await makeDB()
|
||||
const actorA = await createPerson(domain, db, userKEK, 'a@cloudflare.com')
|
||||
|
||||
globalThis.fetch = async (input: any) => {
|
||||
if (input.toString() === 'https://example.com/.well-known/webfinger?resource=acct%3Asven%40example.com') {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
links: [
|
||||
{
|
||||
rel: 'self',
|
||||
type: 'application/activity+json',
|
||||
href: 'https://example.com/users/sven',
|
||||
},
|
||||
],
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
if (input.toString() === 'https://example.com/users/sven') {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
id: 'https://example.com/users/sven',
|
||||
type: 'Person',
|
||||
following: 'https://example.com/users/sven/following',
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
if (input.toString() === 'https://example.com/users/sven/following') {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
id: 'https://example.com/users/sven/following',
|
||||
type: 'OrderedCollection',
|
||||
totalItems: 3,
|
||||
first: 'https://example.com/users/sven/following/1',
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
if (input.toString() === 'https://example.com/users/sven/following/1') {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
id: 'https://example.com/users/sven/following/1',
|
||||
type: 'OrderedCollectionPage',
|
||||
totalItems: 3,
|
||||
partOf: 'https://example.com/users/sven/following',
|
||||
orderedItems: [
|
||||
actorA.id.toString(), // local user
|
||||
'https://example.com/users/b', // remote user
|
||||
],
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
if (input.toString() === 'https://example.com/users/b') {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
id: 'https://example.com/users/b',
|
||||
type: 'Person',
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
throw new Error('unexpected request to ' + input)
|
||||
}
|
||||
|
||||
const connectedActor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com')
|
||||
const req = new Request(`https://${domain}`)
|
||||
const res = await accounts_following.handleRequest(req, db, 'sven@example.com', connectedActor)
|
||||
assert.equal(res.status, 403)
|
||||
const res = await accounts_following.handleRequest(req, db, 'sven@example.com')
|
||||
assert.equal(res.status, 200)
|
||||
|
||||
const data = await res.json<Array<any>>()
|
||||
assert.equal(data.length, 2)
|
||||
|
||||
assert.equal(data[0].acct, 'a@cloudflare.com')
|
||||
assert.equal(data[1].acct, 'b@example.com')
|
||||
})
|
||||
|
||||
test('get remote actor featured_tags', async () => {
|
||||
|
|
|
@ -1,36 +1,70 @@
|
|||
// https://docs.joinmastodon.org/methods/accounts/#followers
|
||||
|
||||
import type { Handle } from 'wildebeest/backend/src/utils/parse'
|
||||
import { actorURL } from 'wildebeest/backend/src/activitypub/actors'
|
||||
import { cors } from 'wildebeest/backend/src/utils/cors'
|
||||
import { loadExternalMastodonAccount } from 'wildebeest/backend/src/mastodon/account'
|
||||
import { urlToHandle } from 'wildebeest/backend/src/utils/handle'
|
||||
import { parseHandle } from 'wildebeest/backend/src/utils/parse'
|
||||
import * as actors from 'wildebeest/backend/src/activitypub/actors'
|
||||
import { urlToHandle } from 'wildebeest/backend/src/utils/handle'
|
||||
import { MastodonAccount } from 'wildebeest/backend/src/types/account'
|
||||
import type { Person } from 'wildebeest/backend/src/activitypub/actors'
|
||||
import type { ContextData } from 'wildebeest/backend/src/types/context'
|
||||
import { getFollowers } from 'wildebeest/backend/src/mastodon/follow'
|
||||
import type { Env } from 'wildebeest/backend/src/types/env'
|
||||
import { domainNotAuthorized } from 'wildebeest/backend/src/errors'
|
||||
import * as actors from 'wildebeest/backend/src/activitypub/actors'
|
||||
import * as webfinger from 'wildebeest/backend/src/webfinger'
|
||||
import { getFollowers, loadActors } from 'wildebeest/backend/src/activitypub/actors/follow'
|
||||
import * as localFollow from 'wildebeest/backend/src/mastodon/follow'
|
||||
|
||||
export const onRequest: PagesFunction<Env, any, ContextData> = async ({ params, request, env, data }) => {
|
||||
return handleRequest(request, env.DATABASE, params.id as string, data.connectedActor)
|
||||
export const onRequest: PagesFunction<Env, any, ContextData> = async ({ params, request, env }) => {
|
||||
return handleRequest(request, env.DATABASE, params.id as string)
|
||||
}
|
||||
|
||||
export async function handleRequest(
|
||||
request: Request,
|
||||
db: D1Database,
|
||||
id: string,
|
||||
connectedActor: Person
|
||||
): Promise<Response> {
|
||||
export async function handleRequest(request: Request, db: D1Database, id: string): Promise<Response> {
|
||||
const handle = parseHandle(id)
|
||||
const domain = new URL(request.url).hostname
|
||||
if (handle.domain !== null && handle.domain !== domain) {
|
||||
return domainNotAuthorized()
|
||||
|
||||
if (handle.domain === null || (handle.domain !== null && handle.domain === domain)) {
|
||||
// Retrieve the infos from a local user
|
||||
return getLocalFollowers(request, handle, db)
|
||||
} else if (handle.domain !== null) {
|
||||
// Retrieve the infos of a remote actor
|
||||
return getRemoteFollowers(request, handle, db)
|
||||
} else {
|
||||
return new Response('', { status: 403 })
|
||||
}
|
||||
}
|
||||
|
||||
async function getRemoteFollowers(request: Request, handle: Handle, db: D1Database): Promise<Response> {
|
||||
const acct = `${handle.localPart}@${handle.domain}`
|
||||
const link = await webfinger.queryAcctLink(handle.domain!, acct)
|
||||
if (link === null) {
|
||||
return new Response('', { status: 404 })
|
||||
}
|
||||
|
||||
const actor = await actors.getAndCache(link, db)
|
||||
const followersIds = await getFollowers(actor)
|
||||
const followers = await loadActors(db, followersIds)
|
||||
|
||||
const promises = followers.map((actor) => {
|
||||
const acct = urlToHandle(actor.id)
|
||||
return loadExternalMastodonAccount(acct, actor, false)
|
||||
})
|
||||
|
||||
const out = await Promise.all(promises)
|
||||
const headers = {
|
||||
...cors(),
|
||||
'content-type': 'application/json; charset=utf-8',
|
||||
}
|
||||
return new Response(JSON.stringify(out), { headers })
|
||||
}
|
||||
|
||||
async function getLocalFollowers(request: Request, handle: Handle, db: D1Database): Promise<Response> {
|
||||
const domain = new URL(request.url).hostname
|
||||
const actorId = actorURL(domain, handle.localPart)
|
||||
const actor = await actors.getAndCache(actorId, db)
|
||||
|
||||
const followers = await localFollow.getFollowers(db, actor)
|
||||
const out: Array<MastodonAccount> = []
|
||||
|
||||
const followers = await getFollowers(db, connectedActor)
|
||||
for (let i = 0, len = followers.length; i < len; i++) {
|
||||
const id = new URL(followers[i])
|
||||
|
||||
|
|
|
@ -1,36 +1,70 @@
|
|||
// https://docs.joinmastodon.org/methods/accounts/#following
|
||||
|
||||
import type { Handle } from 'wildebeest/backend/src/utils/parse'
|
||||
import { actorURL } from 'wildebeest/backend/src/activitypub/actors'
|
||||
import { cors } from 'wildebeest/backend/src/utils/cors'
|
||||
import { loadExternalMastodonAccount } from 'wildebeest/backend/src/mastodon/account'
|
||||
import { parseHandle } from 'wildebeest/backend/src/utils/parse'
|
||||
import { urlToHandle } from 'wildebeest/backend/src/utils/handle'
|
||||
import * as actors from 'wildebeest/backend/src/activitypub/actors'
|
||||
import { MastodonAccount } from 'wildebeest/backend/src/types/account'
|
||||
import type { Person } from 'wildebeest/backend/src/activitypub/actors'
|
||||
import type { ContextData } from 'wildebeest/backend/src/types/context'
|
||||
import { getFollowingId } from 'wildebeest/backend/src/mastodon/follow'
|
||||
import * as localFollow from 'wildebeest/backend/src/mastodon/follow'
|
||||
import type { Env } from 'wildebeest/backend/src/types/env'
|
||||
import { domainNotAuthorized } from 'wildebeest/backend/src/errors'
|
||||
import * as actors from 'wildebeest/backend/src/activitypub/actors'
|
||||
import * as webfinger from 'wildebeest/backend/src/webfinger'
|
||||
import { getFollowing, loadActors } from 'wildebeest/backend/src/activitypub/actors/follow'
|
||||
|
||||
export const onRequest: PagesFunction<Env, any, ContextData> = async ({ params, request, env, data }) => {
|
||||
return handleRequest(request, env.DATABASE, params.id as string, data.connectedActor)
|
||||
export const onRequest: PagesFunction<Env, any, ContextData> = async ({ params, request, env }) => {
|
||||
return handleRequest(request, env.DATABASE, params.id as string)
|
||||
}
|
||||
|
||||
export async function handleRequest(
|
||||
request: Request,
|
||||
db: D1Database,
|
||||
id: string,
|
||||
connectedActor: Person
|
||||
): Promise<Response> {
|
||||
export async function handleRequest(request: Request, db: D1Database, id: string): Promise<Response> {
|
||||
const handle = parseHandle(id)
|
||||
const domain = new URL(request.url).hostname
|
||||
if (handle.domain !== null && handle.domain !== domain) {
|
||||
return domainNotAuthorized()
|
||||
|
||||
if (handle.domain === null || (handle.domain !== null && handle.domain === domain)) {
|
||||
// Retrieve the infos from a local user
|
||||
return getLocalFollowing(request, handle, db)
|
||||
} else if (handle.domain !== null) {
|
||||
// Retrieve the infos of a remote actor
|
||||
return getRemoteFollowing(request, handle, db)
|
||||
} else {
|
||||
return new Response('', { status: 403 })
|
||||
}
|
||||
}
|
||||
|
||||
async function getRemoteFollowing(request: Request, handle: Handle, db: D1Database): Promise<Response> {
|
||||
const acct = `${handle.localPart}@${handle.domain}`
|
||||
const link = await webfinger.queryAcctLink(handle.domain!, acct)
|
||||
if (link === null) {
|
||||
return new Response('', { status: 404 })
|
||||
}
|
||||
|
||||
const actor = await actors.getAndCache(link, db)
|
||||
const followingIds = await getFollowing(actor)
|
||||
const following = await loadActors(db, followingIds)
|
||||
|
||||
const promises = following.map((actor) => {
|
||||
const acct = urlToHandle(actor.id)
|
||||
return loadExternalMastodonAccount(acct, actor, false)
|
||||
})
|
||||
|
||||
const out = await Promise.all(promises)
|
||||
const headers = {
|
||||
...cors(),
|
||||
'content-type': 'application/json; charset=utf-8',
|
||||
}
|
||||
return new Response(JSON.stringify(out), { headers })
|
||||
}
|
||||
|
||||
async function getLocalFollowing(request: Request, handle: Handle, db: D1Database): Promise<Response> {
|
||||
const domain = new URL(request.url).hostname
|
||||
const actorId = actorURL(domain, handle.localPart)
|
||||
const actor = await actors.getAndCache(actorId, db)
|
||||
|
||||
const following = await localFollow.getFollowingId(db, actor)
|
||||
const out: Array<MastodonAccount> = []
|
||||
|
||||
const following = await getFollowingId(db, connectedActor)
|
||||
for (let i = 0, len = following.length; i < len; i++) {
|
||||
const id = new URL(following[i])
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue