feat: allow user.search to handle remote handles, beginning of mentions support

This commit is contained in:
Julian Lam
2024-03-05 09:56:15 -05:00
parent 07c1ea2876
commit 1b64fdb5b3
6 changed files with 75 additions and 8 deletions

View File

@@ -10,8 +10,8 @@
window.slugify = factory(XRegExp); window.slugify = factory(XRegExp);
} }
}(function (XRegExp) { }(function (XRegExp) {
const invalidUnicodeChars = XRegExp('[^\\p{L}\\s\\d\\-_]', 'g'); const invalidUnicodeChars = XRegExp('[^\\p{L}\\s\\d\\-_@.]', 'g');
const invalidLatinChars = /[^\w\s\d\-_]/g; const invalidLatinChars = /[^\w\s\d\-_@.]/g;
const trimRegex = /^\s+|\s+$/g; const trimRegex = /^\s+|\s+$/g;
const collapseWhitespace = /\s+/g; const collapseWhitespace = /\s+/g;
const collapseDash = /-+/g; const collapseDash = /-+/g;

View File

@@ -3,6 +3,7 @@
const winston = require('winston'); const winston = require('winston');
const db = require('../database'); const db = require('../database');
const user = require('../user');
const utils = require('../utils'); const utils = require('../utils');
const activitypub = module.parent.exports; const activitypub = module.parent.exports;
@@ -89,9 +90,33 @@ Actors.assert = async (ids, options = {}) => {
bulkSet.push(['followersUrl:uid', Object.fromEntries(followersUrlMap)]); bulkSet.push(['followersUrl:uid', Object.fromEntries(followersUrlMap)]);
} }
const exists = await db.isSortedSetMembers('usersRemote:lastCrawled', profiles.map(p => p.uid));
const uidsForCurrent = profiles.map((p, idx) => (exists[idx] ? p.uid : 0));
const current = await user.getUsersFields(uidsForCurrent, ['username', 'fullname']);
const searchQueries = profiles.reduce((memo, profile, idx) => {
if (profile) {
const { username, fullname } = current[idx];
if (username !== profile.username) {
memo.remove.push(['ap.preferredUsername:sorted', `${username.toLowerCase()}:${profile.uid}`]);
memo.add.push(['ap.preferredUsername:sorted', 0, `${profile.username.toLowerCase()}:${profile.uid}`]);
}
if (fullname !== profile.fullname) {
memo.remove.push(['ap.name:sorted', `${fullname.toLowerCase()}:${profile.uid}`]);
memo.add.push(['ap.name:sorted', 0, `${profile.fullname.toLowerCase()}:${profile.uid}`]);
}
}
return memo;
}, { remove: [], add: [] });
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', ids.map((id, idx) => (profiles[idx] ? now : null)).filter(Boolean), ids.filter((id, idx) => profiles[idx])),
db.sortedSetRemove('ap.preferredUsername:sorted', searchQueries.remove),
db.sortedSetRemoveBulk(searchQueries.remove),
db.sortedSetAddBulk(searchQueries.add),
]); ]);
return actors.every(Boolean); return actors.every(Boolean);

View File

