added utility module

added ability to create/accept/reject suggestions them
made commands async, because a lot of operations behave like that
in case the database is needed in an async operation, this needs to be in a separate transactional service
fixed ordering of Long parameters (server > channel > message)
added initial emote handling (no adding of emotes yet)
fixed creation of AUserInAServer, in case the user did not exist yet, when loading
changed PostTargetService to return Futures instead of being a void, in order to be able to act on the message posted
set logging level to debug
fixed help for different commands (templates had wrong naming)
This commit is contained in:
Sheldan
2020-03-21 14:31:59 +01:00
parent d3a9a7a9f0
commit df0c156743
57 changed files with 890 additions and 61 deletions

View File

@@ -1,6 +1,6 @@
package dev.sheldan.abstracto.moderation.listener;
import dev.sheldan.abstracto.core.ContextUtils;
import dev.sheldan.abstracto.core.utils.ContextUtils;
import dev.sheldan.abstracto.core.models.database.PostTarget;
import dev.sheldan.abstracto.core.service.MessageCache;
import dev.sheldan.abstracto.core.service.PostTargetService;

View File

@@ -13,6 +13,7 @@
<packaging>pom</packaging>
<modules>
<module>moderation</module>
<module>utility</module>
</modules>

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>dev.sheldan.abstracto</groupId>
<artifactId>abstracto-modules</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>utility</artifactId>
<packaging>pom</packaging>
<modules>
<module>utility-int</module>
<module>utility-impl</module>
</modules>
<dependencies>
<dependency>
<groupId>dev.sheldan.abstracto.command</groupId>
<artifactId>command-int</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>dev.sheldan.abstracto.core</groupId>
<artifactId>core-interface</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>utility</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>utility-impl</artifactId>
<dependencies>
<dependency>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>utility-int</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,46 @@
package dev.sheldan.abstracto.utility.command.suggest;
import dev.sheldan.abstracto.command.Command;
import dev.sheldan.abstracto.command.HelpInfo;
import dev.sheldan.abstracto.command.execution.*;
import dev.sheldan.abstracto.utility.Utility;
import dev.sheldan.abstracto.utility.models.template.SuggestionLog;
import dev.sheldan.abstracto.utility.service.SuggestionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component
public class Accept implements Command {
@Autowired
private SuggestionService suggestionService;
@Override
public Result execute(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters();
Long suggestionId = (Long) parameters.get(0);
String text = parameters.size() == 2 ? (String) parameters.get(1) : "";
SuggestionLog suggestionModel = (SuggestionLog) ContextConverter.fromCommandContext(commandContext, SuggestionLog.class);
suggestionService.acceptSuggestion(suggestionId, text, suggestionModel);
return Result.fromSuccess();
}
@Override
public CommandConfiguration getConfiguration() {
List<Parameter> parameters = new ArrayList<>();
parameters.add(Parameter.builder().name("suggestionId").type(Long.class).optional(false).build());
parameters.add(Parameter.builder().name("text").type(String.class).optional(true).remainder(true).build());
HelpInfo helpInfo = HelpInfo.builder().templated(true).build();
return CommandConfiguration.builder()
.name("accept")
.module(Utility.UTILITY)
.templated(true)
.causesReaction(true)
.parameters(parameters)
.help(helpInfo)
.build();
}
}

View File

@@ -0,0 +1,45 @@
package dev.sheldan.abstracto.utility.command.suggest;
import dev.sheldan.abstracto.command.Command;
import dev.sheldan.abstracto.command.HelpInfo;
import dev.sheldan.abstracto.command.execution.*;
import dev.sheldan.abstracto.utility.Utility;
import dev.sheldan.abstracto.utility.models.template.SuggestionLog;
import dev.sheldan.abstracto.utility.service.SuggestionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component
public class Reject implements Command {
@Autowired
private SuggestionService suggestionService;
@Override
public Result execute(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters();
Long suggestionId = (Long) parameters.get(0);
String text = parameters.size() == 2 ? (String) parameters.get(1) : "";
SuggestionLog suggestionModel = (SuggestionLog) ContextConverter.fromCommandContext(commandContext, SuggestionLog.class);
suggestionService.rejectSuggestion(suggestionId, text, suggestionModel);
return Result.fromSuccess();
}
@Override
public CommandConfiguration getConfiguration() {
List<Parameter> parameters = new ArrayList<>();
parameters.add(Parameter.builder().name("suggestionId").type(Long.class).optional(false).build());
parameters.add(Parameter.builder().name("text").type(String.class).optional(true).remainder(true).build());
HelpInfo helpInfo = HelpInfo.builder().templated(true).build();
return CommandConfiguration.builder()
.name("reject")
.module(Utility.UTILITY)
.templated(true)
.causesReaction(true)
.parameters(parameters)
.help(helpInfo)
.build();
}
}

