1.4.0 • Published 8 months ago

@green-api/whatsapp-chatbot-js-v2 v1.4.0

Weekly downloads
-
License
MIT
Repository
github
Last release
8 months ago

Библиотека для создания WhatsApp-бота

Современная библиотека для создания WhatsApp-бота на Node.js, основанная на состояниях и построенный на базе GREEN-API.

Ссылки поддержки

Support Support Support

Руководства и новости

Guides News News

NPM Version License: MIT

Особенности

  • Система состояний для управления диалогом
  • Встроенное управление сессиями
  • Гибкая обработка сообщений
  • Поддержка навигации
  • Возможность добавления свои адаптеров для разных БД
  • Поддержка TypeScript
  • Автоматическая настройка параметров инстанса

Установка

npm install @green-api/whatsapp-chatbot-js-v2

Быстрый старт

import { WhatsAppBot, State } from '@green-api/whatsapp-chatbot-js-v2';

// Инициализация бота
const bot = new WhatsAppBot({
	idInstance: "your-instance-id",
	apiTokenInstance: "your-token",
	defaultState: "menu"
});

// Пример состояния
const menuState: State = {
	name: "menu",
	async onEnter(message) {
		await bot.sendText(
			message.chatId,
			"Добро пожаловать! Выберите опцию:\n1. Помощь\n2. О нас"
		);
	},
	async onMessage(message) {
		if (message.text === "1") {
			return "help";
		}
		return null; // Продолжить обработку глобальными обработчиками
	}
};

// Добавление состояний и запуск бота
bot.addState(menuState);
bot.start();

Основные компоненты

Конфигурация бота

Полные параметры конфигурации для WhatsAppBot:

interface BotConfig<T = any> {
	/** ID инстанса GREEN API */
	idInstance: string;

	/** Токен инстанса GREEN API */
	apiTokenInstance: string;

	/** Начальное состояние для новых сессий. По умолчанию: "root" */
	defaultState?: string;

	/** Таймаут сессии в секундах. По умолчанию: 300 (5 минут) */
	sessionTimeout?: number;

	/** Функция для генерации сообщения о таймауте сессии на основе данных сессии */
	getSessionTimeoutMessage?: (session: SessionData<T>) => string;

	/** Команда(ы) для навигации назад. По умолчанию: "back" */
	backCommands?: string | string[];

	/** Пользовательский адаптер хранилища для управления сессиями. По умолчанию: MemoryStorage */
	storage?: StorageAdapter<T>;

	/** Пользовательские настройки для инстанса GREEN-API */
	settings?: {
		webhookUrl?: string;
		webhookUrlToken?: string;
		outgoingWebhook?: "yes" | "no";
		stateWebhook?: "yes" | "no";
		incomingWebhook?: "yes" | "no";
		outgoingAPIMessageWebhook?: "yes" | "no";
		outgoingMessageWebhook?: "yes" | "no";
		pollMessageWebhook?: "yes" | "no";
		markIncomingMessagesReaded?: "yes" | "no";
	};

	/** Очищать ли очередь webhook при запуске бота. По умолчанию: false */
	clearWebhookQueueOnStart?: boolean;

	/** Управляет порядком обработки сообщений. При значении true, глобальные обработчики запускаются
	 * перед обработчиком состояний (onMessage). При значении false (по умолчанию), сначала запускается обработчик состояний. */
	handlersFirst?: boolean;
}

WhatsAppBot

Основной класс для создания и управления ботом:

const bot = new WhatsAppBot<CustomSessionData>({
	// Обязательные параметры
	idInstance: "your-instance-id",
	apiTokenInstance: "your-token",

	// Опциональные параметры
	defaultState: "menu",
	sessionTimeout: 30, // 30 секунд
	getSessionTimeoutMessage: (session) => {
		// Пользовательская логика для сообщения о таймауте
		return "Ваша сессия истекла. Начинаем сначала.";
	},
	backCommands: ["back", "назад", "меню"],
	storage: new CustomStorage(),
	clearWebhookQueueOnStart: true,

	// Настройки инстанса GREEN-API
	settings: {
		webhookUrl: "",
		webhookUrlToken: "",
		outgoingWebhook: "no",
		stateWebhook: "no",
		incomingWebhook: "yes",
		outgoingAPIMessageWebhook: "no",
		outgoingMessageWebhook: "no",
		pollMessageWebhook: "yes",
		markIncomingMessagesReaded: "yes"
	}
});

Состояния

Состояния определяют поток диалога:

interface State<T = any> {
	name: string;
	onEnter?: (message, stateData?) => Promise<void | string | StateTransition>;
	onMessage: (message, stateData?) => Promise<void | string | StateTransition | null>;
	onLeave?: (message, stateData?) => Promise<void>;
}

Возвращаемые значения обработчиков состояний:

  • null: Продолжить обработку глобальными обработчиками
  • undefined: Прекратить обработку
  • string: Переход в указанное состояние
  • StateTransition: Переход с данными

Обработчики сообщений

Глобальные обработчики сообщений для определенных шаблонов:

// Точное совпадение текста
bot.onText("help", async (message) => {
	await bot.sendText(message.chatId, "Сообщение помощи");
});

// Регулярное выражение
bot.onRegex(/^order:\s*(\d+)$/, async (message) => {
	const [, orderId] = message.text.match(/^order:\s*(\d+)$/);
	await bot.sendText(message.chatId, `Информация о заказе ${orderId}...`);
});

// Тип сообщения
bot.onType("image", async (message) => {
	await bot.sendText(message.chatId, "Изображение получено!");
});

Хранилище

Встроенное хранилище в памяти с поддержкой пользовательских адаптеров:

interface StorageAdapter<T = any> {
	/** Опциональный эмиттер событий для событий, связанных с сессиями, например, истечение срока жизни сессии */
	events?: StorageEventEmitter<T>;

	/** Получает данные сессии для чата */
	get(chatId: string): Promise<SessionData<T> | null>;

	/** Сохраняет данные сессии для чата */
	set(chatId: string, data: SessionData<T>): Promise<void>;

	/** Опциональный метод для получения значения таймаута сессии (для cleanup процессов) */
	setSessionTimeout?(timeoutMs: number): void;
}

Пример пользовательского хранилища:

class DatabaseStorage implements StorageAdapter {
	public events = new StorageEventEmitter();

	constructor(timeoutSeconds: number) {
		setInterval(() => {
			// Поиск истекших сессий
			const expiredSessions = // ... ваша логика

			for (const session of expiredSessions) {
				this.events.emit('sessionExpired', session.chatId, session);
				// Удаление сессии
			}
		}, 10000);
	}

	async get(chatId: string): Promise<SessionData | null> {
		return await db.sessions.findOne({chatId});
	}

	async set(chatId: string, data: SessionData): Promise<void> {
		await db.sessions.updateOne(
			{chatId},
			{$set: data},
			{upsert: true}
		);
	}
}

Система состояний

Бот использует архитектуру, основанную на состояниях, где каждое состояние представляет определенную точку в потоке диалога. Состояния могут иметь точки входа, обработчики сообщений и точки выхода.

Функции состояний

onEnter

Вызывается при входе бота в состояние. Полезно для отправки начальных сообщений или настройки данных состояния.

const menuState: State = {
	name: "menu",
	async onEnter(message) {
		await bot.sendText(message.chatId, "Добро пожаловать в меню!");
	}
};

Возвращаемые значения onEnter:

  • void - Остаться в текущем состоянии
  • string - Имя состояния для перехода
  • StateTransition - Переход с данными:
    return {
        state: "next_state",
        data: { someData: "value" },
        skipOnEnter: false // Опционально: пропустить onEnter следующего состояния
    };

onMessage

