Как написать игрового Telegram-бота в домашних условиях

Возможно, кто-то о нас слышал. Мы — популярное интернет-издание по программированию. Мы не только пишем статьи на тему современной разработки, но и ищем способы, как держать аудиторию в постоянной заинтересованности к тому, что мы делаем.

В закладки

Однажды мы решили, что нам нужно сделать бота. Бот должен был давать логические задачи в групповом чате в Telegram «Развиваем логику», закреплять их на то время, пока её решают, не давать новую, пока ответ кого-то из участников не наберёт десять (потом снизили до пяти) плюсов, а также вывешивать топ наиболее успешных решателей задачек.

Идея не приходит одна

Задача довольно простая, подумали мы, особенно учитывая волну популярности ботов и количество материала в сети по этой теме. Однако материала не очень качественного: кто-то не использует композер, а кто-то даже пишет весь код в одном файле-обработчике, полагая, очевидно, будто количество файлов влияет на скорость работы кода.

Сначала бот должен был просто давать задачи, причём их можно было пропускать, постоянно вызывая одну и ту же команду. Это первая проблема, которую мы стали решать. Выход нашёлся почти сразу: сохранение состояния бота (а точнее, текущей команды) в базе. Другими словами, получая команду /get, бот даёт задачу и сохраняет команду в базе.

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

После запуска бота в работу мы обнаружили ещё одну проблему: хэштеги не очень удобны для поиска. Ведь задача может уйти далеко наверх, а листать до неё или забивать в поиске — достаточно неинтересное занятие, чтобы стравливать его пользователю. Поэтому мы решили сразу же закреплять задачу, которую даёт бот. Причём закрепляет как раз он сам, а не мы (и да, делает это без уведомлений).

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

Поэтому мы подумали, что коллективное решение намного справедливее решения одного человека, и дали пользователям выбирать самим, помечая плюсом сообщение, с которым они согласны.

Какое решение быстрее наберёт пять плюсов, то и выбирается ботом как лучшее. Это хорошее решение не только с точки зрения справедливости, но и автоматизации: нам больше не придётся следить за чатом и читать все ответы.

Подготовка к созданию

Дело осталось за малым: написать бота. Мы ограничились Composer, библиотекой telegram-bot-sdk и symfony/dotenv для парсинга .env-файла. Весь код приводить не буду: он большой. Посмотреть на то, что получилось, можно по ссылке.

Composer — это стандарт при разработке на PHP. Он позволяет скачивать сторонние библиотеки на проект и предоставляет удобный механизм по автозагрузке классов. Вся работа с Composer происходит через консоль и в файле composer.json. Обычно он выглядит так:

// composer.json { "require": { "irazasyed/telegram-bot-sdk": "3.*@dev", "symfony/dotenv": "^4.2" }, "autoload": { "psr-4": { "App\\": "src/" } } }

Если вы разрабатываете не на фреймворке, то во множестве случаев создаёте composer.json самостоятельно и заполняете секцию autoload, которая загружает ваши классы по правилу psr-4, о котором можно найти много информации в интернете.

Далее вы выполняете команду composer install, и автозагрузка начинает работать. Также не забудьте про библиотеки, которые нужно установить, для этого выполните в терминале в папке с проектом следующие две команды:

composer require irazasyed/telegram-bot-sdk composer require symfony/dotenv

И тогда ваш composer.json станет похож на тот, что я показывал выше.

Mr. Bot

Сразу же продемонстрирую готовую структуру проекта:

Файл app.php является точкой входа в наше приложение, на который мы вешаем веб-хук (это значит, что бот не будет постоянно опрашивать сервер на наличие обновлений; он их будет получать только тогда, когда они будут). Вот как он выглядит:

