added ability to delete embedded posts (only the author and the person embedding can delete them) (this requires the embeds to be stored, they are deleted if the embed is deleted)

added filter to only handle reactions not done by the bot
introduced completable future to the delete message wrapper
fixed multi embed handling always resulting in two embeds
added logging of template exceptions
refactored link embed handling to be in a service instead of the listener
This commit is contained in:
Sheldan
2020-04-07 22:32:56 +02:00
parent 02a7c3633b
commit 523aaaae2a
17 changed files with 442 additions and 95 deletions

View File

@@ -1,33 +1,19 @@
package dev.sheldan.abstracto.utility.listener;
import dev.sheldan.abstracto.core.listener.MessageReceivedListener;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.management.ChannelManagementService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.service.management.UserManagementService;
import dev.sheldan.abstracto.core.models.cache.CachedMessage;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.listener.models.MessageEmbeddedModel;
import dev.sheldan.abstracto.core.service.Bot;
import dev.sheldan.abstracto.core.service.MessageCache;
import dev.sheldan.abstracto.core.models.MessageToSend;
import dev.sheldan.abstracto.templating.TemplateService;
import dev.sheldan.abstracto.core.service.management.UserManagementService;
import dev.sheldan.abstracto.utility.MessageEmbedLink;
import dev.sheldan.abstracto.utility.config.UtilityFeatures;
import dev.sheldan.abstracto.utility.service.MessageEmbedService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.TextChannel;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Nonnull;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.List;
@Component
@Slf4j
@@ -38,81 +24,24 @@ public class MessageEmbedListener implements MessageReceivedListener {
public static final String MESSAGE_EMBED_TEMPLATE = "message";
private Pattern messageRegex = Pattern.compile("(?<whole>https://discordapp.com/channels/(?<server>\\d+)/(?<channel>\\d+)/(?<message>\\d+)(?:.*?))+");
@Autowired
private MessageEmbedListener self;
@Autowired
private ChannelManagementService channelManagementService;
@Autowired
private ServerManagementService serverManagementService;
@Autowired
private UserManagementService userManagementService;
@Autowired
private Bot bot;
@Autowired
private TemplateService templateService;
@Autowired
private ChannelService channelService;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void createEmbedAndPostEmbed(@Nonnull Message postedMessage, CachedMessage message) {
MessageEmbeddedModel messageEmbeddedModel = buildTemplateParameter(postedMessage, message);
MessageToSend embed = templateService.renderEmbedTemplate(MESSAGE_EMBED_TEMPLATE, messageEmbeddedModel);
channelService.sendMessageToEndInTextChannel(embed, postedMessage.getTextChannel());
}
private MessageEmbeddedModel buildTemplateParameter(Message message, CachedMessage embeddedMessage) {
AChannel channel = channelManagementService.loadChannel(message.getChannel().getIdLong());
AServer server = serverManagementService.loadOrCreate(message.getGuild().getIdLong());
AUserInAServer user = userManagementService.loadUser(message.getMember());
Member author = bot.getMemberInServer(embeddedMessage.getServerId(), embeddedMessage.getAuthorId());
TextChannel sourceChannel = bot.getTextChannelFromServer(embeddedMessage.getServerId(), embeddedMessage.getChannelId()).get();
return MessageEmbeddedModel
.builder()
.channel(channel)
.server(server)
.member(message.getMember())
.aUserInAServer(user)
.author(author)
.sourceChannel(sourceChannel)
.embeddingUser(message.getMember())
.user(user.getUserReference())
.messageChannel(message.getChannel())
.guild(message.getGuild())
.embeddedMessage(embeddedMessage)
.build();
}
private MessageEmbedService messageEmbedService;
@Override
public void execute(Message message) {
String messageRaw = message.getContentRaw();
Matcher matcher = messageRegex.matcher(messageRaw);
boolean matched = false;
while(matcher.find()) {
matched = true;
String serverId = matcher.group("server");
String channelId = matcher.group("channel");
String messageId = matcher.group("message");
String wholeLink = matcher.group("whole");
if(message.getGuild().getId().equals(serverId)) {
Long serverIdLong = Long.parseLong(serverId);
Long channelIdLong = Long.parseLong(channelId);
Long messageIdLong = Long.parseLong(messageId);
messageRaw = messageRaw.replace(wholeLink, "");
messageCache.getMessageFromCache(serverIdLong, channelIdLong, messageIdLong).thenAccept(cachedMessage -> {
self.createEmbedAndPostEmbed(message, cachedMessage);
});
}
List<MessageEmbedLink> links = messageEmbedService.getLinksInMessage(messageRaw);
for (MessageEmbedLink messageEmbedLink : links) {
messageRaw = messageRaw.replace(messageEmbedLink.getWholeUrl(), "");
AUserInAServer cause = userManagementService.loadUser(message.getMember());
messageCache.getMessageFromCache(messageEmbedLink.getServerId(), messageEmbedLink.getChannelId(), messageEmbedLink.getMessageId()).thenAccept(cachedMessage -> {
messageEmbedService.embedLink(cachedMessage, message.getTextChannel(), cause, message);
});
}
if(StringUtils.isBlank(messageRaw) && matched) {
if(StringUtils.isBlank(messageRaw) && links.size() > 0) {
message.delete().queue();
}
}

View File

@@ -0,0 +1,71 @@
package dev.sheldan.abstracto.utility.listener;
import dev.sheldan.abstracto.core.listener.ReactedAddedListener;
import dev.sheldan.abstracto.core.models.cache.CachedMessage;
import dev.sheldan.abstracto.core.models.database.AEmote;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.service.Bot;
import dev.sheldan.abstracto.core.service.MessageService;
import dev.sheldan.abstracto.core.service.management.EmoteManagementService;
import dev.sheldan.abstracto.core.utils.EmoteUtils;
import dev.sheldan.abstracto.utility.config.UtilityFeatures;
import dev.sheldan.abstracto.utility.models.EmbeddedMessage;
import dev.sheldan.abstracto.utility.service.management.MessageEmbedPostManagementService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Emote;
import net.dv8tion.jda.api.entities.MessageReaction;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Optional;
@Component
@Slf4j
public class MessageEmbedRemovalReactionListener implements ReactedAddedListener {
public static final String REMOVAL_EMOTE = "removeEmbed";
@Autowired
private EmoteManagementService emoteManagementService;
@Autowired
private Bot bot;
@Autowired
private MessageEmbedPostManagementService messageEmbedPostManagementService;
@Autowired
private MessageService messageService;
@Override
public void executeReactionAdded(CachedMessage message, MessageReaction reaction, AUserInAServer userAdding) {
Long guildId = message.getServerId();
Optional<AEmote> aEmote = emoteManagementService.loadEmoteByName(REMOVAL_EMOTE, guildId);
if(aEmote.isPresent()) {
AEmote emote = aEmote.get();
MessageReaction.ReactionEmote reactionEmote = reaction.getReactionEmote();
Optional<Emote> emoteInGuild = bot.getEmote(guildId, emote);
if(EmoteUtils.isReactionEmoteAEmote(reactionEmote, emote, emoteInGuild.orElse(null))) {
Optional<EmbeddedMessage> embeddedMessageOptional = messageEmbedPostManagementService.findEmbeddedPostByMessageId(message.getMessageId());
if(embeddedMessageOptional.isPresent()) {
EmbeddedMessage embeddedMessage = embeddedMessageOptional.get();
if(embeddedMessage.getEmbeddedUser().getUserReference().getId().equals(userAdding.getUserReference().getId())
|| embeddedMessage.getEmbeddingUser().getUserReference().getId().equals(userAdding.getUserReference().getId())
) {
messageService.deleteMessageInChannelInServer(message.getServerId(), message.getChannelId(), message.getMessageId()).thenAccept(aVoid -> {
messageEmbedPostManagementService.deleteEmbeddedMessageTransactional(embeddedMessage);
});
}
}
}
} else {
log.warn("Emote {} is not defined for guild {}. Embed link deletion not functional.", REMOVAL_EMOTE, guildId);
}
}
@Override
public String getFeature() {
return UtilityFeatures.LINK_EMBEDS;
}
}

View File

@@ -0,0 +1,8 @@
package dev.sheldan.abstracto.utility.repository;
import dev.sheldan.abstracto.utility.models.EmbeddedMessage;
import org.springframework.data.jpa.repository.JpaRepository;
public interface EmbeddedMessageRepository extends JpaRepository<EmbeddedMessage, Long> {
EmbeddedMessage findByEmbeddingMessageId(Long messageId);
}

View File

@@ -0,0 +1,147 @@
package dev.sheldan.abstracto.utility.service;
import dev.sheldan.abstracto.core.listener.models.MessageEmbeddedModel;
import dev.sheldan.abstracto.core.models.MessageToSend;
import dev.sheldan.abstracto.core.models.cache.CachedMessage;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.service.Bot;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.MessageCache;
import dev.sheldan.abstracto.core.service.MessageService;
import dev.sheldan.abstracto.core.service.management.ChannelManagementService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.service.management.UserManagementService;
import dev.sheldan.abstracto.templating.TemplateService;
import dev.sheldan.abstracto.utility.MessageEmbedLink;
import dev.sheldan.abstracto.utility.service.management.MessageEmbedPostManagementService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.TextChannel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Component
@Slf4j
public class MessageEmbedServiceBean implements MessageEmbedService {
private Pattern messageRegex = Pattern.compile("(?<whole>https://discordapp.com/channels/(?<server>\\d+)/(?<channel>\\d+)/(?<message>\\d+)(?:.*?))+");
public static final String MESSAGE_EMBED_TEMPLATE = "message";
public static final String REMOVAL_EMOTE = "removeEmbed";
@Autowired
private ChannelManagementService channelManagementService;
@Autowired
private ServerManagementService serverManagementService;
@Autowired
private UserManagementService userManagementService;
@Autowired
private Bot bot;
@Autowired
private TemplateService templateService;
@Autowired
private ChannelService channelService;
@Autowired
private MessageEmbedService self;
@Autowired
private MessageCache messageCache;
@Autowired
private MessageEmbedPostManagementService messageEmbedPostManagementService;
@Autowired
private MessageService messageService;
@Override
public List<MessageEmbedLink> getLinksInMessage(String message) {
List<MessageEmbedLink> links = new ArrayList<>();
Matcher matcher = messageRegex.matcher(message);
while(matcher.find()) {
String serverId = matcher.group("server");
String channelId = matcher.group("channel");
String messageId = matcher.group("message");
String wholeLink = matcher.group("whole");
Long serverIdLong = Long.parseLong(serverId);
Long channelIdLong = Long.parseLong(channelId);
Long messageIdLong = Long.parseLong(messageId);
MessageEmbedLink messageEmbedLink = MessageEmbedLink
.builder()
.serverId(serverIdLong)
.channelId(channelIdLong)
.messageId(messageIdLong)
.wholeUrl(wholeLink)
.build();
links.add(messageEmbedLink);
}
return links;
}
@Override
public void embedLinks(List<MessageEmbedLink> linksToEmbed, TextChannel target, AUserInAServer reason, Message embeddingMessage) {
linksToEmbed.forEach(messageEmbedLink -> {
messageCache.getMessageFromCache(messageEmbedLink.getServerId(), messageEmbedLink.getChannelId(), messageEmbedLink.getMessageId())
.thenAccept(cachedMessage -> {
self.embedLink(cachedMessage, target, reason, embeddingMessage);
});
});
}
@Override
@Transactional
public void embedLink(CachedMessage cachedMessage, TextChannel target, AUserInAServer cause, Message embeddingMessage) {
MessageEmbeddedModel messageEmbeddedModel = buildTemplateParameter(embeddingMessage, cachedMessage);
MessageToSend embed = templateService.renderEmbedTemplate(MESSAGE_EMBED_TEMPLATE, messageEmbeddedModel);
List<CompletableFuture<Message>> completableFutures = channelService.sendMessageToEndInTextChannel(embed, target);
CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture[0])).thenAccept(aVoid -> {
try {
Message createdMessage = completableFutures.get(0).get();
messageEmbedPostManagementService.createMessageEmbed(cachedMessage, createdMessage, cause);
messageService.addReactionToMessage(REMOVAL_EMOTE, cachedMessage.getServerId(), createdMessage);
} catch (InterruptedException | ExecutionException e) {
log.error("Failed to post message embed.", e);
}
});
}
private MessageEmbeddedModel buildTemplateParameter(Message message, CachedMessage embeddedMessage) {
AChannel channel = channelManagementService.loadChannel(message.getChannel().getIdLong());
AServer server = serverManagementService.loadOrCreate(message.getGuild().getIdLong());
AUserInAServer user = userManagementService.loadUser(message.getMember());
Member author = bot.getMemberInServer(embeddedMessage.getServerId(), embeddedMessage.getAuthorId());
TextChannel sourceChannel = bot.getTextChannelFromServer(embeddedMessage.getServerId(), embeddedMessage.getChannelId()).get();
return MessageEmbeddedModel
.builder()
.channel(channel)
.server(server)
.member(message.getMember())
.aUserInAServer(user)
.author(author)
.sourceChannel(sourceChannel)
.embeddingUser(message.getMember())
.user(user.getUserReference())
.messageChannel(message.getChannel())
.guild(message.getGuild())
.embeddedMessage(embeddedMessage)
.build();
}
}

View File

@@ -0,0 +1,71 @@
package dev.sheldan.abstracto.utility.service.management;
import dev.sheldan.abstracto.core.models.cache.CachedMessage;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.service.MessageService;
import dev.sheldan.abstracto.core.service.management.UserManagementService;
import dev.sheldan.abstracto.utility.models.EmbeddedMessage;
import dev.sheldan.abstracto.utility.repository.EmbeddedMessageRepository;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Message;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.Optional;
@Component
@Slf4j
public class MessageEmbedPostManagementServiceBean implements MessageEmbedPostManagementService {
@Autowired
private EmbeddedMessageRepository embeddedMessageRepository;
@Autowired
private UserManagementService userManagementService;
@Autowired
private MessageService messageService;
@Override
@Transactional
public void createMessageEmbed(CachedMessage embeddedMessage, Message messageContainingEmbed, AUserInAServer cause) {
AServer embeddedServer = AServer.builder().id(embeddedMessage.getServerId()).build();
AChannel embeddedChannel = AChannel.builder().id(embeddedMessage.getChannelId()).build();
AServer embeddingServer = AServer.builder().id(messageContainingEmbed.getGuild().getIdLong()).build();
AChannel embeddingChannel = AChannel.builder().id(messageContainingEmbed.getTextChannel().getIdLong()).build();
AUserInAServer embeddedAuthor = userManagementService.loadUser(embeddedMessage.getServerId(), embeddedMessage.getAuthorId());
EmbeddedMessage messageEmbedPost = EmbeddedMessage
.builder()
.embeddedMessageId(embeddedMessage.getMessageId())
.embeddedChannel(embeddedChannel)
.embeddedServer(embeddedServer)
.embeddingServer(embeddingServer)
.embeddingChannel(embeddingChannel)
.embeddingMessageId(messageContainingEmbed.getIdLong())
.embeddedUser(embeddedAuthor)
.embeddingUser(cause)
.build();
embeddedMessageRepository.save(messageEmbedPost);
}
@Override
public Optional<EmbeddedMessage> findEmbeddedPostByMessageId(Long messageId) {
return Optional.ofNullable(embeddedMessageRepository.findByEmbeddingMessageId(messageId));
}
@Override
public void deleteEmbeddedMessage(EmbeddedMessage embeddedMessage) {
embeddedMessageRepository.delete(embeddedMessage);
}
@Override
@Transactional
public void deleteEmbeddedMessageTransactional(EmbeddedMessage embeddedMessage) {
this.deleteEmbeddedMessage(embeddedMessage);
}
}

View File

@@ -0,0 +1,31 @@
{
"author": {
"name": "${author.effectiveName}",
"avatar": "${author.user.effectiveAvatarUrl}"
},
"color" : {
"r": 200,
"g": 0,
"b": 255
},
<#if embeddedMessage.content?has_content || embeddedMessage.embeds?size gt 0>
"description": "${embeddedMessage.content}
<#list embeddedMessage.embeds>
Embeds:
<#items as embed>
Description: ${embed.description} <#if embed.imageUrl?has_content> ImageUrl: ${embed.imageUrl} </#if>
</#items>
</#list>
",
</#if>
<#if embeddedMessage.attachmentUrls?size gt 0>
"imageUrl": "${embeddedMessage.attachmentUrls[0]}",
</#if>
"fields": [
{
"name": "Quoted by",
"value": "${embeddingUser.asMention} from [${sourceChannel.name}](${embeddedMessage.messageUrl})"
}
],
"timeStamp": "${embeddedMessage.timeCreated}"
}

View File

@@ -1,6 +1,7 @@
abstracto.postTargets.utility=suggestions,starboard
abstracto.emoteNames.suggestion=suggestionYes,suggestionNo
abstracto.emoteNames.starboard=star,star1,star2,star3,star4,starboardBadge1,starboardBadge2,starboardBadge3
abstracto.emoteNames.embed=removeEmbed
abstracto.starboard.lvl[0]=5
abstracto.starboard.lvl[1]=8
abstracto.starboard.lvl[2]=13