Clean up email reading logic
This commit is contained in:
120
src/mail/read.ts
120
src/mail/read.ts
@@ -0,0 +1,120 @@
|
||||
import {Iterable} from "../bones/collection/Iterable.ts";
|
||||
import type {Message} from "../types.ts";
|
||||
import {type FetchMessageObject, type FetchQueryObject, ImapFlow} from "imapflow";
|
||||
import {config} from "../config.ts";
|
||||
import type {Awaitable, Maybe} from "../bones";
|
||||
import {ReplyParser} from "./replies.ts";
|
||||
import {extract} from "letterparser";
|
||||
import {Collection} from "../bones/collection/Collection.ts";
|
||||
import {AsyncCollection} from "../bones/collection/AsyncCollection.ts";
|
||||
|
||||
export async function withMailbox<T>(mailbox: string, cb: (c: AsyncCollection<Message>) => Awaitable<T>): Promise<T> {
|
||||
return MailboxIterable.with(mailbox, i => cb(new AsyncCollection(i, 100)))
|
||||
}
|
||||
|
||||
export class MailboxIterable extends Iterable<Message> {
|
||||
private client?: Maybe<ImapFlow>
|
||||
private query: FetchQueryObject = {
|
||||
envelope: true,
|
||||
source: true,
|
||||
uid: true,
|
||||
bodyParts: ['text'],
|
||||
}
|
||||
|
||||
public static async with<T>(mailbox: string, cb: (i: MailboxIterable) => Awaitable<T>): Promise<T> {
|
||||
const inst = new MailboxIterable(mailbox)
|
||||
let value: T
|
||||
|
||||
try {
|
||||
value = await cb(inst)
|
||||
} finally {
|
||||
await inst.release()
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
constructor(
|
||||
public readonly mailbox: string,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
async at(i: number): Promise<Maybe<Message>> {
|
||||
return this.withMailbox(async client => {
|
||||
const m = await client.fetchOne(`${i+1}`, this.query)
|
||||
return this.format(m)
|
||||
})
|
||||
}
|
||||
|
||||
async range(start: number, end: number): Promise<Collection<Message>> {
|
||||
return this.withMailbox(async client => {
|
||||
const m = await client.fetchAll(`${start+1}:${end+1}`, this.query)
|
||||
return Collection.normalize(m)
|
||||
.map(i => this.format(i))
|
||||
})
|
||||
}
|
||||
|
||||
async count(): Promise<number> {
|
||||
return this.withMailbox(async client => {
|
||||
const m = await client.status(this.mailbox, {
|
||||
messages: true,
|
||||
})
|
||||
|
||||
return m.messages || 0
|
||||
})
|
||||
}
|
||||
|
||||
clone(): MailboxIterable {
|
||||
return new MailboxIterable(this.mailbox)
|
||||
}
|
||||
|
||||
protected format(message: FetchMessageObject): Message {
|
||||
const source = message.source.toString('utf-8')
|
||||
const content = ReplyParser.parseReply(extract(source).text || '')
|
||||
|
||||
return {
|
||||
id: `${this.mailbox}.${message.uid}`,
|
||||
date: message.envelope.date,
|
||||
recipients: message.envelope.to.map(x => x.address || '').filter(Boolean),
|
||||
from: message.envelope.from[0],
|
||||
subject: message.envelope.subject,
|
||||
content,
|
||||
}
|
||||
}
|
||||
|
||||
public async release(): Promise<void> {
|
||||
if ( this.client ) {
|
||||
await this.client.logout()
|
||||
}
|
||||
}
|
||||
|
||||
protected async withMailbox<T>(cb: (client: ImapFlow) => Awaitable<T>): Promise<T> {
|
||||
const client = await this.getClient()
|
||||
const lock = await client.getMailboxLock(this.mailbox)
|
||||
|
||||
let value: T
|
||||
try {
|
||||
await client.mailboxOpen(this.mailbox)
|
||||
value = await cb(client)
|
||||
} finally {
|
||||
lock.release()
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
protected async getClient(): Promise<ImapFlow> {
|
||||
if ( this.client ) {
|
||||
return this.client
|
||||
}
|
||||
|
||||
const client = new ImapFlow({
|
||||
...config.mail.imap,
|
||||
logger: false,
|
||||
})
|
||||
|
||||
await client.connect()
|
||||
return this.client = client
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user