mirror of
https://github.com/hexastack/hexabot
synced 2025-06-26 18:27:28 +00:00
feat: use attachment_id instead of url + in messages + webchannel and secure public urls
This commit is contained in:
parent
d48b88f41e
commit
0737cd99c6
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright © 2024 Hexastack. All rights reserved.
|
* Copyright © 2025 Hexastack. All rights reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
* 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.
|
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
||||||
@ -126,6 +126,7 @@ export default abstract class EventWrapper<
|
|||||||
/**
|
/**
|
||||||
* Sets an event attribute value
|
* Sets an event attribute value
|
||||||
*
|
*
|
||||||
|
* @deprecated
|
||||||
* @param attr - Event attribute name
|
* @param attr - Event attribute name
|
||||||
* @param value - The value to set for the specified attribute.
|
* @param value - The value to set for the specified attribute.
|
||||||
*/
|
*/
|
||||||
@ -136,6 +137,7 @@ export default abstract class EventWrapper<
|
|||||||
/**
|
/**
|
||||||
* Returns an event attribute value, default value if it does exist
|
* Returns an event attribute value, default value if it does exist
|
||||||
*
|
*
|
||||||
|
* @deprecated
|
||||||
* @param attr - Event attribute name
|
* @param attr - Event attribute name
|
||||||
* @param otherwise - Default value if attribute does not exist
|
* @param otherwise - Default value if attribute does not exist
|
||||||
*
|
*
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright © 2024 Hexastack. All rights reserved.
|
* Copyright © 2025 Hexastack. All rights reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
* 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.
|
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
||||||
@ -234,7 +234,7 @@ export default abstract class ChannelHandler<
|
|||||||
* @param attachment The attachment ID or object to generate a signed URL for.
|
* @param attachment The attachment ID or object to generate a signed URL for.
|
||||||
* @return A signed URL string for downloading the specified attachment.
|
* @return A signed URL string for downloading the specified attachment.
|
||||||
*/
|
*/
|
||||||
protected async getPublicUrl(attachment: string | Attachment) {
|
public async getPublicUrl(attachment: string | Attachment) {
|
||||||
const resource =
|
const resource =
|
||||||
typeof attachment === 'string'
|
typeof attachment === 'string'
|
||||||
? await this.attachmentService.findOne(attachment)
|
? await this.attachmentService.findOne(attachment)
|
||||||
|
|||||||
@ -17,8 +17,9 @@ export enum FileType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type AttachmentForeignKey = {
|
export type AttachmentForeignKey = {
|
||||||
url?: string;
|
|
||||||
attachment_id: string;
|
attachment_id: string;
|
||||||
|
/** @deprecated use "attachment_id" instead */
|
||||||
|
url?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface AttachmentPayload<
|
export interface AttachmentPayload<
|
||||||
@ -30,7 +31,5 @@ export interface AttachmentPayload<
|
|||||||
|
|
||||||
export interface IncomingAttachmentPayload {
|
export interface IncomingAttachmentPayload {
|
||||||
type: FileType;
|
type: FileType;
|
||||||
payload: {
|
payload: AttachmentForeignKey;
|
||||||
url: string;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright © 2024 Hexastack. All rights reserved.
|
* Copyright © 2025 Hexastack. All rights reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
* 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.
|
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
||||||
@ -390,6 +390,7 @@ describe('BlockService', () => {
|
|||||||
attachments: {
|
attachments: {
|
||||||
type: FileType.file,
|
type: FileType.file,
|
||||||
payload: {
|
payload: {
|
||||||
|
attachment_id: '9'.repeat(24),
|
||||||
url: 'http://link.to/the/file',
|
url: 'http://link.to/the/file',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright © 2024 Hexastack. All rights reserved.
|
* Copyright © 2025 Hexastack. All rights reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
* 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.
|
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
||||||
@ -77,7 +77,7 @@ export const webList: Web.OutgoingMessageBase = {
|
|||||||
type: ButtonType.postback,
|
type: ButtonType.postback,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
image_url: 'http://localhost:4000/attachment/download/1/attachment.jpg',
|
image_url: 'http://public.url/download/filename.extension?t=any',
|
||||||
subtitle: 'About being first',
|
subtitle: 'About being first',
|
||||||
title: 'First',
|
title: 'First',
|
||||||
},
|
},
|
||||||
@ -89,7 +89,7 @@ export const webList: Web.OutgoingMessageBase = {
|
|||||||
type: ButtonType.postback,
|
type: ButtonType.postback,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
image_url: 'http://localhost:4000/attachment/download/1/attachment.jpg',
|
image_url: 'http://public.url/download/filename.extension?t=any',
|
||||||
subtitle: 'About being second',
|
subtitle: 'About being second',
|
||||||
title: 'Second',
|
title: 'Second',
|
||||||
},
|
},
|
||||||
@ -109,7 +109,7 @@ export const webCarousel: Web.OutgoingMessageBase = {
|
|||||||
type: ButtonType.postback,
|
type: ButtonType.postback,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
image_url: 'http://localhost:4000/attachment/download/1/attachment.jpg',
|
image_url: 'http://public.url/download/filename.extension?t=any',
|
||||||
subtitle: 'About being first',
|
subtitle: 'About being first',
|
||||||
title: 'First',
|
title: 'First',
|
||||||
},
|
},
|
||||||
@ -121,7 +121,7 @@ export const webCarousel: Web.OutgoingMessageBase = {
|
|||||||
type: ButtonType.postback,
|
type: ButtonType.postback,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
image_url: 'http://localhost:4000/attachment/download/1/attachment.jpg',
|
image_url: 'http://public.url/download/filename.extension?t=any',
|
||||||
subtitle: 'About being second',
|
subtitle: 'About being second',
|
||||||
title: 'Second',
|
title: 'Second',
|
||||||
},
|
},
|
||||||
@ -140,7 +140,7 @@ export const webAttachment: Web.OutgoingMessageBase = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
type: FileType.image,
|
type: FileType.image,
|
||||||
url: 'http://localhost:4000/attachment/download/1/attachment.jpg',
|
url: 'http://public.url/download/filename.extension?t=any',
|
||||||
},
|
},
|
||||||
type: Web.OutgoingMessageType.file,
|
type: Web.OutgoingMessageType.file,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright © 2024 Hexastack. All rights reserved.
|
* Copyright © 2025 Hexastack. All rights reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
* 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.
|
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
||||||
@ -14,9 +14,6 @@ import {
|
|||||||
|
|
||||||
import { Web } from '../types';
|
import { Web } from '../types';
|
||||||
|
|
||||||
const img_url =
|
|
||||||
'http://demo.hexabot.ai/attachment/download/5c334078e2c41d11206bd152/myimage.png';
|
|
||||||
|
|
||||||
// Web events
|
// Web events
|
||||||
const webEventPayload: Web.Event = {
|
const webEventPayload: Web.Event = {
|
||||||
type: Web.IncomingMessageType.postback,
|
type: Web.IncomingMessageType.postback,
|
||||||
@ -55,9 +52,10 @@ const webEventLocation: Web.IncomingMessage = {
|
|||||||
const webEventFile: Web.Event = {
|
const webEventFile: Web.Event = {
|
||||||
type: Web.IncomingMessageType.file,
|
type: Web.IncomingMessageType.file,
|
||||||
data: {
|
data: {
|
||||||
type: FileType.image,
|
type: 'image/png',
|
||||||
url: img_url,
|
|
||||||
size: 500,
|
size: 500,
|
||||||
|
name: 'filename.extension',
|
||||||
|
file: Buffer.from('my-image', 'utf-8'),
|
||||||
},
|
},
|
||||||
author: 'web-9be8ac09-b43a-432d-bca0-f11b98cec1ad',
|
author: 'web-9be8ac09-b43a-432d-bca0-f11b98cec1ad',
|
||||||
mid: 'web-event-file',
|
mid: 'web-event-file',
|
||||||
@ -151,18 +149,18 @@ export const webEvents: [string, Web.IncomingMessage, any][] = [
|
|||||||
attachments: {
|
attachments: {
|
||||||
type: FileType.image,
|
type: FileType.image,
|
||||||
payload: {
|
payload: {
|
||||||
url: img_url,
|
attachment_id: '9'.repeat(24),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
message: {
|
message: {
|
||||||
attachment: {
|
attachment: {
|
||||||
payload: {
|
payload: {
|
||||||
url: img_url,
|
attachment_id: '9'.repeat(24),
|
||||||
},
|
},
|
||||||
type: FileType.image,
|
type: FileType.image,
|
||||||
},
|
},
|
||||||
serialized_text: `attachment:image:${img_url}`,
|
serialized_text: 'attachment:image:filename.extension',
|
||||||
type: IncomingMessageType.attachments,
|
type: IncomingMessageType.attachments,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright © 2024 Hexastack. All rights reserved.
|
* Copyright © 2025 Hexastack. All rights reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
* 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.
|
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
||||||
@ -125,9 +125,14 @@ describe('WebChannelHandler', () => {
|
|||||||
}).compile();
|
}).compile();
|
||||||
subscriberService = module.get<SubscriberService>(SubscriberService);
|
subscriberService = module.get<SubscriberService>(SubscriberService);
|
||||||
handler = module.get<WebChannelHandler>(WebChannelHandler);
|
handler = module.get<WebChannelHandler>(WebChannelHandler);
|
||||||
|
|
||||||
|
jest
|
||||||
|
.spyOn(handler, 'getPublicUrl')
|
||||||
|
.mockResolvedValue('http://public.url/download/filename.extension?t=any');
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
|
jest.restoreAllMocks();
|
||||||
await closeInMongodConnection();
|
await closeInMongodConnection();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -204,15 +209,15 @@ describe('WebChannelHandler', () => {
|
|||||||
expect(formatted).toEqual(webButtons);
|
expect(formatted).toEqual(webButtons);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should format list properly', () => {
|
it('should format list properly', async () => {
|
||||||
const formatted = handler._listFormat(contentMessage, {
|
const formatted = await handler._listFormat(contentMessage, {
|
||||||
content: contentMessage.options,
|
content: contentMessage.options,
|
||||||
});
|
});
|
||||||
expect(formatted).toEqual(webList);
|
expect(formatted).toEqual(webList);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should format carousel properly', () => {
|
it('should format carousel properly', async () => {
|
||||||
const formatted = handler._carouselFormat(contentMessage, {
|
const formatted = await handler._carouselFormat(contentMessage, {
|
||||||
content: {
|
content: {
|
||||||
...contentMessage.options,
|
...contentMessage.options,
|
||||||
display: OutgoingMessageFormat.carousel,
|
display: OutgoingMessageFormat.carousel,
|
||||||
@ -221,8 +226,8 @@ describe('WebChannelHandler', () => {
|
|||||||
expect(formatted).toEqual(webCarousel);
|
expect(formatted).toEqual(webCarousel);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should format attachment properly', () => {
|
it('should format attachment properly', async () => {
|
||||||
const formatted = handler._attachmentFormat(attachmentMessage, {});
|
const formatted = await handler._attachmentFormat(attachmentMessage, {});
|
||||||
expect(formatted).toEqual(webAttachment);
|
expect(formatted).toEqual(webAttachment);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright © 2024 Hexastack. All rights reserved.
|
* Copyright © 2025 Hexastack. All rights reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
* 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.
|
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
||||||
@ -13,13 +13,20 @@ import { MongooseModule } from '@nestjs/mongoose';
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
|
||||||
import { AttachmentRepository } from '@/attachment/repositories/attachment.repository';
|
import { AttachmentRepository } from '@/attachment/repositories/attachment.repository';
|
||||||
import { AttachmentModel } from '@/attachment/schemas/attachment.schema';
|
import {
|
||||||
|
Attachment,
|
||||||
|
AttachmentModel,
|
||||||
|
} from '@/attachment/schemas/attachment.schema';
|
||||||
import { AttachmentService } from '@/attachment/services/attachment.service';
|
import { AttachmentService } from '@/attachment/services/attachment.service';
|
||||||
import { ChannelService } from '@/channel/channel.service';
|
import { ChannelService } from '@/channel/channel.service';
|
||||||
import { MessageRepository } from '@/chat/repositories/message.repository';
|
import { MessageRepository } from '@/chat/repositories/message.repository';
|
||||||
import { SubscriberRepository } from '@/chat/repositories/subscriber.repository';
|
import { SubscriberRepository } from '@/chat/repositories/subscriber.repository';
|
||||||
import { MessageModel } from '@/chat/schemas/message.schema';
|
import { MessageModel } from '@/chat/schemas/message.schema';
|
||||||
import { SubscriberModel } from '@/chat/schemas/subscriber.schema';
|
import { SubscriberModel } from '@/chat/schemas/subscriber.schema';
|
||||||
|
import {
|
||||||
|
IncomingMessageType,
|
||||||
|
StdEventType,
|
||||||
|
} from '@/chat/schemas/types/message';
|
||||||
import { MessageService } from '@/chat/services/message.service';
|
import { MessageService } from '@/chat/services/message.service';
|
||||||
import { SubscriberService } from '@/chat/services/subscriber.service';
|
import { SubscriberService } from '@/chat/services/subscriber.service';
|
||||||
import { MenuRepository } from '@/cms/repositories/menu.repository';
|
import { MenuRepository } from '@/cms/repositories/menu.repository';
|
||||||
@ -122,6 +129,18 @@ describe(`Web event wrapper`, () => {
|
|||||||
e,
|
e,
|
||||||
expected.channelData,
|
expected.channelData,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
event._adapter.eventType === StdEventType.message &&
|
||||||
|
event._adapter.messageType === IncomingMessageType.attachments
|
||||||
|
) {
|
||||||
|
event._adapter.attachment = {
|
||||||
|
id: '9'.repeat(24),
|
||||||
|
type: 'image/png',
|
||||||
|
name: 'filename.extension',
|
||||||
|
} as Attachment;
|
||||||
|
}
|
||||||
|
|
||||||
expect(event.getChannelData()).toEqual({
|
expect(event.getChannelData()).toEqual({
|
||||||
...expected.channelData,
|
...expected.channelData,
|
||||||
name: WEB_CHANNEL_NAME,
|
name: WEB_CHANNEL_NAME,
|
||||||
|
|||||||
@ -22,11 +22,13 @@ import { MessageCreateDto } from '@/chat/dto/message.dto';
|
|||||||
import { SubscriberCreateDto } from '@/chat/dto/subscriber.dto';
|
import { SubscriberCreateDto } from '@/chat/dto/subscriber.dto';
|
||||||
import { VIEW_MORE_PAYLOAD } from '@/chat/helpers/constants';
|
import { VIEW_MORE_PAYLOAD } from '@/chat/helpers/constants';
|
||||||
import { Subscriber, SubscriberFull } from '@/chat/schemas/subscriber.schema';
|
import { Subscriber, SubscriberFull } from '@/chat/schemas/subscriber.schema';
|
||||||
|
import { AttachmentForeignKey } from '@/chat/schemas/types/attachment';
|
||||||
import { Button, ButtonType } from '@/chat/schemas/types/button';
|
import { Button, ButtonType } from '@/chat/schemas/types/button';
|
||||||
import {
|
import {
|
||||||
AnyMessage,
|
AnyMessage,
|
||||||
ContentElement,
|
ContentElement,
|
||||||
IncomingMessage,
|
IncomingMessage,
|
||||||
|
IncomingMessageType,
|
||||||
OutgoingMessage,
|
OutgoingMessage,
|
||||||
OutgoingMessageFormat,
|
OutgoingMessageFormat,
|
||||||
PayloadType,
|
PayloadType,
|
||||||
@ -127,9 +129,9 @@ export default abstract class BaseWebChannelHandler<
|
|||||||
* @param incoming - Incoming message
|
* @param incoming - Incoming message
|
||||||
* @returns Formatted web message
|
* @returns Formatted web message
|
||||||
*/
|
*/
|
||||||
private formatIncomingHistoryMessage(
|
private async formatIncomingHistoryMessage(
|
||||||
incoming: IncomingMessage,
|
incoming: IncomingMessage,
|
||||||
): Web.IncomingMessageBase {
|
): Promise<Web.IncomingMessageBase> {
|
||||||
// Format incoming message
|
// Format incoming message
|
||||||
if ('type' in incoming.message) {
|
if ('type' in incoming.message) {
|
||||||
if (incoming.message.type === PayloadType.location) {
|
if (incoming.message.type === PayloadType.location) {
|
||||||
@ -145,14 +147,17 @@ export default abstract class BaseWebChannelHandler<
|
|||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
// @TODO : handle multiple files
|
// @TODO : handle multiple files
|
||||||
const attachment = Array.isArray(incoming.message.attachment)
|
const attachmentPayload = Array.isArray(incoming.message.attachment)
|
||||||
? incoming.message.attachment[0]
|
? incoming.message.attachment[0]
|
||||||
: incoming.message.attachment;
|
: incoming.message.attachment;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: Web.IncomingMessageType.file,
|
type: Web.IncomingMessageType.file,
|
||||||
data: {
|
data: {
|
||||||
type: attachment.type,
|
type: attachmentPayload.type,
|
||||||
url: attachment.payload.url,
|
url: await this.getPublicUrl(
|
||||||
|
attachmentPayload.payload.attachment_id,
|
||||||
|
),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -170,9 +175,9 @@ export default abstract class BaseWebChannelHandler<
|
|||||||
* @param outgoing - The outgoing message
|
* @param outgoing - The outgoing message
|
||||||
* @returns Formatted web message
|
* @returns Formatted web message
|
||||||
*/
|
*/
|
||||||
private formatOutgoingHistoryMessage(
|
private async formatOutgoingHistoryMessage(
|
||||||
outgoing: OutgoingMessage,
|
outgoing: OutgoingMessage,
|
||||||
): Web.OutgoingMessageBase {
|
): Promise<Web.OutgoingMessageBase> {
|
||||||
// Format outgoing message
|
// Format outgoing message
|
||||||
if ('buttons' in outgoing.message) {
|
if ('buttons' in outgoing.message) {
|
||||||
return this._buttonsFormat(outgoing.message);
|
return this._buttonsFormat(outgoing.message);
|
||||||
@ -182,11 +187,11 @@ export default abstract class BaseWebChannelHandler<
|
|||||||
return this._quickRepliesFormat(outgoing.message);
|
return this._quickRepliesFormat(outgoing.message);
|
||||||
} else if ('options' in outgoing.message) {
|
} else if ('options' in outgoing.message) {
|
||||||
if (outgoing.message.options.display === 'carousel') {
|
if (outgoing.message.options.display === 'carousel') {
|
||||||
return this._carouselFormat(outgoing.message, {
|
return await this._carouselFormat(outgoing.message, {
|
||||||
content: outgoing.message.options,
|
content: outgoing.message.options,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return this._listFormat(outgoing.message, {
|
return await this._listFormat(outgoing.message, {
|
||||||
content: outgoing.message.options,
|
content: outgoing.message.options,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -212,12 +217,14 @@ export default abstract class BaseWebChannelHandler<
|
|||||||
*
|
*
|
||||||
* @returns Formatted message
|
* @returns Formatted message
|
||||||
*/
|
*/
|
||||||
protected formatMessages(messages: AnyMessage[]): Web.Message[] {
|
protected async formatMessages(
|
||||||
|
messages: AnyMessage[],
|
||||||
|
): Promise<Web.Message[]> {
|
||||||
const formattedMessages: Web.Message[] = [];
|
const formattedMessages: Web.Message[] = [];
|
||||||
|
|
||||||
for (const anyMessage of messages) {
|
for (const anyMessage of messages) {
|
||||||
if (this.isIncomingMessage(anyMessage)) {
|
if (this.isIncomingMessage(anyMessage)) {
|
||||||
const message = this.formatIncomingHistoryMessage(anyMessage);
|
const message = await this.formatIncomingHistoryMessage(anyMessage);
|
||||||
formattedMessages.push({
|
formattedMessages.push({
|
||||||
...message,
|
...message,
|
||||||
author: anyMessage.sender,
|
author: anyMessage.sender,
|
||||||
@ -226,7 +233,7 @@ export default abstract class BaseWebChannelHandler<
|
|||||||
createdAt: anyMessage.createdAt,
|
createdAt: anyMessage.createdAt,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const message = this.formatOutgoingHistoryMessage(anyMessage);
|
const message = await this.formatOutgoingHistoryMessage(anyMessage);
|
||||||
formattedMessages.push({
|
formattedMessages.push({
|
||||||
...message,
|
...message,
|
||||||
author: 'chatbot',
|
author: 'chatbot',
|
||||||
@ -261,7 +268,7 @@ export default abstract class BaseWebChannelHandler<
|
|||||||
until,
|
until,
|
||||||
n,
|
n,
|
||||||
);
|
);
|
||||||
return this.formatMessages(messages.reverse());
|
return await this.formatMessages(messages.reverse());
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@ -286,7 +293,7 @@ export default abstract class BaseWebChannelHandler<
|
|||||||
since,
|
since,
|
||||||
n,
|
n,
|
||||||
);
|
);
|
||||||
return this.formatMessages(messages);
|
return await this.formatMessages(messages);
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@ -579,9 +586,8 @@ export default abstract class BaseWebChannelHandler<
|
|||||||
'since' in req.query
|
'since' in req.query
|
||||||
? req.query.since // Long polling case
|
? req.query.since // Long polling case
|
||||||
: req.body?.since || undefined; // Websocket case
|
: req.body?.since || undefined; // Websocket case
|
||||||
return this.fetchHistory(req, criteria).then((messages) => {
|
const messages = await this.fetchHistory(req, criteria);
|
||||||
return res.status(200).json({ profile, messages });
|
return res.status(200).json({ profile, messages });
|
||||||
});
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.logger.warn('Web Channel Handler : Unable to subscribe ', err);
|
this.logger.warn('Web Channel Handler : Unable to subscribe ', err);
|
||||||
return res.status(500).json({ err: 'Unable to subscribe' });
|
return res.status(500).json({ err: 'Unable to subscribe' });
|
||||||
@ -589,59 +595,51 @@ export default abstract class BaseWebChannelHandler<
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Upload file as attachment if provided
|
* Handle upload via WebSocket
|
||||||
*
|
*
|
||||||
* @param req Either a HTTP Express request or a WS request (Synthetic Object)
|
* @returns The stored attachment or null
|
||||||
* @param res Either a HTTP Express response or a WS response (Synthetic Object)
|
|
||||||
* @param next Callback Function
|
|
||||||
*/
|
*/
|
||||||
async handleFilesUpload(
|
async handleWsUpload(req: SocketRequest): Promise<Attachment | null> {
|
||||||
req: Request | SocketRequest,
|
try {
|
||||||
res: Response | SocketResponse,
|
const { type, data } = req.body as Web.IncomingMessage;
|
||||||
next: (
|
|
||||||
err: null | Error,
|
|
||||||
result?: Web.IncomingAttachmentMessageData,
|
|
||||||
) => void,
|
|
||||||
): Promise<void> {
|
|
||||||
// Check if any file is provided
|
|
||||||
if (!req.session.web) {
|
|
||||||
this.logger.debug('Web Channel Handler : No session provided');
|
|
||||||
return next(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.isSocketRequest(req)) {
|
// Check if any file is provided
|
||||||
try {
|
if (type !== 'file' || !('file' in data) || !data.file) {
|
||||||
const { type, data } = req.body as Web.IncomingMessage;
|
this.logger.debug('Web Channel Handler : No files provided');
|
||||||
|
return null;
|
||||||
// Check if any file is provided
|
|
||||||
if (type !== 'file' || !('file' in data) || !data.file) {
|
|
||||||
this.logger.debug('Web Channel Handler : No files provided');
|
|
||||||
return next(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
const size = Buffer.byteLength(data.file);
|
|
||||||
|
|
||||||
if (size > config.parameters.maxUploadSize) {
|
|
||||||
return next(new Error('Max upload size has been exceeded'));
|
|
||||||
}
|
|
||||||
|
|
||||||
const attachment = await this.attachmentService.store(data.file, {
|
|
||||||
name: data.name,
|
|
||||||
size: Buffer.byteLength(data.file),
|
|
||||||
type: data.type,
|
|
||||||
});
|
|
||||||
next(null, {
|
|
||||||
type: Attachment.getTypeByMime(attachment.type),
|
|
||||||
url: Attachment.getAttachmentUrl(attachment.id, attachment.name),
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
this.logger.error(
|
|
||||||
'Web Channel Handler : Unable to write uploaded file',
|
|
||||||
err,
|
|
||||||
);
|
|
||||||
return next(new Error('Unable to upload file!'));
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
|
const size = Buffer.byteLength(data.file);
|
||||||
|
|
||||||
|
if (size > config.parameters.maxUploadSize) {
|
||||||
|
throw new Error('Max upload size has been exceeded');
|
||||||
|
}
|
||||||
|
|
||||||
|
const attachment = await this.attachmentService.store(data.file, {
|
||||||
|
name: data.name,
|
||||||
|
size: Buffer.byteLength(data.file),
|
||||||
|
type: data.type,
|
||||||
|
});
|
||||||
|
return attachment;
|
||||||
|
} catch (err) {
|
||||||
|
this.logger.error(
|
||||||
|
'Web Channel Handler : Unable to store uploaded file',
|
||||||
|
err,
|
||||||
|
);
|
||||||
|
throw new Error('Unable to upload file!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle multipart/form-data upload
|
||||||
|
*
|
||||||
|
* @returns The stored attachment or null
|
||||||
|
*/
|
||||||
|
async handleWebUpload(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
): Promise<Attachment | null> {
|
||||||
|
try {
|
||||||
const upload = multer({
|
const upload = multer({
|
||||||
limits: {
|
limits: {
|
||||||
fileSize: config.parameters.maxUploadSize,
|
fileSize: config.parameters.maxUploadSize,
|
||||||
@ -655,36 +653,66 @@ export default abstract class BaseWebChannelHandler<
|
|||||||
})(),
|
})(),
|
||||||
}).single('file'); // 'file' is the field name in the form
|
}).single('file'); // 'file' is the field name in the form
|
||||||
|
|
||||||
upload(req as Request, res as Response, async (err?: any) => {
|
const multerUpload = new Promise<Express.Multer.File | null>(
|
||||||
if (err) {
|
(resolve, reject) => {
|
||||||
this.logger.error(
|
upload(req as Request, res as Response, async (err?: any) => {
|
||||||
'Web Channel Handler : Unable to write uploaded file',
|
if (err) {
|
||||||
err,
|
this.logger.error(
|
||||||
);
|
'Web Channel Handler : Unable to store uploaded file',
|
||||||
return next(new Error('Unable to upload file!'));
|
err,
|
||||||
}
|
);
|
||||||
|
reject(new Error('Unable to upload file!'));
|
||||||
|
}
|
||||||
|
|
||||||
// Check if any file is provided
|
resolve(req.file);
|
||||||
if (!req.file) {
|
});
|
||||||
this.logger.debug('Web Channel Handler : No files provided');
|
},
|
||||||
return next(null);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
const file = await multerUpload;
|
||||||
const file = req.file;
|
|
||||||
const attachment = await this.attachmentService.store(file, {
|
// Check if any file is provided
|
||||||
name: file.originalname,
|
if (!req.file) {
|
||||||
size: file.size,
|
this.logger.debug('Web Channel Handler : No files provided');
|
||||||
type: file.mimetype,
|
return null;
|
||||||
});
|
}
|
||||||
next(null, {
|
|
||||||
type: Attachment.getTypeByMime(attachment.type),
|
const attachment = await this.attachmentService.store(file, {
|
||||||
url: Attachment.getAttachmentUrl(attachment.id, attachment.name),
|
name: file.originalname,
|
||||||
});
|
size: file.size,
|
||||||
} catch (err) {
|
type: file.mimetype,
|
||||||
next(err);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
return attachment;
|
||||||
|
} catch (err) {
|
||||||
|
this.logger.error(
|
||||||
|
'Web Channel Handler : Unable to store uploaded file',
|
||||||
|
err,
|
||||||
|
);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upload file as attachment if provided
|
||||||
|
*
|
||||||
|
* @param req Either a HTTP Express request or a WS request (Synthetic Object)
|
||||||
|
* @param res Either a HTTP Express response or a WS response (Synthetic Object)
|
||||||
|
* @param next Callback Function
|
||||||
|
*/
|
||||||
|
async handleUpload(
|
||||||
|
req: Request | SocketRequest,
|
||||||
|
res: Response | SocketResponse,
|
||||||
|
): Promise<Attachment | null> {
|
||||||
|
// Check if any file is provided
|
||||||
|
if (!req.session.web) {
|
||||||
|
this.logger.debug('Web Channel Handler : No session provided');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isSocketRequest(req)) {
|
||||||
|
return this.handleWsUpload(req);
|
||||||
|
} else {
|
||||||
|
return this.handleWebUpload(req, res as Response);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -748,12 +776,21 @@ export default abstract class BaseWebChannelHandler<
|
|||||||
: req.body.data;
|
: req.body.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.validateSession(req, res, (profile) => {
|
this.validateSession(req, res, async (profile) => {
|
||||||
this.handleFilesUpload(
|
// Set data in file upload case
|
||||||
req,
|
const body: Web.IncomingMessage = req.body;
|
||||||
res,
|
|
||||||
(err: Error, data?: Web.IncomingAttachmentMessageData) => {
|
const channelAttrs = this.getChannelAttributes(req);
|
||||||
if (err) {
|
const event = new WebEventWrapper<N>(this, body, channelAttrs);
|
||||||
|
if (event._adapter.eventType === StdEventType.message) {
|
||||||
|
// Handle upload when files are provided
|
||||||
|
if (event._adapter.messageType === IncomingMessageType.attachments) {
|
||||||
|
try {
|
||||||
|
const attachment = await this.handleUpload(req, res);
|
||||||
|
if (attachment) {
|
||||||
|
event._adapter.attachment = attachment;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
this.logger.warn(
|
this.logger.warn(
|
||||||
'Web Channel Handler : Unable to upload file ',
|
'Web Channel Handler : Unable to upload file ',
|
||||||
err,
|
err,
|
||||||
@ -762,49 +799,39 @@ export default abstract class BaseWebChannelHandler<
|
|||||||
.status(403)
|
.status(403)
|
||||||
.json({ err: 'Web Channel Handler : File upload failed!' });
|
.json({ err: 'Web Channel Handler : File upload failed!' });
|
||||||
}
|
}
|
||||||
// Set data in file upload case
|
}
|
||||||
const body: Web.IncomingMessage = data
|
|
||||||
? {
|
|
||||||
...req.body,
|
|
||||||
data,
|
|
||||||
}
|
|
||||||
: req.body;
|
|
||||||
|
|
||||||
const channelAttrs = this.getChannelAttributes(req);
|
// Handler sync message sent by chabbot
|
||||||
const event = new WebEventWrapper<N>(this, body, channelAttrs);
|
if (body.sync && body.author === 'chatbot') {
|
||||||
if (event.getEventType() === 'message') {
|
const sentMessage: MessageCreateDto = {
|
||||||
// Handler sync message sent by chabbot
|
mid: event.getId(),
|
||||||
if (body.sync && body.author === 'chatbot') {
|
message: event.getMessage() as StdOutgoingMessage,
|
||||||
const sentMessage: MessageCreateDto = {
|
recipient: profile.id,
|
||||||
mid: event.getId(),
|
read: true,
|
||||||
message: event.getMessage() as StdOutgoingMessage,
|
delivery: true,
|
||||||
recipient: profile.id,
|
};
|
||||||
read: true,
|
this.eventEmitter.emit('hook:chatbot:sent', sentMessage, event);
|
||||||
delivery: true,
|
return res.status(200).json(event._adapter.raw);
|
||||||
};
|
} else {
|
||||||
this.eventEmitter.emit('hook:chatbot:sent', sentMessage, event);
|
// Generate unique ID and handle message
|
||||||
return res.status(200).json(event._adapter.raw);
|
event._adapter.raw.mid = this.generateId();
|
||||||
} else {
|
|
||||||
// Generate unique ID and handle message
|
|
||||||
event.set('mid', this.generateId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Force author id from session
|
// Force author id from session
|
||||||
event.set('author', profile.foreign_id);
|
event._adapter.raw.author = profile.foreign_id;
|
||||||
event.setSender(profile);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const type = event.getEventType();
|
event.setSender(profile);
|
||||||
if (type) {
|
|
||||||
this.eventEmitter.emit(`hook:chatbot:${type}`, event);
|
const type = event.getEventType();
|
||||||
} else {
|
if (type) {
|
||||||
this.logger.error(
|
this.eventEmitter.emit(`hook:chatbot:${type}`, event);
|
||||||
'Web Channel Handler : Webhook received unknown event ',
|
} else {
|
||||||
event,
|
this.logger.error(
|
||||||
);
|
'Web Channel Handler : Webhook received unknown event ',
|
||||||
}
|
event,
|
||||||
res.status(200).json(event._adapter.raw);
|
);
|
||||||
},
|
}
|
||||||
);
|
res.status(200).json(event._adapter.raw);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -957,15 +984,15 @@ export default abstract class BaseWebChannelHandler<
|
|||||||
*
|
*
|
||||||
* @returns A ready to be sent attachment message
|
* @returns A ready to be sent attachment message
|
||||||
*/
|
*/
|
||||||
_attachmentFormat(
|
async _attachmentFormat(
|
||||||
message: StdOutgoingAttachmentMessage<Attachment>,
|
message: StdOutgoingAttachmentMessage<Attachment>,
|
||||||
_options?: BlockOptions,
|
_options?: BlockOptions,
|
||||||
): Web.OutgoingMessageBase {
|
): Promise<Web.OutgoingMessageBase> {
|
||||||
const payload: Web.OutgoingMessageBase = {
|
const payload: Web.OutgoingMessageBase = {
|
||||||
type: Web.OutgoingMessageType.file,
|
type: Web.OutgoingMessageType.file,
|
||||||
data: {
|
data: {
|
||||||
type: message.attachment.type,
|
type: message.attachment.type,
|
||||||
url: message.attachment.payload.url,
|
url: await this.getPublicUrl(message.attachment.payload.id),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
if (message.quickReplies && message.quickReplies.length > 0) {
|
if (message.quickReplies && message.quickReplies.length > 0) {
|
||||||
@ -982,35 +1009,42 @@ export default abstract class BaseWebChannelHandler<
|
|||||||
*
|
*
|
||||||
* @returns An array of elements object
|
* @returns An array of elements object
|
||||||
*/
|
*/
|
||||||
_formatElements(
|
async _formatElements(
|
||||||
data: ContentElement[],
|
data: ContentElement[],
|
||||||
options: BlockOptions,
|
options: BlockOptions,
|
||||||
): Web.MessageElement[] {
|
): Promise<Web.MessageElement[]> {
|
||||||
if (!options.content || !options.content.fields) {
|
if (!options.content || !options.content.fields) {
|
||||||
throw new Error('Content options are missing the fields');
|
throw new Error('Content options are missing the fields');
|
||||||
}
|
}
|
||||||
|
|
||||||
const fields = options.content.fields;
|
const fields = options.content.fields;
|
||||||
const buttons: Button[] = options.content.buttons;
|
const buttons: Button[] = options.content.buttons;
|
||||||
return data.map((item) => {
|
const result: Web.MessageElement[] = [];
|
||||||
|
|
||||||
|
for (const item of data) {
|
||||||
const element: Web.MessageElement = {
|
const element: Web.MessageElement = {
|
||||||
title: item[fields.title],
|
title: item[fields.title],
|
||||||
buttons: item.buttons || [],
|
buttons: item.buttons || [],
|
||||||
};
|
};
|
||||||
|
|
||||||
if (fields.subtitle && item[fields.subtitle]) {
|
if (fields.subtitle && item[fields.subtitle]) {
|
||||||
element.subtitle = item[fields.subtitle];
|
element.subtitle = item[fields.subtitle];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fields.image_url && item[fields.image_url]) {
|
if (fields.image_url && item[fields.image_url]) {
|
||||||
const attachmentPayload = item[fields.image_url].payload;
|
const attachmentPayload = item[fields.image_url]
|
||||||
|
.payload as AttachmentForeignKey;
|
||||||
if (attachmentPayload.url) {
|
if (attachmentPayload.url) {
|
||||||
if (!attachmentPayload.id) {
|
if (!attachmentPayload.attachment_id) {
|
||||||
// @deprecated
|
// @deprecated
|
||||||
this.logger.warn(
|
this.logger.warn(
|
||||||
'Web Channel Handler: Attachment remote url has been deprecated',
|
'Web Channel Handler: Attachment remote url has been deprecated',
|
||||||
item,
|
item,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
element.image_url = attachmentPayload.url;
|
element.image_url = await this.getPublicUrl(
|
||||||
|
attachmentPayload.attachment_id,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1047,11 +1081,15 @@ export default abstract class BaseWebChannelHandler<
|
|||||||
}
|
}
|
||||||
element.buttons?.push(btn);
|
element.buttons?.push(btn);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (Array.isArray(element.buttons) && element.buttons.length === 0) {
|
if (Array.isArray(element.buttons) && element.buttons.length === 0) {
|
||||||
delete element.buttons;
|
delete element.buttons;
|
||||||
}
|
}
|
||||||
return element;
|
|
||||||
});
|
result.push(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1062,10 +1100,10 @@ export default abstract class BaseWebChannelHandler<
|
|||||||
*
|
*
|
||||||
* @returns A ready to be sent list template message
|
* @returns A ready to be sent list template message
|
||||||
*/
|
*/
|
||||||
_listFormat(
|
async _listFormat(
|
||||||
message: StdOutgoingListMessage,
|
message: StdOutgoingListMessage,
|
||||||
options: BlockOptions,
|
options: BlockOptions,
|
||||||
): Web.OutgoingMessageBase {
|
): Promise<Web.OutgoingMessageBase> {
|
||||||
const data = message.elements || [];
|
const data = message.elements || [];
|
||||||
const pagination = message.pagination;
|
const pagination = message.pagination;
|
||||||
let buttons: Button[] = [],
|
let buttons: Button[] = [],
|
||||||
@ -1091,7 +1129,7 @@ export default abstract class BaseWebChannelHandler<
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Populate items (elements/cards) with content
|
// Populate items (elements/cards) with content
|
||||||
elements = this._formatElements(data, options);
|
elements = await this._formatElements(data, options);
|
||||||
const topElementStyle = options.content?.top_element_style
|
const topElementStyle = options.content?.top_element_style
|
||||||
? {
|
? {
|
||||||
top_element_style: options.content?.top_element_style,
|
top_element_style: options.content?.top_element_style,
|
||||||
@ -1115,10 +1153,10 @@ export default abstract class BaseWebChannelHandler<
|
|||||||
*
|
*
|
||||||
* @returns A carousel ready to be sent as a message
|
* @returns A carousel ready to be sent as a message
|
||||||
*/
|
*/
|
||||||
_carouselFormat(
|
async _carouselFormat(
|
||||||
message: StdOutgoingListMessage,
|
message: StdOutgoingListMessage,
|
||||||
options: BlockOptions,
|
options: BlockOptions,
|
||||||
): Web.OutgoingMessageBase {
|
): Promise<Web.OutgoingMessageBase> {
|
||||||
const data = message.elements || [];
|
const data = message.elements || [];
|
||||||
// Items count min check
|
// Items count min check
|
||||||
if (data.length === 0) {
|
if (data.length === 0) {
|
||||||
@ -1129,7 +1167,7 @@ export default abstract class BaseWebChannelHandler<
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Populate items (elements/cards) with content
|
// Populate items (elements/cards) with content
|
||||||
const elements = this._formatElements(data, options);
|
const elements = await this._formatElements(data, options);
|
||||||
return {
|
return {
|
||||||
type: Web.OutgoingMessageType.carousel,
|
type: Web.OutgoingMessageType.carousel,
|
||||||
data: {
|
data: {
|
||||||
@ -1146,19 +1184,19 @@ export default abstract class BaseWebChannelHandler<
|
|||||||
*
|
*
|
||||||
* @returns A template filled with its payload
|
* @returns A template filled with its payload
|
||||||
*/
|
*/
|
||||||
_formatMessage(
|
async _formatMessage(
|
||||||
envelope: StdOutgoingEnvelope,
|
envelope: StdOutgoingEnvelope,
|
||||||
options: BlockOptions,
|
options: BlockOptions,
|
||||||
): Web.OutgoingMessageBase {
|
): Promise<Web.OutgoingMessageBase> {
|
||||||
switch (envelope.format) {
|
switch (envelope.format) {
|
||||||
case OutgoingMessageFormat.attachment:
|
case OutgoingMessageFormat.attachment:
|
||||||
return this._attachmentFormat(envelope.message, options);
|
return await this._attachmentFormat(envelope.message, options);
|
||||||
case OutgoingMessageFormat.buttons:
|
case OutgoingMessageFormat.buttons:
|
||||||
return this._buttonsFormat(envelope.message, options);
|
return this._buttonsFormat(envelope.message, options);
|
||||||
case OutgoingMessageFormat.carousel:
|
case OutgoingMessageFormat.carousel:
|
||||||
return this._carouselFormat(envelope.message, options);
|
return await this._carouselFormat(envelope.message, options);
|
||||||
case OutgoingMessageFormat.list:
|
case OutgoingMessageFormat.list:
|
||||||
return this._listFormat(envelope.message, options);
|
return await this._listFormat(envelope.message, options);
|
||||||
case OutgoingMessageFormat.quickReplies:
|
case OutgoingMessageFormat.quickReplies:
|
||||||
return this._quickRepliesFormat(envelope.message, options);
|
return this._quickRepliesFormat(envelope.message, options);
|
||||||
case OutgoingMessageFormat.text:
|
case OutgoingMessageFormat.text:
|
||||||
@ -1206,7 +1244,7 @@ export default abstract class BaseWebChannelHandler<
|
|||||||
options: BlockOptions,
|
options: BlockOptions,
|
||||||
_context?: any,
|
_context?: any,
|
||||||
): Promise<{ mid: string }> {
|
): Promise<{ mid: string }> {
|
||||||
const messageBase: Web.OutgoingMessageBase = this._formatMessage(
|
const messageBase: Web.OutgoingMessageBase = await this._formatMessage(
|
||||||
envelope,
|
envelope,
|
||||||
options,
|
options,
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright © 2024 Hexastack. All rights reserved.
|
* Copyright © 2025 Hexastack. All rights reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
* 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.
|
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
||||||
@ -59,13 +59,13 @@ export namespace Web {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// Depending if it's has been processed or not
|
|
||||||
export type IncomingAttachmentMessageData =
|
export type IncomingAttachmentMessageData =
|
||||||
// After upload and attachment is processed
|
// When it's a incoming history message
|
||||||
| {
|
| {
|
||||||
type: FileType;
|
type: FileType;
|
||||||
url: string; // file download url
|
url: string; // file download url
|
||||||
} // Before upload and attachment is processed
|
}
|
||||||
|
// When it's a file upload message
|
||||||
| {
|
| {
|
||||||
type: string; // mime type
|
type: string; // mime type
|
||||||
size: number; // file size
|
size: number; // file size
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright © 2024 Hexastack. All rights reserved.
|
* Copyright © 2025 Hexastack. All rights reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
* 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.
|
* 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).
|
* 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 { Attachment } from '@/attachment/schemas/attachment.schema';
|
||||||
import EventWrapper from '@/channel/lib/EventWrapper';
|
import EventWrapper from '@/channel/lib/EventWrapper';
|
||||||
import { ChannelName } from '@/channel/types';
|
import { ChannelName } from '@/channel/types';
|
||||||
import {
|
import {
|
||||||
@ -66,14 +67,13 @@ type WebEventAdapter =
|
|||||||
eventType: StdEventType.message;
|
eventType: StdEventType.message;
|
||||||
messageType: IncomingMessageType.attachments;
|
messageType: IncomingMessageType.attachments;
|
||||||
raw: Web.IncomingMessage<Web.IncomingAttachmentMessage>;
|
raw: Web.IncomingMessage<Web.IncomingAttachmentMessage>;
|
||||||
|
attachment: Attachment | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line prettier/prettier
|
// eslint-disable-next-line prettier/prettier
|
||||||
export default class WebEventWrapper<N extends ChannelName> extends EventWrapper<
|
export default class WebEventWrapper<
|
||||||
WebEventAdapter,
|
N extends ChannelName,
|
||||||
Web.Event,
|
> extends EventWrapper<WebEventAdapter, Web.Event, N> {
|
||||||
N
|
|
||||||
> {
|
|
||||||
/**
|
/**
|
||||||
* Constructor : channel's event wrapper
|
* Constructor : channel's event wrapper
|
||||||
*
|
*
|
||||||
@ -216,16 +216,16 @@ export default class WebEventWrapper<N extends ChannelName> extends EventWrapper
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
case IncomingMessageType.attachments:
|
case IncomingMessageType.attachments:
|
||||||
if (!('url' in this._adapter.raw.data)) {
|
if (!this._adapter.attachment) {
|
||||||
throw new Error('Attachment has not been processed');
|
throw new Error('Attachment has not been processed');
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: PayloadType.attachments,
|
type: PayloadType.attachments,
|
||||||
attachments: {
|
attachments: {
|
||||||
type: this._adapter.raw.data.type,
|
type: Attachment.getTypeByMime(this._adapter.raw.data.type),
|
||||||
payload: {
|
payload: {
|
||||||
url: this._adapter.raw.data.url,
|
attachment_id: this._adapter.attachment.id,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -266,19 +266,20 @@ export default class WebEventWrapper<N extends ChannelName> extends EventWrapper
|
|||||||
}
|
}
|
||||||
|
|
||||||
case IncomingMessageType.attachments: {
|
case IncomingMessageType.attachments: {
|
||||||
const attachment = this._adapter.raw.data;
|
if (!this._adapter.attachment) {
|
||||||
|
|
||||||
if (!('url' in attachment)) {
|
|
||||||
throw new Error('Attachment has not been processed');
|
throw new Error('Attachment has not been processed');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fileType = Attachment.getTypeByMime(
|
||||||
|
this._adapter.attachment.type,
|
||||||
|
);
|
||||||
return {
|
return {
|
||||||
type: PayloadType.attachments,
|
type: PayloadType.attachments,
|
||||||
serialized_text: `attachment:${attachment.type}:${attachment.url}`,
|
serialized_text: `attachment:${fileType}:${this._adapter.attachment.name}`,
|
||||||
attachment: {
|
attachment: {
|
||||||
type: attachment.type,
|
type: fileType,
|
||||||
payload: {
|
payload: {
|
||||||
url: attachment.url,
|
attachment_id: this._adapter.attachment.id,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user