Clean up email reading logic

This commit is contained in:
2024-12-30 22:37:16 -05:00
parent 063809e446
commit 9f750bc2eb
4 changed files with 125 additions and 221 deletions

View File

@@ -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
}
}