Compare commits

..

46 Commits

Author SHA1 Message Date
Sheldan
69011ddac7 [maven-release-plugin] prepare release sissi-1.3.2 2022-08-28 23:53:41 +02:00
Sheldan
177934d1c9 [SIS-xxx] upgrading to abstracto 1.4.3 and preparing for new version 2022-08-28 23:30:58 +02:00
Sheldan
44e38ca1de [maven-release-plugin] prepare for next development iteration 2022-07-27 22:42:02 +02:00
Sheldan
cc23eaf2d6 [maven-release-plugin] prepare release sissi-1.3.1 2022-07-27 22:41:59 +02:00
Sheldan
d7f9a62a62 [SIS-xxx] prepare for new version 2022-07-27 22:20:26 +02:00
Sheldan
af8f2c10ff [SIS-xxx] aligning templates with a fix 2022-07-25 22:30:54 +02:00
Sheldan
a2818241f6 [SIS-xxx] adapting dashboard to show better command information 2022-07-25 19:14:07 +02:00
Sheldan
af8bf920c0 [SIS-xxx] fixing application name in grafana dashboard 2022-07-23 17:47:16 +02:00
Sheldan
b4d3aaac15 [maven-release-plugin] prepare for next development iteration 2022-07-23 11:16:40 +02:00
Sheldan
01c86558cd [maven-release-plugin] prepare release sissi-1.3.0 2022-07-23 11:16:35 +02:00
Sheldan
08bab02451 [SIS-xxx] preparing for release 2022-07-23 11:12:17 +02:00
Sheldan
84359fcdac [SIS-10] also enabling modmail 2022-07-22 15:39:59 +02:00
Sheldan
c006665a0c [SIS-10] also enabling assignable role place 2022-07-22 01:28:37 +02:00
Sheldan
4e29134fa5 [SIS-10] enabling additional modules 2022-07-22 01:14:54 +02:00
Sheldan
249a3e3d19 [SIS-9] adding migration scripts for custom commands and economy 2022-07-22 00:27:59 +02:00
Sheldan
7f8c429a04 [SIS-8] creating modmode customization
enabling entertainment and custom command module
2022-07-21 23:09:07 +02:00
Sheldan
d74c10c618 [SIS-5] enabling and customizing moderation 2022-07-18 20:10:33 +02:00
Sheldan
0ec7d83191 [maven-release-plugin] prepare for next development iteration 2022-07-17 21:58:54 +02:00
Sheldan
4bfa706e7c [maven-release-plugin] prepare release sissi-1.2.0 2022-07-17 21:58:44 +02:00
Sheldan
2485bf4113 [SIS-xxxx] prepare for release 2022-07-17 21:58:06 +02:00
Sheldan
48e2e705c2 [SIS-6] adding ability to declare "no_time" for a meetup
adding meetup id to meetup message
refactoring meetup cancellation to be a command
adding command to change the meetup time and notify meetup members
fixing meetup components not being cleaned when cancelling or cleaning up
changing label for no button
2022-07-17 21:55:30 +02:00
Sheldan
f688a066b4 [maven-release-plugin] prepare for next development iteration 2022-07-16 13:49:32 +02:00
Sheldan
1e8a01dccc [maven-release-plugin] prepare release sissi-1.1.0 2022-07-16 13:49:23 +02:00
Sheldan
e7fb1857b0 prepare for release 2022-07-16 13:48:56 +02:00
Sheldan
b5498ab79a [SIS-7] only showing confirmed meetups in meetup list command, fixing #7 2022-07-04 23:04:39 +02:00
Sheldan
1f5aebef1c [SIS-xxx] fixing cancelling a meetup trying to insert a wrong decision 2022-06-24 20:52:39 +02:00
Sheldan
b0bb0e02d1 [SIS-4] adding organizer to meetup message, fixes #4 2022-06-24 18:42:03 +02:00
Sheldan
9bc4ec0253 [SIS-3] adding cleanup of cancelled meetups to job 2022-06-20 20:12:41 +02:00
Sheldan
533c9e3a63 [SIS-3] adding separator for the meetup members
adding member counter to message display
only notify member with yes/maybe decision of meetup cancellation
2022-06-20 19:15:30 +02:00
Sheldan
6ae631c78d [maven-release-plugin] prepare for next development iteration 2022-06-20 00:46:14 +02:00
Sheldan
622bbefc54 [maven-release-plugin] prepare release sissi-1.1.0.RC1 2022-06-20 00:46:10 +02:00
Sheldan
f6956958bb [SIS-3] adding meetup implementation 2022-06-20 00:44:04 +02:00
Sheldan
853e1e4946 [SIS-xxx] adding issue templates 2022-06-16 19:56:26 +02:00
Sheldan
5fbc012afa [RAB-2] adding slash commands to quote comands 2022-06-16 19:30:54 +02:00
Sheldan
ce2c35f4bd [RAB-2] updating to new abstracto version 2022-06-16 17:55:13 +02:00
Sheldan
90d9172cf3 [RAB-1] rename project to Sissi 2022-06-07 00:54:53 +02:00
Sheldan
d06fb7ff30 [RAB-1] rename project to Sissi 2022-06-07 00:54:43 +02:00
Sheldan
a2e304fdd8 [RAB-xxx] fixing host names for monitoring 2022-05-16 18:39:36 +02:00
Sheldan
de8cba8d08 [maven-release-plugin] prepare for next development iteration 2022-05-15 18:08:30 +02:00
Sheldan
a5ead2b6d7 [maven-release-plugin] prepare release r-austria-bot-1.0.2 2022-05-15 18:08:27 +02:00
Sheldan
b4fbe368d7 [RAB-xxx] preparing for release 2022-05-15 18:05:18 +02:00
Sheldan
199ca18cac [RAB-2] adding code to retrieve legacy quotes
adding migration script for legacy quotes
changing docker-compose file to use different container names
2022-05-15 16:13:48 +02:00
Sheldan
da6c4a5b23 [maven-release-plugin] prepare for next development iteration 2022-02-12 23:33:29 +01:00
Sheldan
7f4d02c9e6 [maven-release-plugin] prepare release r-austria-bot-1.0.1 2022-02-12 23:33:26 +01:00
Sheldan
cdfc06a63d [RAB-1] fixing prometheus configuration 2022-02-12 16:26:07 +01:00
Sheldan
ce2050f790 [maven-release-plugin] prepare for next development iteration 2022-02-12 13:15:07 +01:00
278 changed files with 10882 additions and 112 deletions

42
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@@ -0,0 +1,42 @@
name: Bug Report
description: Found a bug that needs fixing?
body:
- type: checkboxes
attributes:
label: General Troubleshooting
description: You confirm to have made the following checks first.
options:
- label: I have checked for similar issues on the Issue-tracker.
required: true
- label: I have updated to the latest version
required: true
- label: I have checked the branches or the maintainers' PRs for upcoming bug fixes.
required: true
- type: textarea
attributes:
label: "Description"
description: "General information about the bug"
placeholder: "..."
validations:
required: true
- type: textarea
attributes:
label: "Steps to reproduce"
description: "What happened when the bug occurred?"
placeholder: "1. ..."
validations:
required: true
- type: textarea
attributes:
label: "Expected behaviour"
description: "What should happen?"
placeholder: "It should..."
validations:
required: true
- type: textarea
attributes:
label: "Actual behaviour"
description: "What did happen instead?"
placeholder: "It actually ..."
validations:
required: true

36
.github/ISSUE_TEMPLATE/feature.yml vendored Normal file
View File

@@ -0,0 +1,36 @@
name: Feature request
description: Want some functionality added?
body:
- type: checkboxes
attributes:
label: General Information
description: You confirm to have made the following checks first.
options:
- label: I have checked for similar issues on the Issue-tracker.
required: true
- label: I have updated to the latest version
required: true
- label: I have checked the branches or the maintainers' PRs for upcoming features fixes.
required: true
- type: textarea
attributes:
label: "Description"
description: "General description of the feature"
placeholder: "..."
validations:
required: true
- type: textarea
attributes:
label: "Feature worth and general use"
description: "Why should this exist?"
placeholder: "..."
validations:
required: true
- type: textarea
attributes:
label: "Suggestions for implementation"
description: "Any ideas about what the feature should behave/look like? Commands?"
placeholder: "It should..."
validations:
required: false

View File

@@ -32,7 +32,7 @@ jobs:
working-directory: ./deployment/image-packaging/src/main/docker
run: docker-compose build && docker-compose push
env:
REGISTRY_PREFIX: docker.pkg.github.com/sheldan/r-austria-bot/
REGISTRY_PREFIX: docker.pkg.github.com/sheldan/sissi/
VERSION: ${{ env.version }}
ABSTRACTO_VERSION: 1.3.11
ABSTRACTO_VERSION: 1.4.3
ABSTRACTO_REGISTRY_PREFIX: docker.pkg.github.com/sheldan/abstracto/

View File

@@ -1,9 +1,9 @@
<?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.raustria.bot.application</groupId>
<groupId>dev.sheldan.sissi.application</groupId>
<artifactId>application</artifactId>
<version>1.0.0</version>
<version>1.3.2</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>executable</artifactId>
@@ -70,11 +70,78 @@
<artifactId>link-embed-impl</artifactId>
</dependency>
<dependency>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>webservices-impl</artifactId>
</dependency>
<dependency>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>utility-impl</artifactId>
</dependency>
<dependency>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>remind-impl</artifactId>
</dependency>
<dependency>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>suggestion-impl</artifactId>
</dependency>
<dependency>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>starboard-impl</artifactId>
</dependency>
<dependency>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>moderation-impl</artifactId>
</dependency>
<dependency>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>entertainment-impl</artifactId>
</dependency>
<dependency>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>assignable-roles-impl</artifactId>
</dependency>
<dependency>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>custom-command-impl</artifactId>
</dependency>
<dependency>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>modmail-impl</artifactId>
</dependency>
<!-- sissi modules -->
<dependency>
<groupId>dev.sheldan.sissi.application.module</groupId>
<artifactId>quotes</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>dev.sheldan.sissi.application.module</groupId>
<artifactId>meetup</artifactId>
<version>${project.version}</version>
</dependency>
<!-- sissi customizations -->
<dependency>
<groupId>dev.sheldan.sissi.application.module.custom</groupId>
<artifactId>moderation-custom</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

View File

@@ -1,4 +1,4 @@
package dev.sheldan.raustria.bot.executable;
package dev.sheldan.sissi.executable;
import dev.sheldan.abstracto.core.service.Startup;
import org.springframework.beans.factory.annotation.Autowired;
@@ -15,10 +15,10 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
@SpringBootApplication
@EnableAutoConfiguration(exclude = { FreeMarkerAutoConfiguration.class })
@ComponentScan(basePackages = {"dev.sheldan.abstracto", "dev.sheldan.raustria.bot"})
@ComponentScan(basePackages = {"dev.sheldan.abstracto", "dev.sheldan.sissi"})
@EnableCaching
@EnableJpaRepositories(basePackages = {"dev.sheldan.abstracto", "dev.sheldan.raustria.bot"})
@EntityScan(basePackages = {"dev.sheldan.abstracto", "dev.sheldan.raustria.bot"})
@EnableJpaRepositories(basePackages = {"dev.sheldan.abstracto", "dev.sheldan.sissi"})
@EntityScan(basePackages = {"dev.sheldan.abstracto", "dev.sheldan.sissi"})
@EnableTransactionManagement
public class Application implements CommandLineRunner {

View File

@@ -1,6 +1,6 @@
spring.datasource.url=jdbc:postgresql://localhost:5432/abstracto
spring.datasource.username= abstracto
spring.datasource.password= abstracto
spring.datasource.username=abstracto
spring.datasource.password=abstracto
spring.jpa.properties.hibernate.default_schema=abstracto
spring.quartz.jdbc.initialize-schema=never
@@ -13,9 +13,9 @@ log4j.logger.org.hibernate.SQL=trace
log4j.logger.org.hibernate.type.descriptor.sql=trace
log4j.logger.org.hibernate.type=trace
management.metrics.tags.application=r-austria-bot
management.metrics.tags.application=sissi
spring.security.user.name=abstracto
spring.security.user.password=password
spring.security.user.roles=USER
spring.application.name=RAustriaBot
spring.application.name=Sissi

View File

@@ -3,9 +3,9 @@ spring.datasource.username= ${DB_USER}
spring.datasource.password= ${DB_PASS}
spring.jpa.hibernate.default_schema=${DB_NAME}
spring.quartz.jdbc.initialize-schema=never
management.metrics.tags.application=r-austria-bot
management.metrics.tags.application=Sissi
spring.security.user.name= ${REST_USER_NAME}
spring.security.user.password= ${REST_PASSWORD}
spring.security.user.roles=USER
spring.application.name=RAustriaBot
spring.application.name=Sissi

View File

@@ -1,16 +1,16 @@
<?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.raustria.bot</groupId>
<artifactId>r-austria-bot</artifactId>
<version>1.0.0</version>
<groupId>dev.sheldan.sissi</groupId>
<artifactId>sissi</artifactId>
<version>1.3.2</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>pom</packaging>
<groupId>dev.sheldan.raustria.bot.application</groupId>
<groupId>dev.sheldan.sissi.application</groupId>
<artifactId>application</artifactId>
<dependencyManagement>
@@ -26,6 +26,8 @@
</dependencyManagement>
<modules>
<module>sissi-modules</module>
<module>sissi-customizations</module>
<module>executable</module>
</modules>

View File

@@ -0,0 +1,42 @@
<?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.sissi.application.module.custom</groupId>
<artifactId>sissi-customizations</artifactId>
<version>1.3.2</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>moderation-custom</artifactId>
<dependencies>
<dependency>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>moderation-int</artifactId>
<version>${abstracto.version}</version>
</dependency>
</dependencies>
<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>
</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,95 @@
package dev.sheldan.sissi.module.custom.moderation.commands;
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.interaction.InteractionService;
import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.moderation.config.ModerationModuleDefinition;
import dev.sheldan.sissi.module.custom.moderation.config.ModerationCustomFeatureDefinition;
import dev.sheldan.sissi.module.custom.moderation.config.ModerationCustomSlashCommandNames;
import dev.sheldan.sissi.module.custom.moderation.service.ModModeServiceBean;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Component
public class ModMode extends AbstractConditionableCommand {
public static final String NEW_STATE_PARAMETER = "newState";
public static final String MOD_MODE_COMMAND = "modMode";
public static final String MOD_MODE_RESPONSE = "modMode_response";
@Autowired
private ModModeServiceBean modModeServiceBean;
@Autowired
private SlashCommandParameterService slashCommandParameterService;
@Autowired
private InteractionService interactionService;
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
Boolean newState = (Boolean) commandContext.getParameters().getParameters().get(0);
return modModeServiceBean.setModModeTo(commandContext.getGuild(), newState)
.thenApply(unused -> CommandResult.fromSuccess());
}
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
Boolean newState = slashCommandParameterService.getCommandOption(NEW_STATE_PARAMETER, event, Boolean.class);
return modModeServiceBean.setModModeTo(event.getGuild(), newState)
.thenApply(unused -> interactionService.replyEmbed(MOD_MODE_RESPONSE, event))
.thenApply(interactionHookCompletableFuture -> CommandResult.fromSuccess());
}
@Override
public CommandConfiguration getConfiguration() {
Parameter memberParameter = Parameter
.builder()
.templated(true)
.name(NEW_STATE_PARAMETER)
.type(Boolean.class)
.build();
List<Parameter> parameters = Collections.singletonList(memberParameter);
HelpInfo helpInfo = HelpInfo
.builder()
.templated(true)
.build();
SlashCommandConfig slashCommandConfig = SlashCommandConfig
.builder()
.enabled(true)
.rootCommandName(ModerationCustomSlashCommandNames.MODERATION)
.commandName(MOD_MODE_COMMAND)
.build();
return CommandConfiguration.builder()
.name(MOD_MODE_COMMAND)
.async(true)
.slashCommandConfig(slashCommandConfig)
.module(ModerationModuleDefinition.MODERATION)
.parameters(parameters)
.help(helpInfo)
.templated(true)
.supportsEmbedException(true)
.causesReaction(true)
.build();
}
@Override
public FeatureDefinition getFeature() {
return ModerationCustomFeatureDefinition.MODERATION_CUSTOM;
}
}

View File

