image

Предыстория


Год назад решил я создать телеграм бот для того, чтобы поиграть в достаточно популярную новогоднюю игру «Тайный Санта». Вдохновился я тем, что пару лет назад мы на работе компанией решили сыграть в эту игру (это показалось очень круто), и плюс я давно следил за клубом АДМ на Хабре. В октябре-ноябре прошлого года, я понял, что нужно сыграть между своей же компанией в этом году снова, но в этот раз не вытягивая имена написанные на листочке с шапки Деда Мороза, а более технологично, что ли. Поскольку все сидели в телеграме и мне было очень интересно написать туда бота, я решил это сделать именно на этой платформе

Кстати, год назад я уже писал об этом проекте статью на Хабре, но я ничего не говорил о реализации. Не говорил не зря, потому что было стыдно, что ли :) Проект готовился только для компании на работе (человек 15-20 на максималках) а вышло так, что проект «стрельнул» в других кругах после пары статей на ресурсах. Далее, более популярные ресурсы сами начали меня рекламировать (я об этом даже не знал, до того как пошел из ниоткуда большой приток людей).

За месяц без единого вложения в рекламу я собрал 5000+ довольных игроков и очень сильно полюбил этот проект. Но помимо всех его бенефитов, в нем был один огромнейший минус, и это реализация.

Как это было год назад


Было забавно, что у меня была кнопка в боте «присоедениться к комнате». Да, именно присоедЕниться. Мне писали пофиксить этот грамматический баг, но я не рисковал, и вот почему :) Далее, прикладываю кусок кода из прошлогодней версии бота.

elseif ($user['state'] == 7) {
        if (mb_stripos($textMessage, 'присоедениться') !== false) {
            if (!empty($user['santa_for_user_id'])) {
                $text = 'Нельзя присоедениться к комнате, если у тебя уже есть Санта';
            } else {
                $text = "Отлично! Если у Вас уже есть комната - напиши мне ее номер сюда, и ты присоеденишься к этой комнате";
                $db->updateState($userId, 8);
            }
        }

Весь бот — это был один огромнейший index.php файлик, который жил за счет функции mb_stripos, по сути. При чем было очень много одинаковых «ифчиков». Т.е. mb_stripos($textMessage, 'присоедениться') !== false мог встретиться не единажды. Если ты поменяешь в кнопке слово «присоедениться» на «присоединиться», и забудешь поменять какой то ифчик (которых, опять же, много) всё может посыпаться. При чем это может быть не сразу заметно (просто бот на определенных сценариях будет не отвечать как надо). Разок я поменял текст, начали писать юзеры, что на определенном сценарии бот отвечает не как надо. Дальше я рисковать не хотел, и подумал, что ошибка не такая уж и критичная :) В принципе, вы поняли. Если была кнопка, например «Найти случайного Санту», я зацеплялся на слово «случайного» через mb_stripos. Весело было, когда появлялась подобная кнопка, с подобным текстом, и когда не надо оно все попадало в не нужный if (если например и там и там есть слово «случайного») :)

Кстати, заметили $user['state']? На то время я ввёл «состояния», чтобы понимать, на каком состоянии сейчас находится пользователь. Захотел ли он присоединиться к комнате, например, или создать, или может он захотел в одиночную игру поиграть? И для каждого стейта свой набор ифчиков шел, который важно было также не сломать.

Крон файлик, кстати, лежал рядом с index.php, его можно было запустить на прямую из под браузера (видимо тогда меня это не особо волновало). Далее, когда вдруг хотелось добавить какой-то «стейт» (лучше бы я этого не хотел) мне приходилось окунаться в это г.., и с первой же попытки ничего конечно же не получалось. Все это еще и лежало на самом дешевом хостинге за 1$ в месяц, который мог меня послать куда подальше, когда начало писать в «час пик» достаточно большое количество человек.

Это был конечно сущий ад для программиста :)

Что я решил делать в этом году


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

