448 lines
11 KiB
TypeScript
448 lines
11 KiB
TypeScript
/**
|
|
* 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<string, string> {
|
|
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<MessageAddressObject> | null {
|
|
return this._data.replyTo ? this._data.replyTo.map(addr => new MessageAddressObject(addr)) : null;
|
|
}
|
|
|
|
get to(): Array<MessageAddressObject> | null {
|
|
return this._data.to ? this._data.to.map(addr => new MessageAddressObject(addr)) : null;
|
|
}
|
|
|
|
get cc(): Array<MessageAddressObject> | null {
|
|
return this._data.cc ? this._data.cc.map(addr => new MessageAddressObject(addr)) : null;
|
|
}
|
|
|
|
get bcc(): Array<MessageAddressObject> | 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<MessagePartObject> {
|
|
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<MessagePartInterface>) {
|
|
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;
|
|
}
|
|
|
|
}
|
|
|