Обрабатывает сообщения, полученные в этом состоянии.

const orderState: State<OrderData> = {
	name: "create_order",
	async onMessage(message, data = {items: []}) {
		if (message.text === "готово") {
			return "confirm_order";
		}

		// Добавить товар в заказ
		data.items.push(message.text);
		return {
			state: "create_order", // Остаться в том же состоянии
			data: data // Обновить данные состояния
		};
	}
};

Возвращаемые значения onMessage:

  • null - Продолжить обработку глобальными обработчиками
  • undefined - Прекратить обработку
  • string - Имя состояния для перехода
  • StateTransition - Переход с данными

onLeave

Вызывается при выходе из состояния. Используется для очистки или финальной обработки.

const paymentState: State = {
	name: "payment",
	async onLeave(message, data) {
		await savePaymentAttempt(data);
	}
};

Управление сессиями

Сессии поддерживают состояние диалога и пользовательские данные между сообщениями.

Структура данных сессии

interface SessionData<T = any> {
	lastActivity: number;     // Временная метка последней активности
	currentState?: string;    // Имя текущего состояния
	stateData?: T;           // Пользовательские данные состояния
	navigationPath?: string[]; // История состояний
	previousState?: string;   // Последнее состояние (для навигации назад)
}

Данные состояния

Пользовательские данные сохраняются между сообщениями в одном состоянии:

interface OrderData {
	orderId: string;
	items: string[];
	total: number;
}

const bot = new WhatsAppBot<OrderData>();

const orderState: State<OrderData> = {
	name: "order",
	async onMessage(message, data = {items: [], total: 0}) {
		// data типизирована как OrderData
		data.items.push(message.text);
		return {state: "order", data};
	}
};

Таймаут сессии

Сессии истекают после периода неактивности (по умолчанию 300 секунд / 5 минут). Вы можете настроить как продолжительность таймаута, так и сообщение, отправляемое при истечении срока жизни сессии:

const bot = new WhatsAppBot({
	// Установить таймаут на 30 секунд
	sessionTimeout: 30,

	// Пользовательское сообщение о таймауте
	getSessionTimeoutMessage: (session) => {
		// Вы можете получить доступ к данным сессии для настройки сообщения
		const userName = session.stateData?.userName || "Пользователь";
		return `Здравствуйте, ${userName}, ваша сессия истекла. Начинаем новый диалог.`;
	}
});

Встроенное хранилище (MemoryStorage) проверяет истекшие сессии каждые 10 секунд и автоматически удаляет их. Если вы реализуете свой собственный адаптер хранилища, вам нужно будет реализовать свой механизм очистки.

События при таймауте сессии

Адаптеры хранилища могут опционально отправлять события при истечении срока жизни сессий. Эта функция позволяет боту отправлять уведомления о таймауте:

import { EventEmitter } from 'events';

class DatabaseStorage implements StorageAdapter {
	public events = new StorageEventEmitter();

	constructor(timeoutSeconds: number) {
		// Настройка очистки, которая отправляет события
		setInterval(() => {
			// Найти истекшие сессии
			const expiredSessions = // ... ваша логика

			for (const session of expiredSessions) {
				this.events.emit('sessionExpired', session.chatId, session);
				// Удалить сессию
			}
		}, 10000);
	}
}

Поле events является опциональным - если вам не нужны уведомления о таймауте, вы можете опустить его в вашей реализации хранилища. Сообщение о таймауте определяется в функции getSessionTimeoutMessage.

Процесс обработки сообщений

Бот предлагает два процесса обработки сообщений, управляемых опцией конфигурации handlersFirst:

Стандартный процесс (handlersFirst: false)

  1. Бот получает сообщение
  2. Обработчик onMessage текущего состояния обрабатывает сообщение
  3. В зависимости от возвращаемого значения:
    • Если null: Глобальные обработчики обрабатывают сообщение (onText, onType, onRegex)
    • Если undefined: Прекратить обработку
    • Если имя состояния/переход: Войти в новое состояние

