mirror of
				https://github.com/NodeBB/NodeBB.git
				synced 2025-10-31 02:55:58 +01:00 
			
		
		
		
	fix: port the try/catch for notes.assert from develop
This commit is contained in:
		| @@ -63,212 +63,219 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { | ||||
| 		return null; | ||||
| 	} | ||||
|  | ||||
| 	if (!options.skipChecks) { | ||||
| 		id = (await activitypub.checkHeader(id)) || id; | ||||
| 	} | ||||
|  | ||||
| 	let chain; | ||||
| 	let context = await activitypub.contexts.get(uid, id); | ||||
| 	if (context.tid) { | ||||
| 		await unlock(id); | ||||
| 		const { tid } = context; | ||||
| 		return { tid, count: 0 }; | ||||
| 	} else if (context.context) { | ||||
| 		chain = Array.from(await activitypub.contexts.getItems(uid, context.context, { input })); | ||||
| 		if (chain && chain.length) { | ||||
| 			// Context resolves, use in later topic creation | ||||
| 			context = context.context; | ||||
| 		} | ||||
| 	} else { | ||||
| 		context = undefined; | ||||
| 	} | ||||
|  | ||||
| 	if (!chain || !chain.length) { | ||||
| 		// Fall back to inReplyTo traversal on context retrieval failure | ||||
| 		chain = Array.from(await Notes.getParentChain(uid, input)); | ||||
| 		chain.reverse(); | ||||
| 	} | ||||
|  | ||||
| 	// Can't resolve — give up. | ||||
| 	if (!chain.length) { | ||||
| 		await unlock(id); | ||||
| 		return null; | ||||
| 	} | ||||
|  | ||||
| 	// Reorder chain items by timestamp | ||||
| 	chain = chain.sort((a, b) => a.timestamp - b.timestamp); | ||||
|  | ||||
| 	const mainPost = chain[0]; | ||||
| 	let { pid: mainPid, tid, uid: authorId, timestamp, title, content, sourceContent, _activitypub } = mainPost; | ||||
| 	const hasTid = !!tid; | ||||
|  | ||||
| 	const cid = hasTid ? await topics.getTopicField(tid, 'cid') : options.cid || -1; | ||||
|  | ||||
| 	if (options.cid && cid === -1) { | ||||
| 		// Move topic if currently uncategorized | ||||
| 		await topics.tools.move(tid, { cid: options.cid, uid: 'system' }); | ||||
| 	} | ||||
|  | ||||
| 	const members = await db.isSortedSetMembers(`tid:${tid}:posts`, chain.slice(1).map(p => p.pid)); | ||||
| 	members.unshift(await posts.exists(mainPid)); | ||||
| 	if (tid && members.every(Boolean)) { | ||||
| 		// All cached, return early. | ||||
| 		activitypub.helpers.log('[notes/assert] No new notes to process.'); | ||||
| 		await unlock(id); | ||||
| 		return { tid, count: 0 }; | ||||
| 	} | ||||
|  | ||||
| 	if (hasTid) { | ||||
| 		mainPid = await topics.getTopicField(tid, 'mainPid'); | ||||
| 	} else { | ||||
| 		// Check recipients/audience for category (local or remote) | ||||
| 		const set = activitypub.helpers.makeSet(_activitypub, ['to', 'cc', 'audience']); | ||||
| 		await activitypub.actors.assert(Array.from(set)); | ||||
|  | ||||
| 		// Local | ||||
| 		const resolved = await Promise.all(Array.from(set).map(async id => await activitypub.helpers.resolveLocalId(id))); | ||||
| 		const recipientCids = resolved | ||||
| 			.filter(Boolean) | ||||
| 			.filter(({ type }) => type === 'category') | ||||
| 			.map(obj => obj.id); | ||||
|  | ||||
| 		// Remote | ||||
| 		let remoteCid; | ||||
| 		const assertedGroups = await categories.exists(Array.from(set)); | ||||
| 		try { | ||||
| 			const { hostname } = new URL(mainPid); | ||||
| 			remoteCid = Array.from(set).filter((id, idx) => { | ||||
| 				const { hostname: cidHostname } = new URL(id); | ||||
| 				return assertedGroups[idx] && cidHostname === hostname; | ||||
| 			}).shift(); | ||||
| 		} catch (e) { | ||||
| 			// noop | ||||
| 			winston.error('[activitypub/notes.assert] Could not parse URL of mainPid', e.stack); | ||||
| 	try { | ||||
| 		if (!options.skipChecks) { | ||||
| 			id = (await activitypub.checkHeader(id)) || id; | ||||
| 		} | ||||
|  | ||||
| 		if (remoteCid || recipientCids.length) { | ||||
| 			// Overrides passed-in value, respect addressing from main post over booster | ||||
| 			options.cid = remoteCid || recipientCids.shift(); | ||||
| 		let chain; | ||||
| 		let context = await activitypub.contexts.get(uid, id); | ||||
| 		if (context.tid) { | ||||
| 			await unlock(id); | ||||
| 			const { tid } = context; | ||||
| 			return { tid, count: 0 }; | ||||
| 		} else if (context.context) { | ||||
| 			chain = Array.from(await activitypub.contexts.getItems(uid, context.context, { input })); | ||||
| 			if (chain && chain.length) { | ||||
| 				// Context resolves, use in later topic creation | ||||
| 				context = context.context; | ||||
| 			} | ||||
| 		} else { | ||||
| 			context = undefined; | ||||
| 		} | ||||
|  | ||||
| 		// Auto-categorization (takes place only if all other categorization efforts fail) | ||||
| 		if (!options.cid) { | ||||
| 			options.cid = await assignCategory(mainPost); | ||||
| 		if (!chain || !chain.length) { | ||||
| 			// Fall back to inReplyTo traversal on context retrieval failure | ||||
| 			chain = Array.from(await Notes.getParentChain(uid, input)); | ||||
| 			chain.reverse(); | ||||
| 		} | ||||
|  | ||||
| 		// mainPid ok to leave as-is | ||||
| 		if (!title) { | ||||
| 			const sentences = tokenizer.sentences(content || sourceContent, { sanitize: true }); | ||||
| 			title = sentences.shift(); | ||||
| 		} | ||||
|  | ||||
| 		// Remove any custom emoji from title | ||||
| 		if (_activitypub && _activitypub.tag && Array.isArray(_activitypub.tag)) { | ||||
| 			_activitypub.tag | ||||
| 				.filter(tag => tag.type === 'Emoji') | ||||
| 				.forEach((tag) => { | ||||
| 					title = title.replace(new RegExp(tag.name, 'g'), ''); | ||||
| 				}); | ||||
| 		} | ||||
| 	} | ||||
| 	mainPid = utils.isNumber(mainPid) ? parseInt(mainPid, 10) : mainPid; | ||||
|  | ||||
| 	// Relation & privilege check for local categories | ||||
| 	const inputIndex = chain.map(n => n.pid).indexOf(id); | ||||
| 	const hasRelation = | ||||
| 		uid || hasTid || | ||||
| 		options.skipChecks || options.cid || | ||||
| 		await assertRelation(chain[inputIndex !== -1 ? inputIndex : 0]); | ||||
|  | ||||
| 	const privilege = `topics:${tid ? 'reply' : 'create'}`; | ||||
| 	const allowed = await privileges.categories.can(privilege, options.cid || cid, activitypub._constants.uid); | ||||
| 	if (!hasRelation || !allowed) { | ||||
| 		if (!hasRelation) { | ||||
| 			activitypub.helpers.log(`[activitypub/notes.assert] Not asserting ${id} as it has no relation to existing tracked content.`); | ||||
| 		} | ||||
|  | ||||
| 		await unlock(id); | ||||
| 		return null; | ||||
| 	} | ||||
|  | ||||
| 	tid = tid || utils.generateUUID(); | ||||
| 	mainPost.tid = tid; | ||||
|  | ||||
| 	const urlMap = chain.reduce((map, post) => (post.url ? map.set(post.url, post.id) : map), new Map()); | ||||
| 	const unprocessed = chain.map((post) => { | ||||
| 		post.tid = tid; // add tid to post hash | ||||
|  | ||||
| 		// Ensure toPids in replies are ids | ||||
| 		if (urlMap.has(post.toPid)) { | ||||
| 			post.toPid = urlMap.get(post.toPid); | ||||
| 		} | ||||
|  | ||||
| 		return post; | ||||
| 	}).filter((p, idx) => !members[idx]); | ||||
| 	const count = unprocessed.length; | ||||
| 	activitypub.helpers.log(`[notes/assert] ${count} new note(s) found.`); | ||||
|  | ||||
| 	if (!hasTid) { | ||||
| 		const { to, cc, attachment } = mainPost._activitypub; | ||||
| 		const tags = await Notes._normalizeTags(mainPost._activitypub.tag || []); | ||||
|  | ||||
| 		try { | ||||
| 			await topics.post({ | ||||
| 				tid, | ||||
| 				uid: authorId, | ||||
| 				cid: options.cid || cid, | ||||
| 				pid: mainPid, | ||||
| 				title, | ||||
| 				timestamp, | ||||
| 				tags, | ||||
| 				content: mainPost.content, | ||||
| 				sourceContent: mainPost.sourceContent, | ||||
| 				_activitypub: mainPost._activitypub, | ||||
| 			}); | ||||
| 			unprocessed.shift(); | ||||
| 		} catch (e) { | ||||
| 			activitypub.helpers.log(`[activitypub/notes.assert] Could not post topic (${mainPost.pid}): ${e.message}`); | ||||
| 		// Can't resolve — give up. | ||||
| 		if (!chain.length) { | ||||
| 			await unlock(id); | ||||
| 			return null; | ||||
| 		} | ||||
|  | ||||
| 		// These must come after topic is posted | ||||
| 		// Reorder chain items by timestamp | ||||
| 		chain = chain.sort((a, b) => a.timestamp - b.timestamp); | ||||
|  | ||||
| 		const mainPost = chain[0]; | ||||
| 		let { pid: mainPid, tid, uid: authorId, timestamp, title, content, sourceContent, _activitypub } = mainPost; | ||||
| 		const hasTid = !!tid; | ||||
|  | ||||
| 		const cid = hasTid ? await topics.getTopicField(tid, 'cid') : options.cid || -1; | ||||
|  | ||||
| 		if (options.cid && cid === -1) { | ||||
| 			// Move topic if currently uncategorized | ||||
| 			await topics.tools.move(tid, { cid: options.cid, uid: 'system' }); | ||||
| 		} | ||||
|  | ||||
| 		const members = await db.isSortedSetMembers(`tid:${tid}:posts`, chain.slice(1).map(p => p.pid)); | ||||
| 		members.unshift(await posts.exists(mainPid)); | ||||
| 		if (tid && members.every(Boolean)) { | ||||
| 			// All cached, return early. | ||||
| 			activitypub.helpers.log('[notes/assert] No new notes to process.'); | ||||
| 			await unlock(id); | ||||
| 			return { tid, count: 0 }; | ||||
| 		} | ||||
|  | ||||
| 		if (hasTid) { | ||||
| 			mainPid = await topics.getTopicField(tid, 'mainPid'); | ||||
| 		} else { | ||||
| 			// Check recipients/audience for category (local or remote) | ||||
| 			const set = activitypub.helpers.makeSet(_activitypub, ['to', 'cc', 'audience']); | ||||
| 			await activitypub.actors.assert(Array.from(set)); | ||||
|  | ||||
| 			// Local | ||||
| 			const resolved = await Promise.all(Array.from(set).map(async id => await activitypub.helpers.resolveLocalId(id))); | ||||
| 			const recipientCids = resolved | ||||
| 				.filter(Boolean) | ||||
| 				.filter(({ type }) => type === 'category') | ||||
| 				.map(obj => obj.id); | ||||
|  | ||||
| 			// Remote | ||||
| 			let remoteCid; | ||||
| 			const assertedGroups = await categories.exists(Array.from(set)); | ||||
| 			try { | ||||
| 				const { hostname } = new URL(mainPid); | ||||
| 				remoteCid = Array.from(set).filter((id, idx) => { | ||||
| 					const { hostname: cidHostname } = new URL(id); | ||||
| 					return assertedGroups[idx] && cidHostname === hostname; | ||||
| 				}).shift(); | ||||
| 			} catch (e) { | ||||
| 				// noop | ||||
| 				winston.error('[activitypub/notes.assert] Could not parse URL of mainPid', e.stack); | ||||
| 			} | ||||
|  | ||||
| 			if (remoteCid || recipientCids.length) { | ||||
| 				// Overrides passed-in value, respect addressing from main post over booster | ||||
| 				options.cid = remoteCid || recipientCids.shift(); | ||||
| 			} | ||||
|  | ||||
| 			// Auto-categorization (takes place only if all other categorization efforts fail) | ||||
| 			if (!options.cid) { | ||||
| 				options.cid = await assignCategory(mainPost); | ||||
| 			} | ||||
|  | ||||
| 			// mainPid ok to leave as-is | ||||
| 			if (!title) { | ||||
| 				const sentences = tokenizer.sentences(content || sourceContent, { sanitize: true }); | ||||
| 				title = sentences.shift(); | ||||
| 			} | ||||
|  | ||||
| 			// Remove any custom emoji from title | ||||
| 			if (_activitypub && _activitypub.tag && Array.isArray(_activitypub.tag)) { | ||||
| 				_activitypub.tag | ||||
| 					.filter(tag => tag.type === 'Emoji') | ||||
| 					.forEach((tag) => { | ||||
| 						title = title.replace(new RegExp(tag.name, 'g'), ''); | ||||
| 					}); | ||||
| 			} | ||||
| 		} | ||||
| 		mainPid = utils.isNumber(mainPid) ? parseInt(mainPid, 10) : mainPid; | ||||
|  | ||||
| 		// Relation & privilege check for local categories | ||||
| 		const inputIndex = chain.map(n => n.pid).indexOf(id); | ||||
| 		const hasRelation = | ||||
| 			uid || hasTid || | ||||
| 			options.skipChecks || options.cid || | ||||
| 			await assertRelation(chain[inputIndex !== -1 ? inputIndex : 0]); | ||||
|  | ||||
| 		const privilege = `topics:${tid ? 'reply' : 'create'}`; | ||||
| 		const allowed = await privileges.categories.can(privilege, options.cid || cid, activitypub._constants.uid); | ||||
| 		if (!hasRelation || !allowed) { | ||||
| 			if (!hasRelation) { | ||||
| 				activitypub.helpers.log(`[activitypub/notes.assert] Not asserting ${id} as it has no relation to existing tracked content.`); | ||||
| 			} | ||||
|  | ||||
| 			await unlock(id); | ||||
| 			return null; | ||||
| 		} | ||||
|  | ||||
| 		tid = tid || utils.generateUUID(); | ||||
| 		mainPost.tid = tid; | ||||
|  | ||||
| 		const urlMap = chain.reduce((map, post) => (post.url ? map.set(post.url, post.id) : map), new Map()); | ||||
| 		const unprocessed = chain.map((post) => { | ||||
| 			post.tid = tid; // add tid to post hash | ||||
|  | ||||
| 			// Ensure toPids in replies are ids | ||||
| 			if (urlMap.has(post.toPid)) { | ||||
| 				post.toPid = urlMap.get(post.toPid); | ||||
| 			} | ||||
|  | ||||
| 			return post; | ||||
| 		}).filter((p, idx) => !members[idx]); | ||||
| 		const count = unprocessed.length; | ||||
| 		activitypub.helpers.log(`[notes/assert] ${count} new note(s) found.`); | ||||
|  | ||||
| 		if (!hasTid) { | ||||
| 			const { to, cc, attachment } = mainPost._activitypub; | ||||
| 			const tags = await Notes._normalizeTags(mainPost._activitypub.tag || []); | ||||
|  | ||||
| 			try { | ||||
| 				await topics.post({ | ||||
| 					tid, | ||||
| 					uid: authorId, | ||||
| 					cid: options.cid || cid, | ||||
| 					pid: mainPid, | ||||
| 					title, | ||||
| 					timestamp, | ||||
| 					tags, | ||||
| 					content: mainPost.content, | ||||
| 					sourceContent: mainPost.sourceContent, | ||||
| 					_activitypub: mainPost._activitypub, | ||||
| 				}); | ||||
| 				unprocessed.shift(); | ||||
| 			} catch (e) { | ||||
| 				activitypub.helpers.log(`[activitypub/notes.assert] Could not post topic (${mainPost.pid}): ${e.message}`); | ||||
| 				await unlock(id); | ||||
| 				return null; | ||||
| 			} | ||||
|  | ||||
| 			// These must come after topic is posted | ||||
| 			await Promise.all([ | ||||
| 				Notes.updateLocalRecipients(mainPid, { to, cc }), | ||||
| 				mainPost._activitypub.image ? topics.thumbs.associate({ | ||||
| 					id: tid, | ||||
| 					path: mainPost._activitypub.image, | ||||
| 				}) : null, | ||||
| 				posts.attachments.update(mainPid, attachment), | ||||
| 			]); | ||||
|  | ||||
| 			if (context) { | ||||
| 				activitypub.helpers.log(`[activitypub/notes.assert] Associating tid ${tid} with context ${context}`); | ||||
| 				await topics.setTopicField(tid, 'context', context); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		for (const post of unprocessed) { | ||||
| 			const { to, cc, attachment } = post._activitypub; | ||||
|  | ||||
| 			try { | ||||
| 				// eslint-disable-next-line no-await-in-loop | ||||
| 				await topics.reply(post); | ||||
| 				// eslint-disable-next-line no-await-in-loop | ||||
| 				await Promise.all([ | ||||
| 					Notes.updateLocalRecipients(post.pid, { to, cc }), | ||||
| 					posts.attachments.update(post.pid, attachment), | ||||
| 				]); | ||||
| 			} catch (e) { | ||||
| 				activitypub.helpers.log(`[activitypub/notes.assert] Could not add reply (${post.pid}): ${e.message}`); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		await Promise.all([ | ||||
| 			Notes.updateLocalRecipients(mainPid, { to, cc }), | ||||
| 			mainPost._activitypub.image ? topics.thumbs.associate({ | ||||
| 				id: tid, | ||||
| 				path: mainPost._activitypub.image, | ||||
| 			}) : null, | ||||
| 			posts.attachments.update(mainPid, attachment), | ||||
| 			Notes.syncUserInboxes(tid, uid), | ||||
| 			unlock(id), | ||||
| 		]); | ||||
|  | ||||
| 		if (context) { | ||||
| 			activitypub.helpers.log(`[activitypub/notes.assert] Associating tid ${tid} with context ${context}`); | ||||
| 			await topics.setTopicField(tid, 'context', context); | ||||
| 		} | ||||
| 		return { tid, count }; | ||||
| 	} catch (e) { | ||||
| 		winston.warn(`[activitypub/notes.assert] Could not assert ${id} (${e.message}), releasing lock.`); | ||||
| 		await unlock(id); | ||||
| 		return null; | ||||
| 	} | ||||
|  | ||||
| 	for (const post of unprocessed) { | ||||
| 		const { to, cc, attachment } = post._activitypub; | ||||
|  | ||||
| 		try { | ||||
| 			// eslint-disable-next-line no-await-in-loop | ||||
| 			await topics.reply(post); | ||||
| 			// eslint-disable-next-line no-await-in-loop | ||||
| 			await Promise.all([ | ||||
| 				Notes.updateLocalRecipients(post.pid, { to, cc }), | ||||
| 				posts.attachments.update(post.pid, attachment), | ||||
| 			]); | ||||
| 		} catch (e) { | ||||
| 			activitypub.helpers.log(`[activitypub/notes.assert] Could not add reply (${post.pid}): ${e.message}`); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	await Promise.all([ | ||||
| 		Notes.syncUserInboxes(tid, uid), | ||||
| 		unlock(id), | ||||
| 	]); | ||||
|  | ||||
| 	return { tid, count }; | ||||
| }; | ||||
|  | ||||
| Notes.assertPrivate = async (object) => { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user