@@ -0,0 +1,36 @@
package dev.sheldan.sissi.module.custom.moderation.config;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.moderation.config.feature.ReportReactionFeatureConfig;
import dev.sheldan.sissi.module.custom.moderation.service.ModModeServiceBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
import static dev.sheldan.sissi.module.custom.moderation.listener.ReactionReportReactionListener.REACTION_REPORT_REACTION_AMOUNT_CONFIG_KEY;
@Component
public class ModerationCustomFeature implements FeatureConfig {
@Autowired
private ReportReactionFeatureConfig reportReactionFeatureConfig;
@Override
public FeatureDefinition getFeature() {
return ModerationCustomFeatureDefinition.MODERATION_CUSTOM;
}
@Override
public List<FeatureConfig> getRequiredFeatures() {
return Arrays.asList(reportReactionFeatureConfig);
}
@Override
public List<String> getRequiredSystemConfigKeys() {
return Arrays.asList(REACTION_REPORT_REACTION_AMOUNT_CONFIG_KEY, ModModeServiceBean.MODMODE_ROLE_CONFIG_KEY,
ModModeServiceBean.MODMODE_CHANGED_ROLE_COLOR_CONFIG_KEY);
}
}

View File

@@ -0,0 +1,15 @@
package dev.sheldan.sissi.module.custom.moderation.config;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import lombok.Getter;
@Getter
public enum ModerationCustomFeatureDefinition implements FeatureDefinition {
MODERATION_CUSTOM("moderationCustom");
private String key;
ModerationCustomFeatureDefinition(String key) {
this.key = key;
}
}

View File

@@ -0,0 +1,10 @@
package dev.sheldan.sissi.module.custom.moderation.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
@Configuration
@PropertySource("classpath:moderation-custom.properties")
public class ModerationCustomProperties {
}

View File

@@ -0,0 +1,5 @@
package dev.sheldan.sissi.module.custom.moderation.config;
public class ModerationCustomSlashCommandNames {
public static final String MODERATION = "moderation";
}

View File

@@ -0,0 +1,15 @@
package dev.sheldan.sissi.module.custom.moderation.exception;
import dev.sheldan.abstracto.core.exception.AbstractoTemplatableException;
public class ModRoleNotFoundException extends AbstractoTemplatableException {
@Override
public String getTemplateName() {
return "mod_role_not_found_exception";
}
@Override
public Object getTemplateModel() {
return new Object();
}
}

View File

@@ -0,0 +1,58 @@
package dev.sheldan.sissi.module.custom.moderation.listener;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
import dev.sheldan.abstracto.core.service.ConfigService;
import dev.sheldan.abstracto.core.service.ReactionService;
import dev.sheldan.abstracto.moderation.listener.ReportMessageCreatedListener;
import dev.sheldan.abstracto.moderation.model.listener.ReportMessageCreatedModel;
import dev.sheldan.sissi.module.custom.moderation.config.ModerationCustomFeatureDefinition;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class ReactionReportReactionListener implements ReportMessageCreatedListener {
public static final String REACTION_REPORT_REACTION_AMOUNT_CONFIG_KEY = "reportReactionAmount";
public static final String REACTION_REPORT_EMOTE_PREFIX = "reactionReport";
@Autowired
private ConfigService configService;
@Autowired
private ReactionService reactionService;
@Override
public DefaultListenerResult execute(ReportMessageCreatedModel model) {
if(model.getReportMessage() == null) {
return DefaultListenerResult.IGNORED;
}
Long reactionAmount = configService.getLongValueOrConfigDefault(REACTION_REPORT_REACTION_AMOUNT_CONFIG_KEY, model.getServerId());
for (int i = 0; i < reactionAmount; i++) {
String emoteKey = buildReactionEmoteName(i + 1);
reactionService.addReactionToMessageAsync(emoteKey,
model.getServerId(), model.getReportMessage().getChannelId(), model.getReportMessage().getMessageId())
.thenAccept(unused -> log.info("Added reaction emote {} on report message {} in channel {} in server {}",
emoteKey, model.getReportMessage().getMessageId(), model.getReportMessage().getChannelId(), model.getServerId()))
.exceptionally(throwable -> {
log.info("Failed to add reaction emote {} on report message {} in channel {} in server {}",
emoteKey, model.getReportMessage().getMessageId(), model.getReportMessage().getChannelId(), model.getServerId());
return null;
});
}
return DefaultListenerResult.PROCESSED;
}
@Override
public FeatureDefinition getFeature() {
return ModerationCustomFeatureDefinition.MODERATION_CUSTOM;
}
private String buildReactionEmoteName(Integer position) {
return REACTION_REPORT_EMOTE_PREFIX + position;
}
}

View File

@@ -0,0 +1,55 @@
package dev.sheldan.sissi.module.custom.moderation.service;
import dev.sheldan.abstracto.core.service.ConfigService;
import dev.sheldan.sissi.module.custom.moderation.exception.ModRoleNotFoundException;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Role;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.awt.*;
import java.util.concurrent.CompletableFuture;
@Component
@Slf4j
public class ModModeServiceBean {
public static final String MODMODE_ROLE_CONFIG_KEY = "modModeRoleId";
public static final String MODMODE_CHANGED_ROLE_COLOR_CONFIG_KEY = "modModeNewRoleColor";
@Autowired
private ConfigService configService;
public CompletableFuture<Void> setModModeTo(Guild guild, Boolean newState) {
if(Boolean.TRUE.equals(newState)) {
return enableModMode(guild);
} else {
return disableModMoe(guild);
}
}
private CompletableFuture<Void> enableModMode(Guild guild) {
Color colorToSet = getColorFromConfig(MODMODE_CHANGED_ROLE_COLOR_CONFIG_KEY, guild);
return setModRoleTo(guild, colorToSet);
}
private CompletableFuture<Void> disableModMoe(Guild guild) {
return setModRoleTo(guild, null);
}
private Color getColorFromConfig(String key, Guild guild) {
String colorString = configService.getStringValueOrConfigDefault(key, guild.getIdLong());
String[] parts = colorString.split(",");
return new Color(Integer.parseInt(parts[0]), Integer.parseInt(parts[1]), Integer.parseInt(parts[2]));
}
private CompletableFuture<Void> setModRoleTo(Guild guild, Color color) {
Long roleId = configService.getLongValue(MODMODE_ROLE_CONFIG_KEY, guild.getIdLong());
Role modRole = guild.getRoleById(roleId);
if(modRole != null) {
return modRole.getManager().setColor(color).submit();
} else {
throw new ModRoleNotFoundException();
}
}
}

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="seedData/data.xml" relativeToChangelogFile="true"/>
</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="default_emote.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="moderation_custom_reaction_message_default_emote-insert">
<insert tableName="default_emote">
<column name="emote_key" value="reactionReport1"/>
<column name="name" value="♂️"/>
</insert>
<insert tableName="default_emote">
<column name="emote_key" value="reactionReport2"/>
<column name="name" value="📣"/>
</insert>
<insert tableName="default_emote">
<column name="emote_key" value="reactionReport3"/>
<column name="name" value="🤫"/>
</insert>
<insert tableName="default_emote">
<column name="emote_key" value="reactionReport4"/>
<column name="name" value="🔨"/>
</insert>
<insert tableName="default_emote">
<column name="emote_key" value="reactionReport5"/>
<column name="name" value="⚠️"/>
</insert>
</changeSet>
</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="moderation_custom_feature-insertion">
<insert tableName="feature">
<column name="key" value="moderationCustom"/>
</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="seedData/data.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -0,0 +1,18 @@
<?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="moderationCustomFeature" value="(SELECT id FROM feature WHERE key = 'moderationCustom')"/>
<property name="moderationModule" value="(SELECT id FROM module WHERE name = 'moderation')"/>
<changeSet author="Sheldan" id="moderationCustom_modmode-commands">
<insert tableName="command">
<column name="name" value="modMode"/>
<column name="module_id" valueComputed="${moderationModule}"/>
<column name="feature_id" valueComputed="${moderationCustomFeature}"/>
</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="command.xml" relativeToChangelogFile="true"/>
</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="1.1.0/collection.xml" relativeToChangelogFile="true"/>
<include file="1.2.1/collection.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -0,0 +1,11 @@
abstracto.featureFlags.moderationCustom.featureName=moderationCustom
abstracto.featureFlags.moderationCustom.enabled=false
abstracto.systemConfigs.reportReactionAmount.name=reportReactionAmount
abstracto.systemConfigs.reportReactionAmount.longValue=5
abstracto.systemConfigs.modModeRoleId.name=modModeRoleId
abstracto.systemConfigs.modModeRoleId.longValue=0
abstracto.systemConfigs.modModeNewRoleColor.name=modModeNewRoleColor
abstracto.systemConfigs.modModeNewRoleColor.stringValue=0,0,0

View File

@@ -0,0 +1,19 @@
<?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>application</artifactId>
<groupId>dev.sheldan.sissi.application</groupId>
<version>1.3.2</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>dev.sheldan.sissi.application.module.custom</groupId>
<artifactId>sissi-customizations</artifactId>
<packaging>pom</packaging>
<modules>
<module>moderation-custom</module>
</modules>
</project>

View File

@@ -0,0 +1,43 @@
<?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.sissi.application</groupId>
<artifactId>sissi-modules</artifactId>
<version>1.3.2</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>dev.sheldan.sissi.application.module</groupId>
<artifactId>meetup</artifactId>
<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.scheduling</groupId>
<artifactId>scheduling-int</artifactId>
<version>${abstracto.version}</version>
</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,111 @@
package dev.sheldan.sissi.module.meetup.commands;
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.interaction.InteractionService;
import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.sissi.module.meetup.config.MeetupFeatureDefinition;
import dev.sheldan.sissi.module.meetup.config.MeetupSlashCommandNames;
import dev.sheldan.sissi.module.meetup.exception.NotMeetupOrganizerException;
import dev.sheldan.sissi.module.meetup.model.database.Meetup;
import dev.sheldan.sissi.module.meetup.service.MeetupServiceBean;
import dev.sheldan.sissi.module.meetup.service.management.MeetupManagementServiceBean;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Component
public class CancelMeetup extends AbstractConditionableCommand {
private static final String MEETUP_ID_PARAMETER = "meetupId";
private static final String CANCEL_MEETUP_COMMAND = "cancelMeetup";
private static final String CANCEL_MEETUP_RESPONSE = "cancelMeetup_response";
@Autowired
private MeetupServiceBean meetupServiceBean;
@Autowired
private SlashCommandParameterService slashCommandParameterService;
@Autowired
private InteractionService interactionService;
@Autowired
private MeetupManagementServiceBean meetupManagementServiceBean;
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
Long meetupId = (Long) commandContext.getParameters().getParameters().get(0);
Meetup meetup = meetupManagementServiceBean.getMeetup(meetupId, commandContext.getGuild().getIdLong());
if(!meetup.getOrganizer().getUserReference().getId().equals(commandContext.getAuthor().getIdLong())) {
throw new NotMeetupOrganizerException();
}
return meetupServiceBean.cancelMeetup(meetup)
.thenApply(interactionHook -> CommandResult.fromSuccess());
}
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
event.deferReply().queue();
Long meetupId = slashCommandParameterService.getCommandOption(MEETUP_ID_PARAMETER, event, Integer.class).longValue();
Meetup meetup = meetupManagementServiceBean.getMeetup(meetupId, event.getGuild().getIdLong());
if(!meetup.getOrganizer().getUserReference().getId().equals(event.getMember().getIdLong())) {
throw new NotMeetupOrganizerException();
}
return meetupServiceBean.cancelMeetup(meetup)
.thenCompose(unused -> FutureUtils.toSingleFutureGeneric(interactionService.sendMessageToInteraction(CANCEL_MEETUP_RESPONSE, new Object(), event.getHook())))
.thenApply(interactionHook -> CommandResult.fromSuccess());
}
@Override
public CommandConfiguration getConfiguration() {
Parameter meetupIdParameter = Parameter
.builder()
.templated(true)
.name(MEETUP_ID_PARAMETER)
.type(Long.class)
.build();
List<Parameter> parameters = Arrays.asList(meetupIdParameter);
HelpInfo helpInfo = HelpInfo
.builder()
.templated(true)
.build();
SlashCommandConfig slashCommandConfig = SlashCommandConfig
.builder()
.enabled(true)
.rootCommandName(MeetupSlashCommandNames.MEETUP)
.commandName("cancel")
.build();
return CommandConfiguration.builder()
.name(CANCEL_MEETUP_COMMAND)
.module(UtilityModuleDefinition.UTILITY)
.templated(true)
.slashCommandConfig(slashCommandConfig)
.async(true)
.supportsEmbedException(true)
.causesReaction(true)
.parameters(parameters)
.help(helpInfo)
.build();
}
@Override
public FeatureDefinition getFeature() {
return MeetupFeatureDefinition.MEETUP;
}
}

View File

@@ -0,0 +1,173 @@
package dev.sheldan.sissi.module.meetup.commands;
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.interaction.ComponentService;
import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.sissi.module.meetup.config.MeetupFeatureDefinition;
import dev.sheldan.sissi.module.meetup.config.MeetupSlashCommandNames;
import dev.sheldan.sissi.module.meetup.exception.MeetupPastTimeException;
import dev.sheldan.sissi.module.meetup.exception.NotMeetupOrganizerException;
import dev.sheldan.sissi.module.meetup.model.command.MeetupChangeTimeConfirmationModel;
import dev.sheldan.sissi.module.meetup.model.database.Meetup;
import dev.sheldan.sissi.module.meetup.service.MeetupServiceBean;
import dev.sheldan.sissi.module.meetup.service.management.MeetupManagementServiceBean;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.time.Instant;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Component
public class ChangeMeetupTime extends AbstractConditionableCommand {
private static final String CHANGE_MEETUP_TIME_COMMAND = "changeMeetupTime";
private static final String MEETUP_ID_PARAMETER = "meetupId";
private static final String MEETUP_NEW_TIMESTAMP_PARAMETER = "newTimeStamp";
private static final String CHANGE_MEETUP_TIME_CONFIRMATION = "changeMeetupTime_confirmation";
@Autowired
private SlashCommandParameterService slashCommandParameterService;
@Autowired
private MeetupManagementServiceBean meetupManagementServiceBean;
@Autowired
private ComponentService componentService;
@Autowired
private InteractionService interactionService;
@Autowired
private MeetupServiceBean meetupServiceBean;
@Autowired
private ChannelService channelService;
@Autowired
private TemplateService templateService;
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters();
Long meetupId = (Long) parameters.get(0);
Meetup meetup = meetupManagementServiceBean.getMeetup(meetupId, commandContext.getGuild().getIdLong());
if(!meetup.getOrganizer().getUserReference().getId().equals(commandContext.getAuthor().getIdLong())) {
throw new NotMeetupOrganizerException();
}
Long newTimestamp = (Long) parameters.get(1);
Instant newMeetupTime = Instant.ofEpochSecond(newTimestamp);
if(newMeetupTime.isBefore(Instant.now())) {
throw new MeetupPastTimeException();
}
String confirmationId = componentService.generateComponentId();
String cancelId = componentService.generateComponentId();
MeetupChangeTimeConfirmationModel model = MeetupChangeTimeConfirmationModel
.builder()
.meetupTime(newMeetupTime)
.topic(meetup.getTopic())
.description(meetup.getDescription())
.userId(commandContext.getAuthor().getIdLong())
.guildId(commandContext.getGuild().getIdLong())
.meetupId(meetupId)
.confirmationId(confirmationId)
.cancelId(cancelId)
.build();
MessageToSend messageToSend = templateService.renderEmbedTemplate(CHANGE_MEETUP_TIME_CONFIRMATION, model, commandContext.getGuild().getIdLong());
return FutureUtils.toSingleFutureGeneric(channelService.sendMessageToSendToChannel(messageToSend, commandContext.getChannel()))
.thenAccept(unused -> meetupServiceBean.storeMeetupChangeTimeConfirmation(model))
.thenApply(unused -> CommandResult.fromSuccess());
}
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
Long meetupId = slashCommandParameterService.getCommandOption(MEETUP_ID_PARAMETER, event, Integer.class).longValue();
Meetup meetup = meetupManagementServiceBean.getMeetup(meetupId, event.getGuild().getIdLong());
if(!meetup.getOrganizer().getUserReference().getId().equals(event.getMember().getIdLong())) {
throw new NotMeetupOrganizerException();
}
Integer time = slashCommandParameterService.getCommandOption(MEETUP_NEW_TIMESTAMP_PARAMETER, event, Long.class, Integer.class);
Instant meetupTime = Instant.ofEpochSecond(time);
if(meetupTime.isBefore(Instant.now())) {
throw new MeetupPastTimeException();
}
String confirmationId = componentService.generateComponentId();
String cancelId = componentService.generateComponentId();
MeetupChangeTimeConfirmationModel model = MeetupChangeTimeConfirmationModel
.builder()
.meetupTime(meetupTime)
.topic(meetup.getTopic())
.description(meetup.getDescription())
.userId(event.getMember().getIdLong())
.guildId(event.getGuild().getIdLong())
.meetupId(meetupId)
.confirmationId(confirmationId)
.cancelId(cancelId)
.build();
return interactionService.replyEmbed(CHANGE_MEETUP_TIME_CONFIRMATION, model, event)
.thenAccept(interactionHook -> meetupServiceBean.storeMeetupChangeTimeConfirmation(model))
.thenApply(interactionHook -> CommandResult.fromSuccess());
}
@Override
public CommandConfiguration getConfiguration() {
Parameter meetupIdParameter = Parameter
.builder()
.templated(true)
.name(MEETUP_ID_PARAMETER)
.type(Long.class)
.build();
Parameter newTimeStampParameter = Parameter
.builder()
.templated(true)
.name(MEETUP_NEW_TIMESTAMP_PARAMETER)
.type(Long.class)
.build();
List<Parameter> parameters = Arrays.asList(meetupIdParameter, newTimeStampParameter);
HelpInfo helpInfo = HelpInfo
.builder()
.templated(true)
.build();
SlashCommandConfig slashCommandConfig = SlashCommandConfig
.builder()
.enabled(true)
.rootCommandName(MeetupSlashCommandNames.MEETUP)
.commandName("changeTime")
.build();
return CommandConfiguration.builder()
.name(CHANGE_MEETUP_TIME_COMMAND)
.module(UtilityModuleDefinition.UTILITY)
.templated(true)
.async(true)
.slashCommandConfig(slashCommandConfig)
.supportsEmbedException(true)
.causesReaction(true)
.parameters(parameters)
.help(helpInfo)
.build();
}
@Override
public FeatureDefinition getFeature() {
return MeetupFeatureDefinition.MEETUP;
}
}

