Наш магазин на eBay Наш магазин на AliExpress Наш канал в telegram

Инструкция по созданию Telegram ботов. Часть 4. Прикручиваем MySQL к чат-боту для Telegram на php (webhook)

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

Первым делом создаём базу данных (пусть она называется, скажем, MYBASE) и дописываем в наш файл config.php данные для доступа к этой БД:

$dbhost = 'MYHOST';    // адрес сервера БД
$dbname = 'MYBASE';    // имя БД
$dbuser = 'username';  // имя пользователя для доступа к БД
$dbpwd = 'password';   // пароль для доступа к БД

Теперь нужно определиться со структурой нашей БД. Пусть в ней будет всего одна таблица, назовём её, скажем, MYTABLE, которая будет содержать следующие поля:

  • user_id — уникальный идентификатор пользователя в Telegram, varchar(32)
  • first_name — имя пользователя в Telegram, varchar(1024)
  • last_name — фамилия пользователя в Telegram, varchar(1024)
  • nick_name — ник пользователя в Telegram, varchar(1024)
  • registered — время первого подключения юзера (timestamp)
  • lastvisit — время последнего общения юзера с ботом (timestamp)
  • status — статус пользователя (забанен/не забанен), uint(8)

Поле user_id нужно пометить как уникальное, для поля status установить значение по умолчанию 1 (у незабаненых юзеров будет status=1, а у забаненых status=0), для полей registered и lastvisit — установить значение по умолчанию «CURRENT_TIMESTAMP», а также дополнительно установить опцию «on update CURRENT_TIMESTAMP» для поля lastvisit (чтобы каждый раз при обновлении записи значение этого поля автоматически обновлялось).

Два небольших замечания:

  • Обычно все любят заводить в таблице первичный ключ id с автоинкрементом, однако нам он не нужен, поскольку user_id и так будут уникальные (это обеспечивает сам Telegram), без всякого автоинкремента, а использовать значение последнего элемента этого поля для подсчёта количества пользователей в таблице — вообще дурь, так как при удалении или обновлении записей в таблице всё сдвинется в любом случае (при обновлении записей в таблицах (запросом UPDATE) автоинкремент выполняется даже если поля реально не обновлялись).
  • Почему-то во всех экспериментах с ботами Telegram, в любом апдейте и с любыми ботами, я наблюдал одну и ту же картину, — chat_id и user_id у одного и того же пользователя всегда совпадают. Причём, я считаю, что получаемое в полях chat_id и user_id значение — это именно user_id, поскольку он одинаковый при общении юзера с любым другим ботом, а значения chat_id должны в этом случае быть разными по определению, — разные боты — разные чаты.

Последнее замечание не очень похоже на ошибку (как если бы нам просто отдавали не то поле), поскольку отправка сообщений методом sendMessage при использовании этого идентификатора (user_id) работает нормально, хотя в описании сказано, что здесь должен быть chat_id. Техподдержка Telegram мой вопрос на эту тему проигнорировала. В общем, либо нам чего-то не договаривают, либо я что-то, где-то не дочитал.

Мой товарищ, Virtual, вообще считает, что так и должно быть, а chat_id юзеров «A» и «B» Telegram получает сложением двух уникальных строк: user_id юзера «A» и user_id юзера «B». Полученный таким сложением идентификатор был бы уникальным (поскольку состоял бы из двух уникальных частей) и однозначно идентифицировал бы кто с кем чатится. Это было бы логично, хотя в доке, в любом случае, написано по-другому.

Ладно, хватит лирических отступлений, идём дальше (но если кто разберётся в чём тут подвох — отпишитесь в коментах или на форуме).

Нам нужно добавлять в БД данные о пользователях и читать данные о пользователях из БД.

Вместо того, чтобы для каждого юзера выяснять, есть ли он в БД, и далее добавлять его данные в базу, если его там нет (это очень долгий путь), просто будем каждый раз пытаться добавить его в базу (если он там уже есть — запрос просто не выполнится). Одновременно с попыткой добавления будем пытаться обновить данные о юзере, записанные в БД, и пытаться прочитать его статус (забанен или нет).

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

Для реализации всех вышеперечисленных идей дописываем в файле bot.php, перед строкой

// начинаем распарсивать полученное сообщение

следующий код:

Код под катом

$user_id = $input_array['message']['from']['id'];  // выделяем идентификатор юзера
$fname = $input_array['message']['chat']['first_name']; // выделяем имя собеседника
$lname = $input_array['message']['chat']['last_name'];  // выделяем фамилию собеседника
$uname = $input_array['message']['chat']['username'];   // выделяем ник собеседника
 
if(!$user_id){    // если в сообщении нет иденификатора юзера
    exit();       // завершаем работу скрипта
}
 
