I created an Api for my bot(Otti) that powerd with AI (Via openAI / Azuer OpenAI). The Project written in TypeScript (Express Node.JS) and MongoDB for the DB. The Api can recive and send messages via multiple providers(connections in my project) - for example Whatsapp(UltraMessage, GreenAPI, GoMesser) and WebChat ( chat in website via webhook ) in the feature also support Telegram and Facebook. This is my files tree - otti-api ├── .gitignore ├── eslintrc.js ├── package-lock.json ├── package.json ├── README.md ├── src │ ├── app.ts │ ├── config │ │ ├── config.ts │ │ └── SettingsConfig.ts │ ├── controllers │ │ ├── analyticsController.ts │ │ ├── businessController.ts │ │ ├── messageController.ts │ │ ├── settingsController.ts │ │ └── userController.ts │ ├── language │ │ └── views │ │ └── userRegister.ts │ ├── middleware │ │ ├── addReactionMiddleware.ts │ │ ├── basicAuth.ts │ │ ├── debounceMiddleware.ts │ │ ├── errorHandler.ts │ │ ├── greetMiddleware.ts │ │ ├── groupChatMiddleware.ts │ │ ├── initBotRequestDataMiddleware.ts │ │ ├── initRequestLogicMiddleware.ts │ │ ├── notAllowedMessagesGuard.ts │ │ ├── rateLimitDuplicateGuard.ts │ │ ├── saveBotMessageMiddleware.ts │ │ ├── saveMessageMiddleware.ts │ │ ├── subscriptionMiddleware.ts │ │ ├── swapConnectionIdMiddleware.ts │ │ ├── userDynamicProfileMiddleware.ts │ │ └── verifyJsonMiddleware.ts │ ├── models │ │ ├── Business.ts │ │ ├── Chat.ts │ │ ├── Connection.ts │ │ ├── Message.ts │ │ ├── NormalizedProfile.ts │ │ ├── Session.ts │ │ ├── Settings.ts │ │ ├── ToolUsage.ts │ │ ├── User.ts │ │ └── UserAnalysis.ts │ ├── routes │ │ ├── adminRoutes.ts │ │ ├── analyticsRoutes.ts │ │ ├── businessRoutes.ts │ │ ├── formRoutes.ts │ │ ├── messageRoutes.ts │ │ ├── settingsRoutes.ts │ │ └── userRoutes.ts │ ├── server.ts │ ├── services │ │ ├── admin │ │ │ ├── defaultSettings.json │ │ │ ├── scripts │ │ │ │ └── seedSettings.ts │ │ │ └── settingsService.ts │ │ ├── ai │ │ │ ├── aiServiceFactory.ts │ │ │ ├── azureOpenaiService.ts │ │ │ ├── baseAIService.ts │ │ │ ├── botResponseService.ts │ │ │ ├── claudeService.ts │ │ │ ├── IAIService.ts │ │ │ ├── openaiService.ts │ │ │ ├── promptDesign.ts │ │ │ ├── tools.ts │ │ │ └── toolService.ts │ │ ├── analytics │ │ │ ├── analyticsService.ts │ │ │ ├── ChatAnalytics.ts │ │ │ ├── MessageAnalytics.ts │ │ │ ├── ToolUsageAnalytics.ts │ │ │ └── UserAnalytics.ts │ │ ├── business │ │ │ └── matchingService.ts │ │ ├── connection │ │ │ └── connectionService.ts │ │ ├── conversation │ │ │ ├── buildGroupHistory.ts │ │ │ ├── buildPrivateHistory.ts │ │ │ └── ConversationService.ts │ │ ├── cron │ │ │ └── cronService.ts │ │ ├── image │ │ │ └── imageService.ts │ │ ├── logger │ │ │ ├── FileLogger.ts │ │ │ └── ILogger.ts │ │ ├── messaging │ │ │ ├── chatService.ts │ │ │ ├── greenApiService.ts │ │ │ ├── InboundMessageFactory.ts │ │ │ ├── IWhatsAppService.ts │ │ │ ├── meserGoService.ts │ │ │ ├── MessageService.ts │ │ │ ├── ProviderServiceFactory.ts │ │ │ ├── reactionsService.ts │ │ │ ├── SessionService.ts │ │ │ ├── twilioService.ts │ │ │ ├── ultraMsgService.ts │ │ │ └── webApiService.ts │ │ ├── nlp │ │ │ └── nlpService.ts │ │ ├── subscription │ │ │ └── subscriptionService.ts │ │ ├── user │ │ │ ├── analyzeSessionsService.ts │ │ │ ├── googleFormService.ts │ │ │ ├── groupService.ts │ │ │ ├── matchingService.ts │ │ │ └── userService.ts │ │ └── utilities │ │ └── weatherService.ts │ ├── types │ │ ├── botRequestData.ts │ │ └── express │ │ ├── index.ts │ │ └── messages │ │ ├── IInboundMessage.ts │ │ └── IOutboundMessage.ts │ ├── utils │ │ ├── BotRequestParser.ts │ │ ├── general.ts │ │ ├── language.ts │ │ ├── parseCriteria.ts │ │ └── truncateHistory.ts │ └── views │ ├── businessRegister.ejs │ ├── subscriptionStatus.ejs │ └── userRegister.ejs ├── tsconfig.json └── uploads this is my main route for the API - import { NextFunction, Request, Response, Router } from "express"; import asyncHandler from 'express-async-handler'; import { receiveMessage } from "../controllers/messageController"; import { debounceMessages } from "../middleware/debounceMiddleware"; import { GroupChatMiddleware } from "../middleware/groupChatMiddleware"; import { initBotRequestDataMiddleware } from "../middleware/initBotRequestDataMiddleware"; import { initRequestLogicMiddleware } from "../middleware/initRequestLogicMiddleware"; import { notAllowedMessagesGuard } from "../middleware/notAllowedMessagesGuard"; import { rateLimitDuplicate as rateLimitDuplicateGuard } from "../middleware/rateLimitDuplicateGuard"; import { SaveMessage } from "../middleware/saveMessageMiddleware"; import { checkSubscription as checkSubscriptionGuard } from "../middleware/subscriptionMiddleware"; import { addReactionMiddleware } from "../middleware/addReactionMiddleware"; import { greetMiddleware } from "../middleware/greetMiddleware"; import SettingsConfig from "../config/SettingsConfig"; import { UserDynamicProfile } from "../middleware/userDynamicProfileMiddleware"; const router = Router(); const settingsConfig = SettingsConfig.getInstance(); const allowCacheSetting = false; router.post( "/webhook/:app/:provider/:externalId", initBotRequestDataMiddleware, rateLimitDuplicateGuard, initRequestLogicMiddleware, SaveMessage, UserDynamicProfile, greetMiddleware, GroupChatMiddleware, debounceMessages, async (req, res, next) => { if (await settingsConfig.getSetting("ENABLE_SUBSCRIPTION_GUARD", false, allowCacheSetting)) { return checkSubscriptionGuard(req, res, next); } next(); }, async (req, res, next) => { if (await settingsConfig.getSetting("ENABLE_ADD_REACTION", false, allowCacheSetting)) { return addReactionMiddleware(req, res, next); } next(); }, async (req, res, next) => { if (await settingsConfig.getSetting("ENABLE_SPAM_FILTER", false, allowCacheSetting)) { return notAllowedMessagesGuard(req, res, next); } next(); }, asyncHandler(async (req: Request, res: Response, next: NextFunction) => { await receiveMessage(req, res); }) ); export default router; this is the middlewares - import { NextFunction, Request, Response } from "express"; import { IUser } from "../models/User"; import { ProviderServiceFactory } from "../services/messaging/ProviderServiceFactory"; import { ConnectionService } from "../services/connection/connectionService"; export const initBotRequestDataMiddleware = async (req: Request, res: Response, next: NextFunction) => { const { app, provider, externalId } = req.params; if (!app?.trim().length) { return res.status(400).json({ error: 'Missing app Id q-parm' }); } if (!provider?.trim().length) { return res.status(400).json({ error: 'Missing Provider Id q-parm' }); } const appService = await ProviderServiceFactory .getAppService(app, provider, externalId) .receiveMessage(req) const connection = await ConnectionService.getConnection(provider, externalId, appService.to); if ("error" in connection) { return res.status(403).json(connection.error); } req.botRequestData = { from: { app: app, provider: connection.provider, providerExternalId: connection.externalId, connectionId: connection.id, phone: appService.from, name: appService.name! }, to: appService.to, self: appService.self ?? false, origin: appService.origin, body: appService.body, msgId: appService.msgId, users: [] as IUser[], sessionId: '', isGroup: appService.isGroup!, chatId: appService.chatId! } next(); }; export default initBotRequestDataMiddleware; import { NextFunction, Request, Response } from 'express'; type BlockDuplicateCallback = (req: Request, res: Response, next: NextFunction, aggregatedMessage: string) => void; interface MessageData { timer: NodeJS.Timeout; messages: string[]; } export class RateLimitDuplicateGuard { private time: number; private messageMap: Map; constructor(time = 1000) { this.time = time; this.messageMap = new Map(); } checkMessage(user: string, message: string, callback: BlockDuplicateCallback, req: Request, res: Response, next: NextFunction) { if (this.messageMap.has(user)) { const messageData = this.messageMap.get(user)!; clearTimeout(messageData.timer); messageData.messages.push(message); // get the last message const lastMessage = messageData.messages[messageData.messages.length - 1]; if (lastMessage !== message) { this.processMessages(user, callback, req, res, next); } else { messageData.timer = setTimeout(() => { this.processMessages(user, callback, req, res, next); }, this.time); } } else { const timer = setTimeout(() => { this.processMessages(user, callback, req, res, next); }, this.time); this.messageMap.set(user, { timer, messages: [message] }); } } private processMessages(user: string, callback: BlockDuplicateCallback, req: Request, res: Response, next: NextFunction) { const messageData = this.messageMap.get(user); if (messageData) { const aggregatedMessage = messageData.messages.join(" "); callback(req, res, next, aggregatedMessage); this.messageMap.delete(user); } } } const rateLimitDuplicateGuard = new RateLimitDuplicateGuard(500); export const rateLimitDuplicate = async (req: Request, res: Response, next: NextFunction) => { if(!req.botRequestData){ return res.status(400).send({ error: "rateLimitDuplicate: botRequestData NOT-FOUND" }); } const phone = req.botRequestData!.from.phone const body = req.botRequestData!.body rateLimitDuplicateGuard.checkMessage( phone, body, (req, res, next, message) => { next(); }, req, res, next); }; import { NextFunction, Request, Response } from "express"; import { findOrCreateChat } from "../services/messaging/chatService"; import { getLastSessionId } from "../services/messaging/SessionService"; import { findOrCreateUsers } from "../services/user/userService"; export const initRequestLogicMiddleware = async ( req: Request, res: Response, next: NextFunction ) => { if (!req.botRequestData) { return res.status(400).send({ error: "initRequestLogicMiddleware: botRequestData NOT-FOUND" }); } const { origin, chatId, isGroup, from: { phone: from, name, connectionId: connection } } = req.botRequestData!; const chatExternalId = chatId || origin; const users = await findOrCreateUsers([{ phoneNumber: from, name: name || "" }]); const chat = await findOrCreateChat(connection, users.map(user => user._id), chatExternalId, isGroup || false); const sessionId = await getLastSessionId(users.map(user => user._id), chat._id); req.botRequestData!.users.push(...users) req.botRequestData!.chatId = chat._id.toString(); req.botRequestData!.sessionId = sessionId next(); }; import { Request, Response, NextFunction } from "express"; import Message from "../models/Message"; import UserAnalysis from "../models/UserAnalysis"; import { Types } from "mongoose"; const isDuplicateMessage = async ( userId: Types.ObjectId, message: string ): Promise => { const lastMessage = await Message.findOne({ userId, sender: "user", }).sort({ timestamp: -1 }); return lastMessage && lastMessage.message === message ? true : false; }; export const SaveMessage = async ( req: Request, res: Response, next: NextFunction ) => { if(!req.botRequestData){ return res.status(400).send({ error: "SaveMessage: botRequestData NOT-FOUND" }); } try { const { sessionId, users, body, isGroup } = req.botRequestData!; const user = users && users[0]; if( !body || body === "" ) { return res.status(400).send({ error: "Message body is required" }); } if (!(await isDuplicateMessage(user._id, body))) { const userMessage = new Message({ sessionId, sender: "user", message: body, userId: user._id, timestamp: new Date(), }); userMessage.save(); if( isGroup ){ return next(); } let userAnalysis = await UserAnalysis.findOne({ userId: user._id }); if (!userAnalysis) { userAnalysis = new UserAnalysis({ userId: user._id }); } userAnalysis.messageCount += 1; userAnalysis.save(); } next(); } catch (error) { res.status(500).send({ error: "Internal server error" }); } } import { Request, Response, NextFunction } from "express"; import { ANALYZE_MESSAGE_ON_GOING_COUNT, analyzeAndUpdateDynamicProfile } from "../services/user/analyzeSessionsService"; import UserAnalysis from "../models/UserAnalysis"; import { FileLogger } from "../services/logger/FileLogger"; const logger = new FileLogger("userDynamicProfileMiddleware.log", false); export const UserDynamicProfile = async ( req: Request, res: Response, next: NextFunction ) => { try { const { users } = req.botRequestData!; const user = users && users[0]; if (!user) { logger.error(`User Not found`); return next(); } const userAnalysis = await UserAnalysis.findOne({ userId: user._id }); if (userAnalysis && userAnalysis.messageCount >= ANALYZE_MESSAGE_ON_GOING_COUNT) { analyzeAndUpdateDynamicProfile(user); } next(); } catch (error) { res.status(500).send({ error: "Internal server error" }); } } import { NextFunction, Request, Response } from "express"; import mongoose from "mongoose"; import { isFirstMessageInChat } from "../services/messaging/chatService"; import { greetNewGroup } from "../services/user/groupService"; import { greetNewUser } from "../services/user/userService"; import { ProviderServiceFactory } from "../services/messaging/ProviderServiceFactory"; import Message from "../models/Message"; export const greetMiddleware = async ( req: Request, res: Response, next: NextFunction ) => { if (!req.botRequestData) { return res.status(400).send({ error: "greetMiddleware: botRequestData NOT-FOUND" }); } try { const { origin, sessionId, users, body, chatId, isGroup } = req.botRequestData!; const user = users && users[0]; const provider = req.botRequestData!.from.provider const providerExternalId = req.botRequestData!.from.providerExternalId const app = req.botRequestData!.from.app const appService = ProviderServiceFactory.getAppService(app, provider, providerExternalId) let botResponse = ""; if (await isFirstMessageInChat(new mongoose.Types.ObjectId(chatId)) && isGroup) { botResponse = await greetNewGroup(body); } else if (user.conversationState === "initial" && !isGroup) { botResponse = await greetNewUser(user, body); } if (botResponse) { const botMessage = new Message({ sessionId, sender: "bot", message: botResponse, userId: user._id, timestamp: new Date(), }); await botMessage.save(); await appService.sendMessage(origin, botResponse); } else { return next(); } } catch (error) { res.status(500).send({ error: "Internal server error" }); } } import { NextFunction, Request, Response } from "express"; import { Types } from "mongoose"; import Chat from "../models/Chat"; import Message from "../models/Message"; import { UserRole } from "../models/User"; import { AnalyticsService } from "../services/analytics/analyticsService"; import { FileLogger } from "../services/logger/FileLogger"; import { getLastSessionId } from "../services/messaging/SessionService"; import { ProviderServiceFactory } from "../services/messaging/ProviderServiceFactory"; const logger = new FileLogger("groupChatMiddleware.log", false); const isAllowAdminGroupChat = (chatId: string) => { const allowGroupList = [] as any[]; return allowGroupList && allowGroupList.length > 0 ? allowGroupList.includes(chatId) : true; } export const GroupChatMiddleware = async ( req: Request, res: Response, next: NextFunction ) => { if (!req.botRequestData) { return res.status(400).send({ error: "GroupChatMiddleware: botRequestData NOT-FOUND" }); } try { const { users, body, isGroup, chatId } = req.botRequestData!; const user = users && users[0]; if (!isGroup) { next(); return; } if (body.match(/אוטי/i) || body.match(/Otti/i)) { next(); return; } if (user && user.role === UserRole.ADMIN && isAllowAdminGroupChat(chatId)) { const app = req.botRequestData!.from.app const provider = req.botRequestData!.from.provider const providerExternalId = req.botRequestData!.from.providerExternalId const appService = ProviderServiceFactory.getAppService(app, provider, providerExternalId) if (body.match(/סטטיסטיקה/i)) { // template is סטטיסטיקה 20.12.2024-21.12.2024 const dateRange = body.match(/(\d{2}\.\d{2}\.\d{4})(?:-(\d{2}\.\d{2}\.\d{4}))?/); let startDate: Date | undefined; let endDate: Date | undefined; function parseDate(dateString: string): Date { const [day, month, year] = dateString.split('.').map(Number); return new Date(year, month - 1, day); } if (dateRange) { startDate = parseDate(dateRange[1]); if (dateRange[2]) { endDate = parseDate(dateRange[2]); } else { endDate = startDate; } } const analyticsService = new AnalyticsService(); try { const analytics = await analyticsService.getAnalytics(startDate, endDate); await appService.sendMessage(user.phoneNumber, analytics); } catch (error: any | Error) { logger.error( `Failed to send analytics: ${error?.message || "Unknown error"}` ); res.status(202).send({ error: "Failed to send analytics" }); } } // check if the body start with "לכולם:" if (body.match(/^לכולם:/)) { const message = body.replace(/^לכולם:/, '').trim(); try { // get all the chats from the mongoDB except the current chat const chats = await Chat.find({ externalId: { $ne: chatId } }); // send the message to all the chats for (const chat of chats) { await new Promise((resolve) => setTimeout(resolve, 1500)); // delay between messages // get last sessionId const sessionId = await getLastSessionId(chat.users.map((user: any) => user._id.toString()), chat._id as unknown as Types.ObjectId); // add the message to the chat const botMessage = new Message({ sessionId, sender: "bot", userId: chat.users[0]._id, timestamp: new Date(), message }); await botMessage.save(); await appService.sendMessage(chat.externalId, message); } } catch (error: any | Error) { logger.error( `Failed to send message to group: ${error?.message || "Unknown error"}` ); res.status(202).send({ error: "Failed to send message to group" }); } } } res.status(202).send(); } catch (error) { res.status(500).send({ error: "Internal server error" }); } } import { NextFunction, Request, Response } from "express"; import Message from "../models/Message"; import { ProviderServiceFactory } from "../services/messaging/ProviderServiceFactory"; import { SubscriptionService } from "../services/subscription/subscriptionService"; import { createBotResponse, TemplateKeys } from "../services/ai/botResponseService"; const subscriptionService = new SubscriptionService(); const isDuplicateMessage = async ( sessionId: string, message: string ): Promise => { const lastMessage = await Message.findOne({ sessionId, sender: "bot", }).sort({ timestamp: -1 }); return lastMessage && lastMessage.message === message ? true : false; }; export const checkSubscription = async ( req: Request, res: Response, next: NextFunction ) => { if(!req.botRequestData){ return res.status(400).send({ error: "checkSubscription: botRequestData NOT-FOUND" }); } try { const { origin, sessionId, users, body } = req.botRequestData!; const user = users[0]; if (user.conversationState === "initial") { next(); return; } const canSendMessage = await subscriptionService.canSendMessage(user._id.toString()); if (canSendMessage) { next(); return; } const botResponse = await createBotResponse(body, TemplateKeys.SUBSCRIPTION_LIMIT_ERROR); if (!isDuplicateMessage(sessionId, botResponse)) { const botMessage = new Message({ sessionId, sender: "bot", message: botResponse, userId: user._id, timestamp: new Date(), }); botMessage.save(); } const app = req.botRequestData!.from.app const provider = req.botRequestData!.from.provider const providerExternalId = req.botRequestData!.from.providerExternalId const appService = ProviderServiceFactory.getAppService(app, provider, providerExternalId) await appService.sendMessage(origin, botResponse); return res.status(202).send({ error: "Message limit reached. Please upgrade your subscription to continue.", }); } catch (error) { res.status(500).send({ error: "Internal server error" }); } }; import { NextFunction, Request, Response } from 'express'; import { ProviderServiceFactory } from '../services/messaging/ProviderServiceFactory'; import { createBotResponse, TemplateKeys } from '../services/ai/botResponseService'; const notAllowedMessageStrings = [ 'system prompt', 'system message', 'initial instruction', 'base instruction', 'base prompt', 'default instruction', 'default prompt', 'default setting', 'change persona', 'change prompt', 'change instruction', 'change setting', 'modify behavior', 'modify persona', 'modify prompt', 'modify instruction', 'modify setting', 'update persona', 'update prompt', 'update instruction', 'update setting', 'how you work', 'how you function', 'how you operate', 'source code', 'reveal your', 'tell me your', 'what is your', // Hebrew translations 'הוראת מערכת', 'הודעת מערכת', 'הוראה ראשונית', 'הוראה בסיסית', 'הנחיית בסיס', 'הגדרת ברירת מחדל', 'שינוי זהות', 'שינוי הנחיה', 'שינוי הוראה', 'שינוי הגדרה', 'שינוי התנהגות', 'שינוי זהות', 'שינוי הנחיה', 'שינוי הוראה', 'שינוי הגדרה', 'עדכון זהות', 'עדכון הנחיה', 'עדכון הוראה', 'עדכון הגדרה', 'איך אתה עובד', 'איך את עובדת', 'איך אתה מתפקד', 'איך את מתפקדת', 'איך אתה פועל', 'איך את פועלת', 'קוד מקור שלך', 'קוד המקור שלך', ] as string[]; export const notAllowedMessagesGuard = async (req: Request, res: Response, next: NextFunction) => { if (!req.botRequestData) { return res.status(400).send({ error: "notAllowedMessagesGuard: botRequestData NOT-FOUND" }); } const body = req.botRequestData!.body const origin = req.botRequestData!.origin const app = req.botRequestData!.from.app const provider = req.botRequestData!.from.provider const providerExternalId = req.botRequestData!.from.providerExternalId const wapp = ProviderServiceFactory.getAppService(app, provider, providerExternalId) if (notAllowedMessageStrings.some((str) => body.toLocaleLowerCase().includes(str))) { await wapp.sendMessage(origin, await createBotResponse(body, TemplateKeys.SUSPICIOUS_MESSAGE)); return res.status(203).send("blocked message body"); } if (body.length > 2000) { await wapp.sendMessage(origin, await createBotResponse(body, TemplateKeys.LONG_MESSAGE_ERROR)); return res.status(203).send("Message is too long"); } next(); }; import { Request, Response } from "express"; import { ConversationService } from "../services/conversation/ConversationService"; import { MessageService } from "../services/messaging/MessageService"; import { ProviderServiceFactory } from "../services/messaging/ProviderServiceFactory"; import { AIServiceFactory } from "../services/ai/aiServiceFactory"; import { getErrorMessageFromBot } from "../services/user/userService"; // or however you had it import { BotRequestParser } from "../utils/BotRequestParser"; export class MessageController { // dependencies (could also be injected in constructor if you prefer) private messageService = new MessageService(); private conversationService = new ConversationService(); private aiService = AIServiceFactory.createAIService(); /** * The main endpoint to receive an incoming message from a user (webhook). */ public receiveMessage = async (req: Request, res: Response) => { try { // Parse and validate request data const botRequestData = BotRequestParser.parse(req); if (!botRequestData) { return res.status(400).send({ error: "receiveMessage: botRequestData NOT-FOUND" }); } // Check if user unsubscribed // TODO - Need to create middleware const user = botRequestData.users[0]; if (user.unsubscribe) { return res.status(400).send({ error: "User has unsubscribed" }); } // Build conversation history for the AI const conversationHistory = await this.conversationService.buildConversationHistory( botRequestData, this.messageService ); // Send conversation to AI const botResponse = await this.aiService.sendMessage( conversationHistory, botRequestData.chatId, { app: botRequestData.from.app, provider: botRequestData.from.provider, providerExternalId: botRequestData.from.providerExternalId, id: user._id.toString(), origin: botRequestData.origin, phone: user.phoneNumber, body: botRequestData.body, } ); // If AI responded, store response in DB and send to user if (botResponse) { await this.messageService.storeBotMessage(botRequestData.sessionId, user._id.toString(), botResponse); const appService = ProviderServiceFactory.getAppService( botRequestData.from.app, botRequestData.from.provider, botRequestData.from.providerExternalId ); await appService.sendMessage(botRequestData.origin, botResponse); } return res.status(200).send("Message processed"); } catch (error) { // Handle errors gracefully return this.handleError(req, res, error); } }; /** * Gracefully handle errors: * 1. Log them, * 2. Attempt to send fallback error message (if possible), * 3. Return 500 to client */ private async handleError(req: Request, res: Response, error: any) { if (req.botRequestData) { // Attempt to send fallback const { origin, users, from, body } = req.botRequestData; const user = users && users[0]; const appService = ProviderServiceFactory.getAppService( from.app, from.provider, from.providerExternalId ); const fallbackMessage = await getErrorMessageFromBot(user, body); await appService.sendMessage(origin, fallbackMessage); } return res.status(500).send({ error: "Failed to receive message" }); } } const messageController = new MessageController(); export const receiveMessage = messageController.receiveMessage; ---- import { GreenApiService } from "./greenApiService"; import { IWhatsAppService } from "./IWhatsAppService"; import { MeserGoService } from "./meserGoService"; import { TwilioService } from "./twilioService"; import { UltraMsgService } from "./ultraMsgService"; import { WebApiService } from "./webApiService"; type channelProviderType = string; export class ProviderServiceFactory { static providers = new Map() static getAppService(app: string, provider: string, externalId: string): IWhatsAppService { const allowedChannel = ['whatsapp', 'telegram', 'web']; if (!allowedChannel.includes(app)) { throw new Error("Unknown App provider"); } const allowed = ['twilio', 'ultramsg', 'greenapi', 'meserGo','chatOtti'] if (!allowed.includes(provider)) { throw new Error("Unknown WhatsApp provider"); } const key = `${app}-${provider}` if (!this.providers.has(key)) { this.providers.set(key, ProviderServiceFactory.createAppService(app, provider, externalId)) } return this.providers.get(key)! } static createAppService(channel: string, provider: string, externalId: string): IWhatsAppService { switch (channel) { case "whatsapp": return ProviderServiceFactory.createWhatsAppService(provider, externalId); case "web": return new WebApiService(provider); case "telegram": throw new Error("No Telegram Service"); default: throw new Error("Unknown WhatsApp provider"); } } static createWhatsAppService(provider: string, externalId: string): IWhatsAppService { switch (provider) { case "meserGo": return new MeserGoService(); case "twilio": return new TwilioService(); case "ultramsg": return new UltraMsgService(externalId); case "greenapi": return new GreenApiService(); default: throw new Error("Unknown WhatsApp provider"); } } }