View File

@@ -0,0 +1,199 @@
package dev.sheldan.sissi.module.meetup.commands;
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.interaction.ComponentService;
import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.models.template.display.MemberDisplay;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.management.ChannelManagementService;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.sissi.module.meetup.config.MeetupFeatureDefinition;
import dev.sheldan.sissi.module.meetup.config.MeetupSlashCommandNames;
import dev.sheldan.sissi.module.meetup.model.command.MeetupConfirmationModel;
import dev.sheldan.sissi.module.meetup.model.database.Meetup;
import dev.sheldan.sissi.module.meetup.service.MeetupServiceBean;
import dev.sheldan.sissi.module.meetup.service.management.MeetupManagementServiceBean;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.time.Instant;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Component
public class CreateMeetup extends AbstractConditionableCommand {
@Autowired
private TemplateService templateService;
@Autowired
private ChannelService channelService;
@Autowired
private ComponentService componentService;
@Autowired
private MeetupServiceBean meetupServiceBean;
@Autowired
private MeetupManagementServiceBean meetupManagementServiceBean;
@Autowired
private UserInServerManagementService userInServerManagementService;
@Autowired
private ChannelManagementService channelManagementService;
@Autowired
private InteractionService interactionService;
@Autowired
private SlashCommandParameterService slashCommandParameterService;
private static final String CREATE_MEETUP_COMMAND = "createMeetup";
private static final String MEETUP_TIME_PARAMETER = "meetupTime";
private static final String TOPIC_PARAMETER = "topic";
private static final String DESCRIPTION_PARAMETER = "description";
private static final String CONFIRMATION_TEMPLATE = "createMeetup_confirmation";
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters();
Instant meetupTime = (Instant) parameters.get(0);
String meetupTopic = (String) parameters.get(1);
String description;
if(parameters.size() > 2) {
description = (String) parameters.get(2);
} else {
description = "";
}
AUserInAServer organizer = userInServerManagementService.loadOrCreateUser(commandContext.getAuthor());
AChannel meetupChannel = channelManagementService.loadChannel(commandContext.getChannel().getIdLong());
Meetup meetup = meetupManagementServiceBean.createMeetup(meetupTime, meetupTopic, description, organizer, meetupChannel);
String confirmationId = componentService.generateComponentId();
String cancelId = componentService.generateComponentId();
MeetupConfirmationModel model = MeetupConfirmationModel
.builder()
.meetupTime(meetupTime)
.guildId(commandContext.getGuild().getIdLong())
.description(description)
.topic(meetupTopic)
.confirmationId(confirmationId)
.cancelId(cancelId)
.meetupId(meetup.getId().getId())
.organizer(MemberDisplay.fromMember(commandContext.getAuthor()))
.build();
MessageToSend messageToSend = templateService.renderEmbedTemplate(CONFIRMATION_TEMPLATE, model, commandContext.getGuild().getIdLong());
List<CompletableFuture<Message>> messageFutures = channelService.sendMessageToSendToChannel(messageToSend, commandContext.getChannel());
return FutureUtils.toSingleFutureGeneric(messageFutures)
.thenAccept(unused -> meetupServiceBean.storeMeetupConfirmation(model))
.thenApply(unused -> CommandResult.fromIgnored());
}
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
Integer time = slashCommandParameterService.getCommandOption(MEETUP_TIME_PARAMETER, event, Long.class, Integer.class);
String topic = slashCommandParameterService.getCommandOption(TOPIC_PARAMETER, event, String.class);
String description;
if(slashCommandParameterService.hasCommandOption(DESCRIPTION_PARAMETER, event)) {
description = slashCommandParameterService.getCommandOption(DESCRIPTION_PARAMETER, event, String.class);
} else {
description = "";
}
Instant meetupTime = Instant.ofEpochSecond(time);
AUserInAServer organizer = userInServerManagementService.loadOrCreateUser(event.getMember());
AChannel meetupChannel = channelManagementService.loadChannel(event.getChannel().getIdLong());
Meetup meetup = meetupManagementServiceBean.createMeetup(meetupTime, topic, description, organizer, meetupChannel);
String confirmationId = componentService.generateComponentId();
String cancelId = componentService.generateComponentId();
MeetupConfirmationModel model = MeetupConfirmationModel
.builder()
.meetupTime(meetupTime)
.guildId(event.getGuild().getIdLong())
.description(description)
.topic(topic)
.confirmationId(confirmationId)
.cancelId(cancelId)
.meetupId(meetup.getId().getId())
.organizer(MemberDisplay.fromMember(event.getMember()))
.build();
MessageToSend messageToSend = templateService.renderEmbedTemplate(CONFIRMATION_TEMPLATE, model, event.getGuild().getIdLong());
return interactionService.replyMessageToSend(messageToSend, event)
.thenAccept(interactionHook -> meetupServiceBean.storeMeetupConfirmation(model))
.thenApply(unused -> CommandResult.fromSuccess());
}
@Override
public CommandConfiguration getConfiguration() {
Parameter timeParameter = Parameter
.builder()
.templated(true)
.name(MEETUP_TIME_PARAMETER)
.type(Instant.class)
.build();
Parameter topicParameter = Parameter
.builder()
.templated(true)
.name(TOPIC_PARAMETER)
.type(String.class)
.build();
Parameter descriptionParameter = Parameter
.builder()
.templated(true)
.name(DESCRIPTION_PARAMETER)
.remainder(true)
.optional(true)
.type(String.class)
.build();
List<Parameter> parameters = Arrays.asList(timeParameter, topicParameter, descriptionParameter);
HelpInfo helpInfo = HelpInfo
.builder()
.templated(true)
.build();
SlashCommandConfig slashCommandConfig = SlashCommandConfig
.builder()
.enabled(true)
.rootCommandName(MeetupSlashCommandNames.MEETUP)
.commandName("create")
.build();
return CommandConfiguration.builder()
.name(CREATE_MEETUP_COMMAND)
.module(UtilityModuleDefinition.UTILITY)
.templated(true)
.slashCommandConfig(slashCommandConfig)
.async(true)
.supportsEmbedException(true)
.causesReaction(false)
.parameters(parameters)
.help(helpInfo)
.build();
}
@Override
public FeatureDefinition getFeature() {
return MeetupFeatureDefinition.MEETUP;
}
}

View File

@@ -0,0 +1,110 @@
package dev.sheldan.sissi.module.meetup.commands;
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.interaction.InteractionService;
import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.sissi.module.meetup.config.MeetupFeatureDefinition;
import dev.sheldan.sissi.module.meetup.config.MeetupSlashCommandNames;
import dev.sheldan.sissi.module.meetup.model.command.MeetupListItemModel;
import dev.sheldan.sissi.module.meetup.model.command.MeetupListModel;
import dev.sheldan.sissi.module.meetup.model.database.Meetup;
import dev.sheldan.sissi.module.meetup.service.management.MeetupManagementServiceBean;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
@Component
public class ListMeetups extends AbstractConditionableCommand {
private static final String LIST_MEETUPS_RESPONSE_TEMPLATE = "listMeetups_response";
@Autowired
private MeetupManagementServiceBean meetupManagementServiceBean;
@Autowired
private TemplateService templateService;
@Autowired
private ChannelService channelService;
@Autowired
private InteractionService interactionService;
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
MessageToSend messageToSend = getMessageToSend(commandContext.getGuild().getIdLong());
return FutureUtils.toSingleFutureGeneric(channelService.sendMessageToSendToChannel(messageToSend, commandContext.getChannel()))
.thenApply(unused -> CommandResult.fromIgnored());
}
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
return interactionService.replyMessageToSend(getMessageToSend(event.getGuild().getIdLong()), event)
.thenApply(interactionHook -> CommandResult.fromSuccess());
}
private MessageToSend getMessageToSend(Long serverId) {
List<Meetup> meetups = meetupManagementServiceBean.getIncomingMeetups();
List<MeetupListItemModel> listItems = meetups
.stream()
.map(MeetupListItemModel::fromMeetup)
.sorted(Comparator.comparing(MeetupListItemModel::getMeetupTime))
.collect(Collectors.toList());
MeetupListModel model = MeetupListModel
.builder()
.meetups(listItems)
.build();
return templateService.renderEmbedTemplate(LIST_MEETUPS_RESPONSE_TEMPLATE, model, serverId);
}
@Override
public CommandConfiguration getConfiguration() {
List<Parameter> parameters = new ArrayList<>();
HelpInfo helpInfo = HelpInfo
.builder()
.templated(true)
.build();
SlashCommandConfig slashCommandConfig = SlashCommandConfig
.builder()
.enabled(true)
.rootCommandName(MeetupSlashCommandNames.MEETUP_PUBLIC)
.commandName("list")
.build();
return CommandConfiguration.builder()
.name("listMeetups")
.module(UtilityModuleDefinition.UTILITY)
.templated(true)
.slashCommandConfig(slashCommandConfig)
.async(true)
.supportsEmbedException(true)
.causesReaction(false)
.parameters(parameters)
.help(helpInfo)
.build();
}
@Override
public FeatureDefinition getFeature() {
return MeetupFeatureDefinition.MEETUP;
}
}

View File

@@ -0,0 +1,121 @@
package dev.sheldan.sissi.module.meetup.commands;
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.interaction.InteractionService;
import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.sissi.module.meetup.config.MeetupFeatureDefinition;
import dev.sheldan.sissi.module.meetup.config.MeetupSlashCommandNames;
import dev.sheldan.sissi.module.meetup.exception.NotMeetupOrganizerException;
import dev.sheldan.sissi.module.meetup.model.database.Meetup;
import dev.sheldan.sissi.module.meetup.service.MeetupServiceBean;
import dev.sheldan.sissi.module.meetup.service.management.MeetupManagementServiceBean;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Component
public class NotifyMeetupParticipants extends AbstractConditionableCommand {
@Autowired
private SlashCommandParameterService slashCommandParameterService;
@Autowired
private MeetupManagementServiceBean meetupManagementServiceBean;
@Autowired
private MeetupServiceBean meetupServiceBean;
@Autowired
private InteractionService interactionService;
private static final String MEETUP_ID_PARAMETER = "meetupId";
private static final String NOTIFICATION_MESSAGE_PARAMETER = "notificationMessage";
private static final String NOTIFY_MEETUP_PARTICIPANTS_COMMAND = "notifyMeetupParticipants";
private static final String NOTIFY_MEETUP_PARTICIPANTS_RESPONSE = "notifyMeetupParticipants_response";
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters();
Long meetupId = (Long) parameters.get(0);
Meetup meetup = meetupManagementServiceBean.getMeetup(meetupId, commandContext.getGuild().getIdLong());
if(!meetup.getOrganizer().getUserReference().getId().equals(commandContext.getAuthor().getIdLong())) {
throw new NotMeetupOrganizerException();
}
String notificationMessage = (String) parameters.get(1);
return meetupServiceBean.notifyMeetupParticipants(meetup, notificationMessage)
.thenApply(unused -> CommandResult.fromSuccess());
}
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
Long meetupId = slashCommandParameterService.getCommandOption(MEETUP_ID_PARAMETER, event, Integer.class).longValue();
Meetup meetup = meetupManagementServiceBean.getMeetup(meetupId, event.getGuild().getIdLong());
if(!meetup.getOrganizer().getUserReference().getId().equals(event.getMember().getIdLong())) {
throw new NotMeetupOrganizerException();
}
String notificationMessage = slashCommandParameterService.getCommandOption(NOTIFICATION_MESSAGE_PARAMETER, event, String.class);
return meetupServiceBean.notifyMeetupParticipants(meetup, notificationMessage)
.thenCompose(unused -> interactionService.replyEmbed(NOTIFY_MEETUP_PARTICIPANTS_RESPONSE, event))
.thenApply(unused -> CommandResult.fromSuccess());
}
@Override
public CommandConfiguration getConfiguration() {
Parameter meetupIdParameter = Parameter
.builder()
.templated(true)
.name(MEETUP_ID_PARAMETER)
.type(Long.class)
.build();
Parameter notificationMessage = Parameter
.builder()
.templated(true)
.name(NOTIFICATION_MESSAGE_PARAMETER)
.type(String.class)
.remainder(true)
.build();
List<Parameter> parameters = Arrays.asList(meetupIdParameter, notificationMessage);
HelpInfo helpInfo = HelpInfo
.builder()
.templated(true)
.build();
SlashCommandConfig slashCommandConfig = SlashCommandConfig
.builder()
.enabled(true)
.rootCommandName(MeetupSlashCommandNames.MEETUP)
.commandName("notify")
.build();
return CommandConfiguration.builder()
.name(NOTIFY_MEETUP_PARTICIPANTS_COMMAND)
.module(UtilityModuleDefinition.UTILITY)
.templated(true)
.slashCommandConfig(slashCommandConfig)
.async(true)
.supportsEmbedException(true)
.causesReaction(true)
.parameters(parameters)
.help(helpInfo)
.build();
}
@Override
public FeatureDefinition getFeature() {
return MeetupFeatureDefinition.MEETUP;
}
}

View File

@@ -0,0 +1,14 @@
package dev.sheldan.sissi.module.meetup.config;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import org.springframework.stereotype.Component;
@Component
public class MeetupFeatureConfig implements FeatureConfig {
@Override
public FeatureDefinition getFeature() {
return MeetupFeatureDefinition.MEETUP;
}
}

View File

@@ -0,0 +1,15 @@
package dev.sheldan.sissi.module.meetup.config;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import lombok.Getter;
@Getter
public enum MeetupFeatureDefinition implements FeatureDefinition {
MEETUP("meetup");
private String key;
MeetupFeatureDefinition(String key) {
this.key = key;
}
}

View File

@@ -0,0 +1,9 @@
package dev.sheldan.sissi.module.meetup.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
@Configuration
@PropertySource("classpath:meetup.properties")
public class MeetupProperties {
}

View File

