Наш канал в 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

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