Процесс "handlersFirst" (handlersFirst: true)

  1. Бот получает сообщение
  2. Глобальные обработчики (onText, onRegex, onType) обрабатывают сообщение
    • Если обработчик возвращает true, продолжить обработку состоянием
    • Если обработчик возвращает что-то другое, прекратить обработку
  3. Если глобальный обработчик вернул true, обработчик onMessage текущего состояния обрабатывает его

Возвращаемые значения обработчиков

При использовании подхода "Обработчики Сначала" (handlersFirst: true), глобальные обработчики сообщений поддерживают дополнительное возвращаемое значение:

  • undefined: Сообщение обработано, прекратить обработку (по умолчанию)
  • true: Сообщение частично обработано, передать сообщение в обработчик состояния (onMessage)
  • что-либо другое: Обрабатывается как undefined (прекратить обработку)

Приоритет обработки сообщений (если handlersFirst: false)

Сообщения обрабатываются в определенном порядке приоритета:

  1. Обработчик состояния (Первый)

    • Обработчик onMessage текущего состояния получает первую возможность обработать каждое сообщение
    • В зависимости от возвращаемого значения:
      • null: Продолжить обработку глобальными обработчиками
      • undefined: Прекратить обработку
      • string: Перейти в указанное состояние и прекратить обработку
      • StateTransition: Перейти с данными и прекратить обработку
  2. Глобальные обработчики (Только если обработчик состояния вернул null) Сообщения, которые не были полностью обработаны состоянием (вернулось null), продолжают обрабатываться глобальными обработчиками в следующем порядке:

    a. Текстовые обработчики

    • Точные совпадения текста (без учета регистра)
    • Первый совпавший обработчик обрабатывает сообщение и прекращает дальнейшую обработку

    b. Обработчики регулярных выражений

    • Совпадения с шаблонами регулярных выражений
    • Первый совпавший шаблон обрабатывает сообщение и прекращает дальнейшую обработку

    c. Обработчики типов

    • Сначала выполняются обработчики конкретных типов
    • Общие обработчики ("*") выполняются последними как резервные
    • Первый совпавший обработчик обрабатывает сообщение и прекращает дальнейшую обработку

Это означает, что если обработчик onMessage вашего состояния возвращает undefined (значение по умолчанию), глобальные обработчики никогда не будут выполнены. Убедитесь, что вы возвращаете null, если хотите разрешить глобальным обработчикам обработать сообщение.

Примеры использования handlersFirst

Настройка порядка обработки

const bot = new WhatsAppBot({
	idInstance: "your-instance-id",
	apiTokenInstance: "your-token",
	defaultState: "menu",
	handlersFirst: true // Обрабатывать глобальные обработчики перед обработчиками состояний
});

Обработчики с разными возвращаемыми значениями

// Обработчик, полностью обрабатывающий сообщение (останавливает дальнейшую обработку)
bot.onText("/help", async (message) => {
	await bot.sendText(message.chatId, "Вот информация по помощи...");
	// Без явного возврата - по умолчанию undefined (прекратить обработку)
});

// Обработчик, частично обрабатывающий сообщение и продолжающий обработку состоянием
bot.onType("location", async (message) => {
	await bot.sendText(message.chatId, "Я получил вашу локацию...");
	return true; // Продолжить обработку состоянием для дополнительной обработки
});

Глобальные обработчики

Текстовые обработчики

Точное совпадение текста (без учета регистра):

bot.onText("меню", async (message) => {
	await bot.sendText(message.chatId, "Главное меню");
});

Обработчики регулярных выражений

Совпадение текстовых шаблонов (regex):

bot.onRegex(/заказ:\s*(\d+)/, async (message) => {
	const [, orderId] = message.text.match(/заказ:\s*(\d+)/);
	await bot.sendText(message.chatId, `Детали заказа ${orderId}...`);
});

