2023-09-20 06:43:48 +00:00
|
|
|
import { idsFromValue } from "./activitypub"
|
|
|
|
|
import * as db from "./db";
|
|
|
|
|
import { ACTOR } from "./env"
|
|
|
|
|
import { fetchObject, send } from "./request";
|
|
|
|
|
|
|
|
|
|
export default async function outbox(activity:any):Promise<Response> {
|
|
|
|
|
const date = new Date()
|
|
|
|
|
const id = `${date.getTime().toString(16)}`
|
|
|
|
|
console.log('outbox', id, activity)
|
|
|
|
|
|
|
|
|
|
// https://www.w3.org/TR/activitypub/#object-without-create
|
|
|
|
|
if(!activity.actor && !(activity.object || activity.target || activity.result || activity.origin || activity.instrument)) {
|
|
|
|
|
const object = activity
|
|
|
|
|
activity = {
|
|
|
|
|
"@context": "https://www.w3.org/ns/activitystreams",
|
|
|
|
|
type: "Create",
|
|
|
|
|
actor: ACTOR,
|
|
|
|
|
object
|
|
|
|
|
}
|
|
|
|
|
const { to, bto, cc, bcc, audience } = object
|
|
|
|
|
if(to) activity.to = to
|
|
|
|
|
if(bto) activity.bto = bto
|
|
|
|
|
if(cc) activity.cc = cc
|
|
|
|
|
if(bcc) activity.bcc = bcc
|
|
|
|
|
if(audience) activity.audience = audience
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
activity.id = `${ACTOR}/outbox/${id}`
|
|
|
|
|
if(!activity.published) activity.published = date.toISOString()
|
|
|
|
|
|
|
|
|
|
if(activity.type === 'Create' && activity.object && Object(activity.object) === activity.object) {
|
|
|
|
|
// When a Create activity is posted, the actor of the activity SHOULD be copied onto the object's attributedTo field.
|
|
|
|
|
activity.object.attributedTo = activity.actor
|
|
|
|
|
if(!activity.object.published) activity.object.published = activity.published
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// get the main recipients ([...new Set()] is to dedupe)
|
2023-09-21 07:09:05 +00:00
|
|
|
const recipientList = [...new Set([...idsFromValue(activity.to), ...idsFromValue(activity.cc), ...idsFromValue(activity.audience)])]
|
2023-09-20 06:43:48 +00:00
|
|
|
// add in the blind recipients
|
2023-09-21 07:09:05 +00:00
|
|
|
const finalRecipientList = [...new Set([...recipientList, ...idsFromValue(activity.bto), ...idsFromValue(activity.bcc)])]
|
2023-09-20 06:43:48 +00:00
|
|
|
// remove the blind recipients from the activity
|
|
|
|
|
delete activity.bto
|
|
|
|
|
delete activity.bcc
|
|
|
|
|
|
|
|
|
|
// now that has been taken care of, it's time to update our local data, depending on the contents of the activity
|
|
|
|
|
switch(activity.type) {
|
|
|
|
|
case "Accept": await accept(activity, id); break;
|
|
|
|
|
case "Follow": await follow(activity, id); break;
|
|
|
|
|
case "Like": await like(activity, id); break;
|
2023-09-21 07:04:27 +00:00
|
|
|
case "Dislike": await dislike(activity, id); break;
|
|
|
|
|
case "Annouce": await announce(activity, id); break;
|
2023-09-20 06:43:48 +00:00
|
|
|
case "Create": await create(activity, id); break;
|
|
|
|
|
case "Undo": await undo(activity); break;
|
2023-09-21 07:04:27 +00:00
|
|
|
case "Delete": await deletePost(activity); break;
|
2023-09-20 06:43:48 +00:00
|
|
|
// TODO: case "Anncounce": return await share(activity)
|
|
|
|
|
}
|
|
|
|
|
// save the activity data for the outbox
|
|
|
|
|
await db.createOutboxActivity(activity, id)
|
|
|
|
|
|
|
|
|
|
// send to the appropriate recipients
|
|
|
|
|
finalRecipientList.forEach((to) => {
|
|
|
|
|
if (to.startsWith(ACTOR + "/followers")) db.listFollowers().then(followers => followers.forEach(f => send(ACTOR, f.actor, activity)))
|
|
|
|
|
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)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return new Response("", { status: 201, headers: { location: activity.id } })
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function create(activity:any, id:string) {
|
|
|
|
|
activity.object.id = activity.object.url = `${ACTOR}/post/${id}`
|
|
|
|
|
await db.createPost(activity.object, id)
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function accept(activity:any, id:string) {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function follow(activity:any, id:string) {
|
|
|
|
|
await db.createFollowing(activity.object , id)
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function like(activity:any, id:string) {
|
|
|
|
|
if(typeof activity.object === 'string'){
|
|
|
|
|
await db.createLiked(activity.object, id)
|
|
|
|
|
activity.object = await fetchObject(ACTOR, activity.object)
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
const liked = await idsFromValue(activity.object)
|
|
|
|
|
liked.forEach(l => db.createLiked(l, id))
|
|
|
|
|
}
|
|
|
|
|
await db.createPost(activity, id)
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-21 07:04:27 +00:00
|
|
|
async function dislike(activity:any, id:string) {
|
|
|
|
|
if(typeof activity.object === 'string'){
|
|
|
|
|
await db.createDisliked(activity.object, id)
|
|
|
|
|
activity.object = await fetchObject(ACTOR, activity.object)
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
const disliked = await idsFromValue(activity.object)
|
|
|
|
|
disliked.forEach(l => db.createDisliked(l, id))
|
|
|
|
|
}
|
|
|
|
|
await db.createPost(activity, id)
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function announce(activity:any, id:string) {
|
|
|
|
|
if(typeof activity.object === 'string'){
|
|
|
|
|
await db.createShared(activity.object, id)
|
|
|
|
|
activity.object = await fetchObject(ACTOR, activity.object)
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
const shared = await idsFromValue(activity.object)
|
|
|
|
|
shared.forEach(l => db.createShared(l, id))
|
|
|
|
|
}
|
|
|
|
|
await db.createPost(activity, id)
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-20 06:43:48 +00:00
|
|
|
// async function share(activity:any) {
|
|
|
|
|
// let object = activity.object
|
|
|
|
|
// if(typeof object === 'string') {
|
|
|
|
|
// try{
|
|
|
|
|
// object = await fetchObject(object)
|
|
|
|
|
// }
|
|
|
|
|
// catch { }
|
|
|
|
|
// }
|
|
|
|
|
// db.createShared(object)
|
|
|
|
|
// return true
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
async function undo(activity:any) {
|
|
|
|
|
const id = await idsFromValue(activity.object).at(0)
|
|
|
|
|
if (!id) return true
|
|
|
|
|
const match = id.match(/\/([0-9a-f]+)\/?$/)
|
|
|
|
|
const local_id = match ? match[1] : id
|
|
|
|
|
console.log('undo', local_id)
|
|
|
|
|
try{
|
|
|
|
|
const existing = await db.getOutboxActivity(local_id)
|
|
|
|
|
|
|
|
|
|
switch(activity.object.type) {
|
|
|
|
|
case "Follow": await db.deleteFollowing(existing.object); break;
|
|
|
|
|
case "Like": idsFromValue(existing.object).forEach(async id => await db.deleteLiked(id)); await db.deletePost(local_id); break;
|
2023-09-21 07:04:27 +00:00
|
|
|
case "Dislike": idsFromValue(existing.object).forEach(async id => await db.deleteDisliked(id)); await db.deletePost(local_id); break;
|
|
|
|
|
case "Announce": idsFromValue(existing.object).forEach(async id => await db.deleteShared(id)); await db.deletePost(local_id); break;
|
|
|
|
|
|
2023-09-20 06:43:48 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-21 07:04:27 +00:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function deletePost(activity:any) {
|
|
|
|
|
const id = await idsFromValue(activity.object).at(0)
|
|
|
|
|
if(!id) return false
|
|
|
|
|
const post = await db.getPostByURL(id)
|
|
|
|
|
if(!post) return false
|
|
|
|
|
await db.deletePost(post.local_id)
|
2023-09-20 06:43:48 +00:00
|
|
|
return true
|
|
|
|
|
}
|