Implement regexp-based thread ID parsing from recipient email
This commit is contained in:
20
src/mail/client.ts
Normal file
20
src/mail/client.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import {ImapFlow} from "imapflow";
|
||||
import type {Awaitable} from "../bones";
|
||||
import {config} from "../config.ts";
|
||||
|
||||
export async function withClient<T>(cb: (client: ImapFlow) => Awaitable<T>): Promise<T> {
|
||||
const client = new ImapFlow({
|
||||
...config.mail.imap,
|
||||
logger: false,
|
||||
})
|
||||
|
||||
await client.connect()
|
||||
|
||||
let result: T
|
||||
try {
|
||||
result = await cb(client)
|
||||
} finally {
|
||||
await client.logout()
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -5,14 +5,31 @@ 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 {collect, Collection} from "../bones/collection/Collection.ts";
|
||||
import {AsyncCollection} from "../bones/collection/AsyncCollection.ts";
|
||||
import {withClient} from "./client.ts";
|
||||
import {buildThreadAddressMatcher} from "../threads/id.ts";
|
||||
|
||||
export async function getMailboxesToSearch(thread?: string): Promise<Collection<string>> {
|
||||
// There are 2 possibilities for where mail might end up.
|
||||
// Either the mail-server is configured to forward the + extensions
|
||||
// to their own folders automatically (e.g. the "c.1234" folder), or
|
||||
// aliased mail just winds up in INBOX.
|
||||
return collect(await withClient(c => c.list()))
|
||||
.filter(box =>
|
||||
box.name === 'INBOX' || box.specialUse === '\\\\Inbox'
|
||||
|| (!thread && box.name.startsWith(config.mail.threads.idPrefix))
|
||||
|| (thread && box.name === thread)
|
||||
)
|
||||
.pluck('name')
|
||||
}
|
||||
|
||||
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 addressMatcher: RegExp
|
||||
private client?: Maybe<ImapFlow>
|
||||
private query: FetchQueryObject = {
|
||||
envelope: true,
|
||||
@@ -38,6 +55,7 @@ export class MailboxIterable extends Iterable<Message> {
|
||||
public readonly mailbox: string,
|
||||
) {
|
||||
super()
|
||||
this.addressMatcher = buildThreadAddressMatcher()
|
||||
}
|
||||
|
||||
async at(i: number): Promise<Maybe<Message>> {
|
||||
@@ -73,13 +91,22 @@ export class MailboxIterable extends Iterable<Message> {
|
||||
const source = message.source.toString('utf-8')
|
||||
const content = ReplyParser.parseReply(extract(source).text || '')
|
||||
|
||||
const recipients = message.envelope.to
|
||||
.map(x => x.address || '')
|
||||
.filter(Boolean)
|
||||
|
||||
const thread = recipients.map(addr => this.addressMatcher.exec(addr))
|
||||
.map(result => result?.[1])
|
||||
.filter(Boolean)[0]
|
||||
|
||||
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,
|
||||
recipients,
|
||||
content,
|
||||
thread,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user