133 lines
3.9 KiB
JavaScript
133 lines
3.9 KiB
JavaScript
|
|
||
|
import { ImapFlow } from 'imapflow';
|
||
|
import EventEmitter from 'node:events';
|
||
|
import 'dotenv/config';
|
||
|
import { simpleParser } from 'mailparser';
|
||
|
|
||
|
// pinned to v2.0.1 due to https://github.com/jhurliman/node-rate-limiter/issues/80
|
||
|
import * as $limiter from 'limiter';
|
||
|
const { RateLimiter } = $limiter
|
||
|
|
||
|
|
||
|
|
||
|
if (!process.env.SCOUT_IMAP_SERVER) throw new Error('SCOUT_IMAP_SERVER is missing from env');
|
||
|
if (!process.env.SCOUT_IMAP_PORT) throw new Error('SCOUT_IMAP_PORT is missing from env');
|
||
|
if (!process.env.SCOUT_IMAP_USERNAME) throw new Error('SCOUT_IMAP_USERNAME is missing from env');
|
||
|
if (!process.env.SCOUT_IMAP_PASSWORD) throw new Error('SCOUT_IMAP_PASSWORD is missing from env');
|
||
|
|
||
|
const limiter = new RateLimiter({ tokensPerInterval: 1, interval: 3000 });
|
||
|
|
||
|
// https://stackoverflow.com/a/49428486/1004931
|
||
|
function streamToString(stream) {
|
||
|
const chunks = [];
|
||
|
return new Promise((resolve, reject) => {
|
||
|
stream.on('data', (chunk) => chunks.push(Buffer.from(chunk)));
|
||
|
stream.on('error', (err) => reject(err));
|
||
|
stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
|
||
|
})
|
||
|
}
|
||
|
|
||
|
|
||
|
export class Email extends EventEmitter {
|
||
|
|
||
|
|
||
|
constructor() {
|
||
|
super()
|
||
|
this.client = null
|
||
|
}
|
||
|
|
||
|
async archiveMessage(uid) {
|
||
|
await limiter.removeTokens(1);
|
||
|
await this.client.messageDelete(uid, { uid: true })
|
||
|
}
|
||
|
|
||
|
async connect() {
|
||
|
this.client = new ImapFlow({
|
||
|
host: process.env.SCOUT_IMAP_SERVER,
|
||
|
port: process.env.SCOUT_IMAP_PORT,
|
||
|
secure: true,
|
||
|
auth: {
|
||
|
user: process.env.SCOUT_IMAP_USERNAME,
|
||
|
pass: process.env.SCOUT_IMAP_PASSWORD
|
||
|
}
|
||
|
});
|
||
|
|
||
|
this.registerEventListeners()
|
||
|
await this.client.connect()
|
||
|
const stat = await this.getStatus()
|
||
|
if (stat.messages > 0) {
|
||
|
await this.emitAllMessages()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
async reconnect() {
|
||
|
console.log(' RECONNECTING...')
|
||
|
delete this.client
|
||
|
await this.connect()
|
||
|
}
|
||
|
|
||
|
async getStatus() {
|
||
|
let lock = await this.client.getMailboxLock('INBOX');
|
||
|
let status;
|
||
|
try {
|
||
|
status = await this.client.status('INBOX', { messages: true });
|
||
|
} finally {
|
||
|
lock.release()
|
||
|
}
|
||
|
return status
|
||
|
}
|
||
|
|
||
|
async loadMessage(uid) {
|
||
|
console.log(` 💾 loading message uid=${uid}`)
|
||
|
let lock = await this.client.getMailboxLock('INBOX');
|
||
|
let dl, body
|
||
|
try {
|
||
|
dl = await this.client.download(uid, undefined, { uid: true })
|
||
|
body = await streamToString(dl.content)
|
||
|
} finally {
|
||
|
lock.release()
|
||
|
}
|
||
|
return body
|
||
|
}
|
||
|
|
||
|
async emitAllMessages() {
|
||
|
console.log('emitAllMessages is running')
|
||
|
let lock = await this.client.getMailboxLock('INBOX');
|
||
|
try {
|
||
|
for await (let message of this.client.fetch('1:*', { envelope: true })) {
|
||
|
// it is tempting to call this.client.download here, but that is not possible while the mailbox is locked.
|
||
|
// client.download must be called outside of this lock
|
||
|
// console.log('here is a message')
|
||
|
console.log(JSON.stringify(message, null, 2))
|
||
|
this.emit('message', message)
|
||
|
}
|
||
|
} finally {
|
||
|
lock.release();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
registerEventListeners() {
|
||
|
console.log(` > REGISTERING EVENT LISTENERS <`)
|
||
|
this.client.once('end', () => this.reconnect())
|
||
|
this.client.on('exists', (evt) => {
|
||
|
// console.log(`exists event! count=${evt.count} prevCount=${evt.prevCount}`)
|
||
|
// console.log(evt)
|
||
|
if (evt.path === 'INBOX') {
|
||
|
this.emitAllMessages()
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
// // Select and lock a mailbox. Throws if mailbox does not exist
|
||
|
// console.log('get lock')
|
||
|
|
||
|
|
||
|
// // log out and close connection
|
||
|
// // await client.logout();
|