From f7c47429879f757e08975b5cd003416db00f5568 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 17 Sep 2025 10:44:51 -0400 Subject: [PATCH 1/7] fix: add pre-processing step to title generation logic so sbd doesn't fall over so badly --- src/activitypub/helpers.js | 46 -------------------------------------- src/activitypub/notes.js | 6 ++++- 2 files changed, 5 insertions(+), 47 deletions(-) diff --git a/src/activitypub/helpers.js b/src/activitypub/helpers.js index e6eb2e1c08..01cfd86d10 100644 --- a/src/activitypub/helpers.js +++ b/src/activitypub/helpers.js @@ -339,52 +339,6 @@ Helpers.resolveObjects = async (ids) => { return objects.length === 1 ? objects[0] : objects; }; -const titleishTags = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'title', 'p', 'span']; -const titleRegex = new RegExp(`<(${titleishTags.join('|')})>(.+?)`, 'm'); -Helpers.generateTitle = (html) => { - // Given an html string, generates a more appropriate title if possible - let title; - - // Try the first paragraph-like element - const match = html.match(titleRegex); - if (match && match.index === 0) { - title = match[2]; - } - - // Fall back to newline splitting (i.e. if no paragraph elements) - title = title || html.split('\n').filter(Boolean).shift(); - - // Discard everything after a line break element - title = title.replace(/.*/g, ''); - - // Strip html - title = utils.stripHTMLTags(title); - - // Split sentences and use only first one - const sentences = title - .split(/(\.|\?|!)\s/) - .reduce((memo, cur, idx, sentences) => { - if (idx % 2) { - memo.push(`${sentences[idx - 1]}${cur}`); - } else if (idx === sentences.length - 1) { - memo.push(cur); - } - - return memo; - }, []); - - if (sentences.length > 1) { - title = sentences.shift(); - } - - // Truncate down if too long - if (title.length > meta.config.maximumTitleLength) { - title = `${title.slice(0, meta.config.maximumTitleLength - 3)}...`; - } - - return title; -}; - Helpers.remoteAnchorToLocalProfile = async (content, isMarkdown = false) => { let anchorRegex; if (isMarkdown) { diff --git a/src/activitypub/notes.js b/src/activitypub/notes.js index ef4abe9add..6ccb2ca209 100644 --- a/src/activitypub/notes.js +++ b/src/activitypub/notes.js @@ -165,7 +165,11 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { // mainPid ok to leave as-is if (!title) { - const sentences = tokenizer.sentences(content || sourceContent, { sanitize: true }); + // Naive pre-processing prior to sbd tokenization + let sbdInput = content || sourceContent; + sbdInput = sbdInput.replace('

', '

\n

'); + + const sentences = tokenizer.sentences(sbdInput, { sanitize: true, newline_boundaries: true }); title = sentences.shift(); } From 6cca55e37f0bce389c3094c5aae07ed1bbed3297 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 17 Sep 2025 10:50:35 -0400 Subject: [PATCH 2/7] fix: use parameterized query for key lookup --- src/database/postgres/main.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/database/postgres/main.js b/src/database/postgres/main.js index c0838b45a0..5b3c7f7e9d 100644 --- a/src/database/postgres/main.js +++ b/src/database/postgres/main.js @@ -85,7 +85,8 @@ module.exports = function (module) { text: ` SELECT o."_key" FROM "legacy_object_live" o - WHERE o."_key" LIKE '${match}'`, + WHERE o."_key" LIKE $1`, + values: [match], }); return res.rows.map(r => r._key); From 532653110c9e0400967c42352415be6dccb8e6a4 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 17 Sep 2025 10:58:07 -0400 Subject: [PATCH 3/7] Revert "fix: add pre-processing step to title generation logic so sbd doesn't fall over so badly" This reverts commit f7c47429879f757e08975b5cd003416db00f5568. --- src/activitypub/helpers.js | 46 ++++++++++++++++++++++++++++++++++++++ src/activitypub/notes.js | 6 +---- 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/src/activitypub/helpers.js b/src/activitypub/helpers.js index 01cfd86d10..e6eb2e1c08 100644 --- a/src/activitypub/helpers.js +++ b/src/activitypub/helpers.js @@ -339,6 +339,52 @@ Helpers.resolveObjects = async (ids) => { return objects.length === 1 ? objects[0] : objects; }; +const titleishTags = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'title', 'p', 'span']; +const titleRegex = new RegExp(`<(${titleishTags.join('|')})>(.+?)`, 'm'); +Helpers.generateTitle = (html) => { + // Given an html string, generates a more appropriate title if possible + let title; + + // Try the first paragraph-like element + const match = html.match(titleRegex); + if (match && match.index === 0) { + title = match[2]; + } + + // Fall back to newline splitting (i.e. if no paragraph elements) + title = title || html.split('\n').filter(Boolean).shift(); + + // Discard everything after a line break element + title = title.replace(/.*/g, ''); + + // Strip html + title = utils.stripHTMLTags(title); + + // Split sentences and use only first one + const sentences = title + .split(/(\.|\?|!)\s/) + .reduce((memo, cur, idx, sentences) => { + if (idx % 2) { + memo.push(`${sentences[idx - 1]}${cur}`); + } else if (idx === sentences.length - 1) { + memo.push(cur); + } + + return memo; + }, []); + + if (sentences.length > 1) { + title = sentences.shift(); + } + + // Truncate down if too long + if (title.length > meta.config.maximumTitleLength) { + title = `${title.slice(0, meta.config.maximumTitleLength - 3)}...`; + } + + return title; +}; + Helpers.remoteAnchorToLocalProfile = async (content, isMarkdown = false) => { let anchorRegex; if (isMarkdown) { diff --git a/src/activitypub/notes.js b/src/activitypub/notes.js index 6ccb2ca209..ef4abe9add 100644 --- a/src/activitypub/notes.js +++ b/src/activitypub/notes.js @@ -165,11 +165,7 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => { // mainPid ok to leave as-is if (!title) { - // Naive pre-processing prior to sbd tokenization - let sbdInput = content || sourceContent; - sbdInput = sbdInput.replace('

