| 
									
										
										
										
											2024-01-12 15:23:30 -05:00
										 |  |  | 'use strict'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const winston = require('winston'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const db = require('../database'); | 
					
						
							| 
									
										
										
										
											2024-01-17 11:54:20 -05:00
										 |  |  | const topics = require('../topics'); | 
					
						
							| 
									
										
										
										
											2024-01-12 15:23:30 -05:00
										 |  |  | const posts = require('../posts'); | 
					
						
							| 
									
										
										
										
											2024-01-18 16:20:37 -05:00
										 |  |  | const utils = require('../utils'); | 
					
						
							| 
									
										
										
										
											2024-01-24 14:35:21 -05:00
										 |  |  | const pubsub = require('../pubsub'); | 
					
						
							| 
									
										
										
										
											2024-02-07 12:28:16 -05:00
										 |  |  | const slugify = require('../slugify'); | 
					
						
							| 
									
										
										
										
											2024-01-12 15:23:30 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  | const activitypub = module.parent.exports; | 
					
						
							|  |  |  | const Notes = module.exports; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-16 10:44:47 -05:00
										 |  |  | Notes.resolveId = async (uid, id) => { | 
					
						
							| 
									
										
										
										
											2024-02-05 16:57:17 -05:00
										 |  |  | 	({ id } = await activitypub.get('uid', uid, id)); | 
					
						
							| 
									
										
										
										
											2024-01-16 10:44:47 -05:00
										 |  |  | 	return id; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-12 15:23:30 -05:00
										 |  |  | // todo: when asserted, notes aren't added to a global sorted set
 | 
					
						
							|  |  |  | // also, db.exists call is probably expensive
 | 
					
						
							| 
									
										
										
										
											2024-01-16 13:55:58 -05:00
										 |  |  | Notes.assert = async (uid, input, options = {}) => { | 
					
						
							| 
									
										
										
										
											2024-01-12 15:23:30 -05:00
										 |  |  | 	// Ensures that each note has been saved to the database
 | 
					
						
							|  |  |  | 	await Promise.all(input.map(async (item) => { | 
					
						
							| 
									
										
										
										
											2024-01-16 10:44:47 -05:00
										 |  |  | 		const id = activitypub.helpers.isUri(item) ? item : item.pid; | 
					
						
							| 
									
										
										
										
											2024-01-12 15:23:30 -05:00
										 |  |  | 		const key = `post:${id}`; | 
					
						
							| 
									
										
										
										
											2024-01-24 14:35:21 -05:00
										 |  |  | 		const exists = await db.exists(key); | 
					
						
							| 
									
										
										
										
											2024-01-12 15:23:30 -05:00
										 |  |  | 		winston.verbose(`[activitypub/notes.assert] Asserting note id ${id}`); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-24 14:35:21 -05:00
										 |  |  | 		if (!exists || options.update === true) { | 
					
						
							| 
									
										
										
										
											2024-01-16 10:44:47 -05:00
										 |  |  | 			let postData; | 
					
						
							| 
									
										
										
										
											2024-02-09 11:31:42 -05:00
										 |  |  | 			winston.verbose(`[activitypub/notes.assert] Not found, retrieving note for persistence...`); | 
					
						
							| 
									
										
										
										
											2024-01-16 10:44:47 -05:00
										 |  |  | 			if (activitypub.helpers.isUri(item)) { | 
					
						
							| 
									
										
										
										
											2024-02-09 11:31:42 -05:00
										 |  |  | 				// get failure throws for now but should save intermediate object
 | 
					
						
							| 
									
										
										
										
											2024-02-05 16:57:17 -05:00
										 |  |  | 				const object = await activitypub.get('uid', uid, item); | 
					
						
							| 
									
										
										
										
											2024-01-16 10:44:47 -05:00
										 |  |  | 				postData = await activitypub.mocks.post(object); | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				postData = item; | 
					
						
							| 
									
										
										
										
											2024-01-12 15:23:30 -05:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2024-01-16 10:44:47 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			await db.setObject(key, postData); | 
					
						
							| 
									
										
										
										
											2024-02-09 11:31:42 -05:00
										 |  |  | 			winston.verbose(`[activitypub/notes.assert] Note ${id} saved.`); | 
					
						
							| 
									
										
										
										
											2024-01-12 15:23:30 -05:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2024-01-24 14:35:21 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		if (options.update === true) { | 
					
						
							|  |  |  | 			require('../posts/cache').del(String(id)); | 
					
						
							|  |  |  | 			pubsub.publish('post:edit', String(id)); | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2024-01-12 15:23:30 -05:00
										 |  |  | 	})); | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Notes.getParentChain = async (uid, input) => { | 
					
						
							|  |  |  | 	// Traverse upwards via `inReplyTo` until you find the root-level Note
 | 
					
						
							|  |  |  | 	const id = activitypub.helpers.isUri(input) ? input : input.id; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	const chain = new Set(); | 
					
						
							|  |  |  | 	const traverse = async (uid, id) => { | 
					
						
							| 
									
										
										
										
											2024-02-09 11:15:03 -05:00
										 |  |  | 		// Handle remote reference to local post
 | 
					
						
							|  |  |  | 		const { type, id: localId } = await activitypub.helpers.resolveLocalId(id); | 
					
						
							|  |  |  | 		if (type === 'post' && localId) { | 
					
						
							|  |  |  | 			return traverse(uid, localId); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-12 15:23:30 -05:00
										 |  |  | 		const exists = await db.exists(`post:${id}`); | 
					
						
							|  |  |  | 		if (exists) { | 
					
						
							| 
									
										
										
										
											2024-01-12 16:39:29 -05:00
										 |  |  | 			const postData = await posts.getPostData(id); | 
					
						
							|  |  |  | 			chain.add(postData); | 
					
						
							|  |  |  | 			if (postData.toPid) { | 
					
						
							|  |  |  | 				await traverse(uid, postData.toPid); | 
					
						
							| 
									
										
										
										
											2024-01-12 15:23:30 -05:00
										 |  |  | 			} | 
					
						
							|  |  |  | 		} else { | 
					
						
							| 
									
										
										
										
											2024-02-09 11:31:42 -05:00
										 |  |  | 			let object; | 
					
						
							|  |  |  | 			try { | 
					
						
							|  |  |  | 				object = await activitypub.get('uid', uid, id); | 
					
						
							|  |  |  | 			} catch (e) { | 
					
						
							|  |  |  | 				winston.warn(`[activitypub/notes/getParentChain] Cannot retrieve ${id}, terminating here.`); | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2024-02-08 11:33:27 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			// Handle incorrect id passed in
 | 
					
						
							|  |  |  | 			if (id !== object.id) { | 
					
						
							|  |  |  | 				return await traverse(uid, object.id); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-12 15:23:30 -05:00
										 |  |  | 			object = await activitypub.mocks.post(object); | 
					
						
							|  |  |  | 			if (object) { | 
					
						
							| 
									
										
										
										
											2024-01-12 16:39:29 -05:00
										 |  |  | 				chain.add(object); | 
					
						
							|  |  |  | 				if (object.toPid) { | 
					
						
							| 
									
										
										
										
											2024-01-12 15:23:30 -05:00
										 |  |  | 					await traverse(uid, object.toPid); | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	await traverse(uid, id); | 
					
						
							|  |  |  | 	return chain; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-19 11:31:04 -05:00
										 |  |  | Notes.assertParentChain = async (chain, tid) => { | 
					
						
							| 
									
										
										
										
											2024-01-16 10:44:47 -05:00
										 |  |  | 	const data = []; | 
					
						
							|  |  |  | 	chain.reduce((child, parent) => { | 
					
						
							|  |  |  | 		data.push([`pid:${parent.pid}:replies`, child.timestamp, child.pid]); | 
					
						
							|  |  |  | 		return parent; | 
					
						
							|  |  |  | 	}); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-19 11:31:04 -05:00
										 |  |  | 	await Promise.all([ | 
					
						
							|  |  |  | 		db.sortedSetAddBulk(data), | 
					
						
							|  |  |  | 		db.setObjectBulk(chain.map(post => [`post:${post.pid}`, { tid }])), | 
					
						
							|  |  |  | 	]); | 
					
						
							| 
									
										
										
										
											2024-01-16 10:44:47 -05:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-12 15:23:30 -05:00
										 |  |  | Notes.assertTopic = async (uid, id) => { | 
					
						
							| 
									
										
										
										
											2024-01-16 10:44:47 -05:00
										 |  |  | 	/** | 
					
						
							|  |  |  | 	 * Given the id of any post, traverses up to cache the entire threaded context | 
					
						
							|  |  |  | 	 * | 
					
						
							|  |  |  | 	 * Unfortunately, due to limitations and fragmentation of the existing ActivityPub landscape, | 
					
						
							|  |  |  | 	 * retrieving the entire reply tree is not possible at this time. | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-12 15:23:30 -05:00
										 |  |  | 	const chain = Array.from(await Notes.getParentChain(uid, id)); | 
					
						
							| 
									
										
										
										
											2024-01-19 11:31:04 -05:00
										 |  |  | 	let { pid: mainPid, tid, uid: authorId, timestamp, name, content } = chain[chain.length - 1]; | 
					
						
							| 
									
										
										
										
											2024-02-07 12:28:16 -05:00
										 |  |  | 	const members = await db.isSortedSetMembers(`tid:${tid}:posts`, chain.map(p => p.pid)); | 
					
						
							| 
									
										
										
										
											2024-01-19 11:31:04 -05:00
										 |  |  | 	if (tid && members.every(Boolean)) { | 
					
						
							| 
									
										
										
										
											2024-01-16 10:44:47 -05:00
										 |  |  | 		// All cached, return early.
 | 
					
						
							|  |  |  | 		winston.info('[notes/assertTopic] No new notes to process.'); | 
					
						
							|  |  |  | 		return tid; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-19 11:31:04 -05:00
										 |  |  | 	tid = tid || utils.generateUUID(); | 
					
						
							| 
									
										
										
										
											2024-01-17 11:54:20 -05:00
										 |  |  | 	const cid = await topics.getTopicField(tid, 'cid'); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-19 11:31:04 -05:00
										 |  |  | 	let title = name || utils.decodeHTMLEntities(utils.stripHTMLTags(content)); | 
					
						
							|  |  |  | 	if (title.length > 64) { | 
					
						
							|  |  |  | 		title = `${title.slice(0, 64)}...`; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-16 10:44:47 -05:00
										 |  |  | 	const unprocessed = chain.filter((p, idx) => !members[idx]); | 
					
						
							| 
									
										
										
										
											2024-01-17 11:47:57 -05:00
										 |  |  | 	winston.info(`[notes/assertTopic] ${unprocessed.length} new note(s) found.`); | 
					
						
							| 
									
										
										
										
											2024-01-16 10:44:47 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-12 15:23:30 -05:00
										 |  |  | 	const [ids, timestamps] = [ | 
					
						
							| 
									
										
										
										
											2024-01-16 10:44:47 -05:00
										 |  |  | 		unprocessed.map(n => n.pid), | 
					
						
							|  |  |  | 		unprocessed.map(n => n.timestamp), | 
					
						
							| 
									
										
										
										
											2024-01-12 15:23:30 -05:00
										 |  |  | 	]; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-07 12:28:16 -05:00
										 |  |  | 	// mainPid doesn't belong in posts zset
 | 
					
						
							|  |  |  | 	ids.pop(); | 
					
						
							|  |  |  | 	timestamps.pop(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-12 16:39:29 -05:00
										 |  |  | 	await Promise.all([ | 
					
						
							| 
									
										
										
										
											2024-02-07 12:28:16 -05:00
										 |  |  | 		db.setObject(`topic:${tid}`, { | 
					
						
							| 
									
										
										
										
											2024-01-12 16:39:29 -05:00
										 |  |  | 			tid, | 
					
						
							| 
									
										
										
										
											2024-01-16 12:00:40 -05:00
										 |  |  | 			uid: authorId, | 
					
						
							| 
									
										
										
										
											2024-01-17 11:54:20 -05:00
										 |  |  | 			cid: cid || -1, | 
					
						
							| 
									
										
										
										
											2024-01-19 11:31:04 -05:00
										 |  |  | 			mainPid, | 
					
						
							| 
									
										
										
										
											2024-01-18 16:20:37 -05:00
										 |  |  | 			title, | 
					
						
							| 
									
										
										
										
											2024-02-07 12:28:16 -05:00
										 |  |  | 			slug: `${tid}/${slugify(title)}`, | 
					
						
							| 
									
										
										
										
											2024-01-17 12:15:58 -05:00
										 |  |  | 			timestamp, | 
					
						
							| 
									
										
										
										
											2024-01-12 16:39:29 -05:00
										 |  |  | 		}), | 
					
						
							| 
									
										
										
										
											2024-02-07 12:28:16 -05:00
										 |  |  | 		db.sortedSetAdd(`tid:${tid}:posts`, timestamps, ids), | 
					
						
							| 
									
										
										
										
											2024-01-16 10:44:47 -05:00
										 |  |  | 		Notes.assert(uid, unprocessed), | 
					
						
							|  |  |  | 	]); | 
					
						
							|  |  |  | 	await Promise.all([ // must be done after .assert()
 | 
					
						
							| 
									
										
										
										
											2024-01-19 11:31:04 -05:00
										 |  |  | 		Notes.assertParentChain(chain, tid), | 
					
						
							| 
									
										
										
										
											2024-01-16 10:44:47 -05:00
										 |  |  | 		Notes.updateTopicCounts(tid), | 
					
						
							| 
									
										
										
										
											2024-01-17 12:15:58 -05:00
										 |  |  | 		topics.updateLastPostTimeFromLastPid(tid), | 
					
						
							|  |  |  | 		topics.updateTeaser(tid), | 
					
						
							| 
									
										
										
										
											2024-01-12 16:39:29 -05:00
										 |  |  | 	]); | 
					
						
							| 
									
										
										
										
											2024-01-12 15:23:30 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	return tid; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-16 10:44:47 -05:00
										 |  |  | Notes.updateTopicCounts = async function (tid) { | 
					
						
							| 
									
										
										
										
											2024-02-07 12:28:16 -05:00
										 |  |  | 	const pids = await db.getSortedSetMembers(`tid:${tid}:posts`); | 
					
						
							| 
									
										
										
										
											2024-01-16 10:44:47 -05:00
										 |  |  | 	let uids = await db.getObjectsFields(pids.map(p => `post:${p}`), ['uid']); | 
					
						
							|  |  |  | 	uids = uids.reduce((set, { uid }) => { | 
					
						
							|  |  |  | 		set.add(uid); | 
					
						
							|  |  |  | 		return set; | 
					
						
							|  |  |  | 	}, new Set()); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-07 12:28:16 -05:00
										 |  |  | 	db.setObject(`topic:${tid}`, { | 
					
						
							| 
									
										
										
										
											2024-01-16 10:44:47 -05:00
										 |  |  | 		postercount: uids.size, | 
					
						
							|  |  |  | 		postcount: pids.length, | 
					
						
							|  |  |  | 	}); | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-12 15:23:30 -05:00
										 |  |  | Notes.getTopicPosts = async (tid, uid, start, stop) => { | 
					
						
							| 
									
										
										
										
											2024-02-07 12:28:16 -05:00
										 |  |  | 	const pids = await db.getSortedSetRange(`tid:${tid}:posts`, start, stop); | 
					
						
							| 
									
										
										
										
											2024-01-12 15:23:30 -05:00
										 |  |  | 	return await posts.getPostsByPids(pids, uid); | 
					
						
							|  |  |  | }; |