Решил я просто вспоминать основные сценарии, а там и добавлять что-то новое по ходу дела. Начал с вопроса: «а что же использовать для написания архитектуры так, чтобы потом не плакать?». После долгих исследований — выбор пал на Botman. У нас есть небольшие статьи на Хабре о нем. Если коротко, то Botman это очень классная вещь. Его можно поставить как на «чистую», так и сразу установить его сборку вместе с Laravel (да, есть сразу ботман установленный сверху Laravel). Я решил и остановиться на этой версии, поскольку Laravel это явно лучше, чем то, что было год назад :) У него есть возможность кэширования из «коробки», удобный роутинг, artisan, миддлвары, удобность возможность работать с БД и прочие бенефиты. Если вдруг вам не нравится Laravel, Вы можете использовать любой другой фреймворк, и сверху него установить Botman, либо же не использовать фреймворк вовсе. Кстати, Botman построен поверх ReactPHP, что как бы круто :)

Далее, я буду описывать бенефиты Botman:

Есть единый файлик botman.php, в котором Вы можете описать все команды. Пример:

$botman->hears('/start', function (BotMan $bot) {
    $bot->startConversation(new StartConversation());
})->stopsConversation();

При написании команды /start, запустится StartConversation (который должен наследоваться от абстрактного класса Conversation) и реализовывать метод run().

Вопросы задаются достаточно удобно, пример:

$question = Question::create("Ты хочешь создать комнату, или присоединиться в существующую?")->addButtons([Button::create('Создать')->value('create'), Button::create('Присоединиться')->value('join')]);
$this->ask($question, function (Answer $answer) {
            if ($answer->isInteractiveMessageReply()) {
            if ($answer->getValue() == 'join') {

Заметили, что у Button мы можем задать value, и в последствии цепляться потом на него? То есть, на ваших глазах баг с «присоедЕниться» пофикшен, за счет того, что я цепляюсь на value() :)
Кстати, еще можно юзать isInteractiveMessageReply метод, который ответит на вопрос, написали ли вам текст, или нажали интерактивную кнопку, при ответе на вопрос заданный юзеру.

Ботман помог избавиться от стейтов, я могу в методе ask сделать еще один метод ask, например если человек нажал «присоединиться», я просто делаю еще один ask внутри этого if.

Вот еще несколько методов (из очень большого количества), которые предоставляет ботман, которые можно легко понять из названия:
$this->repeat($question);

$this->bot->typesAndWaits($secondsToWait);

$this->bot->reply($reply);
Киллер же фичей ботмана является то, что один код может запускаться на многих платформах. То есть, Вы можете написать свой код, изначально запустить его только для Telegram. Потом, решить, что Вы хотите еще перейти в Facebook Manager, и вам совсем не надо начинать разбираться с SDK Facebook, разработчики Botman уже это сделали за вас. Просто нужно будет установить драйвер и задать API Token вашего Facebook Messenger бота в .env. Весь функционал автоматически заработает в фейсбук мессенджере.
Botman поддерживает не только Facebook Messenger и Telegram, в этот список входят также Slack, Skype, WeChat (с полным списком можно ознакомиться у них на сайте).

Также «виновник торжества» славится тем, что с ним уже идет папочка tests/Botman (можете писать юнит тесты, ваш кэп), а также хорошей документацией. Все бенефиты тяжело назвать, поскольку я явно не со всеми поработал, не все помню, но думаю того, что я описал, должно уже хватить, чтобы им как минимум заинтересоваться :)

Ну ок, а хостить опять на хостинге за $1 будем?


Не, в этом году всё серьезно. Хостинг за $10 в месяц и бесплатным доменом с ссл. Шучу :)

Решил подтянуть знания по докеру, купил VPS на DigitalOcean, и развернул проект в докере. Вышло достаточно неплохо, при том что я это всё делал чуть ли не в первый раз. На удивление докер ни разу не падал.

С VPS конечно круче :)

При докере было сильно удобнее вести разработку (версионность на деве и на проде сохранилась).

Самое забавное, что когда я вводил платный функционал в бота, мне нужно было получить апрув от платежной системы. Платежная система постоянно возвращала мне мою заявку на апрув, и говорила «сайт не работает». У меня работал, у друзей работал (мы из Украины), а вот у ребят из РФ не работал. Не долго думая, я увидел, что Роскомнадзор еще год назад забанил айпишник моего дроплета (очень много серверов DigitalOcean пострадало на то время от руки РКН). С этим потом тоже порешали.

На чем написан же все таки твой бот?


  • PHP 7.3
  • Laravel
  • Botman