View File

@@ -0,0 +1,46 @@
package dev.sheldan.abstracto.utility.command.suggest;
import dev.sheldan.abstracto.command.Command;
import dev.sheldan.abstracto.command.HelpInfo;
import dev.sheldan.abstracto.command.execution.*;
import dev.sheldan.abstracto.utility.Utility;
import dev.sheldan.abstracto.utility.models.template.SuggestionLog;
import dev.sheldan.abstracto.utility.service.SuggestionService;
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;
@Component
public class Suggest implements Command {
@Autowired
private SuggestionService suggestionService;
@Override
public Result execute(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters();
String text = (String) parameters.get(0);
SuggestionLog suggestLogModel = (SuggestionLog) ContextConverter.fromCommandContext(commandContext, SuggestionLog.class);
suggestLogModel.setSuggester(commandContext.getAuthor());
suggestionService.createSuggestion(commandContext.getAuthor(), text, suggestLogModel);
return Result.fromSuccess();
}
@Override
public CommandConfiguration getConfiguration() {
List<Parameter> parameters = new ArrayList<>();
parameters.add(Parameter.builder().name("text").type(String.class).optional(false).remainder(true).build());
HelpInfo helpInfo = HelpInfo.builder().templated(true).build();
return CommandConfiguration.builder()
.name("suggest")
.module(Utility.UTILITY)
.templated(true)
.causesReaction(true)
.parameters(parameters)
.help(helpInfo)
.build();
}
}

View File

@@ -0,0 +1,7 @@
package dev.sheldan.abstracto.utility.repository;
import dev.sheldan.abstracto.utility.models.Suggestion;
import org.springframework.data.jpa.repository.JpaRepository;
public interface SuggestionRepository extends JpaRepository<Suggestion, Long> {
}

View File

@@ -0,0 +1,109 @@
package dev.sheldan.abstracto.utility.service;
import dev.sheldan.abstracto.core.management.EmoteManagementService;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.models.database.PostTarget;
import dev.sheldan.abstracto.core.service.Bot;
import dev.sheldan.abstracto.core.service.MessageService;
import dev.sheldan.abstracto.core.service.PostTargetService;
import dev.sheldan.abstracto.core.utils.MessageUtils;
import dev.sheldan.abstracto.templating.TemplateService;
import dev.sheldan.abstracto.utility.models.Suggestion;
import dev.sheldan.abstracto.utility.models.SuggestionState;
import dev.sheldan.abstracto.utility.models.template.SuggestionLog;
import dev.sheldan.abstracto.utility.service.management.AsyncSuggestionServiceBean;
import dev.sheldan.abstracto.utility.service.management.SuggestionManagementService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.entities.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class SuggestionServiceBean implements SuggestionService {
public static final String SUGGESTION_LOG_TEMPLATE = "suggest_log";
public static final String SUGGESTION_YES_EMOTE = "SUGGESTION_YES";
public static final String SUGGESTION_NO_EMOTE = "SUGGESTION_NO";
@Autowired
private SuggestionManagementService suggestionManagementService;
@Autowired
private PostTargetService postTargetService;
@Autowired
private TemplateService templateService;
@Autowired
private Bot botService;
@Autowired
private AsyncSuggestionServiceBean suggestionServiceBean;
@Autowired
private EmoteManagementService emoteManagementService;
@Autowired
private MessageService messageService;
@Override
public void createSuggestion(Member member, String text, SuggestionLog suggestionLog) {
Suggestion suggestion = suggestionManagementService.createSuggestion(member, text);
suggestionLog.setSuggestion(suggestion);
suggestionLog.setText(text);
MessageEmbed embed = templateService.renderEmbedTemplate(SUGGESTION_LOG_TEMPLATE, suggestionLog);
long guildId = member.getGuild().getIdLong();
JDA instance = botService.getInstance();
Guild guildById = instance.getGuildById(guildId);
if(guildById != null) {
postTargetService.sendEmbedInPostTarget(embed, PostTarget.SUGGESTIONS, guildId).thenAccept(message -> {
messageService.addReactionToMessage(SUGGESTION_YES_EMOTE, guildId, message);
messageService.addReactionToMessage(SUGGESTION_NO_EMOTE, guildId, message);
suggestionManagementService.setPostedMessage(suggestion, message);
});
} else {
log.warn("Guild {} or member {} was not found when creating suggestion.", member.getGuild().getIdLong(), member.getIdLong());
}
}
@Override
public void acceptSuggestion(Long suggestionId, String text, SuggestionLog suggestionLog) {
Suggestion suggestion = suggestionManagementService.getSuggestion(suggestionId);
suggestionManagementService.setSuggestionState(suggestion, SuggestionState.ACCEPTED);
updateSuggestion(text, suggestionLog, suggestion);
}
private void updateSuggestion(String text, SuggestionLog suggestionLog, Suggestion suggestion) {
Long channelId = suggestion.getChannel().getId();
Long originalMessageId = suggestion.getMessageId();
Long serverId = suggestion.getServer().getId();
suggestionLog.setOriginalChannelId(channelId);
suggestionLog.setOriginalMessageId(originalMessageId);
suggestionLog.setOriginalMessageUrl(MessageUtils.buildMessageUrl(serverId, channelId, originalMessageId));
AUserInAServer suggester = suggestion.getSuggester();
JDA instance = botService.getInstance();
Guild guildById = instance.getGuildById(serverId);
if(guildById != null) {
Member memberById = guildById.getMemberById(suggester.getUserReference().getId());
if(memberById != null) {
suggestionLog.setSuggester(memberById);
suggestionLog.setSuggestion(suggestion);
TextChannel textChannelById = guildById.getTextChannelById(channelId);
if(textChannelById != null) {
textChannelById.retrieveMessageById(originalMessageId).queue(message -> {
suggestionServiceBean.updateSuggestionMessageText(text, suggestionLog, message);
});
}
}
}
}
@Override
public void rejectSuggestion(Long suggestionId, String text, SuggestionLog log) {
Suggestion suggestion = suggestionManagementService.getSuggestion(suggestionId);
suggestionManagementService.setSuggestionState(suggestion, SuggestionState.REJECTED);
updateSuggestion(text, log, suggestion);
}
}

