From 5d841df77638c3b7d25e29993e045918974579f5 Mon Sep 17 00:00:00 2001 From: jaeyeonling Date: Wed, 10 Jul 2024 13:18:13 +0900 Subject: [PATCH] change the pr merge method from command to message --- .../GithubPullRequestMergeCommand.java | 122 ------------------ .../discord/command/PingPongCommand.java | 1 - .../discord/{ => command}/SlashCommand.java | 2 +- .../SlashCommandListenerMapper.java | 2 +- .../{ => command}/SlashCommandRegistry.java | 2 +- .../GithubPullRequestMergeSubscription.java | 101 +++++++++++++++ .../message/MergeSubscriptionMapper.java | 42 ++++++ .../discord/message/MessageSubscription.java | 10 ++ .../MessagesConfiguration.java} | 6 +- .../MessagesProperties.java} | 6 +- src/main/resources/application.yml | 2 +- 11 files changed, 163 insertions(+), 133 deletions(-) delete mode 100644 src/main/java/cholog/discord/command/GithubPullRequestMergeCommand.java rename src/main/java/cholog/discord/{ => command}/SlashCommand.java (92%) rename src/main/java/cholog/discord/{ => command}/SlashCommandListenerMapper.java (97%) rename src/main/java/cholog/discord/{ => command}/SlashCommandRegistry.java (97%) create mode 100644 src/main/java/cholog/discord/message/GithubPullRequestMergeSubscription.java create mode 100644 src/main/java/cholog/discord/message/MergeSubscriptionMapper.java create mode 100644 src/main/java/cholog/discord/message/MessageSubscription.java rename src/main/java/cholog/discord/{command/CommandsConfiguration.java => message/MessagesConfiguration.java} (56%) rename src/main/java/cholog/discord/{command/CommandsProperties.java => message/MessagesProperties.java} (86%) diff --git a/src/main/java/cholog/discord/command/GithubPullRequestMergeCommand.java b/src/main/java/cholog/discord/command/GithubPullRequestMergeCommand.java deleted file mode 100644 index 4473e46..0000000 --- a/src/main/java/cholog/discord/command/GithubPullRequestMergeCommand.java +++ /dev/null @@ -1,122 +0,0 @@ -package cholog.discord.command; - -import cholog.discord.SlashCommand; -import cholog.github.GithubApi; -import cholog.github.PullRequest; -import cholog.github.PullRequestMergeRequest; -import cholog.github.PullRequestUrl; -import net.dv8tion.jda.api.JDA; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import net.dv8tion.jda.api.interactions.commands.OptionType; -import net.dv8tion.jda.api.interactions.commands.build.OptionData; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Component; - -import java.util.List; -import java.util.Objects; - -import static java.util.Objects.requireNonNull; - -@Component -public final class GithubPullRequestMergeCommand implements SlashCommand { - - private static final Logger log = LoggerFactory.getLogger(GithubPullRequestMergeCommand.class); - - private static final String PR_URL_OPTION_NAME = "pr-url"; - - private final CommandsProperties properties; - - private final GithubApi githubApi; - - public GithubPullRequestMergeCommand( - final CommandsProperties properties, - final GithubApi githubApi - ) { - this.properties = properties; - this.githubApi = githubApi; - - } - - @Override - public String commandName() { - return "pr-merge"; - } - - @Override - public String description() { - return "Github PR 머지 요청을 합니다."; - } - - @Override - public List options() { - return List.of( - new OptionData(OptionType.STRING, PR_URL_OPTION_NAME, "Github PR URL", true) - ); - } - - @Override - public void onEvent(final SlashCommandInteractionEvent event) { - final var userInfo = event.getUser().getEffectiveName() + "(" + event.getUser().getName() + ")"; - final var url = requireNonNull(event.getOption(PR_URL_OPTION_NAME)).getAsString().trim(); - final var pullRequestUrl = new PullRequestUrl(url); - - if (properties.isDisallowOrganization(pullRequestUrl.owner())) { - event.reply(pullRequestUrl.owner() + "는 허용하지 않는 조직입니다.").queue(); - final var warningMessage = "[경고] " + userInfo + " 유저가 " + url + " PR을 머지하려 했습니다."; - sendToMergeChannel(event.getJDA(), warningMessage); - return; - } - if (properties.isDisallowRepository(pullRequestUrl.repository())) { - event.reply(pullRequestUrl.repository() + "는 허용하지 않는 저장소입니다.").queue(); - final var warningMessage = "[경고] " + userInfo + " 유저가 " + url + " PR을 머지하려 했습니다."; - sendToMergeChannel(event.getJDA(), warningMessage); - return; - } - - final var pullRequest = githubApi.getPullRequest( - pullRequestUrl.owner(), - pullRequestUrl.repository(), - pullRequestUrl.pullNumber() - ); - if (pullRequest.state() == PullRequest.PullRequestState.CLOSED) { - event.reply("이미 닫힌 PR입니다.").queue(); - return; - } - - final var targetBranch = pullRequest.base().ref(); - if (properties.isDisallowBranch(targetBranch)) { - event.reply(targetBranch + "브랜치에는 머지할 수 없습니다.").queue(); - return; - } - - final var mergeRequest = new PullRequestMergeRequest( - "고생하셨습니다.", - ":tada: PR 머지 완료! :tada:", - pullRequest.head().sha(), - PullRequestMergeRequest.MergeMethod.SQUASH - ); - final var mergeResult = githubApi.mergePullRequest( - pullRequestUrl.owner(), - pullRequestUrl.repository(), - pullRequestUrl.pullNumber(), - mergeRequest - ); - if (mergeResult.merged()) { - event.reply("PR 머지 완료!").queue(); - final var successMessage = userInfo + " 유저가 " + url + " PR을 머지했습니다."; - sendToMergeChannel(event.getJDA(), successMessage); - } else { - event.reply("[실패] " + mergeResult.message()).queue(); - log.warn("fail to merge [user={}, url={}, message={}]", userInfo, url, mergeResult.message()); - } - } - - private void sendToMergeChannel( - final JDA jda, - final String message - ) { - final var channel = Objects.requireNonNull(jda.getTextChannelById(properties.prMergeChannelId())); - channel.sendMessage(message).queue(); - } -} diff --git a/src/main/java/cholog/discord/command/PingPongCommand.java b/src/main/java/cholog/discord/command/PingPongCommand.java index 82ea1b1..ebc1c31 100644 --- a/src/main/java/cholog/discord/command/PingPongCommand.java +++ b/src/main/java/cholog/discord/command/PingPongCommand.java @@ -1,6 +1,5 @@ package cholog.discord.command; -import cholog.discord.SlashCommand; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import org.springframework.stereotype.Component; diff --git a/src/main/java/cholog/discord/SlashCommand.java b/src/main/java/cholog/discord/command/SlashCommand.java similarity index 92% rename from src/main/java/cholog/discord/SlashCommand.java rename to src/main/java/cholog/discord/command/SlashCommand.java index 5a54fa0..506e73f 100644 --- a/src/main/java/cholog/discord/SlashCommand.java +++ b/src/main/java/cholog/discord/command/SlashCommand.java @@ -1,4 +1,4 @@ -package cholog.discord; +package cholog.discord.command; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.interactions.commands.build.OptionData; diff --git a/src/main/java/cholog/discord/SlashCommandListenerMapper.java b/src/main/java/cholog/discord/command/SlashCommandListenerMapper.java similarity index 97% rename from src/main/java/cholog/discord/SlashCommandListenerMapper.java rename to src/main/java/cholog/discord/command/SlashCommandListenerMapper.java index 01b7c1b..dd1ccbe 100644 --- a/src/main/java/cholog/discord/SlashCommandListenerMapper.java +++ b/src/main/java/cholog/discord/command/SlashCommandListenerMapper.java @@ -1,4 +1,4 @@ -package cholog.discord; +package cholog.discord.command; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.hooks.ListenerAdapter; diff --git a/src/main/java/cholog/discord/SlashCommandRegistry.java b/src/main/java/cholog/discord/command/SlashCommandRegistry.java similarity index 97% rename from src/main/java/cholog/discord/SlashCommandRegistry.java rename to src/main/java/cholog/discord/command/SlashCommandRegistry.java index 2871cc8..e700177 100644 --- a/src/main/java/cholog/discord/SlashCommandRegistry.java +++ b/src/main/java/cholog/discord/command/SlashCommandRegistry.java @@ -1,4 +1,4 @@ -package cholog.discord; +package cholog.discord.command; import jakarta.annotation.PostConstruct; import net.dv8tion.jda.api.entities.Guild; diff --git a/src/main/java/cholog/discord/message/GithubPullRequestMergeSubscription.java b/src/main/java/cholog/discord/message/GithubPullRequestMergeSubscription.java new file mode 100644 index 0000000..6433a17 --- /dev/null +++ b/src/main/java/cholog/discord/message/GithubPullRequestMergeSubscription.java @@ -0,0 +1,101 @@ +package cholog.discord.message; + +import cholog.github.GithubApi; +import cholog.github.PullRequest; +import cholog.github.PullRequestMergeRequest; +import cholog.github.PullRequestUrl; +import net.dv8tion.jda.api.events.message.MessageReceivedEvent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Objects; + +@Component +public final class GithubPullRequestMergeSubscription implements MessageSubscription { + + private static final Logger log = LoggerFactory.getLogger(GithubPullRequestMergeSubscription.class); + + private final MessagesProperties properties; + + private final GithubApi githubApi; + + public GithubPullRequestMergeSubscription( + final MessagesProperties properties, + final GithubApi githubApi + ) { + this.properties = properties; + this.githubApi = githubApi; + } + + @Override + public String channelId() { + return properties.prMergeChannelId(); + } + + @Override + public void onEvent(final MessageReceivedEvent event) { + final var message = event.getMessage(); + final var urls = Arrays.stream(message.getContentRaw().split("\\s+")) + .map(String::trim) + .filter(it -> it.startsWith("http:")) + .toList(); + + final var fails = new HashMap(); + for (final String url : urls) { + final var pullRequestUrl = new PullRequestUrl(url); + + if (properties.isDisallowOrganization(pullRequestUrl.owner())) { + fails.put(url, pullRequestUrl.owner() + "는 허용하지 않는 조직입니다."); + continue; + } + if (properties.isDisallowRepository(pullRequestUrl.repository())) { + fails.put(url, pullRequestUrl.repository() + "는 허용하지 않는 저장소입니다."); + continue; + } + + final var pullRequest = githubApi.getPullRequest( + pullRequestUrl.owner(), + pullRequestUrl.repository(), + pullRequestUrl.pullNumber() + ); + if (pullRequest.state() == PullRequest.PullRequestState.CLOSED) { + fails.put(url, "이미 닫힌 PR입니다."); + continue; + } + + final var targetBranch = pullRequest.base().ref(); + if (properties.isDisallowBranch(targetBranch)) { + fails.put(url, targetBranch + "브랜치에는 머지할 수 없습니다."); + return; + } + + final var mergeRequest = new PullRequestMergeRequest( + "고생하셨습니다.", + ":tada: PR 머지 완료! :tada:", + pullRequest.head().sha(), + PullRequestMergeRequest.MergeMethod.SQUASH + ); + final var mergeResult = githubApi.mergePullRequest( + pullRequestUrl.owner(), + pullRequestUrl.repository(), + pullRequestUrl.pullNumber(), + mergeRequest + ); + if (!mergeResult.merged()) { + fails.put(url, mergeResult.message()); + } + } + + final var doneEmoji = Objects.requireNonNull(message.getJDA().getEmojiById(":done:")); + message.addReaction(doneEmoji).queue(); + if (!fails.isEmpty()) { + final var failMessages = fails.entrySet().stream() + .map(entry -> entry.getKey() + ": " + entry.getValue()) + .collect(StringBuilder::new, StringBuilder::append, StringBuilder::append); + message.reply(failMessages).queue(); + } + } +} diff --git a/src/main/java/cholog/discord/message/MergeSubscriptionMapper.java b/src/main/java/cholog/discord/message/MergeSubscriptionMapper.java new file mode 100644 index 0000000..c903b8a --- /dev/null +++ b/src/main/java/cholog/discord/message/MergeSubscriptionMapper.java @@ -0,0 +1,42 @@ +package cholog.discord.message; + +import net.dv8tion.jda.api.events.message.MessageReceivedEvent; +import net.dv8tion.jda.api.hooks.ListenerAdapter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.lang.NonNull; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Map; + +import static java.util.stream.Collectors.toMap; + +@Component +public final class MergeSubscriptionMapper extends ListenerAdapter { + + private static final Logger log = LoggerFactory.getLogger(MergeSubscriptionMapper.class); + + private final Map messageSubscriptions; + + MergeSubscriptionMapper(final List messageSubscriptions) { + this.messageSubscriptions = messageSubscriptions.stream() + .collect(toMap(MessageSubscription::channelId, command -> command)); + } + + @Override + public void onMessageReceived(@NonNull final MessageReceivedEvent event) { + if (event.getAuthor().isBot()) { + log.debug("Bot message"); + return; + } + + final var channelId = event.getChannel().getId(); + final var messageSubscription = messageSubscriptions.get(channelId); + if (messageSubscription == null) { + log.debug("Message subscription not found [channelId={}, message={}]", channelId, event.getMessage()); + return; + } + messageSubscription.onEvent(event); + } +} diff --git a/src/main/java/cholog/discord/message/MessageSubscription.java b/src/main/java/cholog/discord/message/MessageSubscription.java new file mode 100644 index 0000000..3d6b6a2 --- /dev/null +++ b/src/main/java/cholog/discord/message/MessageSubscription.java @@ -0,0 +1,10 @@ +package cholog.discord.message; + +import net.dv8tion.jda.api.events.message.MessageReceivedEvent; + +public interface MessageSubscription { + + String channelId(); + + void onEvent(MessageReceivedEvent event); +} diff --git a/src/main/java/cholog/discord/command/CommandsConfiguration.java b/src/main/java/cholog/discord/message/MessagesConfiguration.java similarity index 56% rename from src/main/java/cholog/discord/command/CommandsConfiguration.java rename to src/main/java/cholog/discord/message/MessagesConfiguration.java index 3237b87..3172713 100644 --- a/src/main/java/cholog/discord/command/CommandsConfiguration.java +++ b/src/main/java/cholog/discord/message/MessagesConfiguration.java @@ -1,10 +1,10 @@ -package cholog.discord.command; +package cholog.discord.message; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Configuration; -@EnableConfigurationProperties(CommandsProperties.class) +@EnableConfigurationProperties(MessagesProperties.class) @Configuration -public class CommandsConfiguration { +public class MessagesConfiguration { } diff --git a/src/main/java/cholog/discord/command/CommandsProperties.java b/src/main/java/cholog/discord/message/MessagesProperties.java similarity index 86% rename from src/main/java/cholog/discord/command/CommandsProperties.java rename to src/main/java/cholog/discord/message/MessagesProperties.java index 28256d6..b94304b 100644 --- a/src/main/java/cholog/discord/command/CommandsProperties.java +++ b/src/main/java/cholog/discord/message/MessagesProperties.java @@ -1,11 +1,11 @@ -package cholog.discord.command; +package cholog.discord.message; import org.springframework.boot.context.properties.ConfigurationProperties; import java.util.List; -@ConfigurationProperties(prefix = "cholog.jda.commands") -public record CommandsProperties( +@ConfigurationProperties(prefix = "cholog.jda.subscriptions") +public record MessagesProperties( PrMergeProperties prMerge ) { diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 12e9b18..4a1b886 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -10,7 +10,7 @@ cholog: jda: token: guild-id: - commands: + subscriptions: pr-merge: channel-id: allow-organizations: