JavaRush /Java блог /Java-проекты /Добавляем возможность работы админа и статистику для него...
Roman Beekeeper
35 уровень

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

Статья из группы Java-проекты
Всем привет, мои дорогие друзья. Итак, бот уже работает и отправляет уведомления о новых статьях. Если вы еще не используете его — вот ссылка: Javarush Telegram Bot. Ну а сегодня мы поговорим о добавлении команд, которые работают только для админов. Одна из таких команд — статистика и доска помощи. Зачем это нужно? На данный момент интереснее описать работу с аннотациями в рамках этой задачи, чем реальную необходимость в этом."Java-проект от А до Я": Добавляем возможность работы админа и статистику для него - 1Ну а раз уж мы идем в команду статистики, то можно ее расширить и сделать более информативной. Уже после MVP можно будет вернуть статистику для авторов, например. Но об этом уже потом…)

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

Начинаем нашу работу с того, что обновляем main ветку и ее основе создаем новую — STEP_9_JRTB-10. Чтобы разобраться, какая команда относится к админам, а какая — ко всем, нужно промаркировать команду. Для этого создадим аннотацию. Что это значит? Такого мы еще не делали. Это можно выбрать при создании класса в IDEA. Сейчас покажу. В пакете command создадим новый пакет annotation и в нем — аннотацию AdminCommand:"Java-проект от А до Я": Добавляем возможность работы админа и статистику для него. Часть 1 - 2Сама аннотация будет такой:

package com.github.javarushcommunity.jrtb.command.annotation;

import com.github.javarushcommunity.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,
                           JavaRushGroupClient javaRushGroupClient, GroupSubService groupSubService,
                           List<String> admins) {

       this.admins = admins;
А уже в JavaRushTelegramBot будет вся магия по получению коллекции из строки в пропертях:

@Autowired
public JavarushTelegramBot(TelegramUserService telegramUserService, JavaRushGroupClient 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.javarushcommunity.jrtb.command;

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

import static com.github.javarushcommunity.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.javarushcommunity.jrtb.command;

import org.junit.jupiter.api.DisplayName;

import static com.github.javarushcommunity.jrtb.command.AdminHelpCommand.ADMIN_HELP_MESSAGE;
import static com.github.javarushcommunity.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_javarush_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 - список групп, на которые подписан help - получить помощь в работе со мной
Ясно, что админские команды мы не выносим сюда. О них должны знать только админы. Берем это сообщение и передаем его 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. Обсудили, как добавить описание и всплывание команд при написании их в боте. Интересная фича, определенно стоило ее добавить.
На основе этой статьи создал пулл-реквест, можно посмотреть его детали. Всем спасибо за внимание, с вас как обычно: лайк - подписка - колокольчик, звезду нашему проекту, комментарий и оценить статью! До встречи в следующей статье!

Список всех материалов серии в начале этой статьи.

Комментарии (2)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
hRAPovick Уровень 24
19 мая 2021
Роман! Спасибо большое за такую мощнейшую серию статей (порекомендовал ее всем знакомым джунам и стажерам)! Очень давно подписался на ваш гитхаб и всё никак не выдавалось время покопаться, что вы тут такого наворотили))) В итоге за 3 дня все прочитал, попробовал и запустил всё что предполагалось к запуску)) Все очень познавательно, круто и доходчиво)!