mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-11-01 19:46:01 +01:00
feat: mentions support
This commit is contained in:
@@ -93,30 +93,39 @@ Actors.assert = async (ids, options = {}) => {
|
|||||||
const exists = await db.isSortedSetMembers('usersRemote:lastCrawled', profiles.map(p => p.uid));
|
const exists = await db.isSortedSetMembers('usersRemote:lastCrawled', profiles.map(p => p.uid));
|
||||||
const uidsForCurrent = profiles.map((p, idx) => (exists[idx] ? p.uid : 0));
|
const uidsForCurrent = profiles.map((p, idx) => (exists[idx] ? p.uid : 0));
|
||||||
const current = await user.getUsersFields(uidsForCurrent, ['username', 'fullname']);
|
const current = await user.getUsersFields(uidsForCurrent, ['username', 'fullname']);
|
||||||
const searchQueries = profiles.reduce((memo, profile, idx) => {
|
const queries = profiles.reduce((memo, profile, idx) => {
|
||||||
if (profile) {
|
if (profile) {
|
||||||
const { username, fullname } = current[idx];
|
const { username, fullname } = current[idx];
|
||||||
|
|
||||||
if (username !== profile.username) {
|
if (username !== profile.username) {
|
||||||
memo.remove.push(['ap.preferredUsername:sorted', `${username.toLowerCase()}:${profile.uid}`]);
|
if (uidsForCurrent[idx] !== 0) {
|
||||||
memo.add.push(['ap.preferredUsername:sorted', 0, `${profile.username.toLowerCase()}:${profile.uid}`]);
|
memo.searchRemove.push(['ap.preferredUsername:sorted', `${username.toLowerCase()}:${profile.uid}`]);
|
||||||
|
memo.handleRemove.push(username.toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
memo.searchAdd.push(['ap.preferredUsername:sorted', 0, `${profile.username.toLowerCase()}:${profile.uid}`]);
|
||||||
|
memo.handleAdd[profile.username.toLowerCase()] = profile.uid;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fullname !== profile.fullname) {
|
if (fullname !== profile.fullname) {
|
||||||
memo.remove.push(['ap.name:sorted', `${fullname.toLowerCase()}:${profile.uid}`]);
|
if (uidsForCurrent[idx] !== 0) {
|
||||||
memo.add.push(['ap.name:sorted', 0, `${profile.fullname.toLowerCase()}:${profile.uid}`]);
|
memo.searchRemove.push(['ap.name:sorted', `${fullname.toLowerCase()}:${profile.uid}`]);
|
||||||
|
}
|
||||||
|
|
||||||
|
memo.searchAdd.push(['ap.name:sorted', 0, `${profile.fullname.toLowerCase()}:${profile.uid}`]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return memo;
|
return memo;
|
||||||
}, { remove: [], add: [] });
|
}, { searchRemove: [], searchAdd: [], handleRemove: [], handleAdd: {} });
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
db.setObjectBulk(bulkSet),
|
db.setObjectBulk(bulkSet),
|
||||||
db.sortedSetAdd('usersRemote:lastCrawled', ids.map((id, idx) => (profiles[idx] ? now : null)).filter(Boolean), ids.filter((id, idx) => profiles[idx])),
|
db.sortedSetAdd('usersRemote:lastCrawled', profiles.map(p => now), profiles.map(p => p.uid)),
|
||||||
db.sortedSetRemove('ap.preferredUsername:sorted', searchQueries.remove),
|
db.sortedSetRemoveBulk(queries.searchRemove),
|
||||||
db.sortedSetRemoveBulk(searchQueries.remove),
|
db.sortedSetAddBulk(queries.searchAdd),
|
||||||
db.sortedSetAddBulk(searchQueries.add),
|
db.deleteObjectFields('handle:uid', queries.handleRemove),
|
||||||
|
db.setObject('handle:uid', queries.handleAdd),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return actors.every(Boolean);
|
return actors.every(Boolean);
|
||||||
|
|||||||
@@ -235,9 +235,17 @@ Mocks.note = async (post) => {
|
|||||||
|
|
||||||
if (matches.size) {
|
if (matches.size) {
|
||||||
tag = tag || [];
|
tag = tag || [];
|
||||||
Array.from(matches).map(match => ({
|
tag.push(...Array.from(matches).map(({ id: href, slug: name }) => {
|
||||||
type: 'Mention',
|
if (utils.isNumber(href)) { // local ref
|
||||||
name: match,
|
href = `${nconf.get('url')}/user/${name.slice(1)}`;
|
||||||
|
name = `${name}@${nconf.get('url_parsed').hostname}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'Mention',
|
||||||
|
href,
|
||||||
|
name,
|
||||||
|
};
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ const privileges = require('../privileges');
|
|||||||
const user = require('../user');
|
const user = require('../user');
|
||||||
const topics = require('../topics');
|
const topics = require('../topics');
|
||||||
const posts = require('../posts');
|
const posts = require('../posts');
|
||||||
|
const plugins = require('../plugins');
|
||||||
const utils = require('../utils');
|
const utils = require('../utils');
|
||||||
|
|
||||||
const activitypub = module.parent.exports;
|
const activitypub = module.parent.exports;
|
||||||
@@ -59,6 +60,9 @@ Notes.assert = async (uid, input, options = {}) => {
|
|||||||
delete hash._activitypub;
|
delete hash._activitypub;
|
||||||
// should call internal method here to create/edit post
|
// should call internal method here to create/edit post
|
||||||
await db.setObject(key, hash);
|
await db.setObject(key, hash);
|
||||||
|
const post = await posts.getPostData(id);
|
||||||
|
post._activitypub = postData._activitypub;
|
||||||
|
plugins.hooks.fire(`action:post.${(exists && options.update) ? 'edit' : 'save'}`, { post });
|
||||||
winston.verbose(`[activitypub/notes.assert] Note ${id} saved.`);
|
winston.verbose(`[activitypub/notes.assert] Note ${id} saved.`);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
@@ -176,17 +180,14 @@ Notes.getParentChain = async (uid, input) => {
|
|||||||
return chain;
|
return chain;
|
||||||
};
|
};
|
||||||
|
|
||||||
Notes.assertParentChain = async (chain, tid) => {
|
Notes.assertParentChain = async (chain) => {
|
||||||
const data = [];
|
const data = [];
|
||||||
chain.reduce((child, parent) => {
|
chain.reduce((child, parent) => {
|
||||||
data.push([`pid:${parent.pid}:replies`, child.timestamp, child.pid]);
|
data.push([`pid:${parent.pid}:replies`, child.timestamp, child.pid]);
|
||||||
return parent;
|
return parent;
|
||||||
});
|
});
|
||||||
|
|
||||||
await Promise.all([
|
await db.sortedSetAddBulk(data);
|
||||||
db.sortedSetAddBulk(data),
|
|
||||||
db.setObjectBulk(chain.map(post => [`post:${post.pid}`, { tid }])),
|
|
||||||
]);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Notes.assertTopic = async (uid, id) => {
|
Notes.assertTopic = async (uid, id) => {
|
||||||
@@ -238,7 +239,10 @@ Notes.assertTopic = async (uid, id) => {
|
|||||||
tid = tid || utils.generateUUID();
|
tid = tid || utils.generateUUID();
|
||||||
mainPost.tid = tid;
|
mainPost.tid = tid;
|
||||||
|
|
||||||
const unprocessed = chain.filter((p, idx) => !members[idx]);
|
const unprocessed = chain.map((post) => {
|
||||||
|
post.tid = tid; // add tid to post hash
|
||||||
|
return post;
|
||||||
|
}).filter((p, idx) => !members[idx]);
|
||||||
winston.verbose(`[notes/assertTopic] ${unprocessed.length} new note(s) found.`);
|
winston.verbose(`[notes/assertTopic] ${unprocessed.length} new note(s) found.`);
|
||||||
|
|
||||||
const [ids, timestamps] = [
|
const [ids, timestamps] = [
|
||||||
@@ -275,7 +279,7 @@ Notes.assertTopic = async (uid, id) => {
|
|||||||
Notes.assert(uid, unprocessed),
|
Notes.assert(uid, unprocessed),
|
||||||
]);
|
]);
|
||||||
await Promise.all([ // must be done after .assert()
|
await Promise.all([ // must be done after .assert()
|
||||||
Notes.assertParentChain(chain, tid),
|
Notes.assertParentChain(chain),
|
||||||
Notes.updateTopicCounts(tid),
|
Notes.updateTopicCounts(tid),
|
||||||
Notes.syncUserInboxes(tid),
|
Notes.syncUserInboxes(tid),
|
||||||
topics.updateLastPostTimeFromLastPid(tid),
|
topics.updateLastPostTimeFromLastPid(tid),
|
||||||
|
|||||||
@@ -4,12 +4,14 @@ const querystring = require('querystring');
|
|||||||
|
|
||||||
const posts = require('../posts');
|
const posts = require('../posts');
|
||||||
const privileges = require('../privileges');
|
const privileges = require('../privileges');
|
||||||
|
const utils = require('../utils');
|
||||||
|
|
||||||
const helpers = require('./helpers');
|
const helpers = require('./helpers');
|
||||||
|
|
||||||
const postsController = module.exports;
|
const postsController = module.exports;
|
||||||
|
|
||||||
postsController.redirectToPost = async function (req, res, next) {
|
postsController.redirectToPost = async function (req, res, next) {
|
||||||
const pid = parseInt(req.params.pid, 10);
|
const pid = utils.isNumber(req.params.pid) ? parseInt(req.params.pid, 10) : req.params.pid;
|
||||||
if (!pid) {
|
if (!pid) {
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|||||||
28
src/upgrades/4.0.0/searchable_remote_users.js
Normal file
28
src/upgrades/4.0.0/searchable_remote_users.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const db = require('../../database');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
name: 'Allow remote user profiles to be searched',
|
||||||
|
// remember, month is zero-indexed (so January is 0, December is 11)
|
||||||
|
timestamp: Date.UTC(2024, 2, 1),
|
||||||
|
method: async () => {
|
||||||
|
const ids = await db.getSortedSetMembers('usersRemote:lastCrawled');
|
||||||
|
const data = await db.getObjectsFields(ids.map(id => `userRemote:${id}`), ['username', 'fullname']);
|
||||||
|
|
||||||
|
const queries = data.reduce((memo, profile, idx) => {
|
||||||
|
if (profile && profile.username && profile.fullname) {
|
||||||
|
memo.zset.push(['ap.preferredUsername:sorted', 0, `${profile.username.toLowerCase()}:${ids[idx]}`]);
|
||||||
|
memo.zset.push(['ap.name:sorted', 0, `${profile.fullname.toLowerCase()}:${ids[idx]}`]);
|
||||||
|
memo.hash[profile.username.toLowerCase()] = ids[idx];
|
||||||
|
}
|
||||||
|
|
||||||
|
return memo;
|
||||||
|
}, { zset: [], hash: {} });
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
db.sortedSetAddBulk(queries.zset),
|
||||||
|
db.setObject('handle:uid', queries.hash),
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -111,11 +111,8 @@ User.getUidByUserslug = async function (userslug) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// fix this! Forces a remote call, this is bad. Should be done in actors.assert
|
|
||||||
// then mentions. should return actor uri or url or something to parsePost.
|
|
||||||
if (userslug.includes('@')) {
|
if (userslug.includes('@')) {
|
||||||
const { actorUri } = await activitypub.helpers.query(userslug);
|
return (await db.getObjectField('handle:uid', userslug)) || 0;
|
||||||
return actorUri;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return await db.sortedSetScore('userslug:uid', userslug);
|
return await db.sortedSetScore('userslug:uid', userslug);
|
||||||
|
|||||||
Reference in New Issue
Block a user