// пытаемся подключиться к БД
$mysqli = new mysqli($dbhost, $dbuser, $dbpwd, $dbname);
if ($mysqli->connect_errno) {	// если подключиться не получилось - сообщаем админу
    sendMessage($admin_chat_id,'Не удалось подключиться к БД ('.$mysqli->connect_errno.': '.$mysqli->connect_error.') для пользователя с user_id: '.$user_id);
    exit();
}
 
$sel_number = 4;    // номер интересующего нас запроса (нам интересен ответ на запрос SELECT status)
$sql = "";          // здесь формируем строку мультизапроса
// запрос 1 - выставляем кодировку
$sql .= 'SET NAMES utf8;';
// запрос 2 - добавляем юзера в базу
$sql .= 'INSERT IGNORE INTO MYTABLE (user_id, first_name) VALUES("'.$user_id.'", "'.$fname.'");';
// запрос 3 - обновляем данные юзера в базе
$sql .= 'UPDATE IGNORE MYTABLE SET first_name="'.$fname.'", last_name="'.$lname.'", nick_name="'.$uname.'" WHERE user_id="'.$user_id.'";';
// запрос 4 - запрос на поиск юзера в базе
$sql .= 'SELECT status FROM MYTABLE WHERE user_id="'.$user_id.'";';
 
if (!$mysqli->multi_query($sql)) {    // пытаемся выполнить мультизапрос
    sendMessage($admin_chat_id,'Не удалось выполнить мультизапрос ('.$mysqli->errno.': '.$mysqli->error.') для пользователя с user_id: '.$user_id);
    exit();    // подключение отвалится само при завершении скрипта
}
 
$status = NULL;    // вначале статус юзера нам неизвестен
$counter = 0;      // инициализируем счётчик обрабатываемых результатов
do {    $counter += 1;                      // увеличиваем счётчик
        $res = $mysqli->store_result();     // получаем результат i-того запроса
        if ($res){                          // если результат запроса не нулевой
            if($counter == $sel_number){    // если это запрос select status
                $row = $res->fetch_row();   // получаем первую строку ответа
                // (она у нас всего одна должна быть, мы же ищем по user_id, а он уникальный)
                if(isset($row[0])){         // если нулевой элемент этой строки существует
                // (значение status, поскольку мы запрашивали только его),
                    $status = $row[0];      // то статус юзера равен прочитанному
                }
            }
            $res->free();                   // если результат был не пустой, то освобождаем его
        }
} while ($mysqli->more_results() && $mysqli->next_result()); //перебираем все результаты мультизапроса
$mysqli->close();    // закрываем подключение к базе
 
// проверяем что у нас получилось
if(!isset($status)){ // если статус юзера не определён
    sendMessage($admin_chat_id,'Статус юзера с user_id = '.$user_id.' остался неопределёным.');
    exit();
}
elseif(!$status){    // если статус юзера равен нулю (юзер забанен)
    sendMessage($chat_id,'Для Вас заблокирована возможность посылать сообщения роботу @BOTNAME');
    exit();
}

[свернуть]

