Так уж вышло, что род моей деятельности тесно переплетен с созданием ботов для Telegram. Писать я их начал сразу после появления Telegram Bot API, тогда никаких инструментов для этого не было. Пришлось самому писать библиотеку для работы с API, о чем я частично уже рассказывал в своей предыдущей статье. С течением времени библиотека несколько раз была переписана и в итоге обросла разными фишками. В статье я постараюсь рассказать о том, как с ее помощью писать ботов.

Для работы с API прежде всего понадобится token, чтобы его получить достаточно написать этому боту и проследовать его инструкциям.
Начну сразу с примера простого бота:
Работает!

А теперь разберем что там написано:
Тут все понятно, просто объявляем модуль и передаем ему наш токен.
Далее мы объявляем команды и роутеры, которые отвечают за эти команды.
После мы создаем контроллер «PingController» и объявляем в нем обработчик команды ping.
Когда бот получит команду от пользователя, он поймет, что за команду ping отвечает контроллер «PingController» и исполнит обработчик команды ping.
На этом можно закрыть статью и начать писать простых ботов, а я продолжу рассказывать.
Как я уже писал выше, роутер отвечает за связь команд и контроллеров, которые эти команды обрабатывают.
Очевидно, что один контроллер может обрабатывать несколько команд.
Так же у роутера есть функция otherwise, с помощью которой можно объявить контроллер, который будет обрабатывать непредусмотренные команды:
Когда бот получил команду от пользователя в игру вступают контроллеры. Как видно из примера выше — контроллеры обрабатывают команды пользователя. В рамках одного контроллера можно обрабатывать несколько команд:
В контроллере также можно писать любые свои функции, переменные.
Каждый контроллер принимает особую переменную $ — scope.
В ней хранится все что нам нужно знать о запросе:
Вы могли заметить, что, например, функцию sendMessage мы вызывали с помощью scope. Дело в том, что помимо полей описанных выше, scope также содержит все функции библиотеки с уже прописанным chatId для конкретного чата. Ту же функцию sendMessage можно вызвать напрямую:
Согласитесь, не очень удобно писать id чата, учитывая, что мы пишем в тот же чат, откуда пришло сообщение.
Иногда мы что-то спрашиваем у пользователя и ждем от него какой-либо информации. Как это реализовать? Конечно, мы можем хранить состояние пользователей и в зависимости от него обрабатывать это в «OtherController», но это не совсем красиво, и ломает структуру и читаемость.
Для таких случаев у scope есть функция waitForRequest:
Функция waitForRequest принимает один аргумент — callback, который она вызовет когда пользователь отправит следующее сообщение. В этот callback передается новый scope. Как видно из примера выше — мы просим пользователя ввести его имя, ждем его следующее сообщение и приветствуем его.
Допустим у нашего бота есть авторизация, в главном контроллере нам нам нужно как-то проверять авторизован ли пользователь и перенаправлять его в логин, но как? Для этого есть функция routeTo, которая принимает команду и выполняет ее как если бы ее отправил нам пользователь:
Часто бывает, что нужно узнать у пользователя какую-то информацию, для этого есть генератор форм:
Как видно из кода у каждого поля есть сообщение отравляемое пользователю, валидатор, в который приходит сообщение от пользователя и сообщение об ошибке в случае неверности вводимых данных. Также можно отправить клавиатуру (keyboard).
Функция runForm вернет нам объект с теми же полями какие мы написали в самой форме, в нашем случае это name и age.
Похожий инструмент есть и для меню:
Меню автоматически создает клавиатуру с названиями полей и отправляет ее вместе с сообщением, которое мы указываем в поле message.
Пункт меню может быть как объектом так и функцией. Если это объект, то пользователь получит подменю, а если функция, то она будет вызвана и позволит нам обработать запрос пользователя.
Также есть возможность задавать расположение кнопок в клавиатуре меню, за это отвечает поле layout, если его ��е передавать совсем, то на каждой строке будет одна кнопка. Можно передать максимальное число кнопок на строке или массив количества кнопок для каждой строки:
У всеж функций для работы с API есть как обязательные параметры, так и необязательные. Например функция sendMessage — по документации у нее для обязательных параметра (chatId, photo), но мы можем передать и любые дополнительные:
При этом последним параметром всегда является callback.
Вот список поддерживаемых на данный момент функций API с обязательными параметрами (напомню, что если вызывать их из scope, то параметр chatId не нужен):
Примеры вызова некоторых функций:
На этом все, спасибо всем кто осилил статью, а вот и ссылка на GitHub — github.com/Naltox/telegram-node-bot и NPM: npmjs.com/package/telegram-node-bot

