feat: mentions support

This commit is contained in:
Julian Lam
2024-03-05 14:24:13 -05:00
parent 1b64fdb5b3
commit e4c1ca1ede
6 changed files with 73 additions and 25 deletions

View File

@@ -93,30 +93,39 @@ Actors.assert = async (ids, options = {}) => {
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) => {
const queries = 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 (uidsForCurrent[idx] !== 0) {
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) {
memo.remove.push(['ap.name:sorted', `${fullname.toLowerCase()}:${profile.uid}`]);
memo.add.push(['ap.name:sorted', 0, `${profile.fullname.toLowerCase()}:${profile.uid}`]);
if (uidsForCurrent[idx] !== 0) {
memo.searchRemove.push(['ap.name:sorted', `${fullname.toLowerCase()}:${profile.uid}`]);
}
memo.searchAdd.push(['ap.name:sorted', 0, `${profile.fullname.toLowerCase()}:${profile.uid}`]);
}
}
return memo;
}, { remove: [], add: [] });
}, { searchRemove: [], searchAdd: [], handleRemove: [], handleAdd: {} });
await Promise.all([
db.setObjectBulk(bulkSet),
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),
db.sortedSetAdd('usersRemote:lastCrawled', profiles.map(p => now), profiles.map(p => p.uid)),
db.sortedSetRemoveBulk(queries.searchRemove),
db.sortedSetAddBulk(queries.searchAdd),
db.deleteObjectFields('handle:uid', queries.handleRemove),
db.setObject('handle:uid', queries.handleAdd),
]);
return actors.every(Boolean);

View File

@@ -235,9 +235,17 @@ Mocks.note = async (post) => {
if (matches.size) {
tag = tag || [];
Array.from(matches).map(match => ({
type: 'Mention',
name: match,
tag.push(...Array.from(matches).map(({ id: href, slug: name }) => {
if (utils.isNumber(href)) { // local ref
href = `${nconf.get('url')}/user/${name.slice(1)}`;
name = `${name}@${nconf.get('url_parsed').hostname}`;
}
return {
type: 'Mention',
href,
name,
};
}));
}
}

View File

@@ -9,6 +9,7 @@ const privileges = require('../privileges');
const user = require('../user');
const topics = require('../topics');
const posts = require('../posts');
const plugins = require('../plugins');
const utils = require('../utils');
const activitypub = module.parent.exports;
@@ -59,6 +60,9 @@ Notes.assert = async (uid, input, options = {}) => {
delete hash._activitypub;
// should call internal method here to create/edit post
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.`);
}
}));
@@ -176,17 +180,14 @@ Notes.getParentChain = async (uid, input) => {
return chain;
};
Notes.assertParentChain = async (chain, tid) => {
Notes.assertParentChain = async (chain) => {
const data = [];
chain.reduce((child, parent) => {
data.push([`pid:${parent.pid}:replies`, child.timestamp, child.pid]);
return parent;
});
await Promise.all([
db.sortedSetAddBulk(data),
db.setObjectBulk(chain.map(post => [`post:${post.pid}`, { tid }])),
]);
await db.sortedSetAddBulk(data);
};
Notes.assertTopic = async (uid, id) => {
@@ -238,7 +239,10 @@ Notes.assertTopic = async (uid, id) => {
tid = tid || utils.generateUUID();
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.`);
const [ids, timestamps] = [
@@ -275,7 +279,7 @@ Notes.assertTopic = async (uid, id) => {
Notes.assert(uid, unprocessed),
]);
await Promise.all([ // must be done after .assert()
Notes.assertParentChain(chain, tid),
Notes.assertParentChain(chain),
Notes.updateTopicCounts(tid),
Notes.syncUserInboxes(tid),
topics.updateLastPostTimeFromLastPid(tid),

View File

@@ -4,12 +4,14 @@ const querystring = require('querystring');
const posts = require('../posts');
const privileges = require('../privileges');
const utils = require('../utils');
const helpers = require('./helpers');
const postsController = module.exports;
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) {
return next();
}

View 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),
]);
},
};

View File

@@ -111,11 +111,8 @@ User.getUidByUserslug = async function (userslug) {
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('@')) {
const { actorUri } = await activitypub.helpers.query(userslug);
return actorUri;
return (await db.getObjectField('handle:uid', userslug)) || 0;
}
return await db.sortedSetScore('userslug:uid', userslug);