JavaRush /Java блог /Random UA /Додаємо можливість роботи адміну та статистику для нього ...
Roman Beekeeper
35 рівень

Додаємо можливість роботи адміну та статистику для нього - "Java-проект від А до Я"

Стаття з групи Random UA
Всім привіт, дорогі друзі. Отже, бот вже працює і надсилає повідомлення про нові статті. Якщо ви ще не використовуєте його, ось посилання: Javarush Telegram Bot . Ну а сьогодні ми поговоримо про додавання команд, які працюють лише для адмінів. Одна з таких команд – статистика та дошка допомоги. Навіщо це потрібно? На даний момент цікавіше описати роботу з інструкціями в рамках цього завдання, ніж реальну необхідність цього. "Java-проект від А до Я": Додаємо можливість роботи адміну та статистику для нього - 1Ну а якщо ми йдемо в команду статистики, то можна її розширити і зробити більш інформативною. Після MVP можна буде повернути статистику для авторів, наприклад. Але про це вже потім…)

Розбираємося з додаванням адмінів та команд для них

Починаємо нашу роботу з того, що оновлюємо main гілку та її основі створюємо нову STEP_9_JRTB-10. Щоб розібратися, яка команда належить до адмінів, а яка до всіх, потрібно промаркувати команду. Для цього створимо інструкцію. Що це означає? Такого ми ще не робабо. Це можна вибрати під час створення класу в IDEA. Зараз покажу. У пакеті command створимо новий пакет annotation і в ньому – анотацію AdminCommand: "Java-проект від А до Я": Додаємо можливість роботи адміну та статистику для нього.  Частина 1 - 2Сама анотація буде такою:
package com.github.codegymcommunity.jrtb.command.annotation;

import com.github.codegymcommunity.jrtb.command.Command;

import java.lang.annotation.Retention;

import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
* Mark if {@link Command} can be viewed only by admins.
*/
@Retention(RUNTIME)
public @interface AdminCommand {
}
Тут нам більше нічого не потрібно. Далі додаємо її нашій команді StatCommand: "Java-проект від А до Я": Додаємо можливість роботи адміну та статистику для нього.  Частина 1 - 3Тепер все має заробити... Чи ні? Ні, звичайно)) Потрібно навчити наш CommandContainer правильно видавати результат. Для цього оновимо метод retrieveCommand , який видає команду для запуску в залежності від того, що йому передають. Як ідентифікатор адміна будемо використовувати його username у Телеграмі. Він унікальний і легше читаємо, ніж chat_id. Як його одержати? Він знаходиться в об'єкті Update, який надходить із повідомленням:
update.getMessage().getFrom().getUserName()
Підсумовуючи все сказане вище, оновимо метод CommandContainer#retrieveCommand :
public Command retrieveCommand(String commandIdentifier, String username) {
   Command orDefault = commandMap.getOrDefault(commandIdentifier, unknownCommand);
   if (isAdminCommand(orDefault)) {
       if (admins.contains(username)) {
           return orDefault;
       } else {
           return unknownCommand;
       }
   }
   return orDefault;
}

private boolean isAdminCommand(Command command) {
   return nonNull(command.getClass().getAnnotation(AdminCommand.class));
}
Як видно тут, я додав метод isAdminCommand , який перевіряє, чи є анотація AdminCommand в команді, що надається. І якщо це команда, призначена тільки для адмінів, ми перевіряємо, чи є цей username у нас в колекції доступних адмінів. До речі, ось він, ОВП, у всій красі: ми просто передаємо інтерфейс, який може бути будь-якою реалізацією. Але передати можемо лише клас, який реалізував інтерфейс Command . І начебто все зрозуміло, крім одного: звідки взялися адміни? Зараз покажу. Поки що я хочу передавати адмінів як змінну оточення, щоб її можна було легко налаштовувати. У цій змінній буде рядок, в якому через кому вказані всі username телеграм користувачів, які будуть адмінами. Для цього додамо до application.properties:
bot.admins: robeskman,romankh3
У CommandContainer у конструкторі передамо колекцію адмінів та проініціалізуємо її:
public class CommandContainer {

   private final ImmutableMap<String, Command> commandMap;
   private final Command unknownCommand;
   private final List<String> admins;

