
Подробнее о хостинге для размещения Телеграм бота можно узнать из статьи
Продолжение: (Часть 1 и Часть 3, Часть 4) цикла статей по созданию чат-бот-магазина в Телеграм. В этой статье мы перейдем непосредственно к практической части. Забегая вперед сообщу, что исходный код рассматриваемого блока администрирования можно скачать в конце статьи.
Созданию окружения для работы в BOT API мы уделим особое внимание, считаю это одним из основных составляющих приложения.
Что я имею ввиду под словом "окружение": это методы запуска приложения, для работы с базой данных, метод-роутер и методы работы с BOT API.
Рассмотрим метод запуска приложения init()
- это метод, который запускает работу с базой данных и работу роутера.
<?php
//////////////////////////////////
// Запускаем магазин
//////////////////////////////////
/** Стартуем бота
* @return bool
*/
public function init()
{
// создаем соединение с базой данных
$this->setPdo();
// получаем данные от АПИ и преобразуем их в ассоциативный массив
$rawData = json_decode(file_get_contents('php://input'), true);
// направляем данные из бота в метод роутер
// для определения дальнейшего выбора действий
$this->router($rawData);
// в любом случае вернем true для бот апи
return true;
}
?>
В методе работы с базой данных, создадим объект PDO и запишим его в свойство $this->pdo
<?php
/**
* Создаем соединение с БД
*/
private function setPdo()
{
// задаем тип БД, хост, имя базы данных и чарсет
$dsn = "mysql:host=$this->host;dbname=$this->db;charset=$this->charset";
// дополнительные опции
$opt = [
// способ обработки ошибок - режим исключений
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
// тип получаемого результата по-умолчанию - ассоциативный массив
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
// отключаем эмуляцию подготовленных запросов
PDO::ATTR_EMULATE_PREPARES => false,
// определяем кодировку запросов
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci"
];
// записываем объект PDO в свойство $this->pdo
$this->pdo = new PDO($dsn, $this->user, $this->pass, $opt);
}
?>
Наш роутер определяет какой тип данных нам пришел от BOT API и направляет их по заданному нами направлению. В нем мы будем использовать дополнительно еще несколько инструментов для определения статуса пользователя и в случае если это администратор бота, то проверим на цель данных.
Для работы нашего приложения в блоке администрирования бот-магазина, мы будем обрабатывать 2 типа данных — это message
и callback_query
, остальные нам пока не нужны, поэтому будем выводить предупреждение.
В тип message
приходят объекты, отправленные пользователем: текст, картинки, видео, документы, аудиофайл, видеофайл и другие. Объект callback_query
приходит только при нажатии inline кнопки с опцией callback_data
. Мы составим условия таким образом, что принимать message
будем только текст и картинки, а callback_query
будем перенаправлять в необходимый метод и в нем уже обрабатывать запрос на действие.
При проверке на текстовое сообщение мы зададим несколько шаблонов в условиях и при их выполнении направим в нужные нам методы. Это стандартная команда для старта бота /start, для страницы админа /admin и еще несколько команд для отображения экранов для просмотра категорий, контактов, добавления категории.
Все остальные текстовые сообщения, которые пришли в бот, мы будем определять по действию, которое было записано во временную таблицу в бд и при условии, что пользователь является админом. Действия мы записываем в бд по мере вызова методов добавления категорий или товара в категории.
При запросе на добавление категории мы запишем действие в бд как addcategory, и следующее текстовое сообщение мы будем воспринимать как название категории. В случае если будет отправлен другой контент - картинка например, то по условиям будет выведена ошибка в отправленных данных, потому что картинку мы ждем только на 5 шаге добавления товара.
Обратите внимание как обрабатывается объект callback_query
. Мы смотрим какое значение data приходит в объекте, это всегда (в нашем приложении) строка, состоящая из значений разделенные знаком нижнего подчеркивания и первым значением идет название метода в который нужно передать данные, далее это необходимые параметры в основном это идентификаторы.
<?php
/** Роутер - Определяем что делать с запросом от АПИ
* @param $data
*/
private function router($data)
{
// берем технические данные id чата пользователя == его id и текст который пришел
$chat_id = $this->getChatId($data);
$text = $this->getText($data);
// если пришли данные message
if (array_key_exists("message", $data)) {
// дастаем действие админа из базы
$action = $this->getAdminAction();
// текстовые данные
if (array_key_exists("text", $data['message'])) {
// если это пришел старт бота
if ($text == "/start") {
$this->startBot($chat_id);
} elseif ($text == "/admin" && $this->isAdmin($chat_id)) {
// выводим страницу только админу
$this->adminPage();
} elseif ($text == "/admincategory" && $this->isAdmin($chat_id)) {
// Страница админ категорий
$this->adminCategory();
} elseif ($text == "/addcategory" && $this->isAdmin($chat_id)) {
// отправляем на добавление категории
$this->addCategory();
} elseif ($text == "/admincontact" && $this->isAdmin($chat_id)) {
// просмотр контактов
$this->adminContact();
} else {
// смотрим куда отправить данные
if ($action == "addcategory" && $this->isAdmin($chat_id)) {
// если ждем данные для добавления категории
$this->adderCategory($text);
} elseif (preg_match("~^addproduct_1_~", $action) && $this->isAdmin($chat_id)) {
// если ждем данные для добавления товара step_1 - название
$param = explode("_", $action);
// отправляем на добавление описания
$this->addProductName($param['2'], $text);
} elseif (preg_match("~^addproduct_2_~", $action) && $this->isAdmin($chat_id)) {
// если ждем данные для добавления товара step_2 - описание
$param = explode("_", $action);
// отправляем на добавление описания
$this->addProductDescription($param['2'], $param['3'], $text);
} elseif (preg_match("~^addproduct_3_~", $action) && $this->isAdmin($chat_id)) {
// если ждем данные для добавления товара step_3 - единица измерения
$param = explode("_", $action);
// отправляем на добавление описания
$this->addProductPrice($param['2'], $param['3'], $text);
} elseif (preg_match("~^addproduct_4_~", $action) && $this->isAdmin($chat_id)) {
// если ждем данные для добавления товара step_4 - цена
$param = explode("_", $action);
// отправляем на добавление описания
$this->addProductUnit($param['2'], $param['3'], $text);
} elseif (preg_match("~^addcontact_~", $action) && $this->isAdmin($chat_id)) {
// если ждем данные для для редактирования контактов
$param = explode("_", $action);
// отправляем данные на редактирование контактов
$this->rederContact($param[1], $text);
} else {
$this->sendMessage($chat_id, "Нам пока не нужны эти данные. Спасибо.");
}
}
} elseif (array_key_exists("photo", $data['message'])) {
// если пришли картинки
if (preg_match("~^addproduct_5_~", $action) && $this->isAdmin($chat_id)) {
// если ждем данные для добавления товара step_5 - картинка
$param = explode("_", $action);
// берем данные картинки
$file_id = end($data['message']['photo'])['file_id'];
// отправляем на добавление описания
$this->addProductPhoto($param['2'], $param['3'], $file_id);
} else {
$this->sendMessage($chat_id, "Нам пока не нужны эти данные. Спасибо.");
}
} else {
$this->sendMessage($chat_id, "Нам пока не нужны эти данные. Спасибо.");
}
} // если пришел запрос на функцию обратного вызова
elseif (array_key_exists("callback_query", $data)) {
// смотрим какая функция вызывается
$func_param = explode("_", $text);
// определяем функцию в переменную
$func = $func_param[0];
// вызываем функцию передаем ей весь объект
$this->$func($data['callback_query']);
} // Здесь пришли пока не нужные нам форматы
else {
// вернем текст с ошибкой
$this->sendMessage($chat_id, "Нам пока не нужны эти данные. Спасибо.");
}
}
?>
Для работы с действиями администратора есть несколько методов для выборки, для записи и для очистки.
<?php
/** Отменяем все действия админа
* @return mixed
*/
private function adminActionCancel()
{
// возвращаем результат запроса
return $this->pdo->query("DELETE FROM bot_shop_action_admin");
}
/** Получаем действие админа из таблицы
* @return bool
*/
private function getAdminAction()
{
// достаем из базы
$last = $this->pdo->query("SELECT name FROM bot_shop_action_admin ORDER BY id DESC LIMIT 1");
// преобразуем строку в массив
$lastAction = $last->fetch();
// если есть значение то возвращаем его иначе false
return isset($lastAction['name']) ? $lastAction['name'] : false;
}
/** Записываем действие админа
* @param $action
* @return mixed
*/
private function setActionAdmin($action)
{
// отменяем все действия админа
if ($this->adminActionCancel()) {
// готовим запрос
$insertSql = $this->pdo->prepare("INSERT INTO bot_shop_action_admin SET name = :name");
// возвращаем результат
return $insertSql->execute(['name' => $action]);
} else {
// выводим ошибку
$this->sendMessage($this->admin, "Ошибка отмены предыдущих действий.");
}
}
?>
В случае с работой по наполнению категории товаром, действие пишем с дополнительными параметрами напрмиер addproduct_3_2_1, где:
Таким образом будет удобно работать, мы понимаем, какое действие идет, на каком уже шаге, в какой категории мы работаем и с каким объектом товара.
Как и предполагалось в структуре базы данных прошли изменения. Были добавлены 2 таблицы для записи ожидаемых действий администратора при работе по наполнению товаров в бот-магазине и таблица временных данных по товару при добавлении его в категорию. Добавление товара происходит в 5 этапов (шагов):
После успешной обработки текстового сообщения на шаге 1, идет запись во временную таблицу товара и в таблицу действий, о том, что уже шаг 2 и так далее до шага 5 при загрузке картинки. Когда приходит картинка то мы ее загружаем на свой сервер и в случае успеха делаем запись в основную таблицу товаров переписываем в нее все что было добавлено на всех шагах добавления товара далее удаляем из временной таблицы товаров запись и отменяем все действия админа в таблице действий, по итогу выводим на просмотр категорию.
Еще изменил кодировку таблиц для работы с емодзи. Теперь она utf8mb4_unicode_ci
.
--
-- Структура таблицы `bot_shop_action_admin`
--
CREATE TABLE IF NOT EXISTS `bot_shop_action_admin` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- --------------------------------------------------------
--
-- Структура таблицы `bot_shop_basket`
--
CREATE TABLE IF NOT EXISTS `bot_shop_basket` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`product_id` int(11) DEFAULT NULL,
`product_count` int(11) DEFAULT NULL,
`user_id` bigint(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- --------------------------------------------------------
--
-- Структура таблицы `bot_shop_category`
--
CREATE TABLE IF NOT EXISTS `bot_shop_category` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`hide` int(1) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- --------------------------------------------------------
--
-- Структура таблицы `bot_shop_contact`
--
CREATE TABLE IF NOT EXISTS `bot_shop_contact` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`description` text COLLATE utf8mb4_unicode_ci,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
--
-- Дамп данных таблицы `bot_shop_contact`
--
INSERT INTO `bot_shop_contact` (`id`, `description`) VALUES
(1, 'Контакт');
-- --------------------------------------------------------
--
-- Структура таблицы `bot_shop_product`
--
CREATE TABLE IF NOT EXISTS `bot_shop_product` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`parent` int(11) DEFAULT NULL,
`name` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`description` text COLLATE utf8mb4_unicode_ci,
`image_tlg` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`image` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`price` decimal(10,2) DEFAULT NULL,
`unit` varchar(10) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`hide` int(1) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- --------------------------------------------------------
--
-- Структура таблицы `bot_shop_product_temp`
--
CREATE TABLE IF NOT EXISTS `bot_shop_product_temp` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`parent` int(11) DEFAULT NULL,
`name` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`description` text COLLATE utf8mb4_unicode_ci,
`image_tlg` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`image` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`price` decimal(10,2) DEFAULT NULL,
`unit` varchar(10) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- --------------------------------------------------------
--
-- Структура таблицы `bot_shop_profile`
--
CREATE TABLE IF NOT EXISTS `bot_shop_profile` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` bigint(20) DEFAULT NULL,
`first_name` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`last_name` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`phone` varchar(15) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`adress` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`action` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
Несколько скринов интерфейса.
Весь код в статье расписывать не буду, просто получилось 1300 строк из которых 500 это комментарии, старался максимально подробно комментрировать, чтобы было понятно. Реализованные возможности приложения по результату второй части:
Исходный код текущей версии бот-магазина можно скачать. В следующей статье мы будем рассматривать интерфейс пользовательской части магазина: редактирование профиля, просмотр каталога, добавление товара в корзину, управление корзиной.
Авторизуйтесь через Telegram, чтобы оставить комментарий.
Откройте бот @SiteAuthBot,
нажмите кнопку Старт/Start. Следуйте инструкциям бота.