Продолжение: (Часть 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
приходит только при нажатии inline кнопки с опцией callback_query
. Мы составим условия таким образом, что принимать 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
, где:
- addproduct - название действия
- 3 - шаг действия - №3 у нас это определено как добавление единицы измерения
- 2 - id категории
- 1 - id товара в базе
Таким образом будет удобно работать, мы понимаем, какое действие идет, на каком уже шаге, в какой категории мы работаем и с каким объектом товара.
* * *
Структура таблиц в бд и этапы добавления товара
Как и предполагалось в структуре базы данных прошли изменения. Были добавлены 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 это комментарии, старался максимально подробно комментировать, чтобы было понятно. Реализованные возможности приложения по результату второй части:
- Определение ролей: администратор и пользователь
- Добавление, определение видимости в каталоге и удаление категорий.
- Добавление, определение видимости в категории и удаление товара в категории.
- Редактирование данных контактов бот-магазина.
Исходный код текущей версии бот-магазина можно скачать. В следующей статье мы будем рассматривать интерфейс пользовательской части магазина: редактирование профиля, просмотр каталога, добавление товара в корзину, управление корзиной.
Откройте по ссылке или QR бот @iMakeBot, нажмите кнопку Старт/Start.
Следуйте инструкциям бота.
Как выключить админку? Чтобы только пользовательский интерфейс был.
Просто не указывайте в настройках id админа -> поставьте 0, и не будет админки
Как можно поженить Гугл шер, сайт поставщика, и бота для автоматической отправки заказа поставщику, запрос статуса заказа у поставщика, запрос прайса с картинками и актуальным остатком товара на складе, и тд.
Это можно сделать используя их API
Добрый день. Не могу понять как добавить администратора. Подскажите пожалуйста
нужна небольшая помощь - перестало работать обновление сообщения. отправка - норм. куда прислать код? какие условия помощи?
Может временный глюк на стороне телеграмм?
Вы можете мне написать в телегу, помогу. Мой логин - @dvd_teach
Хорошие статьи. Автор молодец! Для улучшения можно добавить оповещение, что мол бот "пишет", "идёт отправка изображения" и так далее. Ну это уже на вкус и цвет.
Добрый вечер
Подскажи а как вывести кнопки Назад.. Вперед.. по товарам.
Если в базе данных свыше 1000 наименований?
LIMIT указал - для вывода...небольшого кол-ва.
LIMIT может иметь несколько параметров, вы указали один (LIMIT 10) - поэтому вам покажет от 0 до 10.
Если вы укажете LIMIT 10, 10 - то вам покажет 10 результатов начиная с 11-го
А под категории сложно будет реализовать ?
Вы можете делать все, что вам нужно, дорабатывайте на свое усмотрение. Уровень сложности зависит от вашего уровня готовности и знаний