Files
upstream-mirror 30810eecd7
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 2s
snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
2026-09-16 01:21:02 +05:00

243 lines
7.3 KiB
JavaScript

import { escapeHTML } from '@rocket.chat/tools';
import { Meteor } from 'meteor/meteor';
import { ltrim } from '../../../../lib/utils/stringUtils';
import { settings } from '../../../settings';
import { callbacks } from '../../callbacks';
import { i18n } from '../../i18n';
import { metrics } from '../../metrics';
import { roomCoordinator } from '../../rooms/roomCoordinator';
import { getURL } from '../../utils/getURL';
import * as Mailer from '../email/api';
let advice = '';
let goToMessage = '';
Meteor.startup(() => {
settings.watch('email_style', () => {
goToMessage = Mailer.inlinecss('<p><a class=\'btn\' href="[room_path]">{Offline_Link_Message}</a></p>');
});
Mailer.getTemplate('Email_Footer_Direct_Reply', (value) => {
advice = value;
});
});
export async function getEmailContent({ message, user, room }) {
const lng = (user && user.language) || settings.get('Language') || 'en';
const roomName = escapeHTML(`#${await roomCoordinator.getRoomName(room.t, room)}`);
const userName = escapeHTML(settings.get('UI_Use_Real_Name') ? message.u.name || message.u.username : message.u.username);
const roomDirectives = roomCoordinator.getRoomDirectives(room.t);
const files = (message.files || [message.file]).filter((file) => file && file.typeGroup !== 'thumb');
const hasFiles = files.length > 0;
const hasText = typeof message.msg === 'string' && message.msg.trim() !== '';
const isGroupChat = roomDirectives.isGroupChat(room);
let header;
if (hasFiles && !hasText) {
// file-only message
const isMultipleFiles = files.length > 1;
let headerKey;
if (isGroupChat) {
headerKey = isMultipleFiles ? 'User_uploaded_files_on_channel' : 'User_uploaded_a_file_on_channel';
} else {
headerKey = isMultipleFiles ? 'User_uploaded_files_to_you' : 'User_uploaded_a_file_to_you';
}
header = i18n.t(headerKey, { username: userName, channel: roomName, count: files.length, lng });
} else {
// text message (with or without files)
header = i18n.t(!isGroupChat ? 'User_sent_a_message_to_you' : 'User_sent_a_message_on_channel', {
username: userName,
channel: roomName,
lng,
});
}
if (message.t === 'e2e') {
return settings.get('Email_notification_show_message') ? i18n.t('Encrypted_message_preview_unavailable', { lng }) : header;
}
if (!settings.get('Email_notification_show_message')) {
return header;
}
const contentParts = [];
if (hasText) {
let messageContent = escapeHTML(message.msg);
message = await callbacks.run('renderMessage', message);
if (message.tokens && message.tokens.length > 0) {
message.tokens.forEach((token) => {
token.text = token.text.replace(/([^\$])(\$[^\$])/gm, '$1$$$2');
messageContent = messageContent.replace(token.token, token.text);
});
}
contentParts.push(messageContent.replace(/\n/gm, '<br/>'));
}
if (hasFiles) {
const attachments = message.attachments || [];
const fileParts = files.map((file, index) => {
let part = escapeHTML(file.name);
if (attachments[index]?.description) {
part += `<br/><br/>${escapeHTML(attachments[index].description)}`;
}
return part;
});
contentParts.push(fileParts.join('<br/><br/>'));
}
if (contentParts.length > 0) {
return `${header}:<br/><br/>${contentParts.join('<br/><br/>')}`;
}
if (Array.isArray(message.attachments) && message.attachments.length > 0) {
const [attachment] = message.attachments;
let content = '';
if (attachment.title) {
content += `${escapeHTML(attachment.title)}<br/>`;
}
if (attachment.text) {
content += `${escapeHTML(attachment.text)}<br/>`;
}
return `${header}:<br/><br/>${content}`;
}
return header;
}
const getButtonUrl = (room, subscription, message) => {
const basePath = roomCoordinator.getRouteLink(room.t, subscription).replace(Meteor.absoluteUrl(), '');
const path = `${ltrim(basePath, '/')}?msg=${message._id}`;
return getURL(
path,
{
full: true,
cloud: settings.get('Offline_Message_Use_DeepLink'),
cloud_route: 'room',
cloud_params: {
rid: room._id,
mid: message._id,
},
},
settings.get('DeepLink_Url'),
);
};
function generateNameEmail(name, email) {
return `${String(name).replace(/@/g, '%40').replace(/[<>,]/g, '')} <${email}>`;
}
export async function getEmailData({ message, receiver, sender, subscription, room, emailAddress, hasMentionToUser }) {
const username = settings.get('UI_Use_Real_Name') ? message.u.name || message.u.username : message.u.username;
let subjectKey = 'Offline_Mention_All_Email';
if (!roomCoordinator.getRoomDirectives(room.t).isGroupChat(room)) {
subjectKey = 'Offline_DM_Email';
} else if (hasMentionToUser) {
subjectKey = 'Offline_Mention_Email';
}
const emailSubject = Mailer.replace(settings.get(subjectKey), {
user: username,
room: await roomCoordinator.getRoomName(room.t, room),
});
const content = await getEmailContent({
message,
user: receiver,
room,
});
const room_path = getButtonUrl(room, subscription, message);
const receiverName = settings.get('UI_Use_Real_Name') ? receiver.name || receiver.username : receiver.username;
const email = {
from: generateNameEmail(username, settings.get('From_Email')),
to: generateNameEmail(receiverName, emailAddress),
subject: emailSubject,
html: content + goToMessage + (settings.get('Direct_Reply_Enable') ? advice : ''),
data: {
room_path,
},
headers: {},
};
if (sender.emails?.length > 0 && settings.get('Add_Sender_To_ReplyTo')) {
const [senderEmail] = sender.emails;
email.headers['Reply-To'] = generateNameEmail(username, senderEmail.address);
}
// If direct reply enabled, email content with headers
if (settings.get('Direct_Reply_Enable')) {
const replyto = settings.get('Direct_Reply_ReplyTo') || settings.get('Direct_Reply_Username');
// Reply-To header with format "username+messageId@domain"
email.headers['Reply-To'] = `${replyto.split('@')[0].split(settings.get('Direct_Reply_Separator'))[0]}${settings.get(
'Direct_Reply_Separator',
)}${message.tmid || message._id}@${replyto.split('@')[1]}`;
}
return email;
}
export function sendEmailFromData(data) {
return Mailer.send(data).then(() => {
metrics.notificationsSent.inc({ notification_type: 'email' });
metrics.notificationsSentTotal.inc({ notification_type: 'email' });
});
}
export function shouldNotifyEmail({
disableAllMessageNotifications,
statusConnection,
emailNotifications,
isHighlighted,
hasMentionToUser,
hasMentionToAll,
hasReplyToThread,
roomType,
isThread,
}) {
// email notifications are disabled globally
if (!settings.get('Accounts_AllowEmailNotifications')) {
return false;
}
// user/room preference to nothing
if (emailNotifications === 'nothing') {
return false;
}
// user connected (don't need to send him an email)
if (statusConnection === 'online') {
return false;
}
// no user or room preference
if (emailNotifications == null) {
if (disableAllMessageNotifications && !isHighlighted && !hasMentionToUser && !hasReplyToThread) {
return false;
}
// default server preference is disabled
if (settings.get('Accounts_Default_User_Preferences_emailNotificationMode') === 'nothing') {
return false;
}
}
return (
(roomType === 'd' ||
isHighlighted ||
emailNotifications === 'all' ||
hasMentionToUser ||
(!disableAllMessageNotifications && hasMentionToAll)) &&
(isHighlighted || !isThread || hasReplyToThread)
);
}