2024-08-07 08:38:58 +02:00
import type { InboxRule , Mail , MailFolder , MoveMailData } from "../../../common/api/entities/tutanota/TypeRefs.js"
2024-07-01 17:56:41 +02:00
import { createMoveMailData } from "../../../common/api/entities/tutanota/TypeRefs.js"
2024-08-07 08:38:58 +02:00
import { InboxRuleType , MailSetKind , MAX_NBR_MOVE_DELETE_MAIL_SERVICE } from "../../../common/api/common/TutanotaConstants"
2024-07-01 17:56:41 +02:00
import { isDomainName , isRegularExpression } from "../../../common/misc/FormatValidator"
2023-10-19 17:59:51 +02:00
import { assertNotNull , asyncFind , debounce , ofClass , promiseMap , splitInChunks } from "@tutao/tutanota-utils"
2024-07-01 17:56:41 +02:00
import { lang } from "../../../common/misc/LanguageViewModel"
2024-08-07 16:29:40 +02:00
import type { MailboxDetail } from "../../../common/mailFunctionality/MailboxModel.js"
2024-07-01 17:56:41 +02:00
import { LockedError , PreconditionFailedError } from "../../../common/api/common/error/RestError"
import type { SelectorItemList } from "../../../common/gui/base/DropDownSelector.js"
2024-08-07 08:38:58 +02:00
import { elementIdPart , isSameId } from "../../../common/api/common/utils/EntityUtils"
2024-07-01 17:56:41 +02:00
import { assertMainOrNode } from "../../../common/api/common/Env"
import { MailFacade } from "../../../common/api/worker/facades/lazy/MailFacade.js"
import { LoginController } from "../../../common/api/main/LoginController.js"
2024-08-07 08:38:58 +02:00
import { getMailHeaders } from "../../../common/mailFunctionality/SharedMailUtils.js"
2024-09-12 15:34:25 +02:00
import { throttle } from "@tutao/tutanota-utils/dist/Utils.js"
2024-08-07 16:29:40 +02:00
import { assertSystemFolderOfType } from "./MailModel.js"
import { mailLocator } from "../../mailLocator.js"
2022-01-07 15:58:30 +01:00
2017-08-15 13:54:22 +02:00
assertMainOrNode ( )
2020-09-28 14:50:34 +02:00
const moveMailDataPerFolder : MoveMailData [ ] = [ ]
const DEBOUNCE_FIRST_MOVE_MAIL_REQUEST_MS = 200
let applyingRules = false // used to avoid concurrent application of rules (-> requests to locked service)
2022-03-09 17:43:29 +01:00
async function sendMoveMailRequest ( mailFacade : MailFacade ) : Promise < void > {
2022-01-07 15:58:30 +01:00
if ( moveMailDataPerFolder . length ) {
const moveToTargetFolder = assertNotNull ( moveMailDataPerFolder . shift ( ) )
const mailChunks = splitInChunks ( MAX_NBR_MOVE_DELETE_MAIL_SERVICE , moveToTargetFolder . mails )
2022-12-27 15:37:40 +01:00
await promiseMap ( mailChunks , ( mailChunk ) = > {
2022-01-07 15:58:30 +01:00
moveToTargetFolder . mails = mailChunk
2024-08-07 08:38:58 +02:00
const sourceFolder = assertNotNull ( moveToTargetFolder . sourceFolder ) // old clients don't send sourceFolder. assertNotNull can be removed once sourceFolder cardinality is ONE
return mailFacade . moveMails ( mailChunk , sourceFolder , moveToTargetFolder . targetFolder )
2022-01-07 15:58:30 +01:00
} )
. catch (
2022-12-27 15:37:40 +01:00
ofClass ( LockedError , ( e ) = > {
2022-01-07 15:58:30 +01:00
//LockedError should no longer be thrown!?!
console . log ( "moving mail failed" , e , moveToTargetFolder )
} ) ,
)
. catch (
2022-12-27 15:37:40 +01:00
ofClass ( PreconditionFailedError , ( e ) = > {
2022-01-07 15:58:30 +01:00
// move mail operation may have been locked by other process
console . log ( "moving mail failed" , e , moveToTargetFolder )
} ) ,
)
. finally ( ( ) = > {
2022-03-09 17:43:29 +01:00
return sendMoveMailRequest ( mailFacade )
2022-01-07 15:58:30 +01:00
} )
} //We are done and unlock for future requests
2020-09-28 14:50:34 +02:00
}
2024-09-12 15:34:25 +02:00
// We throttle the moveMail requests to a rate of 200ms
2020-09-28 14:50:34 +02:00
// Each target folder requires one request
2024-09-12 15:34:25 +02:00
const applyMatchingRules = throttle ( DEBOUNCE_FIRST_MOVE_MAIL_REQUEST_MS , ( mailFacade : MailFacade ) = > {
2022-01-07 15:58:30 +01:00
if ( applyingRules ) return
// We lock to avoid concurrent requests
applyingRules = true
2022-03-09 17:43:29 +01:00
sendMoveMailRequest ( mailFacade ) . finally ( ( ) = > {
2022-01-07 15:58:30 +01:00
applyingRules = false
} )
2020-09-28 14:50:34 +02:00
} )
2022-01-07 15:58:30 +01:00
2019-08-22 18:24:32 +02:00
export function getInboxRuleTypeNameMapping ( ) : SelectorItemList < string > {
2022-01-07 15:58:30 +01:00
return [
{
value : InboxRuleType.FROM_EQUALS ,
name : lang.get ( "inboxRuleSenderEquals_action" ) ,
} ,
{
value : InboxRuleType.RECIPIENT_TO_EQUALS ,
name : lang.get ( "inboxRuleToRecipientEquals_action" ) ,
} ,
{
value : InboxRuleType.RECIPIENT_CC_EQUALS ,
name : lang.get ( "inboxRuleCCRecipientEquals_action" ) ,
} ,
{
value : InboxRuleType.RECIPIENT_BCC_EQUALS ,
name : lang.get ( "inboxRuleBCCRecipientEquals_action" ) ,
} ,
{
value : InboxRuleType.SUBJECT_CONTAINS ,
name : lang.get ( "inboxRuleSubjectContains_action" ) ,
} ,
{
value : InboxRuleType.MAIL_HEADER_CONTAINS ,
name : lang.get ( "inboxRuleMailHeaderContains_action" ) ,
} ,
]
2017-08-15 13:54:22 +02:00
}
2022-01-07 15:58:30 +01:00
2017-08-15 13:54:22 +02:00
export function getInboxRuleTypeName ( type : string ) : string {
2022-12-27 15:37:40 +01:00
let typeNameMapping = getInboxRuleTypeNameMapping ( ) . find ( ( t ) = > t . value === type )
2022-01-07 15:58:30 +01:00
return typeNameMapping != null ? typeNameMapping . name : ""
2017-08-15 13:54:22 +02:00
}
2023-05-17 16:49:56 +02:00
export class InboxRuleHandler {
Remove LegacyMailWrapper (legacy mail bodies) and cleanup TutanotaModel
Prior to starting implementing static MailIds and MailSets, we
want to clean up the current TutanotaModel. Therefore, this commit
removes a lot of legacy metamodel definitions that are not used any
longer, including removing the LegacyMailWrapper (legacy mail bodies).
Additionally, this commit inter alia includes:
* removing types no longer needed after migrating to MailDetails, e.g.
the "body", "toRecipients", "ccRecipients", "bccRecipients",
"replyTos", "sentDate" and "headers" references / values from MAIL_TYPE
* removing "mails" reference form MAIL_BOX_TYPE
* removing "subFolders" reference from MAIL_FOLDER
* removing the legacy types MAIL_BODY_TYPE and MAIL_HEADERS
* removing Value.OLD_OWNER_GROUP_NAME, and Value.OLD_AREA_ID_NAME from
FILE_TYPE and CONTACT_TYPE
Closes #7255
Co-authored-by: sug <sug@tutao.de>
2024-07-23 10:05:35 +02:00
constructor ( private readonly mailFacade : MailFacade , private readonly logins : LoginController ) { }
2023-05-17 16:49:56 +02:00
/ * *
* Checks the mail for an existing inbox rule and moves the mail to the target folder of the rule .
* @returns true if a rule matches otherwise false
* /
2024-08-07 08:38:58 +02:00
async findAndApplyMatchingRule ( mailboxDetail : MailboxDetail , mail : Mail , applyRulesOnServer : boolean ) : Promise < { folder : MailFolder ; mail : Mail } | null > {
2024-08-07 16:29:40 +02:00
if (
mail . _errors ||
! mail . unread ||
! isInboxFolder ( mailboxDetail , mail ) ||
! this . logins . getUserController ( ) . isPremiumAccount ( ) ||
mailboxDetail . mailbox . folders == null
) {
2023-05-17 16:49:56 +02:00
return null
}
2022-01-07 15:58:30 +01:00
Remove LegacyMailWrapper (legacy mail bodies) and cleanup TutanotaModel
Prior to starting implementing static MailIds and MailSets, we
want to clean up the current TutanotaModel. Therefore, this commit
removes a lot of legacy metamodel definitions that are not used any
longer, including removing the LegacyMailWrapper (legacy mail bodies).
Additionally, this commit inter alia includes:
* removing types no longer needed after migrating to MailDetails, e.g.
the "body", "toRecipients", "ccRecipients", "bccRecipients",
"replyTos", "sentDate" and "headers" references / values from MAIL_TYPE
* removing "mails" reference form MAIL_BOX_TYPE
* removing "subFolders" reference from MAIL_FOLDER
* removing the legacy types MAIL_BODY_TYPE and MAIL_HEADERS
* removing Value.OLD_OWNER_GROUP_NAME, and Value.OLD_AREA_ID_NAME from
FILE_TYPE and CONTACT_TYPE
Closes #7255
Co-authored-by: sug <sug@tutao.de>
2024-07-23 10:05:35 +02:00
const inboxRule = await _findMatchingRule ( this . mailFacade , mail , this . logins . getUserController ( ) . props . inboxRules )
2022-01-07 15:58:30 +01:00
if ( inboxRule ) {
2024-08-07 16:29:40 +02:00
const folders = mailLocator . mailModel . getMailboxFoldersForId ( mailboxDetail . mailbox . folders . _id )
let inboxFolder = assertNotNull ( folders . getSystemFolderByType ( MailSetKind . INBOX ) )
let targetFolder = folders . getFolderById ( elementIdPart ( inboxRule . targetFolder ) )
2022-01-07 15:58:30 +01:00
2024-08-07 08:38:58 +02:00
if ( targetFolder && targetFolder . folderType !== MailSetKind . INBOX ) {
2022-01-07 15:58:30 +01:00
if ( applyRulesOnServer ) {
2022-12-27 15:37:40 +01:00
let moveMailData = moveMailDataPerFolder . find ( ( folderMoveMailData ) = > isSameId ( folderMoveMailData . targetFolder , inboxRule . targetFolder ) )
2022-01-07 15:58:30 +01:00
if ( moveMailData ) {
moveMailData . mails . push ( mail . _id )
} else {
2023-11-09 17:04:42 +01:00
moveMailData = createMoveMailData ( {
2024-08-07 08:38:58 +02:00
sourceFolder : inboxFolder._id ,
2023-11-09 17:04:42 +01:00
targetFolder : inboxRule.targetFolder ,
mails : [ mail . _id ] ,
} )
2022-01-07 15:58:30 +01:00
moveMailDataPerFolder . push ( moveMailData )
}
2023-05-17 16:49:56 +02:00
applyMatchingRules ( this . mailFacade )
2022-01-07 15:58:30 +01:00
}
2024-08-07 08:38:58 +02:00
return { folder : targetFolder , mail }
2022-01-07 15:58:30 +01:00
} else {
return null
}
} else {
return null
}
2023-05-17 16:49:56 +02:00
}
2017-08-15 13:54:22 +02:00
}
/ * *
* Finds the first matching inbox rule for the mail and returns it .
* export only for testing
* /
Remove LegacyMailWrapper (legacy mail bodies) and cleanup TutanotaModel
Prior to starting implementing static MailIds and MailSets, we
want to clean up the current TutanotaModel. Therefore, this commit
removes a lot of legacy metamodel definitions that are not used any
longer, including removing the LegacyMailWrapper (legacy mail bodies).
Additionally, this commit inter alia includes:
* removing types no longer needed after migrating to MailDetails, e.g.
the "body", "toRecipients", "ccRecipients", "bccRecipients",
"replyTos", "sentDate" and "headers" references / values from MAIL_TYPE
* removing "mails" reference form MAIL_BOX_TYPE
* removing "subFolders" reference from MAIL_FOLDER
* removing the legacy types MAIL_BODY_TYPE and MAIL_HEADERS
* removing Value.OLD_OWNER_GROUP_NAME, and Value.OLD_AREA_ID_NAME from
FILE_TYPE and CONTACT_TYPE
Closes #7255
Co-authored-by: sug <sug@tutao.de>
2024-07-23 10:05:35 +02:00
export async function _findMatchingRule ( mailFacade : MailFacade , mail : Mail , rules : InboxRule [ ] ) : Promise < InboxRule | null > {
return asyncFind ( rules , ( rule ) = > checkInboxRule ( mailFacade , mail , rule ) ) . then ( ( v ) = > v ? ? null )
2021-06-23 17:39:31 +02:00
}
2017-08-15 13:54:22 +02:00
Remove LegacyMailWrapper (legacy mail bodies) and cleanup TutanotaModel
Prior to starting implementing static MailIds and MailSets, we
want to clean up the current TutanotaModel. Therefore, this commit
removes a lot of legacy metamodel definitions that are not used any
longer, including removing the LegacyMailWrapper (legacy mail bodies).
Additionally, this commit inter alia includes:
* removing types no longer needed after migrating to MailDetails, e.g.
the "body", "toRecipients", "ccRecipients", "bccRecipients",
"replyTos", "sentDate" and "headers" references / values from MAIL_TYPE
* removing "mails" reference form MAIL_BOX_TYPE
* removing "subFolders" reference from MAIL_FOLDER
* removing the legacy types MAIL_BODY_TYPE and MAIL_HEADERS
* removing Value.OLD_OWNER_GROUP_NAME, and Value.OLD_AREA_ID_NAME from
FILE_TYPE and CONTACT_TYPE
Closes #7255
Co-authored-by: sug <sug@tutao.de>
2024-07-23 10:05:35 +02:00
async function checkInboxRule ( mailFacade : MailFacade , mail : Mail , inboxRule : InboxRule ) : Promise < boolean > {
2022-01-07 15:58:30 +01:00
const ruleType = inboxRule . type
try {
if ( ruleType === InboxRuleType . FROM_EQUALS ) {
let mailAddresses = [ mail . sender . address ]
if ( mail . differentEnvelopeSender ) {
mailAddresses . push ( mail . differentEnvelopeSender )
}
return _checkEmailAddresses ( mailAddresses , inboxRule )
} else if ( ruleType === InboxRuleType . RECIPIENT_TO_EQUALS ) {
Remove LegacyMailWrapper (legacy mail bodies) and cleanup TutanotaModel
Prior to starting implementing static MailIds and MailSets, we
want to clean up the current TutanotaModel. Therefore, this commit
removes a lot of legacy metamodel definitions that are not used any
longer, including removing the LegacyMailWrapper (legacy mail bodies).
Additionally, this commit inter alia includes:
* removing types no longer needed after migrating to MailDetails, e.g.
the "body", "toRecipients", "ccRecipients", "bccRecipients",
"replyTos", "sentDate" and "headers" references / values from MAIL_TYPE
* removing "mails" reference form MAIL_BOX_TYPE
* removing "subFolders" reference from MAIL_FOLDER
* removing the legacy types MAIL_BODY_TYPE and MAIL_HEADERS
* removing Value.OLD_OWNER_GROUP_NAME, and Value.OLD_AREA_ID_NAME from
FILE_TYPE and CONTACT_TYPE
Closes #7255
Co-authored-by: sug <sug@tutao.de>
2024-07-23 10:05:35 +02:00
const toRecipients = ( await mailFacade . loadMailDetailsBlob ( mail ) ) . recipients . toRecipients
2022-01-07 15:58:30 +01:00
return _checkEmailAddresses (
add MailDetails feature, #4719
server issues: 1276, 1271, 1279, 1272, 1270, 1258, 1254, 1253, 1242, 1241
2022-11-03 19:03:54 +01:00
toRecipients . map ( ( m ) = > m . address ) ,
2022-01-07 15:58:30 +01:00
inboxRule ,
)
} else if ( ruleType === InboxRuleType . RECIPIENT_CC_EQUALS ) {
Remove LegacyMailWrapper (legacy mail bodies) and cleanup TutanotaModel
Prior to starting implementing static MailIds and MailSets, we
want to clean up the current TutanotaModel. Therefore, this commit
removes a lot of legacy metamodel definitions that are not used any
longer, including removing the LegacyMailWrapper (legacy mail bodies).
Additionally, this commit inter alia includes:
* removing types no longer needed after migrating to MailDetails, e.g.
the "body", "toRecipients", "ccRecipients", "bccRecipients",
"replyTos", "sentDate" and "headers" references / values from MAIL_TYPE
* removing "mails" reference form MAIL_BOX_TYPE
* removing "subFolders" reference from MAIL_FOLDER
* removing the legacy types MAIL_BODY_TYPE and MAIL_HEADERS
* removing Value.OLD_OWNER_GROUP_NAME, and Value.OLD_AREA_ID_NAME from
FILE_TYPE and CONTACT_TYPE
Closes #7255
Co-authored-by: sug <sug@tutao.de>
2024-07-23 10:05:35 +02:00
const ccRecipients = ( await mailFacade . loadMailDetailsBlob ( mail ) ) . recipients . ccRecipients
2022-01-07 15:58:30 +01:00
return _checkEmailAddresses (
add MailDetails feature, #4719
server issues: 1276, 1271, 1279, 1272, 1270, 1258, 1254, 1253, 1242, 1241
2022-11-03 19:03:54 +01:00
ccRecipients . map ( ( m ) = > m . address ) ,
2022-01-07 15:58:30 +01:00
inboxRule ,
)
} else if ( ruleType === InboxRuleType . RECIPIENT_BCC_EQUALS ) {
Remove LegacyMailWrapper (legacy mail bodies) and cleanup TutanotaModel
Prior to starting implementing static MailIds and MailSets, we
want to clean up the current TutanotaModel. Therefore, this commit
removes a lot of legacy metamodel definitions that are not used any
longer, including removing the LegacyMailWrapper (legacy mail bodies).
Additionally, this commit inter alia includes:
* removing types no longer needed after migrating to MailDetails, e.g.
the "body", "toRecipients", "ccRecipients", "bccRecipients",
"replyTos", "sentDate" and "headers" references / values from MAIL_TYPE
* removing "mails" reference form MAIL_BOX_TYPE
* removing "subFolders" reference from MAIL_FOLDER
* removing the legacy types MAIL_BODY_TYPE and MAIL_HEADERS
* removing Value.OLD_OWNER_GROUP_NAME, and Value.OLD_AREA_ID_NAME from
FILE_TYPE and CONTACT_TYPE
Closes #7255
Co-authored-by: sug <sug@tutao.de>
2024-07-23 10:05:35 +02:00
const bccRecipients = ( await mailFacade . loadMailDetailsBlob ( mail ) ) . recipients . bccRecipients
2022-01-07 15:58:30 +01:00
return _checkEmailAddresses (
add MailDetails feature, #4719
server issues: 1276, 1271, 1279, 1272, 1270, 1258, 1254, 1253, 1242, 1241
2022-11-03 19:03:54 +01:00
bccRecipients . map ( ( m ) = > m . address ) ,
2022-01-07 15:58:30 +01:00
inboxRule ,
)
} else if ( ruleType === InboxRuleType . SUBJECT_CONTAINS ) {
return _checkContainsRule ( mail . subject , inboxRule )
} else if ( ruleType === InboxRuleType . MAIL_HEADER_CONTAINS ) {
Remove LegacyMailWrapper (legacy mail bodies) and cleanup TutanotaModel
Prior to starting implementing static MailIds and MailSets, we
want to clean up the current TutanotaModel. Therefore, this commit
removes a lot of legacy metamodel definitions that are not used any
longer, including removing the LegacyMailWrapper (legacy mail bodies).
Additionally, this commit inter alia includes:
* removing types no longer needed after migrating to MailDetails, e.g.
the "body", "toRecipients", "ccRecipients", "bccRecipients",
"replyTos", "sentDate" and "headers" references / values from MAIL_TYPE
* removing "mails" reference form MAIL_BOX_TYPE
* removing "subFolders" reference from MAIL_FOLDER
* removing the legacy types MAIL_BODY_TYPE and MAIL_HEADERS
* removing Value.OLD_OWNER_GROUP_NAME, and Value.OLD_AREA_ID_NAME from
FILE_TYPE and CONTACT_TYPE
Closes #7255
Co-authored-by: sug <sug@tutao.de>
2024-07-23 10:05:35 +02:00
const details = await mailFacade . loadMailDetailsBlob ( mail )
if ( details . headers != null ) {
return _checkContainsRule ( getMailHeaders ( details . headers ) , inboxRule )
2022-01-07 15:58:30 +01:00
} else {
return false
}
} else {
console . warn ( "Unknown rule type: " , inboxRule . type )
return false
}
} catch ( e ) {
console . error ( "Error processing inbox rule:" , e . message )
return false
}
2017-08-15 13:54:22 +02:00
}
2021-06-23 17:39:31 +02:00
function _checkContainsRule ( value : string , inboxRule : InboxRule ) : boolean {
2022-01-07 15:58:30 +01:00
return ( isRegularExpression ( inboxRule . value ) && _matchesRegularExpression ( value , inboxRule ) ) || value . includes ( inboxRule . value )
2017-08-15 13:54:22 +02:00
}
2018-07-18 17:22:54 +02:00
2017-08-15 13:54:22 +02:00
/** export for test. */
export function _matchesRegularExpression ( value : string , inboxRule : InboxRule ) : boolean {
2022-01-07 15:58:30 +01:00
if ( isRegularExpression ( inboxRule . value ) ) {
let flags = inboxRule . value . replace ( /.*\/([gimsuy]*)$/ , "$1" )
let pattern = inboxRule . value . replace ( new RegExp ( "^/(.*?)/" + flags + "$" ) , "$1" )
let regExp = new RegExp ( pattern , flags )
return regExp . test ( value )
}
return false
2017-08-15 13:54:22 +02:00
}
2021-06-23 17:39:31 +02:00
function _checkEmailAddresses ( mailAddresses : string [ ] , inboxRule : InboxRule ) : boolean {
2022-12-27 15:37:40 +01:00
const mailAddress = mailAddresses . find ( ( mailAddress ) = > {
2022-01-07 15:58:30 +01:00
let cleanMailAddress = mailAddress . toLowerCase ( ) . trim ( )
if ( isRegularExpression ( inboxRule . value ) ) {
return _matchesRegularExpression ( cleanMailAddress , inboxRule )
} else if ( isDomainName ( inboxRule . value ) ) {
let domain = cleanMailAddress . split ( "@" ) [ 1 ]
return domain === inboxRule . value
} else {
return cleanMailAddress === inboxRule . value
}
} )
return mailAddress != null
2017-08-15 13:54:22 +02:00
}
2024-08-07 08:38:58 +02:00
export function isInboxFolder ( mailboxDetail : MailboxDetail , mail : Mail ) : boolean {
2024-08-07 16:29:40 +02:00
const folders = mailLocator . mailModel . getMailboxFoldersForId ( assertNotNull ( mailboxDetail . mailbox . folders ) . _id )
const mailFolder = folders . getFolderByMail ( mail )
2024-08-07 08:38:58 +02:00
return mailFolder ? . folderType === MailSetKind . INBOX
2022-12-27 15:37:40 +01:00
}