@@ -0,0 +1,6 @@
package dev.sheldan.sissi.module.meetup.config;
public class MeetupSlashCommandNames {
public static final String MEETUP = "meetup";
public static final String MEETUP_PUBLIC = "meetupPublic";
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,33 @@
package dev.sheldan.sissi.module.meetup.job;
import dev.sheldan.sissi.module.meetup.service.MeetupServiceBean;
import lombok.extern.slf4j.Slf4j;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.PersistJobDataAfterExecution;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.stereotype.Component;
@Slf4j
@DisallowConcurrentExecution
@Component
@PersistJobDataAfterExecution
public class MeetupCleanupJob extends QuartzJobBean {
@Autowired
private MeetupServiceBean meetupServiceBean;
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
try {
log.info("Executing cleanup job for meetups.");
meetupServiceBean.cleanupMeetups();
} catch (Exception e) {
log.error("Meetup cleanup job failed.", e);
}
}
}

View File

@@ -0,0 +1,50 @@
package dev.sheldan.sissi.module.meetup.job;
import dev.sheldan.sissi.module.meetup.service.MeetupServiceBean;
import lombok.extern.slf4j.Slf4j;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.PersistJobDataAfterExecution;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.stereotype.Component;
@Slf4j
@DisallowConcurrentExecution
@Component
@PersistJobDataAfterExecution
public class MeetupReminderJob extends QuartzJobBean {
private Long meetupId;
private Long serverId;
@Autowired
private MeetupServiceBean meetupServiceBean;
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
try {
log.info("Executing reminder job for meetup {} in server {}.", meetupId, serverId);
meetupServiceBean.remindParticipants(meetupId, serverId);
} catch (Exception e) {
log.error("Meetup reminder job failed.", e);
}
}
public Long getMeetupId() {
return meetupId;
}
public void setMeetupId(Long meetupId) {
this.meetupId = meetupId;
}
public Long getServerId() {
return serverId;
}
public void setServerId(Long serverId) {
this.serverId = serverId;
}
}

View File

@@ -0,0 +1,78 @@
package dev.sheldan.sissi.module.meetup.listener;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.config.ListenerPriority;
import dev.sheldan.abstracto.core.interaction.ComponentPayloadManagementService;
import dev.sheldan.abstracto.core.interaction.button.listener.ButtonClickedListener;
import dev.sheldan.abstracto.core.interaction.button.listener.ButtonClickedListenerModel;
import dev.sheldan.abstracto.core.interaction.button.listener.ButtonClickedListenerResult;
import dev.sheldan.abstracto.core.service.MessageService;
import dev.sheldan.sissi.module.meetup.config.MeetupFeatureDefinition;
import dev.sheldan.sissi.module.meetup.model.database.Meetup;
import dev.sheldan.sissi.module.meetup.model.payload.MeetupChangeTimeConfirmationPayload;
import dev.sheldan.sissi.module.meetup.service.MeetupServiceBean;
import dev.sheldan.sissi.module.meetup.service.management.MeetupManagementServiceBean;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.time.Instant;
@Component
@Slf4j
public class MeetupChangeTimeConfirmationListener implements ButtonClickedListener {
@Autowired
private MeetupManagementServiceBean meetupManagementServiceBean;
@Autowired
private MessageService messageService;
@Autowired
private ComponentPayloadManagementService componentPayloadManagementService;
@Autowired
private MeetupServiceBean meetupServiceBean;
@Override
public ButtonClickedListenerResult execute(ButtonClickedListenerModel model) {
MeetupChangeTimeConfirmationPayload payload = (MeetupChangeTimeConfirmationPayload) model.getDeserializedPayload();
if(model.getEvent().getUser().getIdLong() != payload.getOrganizerUserId()) {
return ButtonClickedListenerResult.IGNORED;
}
if(model.getEvent().getComponentId().equals(payload.getConfirmationId())) {
Meetup meetup = meetupManagementServiceBean.getMeetup(payload.getMeetupId(), payload.getGuildId());
meetupServiceBean.changeMeetupTimeAndNotifyParticipants(meetup, Instant.ofEpochSecond(payload.getNewTime()));
messageService.deleteMessage(model.getEvent().getMessage());
cleanupConfirmationMessagePayloads(payload);
} else if(model.getEvent().getComponentId().equals(payload.getCancelId())) {
messageService.deleteMessage(model.getEvent().getMessage());
cleanupConfirmationMessagePayloads(payload);
return ButtonClickedListenerResult.ACKNOWLEDGED;
} else {
return ButtonClickedListenerResult.IGNORED;
}
return ButtonClickedListenerResult.IGNORED;
}
private void cleanupConfirmationMessagePayloads(MeetupChangeTimeConfirmationPayload payload) {
componentPayloadManagementService.deletePayload(payload.getCancelId());
componentPayloadManagementService.deletePayload(payload.getConfirmationId());
}
@Override
public Boolean handlesEvent(ButtonClickedListenerModel model) {
return model.getOrigin().equals(MeetupServiceBean.MEETUP_CHANGE_TIME_CONFIRMATION_BUTTON) &&
model.getEvent().isFromGuild();
}
@Override
public FeatureDefinition getFeature() {
return MeetupFeatureDefinition.MEETUP;
}
@Override
public Integer getPriority() {
return ListenerPriority.LOWEST;
}
}

View File

@@ -0,0 +1,176 @@
package dev.sheldan.sissi.module.meetup.listener;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.config.ListenerPriority;
import dev.sheldan.abstracto.core.interaction.ComponentPayloadManagementService;
import dev.sheldan.abstracto.core.interaction.ComponentPayloadService;
import dev.sheldan.abstracto.core.interaction.ComponentService;
import dev.sheldan.abstracto.core.interaction.button.listener.ButtonClickedListener;
import dev.sheldan.abstracto.core.interaction.button.listener.ButtonClickedListenerModel;
import dev.sheldan.abstracto.core.interaction.button.listener.ButtonClickedListenerResult;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.ComponentPayload;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.MessageService;
import dev.sheldan.abstracto.core.service.management.ChannelManagementService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.sissi.module.meetup.config.MeetupFeatureDefinition;
import dev.sheldan.sissi.module.meetup.model.database.Meetup;
import dev.sheldan.sissi.module.meetup.model.database.MeetupDecision;
import dev.sheldan.sissi.module.meetup.model.database.MeetupState;
import dev.sheldan.sissi.module.meetup.model.payload.MeetupDecisionPayload;
import dev.sheldan.sissi.module.meetup.model.payload.MeetupConfirmationPayload;
import dev.sheldan.sissi.module.meetup.model.template.MeetupMessageModel;
import dev.sheldan.sissi.module.meetup.service.MeetupServiceBean;
import dev.sheldan.sissi.module.meetup.service.management.MeetupComponentManagementServiceBean;
import dev.sheldan.sissi.module.meetup.service.management.MeetupManagementServiceBean;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Message;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import static dev.sheldan.sissi.module.meetup.service.MeetupServiceBean.MEETUP_DECISION_BUTTON;
@Component
@Slf4j
public class MeetupConfirmationListener implements ButtonClickedListener {
@Autowired
private MeetupManagementServiceBean meetupManagementServiceBean;
@Autowired
private MeetupServiceBean meetupServiceBean;
@Autowired
private ComponentService componentService;
@Autowired
private ComponentPayloadService componentPayloadService;
@Autowired
private ServerManagementService serverManagementService;
@Autowired
private ChannelService channelService;
@Autowired
private ChannelManagementService channelManagementService;
@Autowired
private MeetupConfirmationListener self;
@Autowired
private MessageService messageService;
@Autowired
private ComponentPayloadManagementService componentPayloadManagementService;
@Autowired
private MeetupComponentManagementServiceBean meetupComponentManagementServiceBean;
@Override
public ButtonClickedListenerResult execute(ButtonClickedListenerModel model) {
MeetupConfirmationPayload payload = (MeetupConfirmationPayload) model.getDeserializedPayload();
if(model.getEvent().getUser().getIdLong() != payload.getOrganizerUserId()) {
return ButtonClickedListenerResult.IGNORED;
}
Meetup meetup = meetupManagementServiceBean.getMeetup(payload.getMeetupId(), payload.getGuildId());
if(model.getEvent().getComponentId().equals(payload.getConfirmationId())) {
meetup.setState(MeetupState.CONFIRMED);
} else if(model.getEvent().getComponentId().equals(payload.getCancelId())){
meetup.setState(MeetupState.CANCELLED);
messageService.deleteMessage(model.getEvent().getMessage());
cleanupConfirmationMessagePayloads(payload);
meetupManagementServiceBean.deleteMeetup(meetup);
return ButtonClickedListenerResult.ACKNOWLEDGED;
} else {
return ButtonClickedListenerResult.IGNORED;
}
cleanupConfirmationMessagePayloads(payload);
String yesButtonId = componentService.generateComponentId();
String noButtonId = componentService.generateComponentId();
String maybeButtonId = componentService.generateComponentId();
String noTimeButtonId = componentService.generateComponentId();
MeetupMessageModel messageModel = meetupServiceBean.getMeetupMessageModel(meetup);
messageModel.setYesId(yesButtonId);
messageModel.setNoId(noButtonId);
messageModel.setMaybeId(maybeButtonId);
messageModel.setNoTimeId(noTimeButtonId);
messageModel.setCancelled(false);
Long meetupId = payload.getMeetupId();
Long serverId = payload.getGuildId();
MessageToSend messageToSend = meetupServiceBean.getMeetupMessage(messageModel);
List<CompletableFuture<Message>> messageFutures = channelService.sendMessageToSendToChannel(messageToSend, model.getEvent().getMessageChannel());
FutureUtils.toSingleFutureGeneric(messageFutures).thenAccept(unused -> {
messageService.deleteMessage(model.getEvent().getMessage());
Message meetupMessage = messageFutures.get(0).join();
messageService.pinMessage(meetupMessage);
self.persistPayloads(meetupId, serverId, yesButtonId, noButtonId, maybeButtonId, noTimeButtonId, meetupMessage);
}).exceptionally(throwable -> {
log.error("Failed to send meetup message for meetup {}.", meetupId, throwable);
return null;
});
return ButtonClickedListenerResult.ACKNOWLEDGED;
}
private void cleanupConfirmationMessagePayloads(MeetupConfirmationPayload payload) {
componentPayloadManagementService.deletePayload(payload.getCancelId());
componentPayloadManagementService.deletePayload(payload.getConfirmationId());
}
@Transactional
public void persistPayloads(Long meetupId, Long serverId, String yesButtonId, String noButtonId, String maybeButtonId, String noTimeButtonId, Message meetupMessage) {
MeetupDecisionPayload decisionPayload = MeetupDecisionPayload
.builder()
.meetupId(meetupId)
.guildId(serverId)
.componentPayloads(Arrays.asList(yesButtonId, noButtonId, maybeButtonId, noTimeButtonId))
.build();
AServer server = serverManagementService.loadServer(serverId);
decisionPayload.setMeetupDecision(MeetupDecision.YES);
ComponentPayload yesPayload = componentPayloadService.createButtonPayload(yesButtonId, decisionPayload, MEETUP_DECISION_BUTTON, server);
decisionPayload.setMeetupDecision(MeetupDecision.NO);
ComponentPayload noPayload = componentPayloadService.createButtonPayload(noButtonId, decisionPayload, MEETUP_DECISION_BUTTON, server);
decisionPayload.setMeetupDecision(MeetupDecision.MAYBE);
ComponentPayload maybePayload = componentPayloadService.createButtonPayload(maybeButtonId, decisionPayload, MEETUP_DECISION_BUTTON, server);
decisionPayload.setMeetupDecision(MeetupDecision.NO_TIME);
ComponentPayload noTimePayload = componentPayloadService.createButtonPayload(noTimeButtonId, decisionPayload, MEETUP_DECISION_BUTTON, server);
Meetup meetup = meetupManagementServiceBean.getMeetup(meetupId, serverId);
// storing the button IDs, so we can remove them independently
meetupComponentManagementServiceBean.createComponent(meetup, yesButtonId, yesPayload);
meetupComponentManagementServiceBean.createComponent(meetup, noButtonId, noPayload);
meetupComponentManagementServiceBean.createComponent(meetup, maybeButtonId, maybePayload);
meetupComponentManagementServiceBean.createComponent(meetup, noTimeButtonId, noTimePayload);
meetupServiceBean.scheduleReminders(meetup);
meetup.setMessageId(meetupMessage.getIdLong());
AChannel channel = channelManagementService.loadChannel(meetupMessage.getChannel());
meetup.setMeetupChannel(channel);
}
@Override
public Boolean handlesEvent(ButtonClickedListenerModel model) {
return model.getOrigin().equals(MeetupServiceBean.MEETUP_CONFIRMATION_BUTTON) &&
model.getEvent().isFromGuild();
}
@Override
public FeatureDefinition getFeature() {
return MeetupFeatureDefinition.MEETUP;
}
@Override
public Integer getPriority() {
return ListenerPriority.LOWEST;
}
}

View File

@@ -0,0 +1,103 @@
package dev.sheldan.sissi.module.meetup.listener;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.config.ListenerPriority;
import dev.sheldan.abstracto.core.interaction.button.listener.ButtonClickedListener;
import dev.sheldan.abstracto.core.interaction.button.listener.ButtonClickedListenerModel;
import dev.sheldan.abstracto.core.interaction.button.listener.ButtonClickedListenerResult;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.models.template.display.MemberDisplay;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.sissi.module.meetup.config.MeetupFeatureDefinition;
import dev.sheldan.sissi.module.meetup.model.database.Meetup;
import dev.sheldan.sissi.module.meetup.model.database.MeetupDecision;
import dev.sheldan.sissi.module.meetup.model.database.MeetupParticipant;
import dev.sheldan.sissi.module.meetup.model.payload.MeetupDecisionPayload;
import dev.sheldan.sissi.module.meetup.model.template.MeetupMessageModel;
import dev.sheldan.sissi.module.meetup.service.MeetupServiceBean;
import dev.sheldan.sissi.module.meetup.service.management.MeetupManagementServiceBean;
import dev.sheldan.sissi.module.meetup.service.management.MeetupParticipatorManagementServiceBean;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Optional;
@Component
@Slf4j
public class MeetupDecisionListener implements ButtonClickedListener {
@Autowired
private MeetupParticipatorManagementServiceBean meetupParticipatorManagementServiceBean;
@Autowired
private MeetupManagementServiceBean meetupManagementServiceBean;
@Autowired
private UserInServerManagementService userInServerManagementService;
@Autowired
private MeetupServiceBean meetupServiceBean;
@Autowired
private ChannelService channelService;
@Override
public ButtonClickedListenerResult execute(ButtonClickedListenerModel model) {
MeetupDecisionPayload payload = (MeetupDecisionPayload) model.getDeserializedPayload();
Meetup meetup = meetupManagementServiceBean.getMeetup(payload.getMeetupId(), payload.getGuildId());
AUserInAServer userInAServer = userInServerManagementService.loadOrCreateUser(model.getEvent().getMember());
Optional<MeetupParticipant> participationOptional = meetupParticipatorManagementServiceBean.getParticipation(meetup, userInAServer);
if(participationOptional.isPresent()) {
participationOptional.get().setDecision(payload.getMeetupDecision());
} else {
meetupParticipatorManagementServiceBean.createParticipation(meetup, userInAServer, payload.getMeetupDecision());
}
MeetupMessageModel meetupMessageModel = meetupServiceBean.getMeetupMessageModel(meetup);
addParticipationToModel(meetupMessageModel, userInAServer, payload.getMeetupDecision());
MessageToSend messageToSend = meetupServiceBean.getMeetupMessage(meetupMessageModel);
channelService.editEmbedMessageInAChannel(messageToSend.getEmbeds().get(0), model.getEvent().getChannel(), meetup.getMessageId())
.thenAccept(message -> log.info("Updated message of meetup {} in channel {} in server {}.", meetup.getId().getId(), meetup.getMeetupChannel().getId(), meetup.getServer().getId()))
.exceptionally(throwable -> {
log.info("Failed to update message of meetup {} in channel {} in server {}.", meetup.getId().getId(), meetup.getMeetupChannel().getId(), meetup.getServer().getId(), throwable);
return null;
});
return ButtonClickedListenerResult.ACKNOWLEDGED;
}
private void addParticipationToModel(MeetupMessageModel model, AUserInAServer aUserInAServer, MeetupDecision decision) {
if(decision.equals(MeetupDecision.NO)) {
addIfMissing(model.getDeclinedParticipants(), aUserInAServer);
} else if(decision.equals(MeetupDecision.YES)) {
addIfMissing(model.getParticipants(), aUserInAServer);
} else if(decision.equals(MeetupDecision.MAYBE)) {
addIfMissing(model.getMaybeParticipants(), aUserInAServer);
} else if(decision.equals(MeetupDecision.NO_TIME))
addIfMissing(model.getNoTimeParticipants(), aUserInAServer);
}
private void addIfMissing(List<MemberDisplay> list, AUserInAServer aUserInAServer) {
if(list.stream().noneMatch(memberDisplay -> memberDisplay.getUserId().equals(aUserInAServer.getUserReference().getId()))) {
list.add(MemberDisplay.fromAUserInAServer(aUserInAServer));
}
}
@Override
public Boolean handlesEvent(ButtonClickedListenerModel model) {
return model.getOrigin().equals(MeetupServiceBean.MEETUP_DECISION_BUTTON) && model.getEvent().isFromGuild();
}
@Override
public FeatureDefinition getFeature() {
return MeetupFeatureDefinition.MEETUP;
}
@Override
public Integer getPriority() {
return ListenerPriority.MEDIUM;
}
}

