diff --git a/api/src/chat/schemas/types/message.ts b/api/src/chat/schemas/types/message.ts index c8c8e14f..7cf43b0a 100644 --- a/api/src/chat/schemas/types/message.ts +++ b/api/src/chat/schemas/types/message.ts @@ -6,14 +6,24 @@ * 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). */ +/* + * 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 { z } from 'zod'; + import { PluginName } from '@/plugins/types'; import { Message } from '../message.schema'; -import { AttachmentPayload } from './attachment'; -import { Button } from './button'; -import { ContentOptions } from './options'; -import { StdQuickReply } from './quick-reply'; +import { attachmentPayloadSchema } from './attachment'; +import { buttonSchema } from './button'; +import { contentOptionsSchema } from './options'; +import { QuickReplyType, stdQuickReplySchema } from './quick-reply'; /** * StdEventType enum is declared, and currently not used @@ -41,6 +51,10 @@ export enum IncomingMessageType { unknown = '', } +export const incomingMessageType = z.nativeEnum(IncomingMessageType); + +export type IncomingMessageTypeLiteral = z.infer; + export enum OutgoingMessageFormat { text = 'text', quickReplies = 'quickReplies', @@ -50,6 +64,12 @@ export enum OutgoingMessageFormat { carousel = 'carousel', } +export const outgoingMessageFormatSchema = z.nativeEnum(OutgoingMessageFormat); + +export type OutgoingMessageFormatLiteral = z.infer< + typeof outgoingMessageFormatSchema +>; + /** * FileType enum is declared, and currently not used **/ @@ -61,6 +81,10 @@ export enum FileType { unknown = 'unknown', } +export const fileTypeSchema = z.nativeEnum(FileType); + +export type FileTypeLiteral = z.infer; + export enum PayloadType { location = 'location', attachments = 'attachments', @@ -68,85 +92,151 @@ export enum PayloadType { button = 'button', } -export type StdOutgoingTextMessage = { text: string }; +export const payloadTypeSchema = z.nativeEnum(PayloadType); -export type StdOutgoingQuickRepliesMessage = { - text: string; - quickReplies: StdQuickReply[]; -}; +export type PayloadTypeLiteral = z.infer; -export type StdOutgoingButtonsMessage = { - text: string; - buttons: Button[]; -}; +export const stdOutgoingTextMessageSchema = z.object({ + text: z.string(), +}); -export type ContentElement = { id: string; title: string } & Record< - string, - any +export type StdOutgoingTextMessage = z.infer< + typeof stdOutgoingTextMessageSchema >; -export type StdOutgoingListMessage = { - options: ContentOptions; - elements: ContentElement[]; - pagination: { - total: number; - skip: number; - limit: number; - }; -}; +export const stdOutgoingQuickRepliesMessageSchema = z.object({ + text: z.string(), + quickReplies: z.array(stdQuickReplySchema), +}); -export type StdOutgoingAttachmentMessage = { - // Stored in DB as `AttachmentPayload`, `Attachment` when populated for channels relaying - attachment: AttachmentPayload; - quickReplies?: StdQuickReply[]; -}; +export type StdOutgoingQuickRepliesMessage = z.infer< + typeof stdOutgoingQuickRepliesMessageSchema +>; -export type StdPluginMessage = { - plugin: PluginName; - args: { [key: string]: any }; -}; +export const stdOutgoingButtonsMessageSchema = z.object({ + text: z.string(), + buttons: z.array(buttonSchema), +}); -export type BlockMessage = - | string[] - | StdOutgoingTextMessage - | StdOutgoingQuickRepliesMessage - | StdOutgoingButtonsMessage - | StdOutgoingListMessage - | StdOutgoingAttachmentMessage - | StdPluginMessage; +export type StdOutgoingButtonsMessage = z.infer< + typeof stdOutgoingButtonsMessageSchema +>; -export type StdOutgoingMessage = - | StdOutgoingTextMessage - | StdOutgoingQuickRepliesMessage - | StdOutgoingButtonsMessage - | StdOutgoingListMessage - | StdOutgoingAttachmentMessage; +export const contentElementSchema = z + .object({ + id: z.string(), + title: z.string(), + }) + .catchall(z.any()); -type StdIncomingTextMessage = { text: string }; +export type ContentElement = z.infer; -export type StdIncomingPostBackMessage = StdIncomingTextMessage & { - postback: string; -}; +export const stdOutgoingListMessageSchema = z.object({ + options: contentOptionsSchema, + elements: z.array(contentElementSchema), + pagination: z.object({ + total: z.number(), + skip: z.number(), + limit: z.number(), + }), +}); -export type StdIncomingLocationMessage = { - type: PayloadType.location; - coordinates: { - lat: number; - lon: number; - }; -}; +export type StdOutgoingListMessage = z.infer< + typeof stdOutgoingListMessageSchema +>; -export type StdIncomingAttachmentMessage = { - type: PayloadType.attachments; - serialized_text: string; - attachment: AttachmentPayload | AttachmentPayload[]; -}; +export const stdOutgoingAttachmentMessageSchema = z.object({ + attachment: attachmentPayloadSchema, + quickReplies: z.array(stdQuickReplySchema).optional(), +}); -export type StdIncomingMessage = - | StdIncomingTextMessage - | StdIncomingPostBackMessage - | StdIncomingLocationMessage - | StdIncomingAttachmentMessage; +export type StdOutgoingAttachmentMessage = z.infer< + typeof stdOutgoingAttachmentMessageSchema +>; + +export const pluginNameSchema = z.object({ + name: z.string().regex(/-plugin$/) as z.ZodType, +}); + +export const stdPluginMessageSchema = z.object({ + plugin: pluginNameSchema, + args: z.record(z.any()), +}); + +export type StdPluginMessage = z.infer; + +export const BlockMessageSchema = z.union([ + z.array(z.string()), + stdOutgoingTextMessageSchema, + stdOutgoingQuickRepliesMessageSchema, + stdOutgoingButtonsMessageSchema, + stdOutgoingListMessageSchema, + stdOutgoingAttachmentMessageSchema, + stdPluginMessageSchema, +]); + +export type BlockMessage = z.infer; + +export const StdOutgoingMessageSchema = z.union([ + stdOutgoingTextMessageSchema, + stdOutgoingQuickRepliesMessageSchema, + stdOutgoingButtonsMessageSchema, + stdOutgoingListMessageSchema, + stdOutgoingAttachmentMessageSchema, +]); + +export type StdOutgoingMessage = z.infer; + +export const stdIncomingTextMessageSchema = z.object({ + text: z.string(), +}); + +export type StdIncomingTextMessage = z.infer< + typeof stdIncomingTextMessageSchema +>; + +export const stdIncomingPostBackMessageSchema = + stdIncomingTextMessageSchema.extend({ + postback: z.string(), + }); + +export type StdIncomingPostBackMessage = z.infer< + typeof stdIncomingPostBackMessageSchema +>; + +export const stdIncomingLocationMessageSchema = z.object({ + type: z.literal(PayloadType.location), + coordinates: z.object({ + lat: z.number(), + lon: z.number(), + }), +}); + +export type StdIncomingLocationMessage = z.infer< + typeof stdIncomingLocationMessageSchema +>; + +export const stdIncomingAttachmentMessageSchema = z.object({ + type: z.literal(PayloadType.attachments), + serialized_text: z.string(), + attachment: z.union([ + attachmentPayloadSchema, + z.array(attachmentPayloadSchema), + ]), +}); + +export type StdIncomingAttachmentMessage = z.infer< + typeof stdIncomingAttachmentMessageSchema +>; + +export const stdIncomingMessageSchema = z.union([ + stdIncomingTextMessageSchema, + stdIncomingPostBackMessageSchema, + stdIncomingLocationMessageSchema, + stdIncomingAttachmentMessageSchema, +]); + +export type StdIncomingMessage = z.infer; export interface IncomingMessage extends Omit { message: StdIncomingMessage; @@ -162,34 +252,121 @@ export interface OutgoingMessage extends Omit { export type AnyMessage = IncomingMessage | OutgoingMessage; -export interface StdOutgoingTextEnvelope { - format: OutgoingMessageFormat.text; - message: StdOutgoingTextMessage; -} +export const stdOutgoingTextEnvelopeSchema = z.object({ + format: z.literal(OutgoingMessageFormat.text), + message: stdOutgoingTextMessageSchema, +}); -export interface StdOutgoingQuickRepliesEnvelope { - format: OutgoingMessageFormat.quickReplies; - message: StdOutgoingQuickRepliesMessage; -} +export type StdOutgoingTextEnvelope = z.infer< + typeof stdOutgoingTextEnvelopeSchema +>; -export interface StdOutgoingButtonsEnvelope { - format: OutgoingMessageFormat.buttons; - message: StdOutgoingButtonsMessage; -} +export const stdOutgoingQuickRepliesEnvelopeSchema = z.object({ + format: z.literal(OutgoingMessageFormat.quickReplies), + message: stdOutgoingQuickRepliesMessageSchema, +}); -export interface StdOutgoingListEnvelope { - format: OutgoingMessageFormat.list | OutgoingMessageFormat.carousel; - message: StdOutgoingListMessage; -} +export type StdOutgoingQuickRepliesEnvelope = z.infer< + typeof stdOutgoingQuickRepliesEnvelopeSchema +>; -export interface StdOutgoingAttachmentEnvelope { - format: OutgoingMessageFormat.attachment; - message: StdOutgoingAttachmentMessage; -} +export const stdOutgoingButtonsEnvelopeSchema = z.object({ + format: z.literal(OutgoingMessageFormat.buttons), + message: stdOutgoingButtonsMessageSchema, +}); -export type StdOutgoingEnvelope = - | StdOutgoingTextEnvelope - | StdOutgoingQuickRepliesEnvelope - | StdOutgoingButtonsEnvelope - | StdOutgoingListEnvelope - | StdOutgoingAttachmentEnvelope; +export type StdOutgoingButtonsEnvelope = z.infer< + typeof stdOutgoingButtonsEnvelopeSchema +>; + +export const stdOutgoingListEnvelopeSchema = z.object({ + format: z.union([ + z.literal(OutgoingMessageFormat.list), + z.literal(OutgoingMessageFormat.carousel), + ]), + message: stdOutgoingListMessageSchema, +}); + +export type StdOutgoingListEnvelope = z.infer< + typeof stdOutgoingListEnvelopeSchema +>; + +export const stdOutgoingAttachmentEnvelopeSchema = z.object({ + format: z.literal(OutgoingMessageFormat.attachment), + message: stdOutgoingAttachmentMessageSchema, +}); + +export type StdOutgoingAttachmentEnvelope = z.infer< + typeof stdOutgoingAttachmentEnvelopeSchema +>; + +export const stdOutgoingEnvelopeSchema = z.union([ + stdOutgoingTextEnvelopeSchema, + stdOutgoingQuickRepliesEnvelopeSchema, + stdOutgoingButtonsEnvelopeSchema, + stdOutgoingListEnvelopeSchema, + stdOutgoingAttachmentEnvelopeSchema, +]); + +export type StdOutgoingEnvelope = z.infer; + +// is-valid-message-text validation +export const validMessageTextSchema = z.object({ + message: z.string(), +}); + +// is-message validation +const MESSAGE_REGEX = /^function \(context\) \{[^]+\}/; + +export const messageRegexSchema = z.string().regex(MESSAGE_REGEX); + +export const textSchema = z.array(z.string().max(1000)); + +const quickReplySchema = z + .object({ + content_type: z.nativeEnum(QuickReplyType), + title: z.string().max(20).optional(), + payload: z.string().max(1000).optional(), + }) + .superRefine((data, ctx) => { + // When content_type is 'text', title and payload are required. + if (data.content_type === QuickReplyType.text) { + if (data.title == null) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Title is required when content_type is 'text'", + path: ['title'], + }); + } + if (data.payload == null) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Payload is required when content_type is 'text'", + path: ['payload'], + }); + } + } + }); + +// Attachment Message Schema +export const objectSchema = z.object({ + text: z.string().max(1000).optional(), + attachment: z + .object({ + type: z.nativeEnum(FileType), + payload: z.object({ + url: z.string().url().optional(), + id: z.string().optional(), + }), + }) + .optional(), + elements: z.boolean().optional(), + cards: z + .object({ + default_action: buttonSchema, + buttons: z.array(buttonSchema).max(3), + }) + .optional(), + buttons: z.array(buttonSchema).max(3).optional(), + quickReplies: z.array(quickReplySchema).max(11).optional(), +}); diff --git a/api/src/chat/schemas/types/options.ts b/api/src/chat/schemas/types/options.ts index db1fe8b4..79ddacf0 100644 --- a/api/src/chat/schemas/types/options.ts +++ b/api/src/chat/schemas/types/options.ts @@ -1,42 +1,47 @@ /* - * 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: * 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 { Button } from './button'; +import { z } from 'zod'; + +import { buttonSchema } from './button'; import { OutgoingMessageFormat } from './message'; -export interface ContentOptions { - display: OutgoingMessageFormat.list | OutgoingMessageFormat.carousel; - fields: { - title: string; - subtitle: string | null; - image_url: string | null; - url?: string; - action_title?: string; - action_payload?: string; - }; - buttons: Button[]; - limit: number; - query?: any; // Waterline model criteria - entity?: string | number; // ContentTypeID - top_element_style?: 'large' | 'compact'; -} +export const contentOptionsSchema = z.object({ + display: z.nativeEnum(OutgoingMessageFormat), + fields: z.object({ + title: z.string(), + subtitle: z.string().nullable(), + image_url: z.string().nullable(), + url: z.string().optional(), + action_title: z.string().optional(), + action_payload: z.string().optional(), + }), + buttons: z.array(buttonSchema), + limit: z.number().finite(), + query: z.any().optional(), + entity: z.union([z.string(), z.number().finite()]).optional(), + top_element_style: z.enum(['large', 'compact']).optional(), +}); -export interface BlockOptions { - typing?: number; - // In case of carousel/list message - content?: ContentOptions; - // Only if the block has next blocks - fallback?: { - active: boolean; - message: string[]; - max_attempts: number; - }; - assignTo?: string; - // plugins effects - effects?: string[]; -} +export type ContentOptions = z.infer; + +export const BlockOptionsSchema = z.object({ + typing: z.number().optional(), + content: contentOptionsSchema.optional(), + fallback: z + .object({ + active: z.boolean(), + message: z.array(z.string()), + max_attempts: z.number().finite(), + }) + .optional(), + assignTo: z.string().optional(), + effects: z.array(z.string()).optional(), +}); + +export type BlockOptions = z.infer; diff --git a/api/src/chat/schemas/types/quick-reply.ts b/api/src/chat/schemas/types/quick-reply.ts index e58cea15..95862874 100644 --- a/api/src/chat/schemas/types/quick-reply.ts +++ b/api/src/chat/schemas/types/quick-reply.ts @@ -9,7 +9,6 @@ import { z } from 'zod'; import { attachmentPayloadSchema } from './attachment'; -import { PayloadType } from './message'; export enum QuickReplyType { text = 'text', @@ -25,11 +24,11 @@ export const cordinatesSchema = z.object({ export const payloadSchema = z.discriminatedUnion('type', [ z.object({ - type: z.literal(PayloadType.location), + type: z.literal('location'), coordinates: cordinatesSchema, }), z.object({ - type: z.literal(PayloadType.attachments), + type: z.literal('attachments'), attachment: attachmentPayloadSchema, }), ]); diff --git a/api/src/chat/services/block.service.ts b/api/src/chat/services/block.service.ts index c7fedabf..61c6b604 100644 --- a/api/src/chat/services/block.service.ts +++ b/api/src/chat/services/block.service.ts @@ -6,6 +6,14 @@ * 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). */ +/* + * 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 { Injectable } from '@nestjs/common'; import { AttachmentService } from '@/attachment/services/attachment.service'; @@ -224,7 +232,8 @@ export class BlockService extends BaseService< } else if ( typeof pattern === 'object' && 'label' in pattern && - text.trim().toLowerCase() === pattern.label.toLowerCase() + text.trim().toLowerCase() === + (pattern.label as unknown as string).toLowerCase() ) { // Payload (quick reply) return [text]; @@ -568,15 +577,13 @@ export class BlockService extends BaseService< contentBlockOptions, skip, ); - - const envelope: StdOutgoingEnvelope = { + const envelope = { format: contentBlockOptions.display, message: { ...results, options: contentBlockOptions, }, - }; - + } as StdOutgoingEnvelope; return envelope; } catch (err) { this.logger.error( @@ -588,7 +595,7 @@ export class BlockService extends BaseService< } else if (blockMessage && 'plugin' in blockMessage) { const plugin = this.pluginService.findPlugin( PluginType.block, - blockMessage.plugin as PluginName, + blockMessage.plugin as unknown as PluginName, ); // Process custom plugin block try { diff --git a/api/src/chat/validation-rules/is-message.ts b/api/src/chat/validation-rules/is-message.ts index 67688d94..057c71cb 100644 --- a/api/src/chat/validation-rules/is-message.ts +++ b/api/src/chat/validation-rules/is-message.ts @@ -12,103 +12,43 @@ import { ValidatorConstraint, ValidatorConstraintInterface, } from 'class-validator'; -import Joi from 'joi'; -import { BlockMessage } from '../schemas/types/message'; +import { + BlockMessage, + messageRegexSchema, + objectSchema, + textSchema, +} from '../schemas/types/message'; +/* eslint-disable no-console */ export function isValidMessage(msg: any) { if (typeof msg === 'string' && msg !== '') { - // Custom code - const MESSAGE_REGEX = /^function \(context\) \{[^]+\}/; - if (!MESSAGE_REGEX.test(msg)) { - // eslint-disable-next-line - console.error('Block Model : Invalid custom code.', msg); + const result = messageRegexSchema.safeParse(msg); + if (!result.success) { + console.error('Block Model: Invalid custom code.', result.error); return false; - } else { - return true; } + return true; } else if (Array.isArray(msg)) { - // Simple text message - const textSchema = Joi.array().items(Joi.string().max(1000).required()); - const textCheck = textSchema.validate(msg); - return !textCheck.error; - } else if (typeof msg === 'object') { + const result = textSchema.safeParse(msg); + if (!result.success) { + console.error('Block Model: Invalid text message array.', result.error); + } + return result.success; + } else if (typeof msg === 'object' && msg !== null) { if ('plugin' in msg) { return true; - } else { - const buttonsSchema = Joi.array().items( - Joi.object().keys({ - type: Joi.string().valid('postback', 'web_url').required(), - title: Joi.string().max(20), - payload: Joi.alternatives().conditional('type', { - is: 'postback', - then: Joi.string().max(1000).required(), - otherwise: Joi.forbidden(), - }), - url: Joi.alternatives().conditional('type', { - is: 'web_url', - then: Joi.string().uri(), - otherwise: Joi.forbidden(), - }), - messenger_extensions: Joi.alternatives().conditional('type', { - is: 'web_url', - then: Joi.boolean(), - otherwise: Joi.forbidden(), - }), - webview_height_ratio: Joi.alternatives().conditional('type', { - is: 'web_url', - then: Joi.string().valid('compact', 'tall', 'full'), - otherwise: Joi.forbidden(), - }), - }), - ); - // Attachment message - const objectSchema = Joi.object().keys({ - text: Joi.string().max(1000), - attachment: Joi.object().keys({ - type: Joi.string() - .valid('image', 'audio', 'video', 'file', 'unknown') - .required(), - payload: Joi.object().keys({ - url: Joi.string().uri(), - id: Joi.string().allow(null), - }), - }), - elements: Joi.boolean(), - cards: Joi.object().keys({ - default_action: buttonsSchema.max(1), - buttons: buttonsSchema.max(3), - }), - buttons: buttonsSchema.max(3), - quickReplies: Joi.array() - .items( - Joi.object().keys({ - content_type: Joi.string() - .valid('text', 'location', 'user_phone_number', 'user_email') - .required(), - title: Joi.alternatives().conditional('content_type', { - is: 'text', - then: Joi.string().max(20).required(), - }), - payload: Joi.alternatives().conditional('content_type', { - is: 'text', - then: Joi.string().max(1000).required(), - }), - }), - ) - .max(11), - }); - const objectCheck = objectSchema.validate(msg); - if (objectCheck.error) { - // eslint-disable-next-line - console.log('Message validation failed! ', objectCheck); - } - return !objectCheck.error; } - } else { - return false; + const result = objectSchema.safeParse(msg); + if (!result.success) { + console.error('Block Model: Object validation failed!', result.error); + } + return result.success; } + console.log('Validation reached default false'); + return false; } +/* eslint-enable no-console */ @ValidatorConstraint({ async: false }) export class MessageValidator implements ValidatorConstraintInterface { diff --git a/api/src/chat/validation-rules/is-valid-message-text.ts b/api/src/chat/validation-rules/is-valid-message-text.ts index 485e78bc..caf63f51 100644 --- a/api/src/chat/validation-rules/is-valid-message-text.ts +++ b/api/src/chat/validation-rules/is-valid-message-text.ts @@ -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: * 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission. @@ -11,6 +11,7 @@ import { registerDecorator, ValidationOptions } from 'class-validator'; import { StdIncomingMessage, StdOutgoingTextMessage, + validMessageTextSchema, } from '../schemas/types/message'; export function IsValidMessageText(validationOptions?: ValidationOptions) { @@ -21,7 +22,7 @@ export function IsValidMessageText(validationOptions?: ValidationOptions) { options: validationOptions, validator: { validate(message: StdOutgoingTextMessage | StdIncomingMessage) { - return !!(message as StdOutgoingTextMessage).text; + return validMessageTextSchema.safeParse(message).success; }, }, }); diff --git a/api/src/i18n/services/translation.service.spec.ts b/api/src/i18n/services/translation.service.spec.ts index 508d544e..144586fc 100644 --- a/api/src/i18n/services/translation.service.spec.ts +++ b/api/src/i18n/services/translation.service.spec.ts @@ -9,6 +9,7 @@ import { EventEmitter2 } from '@nestjs/event-emitter'; import { Test, TestingModule } from '@nestjs/testing'; +import { BlockMessage } from '@/chat/schemas/types/message'; import { I18nService } from '@/i18n/services/i18n.service'; import { BasePlugin } from '@/plugins/base-plugin.service'; import { PluginService } from '@/plugins/plugins.service'; @@ -154,7 +155,7 @@ describe('TranslationService', () => { model: 'String 1', context: ['String 2', 'String 3'], }, - }, + } as unknown as BlockMessage, options: {}, attachedBlock: null, }; diff --git a/api/src/i18n/services/translation.service.ts b/api/src/i18n/services/translation.service.ts index 93bea9af..1881cd6b 100644 --- a/api/src/i18n/services/translation.service.ts +++ b/api/src/i18n/services/translation.service.ts @@ -11,7 +11,7 @@ import { OnEvent } from '@nestjs/event-emitter'; import { I18nService } from '@/i18n/services/i18n.service'; import { PluginService } from '@/plugins/plugins.service'; -import { PluginType } from '@/plugins/types'; +import { PluginName, PluginType } from '@/plugins/types'; import { SettingService } from '@/setting/services/setting.service'; import { BaseService } from '@/utils/generics/base-service'; @@ -54,7 +54,7 @@ export class TranslationService extends BaseService { if ('plugin' in block.message) { const plugin = this.pluginService.getPlugin( PluginType.block, - block.message.plugin, + block.message.plugin as unknown as PluginName, ); // plugin