© 2008-2024 г.
Все права защищены
Алексей Костюк

Готовый код бота Telegram на PHP для принятия заявок в техподдержку

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

Содержание поста

Требования к боту

  1. Показ приветственного сообщения.
  2. Выбор адреса автомата кнопками.
  3. Загрузка фото с описанием проблемы.
  4. Проверки, чтобы пользователь не смог отправить пустое сообщение или без фото или без текста.
  5. Показ сообщения о принятии заявки.
  6. Отправка сообщения в Телеграм канал техподдержки после отправки фото с текстом.
  7. Сохранять на сервере загруженные картинки и обращения пользователей.

Окружение

Нам нужен будет домен, можно использовать любой поддомен, защищенный сертификат SSL на домен и немного места на вашем хостинге, который бы поддерживал PHP 8.

Выбор библиотеки для создания бота

Развертывание окружения под бота на хостинге

Я у себя создал поддомен, подключил к нему бесплатный сертификат Lets Encrypt, и теперь можно загружать наш пакет библиотеки Telegram bot SDK по FTP.

Вы можете просто распаковать архив библиотеки Telegram Bot SDK for PHP на вашем компьютере и загрузить содержимое по FTP, например через FileZilla.

Как вы видите я создал еще папки image для загрузки фото, которые будут сюда попадать после отправки сообщения и папку logs для записи всех действий с ботом. Также я создал файл bot.php, который будет содержать код нашего бота.

Установка библиотеки Telegram Bot SDK for PHP

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

Устанавливается composer через терминал.

Перейдите в каталог с нашим сайтом:

cd bot.az8.ru

После этого загрузим файл установки:

curl https://getcomposer.org/installer -o composer-setup.php

Далее необходимо запустить его и передать параметр –install-dir. В этом параметре надо будет определить каталог, в который будет загружен phar-архив. Мы установим его в каталог bin

php composer-setup.php --install-dir=bin

Вывод этих двух команд будет примерно следующим:

All settings correct for using Composer
Downloading...

Composer (version 1.9.0) successfully installed to: /home/c0000/bot.az8.ru/bin/composer.phar
Use it: php ./bin/composer.phar

Далее запускаем Composer с помощью следующей команды:

bin/php bin/composer.phar

Проверяем правильно ли мы все сделали. Должна выводиться версия composer.

composer --version

Устанавливаем Telegram Bot SDK for PHP вводя в терминале

composer require irazasyed/telegram-bot-sdk

На выходе мы должны получить папку vendor

Обратите внимание, что у меня библиотека установлена выше каталога www где будет располагаться сам бот. Вы можете установить библиотеку куда угодно и потом в файле бота прописать путь до местонахождения папки vendor.

Теперь нам осталось зарегистрировать бота в Telegram получив API ключ, и через вебхук, также посредством терминала подключить его к нашему домену.

Регистрация бота в Telegram

1. Нужно перейти по ссылке - https://t.me/BotFather и выбрать команду /newbot

2. Выберите имя и username для бота и получите API ключ

3. Создать канал в Telegram.

Думаю не стоит заострять внимание на создание канала. Это довольно просто.

Теперь все готово для создания бота. По итогу у нас есть установленная библиотека Telegram SDK for PHP, полученный API ключ, создан канал в Telegram.

Полный код бота

<?php

require __DIR__ . '/../vendor/autoload.php'; // Подключаем autoload файл из Composer

use Telegram\Bot\Api;
use Telegram\Bot\Keyboard\Keyboard;

function logMessage($message) {
    $logFile = __DIR__ . '/logs/bot.log';
    $currentTime = date('Y-m-d H:i:s');
    if (!file_exists(dirname($logFile))) {
        mkdir(dirname($logFile), 0777, true);
    }
    file_put_contents($logFile, "[$currentTime] $message\n", FILE_APPEND);
}

function downloadFile($url, $saveTo) {
    $ch = curl_init($url);
    $fp = fopen($saveTo, 'wb');

    if ($fp === false) {
        logMessage("Failed to open file: $saveTo");
        return false;
    }

    curl_setopt($ch, CURLOPT_FILE, $fp);
    curl_setopt($ch, CURLOPT_HEADER, 0);

    curl_exec($ch);

    if (curl_errno($ch)) {
        logMessage('cURL error: ' . curl_error($ch));
    }

    curl_close($ch);
    fclose($fp);
    return true;
}

