mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-10-30 02:25:55 +01:00
feat: topic backfill logic via resolvable context #12647
This commit is contained in:
77
src/activitypub/contexts.js
Normal file
77
src/activitypub/contexts.js
Normal file
@@ -0,0 +1,77 @@
|
||||
'use strict';
|
||||
|
||||
const winston = require('winston');
|
||||
|
||||
const posts = require('../posts');
|
||||
|
||||
const activitypub = module.parent.exports;
|
||||
const Contexts = module.exports;
|
||||
|
||||
const acceptableTypes = ['Collection', 'CollectionPage', 'OrderedCollection', 'OrderedCollectionPage'];
|
||||
|
||||
Contexts.get = async (uid, id) => {
|
||||
let context;
|
||||
let type;
|
||||
|
||||
try {
|
||||
({ context } = await activitypub.get('uid', uid, id));
|
||||
({ type } = await activitypub.get('uid', uid, context));
|
||||
} catch (e) {
|
||||
winston.verbose(`[activitypub/context] ${id} context not resolvable.`);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (acceptableTypes.includes(type)) {
|
||||
return context;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
Contexts.getItems = async (uid, id, root = true) => {
|
||||
winston.verbose(`[activitypub/context] Retrieving context ${id}`);
|
||||
let { type, items, first, next } = await activitypub.get('uid', uid, id);
|
||||
if (!acceptableTypes.includes(type)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (items) {
|
||||
items = (await Promise.all(items.map(async (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 await activitypub.mocks.post(object);
|
||||
} catch (e) {
|
||||
// Unresolvable, either temporariliy or permanent, ignore for now.
|
||||
winston.verbose(`[activitypub/context] Cannot retrieve ${id}`);
|
||||
return null;
|
||||
}
|
||||
}))).filter(Boolean);
|
||||
winston.verbose(`[activitypub/context] Found ${items.length} items.`);
|
||||
}
|
||||
|
||||
const chain = new Set(items || []);
|
||||
if (!next && root && first) {
|
||||
next = first;
|
||||
}
|
||||
|
||||
if (next) {
|
||||
winston.verbose('[activitypub/context] Fetching next page...');
|
||||
Array
|
||||
.from(await Contexts.getItems(uid, next, false))
|
||||
.forEach((item) => {
|
||||
chain.add(item);
|
||||
});
|
||||
}
|
||||
|
||||
return chain;
|
||||
};
|
||||
@@ -134,7 +134,6 @@ Helpers.resolveLocalId = async (input) => {
|
||||
activityData = { activity, data, timestamp };
|
||||
}
|
||||
|
||||
// https://bb.devnull.land/cid/2#activity/follow/activitypub@community.nodebb.org│
|
||||
switch (prefix) {
|
||||
case 'uid':
|
||||
return { type: 'user', id: value, ...activityData };
|
||||
|
||||
@@ -40,6 +40,7 @@ ActivityPub.helpers = require('./helpers');
|
||||
ActivityPub.inbox = require('./inbox');
|
||||
ActivityPub.mocks = require('./mocks');
|
||||
ActivityPub.notes = require('./notes');
|
||||
ActivityPub.contexts = require('./contexts');
|
||||
ActivityPub.actors = require('./actors');
|
||||
ActivityPub.instances = require('./instances');
|
||||
|
||||
|
||||
@@ -43,13 +43,23 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const chain = Array.from(await Notes.getParentChain(uid, input));
|
||||
let chain;
|
||||
const context = await activitypub.contexts.get(uid, id);
|
||||
if (context) {
|
||||
chain = Array.from(await activitypub.contexts.getItems(uid, context));
|
||||
} else {
|
||||
// Fall back to inReplyTo traversal
|
||||
chain = Array.from(await Notes.getParentChain(uid, input));
|
||||
}
|
||||
if (!chain.length) {
|
||||
unlock(id);
|
||||
return null;
|
||||
}
|
||||
|
||||
const mainPost = chain[chain.length - 1];
|
||||
// Reorder chain items by timestamp
|
||||
chain = chain.sort((a, b) => a.timestamp - b.timestamp);
|
||||
|
||||
const mainPost = chain[0];
|
||||
let { pid: mainPid, tid, uid: authorId, timestamp, name, content, _activitypub } = mainPost;
|
||||
const hasTid = !!tid;
|
||||
|
||||
@@ -133,7 +143,7 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => {
|
||||
.filter(o => o.type === 'Hashtag' && !systemTags.includes(o.name.slice(1)))
|
||||
.map(o => o.name.slice(1));
|
||||
|
||||
if (maxTags && tags.length > maxTags) {
|
||||
if (tags.length > maxTags) {
|
||||
tags.length = maxTags;
|
||||
}
|
||||
|
||||
@@ -152,10 +162,9 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => {
|
||||
Notes.updateLocalRecipients(mainPid, { to, cc }),
|
||||
posts.attachments.update(mainPid, attachment),
|
||||
]);
|
||||
unprocessed.pop();
|
||||
unprocessed.shift();
|
||||
}
|
||||
|
||||
unprocessed.reverse();
|
||||
for (const post of unprocessed) {
|
||||
const { to, cc, attachment } = post._activitypub;
|
||||
|
||||
|
||||
@@ -101,7 +101,8 @@ Actors.topic = async function (req, res, next) {
|
||||
url: `${nconf.get('url')}/topic/${slug}`,
|
||||
name,
|
||||
type: paginate && items ? 'OrderedCollectionPage' : 'OrderedCollection',
|
||||
audience: `${nconf.get('url')}/category/${cid}`,
|
||||
attributedTo: `${nconf.get('url')}/category/${cid}`,
|
||||
audience: cid !== -1 ? `${nconf.get('url')}/category/${cid}/followers` : undefined,
|
||||
totalItems: postcount,
|
||||
};
|
||||
|
||||
|
||||
@@ -82,6 +82,10 @@ module.exports = function (Posts) {
|
||||
}
|
||||
|
||||
async function checkToPid(toPid, uid) {
|
||||
if (!utils.isNumber(toPid)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const [toPost, canViewToPid] = await Promise.all([
|
||||
Posts.getPostFields(toPid, ['pid', 'deleted']),
|
||||
privileges.posts.can('posts:view_deleted', toPid, uid),
|
||||
|
||||
Reference in New Issue
Block a user