View File

@@ -0,0 +1,21 @@
package dev.sheldan.sissi.module.meetup.model.command;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import java.time.Instant;
@Getter
@Setter
@Builder
public class MeetupChangeTimeConfirmationModel {
private Instant meetupTime;
private String topic;
private Long userId;
private String description;
private Long meetupId;
private Long guildId;
private String confirmationId;
private String cancelId;
}

View File

@@ -0,0 +1,22 @@
package dev.sheldan.sissi.module.meetup.model.command;
import dev.sheldan.abstracto.core.models.template.display.MemberDisplay;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import java.time.Instant;
@Getter
@Setter
@Builder
public class MeetupConfirmationModel {
private MemberDisplay organizer;
private Instant meetupTime;
private Long meetupId;
private String topic;
private String description;
private Long guildId;
private String confirmationId;
private String cancelId;
}

View File

@@ -0,0 +1,33 @@
package dev.sheldan.sissi.module.meetup.model.command;
import dev.sheldan.abstracto.core.models.ServerChannelMessage;
import dev.sheldan.sissi.module.meetup.model.database.Meetup;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import java.time.Instant;
@Getter
@Setter
@Builder
public class MeetupListItemModel {
private String topic;
private Instant meetupTime;
private ServerChannelMessage meetupMessage;
public static MeetupListItemModel fromMeetup(Meetup meetup) {
ServerChannelMessage message = ServerChannelMessage
.builder()
.serverId(meetup.getServer().getId())
.channelId(meetup.getMeetupChannel().getId())
.messageId(meetup.getMessageId())
.build();
return MeetupListItemModel
.builder()
.topic(meetup.getTopic())
.meetupTime(meetup.getMeetupTime())
.meetupMessage(message)
.build();
}
}

View File

@@ -0,0 +1,14 @@
package dev.sheldan.sissi.module.meetup.model.command;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
@Builder
public class MeetupListModel {
private List<MeetupListItemModel> meetups;
}

View File

@@ -0,0 +1,89 @@
package dev.sheldan.sissi.module.meetup.model.database;
import dev.sheldan.abstracto.core.models.ServerSpecificId;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import lombok.*;
import javax.persistence.*;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
@Builder
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "meetup")
@Getter
@Setter
@EqualsAndHashCode
public class Meetup {
@EmbeddedId
@Getter
@Id
private ServerSpecificId id;
@ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.LAZY)
@MapsId("serverId")
@JoinColumn(name = "server_id", referencedColumnName = "id", nullable = false)
private AServer server;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "organizer_user_in_server_id", nullable = false)
private AUserInAServer organizer;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "meetup_channel_id", nullable = false)
private AChannel meetupChannel;
@Getter
@Column(name = "message_id")
private Long messageId;
@OneToMany(
fetch = FetchType.LAZY,
orphanRemoval = true,
cascade = {CascadeType.PERSIST, CascadeType.MERGE},
mappedBy = "meetup")
@Builder.Default
private List<MeetupParticipant> participants = new ArrayList<>();
@OneToMany(
fetch = FetchType.LAZY,
orphanRemoval = true,
cascade = {CascadeType.PERSIST, CascadeType.MERGE},
mappedBy = "meetup")
@Builder.Default
private List<MeetupComponent> meetupComponents = new ArrayList<>();
@Getter
@Enumerated(EnumType.STRING)
@Column(name = "state", nullable = false)
private MeetupState state;
@Getter
@Column(name = "topic", nullable = false)
private String topic;
@Getter
@Column(name = "description")
private String description;
@Column(name = "meetup_time", nullable = false)
private Instant meetupTime;
@Column(name = "created")
private Instant created;
@Column(name = "updated")
private Instant updated;
@Column(name = "early_reminder_job_trigger_key")
private String earlyReminderJobTriggerKey;
@Column(name = "late_reminder_job_trigger_key")
private String lateReminderJobTriggerKey;
}

View File

@@ -0,0 +1,40 @@
package dev.sheldan.sissi.module.meetup.model.database;
import dev.sheldan.abstracto.core.models.database.ComponentPayload;
import dev.sheldan.sissi.module.meetup.model.database.embed.MeetupComponentId;
import lombok.*;
import javax.persistence.*;
import java.time.Instant;
@Builder
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "meetup_component")
@Getter
@Setter
@EqualsAndHashCode
public class MeetupComponent {
@EmbeddedId
@Getter
private MeetupComponentId id;
@ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.LAZY)
@MapsId("componentId")
@JoinColumn(name = "component_id", nullable = false)
private ComponentPayload payload;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumns(
{
@JoinColumn(updatable = false, insertable = false, name = "meetup_id", referencedColumnName = "id"),
@JoinColumn(updatable = false, insertable = false, name = "server_id", referencedColumnName = "server_id")
})
private Meetup meetup;
@Column(name = "created")
private Instant created;
}

View File

@@ -0,0 +1,5 @@
package dev.sheldan.sissi.module.meetup.model.database;
public enum MeetupDecision {
YES, NO, MAYBE, NO_TIME
}

View File

@@ -0,0 +1,46 @@
package dev.sheldan.sissi.module.meetup.model.database;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.sissi.module.meetup.model.database.embed.MeetupParticipationId;
import lombok.*;
import javax.persistence.*;
import java.time.Instant;
@Builder
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "meetup_participator")
@Getter
@Setter
@EqualsAndHashCode
public class MeetupParticipant {
@EmbeddedId
@Getter
private MeetupParticipationId id;
@ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.LAZY)
@MapsId("participatorId")
@JoinColumn(name = "meetup_participator_user_in_server_id", nullable = false)
private AUserInAServer participator;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumns(
{
@JoinColumn(updatable = false, insertable = false, name = "meetup_id", referencedColumnName = "id"),
@JoinColumn(updatable = false, insertable = false, name = "server_id", referencedColumnName = "server_id")
})
private Meetup meetup;
@Enumerated(EnumType.STRING)
@Column(name = "decision", nullable = false)
private MeetupDecision decision;
@Column(name = "created")
private Instant created;
@Column(name = "updated")
private Instant updated;
}

View File

@@ -0,0 +1,5 @@
package dev.sheldan.sissi.module.meetup.model.database;
public enum MeetupState {
NEW, CONFIRMED, CANCELLED, DONE
}

View File

@@ -0,0 +1,25 @@
package dev.sheldan.sissi.module.meetup.model.database.embed;
import lombok.*;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import java.io.Serializable;
@Embeddable
@Getter
@Setter
@Builder
@EqualsAndHashCode
@NoArgsConstructor
@AllArgsConstructor
public class MeetupComponentId implements Serializable {
@Column(name = "component_id")
private String componentId;
@Column(name = "meetup_id")
private Long meetupId;
@Column(name = "server_id")
private Long serverId;
}

View File

@@ -0,0 +1,25 @@
package dev.sheldan.sissi.module.meetup.model.database.embed;
import lombok.*;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import java.io.Serializable;
@Embeddable
@Getter
@Setter
@Builder
@EqualsAndHashCode
@NoArgsConstructor
@AllArgsConstructor
public class MeetupParticipationId implements Serializable {
@Column(name = "meetup_participator_user_in_server_id")
private Long participatorId;
@Column(name = "meetup_id")
private Long meetupId;
@Column(name = "server_id")
private Long serverId;
}

View File

@@ -0,0 +1,18 @@
package dev.sheldan.sissi.module.meetup.model.payload;
import dev.sheldan.abstracto.core.interaction.button.ButtonPayload;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@Builder
@Getter
@Setter
public class MeetupChangeTimeConfirmationPayload implements ButtonPayload {
private Long organizerUserId;
private Long meetupId;
private Long newTime;
private Long guildId;
private String confirmationId;
private String cancelId;
}

View File

@@ -0,0 +1,17 @@
package dev.sheldan.sissi.module.meetup.model.payload;
import dev.sheldan.abstracto.core.interaction.button.ButtonPayload;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@Builder
@Getter
@Setter
public class MeetupConfirmationPayload implements ButtonPayload {
private Long organizerUserId;
private Long meetupId;
private Long guildId;
private String confirmationId;
private String cancelId;
}

View File

@@ -0,0 +1,19 @@
package dev.sheldan.sissi.module.meetup.model.payload;
import dev.sheldan.abstracto.core.interaction.button.ButtonPayload;
import dev.sheldan.sissi.module.meetup.model.database.MeetupDecision;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Builder
@Getter
@Setter
public class MeetupDecisionPayload implements ButtonPayload {
private Long meetupId;
private Long guildId;
private MeetupDecision meetupDecision;
private List<String> componentPayloads;
}

View File

@@ -0,0 +1,29 @@
package dev.sheldan.sissi.module.meetup.model.template;
import dev.sheldan.abstracto.core.models.template.display.MemberDisplay;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import java.time.Instant;
import java.util.List;
@Getter
@Builder
@Setter
public class MeetupMessageModel {
private String topic;
private String description;
private Instant meetupTime;
private MemberDisplay organizer;
private Long meetupId;
private String yesId;
private String noId;
private String maybeId;
private String noTimeId;
private Boolean cancelled;
private List<MemberDisplay> participants;
private List<MemberDisplay> maybeParticipants;
private List<MemberDisplay> noTimeParticipants;
private List<MemberDisplay> declinedParticipants;
}

View File

@@ -0,0 +1,14 @@
package dev.sheldan.sissi.module.meetup.model.template;
import dev.sheldan.abstracto.core.models.template.display.MemberDisplay;
import lombok.Builder;
import lombok.Getter;
import java.util.List;
@Builder
@Getter
public class MeetupNotificationModel {
private List<MemberDisplay> participants;
private String notificationMessage;
}

View File

@@ -0,0 +1,17 @@
package dev.sheldan.sissi.module.meetup.model.template;
import dev.sheldan.abstracto.core.models.ServerChannelMessage;
import lombok.Builder;
import lombok.Getter;
import java.time.Instant;
@Builder
@Getter
public class MeetupTimeChangedNotificationModel {
private String meetupTopic;
private String meetupDescription;
private Instant oldDate;
private Instant newDate;
private ServerChannelMessage meetupMessage;
}

View File

@@ -0,0 +1,10 @@
package dev.sheldan.sissi.module.meetup.repository;
import dev.sheldan.sissi.module.meetup.model.database.MeetupComponent;
import dev.sheldan.sissi.module.meetup.model.database.embed.MeetupComponentId;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface MeetupComponentRepository extends JpaRepository<MeetupComponent, MeetupComponentId> {
}

View File

@@ -0,0 +1,10 @@
package dev.sheldan.sissi.module.meetup.repository;
import dev.sheldan.sissi.module.meetup.model.database.MeetupParticipant;
import dev.sheldan.sissi.module.meetup.model.database.embed.MeetupParticipationId;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface MeetupParticipatorRepository extends JpaRepository<MeetupParticipant, MeetupParticipationId> {
}

View File

@@ -0,0 +1,18 @@
package dev.sheldan.sissi.module.meetup.repository;
import dev.sheldan.abstracto.core.models.ServerSpecificId;
import dev.sheldan.sissi.module.meetup.model.database.Meetup;
import dev.sheldan.sissi.module.meetup.model.database.MeetupState;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.time.Instant;
import java.util.List;
@Repository
public interface MeetupRepository extends JpaRepository<Meetup, ServerSpecificId> {
List<Meetup> findByMeetupTimeLessThan(Instant date);
List<Meetup> findByMeetupTimeGreaterThan(Instant date);
List<Meetup> findByMeetupTimeGreaterThanAndState(Instant date, MeetupState state);
List<Meetup> findByState(MeetupState state);
}

View File

