diff --git a/.github/workflows/pr-validation.yml b/.github/workflows/pr-validation.yml
new file mode 100644
index 0000000..8f6f94a
--- /dev/null
+++ b/.github/workflows/pr-validation.yml
@@ -0,0 +1,25 @@
+name: PR Validation
+
+on:
+ pull_request:
+ branches:
+ - master
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+
+ steps:
+
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Set up JDK 25
+ uses: actions/setup-java@v4
+ with:
+ java-version: '25'
+ distribution: 'temurin'
+ cache: 'maven'
+
+ - name: Run Maven Tests
+ run: mvn clean test
diff --git a/.github/workflows/release-publish.yml b/.github/workflows/release-publish.yml
new file mode 100644
index 0000000..591228d
--- /dev/null
+++ b/.github/workflows/release-publish.yml
@@ -0,0 +1,54 @@
+name: Release & Publish
+
+on:
+ pull_request:
+ types:
+ - closed
+ branches:
+ - master
+
+jobs:
+ release:
+ if: github.event.pull_request.merged == true && github.head_ref == 'develop'
+ runs-on: ubuntu-latest
+
+ steps:
+
+ - name: Checkout master branch
+ uses: actions/checkout@v4
+ with:
+ ref: master
+ token: ${{ secrets.PAT_TOKEN }}
+
+ - name: Set up JDK 25
+ uses: actions/setup-java@v4
+ with:
+ java-version: '25'
+ distribution: 'temurin'
+ cache: 'maven'
+
+ - name: Remove SNAPSHOT from POM
+ id: bump_version
+ run: |
+ mvn versions:set -DremoveSnapshot=true -DgenerateBackupPoms=false
+ NEW_VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)
+ echo "VERSION=$NEW_VERSION" >> $GITHUB_ENV
+
+ - name: Commit and Push to Master
+ run: |
+ git config user.name "github-actions[bot]"
+ git config user.email "github-actions[bot]@users.noreply.github.com"
+ git commit -am "chore: release version ${{ env.VERSION }}"
+ git push origin master
+
+ - name: Package Application
+ # Tests are skipped because they already passed in the PR Validation workflow
+ run: mvn clean package -DskipTests
+
+ - name: Publish GitHub Release
+ uses: softprops/action-gh-release@v2
+ with:
+ tag_name: v${{ env.VERSION }}
+ name: Release v${{ env.VERSION }}
+ files: target/*.jar
+ generate_release_notes: true
diff --git a/pom.xml b/pom.xml
index 92f5d7b..2f68f52 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,24 +7,27 @@
org.springframework.boot
spring-boot-starter-parent
- 2.5.2
+ 4.0.4
ovh.excale
vgreeter
- 2.3.0
+ 26.4.0-SNAPSHOT
VGreeter
Let the bot greet your friends when they join vocal chat on Discord!
https://github.com/devExcale/VGreeter
jar
+
+ scm:git:${project.url}
+
+
- 1.8
- 8
- 8
+ 25
UTF-8
+ 0.1.8
@@ -43,7 +46,27 @@
net.dv8tion
JDA
- 4.3.0_277
+ 6.4.1
+
+
+ club.minnced
+ jdave-api
+ ${jdave.version}
+
+
+ club.minnced
+ jdave-native-win-x86-64
+ ${jdave.version}
+
+
+ club.minnced
+ jdave-native-linux-x86-64
+ ${jdave.version}
+
+
+ club.minnced
+ jdave-native-linux-aarch64
+ ${jdave.version}
@@ -62,7 +85,12 @@
ch.qos.logback
logback-classic
- 1.2.5
+
+
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
@@ -76,18 +104,18 @@
org.postgresql
postgresql
- 42.2.22
+ 42.7.7
-
+
+
+ org.projectlombok
+ lombok
+ 1.18.44
+ provided
+
-
-
- dv8tion
- m2-dv8tion
- https://m2.dv8tion.net/releases
-
-
+
${project.name}-${project.version}
@@ -105,10 +133,31 @@
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.14.1
+
+ ${java.version}
+ full
+
+
+ org.projectlombok
+ lombok
+ 1.18.44
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ ${project.parent.version}
+
+
+
+
+
org.springframework.boot
spring-boot-maven-plugin
- 2.5.2
diff --git a/src/main/java/ovh/excale/vgreeter/VGreeterApplication.java b/src/main/java/ovh/excale/vgreeter/VGreeterApplication.java
index 05c7c5d..bfa68a2 100644
--- a/src/main/java/ovh/excale/vgreeter/VGreeterApplication.java
+++ b/src/main/java/ovh/excale/vgreeter/VGreeterApplication.java
@@ -1,8 +1,8 @@
package ovh.excale.vgreeter;
+import lombok.extern.log4j.Log4j2;
import org.jetbrains.annotations.NotNull;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import org.jetbrains.annotations.Nullable;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.Banner;
@@ -11,11 +11,14 @@
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
+import org.springframework.context.ConfigurableApplicationContext;
+@Log4j2
@SpringBootApplication
public class VGreeterApplication implements CommandLineRunner, ApplicationContextAware {
- private static ApplicationContext ctx;
+ private static Boolean maintenance;
+ private static ConfigurableApplicationContext ctx;
public static void main(String[] args) {
@@ -25,26 +28,64 @@ public static void main(String[] args) {
}
+ // TODO: DON'T CONNECT TO DB DURING MAINTENANCE
+
+ // keep previous maintenance state
+ public static void restart(@Nullable final Runnable then, @Nullable final Boolean maintenance) {
+
+ log.info("Restarting app");
+
+ Thread thread = new Thread(() -> {
+ ctx.close();
+
+ VGreeterApplication.maintenance = maintenance != null ? maintenance : VGreeterApplication.maintenance;
+
+ SpringApplication app = new SpringApplication(VGreeterApplication.class);
+ app.setBannerMode(Banner.Mode.OFF);
+ app.run();
+
+ if(then != null)
+ then.run();
+
+ });
+
+ thread.setDaemon(false);
+ thread.start();
+
+ }
+
+ public static void restart(@NotNull Runnable then) {
+ restart(then, null);
+ }
+
+ public static void restart(boolean maintenance) {
+ restart(null, maintenance);
+ }
+
+ public static boolean isInMaintenance() {
+ return maintenance;
+ }
+
public static ApplicationContext getApplicationContext() {
return ctx;
}
- public final Logger logger;
public final String version;
- public VGreeterApplication(@Value("${application.version}") String version) {
+ public VGreeterApplication(@Value("${application.version}") String version,
+ @Value("${env.MAINTENANCE:false}") boolean maintenance) {
this.version = version;
- logger = LoggerFactory.getLogger(VGreeterApplication.class);
+ VGreeterApplication.maintenance = maintenance;
}
@Override
public void run(String[] args) {
- logger.info("Running on version {}", version);
+ log.info("Running on version {}", version);
}
@Override
public void setApplicationContext(@NotNull ApplicationContext context) throws BeansException {
- ctx = context;
+ ctx = (ConfigurableApplicationContext) context;
}
}
diff --git a/src/main/java/ovh/excale/vgreeter/commands/AbstractCommand.java b/src/main/java/ovh/excale/vgreeter/commands/AbstractCommand.java
deleted file mode 100644
index b761b64..0000000
--- a/src/main/java/ovh/excale/vgreeter/commands/AbstractCommand.java
+++ /dev/null
@@ -1,52 +0,0 @@
-package ovh.excale.vgreeter.commands;
-
-import net.dv8tion.jda.api.events.interaction.SlashCommandEvent;
-import net.dv8tion.jda.api.hooks.EventListener;
-import net.dv8tion.jda.api.interactions.commands.build.CommandData;
-import net.dv8tion.jda.api.requests.restaction.interactions.ReplyAction;
-import org.jetbrains.annotations.Nullable;
-import ovh.excale.vgreeter.utilities.CommandBuilder;
-
-public abstract class AbstractCommand {
-
- private final String name;
- private final String description;
- private final CommandBuilder builder;
-
- protected AbstractCommand(String name, String description) {
- this.name = name;
- this.description = description;
-
- builder = CommandBuilder
- .create(name)
- .setDescription(description);
-
- }
-
- public abstract ReplyAction execute(SlashCommandEvent event);
-
- public String getName() {
- return name;
- }
-
- public String getDescription() {
- return description;
- }
-
- protected CommandBuilder getBuilder() {
- return builder;
- }
-
- public CommandData getData() {
- return builder.build();
- }
-
- public boolean hasListener() {
- return false;
- }
-
- public @Nullable EventListener getListener() {
- return null;
- }
-
-}
diff --git a/src/main/java/ovh/excale/vgreeter/commands/CommandRegister.java b/src/main/java/ovh/excale/vgreeter/commands/CommandRegister.java
deleted file mode 100644
index 0604d44..0000000
--- a/src/main/java/ovh/excale/vgreeter/commands/CommandRegister.java
+++ /dev/null
@@ -1,93 +0,0 @@
-package ovh.excale.vgreeter.commands;
-
-import net.dv8tion.jda.api.events.GenericEvent;
-import net.dv8tion.jda.api.events.interaction.SlashCommandEvent;
-import net.dv8tion.jda.api.exceptions.ErrorResponseException;
-import net.dv8tion.jda.api.hooks.EventListener;
-import net.dv8tion.jda.api.interactions.commands.build.CommandData;
-import net.dv8tion.jda.api.requests.restaction.interactions.ReplyAction;
-import org.jetbrains.annotations.NotNull;
-import org.springframework.stereotype.Service;
-
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-@Service
-public class CommandRegister {
-
- private final Map commands;
- private final Listener listener;
-
- private CommandRegister() {
- commands = new HashMap<>();
- listener = new Listener();
- }
-
- public CommandRegister register(AbstractCommand command) {
-
- commands.put(command.getName(), command);
- if(command.hasListener())
- listener.register(command.getListener());
-
- return this;
-
- }
-
- public CommandData[] getData() {
- return commands
- .values()
- .stream()
- .map(AbstractCommand::getData)
- .toArray(CommandData[]::new);
- }
-
- public Listener getListener() {
- return listener;
- }
-
- private class Listener implements EventListener {
-
- public Set commandListeners;
-
- protected Listener() {
- commandListeners = new HashSet<>();
- }
-
- protected void register(EventListener listener) {
- commandListeners.add(listener);
- }
-
- @Override
- public void onEvent(@NotNull GenericEvent genericEvent) {
-
- if(genericEvent instanceof SlashCommandEvent)
- onSlashCommand((SlashCommandEvent) genericEvent);
- else
- for(EventListener commandListener : commandListeners)
- commandListener.onEvent(genericEvent);
-
- }
-
- private void onSlashCommand(SlashCommandEvent event) {
-
- AbstractCommand command = commands.get(event.getName());
- ReplyAction action;
-
- if(command == null)
- action = event
- .reply("No such command")
- .setEphemeral(true);
- else
- action = command.execute(event);
-
- try {
- action.queue();
- } catch(ErrorResponseException ignored) {
- }
- }
-
- }
-
-}
diff --git a/src/main/java/ovh/excale/vgreeter/commands/TrackIndexCommand.java b/src/main/java/ovh/excale/vgreeter/commands/TrackIndexCommand.java
deleted file mode 100644
index a0bc3ec..0000000
--- a/src/main/java/ovh/excale/vgreeter/commands/TrackIndexCommand.java
+++ /dev/null
@@ -1,186 +0,0 @@
-package ovh.excale.vgreeter.commands;
-
-import net.dv8tion.jda.api.EmbedBuilder;
-import net.dv8tion.jda.api.entities.Emoji;
-import net.dv8tion.jda.api.events.interaction.ButtonClickEvent;
-import net.dv8tion.jda.api.events.interaction.SlashCommandEvent;
-import net.dv8tion.jda.api.hooks.EventListener;
-import net.dv8tion.jda.api.hooks.ListenerAdapter;
-import net.dv8tion.jda.api.interactions.commands.OptionMapping;
-import net.dv8tion.jda.api.interactions.commands.OptionType;
-import net.dv8tion.jda.api.interactions.components.Button;
-import net.dv8tion.jda.api.interactions.components.Component;
-import net.dv8tion.jda.api.requests.restaction.interactions.ReplyAction;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-import org.springframework.data.domain.Page;
-import org.springframework.data.domain.PageRequest;
-import org.springframework.data.domain.Sort;
-import ovh.excale.vgreeter.VGreeterApplication;
-import ovh.excale.vgreeter.models.TrackModel;
-import ovh.excale.vgreeter.repositories.TrackRepository;
-
-import java.awt.*;
-import java.util.Optional;
-import java.util.concurrent.TimeUnit;
-import java.util.stream.Collectors;
-
-public class TrackIndexCommand extends AbstractCommand {
-
- public static final String BUTTON_COMMAND = "trackindex:";
-
- private final TrackRepository trackRepo;
- private final ButtonClickListener buttonListener;
-
- public TrackIndexCommand() {
- super("trackindex", "List all the tracks");
- getBuilder().addOption("page", "Page number", OptionType.INTEGER);
-
- trackRepo = VGreeterApplication
- .getApplicationContext()
- .getBean(TrackRepository.class);
-
- buttonListener = new ButtonClickListener();
-
- }
-
- @Override
- public ReplyAction execute(SlashCommandEvent event) {
-
- int humanBasedPage = Optional
- .ofNullable(event.getOption("page"))
- .map(OptionMapping::getAsLong)
- .map(Long::intValue)
- .orElse(1);
-
- if(humanBasedPage < 1)
- return event
- .reply("Page option must be positive!")
- .setEphemeral(true);
-
- Page trackPage = trackRepo.findAll(PageRequest.of(humanBasedPage - 1,
- 15,
- Sort.by(Sort.Direction.ASC, "id")));
-
- if(trackPage.isEmpty())
- return event
- .reply("Empty page")
- .setEphemeral(true);
-
- EmbedBuilder eb = computeEmbed(trackPage);
-
- // TODO: CHECK PERMS AND MESSAGECHANNEL
- event
- .getChannel()
- .sendMessage(eb.build())
- .setActionRow(computeButtons(trackPage))
- .queueAfter(2, TimeUnit.SECONDS);
-
- String userMention = event
- .getUser()
- .getAsMention();
-
- return event.reply("Track Index requested by " + userMention);
-
- }
-
- @Override
- public boolean hasListener() {
- return true;
- }
-
- @Override
- public @Nullable EventListener getListener() {
- return buttonListener;
- }
-
- private @NotNull EmbedBuilder computeEmbed(Page trackPage) {
-
- return new EmbedBuilder()
- .setTitle("Track Index")
- .setFooter("Page " + (trackPage.getNumber() + 1) + "/" + trackPage.getTotalPages())
- .setColor(Color.BLUE)
- .setDescription(trackPage
- .getContent()
- .stream()
- .map(track -> "**#" + track.getId() + "** *" + track.getName() + "*")
- .collect(Collectors.joining("\n")));
-
- }
-
- private Component[] computeButtons(Page trackPage) {
-
- int zeroBasedPage = trackPage.getNumber();
-
- Button prevButton = Button
- .secondary(BUTTON_COMMAND + (zeroBasedPage), Emoji.fromUnicode("\u25C0"))
- .withDisabled(!trackPage.hasPrevious());
- Button nextButton = Button
- .secondary(BUTTON_COMMAND + (zeroBasedPage + 2), Emoji.fromUnicode("\u25B6"))
- .withDisabled(!trackPage.hasNext());
-
- return new Component[] { prevButton, nextButton };
-
- }
-
- // TODO: CLOSE INDEX BUTTON
- public class ButtonClickListener extends ListenerAdapter {
-
- @Override
- public void onButtonClick(@NotNull ButtonClickEvent event) {
-
- // TODO: COMPONENT_ID COMMAND CHECK
- String stringPage = event
- .getComponentId()
- .replace(BUTTON_COMMAND, "");
- int humanBasedPage;
-
- try {
-
- humanBasedPage = Integer.parseInt(stringPage);
-
- } catch(NumberFormatException e) {
- event
- .reply("Invalid button")
- .setEphemeral(true)
- .queue();
- return;
- }
-
- if(humanBasedPage < 1) {
- event
- .reply("Invalid button")
- .setEphemeral(true)
- .queue();
- return;
- }
-
- Page trackPage = trackRepo.findAll(PageRequest.of(humanBasedPage - 1,
- 15,
- Sort.by(Sort.Direction.ASC, "id")));
-
- if(trackPage.isEmpty()) {
- event
- .reply("Empty page")
- .setEphemeral(true)
- .queue();
- return;
- }
-
- EmbedBuilder eb = computeEmbed(trackPage);
-
- //noinspection ConstantConditions
- event
- .getMessage()
- .delete()
- .and(event
- .getChannel()
- .sendMessage(eb.build())
- .setActionRow(computeButtons(trackPage)))
- .queue();
-
- }
-
- }
-
-}
diff --git a/src/main/java/ovh/excale/vgreeter/commands/button/CloseEmbedCommand.java b/src/main/java/ovh/excale/vgreeter/commands/button/CloseEmbedCommand.java
new file mode 100644
index 0000000..e40af6b
--- /dev/null
+++ b/src/main/java/ovh/excale/vgreeter/commands/button/CloseEmbedCommand.java
@@ -0,0 +1,25 @@
+package ovh.excale.vgreeter.commands.button;
+
+import java.util.Collections;
+
+import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
+import net.dv8tion.jda.api.requests.RestAction;
+import org.jetbrains.annotations.NotNull;
+import ovh.excale.vgreeter.commands.core.AbstractButtonCommand;
+
+public class CloseEmbedCommand extends AbstractButtonCommand {
+
+ public CloseEmbedCommand() {
+ super("close", "Close and embed or a message");
+ }
+
+ @Override
+ public @NotNull RestAction> execute(@NotNull ButtonInteractionEvent event) {
+
+ return event.editMessage("Closed")
+ .setEmbeds(Collections.emptyList())
+ .setComponents(Collections.emptyList());
+
+ }
+
+}
diff --git a/src/main/java/ovh/excale/vgreeter/commands/button/TrackIndexButtonCommand.java b/src/main/java/ovh/excale/vgreeter/commands/button/TrackIndexButtonCommand.java
new file mode 100644
index 0000000..0c593be
--- /dev/null
+++ b/src/main/java/ovh/excale/vgreeter/commands/button/TrackIndexButtonCommand.java
@@ -0,0 +1,55 @@
+package ovh.excale.vgreeter.commands.button;
+
+import lombok.SneakyThrows;
+import lombok.extern.log4j.Log4j2;
+import net.dv8tion.jda.api.components.actionrow.ActionRow;
+import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
+import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback;
+import net.dv8tion.jda.api.requests.RestAction;
+import org.jetbrains.annotations.NotNull;
+import ovh.excale.vgreeter.commands.core.AbstractButtonCommand;
+import ovh.excale.vgreeter.commands.core.CommandOptions;
+import ovh.excale.vgreeter.track.TrackIndex;
+
+import java.util.Arrays;
+
+@Log4j2
+public class TrackIndexButtonCommand extends AbstractButtonCommand {
+
+ public TrackIndexButtonCommand() {
+ super("trackindex", "List all the tracks");
+ }
+
+ @SneakyThrows
+ @Override
+ public @NotNull RestAction> execute(ButtonInteractionEvent event) {
+
+ CommandOptions command = CommandOptions.fromJson(event.getComponentId());
+ //noinspection DuplicatedCode
+ TrackIndex index = new TrackIndex(command);
+
+ try {
+
+ index.fetch();
+
+ } catch(IllegalArgumentException e) {
+ return replyEphemeralWith(e.getMessage(), event);
+ } catch(Exception e) {
+ log.error(e.getMessage(), e);
+ return replyEphemeralWith("There has been an internal error", event);
+ }
+
+ if(index.isEmpty())
+ return replyEphemeralWith("Empty page", event);
+
+ return event.editMessageEmbeds(index.buildEmbed().build())
+ .setComponents(ActionRow.of(Arrays.asList(index.buildButtons())));
+
+ }
+
+ private static RestAction> replyEphemeralWith(String message, IReplyCallback event) {
+ return event.reply(message)
+ .setEphemeral(true);
+ }
+
+}
diff --git a/src/main/java/ovh/excale/vgreeter/commands/core/AbstractButtonCommand.java b/src/main/java/ovh/excale/vgreeter/commands/core/AbstractButtonCommand.java
new file mode 100644
index 0000000..bea6466
--- /dev/null
+++ b/src/main/java/ovh/excale/vgreeter/commands/core/AbstractButtonCommand.java
@@ -0,0 +1,37 @@
+package ovh.excale.vgreeter.commands.core;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import net.dv8tion.jda.api.events.GenericEvent;
+import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
+import net.dv8tion.jda.api.requests.RestAction;
+import org.jetbrains.annotations.NotNull;
+
+public abstract class AbstractButtonCommand extends AbstractCommand {
+
+ protected AbstractButtonCommand(String name, String description) {
+ super(name, description, ButtonInteractionEvent.class);
+ }
+
+ @Override
+ public abstract @NotNull RestAction> execute(@NotNull ButtonInteractionEvent event);
+
+ @Override
+ public boolean accepts(GenericEvent event) {
+
+ if(!(event instanceof ButtonInteractionEvent))
+ return false;
+
+ CommandOptions command;
+ try {
+
+ command = CommandOptions.fromJson(((ButtonInteractionEvent) event).getComponentId());
+
+ } catch(JsonProcessingException e) {
+ return false;
+ }
+
+ return name.equalsIgnoreCase(command.getCommand());
+
+ }
+
+}
diff --git a/src/main/java/ovh/excale/vgreeter/commands/core/AbstractCommand.java b/src/main/java/ovh/excale/vgreeter/commands/core/AbstractCommand.java
new file mode 100644
index 0000000..ea8918e
--- /dev/null
+++ b/src/main/java/ovh/excale/vgreeter/commands/core/AbstractCommand.java
@@ -0,0 +1,37 @@
+package ovh.excale.vgreeter.commands.core;
+
+import lombok.Getter;
+import net.dv8tion.jda.api.events.GenericEvent;
+import net.dv8tion.jda.api.hooks.EventListener;
+import net.dv8tion.jda.api.requests.RestAction;
+import org.jetbrains.annotations.Nullable;
+
+@Getter
+public abstract class AbstractCommand {
+
+ protected final String name;
+ protected final String description;
+
+ private final Class typeClass;
+
+ protected AbstractCommand(String name, String description, Class typeClass) {
+ this.name = name;
+ this.description = description;
+ this.typeClass = typeClass;
+ }
+
+ public abstract RestAction> execute(EventType event);
+
+ public abstract boolean accepts(GenericEvent eventType);
+
+ // TODO: implement method in individual commands
+ public /*abstract*/ boolean bypassesMaintenance() { return true; }
+
+ public boolean hasListener() {
+ return false;
+ }
+
+ public @Nullable EventListener getListener() {
+ return null;
+ }
+}
diff --git a/src/main/java/ovh/excale/vgreeter/commands/core/AbstractMessageCommand.java b/src/main/java/ovh/excale/vgreeter/commands/core/AbstractMessageCommand.java
new file mode 100644
index 0000000..d96ca01
--- /dev/null
+++ b/src/main/java/ovh/excale/vgreeter/commands/core/AbstractMessageCommand.java
@@ -0,0 +1,40 @@
+package ovh.excale.vgreeter.commands.core;
+
+import net.dv8tion.jda.api.events.GenericEvent;
+import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
+import net.dv8tion.jda.api.requests.RestAction;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Locale;
+
+public abstract class AbstractMessageCommand extends AbstractCommand {
+
+ public static final String PREFIX = "vg:";
+
+ protected AbstractMessageCommand(String name, String description) {
+ super(name, description, MessageReceivedEvent.class);
+ }
+
+ @Override
+ public abstract @Nullable RestAction> execute(@NotNull MessageReceivedEvent event);
+
+ public boolean accepts(GenericEvent event) {
+
+ if(!(event instanceof MessageReceivedEvent))
+ return false;
+
+ MessageReceivedEvent messageEvent = (MessageReceivedEvent) event;
+ if(messageEvent.isFromGuild())
+ return false;
+
+ String msgContent = messageEvent
+ .getMessage()
+ .getContentRaw()
+ .toLowerCase(Locale.ROOT);
+
+ return msgContent.startsWith(PREFIX + name);
+
+ }
+
+}
diff --git a/src/main/java/ovh/excale/vgreeter/commands/core/AbstractSlashCommand.java b/src/main/java/ovh/excale/vgreeter/commands/core/AbstractSlashCommand.java
new file mode 100644
index 0000000..76f4515
--- /dev/null
+++ b/src/main/java/ovh/excale/vgreeter/commands/core/AbstractSlashCommand.java
@@ -0,0 +1,36 @@
+package ovh.excale.vgreeter.commands.core;
+
+import lombok.Getter;
+import net.dv8tion.jda.api.events.GenericEvent;
+import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
+import net.dv8tion.jda.api.interactions.commands.build.CommandData;
+import net.dv8tion.jda.api.requests.RestAction;
+import org.jetbrains.annotations.NotNull;
+
+public abstract class AbstractSlashCommand extends AbstractCommand {
+
+ @Getter
+ private final CommandBuilder builder;
+
+ protected AbstractSlashCommand(String name, String description) {
+ super(name, description, SlashCommandInteractionEvent.class);
+
+ builder = CommandBuilder
+ .create(name)
+ .setDescription(description);
+
+ }
+
+ @Override
+ public abstract @NotNull RestAction> execute(SlashCommandInteractionEvent event);
+
+ @Override
+ public boolean accepts(GenericEvent event) {
+ return event instanceof SlashCommandInteractionEvent && name.equals(((SlashCommandInteractionEvent) event).getName());
+ }
+
+ public CommandData getData() {
+ return builder.build();
+ }
+
+}
diff --git a/src/main/java/ovh/excale/vgreeter/utilities/CommandBuilder.java b/src/main/java/ovh/excale/vgreeter/commands/core/CommandBuilder.java
similarity index 86%
rename from src/main/java/ovh/excale/vgreeter/utilities/CommandBuilder.java
rename to src/main/java/ovh/excale/vgreeter/commands/core/CommandBuilder.java
index 7933847..62989ba 100644
--- a/src/main/java/ovh/excale/vgreeter/utilities/CommandBuilder.java
+++ b/src/main/java/ovh/excale/vgreeter/commands/core/CommandBuilder.java
@@ -1,8 +1,10 @@
-package ovh.excale.vgreeter.utilities;
+package ovh.excale.vgreeter.commands.core;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
+import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData;
import net.dv8tion.jda.api.interactions.commands.build.SubcommandData;
+import net.dv8tion.jda.api.interactions.commands.build.Commands;
import java.util.LinkedList;
import java.util.List;
@@ -14,7 +16,7 @@ public static CommandBuilderPrototype create(String name) {
return new CommandBuilderPrototype(name);
}
- private final CommandData commandData;
+ private final SlashCommandData commandData;
private final List subcommands;
private SubcommandData currentSubcommand;
@@ -22,7 +24,7 @@ public static CommandBuilderPrototype create(String name) {
private CommandBuilder(String name, String description) {
- commandData = new CommandData(name, description);
+ commandData = Commands.slash(name, description);
subcommands = new LinkedList<>();
currentSubcommand = null;
@@ -68,7 +70,7 @@ public CommandBuilder subcommand(String name, String description) {
return this;
}
- public CommandData build() {
+ public SlashCommandData build() {
if(subcommand != null && subcommand)
commandData.addSubcommands(subcommands);
diff --git a/src/main/java/ovh/excale/vgreeter/commands/core/CommandKeyword.java b/src/main/java/ovh/excale/vgreeter/commands/core/CommandKeyword.java
new file mode 100644
index 0000000..2a57d2d
--- /dev/null
+++ b/src/main/java/ovh/excale/vgreeter/commands/core/CommandKeyword.java
@@ -0,0 +1,63 @@
+package ovh.excale.vgreeter.commands.core;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.KeyDeserializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.ser.std.StdKeySerializers.StringKeySerializer;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+public enum CommandKeyword {
+
+ // TODO: ADD OTHER PARAMETERS (e.g. track_id)
+ // TODO: CHANGE CASE TO snake_case (track_name)
+ TRACK_NAME("tn", "trackname"),
+ USER_ID("u", "user");
+
+ public static final String COMMAND = "cmd";
+ public static final String SUBCOMMAND = "scmd";
+ public static final String PAGE = "p";
+
+ private static final Map encodeRecord;
+ private static final Map decodeRecord;
+
+ static {
+
+ encodeRecord = new HashMap<>();
+ decodeRecord = new HashMap<>();
+
+ for(CommandKeyword keyword : values()) {
+ encodeRecord.put(keyword.ext, keyword.key);
+ decodeRecord.put(keyword.key, keyword.ext);
+ }
+ }
+
+ public final String key;
+ public final String ext;
+
+ CommandKeyword(String key, String ext) {
+ this.key = key;
+ this.ext = ext;
+ }
+
+ public static class KeywordDecoder extends KeyDeserializer {
+
+ @Override
+ public Object deserializeKey(String key, DeserializationContext deserializationContext) {
+ return decodeRecord.getOrDefault(key, key);
+ }
+
+ }
+
+ public static class KeywordEncoder extends StringKeySerializer {
+
+ @Override
+ public void serialize(Object ext, JsonGenerator g, SerializerProvider provider) throws IOException {
+ g.writeFieldName(encodeRecord.getOrDefault(ext.toString(), ext.toString()));
+ }
+
+ }
+}
diff --git a/src/main/java/ovh/excale/vgreeter/commands/core/CommandOptions.java b/src/main/java/ovh/excale/vgreeter/commands/core/CommandOptions.java
new file mode 100644
index 0000000..6eeb6b9
--- /dev/null
+++ b/src/main/java/ovh/excale/vgreeter/commands/core/CommandOptions.java
@@ -0,0 +1,95 @@
+package ovh.excale.vgreeter.commands.core;
+
+import com.fasterxml.jackson.annotation.*;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectReader;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+import ovh.excale.vgreeter.commands.core.CommandKeyword.KeywordDecoder;
+import ovh.excale.vgreeter.commands.core.CommandKeyword.KeywordEncoder;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+
+import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_EMPTY;
+
+@JsonInclude(NON_EMPTY)
+@Accessors(chain = true)
+@Getter
+@Setter
+public class CommandOptions {
+
+ private static final ObjectMapper objWriter = new ObjectMapper();
+ private static final ObjectReader objReader = objWriter.readerFor(CommandOptions.class);
+
+ public static CommandOptions fromJson(String payload) throws JsonProcessingException {
+ return objReader.readValue(payload);
+ }
+
+ @JsonProperty(CommandKeyword.COMMAND)
+ private final String command;
+
+ @JsonProperty(CommandKeyword.SUBCOMMAND)
+ private final String subcommand;
+
+ @JsonProperty(CommandKeyword.PAGE)
+ private Integer page;
+
+ @Getter(AccessLevel.NONE)
+ @Setter(AccessLevel.NONE)
+ @JsonAnySetter
+ @JsonAnyGetter
+ @JsonSerialize(keyUsing = KeywordEncoder.class)
+ @JsonDeserialize(keyUsing = KeywordDecoder.class)
+ private final Map extendedOptions;
+
+ public CommandOptions(String command) {
+ this(command, null);
+ }
+
+ @JsonCreator
+ public CommandOptions(@JsonProperty(CommandKeyword.COMMAND) String command,
+ @JsonProperty(CommandKeyword.SUBCOMMAND) String subcommand) {
+ this.command = Objects.requireNonNull(command);
+ this.subcommand = subcommand;
+ extendedOptions = new HashMap<>();
+ }
+
+ public boolean hasSubcommand() {
+ return subcommand != null && !subcommand.isEmpty();
+ }
+
+ @JsonIgnore
+ public int getPageSafe() {
+ return page != null ? page : 1;
+ }
+
+ public Optional getOption(String key) {
+ return Optional.ofNullable(extendedOptions.get(key));
+ }
+
+ public CommandOptions putOption(String key, Object value) {
+ extendedOptions.put(key, value.toString());
+ return this;
+ }
+
+ public String json() throws JsonProcessingException {
+ return objWriter.writeValueAsString(this);
+ }
+
+ @Override
+ public String toString() {
+ return "CommandOptions{" + "command='" + command + '\'' + ", subcommand='" + subcommand + '\'' + ", page=" +
+ page + ", extendedOptions=" + extendedOptions + '}';
+ }
+
+}
+
+
diff --git a/src/main/java/ovh/excale/vgreeter/commands/core/CommandRegister.java b/src/main/java/ovh/excale/vgreeter/commands/core/CommandRegister.java
new file mode 100644
index 0000000..48c8e55
--- /dev/null
+++ b/src/main/java/ovh/excale/vgreeter/commands/core/CommandRegister.java
@@ -0,0 +1,105 @@
+package ovh.excale.vgreeter.commands.core;
+
+import lombok.SneakyThrows;
+import net.dv8tion.jda.api.events.GenericEvent;
+import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
+import net.dv8tion.jda.api.hooks.EventListener;
+import net.dv8tion.jda.api.interactions.commands.build.CommandData;
+import net.dv8tion.jda.api.requests.RestAction;
+import org.jetbrains.annotations.NotNull;
+import org.springframework.stereotype.Service;
+
+import java.lang.reflect.Method;
+import java.util.*;
+import java.util.stream.Stream;
+
+@Service
+public class CommandRegister {
+
+ private final ListenerRegister listenerRegister;
+ private final Map, Set extends AbstractCommand>>> masterRecord;
+
+ private CommandRegister() {
+ listenerRegister = new ListenerRegister();
+ masterRecord = new HashMap<>();
+ }
+
+ public > CommandRegister register(C command) {
+
+ Class extends GenericEvent> commandType = command.getTypeClass();
+ //noinspection unchecked
+ Set commandSet = (Set) masterRecord.computeIfAbsent(commandType, k -> new HashSet<>());
+
+ commandSet.add(command);
+ if(command.hasListener())
+ listenerRegister.register(command.getListener());
+
+ return this;
+
+ }
+
+ public CommandData[] getSlashCommandsData() {
+
+ //noinspection unchecked
+ return Optional.ofNullable((Set) masterRecord.get(SlashCommandInteractionEvent.class))
+ .map(Collection::stream)
+ .orElseGet(Stream::empty)
+ .map(AbstractSlashCommand::getData)
+ .toArray(CommandData[]::new);
+
+ }
+
+ public ListenerRegister getListener() {
+ return listenerRegister;
+ }
+
+ private class ListenerRegister implements EventListener {
+
+ public Set commandListeners;
+
+ protected ListenerRegister() {
+ commandListeners = new HashSet<>();
+ }
+
+ protected void register(EventListener listener) {
+ commandListeners.add(listener);
+ }
+
+ @SneakyThrows
+ @Override
+ public void onEvent(@NotNull GenericEvent event) {
+
+ AbstractCommand> abstractCommand = masterRecord.entrySet()
+ .stream()
+ .filter(entry -> entry.getKey()
+ .isInstance(event))
+ .map(Map.Entry::getValue)
+ .flatMap(Collection::stream)
+ .filter(command -> command.accepts(event))
+ .findFirst()
+ .orElse(null);
+
+ if(abstractCommand != null) {
+
+ //noinspection OptionalGetWithoutIsPresent
+ Method execMethod = Arrays.stream(abstractCommand.getClass()
+ .getDeclaredMethods())
+ .filter(method -> method.getName()
+ .equals("execute"))
+ .findFirst()
+ .get();
+
+ Optional.ofNullable(execMethod.invoke(abstractCommand, event))
+ .map(result -> ((RestAction>) result))
+ .ifPresent(RestAction::queue);
+
+ }
+
+ for(EventListener commandListener : commandListeners)
+ commandListener.onEvent(event);
+
+ }
+
+ }
+
+}
diff --git a/src/main/java/ovh/excale/vgreeter/commands/message/RestartCommand.java b/src/main/java/ovh/excale/vgreeter/commands/message/RestartCommand.java
new file mode 100644
index 0000000..32517f0
--- /dev/null
+++ b/src/main/java/ovh/excale/vgreeter/commands/message/RestartCommand.java
@@ -0,0 +1,52 @@
+package ovh.excale.vgreeter.commands.message;
+
+import net.dv8tion.jda.api.JDA;
+import net.dv8tion.jda.api.entities.Message;
+import net.dv8tion.jda.api.entities.User;
+import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
+import net.dv8tion.jda.api.requests.RestAction;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import ovh.excale.vgreeter.VGreeterApplication;
+import ovh.excale.vgreeter.commands.core.AbstractMessageCommand;
+import ovh.excale.vgreeter.utilities.ArgumentsParser;
+
+// TODO: OWNER/MOD ONLY
+public class RestartCommand extends AbstractMessageCommand {
+
+ public RestartCommand() {
+ super("restart", "");
+ }
+
+ @Override
+ public @Nullable RestAction> execute(@NotNull final MessageReceivedEvent event) {
+
+ Message message = event.getMessage();
+ ArgumentsParser arguments = new ArgumentsParser(message.getContentRaw());
+
+ boolean maintenance = arguments
+ .getArgumentString(1)
+ .map("maintenance"::equalsIgnoreCase)
+ .orElse(false);
+ String restartMessage = maintenance ? "*Restarting on maintenance mode...*" : "*Restarting...*";
+
+ message.reply(restartMessage)
+ // Wait for message to be sent and then restart
+ .complete();
+
+ final long authorId = event
+ .getAuthor()
+ .getIdLong();
+
+ VGreeterApplication.restart(() -> VGreeterApplication
+ .getApplicationContext()
+ .getBean(JDA.class)
+ .retrieveUserById(authorId)
+ .flatMap(User::openPrivateChannel)
+ .flatMap(dm -> dm.sendMessage("*Done!*"))
+ .queue(), maintenance);
+
+ return null;
+ }
+
+}
diff --git a/src/main/java/ovh/excale/vgreeter/commands/message/TrackUploadCommand.java b/src/main/java/ovh/excale/vgreeter/commands/message/TrackUploadCommand.java
new file mode 100644
index 0000000..7e5d2c9
--- /dev/null
+++ b/src/main/java/ovh/excale/vgreeter/commands/message/TrackUploadCommand.java
@@ -0,0 +1,155 @@
+package ovh.excale.vgreeter.commands.message;
+
+import lombok.extern.log4j.Log4j2;
+import net.dv8tion.jda.api.entities.Message;
+import net.dv8tion.jda.api.entities.User;
+import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
+import net.dv8tion.jda.api.requests.RestAction;
+import org.gagravarr.ogg.OggFile;
+import org.gagravarr.opus.OpusFile;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import ovh.excale.vgreeter.VGreeterApplication;
+import ovh.excale.vgreeter.commands.core.AbstractMessageCommand;
+import ovh.excale.vgreeter.models.TrackModel;
+import ovh.excale.vgreeter.models.UserModel;
+import ovh.excale.vgreeter.repositories.TrackRepository;
+import ovh.excale.vgreeter.repositories.UserRepository;
+import ovh.excale.vgreeter.services.TrackService;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+import java.util.Optional;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+@Log4j2
+public class TrackUploadCommand extends AbstractMessageCommand {
+
+ private static final Pattern TRACK_NAME_PATTERN = Pattern.compile("([\\w\\d-_]+)\\.opus");
+
+ private final UserRepository userRepo;
+ private final TrackService trackService;
+
+ public TrackUploadCommand() {
+ super("upload", "");
+
+ userRepo = VGreeterApplication
+ .getApplicationContext()
+ .getBean(UserRepository.class);
+
+ trackService = VGreeterApplication
+ .getApplicationContext()
+ .getBean(TrackService.class);
+
+ }
+
+ @Override
+ public @Nullable RestAction> execute(@NotNull MessageReceivedEvent event) {
+
+ // TODO: COMMAND PARAMETERS
+
+ User user = event.getAuthor();
+ if(user.isBot())
+ return null;
+
+ Message message = event.getMessage();
+ Optional opt = userRepo.findById(user.getIdLong());
+
+ boolean hasNotAltname = !opt.isPresent() || opt
+ .get()
+ .getAltname() == null;
+
+ if(hasNotAltname)
+ return message.reply("You must set an `/altname` first to upload a track");
+
+ UserModel userModel = opt.get();
+
+ List attachments = message.getAttachments();
+ if(attachments.isEmpty())
+ return message.reply("The track must be **opus encoded**");
+
+ Message.Attachment attachment = attachments.get(0);
+ String filename = attachment.getFileName();
+ int size = attachment.getSize();
+
+ if(size > TrackService.DEFAULT_MAX_TRACK_SIZE)
+ return message.reply("The file is too big (Max. " + TrackService.DEFAULT_MAX_TRACK_SIZE + ")");
+
+ Matcher filenameMatcher = TRACK_NAME_PATTERN.matcher(filename.toLowerCase(Locale.ROOT));
+ if(!filenameMatcher.matches())
+ return message.reply("Filename or extension invalid (filename must be alphanumeric" +
+ " and can only contain *dashes* `-` and *underscores* `_`, extension must be `.opus`)");
+
+ InputStream in;
+ try {
+
+ in = attachment
+ .getProxy()
+ .download()
+ .join();
+
+ } catch(Exception e) {
+
+ log.warn("Error while retrieving Track InputStream", e);
+ return message.reply("There has been an internal error while computing the file, please retry. " +
+ "If the error persists, contact a developer");
+
+ }
+
+ byte[] data = new byte[size];
+ try {
+
+ int read = 0, c;
+ do {
+
+ c = in.read(data, read, size - read);
+ if(c > 0)
+ read += c;
+
+ } while(c > 0);
+
+ in.close();
+
+ if(read != size) {
+ log.warn("Size mismatch while reading InputStream. Expected size: " + size + ", read: " + read);
+ data = Arrays.copyOfRange(data, 0, read);
+ }
+
+ new OpusFile(new OggFile(new ByteArrayInputStream(data)));
+
+ // TODO: USE TRACK_SERVICE
+ TrackRepository trackRepo = trackService.getTrackRepo();
+ String trackName = filenameMatcher.group(1);
+
+ if(trackRepo.existsByNameAndUploader(trackName, userModel))
+ return message.reply("You've already uploaded a track with the same name");
+
+ TrackModel track = TrackModel
+ .builder()
+ .name(filenameMatcher.group(1))
+ .uploader(userModel)
+ .size((long) data.length)
+ .data(data)
+ .build();
+ trackRepo.save(track);
+
+ } catch(IOException e) {
+
+ log.warn("Error while reading Track InputStream", e);
+ return message.reply("There has been an internal error while computing the file, please retry. " +
+ "If the error persists, contact a developer");
+
+ } catch(IllegalArgumentException e) {
+ // Not an opus track
+ return message.reply("The track is not **opus-encoded**.");
+ }
+
+ return message.reply("Track successfully inserted!");
+ }
+
+}
diff --git a/src/main/java/ovh/excale/vgreeter/commands/AltnameCommand.java b/src/main/java/ovh/excale/vgreeter/commands/slash/AltnameCommand.java
similarity index 78%
rename from src/main/java/ovh/excale/vgreeter/commands/AltnameCommand.java
rename to src/main/java/ovh/excale/vgreeter/commands/slash/AltnameCommand.java
index f1120d1..6e7eed7 100644
--- a/src/main/java/ovh/excale/vgreeter/commands/AltnameCommand.java
+++ b/src/main/java/ovh/excale/vgreeter/commands/slash/AltnameCommand.java
@@ -1,18 +1,19 @@
-package ovh.excale.vgreeter.commands;
+package ovh.excale.vgreeter.commands.slash;
import net.dv8tion.jda.api.entities.User;
-import net.dv8tion.jda.api.events.interaction.SlashCommandEvent;
+import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType;
-import net.dv8tion.jda.api.requests.restaction.interactions.ReplyAction;
+import net.dv8tion.jda.api.requests.RestAction;
import ovh.excale.vgreeter.VGreeterApplication;
+import ovh.excale.vgreeter.commands.core.AbstractSlashCommand;
import ovh.excale.vgreeter.models.UserModel;
import ovh.excale.vgreeter.repositories.UserRepository;
import java.util.Optional;
import java.util.regex.Pattern;
-public class AltnameCommand extends AbstractCommand {
+public class AltnameCommand extends AbstractSlashCommand {
private static final Pattern ALTNAME_PATTERN = Pattern.compile("[\\w\\d-_]{3,}");
@@ -30,9 +31,9 @@ public AltnameCommand() {
}
@Override
- public ReplyAction execute(SlashCommandEvent event) {
+ public RestAction> execute(SlashCommandInteractionEvent event) {
- ReplyAction reply;
+ RestAction> reply;
User user = event.getUser();
String name = Optional.ofNullable(event.getOption("username"))
@@ -47,7 +48,10 @@ public ReplyAction execute(SlashCommandEvent event) {
if(!userRepo.existsByAltname(name)) {
UserModel userModel = userRepo.findById(user.getIdLong())
- .orElseGet(() -> new UserModel(user.getIdLong()));
+ .orElseGet(() -> UserModel
+ .builder()
+ .snowflake(user.getIdLong())
+ .build());
userModel.setAltname(name);
userRepo.save(userModel);
diff --git a/src/main/java/ovh/excale/vgreeter/commands/PlaytestCommand.java b/src/main/java/ovh/excale/vgreeter/commands/slash/PlaytestCommand.java
similarity index 75%
rename from src/main/java/ovh/excale/vgreeter/commands/PlaytestCommand.java
rename to src/main/java/ovh/excale/vgreeter/commands/slash/PlaytestCommand.java
index 46c996b..3be7ae1 100644
--- a/src/main/java/ovh/excale/vgreeter/commands/PlaytestCommand.java
+++ b/src/main/java/ovh/excale/vgreeter/commands/slash/PlaytestCommand.java
@@ -1,27 +1,26 @@
-package ovh.excale.vgreeter.commands;
+package ovh.excale.vgreeter.commands.slash;
+import lombok.extern.log4j.Log4j2;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
-import net.dv8tion.jda.api.entities.VoiceChannel;
-import net.dv8tion.jda.api.events.interaction.SlashCommandEvent;
+import net.dv8tion.jda.api.entities.channel.unions.AudioChannelUnion;
+import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.exceptions.InsufficientPermissionException;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.managers.AudioManager;
-import net.dv8tion.jda.api.requests.restaction.interactions.ReplyAction;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import net.dv8tion.jda.api.requests.RestAction;
import ovh.excale.vgreeter.VGreeterApplication;
+import ovh.excale.vgreeter.commands.core.AbstractSlashCommand;
import ovh.excale.vgreeter.models.TrackModel;
import ovh.excale.vgreeter.repositories.TrackRepository;
import ovh.excale.vgreeter.services.DiscordService;
-import ovh.excale.vgreeter.utilities.TrackPlayer;
+import ovh.excale.vgreeter.track.TrackPlayer;
import java.util.Optional;
import java.util.Set;
-public class PlaytestCommand extends AbstractCommand {
-
- private static final Logger logger = LoggerFactory.getLogger(PlaytestCommand.class);
+@Log4j2
+public class PlaytestCommand extends AbstractSlashCommand {
public PlaytestCommand() {
super("playtest", "Test a track");
@@ -32,7 +31,7 @@ public PlaytestCommand() {
}
@Override
- public ReplyAction execute(SlashCommandEvent event) {
+ public RestAction> execute(SlashCommandInteractionEvent event) {
Guild guild = event.getGuild();
Member member = event.getMember();
@@ -47,7 +46,7 @@ public ReplyAction execute(SlashCommandEvent event) {
.setEphemeral(true);
//noinspection ConstantConditions
- VoiceChannel channel = member.getVoiceState()
+ AudioChannelUnion channel = member.getVoiceState()
.getChannel();
if(channel == null)
@@ -70,7 +69,7 @@ public ReplyAction execute(SlashCommandEvent event) {
TrackPlayer trackPlayer = new TrackPlayer(opt.get());
if(!trackPlayer.canProvide()) {
- logger.error("TrackPlayer cannot provide");
+ log.error("TrackPlayer cannot provide");
return event.reply("There has been an internal error, retry or contact a developer.")
.setEphemeral(true);
}
@@ -83,6 +82,8 @@ public ReplyAction execute(SlashCommandEvent event) {
audioManager.openAudioConnection(channel);
guildLocks.add(guild.getIdLong());
} catch(InsufficientPermissionException ignored) {
+ return event.reply("The bot is missing permission to connect or speak in that voice channel")
+ .setEphemeral(true);
}
return event.reply("Playing track `#" + trackId + "`")
diff --git a/src/main/java/ovh/excale/vgreeter/commands/ProbabilityCommand.java b/src/main/java/ovh/excale/vgreeter/commands/slash/ProbabilityCommand.java
similarity index 84%
rename from src/main/java/ovh/excale/vgreeter/commands/ProbabilityCommand.java
rename to src/main/java/ovh/excale/vgreeter/commands/slash/ProbabilityCommand.java
index f2bb88c..f143f1b 100644
--- a/src/main/java/ovh/excale/vgreeter/commands/ProbabilityCommand.java
+++ b/src/main/java/ovh/excale/vgreeter/commands/slash/ProbabilityCommand.java
@@ -1,19 +1,20 @@
-package ovh.excale.vgreeter.commands;
+package ovh.excale.vgreeter.commands.slash;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
-import net.dv8tion.jda.api.events.interaction.SlashCommandEvent;
+import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType;
-import net.dv8tion.jda.api.requests.restaction.interactions.ReplyAction;
+import net.dv8tion.jda.api.requests.RestAction;
import ovh.excale.vgreeter.VGreeterApplication;
+import ovh.excale.vgreeter.commands.core.AbstractSlashCommand;
import ovh.excale.vgreeter.models.GuildModel;
import ovh.excale.vgreeter.repositories.GuildRepository;
import java.util.Optional;
-public class ProbabilityCommand extends AbstractCommand {
+public class ProbabilityCommand extends AbstractSlashCommand {
private final GuildRepository guildRepo;
@@ -32,7 +33,7 @@ public ProbabilityCommand() {
}
@Override
- public ReplyAction execute(SlashCommandEvent event) {
+ public RestAction> execute(SlashCommandInteractionEvent event) {
Guild guild = event.getGuild();
@@ -42,10 +43,13 @@ public ReplyAction execute(SlashCommandEvent event) {
Member member = event.getMember();
Optional opt = guildRepo.findById(guild.getIdLong());
- GuildModel guildModel = opt.orElseGet(() -> new GuildModel(guild.getIdLong()));
+ GuildModel guildModel = opt.orElseGet(() -> GuildModel
+ .builder()
+ .id(guild.getIdLong())
+ .build());
int prevProbab = guildModel.getJoinProbability();
- ReplyAction reply;
+ RestAction> reply;
String subcommand = Optional.ofNullable(event.getSubcommandName())
.orElse("");
diff --git a/src/main/java/ovh/excale/vgreeter/commands/slash/TrackDownloadCommand.java b/src/main/java/ovh/excale/vgreeter/commands/slash/TrackDownloadCommand.java
new file mode 100644
index 0000000..178a3e2
--- /dev/null
+++ b/src/main/java/ovh/excale/vgreeter/commands/slash/TrackDownloadCommand.java
@@ -0,0 +1,58 @@
+package ovh.excale.vgreeter.commands.slash;
+
+import net.dv8tion.jda.api.entities.Guild;
+import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
+import net.dv8tion.jda.api.interactions.commands.OptionType;
+import net.dv8tion.jda.api.requests.RestAction;
+import net.dv8tion.jda.api.utils.FileUpload;
+import ovh.excale.vgreeter.VGreeterApplication;
+import ovh.excale.vgreeter.commands.core.AbstractSlashCommand;
+import ovh.excale.vgreeter.models.TrackModel;
+import ovh.excale.vgreeter.repositories.TrackRepository;
+
+import java.util.Optional;
+
+public class TrackDownloadCommand extends AbstractSlashCommand {
+
+ private final TrackRepository trackRepo;
+
+ public TrackDownloadCommand() {
+ super("trackdownload", "download command placeholder");
+
+ this.getBuilder()
+ .addOptionRequired("trackid", "The track to download", OptionType.INTEGER);
+
+ trackRepo = VGreeterApplication.getApplicationContext()
+ .getBean(TrackRepository.class);
+
+ }
+
+ // TODO: 30sec cooldown (whole-guild scope) on download, probably with stopwatch and queue
+
+ @Override
+ public RestAction> execute(SlashCommandInteractionEvent event) {
+
+ Guild guild = event.getGuild();
+
+ if(guild == null)
+ return event.reply("This command can be executed in a guild only")
+ .setEphemeral(true);
+
+ //noinspection ConstantConditions
+ long trackId = Long.parseLong(event.getOption("trackid")
+ .getAsString());
+
+ Optional opt = trackRepo.findById(trackId);
+
+ if(!opt.isPresent())
+ return event.reply("No track with such id")
+ .setEphemeral(true);
+
+ TrackModel track = opt.get();
+
+ return event.reply(String.format("Track `#%d`", track.getId()))
+ .addFiles(FileUpload.fromData(track.getData(), track.getName() + ".opus"));
+
+ }
+
+}
diff --git a/src/main/java/ovh/excale/vgreeter/commands/slash/TrackIndexSlashCommand.java b/src/main/java/ovh/excale/vgreeter/commands/slash/TrackIndexSlashCommand.java
new file mode 100644
index 0000000..3ee1c74
--- /dev/null
+++ b/src/main/java/ovh/excale/vgreeter/commands/slash/TrackIndexSlashCommand.java
@@ -0,0 +1,84 @@
+package ovh.excale.vgreeter.commands.slash;
+
+import lombok.extern.log4j.Log4j2;
+import net.dv8tion.jda.api.components.actionrow.ActionRow;
+import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
+import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback;
+import net.dv8tion.jda.api.interactions.commands.OptionMapping;
+import net.dv8tion.jda.api.interactions.commands.OptionType;
+import net.dv8tion.jda.api.requests.RestAction;
+import org.jetbrains.annotations.NotNull;
+import ovh.excale.vgreeter.commands.core.AbstractSlashCommand;
+import ovh.excale.vgreeter.commands.core.CommandOptions;
+import ovh.excale.vgreeter.track.TrackIndex;
+
+import java.util.Arrays;
+import java.util.Optional;
+
+import static ovh.excale.vgreeter.commands.core.CommandKeyword.TRACK_NAME;
+import static ovh.excale.vgreeter.commands.core.CommandKeyword.USER_ID;
+import static ovh.excale.vgreeter.track.TrackIndex.*;
+
+@Log4j2
+public class TrackIndexSlashCommand extends AbstractSlashCommand {
+
+ public TrackIndexSlashCommand() {
+ super("trackindex", "List all the tracks");
+
+ getBuilder()
+ // [SUB] all
+ .subcommand(FILTER_ALL, "Search for all tracks")
+ .addOption("page", "Page number", OptionType.INTEGER)
+ // [SUB] name
+ .subcommand(FILTER_NAME, "Search for all tracks with something in the name")
+ .addOptionRequired(TRACK_NAME.ext, "Track name", OptionType.STRING)
+ .addOption("page", "Page number", OptionType.INTEGER)
+ // [SUB] user
+ .subcommand(FILTER_USER, "Search for tracks by a user")
+ .addOptionRequired(USER_ID.ext, "The user to query for", OptionType.USER)
+ .addOption("page", "Page number", OptionType.INTEGER);
+
+ }
+
+ @Override
+ public @NotNull RestAction> execute(SlashCommandInteractionEvent event) {
+
+ int page = Optional.ofNullable(event.getOption("page"))
+ .map(OptionMapping::getAsLong)
+ .map(Long::intValue)
+ .orElse(1);
+
+ CommandOptions command = new CommandOptions(event.getName(), event.getSubcommandName()).setPage(page);
+ event.getOptions()
+ .stream()
+ .filter(option -> !"page".equals(option.getName()))
+ .forEach(option -> command.putOption(option.getName(), option.getAsString()));
+
+ //noinspection DuplicatedCode
+ TrackIndex index = new TrackIndex(command);
+ try {
+
+ index.fetch();
+
+ } catch(IllegalArgumentException e) {
+ return replyEphemeralWith(e.getMessage(), event);
+ } catch(Exception e) {
+ log.error(e.getMessage(), e);
+ return replyEphemeralWith("There has been an internal error", event);
+ }
+
+ if(index.isEmpty())
+ return replyEphemeralWith("Empty page", event);
+
+ return event.replyEmbeds(index.buildEmbed().build())
+ .setEphemeral(true)
+ .addComponents(ActionRow.of(Arrays.asList(index.buildButtons())));
+
+ }
+
+ private static RestAction> replyEphemeralWith(String message, IReplyCallback event) {
+ return event.reply(message)
+ .setEphemeral(true);
+ }
+
+}
diff --git a/src/main/java/ovh/excale/vgreeter/commands/TracknameCommand.java b/src/main/java/ovh/excale/vgreeter/commands/slash/TrackNameCommand.java
similarity index 82%
rename from src/main/java/ovh/excale/vgreeter/commands/TracknameCommand.java
rename to src/main/java/ovh/excale/vgreeter/commands/slash/TrackNameCommand.java
index ef439f0..e437a6f 100644
--- a/src/main/java/ovh/excale/vgreeter/commands/TracknameCommand.java
+++ b/src/main/java/ovh/excale/vgreeter/commands/slash/TrackNameCommand.java
@@ -1,10 +1,11 @@
-package ovh.excale.vgreeter.commands;
+package ovh.excale.vgreeter.commands.slash;
import net.dv8tion.jda.api.entities.User;
-import net.dv8tion.jda.api.events.interaction.SlashCommandEvent;
+import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.interactions.commands.OptionType;
-import net.dv8tion.jda.api.requests.restaction.interactions.ReplyAction;
+import net.dv8tion.jda.api.requests.RestAction;
import ovh.excale.vgreeter.VGreeterApplication;
+import ovh.excale.vgreeter.commands.core.AbstractSlashCommand;
import ovh.excale.vgreeter.models.TrackModel;
import ovh.excale.vgreeter.models.UserModel;
import ovh.excale.vgreeter.repositories.TrackRepository;
@@ -12,13 +13,13 @@
import java.util.Optional;
import java.util.regex.Pattern;
-public class TracknameCommand extends AbstractCommand {
+public class TrackNameCommand extends AbstractSlashCommand {
private static final Pattern TRACKNAME_PATTERN = Pattern.compile("[\\w\\d-_]+");
private final TrackRepository trackRepo;
- public TracknameCommand() {
+ public TrackNameCommand() {
super("trackname", "Edit the name of a track");
this.getBuilder()
@@ -32,7 +33,7 @@ public TracknameCommand() {
}
@Override
- public ReplyAction execute(SlashCommandEvent event) {
+ public RestAction> execute(SlashCommandInteractionEvent event) {
//noinspection ConstantConditions
long trackId = Long.parseLong(event.getOption("trackid")
diff --git a/src/main/java/ovh/excale/vgreeter/commands/slash/TrackRemoveCommand.java b/src/main/java/ovh/excale/vgreeter/commands/slash/TrackRemoveCommand.java
new file mode 100644
index 0000000..ad00c7a
--- /dev/null
+++ b/src/main/java/ovh/excale/vgreeter/commands/slash/TrackRemoveCommand.java
@@ -0,0 +1,69 @@
+package ovh.excale.vgreeter.commands.slash;
+
+import net.dv8tion.jda.api.entities.User;
+import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
+import net.dv8tion.jda.api.interactions.commands.OptionType;
+import net.dv8tion.jda.api.requests.RestAction;
+import ovh.excale.vgreeter.VGreeterApplication;
+import ovh.excale.vgreeter.commands.core.AbstractSlashCommand;
+import ovh.excale.vgreeter.models.TrackModel;
+import ovh.excale.vgreeter.repositories.TrackRepository;
+
+import java.util.Optional;
+
+public class TrackRemoveCommand extends AbstractSlashCommand {
+
+ private final TrackRepository trackRepo;
+
+ public TrackRemoveCommand() {
+ super("trackremove", "Delete a track");
+ this
+ .getBuilder()
+ .addOptionRequired("trackid", "The track to remove", OptionType.INTEGER);
+
+ trackRepo = VGreeterApplication
+ .getApplicationContext()
+ .getBean(TrackRepository.class);
+
+ }
+
+ @Override
+ public RestAction> execute(SlashCommandInteractionEvent event) {
+
+ RestAction> reply;
+ User user = event.getUser();
+
+ //noinspection ConstantConditions
+ long trackId = event
+ .getOption("trackid")
+ .getAsLong();
+
+ Optional opt = trackRepo.findById(trackId);
+ if(!opt.isPresent())
+ reply = event
+ .reply("No track with such id")
+ .setEphemeral(true);
+ else {
+
+ TrackModel track = opt.get();
+ Long userId = user.getIdLong();
+
+ if(!userId.equals(track.getUploaderId()))
+ reply = event
+ .reply("You're not the uploader of track `" + trackId + "`")
+ .setEphemeral(true);
+ else {
+
+ trackRepo.delete(track);
+ reply = event
+ .reply("Track `" + trackId + "` deleted successfully")
+ .setEphemeral(true);
+
+ }
+
+ }
+
+ return reply;
+ }
+
+}
diff --git a/src/main/java/ovh/excale/vgreeter/commands/UploadCommand.java b/src/main/java/ovh/excale/vgreeter/commands/slash/UploadHelpCommand.java
similarity index 68%
rename from src/main/java/ovh/excale/vgreeter/commands/UploadCommand.java
rename to src/main/java/ovh/excale/vgreeter/commands/slash/UploadHelpCommand.java
index 56ffc2d..4948774 100644
--- a/src/main/java/ovh/excale/vgreeter/commands/UploadCommand.java
+++ b/src/main/java/ovh/excale/vgreeter/commands/slash/UploadHelpCommand.java
@@ -1,16 +1,17 @@
-package ovh.excale.vgreeter.commands;
+package ovh.excale.vgreeter.commands.slash;
-import net.dv8tion.jda.api.events.interaction.SlashCommandEvent;
-import net.dv8tion.jda.api.requests.restaction.interactions.ReplyAction;
+import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
+import net.dv8tion.jda.api.requests.RestAction;
+import ovh.excale.vgreeter.commands.core.AbstractSlashCommand;
-public class UploadCommand extends AbstractCommand {
+public class UploadHelpCommand extends AbstractSlashCommand {
- public UploadCommand() {
+ public UploadHelpCommand() {
super("upload", "Show help to upload a track");
}
@Override
- public ReplyAction execute(SlashCommandEvent event) {
+ public RestAction> execute(SlashCommandInteractionEvent event) {
//noinspection StringBufferReplaceableByString
StringBuilder sb = new StringBuilder();
diff --git a/src/main/java/ovh/excale/vgreeter/models/GuildModel.java b/src/main/java/ovh/excale/vgreeter/models/GuildModel.java
index 08a4d74..4cae578 100644
--- a/src/main/java/ovh/excale/vgreeter/models/GuildModel.java
+++ b/src/main/java/ovh/excale/vgreeter/models/GuildModel.java
@@ -1,10 +1,18 @@
package ovh.excale.vgreeter.models;
-import javax.persistence.Column;
-import javax.persistence.Entity;
-import javax.persistence.Id;
-import javax.persistence.Table;
-
+import lombok.*;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.Id;
+import jakarta.persistence.Table;
+
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+@Setter
+@Getter
+@ToString
@Entity
@Table(name = "guild")
public class GuildModel {
@@ -15,34 +23,9 @@ public class GuildModel {
@Column(name = "id_guild")
private Long id;
+ @Builder.Default
@Column(name = "join_probability")
- private Integer joinProbability;
-
- public GuildModel() {
- joinProbability = DEFAULT_JOIN_PROBABILITY;
- }
-
- public GuildModel(Long id) {
- this.id = id;
- joinProbability = DEFAULT_JOIN_PROBABILITY;
- }
-
- public Long getId() {
- return id;
- }
-
- public GuildModel setId(Long id) {
- this.id = id;
- return this;
- }
-
- public Integer getJoinProbability() {
- return joinProbability;
- }
-
- public GuildModel setJoinProbability(Integer joinProbability) {
- this.joinProbability = joinProbability;
- return this;
- }
+ private Integer joinProbability = DEFAULT_JOIN_PROBABILITY;
}
+
diff --git a/src/main/java/ovh/excale/vgreeter/models/TrackModel.java b/src/main/java/ovh/excale/vgreeter/models/TrackModel.java
index ab760c4..2164565 100644
--- a/src/main/java/ovh/excale/vgreeter/models/TrackModel.java
+++ b/src/main/java/ovh/excale/vgreeter/models/TrackModel.java
@@ -1,12 +1,19 @@
package ovh.excale.vgreeter.models;
+import lombok.*;
import org.gagravarr.ogg.OggPacketReader;
-import javax.persistence.*;
+import jakarta.persistence.*;
import java.io.ByteArrayInputStream;
import java.sql.Timestamp;
import java.time.Instant;
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+@Setter
+@Getter
+@ToString
@Entity
@Table(name = "track")
public class TrackModel {
@@ -23,76 +30,25 @@ public class TrackModel {
@Basic
private Long size;
+ @Builder.Default
@Basic
- private Timestamp uploadDate;
+ private Timestamp uploadDate = Timestamp.from(Instant.now());
+ @ToString.Exclude
@Basic(fetch = FetchType.LAZY)
private byte[] data;
+ @ToString.Exclude
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "uploader_id")
private UserModel uploader;
- public TrackModel() {
- uploadDate = Timestamp.from(Instant.now());
- }
-
- public Long getId() {
- return id;
- }
-
- public TrackModel setId(Long id) {
- this.id = id;
- return this;
- }
-
- public String getName() {
- return name;
- }
-
- public TrackModel setName(String name) {
- this.name = name;
- return this;
- }
-
- public Long getSize() {
- return size;
- }
-
- public TrackModel setSize(Long size) {
- this.size = size;
- return this;
- }
-
- public byte[] getData() {
- return data;
- }
-
- public TrackModel setData(byte[] data) {
- this.data = data;
- return this;
- }
-
- public UserModel getUploader() {
- return uploader;
- }
-
- public TrackModel setUploader(UserModel uploader) {
- this.uploader = uploader;
- return this;
- }
-
- public Timestamp getUploadDate() {
- return uploadDate;
- }
-
- public TrackModel setUploadDate(Timestamp uploadDate) {
- this.uploadDate = uploadDate;
- return this;
- }
+ @Column(name = "uploader_id", insertable = false, updatable = false)
+ private Long uploaderId;
public OggPacketReader getPacketReader() {
return new OggPacketReader(new ByteArrayInputStream(getData()));
}
+
}
diff --git a/src/main/java/ovh/excale/vgreeter/models/UserModel.java b/src/main/java/ovh/excale/vgreeter/models/UserModel.java
index 463b273..fe81fdf 100644
--- a/src/main/java/ovh/excale/vgreeter/models/UserModel.java
+++ b/src/main/java/ovh/excale/vgreeter/models/UserModel.java
@@ -1,7 +1,16 @@
package ovh.excale.vgreeter.models;
-import javax.persistence.*;
+import lombok.*;
+import jakarta.persistence.*;
+import java.util.Set;
+
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+@Setter
+@Getter
+@ToString
@Entity
@Table(name = "\"user\"")
public class UserModel {
@@ -12,44 +21,14 @@ public class UserModel {
@Basic
private String altname;
+ @Builder.Default
@Basic
@Column(name = "tracks_max")
- private Integer trackMaxSize;
-
- public UserModel() {
- trackMaxSize = 64 * 1024;
- }
-
- public UserModel(long snowflake) {
- this.snowflake = snowflake;
- trackMaxSize = 64 * 1024;
- }
-
- public Long getSnowflake() {
- return snowflake;
- }
-
- public UserModel setSnowflake(Long snowflake) {
- this.snowflake = snowflake;
- return this;
- }
-
- public String getAltname() {
- return altname;
- }
-
- public UserModel setAltname(String altname) {
- this.altname = altname;
- return this;
- }
-
- public Integer getTrackMaxSize() {
- return trackMaxSize;
- }
-
- public UserModel setTrackMaxSize(Integer maxTracks) {
- this.trackMaxSize = maxTracks;
- return this;
- }
+ private Integer trackMaxSize = 64 * 1024;
+
+ @ToString.Exclude
+ @OneToMany(fetch = FetchType.LAZY, mappedBy = "uploader")
+ private Set tracks;
+
}
diff --git a/src/main/java/ovh/excale/vgreeter/repositories/GuildRepository.java b/src/main/java/ovh/excale/vgreeter/repositories/GuildRepository.java
index ef3a6e4..2ca7c51 100644
--- a/src/main/java/ovh/excale/vgreeter/repositories/GuildRepository.java
+++ b/src/main/java/ovh/excale/vgreeter/repositories/GuildRepository.java
@@ -1,10 +1,10 @@
package ovh.excale.vgreeter.repositories;
-import org.springframework.data.repository.CrudRepository;
+import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import ovh.excale.vgreeter.models.GuildModel;
@Repository
-public interface GuildRepository extends CrudRepository {
+public interface GuildRepository extends JpaRepository {
}
diff --git a/src/main/java/ovh/excale/vgreeter/repositories/TrackRepository.java b/src/main/java/ovh/excale/vgreeter/repositories/TrackRepository.java
index 874433d..8c978fa 100644
--- a/src/main/java/ovh/excale/vgreeter/repositories/TrackRepository.java
+++ b/src/main/java/ovh/excale/vgreeter/repositories/TrackRepository.java
@@ -1,13 +1,21 @@
package ovh.excale.vgreeter.repositories;
-import org.springframework.data.repository.PagingAndSortingRepository;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import ovh.excale.vgreeter.models.TrackModel;
import ovh.excale.vgreeter.models.UserModel;
@Repository
-public interface TrackRepository extends PagingAndSortingRepository {
+public interface TrackRepository extends JpaRepository {
boolean existsByNameAndUploader(String name, UserModel uploader);
+ @Query("select t from TrackModel t where lower(t.name) like lower(?1)")
+ Page findAllByNameQuery(String name, Pageable pageable);
+
+ Page findAllByUploaderIdIs(long userId, Pageable pageable);
+
}
diff --git a/src/main/java/ovh/excale/vgreeter/repositories/UserRepository.java b/src/main/java/ovh/excale/vgreeter/repositories/UserRepository.java
index 6f1c23e..662e412 100644
--- a/src/main/java/ovh/excale/vgreeter/repositories/UserRepository.java
+++ b/src/main/java/ovh/excale/vgreeter/repositories/UserRepository.java
@@ -1,11 +1,11 @@
package ovh.excale.vgreeter.repositories;
-import org.springframework.data.repository.CrudRepository;
+import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import ovh.excale.vgreeter.models.UserModel;
@Repository
-public interface UserRepository extends CrudRepository {
+public interface UserRepository extends JpaRepository {
boolean existsByAltname(String altname);
diff --git a/src/main/java/ovh/excale/vgreeter/services/DiscordEventHandlerService.java b/src/main/java/ovh/excale/vgreeter/services/DiscordEventHandlerService.java
deleted file mode 100644
index 29e8949..0000000
--- a/src/main/java/ovh/excale/vgreeter/services/DiscordEventHandlerService.java
+++ /dev/null
@@ -1,220 +0,0 @@
-package ovh.excale.vgreeter.services;
-
-import net.dv8tion.jda.api.entities.*;
-import net.dv8tion.jda.api.events.guild.voice.GuildVoiceJoinEvent;
-import net.dv8tion.jda.api.events.guild.voice.GuildVoiceLeaveEvent;
-import net.dv8tion.jda.api.events.message.priv.PrivateMessageReceivedEvent;
-import net.dv8tion.jda.api.exceptions.InsufficientPermissionException;
-import net.dv8tion.jda.api.hooks.ListenerAdapter;
-import net.dv8tion.jda.api.managers.AudioManager;
-import org.gagravarr.ogg.OggFile;
-import org.gagravarr.opus.OpusFile;
-import org.jetbrains.annotations.NotNull;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.stereotype.Service;
-import ovh.excale.vgreeter.models.GuildModel;
-import ovh.excale.vgreeter.models.TrackModel;
-import ovh.excale.vgreeter.models.UserModel;
-import ovh.excale.vgreeter.repositories.GuildRepository;
-import ovh.excale.vgreeter.repositories.TrackRepository;
-import ovh.excale.vgreeter.repositories.UserRepository;
-import ovh.excale.vgreeter.utilities.TrackPlayer;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.UncheckedIOException;
-import java.util.*;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-@Service
-public class DiscordEventHandlerService extends ListenerAdapter {
-
- private final static Logger logger = LoggerFactory.getLogger(DiscordEventHandlerService.class);
- private static final Pattern TRACK_NAME_PATTERN = Pattern.compile("([\\w\\d-_]+)\\.opus");
-
- private final UserRepository userRepo;
- private final GuildRepository guildRepo;
- private final TrackService trackService;
-
- private final Random random;
-
- public DiscordEventHandlerService(UserRepository userRepo, GuildRepository guildRepo, TrackService trackService) {
- this.userRepo = userRepo;
- this.guildRepo = guildRepo;
- this.trackService = trackService;
- random = new Random();
- }
-
- @Override
- public void onGuildVoiceJoin(@NotNull GuildVoiceJoinEvent event) {
-
- Guild guild = event.getGuild();
- User user = event.getMember()
- .getUser();
-
- Set guildLocks = DiscordService.getGuildVoiceLocks();
- if(user.isBot() || guildLocks.contains(guild.getIdLong()))
- return;
-
- int joinProbability;
-
- Optional opt = guildRepo.findById(guild.getIdLong());
- if(opt.isPresent())
- joinProbability = opt.get()
- .getJoinProbability();
- else {
- GuildModel guildModel = new GuildModel(guild.getIdLong());
- joinProbability = guildModel.getJoinProbability();
- guildRepo.save(guildModel);
- }
-
- if(random.nextInt(100) + 1 > joinProbability)
- return;
-
- TrackPlayer trackPlayer = new TrackPlayer(trackService.randomTrack());
- if(!trackPlayer.canProvide()) {
- logger.error("TrackPlayer cannot provide");
- return;
- }
-
- VoiceChannel channel = event.getChannelJoined();
- AudioManager audioManager = event.getGuild()
- .getAudioManager();
-
- trackPlayer.setTrackEndAction(audioManager::closeAudioConnection);
-
- try {
- audioManager.setSendingHandler(trackPlayer);
- audioManager.openAudioConnection(channel);
- guildLocks.add(guild.getIdLong());
- } catch(InsufficientPermissionException ignored) {
- }
-
- }
-
- @Override
- public void onGuildVoiceLeave(@NotNull GuildVoiceLeaveEvent event) {
-
- Guild guild = event.getGuild();
- User user = event.getMember()
- .getUser();
- SelfUser selfUser = event.getJDA()
- .getSelfUser();
-
- Set guildLocks = DiscordService.getGuildVoiceLocks();
- if(user.getIdLong() == selfUser.getIdLong())
- guildLocks.remove(guild.getIdLong());
-
- }
-
- @Override
- public void onPrivateMessageReceived(@NotNull PrivateMessageReceivedEvent event) {
-
- User user = event.getAuthor();
- if(user.isBot())
- return;
-
- Message message = event.getMessage();
- Optional opt = userRepo.findById(user.getIdLong());
-
- if(!opt.isPresent() || opt.get()
- .getAltname() == null) {
- message.reply("You must set an `/altname` first to upload a track")
- .queue();
- return;
- }
-
- UserModel userModel = opt.get();
-
- List attachments = message.getAttachments();
- if(attachments.isEmpty()) {
- message.reply("To upload a track you must send me an **opus encoded** file")
- .queue();
- return;
- }
-
- Message.Attachment attachment = attachments.get(0);
- String filename = attachment.getFileName();
- int size = attachment.getSize();
-
- if(size > TrackService.DEFAULT_MAX_TRACK_SIZE) {
- message.reply("The file is too big")
- .queue();
- return;
- }
-
- Matcher filenameMatcher = TRACK_NAME_PATTERN.matcher(filename.toLowerCase(Locale.ROOT));
- if(!filenameMatcher.matches()) {
- message.reply(
- "Filename or extension invalid (filename must be alphanumeric and can only contain *dashes* `-` and *underscores* `_`, extension must be `.opus`)")
- .queue();
- return;
- }
-
- attachment.retrieveInputStream()
- .thenAcceptAsync(in -> {
-
- byte[] data = new byte[size];
-
- try {
-
- int read = 0, c;
-
- do {
- c = in.read(data, read, size - read);
- if(c > 0)
- read += c;
- } while(c > 0);
-
- in.close();
-
- if(read != size) {
- logger.warn("Size mismatch while reading InputStream. Expected size: " + size + ", read: " +
- read);
- data = Arrays.copyOfRange(data, 0, read);
- }
-
- new OpusFile(new OggFile(new ByteArrayInputStream(data)));
-
- // TODO: USE TRACK_SERVICE
- TrackRepository trackRepo = trackService.getTrackRepo();
- String trackname = filenameMatcher.group(1);
-
- if(trackRepo.existsByNameAndUploader(trackname, userModel)) {
- message.reply("You've already uploaded a track with the same name")
- .queue();
- return;
- }
-
- TrackModel track = new TrackModel().setName(filenameMatcher.group(1))
- .setUploader(userModel)
- .setSize((long) data.length)
- .setData(data);
- trackRepo.save(track);
-
- message.reply("Track successfully inserted!")
- .queue();
-
- } catch(IOException e) {
- throw new UncheckedIOException(e);
- } catch(IllegalArgumentException e) {
-
- // Not an opus track
- message.reply("The provided file is not an **opus-encoded** track.")
- .queue();
-
- }
- })
- .exceptionally(e -> {
- message.reply(
- "There has been an internal error while computing the file, please retry or contact a developer")
- .queue();
- logger.warn("Error while opening OpusFile", e);
- return null;
- });
-
- }
-
-}
diff --git a/src/main/java/ovh/excale/vgreeter/services/DiscordService.java b/src/main/java/ovh/excale/vgreeter/services/DiscordService.java
index a98b429..ac2187a 100644
--- a/src/main/java/ovh/excale/vgreeter/services/DiscordService.java
+++ b/src/main/java/ovh/excale/vgreeter/services/DiscordService.java
@@ -1,71 +1,101 @@
package ovh.excale.vgreeter.services;
+import lombok.Getter;
+import lombok.extern.log4j.Log4j2;
+import club.minnced.discord.jdave.interop.JDaveSessionFactory;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.JDABuilder;
+import net.dv8tion.jda.api.audio.AudioModuleConfig;
import net.dv8tion.jda.api.entities.Activity;
import net.dv8tion.jda.api.interactions.commands.Command;
import net.dv8tion.jda.api.requests.GatewayIntent;
import net.dv8tion.jda.api.utils.cache.CacheFlag;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Service;
-import ovh.excale.vgreeter.commands.*;
+import ovh.excale.vgreeter.commands.button.CloseEmbedCommand;
+import ovh.excale.vgreeter.commands.button.TrackIndexButtonCommand;
+import ovh.excale.vgreeter.commands.slash.AltnameCommand;
+import ovh.excale.vgreeter.commands.slash.ProbabilityCommand;
+import ovh.excale.vgreeter.commands.message.RestartCommand;
+import ovh.excale.vgreeter.commands.slash.UploadHelpCommand;
+import ovh.excale.vgreeter.commands.core.CommandRegister;
+import ovh.excale.vgreeter.commands.message.TrackUploadCommand;
+import ovh.excale.vgreeter.commands.slash.*;
-import javax.security.auth.login.LoginException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
+@Log4j2
@Service
public class DiscordService {
- private static final Logger logger = LoggerFactory.getLogger(DiscordService.class);
+ @Getter
private static final Set guildVoiceLocks = Collections.synchronizedSet(new HashSet<>());
private final JDA jda;
- public DiscordService(DiscordEventHandlerService eventHandler, CommandRegister commands,
- @Value("${env.DISCORD_TOKEN}") String token) throws LoginException, InterruptedException {
+ public DiscordService(
+ VoiceChannelHandler eventHandler,
+ CommandRegister commands,
+ @Value("${env.DISCORD_TOKEN}") String token
+ ) throws InterruptedException {
- jda = JDABuilder.create(token,
+ jda = JDABuilder
+ .create(
+ token,
GatewayIntent.GUILD_VOICE_STATES,
GatewayIntent.DIRECT_MESSAGES,
- GatewayIntent.GUILD_VOICE_STATES)
- .disableCache(CacheFlag.ACTIVITY,
- CacheFlag.ONLINE_STATUS,
- CacheFlag.CLIENT_STATUS,
- CacheFlag.MEMBER_OVERRIDES,
- CacheFlag.EMOTE)
- .setActivity(Activity.listening("people"))
- .addEventListeners(eventHandler, commands.getListener())
- .build()
- .awaitReady();
+ GatewayIntent.MESSAGE_CONTENT
+ )
+ .disableCache(
+ CacheFlag.ACTIVITY,
+ CacheFlag.ONLINE_STATUS,
+ CacheFlag.CLIENT_STATUS,
+ CacheFlag.MEMBER_OVERRIDES,
+ CacheFlag.EMOJI
+ )
+ .setActivity(Activity.listening("people"))
+ .addEventListeners(eventHandler, commands.getListener())
+ .setAudioModuleConfig(
+ new AudioModuleConfig().withDaveSessionFactory(new JDaveSessionFactory())
+ )
+ .build()
+ .awaitReady();
+ log.info("JDA connected");
- jda.updateCommands()
- .addCommands(commands.register(new ProbabilityCommand())
+ String commandListString = jda
+ .updateCommands()
+ .addCommands(commands
+ // SLASH COMMANDS
+ .register(new ProbabilityCommand())
.register(new AltnameCommand())
- .register(new UploadCommand())
+ .register(new UploadHelpCommand())
.register(new PlaytestCommand())
- .register(new TracknameCommand())
- .register(new TrackIndexCommand())
- .getData())
- .queue(commandList -> logger.info("[Registered commands] " + commandList.stream()
- .map(Command::getName)
- .collect(Collectors.joining(", "))), e -> logger.warn("Couldn't update commands", e));
+ .register(new TrackNameCommand())
+ .register(new TrackIndexSlashCommand())
+ .register(new TrackRemoveCommand())
+ .register(new TrackDownloadCommand())
+ // MESSAGE COMMANDS
+ .register(new RestartCommand())
+ .register(new TrackUploadCommand())
+ // BUTTON COMMANDS
+ .register(new CloseEmbedCommand())
+ .register(new TrackIndexButtonCommand())
+ .getSlashCommandsData())
+ .complete()
+ .stream()
+ .map(Command::getName)
+ .collect(Collectors.joining(", "));
- logger.info("JDA connected");
+ log.info("[Registered SlashCommands] " + commandListString);
}
- public static Set getGuildVoiceLocks() {
- return guildVoiceLocks;
- }
-
- @Bean
+ @Bean(destroyMethod = "shutdown")
public JDA getJda() {
return jda;
}
diff --git a/src/main/java/ovh/excale/vgreeter/services/TrackService.java b/src/main/java/ovh/excale/vgreeter/services/TrackService.java
index 7f8848a..08fbce3 100644
--- a/src/main/java/ovh/excale/vgreeter/services/TrackService.java
+++ b/src/main/java/ovh/excale/vgreeter/services/TrackService.java
@@ -1,5 +1,6 @@
package ovh.excale.vgreeter.services;
+import lombok.Getter;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
@@ -11,12 +12,14 @@ public class TrackService {
public static final int DEFAULT_MAX_TRACK_SIZE = 1024 * 64;
+ @Getter
private final TrackRepository trackRepo;
public TrackService(TrackRepository trackRepo) {
this.trackRepo = trackRepo;
}
+ // TODO: nullsafe
public TrackModel randomTrack() {
long qty = trackRepo.count();
@@ -31,8 +34,4 @@ public TrackModel randomTrack() {
return track;
}
- public TrackRepository getTrackRepo() {
- return trackRepo;
- }
-
}
diff --git a/src/main/java/ovh/excale/vgreeter/services/VoiceChannelHandler.java b/src/main/java/ovh/excale/vgreeter/services/VoiceChannelHandler.java
new file mode 100644
index 0000000..c281ba6
--- /dev/null
+++ b/src/main/java/ovh/excale/vgreeter/services/VoiceChannelHandler.java
@@ -0,0 +1,93 @@
+package ovh.excale.vgreeter.services;
+
+import lombok.extern.log4j.Log4j2;
+import net.dv8tion.jda.api.entities.Guild;
+import net.dv8tion.jda.api.entities.SelfUser;
+import net.dv8tion.jda.api.entities.User;
+import net.dv8tion.jda.api.entities.channel.unions.AudioChannelUnion;
+import net.dv8tion.jda.api.events.guild.voice.GuildVoiceUpdateEvent;
+import net.dv8tion.jda.api.exceptions.InsufficientPermissionException;
+import net.dv8tion.jda.api.hooks.ListenerAdapter;
+import net.dv8tion.jda.api.managers.AudioManager;
+import org.jetbrains.annotations.NotNull;
+import org.springframework.stereotype.Service;
+import ovh.excale.vgreeter.models.GuildModel;
+import ovh.excale.vgreeter.repositories.GuildRepository;
+import ovh.excale.vgreeter.track.TrackPlayer;
+
+import java.util.Optional;
+import java.util.Random;
+import java.util.Set;
+
+@Log4j2
+@Service
+public class VoiceChannelHandler extends ListenerAdapter {
+
+ private final GuildRepository guildRepo;
+ private final TrackService trackService;
+
+ private final Random random;
+
+ public VoiceChannelHandler(GuildRepository guildRepo, TrackService trackService) {
+ this.guildRepo = guildRepo;
+ this.trackService = trackService;
+ random = new Random();
+ }
+
+ // TODO: DISABLE VOICE EVENT HANDLING UNDER MAINTENANCE
+
+ @Override
+ public void onGuildVoiceUpdate(@NotNull GuildVoiceUpdateEvent event) {
+
+ Guild guild = event.getGuild();
+ User user = event.getMember().getUser();
+ Set guildLocks = DiscordService.getGuildVoiceLocks();
+
+ if(event.getChannelLeft() != null) {
+ SelfUser selfUser = event.getJDA().getSelfUser();
+ if(user.getIdLong() == selfUser.getIdLong())
+ guildLocks.remove(guild.getIdLong());
+ }
+
+ if(event.getChannelJoined() == null)
+ return;
+
+ if(user.isBot() || guildLocks.contains(guild.getIdLong()))
+ return;
+
+ int joinProbability;
+
+ Optional opt = guildRepo.findById(guild.getIdLong());
+ if(opt.isPresent())
+ joinProbability = opt.get().getJoinProbability();
+ else {
+ GuildModel guildModel = GuildModel.builder().id(guild.getIdLong()).build();
+ joinProbability = guildModel.getJoinProbability();
+ guildRepo.save(guildModel);
+ }
+
+ if(random.nextInt(100) + 1 > joinProbability)
+ return;
+
+ TrackPlayer trackPlayer = new TrackPlayer(trackService.randomTrack());
+ if(!trackPlayer.canProvide()) {
+ log.error("TrackPlayer cannot provide");
+ return;
+ }
+
+ AudioChannelUnion channel = event.getChannelJoined();
+ AudioManager audioManager = guild.getAudioManager();
+ trackPlayer.setTrackEndAction(audioManager::closeAudioConnection);
+
+ try {
+ audioManager.setSendingHandler(trackPlayer);
+ audioManager.openAudioConnection(channel);
+ guildLocks.add(guild.getIdLong());
+ } catch(InsufficientPermissionException ignored) {
+ // The bot doesn't have permissions to connect to the Voice Channel, do nothing
+ }
+
+ }
+
+
+}
diff --git a/src/main/java/ovh/excale/vgreeter/track/TrackIndex.java b/src/main/java/ovh/excale/vgreeter/track/TrackIndex.java
new file mode 100644
index 0000000..5e98947
--- /dev/null
+++ b/src/main/java/ovh/excale/vgreeter/track/TrackIndex.java
@@ -0,0 +1,161 @@
+package ovh.excale.vgreeter.track;
+
+import lombok.Getter;
+import lombok.Setter;
+import lombok.SneakyThrows;
+import net.dv8tion.jda.api.EmbedBuilder;
+import net.dv8tion.jda.api.components.buttons.Button;
+import org.jetbrains.annotations.NotNull;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Sort;
+import ovh.excale.vgreeter.VGreeterApplication;
+import ovh.excale.vgreeter.commands.core.CommandOptions;
+import ovh.excale.vgreeter.models.TrackModel;
+import ovh.excale.vgreeter.repositories.TrackRepository;
+import ovh.excale.vgreeter.utilities.Emojis;
+
+import java.awt.*;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import static ovh.excale.vgreeter.commands.core.CommandKeyword.TRACK_NAME;
+import static ovh.excale.vgreeter.commands.core.CommandKeyword.USER_ID;
+
+public class TrackIndex {
+
+ public static final String FILTER_ALL = "all";
+ public static final String FILTER_NAME = "name";
+ public static final String FILTER_USER = "user";
+ public static final int DEFAULT_PAGE_SIZE = 15;
+
+ private final CommandOptions options;
+ private final TrackRepository trackRepo;
+ private Page trackPage;
+
+ @Setter
+ @Getter
+ private Color embedColor;
+ private String filterContent;
+
+ @Setter
+ @Getter
+ private int pageSize;
+
+ public TrackIndex(CommandOptions options) {
+ this.options = Objects.requireNonNull(options);
+ trackRepo = VGreeterApplication.getApplicationContext()
+ .getBean(TrackRepository.class);
+
+ trackPage = null;
+ filterContent = null;
+ embedColor = Color.BLUE;
+ pageSize = DEFAULT_PAGE_SIZE;
+
+ }
+
+ private void trackPageCheck() throws IllegalStateException {
+ if(trackPage == null)
+ throw new IllegalStateException();
+ }
+
+ public boolean isEmpty() {
+ return trackPage == null || !trackPage.hasContent();
+ }
+
+ public void fetch() throws IllegalArgumentException {
+
+ int humanBasedPage = options.getPageSafe();
+ if(humanBasedPage < 1)
+ throw new IllegalArgumentException("Page option must be positive");
+
+ Sort sorting = Sort.by(Sort.Direction.ASC, "id");
+ String filter = options.hasSubcommand() ? options.getSubcommand() : FILTER_ALL;
+
+ switch(filter) {
+
+ case FILTER_ALL:
+
+ trackPage = trackRepo.findAll(PageRequest.of(humanBasedPage - 1, pageSize, sorting));
+
+ break;
+
+ case FILTER_NAME:
+
+ String trackName = options.getOption(TRACK_NAME.ext)
+ .orElseThrow(() -> new IllegalArgumentException("Missing parameter " + TRACK_NAME.ext));
+ filterContent = trackName;
+
+ String formattedTrackName = "%" + trackName.replaceAll("\\s+", "%") + "%";
+ trackPage = trackRepo.findAllByNameQuery(formattedTrackName,
+ PageRequest.of(humanBasedPage - 1, pageSize, sorting));
+
+ break;
+
+ case FILTER_USER:
+
+ long userId = options.getOption(USER_ID.ext)
+ .map(Long::valueOf)
+ .orElseThrow(() -> new IllegalArgumentException("Missing parameter " + USER_ID.ext));
+
+ filterContent = String.format("<@%d>", userId);
+ trackPage = trackRepo.findAllByUploaderIdIs(userId,
+ PageRequest.of(humanBasedPage - 1, pageSize, sorting));
+
+ break;
+
+ default:
+ throw new IllegalArgumentException("Unknown filter " + filter);
+
+ }
+
+ }
+
+ public @NotNull EmbedBuilder buildEmbed() {
+
+ trackPageCheck();
+
+ String filterOut = (filterContent != null) ? "\n\n| _**Filter:** " + filterContent + "_" : "";
+
+ return new EmbedBuilder()
+ .setTitle("TrackIndex")
+ .setFooter("Page " + (trackPage.getNumber() + 1) + "/" + trackPage.getTotalPages())
+ .setColor(embedColor)
+ .setDescription(trackPage.getContent()
+ .stream()
+ .map(track -> "**#" + track.getId() + "** *" + track.getName() + "*")
+ .collect(Collectors.joining("\n"))
+ .concat(filterOut));
+
+ }
+
+ @SneakyThrows
+ public Button[] buildButtons() {
+
+ trackPageCheck();
+
+ int zeroBasedPage = trackPage.getNumber();
+ boolean enablePageChange = trackPage.getTotalPages() != 1;
+
+ // button
+ options.setPage(trackPage.hasPrevious() ? zeroBasedPage : trackPage.getTotalPages());
+ Button prevButton = Button.secondary(enablePageChange ? options.json() : "{\"_\":0}", Emojis.PREVIOUS)
+ .withDisabled(!enablePageChange);
+
+ // button
+ options.setPage(!trackPage.isLast() ? zeroBasedPage + 2 : 0);
+ Button nextButton = Button.secondary(enablePageChange ? options.json() : "{\"_\":1}", Emojis.NEXT)
+ .withDisabled(!enablePageChange);
+
+ // button
+ options.setPage(zeroBasedPage + 1);
+ Button reloadButton = Button.secondary(options.json(), Emojis.RELOAD);
+
+ CommandOptions closeCommand = new CommandOptions("close");
+ Button closeButton = Button.secondary(closeCommand.json(), Emojis.CLOSE);
+
+ return new Button[] { prevButton, nextButton, reloadButton, closeButton };
+
+ }
+
+}
diff --git a/src/main/java/ovh/excale/vgreeter/utilities/TrackPlayer.java b/src/main/java/ovh/excale/vgreeter/track/TrackPlayer.java
similarity index 88%
rename from src/main/java/ovh/excale/vgreeter/utilities/TrackPlayer.java
rename to src/main/java/ovh/excale/vgreeter/track/TrackPlayer.java
index 03ed9e3..ae04a98 100644
--- a/src/main/java/ovh/excale/vgreeter/utilities/TrackPlayer.java
+++ b/src/main/java/ovh/excale/vgreeter/track/TrackPlayer.java
@@ -1,11 +1,10 @@
-package ovh.excale.vgreeter.utilities;
+package ovh.excale.vgreeter.track;
+import lombok.extern.log4j.Log4j2;
import net.dv8tion.jda.api.audio.AudioSendHandler;
import org.gagravarr.ogg.OggPacket;
import org.gagravarr.ogg.OggPacketReader;
import org.jetbrains.annotations.Nullable;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import ovh.excale.vgreeter.models.TrackModel;
import java.io.IOException;
@@ -16,10 +15,9 @@
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
+@Log4j2
public class TrackPlayer implements AudioSendHandler {
- private final static Logger logger = LoggerFactory.getLogger(TrackPlayer.class);
-
private final TrackModel track;
private final Iterator packetIterator;
private Runnable trackEndAction;
@@ -37,7 +35,7 @@ public TrackPlayer(TrackModel track) {
while((packet = packetReader.getNextPacket()) != null)
packetList.add(packet);
} catch(IOException e) {
- logger.error(e.getMessage(), e);
+ log.error(e.getMessage(), e);
}
}
diff --git a/src/main/java/ovh/excale/vgreeter/utilities/ArgumentsParser.java b/src/main/java/ovh/excale/vgreeter/utilities/ArgumentsParser.java
new file mode 100644
index 0000000..9eba0d6
--- /dev/null
+++ b/src/main/java/ovh/excale/vgreeter/utilities/ArgumentsParser.java
@@ -0,0 +1,95 @@
+package ovh.excale.vgreeter.utilities;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Arrays;
+import java.util.Optional;
+import java.util.function.Function;
+
+// TODO: USER/MEMBER PARSE
+public class ArgumentsParser {
+
+ private final String rawArguments;
+ private final String[] splitArguments;
+
+ public ArgumentsParser(String rawArguments) {
+ this.rawArguments = rawArguments;
+ splitArguments = rawArguments
+ .replaceAll(" {2,}", " ")
+ .trim()
+ .split(" ");
+ }
+
+ public ArgumentsParser(String rawArguments, String splitRegex) {
+ this.rawArguments = rawArguments;
+ splitArguments = rawArguments.split(splitRegex);
+ }
+
+ public String getRawArguments() {
+ return rawArguments;
+ }
+
+ public String[] getSplitArguments() {
+ return splitArguments;
+ }
+
+ public Optional getArgumentCast(int index, Function parser) {
+
+ Optional opt = Optional.empty();
+ if(index < splitArguments.length)
+ try {
+
+ opt = Optional.of(parser.apply(splitArguments[index]));
+
+ } catch(Exception ignored) {
+ }
+
+ return opt;
+ }
+
+ public Optional getArgumentString(int index) {
+ return getArgumentCast(index, String::toString);
+ }
+
+ public @NotNull String getArgumentString(int index, @NotNull String defValue) {
+ return getArgumentString(index).orElse(defValue);
+ }
+
+ public Optional getArgumentInteger(int index) {
+ return getArgumentCast(index, Integer::parseInt);
+ }
+
+ public int getArgumentInteger(int index, int defValue) {
+ return getArgumentInteger(index).orElse(defValue);
+ }
+
+ public Optional getArgumentLong(int index) {
+ return getArgumentCast(index, Long::parseLong);
+ }
+
+ public long getArgumentLong(int index, long defValue) {
+ return getArgumentLong(index).orElse(defValue);
+ }
+
+ public Optional getArgumentBoolean(int index) {
+ return getArgumentCast(index, Boolean::parseBoolean);
+ }
+
+ public boolean getArgumentBoolean(int index, boolean defValue) {
+ return getArgumentBoolean(index).orElse(defValue);
+ }
+
+ public Optional getArgumentText(int index) {
+
+ Optional opt = Optional.empty();
+ if(index < splitArguments.length)
+ opt = Optional.of(String.join(" ", Arrays.copyOfRange(splitArguments, index, splitArguments.length)));
+
+ return opt;
+ }
+
+ public @NotNull String getArgumentText(int index, @NotNull String defValue) {
+ return getArgumentText(index).orElse(defValue);
+ }
+
+}
diff --git a/src/main/java/ovh/excale/vgreeter/utilities/Emojis.java b/src/main/java/ovh/excale/vgreeter/utilities/Emojis.java
new file mode 100644
index 0000000..39348cd
--- /dev/null
+++ b/src/main/java/ovh/excale/vgreeter/utilities/Emojis.java
@@ -0,0 +1,17 @@
+package ovh.excale.vgreeter.utilities;
+
+import lombok.NoArgsConstructor;
+import net.dv8tion.jda.api.entities.emoji.Emoji;
+
+@NoArgsConstructor(access = lombok.AccessLevel.PRIVATE)
+public class Emojis {
+
+ public static final Emoji PREVIOUS = Emoji.fromUnicode("◀");
+
+ public static final Emoji NEXT = Emoji.fromUnicode("▶");
+
+ public static final Emoji RELOAD = Emoji.fromUnicode("🔄");
+
+ public static final Emoji CLOSE = Emoji.fromUnicode("❌");
+
+}
diff --git a/src/main/resources-filtered/application.properties b/src/main/resources-filtered/application.properties
index 158743f..4224120 100644
--- a/src/main/resources-filtered/application.properties
+++ b/src/main/resources-filtered/application.properties
@@ -6,7 +6,6 @@ spring.datasource.username: ${env.DATASOURCE_USERNAME}
spring.datasource.password: ${env.DATASOURCE_PASSWORD}
spring.datasource.hikari.keepalive-time: 30000
spring.datasource.driver-class-name: org.postgresql.Driver
-spring.jpa.database-platform: org.hibernate.dialect.PostgreSQL10Dialect
# Custom properties
application.name: @project.name@