| 
									
										
										
										
											2023-12-08 10:55:16 -05:00
										 |  |  | 'use strict'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * DEVELOPMENT NOTE | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * THIS FILE IS UNDER ACTIVE DEVELOPMENT AND IS EXPLICITLY EXCLUDED FROM IMMUTABILITY GUARANTEES | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * If you use api methods in this file, be prepared that they may be removed or modified with no warning. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-22 15:53:04 -05:00
										 |  |  | const nconf = require('nconf'); | 
					
						
							| 
									
										
										
										
											2024-06-13 18:36:05 -04:00
										 |  |  | const winston = require('winston'); | 
					
						
							| 
									
										
										
										
											2023-12-22 15:53:04 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-08 10:55:16 -05:00
										 |  |  | const db = require('../database'); | 
					
						
							| 
									
										
										
										
											2024-04-15 14:40:26 -04:00
										 |  |  | const user = require('../user'); | 
					
						
							| 
									
										
										
										
											2025-05-08 13:41:43 -04:00
										 |  |  | const categories = require('../categories'); | 
					
						
							| 
									
										
										
										
											2024-02-26 15:39:09 -05:00
										 |  |  | const meta = require('../meta'); | 
					
						
							|  |  |  | const privileges = require('../privileges'); | 
					
						
							| 
									
										
										
										
											2023-12-08 10:55:16 -05:00
										 |  |  | const activitypub = require('../activitypub'); | 
					
						
							| 
									
										
										
										
											2024-01-24 11:44:10 -05:00
										 |  |  | const posts = require('../posts'); | 
					
						
							| 
									
										
										
										
											2024-08-22 14:45:17 -04:00
										 |  |  | const topics = require('../topics'); | 
					
						
							| 
									
										
										
										
											2024-10-07 14:09:34 -04:00
										 |  |  | const messaging = require('../messaging'); | 
					
						
							| 
									
										
										
										
											2024-05-07 12:43:27 -04:00
										 |  |  | const utils = require('../utils'); | 
					
						
							| 
									
										
										
										
											2023-12-08 10:55:16 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  | const activitypubApi = module.exports; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-26 15:39:09 -05:00
										 |  |  | function enabledCheck(next) { | 
					
						
							|  |  |  | 	return async function (caller, params) { | 
					
						
							| 
									
										
										
										
											2024-06-06 21:10:24 -04:00
										 |  |  | 		if (meta.config.activitypubEnabled) { | 
					
						
							| 
									
										
										
										
											2024-07-10 10:15:31 -04:00
										 |  |  | 			try { | 
					
						
							|  |  |  | 				await next(caller, params); | 
					
						
							|  |  |  | 			} catch (e) { | 
					
						
							|  |  |  | 				winston.error(`[activitypub/api] Error\n${e.stack}`); | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2024-02-26 15:39:09 -05:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-15 14:40:26 -04:00
										 |  |  | activitypubApi.follow = enabledCheck(async (caller, { type, id, actor } = {}) => { | 
					
						
							|  |  |  | 	// Privilege checks should be done upstream
 | 
					
						
							| 
									
										
										
										
											2025-03-24 11:52:09 -04:00
										 |  |  | 	const acceptedTypes = ['uid', 'cid']; | 
					
						
							| 
									
										
										
										
											2024-04-15 14:40:26 -04:00
										 |  |  | 	const assertion = await activitypub.actors.assert(actor); | 
					
						
							| 
									
										
										
										
											2025-03-24 11:52:09 -04:00
										 |  |  | 	if (!acceptedTypes.includes(type) || !assertion || (Array.isArray(assertion) && assertion.length)) { | 
					
						
							| 
									
										
										
										
											2024-01-08 14:30:09 -05:00
										 |  |  | 		throw new Error('[[error:activitypub.invalid-id]]'); | 
					
						
							| 
									
										
										
										
											2023-12-08 10:55:16 -05:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-08 13:41:43 -04:00
										 |  |  | 	if (actor.includes('@')) { | 
					
						
							|  |  |  | 		const [uid, cid] = await Promise.all([ | 
					
						
							|  |  |  | 			user.getUidByUserslug(actor), | 
					
						
							|  |  |  | 			categories.getCidByHandle(actor), | 
					
						
							|  |  |  | 		]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		actor = uid || cid; | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-03-24 11:52:09 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-05 16:35:12 -04:00
										 |  |  | 	const isFollowing = await db.isSortedSetMember(type === 'uid' ? `followingRemote:${id}` : `cid:${id}:following`, actor); | 
					
						
							| 
									
										
										
										
											2025-03-24 11:52:09 -04:00
										 |  |  | 	if (isFollowing) { // already following
 | 
					
						
							|  |  |  | 		return; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-30 14:05:59 -04:00
										 |  |  | 	const timestamp = Date.now(); | 
					
						
							| 
									
										
										
										
											2024-04-15 14:40:26 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-30 14:05:59 -04:00
										 |  |  | 	await db.sortedSetAdd(`followRequests:${type}.${id}`, timestamp, actor); | 
					
						
							| 
									
										
										
										
											2025-01-09 11:46:48 -05:00
										 |  |  | 	try { | 
					
						
							|  |  |  | 		await activitypub.send(type, id, [actor], { | 
					
						
							| 
									
										
										
										
											2025-05-05 16:35:12 -04:00
										 |  |  | 			id: `${nconf.get('url')}/${type}/${id}#activity/follow/${encodeURIComponent(actor)}/${timestamp}`, | 
					
						
							| 
									
										
										
										
											2025-01-09 11:46:48 -05:00
										 |  |  | 			type: 'Follow', | 
					
						
							|  |  |  | 			object: actor, | 
					
						
							|  |  |  | 		}); | 
					
						
							|  |  |  | 	} catch (e) { | 
					
						
							|  |  |  | 		await db.sortedSetRemove(`followRequests:${type}.${id}`, actor); | 
					
						
							|  |  |  | 		throw e; | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-02-26 15:39:09 -05:00
										 |  |  | }); | 
					
						
							| 
									
										
										
										
											2023-12-08 10:55:16 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-01 15:59:29 -05:00
										 |  |  | // should be .undo.follow
 | 
					
						
							| 
									
										
										
										
											2024-04-15 14:40:26 -04:00
										 |  |  | activitypubApi.unfollow = enabledCheck(async (caller, { type, id, actor }) => { | 
					
						
							| 
									
										
										
										
											2025-03-24 11:52:09 -04:00
										 |  |  | 	const acceptedTypes = ['uid', 'cid']; | 
					
						
							| 
									
										
										
										
											2024-04-15 14:40:26 -04:00
										 |  |  | 	const assertion = await activitypub.actors.assert(actor); | 
					
						
							| 
									
										
										
										
											2025-03-24 11:52:09 -04:00
										 |  |  | 	if (!acceptedTypes.includes(type) || !assertion) { | 
					
						
							| 
									
										
										
										
											2024-01-08 14:30:09 -05:00
										 |  |  | 		throw new Error('[[error:activitypub.invalid-id]]'); | 
					
						
							| 
									
										
										
										
											2023-12-08 10:55:16 -05:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-08 13:55:17 -04:00
										 |  |  | 	if (actor.includes('@')) { | 
					
						
							|  |  |  | 		const [uid, cid] = await Promise.all([ | 
					
						
							|  |  |  | 			user.getUidByUserslug(actor), | 
					
						
							|  |  |  | 			categories.getCidByHandle(actor), | 
					
						
							|  |  |  | 		]); | 
					
						
							| 
									
										
										
										
											2025-03-24 11:52:09 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-08 13:55:17 -04:00
										 |  |  | 		actor = uid || cid; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-08 14:12:33 -04:00
										 |  |  | 	const [isFollowing, isPending] = await Promise.all([ | 
					
						
							| 
									
										
										
										
											2025-03-24 11:52:09 -04:00
										 |  |  | 		db.isSortedSetMember(type === 'uid' ? `followingRemote:${id}` : `cid:${id}:following`, actor), | 
					
						
							| 
									
										
										
										
											2025-05-08 13:55:17 -04:00
										 |  |  | 		db.isSortedSetMember(`followRequests:${type === 'uid' ? 'uid' : 'cid'}.${id}`, actor), | 
					
						
							| 
									
										
										
										
											2025-03-24 11:52:09 -04:00
										 |  |  | 	]); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-08 13:55:17 -04:00
										 |  |  | 	if (!isFollowing && !isPending) { // already not following/pending
 | 
					
						
							| 
									
										
										
										
											2025-03-24 11:52:09 -04:00
										 |  |  | 		return; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-30 14:05:59 -04:00
										 |  |  | 	const timestamps = await db.sortedSetsScore([ | 
					
						
							|  |  |  | 		`followRequests:${type}.${id}`, | 
					
						
							|  |  |  | 		type === 'uid' ? `followingRemote:${id}` : `cid:${id}:following`, | 
					
						
							|  |  |  | 	], actor); | 
					
						
							|  |  |  | 	const timestamp = timestamps[0] || timestamps[1]; | 
					
						
							| 
									
										
										
										
											2024-04-15 14:40:26 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	const object = { | 
					
						
							| 
									
										
										
										
											2025-05-05 16:35:12 -04:00
										 |  |  | 		id: `${nconf.get('url')}/${type}/${id}#activity/follow/${encodeURIComponent(actor)}/${timestamp}`, | 
					
						
							| 
									
										
										
										
											2024-04-15 14:40:26 -04:00
										 |  |  | 		type: 'Follow', | 
					
						
							|  |  |  | 		object: actor, | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | 	if (type === 'uid') { | 
					
						
							|  |  |  | 		object.actor = `${nconf.get('url')}/uid/${id}`; | 
					
						
							|  |  |  | 	} else if (type === 'cid') { | 
					
						
							|  |  |  | 		object.actor = `${nconf.get('url')}/category/${id}`; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	await activitypub.send(type, id, [actor], { | 
					
						
							| 
									
										
										
										
											2025-05-06 10:03:27 -04:00
										 |  |  | 		id: `${nconf.get('url')}/${type}/${id}#activity/undo:follow/${encodeURIComponent(actor)}/${timestamp}`, | 
					
						
							| 
									
										
										
										
											2023-12-22 15:53:04 -05:00
										 |  |  | 		type: 'Undo', | 
					
						
							| 
									
										
										
										
											2024-04-15 14:40:26 -04:00
										 |  |  | 		object, | 
					
						
							| 
									
										
										
										
											2023-12-08 10:55:16 -05:00
										 |  |  | 	}); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-15 14:40:26 -04:00
										 |  |  | 	if (type === 'uid') { | 
					
						
							|  |  |  | 		await Promise.all([ | 
					
						
							|  |  |  | 			db.sortedSetRemove(`followingRemote:${id}`, actor), | 
					
						
							| 
									
										
										
										
											2024-12-11 13:27:34 -05:00
										 |  |  | 			db.sortedSetRemove(`followRequests:uid.${id}`, actor), | 
					
						
							| 
									
										
										
										
											2024-04-15 14:40:26 -04:00
										 |  |  | 			db.decrObjectField(`user:${id}`, 'followingRemoteCount'), | 
					
						
							|  |  |  | 		]); | 
					
						
							|  |  |  | 	} else if (type === 'cid') { | 
					
						
							|  |  |  | 		await Promise.all([ | 
					
						
							|  |  |  | 			db.sortedSetRemove(`cid:${id}:following`, actor), | 
					
						
							|  |  |  | 			db.sortedSetRemove(`followRequests:cid.${id}`, actor), | 
					
						
							| 
									
										
										
										
											2024-07-04 17:54:04 -04:00
										 |  |  | 			db.sortedSetRemove(`followersRemote:${actor}`, `cid|${id}`), | 
					
						
							| 
									
										
										
										
											2024-04-15 14:40:26 -04:00
										 |  |  | 		]); | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-02-26 15:39:09 -05:00
										 |  |  | }); | 
					
						
							| 
									
										
										
										
											2024-01-24 11:44:10 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  | activitypubApi.create = {}; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-05 10:42:44 -04:00
										 |  |  | activitypubApi.create.note = enabledCheck(async (caller, { pid, post }) => { | 
					
						
							| 
									
										
										
										
											2024-01-25 15:35:45 -05:00
										 |  |  | 	if (!post) { | 
					
						
							| 
									
										
										
										
											2024-09-05 10:42:44 -04:00
										 |  |  | 		post = (await posts.getPostSummaryByPids([pid], caller.uid, { stripTags: false })).pop(); | 
					
						
							|  |  |  | 		if (!post) { | 
					
						
							|  |  |  | 			return; | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2024-09-06 22:04:20 -04:00
										 |  |  | 	} else { | 
					
						
							|  |  |  | 		pid = post.pid; | 
					
						
							| 
									
										
										
										
											2024-01-25 15:35:45 -05:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-08 20:45:54 -05:00
										 |  |  | 	const allowed = await privileges.posts.can('topics:read', pid, activitypub._constants.uid); | 
					
						
							| 
									
										
										
										
											2024-02-26 15:39:09 -05:00
										 |  |  | 	if (!allowed) { | 
					
						
							| 
									
										
										
										
											2024-10-12 22:49:24 -04:00
										 |  |  | 		activitypub.helpers.log(`[activitypub/api] Not federating creation of pid ${pid} to the fediverse due to privileges.`); | 
					
						
							| 
									
										
										
										
											2024-02-26 15:39:09 -05:00
										 |  |  | 		return; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-19 14:01:05 -04:00
										 |  |  | 	const { activity, targets } = await activitypub.mocks.activities.create(pid, caller.uid, post); | 
					
						
							| 
									
										
										
										
											2024-06-14 13:17:13 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-07 08:21:41 -05:00
										 |  |  | 	await Promise.all([ | 
					
						
							| 
									
										
										
										
											2025-03-11 12:27:26 -04:00
										 |  |  | 		activitypub.send('uid', caller.uid, Array.from(targets), activity), | 
					
						
							|  |  |  | 		activitypub.feps.announce(pid, activity), | 
					
						
							| 
									
										
										
										
											2025-03-21 14:16:33 -04:00
										 |  |  | 		// utils.isNumber(post.cid) ? activitypubApi.add(caller, { pid }) : undefined,
 | 
					
						
							| 
									
										
										
										
											2025-02-07 08:21:41 -05:00
										 |  |  | 	]); | 
					
						
							| 
									
										
										
										
											2024-02-26 15:39:09 -05:00
										 |  |  | }); | 
					
						
							| 
									
										
										
										
											2024-01-24 20:10:22 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-10-07 14:39:55 -04:00
										 |  |  | activitypubApi.create.privateNote = enabledCheck(async (caller, { messageObj }) => { | 
					
						
							| 
									
										
										
										
											2024-10-07 14:09:34 -04:00
										 |  |  | 	const { roomId } = messageObj; | 
					
						
							|  |  |  | 	let targets = await messaging.getUidsInRoom(roomId, 0, -1); | 
					
						
							|  |  |  | 	targets = targets.filter(uid => !utils.isNumber(uid)); // remote uids only
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-10-07 14:29:45 -04:00
										 |  |  | 	const object = await activitypub.mocks.notes.private({ messageObj }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	const payload = { | 
					
						
							|  |  |  | 		id: `${object.id}#activity/create/${Date.now()}`, | 
					
						
							|  |  |  | 		type: 'Create', | 
					
						
							| 
									
										
										
										
											2025-02-07 08:20:27 -05:00
										 |  |  | 		actor: object.attributedTo, | 
					
						
							| 
									
										
										
										
											2024-10-07 14:29:45 -04:00
										 |  |  | 		to: object.to, | 
					
						
							|  |  |  | 		object, | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-10-07 14:09:34 -04:00
										 |  |  | 	await activitypub.send('uid', messageObj.fromuid, targets, payload); | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-24 20:10:22 -05:00
										 |  |  | activitypubApi.update = {}; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-26 15:39:09 -05:00
										 |  |  | activitypubApi.update.profile = enabledCheck(async (caller, { uid }) => { | 
					
						
							| 
									
										
										
										
											2024-06-25 11:38:13 -04:00
										 |  |  | 	const [object, targets] = await Promise.all([ | 
					
						
							| 
									
										
										
										
											2024-02-02 17:19:59 -05:00
										 |  |  | 		activitypub.mocks.actors.user(uid), | 
					
						
							| 
									
										
										
										
											2024-07-05 11:14:41 -04:00
										 |  |  | 		db.getSortedSetMembers(`followersRemote:${caller.uid}`), | 
					
						
							| 
									
										
										
										
											2024-01-24 20:10:22 -05:00
										 |  |  | 	]); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-25 11:38:13 -04:00
										 |  |  | 	await activitypub.send('uid', caller.uid, targets, { | 
					
						
							|  |  |  | 		id: `${object.id}#activity/update/${Date.now()}`, | 
					
						
							|  |  |  | 		type: 'Update', | 
					
						
							| 
									
										
										
										
											2025-02-07 08:20:27 -05:00
										 |  |  | 		actor: object.id, | 
					
						
							| 
									
										
										
										
											2024-06-25 11:38:13 -04:00
										 |  |  | 		to: [activitypub._constants.publicAddress], | 
					
						
							|  |  |  | 		cc: [], | 
					
						
							|  |  |  | 		object, | 
					
						
							|  |  |  | 	}); | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | activitypubApi.update.category = enabledCheck(async (caller, { cid }) => { | 
					
						
							|  |  |  | 	const [object, targets] = await Promise.all([ | 
					
						
							|  |  |  | 		activitypub.mocks.actors.category(cid), | 
					
						
							| 
									
										
										
										
											2024-07-05 11:14:41 -04:00
										 |  |  | 		activitypub.notes.getCategoryFollowers(cid), | 
					
						
							| 
									
										
										
										
											2024-06-25 11:38:13 -04:00
										 |  |  | 	]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	await activitypub.send('cid', cid, targets, { | 
					
						
							| 
									
										
										
										
											2024-04-12 16:43:33 +02:00
										 |  |  | 		id: `${object.id}#activity/update/${Date.now()}`, | 
					
						
							| 
									
										
										
										
											2024-01-24 20:10:22 -05:00
										 |  |  | 		type: 'Update', | 
					
						
							| 
									
										
										
										
											2025-02-07 08:20:27 -05:00
										 |  |  | 		actor: object.id, | 
					
						
							| 
									
										
										
										
											2024-01-24 20:10:22 -05:00
										 |  |  | 		to: [activitypub._constants.publicAddress], | 
					
						
							|  |  |  | 		cc: [], | 
					
						
							|  |  |  | 		object, | 
					
						
							|  |  |  | 	}); | 
					
						
							| 
									
										
										
										
											2024-02-26 15:39:09 -05:00
										 |  |  | }); | 
					
						
							| 
									
										
										
										
											2024-01-30 11:25:45 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-06 01:18:46 +02:00
										 |  |  | activitypubApi.update.note = enabledCheck(async (caller, { post }) => { | 
					
						
							| 
									
										
										
										
											2024-05-07 12:43:27 -04:00
										 |  |  | 	// Only applies to local posts
 | 
					
						
							|  |  |  | 	if (!utils.isNumber(post.pid)) { | 
					
						
							|  |  |  | 		return; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-10-07 14:09:34 -04:00
										 |  |  | 	const object = await activitypub.mocks.notes.public(post); | 
					
						
							| 
									
										
										
										
											2024-10-23 11:28:55 -04:00
										 |  |  | 	const { to, cc, targets } = await activitypub.buildRecipients(object, { pid: post.pid, uid: post.user.uid }); | 
					
						
							|  |  |  | 	object.to = to; | 
					
						
							|  |  |  | 	object.cc = cc; | 
					
						
							| 
									
										
										
										
											2024-01-30 11:25:45 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-26 15:39:09 -05:00
										 |  |  | 	const allowed = await privileges.posts.can('topics:read', post.pid, activitypub._constants.uid); | 
					
						
							|  |  |  | 	if (!allowed) { | 
					
						
							| 
									
										
										
										
											2024-10-12 22:49:24 -04:00
										 |  |  | 		activitypub.helpers.log(`[activitypub/api] Not federating update of pid ${post.pid} to the fediverse due to privileges.`); | 
					
						
							| 
									
										
										
										
											2024-02-26 15:39:09 -05:00
										 |  |  | 		return; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-30 11:25:45 -05:00
										 |  |  | 	const payload = { | 
					
						
							| 
									
										
										
										
											2024-05-07 12:37:57 -04:00
										 |  |  | 		id: `${object.id}#activity/update/${post.edited || Date.now()}`, | 
					
						
							| 
									
										
										
										
											2024-01-30 11:25:45 -05:00
										 |  |  | 		type: 'Update', | 
					
						
							| 
									
										
										
										
											2025-02-07 08:20:27 -05:00
										 |  |  | 		actor: object.attributedTo, | 
					
						
							| 
									
										
										
										
											2024-05-10 15:30:45 -04:00
										 |  |  | 		to, | 
					
						
							|  |  |  | 		cc, | 
					
						
							| 
									
										
										
										
											2024-01-30 11:25:45 -05:00
										 |  |  | 		object, | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-07 08:21:41 -05:00
										 |  |  | 	await Promise.all([ | 
					
						
							|  |  |  | 		activitypub.send('uid', caller.uid, Array.from(targets), payload), | 
					
						
							|  |  |  | 		activitypub.feps.announce(post.pid, payload), | 
					
						
							|  |  |  | 	]); | 
					
						
							| 
									
										
										
										
											2024-02-26 15:39:09 -05:00
										 |  |  | }); | 
					
						
							| 
									
										
										
										
											2024-02-01 15:59:29 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-10-10 13:04:07 -04:00
										 |  |  | activitypubApi.update.privateNote = enabledCheck(async (caller, { messageObj }) => { | 
					
						
							| 
									
										
										
										
											2024-10-17 11:05:27 -04:00
										 |  |  | 	if (!utils.isNumber(messageObj.mid)) { | 
					
						
							|  |  |  | 		return; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-10-10 13:04:07 -04:00
										 |  |  | 	const { roomId } = messageObj; | 
					
						
							| 
									
										
										
										
											2024-10-17 11:05:27 -04:00
										 |  |  | 	let uids = await messaging.getUidsInRoom(roomId, 0, -1); | 
					
						
							|  |  |  | 	uids = uids.filter(uid => String(uid) !== String(messageObj.fromuid)); // no author
 | 
					
						
							|  |  |  | 	const to = uids.map(uid => (utils.isNumber(uid) ? `${nconf.get('url')}/uid/${uid}` : uid)); | 
					
						
							|  |  |  | 	const targets = uids.filter(uid => !utils.isNumber(uid)); // remote uids only
 | 
					
						
							| 
									
										
										
										
											2024-10-10 13:04:07 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	const object = await activitypub.mocks.notes.private({ messageObj }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	const payload = { | 
					
						
							|  |  |  | 		id: `${object.id}#activity/create/${Date.now()}`, | 
					
						
							|  |  |  | 		type: 'Update', | 
					
						
							| 
									
										
										
										
											2025-02-07 08:20:27 -05:00
										 |  |  | 		actor: object.attributedTo, | 
					
						
							| 
									
										
										
										
											2024-10-17 11:05:27 -04:00
										 |  |  | 		to, | 
					
						
							| 
									
										
										
										
											2024-10-10 13:04:07 -04:00
										 |  |  | 		object, | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-10-15 11:27:54 -04:00
										 |  |  | 	await activitypub.send('uid', caller.uid, targets, payload); | 
					
						
							| 
									
										
										
										
											2024-10-10 13:04:07 -04:00
										 |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-09 15:48:58 -04:00
										 |  |  | activitypubApi.delete = {}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | activitypubApi.delete.note = enabledCheck(async (caller, { pid }) => { | 
					
						
							|  |  |  | 	// Only applies to local posts
 | 
					
						
							|  |  |  | 	if (!utils.isNumber(pid)) { | 
					
						
							|  |  |  | 		return; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	const id = `${nconf.get('url')}/post/${pid}`; | 
					
						
							| 
									
										
										
										
											2024-05-10 15:30:45 -04:00
										 |  |  | 	const post = (await posts.getPostSummaryByPids([pid], caller.uid, { stripTags: false })).pop(); | 
					
						
							| 
									
										
										
										
											2024-10-07 14:09:34 -04:00
										 |  |  | 	const object = await activitypub.mocks.notes.public(post); | 
					
						
							| 
									
										
										
										
											2024-10-23 11:28:55 -04:00
										 |  |  | 	const { to, cc, targets } = await activitypub.buildRecipients(object, { pid, uid: post.user.uid }); | 
					
						
							| 
									
										
										
										
											2024-05-09 15:48:58 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	const allowed = await privileges.posts.can('topics:read', pid, activitypub._constants.uid); | 
					
						
							|  |  |  | 	if (!allowed) { | 
					
						
							| 
									
										
										
										
											2024-10-12 22:49:24 -04:00
										 |  |  | 		activitypub.helpers.log(`[activitypub/api] Not federating update of pid ${pid} to the fediverse due to privileges.`); | 
					
						
							| 
									
										
										
										
											2024-05-09 15:48:58 -04:00
										 |  |  | 		return; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	const payload = { | 
					
						
							|  |  |  | 		id: `${id}#activity/delete/${Date.now()}`, | 
					
						
							|  |  |  | 		type: 'Delete', | 
					
						
							| 
									
										
										
										
											2025-02-07 08:20:27 -05:00
										 |  |  | 		actor: object.attributedTo, | 
					
						
							| 
									
										
										
										
											2024-05-10 15:30:45 -04:00
										 |  |  | 		to, | 
					
						
							|  |  |  | 		cc, | 
					
						
							| 
									
										
										
										
											2024-05-09 15:48:58 -04:00
										 |  |  | 		object: id, | 
					
						
							| 
									
										
										
										
											2024-05-10 17:00:27 -04:00
										 |  |  | 		origin: object.context, | 
					
						
							| 
									
										
										
										
											2024-05-09 15:48:58 -04:00
										 |  |  | 	}; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-07 08:21:41 -05:00
										 |  |  | 	await Promise.all([ | 
					
						
							|  |  |  | 		activitypub.send('uid', caller.uid, Array.from(targets), payload), | 
					
						
							|  |  |  | 		activitypub.feps.announce(pid, payload), | 
					
						
							|  |  |  | 	]); | 
					
						
							| 
									
										
										
										
											2024-05-09 15:48:58 -04:00
										 |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-01 15:59:29 -05:00
										 |  |  | activitypubApi.like = {}; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-26 15:39:09 -05:00
										 |  |  | activitypubApi.like.note = enabledCheck(async (caller, { pid }) => { | 
					
						
							| 
									
										
										
										
											2025-05-12 14:59:57 -04:00
										 |  |  | 	const payload = { | 
					
						
							|  |  |  | 		id: `${nconf.get('url')}/uid/${caller.uid}#activity/like/${encodeURIComponent(pid)}`, | 
					
						
							|  |  |  | 		type: 'Like', | 
					
						
							|  |  |  | 		actor: `${nconf.get('url')}/uid/${caller.uid}`, | 
					
						
							|  |  |  | 		object: utils.isNumber(pid) ? `${nconf.get('url')}/post/${pid}` : pid, | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (!activitypub.helpers.isUri(pid)) { // only 1b12 announce for local likes
 | 
					
						
							|  |  |  | 		await activitypub.feps.announce(pid, payload); | 
					
						
							| 
									
										
										
										
											2024-02-01 15:59:29 -05:00
										 |  |  | 		return; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	const uid = await posts.getPostField(pid, 'uid'); | 
					
						
							|  |  |  | 	if (!activitypub.helpers.isUri(uid)) { | 
					
						
							|  |  |  | 		return; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-07 08:21:41 -05:00
										 |  |  | 	await Promise.all([ | 
					
						
							|  |  |  | 		activitypub.send('uid', caller.uid, [uid], payload), | 
					
						
							|  |  |  | 		activitypub.feps.announce(pid, payload), | 
					
						
							|  |  |  | 	]); | 
					
						
							| 
									
										
										
										
											2024-02-26 15:39:09 -05:00
										 |  |  | }); | 
					
						
							| 
									
										
										
										
											2024-02-01 15:59:29 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-22 14:45:17 -04:00
										 |  |  | activitypubApi.announce = {}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | activitypubApi.announce.note = enabledCheck(async (caller, { tid }) => { | 
					
						
							|  |  |  | 	const { mainPid: pid, cid } = await topics.getTopicFields(tid, ['mainPid', 'cid']); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-22 15:19:24 -04:00
										 |  |  | 	// Only remote posts can be announced to real categories
 | 
					
						
							|  |  |  | 	if (utils.isNumber(pid) || parseInt(cid, 10) === -1) { | 
					
						
							| 
									
										
										
										
											2024-08-22 14:45:17 -04:00
										 |  |  | 		return; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	const uid = await posts.getPostField(pid, 'uid'); // author
 | 
					
						
							|  |  |  | 	const allowed = await privileges.posts.can('topics:read', pid, activitypub._constants.uid); | 
					
						
							|  |  |  | 	if (!allowed) { | 
					
						
							| 
									
										
										
										
											2024-10-12 22:49:24 -04:00
										 |  |  | 		activitypub.helpers.log(`[activitypub/api] Not federating announce of pid ${pid} to the fediverse due to privileges.`); | 
					
						
							| 
									
										
										
										
											2024-08-22 14:45:17 -04:00
										 |  |  | 		return; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-10-23 11:28:55 -04:00
										 |  |  | 	const { to, cc, targets } = await activitypub.buildRecipients({ | 
					
						
							| 
									
										
										
										
											2024-09-18 14:35:42 -04:00
										 |  |  | 		id: pid, | 
					
						
							| 
									
										
										
										
											2024-09-06 14:35:57 -04:00
										 |  |  | 		to: [activitypub._constants.publicAddress], | 
					
						
							|  |  |  | 		cc: [`${nconf.get('url')}/uid/${caller.uid}/followers`, uid], | 
					
						
							| 
									
										
										
										
											2024-08-22 14:45:17 -04:00
										 |  |  | 	}, { uid: caller.uid }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	await activitypub.send('uid', caller.uid, Array.from(targets), { | 
					
						
							|  |  |  | 		id: `${nconf.get('url')}/post/${encodeURIComponent(pid)}#activity/announce/${Date.now()}`, | 
					
						
							|  |  |  | 		type: 'Announce', | 
					
						
							| 
									
										
										
										
											2025-02-07 08:20:27 -05:00
										 |  |  | 		actor: `${nconf.get('url')}/uid/${caller.uid}`, | 
					
						
							| 
									
										
										
										
											2024-08-22 14:45:17 -04:00
										 |  |  | 		to, | 
					
						
							|  |  |  | 		cc, | 
					
						
							|  |  |  | 		object: pid, | 
					
						
							|  |  |  | 		target: `${nconf.get('url')}/category/${cid}`, | 
					
						
							|  |  |  | 	}); | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-01 15:59:29 -05:00
										 |  |  | activitypubApi.undo = {}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // activitypubApi.undo.follow =
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-26 15:39:09 -05:00
										 |  |  | activitypubApi.undo.like = enabledCheck(async (caller, { pid }) => { | 
					
						
							| 
									
										
										
										
											2024-02-01 15:59:29 -05:00
										 |  |  | 	if (!activitypub.helpers.isUri(pid)) { | 
					
						
							|  |  |  | 		return; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	const uid = await posts.getPostField(pid, 'uid'); | 
					
						
							|  |  |  | 	if (!activitypub.helpers.isUri(uid)) { | 
					
						
							|  |  |  | 		return; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-07 08:21:41 -05:00
										 |  |  | 	const payload = { | 
					
						
							| 
									
										
										
										
											2024-06-11 22:55:45 +02:00
										 |  |  | 		id: `${nconf.get('url')}/uid/${caller.uid}#activity/undo:like/${encodeURIComponent(pid)}/${Date.now()}`, | 
					
						
							| 
									
										
										
										
											2024-02-01 15:59:29 -05:00
										 |  |  | 		type: 'Undo', | 
					
						
							| 
									
										
										
										
											2025-02-07 08:21:41 -05:00
										 |  |  | 		actor: `${nconf.get('url')}/uid/${caller.uid}`, | 
					
						
							| 
									
										
										
										
											2024-02-01 15:59:29 -05:00
										 |  |  | 		object: { | 
					
						
							|  |  |  | 			actor: `${nconf.get('url')}/uid/${caller.uid}`, | 
					
						
							| 
									
										
										
										
											2024-04-22 11:40:31 +02:00
										 |  |  | 			id: `${nconf.get('url')}/uid/${caller.uid}#activity/like/${encodeURIComponent(pid)}`, | 
					
						
							| 
									
										
										
										
											2024-02-01 15:59:29 -05:00
										 |  |  | 			type: 'Like', | 
					
						
							|  |  |  | 			object: pid, | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2025-02-07 08:21:41 -05:00
										 |  |  | 	}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	await Promise.all([ | 
					
						
							|  |  |  | 		activitypub.send('uid', caller.uid, [uid], payload), | 
					
						
							|  |  |  | 		activitypub.feps.announce(pid, payload), | 
					
						
							|  |  |  | 	]); | 
					
						
							| 
									
										
										
										
											2024-02-26 15:39:09 -05:00
										 |  |  | }); | 
					
						
							| 
									
										
										
										
											2024-04-14 00:51:53 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | activitypubApi.flag = enabledCheck(async (caller, flag) => { | 
					
						
							|  |  |  | 	if (!activitypub.helpers.isUri(flag.targetId)) { | 
					
						
							|  |  |  | 		return; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	const reportedIds = [flag.targetId]; | 
					
						
							|  |  |  | 	if (flag.type === 'post' && activitypub.helpers.isUri(flag.targetUid)) { | 
					
						
							|  |  |  | 		reportedIds.push(flag.targetUid); | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-04-14 01:52:07 +02:00
										 |  |  | 	const reason = flag.reason || | 
					
						
							|  |  |  | 		(flag.reports && flag.reports.filter(report => report.reporter.uid === caller.uid).at(-1).value); | 
					
						
							| 
									
										
										
										
											2024-04-14 00:51:53 +02:00
										 |  |  | 	await activitypub.send('uid', caller.uid, reportedIds, { | 
					
						
							| 
									
										
										
										
											2024-04-14 01:52:07 +02:00
										 |  |  | 		id: `${nconf.get('url')}/${flag.type}/${encodeURIComponent(flag.targetId)}#activity/flag/${caller.uid}`, | 
					
						
							| 
									
										
										
										
											2024-04-14 00:51:53 +02:00
										 |  |  | 		type: 'Flag', | 
					
						
							| 
									
										
										
										
											2025-02-07 08:20:27 -05:00
										 |  |  | 		actor: `${nconf.get('url')}/uid/${caller.uid}`, | 
					
						
							| 
									
										
										
										
											2024-04-14 00:51:53 +02:00
										 |  |  | 		object: reportedIds, | 
					
						
							| 
									
										
										
										
											2024-04-14 01:52:07 +02:00
										 |  |  | 		content: reason, | 
					
						
							| 
									
										
										
										
											2024-04-14 00:51:53 +02:00
										 |  |  | 	}); | 
					
						
							| 
									
										
										
										
											2024-04-14 01:52:07 +02:00
										 |  |  | 	await db.sortedSetAdd(`flag:${flag.flagId}:remote`, Date.now(), caller.uid); | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-28 16:06:38 -04:00
										 |  |  | /* | 
					
						
							|  |  |  | activitypubApi.add = enabledCheck((async (_, { pid }) => { | 
					
						
							|  |  |  | 	let localId; | 
					
						
							|  |  |  | 	if (String(pid).startsWith(nconf.get('url'))) { | 
					
						
							|  |  |  | 		({ id: localId } = await activitypub.helpers.resolveLocalId(pid)); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	const tid = await posts.getPostField(localId || pid, 'tid'); | 
					
						
							|  |  |  | 	const cid = await posts.getCidByPid(localId || pid); | 
					
						
							|  |  |  | 	if (!utils.isNumber(tid) || cid <= 0) { // `Add` only federated on categorized topics started locally
 | 
					
						
							|  |  |  | 		return; | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-10-03 11:10:21 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-28 16:06:38 -04:00
										 |  |  | 	let to = [activitypub._constants.publicAddress]; | 
					
						
							|  |  |  | 	let cc = []; | 
					
						
							|  |  |  | 	let targets; | 
					
						
							|  |  |  | 	({ to, cc, targets } = await activitypub.buildRecipients({ to, cc }, { pid: localId || pid, cid })); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	await activitypub.send('cid', cid, Array.from(targets), { | 
					
						
							|  |  |  | 		id: `${nconf.get('url')}/post/${encodeURIComponent(localId || pid)}#activity/add/${Date.now()}`, | 
					
						
							|  |  |  | 		type: 'Add', | 
					
						
							|  |  |  | 		to, | 
					
						
							|  |  |  | 		cc, | 
					
						
							|  |  |  | 		object: utils.isNumber(pid) ? `${nconf.get('url')}/post/${pid}` : pid, | 
					
						
							|  |  |  | 		target: `${nconf.get('url')}/topic/${tid}`, | 
					
						
							|  |  |  | 	}); | 
					
						
							|  |  |  | })); | 
					
						
							|  |  |  | */ | 
					
						
							| 
									
										
										
										
											2024-04-14 01:52:07 +02:00
										 |  |  | activitypubApi.undo.flag = enabledCheck(async (caller, flag) => { | 
					
						
							|  |  |  | 	if (!activitypub.helpers.isUri(flag.targetId)) { | 
					
						
							|  |  |  | 		return; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	const reportedIds = [flag.targetId]; | 
					
						
							|  |  |  | 	if (flag.type === 'post' && activitypub.helpers.isUri(flag.targetUid)) { | 
					
						
							|  |  |  | 		reportedIds.push(flag.targetUid); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	const reason = flag.reason || | 
					
						
							|  |  |  | 		(flag.reports && flag.reports.filter(report => report.reporter.uid === caller.uid).at(-1).value); | 
					
						
							|  |  |  | 	await activitypub.send('uid', caller.uid, reportedIds, { | 
					
						
							| 
									
										
										
										
											2024-06-11 22:55:45 +02:00
										 |  |  | 		id: `${nconf.get('url')}/${flag.type}/${encodeURIComponent(flag.targetId)}#activity/undo:flag/${caller.uid}/${Date.now()}`, | 
					
						
							| 
									
										
										
										
											2024-04-14 01:52:07 +02:00
										 |  |  | 		type: 'Undo', | 
					
						
							| 
									
										
										
										
											2025-02-07 08:20:27 -05:00
										 |  |  | 		actor: `${nconf.get('url')}/uid/${caller.uid}`, | 
					
						
							| 
									
										
										
										
											2024-04-14 01:52:07 +02:00
										 |  |  | 		object: { | 
					
						
							|  |  |  | 			id: `${nconf.get('url')}/${flag.type}/${encodeURIComponent(flag.targetId)}#activity/flag/${caller.uid}`, | 
					
						
							|  |  |  | 			actor: `${nconf.get('url')}/uid/${caller.uid}`, | 
					
						
							|  |  |  | 			type: 'Flag', | 
					
						
							|  |  |  | 			object: reportedIds, | 
					
						
							|  |  |  | 			content: reason, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	}); | 
					
						
							|  |  |  | 	await db.sortedSetRemove(`flag:${flag.flagId}:remote`, caller.uid); | 
					
						
							| 
									
										
										
										
											2024-04-14 00:51:53 +02:00
										 |  |  | }); |