From ffa73698a5070efa316165b6e85f006eb54a79ca Mon Sep 17 00:00:00 2001 From: Hafiz Date: Tue, 5 Aug 2025 12:39:33 -0500 Subject: [PATCH] Server now matches usernames case insensitively like client - Preserves original capitalization in mention text - Fixes profile links not working with wrong case mentions --- test/api/unit/libs/highlightMentions.test.js | 6 ++++++ test/api/v3/integration/chat/POST-chat.test.js | 12 ++++++++++++ website/server/libs/highlightMentions.js | 16 ++++++++++++---- 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/test/api/unit/libs/highlightMentions.test.js b/test/api/unit/libs/highlightMentions.test.js index 30fb34c72c..266bfbaaa6 100644 --- a/test/api/unit/libs/highlightMentions.test.js +++ b/test/api/unit/libs/highlightMentions.test.js @@ -47,6 +47,12 @@ describe('highlightMentions', () => { expect(result[0]).to.equal('[@user-dash](/profile/444): message [@user_underscore](/profile/555)'); }); + it('highlights users with case-insensitive matching', async () => { + const text = '@USER: message @User2 @USER3'; + const result = await highlightMentions(text); + expect(result[0]).to.equal('[@USER](/profile/111): message [@User2](/profile/222) [@USER3](/profile/333)'); + }); + it('doesn\'t highlight nonexisting users', async () => { const text = '@nouser message'; const result = await highlightMentions(text); diff --git a/test/api/v3/integration/chat/POST-chat.test.js b/test/api/v3/integration/chat/POST-chat.test.js index ba4ac00ed0..f16cbea6f9 100644 --- a/test/api/v3/integration/chat/POST-chat.test.js +++ b/test/api/v3/integration/chat/POST-chat.test.js @@ -238,6 +238,18 @@ describe('POST /chat', () => { expect(groupMessages[0].id).to.exist; }); + it('creates a chat with case-insensitive mentions', async () => { + const originalUsername = member.auth.local.username; + const uppercaseUsername = originalUsername.toUpperCase(); + const messageWithMentions = `hi @${uppercaseUsername}`; + const newMessage = await user.post(`/groups/${groupWithChat._id}/chat`, { message: messageWithMentions }); + const groupMessages = await user.get(`/groups/${groupWithChat._id}/chat`); + + expect(newMessage.message.id).to.exist; + expect(newMessage.message.text).to.include(`[@${uppercaseUsername}](/profile/${member._id})`); + expect(groupMessages[0].id).to.exist; + }); + it('creates a chat with a max length of 3000 chars', async () => { const veryLongMessage = ` 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789. diff --git a/website/server/libs/highlightMentions.js b/website/server/libs/highlightMentions.js index ccb074590e..9798b490d0 100644 --- a/website/server/libs/highlightMentions.js +++ b/website/server/libs/highlightMentions.js @@ -164,18 +164,26 @@ export default async function highlightMentions (text) { if (mentions && mentions.length <= 5) { const usernames = mentions.map(mention => mention.substr(1)); + const usernameRegexes = usernames.map(username => new RegExp(`^${escapeRegExp(username)}$`, 'i')); members = await User - .find({ 'auth.local.username': { $in: usernames }, 'flags.verifiedUsername': true }) + .find({ + $or: usernameRegexes.map(regex => ({ 'auth.local.username': regex })), + 'flags.verifiedUsername': true + }) .select(['auth.local.username', '_id', 'preferences.pushNotifications', 'pushDevices', 'party', 'guilds']) .lean() .exec(); const baseUrl = determineBaseUrl(); members.forEach(member => { const { username } = member.auth.local; - const regex = new RegExp(`@${username}(?![\\-\\w])`, 'g'); - const replacement = `[@${username}](${baseUrl}/profile/${member._id})`; + const regex = new RegExp(`@${escapeRegExp(username)}(?![\\-\\w])`, 'gi'); - textBlocks.transformValidBlocks(blockText => blockText.replace(regex, replacement)); + textBlocks.transformValidBlocks(blockText => + blockText.replace(regex, match => { + const mentionedUsername = match.substr(1); + return `[@${mentionedUsername}](${baseUrl}/profile/${member._id})`; + }) + ); }); }