@@ -0,0 +1,389 @@
package dev.sheldan.sissi.module.meetup.service;
import dev.sheldan.abstracto.core.interaction.ComponentPayloadManagementService;
import dev.sheldan.abstracto.core.interaction.ComponentPayloadService;
import dev.sheldan.abstracto.core.models.ServerChannelMessage;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.template.display.MemberDisplay;
import dev.sheldan.abstracto.core.service.*;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.scheduling.model.JobParameters;
import dev.sheldan.abstracto.scheduling.service.SchedulerService;
import dev.sheldan.sissi.module.meetup.model.command.MeetupChangeTimeConfirmationModel;
import dev.sheldan.sissi.module.meetup.model.command.MeetupConfirmationModel;
import dev.sheldan.sissi.module.meetup.model.database.Meetup;
import dev.sheldan.sissi.module.meetup.model.database.MeetupDecision;
import dev.sheldan.sissi.module.meetup.model.database.MeetupParticipant;
import dev.sheldan.sissi.module.meetup.model.database.MeetupState;
import dev.sheldan.sissi.module.meetup.model.payload.MeetupChangeTimeConfirmationPayload;
import dev.sheldan.sissi.module.meetup.model.payload.MeetupConfirmationPayload;
import dev.sheldan.sissi.module.meetup.model.template.MeetupMessageModel;
import dev.sheldan.sissi.module.meetup.model.template.MeetupNotificationModel;
import dev.sheldan.sissi.module.meetup.model.template.MeetupTimeChangedNotificationModel;
import dev.sheldan.sissi.module.meetup.service.management.MeetupComponentManagementServiceBean;
import dev.sheldan.sissi.module.meetup.service.management.MeetupManagementServiceBean;
import dev.sheldan.sissi.module.meetup.service.management.MeetupParticipatorManagementServiceBean;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.GuildMessageChannel;
import net.dv8tion.jda.api.entities.MessageChannel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
@Component
@Slf4j
public class MeetupServiceBean {
public static final String MEETUP_CONFIRMATION_BUTTON = "MEETUP_CONFIRMATION_BUTTON";
public static final String MEETUP_CHANGE_TIME_CONFIRMATION_BUTTON = "MEETUP_CHANGE_TIME_CONFIRMATION_BUTTON";
public static final String MEETUP_DECISION_BUTTON = "MEETUP_DECISION_BUTTON";
private static final String MEETUP_DISPLAY_TEMPLATE = "meetup_display";
private static final String MEETUP_CANCELLATION_TEMPLATE = "meetup_cancel_notification";
private static final String MEETUP_CHANGE_TIME_NOTIFICATION_TEMPLATE = "changeMeetupTime_notification";
private static final String MEETUP_REMINDER_TEMPLATE = "meetup_reminder_notification";
private static final String MEETUP_LATE_REMINDER_CONFIG_KEY = "meetupLateReminderSeconds";
private static final String MEETUP_EARLY_REMINDER_CONFIG_KEY = "meetupEarlyReminderSeconds";
public static final String MEETUP_REMINDER_JOB_NAME = "meetupReminderJob";
@Autowired
private TemplateService templateService;
@Autowired
private ComponentPayloadService componentPayloadService;
@Autowired
private ServerManagementService serverManagementService;
@Autowired
private MessageService messageService;
@Autowired
private ComponentPayloadManagementService componentPayloadManagementService;
@Autowired
private MeetupManagementServiceBean meetupManagementServiceBean;
@Autowired
private MeetupServiceBean self;
@Autowired
private ChannelService channelService;
@Autowired
private UserService userService;
@Autowired
private ConfigService configService;
@Autowired
private SchedulerService schedulerService;
@Autowired
private MeetupParticipatorManagementServiceBean meetupParticipatorManagementServiceBean;
@Autowired
private MeetupComponentManagementServiceBean meetupComponentManagementServiceBean;
public void storeMeetupConfirmation(MeetupConfirmationModel model) {
AServer server = serverManagementService.loadServer(model.getGuildId());
MeetupConfirmationPayload confirmationPayload = MeetupConfirmationPayload
.builder()
.confirmationId(model.getConfirmationId())
.meetupId(model.getMeetupId())
.cancelId(model.getCancelId())
.organizerUserId(model.getOrganizer().getUserId())
.guildId(model.getGuildId())
.build();
componentPayloadService.createButtonPayload(model.getConfirmationId(), confirmationPayload, MEETUP_CONFIRMATION_BUTTON, server);
componentPayloadService.createButtonPayload(model.getCancelId(), confirmationPayload, MEETUP_CONFIRMATION_BUTTON, server);
}
public void storeMeetupChangeTimeConfirmation(MeetupChangeTimeConfirmationModel model) {
AServer server = serverManagementService.loadServer(model.getGuildId());
MeetupChangeTimeConfirmationPayload confirmationPayload = MeetupChangeTimeConfirmationPayload
.builder()
.newTime(model.getMeetupTime().getEpochSecond())
.confirmationId(model.getConfirmationId())
.meetupId(model.getMeetupId())
.organizerUserId(model.getUserId())
.cancelId(model.getCancelId())
.guildId(model.getGuildId())
.build();
componentPayloadService.createButtonPayload(model.getConfirmationId(), confirmationPayload, MEETUP_CHANGE_TIME_CONFIRMATION_BUTTON, server);
componentPayloadService.createButtonPayload(model.getCancelId(), confirmationPayload, MEETUP_CHANGE_TIME_CONFIRMATION_BUTTON, server);
}
public MeetupMessageModel getMeetupMessageModel(Meetup meetup) {
List<MeetupParticipant> allParticipants = meetup.getParticipants();
List<MeetupParticipant> participating = allParticipants
.stream()
.filter(meetupParticipator -> meetupParticipator.getDecision().equals(MeetupDecision.YES))
.collect(Collectors.toList());
List<MeetupParticipant> maybe = allParticipants
.stream()
.filter(meetupParticipator -> meetupParticipator.getDecision().equals(MeetupDecision.MAYBE))
.collect(Collectors.toList());
List<MeetupParticipant> notParticipating = allParticipants
.stream()
.filter(meetupParticipator -> meetupParticipator.getDecision().equals(MeetupDecision.NO))
.collect(Collectors.toList());
List<MeetupParticipant> notTimeParticipating = allParticipants
.stream()
.filter(meetupParticipator -> meetupParticipator.getDecision().equals(MeetupDecision.NO_TIME))
.collect(Collectors.toList());
return MeetupMessageModel
.builder()
.description(meetup.getDescription())
.topic(meetup.getTopic())
.meetupTime(meetup.getMeetupTime())
.meetupId(meetup.getId().getId())
.participants(getMemberDisplays(participating))
.declinedParticipants(getMemberDisplays(notParticipating))
.noTimeParticipants(getMemberDisplays(notTimeParticipating))
.maybeParticipants(getMemberDisplays(maybe))
.cancelled(meetup.getState().equals(MeetupState.CANCELLED))
.organizer(MemberDisplay.fromAUserInAServer(meetup.getOrganizer()))
.build();
}
private List<MemberDisplay> getMemberDisplays(List<MeetupParticipant> participants) {
return participants
.stream()
.map(meetupParticipator -> MemberDisplay.fromAUserInAServer(meetupParticipator.getParticipator()))
.collect(Collectors.toList());
}
public MessageToSend getMeetupMessage(MeetupMessageModel model) {
return templateService.renderEmbedTemplate(MEETUP_DISPLAY_TEMPLATE, model);
}
public CompletableFuture<Void> cancelMeetup(Meetup meetup) {
Long serverId = meetup.getServer().getId();
Long meetupId = meetup.getId().getId();
GuildMessageChannel channel = channelService.getMessageChannelFromServer(serverId, meetup.getMeetupChannel().getId());
MeetupMessageModel model = getMeetupMessageModel(meetup);
List<String> componentPayloads = meetup
.getMeetupComponents()
.stream()
.map(meetupComponent -> meetupComponent.getId().getComponentId())
.collect(Collectors.toList());
model.setCancelled(true);
MessageToSend meetupMessage = getMeetupMessage(model);
return messageService.editMessageInChannel(channel, meetupMessage, meetup.getMessageId())
.thenAccept(unused -> self.notifyParticipants(meetupId, serverId))
.thenAccept(unused -> self.cleanupMeetup(meetupId, serverId, componentPayloads));
}
@Transactional
public void notifyParticipants(Long meetupId, Long serverId) {
Meetup meetup = meetupManagementServiceBean.getMeetup(meetupId, serverId);
MeetupMessageModel model = getMeetupMessageModel(meetup);
MessageToSend messageToSend = templateService.renderEmbedTemplate(MEETUP_CANCELLATION_TEMPLATE, model);
meetup
.getParticipants()
.stream()
.filter(meetupParticipator ->
meetupParticipator.getDecision().equals(MeetupDecision.MAYBE) ||
meetupParticipator.getDecision().equals(MeetupDecision.YES))
.forEach(meetupParticipator -> {
Long userId = meetupParticipator.getParticipator().getUserReference().getId();
userService.retrieveUserForId(userId)
.thenCompose(user -> messageService.sendMessageToSendToUser(user, messageToSend))
.thenAccept(message -> log.info("Notified user {} about cancellation of meetup {} in server {}.", userId, meetupId, serverId))
.exceptionally(throwable -> {
log.warn("Failed to notify user {} about cancellation of meetup {} in server {}.", userId, meetupId, serverId);
return null;
});
});
}
@Transactional
public void cleanupMeetup(Long meetupId, Long serverId, List<String> componentPayloads) {
Meetup meetup = meetupManagementServiceBean.getMeetup(meetupId, serverId);
meetup.setState(MeetupState.CANCELLED);
meetupComponentManagementServiceBean.deleteAllComponents(meetup);
log.info("Cleanup meetup {} in server {}.", meetup, serverId);
if(meetup.getEarlyReminderJobTriggerKey() != null) {
schedulerService.stopTrigger(meetup.getEarlyReminderJobTriggerKey());
}
if(meetup.getLateReminderJobTriggerKey() != null) {
schedulerService.stopTrigger(meetup.getLateReminderJobTriggerKey());
}
componentPayloads.forEach(s -> componentPayloadManagementService.deletePayload(s));
}
public void scheduleReminders(Meetup meetup) {
Long serverId = meetup.getServer().getId();
Long meetupId = meetup.getId().getId();
Long earlyReminderSeconds = configService.getLongValueOrConfigDefault(MEETUP_EARLY_REMINDER_CONFIG_KEY, serverId);
Long lateReminderSeconds = configService.getLongValueOrConfigDefault(MEETUP_LATE_REMINDER_CONFIG_KEY, serverId);
HashMap<Object, Object> parameters = new HashMap<>();
parameters.put("meetupId", meetupId.toString());
parameters.put("serverId", serverId.toString());
JobParameters jobParameters = JobParameters
.builder()
.parameters(parameters)
.build();
Instant meetupDate = meetup.getMeetupTime();
Instant earlyDate = meetupDate.minus(earlyReminderSeconds, ChronoUnit.SECONDS);
if(earlyDate.isAfter(Instant.now())) {
log.info("Scheduling early reminder job for meetup {} in server {} at {}.", meetupId, serverId, earlyDate);
String earlyTriggerKey = schedulerService.executeJobWithParametersOnce(MEETUP_REMINDER_JOB_NAME, "meetup", jobParameters, Date.from(earlyDate));
meetup.setEarlyReminderJobTriggerKey(earlyTriggerKey);
}
Instant lateDate = meetupDate.minus(lateReminderSeconds, ChronoUnit.SECONDS);
if(lateDate.isAfter(Instant.now())) {
log.info("Scheduling late reminder job for meetup {} in server {} at {}.", meetupId, serverId, lateDate);
String lateTriggerKey = schedulerService.executeJobWithParametersOnce(MEETUP_REMINDER_JOB_NAME, "meetup", jobParameters, Date.from(lateDate));
meetup.setLateReminderJobTriggerKey(lateTriggerKey);
}
}
public CompletableFuture<Void> notifyMeetupParticipants(Meetup meetup, String message) {
List<MeetupDecision> decisionsToBeNotified = Arrays.asList(MeetupDecision.MAYBE, MeetupDecision.YES);
List<MemberDisplay> participants = meetup
.getParticipants()
.stream()
.filter(meetupParticipator -> decisionsToBeNotified.contains(meetupParticipator.getDecision()))
.map(meetupParticipator -> MemberDisplay.fromAUserInAServer(meetupParticipator.getParticipator()))
.collect(Collectors.toList());
MeetupNotificationModel model = MeetupNotificationModel
.builder()
.notificationMessage(message)
.participants(participants)
.build();
MessageChannel channel = channelService.getMessageChannelFromServer(meetup.getServer().getId(), meetup.getMeetupChannel().getId());
MessageToSend messageToSend = templateService.renderEmbedTemplate("notifyMeetupParticipants_notification_message", model, meetup.getServer().getId());
return FutureUtils.toSingleFutureGeneric(channelService.sendMessageToSendToChannel(messageToSend, channel));
}
@Transactional
public void remindParticipants(Long meetupId, Long serverId) {
Meetup meetup = meetupManagementServiceBean.getMeetup(meetupId, serverId);
MeetupMessageModel model = getMeetupMessageModel(meetup);
MessageToSend messageToSend = templateService.renderEmbedTemplate(MEETUP_REMINDER_TEMPLATE, model);
meetup
.getParticipants()
.stream()
.filter(meetupParticipator ->
meetupParticipator.getDecision().equals(MeetupDecision.MAYBE) ||
meetupParticipator.getDecision().equals(MeetupDecision.YES))
.forEach(meetupParticipator -> {
Long userId = meetupParticipator.getParticipator().getUserReference().getId();
userService.retrieveUserForId(userId)
.thenCompose(user -> messageService.sendMessageToSendToUser(user, messageToSend))
.thenAccept(message -> log.info("Notified user {} about incoming meetup {} in server {}.", userId, meetupId, serverId));
});
}
@Transactional
public void cleanupMeetups() {
Instant time = Instant.now().minus(1, ChronoUnit.DAYS);
List<Meetup> oldMeetups = meetupManagementServiceBean.getMeetupsOlderThan(time);
log.info("Deleting {} old meetup.s", oldMeetups.size());
deleteMeetups(oldMeetups);
List<Meetup> cancelledMeetups = meetupManagementServiceBean.findCancelledMeetups();
log.info("Deleting {} cancelled meetups.", cancelledMeetups.size());
deleteMeetups(cancelledMeetups);
}
private void deleteMeetups(List<Meetup> oldMeetups) {
oldMeetups.forEach(meetup -> {
if(meetup.getMessageId() != null) {
Long messageId = meetup.getMessageId();
Long meetupId = meetup.getId().getId();
Long serverId = meetup.getServer().getId();
messageService.deleteMessageInChannelInServer(meetup.getServer().getId(), meetup.getMeetupChannel().getId(), meetup.getMessageId())
.exceptionally(throwable -> {
log.error("Failed to delete message {} for meetup {} in server {}.", messageId, meetupId, serverId);
return null;
});
}
meetupComponentManagementServiceBean.deleteAllComponents(meetup);
});
meetupManagementServiceBean.deleteMeetups(oldMeetups);
}
public CompletableFuture<Void> changeMeetupTimeAndNotifyParticipants(Meetup meetup, Instant newTime) {
List<MeetupDecision> decisions = Arrays.asList(MeetupDecision.MAYBE, MeetupDecision.NO_TIME, MeetupDecision.YES);
List<MeetupParticipant> participants = meetup
.getParticipants()
.stream()
.filter(meetupParticipator -> decisions.contains(meetupParticipator.getDecision()))
.collect(Collectors.toList());
List<Long> userIdsToNotify = participants
.stream()
.map(meetupParticipator -> meetupParticipator.getParticipator().getUserReference().getId())
.collect(Collectors.toList());
Long serverId = meetup.getServer().getId();
ServerChannelMessage meetupMessage = ServerChannelMessage
.builder()
.serverId(serverId)
.channelId(meetup.getMeetupChannel().getId())
.messageId(meetup.getMessageId())
.build();
MeetupTimeChangedNotificationModel notificationModel = MeetupTimeChangedNotificationModel
.builder()
.meetupDescription(meetup.getDescription())
.meetupTopic(meetup.getTopic())
.meetupMessage(meetupMessage)
.newDate(newTime)
.oldDate(meetup.getMeetupTime())
.build();
if(meetup.getEarlyReminderJobTriggerKey() != null) {
schedulerService.stopTrigger(meetup.getEarlyReminderJobTriggerKey());
}
if(meetup.getLateReminderJobTriggerKey() != null) {
schedulerService.stopTrigger(meetup.getLateReminderJobTriggerKey());
}
// set the new time here, so that we can use it in schedule
meetup.setMeetupTime(newTime);
scheduleReminders(meetup);
MessageToSend messageToSend = templateService.renderEmbedTemplate(MEETUP_CHANGE_TIME_NOTIFICATION_TEMPLATE, notificationModel, serverId);
Long meetupId = meetup.getId().getId();
userIdsToNotify.forEach(userId -> {
userService.retrieveUserForId(userId).thenCompose(user -> messageService.sendMessageToSendToUser(user, messageToSend)
.exceptionally(throwable -> {
log.warn("Failed to notify user {} about changed time of meetup {} in server {}.", userId, meetupId, serverId);
return null;
}));
});
meetupParticipatorManagementServiceBean.deleteParticipants(participants);
List<Long> userInServerIds = participants
.stream()
.map(meetupParticipant -> meetupParticipant.getParticipator().getUserInServerId())
.collect(Collectors.toList());
meetup
.getParticipants().removeIf(meetupParticipant -> userInServerIds.contains(meetupParticipant.getParticipator().getUserInServerId()));
MeetupMessageModel meetupMessageModel = getMeetupMessageModel(meetup);
meetupMessageModel.setParticipants(new ArrayList<>());
meetupMessageModel.setMaybeParticipants(new ArrayList<>());
meetupMessageModel.setNoTimeParticipants(new ArrayList<>());
MessageToSend updatedMeetupMessage = getMeetupMessage(meetupMessageModel);
GuildMessageChannel meetupChannel = channelService.getMessageChannelFromServer(serverId, meetup.getMeetupChannel().getId());
return channelService.editEmbedMessageInAChannel(updatedMeetupMessage.getEmbeds().get(0), meetupChannel, meetup.getMessageId())
.thenAccept(message -> log.info("Updated message of meetup {} in channel {} in server {}.", meetupId, meetup.getMeetupChannel().getId(), serverId))
.exceptionally(throwable -> {
log.info("Failed to update message of meetup {} in channel {} in server {}.", meetupId, meetup.getMeetupChannel().getId(), serverId, throwable);
return null;
});
}
}

View File

@@ -0,0 +1,40 @@
package dev.sheldan.sissi.module.meetup.service.management;
import dev.sheldan.abstracto.core.models.database.ComponentPayload;
import dev.sheldan.sissi.module.meetup.model.database.Meetup;
import dev.sheldan.sissi.module.meetup.model.database.MeetupComponent;
import dev.sheldan.sissi.module.meetup.model.database.embed.MeetupComponentId;
import dev.sheldan.sissi.module.meetup.repository.MeetupComponentRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class MeetupComponentManagementServiceBean {
@Autowired
private MeetupComponentRepository meetupComponentRepository;
public MeetupComponent createComponent(Meetup meetup, String componentId, ComponentPayload payload) {
MeetupComponentId id = MeetupComponentId
.builder()
.meetupId(meetup.getId().getId())
.serverId(meetup.getServer().getId())
.componentId(componentId)
.build();
MeetupComponent component = MeetupComponent
.builder()
.id(id)
.payload(payload)
.meetup(meetup)
.build();
return meetupComponentRepository.save(component);
}
public void deleteComponent(MeetupComponent meetupComponent) {
meetupComponentRepository.delete(meetupComponent);
}
public void deleteAllComponents(Meetup meetup) {
meetupComponentRepository.deleteAll(meetup.getMeetupComponents());
}
}

