added module responsible for accumulating the dependencies and resulting in a jar

added channels module
added help info object to command configuration
added description field to parameter
added modules for commands (and packed modules), they are mapped by name
added post command execution interface
added support for optional parameters
added support for using guildchannel as parameter
added printing of modules to help command
added service beans to wrap over the operations on the repository
added synchronizing of channels/roles on startup (controlled by flag)
added builder annotations to model classes
added more model classes
This commit is contained in:
Sheldan
2019-12-12 16:47:54 +01:00
parent 42cfe33b3a
commit 5c6b7b9a78
61 changed files with 1136 additions and 138 deletions

View File

@@ -0,0 +1,23 @@
<?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>
<artifactId>command-base</artifactId>
<groupId>dev.sheldan.abstracto.command</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>channels</artifactId>
<dependencies>
<dependency>
<groupId>dev.sheldan.abstracto.core</groupId>
<artifactId>core-interface</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,18 @@
package dev.sheldan.abstracto.command.channels;
import dev.sheldan.abstracto.command.Module;
import dev.sheldan.abstracto.command.module.ModuleInfo;
import org.springframework.stereotype.Component;
@Component
public class ChannelsModule implements Module {
@Override
public ModuleInfo getInfo() {
return ModuleInfo.builder().name("channels").description("Includes utilities to configure the channel configuration stored in the database").build();
}
@Override
public String getParentModule() {
return "default";
}
}

View File

@@ -0,0 +1,49 @@
package dev.sheldan.abstracto.command.channels;
import dev.sheldan.abstracto.command.Command;
import dev.sheldan.abstracto.command.execution.Configuration;
import dev.sheldan.abstracto.command.execution.Context;
import dev.sheldan.abstracto.command.execution.Parameter;
import dev.sheldan.abstracto.command.execution.Result;
import dev.sheldan.abstracto.core.models.AChannel;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.PostTargetService;
import net.dv8tion.jda.api.entities.GuildChannel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.List;
@Service
public class PostTarget implements Command {
@Autowired
private PostTargetService service;
@Autowired
private ChannelService channelService;
@Override
public Result execute(Context context) {
GuildChannel channel = (GuildChannel) context.getParameters().getParameters().get(1);
String targetName = (String) context.getParameters().getParameters().get(0);
AChannel dbChannel = channelService.loadChannel(channel.getIdLong());
service.createOrUpdate(targetName, dbChannel);
return Result.fromSuccess();
}
@Override
public Configuration getConfiguration() {
Parameter channel = Parameter.builder().name("channel").type(GuildChannel.class).description("The channel to post towards").build();
Parameter postTargetName = Parameter.builder().name("name").type(String.class).description("The name of the post target to redirect").build();
List<Parameter> parameters = Arrays.asList(postTargetName, channel);
return Configuration.builder()
.name("posttarget")
.module("channels")
.parameters(parameters)
.description("Sets the target of a post done by the bot")
.causesReaction(false)
.build();
}
}

View File

@@ -15,6 +15,7 @@
<modules>
<module>utility</module>
<module>support</module>
<module>channels</module>
</modules>
<dependencies>

View File

@@ -1,38 +1,111 @@
package dev.sheldan.abstracto.command.support;
import dev.sheldan.abstracto.command.Command;
import dev.sheldan.abstracto.command.execution.Configuration;
import dev.sheldan.abstracto.command.execution.Context;
import dev.sheldan.abstracto.command.execution.Result;
import dev.sheldan.abstracto.command.meta.CommandRegistry;
import net.dv8tion.jda.api.EmbedBuilder;
import dev.sheldan.abstracto.command.CommandHierarchy;
import dev.sheldan.abstracto.command.ModuleRegistry;
import dev.sheldan.abstracto.command.PackedModule;
import dev.sheldan.abstracto.command.execution.*;
import dev.sheldan.abstracto.command.module.ModuleInfo;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@Service
public class Help implements Command {
@Autowired
private CommandRegistry registry;
private ModuleRegistry registry;
@Override
public Result execute(Context context) {
EmbedBuilder builder = new EmbedBuilder();
registry.getAllCommands().forEach(command -> {
builder.addField(command.getConfiguration().getName(), command.getConfiguration().getDescription(), false);
});
context.getChannel().sendMessage(builder.build()).queue();
CommandHierarchy commandStructure = registry.getDetailedModules();
StringBuilder sb = new StringBuilder();
if(context.getParameters().getParameters().isEmpty()){
sb.append("Help | Module overview \n");
sb.append("```");
commandStructure.getRootModules().forEach(packedModule -> {
sb.append(getModule(packedModule, 0, true));
sb.append("\n");
});
sb.append("```");
} else {
String parameterValue = context.getParameters().getParameters().get(0).toString();
PackedModule module = commandStructure.getModuleWithName(parameterValue);
if(module != null){
sb.append("Help | Module overview \n");
sb.append(getModule(module, 0, false));
module.getCommands().forEach(command -> {
sb.append(getCommand(command));
});
} else {
Command command = commandStructure.getCommandWithName(parameterValue);
if(command != null) {
sb.append("Help | Command overview");
sb.append("\n");
sb.append(getCommand(command));
}
}
}
context.getChannel().sendMessage(sb.toString()).queue();
return Result.fromSuccess();
}
private String getCommand(Command command){
StringBuilder sb = new StringBuilder();
Configuration configuration = command.getConfiguration();
sb.append(String.format("Command: **%s**", configuration.getName()));
sb.append("\n");
sb.append(String.format("Description: %s", configuration.getDescription()));
sb.append("\n");
if(configuration.getHelp() != null){
sb.append(String.format("Usage: %s", configuration.getHelp().getUsage()));
sb.append("\n");
sb.append(String.format("Detailed help: %s", configuration.getHelp().getLongHelp()));
}
return sb.toString();
}
private String getModule(PackedModule module, int depth, boolean recursive){
StringBuilder sb = new StringBuilder();
String intentation = "";
if(depth > 0){
intentation = StringUtils.repeat("-", depth) + ">";
}
ModuleInfo info = module.getModule().getInfo();
sb.append(String.format(intentation +"**%s** \n", info.getName()));
sb.append(String.format(intentation + "%s \n", info.getDescription()));
if(recursive) {
module.getSubModules().forEach(subModule -> {
sb.append(getModule(subModule, depth + 1, true));
});
}
sb.append("\n");
return sb.toString();
}
@Override
public Configuration getConfiguration() {
Parameter moduleOrCommandName = Parameter.builder()
.name("name")
.optional(true)
.description("Name of module or command")
.type(String.class)
.build();
return Configuration.builder()
.name("help")
.module("utility")
.module("support")
.parameters(Collections.singletonList(moduleOrCommandName))
.description("Prints the help")
.causesReaction(false)
.build();
}
}