И всем советую использовать именно этот стэк при написании своих ботов на PHP (чтобы в последствии себе не стрелять в ногу, как это было у меня).

Что хоть нового в боте появилось?


Санта научился звонить


У Санты можно заказать звонок! Он даже будет Вас понимать и слушать :)

Санта звонит на указанный номер (с номеров USA), задает вопросы, например «как ты себя вёл в году?», «что хочешь на Новый Год?», «Стишок знаешь?» и т.д. Если пользователь говорит, что стишок не знает, то Санта пойдет по другому сценарию вопросов, если скажет, что знает, то Санта любезно попросит рассказать стишок :) Еще: когда человек говорит свой список желаний на Новый Год Санте, Санта слушает, и присылает потом этот список желаний пользователю, который заказал звонок (вдруг, ребёнок закрылся от родителей в комнате, а им как-то надо узнать что же он попросил у Санты?). Также Санта присылает саму аудиозапись звонка с Сантой на память :)

Теперь можно узнать кто твой Санта


Зрада? Это же противоречит названию игры «Тайный Санта», разве нет? В принципе — да. НО в прошлом году от количества желающих узнать своего Санту мои ЛС разрывались. «Мне будет дарить подарок босс?», «Нам одной девочке не подарил подарок кто-то, можете сказать, кто ей должен был дарить?» ну и всё в таком роде. Сейчас есть такая возможность, но что бы было не повадно — такое удовольствие выйдет в $5.99 :)

Выводы


Не стоит надеяться на то, что ваш проект всегда будет маленьким. Не нужно создавать один index.php с кучкой ифов, даже если проект начинается с всего лишь пары тройки ифов (я достаточно таких проектов знаю)). Лучше сделать сразу хорошо. Даже если вы потратите на это больше времени, это будет давать вам бенефиты в том, что когда в проект срочно нужно будет изменять/добавлять логику, не пришлось думать как менять ифы так чтобы бот не упал, как это вышло у меня :). Также этот подход (с ифами) учит вас делать все другие решения костыльно (не лучший навык, согласитесь). Ну и конечно подумайте о себе, вам же придется по позже зайти в этот код, и придется с ним разбираться, и тогда будет очень не сладко :(

Всем хорошего кода, написания своих чат-ботов, а также пишите свои проекты. Это кайфово!

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

Если захотелось поиграть — велкам :)