Для работы с API прежде всего понадобится token, чтобы его получить достаточно написать этому боту и проследовать его инструкциям.
Начну сразу с примера простого бота:
'use strict' var tg = require('telegram-node-bot')('YOUR_TOKEN') tg.router. when(['ping'], 'PingController') tg.controller('PingController', ($) => { tg.for('ping', () => { $.sendMessage('pong') }) })
Работает!
А теперь разберем что там написано:
var tg = require('telegram-node-bot')('YOUR_TOKEN')
Тут все понятно, просто объявляем модуль и передаем ему наш токен.
tg.router. when(['ping'], 'PingController')
Далее мы объявляем команды и роутеры, которые отвечают за эти команды.
tg.controller('PingController', ($) => { tg.for('ping', () => { $.sendMessage('pong') }) })
После мы создаем контроллер «PingController» и объявляем в нем обработчик команды ping.
Когда бот получит команду от пользователя, он поймет, что за команду ping отвечает контроллер «PingController» и исполнит обработчик команды ping.
На этом можно закрыть статью и начать писать простых ботов, а я продолжу рассказывать.
Роутер
Как я уже писал выше, роутер отвечает за связь команд и контроллеров, которые эти команды обрабатывают.
Очевидно, что один контроллер может обрабатывать несколько команд.
Так же у роутера есть функция otherwise, с помощью которой можно объявить контроллер, который будет обрабатывать непредусмотренные команды:
tg.router. when(['test', 'test2'], 'TestController'). // "TestController" будет обрабатывать как команду test, так и команду test2 when(['ping'], 'PingController'). otherwise('OtherController') //"OtherController" будет вызван для всех остальных команд.
Контроллер
Когда бот получил команду от пользователя в игру вступают контроллеры. Как видно из примера выше — контроллеры обрабатывают команды пользователя. В рамках одного контроллера можно обрабатывать несколько команд:
tg.controller('TestController', ($) => { tg.for('test', () => { $.sendMessage('test') }) tg.for('test2', () => { $.sendMessage('test2') }) })
В контроллере также можно писать любые свои функции, переменные.
Scope
Каждый контроллер принимает особую переменную $ — scope.
В ней хранится все что нам нужно знать о запросе:
- chatId — id чата, откуда пришел запрос
- user — информация о пользователе, который отправил запрос, подробнее тут
- message — вся информация о сообщении, подробнее
- args — сообщение, которое отправил пользователь, но без самой команды. (Если пользователь отправит "/start 1" в args будет — «1»)
Вы могли заметить, что, например, функцию sendMessage мы вызывали с помощью scope. Дело в том, что помимо полей описанных выше, scope также содержит все функции библиотеки с уже прописанным chatId для конкретного чата. Ту же функцию sendMessage можно вызвать напрямую:
tg.controller('TestController', ($) => { tg.for('test', () => { tg.sendMessage($.chatId, 'test') }) tg.for('test2', () => { tg.sendMessage($.chatId, 'test2') }) })
Согласитесь, не очень удобно писать id чата, учитывая, что мы пишем в тот же чат, откуда пришло сообщение.
Цепочка вызовов
Иногда мы что-то спрашиваем у пользователя и ждем от него какой-либо информации. Как это реализовать? Конечно, мы можем хранить состояние пользователей и в зависимости от него обрабатывать это в «OtherController», но это не совсем красиво, и ломает структуру и читаемость.
Для таких случаев у scope есть функция waitForRequest:
tg.controller('TestController', ($) => { tg.for('/reg', ($) => { $.sendMessage('Send me your name!') $.waitForRequest(($) => { $.sendMessage('Hi ' + $.message.text + '!') }) }) })
Функция waitForRequest принимает один аргумент — callback, который она вызовет когда пользователь отправит следующее сообщение. В этот callback передается новый scope. Как видно из примера выше — мы просим пользователя ввести его имя, ждем его следующее сообщение и приветствуем его.
Навигация
Допустим у нашего бота есть авторизация, в главном контроллере нам нам нужно как-то проверять авторизован ли пользователь и перенаправлять его в логин, но как? Для этого есть функция routeTo, которая принимает команду и выполняет ее как если бы ее отправил нам пользователь:
tg.controller('StartController', ($) => { tg.for('/profile', ($) => { if(!logined){ // какая то логика авторизации $.routeTo("/login") // перенаправляем пользователя } }) })
Формы
Часто бывает, что нужно узнать у пользователя какую-то информацию, для этого есть генератор форм:
var form = { name: { q: 'Send me your name', error: 'sorry, wrong input', validator: (input, callback) => { if(input['text']) { callback(true) return } callback(false) } }, age: { q: 'Send me your age', error: 'sorry, wrong input', validator: (input, callback) => { if(input['text'] && IsNumeric(input['text'])) { callback(true) return } callback(false) } }, sex: { q: 'Select your sex', keyboard: [['male'],['famale'], ['UFO']], error: 'sorry, wrong input', validator: (input, callback) => { if(input['text'] && ['male', 'famale', 'UFO'].indexOf(input['text']) > -1) { callback(true) return } callback(false) } }, } $.runForm(form, (result) => { console.log(result) })
Как видно из кода у каждого поля есть сообщение отравляемое пользователю, валидатор, в который приходит сообщение от пользователя и сообщение об ошибке в случае неверности вводимых данных. Также можно отправить клавиатуру (keyboard).
Функция runForm вернет нам объект с теми же полями какие мы написали в самой форме, в нашем случае это name и age.
Меню
Похожий инструмент есть и для меню:
$.runMenu({ message: 'Select:', 'Exit': { message: 'Do you realy want to exit?', 'yes': () => { }, 'no': () => { } } })
Меню автоматически создает клавиатуру с названиями полей и отправляет ее вместе с сообщением, которое мы указываем в поле message.
Пункт меню может быть как объектом так и функцией. Если это объект, то пользователь получит подменю, а если функция, то она будет вызвана и позволит нам обработать запрос пользователя.
Также есть возможность задавать расположение кнопок в клавиатуре меню, за это отвечает поле layout, если его ��е передавать совсем, то на каждой строке будет одна кнопка. Можно передать максимальное число кнопок на строке или массив количества кнопок для каждой строки:
$.runMenu({ message: 'Select:', layout: 2, 'test1': () => {}, //будет на первой строке 'test2': () => {}, //будет на первой строке 'test3': () => {}, //будет на второй строке 'test4': () => {}, //будет на третей строке 'test5': () => {}, //будет на четвертой строке })
$.runMenu({ message: 'Select:', layout: [1, 2, 1, 1], 'test1': () => {}, //будет на первой строке 'test2': () => {}, //будет на второй строке 'test3': () => {}, //будет на второй строке 'test4': () => {}, //будет на третей строке 'test5': () => {}, ///будет на четвертой строке })
Функции API
У всеж функций для работы с API есть как обязательные параметры, так и необязательные. Например функция sendMessage — по документации у нее для обязательных параметра (chatId, photo), но мы можем передать и любые дополнительные:
var options = { reply_markup: JSON.stringify({ one_time_keyboard: true, keyboard: [['test']] }) } $.sendMessage('test', options)
При этом последним параметром всегда является callback.
Вот список поддерживаемых на данный момент функций API с обязательными параметрами (напомню, что если вызывать их из scope, то параметр chatId не нужен):
- sendPhoto(chatId, photo)
- sendDocument(chatId, document)
- sendMessage(chatId, text)
- sendLocation(chatId, latitude, longitude)
- sendAudio(chatId, audio)
- forwardMessage(chatId, fromChatId, messageId)
- getFile(fileId)
- sendChatAction(chatId, action)
- getUserProfilePhotos(userId)
- sendSticker(chatId, sticker)
- sendVoice(chatId, voice)
- sendVideo(chatId, video)
Примеры вызова некоторых функций:
var doc = { value: fs.createReadStream('file.png'), //stream filename: 'photo.png', contentType: 'image/png' } $.sendDocument(doc) $.sendPhoto(fs.createReadStream('photo.jpeg')) $.sendAudio(fs.createReadStream('audio.mp3')) $.sendVoice(fs.createReadStream('voice.ogg')) $.sendVideo(fs.createReadStream('video.mp4')) $.sendSticker(fs.createReadStream('sticker.webp'))
На этом все, спасибо всем кто осилил статью, а вот и ссылка на GitHub — github.com/Naltox/telegram-node-bot и NPM: npmjs.com/package/telegram-node-bot