Обработчики типов

Обработка определенных типов сообщений:

// Обработка всех изображений
bot.onType("image", async (message) => {
	const url = message.media?.url;
	await processImage(url);
});

// Обработка всех сообщений (fallback)
bot.onType("*", async (message) => {
	console.log("Необработанное сообщение:", message);
});

Пользовательское хранилище

Хранилище по умолчанию находится в памяти (in-memory), но вы можете реализовать пользовательское хранилище:

class DatabaseStorage implements StorageAdapter {
	async get(chatId: string): Promise<SessionData | null> {
		return await db.sessions.findOne({chatId});
	}

	async set(chatId: string, data: SessionData): Promise<void> {
		await db.sessions.updateOne(
			{chatId},
			{$set: data},
			{upsert: true}
		);
	}
}

const bot = new WhatsAppBot({
	storage: new DatabaseStorage()
});

Примеры переходов между состояниями

Простая цепочка состояний

const steps: State[] = [
	{
		name: "step1",
		async onMessage(message) {
			if (message.text) return "step2";
			return undefined;
		}
	},
	{
		name: "step2",
		async onMessage(message) {
			if (message.text) return "step3";
			return undefined;
		}
	}
];

Переходы по условиям (switch/case)

const menuState: State = {
	name: "menu",
	async onMessage(message) {
		switch (message.text) {
			case "1":
				return "orders";
			case "2":
				return "settings";
			default:
				return null;
		}
	}
};

Передача данных между состояниями

interface UserData {
	name?: string;
	age?: number;
}

const nameState: State<UserData> = {
	name: "get_name",
	async onMessage(message, data = {}) {
		return {
			state: "get_age",
			data: {...data, name: message.text}
		};
	}
};

const ageState: State<UserData> = {
	name: "get_age",
	async onMessage(message, data = {}) {
		const age = parseInt(message.text);
		return {
			state: "confirm",
			data: {...data, age}
		};
	}
};

Продвинутые функции

Пользовательские данные состояния

Типизуйте данные ваших состояний:

interface OrderData {
	orderId?: string;
	items: string[];
	total: number;
}

const bot = new WhatsAppBot<OrderData>({...});

const orderState: State<OrderData> = {
	name: "create_order",
	async onMessage(message, data = {items: [], total: 0}) {
		// Типобезопасный доступ к пользовательским данным
		data.items.push(message.text);
		return {
			state: "confirm_order",
			data: data
		};
	}
};

Работа с файлами

Отправка различных типов файлов:

await bot.sendFile(chatId, {
	url: "https://example.com/image.jpg",
	type: "image",
	caption: "Посмотрите на это!"
});

Лучшие практики

  1. Организация состояний

    • Сохраняйте состояния сфокусированными и однозначными
    • Используйте понятные соглашения об именовании
    • Обрабатывайте граничные случаи и ошибки
  2. Обработка ошибок

    • Реализуйте глобальные обработчики ошибок
    • Ведите соответствующее логирование ошибок
    • Предоставляйте понятные пользователю сообщения об ошибках
  3. Управление сессиями

    • Регулярно очищайте старые сессии
    • Реализуйте правильную обработку таймаутов

Примеры

Бот службы поддержки

Смотрите examples/tickets.ts для полного примера системы тикетов поддержки, демонстрирующего управление состояниями, обработку файлов и сложные потоки диалогов.

Реализация пользовательского хранилища

Проверьте examples/custom-storage/ для примера реализации пользовательского провайдера хранилища с простой реализацией бота.

Демонстрационный чат-бот

Ознакомьтесь с нашим демонстрационным чат-ботом, чтобы увидеть реализацию, основанную на GREEN-API.

Лицензия

MIT

1.4.0

8 months ago

1.3.0

8 months ago

1.2.1

8 months ago

1.2.0

8 months ago

1.1.0

8 months ago

1.0.0

8 months ago