// app.php <?php use App\Bot\CommonChatHandler; use App\Bot\PrivateChatHandler; use App\Handler; use App\Storage\DB; use Telegram\Bot\Api; require __DIR__ .'/vendor/autoload.php'; $settings = require __DIR__ . '/config/settings.php'; $api = new Api($settings['token']); $bot = new Handler($api); $db = new DB($settings['db']); if ($bot->getChatType() === "private") { (new PrivateChatHandler($bot, $db))->start(); } elseif ($bot->getChatType() === "supergroup") { (new CommonChatHandler($bot, $db))->start(); }

Ничего необычного, создаём объекты всех нужных нам классов и делаем проверку на то, в каком чате мы находимся — приватном или супергруппе. Да, разделить код на достаточно независимые части является хорошей практикой. К тому же легче рефакторить и добавлять код, когда проблемы того потребуют.

Мы любим чистый код, поэтому настройки храним в файле .env, что позволит любому пользователю ввести свои, и бот будет работать (разумеется, после создания всех нужных таблиц).

// .env DB_DSN=mysql:host=changeme;dbname=changeme DB_USERNAME=changeme DB_PASSWORD=changeme BOT_ADMIN=changeme TOKEN=changeme

Однако получить переменные окружения из .env можно только в том случае, если вы спарсите этот файл. Делается это крайне просто:

// config/settings.php <?php use Symfony\Component\Dotenv\Dotenv; $dotenv = new Dotenv(); $dotenv->loadEnv(__DIR__ . '/../.env'); return [ 'db' => [ 'dsn' => getenv('DB_DSN'), 'username' => getenv('DB_USERNAME'), 'password' => getenv('DB_PASSWORD') ], 'token' => getenv('TOKEN') ];

Это тот самый файл, который мы включили в app.php. Там мы просто по ключу достаём нужные нам настройки:

$settings = require __DIR__ . '/config/settings.php'; $settings['token']; $settings['db'];

В папках Admin и User хранятся классы по работе как с приватным чатом, так и с супергруппой. Это не только удобно, как я говорил выше, но и даёт возможность отключить деятельность бота в одном из режимов, если мы соберёмся добавлять в него функциональность.

Больше томить вас кодом не буду, повторю только, что теперь он в свободном доступе.

Так мы решили сразу несколько проблем и написали достаточно неплохой, легко поддерживаемый код для бота, который по-прежнему в добром здравии и надёжно выполняет свои функции.

Материал опубликован пользователем. Нажмите кнопку «Написать», чтобы поделиться мнением или рассказать о своём проекте.

Написать
{ "author_name": "Библиотека Программиста", "author_type": "self", "tags": [], "comments": 23, "likes": 26, "favorites": 23, "is_advertisement": false, "subsite_label": "dev", "id": 56316, "is_wide": false, "is_ugc": true, "date": "Wed, 23 Jan 2019 21:52:08 +0300" }
{ "id": 56316, "author_id": 242696, "diff_limit": 1000, "urls": {"diff":"\/comments\/56316\/get","add":"\/comments\/56316\/add","edit":"\/comments\/edit","remove":"\/admin\/comments\/remove","pin":"\/admin\/comments\/pin","get4edit":"\/comments\/get4edit","complain":"\/comments\/complain","load_more":"\/comments\/loading\/56316"}, "attach_limit": 2, "max_comment_text_length": 5000, "subsite_id": 235819, "last_count_and_date": null }

23 комментария 23 комм.

Популярные

По порядку

Написать комментарий...
2

Мы ограничились composer, библиотекой telegram-bot-sdk

telegram-bot-sdk можно сказать заброшенный проект, они 2 года не добавляли новые фичи из Telegram Bot API, а там много что появилось.
Вот эта либа получше https://github.com/TelegramBot/Api но и там не все фичи реализованы, многое сам дописывал. Полных библиотек наверное и нету.

Ответить
0

Это совсем не значит, что если там чего-то нет, это нельзя использовать. Например, в ней не добавили возможность закреплять сообщения и ограничивать пользователя на сутки. Но мы всё равно вызываем эти методы так, будто они там есть (хоть phpstorm на это и ругается), и они работают. Можете скачать и убедиться сами.

Ответить
1

