More federation work
This commit is contained in:
parent
aea8a2d826
commit
ce30ca9d34
|
@ -33,6 +33,9 @@ export interface UndoActivity extends Activity {
|
|||
}
|
||||
|
||||
export interface NoteObject extends Object {
|
||||
to: string[];
|
||||
cc: string[];
|
||||
directMessage?: boolean;
|
||||
attributedTo: string;
|
||||
content: string;
|
||||
published: string;
|
||||
|
@ -46,7 +49,9 @@ export interface DeleteActivity extends Activity {
|
|||
|
||||
export interface ActorObject {
|
||||
id: string;
|
||||
url: string;
|
||||
name: string;
|
||||
preferredUsername: string;
|
||||
inbox: string;
|
||||
endpoints?: {
|
||||
sharedInbox?: string;
|
||||
|
|
|
@ -2,7 +2,13 @@ import { promises as fs } from "fs";
|
|||
import crypto from "crypto";
|
||||
import uuidv4 from "uuid/v4";
|
||||
import request from "request";
|
||||
import { Activity, ArticleObject, FollowActivity, AcceptActivity, ActorObject, CreateActivity } from "./activity";
|
||||
import {
|
||||
Activity,
|
||||
ArticleObject,
|
||||
ActorObject,
|
||||
CreateActivity,
|
||||
NoteObject
|
||||
} from "./activity";
|
||||
import { URL } from "url";
|
||||
import { getConnection } from "typeorm";
|
||||
import Actor from "../entity/Actor";
|
||||
|
@ -10,7 +16,7 @@ import Article from "../entity/Article";
|
|||
|
||||
const domain = process.env.DOMAIN;
|
||||
|
||||
function createActivity(article: ArticleObject): CreateActivity {
|
||||
export function createActivity(object: ArticleObject | NoteObject): CreateActivity {
|
||||
const uuid = uuidv4();
|
||||
const createObject = {
|
||||
"@context": [
|
||||
|
@ -19,9 +25,9 @@ function createActivity(article: ArticleObject): CreateActivity {
|
|||
"type": "Create",
|
||||
"id": `https://${domain}/ap/${uuid}`,
|
||||
"actor": `https://${domain}/ap/actor`,
|
||||
"to": article.to,
|
||||
"cc": article.cc,
|
||||
"object": article
|
||||
"to": object.to,
|
||||
"cc": object.cc,
|
||||
"object": object
|
||||
};
|
||||
return createObject;
|
||||
}
|
||||
|
@ -44,15 +50,7 @@ export async function getCachedActor(url: string): Promise<ActorObject | null> {
|
|||
const result = await getConnection().manager.findByIds(Actor, [url]);
|
||||
if (result.length > 0) {
|
||||
const actor = result[0];
|
||||
return {
|
||||
id: actor.id,
|
||||
name: actor.displayName,
|
||||
inbox: actor.inbox,
|
||||
icon: actor.iconURL,
|
||||
publicKey: {
|
||||
publicKeyPem: actor.publicKeyPem
|
||||
}
|
||||
} as ActorObject;
|
||||
return actor.actorObject;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Router, Request, Response } from "express";
|
||||
import uuidv4 from "uuid/v4";
|
||||
import { getActor, signAndSend } from "./federate";
|
||||
import {createActivity, getActor, signAndSend} from "./federate";
|
||||
import { Activity, FollowActivity, AcceptActivity, UndoActivity, CreateActivity, NoteObject, DeleteActivity } from "./activity";
|
||||
import { Database } from "sqlite3";
|
||||
import { URL } from "url";
|
||||
|
@ -8,6 +8,7 @@ import sanitizeHtml from "sanitize-html";
|
|||
import Note from "../entity/Note";
|
||||
import { getConnection } from "typeorm";
|
||||
import Actor from "../entity/Actor";
|
||||
import Article from "../entity/Article";
|
||||
|
||||
const domain = process.env.DOMAIN;
|
||||
|
||||
|
@ -16,7 +17,7 @@ export default function inbox(router: Router) {
|
|||
router.post("/inbox", handleInbox);
|
||||
}
|
||||
|
||||
async function handleInbox(req: Request, res: Response) {
|
||||
function handleInbox(req: Request, res: Response) {
|
||||
console.log(req.body);
|
||||
const activity = req.body as Activity;
|
||||
if (activity.type === "Follow") {
|
||||
|
@ -55,15 +56,22 @@ async function handleFollow(activity: Activity, req: Request, res: Response) {
|
|||
"actor": `https://${domain}/ap/actor`,
|
||||
"object": follow
|
||||
} as AcceptActivity;
|
||||
signAndSend(acceptObject, actor.inbox);
|
||||
try {
|
||||
await signAndSend(acceptObject, actor.inbox);
|
||||
// prefer shared server inbox
|
||||
const serverInbox = actor.endpoints && actor.endpoints.sharedInbox ? actor.endpoints.sharedInbox : actor.inbox;
|
||||
await getConnection().createQueryBuilder()
|
||||
.update(Actor)
|
||||
.set({ isFollower: true })
|
||||
.where("id = :id", { id: actor.id })
|
||||
.set({
|
||||
isFollower: true,
|
||||
inbox: serverInbox
|
||||
})
|
||||
.where("id = :id", {id: actor.id})
|
||||
.execute();
|
||||
res.end();
|
||||
} catch (err) {
|
||||
res.status(500).json({ message: "Encountered error handling follow.", error: err }).end();
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCreate(activity: Activity, req: Request, res: Response) {
|
||||
|
@ -91,6 +99,36 @@ function sanitizeStatus(content: string): string {
|
|||
async function handleCreateNote(create: CreateActivity, req: Request, res: Response) {
|
||||
const noteObject = create.object as NoteObject;
|
||||
const actor = await getActor(noteObject.attributedTo); // get and cache the actor if it's not already cached
|
||||
|
||||
const asPublic = "https://www.w3.org/ns/activitystreams#Public";
|
||||
const article = await getConnection().getRepository(Article).findOne({ where: { conversation: noteObject.conversation } });
|
||||
if (!article) {
|
||||
console.log(`Ignoring message not in response to an article: ${noteObject.id}`);
|
||||
res.end();
|
||||
} else if (!process.env.ACCEPT_NON_PUBLIC_NOTES && !noteObject.to.includes(asPublic) && !noteObject.cc.includes(asPublic)) {
|
||||
console.log(`Ignoring non-public post: ${noteObject.id}`);
|
||||
|
||||
try {
|
||||
const note: NoteObject = {
|
||||
type: "Note",
|
||||
id: `https://${domain}/ap/object/${uuidv4()}`,
|
||||
to: [actor.id],
|
||||
cc: [],
|
||||
directMessage: true,
|
||||
attributedTo: `https://${domain}/ap/actor`,
|
||||
content: `<a href="${actor.url}" class="mention">@<span>${actor.preferredUsername}</span></a> Non-public posts are not accepted. To respond to a blog post, use either Public or Unlisted.`,
|
||||
published: new Date().toISOString(),
|
||||
inReplyTo: noteObject.id,
|
||||
conversation: noteObject.conversation || `https://${domain}/ap/conversation/${uuidv4()}`
|
||||
};
|
||||
const responseCreate = createActivity(note);
|
||||
signAndSend(responseCreate, actor.inbox);
|
||||
} catch (err) {
|
||||
console.error(`Couldn't send non-public reply Note to ${noteObject.id}`, err);
|
||||
}
|
||||
|
||||
res.end();
|
||||
} else {
|
||||
const sanitizedContent = sanitizeStatus(noteObject.content);
|
||||
const note = new Note();
|
||||
note.id = noteObject.id;
|
||||
|
@ -102,10 +140,12 @@ async function handleCreateNote(create: CreateActivity, req: Request, res: Respo
|
|||
note.published = noteObject.published;
|
||||
try {
|
||||
await getConnection().getRepository(Note).save(note);
|
||||
res.end();
|
||||
} catch (err) {
|
||||
console.error(`Encountered error storing reply ${noteObject.id}`, err);
|
||||
res.status(500).end();
|
||||
}
|
||||
}
|
||||
res.end();
|
||||
}
|
||||
|
||||
async function handleDelete(activity: Activity, req: Request, res: Response) {
|
||||
|
@ -123,7 +163,7 @@ async function handleDelete(activity: Activity, req: Request, res: Response) {
|
|||
res.end();
|
||||
}
|
||||
|
||||
async function handleUndo(activity: Activity, req: Request, res: Response) {
|
||||
function handleUndo(activity: Activity, req: Request, res: Response) {
|
||||
const undo = activity as UndoActivity;
|
||||
if (undo.object.type === "Follow") {
|
||||
handleUndoFollow(undo, req, res);
|
||||
|
|
Loading…
Reference in New Issue