Update sent folder regex (#14069)

This commit is contained in:
neo773
2025-08-25 21:17:31 +05:30
committed by GitHub
parent 734b0bd2d2
commit 0c8631c1c8
3 changed files with 180 additions and 23 deletions
@@ -3,6 +3,8 @@ import { Injectable, Logger } from '@nestjs/common';
import { isNumber } from 'class-validator';
import { ListResponse, type ImapFlow } from 'imapflow';
import { getImapSentFolderCandidatesByRegex } from 'src/modules/messaging/message-import-manager/drivers/imap/utils/get-sent-folder-candidates-by-regex.util';
/**
* Service to find sent folder using IMAP special-use flags
*
@@ -89,7 +91,7 @@ export class ImapFindSentFolderService {
client: ImapFlow,
list: ListResponse[],
): Promise<string | null> {
const regexCandidateFolders = this.getSentFolderCandidatesByRegex(list);
const regexCandidateFolders = getImapSentFolderCandidatesByRegex(list);
for (const folder of regexCandidateFolders) {
const messageCount = await this.getFolderMessageCount(client, folder);
@@ -112,28 +114,6 @@ export class ImapFindSentFolderService {
return null;
}
private getSentFolderCandidatesByRegex(list: ListResponse[]): string[] {
// Comprehensive regex pattern for legacy IMAP servers
// Source: https://imapsync.lamiral.info/FAQ.d/FAQ.Folders_Mapping.txt
// Based on imapsync's regextrans2 examples (originally "Sent|Sent Messages|Gesendet")
// Extended with additional common localizations for broader provider/language support
const sentFolderPattern =
/^(.*\/)?(sent|sent[\s_-]?(items|mail|messages|elements)?|envoy[éê]s?|[ée]l[ée]ments[\s_-]?envoy[éê]s|gesendet|gesendete[\s_-]?elemente|enviados?|elementos[\s_-]?enviados|itens[\s_-]?enviados|posta[\s_-]?inviata|inviati|보낸편지함|\[gmail\]\/sent[\s_-]?mail)$/i;
const regexCandidateFolders = [];
for (const folder of list) {
if (sentFolderPattern.test(folder.path)) {
this.logger.debug(
`Found potential sent folder via pattern match: ${folder.path}`,
);
regexCandidateFolders.push(folder.path);
}
}
return regexCandidateFolders;
}
private async getFolderMessageCount(
client: ImapFlow,
folderPath: string,
@@ -0,0 +1,124 @@
import { type ListResponse } from 'imapflow';
import { getImapSentFolderCandidatesByRegex } from 'src/modules/messaging/message-import-manager/drivers/imap/utils/get-sent-folder-candidates-by-regex.util';
function makeList(paths: string[]): ListResponse[] {
return paths.map((p) => ({ path: p }) as ListResponse);
}
describe('getSentFolderCandidatesByRegex', () => {
it('matches English variants', () => {
const englishVariants = [
'Sent',
'Sent Items',
'Sent Mail',
'Sent Messages',
'sent-elements',
];
const input = makeList(englishVariants);
const result = getImapSentFolderCandidatesByRegex(input);
expect(result).toEqual(expect.arrayContaining(englishVariants));
});
it('matches French variants', () => {
const frenchVariants = ['Envoyés', 'Éléments envoyés', 'Objets envoyés'];
const input = makeList(frenchVariants);
const result = getImapSentFolderCandidatesByRegex(input);
expect(result).toEqual(expect.arrayContaining(frenchVariants));
});
it('matches German variants', () => {
const germanVariants = ['Gesendet', 'Gesendete Elemente'];
const input = makeList(germanVariants);
const result = getImapSentFolderCandidatesByRegex(input);
expect(result).toEqual(expect.arrayContaining(germanVariants));
});
it('matches Spanish variants', () => {
const spanishVariants = ['Enviados', 'Elementos enviados'];
const input = makeList(spanishVariants);
const result = getImapSentFolderCandidatesByRegex(input);
expect(result).toEqual(expect.arrayContaining(spanishVariants));
});
it('matches Portuguese variants', () => {
const portugueseVariants = ['Itens enviados'];
const input = makeList(portugueseVariants);
const result = getImapSentFolderCandidatesByRegex(input);
expect(result).toEqual(expect.arrayContaining(portugueseVariants));
});
it('matches Italian variants', () => {
const italianVariants = ['Posta inviata', 'Inviati'];
const input = makeList(italianVariants);
const result = getImapSentFolderCandidatesByRegex(input);
expect(result).toEqual(expect.arrayContaining(italianVariants));
});
it('matches Korean variant', () => {
const koreanVariants = ['보낸편지함'];
const input = makeList(koreanVariants);
const result = getImapSentFolderCandidatesByRegex(input);
expect(result).toEqual(koreanVariants);
});
it('matches Japanese variants', () => {
const japaneseVariants = ['送信済みメール', '送信済み'];
const input = makeList(japaneseVariants);
const result = getImapSentFolderCandidatesByRegex(input);
expect(result).toEqual(expect.arrayContaining(japaneseVariants));
});
it('matches Polish variants', () => {
const polishVariants = ['Wysłane', 'Elementy wysłane'];
const input = makeList(polishVariants);
const result = getImapSentFolderCandidatesByRegex(input);
expect(result).toEqual(expect.arrayContaining(polishVariants));
});
it('matches Russian variants', () => {
const russianVariants = [
'Отправленные',
'Отправленные письма',
'Отправленные сообщения',
'Исходящие',
];
const input = makeList(russianVariants);
const result = getImapSentFolderCandidatesByRegex(input);
expect(result).toEqual(expect.arrayContaining(russianVariants));
});
it('matches Gmail special folder', () => {
const gmailVariants = ['[Gmail]/Sent Mail', '[Gmail]\\Sent Mail'];
const input = makeList(gmailVariants);
const result = getImapSentFolderCandidatesByRegex(input);
expect(result).toEqual(expect.arrayContaining(gmailVariants));
});
it('does not match unrelated folders', () => {
const unrelatedFolders = [
'Inbox',
'Drafts',
'Trash',
'Archive',
'Junk',
'Important',
'RandomFolder',
];
const input = makeList(unrelatedFolders);
const result = getImapSentFolderCandidatesByRegex(input);
expect(result).toEqual([]);
});
});
@@ -0,0 +1,53 @@
import { type ListResponse } from 'imapflow';
export function getImapSentFolderCandidatesByRegex(
list: ListResponse[],
): string[] {
const sentFolderPattern = new RegExp(
[
// EN
'sent([\\s_-]?(items|mail|messages|elements))?',
// FR
'envoy[éê]s?',
'[ée]l[ée]ments[\\s_-]?envoy[éê]s',
// DE
'gesendet',
'gesendete[\\s_-]?elemente',
// ES
'enviados?',
'elementos[\\s_-]?enviados',
// PT
'itens[\\s_-]?enviados',
// IT
'posta[\\s_-]?inviata',
'inviati',
// KO
'보낸편지함',
// JA
'送信済みメール',
'送信済み',
// PL
'elementy[\\s_-]?wysłane',
'wysłane',
// RU
'отправленные',
'отправленные[\\s_-]?(сообщения|письма)?',
'исходящие',
// GMAIL
'\\[gmail\\][\\/]+sent[\\s_-]?mail',
]
.map((s) => `(${s})`)
.join('|'),
'i',
);
const regexCandidateFolders = [];
for (const folder of list) {
if (sentFolderPattern.test(folder.path)) {
regexCandidateFolders.push(folder.path);
}
}
return regexCandidateFolders;
}