   public CommandContainer(SendBotMessageService sendBotMessageService, TelegramUserService telegramUserService,
                           CodeGymGroupClient codeGymGroupClient, GroupSubService groupSubService,
                           List<String> admins) {

       this.admins = admins;
А вже в CodeGymTelegramBot буде вся магія з отримання колекції з рядка в пертях:
@Autowired
public JavarushTelegramBot(TelegramUserService telegramUserService, CodeGymGroupClient groupClient, GroupSubService groupSubService,
                          @Value("#{'${bot.admins}'.split(',')}") List<String> admins) {
   this.commandContainer =
           new CommandContainer(new SendBotMessageServiceImpl(this),
                   telegramUserService, groupClient, groupSubService, admins);
}
Як видно з конструктора вище, знову використовуємо інструкцію Value , в яку передаємо логіку створення колекції. І все, таким чином, додавання адміну закінчилося. Тепер, коли адмін не захоче отримати дані зі статистики бота, він отримає таку відповідь: Не розумію тебе 😟, напиши /help щоб дізнатися що я розумію. Таким чином, ми розмежували ролі для команд бота.

Додаємо команду help для адмінів

Далі логічно було б створити окрему команду help для адмінів. У майбутньому ця частина може значно зрости. Додаємо значення адмінського help в CommandName:
ADMIN_HELP("/ahelp")
Створюємо клас AdminHelpCommand у пакеті command:
package com.github.codegymcommunity.jrtb.command;

import com.github.codegymcommunity.jrtb.service.SendBotMessageService;
import org.telegram.telegrambots.meta.api.objects.Update;

import static com.github.codegymcommunity.jrtb.command.CommandName.STAT;
import static java.lang.String.format;

/**
* Admin Help {@link Command}.
*/
public class AdminHelpCommand implements Command {

   public static final String ADMIN_HELP_MESSAGE = format("✨<b>Доступные команды админа</b>✨\n\n"
                   + "<b>Получить статистику</b>\n"
                   + "%s - статистика бота\n",
           STAT.getCommandName());

   private final SendBotMessageService sendBotMessageService;

   public AdminHelpCommand(SendBotMessageService sendBotMessageService) {
       this.sendBotMessageService = sendBotMessageService;
   }

   @Override
   public void execute(Update update) {
       sendBotMessageService.sendMessage(update.getMessage().getChatId().toString(), ADMIN_HELP_MESSAGE);
   }
}
Поки що він дуже простий. У майбутньому може цілком непогано розростися. Для цієї команди — тест із нашого шаблону:
package com.github.codegymcommunity.jrtb.command;

import org.junit.jupiter.api.DisplayName;

import static com.github.codegymcommunity.jrtb.command.AdminHelpCommand.ADMIN_HELP_MESSAGE;
import static com.github.codegymcommunity.jrtb.command.CommandName.ADMIN_HELP;

@DisplayName("Unit-level testing for AdminHelpCommand")
public class AdminHelpCommandTest extends AbstractCommandTest {

   @Override
   String getCommandName() {
       return ADMIN_HELP.getCommandName();
   }

   @Override
   String getCommandMessage() {
       return ADMIN_HELP_MESSAGE;
   }

   @Override
   Command getCommand() {
       return new AdminHelpCommand(sendBotMessageService);
   }
}
Зрозуміло, команду потрібно додати CommandContainer в нашу карту:
.put(ADMIN_HELP.getCommandName(), new AdminHelpCommand(sendBotMessageService))

Додаємо опис команд у бота

Телеграм-боти мають ще цікаву особливість: можна додати значення та опис команд, які він приймає, щоб користувачеві було легше використовувати команди. Як це виглядає? За прикладом підемо до BotFather — найголовнішого робота Телеграма. Якщо почати писати повідомлення зі слєша /, бот запропонує варіанти: "Java-проект від А до Я": Додаємо можливість роботи адміну та статистику для нього.  Частина 1 - 4І якщо продовжити писати, він відфільтрує та покаже релевантні варіанти:"Java-проект від А до Я": Додаємо можливість роботи адміну та статистику для нього.  Частина 1 - 5Класний функціонал, так? От я таке саме хочу зробити і в нас. Я робитиму так, як вмію — через додаток Телеграма. Я знаю, що це можна зробити програмно. Але я не вмію. У рамках цієї серії статей це не потрібно. Якщо хтось уміє це робити — пишіть мені, додаватимемо. Я із задоволенням прийму будь-яку допомогу в цьому. Я якось читав, що це можна зробити через патерн команда, яка у нас працює. Зараз я покажу, як я це вмію: нам потрібно знайти у Телеграмі BotFather, вибрати в нього бота, якого хочемо налаштувати. Далі вибрати редагування робота і секцію для команди. Зараз я покажу все на прикладі мого тестового робота для Javarush. У BotFather пишемо команду: /mybots"Java-проект від А до Я": Додаємо можливість роботи адміну та статистику для нього.  Частина 1 - 6 Далі вибираємо потрібного нам бота, в моєму випадку це буде test_codegym_community_bot:"Java-проект від А до Я": Додаємо можливість роботи адміну та статистику для нього.  Частина 1 - 7Як видно з переліку кнопок тут можна і подивитися токен, і видалити бота, і передати його комусь іншому. Нас цікавить редагування бота, тому вибираємо Edit Bot : "Java-проект від А до Я": Додаємо можливість роботи адміну та статистику для нього.  Частина 1 - 8І тут вибираємо Edit Commands : "Java-проект від А до Я": Додаємо можливість роботи адміну та статистику для нього.  Частина 1 - 9Нам потрібно просто надати у конкретному форматі повідомлення, і воно буде записано як команди. Або якщо хочемо прибрати їх усі, написати /empty. Для цієї справи створимо в корені проекту файл SET_UP_COMMANDS_BOT_FATHER , в якому напишу всі наші команди, щоб легко відновити або оновити в разі чого. SET_UP_COMMANDS_BOT_FATHER:
start - почати/відновити роботу з ботом stop - призупинити роботу з ботом addGroupSub - підписатися на групу статей deleteGroupSub - відписатися від групи статей listGroupSub - список груп, на які підписано
Зрозуміло, що адмінські команди ми не виносимо сюди. Про них мають знати лише адміни. Беремо це повідомлення та передаємо його BotFather: "Java-проект від А до Я": Додаємо можливість роботи адміну та статистику для нього.  Частина 1 - 10Як це зазвичай водиться, з першого разу не вийшло. Після декількох хвабон роздумів, передав усі команди в нижньому регістрі, а не в CamelCase, як було до цього, і все пройшло успішно. Оновлюємо у нашому файлі: SET_UP_COMMANDS_BOT_FATHER:
start - почати/відновити роботу з ботом stop - призупинити роботу з ботом addgroupsub - підписатися на групу статей deletegroupsub - відписатися від групи статей listgroupsub - список груп, на які підписано help - отримати допомогу в роботі зі мною
Тепер можна піти в нашого бота і посморіти, чи підтяглося підвантаження команд автоматично: "Java-проект від А до Я": Додаємо можливість роботи адміну та статистику для нього.  Частина 1 - 11Подивіться, яка тепер краса! Хотів у рамках цієї статті ще й розширити функціонал статистики, але матеріал і так вийшов об'ємним і за змістом, і за змістом. Тому перенесемо це наступного разу. Тобто завдання JRTB-10 зроблено не повністю: ми його доробимо в рамках наступної статті. Водночас усі зміни, які вже є, я додам до основного робота. Бажаєте підтримати автора, але не знаєте як? Це дуже просто – підписуйтесь на мій ТГ-канал , на мій GitHub аккаунт і пишіть тут у статтях свою думку про них. Цей зворотний зв'язок для мене важливий, так я розумію, що їх читають і ними цікавляться.

Висновки

Підсумуємо те, що ми сьогодні пройшли:
  1. Обговорабо, як додавати власну інструкцію, як її можна використовувати як маркер для розмежування ролей у командах. До речі, так можна було зробити і за допомогою інтерфейсу. Так само створабо б інтерфейс-маркер і потім перевіряли б, чи реалізує цей інтерфейс ні об'єкт, який приходить.
  2. Додали Help команду для адмінів. Як на мене — також важлива частина розвитку цього бота.
  3. Обговорабо, як додати опис та сплив команд при написанні їх у боті. Цікава фіча, напевно, варто було її додати.
На основі цієї статті створив пулл-реквест , можна переглянути його деталі. Всім дякую за увагу, з вас як завжди: лайк - передплата - дзвіночок , зірку нашому проекту, коментар та оцінити статтю! До зустрічі у наступній статті!

Список всіх матеріалів серії на початку цієї статті.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