mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-10-26 16:46:12 +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