View File

@@ -0,0 +1,79 @@
package dev.sheldan.sissi.module.meetup.service.management;
import dev.sheldan.abstracto.core.models.ServerSpecificId;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.service.CounterService;
import dev.sheldan.sissi.module.meetup.exception.MeetupNotFoundException;
import dev.sheldan.sissi.module.meetup.exception.MeetupPastTimeException;
import dev.sheldan.sissi.module.meetup.model.database.Meetup;
import dev.sheldan.sissi.module.meetup.model.database.MeetupState;
import dev.sheldan.sissi.module.meetup.repository.MeetupRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.time.Instant;
import java.util.List;
@Component
public class MeetupManagementServiceBean {
@Autowired
private MeetupRepository meetupRepository;
@Autowired
private CounterService counterService;
public static final String MEETUP_COUNTER_KEY = "meetup";
public Meetup createMeetup(Instant timeStamp, String topic, String description, AUserInAServer organizer, AChannel meetupChannel) {
if(timeStamp.isBefore(Instant.now())) {
throw new MeetupPastTimeException();
}
Long meetupId = counterService.getNextCounterValue(organizer.getServerReference(), MEETUP_COUNTER_KEY);
Meetup meetup = Meetup
.builder()
.meetupTime(timeStamp)
.description(description)
.topic(topic)
.organizer(organizer)
.server(organizer.getServerReference())
.meetupChannel(meetupChannel)
.state(MeetupState.NEW)
.id(new ServerSpecificId(organizer.getServerReference().getId(), meetupId))
.build();
return meetupRepository.save(meetup);
}
public Meetup getMeetup(Long meetupId, Long serverId) {
return meetupRepository.findById(new ServerSpecificId(serverId, meetupId))
.orElseThrow(MeetupNotFoundException::new);
}
public List<Meetup> getMeetupsOlderThan(Instant instant) {
return meetupRepository.findByMeetupTimeLessThan(instant);
}
public List<Meetup> getMeetups() {
return meetupRepository.findAll();
}
public List<Meetup> findCancelledMeetups() {
return meetupRepository.findByState(MeetupState.CANCELLED);
}
public List<Meetup> getFutureMeetups() {
return meetupRepository.findByMeetupTimeGreaterThan(Instant.now());
}
public List<Meetup> getIncomingMeetups() {
return meetupRepository.findByMeetupTimeGreaterThanAndState(Instant.now(), MeetupState.CONFIRMED);
}
public void deleteMeetups(List<Meetup> meetups) {
meetupRepository.deleteAll(meetups);
}
public void deleteMeetup(Meetup meetup) {
meetupRepository.delete(meetup);
}
}

View File

@@ -0,0 +1,55 @@
package dev.sheldan.sissi.module.meetup.service.management;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.sissi.module.meetup.model.database.Meetup;
import dev.sheldan.sissi.module.meetup.model.database.MeetupDecision;
import dev.sheldan.sissi.module.meetup.model.database.MeetupParticipant;
import dev.sheldan.sissi.module.meetup.model.database.embed.MeetupParticipationId;
import dev.sheldan.sissi.module.meetup.repository.MeetupParticipatorRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Optional;
@Component
@Slf4j
public class MeetupParticipatorManagementServiceBean {
@Autowired
private MeetupParticipatorRepository repository;
public Optional<MeetupParticipant> getParticipation(Meetup meetup, AUserInAServer aUserInAServer) {
MeetupParticipationId id = MeetupParticipationId
.builder()
.meetupId(meetup.getId().getId())
.serverId(meetup.getServer().getId())
.participatorId(aUserInAServer.getUserInServerId())
.build();
return repository.findById(id);
}
public void deleteParticipants(List<MeetupParticipant> participants) {
log.info("Deleting {} participants", participants.size());
repository.deleteAll(participants);
participants.forEach(meetupParticipant -> meetupParticipant.setMeetup(null));
}
public MeetupParticipant createParticipation(Meetup meetup, AUserInAServer aUserInAServer, MeetupDecision meetupDecision) {
MeetupParticipationId id = MeetupParticipationId
.builder()
.meetupId(meetup.getId().getId())
.serverId(meetup.getServer().getId())
.participatorId(aUserInAServer.getUserInServerId())
.build();
MeetupParticipant participator = MeetupParticipant
.builder()
.id(id)
.meetup(meetup)
.participator(aUserInAServer)
.decision(meetupDecision)
.build();
return repository.save(participator);
}
}

View File

@@ -0,0 +1,8 @@
abstracto.featureFlags.meetup.featureName=meetup
abstracto.featureFlags.meetup.enabled=false
abstracto.systemConfigs.meetupEarlyReminderSeconds.name=meetupEarlyReminderSeconds
abstracto.systemConfigs.meetupEarlyReminderSeconds.longValue=604800
abstracto.systemConfigs.meetupLateReminderSeconds.name=meetupLateReminderSeconds
abstracto.systemConfigs.meetupLateReminderSeconds.longValue=86400

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="meetupFeature" value="(SELECT id FROM feature WHERE key = 'meetup')"/>
<changeSet author="Sheldan" id="meetup-commands">
<insert tableName="command">
<column name="name" value="createMeetup"/>
<column name="module_id" valueComputed="${utilityModule}"/>
<column name="feature_id" valueComputed="${meetupFeature}"/>
</insert>
<insert tableName="command">
<column name="name" value="listMeetups"/>
<column name="module_id" valueComputed="${utilityModule}"/>
<column name="feature_id" valueComputed="${meetupFeature}"/>
</insert>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,13 @@
<?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"/>
<include file="reminder_job.xml" relativeToChangelogFile="true"/>
<include file="meetup_cleanup_job.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="meetup_feature-insertion">
<insert tableName="feature">
<column name="key" value="meetup"/>
</insert>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,19 @@
<?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="meetup-cleanup-job-insert">
<insert tableName="scheduler_job">
<column name="name" value="meetupCleanupJob"/>
<column name="group_name" value="meetup"/>
<column name="clazz" value="dev.sheldan.sissi.module.meetup.job.MeetupCleanupJob"/>
<column name="active" value="true"/>
<column name="cron_expression" value="0 0 0 * * ?"/>
<column name="recovery" value="false"/>
</insert>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,18 @@
<?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="meetup-reminder-job-insert">
<insert tableName="scheduler_job">
<column name="name" value="meetupReminderJob"/>
<column name="group_name" value="meetup"/>
<column name="clazz" value="dev.sheldan.sissi.module.meetup.job.MeetupReminderJob"/>
<column name="active" value="true"/>
<column name="recovery" value="false"/>
</insert>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,66 @@
<?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="meetup-table">
<createTable tableName="meetup">
<column name="id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="organizer_user_in_server_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="meetup_channel_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="server_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="message_id" type="BIGINT">
<constraints nullable="true"/>
</column>
<column name="topic" type="VARCHAR(256)">
<constraints nullable="false"/>
</column>
<column name="state" type="VARCHAR(256)">
<constraints nullable="false"/>
</column>
<column name="description" type="VARCHAR(2048)">
<constraints nullable="true"/>
</column>
<column name="meetup_time" type="TIMESTAMP WITHOUT TIME ZONE">
<constraints nullable="false"/>
</column>
<column name="created" type="TIMESTAMP WITHOUT TIME ZONE"/>
<column name="updated" type="TIMESTAMP WITHOUT TIME ZONE"/>
<column name="early_reminder_job_trigger_key" type="VARCHAR(255)"/>
<column name="late_reminder_job_trigger_key" type="VARCHAR(255)"/>
</createTable>
<addPrimaryKey columnNames="server_id, id" tableName="meetup" constraintName="pk_meetup" validate="true"/>
<addForeignKeyConstraint baseColumnNames="meetup_channel_id" baseTableName="meetup" constraintName="fk_meetup_channel"
deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION"
referencedColumnNames="id" referencedTableName="channel" validate="true"/>
<addForeignKeyConstraint baseColumnNames="organizer_user_in_server_id" baseTableName="meetup" constraintName="fk_meetup_organizer"
deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION"
referencedColumnNames="user_in_server_id" referencedTableName="user_in_server" validate="true"/>
<addForeignKeyConstraint baseColumnNames="server_id" baseTableName="meetup" constraintName="fk_meetup_server"
deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION"
referencedColumnNames="id" referencedTableName="server" validate="true"/>
<sql>
DROP TRIGGER IF EXISTS meetup_update_trigger ON meetup;
CREATE TRIGGER meetup_update_trigger BEFORE UPDATE ON meetup FOR EACH ROW EXECUTE PROCEDURE update_trigger_procedure();
</sql>
<sql>
DROP TRIGGER IF EXISTS meetup_insert_trigger ON meetup;
CREATE TRIGGER meetup_insert_trigger BEFORE INSERT ON meetup FOR EACH ROW EXECUTE PROCEDURE insert_trigger_procedure();
</sql>
<sql>
ALTER TABLE meetup ADD CONSTRAINT check_meetup_state CHECK (state IN ('NEW', 'CONFIRMED', 'CANCELLED', 'DONE'));
</sql>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,48 @@
<?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="meetup_participator-table">
<createTable tableName="meetup_participator">
<column name="meetup_participator_user_in_server_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="meetup_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="server_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="decision" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>
<column name="created" type="TIMESTAMP WITHOUT TIME ZONE">
<constraints nullable="false"/>
</column>
<column name="updated" type="TIMESTAMP WITHOUT TIME ZONE"/>
</createTable>
<addPrimaryKey columnNames="meetup_participator_user_in_server_id, meetup_id, server_id" tableName="meetup_participator" constraintName="pk_meetup_participator" validate="false"/>
<addForeignKeyConstraint baseColumnNames="meetup_id, server_id" baseTableName="meetup_participator" constraintName="fk_meetup_participator_meetup"
deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION"
referencedColumnNames="id, server_id" referencedTableName="meetup" validate="false"/>
<addForeignKeyConstraint baseColumnNames="meetup_participator_user_in_server_id" baseTableName="meetup_participator" constraintName="fk_meetup_participator_participator"
deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION"
referencedColumnNames="user_in_server_id" referencedTableName="user_in_server" validate="false"/>
<sql>
DROP TRIGGER IF EXISTS meetup_participator_update_trigger ON meetup_participator;
CREATE TRIGGER meetup_participator_update_trigger BEFORE UPDATE ON meetup_participator FOR EACH ROW EXECUTE PROCEDURE update_trigger_procedure();
</sql>
<sql>
DROP TRIGGER IF EXISTS meetup_participator_insert_trigger ON meetup_participator;
CREATE TRIGGER meetup_participator_insert_trigger BEFORE INSERT ON meetup_participator FOR EACH ROW EXECUTE PROCEDURE insert_trigger_procedure();
</sql>
<sql>
ALTER TABLE meetup_participator ADD CONSTRAINT check_meetup_participator_decision CHECK (decision IN ('YES','NO', 'MAYBE'));
</sql>
</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="meetup.xml" relativeToChangelogFile="true"/>
<include file="meetup_participator.xml" relativeToChangelogFile="true"/>
</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="seedData/data.xml" relativeToChangelogFile="true"/>
<include file="tables/tables.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -0,0 +1,30 @@
<?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="meetupFeature" value="(SELECT id FROM feature WHERE key = 'meetup')"/>
<changeSet author="Sheldan" id="meetup-commands">
<insert tableName="command">
<column name="name" value="cancelMeetup"/>
<column name="module_id" valueComputed="${utilityModule}"/>
<column name="feature_id" valueComputed="${meetupFeature}"/>
</insert>
<insert tableName="command">
<column name="name" value="changeMeetupTime"/>
<column name="module_id" valueComputed="${utilityModule}"/>
<column name="feature_id" valueComputed="${meetupFeature}"/>
</insert>
<insert tableName="command">
<column name="name" value="notifyMeetupParticipants"/>
<column name="module_id" valueComputed="${utilityModule}"/>
<column name="feature_id" valueComputed="${meetupFeature}"/>
</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="command.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -0,0 +1,37 @@
<?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="meetup_component-table">
<createTable tableName="meetup_component">
<column name="component_id" type="VARCHAR(100)">
<constraints nullable="false"/>
</column>
<column name="meetup_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="server_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="created" type="TIMESTAMP WITHOUT TIME ZONE">
<constraints nullable="false"/>
</column>
</createTable>
<addPrimaryKey columnNames="component_id, meetup_id, server_id" tableName="meetup_component" constraintName="pk_meetup_component" validate="true"/>
<addForeignKeyConstraint baseColumnNames="meetup_id, server_id" baseTableName="meetup_component" constraintName="fk_meetup_component_meetup"
deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION"
referencedColumnNames="id, server_id" referencedTableName="meetup" validate="true"/>
<addForeignKeyConstraint baseColumnNames="component_id" baseTableName="meetup_component" constraintName="fk_meetup_component_component"
deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION"
referencedColumnNames="id" referencedTableName="component_payload" validate="false"/>
<sql>
DROP TRIGGER IF EXISTS meetup_component_insert_trigger ON meetup_component;
CREATE TRIGGER meetup_component_insert_trigger BEFORE INSERT ON meetup_component FOR EACH ROW EXECUTE PROCEDURE insert_trigger_procedure();
</sql>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,16 @@
<?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="meetup_participator-table">
<sql>
ALTER TABLE meetup_participator DROP CONSTRAINT check_meetup_participator_decision;
ALTER TABLE meetup_participator ADD CONSTRAINT check_meetup_participator_decision CHECK (decision IN ('YES','NO', 'MAYBE', 'NO_TIME'));
</sql>
</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="meetup_participator.xml" relativeToChangelogFile="true"/>
<include file="meetup_component.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

File diff suppressed because it is too large Load Diff

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="1.1.0/collection.xml" relativeToChangelogFile="true"/>
<include file="1.2.0/collection.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -0,0 +1,19 @@
<?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.sissi.application</groupId>
<artifactId>application</artifactId>
<version>1.3.2</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>sissi-modules</artifactId>
<packaging>pom</packaging>
<modules>
<module>quotes</module>
<module>meetup</module>
</modules>
</project>

View File

