/** * Message and MessagePart model classes */ import type { MessageAddressInterface, MessageInterface, MessageModelInterface, MessagePartInterface, MessagePartModelInterface } from "@/types/message"; import { clonePlain } from './clone-plain'; /** * Message class for working with message objects */ export class MessageObject implements MessageModelInterface { _data: MessageInterface; _body: MessagePartObject | null = null; constructor() { this._data = { '@type': 'mail:message', }; this._body = null; } fromJson(data: MessageInterface): MessageObject { this._data = clonePlain(data); this._body = null; return this; } toJson(): MessageInterface { const json = this._body ? { ...this._data, body: this._body.toJson(), } : this._data; return clonePlain(json); } clone(): MessageObject { return new MessageObject().fromJson(this.toJson()); } /** Properties */ get size(): number { return this._data.size ?? 0; } get headers(): Record { return clonePlain(this._data.headers ?? {}); } get urid(): string | null{ return this._data.urid ?? null; } get inReplyTo(): string | null { return this._data.inReplyTo ?? null; } get references(): string | null { return this._data.references ?? null; } get received(): string | null { return this._data.received ?? null; } get sent(): string | null { return this._data.sent ?? null; } get sender(): MessageAddressObject | null { return this._data.sender ? new MessageAddressObject(this._data.sender) : null; } get from(): MessageAddressObject | null { return this._data.from ? new MessageAddressObject(this._data.from) : null; } get replyTo(): Array | null { return this._data.replyTo ? this._data.replyTo.map(addr => new MessageAddressObject(addr)) : null; } get to(): Array | null { return this._data.to ? this._data.to.map(addr => new MessageAddressObject(addr)) : null; } get cc(): Array | null { return this._data.cc ? this._data.cc.map(addr => new MessageAddressObject(addr)) : null; } get bcc(): Array | null { return this._data.bcc ? this._data.bcc.map(addr => new MessageAddressObject(addr)) : null; } get subject(): string | null { return this._data.subject ?? null; } get body(): MessagePartObject | null { if (this._body) { return this._body; } else if (this._data.body) { this._body = new MessagePartObject(this._data.body); return this._body; } return null; } get attachments(): Array { return this._data.attachments ? this._data.attachments.map(att => new MessagePartObject(att)) : []; } get flags(): { read?: boolean; flagged?: boolean; answered?: boolean; draft?: boolean } | {} { return clonePlain(this._data.flags ?? {}); } /** Helper methods */ get isRead(): boolean { return this._data.flags?.read ?? false; } get isFlagged(): boolean { return this._data.flags?.flagged ?? false; } get isAnswered(): boolean { return this._data.flags?.answered ?? false; } get isDraft(): boolean { return this._data.flags?.draft ?? false; } get hasAttachments(): boolean { return (this._data.attachments?.length ?? 0) > 0; } hasRecipients(): boolean { return (this._data.to?.length ?? 0) > 0 || (this._data.cc?.length ?? 0) > 0 || (this._data.bcc?.length ?? 0) > 0; } /** Body content helpers */ getBody(): MessagePartObject | null { if (!this._body && this._data.body) { this._body = new MessagePartObject(this._data.body); } return this._body; } hasContent(): boolean { return !!this.getTextContent() || !!this.getHtmlContent(); } hasTextContent(): boolean { return !!this.getTextContent(); } getTextContent(): string | null { const bodyPart = this.getBody(); return bodyPart ? bodyPart.extractTextContent() : null; } hasHtmlContent(): boolean { return !!this.getHtmlContent(); } getHtmlContent(): string | null { const bodyPart = this.getBody(); return bodyPart ? bodyPart.extractHtmlContent() : null; } findPartById(partId: string): MessagePartInterface | null { const bodyPart = this.getBody(); return bodyPart ? bodyPart.findPartById(partId) : null; } findPartsByType(type: string): MessagePartInterface[] { const bodyPart = this.getBody(); return bodyPart ? bodyPart.findPartsByType(type) : []; } } export class MessageAddressObject implements MessageAddressInterface { _data: MessageAddressInterface; constructor(data: MessageAddressInterface) { this._data = clonePlain(data); } fromJson(data: MessageAddressInterface): MessageAddressObject { this._data = clonePlain(data); return this; } toJson(): MessageAddressInterface { return clonePlain(this._data); } clone(): MessageAddressObject { return new MessageAddressObject(this.toJson()); } /** Properties */ get address(): string { return this._data.address; } get label(): string | undefined { return this._data.label; } } /** * MessagePart class for working with message body parts */ export class MessagePartObject implements MessagePartModelInterface { _data: MessagePartInterface; _subParts: MessagePartObject[] = []; constructor(data?: Partial) { this._data = { partId: clonePlain(data?.partId ?? null), blobId: clonePlain(data?.blobId ?? null), size: clonePlain(data?.size ?? null), name: clonePlain(data?.name ?? null), type: clonePlain(data?.type ?? null), charset: clonePlain(data?.charset ?? null), disposition: clonePlain(data?.disposition ?? null), cid: clonePlain(data?.cid ?? null), language: clonePlain(data?.language ?? null), location: clonePlain(data?.location ?? null), content: clonePlain(data?.content ?? null), subParts: clonePlain(data?.subParts ?? []), }; } fromJson(data: MessagePartInterface): MessagePartObject { this._data = clonePlain(data); this._subParts = []; return this; } toJson(): MessagePartInterface { const json = this._subParts.length > 0 ? { ...this._data, subParts: this._subParts.map(subPart => subPart.toJson()), } : this._data; return clonePlain(json); } clone(): MessagePartObject { return new MessagePartObject().fromJson(this.toJson()); } /** Properties */ get partId(): string | null { return this._data.partId ?? null; } get blobId(): string | null { return this._data.blobId ?? null; } get size(): number | null { return this._data.size ?? null; } get name(): string | null { return this._data.name ?? null; } get type(): string | null { return this._data.type ?? null; } get charset(): string | null { return this._data.charset ?? null; } get disposition(): string | null { return this._data.disposition ?? null; } get cid(): string | null { return this._data.cid ?? null; } get language(): string | null { return this._data.language ?? null; } get location(): string | null { return this._data.location ?? null; } get content(): string | null { return this._data.content ?? null; } get subParts(): MessagePartModelInterface[] { if (this._subParts) { return this._subParts; } else if (this._data.subParts) { this._subParts = this._data.subParts.map((subPart) => new MessagePartObject(subPart)); return this._subParts; } return []; } /** Helper methods */ hasContent(): boolean { return !!this._data.content; } hasSubParts(): boolean { return this.subParts.length > 0; } isMultipart(): boolean { return this._data.type?.startsWith('multipart/') ?? false; } isText(): boolean { return this._data.type === 'text/plain'; } isHtml(): boolean { return this._data.type === 'text/html'; } isAttachment(): boolean { return this._data.disposition === 'attachment'; } isInline(): boolean { return this._data.disposition === 'inline'; } /** * Find a part by partId (recursive search) */ findPartById(partId: string): MessagePartInterface | null { if (this._data.partId === partId) { return this._data; } if (this._data.subParts) { for (const subPart of this._data.subParts) { const part = new MessagePartObject(subPart); const found = part.findPartById(partId); if (found) { return found; } } } return null; } /** * Find all parts of a specific type (recursive search) */ findPartsByType(type: string): MessagePartInterface[] { const parts: MessagePartInterface[] = []; if (this._data.type === type) { parts.push(this._data); } if (this._data.subParts) { for (const subPart of this._data.subParts) { const part = new MessagePartObject(subPart); parts.push(...part.findPartsByType(type)); } } return parts; } /** * Extract text content from body structure */ extractTextContent(): string | null { if (this._data.type === 'text/plain' && this._data.content) { return this._data.content; } if (this._data.subParts) { for (const subPart of this._data.subParts) { const part = new MessagePartObject(subPart); const content = part.extractTextContent(); if (content) { return content; } } } return null; } /** * Extract HTML content from body structure */ extractHtmlContent(): string | null { if (this._data.type === 'text/html' && this._data.content) { return this._data.content; } if (this._data.subParts) { for (const subPart of this._data.subParts) { const part = new MessagePartObject(subPart); const content = part.extractHtmlContent(); if (content) { return content; } } } return null; } }