Привожу простой пример инфо-бота с практически неограниченной вложенностью. Вывод на экран текстового и медиа (картинка, видео, аудио, документ) сообщения. Многоуровневое меню включено.
Хотел привести пример как отобразить в Телеграм многоуровневое меню, но получился простой в создании информационный бот с вложенностью, ограниченной только возможной длинной значения в параметре callback_data
inline-кнопки (1-64 bytes).
Еще интересный момент это — в одном боте можно создать неограниченное количество сценариев, для этого достаточно просто стартовать бота по html-ссылке со специальными параметрами. При обычном старте бота — запускается первый сценарий. Об этом чуть ниже.
Настройки
В настройках бота нужно указать токен бота, id админа бота и заполнить массив с информацией о «шагах».
steps
|__
|__
|__
|__
|__
|__
|__
|__
|__
Каждый шаг имеет простой набор параметров:
[
"name" => "", // string
"line" => 0, // int
"type" => "text|photo|video|audio|document", // string
"text" => "", // string
"media" => "" // string | null,
"steps" => [] // array
]
- name* — Название шага, будет отображено на кнопке
- line* — уровень ряда в наборе кнопок
- type*- тип сообщения (text|photo|video|audio|document)
- text — текстовое сообщение, обязательно для
type="text"
- media — ссылка или file_id медиа файла, при
type="text"
должно быть значениеnull
- steps — это массив вложенных шагов,
—
* — обязательный параметр
Для ускорения отрисовки экранов желательно в параметре media
указывать file_id файла, он для каждого бота уникальный, поэтому из моего примера медиа у вас не будут подгружаться — их надо будет заменить.
Чтобы вам получить file_id
я добавил небольшой функционал (только если указана настройка bot_admin), нужно просто отправить в бот файл: документ, картинку, видео или аудио файлы.
В ответ бот пришлет строку — она же и будет file_id
, просто скопируйте ее и подставьте в параметр media
в нужном шаге (массива $steps).
* * *
Что из интересного?
Весь контент бота упакован в массив steps он же $content
. Важным моментом является конечно же валидность массива.
При старте бота настроен выбор сценария под индексом 0 массива (можно указать любой).
/**
1 параметр это индекс элемента массива
2 параметр это индексы родительских элементов массива steps, разделены тире (-), если null то выводим из верхнего уровня вложенности
3 параметр это id чата пользователя
*/
$printUpdate(0, null, $chat_id);
Если старт по HTML-ссылке то сценарий будет выбран из параметров ссылки
tg://resolve?domain=iMakeBot&start=s_3_0-1
где,
domain — это username вашего бота
start — это значение для выбора сценария
Параметр start имеет 3 вложенных параметра разделенных знаком нижнего подчеркивания (_
), где
1 подпараметр — это action, он всегда будет s
2 подпараметр — это индекс элемента массива steps
3 подпараметр — это индексы родительских элементов массива steps, разделены тире (-
)
3 подпараметр может быть пустым, то есть можно передать только первые 2 подпараметра (s_0 или s_3)
Если разбирать приведенный в ссылке пример параметра start (s_3_0-1) то это будет означать, что на экран по ссылке выведется элемент массива steps
$content['steps'][0]['steps'][1]['steps'][3];
// в раскрытом виде
$content = [
'steps' => [
[
'name' => 'Название элемента 0',
// ...
'steps' => [
[
'name' => 'Название элемента 0-0'
// ...
],
[
'name' => 'Название элемента 0-1',
// ...
'steps' => [
[
'name' => 'Название элемента 0-1-0'
// ...
],
[
'name' => 'Название элемента 0-1-1'
// ...
],
[
'name' => 'Название элемента 0-1-2'
// ...
],
[
'name' => 'Название элемента 0-1-3'
// ... Вот этот элемент будет отработан для вывода на экран
],
]
]
]
]
]
]
Чтобы получить из массива нужный элемент, пропускаем запрос через рекурсивную (самовызывающуюся) функцию
/** Получаем контент
* @param $step_idx
* @param $parents
* @param $data
* @return array
*/
$getContent = function ($step_idx, $parents, $data) use (&$getContent) {
// определим результат по умолчанию
$result = null;
// проверим родительские элементы
if (!is_null($parents)) {
// получим первого по списку родителя
$parent = array_shift($parents);
// проверим наличие родителя в массиве
if (isset($data['steps'][$parent])) {
// проверим путь - если еще остались в списке
if (count($parents)) {
// отправим на рекурсию - подставив новые параметры
$result = $getContent($step_idx, $parents, $data['steps'][$parent]);
} else {
// определим результат
$result = $data['steps'][$parent]['steps'][$step_idx];
}
}
} else {
// определим результат
$result = $data["steps"][$step_idx];
}
// вернем результат
return $result;
};
* * *
Отрисовка экрана
/** Выводим сообщение по запросу
* @param $step_idx
* @param $parents
* @param $chat_id
* @param null $cbq_id
* @param null $message_id
*/
$printUpdate = function ($step_idx, $parents, $chat_id, $cbq_id = null, $message_id = null) use ($getContent, $query, $notice, $content) {
// переопределим вложенность
$parents = !is_null($parents) ? explode("-", $parents) : null;
// получаем шаг
$step = $getContent($step_idx, $parents, $content);
// проверим
if (!is_null($step)) {
// готовим данные
$data = [
"chat_id" => $chat_id,
];
// если это нажатие по кнопке то удалим текущее сообщение
if (!is_null($cbq_id)) {
// гасим запрос
$notice($cbq_id);
// удаляем текущее сообщение
$query("deleteMessage", array_merge($data, ["message_id" => $message_id]));
}
// дополним данные
$data["parse_mode"] = "html";
// определим кнопки если они есть
$buttons = [];
// проверим
if (count($step['steps'])) {
// определим путь
$parents_ = !is_null($parents) ? implode("-", array_merge($parents, [$step_idx])) : $step_idx;
// переберем
foreach ($step['steps'] as $key => $next) {
// добавим кнопку
$buttons[$next['line']][] = [
"text" => $next['name'],
"callback_data" => "s_" . $key . "_" . $parents_
];
}
}
// кнопка вернуться
if (!is_null($parents)) {
// получим первого
$parent = array_pop($parents);
// добавим кнопку последним рядом
$buttons[count($buttons)][] = [
"text" => "Вернуться",
"callback_data" => "s_" . $parent . "_" . implode("-", $parents)
];
}
// проверим добавление кнопок
if (count($buttons)) {
// добавим кнопки
$data["reply_markup"] = json_encode(['inline_keyboard' => array_values($buttons)]);
}
// поддерживаемые типы
if (!is_null($step['media']) && in_array($step['type'], ['photo', 'video', 'audio', 'document'])) {
// проверим описание
if (!empty($step['text'])) {
// добавим описание
$data['caption'] = $step['text'];
}
// добавим медиа
$data[$step['type']] = $step['media'];
// отправим сообщение
$query("send" . ucfirst($step['type']), $data);
} elseif ($step['type'] === "text" && !empty($step['text'])) {
// добавим текст
$data['text'] = $step['text'];
// отправим сообщение
$query("sendMessage", $data);
} else {
// выведем ошибку о не поддерживаемом методе
$query("sendMessage", array_merge($data, ["text" => "Sorry, error 405"]));
}
} else {
// проверим на нажатие кнопки
if (!is_null($cbq_id)) {
// выведем уведомление
$notice($cbq_id, "Error 404 STEP");
}
}
};
* * *
Простой роутер для бота
/**
* Простой роутер бота
*/
if (isset($data->message)) {
// получим id чата
$chat_id = $data->message->from->id;
// если это текстовое сообщение
if (isset($data->message->text)) {
// проверим что это старт бота
if ($data->message->text == "/start") {
// выводим сообщение
$printUpdate(0, null, $chat_id);
}
// если это старт по ссылке
elseif (preg_match("~\/start s_([\d]+)_?([\d-]*)~", $data->message->text, $matches)) {
// выведем сообщение по ссылке
$printUpdate($matches[1], $matches[2], $chat_id);
}
}
// другие типы сообщений
else {
// если это админ бота направляет сообщение
if ($chat_id === $bot_admin) {
// по умолчанию
$file_id = null;
// если это картинка
if (isset($data->message->photo)) {
// file_id последней картикни
$file_id = end($data->message->photo)->file_id;
}
// если это видео-файл
elseif (isset($data->message->video)) {
// file_id видео-файла
$file_id = $data->message->video->file_id;
}
// если это аудио-файл
elseif (isset($data->message->audio)) {
// file_id аудио-файла
$file_id = $data->message->audio->file_id;
}
// если это документ
elseif (isset($data->message->document)) {
// file_id документа
$file_id = $data->message->document->file_id;
}
// проверим необходимость отправки
if (!is_null($file_id)) {
// отправим file_id
$query("sendMessage", [
"chat_id" => $chat_id,
"text" => $file_id
]);
}
}
}
// если это нажатие по кнопке
} elseif (isset($data->callback_query)) {
// получим id чата
$chat_id = $data->callback_query->from->id;
// получим callBackQuery_id
$cbq_id = $data->callback_query->id;
// получим переданное значение в кнопке
$c_data = $data->callback_query->data;
// спарсим значения
$params = explode("_", $c_data);
// если это переход по шагам
if ($params[0] == "s") {
// выводим сообщение
$printUpdate(
$params[1],
($params[2] !== "")
? $params[2]
: null,
$chat_id,
$cbq_id,
$data->callback_query->message->message_id
);
}
// если это другие кнопки
else {
// заглушим просто запрос
$notice($cbq_id, "This is notice for bot");
}
}
* * *
Исходный код бота
Бот настроен под работу с Webhook