Refactored to store actor info in a separate file
This makes it more easy to modify for future users
This commit is contained in:
parent
6647dabdc8
commit
6f2e122dd6
9 changed files with 247 additions and 161 deletions
88
actor.ts
Normal file
88
actor.ts
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
import { BASE_URL, PUBLIC_KEY } from "./src/env"
|
||||||
|
|
||||||
|
// change "activitypub" to whatever you want your account name to be
|
||||||
|
export const preferredUsername:string = process.env.ACCOUNT || "activitypub"
|
||||||
|
export const name = process.env.REAL_NAME || preferredUsername
|
||||||
|
export const summary = ""
|
||||||
|
|
||||||
|
// avatar image
|
||||||
|
const icon:{ type:"Image", mediaType:string, url:string } = {
|
||||||
|
type: "Image",
|
||||||
|
mediaType: "image/svg+xml",
|
||||||
|
url: BASE_URL + "/assets/img/avatar-tt.svg"
|
||||||
|
}
|
||||||
|
|
||||||
|
// banner image
|
||||||
|
const image:{ type:"Image", mediaType:string, url:string } = {
|
||||||
|
type: "Image",
|
||||||
|
mediaType: "image/jpeg",
|
||||||
|
url: BASE_URL + "/assets/img/banner-1500x500.jpg"
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a list of other actor ids you identify as
|
||||||
|
const alsoKnownAs:string[] = []
|
||||||
|
|
||||||
|
// I'm not exactly sure what this time represents.
|
||||||
|
// The date your website got published?
|
||||||
|
const published:string = "2023-09-14T00:00:00Z"
|
||||||
|
|
||||||
|
// customize this to add or remove PropertyValues
|
||||||
|
// this is the table that appears on Mastodon, etc, profile pages
|
||||||
|
const attachment:{ type:"PropertyValue", name:string, value:string }[] = [
|
||||||
|
{
|
||||||
|
type: "PropertyValue",
|
||||||
|
name: "Pronouns",
|
||||||
|
value: "they/them"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "PropertyValue",
|
||||||
|
name: "Website",
|
||||||
|
value: `<a href="${BASE_URL}" target="_blank" rel="nofollow noopener noreferrer me">${BASE_URL}</a>`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
export default {
|
||||||
|
"@context": [
|
||||||
|
"https://www.w3.org/ns/activitystreams",
|
||||||
|
"https://w3id.org/security/v1",
|
||||||
|
{
|
||||||
|
manuallyApprovesFollowers: "as:manuallyApprovesFollowers",
|
||||||
|
toot: "http://joinmastodon.org/ns#",
|
||||||
|
alsoKnownAs: {
|
||||||
|
"@id": "as:alsoKnownAs",
|
||||||
|
"@type": "@id"
|
||||||
|
},
|
||||||
|
schema: "http://schema.org#",
|
||||||
|
PropertyValue: "schema:PropertyValue",
|
||||||
|
value: "schema:value",
|
||||||
|
discoverable: "toot:discoverable",
|
||||||
|
Ed25519Signature: "toot:Ed25519Signature",
|
||||||
|
Ed25519Key: "toot:Ed25519Key",
|
||||||
|
Curve25519Key: "toot:Curve25519Key",
|
||||||
|
EncryptedMessage: "toot:EncryptedMessage",
|
||||||
|
publicKeyBase64: "toot:publicKeyBase64"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
id: BASE_URL,
|
||||||
|
type: "Person",
|
||||||
|
following: `${BASE_URL}/following`,
|
||||||
|
followers: `${BASE_URL}/followers`,
|
||||||
|
inbox: `${BASE_URL}/inbox`,
|
||||||
|
outbox: `${BASE_URL}/outbox`,
|
||||||
|
preferredUsername,
|
||||||
|
name,
|
||||||
|
summary,
|
||||||
|
url: BASE_URL,
|
||||||
|
manuallyApprovesFollowers: false,
|
||||||
|
discoverable: true,
|
||||||
|
published,
|
||||||
|
alsoKnownAs,
|
||||||
|
publicKey: {
|
||||||
|
id: `${BASE_URL}#main-key`,
|
||||||
|
owner: BASE_URL,
|
||||||
|
publicKeyPem: PUBLIC_KEY,
|
||||||
|
},
|
||||||
|
attachment,
|
||||||
|
icon,
|
||||||
|
image
|
||||||
|
}
|
|
@ -1,33 +1,23 @@
|
||||||
import { ACCOUNT, ACTOR, ACTOR_OBJ, HOSTNAME, PUBLIC_KEY } from "./env"
|
|
||||||
import * as db from "./db"
|
import * as db from "./db"
|
||||||
import { reqIsActivityPub, send, verify } from "./request"
|
import { reqIsActivityPub, verify } from "./request"
|
||||||
import outbox from "./outbox"
|
import outbox from "./outbox"
|
||||||
import inbox from "./inbox"
|
import inbox from "./inbox"
|
||||||
|
import ACTOR from "../actor"
|
||||||
|
|
||||||
export default (req: Request): Response | Promise<Response> | undefined => {
|
export default (req: Request): Response | Promise<Response> | undefined => {
|
||||||
const url = new URL(req.url)
|
const url = new URL(req.url)
|
||||||
let match
|
let match
|
||||||
|
|
||||||
if(!reqIsActivityPub(req)) return undefined
|
if(!reqIsActivityPub(req)) return undefined
|
||||||
|
else if(req.method == "GET" && (match = url.pathname.match(/^\/?$/i))) return getActor(req)
|
||||||
// else if(req.method == "GET" && (match = url.pathname.match(/^\/([^\/]+)\/?$/i))) return getActor(req, match[1])
|
else if(req.method == "GET" && (match = url.pathname.match(/^\/outbox\/?$/i))) return getOutbox(req)
|
||||||
// else if(req.method == "GET" && (match = url.pathname.match(/^\/([^\/]+)\/outbox\/?$/i))) return getOutbox(req, match[1])
|
else if(req.method == "POST" && (match = url.pathname.match(/^\/inbox\/?$/i))) return postInbox(req)
|
||||||
// else if(req.method == "POST" && (match = url.pathname.match(/^\/([^\/]+)\/inbox\/?$/i))) return postInbox(req, match[1])
|
else if(req.method == "POST" && (match = url.pathname.match(/^\/outbox\/?$/i))) return postOutbox(req)
|
||||||
// else if(req.method == "POST" && (match = url.pathname.match(/^\/([^\/]+)\/outbox\/?$/i))) return postOutbox(req, match[1])
|
else if(req.method == "GET" && (match = url.pathname.match(/^\/followers\/?$/i))) return getFollowers(req)
|
||||||
// else if(req.method == "GET" && (match = url.pathname.match(/^\/([^\/]+)\/followers\/?$/i))) return getFollowers(req, match[1])
|
else if(req.method == "GET" && (match = url.pathname.match(/^\/following\/?$/i))) return getFollowing(req)
|
||||||
// else if(req.method == "GET" && (match = url.pathname.match(/^\/([^\/]+)\/following\/?$/i))) return getFollowing(req, match[1])
|
else if(req.method == "GET" && (match = url.pathname.match(/^\/posts\/([^\/]+)\/?$/i))) return getPost(req, match[1])
|
||||||
// else if(req.method == "GET" && (match = url.pathname.match(/^\/([^\/]+)\/posts\/([^\/]+)\/?$/i))) return getPost(req, match[1], match[2])
|
else if(req.method == "GET" && (match = url.pathname.match(/^\/posts\/([^\/]+)\/activity\/?$/i))) return getOutboxActivity(req, match[1])
|
||||||
// else if(req.method == "GET" && (match = url.pathname.match(/^\/([^\/]+)\/posts\/([^\/]+)\/activity\/?$/i))) return getActivity(req, match[1], match[2])
|
else if(req.method == "GET" && (match = url.pathname.match(/^\/outbox\/([^\/]+)\/?$/i))) return getOutboxActivity(req, match[1])
|
||||||
|
|
||||||
else if(req.method == "GET" && (match = url.pathname.match(/^\/?$/i))) return getActor(req, ACCOUNT)
|
|
||||||
else if(req.method == "GET" && (match = url.pathname.match(/^\/outbox\/?$/i))) return getOutbox(req, ACCOUNT)
|
|
||||||
else if(req.method == "POST" && (match = url.pathname.match(/^\/inbox\/?$/i))) return postInbox(req, ACCOUNT)
|
|
||||||
else if(req.method == "POST" && (match = url.pathname.match(/^\/outbox\/?$/i))) return postOutbox(req, ACCOUNT)
|
|
||||||
else if(req.method == "GET" && (match = url.pathname.match(/^\/followers\/?$/i))) return getFollowers(req, ACCOUNT)
|
|
||||||
else if(req.method == "GET" && (match = url.pathname.match(/^\/following\/?$/i))) return getFollowing(req, ACCOUNT)
|
|
||||||
else if(req.method == "GET" && (match = url.pathname.match(/^\/posts\/([^\/]+)\/?$/i))) return getPost(req, ACCOUNT, match[1])
|
|
||||||
else if(req.method == "GET" && (match = url.pathname.match(/^\/posts\/([^\/]+)\/activity\/?$/i))) return getOutboxActivity(req, ACCOUNT, match[1])
|
|
||||||
else if(req.method == "GET" && (match = url.pathname.match(/^\/outbox\/([^\/]+)\/?$/i))) return getOutboxActivity(req, ACCOUNT, match[1])
|
|
||||||
|
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
@ -41,9 +31,8 @@ export function idsFromValue(value:any):string[] {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
const postOutbox = async (req:Request, account:string):Promise<Response> => {
|
const postOutbox = async (req:Request):Promise<Response> => {
|
||||||
console.log("PostOutbox", account)
|
console.log("PostOutbox")
|
||||||
if (ACCOUNT !== account) return new Response("", { status: 404 })
|
|
||||||
|
|
||||||
const bodyText = await req.text()
|
const bodyText = await req.text()
|
||||||
|
|
||||||
|
@ -51,14 +40,13 @@ const postOutbox = async (req:Request, account:string):Promise<Response> => {
|
||||||
|
|
||||||
const body = JSON.parse(bodyText)
|
const body = JSON.parse(bodyText)
|
||||||
// ensure that the verified actor matches the actor in the request body
|
// ensure that the verified actor matches the actor in the request body
|
||||||
if (ACTOR !== body.actor) return new Response("", { status: 401 })
|
if (ACTOR.id !== body.actor) return new Response("", { status: 401 })
|
||||||
|
|
||||||
return await outbox(body)
|
return await outbox(body)
|
||||||
}
|
}
|
||||||
|
|
||||||
const postInbox = async (req:Request, account:string):Promise<Response> => {
|
const postInbox = async (req:Request):Promise<Response> => {
|
||||||
console.log("PostInbox", account)
|
console.log("PostInbox")
|
||||||
if (ACCOUNT !== account) return new Response("", { status: 404 })
|
|
||||||
|
|
||||||
const bodyText = await req.text()
|
const bodyText = await req.text()
|
||||||
|
|
||||||
|
@ -77,40 +65,28 @@ const postInbox = async (req:Request, account:string):Promise<Response> => {
|
||||||
if (from !== body.actor) return new Response("", { status: 401 })
|
if (from !== body.actor) return new Response("", { status: 401 })
|
||||||
|
|
||||||
return await inbox(body)
|
return await inbox(body)
|
||||||
|
|
||||||
// TODO: add support for more types! we want replies, likes, boosts, etc!
|
|
||||||
// switch (body.type) {
|
|
||||||
// case "Follow": await follow(body);
|
|
||||||
// case "Undo": await undo(body);
|
|
||||||
// case "Accept": await accept(body);
|
|
||||||
// case "Reject": await reject(body);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return new Response("", { status: 204 })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const getOutbox = async (req:Request, account:string):Promise<Response> => {
|
const getOutbox = async (req:Request):Promise<Response> => {
|
||||||
console.log("GetOutbox", account)
|
console.log("GetOutbox")
|
||||||
if (ACCOUNT !== account) return new Response("", { status: 404 })
|
|
||||||
|
|
||||||
// TODO: Paging?
|
// TODO: Paging?
|
||||||
const posts = await db.listOutboxActivities()
|
const posts = await db.listOutboxActivities()
|
||||||
|
|
||||||
return Response.json({
|
return Response.json({
|
||||||
"@context": "https://www.w3.org/ns/activitystreams",
|
"@context": "https://www.w3.org/ns/activitystreams",
|
||||||
id: `${ACTOR}/outbox`,
|
id: ACTOR.outbox,
|
||||||
type: "OrderedCollection",
|
type: "OrderedCollection",
|
||||||
totalItems: posts.length,
|
totalItems: posts.length,
|
||||||
orderedItems: posts.map((post) => ({
|
orderedItems: posts.map((post) => ({
|
||||||
...post,
|
...post,
|
||||||
actor: ACTOR
|
actor: ACTOR.id
|
||||||
})).sort( (a,b) => new Date(b.published).getTime() - new Date(a.published).getTime())
|
})).sort( (a,b) => new Date(b.published).getTime() - new Date(a.published).getTime())
|
||||||
}, { headers: { "Content-Type": "application/activity+json"} })
|
}, { headers: { "Content-Type": "application/activity+json"} })
|
||||||
}
|
}
|
||||||
|
|
||||||
const getFollowers = async (req:Request, account:String):Promise<Response> => {
|
const getFollowers = async (req:Request):Promise<Response> => {
|
||||||
console.log("GetFollowers", account)
|
console.log("GetFollowers")
|
||||||
if (ACCOUNT !== account) return new Response("", { status: 404 })
|
|
||||||
|
|
||||||
const url = new URL(req.url)
|
const url = new URL(req.url)
|
||||||
const page = url.searchParams.get("page")
|
const page = url.searchParams.get("page")
|
||||||
|
@ -118,24 +94,23 @@ const getFollowers = async (req:Request, account:String):Promise<Response> => {
|
||||||
|
|
||||||
if(!page) return Response.json({
|
if(!page) return Response.json({
|
||||||
"@context": "https://www.w3.org/ns/activitystreams",
|
"@context": "https://www.w3.org/ns/activitystreams",
|
||||||
id: `${ACTOR}/followers`,
|
id: ACTOR.followers,
|
||||||
type: "OrderedCollection",
|
type: "OrderedCollection",
|
||||||
totalItems: followers.length,
|
totalItems: followers.length,
|
||||||
first: `${ACTOR}/followers?page=1`,
|
first: `${ACTOR.followers}?page=1`,
|
||||||
})
|
})
|
||||||
else return Response.json({
|
else return Response.json({
|
||||||
"@context": "https://www.w3.org/ns/activitystreams",
|
"@context": "https://www.w3.org/ns/activitystreams",
|
||||||
id: `${ACTOR}/followers?page=${page}`,
|
id: `${ACTOR.followers}?page=${page}`,
|
||||||
type: "OrderedCollectionPage",
|
type: "OrderedCollectionPage",
|
||||||
partOf: `${ACTOR}/followers`,
|
partOf: ACTOR.followers,
|
||||||
totalItems: followers.length,
|
totalItems: followers.length,
|
||||||
orderedItems: followers.map(follower => follower.actor)
|
orderedItems: followers.map(follower => follower.actor)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const getFollowing = async (req:Request, account:String):Promise<Response> => {
|
const getFollowing = async (req:Request):Promise<Response> => {
|
||||||
console.log("GetFollowing", account)
|
console.log("GetFollowing")
|
||||||
if (ACCOUNT !== account) return new Response("", { status: 404 })
|
|
||||||
|
|
||||||
const url = new URL(req.url)
|
const url = new URL(req.url)
|
||||||
const page = url.searchParams.get("page")
|
const page = url.searchParams.get("page")
|
||||||
|
@ -143,40 +118,37 @@ const getFollowing = async (req:Request, account:String):Promise<Response> => {
|
||||||
|
|
||||||
if(!page) return Response.json({
|
if(!page) return Response.json({
|
||||||
"@context": "https://www.w3.org/ns/activitystreams",
|
"@context": "https://www.w3.org/ns/activitystreams",
|
||||||
id: `${ACTOR}/following`,
|
id: ACTOR.following,
|
||||||
type: "OrderedCollection",
|
type: "OrderedCollection",
|
||||||
totalItems: following.length,
|
totalItems: following.length,
|
||||||
first: `${ACTOR}/following?page=1`,
|
first: `${ACTOR.following}?page=1`,
|
||||||
})
|
})
|
||||||
else return Response.json({
|
else return Response.json({
|
||||||
"@context": "https://www.w3.org/ns/activitystreams",
|
"@context": "https://www.w3.org/ns/activitystreams",
|
||||||
id: `${ACTOR}/following?page=${page}`,
|
id: `${ACTOR.following}?page=${page}`,
|
||||||
type: "OrderedCollectionPage",
|
type: "OrderedCollectionPage",
|
||||||
partOf: `${ACTOR}/following`,
|
partOf: ACTOR.following,
|
||||||
totalItems: following.length,
|
totalItems: following.length,
|
||||||
orderedItems: following.map(follow => follow.actor)
|
orderedItems: following.map(follow => follow.actor)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const getActor = async (req:Request, account:string):Promise<Response> => {
|
const getActor = async (req:Request):Promise<Response> => {
|
||||||
console.log("GetActor", account)
|
console.log("GetActor")
|
||||||
if (ACCOUNT !== account) return new Response("", { status: 404 })
|
|
||||||
|
|
||||||
if(reqIsActivityPub(req)) return Response.json(ACTOR_OBJ, { headers: { "Content-Type": "application/activity+json"}})
|
if(reqIsActivityPub(req)) return Response.json(ACTOR, { headers: { "Content-Type": "application/activity+json"}})
|
||||||
else return Response.json(await db.listPosts())
|
else return Response.json(await db.listPosts())
|
||||||
}
|
}
|
||||||
|
|
||||||
const getPost = async (req:Request, account:string, id:string):Promise<Response> => {
|
const getPost = async (req:Request, id:string):Promise<Response> => {
|
||||||
console.log("GetPost", account, id)
|
console.log("GetPost", id)
|
||||||
if (ACCOUNT !== account) return new Response("", { status: 404 })
|
|
||||||
|
|
||||||
if(reqIsActivityPub(req)) return Response.json((await db.getOutboxActivity(id)).object, { headers: { "Content-Type": "application/activity+json"}})
|
if(reqIsActivityPub(req)) return Response.json((await db.getOutboxActivity(id)).object, { headers: { "Content-Type": "application/activity+json"}})
|
||||||
else return Response.json(await db.getPost(id))
|
else return Response.json(await db.getPost(id))
|
||||||
}
|
}
|
||||||
|
|
||||||
const getOutboxActivity = async (req:Request, account:string, id:string):Promise<Response> => {
|
const getOutboxActivity = async (req:Request, id:string):Promise<Response> => {
|
||||||
console.log("GetOutboxActivity", account, id)
|
console.log("GetOutboxActivity", id)
|
||||||
if (ACCOUNT !== account) return new Response("", { status: 404 })
|
|
||||||
|
|
||||||
return Response.json((await db.getOutboxActivity(id)), { headers: { "Content-Type": "application/activity+json"}})
|
return Response.json((await db.getOutboxActivity(id)), { headers: { "Content-Type": "application/activity+json"}})
|
||||||
}
|
}
|
45
src/admin.ts
45
src/admin.ts
|
@ -1,8 +1,9 @@
|
||||||
import { idsFromValue } from "./activitypub"
|
import { idsFromValue } from "./activitypub"
|
||||||
import * as db from "./db"
|
import * as db from "./db"
|
||||||
import { ACTOR, ADMIN_PASSWORD, ADMIN_USERNAME, BASE_URL, CONTENT_PATH, STATIC_PATH } from "./env"
|
import { ADMIN_PASSWORD, ADMIN_USERNAME } from "./env"
|
||||||
import outbox from "./outbox"
|
import outbox from "./outbox"
|
||||||
import { activityMimeTypes, fetchObject } from "./request"
|
import { activityMimeTypes, fetchObject } from "./request"
|
||||||
|
import ACTOR from "../actor"
|
||||||
|
|
||||||
export default (req: Request): Response | Promise<Response> | undefined => {
|
export default (req: Request): Response | Promise<Response> | undefined => {
|
||||||
const url = new URL(req.url)
|
const url = new URL(req.url)
|
||||||
|
@ -54,15 +55,15 @@ const create = async (req:Request, inReplyTo:string|null = null):Promise<Respons
|
||||||
|
|
||||||
if(!inReplyTo && body.object.inReplyTo) inReplyTo = body.object.inReplyTo
|
if(!inReplyTo && body.object.inReplyTo) inReplyTo = body.object.inReplyTo
|
||||||
|
|
||||||
const original = inReplyTo ? await (await fetchObject(ACTOR, inReplyTo)).json() : null
|
const original = inReplyTo ? await (await fetchObject(inReplyTo)).json() : null
|
||||||
|
|
||||||
// create the object, merging in supplied data
|
// create the object, merging in supplied data
|
||||||
const date = new Date()
|
const date = new Date()
|
||||||
const object = {
|
const object = {
|
||||||
attributedTo: ACTOR,
|
attributedTo: ACTOR.id,
|
||||||
published: date.toISOString(),
|
published: date.toISOString(),
|
||||||
to: ["https://www.w3.org/ns/activitystreams#Public"],
|
to: ["https://www.w3.org/ns/activitystreams#Public"],
|
||||||
cc: [`${ACTOR}/followers`],
|
cc: [ACTOR.followers],
|
||||||
...body.object
|
...body.object
|
||||||
}
|
}
|
||||||
if(inReplyTo){
|
if(inReplyTo){
|
||||||
|
@ -74,7 +75,7 @@ const create = async (req:Request, inReplyTo:string|null = null):Promise<Respons
|
||||||
"@context": "https://www.w3.org/ns/activitystreams",
|
"@context": "https://www.w3.org/ns/activitystreams",
|
||||||
type: "Create",
|
type: "Create",
|
||||||
published: date.toISOString(),
|
published: date.toISOString(),
|
||||||
actor: ACTOR,
|
actor: ACTOR.id,
|
||||||
to: object.to,
|
to: object.to,
|
||||||
cc: object.cc,
|
cc: object.cc,
|
||||||
...body,
|
...body,
|
||||||
|
@ -92,7 +93,7 @@ const follow = async (req:Request, handle:string):Promise<Response> => {
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
// this is not a valid url. Probably a someone@domain.tld format
|
// this is not a valid url. Probably a someone@domain.tld format
|
||||||
const [user, host] = handle.split('@')
|
const [_, host] = handle.split('@')
|
||||||
const res = await fetch(`https://${host}/.well-known/webfinger/?resource=acct:${handle}`)
|
const res = await fetch(`https://${host}/.well-known/webfinger/?resource=acct:${handle}`)
|
||||||
const webfinger = await res.json()
|
const webfinger = await res.json()
|
||||||
if(!webfinger.links) return new Response("", { status: 404 })
|
if(!webfinger.links) return new Response("", { status: 404 })
|
||||||
|
@ -107,7 +108,7 @@ const follow = async (req:Request, handle:string):Promise<Response> => {
|
||||||
return await outbox({
|
return await outbox({
|
||||||
"@context": "https://www.w3.org/ns/activitystreams",
|
"@context": "https://www.w3.org/ns/activitystreams",
|
||||||
type: "Follow",
|
type: "Follow",
|
||||||
actor: ACTOR,
|
actor: ACTOR.id,
|
||||||
object: url,
|
object: url,
|
||||||
to: [url, "https://www.w3.org/ns/activitystreams#Public"]
|
to: [url, "https://www.w3.org/ns/activitystreams#Public"]
|
||||||
})
|
})
|
||||||
|
@ -122,22 +123,22 @@ const unfollow = async (req:Request, handle:string):Promise<Response> => {
|
||||||
return await outbox({
|
return await outbox({
|
||||||
"@context": "https://www.w3.org/ns/activitystreams",
|
"@context": "https://www.w3.org/ns/activitystreams",
|
||||||
type: "Undo",
|
type: "Undo",
|
||||||
actor: ACTOR,
|
actor: ACTOR.id,
|
||||||
object: activity,
|
object: activity,
|
||||||
to: activity.to
|
to: activity.to
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const like = async (req:Request, object_url:string):Promise<Response> => {
|
const like = async (req:Request, object_url:string):Promise<Response> => {
|
||||||
const object = await (await fetchObject(ACTOR, object_url)).json()
|
const object = await (await fetchObject(object_url)).json()
|
||||||
|
|
||||||
return await outbox({
|
return await outbox({
|
||||||
"@context": "https://www.w3.org/ns/activitystreams",
|
"@context": "https://www.w3.org/ns/activitystreams",
|
||||||
type: "Like",
|
type: "Like",
|
||||||
actor: ACTOR,
|
actor: ACTOR.id,
|
||||||
object: object,
|
object: object,
|
||||||
to: [...idsFromValue(object.attributedTo), "https://www.w3.org/ns/activitystreams#Public"],
|
to: [...idsFromValue(object.attributedTo), "https://www.w3.org/ns/activitystreams#Public"],
|
||||||
cc: [ACTOR + 'followers']
|
cc: [ACTOR.followers]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,7 +147,7 @@ const unlike = async (req:Request, object_id:string):Promise<Response> => {
|
||||||
const liked = await db.listLiked()
|
const liked = await db.listLiked()
|
||||||
let existing = liked.find(o => o.object_id === object_id)
|
let existing = liked.find(o => o.object_id === object_id)
|
||||||
if (!existing){
|
if (!existing){
|
||||||
const object = await (await fetchObject(ACTOR, object_id)).json()
|
const object = await (await fetchObject(object_id)).json()
|
||||||
idsFromValue(object).forEach(id => {
|
idsFromValue(object).forEach(id => {
|
||||||
const e = liked.find(o => o.object_id === id)
|
const e = liked.find(o => o.object_id === id)
|
||||||
if(e) existing = e
|
if(e) existing = e
|
||||||
|
@ -158,15 +159,15 @@ const unlike = async (req:Request, object_id:string):Promise<Response> => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const dislike = async (req:Request, object_url:string):Promise<Response> => {
|
const dislike = async (req:Request, object_url:string):Promise<Response> => {
|
||||||
const object = await (await fetchObject(ACTOR, object_url)).json()
|
const object = await (await fetchObject(object_url)).json()
|
||||||
|
|
||||||
return await outbox({
|
return await outbox({
|
||||||
"@context": "https://www.w3.org/ns/activitystreams",
|
"@context": "https://www.w3.org/ns/activitystreams",
|
||||||
type: "Dislike",
|
type: "Dislike",
|
||||||
actor: ACTOR,
|
actor: ACTOR.id,
|
||||||
object: object,
|
object: object,
|
||||||
to: [...idsFromValue(object.attributedTo), "https://www.w3.org/ns/activitystreams#Public"],
|
to: [...idsFromValue(object.attributedTo), "https://www.w3.org/ns/activitystreams#Public"],
|
||||||
cc: [ACTOR + 'followers']
|
cc: [ACTOR.followers]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,7 +176,7 @@ const undislike = async (req:Request, object_id:string):Promise<Response> => {
|
||||||
const disliked = await db.listDisliked()
|
const disliked = await db.listDisliked()
|
||||||
let existing = disliked.find(o => o.object_id === object_id)
|
let existing = disliked.find(o => o.object_id === object_id)
|
||||||
if (!existing){
|
if (!existing){
|
||||||
const object = await (await fetchObject(ACTOR, object_id)).json()
|
const object = await (await fetchObject(object_id)).json()
|
||||||
idsFromValue(object).forEach(id => {
|
idsFromValue(object).forEach(id => {
|
||||||
const e = disliked.find(o => o.object_id === id)
|
const e = disliked.find(o => o.object_id === id)
|
||||||
if(e) existing = e
|
if(e) existing = e
|
||||||
|
@ -187,15 +188,15 @@ const undislike = async (req:Request, object_id:string):Promise<Response> => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const share = async (req:Request, object_url:string):Promise<Response> => {
|
const share = async (req:Request, object_url:string):Promise<Response> => {
|
||||||
const object = await (await fetchObject(ACTOR, object_url)).json()
|
const object = await (await fetchObject(object_url)).json()
|
||||||
|
|
||||||
return await outbox({
|
return await outbox({
|
||||||
"@context": "https://www.w3.org/ns/activitystreams",
|
"@context": "https://www.w3.org/ns/activitystreams",
|
||||||
type: "Announce",
|
type: "Announce",
|
||||||
actor: ACTOR,
|
actor: ACTOR.id,
|
||||||
object: object,
|
object: object,
|
||||||
to: [...idsFromValue(object.attributedTo), "https://www.w3.org/ns/activitystreams#Public"],
|
to: [...idsFromValue(object.attributedTo), "https://www.w3.org/ns/activitystreams#Public"],
|
||||||
cc: [ACTOR + 'followers']
|
cc: [ACTOR.followers]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,7 +205,7 @@ const unshare = async (req:Request, object_id:string):Promise<Response> => {
|
||||||
const shared = await db.listShared()
|
const shared = await db.listShared()
|
||||||
let existing = shared.find(o => o.object_id === object_id)
|
let existing = shared.find(o => o.object_id === object_id)
|
||||||
if (!existing){
|
if (!existing){
|
||||||
const object = await (await fetchObject(ACTOR, object_id)).json()
|
const object = await (await fetchObject(object_id)).json()
|
||||||
idsFromValue(object).forEach(id => {
|
idsFromValue(object).forEach(id => {
|
||||||
const e = shared.find(o => o.object_id === id)
|
const e = shared.find(o => o.object_id === id)
|
||||||
if(e) existing = e
|
if(e) existing = e
|
||||||
|
@ -220,7 +221,7 @@ const undo = async(activity:any):Promise<Response> => {
|
||||||
return await outbox({
|
return await outbox({
|
||||||
"@context": "https://www.w3.org/ns/activitystreams",
|
"@context": "https://www.w3.org/ns/activitystreams",
|
||||||
type: "Undo",
|
type: "Undo",
|
||||||
actor: ACTOR,
|
actor: ACTOR.id,
|
||||||
object: activity,
|
object: activity,
|
||||||
to: activity.to,
|
to: activity.to,
|
||||||
cc: activity.cc
|
cc: activity.cc
|
||||||
|
@ -234,7 +235,7 @@ const deletePost = async (req:Request, id:string):Promise<Response> => {
|
||||||
return await outbox({
|
return await outbox({
|
||||||
"@context": "https://www.w3.org/ns/activitystreams",
|
"@context": "https://www.w3.org/ns/activitystreams",
|
||||||
type: "Delete",
|
type: "Delete",
|
||||||
actor: ACTOR,
|
actor: ACTOR.id,
|
||||||
to: activity.to,
|
to: activity.to,
|
||||||
cc: activity.cc,
|
cc: activity.cc,
|
||||||
audience: activity.audience,
|
audience: activity.audience,
|
||||||
|
|
11
src/db.ts
11
src/db.ts
|
@ -1,9 +1,10 @@
|
||||||
import { ACCOUNT, ACTIVITY_INBOX_PATH, ACTIVITY_OUTBOX_PATH, ACTOR, CONTENT_PATH, DATA_PATH, POSTS_PATH, PUBLIC_KEY, STATIC_PATH } from "./env";
|
import { ACTIVITY_INBOX_PATH, ACTIVITY_OUTBOX_PATH, CONTENT_PATH, DATA_PATH, POSTS_PATH, STATIC_PATH } from "./env"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { readdir } from "fs/promises"
|
import { readdir } from "fs/promises"
|
||||||
import { unlinkSync } from "node:fs"
|
import { unlinkSync } from "node:fs"
|
||||||
import { fetchObject } from "./request";
|
import { fetchObject } from "./request"
|
||||||
import { idsFromValue } from "./activitypub";
|
import { idsFromValue } from "./activitypub"
|
||||||
|
import ACTOR from "../actor"
|
||||||
const matter = require('gray-matter')
|
const matter = require('gray-matter')
|
||||||
const Eleventy = require("@11ty/eleventy")
|
const Eleventy = require("@11ty/eleventy")
|
||||||
|
|
||||||
|
@ -50,7 +51,7 @@ export async function listOutboxActivities() {
|
||||||
export async function createPost(post_object:any, object_id:string) {
|
export async function createPost(post_object:any, object_id:string) {
|
||||||
const file = Bun.file(path.join(POSTS_PATH, `${object_id}.md`))
|
const file = Bun.file(path.join(POSTS_PATH, `${object_id}.md`))
|
||||||
let {type, object, inReplyTo} = post_object
|
let {type, object, inReplyTo} = post_object
|
||||||
if(inReplyTo && typeof inReplyTo === 'string') inReplyTo = await fetchObject(ACTOR, inReplyTo)
|
if(inReplyTo && typeof inReplyTo === 'string') inReplyTo = await fetchObject(inReplyTo)
|
||||||
|
|
||||||
if(object){
|
if(object){
|
||||||
let { content, published, id, attributedTo } = object
|
let { content, published, id, attributedTo } = object
|
||||||
|
@ -91,7 +92,7 @@ export async function getPost(id:string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getPostByURL(url_id:string) {
|
export async function getPostByURL(url_id:string) {
|
||||||
if(!url_id || !url_id.startsWith(ACTOR + '/posts/')) return null
|
if(!url_id || !url_id.startsWith(ACTOR.url + '/posts/')) return null
|
||||||
const match = url_id.match(/\/([0-9a-f]+)\/?$/)
|
const match = url_id.match(/\/([0-9a-f]+)\/?$/)
|
||||||
const local_id = match ? match[1] : url_id
|
const local_id = match ? match[1] : url_id
|
||||||
return await getPost(local_id)
|
return await getPost(local_id)
|
||||||
|
|
86
src/env.ts
86
src/env.ts
|
@ -1,9 +1,6 @@
|
||||||
import forge from "node-forge" // import crypto from "node:crypto"
|
import forge from "node-forge" // import crypto from "node:crypto"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
|
|
||||||
// change "activitypub" to whatever you want your account name to be
|
|
||||||
export const ACCOUNT = process.env.ACCOUNT || "activitypub"
|
|
||||||
|
|
||||||
// set up username and password for admin actions
|
// set up username and password for admin actions
|
||||||
export const ADMIN_USERNAME = process.env.ADMIN_USERNAME || "";
|
export const ADMIN_USERNAME = process.env.ADMIN_USERNAME || "";
|
||||||
export const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || "";
|
export const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || "";
|
||||||
|
@ -15,8 +12,6 @@ export const PORT = process.env.PORT || "3000"
|
||||||
|
|
||||||
export const BASE_URL = (HOSTNAME === "localhost" ? "http://" : "https://") + HOSTNAME
|
export const BASE_URL = (HOSTNAME === "localhost" ? "http://" : "https://") + HOSTNAME
|
||||||
|
|
||||||
export const ACTOR = BASE_URL //+ '/' + ACCOUNT
|
|
||||||
|
|
||||||
// in development, generate a key pair to make it easier to get started
|
// in development, generate a key pair to make it easier to get started
|
||||||
const keypair =
|
const keypair =
|
||||||
NODE_ENV === "development"
|
NODE_ENV === "development"
|
||||||
|
@ -50,30 +45,57 @@ export const DEFAULT_DOCUMENTS = process.env.DEFAULT_DOCUMENTS || [
|
||||||
'default.htm'
|
'default.htm'
|
||||||
]
|
]
|
||||||
|
|
||||||
export const ACTOR_OBJ = {
|
// export const ACTOR_OBJ = {
|
||||||
"@context": [
|
// "@context": [
|
||||||
"https://www.w3.org/ns/activitystreams",
|
// "https://www.w3.org/ns/activitystreams",
|
||||||
"https://w3id.org/security/v1",
|
// "https://w3id.org/security/v1",
|
||||||
],
|
// ],
|
||||||
id: ACTOR,
|
// id: ACTOR,
|
||||||
type: "Person",
|
// type: "Person",
|
||||||
preferredUsername: ACCOUNT,
|
// following: `${ACTOR}/following`,
|
||||||
url: ACTOR,
|
// followers: `${ACTOR}/followers`,
|
||||||
manuallyApprovesFollowers: false,
|
// inbox: `${ACTOR}/inbox`,
|
||||||
discoverable: true,
|
// outbox: `${ACTOR}/outbox`,
|
||||||
published: "2023-09-14T00:00:00Z",
|
// //featured: `${ACTOR}/featured`,
|
||||||
inbox: `${ACTOR}/inbox`,
|
// //featuredTags: `${ACTOR}/tags`,
|
||||||
outbox: `${ACTOR}/outbox`,
|
// preferredUsername: ACCOUNT,
|
||||||
followers: `${ACTOR}/followers`,
|
// name: REAL_NAME,
|
||||||
following: `${ACTOR}/following`,
|
// summary: SUMMARY,
|
||||||
publicKey: {
|
// url: ACTOR,
|
||||||
id: `${ACTOR}#main-key`,
|
// manuallyApprovesFollowers: false,
|
||||||
owner: ACTOR,
|
// discoverable: true,
|
||||||
publicKeyPem: PUBLIC_KEY,
|
// published: "2023-09-14T00:00:00Z",
|
||||||
},
|
// //devices: `${ACTOR}/devices`
|
||||||
icon: {
|
// alsoKnownAs: ALSO_KNOWN_AS,
|
||||||
type: "Image",
|
// publicKey: {
|
||||||
mediaType: "image/png",
|
// id: `${ACTOR}#main-key`,
|
||||||
url: BASE_URL + "/assets/img/avatar-tt@800.png"
|
// owner: ACTOR,
|
||||||
},
|
// publicKeyPem: PUBLIC_KEY,
|
||||||
}
|
// },
|
||||||
|
// // attachment: [
|
||||||
|
// // {
|
||||||
|
// // "type": "PropertyValue",
|
||||||
|
// // "name": "Pronouns",
|
||||||
|
// // "value": "he/him"
|
||||||
|
// // },
|
||||||
|
// // {
|
||||||
|
// // "type": "PropertyValue",
|
||||||
|
// // "name": "Website",
|
||||||
|
// // "value": "<a href=\"https://death.id.au\" target=\"_blank\" rel=\"nofollow noopener noreferrer me\"><span class=\"invisible\">https://</span>death.id.au</a>"
|
||||||
|
// // }
|
||||||
|
// // ],
|
||||||
|
// // tag: [],
|
||||||
|
// // endpoints: {
|
||||||
|
// // sharedInbox: N/A
|
||||||
|
// // },
|
||||||
|
// icon: {
|
||||||
|
// type: "Image",
|
||||||
|
// mediaType: "image/svg+xml",
|
||||||
|
// url: BASE_URL + "/assets/img/avatar-tt.svg"
|
||||||
|
// },
|
||||||
|
// image: {
|
||||||
|
// type: "Image",
|
||||||
|
// mediaType: "image/jpeg",
|
||||||
|
// url: BASE_URL + "/assets/img/banner-1500x500.jpg"
|
||||||
|
// }
|
||||||
|
// }
|
16
src/inbox.ts
16
src/inbox.ts
|
@ -1,8 +1,8 @@
|
||||||
import { idsFromValue } from "./activitypub";
|
import { idsFromValue } from "./activitypub"
|
||||||
import * as db from "./db";
|
import * as db from "./db"
|
||||||
import { ACTOR, BASE_URL } from "./env";
|
import outbox from "./outbox"
|
||||||
import outbox from "./outbox";
|
import { send } from "./request"
|
||||||
import { send } from "./request";
|
import ACTOR from "../actor"
|
||||||
|
|
||||||
export default async function inbox(activity:any) {
|
export default async function inbox(activity:any) {
|
||||||
const date = new Date()
|
const date = new Date()
|
||||||
|
@ -10,8 +10,8 @@ export default async function inbox(activity:any) {
|
||||||
const recipientList = [...new Set([...idsFromValue(activity.to), ...idsFromValue(activity.cc), ...idsFromValue(activity.audience)])]
|
const recipientList = [...new Set([...idsFromValue(activity.to), ...idsFromValue(activity.cc), ...idsFromValue(activity.audience)])]
|
||||||
|
|
||||||
// if my list of followers in the list of recipients, then forward to them as well
|
// if my list of followers in the list of recipients, then forward to them as well
|
||||||
if(recipientList.includes(ACTOR + "/followers")) {
|
if(recipientList.includes(ACTOR.url + "/followers")) {
|
||||||
(await db.listFollowers()).forEach(f => send(activity.attributedTo, f, activity))
|
(await db.listFollowers()).forEach(f => send(f, activity, activity.attributedTo))
|
||||||
}
|
}
|
||||||
|
|
||||||
// save this activity to my inbox
|
// save this activity to my inbox
|
||||||
|
@ -37,7 +37,7 @@ const follow = async (activity:any, id:string) => {
|
||||||
await outbox({
|
await outbox({
|
||||||
"@context": "https://www.w3.org/ns/activitystreams",
|
"@context": "https://www.w3.org/ns/activitystreams",
|
||||||
type: "Accept",
|
type: "Accept",
|
||||||
actor: ACTOR,
|
actor: ACTOR.id,
|
||||||
to: [activity.actor],
|
to: [activity.actor],
|
||||||
object: activity,
|
object: activity,
|
||||||
});
|
});
|
||||||
|
|
23
src/index.ts
23
src/index.ts
|
@ -1,10 +1,11 @@
|
||||||
import { ACCOUNT, ACTOR, DEFAULT_DOCUMENTS, HOSTNAME, STATIC_PATH } from "./env";
|
import { DEFAULT_DOCUMENTS, HOSTNAME, STATIC_PATH } from "./env"
|
||||||
import admin from './admin'
|
import admin from './admin'
|
||||||
import activitypub from "./activitypub";
|
import activitypub from "./activitypub"
|
||||||
import { fetchObject } from "./request";
|
import { fetchObject } from "./request"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { BunFile } from "bun";
|
import { BunFile } from "bun"
|
||||||
import { rebuild } from "./db";
|
import { rebuild } from "./db"
|
||||||
|
import ACTOR from "../actor"
|
||||||
|
|
||||||
rebuild()
|
rebuild()
|
||||||
|
|
||||||
|
@ -22,7 +23,7 @@ const server = Bun.serve({
|
||||||
const object_url = url.searchParams.get('url')
|
const object_url = url.searchParams.get('url')
|
||||||
if(!object_url) return new Response("No url supplied", { status: 400})
|
if(!object_url) return new Response("No url supplied", { status: 400})
|
||||||
|
|
||||||
return fetchObject(ACTOR, object_url)
|
return fetchObject(object_url)
|
||||||
}
|
}
|
||||||
|
|
||||||
return admin(req) || activitypub(req) || staticFile(req)
|
return admin(req) || activitypub(req) || staticFile(req)
|
||||||
|
@ -31,21 +32,21 @@ const server = Bun.serve({
|
||||||
|
|
||||||
const webfinger = async (req: Request, resource: string | null) => {
|
const webfinger = async (req: Request, resource: string | null) => {
|
||||||
|
|
||||||
if(resource !== `acct:${ACCOUNT}@${HOSTNAME}`) return new Response("", { status: 404 })
|
if(resource !== `acct:${ACTOR.preferredUsername}@${HOSTNAME}`) return new Response("", { status: 404 })
|
||||||
|
|
||||||
return Response.json({
|
return Response.json({
|
||||||
subject: `acct:${ACCOUNT}@${HOSTNAME}`,
|
subject: `acct:${ACTOR.preferredUsername}@${HOSTNAME}`,
|
||||||
aliases: [ACTOR],
|
aliases: [ACTOR.id],
|
||||||
links: [
|
links: [
|
||||||
{
|
{
|
||||||
"rel": "http://webfinger.net/rel/profile-page",
|
"rel": "http://webfinger.net/rel/profile-page",
|
||||||
"type": "text/html",
|
"type": "text/html",
|
||||||
"href": ACTOR
|
"href": ACTOR.url
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
rel: "self",
|
rel: "self",
|
||||||
type: "application/activity+json",
|
type: "application/activity+json",
|
||||||
href: ACTOR,
|
href: ACTOR.id,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}, { headers: { "content-type": "application/activity+json" }})
|
}, { headers: { "content-type": "application/activity+json" }})
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { idsFromValue } from "./activitypub"
|
import { idsFromValue } from "./activitypub"
|
||||||
import * as db from "./db";
|
import * as db from "./db"
|
||||||
import { ACTOR } from "./env"
|
import { fetchObject, send } from "./request"
|
||||||
import { fetchObject, send } from "./request";
|
import ACTOR from "../actor"
|
||||||
|
|
||||||
export default async function outbox(activity:any):Promise<Response> {
|
export default async function outbox(activity:any):Promise<Response> {
|
||||||
const date = new Date()
|
const date = new Date()
|
||||||
|
@ -14,7 +14,7 @@ export default async function outbox(activity:any):Promise<Response> {
|
||||||
activity = {
|
activity = {
|
||||||
"@context": "https://www.w3.org/ns/activitystreams",
|
"@context": "https://www.w3.org/ns/activitystreams",
|
||||||
type: "Create",
|
type: "Create",
|
||||||
actor: ACTOR,
|
actor: ACTOR.id,
|
||||||
object
|
object
|
||||||
}
|
}
|
||||||
const { to, bto, cc, bcc, audience } = object
|
const { to, bto, cc, bcc, audience } = object
|
||||||
|
@ -25,7 +25,7 @@ export default async function outbox(activity:any):Promise<Response> {
|
||||||
if(audience) activity.audience = audience
|
if(audience) activity.audience = audience
|
||||||
}
|
}
|
||||||
|
|
||||||
activity.id = `${ACTOR}/outbox/${id}`
|
activity.id = `${ACTOR.url}/outbox/${id}`
|
||||||
if(!activity.published) activity.published = date.toISOString()
|
if(!activity.published) activity.published = date.toISOString()
|
||||||
|
|
||||||
if(activity.type === 'Create' && activity.object && Object(activity.object) === activity.object) {
|
if(activity.type === 'Create' && activity.object && Object(activity.object) === activity.object) {
|
||||||
|
@ -59,16 +59,16 @@ export default async function outbox(activity:any):Promise<Response> {
|
||||||
|
|
||||||
// send to the appropriate recipients
|
// send to the appropriate recipients
|
||||||
finalRecipientList.forEach((to) => {
|
finalRecipientList.forEach((to) => {
|
||||||
if (to.startsWith(ACTOR + "/followers")) db.listFollowers().then(followers => followers.forEach(f => send(ACTOR, f.actor, activity)))
|
if (to.startsWith(ACTOR.url + "/followers")) db.listFollowers().then(followers => followers.forEach(f => send(f.actor, activity)))
|
||||||
else if (to === "https://www.w3.org/ns/activitystreams#Public") return // there's nothing to "send" to here
|
else if (to === "https://www.w3.org/ns/activitystreams#Public") return // there's nothing to "send" to here
|
||||||
else if (to) send(ACTOR, to, activity)
|
else if (to) send(to, activity)
|
||||||
})
|
})
|
||||||
|
|
||||||
return new Response("", { status: 201, headers: { location: activity.id } })
|
return new Response("", { status: 201, headers: { location: activity.id } })
|
||||||
}
|
}
|
||||||
|
|
||||||
async function create(activity:any, id:string) {
|
async function create(activity:any, id:string) {
|
||||||
activity.object.id = activity.object.url = `${ACTOR}/posts/${id}`
|
activity.object.id = activity.object.url = `${ACTOR.url}/posts/${id}`
|
||||||
await db.createPost(activity.object, id)
|
await db.createPost(activity.object, id)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -85,7 +85,7 @@ async function follow(activity:any, id:string) {
|
||||||
async function like(activity:any, id:string) {
|
async function like(activity:any, id:string) {
|
||||||
if(typeof activity.object === 'string'){
|
if(typeof activity.object === 'string'){
|
||||||
await db.createLiked(activity.object, id)
|
await db.createLiked(activity.object, id)
|
||||||
activity.object = await fetchObject(ACTOR, activity.object)
|
activity.object = await fetchObject(activity.object)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const liked = await idsFromValue(activity.object)
|
const liked = await idsFromValue(activity.object)
|
||||||
|
@ -98,7 +98,7 @@ async function like(activity:any, id:string) {
|
||||||
async function dislike(activity:any, id:string) {
|
async function dislike(activity:any, id:string) {
|
||||||
if(typeof activity.object === 'string'){
|
if(typeof activity.object === 'string'){
|
||||||
await db.createDisliked(activity.object, id)
|
await db.createDisliked(activity.object, id)
|
||||||
activity.object = await fetchObject(ACTOR, activity.object)
|
activity.object = await fetchObject(activity.object)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const disliked = await idsFromValue(activity.object)
|
const disliked = await idsFromValue(activity.object)
|
||||||
|
@ -111,7 +111,7 @@ async function dislike(activity:any, id:string) {
|
||||||
async function announce(activity:any, id:string) {
|
async function announce(activity:any, id:string) {
|
||||||
if(typeof activity.object === 'string'){
|
if(typeof activity.object === 'string'){
|
||||||
await db.createShared(activity.object, id)
|
await db.createShared(activity.object, id)
|
||||||
activity.object = await fetchObject(ACTOR, activity.object)
|
activity.object = await fetchObject(activity.object)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const shared = await idsFromValue(activity.object)
|
const shared = await idsFromValue(activity.object)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import forge from "node-forge" // import crypto from "node:crypto"
|
import forge from "node-forge" // import crypto from "node:crypto"
|
||||||
import { ACTOR, PRIVATE_KEY } from "./env";
|
import { PRIVATE_KEY } from "./env";
|
||||||
|
import ACTOR from "../actor"
|
||||||
|
|
||||||
export const activityMimeTypes:string[] = ['application/activity+json', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"']
|
export const activityMimeTypes:string[] = ['application/activity+json', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"']
|
||||||
|
|
||||||
|
@ -39,7 +40,7 @@ export function signedFetch(url: string | URL | Request, init?: FetchRequestInit
|
||||||
const data = Object.entries(dataObj).map(([key, value]) => `${key}: ${value}`).join('\n')
|
const data = Object.entries(dataObj).map(([key, value]) => `${key}: ${value}`).join('\n')
|
||||||
const signature = forge.util.encode64(key.sign(forge.md.sha256.create().update(data)))
|
const signature = forge.util.encode64(key.sign(forge.md.sha256.create().update(data)))
|
||||||
const signatureObj = {
|
const signatureObj = {
|
||||||
keyId: `${ACTOR}#main-key`,
|
keyId: `${ACTOR.id}#main-key`,
|
||||||
headers: Object.keys(dataObj).join(' '),
|
headers: Object.keys(dataObj).join(' '),
|
||||||
signature: signature
|
signature: signature
|
||||||
}
|
}
|
||||||
|
@ -56,11 +57,11 @@ export function signedFetch(url: string | URL | Request, init?: FetchRequestInit
|
||||||
|
|
||||||
/** Fetches and returns an actor at a URL. */
|
/** Fetches and returns an actor at a URL. */
|
||||||
async function fetchActor(url:string) {
|
async function fetchActor(url:string) {
|
||||||
return await (await fetchObject(ACTOR, url)).json()
|
return await (await fetchObject(url)).json()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Fetches and returns an object at a URL. */
|
/** Fetches and returns an object at a URL. */
|
||||||
export async function fetchObject(sender:string, object_url:string) {
|
export async function fetchObject(object_url:string) {
|
||||||
console.log(`fetch ${object_url}`)
|
console.log(`fetch ${object_url}`)
|
||||||
|
|
||||||
const res = await fetch(object_url);
|
const res = await fetch(object_url);
|
||||||
|
@ -77,7 +78,7 @@ export async function fetchObject(sender:string, object_url:string) {
|
||||||
* @param recipient The recipient's actor URL.
|
* @param recipient The recipient's actor URL.
|
||||||
* @param message the body of the request to send.
|
* @param message the body of the request to send.
|
||||||
*/
|
*/
|
||||||
export async function send(sender:string, recipient:string, message:any) {
|
export async function send(recipient:string, message:any, from:string=ACTOR.id) {
|
||||||
console.log(`Sending to ${recipient}`, message)
|
console.log(`Sending to ${recipient}`, message)
|
||||||
// TODO: revisit fetch actor to use webfinger to get the inbox maybe?
|
// TODO: revisit fetch actor to use webfinger to get the inbox maybe?
|
||||||
const actor = await fetchActor(recipient)
|
const actor = await fetchActor(recipient)
|
||||||
|
|
Loading…
Reference in a new issue