Compare commits
7 Commits
bc5b4cf995
...
3703a1973c
Author | SHA1 | Date | |
---|---|---|---|
3703a1973c | |||
3c51d1119c | |||
27a135f730 | |||
69df5c22d8 | |||
e6b77e0164 | |||
93a0ad49f5 | |||
9d85892979 |
@ -60,8 +60,9 @@ public class MergeRequestsServiceImpl implements MergeRequestsService {
|
||||
public MergeRequest create(@NonNull MergeRequest mergeRequest) {
|
||||
final boolean botUserReviewer = isBotUserReviewer(mergeRequest);
|
||||
final boolean botUserAssignee = isBotUserAssigneeAndNotAuthor(mergeRequest);
|
||||
final boolean botUserAuthor = personInformation.getId().equals(mergeRequest.getAuthor().getId());
|
||||
|
||||
mergeRequest.setNotification(botUserReviewer || botUserAssignee);
|
||||
mergeRequest.setNotification(botUserReviewer || botUserAssignee || botUserAuthor);
|
||||
mergeRequest.setUserAssignee(botUserAssignee);
|
||||
mergeRequest.setUserReviewer(botUserReviewer);
|
||||
|
||||
|
@ -1,5 +1,9 @@
|
||||
# Общая архитектура
|
||||
|
||||
!!! warning
|
||||
|
||||
Данный раздел находится в процессе написания
|
||||
|
||||
Поддерживается два режима работы: периодические запуски на ПК и запуск на сервере в режиме 24/7.
|
||||
|
||||
## Схема БД { id="schema-database" }
|
||||
@ -22,7 +26,4 @@
|
||||
|
||||
Не везде имется возможность установить произвольные Webhook. Например, вряд ли кто-то позволит вам установить Webhook из корпоративного GitLab во внешнюю сеть. Переодическое обращение к GitLab API можно выполнять прямо с рабочей машины.
|
||||
|
||||
В будущем планирую добавить поддержку и Webhook.
|
||||
|
||||
## Отслеживание репозиториев
|
||||
Ключевым (рутовым) элементом являются репозитории.
|
||||
В будущем планирую добавить поддержку и Webhook.
|
BIN
documentation/ru/docs/features/img/gitlab-thread-answer.png
Normal file
BIN
documentation/ru/docs/features/img/gitlab-thread-answer.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 86 KiB |
BIN
documentation/ru/docs/features/img/telegram-thread-answer.png
Normal file
BIN
documentation/ru/docs/features/img/telegram-thread-answer.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 244 KiB |
30
documentation/ru/docs/features/interaction-bot.md
Normal file
30
documentation/ru/docs/features/interaction-bot.md
Normal file
@ -0,0 +1,30 @@
|
||||
# Взаимодействие с ботом
|
||||
|
||||
Главное меню бота вызывается с помощью команды `/start`
|
||||
|
||||
!!! note
|
||||
|
||||
На данный момент главное меню содержит только пункт: "Добавить новый репозиторий". В будущем функционал существенно расширится.
|
||||
|
||||
## Добавить новый репозиторий
|
||||
|
||||
Если во время первичной инициализации не стали добавлять все доступные репозитории, или не включили автоматическое отслеживание появления новых репозиториев, то вы можете добавлять репозитории самостоятельно.
|
||||
|
||||
Для этого:
|
||||
|
||||
* Вызовите главное меню командой `/start`
|
||||
* Выберете пункт `Add repository`, который позволяет поставить репозиторий на отслеживание
|
||||
* Скопируйте ссылку на репозиторий из адресной строки браузера и отправьте ее боту.
|
||||
* Если все пройдет успешно, то вы получите уведомление `👍 Projects added successfully!`
|
||||
|
||||
Это позволит вам получать все уведомления репозитория и его сущностей, таких как MR, пайплайны и так далее.
|
||||
|
||||
!!! tip "Массовое добавление"
|
||||
|
||||
Если вы хотите добавить множество репозиториев разом, то вы можете это сделать. Сформируйте сообщение с несколькими ссылками, где каждая ссылка на репозиторий будет на новой строке:
|
||||
|
||||
```
|
||||
https://gitlab.com/groupname/projectname1/repositoryname1
|
||||
https://gitlab.com/groupname/projectname1/repositoryname2
|
||||
https://gitlab.com/groupname/projectname2/repositoryname3
|
||||
```
|
@ -1,6 +1,16 @@
|
||||
# Взаимодействие с GitLab
|
||||
|
||||
## Добавить новый репозиторий
|
||||
|
||||
## Ответ в треде
|
||||
Допустим вас упомянули в обсуждении, сразу знаете что ответить? Не тратьте время, отвечайте прямо из телеграм. Для этого просто ответьте на сообщение и напишите ваш комментарий.
|
||||
Допустим вас упомянули в обсуждении, сразу знаете что ответить? Не тратьте время, отвечайте прямо из телеграм. Для этого просто ответьте на сообщение и напишите ваш комментарий.
|
||||
|
||||
Просто ответьте на сообщение уведомления:
|
||||
|
||||
<figure markdown>
|
||||
![telegram-thread-answer.png](img/telegram-thread-answer.png){ loading=lazy align=left width="700" }
|
||||
</figure>
|
||||
|
||||
Так это будет выглядеть в GitLab:
|
||||
|
||||
<figure markdown>
|
||||
![gitlab-thread-answer.png](img/gitlab-thread-answer.png){ loading=lazy align=left width="700" }
|
||||
</figure>
|
@ -190,14 +190,20 @@
|
||||
## Уведомление о пайплайне
|
||||
Полезно сразу узнавать, что сборка закончилась успешно или упала.
|
||||
|
||||
!!! question "Я буду получать уведомление обо всех пайплайнах?"
|
||||
|
||||
Нет. Вы будете получать уведомление только о тех пайплайнах, в которых выступили инициатором. Напримр, запустили CI руками из GitLab, или нажали кнопку Merge в MR.
|
||||
|
||||
<figure markdown>
|
||||
![notify about new pipeline](img/notify-new-pipeline.png){ loading=lazy width="500" }
|
||||
</figure>
|
||||
|
||||
Уведомление содержит:
|
||||
|
||||
- Предыдущий статус пайплайна и новый
|
||||
-
|
||||
- Предыдущий статус пайплайна и новый статус
|
||||
- Номер пайплайна
|
||||
- Название репозитория
|
||||
- Ветка, для которой запускалась сборка
|
||||
|
||||
Доступные быстрые действия:
|
||||
|
||||
|
@ -2,10 +2,13 @@
|
||||
|
||||
Есть несколько способов запустить бота-помощника. Бот был спроектирован таким образом, чтобы работать локально на вашем ПК, но вы можете запустить его на сервере в режиме 24/4.
|
||||
|
||||
Первым делом вам предложат ввести имя для бота.
|
||||
Для начала выполните эти действия:
|
||||
|
||||
1. [Создание бота в Telegram](creating-telegram-bot.md)
|
||||
2. [Получение персонального токена в GitLab](create-gitlab-token.md)
|
||||
|
||||
## Конфигурация
|
||||
Несмотря на то, какой вариант запуска вы вберете, необходимо указать следующие переменные среды:
|
||||
Вне зависимости от того, какой способ вы выберете, необходимо будет указать данные переменные среды:
|
||||
|
||||
* `TELEGRAM_BOT_TOKEN` — токен, который вы получили при [создании бота](creating-telegram-bot.md).
|
||||
* `TELEGRAM_BOT_USERNAME` — название, которое вы дали боту. Оканчивается на bot.
|
||||
@ -98,22 +101,4 @@ docker run --name gitlab-notify \
|
||||
--env DATASOURCE_USERNAME=postgres \
|
||||
--env DATASOURCE_PASSWORD=value \
|
||||
--network="host" upagge/gitlab-telegram-notify:latest
|
||||
```
|
||||
|
||||
## Запуск в IDEA
|
||||
|
||||
## Запуск JAR релиза
|
||||
|
||||
Скачать актуальный jar-файл всегда можно на странице релизов GitHub.
|
||||
|
||||
``` shell
|
||||
java -DTELEGRAM_BOT_USERNAME=value \
|
||||
-DTELEGRAM_BOT_TOKEN=value \
|
||||
-DTELEGRAM_PERSON_ID=value \
|
||||
-DDATASOURCE_URL=value \
|
||||
-DDATASOURCE_PASSWORD=value \
|
||||
-DDATASOURCE_USERNAME=value \
|
||||
-DGITLAB_PERSONAL_TOKEN=value \
|
||||
-DGITLAB_URL=value \
|
||||
-jar gitlab-notification.jar
|
||||
```
|
@ -1 +1,19 @@
|
||||
# Создание токена GitLab
|
||||
# Создание токена GitLab
|
||||
|
||||
Для взаимодействия с GitLab необходим персональный токен доступа.
|
||||
|
||||
Чтобы его получить перейдите по адресу: [https://gitlab.com/-/profile/personal_access_tokens](https://gitlab.com/-/profile/personal_access_tokens)
|
||||
|
||||
!!! tip "Корпоративный GitLab"
|
||||
|
||||
Замените `https://gitlab.com/` на адрес своего GitLab, если вы используете self-host решение.
|
||||
|
||||
* Придумайте название токену, например, `GitLab Notify`.
|
||||
* Выдайте права на чтение - `read_api`
|
||||
|
||||
!!! info "Уровень разрешений"
|
||||
|
||||
Выберете уровень разрешения `api`, если планируете пользоваться такими функциями, как: [ответ в треде](../features/interaction-gitlab.md#ответ-в-треде)
|
||||
|
||||
* Нажмите кнопку `Create personal access token`.
|
||||
* Сохраните полученный токен.
|
@ -6,7 +6,7 @@
|
||||
|
||||
Первые два пункта диалогового меню будет про название бота. Учтите, что это называние общедоступно и случайноы пользователь Telegram сможет найти вашего бота.
|
||||
|
||||
Не переживайте, приложение имеет встроенную защиту от несанкционарованного доступа к боту. Но не смотря на это, ==не рекомендуется использовать в названии бота название организации, или вашу фамилию. Лучше использовать случайное имя.==
|
||||
Не переживайте, приложение имеет встроенную [защиту от несанкционарованного доступа к боту](../privacy/index.md#access-control). Но не смотря на это, ==не рекомендуется использовать в названии бота название организации, или вашу фамилию. Лучше использовать случайное имя.==
|
||||
|
||||
После регистрации вам будет выдан токен доступа. Он будет использоваться при запуске ассистента.
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
# Инициализация бота
|
||||
|
||||
В данном видео демонстрируется процесс первичной настройки бота, чтобы вы знали, чего ожидать:
|
||||
|
||||
<video controls>
|
||||
<source id="mp4" src="../mp4/init-start.mp4" type="video/mp4">
|
||||
</videos>
|
@ -1,10 +0,0 @@
|
||||
# Hello Mutiny!
|
||||
|
||||
Once you made Mutiny available to your classpath, you can start writing code.
|
||||
Let's start with this simple program:
|
||||
|
||||
## Test
|
||||
|
||||
### Test 2
|
||||
|
||||
## Test 4
|
@ -7,9 +7,9 @@ hide:
|
||||
---
|
||||
|
||||
# GitLab Notification – Персональный Telegram бот для GitLab
|
||||
Персональный ассистент для упрощения работы с GitLab. Получайте персональные уведомления о событиях в GitLab: новый MR на ревью, новый ответ в треде и многое другое.
|
||||
Персональный ассистент призван упростить работу с GitLab. Получайте персональные уведомления о событиях в GitLab, ничего не пропустите и не забудьте.
|
||||
|
||||
Бота можно запустить как для облачного GitLab, так и для Self-host решений.
|
||||
Бота-ассистента можно запустить как для облачного GitLab, так и для Self-host решений.
|
||||
|
||||
!!! info "GodFather Telegram"
|
||||
|
||||
|
@ -4,13 +4,17 @@
|
||||
|
||||
!!! tip "Доверие"
|
||||
|
||||
Вы не должны верить мне на слово. Вы можете [самостоятельно изучить код, он открыт и не сложен.](https://github.com/uPagge/gitlab-notification) После проверки можно самостоятельно собрать jar и упаковать его в Docker. Либо запускать код прямо из Idea.
|
||||
Вы не должны верить мне на слово. Вы можете [самостоятельно изучить код, он открыт и не сложен.](https://github.com/uPagge/gitlab-notification) После проверки можно самостоятельно собрать jar и [упаковать его в Docker](https://github.com/uPagge/gitlab-notification/blob/master/Dockerfile). Либо запускать код прямо из Idea.
|
||||
|
||||
## Защита токена GitLab
|
||||
Для работы ассистента необходим персональный токен GitLab. Он указывается в переменные среды и нигде дополнительно не дублируется. Таким образом токен не попадает в Telegram и хранится только у вас на компьютере и в контейнере приложения.
|
||||
Для работы ассистента необходим персональный токен GitLab. Он указывается в переменные среды и нигде дополнительно не хранится и не передается. Таким образом токен не попадает в Telegram и хранится только у вас на компьютере и в контейнере приложения.
|
||||
|
||||
Токен используется только при обращении к указанному GitLab, и только для выполнения описанных в документации возможностей. Никакой скрытой работы не выполняется, по возможности обо всех взаимодействиях с GitLab дополнительно сообщается во время настройки.
|
||||
|
||||
!!! question "А что, если ты заполучишь токен?"
|
||||
|
||||
Ну начнем с того, что мне ваш токен даром не нужен. Изучайте код, если мне не верите, или не пользуйтесь моим ботом. Эта страница призвана ответить на возникающие вопросы безопаности решения, не более этого. Дополнительно убеждать никого не собираюсь.
|
||||
|
||||
## Уровни конфиденциальности
|
||||
|
||||
Некоторые уведомления могут содержать множество чувствительной информации. Например, уведомления о новых сообщениях в тредах. Возможно вы не захотите раскрывать столько информации о вашей разработке телеграму, ведь через него идет получение уведомлений. Специально для таких случаев предусмотрены уровни конфиденциальности разных типов уведомлений.
|
||||
@ -22,7 +26,7 @@
|
||||
|
||||
Прочитать подробнее можно в разделе: [Работа с базой данных](../architecture/concept.md#schema-database)
|
||||
|
||||
## Несанкционированный доступ
|
||||
## Несанкционированный доступ { id="access-control" }
|
||||
==Все боты в Telegram являются публичными.== Это значит, что ваш бот может быть найден через поиск в Telegram. Поэтому ==не рекомендуется давать название боту, которое может раскрыть его предназначение.==
|
||||
|
||||
Даже если кто-то случайно напишет вашему боту ничего не случится. ==В боте встроена проверка прав доступа.== Вот как она работает:
|
||||
|
@ -10,7 +10,8 @@ nav:
|
||||
- Конфиденциальность: privacy/index.md
|
||||
- Возможности:
|
||||
- Уведомления: features/notify.md
|
||||
- Быстрые действия: features/interaction-gitlab.md
|
||||
- Взаимодействие с GitLab: features/interaction-gitlab.md
|
||||
- features/interaction-bot.md
|
||||
- Быстрый старт:
|
||||
- getting-started/create-gitlab-token.md
|
||||
- getting-started/creating-telegram-bot.md
|
||||
|
@ -32,9 +32,9 @@ public class SchedulerService {
|
||||
private final MergeRequestsService mergeRequestsService;
|
||||
private final DiscussionService discussionService;
|
||||
|
||||
@Scheduled(cron = "0 */1 * * * *")
|
||||
public void newMergeRequest() {
|
||||
log.info("Запуск процесса обновления данных c GitLab");
|
||||
@Scheduled(cron = "0 0 */1 * * *")
|
||||
public void newProjects() {
|
||||
log.info("Запуск процесса получение новых репозиториев c GitLab");
|
||||
if (!settingService.isFirstStart()) {
|
||||
if (settingService.isOwnerProjectScan()) {
|
||||
projectParser.parseAllProjectOwner();
|
||||
@ -42,8 +42,24 @@ public class SchedulerService {
|
||||
if (settingService.isPrivateProjectScan()) {
|
||||
projectParser.parseAllPrivateProject();
|
||||
}
|
||||
mergeRequestParser.parsingOldMergeRequest();
|
||||
}
|
||||
log.info("Конец процесса получение новых репозиториев c GitLab");
|
||||
}
|
||||
|
||||
@Scheduled(cron = "0 */15 * * * *")
|
||||
public void newMergeRequests() {
|
||||
log.info("Запуск процесса получение новых MR c GitLab");
|
||||
if (!settingService.isFirstStart()) {
|
||||
mergeRequestParser.parsingNewMergeRequest();
|
||||
}
|
||||
log.info("Конец процесса получение новых MR c GitLab");
|
||||
}
|
||||
|
||||
@Scheduled(cron = "0 */1 * * * *")
|
||||
public void newMergeRequest() {
|
||||
log.info("Запуск процесса обновления данных c GitLab");
|
||||
if (!settingService.isFirstStart()) {
|
||||
mergeRequestParser.parsingOldMergeRequest();
|
||||
pipelineParser.scanOldPipeline();
|
||||
pipelineParser.scanNewPipeline();
|
||||
discussionParser.scanOldDiscussions();
|
||||
|
2
pom.xml
2
pom.xml
@ -44,7 +44,7 @@
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
|
||||
<godfather.telegram.version>0.0.50</godfather.telegram.version>
|
||||
<godfather.telegram.version>0.0.51</godfather.telegram.version>
|
||||
<haiti.utils.version>2.7.2</haiti.utils.version>
|
||||
<haiti.utils.fields.version>0.0.11</haiti.utils.fields.version>
|
||||
<haiti.filter.version>0.0.5</haiti.filter.version>
|
||||
|
@ -1,12 +1,12 @@
|
||||
package dev.struchkov.bot.gitlab.telegram.service;
|
||||
|
||||
import dev.struchkov.bot.gitlab.context.service.AppSettingService;
|
||||
import dev.struchkov.bot.gitlab.context.service.NotifyService;
|
||||
import dev.struchkov.bot.gitlab.context.utils.Icons;
|
||||
import dev.struchkov.bot.gitlab.core.config.properties.AppProperty;
|
||||
import dev.struchkov.bot.gitlab.core.config.properties.PersonProperty;
|
||||
import dev.struchkov.godfather.simple.domain.BoxAnswer;
|
||||
import dev.struchkov.godfather.simple.domain.SentBox;
|
||||
import dev.struchkov.godfather.telegram.domain.ClientBotCommand;
|
||||
import dev.struchkov.godfather.telegram.simple.context.service.TelegramSending;
|
||||
import dev.struchkov.godfather.telegram.simple.context.service.TelegramService;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
@ -17,6 +17,7 @@ import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@ -29,8 +30,8 @@ import static dev.struchkov.haiti.utils.Checker.checkNotBlank;
|
||||
/**
|
||||
* @author upagge 19.01.2021
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class StartNotify {
|
||||
|
||||
@ -39,8 +40,6 @@ public class StartNotify {
|
||||
private final TelegramSending sending;
|
||||
private final TelegramService telegramService;
|
||||
|
||||
private final NotifyService notifyService;
|
||||
|
||||
private final AppProperty appProperty;
|
||||
private final AppSettingService settingService;
|
||||
private final PersonProperty personProperty;
|
||||
@ -66,7 +65,6 @@ public class StartNotify {
|
||||
.payload(DISABLE_WEB_PAGE_PREVIEW, true)
|
||||
.build();
|
||||
sending.send(boxAnswer);
|
||||
|
||||
sendNotice();
|
||||
}
|
||||
registrationForStatistic();
|
||||
@ -102,7 +100,7 @@ public class StartNotify {
|
||||
final BoxAnswer notice = BoxAnswer.builder()
|
||||
.message(noticeMessage)
|
||||
.recipientPersonId(personProperty.getTelegramId())
|
||||
// .payload(DISABLE_WEB_PAGE_PREVIEW, true)
|
||||
.payload(DISABLE_WEB_PAGE_PREVIEW, true)
|
||||
.build();
|
||||
final Optional<SentBox> optSentBox = sending.send(notice);
|
||||
if (optSentBox.isPresent()) {
|
||||
@ -117,17 +115,12 @@ public class StartNotify {
|
||||
}
|
||||
}
|
||||
|
||||
// @PostConstruct
|
||||
// public void demo() {
|
||||
// notifyService.send(
|
||||
// DiscussionNewNotify.builder()
|
||||
// .authorName("Ivan Ivanov")
|
||||
// .threadId("1")
|
||||
// .discussionMessage("Кажется здесь можно сделать лучше.")
|
||||
// .mergeRequestName("Merge Request Name")
|
||||
// .url("https://ya.ru")
|
||||
// .build()
|
||||
// );
|
||||
// }
|
||||
@PostConstruct
|
||||
public void createCommands() {
|
||||
final ClientBotCommand clientBotCommand = new ClientBotCommand();
|
||||
clientBotCommand.setDescription("Open general menu");
|
||||
clientBotCommand.setKey("start");
|
||||
telegramService.addCommand(List.of(clientBotCommand));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -521,11 +521,9 @@ public class InitSettingFlow implements UnitConfiguration {
|
||||
final ButtonClickAttachment buttonClick = Attachments.findFirstButtonClick(mail.getAttachments()).orElseThrow();
|
||||
final DiscussionLevel level = DiscussionLevel.valueOf(buttonClick.getRawCallBackData().toUpperCase());
|
||||
settingService.setDiscussionLevel(level);
|
||||
replaceBoxAnswer("\uD83D\uDC4D you have successfully set the privacy level for threads");
|
||||
return replaceBoxAnswer("\uD83D\uDC4D you have successfully set the privacy level for threads");
|
||||
})
|
||||
.callBack(
|
||||
sentBox -> scheduledExecutorService.schedule(() -> sending.deleteMessage(sentBox.getPersonId(), sentBox.getMessageId()), 6, TimeUnit.SECONDS)
|
||||
)
|
||||
.callBack(sentBox -> scheduledExecutorService.schedule(() -> sending.deleteMessage(sentBox.getPersonId(), sentBox.getMessageId()), 6, TimeUnit.SECONDS))
|
||||
.next(endSetting)
|
||||
.build();
|
||||
}
|
||||
@ -541,8 +539,8 @@ public class InitSettingFlow implements UnitConfiguration {
|
||||
return BoxAnswer.builder()
|
||||
.message(
|
||||
"""
|
||||
Configuration completed successfully
|
||||
Developer: [Struchkov.Dev](https://mark.struchkov.dev)
|
||||
🎉 Configuration completed successfully
|
||||
🧑💻 Developer: [Struchkov.Dev](https://mark.struchkov.dev)
|
||||
"""
|
||||
).keyBoard(inlineKeyBoard(simpleButton("Open Menu", "/start")))
|
||||
.payload(DISABLE_WEB_PAGE_PREVIEW, true)
|
||||
|
@ -14,7 +14,6 @@ public final class UnitName {
|
||||
public static final String ANSWER_NOTE = "answerNote";
|
||||
public static final String TEXT_PARSER_PRIVATE_PROJECT = "textParserPrivateProject";
|
||||
public static final String CHECK_PARSER_PRIVATE_PROJECT_YES = "checkParserPrivateProject";
|
||||
public static final String TEXT_PARSE_OWNER_PROJECT = "textParseOwnerProject";
|
||||
public static final String CHECK_PARSE_OWNER_PROJECT_YES = "checkParseOwnerProject";
|
||||
public static final String END_SETTING = "endSetting";
|
||||
public static final String ACCESS_ERROR = "ACCESS_ERROR";
|
||||
@ -24,7 +23,6 @@ public final class UnitName {
|
||||
public static final String AUTO_PARSE_PRIVATE_PROJECT = "AUTO_PARSE_PRIVATE_PROJECT";
|
||||
public static final String AUTO_PARSE_OWNER_PROJECT = "AUTO_PARSE_PUBLIC_PROJECT";
|
||||
public static final String TEXT_AUTO_PARSE_OWNER_PROJECT = "TEXT_AUTO_PARSE_OWNER_PROJECT";
|
||||
public static final String GUIDE_START = "GUIDE_START";
|
||||
public static final String TEXT_PARSER_OWNER_PROJECT = "TEXT_PARSER_OWNER_PROJECT";
|
||||
|
||||
// команды
|
||||
|
Loading…
Reference in New Issue
Block a user