Created
November 20, 2022 22:48
-
-
Save clouedoc/b59cdbc6bd38f3eb06b4df6ae4180acb to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import type { IMailCredentials } from './credentials'; | |
import { findAndFetchMessage } from './fetch-mail'; | |
/** | |
* Check if the credentials are working. | |
* @param credentials | |
* @throws if the credentials are invalid | |
*/ | |
export async function mailCheckValid(credentials: IMailCredentials): Promise<void> { | |
const messages = await findAndFetchMessage(credentials, ['ALL']); | |
if (messages.length < 1) { | |
throw new Error('There are no messages in the mailbox.'); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Credentials to an email account. | |
*/ | |
export interface IMailCredentials { | |
mail: string; | |
password: string; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const messages = await findAndFetchMessage( | |
{ | |
mail: credentials.address, | |
password: credentials.password | |
}, | |
['ALL'] | |
); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import delay from 'delay'; | |
import Imap from 'imap'; | |
import { logger } from '../telemetry'; | |
import type { IMailCredentials } from './credentials'; | |
import type { IMail } from './mail'; | |
import { findMailProvider, providerConfigurationMap, type IMailProviderConfig } from './provider'; | |
/** | |
* Execute the given function with the given mail client | |
* @param credentials | |
* @param cb | |
* @returns | |
*/ | |
async function executeWithMailClient<T>( | |
credentials: IMailCredentials, | |
cb: (imap: Imap) => T | |
): Promise<T> { | |
const mailProviderConfiguration: IMailProviderConfig = | |
providerConfigurationMap[findMailProvider(credentials.mail)]; | |
const imap: Imap = new Imap({ | |
host: mailProviderConfiguration.host, | |
port: mailProviderConfiguration.port, | |
user: credentials.mail, | |
password: credentials.password, | |
tls: true | |
}); | |
logger.silly('Instantiated IMAP client', { credentials }); | |
await Promise.all([ | |
Promise.race([ | |
new Promise<void>((_, reject) => { | |
imap.once('error', (err: Error) => { | |
reject(err); | |
}); | |
}), | |
new Promise<void>((resolve) => imap.once('ready', resolve)) | |
]), | |
imap.connect() | |
]); | |
logger.silly('Connected IMAP client', { credentials }); | |
const output: T = await cb(imap); | |
imap.end(); | |
return output; | |
} | |
/** | |
* Fetch messages matching the given search criteria | |
* @param credentials the credentials of the mail account | |
* @returns all the messages of the account | |
*/ | |
export async function findAndFetchMessage( | |
credentials: IMailCredentials, | |
searchCriteria: string[] = ['ALL'] | |
): Promise<IMail[]> { | |
return executeWithMailClient(credentials, async (imap) => { | |
return new Promise<IMail[]>((resolve, reject) => { | |
imap.openBox('INBOX', true, (err, box) => { | |
if (err) { | |
reject(err); | |
return; | |
} | |
imap.search([searchCriteria], async (err, results) => { | |
if (err) { | |
reject(err); | |
return; | |
} | |
let f: Imap.ImapFetch; | |
try { | |
f = imap.fetch(results, { | |
bodies: '' | |
}); | |
} catch (err) { | |
reject(err); | |
return; | |
} | |
const messages: IMail[] = []; | |
f.on('message', (msg) => { | |
msg.on('body', async (stream) => { | |
const chunks: Buffer[] = []; | |
stream.on('data', (chunk) => { | |
chunks.push(chunk); | |
}); | |
await new Promise<void>((resolve) => stream.on('end', resolve)); | |
const message: string = Buffer.concat(chunks).toString().replace(/\r/g, ''); | |
const [, headers, body] = /(.*?)\n\n(.*)/s.exec(message) ?? []; | |
if (!headers || !body) { | |
reject(new Error('Could not parse message')); | |
return; | |
} | |
messages.push({ | |
headers: headers | |
.replace(/\n\t/g, '') | |
.split('\n') | |
.map((line) => line.split(':')) | |
.map((line) => [line[0]?.trim(), line[1]?.trim()]) | |
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}), | |
body | |
}); | |
}); | |
}); | |
await delay(2000); | |
resolve(messages); | |
}); | |
}); | |
}); | |
}); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export type { IMailCredentials } from './credentials'; | |
export { findAndFetchMessage } from './fetch-mail'; | |
export type { IMail } from './mail'; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export interface IMail { | |
headers: Record<string, string>; | |
body: string; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { throwExpression } from '../utils'; | |
enum MailProvider { | |
Outlook = 'outlook' | |
} | |
const domainToProviderMapping: Record<string, MailProvider> = { | |
'outlook.com': MailProvider.Outlook | |
}; | |
export interface IMailProviderConfig { | |
host: string; | |
port: number; | |
} | |
export const providerConfigurationMap: Record<MailProvider, IMailProviderConfig> = { | |
[MailProvider.Outlook]: { | |
host: 'outlook.office365.com', | |
port: 993 | |
} | |
}; | |
/** | |
* Find the mail provider of a given email | |
*/ | |
export function findMailProvider(mail: string): MailProvider { | |
const domain: string = mail.split('@')[1]; | |
return ( | |
domainToProviderMapping[domain] ?? throwExpression('Unknown mail provider for domain ' + domain) | |
); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment