Русский
Русский
English
Статистика
Реклама

Bots

Keyboa клавиатуры на максималках для ботов в Telegram

09.10.2020 14:15:38 | Автор: admin

Боты - одна из особенностей Telegram, сделавших мессенджер столь популярным. А его встроенные клавиатуры дают разработчикам большую свободу взаимодействия с пользователями.

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

В этой статье рассмотрим базовые возможности модуля - создание клавиатур из разных наборов данных, автоматическое и ручное распределение кнопок по рядам, объединение нескольких клавиатур в одну. Научимся создавать сложные, динамические callback, сохраняя в них информацию о выборе пользователя.

Статья рассчитана на тех, кто знает основы Telegram Bot API и хотя бы немного знаком с фреймворком pyTelegramBotAPI.

Модуль требует > Python 3.5 и ставится через pip:

pip install keyboa

В официальной документации Telegram объект inline_keyboard определен как массив, состоящий из массивов кнопок (Array of Array of InlineKeyboardButton). То есть основной массив (список) - это клавиатура в целом, а его элементы (вложенные списки) - это ряды кнопок. Не переживайте, если определение кажется сложным - позже мы разберём его на простом примере.

Предположим, что сам бот у нас уже настроен:

import osfrom telebot import TeleBotfrom keyboa import keyboa_makertoken = os.environ["TELEGRAM_BOT_TOKEN"]uid = os.environ["TELEGRAM_USER_ID"] bot = TeleBot(token=token)

Создаём клавиатуру

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

fruits = [  "banana", "coconut", "orange",  "peach", "apricot", "apple",  "pineapple", "avocado", "melon"]

Создадим самую простую клавиатуру, в которой каждый элемент встанет на отдельный ряд:

kb_fruits = keyboa_maker(items=fruits, copy_text_to_callback=True)

Всё, что потребовалось - передать список в items. Оставить пустой сallback_data (ответную часть кнопки) мы не можем, ведь иначе не узнаем, что нажал пользователь. Поэтому добавляем текст каждой кнопки в её callback_data, устанавливая параметр copy_text_to_callback.

Отправим сообщение пользователю:

bot.send_message(  chat_id=uid, reply_markup=kb_fruits,  text="Please select one of the fruit:")

Распределяем кнопки по рядам

Разместить несколько кнопок в ряд можно любым из трёх способов.

Указать количество кнопок в ряду

Определите параметр items_in_row, чтобы задать количество кнопок в ряду. Их число должно быть от одного до восьми. Больше кнопок нам не позволит добавить сам Telegram.

Создадим клавиатуру и отправим обновлённое сообщение:

kb_fruits = keyboa_maker(items=fruits, copy_text_to_callback=True, items_in_row=3)bot.send_message(  chat_id=uid, reply_markup=kb_fruits,  text="Please select one of the fruit:")

Использовать автоподбор

Если клавиатура создается динамически, мы не всегда знаем, сколько в ней окажется кнопок. Чтобы автоматически равномерно распределить их по рядам, установите параметр auto_alignment. Если выставить True, Keyboa попробует подобрать делитель в диапазоне от трёх до пяти, а можно задать свой диапазон в виде списка чисел от одного до восьми, например, [2, 3, 7].

Параметр reverse_alignment_range определяет, будет ли Keyboa искать делитель с начала или с конца диапазона, указанного в auto_alignment.

Распределить кнопки вручную

С помощью Keyboa можно легко вручную задать количество кнопок в каждом ряду клавиатуры с помощью вложенных списков. Для примера объединим в ряды часть наших фруктов. Принцип остался прежним: каждый элемент основного списка - отдельный ряд клавиатуры, только теперь состоящий из нескольких элементов - кнопок.

fruitscomplex = [  "banana",  ["coconut", "orange"],  ["peach", "apricot", "apple"],  "pineapple",  ["avocado", "melon"],]kb_fruits_complex = keyboa_maker(items=fruits_complex, copy_text_to_callback=True)bot.send_message(  chat_id=uid, reply_markup=kb_fruits_complex,  text="Please select one of the fruit:")

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

Управляем содержимым callback

Мы упоминали, что callback_data - это строка, которую Telegram отправляет вам после нажатия кнопки пользователем. По её содержимому вы можете понять, какая именно кнопка была нажата.

Порой необходимо записать в callback_data дополнительные данные, например, идентификатор или другие свойства элемента. Чтобы задать кнопкам уникальные callback_data с требуемой информацией воспользуемся списком словарей или списком кортежей.

fruits_with_ids = [  {"banana": "101"}, {"coconut": "102"}, {"orange": "103"},  {"peach": "104"}, {"apricot": "105"}, {"apple": "106"},  {"pineapple": "107"}, {"avocado": "108"}, {"melon": "109"}, ]# or [# ("banana", "101"), ("coconut", "102"), ("orange", "103"),# ("peach", "104"), ("apricot", "105"), ("apple", "106"),# ("pineapple", "107"), ("avocado", "108"), ("melon", "109"), ]

В этом случае ключ каждого словаря станет текстом кнопки, а его значение пойдет в callback_data. Для кортежей это будут нулевой и первый элементы соответственно. И поскольку мы явно указываем, что добавить в callback_data, параметр copy_text_to_callback больше не нужен.

kb_fruits = keyboa_maker(items=fruits_with_ids, items_in_row=3)bot.send_message(  chat_id=uid, reply_markup=kb_fruits,text="Please select one of the fruit:")

Теперь, после нажатия пользователем одной из кнопок, мы получим в ответ не название фрукта, а его id номер, записанный в callback_data.

{"text": "banana", "callback_data": "101"}, ...

Этого достаточно, если мы используем числа только для обозначения фруктовых id. А если нужно принимать разные числовые параметры, например вес или цену? Как определить, к чему относится полученное число?

С Keyboa мы можем явно указать это при создании клавиатуры. Параметр front_marker добавляет одинаковый текст в начало callback_data каждой кнопки, а back_marker, соответственно, в конец. Рассмотрим на примере:

kb_fruits = keyboa_maker(items=fruits_with_ids, front_marker="&fruit_id=")

Теперь callback_data кнопок будет состоять из текста формата front_marker + value, например: '&fruit_id=101', '&fruit_id=102' и т. д. Такой подход позволит нам легко расшифровать строку и понять, какие получены данные и с какими значениями.

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

# коллбек, полученный после нажатия на кнопку, например "&fruit_id=102"selected_fruit_id = call.dataavailable_weight = [1, 2, 5, 10, 100, ]kb_available_weight = keyboa_maker(items=available_weight, copy_text_to_callback=True, front_marker="&weight=", back_marker=selected_fruit_id)

Теперь callback_data каждой кнопки будет выглядеть так:

'&weight=1&fruit_id=102', '&weight=2&fruit_id=102', '&weight=5&fruit_id=102'

Здесь мы используем front_marker и back_marker, чтобы обернуть передаваемое значение, указав тип текущего значения и добавив в callback_data уже полученную информацию. Имейте в виду, что у Telegram есть ограничение на длину callback_data в 64 байта, поэтому, если передаваемых параметров будет много - придётся сокращать названия или использовать аббревиатуры.

Объединение нескольких клавиатур в одну

Иногда в одну клавиатуру могут входить статические и динамические элементы, например, навигационное меню и меняющийся набор элементов. Тогда возникает потребность собрать клавиатуру из нескольких частей. Специально для этого у нас есть функцияkeyboa_combiner().

У нее есть только один параметр - keyboards, представляющий собой набор уже созданных клавиатур. Вот, как это работает:

from keyboa import keyboa_maker, keyboa_combinercontrols = [["", "", "", "", ""], ]tracks = list(range(1, 13))keyboard_controls = keyboa_maker(items=controls, copy_text_to_callback=True)keyboard_tracks = keyboa_maker(items=tracks, items_in_row=4, copy_text_to_callback=True)keyboard = keyboa_combiner(keyboards=(keyboard_tracks, keyboard_controls))bot.send_message(  chat_id=uid, reply_markup=keyboard,   text="Please select the track number:")

Можно объединить сразу много клавиатур - главное, чтобы общее количество кнопок было не больше ста. Это ограничение Telegram.

Итог

В этой статье мы кратко рассмотрели создание клавиатур для ботов в Telegram с помощью Keyboa. Больше информации и описание остальных параметров можно найти на странице проекта в Github.

Буду рад вашим отзывам, идеям и конструктивной критике по развитию проекта.

Подробнее..

Telegram-бот на Java для самых маленьких от старта до бесплатного размещения на heroku

25.11.2020 10:14:05 | Автор: admin


Для кого написано


