mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-12-29 03:40:27 +01:00
151 lines
4.0 KiB
JavaScript
151 lines
4.0 KiB
JavaScript
'use strict';
|
|
|
|
const winston = require('winston');
|
|
|
|
const db = require('../database');
|
|
const posts = require('../posts');
|
|
const topics = require('../topics');
|
|
|
|
const activitypub = module.parent.exports;
|
|
const Contexts = module.exports;
|
|
|
|
const acceptableTypes = ['Collection', 'CollectionPage', 'OrderedCollection', 'OrderedCollectionPage'];
|
|
|
|
Contexts.get = async (uid, id) => {
|
|
let context;
|
|
let type;
|
|
|
|
// Generate digest for If-None-Match if locally cached
|
|
const tid = await posts.getPostField(id, 'tid');
|
|
const headers = {};
|
|
if (tid) {
|
|
const [mainPid, pids] = await Promise.all([
|
|
topics.getTopicField(tid, 'mainPid'),
|
|
db.getSortedSetMembers(`tid:${tid}:posts`),
|
|
]);
|
|
pids.push(mainPid);
|
|
const digest = activitypub.helpers.generateDigest(new Set(pids));
|
|
headers['If-None-Match'] = `"${digest}"`;
|
|
}
|
|
|
|
try {
|
|
({ context } = await activitypub.get('uid', uid, id, { headers }));
|
|
if (!context) {
|
|
winston.verbose(`[activitypub/context] ${id} contains no context.`);
|
|
return false;
|
|
}
|
|
({ type } = await activitypub.get('uid', uid, context));
|
|
} catch (e) {
|
|
if (e.code === 'ap_get_304') {
|
|
winston.verbose(`[activitypub/context] ${id} context unchanged.`);
|
|
return { tid };
|
|
}
|
|
|
|
winston.verbose(`[activitypub/context] ${id} context not resolvable.`);
|
|
return false;
|
|
}
|
|
|
|
if (acceptableTypes.includes(type)) {
|
|
return { context };
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
Contexts.getItems = async (uid, id, options) => {
|
|
if (!options.hasOwnProperty('root')) {
|
|
options.root = true;
|
|
}
|
|
|
|
winston.verbose(`[activitypub/context] Retrieving context ${id}`);
|
|
let { type, items, orderedItems, first, next } = await activitypub.get('uid', uid, id);
|
|
if (!acceptableTypes.includes(type)) {
|
|
return [];
|
|
}
|
|
|
|
if (type.startsWith('Ordered') && orderedItems) {
|
|
items = orderedItems;
|
|
}
|
|
|
|
if (items) {
|
|
items = await Promise.all(items
|
|
.map(async item => (activitypub.helpers.isUri(item) ? parseString(uid, item) : parseItem(uid, item))));
|
|
items = items.filter(Boolean);
|
|
winston.verbose(`[activitypub/context] Found ${items.length} items.`);
|
|
}
|
|
|
|
const chain = new Set(items || []);
|
|
if (!next && options.root && first) {
|
|
next = first;
|
|
}
|
|
|
|
if (next) {
|
|
winston.verbose('[activitypub/context] Fetching next page...');
|
|
Array
|
|
.from(await Contexts.getItems(uid, next, {
|
|
...options,
|
|
root: false,
|
|
}))
|
|
.forEach((item) => {
|
|
chain.add(item);
|
|
});
|
|
}
|
|
|
|
// Handle special case where originating object is not actually part of the context collection
|
|
const inputId = activitypub.helpers.isUri(options.input) ? options.input : options.input.id;
|
|
const inCollection = Array.from(chain).map(p => p.pid).includes(inputId);
|
|
if (!inCollection) {
|
|
chain.add(activitypub.helpers.isUri(options.input) ?
|
|
await parseString(uid, options.input) :
|
|
await parseItem(uid, options.input));
|
|
}
|
|
|
|
return chain;
|
|
};
|
|
|
|
async function parseString(uid, item) {
|
|
const { type, id } = await activitypub.helpers.resolveLocalId(item);
|
|
const pid = type === 'post' && id ? id : item;
|
|
const postData = await posts.getPostData(pid);
|
|
if (postData) {
|
|
// Already cached
|
|
return postData;
|
|
}
|
|
|
|
// No local copy, fetch from source
|
|
try {
|
|
const object = await activitypub.get('uid', uid, pid);
|
|
winston.verbose(`[activitypub/context] Retrieved ${pid}`);
|
|
|
|
return parseItem(uid, object);
|
|
} catch (e) {
|
|
// Unresolvable, either temporarily or permanent, ignore for now.
|
|
winston.verbose(`[activitypub/context] Cannot retrieve ${pid}`);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async function parseItem(uid, item) {
|
|
const { type, id } = await activitypub.helpers.resolveLocalId(item.id);
|
|
const pid = type === 'post' && id ? id : item.id;
|
|
const postData = await posts.getPostData(pid);
|
|
if (postData) {
|
|
// Already cached
|
|
return postData;
|
|
}
|
|
|
|
// Handle activity wrapper
|
|
if (item.type === 'Create') {
|
|
item = item.object;
|
|
if (activitypub.helpers.isUri(item)) {
|
|
return parseString(uid, item);
|
|
}
|
|
} else if (!activitypub._constants.acceptedPostTypes.includes(item.type)) {
|
|
// Not a note, silently skip.
|
|
return null;
|
|
}
|
|
|
|
winston.verbose(`[activitypub/context] Parsing ${pid}`);
|
|
return await activitypub.mocks.post(item);
|
|
}
|