"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var src_exports = {}; __export(src_exports, { Bot: () => Bot, ButtonTemplate: () => ButtonTemplate, CallButton: () => CallButton, GenericElement: () => GenericElement, GenericTemplate: () => GenericTemplate, MediaElement: () => MediaElement, MediaTemplate: () => MediaTemplate, PostbackButton: () => PostbackButton, QuickReplies: () => QuickReplies, QuickReply: () => QuickReply, ReceiptElement: () => ReceiptElement, ReceiptTemplate: () => ReceiptTemplate, URLButton: () => URLButton, colors: () => colors, determineEventType: () => determineEventType, logger: () => logger }); module.exports = __toCommonJS(src_exports); // src/constants/index.ts var GRAPH_URL = "https://graph.facebook.com"; // src/utils/determineEventType.ts function determineEventType(event) { if ("message" in event) { if ("quick_reply" in event.message) { return "quick_reply"; } else if ("is_echo" in event.message) { return "echo"; } return "message"; } else if ("postback" in event) { return "postback"; } else if ("template" in event) { return "template"; } else if ("referral" in event) { return "referral"; } return "unknown"; } // src/utils/logger.ts var colors = { reset: "\x1B[0m", gray: "\x1B[90m", blue: "\x1B[34m", yellow: "\x1B[33m", red: "\x1B[31m" }; var prefix = `${colors.gray}[bot]${colors.reset}`; var logger = { /** * Logs a general message. * @param args - The message or values to log. */ log: (...args) => { console.log(prefix, ...args); }, /** * Logs an informational message, with a blue `[info]` tag. * @param args - The message or values to log. */ info: (...args) => { console.log(prefix, `${colors.blue}[info]${colors.reset}`, ...args); }, /** * Logs a warning message, with a yellow `[warn]` tag. * @param args - The message or values to log. */ warn: (...args) => { console.log(prefix, `${colors.yellow}[warn]${colors.reset}`, ...args); }, /** * Logs an error message, with a red `[error]` tag, and exits the process. * @param args - The message or values to log. */ error: (...args) => { console.log(prefix, `${colors.red}[error]${colors.reset}`, ...args); process.exit(1); } }; // src/bot.ts var import_events = __toESM(require("events"), 1); var import_express = __toESM(require("express"), 1); var Bot = class extends import_events.default { server; accessToken; verifyToken; /** Public bot configuration. */ bot; /** * Creates an instance of Bot. * @param options - Configuration options for the Bot. */ constructor(options) { super(); this.server = (0, import_express.default)(); this.accessToken = options.accessToken; this.verifyToken = options.verifyToken; if (!options.accessToken) { logger.error( "Access token is required: https://developers.facebook.com/docs/messenger-platform/getting-started/quick-start" ); } if (!options.verifyToken) { logger.error( "Verify token is required: https://developers.facebook.com/docs/messenger-platform/getting-started/quick-start" ); } this.bot = { id: "", name: "", port: options.port ?? 8080, endpoint: options.endpoint ?? "/webhook", version: options.version ?? "v19.0" }; } /** * Starts the bot server and sets up the webhook endpoints. */ start() { this.server.use((0, import_express.json)()); this.server.get(this.bot.endpoint, (req, res) => { const { "hub.mode": mode, "hub.challenge": challenge, "hub.verify_token": verifyToken } = req.query; if (mode && verifyToken) { if (mode === "subscribe" && verifyToken === this.verifyToken) { res.status(200).send(challenge); } else { res.status(403).send("Forbidden"); } } else { res.status(400).send("Bad Request"); } }); this.server.post(this.bot.endpoint, (req, res) => { const { object, entry } = req.body; if (object !== "page") { res.status(400).send("Bad Request"); return; } for (const e of entry) { const messaging = e.messaging[0]; const event = determineEventType(messaging); if (event) { this.emit(event, messaging); } } res.sendStatus(200); }); this.server.listen(this.bot.port, async () => { if (!this.bot.id || !this.bot.name) { await this.getAppInfo().catch((error) => { logger.error( "Error getting app info:", error instanceof Error ? error.message : String(error) ); }); } logger.info( `${this.bot.name} ${colors.gray}(${this.bot.id})${colors.reset} is running on port ${colors.yellow}${this.bot.port}${colors.reset} ${colors.gray}(${this.bot.endpoint})${colors.reset}` ); }); } /** * Sends an HTTP request to the Facebook Graph API. * @template T - The type of the response data. * @param method - The HTTP method. * @param endpoint - The API endpoint. * @param requestBody - The request body for 'POST' requests. * @returns A promise that resolves with the response data. * @throws Will throw an error if the response is not ok. */ async sendRequest(method, endpoint, requestBody) { const response = await fetch( `${GRAPH_URL}/${this.bot.version}/${endpoint}?access_token=${this.accessToken}`, { method, headers: { "Content-Type": "application/json" }, body: requestBody ? JSON.stringify(requestBody) : void 0 } ); if (!response.ok) { const errorData = await response.json(); throw new Error(`HTTP error!: ${response.statusText}`, { cause: errorData }); } return response.json(); } /** * Retrieves the bot's application information from the Facebook API. */ async getAppInfo() { const response = await this.sendRequest("GET", "me"); this.bot.id = response.id; this.bot.name = response.name; } /** * Sends a message to a recipient. * @param recipientId - The ID of the recipient. * @param message - The message object to send. */ async sendMessage(recipientId, message) { await this.sendRequest("POST", "/me/messages", { recipient: { id: recipientId }, message }); } /** * Sends a text message to a recipient. * @param recipientId - The ID of the recipient. * @param message - The text message to send. * @throws Will throw an error if the message exceeds 2000 characters. */ async sendTextMessage(recipientId, message) { if (message.length > 2e3) { throw new Error("Message exceeds 2000 character limit"); } await this.sendRequest("POST", "/me/messages", { recipient: { id: recipientId }, message: { text: message } }); } /** * Sends an attachment (audio, file, image, video, or template) to a recipient. * @param recipientId - The ID of the recipient. * @param type - The type of the attachment. * @param url - The URL of the attachment. * @param isReusable - Whether the attachment is reusable. */ async sendAttachment(recipientId, type, url, isReusable = true) { await this.sendRequest("POST", "me/messages", { recipient: { id: recipientId }, message: { attachment: { type, payload: { url, is_reusable: isReusable } } } }); } /** * Sets the typing status of the recipient. * @param recipientId - The ID of the recipient. * @param isTyping - Whether the recipient is typing. */ async setTyping(recipientId, isTyping) { await this.sendRequest("POST", "/me/messages", { recipient: { id: recipientId }, sender_action: isTyping ? "typing_on" : "typing_off" }); } }; // src/messaging/buttons/CallButton.ts var CallButton = class { title; phone_number; /** * Creates a new CallButton. * @param title - The title of the button. * @param phone_number - The phone number to call when clicked. */ constructor(title, phone_number) { if (title.length > 20) { throw new Error("Button title must be 20 characters or less."); } this.title = title; this.phone_number = phone_number; } /** * Converts the CallButton object to a JSON representation. * @returns The JSON representation of the CallButton. */ toJSON() { return { type: "phone_number", title: this.title, payload: this.phone_number }; } }; // src/messaging/buttons/PostbackButton.ts var PostbackButton = class { title; payload; /** * Creates a new PostbackButton. * @param title - The title of the button. * @param payload - The payload to send to the server when clicked. */ constructor(title, payload) { if (title.length > 20) { throw new Error("Button title must be 20 characters or less."); } this.title = title; this.payload = payload; } /** * Converts the PostbackButton object to a JSON representation. * @returns The JSON representation of the PostbackButton. */ toJSON() { return { type: "postback", title: this.title, payload: this.payload }; } }; // src/messaging/buttons/URLButton.ts var URLButton = class { title; url; webview_height_ratio; messenger_extensions; fallback_url; webview_share_button; /** * Creates a new URLButton. * @param title - The title of the button. * @param url - The URL to open when the button is clicked. */ constructor(title, url) { if (title.length > 20) { throw new Error("Button title must be 20 characters or less."); } this.title = title; this.url = url; } /** * Sets the height ratio for the webview. * @param webview_height_ratio - The height ratio of the webview. * @returns The current instance of the URLButton. */ setWebviewHeightRatio(webview_height_ratio) { this.webview_height_ratio = webview_height_ratio; return this; } /** * Enables or disables Messenger extensions. * @param messenger_extensions - Whether to enable Messenger extensions. * @returns The current instance of the URLButton. */ setMessengerExtensions(messenger_extensions) { this.messenger_extensions = messenger_extensions; return this; } /** * Sets a fallback URL if Messenger extensions are not supported. * @param fallback_url - The fallback URL. * @returns The current instance of the URLButton. */ setFallbackUrl(fallback_url) { this.fallback_url = fallback_url; return this; } /** * Hides or shows the webview share button. * @param webview_share_button - Whether to hide or show the share button. * @returns The current instance of the URLButton. */ setWebviewShareButton(webview_share_button) { this.webview_share_button = webview_share_button; return this; } /** * Converts the URLButton object to a JSON representation. * @returns The JSON representation of the URLButton. */ toJSON() { return { type: "web_url", title: this.title, url: this.url, webview_height_ratio: this.webview_height_ratio, messenger_extensions: this.messenger_extensions, fallback_url: this.fallback_url, webview_share_button: this.webview_share_button }; } }; // src/messaging/templates/ButtonTemplate.ts var ButtonTemplate = class { text; buttons = []; /** * Creates a new ButtonTemplate. * @param text - The text to display in the template. Must be 640 characters or less. * @throws Error if the text exceeds 640 characters. */ constructor(text) { if (text.length > 640) { throw new Error("Button template text must be 640 characters or less."); } this.text = text; } /** * Adds buttons to the ButtonTemplate. A maximum of 3 buttons can be added. * @param buttons - An array of Button objects to add to the template. * @returns The current instance of the ButtonTemplate. * @throws Error if adding the buttons exceeds the maximum of 3 buttons. */ addButtons(buttons) { if (this.buttons && this.buttons.length + buttons.length > 3) { throw new Error("Button template can have a maximum of 3 buttons."); } this.buttons = this.buttons ? [...this.buttons, ...buttons] : buttons; return this; } /** * Converts the ButtonTemplate into the JSON format required for sending the template in a messaging platform. * @returns The ButtonTemplate in the required JSON format. */ toJSON() { return { attachment: { type: "template", payload: { template_type: "button", text: this.text, buttons: this.buttons.map((button) => button.toJSON()) } } }; } }; // src/messaging/templates/GenericTemplate.ts var GenericElement = class { title; subtitle; image_url; default_action; buttons = []; /** * Creates a new GenericElement. * @param title - The title of the element. Must be 80 characters or less. * @throws Error if the title exceeds 80 characters. */ constructor(title) { if (title.length > 80) { throw new Error("Title must be 80 characters or less."); } this.title = title; } /** * Sets the subtitle of the GenericElement. * @param subtitle - The subtitle to set. Must be 80 characters or less. * @returns The current instance of GenericElement. * @throws Error if the subtitle exceeds 80 characters. */ setSubtitle(subtitle) { if (subtitle.length > 80) { throw new Error("Subtitle must be 80 characters or less."); } this.subtitle = subtitle; return this; } /** * Sets the image URL of the GenericElement. * @param image_url - The image URL to set. * @returns The current instance of GenericElement. */ setImageUrl(image_url) { this.image_url = image_url; return this; } /** * Sets the default action of the GenericElement. * @param default_action - The default action object to set. * @returns The current instance of GenericElement. */ setDefaultAction(default_action) { this.default_action = default_action; return this; } /** * Adds buttons to the GenericElement. A maximum of 3 buttons can be added. * @param buttons - An array of Button objects to add. * @returns The current instance of GenericElement. * @throws Error if adding the buttons exceeds the maximum of 3 buttons. */ addButtons(buttons) { if (this.buttons && this.buttons.length + buttons.length > 3) { throw new Error("Button template can have a maximum of 3 buttons."); } this.buttons = this.buttons ? [...this.buttons, ...buttons] : buttons; return this; } /** * Converts the GenericElement into a JSON object. * @returns The GenericElement as a JSON object. */ toJSON() { return { title: this.title, subtitle: this.subtitle, image_url: this.image_url, default_action: this.default_action, buttons: this.buttons }; } }; var GenericTemplate = class { elements = []; sharable; /** * Creates a new GenericTemplate. * @param options - The options for creating the GenericTemplate. * @param options.sharable - Whether the template is sharable. Defaults to false. */ constructor({ sharable = false } = {}) { this.elements = []; this.sharable = sharable; } /** * Adds an element to the GenericTemplate. A maximum of 10 elements can be added. * @param element - The GenericElement to add. * @returns The current instance of GenericTemplate. * @throws Error if adding the element exceeds the maximum of 10 elements. */ addElement(element) { if (this.elements.length >= 10) { throw new Error("Generic template supports a maximum of 10 elements"); } this.elements.push(...element); return this; } /** * Converts the GenericTemplate into a JSON object. * @returns The GenericTemplate as a JSON object. */ toJSON() { return { attachment: { type: "template", payload: { template_type: "generic", elements: this.elements, sharable: this.sharable } } }; } }; // src/messaging/templates/MediaTemplate.ts var MediaElement = class { media_type; attachment_id; url; buttons = []; /** * Creates a new MediaElement. * @param media_type - The type of media (image or video). */ constructor(media_type) { this.media_type = media_type; } /** * Sets the attachment ID for the MediaElement. * @param attachment_id - The attachment ID to set. * @returns The current instance of MediaElement. * @throws Error if both attachment_id and url are set. */ setAttachmentId(attachment_id) { if (this.url) { throw new Error("Cannot set both attachment_id and url"); } this.attachment_id = attachment_id; return this; } /** * Sets the URL for the MediaElement. * @param url - The URL to set. * @returns The current instance of MediaElement. * @throws Error if both attachment_id and url are set. */ setUrl(url) { if (this.attachment_id) { throw new Error("Cannot set both attachment_id and url"); } this.url = url; return this; } /** * Adds buttons to the MediaElement. A maximum of 3 buttons can be added. * @param buttons - An array of Button objects to add. * @returns The current instance of MediaElement. * @throws Error if adding the buttons exceeds the maximum of 3 buttons. */ addButtons(buttons) { if (this.buttons && this.buttons.length + buttons.length > 3) { throw new Error("Button template can have a maximum of 3 buttons."); } this.buttons = this.buttons ? [...this.buttons, ...buttons] : buttons; return this; } /** * Converts the MediaElement into a JSON object. * @returns The MediaElement as a JSON object. */ toJSON() { return { media_type: this.media_type, ...this.attachment_id && { attachment_id: this.attachment_id }, ...this.url && { url: this.url }, buttons: this.buttons }; } }; var MediaTemplate = class { elements = []; sharable; /** * Creates a new MediaTemplate. * @param options - The options for creating the MediaTemplate. * @param options.sharable - Whether the template is sharable. Defaults to false. */ constructor({ sharable = false } = {}) { this.sharable = sharable; } /** * Adds an element to the MediaTemplate. * @param element - The MediaElement to add. * @returns The current instance of MediaTemplate. */ addElement(element) { this.elements.push(element); return this; } /** * Converts the MediaTemplate into a JSON object. * @returns The MediaTemplate as a JSON object. */ toJSON() { return { attachment: { type: "template", payload: { template_type: "media", elements: this.elements, sharable: this.sharable } } }; } }; // src/messaging/templates/ReceiptTemplate.ts var ReceiptElement = class { title; subtitle; quantity; price; currency; image_url; /** * Creates a new ReceiptElement. * @param title - The title of the element. * @param price - The price of the element. */ constructor(title, price) { this.title = title; this.price = price; } /** * Sets the subtitle of the ReceiptElement. * @param subtitle - The subtitle to set. * @returns The current instance of ReceiptElement. */ setSubtitle(subtitle) { this.subtitle = subtitle; return this; } /** * Sets the quantity of the ReceiptElement. * @param quantity - The quantity to set. * @returns The current instance of ReceiptElement. */ setQuantity(quantity) { this.quantity = quantity; return this; } /** * Sets the currency of the ReceiptElement. * @param currency - The currency to set. * @returns The current instance of ReceiptElement. */ setCurrency(currency) { this.currency = currency; return this; } /** * Sets the image URL of the ReceiptElement. * @param imageUrl - The image URL to set. * @returns The current instance of ReceiptElement. */ setImageUrl(imageUrl) { this.image_url = imageUrl; return this; } /** * Converts the ReceiptElement into a JSON object. * @returns The ReceiptElement as a JSON object. */ toJSON() { return { title: this.title, subtitle: this.subtitle, quantity: this.quantity, price: this.price, currency: this.currency, image_url: this.image_url }; } }; var ReceiptTemplate = class { sharable; recipient_name; merchant_name; order_number; order_url; currency; payment_method; timestamp; elements; address; summary; adjustments; /** * Creates a new ReceiptTemplate. * @param recipient_name - The name of the recipient. * @param order_number - The order number. * @param currency - The currency used. * @param payment_method - The payment method used. * @param options - Additional options for the ReceiptTemplate. * @param options.sharable - Whether the template is sharable. Defaults to false. */ constructor(recipient_name, order_number, currency, payment_method, { sharable = false } = {}) { this.recipient_name = recipient_name; this.order_number = order_number; this.currency = currency; this.payment_method = payment_method; this.sharable = sharable; this.elements = []; this.summary = { total_cost: 0 }; } /** * Sets the merchant name for the ReceiptTemplate. * @param merchant_name - The merchant name to set. * @returns The current instance of ReceiptTemplate. */ setMerchantName(merchant_name) { this.merchant_name = merchant_name; return this; } /** * Sets the order URL for the ReceiptTemplate. * @param order_url - The order URL to set. * @returns The current instance of ReceiptTemplate. */ setOrderUrl(order_url) { this.order_url = order_url; return this; } /** * Sets the timestamp for the ReceiptTemplate. * @param timestamp - The timestamp to set. * @returns The current instance of ReceiptTemplate. */ setTimestamp(timestamp) { this.timestamp = timestamp; return this; } /** * Sets the address for the ReceiptTemplate. * @param address - The address object to set. * @returns The current instance of ReceiptTemplate. */ setAddress(address) { this.address = address; return this; } /** * Sets the summary for the ReceiptTemplate. * @param summary - The summary object to set. * @returns The current instance of ReceiptTemplate. */ setSummary(summary) { this.summary = { ...this.summary, ...summary }; return this; } /** * Adds an adjustment to the ReceiptTemplate. * @param adjustment - The adjustment object to add. * @returns The current instance of ReceiptTemplate. */ addAdjustment(adjustment) { if (!this.adjustments) { this.adjustments = []; } this.adjustments.push(...adjustment); return this; } /** * Adds an element to the ReceiptTemplate. A maximum of 100 elements can be added. * @param element - The ReceiptElement to add. * @returns The current instance of ReceiptTemplate. * @throws Error if adding the element exceeds the maximum of 100 elements. */ addElement(element) { if (this.elements.length >= 100) { throw new Error("Receipt template supports a maximum of 100 elements"); } this.elements.push(...element); return this; } /** * Converts the ReceiptTemplate into a JSON object. * @returns The ReceiptTemplate as a JSON object. */ toJSON() { return { attachment: { type: "template", payload: { template_type: "receipt", sharable: this.sharable, recipient_name: this.recipient_name, merchant_name: this.merchant_name, order_number: this.order_number, order_url: this.order_url, currency: this.currency, payment_method: this.payment_method, timestamp: this.timestamp, elements: this.elements, address: this.address, summary: this.summary, adjustments: this.adjustments } } }; } }; // src/messaging/QuickReplies.ts var QuickReply = class { content_type = "text"; title; payload; image_url; /** * Creates a new QuickReply. * @param title - The title of the quick reply. */ constructor(title) { this.title = title; } /** * Sets the payload for the QuickReply. * @param payload - The payload to set. * @returns The current instance of QuickReply. */ setPayload(payload) { this.payload = payload; return this; } /** * Sets the image URL for the QuickReply. * @param imageUrl - The image URL to set. * @returns The current instance of QuickReply. */ setImageUrl(imageUrl) { this.image_url = imageUrl; return this; } /** * Converts the QuickReply into a JSON object. * @returns The QuickReply as a JSON object. */ toJSON() { return { content_type: this.content_type, title: this.title, payload: this.payload, image_url: this.image_url }; } }; var QuickReplies = class { text; attachment; quick_replies = []; /** * Creates a new QuickReplies instance. * @param text - The text to display with the quick replies. */ constructor(text) { this.text = text; } /** * Sets the attachment for the QuickReplies. * @param attachment - The attachment object to set. * @returns The current instance of QuickReplies. */ setAttachment(attachment) { this.attachment = attachment; return this; } /** * Adds quick replies to the QuickReplies instance. * @param replies - An array of QuickReply objects to add. * @returns The current instance of QuickReplies. */ addQuickReply(replies) { this.quick_replies.push(...replies); return this; } /** * Converts the QuickReplies into a JSON object. * @returns The QuickReplies as a JSON object. */ toJSON() { return { text: this.text, attachment: this.attachment, quick_replies: this.quick_replies }; } }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { Bot, ButtonTemplate, CallButton, GenericElement, GenericTemplate, MediaElement, MediaTemplate, PostbackButton, QuickReplies, QuickReply, ReceiptElement, ReceiptTemplate, URLButton, colors, determineEventType, logger }); //# sourceMappingURL=index.cjs.map