Если вы ни разу не писали Telegram-ботов на Java и только начинаете разбираться эта статья для вас. В ней подробно и с пояснениями описано создание реального бота, автоматизирующего одну конкретную функцию. Можно использовать статью как мануал для создания скелета своего бота, а потом подключить его к своей бизнес-логике.

Предыстория


Когда моя дочь начала изучать арифметику, я между делом накидал алгоритм генерации простых примеров на сложение и вычитание вида 5 + 7 =, чтобы не придумывать и не гуглить для неё задания.

И тут на глаза попалась новость, что Telegram выпустил новую версию Bot API 5.0. Ботов я раньше не писал, и потому решил попробовать поднять бота как интерфейс для своей поделки. Все примеры, которые мне удалось найти, показались либо совсем простыми (нужные мне функции не были представлены), либо очень сложными для новичка. Также мне не хватало объяснений, почему выбран тот или иной путь. В общем, написано было сразу для умных, а не для меня. Потому я решил описать свой опыт создания простого бота надеюсь, кому-нибудь это поможет быстрее въехать в тему.

Что в статье есть, чего нет


В статье есть про:

  • создание бекенда не-инлайн бота на Java 11 с использованием Telegram Bot Api 5.0;
  • обработка команд вида /dosomething;
  • обработка текстовых сообщений, не являющихся командами (т.е. не начинающихся с "/");
  • отправку пользователю текстовых сообщений и файлов;
  • деплой и запуск бота на heroku.

В статье нет про:

  • использование функций ботов, не перечисленных выше (например, создание клавиатур я их сначала добавил, но в итоге они мне просто не пригодились);
  • создание списка заданий;
  • работу с Word-документом;
  • обеспечивающие функции логирование, тесты и т.п.;
  • общение с BotFather (создание бота, получение его токена и формирование списка команд подробно и понятно описано во многих источниках, вот первый попавшийся мануал).

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

Бизнес-функции бота


Очень кратко, чтобы проще было воспринимать код. Бот позволяет:

  • выдавать пользователю справочную текстовую информацию в ответ на команды /start, /help и /settings;
  • обрабатывать и запоминать пользовательские настройки, направленные текстовым сообщением заданного формата. Настроек три минимальное + максимальное число, используемые в заданиях, и количество страниц выгружаемого файла;
  • оповещать пользователя о несоблюдении им формата сообщения;
  • формировать Word-файл с заданиями на сложение, вычитание или вперемешку в ответ на команды /plus, /minus и /plusminus с использованием дефолтных или установленных пользователем настроек.

Можно потыкать MentalCalculationBot (должен работать). Выглядит так:



Общий порядок действий


  • разобраться с зависимостями;
  • создать класс бота и реализовать обработку текстовых сообщений пользователя, не являющихся командами;
  • создать классы команд;
  • прописать запуск приложения;
  • задеплоить на heroku.

Ниже подробно расписан каждый пункт.

Зависимости


Для управления зависимостями использовался Apache Maven. Нужные зависимости собственно Telegram Bots и Lombok, использовавшийся для упрощения кода (заменяет стандартные java-методы аннотациями).

Вот что вышло в
pom.xml
    <groupId>***</groupId>    <artifactId>***</artifactId>    <version>1.0-SNAPSHOT</version>    <name>***</name>    <description>***</description>    <packaging>jar</packaging>    <properties>        <java.version>11</java.version>        <maven.compiler.source>${java.version}</maven.compiler.source>        <maven.compiler.target>${java.version}</maven.compiler.target>        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>        <org.projectlombok.version>1.18.16</org.projectlombok.version>        <apache.poi.version>4.1.2</apache.poi.version>        <telegram.version>5.0.1</telegram.version>    </properties>    <dependencies>        <!-- Telegram API -->        <dependency>            <groupId>org.telegram</groupId>            <artifactId>telegrambots</artifactId>            <version>${telegram.version}</version>        </dependency>        <dependency>            <groupId>org.telegram</groupId>            <artifactId>telegrambotsextensions</artifactId>            <version>${telegram.version}</version>        </dependency>        <!-- Lombok -->        <dependency>            <groupId>org.projectlombok</groupId>            <artifactId>lombok</artifactId>            <version>${org.projectlombok.version}</version>            <scope>compile</scope>        </dependency>    </dependencies>    <build>        <finalName>${project.artifactId}</finalName>        <plugins>            <plugin>                <groupId>org.apache.maven.plugins</groupId>                <artifactId>maven-compiler-plugin</artifactId>                <version>3.8.1</version>                <configuration>                    <release>${java.version}</release>                    <annotationProcessorPaths>                        <path>                            <groupId>org.projectlombok</groupId>                            <artifactId>lombok</artifactId>                            <version>${org.projectlombok.version}</version>                        </path>                    </annotationProcessorPaths>                </configuration>            </plugin>            <plugin>                <groupId>org.apache.maven.plugins</groupId>                <artifactId>maven-dependency-plugin</artifactId>                <version>3.1.2</version>                <executions>                    <execution>                        <id>copy-dependencies</id>                        <phase>package</phase>                        <goals>                            <goal>copy-dependencies</goal>                        </goals>                    </execution>                </executions>            </plugin>            <plugin>                <groupId>org.apache.maven.plugins</groupId>                <artifactId>maven-surefire-plugin</artifactId>                <version>3.0.0-M5</version>            </plugin>        </plugins>    </build>


