hexabot/api/src/channel/lib/EventWrapper.ts

412 lines
9.8 KiB
TypeScript

/*
* Copyright © 2025 Hexastack. All rights reserved.
*
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
*/
import { Subscriber } from '@/chat/schemas/subscriber.schema';
import { AttachmentPayload } from '@/chat/schemas/types/attachment';
import { SubscriberChannelData } from '@/chat/schemas/types/channel';
import {
IncomingMessageType,
StdEventType,
StdIncomingMessage,
} from '@/chat/schemas/types/message';
import { Payload } from '@/chat/schemas/types/quick-reply';
import { NLU } from '@/helper/types';
import { ChannelName } from '../types';
import ChannelHandler from './Handler';
export interface ChannelEvent {}
// eslint-disable-next-line prettier/prettier
export default abstract class EventWrapper<
A extends {
eventType: StdEventType;
messageType?: IncomingMessageType;
raw: E;
},
E,
N extends ChannelName = ChannelName,
C extends ChannelHandler = ChannelHandler<N>,
S = SubscriberChannelDict[N],
> {
_adapter: A = { raw: {}, eventType: StdEventType.unknown } as A;
_handler: C;
channelAttrs: S;
_profile!: Subscriber;
_nlp!: NLU.ParseEntities;
/**
* Constructor : Class used to wrap any channel's event in order
* to provide a unified interface for accessing data by the chatbot.
*
* Any method declared in this class should be extended and overridden in any given channel's
* event wrapper if needed.
* @param handler - The channel's handler
* @param event - The message event received
* @param channelAttrs - Channel's specific data
*/
constructor(handler: C, event: A['raw'], channelAttrs: S = {} as S) {
this._handler = handler;
this._init(event);
this.channelAttrs = channelAttrs;
}
toString() {
return JSON.stringify(
{
handler: this._handler.getName(),
channelData: this.getChannelData(),
sender: this.getSender(),
// recipient: this.getRecipientForeignId(),
eventType: this.getEventType(),
messageType: this.getMessageType(),
payload: this.getPayload(),
message: this.getMessage(),
attachments: this.getAttachments(),
deliveredMessages: this.getDeliveredMessages(),
watermark: this.getWatermark(),
},
null,
4,
);
}
/**
* Called by the parent constructor, it defines `_adapter` which should store :
*
* - `_adapter.eventType` : The type of event received
*
*- `_adapter.messageType` : The type of message when the event is a message.
*
*- `_adapter.raw` : Sets a typed object of the event raw data
* @param event - The message event received from a given channel
*/
abstract _init(event: A['raw']): void;
/**
* Retrieves the current channel handler.
*
* @returns The current instance of the channel handler.
*/
getHandler(): C {
return this._handler;
}
/**
* Retrieves channel data.
*
* @returns Returns any channel related data.
*/
getChannelData(): SubscriberChannelData<N> {
return {
name: this._handler.getName(),
...this.channelAttrs,
} as SubscriberChannelData<N>;
}
/**
* Returns the message id.
* @returns the message id.
*/
abstract getId(): string;
/**
* Sets an event attribute value
*
* @deprecated
* @param attr - Event attribute name
* @param value - The value to set for the specified attribute.
*/
set(attr: string, value: any) {
(this._adapter as any).raw[attr] = value;
}
/**
* Returns an event attribute value, default value if it does exist
*
* @deprecated
* @param attr - Event attribute name
* @param otherwise - Default value if attribute does not exist
*
* @returns The value of the specified attribute or the default value.
*/
get(attr: string, otherwise: any): any {
return attr in (this._adapter as any).raw
? ((this._adapter as any).raw as any)[attr]
: otherwise || {};
}
/**
* Returns attached NLP parse results
*
* @returns The parsed NLP entities, or null if not available.
*/
getNLP(): NLU.ParseEntities | null {
return this._nlp;
}
/**
* Attaches the NLP object to the event
*
* @param nlp - NLP parse results
*/
setNLP(nlp: NLU.ParseEntities) {
this._nlp = nlp;
}
/**
* Returns event sender/profile id (channel's id)
*
* @returns sender/profile id
*/
abstract getSenderForeignId(): string;
/**
* Returns event sender data
*
* @returns event sender data
*/
getSender(): Subscriber {
return this._profile;
}
/**
* Sets event sender data
*
* @param profile - Sender data
*/
setSender(profile: Subscriber) {
this._profile = profile;
}
/**
* Pre-Process the message event
*
* Child class can perform operations such as storing files as attachments.
*/
preprocess() {
// Nothing ...
return Promise.resolve();
}
/**
* Returns event recipient id
*
* @returns event recipient id
*/
abstract getRecipientForeignId(): string;
/**
* Returns the type of event received (message, delivery, read, ...)
*
* @returns The type of event received (message, delivery, read, ...)
*/
getEventType(): StdEventType {
return this._adapter.eventType;
}
/**
* Identifies the type of the message received
*
* @return The type of message
*/
getMessageType(): IncomingMessageType | undefined {
return this._adapter.messageType;
}
/**
* Return payload whenever user clicks on a button/quick_reply or sends an attachment, false otherwise
*
* @returns The payload content
*/
abstract getPayload(): Payload | string | undefined;
/**
* Returns the message in a standardized format
*
* @returns The received message
*/
abstract getMessage(): StdIncomingMessage;
/**
* Return the text message received
*
* @returns Received text message
*/
getText(): string {
const message = this.getMessage();
if ('text' in message) {
return message.text;
} else if ('serialized_text' in message) {
return message.serialized_text;
}
return '';
}
/**
* Returns the list of received attachments
*
* @returns Received attachments message
*/
abstract getAttachments(): AttachmentPayload[];
/**
* Returns the list of delivered messages
*
* @returns Array of message ids
*/
abstract getDeliveredMessages(): string[];
/**
* Returns the message's watermark
*
* @returns The message's watermark
*/
abstract getWatermark(): number;
}
type GenericEvent = { senderId: string; messageId: string };
type GenericEventAdapter = {
eventType: StdEventType.unknown;
messageType: IncomingMessageType.unknown;
raw: GenericEvent;
};
export class GenericEventWrapper extends EventWrapper<
GenericEventAdapter,
GenericEvent,
ChannelName
> {
/**
* Constructor : channel's event wrapper
*
* @param handler - The channel's handler
* @param event - The message event received
*/
constructor(handler: ChannelHandler, event: GenericEvent) {
super(handler, event);
}
/**
* Called by the parent constructor, it defines :
*
* - The type of event received
*
* - The type of message when the event is a message.
*
* - Sets a typed raw object of the event data
* @param event - The message event received
*/
_init(event: GenericEvent): void {
this._adapter.eventType = StdEventType.unknown;
this._adapter.messageType = IncomingMessageType.unknown;
this._adapter.raw = event;
}
/**
* Returns the message id
*
* @returns The message id
*/
getId(): string {
if (this._adapter.raw.messageId) {
return this._adapter.raw.messageId;
}
throw new Error('The message id `mid` is missing');
}
/**
* Returns event sender id
*
* @returns event sender id
*/
getSenderForeignId(): string {
if (this._adapter.raw.senderId) {
return this._adapter.raw.senderId;
}
throw new Error('The sender id is missing');
}
/**
* Returns event recipient id (channel's id)
*
* @returns Returns event recipient id
*/
getRecipientForeignId(): string {
throw new Error('The recipient id is missing');
}
/**
* Returns the type of event received
*
* @returns The type of event received
*/
getEventType(): StdEventType {
return this._adapter.eventType;
}
/**
* Finds out and returns the type of the event received from the channel
*
* @returns The type of message
*/
getMessageType(): IncomingMessageType {
return this._adapter.messageType;
}
/**
* Returns payload whenever user clicks on a button/quick_reply or sends an attachment
*
* @returns The payload content
*/
getPayload(): Payload | string | undefined {
return undefined;
}
/**
* Returns a standard message format that can be stored in DB
*
* @returns Received message in standard format
*/
getMessage(): StdIncomingMessage {
throw new Error('Unknown incoming message type');
}
/**
* @returns A list of received attachments
* @deprecated - This method is deprecated
*/
getAttachments(): AttachmentPayload[] {
return [];
}
/**
* Returns the delivered messages ids
*
* @returns return delivered messages ids
*/
getDeliveredMessages(): string[] {
return [];
}
/**
* Returns the message's watermark (timestamp or equivalent).
*
* @returns The message's watermark
*/
getWatermark() {
return 0;
}
}