View File

@@ -0,0 +1,41 @@
package dev.sheldan.abstracto.utility.service.management;
import dev.sheldan.abstracto.core.models.database.PostTarget;
import dev.sheldan.abstracto.core.service.PostTargetService;
import dev.sheldan.abstracto.templating.TemplateService;
import dev.sheldan.abstracto.utility.models.template.SuggestionLog;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.MessageEmbed;
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 java.util.Optional;
import static dev.sheldan.abstracto.utility.service.SuggestionServiceBean.SUGGESTION_LOG_TEMPLATE;
@Component
@Slf4j
public class AsyncSuggestionServiceBean {
@Autowired
private TemplateService templateService;
@Autowired
private PostTargetService postTargetService;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void updateSuggestionMessageText(String text, SuggestionLog suggestionLog, Message message) {
Optional<MessageEmbed> embedOptional = message.getEmbeds().stream().filter(embed -> embed.getDescription() != null).findFirst();
if(embedOptional.isPresent()) {
MessageEmbed suggestionEmbed = embedOptional.get();
suggestionLog.setReason(text);
suggestionLog.setText(suggestionEmbed.getDescription());
MessageEmbed embed = templateService.renderEmbedTemplate(SUGGESTION_LOG_TEMPLATE, suggestionLog);
postTargetService.sendEmbedInPostTarget(embed, PostTarget.SUGGESTIONS, suggestionLog.getServer().getId());
}
}
}

View File

@@ -0,0 +1,73 @@
package dev.sheldan.abstracto.utility.service.management;
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.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.utility.models.Suggestion;
import dev.sheldan.abstracto.utility.models.SuggestionState;
import dev.sheldan.abstracto.utility.repository.SuggestionRepository;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.time.Instant;
@Component
public class SuggestionManagementServiceBean implements SuggestionManagementService {
@Autowired
private SuggestionRepository suggestionRepository;
@Autowired
private ChannelManagementService channelManagementService;
@Autowired
private UserManagementService userManagementService;
@Autowired
private ServerManagementService serverManagementService;
@Override
public Suggestion createSuggestion(Member suggester, String text) {
AUserInAServer user = userManagementService.loadUser(suggester);
return this.createSuggestion(user, text);
}
@Override
public Suggestion createSuggestion(AUserInAServer suggester, String text) {
Suggestion suggestion = Suggestion
.builder()
.state(SuggestionState.NEW)
.suggester(suggester)
.suggestionDate(Instant.now())
.build();
suggestionRepository.save(suggestion);
return suggestion;
}
@Override
public Suggestion getSuggestion(Long suggestionId) {
return suggestionRepository.getOne(suggestionId);
}
@Override
public void setPostedMessage(Suggestion suggestion, Message message) {
suggestion.setMessageId(message.getIdLong());
AChannel channel = channelManagementService.loadChannel(message.getTextChannel().getIdLong());
suggestion.setChannel(channel);
AServer server = serverManagementService.loadServer(message.getGuild().getIdLong());
suggestion.setServer(server);
suggestionRepository.save(suggestion);
}
@Override
public void setSuggestionState(Suggestion suggestion, SuggestionState newState) {
suggestion.setState(newState);
suggestionRepository.save(suggestion);
}
}

