feat: handle incoming non-public activities as chat message, #12834

This commit is contained in:
Julian Lam
2024-10-08 14:26:55 -04:00
parent 8f7d049957
commit b82e20dfc7
5 changed files with 83 additions and 3 deletions

View File

@@ -154,6 +154,9 @@ Helpers.resolveLocalId = async (input) => {
const uid = await user.getUidByUserslug(value);
return { type: 'user', id: uid, ...activityData };
}
case 'message':
return { type: 'message', id: value, ...activityData };
}
return { type: null, id: null, ...activityData };

View File

@@ -62,9 +62,9 @@ async function announce(id, activity) {
inbox.create = async (req) => {
const { object } = req.body;
// Temporary, reject non-public notes.
// Alternative logic for non-public objects
if (![...object.to, ...object.cc].includes(activitypub._constants.publicAddress)) {
throw new Error('[[error:activitypub.not-implemented]]');
return await activitypub.notes.assertPrivate(object);
}
const asserted = await activitypub.notes.assert(0, object);

View File

@@ -8,6 +8,7 @@ const batch = require('../batch');
const meta = require('../meta');
const privileges = require('../privileges');
const categories = require('../categories');
const messaging = require('../messaging');
const user = require('../user');
const topics = require('../topics');
const posts = require('../posts');
@@ -190,6 +191,79 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => {
return { tid, count };
};
Notes.assertPrivate = async (object) => {
// Given an object, adds it to an existing chat or creates a new chat otherwise
// todo: context stuff
const recipients = new Set([...object.to, ...object.cc]);
// Remove follower urls
const isFollowerUrl = await db.isObjectFields('followersUrl:uid', Array.from(recipients));
Array.from(recipients).forEach((id, idx) => {
if (isFollowerUrl[idx]) {
recipients.delete(id);
}
});
const localUids = [];
const recipientsResolved = new Set([...recipients]);
await Promise.all(Array.from(recipients).map(async (value) => {
const { type, id } = await activitypub.helpers.resolveLocalId(value);
if (type === 'user') {
localUids.push(id);
recipientsResolved.delete(value);
recipientsResolved.add(parseInt(id, 10));
}
}));
// Locate the roomId based on `inReplyTo`
let roomId;
const resolved = await activitypub.helpers.resolveLocalId(object.inReplyTo);
let toMid = resolved.type === 'message' && resolved.id;
if (object.inReplyTo && await messaging.messageExists(toMid || object.inReplyTo)) {
roomId = await messaging.getMessageField(toMid || object.inReplyTo, 'roomId');
}
// Compare room members with object recipients; if someone in-room is omitted, start new chat
if (roomId) {
const participants = await messaging.getUsersInRoom(roomId, 0, -1);
const omitted = participants.filter((user) => {
let { uid } = user;
uid = utils.isNumber(uid) ? `${nconf.get('url')}/uid/${uid}` : uid;
return !recipients.has(uid) && uid !== object.attributedTo;
});
if (omitted.length) {
toMid = undefined; // message creation logic fails if toMid is not in room
roomId = null;
}
}
let timestamp;
try {
timestamp = new Date(object.published).getTime() || Date.now();
} catch (e) {
timestamp = Date.now();
}
if (!roomId) {
roomId = await messaging.newRoom(object.attributedTo, { uids: [...recipientsResolved] });
timestamp = Date.now(); // otherwise message can't be seen in room as it pre-dates participants joining
}
// Add message to room
await messaging.sendMessage({
mid: object.id,
uid: object.attributedTo,
roomId: roomId,
content: object.content,
toMid: toMid,
timestamp,
// ip: caller.ip,
});
return { roomId };
};
async function assertRelation(post) {
/**
* Given a mocked post object, ensures that it is related to some other object in database

View File

@@ -50,7 +50,7 @@ module.exports = function (Messaging) {
throw new Error('[[error:no-privileges]]');
}
}
const mid = await db.incrObjectField('global', 'nextMid');
const mid = data.mid || await db.incrObjectField('global', 'nextMid');
const timestamp = data.timestamp || Date.now();
let message = {
mid: mid,

View File

@@ -5,6 +5,7 @@ const validator = require('validator');
const db = require('../database');
const user = require('../user');
const posts = require('../posts');
const utils = require('../utils');
const plugins = require('../plugins');
@@ -185,6 +186,8 @@ module.exports = function (Messaging) {
async function parseMessage(message, uid, roomId, isNew) {
if (message.system) {
return validator.escape(String(message.content));
} else if (!utils.isNumber(message.mid)) {
return posts.sanitize(message.content);
}
return await Messaging.parse(message.content, message.fromuid, uid, roomId, isNew);