Merge branch 'develop' into feature/delayed_sending

This commit is contained in:
Руслан Дергачев 2023-02-08 11:08:10 +03:00
commit 8d8acb6496
39 changed files with 719 additions and 218 deletions

135
README.md
View File

@ -1,30 +1,57 @@
# Уведомления GitLab в Telegram # Уведомления GitLab в Telegram
Это приложение позволит вам получать уведомления о событиях из GitLab. Этот проект позволяет оперативно получать и настраивать персональные уведомления о событиях из GitLab.
> Статья-документация: https://struchkov.dev/blog/gitlab-telegram-bot/ > Статья о проекте: https://struchkov.dev/blog/gitlab-telegram-bot/
> > Канал в Telegram, в который публикуется информация о разработке: https://t.me/gitlab_notification
> Исходный код: https://github.com/uPagge/gitlab-notification
## Возможности бота > ⚠️На данный момент вся разработка ведется в моем Gitea: https://git.struchkov.dev/Telegram-Bots/gitlab-notification
1. Уведомление о новых MergeRequest > GitHub остается для удобства пользователей проекта, чтобы иметь возможность давать обратную связь по багам и
2. Уведомление о возникновении конфликта в MergeRequest > предложениям.
3. Уведомление о добавлении вас в новый проект
4. Уведомление о смене статуса вашего MergeRequest > ⚠️Идет активная подготовка к выпуску версии 2.0.0, в котором будут исправлены все существующие баги, а также добавлена
5. Уведомление о комментариях, в которых вас упоминают в формате @nickname > поддержка уведомлений Issue.
6. Уведомит о новой задаче в вашем MR. > На данный момент лучше использовать тег develop, вместо latest.
7. Уведомит о закрытии вашей задачи в чужом MR
8. Уведомление о Pipeline ## Основные возможности
1. Уведомление о новых Merge Request.
2. Уведомление о возникновении конфликта в MergeRequest.
3. Уведомление о появлении нового проекта.
4. Уведомление о смене статуса вашего MergeRequest.
5. Уведомление о комментариях, в которых вас упоминают в формате @nickname.
6. Уведомит о новом комментарии-треде в вашем MR.
7. Уведомит о закрытии вашего треда в чужом MR.
8. Уведомление о результате сборки.
## Как запустить ## Как запустить
1. Для начала нужно создать бота, который будет посылать вам уведомления в телеграмм. Делается это в [специальном боте](https://t.me/botfather) 1. Для начала нужно создать бота, который будет посылать вам уведомления. Делается это
2. После создания вы получите токен, сохраните его. Пример: 34534050345:FlfrleflerferfRE-ergerFLREF9ERF-NGjM в [специальном боте](https://t.me/botfather)
3. Теперь необходимо получить персональный токен в вашем gitlab. Достаточно токена на чтение. Сохраните его 2. После создания вы получите токен, сохраните его. Пример: `34534050345:FlfrleflerferfRE-ergerFLREF9ERF-NGjM`
4. Создайте базу данных gitlab_bot. **Обязательно именно такое название** 3. Теперь необходимо получить персональный токен в вашем gitlab. Достаточно токена на чтение.
4. Можно приступать к запуску 4. Можно приступать к запуску используя один из способов ниже.
### Переменные среды
* `TELEGRAM_BOT_TOKEN` -- токен, который вы получили при создание бота.
* `TELEGRAM_BOT_USERNAME` -- название, которое вы дали боту. Пример my_gitlab_bot.
* `GITLAB_PERSONAL_TOKEN` -- ваш персональный токен из GitLab.
* `TELEGRAM_PERSON_ID` -- ваш id в телеграм, можно узнать у [этого бота](https://t.me/myidbot)
* `GITLAB_URL` -- можно указать https://gitlab.com или url на ваш локальный/корпоративный GitLab строго в таком
формате http://localhost:7990.
* `DATASOURCE_URL` -- ссылка на базу данных Postgres, в следующем формате: jdbc:postgresql://localhost:5432/gitlab_bot
* `DATASOURCE_USERNAME` -- пользовать базы данных
* `DATASOURCE_PASSWORD` -- пароль пользователя базы данных
### Запуск
Есть несколько способов запуска. Для удобства я собрал проект в Docker образ, а также подготовил Docker Compose.
#### Docker
Подойдет, если вы не хотите поднимать отдельный контейнер с базой данных
### Пример запуска
``` ```
sudo docker run --name gitlab-notify \ sudo docker run --name gitlab-notify \
--env TELEGRAM_BOT_TOKEN=value \ --env TELEGRAM_BOT_TOKEN=value \
@ -38,15 +65,65 @@ sudo docker run --name gitlab-notify \
--network="host" upagge/gitlab-telegram-notify:latest --network="host" upagge/gitlab-telegram-notify:latest
``` ```
#### Переменные #### Docker Compose
* `TELEGRAM_BOT_TOKEN` -- токен, который вы получили при создание бота. Самый простой способ запуска.
* `TELEGRAM_BOT_USERNAME` -- название, которое вы дали боту. Пример my_gitlab_bot
* `GITLAB_PERSONAL_TOKEN` -- токен, который вы получили в GitLab
* `TELEGRAM_PERSON_ID` -- ваш id в телеграм, можно узнать у [этого бота](https://t.me/myidbot)
* `GITLAB_URL` -- можно указать https://gitlab.com или url на ваш локальный GitLab строго в таком формате http://localhost:7990
* `DATASOURCE_URL` -- ссылка на базу данных Postgres, в следующем формате: jdbc:postgresql://localhost:5432/gitlab_bot
* `DATASOURCE_USERNAME` -- пользовать бд
* `DATASOURCE_PASSWORD` -- пароль от бд
После этого необходимо отправить боту сообщение, чтобы пройти первичную настройку. docker-compose.yml
```yaml
version: '3.8'
services:
gitlab-bot-database:
image: postgres:15.1-alpine
restart: always
hostname: gitlab-bot-database
container_name: gitlab-bot-database
networks:
gitlab-bot:
environment:
POSTGRES_DB: "gitlab_bot"
POSTGRES_USER: "postgres"
POSTGRES_PASSWORD: ${DATASOURCE_PASSWORD}
volumes:
- gitlab-bot-database:/var/lib/postgresql/data/
gitlab-bot:
image: upagge/gitlab-telegram-notify:latest
hostname: gitlab-bot
container_name: gitlab-bot
privileged: true
networks:
gitlab-bot:
depends_on:
- gitlab-bot-database
environment:
TELEGRAM_BOT_TOKEN: ${TELEGRAM_BOT_TOKEN}
TELEGRAM_BOT_USERNAME: ${TELEGRAM_BOT_USERNAME}
GITLAB_PERSONAL_TOKEN: ${GITLAB_PERSONAL_TOKEN}
TELEGRAM_PERSON_ID: ${TELEGRAM_PERSON_ID}
GITLAB_URL: ${GITLAB_URL}
DATASOURCE_URL: "jdbc:postgresql://gitlab-bot-database:5432/gitlab_bot"
DATASOURCE_USERNAME: ${DATASOURCE_USERNAME}
DATASOURCE_PASSWORD: ${DATASOURCE_PASSWORD}
volumes:
gitlab-bot-database:
networks:
gitlab-bot:
```
.env
```
TELEGRAM_BOT_TOKEN=
TELEGRAM_BOT_USERNAME=
GITLAB_PERSONAL_TOKEN=
TELEGRAM_PERSON_ID=
GITLAB_URL=
DATASOURCE_USERNAME=
DATASOURCE_PASSWORD=
```
После запуска необходимо отправить боту сообщение, чтобы пройти первичную настройку.

View File

@ -1,7 +1,10 @@
package dev.struchkov.bot.gitlab.context.domain.entity; package dev.struchkov.bot.gitlab.context.domain.entity;
import dev.struchkov.bot.gitlab.context.domain.notify.level.DiscussionLevel;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.Id; import jakarta.persistence.Id;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import lombok.Getter; import lombok.Getter;
@ -34,4 +37,8 @@ public class AppSetting {
@Column(name = "project_private_scan") @Column(name = "project_private_scan")
private boolean projectPrivateScan; private boolean projectPrivateScan;
@Enumerated(EnumType.STRING)
@Column(name = "discussion_notify_level")
private DiscussionLevel discussionNotifyLevel;
} }

View File

@ -17,6 +17,8 @@ import lombok.Setter;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import static dev.struchkov.haiti.utils.Checker.checkNotEmpty;
/** /**
* @author upagge 11.02.2021 * @author upagge 11.02.2021
*/ */
@ -38,6 +40,9 @@ public class Discussion {
@Column(name = "resolved") @Column(name = "resolved")
private Boolean resolved; private Boolean resolved;
@Column(name = "notification")
private boolean notification;
@ManyToOne(optional = false, cascade = CascadeType.REMOVE) @ManyToOne(optional = false, cascade = CascadeType.REMOVE)
@JoinTable( @JoinTable(
name = "discussion_merge_request", name = "discussion_merge_request",
@ -62,6 +67,20 @@ public class Discussion {
this.notes = notes; this.notes = notes;
} }
public int getNoteSize() {
if (checkNotEmpty(notes)) {
return notes.size();
}
return 0;
}
public Optional<Note> getNoteByNumber(int number) {
if (checkNotEmpty(notes) && number < notes.size()) {
return Optional.of(notes.get(number));
}
return Optional.empty();
}
public Note getFirstNote() { public Note getFirstNote() {
return this.notes.get(0); return this.notes.get(0);
} }

View File

@ -45,4 +45,7 @@ public class MergeRequestForDiscussion {
@Column(name = "web_url") @Column(name = "web_url")
private String webUrl; private String webUrl;
@Column(name = "notification")
private boolean notification;
} }

View File

@ -9,6 +9,8 @@ public final class NewCommentNotify implements Notify {
public static final String TYPE = "NewCommentNotify"; public static final String TYPE = "NewCommentNotify";
private final String threadId;
private final String mergeRequestName;
private final String url; private final String url;
private final String discussionMessage; private final String discussionMessage;
private final String discussionAuthor; private final String discussionAuthor;
@ -16,17 +18,23 @@ public final class NewCommentNotify implements Notify {
private final String previousAuthor; private final String previousAuthor;
private final String authorName; private final String authorName;
private final String message; private final String message;
private final int numberNotes;
@Builder @Builder
public NewCommentNotify( public NewCommentNotify(
String threadId,
String mergeRequestName,
String url, String url,
String discussionMessage, String discussionMessage,
String discussionAuthor, String discussionAuthor,
String previousMessage, String previousMessage,
String previousAuthor, String previousAuthor,
String authorName, String authorName,
String message String message,
int numberNotes
) { ) {
this.threadId = threadId;
this.mergeRequestName = mergeRequestName;
this.url = url; this.url = url;
this.discussionMessage = discussionMessage; this.discussionMessage = discussionMessage;
this.discussionAuthor = discussionAuthor; this.discussionAuthor = discussionAuthor;
@ -34,9 +42,9 @@ public final class NewCommentNotify implements Notify {
this.previousAuthor = previousAuthor; this.previousAuthor = previousAuthor;
this.authorName = authorName; this.authorName = authorName;
this.message = message; this.message = message;
this.numberNotes = numberNotes;
} }
@Override @Override
public String getType() { public String getType() {
return TYPE; return TYPE;

View File

@ -0,0 +1,7 @@
package dev.struchkov.bot.gitlab.context.domain.notify.level;
public enum DiscussionLevel {
WITHOUT_NOTIFY, NOTIFY_WITHOUT_CONTEXT, NOTIFY_WITH_CONTEXT
}

View File

@ -0,0 +1,30 @@
package dev.struchkov.bot.gitlab.context.domain.notify.mergerequest;
import lombok.Builder;
import lombok.Getter;
@Getter
public class ConflictResolveMrNotify extends MrNotify {
public static final String TYPE = "ConflictResolveMrNotify";
private final String sourceBranch;
@Builder
private ConflictResolveMrNotify(
Long mrId,
String name,
String url,
String projectKey,
String sourceBranch
) {
super(mrId, projectKey, name, url);
this.sourceBranch = sourceBranch;
}
@Override
public String getType() {
return TYPE;
}
}

View File

@ -15,11 +15,13 @@ public class DiscussionNewNotify extends TaskNotify {
public static final String TYPE = "DiscussionNewNotify"; public static final String TYPE = "DiscussionNewNotify";
private final String threadId;
private final String mrName; private final String mrName;
private final List<Pair<String, String>> notes; private final List<Pair<String, String>> notes;
@Builder @Builder
public DiscussionNewNotify( public DiscussionNewNotify(
String threadId,
String mrName, String mrName,
String authorName, String authorName,
String url, String url,
@ -27,6 +29,7 @@ public class DiscussionNewNotify extends TaskNotify {
@Singular List<Pair<String, String>> notes @Singular List<Pair<String, String>> notes
) { ) {
super(authorName, url, discussionMessage); super(authorName, url, discussionMessage);
this.threadId = threadId;
this.mrName = mrName; this.mrName = mrName;
this.notes = notes; this.notes = notes;
} }

View File

@ -30,4 +30,6 @@ public interface DiscussionRepository {
void cleanOld(); void cleanOld();
void notification(boolean enable, String discussionId);
} }

View File

@ -28,6 +28,8 @@ public interface MergeRequestRepository {
Set<Long> findAllIds(); Set<Long> findAllIds();
void disableNotify(Long mrId); void notification(boolean enable, Long mrId);
void notificationByProjectId(boolean enable, Set<Long> projectIds);
} }

View File

@ -1,5 +1,7 @@
package dev.struchkov.bot.gitlab.context.service; package dev.struchkov.bot.gitlab.context.service;
import dev.struchkov.bot.gitlab.context.domain.notify.level.DiscussionLevel;
/** /**
* Сервис отвечает за пользовательские настройки приложения. * Сервис отвечает за пользовательские настройки приложения.
* *
@ -33,4 +35,6 @@ public interface AppSettingService {
boolean isPrivateProjectScan(); boolean isPrivateProjectScan();
DiscussionLevel getLevelDiscussionNotify();
} }

View File

@ -43,4 +43,6 @@ public interface DiscussionService {
void cleanOld(); void cleanOld();
void notification(boolean enable, String discussionId);
} }

View File

@ -38,6 +38,8 @@ public interface MergeRequestsService {
Set<Long> getAllIds(); Set<Long> getAllIds();
void disableNotify(@NonNull Long mrId); void notification(boolean enable, @NonNull Long mrId);
void notificationByProjectId(boolean enable, @NonNull Set<Long> projectIds);
} }

View File

@ -13,7 +13,7 @@ import java.util.Set;
*/ */
public interface ProjectService { public interface ProjectService {
Project create(@NonNull Project project); Project create(@NonNull Project project, boolean sendNotify);
Project update(@NonNull Project project); Project update(@NonNull Project project);

View File

@ -16,6 +16,7 @@ public class Icons {
public static final String TASK = "\uD83D\uDCBC"; public static final String TASK = "\uD83D\uDCBC";
public static final String ARROW = ""; public static final String ARROW = "";
public static final String DANGEROUS = "⚠️"; public static final String DANGEROUS = "⚠️";
public static final String GREEN_CIRCLE = "\uD83D\uDFE2";
public static final String PEN = "✏️"; public static final String PEN = "✏️";
public static final String ASSIGNEE = "\uD83C\uDFA9"; public static final String ASSIGNEE = "\uD83C\uDFA9";
public static final String BUILD = "⚙️"; public static final String BUILD = "⚙️";

View File

@ -1,6 +1,7 @@
package dev.struchkov.bot.gitlab.core.service.impl; package dev.struchkov.bot.gitlab.core.service.impl;
import dev.struchkov.bot.gitlab.context.domain.entity.AppSetting; import dev.struchkov.bot.gitlab.context.domain.entity.AppSetting;
import dev.struchkov.bot.gitlab.context.domain.notify.level.DiscussionLevel;
import dev.struchkov.bot.gitlab.context.repository.AppSettingRepository; import dev.struchkov.bot.gitlab.context.repository.AppSettingRepository;
import dev.struchkov.bot.gitlab.context.service.AppSettingService; import dev.struchkov.bot.gitlab.context.service.AppSettingService;
import dev.struchkov.haiti.context.exception.NotFoundException; import dev.struchkov.haiti.context.exception.NotFoundException;
@ -79,6 +80,11 @@ public class AppSettingServiceImpl implements AppSettingService {
return getAppSetting().isProjectPrivateScan(); return getAppSetting().isProjectPrivateScan();
} }
@Override
public DiscussionLevel getLevelDiscussionNotify() {
return getAppSetting().getDiscussionNotifyLevel();
}
private AppSetting getAppSetting() { private AppSetting getAppSetting() {
return appSettingRepository.findById(KEY) return appSettingRepository.findById(KEY)
.orElseThrow(NOT_FOUND_SETTINGS); .orElseThrow(NOT_FOUND_SETTINGS);

View File

@ -7,9 +7,11 @@ import dev.struchkov.bot.gitlab.context.domain.entity.MergeRequestForDiscussion;
import dev.struchkov.bot.gitlab.context.domain.entity.Note; import dev.struchkov.bot.gitlab.context.domain.entity.Note;
import dev.struchkov.bot.gitlab.context.domain.entity.Person; import dev.struchkov.bot.gitlab.context.domain.entity.Person;
import dev.struchkov.bot.gitlab.context.domain.notify.comment.NewCommentNotify; import dev.struchkov.bot.gitlab.context.domain.notify.comment.NewCommentNotify;
import dev.struchkov.bot.gitlab.context.domain.notify.level.DiscussionLevel;
import dev.struchkov.bot.gitlab.context.domain.notify.task.DiscussionNewNotify; import dev.struchkov.bot.gitlab.context.domain.notify.task.DiscussionNewNotify;
import dev.struchkov.bot.gitlab.context.domain.notify.task.TaskCloseNotify; import dev.struchkov.bot.gitlab.context.domain.notify.task.TaskCloseNotify;
import dev.struchkov.bot.gitlab.context.repository.DiscussionRepository; import dev.struchkov.bot.gitlab.context.repository.DiscussionRepository;
import dev.struchkov.bot.gitlab.context.service.AppSettingService;
import dev.struchkov.bot.gitlab.context.service.DiscussionService; import dev.struchkov.bot.gitlab.context.service.DiscussionService;
import dev.struchkov.bot.gitlab.context.service.NotifyService; import dev.struchkov.bot.gitlab.context.service.NotifyService;
import dev.struchkov.bot.gitlab.core.config.properties.GitlabProperty; import dev.struchkov.bot.gitlab.core.config.properties.GitlabProperty;
@ -38,6 +40,8 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static dev.struchkov.bot.gitlab.context.domain.notify.level.DiscussionLevel.NOTIFY_WITH_CONTEXT;
import static dev.struchkov.bot.gitlab.context.domain.notify.level.DiscussionLevel.WITHOUT_NOTIFY;
import static dev.struchkov.haiti.context.exception.NotFoundException.notFoundException; import static dev.struchkov.haiti.context.exception.NotFoundException.notFoundException;
import static dev.struchkov.haiti.utils.Checker.checkNotNull; import static dev.struchkov.haiti.utils.Checker.checkNotNull;
import static java.lang.Boolean.FALSE; import static java.lang.Boolean.FALSE;
@ -53,24 +57,70 @@ import static java.lang.Boolean.FALSE;
public class DiscussionServiceImpl implements DiscussionService { public class DiscussionServiceImpl implements DiscussionService {
protected static final Pattern PATTERN = Pattern.compile("@[\\w]+"); protected static final Pattern PATTERN = Pattern.compile("@[\\w]+");
private final OkHttpClient client = new OkHttpClient();
private final DiscussionRepository repository; private final DiscussionRepository repository;
private final PersonInformation personInformation;
private final OkHttpClient client = new OkHttpClient(); private final NotifyService notifyService;
private final AppSettingService settingService;
private final PersonInformation personInformation;
private final GitlabProperty gitlabProperty; private final GitlabProperty gitlabProperty;
private final PersonProperty personProperty; private final PersonProperty personProperty;
private final NotifyService notifyService;
@Override @Override
@Transactional @Transactional
public Discussion create(@NonNull Discussion discussion) { public Discussion create(@NonNull Discussion discussion) {
final List<Note> notes = discussion.getNotes(); final List<Note> notes = discussion.getNotes();
final DiscussionLevel levelDiscussionNotify = settingService.getLevelDiscussionNotify();
if (!WITHOUT_NOTIFY.equals(levelDiscussionNotify)) {
discussion.setNotification(true);
if (isNeedNotifyNewNote(discussion)) { if (isNeedNotifyNewNote(discussion)) {
notifyNewDiscussion(discussion); notifyNewDiscussion(discussion);
} else { } else {
notes.forEach(this::notificationPersonal); notes.forEach(note -> notificationPersonal(discussion, note));
}
} else {
discussion.setNotification(false);
}
final boolean resolved = discussion.getNotes().stream()
.allMatch(note -> note.isResolvable() && note.getResolved());
discussion.setResolved(resolved);
return repository.save(discussion);
}
@Override
@Transactional
public Discussion update(@NonNull Discussion discussion) {
final Discussion oldDiscussion = repository.findById(discussion.getId())
.orElseThrow(notFoundException("Дискуссия не найдена"));
discussion.setResponsible(oldDiscussion.getResponsible());
discussion.setMergeRequest(oldDiscussion.getMergeRequest());
discussion.setNotification(oldDiscussion.isNotification());
final Person responsiblePerson = discussion.getResponsible();
if (checkNotNull(responsiblePerson)) {
for (Note note : discussion.getNotes()) {
if (responsiblePerson.getId().equals(note.getAuthor().getId())) {
note.setAuthor(responsiblePerson);
}
final Person resolvedBy = note.getResolvedBy();
if (checkNotNull(resolvedBy)) {
if (responsiblePerson.getId().equals(resolvedBy.getId())) {
note.setResolvedBy(responsiblePerson);
}
}
}
}
if (oldDiscussion.isNotification()) {
notifyUpdateNote(oldDiscussion, discussion);
} }
final boolean resolved = discussion.getNotes().stream() final boolean resolved = discussion.getNotes().stream()
@ -90,6 +140,7 @@ public class DiscussionServiceImpl implements DiscussionService {
final MergeRequestForDiscussion mergeRequest = discussion.getMergeRequest(); final MergeRequestForDiscussion mergeRequest = discussion.getMergeRequest();
final DiscussionNewNotify.DiscussionNewNotifyBuilder notifyBuilder = DiscussionNewNotify.builder() final DiscussionNewNotify.DiscussionNewNotifyBuilder notifyBuilder = DiscussionNewNotify.builder()
.threadId(discussion.getId())
.mrName(mergeRequest.getTitle()) .mrName(mergeRequest.getTitle())
.authorName(firstNote.getAuthor().getName()) .authorName(firstNote.getAuthor().getName())
.discussionMessage(firstNote.getBody()) .discussionMessage(firstNote.getBody())
@ -116,39 +167,6 @@ public class DiscussionServiceImpl implements DiscussionService {
&& FALSE.equals(firstNote.getResolved()); // Комментарий не отмечен как решенный && FALSE.equals(firstNote.getResolved()); // Комментарий не отмечен как решенный
} }
@Override
@Transactional
public Discussion update(@NonNull Discussion discussion) {
final Discussion oldDiscussion = repository.findById(discussion.getId())
.orElseThrow(notFoundException("Дискуссия не найдена"));
discussion.setResponsible(oldDiscussion.getResponsible());
discussion.setMergeRequest(oldDiscussion.getMergeRequest());
final Person responsiblePerson = discussion.getResponsible();
if (checkNotNull(responsiblePerson)) {
for (Note note : discussion.getNotes()) {
if (responsiblePerson.getId().equals(note.getAuthor().getId())) {
note.setAuthor(responsiblePerson);
}
final Person resolvedBy = note.getResolvedBy();
if (checkNotNull(resolvedBy)) {
if (responsiblePerson.getId().equals(resolvedBy.getId())) {
note.setResolvedBy(responsiblePerson);
}
}
}
}
notifyUpdateNote(oldDiscussion, discussion);
final boolean resolved = discussion.getNotes().stream()
.allMatch(note -> note.isResolvable() && note.getResolved());
discussion.setResolved(resolved);
return repository.save(discussion);
}
@Override @Override
public List<Discussion> updateAll(@NonNull List<Discussion> discussions) { public List<Discussion> updateAll(@NonNull List<Discussion> discussions) {
return discussions.stream() return discussions.stream()
@ -178,39 +196,13 @@ public class DiscussionServiceImpl implements DiscussionService {
if (userParticipatedInDiscussion) { if (userParticipatedInDiscussion) {
notifyNewAnswer(discussion, newNote); notifyNewAnswer(discussion, newNote);
} else { } else {
notificationPersonal(newNote); notificationPersonal(discussion, newNote);
} }
} }
} }
} }
private void notifyNewAnswer(Discussion discussion, Note note) {
if (!personInformation.getId().equals(note.getAuthor().getId())) {
final Note firstNote = discussion.getFirstNote();
final Optional<Note> prevLastNote = discussion.getPrevLastNote();
final NewCommentNotify.NewCommentNotifyBuilder notifyBuilder = NewCommentNotify.builder();
if (prevLastNote.isPresent()) {
final Note prevNote = prevLastNote.get();
notifyBuilder.previousMessage(prevNote.getBody());
notifyBuilder.previousAuthor(prevNote.getAuthor().getName());
}
notifyService.send(
notifyBuilder
.url(note.getWebUrl())
.discussionMessage(firstNote.getBody())
.discussionAuthor(firstNote.getAuthor().getName())
.message(note.getBody())
.authorName(note.getAuthor().getName())
.build()
);
}
}
private void updateTask(Note note, Note oldNote) { private void updateTask(Note note, Note oldNote) {
if (isResolved(note, oldNote)) { if (isResolved(note, oldNote)) {
final MergeRequestForDiscussion mergeRequest = oldNote.getDiscussion().getMergeRequest(); final MergeRequestForDiscussion mergeRequest = oldNote.getDiscussion().getMergeRequest();
@ -253,20 +245,18 @@ public class DiscussionServiceImpl implements DiscussionService {
final String requestUrl = MessageFormat.format(gitlabProperty.getNewNoteUrl(), projectId, mergeRequest.getTwoId(), discussion.getId(), text); final String requestUrl = MessageFormat.format(gitlabProperty.getNewNoteUrl(), projectId, mergeRequest.getTwoId(), discussion.getId(), text);
RequestBody formBody = new FormBody.Builder().build(); final RequestBody formBody = new FormBody.Builder().build();
Request request = new Request.Builder() final Request request = new Request.Builder()
.post(formBody) .post(formBody)
.header(StringUtils.H_PRIVATE_TOKEN, personProperty.getToken()) .header(StringUtils.H_PRIVATE_TOKEN, personProperty.getToken())
.url(requestUrl) .url(requestUrl)
.build(); .build();
try { try {
client.newCall(request).execute(); client.newCall(request).execute();
} catch (IOException e) { } catch (IOException e) {
log.error(e.getMessage(), e); log.error(e.getMessage(), e);
} }
} }
@Override @Override
@ -319,24 +309,84 @@ public class DiscussionServiceImpl implements DiscussionService {
log.debug("Конец очистки старых дискуссий"); log.debug("Конец очистки старых дискуссий");
} }
@Override
@Transactional
public void notification(boolean enable, String discussionId) {
repository.notification(enable, discussionId);
}
private void notifyNewAnswer(Discussion discussion, Note note) {
final DiscussionLevel discussionLevel = settingService.getLevelDiscussionNotify();
if (!WITHOUT_NOTIFY.equals(discussionLevel)
&& !personInformation.getId().equals(note.getAuthor().getId())) {
final Note firstNote = discussion.getFirstNote();
final NewCommentNotify.NewCommentNotifyBuilder notifyBuilder = NewCommentNotify.builder()
.threadId(discussion.getId())
.url(note.getWebUrl())
.mergeRequestName(discussion.getMergeRequest().getTitle());
if (NOTIFY_WITH_CONTEXT.equals(discussionLevel)) {
final Optional<Note> prevLastNote = discussion.getPrevLastNote();
if (prevLastNote.isPresent()) {
final Note prevNote = prevLastNote.get();
notifyBuilder.previousMessage(prevNote.getBody());
notifyBuilder.previousAuthor(prevNote.getAuthor().getName());
}
notifyBuilder
.discussionMessage(firstNote.getBody())
.discussionAuthor(firstNote.getAuthor().getName())
.message(note.getBody())
.authorName(note.getAuthor().getName());
}
notifyService.send(notifyBuilder.build());
}
}
/** /**
* Уведомляет пользователя, если его никнейм упоминается в комментарии. * Уведомляет пользователя, если его никнейм упоминается в комментарии.
*/ */
protected void notificationPersonal(@NonNull Note note) { protected void notificationPersonal(Discussion discussion, Note note) {
final DiscussionLevel discussionLevel = settingService.getLevelDiscussionNotify();
if (!WITHOUT_NOTIFY.equals(discussionLevel)) {
final Matcher matcher = PATTERN.matcher(note.getBody()); final Matcher matcher = PATTERN.matcher(note.getBody());
final Set<String> recipientsLogins = new HashSet<>(); final Set<String> recipientsLogins = new HashSet<>();
while (matcher.find()) { while (matcher.find()) {
final String login = matcher.group(0).replace("@", ""); final String login = matcher.group(0).replace("@", "");
recipientsLogins.add(login); recipientsLogins.add(login);
} }
if (recipientsLogins.contains(personInformation.getUsername())) { if (recipientsLogins.contains(personInformation.getUsername())) {
notifyService.send( final NewCommentNotify.NewCommentNotifyBuilder notifyBuilder = NewCommentNotify.builder()
NewCommentNotify.builder() .threadId(discussion.getId())
.authorName(note.getAuthor().getName()) .mergeRequestName(discussion.getMergeRequest().getTitle())
.message(note.getBody()) .url(note.getWebUrl());
.url(note.getWebUrl())
.build() if (NOTIFY_WITH_CONTEXT.equals(discussionLevel)) {
); final Optional<Note> prevLastNote = discussion.getPrevLastNote();
final Note firstNote = discussion.getFirstNote();
if (!firstNote.equals(note)) {
notifyBuilder.message(note.getBody())
.authorName(note.getAuthor().getName());
}
if (prevLastNote.isPresent()) {
final Note prevNote = prevLastNote.get();
notifyBuilder.previousMessage(prevNote.getBody());
notifyBuilder.previousAuthor(prevNote.getAuthor().getName());
}
notifyBuilder
.discussionMessage(firstNote.getBody())
.discussionAuthor(firstNote.getAuthor().getName());
}
notifyService.send(notifyBuilder.build());
}
} }
} }

View File

@ -12,6 +12,7 @@ import dev.struchkov.bot.gitlab.context.domain.entity.MergeRequestForDiscussion;
import dev.struchkov.bot.gitlab.context.domain.entity.Person; import dev.struchkov.bot.gitlab.context.domain.entity.Person;
import dev.struchkov.bot.gitlab.context.domain.entity.Project; import dev.struchkov.bot.gitlab.context.domain.entity.Project;
import dev.struchkov.bot.gitlab.context.domain.notify.mergerequest.ConflictMrNotify; import dev.struchkov.bot.gitlab.context.domain.notify.mergerequest.ConflictMrNotify;
import dev.struchkov.bot.gitlab.context.domain.notify.mergerequest.ConflictResolveMrNotify;
import dev.struchkov.bot.gitlab.context.domain.notify.mergerequest.NewMrForAssignee; import dev.struchkov.bot.gitlab.context.domain.notify.mergerequest.NewMrForAssignee;
import dev.struchkov.bot.gitlab.context.domain.notify.mergerequest.NewMrForReview; import dev.struchkov.bot.gitlab.context.domain.notify.mergerequest.NewMrForReview;
import dev.struchkov.bot.gitlab.context.domain.notify.mergerequest.StatusMrNotify; import dev.struchkov.bot.gitlab.context.domain.notify.mergerequest.StatusMrNotify;
@ -179,7 +180,8 @@ public class MergeRequestsServiceImpl implements MergeRequestsService {
if (isChangedMr) { if (isChangedMr) {
notifyAboutStatus(oldMergeRequest, mergeRequest, project); notifyAboutStatus(oldMergeRequest, mergeRequest, project);
notifyAboutConflict(oldMergeRequest, mergeRequest, project); notifyAboutNewConflict(oldMergeRequest, mergeRequest, project);
notifyAboutResolveConflict(oldMergeRequest, mergeRequest, project);
notifyAboutUpdate(oldMergeRequest, mergeRequest, project); notifyAboutUpdate(oldMergeRequest, mergeRequest, project);
} }
@ -195,14 +197,15 @@ public class MergeRequestsServiceImpl implements MergeRequestsService {
} }
//TODO [05.12.2022|uPagge]: Добавить уведомление, если происходит удаление //TODO [05.12.2022|uPagge]: Добавить уведомление, если происходит удаление
private void notifyAssignee(AssigneeChanged assigneeChanged, MergeRequest oldMergeRequest, MergeRequest mergeRequest, Project project) { private void notifyAssignee(AssigneeChanged assigneeChanged, MergeRequest oldMergeRequest, MergeRequest mergeRequest, Project project) {
switch (assigneeChanged) { switch (assigneeChanged) {
case BECOME -> case BECOME ->
sendNotifyNewAssignee(mergeRequest, project.getName(), Optional.ofNullable(oldMergeRequest.getAssignee()).map(Person::getName).orElse(null)); sendNotifyNewAssignee(mergeRequest, project.getName(), Optional.ofNullable(oldMergeRequest.getAssignee()).map(Person::getName).orElse(null));
} }
} }
//TODO [05.12.2022|uPagge]: Добавить уведомление, если происходит удаление ревьювера //TODO [05.12.2022|uPagge]: Добавить уведомление, если происходит удаление ревьювера
private void notifyReviewer(ReviewerChanged reviewerChanged, MergeRequest mergeRequest, Project project) { private void notifyReviewer(ReviewerChanged reviewerChanged, MergeRequest mergeRequest, Project project) {
switch (reviewerChanged) { switch (reviewerChanged) {
case BECOME -> sendNotifyNewMrReview(mergeRequest, project.getName()); case BECOME -> sendNotifyNewMrReview(mergeRequest, project.getName());
@ -268,8 +271,14 @@ public class MergeRequestsServiceImpl implements MergeRequestsService {
@Override @Override
@Transactional @Transactional
public void disableNotify(@NonNull Long mrId) { public void notification(boolean enable, @NonNull Long mrId) {
repository.disableNotify(mrId); repository.notification(enable, mrId);
}
@Override
@Transactional
public void notificationByProjectId(boolean enable, @NonNull Set<Long> projectIds) {
repository.notificationByProjectId(enable, projectIds);
} }
private void notifyAboutUpdate(MergeRequest oldMergeRequest, MergeRequest mergeRequest, Project project) { private void notifyAboutUpdate(MergeRequest oldMergeRequest, MergeRequest mergeRequest, Project project) {
@ -317,7 +326,7 @@ public class MergeRequestsServiceImpl implements MergeRequestsService {
} }
} }
protected void notifyAboutConflict(MergeRequest oldMergeRequest, MergeRequest mergeRequest, Project project) { protected void notifyAboutNewConflict(MergeRequest oldMergeRequest, MergeRequest mergeRequest, Project project) {
final Long gitlabUserId = personInformation.getId(); final Long gitlabUserId = personInformation.getId();
if ( if (
!oldMergeRequest.isConflict() // У старого MR не было конфликта !oldMergeRequest.isConflict() // У старого MR не было конфликта
@ -336,6 +345,25 @@ public class MergeRequestsServiceImpl implements MergeRequestsService {
} }
} }
private void notifyAboutResolveConflict(MergeRequest oldMergeRequest, MergeRequest mergeRequest, Project project) {
final Long gitlabUserId = personInformation.getId();
if (
oldMergeRequest.isConflict() // У старого MR был конфликт
&& !mergeRequest.isConflict() // А у нового нет
&& gitlabUserId.equals(oldMergeRequest.getAuthor().getId()) // и MR создан пользователем бота
) {
notifyService.send(
ConflictResolveMrNotify.builder()
.mrId(oldMergeRequest.getId())
.sourceBranch(oldMergeRequest.getSourceBranch())
.name(mergeRequest.getTitle())
.url(mergeRequest.getWebUrl())
.projectKey(project.getName())
.build()
);
}
}
protected void notifyAboutStatus(MergeRequest oldMergeRequest, MergeRequest newMergeRequest, Project project) { protected void notifyAboutStatus(MergeRequest oldMergeRequest, MergeRequest newMergeRequest, Project project) {
final MergeRequestState oldStatus = oldMergeRequest.getState(); final MergeRequestState oldStatus = oldMergeRequest.getState();
final MergeRequestState newStatus = newMergeRequest.getState(); final MergeRequestState newStatus = newMergeRequest.getState();

View File

@ -33,11 +33,13 @@ public class ProjectServiceImpl implements ProjectService {
@Override @Override
@Transactional @Transactional
public Project create(@NonNull Project project) { public Project create(@NonNull Project project, boolean sendNotify) {
final Project newProject = repository.save(project); final Project newProject = repository.save(project);
if (sendNotify) {
final String authorName = personService.getByIdOrThrown(newProject.getCreatorId()).getName(); final String authorName = personService.getByIdOrThrown(newProject.getCreatorId()).getName();
notifyAboutNewProject(newProject, authorName); notifyAboutNewProject(newProject, authorName);
}
return newProject; return newProject;
} }
@ -57,7 +59,7 @@ public class ProjectServiceImpl implements ProjectService {
@Transactional @Transactional
public List<Project> createAll(List<Project> newProjects) { public List<Project> createAll(List<Project> newProjects) {
return newProjects.stream() return newProjects.stream()
.map(this::create) .map(newProject -> create(newProject, true))
.toList(); .toList();
} }

View File

@ -3,6 +3,7 @@ package dev.struchkov.bot.gitlab.core.service.parser;
import dev.struchkov.bot.gitlab.context.domain.ExistContainer; import dev.struchkov.bot.gitlab.context.domain.ExistContainer;
import dev.struchkov.bot.gitlab.context.domain.entity.Person; import dev.struchkov.bot.gitlab.context.domain.entity.Person;
import dev.struchkov.bot.gitlab.context.domain.entity.Project; import dev.struchkov.bot.gitlab.context.domain.entity.Project;
import dev.struchkov.bot.gitlab.context.service.MergeRequestsService;
import dev.struchkov.bot.gitlab.context.service.PersonService; import dev.struchkov.bot.gitlab.context.service.PersonService;
import dev.struchkov.bot.gitlab.context.service.ProjectService; import dev.struchkov.bot.gitlab.context.service.ProjectService;
import dev.struchkov.bot.gitlab.core.config.properties.GitlabProperty; import dev.struchkov.bot.gitlab.core.config.properties.GitlabProperty;
@ -38,8 +39,8 @@ public class ProjectParser {
public static final String OWNER = "&owned=true"; public static final String OWNER = "&owned=true";
public static final String PRIVATE = "&visibility=private"; public static final String PRIVATE = "&visibility=private";
public static final String PUBLIC_OWNER = "&visibility=public&owned=true";
private final MergeRequestsService mergeRequestsService;
private final ProjectService projectService; private final ProjectService projectService;
private final PersonService personService; private final PersonService personService;
@ -86,17 +87,18 @@ public class ProjectParser {
} }
} }
public void parseByUrl(String projectUrl) { public Project parseByUrl(String projectUrl) {
final ProjectJson projectJson = HttpParse.request(projectUrl) final ProjectJson projectJson = HttpParse.request(projectUrl)
.header(ACCEPT) .header(ACCEPT)
.header(StringUtils.H_PRIVATE_TOKEN, personProperty.getToken()) .header(StringUtils.H_PRIVATE_TOKEN, personProperty.getToken())
.execute(ProjectJson.class) .execute(ProjectJson.class)
.orElseThrow(convertException("Ошибка получения проекта")); .orElseThrow(convertException("Ошибка получения репозитория."));
if (!projectService.existsById(projectJson.getId())) { if (!projectService.existsById(projectJson.getId())) {
createNewPersons(List.of(projectJson)); createNewPersons(List.of(projectJson));
final Project newProject = conversionService.convert(projectJson, Project.class); final Project newProject = conversionService.convert(projectJson, Project.class);
projectService.create(newProject); return projectService.create(newProject, false);
} else {
return projectService.getByIdOrThrow(projectJson.getId());
} }
} }

View File

@ -62,4 +62,9 @@ public class DiscussionRepositoryImpl implements DiscussionRepository {
jpaRepository.removeAllByMergeRequestIsNull(); jpaRepository.removeAllByMergeRequestIsNull();
} }
@Override
public void notification(boolean enable, String discussionId) {
jpaRepository.notification(enable, discussionId);
}
} }

View File

@ -71,8 +71,13 @@ public class MergeRequestRepositoryImpl implements MergeRequestRepository {
} }
@Override @Override
public void disableNotify(Long mrId) { public void notification(boolean enable, Long mrId) {
jpaRepository.disableNotify(mrId); jpaRepository.disableNotify(enable, mrId);
}
@Override
public void notificationByProjectId(boolean enable, Set<Long> projectIds) {
jpaRepository.notificationByProjectId(enable, projectIds);
} }
} }

View File

@ -28,4 +28,8 @@ public interface DiscussionJpaRepository extends JpaRepository<Discussion, Strin
@Query("DELETE FROM Discussion d WHERE d.id = :id") @Query("DELETE FROM Discussion d WHERE d.id = :id")
void deleteById(@Param("id") String id); void deleteById(@Param("id") String id);
@Modifying
@Query("UPDATE Discussion d SET d.notification = :enable WHERE d.id = :discussionId")
void notification(@Param("enable") boolean enable, @Param("discussionId") String discussionId);
} }

View File

@ -29,7 +29,11 @@ public interface MergeRequestJpaRepository extends JpaRepositoryImplementation<M
Set<Long> findAllIds(); Set<Long> findAllIds();
@Modifying @Modifying
@Query("UPDATE MergeRequest mr SET mr.notification = false WHERE mr.id = :mrId") @Query("UPDATE MergeRequest mr SET mr.notification = :enable WHERE mr.id = :mrId")
void disableNotify(@Param("mrId") Long mrId); void disableNotify(@Param("enable") boolean enable, @Param("mrId") Long mrId);
@Modifying
@Query("UPDATE MergeRequest mr SET mr.notification = :enable WHERE mr.projectId in :projectIds")
void notificationByProjectId(@Param("enable") boolean enable, @Param("projectIds") Set<Long> projectIds);
} }

View File

@ -32,7 +32,7 @@ public class SchedulerService {
private final MergeRequestsService mergeRequestsService; private final MergeRequestsService mergeRequestsService;
private final DiscussionService discussionService; private final DiscussionService discussionService;
@Scheduled(cron = "0 */2 * * * *") @Scheduled(cron = "0 */1 * * * *")
public void newMergeRequest() { public void newMergeRequest() {
log.info("Запуск процесса обновления данных c GitLab"); log.info("Запуск процесса обновления данных c GitLab");
if (!settingService.isFirstStart()) { if (!settingService.isFirstStart()) {

View File

@ -20,6 +20,9 @@
<column name="project_private_scan" type="boolean" defaultValue="false"> <column name="project_private_scan" type="boolean" defaultValue="false">
<constraints nullable="false"/> <constraints nullable="false"/>
</column> </column>
<column name="discussion_notify_level" type="varchar(64)" defaultValue="NOTIFY_WITH_CONTEXT">
<constraints nullable="false"/>
</column>
</createTable> </createTable>
</changeSet> </changeSet>
@ -160,6 +163,9 @@
references="person(id)"/> references="person(id)"/>
</column> </column>
<column name="resolved" type="boolean"/> <column name="resolved" type="boolean"/>
<column name="notification" type="boolean">
<constraints nullable="false"/>
</column>
</createTable> </createTable>
<createIndex tableName="discussion" indexName="i_discussion_responsible_id"> <createIndex tableName="discussion" indexName="i_discussion_responsible_id">

View File

@ -5,6 +5,7 @@ import dev.struchkov.bot.gitlab.telegram.unit.MenuConfig;
import dev.struchkov.bot.gitlab.telegram.unit.command.AnswerNoteUnit; import dev.struchkov.bot.gitlab.telegram.unit.command.AnswerNoteUnit;
import dev.struchkov.bot.gitlab.telegram.unit.command.DeleteMessageUnit; import dev.struchkov.bot.gitlab.telegram.unit.command.DeleteMessageUnit;
import dev.struchkov.bot.gitlab.telegram.unit.command.DisableNotifyMrUnit; import dev.struchkov.bot.gitlab.telegram.unit.command.DisableNotifyMrUnit;
import dev.struchkov.bot.gitlab.telegram.unit.command.DisableNotifyThreadUnit;
import dev.struchkov.bot.gitlab.telegram.unit.command.EnableProjectNotify; import dev.struchkov.bot.gitlab.telegram.unit.command.EnableProjectNotify;
import dev.struchkov.bot.gitlab.telegram.unit.flow.InitSettingFlow; import dev.struchkov.bot.gitlab.telegram.unit.flow.InitSettingFlow;
import dev.struchkov.godfather.main.core.unit.TypeUnit; import dev.struchkov.godfather.main.core.unit.TypeUnit;
@ -80,9 +81,11 @@ public class TelegramBotConfig {
AnswerNoteUnit commandUnit, AnswerNoteUnit commandUnit,
DeleteMessageUnit deleteMessageUnit, DeleteMessageUnit deleteMessageUnit,
DisableNotifyMrUnit disableNotifyMrUnit, DisableNotifyMrUnit disableNotifyMrUnit,
DisableNotifyThreadUnit disableNotifyThreadUnit,
EnableProjectNotify enableProjectNotify EnableProjectNotify enableProjectNotify
) { ) {
final List<Object> config = List.of(menuConfig, unitConfig, commandUnit, deleteMessageUnit, disableNotifyMrUnit, enableProjectNotify); final List<Object> config = List.of(menuConfig, unitConfig, commandUnit, deleteMessageUnit, disableNotifyMrUnit,
disableNotifyThreadUnit, enableProjectNotify);
return new StorylineMailService( return new StorylineMailService(
unitPointerService, unitPointerService,

View File

@ -5,8 +5,15 @@ import dev.struchkov.bot.gitlab.context.utils.Icons;
import dev.struchkov.godfather.main.domain.BoxAnswer; import dev.struchkov.godfather.main.domain.BoxAnswer;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import static dev.struchkov.bot.gitlab.context.utils.Icons.link; import static dev.struchkov.bot.gitlab.telegram.utils.Const.BUTTON_ARG_CONFIRMATION;
import static dev.struchkov.bot.gitlab.telegram.utils.Const.BUTTON_ARG_DISABLE_NOTIFY_MR_ID;
import static dev.struchkov.bot.gitlab.telegram.utils.Const.BUTTON_VALUE_FALSE;
import static dev.struchkov.bot.gitlab.telegram.utils.UnitName.DELETE_MESSAGE;
import static dev.struchkov.godfather.main.domain.BoxAnswer.boxAnswer; import static dev.struchkov.godfather.main.domain.BoxAnswer.boxAnswer;
import static dev.struchkov.godfather.main.domain.keyboard.button.SimpleButton.simpleButton;
import static dev.struchkov.godfather.main.domain.keyboard.simple.SimpleKeyBoardLine.simpleLine;
import static dev.struchkov.godfather.telegram.domain.keyboard.InlineKeyBoard.inlineKeyBoard;
import static dev.struchkov.godfather.telegram.domain.keyboard.button.UrlButton.urlButton;
import static dev.struchkov.haiti.utils.Strings.escapeMarkdown; import static dev.struchkov.haiti.utils.Strings.escapeMarkdown;
@Component @Component
@ -15,12 +22,24 @@ public class ConflictPrNotifyGenerator implements NotifyBoxAnswerGenerator<Confl
@Override @Override
public BoxAnswer generate(ConflictMrNotify notify) { public BoxAnswer generate(ConflictMrNotify notify) {
final StringBuilder builder = new StringBuilder(Icons.DANGEROUS).append(" *Attention! MergeRequest conflict | ").append(escapeMarkdown(notify.getProjectName())).append("*") final StringBuilder builder = new StringBuilder(Icons.DANGEROUS).append(" *Attention. MergeRequest conflict!*")
.append(Icons.HR) .append(Icons.HR)
.append(link(notify.getTitle(), notify.getUrl())); .append(escapeMarkdown(notify.getTitle()))
.append(Icons.HR)
.append(Icons.PROJECT).append(": ").append(escapeMarkdown(notify.getProjectName())).append("\n")
.append(Icons.TREE).append(": ").append(escapeMarkdown(notify.getSourceBranch()));
final String notifyMessage = builder.toString(); final String notifyMessage = builder.toString();
return boxAnswer(notifyMessage); return boxAnswer(
notifyMessage,
inlineKeyBoard(
simpleLine(
simpleButton(Icons.VIEW, DELETE_MESSAGE),
urlButton(Icons.LINK, notify.getUrl()),
simpleButton(Icons.DISABLE_NOTIFY, "[" + BUTTON_ARG_DISABLE_NOTIFY_MR_ID + ":" + notify.getMrId() + ";" + BUTTON_ARG_CONFIRMATION + ":" + BUTTON_VALUE_FALSE + "]")
)
)
);
} }
@Override @Override

View File

@ -0,0 +1,50 @@
package dev.struchkov.bot.gitlab.telegram.service.notify;
import dev.struchkov.bot.gitlab.context.domain.notify.mergerequest.ConflictResolveMrNotify;
import dev.struchkov.bot.gitlab.context.utils.Icons;
import dev.struchkov.godfather.main.domain.BoxAnswer;
import org.springframework.stereotype.Component;
import static dev.struchkov.bot.gitlab.telegram.utils.Const.BUTTON_ARG_CONFIRMATION;
import static dev.struchkov.bot.gitlab.telegram.utils.Const.BUTTON_ARG_DISABLE_NOTIFY_MR_ID;
import static dev.struchkov.bot.gitlab.telegram.utils.Const.BUTTON_VALUE_FALSE;
import static dev.struchkov.bot.gitlab.telegram.utils.UnitName.DELETE_MESSAGE;
import static dev.struchkov.godfather.main.domain.BoxAnswer.boxAnswer;
import static dev.struchkov.godfather.main.domain.keyboard.button.SimpleButton.simpleButton;
import static dev.struchkov.godfather.main.domain.keyboard.simple.SimpleKeyBoardLine.simpleLine;
import static dev.struchkov.godfather.telegram.domain.keyboard.InlineKeyBoard.inlineKeyBoard;
import static dev.struchkov.godfather.telegram.domain.keyboard.button.UrlButton.urlButton;
import static dev.struchkov.haiti.utils.Strings.escapeMarkdown;
@Component
public class ConflictResolvePrNotifyGenerator implements NotifyBoxAnswerGenerator<ConflictResolveMrNotify> {
@Override
public BoxAnswer generate(ConflictResolveMrNotify notify) {
final StringBuilder builder = new StringBuilder(Icons.GREEN_CIRCLE).append(" *Merge request conflict resolved!*")
.append(Icons.HR)
.append(escapeMarkdown(notify.getTitle()))
.append(Icons.HR)
.append(Icons.PROJECT).append(": ").append(escapeMarkdown(notify.getProjectName())).append("\n")
.append(Icons.TREE).append(": ").append(escapeMarkdown(notify.getSourceBranch()));
final String notifyMessage = builder.toString();
return boxAnswer(
notifyMessage,
inlineKeyBoard(
simpleLine(
simpleButton(Icons.VIEW, DELETE_MESSAGE),
urlButton(Icons.LINK, notify.getUrl()),
simpleButton(Icons.DISABLE_NOTIFY, "[" + BUTTON_ARG_DISABLE_NOTIFY_MR_ID + ":" + notify.getMrId() + ";" + BUTTON_ARG_CONFIRMATION + ":" + BUTTON_VALUE_FALSE + "]")
)
)
);
}
@Override
public String getNotifyType() {
return ConflictResolveMrNotify.TYPE;
}
}

View File

@ -9,8 +9,15 @@ import org.springframework.stereotype.Component;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static dev.struchkov.bot.gitlab.telegram.utils.Const.BUTTON_ARG_CONFIRMATION;
import static dev.struchkov.bot.gitlab.telegram.utils.Const.BUTTON_ARG_DISABLE_NOTIFY_THREAD_ID;
import static dev.struchkov.bot.gitlab.telegram.utils.Const.BUTTON_VALUE_FALSE;
import static dev.struchkov.bot.gitlab.telegram.utils.UnitName.DELETE_MESSAGE;
import static dev.struchkov.godfather.main.domain.BoxAnswer.boxAnswer; import static dev.struchkov.godfather.main.domain.BoxAnswer.boxAnswer;
import static dev.struchkov.haiti.utils.Checker.checkNotNull; import static dev.struchkov.godfather.main.domain.keyboard.button.SimpleButton.simpleButton;
import static dev.struchkov.godfather.telegram.domain.keyboard.InlineKeyBoard.inlineKeyBoard;
import static dev.struchkov.godfather.telegram.domain.keyboard.button.UrlButton.urlButton;
import static dev.struchkov.haiti.utils.Checker.checkNotEmpty;
import static dev.struchkov.haiti.utils.Strings.escapeMarkdown; import static dev.struchkov.haiti.utils.Strings.escapeMarkdown;
@Component @Component
@ -18,20 +25,27 @@ public class DiscussionNewNotifyGenerator implements NotifyBoxAnswerGenerator<Di
@Override @Override
public BoxAnswer generate(DiscussionNewNotify notify) { public BoxAnswer generate(DiscussionNewNotify notify) {
final StringBuilder builder = new StringBuilder(Icons.TASK).append(" [New discussion](").append(notify.getUrl()).append(")") final StringBuilder builder = new StringBuilder(Icons.TASK).append(" *New Thread in your MR*")
.append(Icons.HR) .append(Icons.HR)
.append(escapeMarkdown(notify.getMrName())) .append(Icons.link(escapeMarkdown(notify.getMrName()), notify.getUrl()))
.append(Icons.HR) .append(Icons.HR)
.append("*").append(notify.getAuthorName()).append("*: ").append(escapeMarkdown(notify.getMessageTask())); .append("*").append(notify.getAuthorName()).append("*: ").append(escapeMarkdown(notify.getMessageTask()));
final List<Pair<String, String>> notes = notify.getNotes(); final List<Pair<String, String>> notes = notify.getNotes();
if (checkNotNull(notes)) { if (checkNotEmpty(notes)) {
builder.append("\n-- -- -- -- comments -- -- -- --\n") builder.append("\n-- -- -- -- comments -- -- -- --\n")
.append(convertNotes(notes)); .append(convertNotes(notes));
} }
final String notifyMessage = builder.toString(); final String notifyMessage = builder.toString();
return boxAnswer(notifyMessage); return boxAnswer(
notifyMessage,
inlineKeyBoard(
simpleButton(Icons.VIEW, DELETE_MESSAGE),
urlButton(Icons.LINK, notify.getUrl()),
simpleButton(Icons.DISABLE_NOTIFY, "[" + BUTTON_ARG_DISABLE_NOTIFY_THREAD_ID + ":" + notify.getThreadId() + ";" + BUTTON_ARG_CONFIRMATION + ":" + BUTTON_VALUE_FALSE + "]")
)
);
} }
private String convertNotes(List<Pair<String, String>> notes) { private String convertNotes(List<Pair<String, String>> notes) {

View File

@ -3,34 +3,58 @@ package dev.struchkov.bot.gitlab.telegram.service.notify;
import dev.struchkov.bot.gitlab.context.domain.notify.comment.NewCommentNotify; import dev.struchkov.bot.gitlab.context.domain.notify.comment.NewCommentNotify;
import dev.struchkov.bot.gitlab.context.utils.Icons; import dev.struchkov.bot.gitlab.context.utils.Icons;
import dev.struchkov.godfather.main.domain.BoxAnswer; import dev.struchkov.godfather.main.domain.BoxAnswer;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import static dev.struchkov.bot.gitlab.telegram.utils.Const.BUTTON_ARG_CONFIRMATION;
import static dev.struchkov.bot.gitlab.telegram.utils.Const.BUTTON_ARG_DISABLE_NOTIFY_THREAD_ID;
import static dev.struchkov.bot.gitlab.telegram.utils.Const.BUTTON_VALUE_FALSE;
import static dev.struchkov.bot.gitlab.telegram.utils.UnitName.DELETE_MESSAGE;
import static dev.struchkov.godfather.main.domain.BoxAnswer.boxAnswer; import static dev.struchkov.godfather.main.domain.BoxAnswer.boxAnswer;
import static dev.struchkov.godfather.main.domain.keyboard.button.SimpleButton.simpleButton;
import static dev.struchkov.godfather.telegram.domain.keyboard.InlineKeyBoard.inlineKeyBoard;
import static dev.struchkov.godfather.telegram.domain.keyboard.button.UrlButton.urlButton;
import static dev.struchkov.haiti.utils.Checker.checkNotNull; import static dev.struchkov.haiti.utils.Checker.checkNotNull;
import static dev.struchkov.haiti.utils.Strings.escapeMarkdown; import static dev.struchkov.haiti.utils.Strings.escapeMarkdown;
@Component @Component
@RequiredArgsConstructor
public class NewCommentNotifyGenerator implements NotifyBoxAnswerGenerator<NewCommentNotify> { public class NewCommentNotifyGenerator implements NotifyBoxAnswerGenerator<NewCommentNotify> {
@Override @Override
public BoxAnswer generate(NewCommentNotify notify) { public BoxAnswer generate(NewCommentNotify notify) {
final StringBuilder builder = new StringBuilder(Icons.COMMENT).append(" [New answer in discussion](").append(notify.getUrl()).append(")"); final StringBuilder builder = new StringBuilder(Icons.COMMENT).append(" *New answer in Thread*")
.append(Icons.HR)
.append(Icons.link(escapeMarkdown(notify.getMergeRequestName()), notify.getUrl()))
.append("\n");
if (checkNotNull(notify.getDiscussionMessage())) { if (checkNotNull(notify.getDiscussionMessage())) {
builder.append("\n-- -- discussion first message -- --\n") builder.append("\n-- -- thread first message -- --\n")
.append("*").append(notify.getDiscussionAuthor()).append("*: ").append(escapeMarkdown(notify.getDiscussionMessage())); .append("*").append(notify.getDiscussionAuthor()).append("*: ").append(escapeMarkdown(notify.getDiscussionMessage()))
.append("\n");
} }
if (checkNotNull(notify.getPreviousMessage())) { if (checkNotNull(notify.getPreviousMessage())) {
builder.append("\n-- -- -- previous message -- -- --\n") builder.append("\n-- -- -- previous message -- -- --\n")
.append("*").append(notify.getPreviousAuthor()).append("*: ").append(escapeMarkdown(notify.getPreviousMessage())); .append("*").append(notify.getPreviousAuthor()).append("*: ").append(escapeMarkdown(notify.getPreviousMessage()))
.append("\n");
} }
if (checkNotNull(notify.getMessage())) {
builder.append("\n-- -- -- --- new answer --- -- -- --\n") builder.append("\n-- -- -- --- new answer --- -- -- --\n")
.append("*").append(notify.getAuthorName()).append("*: ").append(escapeMarkdown(notify.getMessage())); .append("*").append(notify.getAuthorName()).append("*: ").append(escapeMarkdown(notify.getMessage()))
.append("\n");
}
final String messageNotify = builder.toString(); final String messageNotify = builder.toString();
return boxAnswer(messageNotify); return boxAnswer(
messageNotify,
inlineKeyBoard(
simpleButton(Icons.VIEW, DELETE_MESSAGE),
urlButton(Icons.LINK, notify.getUrl()),
simpleButton(Icons.DISABLE_NOTIFY, "[" + BUTTON_ARG_DISABLE_NOTIFY_THREAD_ID + ":" + notify.getThreadId() + ";" + BUTTON_ARG_CONFIRMATION + ":" + BUTTON_VALUE_FALSE + "]")
)
);
} }
@Override @Override

View File

@ -2,9 +2,11 @@ package dev.struchkov.bot.gitlab.telegram.unit;
import dev.struchkov.bot.gitlab.context.domain.PersonInformation; import dev.struchkov.bot.gitlab.context.domain.PersonInformation;
import dev.struchkov.bot.gitlab.context.domain.entity.MergeRequest; import dev.struchkov.bot.gitlab.context.domain.entity.MergeRequest;
import dev.struchkov.bot.gitlab.context.domain.entity.Project;
import dev.struchkov.bot.gitlab.context.service.AppSettingService; import dev.struchkov.bot.gitlab.context.service.AppSettingService;
import dev.struchkov.bot.gitlab.context.service.MergeRequestsService; import dev.struchkov.bot.gitlab.context.service.MergeRequestsService;
import dev.struchkov.bot.gitlab.context.service.NoteService; import dev.struchkov.bot.gitlab.context.service.NoteService;
import dev.struchkov.bot.gitlab.context.service.ProjectService;
import dev.struchkov.bot.gitlab.context.utils.Icons; import dev.struchkov.bot.gitlab.context.utils.Icons;
import dev.struchkov.bot.gitlab.core.config.properties.GitlabProperty; import dev.struchkov.bot.gitlab.core.config.properties.GitlabProperty;
import dev.struchkov.bot.gitlab.core.service.parser.ProjectParser; import dev.struchkov.bot.gitlab.core.service.parser.ProjectParser;
@ -14,15 +16,16 @@ import dev.struchkov.godfather.main.domain.annotation.Unit;
import dev.struchkov.godfather.main.domain.content.Mail; import dev.struchkov.godfather.main.domain.content.Mail;
import dev.struchkov.godfather.simple.core.unit.AnswerText; import dev.struchkov.godfather.simple.core.unit.AnswerText;
import dev.struchkov.godfather.simple.core.unit.MainUnit; import dev.struchkov.godfather.simple.core.unit.MainUnit;
import dev.struchkov.godfather.simple.data.StorylineContext; import dev.struchkov.godfather.telegram.domain.attachment.LinkAttachment;
import dev.struchkov.godfather.telegram.domain.keyboard.InlineKeyBoard; import dev.struchkov.godfather.telegram.domain.keyboard.InlineKeyBoard;
import dev.struchkov.godfather.telegram.simple.context.service.TelegramSending; import dev.struchkov.godfather.telegram.main.core.util.Attachments;
import dev.struchkov.haiti.utils.Checker; import dev.struchkov.haiti.utils.Checker;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static dev.struchkov.bot.gitlab.telegram.utils.UnitName.ACCESS_ERROR; import static dev.struchkov.bot.gitlab.telegram.utils.UnitName.ACCESS_ERROR;
@ -33,10 +36,13 @@ import static dev.struchkov.bot.gitlab.telegram.utils.UnitName.GET_TASKS;
import static dev.struchkov.bot.gitlab.telegram.utils.UnitName.SETTINGS; import static dev.struchkov.bot.gitlab.telegram.utils.UnitName.SETTINGS;
import static dev.struchkov.bot.gitlab.telegram.utils.UnitName.TEXT_ADD_NEW_PROJECT; import static dev.struchkov.bot.gitlab.telegram.utils.UnitName.TEXT_ADD_NEW_PROJECT;
import static dev.struchkov.godfather.main.domain.BoxAnswer.boxAnswer; import static dev.struchkov.godfather.main.domain.BoxAnswer.boxAnswer;
import static dev.struchkov.godfather.main.domain.BoxAnswer.replaceBoxAnswer;
import static dev.struchkov.godfather.main.domain.keyboard.button.SimpleButton.simpleButton; import static dev.struchkov.godfather.main.domain.keyboard.button.SimpleButton.simpleButton;
import static dev.struchkov.godfather.main.domain.keyboard.simple.SimpleKeyBoardLine.simpleLine; import static dev.struchkov.godfather.main.domain.keyboard.simple.SimpleKeyBoardLine.simpleLine;
import static dev.struchkov.godfather.telegram.domain.keyboard.InlineKeyBoard.inlineKeyBoard; import static dev.struchkov.godfather.telegram.domain.keyboard.InlineKeyBoard.inlineKeyBoard;
import static dev.struchkov.godfather.telegram.simple.core.util.TriggerChecks.clickButtonRaw; import static dev.struchkov.godfather.telegram.simple.core.util.TriggerChecks.clickButtonRaw;
import static dev.struchkov.godfather.telegram.simple.core.util.TriggerChecks.isLinks;
import static java.util.Collections.singleton;
/** /**
* // TODO: 16.01.2021 Добавить описание. * // TODO: 16.01.2021 Добавить описание.
@ -47,16 +53,17 @@ import static dev.struchkov.godfather.telegram.simple.core.util.TriggerChecks.cl
@RequiredArgsConstructor @RequiredArgsConstructor
public class MenuConfig { public class MenuConfig {
private final StorylineContext context;
private final TelegramSending sending;
private final ProjectParser projectParser;
private final GitlabProperty gitlabProperty; private final GitlabProperty gitlabProperty;
private final PersonInformation personInformation; private final PersonInformation personInformation;
private final ProjectParser projectParser;
private final ProjectService projectService;
private final NoteService noteService; private final NoteService noteService;
private final MergeRequestsService mergeRequestsService; private final MergeRequestsService mergeRequestsService;
private final AppSettingService settingService; private final AppSettingService settingService;
@Unit(value = ACCESS_ERROR, main = true) @Unit(value = ACCESS_ERROR, main = true)
public AnswerText<Mail> accessError() { public AnswerText<Mail> accessError() {
return AnswerText.<Mail>builder() return AnswerText.<Mail>builder()
@ -92,12 +99,12 @@ public class MenuConfig {
.answer(mail -> { .answer(mail -> {
final String messageText = "This is the bot menu, select a new item"; final String messageText = "This is the bot menu, select a new item";
final InlineKeyBoard generalMenuKeyBoard = inlineKeyBoard( final InlineKeyBoard generalMenuKeyBoard = inlineKeyBoard(
simpleLine(simpleButton("Add project", TEXT_ADD_NEW_PROJECT)), simpleLine(simpleButton("Add project", TEXT_ADD_NEW_PROJECT))
simpleLine( // simpleLine(
simpleButton("My tasks", GET_TASKS), // simpleButton("My tasks", GET_TASKS),
simpleButton("Merge Request", GET_ASSIGNEE_MERGE_REQUEST) // simpleButton("Merge Request", GET_ASSIGNEE_MERGE_REQUEST)
), // );
simpleLine(simpleButton("Settings", SETTINGS)) // simpleLine(simpleButton("Settings", SETTINGS))
); );
return boxAnswer(messageText, generalMenuKeyBoard); return boxAnswer(messageText, generalMenuKeyBoard);
} }
@ -115,7 +122,7 @@ public class MenuConfig {
) { ) {
return AnswerText.<Mail>builder() return AnswerText.<Mail>builder()
.triggerCheck(clickButtonRaw(TEXT_ADD_NEW_PROJECT)) .triggerCheck(clickButtonRaw(TEXT_ADD_NEW_PROJECT))
.answer(boxAnswer("Copy the url of the project and send it to me")) .answer(replaceBoxAnswer("Send me links to repositories you want to track"))
.next(addNewProject) .next(addNewProject)
.build(); .build();
} }
@ -123,13 +130,20 @@ public class MenuConfig {
@Unit(ADD_NEW_PROJECT) @Unit(ADD_NEW_PROJECT)
public AnswerText<Mail> addNewProject() { public AnswerText<Mail> addNewProject() {
return AnswerText.<Mail>builder() return AnswerText.<Mail>builder()
.triggerCheck(isLinks())
.answer(mail -> { .answer(mail -> {
final String mailText = mail.getText(); final List<LinkAttachment> links = Attachments.findAllLinks(mail.getAttachments());
final String projectUrl = gitlabProperty.getProjectAddUrl() + mailText.replace(gitlabProperty.getBaseUrl(), "") for (LinkAttachment link : links) {
final String projectUrl = gitlabProperty.getProjectAddUrl() + link.getUrl().replace(gitlabProperty.getBaseUrl(), "")
.substring(1) .substring(1)
.replace("/", "%2F"); .replace("/", "%2F");
projectParser.parseByUrl(projectUrl); final Project project = projectParser.parseByUrl(projectUrl);
return boxAnswer("Project added successfully"); final Set<Long> projectId = singleton(project.getId());
projectService.notification(true, projectId);
projectService.processing(true, projectId);
mergeRequestsService.notificationByProjectId(true, projectId);
}
return boxAnswer("\uD83D\uDC4D Projects added successfully!");
}) })
.build(); .build();
} }

View File

@ -5,20 +5,19 @@ import dev.struchkov.bot.gitlab.context.domain.entity.Note;
import dev.struchkov.bot.gitlab.context.service.AppSettingService; import dev.struchkov.bot.gitlab.context.service.AppSettingService;
import dev.struchkov.bot.gitlab.context.service.DiscussionService; import dev.struchkov.bot.gitlab.context.service.DiscussionService;
import dev.struchkov.bot.gitlab.context.service.NoteService; import dev.struchkov.bot.gitlab.context.service.NoteService;
import dev.struchkov.godfather.main.domain.BoxAnswer;
import dev.struchkov.godfather.main.domain.annotation.Unit; import dev.struchkov.godfather.main.domain.annotation.Unit;
import dev.struchkov.godfather.main.domain.content.Attachment;
import dev.struchkov.godfather.main.domain.content.Mail; import dev.struchkov.godfather.main.domain.content.Mail;
import dev.struchkov.godfather.simple.core.unit.AnswerText; import dev.struchkov.godfather.simple.core.unit.AnswerText;
import dev.struchkov.godfather.telegram.domain.attachment.LinkAttachment; import dev.struchkov.godfather.telegram.domain.attachment.LinkAttachment;
import dev.struchkov.godfather.telegram.domain.attachment.TelegramAttachmentType;
import dev.struchkov.godfather.telegram.main.core.util.Attachments; import dev.struchkov.godfather.telegram.main.core.util.Attachments;
import dev.struchkov.godfather.telegram.simple.context.service.TelegramSending;
import dev.struchkov.haiti.utils.Checker; import dev.struchkov.haiti.utils.Checker;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.util.List; import java.util.List;
import java.util.concurrent.ScheduledExecutorService;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -35,21 +34,24 @@ public class AnswerNoteUnit {
private final AppSettingService settingService; private final AppSettingService settingService;
private final NoteService noteService; private final NoteService noteService;
private final DiscussionService discussionService; private final DiscussionService discussionService;
private final ScheduledExecutorService scheduledExecutorService;
private final TelegramSending telegramSending;
@Unit(value = ANSWER_NOTE, main = true) //TODO [07.02.2023|uPagge]: Можно возвращать ссылку на ответ
@Unit(value = ANSWER_NOTE, global = true)
public AnswerText<Mail> answerNote() { public AnswerText<Mail> answerNote() {
return AnswerText.<Mail>builder() return AnswerText.<Mail>builder()
.triggerCheck( .triggerCheck(
mail -> { mail -> {
final boolean isAccess = personInformation.getTelegramId().equals(mail.getPersonId());
if (isAccess) {
final boolean isFirstStart = settingService.isFirstStart();
if (!isFirstStart) {
final List<Mail> forwardMails = mail.getForwardMail(); final List<Mail> forwardMails = mail.getForwardMail();
if (Checker.checkNotNull(forwardMails) && forwardMails.size() == 1) { if (Checker.checkNotNull(forwardMails) && forwardMails.size() == 1) {
final Mail forwardMail = forwardMails.get(0); final Mail forwardMail = forwardMails.get(0);
return Attachments.findFirstLink(forwardMail.getAttachments()).isPresent(); final boolean isLink = Attachments.findFirstLink(forwardMail.getAttachments())
} .isPresent();
if (isLink) {
final boolean isAccess = personInformation.getTelegramId().equals(mail.getPersonId());
final boolean firstStart = settingService.isFirstStart();
return isAccess && !firstStart;
} }
} }
return false; return false;
@ -57,20 +59,19 @@ public class AnswerNoteUnit {
) )
.answer( .answer(
mail -> { mail -> {
final List<Attachment> attachments = mail.getForwardMail().get(0).getAttachments(); final String noteUrl = Attachments.findFirstLink(mail.getForwardMail().get(0).getAttachments())
for (Attachment attachment : attachments) { .map(LinkAttachment::getUrl)
if (TelegramAttachmentType.LINK.name().equals(attachment.getType())) { .orElseThrow();
final String url = ((LinkAttachment) attachment).getUrl(); final Matcher matcher = NOTE_LINK.matcher(noteUrl);
final Matcher matcher = NOTE_LINK.matcher(url);
if (matcher.find()) { if (matcher.find()) {
final String noteText = url.substring(matcher.start(), matcher.end()); final String noteText = noteUrl.substring(matcher.start(), matcher.end());
final Long noteId = Long.valueOf(noteText.replaceAll("#note_", "")); final Long noteId = Long.valueOf(noteText.replace("#note_", ""));
final Note note = noteService.getByIdOrThrow(noteId); final Note note = noteService.getByIdOrThrow(noteId);
final String discussionId = note.getDiscussion().getId(); final String discussionId = note.getDiscussion().getId();
discussionService.answer(discussionId, MessageFormat.format("@{0}, {1}", note.getAuthor().getUserName(), mail.getText())); discussionService.answer(discussionId, MessageFormat.format("@{0}, {1}", note.getAuthor().getUserName(), mail.getText()));
return BoxAnswer.builder().build(); return boxAnswer(
} "\uD83D\uDC4D Response sent successfully"
} );
} }
return boxAnswer("Error"); return boxAnswer("Error");
} }

View File

@ -14,7 +14,6 @@ import dev.struchkov.godfather.telegram.simple.context.service.TelegramSending;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.Optional;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -54,16 +53,13 @@ public class DisableNotifyMrUnit {
public AnswerText<Mail> disableNotifyMr() { public AnswerText<Mail> disableNotifyMr() {
return AnswerText.<Mail>builder() return AnswerText.<Mail>builder()
.triggerCheck(mail -> { .triggerCheck(mail -> {
final boolean isDisableButtonClick = Attachments.findFirstButtonClick(mail.getAttachments())
.flatMap(buttonClick -> buttonClick.getArgByType(BUTTON_ARG_DISABLE_NOTIFY_MR_ID))
.isPresent();
if (isDisableButtonClick) {
final boolean isAccess = personInformation.getTelegramId().equals(mail.getPersonId()); final boolean isAccess = personInformation.getTelegramId().equals(mail.getPersonId());
if (isAccess) {
final boolean isFirstStart = settingService.isFirstStart(); final boolean isFirstStart = settingService.isFirstStart();
if (!isFirstStart) { return isAccess && !isFirstStart;
final Optional<ButtonClickAttachment> optButtonClick = Attachments.findFirstButtonClick(mail.getAttachments());
if (optButtonClick.isPresent()) {
final ButtonClickAttachment buttonClick = optButtonClick.get();
return buttonClick.getArgByType(BUTTON_ARG_DISABLE_NOTIFY_MR_ID).isPresent();
}
}
} }
return false; return false;
}) })
@ -79,7 +75,7 @@ public class DisableNotifyMrUnit {
.orElseThrow(); .orElseThrow();
if (confirmation) { if (confirmation) {
mergeRequestsService.disableNotify(mrId); mergeRequestsService.notification(false, mrId);
scheduledExecutorService.schedule(() -> telegramSending.deleteMessage(mail.getPersonId(), clickButton.getMessageId()), 5, TimeUnit.SECONDS); scheduledExecutorService.schedule(() -> telegramSending.deleteMessage(mail.getPersonId(), clickButton.getMessageId()), 5, TimeUnit.SECONDS);
return replaceBoxAnswer(SUCCESSFULLY_DISABLED); return replaceBoxAnswer(SUCCESSFULLY_DISABLED);
} else { } else {

View File

@ -0,0 +1,92 @@
package dev.struchkov.bot.gitlab.telegram.unit.command;
import dev.struchkov.bot.gitlab.context.domain.PersonInformation;
import dev.struchkov.bot.gitlab.context.service.AppSettingService;
import dev.struchkov.bot.gitlab.context.service.DiscussionService;
import dev.struchkov.bot.gitlab.context.utils.Icons;
import dev.struchkov.godfather.main.domain.annotation.Unit;
import dev.struchkov.godfather.main.domain.content.Mail;
import dev.struchkov.godfather.simple.core.unit.AnswerText;
import dev.struchkov.godfather.telegram.domain.attachment.ButtonClickAttachment;
import dev.struchkov.godfather.telegram.domain.attachment.ButtonClickAttachment.Arg;
import dev.struchkov.godfather.telegram.main.core.util.Attachments;
import dev.struchkov.godfather.telegram.simple.context.service.TelegramSending;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import static dev.struchkov.bot.gitlab.telegram.utils.Const.BUTTON_ARG_CONFIRMATION;
import static dev.struchkov.bot.gitlab.telegram.utils.Const.BUTTON_ARG_DISABLE_NOTIFY_THREAD_ID;
import static dev.struchkov.bot.gitlab.telegram.utils.Const.BUTTON_VALUE_TRUE;
import static dev.struchkov.bot.gitlab.telegram.utils.UnitName.DELETE_MESSAGE;
import static dev.struchkov.bot.gitlab.telegram.utils.UnitName.DISABLE_NOTIFY_THREAD;
import static dev.struchkov.godfather.main.domain.BoxAnswer.replaceBoxAnswer;
import static dev.struchkov.godfather.main.domain.keyboard.button.SimpleButton.simpleButton;
import static dev.struchkov.godfather.main.domain.keyboard.simple.SimpleKeyBoardLine.simpleLine;
import static dev.struchkov.godfather.telegram.domain.keyboard.InlineKeyBoard.inlineKeyBoard;
@Component
@RequiredArgsConstructor
public class DisableNotifyThreadUnit {
public static final String WARNING_ABOUT_DISABLE_NOTIFY = Icons.DISABLE_NOTIFY + """
*Disabling notifications*
Are you sure you want to stop receiving notifications of new replies to this thread?
""";
public static final String SUCCESSFULLY_DISABLED = "Notifications successfully disabled for this thread";
private final DiscussionService discussionService;
private final PersonInformation personInformation;
private final AppSettingService settingService;
private final TelegramSending telegramSending;
private final ScheduledExecutorService scheduledExecutorService;
@Unit(value = DISABLE_NOTIFY_THREAD, global = true)
public AnswerText<Mail> disableNotifyThread() {
return AnswerText.<Mail>builder()
.triggerCheck(mail -> {
final boolean isDisableButtonClick = Attachments.findFirstButtonClick(mail.getAttachments())
.flatMap(buttonClick -> buttonClick.getArgByType(BUTTON_ARG_DISABLE_NOTIFY_THREAD_ID))
.isPresent();
if (isDisableButtonClick) {
final boolean isAccess = personInformation.getTelegramId().equals(mail.getPersonId());
final boolean isFirstStart = settingService.isFirstStart();
return isAccess && !isFirstStart;
}
return false;
})
.answer(mail -> {
final ButtonClickAttachment clickButton = Attachments.findFirstButtonClick(mail.getAttachments()).orElseThrow();
final boolean confirmation = clickButton.getArgByType(BUTTON_ARG_CONFIRMATION)
.map(Arg::getValue)
.map(BUTTON_VALUE_TRUE::equals)
.orElseThrow();
final String discussionId = clickButton.getArgByType(BUTTON_ARG_DISABLE_NOTIFY_THREAD_ID)
.map(Arg::getValue)
.map(String::valueOf)
.orElseThrow();
if (confirmation) {
discussionService.notification(false, discussionId);
scheduledExecutorService.schedule(() -> telegramSending.deleteMessage(mail.getPersonId(), clickButton.getMessageId()), 5, TimeUnit.SECONDS);
return replaceBoxAnswer(SUCCESSFULLY_DISABLED);
} else {
return replaceBoxAnswer(
WARNING_ABOUT_DISABLE_NOTIFY,
inlineKeyBoard(
simpleLine(
simpleButton(Icons.YES, "[" + BUTTON_ARG_DISABLE_NOTIFY_THREAD_ID + ":" + discussionId + ";" + BUTTON_ARG_CONFIRMATION + ":" + BUTTON_VALUE_TRUE + "]"),
simpleButton(Icons.NO, DELETE_MESSAGE)
)
)
);
}
})
.build();
}
}

View File

@ -2,6 +2,7 @@ package dev.struchkov.bot.gitlab.telegram.unit.command;
import dev.struchkov.bot.gitlab.context.domain.PersonInformation; import dev.struchkov.bot.gitlab.context.domain.PersonInformation;
import dev.struchkov.bot.gitlab.context.service.AppSettingService; import dev.struchkov.bot.gitlab.context.service.AppSettingService;
import dev.struchkov.bot.gitlab.context.service.MergeRequestsService;
import dev.struchkov.bot.gitlab.context.service.ProjectService; import dev.struchkov.bot.gitlab.context.service.ProjectService;
import dev.struchkov.bot.gitlab.context.utils.Icons; import dev.struchkov.bot.gitlab.context.utils.Icons;
import dev.struchkov.bot.gitlab.telegram.utils.UnitName; import dev.struchkov.bot.gitlab.telegram.utils.UnitName;
@ -28,6 +29,7 @@ import static dev.struchkov.godfather.main.domain.BoxAnswer.replaceBoxAnswer;
public class EnableProjectNotify { public class EnableProjectNotify {
private final ProjectService projectService; private final ProjectService projectService;
private final MergeRequestsService mergeRequestsService;
private final AppSettingService settingService; private final AppSettingService settingService;
private final PersonInformation personInformation; private final PersonInformation personInformation;
@ -59,6 +61,7 @@ public class EnableProjectNotify {
final Set<Long> setProjectId = Set.of(projectId); final Set<Long> setProjectId = Set.of(projectId);
projectService.processing(true, setProjectId); projectService.processing(true, setProjectId);
projectService.notification(true, setProjectId); projectService.notification(true, setProjectId);
mergeRequestsService.notificationByProjectId(true, setProjectId);
return replaceBoxAnswer(mail.getId(), Icons.GOOD + " you will now receive notifications!"); return replaceBoxAnswer(mail.getId(), Icons.GOOD + " you will now receive notifications!");
} }
) )

View File

@ -14,7 +14,6 @@ import dev.struchkov.godfather.main.domain.annotation.Unit;
import dev.struchkov.godfather.main.domain.content.Mail; import dev.struchkov.godfather.main.domain.content.Mail;
import dev.struchkov.godfather.simple.core.unit.AnswerText; import dev.struchkov.godfather.simple.core.unit.AnswerText;
import dev.struchkov.godfather.simple.core.unit.MainUnit; import dev.struchkov.godfather.simple.core.unit.MainUnit;
import dev.struchkov.godfather.simple.data.StorylineContext;
import dev.struchkov.godfather.telegram.domain.attachment.ButtonClickAttachment; import dev.struchkov.godfather.telegram.domain.attachment.ButtonClickAttachment;
import dev.struchkov.godfather.telegram.main.core.util.Attachments; import dev.struchkov.godfather.telegram.main.core.util.Attachments;
import dev.struchkov.godfather.telegram.simple.context.service.TelegramSending; import dev.struchkov.godfather.telegram.simple.context.service.TelegramSending;
@ -57,7 +56,6 @@ import static java.text.MessageFormat.format;
public class InitSettingFlow { public class InitSettingFlow {
private final TelegramSending sending; private final TelegramSending sending;
private final StorylineContext context;
private final PersonInformation personInformation; private final PersonInformation personInformation;
@ -138,7 +136,7 @@ public class InitSettingFlow {
) { ) {
return AnswerText.<Mail>builder() return AnswerText.<Mail>builder()
.triggerCheck(clickButtonRaw("NO")) .triggerCheck(clickButtonRaw("NO"))
.answer(replaceBoxAnswer("Okay, I won't scan public projects.")) .answer(replaceBoxAnswer("\uD83D\uDC4C I won't scan public projects."))
.<Integer>callBack( .<Integer>callBack(
sentBox -> scheduledExecutorService.schedule(() -> sending.deleteMessage(sentBox.getPersonId(), sentBox.getMessageId()), 10, TimeUnit.SECONDS) sentBox -> scheduledExecutorService.schedule(() -> sending.deleteMessage(sentBox.getPersonId(), sentBox.getMessageId()), 10, TimeUnit.SECONDS)
) )
@ -217,7 +215,7 @@ public class InitSettingFlow {
discussionParser.scanNewDiscussion(); discussionParser.scanNewDiscussion();
final int discussionCount = discussionService.getAllIds().size() - oldCountThreads; final int discussionCount = discussionService.getAllIds().size() - oldCountThreads;
return replaceBoxAnswer(format(finalAnswer, pipelineCount, mrCount, pipelineCount, discussionCount)); return replaceBoxAnswer(format(finalAnswer, projectCount, mrCount, pipelineCount, discussionCount));
}) })
.<Integer>callBack( .<Integer>callBack(
sentBox -> scheduledExecutorService.schedule(() -> sending.deleteMessage(sentBox.getPersonId(), sentBox.getMessageId()), 10, TimeUnit.SECONDS) sentBox -> scheduledExecutorService.schedule(() -> sending.deleteMessage(sentBox.getPersonId(), sentBox.getMessageId()), 10, TimeUnit.SECONDS)
@ -260,6 +258,7 @@ public class InitSettingFlow {
.answer(mail -> { .answer(mail -> {
final ButtonClickAttachment buttonClick = Attachments.findFirstButtonClick(mail.getAttachments()).orElseThrow(); final ButtonClickAttachment buttonClick = Attachments.findFirstButtonClick(mail.getAttachments()).orElseThrow();
if ("YES".equals(buttonClick.getRawCallBackData())) { if ("YES".equals(buttonClick.getRawCallBackData())) {
sending.replaceMessage(mail.getPersonId(), mail.getId(), boxAnswer("⌛I write down the available projects. This may take a long time."));
projectParser.parseAllProjectOwner(); projectParser.parseAllProjectOwner();
settingService.ownerProjectScan(true); settingService.ownerProjectScan(true);
} else { } else {
@ -378,7 +377,7 @@ public class InitSettingFlow {
) { ) {
return AnswerText.<Mail>builder() return AnswerText.<Mail>builder()
.triggerPhrase("NO") .triggerPhrase("NO")
.answer(replaceBoxAnswer("Okay, I won't scan private projects.")) .answer(replaceBoxAnswer("\uD83D\uDC4C I won't scan private projects."))
.<Integer>callBack( .<Integer>callBack(
sentBox -> scheduledExecutorService.schedule(() -> sending.deleteMessage(sentBox.getPersonId(), sentBox.getMessageId()), 10, TimeUnit.SECONDS) sentBox -> scheduledExecutorService.schedule(() -> sending.deleteMessage(sentBox.getPersonId(), sentBox.getMessageId()), 10, TimeUnit.SECONDS)
) )
@ -394,7 +393,11 @@ public class InitSettingFlow {
.activeType(AFTER) .activeType(AFTER)
.answer( .answer(
boxAnswer( boxAnswer(
"Do you want to enable automatic notification of new private projects available to you?", """
Do you want to enable automatic notification of new private projects available to you?
-- -- -- -- --
I will be forced to scan all available private projects for this. I will not scan other entities in projects and send any notifications for these projects.
""",
inlineKeyBoard( inlineKeyBoard(
simpleLine( simpleLine(
simpleButton("Yes", "YES"), simpleButton("Yes", "YES"),
@ -416,6 +419,7 @@ public class InitSettingFlow {
.answer(mail -> { .answer(mail -> {
final ButtonClickAttachment buttonClick = Attachments.findFirstButtonClick(mail.getAttachments()).orElseThrow(); final ButtonClickAttachment buttonClick = Attachments.findFirstButtonClick(mail.getAttachments()).orElseThrow();
if ("YES".equals(buttonClick.getRawCallBackData())) { if ("YES".equals(buttonClick.getRawCallBackData())) {
sending.replaceMessage(mail.getPersonId(), mail.getId(), boxAnswer("⌛I write down the available private projects. This may take a long time."));
projectParser.parseAllPrivateProject(); projectParser.parseAllPrivateProject();
settingService.privateProjectScan(true); settingService.privateProjectScan(true);
} else { } else {

View File

@ -9,6 +9,7 @@ public class Const {
public static final String BUTTON_VALUE_TRUE = "t"; public static final String BUTTON_VALUE_TRUE = "t";
public static final String BUTTON_ARG_DISABLE_NOTIFY_MR_ID = "dis_mr_id"; public static final String BUTTON_ARG_DISABLE_NOTIFY_MR_ID = "dis_mr_id";
public static final String BUTTON_ARG_DISABLE_NOTIFY_THREAD_ID = "dis_th_id";
public static final String BUTTON_ARG_ENABLE_NOTIFY_PROJECT_ID = "ena_p_id"; public static final String BUTTON_ARG_ENABLE_NOTIFY_PROJECT_ID = "ena_p_id";
public static final String BUTTON_ARG_CONFIRMATION = "conf"; public static final String BUTTON_ARG_CONFIRMATION = "conf";

View File

@ -30,6 +30,7 @@ public final class UnitName {
// команды // команды
public static final String DELETE_MESSAGE = "DELETE_MESSAGE"; public static final String DELETE_MESSAGE = "DELETE_MESSAGE";
public static final String DISABLE_NOTIFY_MR = "DISABLE_NOTIFY_MR"; public static final String DISABLE_NOTIFY_MR = "DISABLE_NOTIFY_MR";
public static final String DISABLE_NOTIFY_THREAD = "DISABLE_NOTIFY_THREAD";
public static final String ENABLE_NOTIFY_PROJECT = "ENABLE_NOTIFY_PROJECT"; public static final String ENABLE_NOTIFY_PROJECT = "ENABLE_NOTIFY_PROJECT";
private UnitName() { private UnitName() {