diff --git a/src/utils/jmap.ts b/src/utils/jmap.ts index 60bad85..96d5154 100644 --- a/src/utils/jmap.ts +++ b/src/utils/jmap.ts @@ -165,17 +165,47 @@ export class JmapClient { return '' } - /** 拉取邮件 RFC 5322 原文(含全部头部),通过 Email/get 的 raw 属性(base64) */ + /** 拉取邮件 RFC 5322 原文(含全部头部)。 + * + * 优先用 Email/get 的 raw 属性(RFC 8621,base64);Stalwart 部分版本不支持 + * raw(返回 notFound/空),回退 blobId → Blob/get 取完整消息,兼容 + * data / data:asBytes / data:asText 三种返回格式。 + */ async fetchEmailRaw(emailId: string): Promise { - const payload = await this.call<{ list?: Array<{ raw?: string }> }>('Email/get', { + const payload = await this.call<{ list?: Array<{ raw?: string; blobId?: string }> }>('Email/get', { accountId: this.accountId, ids: [emailId], - properties: ['raw'], + properties: ['raw', 'blobId'], }) - const raw = payload.list?.[0]?.raw - if (raw == null) return '' + const item = payload.list?.[0] + if (!item) return '' + if (item.raw != null) { + try { + return atob(item.raw) + } catch { + /* raw 解码失败,走 blob 回退 */ + } + } + if (!item.blobId) return '' try { - return atob(raw) + const blob = await this.call<{ + list?: Array<{ data?: string; 'data:asText'?: string; 'data:asBytes'?: string }> + }>('Blob/get', { accountId: this.accountId, ids: [item.blobId] }, [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:blob', + ]) + const b = blob.list?.[0] + if (!b) return '' + if (b['data:asText'] != null) return b['data:asText'] + const b64 = b.data ?? b['data:asBytes'] + if (b64) { + try { + return atob(b64) + } catch { + return '' + } + } + return '' } catch { return '' }