mirror of
				https://github.com/NodeBB/NodeBB.git
				synced 2025-10-31 11:05:54 +01:00 
			
		
		
		
	feat: allow user.search to handle remote handles, beginning of mentions support
This commit is contained in:
		| @@ -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; | ||||||
|   | |||||||
| @@ -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); | ||||||
|   | |||||||
| @@ -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) => { | ||||||
|   | |||||||
| @@ -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, | ||||||
|   | |||||||
| @@ -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; | ||||||
|   | |||||||
| @@ -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 = []; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user