128 lines
3.7 KiB
JavaScript
128 lines
3.7 KiB
JavaScript
|
|
import { ImapFlow } from 'imapflow';
|
|
import EventEmitter from 'node:events';
|
|
import 'dotenv/config';
|
|
import { simpleParser } from 'mailparser';
|
|
|
|
|
|
|
|
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');
|
|
|
|
// 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 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
|
|
},
|
|
logger: false
|
|
});
|
|
|
|
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, null, { 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
|
|
// commenting these out because it lags Tilt
|
|
// 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();
|