mirror of
https://github.com/schroinerxy/cloud-mail.git
synced 2026-06-21 19:35:50 +08:00
249 lines
6.2 KiB
JavaScript
249 lines
6.2 KiB
JavaScript
import PostalMime from 'postal-mime';
|
|
import emailService from '../service/email-service';
|
|
import accountService from '../service/account-service';
|
|
import settingService from '../service/setting-service';
|
|
import attService from '../service/att-service';
|
|
import constant from '../const/constant';
|
|
import fileUtils from '../utils/file-utils';
|
|
import { emailConst, isDel, roleConst, settingConst } from '../const/entity-const';
|
|
import emailUtils from '../utils/email-utils';
|
|
import dayjs from 'dayjs';
|
|
import utc from 'dayjs/plugin/utc';
|
|
import timezone from 'dayjs/plugin/timezone';
|
|
import roleService from '../service/role-service';
|
|
import verifyUtils from '../utils/verify-utils';
|
|
import r2Service from '../service/r2-service';
|
|
|
|
dayjs.extend(utc);
|
|
dayjs.extend(timezone);
|
|
|
|
export async function email(message, env, ctx) {
|
|
|
|
try {
|
|
|
|
const {
|
|
receive,
|
|
tgBotToken,
|
|
tgChatId,
|
|
tgBotStatus,
|
|
forwardStatus,
|
|
forwardEmail,
|
|
ruleEmail,
|
|
ruleType,
|
|
r2Domain,
|
|
noRecipient
|
|
} = await settingService.query({ env });
|
|
|
|
if (receive === settingConst.receive.CLOSE) {
|
|
message.setReject('Service suspended');
|
|
return;
|
|
}
|
|
|
|
|
|
const reader = message.raw.getReader();
|
|
let content = '';
|
|
|
|
while (true) {
|
|
const { done, value } = await reader.read();
|
|
if (done) break;
|
|
content += new TextDecoder().decode(value);
|
|
}
|
|
|
|
const email = await PostalMime.parse(content);
|
|
|
|
const account = await accountService.selectByEmailIncludeDel({ env: env }, message.to);
|
|
|
|
if (!account && noRecipient === settingConst.noRecipient.CLOSE) {
|
|
message.setReject('Recipient not found');
|
|
return;
|
|
}
|
|
|
|
if (account && account.email !== env.admin) {
|
|
|
|
let { banEmail, banEmailType, availDomain } = await roleService.selectByUserId({ env: env }, account.userId);
|
|
|
|
if (!roleService.hasAvailDomainPerm(availDomain, message.to)) {
|
|
message.setReject('Mailbox disabled');
|
|
return;
|
|
}
|
|
|
|
banEmail = banEmail.split(',').filter(item => item !== '');
|
|
|
|
|
|
if (banEmail.includes('*')) {
|
|
|
|
if (!banEmailHandler(banEmailType, message, email)) return;
|
|
|
|
}
|
|
|
|
for (const item of banEmail) {
|
|
|
|
if (verifyUtils.isDomain(item)) {
|
|
|
|
const banDomain = item.toLowerCase();
|
|
const receiveDomain = emailUtils.getDomain(email.from.address.toLowerCase());
|
|
|
|
if (banDomain === receiveDomain) {
|
|
|
|
if (!banEmailHandler(banEmailType, message, email)) return;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (item.toLowerCase() === email.from.address.toLowerCase()) {
|
|
|
|
if (!banEmailHandler(banEmailType, message, email)) return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
if (!email.to) {
|
|
email.to = [{ address: message.to, name: emailUtils.getName(message.to)}]
|
|
}
|
|
|
|
const toName = email.to.find(item => item.address === message.to)?.name || '';
|
|
|
|
const params = {
|
|
toEmail: message.to,
|
|
toName: toName,
|
|
sendEmail: email.from.address,
|
|
name: email.from.name || emailUtils.getName(email.from.address),
|
|
subject: email.subject,
|
|
content: email.html,
|
|
text: email.text,
|
|
cc: email.cc ? JSON.stringify(email.cc) : '[]',
|
|
bcc: email.bcc ? JSON.stringify(email.bcc) : '[]',
|
|
recipient: JSON.stringify(email.to),
|
|
inReplyTo: email.inReplyTo,
|
|
relation: email.references,
|
|
messageId: email.messageId,
|
|
userId: account ? account.userId : 0,
|
|
accountId: account ? account.accountId : 0,
|
|
isDel: isDel.DELETE,
|
|
status: emailConst.status.SAVING
|
|
};
|
|
|
|
const attachments = [];
|
|
const cidAttachments = [];
|
|
|
|
for (let item of email.attachments) {
|
|
let attachment = { ...item };
|
|
attachment.key = constant.ATTACHMENT_PREFIX + await fileUtils.getBuffHash(attachment.content) + fileUtils.getExtFileName(item.filename);
|
|
attachment.size = item.content.length ?? item.content.byteLength;
|
|
attachments.push(attachment);
|
|
if (attachment.contentId) {
|
|
cidAttachments.push(attachment);
|
|
}
|
|
}
|
|
|
|
let emailRow = await emailService.receive({ env }, params, cidAttachments, r2Domain);
|
|
|
|
attachments.forEach(attachment => {
|
|
attachment.emailId = emailRow.emailId;
|
|
attachment.userId = emailRow.userId;
|
|
attachment.accountId = emailRow.accountId;
|
|
});
|
|
|
|
try {
|
|
if (attachments.length > 0 && await r2Service.hasOSS({ env })) {
|
|
await attService.addAtt({ env }, attachments);
|
|
}
|
|
} catch (e) {
|
|
console.error(e);
|
|
}
|
|
|
|
emailRow = await emailService.completeReceive({ env }, account ? emailConst.status.RECEIVE : emailConst.status.NOONE, emailRow.emailId);
|
|
|
|
|
|
if (ruleType === settingConst.ruleType.RULE) {
|
|
|
|
const emails = ruleEmail.split(',');
|
|
|
|
if (!emails.includes(message.to)) {
|
|
return;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
if (tgBotStatus === settingConst.tgBotStatus.OPEN && tgChatId) {
|
|
|
|
const tgMessage = `<b>${params.subject}</b>
|
|
|
|
<b>发件人:</b>${params.name} <${params.sendEmail}>
|
|
<b>收件人:\u200B</b>${message.to}
|
|
<b>时间:</b>${dayjs.utc(emailRow.createTime).tz('Asia/Shanghai').format('YYYY-MM-DD HH:mm')}
|
|
|
|
${params.text || emailUtils.htmlToText(params.content) || ''}
|
|
`;
|
|
|
|
const tgChatIds = tgChatId.split(',');
|
|
|
|
await Promise.all(tgChatIds.map(async chatId => {
|
|
try {
|
|
const res = await fetch(`https://api.telegram.org/bot${tgBotToken}/sendMessage`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
chat_id: chatId,
|
|
parse_mode: 'HTML',
|
|
text: tgMessage
|
|
})
|
|
});
|
|
if (!res.ok) {
|
|
console.error(`转发 Telegram 失败: chatId=${chatId}, 状态码=${res.status}`);
|
|
}
|
|
} catch (e) {
|
|
console.error(`转发 Telegram 失败: chatId=${chatId}`, e);
|
|
}
|
|
}));
|
|
}
|
|
|
|
if (forwardStatus === settingConst.forwardStatus.OPEN && forwardEmail) {
|
|
|
|
const emails = forwardEmail.split(',');
|
|
|
|
await Promise.all(emails.map(async email => {
|
|
|
|
try {
|
|
await message.forward(email);
|
|
} catch (e) {
|
|
console.error(`转发邮箱 ${email} 失败:`, e);
|
|
}
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
console.error('邮件接收异常: ', e);
|
|
}
|
|
}
|
|
|
|
function banEmailHandler(banEmailType, message, email) {
|
|
|
|
if (banEmailType === roleConst.banEmailType.ALL) {
|
|
message.setReject('Mailbox disabled');
|
|
return false;
|
|
}
|
|
|
|
if (banEmailType === roleConst.banEmailType.CONTENT) {
|
|
email.html = 'The content has been deleted';
|
|
email.text = 'The content has been deleted';
|
|
email.attachments = [];
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|