Класс бота и обработка текстовых сообщений


Мой класс Bot унаследован от TelegramLongPollingCommandBot, который, в свою очередь, наследуется от более распространённого в примерах TelegramLongPollingBot.

CommandBot хорош тем, что в нём уже реализованы приём и обработка команд то есть сообщений, начинающихся с "/". Можно создавать отдельные классы команд (подробнее о них ниже), инициализировать их в конструкторе бота и уже в них писать логику их обработки.

В классе Bot таким образом остаётся только логика обработки текстовых сообщений, не являющихся командами. В моём случае это пользовательские настройки или мусорные сообщения, не соответствующие формату. Для лаконичности логику их обработки тоже стоит вынести в отдельный вспомогательный класс, вызывая его метод из переопределенного метода processNonCommandUpdate(Update update) класса Bot.

В качестве параметров для инициализации бота используются его имя и токен, полученные от BotFather. В ходе разработки удобно создать тестового бота и прописать его параметры прямо в коде, чтобы проще было запускать бекенд локально и отлаживать прямо в Telegram, однако перед релизом стоит пересоздать бота и вынести эти настройки из кода например, в переменные окружения (об этом ниже).

Получился вот такой
Bot.java
import lombok.Getter;import org.telegram.telegrambots.extensions.bots.commandbot.TelegramLongPollingCommandBot;import org.telegram.telegrambots.meta.api.methods.send.SendMessage;import org.telegram.telegrambots.meta.api.objects.Message;import org.telegram.telegrambots.meta.api.objects.Update;import org.telegram.telegrambots.meta.api.objects.User;import org.telegram.telegrambots.meta.exceptions.TelegramApiException;import ru.taksebe.telegram.mentalCalculation.telegram.commands.operations.MinusCommand;import ru.taksebe.telegram.mentalCalculation.telegram.commands.operations.PlusCommand;import ru.taksebe.telegram.mentalCalculation.telegram.commands.operations.PlusMinusCommand;import ru.taksebe.telegram.mentalCalculation.telegram.commands.service.HelpCommand;import ru.taksebe.telegram.mentalCalculation.telegram.commands.service.SettingsCommand;import ru.taksebe.telegram.mentalCalculation.telegram.commands.service.StartCommand;import ru.taksebe.telegram.mentalCalculation.telegram.nonCommand.NonCommand;import ru.taksebe.telegram.mentalCalculation.telegram.nonCommand.Settings;import java.util.HashMap;import java.util.Map;public final class Bot extends TelegramLongPollingCommandBot {    private final String BOT_NAME;    private final String BOT_TOKEN;    //Класс для обработки сообщений, не являющихся командой    private final NonCommand nonCommand;    /**     * Настройки файла для разных пользователей. Ключ - уникальный id чата     */    @Getter    private static Map<Long, Settings> userSettings;    public Bot(String botName, String botToken) {        super();        this.BOT_NAME = botName;        this.BOT_TOKEN = botToken;        //создаём вспомогательный класс для работы с сообщениями, не являющимися командами        this.nonCommand = new NonCommand();        //регистрируем команды        register(new StartCommand("start", "Старт"));        register(new PlusCommand("plus", "Сложение"));        register(new MinusCommand("minus", "Вычитание"));        register(new PlusMinusCommand("plusminus", "Сложение и вычитание"));        register(new HelpCommand("help","Помощь"));        register(new SettingsCommand("settings", "Мои настройки"));        userSettings = new HashMap<>();    }    @Override    public String getBotToken() {        return BOT_TOKEN;    }    @Override    public String getBotUsername() {        return BOT_NAME;    }    /**     * Ответ на запрос, не являющийся командой     */    @Override    public void processNonCommandUpdate(Update update) {        Message msg = update.getMessage();        Long chatId = msg.getChatId();        String userName = getUserName(msg);        String answer = nonCommand.nonCommandExecute(chatId, userName, msg.getText());        setAnswer(chatId, userName, answer);    }    /**     * Формирование имени пользователя     * @param msg сообщение     */    private String getUserName(Message msg) {        User user = msg.getFrom();        String userName = user.getUserName();        return (userName != null) ? userName : String.format("%s %s", user.getLastName(), user.getFirstName());    }    /**     * Отправка ответа     * @param chatId id чата     * @param userName имя пользователя     * @param text текст ответа     */    private void setAnswer(Long chatId, String userName, String text) {        SendMessage answer = new SendMessage();        answer.setText(text);        answer.setChatId(chatId.toString());        try {            execute(answer);        } catch (TelegramApiException e) {            //логируем сбой Telegram Bot API, используя userName        }    }}


Класс обработки текстовых сообщений
NonCommand.java
import ru.taksebe.telegram.mentalCalculation.exceptions.IllegalSettingsException;import ru.taksebe.telegram.mentalCalculation.telegram.Bot;/** * Обработка сообщения, не являющегося командой (т.е. обычного текста не начинающегося с "/") */public class NonCommand {    public String nonCommandExecute(Long chatId, String userName, String text) {        Settings settings;        String answer;        try {            //создаём настройки из сообщения пользователя            settings = createSettings(text);            //добавляем настройки в мапу, чтобы потом их использовать для этого пользователя при генерации файла            saveUserSettings(chatId, settings);            answer = "Настройки обновлены. Вы всегда можете их посмотреть с помощью /settings";            //логируем событие, используя userName        } catch (IllegalSettingsException e) {            answer = e.getMessage() +                    "\n\n Настройки не были изменены. Вы всегда можете их посмотреть с помощью /settings";            //логируем событие, используя userName        } catch (Exception e) {            answer = "Простите, я не понимаю Вас. Возможно, Вам поможет /help";            //логируем событие, используя userName        }        return answer;    }    /**     * Создание настроек из полученного пользователем сообщения     * @param text текст сообщения     * @throws IllegalArgumentException пробрасывается, если сообщение пользователя не соответствует формату     */    private Settings createSettings(String text) throws IllegalArgumentException {        //отсекаем файлы, стикеры, гифки и прочий мусор        if (text == null) {            throw new IllegalArgumentException("Сообщение не является текстом");        }        //создаём из сообщения пользователя 3 числа-настройки (min, max, listCount) либо пробрасываем исключение о несоответствии сообщения требуемому формату        return new Settings(min, max, listCount);    }    /**     * Добавление настроек пользователя в мапу, чтобы потом их использовать для этого пользователя при генерации файла     * Если настройки совпадают с дефолтными, они не сохраняются, чтобы впустую не раздувать мапу     * @param chatId id чата     * @param settings настройки     */    private void saveUserSettings(Long chatId, Settings settings) {        if (!settings.equals(Settings.getDefaultSettings())) {            Bot.getUserSettings().put(chatId, settings);        }    }}


Классы команд


Все классы команд наследуются от BotCommand.

Команды в моём боте делятся на 2 группы:

  • Сервисные возвращают справочную информацию;
  • Основные формируют файл с заданиями.

Структура классов команд для этих групп идентична в абстрактном суперклассе реализуются общие методы, в наследуемые от него классах отдельных команд вынесена их кастомная логика. Разница лишь в том, что Основные команды обращаются к внешним классам, где реализована бизнес-логика, в то время как Сервисные просто формируют текстовый ответ.

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

Абстрактный суперкласс Сервисных команд
ServiceCommand.java
import org.telegram.telegrambots.extensions.bots.commandbot.commands.BotCommand;import org.telegram.telegrambots.meta.api.methods.send.SendMessage;import org.telegram.telegrambots.meta.bots.AbsSender;import org.telegram.telegrambots.meta.exceptions.TelegramApiException;/** * Суперкласс для сервисных команд */abstract class ServiceCommand extends BotCommand {    ServiceCommand(String identifier, String description) {        super(identifier, description);    }    /**     * Отправка ответа пользователю     */    void sendAnswer(AbsSender absSender, Long chatId, String commandName, String userName, String text) {        SendMessage message = new SendMessage();        //включаем поддержку режима разметки, чтобы управлять отображением текста и добавлять эмодзи        message.enableMarkdown(true);        message.setChatId(chatId.toString());        message.setText(text);        try {            absSender.execute(message);        } catch (TelegramApiException e) {            //логируем сбой Telegram Bot API, используя commandName и userName        }    }}


Класс Сервисной команды на примере
StartCommand.java
import org.telegram.telegrambots.meta.api.objects.Chat;import org.telegram.telegrambots.meta.api.objects.User;import org.telegram.telegrambots.meta.bots.AbsSender;/** * Команда "Старт" */public class StartCommand extends ServiceCommand {    public StartCommand(String identifier, String description) {        super(identifier, description);    }    @Override    public void execute(AbsSender absSender, User user, Chat chat, String[] strings) {        //формируем имя пользователя - поскольку userName может быть не заполнено, для этого случая используем имя и фамилию пользователя        String userName = (user.getUserName() != null) ? user.getUserName() :                String.format("%s %s", user.getLastName(), user.getFirstName());        //обращаемся к методу суперкласса для отправки пользователю ответа        sendAnswer(absSender, chat.getId(), this.getCommandIdentifier(), userName,                "Давайте начнём! Если Вам нужна помощь, нажмите /help");    }}


В суперклассе Основных команд, помимо аналогичного метода отправки ответов, содержится формирование Word-документа.
OperationCommand.java
import org.telegram.telegrambots.extensions.bots.commandbot.commands.BotCommand;import org.telegram.telegrambots.meta.api.methods.send.SendDocument;import org.telegram.telegrambots.meta.api.methods.send.SendMessage;import org.telegram.telegrambots.meta.api.objects.InputFile;import org.telegram.telegrambots.meta.bots.AbsSender;import org.telegram.telegrambots.meta.exceptions.TelegramApiException;import ru.taksebe.telegram.mentalCalculation.calculation.Calculator;import ru.taksebe.telegram.mentalCalculation.calculation.PlusMinusService;import ru.taksebe.telegram.mentalCalculation.enums.OperationEnum;import ru.taksebe.telegram.mentalCalculation.fileProcessor.WordFileProcessorImpl;import ru.taksebe.telegram.mentalCalculation.telegram.Settings;import java.io.FileInputStream;import java.io.IOException;import java.util.List;/** * Суперкласс для команд создания заданий с различными операциями */abstract class OperationCommand extends BotCommand {    private PlusMinusService service;    OperationCommand(String identifier, String description) {        super(identifier, description);        this.service = new PlusMinusService(new WordFileProcessorImpl(), new Calculator());    }    /**     * Отправка ответа пользователю     */    void sendAnswer(AbsSender absSender, Long chatId, List<OperationEnum> operations, String description, String commandName, String userName) {        try {            absSender.execute(createDocument(chatId, operations, description));        } catch (IOException | IllegalArgumentException e) {            sendError(absSender, chatId, commandName, userName);            e.printStackTrace();        } catch (TelegramApiException e) {            //логируем сбой Telegram Bot API, используя commandName и userName        }    }    /**     * Создание документа для отправки пользователю     * @param chatId id чата     * @param operations список типов операций (сложение и/или вычитание)     * @param fileName имя, которое нужно присвоить файлу     */    private SendDocument createDocument(Long chatId, List<OperationEnum> operations, String fileName) throws IOException {        FileInputStream stream = service.getPlusMinusFile(operations, Bot.getUserSettings(chatId));        SendDocument document = new SendDocument();        document.setChatId(chatId.toString());        document.setDocument(new InputFile(stream, String.format("%s.docx", fileName)));        return document;    }    /**     * Отправка пользователю сообщения об ошибке     */    private void sendError(AbsSender absSender, Long chatId, String commandName, String userName) {        try {            absSender.execute(new SendMessage(chatId.toString(), "Похоже, я сломался. Попробуйте позже"));        } catch (TelegramApiException e) {            //логируем сбой Telegram Bot API, используя commandName и userName        }    }}


Класс Основной команды на примере
PlusMinusCommand.java
import org.telegram.telegrambots.meta.api.objects.Chat;import org.telegram.telegrambots.meta.api.objects.User;import org.telegram.telegrambots.meta.bots.AbsSender;import ru.taksebe.telegram.mentalCalculation.enums.OperationEnum;/** * Команда получение файла с заданиями на сложение и вычитание */public class PlusMinusCommand extends OperationCommand {    public PlusMinusCommand(String identifier, String description) {        super(identifier, description);    }    @Override    public void execute(AbsSender absSender, User user, Chat chat, String[] strings) {        //формируем имя пользователя - поскольку userName может быть не заполнено, для этого случая используем имя и фамилию пользователя        String userName = (user.getUserName() != null) ? user.getUserName() :                String.format("%s %s", user.getLastName(), user.getFirstName());        //обращаемся к методу суперкласса для формирования файла на сложение и вычитание (за это отвечает метод getPlusMinus() перечисления OperationEnum) и отправки его пользователю        sendAnswer(absSender, chat.getId(), OperationEnum.getPlusMinus(), this.getDescription(), this.getCommandIdentifier(), userName);    }}


Приложение


В методе main инициализируется TelegramBotsApi, в котором и регистрируется Bot.

TelegramBotsApi в качестве параметра принимает Class<? extends BotSession>. Если нет никаких заморочек с прокси, можно использовать DefaultBotSession.class.

Чтобы получать имя и токен бота как переменные окружения, необходимо использовать System.getenv().

Получаем вот такой
MentalCalculationApplication.java
import org.telegram.telegrambots.meta.TelegramBotsApi;import org.telegram.telegrambots.meta.exceptions.TelegramApiException;import org.telegram.telegrambots.updatesreceivers.DefaultBotSession;import ru.taksebe.telegram.mentalCalculation.telegram.Bot;import java.util.Map;public class MentalCalculationApplication {    private static final Map<String, String> getenv = System.getenv();    public static void main(String[] args) {        try {            TelegramBotsApi botsApi = new TelegramBotsApi(DefaultBotSession.class);            botsApi.registerBot(new Bot(getenv.get("BOT_NAME"), getenv.get("BOT_TOKEN")));        } catch (TelegramApiException e) {            e.printStackTrace();        }    }}


Деплой на heroku


Для начала нужно создать в корне проекта файл Procfile и написать в него одну строку:
worker: java -Xmx300m -Xss512k -XX:CICompilerCount=2 -Dfile.encoding=UTF-8 -cp ./target/classes:./target/dependency/* <путь до приложения, в моём случае ru.taksebe.telegram.mentalCalculation.MentalCalculationApplication>
, где worker это тип процесса.

Если в проекте используется версия Java, отличная от 8, также необходимо создать в корне проекта файл system.properties и прописать в нём одну строку:
java.runtime.version=<версия Java>

Далее порядок такой:

  1. Регистрируемся на heroku и идём в консоль;
  2. mvn clean install;
  3. heroku login после выполнения потребуется нажать любую клавишу и залогиниться в открывшемся окне браузера;
  4. heroku create <имя приложения> создаём приложение на heroku;
  5. git push heroku master пушим в репозиторий heroku;
  6. heroku config:set BOT_NAME=<имя бота> добавляем имя бота в переменные окружения;
  7. heroku config:set BOT_TOKEN=<токен бота> добавляем токен бота в переменные окружения;
  8. heroku config:get BOT_NAME (аналогично BOT_TOKEN) убеждаемся, что переменные окружения установлены верно;
  9. heroku ps:scale worker=1 устанавливаем количество контейнеров (dynos) для типа процесса worker (ранее мы выбрали этот тип в Procfile), при этом происходит рестарт приложения;
  10. В интерфейсе управления приложением в личном кабинете на heroku переходим к логам (прячутся под кнопкой More в правом верхнем углу) и убеждаемся, что приложение запущено;
  11. Тестируем бота через Telegram.

Если вы храните код на GitHub, то в интерфейсе управления приложением в личном кабинете на heroku на вкладке Deploy вы можете в дальнейшем переключить деплой на GitHub-репозиторий (по запросу или автоматически), чтобы не пушить параллельно в два репозитория.

Вместо заключения


Как выяснилось, не только лишь все видели чудесный советский мультик про козлёнка, который учился считать до 10.
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru