feat: ability to add/remove auto-categorization rules for incoming federated content

This commit is contained in:
Julian Lam
2025-08-17 22:07:30 -04:00
parent cb0b609289
commit bdcf28a3d9
6 changed files with 74 additions and 5 deletions

View File

@@ -18,6 +18,16 @@
"probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout": "Lookup Timeout (milliseconds)",
"probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.",
"rules": "Categorization",
"rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)",
"rules.modal.title": "How it works",
"rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.<br /><br /><strong>N.B.</strong> Content that is already categorized (i.e. in a remote category) will not pass through these rules.",
"rules.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. <code>one,two,three</code>)",
"rules.add": "Add New Rule",
"rules.type": "Type",
"rules.value": "Value",
"rules.cid": "Category",
"server-filtering": "Filtering", "server-filtering": "Filtering",
"count": "This NodeBB is currently aware of <strong>%1</strong> server(s)", "count": "This NodeBB is currently aware of <strong>%1</strong> server(s)",
"server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively <em>allow</em> federation with specific servers, instead. Both options are supported, although they are mutually exclusive.", "server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively <em>allow</em> federation with specific servers, instead. Both options are supported, although they are mutually exclusive.",

View File

@@ -64,6 +64,7 @@ ActivityPub.contexts = require('./contexts');
ActivityPub.actors = require('./actors'); ActivityPub.actors = require('./actors');
ActivityPub.instances = require('./instances'); ActivityPub.instances = require('./instances');
ActivityPub.feps = require('./feps'); ActivityPub.feps = require('./feps');
ActivityPub.rules = require('./rules');
ActivityPub.startJobs = () => { ActivityPub.startJobs = () => {
ActivityPub.helpers.log('[activitypub/jobs] Registering jobs.'); ActivityPub.helpers.log('[activitypub/jobs] Registering jobs.');

View File

@@ -159,11 +159,15 @@ settingsController.api = async (req, res) => {
}; };
settingsController.activitypub = async (req, res) => { settingsController.activitypub = async (req, res) => {
const instanceCount = await activitypub.instances.getCount(); const [instanceCount, rules] = await Promise.all([
activitypub.instances.getCount(),
activitypub.rules.list(),
]);
res.render('admin/settings/activitypub', { res.render('admin/settings/activitypub', {
title: `[[admin/menu:settings/activitypub]]`, title: `[[admin/menu:settings/activitypub]]`,
instanceCount, instanceCount,
rules,
}); });
}; };
@@ -186,7 +190,3 @@ settingsController.advanced = async (req, res) => {
groupsExemptFromMaintenanceMode: groupData, groupsExemptFromMaintenanceMode: groupData,
}); });
}; };

View File

@@ -1,9 +1,11 @@
'use strict'; 'use strict';
const categories = require('../../categories');
const api = require('../../api'); const api = require('../../api');
const helpers = require('../helpers'); const helpers = require('../helpers');
const messaging = require('../../messaging'); const messaging = require('../../messaging');
const events = require('../../events'); const events = require('../../events');
const activitypub = require('../../activitypub');
const Admin = module.exports; const Admin = module.exports;
@@ -82,3 +84,22 @@ Admin.chats.deleteRoom = async (req, res) => {
Admin.listGroups = async (req, res) => { Admin.listGroups = async (req, res) => {
helpers.formatApiResponse(200, res, await api.admin.listGroups()); helpers.formatApiResponse(200, res, await api.admin.listGroups());
}; };
Admin.activitypub = {};
Admin.activitypub.addRule = async (req, res) => {
const { type, value, cid } = req.body;
const exists = await categories.exists(cid);
if (!value || !exists) {
helpers.formatApiResponse(400, res);
}
await activitypub.rules.add(type, value, cid);
helpers.formatApiResponse(200, res, await activitypub.rules.list());
};
Admin.activitypub.deleteRule = async (req, res) => {
const { rid } = req.params;
await activitypub.rules.delete(rid);
helpers.formatApiResponse(200, res, await activitypub.rules.list());
};

View File

@@ -25,5 +25,8 @@ module.exports = function () {
setupApiRoute(router, 'get', '/groups', [...middlewares], controllers.write.admin.listGroups); setupApiRoute(router, 'get', '/groups', [...middlewares], controllers.write.admin.listGroups);
setupApiRoute(router, 'post', '/activitypub/rules', [...middlewares, middleware.checkRequired.bind(null, ['cid', 'value', 'type'])], controllers.write.admin.activitypub.addRule);
setupApiRoute(router, 'delete', '/activitypub/rules/:rid', [...middlewares], controllers.write.admin.activitypub.deleteRule);
return router; return router;
}; };

View File

@@ -44,6 +44,40 @@
</div> </div>
</div> </div>
<div class="row settings m-0">
<div class="col-sm-2 col-12 settings-header">[[admin/settings/activitypub:rules]]</div>
<div class="col-sm-10 col-12">
<div class="mb-3">
<p>[[admin/settings/activitypub:rules-intro]]</p>
<table class="table table-striped" id="rules">
<thead>
<th>[[admin/settings/activitypub:rules.type]]</th>
<th>[[admin/settings/activitypub:rules.value]]</th>
<th>[[admin/settings/activitypub:rules.cid]]</th>
<th></th>
</thead>
<tbody>
{{{ each rules }}}
<tr data-rid="{./rid}">
<td>{./type}</td>
<td>{./value}</td>
<td>{./cid}</td>
<td><a href="#" data-action="rules.delete"><i class="fa fa-trash link-danger"></i></a></td>
</tr>
{{{ end }}}
</tbody>
<tfoot>
<tr>
<td colspan="3">
<button class="btn btn-sm btn-primary" data-action="rules.add">[[admin/settings/activitypub:rules.add]]</button>
</td>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
<div class="row settings m-0"> <div class="row settings m-0">
<div class="col-sm-2 col-12 settings-header">[[admin/settings/activitypub:pruning]]</div> <div class="col-sm-2 col-12 settings-header">[[admin/settings/activitypub:pruning]]</div>
<div class="col-sm-10 col-12"> <div class="col-sm-10 col-12">