feat: federating out chat messages

re #12834
This commit is contained in:
Julian Lam
2024-10-07 14:09:34 -04:00
parent 8ef0df57e6
commit 2ab5ea39a6
6 changed files with 104 additions and 12 deletions

View File

@@ -264,7 +264,7 @@ Helpers.resolveObjects = async (ids) => {
if (!post) {
throw new Error('[[error:activitypub.invalid-id]]');
}
return activitypub.mocks.note(post);
return activitypub.mocks.notes.public(post);
}
case 'category': {
if (!await categories.exists(resolvedId)) {

View File

@@ -10,6 +10,7 @@ const user = require('../user');
const categories = require('../categories');
const posts = require('../posts');
const topics = require('../topics');
const messaging = require('../messaging');
const plugins = require('../plugins');
const slugify = require('../slugify');
const translator = require('../translator');
@@ -271,7 +272,9 @@ Mocks.actors.category = async (cid) => {
};
};
Mocks.note = async (post) => {
Mocks.notes = {};
Mocks.notes.public = async (post) => {
const id = `${nconf.get('url')}/post/${post.pid}`;
// Return a tombstone for a deleted post
@@ -434,7 +437,6 @@ Mocks.note = async (post) => {
attributedTo: `${nconf.get('url')}/uid/${post.user.uid}`,
context: `${nconf.get('url')}/topic/${post.topic.tid}`,
audience: `${nconf.get('url')}/category/${post.category.cid}`,
sensitive: false, // todo
summary: null,
name,
content: post.content,
@@ -447,6 +449,66 @@ Mocks.note = async (post) => {
return object;
};
Mocks.notes.private = async ({ messageObj }) => {
// todo: deleted messages
let uids = await messaging.getUidsInRoom(messageObj.roomId, 0, -1);
const remoteUids = uids.filter(uid => !utils.isNumber(uid));
uids = uids.map(uid => (utils.isNumber(uid) ? `${nconf.get('url')}/uid/${uid}` : uid));
const id = `${nconf.get('url')}/message/${messageObj.mid}`;
const to = new Set(uids);
const published = messageObj.timestampISO;
const updated = messageObj.edited ? messageObj.editedISO : undefined;
const content = await messaging.parse(messageObj.content, messageObj.fromuid, 0, messageObj.roomId, false);
let source;
const markdownEnabled = await plugins.isActive('nodebb-plugin-markdown');
if (markdownEnabled) {
source = {
content: messageObj.content,
mediaType: 'text/markdown',
};
}
const mentions = await user.getUsersFields(remoteUids, ['uid', 'userslug']);
const tag = [];
tag.push(...mentions.map(({ uid, userslug }) => ({
type: 'Mention',
href: uid,
name: userslug,
})));
let inReplyTo;
if (messageObj.toMid) {
inReplyTo = utils.isNumber(messageObj.toMid) ?
`${nconf.get('url')}/api/v3/chats/${messageObj.roomId}/messages/${messageObj.toMid}` :
messageObj.toMid;
}
const object = {
'@context': 'https://www.w3.org/ns/activitystreams',
id,
type: 'Note',
to: Array.from(to),
cc: [],
inReplyTo,
published,
updated,
url: id,
attributedTo: `${nconf.get('url')}/uid/${messageObj.fromuid}`,
// context: `${nconf.get('url')}/topic/${post.topic.tid}`,
// audience: `${nconf.get('url')}/category/${post.category.cid}`,
summary: null,
// name,
content: content,
source,
tag,
// attachment: [], // todo
// replies: `${id}/replies`, // todo
};
return object;
};
Mocks.tombstone = async properties => ({
'@context': 'https://www.w3.org/ns/activitystreams',
type: 'Tombstone',

View File

@@ -18,6 +18,7 @@ const privileges = require('../privileges');
const activitypub = require('../activitypub');
const posts = require('../posts');
const topics = require('../topics');
const messaging = require('../messaging');
const utils = require('../utils');
const activitypubApi = module.exports;
@@ -180,7 +181,7 @@ activitypubApi.create.note = enabledCheck(async (caller, { pid, post }) => {
return;
}
const object = await activitypub.mocks.note(post);
const object = await activitypub.mocks.notes.public(post);
const { to, cc, targets } = await buildRecipients(object, { pid, uid: post.user.uid });
const { cid } = post.category;
const followers = await activitypub.notes.getCategoryFollowers(cid);
@@ -211,6 +212,21 @@ activitypubApi.create.note = enabledCheck(async (caller, { pid, post }) => {
}
});
activitypubApi.create.privateNote = enabledCheck(async (caller, { mid, messageObj }) => {
if (!messageObj) {
messageObj = await messaging.getMessageFields(mid, []);
if (!messageObj) {
throw new Error('[[error:invalid-data]]');
}
}
const { roomId } = messageObj;
let targets = await messaging.getUidsInRoom(roomId, 0, -1);
targets = targets.filter(uid => !utils.isNumber(uid)); // remote uids only
const payload = await activitypub.mocks.notes.private({ messageObj });
await activitypub.send('uid', messageObj.fromuid, targets, payload);
});
activitypubApi.update = {};
activitypubApi.update.profile = enabledCheck(async (caller, { uid }) => {
@@ -249,7 +265,7 @@ activitypubApi.update.note = enabledCheck(async (caller, { post }) => {
return;
}
const object = await activitypub.mocks.note(post);
const object = await activitypub.mocks.notes.public(post);
const { to, cc, targets } = await buildRecipients(object, { pid: post.pid, uid: post.user.uid });
const allowed = await privileges.posts.can('topics:read', post.pid, activitypub._constants.uid);
@@ -281,7 +297,7 @@ activitypubApi.delete.note = enabledCheck(async (caller, { pid }) => {
const id = `${nconf.get('url')}/post/${pid}`;
const post = (await posts.getPostSummaryByPids([pid], caller.uid, { stripTags: false })).pop();
const object = await activitypub.mocks.note(post);
const object = await activitypub.mocks.notes.public(post);
const { to, cc, targets } = await buildRecipients(object, { pid, uid: post.user.uid });
const allowed = await privileges.posts.can('topics:read', pid, activitypub._constants.uid);

View File

@@ -9,6 +9,7 @@ const privileges = require('../../privileges');
const posts = require('../../posts');
const topics = require('../../topics');
const categories = require('../../categories');
const messaging = require('../../messaging');
const activitypub = require('../../activitypub');
const utils = require('../../utils');
@@ -75,7 +76,7 @@ Actors.note = async function (req, res) {
return res.sendStatus(404);
}
const payload = await activitypub.mocks.note(post);
const payload = await activitypub.mocks.notes.public(post);
res.status(200).json(payload);
};
@@ -184,3 +185,14 @@ Actors.category = async function (req, res, next) {
const payload = await activitypub.mocks.actors.category(req.params.cid);
res.status(200).json(payload);
};
Actors.message = async function (req, res, next) {
// Handle requests for remote content
if (!utils.isNumber(req.params.mid)) {
return res.set('Location', req.params.mid).sendStatus(308);
}
const messageObj = await messaging.getMessageFields(req.params.mid, []);
const payload = await activitypub.mocks.notes.private({ messageObj });
res.status(200).json(payload);
};

View File

@@ -8,6 +8,7 @@ const db = require('../database');
const notifications = require('../notifications');
const user = require('../user');
const io = require('../socket.io');
const api = require('../api');
const plugins = require('../plugins');
const utils = require('../utils');
@@ -79,8 +80,10 @@ module.exports = function (Messaging) {
}
try {
await sendNotification(fromUid, roomId, messageObj);
// await federate(fromUid, roomId, messageObj);
await Promise.all([
sendNotification(fromUid, roomId, messageObj),
!isPublic ? api.activitypub.create.privateNote({ uid: fromUid }, { messageObj }) : null,
]);
} catch (err) {
winston.error(`[messaging/notifications] Unabled to send notification\n${err.stack}`);
}
@@ -142,7 +145,4 @@ module.exports = function (Messaging) {
await notifications.push(notification, uidsToNotify);
}
}
// async function federate(fromUid, roomId, messageObj) {
// }
};

View File

@@ -45,4 +45,6 @@ module.exports = function (app, middleware, controllers) {
app.post('/category/:cid/inbox', [...inboxMiddlewares, middleware.assert.category, ...inboxMiddlewares], controllers.activitypub.postInbox);
app.get('/category/:cid/outbox', [...middlewares, middleware.assert.category], controllers.activitypub.getCategoryOutbox);
app.post('/category/:cid/outbox', [...middlewares, middleware.assert.category], controllers.activitypub.postOutbox);
app.get('/message/:mid', [...middlewares, middleware.assert.message], controllers.activitypub.actors.message);
};