Так уж вышло, что род моей деятельности тесно переплетен с созданием ботов для 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
