feat: ability to browse to any ActivityPub note and have the entire topic chain render

Added methods for going up the inReplyTo chain to parent, asserting the topic, etc.
This commit is contained in:
Julian Lam
2024-01-12 15:23:30 -05:00
parent d992239d7b
commit 485cf20006
5 changed files with 175 additions and 0 deletions

82
src/activitypub/notes.js Normal file
View File

@@ -0,0 +1,82 @@
'use strict';
const winston = require('winston');
const db = require('../database');
const posts = require('../posts');
const activitypub = module.parent.exports;
const Notes = module.exports;
// todo: when asserted, notes aren't added to a global sorted set
// also, db.exists call is probably expensive
Notes.assert = async (uid, input) => {
// Ensures that each note has been saved to the database
await Promise.all(input.map(async (item) => {
const id = activitypub.helpers.isUri(item) ? item : item.id;
const key = `post:${id}`;
const exists = await db.exists(key);
winston.verbose(`[activitypub/notes.assert] Asserting note id ${id}`);
let postData;
if (!exists) {
winston.verbose(`[activitypub/notes.assert] Not found, saving note to database`);
const object = activitypub.helpers.isUri(item) ? await activitypub.get(uid, item) : item;
postData = await activitypub.mocks.post(object);
if (postData) {
await db.setObject(key, postData);
}
}
}));
};
Notes.getParentChain = async (uid, input) => {
// Traverse upwards via `inReplyTo` until you find the root-level Note
const id = activitypub.helpers.isUri(input) ? input : input.id;
const chain = new Set();
const traverse = async (uid, id) => {
const exists = await db.exists(`post:${id}`);
if (exists) {
const { toPid, timestamp } = await posts.getPostFields(id, ['toPid', 'timestamp']);
chain.add({ id, timestamp });
if (toPid) {
await traverse(uid, toPid);
}
} else {
let object = await activitypub.get(uid, id);
object = await activitypub.mocks.post(object);
if (object) {
chain.add({ id, timestamp: object.timestamp });
if (object.hasOwnProperty('toPid') && object.toPid) {
await traverse(uid, object.toPid);
}
}
}
};
await traverse(uid, id);
return chain;
};
Notes.assertTopic = async (uid, id) => {
// Given the id of any post, traverses up (and soon, down) to cache the entire threaded context
const chain = Array.from(await Notes.getParentChain(uid, id));
const tid = chain[chain.length - 1].id;
const sorted = chain.sort((a, b) => a.timestamp - b.timestamp);
const [ids, timestamps] = [
sorted.map(n => n.id),
sorted.map(n => n.timestamp),
];
await db.sortedSetAdd(`topicRemote:${tid}`, timestamps, ids);
await Notes.assert(uid, chain);
return tid;
};
Notes.getTopicPosts = async (tid, uid, start, stop) => {
const pids = await db.getSortedSetRange(`topicRemote:${tid}`, start, stop);
return await posts.getPostsByPids(pids, uid);
};