2024-01-26 15:10:35 -05:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
|
|
const winston = require('winston');
|
|
|
|
|
|
|
|
|
|
const db = require('../database');
|
2024-03-05 09:56:15 -05:00
|
|
|
const user = require('../user');
|
2024-01-26 15:10:35 -05:00
|
|
|
const utils = require('../utils');
|
|
|
|
|
|
|
|
|
|
const activitypub = module.parent.exports;
|
|
|
|
|
|
|
|
|
|
const Actors = module.exports;
|
|
|
|
|
|
2024-01-26 16:48:16 -05:00
|
|
|
Actors.assert = async (ids, options = {}) => {
|
2024-01-26 15:10:35 -05:00
|
|
|
// Handle single values
|
|
|
|
|
if (!Array.isArray(ids)) {
|
|
|
|
|
ids = [ids];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Filter out uids if passed in
|
|
|
|
|
ids = ids.filter(id => !utils.isNumber(id));
|
|
|
|
|
|
|
|
|
|
// Filter out existing
|
2024-01-26 16:48:16 -05:00
|
|
|
if (!options.update) {
|
|
|
|
|
const exists = await db.isSortedSetMembers('usersRemote:lastCrawled', ids.map(id => ((typeof id === 'object' && id.hasOwnProperty('id')) ? id.id : id)));
|
|
|
|
|
ids = ids.filter((id, idx) => !exists[idx]);
|
|
|
|
|
}
|
2024-01-26 15:10:35 -05:00
|
|
|
|
|
|
|
|
if (!ids.length) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-20 11:57:50 -05:00
|
|
|
winston.verbose(`[activitypub/actors] Asserting ${ids.length} actor(s)`);
|
|
|
|
|
|
2024-02-12 14:32:55 -05:00
|
|
|
const followersUrlMap = new Map();
|
2024-02-21 13:43:56 -05:00
|
|
|
const pubKeysMap = new Map();
|
2024-01-26 15:10:35 -05:00
|
|
|
const actors = await Promise.all(ids.map(async (id) => {
|
|
|
|
|
try {
|
2024-02-20 11:57:50 -05:00
|
|
|
winston.verbose(`[activitypub/actors] Processing ${id}`);
|
2024-02-05 16:57:17 -05:00
|
|
|
const actor = (typeof id === 'object' && id.hasOwnProperty('id')) ? id : await activitypub.get('uid', 0, id);
|
2024-01-26 15:10:35 -05:00
|
|
|
|
|
|
|
|
// Follow counts
|
2024-01-26 16:24:14 -05:00
|
|
|
try {
|
|
|
|
|
const [followers, following] = await Promise.all([
|
2024-02-05 16:57:17 -05:00
|
|
|
actor.followers ? activitypub.get('uid', 0, actor.followers) : { totalItems: 0 },
|
|
|
|
|
actor.following ? activitypub.get('uid', 0, actor.following) : { totalItems: 0 },
|
2024-01-26 16:24:14 -05:00
|
|
|
]);
|
|
|
|
|
actor.followerCount = followers.totalItems;
|
|
|
|
|
actor.followingCount = following.totalItems;
|
|
|
|
|
} catch (e) {
|
|
|
|
|
// no action required
|
2024-01-26 16:48:16 -05:00
|
|
|
winston.verbose(`[activitypub/actor.assert] Unable to retrieve follower counts for ${actor.id}`);
|
2024-01-26 16:24:14 -05:00
|
|
|
}
|
2024-01-26 15:10:35 -05:00
|
|
|
|
|
|
|
|
// Post count
|
2024-02-26 11:34:03 -05:00
|
|
|
try {
|
|
|
|
|
const outbox = actor.outbox ? await activitypub.get('uid', 0, actor.outbox) : { totalItems: 0 };
|
|
|
|
|
actor.postcount = outbox.totalItems;
|
|
|
|
|
} catch (e) {
|
|
|
|
|
// no action required
|
|
|
|
|
winston.verbose(`[activitypub/actor.assert] Unable to retrieve post counts for ${actor.id}`);
|
|
|
|
|
}
|
2024-01-26 15:10:35 -05:00
|
|
|
|
2024-02-12 14:32:55 -05:00
|
|
|
// Followers url for backreference
|
|
|
|
|
if (actor.hasOwnProperty('followers') && activitypub.helpers.isUri(actor.followers)) {
|
|
|
|
|
followersUrlMap.set(actor.followers, actor.id);
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-21 13:43:56 -05:00
|
|
|
// Public keys
|
|
|
|
|
pubKeysMap.set(actor.id, actor.publicKey);
|
|
|
|
|
|
2024-01-26 15:10:35 -05:00
|
|
|
return actor;
|
|
|
|
|
} catch (e) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
// Build userData object for storage
|
|
|
|
|
const profiles = await activitypub.mocks.profile(actors);
|
|
|
|
|
const now = Date.now();
|
|
|
|
|
|
2024-02-21 13:43:56 -05:00
|
|
|
const bulkSet = profiles.reduce((memo, profile) => {
|
|
|
|
|
if (profile) {
|
|
|
|
|
const key = `userRemote:${profile.uid}`;
|
|
|
|
|
memo.push([key, profile], [`${key}:keys`, pubKeysMap.get(profile.uid)]);
|
2024-02-12 14:32:55 -05:00
|
|
|
}
|
2024-02-21 13:43:56 -05:00
|
|
|
|
|
|
|
|
return memo;
|
|
|
|
|
}, []);
|
2024-02-12 14:32:55 -05:00
|
|
|
if (followersUrlMap.size) {
|
|
|
|
|
bulkSet.push(['followersUrl:uid', Object.fromEntries(followersUrlMap)]);
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-05 09:56:15 -05:00
|
|
|
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: [] });
|
|
|
|
|
|
2024-01-26 15:10:35 -05:00
|
|
|
await Promise.all([
|
2024-02-12 14:32:55 -05:00
|
|
|
db.setObjectBulk(bulkSet),
|
2024-01-26 15:10:35 -05:00
|
|
|
db.sortedSetAdd('usersRemote:lastCrawled', ids.map((id, idx) => (profiles[idx] ? now : null)).filter(Boolean), ids.filter((id, idx) => profiles[idx])),
|
2024-03-05 09:56:15 -05:00
|
|
|
db.sortedSetRemove('ap.preferredUsername:sorted', searchQueries.remove),
|
|
|
|
|
db.sortedSetRemoveBulk(searchQueries.remove),
|
|
|
|
|
db.sortedSetAddBulk(searchQueries.add),
|
2024-01-26 15:10:35 -05:00
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
return actors.every(Boolean);
|
|
|
|
|
};
|