Pull to refresh

Фреймворк для создания ботов для Telegram

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





Для работы с 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
Tags:
Hubs:
+26
Comments 11
Comments Comments 11

Articles