View File

@@ -0,0 +1,22 @@
package dev.sheldan.abstracto.command.support;
import dev.sheldan.abstracto.command.Module;
import dev.sheldan.abstracto.command.module.AbstracatoModule;
import dev.sheldan.abstracto.command.module.ModuleInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class SupportModule implements Module {
@Override
public ModuleInfo getInfo() {
return ModuleInfo.builder().name("support").description("Utilities for support").build();
}
@Override
public String getParentModule() {
return "default";
}
}

View File

@@ -1,6 +1,7 @@
package dev.sheldan.abstracto.command.utility;
import dev.sheldan.abstracto.command.Command;
import dev.sheldan.abstracto.command.HelpInfo;
import dev.sheldan.abstracto.command.execution.Configuration;
import dev.sheldan.abstracto.command.execution.Context;
import dev.sheldan.abstracto.command.execution.Parameter;
@@ -27,12 +28,14 @@ public class Echo implements Command {
public Configuration getConfiguration() {
List<Parameter> parameters = new ArrayList<>();
parameters.add(Parameter.builder().name("input").type(String.class).remainder(true).build());
HelpInfo helpInfo = HelpInfo.builder().usage("echo <text>").longHelp("Echos back the text put in").build();
return Configuration.builder()
.name("echo")
.module("utility")
.description("Echos the input back to the same channel")
.causesReaction(false)
.parameters(parameters)
.help(helpInfo)
.build();
}
}

View File

@@ -1,9 +1,11 @@
package dev.sheldan.abstracto.command.utility;
import dev.sheldan.abstracto.command.Command;
import dev.sheldan.abstracto.command.Module;
import dev.sheldan.abstracto.command.execution.Configuration;
import dev.sheldan.abstracto.command.execution.Context;
import dev.sheldan.abstracto.command.execution.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
@@ -25,4 +27,5 @@ public class Ping implements Command {
.causesReaction(false)
.build();
}
}

View File

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

View File

@@ -0,0 +1,61 @@
package dev.sheldan.abstracto.command;
import lombok.Builder;
import lombok.Getter;
import java.util.List;
import java.util.Optional;
@Getter @Builder
public class CommandHierarchy {
private List<PackedModule> rootModules;
public PackedModule getModuleWithName(String name){
for (PackedModule module: rootModules) {
PackedModule found = getModuleWithName(name, module);
if(found != null){
return found;
}
}
return null;
}
private PackedModule getModuleWithName(String name, PackedModule module){
if(module.getModule().getInfo().getName().equals(name)){
return module;
} else {
for (PackedModule subModule: module.getSubModules()) {
PackedModule possibleModule = getModuleWithName(name, subModule);
if(possibleModule != null){
return possibleModule;
}
}
return null;
}
}
public Command getCommandWithName(String name) {
for (PackedModule module: rootModules) {
Command command = getCommandFromModule(name, module);
if(command != null){
return command;
}
}
return null;
}
private Command getCommandFromModule(String name, PackedModule module){
Command foundCommand = module.getCommands().stream().filter(command -> command.getConfiguration().getName().equals(name)).findAny().orElse(null);
if(foundCommand == null){
for (PackedModule subModule: module.getSubModules()) {
Command command = getCommandFromModule(name, subModule);
if(command != null){
return command;
}
}
return null;
} else {
return foundCommand;
}
}
}

View File

@@ -0,0 +1,10 @@
package dev.sheldan.abstracto.command;
import lombok.Builder;
import lombok.Getter;
@Getter @Builder
public class HelpInfo {
private String usage;
private String longHelp;
}