@@ -57,10 +57,16 @@ Helpers.query = async (id) => {
({ href: actorUri } = actorUri); ({ href: actorUri } = actorUri);
} }
const { publicKey } = body; const { subject, publicKey } = body;
const payload = { subject, username, hostname, actorUri, publicKey };
webfingerCache.set(id, { username, hostname, actorUri, publicKey }); const claimedId = subject.slice(5);
return { username, hostname, actorUri, publicKey }; webfingerCache.set(claimedId, payload);
if (claimedId !== id) {
webfingerCache.set(id, payload);
}
return payload;
}; };
Helpers.generateKeys = async (type, id) => { Helpers.generateKeys = async (type, id) => {

View File

@@ -9,6 +9,7 @@ const user = require('../user');
const categories = require('../categories'); const categories = require('../categories');
const posts = require('../posts'); const posts = require('../posts');
const topics = require('../topics'); const topics = require('../topics');
const plugins = require('../plugins');
const utils = require('../utils'); const utils = require('../utils');
const activitypub = module.parent.exports; const activitypub = module.parent.exports;
@@ -227,6 +228,20 @@ Mocks.note = async (post) => {
})); }));
} }
const mentionsEnabled = await plugins.isActive('nodebb-plugin-mentions');
if (mentionsEnabled) {
const mentions = require.main.require('nodebb-plugin-mentions');
const matches = await mentions.getMatches(post.content);
if (matches.size) {
tag = tag || [];
Array.from(matches).map(match => ({
type: 'Mention',
name: match,
}));
}
}
const object = { const object = {
'@context': 'https://www.w3.org/ns/activitystreams', '@context': 'https://www.w3.org/ns/activitystreams',
id, id,

View File

@@ -111,6 +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); const { actorUri } = await activitypub.helpers.query(userslug);
return actorUri; return actorUri;

View File

@@ -7,6 +7,7 @@ const meta = require('../meta');
const plugins = require('../plugins'); const plugins = require('../plugins');
const db = require('../database'); const db = require('../database');
const groups = require('../groups'); const groups = require('../groups');
const activitypub = require('../activitypub');
const utils = require('../utils'); const utils = require('../utils');
module.exports = function (User) { module.exports = function (User) {
@@ -42,9 +43,21 @@ module.exports = function (User) {
} else { } else {
const searchMethod = data.findUids || findUids; const searchMethod = data.findUids || findUids;
uids = await searchMethod(query, searchBy, data.hardCap); uids = await searchMethod(query, searchBy, data.hardCap);
const mapping = {
username: 'ap.preferredUsername',
fullname: 'ap.name',
};
if (meta.config.activitypubEnabled && mapping.hasOwnProperty(searchBy)) {
uids = uids.concat(await searchMethod(query, mapping[searchBy], data.hardCap));
}
} }
uids = await filterAndSortUids(uids, data); uids = await filterAndSortUids(uids, data);
if (data.hardCap > 0) {
uids.length = data.hardCap;
}
const result = await plugins.hooks.fire('filter:users.search', { uids: uids, uid: uid }); const result = await plugins.hooks.fire('filter:users.search', { uids: uids, uid: uid });
uids = result.uids; uids = result.uids;
@@ -62,7 +75,8 @@ module.exports = function (User) {
const userData = await User.getUsers(uids, uid); const userData = await User.getUsers(uids, uid);
searchResult.timing = (process.elapsedTimeSince(startTime) / 1000).toFixed(2); searchResult.timing = (process.elapsedTimeSince(startTime) / 1000).toFixed(2);
searchResult.users = userData.filter(user => user && user.uid > 0); searchResult.users = userData.filter(user => (user &&
utils.isNumber(user.uid) ? user.uid > 0 : activitypub.helpers.isUri(user.uid)));
return searchResult; return searchResult;
}; };
@@ -78,12 +92,17 @@ module.exports = function (User) {
hardCap = hardCap || resultsPerPage * 10; hardCap = hardCap || resultsPerPage * 10;
const data = await db.getSortedSetRangeByLex(`${searchBy}:sorted`, min, max, 0, hardCap); const data = await db.getSortedSetRangeByLex(`${searchBy}:sorted`, min, max, 0, hardCap);
const uids = data.map(data => data.split(':').pop()); // const uids = data.map(data => data.split(':').pop());
const uids = data.map((data) => {
data = data.split(':');
data.shift();
return data.join(':');
});
return uids; return uids;
} }
async function filterAndSortUids(uids, data) { async function filterAndSortUids(uids, data) {
uids = uids.filter(uid => parseInt(uid, 10)); uids = uids.filter(uid => parseInt(uid, 10) || activitypub.helpers.isUri(uid));
let filters = data.filters || []; let filters = data.filters || [];
filters = Array.isArray(filters) ? filters : [data.filters]; filters = Array.isArray(filters) ? filters : [data.filters];
const fields = []; const fields = [];