mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-11-01 03:26:04 +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 };
|
activityData = { activity, data, timestamp };
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://bb.devnull.land/cid/2#activity/follow/activitypub@community.nodebb.org│
|
|
||||||
switch (prefix) {
|
switch (prefix) {
|
||||||
case 'uid':
|
case 'uid':
|
||||||
return { type: 'user', id: value, ...activityData };
|
return { type: 'user', id: value, ...activityData };
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ ActivityPub.helpers = require('./helpers');
|
|||||||
ActivityPub.inbox = require('./inbox');
|
ActivityPub.inbox = require('./inbox');
|
||||||
ActivityPub.mocks = require('./mocks');
|
ActivityPub.mocks = require('./mocks');
|
||||||
ActivityPub.notes = require('./notes');
|
ActivityPub.notes = require('./notes');
|
||||||
|
ActivityPub.contexts = require('./contexts');
|
||||||
ActivityPub.actors = require('./actors');
|
ActivityPub.actors = require('./actors');
|
||||||
ActivityPub.instances = require('./instances');
|
ActivityPub.instances = require('./instances');
|
||||||
|
|
||||||
|
|||||||
@@ -43,13 +43,23 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => {
|
|||||||
return null;
|
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) {
|
if (!chain.length) {
|
||||||
unlock(id);
|
unlock(id);
|
||||||
return null;
|
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;
|
let { pid: mainPid, tid, uid: authorId, timestamp, name, content, _activitypub } = mainPost;
|
||||||
const hasTid = !!tid;
|
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)))
|
.filter(o => o.type === 'Hashtag' && !systemTags.includes(o.name.slice(1)))
|
||||||
.map(o => o.name.slice(1));
|
.map(o => o.name.slice(1));
|
||||||
|
|
||||||
if (maxTags && tags.length > maxTags) {
|
if (tags.length > maxTags) {
|
||||||
tags.length = maxTags;
|
tags.length = maxTags;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,10 +162,9 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => {
|
|||||||
Notes.updateLocalRecipients(mainPid, { to, cc }),
|
Notes.updateLocalRecipients(mainPid, { to, cc }),
|
||||||
posts.attachments.update(mainPid, attachment),
|
posts.attachments.update(mainPid, attachment),
|
||||||
]);
|
]);
|
||||||
unprocessed.pop();
|
unprocessed.shift();
|
||||||
}
|
}
|
||||||
|
|
||||||
unprocessed.reverse();
|
|
||||||
for (const post of unprocessed) {
|
for (const post of unprocessed) {
|
||||||
const { to, cc, attachment } = post._activitypub;
|
const { to, cc, attachment } = post._activitypub;
|
||||||
|
|
||||||
|
|||||||
@@ -101,7 +101,8 @@ Actors.topic = async function (req, res, next) {
|
|||||||
url: `${nconf.get('url')}/topic/${slug}`,
|
url: `${nconf.get('url')}/topic/${slug}`,
|
||||||
name,
|
name,
|
||||||
type: paginate && items ? 'OrderedCollectionPage' : 'OrderedCollection',
|
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,
|
totalItems: postcount,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -82,6 +82,10 @@ module.exports = function (Posts) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function checkToPid(toPid, uid) {
|
async function checkToPid(toPid, uid) {
|
||||||
|
if (!utils.isNumber(toPid)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const [toPost, canViewToPid] = await Promise.all([
|
const [toPost, canViewToPid] = await Promise.all([
|
||||||
Posts.getPostFields(toPid, ['pid', 'deleted']),
|
Posts.getPostFields(toPid, ['pid', 'deleted']),
|
||||||
privileges.posts.can('posts:view_deleted', toPid, uid),
|
privileges.posts.can('posts:view_deleted', toPid, uid),
|
||||||
|
|||||||
Reference in New Issue
Block a user