WebPurple Telegram Bot
В последние пару недель я пытаюсь активно саморазвиваться, изучать что-то новое и делать чисто из интереса, даже диплом писать начал.
В первую очередь мой интерес направлен на NodeJS и всё вот это серверное, поэтому дальше BackEnd’а дипломного проекта я не дошёл, но разговор не об этом.
В Рязани наиболее активным и растущим сообществом разработчиков является WebPurple, к которому я себя смело могу отнести, ибо я являюсь активным участником (ну, я так считаю).
Для обсуждения всякого разного был создан чатик в Telegram, ну и чтобы привнести интерактивности, было принято решение сделать какого-нибудь бота, чтоб прямо круто всё было.
Изначально бот предполагался в виде типичного бота из IRC-каналов, но в итоге обсуждения он перешёл в разряд «самостоятельных» ботов. Разработкой решил заниматься я, однако в плане различных особенностей на уровне разработки, языка (NodeJS) и его использования мне помогали коллеги, чему я очень рад и благодаря чему я опять же узнал много нового.
Бот не должен был быть сложным и нагруженным, его задачей было просто джелиться информацией, составленный список функций выглядел как-то так:
- По запросу отправлять последние новости;
- По запросу отправлять информацию о планируемых мероприятиях;
- По запросу отправлять ссылочки на сообщества, репозитории и всякое такое;
- Дать юзеру возможность подписаться на рассылку новостей и как только, так сразу слать ему посты.
Вроде бы всё, изначально информацию планировалось тащить из API нашего сайта, но потом всё-таки передумали, и теперь тащим всё прямиком из группы ВКонтакте, ибо она в большей части кейсов является как бы первоисточником всего и вся.
Дабы упростить процесс разработки я, недолго думая, ввёл следующую команду в терминал и был рад.
npm install --save node-telegram-bot-api
Первые сложности возникли, когда я пытался с Long-Pulling соединения всё на WebHook’и переделать, но это я мануалы плохо читал.
По итогу первая версия, которая просто отдавала «статику» завелась за пару часов. А всё просто потому, что надо учитывать всякие слэши и вот это всё, ну и то, что у меня не чисто бот, а Express, «внутри» которого живёт бот.
const TelegramBot = require('node-telegram-bot-api');
const botUrl = `/bot${botToken}`;
const bot = new TelegramBot(botToken, {
onlyFirstMatch: true,
});
app.post(botUrl, (req, res) => {
bot.processUpdate(req.body);
res.sendStatus(200);
});
app.listen(port, () => {
console.log(`Express server is listening on ${port}`);
bot.setWebHook(`https://example.com${botUrl}`);
});
Стоит учесть, что мне было лень разбираться с нормальным выстраиванием процессов разработки бота, поэтому всё отлаживал я прямо на Heroku, деплоя туда новую версию, т. к. всё было быстро, неудобств я не испытывал. Но для вероятных контрибьюторов я упростил задачу и сделал Long-Pooling в development-окружении, а WebHook’и только в production:
const TelegramBot = require('node-telegram-bot-api');
const botUrl = `/bot${botToken}`;
const bot = new TelegramBot(botToken, {
onlyFirstMatch: true,
polling: !isProduction,
});
app.listen(port, () => {
console.log(`Express server is listening on ${port}`);
if (isProduction) {
bot.setWebHook(`${hookUrl}${botUrl}`);
}
});
Следющая сложность — многоязычность. Довольно много времени было потрачено на поддержку нескольких языков, чтобы бот, в зависимости от языка, слал то или иное сообщение. Не понятно, зачем оно нужно было, но раз есть, значит есть. Поэтому тексты всех сообщений и регулярных выражений получаются из функции, которая принимает на вход яхык.
Ну, и пару раз я думал, что всё сломал, а это просто Telegram кэширует (или что-то ещё с ними делает) настройки языка.
Затем пришла пора работать с API ВКонтакте, обрабатывать хуки от Callback API и вот это всё. Документацию я опять же читал не совсем внимательно, но когда в ответ на запрос нужно отправлять помимо статуса 200 ещё и текст `ok` именно маленькими буквами было для меня чуть-чуть неожиданно.
А перед этим на этот же запрос нужно отправлять ответ в виде «сколько-то_там_значного» кода. Чтобы не производить деплой пару раз подряд я вынес эту переменную в ENV.
Callback API — лучшая часть VK API, потому что... использовать его ну очень просто. Получить текст записи, только что опубликованной в группе — элементарно! Однако, я хотел дать пользователям возможность получать контент по некоторому набору категорий, которые задавались в постах хэш-тэгами. Несколько реализаций «парсера» были испробованы, в итоге остановился на варианте ниже, т. к. проще, удобнее, теги можно обработать перед отправкой:
const formattedText = text.replace(
regExp,
(_, type) => {
types.push(type);
return `#${type}`;
}
);
Плюс ко всему теперь на любой запрос нужен хоть какой-то ключ, из сообщества нельзя вытащить список эвентов, организованных им, поэтому пришлось приделывать костыли в виде поиска по группам от имени пользователя и фильтрации нужных групп-эвентов. Т. к. поиск идёт по названиям, то кто-то левый вполне может попасть в выборку, ведь даже список организаторов эвента получить нельзя. Надеюсь, ВК скоро решат эту проблему.
Таким образом получение планируемых эвентов выглядит как-то так:
fetch(/* Получаем 5 групп по определённому названию, с типом "event", старт которых только будет в будущем */)
.then(r => r.json())
.then((r) => {
// Для вязкости добавим проверок
if (r.response && r.response.items && r.response.items.length) {
const groups = r.response.items.map(g => g.id);
fetch(/* О каждой группе получим информацию */)
.then(res => res.json())
.then((res) => {
if (res.response && res.response.length) {
const processEvent = vkEventProcess(uid, bot);
res.response
.sort((a, b) => a.start_date - b.start_date)
.reduce(
// Тут "чейним" отправку сообщений, чтобы был точный порядок
(promise, event) => promise.then(
() => processEvent(event),
err => console.log(err),
),
Promise.resolve(),
);
}
});
} else {
bot.sendMessage(uid, getNoEventsMessage(lang));
}
});
Получение ключа пользователя для сервера — отдельная повесть, в неё я углубляться не буду, просто скажу, что раньше было лучше... и проще с этим всем. Да и с документацией API дела лучше обстояли, во всяком случае не было подобного:
Описание `member_status`
Потом были этапы отладки, поиска багов и вот этого всего «предрелизного». Затем проведены интеграции, написаны тесты, небольшой README, добавлена возможность нормальной локальной отладки (был бы localetunnel, была б сказка, но там всё от параметров сети сильно зависит), CI, простой деплой на собственный инстанс Heroku и... «релиз».
После релиза я обнаружил, что событие «Репост» у ВКонтакте работает не совсем так, как я ожадал, поэтому пришлось быстренько фиксить, но все подписчики бота тогда уже получили невалидное сообщение.
Пост изначально планировался каким-то более содержательным и интересным, но из-за своей лени я остановился просто на перечислении сложностей, которые возникли в процессе разработки. Это всё-таки просто мой дневник в стиле «а что я вообще тогда делал».
Посмотреть на бота можно здесь: Telegram Bot.
Поучаствовать в разработке, предложить функционал и сообщить о багах можно здесь: GitHub.