View File

@@ -0,0 +1,8 @@
package dev.sheldan.abstracto.command;
import dev.sheldan.abstracto.command.module.ModuleInfo;
public interface Module {
ModuleInfo getInfo();
String getParentModule();
}

View File

@@ -0,0 +1,9 @@
package dev.sheldan.abstracto.command;
import java.util.List;
public interface ModuleRegistry {
CommandHierarchy getDetailedModules();
List<Module> getModules();
}

View File

@@ -0,0 +1,15 @@
package dev.sheldan.abstracto.command;
import lombok.Builder;
import lombok.Getter;
import java.util.List;
@Builder
@Getter
public class PackedModule {
private Module module;
private PackedModule parentModule;
private List<PackedModule> subModules;
private List<Command> commands;
}

View File

@@ -0,0 +1,8 @@
package dev.sheldan.abstracto.command;
import dev.sheldan.abstracto.command.execution.Context;
import dev.sheldan.abstracto.command.execution.Result;
public interface PostCommandExecution {
void execute(Context context, Result result, Command command);
}

View File

@@ -1,12 +1,13 @@
package dev.sheldan.abstracto.command.execution;
import dev.sheldan.abstracto.command.HelpInfo;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter @Setter @Builder
@Getter @Builder
public class Configuration {
private String name;
@@ -14,4 +15,9 @@ public class Configuration {
private String description;
private List<Parameter> parameters;
private boolean causesReaction;
private HelpInfo help;
public long getNecessaryParameterCount(){
return parameters.stream().filter(parameter -> !parameter.isOptional()).count();
}
}

View File

@@ -8,6 +8,7 @@ import lombok.Setter;
public class Parameter {
private String name;
private Class type;
private String description;
private boolean optional;
private boolean remainder;
}

View File

@@ -2,6 +2,7 @@ package dev.sheldan.abstracto.command.meta;
import dev.sheldan.abstracto.command.Command;
import dev.sheldan.abstracto.command.Module;
import net.dv8tion.jda.api.entities.Message;
import java.util.List;
@@ -10,5 +11,6 @@ public interface CommandRegistry {
Command findCommandByParameters(String name, UnParsedCommandParameter context);
Command findCommand(String message);
List<Command> getAllCommands();
List<Command> getAllCommandsFromModule(Module module);
boolean isCommand(Message message);
}

View File

@@ -0,0 +1,17 @@
package dev.sheldan.abstracto.command.module;
import dev.sheldan.abstracto.command.Module;
import org.springframework.stereotype.Service;
@Service
public class AbstracatoModule implements Module {
@Override
public ModuleInfo getInfo() {
return ModuleInfo.builder().name("default").description("Default module provided by abstracto").build();
}
@Override
public String getParentModule() {
return null;
}
}

View File

@@ -0,0 +1,11 @@
package dev.sheldan.abstracto.command.module;
import lombok.Builder;
import lombok.Getter;
@Getter @Builder
public class ModuleInfo {
private String name;
private String description;
}

View File

@@ -1,6 +1,7 @@
package dev.sheldan.abstracto.commands.management;
import dev.sheldan.abstracto.command.Command;
import dev.sheldan.abstracto.command.Module;
import dev.sheldan.abstracto.command.execution.Configuration;
import dev.sheldan.abstracto.command.execution.Parameter;
import dev.sheldan.abstracto.command.meta.CommandRegistry;
@@ -9,6 +10,7 @@ import net.dv8tion.jda.api.entities.Message;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@@ -24,7 +26,7 @@ public class CommandManager implements CommandRegistry {
Configuration configuration = o.getConfiguration();
boolean parameterFit;
if(configuration.getParameters() != null){
boolean paramCountFits = unParsedCommandParameter.getParameters().size() - 1 == configuration.getParameters().size();
boolean paramCountFits = unParsedCommandParameter.getParameters().size() >= configuration.getNecessaryParameterCount();
boolean hasRemainderParameter = configuration.getParameters().stream().anyMatch(Parameter::isRemainder);
parameterFit = paramCountFits || hasRemainderParameter;
} else {
@@ -54,6 +56,17 @@ public class CommandManager implements CommandRegistry {
return commands;
}
@Override
public List<Command> getAllCommandsFromModule(Module module) {
List<Command> commands = new ArrayList<>();
this.getAllCommands().forEach(command -> {
if(command.getConfiguration().getModule().equals(module.getInfo().getName())){
commands.add(command);
}
});
return commands;
}
@Override
public boolean isCommand(Message message) {
return message.getContentRaw().startsWith("!");

View File

@@ -1,10 +1,15 @@
package dev.sheldan.abstracto.commands.management;
import dev.sheldan.abstracto.command.Command;
import dev.sheldan.abstracto.command.PostCommandExecution;
import dev.sheldan.abstracto.command.execution.Context;
import dev.sheldan.abstracto.command.execution.Parameter;
import dev.sheldan.abstracto.command.execution.Parameters;
import dev.sheldan.abstracto.command.execution.Result;
import dev.sheldan.abstracto.command.meta.UnParsedCommandParameter;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.GuildChannel;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
@@ -21,6 +26,9 @@ public class CommandReceivedHandler extends ListenerAdapter {
@Autowired
private CommandManager manager;
@Autowired
private PostCommandExecution execution;
@Override
public void onMessageReceived(@Nonnull MessageReceivedEvent event) {
if(!manager.isCommand(event.getMessage())) {
@@ -31,7 +39,7 @@ public class CommandReceivedHandler extends ListenerAdapter {
unparsedParameter.setParameters(parameters.subList(1, parameters.size()));
String withoutPrefix = parameters.get(0).substring(1);
Command foundCommand = manager.findCommandByParameters(withoutPrefix, unparsedParameter);
Parameters parsedParameters = getParsedParameters(unparsedParameter, foundCommand);
Parameters parsedParameters = getParsedParameters(unparsedParameter, foundCommand, event.getMessage());
Context context = Context.builder()
.author(event.getAuthor())
.channel(event.getTextChannel())
@@ -39,11 +47,13 @@ public class CommandReceivedHandler extends ListenerAdapter {
.parameters(parsedParameters)
.jda(event.getJDA())
.build();
foundCommand.execute(context);
Result result = foundCommand.execute(context);
execution.execute(context, result, foundCommand);
}
public Parameters getParsedParameters(UnParsedCommandParameter unParsedCommandParameter, Command command){
public Parameters getParsedParameters(UnParsedCommandParameter unParsedCommandParameter, Command command, Message message){
List<Object> parsedParameters = new ArrayList<>();
int mentionedChannelsCount = 0;
for (int i = 0; i < unParsedCommandParameter.getParameters().size(); i++) {
Parameter param = command.getConfiguration().getParameters().get(i);
String value = unParsedCommandParameter.getParameters().get(i);
@@ -51,7 +61,10 @@ public class CommandReceivedHandler extends ListenerAdapter {
parsedParameters.add(Integer.parseInt(value));
} else if(param.getType().equals(Double.class)){
parsedParameters.add(Double.parseDouble(value));
} else {
} else if(param.getType().equals(GuildChannel.class)){
parsedParameters.add(message.getMentionedChannels().get(mentionedChannelsCount));
mentionedChannelsCount++;
} else{
parsedParameters.add(value);
}
}

View File

@@ -0,0 +1,88 @@
package dev.sheldan.abstracto.commands.management;
import dev.sheldan.abstracto.command.*;
import dev.sheldan.abstracto.command.Module;
import dev.sheldan.abstracto.command.meta.CommandRegistry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.stream.Collectors;
@Service
public class ModuleManager implements ModuleRegistry {
@Autowired
private List<Module> modules;
@Autowired
private CommandRegistry commandRegistry;
@Override
public CommandHierarchy getDetailedModules() {
List<PackedModule> modulesWithCommands = new ArrayList<>();
List<Module> currentModules = getModules();
currentModules.forEach(module -> {
List<Command> commands = commandRegistry.getAllCommandsFromModule(module);
PackedModule packed = PackedModule.builder().commands(commands).module(module).subModules(new ArrayList<>()).build();
modulesWithCommands.add(packed);
});
return getHierarchicalPacks(modulesWithCommands, currentModules);
}
private CommandHierarchy getHierarchicalPacks(List<PackedModule> modules, List<Module> currentModules){
List<PackedModule> hierarchical = modules.stream().filter(packedModule -> packedModule.getModule().getParentModule() == null).collect(Collectors.toList());
List<PackedModule> subModules = modules.stream().filter(packedModule -> packedModule.getModule().getParentModule() != null).collect(Collectors.toList());
subModules.forEach(module -> {
List<Module> path = getModulePath(module, currentModules);
Collections.reverse(path);
Module rootModule = path.get(0);
Optional<PackedModule> any = hierarchical.stream().filter(moduleInList -> moduleInList.getModule().getInfo().getName().equals(rootModule.getInfo().getName())).findAny();
if(any.isPresent()){
PackedModule currentNodeInHierarchy = any.get();
for (int i = 1; i < path.size(); i++) {
Optional<PackedModule> nextInHierarchy = currentNodeInHierarchy.getSubModules().stream().filter(module1 -> module1.getModule().equals(module.getModule())).findAny();
if(nextInHierarchy.isPresent()){
currentNodeInHierarchy = nextInHierarchy.get();
} else {
currentNodeInHierarchy.getSubModules().add(module);
currentNodeInHierarchy = module;
}
}
if(path.size() == 1){
currentNodeInHierarchy.getSubModules().add(module);
}
}
});
return CommandHierarchy.builder().rootModules(hierarchical).build();
}
private List<Module> getModulePath(PackedModule moduleToPathFor, List<Module> currentModules){
List<Module> modulesBetweenRootAndThis = new ArrayList<>();
Module current = moduleToPathFor.getModule();
modulesBetweenRootAndThis.add(current);
while(current.getParentModule() != null){
String parentModule = current.getParentModule();
Optional<Module> possibleModule = currentModules.stream().filter(module1 -> module1.getInfo().getName().equals(parentModule)).findFirst();
if(possibleModule.isPresent()){
Module foundModule = possibleModule.get();
modulesBetweenRootAndThis.add(foundModule);
current = foundModule;
} else {
break;
}
}
return modulesBetweenRootAndThis;
}
@Override
public List<Module> getModules() {
return modules;
}
}

View File

@@ -0,0 +1,7 @@
package dev.sheldan.abstracto.commands.management;
public class PostTargetException extends RuntimeException {
public PostTargetException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,17 @@
package dev.sheldan.abstracto.commands.management;
import dev.sheldan.abstracto.command.Command;
import dev.sheldan.abstracto.command.PostCommandExecution;
import dev.sheldan.abstracto.command.execution.Context;
import dev.sheldan.abstracto.command.execution.Result;
import org.springframework.stereotype.Service;
@Service
public class ReactionPostExecution implements PostCommandExecution {
@Override
public void execute(Context context, Result result, Command command) {
if(command.getConfiguration().isCausesReaction()){
context.getMessage().addReaction("").queue();
}
}
}

View File

@@ -43,11 +43,6 @@
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
@@ -95,20 +90,6 @@
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>dev.sheldan.abstracto.command</groupId>
<artifactId>utility</artifactId>
<version>${project.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>dev.sheldan.abstracto.command</groupId>
<artifactId>support</artifactId>
<version>${project.version}</version>
<scope>runtime</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,19 @@
package dev.sheldan.abstracto;
import dev.sheldan.abstracto.core.models.SnowFlake;
import net.dv8tion.jda.api.entities.ISnowflake;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
public class SnowflakeUtils {
public static Set<Long> getOwnItemsIds(List<? extends SnowFlake> elements){
return elements.stream().map(SnowFlake::getId).collect(Collectors.toSet());
}
public static Set<Long> getSnowflakeIds(List<? extends ISnowflake> elements){
return elements.stream().map(ISnowflake::getIdLong).collect(Collectors.toSet());
}
}

View File

@@ -1,4 +1,4 @@
package dev.sheldan.abstracto.service;
package dev.sheldan.abstracto.core.service;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.JDABuilder;

View File

@@ -0,0 +1,24 @@
package dev.sheldan.abstracto.core.service;
import dev.sheldan.abstracto.core.models.AChannel;
import dev.sheldan.abstracto.core.models.AChannelType;
import dev.sheldan.abstracto.repository.ChannelRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class ChannelServiceBean implements ChannelService {
@Autowired
private ChannelRepository repository;
@Override
public AChannel loadChannel(Long id) {
return repository.getOne(id);
}
@Override
public AChannel createChannel(Long id, AChannelType type) {
return repository.save(AChannel.builder().id(id).type(type).build());
}
}

View File

@@ -0,0 +1,47 @@
package dev.sheldan.abstracto.core.service;
import dev.sheldan.abstracto.commands.management.PostTargetException;
import dev.sheldan.abstracto.core.models.AChannel;
import dev.sheldan.abstracto.core.models.PostTarget;
import dev.sheldan.abstracto.repository.ChannelRepository;
import dev.sheldan.abstracto.repository.PostTargetRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.transaction.Transactional;
@Service
public class PostTargetServiceServiceBean implements PostTargetService {
@Autowired
private PostTargetRepository postTargetRepository;
@Autowired
private ChannelRepository channelRepository;
@Override
@Transactional
public void createPostTarget(String name, AChannel targetChannel) {
if(!PostTarget.AVAILABLE_POST_TARGETS.contains(name)) {
throw new PostTargetException("PostTarget not found");
}
postTargetRepository.save(PostTarget.builder().name(name).AChannel(targetChannel).build());
}
@Override
@Transactional
public void createOrUpdate(String name, AChannel targetChannel) {
PostTarget existing = postTargetRepository.findPostTargetByName(name);
if(existing == null){
this.createPostTarget(name, targetChannel);
} else {
this.updatePostTarget(existing, targetChannel);
}
}
@Override
@Transactional
public void updatePostTarget(PostTarget target, AChannel newTargetChannel) {
postTargetRepository.getOne(target.getId()).setAChannel(newTargetChannel);
}
}

View File

@@ -0,0 +1,18 @@
package dev.sheldan.abstracto.core.service;
import dev.sheldan.abstracto.core.models.ARole;
import dev.sheldan.abstracto.repository.RoleRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class RoleServiceBean implements RoleService {
@Autowired
private RoleRepository repository;
@Override
public ARole createRole(Long id) {
return repository.save(ARole.builder().id(id).build());
}
}

View File

@@ -0,0 +1,28 @@
package dev.sheldan.abstracto.core.service;
import dev.sheldan.abstracto.core.models.AChannel;
import dev.sheldan.abstracto.core.models.AServer;
import dev.sheldan.abstracto.repository.ServerRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.transaction.Transactional;
@Service
public class ServerServiceBean implements ServerService {
@Autowired
private ServerRepository repository;
@Override
public AServer createServer(Long id) {
return repository.save(AServer.builder().id(id).build());
}
@Override
public void addChannelToServer(AServer server, AChannel channel) {
server.getChannels().add(channel);
}
}

View File

@@ -0,0 +1,93 @@
package dev.sheldan.abstracto.core.service;
import dev.sheldan.abstracto.SnowflakeUtils;
import dev.sheldan.abstracto.core.models.AChannel;
import dev.sheldan.abstracto.core.models.AChannelType;
import dev.sheldan.abstracto.core.models.ARole;
import dev.sheldan.abstracto.core.models.AServer;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.GuildChannel;
import net.dv8tion.jda.api.entities.Role;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.apache.commons.collections4.SetUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.security.auth.login.LoginException;
import javax.transaction.Transactional;
import java.util.List;
import java.util.Set;
@Service
public class StartupManager implements Startup {
@Autowired
private BotService service;
@Autowired
private List<? extends ListenerAdapter> listeners;
@Autowired
private ServerService serverService;
@Autowired
private ChannelService channelService;
@Autowired
private RoleService roleService;
@Override
public void startBot() throws LoginException {
service.login();
listeners.forEach(o -> service.getInstance().addEventListener(o));
}
@Override
@Transactional
public void synchronize() {
synchronizeServers();
}
private void synchronizeServers(){
JDA instance = service.getInstance();
List<Guild> onlineGuilds = instance.getGuilds();
Set<Long> availableServers = SnowflakeUtils.getSnowflakeIds(onlineGuilds);
availableServers.forEach(aLong -> {
AServer newAServer = serverService.createServer(aLong);
Guild newGuild = instance.getGuildById(aLong);
if(newGuild != null){
synchronizeRolesOf(newGuild, newAServer);
synchronizeChannelsOf(newGuild, newAServer);
}
});
}
private void synchronizeRolesOf(Guild guild, AServer existingAServer){
List<Role> existingRoles = guild.getRoles();
List<ARole> knownARoles = existingAServer.getRoles();
Set<Long> knownRolesId = SnowflakeUtils.getOwnItemsIds(knownARoles);
Set<Long> availableRoles = SnowflakeUtils.getSnowflakeIds(existingRoles);
Set<Long> newRoles = SetUtils.disjunction(availableRoles, knownRolesId);
newRoles.forEach(aLong -> {
ARole newRole = roleService.createRole(aLong);
existingAServer.getRoles().add(newRole);
});
}
private void synchronizeChannelsOf(Guild guild, AServer existingServer){
List<GuildChannel> available = guild.getChannels();
List<AChannel> knownChannels = existingServer.getChannels();
Set<Long> knownChannelsIds = SnowflakeUtils.getOwnItemsIds(knownChannels);
Set<Long> existingChannelsIds = SnowflakeUtils.getSnowflakeIds(available);
Set<Long> newChannels = SetUtils.disjunction(existingChannelsIds, knownChannelsIds);
newChannels.forEach(aLong -> {
GuildChannel channel1 = available.stream().filter(channel -> channel.getIdLong() == aLong).findFirst().get();
AChannelType type = AChannel.getAChannelType(channel1.getType());
AChannel newChannel = channelService.createChannel(channel1.getIdLong(), type);
serverService.addChannelToServer(existingServer, newChannel);
});
}
}

View File

@@ -0,0 +1,44 @@
package dev.sheldan.abstracto.listener;
import dev.sheldan.abstracto.core.models.AChannel;
import dev.sheldan.abstracto.core.models.AServer;
import dev.sheldan.abstracto.repository.ServerRepository;
import net.dv8tion.jda.api.entities.TextChannel;
import net.dv8tion.jda.api.events.channel.text.TextChannelCreateEvent;
import net.dv8tion.jda.api.events.channel.text.TextChannelDeleteEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.Nonnull;
import java.util.Optional;
@Service
public class ChannelListener extends ListenerAdapter {
@Autowired
private ServerRepository serverRepository;
private static Logger logger = LoggerFactory.getLogger(ChannelListener.class);
@Override
public void onTextChannelDelete(@Nonnull TextChannelDeleteEvent event) {
AServer serverObject = serverRepository.getOne(event.getGuild().getIdLong());
serverObject.getChannels().add(AChannel.builder().id(event.getChannel().getIdLong()).build());
}
@Override
public void onTextChannelCreate(@Nonnull TextChannelCreateEvent event) {
AServer serverObject = serverRepository.getOne(event.getGuild().getIdLong());
TextChannel createdChannel = event.getChannel();
Optional<AChannel> possibleChannel = serverObject.getChannels().stream().filter(aChannel -> aChannel.id == createdChannel.getIdLong()).findAny();
if(possibleChannel.isPresent()){
serverObject.getChannels().remove(possibleChannel.get());
logger.info("Adding channel {} with id {}", createdChannel.getName(), createdChannel.getIdLong());
} else {
logger.warn("Channel removed event for channel which was not in present");
}
}
}

View File

@@ -0,0 +1,27 @@
package dev.sheldan.abstracto.listener;
import dev.sheldan.abstracto.core.service.StartupManager;
import net.dv8tion.jda.api.events.ReadyEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.annotation.Nonnull;
@Service
public class ReadyListener extends ListenerAdapter {
@Autowired
private StartupManager startup;
@Value("${abstracto.startup.synchronize}")
private boolean synchronize;
@Override
public void onReady(@Nonnull ReadyEvent event) {
if(synchronize){
startup.synchronize();
}
}
}

View File

@@ -1,24 +0,0 @@
package dev.sheldan.abstracto.models;
import lombok.Builder;
import lombok.Getter;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import java.util.Set;
@Entity
@Table
@Builder
public class Channel {
@Id
@Getter
public Long id;
@Getter
@ManyToMany(mappedBy = "channels")
private Set<ChannelGroup> groups;
}

View File

@@ -1,26 +0,0 @@
package dev.sheldan.abstracto.models;
import lombok.Getter;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
import javax.persistence.*;
@Entity
@Table
public class PostTarget {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Getter
private Long id;
@Column(unique = true)
@Getter
private String name;
@OneToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "id", nullable = false)
@Getter
private Channel channel;
}

View File

@@ -1,9 +1,12 @@
package dev.sheldan.abstracto.repository;
import dev.sheldan.abstracto.models.Channel;
import dev.sheldan.abstracto.core.models.AChannel;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface ChannelRepository extends JpaRepository<Channel, Long> {
public interface ChannelRepository extends JpaRepository<AChannel, Long> {
List<AChannel> findAll();
}

View File

@@ -0,0 +1,12 @@
package dev.sheldan.abstracto.repository;
import dev.sheldan.abstracto.core.models.PostTarget;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface PostTargetRepository extends JpaRepository<PostTarget, Long> {
PostTarget findPostTargetByName(String name);
}

View File

@@ -0,0 +1,9 @@
package dev.sheldan.abstracto.repository;
import dev.sheldan.abstracto.core.models.ARole;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface RoleRepository extends JpaRepository<ARole, Long> {
}

View File

@@ -0,0 +1,12 @@
package dev.sheldan.abstracto.repository;
import dev.sheldan.abstracto.core.models.AServer;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface ServerRepository extends JpaRepository<AServer, Long> {
List<AServer> findAll();
}

View File

@@ -1,25 +0,0 @@
package dev.sheldan.abstracto.service;
import dev.sheldan.abstracto.commands.management.CommandReceivedHandler;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.security.auth.login.LoginException;
import java.util.List;
@Service
public class StartupManager implements Startup {
@Autowired
private BotService service;
@Autowired
private List<? extends ListenerAdapter> listeners;
@Override
public void startBot() throws LoginException {
service.login();
listeners.forEach(o -> service.getInstance().addEventListener(o));
}
}

View File

@@ -1,6 +0,0 @@
spring.datasource.url=jdbc:postgresql://localhost:5432/abstracto
spring.datasource.username= abstracto
spring.datasource.password= abstracto
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect
spring.jpa.hibernate.ddl-auto = update

View File

@@ -22,5 +22,9 @@
<groupId>net.dv8tion</groupId>
<artifactId>JDA</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,40 @@
package dev.sheldan.abstracto.core.models;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import net.dv8tion.jda.api.entities.ChannelType;
import javax.persistence.*;
import java.util.Set;
@Entity
@Table(name="channel")
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AChannel implements SnowFlake {
@Id
@Getter
public Long id;
@Getter
@ManyToMany(mappedBy = "channels")
private Set<ChannelGroup> groups;
@Getter
@Enumerated(EnumType.STRING)
private AChannelType type;
public static AChannelType getAChannelType(ChannelType type) {
switch (type) {
case TEXT: return AChannelType.TEXT;
case PRIVATE: return AChannelType.DM;
case VOICE: return AChannelType.VOICE;
case CATEGORY: return AChannelType.CATEGORY;
default: return AChannelType.UNKOWN;
}
}
}

View File

@@ -0,0 +1,5 @@
package dev.sheldan.abstracto.core.models;
public enum AChannelType {
TEXT, DM, VOICE, NEWS, CATEGORY, UNKOWN
}

View File

@@ -1,7 +1,6 @@
package dev.sheldan.abstracto.models;
package dev.sheldan.abstracto.core.models;
import lombok.Getter;
import lombok.Setter;
import lombok.*;
import javax.persistence.Column;
import javax.persistence.Entity;
@@ -9,11 +8,14 @@ import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table
public class Role {
@Table(name="role")
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ARole implements SnowFlake {
@Id
@Getter
@Getter @Setter
private Long id;
@Column(unique = true)

View File

@@ -0,0 +1,35 @@
package dev.sheldan.abstracto.core.models;
import lombok.*;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Entity
@Table(name = "server")
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AServer implements SnowFlake {
@Id
@Getter @Setter
private Long id;
@Getter
private String name;
@OneToMany(fetch = FetchType.LAZY)
@Getter
@Builder.Default
private List<ARole> roles = new ArrayList<>();
@OneToMany(fetch = FetchType.LAZY)
@Getter
@Builder.Default
private List<AChannel> channels = new ArrayList<>();
}

View File

@@ -1,13 +1,15 @@
package dev.sheldan.abstracto.models;
package dev.sheldan.abstracto.core.models;
import lombok.Getter;
import lombok.Setter;
import lombok.*;
import javax.persistence.*;
import java.util.Set;
@Entity
@Table
@Table(name="channelGroup")
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ChannelGroup {
@Id
@@ -25,7 +27,7 @@ public class ChannelGroup {
joinColumns = @JoinColumn(name = "group_id"),
inverseJoinColumns = @JoinColumn(name = "channel_id"))
@Getter
private Set<Channel> channels;
private Set<AChannel> channels;
}

View File

@@ -0,0 +1,34 @@
package dev.sheldan.abstracto.core.models;
import lombok.*;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@Entity
@Table(name="posttarget")
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class PostTarget {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Getter
private Long id;
@Column(unique = true)
@Getter
private String name;
@OneToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "id", nullable = false)
@Getter @Setter
private AChannel AChannel;
public static String JOIN_LOG = "joinlog";
public static List<String> AVAILABLE_POST_TARGETS = Arrays.asList(JOIN_LOG);
}

View File

@@ -0,0 +1,5 @@
package dev.sheldan.abstracto.core.models;
public interface SnowFlake {
Long getId();
}

View File

@@ -1,4 +1,4 @@
package dev.sheldan.abstracto.service;
package dev.sheldan.abstracto.core.service;
import net.dv8tion.jda.api.JDA;
import org.springframework.stereotype.Service;

View File

@@ -0,0 +1,9 @@
package dev.sheldan.abstracto.core.service;
import dev.sheldan.abstracto.core.models.AChannel;
import dev.sheldan.abstracto.core.models.AChannelType;
public interface ChannelService {
AChannel loadChannel(Long id);
AChannel createChannel(Long id, AChannelType type);
}

View File

@@ -0,0 +1,10 @@
package dev.sheldan.abstracto.core.service;
import dev.sheldan.abstracto.core.models.AChannel;
import dev.sheldan.abstracto.core.models.PostTarget;
public interface PostTargetService {
void createPostTarget(String name, AChannel targetChanel);
void createOrUpdate(String name, AChannel targetChannel);
void updatePostTarget(PostTarget target, AChannel newTargetChannel);
}

View File

@@ -0,0 +1,7 @@
package dev.sheldan.abstracto.core.service;
import dev.sheldan.abstracto.core.models.ARole;
public interface RoleService {
ARole createRole(Long id);
}

View File

@@ -0,0 +1,9 @@
package dev.sheldan.abstracto.core.service;
import dev.sheldan.abstracto.core.models.AChannel;
import dev.sheldan.abstracto.core.models.AServer;
public interface ServerService {
AServer createServer(Long id);
void addChannelToServer(AServer server, AChannel channel);
}

View File

@@ -1,4 +1,4 @@
package dev.sheldan.abstracto.service;
package dev.sheldan.abstracto.core.service;
import org.springframework.stereotype.Service;
@@ -6,5 +6,6 @@ import javax.security.auth.login.LoginException;
@Service
public interface Startup {
public void startBot() throws LoginException;
void startBot() throws LoginException;
void synchronize();
}

View File

@@ -0,0 +1,70 @@
<?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>
<artifactId>abstracto-application</artifactId>
<groupId>dev.sheldan.abstracto</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>executable</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>dev.sheldan.abstracto.core</groupId>
<artifactId>core-interface</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>dev.sheldan.abstracto.core</groupId>
<artifactId>core-interface</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>dev.sheldan.abstracto.core</groupId>
<artifactId>core-impl</artifactId>
<version>${project.version}</version>
<scope>runtime</scope>
</dependency>
<!-- modules containing commands -->
<dependency>
<groupId>dev.sheldan.abstracto.command</groupId>
<artifactId>utility</artifactId>
<version>${project.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>dev.sheldan.abstracto.command</groupId>
<artifactId>support</artifactId>
<version>${project.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>dev.sheldan.abstracto.command</groupId>
<artifactId>channels</artifactId>
<version>${project.version}</version>
<scope>runtime</scope>
</dependency>
</dependencies>
</project>

View File

@@ -1,6 +1,6 @@
package dev.sheldan.abstracto;
import dev.sheldan.abstracto.service.StartupManager;
import dev.sheldan.abstracto.core.service.Startup;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
@@ -9,12 +9,12 @@ import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@ComponentScan(basePackages = "dev.sheldan.abstracto")
@ComponentScan(basePackages = {"dev.sheldan.abstracto"})
@EnableCaching
public class Application implements CommandLineRunner {
@Autowired
private StartupManager startup;
private Startup startup;
public static void main(String[] args) {

View File

@@ -0,0 +1,11 @@
spring.datasource.url=jdbc:postgresql://localhost:5432/abstracto
spring.datasource.username= abstracto
spring.datasource.password= abstracto
spring.jpa.properties.hibernate.default_schema=abstracto
spring.jpa.show-sql = true
spring.jpa.hibernate.ddl-auto = create-drop
spring.jpa.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect
spring.jpa.hibernate.naming_strategy = org.hibernate.cfg.ImprovedNamingStrategy
abstracto.startup.synchronize=true

View File

@@ -17,6 +17,7 @@
<modules>
<module>command</module>
<module>core</module>
<module>executable</module>
</modules>
@@ -33,6 +34,17 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.3</version>
</dependency>
</dependencies>
</dependencyManagement>
@@ -42,6 +54,15 @@
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
</dependency>
</dependencies>
</project>