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

@@ -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

@@ -1,29 +0,0 @@
package dev.sheldan.abstracto;
import dev.sheldan.abstracto.service.StartupManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@ComponentScan(basePackages = "dev.sheldan.abstracto")
@EnableCaching
public class Application implements CommandLineRunner {
@Autowired
private StartupManager startup;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Override
public void run(String... args) throws Exception {
startup.startBot();
}
}

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();
}