Compare commits
7 Commits
5ab219f04c
...
ce1ae13c01
Author | SHA1 | Date | |
---|---|---|---|
ce1ae13c01 | |||
f918e7cefc | |||
17d300fbef | |||
7d6883abd2 | |||
ace251118a | |||
c835837156 | |||
dd9ae83014 |
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@ -1 +1 @@
|
|||||||
custom: ["https://tinkoff.ru/sl/NSGSK7FgEI"]
|
custom: ["https://docs.struchkov.dev/gitlab-notification/support-development/"]
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>dev.struchkov.bot.gitlab</groupId>
|
<groupId>dev.struchkov.bot.gitlab</groupId>
|
||||||
<artifactId>gitlab-bot</artifactId>
|
<artifactId>gitlab-bot</artifactId>
|
||||||
<version>2.0.0-SNAPSHOT</version>
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>bot-context</artifactId>
|
<artifactId>bot-context</artifactId>
|
||||||
|
@ -85,6 +85,13 @@ public class Discussion {
|
|||||||
return this.notes.get(0);
|
return this.notes.get(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Optional<Note> getLastNote() {
|
||||||
|
if (this.notes.size() > 1) {
|
||||||
|
return Optional.ofNullable(this.notes.get(this.notes.size() - 1));
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
public Optional<Note> getPrevLastNote() {
|
public Optional<Note> getPrevLastNote() {
|
||||||
final int size = notes.size();
|
final int size = notes.size();
|
||||||
if (size > 2) {
|
if (size > 2) {
|
||||||
|
@ -15,26 +15,24 @@ import static dev.struchkov.bot.gitlab.context.domain.notify.task.DiscussionNewN
|
|||||||
*/
|
*/
|
||||||
@Getter
|
@Getter
|
||||||
@FieldNames
|
@FieldNames
|
||||||
public class DiscussionNewNotify extends TaskNotify {
|
public class DiscussionNewNotify extends ThreadNotify {
|
||||||
|
|
||||||
public static final String TYPE = CLASS_NAME;
|
public static final String TYPE = CLASS_NAME;
|
||||||
|
|
||||||
private final String threadId;
|
private final String threadId;
|
||||||
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 threadId,
|
||||||
String mrName,
|
String mergeRequestName,
|
||||||
String authorName,
|
String authorName,
|
||||||
String url,
|
String url,
|
||||||
String discussionMessage,
|
String discussionMessage,
|
||||||
@Singular List<Pair<String, String>> notes
|
@Singular List<Pair<String, String>> notes
|
||||||
) {
|
) {
|
||||||
super(authorName, url, discussionMessage);
|
super(mergeRequestName, authorName, url, discussionMessage);
|
||||||
this.threadId = threadId;
|
this.threadId = threadId;
|
||||||
this.mrName = mrName;
|
|
||||||
this.notes = notes;
|
this.notes = notes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,24 +11,31 @@ import static dev.struchkov.bot.gitlab.context.domain.notify.task.TaskCloseNotif
|
|||||||
*/
|
*/
|
||||||
@Getter
|
@Getter
|
||||||
@FieldNames
|
@FieldNames
|
||||||
public class TaskCloseNotify extends TaskNotify {
|
public class ThreadCloseNotify extends ThreadNotify {
|
||||||
|
|
||||||
public static final String TYPE = CLASS_NAME;
|
public static final String TYPE = CLASS_NAME;
|
||||||
|
|
||||||
private final Long personTasks;
|
private final Long personTasks;
|
||||||
private final Long personResolvedTasks;
|
private final Long personResolvedTasks;
|
||||||
|
private final String authorLastNote;
|
||||||
|
private final String messageLastNote;
|
||||||
|
|
||||||
@Builder
|
@Builder
|
||||||
protected TaskCloseNotify(
|
protected ThreadCloseNotify(
|
||||||
|
String mergeRequestName,
|
||||||
String authorName,
|
String authorName,
|
||||||
String url,
|
String url,
|
||||||
String messageTask,
|
String messageTask,
|
||||||
Long personTasks,
|
Long personTasks,
|
||||||
Long personResolvedTasks
|
Long personResolvedTasks,
|
||||||
|
String authorLastNote,
|
||||||
|
String messageLastNote
|
||||||
) {
|
) {
|
||||||
super(authorName, url, messageTask);
|
super(mergeRequestName, authorName, url, messageTask);
|
||||||
this.personTasks = personTasks;
|
this.personTasks = personTasks;
|
||||||
this.personResolvedTasks = personResolvedTasks;
|
this.personResolvedTasks = personResolvedTasks;
|
||||||
|
this.authorLastNote = authorLastNote;
|
||||||
|
this.messageLastNote = messageLastNote;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
@ -4,17 +4,20 @@ import dev.struchkov.bot.gitlab.context.domain.notify.Notify;
|
|||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
public abstract class TaskNotify implements Notify {
|
public abstract class ThreadNotify implements Notify {
|
||||||
|
|
||||||
|
protected final String mergeRequestName;
|
||||||
protected final String authorName;
|
protected final String authorName;
|
||||||
protected final String url;
|
protected final String url;
|
||||||
protected final String messageTask;
|
protected final String messageTask;
|
||||||
|
|
||||||
protected TaskNotify(
|
protected ThreadNotify(
|
||||||
|
String mergeRequestName,
|
||||||
String authorName,
|
String authorName,
|
||||||
String url,
|
String url,
|
||||||
String messageTask
|
String messageTask
|
||||||
) {
|
) {
|
||||||
|
this.mergeRequestName = mergeRequestName;
|
||||||
this.authorName = authorName;
|
this.authorName = authorName;
|
||||||
this.url = url;
|
this.url = url;
|
||||||
this.messageTask = messageTask;
|
this.messageTask = messageTask;
|
@ -13,7 +13,7 @@ public class Icons {
|
|||||||
public static final String AUTHOR = "\uD83D\uDC68\u200D\uD83D\uDCBB️";
|
public static final String AUTHOR = "\uD83D\uDC68\u200D\uD83D\uDCBB️";
|
||||||
public static final String UPDATE = "\uD83D\uDD04";
|
public static final String UPDATE = "\uD83D\uDD04";
|
||||||
public static final String COMMENT = "\uD83D\uDCAC";
|
public static final String COMMENT = "\uD83D\uDCAC";
|
||||||
public static final String TASK = "\uD83D\uDCBC";
|
public static final String THREAD = "\uD83E\uDDF5";
|
||||||
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 GREEN_CIRCLE = "\uD83D\uDFE2";
|
||||||
@ -27,7 +27,6 @@ public class Icons {
|
|||||||
public static final String YES = "✅";
|
public static final String YES = "✅";
|
||||||
public static final String NO = "❌";
|
public static final String NO = "❌";
|
||||||
public static final String NOTIFY = "\uD83D\uDD14";
|
public static final String NOTIFY = "\uD83D\uDD14";
|
||||||
public static final String NO_PROCESSING = "\uD83D\uDDD1";
|
|
||||||
public static final String GOOD = "\uD83D\uDC4D";
|
public static final String GOOD = "\uD83D\uDC4D";
|
||||||
|
|
||||||
private Icons() {
|
private Icons() {
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>dev.struchkov.bot.gitlab</groupId>
|
<groupId>dev.struchkov.bot.gitlab</groupId>
|
||||||
<artifactId>gitlab-bot</artifactId>
|
<artifactId>gitlab-bot</artifactId>
|
||||||
<version>2.0.0-SNAPSHOT</version>
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>bot-core</artifactId>
|
<artifactId>bot-core</artifactId>
|
||||||
|
@ -9,7 +9,7 @@ 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.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.ThreadCloseNotify;
|
||||||
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.AppSettingService;
|
||||||
import dev.struchkov.bot.gitlab.context.service.DiscussionService;
|
import dev.struchkov.bot.gitlab.context.service.DiscussionService;
|
||||||
@ -44,7 +44,9 @@ import static dev.struchkov.bot.gitlab.context.domain.notify.level.DiscussionLev
|
|||||||
import static dev.struchkov.bot.gitlab.context.domain.notify.level.DiscussionLevel.WITHOUT_NOTIFY;
|
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 dev.struchkov.haiti.utils.Checker.checkNull;
|
||||||
import static java.lang.Boolean.FALSE;
|
import static java.lang.Boolean.FALSE;
|
||||||
|
import static java.lang.Boolean.TRUE;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Сервис для работы с дискуссиями.
|
* Сервис для работы с дискуссиями.
|
||||||
@ -80,7 +82,7 @@ public class DiscussionServiceImpl implements DiscussionService {
|
|||||||
if (isNeedNotifyNewNote(discussion)) {
|
if (isNeedNotifyNewNote(discussion)) {
|
||||||
notifyNewThread(discussion);
|
notifyNewThread(discussion);
|
||||||
} else {
|
} else {
|
||||||
notes.forEach(note -> notificationPersonal(discussion, note));
|
notes.forEach(note -> notifyAboutPersonalAnswer(discussion, note));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
discussion.setNotification(false);
|
discussion.setNotification(false);
|
||||||
@ -119,14 +121,14 @@ public class DiscussionServiceImpl implements DiscussionService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final boolean resolved = discussion.getNotes().stream()
|
||||||
|
.allMatch(note -> note.isResolvable() && note.getResolved());
|
||||||
|
discussion.setResolved(resolved);
|
||||||
|
|
||||||
if (oldDiscussion.isNotification()) {
|
if (oldDiscussion.isNotification()) {
|
||||||
notifyUpdateNote(oldDiscussion, discussion);
|
notifyUpdateNote(oldDiscussion, discussion);
|
||||||
}
|
}
|
||||||
|
|
||||||
final boolean resolved = discussion.getNotes().stream()
|
|
||||||
.allMatch(note -> note.isResolvable() && note.getResolved());
|
|
||||||
|
|
||||||
discussion.setResolved(resolved);
|
|
||||||
|
|
||||||
return repository.save(discussion);
|
return repository.save(discussion);
|
||||||
}
|
}
|
||||||
@ -148,7 +150,7 @@ public class DiscussionServiceImpl implements DiscussionService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void notifyUpdateNote(Discussion oldDiscussion, Discussion discussion) {
|
private void notifyUpdateNote(Discussion oldDiscussion, Discussion discussion) {
|
||||||
final Map<Long, Note> noteMap = oldDiscussion
|
final Map<Long, Note> oldNoteMap = oldDiscussion
|
||||||
.getNotes().stream()
|
.getNotes().stream()
|
||||||
.collect(Collectors.toMap(Note::getId, n -> n));
|
.collect(Collectors.toMap(Note::getId, n -> n));
|
||||||
|
|
||||||
@ -156,29 +158,31 @@ public class DiscussionServiceImpl implements DiscussionService {
|
|||||||
final boolean userParticipatedInDiscussion = oldDiscussion.getNotes().stream()
|
final boolean userParticipatedInDiscussion = oldDiscussion.getNotes().stream()
|
||||||
.anyMatch(note -> personInformation.getId().equals(note.getAuthor().getId()));
|
.anyMatch(note -> personInformation.getId().equals(note.getAuthor().getId()));
|
||||||
|
|
||||||
|
final Note threadFirstNote = discussion.getFirstNote();
|
||||||
|
if (TRUE.equals(discussion.getResolved())) {
|
||||||
|
notifyAboutCloseThread(threadFirstNote, oldNoteMap.get(threadFirstNote.getId()), discussion.getLastNote());
|
||||||
|
}
|
||||||
|
|
||||||
for (Note newNote : discussion.getNotes()) {
|
for (Note newNote : discussion.getNotes()) {
|
||||||
final Long newNoteId = newNote.getId();
|
final Long newNoteId = newNote.getId();
|
||||||
if (noteMap.containsKey(newNoteId)) {
|
if (!oldNoteMap.containsKey(newNoteId)) {
|
||||||
final Note oldNote = noteMap.get(newNoteId);
|
|
||||||
|
|
||||||
if (newNote.isResolvable()) {
|
|
||||||
updateTask(newNote, oldNote);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
if (userParticipatedInDiscussion) {
|
if (userParticipatedInDiscussion) {
|
||||||
notifyNewAnswer(discussion, newNote);
|
notifyAboutNewAnswer(discussion, newNote);
|
||||||
} else {
|
} else {
|
||||||
notificationPersonal(discussion, newNote);
|
notifyAboutPersonalAnswer(discussion, newNote);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateTask(Note note, Note oldNote) {
|
private void notifyAboutCloseThread(Note newNote, Note oldNote, Optional<Note> lastNote) {
|
||||||
if (isResolved(note, oldNote)) {
|
final DiscussionLevel level = settingService.getLevelDiscussionNotify();
|
||||||
|
if (!WITHOUT_NOTIFY.equals(level)) {
|
||||||
|
|
||||||
|
if (isResolved(newNote, oldNote)) {
|
||||||
final MergeRequestForDiscussion mergeRequest = oldNote.getDiscussion().getMergeRequest();
|
final MergeRequestForDiscussion mergeRequest = oldNote.getDiscussion().getMergeRequest();
|
||||||
|
|
||||||
final List<Discussion> discussions = getAllByMergeRequestId(mergeRequest.getId())
|
final List<Discussion> discussions = getAllByMergeRequestId(mergeRequest.getId())
|
||||||
.stream()
|
.stream()
|
||||||
.filter(discussion -> Objects.nonNull(discussion.getResponsible()))
|
.filter(discussion -> Objects.nonNull(discussion.getResponsible()))
|
||||||
@ -189,23 +193,38 @@ public class DiscussionServiceImpl implements DiscussionService {
|
|||||||
final long resolvedYouTask = discussions.stream()
|
final long resolvedYouTask = discussions.stream()
|
||||||
.filter(discussion -> personInformation.getId().equals(discussion.getFirstNote().getAuthor().getId()) && discussion.getResolved())
|
.filter(discussion -> personInformation.getId().equals(discussion.getFirstNote().getAuthor().getId()) && discussion.getResolved())
|
||||||
.count();
|
.count();
|
||||||
notifyService.send(
|
|
||||||
TaskCloseNotify.builder()
|
final ThreadCloseNotify.ThreadCloseNotifyBuilder notifyBuilder = ThreadCloseNotify.builder()
|
||||||
.authorName(oldNote.getAuthor().getName())
|
.mergeRequestName(mergeRequest.getTitle())
|
||||||
.messageTask(oldNote.getBody())
|
|
||||||
.url(oldNote.getWebUrl())
|
.url(oldNote.getWebUrl())
|
||||||
.personTasks(allYouTasks)
|
.personTasks(allYouTasks)
|
||||||
.personResolvedTasks(resolvedYouTask)
|
.personResolvedTasks(resolvedYouTask);
|
||||||
.build()
|
|
||||||
|
if (NOTIFY_WITH_CONTEXT.equals(level)) {
|
||||||
|
notifyBuilder
|
||||||
|
.authorName(oldNote.getAuthor().getName())
|
||||||
|
.messageTask(oldNote.getBody());
|
||||||
|
|
||||||
|
|
||||||
|
lastNote.ifPresent(
|
||||||
|
note -> {
|
||||||
|
notifyBuilder.authorLastNote(note.getAuthor().getName());
|
||||||
|
notifyBuilder.messageLastNote(note.getBody());
|
||||||
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
notifyService.send(notifyBuilder.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isResolved(Note note, Note oldNote) {
|
private boolean isResolved(Note note, Note oldNote) {
|
||||||
return oldNote.getResolvedBy() == null
|
return checkNull(oldNote.getResolvedBy()) // В старом комментарии не было отметки о решении
|
||||||
&& note.getResolvedBy() != null
|
&& checkNotNull(note.getResolvedBy()) // А в новом есть отметка
|
||||||
&& personInformation.getId().equals(oldNote.getAuthor().getId())
|
&& personInformation.getId().equals(oldNote.getAuthor().getId()) // и решающий не является пользователем бота
|
||||||
&& !note.getResolvedBy().getId().equals(oldNote.getAuthor().getId());
|
&& !note.getResolvedBy().getId().equals(oldNote.getAuthor().getId()); // и решающий не является автором треда
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -288,7 +307,7 @@ public class DiscussionServiceImpl implements DiscussionService {
|
|||||||
repository.notification(enable, discussionId);
|
repository.notification(enable, discussionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void notifyNewAnswer(Discussion discussion, Note note) {
|
private void notifyAboutNewAnswer(Discussion discussion, Note note) {
|
||||||
final DiscussionLevel discussionLevel = settingService.getLevelDiscussionNotify();
|
final DiscussionLevel discussionLevel = settingService.getLevelDiscussionNotify();
|
||||||
|
|
||||||
if (!WITHOUT_NOTIFY.equals(discussionLevel)
|
if (!WITHOUT_NOTIFY.equals(discussionLevel)
|
||||||
@ -322,7 +341,7 @@ public class DiscussionServiceImpl implements DiscussionService {
|
|||||||
/**
|
/**
|
||||||
* Уведомляет пользователя, если его никнейм упоминается в комментарии.
|
* Уведомляет пользователя, если его никнейм упоминается в комментарии.
|
||||||
*/
|
*/
|
||||||
private void notificationPersonal(Discussion discussion, Note note) {
|
private void notifyAboutPersonalAnswer(Discussion discussion, Note note) {
|
||||||
final DiscussionLevel discussionLevel = settingService.getLevelDiscussionNotify();
|
final DiscussionLevel discussionLevel = settingService.getLevelDiscussionNotify();
|
||||||
if (!WITHOUT_NOTIFY.equals(discussionLevel)) {
|
if (!WITHOUT_NOTIFY.equals(discussionLevel)) {
|
||||||
final Matcher matcher = PATTERN.matcher(note.getBody());
|
final Matcher matcher = PATTERN.matcher(note.getBody());
|
||||||
@ -372,10 +391,10 @@ public class DiscussionServiceImpl implements DiscussionService {
|
|||||||
final Note firstNote = discussion.getFirstNote();
|
final Note firstNote = discussion.getFirstNote();
|
||||||
|
|
||||||
final MergeRequestForDiscussion mergeRequest = discussion.getMergeRequest();
|
final MergeRequestForDiscussion mergeRequest = discussion.getMergeRequest();
|
||||||
DiscussionNewNotify.DiscussionNewNotifyBuilder messageBuilder = DiscussionNewNotify.builder()
|
final DiscussionNewNotify.DiscussionNewNotifyBuilder messageBuilder = DiscussionNewNotify.builder()
|
||||||
.url(firstNote.getWebUrl())
|
.url(firstNote.getWebUrl())
|
||||||
.threadId(discussion.getId())
|
.threadId(discussion.getId())
|
||||||
.mrName(mergeRequest.getTitle())
|
.mergeRequestName(mergeRequest.getTitle())
|
||||||
.authorName(firstNote.getAuthor().getName());
|
.authorName(firstNote.getAuthor().getName());
|
||||||
|
|
||||||
if (NOTIFY_WITH_CONTEXT.equals(discussionLevel)) {
|
if (NOTIFY_WITH_CONTEXT.equals(discussionLevel)) {
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>dev.struchkov.bot.gitlab</groupId>
|
<groupId>dev.struchkov.bot.gitlab</groupId>
|
||||||
<artifactId>gitlab-bot</artifactId>
|
<artifactId>gitlab-bot</artifactId>
|
||||||
<version>2.0.0-SNAPSHOT</version>
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>bot-data</artifactId>
|
<artifactId>bot-data</artifactId>
|
||||||
|
20
documentation/docs/ru/index.md
Normal file
20
documentation/docs/ru/index.md
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
title: Быстрый старт
|
||||||
|
hide:
|
||||||
|
- toc
|
||||||
|
- comments
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# GitLab Notification – Персональный Telegram для GitLab
|
||||||
|
Персональный помощник для взаимодействия с GitLab. Получайте уведомления о событиях в GitLab: новые MR, где вы ревьювер, конфликты в ваших MR, уведомления о новых сообщениях в тредах, где вы являетесь участником, и многое другое.
|
||||||
|
|
||||||
|
## :ninja: Privacy
|
||||||
|
|
||||||
|
Мое решение сфокусировано на приватности и прозрачности. Код и используемые зависимости полностью открыты и доступны для изучения и самостоятельной сборки.
|
||||||
|
|
||||||
|
Для работы бота токен доступа устанавливается в переменные среды и никуда не передается, кроме запросов в GitLab.
|
||||||
|
|
||||||
|
Некоторые уведомления могут содержать чуствительную информацию. Например, уведомления о новых сообщениях в тредах. Возможно вы не захотите раскрывать столько информации о ваших репозиториях Телеграму, ведь через него идет получение уведомлений. Специально для таких случаев предусмотрены уровни конфиденциальности разных типов уведомлений.
|
||||||
|
|
||||||
|
Возьмем для примера уведомление о новом сообщении в треде. При минимальном уровне конфиденциальности вы получите уведомление с текстом коментария и сможете сразу ответить на него в телеграм, а при максимальном уровне конфиденциальности будет сообщаться только о факте нового комментария, без содержания. Все это настраивается при первом запуске.
|
9
documentation/docs/ru/support-development/index.md
Normal file
9
documentation/docs/ru/support-development/index.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
title: Поддержать разработку
|
||||||
|
hide:
|
||||||
|
- comments
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
wefwef
|
@ -5,15 +5,18 @@ repo_url: https://github.com/uPagge/gitlab-notification
|
|||||||
repo_name: uPagge/gitlab-notification
|
repo_name: uPagge/gitlab-notification
|
||||||
#edit_uri: edit/master/documentation/docs
|
#edit_uri: edit/master/documentation/docs
|
||||||
|
|
||||||
|
|
||||||
nav:
|
nav:
|
||||||
- О проекте: index.md
|
- О проекте: ru/index.md
|
||||||
- Возможности: getting-started/features.md
|
- Возможности: ru/getting-started/features.md
|
||||||
- Быстрый старт:
|
- Быстрый старт:
|
||||||
- Запуск: getting-started/configuration.md
|
- Запуск: ru/getting-started/configuration.md
|
||||||
- Архитектура:
|
- Архитектура:
|
||||||
- Концепт: architecture/concept.md
|
- Концепт: ru/architecture/concept.md
|
||||||
|
- "Поддержать разработку":
|
||||||
|
- ru/support-development/index.md
|
||||||
- ChangeLog:
|
- ChangeLog:
|
||||||
- changelog/index.md
|
- ru/changelog/index.md
|
||||||
|
|
||||||
theme:
|
theme:
|
||||||
name: material
|
name: material
|
||||||
@ -88,7 +91,10 @@ extra:
|
|||||||
property: foobar
|
property: foobar
|
||||||
version:
|
version:
|
||||||
provider: mike
|
provider: mike
|
||||||
# alternate:
|
alternate:
|
||||||
# - name: Русский
|
- name: Русский
|
||||||
# link: /ru/
|
link: /ru/
|
||||||
# lang: ru
|
lang: ru
|
||||||
|
- name: English
|
||||||
|
link: /en/
|
||||||
|
lang: en
|
@ -6,7 +6,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>dev.struchkov.bot.gitlab</groupId>
|
<groupId>dev.struchkov.bot.gitlab</groupId>
|
||||||
<artifactId>gitlab-bot</artifactId>
|
<artifactId>gitlab-bot</artifactId>
|
||||||
<version>2.0.0-SNAPSHOT</version>
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>gitlab-app</artifactId>
|
<artifactId>gitlab-app</artifactId>
|
||||||
|
@ -36,7 +36,7 @@ telegram:
|
|||||||
password: ${PROXY_PASSWORD:}
|
password: ${PROXY_PASSWORD:}
|
||||||
|
|
||||||
gitlab-bot:
|
gitlab-bot:
|
||||||
version: 2.0.0-SNAPSHOT
|
version: 0.0.1-SNAPSHOT
|
||||||
person:
|
person:
|
||||||
telegram-id: ${TELEGRAM_PERSON_ID}
|
telegram-id: ${TELEGRAM_PERSON_ID}
|
||||||
token: ${GITLAB_PERSONAL_TOKEN}
|
token: ${GITLAB_PERSONAL_TOKEN}
|
||||||
|
@ -3,6 +3,6 @@
|
|||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.17.xsd">
|
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.17.xsd">
|
||||||
|
|
||||||
<include file="v.2.0.0/changelog.xml" relativeToChangelogFile="true"/>
|
<include file="v.0.0.1/changelog.xml" relativeToChangelogFile="true"/>
|
||||||
|
|
||||||
</databaseChangeLog>
|
</databaseChangeLog>
|
@ -6,7 +6,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>dev.struchkov.bot.gitlab</groupId>
|
<groupId>dev.struchkov.bot.gitlab</groupId>
|
||||||
<artifactId>gitlab-bot</artifactId>
|
<artifactId>gitlab-bot</artifactId>
|
||||||
<version>2.0.0-SNAPSHOT</version>
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>gitlab-sdk</artifactId>
|
<artifactId>gitlab-sdk</artifactId>
|
||||||
|
2
pom.xml
2
pom.xml
@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
<groupId>dev.struchkov.bot.gitlab</groupId>
|
<groupId>dev.struchkov.bot.gitlab</groupId>
|
||||||
<artifactId>gitlab-bot</artifactId>
|
<artifactId>gitlab-bot</artifactId>
|
||||||
<version>2.0.0-SNAPSHOT</version>
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
|
|
||||||
<modules>
|
<modules>
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>dev.struchkov.bot.gitlab</groupId>
|
<groupId>dev.struchkov.bot.gitlab</groupId>
|
||||||
<artifactId>gitlab-bot</artifactId>
|
<artifactId>gitlab-bot</artifactId>
|
||||||
<version>2.0.0-SNAPSHOT</version>
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>telegram-bot</artifactId>
|
<artifactId>telegram-bot</artifactId>
|
||||||
|
@ -5,24 +5,39 @@ 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.AppProperty;
|
||||||
import dev.struchkov.bot.gitlab.core.config.properties.PersonProperty;
|
import dev.struchkov.bot.gitlab.core.config.properties.PersonProperty;
|
||||||
import dev.struchkov.godfather.simple.domain.BoxAnswer;
|
import dev.struchkov.godfather.simple.domain.BoxAnswer;
|
||||||
|
import dev.struchkov.godfather.simple.domain.SentBox;
|
||||||
import dev.struchkov.godfather.telegram.simple.context.service.TelegramSending;
|
import dev.struchkov.godfather.telegram.simple.context.service.TelegramSending;
|
||||||
|
import dev.struchkov.godfather.telegram.simple.context.service.TelegramService;
|
||||||
import jakarta.annotation.PostConstruct;
|
import jakarta.annotation.PostConstruct;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.Response;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
import static dev.struchkov.bot.gitlab.telegram.utils.UnitName.DELETE_MESSAGE;
|
import static dev.struchkov.bot.gitlab.telegram.utils.UnitName.DELETE_MESSAGE;
|
||||||
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.telegram.domain.keyboard.InlineKeyBoard.inlineKeyBoard;
|
import static dev.struchkov.godfather.telegram.domain.keyboard.InlineKeyBoard.inlineKeyBoard;
|
||||||
import static dev.struchkov.godfather.telegram.main.context.BoxAnswerPayload.DISABLE_WEB_PAGE_PREVIEW;
|
import static dev.struchkov.godfather.telegram.main.context.BoxAnswerPayload.DISABLE_WEB_PAGE_PREVIEW;
|
||||||
|
import static dev.struchkov.haiti.utils.Checker.checkNotBlank;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author upagge 19.01.2021
|
* @author upagge 19.01.2021
|
||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
|
@Slf4j
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class StartNotify {
|
public class StartNotify {
|
||||||
|
|
||||||
|
private final OkHttpClient client = new OkHttpClient();
|
||||||
|
|
||||||
private final TelegramSending sending;
|
private final TelegramSending sending;
|
||||||
|
private final TelegramService telegramService;
|
||||||
|
|
||||||
private final AppProperty appProperty;
|
private final AppProperty appProperty;
|
||||||
private final AppSettingService settingService;
|
private final AppSettingService settingService;
|
||||||
private final PersonProperty personProperty;
|
private final PersonProperty personProperty;
|
||||||
@ -35,9 +50,9 @@ public class StartNotify {
|
|||||||
.message(
|
.message(
|
||||||
"Hello \uD83D\uDC4B\nI wish you a productive day \uD83C\uDF40" +
|
"Hello \uD83D\uDC4B\nI wish you a productive day \uD83C\uDF40" +
|
||||||
"\n-- -- -- -- --\n" +
|
"\n-- -- -- -- --\n" +
|
||||||
"\uD83E\uDD16 Bot Version: " + appProperty.getVersion() +
|
"\uD83E\uDD16 Version: " + appProperty.getVersion() +
|
||||||
"\n\uD83D\uDC68\u200D\uD83D\uDCBB️ Developer: [uPagge](https://mark.struchkov.dev)\n" +
|
"\n\uD83D\uDC68\u200D\uD83D\uDCBB️ Developer: [Struchkov.Dev](https://mark.struchkov.dev)\n-- -- -- -- --\n" +
|
||||||
"\uD83C\uDFE0 [HomePage](https://git.struchkov.dev/Telegram-Bots/gitlab-notification) • \uD83D\uDC1B [Issues](https://github.com/uPagge/gitlab-notification/issues) • \uD83D\uDEE3 [RoadMap](https://git.struchkov.dev/Telegram-Bots/gitlab-notification/issues)"
|
"\uD83C\uDFE0 [HomePage](https://docs.struchkov.dev/gitlab-notification/) • \uD83D\uDC1B [Report Bug](https://github.com/uPagge/gitlab-notification/issues) • \uD83C\uDD98 [Donation](https://docs.struchkov.dev/gitlab-notification/support-development/)"
|
||||||
)
|
)
|
||||||
.keyBoard(
|
.keyBoard(
|
||||||
inlineKeyBoard(
|
inlineKeyBoard(
|
||||||
@ -48,6 +63,40 @@ public class StartNotify {
|
|||||||
.payload(DISABLE_WEB_PAGE_PREVIEW, true)
|
.payload(DISABLE_WEB_PAGE_PREVIEW, true)
|
||||||
.build();
|
.build();
|
||||||
sending.send(boxAnswer);
|
sending.send(boxAnswer);
|
||||||
|
|
||||||
|
sendNotice();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Используется для уведомления пользователя о выходе новой версии.
|
||||||
|
*/
|
||||||
|
private void sendNotice() {
|
||||||
|
final String requestUrl = "https://metrika.struchkov.dev/gitlab-notify/start-notice";
|
||||||
|
final Request request = new Request.Builder()
|
||||||
|
.get()
|
||||||
|
.url(requestUrl)
|
||||||
|
.build();
|
||||||
|
try {
|
||||||
|
final Response response = client.newCall(request).execute();
|
||||||
|
if (response.code() == 200) {
|
||||||
|
final String noticeMessage = response.body().string();
|
||||||
|
if (checkNotBlank(noticeMessage)) {
|
||||||
|
final BoxAnswer notice = BoxAnswer.builder()
|
||||||
|
.message(noticeMessage)
|
||||||
|
.recipientPersonId(personProperty.getTelegramId())
|
||||||
|
// .payload(DISABLE_WEB_PAGE_PREVIEW, true)
|
||||||
|
.build();
|
||||||
|
final Optional<SentBox> optSentBox = sending.send(notice);
|
||||||
|
if (optSentBox.isPresent()) {
|
||||||
|
final SentBox sentBox = optSentBox.get();
|
||||||
|
final String messageId = sentBox.getMessageId();
|
||||||
|
telegramService.pinMessage(personProperty.getTelegramId(), messageId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.warn(e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,26 +24,22 @@ public class NewCommentNotifyGenerator implements NotifyBoxAnswerGenerator<NewCo
|
|||||||
@Override
|
@Override
|
||||||
public BoxAnswer generate(NewCommentNotify notify) {
|
public BoxAnswer generate(NewCommentNotify notify) {
|
||||||
final StringBuilder builder = new StringBuilder(Icons.COMMENT).append(" *New answer in Thread*")
|
final StringBuilder builder = new StringBuilder(Icons.COMMENT).append(" *New answer in Thread*")
|
||||||
.append(Icons.HR)
|
.append("\n-- -- -- merge request -- -- --\n")
|
||||||
.append(Icons.link(escapeMarkdown(notify.getMergeRequestName()), notify.getUrl()))
|
.append(Icons.link(escapeMarkdown(notify.getMergeRequestName()), notify.getUrl()));
|
||||||
.append("\n");
|
|
||||||
|
|
||||||
if (checkNotNull(notify.getDiscussionMessage())) {
|
if (checkNotNull(notify.getDiscussionMessage())) {
|
||||||
builder.append("\n-- -- thread first message -- --\n")
|
builder.append("\n\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\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())) {
|
if (checkNotNull(notify.getMessage())) {
|
||||||
builder.append("\n-- -- -- --- new answer --- -- -- --\n")
|
builder.append("\n\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();
|
||||||
|
@ -25,15 +25,15 @@ public class NewThreadNotifyGenerator implements NotifyBoxAnswerGenerator<Discus
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BoxAnswer generate(DiscussionNewNotify notify) {
|
public BoxAnswer generate(DiscussionNewNotify notify) {
|
||||||
final StringBuilder builder = new StringBuilder(Icons.TASK).append(" *New Thread in your MR*")
|
final StringBuilder builder = new StringBuilder(Icons.THREAD).append(" *New Thread in your MR*")
|
||||||
.append(Icons.HR)
|
.append("\n -- -- -- merge request -- -- --\n")
|
||||||
.append(Icons.link(escapeMarkdown(notify.getMrName()), notify.getUrl()))
|
.append(Icons.link(escapeMarkdown(notify.getMergeRequestName()), notify.getUrl()))
|
||||||
.append(Icons.HR)
|
.append("\n\n -- -- -- thread message -- -- --\n")
|
||||||
.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 (checkNotEmpty(notes)) {
|
if (checkNotEmpty(notes)) {
|
||||||
builder.append("\n-- -- -- -- comments -- -- -- --\n")
|
builder.append("\n\n-- -- -- -- comments -- -- -- --\n")
|
||||||
.append(convertNotes(notes));
|
.append(convertNotes(notes));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,31 +1,55 @@
|
|||||||
package dev.struchkov.bot.gitlab.telegram.service.notify;
|
package dev.struchkov.bot.gitlab.telegram.service.notify;
|
||||||
|
|
||||||
import dev.struchkov.bot.gitlab.context.domain.notify.task.TaskCloseNotify;
|
import dev.struchkov.bot.gitlab.context.domain.notify.task.ThreadCloseNotify;
|
||||||
import dev.struchkov.bot.gitlab.context.utils.Icons;
|
import dev.struchkov.bot.gitlab.context.utils.Icons;
|
||||||
import dev.struchkov.godfather.simple.domain.BoxAnswer;
|
import dev.struchkov.godfather.simple.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.UnitName.DELETE_MESSAGE;
|
||||||
|
import static dev.struchkov.godfather.main.domain.keyboard.button.SimpleButton.simpleButton;
|
||||||
import static dev.struchkov.godfather.simple.domain.BoxAnswer.boxAnswer;
|
import static dev.struchkov.godfather.simple.domain.BoxAnswer.boxAnswer;
|
||||||
|
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.checkNotBlank;
|
||||||
import static dev.struchkov.haiti.utils.Strings.escapeMarkdown;
|
import static dev.struchkov.haiti.utils.Strings.escapeMarkdown;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class ThreadCloseNotifyGenerate implements NotifyBoxAnswerGenerator<TaskCloseNotify> {
|
public class ThreadCloseNotifyGenerate implements NotifyBoxAnswerGenerator<ThreadCloseNotify> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BoxAnswer generate(TaskCloseNotify notify) {
|
public BoxAnswer generate(ThreadCloseNotify notify) {
|
||||||
|
|
||||||
final StringBuilder builder = new StringBuilder(Icons.TASK).append(" *Closed ").append(link("task", notify.getUrl()))
|
final StringBuilder builder = new StringBuilder(Icons.THREAD).append(" *Closed thread*")
|
||||||
.append(Icons.HR)
|
.append("\n-- -- -- merge request -- -- --\n")
|
||||||
.append("*").append(notify.getAuthorName()).append("*: ").append(escapeMarkdown(notify.getMessageTask()));
|
.append(escapeMarkdown(notify.getMergeRequestName()))
|
||||||
|
.append("\n");
|
||||||
|
|
||||||
|
if (checkNotBlank(notify.getAuthorName())) {
|
||||||
|
builder
|
||||||
|
.append("\n-- -- -- thread message -- -- --\n")
|
||||||
|
.append("*").append(escapeMarkdown(notify.getAuthorName())).append("*: ").append(escapeMarkdown(notify.getMessageTask()))
|
||||||
|
.append("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (checkNotBlank(notify.getAuthorLastNote())) {
|
||||||
|
builder
|
||||||
|
.append("\n-- -- -- last message -- -- -- --\n")
|
||||||
|
.append("*").append(escapeMarkdown(notify.getAuthorLastNote())).append("*: ").append(escapeMarkdown(notify.getMessageLastNote()));
|
||||||
|
}
|
||||||
|
|
||||||
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())
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getNotifyType() {
|
public String getNotifyType() {
|
||||||
return TaskCloseNotify.TYPE;
|
return ThreadCloseNotify.TYPE;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user