', '

\n

'); - - const sentences = tokenizer.sentences(sbdInput, { sanitize: true, newline_boundaries: true }); + const sentences = tokenizer.sentences(content || sourceContent, { sanitize: true }); title = sentences.shift(); } From a6674f67a1cfb92f6236e76447e5e9213b1b5710 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 17 Sep 2025 10:58:26 -0400 Subject: [PATCH 4/7] lint: remove unused --- src/activitypub/helpers.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/activitypub/helpers.js b/src/activitypub/helpers.js index 01cfd86d10..f24d18b730 100644 --- a/src/activitypub/helpers.js +++ b/src/activitypub/helpers.js @@ -8,7 +8,6 @@ const validator = require('validator'); // const cheerio = require('cheerio'); const crypto = require('crypto'); -const meta = require('../meta'); const posts = require('../posts'); const categories = require('../categories'); const messaging = require('../messaging'); @@ -16,7 +15,6 @@ const request = require('../request'); const db = require('../database'); const ttl = require('../cache/ttl'); const user = require('../user'); -const utils = require('../utils'); const activitypub = require('.'); const webfingerRegex = /^(@|acct:)?[\w-.]+@.+$/; From 5beeedd67cc2fc08b6dda77f237ba7892b5329b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 17 Sep 2025 11:09:02 -0400 Subject: [PATCH 5/7] Revert "lint: remove unused" This reverts commit a6674f67a1cfb92f6236e76447e5e9213b1b5710. --- src/activitypub/helpers.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/activitypub/helpers.js b/src/activitypub/helpers.js index e0f96fe3aa..e6eb2e1c08 100644 --- a/src/activitypub/helpers.js +++ b/src/activitypub/helpers.js @@ -8,6 +8,7 @@ const validator = require('validator'); // const cheerio = require('cheerio'); const crypto = require('crypto'); +const meta = require('../meta'); const posts = require('../posts'); const categories = require('../categories'); const messaging = require('../messaging'); @@ -15,6 +16,7 @@ const request = require('../request'); const db = require('../database'); const ttl = require('../cache/ttl'); const user = require('../user'); +const utils = require('../utils'); const activitypub = require('.'); const webfingerRegex = /^(@|acct:)?[\w-.]+@.+$/; From d1f5060f11a257388690d1441726efd58ca88b5a Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 18 Sep 2025 13:33:16 -0400 Subject: [PATCH 6/7] fix(deps): bump 2factor to 7.6.0 --- install/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/install/package.json b/install/package.json index cd5d08f5c1..f9e5b8b0ed 100644 --- a/install/package.json +++ b/install/package.json @@ -96,7 +96,7 @@ "mousetrap": "1.6.5", "multer": "2.0.2", "nconf": "0.13.0", - "nodebb-plugin-2factor": "7.5.10", + "nodebb-plugin-2factor": "7.6.0", "nodebb-plugin-composer-default": "10.3.1", "nodebb-plugin-dbsearch": "6.3.2", "nodebb-plugin-emoji": "6.0.3", @@ -201,4 +201,4 @@ "url": "https://github.com/barisusakli" } ] -} \ No newline at end of file +} From f9edb13f6209b075d4a53c130d1bba166ae188fa Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 19 Sep 2025 14:43:04 -0400 Subject: [PATCH 7/7] fix: missing actor assertion on 1b12 announced upboat --- src/activitypub/inbox.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/activitypub/inbox.js b/src/activitypub/inbox.js index 754720f208..ea5b032a1a 100644 --- a/src/activitypub/inbox.js +++ b/src/activitypub/inbox.js @@ -298,6 +298,7 @@ inbox.announce = async (req) => { const exists = await posts.exists(localId || id); if (exists) { try { + await activitypub.actors.assert(object.actor); const result = await posts.upvote(localId || id, object.actor); if (localId) { socketHelpers.upvote(result, 'notifications:upvoted-your-post-in');