@@ -0,0 +1,35 @@
<?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.sissi.application</groupId>
<artifactId>sissi-modules</artifactId>
<version>1.3.2</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>dev.sheldan.sissi.application.module</groupId>
<artifactId>quotes</artifactId>
<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>
</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,150 @@
package dev.sheldan.sissi.module.quotes.commands;
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.interaction.InteractionService;
import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.sissi.module.quotes.config.QuoteSlashCommandNames;
import dev.sheldan.sissi.module.quotes.config.QuotesFeatureDefinition;
import dev.sheldan.sissi.module.quotes.config.QuotesModuleDefinition;
import dev.sheldan.sissi.module.quotes.exception.QuoteNotFoundException;
import dev.sheldan.sissi.module.quotes.model.database.Quote;
import dev.sheldan.sissi.module.quotes.service.QuoteServiceBean;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.MessageChannel;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.interactions.InteractionHook;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
@Component
public class QuoteCommand extends AbstractConditionableCommand {
private static final String QUOTE_COMMAND = "quote";
private static final String MEMBER_PARAMETER = "member";
@Autowired
private QuoteServiceBean quoteServiceBean;
@Autowired
private UserInServerManagementService userInServerManagementService;
@Autowired
private ServerManagementService serverManagementService;
@Autowired
private ChannelService channelService;
@Autowired
private QuoteCommand self;
@Autowired
private SlashCommandParameterService slashCommandParameterService;
@Autowired
private InteractionService interactionService;
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters();
Optional<Quote> foundQuote;
if(parameters.isEmpty()) {
AServer server = serverManagementService.loadServer(commandContext.getGuild().getIdLong());
foundQuote = quoteServiceBean.getRandomQuote(server);
} else {
Member targetMember = (Member) parameters.get(0);
AUserInAServer user = userInServerManagementService.loadOrCreateUser(targetMember);
foundQuote = quoteServiceBean.getRandomQuoteForMember(user);
}
Quote quoteToDisplay = foundQuote.orElseThrow(QuoteNotFoundException::new);
return quoteServiceBean.renderQuoteToMessageToSend(quoteToDisplay)
.thenCompose(messageToSend -> self.sendMessageToChannel(messageToSend, commandContext.getChannel()))
.thenApply(unused -> CommandResult.fromSuccess());
}
@Transactional
public CompletableFuture<Void> sendMessageToChannel(MessageToSend messageToSend, MessageChannel messageChannel) {
return FutureUtils.toSingleFutureGeneric(channelService.sendMessageToSendToChannel(messageToSend, messageChannel));
}
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
Optional<Quote> foundQuote;
if(slashCommandParameterService.hasCommandOption(MEMBER_PARAMETER, event)) {
Member targetMember = slashCommandParameterService.getCommandOption(MEMBER_PARAMETER, event, Member.class);
AUserInAServer user = userInServerManagementService.loadOrCreateUser(targetMember);
foundQuote = quoteServiceBean.getRandomQuoteForMember(user);
} else {
AServer server = serverManagementService.loadServer(event.getGuild().getIdLong());
foundQuote = quoteServiceBean.getRandomQuote(server);
}
Quote quoteToDisplay = foundQuote.orElseThrow(QuoteNotFoundException::new);
return quoteServiceBean.renderQuoteToMessageToSend(quoteToDisplay)
.thenCompose(messageToSend -> self.replyMessage(event, messageToSend))
.thenApply(unused -> CommandResult.fromSuccess());
}
@Transactional
public CompletableFuture<InteractionHook> replyMessage(SlashCommandInteractionEvent event, MessageToSend messageToSend) {
return interactionService.replyMessageToSend(messageToSend, event);
}
@Override
public CommandConfiguration getConfiguration() {
Parameter memberParameter = Parameter
.builder()
.templated(true)
.name(MEMBER_PARAMETER)
.type(Member.class)
.optional(true)
.build();
SlashCommandConfig slashCommandConfig = SlashCommandConfig
.builder()
.enabled(true)
.rootCommandName(QuoteSlashCommandNames.QUOTE)
.commandName("random")
.build();
List<Parameter> parameters = Collections.singletonList(memberParameter);
HelpInfo helpInfo = HelpInfo
.builder()
.templated(true)
.build();
return CommandConfiguration.builder()
.name(QUOTE_COMMAND)
.module(QuotesModuleDefinition.QUOTES)
.templated(true)
.slashCommandConfig(slashCommandConfig)
.async(true)
.supportsEmbedException(true)
.causesReaction(false)
.parameters(parameters)
.help(helpInfo)
.build();
}
@Override
public FeatureDefinition getFeature() {
return QuotesFeatureDefinition.QUOTES;
}
}

View File

@@ -0,0 +1,103 @@
package dev.sheldan.sissi.module.quotes.commands;
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.interaction.InteractionService;
import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.sissi.module.quotes.config.QuoteSlashCommandNames;
import dev.sheldan.sissi.module.quotes.config.QuotesFeatureDefinition;
import dev.sheldan.sissi.module.quotes.config.QuotesModuleDefinition;
import dev.sheldan.sissi.module.quotes.service.QuoteServiceBean;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Component
public class QuoteDelete extends AbstractConditionableCommand {
private static final String QUOTE_DELETE_COMMAND = "quoteDelete";
private static final String QUOTE_ID_PARAMETER = "quoteId";
private static final String QUOTE_DELETE_RESPONSE = "quoteDelete_response";
@Autowired
private ServerManagementService serverManagementService;
@Autowired
private QuoteServiceBean quoteServiceBean;
@Autowired
private InteractionService interactionService;
@Autowired
private SlashCommandParameterService slashCommandParameterService;
@Override
public CommandResult execute(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters();
Long quoteId = (Long) parameters.get(0);
AServer server = serverManagementService.loadServer(commandContext.getGuild().getIdLong());
quoteServiceBean.deleteQuote(quoteId, server);
return CommandResult.fromSuccess();
}
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
Long quoteId = slashCommandParameterService.getCommandOption(QUOTE_ID_PARAMETER, event, Integer.class).longValue();
AServer server = serverManagementService.loadServer(event.getGuild().getIdLong());
quoteServiceBean.deleteQuote(quoteId, server);
return interactionService.replyEmbed(QUOTE_DELETE_RESPONSE, event)
.thenApply(interactionHook -> CommandResult.fromSuccess());
}
@Override
public CommandConfiguration getConfiguration() {
Parameter quoteIdParameter = Parameter
.builder()
.templated(true)
.name(QUOTE_ID_PARAMETER)
.type(Long.class)
.build();
List<Parameter> parameters = Collections.singletonList(quoteIdParameter);
HelpInfo helpInfo = HelpInfo
.builder()
.templated(true)
.build();
SlashCommandConfig slashCommandConfig = SlashCommandConfig
.builder()
.enabled(true)
.rootCommandName(QuoteSlashCommandNames.QUOTE_INTERNAL)
.commandName("delete")
.build();
return CommandConfiguration.builder()
.name(QUOTE_DELETE_COMMAND)
.module(QuotesModuleDefinition.QUOTES)
.templated(true)
.slashCommandConfig(slashCommandConfig)
.async(false)
.requiresConfirmation(true)
.supportsEmbedException(true)
.causesReaction(true)
.parameters(parameters)
.help(helpInfo)
.build();
}
@Override
public FeatureDefinition getFeature() {
return QuotesFeatureDefinition.QUOTES;
}
}

View File

@@ -0,0 +1,133 @@
package dev.sheldan.sissi.module.quotes.commands;
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.interaction.InteractionService;
import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.sissi.module.quotes.config.QuoteSlashCommandNames;
import dev.sheldan.sissi.module.quotes.config.QuotesFeatureDefinition;
import dev.sheldan.sissi.module.quotes.config.QuotesModuleDefinition;
import dev.sheldan.sissi.module.quotes.exception.QuoteNotFoundException;
import dev.sheldan.sissi.module.quotes.model.database.Quote;
import dev.sheldan.sissi.module.quotes.service.QuoteServiceBean;
import net.dv8tion.jda.api.entities.MessageChannel;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.interactions.InteractionHook;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
@Component
public class QuoteGet extends AbstractConditionableCommand {
private static final String QUOTE_GET_COMMAND = "quoteGet";
private static final String QUOTE_ID_PARAMETER = "quoteId";
@Autowired
private QuoteServiceBean quoteServiceBean;
@Autowired
private ServerManagementService serverManagementService;
@Autowired
private ChannelService channelService;
@Autowired
private QuoteGet self;
@Autowired
private InteractionService interactionService;
@Autowired
private SlashCommandParameterService slashCommandParameterService;
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters();
Long quoteId = (Long) parameters.get(0);
AServer server = serverManagementService.loadServer(commandContext.getGuild().getIdLong());
Optional<Quote> possibleQuote = quoteServiceBean.getQuote(quoteId, server);
Quote quoteToDisplay = possibleQuote.orElseThrow(QuoteNotFoundException::new);
return quoteServiceBean.renderQuoteToMessageToSend(quoteToDisplay)
.thenCompose(messageToSend -> self.sendMessageToChannel(messageToSend, commandContext.getChannel()))
.thenApply(unused -> CommandResult.fromSuccess());
}
@Transactional
public CompletableFuture<Void> sendMessageToChannel(MessageToSend messageToSend, MessageChannel messageChannel) {
return FutureUtils.toSingleFutureGeneric(channelService.sendMessageToSendToChannel(messageToSend, messageChannel));
}
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
Long quoteId = slashCommandParameterService.getCommandOption(QUOTE_ID_PARAMETER, event, Integer.class).longValue();
AServer server = serverManagementService.loadServer(event.getGuild().getIdLong());
Optional<Quote> possibleQuote = quoteServiceBean.getQuote(quoteId, server);
Quote quoteToDisplay = possibleQuote.orElseThrow(QuoteNotFoundException::new);
return quoteServiceBean.renderQuoteToMessageToSend(quoteToDisplay)
.thenCompose(messageToSend -> self.replySlashCommand(event, messageToSend))
.thenApply(unused -> CommandResult.fromSuccess());
}
@Transactional
public CompletableFuture<InteractionHook> replySlashCommand(SlashCommandInteractionEvent event, MessageToSend messageToSend) {
return interactionService.replyMessageToSend(messageToSend, event);
}
@Override
public CommandConfiguration getConfiguration() {
Parameter quoteIdParameter = Parameter
.builder()
.templated(true)
.name(QUOTE_ID_PARAMETER)
.type(Long.class)
.build();
List<Parameter> parameters = Collections.singletonList(quoteIdParameter);
HelpInfo helpInfo = HelpInfo
.builder()
.templated(true)
.build();
SlashCommandConfig slashCommandConfig = SlashCommandConfig
.builder()
.enabled(true)
.rootCommandName(QuoteSlashCommandNames.QUOTE)
.commandName("get")
.build();
return CommandConfiguration.builder()
.name(QUOTE_GET_COMMAND)
.module(QuotesModuleDefinition.QUOTES)
.templated(true)
.slashCommandConfig(slashCommandConfig)
.async(true)
.supportsEmbedException(true)
.causesReaction(false)
.parameters(parameters)
.help(helpInfo)
.build();
}
@Override
public FeatureDefinition getFeature() {
return QuotesFeatureDefinition.QUOTES;
}
}

View File

@@ -0,0 +1,133 @@
package dev.sheldan.sissi.module.quotes.commands;
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.interaction.InteractionService;
import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.sissi.module.quotes.config.QuoteSlashCommandNames;
import dev.sheldan.sissi.module.quotes.config.QuotesFeatureDefinition;
import dev.sheldan.sissi.module.quotes.config.QuotesModuleDefinition;
import dev.sheldan.sissi.module.quotes.exception.QuoteNotFoundException;
import dev.sheldan.sissi.module.quotes.model.database.Quote;
import dev.sheldan.sissi.module.quotes.service.QuoteServiceBean;
import net.dv8tion.jda.api.entities.MessageChannel;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.interactions.InteractionHook;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
@Component
public class QuoteSearch extends AbstractConditionableCommand {
private static final String QUOTE_SEARCH_COMMAND = "quoteSearch";
private static final String QUERY_PARAMETER = "query";
@Autowired
private QuoteServiceBean quoteServiceBean;
@Autowired
private ServerManagementService serverManagementService;
@Autowired
private ChannelService channelService;
@Autowired
private QuoteSearch self;
@Autowired
private SlashCommandParameterService slashCommandParameterService;
@Autowired
private InteractionService interactionService;
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters();
String query = (String) parameters.get(0);
AServer server = serverManagementService.loadServer(commandContext.getGuild().getIdLong());
Optional<Quote> possibleQuote = quoteServiceBean.searchQuote(query, server);
Quote quoteToDisplay = possibleQuote.orElseThrow(QuoteNotFoundException::new);
return quoteServiceBean.renderQuoteToMessageToSend(quoteToDisplay)
.thenCompose(messageToSend -> self.sendMessageToChannel(messageToSend, commandContext.getChannel()))
.thenApply(unused -> CommandResult.fromSuccess());
}
@Transactional
public CompletableFuture<Void> sendMessageToChannel(MessageToSend messageToSend, MessageChannel messageChannel) {
return FutureUtils.toSingleFutureGeneric(channelService.sendMessageToSendToChannel(messageToSend, messageChannel));
}
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
String query = slashCommandParameterService.getCommandOption(QUERY_PARAMETER, event, String.class);
AServer server = serverManagementService.loadServer(event.getGuild().getIdLong());
Optional<Quote> possibleQuote = quoteServiceBean.searchQuote(query, server);
Quote quoteToDisplay = possibleQuote.orElseThrow(QuoteNotFoundException::new);
return quoteServiceBean.renderQuoteToMessageToSend(quoteToDisplay)
.thenCompose(messageToSend -> self.replyMessage(event, messageToSend))
.thenApply(unused -> CommandResult.fromSuccess());
}
@Transactional
public CompletableFuture<InteractionHook> replyMessage(SlashCommandInteractionEvent event, MessageToSend messageToSend) {
return interactionService.replyMessageToSend(messageToSend, event);
}
@Override
public CommandConfiguration getConfiguration() {
Parameter searchParameter = Parameter
.builder()
.templated(true)
.name(QUERY_PARAMETER)
.type(String.class)
.build();
List<Parameter> parameters = Collections.singletonList(searchParameter);
HelpInfo helpInfo = HelpInfo
.builder()
.templated(true)
.build();
SlashCommandConfig slashCommandConfig = SlashCommandConfig
.builder()
.enabled(true)
.rootCommandName(QuoteSlashCommandNames.QUOTE)
.commandName("search")
.build();
return CommandConfiguration.builder()
.name(QUOTE_SEARCH_COMMAND)
.module(QuotesModuleDefinition.QUOTES)
.templated(true)
.slashCommandConfig(slashCommandConfig)
.async(true)
.supportsEmbedException(true)
.causesReaction(false)
.parameters(parameters)
.help(helpInfo)
.build();
}
@Override
public FeatureDefinition getFeature() {
return QuotesFeatureDefinition.QUOTES;
}
}

View File

@@ -0,0 +1,103 @@
package dev.sheldan.sissi.module.quotes.commands;
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.interaction.InteractionService;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.sissi.module.quotes.config.QuotesFeatureDefinition;
import dev.sheldan.sissi.module.quotes.config.QuotesModuleDefinition;
import dev.sheldan.sissi.module.quotes.model.command.QuoteStatsModel;
import dev.sheldan.sissi.module.quotes.service.QuoteServiceBean;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Component
public class QuoteStats extends AbstractConditionableCommand {
private static final String QUOTE_STATS_COMMAND = "quoteStats";
private static final String MEMBER_PARAMETER = "member";
private static final String QUOTE_STATS_RESPONSE_TEMPLATE_KEY = "quoteStats_response";
@Autowired
private QuoteServiceBean quoteServiceBean;
@Autowired
private ChannelService channelService;
@Autowired
private SlashCommandParameterService slashCommandParameterService;
@Autowired
private InteractionService interactionService;
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters();
Member targetMember;
if(parameters.isEmpty()) {
targetMember = commandContext.getAuthor();
} else {
targetMember = (Member) parameters.get(0);
}
QuoteStatsModel model = quoteServiceBean.getQuoteStats(targetMember);
return FutureUtils.toSingleFutureGeneric(channelService.sendEmbedTemplateInMessageChannelList(QUOTE_STATS_RESPONSE_TEMPLATE_KEY, model, commandContext.getChannel()))
.thenApply(unused -> CommandResult.fromSuccess());
}
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
Member target;
if(slashCommandParameterService.hasCommandOption(MEMBER_PARAMETER, event)) {
target = slashCommandParameterService.getCommandOption(MEMBER_PARAMETER, event, Member.class);
} else {
target = event.getMember();
}
QuoteStatsModel model = quoteServiceBean.getQuoteStats(target);
return interactionService.replyEmbed(QUOTE_STATS_RESPONSE_TEMPLATE_KEY, model, event)
.thenApply(interactionHook -> CommandResult.fromSuccess());
}
@Override
public CommandConfiguration getConfiguration() {
Parameter searchParameter = Parameter
.builder()
.templated(true)
.name(MEMBER_PARAMETER)
.type(Member.class)
.optional(true)
.build();
List<Parameter> parameters = Collections.singletonList(searchParameter);
HelpInfo helpInfo = HelpInfo
.builder()
.templated(true)
.build();
return CommandConfiguration.builder()
.name(QUOTE_STATS_COMMAND)
.module(QuotesModuleDefinition.QUOTES)
.templated(true)
.async(true)
.supportsEmbedException(true)
.causesReaction(false)
.parameters(parameters)
.help(helpInfo)
.build();
}
@Override
public FeatureDefinition getFeature() {
return QuotesFeatureDefinition.QUOTES;
}
}

View File

@@ -0,0 +1,6 @@
package dev.sheldan.sissi.module.quotes.config;
public class QuoteSlashCommandNames {
public static final String QUOTE = "quote";
public static final String QUOTE_INTERNAL = "quoteInternal";
}

View File

@@ -0,0 +1,14 @@
package dev.sheldan.sissi.module.quotes.config;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import org.springframework.stereotype.Component;
@Component
public class QuotesFeatureConfig implements FeatureConfig {
@Override
public FeatureDefinition getFeature() {
return QuotesFeatureDefinition.QUOTES;
}
}

Some files were not shown because too many files have changed in this diff Show More