View File

@@ -0,0 +1 @@
Suggests a text in the suggestions channel to be voted on.

View File

@@ -0,0 +1,29 @@
{
"author": {
"name": "${suggester.effectiveName}",
"avatar": "${suggester.user.effectiveAvatarUrl}"
},
"color" : {
"r": 200,
"g": 0,
"b": 255
},
<#if suggestion.state = "ACCEPTED">
"description": "~~${text}~~ \n✅ ${reason} - Accepted by ${member.effectiveName}",
<#elseif suggestion.state = "REJECTED">
"description": "~~${text}~~ \n❌ ${reason} - Rejected by ${member.effectiveName}",
<#else>
"description": "${text}",
</#if>
<#if suggestion.state = "ACCEPTED" || suggestion.state = "REJECTED">
"fields": [
{
"name": "Link",
"value": "[Jump](${originalMessageUrl})"
}
],
</#if>
"footer": {
"text": "Suggestion #${suggestion.id}"
}
}

View File

@@ -0,0 +1 @@
Suggests a text in the suggestions channel to be voted on.

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>utility</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>utility-int</artifactId>
</project>

View File

@@ -0,0 +1,21 @@
package dev.sheldan.abstracto.utility;
import dev.sheldan.abstracto.command.Module;
import dev.sheldan.abstracto.command.module.ModuleInfo;
import org.springframework.stereotype.Component;
@Component
public class Utility implements Module {
public static final String UTILITY = "utility";
@Override
public ModuleInfo getInfo() {
return ModuleInfo.builder().name(UTILITY).description("General utilities").build();
}
@Override
public String getParentModule() {
return "default";
}
}

View File

@@ -0,0 +1,50 @@
package dev.sheldan.abstracto.utility.models;
import dev.sheldan.abstracto.core.models.AChannelType;
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 lombok.*;
import javax.persistence.*;
import java.time.Instant;
@Entity
@Table(name="suggestion")
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter @Setter
public class Suggestion {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Getter
private Long id;
@Getter
@ManyToOne
@JoinColumn(name = "suggesterId")
private AUserInAServer suggester;
@Getter
private Long messageId;
@Getter
@ManyToOne
@JoinColumn(name = "channelId")
private AChannel channel;
@Getter
@ManyToOne
@JoinColumn(name = "serverId")
private AServer server;
@Getter
private Instant suggestionDate;
@Getter
@Enumerated(EnumType.STRING)
private SuggestionState state;
}

View File

@@ -0,0 +1,5 @@
package dev.sheldan.abstracto.utility.models;
public enum SuggestionState {
NEW, ACCEPTED, REJECTED
}

View File

@@ -0,0 +1,19 @@
package dev.sheldan.abstracto.utility.models.template;
import dev.sheldan.abstracto.core.models.UserInitiatedServerContext;
import dev.sheldan.abstracto.utility.models.Suggestion;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.SuperBuilder;
import net.dv8tion.jda.api.entities.Member;
@Getter @Setter @SuperBuilder
public class SuggestionLog extends UserInitiatedServerContext {
private Suggestion suggestion;
private Member suggester;
private String text;
private String reason;
private Long originalMessageId;
private Long originalChannelId;
private String originalMessageUrl;
}

View File

@@ -0,0 +1,10 @@
package dev.sheldan.abstracto.utility.service;
import dev.sheldan.abstracto.utility.models.template.SuggestionLog;
import net.dv8tion.jda.api.entities.Member;
public interface SuggestionService {
void createSuggestion(Member member, String text, SuggestionLog log);
void acceptSuggestion(Long suggestionId, String text, SuggestionLog log);
void rejectSuggestion(Long suggestionId, String text, SuggestionLog log);
}

View File

@@ -0,0 +1,15 @@
package dev.sheldan.abstracto.utility.service.management;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.utility.models.Suggestion;
import dev.sheldan.abstracto.utility.models.SuggestionState;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
public interface SuggestionManagementService {
Suggestion createSuggestion(Member suggester, String text);
Suggestion createSuggestion(AUserInAServer suggester, String text);
Suggestion getSuggestion(Long suggestionId);
void setPostedMessage(Suggestion suggestion, Message message);
void setSuggestionState(Suggestion suggestion, SuggestionState newState);
}