[AB-64] adding voice context channel feature

This commit is contained in:
Sheldan
2021-07-12 00:56:15 +02:00
parent 7e7591a4b3
commit c7514a6bad
40 changed files with 2327 additions and 1 deletions

View File

@@ -27,6 +27,7 @@
<module>logging</module>
<module>invite-filter</module>
<module>profanity-filter</module>
<module>voice-channel-context</module>
</modules>
</project>

View File

@@ -0,0 +1,28 @@
<?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-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.3.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>voice-channel-context</artifactId>
<packaging>pom</packaging>
<modules>
<module>voice-channel-context-int</module>
<module>voice-channel-context-impl</module>
</modules>
<dependencies>
<dependency>
<groupId>dev.sheldan.abstracto.core</groupId>
<artifactId>core-int</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,58 @@
<?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>voice-channel-context</artifactId>
<version>1.3.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>voice-channel-context-impl</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptors>
<descriptor>src/main/assembly/liquibase.xml</descriptor>
</descriptors>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>voice-channel-context-int</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>dev.sheldan.abstracto.core</groupId>
<artifactId>core-int</artifactId>
<version>${project.version}</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,18 @@
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.1.0 http://maven.apache.org/xsd/assembly-2.1.0.xsd">
<id>liquibase</id>
<formats>
<format>zip</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<fileSets>
<fileSet>
<outputDirectory>.</outputDirectory>
<directory>${project.basedir}/src/main/resources/migrations</directory>
<includes>
<include>**/*</include>
</includes>
</fileSet>
</fileSets>
</assembly>

View File

@@ -0,0 +1,57 @@
package dev.sheldan.abstracto.vccontext.command;
import dev.sheldan.abstracto.core.command.UtilityModuleDefinition;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.vccontext.config.VoiceChannelContextFeatureDefinition;
import dev.sheldan.abstracto.vccontext.service.VoiceChannelContextService;
import net.dv8tion.jda.api.entities.Role;
import net.dv8tion.jda.api.entities.VoiceChannel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
@Component
public class CreateVoiceChannelContext extends AbstractConditionableCommand {
@Autowired
private VoiceChannelContextService voiceChannelContextService;
@Override
public CommandResult execute(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters();
VoiceChannel channel = (VoiceChannel) parameters.get(0);
Role role = (Role) parameters.get(1);
voiceChannelContextService.createVoiceChannelContext(channel, role);
return CommandResult.fromSuccess();
}
@Override
public CommandConfiguration getConfiguration() {
Parameter voiceChannel = Parameter.builder().name("voiceChannel").type(VoiceChannel.class).templated(true).build();
Parameter contextRole = Parameter.builder().name("role").type(Role.class).templated(true).build();
List<Parameter> parameters = Arrays.asList(voiceChannel, contextRole);
HelpInfo helpInfo = HelpInfo.builder().templated(true).build();
return CommandConfiguration.builder()
.name("createVoiceChannelContext")
.module(UtilityModuleDefinition.UTILITY)
.templated(true)
.supportsEmbedException(true)
.causesReaction(true)
.parameters(parameters)
.help(helpInfo)
.build();
}
@Override
public FeatureDefinition getFeature() {
return VoiceChannelContextFeatureDefinition.VOICE_CHANNEL_CONTEXT;
}
}

View File

@@ -0,0 +1,59 @@
package dev.sheldan.abstracto.vccontext.command;
import dev.sheldan.abstracto.core.command.UtilityModuleDefinition;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.service.management.ChannelManagementService;
import dev.sheldan.abstracto.vccontext.config.VoiceChannelContextFeatureDefinition;
import dev.sheldan.abstracto.vccontext.service.VoiceChannelContextService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
@Component
public class DeleteVoiceChannelContext extends AbstractConditionableCommand {
@Autowired
private VoiceChannelContextService voiceChannelContextService;
@Autowired
private ChannelManagementService channelManagementService;
@Override
public CommandResult execute(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters();
AChannel channel = (AChannel) parameters.get(0);
AChannel actualChannel = channelManagementService.loadChannel(channel.getId());
voiceChannelContextService.deleteVoiceChannelContext(actualChannel);
return CommandResult.fromSuccess();
}
@Override
public CommandConfiguration getConfiguration() {
Parameter voiceChannel = Parameter.builder().name("channel").type(AChannel.class).templated(true).build();
List<Parameter> parameters = Arrays.asList(voiceChannel);
HelpInfo helpInfo = HelpInfo.builder().templated(true).build();
return CommandConfiguration.builder()
.name("deleteVoiceChannelContext")
.module(UtilityModuleDefinition.UTILITY)
.templated(true)
.supportsEmbedException(true)
.causesReaction(true)
.parameters(parameters)
.help(helpInfo)
.build();
}
@Override
public FeatureDefinition getFeature() {
return VoiceChannelContextFeatureDefinition.VOICE_CHANNEL_CONTEXT;
}
}

View File

@@ -0,0 +1,10 @@
package dev.sheldan.abstracto.vccontext.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
@Configuration
@PropertySource("classpath:voice-channel-context-config.properties")
public class VoiceChannelContextConfig {
}

View File

@@ -0,0 +1,56 @@
package dev.sheldan.abstracto.vccontext.listener;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
import dev.sheldan.abstracto.core.listener.async.jda.AsyncVoiceChannelJoinedListener;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.listener.VoiceChannelJoinedModel;
import dev.sheldan.abstracto.core.service.RoleService;
import dev.sheldan.abstracto.core.service.management.ChannelManagementService;
import dev.sheldan.abstracto.vccontext.config.VoiceChannelContextFeatureDefinition;
import dev.sheldan.abstracto.vccontext.model.VoiceChannelContext;
import dev.sheldan.abstracto.vccontext.service.management.VoiceChannelContextManagementService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Optional;
@Component
@Slf4j
public class VoiceChannelContextJoinedListener implements AsyncVoiceChannelJoinedListener {
@Autowired
private VoiceChannelContextManagementService voiceChannelContextManagementService;
@Autowired
private ChannelManagementService channelManagementService;
@Autowired
private RoleService roleService;
@Override
public DefaultListenerResult execute(VoiceChannelJoinedModel model) {
log.info("Member {} joined voice channel {}.", model.getMember().getIdLong(), model.getChannel().getIdLong());
AChannel aChannel = channelManagementService.loadChannel(model.getChannel().getIdLong());
Optional<VoiceChannelContext> voiceChannelContextOptional = voiceChannelContextManagementService.getVoiceChannelContextForChannel(aChannel);
if(voiceChannelContextOptional.isPresent()) {
VoiceChannelContext context = voiceChannelContextOptional.get();
log.info("Channel {} is a voice channel with context - applying role {}.", aChannel.getId(), context.getRole().getId());
Long roleId = context.getRole().getId();
roleService.addRoleToMemberAsync(model.getMember(), context.getRole())
.thenAccept(unused -> log.info("Successfully applied role {} to member {} for channel {}.", roleId, model.getMember().getId(), model.getChannel().getId()))
.exceptionally(throwable -> {
log.error("Failed to apply role {} to member {} for channel {}.", roleId, model.getMember().getId(), model.getChannel().getId(), throwable);
return null;
});
return DefaultListenerResult.PROCESSED;
}
return DefaultListenerResult.IGNORED;
}
@Override
public FeatureDefinition getFeature() {
return VoiceChannelContextFeatureDefinition.VOICE_CHANNEL_CONTEXT;
}
}

View File

@@ -0,0 +1,56 @@
package dev.sheldan.abstracto.vccontext.listener;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
import dev.sheldan.abstracto.core.listener.async.jda.AsyncVoiceChannelLeftListener;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.listener.VoiceChannelLeftModel;
import dev.sheldan.abstracto.core.service.RoleService;
import dev.sheldan.abstracto.core.service.management.ChannelManagementService;
import dev.sheldan.abstracto.vccontext.config.VoiceChannelContextFeatureDefinition;
import dev.sheldan.abstracto.vccontext.model.VoiceChannelContext;
import dev.sheldan.abstracto.vccontext.service.management.VoiceChannelContextManagementService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Optional;
@Component
@Slf4j
public class VoiceChannelContextLeftListener implements AsyncVoiceChannelLeftListener {
@Autowired
private VoiceChannelContextManagementService voiceChannelContextManagementService;
@Autowired
private ChannelManagementService channelManagementService;
@Autowired
private RoleService roleService;
@Override
public DefaultListenerResult execute(VoiceChannelLeftModel model) {
log.info("Member {} left voice channel {}.", model.getMember().getIdLong(), model.getChannel().getIdLong());
AChannel aChannel = channelManagementService.loadChannel(model.getChannel().getIdLong());
Optional<VoiceChannelContext> voiceChannelContextOptional = voiceChannelContextManagementService.getVoiceChannelContextForChannel(aChannel);
if(voiceChannelContextOptional.isPresent()) {
VoiceChannelContext context = voiceChannelContextOptional.get();
log.info("Channel {} is a voice channel with context - removing role {}.", aChannel.getId(), context.getRole().getId());
Long roleId = context.getRole().getId();
roleService.removeRoleFromMemberAsync(model.getMember(), context.getRole())
.thenAccept(unused -> log.info("Successfully removed role {} from member {} for channel {}.", roleId, model.getMember().getId(), model.getChannel().getId()))
.exceptionally(throwable -> {
log.error("Failed to remove role {} from member {} for channel {}.", roleId, model.getMember().getId(), model.getChannel().getId(), throwable);
return null;
});
return DefaultListenerResult.PROCESSED;
}
return DefaultListenerResult.IGNORED;
}
@Override
public FeatureDefinition getFeature() {
return VoiceChannelContextFeatureDefinition.VOICE_CHANNEL_CONTEXT;
}
}

View File

@@ -0,0 +1,9 @@
package dev.sheldan.abstracto.vccontext.repository;
import dev.sheldan.abstracto.vccontext.model.VoiceChannelContext;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface VoiceChannelContextRepository extends JpaRepository<VoiceChannelContext, Long> {
}

View File

@@ -0,0 +1,48 @@
package dev.sheldan.abstracto.vccontext.service;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.ARole;
import dev.sheldan.abstracto.core.service.management.ChannelManagementService;
import dev.sheldan.abstracto.core.service.management.RoleManagementService;
import dev.sheldan.abstracto.vccontext.exception.VoiceChannelContextAlreadyExistsException;
import dev.sheldan.abstracto.vccontext.exception.VoiceChannelContextNotExistsException;
import dev.sheldan.abstracto.vccontext.model.VoiceChannelContext;
import dev.sheldan.abstracto.vccontext.service.management.VoiceChannelContextManagementService;
import net.dv8tion.jda.api.entities.Role;
import net.dv8tion.jda.api.entities.VoiceChannel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Optional;
@Component
public class VoiceChannelContextServiceBean implements VoiceChannelContextService {
@Autowired
private VoiceChannelContextManagementService voiceChannelContextManagementService;
@Autowired
private ChannelManagementService channelManagementService;
@Autowired
private RoleManagementService roleManagementService;
@Override
public VoiceChannelContext createVoiceChannelContext(VoiceChannel voiceChannel, Role role) {
AChannel channel = channelManagementService.loadChannel(voiceChannel.getIdLong());
if(voiceChannelContextManagementService.getVoiceChannelContextForChannel(channel).isPresent()) {
throw new VoiceChannelContextAlreadyExistsException();
}
ARole arole = roleManagementService.findRole(role.getIdLong());
return voiceChannelContextManagementService.createVoiceChannelContext(channel, arole);
}
@Override
public void deleteVoiceChannelContext(AChannel channel) {
Optional<VoiceChannelContext> ctxOptional = voiceChannelContextManagementService.getVoiceChannelContextForChannel(channel);
if(!ctxOptional.isPresent()) {
throw new VoiceChannelContextNotExistsException();
}
ctxOptional.ifPresent(context -> voiceChannelContextManagementService.deleteVoiceChannelContext(context));
}
}

View File

@@ -0,0 +1,43 @@
package dev.sheldan.abstracto.vccontext.service.management;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.ARole;
import dev.sheldan.abstracto.vccontext.model.VoiceChannelContext;
import dev.sheldan.abstracto.vccontext.repository.VoiceChannelContextRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Optional;
@Component
public class VoiceChannelContextManagementServiceBean implements VoiceChannelContextManagementService {
@Autowired
private VoiceChannelContextRepository repository;
@Override
public VoiceChannelContext createVoiceChannelContext(AChannel channel, ARole role) {
VoiceChannelContext context = VoiceChannelContext
.builder()
.id(channel.getId())
.role(role)
.channel(channel)
.build();
return repository.save(context);
}
@Override
public void deleteVoiceChannelContext(AChannel channel) {
repository.deleteById(channel.getId());
}
@Override
public void deleteVoiceChannelContext(VoiceChannelContext context) {
repository.delete(context);
}
@Override
public Optional<VoiceChannelContext> getVoiceChannelContextForChannel(AChannel channel) {
return repository.findById(channel.getId());
}
}

View File

@@ -0,0 +1,11 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog dbchangelog.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<include file="tables/tables.xml" relativeToChangelogFile="true"/>
<include file="seedData/data.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -0,0 +1,25 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog dbchangelog.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<property name="utilityModule" value="(SELECT id FROM module WHERE name = 'utility')"/>
<property name="voiceChannelContextFeature" value="(SELECT id FROM feature WHERE key = 'voiceChannelContext')"/>
<changeSet author="Sheldan" id="voiceChannelContext-commands">
<insert tableName="command">
<column name="name" value="createVoiceChannelContext"/>
<column name="module_id" valueComputed="${utilityModule}"/>
<column name="feature_id" valueComputed="${voiceChannelContextFeature}"/>
</insert>
<insert tableName="command">
<column name="name" value="deleteVoiceChannelContext"/>
<column name="module_id" valueComputed="${utilityModule}"/>
<column name="feature_id" valueComputed="${voiceChannelContextFeature}"/>
</insert>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,11 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog dbchangelog.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<include file="feature.xml" relativeToChangelogFile="true"/>
<include file="command.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -0,0 +1,14 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog dbchangelog.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<changeSet author="Sheldan" id="voice_channel_context_feature-insertion">
<insert tableName="feature">
<column name="key" value="voiceChannelContext"/>
</insert>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,10 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog dbchangelog.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<include file="voice_channel_context.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -0,0 +1,31 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog dbchangelog.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<changeSet author="Sheldan" id="voice_channel_context-table">
<createTable tableName="voice_channel_context">
<column autoIncrement="true" name="id" type="BIGINT">
<constraints nullable="false" primaryKey="true" primaryKeyName="voice_channel_context_pkey"/>
</column>
<column name="created" type="TIMESTAMP WITHOUT TIME ZONE">
<constraints nullable="false"/>
</column>
<column name="role_id" type="BIGINT">
<constraints nullable="false" />
</column>
</createTable>
<addForeignKeyConstraint baseColumnNames="id" baseTableName="voice_channel_context" constraintName="fk_voice_channel_context_channel" deferrable="false"
initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION" referencedColumnNames="id" referencedTableName="channel" validate="true"/>
<addForeignKeyConstraint baseColumnNames="role_id" baseTableName="voice_channel_context" constraintName="fk_voice_channel_context_role" deferrable="false"
initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION" referencedColumnNames="id"
referencedTableName="role" validate="true"/>
<sql>
DROP TRIGGER IF EXISTS voice_channel_context_insert_trigger ON voice_channel_context;
CREATE TRIGGER voice_channel_context_insert_trigger BEFORE INSERT ON voice_channel_context FOR EACH ROW EXECUTE PROCEDURE insert_trigger_procedure();
</sql>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,10 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog dbchangelog.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<include file="1.3.1/collection.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -0,0 +1,2 @@
abstracto.featureFlags.voiceChannelContext.featureName=voiceChannelContext
abstracto.featureFlags.voiceChannelContext.enabled=false

View File

@@ -0,0 +1,20 @@
<?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>voice-channel-context</artifactId>
<version>1.3.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>voice-channel-context-int</artifactId>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
</project>

View File

@@ -0,0 +1,15 @@
package dev.sheldan.abstracto.vccontext.config;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import org.springframework.stereotype.Component;
@Component
public class VoiceChannelContextFeatureConfig implements FeatureConfig {
@Override
public FeatureDefinition getFeature() {
return VoiceChannelContextFeatureDefinition.VOICE_CHANNEL_CONTEXT;
}
}

View File

@@ -0,0 +1,15 @@
package dev.sheldan.abstracto.vccontext.config;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import lombok.Getter;
@Getter
public enum VoiceChannelContextFeatureDefinition implements FeatureDefinition {
VOICE_CHANNEL_CONTEXT("voiceChannelContext");
private String key;
VoiceChannelContextFeatureDefinition(String key) {
this.key = key;
}
}

View File

@@ -0,0 +1,15 @@
package dev.sheldan.abstracto.vccontext.exception;
import dev.sheldan.abstracto.core.exception.AbstractoTemplatableException;
public class VoiceChannelContextAlreadyExistsException extends AbstractoTemplatableException {
@Override
public String getTemplateName() {
return "voice_channel_context_already_exists_exception";
}
@Override
public Object getTemplateModel() {
return new Object();
}
}

View File

@@ -0,0 +1,15 @@
package dev.sheldan.abstracto.vccontext.exception;
import dev.sheldan.abstracto.core.exception.AbstractoTemplatableException;
public class VoiceChannelContextNotExistsException extends AbstractoTemplatableException {
@Override
public String getTemplateName() {
return "voice_channel_context_not_exists_exception";
}
@Override
public Object getTemplateModel() {
return new Object();
}
}

View File

@@ -0,0 +1,32 @@
package dev.sheldan.abstracto.vccontext.model;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.ARole;
import lombok.*;
import javax.persistence.*;
import java.io.Serializable;
@Entity
@Table(name="voice_channel_context")
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@EqualsAndHashCode
public class VoiceChannelContext implements Serializable {
@Id
@Column(name = "id", nullable = false)
private Long id;
@OneToOne(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
@PrimaryKeyJoinColumn
private AChannel channel;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "role_id", nullable = false)
private ARole role;
}

View File

@@ -0,0 +1,11 @@
package dev.sheldan.abstracto.vccontext.service;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.vccontext.model.VoiceChannelContext;
import net.dv8tion.jda.api.entities.Role;
import net.dv8tion.jda.api.entities.VoiceChannel;
public interface VoiceChannelContextService {
VoiceChannelContext createVoiceChannelContext(VoiceChannel voiceChannel, Role role);
void deleteVoiceChannelContext(AChannel channel);
}

View File

@@ -0,0 +1,14 @@
package dev.sheldan.abstracto.vccontext.service.management;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.ARole;
import dev.sheldan.abstracto.vccontext.model.VoiceChannelContext;
import java.util.Optional;
public interface VoiceChannelContextManagementService {
VoiceChannelContext createVoiceChannelContext(AChannel channel, ARole role);
void deleteVoiceChannelContext(AChannel channel);
void deleteVoiceChannelContext(VoiceChannelContext context);
Optional<VoiceChannelContext> getVoiceChannelContextForChannel(AChannel channel);
}

View File

@@ -72,6 +72,18 @@
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>voice-channel-context-int</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>voice-channel-context-impl</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>dev.sheldan.abstracto.scheduling</groupId>
<artifactId>scheduling-impl</artifactId>

View File

@@ -0,0 +1,53 @@
package dev.sheldan.abstracto.core.command.handler;
import dev.sheldan.abstracto.core.command.Command;
import dev.sheldan.abstracto.core.command.CommandConstants;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.exception.AbstractoTemplatedException;
import dev.sheldan.abstracto.core.command.execution.ParameterPieceType;
import dev.sheldan.abstracto.core.command.execution.UnparsedCommandParameterPiece;
import dev.sheldan.abstracto.core.command.handler.provided.VoiceChannelParameterHandler;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.VoiceChannel;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.regex.Matcher;
@Component
public class VoiceChannelParameterHandlerImpl implements VoiceChannelParameterHandler {
@Override
public boolean handles(Class clazz, UnparsedCommandParameterPiece value) {
return clazz.equals(VoiceChannel.class) && value.getType().equals(ParameterPieceType.STRING);
}
@Override
public Object handle(UnparsedCommandParameterPiece input, CommandParameterIterators iterators, Parameter param, Message context, Command command) {
String inputString = ((String) input.getValue()).trim();
Matcher matcher = Message.MentionType.CHANNEL.getPattern().matcher(inputString);
if(matcher.matches()) {
long channelId = Long.parseLong(matcher.group(1));
return context.getGuild().getVoiceChannelById(channelId);
} else {
if(NumberUtils.isParsable(inputString)) {
long channelId = Long.parseLong(inputString);
return context.getGuild().getVoiceChannelById(channelId);
} else {
List<VoiceChannel> possibleVoiceChannels = context.getGuild().getVoiceChannelsByName(inputString, true);
if(possibleVoiceChannels.isEmpty()) {
throw new AbstractoTemplatedException("No channel found with name.", "no_channel_found_by_name_exception");
}
if(possibleVoiceChannels.size() > 1) {
throw new AbstractoTemplatedException("Multiple channels found with name.", "multiple_channels_found_by_name_exception");
}
return possibleVoiceChannels.get(0);
}
}
}
@Override
public Integer getPriority() {
return CommandConstants.CORE_HANDLER_PRIORITY;
}
}

View File

@@ -147,4 +147,14 @@ public class ListenerExecutorConfig {
return executorService.setupExecutorFor("userBannedListener");
}
@Bean(name = "voiceChatJoinedExecutor")
public TaskExecutor voiceChatJoinedExecutor() {
return executorService.setupExecutorFor("voiceChatJoinedListener");
}
@Bean(name = "voiceChatLeftExecutor")
public TaskExecutor voiceChatLeftExecutor() {
return executorService.setupExecutorFor("voiceChatLeftListener");
}
}

View File

@@ -0,0 +1,44 @@
package dev.sheldan.abstracto.core.listener.async.jda;
import dev.sheldan.abstracto.core.listener.ListenerService;
import dev.sheldan.abstracto.core.models.listener.VoiceChannelJoinedModel;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.events.guild.voice.GuildVoiceJoinEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.task.TaskExecutor;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
@Slf4j
public class AsyncVoiceChannelJoinedListenerBean extends ListenerAdapter {
@Autowired(required = false)
private List<AsyncVoiceChannelJoinedListener> listenerList;
@Autowired
@Qualifier("voiceChatJoinedExecutor")
private TaskExecutor voiceChatJoinedExecutor;
@Autowired
private ListenerService listenerService;
@Override
public void onGuildVoiceJoin(@NotNull GuildVoiceJoinEvent event) {
if(listenerList == null) return;
VoiceChannelJoinedModel model = getModel(event);
listenerList.forEach(leaveListener -> listenerService.executeFeatureAwareListener(leaveListener, model, voiceChatJoinedExecutor));
}
private VoiceChannelJoinedModel getModel(GuildVoiceJoinEvent event) {
return VoiceChannelJoinedModel
.builder()
.channel(event.getChannelJoined())
.member(event.getMember())
.build();
}
}

View File

@@ -0,0 +1,44 @@
package dev.sheldan.abstracto.core.listener.async.jda;
import dev.sheldan.abstracto.core.listener.ListenerService;
import dev.sheldan.abstracto.core.models.listener.VoiceChannelLeftModel;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.events.guild.voice.GuildVoiceLeaveEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.task.TaskExecutor;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
@Slf4j
public class AsyncVoiceChannelLeftListenerBean extends ListenerAdapter {
@Autowired
private List<AsyncVoiceChannelLeftListener> listenerList;
@Autowired
@Qualifier("voiceChatLeftExecutor")
private TaskExecutor voiceChatLeaveExecutor;
@Autowired
private ListenerService listenerService;
@Override
public void onGuildVoiceLeave(@NotNull GuildVoiceLeaveEvent event) {
if(listenerList == null) return;
VoiceChannelLeftModel model = getModel(event);
listenerList.forEach(leaveListener -> listenerService.executeFeatureAwareListener(leaveListener, model, voiceChatLeaveExecutor));
}
private VoiceChannelLeftModel getModel(GuildVoiceLeaveEvent event) {
return VoiceChannelLeftModel
.builder()
.channel(event.getChannelLeft())
.member(event.getMember())
.build();
}
}

View File

@@ -0,0 +1,6 @@
package dev.sheldan.abstracto.core.command.handler.provided;
import dev.sheldan.abstracto.core.command.handler.CommandParameterHandler;
public interface VoiceChannelParameterHandler extends CommandParameterHandler {
}

View File

@@ -0,0 +1,8 @@
package dev.sheldan.abstracto.core.listener.async.jda;
import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
import dev.sheldan.abstracto.core.listener.FeatureAwareListener;
import dev.sheldan.abstracto.core.models.listener.VoiceChannelJoinedModel;
public interface AsyncVoiceChannelJoinedListener extends FeatureAwareListener<VoiceChannelJoinedModel, DefaultListenerResult> {
}

View File

@@ -0,0 +1,8 @@
package dev.sheldan.abstracto.core.listener.async.jda;
import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
import dev.sheldan.abstracto.core.listener.FeatureAwareListener;
import dev.sheldan.abstracto.core.models.listener.VoiceChannelLeftModel;
public interface AsyncVoiceChannelLeftListener extends FeatureAwareListener<VoiceChannelLeftModel, DefaultListenerResult> {
}

View File

@@ -0,0 +1,22 @@
package dev.sheldan.abstracto.core.models.listener;
import dev.sheldan.abstracto.core.listener.FeatureAwareListenerModel;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.VoiceChannel;
@Getter
@Setter
@Builder
public class VoiceChannelJoinedModel implements FeatureAwareListenerModel {
private Member member;
private VoiceChannel channel;
@Override
public Long getServerId() {
return channel.getGuild().getIdLong();
}
}

View File

@@ -0,0 +1,22 @@
package dev.sheldan.abstracto.core.models.listener;
import dev.sheldan.abstracto.core.listener.FeatureAwareListenerModel;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.VoiceChannel;
@Getter
@Setter
@Builder
public class VoiceChannelLeftModel implements FeatureAwareListenerModel {
private Member member;
private VoiceChannel channel;
@Override
public Long getServerId() {
return channel.getGuild().getIdLong();
}
}

View File

@@ -213,4 +213,21 @@ Mock the message of another user::
Add text as reactions to another message::
* Usage: `react <message> <text>`
* Description: Takes the `text`, converts it into unicode characters, while trying to avoid duplicates, and adds the reactions to the given `message`. If it was not possible to avoid duplicates, or the overall reactions (including already existing reactions) would go over the Discord limit, this command will show an error message, without adding any reaction. Some characters can be replaced with one unicode character, for example 'SOS'.
* Description: Takes the `text`, converts it into unicode characters, while trying to avoid duplicates, and adds the reactions to the given `message`. If it was not possible to avoid duplicates, or the overall reactions (including already existing reactions) would go over the Discord limit, this command will show an error message, without adding any reaction. Some characters can be replaced with one unicode character, for example 'SOS'.
=== Voice channel context
This feature provides the ability to show certain text channels for certain voice channels and enable voice channels to be used for chatting while in voice channels.
Feature key: `voiceChannelContext`
Create a voice channel context::
* Usage `createVoiceChannelContext <voiceChannel> <role>`
* Description: Creates a connection between the `voiceChannel` and the given `role`. When a member joins the `voiceChannel` they will be given the `role`.
This role can then be used to provide the 'view channel' permission on a text channel which can be used as a 'context channel' for the voice chat.
The voice channel can be provided as a parameter via a mention (type '#!' + voice channel name), channel ID or the channel name.
Deleting a voice channel context::
* Usage `deleteVoiceChannelContext <voiceChannel>`
* Description: Deletes any voice channel context for the given `voiceChannel`. Members will no longer receive a role, when joining the voice channel. The `voiceChannel` can also be a channel ID.