Например, в ней не добавили возможность закреплять сообщения и ограничивать пользователя на сутки

С этими действиями понятно, там параметры скалярные и в ответ приходит только true или false. Если бы все методы были такими, то библиотека и не нужна была бы, то же самое что напрямую слать.
Попробуйте сделать вызовы новых методов, где в параметрах есть объекты или в ответах от сервера есть объекты, не реализованные в библиотеке. Например sendInvoice или sendMediaGroup.

Ответить
1

Не помню точно, но метод sendMediaGroup (или что-то похожее) там есть. В любом случае на тот момент эта библиотека была хорошим выбором. В крайнем случае можно сбоку написать свой функционал, обычная практика.

Ответить
0

В крайнем случае можно сбоку написать свой функционал, обычная практика.

Это да, с той библиотекой я тоже многое сам дописывал. Там например нету типов CachedDocument, CachedPhoto и других, нужных для инлайн режима бота.
Полных либ я еще не видел, чтобы прям весь API был реализован.

Ответить
0

Отличный повод написать свою реализацию и войти в топы гитхаба.

Ответить
–1

Возможно, там и больше функционала, но вот это точно никуда не годится:
$bot->sendMessage($chatId, $messageText, null, false, null, $keyboard);
Передавать ассоциативный массив, как это реализовано у нас, намного понятнее и удобнее. А с этой библиотекой придётся мусорить переменными по всему коду, если не хотите оставлять null, false или true магическими переменными, о назначении которых вы забудете на следующий день.

Ответить
1

Передавать ассоциативный массив, как это реализовано у нас, намного понятнее и удобнее.

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

Ответить
0

С этим соглашусь.

Ответить

Комментарий удален

0

Почему был выбран именно PHP?
Для решения задачи написания бота это очень плохо подходящая технология.

Ответить
1

Да какая разница вообще, на чем писать? API телеграма подробнейше документировано, надо просто правильно формировать запросы на удобном для себя языке. Все равно все самое сложное - это логика самого бота.

Ответить
0

PS Я ради интереса написал бота, который полностью жил в базе MSSQL. Просто потому что могу :) (на самом деле, потому что понял, что мне больше ничего не нужно)

Ответить
0

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

Ответить
1

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

Но реальное отличие есть у функциональных языков.
Вот Erlang например может и лучше бы для ботов подошел, но разработка может быть сложнее, специалисты стоят дороже и в итоге оно того не стоит.

Ответить
0

Непонятно, чем именно php уступает другим языкам для этой задачи.

Ответить
0

У PHP реально только одна проблема, низкий порог входа и поэтому есть большое количество говнокода написанное начинающими.
Если аккуратно выбирать фреймворк и библиотеки, то никаких проблем нет.

Ответить
0

Всё так, php продолжают не любить по старой привычке. Адекватных аргументов против него становится все меньше с каждым новым релизом языка. У php много хороших инструментов и библиотек, хорошие фреймворки (Symfony, Slim) и проч.

Ответить
0

Спасибо, а какие еще телеграм боты вы писали? Есть какой-то список?

Ответить
0

Нет, Денис, мы не специализируемся на серийном создании ботов, мы лишь делаем то, что помогает развивать проект. В целом, все боты функционально примерно одинаковые, остальное уже зависит от логики, которую вы кладёте внутрь, и часто она не связана с api телеграма.

Ответить
0

А какие еще темы вы могли бы осветить в /dev?

Желательно, не для начального уровня, уже детальнее и глубже, тут довольно много разработчиков

Ответить
3

Ну, мы ещё делали наш корпоративный сокращатель ссылок по типу bit.ly на фреймворке Symfony. Можем написать, как это работает, если кому интересно. Ещё различные сервисы для внутренних нужд. Планируется также кое-что большое от нашей библиотеки, о чём, возможно, потом мы захотим рассказать.

Ответить
–1

"Возможно, кто-то о нас слышал." и "Мы — популярное интернет-издание по программированию. "
Шуточка за 300

Ответить
0
{ "page_type": "article" }

Прямой эфир

[ { "id": 1, "label": "100%×150_Branding_desktop", "provider": "adfox", "adaptive": [ "desktop" ], "adfox_method": "createAdaptive", "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "ezfl" } } }, { "id": 2, "label": "1200х400", "provider": "adfox", "adaptive": [ "phone" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "ezfn" } } }, { "id": 3, "label": "240х200 _ТГБ_desktop", "provider": "adfox", "adaptive": [ "desktop" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "fizc" } } }, { "id": 4, "label": "240х200_mobile", "provider": "adfox", "adaptive": [ "phone" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "flbq" } } }, { "id": 5, "label": "300x500_desktop", "provider": "adfox", "adaptive": [ "desktop" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "ezfk" } } }, { "id": 6, "label": "1180х250_Interpool_баннер над комментариями_Desktop", "provider": "adfox", "adaptive": [ "desktop" ], "adfox": { "ownerId": 228129, "params": { "pp": "h", "ps": "bugf", "p2": "ffyh" } } }, { "id": 7, "label": "Article Footer 100%_desktop_mobile", "provider": "adfox", "adaptive": [ "desktop", "tablet", "phone" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "fjxb" } } }, { "id": 8, "label": "Fullscreen Desktop", "provider": "adfox", "adaptive": [ "desktop", "tablet" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "fjoh" } } }, { "id": 9, "label": "Fullscreen Mobile", "provider": "adfox", "adaptive": [ "phone" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "fjog" } } }, { "id": 10, "disable": true, "label": "Native Partner Desktop", "provider": "adfox", "adaptive": [ "desktop", "tablet" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fmyb" } } }, { "id": 11, "disable": true, "label": "Native Partner Mobile", "provider": "adfox", "adaptive": [ "phone" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fmyc" } } }, { "id": 12, "label": "Кнопка в шапке", "provider": "adfox", "adaptive": [ "desktop" ], "adfox": { "ownerId": 228129, "params": { "p1": "bscsh", "p2": "fdhx" } } }, { "id": 13, "label": "DM InPage Video PartnerCode", "provider": "adfox", "adaptive": [ "desktop", "tablet", "phone" ], "adfox_method": "createAdaptive", "adfox": { "ownerId": 228129, "params": { "pp": "h", "ps": "bugf", "p2": "flvn" } } }, { "id": 14, "label": "Yandex context video banner", "provider": "yandex", "yandex": { "block_id": "VI-223676-0", "render_to": "inpage_VI-223676-0-1104503429", "adfox_url": "//ads.adfox.ru/228129/getCode?pp=h&ps=bugf&p2=fpjw&puid1=&puid2=&puid3=&puid4=&puid8=&puid9=&puid10=&puid21=&puid22=&puid31=&puid32=&puid33=&fmt=1&dl={REFERER}&pr=" } }, { "id": 15, "label": "Плашка на главной", "provider": "adfox", "adaptive": [ "desktop", "tablet", "phone" ], "adfox": { "ownerId": 228129, "params": { "p1": "byudx", "p2": "ftjf" } } }, { "id": 16, "label": "Кнопка в шапке мобайл", "provider": "adfox", "adaptive": [ "tablet", "phone" ], "adfox": { "ownerId": 228129, "params": { "p1": "byzqf", "p2": "ftwx" } } }, { "id": 17, "label": "Stratum Desktop", "provider": "adfox", "adaptive": [ "desktop" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "fzvb" } } }, { "id": 18, "label": "Stratum Mobile", "provider": "adfox", "adaptive": [ "tablet", "phone" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "fzvc" } } }, { "id": 19, "label": "Тизер на главной", "provider": "adfox", "adaptive": [ "desktop", "tablet", "phone" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "p1": "cbltd", "p2": "gazs" } } } ]
Приложение-плацебо скачали
больше миллиона раз
Подписаться на push-уведомления
{ "page_type": "default" }