Комментарии (16)


  1. fireSparrow
    17.11.2019 01:26
    +2

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


    1. driverx18 Автор
      17.11.2019 01:31
      +1

      Я кстати долго думал прикладывать ли ссылку или нет на проект, видимо не надо было)
      Я дал свой совет, что нужно использовать, в чем бенефиты подхода, как я начинал писать (с кучки ифов и думал что все будет норм, но по мере роста проекта я понял, что этот подход плохой). Просто я уверен, что очень много проектов чат-ботов это как раз тот самый index.php с кучкой ифов )


  1. Twitt
    17.11.2019 01:41

    знакомая ситуация была, когда занимался написанием бота на фрилансе для вк )) заказчики просили добавлять логику, и по итогу когда логику совсем закрутили, пришлось все переписывать
    botman кстати не поддерживает ВК?


    1. driverx18 Автор
      17.11.2019 01:50

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


  1. kafeman
    17.11.2019 06:48
    +1

    Теперь можно узнать кто твой Санта <...> такое удовольствие выйдет в $5.99
    Есть ли у Санты возможность в течение какого-то времени перебить ставку? Скажем, за $6.99?


  1. extempl
    17.11.2019 08:51

    По-моему, выводы неправильные. И решение неправильное.
    Нет, разумеется, писать сразу с кучей фреймворков где половина дела сделана это хорошо, и по-своему, правильно.
    Но простые вещи с простой логикой всё-же можно и нужно писать простыми.
    Проблема-то изначально крылась не в ифчиках. А в привязке к подстрокам и копипасте.
    Можно сравнивать строки, но нужно избавиться от копипасты. Использовать подход i18n например — вынести сам текст из кода и сравнивать по ключам. Так вы сможете хоть слово единичками заменить и логика не сломается. Не говоря уже о том, для чего оно предназначено — локализовать.
    Ну и, конечно, если и сравнивать, то строки целиком, а не подстроки (проблема "случайного") с уникальными ключами.


    Так же, что бы я сделал, так это вынес бы флоу (кнопки C и D появятся только после нажатия кнопки B, а после A нужно спросить адрес) в конфиг ("стейт"-проблема) с отдельной логикой построения кнопок. Это сильно упростит дальнейшую поддержку, расширение или изменение этих самых флоу.
    Подозреваю, в большинстве бойлерплейтов для ботов это работает именно так.


    P.S. Не говоря уже о глобальном поиске с заменой.


  1. Gexon
    17.11.2019 08:52

    Автор походу узнал новое слово — бенефит.


  1. Andchir
    17.11.2019 16:39

    Идея хорошая (создать бота), но реализация не очень. Зашел на сайт и ничего не понял. Зачем мне платный звонок от Санты? Что я получу за свои деньги? Бот тоже ничего толком не объясняет, а сразу предлагает дать ему список желаний. Что он будет делать с этим списком?


    1. driverx18 Автор
      17.11.2019 16:49
      -1

      Очень тяжело мне понять, что же непонятно)
      Когда человек жмет /start, ему говорится, что Санта звонит фанатам, если заинтересовало — жми /call (где вся инфа с примерами)
      С вишлистом тоже не совсем понятно что вам непонятно) этот вишлист попадет вашему санте потом, и он будет понимать, какой подарок вы хотите (и ему проще будет выбрать подарок)


      1. Andchir
        17.11.2019 17:26

        если заинтересовало — жми /call (где вся инфа с примерами)
        Т.е. вы ожидаете заинтересованность до того как дали всю инфу? Можно было бы добавить ещё один шаг "/info".
        этот вишлист попадет вашему санте потом, и он будет понимать, какой подарок вы хотите
        Кто такой этот санта, которому попадет список? Вариантов много: 1. это просто бот, которому пофигу. 2. Это один живой человек, который тоже вряд ли всё будет читать. 3. Это случайный человек, который хочет сделать подарок и получить подарок в ответ (была такая игра вроде в прошлом году).
        У вас там написано «Санта теперь звонит своим фанатам!». Вот это слово «теперь» очень сбивает с толку. Значит, есть ещё какие-то функции вашего бота кроме звонка? А какие?


        1. driverx18 Автор
          17.11.2019 17:39
          -1

          У телеграма есть функционал (когда в боте пишешь слэш) который сразу показывает все команды которые в нем доступны. Там также есть /help.
          Я спорить не буду, может быть — вы правы и я смотрю со стороны разработчика которому точно все понятно. Хотя я вот вижу по статистике, что 900 человек за неделю уже сыграло без вопросов в лс. Но ваш совет принял, буду думать, как сделать проще для пользователей. Спасибо :)


          1. Andchir
            17.11.2019 17:59

            Хотя я вот вижу по статистике, что 900 человек за неделю уже сыграло без вопросов в лс
            Может это люди, которые уже играли раньше в вашу игру, поэтому они знают правила? Или боты. Я так и не нашел ответы на свои вопросы.


            1. driverx18 Автор
              17.11.2019 18:30

              Вы удивитесь, но в прошлом году успешно сыграло 5к+ человек (правила были описаны также))
              Из новых в этом году 60% от нынешних 900
              Так что либо мне надо поменять что-то, либо я не знаю
              p.s.

              Значит, есть ещё какие-то функции вашего бота кроме звонка? А какие?

              Если что, то бот создан для игры в «Тайный Санта». Это пишется везде)
              Звонки — это просто как дополнительный функционал.


              1. Metotron0
                18.11.2019 04:08

                Я тоже, как и предыдущий комментатор, не знаю, что это за игра. И когда я захожу к боту, а он мне описывает, что выберет каких-то одиночных Сант, командных Сант, то мне хотелось бы видеть немного предыстории. Хотя бы ссылку на википедию с правилами игры. Да, есть много людей, которые знают, как играть в какие-то популярные игры, например, в Монополию, но есть и те, кто не знает, было бы неплохо предусмотреть их появление наканале у бота.


  1. roller
    20.11.2019 13:22

    Я один не понимаю, как люди, не умеющие программировать начинают зарабатывать деньги с места в карьер?


    1. loki82
      20.11.2019 13:54

      По моему, это не удачно выбранная архитектура.