try {
    // Создаем экземпляр API
    $telegram = new Api('Сюда вставляем ваш API ключ');
    $channelId = '@Ваштелеграмканал';

    // Получаем обновления
    $updates = $telegram->getWebhookUpdates();
    logMessage("Received update: " . json_encode($updates));

    if (isset($updates['message'])) {
        $message = $updates['message'];
        $chatId = $message['chat']['id'];
        logMessage("Message from chat ID $chatId");

        if (isset($message['text'])) {
            $text = $message['text'];
            logMessage("Received text message: $text");

            if ($text == '/start') {
                // Приветственное сообщение и кнопки выбора адреса
                $keyboard = Keyboard::make()
                    ->inline()
                    ->row([
                        Keyboard::inlineButton(['text' => 'Кофемат №1', 'callback_data' => 'Вайнера 55']),
                        Keyboard::inlineButton(['text' => 'Кофемат №2', 'callback_data' => 'Соболева 5'])
                    ])
                    ->row([
                        Keyboard::inlineButton(['text' => 'Кофемат №3', 'callback_data' => 'Успенский 151']),
                        Keyboard::inlineButton(['text' => 'Кофемат №4', 'callback_data' => 'Ленина 83'])
                    ])
                    ->row([
                        Keyboard::inlineButton(['text' => 'Кофемат №5', 'callback_data' => 'Монтажников 26']),
                    ]);

                $telegram->sendMessage([
                    'chat_id' => $chatId,
                    'text' => 'Это техническая поддержка. Мы поможем с проблемой кофемата. Выберите адрес вашего автомата, далее загрузите фото чека с описанием проблемы и укажите ваш номер телефона для связи.',
                    'reply_markup' => $keyboard
                ]);
                logMessage("Sent start message with keyboard");
            } else {
                // Получаем сохраненный адрес кофемата
                $sessionFile = __DIR__ . "/sessions/$chatId.txt";
                if (!file_exists($sessionFile)) {
                    $telegram->sendMessage([
                        'chat_id' => $chatId,
                        'text' => 'Пожалуйста, выберите ваш кофемат перед отправкой сообщения. Без этого мы не сможем решить вашу проблему'
                    ]);
                    logMessage("Reminded user to select a coffee machine");
                } else {
                    $address = file_get_contents($sessionFile);

                    // Сообщение пользователю о необходимости отправки фото
                    $telegram->sendMessage([
                        'chat_id' => $chatId,
                        'text' => 'Пожалуйста, отправьте фото чека с описанием проблемы и укажите ваш номер телефона. Без этого мы не сможем решить вашу проблему'
                    ]);
                    logMessage("Sent reminder to user to send photo");
                }
            }
        } elseif (isset($message['photo'])) {
            // Логика обработки загруженного фото
            $caption = $message['caption'] ?? '';
            $fileId = end($message['photo'])['file_id'];
            logMessage("Received photo with caption: $caption");

            // Проверка наличия текста
            if (empty($caption)) {
                $telegram->sendMessage([
                    'chat_id' => $chatId,
                    'text' => 'Пожалуйста, отправьте еще раз фото чека с текстом описания проблемы и укажите ваш номер телефона для связи.'
                ]);
                logMessage("Reminded user to send photo with caption");
            } else {
                // Получение файла
                $file = $telegram->getFile(['file_id' => $fileId]);
                $filePath = $file->getFilePath();
                $fileUrl = "https://api.telegram.org/file/botВставляем ваш ключ API/{$filePath}";

                // Создание директории, если не существует
                $imageDir = __DIR__ . '/image/';
                if (!file_exists($imageDir)) {
                    mkdir($imageDir, 0777, true);
                }

                // Скачивание файла через cURL
                $localFilePath = $imageDir . basename($filePath);
                if (downloadFile($fileUrl, $localFilePath)) {
                    logMessage("Saved photo to $localFilePath");

                    // Получаем сохраненный адрес кофемата
                    $sessionFile = __DIR__ . "/sessions/$chatId.txt";
                    if (!file_exists($sessionFile)) {
                        $telegram->sendMessage([
                            'chat_id' => $chatId,
                            'text' => 'Пожалуйста, выберите ваш кофемат перед отправкой сообщения. Без этого мы не сможем решить вашу проблему'
                        ]);
                        logMessage("Reminded user to select a coffee machine");
                    } else {
                        $address = file_get_contents($sessionFile);

                        // Отправка информации в канал
                        $telegram->sendPhoto([
                            'chat_id' => $channelId,
                            'photo' => $fileId,
                            'caption' => "Адрес кофемата: $address\nОписание проблемы:\n$caption"
                        ]);
                        logMessage("Forwarded photo to channel");

                        // Сообщение пользователю об успешной загрузке
                        $telegram->sendMessage([
                            'chat_id' => $chatId,
                            'text' => 'Спасибо! Ваша заявка принята.'
                        ]);
                        logMessage("Sent confirmation message to user");
                    }
                } else {
                    logMessage("Failed to save photo to $localFilePath");
                }
            }
        }
    } elseif (isset($updates['callback_query'])) {
        $callbackQuery = $updates['callback_query'];
        $chatId = $callbackQuery['message']['chat']['id'];
        $data = $callbackQuery['data'];
        logMessage("Received callback query: $data from chat ID $chatId");

        // Обработка выбора адреса
        $telegram->sendMessage([
            'chat_id' => $chatId,
            'text' => 'Вам необходимо загрузить фото чека и описать проблему, указав номер телефона для связи. Без этого мы не сможем решить вашу проблему!'
        ]);
        logMessage("Sent address selection message to user");

        // Сохраняем адрес кофемата в сессии пользователя
        $sessionDir = __DIR__ . '/sessions/';
        if (!file_exists($sessionDir)) {
            mkdir($sessionDir, 0777, true);
        }
        file_put_contents($sessionDir . "$chatId.txt", $data);
    }
} catch (Exception $e) {
    logMessage("Error: " . $e->getMessage());
}
?>

