diff --git a/src/activitypub/helpers.js b/src/activitypub/helpers.js index 9588353623..b54366fa4c 100644 --- a/src/activitypub/helpers.js +++ b/src/activitypub/helpers.js @@ -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 }; diff --git a/src/activitypub/inbox.js b/src/activitypub/inbox.js index 9b38242bc9..06a9d09111 100644 --- a/src/activitypub/inbox.js +++ b/src/activitypub/inbox.js @@ -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); diff --git a/src/activitypub/notes.js b/src/activitypub/notes.js index cf80588ad1..cb1468b1be 100644 --- a/src/activitypub/notes.js +++ b/src/activitypub/notes.js @@ -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 diff --git a/src/messaging/create.js b/src/messaging/create.js index 2d7063a0bc..e6b7713be1 100644 --- a/src/messaging/create.js +++ b/src/messaging/create.js @@ -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, diff --git a/src/messaging/data.js b/src/messaging/data.js index 20568cc3f7..9cbe5d2a01 100644 --- a/src/messaging/data.js +++ b/src/messaging/data.js @@ -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);