В этой статье (по крайней мере в первой части) я расскажу, как создать простого (в архитектуре и функционале) чат-бота, работающего в сервисе VK.
Сама разработка бота будет состоять из трех частей, включающих:
Итак, приступим к созданию основы бота
Для бота в социальных сетях лучше всегда иметь расширяемый и гибкий код. А для этого нужно придумать хорошую архитектуру. Предоставленная здесь архитектура имеет ту расширяемость и гибкость в достаточном количестве для не высоконагруженного простого чат-бота, при том, что она очень проста для реализации, которая как раз подойдет для начинающих разработчиков ботов.
В чем же её простота?
А простота заключается в том, что фундамент, на котором он построен не требует знаний Java Core. Достаточно будет знать концепции ООП, практическое использование коллекций и знание стандартных функций.
Но скажу сразу, что это относится лишь к фундаменту, а сами команды могут отличаться сильно, в зависимости от ваших потребностей. Например, чтобы получать погоду (а именно парсить) необходимо будет знание таких библиотек как jsoup или при работе с сериализацией знание gson.
Все зависит от ваших потребностей, а создание команд рассмотрим во второй части этой серии статей.
Как получать запросы пользователя?
Для этого VK представляет нам SDK для Java, который позволяет через использование библиотеки Java получать сообщения, отправляемые в группу.
Необходимо будет настроить окружение через maven или gradle, добавив зависимости с официальной документации Java SDK для VK API.
Я в своем проекте использовал maven. Файл pom.xml вы сможете найти в репозитории проекта.
Рекомендую для ознакомления прочитать эту статью (
Необходимые зависимости: https://vk.com/dev/Java_SDK
Теперь настроим бота
Бот представляет с собой группу с некоторыми дополнительными разрешениями. Чтобы мы могли читать поступающие сообщения необходимо его настроить, а именно:
Еще дополнительно нужно будет включить «Возможности ботов» в разделе “Сообщения”
Итак, окружение и группа с ботом готовы. Приступим к программированию.
Часто бывает так, что люди пишут все свои access token или пароли прямо в исходниках и заливают их, например, на github. Что может привести к неприятным последствиям, если этим воспользуются токсичные люди.
Поэтому, хорошей практикой бывает решение создать файл конфигурации. Это может обычный txt файл, но в силу того, что могут возникнуть осложнения с его чтением – лучше воспользоваться готовым решением, а именно файл properties.
С помощью таких файлов мы можем получать значения в виде карт (map или же словари). Таким образом, можно хранить свои файлы конфигураций, а потом добавить их в .gitignore, чтобы они не высвечивались в свободном доступе, если репозиторий не приватный.
Для тех, кто уже с этим более или менее знаком, может возникнуть вопрос: а что делать, если мы хотим через git залить его куда-то на сервер и запустить оттуда? Откуда брать конфигурации? Для таких целей можно использовать переменную окружения, но об этом поговорим в третьей части.
Создадим файл vkconfig.properties и добавим туда значения:
Теперь нам будет необходимо получить доступ к группе через эти параметры. Чтобы иметь доступ к возможностям, создадим класс VKCore, где будет производить авторизацию нашего приложения:
Рассмотрим поля и конструктор этого класса:
Я не хочу сейчас подробно заставлять вникать вас в этот код, так как обычно это только путает. Со временем вы сами сможете подробнее узнать о значении каждого из этих полей. Но если коротко, то:
GroupActor actor – это по сути и есть ваша группа, с помощью которого вы сможете отправлять сообщения от имени группы.
VkApiClient vk – это интерфейс для взаимодействия с VK API с помощью запросов.
Грубо говоря, вы будете вызывать методы VKApiClient и передавать туда в качестве аргумента ваш GroupActor, чтобы идентифицировать группу.
Еще одним немаловажным является значения полей ts и maxMsgId:
ts – это идентификатор сообщения с которого нужно начинать обрабатывать сообщения. То есть, если значение ts будет константой, то VK API будет отправлять вам одни и те же сообщения. Поэтому после получения любого сообщения, необходимо его менять.
maxMsgId – сначала этот параметр может показаться ненужным, так как без него бот будет работать, но не долго, а именно как говорят в документации около суток, но лично у меня бот упал уже после 12 часов работы. К тому же выйдет непонятная ошибка internal server error, что означает внутреннюю ошибку сервера. Эта ошибка может возникнуть если ts – очень старый (более суток) и не передается параметр max_msg_id, который хранит значение максимального идентификатора сообщения среди уже имеющихся в локальной копии.
В конструкторе класса настроим соединение и создадим объект GroupActor с помощью наших access token и groupId, находящихся в файле конфигурации.
Теперь добавим метод getMessage – с помощью которого мы будем получать сообщения:
Здесь все довольно тривиально за исключением, возможно, объекта Message.
Что за файл Message и откуда он берется?
Когда мы делаем запрос в VK API, то ответ поступает в виде JSON файла. И встает вопрос: как его превратить в java объект (POJO). Для этого существуют специальные библиотеки, такие как json-simple или gson. Конкретно в случае с Java VK SDK используется gson для десериализации в java-объект.
Если рассмотреть исходный код Message:
То можно заметить, что это лишь некая структура данных с геттерами внизу. Чтобы получше разобраться в этом, рекомендую прочитать как работает библиотека gson (
Если коротко, то это просто превращение JSON-данных в java-объект с помощью специальных методов.
Теперь, когда мы настроили VKCore можем приступать непосредственно к обработке и созданию запросов.
Для этого создадим класс VKserver:
Здесь тоже ничего сложного нет – делаем запрос каждые 300 мс и если есть не пустое сообщение, то отправляем его в класс Messenger в качестве аргумента конструктора.
Сразу же посмотрим класс Messenger:
Этот класс нужен, чтобы обрабатывать запросы многопоточно. Здесь минимальная реализация, чтобы было легче понять. Для действительно функционального бота – здесь еще можно много чего добавить.
И здесь мы опять встречаем непонятный класс Commander. Что же это за класс?
Это будет ядром нашего бота. Грубо говоря, он будет обрабатывать сообщения пользователя и отправлять ему какой-либо ответ.
Состоит он не только из самого себя. В его число также входят:
Разберем по порядку каждый из них
Для тех, кто знает полиморфизм – понять будет легко, а те, кто с этим не знаком, прошу погрузиться в пучину ООП, и, если вы там не пропадёте – вернуться уже с новыми знаниями.
Этот класс, как ни странно, нужен для абстракции, чтобы мы могли писать методы, работающие для всех классов команд и определить для них общий метод инициализации.
Лучше всего будет посмотреть исходники:
В конструкторе мы определяем имя команды, по которому мы будем её вызывать, а также объявляем метод exec, который в качестве аргумента получает рассмотренный ранее объект Message. В методе exec мы будем писать логику команды, то есть фактическую её реализацию.
В качестве примера создадим команду Unknown:
Теперь рассмотрим оставшиеся два вспомогательных класса:
CommandDeterminant – определитель команд:
Он производит выборку команды из коллекции команд, по первому ключевому слову. Иначе говоря, если во вводе пользователя первым словом будет имя нашей команды, которую мы указали в конструкторе Command, то метод getCommand() – возвратит именно ту команду.
CommandManager – менеджер команд, где будем хранить объекты команд
Здесь мы добавляем две команды Unknown, которую мы создали ранее, и Weather – команда вывода погоды, её мы разберем во второй части.
Также необходимо обратить внимание на метод getCommands() – который возвращает коллекцию наших команд.
И наконец, перейдем к основному классу Commander:
Что мы здесь делаем?
Вызываем метод getCommand у CommandDeterminant, в котором в качестве аргумента передаем команды из CommandManager и сообщение Message. Далее этот метод делает выборку и возвращает объект Command, у которого мы вызываем метод exec с аргументом Message.
Давайте проверим как все это работает, запустив класс VKServer.
Как мы видим, когда мы отправляем сообщение, которое бот не может определить - ничего не происходит, но если мы вводим известную команду, вроде weather – он дает нам ответ. Давайте сделаем так, чтобы если команда не определена – бот об этом сообщал.
Для отправки сообщений воспользуемся классом VKManager:
Не следует углубляться во все это, главное запомнить метод sendMessage, с помощью которого мы будем отправлять сообщение. msg – это сообщение, которое мы отправим, а peerId – идентификатор пользователя в вк, которому мы будем отправлять сообщение (возьмем его из объекта Message)
Добавим его в Unknown:
Проверим:
Если сейчас все не очень понятно – это нормально, но думаю для тех, кто знаком с Java Core здесь все будет тривиально. Я же попытался максимально упростить архитектуру и реализацию бота.
В следующих частях разберем создание команд, тогда, надеюсь, все станет понятнее.
Проект будет выложен на GitHub, чтобы вы могли его клонировать и изучить самостоятельно, ну и дополнить его своим функционалом.
После окончания всей серии статей, возможно (если будет время, и я не поленюсь), создам шаблонный проект с минимальной реализацией, но масштабируемый, клонировав который, вы сможете создать своего бота, добавляя свои команды. А пока на этом все.
Ссылка на github:
Сама разработка бота будет состоять из трех частей, включающих:
- Создание фундамента
- Его расширение командами
- Деплой бота в сервисе heroku и подключение БД
Итак, приступим к созданию основы бота
Для бота в социальных сетях лучше всегда иметь расширяемый и гибкий код. А для этого нужно придумать хорошую архитектуру. Предоставленная здесь архитектура имеет ту расширяемость и гибкость в достаточном количестве для не высоконагруженного простого чат-бота, при том, что она очень проста для реализации, которая как раз подойдет для начинающих разработчиков ботов.
В чем же её простота?
А простота заключается в том, что фундамент, на котором он построен не требует знаний Java Core. Достаточно будет знать концепции ООП, практическое использование коллекций и знание стандартных функций.
Но скажу сразу, что это относится лишь к фундаменту, а сами команды могут отличаться сильно, в зависимости от ваших потребностей. Например, чтобы получать погоду (а именно парсить) необходимо будет знание таких библиотек как jsoup или при работе с сериализацией знание gson.
Все зависит от ваших потребностей, а создание команд рассмотрим во второй части этой серии статей.
Как получать запросы пользователя?
Для этого VK представляет нам SDK для Java, который позволяет через использование библиотеки Java получать сообщения, отправляемые в группу.
Необходимо будет настроить окружение через maven или gradle, добавив зависимости с официальной документации Java SDK для VK API.
Я в своем проекте использовал maven. Файл pom.xml вы сможете найти в репозитории проекта.
Рекомендую для ознакомления прочитать эту статью (
Пожалуйста, авторизуйтесь для просмотра ссылки.
)Необходимые зависимости: https://vk.com/dev/Java_SDK
Теперь настроим бота
Бот представляет с собой группу с некоторыми дополнительными разрешениями. Чтобы мы могли читать поступающие сообщения необходимо его настроить, а именно:
- Получить ключ доступа ( access token)
- Включить Long Poll API
- Добавить обработку событий входящих сообщений
Еще дополнительно нужно будет включить «Возможности ботов» в разделе “Сообщения”
Итак, окружение и группа с ботом готовы. Приступим к программированию.
Часто бывает так, что люди пишут все свои access token или пароли прямо в исходниках и заливают их, например, на github. Что может привести к неприятным последствиям, если этим воспользуются токсичные люди.
Поэтому, хорошей практикой бывает решение создать файл конфигурации. Это может обычный txt файл, но в силу того, что могут возникнуть осложнения с его чтением – лучше воспользоваться готовым решением, а именно файл properties.
С помощью таких файлов мы можем получать значения в виде карт (map или же словари). Таким образом, можно хранить свои файлы конфигураций, а потом добавить их в .gitignore, чтобы они не высвечивались в свободном доступе, если репозиторий не приватный.
Для тех, кто уже с этим более или менее знаком, может возникнуть вопрос: а что делать, если мы хотим через git залить его куда-то на сервер и запустить оттуда? Откуда брать конфигурации? Для таких целей можно использовать переменную окружения, но об этом поговорим в третьей части.
Создадим файл vkconfig.properties и добавим туда значения:
Код:
access_token = ag3t3agagfsdagdshfjjh4w4 (ваш access token)
group_id = 125162 (ИД вашей группы)
Теперь нам будет необходимо получить доступ к группе через эти параметры. Чтобы иметь доступ к возможностям, создадим класс VKCore, где будет производить авторизацию нашего приложения:
Рассмотрим поля и конструктор этого класса:
Код:
public class VKCore {
private VkApiClient vk;
private static int ts;
private GroupActor actor;
private static int maxMsgId = -1;
public VKCore() throws ClientException, ApiException {
TransportClient transportClient = HttpTransportClient.getInstance();
vk = new VkApiClient(transportClient);
// Загрузка конфигураций
Properties prop = new Properties();
int groupId;
String access_token;
try {
prop.load(new FileInputStream("src/main/resources/vkconfig.properties"));
groupId = Integer.valueOf(prop.getProperty("groupId"));
access_token = prop.getProperty("accessToken");
actor = new GroupActor(groupId, access_token);
ts = vk.messages().getLongPollServer(actor).execute().getTs();
} catch (IOException e) {
e.printStackTrace();
System.out.println("Ошибка при загрузке файда конфигурации");
}
}
Я не хочу сейчас подробно заставлять вникать вас в этот код, так как обычно это только путает. Со временем вы сами сможете подробнее узнать о значении каждого из этих полей. Но если коротко, то:
GroupActor actor – это по сути и есть ваша группа, с помощью которого вы сможете отправлять сообщения от имени группы.
VkApiClient vk – это интерфейс для взаимодействия с VK API с помощью запросов.
Грубо говоря, вы будете вызывать методы VKApiClient и передавать туда в качестве аргумента ваш GroupActor, чтобы идентифицировать группу.
Еще одним немаловажным является значения полей ts и maxMsgId:
ts – это идентификатор сообщения с которого нужно начинать обрабатывать сообщения. То есть, если значение ts будет константой, то VK API будет отправлять вам одни и те же сообщения. Поэтому после получения любого сообщения, необходимо его менять.
maxMsgId – сначала этот параметр может показаться ненужным, так как без него бот будет работать, но не долго, а именно как говорят в документации около суток, но лично у меня бот упал уже после 12 часов работы. К тому же выйдет непонятная ошибка internal server error, что означает внутреннюю ошибку сервера. Эта ошибка может возникнуть если ts – очень старый (более суток) и не передается параметр max_msg_id, который хранит значение максимального идентификатора сообщения среди уже имеющихся в локальной копии.
В конструкторе класса настроим соединение и создадим объект GroupActor с помощью наших access token и groupId, находящихся в файле конфигурации.
Теперь добавим метод getMessage – с помощью которого мы будем получать сообщения:
Код:
public Message getMessage() throws ClientException, ApiException {
MessagesGetLongPollHistoryQuery eventsQuery = vk.messages()
.getLongPollHistory(actor)
.ts(ts);
if (maxMsgId > 0){
eventsQuery.maxMsgId(maxMsgId);
}
List<Message> messages = eventsQuery
.execute()
.getMessages()
.getMessages();
if (!messages.isEmpty()){
try {
ts = vk.messages()
.getLongPollServer(actor)
.execute()
.getTs();
} catch (ClientException e) {
e.printStackTrace();
}
}
if (!messages.isEmpty() && !messages.get(0).isOut()) {
/*
messageId - максимально полученный ID, нужен, чтобы не было ошибки 10 internal server error,
который является ограничением в API VK. В случае, если ts слишком старый (больше суток),
а max_msg_id не передан, метод может вернуть ошибку 10 (Internal server error).
*/
int messageId = messages.get(0).getId();
if (messageId > maxMsgId){
maxMsgId = messageId;
}
return messages.get(0);
}
return null;
}
Здесь все довольно тривиально за исключением, возможно, объекта Message.
Что за файл Message и откуда он берется?
Когда мы делаем запрос в VK API, то ответ поступает в виде JSON файла. И встает вопрос: как его превратить в java объект (POJO). Для этого существуют специальные библиотеки, такие как json-simple или gson. Конкретно в случае с Java VK SDK используется gson для десериализации в java-объект.
Если рассмотреть исходный код Message:
Код:
public class Message {
/**
* Message ID
*/
@SerializedName("id")
private Integer id;
/**
* Date when the message has been sent in Unixtime
*/
@SerializedName("date")
private Integer date;
/**
* Date when the message has been updated in Unixtime
*/
@SerializedName("update_time")
private Integer updateTime;
/**
* Information whether the message is outcoming
*/
@SerializedName("out")
private BoolInt out;
То можно заметить, что это лишь некая структура данных с геттерами внизу. Чтобы получше разобраться в этом, рекомендую прочитать как работает библиотека gson (
Пожалуйста, авторизуйтесь для просмотра ссылки.
)Если коротко, то это просто превращение JSON-данных в java-объект с помощью специальных методов.
Теперь, когда мы настроили VKCore можем приступать непосредственно к обработке и созданию запросов.
Для этого создадим класс VKserver:
Код:
public class VKServer {
public static VKCore vkCore;
static {
try {
vkCore = new VKCore();
} catch (ApiException | ClientException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws NullPointerException, ApiException, InterruptedException {
System.out.println("Running server...");
while (true) {
Thread.sleep(300);
try {
Message message = vkCore.getMessage();
if (message != null) {
ExecutorService exec = Executors.newCachedThreadPool();
exec.execute(new Messenger(message));
}
} catch (ClientException e) {
System.out.println("Возникли проблемы");
final int RECONNECT_TIME = 10000;
System.out.println("Повторное соединение через " + RECONNECT_TIME / 1000 + " секунд");
Thread.sleep(RECONNECT_TIME);
}
}
}
}
Здесь тоже ничего сложного нет – делаем запрос каждые 300 мс и если есть не пустое сообщение, то отправляем его в класс Messenger в качестве аргумента конструктора.
Сразу же посмотрим класс Messenger:
Код:
public class Messenger implements Runnable{
private Message message;
public Messenger(Message message){
this.message = message;
}
@Override
public void run() {
Commander.execute(message);
}
}
Этот класс нужен, чтобы обрабатывать запросы многопоточно. Здесь минимальная реализация, чтобы было легче понять. Для действительно функционального бота – здесь еще можно много чего добавить.
И здесь мы опять встречаем непонятный класс Commander. Что же это за класс?
Это будет ядром нашего бота. Грубо говоря, он будет обрабатывать сообщения пользователя и отправлять ему какой-либо ответ.
Состоит он не только из самого себя. В его число также входят:
- Менеджер команд, где мы храним объекты команд
- Определитель команд, где мы делаем выборку по ключевому слову
- И абстрактный класс Command – суперкласс для всех классов команд
Разберем по порядку каждый из них
Для тех, кто знает полиморфизм – понять будет легко, а те, кто с этим не знаком, прошу погрузиться в пучину ООП, и, если вы там не пропадёте – вернуться уже с новыми знаниями.
Этот класс, как ни странно, нужен для абстракции, чтобы мы могли писать методы, работающие для всех классов команд и определить для них общий метод инициализации.
Лучше всего будет посмотреть исходники:
Код:
/**
* Abstract class for all executable classes-commands
* Field {@link #name} identification command,he is called by this name
*
* @author Arthur Kupriyanov
* @version 1.1
*/
public abstract class Command {
public final String name;
public Command(String name){
this.name = name;
}
/**
* Метод, который будет вызываться для исполнения команды
* @param message сообщение пользователя
*/
public abstract void exec(Message message);
/**
* Возвращает строку в формате:<br>
* name: имяКоманды<br>
*
* @return форматированное имя и мод команды
*/
@Override
public String toString() {
return String.format("name: %s",this.name);
}
/**
* Берет хэш-код значащего поля {@link #name}
*
* @return хэш-код команды
*/
@Override
public int hashCode() {
return this.name.hashCode();
}
/**
* Объекты эквивалентны только, если поля <code>{@link #name}</code> равны
* имеют одинаковое значение и объект является классом-наследником {@link Command}
* @param obj сравниваемый объект
* @return {@code true} если объекты эквивалентны; {@code false} если объекты различаются
*/
@Override
public boolean equals(Object obj) {
if (obj instanceof Command){
if (name.equals(((Command) obj).name)){
return true;
}
}
return false;
}
}
В конструкторе мы определяем имя команды, по которому мы будем её вызывать, а также объявляем метод exec, который в качестве аргумента получает рассмотренный ранее объект Message. В методе exec мы будем писать логику команды, то есть фактическую её реализацию.
В качестве примера создадим команду Unknown:
Код:
/**
* @author Arthur Kupriyanov
*/
public class Unknown extends Command {
public Unknown(String name) {
super(name);
}
@Override
public void exec(Message message) {
System.out.println("Unknown command");
}
}
Теперь рассмотрим оставшиеся два вспомогательных класса:
CommandDeterminant – определитель команд:
Код:
/**
* Определяет команду
*
* @author Артур Куприянов
* @version 1.1.0
*/
public class CommandDeterminant {
public static Command getCommand(Collection<Command> commands, Message message) {
String body = message.getBody();
for (Command command : commands
) {
if (command.name.equals(body.split(" ")[0])) {
return command;
}
}
return new Unknown("unknown");
}
}
Он производит выборку команды из коллекции команд, по первому ключевому слову. Иначе говоря, если во вводе пользователя первым словом будет имя нашей команды, которую мы указали в конструкторе Command, то метод getCommand() – возвратит именно ту команду.
CommandManager – менеджер команд, где будем хранить объекты команд
Код:
/**
* @author Arthur Kupriyanov
*/
public class CommandManager {
private static HashSet<Command> commands = new HashSet<>();
static {
commands.add(new Unknown("unknown"));
commands.add(new Weather("weather"));
}
public static HashSet<Command> getCommands(){
return commands;
}
public static void addCommand(Command command) { commands.add(command);}
}
Здесь мы добавляем две команды Unknown, которую мы создали ранее, и Weather – команда вывода погоды, её мы разберем во второй части.
Также необходимо обратить внимание на метод getCommands() – который возвращает коллекцию наших команд.
И наконец, перейдем к основному классу Commander:
Код:
public class Commander {
/**
* Обработка сообщений, получаемых через сервис Вконтакте. Имеет ряд дополнительной информации.
* @param message сообщение (запрос) пользователя
*/
public static void execute(Message message){
CommandDeterminant.getCommand(CommandManager.getCommands(), message).exec(message);
}
}
Что мы здесь делаем?
Вызываем метод getCommand у CommandDeterminant, в котором в качестве аргумента передаем команды из CommandManager и сообщение Message. Далее этот метод делает выборку и возвращает объект Command, у которого мы вызываем метод exec с аргументом Message.
Давайте проверим как все это работает, запустив класс VKServer.
Как мы видим, когда мы отправляем сообщение, которое бот не может определить - ничего не происходит, но если мы вводим известную команду, вроде weather – он дает нам ответ. Давайте сделаем так, чтобы если команда не определена – бот об этом сообщал.
Для отправки сообщений воспользуемся классом VKManager:
Код:
/**
* @author Arthur Kupriyanov
*/
public class VKManager {
public static VKCore vkCore;
static {
try {
vkCore = new VKCore();
} catch (ApiException | ClientException e) {
e.printStackTrace();
}
}
public void sendMessage(String msg, int peerId){
if (msg == null){
System.out.println("null");
return;
}
try {
vkCore.getVk().messages().send(vkCore.getActor()).peerId(peerId).message(msg).execute();
} catch (ApiException | ClientException e) {
e.printStackTrace();
}
}
public MessagesSendQuery getSendQuery(){
return vkCore.getVk().messages().send(vkCore.getActor());
}
/**
* Обращается к VK API и получает объект, описывающий пользователя.
* @param id идентификатор пользователя в VK
* @return {@link UserXtrCounters} информацию о пользователе
* @see UserXtrCounters
*/
public static UserXtrCounters getUserInfo(int id){
try {
return vkCore.getVk().users()
.get(vkCore.getActor())
.userIds(String.valueOf(id))
.execute()
.get(0);
} catch (ApiException | ClientException e) {
e.printStackTrace();
}
return null;
}
}
Не следует углубляться во все это, главное запомнить метод sendMessage, с помощью которого мы будем отправлять сообщение. msg – это сообщение, которое мы отправим, а peerId – идентификатор пользователя в вк, которому мы будем отправлять сообщение (возьмем его из объекта Message)
Добавим его в Unknown:
Код:
/**
* @author Arthur Kupriyanov
*/
public class Unknown extends Command {
public Unknown(String name) {
super(name);
}
@Override
public void exec(Message message) {
new VKManager().sendMessage("Неизвестная команда", message.getUserId());
}
}
Проверим:
Если сейчас все не очень понятно – это нормально, но думаю для тех, кто знаком с Java Core здесь все будет тривиально. Я же попытался максимально упростить архитектуру и реализацию бота.
В следующих частях разберем создание команд, тогда, надеюсь, все станет понятнее.
Проект будет выложен на GitHub, чтобы вы могли его клонировать и изучить самостоятельно, ну и дополнить его своим функционалом.
После окончания всей серии статей, возможно (если будет время, и я не поленюсь), создам шаблонный проект с минимальной реализацией, но масштабируемый, клонировав который, вы сможете создать своего бота, добавляя свои команды. А пока на этом все.
Ссылка на github:
- Этот проект: Пожалуйста, авторизуйтесь для просмотра ссылки.
- Мой профиль: Пожалуйста, авторизуйтесь для просмотра ссылки.