mirror of
https://github.com/Sheldan/abstracto.git
synced 2026-04-01 23:53:27 +00:00
added some utility methods to bot service
reworked message cache to not be jda messages, but own objects: cachedMessages, jda messages should not be kept around added listener to listen for message links, to represent them in an embed (or embeds) added offsetDateTime handling for gson introduced a way on how to handle async transaction handling for suggestions and message loading (self reference with another method with transactional) added timestamp support to embeds
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
package dev.sheldan.abstracto.config;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
@Configuration
|
||||
public class CoreConfig {
|
||||
@Bean
|
||||
public Gson gson() {
|
||||
return new GsonBuilder()
|
||||
.registerTypeAdapter(OffsetDateTime.class, new OffsetDateTimeAdapter())
|
||||
.setPrettyPrinting().create();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package dev.sheldan.abstracto.config;
|
||||
|
||||
import com.google.gson.*;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.time.Instant;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.temporal.TemporalAccessor;
|
||||
|
||||
public class OffsetDateTimeAdapter implements JsonDeserializer<OffsetDateTime> {
|
||||
|
||||
@Override
|
||||
public OffsetDateTime deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
|
||||
TemporalAccessor ta = DateTimeFormatter.ISO_INSTANT.parse(jsonElement.getAsString());
|
||||
Instant i = Instant.from(ta);
|
||||
return OffsetDateTime.ofInstant(i, ZoneId.systemDefault());
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,11 @@
|
||||
package dev.sheldan.abstracto.core.service;
|
||||
|
||||
import dev.sheldan.abstracto.core.models.ServerChannelUser;
|
||||
import net.dv8tion.jda.api.JDA;
|
||||
import net.dv8tion.jda.api.JDABuilder;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.entities.TextChannel;
|
||||
import net.dv8tion.jda.api.utils.cache.CacheFlag;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@@ -28,6 +32,50 @@ public class BotService implements Bot {
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerChannelUser getServerChannelUser(Long serverId, Long channelId, Long userId) {
|
||||
TextChannel textChannelById = getTextChannelFromServer(serverId, channelId);
|
||||
Guild guildById = getGuildById(serverId);
|
||||
if(textChannelById != null) {
|
||||
Member member = guildById.getMemberById(userId);
|
||||
return ServerChannelUser.builder().guild(guildById).textChannel(textChannelById).member(member).build();
|
||||
}
|
||||
throw new RuntimeException(String.format("Member %s or text channel %s not found in guild %s", userId, channelId, serverId));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Member getMemberInServer(Long serverId, Long memberId) {
|
||||
Guild guildById = instance.getGuildById(serverId);
|
||||
if(guildById != null) {
|
||||
return guildById.getMemberById(memberId);
|
||||
} else {
|
||||
throw new RuntimeException(String.format("Member %s not found in guild %s", memberId, serverId));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextChannel getTextChannelFromServer(Long serverId, Long textChannelId) {
|
||||
Guild guild = getGuildById(serverId);
|
||||
TextChannel textChannelById = guild.getTextChannelById(textChannelId);
|
||||
if(textChannelById != null) {
|
||||
return textChannelById;
|
||||
} else {
|
||||
throw new RuntimeException(String.format("Text channel %s in guild %s not found", textChannelId, serverId));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Guild getGuildById(Long serverId) {
|
||||
Guild guildById = instance.getGuildById(serverId);
|
||||
if(guildById != null) {
|
||||
return guildById;
|
||||
} else {
|
||||
throw new RuntimeException(String.format("Guild %s not found", serverId));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package dev.sheldan.abstracto.core.service;
|
||||
|
||||
import dev.sheldan.abstracto.core.models.CachedMessage;
|
||||
import dev.sheldan.abstracto.core.models.embed.*;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import net.dv8tion.jda.api.entities.Message;
|
||||
import net.dv8tion.jda.api.entities.MessageEmbed;
|
||||
import net.dv8tion.jda.api.entities.TextChannel;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cache.annotation.CacheConfig;
|
||||
@@ -10,6 +12,12 @@ import org.springframework.cache.annotation.CachePut;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
@Component @Slf4j
|
||||
@CacheConfig(cacheNames = {"messages"})
|
||||
public class MessageCacheBean implements MessageCache {
|
||||
@@ -19,38 +27,110 @@ public class MessageCacheBean implements MessageCache {
|
||||
|
||||
@Override
|
||||
@CachePut(key = "#message.id")
|
||||
public Message putMessageInCache(Message message) {
|
||||
public CachedMessage putMessageInCache(Message message) {
|
||||
log.debug("Adding message {} to cache", message.getId());
|
||||
return buildCachedMessageFromMessage(message);
|
||||
}
|
||||
|
||||
@CachePut(key = "#message.messageId")
|
||||
public CachedMessage putMessageInCache(CachedMessage message) {
|
||||
return message;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(key = "#message.id")
|
||||
public Message getMessageFromCache(Message message) {
|
||||
public CachedMessage getMessageFromCache(Message message) throws ExecutionException, InterruptedException {
|
||||
log.debug("Retrieving message {}", message.getId());
|
||||
return getMessageInTextChannelOfGuild(message.getIdLong(), message.getTextChannel().getIdLong(), message.getGuild().getIdLong());
|
||||
return getMessageFromCache(message.getGuild().getIdLong(), message.getChannel().getIdLong(), message.getIdLong());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(key = "#messageId.toString()")
|
||||
public Message getMessageFromCache(Long messageId, Long textChannelId, Long guildId) {
|
||||
public CachedMessage getMessageFromCache(Long guildId, Long textChannelId, Long messageId) throws ExecutionException, InterruptedException {
|
||||
log.info("Retrieving message with parameters");
|
||||
return getMessageInTextChannelOfGuild(messageId, textChannelId, guildId);
|
||||
|
||||
CompletableFuture<CachedMessage> cachedMessageCompletableFuture =
|
||||
getMessage(guildId, textChannelId, messageId)
|
||||
.thenApply(jdaMessage -> {
|
||||
CachedMessage cachedMessage = buildCachedMessageFromMessage(jdaMessage);
|
||||
putMessageInCache(cachedMessage);
|
||||
return cachedMessage;
|
||||
});
|
||||
|
||||
return cachedMessageCompletableFuture.get();
|
||||
}
|
||||
|
||||
private Message getMessageInTextChannelOfGuild(Long messageId, Long textChannelId, Long guildId) {
|
||||
Guild guildById = bot.getInstance().getGuildById(guildId);
|
||||
if(guildById != null) {
|
||||
TextChannel textChannelById = guildById.getTextChannelById(textChannelId);
|
||||
if(textChannelById != null) {
|
||||
return textChannelById.retrieveMessageById(messageId).complete();
|
||||
} else {
|
||||
log.warn("Failed to load text channel {} of message {} in guild {}", textChannelId, messageId, guildId);
|
||||
}
|
||||
} else {
|
||||
log.warn("Failed to guild {} of message {}", guildId, messageId);
|
||||
@Override
|
||||
public CompletableFuture<Message> getMessage(Long guildId, Long textChannelId, Long messageId) {
|
||||
TextChannel textChannelById = bot.getTextChannelFromServer(guildId, textChannelId);
|
||||
return textChannelById.retrieveMessageById(messageId).submit();
|
||||
}
|
||||
|
||||
private CachedMessage buildCachedMessageFromMessage(Message message) {
|
||||
List<String> attachmentUrls = new ArrayList<>();
|
||||
message.getAttachments().forEach(attachment -> {
|
||||
attachmentUrls.add(attachment.getProxyUrl());
|
||||
});
|
||||
List<CachedEmbed> embeds = new ArrayList<>();
|
||||
message.getEmbeds().forEach(embed -> {
|
||||
embeds.add(getCachedEmbedFromEmbed(embed));
|
||||
});
|
||||
return CachedMessage.builder()
|
||||
.authorId(message.getAuthor().getIdLong())
|
||||
.serverId(message.getGuild().getIdLong())
|
||||
.channelId(message.getChannel().getIdLong())
|
||||
.content(message.getContentRaw())
|
||||
.embeds(embeds)
|
||||
.timeCreated(message.getTimeCreated())
|
||||
.attachmentUrls(attachmentUrls)
|
||||
.build();
|
||||
}
|
||||
|
||||
private CachedEmbed getCachedEmbedFromEmbed(MessageEmbed embed) {
|
||||
CachedEmbed.CachedEmbedBuilder builder = CachedEmbed
|
||||
.builder();
|
||||
MessageEmbed.AuthorInfo author = embed.getAuthor();
|
||||
if(author != null) {
|
||||
builder.author(EmbedAuthor.builder().avatar(author.getProxyIconUrl()).url(author.getUrl()).name(author.getName()).build());
|
||||
}
|
||||
List<MessageEmbed.Field> fields = embed.getFields();
|
||||
if(!fields.isEmpty()) {
|
||||
List<EmbedField> embedFields = new ArrayList<>();
|
||||
fields.forEach(field -> {
|
||||
EmbedField build = EmbedField
|
||||
.builder()
|
||||
.name(field.getName())
|
||||
.value(field.getValue())
|
||||
.inline(field.isInline())
|
||||
.build();
|
||||
embedFields.add(build);
|
||||
});
|
||||
builder.fields(embedFields);
|
||||
}
|
||||
MessageEmbed.ImageInfo image = embed.getImage();
|
||||
if(image != null) {
|
||||
builder.imageUrl(image.getProxyUrl());
|
||||
}
|
||||
Color color = embed.getColor();
|
||||
if(color != null) {
|
||||
EmbedColor build = EmbedColor
|
||||
.builder()
|
||||
.r(color.getRed())
|
||||
.g(color.getGreen())
|
||||
.b(color.getBlue())
|
||||
.build();
|
||||
builder.color(build);
|
||||
}
|
||||
builder.description(embed.getDescription());
|
||||
MessageEmbed.Footer footer = embed.getFooter();
|
||||
if(footer != null) {
|
||||
EmbedFooter build = EmbedFooter
|
||||
.builder()
|
||||
.icon(footer.getProxyIconUrl())
|
||||
.text(footer.getText())
|
||||
.build();
|
||||
builder.footer(build);
|
||||
}
|
||||
throw new RuntimeException("Message was not found");
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,21 +21,17 @@ public class MessageServiceBean implements MessageService {
|
||||
|
||||
@Override
|
||||
public void addReactionToMessage(String emoteKey, Long serverId, Message message) {
|
||||
Guild guildById = bot.getInstance().getGuildById(serverId);
|
||||
if(guildById != null) {
|
||||
AEmote emote = emoteManagementService.loadEmoteByName(emoteKey, serverId);
|
||||
if(emote.getCustom()) {
|
||||
Emote emoteById = guildById.getEmoteById(emote.getEmoteId());
|
||||
if(emoteById != null) {
|
||||
message.addReaction(emoteById).queue();
|
||||
} else {
|
||||
log.warn("Emote with key {} and id {} for guild {} was not found.", emoteKey, emote.getEmoteId(), guildById.getId());
|
||||
}
|
||||
Guild guildById = bot.getGuildById(serverId);
|
||||
AEmote emote = emoteManagementService.loadEmoteByName(emoteKey, serverId);
|
||||
if(emote.getCustom()) {
|
||||
Emote emoteById = guildById.getEmoteById(emote.getEmoteId());
|
||||
if(emoteById != null) {
|
||||
message.addReaction(emoteById).queue();
|
||||
} else {
|
||||
message.addReaction(emote.getEmoteKey()).queue();
|
||||
log.warn("Emote with key {} and id {} for guild {} was not found.", emoteKey, emote.getEmoteId(), guildById.getId());
|
||||
}
|
||||
} else {
|
||||
log.warn("Guild {} was not found when trying to react to a message.", serverId);
|
||||
message.addReaction(emote.getEmoteKey()).queue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
package dev.sheldan.abstracto.listener;
|
||||
|
||||
import dev.sheldan.abstracto.core.management.ChannelManagementService;
|
||||
import dev.sheldan.abstracto.core.management.ServerManagementService;
|
||||
import dev.sheldan.abstracto.core.management.UserManagementService;
|
||||
import dev.sheldan.abstracto.core.models.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.models.listener.MessageEmbeddedModel;
|
||||
import dev.sheldan.abstracto.core.service.Bot;
|
||||
import dev.sheldan.abstracto.core.service.MessageCache;
|
||||
import dev.sheldan.abstracto.templating.TemplateService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.entities.MessageEmbed;
|
||||
import net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent;
|
||||
import net.dv8tion.jda.api.hooks.ListenerAdapter;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
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.concurrent.ExecutionException;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public class MessageEmbedListener extends ListenerAdapter {
|
||||
|
||||
@Autowired
|
||||
private MessageCache messageCache;
|
||||
|
||||
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;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void onGuildMessageReceived(@Nonnull GuildMessageReceivedEvent event) {
|
||||
String messageRaw = event.getMessage().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(event.getMessage().getGuild().getId().equals(serverId)) {
|
||||
Long serverIdLong = Long.parseLong(serverId);
|
||||
Long channelIdLong = Long.parseLong(channelId);
|
||||
Long messageIdLong = Long.parseLong(messageId);
|
||||
try {
|
||||
CachedMessage messageFromCache = messageCache.getMessageFromCache(serverIdLong, channelIdLong, messageIdLong);
|
||||
messageRaw = messageRaw.replace(wholeLink, "");
|
||||
self.createEmbedAndPostEmbed(event, messageFromCache);
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
log.warn("Failed to load message from cache", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
if(StringUtils.isBlank(messageRaw) && matched) {
|
||||
event.getMessage().delete().queue();
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
||||
public void createEmbedAndPostEmbed(@Nonnull GuildMessageReceivedEvent event, CachedMessage message) {
|
||||
MessageEmbeddedModel messageEmbeddedModel = buildTemplateParameter(event, message);
|
||||
MessageEmbed embed = templateService.renderEmbedTemplate(MESSAGE_EMBED_TEMPLATE, messageEmbeddedModel);
|
||||
event.getChannel().sendMessage(embed).queue();
|
||||
}
|
||||
|
||||
private MessageEmbeddedModel buildTemplateParameter(GuildMessageReceivedEvent event, CachedMessage embeddedMessage) {
|
||||
AChannel channel = channelManagementService.loadChannel(event.getChannel().getIdLong());
|
||||
AServer server = serverManagementService.loadServer(event.getGuild().getIdLong());
|
||||
AUserInAServer user = userManagementService.loadUser(event.getMember());
|
||||
Member author = bot.getMemberInServer(embeddedMessage.getServerId(), embeddedMessage.getAuthorId());
|
||||
return MessageEmbeddedModel
|
||||
.builder()
|
||||
.channel(channel)
|
||||
.server(server)
|
||||
.member(event.getMember())
|
||||
.aUserInAServer(user)
|
||||
.author(author)
|
||||
.sourceChannel(event.getChannel())
|
||||
.embeddingUser(event.getMember())
|
||||
.user(user.getUserReference())
|
||||
.textChannel(event.getChannel())
|
||||
.guild(event.getGuild())
|
||||
.embeddedMessage(embeddedMessage)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
package dev.sheldan.abstracto.listener;
|
||||
|
||||
import dev.sheldan.abstracto.core.MessageTextUpdatedListener;
|
||||
import dev.sheldan.abstracto.core.models.CachedMessage;
|
||||
import dev.sheldan.abstracto.core.service.MessageCache;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.dv8tion.jda.api.entities.Message;
|
||||
import net.dv8tion.jda.api.events.message.guild.GuildMessageUpdateEvent;
|
||||
import net.dv8tion.jda.api.hooks.ListenerAdapter;
|
||||
@@ -10,8 +12,10 @@ import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public class MessageUpdatedListener extends ListenerAdapter {
|
||||
|
||||
@Autowired
|
||||
@@ -22,10 +26,15 @@ public class MessageUpdatedListener extends ListenerAdapter {
|
||||
|
||||
@Override
|
||||
public void onGuildMessageUpdate(@Nonnull GuildMessageUpdateEvent event) {
|
||||
Message fromCache = messageCache.getMessageFromCache(event.getMessage());
|
||||
listener.forEach(messageTextUpdatedListener -> {
|
||||
messageTextUpdatedListener.execute(fromCache, event.getMessage());
|
||||
});
|
||||
messageCache.putMessageInCache(event.getMessage());
|
||||
Message message = event.getMessage();
|
||||
try {
|
||||
CachedMessage fromCache = messageCache.getMessageFromCache(message.getGuild().getIdLong(), message.getTextChannel().getIdLong(), event.getMessageIdLong());
|
||||
listener.forEach(messageTextUpdatedListener -> {
|
||||
messageTextUpdatedListener.execute(fromCache, message);
|
||||
});
|
||||
messageCache.putMessageInCache(message);
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
log.warn("Failed to load message", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"author": {
|
||||
"name": "${author.effectiveName}",
|
||||
"avatar": "${author.user.avatarUrl}"
|
||||
},
|
||||
"color" : {
|
||||
"r": 200,
|
||||
"g": 0,
|
||||
"b": 255
|
||||
},
|
||||
<#if embeddedMessage.content?has_content >
|
||||
"description": "${embeddedMessage.content}",
|
||||
</#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}"
|
||||
}
|
||||
Reference in New Issue
Block a user