Теперь разберем код, что за что отвечает, чтобы могли скорректировать его под свои задачи.

Подключение библиотеки /../vendor/autoload.php.

У меня папка vendor находиться выше каталога, где находиться файл bot.php. Если у вас по другому, то вам необходимо скорректировать путь до папки.

require __DIR__ . '/../vendor/autoload.php'; // Подключаем autoload файл из Composer

use Telegram\Bot\Api;
use Telegram\Bot\Keyboard\Keyboard;

Указываем API ключ в двух местах

 $telegram = new Api('Сюда вставляем ваш API ключ');
 $fileUrl = "https://api.telegram.org/file/botВставляем ваш ключ API/{$filePath}";

Во второй строке обратите внимание, что ключ вставляется после слова bot.

Создание функции, которая будет записывать все ошибки и данные при взаимодействии пользователя ботом в файл bot.log в папке logs.

function logMessage($message) {
    $logFile = __DIR__ . '/logs/bot.log';
    $currentTime = date('Y-m-d H:i:s');
    if (!file_exists(dirname($logFile))) {
        mkdir(dirname($logFile), 0777, true);
    }
    file_put_contents($logFile, "[$currentTime] $message\n", FILE_APPEND);
}

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

function downloadFile($url, $saveTo) {
    $ch = curl_init($url);
    $fp = fopen($saveTo, 'wb');

    if ($fp === false) {
        logMessage("Failed to open file: $saveTo");
        return false;
    }

    curl_setopt($ch, CURLOPT_FILE, $fp);
    curl_setopt($ch, CURLOPT_HEADER, 0);

    curl_exec($ch);

    if (curl_errno($ch)) {
        logMessage('cURL error: ' . curl_error($ch));
    }

    curl_close($ch);
    fclose($fp);
    return true;
}

Указываем название Телеграм канала.

$channelId = '@Ваштелеграмканал';

Название канала указывается в виде @canal.

Создание кнопок на клавиатуре.

У меня 5 кофематов и пользователю нужно дать выбрать, тот автомат с которым проблемы, а техподдержке должен приходить фактический адрес автомата, который выбрал пользователь

 $keyboard = Keyboard::make()
                    ->inline()
                    ->row([
                        Keyboard::inlineButton(['text' => 'Кофемат №1', 'callback_data' => 'Текст, которые передается в сообщение в ваш канал']),
                        Keyboard::inlineButton(['text' => 'Кофемат №2', 'callback_data' => 'Текст, которые передается в сообщение в ваш канал'])
                    ])
                    ->row([
                        Keyboard::inlineButton(['text' => 'Кофемат №3', 'callback_data' => 'Текст, которые передается в сообщение в ваш канал']),
                        Keyboard::inlineButton(['text' => 'Кофемат №4', 'callback_data' => 'Текст, которые передается в сообщение в ваш канал'])
                    ])
                    ->row([
                        Keyboard::inlineButton(['text' => 'Кофемат №5', 'callback_data' => 'Текст, которые передается в сообщение в ваш канал']),
                    ]);

->row создает колонки и в данном коде у нас получиться 2 колонки с 2мя кнопками

->row([
 Keyboard::inlineButton(['text' => 'Кофемат №1', 'callback_data' => 'Текст, которые передается в сообщение в ваш канал']),
 Keyboard::inlineButton(['text' => 'Кофемат №2', 'callback_data' => 'Текст, которые передается в сообщение в ваш канал'])
])

Как выглядят кнопки?

Вместо названий моих кнопок вы можете поменять на свои.'callback_data' => 'Текст, которые передается в сообщение в ваш канал' Текст сообщения замените на свой.

Приветственное сообщение при нажатии на кнопку /start

Выше на скриншоте мы видим сообщение. Оно меняется в этом коде:

$telegram->sendMessage([
  'chat_id' => $chatId,
  'text' => 'Это техническая поддержка. Мы поможем с проблемой кофемата. Выберите адрес вашего автомата, далее загрузите фото чека с описанием проблемы и укажите ваш номер телефона для связи.',
  'reply_markup' => $keyboard
]);

Проверки

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

Код проверок выбора кнопок:

 if (!file_exists($sessionFile)) {
   $telegram->sendMessage([
   'chat_id' => $chatId,
   'text' => 'Пожалуйста, выберите ваш кофемат перед отправкой сообщения. Без этого мы не сможем решить вашу проблему'
  ]);
  logMessage("Reminded user to select a coffee machine");
}

После выбора кнопок пользователь должен загрузить фото с описанием проблемы и если он отправляет простое сообщение, то показываем текст:

$telegram->sendMessage([
 'chat_id' => $chatId,
 'text' => 'Пожалуйста, отправьте фото чека с описанием проблемы и укажите ваш номер телефона. Без этого мы не сможем решить вашу проблему'
]);
logMessage("Sent reminder to user to send photo");

Если пользователь загружает фото, но добавляет текст к нему, то показываем ему текст:

 if (empty($caption)) {
   $telegram->sendMessage([
      'chat_id' => $chatId,
      'text' => 'Пожалуйста, отправьте еще раз фото чека с текстом описания проблемы и укажите ваш номер телефона для связи.'
  ]);
  logMessage("Reminded user to send photo with caption");
}

Отправляем сообщение в канал техподдержки

Теперь, когда пользователь прошел все проверки и сделал все как надо отправляем сообщение в наш канал. Текст "Адрес кофемата" вы можете изменить на свой вариант:

$telegram->sendPhoto([
 'chat_id' => $channelId,
 'photo' => $fileId,
 'caption' => "Адрес кофемата: $address\nОписание проблемы:\n$caption"
]);

Демонстрации работы бота

Как видим бот обработал успешно все ошибки и не дал пользователю отправить простое сообщение и без выбора кнопок и загрузки фото.

Вот и все! Успехов вам в вашем проекте!

Привет Username! Я Алексей Костюк
14 лет занимаюсь тем, что помогаю людям создавать сайты и дизайн, настраивать рекламу, продвигать проекты в Интернете. Я создал этот блог, чтобы делиться своим опытом и мыслями, а если вы хотите, то можете меня поддержать, это поможет мне создавать больше полезных статей для вас.