Теперь нам нужно добавить две новые команды: /ban и /unban. Это делается следующим образом:

  • Во-первых, научим нашего бота выделять адресата (user_id) для этих новых команд. Для этого заменяем в файле bot.php вот такую строку кода:
  • if(($chat_id == $admin_chat_id) && ($command === '/send') && ($user_text_length!=0)){

    на вот такую:

    if(($chat_id == $admin_chat_id) && (($command === '/send') || ($command === '/ban') || ($command === '/unban')) && ($user_text_length!=0)){
  • Во-вторых, внесём их в список, выдаваемый командами /start и /help. Для этого в том же файле заменяем строку
  • /send <i>chat_id</i> <i>message</i> - послать <i>message</i> в указанный чат');

    на вот такие:

    /send <i>chat_id</i> <i>message</i> - послать <i>message</i> в указанный чат
    /ban <i>user_id</i> - забанить пользователя с указанным user_id
    /unban <i>user_id</i> - разбанить пользователя с указанным user_id');
  • Ну и, наконец, нужно написать для новых команд обработчики. В файл bot.php, перед строкой
  • default:

    дописываем следующий код:

    Код под катом

    case('/ban'):
        if($chat_id == $admin_chat_id){             // если это команда от админа
            if($user_chat_id != $admin_chat_id){    // если админ не пытается забанить сам себя
                // пытаемся подключиться к БД
                $mysqli = new mysqli($dbhost, $dbuser, $dbpwd, $dbname);
                if ($telegram_mysqli->connect_errno) {
                    sendMessage($admin_chat_id,'Не удалось подключиться к БД ('.$mysqli->connect_errno.': '.$mysqli->connect_error.') для пользователя с user_id: '.$user_id);
                    exit();
                }
                // формируем запрос на обновление статуса указанного юзера в базе (меняем его на 0)
                $sql = 'UPDATE IGNORE MYTABLE SET status="0" WHERE user_id="'.$user_chat_id.'";';
                if (!$mysqli->multi_query($sql)) {  // пытаемся выполнить мультизапрос
                    sendMessage($admin_chat_id,'Не удалось добавить в бан пользователя c user_id = '.$user_chat_id);
                    exit();                         // подключение отвалится само при завершении скрипта
                }
                $mysqli->close();                   // закрываем подключение к базе
                sendMessage($admin_chat_id,'Запрос на добавление в бан пользователя c user_id = '.$user_chat_id.' выполнен');
            }
            else{                                   // если всё же админ пытается забанить сам себя
                sendMessage($admin_chat_id,'Никто не имеет права банить админа, даже сам админ!');
            }
        }
        else{
            sendMessage($chat_id,'неизвестная команда'); // если команда не от админа, то её как бы нет
        }
    break;
    case('/unban'):
        if($chat_id == $admin_chat_id){             // если это команда от админа
            // пытаемся подключиться к БД
            $mysqli = new mysqli($dbhost, $dbuser, $dbpwd, $dbname);
            if ($mysqli->connect_errno) {
                sendMessage($admin_chat_id,'Не удалось подключиться к БД ('.$mysqli->connect_errno.': '.$mysqli->connect_error.') для пользователя с user_id: '.$user_id);
                exit();
            }
            // формируем запрос на обновление статуса юзера в базе (меняем его на 1)
            $sql = 'UPDATE IGNORE MYTABLE SET status="1" WHERE user_id="'.$user_chat_id.'";';
            if (!$mysqli->multi_query($sql)) {      // пытаемся выполнить мультизапрос
                sendMessage($admin_chat_id,'Не удалось выполнить отмену бана пользователя c user_id = '.$user_chat_id);
                exit();
            }
            $mysqli->close();                       // закрываем подключение к базе
            sendMessage($admin_chat_id,'Запрос на отмену бана пользователя c user_id = '.$user_chat_id.' выполнен');
        }
        else{
            sendMessage($chat_id,'неизвестная команда'); // если команда не от админа, то её как бы нет
        }
    break;

    [свернуть]

    Загружаем к себе на сервер (в папку MYBOT) исправленные файлы bot.php и config.php и смотрим, что получилось. На этом этапе у нас уже всё должно работать. Остаётся, как обычно, хоть чуть-чуть озаботится вопросами безопасности.

    Поскольку мы добавили код, который позволяет вводить что-то, что в дальнейшем участвует в формировании SQL-запросов, то защищаться нам придётся от возможных SQL-инъекций. Соответственно, нам нужно добавить какую-то обработку вводимой информации. Самое простое, что мы можем в данном случае сделать, — это заменять спецсимволы на их html-коды (например, > на &gt; и так далее). Открываем файл bot.php и дописываем после строки

    $uname = $input_array['message']['chat']['username']; // выделяем ник собеседника

    следующий код:

    // обрабатываем принятое сообщение для защиты и удобства
    $message = trim($message);                         // удаляем пробелы
    $message = htmlspecialchars($message, ENT_QUOTES); // преобразуем спецсимволы (&, ", ', <, >) в html-сущности

    Снова перезаливаем исправленный файл bot.php на сервак и наслаждаемся результатом, — у нас есть организованный через бота Telegram-чат с регистрацией юзеров и возможностью из забанить или разбанить.

    Что ещё можно добавить? Да много чего. Например:

    • возможность существования нескольких админов, которым бот будет присылать сообщения от юзеров
    • возможность добавления/удаления админов
    • возможность админам включать режим «не беспокоить», для временной приостановки получения сообщений
    • возможность сохранения переписки

    Однако, всё это вы можете сделать самостоятельно, а я на этом закончу (и так уже слишком много букв). Лучше начну писать про long polling.

    1. Часть 1. Что такое Telegram боты и как они работают
    2. Часть 2. Регистрация аккаунтов Telegram ботов в картинках
    3. Часть 3. Пишем простого чат-бота для Telegram на чистом php (webhook)
    4. Часть 4. Прикручиваем MySQL к чат-боту для Telegram на php (webhook)
    5. Часть 5. Пишем Telegram бота на php для работы через longpolling
    6. Часть 6. Делаем пользовательскую клавиатуру для бота
    7. Дополнение. Как отправлять правильные https-запросы через curl и при чём здесь SSL-cертификаты

    Добавить комментарий