diff --git a/src/activitypub/notes.js b/src/activitypub/notes.js index 7264d5855d..95ece9b085 100644 --- a/src/activitypub/notes.js +++ b/src/activitypub/notes.js @@ -73,14 +73,17 @@ Notes.getParentChain = async (uid, input) => { return chain; }; -Notes.assertParentChain = async (chain) => { +Notes.assertParentChain = async (chain, tid) => { const data = []; chain.reduce((child, parent) => { data.push([`pid:${parent.pid}:replies`, child.timestamp, child.pid]); return parent; }); - await db.sortedSetAddBulk(data); + await Promise.all([ + db.sortedSetAddBulk(data), + db.setObjectBulk(chain.map(post => [`post:${post.pid}`, { tid }])), + ]); }; Notes.assertTopic = async (uid, id) => { @@ -92,22 +95,22 @@ Notes.assertTopic = async (uid, id) => { */ const chain = Array.from(await Notes.getParentChain(uid, id)); - const { pid: tid, uid: authorId, timestamp, name, content } = chain[chain.length - 1]; - + let { pid: mainPid, tid, uid: authorId, timestamp, name, content } = chain[chain.length - 1]; const members = await db.isSortedSetMembers(`tidRemote:${tid}:posts`, chain.map(p => p.pid)); - if (members.every(Boolean)) { + if (tid && members.every(Boolean)) { // All cached, return early. winston.info('[notes/assertTopic] No new notes to process.'); return tid; } - let title = name || utils.stripHTMLTags(content); - if (title.length > 256) { - title = `${title.slice(0, 256)}...`; - } - + tid = tid || utils.generateUUID(); const cid = await topics.getTopicField(tid, 'cid'); + let title = name || utils.decodeHTMLEntities(utils.stripHTMLTags(content)); + if (title.length > 64) { + title = `${title.slice(0, 64)}...`; + } + const unprocessed = chain.filter((p, idx) => !members[idx]); winston.info(`[notes/assertTopic] ${unprocessed.length} new note(s) found.`); @@ -121,16 +124,16 @@ Notes.assertTopic = async (uid, id) => { tid, uid: authorId, cid: cid || -1, - mainPid: tid, + mainPid, title, - slug: `remote?resource=${encodeURIComponent(tid)}`, + slug: `../world/${tid}`, timestamp, }), db.sortedSetAdd(`tidRemote:${tid}:posts`, timestamps, ids), Notes.assert(uid, unprocessed), ]); await Promise.all([ // must be done after .assert() - Notes.assertParentChain(chain), + Notes.assertParentChain(chain, tid), Notes.updateTopicCounts(tid), topics.updateLastPostTimeFromLastPid(tid), topics.updateTeaser(tid), diff --git a/src/controllers/activitypub/topics.js b/src/controllers/activitypub/topics.js index 5694625c1a..eaa51cd2f9 100644 --- a/src/controllers/activitypub/topics.js +++ b/src/controllers/activitypub/topics.js @@ -44,14 +44,13 @@ controller.get = async function (req, res, next) { * Ideally we would use the existing topicsController.get... * this controller may be a stopgap towards that end goal. */ - const pid = await notes.resolveId(req.uid, req.query.resource); - if (pid !== req.query.resource) { - return helpers.redirect(res, `/topic/remote?resource=${pid}`, true); - } + // const pid = await notes.resolveId(req.uid, req.query.resource); + // if (pid !== req.query.resource) { + // return helpers.redirect(res, `/topic/remote?resource=${pid}`, true); + // } - const tid = await notes.assertTopic(req.uid, req.query.resource); - - let postIndex = Math.max(1, await db.sortedSetRank(`tidRemote:${tid}:posts`, req.query.resource)); + const tid = req.params.topic_id; + let postIndex = parseInt(req.params.post_index, 10) || 1; const [ userPrivileges, settings, diff --git a/src/posts/data.js b/src/posts/data.js index c73d2f6f0d..61738181de 100644 --- a/src/posts/data.js +++ b/src/posts/data.js @@ -61,6 +61,7 @@ function modifyPost(post, fields) { if (activitypub.helpers.isUri(post.pid)) { intFields.splice(intFields.indexOf('pid'), 1); intFields.splice(intFields.indexOf('uid'), 1); + intFields.splice(intFields.indexOf('tid'), 1); } db.parseIntFields(post, intFields, fields); if (post.hasOwnProperty('upvotes') && post.hasOwnProperty('downvotes')) { diff --git a/src/routes/activitypub.js b/src/routes/activitypub.js index 63efeafc13..8bd686308f 100644 --- a/src/routes/activitypub.js +++ b/src/routes/activitypub.js @@ -3,7 +3,8 @@ const helpers = require('./helpers'); module.exports = function (app, middleware, controllers) { - helpers.setupPageRoute(app, '/world/:view?', [middleware.activitypub.enabled], controllers.activitypub.topics.list); + helpers.setupPageRoute(app, '/world', [middleware.activitypub.enabled], controllers.activitypub.topics.list); + helpers.setupPageRoute(app, '/world/:topic_id/:post_index?', [middleware.activitypub.enabled], controllers.activitypub.topics.get); /** * These controllers only respond if the sender is making an json+activitypub style call (i.e. S2S-only) diff --git a/src/socket.io/topics/infinitescroll.js b/src/socket.io/topics/infinitescroll.js index 7890500eb9..3b478154ee 100644 --- a/src/socket.io/topics/infinitescroll.js +++ b/src/socket.io/topics/infinitescroll.js @@ -1,11 +1,12 @@ 'use strict'; +const validator = require('validator'); + const topics = require('../../topics'); const privileges = require('../../privileges'); const meta = require('../../meta'); const utils = require('../../utils'); const social = require('../../social'); -const activitypub = require('../../activitypub'); module.exports = function (SocketTopics) { SocketTopics.loadMore = async function (socket, data) { @@ -22,7 +23,7 @@ module.exports = function (SocketTopics) { throw new Error('[[error:no-privileges]]'); } - const setPrefix = activitypub.helpers.isUri(data.tid) ? 'tidRemote' : 'tid'; + const setPrefix = validator.isUUID(String(data.tid)) ? 'tidRemote' : 'tid'; const set = data.topicPostSort === 'most_votes' ? `${setPrefix}:${data.tid}:posts:votes` : `${setPrefix}:${data.tid}:posts`; const reverse = data.topicPostSort === 'newest_to_oldest' || data.topicPostSort === 'most_votes'; let start = Math.max(0, parseInt(data.after, 10)); diff --git a/src/topics/data.js b/src/topics/data.js index 859de72395..18e68dcb26 100644 --- a/src/topics/data.js +++ b/src/topics/data.js @@ -7,7 +7,6 @@ const categories = require('../categories'); const utils = require('../utils'); const translator = require('../translator'); const plugins = require('../plugins'); -const activitypub = require('../activitypub'); const intFields = [ 'tid', 'cid', 'uid', 'mainPid', 'postcount', @@ -27,7 +26,7 @@ module.exports = function (Topics) { fields.push('timestamp'); } - const keys = tids.map(tid => `${activitypub.helpers.isUri(tid) ? 'topicRemote' : 'topic'}:${tid}`); + const keys = tids.map(tid => `${validator.isUUID(String(tid)) ? 'topicRemote' : 'topic'}:${tid}`); const topics = await db.getObjects(keys, fields); const result = await plugins.hooks.fire('filter:topic.getFields', { tids: tids, @@ -64,12 +63,12 @@ module.exports = function (Topics) { }; Topics.setTopicField = async function (tid, field, value) { - const setPrefix = activitypub.helpers.isUri(tid) ? 'topicRemote' : 'topic'; + const setPrefix = validator.isUUID(String(tid)) ? 'topicRemote' : 'topic'; await db.setObjectField(`${setPrefix}:${tid}`, field, value); }; Topics.setTopicFields = async function (tid, data) { - const setPrefix = activitypub.helpers.isUri(tid) ? 'topicRemote' : 'topic'; + const setPrefix = validator.isUUID(String(tid)) ? 'topicRemote' : 'topic'; await db.setObject(`${setPrefix}:${tid}`, data); }; @@ -98,7 +97,7 @@ function modifyTopic(topic, fields) { return; } - if (activitypub.helpers.isUri(topic.tid)) { + if (validator.isUUID(String(topic.tid))) { intFields.splice(intFields.indexOf('uid'), 1); intFields.splice(intFields.indexOf('tid'), 1); } diff --git a/src/topics/index.js b/src/topics/index.js index fbf21d0786..0f09e1444a 100644 --- a/src/topics/index.js +++ b/src/topics/index.js @@ -72,10 +72,10 @@ Topics.getTopicsByTids = async function (tids, options) { const topics = await Topics.getTopicsData(tids); const uids = _.uniq(topics .map(t => t && t.uid && t.uid.toString()) - .filter(v => utils.isNumber(v) || activitypub.helpers.isUri(v))); + .filter(v => utils.isNumber(v) || validator.isUUID(String(v)))); const cids = _.uniq(topics .map(t => t && t.cid && t.cid.toString()) - .filter(v => utils.isNumber(v) || activitypub.helpers.isUri(v))); + .filter(v => utils.isNumber(v) || validator.isUUID(String(v)))); const guestTopics = topics.filter(t => t && t.uid === 0); async function loadGuestHandles() { diff --git a/src/topics/posts.js b/src/topics/posts.js index 6c9d53aee0..d55982f4f3 100644 --- a/src/topics/posts.js +++ b/src/topics/posts.js @@ -231,7 +231,7 @@ module.exports = function (Topics) { Topics.getLatestUndeletedReply = async function (tid) { let isDeleted = false; let index = 0; - const setPrefix = activitypub.helpers.isUri(tid) ? 'tidRemote' : 'tid'; + const setPrefix = validator.isUUID(String(tid)) ? 'tidRemote' : 'tid'; do { /* eslint-disable no-await-in-loop */ const pids = await db.getSortedSetRevRange(`${setPrefix}:${tid}:posts`, index, index); @@ -312,7 +312,7 @@ module.exports = function (Topics) { }; async function incrementFieldAndUpdateSortedSet(tid, field, by, set) { - const value = await db.incrObjectFieldBy(`${activitypub.helpers.isUri(tid) ? 'topicRemote' : 'topic'}:${tid}`, field, by); + const value = await db.incrObjectFieldBy(`${validator.isUUID(String(tid)) ? 'topicRemote' : 'topic'}:${tid}`, field, by); await db[Array.isArray(set) ? 'sortedSetsAdd' : 'sortedSetAdd'](set, value, tid); } diff --git a/src/topics/recent.js b/src/topics/recent.js index 67c02d1975..d360a44f10 100644 --- a/src/topics/recent.js +++ b/src/topics/recent.js @@ -1,10 +1,11 @@ 'use strict'; +const validator = require('validator'); + const db = require('../database'); const plugins = require('../plugins'); const posts = require('../posts'); -const activitypub = require('../activitypub'); module.exports = function (Topics) { const terms = { @@ -74,7 +75,7 @@ module.exports = function (Topics) { data = await plugins.hooks.fire('filter:topics.updateRecent', { tid: tid, timestamp: timestamp }); } if (data && data.tid && data.timestamp) { - const setPrefix = activitypub.helpers.isUri(data.tid) ? 'topicsRemote' : 'topics'; + const setPrefix = validator.isUUID(String(data.tid)) ? 'topicsRemote' : 'topics'; await db.sortedSetAdd(`${setPrefix}:recent`, data.timestamp, data.tid); } };