IoBroker. Inline меню для Telegram бота
В этой статье мы с вами по шагам будем создавать меню для telegram бота Умного Дома. Рекомендую изучить минимальные азы по языку программирования JavaSсript, это облегчит понимание того, что тут вообще происходит.
Приготовления
Сначала необходимо установить драйвера Telegram и Script Engine. Вспомнить как это делается, можно в статье ioBroker - устанавливаем первый драйвер. Не забываем подключить своего бота к драйверу Telegram, как описано начале статьи - ioBroker - уведомления.
При увеличении количества написанных скриптов, будет нарастать бардак в дереве, поэтому рекомендую сразу приучать себя разбивать скрипты по группам.
Добавим новую группу в папку common и назовем ее например Telegram. В этой группе в дальнейшем можно будет создавать все скрипты, которые будут относиться к работе с драйвером Telegram.
Вот теперь можно добавить наш будущий скрипт для меню. Для этого надо выделить созданную группу Telegram и нажать кнопку Новый скрипт
В появившемся окне выбираем нужный тип языка - JavaScript
Поменяем имя на Телеграм бот и сохраним изменения.
Все готово к созданию меню для Умного дома.
Создаем Меню
Предварительно необходимо на листике или в уме подготовить набросок древовидной структуры будущего меню
В этой статье попробуем реализовать подобную структуру меню.
Не обязательно придерживаться разбиения по комнатам, рекомендую в основные ветки выносить управление тем, что чаще всего используется, т.к. допустим для запуска Сцены 3 в зале надо сделать целых 4 нажатия!
Вызвать Меню - Зал - Сценарии - Сцена 3
Ежедневно запускать сценарий по такому длинному пути быстро надоест :)
Набросаем наше дерево в скрипте
var button = [{name: 'Меню',button: ['Зал', 'Кухня', 'Спальня', 'Ванная', 'Закрыть', 'Меню']},
{name: 'Зал',button: ['Люстра', 'Зал. Кондиционер', 'Зал. Сцены', 'Назад', 'Закрыть', 'Меню']},
{name: 'Зал. Сцены', button: ['Кино', 'Весь свет', 'Приглушенный свет', 'Назад', 'Закрыть', 'Зал']},
{name: 'Кухня', button: ['Кухня. Свет', 'Вентиляция', 'Назад', 'Закрыть', 'Меню']},
{name: 'Верхний свет', button: ['Включить', 'Выключить', 'Назад', 'Закрыть', 'Кухня']},
{name: 'Спальня', button: ['Спальня. Свет', 'Спальня. Кондиционер', 'Спальня. Сцены', 'Назад', 'Закрыть', 'Меню']},
{name: 'Спальня. Свет', button: ['Верхний', 'Бра', 'Назад', 'Закрыть', 'Спальня']},
{name: 'Спальня. Сцены', button: ['Сцена 1', 'Сцена 2', 'Назад', 'Закрыть', 'Спальня']},
{name: 'Ванная', button: ['Ванная. Свет', 'Бойлер', 'Вытяжка', 'Назад', 'Закрыть', 'Меню']},
];
Разберем подробнее
В первой строке в квадратных скобках перечисляются основные кнопки (ветки) меню, плюс дополнительно добавляется кнопка Закрыть. Она позволит закрывать меню в чате бота, чтобы у нас не получилось куча сообщений от бота с открытыми менюшками. Ну и в конце текст в кавычках ‘Меню’ тоже обязателен. В этом месте будет указываться название вышестоящей ветки меню, т.к. первая строка уже является верхушкой дерева, то текст в этом месте дублирует начало.
Вторая строка - переход по дереву ниже на ветку Зал. Соответственно в квадратных скобках уже перечислены кнопки меню Зал. К Закрыть, добавилась кнопка Назад, которая позволит подняться на одну ветку выше и в конце укажем куда - ‘Меню’.
Третья строка - переход еще ниже в меню Зал. Сцены. Для корректной работы кнопки Назад, в конце пишем - ‘Зал’.
Отступы для каждой строки сделаны лишь для удобства восприятия структуры меню и никакой функциональности не несут.
Внимательный читатель, надеюсь, обратил внимание что названия веток меню в наброске и в коде отличаются :) Почему так сделано, будет описано дальше.
Дальнейший код будет описан только в объеме, необходимом для оформления своего меню, плюс краткое пояснение функций.
Добавляем в скрипт весь остальной код.
var button = [{name: 'Меню',button: ['Зал', 'Кухня', 'Спальня', 'Ванная', 'Закрыть', 'Меню']},
{name: 'Зал',button: ['Люстра', 'Зал. Кондиционер', 'Зал. Сцены', 'Назад', 'Закрыть', 'Меню']},
{name: 'Зал. Сцены', button: ['Кино', 'Весь свет', 'Приглушенный свет', 'Назад', 'Закрыть', 'Зал']},
{name: 'Кухня', button: ['Кухня. Свет', 'Вентиляция', 'Назад', 'Закрыть', 'Меню']},
{name: 'Верхний свет', button: ['Включить', 'Выключить', 'Назад', 'Закрыть', 'Кухня']},
{name: 'Спальня', button: ['Спальня. Свет', 'Спальня. Кондиционер', 'Спальня. Сцены', 'Назад', 'Закрыть', 'Меню']},
{name: 'Спальня. Свет', button: ['Верхний', 'Бра', 'Назад', 'Закрыть', 'Спальня']},
{name: 'Спальня. Сцены', button: ['Сцена 1', 'Сцена 2', 'Назад', 'Закрыть', 'Спальня']},
{name: 'Ванная', button: ['Ванная. Свет', 'Бойлер', 'Вытяжка', 'Назад', 'Закрыть', 'Меню']},
];
var menuUp = 'Меню';
var first_tap = false;
var menu_current;
var topTextGlobal;
on({id: "telegram.0.communicate.request", change: 'any'}, function (obj) {
command = obj.state.val.substring(obj.state.val.indexOf(']') + 1);
user = obj.state.val.substring(obj.state.val.indexOf('[')+1, obj.state.val.indexOf(']'));
//log(command);
//log(user);
//************************************
// Меню
//************************************
if (command ==="/buttons" || command ==="кнопки" || command ==="Кнопки")
sendTo('telegram', {
user: user,
text: 'Показать меню',
reply_markup: {
keyboard: [['Показать меню']],
resize_keyboard: true,
one_time_keyboard: true
}
});
//************************************
// меню inline
//************************************
var menu = {
reply_markup: {
inline_keyboard: [[],[],[],[],[],[],[]],
}
};
if (command === 'Показать меню') command = menuUp;
log (command);
if (command === 'Меню') first_tap = true;
if (command === 'Назад') command = menuUp;
var but1 = getButtonArray(button, 'name', command).toString();
if (but1.length > 0) { // проверяем, что строка не пустая
var but2 = but1.split(','); //преобразуем в массив
menuUp = but2.pop(); //вырезаем последний элемент
if (but2.length > 0) { // проверяем что массив не пуст
var index = 0;
for (var i=0, len=but2.length; i<len; ((i%3="" if="" but2[i]});="" but2[i],="" menu.reply_markup.inline_keyboard[index].push({="" {="" i++)="">= 2)&&(index < 6)) index = ++index;
}
var topText = funcTopText(command);
topTextGlobal = command;
menu_current = menu.reply_markup;
if (first_tap) {
sendTo('telegram.0', {user: user, text: topText, parse_mode: 'markdown', reply_markup: menu.reply_markup});
first_tap = false;
} else {
updateMenuButton(user, topText, menu.reply_markup);
}
}
}
//************************************
// Команды
//************************************
// ищем в тексте команды
switch (command) {
case "Закрыть":
sendTo('telegram', {
user: user,
deleteMessage: {
options: {
chat_id: getState("telegram.0.communicate.requestChatId").val,
message_id: getState("telegram.0.communicate.requestMessageId").val,
}
}
});
break;
}
});
function updateMenuButton(user, topText, menu){
sendTo('telegram', {
user: user,
text: topText,
editMessageText: {
options: {
chat_id: getState("telegram.0.communicate.requestChatId").val,
message_id: getState("telegram.0.communicate.requestMessageId").val,
parse_mode: 'markdown',
reply_markup: menu
}
}
});
}
function waitConfirmCommand(obj, command, timeout, ack = false){
var mySubscription;
var timeID = setTimeout(() => {
unsubscribe(mySubscription);
CommandDone('Не выполнено!');
}, timeout);
mySubscription = on({id: obj, change: 'ne'}, function (data) {
// unsubscribe after first trigger
if (ack === true)
if (data.state.ack) ack = false;
if (!ack) {
updateMenuButton(user, funcTopText(command), menu_current);
unsubscribe(mySubscription);
clearTimeout(timeID);
CommandDone('Выполнено!');
}
});
}
function CommandDone(text){
if (text === '') text = "Выполнено!";
sendTo('telegram', {
user: user,
answerCallbackQuery: {
text: text,
showAlert: false
}
});
}
function getButtonArray(obj, keyName, Name) {
var result = [];
for (var attr in obj) {
if (obj[attr] && typeof obj[attr] === 'object') {
result = result.concat(getButtonArray(obj[attr], keyName, Name));
}
if (attr === keyName && obj[attr] === Name) {
result.push(obj.button);
}
}
return result;
}
function stateSelection(state){
if ((state.val === false) || (state.val === 'false')) return "ВЫКЛ. в " + formatDate(state.lc, "SS:mm:ss TT.MM");
if ((state.val === true) || (state.val === 'true')) return "ВКЛ. в " + formatDate(state.lc, "SS:mm:ss TT.MM");
return "неопределено";
}
function funcTopText (command){
var text = command;
switch (command) {
case 'Меню':
text = "*Зал*: Температура 24.2 градуса \n"
+ "*Спальня*: Температура 23.5 градуса \n";
break;
}
return text;
}</len;>
Уже на этом этапе можно проверить работу меню. Для этого сохраняем скрипт, запускаем и в Telegramотправляем боту слово Меню(внимание, слово должно быть с большой буквы) или Кнопки.
Управление
Переходим к следующему шагу. Добавим в скрипт команды управления оборудованием (свет, бойлер, сценарии, насосы, краны, телевизор, кондиционер и т.д.). Для этого создадим виртуальный выключатель (код добавим в самое начало скрипта)
createState("Test.Switch.command", false); // Тестовый выключатель света
javascript Копировать
Сохраним и команда createStateсоздаст новый объект, который можно посмотреть на вкладке Объекты - javascript.0
Дальше вставим в скрипт команды управления светом на кухне на примере виртуального выключателя
case "Включить": //включить свет на кухне
setState("javascript.0.Test.Switch.command"/*Test.Switch.command*/, true);
CommandDone('Выполнено!');
break;
case "Выключить": //выключить свет на кухне
setState("javascript.0.Test.Switch.command"/*Test.Switch.command*/, false);
CommandDone('Выполнено!');
break;
javascript Копировать
должно получиться так
Ранее в статье был момент, когда названия веток (кнопок) немного менялись и стали отличаться от наброска меню. Весь смысл в том, что названия всех кнопок в меню бота должны бытьуникальны, это связано с особенностями API Telegram. Подробнее можно почитать тут. Иначе при нажатии на одинаковые названия кнопок в меню, всегда будут выполняться команды только для какой-то одной кнопки, даже если вы на нее не нажимали.
Разберем подробнее, что же мы сделали:
switch- сравнивает выражение со случаями, перечисленными внутри неё, а затем выполняет соответствующие инструкции. Подробнее тут.
command- будет содержать уникальное имя нажатой кнопки
case “Включить” - сравниваем со всеми описанными в секции switchименами кнопок и ищем команды для нажатой кнопки Выключить.
setState(“javascript.0.Test.Switch.command”/*Test.Switch.command*/,true);- записываем значение True в объект Test.Switch.command. Дополнительно по команде setStateможно почитать тут.
CommandDone(‘Выполнено!’); - вызываем функцию CommandDone и передаем ей текст, который хотим отобразить во всплывающем сообщении при нажатии кнопки. В данном случае текст будет Выполнено!
break; - указываем, что выполнение операций для кнопки Включитьзавершаем. Если не добавить команду break, JS начнет выполнение следующего case.
Для кнопки Выключить все тоже самое, только записываемое значение false.
Сохраняем и пробуем! (результат можно увидеть на вкладке Объекты. Будет меняться значение виртуального выключателя).
Для элементарного меню и простых команд этого уже достаточно. А для тех, кто хочет большего, продолжим.
Красивости
Начнем добавлять различные красивости: отображение текущего состояния, обновление состояний при нажатии кнопки, красивый вывод состояний, эмодзи и т.д.
За вывод подобной информации отвечает функция funcTopText
function funcTopText (command){
var text = command;
switch (command) {
case 'Меню':
text = "*Зал*: Температура 24.2 градуса \n"
+ "*Спальня*: Температура 23.5 градуса \n";
break;
}
return text;
}
javascript Копировать
В функцию автоматически через переменную command передается нажатая кнопка дерева (например Меню, Спальня, Кухня. Свет и т.д.), самый нижний уровень дереве меню передать нельзя (например Кино, Бойлер, Бра и т.д.). И уже в самой функции с помощью уже знакомой конструкции switch (command) можно добавить вывод необходимой информации о состоянии оборудования или просто какой-то текст для выбранной ветки меню. В примере выше указана ветка Меню, в ней выводим температуры (пока как простой текст) в комнатах Зал и Спальня.
Как сделать форматирование текста жирным или курсивом, можно почитать тут.
Выведем температуру в зале из реального объекта. Надо выбрать из ваших существующих объектов, иначе будет ошибка.
case 'Меню':
text = "*Зал*: Температура " + getState("mysensors.0.70.3_TEMP.V_TEMP").val + "°C, \n"
+ "*Спальня*: Температура 23.5 градуса \n";
javascript Копировать
Из объекта mysensors.0.70.3_TEMP.V_TEMP считываем значение температуры и подставляем его в строку (подробнее о команде getState тут).
</b>n – символ новой строки, является эквивалентом символа перевода строки.
Добавим в меню Кухняотображение состояния виртуального выключателя
function funcTopText (command){
var text = command;
switch (command) {
case 'Меню':
text = "*Зал*: Температура " + getState("mysensors.0.70.3_TEMP.V_TEMP").val + "°C, \n"
+ "*Спальня*: Температура 23.5 градуса \n";
break;
case 'Кухня':
text = "*Свет*: состояние: " + getState("Test.Switch.command").val + "\n";
break;
}
return text;
}
javascript Копировать
Состояние false, как-то скучно… да объясняй потом жене, ребенку, друзьям что за false такой…
Используем функцию stateSelection(state), которая будет вместо false/true возвращать нормальный текст Вкл/Выкл. и дополнительно выводить время подачи команды (или любой другой текст на ваше усмотрение)
function stateSelection(state){
if ((state.val === false) || (state.val === 'false')) return "ВЫКЛ. в " + formatDate(state.lc, "SS:mm:ss TT.MM");
if ((state.val === true) || (state.val === 'true')) return "ВКЛ. в " + formatDate(state.lc, "SS:mm:ss TT.MM");
return "неопределено";
}
function funcTopText (command){
var text = command;
switch (command) {
case 'Меню':
text = "*Зал*: Температура " + getState("mysensors.0.70.3_TEMP.V_TEMP").val + "°C, \n"
+ "*Спальня*: Температура 23.5 градуса \n";
break;
case 'Кухня':
text = "*Свет*: состояние: " + stateSelection(getState("Test.Switch.command")) + "\n";
break;
}
return text;
}
javascript Копировать
Так гораздо лучше и понятней смотрится!
В функцию stateSelection(state) при необходимости можно добавить другие типы состояний, если они будут выводиться в заголовок меню.
Может еще улучшим? Сделаем, чтобы при нажатии кнопки Кухня. Свет сразу в заголовке менялось состояние, для этого воспользуемся функцией
function waitConfirmCommand(obj, command, timeout, ack = false){
var mySubscription;
var timeID = setTimeout(() => {
unsubscribe(mySubscription);
CommandDone('Не выполнено!');
}, timeout);
mySubscription = on({id: obj, change: 'ne'}, function (data) {
// unsubscribe after first trigger
if (ack === true)
if (data.state.ack) ack = false;
if (!ack) {
updateMenuButton(user, funcTopText(command), menu_current);
unsubscribe(mySubscription);
clearTimeout(timeID);
CommandDone('Выполнено!');
}
});
}
javascript Копировать
Функция подписывается на переданный объект obj и в течение заданного количества миллисекунд timeout ожидает изменения объекта obj. Если событие произошло и была задана дополнительно (но не обязательно) проверка флага ACK, проверяется на условие ack=true. Если совпало или не была задана проверка флага ACK – отобразится текст Выполнено. Если не совпало – Не выполнено. После этого функция отписывается от объекта, чтобы в будущем снова не реагировать на него.
Внесем изменения в скрипт
function funcTopText (command){
var text = command;
switch (command) {
case 'Меню':
text = "*Зал*: Температура " + getState("mysensors.0.70.3_TEMP.V_TEMP").val + "°C, \n"
+ "*Спальня*: Температура 23.5 градуса \n";
break;
case 'Кухня':
text = "*Свет*: " + stateSelection(getState("javascript.0.Test.Switch.command")) + "\n";
break;
case 'Кухня. Свет':
text = "*Свет*: " + stateSelection(getState("javascript.0.Test.Switch.command")) + "\n";
break;
}
return text;
}
javascript Копировать
case "Включить": //включить свет на кухне
setState("javascript.0.Test.Switch.command"/*Test.Switch.command*/, true);
waitConfirmCommand("javascript.0.Test.Switch.command", topTextGlobal, 2000, true);
break;
case "Выключить": //выключить свет на кухне
setState("javascript.0.Test.Switch.command"/*Test.Switch.command*/, false);
waitConfirmCommand("javascript.0.Test.Switch.command", topTextGlobal, 2000);
break;
javascript Копировать
javascript.0.Test.Switch.command – объект для проверки выполнения команды. Т.к. у нас нет отдельного объекта для обратной связи, используем для этой цели объект-команду.
2000– время ожидания в миллисекундах
topTextGlobal – эту переменную ставим всегда!
Для кнопки Включитьдобавили проверку флага ACK, для кнопки Выключить нет. Сохраняем и пробуем что получилось.
Уберем проверку флага ACK для кнопки Включить и снова пробуем.
Не забываем смотреть на всплывающие сообщения!
Надеюсь, суть уловили ?
Эмодзи
Пришла очередь добавить символы Эмодзи. Для этого заходим в библиотеку один или два. Выбираем подходящие символы для меню. Возьмем для кнопки Назад следующий символ
Выделяем, копируем и вставим в наш скрипт.
Внимание!Менять надо во всех местах скрипта, где использовано словоНазад.
Замену лучше делать через Поисккомбинацией клавиш ctrl+H
Сохраняем и любуемся результатом
Надеюсь у вас все получилось ?
Итоговый скрипт меню
createState("Test.Switch.command", false); // Тестовый выключатель света
var button = [{name: 'Меню',button: ['Зал', 'Кухня', '?', 'Ванная', 'Закрыть', 'Меню']},
{name: 'Зал',button: ['Люстра', 'Зал. Кондиционер', 'Зал. Сцены', '◀️ Назад', 'Закрыть', 'Меню']},
{name: 'Зал. Сцены', button: ['Кино', 'Весь свет', 'Приглушенный свет', '◀️ Назад', 'Закрыть', 'Зал']},
{name: 'Кухня', button: ['Кухня ?', 'Вентиляция', '◀️ Назад', 'Закрыть', 'Меню']},
{name: 'Кухня ?', button: ['Включить', 'Выключить', '◀️ Назад', 'Закрыть', 'Кухня']},
{name: '?', button: ['? Спальня. Свет', '? Кондиционер', '? Сцены', '◀️ Назад', 'Закрыть', 'Меню']},
{name: '? Свет', button: ['Верхний', 'Бра', '◀️ Назад', 'Закрыть', '?']},
{name: '? Сцены', button: ['Сцена 1', 'Сцена 2', '◀️ Назад', 'Закрыть', '?']},
{name: 'Ванная', button: ['Ванная. Свет', 'Бойлер', 'Вытяжка', '◀️ Назад', 'Закрыть', 'Меню']},
];
var menuUp = 'Меню';
var first_tap = false;
var menu_current;
var topTextGlobal;
on({id: "telegram.0.communicate.request", change: 'any'}, function (obj) {
command = obj.state.val.substring(obj.state.val.indexOf(']') + 1);
user = obj.state.val.substring(obj.state.val.indexOf('[')+1, obj.state.val.indexOf(']'));
//log(command);
//log(user);
//************************************
// Меню
//************************************
if (command ==="/buttons" || command ==="кнопки" || command ==="Кнопки")
sendTo('telegram', {
user: user,
text: 'Показать меню',
reply_markup: {
keyboard: [['Показать меню']],
resize_keyboard: true,
one_time_keyboard: true
}
});
//************************************
// меню inline
//************************************
var menu = {
reply_markup: {
inline_keyboard: [[],[],[],[],[],[],[]],
}
};
if (command === 'Показать меню') command = menuUp;
log (command);
if (command === 'Меню') first_tap = true;
if (command === '◀️ Назад') command = menuUp;
var but1 = getButtonArray(button, 'name', command).toString();
if (but1.length > 0) { // проверяем, что строка не пустая
var but2 = but1.split(','); //преобразуем в массив
menuUp = but2.pop(); //вырезаем последний элемент
if (but2.length > 0) { // проверяем что массив не пуст
var index = 0;
for (var i=0, len=but2.length; i<len; i++) {
menu.reply_markup.inline_keyboard[index].push({ text: but2[i], callback_data: but2[i]});
if ((i%3 >= 2)&&(index < 6)) index = ++index;
}
var topText = funcTopText(command);
topTextGlobal = command;
menu_current = menu.reply_markup;
if (first_tap) {
sendTo('telegram.0', {user: user, text: topText, parse_mode: 'markdown', reply_markup: menu.reply_markup});
first_tap = false;
} else {
updateMenuButton(user, topText, menu.reply_markup);
}
}
}
//************************************
// Команды
//************************************
// ищем в тексте команды
switch (command) {
case "Включить": //включить свет на кухне
setState("javascript.0.Test.Switch.command"/*Test.Switch.command*/, true);
waitConfirmCommand("javascript.0.Test.Switch.command", topTextGlobal, 2000, true);
break;
case "Выключить": //выключить свет на кухне
setState("javascript.0.Test.Switch.command"/*Test.Switch.command*/, false);
waitConfirmCommand("javascript.0.Test.Switch.command", topTextGlobal, 2000);
break;
case "Закрыть":
sendTo('telegram', {
user: user,
deleteMessage: {
options: {
chat_id: getState("telegram.0.communicate.requestChatId").val,
message_id: getState("telegram.0.communicate.requestMessageId").val,
}
}
});
break;
}
});
function updateMenuButton(user, topText, menu){
sendTo('telegram', {
user: user,
text: topText,
editMessageText: {
options: {
chat_id: getState("telegram.0.communicate.requestChatId").val,
message_id: getState("telegram.0.communicate.requestMessageId").val,
parse_mode: 'markdown',
reply_markup: menu
}
}
});
}
function waitConfirmCommand(obj, command, timeout, ack = false){
var mySubscription;
var timeID = setTimeout(() => {
unsubscribe(mySubscription);
CommandDone('Не выполнено!');
}, timeout);
mySubscription = on({id: obj, change: 'any'}, function (data) {
// unsubscribe after first trigger
if (ack === true)
if (data.state.ack) ack = false;
if (!ack) {
updateMenuButton(user, funcTopText(command), menu_current);
unsubscribe(mySubscription);
clearTimeout(timeID);
CommandDone('Выполнено!');
}
});
}
function CommandDone(text){
if (text === '') text = "Выполнено!";
sendTo('telegram', {
user: user,
answerCallbackQuery: {
text: text,
showAlert: false
}
});
}
function getButtonArray(obj, keyName, Name) {
var result = [];
for (var attr in obj) {
if (obj[attr] && typeof obj[attr] === 'object') {
result = result.concat(getButtonArray(obj[attr], keyName, Name));
}
if (attr === keyName && obj[attr] === Name) {
result.push(obj.button);
}
}
return result;
}
function stateSelection(state){
if ((state.val === false) || (state.val === 'false')) return "ВЫКЛ. в " + formatDate(state.lc, "SS:mm:ss TT.MM");
if ((state.val === true) || (state.val === 'true')) return "ВКЛ. в " + formatDate(state.lc, "SS:mm:ss TT.MM");
return "неопределено";
}
function funcTopText (command){
var text = command;
switch (command) {
case 'Меню':
text = "*Зал*: ?️ " + getState("mysensors.0.70.3_TEMP.V_TEMP").val + "°C, \n"
+ "? ? 23.5 градуса \n";
break;
case 'Кухня':
text = "? " + stateSelection(getState("javascript.0.Test.Switch.command")) + "\n";
break;
case 'Кухня ?':
text = "? " + stateSelection(getState("javascript.0.Test.Switch.command")) + "\n";
break;
}
return text;
}
javascript Копировать
Лайфхак
Через @botFather можно добавить команду вызова меню
Тогда для вызова меню будет достаточно ввести символ / и в выпадающем меню выбрать /buttons. В результате появится кнопочка вызова меню рядом с меню смайликов. Роман Б. (Haba) Россия, ст-ца. Динская@Habaaaa Отблагодарить автора
Comments