From 370c983834d1fc74f1a5fe7e844115fb443f77ac Mon Sep 17 00:00:00 2001 From: TheZexquex Date: Sat, 6 Jun 2026 18:39:34 +0200 Subject: [PATCH 1/8] Initial work for running the game servers in docker --- .dockerignore | 4 + Dockerfile | 2 +- bot/build.gradle.kts | 10 ++ bot/src/main/java/de/chojo/gamejam/Bot.java | 1 + .../gamejam/configuration/ConfigFile.java | 18 +- .../gamejam/configuration/Configuration.java | 38 +++-- .../configuration/elements/Docker.java | 47 +++++ .../chojo/gamejam/server/DockerService.java | 145 ++++++++++++++++ .../chojo/gamejam/server/ServerService.java | 7 +- .../de/chojo/gamejam/server/TeamServer.java | 161 +++--------------- bot/src/main/resources/log4j2.xml | 30 +--- dev.Dockerfile | 34 ++++ dev.compose.yml | 9 +- settings.gradle.kts | 5 + 14 files changed, 316 insertions(+), 195 deletions(-) create mode 100644 .dockerignore create mode 100644 bot/src/main/java/de/chojo/gamejam/configuration/elements/Docker.java create mode 100644 bot/src/main/java/de/chojo/gamejam/server/DockerService.java create mode 100644 dev.Dockerfile diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..8caa093f --- /dev/null +++ b/.dockerignore @@ -0,0 +1,4 @@ +.gradle +**/build +.git +.idea \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index fc0d17c9..35e9a228 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # This dockerfile is far from best practice and is a pure "Make it work" approach. Please do not use it as a reference of any kind. -FROM gradle:jdk21-alpine as build +FROM gradle:jdk25-alpine as build COPY . . RUN gradle clean :bot:build :plugin-paper:build --no-daemon diff --git a/bot/build.gradle.kts b/bot/build.gradle.kts index 654328b0..88a45422 100644 --- a/bot/build.gradle.kts +++ b/bot/build.gradle.kts @@ -20,16 +20,26 @@ dependencies { implementation("de.chojo", "cjda-util", "2.14.5+jda-6.3.0") { exclude(module = "opus-java") } + + implementation("net.dv8tion", "JDA", "6.4.1") + implementation(libs.javalin.bundle) implementation(libs.javalin.openapi) implementation(libs.javalin.swagger) annotationProcessor(libs.javalin.annotation) implementation("net.lingala.zip4j", "zip4j", "2.11.6") + // config + implementation("dev.chojo", "ocular", "2.2.1") + implementation("tools.jackson.dataformat:jackson-dataformat-yaml:3.1.4") + // database implementation(libs.bundles.sadu) implementation("org.postgresql", "postgresql", "42.7.11") + // docker api + implementation(libs.bundles.docker) + // Logging implementation(libs.bundles.logging) implementation("club.minnced", "discord-webhooks", "0.8.4") diff --git a/bot/src/main/java/de/chojo/gamejam/Bot.java b/bot/src/main/java/de/chojo/gamejam/Bot.java index 65ab4956..1cb918d3 100644 --- a/bot/src/main/java/de/chojo/gamejam/Bot.java +++ b/bot/src/main/java/de/chojo/gamejam/Bot.java @@ -155,6 +155,7 @@ private void buildLocale() { .addLanguage(DiscordLocale.GERMAN) .withLanguageProvider(guild -> Optional.ofNullable(guilds.guild(guild).settings().locale()) .map(DiscordLocale::from)) + .withGuildLocaleCodeProvider((guild, s) -> Optional.empty()) .build(); } diff --git a/bot/src/main/java/de/chojo/gamejam/configuration/ConfigFile.java b/bot/src/main/java/de/chojo/gamejam/configuration/ConfigFile.java index eb299c9c..a47ed142 100644 --- a/bot/src/main/java/de/chojo/gamejam/configuration/ConfigFile.java +++ b/bot/src/main/java/de/chojo/gamejam/configuration/ConfigFile.java @@ -6,12 +6,7 @@ package de.chojo.gamejam.configuration; -import de.chojo.gamejam.configuration.elements.Api; -import de.chojo.gamejam.configuration.elements.BaseSettings; -import de.chojo.gamejam.configuration.elements.Database; -import de.chojo.gamejam.configuration.elements.Plugins; -import de.chojo.gamejam.configuration.elements.ServerManagement; -import de.chojo.gamejam.configuration.elements.ServerTemplate; +import de.chojo.gamejam.configuration.elements.*; @SuppressWarnings("FieldMayBeFinal") public class ConfigFile { @@ -19,7 +14,14 @@ public class ConfigFile { private Database database = new Database(); private Api api = new Api(); private ServerManagement serverManagement = new ServerManagement(); + + private Docker docker = new Docker(); private Plugins plugins = new Plugins(); + + public void setServerTemplate(ServerTemplate serverTemplate) { + this.serverTemplate = serverTemplate; + } + private ServerTemplate serverTemplate = new ServerTemplate(); public BaseSettings baseSettings() { @@ -38,6 +40,10 @@ public ServerManagement serverManagement(){ return serverManagement; } + public Docker docker() { + return docker; + } + public Plugins plugins() { return plugins; } diff --git a/bot/src/main/java/de/chojo/gamejam/configuration/Configuration.java b/bot/src/main/java/de/chojo/gamejam/configuration/Configuration.java index ab52e432..cf59197a 100644 --- a/bot/src/main/java/de/chojo/gamejam/configuration/Configuration.java +++ b/bot/src/main/java/de/chojo/gamejam/configuration/Configuration.java @@ -6,17 +6,19 @@ package de.chojo.gamejam.configuration; -import de.chojo.gamejam.configuration.elements.Api; -import de.chojo.gamejam.configuration.elements.BaseSettings; -import de.chojo.gamejam.configuration.elements.Database; -import de.chojo.gamejam.configuration.elements.Plugins; -import de.chojo.gamejam.configuration.elements.ServerManagement; -import de.chojo.gamejam.configuration.elements.ServerTemplate; -import de.chojo.jdautil.configuration.BaseConfiguration; - -public class Configuration extends BaseConfiguration { +import de.chojo.gamejam.configuration.elements.*; +import dev.chojo.ocular.Configurations; +import dev.chojo.ocular.dataformats.YamlDataFormat; +import dev.chojo.ocular.key.Key; + +import java.nio.file.Path; +import java.util.List; + +public class Configuration extends Configurations { + public static final Key MAIN = Key.builder(Path.of("config.yml"), ConfigFile::new).build(); + private Configuration() { - super(new ConfigFile()); + super(Path.of("config"), MAIN, List.of(new YamlDataFormat()), Configuration.class.getClassLoader(), null); } public static Configuration create() { @@ -27,26 +29,30 @@ public static Configuration create() { public Database database() { - return config().database(); + return main().database(); + } + + public Docker docker() { + return main().docker(); } public BaseSettings baseSettings() { - return config().baseSettings(); + return main().baseSettings(); } public Api api() { - return config().api(); + return main().api(); } public ServerManagement serverManagement() { - return config().serverManagement(); + return main().serverManagement(); } public Plugins plugins() { - return config().plugins(); + return main().plugins(); } public ServerTemplate serverTemplate() { - return config().serverTemplate(); + return main().serverTemplate(); } } diff --git a/bot/src/main/java/de/chojo/gamejam/configuration/elements/Docker.java b/bot/src/main/java/de/chojo/gamejam/configuration/elements/Docker.java new file mode 100644 index 00000000..c7c5958a --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/configuration/elements/Docker.java @@ -0,0 +1,47 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.configuration.elements; + + +@SuppressWarnings({"FieldMayBeFinal", "FieldCanBeLocal"}) +public class Docker { + private String host = "unix:///var/run/docker.sock"; + private String certPath = "/home/user/.docker"; + private boolean tlsVerify = true; + private String registryUsername; + private String registryPassword; + private String registryEmail; + private String registryUrl; + + public String getHost() { + return host; + } + + public String getCertPath() { + return certPath; + } + + public boolean isTlsVerify() { + return tlsVerify; + } + + public String getRegistryUsername() { + return registryUsername; + } + + public String getRegistryPassword() { + return registryPassword; + } + + public String getRegistryEmail() { + return registryEmail; + } + + public String getRegistryUrl() { + return registryUrl; + } +} \ No newline at end of file diff --git a/bot/src/main/java/de/chojo/gamejam/server/DockerService.java b/bot/src/main/java/de/chojo/gamejam/server/DockerService.java new file mode 100644 index 00000000..bad536d5 --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/server/DockerService.java @@ -0,0 +1,145 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.server; + +import com.github.dockerjava.api.DockerClient; +import com.github.dockerjava.api.model.*; +import com.github.dockerjava.core.DefaultDockerClientConfig; +import com.github.dockerjava.core.DockerClientConfig; +import com.github.dockerjava.core.DockerClientImpl; +import com.github.dockerjava.httpclient5.ApacheDockerHttpClient; +import com.github.dockerjava.transport.DockerHttpClient; +import de.chojo.gamejam.configuration.elements.Docker; +import org.slf4j.Logger; + +import java.io.IOException; +import java.nio.file.Path; +import java.time.Duration; +import java.util.List; +import java.util.Optional; + +import static org.slf4j.LoggerFactory.getLogger; + +public class DockerService { + private DockerClientConfig dockerClientConfig; + private DockerClient dockerClient; + private static final Logger log = getLogger(DockerService.class); + private static final String DOCKER_IMAGE = "itzg/minecraft-server:latest"; + private static final String DOCKER_VOLUME_DATA_DIR = "/data"; + + public DockerService(Docker dockerConfig) { + this.dockerClientConfig = DefaultDockerClientConfig + .createDefaultConfigBuilder() + .withDockerHost(dockerConfig.getHost()) + .withDockerCertPath(dockerConfig.getCertPath()) + .withDockerTlsVerify(dockerConfig.isTlsVerify()) + .withRegistryUsername(dockerConfig.getRegistryUsername()) + .withRegistryPassword(dockerConfig.getRegistryPassword()) + .withRegistryEmail(dockerConfig.getRegistryEmail()) + .withRegistryUrl(dockerConfig.getRegistryUrl()) + .build(); + } + + public void initDockerClient() { + DockerHttpClient httpClient = new ApacheDockerHttpClient.Builder() + .dockerHost(dockerClientConfig.getDockerHost()) + .sslConfig(dockerClientConfig.getSSLConfig()) + .maxConnections(100) + .connectionTimeout(Duration.ofSeconds(30)) + .responseTimeout(Duration.ofSeconds(45)) + .build(); + + dockerClient = DockerClientImpl.getInstance(dockerClientConfig, httpClient); + } + + public void shutdown() throws IOException { + dockerClient.close(); + } + + public void provisionServer(int teamId) { + log.info("Provisioning server for team {}", teamId); + dockerClient.createVolumeCmd() + .withName(volumeName(teamId)) + .exec(); + + HostConfig hostConfig = HostConfig.newHostConfig() + .withBinds(new Bind(volumeName(teamId), new Volume(DOCKER_VOLUME_DATA_DIR))); + + dockerClient.createContainerCmd(DOCKER_IMAGE) + .withName(containerName(teamId)) + .withHostConfig(hostConfig) + .exec(); + log.info("Server provisioned for team with container name {} and volume name {}", containerName(teamId), volumeName(teamId)); + } + + public void destroyServer(int teamId) { + log.info("Destroying server for team {}", teamId); + dockerClient.removeContainerCmd(containerName(teamId)).exec(); + dockerClient.removeVolumeCmd(volumeName(teamId)).exec(); + } + + public void startServer(int teamId) { + log.info("Starting server for team {}", teamId); + dockerClient.startContainerCmd(containerName(teamId)).exec(); + } + + public void stopServer(int teamId) { + log.info("Stopping server for team {}", teamId); + dockerClient.stopContainerCmd(containerName(teamId)).exec(); + } + + public void restartServer(int teamId) { + dockerClient.restartContainerCmd(containerName(teamId)).exec(); + } + + public boolean running(int teamId) { + return dockerClient.listContainersCmd() + .withShowAll(true) + .withNameFilter(List.of(containerName(teamId))) + .exec() + .stream() + .anyMatch(container -> container.getState().equals("running")); + } + + public void sendCommand(int teamId, String command) { + dockerClient.execCreateCmd(containerName(teamId)) + .withCmd(String.format("mc rcon-cli %s", command)) + .exec(); + } + + public boolean exists(int teamId) { + return dockerClient.listContainersCmd() + .withShowAll(true) + .exec() + .stream() + .anyMatch(container -> container.getId().startsWith(containerName(teamId))); + } + + public Optional container(int teamId) { + return dockerClient.listContainersCmd() + .withShowAll(true) + .withNameFilter(List.of(containerName(teamId))) + .exec() + .stream() + .findFirst(); + } + + public void copyArchiveToContainer(int teamId, Path source, Path destination) { + dockerClient.copyArchiveToContainerCmd(containerName(teamId)) + .withHostResource(source.toString()) + .withRemotePath(destination.toString()) + .exec(); + } + + private String volumeName(int teamId) { + return "plugin-jam-team-" + teamId; + } + + public String containerName(int teamId) { + return "plugin-jam-team-" + teamId; + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/server/ServerService.java b/bot/src/main/java/de/chojo/gamejam/server/ServerService.java index 81a7b82f..794e8871 100644 --- a/bot/src/main/java/de/chojo/gamejam/server/ServerService.java +++ b/bot/src/main/java/de/chojo/gamejam/server/ServerService.java @@ -36,6 +36,7 @@ public class ServerService implements Runnable { private Teams teams; private final Configuration configuration; private final Stack freePorts = new Stack<>(); + private final DockerService dockerService; public static ServerService create(ScheduledExecutorService executorService, Configuration configuration) { var serverService = new ServerService(configuration); @@ -47,6 +48,8 @@ private ServerService(Configuration configuration) { this.configuration = configuration; IntStream.rangeClosed(configuration.serverManagement().minPort(), configuration.serverManagement().maxPort()) .forEach(freePorts::add); + this.dockerService = new DockerService(configuration.docker()); + this.dockerService.initDockerClient(); } public void shutdown() { @@ -123,7 +126,7 @@ public CompletableFuture syncVelocity() { } var team = optTeam.get(); log.info("Registered server for team {} with id {}", team.meta().name(), team.id()); - var teamServer = new TeamServer(this, team, configuration, registration.port(), registration.apiPort()); + var teamServer = new TeamServer(this, dockerService, team, configuration, registration.port(), registration.apiPort()); teamServer.running(true); server.put(team, teamServer); freePorts.removeElement(registration.apiPort()); @@ -134,7 +137,7 @@ public CompletableFuture syncVelocity() { } public TeamServer get(Team team) { - return server.computeIfAbsent(team, key -> new TeamServer(this, key, configuration, nextPort(), nextPort())); + return server.computeIfAbsent(team, key -> new TeamServer(this, dockerService, key, configuration, nextPort(), nextPort())); } private int nextPort() { diff --git a/bot/src/main/java/de/chojo/gamejam/server/TeamServer.java b/bot/src/main/java/de/chojo/gamejam/server/TeamServer.java index 2f34332e..44c89680 100644 --- a/bot/src/main/java/de/chojo/gamejam/server/TeamServer.java +++ b/bot/src/main/java/de/chojo/gamejam/server/TeamServer.java @@ -11,12 +11,10 @@ import de.chojo.gamejam.data.dao.guild.jams.jam.teams.Team; import de.chojo.gamejam.util.Mapper; import de.chojo.jdautil.localization.util.LocalizedEmbedBuilder; -import de.chojo.jdautil.util.Futures; import de.chojo.jdautil.wrapper.EventContext; import de.chojo.pluginjam.payload.RequestsPayload; import de.chojo.pluginjam.payload.StatsPayload; import net.dv8tion.jda.api.entities.MessageEmbed; -import net.lingala.zip4j.ZipFile; import org.slf4j.Logger; import java.io.File; @@ -31,7 +29,6 @@ import java.nio.file.StandardCopyOption; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; -import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.Optional; @@ -45,6 +42,7 @@ public class TeamServer { private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH:mm:ss.SSSS"); private static final Logger log = getLogger(TeamServer.class); private final ServerService serverService; + private final DockerService dockerService; private final Team team; private final Configuration configuration; private final int port; @@ -72,8 +70,9 @@ public class TeamServer { private boolean running; - public TeamServer(ServerService serverService, Team team, Configuration configuration, int port, int apiPort) { + public TeamServer(ServerService serverService, DockerService dockerService, Team team, Configuration configuration, int port, int apiPort) { this.serverService = serverService; + this.dockerService = dockerService; this.team = team; this.configuration = configuration; this.port = port; @@ -85,7 +84,7 @@ public boolean running() { } public boolean exists() { - return serverDir().toFile().exists(); + return dockerService.exists(team.id()); } /** @@ -96,6 +95,7 @@ public boolean exists() { */ public boolean setup() throws IOException { if (exists()) return false; + dockerService.provisionServer(team.id()); log.info("Setting up server of team {}", team); writeTemplate(); return true; @@ -159,49 +159,20 @@ private void writeTemplate() throws IOException { */ public boolean purge() throws IOException { if (!exists()) return false; - if(running()) stop().join(); + if(running()) { + log.info("Stopping server for team {}", team); + stop().join(); + } log.info("Purging server of team {}", team); - return deleteDirectory(serverDir()); + dockerService.destroyServer(team.id()); + return true; } public boolean start() { if (!exists() || running()) return false; - var server = configuration.serverManagement(); - var command = new ArrayList(); - command.add("screen"); - command.add("-dmS"); - command.add(screenName()); - command.add("java"); - command.add("-Xmx%dM".formatted(server.memory())); - command.add("-Xms%dM".formatted(server.memory())); - command.addAll(AIKAR); - command.addAll(server.parameter()); - command.add("-Dpluginjam.port=" + server.velocityPort()); - command.add("-Dpluginjam.host=" + server.getVelocityHost()); - command.add("-Dpluginjam.team.id=" + team.id()); - command.add("-Dpluginjam.team.name=" + teamName()); - command.add("-Djavalin.port=" + apiPort); - command.add("-Dcom.mojang.eula.agree=true"); - command.add("--enable-preview"); - command.add("-jar"); - command.add("server.jar"); - command.add("--max-players"); - command.add(String.valueOf(server.maxPlayers())); - command.add("--nogui"); - command.add("--port"); - command.add(String.valueOf(port)); log.info("Starting server server of team {}", team); - try { - new ProcessBuilder() - .directory(serverDir().toFile()) - .command(command) - .redirectOutput(ProcessBuilder.Redirect.to(processLogFile("start"))) - .start(); - running = true; - } catch (IOException e) { - log.error("Could not start server", e); - return false; - } + dockerService.startServer(team.id()); + running = true; return true; } @@ -218,50 +189,16 @@ public CompletableFuture stop(boolean restart) { return CompletableFuture.completedFuture(null); } running = false; - try { - CompletableFuture future = new ProcessBuilder() - .directory(new File("").toPath().toAbsolutePath().toFile()) - .command("./wait.sh", screenName()) - .redirectOutput(ProcessBuilder.Redirect.to(processLogFile("stop"))) - .start() - .onExit() - .whenComplete(Futures.whenComplete( - exit -> { - log.info("Stopped server of team {}", team); - serverService.stopped(this, restart); - }, - err -> log.error("Could not stop server {}", team)) - ) - .thenApply(r -> null); - send("stop"); - log.info("Stopping server of team {}", team); - return future; - } catch (IOException e) { - log.error("Failed to build process builder", e); - throw new RuntimeException(e); - } + CompletableFuture future = CompletableFuture.runAsync(() -> { + dockerService.stopServer(team.id()); + }); + log.info("Stopping server of team {}", team); + return future; } public void send(String command) { log.info("Sending command \"{}\" to server of team {}.", command, team); - try { - new ProcessBuilder() - .directory(serverDir().toFile()) - .redirectOutput(ProcessBuilder.Redirect.to(processLogFile("send"))) - .command(List.of( - "screen", - "-S", - screenName(), - "-p", - "0", - "-X", - "stuff", - "%s^M".formatted(command) - )) - .start(); - } catch (IOException e) { - throw new RuntimeException(e); - } + dockerService.sendCommand(team.id(), command); } private File processLogFile(String type) { @@ -284,10 +221,6 @@ private String teamName() { return team.meta().name().toLowerCase().replace(" ", "_"); } - private String screenName() { - return "team_%d_%d".formatted(team.id(), port); - } - public Team team() { return team; } @@ -315,60 +248,8 @@ public Path logFile() { public boolean replaceWorld(Path newWorld) { log.info("Replacing world"); - var worldDir = serverDir().resolve("world"); - var tempWorld = serverDir().resolve("t_world"); - - try (var zip = new ZipFile(newWorld.toFile())) { - log.info("Extracting zip file"); - zip.extractAll(tempWorld.toAbsolutePath().toString()); - } catch (IOException e) { - log.info("Failed to extract zip file", e); - return false; - } - - var copyWorld = tempWorld; - var dirFiles = List.of(tempWorld.toFile().listFiles()); - - var dirOffstet = 3; - - if (dirFiles.size() == 1) { - log.info("No world data found"); - copyWorld = tempWorld.resolve(dirFiles.get(0).getName()); - dirOffstet++; - } - - if (!copyWorld.resolve("region").toFile().exists()) { - log.warn("No region directory."); - return false; - } - - log.info("Deleting old world"); - if (!deleteDirectory(worldDir)) { - return false; - } - - if (copyWorld.resolve("session.lock").toFile().exists()) { - log.info("Found session lock. Deleting."); - copyWorld.resolve("session.lock").toFile().delete(); - } - - log.info("Copy new world data."); - try (var files = Files.walk(copyWorld)) { - Files.createDirectories(worldDir); - for (var sourceTarget : files.toList()) { - // skip root dir - if (sourceTarget.getNameCount() == dirOffstet) continue; - var filePath = sourceTarget.subpath(dirOffstet, sourceTarget.getNameCount()); - var serverTarget = worldDir.resolve(filePath); - Files.copy(sourceTarget, serverTarget, StandardCopyOption.REPLACE_EXISTING); - } - } catch (IOException e) { - log.error("Could not copy world", e); - return false; - } - - log.info("Cleaning up temp world"); - return deleteDirectory(tempWorld); + dockerService.copyArchiveToContainer(team.id(), newWorld, serverDir()); + return true; } public boolean deleteDirectory(Path path) { diff --git a/bot/src/main/resources/log4j2.xml b/bot/src/main/resources/log4j2.xml index f05b7d95..aa2c29e1 100644 --- a/bot/src/main/resources/log4j2.xml +++ b/bot/src/main/resources/log4j2.xml @@ -1,5 +1,5 @@ - + @@ -16,28 +16,6 @@ - - - - - - - - - - - - - - - - - - - @@ -45,13 +23,9 @@ - + - - - - diff --git a/dev.Dockerfile b/dev.Dockerfile new file mode 100644 index 00000000..026dbe69 --- /dev/null +++ b/dev.Dockerfile @@ -0,0 +1,34 @@ +FROM gradle:jdk25-alpine as build +WORKDIR /home/gradle + +COPY --chown=gradle:gradle settings.gradle* build.gradle* gradle.properties* ./ +COPY --chown=gradle:gradle bot/build.gradle* ./bot/ +COPY --chown=gradle:gradle plugin-paper/build.gradle* ./plugin-paper/ + +RUN gradle dependencies --no-daemon || true + +COPY --chown=gradle:gradle . . + +RUN gradle :bot:build :plugin-paper:build --no-daemon + +# We use a jammy image because we need some more stuff than alpine provides +FROM eclipse-temurin:25-jammy as runtime +WORKDIR /app + +# Setting up the bot +COPY --from=build /home/gradle/bot/build/libs/bot-*-all.jar ./bot.jar +RUN mkdir plugins +RUN mkdir servers +RUN mkdir template +RUN mkdir template/plugins +COPY docker/resources/bot/wait.sh . +# Copy the plugin jam plugin into the template. +COPY --from=build /home/gradle/plugin-paper/build/libs/plugin-paper-*-all.jar ./bot/template/plugins/pluginjam.jar + +COPY docker/resources/docker-entrypoint.sh . + +EXPOSE 8080 + +HEALTHCHECK CMD curl --fail http://localhost:8080/swagger-ui || exit 1 + +ENTRYPOINT ["bash", "docker-entrypoint.sh"] diff --git a/dev.compose.yml b/dev.compose.yml index d9aa4790..e1de3f2e 100644 --- a/dev.compose.yml +++ b/dev.compose.yml @@ -1,17 +1,22 @@ services: bot: - build: . + build: + context: . + dockerfile: dev.Dockerfile volumes: - ./data/bot/config:/app/config - ./data/bot/plugins:/app/plugins - ./data/bot/servers:/app/servers - ./data/bot/template:/app/template + - /var/run/docker.sock:/var/run/docker.sock + group_add: + - 131 database: image: postgres:16 environment: POSTGRES_PASSWORD: postgres ports: - - 5433:5432 + - "127.0.0.1:5433:5432" velocity: image: itzg/mc-proxy:latest tty: true diff --git a/settings.gradle.kts b/settings.gradle.kts index c4470c07..92977b96 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -22,6 +22,11 @@ dependencyResolutionManagement { library("log4j-slf4j2", "org.apache.logging.log4j", "log4j-slf4j2-impl").versionRef("log4j") bundle("logging", listOf("slf4j", "log4j-core", "log4j-slf4j2")) + version("docker", "3.7.1") + library("docker-api", "com.github.docker-java", "docker-java-core").versionRef("docker") + library("docker-transport", "com.github.docker-java", "docker-java-transport-httpclient5").versionRef("docker") + bundle("docker", listOf("docker-api", "docker-transport")) + version("javalin", "7.2.2") library("javalin-core", "io.javalin", "javalin").versionRef("javalin") From d8ee5a34e59cc9cd2615db3903c7cf7bfc753c08 Mon Sep 17 00:00:00 2001 From: Nora <46890129+RainbowDashLabs@users.noreply.github.com> Date: Sat, 6 Jun 2026 19:02:09 +0200 Subject: [PATCH 2/8] Fix logging --- bot/src/main/resources/log4j2.xml | 4 ++-- dev.Dockerfile | 3 +++ dev.compose.yml | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/bot/src/main/resources/log4j2.xml b/bot/src/main/resources/log4j2.xml index aa2c29e1..79a4c7c7 100644 --- a/bot/src/main/resources/log4j2.xml +++ b/bot/src/main/resources/log4j2.xml @@ -1,10 +1,10 @@ - + diff --git a/dev.Dockerfile b/dev.Dockerfile index 026dbe69..f202382d 100644 --- a/dev.Dockerfile +++ b/dev.Dockerfile @@ -29,6 +29,9 @@ COPY docker/resources/docker-entrypoint.sh . EXPOSE 8080 +COPY bot/src/main/resources/log4j2.xml config/log4j2.xml +RUN touch config/config.yml + HEALTHCHECK CMD curl --fail http://localhost:8080/swagger-ui || exit 1 ENTRYPOINT ["bash", "docker-entrypoint.sh"] diff --git a/dev.compose.yml b/dev.compose.yml index e1de3f2e..daa2ee9c 100644 --- a/dev.compose.yml +++ b/dev.compose.yml @@ -4,7 +4,7 @@ services: context: . dockerfile: dev.Dockerfile volumes: - - ./data/bot/config:/app/config + - ./data/bot/config/config.yml:/app/config/config.yml - ./data/bot/plugins:/app/plugins - ./data/bot/servers:/app/servers - ./data/bot/template:/app/template From 08d0206e29a390e3eb2ee002816b3c48b74db4d2 Mon Sep 17 00:00:00 2001 From: TheZexquex Date: Sat, 6 Jun 2026 19:10:33 +0200 Subject: [PATCH 3/8] Fix check if server exists --- bot/src/main/java/de/chojo/gamejam/server/DockerService.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bot/src/main/java/de/chojo/gamejam/server/DockerService.java b/bot/src/main/java/de/chojo/gamejam/server/DockerService.java index bad536d5..72fe3a40 100644 --- a/bot/src/main/java/de/chojo/gamejam/server/DockerService.java +++ b/bot/src/main/java/de/chojo/gamejam/server/DockerService.java @@ -114,9 +114,11 @@ public void sendCommand(int teamId, String command) { public boolean exists(int teamId) { return dockerClient.listContainersCmd() .withShowAll(true) + .withNameFilter(List.of(containerName(teamId))) .exec() .stream() - .anyMatch(container -> container.getId().startsWith(containerName(teamId))); + .findAny() + .isPresent(); } public Optional container(int teamId) { From 4dd813b3faf341b259dbb045242b2254eadd097f Mon Sep 17 00:00:00 2001 From: TheZexquex Date: Mon, 8 Jun 2026 19:41:06 +0200 Subject: [PATCH 4/8] Switch some more things to docker (stats, console commands, ...) --- .../java/de/chojo/gamejam/api/v1/Server.java | 4 +- .../commands/server/configure/MaxPlayers.java | 3 +- .../commands/server/configure/Message.java | 2 +- .../server/configure/SpectatorOverflow.java | 3 +- .../commands/server/configure/Whitelist.java | 3 +- .../gamejam/commands/server/process/Log.java | 20 ++--- .../commands/server/process/Restart.java | 4 +- .../gamejam/commands/server/process/Stop.java | 2 +- .../handler/restart/RestartAll.java | 4 +- .../handler/restart/RestartTeam.java | 2 +- .../serveradmin/handler/stop/StopAll.java | 4 +- .../serveradmin/handler/stop/StopTeam.java | 2 +- .../configuration/elements/Docker.java | 7 +- .../configuration/elements/Plugins.java | 5 ++ .../chojo/gamejam/server/DockerService.java | 84 +++++++++++++++-- .../chojo/gamejam/server/ServerService.java | 6 +- .../de/chojo/gamejam/server/TeamServer.java | 89 +++++++------------ compose.yml | 12 +++ dev.compose.yml | 13 +++ 19 files changed, 173 insertions(+), 96 deletions(-) diff --git a/bot/src/main/java/de/chojo/gamejam/api/v1/Server.java b/bot/src/main/java/de/chojo/gamejam/api/v1/Server.java index 2287ac1b..43567bfc 100644 --- a/bot/src/main/java/de/chojo/gamejam/api/v1/Server.java +++ b/bot/src/main/java/de/chojo/gamejam/api/v1/Server.java @@ -107,9 +107,9 @@ private void handle(@NotNull Context ctx) { ctx.status(HttpStatus.ACCEPTED); String restart = ctx.queryParam("restart"); - if ("true".equals(restart) && teamServer.running()) { + if ("true".equals(restart) && teamServer.isRunning()) { teamServer.restart(); - } else if (teamServer.running()) { + } else if (teamServer.isRunning()) { teamServer.send("say Plugin Updated"); } } diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/configure/MaxPlayers.java b/bot/src/main/java/de/chojo/gamejam/commands/server/configure/MaxPlayers.java index 8008c8f2..d85e6b27 100644 --- a/bot/src/main/java/de/chojo/gamejam/commands/server/configure/MaxPlayers.java +++ b/bot/src/main/java/de/chojo/gamejam/commands/server/configure/MaxPlayers.java @@ -7,7 +7,6 @@ package de.chojo.gamejam.commands.server.configure; import de.chojo.gamejam.commands.server.Server; -import de.chojo.gamejam.server.TeamServer; import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; import de.chojo.jdautil.util.Futures; import de.chojo.jdautil.wrapper.EventContext; @@ -37,7 +36,7 @@ public void onSlashCommand(SlashCommandInteractionEvent event, EventContext cont .POST(HttpRequest.BodyPublishers.ofString(String.valueOf(event.getOption("amount").getAsInt()))) .build(); - if (!teamServer.running()) { + if (!teamServer.isRunning()) { event.reply(context.localize("error.servernotrunning")).queue(); return; } diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/configure/Message.java b/bot/src/main/java/de/chojo/gamejam/commands/server/configure/Message.java index 9491627e..aa124fd4 100644 --- a/bot/src/main/java/de/chojo/gamejam/commands/server/configure/Message.java +++ b/bot/src/main/java/de/chojo/gamejam/commands/server/configure/Message.java @@ -37,7 +37,7 @@ public void onSlashCommand(SlashCommandInteractionEvent event, EventContext cont var teamServer = optServer.get(); - if (!teamServer.running()) { + if (!teamServer.isRunning()) { event.reply(context.localize("error.servernotrunning")).queue(); return; } diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/configure/SpectatorOverflow.java b/bot/src/main/java/de/chojo/gamejam/commands/server/configure/SpectatorOverflow.java index b7a987fa..78292373 100644 --- a/bot/src/main/java/de/chojo/gamejam/commands/server/configure/SpectatorOverflow.java +++ b/bot/src/main/java/de/chojo/gamejam/commands/server/configure/SpectatorOverflow.java @@ -7,7 +7,6 @@ package de.chojo.gamejam.commands.server.configure; import de.chojo.gamejam.commands.server.Server; -import de.chojo.gamejam.server.TeamServer; import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; import de.chojo.jdautil.util.Futures; import de.chojo.jdautil.wrapper.EventContext; @@ -34,7 +33,7 @@ public void onSlashCommand(SlashCommandInteractionEvent event, EventContext cont var teamServer = optServer.get(); - if (!teamServer.running()) { + if (!teamServer.isRunning()) { event.reply(context.localize("error.servernotrunning")).queue(); return; } diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/configure/Whitelist.java b/bot/src/main/java/de/chojo/gamejam/commands/server/configure/Whitelist.java index 8568d957..dd17ff28 100644 --- a/bot/src/main/java/de/chojo/gamejam/commands/server/configure/Whitelist.java +++ b/bot/src/main/java/de/chojo/gamejam/commands/server/configure/Whitelist.java @@ -7,7 +7,6 @@ package de.chojo.gamejam.commands.server.configure; import de.chojo.gamejam.commands.server.Server; -import de.chojo.gamejam.server.TeamServer; import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; import de.chojo.jdautil.util.Futures; import de.chojo.jdautil.wrapper.EventContext; @@ -34,7 +33,7 @@ public void onSlashCommand(SlashCommandInteractionEvent event, EventContext cont var teamServer = optServer.get(); - if (!teamServer.running()) { + if (!teamServer.isRunning()) { event.reply(context.localize("error.servernotrunning")).queue(); return; } diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/process/Log.java b/bot/src/main/java/de/chojo/gamejam/commands/server/process/Log.java index 60d0d896..ffa97194 100644 --- a/bot/src/main/java/de/chojo/gamejam/commands/server/process/Log.java +++ b/bot/src/main/java/de/chojo/gamejam/commands/server/process/Log.java @@ -12,7 +12,10 @@ import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.utils.FileUpload; +import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; public class Log implements SlashHandler { @@ -27,16 +30,13 @@ public void onSlashCommand(SlashCommandInteractionEvent event, EventContext cont var optServer = server.getServer(event, context); if(optServer.isEmpty())return; var teamServer = optServer.get(); - var logFile = teamServer.logFile(); - String content; - try { - content = Files.readString(logFile); - } catch (IOException e) { - content = ""; + var logs = teamServer.logs(0); + var content = logs.substring(Math.max(logs.length() - 1950, 0)); + try(InputStream inputStream = new ByteArrayInputStream(logs.getBytes(StandardCharsets.UTF_8))) { + event.reply("```log%n%s%n```".formatted(content)) + .addFiles(FileUpload.fromData(inputStream, "latest.log")) + .queue(); + } catch (IOException _) { } - content = content.substring(Math.max(content.length() - 1950, 0)); - event.reply("```log%n%s%n```".formatted(content)) - .addFiles(FileUpload.fromData(logFile, "latest.log")) - .queue(); } } diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/process/Restart.java b/bot/src/main/java/de/chojo/gamejam/commands/server/process/Restart.java index 487b31b5..14542bb3 100644 --- a/bot/src/main/java/de/chojo/gamejam/commands/server/process/Restart.java +++ b/bot/src/main/java/de/chojo/gamejam/commands/server/process/Restart.java @@ -24,8 +24,8 @@ public void onSlashCommand(SlashCommandInteractionEvent event, EventContext cont if(optServer.isEmpty())return; var teamServer = optServer.get(); if (teamServer.exists()) { - teamServer.stop(true) - .thenRun(() -> event.getHook().editOriginal(context.localize("command.server.process.restart.message.restarted")).queue()); + teamServer.stop(); + event.getHook().editOriginal(context.localize("command.server.process.restart.message.restarted")).queue(); event.reply(context.localize("command.server.process.restart.message.restarting")).queue(); } } diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/process/Stop.java b/bot/src/main/java/de/chojo/gamejam/commands/server/process/Stop.java index 9e28f917..fc7cb3f0 100644 --- a/bot/src/main/java/de/chojo/gamejam/commands/server/process/Stop.java +++ b/bot/src/main/java/de/chojo/gamejam/commands/server/process/Stop.java @@ -25,7 +25,7 @@ public void onSlashCommand(SlashCommandInteractionEvent event, EventContext cont var teamServer = optServer.get(); if (teamServer.exists()) { event.reply(context.localize("command.server.process.stop.message.stopping")).queue(); - teamServer.stop(false).thenRun(() -> event.getHook().editOriginal( + teamServer.stop().thenRun(() -> event.getHook().editOriginal( context.localize("command.server.process.stop.message.stopped")).queue()); } } diff --git a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/restart/RestartAll.java b/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/restart/RestartAll.java index 8472b89f..7b35cd11 100644 --- a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/restart/RestartAll.java +++ b/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/restart/RestartAll.java @@ -34,9 +34,9 @@ public void onSlashCommand(SlashCommandInteractionEvent event, EventContext cont var count = jam.teams().teams().stream() .map(serverService::get) .filter(server -> { - var running = server.running(); + var running = server.isRunning(); if (running) { - server.stop(true); + server.stop(); } return running; }) diff --git a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/restart/RestartTeam.java b/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/restart/RestartTeam.java index e712f51b..57190df4 100644 --- a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/restart/RestartTeam.java +++ b/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/restart/RestartTeam.java @@ -42,7 +42,7 @@ public void onSlashCommand(SlashCommandInteractionEvent event, EventContext cont } var started = optTeam.map(serverService::get).map(server -> { - var running = server.running(); + var running = server.isRunning(); server.restart(); return running; }).orElse(false); diff --git a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/stop/StopAll.java b/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/stop/StopAll.java index c6939e3c..bdafd25e 100644 --- a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/stop/StopAll.java +++ b/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/stop/StopAll.java @@ -34,8 +34,8 @@ public void onSlashCommand(SlashCommandInteractionEvent event, EventContext cont var count = jam.teams().teams().stream() .map(serverService::get) .map(server -> { - var running = server.running(); - server.stop(false); + var running = server.isRunning(); + server.stop(); return running; }) .filter(v -> v) diff --git a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/stop/StopTeam.java b/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/stop/StopTeam.java index f6d25f81..cbf77bb8 100644 --- a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/stop/StopTeam.java +++ b/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/stop/StopTeam.java @@ -42,7 +42,7 @@ public void onSlashCommand(SlashCommandInteractionEvent event, EventContext cont } var started = optTeam.map(serverService::get).map(server -> { - var running = server.running(); + var running = server.isRunning(); server.stop(); return running; }).orElse(false); diff --git a/bot/src/main/java/de/chojo/gamejam/configuration/elements/Docker.java b/bot/src/main/java/de/chojo/gamejam/configuration/elements/Docker.java index c7c5958a..3471c18c 100644 --- a/bot/src/main/java/de/chojo/gamejam/configuration/elements/Docker.java +++ b/bot/src/main/java/de/chojo/gamejam/configuration/elements/Docker.java @@ -6,7 +6,6 @@ package de.chojo.gamejam.configuration.elements; - @SuppressWarnings({"FieldMayBeFinal", "FieldCanBeLocal"}) public class Docker { private String host = "unix:///var/run/docker.sock"; @@ -16,6 +15,7 @@ public class Docker { private String registryPassword; private String registryEmail; private String registryUrl; + private String networkName = "plugin-jam-network"; public String getHost() { return host; @@ -44,4 +44,9 @@ public String getRegistryEmail() { public String getRegistryUrl() { return registryUrl; } + + public String getNetworkName() { + return networkName; + } + } \ No newline at end of file diff --git a/bot/src/main/java/de/chojo/gamejam/configuration/elements/Plugins.java b/bot/src/main/java/de/chojo/gamejam/configuration/elements/Plugins.java index 7ea8a949..db7a00bd 100644 --- a/bot/src/main/java/de/chojo/gamejam/configuration/elements/Plugins.java +++ b/bot/src/main/java/de/chojo/gamejam/configuration/elements/Plugins.java @@ -16,11 +16,16 @@ @SuppressWarnings({"FieldMayBeFinal", "FieldCanBeLocal"}) public class Plugins { private String pluginDir = "plugins"; + private List defaultPlugins = List.of(); public String pluginDir() { return pluginDir; } + public List defaultPlugins() { + return defaultPlugins; + } + private Path pluginPath() { return Path.of(pluginDir); } diff --git a/bot/src/main/java/de/chojo/gamejam/server/DockerService.java b/bot/src/main/java/de/chojo/gamejam/server/DockerService.java index 72fe3a40..312b3e11 100644 --- a/bot/src/main/java/de/chojo/gamejam/server/DockerService.java +++ b/bot/src/main/java/de/chojo/gamejam/server/DockerService.java @@ -11,9 +11,11 @@ import com.github.dockerjava.core.DefaultDockerClientConfig; import com.github.dockerjava.core.DockerClientConfig; import com.github.dockerjava.core.DockerClientImpl; +import com.github.dockerjava.api.async.ResultCallback; import com.github.dockerjava.httpclient5.ApacheDockerHttpClient; import com.github.dockerjava.transport.DockerHttpClient; import de.chojo.gamejam.configuration.elements.Docker; +import de.chojo.gamejam.configuration.elements.Plugins; import org.slf4j.Logger; import java.io.IOException; @@ -30,8 +32,12 @@ public class DockerService { private static final Logger log = getLogger(DockerService.class); private static final String DOCKER_IMAGE = "itzg/minecraft-server:latest"; private static final String DOCKER_VOLUME_DATA_DIR = "/data"; + private final String networkName; + private final String pluginUrls; - public DockerService(Docker dockerConfig) { + public DockerService(Docker dockerConfig, Plugins pluginsConfig) { + this.networkName = dockerConfig.getNetworkName(); + this.pluginUrls = String.join(",", pluginsConfig.defaultPlugins()); this.dockerClientConfig = DefaultDockerClientConfig .createDefaultConfigBuilder() .withDockerHost(dockerConfig.getHost()) @@ -54,6 +60,20 @@ public void initDockerClient() { .build(); dockerClient = DockerClientImpl.getInstance(dockerClientConfig, httpClient); + ensureNetwork(); + } + + private void ensureNetwork() { + var networks = dockerClient.listNetworksCmd() + .withNameFilter(networkName) + .exec(); + if (networks.stream().noneMatch(n -> n.getName().equals(networkName))) { + dockerClient.createNetworkCmd() + .withName(networkName) + .withDriver("bridge") + .exec(); + log.info("Created docker network {}", networkName); + } } public void shutdown() throws IOException { @@ -69,8 +89,11 @@ public void provisionServer(int teamId) { HostConfig hostConfig = HostConfig.newHostConfig() .withBinds(new Bind(volumeName(teamId), new Volume(DOCKER_VOLUME_DATA_DIR))); + hostConfig.withNetworkMode(networkName); + dockerClient.createContainerCmd(DOCKER_IMAGE) .withName(containerName(teamId)) + .withEnv("EULA=TRUE", "TYPE=PAPER", "VERSION=26.1.2", String.format("PLUGINS=%s", pluginUrls)) .withHostConfig(hostConfig) .exec(); log.info("Server provisioned for team with container name {} and volume name {}", containerName(teamId), volumeName(teamId)); @@ -96,7 +119,7 @@ public void restartServer(int teamId) { dockerClient.restartContainerCmd(containerName(teamId)).exec(); } - public boolean running(int teamId) { + public boolean isRunning(int teamId) { return dockerClient.listContainersCmd() .withShowAll(true) .withNameFilter(List.of(containerName(teamId))) @@ -106,11 +129,31 @@ public boolean running(int teamId) { } public void sendCommand(int teamId, String command) { - dockerClient.execCreateCmd(containerName(teamId)) - .withCmd(String.format("mc rcon-cli %s", command)) - .exec(); + var container = container(teamId); + if (container.isEmpty()) { + log.error("Container not found for team {}", teamId); + return; + } + var execId = dockerClient.execCreateCmd(container.get().getId()) + .withAttachStdout(true) + .withAttachStderr(true) + .withCmd("rcon-cli", command) + .exec() + .getId(); + try { + dockerClient.execStartCmd(execId) + .exec(new ResultCallback.Adapter() { + @Override + public void onNext(Frame frame) { + log.info("rcon-cli response for team {}: {}", teamId, new String(frame.getPayload())); + } + }) + .awaitCompletion(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + log.error("Interrupted while sending command to team {}", teamId, e); + } } - public boolean exists(int teamId) { return dockerClient.listContainersCmd() .withShowAll(true) @@ -130,6 +173,35 @@ public Optional container(int teamId) { .findFirst(); } + public String logs(int teamId, int tail) { + var callback = new ResultCallback.Adapter() { + private final StringBuilder logs = new StringBuilder(); + + @Override + public void onNext(Frame frame) { + logs.append(new String(frame.getPayload())); + } + + public String getLogs() { + return logs.toString(); + } + }; + + try { + dockerClient.logContainerCmd(containerName(teamId)) + .withStdOut(true) + .withStdErr(true) + .withTail(tail) + .exec(callback) + .awaitCompletion(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + log.error("Interrupted while retrieving logs for team {}", teamId, e); + } + + return callback.getLogs(); + } + public void copyArchiveToContainer(int teamId, Path source, Path destination) { dockerClient.copyArchiveToContainerCmd(containerName(teamId)) .withHostResource(source.toString()) diff --git a/bot/src/main/java/de/chojo/gamejam/server/ServerService.java b/bot/src/main/java/de/chojo/gamejam/server/ServerService.java index 794e8871..dd7d73dd 100644 --- a/bot/src/main/java/de/chojo/gamejam/server/ServerService.java +++ b/bot/src/main/java/de/chojo/gamejam/server/ServerService.java @@ -48,7 +48,7 @@ private ServerService(Configuration configuration) { this.configuration = configuration; IntStream.rangeClosed(configuration.serverManagement().minPort(), configuration.serverManagement().maxPort()) .forEach(freePorts::add); - this.dockerService = new DockerService(configuration.docker()); + this.dockerService = new DockerService(configuration.docker(), configuration.plugins()); this.dockerService.initDockerClient(); } @@ -61,7 +61,7 @@ public void shutdown() { @Override public void run() { for (var value : server.values()) { - if (!value.running()) continue; + if (!value.isRunning()) continue; try { value.serverRequests() .ifPresent(server -> { @@ -127,7 +127,7 @@ public CompletableFuture syncVelocity() { var team = optTeam.get(); log.info("Registered server for team {} with id {}", team.meta().name(), team.id()); var teamServer = new TeamServer(this, dockerService, team, configuration, registration.port(), registration.apiPort()); - teamServer.running(true); + //teamServer.running(true); server.put(team, teamServer); freePorts.removeElement(registration.apiPort()); freePorts.removeElement(registration.port()); diff --git a/bot/src/main/java/de/chojo/gamejam/server/TeamServer.java b/bot/src/main/java/de/chojo/gamejam/server/TeamServer.java index 44c89680..a11defd5 100644 --- a/bot/src/main/java/de/chojo/gamejam/server/TeamServer.java +++ b/bot/src/main/java/de/chojo/gamejam/server/TeamServer.java @@ -30,7 +30,6 @@ import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.Comparator; -import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; @@ -41,34 +40,13 @@ public class TeamServer { private static final HttpClient http = HttpClient.newHttpClient(); private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH:mm:ss.SSSS"); private static final Logger log = getLogger(TeamServer.class); + private static final int DEFAULT_API_PORT = 30000; private final ServerService serverService; private final DockerService dockerService; private final Team team; private final Configuration configuration; private final int port; private final int apiPort; - private static final List AIKAR = List.of( - "-XX:+ParallelRefProcEnabled", - "-XX:MaxGCPauseMillis=200", - "-XX:+UnlockExperimentalVMOptions", - "-XX:+DisableExplicitGC", - "-XX:+AlwaysPreTouch", - "-XX:G1NewSizePercent=30", - "-XX:G1MaxNewSizePercent=40", "-XX:G1HeapRegionSize=8M", - "-XX:G1ReservePercent=20", - "-XX:G1HeapWastePercent=5", - "-XX:G1MixedGCCountTarget=4", - "-XX:InitiatingHeapOccupancyPercent=15", - "-XX:G1MixedGCLiveThresholdPercent=90", - "-XX:G1RSetUpdatingPauseTimePercent=5", - "-XX:SurvivorRatio=32", - "-XX:+PerfDisableSharedMem", - "-XX:MaxTenuringThreshold=1", - "-Dusing.aikars.flags=https://mcflags.emc.gs", - "-Daikars.new.flags=true" - ); - private boolean running; - public TeamServer(ServerService serverService, DockerService dockerService, Team team, Configuration configuration, int port, int apiPort) { this.serverService = serverService; @@ -79,8 +57,8 @@ public TeamServer(ServerService serverService, DockerService dockerService, Team this.apiPort = apiPort; } - public boolean running() { - return running; + public boolean isRunning() { + return dockerService.isRunning(team.id()); } public boolean exists() { @@ -159,9 +137,9 @@ private void writeTemplate() throws IOException { */ public boolean purge() throws IOException { if (!exists()) return false; - if(running()) { + if (isRunning()) { log.info("Stopping server for team {}", team); - stop().join(); + stop(); } log.info("Purging server of team {}", team); dockerService.destroyServer(team.id()); @@ -169,31 +147,24 @@ public boolean purge() throws IOException { } public boolean start() { - if (!exists() || running()) return false; + if (!exists() || isRunning()) return false; log.info("Starting server server of team {}", team); dockerService.startServer(team.id()); - running = true; return true; } - public CompletableFuture stop() { - return stop(false); - } - - public CompletableFuture restart() { - return stop(true); + public CompletableFuture restart() { + return CompletableFuture.runAsync(() -> { + dockerService.restartServer(team.id()); + log.info("Starting server of team {}", team); + }); } - public CompletableFuture stop(boolean restart) { - if (!running) { - return CompletableFuture.completedFuture(null); - } - running = false; - CompletableFuture future = CompletableFuture.runAsync(() -> { + public CompletableFuture stop() { + return CompletableFuture.runAsync(() -> { dockerService.stopServer(team.id()); + log.info("Stopping server of team {}", team); }); - log.info("Stopping server of team {}", team); - return future; } public void send(String command) { @@ -236,14 +207,15 @@ public int apiPort() { @Override public String toString() { return "TeamServer{" + - "team=" + team + - ", port=" + port + - ", apiPort=" + apiPort + - '}'; + "team=" + team + + ", port=" + port + + ", apiPort=" + apiPort + + '}'; } - public Path logFile() { - return serverDir().resolve("logs").resolve("latest.log"); + public String logs(int tail) { + + return dockerService.logs(team.id(), tail); } public boolean replaceWorld(Path newWorld) { @@ -252,6 +224,7 @@ public boolean replaceWorld(Path newWorld) { return true; } + //TODO: replace with docker version public boolean deleteDirectory(Path path) { try (var files = Files.walk(path)) { files.sorted(Comparator.reverseOrder()) @@ -294,7 +267,7 @@ public CompletableFuture detailStatus(EventContext context) { if (!exists()) { builder.setDescription("teamserver.message.detailstatus.nonexisting.description"); } else { - if (running()) { + if (isRunning()) { builder.setDescription("teamserver.message.detailstatus.existing.description") .addField("word.ports", "$word.server$: %d%n$word.api$: %d".formatted(port, apiPort), true); stats().ifPresent(stats -> { @@ -318,7 +291,7 @@ public CompletableFuture detailStatus(EventContext context) { public String status() { var status = statusEmoji(); var ports = ""; - if (exists() && running()) { + if (exists() && isRunning()) { ports = "Server: %s Api: %s".formatted(port, apiPort); } return "%s %s %s".formatted(status, team, ports); @@ -347,23 +320,23 @@ public Optional stats() { } public HttpRequest.Builder requestBuilder(String path) { - return HttpRequest.newBuilder(URI.create("http://localhost:%d/%s".formatted(apiPort(), path))); + return HttpRequest.newBuilder(URI.create("http://%s:%d/%s".formatted(containerName(), DEFAULT_API_PORT, path))); } public HttpRequest.Builder requestBuilder(String path, String query) { - return HttpRequest.newBuilder(URI.create("http://localhost:%d/%s?%s".formatted(apiPort(), path, query))); + return HttpRequest.newBuilder(URI.create("http://%s:%d/%s?%s".formatted(containerName(), DEFAULT_API_PORT, path, query))); } - public HttpClient http() { - return http; + public String containerName() { + return dockerService.containerName(team.id()); } - public void running(boolean running) { - this.running = running; + public HttpClient http() { + return http; } private String statusEmoji() { - if (exists() && running()) return "🟢"; + if (exists() && isRunning()) return "🟢"; if (exists()) return "🟡"; return "🔴"; } diff --git a/compose.yml b/compose.yml index fd3f0bd4..4188cbf4 100644 --- a/compose.yml +++ b/compose.yml @@ -6,8 +6,11 @@ services: - ./data/bot/plugins:/app/plugins - ./data/bot/servers:/app/servers - ./data/bot/template:/app/template + - /var/run/docker.sock:/var/run/docker.sock depends_on: - + networks: + - plugin-jam-network velocity: image: itzg/mc-proxy:latest tty: true @@ -18,6 +21,8 @@ services: volumes: - ./data/velocity/config:/config - ./data/velocity/plugins:/plugins + networks: + - plugin-jam-network lobby: image: itzg/minecraft-server:latest stdin_open: true @@ -28,3 +33,10 @@ services: MEMORY: 2G volumes: - ./data/lobby:/data + networks: + - plugin-jam-network + +networks: + plugin-jam-network: + name: plugin-jam-network + driver: bridge diff --git a/dev.compose.yml b/dev.compose.yml index daa2ee9c..18f4b6f6 100644 --- a/dev.compose.yml +++ b/dev.compose.yml @@ -11,12 +11,16 @@ services: - /var/run/docker.sock:/var/run/docker.sock group_add: - 131 + networks: + - plugin-jam-network database: image: postgres:16 environment: POSTGRES_PASSWORD: postgres ports: - "127.0.0.1:5433:5432" + networks: + - plugin-jam-network velocity: image: itzg/mc-proxy:latest tty: true @@ -27,6 +31,8 @@ services: volumes: - ./data/velocity/config:/config - ./data/velocity/plugins:/plugins + networks: + - plugin-jam-network lobby: image: itzg/minecraft-server:latest stdin_open: true @@ -37,3 +43,10 @@ services: MEMORY: 2G volumes: - ./data/lobby:/data + networks: + - plugin-jam-network + +networks: + plugin-jam-network: + name: plugin-jam-network + driver: bridge From df3e36fe457221766fcb09163dba765aed4a1b25 Mon Sep 17 00:00:00 2001 From: TheZexquex Date: Tue, 9 Jun 2026 22:16:36 +0200 Subject: [PATCH 5/8] Custom docker images for the team servers and velocity, more refactoring and removing unused code. --- Dockerfile => Dockerfile.bot | 0 dev.Dockerfile => Dockerfile.bot.dev | 3 +- Dockerfile.mc | 15 +++ Dockerfile.velocity | 14 +++ bot/src/main/java/de/chojo/gamejam/Bot.java | 2 +- .../jamadmin/handler/jam/JamStart.java | 2 +- .../gamejam/commands/server/process/Log.java | 2 +- .../commands/serveradmin/ServerAdmin.java | 8 -- .../handler/refresh/RefreshAll.java | 43 ------- .../handler/refresh/RefreshTeam.java | 68 ----------- .../configuration/elements/Docker.java | 28 ++++- .../de/chojo/gamejam/message/EmbedHelper.java | 47 ++++++++ .../chojo/gamejam/server/DockerService.java | 30 +++-- .../chojo/gamejam/server/ServerService.java | 57 +-------- .../de/chojo/gamejam/server/TeamServer.java | 113 ++---------------- dev.compose.yml => compose.dev.yml | 23 +++- compose.yml | 16 ++- docker/docker-compose.yml | 28 ----- docker/resources/docker-entrypoint.sh | 2 +- 19 files changed, 163 insertions(+), 338 deletions(-) rename Dockerfile => Dockerfile.bot (100%) rename dev.Dockerfile => Dockerfile.bot.dev (92%) create mode 100644 Dockerfile.mc create mode 100644 Dockerfile.velocity delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/refresh/RefreshAll.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/refresh/RefreshTeam.java create mode 100644 bot/src/main/java/de/chojo/gamejam/message/EmbedHelper.java rename dev.compose.yml => compose.dev.yml (67%) delete mode 100644 docker/docker-compose.yml diff --git a/Dockerfile b/Dockerfile.bot similarity index 100% rename from Dockerfile rename to Dockerfile.bot diff --git a/dev.Dockerfile b/Dockerfile.bot.dev similarity index 92% rename from dev.Dockerfile rename to Dockerfile.bot.dev index f202382d..4b25a086 100644 --- a/dev.Dockerfile +++ b/Dockerfile.bot.dev @@ -29,8 +29,7 @@ COPY docker/resources/docker-entrypoint.sh . EXPOSE 8080 -COPY bot/src/main/resources/log4j2.xml config/log4j2.xml -RUN touch config/config.yml +COPY bot/src/main/resources/log4j2.xml log4j2.xml HEALTHCHECK CMD curl --fail http://localhost:8080/swagger-ui || exit 1 diff --git a/Dockerfile.mc b/Dockerfile.mc new file mode 100644 index 00000000..5c059088 --- /dev/null +++ b/Dockerfile.mc @@ -0,0 +1,15 @@ +FROM gradle:jdk25-alpine AS build +WORKDIR /home/gradle +COPY --chown=gradle:gradle settings.gradle* build.gradle* gradle.properties* ./ +COPY --chown=gradle:gradle bot/build.gradle* ./bot/ +COPY --chown=gradle:gradle plugin-api/build.gradle* ./plugin-api/ +COPY --chown=gradle:gradle plugin-paper/build.gradle* ./plugin-paper/ +COPY --chown=gradle:gradle plugin-velocity/build.gradle* ./plugin-velocity/ +RUN mkdir -p plugin-paper/Readme.md && \ + gradle dependencies --no-daemon || true +COPY --chown=gradle:gradle plugin-api ./plugin-api +COPY --chown=gradle:gradle plugin-paper ./plugin-paper +RUN gradle :plugin-paper:shadowJar --no-daemon + +FROM itzg/minecraft-server:stable-java25 +COPY --from=build /home/gradle/plugin-paper/build/libs/plugin-paper-*-all.jar /plugins/pluginjam.jar diff --git a/Dockerfile.velocity b/Dockerfile.velocity new file mode 100644 index 00000000..c87e9920 --- /dev/null +++ b/Dockerfile.velocity @@ -0,0 +1,14 @@ +FROM gradle:jdk25-alpine AS build +WORKDIR /home/gradle +COPY --chown=gradle:gradle settings.gradle* build.gradle* gradle.properties* ./ +COPY --chown=gradle:gradle bot/build.gradle* ./bot/ +COPY --chown=gradle:gradle plugin-api/build.gradle* ./plugin-api/ +COPY --chown=gradle:gradle plugin-paper/build.gradle* ./plugin-paper/ +COPY --chown=gradle:gradle plugin-velocity/build.gradle* ./plugin-velocity/ +RUN mkdir -p plugin-paper/Readme.md && \ + gradle dependencies --no-daemon || true +COPY --chown=gradle:gradle plugin-api ./plugin-api +COPY --chown=gradle:gradle plugin-velocity ./plugin-velocity +RUN gradle :plugin-velocity:shadowJar --no-daemon +FROM itzg/mc-proxy:latest +COPY --from=build /home/gradle/plugin-velocity/build/libs/plugin-velocity-*-all.jar /plugins/pluginjam.jar diff --git a/bot/src/main/java/de/chojo/gamejam/Bot.java b/bot/src/main/java/de/chojo/gamejam/Bot.java index 1cb918d3..de929ea3 100644 --- a/bot/src/main/java/de/chojo/gamejam/Bot.java +++ b/bot/src/main/java/de/chojo/gamejam/Bot.java @@ -243,7 +243,7 @@ private void initDb() throws IOException, SQLException { } private void initServer() throws IOException { - serverService = ServerService.create(createScheduledExecutor("Server ping", 1), configuration); + serverService = ServerService.create(configuration); var templateDir = Path.of(configuration.serverTemplate().templateDir()); var serverDir = Path.of(configuration.serverManagement().serverDir()); diff --git a/bot/src/main/java/de/chojo/gamejam/commands/jamadmin/handler/jam/JamStart.java b/bot/src/main/java/de/chojo/gamejam/commands/jamadmin/handler/jam/JamStart.java index 32296d60..ee317ca6 100644 --- a/bot/src/main/java/de/chojo/gamejam/commands/jamadmin/handler/jam/JamStart.java +++ b/bot/src/main/java/de/chojo/gamejam/commands/jamadmin/handler/jam/JamStart.java @@ -32,7 +32,7 @@ public void onSlashCommand(SlashCommandInteractionEvent event, EventContext cont event.reply(context.localize("command.start.message.activated")) .setEphemeral(true) .queue(); - + return; } event.reply(context.localize("error.noupcomingjam")).setEphemeral(true) .queue(); diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/process/Log.java b/bot/src/main/java/de/chojo/gamejam/commands/server/process/Log.java index ffa97194..68e46418 100644 --- a/bot/src/main/java/de/chojo/gamejam/commands/server/process/Log.java +++ b/bot/src/main/java/de/chojo/gamejam/commands/server/process/Log.java @@ -30,7 +30,7 @@ public void onSlashCommand(SlashCommandInteractionEvent event, EventContext cont var optServer = server.getServer(event, context); if(optServer.isEmpty())return; var teamServer = optServer.get(); - var logs = teamServer.logs(0); + var logs = teamServer.logs(); var content = logs.substring(Math.max(logs.length() - 1950, 0)); try(InputStream inputStream = new ByteArrayInputStream(logs.getBytes(StandardCharsets.UTF_8))) { event.reply("```log%n%s%n```".formatted(content)) diff --git a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/ServerAdmin.java b/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/ServerAdmin.java index 6a589224..a633f9af 100644 --- a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/ServerAdmin.java +++ b/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/ServerAdmin.java @@ -9,8 +9,6 @@ import de.chojo.gamejam.commands.serveradmin.handler.SyncVelocity; import de.chojo.gamejam.commands.serveradmin.handler.info.Detailed; import de.chojo.gamejam.commands.serveradmin.handler.info.Short; -import de.chojo.gamejam.commands.serveradmin.handler.refresh.RefreshAll; -import de.chojo.gamejam.commands.serveradmin.handler.refresh.RefreshTeam; import de.chojo.gamejam.commands.serveradmin.handler.restart.RestartAll; import de.chojo.gamejam.commands.serveradmin.handler.restart.RestartTeam; import de.chojo.gamejam.commands.serveradmin.handler.start.StartAll; @@ -47,12 +45,6 @@ public ServerAdmin(Guilds guilds, ServerService serverService) { .subCommand(SubCommand.of("team", "command.serveradmin.stop.team.description") .handler(new StopTeam(serverService, guilds)) .argument(Argument.text("team", "command.serveradmin.stop.team.options.team.description").asRequired().withAutoComplete()))) - .group(Group.of("refresh", "command.serveradmin.refresh.description") - .subCommand(SubCommand.of("all", "command.serveradmin.refresh.all.description") - .handler(new RefreshAll(serverService, guilds))) - .subCommand(SubCommand.of("team", "command.serveradmin.refresh.team.description") - .handler(new RefreshTeam(serverService, guilds)) - .argument(Argument.text("team", "command.serveradmin.refresh.team.options.team.description").asRequired().withAutoComplete()))) .group(Group.of("info", "command.serveradmin.info.description") .subCommand(SubCommand.of("short", "command.serveradmin.info.short.description") .handler(new Short(serverService, guilds))) diff --git a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/refresh/RefreshAll.java b/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/refresh/RefreshAll.java deleted file mode 100644 index 01078f79..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/refresh/RefreshAll.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.serveradmin.handler.refresh; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.server.ServerService; -import de.chojo.gamejam.server.TeamServer; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.localization.util.Replacement; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -public class RefreshAll implements SlashHandler { - private final ServerService serverService; - private final Guilds guilds; - - public RefreshAll(ServerService serverService, Guilds guilds) { - this.serverService = serverService; - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var currentJam = guilds.guild(event).jams().getCurrentJam(); - if (currentJam.isEmpty()) { - event.reply(context.localize("error.noactivejam")).queue(); - return; - } - var jam = currentJam.get(); - - var count = jam.teams().teams().stream() - .map(serverService::get) - .map(TeamServer::refresh) - .filter(v -> v) - .count(); - event.reply(context.localize("command.serveradmin.refresh.refreshall.message.refreshed", - Replacement.create("AMOUNT", count))).queue(); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/refresh/RefreshTeam.java b/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/refresh/RefreshTeam.java deleted file mode 100644 index 520b5a26..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/refresh/RefreshTeam.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.serveradmin.handler.refresh; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.server.ServerService; -import de.chojo.gamejam.server.TeamServer; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.localization.util.Replacement; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -import java.util.Collections; - -public class RefreshTeam implements SlashHandler { - private final ServerService serverService; - private final Guilds guilds; - - public RefreshTeam(ServerService serverService, Guilds guilds) { - this.serverService = serverService; - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var currentJam = guilds.guild(event).jams().getCurrentJam(); - if (currentJam.isEmpty()) { - event.reply(context.localize("error.noactivejam")).queue(); - return; - } - var jam = currentJam.get(); - - var optTeam = jam.teams().byName(event.getOption("team").getAsString()); - - if (optTeam.isEmpty()) { - event.reply(context.localize("error.unkownteam")).queue(); - return; - } - - var started = optTeam.map(serverService::get) - .map(TeamServer::refresh) - .orElse(false); - if (started) { - event.reply(context.localize("command.serveradmin.refresh.refreshteam.message.refreshed", - Replacement.create("TEAM", optTeam.get()))).queue(); - } else { - event.reply(context.localize("command.serveradmin.refresh.refreshteam.message.failed", - Replacement.create("TEAM", optTeam.get()))).queue(); - } - } - - @Override - public void onAutoComplete(CommandAutoCompleteInteractionEvent event, EventContext context) { - var guild = guilds.guild(event); - var option = event.getFocusedOption(); - if ("team".equals(option.getName())) { - var choices = guild.jams().nextOrCurrent() - .map(jam -> jam.teams().completeTeam(option.getValue())) - .orElse(Collections.emptyList()); - event.replyChoices(choices).queue(); - } - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/configuration/elements/Docker.java b/bot/src/main/java/de/chojo/gamejam/configuration/elements/Docker.java index 3471c18c..99d85426 100644 --- a/bot/src/main/java/de/chojo/gamejam/configuration/elements/Docker.java +++ b/bot/src/main/java/de/chojo/gamejam/configuration/elements/Docker.java @@ -10,12 +10,13 @@ public class Docker { private String host = "unix:///var/run/docker.sock"; private String certPath = "/home/user/.docker"; - private boolean tlsVerify = true; + private boolean tlsVerify = false; private String registryUsername; private String registryPassword; private String registryEmail; private String registryUrl; private String networkName = "plugin-jam-network"; + private String teamServerImage = "plugin-jam-mc-server:latest"; public String getHost() { return host; @@ -29,24 +30,39 @@ public boolean isTlsVerify() { return tlsVerify; } - public String getRegistryUsername() { + public String host() { + return host; + } + + public String certPath() { + return certPath; + } + + public boolean tlsVerify() { + return tlsVerify; + } + + public String registryUsername() { return registryUsername; } - public String getRegistryPassword() { + public String registryPassword() { return registryPassword; } - public String getRegistryEmail() { + public String registryEmail() { return registryEmail; } - public String getRegistryUrl() { + public String registryUrl() { return registryUrl; } - public String getNetworkName() { + public String networkName() { return networkName; } + public String teamServerImage() { + return teamServerImage; + } } \ No newline at end of file diff --git a/bot/src/main/java/de/chojo/gamejam/message/EmbedHelper.java b/bot/src/main/java/de/chojo/gamejam/message/EmbedHelper.java new file mode 100644 index 00000000..378512e2 --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/message/EmbedHelper.java @@ -0,0 +1,47 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.message; + +import de.chojo.gamejam.server.TeamServer; +import de.chojo.jdautil.localization.util.LocalizedEmbedBuilder; +import de.chojo.jdautil.wrapper.EventContext; +import net.dv8tion.jda.api.entities.MessageEmbed; + +import java.util.concurrent.CompletableFuture; + +public class EmbedHelper { + + public static CompletableFuture embedDetailedStatus(TeamServer teamServer, EventContext context) { + return CompletableFuture.supplyAsync(() -> { + + var builder = new LocalizedEmbedBuilder(context.guildLocalizer()) + .setTitle("%s #%d | %s".formatted(teamServer.statusEmoji(), teamServer.team().id(), teamServer.team().meta().name())); + if (!teamServer.exists()) { + builder.setDescription("teamserver.message.detailstatus.nonexisting.description"); + } else { + if (teamServer.isRunning()) { + builder.setDescription("teamserver.message.detailstatus.existing.description"); + + var serverStats = teamServer.stats(); + serverStats.ifPresent(stats -> { + var memory = stats.memory(); + builder.addField("word.memory", "$word.used$ %d%n$word.total$: %d%n$word.max$: %d".formatted(memory.usedMb(), memory.totalMb(), memory.maxMb()), true) + .addField("word.tps", "1 $word.min$: %.2f%n5 $word.min$: %.2f%n 15 $word.min$: %.2f%n$word.averageticktime$ %.2f".formatted( + stats.tps()[0], stats.tps()[1], stats.tps()[2], stats.averageTickTime()), true) + .addField("word.players", String.valueOf(stats.onlinePlayers()), true) + .addField("word.system", "$word.activethreads$: %d".formatted(stats.activeThreads()), true); + }); + } else { + builder.setDescription("word.serversetup") + .addField("word.ports", "word.notrunning", true); + } + } + + return builder.build(); + }); + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/server/DockerService.java b/bot/src/main/java/de/chojo/gamejam/server/DockerService.java index 312b3e11..38f2206f 100644 --- a/bot/src/main/java/de/chojo/gamejam/server/DockerService.java +++ b/bot/src/main/java/de/chojo/gamejam/server/DockerService.java @@ -27,26 +27,25 @@ import static org.slf4j.LoggerFactory.getLogger; public class DockerService { - private DockerClientConfig dockerClientConfig; + private final Docker dockerConfig; + private final DockerClientConfig dockerClientConfig; private DockerClient dockerClient; private static final Logger log = getLogger(DockerService.class); - private static final String DOCKER_IMAGE = "itzg/minecraft-server:latest"; private static final String DOCKER_VOLUME_DATA_DIR = "/data"; - private final String networkName; private final String pluginUrls; public DockerService(Docker dockerConfig, Plugins pluginsConfig) { - this.networkName = dockerConfig.getNetworkName(); + this.dockerConfig = dockerConfig; this.pluginUrls = String.join(",", pluginsConfig.defaultPlugins()); this.dockerClientConfig = DefaultDockerClientConfig .createDefaultConfigBuilder() .withDockerHost(dockerConfig.getHost()) .withDockerCertPath(dockerConfig.getCertPath()) .withDockerTlsVerify(dockerConfig.isTlsVerify()) - .withRegistryUsername(dockerConfig.getRegistryUsername()) - .withRegistryPassword(dockerConfig.getRegistryPassword()) - .withRegistryEmail(dockerConfig.getRegistryEmail()) - .withRegistryUrl(dockerConfig.getRegistryUrl()) + .withRegistryUsername(dockerConfig.registryUsername()) + .withRegistryPassword(dockerConfig.registryPassword()) + .withRegistryEmail(dockerConfig.registryEmail()) + .withRegistryUrl(dockerConfig.registryUrl()) .build(); } @@ -65,14 +64,14 @@ public void initDockerClient() { private void ensureNetwork() { var networks = dockerClient.listNetworksCmd() - .withNameFilter(networkName) + .withNameFilter(dockerConfig.networkName()) .exec(); - if (networks.stream().noneMatch(n -> n.getName().equals(networkName))) { + if (networks.stream().noneMatch(n -> n.getName().equals(dockerConfig.networkName()))) { dockerClient.createNetworkCmd() - .withName(networkName) + .withName(dockerConfig.networkName()) .withDriver("bridge") .exec(); - log.info("Created docker network {}", networkName); + log.info("Created docker network {}", dockerConfig.networkName()); } } @@ -89,9 +88,9 @@ public void provisionServer(int teamId) { HostConfig hostConfig = HostConfig.newHostConfig() .withBinds(new Bind(volumeName(teamId), new Volume(DOCKER_VOLUME_DATA_DIR))); - hostConfig.withNetworkMode(networkName); + hostConfig.withNetworkMode(dockerConfig.networkName()); - dockerClient.createContainerCmd(DOCKER_IMAGE) + dockerClient.createContainerCmd(dockerConfig.teamServerImage()) .withName(containerName(teamId)) .withEnv("EULA=TRUE", "TYPE=PAPER", "VERSION=26.1.2", String.format("PLUGINS=%s", pluginUrls)) .withHostConfig(hostConfig) @@ -173,7 +172,7 @@ public Optional container(int teamId) { .findFirst(); } - public String logs(int teamId, int tail) { + public String logs(int teamId) { var callback = new ResultCallback.Adapter() { private final StringBuilder logs = new StringBuilder(); @@ -191,7 +190,6 @@ public String getLogs() { dockerClient.logContainerCmd(containerName(teamId)) .withStdOut(true) .withStdErr(true) - .withTail(tail) .exec(callback) .awaitCompletion(); } catch (InterruptedException e) { diff --git a/bot/src/main/java/de/chojo/gamejam/server/ServerService.java b/bot/src/main/java/de/chojo/gamejam/server/ServerService.java index dd7d73dd..6101e21a 100644 --- a/bot/src/main/java/de/chojo/gamejam/server/ServerService.java +++ b/bot/src/main/java/de/chojo/gamejam/server/ServerService.java @@ -22,7 +22,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Stack; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @@ -30,24 +29,19 @@ import static org.slf4j.LoggerFactory.getLogger; -public class ServerService implements Runnable { +public class ServerService { private static final Logger log = getLogger(ServerService.class); private final Map server = new HashMap<>(); private Teams teams; private final Configuration configuration; - private final Stack freePorts = new Stack<>(); private final DockerService dockerService; - public static ServerService create(ScheduledExecutorService executorService, Configuration configuration) { - var serverService = new ServerService(configuration); - executorService.scheduleAtFixedRate(serverService, 10, 10, TimeUnit.SECONDS); - return serverService; + public static ServerService create(Configuration configuration) { + return new ServerService(configuration); } private ServerService(Configuration configuration) { this.configuration = configuration; - IntStream.rangeClosed(configuration.serverManagement().minPort(), configuration.serverManagement().maxPort()) - .forEach(freePorts::add); this.dockerService = new DockerService(configuration.docker(), configuration.plugins()); this.dockerService.initDockerClient(); } @@ -58,30 +52,10 @@ public void shutdown() { }); } - @Override - public void run() { - for (var value : server.values()) { - if (!value.isRunning()) continue; - try { - value.serverRequests() - .ifPresent(server -> { - if (server.restart()) { - log.info("Server of team {} requested restart", value.team()); - value.restart(); - } - }); - } catch (RuntimeException e) { - log.error("Could not reach server {}", value); - } - } - } - public CompletableFuture syncVelocity() { return CompletableFuture.supplyAsync(() -> { log.info("Syncing server with velocity instance."); - freePorts.clear(); - IntStream.rangeClosed(configuration.serverManagement().minPort(), configuration.serverManagement().maxPort()) - .forEach(freePorts::add); + var velocityPort = configuration.serverManagement().velocityPort(); var velocityHost = configuration.serverManagement().getVelocityHost(); var httpClient = HttpClient.newHttpClient(); @@ -126,34 +100,15 @@ public CompletableFuture syncVelocity() { } var team = optTeam.get(); log.info("Registered server for team {} with id {}", team.meta().name(), team.id()); - var teamServer = new TeamServer(this, dockerService, team, configuration, registration.port(), registration.apiPort()); - //teamServer.running(true); + var teamServer = new TeamServer(dockerService, team, configuration); server.put(team, teamServer); - freePorts.removeElement(registration.apiPort()); - freePorts.removeElement(registration.port()); } return true; }); } public TeamServer get(Team team) { - return server.computeIfAbsent(team, key -> new TeamServer(this, dockerService, key, configuration, nextPort(), nextPort())); - } - - private int nextPort() { - if (!freePorts.isEmpty()) { - return freePorts.pop(); - } - throw new RuntimeException("Ports exhausted"); - } - - void stopped(TeamServer server, boolean restart) { - this.server.remove(server.team()); - freePorts.push(server.port()); - freePorts.push(server.apiPort()); - if (restart) { - get(server.team()).start(); - } + return server.computeIfAbsent(team, key -> new TeamServer(dockerService, key, configuration)); } public void inject(Teams teams) { diff --git a/bot/src/main/java/de/chojo/gamejam/server/TeamServer.java b/bot/src/main/java/de/chojo/gamejam/server/TeamServer.java index a11defd5..cef76267 100644 --- a/bot/src/main/java/de/chojo/gamejam/server/TeamServer.java +++ b/bot/src/main/java/de/chojo/gamejam/server/TeamServer.java @@ -9,6 +9,8 @@ import com.fasterxml.jackson.core.JsonProcessingException; import de.chojo.gamejam.configuration.Configuration; import de.chojo.gamejam.data.dao.guild.jams.jam.teams.Team; +import de.chojo.gamejam.data.dao.guild.jams.jam.user.JamUser; +import de.chojo.gamejam.message.EmbedHelper; import de.chojo.gamejam.util.Mapper; import de.chojo.jdautil.localization.util.LocalizedEmbedBuilder; import de.chojo.jdautil.wrapper.EventContext; @@ -41,20 +43,14 @@ public class TeamServer { private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH:mm:ss.SSSS"); private static final Logger log = getLogger(TeamServer.class); private static final int DEFAULT_API_PORT = 30000; - private final ServerService serverService; private final DockerService dockerService; private final Team team; private final Configuration configuration; - private final int port; - private final int apiPort; - public TeamServer(ServerService serverService, DockerService dockerService, Team team, Configuration configuration, int port, int apiPort) { - this.serverService = serverService; + public TeamServer(DockerService dockerService, Team team, Configuration configuration) { this.dockerService = dockerService; this.team = team; this.configuration = configuration; - this.port = port; - this.apiPort = apiPort; } public boolean isRunning() { @@ -75,60 +71,9 @@ public boolean setup() throws IOException { if (exists()) return false; dockerService.provisionServer(team.id()); log.info("Setting up server of team {}", team); - writeTemplate(); return true; } - /** - * Refresh the files of the server present in the template. This is basically a new setup without purging the data beforehand. - *

- * Files with the same name will be overridden - * - * @return true when the refresh was successful - */ - public boolean refresh() { - log.info("Refreshing template files of server {}", team); - try { - writeTemplate(); - } catch (IOException e) { - log.error("Could not refresh template", e); - return false; - } - return true; - } - - private void writeTemplate() throws IOException { - var serverDir = serverDir(); - Files.createDirectories(serverDir); - - var sourceDir = Path.of(configuration.serverTemplate().templateDir()); - var symlinks = configuration.serverTemplate().symLinks() - .stream() - .map(sourceDir::resolve) - .collect(Collectors.toSet()); - try (var files = Files.walk(sourceDir)) { - for (var sourceTarget : files.toList()) { - // skip root dir - if (sourceTarget.getNameCount() == 1) continue; - var filePath = sourceTarget.subpath(1, sourceTarget.getNameCount()); - var serverTarget = serverDir.resolve(filePath); - if (symlinks.contains(sourceTarget)) { - // Not really required since the current and new symlink are probably equal, but the creation will fail otherwise. - if (serverTarget.toFile().isFile() && serverTarget.toFile().delete()) { - log.debug("Deleted old version of file {}", serverTarget); - } - Files.createSymbolicLink(serverTarget, sourceTarget.toAbsolutePath()); - } else { - // ignore already existing directories - if (sourceTarget.toFile().isDirectory() && serverTarget.toFile().exists()) { - continue; - } - Files.copy(sourceTarget, serverTarget, StandardCopyOption.REPLACE_EXISTING); - } - } - } - } - /** * Delete all the server data. * @@ -196,26 +141,16 @@ public Team team() { return team; } - public int port() { - return port; - } - - public int apiPort() { - return apiPort; - } - @Override public String toString() { return "TeamServer{" + "team=" + team + - ", port=" + port + - ", apiPort=" + apiPort + '}'; } - public String logs(int tail) { + public String logs() { - return dockerService.logs(team.id(), tail); + return dockerService.logs(team.id()); } public boolean replaceWorld(Path newWorld) { @@ -259,41 +194,9 @@ public Path world() { return plugins; } - public CompletableFuture detailStatus(EventContext context) { - return CompletableFuture.supplyAsync(() -> { - - var builder = new LocalizedEmbedBuilder(context.guildLocalizer()) - .setTitle("%s #%d | %s".formatted(statusEmoji(), team.id(), team.meta().name())); - if (!exists()) { - builder.setDescription("teamserver.message.detailstatus.nonexisting.description"); - } else { - if (isRunning()) { - builder.setDescription("teamserver.message.detailstatus.existing.description") - .addField("word.ports", "$word.server$: %d%n$word.api$: %d".formatted(port, apiPort), true); - stats().ifPresent(stats -> { - var memory = stats.memory(); - builder.addField("word.memory", "$word.used$ %d%n$word.total$: %d%n$word.max$: %d".formatted(memory.usedMb(), memory.totalMb(), memory.maxMb()), true) - .addField("word.tps", "1 $word.min$: %.2f%n5 $word.min$: %.2f%n 15 $word.min$: %.2f%n$word.averageticktime$ %.2f".formatted( - stats.tps()[0], stats.tps()[1], stats.tps()[2], stats.averageTickTime()), true) - .addField("word.players", String.valueOf(stats.onlinePlayers()), true) - .addField("word.system", "$word.activethreads$: %d".formatted(stats.activeThreads()), true); - }); - } else { - builder.setDescription("word.serversetup") - .addField("word.ports", "word.notrunning", true); - } - } - - return builder.build(); - }); - } - public String status() { var status = statusEmoji(); var ports = ""; - if (exists() && isRunning()) { - ports = "Server: %s Api: %s".formatted(port, apiPort); - } return "%s %s %s".formatted(status, team, ports); } @@ -335,7 +238,7 @@ public HttpClient http() { return http; } - private String statusEmoji() { + public String statusEmoji() { if (exists() && isRunning()) return "🟢"; if (exists()) return "🟡"; return "🔴"; @@ -360,4 +263,8 @@ public Optional serverRequests() { throw new RuntimeException(e); } } + + public CompletableFuture detailStatus(EventContext context) { + return EmbedHelper.embedDetailedStatus(this, context); + } } diff --git a/dev.compose.yml b/compose.dev.yml similarity index 67% rename from dev.compose.yml rename to compose.dev.yml index 18f4b6f6..a2666a8c 100644 --- a/dev.compose.yml +++ b/compose.dev.yml @@ -1,10 +1,19 @@ services: + mc-server-image: + build: + context: . + dockerfile: Dockerfile.mc + image: plugin-jam-mc-server + entrypoint: ["echo", "image built"] bot: build: context: . - dockerfile: dev.Dockerfile + dockerfile: Dockerfile.bot.dev + depends_on: + mc-server-image: + condition: service_completed_successfully volumes: - - ./data/bot/config/config.yml:/app/config/config.yml + - ./data/bot/config:/app/config - ./data/bot/plugins:/app/plugins - ./data/bot/servers:/app/servers - ./data/bot/template:/app/template @@ -21,8 +30,12 @@ services: - "127.0.0.1:5433:5432" networks: - plugin-jam-network + volumes: + - ./data/db:/var/lib/postgresql/data velocity: - image: itzg/mc-proxy:latest + build: + context: . + dockerfile: Dockerfile.velocity tty: true environment: EULA: "TRUE" @@ -30,7 +43,6 @@ services: JVM_DD_OPTS: javalin.port=30000 volumes: - ./data/velocity/config:/config - - ./data/velocity/plugins:/plugins networks: - plugin-jam-network lobby: @@ -38,8 +50,9 @@ services: stdin_open: true tty: true environment: - VERSION: 1.21 + VERSION: 26.1.2 TYPE: PAPER + EULA: "TRUE" MEMORY: 2G volumes: - ./data/lobby:/data diff --git a/compose.yml b/compose.yml index 4188cbf4..9337343f 100644 --- a/compose.yml +++ b/compose.yml @@ -1,18 +1,27 @@ services: + mc-server-image: + build: + context: . + dockerfile: Dockerfile.mc + image: plugin-jam-mc-server + entrypoint: ["echo", "image built"] bot: image: ghcr.io/devcordde/plugin-jam-bot:latest + depends_on: + mc-server-image: + condition: service_completed_successfully volumes: - ./data/bot/config:/app/config - ./data/bot/plugins:/app/plugins - ./data/bot/servers:/app/servers - ./data/bot/template:/app/template - /var/run/docker.sock:/var/run/docker.sock - depends_on: - - networks: - plugin-jam-network velocity: - image: itzg/mc-proxy:latest + build: + context: . + dockerfile: Dockerfile.velocity tty: true environment: EULA: "TRUE" @@ -20,7 +29,6 @@ services: JVM_DD_OPTS: javalin.port=30000 volumes: - ./data/velocity/config:/config - - ./data/velocity/plugins:/plugins networks: - plugin-jam-network lobby: diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml deleted file mode 100644 index 5d358306..00000000 --- a/docker/docker-compose.yml +++ /dev/null @@ -1,28 +0,0 @@ -version: v3 - -services: - bot: - image: game-jam-bot - build: - dockerfile: ../bot/Dockerfile - context: .. - networks: - - plugin-jam - depends_on: - - database - ports: - - 8888:8888 - database: - image: postgres:18.4 - restart: always - user: postgres - environment: - POSTGRES_PASSWORD: "changeme" - POSTGRES_USER: "postgres" - POSTGRES_DB: "db" - networks: - - plugin-jam -networks: - plugin-jam: - name: "plugin-jam" - external: false diff --git a/docker/resources/docker-entrypoint.sh b/docker/resources/docker-entrypoint.sh index f08937b3..ef4b3cdf 100755 --- a/docker/resources/docker-entrypoint.sh +++ b/docker/resources/docker-entrypoint.sh @@ -1,2 +1,2 @@ #!/bin/sh -exec java -Xms256m -Xmx2048m -Dbot.config=config/config.json -Dlog4j.configurationFile=config/log4j2.xml -Dcjda.localisation.error.name=false -jar ./bot.jar +exec java -Xms256m -Xmx2048m -Dlog4j.configurationFile=log4j2.xml -Dcjda.localisation.error.name=false -jar ./bot.jar \ No newline at end of file From a2ee5b93f1430dce9a60043ef040015f92ce827d Mon Sep 17 00:00:00 2001 From: TheZexquex Date: Wed, 10 Jun 2026 04:24:03 +0200 Subject: [PATCH 6/8] Savepoint (convert all commands to jda commands) and more refactoring --- bot/build.gradle.kts | 2 + bot/src/main/java/de/chojo/gamejam/Bot.java | 66 ++--- .../main/java/de/chojo/gamejam/BotModule.java | 27 ++ .../main/java/de/chojo/gamejam/api/Api.java | 4 +- .../api/v1/{Server.java => ServerRoute.java} | 10 +- .../commands/jamadmin/ChangeVotesCommand.java | 38 +++ .../gamejam/commands/jamadmin/JamAdmin.java | 56 ----- .../commands/jamadmin/JamCreateCommand.java | 102 ++++++++ .../commands/jamadmin/JamEndCommand.java | 42 ++++ .../commands/jamadmin/JamStartCommand.java | 44 ++++ .../commands/jamadmin/handler/Create.java | 87 ------- .../commands/jamadmin/handler/jam/JamEnd.java | 38 --- .../jamadmin/handler/jam/JamStart.java | 40 --- .../jamadmin/handler/votes/ChangeVotes.java | 35 --- .../gamejam/commands/register/Register.java | 19 -- .../commands/register/RegisterCommand.java | 69 +++++ .../commands/register/UnregisterCommand.java | 54 ++++ .../commands/register/handler/Handler.java | 65 ----- .../chojo/gamejam/commands/server/Server.java | 142 ----------- .../commands/server/configure/MaxPlayers.java | 48 ---- .../server/configure/MaxPlayersCommand.java | 49 ++++ .../commands/server/configure/Message.java | 60 ----- .../server/configure/MessageCommand.java | 67 +++++ .../server/configure/SpectatorOverflow.java | 48 ---- .../configure/SpectatorOverflowCommand.java | 46 ++++ .../commands/server/configure/Whitelist.java | 48 ---- .../server/download/DownloadPluginData.java | 174 ------------- .../commands/server/plugins/Install.java | 76 ------ .../commands/server/plugins/Uninstall.java | 96 ------- .../commands/server/process/Console.java | 35 --- .../server/process/ConsoleCommand.java | 47 ++++ .../gamejam/commands/server/process/Log.java | 42 ---- .../commands/server/process/LogCommand.java | 50 ++++ .../commands/server/process/Restart.java | 32 --- .../server/process/RestartCommand.java | 45 ++++ .../commands/server/process/Start.java | 34 --- .../commands/server/process/StartCommand.java | 44 ++++ .../commands/server/process/Status.java | 28 --- .../server/process/StatusCommand.java | 38 +++ .../gamejam/commands/server/process/Stop.java | 32 --- .../commands/server/process/StopCommand.java | 41 +++ .../commands/server/system/Delete.java | 47 ---- .../commands/server/system/DeleteCommand.java | 46 ++++ .../gamejam/commands/server/system/Setup.java | 47 ---- .../commands/server/system/SetupCommand.java | 45 ++++ .../commands/server/upload/Plugin.java | 55 ---- .../server/upload/UploadPluginData.java | 157 ------------ .../gamejam/commands/server/upload/World.java | 60 ----- .../commands/serveradmin/ServerAdmin.java | 58 ----- .../serveradmin/ServerAdminCommands.java | 11 + .../serveradmin/handler/SyncVelocity.java | 26 -- .../serveradmin/handler/info/Detailed.java | 81 ------ .../serveradmin/handler/info/Short.java | 43 ---- .../handler/restart/RestartAll.java | 47 ---- .../handler/restart/RestartTeam.java | 69 ----- .../serveradmin/handler/start/StartAll.java | 43 ---- .../serveradmin/handler/start/StartTeam.java | 66 ----- .../serveradmin/handler/stop/StopAll.java | 46 ---- .../serveradmin/handler/stop/StopTeam.java | 69 ----- .../commands/settings/InfoCommand.java | 47 ++++ .../commands/settings/JamRoleCommand.java | 39 +++ .../gamejam/commands/settings/Settings.java | 40 --- .../commands/settings/TeamSizeCommand.java | 37 +++ .../commands/settings/handler/Info.java | 38 --- .../commands/settings/handler/JamRole.java | 26 -- .../commands/settings/handler/Locale.java | 34 --- .../commands/settings/handler/TeamSize.java | 28 --- .../de/chojo/gamejam/commands/team/Team.java | 64 ----- .../{handler/Api.java => TeamApiCommand.java} | 15 +- .../Create.java => TeamCreateCommand.java} | 49 ++-- .../commands/team/TeamDisbandCommand.java | 72 ++++++ .../commands/team/TeamEditCommand.java | 77 ++++++ .../commands/team/TeamInviteCommand.java | 98 ++++++++ .../commands/team/TeamLeaveCommand.java | 55 ++++ .../commands/team/TeamListCommand.java | 80 ++++++ .../commands/team/TeamProfileCommand.java | 81 ++++++ .../commands/team/TeamPromoteCommand.java | 57 +++++ .../commands/team/TeamRenameCommand.java | 63 +++++ .../commands/team/handler/Disband.java | 65 ----- .../gamejam/commands/team/handler/Edit.java | 53 ---- .../gamejam/commands/team/handler/Invite.java | 134 ---------- .../gamejam/commands/team/handler/Leave.java | 55 ---- .../gamejam/commands/team/handler/List.java | 41 --- .../commands/team/handler/Profile.java | 71 ------ .../commands/team/handler/Promote.java | 49 ---- .../gamejam/commands/team/handler/Rename.java | 55 ---- .../commands/unregister/Unregister.java | 19 -- .../commands/unregister/handler/Handler.java | 54 ---- .../gamejam/commands/vote/VoteCommand.java | 123 +++++++++ .../de/chojo/gamejam/commands/vote/Votes.java | 33 --- .../commands/vote/VotesInfoCommand.java | 50 ++++ .../commands/vote/VotesRankingCommand.java | 95 +++++++ .../gamejam/commands/vote/handler/Info.java | 47 ---- .../commands/vote/handler/Ranking.java | 55 ---- .../gamejam/commands/vote/handler/Vote.java | 116 --------- .../data/dao/guild/jams/jam/teams/Team.java | 20 +- .../listener/InviteButtonListener.java | 102 ++++++++ .../de/chojo/gamejam/message/EmbedHelper.java | 41 +-- .../server/CommandContextProvider.java | 35 +++ .../gamejam/server/ServerHttpService.java | 141 +++++++++++ .../chojo/gamejam/server/ServerService.java | 126 ++++------ .../de/chojo/gamejam/server/ServerStatus.java | 23 ++ .../de/chojo/gamejam/server/TeamServer.java | 236 +----------------- .../de/chojo/gamejam/server/UserContext.java | 13 + compose.dev.yml | 2 + .../chojo/pluginjam/payload/Registration.java | 2 +- .../java/de/chojo/pluginjam/PluginJam.java | 7 - .../main/java/de/chojo/pluginjam/api/Api.java | 27 +- ...iguration.java => ConfigurationRoute.java} | 4 +- .../{Requests.java => OnlineRoute.java} | 14 +- .../routes/{Stats.java => StatsRoute.java} | 4 +- .../chojo/pluginjam/serverapi/ServerApi.java | 36 --- .../pluginjam/velocity/ReportService.java | 4 +- .../pluginjam/servers/ServerRegistry.java | 23 +- .../de/chojo/pluginjam/web/server/Server.java | 3 +- 115 files changed, 2376 insertions(+), 3738 deletions(-) create mode 100644 bot/src/main/java/de/chojo/gamejam/BotModule.java rename bot/src/main/java/de/chojo/gamejam/api/v1/{Server.java => ServerRoute.java} (95%) create mode 100644 bot/src/main/java/de/chojo/gamejam/commands/jamadmin/ChangeVotesCommand.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/jamadmin/JamAdmin.java create mode 100644 bot/src/main/java/de/chojo/gamejam/commands/jamadmin/JamCreateCommand.java create mode 100644 bot/src/main/java/de/chojo/gamejam/commands/jamadmin/JamEndCommand.java create mode 100644 bot/src/main/java/de/chojo/gamejam/commands/jamadmin/JamStartCommand.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/jamadmin/handler/Create.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/jamadmin/handler/jam/JamEnd.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/jamadmin/handler/jam/JamStart.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/jamadmin/handler/votes/ChangeVotes.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/register/Register.java create mode 100644 bot/src/main/java/de/chojo/gamejam/commands/register/RegisterCommand.java create mode 100644 bot/src/main/java/de/chojo/gamejam/commands/register/UnregisterCommand.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/register/handler/Handler.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/server/Server.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/server/configure/MaxPlayers.java create mode 100644 bot/src/main/java/de/chojo/gamejam/commands/server/configure/MaxPlayersCommand.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/server/configure/Message.java create mode 100644 bot/src/main/java/de/chojo/gamejam/commands/server/configure/MessageCommand.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/server/configure/SpectatorOverflow.java create mode 100644 bot/src/main/java/de/chojo/gamejam/commands/server/configure/SpectatorOverflowCommand.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/server/configure/Whitelist.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/server/download/DownloadPluginData.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/server/plugins/Install.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/server/plugins/Uninstall.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/server/process/Console.java create mode 100644 bot/src/main/java/de/chojo/gamejam/commands/server/process/ConsoleCommand.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/server/process/Log.java create mode 100644 bot/src/main/java/de/chojo/gamejam/commands/server/process/LogCommand.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/server/process/Restart.java create mode 100644 bot/src/main/java/de/chojo/gamejam/commands/server/process/RestartCommand.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/server/process/Start.java create mode 100644 bot/src/main/java/de/chojo/gamejam/commands/server/process/StartCommand.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/server/process/Status.java create mode 100644 bot/src/main/java/de/chojo/gamejam/commands/server/process/StatusCommand.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/server/process/Stop.java create mode 100644 bot/src/main/java/de/chojo/gamejam/commands/server/process/StopCommand.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/server/system/Delete.java create mode 100644 bot/src/main/java/de/chojo/gamejam/commands/server/system/DeleteCommand.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/server/system/Setup.java create mode 100644 bot/src/main/java/de/chojo/gamejam/commands/server/system/SetupCommand.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/server/upload/Plugin.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/server/upload/UploadPluginData.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/server/upload/World.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/serveradmin/ServerAdmin.java create mode 100644 bot/src/main/java/de/chojo/gamejam/commands/serveradmin/ServerAdminCommands.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/SyncVelocity.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/info/Detailed.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/info/Short.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/restart/RestartAll.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/restart/RestartTeam.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/start/StartAll.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/start/StartTeam.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/stop/StopAll.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/stop/StopTeam.java create mode 100644 bot/src/main/java/de/chojo/gamejam/commands/settings/InfoCommand.java create mode 100644 bot/src/main/java/de/chojo/gamejam/commands/settings/JamRoleCommand.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/settings/Settings.java create mode 100644 bot/src/main/java/de/chojo/gamejam/commands/settings/TeamSizeCommand.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/settings/handler/Info.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/settings/handler/JamRole.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/settings/handler/Locale.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/settings/handler/TeamSize.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/team/Team.java rename bot/src/main/java/de/chojo/gamejam/commands/team/{handler/Api.java => TeamApiCommand.java} (78%) rename bot/src/main/java/de/chojo/gamejam/commands/team/{handler/Create.java => TeamCreateCommand.java} (66%) create mode 100644 bot/src/main/java/de/chojo/gamejam/commands/team/TeamDisbandCommand.java create mode 100644 bot/src/main/java/de/chojo/gamejam/commands/team/TeamEditCommand.java create mode 100644 bot/src/main/java/de/chojo/gamejam/commands/team/TeamInviteCommand.java create mode 100644 bot/src/main/java/de/chojo/gamejam/commands/team/TeamLeaveCommand.java create mode 100644 bot/src/main/java/de/chojo/gamejam/commands/team/TeamListCommand.java create mode 100644 bot/src/main/java/de/chojo/gamejam/commands/team/TeamProfileCommand.java create mode 100644 bot/src/main/java/de/chojo/gamejam/commands/team/TeamPromoteCommand.java create mode 100644 bot/src/main/java/de/chojo/gamejam/commands/team/TeamRenameCommand.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/team/handler/Disband.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/team/handler/Edit.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/team/handler/Invite.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/team/handler/Leave.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/team/handler/List.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/team/handler/Profile.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/team/handler/Promote.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/team/handler/Rename.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/unregister/Unregister.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/unregister/handler/Handler.java create mode 100644 bot/src/main/java/de/chojo/gamejam/commands/vote/VoteCommand.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/vote/Votes.java create mode 100644 bot/src/main/java/de/chojo/gamejam/commands/vote/VotesInfoCommand.java create mode 100644 bot/src/main/java/de/chojo/gamejam/commands/vote/VotesRankingCommand.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/vote/handler/Info.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/vote/handler/Ranking.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/vote/handler/Vote.java create mode 100644 bot/src/main/java/de/chojo/gamejam/listener/InviteButtonListener.java create mode 100644 bot/src/main/java/de/chojo/gamejam/server/CommandContextProvider.java create mode 100644 bot/src/main/java/de/chojo/gamejam/server/ServerHttpService.java create mode 100644 bot/src/main/java/de/chojo/gamejam/server/ServerStatus.java create mode 100644 bot/src/main/java/de/chojo/gamejam/server/UserContext.java rename plugin-paper/src/main/java/de/chojo/pluginjam/api/routes/{Configuration.java => ConfigurationRoute.java} (95%) rename plugin-paper/src/main/java/de/chojo/pluginjam/api/routes/{Requests.java => OnlineRoute.java} (53%) rename plugin-paper/src/main/java/de/chojo/pluginjam/api/routes/{Stats.java => StatsRoute.java} (95%) delete mode 100644 plugin-paper/src/main/java/de/chojo/pluginjam/serverapi/ServerApi.java diff --git a/bot/build.gradle.kts b/bot/build.gradle.kts index 88a45422..27e511ec 100644 --- a/bot/build.gradle.kts +++ b/bot/build.gradle.kts @@ -21,6 +21,8 @@ dependencies { exclude(module = "opus-java") } + implementation("io.github.kaktushose:jda-commands:5.0.0") + implementation("net.dv8tion", "JDA", "6.4.1") implementation(libs.javalin.bundle) diff --git a/bot/src/main/java/de/chojo/gamejam/Bot.java b/bot/src/main/java/de/chojo/gamejam/Bot.java index de929ea3..14548617 100644 --- a/bot/src/main/java/de/chojo/gamejam/Bot.java +++ b/bot/src/main/java/de/chojo/gamejam/Bot.java @@ -6,23 +6,18 @@ package de.chojo.gamejam; +import com.google.inject.Guice; import com.zaxxer.hikari.HikariDataSource; import de.chojo.gamejam.api.Api; -import de.chojo.gamejam.commands.jamadmin.JamAdmin; -import de.chojo.gamejam.commands.register.Register; -import de.chojo.gamejam.commands.server.Server; -import de.chojo.gamejam.commands.serveradmin.ServerAdmin; -import de.chojo.gamejam.commands.settings.Settings; -import de.chojo.gamejam.commands.team.Team; -import de.chojo.gamejam.commands.unregister.Unregister; -import de.chojo.gamejam.commands.vote.Votes; import de.chojo.gamejam.configuration.Configuration; import de.chojo.gamejam.data.access.Guilds; +import de.chojo.gamejam.listener.InviteButtonListener; import de.chojo.gamejam.data.access.Teams; +import de.chojo.gamejam.server.DockerService; import de.chojo.gamejam.server.ServerService; +import de.chojo.gamejam.server.CommandContextProvider; import de.chojo.gamejam.util.LogNotify; import de.chojo.gamejam.util.Token; -import de.chojo.jdautil.interactions.dispatching.InteractionHub; import de.chojo.jdautil.localization.ILocalizer; import de.chojo.jdautil.localization.Localizer; import de.chojo.sadu.core.exceptions.ExceptionTransformer; @@ -35,6 +30,8 @@ import de.chojo.sadu.queries.api.configuration.QueryConfiguration; import de.chojo.sadu.updater.QueryReplacement; import de.chojo.sadu.updater.SqlUpdater; +import io.github.kaktushose.jdac.JDACommands; +import io.github.kaktushose.jdac.guice.GuiceExtensionData; import net.dv8tion.jda.api.interactions.DiscordLocale; import net.dv8tion.jda.api.requests.GatewayIntent; import net.dv8tion.jda.api.requests.RestAction; @@ -46,19 +43,13 @@ import javax.security.auth.login.LoginException; import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.nio.file.attribute.PosixFilePermission; import java.sql.SQLException; import java.util.List; import java.util.Optional; -import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadFactory; -import java.util.concurrent.TimeUnit; import static de.chojo.sadu.queries.api.call.Call.call; import static org.slf4j.LoggerFactory.getLogger; @@ -80,6 +71,7 @@ public class Bot { private ShardManager shardManager; private Guilds guilds; private ServerService serverService; + private DockerService dockerService; private Teams teams; private Api api; @@ -160,22 +152,12 @@ private void buildLocale() { } private void buildCommands() { - InteractionHub.builder(shardManager) - .withLocalizer(localizer) - .withCommands(new JamAdmin(guilds), - new Register(guilds), - new Settings(guilds), - new Team(guilds, configuration), - new Unregister(guilds), - new Votes(guilds), - new Server(guilds, serverService, configuration), - new ServerAdmin(guilds, serverService)) - .withPagination(builder -> builder.withLocalizer(localizer) - .withCache(cache -> cache.expireAfterAccess(30, TimeUnit.MINUTES))) - .withMenuService(builder -> builder.withLocalizer(localizer) - .withCache(cache -> cache.expireAfterAccess(30, TimeUnit.MINUTES))) - .withModalService(builder -> builder.withLocalizer(localizer)) - .build(); + var userContextProvider = new CommandContextProvider(guilds); + var injector = Guice.createInjector(new BotModule(serverService, userContextProvider)); + JDACommands.builder(shardManager) + .extensionData(new GuiceExtensionData(injector)) + .start(); + shardManager.addEventListener(new InviteButtonListener(guilds)); } private void initBot() { @@ -189,8 +171,6 @@ private void initBot() { .setEventPool(Executors.newScheduledThreadPool(5, createThreadFactory("Event Worker"))) .build(); RestAction.setDefaultFailure(throwable -> log.error("Unhandled exception occurred: ", throwable)); - serverService.inject(teams); - serverService.syncVelocity(); } private void initDb() throws IOException, SQLException { @@ -243,23 +223,7 @@ private void initDb() throws IOException, SQLException { } private void initServer() throws IOException { - serverService = ServerService.create(configuration); - - var templateDir = Path.of(configuration.serverTemplate().templateDir()); - var serverDir = Path.of(configuration.serverManagement().serverDir()); - var pluginDir = Path.of(configuration.plugins().pluginDir()); - - Files.createDirectories(templateDir); - Files.createDirectories(serverDir); - Files.createDirectories(pluginDir); - - Path wait = Path.of("wait.sh"); - Files.copy(getClass().getClassLoader().getResourceAsStream("wait.sh"), - wait, StandardCopyOption.REPLACE_EXISTING); - try { - Files.setPosixFilePermissions(wait, Set.of(PosixFilePermission.OWNER_EXECUTE, PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE)); - } catch (UnsupportedOperationException e) { - log.error("Use linux..."); - } + serverService = new ServerService(configuration); + dockerService = new DockerService(configuration.docker(), configuration.plugins()); } } diff --git a/bot/src/main/java/de/chojo/gamejam/BotModule.java b/bot/src/main/java/de/chojo/gamejam/BotModule.java new file mode 100644 index 00000000..b8f73d9a --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/BotModule.java @@ -0,0 +1,27 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam; + +import com.google.inject.AbstractModule; +import de.chojo.gamejam.server.ServerService; +import de.chojo.gamejam.server.CommandContextProvider; + +public class BotModule extends AbstractModule { + private final ServerService serverService; + private final CommandContextProvider commandContextProvider; + + public BotModule(ServerService serverService, CommandContextProvider commandContextProvider) { + this.serverService = serverService; + this.commandContextProvider = commandContextProvider; + } + + @Override + protected void configure() { + bind(ServerService.class).toInstance(serverService); + bind(CommandContextProvider.class).toInstance(commandContextProvider); + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/api/Api.java b/bot/src/main/java/de/chojo/gamejam/api/Api.java index 6a45e058..f5f2bed2 100644 --- a/bot/src/main/java/de/chojo/gamejam/api/Api.java +++ b/bot/src/main/java/de/chojo/gamejam/api/Api.java @@ -7,7 +7,7 @@ package de.chojo.gamejam.api; import de.chojo.gamejam.api.exception.InterruptException; -import de.chojo.gamejam.api.v1.Server; +import de.chojo.gamejam.api.v1.ServerRoute; import de.chojo.gamejam.api.v1.Teams; import de.chojo.gamejam.api.v1.Users; import de.chojo.gamejam.configuration.Configuration; @@ -95,7 +95,7 @@ private void routes() { users.routes(); var teams = new Teams(shardManager, guilds); teams.routes(); - Server server = new Server(serverService, this.teams); + ServerRoute server = new ServerRoute(serverService, this.teams); server.routes(); }); diff --git a/bot/src/main/java/de/chojo/gamejam/api/v1/Server.java b/bot/src/main/java/de/chojo/gamejam/api/v1/ServerRoute.java similarity index 95% rename from bot/src/main/java/de/chojo/gamejam/api/v1/Server.java rename to bot/src/main/java/de/chojo/gamejam/api/v1/ServerRoute.java index 43567bfc..bb49dd46 100644 --- a/bot/src/main/java/de/chojo/gamejam/api/v1/Server.java +++ b/bot/src/main/java/de/chojo/gamejam/api/v1/ServerRoute.java @@ -28,12 +28,12 @@ import static io.javalin.apibuilder.ApiBuilder.post; import static org.slf4j.LoggerFactory.getLogger; -public class Server { +public class ServerRoute { private final ServerService serverService; private final Teams teams; - private static final Logger log = getLogger(Server.class); + private static final Logger log = getLogger(ServerRoute.class); - public Server(ServerService serverService, Teams teams) { + public ServerRoute(ServerService serverService, Teams teams) { this.serverService = serverService; this.teams = teams; } @@ -73,7 +73,7 @@ public void routes() { } ) private void handle(@NotNull Context ctx) { - String token = ctx.pathParam("token"); + /*String token = ctx.pathParam("token"); Optional team = teams.byToken(token); if (team.isEmpty()) { ctx.status(HttpStatus.NOT_FOUND); @@ -112,5 +112,7 @@ private void handle(@NotNull Context ctx) { } else if (teamServer.isRunning()) { teamServer.send("say Plugin Updated"); } + + */ } } diff --git a/bot/src/main/java/de/chojo/gamejam/commands/jamadmin/ChangeVotesCommand.java b/bot/src/main/java/de/chojo/gamejam/commands/jamadmin/ChangeVotesCommand.java new file mode 100644 index 00000000..47634ad9 --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/commands/jamadmin/ChangeVotesCommand.java @@ -0,0 +1,38 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.commands.jamadmin; + +import com.google.inject.Inject; +import de.chojo.gamejam.server.CommandContextProvider; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.annotations.interactions.Param; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; + +@Bundle("locale") +@Interaction +public final class ChangeVotesCommand { + private final CommandContextProvider commandContextProvider; + + @Inject + public ChangeVotesCommand(CommandContextProvider commandContextProvider) { + this.commandContextProvider = commandContextProvider; + } + + @Command("jamdamin changevotes") + public void onCommand(CommandEvent event, @Param("voting") boolean voting) { + var guilds = commandContextProvider.guilds(); + var jam = guilds.guild(event).jams().activeJam(); + if (jam.isEmpty()) { + event.reply("error.noactivejam"); + return; + } + jam.get().state().voting(voting); + event.reply("asd"); + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/jamadmin/JamAdmin.java b/bot/src/main/java/de/chojo/gamejam/commands/jamadmin/JamAdmin.java deleted file mode 100644 index 80be89d5..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/jamadmin/JamAdmin.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.jamadmin; - -import de.chojo.gamejam.commands.jamadmin.handler.Create; -import de.chojo.gamejam.commands.jamadmin.handler.jam.JamEnd; -import de.chojo.gamejam.commands.jamadmin.handler.jam.JamStart; -import de.chojo.gamejam.commands.jamadmin.handler.votes.ChangeVotes; -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.jdautil.interactions.slash.Argument; -import de.chojo.jdautil.interactions.slash.Group; -import de.chojo.jdautil.interactions.slash.Slash; -import de.chojo.jdautil.interactions.slash.SubCommand; -import de.chojo.jdautil.interactions.slash.provider.SlashCommand; - -public class JamAdmin extends SlashCommand { - - - public JamAdmin(Guilds guilds) { - super(Slash.of("jamadmin", "command.jamadmin.description") - .adminCommand() - .subCommand(SubCommand.of("create", "command.jamadmin.create.description") - .handler(new Create(guilds)) - .argument(Argument.text("topic", "command.jamadmin.create.options.topic.description") - .asRequired()) - .argument(Argument.text("tagline", "command.jamadmin.create.options.tagline.description") - .asRequired()) - .argument(Argument.text("timezone", "command.jamadmin.create.options.timezone.description") - .asRequired() - .withAutoComplete()) - .argument(Argument.text("registerstart", "command.jamadmin.create.options.registerstart.description") - .asRequired()) - .argument(Argument.text("registerend", "command.jamadmin.create.options.registerend.description") - .asRequired()) - .argument(Argument.text("jamstart", "command.jamadmin.create.options.jamstart.description") - .asRequired()) - .argument(Argument.text("jamend", "command.jamadmin.create.options.jamend.description") - .asRequired())) - .group(Group.of("jam", "command.jamadmin.jam.description") - .subCommand(SubCommand.of("start", "command.jamadmin.jam.start.description") - .handler(new JamStart(guilds))) - .subCommand(SubCommand.of("end", "command.jamadmin.jam.end.description") - .handler(new JamEnd(guilds)) - .argument(Argument.bool("confirm", "command.jamadmin.jam.end.options.confirm.description")))) - .group(Group.of("votes", "command.jamadmin.votes.description") - .subCommand(SubCommand.of("open", "command.jamadmin.votes.open.description") - .handler(new ChangeVotes(guilds, true, "command.jamadmin.votes.message.opened"))) - .subCommand(SubCommand.of("close", "command.jamadmin.votes.close.description") - .handler(new ChangeVotes(guilds, false, "command.jamadmin.votes.message.closed")))) - .build()); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/jamadmin/JamCreateCommand.java b/bot/src/main/java/de/chojo/gamejam/commands/jamadmin/JamCreateCommand.java new file mode 100644 index 00000000..44645a6b --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/commands/jamadmin/JamCreateCommand.java @@ -0,0 +1,102 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.commands.jamadmin; + +import com.google.inject.Inject; +import de.chojo.gamejam.data.wrapper.jam.JamCreator; +import de.chojo.gamejam.data.dao.guild.jams.jam.JamTimes; +import de.chojo.gamejam.data.wrapper.jam.TimeFrame; +import de.chojo.gamejam.server.CommandContextProvider; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.AutoComplete; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.annotations.interactions.Param; +import io.github.kaktushose.jdac.dispatching.events.interactions.AutoCompleteEvent; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; +import io.github.kaktushose.jdac.message.placeholder.Entry; +import net.dv8tion.jda.api.interactions.commands.Command; + +import java.time.DateTimeException; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Locale; +import java.util.stream.Collectors; + +@Bundle("locale") +@Interaction +public class JamCreateCommand { + public static final String PATTERN = "yyyy.MM.dd HH:mm"; + private static final DateTimeFormatter DATE_PARSER = DateTimeFormatter.ofPattern(PATTERN); + private final CommandContextProvider commandContextProvider; + + @Inject + public JamCreateCommand(CommandContextProvider commandContextProvider) { + this.commandContextProvider = commandContextProvider; + } + + @io.github.kaktushose.jdac.annotations.interactions.Command(value = "jamadmin create") + public void onCommand( + CommandEvent event, + @Param("topic") String topic, + @Param("tagline") String tagline, + @Param("timezone") String timezone, + @Param("registerstart") String registerStart, + @Param("registerend") String registerEnd, + @Param("jamstart") String jamStart, + @Param("jamend") String jamEnd + ) { + var guild = commandContextProvider.guilds().guild(event); + var titleAndSlogan = String.join("\n", topic, tagline); + ZoneId parsedTimeZone; + try { + parsedTimeZone = ZoneId.of(timezone); + } catch (DateTimeException e) { + event.reply("error.invalidtimezone"); + return; + } + + var jamBuilder = JamCreator.create().setTopic(titleAndSlogan); + try { + var registerStartParsed = parseTime(registerStart, parsedTimeZone); + var registerEndParsed = parseTime(registerEnd, parsedTimeZone); + var jamStartParsed = parseTime(jamStart, parsedTimeZone); + var jamEndParsed = parseTime(jamEnd, parsedTimeZone); + + var times = new JamTimes( + parsedTimeZone, + new TimeFrame(registerStartParsed, registerEndParsed), + new TimeFrame(jamStartParsed, jamEndParsed) + ); + jamBuilder.setTimes(times); + } catch (DateTimeException e) { + event.reply("error.invalidrimeformat", Entry.entry("FORMAT", PATTERN)); + return; + } + + guild.jams().create(jamBuilder.build()); + event.reply("command.jamadmin.create.message.created"); + } + + + private ZonedDateTime parseTime(String time, ZoneId zoneId) throws DateTimeException { + var parsed = LocalDateTime.from(DATE_PARSER.parse(time)); + return ZonedDateTime.ofInstant(parsed, zoneId.getRules().getOffset(parsed), zoneId); + } + + @AutoComplete(value = "jamadmin create", options = "timezone") + public void onAutoComplete(AutoCompleteEvent event) { + var value = event.getValue().toLowerCase(Locale.ROOT); + var choices = ZoneId.getAvailableZoneIds().stream() + .filter(zone -> zone.toLowerCase(Locale.ROOT).contains(value)) + .limit(25) + .map(zone -> new Command.Choice(zone, zone)) + .collect(Collectors.toList()); + event.replyChoices(choices); + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/jamadmin/JamEndCommand.java b/bot/src/main/java/de/chojo/gamejam/commands/jamadmin/JamEndCommand.java new file mode 100644 index 00000000..c0f018f9 --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/commands/jamadmin/JamEndCommand.java @@ -0,0 +1,42 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.commands.jamadmin; + +import com.google.inject.Inject; +import de.chojo.gamejam.server.CommandContextProvider; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.annotations.interactions.Param; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; + +@Bundle("locale") +@Interaction +public final class JamEndCommand { + private final CommandContextProvider commandContextProvider; + + @Inject + public JamEndCommand(CommandContextProvider commandContextProvider) { + this.commandContextProvider = commandContextProvider; + } + + @Command(value = "jamadmin end") + public void onCommand(CommandEvent event, @Param("confirm") boolean confirm) { + if (!confirm) { + event.reply("error.noconfirm"); + return; + } + + commandContextProvider.guilds().guild(event).jams().activeJam().ifPresentOrElse( + jam -> { + jam.state().finish(); + event.reply("command.jamadmin.jam.end.message.ended"); + }, + () -> event.reply("error.noactivejam") + ); + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/jamadmin/JamStartCommand.java b/bot/src/main/java/de/chojo/gamejam/commands/jamadmin/JamStartCommand.java new file mode 100644 index 00000000..a12f2567 --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/commands/jamadmin/JamStartCommand.java @@ -0,0 +1,44 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.commands.jamadmin; + +import com.google.inject.Inject; +import de.chojo.gamejam.server.CommandContextProvider; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; + +@Bundle("locale") +@Interaction +public final class JamStartCommand { + private final CommandContextProvider commandContextProvider; + + @Inject + public JamStartCommand(CommandContextProvider commandContextProvider) { + this.commandContextProvider = commandContextProvider; + } + + @Command(value = "jamadmin start") + public void onCommand(CommandEvent event) { + var guild = commandContextProvider.guilds().guild(event); + var jams = guild.jams(); + var currJam = jams.activeJam(); + if (currJam.isPresent()) { + event.reply("error.alreadyActive"); + return; + } + var next = jams.nextOrCurrent(); + if (next.isPresent()) { + next.get().state().active(true); + event.reply("command.start.message.activated"); + return; + } + + event.reply("error.noupcomingjam"); + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/jamadmin/handler/Create.java b/bot/src/main/java/de/chojo/gamejam/commands/jamadmin/handler/Create.java deleted file mode 100644 index 10331ce6..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/jamadmin/handler/Create.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.jamadmin.handler; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.data.wrapper.jam.JamCreator; -import de.chojo.gamejam.data.dao.guild.jams.jam.JamTimes; -import de.chojo.gamejam.data.wrapper.jam.TimeFrame; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.localization.util.Replacement; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import net.dv8tion.jda.api.interactions.commands.Command; -import net.dv8tion.jda.api.interactions.commands.OptionMapping; - -import java.time.DateTimeException; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; -import java.util.Locale; -import java.util.stream.Collectors; - -public class Create implements SlashHandler { - public static final String PATTERN = "yyyy.MM.dd HH:mm"; - private static final DateTimeFormatter DATE_PARSER = DateTimeFormatter.ofPattern(PATTERN); - private final Guilds guilds; - - public Create(Guilds guilds) { - this.guilds = guilds; - } - - private ZonedDateTime parseTime(String time, ZoneId zoneId) throws DateTimeException { - var parsed = LocalDateTime.from(DATE_PARSER.parse(time)); - return ZonedDateTime.ofInstant(parsed, zoneId.getRules().getOffset(parsed), zoneId); - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var guild = guilds.guild(event); - var topic = String.join("\n", event.getOption("topic").getAsString(), - event.getOption("tagline", "", OptionMapping::getAsString)); - ZoneId timezone; - try { - timezone = ZoneId.of(event.getOption("timezone").getAsString()); - } catch (DateTimeException e) { - event.reply(context.localize("error.invalidtimezone")).setEphemeral(true).queue(); - return; - } - - var jamBuilder = JamCreator.create() - .setTopic(topic); - try { - var registerStart = parseTime(event.getOption("registerstart").getAsString(), timezone); - var registerEnd = parseTime(event.getOption("registerend").getAsString(), timezone); - var jamStart = parseTime(event.getOption("jamstart").getAsString(), timezone); - var jamEnd = parseTime(event.getOption("jamend").getAsString(), timezone); - var times = new JamTimes(timezone, new TimeFrame(registerStart, registerEnd), new TimeFrame(jamStart, jamEnd)); - jamBuilder.setTimes(times); - } catch (DateTimeException e) { - event.reply(context.localize("error.invalidrimeformat", Replacement.create("FORMAT", PATTERN))) - .setEphemeral(true).queue(); - return; - } - - guild.jams().create(jamBuilder.build()); - event.reply(context.localize("command.jamadmin.create.message.created")).setEphemeral(true).queue(); - } - - @Override - public void onAutoComplete(CommandAutoCompleteInteractionEvent event, EventContext context) { - if ("timezone".equals(event.getFocusedOption().getName())) { - var value = event.getFocusedOption().getValue().toLowerCase(Locale.ROOT); - var choices = ZoneId.getAvailableZoneIds().stream() - .filter(zone -> zone.toLowerCase(Locale.ROOT).contains(value)) - .limit(25) - .map(zone -> new Command.Choice(zone, zone)) - .collect(Collectors.toList()); - event.replyChoices(choices).queue(); - } - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/jamadmin/handler/jam/JamEnd.java b/bot/src/main/java/de/chojo/gamejam/commands/jamadmin/handler/jam/JamEnd.java deleted file mode 100644 index 6f2aed1c..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/jamadmin/handler/jam/JamEnd.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.jamadmin.handler.jam; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -public final class JamEnd implements SlashHandler { - private final Guilds guilds; - - public JamEnd(Guilds guilds) { - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - if (!event.getOption("confirm").getAsBoolean()) { - event.reply(context.localize("error.noconfirm")).setEphemeral(true).queue(); - return; - } - - - guilds.guild(event).jams().activeJam() - .ifPresentOrElse( - jam -> { - jam.state().finish(); - event.reply(context.localize("command.jamadmin.jam.end.message.ended")).setEphemeral(true) - .queue(); - }, - () -> event.reply(context.localize("error.noactivejam")).setEphemeral(true).queue()); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/jamadmin/handler/jam/JamStart.java b/bot/src/main/java/de/chojo/gamejam/commands/jamadmin/handler/jam/JamStart.java deleted file mode 100644 index ee317ca6..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/jamadmin/handler/jam/JamStart.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.jamadmin.handler.jam; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -public final class JamStart implements SlashHandler { - private final Guilds guilds; - - public JamStart(Guilds guilds) { - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var jams = guilds.guild(event).jams(); - var currJam = jams.activeJam(); - if (currJam.isPresent()) { - event.reply(context.localize("error.alreadyActive")).setEphemeral(true).queue(); - return; - } - var next = jams.nextOrCurrent(); - if (next.isPresent()) { - next.get().state().active(true); - event.reply(context.localize("command.start.message.activated")) - .setEphemeral(true) - .queue(); - return; - } - event.reply(context.localize("error.noupcomingjam")).setEphemeral(true) - .queue(); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/jamadmin/handler/votes/ChangeVotes.java b/bot/src/main/java/de/chojo/gamejam/commands/jamadmin/handler/votes/ChangeVotes.java deleted file mode 100644 index f98bc32d..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/jamadmin/handler/votes/ChangeVotes.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.jamadmin.handler.votes; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -public final class ChangeVotes implements SlashHandler { - private final Guilds guilds; - private final boolean voting; - private final String content; - - public ChangeVotes(Guilds guilds, boolean voting, String content) { - this.guilds = guilds; - this.voting = voting; - this.content = content; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - guilds.guild(event).jams().activeJam() - .ifPresentOrElse( - jam -> { - jam.state().voting(voting); - event.reply(context.localize(content)).queue(); - }, - () -> event.reply(context.localize("error.noactivejam")).setEphemeral(true).queue()); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/register/Register.java b/bot/src/main/java/de/chojo/gamejam/commands/register/Register.java deleted file mode 100644 index 8f68e627..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/register/Register.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.register; - -import de.chojo.gamejam.commands.register.handler.Handler; -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.jdautil.interactions.slash.Slash; -import de.chojo.jdautil.interactions.slash.provider.SlashCommand; - -public class Register extends SlashCommand { - public Register(Guilds guilds) { - super(Slash.of("register", "command.register.description") - .command(new Handler(guilds))); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/register/RegisterCommand.java b/bot/src/main/java/de/chojo/gamejam/commands/register/RegisterCommand.java new file mode 100644 index 00000000..6f47575c --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/commands/register/RegisterCommand.java @@ -0,0 +1,69 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.commands.register; + +import com.google.inject.Inject; +import de.chojo.gamejam.server.CommandContextProvider; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; +import io.github.kaktushose.jdac.message.placeholder.Entry; +import net.dv8tion.jda.api.utils.TimeFormat; + +import java.time.ZonedDateTime; + +@Bundle("locale") +@Interaction +public class RegisterCommand { + private final CommandContextProvider commandContextProvider; + + @Inject + public RegisterCommand(CommandContextProvider commandContextProvider) { + this.commandContextProvider = commandContextProvider; + } + + @Command(value = "register") + public void onCommand(CommandEvent event) { + var guild = commandContextProvider.guilds().guild(event); + var optJam = guild.jams().nextOrCurrent(); + if (optJam.isEmpty()) { + event.reply("error.noupcomingjam"); + return; + } + var jam = optJam.get(); + var times = jam.times(); + + if (!times.registration().contains(ZonedDateTime.now())) { + if (times.registration().start().isAfter(ZonedDateTime.now())) { + event.reply( + "command.register.message.notyet", + Entry.entry("TIMESTAMP", TimeFormat.DATE_TIME_LONG.format(times.registration().start())) + ); + return; + } + event.reply("command.register.message.notanymore"); + return; + } + + if (jam.registrations().contains(event.getMember().getIdLong())) { + event.reply("command.register.message.alreadyregistered"); + return; + } + + jam.register(event.getMember()); + var settings = guild.jamSettings(); + var role = event.getGuild().getRoleById(settings.jamRole()); + if (role != null) { + event.getGuild().addRoleToMember(event.getMember(), role).queue(); + } + event.reply( + "command.register.message.registered", + Entry.entry("TIMESTAMP", TimeFormat.DATE_TIME_LONG.format(times.registration().start())) + ); + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/register/UnregisterCommand.java b/bot/src/main/java/de/chojo/gamejam/commands/register/UnregisterCommand.java new file mode 100644 index 00000000..d00ce8c7 --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/commands/register/UnregisterCommand.java @@ -0,0 +1,54 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.commands.register; + +import com.google.inject.Inject; +import de.chojo.gamejam.server.CommandContextProvider; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; + +@Bundle("locale") +@Interaction +public class UnregisterCommand { + private final CommandContextProvider commandContextProvider; + + @Inject + public UnregisterCommand(CommandContextProvider commandContextProvider) { + this.commandContextProvider = commandContextProvider; + } + + @Command(value = "unregister") + public void onCommand(CommandEvent event) { + var guild = commandContextProvider.guilds().guild(event); + var optJam = guild.jams().nextOrCurrent(); + if (optJam.isEmpty()) { + event.reply("error.noupcomingjam"); + return; + } + + var jam = optJam.get(); + + if (!jam.registrations().contains(event.getMember().getIdLong())) { + event.reply("command.unregister.message.notregistered"); + return; + } + + jam.teams().byMember(event.getMember()).ifPresentOrElse( + team -> event.reply("command.unregister.message.inteam"), + () -> { + var settings = guild.jamSettings(); + var role = event.getGuild().getRoleById(settings.jamRole()); + if (role != null) { + event.getGuild().removeRoleFromMember(event.getMember(), role).queue(); + } + event.reply("command.unregister.message.unregistered"); + + }); + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/register/handler/Handler.java b/bot/src/main/java/de/chojo/gamejam/commands/register/handler/Handler.java deleted file mode 100644 index f5944834..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/register/handler/Handler.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.register.handler; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.localization.util.Replacement; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import net.dv8tion.jda.api.utils.TimeFormat; - -import java.time.ZonedDateTime; - -public class Handler implements SlashHandler { - private final Guilds guilds; - - public Handler(Guilds guilds) { - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var guild = guilds.guild(event); - var optJam = guild.jams().nextOrCurrent(); - if (optJam.isEmpty()) { - event.reply(context.localize("error.noupcomingjam")).setEphemeral(true).queue(); - return; - } - var jam = optJam.get(); - var times = jam.times(); - - if (!times.registration().contains(ZonedDateTime.now())) { - if (times.registration().start().isAfter(ZonedDateTime.now())) { - event.reply(context.localize("command.register.message.notyet", - Replacement.create("TIMESTAMP", TimeFormat.DATE_TIME_LONG.format(times.registration() - .start())))) - .setEphemeral(true) - .queue(); - return; - } - event.reply(context.localize("command.register.message.notanymore")).setEphemeral(true).queue(); - return; - } - - if (jam.registrations().contains(event.getMember().getIdLong())) { - event.reply(context.localize("command.register.message.alreadyregistered")).setEphemeral(true).queue(); - return; - } - - jam.register(event.getMember()); - var settings = guild.jamSettings(); - var role = event.getGuild().getRoleById(settings.jamRole()); - if (role != null) { - event.getGuild().addRoleToMember(event.getMember(), role).queue(); - } - event.reply(context.localize("command.register.message.registered", - Replacement.create("TIMESTAMP", TimeFormat.DATE_TIME_LONG.format(times.jam().start())))) - .setEphemeral(true) - .queue(); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/Server.java b/bot/src/main/java/de/chojo/gamejam/commands/server/Server.java deleted file mode 100644 index 71189604..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/server/Server.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.server; - -import de.chojo.gamejam.commands.server.configure.MaxPlayers; -import de.chojo.gamejam.commands.server.configure.Message; -import de.chojo.gamejam.commands.server.configure.SpectatorOverflow; -import de.chojo.gamejam.commands.server.configure.Whitelist; -import de.chojo.gamejam.commands.server.download.DownloadPluginData; -import de.chojo.gamejam.commands.server.plugins.Install; -import de.chojo.gamejam.commands.server.plugins.Uninstall; -import de.chojo.gamejam.commands.server.process.Console; -import de.chojo.gamejam.commands.server.process.Log; -import de.chojo.gamejam.commands.server.process.Restart; -import de.chojo.gamejam.commands.server.process.Start; -import de.chojo.gamejam.commands.server.process.Status; -import de.chojo.gamejam.commands.server.process.Stop; -import de.chojo.gamejam.commands.server.system.Delete; -import de.chojo.gamejam.commands.server.system.Setup; -import de.chojo.gamejam.commands.server.upload.Plugin; -import de.chojo.gamejam.commands.server.upload.UploadPluginData; -import de.chojo.gamejam.commands.server.upload.World; -import de.chojo.gamejam.configuration.Configuration; -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.server.ServerService; -import de.chojo.gamejam.server.TeamServer; -import de.chojo.jdautil.interactions.slash.Argument; -import de.chojo.jdautil.interactions.slash.Group; -import de.chojo.jdautil.interactions.slash.Slash; -import de.chojo.jdautil.interactions.slash.SubCommand; -import de.chojo.jdautil.interactions.slash.provider.SlashProvider; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -import java.util.Optional; - -public class Server implements SlashProvider { - private final Guilds guilds; - private final ServerService serverService; - private final Configuration configuration; - - public Server(Guilds guilds, ServerService serverService, Configuration configuration) { - this.guilds = guilds; - this.serverService = serverService; - this.configuration = configuration; - } - - @Override - public Slash slash() { - return Slash.of("server", "command.server.description") - .adminCommand() - .group(Group.of("process", "command.server.process.description") - .subCommand(SubCommand.of("start", "command.server.process.start.description") - .handler(new Start(this))) - .subCommand(SubCommand.of("stop", "command.server.process.stop.description") - .handler(new Stop(this))) - .subCommand(SubCommand.of("restart", "command.server.process.restart.description") - .handler(new Restart(this))) - .subCommand(SubCommand.of("console", "command.server.process.console.description") - .handler(new Console(this)) - .argument(Argument.text("command", "command.server.process.console.options.command.description").asRequired())) - .subCommand(SubCommand.of("status", "command.server.process.status.description") - .handler(new Status(this))) - .subCommand(SubCommand.of("log", "command.server.process.log.description") - .handler(new Log(this)))) - .group(Group.of("system", "command.server.system.description") - .subCommand(SubCommand.of("setup", "command.server.system.setup.description") - .handler(new Setup(this))) - .subCommand(SubCommand.of("delete", "command.server.system.delete.description") - .handler(new Delete(this)))) - .group(Group.of("upload", "command.server.upload.description") - .subCommand(SubCommand.of("world", "command.server.upload.world.description") - .handler(new World(this)) - .argument(Argument.text("url", "command.server.upload.world.options.url.description")) - .argument(Argument.attachment("file", "command.server.upload.world.options.file.description"))) - .subCommand(SubCommand.of("plugin", "command.server.upload.plugin.description") - .handler(new Plugin(this)) - .argument(Argument.attachment("file", "command.server.upload.plugin.options.file.description") - .asRequired())) - .subCommand(SubCommand.of("plugindata", "command.server.upload.plugindata.description") - .handler(new UploadPluginData(this, guilds, serverService)) - .argument(Argument.text("path", "command.server.upload.plugindata.options.path.description") - .asRequired() - .withAutoComplete()) - .argument(Argument.attachment("file", "command.server.upload.plugindata.options.file.description") - .asRequired()))) - .group(Group.of("download", "command.server.download.description") - .subCommand(SubCommand.of("plugindata", "command.server.download.plugindata.description") - .handler(new DownloadPluginData(this, guilds, serverService)) - .argument(Argument.text("path", "command.server.download.plugindata.options.path.description") - .asRequired() - .withAutoComplete()))) - .group(Group.of("plugins", "command.server.plugins.description") - .subCommand(SubCommand.of("install", "command.server.plugins.install.description") - .handler(new Install(configuration, this)) - .argument(Argument.text("plugin", "command.server.plugins.install.options.plugin.description") - .asRequired() - .withAutoComplete())) - .subCommand(SubCommand.of("uninstall", "command.server.plugins.uninstall.description") - .handler(new Uninstall(this, configuration, guilds, serverService)) - .argument(Argument.text("plugin", "command.server.plugins.uninstall.options.plugin.description").asRequired() - .withAutoComplete()) - .argument(Argument.bool("deletedata", "command.server.plugins.uninstall.options.deletedata.description") - .asRequired()))) - .group(Group.of("configure", "command.server.configure.description") - .subCommand(SubCommand.of("message", "command.server.configure.message.description") - .handler(new Message(this))) - .subCommand(SubCommand.of("maxplayers", "command.server.configure.maxplayers.description") - .handler(new MaxPlayers(this)) - .argument(Argument.integer("amount", "command.server.configure.maxplayers.options.amount.description").asRequired())) - .subCommand(SubCommand.of("spectatoroverflow", "command.server.configure.spectatoroverflow.description") - .handler(new SpectatorOverflow(this)) - .argument(Argument.bool("state", "command.server.configure.spectatoroverflow.options.state.description").asRequired())) - .subCommand(SubCommand.of("whitelist", "command.server.configure.whitelist.description") - .handler(new Whitelist(this)) - .argument(Argument.bool("state", "command.server.configure.whitelist.options.state.description").asRequired()))) - .build(); - } - - public Optional getServer(SlashCommandInteractionEvent event, EventContext context) { - var optJam = guilds.guild(event).jams().activeJam(); - - if (optJam.isEmpty()) { - event.reply(context.localize("error.nojamactive")).setEphemeral(true).queue(); - return Optional.empty(); - } - - var jam = optJam.get(); - var optTeam = jam.teams().byMember(event.getUser()); - - if (optTeam.isEmpty()) { - event.reply(context.localize("error.noteam")).setEphemeral(true).queue(); - return Optional.empty(); - } - - return Optional.ofNullable(serverService.get(optTeam.get())); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/configure/MaxPlayers.java b/bot/src/main/java/de/chojo/gamejam/commands/server/configure/MaxPlayers.java deleted file mode 100644 index d85e6b27..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/server/configure/MaxPlayers.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.server.configure; - -import de.chojo.gamejam.commands.server.Server; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.util.Futures; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import org.slf4j.Logger; - -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; - -import static org.slf4j.LoggerFactory.getLogger; - -public class MaxPlayers implements SlashHandler { - private static final Logger log = getLogger(MaxPlayers.class); - private final Server server; - - public MaxPlayers(Server server) { - this.server = server; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var optServer = server.getServer(event, context); - if (optServer.isEmpty()) return; - - var teamServer = optServer.get(); - var request = teamServer.requestBuilder("v1/config/maxplayers") - .POST(HttpRequest.BodyPublishers.ofString(String.valueOf(event.getOption("amount").getAsInt()))) - .build(); - - if (!teamServer.isRunning()) { - event.reply(context.localize("error.servernotrunning")).queue(); - return; - } - - teamServer.http().sendAsync(request, HttpResponse.BodyHandlers.discarding()) - .whenComplete(Futures.whenComplete(res -> event.reply("command.server.configure.maxplayers.message.success").queue(), - err -> log.error("Failed to send request.", err)));; - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/configure/MaxPlayersCommand.java b/bot/src/main/java/de/chojo/gamejam/commands/server/configure/MaxPlayersCommand.java new file mode 100644 index 00000000..a42b5751 --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/commands/server/configure/MaxPlayersCommand.java @@ -0,0 +1,49 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.commands.server.configure; + +import com.google.inject.Inject; +import de.chojo.gamejam.server.ServerService; +import de.chojo.gamejam.server.CommandContextProvider; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.annotations.interactions.Param; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; +import org.slf4j.Logger; + +import static org.slf4j.LoggerFactory.getLogger; + +@Bundle("locale") +@Interaction +public class MaxPlayersCommand { + private static final Logger log = getLogger(MaxPlayersCommand.class); + + private final CommandContextProvider userContextService; + private final ServerService serverService; + + @Inject + public MaxPlayersCommand(CommandContextProvider commandContextProvider, ServerService serverService) { + this.userContextService = commandContextProvider; + this.serverService = serverService; + } + + @Command(value = "server configure maxplayers") + public void onCommand(CommandEvent event, @Param("amount" ) int amount) { + var team = userContextService.getUserContext(event.getMember()).team(); + + if (!serverService.dockerService().isRunning(team.id())) { + event.reply("error.servernotrunning"); + return; + } + + var containerName = serverService.dockerService().containerName(team.id()); + + serverService.serverHttpService().configureMaxPlayers(containerName, amount); + event.reply("command.server.configure.maxplayers.message.success"); + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/configure/Message.java b/bot/src/main/java/de/chojo/gamejam/commands/server/configure/Message.java deleted file mode 100644 index aa124fd4..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/server/configure/Message.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.server.configure; - -import de.chojo.gamejam.commands.server.Server; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.modals.handler.ModalHandler; -import de.chojo.jdautil.modals.handler.TextInputHandler; -import de.chojo.jdautil.util.Futures; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.components.textinput.TextInputStyle; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -import org.slf4j.Logger; - -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; - -import static org.slf4j.LoggerFactory.getLogger; - -public class Message implements SlashHandler { - private static final Logger log = getLogger(Message.class); - private final Server server; - - public Message(Server server) { - this.server = server; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var optServer = server.getServer(event, context); - if (optServer.isEmpty()) return; - - var teamServer = optServer.get(); - - if (!teamServer.isRunning()) { - event.reply(context.localize("error.servernotrunning")).queue(); - return; - } - - context.registerModal(ModalHandler.builder(context.localize("command.server.configure.message.message.modal.title")) - .addInput(TextInputHandler.builder("message", context.localize("command.server.configure.message.message.modal.input.message.label"), TextInputStyle.PARAGRAPH) - .withPlaceholder(context.localize("command.server.configure.message.message.modal.input.message.placeholder")) - .build()) - .withHandler(modalEvent -> { - var content = modalEvent.getValues().get(0).getAsString(); - var request = teamServer.requestBuilder("v1/config/message") - .POST(HttpRequest.BodyPublishers.ofString(content)) - .build(); - teamServer.http().sendAsync(request, HttpResponse.BodyHandlers.discarding()) - .whenComplete(Futures.whenComplete(res -> modalEvent.reply(context.localize("command.server.configure.message.message.success")).queue(), - err -> log.error("Failed to send request.", err))); - }) - .build()); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/configure/MessageCommand.java b/bot/src/main/java/de/chojo/gamejam/commands/server/configure/MessageCommand.java new file mode 100644 index 00000000..b0924338 --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/commands/server/configure/MessageCommand.java @@ -0,0 +1,67 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.commands.server.configure; + +import com.google.inject.Inject; +import de.chojo.gamejam.server.ServerService; +import de.chojo.gamejam.server.CommandContextProvider; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.annotations.interactions.Modal; +import io.github.kaktushose.jdac.annotations.interactions.Param; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; +import io.github.kaktushose.jdac.dispatching.events.interactions.ModalEvent; +import net.dv8tion.jda.api.components.label.Label; +import net.dv8tion.jda.api.components.textinput.TextInput; +import net.dv8tion.jda.api.components.textinput.TextInputStyle; +import org.slf4j.Logger; + +import static org.slf4j.LoggerFactory.getLogger; + +@Bundle("locale") +@Interaction +public class MessageCommand { + private static final Logger log = getLogger(MessageCommand.class); + + private final ServerService serverService; + private final CommandContextProvider commandContextProvider; + + @Inject + public MessageCommand(ServerService serverService, CommandContextProvider commandContextProvider) { + this.serverService = serverService; + this.commandContextProvider = commandContextProvider; + } + + @Command + public void onCommand(CommandEvent event, @Param("message") String message) { + var team = commandContextProvider.getUserContext(event.getMember()).team(); + + if (!serverService.dockerService().isRunning(team.id())) { + event.reply("error.servernotrunning"); + return; + } + + event.replyModal("onModal", + Label.of("command.server.configure.message.message.modal.input.message.label", TextInput.create("message-input", TextInputStyle.SHORT) + .setPlaceholder("command.server.configure.message.message.modal.input.message.placeholder") + .build()) + ); + + serverService.serverHttpService().configureMessage(serverService.dockerService().containerName(team.id()), message); + event.reply("command.server.configure.message.message.success"); + } + + @Modal("command.server.configure.message.message.modal.title") + public void onModal(ModalEvent event) { + var team = commandContextProvider.getUserContext(event.getMember()).team(); + var containerName = serverService.dockerService().containerName(team.id()); + var content = event.value("message-input").getAsString(); + serverService.serverHttpService().configureMessage(containerName, content); + event.reply("command.server.configure.message.message.success"); + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/configure/SpectatorOverflow.java b/bot/src/main/java/de/chojo/gamejam/commands/server/configure/SpectatorOverflow.java deleted file mode 100644 index 78292373..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/server/configure/SpectatorOverflow.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.server.configure; - -import de.chojo.gamejam.commands.server.Server; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.util.Futures; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import org.slf4j.Logger; - -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; - -import static org.slf4j.LoggerFactory.getLogger; - -public class SpectatorOverflow implements SlashHandler { - private static final Logger log = getLogger(SpectatorOverflow.class); - private final Server server; - - public SpectatorOverflow(Server server) { - this.server = server; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var optServer = server.getServer(event, context); - if (optServer.isEmpty()) return; - - var teamServer = optServer.get(); - - if (!teamServer.isRunning()) { - event.reply(context.localize("error.servernotrunning")).queue(); - return; - } - - var request = teamServer.requestBuilder("v1/config/spectatoroverflow") - .POST(HttpRequest.BodyPublishers.ofString(String.valueOf(event.getOption("state").getAsBoolean()))) - .build(); - teamServer.http().sendAsync(request, HttpResponse.BodyHandlers.discarding()) - .whenComplete(Futures.whenComplete(res -> event.reply(context.localize("command.server.configure.spectatoroverflow.message.success")).queue(), - err -> log.error("Failed to send request.", err)));; - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/configure/SpectatorOverflowCommand.java b/bot/src/main/java/de/chojo/gamejam/commands/server/configure/SpectatorOverflowCommand.java new file mode 100644 index 00000000..2d2de7f2 --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/commands/server/configure/SpectatorOverflowCommand.java @@ -0,0 +1,46 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.commands.server.configure; + +import com.google.inject.Inject; +import de.chojo.gamejam.server.ServerService; +import de.chojo.gamejam.server.CommandContextProvider; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.annotations.interactions.Param; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; +import org.slf4j.Logger; + +import static org.slf4j.LoggerFactory.getLogger; + +@Bundle("locale") +@Interaction +public class SpectatorOverflowCommand { + private static final Logger log = getLogger(SpectatorOverflowCommand.class); + private final ServerService serverService; + private final CommandContextProvider commandContextProvider; + + @Inject + public SpectatorOverflowCommand(ServerService serverService, CommandContextProvider commandContextProvider) { + this.serverService = serverService; + this.commandContextProvider = commandContextProvider; + } + + @Command(value = "server configure spectatoroverflow") + public void onCommand(CommandEvent event, @Param("state") boolean state) { + var team = commandContextProvider.getUserContext(event.getMember()).team(); + + if (!serverService.dockerService().isRunning(team.id())) { + event.reply("error.servernotrunning"); + return; + } + + serverService.serverHttpService().configureSpectatorOverflow(serverService.dockerService().containerName(team.id()), state); + event.reply("command.server.configure.spectatoroverflow.message.success"); + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/configure/Whitelist.java b/bot/src/main/java/de/chojo/gamejam/commands/server/configure/Whitelist.java deleted file mode 100644 index dd17ff28..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/server/configure/Whitelist.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.server.configure; - -import de.chojo.gamejam.commands.server.Server; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.util.Futures; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import org.slf4j.Logger; - -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; - -import static org.slf4j.LoggerFactory.getLogger; - -public class Whitelist implements SlashHandler { - private static final Logger log = getLogger(Whitelist.class); - private final Server server; - - public Whitelist(Server server) { - this.server = server; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var optServer = server.getServer(event, context); - if (optServer.isEmpty()) return; - - var teamServer = optServer.get(); - - if (!teamServer.isRunning()) { - event.reply(context.localize("error.servernotrunning")).queue(); - return; - } - - var request = teamServer.requestBuilder("v1/config/whitelist") - .POST(HttpRequest.BodyPublishers.ofString(String.valueOf(event.getOption("state").getAsBoolean()))) - .build(); - teamServer.http().sendAsync(request, HttpResponse.BodyHandlers.discarding()) - .whenComplete(Futures.whenComplete(res -> event.reply(context.localize("command.server.configure.whitelist.message.success")).queue(), - err -> log.error("Failed to send request.", err)));; - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/download/DownloadPluginData.java b/bot/src/main/java/de/chojo/gamejam/commands/server/download/DownloadPluginData.java deleted file mode 100644 index 584c9012..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/server/download/DownloadPluginData.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.server.download; - -import de.chojo.gamejam.commands.server.Server; -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.data.dao.guild.jams.Jam; -import de.chojo.gamejam.server.ServerService; -import de.chojo.gamejam.util.TempFile; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.util.Choice; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import net.dv8tion.jda.api.exceptions.ErrorResponseException; -import net.dv8tion.jda.api.requests.ErrorResponse; -import net.dv8tion.jda.api.requests.RestAction; -import net.dv8tion.jda.api.utils.FileUpload; -import net.lingala.zip4j.ZipFile; -import net.lingala.zip4j.exception.ZipException; -import org.slf4j.Logger; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; - -import static org.slf4j.LoggerFactory.getLogger; - -public class DownloadPluginData implements SlashHandler { - private static final Logger log = getLogger(DownloadPluginData.class); - private final Server server; - private final Guilds guilds; - private final ServerService serverService; - - public DownloadPluginData(Server server, Guilds guilds, ServerService serverService) { - this.server = server; - this.guilds = guilds; - this.serverService = serverService; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var optServer = server.getServer(event, context); - if (optServer.isEmpty()) return; - var teamServer = optServer.get(); - - var path = event.getOption("path").getAsString(); - - if (path.contains("..")) { - event.reply(context.localize("error.invalidpath")).queue(); - return; - } - - var pluginFile = teamServer.plugins().resolve(path); - - if (pluginFile.toFile().isFile()) { - // No download from plugin root - if (pluginFile.getParent().equals(teamServer.plugins())) { - event.reply(context.localize("error.invalidpath")).queue(); - return; - } - event.replyFiles(FileUpload.fromData(pluginFile, pluginFile.toFile().getName())).queue(); - return; - } - - event.reply(context.localize("command.server.download.downloadplugindata.message.zipping")).queue(); - Path tempFile; - try { - tempFile = TempFile.createPath("download", "zip"); - try (var zip = new ZipFile(tempFile.toFile())) { - zip.addFolder(pluginFile.toFile()); - } - tempFile.toFile().deleteOnExit(); - } catch (ZipException e) { - log.error("Failed to zip date", e); - event.getHook().editOriginal(context.localize("command.server.download.downloadplugindata.message.fail.zip")).queue(); - return; - } catch (IOException e) { - log.error("Failed to create zip file", e); - event.getHook().editOriginal(context.localize("command.server.download.downloadplugindata.message.fail.tempfile")).queue(); - return; - } - event.getHook().editOriginal(context.localize("command.server.download.downloadplugindata.message.success")) - .setFiles(FileUpload.fromData(tempFile, pluginFile.toFile().getName() + ".zip")) - .queue(RestAction.getDefaultSuccess(), err -> { - if (err instanceof ErrorResponseException response) { - if (response.getErrorResponse() == ErrorResponse.FILE_UPLOAD_MAX_SIZE_EXCEEDED) { - event.getHook().editOriginal(context.localize("command.server.download.downloadplugindata.message.fail.filetolarge")).queue(); - } - } - }); - } - - @Override - public void onAutoComplete(CommandAutoCompleteInteractionEvent event, EventContext context) { - var optServer = guilds.guild(event).jams().activeJam() - .map(Jam::teams) - .flatMap(teams -> teams.byMember(event.getUser())) - .map(serverService::get); - if (optServer.isEmpty()) return; - var server = optServer.get(); - - var option = event.getFocusedOption(); - if ("path".equals(option.getName())) { - var plugins = server.plugins(); - var currPath = option.getValue(); - if (currPath.contains("..")) { - event.replyChoices().queue(); - return; - } - var split = currPath.split("/"); - - // Root dir - if (split.length == 1 && !currPath.endsWith("/")) { - var currValue = split[0].toLowerCase(); - var files = files(plugins).stream() - .filter(File::isDirectory) - .filter(file -> file.getName().toLowerCase().startsWith(currValue)) - .limit(25) - .map(line -> fileName(plugins, line)); - event.replyChoices(Choice.toStringChoice(files)).queue(); - return; - } - - if (!currPath.endsWith("/")) { - split = Arrays.copyOfRange(split, 0, split.length - 1); - } - - var path = plugins; - - for (var s : split) { - path = path.resolve(s); - } - - if (currPath.endsWith("/")) { - var files = files(path).stream() - .limit(25) - .map(line -> fileName(plugins, line)); - event.replyChoices(Choice.toStringChoice(files)).queue(); - return; - } - split = currPath.split("/"); - var currValue = currPath.split("/")[split.length - 1].toLowerCase(); - - var files = files(path).stream() - .filter(file -> file.getName().toLowerCase().startsWith(currValue)) - .limit(24) - .map(line -> fileName(plugins, line)) - .collect(Collectors.toCollection(ArrayList::new)); - - event.replyChoices(Choice.toStringChoice(files)).queue(); - } - } - - private List files(Path path) { - return Arrays.stream(path.toFile().listFiles()).toList(); - } - - private String fileName(Path strip, File file) { - if (file.isDirectory()) { - return (file.toPath() + "/").replace(strip.toString(), "").replaceAll("^/", ""); - } - return file.toPath().toString().replace(strip.toString(), "").replaceAll("^/", ""); - - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/plugins/Install.java b/bot/src/main/java/de/chojo/gamejam/commands/server/plugins/Install.java deleted file mode 100644 index 08aa3100..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/server/plugins/Install.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.server.plugins; - -import de.chojo.gamejam.commands.server.Server; -import de.chojo.gamejam.configuration.Configuration; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.localization.util.Replacement; -import de.chojo.jdautil.util.Choice; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import org.slf4j.Logger; - -import java.io.IOException; -import java.nio.file.Files; - -import static org.slf4j.LoggerFactory.getLogger; - -public class Install implements SlashHandler { - private static final Logger log = getLogger(Install.class); - private final Configuration configuration; - private final Server server; - - public Install(Configuration configuration, Server server) { - this.configuration = configuration; - this.server = server; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var optServer = server.getServer(event, context); - if (optServer.isEmpty()) return; - var teamServer = optServer.get(); - - var pluginName = event.getOption("plugin").getAsString(); - var optPlugin = configuration.plugins().byName(pluginName); - if (optPlugin.isEmpty()) { - event.reply(context.localize("error.pluginnotfound")).queue(); - return; - } - var plugin = optPlugin.get(); - - var pluginPath = teamServer.plugins().resolve(plugin.toFile().getName()); - pluginPath.toFile().delete(); - try { - Files.createSymbolicLink(pluginPath, plugin.toAbsolutePath()); - } catch (IOException e) { - event.reply(context.localize("command.server.plugins.install.message.fail")).queue(); - log.error("Could not install plugin", e); - return; - } - event.reply(context.localize("command.server.plugins.install.message.success", Replacement.create("NAME", pluginName))).queue(); - } - - @Override - public void onAutoComplete(CommandAutoCompleteInteractionEvent event, EventContext context) { - var option = event.getFocusedOption(); - if ("plugin".equals(option.getName())) { - var currValue = option.getValue().toLowerCase(); - if (currValue.isEmpty()) { - event.replyChoices(Choice.toStringChoice(configuration.plugins().pluginNames().stream().limit(25))) - .queue(); - return; - } - var stream = configuration.plugins().pluginNames().stream() - .filter(name -> name.toLowerCase().startsWith(currValue)) - .limit(25); - event.replyChoices(Choice.toStringChoice(stream)).queue(); - } - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/plugins/Uninstall.java b/bot/src/main/java/de/chojo/gamejam/commands/server/plugins/Uninstall.java deleted file mode 100644 index 82808724..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/server/plugins/Uninstall.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.server.plugins; - -import de.chojo.gamejam.commands.server.Server; -import de.chojo.gamejam.configuration.Configuration; -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.data.dao.guild.jams.Jam; -import de.chojo.gamejam.server.ServerService; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.util.Choice; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -import java.io.File; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -public class Uninstall implements SlashHandler { - private final Server server; - private final Configuration configuration; - private final Guilds guilds; - private final ServerService serverService; - - public Uninstall(Server server, Configuration configuration, Guilds guilds, ServerService serverService) { - this.server = server; - this.configuration = configuration; - this.guilds = guilds; - this.serverService = serverService; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var optServer = server.getServer(event, context); - if (optServer.isEmpty()) return; - var teamServer = optServer.get(); - - var pluginName = event.getOption("plugin").getAsString(); - var optPlugin = configuration.plugins().byName(pluginName); - if (optPlugin.isEmpty()) { - event.reply(context.localize("error.pluginnotfound")).queue(); - return; - } - var plugin = optPlugin.get(); - - var pluginPath = teamServer.plugins().resolve(plugin.toFile().getName()); - pluginPath.toFile().delete(); - - if (event.getOption("deletedata").getAsBoolean()) { - var pluginDir = teamServer.plugins().resolve(pluginName); - teamServer.deleteDirectory(pluginDir); - event.reply(context.localize("command.server.plugins.uninstall.message.success.pluginanddata")).queue(); - } else { - event.reply(context.localize("command.server.plugins.uninstall.message.success.plugin")).queue(); - } - } - - @Override - public void onAutoComplete(CommandAutoCompleteInteractionEvent event, EventContext context) { - var optServer = guilds.guild(event).jams().activeJam() - .map(Jam::teams) - .flatMap(teams -> teams.byMember(event.getUser())) - .map(serverService::get); - if (optServer.isEmpty()) return; - - var option = event.getFocusedOption(); - if ("plugin".equals(option.getName())) { - var allowedPlugins = configuration.plugins().pluginFiles().stream() - .map(File::getName) - .collect(Collectors.toSet()); - - var installedPlugins = Stream.of(optServer.get().plugins().toFile().listFiles(File::isFile)) - .filter(plugin -> allowedPlugins.contains(plugin.getName())) - .map(file -> file.getName().replace(".jar", "")) - .toList(); - - var currValue = option.getValue().toLowerCase(); - - if (currValue.isEmpty()) { - event.replyChoices(Choice.toStringChoice(installedPlugins)) - .queue(); - return; - } - - var stream = installedPlugins.stream() - .filter(name -> name.toLowerCase().startsWith(currValue)) - .limit(25); - event.replyChoices(Choice.toStringChoice(stream)).queue(); - } - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/process/Console.java b/bot/src/main/java/de/chojo/gamejam/commands/server/process/Console.java deleted file mode 100644 index 583cf28c..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/server/process/Console.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.server.process; - -import de.chojo.gamejam.commands.server.Server; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -public class Console implements SlashHandler { - private final Server server; - - public Console(Server server) { - this.server = server; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var optServer = server.getServer(event, context); - if (optServer.isEmpty()) return; - var teamServer = optServer.get(); - var command = event.getOption("command").getAsString(); - - if (command.startsWith("stop") || command.startsWith("restart")) { - event.reply(context.localize("command.server.process.console.message.notexecutable")).queue(); - return; - } - teamServer.send(command); - event.reply(context.localize("command.server.process.console.message.executed")).queue(); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/process/ConsoleCommand.java b/bot/src/main/java/de/chojo/gamejam/commands/server/process/ConsoleCommand.java new file mode 100644 index 00000000..a4e93f8e --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/commands/server/process/ConsoleCommand.java @@ -0,0 +1,47 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.commands.server.process; + +import com.google.inject.Inject; +import de.chojo.gamejam.server.ServerService; +import de.chojo.gamejam.server.CommandContextProvider; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.annotations.interactions.Param; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; + +@Bundle("locale") +@Interaction +public class ConsoleCommand { + private final ServerService serverService; + private final CommandContextProvider commandContextProvider; + + @Inject + public ConsoleCommand(ServerService serverService, CommandContextProvider commandContextProvider) { + this.serverService = serverService; + this.commandContextProvider = commandContextProvider; + } + + @Command(value = "server process console") + public void onCommand(CommandEvent event, @Param("command") String command) { + var team = commandContextProvider.getUserContext(event.getMember()).team(); + + if (!serverService.dockerService().isRunning(team.id())) { + event.reply("error.servernotrunning"); + return; + } + + if (command.startsWith("stop") || command.startsWith("restart")) { + event.reply("command.server.process.console.message.notexecutable"); + return; + } + + serverService.dockerService().sendCommand(team.id(), command); + event.reply("command.server.process.console.message.executed"); + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/process/Log.java b/bot/src/main/java/de/chojo/gamejam/commands/server/process/Log.java deleted file mode 100644 index 68e46418..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/server/process/Log.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.server.process; - -import de.chojo.gamejam.commands.server.Server; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import net.dv8tion.jda.api.utils.FileUpload; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; - -public class Log implements SlashHandler { - private final Server server; - - public Log(Server server) { - this.server = server; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var optServer = server.getServer(event, context); - if(optServer.isEmpty())return; - var teamServer = optServer.get(); - var logs = teamServer.logs(); - var content = logs.substring(Math.max(logs.length() - 1950, 0)); - try(InputStream inputStream = new ByteArrayInputStream(logs.getBytes(StandardCharsets.UTF_8))) { - event.reply("```log%n%s%n```".formatted(content)) - .addFiles(FileUpload.fromData(inputStream, "latest.log")) - .queue(); - } catch (IOException _) { - } - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/process/LogCommand.java b/bot/src/main/java/de/chojo/gamejam/commands/server/process/LogCommand.java new file mode 100644 index 00000000..54975729 --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/commands/server/process/LogCommand.java @@ -0,0 +1,50 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.commands.server.process; + +import com.google.inject.Inject; +import de.chojo.gamejam.server.ServerService; +import de.chojo.gamejam.server.CommandContextProvider; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; +import net.dv8tion.jda.api.utils.FileUpload; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +@Bundle("locale") +@Interaction +public class LogCommand { + private final ServerService serverService; + private final CommandContextProvider commandContextProvider; + + @Inject + public LogCommand(ServerService serverService, CommandContextProvider commandContextProvider) { + this.serverService = serverService; + this.commandContextProvider = commandContextProvider; + } + + @Command(value = "server process log") + public void onCommand(CommandEvent event) { + var team = commandContextProvider.getUserContext(event.getMember()).team(); + var dockerService = serverService.dockerService(); + + var logs = dockerService.logs(team.id()); + + var content = logs.substring(Math.max(logs.length() - 1950, 0)); + try(InputStream inputStream = new ByteArrayInputStream(logs.getBytes(StandardCharsets.UTF_8))) { + event.reply("```log%n%s%n```".formatted(content)) + .replyFiles(FileUpload.fromData(inputStream, "latest.log")) + .queue(); + } catch (IOException _) { + } + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/process/Restart.java b/bot/src/main/java/de/chojo/gamejam/commands/server/process/Restart.java deleted file mode 100644 index 14542bb3..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/server/process/Restart.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.server.process; - -import de.chojo.gamejam.commands.server.Server; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -public class Restart implements SlashHandler { - private final Server server; - - public Restart(Server server) { - this.server = server; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var optServer = server.getServer(event, context); - if(optServer.isEmpty())return; - var teamServer = optServer.get(); - if (teamServer.exists()) { - teamServer.stop(); - event.getHook().editOriginal(context.localize("command.server.process.restart.message.restarted")).queue(); - event.reply(context.localize("command.server.process.restart.message.restarting")).queue(); - } - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/process/RestartCommand.java b/bot/src/main/java/de/chojo/gamejam/commands/server/process/RestartCommand.java new file mode 100644 index 00000000..17d0bce7 --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/commands/server/process/RestartCommand.java @@ -0,0 +1,45 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.commands.server.process; + +import com.google.inject.Inject; +import de.chojo.gamejam.server.ServerService; +import de.chojo.gamejam.server.CommandContextProvider; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; + +@Bundle("locale") +@Interaction +public class RestartCommand { + + private final ServerService serverService; + private final CommandContextProvider commandContextProvider; + + @Inject + public RestartCommand(ServerService serverService, CommandContextProvider commandContextProvider) { + this.serverService = serverService; + this.commandContextProvider = commandContextProvider; + } + + @Command(value = "server process restart") + public void onCommand(CommandEvent event) { + var team = commandContextProvider.getUserContext(event.getMember()).team(); + var dockerService = serverService.dockerService(); + + if (!dockerService.isRunning(team.id())) { + event.reply("error.servernotrunning"); + return; + } + + var teamServer = serverService.get(team); + + serverService.restartServer(teamServer); + event.reply("command.server.process.restart.message.restarting"); + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/process/Start.java b/bot/src/main/java/de/chojo/gamejam/commands/server/process/Start.java deleted file mode 100644 index 45f60ed8..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/server/process/Start.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.server.process; - -import de.chojo.gamejam.commands.server.Server; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -public class Start implements SlashHandler { - private final Server server; - - public Start(Server server) { - this.server = server; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var optServer = server.getServer(event, context); - if(optServer.isEmpty())return; - var teamServer = optServer.get(); - if (teamServer.exists()) { - if (teamServer.start()) { - event.reply(context.localize("command.server.process.start.message.success")).queue(); - } else { - event.reply(context.localize("command.server.process.start.message.fail")).queue(); - } - } - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/process/StartCommand.java b/bot/src/main/java/de/chojo/gamejam/commands/server/process/StartCommand.java new file mode 100644 index 00000000..41310405 --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/commands/server/process/StartCommand.java @@ -0,0 +1,44 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.commands.server.process; + +import com.google.inject.Inject; +import de.chojo.gamejam.server.ServerService; +import de.chojo.gamejam.server.CommandContextProvider; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; + +@Bundle("locale") +@Interaction +public class StartCommand { + private final ServerService serverService; + private final CommandContextProvider commandContextProvider; + + @Inject + public StartCommand(ServerService serverService, CommandContextProvider commandContextProvider) { + this.serverService = serverService; + this.commandContextProvider = commandContextProvider; + } + + @Command(value = "server process start") + public void onCommand(CommandEvent event) { + var team = commandContextProvider.getUserContext(event.getMember()).team(); + + var dockerService = serverService.dockerService(); + if (!dockerService.exists(team.id())) { + event.reply("command.server.process.start.message.fail"); + return; + } + + var teamServer = serverService.get(team); + + serverService.startServer(teamServer); + event.reply("command.server.process.start.message.success"); + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/process/Status.java b/bot/src/main/java/de/chojo/gamejam/commands/server/process/Status.java deleted file mode 100644 index 23cedd31..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/server/process/Status.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.server.process; - -import de.chojo.gamejam.commands.server.Server; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -public class Status implements SlashHandler { - private final Server server; - - public Status(Server server) { - this.server = server; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var optServer = server.getServer(event, context); - if (optServer.isEmpty()) return; - - event.replyEmbeds(optServer.get().detailStatus(context).join()).queue(); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/process/StatusCommand.java b/bot/src/main/java/de/chojo/gamejam/commands/server/process/StatusCommand.java new file mode 100644 index 00000000..b67babed --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/commands/server/process/StatusCommand.java @@ -0,0 +1,38 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.commands.server.process; + +import com.google.inject.Inject; +import de.chojo.gamejam.message.EmbedHelper; +import de.chojo.gamejam.server.ServerService; +import de.chojo.gamejam.server.CommandContextProvider; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; + +@Bundle("locale") +@Interaction +public class StatusCommand { + + private final ServerService serverService; + private final CommandContextProvider commandContextProvider; + + @Inject + public StatusCommand(ServerService serverService, CommandContextProvider commandContextProvider) { + this.serverService = serverService; + this.commandContextProvider = commandContextProvider; + } + + @Command(value = "server process status") + public void onCommand(CommandEvent event) { + var team = commandContextProvider.getUserContext(event.getMember()).team(); + + EmbedHelper.embedDetailedStatus(team, serverService, event) + .whenComplete((embed, _) -> event.reply(embed)); + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/process/Stop.java b/bot/src/main/java/de/chojo/gamejam/commands/server/process/Stop.java deleted file mode 100644 index fc7cb3f0..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/server/process/Stop.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.server.process; - -import de.chojo.gamejam.commands.server.Server; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -public class Stop implements SlashHandler { - private final Server server; - - public Stop(Server server) { - this.server = server; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var optServer = server.getServer(event, context); - if(optServer.isEmpty())return; - var teamServer = optServer.get(); - if (teamServer.exists()) { - event.reply(context.localize("command.server.process.stop.message.stopping")).queue(); - teamServer.stop().thenRun(() -> event.getHook().editOriginal( - context.localize("command.server.process.stop.message.stopped")).queue()); - } - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/process/StopCommand.java b/bot/src/main/java/de/chojo/gamejam/commands/server/process/StopCommand.java new file mode 100644 index 00000000..f188a9e4 --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/commands/server/process/StopCommand.java @@ -0,0 +1,41 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.commands.server.process; + +import com.google.inject.Inject; +import de.chojo.gamejam.server.ServerService; +import de.chojo.gamejam.server.CommandContextProvider; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; + +@Bundle("locale") +@Interaction +public class StopCommand { + private final ServerService serverService; + private final CommandContextProvider commandContextProvider; + + @Inject + public StopCommand(ServerService serverService, CommandContextProvider commandContextProvider) { + this.serverService = serverService; + this.commandContextProvider = commandContextProvider; + } + + @Command(value = "server process stop") + public void onCommand(CommandEvent event) { + var team = commandContextProvider.getUserContext(event.getMember()).team(); + var dockerService = serverService.dockerService(); + if (!dockerService.isRunning(team.id())) { + event.reply("error.servernotrunning"); + return; + } + var teamServer = serverService.get(team); + serverService.stopServer(teamServer); + event.reply("command.server.process.stop.message.stopping"); + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/system/Delete.java b/bot/src/main/java/de/chojo/gamejam/commands/server/system/Delete.java deleted file mode 100644 index e92327ac..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/server/system/Delete.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.server.system; - -import de.chojo.gamejam.commands.server.Server; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import org.slf4j.Logger; - -import java.io.IOException; - -import static org.slf4j.LoggerFactory.getLogger; - -public class Delete implements SlashHandler { - private static final Logger log = getLogger(Delete.class); - private final Server server; - - public Delete(Server server) { - this.server = server; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var optServer = server.getServer(event, context); - if (optServer.isEmpty()) return; - var teamServer = optServer.get(); - boolean deleted; - try { - deleted = teamServer.purge(); - } catch (IOException e) { - log.error("Could not purge server", e); - event.reply(context.localize("command.server.system.delete.message.error")).queue(); - return; - } - - if (deleted) { - event.reply(context.localize("command.server.system.delete.message.success")).queue(); - } else { - event.reply(context.localize("command.server.system.delete.message.notsetup")).queue(); - } - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/system/DeleteCommand.java b/bot/src/main/java/de/chojo/gamejam/commands/server/system/DeleteCommand.java new file mode 100644 index 00000000..92fa3fe4 --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/commands/server/system/DeleteCommand.java @@ -0,0 +1,46 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.commands.server.system; + +import com.google.inject.Inject; +import de.chojo.gamejam.server.ServerService; +import de.chojo.gamejam.server.CommandContextProvider; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; +import org.slf4j.Logger; + +import static org.slf4j.LoggerFactory.getLogger; + +@Bundle("locale") +@Interaction +public class DeleteCommand { + private static final Logger log = getLogger(DeleteCommand.class); + + private final ServerService serverService; + private final CommandContextProvider commandContextProvider; + + @Inject + public DeleteCommand(ServerService serverService, CommandContextProvider commandContextProvider) { + this.serverService = serverService; + this.commandContextProvider = commandContextProvider; + } + + @Command(value = "server system delete") + public void onCommand(CommandEvent event) { + var team = commandContextProvider.getUserContext(event.getMember()).team(); + + if (!serverService.dockerService().exists(team.id())) { + event.reply("command.server.system.delete.message.notsetup"); + return; + } + + serverService.dockerService().destroyServer(team.id()); + event.reply("command.server.system.delete.message.success"); + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/system/Setup.java b/bot/src/main/java/de/chojo/gamejam/commands/server/system/Setup.java deleted file mode 100644 index 10fdb03b..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/server/system/Setup.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.server.system; - -import de.chojo.gamejam.commands.server.Server; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import org.slf4j.Logger; - -import java.io.IOException; - -import static org.slf4j.LoggerFactory.getLogger; - -public class Setup implements SlashHandler { - private static final Logger log = getLogger(Setup.class); - private final Server server; - - public Setup(Server server) { - this.server = server; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var optServer = server.getServer(event, context); - if(optServer.isEmpty())return; - var teamServer = optServer.get(); - boolean setup; - try { - setup = teamServer.setup(); - } catch (IOException e) { - log.error("Could not setup server", e); - event.reply(context.localize("command.server.system.setup.message.error")).queue(); - return; - } - - if (setup) { - event.reply(context.localize("command.server.system.setup.message.success")).queue(); - } else { - event.reply(context.localize("command.server.system.setup.message.alreadysetup")).queue(); - } - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/system/SetupCommand.java b/bot/src/main/java/de/chojo/gamejam/commands/server/system/SetupCommand.java new file mode 100644 index 00000000..a6351f00 --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/commands/server/system/SetupCommand.java @@ -0,0 +1,45 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.commands.server.system; + +import com.google.inject.Inject; +import de.chojo.gamejam.server.ServerService; +import de.chojo.gamejam.server.CommandContextProvider; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; +import org.slf4j.Logger; + +import static org.slf4j.LoggerFactory.getLogger; + +@Bundle("locale") +@Interaction +public class SetupCommand { + private static final Logger log = getLogger(SetupCommand.class); + private final ServerService serverService; + private final CommandContextProvider commandContextProvider; + + @Inject + public SetupCommand(ServerService serverService, CommandContextProvider commandContextProvider) { + this.serverService = serverService; + this.commandContextProvider = commandContextProvider; + } + + @Command(value = "server system setup") + public void onCommand(CommandEvent event) { + var team = commandContextProvider.getUserContext(event.getMember()).team(); + + if (serverService.dockerService().exists(team.id())) { + event.reply("command.server.system.setup.message.alreadysetup"); + return; + } + + serverService.dockerService().provisionServer(team.id()); + event.reply("command.server.system.setup.message.success"); + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/upload/Plugin.java b/bot/src/main/java/de/chojo/gamejam/commands/server/upload/Plugin.java deleted file mode 100644 index 945e84ff..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/server/upload/Plugin.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.server.upload; - -import de.chojo.gamejam.commands.server.Server; -import de.chojo.gamejam.commands.server.util.ProgressDownloader; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import org.slf4j.Logger; - -import java.io.IOException; -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardCopyOption; - -import static org.slf4j.LoggerFactory.getLogger; - -public class Plugin implements SlashHandler { - private static final Logger log = getLogger(Plugin.class); - private final Server server; - - public Plugin(Server server) { - this.server = server; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var optServer = server.getServer(event, context); - if (optServer.isEmpty()) return; - var teamServer = optServer.get(); - - var downloadUrl = event.getOption("file").getAsAttachment().getProxy().getUrl(); - - var download = ProgressDownloader.download(event, context, downloadUrl); - - if (download.isEmpty()) return; - - var pluginFile = teamServer.plugins().resolve("plugin.jar"); - try { - Files.copy(download.get(), pluginFile, StandardCopyOption.REPLACE_EXISTING); - event.getHook().editOriginal(context.localize("command.server.upload.plugin.message.success")).queue(); - } catch (IOException e) { - event.getHook().editOriginal(context.localize("command.server.upload.plugin.message.fail")).queue(); - } - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/upload/UploadPluginData.java b/bot/src/main/java/de/chojo/gamejam/commands/server/upload/UploadPluginData.java deleted file mode 100644 index 52e9ce17..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/server/upload/UploadPluginData.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.server.upload; - -import de.chojo.gamejam.commands.server.Server; -import de.chojo.gamejam.commands.server.util.ProgressDownloader; -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.data.dao.guild.jams.Jam; -import de.chojo.gamejam.server.ServerService; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.util.Choice; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import org.slf4j.Logger; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; - -import static org.slf4j.LoggerFactory.getLogger; - -public class UploadPluginData implements SlashHandler { - private static final Logger log = getLogger(UploadPluginData.class); - private final Server server; - private final Guilds guilds; - private final ServerService serverService; - - public UploadPluginData(Server server, Guilds guilds, ServerService serverService) { - this.server = server; - this.guilds = guilds; - this.serverService = serverService; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var optServer = server.getServer(event, context); - if (optServer.isEmpty()) return; - var teamServer = optServer.get(); - - var downloadUrl = event.getOption("file").getAsAttachment().getProxy().getUrl(); - var path = event.getOption("path").getAsString(); - - if (path.contains("..")) { - event.reply(context.localize("error.invalidpath")).queue(); - return; - } - - var download = ProgressDownloader.download(event, context, downloadUrl); - - if (download.isEmpty()) return; - - var pluginFile = teamServer.plugins().resolve(path); - // No upload in plugin root - if (pluginFile.getParent().equals(teamServer.plugins())) { - event.reply(context.localize("error.invalidpath")).queue(); - return; - } - - // No updates into the update directory - if(pluginFile.equals(teamServer.plugins().resolve("update"))){ - event.reply(context.localize("error.invalidpath")).queue(); - return; - } - try { - Files.copy(download.get(), pluginFile, StandardCopyOption.REPLACE_EXISTING); - event.getHook().editOriginal(context.localize("command.server.upload.uploadplugindata.message.success")).queue(); - } catch (IOException e) { - event.getHook().editOriginal(context.localize("command.server.upload.uploadplugindata.message.fail")).queue(); - } - } - - @Override - public void onAutoComplete(CommandAutoCompleteInteractionEvent event, EventContext context) { - var optServer = guilds.guild(event).jams().activeJam() - .map(Jam::teams) - .flatMap(teams -> teams.byMember(event.getUser())) - .map(serverService::get); - if (optServer.isEmpty()) return; - var server = optServer.get(); - - var option = event.getFocusedOption(); - if ("path".equals(option.getName())) { - var plugins = server.plugins(); - var currPath = option.getValue(); - if (currPath.contains("..")) { - event.replyChoices().queue(); - return; - } - var split = currPath.split("/"); - - // Root dir - if (split.length == 1 && !currPath.endsWith("/")) { - var currValue = split[0].toLowerCase(); - var files = files(plugins).stream() - .filter(File::isDirectory) - .filter(file -> file.getName().toLowerCase().startsWith(currValue)) - .limit(25) - .map(line -> fileName(plugins, line)); - event.replyChoices(Choice.toStringChoice(files)).queue(); - return; - } - - if (!currPath.endsWith("/")) { - split = Arrays.copyOfRange(split, 0, split.length - 1); - } - - var path = plugins; - - for (var s : split) { - path = path.resolve(s); - } - - if (currPath.endsWith("/")) { - var files = files(path).stream() - .limit(25) - .map(line -> fileName(plugins, line)); - event.replyChoices(Choice.toStringChoice(files)).queue(); - return; - } - split = currPath.split("/"); - var currValue = currPath.split("/")[split.length - 1].toLowerCase(); - - var files = files(path).stream() - .filter(file -> file.getName().toLowerCase().startsWith(currValue)) - .limit(24) - .map(line -> fileName(plugins, line)) - .collect(Collectors.toCollection(ArrayList::new)); - - files.add(0, currPath); - - event.replyChoices(Choice.toStringChoice(files)).queue(); - } - } - - private List files(Path path) { - return Arrays.stream(path.toFile().listFiles()).toList(); - } - - private String fileName(Path strip, File file) { - if (file.isDirectory()) { - return (file.toPath() + "/").replace(strip.toString(), "").replaceAll("^/", ""); - } - return file.toPath().toString().replace(strip.toString(), "").replaceAll("^/", ""); - - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/upload/World.java b/bot/src/main/java/de/chojo/gamejam/commands/server/upload/World.java deleted file mode 100644 index 720372a3..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/server/upload/World.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.server.upload; - -import de.chojo.gamejam.commands.server.Server; -import de.chojo.gamejam.commands.server.util.ProgressDownloader; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import org.slf4j.Logger; - -import static org.slf4j.LoggerFactory.getLogger; - -public class World implements SlashHandler { - private static final Logger log = getLogger(World.class); - private final Server server; - - public World(Server server) { - this.server = server; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var optServer = server.getServer(event, context); - if (optServer.isEmpty()) return; - var teamServer = optServer.get(); - - String downloadUrl = null; - - var urlOption = event.getOption("url"); - if (urlOption != null) { - downloadUrl = urlOption.getAsString(); - } - - var file = event.getOption("file"); - if (file != null) { - downloadUrl = file.getAsAttachment().getProxy().getUrl(); - } - - if (downloadUrl == null) { - event.reply(context.localize("command.server.upload.world.message.nofileorurl")).queue(); - return; - } - - var download = ProgressDownloader.download(event, context, downloadUrl); - - if (download.isEmpty()) return; - - event.getHook().editOriginal(context.localize("command.server.upload.world.message.replacing")).queue(); - if (teamServer.replaceWorld(download.get())) { - event.getHook().editOriginal(context.localize("command.server.upload.world.message.replaced")).queue(); - } else { - event.getHook().editOriginal(context.localize("command.server.upload.world.message.failed")).queue(); - } - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/ServerAdmin.java b/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/ServerAdmin.java deleted file mode 100644 index a633f9af..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/ServerAdmin.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.serveradmin; - -import de.chojo.gamejam.commands.serveradmin.handler.SyncVelocity; -import de.chojo.gamejam.commands.serveradmin.handler.info.Detailed; -import de.chojo.gamejam.commands.serveradmin.handler.info.Short; -import de.chojo.gamejam.commands.serveradmin.handler.restart.RestartAll; -import de.chojo.gamejam.commands.serveradmin.handler.restart.RestartTeam; -import de.chojo.gamejam.commands.serveradmin.handler.start.StartAll; -import de.chojo.gamejam.commands.serveradmin.handler.start.StartTeam; -import de.chojo.gamejam.commands.serveradmin.handler.stop.StopAll; -import de.chojo.gamejam.commands.serveradmin.handler.stop.StopTeam; -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.server.ServerService; -import de.chojo.jdautil.interactions.slash.Argument; -import de.chojo.jdautil.interactions.slash.Group; -import de.chojo.jdautil.interactions.slash.Slash; -import de.chojo.jdautil.interactions.slash.SubCommand; -import de.chojo.jdautil.interactions.slash.provider.SlashCommand; - -public class ServerAdmin extends SlashCommand { - public ServerAdmin(Guilds guilds, ServerService serverService) { - super(Slash.of("serveradmin", "command.serveradmin.description") - .adminCommand() - .group(Group.of("start", "command.serveradmin.start.description") - .subCommand(SubCommand.of("all", "command.serveradmin.start.all.description") - .handler(new StartAll(serverService, guilds))) - .subCommand(SubCommand.of("team", "command.serveradmin.start.team.description") - .handler(new StartTeam(serverService, guilds)) - .argument(Argument.text("team", "command.serveradmin.start.team.options.team.description").asRequired().withAutoComplete()))) - .group(Group.of("restart", "command.serveradmin.restart.description") - .subCommand(SubCommand.of("all", "command.serveradmin.restart.all.description") - .handler(new RestartAll(serverService, guilds))) - .subCommand(SubCommand.of("team", "command.serveradmin.restart.team.description") - .handler(new RestartTeam(serverService, guilds)) - .argument(Argument.text("team", "command.serveradmin.restart.team.options.team.description").asRequired().withAutoComplete()))) - .group(Group.of("stop", "command.serveradmin.stop.description") - .subCommand(SubCommand.of("all", "command.serveradmin.stop.all.description") - .handler(new StopAll(serverService, guilds))) - .subCommand(SubCommand.of("team", "command.serveradmin.stop.team.description") - .handler(new StopTeam(serverService, guilds)) - .argument(Argument.text("team", "command.serveradmin.stop.team.options.team.description").asRequired().withAutoComplete()))) - .group(Group.of("info", "command.serveradmin.info.description") - .subCommand(SubCommand.of("short", "command.serveradmin.info.short.description") - .handler(new Short(serverService, guilds))) - .subCommand(SubCommand.of("detailed", "command.serveradmin.info.detailed.description") - .handler(new Detailed(serverService, guilds)) - .argument(Argument.text("team", "command.serveradmin.info.detailed.options.team.description").withAutoComplete()))) - .subCommand(SubCommand.of("syncvelocity", "command.serveradmin.syncvelocity.description") - .handler(new SyncVelocity(serverService))) - ); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/ServerAdminCommands.java b/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/ServerAdminCommands.java new file mode 100644 index 00000000..db4b9831 --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/ServerAdminCommands.java @@ -0,0 +1,11 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.commands.serveradmin; + +public class ServerAdminCommands { + //TODO: Readd admin commands +} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/SyncVelocity.java b/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/SyncVelocity.java deleted file mode 100644 index 34755351..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/SyncVelocity.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.serveradmin.handler; - -import de.chojo.gamejam.server.ServerService; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -public class SyncVelocity implements SlashHandler { - private final ServerService serverService; - - public SyncVelocity(ServerService serverService) { - this.serverService = serverService; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - serverService.syncVelocity(); - event.reply(context.localize("command.serveradmin.syncvelocity.message.synced")).queue(); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/info/Detailed.java b/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/info/Detailed.java deleted file mode 100644 index a8577e04..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/info/Detailed.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.serveradmin.handler.info; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.server.ServerService; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.pagination.bag.ListPageBag; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import net.dv8tion.jda.api.utils.messages.MessageEditData; - -import java.util.Collections; -import java.util.concurrent.ExecutionException; - -public class Detailed implements SlashHandler { - private final ServerService serverService; - private final Guilds guilds; - - public Detailed(ServerService serverService, Guilds guilds) { - this.serverService = serverService; - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var currentJam = guilds.guild(event).jams().getCurrentJam(); - if (currentJam.isEmpty()) { - event.reply(context.localize("error.noactivejam")).queue(); - return; - } - var jam = currentJam.get(); - - var teamArg = event.getOption("team"); - - if (teamArg != null) { - var team = jam.teams().byName(teamArg.getAsString()); - if (team.isEmpty()) { - event.reply(context.localize("error.unkownteam")).queue(); - return; - } - var teamServer = serverService.get(team.get()); - event.replyEmbeds(teamServer.detailStatus(context).join()).queue(); - return; - } - - var servers = jam.teams().teams().stream() - .map(serverService::get) - .toList(); - - context.registerPage(new ListPageBag<>(servers) { - @Override - public MessageEditData buildPage() { - try { - return currentElement().detailStatus(context).thenApply(MessageEditData::fromEmbeds).get(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } catch (ExecutionException e) { - throw new RuntimeException(e); - } - } - }); - } - - @Override - public void onAutoComplete(CommandAutoCompleteInteractionEvent event, EventContext context) { - var guild = guilds.guild(event); - var option = event.getFocusedOption(); - if ("team".equals(option.getName())) { - var choices = guild.jams().nextOrCurrent() - .map(jam -> jam.teams().completeTeam(option.getValue())) - .orElse(Collections.emptyList()); - event.replyChoices(choices).queue(); - } - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/info/Short.java b/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/info/Short.java deleted file mode 100644 index bf8a1f9b..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/info/Short.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.serveradmin.handler.info; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.server.ServerService; -import de.chojo.gamejam.server.TeamServer; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -import java.util.stream.Collectors; - -public class Short implements SlashHandler { - private final ServerService serverService; - private final Guilds guilds; - - public Short(ServerService serverService, Guilds guilds) { - this.serverService = serverService; - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var currentJam = guilds.guild(event).jams().getCurrentJam(); - if (currentJam.isEmpty()) { - event.reply(context.localize("error.noactivejam")).queue(); - return; - } - var jam = currentJam.get(); - - var servers = jam.teams().teams().stream() - .map(serverService::get) - .map(TeamServer::status) - .collect(Collectors.joining("\n")); - - event.reply(servers).queue(); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/restart/RestartAll.java b/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/restart/RestartAll.java deleted file mode 100644 index 7b35cd11..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/restart/RestartAll.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.serveradmin.handler.restart; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.server.ServerService; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.localization.util.Replacement; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -public class RestartAll implements SlashHandler { - private final ServerService serverService; - private final Guilds guilds; - - public RestartAll(ServerService serverService, Guilds guilds) { - this.serverService = serverService; - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var currentJam = guilds.guild(event).jams().getCurrentJam(); - if (currentJam.isEmpty()) { - event.reply(context.localize("error.noactivejam")).queue(); - return; - } - var jam = currentJam.get(); - - var count = jam.teams().teams().stream() - .map(serverService::get) - .filter(server -> { - var running = server.isRunning(); - if (running) { - server.stop(); - } - return running; - }) - .count(); - event.reply(context.localize("command.serveradmin.restart.restartall.message.restarted", - Replacement.create("AMOUNT", count))).queue(); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/restart/RestartTeam.java b/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/restart/RestartTeam.java deleted file mode 100644 index 57190df4..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/restart/RestartTeam.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.serveradmin.handler.restart; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.server.ServerService; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.localization.util.Replacement; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -import java.util.Collections; - -public class RestartTeam implements SlashHandler { - private final ServerService serverService; - private final Guilds guilds; - - public RestartTeam(ServerService serverService, Guilds guilds) { - this.serverService = serverService; - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var currentJam = guilds.guild(event).jams().getCurrentJam(); - if (currentJam.isEmpty()) { - event.reply(context.localize("error.noactivejam")).queue(); - return; - } - var jam = currentJam.get(); - - var optTeam = jam.teams().byName(event.getOption("team").getAsString()); - - if (optTeam.isEmpty()) { - event.reply(context.localize("error.unkownteam")).queue(); - return; - } - - var started = optTeam.map(serverService::get).map(server -> { - var running = server.isRunning(); - server.restart(); - return running; - }).orElse(false); - if (started) { - event.reply(context.localize("command.serveradmin.restart.restartteam.message.restarted", - Replacement.create("TEAM", optTeam.get()))).queue(); - } else { - event.reply(context.localize("command.serveradmin.restart.restartteam.message.failed", - Replacement.create("TEAM", optTeam.get()))).queue(); - } - } - - @Override - public void onAutoComplete(CommandAutoCompleteInteractionEvent event, EventContext context) { - var guild = guilds.guild(event); - var option = event.getFocusedOption(); - if ("team".equals(option.getName())) { - var choices = guild.jams().nextOrCurrent() - .map(jam -> jam.teams().completeTeam(option.getValue())) - .orElse(Collections.emptyList()); - event.replyChoices(choices).queue(); - } - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/start/StartAll.java b/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/start/StartAll.java deleted file mode 100644 index 72bb07ab..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/start/StartAll.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.serveradmin.handler.start; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.server.ServerService; -import de.chojo.gamejam.server.TeamServer; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.localization.util.Replacement; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -public class StartAll implements SlashHandler { - private final ServerService serverService; - private final Guilds guilds; - - public StartAll(ServerService serverService, Guilds guilds) { - this.serverService = serverService; - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var currentJam = guilds.guild(event).jams().getCurrentJam(); - if (currentJam.isEmpty()) { - event.reply(context.localize("error.noactivejam")).queue(); - return; - } - var jam = currentJam.get(); - - var count = jam.teams().teams().stream() - .map(serverService::get) - .map(TeamServer::start) - .filter(v -> v) - .count(); - event.reply(context.localize("command.serveradmin.start.startall.message.started", - Replacement.create("AMOUNT", count))).queue(); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/start/StartTeam.java b/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/start/StartTeam.java deleted file mode 100644 index 60caf872..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/start/StartTeam.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.serveradmin.handler.start; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.server.ServerService; -import de.chojo.gamejam.server.TeamServer; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.localization.util.Replacement; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -import java.util.Collections; - -public class StartTeam implements SlashHandler { - private final ServerService serverService; - private final Guilds guilds; - - public StartTeam(ServerService serverService, Guilds guilds) { - this.serverService = serverService; - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var currentJam = guilds.guild(event).jams().getCurrentJam(); - if (currentJam.isEmpty()) { - event.reply(context.localize("error.noactivejam")).queue(); - return; - } - var jam = currentJam.get(); - - var optTeam = jam.teams().byName(event.getOption("team").getAsString()); - - if (optTeam.isEmpty()) { - event.reply(context.localize("error.unkownteam")).queue(); - return; - } - - var started = optTeam.map(serverService::get).map(TeamServer::start).orElse(false); - if (started) { - event.reply(context.localize("command.serveradmin.start.startteam.message.started", - Replacement.create("TEAM", optTeam.get()))).queue(); - } else { - event.reply(context.localize("command.serveradmin.start.startteam.message.failed", - Replacement.create("TEAM", optTeam.get()))).queue(); - } - } - - @Override - public void onAutoComplete(CommandAutoCompleteInteractionEvent event, EventContext context) { - var guild = guilds.guild(event); - var option = event.getFocusedOption(); - if ("team".equals(option.getName())) { - var choices = guild.jams().nextOrCurrent() - .map(jam -> jam.teams().completeTeam(option.getValue())) - .orElse(Collections.emptyList()); - event.replyChoices(choices).queue(); - } - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/stop/StopAll.java b/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/stop/StopAll.java deleted file mode 100644 index bdafd25e..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/stop/StopAll.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.serveradmin.handler.stop; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.server.ServerService; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.localization.util.Replacement; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -public class StopAll implements SlashHandler { - private final ServerService serverService; - private final Guilds guilds; - - public StopAll(ServerService serverService, Guilds guilds) { - this.serverService = serverService; - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var currentJam = guilds.guild(event).jams().getCurrentJam(); - if (currentJam.isEmpty()) { - event.reply(context.localize("error.noactivejam")).queue(); - return; - } - var jam = currentJam.get(); - - var count = jam.teams().teams().stream() - .map(serverService::get) - .map(server -> { - var running = server.isRunning(); - server.stop(); - return running; - }) - .filter(v -> v) - .count(); - event.reply(context.localize("command.serveradmin.stop.stopall.message.stopped", - Replacement.create("AMOUNT", count))).queue(); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/stop/StopTeam.java b/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/stop/StopTeam.java deleted file mode 100644 index cbf77bb8..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/stop/StopTeam.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.serveradmin.handler.stop; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.server.ServerService; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.localization.util.Replacement; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -import java.util.Collections; - -public class StopTeam implements SlashHandler { - private final ServerService serverService; - private final Guilds guilds; - - public StopTeam(ServerService serverService, Guilds guilds) { - this.serverService = serverService; - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var currentJam = guilds.guild(event).jams().getCurrentJam(); - if (currentJam.isEmpty()) { - event.reply(context.localize("error.noactivejam")).queue(); - return; - } - var jam = currentJam.get(); - - var optTeam = jam.teams().byName(event.getOption("team").getAsString()); - - if (optTeam.isEmpty()) { - event.reply(context.localize("error.unkownteam")).queue(); - return; - } - - var started = optTeam.map(serverService::get).map(server -> { - var running = server.isRunning(); - server.stop(); - return running; - }).orElse(false); - if (started) { - event.reply(context.localize("command.serveradmin.stop.stopteam.message.stopped", - Replacement.create("TEAM", optTeam.get()))).queue(); - } else { - event.reply(context.localize("command.serveradmin.stop.stopteam.message.failed", - Replacement.create("TEAM", optTeam.get()))).queue(); - } - } - - @Override - public void onAutoComplete(CommandAutoCompleteInteractionEvent event, EventContext context) { - var guild = guilds.guild(event); - var option = event.getFocusedOption(); - if ("team".equals(option.getName())) { - var choices = guild.jams().nextOrCurrent() - .map(jam -> jam.teams().completeTeam(option.getValue())) - .orElse(Collections.emptyList()); - event.replyChoices(choices).queue(); - } - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/settings/InfoCommand.java b/bot/src/main/java/de/chojo/gamejam/commands/settings/InfoCommand.java new file mode 100644 index 00000000..5338ff6a --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/commands/settings/InfoCommand.java @@ -0,0 +1,47 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.commands.settings; + +import com.google.inject.Inject; +import de.chojo.gamejam.data.dao.JamGuild; +import de.chojo.gamejam.data.dao.guild.JamSettings; +import de.chojo.gamejam.server.CommandContextProvider; +import de.chojo.jdautil.localization.util.LocalizedEmbedBuilder; +import de.chojo.jdautil.util.MentionUtil; +import de.chojo.jdautil.wrapper.EventContext; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; + +@Bundle("locale") +@Interaction +public final class InfoCommand { + private final CommandContextProvider commandContextProvider; + + @Inject + public InfoCommand(CommandContextProvider commandContextProvider) { + this.commandContextProvider = commandContextProvider; + } + + @Command(value = "settings info") + public void onCommand(CommandEvent event) { + var jamGuild = commandContextProvider.guilds().guild(event.getGuild()); + var jamSettings = jamGuild.jamSettings(); + + var settings = jamGuild.settings(); + + var embed = event.embed("settings-info"); + embed.title("command.settings.info.embed.settings"); + embed.fields().add("command.settings.info.embed.jamrole", MentionUtil.role(jamSettings.jamRole()), true); + embed.fields().add("command.settings.info.embed.teamsize", String.valueOf(jamSettings.teamSize()), true); + embed.fields().add("command.settings.info.embed.orgarole", MentionUtil.role(settings.orgaRole()), true); + + event.reply(embed.build()); + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/settings/JamRoleCommand.java b/bot/src/main/java/de/chojo/gamejam/commands/settings/JamRoleCommand.java new file mode 100644 index 00000000..3f318d25 --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/commands/settings/JamRoleCommand.java @@ -0,0 +1,39 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.commands.settings; + +import com.google.inject.Inject; +import de.chojo.gamejam.data.access.Guilds; +import de.chojo.gamejam.server.CommandContextProvider; +import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; +import de.chojo.jdautil.wrapper.EventContext; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.annotations.interactions.Param; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; +import net.dv8tion.jda.api.entities.Role; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; +import net.dv8tion.jda.api.interactions.commands.OptionType; + +@Bundle("locale") +@Interaction +public final class JamRoleCommand { + private final CommandContextProvider commandContextProvider; + + @Inject + public JamRoleCommand(CommandContextProvider commandContextProvider) { + this.commandContextProvider = commandContextProvider; + } + + @Command(value = "settings jamrole") + public void onCommand(CommandEvent event, @Param(value = "role", type = OptionType.ROLE) Role role) { + var guilds = commandContextProvider.guilds(); + guilds.guild(event.getGuild()).jamSettings().jamRole(role); + event.reply("command.settings.jamrole.message.updated"); + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/settings/Settings.java b/bot/src/main/java/de/chojo/gamejam/commands/settings/Settings.java deleted file mode 100644 index 75daa8de..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/settings/Settings.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.settings; - -import de.chojo.gamejam.commands.settings.handler.Info; -import de.chojo.gamejam.commands.settings.handler.JamRole; -import de.chojo.gamejam.commands.settings.handler.Locale; -import de.chojo.gamejam.commands.settings.handler.TeamSize; -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.jdautil.interactions.slash.Argument; -import de.chojo.jdautil.interactions.slash.Slash; -import de.chojo.jdautil.interactions.slash.SubCommand; -import de.chojo.jdautil.interactions.slash.provider.SlashCommand; - -public class Settings extends SlashCommand { - public Settings(Guilds guilds) { - super(Slash.of("settings", "command.settings.description") - .adminCommand() - .subCommand(SubCommand.of("jamrole", "command.settings.jamrole.description") - .handler(new JamRole(guilds)) - .argument(Argument.role("role", "command.settings.jamrole.options.role.description").asRequired())) - .subCommand(SubCommand.of("teamsize", "command.settings.teamsize.description") - .handler(new TeamSize(guilds)) - .argument(Argument.integer("size", "command.settings.teamsize.options.size.description").asRequired())) - .subCommand(SubCommand.of("orgarole", "command.settings.orgarole.description") - // TODO: Command gone? - .handler(null) - .argument(Argument.role("role", "command.settings.orgarole.options.role.description").asRequired())) - .subCommand(SubCommand.of("locale", "command.settings.locale.description") - .handler(new Locale(guilds)) - .argument(Argument.text("locale", "command.settings.locale.options.locale.description").asRequired())) - .subCommand(SubCommand.of("info", "command.settings.info.description") - .handler(new Info(guilds))) - ); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/settings/TeamSizeCommand.java b/bot/src/main/java/de/chojo/gamejam/commands/settings/TeamSizeCommand.java new file mode 100644 index 00000000..67b33771 --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/commands/settings/TeamSizeCommand.java @@ -0,0 +1,37 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.commands.settings; + +import com.google.inject.Inject; +import de.chojo.gamejam.data.access.Guilds; +import de.chojo.gamejam.server.CommandContextProvider; +import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; +import de.chojo.jdautil.wrapper.EventContext; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.annotations.interactions.Param; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; + +@Bundle("locale") +@Interaction +public final class TeamSizeCommand { + private final CommandContextProvider commandContextProvider; + + @Inject + public TeamSizeCommand(CommandContextProvider commandContextProvider) { + this.commandContextProvider = commandContextProvider; + } + + @Command(value = "settings teamsize") + public void onCommand(CommandEvent event, @Param("size") int size) { + var settings = commandContextProvider.guilds().guild(event).jamSettings(); + settings.teamSize(size); + event.with().ephemeral(true).reply("command.settings.teamsize.message.updated"); + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/settings/handler/Info.java b/bot/src/main/java/de/chojo/gamejam/commands/settings/handler/Info.java deleted file mode 100644 index de9e23f3..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/settings/handler/Info.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.settings.handler; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.data.dao.JamGuild; -import de.chojo.gamejam.data.dao.guild.JamSettings; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.localization.util.LocalizedEmbedBuilder; -import de.chojo.jdautil.util.MentionUtil; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -public final class Info implements SlashHandler { - private final Guilds guilds; - - public Info(Guilds guilds) { - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - JamGuild guild = guilds.guild(event.getGuild()); - JamSettings jamSettings = guild.jamSettings(); - var settings = guild.settings(); - var embed = new LocalizedEmbedBuilder(context.guildLocalizer()) - .setTitle("command.settings.info.embed.settings") - .addField("command.settings.info.embed.jamrole", MentionUtil.role(jamSettings.jamRole()), true) - .addField("command.settings.info.embed.teamsize", String.valueOf(jamSettings.teamSize()), true) - .addField("command.settings.info.embed.orgarole", MentionUtil.role(settings.orgaRole()), true) - .build(); - event.replyEmbeds(embed).setEphemeral(true).queue(); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/settings/handler/JamRole.java b/bot/src/main/java/de/chojo/gamejam/commands/settings/handler/JamRole.java deleted file mode 100644 index 3945d971..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/settings/handler/JamRole.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.settings.handler; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -public final class JamRole implements SlashHandler { - private final Guilds guilds; - - public JamRole(Guilds guilds) { - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - guilds.guild(event.getGuild()).jamSettings().jamRole(event.getOption("role").getAsRole()); - event.reply(context.localize("command.settings.jamrole.message.updated")).setEphemeral(true).queue(); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/settings/handler/Locale.java b/bot/src/main/java/de/chojo/gamejam/commands/settings/handler/Locale.java deleted file mode 100644 index 0dd3c670..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/settings/handler/Locale.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.settings.handler; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.data.dao.guild.Settings; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -public final class Locale implements SlashHandler { - private final Guilds guilds; - - public Locale(Guilds guilds) { - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var settings = guilds.guild(event).settings(); - var locale = event.getOption("locale").getAsString(); - context.guildLocalizer().localizer().getLanguage(locale) - .ifPresentOrElse(language -> { - settings.locale(language.getLocale()); - event.reply(context.localize("command.settings.locale.message.updated")).setEphemeral(true).queue(); - context.interactionHub().refreshGuildCommands(event.getGuild()); - }, () -> event.reply(context.localize("command.settings.locale.message.invalid")).setEphemeral(true).queue()); - - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/settings/handler/TeamSize.java b/bot/src/main/java/de/chojo/gamejam/commands/settings/handler/TeamSize.java deleted file mode 100644 index d58a4067..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/settings/handler/TeamSize.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.settings.handler; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -public final class TeamSize implements SlashHandler { - private final Guilds guilds; - - public TeamSize(Guilds guilds) { - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var settings = guilds.guild(event).jamSettings(); - settings.teamSize(event.getOption("size").getAsInt()); - event.reply(context.localize("command.settings.teamsize.message.updated")).setEphemeral(true).queue(); - - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/team/Team.java b/bot/src/main/java/de/chojo/gamejam/commands/team/Team.java deleted file mode 100644 index b2ab25a0..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/team/Team.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.team; - -import de.chojo.gamejam.commands.team.handler.Api; -import de.chojo.gamejam.commands.team.handler.Create; -import de.chojo.gamejam.commands.team.handler.Disband; -import de.chojo.gamejam.commands.team.handler.Edit; -import de.chojo.gamejam.commands.team.handler.Invite; -import de.chojo.gamejam.commands.team.handler.Leave; -import de.chojo.gamejam.commands.team.handler.List; -import de.chojo.gamejam.commands.team.handler.Profile; -import de.chojo.gamejam.commands.team.handler.Promote; -import de.chojo.gamejam.commands.team.handler.Rename; -import de.chojo.gamejam.configuration.Configuration; -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.jdautil.interactions.slash.Argument; -import de.chojo.jdautil.interactions.slash.Slash; -import de.chojo.jdautil.interactions.slash.SubCommand; -import de.chojo.jdautil.interactions.slash.provider.SlashCommand; - -public class Team extends SlashCommand { - public Team(Guilds guilds, Configuration configuration) { - super(Slash.of("team", "command.team.description") - .subCommand(SubCommand.of("create", "command.team.create.description") - .handler(new Create(guilds)) - .argument(Argument.text("name", "command.team.create.options.name.description") - .asRequired())) - .subCommand(SubCommand.of("edit", "Edit Eeam information") - .handler(new Edit(guilds))) - .subCommand(SubCommand.of("invite", "command.team.invite.description") - .handler(new Invite(guilds)) - .argument(Argument.user("user", "command.team.invite.options.user.description") - .asRequired())) - .subCommand(SubCommand.of("leave", "command.team.leave.description") - .handler(new Leave(guilds))) - .subCommand(SubCommand.of("disband", "command.team.disband.description") - .handler(new Disband(guilds)) - .argument(Argument.bool("confirm", "command.team.disband.options.confirm.description") - .asRequired())) - .subCommand(SubCommand.of("promote", "command.team.promote.description") - .handler(new Promote(guilds)) - .argument(Argument.user("user", "command.team.promote.options.user.description") - .asRequired())) - .subCommand(SubCommand.of("profile", "command.team.profile.description") - .handler(new Profile(guilds)) - .argument(Argument.user("user", "command.team.profile.options.user.description")) - .argument(Argument.text("team", "command.team.profile.options.team.description") - .withAutoComplete())) - .subCommand(SubCommand.of("list", "command.team.list.description") - .handler(new List(guilds))) - .subCommand(SubCommand.of("rename", "command.team.rename.description") - .handler(new Rename(guilds)) - .argument(Argument.text("name", "command.team.rename.options.name.description") - .asRequired())) - .subCommand(SubCommand.of("api", "command.team.api.description") - .handler(new Api(configuration, guilds))) - ); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/team/handler/Api.java b/bot/src/main/java/de/chojo/gamejam/commands/team/TeamApiCommand.java similarity index 78% rename from bot/src/main/java/de/chojo/gamejam/commands/team/handler/Api.java rename to bot/src/main/java/de/chojo/gamejam/commands/team/TeamApiCommand.java index e84c7d63..ae1f3cfd 100644 --- a/bot/src/main/java/de/chojo/gamejam/commands/team/handler/Api.java +++ b/bot/src/main/java/de/chojo/gamejam/commands/team/TeamApiCommand.java @@ -4,21 +4,24 @@ * Copyright (C) 2022 DevCord Team and Contributor */ -package de.chojo.gamejam.commands.team.handler; +package de.chojo.gamejam.commands.team; import de.chojo.gamejam.configuration.Configuration; import de.chojo.gamejam.data.access.Guilds; import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; import de.chojo.jdautil.localization.util.LocalizedEmbedBuilder; import de.chojo.jdautil.wrapper.EventContext; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; import net.dv8tion.jda.api.entities.MessageEmbed; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -public class Api implements SlashHandler { +public class TeamApiCommand { + /* private final Configuration configuration; private final Guilds guilds; - public Api(Configuration configuration, Guilds guilds) { + public TeamApiCommand(Configuration configuration, Guilds guilds) { this.configuration = configuration; this.guilds = guilds; } @@ -27,12 +30,12 @@ public Api(Configuration configuration, Guilds guilds) { public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { var optJam = guilds.guild(event).jams().nextOrCurrent(); if (optJam.isEmpty()) { - event.reply(context.localize("error.nojamactive")).setEphemeral(true).queue(); + event.with().ephemeral(true).reply("error.nojamactive"); return; } var optTeam = optJam.get().teams().byMember(event.getMember()); if (optTeam.isEmpty()) { - event.reply(context.localize("error.noteam")).setEphemeral(true).queue(); + event.with().ephemeral(true).reply("error.noteam"); return; } @@ -44,4 +47,6 @@ public void onSlashCommand(SlashCommandInteractionEvent event, EventContext cont event.replyEmbeds(build).setEphemeral(true).queue(); } + + */ } diff --git a/bot/src/main/java/de/chojo/gamejam/commands/team/handler/Create.java b/bot/src/main/java/de/chojo/gamejam/commands/team/TeamCreateCommand.java similarity index 66% rename from bot/src/main/java/de/chojo/gamejam/commands/team/handler/Create.java rename to bot/src/main/java/de/chojo/gamejam/commands/team/TeamCreateCommand.java index 77b1b4ac..a110e94f 100644 --- a/bot/src/main/java/de/chojo/gamejam/commands/team/handler/Create.java +++ b/bot/src/main/java/de/chojo/gamejam/commands/team/TeamCreateCommand.java @@ -4,64 +4,69 @@ * Copyright (C) 2022 DevCord Team and Contributor */ -package de.chojo.gamejam.commands.team.handler; +package de.chojo.gamejam.commands.team; -import de.chojo.gamejam.data.access.Guilds; +import com.google.inject.Inject; +import de.chojo.gamejam.server.CommandContextProvider; import de.chojo.gamejam.util.Token; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.wrapper.EventContext; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.annotations.interactions.Param; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; import net.dv8tion.jda.api.Permission; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import java.util.Collections; import java.util.EnumSet; -public final class Create implements SlashHandler { - private final Guilds guilds; +@Bundle("locale") +@Interaction +public final class TeamCreateCommand { + private final CommandContextProvider commandContextProvider; - public Create(Guilds guilds) { - this.guilds = guilds; + @Inject + public TeamCreateCommand(CommandContextProvider commandContextProvider) { + this.commandContextProvider = commandContextProvider; } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var jamGuild = guilds.guild(event.getGuild()); + + @Command(value = "team create") + public void onCommand(CommandEvent event, @Param("name") String teamName) { + var jamGuild = commandContextProvider.guilds().guild(event.getGuild()); var optJam = jamGuild.jams().nextOrCurrent(); if (optJam.isEmpty()) { - event.reply(context.localize("error.nojamactive")).setEphemeral(true).queue(); + event.with().ephemeral(true).reply("error.nojamactive"); return; } var jam = optJam.get(); if (jam.state().isVoting()) { - event.reply(context.localize("error.votingactive")).setEphemeral(true).queue(); + event.with().ephemeral(true).reply("error.votingactive"); return; } if (!jam.registrations().contains(event.getMember().getIdLong())) { - event.reply(context.localize("command.team.create.message.unregistered")).setEphemeral(true).queue(); + event.with().ephemeral(true).reply("command.team.create.message.unregistered"); return; } var jamUser = jam.user(event.getMember()); var userTeam = jamUser.team(); if (userTeam.isPresent()) { - event.reply(context.localize("command.team.create.message.alreadymember")).setEphemeral(true).queue(); + event.with().ephemeral(true).reply("command.team.create.message.alreadymember"); return; } - var teamName = event.getOption("name").getAsString(); // TODO: Enforce constrains of length and allowed chars - var optTeam = jam.teams().byName(event.getOption("name").getAsString()); + var optTeam = jam.teams().byName(teamName); if (optTeam.isPresent()) { - event.reply(context.localize("command.team.create.message.nametaken")).setEphemeral(true).queue(); + event.with().ephemeral(true).reply("command.team.create.message.nametaken"); return; } - event.deferReply().setEphemeral(true).queue(); + event.deferReply(true); var categoryList = event.getGuild().getCategoriesByName("Team", true); @@ -104,6 +109,6 @@ public void onSlashCommand(SlashCommandInteractionEvent event, EventContext cont meta.token(Token.generate(40)); jamUser.join(team); - event.getHook().editOriginal(context.localize("command.team.create.message.created")).queue(); + event.with().ephemeral(true).reply("command.team.create.message.created"); } } diff --git a/bot/src/main/java/de/chojo/gamejam/commands/team/TeamDisbandCommand.java b/bot/src/main/java/de/chojo/gamejam/commands/team/TeamDisbandCommand.java new file mode 100644 index 00000000..26d1586d --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/commands/team/TeamDisbandCommand.java @@ -0,0 +1,72 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.commands.team; + +import com.google.inject.Inject; +import de.chojo.gamejam.data.dao.guild.jams.Jam; +import de.chojo.gamejam.server.CommandContextProvider; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.annotations.interactions.Param; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; + +import java.util.Optional; + +@Bundle("locale") +@Interaction +public final class TeamDisbandCommand { + private final CommandContextProvider commandContextProvider; + + @Inject + public TeamDisbandCommand(CommandContextProvider commandContextProvider) { + this.commandContextProvider = commandContextProvider; + } + + @Command(value = "team disband") + public void onCommand(CommandEvent event, @Param("confirm") boolean confirm) { + Optional optJam = commandContextProvider.guilds().guild(event).jams().nextOrCurrent(); + if (optJam.isEmpty()) { + event.with().ephemeral(true).reply("error.nojamactive"); + return; + } + var jam = optJam.get(); + + if (jam.state().isVoting()) { + event.with().ephemeral(true).reply("error.votingactive"); + return; + } + + if (!confirm) { + event.with().ephemeral(true).reply("error.noconfirm"); + return; + } + + var jamTeam = jam.teams().byMember(event.getMember()); + if (jamTeam.isEmpty()) { + event.with().ephemeral(true).reply("error.noteam"); + return; + } + + var team = jamTeam.get(); + + + var members = team.member(); + for (var teamMember : members) { + teamMember.member() + .getUser() + .openPrivateChannel() + //TODO: fix localization + .flatMap(channel -> channel.sendMessage("command.team.disband.message.disbanded")) + .queue(); + } + + if (team.disband()) { + event.with().ephemeral(true).reply("command.team.disband.message.disbanded"); + } + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/team/TeamEditCommand.java b/bot/src/main/java/de/chojo/gamejam/commands/team/TeamEditCommand.java new file mode 100644 index 00000000..3a759515 --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/commands/team/TeamEditCommand.java @@ -0,0 +1,77 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.commands.team; + +import com.google.inject.Inject; +import de.chojo.gamejam.server.CommandContextProvider; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.annotations.interactions.Modal; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; +import io.github.kaktushose.jdac.dispatching.events.interactions.ModalEvent; +import net.dv8tion.jda.api.components.label.Label; +import net.dv8tion.jda.api.components.textinput.TextInput; +import net.dv8tion.jda.api.components.textinput.TextInputStyle; + +import java.util.List; + +@Bundle("locale") +@Interaction +public class TeamEditCommand { + private final CommandContextProvider commandContextProvider; + + @Inject + public TeamEditCommand(CommandContextProvider commandContextProvider) { + this.commandContextProvider = commandContextProvider; + } + + @Command(value = "team edit") + public void onCommand(CommandEvent event) { + var optJam = commandContextProvider.guilds().guild(event).jams().nextOrCurrent(); + if (optJam.isEmpty()) { + event.with().ephemeral(true).reply("error.nojamactive"); + return; + } + var optTeam = optJam.get().teams().byMember(event.getMember()); + if (optTeam.isEmpty()) { + event.with().ephemeral(true).reply("error.noteam"); + return; + } + + var meta = optTeam.get().meta(); + + event.replyModal("onModal", List.of( + Label.of("Project Description", TextInput.create("descr", TextInputStyle.PARAGRAPH) + .setValue(meta.projectDescription().isBlank() ? "None" : meta.projectDescription()) + .setMaxLength(100) + .build()), + Label.of("Project url", TextInput.create("url", TextInputStyle.SHORT) + .setValue(meta.projectUrl().isBlank() ? "none" : meta.projectUrl()) + .setMaxLength(200) + .build()) + ) + ); + } + + @Modal("Edit Profile") + public void onModal(ModalEvent event) { + var optJam = commandContextProvider.guilds().guild(event).jams().nextOrCurrent(); + if (optJam.isEmpty()) { + return; + } + var optTeam = optJam.get().teams().byMember(event.getMember()); + if (optTeam.isEmpty()) { + return; + } + + var meta = optTeam.get().meta(); + meta.projectDescription(event.value("descr").getAsString()); + meta.projectUrl(event.value("url").getAsString()); + event.reply("Updated"); + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/team/TeamInviteCommand.java b/bot/src/main/java/de/chojo/gamejam/commands/team/TeamInviteCommand.java new file mode 100644 index 00000000..277a11bc --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/commands/team/TeamInviteCommand.java @@ -0,0 +1,98 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.commands.team; + +import com.google.inject.Inject; +import de.chojo.gamejam.data.dao.JamGuild; +import de.chojo.gamejam.server.CommandContextProvider; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.annotations.interactions.Param; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; +import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.components.buttons.Button; +import net.dv8tion.jda.api.entities.User; +import net.dv8tion.jda.api.components.actionrow.ActionRow; +import net.dv8tion.jda.api.interactions.commands.OptionType; + +@Bundle("locale") +@Interaction +public final class TeamInviteCommand { + private final CommandContextProvider commandContextProvider; + + @Inject + public TeamInviteCommand(CommandContextProvider commandContextProvider) { + this.commandContextProvider = commandContextProvider; + } + + @Command(value = "team invite") + public void onCommand(CommandEvent event, @Param(value = "user", type = OptionType.USER) User user) { + JamGuild guild = commandContextProvider.guilds().guild(event); + var optJam = guild.jams().nextOrCurrent(); + if (optJam.isEmpty()) { + event.with().ephemeral(true).reply("error.nojamactive"); + return; + } + var jam = optJam.get(); + + if (jam.state().isVoting()) { + event.with().ephemeral(true).reply("error.votingactive"); + return; + } + + var optTeam = jam.teams().byMember(event.getMember()); + if (optTeam.isEmpty()) { + event.with().ephemeral(true).reply("error.noteam"); + return; + } + + var team = optTeam.get(); + + if (!team.isLeader(event.getUser())) { + event.with().ephemeral(true).reply("command.team.invite.message.noleader"); + return; + } + + var members = team.member(); + var settings = guild.jamSettings(); + + if (members.size() >= settings.teamSize()) { + event.with().ephemeral(true).reply("error.maxteamsize"); + return; + } + + if (!jam.registrations().contains(user.getIdLong())) { + event.with().ephemeral(true).reply("command.team.invite.message.notRegistered"); + return; + } + + var currTeam = jam.teams().byMember(user); + + if (currTeam.isPresent()) { + event.reply("command.team.invite.message.partofteam"); + return; + } + + var guildId = event.getGuild().getIdLong(); + var teamId = team.id(); + var buttonId = "invite-accept:" + guildId + ":" + teamId + ":" + user.getIdLong(); + + var embed = new EmbedBuilder() + .setTitle("You have been invited to join a team on " + event.getGuild().getName()) + .setDescription(event.getUser().getAsMention() + " has invited you to join team **" + team.meta().name() + "**") + .build(); + + user.openPrivateChannel().queue(channel -> + channel.sendMessageEmbeds(embed) + .setComponents(ActionRow.of(Button.success(buttonId, "Accept"))) + .queue() + ); + + event.with().ephemeral(true).reply("command.team.invite.message.send"); + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/team/TeamLeaveCommand.java b/bot/src/main/java/de/chojo/gamejam/commands/team/TeamLeaveCommand.java new file mode 100644 index 00000000..b7d0c673 --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/commands/team/TeamLeaveCommand.java @@ -0,0 +1,55 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.commands.team; + +import com.google.inject.Inject; +import de.chojo.gamejam.server.CommandContextProvider; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; + +@Bundle("locale") +@Interaction +public final class TeamLeaveCommand { + private final CommandContextProvider commandContextProvider; + + @Inject + public TeamLeaveCommand(CommandContextProvider commandContextProvider) { + this.commandContextProvider = commandContextProvider; + } + + @Command(value = "team leave") + public void onCommand(CommandEvent event) { + var jamGuild = commandContextProvider.guilds().guild(event); + var optJam = jamGuild.jams().nextOrCurrent(); + if (optJam.isEmpty()) { + event.with().ephemeral(true).reply("error.nojamactive"); + return; + } + var jam = optJam.get(); + + if (jam.state().isVoting()) { + event.with().ephemeral(true).reply("error.votingactive"); + return; + } + + jam.teams().byMember(event.getMember()).ifPresentOrElse(team -> { + if (!team.isLeader(event.getUser())) { + event.with().ephemeral(true).reply("command.team.leave.message.leaderleave"); + return; + } + team.member(event.getMember()).ifPresent(member -> { + member.leave(); + team.meta().textChannel().ifPresent(channel -> { + //TODO: reimplement message + }); + event.with().ephemeral(true).reply("command.team.leave.left"); + }); + }, () -> event.with().ephemeral(true).reply("error.noteam")); + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/team/TeamListCommand.java b/bot/src/main/java/de/chojo/gamejam/commands/team/TeamListCommand.java new file mode 100644 index 00000000..97c75f79 --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/commands/team/TeamListCommand.java @@ -0,0 +1,80 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.commands.team; + +import com.google.inject.Inject; +import de.chojo.gamejam.data.dao.JamGuild; +import de.chojo.gamejam.server.CommandContextProvider; +import de.chojo.jdautil.pagination.bag.PrivateListPageBag; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Button; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; +import io.github.kaktushose.jdac.dispatching.events.interactions.ComponentEvent; +import io.github.kaktushose.jdac.dispatching.reply.Component; +import io.github.kaktushose.jdac.embeds.Embed; +import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.components.buttons.ButtonStyle; +import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.utils.messages.MessageEditData; + +import java.util.List; + +@Bundle("locale") +@Interaction +public class TeamListCommand { + private final CommandContextProvider commandContextProvider; + + private List pages; + private int currentPage; + + @Inject + public TeamListCommand(CommandContextProvider commandContextProvider) { + this.commandContextProvider = commandContextProvider; + } + + @Command(value = "team list") + public void onCommand(CommandEvent event) { + JamGuild guild = commandContextProvider.guilds().guild(event); + var optJam = guild.jams().nextOrCurrent(); + if (optJam.isEmpty()) { + event.with().ephemeral(true).reply("error.nojamactive"); + return; + } + var jam = optJam.get(); + + pages = jam.teams().teams().stream().map(team -> team.profileEmbed(event)).toList(); + currentPage = 0; + event.with().components(Component.disabled("onPrev"), Component.enabled("onNext")) + .embeds(pages.getFirst()).reply(); + } + + //TODO: localization + @Button(value = "◀ Zurück", style = ButtonStyle.SECONDARY) + public void onPrev(ComponentEvent event) { + currentPage--; + event.with() + .components( + currentPage == 0 ? Component.disabled("onPrev") : Component.enabled("onPrev"), + Component.enabled("onNext") + ) + .embeds(pages.get(currentPage)).reply(); + } + + //TODO: localization + @Button(value = "Weiter ▶", style = ButtonStyle.SECONDARY) + public void onNext(ComponentEvent event) { + currentPage++; + event.with() + .components( + Component.enabled("onPrev"), + currentPage == pages.size() - 1 ? Component.disabled("onNext") : Component.enabled("onNext") + ) + .embeds(pages.get(currentPage)).reply(); + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/team/TeamProfileCommand.java b/bot/src/main/java/de/chojo/gamejam/commands/team/TeamProfileCommand.java new file mode 100644 index 00000000..5c236487 --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/commands/team/TeamProfileCommand.java @@ -0,0 +1,81 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.commands.team; + +import com.google.inject.Inject; +import de.chojo.gamejam.data.dao.guild.jams.jam.teams.Team; +import de.chojo.gamejam.server.CommandContextProvider; +import de.chojo.jdautil.wrapper.EventContext; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.AutoComplete; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.annotations.interactions.Param; +import io.github.kaktushose.jdac.dispatching.events.interactions.AutoCompleteEvent; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; +import net.dv8tion.jda.api.entities.User; +import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; +import net.dv8tion.jda.api.interactions.commands.OptionType; + +import java.util.Collections; + +@Bundle("locale") +@Interaction +public final class TeamProfileCommand { + private final CommandContextProvider commandContextProvider; + + @Inject + public TeamProfileCommand(CommandContextProvider commandContextProvider) { + this.commandContextProvider = commandContextProvider; + } + + @Command(value = "team profile") + public void onCommand( + CommandEvent event, + @Param(name = "user", optional = true, type = OptionType.USER) User user, + @Param(value = "team", optional = true) String teamName + ) { + var optJam = commandContextProvider.guilds().guild(event).jams().nextOrCurrent(); + if (optJam.isEmpty()) { + event.with().ephemeral(true).reply("error.nojamactive"); + return; + } + var jam = optJam.get(); + var teams = jam.teams(); + + if (user != null) { + teams.byMember(user).ifPresentOrElse(team -> + sendProfile(event, team), + () -> event.with().ephemeral(true).reply("command.team.profile.message.nouserteam")); + return; + } + if (teamName != null) { + teams.byName(teamName).ifPresentOrElse(team -> + sendProfile(event, team), + () -> event.with().ephemeral(true).reply("error.unkownteam")); + return; + } + + teams.byMember(event.getMember()).ifPresentOrElse(team -> + sendProfile(event, team), + () -> event.with().ephemeral(true).reply("error.noteam")); + } + + private void sendProfile(CommandEvent event, Team team) { + event.with().ephemeral(true).embeds(team.profileEmbed(event)).reply(); + } + + @AutoComplete(value = "team profile", options = "team") + public void onAutoComplete(AutoCompleteEvent event) { + var guild = commandContextProvider.guilds().guild(event); + var choices = guild.jams().nextOrCurrent() + .map(jam -> jam.teams().completeTeam(event.getValue())) + .orElse(Collections.emptyList()); + event.replyChoices(choices); + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/team/TeamPromoteCommand.java b/bot/src/main/java/de/chojo/gamejam/commands/team/TeamPromoteCommand.java new file mode 100644 index 00000000..9df417e9 --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/commands/team/TeamPromoteCommand.java @@ -0,0 +1,57 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.commands.team; + +import com.google.inject.Inject; +import de.chojo.gamejam.data.access.Guilds; +import de.chojo.gamejam.data.dao.JamGuild; +import de.chojo.gamejam.server.CommandContextProvider; +import de.chojo.jdautil.wrapper.EventContext; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.annotations.interactions.Param; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; +import net.dv8tion.jda.api.entities.User; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; +import net.dv8tion.jda.api.interactions.commands.OptionType; + +@Bundle("locale") +@Interaction +public class TeamPromoteCommand { + private final CommandContextProvider commandContextProvider; + + @Inject + public TeamPromoteCommand(CommandContextProvider commandContextProvider) { + this.commandContextProvider = commandContextProvider; + } + + @Command(value = "team promote") + public void onCommand(CommandEvent event, @Param(value = "user", type = OptionType.USER) User user) { + JamGuild guild = commandContextProvider.guilds().guild(event); + var optJam = guild.jams().nextOrCurrent(); + if (optJam.isEmpty()) { + event.with().ephemeral(true).reply("error.nojamactive"); + return; + } + var jam = optJam.get(); + + var member = guild.guild().getMember(user); + + jam.teams().byMember(user).ifPresentOrElse( + targetTeam -> { + if (!targetTeam.isLeader(event.getUser())) { + event.with().ephemeral(true).reply("error.noleader"); + return; + } + + targetTeam.meta().leader(member); + event.with().ephemeral(true).reply("command.team.promote.message.done"); + }, + () -> event.with().ephemeral(true).reply("error.noteam")); + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/team/TeamRenameCommand.java b/bot/src/main/java/de/chojo/gamejam/commands/team/TeamRenameCommand.java new file mode 100644 index 00000000..103ade90 --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/commands/team/TeamRenameCommand.java @@ -0,0 +1,63 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.commands.team; + +import com.google.inject.Inject; +import de.chojo.gamejam.data.access.Guilds; +import de.chojo.gamejam.data.dao.JamGuild; +import de.chojo.gamejam.server.CommandContextProvider; +import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; +import de.chojo.jdautil.wrapper.EventContext; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.annotations.interactions.Param; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; + +@Bundle("locale") +@Interaction +public class TeamRenameCommand { + private final CommandContextProvider commandContextProvider; + + @Inject + public TeamRenameCommand(CommandContextProvider commandContextProvider) { + this.commandContextProvider = commandContextProvider; + } + + @Command(value = "team rename") + public void onCommand(CommandEvent event, @Param("name") String teamName) { + JamGuild guild = commandContextProvider.guilds().guild(event); + var optJam = guild.jams().nextOrCurrent(); + if (optJam.isEmpty()) { + event.with().ephemeral(true).reply("error.nojamactive"); + return; + } + var jam = optJam.get(); + + jam.teams().byName(teamName).ifPresentOrElse( + team -> event.with().ephemeral(true).reply("command.team.create.message.nametaken"), + () -> { + var optCurrTeam = jam.teams().byMember(event.getUser()); + + if (optCurrTeam.isEmpty()) { + event.with().ephemeral(true).reply("error.noteam"); + return; + } + + var team = optCurrTeam.get(); + + if (!team.isLeader(event.getUser())) { + event.with().ephemeral(true).reply("error.noleader"); + return; + } + + team.meta().rename(teamName); + event.with().ephemeral(true).reply("command.team.rename.message.done"); + }); + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/team/handler/Disband.java b/bot/src/main/java/de/chojo/gamejam/commands/team/handler/Disband.java deleted file mode 100644 index fac2a9a3..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/team/handler/Disband.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.team.handler; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.data.dao.guild.jams.Jam; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -import java.util.Optional; - -public final class Disband implements SlashHandler { - private final Guilds guilds; - - public Disband(Guilds guilds) { - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - Optional optJam = guilds.guild(event).jams().nextOrCurrent(); - if (optJam.isEmpty()) { - event.reply(context.localize("error.nojamactive")).setEphemeral(true).queue(); - return; - } - var jam = optJam.get(); - - if (jam.state().isVoting()) { - event.reply(context.localize("error.votingactive")).setEphemeral(true).queue(); - return; - } - - if (!event.getOption("confirm").getAsBoolean()) { - event.reply(context.localize("error.noconfirm")).setEphemeral(true).queue(); - return; - } - - var jamTeam = jam.teams().byMember(event.getMember()); - if (jamTeam.isEmpty()) { - event.reply(context.localize("error.noteam")).setEphemeral(true).queue(); - return; - } - - var team = jamTeam.get(); - - - var members = team.member(); - for (var teamMember : members) { - teamMember.member() - .getUser() - .openPrivateChannel() - .flatMap(channel -> channel.sendMessage(context.localize("command.team.disband.message.disbanded"))) - .queue(); - } - - if (team.disband()) { - event.reply(context.localize("command.team.disband.message.disbanded")).setEphemeral(true).queue(); - } - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/team/handler/Edit.java b/bot/src/main/java/de/chojo/gamejam/commands/team/handler/Edit.java deleted file mode 100644 index 0d2a2683..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/team/handler/Edit.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.team.handler; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.modals.handler.ModalHandler; -import de.chojo.jdautil.modals.handler.TextInputHandler; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.components.textinput.TextInputStyle; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -public class Edit implements SlashHandler { - private final Guilds guilds; - - public Edit(Guilds guilds) { - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var optJam = guilds.guild(event).jams().nextOrCurrent(); - if (optJam.isEmpty()) { - event.reply(context.localize("error.nojamactive")).setEphemeral(true).queue(); - return; - } - var optTeam = optJam.get().teams().byMember(event.getMember()); - if (optTeam.isEmpty()) { - event.reply(context.localize("error.noteam")).setEphemeral(true).queue(); - return; - } - - var meta = optTeam.get().meta(); - - context.registerModal(ModalHandler.builder("Edit Profile") - .addInput(TextInputHandler.builder("descr", "Project Description", TextInputStyle.PARAGRAPH) - .withValue(meta.projectDescription().isBlank() ? "None" : meta.projectDescription()) - .withMaxLength(100) - .withHandler(mapping -> meta.projectDescription(mapping.getAsString()))) - .addInput(TextInputHandler.builder("url", "Project url", TextInputStyle.SHORT) - .withMaxLength(200) - .withValue(meta.projectUrl().isBlank() ? "none": meta.projectUrl()) - .withHandler(mapping -> meta.projectUrl(mapping.getAsString()))) - .withHandler(modalEvent -> { - modalEvent.reply("Updated").queue(); - }) - .build()); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/team/handler/Invite.java b/bot/src/main/java/de/chojo/gamejam/commands/team/handler/Invite.java deleted file mode 100644 index a4404ec4..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/team/handler/Invite.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.team.handler; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.data.dao.JamGuild; -import de.chojo.gamejam.data.dao.guild.jams.jam.teams.Team; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.localization.LocalizationContext; -import de.chojo.jdautil.localization.util.LocalizedEmbedBuilder; -import de.chojo.jdautil.localization.util.Replacement; -import de.chojo.jdautil.menus.EntryContext; -import de.chojo.jdautil.menus.MenuAction; -import de.chojo.jdautil.menus.entries.ButtonEntry; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.components.buttons.Button; -import net.dv8tion.jda.api.components.buttons.ButtonStyle; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; - -public final class Invite implements SlashHandler { - private final Guilds guilds; - - public Invite(Guilds guilds) { - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - JamGuild guild = guilds.guild(event); - var optJam = guild.jams().nextOrCurrent(); - if (optJam.isEmpty()) { - event.reply(context.localize("error.nojamactive")).setEphemeral(true).queue(); - return; - } - var jam = optJam.get(); - - if (jam.state().isVoting()) { - event.reply(context.localize("error.votingactive")).setEphemeral(true).queue(); - return; - } - - var optTeam = jam.teams().byMember(event.getMember()); - if (optTeam.isEmpty()) { - event.reply(context.localize("error.noteam")).setEphemeral(true).queue(); - return; - } - - var team = optTeam.get(); - - if (!team.isLeader(event.getUser())) { - event.reply(context.localize("command.team.invite.message.noleader")).setEphemeral(true).queue(); - return; - } - - var member = team.member(); - var settings = guild.jamSettings(); - - if (member.size() >= settings.teamSize()) { - event.reply(context.localize("error.maxteamsize")).setEphemeral(true).queue(); - return; - } - - var user = event.getOption("user").getAsUser(); - - if (!jam.registrations().contains(user.getIdLong())) { - event.reply(context.localize("command.team.invite.message.notRegistered")).setEphemeral(true).queue(); - return; - } - - var currTeam = jam.teams().byMember(user); - - if (currTeam.isPresent()) { - event.reply(context.localize("command.team.invite.message.partofteam")).queue(); - return; - } - - user.openPrivateChannel().queue(channel -> { - var embed = new LocalizedEmbedBuilder(context.guildLocalizer()) - .setTitle("command.team.invite.message.invited", Replacement.create("GUILD", event.getGuild() - .getName())) - .setDescription("command.team.invite.message.invitation", - Replacement.createMention(event.getUser()), Replacement.create("TEAM", team.meta().name())) - .build(); - event.reply(context.localize("command.team.invite.message.send")).setEphemeral(true).queue(); - context.registerMenu(MenuAction.forChannel(embed, channel) - .addComponent(ButtonEntry.of(Button.of(ButtonStyle.SUCCESS, "accept", "command.team.invite.message.accept"), - button -> accept(button, event.getGuild().getIdLong(), - team, user.getIdLong(), context.guildLocalizer()))) - .build()); - }); - } - - private void accept(EntryContext button, long guildId, Team team, long userId, LocalizationContext localizer) { - var members = team.member(); - var interaction = button.event(); - interaction.deferReply().queue(); - var manager = interaction.getJDA().getShardManager(); - var guild = manager.getGuildById(guildId); - var user = manager.retrieveUserById(userId).complete(); - var member = guild.retrieveMember(user).complete(); - var jamGuild = guilds.guild(guild); - var settings = jamGuild.jamSettings(); - - if (members.size() >= settings.teamSize()) { - interaction.getHook().editOriginal(localizer.localize("error.maxteamsize")).queue(); - return; - } - var jam = jamGuild.jams().nextOrCurrent(); - if (jam.isEmpty()) { - interaction.getHook().editOriginal(localizer.localize("command.team.invite.gameJamOver")).queue(); - return; - } - - var currTeam = jam.get().teams().byMember(user); - - if (currTeam.isPresent()) { - interaction.getHook().editOriginal(localizer.localize("command.team.invite.alreadyMember")).queue(); - return; - } - - jam.get().user(member).join(team); - team.meta().role().ifPresent(role -> guild.addRoleToMember(member, role)); - interaction.getHook().editOriginal(localizer.localize("command.team.invite.joined")).queue(); - team.meta().textChannel().ifPresent(channel -> { - channel.sendMessage(localizer.localize("command.team.invite.joinedBroadcast", Replacement.createMention(member))) - .queue(); - }); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/team/handler/Leave.java b/bot/src/main/java/de/chojo/gamejam/commands/team/handler/Leave.java deleted file mode 100644 index e66e873c..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/team/handler/Leave.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.team.handler; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.data.dao.JamGuild; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.localization.util.Replacement; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -public final class Leave implements SlashHandler { - private final Guilds guilds; - - public Leave(Guilds guilds) { - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var jamGuild = guilds.guild(event); - var optJam = jamGuild.jams().nextOrCurrent(); - if (optJam.isEmpty()) { - event.reply(context.localize("error.nojamactive")).setEphemeral(true).queue(); - return; - } - var jam = optJam.get(); - - if (jam.state().isVoting()) { - event.reply(context.localize("error.votingactive")).setEphemeral(true).queue(); - return; - } - - jam.teams().byMember(event.getMember()) - .ifPresentOrElse(team -> { - if (!team.isLeader(event.getUser())) { - event.reply(context.localize("command.team.leave.message.leaderleave")).setEphemeral(true).queue(); - return; - } - team.member(event.getMember()).ifPresent(member -> { - member.leave(); - team.meta().textChannel().ifPresent(channel -> { - channel.sendMessage(context.localize("command.team.leave.leftBroadcast", - Replacement.createMention(event.getMember()))) - .queue(); - }); - event.reply(context.localize("command.team.leave.left")).setEphemeral(true).queue(); - }); - }, () -> event.reply(context.localize("error.noteam")).setEphemeral(true).queue()); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/team/handler/List.java b/bot/src/main/java/de/chojo/gamejam/commands/team/handler/List.java deleted file mode 100644 index 5434df40..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/team/handler/List.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.team.handler; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.data.dao.JamGuild; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.pagination.bag.PrivateListPageBag; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import net.dv8tion.jda.api.utils.messages.MessageEditData; - -public class List implements SlashHandler { - private final Guilds guilds; - - public List(Guilds guilds) { - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - JamGuild guild = guilds.guild(event); - var optJam = guild.jams().nextOrCurrent(); - if (optJam.isEmpty()) { - event.reply(context.localize("error.nojamactive")).setEphemeral(true).queue(); - return; - } - var jam = optJam.get(); - - context.registerPage(new PrivateListPageBag<>(jam.teams().teams(), event.getUser().getIdLong()) { - @Override - public MessageEditData buildPage() { - return MessageEditData.fromEmbeds(currentElement().profileEmbed(context.guildLocalizer())); - } - }, true); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/team/handler/Profile.java b/bot/src/main/java/de/chojo/gamejam/commands/team/handler/Profile.java deleted file mode 100644 index 0b7797f9..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/team/handler/Profile.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.team.handler; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.data.dao.JamGuild; -import de.chojo.gamejam.data.dao.guild.jams.jam.teams.Team; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import net.dv8tion.jda.api.interactions.commands.Command; - -import java.util.Collections; - -public final class Profile implements SlashHandler { - private final Guilds guilds; - - public Profile(Guilds guilds) { - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var optJam = guilds.guild(event).jams().nextOrCurrent(); - if (optJam.isEmpty()) { - event.reply(context.localize("error.nojamactive")).setEphemeral(true).queue(); - return; - } - var jam = optJam.get(); - var teams = jam.teams(); - - if (event.getOption("user") != null) { - teams.byMember(event.getOption("user").getAsMember()) - .ifPresentOrElse(team -> sendProfile(event, team, context), - () -> event.reply(context.localize("command.team.profile.message.nouserteam")) - .setEphemeral(true) - .queue()); - return; - } - if (event.getOption("team") != null) { - teams.byName(event.getOption("team").getAsString()) - .ifPresentOrElse(team -> sendProfile(event, team, context), - () -> event.reply(context.localize("error.unkownteam")).setEphemeral(true).queue()); - return; - } - teams.byMember(event.getMember()) - .ifPresentOrElse(team -> sendProfile(event, team, context), - () -> event.reply(context.localize("error.noteam")).setEphemeral(true).queue()); - } - - private void sendProfile(SlashCommandInteractionEvent event, Team team, EventContext context) { - event.replyEmbeds(team.profileEmbed(context.guildLocalizer())).setEphemeral(true).queue(); - } - - @Override - public void onAutoComplete(CommandAutoCompleteInteractionEvent event, EventContext context) { - var guild = guilds.guild(event); - var option = event.getFocusedOption(); - if ("team".equals(option.getName())) { - var choices = guild.jams().nextOrCurrent() - .map(jam -> jam.teams().completeTeam(option.getValue())) - .orElse(Collections.emptyList()); - event.replyChoices(choices).queue(); - } - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/team/handler/Promote.java b/bot/src/main/java/de/chojo/gamejam/commands/team/handler/Promote.java deleted file mode 100644 index 19840efd..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/team/handler/Promote.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.team.handler; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.data.dao.JamGuild; -import de.chojo.gamejam.data.dao.guild.jams.jam.teams.Team; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -import java.util.Optional; - -public class Promote implements SlashHandler { - private final Guilds guilds; - - public Promote(Guilds guilds) { - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - JamGuild guild = guilds.guild(event); - var optJam = guild.jams().nextOrCurrent(); - if (optJam.isEmpty()) { - event.reply(context.localize("error.nojamactive")).setEphemeral(true).queue(); - return; - } - var jam = optJam.get(); - - var user = event.getOption("user").getAsMember(); - jam.teams().byMember(user) - .ifPresentOrElse( - targetTeam -> { - if (!targetTeam.isLeader(event.getUser())) { - event.reply(context.localize("error.noleader")).setEphemeral(true).queue(); - return; - } - - targetTeam.meta().leader(user); - event.reply(context.localize("command.team.promote.message.done")).setEphemeral(true).queue(); - }, - () -> event.reply(context.localize("error.noteam")).setEphemeral(true).queue()); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/team/handler/Rename.java b/bot/src/main/java/de/chojo/gamejam/commands/team/handler/Rename.java deleted file mode 100644 index d31a6bcb..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/team/handler/Rename.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.team.handler; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.data.dao.JamGuild; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -public class Rename implements SlashHandler { - private final Guilds guilds; - - public Rename(Guilds guilds) { - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - JamGuild guild = guilds.guild(event); - var optJam = guild.jams().nextOrCurrent(); - if (optJam.isEmpty()) { - event.reply(context.localize("error.nojamactive")).setEphemeral(true).queue(); - return; - } - var jam = optJam.get(); - - jam.teams().byName(event.getOption("name").getAsString()) - .ifPresentOrElse( - team -> event.reply(context.localize("command.team.create.message.nametaken")).setEphemeral(true) - .queue(), - () -> { - var optCurrTeam = jam.teams().byMember(event.getUser()); - - if (optCurrTeam.isEmpty()) { - event.reply(context.localize("error.noteam")).setEphemeral(true).queue(); - return; - } - - var team = optCurrTeam.get(); - - if (!team.isLeader(event.getUser())) { - event.reply(context.localize("error.noleader")).setEphemeral(true).queue(); - return; - } - - team.meta().rename(event.getOption("name").getAsString()); - event.reply(context.localize("command.team.rename.message.done")).setEphemeral(true).queue(); - }); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/unregister/Unregister.java b/bot/src/main/java/de/chojo/gamejam/commands/unregister/Unregister.java deleted file mode 100644 index b176e477..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/unregister/Unregister.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.unregister; - -import de.chojo.gamejam.commands.unregister.handler.Handler; -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.jdautil.interactions.slash.Slash; -import de.chojo.jdautil.interactions.slash.provider.SlashCommand; - -public class Unregister extends SlashCommand { - public Unregister(Guilds guilds) { - super(Slash.of("unregister", "command.unregister.description") - .command(new Handler(guilds))); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/unregister/handler/Handler.java b/bot/src/main/java/de/chojo/gamejam/commands/unregister/handler/Handler.java deleted file mode 100644 index a8969d3d..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/unregister/handler/Handler.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.unregister.handler; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -public class Handler implements SlashHandler { - private final Guilds guilds; - - public Handler(Guilds guilds) { - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var guild = guilds.guild(event); - var optJam = guild.jams().nextOrCurrent(); - if (optJam.isEmpty()) { - event.reply(context.localize("error.noupcomingjam")) - .setEphemeral(true) - .queue(); - return; - } - - var jam = optJam.get(); - - if (!jam.registrations().contains(event.getMember().getIdLong())) { - event.reply(context.localize("command.unregister.message.notregistered")).setEphemeral(true).queue(); - return; - } - - jam.teams().byMember(event.getMember()) - .ifPresentOrElse( - team -> event.reply(context.localize("command.unregister.message.inteam")).queue(), - () -> { - var settings = guild.jamSettings(); - var role = event.getGuild().getRoleById(settings.jamRole()); - if (role != null) { - event.getGuild().removeRoleFromMember(event.getMember(), role).queue(); - } - event.reply(context.localize("command.unregister.message.unregistered")) - .setEphemeral(true) - .queue(); - - }); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/vote/VoteCommand.java b/bot/src/main/java/de/chojo/gamejam/commands/vote/VoteCommand.java new file mode 100644 index 00000000..733c3a1a --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/commands/vote/VoteCommand.java @@ -0,0 +1,123 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.commands.vote; + +import com.google.inject.Inject; +import de.chojo.gamejam.data.access.Guilds; +import de.chojo.gamejam.server.CommandContextProvider; +import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; +import de.chojo.jdautil.localization.util.Format; +import de.chojo.jdautil.localization.util.Replacement; +import de.chojo.jdautil.wrapper.EventContext; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.AutoComplete; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.annotations.interactions.Param; +import io.github.kaktushose.jdac.dispatching.events.interactions.AutoCompleteEvent; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; +import io.github.kaktushose.jdac.message.placeholder.Entry; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; +import net.dv8tion.jda.api.interactions.commands.Command; + +import java.util.Collections; +import java.util.stream.IntStream; + +import static net.dv8tion.jda.api.utils.MarkdownUtil.bold; + +@Bundle("locale") +@Interaction +public class VoteCommand { + private final CommandContextProvider commandContextProvider; + + @Inject + public VoteCommand(CommandContextProvider commandContextProvider) { + this.commandContextProvider = commandContextProvider; + } + + @io.github.kaktushose.jdac.annotations.interactions.Command(value = "vote") + public void onCommand(CommandEvent event, @Param("team") String team, @Param("points") int points) { + var guild = commandContextProvider.guilds().guild(event); + var optJam = guild.jams().nextOrCurrent(); + if (optJam.isEmpty()) { + event.with().ephemeral(true).reply("error.nojamactive"); + return; + } + + var jam = optJam.get(); + + event.deferReply(true); + if (!jam.state().isVoting()) { + event.reply("command.votes.vote.message.notactive"); + return; + } + + if (!jam.registrations().contains(event.getMember().getIdLong())) { + event.reply("error.notregistered"); + return; + } + + var teams = jam.teams(); + var teamCount = teams.teams().size(); + var optVoteTeam = teams.byName(team); + + if (optVoteTeam.isEmpty()) { + event.reply("error.unkownteam"); + return; + } + + var voteTeam = optVoteTeam.get(); + + if (voteTeam.member(event.getMember()).isPresent()) { + event.reply("command.votes.vote.message.ownteam"); + return; + } + + var user = jam.user(event.getMember()); + + var pointsGiven = user.votesGiven(); + + //TODO: Max points and max points per team are currently hardcoded. should be configurable in the future. + var finalPoints = Math.clamp(points, 0, 5); + + var votes = voteTeam.votes(event.getMember()); + + if (votes < finalPoints && pointsGiven + finalPoints > teamCount) { + event.reply("command.votes.vote.message.maxpointsreached", Entry.entry("REMAINING", bold(String.valueOf(teamCount - pointsGiven)))); + return; + } + + voteTeam.vote(event.getMember(), finalPoints); + + event.reply("command.votes.vote.message.done", + Entry.entry("REMAINING", bold(String.valueOf(teamCount - user.votesGiven()))), + Entry.entry("POINTS", bold(String.valueOf(finalPoints))), + Entry.entry("TEAM", bold(voteTeam.meta().name())) + ); + } + + @AutoComplete(value = "vote", options = "team") + public void onAutoCompleteTeam(AutoCompleteEvent event) { + var jam = commandContextProvider.guilds().guild(event).jams().nextOrCurrent(); + + if (jam.isEmpty()) { + event.replyChoices(Collections.emptyList()); + return; + } + var teams = jam.get().teams().teams().stream() + .filter(team -> team.matchName(event.getValue())) + .map(team -> team.meta().name()) + .map(team -> new Command.Choice(team, team)) + .toList(); + event.replyChoices(teams); + } + + @AutoComplete(value = "vote", options = "points") + public void onAutoCompletePoints(AutoCompleteEvent event) { + event.replyChoices(IntStream.range(0, 6).mapToObj(num -> new Command.Choice(String.valueOf(num), num)) + .toList()); + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/vote/Votes.java b/bot/src/main/java/de/chojo/gamejam/commands/vote/Votes.java deleted file mode 100644 index b52829aa..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/vote/Votes.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.vote; - -import de.chojo.gamejam.commands.vote.handler.Info; -import de.chojo.gamejam.commands.vote.handler.Ranking; -import de.chojo.gamejam.commands.vote.handler.Vote; -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.jdautil.interactions.slash.Argument; -import de.chojo.jdautil.interactions.slash.Slash; -import de.chojo.jdautil.interactions.slash.SubCommand; -import de.chojo.jdautil.interactions.slash.provider.SlashCommand; - -public class Votes extends SlashCommand { - public Votes(Guilds guilds) { - super(Slash.of("votes", "command.votes.description") - .subCommand(SubCommand.of("vote", "command.votes.vote.description") - .handler(new Vote(guilds)) - .argument(Argument.text("team", "command.votes.vote.options.team.description").asRequired() - .withAutoComplete()) - .argument(Argument.integer("points", "command.votes.vote.options.points.description").asRequired() - .withAutoComplete())) - .subCommand(SubCommand.of("info", "command.votes.info.description") - .handler(new Info(guilds))) - .subCommand(SubCommand.of("ranking", "command.votes.ranking.description") - .handler(new Ranking(guilds))) - ); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/vote/VotesInfoCommand.java b/bot/src/main/java/de/chojo/gamejam/commands/vote/VotesInfoCommand.java new file mode 100644 index 00000000..7b5dc4e4 --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/commands/vote/VotesInfoCommand.java @@ -0,0 +1,50 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.commands.vote; + +import com.google.inject.Inject; +import de.chojo.gamejam.server.CommandContextProvider; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; + +import java.util.stream.Collectors; + +@Bundle("locale") +@Interaction +public class VotesInfoCommand { + private final CommandContextProvider commandContextProvider; + + @Inject + public VotesInfoCommand(CommandContextProvider commandContextProvider) { + this.commandContextProvider = commandContextProvider; + } + + @Command(value = "votes info") + public void onCommand(CommandEvent event) { + var guild = commandContextProvider.guilds().guild(event); + var optJam = guild.jams().nextOrCurrent(); + if (optJam.isEmpty()) { + event.with().ephemeral(true).reply("error.nojamactive"); + return; + } + var jam = optJam.get(); + + var voteEntries = jam.user(event.getMember()).votes(); + var given = voteEntries.stream() + .filter(e -> e.points() != 0) + .map(e -> e.team().meta().name() + ": **" + e.points() + "**") + .collect(Collectors.joining("\n")); + + var embed = event.embed("votes-info"); + embed.title("command.votes.info.embed.title"); + embed.description(given); + + event.with().ephemeral(true).embeds(embed).reply(); + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/vote/VotesRankingCommand.java b/bot/src/main/java/de/chojo/gamejam/commands/vote/VotesRankingCommand.java new file mode 100644 index 00000000..55d70774 --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/commands/vote/VotesRankingCommand.java @@ -0,0 +1,95 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.commands.vote; + +import com.google.inject.Inject; +import de.chojo.gamejam.data.access.Guilds; +import de.chojo.gamejam.server.CommandContextProvider; +import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; +import de.chojo.jdautil.localization.util.LocalizedEmbedBuilder; +import de.chojo.jdautil.pagination.bag.ListPageBag; +import de.chojo.jdautil.wrapper.EventContext; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Button; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; +import io.github.kaktushose.jdac.dispatching.events.interactions.ComponentEvent; +import io.github.kaktushose.jdac.dispatching.reply.Component; +import io.github.kaktushose.jdac.embeds.Embed; +import net.dv8tion.jda.api.components.buttons.ButtonStyle; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; +import net.dv8tion.jda.api.utils.messages.MessageEditData; + +import java.util.List; + +@Bundle("locale") +@Interaction +public class VotesRankingCommand { + private final CommandContextProvider commandContextProvider; + + private List pages; + private int currentPage; + + @Inject + public VotesRankingCommand(CommandContextProvider commandContextProvider) { + this.commandContextProvider = commandContextProvider; + } + + @Command(value = "votes ranking") + public void onCommand(CommandEvent event) { + var guild = commandContextProvider.guilds().guild(event); + var optJam = guild.jams().nextOrCurrent(); + if (optJam.isEmpty()) { + event.with().ephemeral(true).reply("error.nojamactive"); + return; + } + + var jam = optJam.get(); + + if (jam.state().isVoting()) { + event.with().ephemeral(true).reply("command.votes.ranking.message.voteactive"); + return; + } + + var ranking = jam.votes(); + + pages = jam.votes().stream().map(team -> { + var embed = event.embed("votes-ranking"); + embed.title(team.rank() + " | " + team.team().meta().name()); + embed.fields().add("command.votes.ranking.embed.votes", String.valueOf(team.votes()), true); + return embed; + }).toList(); + currentPage = 0; + event.with().components(Component.disabled("onPrev"), Component.enabled("onNext")) + .embeds(pages.getFirst()).reply(); + } + + //TODO: localization + @Button(value = "◀ Zurück", style = ButtonStyle.SECONDARY) + public void onPrev(ComponentEvent event) { + currentPage--; + event.with() + .components( + currentPage == 0 ? Component.disabled("onPrev") : Component.enabled("onPrev"), + Component.enabled("onNext") + ) + .embeds(pages.get(currentPage)).reply(); + } + + //TODO: localization + @Button(value = "Weiter ▶", style = ButtonStyle.SECONDARY) + public void onNext(ComponentEvent event) { + currentPage++; + event.with() + .components( + Component.enabled("onPrev"), + currentPage == pages.size() - 1 ? Component.disabled("onNext") : Component.enabled("onNext") + ) + .embeds(pages.get(currentPage)).reply(); + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/vote/handler/Info.java b/bot/src/main/java/de/chojo/gamejam/commands/vote/handler/Info.java deleted file mode 100644 index 6e9cfa57..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/vote/handler/Info.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.vote.handler; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.localization.util.LocalizedEmbedBuilder; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -import java.util.stream.Collectors; - -public class Info implements SlashHandler { - private final Guilds guilds; - - public Info(Guilds guilds) { - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var guild = guilds.guild(event); - var optJam = guild.jams().nextOrCurrent(); - if (optJam.isEmpty()) { - event.reply(context.localize("error.nojamactive")).setEphemeral(true).queue(); - return; - } - var jam = optJam.get(); - - var voteEntries = jam.user(event.getMember()).votes(); - var given = voteEntries.stream() - .filter(e -> e.points() != 0) - .map(e -> e.team().meta().name() + ": **" + e.points() + "**") - .collect(Collectors.joining("\n")); - - var build = new LocalizedEmbedBuilder(context.guildLocalizer()) - .setTitle("command.votes.info.embed.title") - .setDescription(given) - .build(); - event.replyEmbeds(build).setEphemeral(true).queue(); - - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/vote/handler/Ranking.java b/bot/src/main/java/de/chojo/gamejam/commands/vote/handler/Ranking.java deleted file mode 100644 index edd050fc..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/vote/handler/Ranking.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.vote.handler; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.localization.util.LocalizedEmbedBuilder; -import de.chojo.jdautil.pagination.bag.ListPageBag; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import net.dv8tion.jda.api.utils.messages.MessageEditData; - -public class Ranking implements SlashHandler { - private final Guilds guilds; - - public Ranking(Guilds guilds) { - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var guild = guilds.guild(event); - var optJam = guild.jams().nextOrCurrent(); - if (optJam.isEmpty()) { - event.reply(context.localize("error.nojamactive")).setEphemeral(true).queue(); - return; - } - - var jam = optJam.get(); - - if (jam.state().isVoting()) { - event.reply(context.localize("command.votes.ranking.message.voteactive")).setEphemeral(true).queue(); - return; - } - - var ranking = jam.votes(); - - var pageBag = new ListPageBag<>(ranking) { - @Override - public MessageEditData buildPage() { - var teamVote = currentElement(); - var embed = new LocalizedEmbedBuilder(context.guildLocalizer()) - .setTitle(teamVote.rank() + " | " + teamVote.team().meta().name()) - .addField("command.votes.ranking.embed.votes", String.valueOf(teamVote.votes()), true) - .build(); - return MessageEditData.fromEmbeds(embed); - } - }; - context.registerPage(pageBag, true); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/vote/handler/Vote.java b/bot/src/main/java/de/chojo/gamejam/commands/vote/handler/Vote.java deleted file mode 100644 index 9ec1e456..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/vote/handler/Vote.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.vote.handler; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.data.dao.guild.jams.jam.teams.Team; -import de.chojo.gamejam.data.dao.guild.jams.jam.user.JamUser; -import de.chojo.gamejam.data.wrapper.votes.VoteEntry; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.localization.util.Format; -import de.chojo.jdautil.localization.util.Replacement; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import net.dv8tion.jda.api.interactions.commands.Command; - -import java.util.Collections; -import java.util.stream.IntStream; - -public class Vote implements SlashHandler { - private final Guilds guilds; - - public Vote(Guilds guilds) { - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var guild = guilds.guild(event); - var optJam = guild.jams().nextOrCurrent(); - if (optJam.isEmpty()) { - event.reply(context.localize("error.nojamactive")).setEphemeral(true).queue(); - return; - } - - var jam = optJam.get(); - - event.deferReply(true).queue(); - if (!jam.state().isVoting()) { - event.getHook().editOriginal(context.localize("command.votes.vote.message.notactive")).queue(); - return; - } - - if (!jam.registrations().contains(event.getMember().getIdLong())) { - event.getHook().editOriginal(context.localize("error.notregistered")).queue(); - return; - } - - var teams = jam.teams(); - var teamCount = teams.teams().size(); - var optVoteTeam = teams.byName(event.getOption("team").getAsString()); - - if (optVoteTeam.isEmpty()) { - event.getHook().editOriginal(context.localize("error.unkownteam")).queue(); - return; - } - - var voteTeam = optVoteTeam.get(); - - if (voteTeam.member(event.getMember()).isPresent()) { - event.getHook().editOriginal(context.localize("command.votes.vote.message.ownteam")).queue(); - return; - } - - var user = jam.user(event.getMember()); - - var pointsGiven = user.votesGiven(); - - //TODO: Max points and max points per team are currently hardcoded. should be configurable in the future. - var points = Math.min(5, Math.max(0, event.getOption("points").getAsInt())); - - var votes = voteTeam.votes(event.getMember()); - - if (votes < points && pointsGiven + points > teamCount) { - event.getHook().editOriginal(context.localize("command.votes.vote.message.maxpointsreached", - Replacement.create("REMAINING", teamCount - pointsGiven) - .addFormatting(Format.BOLD))) - .queue(); - return; - } - - voteTeam.vote(event.getMember(), points); - - event.getHook().editOriginal(context.localize("command.votes.vote.message.done", - Replacement.create("REMAINING", teamCount - user.votesGiven()).addFormatting(Format.BOLD), - Replacement.create("POINTS", points).addFormatting(Format.BOLD), - Replacement.create("TEAM", voteTeam.meta().name()).addFormatting(Format.BOLD))) - .queue(); - } - - @Override - public void onAutoComplete(CommandAutoCompleteInteractionEvent event, EventContext context) { - var jam = guilds.guild(event).jams().nextOrCurrent(); - var option = event.getFocusedOption(); - if ("team".equals(option.getName())) { - if (jam.isEmpty()) { - event.replyChoices(Collections.emptyList()).queue(); - return; - } - var teams = jam.get().teams().teams().stream() - .filter(team -> team.matchName(option.getValue())) - .map(team -> team.meta().name()) - .map(team -> new Command.Choice(team, team)) - .toList(); - event.replyChoices(teams).queue(); - } - if ("points".equals(option.getName())) { - event.replyChoices(IntStream.range(0, 6).mapToObj(num -> new Command.Choice(String.valueOf(num), num)) - .toList()).queue(); - } - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/data/dao/guild/jams/jam/teams/Team.java b/bot/src/main/java/de/chojo/gamejam/data/dao/guild/jams/jam/teams/Team.java index 7097917c..1135088b 100644 --- a/bot/src/main/java/de/chojo/gamejam/data/dao/guild/jams/jam/teams/Team.java +++ b/bot/src/main/java/de/chojo/gamejam/data/dao/guild/jams/jam/teams/Team.java @@ -13,6 +13,8 @@ import de.chojo.jdautil.localization.LocalizationContext; import de.chojo.jdautil.localization.util.LocalizedEmbedBuilder; import de.chojo.jdautil.util.MentionUtil; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; +import io.github.kaktushose.jdac.embeds.Embed; import net.dv8tion.jda.api.entities.ISnowflake; import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.MessageEmbed; @@ -43,21 +45,21 @@ public void delete() { meta.role().ifPresent(role -> role.delete().queue()); } - public MessageEmbed profileEmbed(LocalizationContext localizer) { + public Embed profileEmbed(CommandEvent commandEvent) { var member = member().stream() .map(u -> u.member().getAsMention()) .collect(Collectors.joining(", ")); var meta = meta(); - return new LocalizedEmbedBuilder(localizer) - .setTitle(meta.name()) - .setDescription(meta.projectDescription()) - .addField("command.team.profile.member", member, true) - .addField("command.team.profile.leader", MentionUtil.user(meta.leader()), true) - .addField("command.team.profile.projecturl", meta.projectUrl(), true) - .setFooter(String.format("#%s", id())) - .build(); + + var embed = commandEvent.embed("team-profile"); + embed.title(meta.name()); + embed.fields().add("command.team.profile.member", member, true); + embed.fields().add("command.team.profile.leader", MentionUtil.user(meta.leader()), true); + embed.fields().add("command.team.profile.projecturl", meta.projectUrl(), true); + + return embed; } public List member() { diff --git a/bot/src/main/java/de/chojo/gamejam/listener/InviteButtonListener.java b/bot/src/main/java/de/chojo/gamejam/listener/InviteButtonListener.java new file mode 100644 index 00000000..354f3987 --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/listener/InviteButtonListener.java @@ -0,0 +1,102 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.listener; + +import de.chojo.gamejam.data.access.Guilds; +import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; +import net.dv8tion.jda.api.hooks.ListenerAdapter; +import org.slf4j.Logger; + +import static org.slf4j.LoggerFactory.getLogger; + +public class InviteButtonListener extends ListenerAdapter { + private static final Logger log = getLogger(InviteButtonListener.class); + private static final String BUTTON_PREFIX = "invite-accept:"; + + private final Guilds guilds; + + public InviteButtonListener(Guilds guilds) { + this.guilds = guilds; + } + + @Override + public void onButtonInteraction(ButtonInteractionEvent event) { + var componentId = event.getComponentId(); + if (!componentId.startsWith(BUTTON_PREFIX)) { + return; + } + + event.deferReply().queue(); + + var parts = componentId.substring(BUTTON_PREFIX.length()).split(":"); + if (parts.length != 3) { + event.getHook().editOriginal("Invalid invite data.").queue(); + return; + } + + long guildId; + int teamId; + long userId; + try { + guildId = Long.parseLong(parts[0]); + teamId = Integer.parseInt(parts[1]); + userId = Long.parseLong(parts[2]); + } catch (NumberFormatException e) { + event.getHook().editOriginal("Invalid invite data.").queue(); + return; + } + + var manager = event.getJDA().getShardManager(); + var guild = manager.getGuildById(guildId); + if (guild == null) { + event.getHook().editOriginal("Guild not found.").queue(); + return; + } + + var user = manager.retrieveUserById(userId).complete(); + var member = guild.retrieveMember(user).complete(); + var jamGuild = guilds.guild(guild); + var settings = jamGuild.jamSettings(); + + var optJam = jamGuild.jams().nextOrCurrent(); + if (optJam.isEmpty()) { + event.getHook().editOriginal("The game jam is over.").queue(); + return; + } + + var jam = optJam.get(); + var optTeam = jam.teams().byId(teamId); + if (optTeam.isEmpty()) { + event.getHook().editOriginal("Team not found.").queue(); + return; + } + + var team = optTeam.get(); + var members = team.member(); + + if (members.size() >= settings.teamSize()) { + event.getHook().editOriginal("The team is already full.").queue(); + return; + } + + var currTeam = jam.teams().byMember(user); + if (currTeam.isPresent()) { + event.getHook().editOriginal("You are already part of a team.").queue(); + return; + } + + jam.user(member).join(team); + team.meta().role().ifPresent(role -> guild.addRoleToMember(member, role).queue()); + event.getHook().editOriginal("You have joined the team!").queue(); + team.meta().textChannel().ifPresent(channel -> + channel.sendMessage(member.getAsMention() + " has joined the team!").queue() + ); + + // Disable the button after use + event.getMessage().editMessageComponents().queue(); + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/message/EmbedHelper.java b/bot/src/main/java/de/chojo/gamejam/message/EmbedHelper.java index 378512e2..df6cb3cf 100644 --- a/bot/src/main/java/de/chojo/gamejam/message/EmbedHelper.java +++ b/bot/src/main/java/de/chojo/gamejam/message/EmbedHelper.java @@ -6,42 +6,43 @@ package de.chojo.gamejam.message; -import de.chojo.gamejam.server.TeamServer; -import de.chojo.jdautil.localization.util.LocalizedEmbedBuilder; -import de.chojo.jdautil.wrapper.EventContext; +import de.chojo.gamejam.data.dao.guild.jams.jam.teams.Team; +import de.chojo.gamejam.server.ServerService; +import de.chojo.gamejam.server.ServerStatus; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; import net.dv8tion.jda.api.entities.MessageEmbed; import java.util.concurrent.CompletableFuture; public class EmbedHelper { - public static CompletableFuture embedDetailedStatus(TeamServer teamServer, EventContext context) { + public static CompletableFuture embedDetailedStatus(Team team, ServerService serverService, CommandEvent event) { return CompletableFuture.supplyAsync(() -> { + var serverStatus = serverService.getServerStatus(team.id()); - var builder = new LocalizedEmbedBuilder(context.guildLocalizer()) - .setTitle("%s #%d | %s".formatted(teamServer.statusEmoji(), teamServer.team().id(), teamServer.team().meta().name())); - if (!teamServer.exists()) { - builder.setDescription("teamserver.message.detailstatus.nonexisting.description"); + var embed = event.embed("status"); + embed.title("%s #%d | %s".formatted(serverStatus.emoji(), team.id(), team.meta().name())); + + if (serverStatus == ServerStatus.VOID) { + embed.description("teamserver.message.detailstatus.nonexisting.description"); } else { - if (teamServer.isRunning()) { - builder.setDescription("teamserver.message.detailstatus.existing.description"); + if (serverStatus == ServerStatus.RUNNING) { + embed.description("teamserver.message.detailstatus.existing.description"); - var serverStats = teamServer.stats(); + var serverStats = serverService.serverHttpService().fetchServerStats(serverService.dockerService().containerName(team.id())); serverStats.ifPresent(stats -> { var memory = stats.memory(); - builder.addField("word.memory", "$word.used$ %d%n$word.total$: %d%n$word.max$: %d".formatted(memory.usedMb(), memory.totalMb(), memory.maxMb()), true) - .addField("word.tps", "1 $word.min$: %.2f%n5 $word.min$: %.2f%n 15 $word.min$: %.2f%n$word.averageticktime$ %.2f".formatted( - stats.tps()[0], stats.tps()[1], stats.tps()[2], stats.averageTickTime()), true) - .addField("word.players", String.valueOf(stats.onlinePlayers()), true) - .addField("word.system", "$word.activethreads$: %d".formatted(stats.activeThreads()), true); + embed.fields().add("word.memory", "$word.used$ %d%n$word.total$: %d%n$word.max$: %d".formatted(memory.usedMb(), memory.totalMb(), memory.maxMb()), true); + embed.fields().add("word.tps", "1 $word.min$: %.2f%n5 $word.min$: %.2f%n 15 $word.min$: %.2f%n$word.averageticktime$ %.2f".formatted( + stats.tps()[0], stats.tps()[1], stats.tps()[2], stats.averageTickTime()), true); + embed.fields().add("word.players", String.valueOf(stats.onlinePlayers()), true); + embed.fields().add("word.system", "$word.activethreads$: %d".formatted(stats.activeThreads()), true); }); } else { - builder.setDescription("word.serversetup") - .addField("word.ports", "word.notrunning", true); + embed.description("word.serversetup"); } } - - return builder.build(); + return embed.build(); }); } } diff --git a/bot/src/main/java/de/chojo/gamejam/server/CommandContextProvider.java b/bot/src/main/java/de/chojo/gamejam/server/CommandContextProvider.java new file mode 100644 index 00000000..96493a52 --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/server/CommandContextProvider.java @@ -0,0 +1,35 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.server; + +import de.chojo.gamejam.data.access.Guilds; +import net.dv8tion.jda.api.entities.Member; + +public class CommandContextProvider { + private final Guilds guilds; + + public CommandContextProvider(Guilds guilds) { + this.guilds = guilds; + } + + public UserContext getUserContext(Member member) { + var jamOpt = guilds.guild(member.getGuild()).jams().activeJam(); + + if (jamOpt.isEmpty()) { + return new UserContext(null); + } + + var jam = jamOpt.get(); + var team = jam.teams().byMember(member); + return team.map(UserContext::new).orElseGet(() -> new UserContext(null)); + + } + + public Guilds guilds() { + return guilds; + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/server/ServerHttpService.java b/bot/src/main/java/de/chojo/gamejam/server/ServerHttpService.java new file mode 100644 index 00000000..a20a91ef --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/server/ServerHttpService.java @@ -0,0 +1,141 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.server; + +import de.chojo.gamejam.configuration.Configuration; +import de.chojo.gamejam.util.Mapper; +import de.chojo.pluginjam.payload.Registration; +import de.chojo.pluginjam.payload.StatsPayload; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.Optional; + +public class ServerHttpService { + private final HttpClient http = HttpClient.newHttpClient(); + private static final int DEFAULT_API_PORT = 30000; + private final Logger log = LoggerFactory.getLogger(ServerHttpService.class); + private final Configuration configuration; + + public ServerHttpService(Configuration configuration) { + this.configuration = configuration; + } + + public Optional fetchServerStats(String containerName) { + var request = requestBuilder("v1/stats", containerName).GET().build(); + + try { + var response = http.send(request, HttpResponse.BodyHandlers.ofString()); + var stats = Mapper.MAPPER.readValue(response.body(), StatsPayload.class); + return Optional.of(stats); + } catch (IOException | InterruptedException e) { + log.error("Could not read stats", e); + } + return Optional.empty(); + } + + public boolean fetchOnlineStatus(String containerName) { + var request = requestBuilder("v1/online", containerName).GET().build(); + try { + var response = http.send(request, HttpResponse.BodyHandlers.ofString()); + return response.statusCode() == 200; + } catch (IOException | InterruptedException e) { + log.error("Could not read online status", e); + } + return false; + } + + public boolean configureSpectatorOverflow(String containerName, boolean enabled) { + var request = requestBuilder("v1/config/spectatoroverflow", containerName).POST(HttpRequest.BodyPublishers.ofString(Boolean.toString(enabled))).build(); + try { + var response = http.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() != 200) { + log.error("Could not configure spectator overflow"); + } + return response.statusCode() == 200; + } catch (IOException | InterruptedException e) { + log.error("Could not configure spectator overflow", e); + return false; + } + } + + public boolean configureMaxPlayers(String containerName, int maxPlayers) { + var request = requestBuilder("v1/config/maxplayers", containerName).POST(HttpRequest.BodyPublishers.ofString(Integer.toString(maxPlayers))).build(); + try { + var response = http.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() != 200) { + log.error("Could not configure max players"); + } + return response.statusCode() == 200; + } catch (IOException | InterruptedException e) { + log.error("Could not configure max players", e); + return false; + } + } + + public boolean configureMessage(String containerName, String message) { + var request = requestBuilder("v1/config/message", containerName).POST(HttpRequest.BodyPublishers.ofString(message)).build(); + try { + var response = http.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() != 200) { + log.error("Could not configure message"); + } + return response.statusCode() == 200; + } catch (IOException | InterruptedException e) { + log.error("Could not configure message", e); + return false; + } + } + + public void registerToVelocity(TeamServer teamServer, String containerName) { + var registration = new Registration(teamServer.team().id(), teamServer.team().meta().name(), containerName); + var velocityHost = configuration.serverManagement().getVelocityHost(); + var velocityPort = configuration.serverManagement().velocityPort(); + + try { + var body = Mapper.MAPPER.writeValueAsString(registration); + var request = HttpRequest.newBuilder(URI.create("https://%s:%d/%s".formatted(velocityHost, velocityPort, "v1/server"))) + .POST(HttpRequest.BodyPublishers.ofString(body)).build(); + var response = http.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() != 200) { + log.error("Could not register to velocity"); + } + } catch (IOException | InterruptedException e) { + log.error("Could not register to velocity", e); + } + } + + public void unregisterFromVelocity(TeamServer teamServer, String containerName) { + var velocityHost = configuration.serverManagement().getVelocityHost(); + var velocityPort = configuration.serverManagement().velocityPort(); + + var request = HttpRequest.newBuilder(URI.create("https://%s:%d/%s?id=%d&host=%s" + .formatted(velocityHost, velocityPort, "v1/server", teamServer.team().id(), containerName))) + .DELETE().build(); + try { + var response = http.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() != 200) { + log.error("Could not unregister from velocity"); + } + } catch (IOException | InterruptedException e) { + log.error("Could not unregister from velocity", e); + } + } + + public HttpRequest.Builder requestBuilder(String path, String host) { + return HttpRequest.newBuilder(URI.create("https://%s:%d/%s".formatted(host, DEFAULT_API_PORT, path))); + } + + public HttpRequest.Builder requestBuilder(String path, String host, String query) { + return HttpRequest.newBuilder(URI.create("https://%s:%d/%s?%s".formatted(host, DEFAULT_API_PORT, path, query))); + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/server/ServerService.java b/bot/src/main/java/de/chojo/gamejam/server/ServerService.java index 6101e21a..051f0e3e 100644 --- a/bot/src/main/java/de/chojo/gamejam/server/ServerService.java +++ b/bot/src/main/java/de/chojo/gamejam/server/ServerService.java @@ -6,112 +6,86 @@ package de.chojo.gamejam.server; -import com.fasterxml.jackson.core.JsonProcessingException; import de.chojo.gamejam.configuration.Configuration; import de.chojo.gamejam.data.access.Teams; import de.chojo.gamejam.data.dao.guild.jams.jam.teams.Team; -import de.chojo.gamejam.util.Mapper; -import de.chojo.pluginjam.payload.Registration; import org.slf4j.Logger; -import java.io.IOException; -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; import java.util.HashMap; -import java.util.List; import java.util.Map; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import java.util.stream.IntStream; import static org.slf4j.LoggerFactory.getLogger; public class ServerService { private static final Logger log = getLogger(ServerService.class); private final Map server = new HashMap<>(); - private Teams teams; private final Configuration configuration; private final DockerService dockerService; + private final ServerHttpService serverHttpService; - public static ServerService create(Configuration configuration) { - return new ServerService(configuration); - } - - private ServerService(Configuration configuration) { + public ServerService(Configuration configuration) { this.configuration = configuration; + this.serverHttpService = new ServerHttpService(configuration); this.dockerService = new DockerService(configuration.docker(), configuration.plugins()); this.dockerService.initDockerClient(); } public void shutdown() { server.forEach((team, teamServer) -> { - teamServer.stop(); + stopServer(teamServer); }); } - public CompletableFuture syncVelocity() { - return CompletableFuture.supplyAsync(() -> { - log.info("Syncing server with velocity instance."); - - var velocityPort = configuration.serverManagement().velocityPort(); - var velocityHost = configuration.serverManagement().getVelocityHost(); - var httpClient = HttpClient.newHttpClient(); - var req = HttpRequest.newBuilder(URI.create("http://%s:%d/v1/server".formatted(velocityHost, velocityPort))) - .GET() - .build(); - HttpResponse response = null; - var retries = 0; - while (retries < 5) { - try { - response = httpClient.send(req, HttpResponse.BodyHandlers.ofString()); - break; - } catch (IOException e) { - log.error("Could not reach velocity instance"); - } catch (InterruptedException e) { - log.error("Interrupted", e); - } - retries++; - try { - Thread.sleep(5000); - } catch (InterruptedException e) { - return false; - } - } - if (retries == 5) return false; - var collectionType = Mapper.MAPPER.getTypeFactory() - .constructCollectionType(List.class, Registration.class); - List registrations; - try { - registrations = Mapper.MAPPER.readValue(response.body(), collectionType); - } catch (JsonProcessingException e) { - log.error("Could not map response"); - return false; - } - - server.clear(); - for (var registration : registrations) { - var optTeam = teams.byId(registration.id()); - if (optTeam.isEmpty()) { - log.warn("Could not find a matching team for id {} of team {}", registration.id(), registration.name()); - continue; - } - var team = optTeam.get(); - log.info("Registered server for team {} with id {}", team.meta().name(), team.id()); - var teamServer = new TeamServer(dockerService, team, configuration); - server.put(team, teamServer); - } - return true; - }); + public void startServer(TeamServer teamServer) { + var teamId = teamServer.team().id(); + var containerName = dockerService.containerName(teamId); + dockerService.startServer(teamId); + serverHttpService.registerToVelocity(teamServer, containerName); + } + + public void stopServer(TeamServer teamServer) { + var teamId = teamServer.team().id(); + var containerName = dockerService.containerName(teamId); + serverHttpService.unregisterFromVelocity(teamServer, containerName); + dockerService.stopServer(teamId); + } + + public void restartServer(TeamServer teamServer) { + var teamId = teamServer.team().id(); + var containerName = dockerService.containerName(teamId); + serverHttpService.unregisterFromVelocity(teamServer, containerName); + dockerService.restartServer(teamId); + serverHttpService.registerToVelocity(teamServer, containerName); } public TeamServer get(Team team) { - return server.computeIfAbsent(team, key -> new TeamServer(dockerService, key, configuration)); + return server.computeIfAbsent(team, t -> new TeamServer(t, dockerService.containerName(t.id()))); + } + + public ServerStatus getServerStatus(int teamId) { + if (dockerService.isRunning(teamId)) { + return ServerStatus.VOID; + } + + var minecraftOnline = serverHttpService.fetchOnlineStatus(dockerService.containerName(teamId)); + var containerRunning = dockerService.isRunning(teamId); + + if (minecraftOnline && containerRunning) { + return ServerStatus.RUNNING; + } + + if (containerRunning) { + return ServerStatus.STARTING_STOPPING; + } + + return ServerStatus.STOPPED; + } + + public ServerHttpService serverHttpService() { + return serverHttpService; } - public void inject(Teams teams) { - this.teams = teams; + public DockerService dockerService() { + return dockerService; } } diff --git a/bot/src/main/java/de/chojo/gamejam/server/ServerStatus.java b/bot/src/main/java/de/chojo/gamejam/server/ServerStatus.java new file mode 100644 index 00000000..e8c1ca95 --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/server/ServerStatus.java @@ -0,0 +1,23 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.server; + +public enum ServerStatus { + STARTING_STOPPING("⏳"), + RUNNING("🟢"), + STOPPED("🔴"), + VOID("❌"); + + private final String emoji; + ServerStatus(String emoji) { + this.emoji = emoji; + } + + public String emoji() { + return emoji; + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/server/TeamServer.java b/bot/src/main/java/de/chojo/gamejam/server/TeamServer.java index cef76267..aa6b317e 100644 --- a/bot/src/main/java/de/chojo/gamejam/server/TeamServer.java +++ b/bot/src/main/java/de/chojo/gamejam/server/TeamServer.java @@ -6,131 +6,22 @@ package de.chojo.gamejam.server; -import com.fasterxml.jackson.core.JsonProcessingException; -import de.chojo.gamejam.configuration.Configuration; import de.chojo.gamejam.data.dao.guild.jams.jam.teams.Team; -import de.chojo.gamejam.data.dao.guild.jams.jam.user.JamUser; -import de.chojo.gamejam.message.EmbedHelper; -import de.chojo.gamejam.util.Mapper; -import de.chojo.jdautil.localization.util.LocalizedEmbedBuilder; -import de.chojo.jdautil.wrapper.EventContext; -import de.chojo.pluginjam.payload.RequestsPayload; -import de.chojo.pluginjam.payload.StatsPayload; -import net.dv8tion.jda.api.entities.MessageEmbed; import org.slf4j.Logger; -import java.io.File; -import java.io.IOException; -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.nio.file.Files; -import java.nio.file.NoSuchFileException; -import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; -import java.util.Comparator; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; -import java.util.stream.Collectors; import static org.slf4j.LoggerFactory.getLogger; public class TeamServer { - private static final HttpClient http = HttpClient.newHttpClient(); private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH:mm:ss.SSSS"); private static final Logger log = getLogger(TeamServer.class); - private static final int DEFAULT_API_PORT = 30000; - private final DockerService dockerService; private final Team team; - private final Configuration configuration; + private final String containerName; - public TeamServer(DockerService dockerService, Team team, Configuration configuration) { - this.dockerService = dockerService; + public TeamServer(Team team, String containerName) { this.team = team; - this.configuration = configuration; - } - - public boolean isRunning() { - return dockerService.isRunning(team.id()); - } - - public boolean exists() { - return dockerService.exists(team.id()); - } - - /** - * Sets up the server if it doesn't exist yet. - * - * @return true if setup was successful. - * @throws IOException if data could not be written - */ - public boolean setup() throws IOException { - if (exists()) return false; - dockerService.provisionServer(team.id()); - log.info("Setting up server of team {}", team); - return true; - } - - /** - * Delete all the server data. - * - * @return true when server was deleted. - * @throws IOException - */ - public boolean purge() throws IOException { - if (!exists()) return false; - if (isRunning()) { - log.info("Stopping server for team {}", team); - stop(); - } - log.info("Purging server of team {}", team); - dockerService.destroyServer(team.id()); - return true; - } - - public boolean start() { - if (!exists() || isRunning()) return false; - log.info("Starting server server of team {}", team); - dockerService.startServer(team.id()); - return true; - } - - public CompletableFuture restart() { - return CompletableFuture.runAsync(() -> { - dockerService.restartServer(team.id()); - log.info("Starting server of team {}", team); - }); - } - - public CompletableFuture stop() { - return CompletableFuture.runAsync(() -> { - dockerService.stopServer(team.id()); - log.info("Stopping server of team {}", team); - }); - } - - public void send(String command) { - log.info("Sending command \"{}\" to server of team {}.", command, team); - dockerService.sendCommand(team.id(), command); - } - - private File processLogFile(String type) { - var processlog = serverDir().resolve("processlog"); - try { - Files.createDirectories(processlog); - } catch (IOException e) { - throw new RuntimeException(e); - } - return processlog - .resolve("%s_%s.log".formatted(type, FORMATTER.format(LocalDateTime.now()))) - .toFile(); - } - - private Path serverDir() { - return Path.of(configuration.serverManagement().serverDir(), String.valueOf(team().id())); + this.containerName = containerName; } private String teamName() { @@ -144,127 +35,12 @@ public Team team() { @Override public String toString() { return "TeamServer{" + - "team=" + team + + "team=" + team + "," + + "containerName='" + containerName + '}'; } - public String logs() { - - return dockerService.logs(team.id()); - } - - public boolean replaceWorld(Path newWorld) { - log.info("Replacing world"); - dockerService.copyArchiveToContainer(team.id(), newWorld, serverDir()); - return true; - } - - //TODO: replace with docker version - public boolean deleteDirectory(Path path) { - try (var files = Files.walk(path)) { - files.sorted(Comparator.reverseOrder()) - .map(Path::toFile) - .forEach(File::delete); - } catch (NoSuchFileException e) { - return true; - } catch (IOException e) { - log.info("Could not delete directory", e); - return false; - } - return true; - } - - public Path plugins() { - var plugins = serverDir().resolve("plugins"); - try { - Files.createDirectories(plugins); - } catch (IOException e) { - //ignore - } - return plugins; - } - - public Path world() { - var plugins = serverDir().resolve("world"); - try { - Files.createDirectories(plugins); - } catch (IOException e) { - //ignore - } - return plugins; - } - - public String status() { - var status = statusEmoji(); - var ports = ""; - return "%s %s %s".formatted(status, team, ports); - } - - public Optional stats() { - var request = requestBuilder("v1/stats") - .GET() - .build(); - HttpResponse send = null; - try { - send = http().send(request, HttpResponse.BodyHandlers.ofString()); - } catch (IOException e) { - log.error("Could not read stats", e); - return Optional.empty(); - } catch (InterruptedException e) { - log.error("Interrupted", e); - return Optional.empty(); - } - try { - return Optional.of(Mapper.MAPPER.readValue(send.body(), StatsPayload.class)); - } catch (JsonProcessingException e) { - log.error("Could not parse status", e); - return Optional.empty(); - } - } - - public HttpRequest.Builder requestBuilder(String path) { - return HttpRequest.newBuilder(URI.create("http://%s:%d/%s".formatted(containerName(), DEFAULT_API_PORT, path))); - } - - public HttpRequest.Builder requestBuilder(String path, String query) { - return HttpRequest.newBuilder(URI.create("http://%s:%d/%s?%s".formatted(containerName(), DEFAULT_API_PORT, path, query))); - } - public String containerName() { - return dockerService.containerName(team.id()); - } - - public HttpClient http() { - return http; - } - - public String statusEmoji() { - if (exists() && isRunning()) return "🟢"; - if (exists()) return "🟡"; - return "🔴"; - } - - public Optional serverRequests() { - var request = requestBuilder("v1/requests").GET().build(); - HttpResponse send; - try { - send = http().send(request, HttpResponse.BodyHandlers.ofString()); - } catch (IOException e) { - log.error("Could not connect to server"); - throw new RuntimeException(e); - } catch (InterruptedException e) { - log.error("Could not connect to server", e); - throw new RuntimeException(e); - } - try { - return Optional.of(Mapper.MAPPER.readValue(send.body(), RequestsPayload.class)); - } catch (JsonProcessingException e) { - log.error("Could not parse response", e); - throw new RuntimeException(e); - } - } - - public CompletableFuture detailStatus(EventContext context) { - return EmbedHelper.embedDetailedStatus(this, context); + return containerName; } } diff --git a/bot/src/main/java/de/chojo/gamejam/server/UserContext.java b/bot/src/main/java/de/chojo/gamejam/server/UserContext.java new file mode 100644 index 00000000..36696b5c --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/server/UserContext.java @@ -0,0 +1,13 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.server; + +import de.chojo.gamejam.data.dao.guild.jams.jam.teams.Team; + +public record UserContext(Team team) { + +} diff --git a/compose.dev.yml b/compose.dev.yml index a2666a8c..84f2b239 100644 --- a/compose.dev.yml +++ b/compose.dev.yml @@ -43,6 +43,8 @@ services: JVM_DD_OPTS: javalin.port=30000 volumes: - ./data/velocity/config:/config + ports: + - "127.0.0.1:27777:25577" networks: - plugin-jam-network lobby: diff --git a/plugin-api/src/main/java/de/chojo/pluginjam/payload/Registration.java b/plugin-api/src/main/java/de/chojo/pluginjam/payload/Registration.java index bd93cbd6..dcf8328c 100644 --- a/plugin-api/src/main/java/de/chojo/pluginjam/payload/Registration.java +++ b/plugin-api/src/main/java/de/chojo/pluginjam/payload/Registration.java @@ -6,5 +6,5 @@ package de.chojo.pluginjam.payload; -public record Registration(int id, String name, String host, int port, int apiPort) { +public record Registration(int id, String name, String host) { } diff --git a/plugin-paper/src/main/java/de/chojo/pluginjam/PluginJam.java b/plugin-paper/src/main/java/de/chojo/pluginjam/PluginJam.java index 3efa51c8..0500f0bd 100644 --- a/plugin-paper/src/main/java/de/chojo/pluginjam/PluginJam.java +++ b/plugin-paper/src/main/java/de/chojo/pluginjam/PluginJam.java @@ -8,7 +8,6 @@ import de.chojo.pluginjam.api.Api; import de.chojo.pluginjam.greeting.Welcomer; -import de.chojo.pluginjam.serverapi.ServerApi; import de.chojo.pluginjam.service.CommandBlocker; import de.chojo.pluginjam.service.JoinService; import de.chojo.pluginjam.service.ServerRequests; @@ -26,7 +25,6 @@ public class PluginJam extends EldoPlugin implements Listener { private Api api; private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1); private ReportService service; - private ServerApi serverApi; @Override public Level getLogLevel() { @@ -47,7 +45,6 @@ public void onPluginEnable() { api = Api.create(this, serverRequests); service = ReportService.create(this, executor); - serverApi = new ServerApi(this, serverRequests); registerListener(new CommandBlocker(serverRequests, localizer), new Welcomer(this), new JoinService(this, serverRequests, localizer)); } @@ -57,8 +54,4 @@ public void onPluginDisable() { service.shutdown(); executor.shutdown(); } - - public ServerApi api() { - return serverApi; - } } diff --git a/plugin-paper/src/main/java/de/chojo/pluginjam/api/Api.java b/plugin-paper/src/main/java/de/chojo/pluginjam/api/Api.java index 040b93ab..8ca9976d 100644 --- a/plugin-paper/src/main/java/de/chojo/pluginjam/api/Api.java +++ b/plugin-paper/src/main/java/de/chojo/pluginjam/api/Api.java @@ -7,9 +7,9 @@ package de.chojo.pluginjam.api; import de.chojo.pluginjam.PluginJam; -import de.chojo.pluginjam.api.routes.Configuration; -import de.chojo.pluginjam.api.routes.Requests; -import de.chojo.pluginjam.api.routes.Stats; +import de.chojo.pluginjam.api.routes.ConfigurationRoute; +import de.chojo.pluginjam.api.routes.OnlineRoute; +import de.chojo.pluginjam.api.routes.StatsRoute; import de.chojo.pluginjam.service.ServerRequests; import io.javalin.Javalin; import org.bukkit.plugin.Plugin; @@ -21,15 +21,14 @@ public class Api { private static final Logger log = getLogger(Api.class); - private final Configuration configuration; - private final Stats stats; - private final Requests requests; - private Javalin javalin; + private final ConfigurationRoute configurationRoute; + private final StatsRoute statsRoute; + private final OnlineRoute onlineRoute; private Api(Plugin plugin, ServerRequests serverRequests) { - configuration = new Configuration(plugin); - stats = new Stats(plugin); - requests = new Requests(serverRequests); + configurationRoute = new ConfigurationRoute(plugin); + statsRoute = new StatsRoute(plugin); + onlineRoute = new OnlineRoute(); } public static Api create(Plugin plugin, ServerRequests serverRequests) { @@ -41,7 +40,7 @@ public static Api create(Plugin plugin, ServerRequests serverRequests) { private void ignite() { var classLoader = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(PluginJam.class.getClassLoader()); - javalin = Javalin.create(config -> { + Javalin javalin = Javalin.create(config -> { config.concurrency.useVirtualThreads = true; config.routes.apiBuilder(this::routes); }); @@ -51,8 +50,8 @@ private void ignite() { private void routes() { before(ctx -> log.debug("Received request on {}.", ctx.path())); - path("v1", configuration::buildRoutes); - path("v1", stats::buildRoutes); - path("v1", requests::buildRoutes); + path("v1", configurationRoute::buildRoutes); + path("v1", statsRoute::buildRoutes); + path("v1", onlineRoute::buildRoutes); } } diff --git a/plugin-paper/src/main/java/de/chojo/pluginjam/api/routes/Configuration.java b/plugin-paper/src/main/java/de/chojo/pluginjam/api/routes/ConfigurationRoute.java similarity index 95% rename from plugin-paper/src/main/java/de/chojo/pluginjam/api/routes/Configuration.java rename to plugin-paper/src/main/java/de/chojo/pluginjam/api/routes/ConfigurationRoute.java index 526b5f82..2e707afc 100644 --- a/plugin-paper/src/main/java/de/chojo/pluginjam/api/routes/Configuration.java +++ b/plugin-paper/src/main/java/de/chojo/pluginjam/api/routes/ConfigurationRoute.java @@ -12,10 +12,10 @@ import static io.javalin.apibuilder.ApiBuilder.path; import static io.javalin.apibuilder.ApiBuilder.post; -public class Configuration { +public class ConfigurationRoute { private final Plugin plugin; - public Configuration(Plugin plugin) { + public ConfigurationRoute(Plugin plugin) { this.plugin = plugin; } diff --git a/plugin-paper/src/main/java/de/chojo/pluginjam/api/routes/Requests.java b/plugin-paper/src/main/java/de/chojo/pluginjam/api/routes/OnlineRoute.java similarity index 53% rename from plugin-paper/src/main/java/de/chojo/pluginjam/api/routes/Requests.java rename to plugin-paper/src/main/java/de/chojo/pluginjam/api/routes/OnlineRoute.java index cb8b60df..ab68e87d 100644 --- a/plugin-paper/src/main/java/de/chojo/pluginjam/api/routes/Requests.java +++ b/plugin-paper/src/main/java/de/chojo/pluginjam/api/routes/OnlineRoute.java @@ -6,23 +6,15 @@ package de.chojo.pluginjam.api.routes; -import de.chojo.pluginjam.service.ServerRequests; -import org.bukkit.plugin.Plugin; - import static io.javalin.apibuilder.ApiBuilder.get; import static io.javalin.apibuilder.ApiBuilder.path; -public class Requests { - private final ServerRequests requests; - - public Requests(ServerRequests requests) { - this.requests = requests; - } +public class OnlineRoute { public void buildRoutes() { - path("requests", () -> { + path("online", () -> { get("", ctx -> { - ctx.json(requests.get()); + ctx.json("online"); }); }); } diff --git a/plugin-paper/src/main/java/de/chojo/pluginjam/api/routes/Stats.java b/plugin-paper/src/main/java/de/chojo/pluginjam/api/routes/StatsRoute.java similarity index 95% rename from plugin-paper/src/main/java/de/chojo/pluginjam/api/routes/Stats.java rename to plugin-paper/src/main/java/de/chojo/pluginjam/api/routes/StatsRoute.java index 1546798f..4a6cdec8 100644 --- a/plugin-paper/src/main/java/de/chojo/pluginjam/api/routes/Stats.java +++ b/plugin-paper/src/main/java/de/chojo/pluginjam/api/routes/StatsRoute.java @@ -12,11 +12,11 @@ import static io.javalin.apibuilder.ApiBuilder.get; import static io.javalin.apibuilder.ApiBuilder.path; -public class Stats { +public class StatsRoute { private static final int MB = 1024 * 1024; private final Plugin plugin; - public Stats(Plugin plugin) { + public StatsRoute(Plugin plugin) { this.plugin = plugin; } diff --git a/plugin-paper/src/main/java/de/chojo/pluginjam/serverapi/ServerApi.java b/plugin-paper/src/main/java/de/chojo/pluginjam/serverapi/ServerApi.java deleted file mode 100644 index 781de212..00000000 --- a/plugin-paper/src/main/java/de/chojo/pluginjam/serverapi/ServerApi.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.pluginjam.serverapi; - -import de.chojo.pluginjam.service.ServerRequests; -import org.bukkit.plugin.Plugin; - -public class ServerApi { - private final Plugin plugin; - private final ServerRequests requests; - - public ServerApi(Plugin plugin, ServerRequests requests) { - this.plugin = plugin; - this.requests = requests; - } - - /** - * Requests a restart of the server - */ - public void requestRestart() { - requests.restartByUserOrServer(true); - } - - /** - * Sets the join message of this server - * @param message - */ - public void setMessage(String message) { - plugin.getConfig().set("message", message); - plugin.saveConfig(); - } -} diff --git a/plugin-paper/src/main/java/de/chojo/pluginjam/velocity/ReportService.java b/plugin-paper/src/main/java/de/chojo/pluginjam/velocity/ReportService.java index 63da7a7e..a485f4a0 100644 --- a/plugin-paper/src/main/java/de/chojo/pluginjam/velocity/ReportService.java +++ b/plugin-paper/src/main/java/de/chojo/pluginjam/velocity/ReportService.java @@ -58,7 +58,7 @@ public void run() { private void register() { log.info("Registering server at velocity instance"); - var registration = new Registration(id, name, host, plugin.getServer().getPort(), apiPort); + var registration = new Registration(id, name, host); var builder = HttpRequest.newBuilder(apiUrl("v1", "server")) .POST(HttpRequest.BodyPublishers.ofString(gson.toJson(registration))) .build(); @@ -74,7 +74,7 @@ private void register() { } private void ping() { - var registration = new Registration(id, name, host, plugin.getServer().getPort(), apiPort); + var registration = new Registration(id, name, host); var builder = HttpRequest.newBuilder(apiUrl("v1", "server")) .method("PATCH", HttpRequest.BodyPublishers.ofString(gson.toJson(registration))) .build(); diff --git a/plugin-velocity/src/main/java/de/chojo/pluginjam/servers/ServerRegistry.java b/plugin-velocity/src/main/java/de/chojo/pluginjam/servers/ServerRegistry.java index 0166d86b..c6f5efae 100644 --- a/plugin-velocity/src/main/java/de/chojo/pluginjam/servers/ServerRegistry.java +++ b/plugin-velocity/src/main/java/de/chojo/pluginjam/servers/ServerRegistry.java @@ -26,7 +26,6 @@ public class ServerRegistry implements Runnable { private static final Logger log = getLogger(ServerRegistry.class); private final Map ids = new HashMap<>(); - private final Map ports = new HashMap<>(); private final Map seen = new HashMap<>(); private final ProxyServer proxy; @@ -46,7 +45,7 @@ public void run() { var timeout = Instant.now().minus(2, ChronoUnit.MINUTES); for (var entry : new HashMap<>(seen).entrySet()) { if (entry.getValue().isBefore(timeout)) { - log.info("Server {}:{} timed out.", entry.getKey().name(), entry.getKey().port()); + log.info("Server {} timed out.", entry.getKey().name()); unregister(entry.getKey()); } } @@ -59,25 +58,18 @@ public void register(Registration registration) { throw AlreadyRegisteredException.forName(registration.name()); } - reg = ports.get(registration.port()); - - if (reg != null && !reg.equals(registration)) { - throw AlreadyRegisteredException.forPort(registration.port()); - } - - log.info("Registered server {} on port {}", registration.name(), registration.port()); + log.info("Registered server {}", registration.name()); ids.put(registration.id(), registration); - ports.put(registration.port(), registration); - proxy.registerServer(new ServerInfo(registration.name(), new InetSocketAddress(registration.host(), registration.port()))); + proxy.registerServer(new ServerInfo(registration.name(), new InetSocketAddress(registration.host(), 25565))); ping(registration); } public void ping(Registration registration) { log.debug("Ping of server {} received.", registration.name()); seen.put(registration, Instant.now()); - if (!ids.containsKey(registration.id()) && !ports.containsKey(registration.port())) { + if (!ids.containsKey(registration.id())) { log.info("Received ping of unknown server {} with id {}", registration.id(), registration.name()); log.info("Attempting to register server."); register(registration); @@ -87,13 +79,12 @@ public void ping(Registration registration) { public void unregister(Registration registration) { var removed = ids.remove(registration.id()); if (removed == null) { - log.warn("Unregistered server {} from port {}, but this server is not known.", registration.id(), registration.port()); + log.warn("Unregistered server {}, but this server is not known.", registration.id()); return; } - ports.remove(removed.port()); seen.remove(removed); - log.info("Unregistered server {} on port {}", removed.name(), removed.port()); - proxy.unregisterServer(new ServerInfo(removed.name(), new InetSocketAddress("localhost", removed.port()))); + log.info("Unregistered server {}", removed.name()); + proxy.unregisterServer(new ServerInfo(removed.name(), new InetSocketAddress("localhost", 25565))); } public Collection server() { diff --git a/plugin-velocity/src/main/java/de/chojo/pluginjam/web/server/Server.java b/plugin-velocity/src/main/java/de/chojo/pluginjam/web/server/Server.java index 40e7d6f5..d96d90bc 100644 --- a/plugin-velocity/src/main/java/de/chojo/pluginjam/web/server/Server.java +++ b/plugin-velocity/src/main/java/de/chojo/pluginjam/web/server/Server.java @@ -37,9 +37,8 @@ public void buildRoutes() { delete("", ctx -> { var id = ctx.queryParam("id"); - var port = ctx.queryParam("port"); var host = ctx.queryParam("host"); - var registration = new Registration(Integer.parseInt(id), "", host, Integer.parseInt(port), 0); + var registration = new Registration(Integer.parseInt(id), "", host); registry.unregister(registration); ctx.status(HttpStatus.ACCEPTED); }); From 3b391958578a13cf928a2e65de2c9d9314077b48 Mon Sep 17 00:00:00 2001 From: TheZexquex Date: Thu, 11 Jun 2026 01:41:12 +0200 Subject: [PATCH 7/8] Start rewriting everything lol. Backend with micronaut and refactoring database entities --- .gitignore | 3 +- Dockerfile.bot | 2 +- Dockerfile.bot.dev | 4 +- Dockerfile.mc | 2 +- Dockerfile.velocity | 2 +- README.md | 2 +- backend/.gitignore | 15 + backend/README.md | 49 ++++ backend/build.gradle.kts | 114 ++++++++ backend/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 48462 bytes .../gradle/wrapper/gradle-wrapper.properties | 9 + backend/gradlew | 248 ++++++++++++++++ backend/gradlew.bat | 82 ++++++ backend/micronaut-cli.yml | 6 + backend/settings.gradle.kts | 2 + .../java/de/chojo/pluginjam/Application.java | 22 ++ .../main/java/de/chojo/pluginjam/bot/Bot.java | 119 ++++++++ .../de/chojo/pluginjam/bot/BotModule.java | 23 ++ .../bot/commands/CommandContextProvider.java | 29 ++ .../commands/jamadmin/ChangeVotesCommand.java | 22 +- .../commands/jamadmin/JamCreateCommand.java | 39 ++- .../bot}/commands/jamadmin/JamEndCommand.java | 26 +- .../bot/commands/jamadmin/JamListCommand.java | 53 ++++ .../commands/jamadmin/JamStartCommand.java | 26 +- .../commands/register/RegisterCommand.java | 76 +++++ .../commands/register/UnregisterCommand.java | 59 ++++ .../settings/SettingsInfoCommand.java | 39 +++ .../settings/SettingsRoleOrgaCommand.java | 20 +- .../SettingsRoleParticipantCommand.java | 35 +++ .../settings/SettingsTeamSizeCommand.java | 18 +- .../bot/commands/team/TeamCreateCommand.java | 119 ++++++++ .../bot/commands/team/TeamDisbandCommand.java | 72 +++++ .../bot/listener/InviteButtonListener.java | 103 +++++++ .../pluginjam/bot/message/EmbedHelper.java | 90 ++++++ .../bot/message/StringValidator.java | 10 + .../chojo/pluginjam/bot/util/MentionUtil.java | 12 + .../pluginjam/controller/TeamController.java | 8 + .../database/entity/SettingsEntity.java | 85 ++++++ .../pluginjam/database/entity/VoteEntity.java | 18 ++ .../pluginjam/database/entity/jam/Jam.java | 25 ++ .../database/entity/jam/JamMeta.java | 17 ++ .../database/entity/jam/JamRegistration.java | 17 ++ .../database/entity/jam/JamState.java | 18 ++ .../database/entity/jam/JamTime.java | 27 ++ .../pluginjam/database/entity/team/Team.java | 21 ++ .../database/entity/team/TeamMember.java | 17 ++ .../database/entity/team/TeamMeta.java | 30 ++ .../repository/SettingsRepository.java | 22 ++ .../repository/TeamMemberRepository.java | 13 + .../repository/TeamMetaRepository.java | 10 + .../database/repository/TeamRepository.java | 20 ++ .../database/repository/VotesRepository.java | 22 ++ .../jam/JamRegistrationRepository.java | 22 ++ .../repository/jam/JamRepository.java | 76 +++++ .../BotApiKeyAuthenticationFetcher.java | 34 +++ .../chojo/pluginjam/service/JamService.java | 93 ++++++ .../pluginjam/service/SettingsService.java | 40 +++ .../chojo/pluginjam/service/TeamService.java | 73 +++++ .../src/main/resources/application-dev.yml | 7 + backend/src/main/resources/application.yml | 46 +++ .../migration/V1__create_initial_tables.sql | 143 ++++++++++ backend/src/main/resources/locale/de.ftl | 264 ++++++++++++++++++ backend/src/main/resources/locale/en.ftl | 264 ++++++++++++++++++ backend/src/main/resources/logback.xml | 16 ++ .../java/de/chojo/pluginjam/BackendTest.java | 22 ++ .../commands/register/RegisterCommand.java | 69 ----- .../commands/register/UnregisterCommand.java | 54 ---- .../commands/settings/InfoCommand.java | 47 ---- .../de/chojo/gamejam/data/access/Teams.java | 4 +- .../de/chojo/gamejam/data/dao/guild/Jams.java | 16 +- .../gamejam/data/dao/guild/Settings.java | 2 +- .../data/dao/guild/jams/jam/JamTeams.java | 26 +- .../resources/database/postgresql/1/setup.sql | 16 +- bot/src/main/resources/locale.properties | 46 +-- bot/src/main/resources/locale_de.properties | 46 +-- .../main/resources/locale_en_US.properties | 82 +++--- compose.dev.yml | 6 +- 77 files changed, 3058 insertions(+), 378 deletions(-) create mode 100644 backend/.gitignore create mode 100644 backend/README.md create mode 100644 backend/build.gradle.kts create mode 100644 backend/gradle/wrapper/gradle-wrapper.jar create mode 100644 backend/gradle/wrapper/gradle-wrapper.properties create mode 100755 backend/gradlew create mode 100644 backend/gradlew.bat create mode 100644 backend/micronaut-cli.yml create mode 100644 backend/settings.gradle.kts create mode 100644 backend/src/main/java/de/chojo/pluginjam/Application.java create mode 100644 backend/src/main/java/de/chojo/pluginjam/bot/Bot.java create mode 100644 backend/src/main/java/de/chojo/pluginjam/bot/BotModule.java create mode 100644 backend/src/main/java/de/chojo/pluginjam/bot/commands/CommandContextProvider.java rename {bot/src/main/java/de/chojo/gamejam => backend/src/main/java/de/chojo/pluginjam/bot}/commands/jamadmin/ChangeVotesCommand.java (55%) rename {bot/src/main/java/de/chojo/gamejam => backend/src/main/java/de/chojo/pluginjam/bot}/commands/jamadmin/JamCreateCommand.java (70%) rename {bot/src/main/java/de/chojo/gamejam => backend/src/main/java/de/chojo/pluginjam/bot}/commands/jamadmin/JamEndCommand.java (53%) create mode 100644 backend/src/main/java/de/chojo/pluginjam/bot/commands/jamadmin/JamListCommand.java rename {bot/src/main/java/de/chojo/gamejam => backend/src/main/java/de/chojo/pluginjam/bot}/commands/jamadmin/JamStartCommand.java (51%) create mode 100644 backend/src/main/java/de/chojo/pluginjam/bot/commands/register/RegisterCommand.java create mode 100644 backend/src/main/java/de/chojo/pluginjam/bot/commands/register/UnregisterCommand.java create mode 100644 backend/src/main/java/de/chojo/pluginjam/bot/commands/settings/SettingsInfoCommand.java rename bot/src/main/java/de/chojo/gamejam/commands/settings/JamRoleCommand.java => backend/src/main/java/de/chojo/pluginjam/bot/commands/settings/SettingsRoleOrgaCommand.java (56%) create mode 100644 backend/src/main/java/de/chojo/pluginjam/bot/commands/settings/SettingsRoleParticipantCommand.java rename bot/src/main/java/de/chojo/gamejam/commands/settings/TeamSizeCommand.java => backend/src/main/java/de/chojo/pluginjam/bot/commands/settings/SettingsTeamSizeCommand.java (54%) create mode 100644 backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamCreateCommand.java create mode 100644 backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamDisbandCommand.java create mode 100644 backend/src/main/java/de/chojo/pluginjam/bot/listener/InviteButtonListener.java create mode 100644 backend/src/main/java/de/chojo/pluginjam/bot/message/EmbedHelper.java create mode 100644 backend/src/main/java/de/chojo/pluginjam/bot/message/StringValidator.java create mode 100644 backend/src/main/java/de/chojo/pluginjam/bot/util/MentionUtil.java create mode 100644 backend/src/main/java/de/chojo/pluginjam/controller/TeamController.java create mode 100644 backend/src/main/java/de/chojo/pluginjam/database/entity/SettingsEntity.java create mode 100644 backend/src/main/java/de/chojo/pluginjam/database/entity/VoteEntity.java create mode 100644 backend/src/main/java/de/chojo/pluginjam/database/entity/jam/Jam.java create mode 100644 backend/src/main/java/de/chojo/pluginjam/database/entity/jam/JamMeta.java create mode 100644 backend/src/main/java/de/chojo/pluginjam/database/entity/jam/JamRegistration.java create mode 100644 backend/src/main/java/de/chojo/pluginjam/database/entity/jam/JamState.java create mode 100644 backend/src/main/java/de/chojo/pluginjam/database/entity/jam/JamTime.java create mode 100644 backend/src/main/java/de/chojo/pluginjam/database/entity/team/Team.java create mode 100644 backend/src/main/java/de/chojo/pluginjam/database/entity/team/TeamMember.java create mode 100644 backend/src/main/java/de/chojo/pluginjam/database/entity/team/TeamMeta.java create mode 100644 backend/src/main/java/de/chojo/pluginjam/database/repository/SettingsRepository.java create mode 100644 backend/src/main/java/de/chojo/pluginjam/database/repository/TeamMemberRepository.java create mode 100644 backend/src/main/java/de/chojo/pluginjam/database/repository/TeamMetaRepository.java create mode 100644 backend/src/main/java/de/chojo/pluginjam/database/repository/TeamRepository.java create mode 100644 backend/src/main/java/de/chojo/pluginjam/database/repository/VotesRepository.java create mode 100644 backend/src/main/java/de/chojo/pluginjam/database/repository/jam/JamRegistrationRepository.java create mode 100644 backend/src/main/java/de/chojo/pluginjam/database/repository/jam/JamRepository.java create mode 100644 backend/src/main/java/de/chojo/pluginjam/security/BotApiKeyAuthenticationFetcher.java create mode 100644 backend/src/main/java/de/chojo/pluginjam/service/JamService.java create mode 100644 backend/src/main/java/de/chojo/pluginjam/service/SettingsService.java create mode 100644 backend/src/main/java/de/chojo/pluginjam/service/TeamService.java create mode 100644 backend/src/main/resources/application-dev.yml create mode 100644 backend/src/main/resources/application.yml create mode 100644 backend/src/main/resources/db/migration/V1__create_initial_tables.sql create mode 100644 backend/src/main/resources/locale/de.ftl create mode 100644 backend/src/main/resources/locale/en.ftl create mode 100644 backend/src/main/resources/logback.xml create mode 100644 backend/src/test/java/de/chojo/pluginjam/BackendTest.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/register/RegisterCommand.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/register/UnregisterCommand.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/settings/InfoCommand.java diff --git a/.gitignore b/.gitignore index 42d49731..57cf3863 100644 --- a/.gitignore +++ b/.gitignore @@ -44,5 +44,6 @@ buildNumber.properties /wait.sh /conf/ .direnv -data +data/ run +/.env diff --git a/Dockerfile.bot b/Dockerfile.bot index 35e9a228..3a04edbc 100644 --- a/Dockerfile.bot +++ b/Dockerfile.bot @@ -19,7 +19,7 @@ RUN mkdir servers RUN mkdir template RUN mkdir template/plugins COPY docker/resources/bot/wait.sh . -# Copy the plugin jam plugin into the template. +# Copy the plugin jamEntity plugin into the template. COPY --from=build /home/gradle/plugin-paper/build/libs/plugin-paper-*-all.jar ./bot/template/plugins/pluginjam.jar COPY docker/resources/docker-entrypoint.sh . diff --git a/Dockerfile.bot.dev b/Dockerfile.bot.dev index 4b25a086..40a240c5 100644 --- a/Dockerfile.bot.dev +++ b/Dockerfile.bot.dev @@ -1,7 +1,7 @@ FROM gradle:jdk25-alpine as build WORKDIR /home/gradle -COPY --chown=gradle:gradle settings.gradle* build.gradle* gradle.properties* ./ +COPY --chown=gradle:gradle settingsEntity.gradle* build.gradle* gradle.properties* ./ COPY --chown=gradle:gradle bot/build.gradle* ./bot/ COPY --chown=gradle:gradle plugin-paper/build.gradle* ./plugin-paper/ @@ -22,7 +22,7 @@ RUN mkdir servers RUN mkdir template RUN mkdir template/plugins COPY docker/resources/bot/wait.sh . -# Copy the plugin jam plugin into the template. +# Copy the plugin jamEntity plugin into the template. COPY --from=build /home/gradle/plugin-paper/build/libs/plugin-paper-*-all.jar ./bot/template/plugins/pluginjam.jar COPY docker/resources/docker-entrypoint.sh . diff --git a/Dockerfile.mc b/Dockerfile.mc index 5c059088..bcbde324 100644 --- a/Dockerfile.mc +++ b/Dockerfile.mc @@ -1,6 +1,6 @@ FROM gradle:jdk25-alpine AS build WORKDIR /home/gradle -COPY --chown=gradle:gradle settings.gradle* build.gradle* gradle.properties* ./ +COPY --chown=gradle:gradle settingsEntity.gradle* build.gradle* gradle.properties* ./ COPY --chown=gradle:gradle bot/build.gradle* ./bot/ COPY --chown=gradle:gradle plugin-api/build.gradle* ./plugin-api/ COPY --chown=gradle:gradle plugin-paper/build.gradle* ./plugin-paper/ diff --git a/Dockerfile.velocity b/Dockerfile.velocity index c87e9920..3c56ccfd 100644 --- a/Dockerfile.velocity +++ b/Dockerfile.velocity @@ -1,6 +1,6 @@ FROM gradle:jdk25-alpine AS build WORKDIR /home/gradle -COPY --chown=gradle:gradle settings.gradle* build.gradle* gradle.properties* ./ +COPY --chown=gradle:gradle settingsEntity.gradle* build.gradle* gradle.properties* ./ COPY --chown=gradle:gradle bot/build.gradle* ./bot/ COPY --chown=gradle:gradle plugin-api/build.gradle* ./plugin-api/ COPY --chown=gradle:gradle plugin-paper/build.gradle* ./plugin-paper/ diff --git a/README.md b/README.md index 01f6b695..561881a2 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -Bot and spigot plugin used for the dev cord plugin jam +Bot and spigot plugin used for the dev cord plugin jamEntity ## Start Arguments for paper `-Dpluginjam.port` diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 00000000..7615dda8 --- /dev/null +++ b/backend/.gitignore @@ -0,0 +1,15 @@ +Thumbs.db +.DS_Store +.gradle +build/ +target/ +out/ +.micronaut/ +.idea +*.iml +*.ipr +*.iws +.project +.settingsEntity +.classpath +.factorypath diff --git a/backend/README.md b/backend/README.md new file mode 100644 index 00000000..7b0161bd --- /dev/null +++ b/backend/README.md @@ -0,0 +1,49 @@ +## Micronaut 5.0.2 Documentation + +- [User Guide](https://docs.micronaut.io/5.0.2/guide/index.html) +- [API Reference](https://docs.micronaut.io/5.0.2/api/index.html) +- [Configuration Reference](https://docs.micronaut.io/5.0.2/guide/configurationreference.html) +- [Micronaut Guides](https://guides.micronaut.io/index.html) + +--- + +- [Micronaut Gradle Plugin documentation](https://micronaut-projects.github.io/micronaut-gradle-plugin/latest/) +- [GraalVM Gradle Plugin documentation](https://graalvm.github.io/native-build-tools/latest/gradle-plugin.html) +- [Shadow Gradle Plugin](https://gradleup.com/shadow/) + +## Feature http-client documentation + +- [Micronaut HTTP Client documentation](https://docs.micronaut.io/latest/guide/index.html#nettyHttpClient) + +## Feature flyway documentation + +- [Micronaut Flyway Database Migration documentation](https://micronaut-projects.github.io/micronaut-flyway/latest/guide/index.html) + + +- [https://flywaydb.org/](https://flywaydb.org/) + +## Feature serialization-jackson documentation + +- [Micronaut Serialization Jackson Core documentation](https://micronaut-projects.github.io/micronaut-serialization/latest/guide/) + +## Feature jdbc-hikari documentation + +- [Micronaut Hikari JDBC Connection Pool documentation](https://micronaut-projects.github.io/micronaut-sql/latest/guide/index.html#jdbc) + +## Feature security-oauth2 documentation + +- [Micronaut Security OAuth 2.0 documentation](https://micronaut-projects.github.io/micronaut-security/latest/guide/index.html#oauth) + +## Feature test-resources documentation + +- [Micronaut Test Resources documentation](https://micronaut-projects.github.io/micronaut-test-resources/latest/guide/) + +## Feature data-jdbc documentation + +- [Micronaut Data JDBC documentation](https://micronaut-projects.github.io/micronaut-data/latest/guide/index.html#jdbc) + +## Feature micronaut-aot documentation + +- [Micronaut AOT documentation](https://micronaut-projects.github.io/micronaut-aot/latest/guide/) + + diff --git a/backend/build.gradle.kts b/backend/build.gradle.kts new file mode 100644 index 00000000..da36e94b --- /dev/null +++ b/backend/build.gradle.kts @@ -0,0 +1,114 @@ +plugins { + id("io.micronaut.application") version "5.0.0" + id("com.gradleup.shadow") version "9.4.1" + id("io.micronaut.test-resources") version "5.0.0" + id("io.micronaut.aot") version "5.0.0" +} + +version = "0.1" +group = "de.chojo.pluginjam" + + + +repositories { + mavenCentral() +} + +dependencies { + annotationProcessor("io.micronaut.data:micronaut-data-processor") + annotationProcessor("io.micronaut:micronaut-http-validation") + annotationProcessor("io.micronaut.security:micronaut-security-processor") + annotationProcessor("io.micronaut.serde:micronaut-serde-processor") + implementation("io.micronaut:micronaut-http-client") + implementation("io.micronaut.data:micronaut-data-jdbc") + implementation("io.micronaut.flyway:micronaut-flyway") + implementation("io.micronaut.security:micronaut-security-oauth2") + implementation("io.micronaut.serde:micronaut-serde-jackson") + implementation("io.micronaut.sql:micronaut-jdbc-hikari") + implementation("io.micronaut.reactor:micronaut-reactor") + + // discord + implementation("io.github.kaktushose:jda-commands:5.0.0") { + exclude("org.slf4j", "slf4j-simple") + } + implementation("net.dv8tion:JDA:6.4.1") + + runtimeOnly("ch.qos.logback:logback-classic") + runtimeOnly("org.flywaydb:flyway-database-postgresql") + runtimeOnly("org.postgresql:postgresql") + runtimeOnly("org.yaml:snakeyaml") + testRuntimeOnly("org.junit.platform:junit-platform-launcher") + aotPlugins(platform("io.micronaut.platform:micronaut-platform:5.0.2")) + aotPlugins("io.micronaut.security:micronaut-security-aot") +} + + + +application { + mainClass = "de.chojo.pluginjam.Application" +} + +java { + sourceCompatibility = JavaVersion.toVersion("25") + targetCompatibility = JavaVersion.toVersion("25") +} + + + + +graalvmNative.toolchainDetection = false +graalvmNative { + binaries { + all { + buildArgs.add("-H:+SharedArenaSupport") + } + } +} + + + + +micronaut { + runtime("netty") + testRuntime("junit5") + processing { + incremental(true) + annotations("de.chojo.pluginjam.*") + } + testResources { + version = "4.0.0" + } + aot { + // Please review carefully the optimizations enabled below + // Check https://micronaut-projects.github.io/micronaut-aot/latest/guide/ for more details + optimizeServiceLoading = false + convertYamlToJava = false + precomputeOperations = true + cacheEnvironment = true + optimizeClassLoading = true + deduceEnvironment = true + optimizeNetty = true + replaceLogbackXml = true + configurationProperties.put("micronaut.security.jwks.enabled", "false") + configurationProperties.put("micronaut.security.openid-configuration.enabled", "false") + } + +} +tasks.withType().configureEach { + useClassDataSharing.set(false) +} + +tasks.named("dockerfile") { + + baseImage = "eclipse-temurin:25-jre" +} + + +// https://docs.gradle.org/current/userguide/upgrading_major_version_9.html#test_task_fails_when_no_tests_are_discovered +tasks.withType().configureEach { + failOnNoDiscoveredTests = false +} + + + + diff --git a/backend/gradle/wrapper/gradle-wrapper.jar b/backend/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..b1b8ef56b44f16b14dc800fa8103a6d89abb526f GIT binary patch literal 48462 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 R2np~f1o-$>Q}y+){|A@R9n$~+ literal 0 HcmV?d00001 diff --git a/backend/gradle/wrapper/gradle-wrapper.properties b/backend/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..df6a6ad7 --- /dev/null +++ b/backend/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,9 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip +networkTimeout=10000 +retries=0 +retryBackOffMs=500 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/backend/gradlew b/backend/gradlew new file mode 100755 index 00000000..b9bb139f --- /dev/null +++ b/backend/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/backend/gradlew.bat b/backend/gradlew.bat new file mode 100644 index 00000000..24c62d56 --- /dev/null +++ b/backend/gradlew.bat @@ -0,0 +1,82 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +"%COMSPEC%" /c exit 1 + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +"%COMSPEC%" /c exit 1 + +:execute +@rem Setup the command line + + + +@rem Execute Gradle +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel + +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% diff --git a/backend/micronaut-cli.yml b/backend/micronaut-cli.yml new file mode 100644 index 00000000..d9e6fda1 --- /dev/null +++ b/backend/micronaut-cli.yml @@ -0,0 +1,6 @@ +applicationType: default +defaultPackage: de.chojo.pluginjam +testFramework: junit +sourceLanguage: java +buildTool: gradle_kotlin +features: [ app-name, data, data-jdbc, flyway, gradle, http-client, http-client-test, java, java-application, jdbc-hikari, junit, logback, micronaut-aot, micronaut-build, micronaut-configuration-validation-gradle-plugin, micronaut-http-validation, netty-server, postgres, readme, security-oauth2, security-processor, serialization-jackson, shade, static-resources, test-resources, yaml, yaml-build ] diff --git a/backend/settings.gradle.kts b/backend/settings.gradle.kts new file mode 100644 index 00000000..96294e18 --- /dev/null +++ b/backend/settings.gradle.kts @@ -0,0 +1,2 @@ +rootProject.name = "backend" + diff --git a/backend/src/main/java/de/chojo/pluginjam/Application.java b/backend/src/main/java/de/chojo/pluginjam/Application.java new file mode 100644 index 00000000..5a94f868 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/Application.java @@ -0,0 +1,22 @@ +package de.chojo.pluginjam; + +import io.micronaut.core.annotation.NonNull; +import io.micronaut.context.ApplicationContextBuilder; +import io.micronaut.context.ApplicationContextConfigurer; +import io.micronaut.context.annotation.ContextConfigurer; +import io.micronaut.runtime.Micronaut; + +public class Application { + + @ContextConfigurer + public static class Configurer implements ApplicationContextConfigurer { + @Override + public void configure(@NonNull ApplicationContextBuilder builder) { + builder.defaultEnvironments("dev"); + } + } + + public static void main(String[] args) { + Micronaut.run(Application.class, args); + } +} \ No newline at end of file diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/Bot.java b/backend/src/main/java/de/chojo/pluginjam/bot/Bot.java new file mode 100644 index 00000000..97953a96 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/bot/Bot.java @@ -0,0 +1,119 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.pluginjam.bot; + +import com.google.inject.Guice; +import de.chojo.pluginjam.bot.commands.CommandContextProvider; +import de.chojo.pluginjam.database.repository.SettingsRepository; +import de.chojo.pluginjam.service.JamService; +import de.chojo.pluginjam.service.SettingsService; +import de.chojo.pluginjam.service.TeamService; +import io.github.kaktushose.jdac.JDACommands; +import io.github.kaktushose.jdac.annotations.interactions.CommandConfig; +import io.github.kaktushose.jdac.annotations.interactions.CommandScope; +import io.github.kaktushose.jdac.definitions.interactions.command.CommandDefinition; +import io.github.kaktushose.jdac.guice.GuiceExtensionData; +import io.github.kaktushose.jdac.scope.GuildScopeProvider; +import io.micronaut.context.annotation.Value; +import io.micronaut.context.event.ApplicationEventListener; +import io.micronaut.runtime.event.ApplicationStartupEvent; +import jakarta.inject.Singleton; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.interactions.commands.build.CommandData; +import net.dv8tion.jda.api.requests.GatewayIntent; +import net.dv8tion.jda.api.requests.RestAction; +import net.dv8tion.jda.api.sharding.DefaultShardManagerBuilder; +import net.dv8tion.jda.api.sharding.ShardManager; +import net.dv8tion.jda.api.utils.MemberCachePolicy; +import org.jspecify.annotations.NonNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Set; +import java.util.stream.Collectors; + +@Singleton +public class Bot implements ApplicationEventListener { + private static final Logger LOG = LoggerFactory.getLogger(Bot.class); + private final JamService jamService; + private final SettingsService settingsService; + private final TeamService teamService; + private final String token; + + private ShardManager shardManager; + + public Bot(JamService jamService, SettingsService settingsService, TeamService teamService, @Value("${bot.token}") String token) { + this.jamService = jamService; + this.settingsService = settingsService; + this.teamService = teamService; + this.token = token; + } + + @Override + public void onApplicationEvent(ApplicationStartupEvent event) { + try { + start(); + } catch (Exception e) { + LOG.error("Failed to start bot", e); + throw new RuntimeException(e); + } + } + + public void start() { + + LOG.info("Initializing Shard Manager"); + initBot(); + + LOG.info("Initializing Commands"); + buildCommands(); + + buildShutdownHook(); + } + + private void buildShutdownHook() { + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + LOG.info("Received SIGTERM. Shutdown hook activated."); + shardManager.shutdown(); + })); + } + + private void buildCommands() { + var userContextProvider = new CommandContextProvider( + jamService, + settingsService, + teamService + ); + var injector = Guice.createInjector(new BotModule(userContextProvider)); + JDACommands.builder(shardManager) + .extensionData(new GuiceExtensionData(injector)) + .globalCommandConfig(CommandDefinition.CommandConfig.of(builder -> { + builder.scope(CommandScope.GUILD); + })) + .guildScopeProvider(new GuildScopeProvider() { + @Override + public @NonNull Set apply(@NonNull CommandData commandData) { + return shardManager.getGuilds().stream() + .map(Guild::getIdLong) + .collect(Collectors.toSet()); + } + }) + .start(); + //shardManager.addEventListener(new InviteButtonListener(guilds)); + } + + private void initBot() { + shardManager = DefaultShardManagerBuilder.createDefault(token) + .setEnableShutdownHook(false) + .enableIntents( + GatewayIntent.GUILD_MEMBERS, + GatewayIntent.DIRECT_MESSAGES, + GatewayIntent.GUILD_MESSAGES) + .setMemberCachePolicy(MemberCachePolicy.DEFAULT) + .build(); + RestAction.setDefaultFailure(throwable -> LOG.error("Unhandled exception occurred: ", throwable)); + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/BotModule.java b/backend/src/main/java/de/chojo/pluginjam/bot/BotModule.java new file mode 100644 index 00000000..13d32ad1 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/bot/BotModule.java @@ -0,0 +1,23 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.pluginjam.bot; + +import com.google.inject.AbstractModule; +import de.chojo.pluginjam.bot.commands.CommandContextProvider; + +public class BotModule extends AbstractModule { + private final CommandContextProvider commandContextProvider; + + public BotModule(CommandContextProvider commandContextProvider) { + this.commandContextProvider = commandContextProvider; + } + + @Override + protected void configure() { + bind(CommandContextProvider.class).toInstance(commandContextProvider); + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/commands/CommandContextProvider.java b/backend/src/main/java/de/chojo/pluginjam/bot/commands/CommandContextProvider.java new file mode 100644 index 00000000..27a610c6 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/bot/commands/CommandContextProvider.java @@ -0,0 +1,29 @@ +package de.chojo.pluginjam.bot.commands; + +import de.chojo.pluginjam.service.JamService; +import de.chojo.pluginjam.service.SettingsService; +import de.chojo.pluginjam.service.TeamService; + +public class CommandContextProvider { + private final JamService jamService; + private final SettingsService settingsService; + private final TeamService teamService; + + public CommandContextProvider(JamService jamService, SettingsService settingsService, TeamService teamService) { + this.jamService = jamService; + this.settingsService = settingsService; + this.teamService = teamService; + } + + public JamService pluginJamService() { + return jamService; + } + + public SettingsService settingsService() { + return settingsService; + } + + public TeamService teamService() { + return teamService; + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/jamadmin/ChangeVotesCommand.java b/backend/src/main/java/de/chojo/pluginjam/bot/commands/jamadmin/ChangeVotesCommand.java similarity index 55% rename from bot/src/main/java/de/chojo/gamejam/commands/jamadmin/ChangeVotesCommand.java rename to backend/src/main/java/de/chojo/pluginjam/bot/commands/jamadmin/ChangeVotesCommand.java index 47634ad9..3e00acf3 100644 --- a/bot/src/main/java/de/chojo/gamejam/commands/jamadmin/ChangeVotesCommand.java +++ b/backend/src/main/java/de/chojo/pluginjam/bot/commands/jamadmin/ChangeVotesCommand.java @@ -4,14 +4,12 @@ * Copyright (C) 2022 DevCord Team and Contributor */ -package de.chojo.gamejam.commands.jamadmin; +package de.chojo.pluginjam.bot.commands.jamadmin; import com.google.inject.Inject; -import de.chojo.gamejam.server.CommandContextProvider; +import de.chojo.pluginjam.bot.commands.CommandContextProvider; import io.github.kaktushose.jdac.annotations.i18n.Bundle; -import io.github.kaktushose.jdac.annotations.interactions.Command; -import io.github.kaktushose.jdac.annotations.interactions.Interaction; -import io.github.kaktushose.jdac.annotations.interactions.Param; +import io.github.kaktushose.jdac.annotations.interactions.*; import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; @Bundle("locale") @@ -26,13 +24,15 @@ public ChangeVotesCommand(CommandContextProvider commandContextProvider) { @Command("jamdamin changevotes") public void onCommand(CommandEvent event, @Param("voting") boolean voting) { - var guilds = commandContextProvider.guilds(); - var jam = guilds.guild(event).jams().activeJam(); - if (jam.isEmpty()) { - event.reply("error.noactivejam"); + var guildId = event.getGuild().getIdLong(); + var activeJam = commandContextProvider.pluginJamService().getActiveJam(guildId); + + if (activeJam.isEmpty()) { + event.with().ephemeral(true).reply("error-noactivejam"); return; } - jam.get().state().voting(voting); - event.reply("asd"); + + commandContextProvider.pluginJamService().setVoting(activeJam.get().id(), voting); + event.with().ephemeral(true).reply("command-jamadmin-changevotes-message-changed"); } } diff --git a/bot/src/main/java/de/chojo/gamejam/commands/jamadmin/JamCreateCommand.java b/backend/src/main/java/de/chojo/pluginjam/bot/commands/jamadmin/JamCreateCommand.java similarity index 70% rename from bot/src/main/java/de/chojo/gamejam/commands/jamadmin/JamCreateCommand.java rename to backend/src/main/java/de/chojo/pluginjam/bot/commands/jamadmin/JamCreateCommand.java index 44645a6b..fa5aac80 100644 --- a/bot/src/main/java/de/chojo/gamejam/commands/jamadmin/JamCreateCommand.java +++ b/backend/src/main/java/de/chojo/pluginjam/bot/commands/jamadmin/JamCreateCommand.java @@ -4,17 +4,12 @@ * Copyright (C) 2022 DevCord Team and Contributor */ -package de.chojo.gamejam.commands.jamadmin; +package de.chojo.pluginjam.bot.commands.jamadmin; import com.google.inject.Inject; -import de.chojo.gamejam.data.wrapper.jam.JamCreator; -import de.chojo.gamejam.data.dao.guild.jams.jam.JamTimes; -import de.chojo.gamejam.data.wrapper.jam.TimeFrame; -import de.chojo.gamejam.server.CommandContextProvider; +import de.chojo.pluginjam.bot.commands.CommandContextProvider; import io.github.kaktushose.jdac.annotations.i18n.Bundle; -import io.github.kaktushose.jdac.annotations.interactions.AutoComplete; -import io.github.kaktushose.jdac.annotations.interactions.Interaction; -import io.github.kaktushose.jdac.annotations.interactions.Param; +import io.github.kaktushose.jdac.annotations.interactions.*; import io.github.kaktushose.jdac.dispatching.events.interactions.AutoCompleteEvent; import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; import io.github.kaktushose.jdac.message.placeholder.Entry; @@ -51,36 +46,36 @@ public void onCommand( @Param("jamstart") String jamStart, @Param("jamend") String jamEnd ) { - var guild = commandContextProvider.guilds().guild(event); - var titleAndSlogan = String.join("\n", topic, tagline); + var guild = event.getGuild(); ZoneId parsedTimeZone; try { parsedTimeZone = ZoneId.of(timezone); } catch (DateTimeException e) { - event.reply("error.invalidtimezone"); + event.reply("error-invalidtimezone"); return; } - var jamBuilder = JamCreator.create().setTopic(titleAndSlogan); try { var registerStartParsed = parseTime(registerStart, parsedTimeZone); var registerEndParsed = parseTime(registerEnd, parsedTimeZone); var jamStartParsed = parseTime(jamStart, parsedTimeZone); var jamEndParsed = parseTime(jamEnd, parsedTimeZone); - var times = new JamTimes( - parsedTimeZone, - new TimeFrame(registerStartParsed, registerEndParsed), - new TimeFrame(jamStartParsed, jamEndParsed) + commandContextProvider.pluginJamService().createJam( + guild.getIdLong(), + topic, + tagline, + parsedTimeZone.getId(), + registerStartParsed, + registerEndParsed, + jamStartParsed, + jamEndParsed ); - jamBuilder.setTimes(times); } catch (DateTimeException e) { - event.reply("error.invalidrimeformat", Entry.entry("FORMAT", PATTERN)); + event.with().ephemeral(true).reply("error-invalidrimeformat", Entry.entry("FORMAT", PATTERN)); return; } - - guild.jams().create(jamBuilder.build()); - event.reply("command.jamadmin.create.message.created"); + event.with().ephemeral(true).reply("command-jamadmin-create-message-created"); } @@ -89,7 +84,7 @@ private ZonedDateTime parseTime(String time, ZoneId zoneId) throws DateTimeExcep return ZonedDateTime.ofInstant(parsed, zoneId.getRules().getOffset(parsed), zoneId); } - @AutoComplete(value = "jamadmin create", options = "timezone") + @AutoComplete(value = "onCommand", options = "timezone") public void onAutoComplete(AutoCompleteEvent event) { var value = event.getValue().toLowerCase(Locale.ROOT); var choices = ZoneId.getAvailableZoneIds().stream() diff --git a/bot/src/main/java/de/chojo/gamejam/commands/jamadmin/JamEndCommand.java b/backend/src/main/java/de/chojo/pluginjam/bot/commands/jamadmin/JamEndCommand.java similarity index 53% rename from bot/src/main/java/de/chojo/gamejam/commands/jamadmin/JamEndCommand.java rename to backend/src/main/java/de/chojo/pluginjam/bot/commands/jamadmin/JamEndCommand.java index c0f018f9..57f07989 100644 --- a/bot/src/main/java/de/chojo/gamejam/commands/jamadmin/JamEndCommand.java +++ b/backend/src/main/java/de/chojo/pluginjam/bot/commands/jamadmin/JamEndCommand.java @@ -4,14 +4,12 @@ * Copyright (C) 2022 DevCord Team and Contributor */ -package de.chojo.gamejam.commands.jamadmin; +package de.chojo.pluginjam.bot.commands.jamadmin; import com.google.inject.Inject; -import de.chojo.gamejam.server.CommandContextProvider; +import de.chojo.pluginjam.bot.commands.CommandContextProvider; import io.github.kaktushose.jdac.annotations.i18n.Bundle; -import io.github.kaktushose.jdac.annotations.interactions.Command; -import io.github.kaktushose.jdac.annotations.interactions.Interaction; -import io.github.kaktushose.jdac.annotations.interactions.Param; +import io.github.kaktushose.jdac.annotations.interactions.*; import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; @Bundle("locale") @@ -27,16 +25,18 @@ public JamEndCommand(CommandContextProvider commandContextProvider) { @Command(value = "jamadmin end") public void onCommand(CommandEvent event, @Param("confirm") boolean confirm) { if (!confirm) { - event.reply("error.noconfirm"); + event.with().ephemeral(true).reply("error-noconfirm"); return; } - commandContextProvider.guilds().guild(event).jams().activeJam().ifPresentOrElse( - jam -> { - jam.state().finish(); - event.reply("command.jamadmin.jam.end.message.ended"); - }, - () -> event.reply("error.noactivejam") - ); + var guildId = event.getGuild().getIdLong(); + + if (!commandContextProvider.pluginJamService().isJamActive(guildId)) { + event.with().ephemeral(true).reply("error-nojamactive"); + return; + } + + commandContextProvider.pluginJamService().endActiveJams(guildId); + event.with().ephemeral(true).reply("command-jamadmin-jam-end-message-ended"); } } diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/commands/jamadmin/JamListCommand.java b/backend/src/main/java/de/chojo/pluginjam/bot/commands/jamadmin/JamListCommand.java new file mode 100644 index 00000000..5ce0b221 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/bot/commands/jamadmin/JamListCommand.java @@ -0,0 +1,53 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.pluginjam.bot.commands.jamadmin; + +import com.google.inject.Inject; +import de.chojo.pluginjam.bot.commands.CommandContextProvider; +import de.chojo.pluginjam.bot.message.EmbedHelper; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.*; +import io.github.kaktushose.jdac.dispatching.events.interactions.AutoCompleteEvent; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; +import io.github.kaktushose.jdac.message.placeholder.Entry; +import io.github.kaktushose.jdac.message.resolver.MessageResolver; +import net.dv8tion.jda.api.interactions.commands.Command; + +import java.time.DateTimeException; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Locale; +import java.util.stream.Collectors; + +@Bundle("locale") +@Interaction +public class JamListCommand { + public static final String PATTERN = "yyyy.MM.dd HH:mm"; + private static final DateTimeFormatter DATE_PARSER = DateTimeFormatter.ofPattern(PATTERN); + private final CommandContextProvider commandContextProvider; + private final MessageResolver messageResolver; + + @Inject + public JamListCommand(CommandContextProvider commandContextProvider, MessageResolver messageResolver) { + this.commandContextProvider = commandContextProvider; + this.messageResolver = messageResolver; + } + + @io.github.kaktushose.jdac.annotations.interactions.Command(value = "jamadmin list") + public void onCommand(CommandEvent event) { + var jams = commandContextProvider.pluginJamService().getJams(event.getGuild().getIdLong()); + if (jams == null || jams.isEmpty()) { + event.with().ephemeral(true).reply("command-jamadmin-list-no-jams"); + return; + } + var locale = event.getUserLocale(); + var embed = EmbedHelper.buildJamListEmbed(jams, event, messageResolver, locale); + event.reply(embed); + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/jamadmin/JamStartCommand.java b/backend/src/main/java/de/chojo/pluginjam/bot/commands/jamadmin/JamStartCommand.java similarity index 51% rename from bot/src/main/java/de/chojo/gamejam/commands/jamadmin/JamStartCommand.java rename to backend/src/main/java/de/chojo/pluginjam/bot/commands/jamadmin/JamStartCommand.java index a12f2567..22d3ec25 100644 --- a/bot/src/main/java/de/chojo/gamejam/commands/jamadmin/JamStartCommand.java +++ b/backend/src/main/java/de/chojo/pluginjam/bot/commands/jamadmin/JamStartCommand.java @@ -4,12 +4,14 @@ * Copyright (C) 2022 DevCord Team and Contributor */ -package de.chojo.gamejam.commands.jamadmin; +package de.chojo.pluginjam.bot.commands.jamadmin; import com.google.inject.Inject; -import de.chojo.gamejam.server.CommandContextProvider; +import de.chojo.pluginjam.bot.commands.CommandContextProvider; import io.github.kaktushose.jdac.annotations.i18n.Bundle; import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.CommandConfig; +import io.github.kaktushose.jdac.annotations.interactions.CommandScope; import io.github.kaktushose.jdac.annotations.interactions.Interaction; import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; @@ -25,20 +27,20 @@ public JamStartCommand(CommandContextProvider commandContextProvider) { @Command(value = "jamadmin start") public void onCommand(CommandEvent event) { - var guild = commandContextProvider.guilds().guild(event); - var jams = guild.jams(); - var currJam = jams.activeJam(); - if (currJam.isPresent()) { - event.reply("error.alreadyActive"); + var guildId = event.getGuild().getIdLong(); + + if (commandContextProvider.pluginJamService().isJamActive(guildId)) { + event.with().ephemeral(true).reply("error-alreadyActive"); return; } - var next = jams.nextOrCurrent(); - if (next.isPresent()) { - next.get().state().active(true); - event.reply("command.start.message.activated"); + + var nextJam = commandContextProvider.pluginJamService().getUpComingJam(guildId); + if (nextJam.isPresent()) { + commandContextProvider.pluginJamService().startJam(nextJam.get().id()); + event.with().ephemeral(true).reply("command-start-message-activated"); return; } - event.reply("error.noupcomingjam"); + event.with().ephemeral(true).reply("error-noupcomingjam"); } } diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/commands/register/RegisterCommand.java b/backend/src/main/java/de/chojo/pluginjam/bot/commands/register/RegisterCommand.java new file mode 100644 index 00000000..d4977e48 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/bot/commands/register/RegisterCommand.java @@ -0,0 +1,76 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.pluginjam.bot.commands.register; + +import com.google.inject.Inject; +import de.chojo.pluginjam.bot.commands.CommandContextProvider; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.CommandConfig; +import io.github.kaktushose.jdac.annotations.interactions.CommandScope; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; +import io.github.kaktushose.jdac.message.placeholder.Entry; +import net.dv8tion.jda.api.utils.TimeFormat; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.chrono.ChronoLocalDateTime; + +@Bundle("locale") +@Interaction +public class RegisterCommand { + private final CommandContextProvider commandContextProvider; + + @Inject + public RegisterCommand(CommandContextProvider commandContextProvider) { + this.commandContextProvider = commandContextProvider; + } + + @Command(value = "register") + public void onCommand(CommandEvent event) { + var guildId = event.getGuild().getIdLong(); + var optJam = commandContextProvider.pluginJamService().getCurrentOrUpcoming(guildId); + + if (optJam.isEmpty()) { + event.with().ephemeral(true).reply("error-noupcomingjam"); + return; + } + + var jam = optJam.get(); + + var registrationStart = jam.time().registrationStart(); + var registrationEnd = jam.time().registrationEnd(); + + if (registrationStart.isAfter(ChronoLocalDateTime.from(ZonedDateTime.now()))) { + event.with().ephemeral(true).reply("command-register-message-notyet", Entry.entry("TIMESTAMP", TimeFormat.DATE_TIME_LONG.format(registrationStart))); + return; + } + + if (registrationEnd.isBefore(ChronoLocalDateTime.from(ZonedDateTime.now()))) { + event.with().ephemeral(true).reply("command-register-message-notanymore"); + return; + } + + if (jam.registrations().stream().anyMatch(member -> member.userId().equals(event.getMember().getIdLong()))) { + event.with().ephemeral(true).reply("command-register-message-alreadyregistered"); + return; + } + + commandContextProvider.pluginJamService().registerUser(jam.id(), event.getMember().getIdLong()); + + var settings = commandContextProvider.settingsService().getSettings(guildId); + var role = event.getGuild().getRoleById(settings.getParticipantRole()); + if (role != null) { + event.getGuild().addRoleToMember(event.getMember(), role).queue(); + } + event.with().ephemeral(true).reply( + "command-register-message-registered", + Entry.entry("TIMESTAMP", TimeFormat.DATE_TIME_LONG.format(ZonedDateTime.of(jam.time().registrationStart(), ZoneId.of(jam.time().zoneId())))) + ); + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/commands/register/UnregisterCommand.java b/backend/src/main/java/de/chojo/pluginjam/bot/commands/register/UnregisterCommand.java new file mode 100644 index 00000000..057b7015 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/bot/commands/register/UnregisterCommand.java @@ -0,0 +1,59 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.pluginjam.bot.commands.register; + +import com.google.inject.Inject; +import de.chojo.pluginjam.bot.commands.CommandContextProvider; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.CommandConfig; +import io.github.kaktushose.jdac.annotations.interactions.CommandScope; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; + +@Bundle("locale") +@Interaction +public class UnregisterCommand { + private final CommandContextProvider commandContextProvider; + + @Inject + public UnregisterCommand(CommandContextProvider commandContextProvider) { + this.commandContextProvider = commandContextProvider; + } + + @Command(value = "unregister") + public void onCommand(CommandEvent event) { + var guildId = event.getGuild().getIdLong(); + var optJam = commandContextProvider.pluginJamService().getCurrentOrUpcoming(guildId); + if (optJam.isEmpty()) { + event.with().ephemeral(true).reply("error-noupcomingjam"); + return; + } + + var jam = optJam.get(); + + if (jam.registrations().stream().noneMatch(r -> r.userId().equals(event.getMember().getIdLong()))) { + event.with().ephemeral(true).reply("command-unregister-message-notregistered"); + return; + } + + var optTeam = commandContextProvider.teamService().getUserTeam(event.getMember().getIdLong()); + if (optTeam.isPresent()) { + event.with().ephemeral(true).reply("command-unregister-message-inteam"); + return; + } + + commandContextProvider.pluginJamService().unregisterUser(jam.id(), event.getMember().getIdLong()); + + var settings = commandContextProvider.settingsService().getSettings(guildId); + var role = event.getGuild().getRoleById(settings.getParticipantRole()); + if (role != null) { + event.getGuild().removeRoleFromMember(event.getMember(), role).queue(); + } + event.with().ephemeral(true).reply("command-unregister-message-unregistered"); + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/commands/settings/SettingsInfoCommand.java b/backend/src/main/java/de/chojo/pluginjam/bot/commands/settings/SettingsInfoCommand.java new file mode 100644 index 00000000..e6fbdeff --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/bot/commands/settings/SettingsInfoCommand.java @@ -0,0 +1,39 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.pluginjam.bot.commands.settings; + +import com.google.inject.Inject; +import de.chojo.pluginjam.bot.commands.CommandContextProvider; +import de.chojo.pluginjam.bot.message.EmbedHelper; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; +import io.github.kaktushose.jdac.message.resolver.MessageResolver; + +@Bundle("locale") +@Interaction +public final class SettingsInfoCommand { + private final CommandContextProvider commandContextProvider; + private final MessageResolver messageResolver; + + @Inject + public SettingsInfoCommand(CommandContextProvider commandContextProvider, MessageResolver messageResolver) { + this.commandContextProvider = commandContextProvider; + this.messageResolver = messageResolver; + } + + @Command(value = "settings info") + public void onCommand(CommandEvent event) { + var guildId = event.getGuild().getIdLong(); + + var settings = commandContextProvider.settingsService().getSettings(guildId); + var embed = EmbedHelper.buildSettingsInfoEmbed(settings, messageResolver, event.getUserLocale()); + + event.reply(embed); + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/settings/JamRoleCommand.java b/backend/src/main/java/de/chojo/pluginjam/bot/commands/settings/SettingsRoleOrgaCommand.java similarity index 56% rename from bot/src/main/java/de/chojo/gamejam/commands/settings/JamRoleCommand.java rename to backend/src/main/java/de/chojo/pluginjam/bot/commands/settings/SettingsRoleOrgaCommand.java index 3f318d25..a2067ebc 100644 --- a/bot/src/main/java/de/chojo/gamejam/commands/settings/JamRoleCommand.java +++ b/backend/src/main/java/de/chojo/pluginjam/bot/commands/settings/SettingsRoleOrgaCommand.java @@ -4,36 +4,32 @@ * Copyright (C) 2022 DevCord Team and Contributor */ -package de.chojo.gamejam.commands.settings; +package de.chojo.pluginjam.bot.commands.settings; import com.google.inject.Inject; -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.server.CommandContextProvider; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.wrapper.EventContext; +import de.chojo.pluginjam.bot.commands.CommandContextProvider; import io.github.kaktushose.jdac.annotations.i18n.Bundle; import io.github.kaktushose.jdac.annotations.interactions.Command; import io.github.kaktushose.jdac.annotations.interactions.Interaction; import io.github.kaktushose.jdac.annotations.interactions.Param; import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; import net.dv8tion.jda.api.entities.Role; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.interactions.commands.OptionType; @Bundle("locale") @Interaction -public final class JamRoleCommand { +public final class SettingsRoleOrgaCommand { private final CommandContextProvider commandContextProvider; @Inject - public JamRoleCommand(CommandContextProvider commandContextProvider) { + public SettingsRoleOrgaCommand(CommandContextProvider commandContextProvider) { this.commandContextProvider = commandContextProvider; } - @Command(value = "settings jamrole") + @Command(value = "settings role orga") public void onCommand(CommandEvent event, @Param(value = "role", type = OptionType.ROLE) Role role) { - var guilds = commandContextProvider.guilds(); - guilds.guild(event.getGuild()).jamSettings().jamRole(role); - event.reply("command.settings.jamrole.message.updated"); + var guildId = event.getGuild().getIdLong(); + commandContextProvider.settingsService().setOrgaRole(guildId, role.getIdLong()); + event.with().ephemeral(true).reply("command-settings-orgarole-message-updated"); } } diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/commands/settings/SettingsRoleParticipantCommand.java b/backend/src/main/java/de/chojo/pluginjam/bot/commands/settings/SettingsRoleParticipantCommand.java new file mode 100644 index 00000000..85a1a568 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/bot/commands/settings/SettingsRoleParticipantCommand.java @@ -0,0 +1,35 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.pluginjam.bot.commands.settings; + +import com.google.inject.Inject; +import de.chojo.pluginjam.bot.commands.CommandContextProvider; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.annotations.interactions.Param; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; +import net.dv8tion.jda.api.entities.Role; +import net.dv8tion.jda.api.interactions.commands.OptionType; + +@Bundle("locale") +@Interaction +public final class SettingsRoleParticipantCommand { + private final CommandContextProvider commandContextProvider; + + @Inject + public SettingsRoleParticipantCommand(CommandContextProvider commandContextProvider) { + this.commandContextProvider = commandContextProvider; + } + + @Command(value = "settings role participant") + public void onCommand(CommandEvent event, @Param(value = "role", type = OptionType.ROLE) Role role) { + var guildId = event.getGuild().getIdLong(); + commandContextProvider.settingsService().setJamRole(guildId, role.getIdLong()); + event.with().ephemeral(true).reply("command-settings-jamrole-message-updated"); + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/settings/TeamSizeCommand.java b/backend/src/main/java/de/chojo/pluginjam/bot/commands/settings/SettingsTeamSizeCommand.java similarity index 54% rename from bot/src/main/java/de/chojo/gamejam/commands/settings/TeamSizeCommand.java rename to backend/src/main/java/de/chojo/pluginjam/bot/commands/settings/SettingsTeamSizeCommand.java index 67b33771..a353e065 100644 --- a/bot/src/main/java/de/chojo/gamejam/commands/settings/TeamSizeCommand.java +++ b/backend/src/main/java/de/chojo/pluginjam/bot/commands/settings/SettingsTeamSizeCommand.java @@ -4,34 +4,30 @@ * Copyright (C) 2022 DevCord Team and Contributor */ -package de.chojo.gamejam.commands.settings; +package de.chojo.pluginjam.bot.commands.settings; import com.google.inject.Inject; -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.server.CommandContextProvider; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.wrapper.EventContext; +import de.chojo.pluginjam.bot.commands.CommandContextProvider; import io.github.kaktushose.jdac.annotations.i18n.Bundle; import io.github.kaktushose.jdac.annotations.interactions.Command; import io.github.kaktushose.jdac.annotations.interactions.Interaction; import io.github.kaktushose.jdac.annotations.interactions.Param; import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; @Bundle("locale") @Interaction -public final class TeamSizeCommand { +public final class SettingsTeamSizeCommand { private final CommandContextProvider commandContextProvider; @Inject - public TeamSizeCommand(CommandContextProvider commandContextProvider) { + public SettingsTeamSizeCommand(CommandContextProvider commandContextProvider) { this.commandContextProvider = commandContextProvider; } @Command(value = "settings teamsize") public void onCommand(CommandEvent event, @Param("size") int size) { - var settings = commandContextProvider.guilds().guild(event).jamSettings(); - settings.teamSize(size); - event.with().ephemeral(true).reply("command.settings.teamsize.message.updated"); + var guildId = event.getGuild().getIdLong(); + commandContextProvider.settingsService().setTeamSize(guildId, size); + event.with().ephemeral(true).reply("command-settings-teamsize-message-updated"); } } diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamCreateCommand.java b/backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamCreateCommand.java new file mode 100644 index 00000000..f9bf7af5 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamCreateCommand.java @@ -0,0 +1,119 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.pluginjam.bot.commands.team; + + +import com.google.inject.Inject; +import de.chojo.pluginjam.bot.commands.CommandContextProvider; +import de.chojo.pluginjam.bot.message.StringValidator; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.annotations.interactions.Param; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; +import net.dv8tion.jda.api.Permission; + +import java.util.Collections; +import java.util.EnumSet; + +@Bundle("locale") +@Interaction +public final class TeamCreateCommand { + private final CommandContextProvider commandContextProvider; + + @Inject + public TeamCreateCommand(CommandContextProvider commandContextProvider) { + this.commandContextProvider = commandContextProvider; + } + + @Command(value = "team create") + public void onCommand(CommandEvent event, @Param("name") String teamName) { + var guildId = event.getGuild().getIdLong(); + + var optJam = commandContextProvider.pluginJamService().getCurrentOrUpcoming(guildId); + + if (optJam.isEmpty()) { + event.with().ephemeral(true).reply("error-nojamactive"); + return; + } + var jam = optJam.get(); + + if (jam.state().voting()) { + event.with().ephemeral(true).reply("error-votingactive"); + return; + } + + if (jam.registrations().stream().noneMatch(r -> r.userId().equals(event.getMember().getIdLong()))) { + event.with().ephemeral(true).reply("command-team-create-message-unregistered"); + return; + } + var existingTeam = commandContextProvider.teamService().getUserTeam(event.getMember().getIdLong()); + if (existingTeam.isPresent()) { + event.with().ephemeral(true).reply("command-team-create-message-alreadymember"); + return; + } + + if (!StringValidator.isValidTeamName(teamName)) { + event.with().ephemeral(true).reply("command-team-create-message-invalidname"); + return; + } + + var optTeam = commandContextProvider.teamService().getTeamByName(jam.id(), teamName); + + if (optTeam.isPresent()) { + event.with().ephemeral(true).reply("command-team-create-message-nametaken"); + return; + } + + event.deferReply(true); + + var categoryList = event.getGuild().getCategoriesByName("Team", true); + + var optCategory = categoryList.stream().filter(cat -> cat.getChannels().size() < 48).findFirst(); + // This is really hacky and I dont like it. + // All this stuff is blocking atm but in a different thread already + var category = optCategory.orElseGet(() -> event.getGuild().createCategory("Team").complete()); + + var role = event.getGuild() + .createRole() + .setPermissions(0L) + .setMentionable(false) + .setHoisted(false) + .setName(teamName) + .complete(); + + var textChannel = event.getGuild().createTextChannel(teamName.replace(" ", "-"), category) + .addRolePermissionOverride(role.getIdLong(), EnumSet.of(Permission.VIEW_CHANNEL), Collections.emptySet()) + .addMemberPermissionOverride(event.getJDA().getSelfUser() + .getIdLong(), EnumSet.of(Permission.VIEW_CHANNEL, Permission.MANAGE_CHANNEL), Collections.emptySet()) + .addRolePermissionOverride(event.getGuild().getPublicRole() + .getIdLong(), Collections.emptySet(), EnumSet.of(Permission.VIEW_CHANNEL)) + .complete(); + + var voiceChannel = event.getGuild().createVoiceChannel(teamName, category) + .addRolePermissionOverride(role.getIdLong(), EnumSet.of(Permission.VIEW_CHANNEL), Collections.emptySet()) + .addMemberPermissionOverride(event.getJDA().getSelfUser() + .getIdLong(), EnumSet.of(Permission.VIEW_CHANNEL, Permission.MANAGE_CHANNEL), Collections.emptySet()) + .addRolePermissionOverride(event.getGuild().getPublicRole() + .getIdLong(), Collections.emptySet(), EnumSet.of(Permission.VIEW_CHANNEL)) + .complete(); + + //meta.token(Token.generate(40)); + + var team = commandContextProvider.teamService().createTeam( + jam.id(), + teamName, + event.getMember().getIdLong(), + role.getIdLong(), + textChannel.getIdLong(), + voiceChannel.getIdLong() + ); + + commandContextProvider.teamService().joinTeam(team, event.getMember(), event.getGuild()); + event.with().ephemeral(true).reply("command-team-create-message-created"); + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamDisbandCommand.java b/backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamDisbandCommand.java new file mode 100644 index 00000000..f9f27475 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamDisbandCommand.java @@ -0,0 +1,72 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.pluginjam.bot.commands.team; + +import com.google.inject.Inject; +import de.chojo.pluginjam.bot.commands.CommandContextProvider; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.annotations.interactions.Param; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; +import io.github.kaktushose.jdac.message.resolver.MessageResolver; + +import java.util.Optional; + +@Bundle("locale") +@Interaction +public final class TeamDisbandCommand { + private final CommandContextProvider commandContextProvider; + private final MessageResolver messageResolver; + + @Inject + public TeamDisbandCommand(CommandContextProvider commandContextProvider, MessageResolver messageResolver) { + this.commandContextProvider = commandContextProvider; + this.messageResolver = messageResolver; + } + + @Command(value = "team disband") + public void onCommand(CommandEvent event, @Param("confirm") boolean confirm) { + var optJam = commandContextProvider.pluginJamService().getCurrentOrUpcoming(event.getGuild().getIdLong()); + + if (optJam.isEmpty()) { + event.with().ephemeral(true).reply("error-nojamactive"); + return; + } + var jam = optJam.get(); + + if (jam.state().voting()) { + event.with().ephemeral(true).reply("error-votingactive"); + return; + } + + if (!confirm) { + event.with().ephemeral(true).reply("error-noconfirm"); + return; + } + + var existingTeamOpt = commandContextProvider.teamService().getUserTeam(event.getMember().getIdLong()); + if (existingTeamOpt.isEmpty()) { + event.with().ephemeral(true).reply("error-noteam"); + return; + } + + var team = existingTeamOpt.get(); + + var members = team.members(); + for (var teamMember : members) { + var member = event.getGuild().getMemberById(teamMember.userId()); + var user = member.getUser(); + user.openPrivateChannel() + .flatMap(channel -> channel.sendMessage(messageResolver.resolve("command-team-disband-message-disbanded", member.getGuild().getLocale()))) + .queue(); + } + + commandContextProvider.teamService().disbandTeam(team, event.getGuild()); + event.with().ephemeral(true).reply("command-team-disband-message-disbanded"); + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/listener/InviteButtonListener.java b/backend/src/main/java/de/chojo/pluginjam/bot/listener/InviteButtonListener.java new file mode 100644 index 00000000..e36c8852 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/bot/listener/InviteButtonListener.java @@ -0,0 +1,103 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.pluginjam.bot.listener; + +import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; +import net.dv8tion.jda.api.hooks.ListenerAdapter; +import org.slf4j.Logger; + +import static org.slf4j.LoggerFactory.getLogger; + +public class InviteButtonListener extends ListenerAdapter { + /*private static final Logger log = getLogger(InviteButtonListener.class); + private static final String BUTTON_PREFIX = "invite-accept:"; + + private final Guilds guilds; + + public InviteButtonListener(Guilds guilds) { + this.guilds = guilds; + } + + @Override + public void onButtonInteraction(ButtonInteractionEvent event) { + var componentId = event.getComponentId(); + if (!componentId.startsWith(BUTTON_PREFIX)) { + return; + } + + event.deferReply().queue(); + + var parts = componentId.substring(BUTTON_PREFIX.length()).split(":"); + if (parts.length != 3) { + event.getHook().editOriginal("Invalid invite data.").queue(); + return; + } + + long guildId; + int teamId; + long userId; + try { + guildId = Long.parseLong(parts[0]); + teamId = Integer.parseInt(parts[1]); + userId = Long.parseLong(parts[2]); + } catch (NumberFormatException e) { + event.getHook().editOriginal("Invalid invite data.").queue(); + return; + } + + var manager = event.getJDA().getShardManager(); + var guild = manager.getGuildById(guildId); + if (guild == null) { + event.getHook().editOriginal("Guild not found.").queue(); + return; + } + + var user = manager.retrieveUserById(userId).complete(); + var member = guild.retrieveMember(user).complete(); + var jamGuild = guilds.guild(guild); + var settings = jamGuild.jamSettings(); + + var optJam = jamGuild.jams().nextOrCurrent(); + if (optJam.isEmpty()) { + event.getHook().editOriginal("The game jam is over.").queue(); + return; + } + + var jam = optJam.get(); + var optTeam = jam.teams().byId(teamId); + if (optTeam.isEmpty()) { + event.getHook().editOriginal("Team not found.").queue(); + return; + } + + var team = optTeam.get(); + var members = team.member(); + + if (members.size() >= settings.teamSize()) { + event.getHook().editOriginal("The team is already full.").queue(); + return; + } + + var currTeam = jam.teams().byMember(user); + if (currTeam.isPresent()) { + event.getHook().editOriginal("You are already part of a team.").queue(); + return; + } + + jam.user(member).join(team); + team.meta().role().ifPresent(role -> guild.addRoleToMember(member, role).queue()); + event.getHook().editOriginal("You have joined the team!").queue(); + team.meta().textChannel().ifPresent(channel -> + channel.sendMessage(member.getAsMention() + " has joined the team!").queue() + ); + + // Disable the button after use + event.getMessage().editMessageComponents().queue(); + } + + */ +} diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/message/EmbedHelper.java b/backend/src/main/java/de/chojo/pluginjam/bot/message/EmbedHelper.java new file mode 100644 index 00000000..a7ec4055 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/bot/message/EmbedHelper.java @@ -0,0 +1,90 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.pluginjam.bot.message; + +import de.chojo.pluginjam.bot.util.MentionUtil; +import de.chojo.pluginjam.database.entity.SettingsEntity; +import de.chojo.pluginjam.database.entity.jam.Jam; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; +import io.github.kaktushose.jdac.message.resolver.MessageResolver; +import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.interactions.DiscordLocale; +import net.dv8tion.jda.api.utils.TimeFormat; + +import java.awt.*; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.List; + +public class EmbedHelper { + + /* public static CompletableFuture embedDetailedStatus(Team team, ServerService serverService, CommandEvent event) { + return CompletableFuture.supplyAsync(() -> { + var serverStatus = serverService.getServerStatus(team.id()); + + var embed = event.embed("status"); + embed.title("%s #%d | %s".formatted(serverStatus.emoji(), team.id(), team.meta().name())); + + if (serverStatus == ServerStatus.VOID) { + embed.description("teamserver.message.detailstatus.nonexisting.description"); + } else { + if (serverStatus == ServerStatus.RUNNING) { + embed.description("teamserver.message.detailstatus.existing.description"); + + var serverStats = serverService.serverHttpService().fetchServerStats(serverService.dockerService().containerName(team.id())); + serverStats.ifPresent(stats -> { + var memory = stats.memory(); + embed.fields().add("word.memory", "$word.used$ %d%n$word.total$: %d%n$word.max$: %d".formatted(memory.usedMb(), memory.totalMb(), memory.maxMb()), true); + embed.fields().add("word.tps", "1 $word.min$: %.2f%n5 $word.min$: %.2f%n 15 $word.min$: %.2f%n$word.averageticktime$ %.2f".formatted( + stats.tps()[0], stats.tps()[1], stats.tps()[2], stats.averageTickTime()), true); + embed.fields().add("word.players", String.valueOf(stats.onlinePlayers()), true); + embed.fields().add("word.system", "$word.activethreads$: %d".formatted(stats.activeThreads()), true); + }); + } else { + embed.description("word.serversetup"); + } + } + return embed.build(); + }); + } + + */ + + public static MessageEmbed buildJamListEmbed(List jams, CommandEvent event, MessageResolver messageResolver, DiscordLocale locale) { + var embed = new EmbedBuilder(); + embed.setTitle(messageResolver.resolve("word-jams", locale)); + embed.setDescription(String.join("\n", jams.stream().map(jam -> formatJam(jam, messageResolver, locale)).toList())); + return embed.build(); + } + + private static String formatJam(Jam jam, MessageResolver messageResolver, DiscordLocale locale) { + return "`" + jam.id() + " | " + jam.meta().topic() + " |`" + getJamState(jam, messageResolver, locale); + } + + private static String getJamState(Jam jam, MessageResolver messageResolver, DiscordLocale locale) { + if (jam.state().active()) { + return messageResolver.resolve("word-active", locale); + } + if (jam.state().ended()) { + return messageResolver.resolve("word-ended", locale); + } + + return messageResolver.resolve("word-upcoming", locale) + " " + TimeFormat.DATE_TIME_LONG.format(ZonedDateTime.of(jam.time().jamStart(), ZoneId.of(jam.time().zoneId()))); + } + + public static MessageEmbed buildSettingsInfoEmbed(SettingsEntity settings, MessageResolver messageResolver, DiscordLocale userLocale) { + var embed = new EmbedBuilder(); + embed.setTitle(messageResolver.resolve("command-settings-info-embed-settings", userLocale)); + embed.addField(messageResolver.resolve("command-settings-info-embed-jamrole", userLocale), MentionUtil.role(settings.getParticipantRole()), true); + embed.addField(messageResolver.resolve("command-settings-info-embed-teamsize", userLocale), String.valueOf(settings.getTeamSize()), true); + embed.addField(messageResolver.resolve("command-settings-info-embed-orgarole", userLocale), MentionUtil.role(settings.getOrgaRole()), true); + embed.setColor(Color.CYAN); + return embed.build(); + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/message/StringValidator.java b/backend/src/main/java/de/chojo/pluginjam/bot/message/StringValidator.java new file mode 100644 index 00000000..a6aa63fc --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/bot/message/StringValidator.java @@ -0,0 +1,10 @@ +package de.chojo.pluginjam.bot.message; + +public class StringValidator { + private static String allowedCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\\._-"; + private static int maxLength = 100; + + public static boolean isValidTeamName(String input) { + return input.length() <= maxLength && input.matches("[" + allowedCharacters + "]+"); + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/util/MentionUtil.java b/backend/src/main/java/de/chojo/pluginjam/bot/util/MentionUtil.java new file mode 100644 index 00000000..149e56ae --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/bot/util/MentionUtil.java @@ -0,0 +1,12 @@ +package de.chojo.pluginjam.bot.util; + +public class MentionUtil { + + public static String role(long id) { + return "<@&" + id + ">"; + } + + public static String user(long id) { + return "<@" + id + ">"; + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/controller/TeamController.java b/backend/src/main/java/de/chojo/pluginjam/controller/TeamController.java new file mode 100644 index 00000000..ad5decc7 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/controller/TeamController.java @@ -0,0 +1,8 @@ +package de.chojo.pluginjam.controller; + +import io.micronaut.http.annotation.Controller; + +@Controller("api/v1/teams") +public class TeamController { + +} diff --git a/backend/src/main/java/de/chojo/pluginjam/database/entity/SettingsEntity.java b/backend/src/main/java/de/chojo/pluginjam/database/entity/SettingsEntity.java new file mode 100644 index 00000000..1b45720b --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/database/entity/SettingsEntity.java @@ -0,0 +1,85 @@ +package de.chojo.pluginjam.database.entity; + +import io.micronaut.data.annotation.Id; +import io.micronaut.data.annotation.MappedEntity; +import io.micronaut.data.annotation.MappedProperty; +import io.micronaut.serde.annotation.Serdeable; + +@Serdeable +@MappedEntity(value = "settings", schema = "gamejam") +public final class SettingsEntity { + @Id + @MappedProperty("guild_id") + private Long guildId; + @MappedProperty("manager_role") + private Long orgaRole; + @MappedProperty("participant_role") + private Long participantRole; + @MappedProperty("locale") + private String locale; + @MappedProperty("team_size") + private Integer teamSize; + + public SettingsEntity( + @Id + Long guildId, + Long orgaRole, + Long participantRole, + String locale, + Integer teamSize + ) { + this.guildId = guildId; + this.orgaRole = orgaRole; + this.participantRole = participantRole; + this.locale = locale; + this.teamSize = teamSize; + } + + public SettingsEntity(long guildId) { + this.guildId = guildId; + this.orgaRole = 0L; + this.participantRole = 0L; + this.locale = "en"; + this.teamSize = 3; + } + + public Long getGuildId() { + return guildId; + } + + public Long getOrgaRole() { + return orgaRole; + } + + public Long getParticipantRole() { + return participantRole; + } + + public String getLocale() { + return locale; + } + + public Integer getTeamSize() { + return teamSize; + } + + public void setGuildId(Long guildId) { + this.guildId = guildId; + } + + public void setOrgaRole(Long orgaRole) { + this.orgaRole = orgaRole; + } + + public void setParticipantRole(Long participantRole) { + this.participantRole = participantRole; + } + + public void setLocale(String locale) { + this.locale = locale; + } + + public void setTeamSize(Integer teamSize) { + this.teamSize = teamSize; + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/database/entity/VoteEntity.java b/backend/src/main/java/de/chojo/pluginjam/database/entity/VoteEntity.java new file mode 100644 index 00000000..c61646c6 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/database/entity/VoteEntity.java @@ -0,0 +1,18 @@ +package de.chojo.pluginjam.database.entity; + +import io.micronaut.data.annotation.GeneratedValue; +import io.micronaut.data.annotation.Id; +import io.micronaut.data.annotation.MappedEntity; +import io.micronaut.data.annotation.MappedProperty; +import io.micronaut.serde.annotation.Serdeable; + +@Serdeable +@MappedEntity(value = "vote", schema = "gamejam") +public record VoteEntity( + @MappedProperty("team_id") + Long teamId, + @MappedProperty("voter_id") + Long voterId, + Integer points +) { +} diff --git a/backend/src/main/java/de/chojo/pluginjam/database/entity/jam/Jam.java b/backend/src/main/java/de/chojo/pluginjam/database/entity/jam/Jam.java new file mode 100644 index 00000000..a5a77eb7 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/database/entity/jam/Jam.java @@ -0,0 +1,25 @@ +package de.chojo.pluginjam.database.entity.jam; + +import io.micronaut.data.annotation.*; +import io.micronaut.serde.annotation.Serdeable; + +import java.util.List; + +@Serdeable +@MappedEntity(value = "jam", schema = "gamejam") +public record Jam( + @Id + @GeneratedValue + Integer id, + @MappedProperty("guild_id") + Long guildId, + @Relation(value = Relation.Kind.ONE_TO_ONE, mappedBy = "jamId") + JamMeta meta, + @Relation(value = Relation.Kind.ONE_TO_ONE, mappedBy = "jamId") + JamTime time, + @Relation(value = Relation.Kind.ONE_TO_ONE, mappedBy = "jamId") + JamState state, + @Relation(value = Relation.Kind.ONE_TO_MANY, mappedBy = "jamId") + List registrations +) { +} diff --git a/backend/src/main/java/de/chojo/pluginjam/database/entity/jam/JamMeta.java b/backend/src/main/java/de/chojo/pluginjam/database/entity/jam/JamMeta.java new file mode 100644 index 00000000..f5dd8a2a --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/database/entity/jam/JamMeta.java @@ -0,0 +1,17 @@ +package de.chojo.pluginjam.database.entity.jam; + +import io.micronaut.data.annotation.Id; +import io.micronaut.data.annotation.MappedEntity; +import io.micronaut.data.annotation.MappedProperty; +import io.micronaut.serde.annotation.Serdeable; + +@Serdeable +@MappedEntity(value = "jam_meta", schema = "gamejam") +public record JamMeta( + @Id + @MappedProperty("jam_id") + Integer jamId, + String topic, + String tagline +) { +} diff --git a/backend/src/main/java/de/chojo/pluginjam/database/entity/jam/JamRegistration.java b/backend/src/main/java/de/chojo/pluginjam/database/entity/jam/JamRegistration.java new file mode 100644 index 00000000..e1ad09c1 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/database/entity/jam/JamRegistration.java @@ -0,0 +1,17 @@ +package de.chojo.pluginjam.database.entity.jam; + +import io.micronaut.data.annotation.Id; +import io.micronaut.data.annotation.MappedEntity; +import io.micronaut.data.annotation.MappedProperty; +import io.micronaut.serde.annotation.Serdeable; + +@Serdeable +@MappedEntity(value = "jam_registrations", schema = "gamejam") +public record JamRegistration( + @MappedProperty("jam_id") + Integer jamId, + @Id + @MappedProperty("user_id") + Long userId +) { +} diff --git a/backend/src/main/java/de/chojo/pluginjam/database/entity/jam/JamState.java b/backend/src/main/java/de/chojo/pluginjam/database/entity/jam/JamState.java new file mode 100644 index 00000000..cf163d11 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/database/entity/jam/JamState.java @@ -0,0 +1,18 @@ +package de.chojo.pluginjam.database.entity.jam; + +import io.micronaut.data.annotation.Id; +import io.micronaut.data.annotation.MappedEntity; +import io.micronaut.data.annotation.MappedProperty; +import io.micronaut.serde.annotation.Serdeable; + +@Serdeable +@MappedEntity(value = "jam_state", schema = "gamejam") +public record JamState( + @Id + @MappedProperty("jam_id") + Integer jamId, + boolean active, + boolean voting, + boolean ended +) { +} diff --git a/backend/src/main/java/de/chojo/pluginjam/database/entity/jam/JamTime.java b/backend/src/main/java/de/chojo/pluginjam/database/entity/jam/JamTime.java new file mode 100644 index 00000000..235aa49e --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/database/entity/jam/JamTime.java @@ -0,0 +1,27 @@ +package de.chojo.pluginjam.database.entity.jam; + +import io.micronaut.data.annotation.Id; +import io.micronaut.data.annotation.MappedEntity; +import io.micronaut.data.annotation.MappedProperty; +import io.micronaut.serde.annotation.Serdeable; + +import java.time.LocalDateTime; + +@Serdeable +@MappedEntity(value = "jam_time", schema = "gamejam") +public record JamTime( + @Id + @MappedProperty("jam_id") + Integer jamId, + @MappedProperty("zone_id") + String zoneId, + @MappedProperty("registration_start") + LocalDateTime registrationStart, + @MappedProperty("registration_end") + LocalDateTime registrationEnd, + @MappedProperty("jam_start") + LocalDateTime jamStart, + @MappedProperty("jam_end") + LocalDateTime jamEnd +) { +} diff --git a/backend/src/main/java/de/chojo/pluginjam/database/entity/team/Team.java b/backend/src/main/java/de/chojo/pluginjam/database/entity/team/Team.java new file mode 100644 index 00000000..1370bc93 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/database/entity/team/Team.java @@ -0,0 +1,21 @@ +package de.chojo.pluginjam.database.entity.team; + +import io.micronaut.data.annotation.*; +import io.micronaut.serde.annotation.Serdeable; + +import java.util.List; + +@Serdeable +@MappedEntity(value = "team", schema = "gamejam") +public record Team( + @Id + @GeneratedValue(GeneratedValue.Type.AUTO) + Integer id, + @MappedProperty("jam_id") + Integer jamId, + @Relation(value = Relation.Kind.ONE_TO_ONE, mappedBy = "teamId") + TeamMeta meta, + @Relation(value = Relation.Kind.ONE_TO_MANY, mappedBy = "teamId") + List members +) { +} diff --git a/backend/src/main/java/de/chojo/pluginjam/database/entity/team/TeamMember.java b/backend/src/main/java/de/chojo/pluginjam/database/entity/team/TeamMember.java new file mode 100644 index 00000000..bee18374 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/database/entity/team/TeamMember.java @@ -0,0 +1,17 @@ +package de.chojo.pluginjam.database.entity.team; + +import io.micronaut.data.annotation.Id; +import io.micronaut.data.annotation.MappedEntity; +import io.micronaut.data.annotation.MappedProperty; +import io.micronaut.serde.annotation.Serdeable; + +@Serdeable +@MappedEntity(value = "team_member", schema = "gamejam") +public record TeamMember( + @MappedProperty("team_id") + Integer teamId, + @Id + @MappedProperty("user_id") + Long userId +) { +} diff --git a/backend/src/main/java/de/chojo/pluginjam/database/entity/team/TeamMeta.java b/backend/src/main/java/de/chojo/pluginjam/database/entity/team/TeamMeta.java new file mode 100644 index 00000000..189c6ef9 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/database/entity/team/TeamMeta.java @@ -0,0 +1,30 @@ +package de.chojo.pluginjam.database.entity.team; + +import io.micronaut.data.annotation.Id; +import io.micronaut.data.annotation.MappedEntity; +import io.micronaut.data.annotation.MappedProperty; +import io.micronaut.serde.annotation.Serdeable; + +@Serdeable +@MappedEntity(value = "team_meta", schema = "gamejam") +public record TeamMeta( + @Id + @MappedProperty("team_id") + Integer teamId, + @MappedProperty("team_name") + String teamName, + @MappedProperty("leader_id") + Long leaderId, + @MappedProperty("role_id") + Long roleId, + @MappedProperty("text_channel_id") + Long textChannelId, + @MappedProperty("voice_channel_id") + Long voiceChannelId, + @MappedProperty("project_description") + String projectDescription, + @MappedProperty("project_url") + String projectUrl, + String token +) { +} diff --git a/backend/src/main/java/de/chojo/pluginjam/database/repository/SettingsRepository.java b/backend/src/main/java/de/chojo/pluginjam/database/repository/SettingsRepository.java new file mode 100644 index 00000000..64e14b5c --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/database/repository/SettingsRepository.java @@ -0,0 +1,22 @@ +package de.chojo.pluginjam.database.repository; + +import de.chojo.pluginjam.database.entity.SettingsEntity; +import io.micronaut.data.annotation.Query; +import io.micronaut.data.jdbc.annotation.JdbcRepository; +import io.micronaut.data.model.query.builder.sql.Dialect; +import io.micronaut.data.repository.GenericRepository; +import org.jspecify.annotations.NonNull; + +import java.util.Optional; + +@JdbcRepository(dialect = Dialect.POSTGRES) +public interface SettingsRepository extends GenericRepository { + + @Query("INSERT INTO gamejam.settings (guild_id, participant_role, manager_role, locale, team_size) " + + "VALUES (:guildId, :participantRole, :managerRole, :locale, :teamSize) " + + "ON CONFLICT (guild_id) DO UPDATE SET participant_role = :participantRole, manager_role = :managerRole, locale = :locale, team_size = :teamSize") + void save(@NonNull Long guildId, @NonNull Long participantRole, @NonNull Long managerRole, @NonNull String locale, @NonNull Integer teamSize); + + @Query("SELECT guild_id, manager_role, participant_role, team_size, locale FROM gamejam.settings WHERE guild_id = :guildId") + Optional findById(long guildId); +} \ No newline at end of file diff --git a/backend/src/main/java/de/chojo/pluginjam/database/repository/TeamMemberRepository.java b/backend/src/main/java/de/chojo/pluginjam/database/repository/TeamMemberRepository.java new file mode 100644 index 00000000..b49f53b6 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/database/repository/TeamMemberRepository.java @@ -0,0 +1,13 @@ +package de.chojo.pluginjam.database.repository; + +import de.chojo.pluginjam.database.entity.team.TeamMember; +import io.micronaut.data.jdbc.annotation.JdbcRepository; +import io.micronaut.data.model.query.builder.sql.Dialect; +import io.micronaut.data.repository.CrudRepository; + +@JdbcRepository(dialect = Dialect.POSTGRES) +public interface TeamMemberRepository extends CrudRepository { + + void deleteByTeamIdAndUserId(Integer teamId, Long userId); + void deleteAllByTeamId(Integer teamId); +} diff --git a/backend/src/main/java/de/chojo/pluginjam/database/repository/TeamMetaRepository.java b/backend/src/main/java/de/chojo/pluginjam/database/repository/TeamMetaRepository.java new file mode 100644 index 00000000..a7bff9ab --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/database/repository/TeamMetaRepository.java @@ -0,0 +1,10 @@ +package de.chojo.pluginjam.database.repository; + +import de.chojo.pluginjam.database.entity.team.TeamMeta; +import io.micronaut.data.jdbc.annotation.JdbcRepository; +import io.micronaut.data.model.query.builder.sql.Dialect; +import io.micronaut.data.repository.CrudRepository; + +@JdbcRepository(dialect = Dialect.POSTGRES) +public interface TeamMetaRepository extends CrudRepository { +} diff --git a/backend/src/main/java/de/chojo/pluginjam/database/repository/TeamRepository.java b/backend/src/main/java/de/chojo/pluginjam/database/repository/TeamRepository.java new file mode 100644 index 00000000..cedf24c4 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/database/repository/TeamRepository.java @@ -0,0 +1,20 @@ +package de.chojo.pluginjam.database.repository; + +import de.chojo.pluginjam.database.entity.team.Team; +import io.micronaut.data.annotation.Join; +import io.micronaut.data.jdbc.annotation.JdbcRepository; +import io.micronaut.data.model.query.builder.sql.Dialect; +import io.micronaut.data.repository.CrudRepository; + +import java.util.List; +import java.util.Optional; + +@JdbcRepository(dialect = Dialect.POSTGRES) +@Join(value = "meta", type = Join.Type.LEFT_FETCH) +@Join(value = "members", type = Join.Type.LEFT_FETCH) +public interface TeamRepository extends CrudRepository { + + List findByJamId(Integer jamId); + + Optional findByMembersUserId(Long userId); +} diff --git a/backend/src/main/java/de/chojo/pluginjam/database/repository/VotesRepository.java b/backend/src/main/java/de/chojo/pluginjam/database/repository/VotesRepository.java new file mode 100644 index 00000000..6b9e1d2d --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/database/repository/VotesRepository.java @@ -0,0 +1,22 @@ +package de.chojo.pluginjam.database.repository; + +import de.chojo.pluginjam.database.entity.VoteEntity; +import io.micronaut.data.annotation.Query; +import io.micronaut.data.jdbc.annotation.JdbcRepository; +import io.micronaut.data.model.query.builder.sql.Dialect; +import io.micronaut.data.repository.GenericRepository; + +import java.util.List; + +@JdbcRepository(dialect = Dialect.POSTGRES) +public interface VotesRepository extends GenericRepository { + + @Query("SELECT * FROM gamejam.vote WHERE team_id = :teamId") + List findByTeamId(Long teamId); + + @Query("INSERT INTO gamejam.vote (team_id, voter_id, points) VALUES (:teamId, :voterId, :points) ON CONFLICT (team_id, voter_id) DO UPDATE SET points = :points") + void save(Long teamId, Long voterId, Integer points); + + @Query("DELETE FROM gamejam.vote WHERE team_id = :teamId AND voter_id = :voterId") + void delete(Long teamId, Long voterId); +} diff --git a/backend/src/main/java/de/chojo/pluginjam/database/repository/jam/JamRegistrationRepository.java b/backend/src/main/java/de/chojo/pluginjam/database/repository/jam/JamRegistrationRepository.java new file mode 100644 index 00000000..f48c35b2 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/database/repository/jam/JamRegistrationRepository.java @@ -0,0 +1,22 @@ +package de.chojo.pluginjam.database.repository.jam; + +import de.chojo.pluginjam.database.entity.jam.JamRegistration; +import io.micronaut.data.annotation.Query; +import io.micronaut.data.jdbc.annotation.JdbcRepository; +import io.micronaut.data.model.query.builder.sql.Dialect; +import io.micronaut.data.repository.GenericRepository; + +import java.util.List; + +@JdbcRepository(dialect = Dialect.POSTGRES) +public interface JamRegistrationRepository extends GenericRepository { + + @Query("SELECT * FROM gamejam.jam_registrations WHERE jam_id = :jamId") + List findByJamId(Integer jamId); + + @Query("INSERT INTO gamejam.jam_registrations (jam_id, user_id) VALUES (:jamId, :userId) ON CONFLICT DO NOTHING") + void save(Integer jamId, Long userId); + + @Query("DELETE FROM gamejam.jam_registrations WHERE jam_id = :jamId AND user_id = :userId") + void delete(Integer jamId, Long userId); +} diff --git a/backend/src/main/java/de/chojo/pluginjam/database/repository/jam/JamRepository.java b/backend/src/main/java/de/chojo/pluginjam/database/repository/jam/JamRepository.java new file mode 100644 index 00000000..a5795433 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/database/repository/jam/JamRepository.java @@ -0,0 +1,76 @@ +package de.chojo.pluginjam.database.repository.jam; + +import de.chojo.pluginjam.database.entity.jam.Jam; +import io.micronaut.data.annotation.Join; +import io.micronaut.data.annotation.Query; +import io.micronaut.data.jdbc.annotation.JdbcRepository; +import io.micronaut.data.model.query.builder.sql.Dialect; +import io.micronaut.data.repository.CrudRepository; +import org.jspecify.annotations.NonNull; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +@JdbcRepository(dialect = Dialect.POSTGRES) +public interface JamRepository extends CrudRepository { + + @Join(value = "meta", type = Join.Type.LEFT_FETCH) + @Join(value = "time", type = Join.Type.LEFT_FETCH) + @Join(value = "state", type = Join.Type.LEFT_FETCH) + @Join(value = "registrations", type = Join.Type.LEFT_FETCH) + @NonNull + Optional findById(@NonNull Integer id); + + @Join(value = "meta", type = Join.Type.LEFT_FETCH) + @Join(value = "time", type = Join.Type.LEFT_FETCH) + @Join(value = "state", type = Join.Type.LEFT_FETCH) + @Join(value = "registrations", type = Join.Type.LEFT_FETCH) + List findByGuildId(Long guildId); + + @Join(value = "meta", type = Join.Type.LEFT_FETCH) + @Join(value = "time", type = Join.Type.LEFT_FETCH) + @Join(value = "state", type = Join.Type.LEFT_FETCH) + @Join(value = "registrations", type = Join.Type.LEFT_FETCH) + Optional findByGuildIdAndStateActive(Long guildId, boolean active); + + @Join(value = "meta", type = Join.Type.LEFT_FETCH) + @Join(value = "time", type = Join.Type.LEFT_FETCH) + @Join(value = "state", type = Join.Type.LEFT_FETCH) + @Join(value = "registrations", type = Join.Type.LEFT_FETCH) + List findByGuildIdAndStateActiveAndStateEnded(Long guildId, boolean active, boolean ended); + + @Query(""" + WITH new_jam AS ( + INSERT INTO gamejam.jam (guild_id) VALUES (:guildId) RETURNING id + ), + ins_meta AS ( + INSERT INTO gamejam.jam_meta (jam_id, topic, tagline) SELECT id, :topic ,:tagline FROM new_jam + ), + ins_time AS ( + INSERT INTO gamejam.jam_time (jam_id, zone_id, registration_start, registration_end, jam_start, jam_end) + SELECT id, :zoneId, :registrationStart, :registrationEnd, :jamStart, :jamEnd FROM new_jam + ), + ins_state AS ( + INSERT INTO gamejam.jam_state (jam_id, active, voting, ended) + SELECT id, false, false, false FROM new_jam + ) + SELECT id FROM new_jam + """) + Integer createJam(Long guildId, String topic, String tagline, String zoneId, + LocalDateTime registrationStart, LocalDateTime registrationEnd, + LocalDateTime jamStart, LocalDateTime jamEnd); + + @Query("UPDATE gamejam.jam_state SET active = :active, voting = :voting, ended = :ended WHERE jam_id = :jamId") + void updateState(Integer jamId, boolean active, boolean voting, boolean ended); + + @Query("UPDATE gamejam.jam_state SET voting = :voting WHERE jam_id = :jamId") + void updateVoting(Integer jamId, boolean voting); + + @Query(""" + UPDATE gamejam.jam_state SET active = false, voting = false, ended = true + WHERE jam_id IN (SELECT id FROM gamejam.jam WHERE guild_id = :guildId) + AND active = true + """) + void endActiveJams(Long guildId); +} diff --git a/backend/src/main/java/de/chojo/pluginjam/security/BotApiKeyAuthenticationFetcher.java b/backend/src/main/java/de/chojo/pluginjam/security/BotApiKeyAuthenticationFetcher.java new file mode 100644 index 00000000..a0d6d45c --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/security/BotApiKeyAuthenticationFetcher.java @@ -0,0 +1,34 @@ +package de.chojo.pluginjam.security; + +import io.micronaut.context.annotation.Value; +import io.micronaut.http.HttpRequest; +import io.micronaut.security.authentication.Authentication; +import io.micronaut.security.filters.AuthenticationFetcher; +import jakarta.inject.Singleton; +import org.reactivestreams.Publisher; +import reactor.core.publisher.Mono; + +import java.util.List; + +@Singleton +public class BotApiKeyAuthenticationFetcher implements AuthenticationFetcher> { + + private final String configuredApiKey; + + public BotApiKeyAuthenticationFetcher(@Value("${bot.api-key}") String configuredApiKey) { + this.configuredApiKey = configuredApiKey; + } + + @Override + public Publisher fetchAuthentication(HttpRequest request) { + String requestApiKey = request.getHeaders().get("X-API-Key"); + + if (requestApiKey != null && requestApiKey.equals(configuredApiKey)) { + Authentication authentication = Authentication.build("discord-bot", List.of("ROLE_BOT")); + + return Mono.just(authentication); + } + + return Mono.empty(); + } +} \ No newline at end of file diff --git a/backend/src/main/java/de/chojo/pluginjam/service/JamService.java b/backend/src/main/java/de/chojo/pluginjam/service/JamService.java new file mode 100644 index 00000000..f79696a5 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/service/JamService.java @@ -0,0 +1,93 @@ +package de.chojo.pluginjam.service; + +import de.chojo.pluginjam.database.entity.jam.Jam; +import de.chojo.pluginjam.database.repository.jam.JamRegistrationRepository; +import de.chojo.pluginjam.database.repository.jam.JamRepository; +import de.chojo.pluginjam.database.repository.SettingsRepository; +import jakarta.inject.Singleton; + +import java.time.ZonedDateTime; +import java.util.List; +import java.util.Optional; + +@Singleton +public class JamService { + private final JamRepository jamRepository; + private final JamRegistrationRepository jamRegistrationRepository; + private final SettingsRepository settingsRepository; + + public JamService(JamRepository jamRepository, JamRegistrationRepository jamRegistrationRepository, SettingsRepository settingsRepository) { + this.jamRepository = jamRepository; + this.jamRegistrationRepository = jamRegistrationRepository; + this.settingsRepository = settingsRepository; + } + + public void createJam( + long guildId, + String topic, + String tagline, + String zoneId, + ZonedDateTime startDate, + ZonedDateTime endDate, + ZonedDateTime registrationStartDate, + ZonedDateTime registrationEndDate + ) { + jamRepository.createJam( + guildId, + topic, + tagline, + zoneId, + registrationStartDate.toLocalDateTime(), + registrationEndDate.toLocalDateTime(), + startDate.toLocalDateTime(), + endDate.toLocalDateTime() + ); + } + + public boolean isJamActive(long guildId) { + return jamRepository.findByGuildIdAndStateActive(guildId, true).isPresent(); + } + + public Optional getJamById(int jamId) { + return jamRepository.findById(jamId); + } + + public Optional getActiveJam(long guildId) { + return jamRepository.findByGuildIdAndStateActive(guildId, true); + } + + public Optional getUpComingJam(long guildId) { + return jamRepository.findByGuildIdAndStateActiveAndStateEnded(guildId, false, false) + .stream() + .findFirst(); + } + + public void endActiveJams(long guildId) { + jamRepository.endActiveJams(guildId); + } + + public Optional getCurrentOrUpcoming(long guildId) { + return getUpComingJam(guildId) + .or(() -> getActiveJam(guildId)); + } + + public void setVoting(int jamId, boolean voting) { + jamRepository.updateVoting(jamId, voting); + } + + public void startJam(int id) { + jamRepository.updateState(id, true, false, false); + } + + public List getJams(long guildId) { + return jamRepository.findByGuildId(guildId); + } + + public void registerUser(int jamId, long userId) { + jamRegistrationRepository.save(jamId, userId); + } + + public void unregisterUser(int jamId, long userId) { + jamRegistrationRepository.delete(jamId, userId); + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/service/SettingsService.java b/backend/src/main/java/de/chojo/pluginjam/service/SettingsService.java new file mode 100644 index 00000000..35c0b2d3 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/service/SettingsService.java @@ -0,0 +1,40 @@ +package de.chojo.pluginjam.service; + +import de.chojo.pluginjam.database.entity.SettingsEntity; +import de.chojo.pluginjam.database.repository.SettingsRepository; +import jakarta.inject.Singleton; + +@Singleton +public class SettingsService { + private final SettingsRepository settingsRepository; + + public SettingsService(SettingsRepository settingsRepository) { + this.settingsRepository = settingsRepository; + } + + public SettingsEntity getSettings(long guildId) { + return settingsRepository.findById(guildId).orElseGet(() -> new SettingsEntity(guildId)); + } + + public void saveSettings(SettingsEntity settings) { + settingsRepository.save(settings.getGuildId(), settings.getParticipantRole(), settings.getOrgaRole(), settings.getLocale(), settings.getTeamSize()); + } + + public void setJamRole(long guildId, long roleId) { + var settings = settingsRepository.findById(guildId).orElseGet(() -> new SettingsEntity(guildId)); + settings.setParticipantRole(roleId); + settingsRepository.save(settings.getGuildId(), settings.getParticipantRole(), settings.getOrgaRole(), settings.getLocale(), settings.getTeamSize()); + } + + public void setOrgaRole(long guildId, long roleId) { + var settings = settingsRepository.findById(guildId).orElseGet(() -> new SettingsEntity(guildId)); + settings.setOrgaRole(roleId); + settingsRepository.save(settings.getGuildId(), settings.getParticipantRole(), settings.getOrgaRole(), settings.getLocale(), settings.getTeamSize()); + } + + public void setTeamSize(long guildId, int teamSize) { + var settings = settingsRepository.findById(guildId).orElseGet(() -> new SettingsEntity(guildId)); + settings.setTeamSize(teamSize); + settingsRepository.save(settings.getGuildId(), settings.getParticipantRole(), settings.getOrgaRole(), settings.getLocale(), settings.getTeamSize()); + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/service/TeamService.java b/backend/src/main/java/de/chojo/pluginjam/service/TeamService.java new file mode 100644 index 00000000..275f820a --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/service/TeamService.java @@ -0,0 +1,73 @@ +package de.chojo.pluginjam.service; + +import de.chojo.pluginjam.database.entity.team.Team; +import de.chojo.pluginjam.database.entity.team.TeamMember; +import de.chojo.pluginjam.database.entity.team.TeamMeta; +import de.chojo.pluginjam.database.repository.TeamMemberRepository; +import de.chojo.pluginjam.database.repository.TeamMetaRepository; +import de.chojo.pluginjam.database.repository.TeamRepository; +import jakarta.inject.Singleton; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.Role; +import net.dv8tion.jda.api.entities.User; + +import java.util.List; +import java.util.Optional; + +@Singleton +public class TeamService { + private final TeamRepository teamRepository; + private final TeamMemberRepository teamMemberRepository; + private final TeamMetaRepository teamMetaRepository; + + public TeamService(TeamRepository teamRepository, TeamMemberRepository teamMemberRepository, TeamMetaRepository teamMetaRepository) { + this.teamRepository = teamRepository; + this.teamMemberRepository = teamMemberRepository; + this.teamMetaRepository = teamMetaRepository; + } + + public Team createTeam(long jamId, String name, long leaderId, long roleId, long textChannelId, long voiceChannelId) { + var team = teamRepository.save(new Team(null, (int) jamId, null, List.of())); + teamMetaRepository.save(new TeamMeta(team.id(), name, leaderId, roleId, textChannelId, voiceChannelId, "", "", "")); + return teamRepository.findById(team.id()).orElseThrow(); + } + + public Optional getTeam(int id) { + return teamRepository.findById(id); + } + + public Optional getUserTeam(long userId) { + return teamRepository.findByMembersUserId(userId); + } + + public void removeUserFromTeam(long userId) { + getUserTeam(userId).ifPresent(team -> teamMemberRepository.deleteByTeamIdAndUserId(team.id(), userId)); + } + + public Optional getTeamByName(int jamId, String teamName) { + return teamRepository.findByJamId(jamId).stream() + .filter(team -> team.meta() != null && teamName.equals(team.meta().teamName())) + .findFirst(); + } + + public void joinTeam(Team team, Member user, Guild guild) { + teamMemberRepository.save(new TeamMember(team.id(), user.getIdLong())); + guild.addRoleToMember(user, guild.getRoleById(team.meta().roleId())).queue(); + } + + public void leaveTeam(Team team, Member user, Guild guild) { + teamMemberRepository.deleteByTeamIdAndUserId(team.id(), user.getIdLong()); + guild.removeRoleFromMember(user, guild.getRoleById(team.meta().roleId())).queue(); + } + + public void disbandTeam(Team team, Guild guild) { + teamMemberRepository.deleteAllByTeamId(team.id()); + teamRepository.deleteById(team.id()); + teamMetaRepository.deleteById(team.id()); + + guild.getRoleById(team.meta().roleId()).delete().queue(); + guild.getTextChannelById(team.meta().textChannelId()).delete().queue(); + guild.getVoiceChannelById(team.meta().voiceChannelId()).delete().queue(); + } +} diff --git a/backend/src/main/resources/application-dev.yml b/backend/src/main/resources/application-dev.yml new file mode 100644 index 00000000..c7e8fe19 --- /dev/null +++ b/backend/src/main/resources/application-dev.yml @@ -0,0 +1,7 @@ +micronaut: + security: + oauth2: + clients: + default: + client-id: ${OAUTH_CLIENT_ID:XXX} + client-secret: ${OAUTH_CLIENT_SECRET:YYY} diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml new file mode 100644 index 00000000..2558679f --- /dev/null +++ b/backend/src/main/resources/application.yml @@ -0,0 +1,46 @@ +micronaut: + application: + name: backend + security: + authentication: cookie + oauth2: + enabled: true + clients: + discord: + client-id: ${OAUTH_CLIENT_ID} + client-secret: ${OAUTH_CLIENT_SECRET} + openid: + issuer: ${OAUTH_ISSUER_URL} + scopes: openid,profile + server: + cors: + enabled: true + configurations: + web: + allowed-origins: ${FRONTEND_URL} + allowed-headers: authorization,content-type + allowed-methods: GET,POST,PUT,DELETE,OPTIONS +datasources: + default: + driver-class-name: org.postgresql.Driver + db-type: postgres + dialect: POSTGRES + jdbc-url: jdbc:postgresql://${DB_HOST}:${DB_PORT}/${DB_NAME} + password: ${DB_PASSWORD} + username: ${DB_USERNAME} +flyway: + datasources: + default: + enabled: true + default-schema: gamejam + create-schemas: true + schemas: + - gamejam +bot: + api-key: ${BOT_API_KEY} + token: ${BOT_TOKEN} + +logger: + level: + de.chojo: TRACE + io.micronaut.data: TRACE \ No newline at end of file diff --git a/backend/src/main/resources/db/migration/V1__create_initial_tables.sql b/backend/src/main/resources/db/migration/V1__create_initial_tables.sql new file mode 100644 index 00000000..e599a1c7 --- /dev/null +++ b/backend/src/main/resources/db/migration/V1__create_initial_tables.sql @@ -0,0 +1,143 @@ +CREATE SEQUENCE gamejam.jam_times_id_seq + AS INTEGER; + +CREATE TABLE IF NOT EXISTS gamejam.jam +( + id SERIAL, + guild_id BIGINT NOT NULL, + CONSTRAINT jam_pk + PRIMARY KEY (id) +); + +CREATE INDEX IF NOT EXISTS jam_guild_id_index + ON gamejam.jam (guild_id); + +CREATE TABLE IF NOT EXISTS gamejam.jam_time +( + jam_id INTEGER NOT NULL, + registration_start TIMESTAMP NOT NULL, + registration_end TIMESTAMP NOT NULL, + zone_id TEXT NOT NULL, + jam_start TIMESTAMP NOT NULL, + jam_end TIMESTAMP NOT NULL, + CONSTRAINT jam_times_pk + PRIMARY KEY (jam_id), + CONSTRAINT jam_times_jam_id_fk + FOREIGN KEY (jam_id) REFERENCES gamejam.jam + ON DELETE CASCADE +); + +ALTER SEQUENCE gamejam.jam_times_id_seq OWNED BY gamejam.jam_time.jam_id; + +CREATE TABLE IF NOT EXISTS gamejam.jam_meta +( + jam_id INTEGER NOT NULL, + topic TEXT NOT NULL, + tagline TEXT NOT NULL, + CONSTRAINT jam_topic_pk + PRIMARY KEY (jam_id), + CONSTRAINT jam_topic_jam_id_fk + FOREIGN KEY (jam_id) REFERENCES gamejam.jam + ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS gamejam.team +( + id SERIAL, + jam_id INTEGER NOT NULL, + CONSTRAINT team_pk + PRIMARY KEY (id), + CONSTRAINT team_jam_id_fk + FOREIGN KEY (jam_id) REFERENCES gamejam.jam + ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS gamejam.team_member +( + team_id INTEGER NOT NULL, + user_id BIGINT, + CONSTRAINT team_member_team_id_fk + FOREIGN KEY (team_id) REFERENCES gamejam.team + ON DELETE CASCADE +); + +CREATE UNIQUE INDEX IF NOT EXISTS team_member_team_id_user_id_uindex + ON gamejam.team_member (team_id, user_id); + +CREATE INDEX IF NOT EXISTS team_member_team_id_index + ON gamejam.team_member (team_id); + +CREATE TABLE IF NOT EXISTS gamejam.team_meta +( + team_id INTEGER NOT NULL, + team_name TEXT NOT NULL, + leader_id BIGINT DEFAULT 0 NOT NULL, + role_id BIGINT DEFAULT 0 NOT NULL, + text_channel_id BIGINT DEFAULT 0 NOT NULL, + voice_channel_id BIGINT DEFAULT 0 NOT NULL, + project_description TEXT DEFAULT '' NOT NULL, + project_url TEXT DEFAULT '' NOT NULL, + token TEXT, + CONSTRAINT team_meta_pk + PRIMARY KEY (team_id), + CONSTRAINT team_meta_team_id_fk + FOREIGN KEY (team_id) REFERENCES gamejam.team + ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS gamejam.votes +( + team_id BIGINT NOT NULL, + voter_id BIGINT NOT NULL, + points INTEGER DEFAULT 0 NOT NULL, + CONSTRAINT vote_team_id_fk + FOREIGN KEY (team_id) REFERENCES gamejam.team + ON DELETE CASCADE +); + +CREATE UNIQUE INDEX IF NOT EXISTS vote_team_id_voter_id_uindex + ON gamejam.votes (team_id, voter_id); + +CREATE OR REPLACE VIEW gamejam.team_ranking AS +SELECT ROW_NUMBER() OVER (PARTITION BY jam_id ORDER BY points DESC ) as rank, team_id, points, jam_id +FROM (SELECT team_id, SUM(points) AS points + FROM gamejam.votes + GROUP BY team_id) a + LEFT JOIN gamejam.team t ON t.id = a.team_id +ORDER BY points DESC; + +CREATE TABLE IF NOT EXISTS gamejam.jam_registrations +( + jam_id INTEGER NOT NULL, + user_id BIGINT NOT NULL, + CONSTRAINT jam_registrations_jam_id_fk + FOREIGN KEY (jam_id) REFERENCES gamejam.jam + ON DELETE CASCADE +); + +CREATE UNIQUE INDEX IF NOT EXISTS jam_registrations_jam_id_user_id_uindex + ON gamejam.jam_registrations (jam_id, user_id); + +CREATE TABLE IF NOT EXISTS gamejam.jam_state +( + jam_id INTEGER NOT NULL, + active BOOLEAN DEFAULT FALSE NOT NULL, + voting BOOLEAN DEFAULT FALSE NOT NULL, + ended BOOLEAN DEFAULT FALSE NOT NULL, + CONSTRAINT jam_state_pk + PRIMARY KEY (jam_id), + CONSTRAINT jam_state_jam_id_fk + FOREIGN KEY (jam_id) REFERENCES gamejam.jam + ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS gamejam.settings +( + guild_id BIGINT DEFAULT 0 NOT NULL, + manager_role BIGINT DEFAULT 0, + participant_role BIGINT DEFAULT 0 NOT NULL, + team_size INTEGER DEFAULT 4 NOT NULL, + locale TEXT, + CONSTRAINT settings_pk + PRIMARY KEY (guild_id) +); diff --git a/backend/src/main/resources/locale/de.ftl b/backend/src/main/resources/locale/de.ftl new file mode 100644 index 00000000..4cc74eeb --- /dev/null +++ b/backend/src/main/resources/locale/de.ftl @@ -0,0 +1,264 @@ +command-jamadmin-create-description=Neuen Game-Jam erstellen +command-jamadmin-create-options-jamend-description=Game-Jam-Ende. $words.format$: $words.dateformat$ +command-jamadmin-create-options-jamstart-description=Game-Jam-Start. $words.format$: $words.dateformat$ +command-jamadmin-create-message-created=Game-Jam erstellt. +command-jamadmin-create-options-registerend-description=Registrierungen schließen. $words.format$: $words.dateformat$ +command-jamadmin-create-options-registerstart-description=Registrierungen öffnen. $words.format$: $words.dateformat$ +command-jamadmin-create-options-tagline-description=Topic-Tagline als Zusatz zum Thema +command-jamadmin-create-options-timezone-description=Die Zeitzone des Game Jams. Zum Beispiel "Europe/Berlin". +command-jamadmin-create-options-topic-description=Das Thema des Game-Jam +command-jamadmin-description=Verwalte Game-Jams +command-jamadmin-jam-end-options-confirm-description=Bestätige das Ende. +command-jamadmin-jam-description=Verwalte Game-Jams +command-jamadmin-jam-end-description=Beendet den aktuellen Game-Jam. Löscht alle Rollen und Kanäle +command-jamadmin-jam-end-message-ended=Game-Jam beendet. +command-jamadmin-jam-start-description=Startet den nächsten geplanten Game-Jam +command-jamadmin-votes-close-description=Abstimmungen für den aktuellen Game-Jam schließen. +command-jamadmin-votes-description=Verwalte Abstimmungen +command-jamadmin-votes-message-closed=Abstimmungen für den aktuellen Game-Jam geschlossen. +command-jamadmin-votes-message-opened=Abtimmung für den aktuellen Game-Jam geöffnet. +command-jamadmin-votes-open-description=Abtimmung für den aktuellen Game-Jam öffnen. +command-register-description=Du hast dich für einen kommenden Game-Jam angemeldet. +command-register-message-alreadyregistered=Du bist bereits registriert. +command-register-message-notanymore=Registrierungen sind geschlossen. +command-register-message-notyet=Du kannst dich noch nicht für diesen Game-Jam anmelden. Du kannst dich am %TIMESTAMP% anmelden. +command-register-message-registered=Du hast dich für den nächsten Game-Jam angemeldet. Er beginnt am %TIMESTAMP%. +command-server-configure-description=Server konfigurieren +command-server-configure-maxplayers-options-amount-description=Maximale Anzahl von Spielern. +command-server-configure-maxplayers-description=Die maximale Spieleranzahl für diesen Server festlegen +command-server-configure-maxplayers-message-success=Maximale Spielerzahl festlegen. +command-server-configure-message-description=Lege die Willkommensnachricht fest +command-server-configure-message-message-modal-input-message-label=Willkommensnachricht +command-server-configure-message-message-modal-input-message-placeholder=Willkommensnachricht +command-server-configure-message-message-modal-title=Definiere die Willkommensnachricht +command-server-configure-message-message-success=Nachricht einstellen. +command-server-configure-spectatoroverflow-description=Aktiviere den Zuschauerüberlauf +command-server-configure-spectatoroverflow-message-success=Überlauf einstellen. +command-server-configure-spectatoroverflow-options-state-description=True, um den Überlauf zu aktivieren +command-server-configure-whitelist-description=Aktiviere die Whitelist +command-server-configure-whitelist-message-success=Whitelist gesetzt. +command-server-configure-whitelist-options-state-description=True, um die Whitelist zu aktivieren +command-server-description=Verwalte deinen Server +command-server-download-description=Dateien herunterladen +command-server-download-downloadplugindata-message-fail-filetolarge=Die Datei ist zu groß. +command-server-download-downloadplugindata-message-fail-tempfile=Temporäre Datei konnte nicht erstellt werden +command-server-download-downloadplugindata-message-fail-zip=Daten konnten nicht gezippt werden. +command-server-download-downloadplugindata-message-success=Upload durchgeführt +command-server-download-downloadplugindata-message-zipping=Daten zippen. +command-server-download-plugindata-description=Plugin-Dateien herunterladen +command-server-download-plugindata-options-path-description=Pfad im Plugin-Verzeichnis +command-server-plugins-description=Installierte Plugins verwalten +command-server-plugins-install-description=Ein anderes Plugin installieren +command-server-plugins-install-message-fail=Plugin konnte nicht installiert werden +command-server-plugins-install-message-success=Installiert %NAME%. Neustart, um Änderungen zu übernehmen +command-server-plugins-install-options-plugin-description=Zu installierendes Plugin +command-server-plugins-uninstall-options-deletedata-description=Richtig, um auch die Plugin-Daten zu löschen +command-server-plugins-uninstall-description=Zu deinstallierendes Plugin +command-server-plugins-uninstall-message-success-plugin=Plugin deinstalliert. +command-server-plugins-uninstall-message-success-pluginanddata=Plugin deinstalliert und Daten gelöscht +command-server-plugins-uninstall-options-plugin-description=Das zu deinstallierende Plugin +command-server-process-console-options-command-description=Der zu sendende Befehl +command-server-process-console-description=Einen Befehl über die Konsole senden +command-server-process-console-message-executed=Ausgeführt +command-server-process-console-message-notexecutable=Diese Befehle können nicht ausgeführt werden +command-server-process-description=Serverprozess verwalten +command-server-process-log-description=Neustart des Servers +command-server-process-restart-description=Neustart des Servers +command-server-process-restart-message-restarted=Der Server wurde neu gestartet. +command-server-process-restart-message-restarting=Server neu gestartet +command-server-process-start-description=Start des Servers +command-server-process-start-message-fail=Der Server konnte nicht gestartet werden. Er läuft bereits oder ist nicht eingerichtet. +command-server-process-start-message-success=Server gestartet +command-server-process-status-description=Status des Servers +command-server-process-stop-description=Server anhalten +command-server-process-stop-message-stopped=Server gestoppt +command-server-process-stop-message-stopping=Server gestoppt +command-server-system-delete-description=Löschen der Serverdaten +command-server-system-delete-message-error=Beim Löschen des Servers ist etwas schief gelaufen +command-server-system-delete-message-notsetup=Server ist nicht eingerichtet. +command-server-system-delete-message-success=Der Server wurde erfolgreich gelöscht. +command-server-system-description=Verwalten des Serversystems +command-server-system-setup-description=Den Server einrichten +command-server-system-setup-message-alreadysetup=Der Server wurde bereits erstellt. +command-server-system-setup-message-error=Bei der Einrichtung des Servers ist etwas schief gelaufen +command-server-system-setup-message-success=Der Server wurde erfolgreich eingerichtet. +command-server-upload-description=Dateien hochladen +command-server-upload-plugin-description=Dein Plugin hochladen +command-server-upload-plugin-options-file-description=Deine Plugin-Datei +command-server-upload-plugin-message-fail=Plugin konnte nicht hinzugefügt werden. +command-server-upload-plugin-message-success=Plugin hinzugefügt oder ersetzt. +command-server-upload-plugindata-description=Plugin-Daten hochladen +command-server-upload-plugindata-options-file-description=Datei zum Hochladen +command-server-upload-plugindata-options-path-description=Pfad im Plugin-Verzeichnis +command-server-upload-uploadplugindata-message-fail=Datei konnte nicht hinzugefügt werden. +command-server-upload-uploadplugindata-message-success=Datei hinzugefügt oder ersetzt. +command-server-upload-world-description=Eine Welt hochladen, die die aktuelle Welt ersetzt +command-server-upload-world-options-file-description=Welt als zip +command-server-upload-world-message-failed=Ersetzen der Welt fehlgeschlagen +command-server-upload-world-message-nofileorurl=Keine Datei oder Url angegeben +command-server-upload-world-message-replaced=Ersetzte Welt +command-server-upload-world-message-replacing=Download abgeschlossen und ersetzt. +command-server-upload-world-options-url-description=Link zum Herunterladen der Welt als zip +command-server-util-progressdownloader-message-attempting=Ich versuche, die Datei herunterzuladen. +command-server-util-progressdownloader-message-done=Download abgeschlossen. +command-server-util-progressdownloader-message-downloading=Datei wird heruntergeladen. +command-server-util-progressdownloader-message-fail-download=Datei konnte nicht heruntergeladen werden. +command-server-util-progressdownloader-message-fail-tempfile=Temporäre Datei kann nicht erstellt werden +command-serveradmin-description=Verwaltung der Teamserver +command-serveradmin-info-description=Server-Informationen +command-serveradmin-info-detailed-description=Detaillierte Informationen zu Teamservern +command-serveradmin-info-detailed-options-team-description=team +command-serveradmin-info-short-description=Kurze Informationen über Teamserver +command-serveradmin-refresh-all-description=Alle Server aktualisieren +command-serveradmin-refresh-description=Aktualisiere die Dateien der Vorlage auf allen Servern. +command-serveradmin-refresh-refreshall-message-refreshed=%AMOUNT% Server aktualisiert. +command-serveradmin-refresh-refreshteam-message-failed=Bei der Aktualisierung von %TEAM% ist ein Fehler aufgetreten. +command-serveradmin-refresh-refreshteam-message-refreshed=Der Server von Team %TEAM% wurde aktualisiert. +command-serveradmin-refresh-team-description=Aktualisiere einen Teamserver +command-serveradmin-refresh-team-options-team-description=team +command-serveradmin-restart-all-description=Alle Server starten +command-serveradmin-restart-description=Server neu starten +command-serveradmin-restart-restartall-message-restarted=Neustart von %AMOUNT% Servern. +command-serveradmin-restart-restartteam-message-failed=Server von Team %TEAM% wurde nicht gestartet. +command-serveradmin-restart-restartteam-message-restarted=Der Server von Team %TEAM% wurde neu gestartet. +command-serveradmin-restart-team-description=Start eines Team-Servers +command-serveradmin-restart-team-options-team-description=team +command-serveradmin-start-all-description=Alle Server starten +command-serveradmin-start-description=Server starten +command-serveradmin-start-startall-message-started=Starte %AMOUNT% Server. +command-serveradmin-start-startteam-message-failed=Der Server von Team %TEAM% konnte nicht gestartet werden. +command-serveradmin-start-startteam-message-started=Server von Team %TEAM% wurde gestartet. +command-serveradmin-start-team-description=Starte einen Teamserver +command-serveradmin-start-team-options-team-description=Team +command-serveradmin-stop-all-description=Alle Server anhalten +command-serveradmin-stop-description=Server anhalten +command-serveradmin-stop-stopall-message-stopped=%AMOUNT% Server gestoppt. +command-serveradmin-stop-stopteam-message-failed=Server von Team %TEAM% läuft nicht. +command-serveradmin-stop-stopteam-message-stopped=Server von Team %TEAM% wurde gestoppt. +command-serveradmin-stop-team-description=Einen Teamserver stoppen +command-serveradmin-stop-team-options-team-description=team +command-serveradmin-syncvelocity-description=Serverstatus mit Velocity-Daten synchronisieren +command-serveradmin-syncvelocity-message-synced=Server synchronisiert +command-settings-description=Verwalte die Bot-Einstellungen +command-settings-info-description=Zeige die aktuellen Einstellungen +command-settings-info-embed-jamrole=Game-Jam Rolle +command-settings-info-embed-orgarole=Organisations-Rolle +command-settings-info-embed-settings=Einstellungen +command-settings-info-embed-teamsize=Max Team Size +command-settings-jamrole-description=Stellt die Rolle ein, die den registrierten Mitgliedern zugewiesen wird. +command-settings-jamrole-message-updated=Aktualisiere die Game-Jam Rolle. +command-settings-jamrole-options-role-description=Die Rolle, die nach der Registrierung zugewiesen wird +command-settings-locale-description=Ändert die Bot-Sprache. +command-settings-locale-options-locale-description=Die neue Sprache +command-settings-locale-message-invalid=Ungültige Sprache +command-settings-locale-message-updated=Sprache aktualisiert +command-settings-orgarole-description=Definiere die Rolle des Organisationsteams +command-settings-orgarole-options-role-description=Die Rolle, die den Bot verwalten kann +command-settings-teamsize-description=Definiere die maximale Teamgröße. +command-settings-teamsize-message-updated=Aktualisierte maximale Teamgröße. +command-settings-teamsize-options-size-description=Die maximale Teamgröße. +command-start-message-activated=Der Teamstatus wurde auf aktiv geändert. +command-team-create-description=Erstelle ein Team +command-team-create-message-alreadymember=Du bist bereits Teil eines Teams. Um dein eigenes Team zu erstellen, musst du es erst verlassen. +command-team-create-message-created=Team erstellt. +command-team-create-message-nametaken=Dieser Teamname ist bereits vergeben. +command-team-create-message-unregistered=Du musst dich erst registrieren, um ein Team zu erstellen +command-team-create-options-name-description=Name deines Teams +command-team-description=Verwalte dein Team +command-team-disband-options-confirm-description=Bestätige die Löschung deines Teams mit "true" +command-team-disband-description=Löse dein Team auf +command-team-disband-message-disbanded=Dein Team wurde aufgelöst. +command-team-invite-alreadyMember=Du bist bereits Mitglied in einem Team. +command-team-invite-description=Lade jemanden in dein Team ein +command-team-invite-gameJamOver=Der Game-Jam ist vorbei +command-team-invite-joined=Du bist dem Team beigetreten. +command-team-invite-joinedBroadcast=%USER% ist dem Team beigetreten. +command-team-invite-message-accept=Akzeptieren +command-team-invite-message-invitation=%USER% hat dich eingeladen, dem Team %TEAM% beizutreten. +command-team-invite-message-invited=Du hast eine Einladung für den Game-Jam auf %GUILD% erhalten +command-team-invite-message-noleader=Nur die/die Gruppenleiter:in kann einladen. +command-team-invite-message-notRegistered=Dieser Benutzer ist nicht für den Game-Jam registriert. +command-team-invite-message-partofteam=Dieser/diese Benutzer:in ist bereits Teil eines Teams. +command-team-invite-message-send=Einladung senden. +command-team-invite-options-user-description=Der/Die Benutzer:in, den/die du einladen möchtest +command-team-leave-description=Verlasse dein Team +command-team-leave-left=Du hast das Team verlassen. +command-team-leave-leftBroadcast=%USER% hat das Team verlassen. +command-team-leave-message-leaderleave=Der Anführer kann das Team nicht verlassen. +command-team-list-description=Erhalte eine Liste alles existierenden Teams. +command-team-profile-description=Zeigt das Profil eines Teams oder dein eigenes an +command-team-profile-leader=Leiter +command-team-profile-member=Mitglied +command-team-profile-message-nouserteam=Dieser Benutzer ist nicht Teil eines Teams. +command-team-profile-projecturl=Projekt Adresse +command-team-profile-options-team-description=Dieses Team anzeigen +command-team-profile-options-user-description=Das Team dieses Benutzers anzeigen +command-team-promote-description=Ändere den/die Teamleiter:in des Teams +command-team-promote-message-done=Der/Die Teamleiter:in wurde geändert +command-team-promote-message-notinteam=Diese/r Nutzer:in ist nicht Teil des Teams. +command-team-promote-options-user-description=Der/Die neue Teamleiter:in. +command-team-rename-description=Ändere den Namen des Teams. +command-team-rename-message-done=Der Name wurde geändert. +command-team-rename-options-name-description=Der neue Teamname +command-unregister-description=Ziehe deine Registrierung zurück. +command-unregister-message-inteam=Du musst erst dein Team verlassen. +command-unregister-message-notregistered=Du bist derzeit für keinen Game-Jam registriert. +command-unregister-message-unregistered=Deine Registrierung wurde zurückgezogen. +command-votes-description=Bewertung vergeben +command-votes-info-description=Informationen über deine Stimmen. +command-votes-info-embed-title=Vergebene Punkte +command-votes-ranking-description=Das aktuelle Ranking +command-votes-ranking-embed-votes=Punkte +command-votes-ranking-message-voteactive=Abstimmung aktiv. Die Rangliste kann erst nach dem Beenden der Abstimmung angezeigt werden. +command-votes-vote-description=Stimme für ein Team ab. +command-votes-vote-message-done=Du hast %TEAM% %POINTS% punkte gegeben. Verbleibende Punkte: %REMAINING% +command-votes-vote-message-maxpointsreached=Du hast das maximum an Punkten vergeben. Entferne Punkte von anderen teams wenn du weiter Punkte vergeben willst. Verbleibende Punkte: %REMAINING% +command-votes-vote-message-notactive=Es ist keine Abstimmung aktiv. +command-votes-vote-message-ownteam=Du kannst nicht für dein eigenes Team abstimmen. +command-votes-vote-options-points-description=Die Punkte zum vergeben +command-votes-vote-options-team-description=Das Tam für das du abstimmen möchtest. +command-team-api-description=Informationen über die team api +error-alreadyActive=Ein Game-Jam ist bereits aktiv. +error-invalidpath=Ungültiger Pfad +error-invalidrimeformat=Ungültiges Zeitformat. Das Format ist %FORMAT% +error-invalidtimezone=Ungültige Zeitzone. +error-maxteamsize=Das Team hat die maximale Größe erreicht. +error-noactivejam=Es gibt keinen aktiven Game-Jam. +error-noconfirm=Bitte bestätige mit dem Parameter confirm +error-nojamactive=Es ist kein Game-Jam im Gange. Teams sind nicht verfügbar. +error-noleader=Nur der/die Teamleiter:in kann dies tun. +error-noteam=Du bist nicht Teil eines Teams. +error-notregistered=Du bist für keinen Game-Jam registriert. +error-noupcomingjam=Es gibt keinen bevorstehenden Game-Jams. +error-pluginnotfound=Plugin nicht gefunden +error-servernotrunning=Server ist nicht online. +error-unkownteam=Dieses Team existiert nicht. +error-votingactive=Das ist nicht möglich während eine Abstimmung aktiv ist. + +teamserver-message-detailstatus-existing-description=Server läuft +teamserver-message-detailstatus-nonexisting-description=Server nicht eingerichtet. + +word-activethreads=Aktive Threads +word-api=Api +word-averageticktime=Durchschnittliche Tick-Zeit +word-max=Max +word-memory=Speicher +word-min=min +word-notrunning=Läuft nicht +word-players=Spieler +word-ports=Ports +word-server=Server +word-serversetup=Server eingerichtet +word-system=System +word-total=Gesamt +word-tps=Tps +word-used=Verwendet +word-upcoming=Bevorstehend +word-ended=Beendet +word-active=Aktiv +word-jamlist=Jam-Liste + +words-dateformat=yyyy.MM.dd HH:mm +words-format=Format +command-team-edit-description=Bearbeite Team Informationen +command-team-profile-projectdescription=Projekt Beschreibung diff --git a/backend/src/main/resources/locale/en.ftl b/backend/src/main/resources/locale/en.ftl new file mode 100644 index 00000000..cc4f701a --- /dev/null +++ b/backend/src/main/resources/locale/en.ftl @@ -0,0 +1,264 @@ +command-jamadmin-create-description=Create a new game jam +command-jamadmin-create-options-jamend-description=Game Jam end. $words.format$: $words.dateformat$ +command-jamadmin-create-options-jamstart-description=Game Jam start. $words.format$: $words.dateformat$ +command-jamadmin-create-message-created=Jam created. +command-jamadmin-create-options-registerend-description=Registrations close. $words.format$: $words.dateformat$ +command-jamadmin-create-options-registerstart-description=Registrations opening. $words.format$: $words.dateformat$ +command-jamadmin-create-options-tagline-description=Topic tagline as an addition to the topic +command-jamadmin-create-options-timezone-description=The timezone of the game jam. "Europe/Berlin" for example. +command-jamadmin-create-options-topic-description=The topic of the game jam +command-jamadmin-description=Manage jams +command-jamadmin-jam-end-options-confirm-description=Confirm the end +command-jamadmin-jam-description=Manage jams +command-jamadmin-jam-end-description=Ends the currently active jam. Deletes all roles and channel +command-jamadmin-jam-end-message-ended=Jam ended. +command-jamadmin-jam-start-description=Start the next scheduled jam +command-jamadmin-votes-close-description=Close votes for the current jam +command-jamadmin-votes-description=Manage votes +command-jamadmin-votes-message-closed=Votes closed for the current jam. +command-jamadmin-votes-message-opened=Votes for the current jam opened. +command-jamadmin-votes-open-description=Open votes for the current jam +command-register-description=Register for an upcomming Game Jam +command-register-message-alreadyregistered=You are already registered. +command-register-message-notanymore=Registrations are closed. +command-register-message-notyet=You cant register for this game jam yet. You can register at %TIMESTAMP% +command-register-message-registered=You have registered yourself for the next game jam. It will start at %TIMESTAMP%. +command-server-configure-description=Configure server +command-server-configure-maxplayers-options-amount-description=Max amount of players. +command-server-configure-maxplayers-description=Set the max players of this server +command-server-configure-maxplayers-message-success=Max players set. +command-server-configure-message-description=Set the welcome message +command-server-configure-message-message-modal-input-message-label=Welcome message +command-server-configure-message-message-modal-input-message-placeholder=Welcome message +command-server-configure-message-message-modal-title=Define the welcome message +command-server-configure-message-message-success=Message set. +command-server-configure-spectatoroverflow-description=Active the spectator overflow +command-server-configure-spectatoroverflow-message-success=Overflow set. +command-server-configure-spectatoroverflow-options-state-description=True to enable overflow +command-server-configure-whitelist-description=Enable the whitelist +command-server-configure-whitelist-message-success=Whitelist set. +command-server-configure-whitelist-options-state-description=True to enable whitelist +command-server-description=Manage your server +command-server-download-description=Download files +command-server-download-downloadplugindata-message-fail-filetolarge=File is too large. +command-server-download-downloadplugindata-message-fail-tempfile=Failed to create temp file +command-server-download-downloadplugindata-message-fail-zip=Failed to zip data. +command-server-download-downloadplugindata-message-success=Upload done +command-server-download-downloadplugindata-message-zipping=Zipping data. +command-server-download-plugindata-description=Download plugin files +command-server-download-plugindata-options-path-description=Path in the plugin directory +command-server-plugins-description=Manage installed plugins +command-server-plugins-install-description=Install another plugin +command-server-plugins-install-message-fail=Could not install plugin +command-server-plugins-install-message-success=Installed %NAME%. Restart to apply changes +command-server-plugins-install-options-plugin-description=Plugin to install +command-server-plugins-uninstall-options-deletedata-description=True to delete the plugin data as well +command-server-plugins-uninstall-description=Plugin to uninstall +command-server-plugins-uninstall-message-success-plugin=Uninstalled plugin. +command-server-plugins-uninstall-message-success-pluginanddata=Uninstalled plugin and deleted data +command-server-plugins-uninstall-options-plugin-description=The plugin to uninstall +command-server-process-console-options-command-description=The command to send +command-server-process-console-description=Send a command via console +command-server-process-console-message-executed=Executed +command-server-process-console-message-notexecutable=Those commands can not be executed +command-server-process-description=Manage server process +command-server-process-log-description=Restart the server +command-server-process-restart-description=Restart the server +command-server-process-restart-message-restarted=Server restarted. +command-server-process-restart-message-restarting=Server restarting +command-server-process-start-description=Start the server +command-server-process-start-message-fail=Could not start server. It is already running or not set up. +command-server-process-start-message-success=Server started +command-server-process-status-description=Server status +command-server-process-stop-description=Stop the server +command-server-process-stop-message-stopped=Server stopped +command-server-process-stop-message-stopping=Stopping server +command-server-system-delete-description=Delete the server data +command-server-system-delete-message-error=Something went wrong during server deletion +command-server-system-delete-message-notsetup=Server is not set up. +command-server-system-delete-message-success=Server was deleted successfully. +command-server-system-description=Manage the server system +command-server-system-setup-description=Setup the server +command-server-system-setup-message-alreadysetup=Server was already created. +command-server-system-setup-message-error=Something went wrong during server setup +command-server-system-setup-message-success=Server was setup successfully. +command-server-upload-description=Upload files +command-server-upload-plugin-description=Upload your plugin +command-server-upload-plugin-options-file-description=Your plugin file +command-server-upload-plugin-message-fail=Failed to add plugin. +command-server-upload-plugin-message-success=Added or replaced plugin. +command-server-upload-plugindata-description=Upload plugin data +command-server-upload-plugindata-options-file-description=File to upload +command-server-upload-plugindata-options-path-description=Path in the plugin directory +command-server-upload-uploadplugindata-message-fail=Failed to add file. +command-server-upload-uploadplugindata-message-success=Added or replaced file. +command-server-upload-world-description=Upload a world replacing the current world +command-server-upload-world-options-file-description=World as zip +command-server-upload-world-message-failed=Failed to replace world +command-server-upload-world-message-nofileorurl=No file or url provided +command-server-upload-world-message-replaced=Replaced world +command-server-upload-world-message-replacing=Download done. Replacing. +command-server-upload-world-options-url-description=Link to download the world as zip +command-server-util-progressdownloader-message-attempting=Attempting to download file. +command-server-util-progressdownloader-message-done=Download done. +command-server-util-progressdownloader-message-downloading=Downloading file. +command-server-util-progressdownloader-message-fail-download=Could not download file. +command-server-util-progressdownloader-message-fail-tempfile=Failed to create temp file +command-serveradmin-description=Administration of team servers +command-serveradmin-info-description=Server information +command-serveradmin-info-detailed-description=Detailed information about team servers +command-serveradmin-info-detailed-options-team-description=team +command-serveradmin-info-short-description=Short information about team servers +command-serveradmin-refresh-all-description=Refresh all server +command-serveradmin-refresh-description=Refresh files of the template in all servers. +command-serveradmin-refresh-refreshall-message-refreshed=Refreshed %AMOUNT% servers. +command-serveradmin-refresh-refreshteam-message-failed=Failed during refresh of %TEAM%. +command-serveradmin-refresh-refreshteam-message-refreshed=Server of team %TEAM% refreshed. +command-serveradmin-refresh-team-description=Refresh a team server +command-serveradmin-refresh-team-options-team-description=team +command-serveradmin-restart-all-description=Start all server +command-serveradmin-restart-description=Restart servers +command-serveradmin-restart-restartall-message-restarted=Restarted %AMOUNT% servers. +command-serveradmin-restart-restartteam-message-failed=Server of team %TEAM% was not running. +command-serveradmin-restart-restartteam-message-restarted=Server of team %TEAM% restarted. +command-serveradmin-restart-team-description=Start a team server +command-serveradmin-restart-team-options-team-description=team +command-serveradmin-start-all-description=Start all server +command-serveradmin-start-description=Start servers +command-serveradmin-start-startall-message-started=Started %AMOUNT% servers. +command-serveradmin-start-startteam-message-failed=Could not start server of team %TEAM%. +command-serveradmin-start-startteam-message-started=Server of team %TEAM% started. +command-serveradmin-start-team-description=Start a team server +command-serveradmin-start-team-options-team-description=team +command-serveradmin-stop-all-description=Stop all server +command-serveradmin-stop-description=Stop servers +command-serveradmin-stop-stopall-message-stopped=Stopped %AMOUNT% servers. +command-serveradmin-stop-stopteam-message-failed=Server of team %TEAM% is not running. +command-serveradmin-stop-stopteam-message-stopped=Server of team %TEAM% stopped. +command-serveradmin-stop-team-description=Stop a team server +command-serveradmin-stop-team-options-team-description=team +command-serveradmin-syncvelocity-description=Sync servers with velocity data +command-serveradmin-syncvelocity-message-synced=Synced server +command-settings-description=Manage bot settings +command-settings-info-description=Show the current settings +command-settings-info-embed-jamrole=Game Jam Role +command-settings-info-embed-orgarole=Organization Role +command-settings-info-embed-settings=Settings +command-settings-info-embed-teamsize=Max Team Size +command-settings-jamrole-description=Set the role which will be assigned to registered members. +command-settings-jamrole-message-updated=Updated the game jam role. +command-settings-jamrole-options-role-description=The role to assign after registration +command-settings-locale-description=Change the bot language. +command-settings-locale-options-locale-description=The new language +command-settings-locale-message-invalid=Invalid locale +command-settings-locale-message-updated=Locale updated +command-settings-orgarole-description=Define the organisation team role +command-settings-orgarole-options-role-description=The role which can manage the bot +command-settings-teamsize-description=Define the max team size. +command-settings-teamsize-message-updated=Updated max team size. +command-settings-teamsize-options-size-description=The max team size. +command-start-message-activated=Jam state changed to active. +command-team-create-description=Create a team +command-team-create-message-alreadymember=You are already part of a team. You need to leave first to create your own team. +command-team-create-message-created=Team created. +command-team-create-message-nametaken=This team name is already taken. +command-team-create-message-unregistered=You need to register first to create a team +command-team-create-options-name-description=Name of your team +command-team-description=Manage your team +command-team-disband-options-confirm-description=Confirm deletion of your team with "true" +command-team-disband-description=Disband your team +command-team-disband-message-disbanded=Your team was disbanded. +command-team-invite-alreadyMember=You are already member of a team. +command-team-invite-description=Invite someone to your team +command-team-invite-gameJamOver=The game jam is over +command-team-invite-joined=You joined the team. +command-team-invite-joinedBroadcast=%USER% joined the team. +command-team-invite-message-accept=Accept +command-team-invite-message-invitation=%USER% invited you to join their team %TEAM%. +command-team-invite-message-invited=You received a invitation for the game jam on %GUILD% +command-team-invite-message-noleader=Only the group leader can invite people. +command-team-invite-message-notRegistered=This user is not registered for the game jam. +command-team-invite-message-partofteam=This user is already part of a team. +command-team-invite-message-send=Invitation send. +command-team-invite-options-user-description=The user you want to invite +command-team-leave-description=Leave your team +command-team-leave-left=You left the team. +command-team-leave-leftBroadcast=%USER% left the team. +command-team-leave-message-leaderleave=The leader cant leave the team. +command-team-list-description=Get a list of all existing teams. +command-team-profile-description=Shows the profile of a team or your own +command-team-profile-leader=Leader +command-team-profile-member=Member +command-team-profile-message-nouserteam=This user is not part of a team. +command-team-profile-projecturl=Project Url +command-team-profile-options-team-description=Show this team +command-team-profile-options-user-description=Show team of this user +command-team-promote-description=Change the team leader. +command-team-promote-message-done=Changed the team leader. +command-team-promote-message-notinteam=This user is not part of your team. +command-team-promote-options-user-description=The new team leader. +command-team-rename-description=Change the team name. +command-team-rename-message-done=Team name changed. +command-team-rename-options-name-description=The new team name +command-unregister-description=Withdraw your registration +command-unregister-message-inteam=You need to leave your team first. +command-unregister-message-notregistered=You are not registered for a jam. +command-unregister-message-unregistered=You are now unregistered. +command-votes-description=Cast votes +command-votes-info-description=Information about your votes. +command-votes-info-embed-title=Given Votes +command-votes-ranking-description=The current ranking +command-votes-ranking-embed-votes=Votes +command-votes-ranking-message-voteactive=A vote is active. Rankings can be accessed after the polls are closed. +command-votes-vote-description=Vote for a team. +command-votes-vote-message-done=You gave %TEAM% %POINTS% points. Remaining points: %REMAINING% +command-votes-vote-message-maxpointsreached=You have reached the max amount of given points. Remove points from other teams if you want to add more. Remaining points: %REMAINING% +command-votes-vote-message-notactive=No voting is active. +command-votes-vote-message-ownteam=You cant vote for your own team. +command-votes-vote-options-points-description=The points to give. +command-votes-vote-options-team-description=The team you want to vote for. +command-team-api-description=Information about the team api +error-alreadyActive=A jam is already active. +error-invalidpath=Invalid path +error-invalidrimeformat=Invalid time format. Format is %FORMAT% +error-invalidtimezone=Invalid timezone. +error-maxteamsize=The team has reached the max size. +error-noactivejam=There is no active jam. +error-noconfirm=Please confirm by setting the confirm parameter +error-nojamactive=No jam is in progress. Teams are not available. +error-noleader=Only the leader can do this. +error-noteam=You are not part of a team. +error-notregistered=You are not registered for a jam. +error-noupcomingjam=There is no upcoming jam. +error-pluginnotfound=Plugin not found +error-servernotrunning=Server is not online. +error-unkownteam=This team does not exist. +error-votingactive=This function is not available while a vote is active. + +teamserver-message-detailstatus-existing-description=Server running +teamserver-message-detailstatus-nonexisting-description=Server not set up. + +word-activethreads=Active threads +word-api=Api +word-averageticktime=Average Tick time +word-max=Max +word-memory=Memory +word-min=min +word-notrunning=Not running +word-players=Players +word-ports=Ports +word-server=Server +word-serversetup=Server set up +word-system=System +word-total=Total +word-tps=Tps +word-used=Used +word-upcoming=Upcoming +word-ended=Ended +word-active=Active +word-jamlist=Jam list + +words-dateformat=yyyy.MM.dd HH:mm +words-format=Format +command-team-edit-description=Edit team information +command-team-profile-projectdescription=Project Description diff --git a/backend/src/main/resources/logback.xml b/backend/src/main/resources/logback.xml new file mode 100644 index 00000000..1529d6b4 --- /dev/null +++ b/backend/src/main/resources/logback.xml @@ -0,0 +1,16 @@ + + + + + %cyan(%d{HH:mm:ss.SSS}) %gray([%thread]) %highlight(%-5level) %magenta(%logger{36}) - %msg%n + + + + + + + + + + diff --git a/backend/src/test/java/de/chojo/pluginjam/BackendTest.java b/backend/src/test/java/de/chojo/pluginjam/BackendTest.java new file mode 100644 index 00000000..b353b7fd --- /dev/null +++ b/backend/src/test/java/de/chojo/pluginjam/BackendTest.java @@ -0,0 +1,22 @@ +package de.chojo.pluginjam; + + +import io.micronaut.runtime.EmbeddedApplication; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Assertions; + +import jakarta.inject.Inject; + +@MicronautTest +class BackendTest { + + @Inject + EmbeddedApplication application; + + @Test + void testItWorks() { + Assertions.assertTrue(application.isRunning()); + } + +} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/register/RegisterCommand.java b/bot/src/main/java/de/chojo/gamejam/commands/register/RegisterCommand.java deleted file mode 100644 index 6f47575c..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/register/RegisterCommand.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.register; - -import com.google.inject.Inject; -import de.chojo.gamejam.server.CommandContextProvider; -import io.github.kaktushose.jdac.annotations.i18n.Bundle; -import io.github.kaktushose.jdac.annotations.interactions.Command; -import io.github.kaktushose.jdac.annotations.interactions.Interaction; -import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; -import io.github.kaktushose.jdac.message.placeholder.Entry; -import net.dv8tion.jda.api.utils.TimeFormat; - -import java.time.ZonedDateTime; - -@Bundle("locale") -@Interaction -public class RegisterCommand { - private final CommandContextProvider commandContextProvider; - - @Inject - public RegisterCommand(CommandContextProvider commandContextProvider) { - this.commandContextProvider = commandContextProvider; - } - - @Command(value = "register") - public void onCommand(CommandEvent event) { - var guild = commandContextProvider.guilds().guild(event); - var optJam = guild.jams().nextOrCurrent(); - if (optJam.isEmpty()) { - event.reply("error.noupcomingjam"); - return; - } - var jam = optJam.get(); - var times = jam.times(); - - if (!times.registration().contains(ZonedDateTime.now())) { - if (times.registration().start().isAfter(ZonedDateTime.now())) { - event.reply( - "command.register.message.notyet", - Entry.entry("TIMESTAMP", TimeFormat.DATE_TIME_LONG.format(times.registration().start())) - ); - return; - } - event.reply("command.register.message.notanymore"); - return; - } - - if (jam.registrations().contains(event.getMember().getIdLong())) { - event.reply("command.register.message.alreadyregistered"); - return; - } - - jam.register(event.getMember()); - var settings = guild.jamSettings(); - var role = event.getGuild().getRoleById(settings.jamRole()); - if (role != null) { - event.getGuild().addRoleToMember(event.getMember(), role).queue(); - } - event.reply( - "command.register.message.registered", - Entry.entry("TIMESTAMP", TimeFormat.DATE_TIME_LONG.format(times.registration().start())) - ); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/register/UnregisterCommand.java b/bot/src/main/java/de/chojo/gamejam/commands/register/UnregisterCommand.java deleted file mode 100644 index d00ce8c7..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/register/UnregisterCommand.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.register; - -import com.google.inject.Inject; -import de.chojo.gamejam.server.CommandContextProvider; -import io.github.kaktushose.jdac.annotations.i18n.Bundle; -import io.github.kaktushose.jdac.annotations.interactions.Command; -import io.github.kaktushose.jdac.annotations.interactions.Interaction; -import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; - -@Bundle("locale") -@Interaction -public class UnregisterCommand { - private final CommandContextProvider commandContextProvider; - - @Inject - public UnregisterCommand(CommandContextProvider commandContextProvider) { - this.commandContextProvider = commandContextProvider; - } - - @Command(value = "unregister") - public void onCommand(CommandEvent event) { - var guild = commandContextProvider.guilds().guild(event); - var optJam = guild.jams().nextOrCurrent(); - if (optJam.isEmpty()) { - event.reply("error.noupcomingjam"); - return; - } - - var jam = optJam.get(); - - if (!jam.registrations().contains(event.getMember().getIdLong())) { - event.reply("command.unregister.message.notregistered"); - return; - } - - jam.teams().byMember(event.getMember()).ifPresentOrElse( - team -> event.reply("command.unregister.message.inteam"), - () -> { - var settings = guild.jamSettings(); - var role = event.getGuild().getRoleById(settings.jamRole()); - if (role != null) { - event.getGuild().removeRoleFromMember(event.getMember(), role).queue(); - } - event.reply("command.unregister.message.unregistered"); - - }); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/settings/InfoCommand.java b/bot/src/main/java/de/chojo/gamejam/commands/settings/InfoCommand.java deleted file mode 100644 index 5338ff6a..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/settings/InfoCommand.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.settings; - -import com.google.inject.Inject; -import de.chojo.gamejam.data.dao.JamGuild; -import de.chojo.gamejam.data.dao.guild.JamSettings; -import de.chojo.gamejam.server.CommandContextProvider; -import de.chojo.jdautil.localization.util.LocalizedEmbedBuilder; -import de.chojo.jdautil.util.MentionUtil; -import de.chojo.jdautil.wrapper.EventContext; -import io.github.kaktushose.jdac.annotations.i18n.Bundle; -import io.github.kaktushose.jdac.annotations.interactions.Command; -import io.github.kaktushose.jdac.annotations.interactions.Interaction; -import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -@Bundle("locale") -@Interaction -public final class InfoCommand { - private final CommandContextProvider commandContextProvider; - - @Inject - public InfoCommand(CommandContextProvider commandContextProvider) { - this.commandContextProvider = commandContextProvider; - } - - @Command(value = "settings info") - public void onCommand(CommandEvent event) { - var jamGuild = commandContextProvider.guilds().guild(event.getGuild()); - var jamSettings = jamGuild.jamSettings(); - - var settings = jamGuild.settings(); - - var embed = event.embed("settings-info"); - embed.title("command.settings.info.embed.settings"); - embed.fields().add("command.settings.info.embed.jamrole", MentionUtil.role(jamSettings.jamRole()), true); - embed.fields().add("command.settings.info.embed.teamsize", String.valueOf(jamSettings.teamSize()), true); - embed.fields().add("command.settings.info.embed.orgarole", MentionUtil.role(settings.orgaRole()), true); - - event.reply(embed.build()); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/data/access/Teams.java b/bot/src/main/java/de/chojo/gamejam/data/access/Teams.java index 9c4664ce..f46d6d41 100644 --- a/bot/src/main/java/de/chojo/gamejam/data/access/Teams.java +++ b/bot/src/main/java/de/chojo/gamejam/data/access/Teams.java @@ -6,7 +6,7 @@ package de.chojo.gamejam.data.access; -import de.chojo.gamejam.data.dao.guild.jams.jam.teams.Team; +import de.chojo.gamejam.data.dao.guild.jams.jamEntity.teams.Team; import net.dv8tion.jda.api.sharding.ShardManager; import org.slf4j.Logger; @@ -30,7 +30,7 @@ public Optional byId(int id) { return query(""" SELECT guild_id FROM team t - LEFT JOIN jam j ON t.jam_id = j.id + LEFT JOIN jamEntity j ON t.jam_id = j.id WHERE t.id = ? """) .single(call().bind(id)) diff --git a/bot/src/main/java/de/chojo/gamejam/data/dao/guild/Jams.java b/bot/src/main/java/de/chojo/gamejam/data/dao/guild/Jams.java index 8ad97fa7..09515c62 100644 --- a/bot/src/main/java/de/chojo/gamejam/data/dao/guild/Jams.java +++ b/bot/src/main/java/de/chojo/gamejam/data/dao/guild/Jams.java @@ -8,7 +8,7 @@ import de.chojo.gamejam.data.dao.JamGuild; import de.chojo.gamejam.data.dao.guild.jams.Jam; -import de.chojo.gamejam.data.wrapper.jam.JamCreator; +import de.chojo.gamejam.data.wrapper.jamEntity.JamCreator; import de.chojo.sadu.queries.api.configuration.QueryConfiguration; import java.util.Optional; @@ -26,7 +26,7 @@ public Jams(JamGuild jamGuild) { public void create(JamCreator jamCreator) { try (var conf = QueryConfiguration.getDefault().withSingleTransaction()) { - conf.query("INSERT INTO jam(guild_id) VALUES(?) RETURNING id") + conf.query("INSERT INTO jamEntity(guild_id) VALUES(?) RETURNING id") .single(call().bind(jamGuild.guildId())) .map(r -> r.getInt("id")) .first() @@ -43,8 +43,8 @@ INSERT INTO jam_time( .single(call().bind(id) .bind(times.registration().startTimestamp()) .bind(times.registration().endTimestamp()) - .bind(times.jam().startTimestamp()) - .bind(times.jam().endTimestamp()) + .bind(times.jamEntity().startTimestamp()) + .bind(times.jamEntity().endTimestamp()) .bind(times.zone().getId()) ) .insert(); @@ -64,7 +64,7 @@ public Optional getCurrentJam() { SELECT id FROM jam_time t - LEFT JOIN jam j ON j.id = t.jam_id + LEFT JOIN jamEntity j ON j.id = t.jam_id WHERE registration_start < NOW() AT TIME ZONE 'utc' AND t.jam_end > NOW() AT TIME ZONE 'utc' AND guild_id = ?; @@ -79,7 +79,7 @@ public Optional nextOrCurrent() { SELECT id FROM jam_time t - LEFT JOIN jam j ON j.id = t.jam_id + LEFT JOIN jamEntity j ON j.id = t.jam_id LEFT JOIN jam_state js ON j.id = js.jam_id WHERE js.active OR t.jam_end > NOW() AT TIME ZONE 'utc' AND guild_id = ? @@ -97,7 +97,7 @@ public Optional activeJam() { SELECT id FROM jam_state s - LEFT JOIN jam j ON j.id = s.jam_id + LEFT JOIN jamEntity j ON j.id = s.jam_id WHERE s.active AND guild_id = ? LIMIT 1; @@ -110,7 +110,7 @@ public Optional activeJam() { public Optional byId(int id) { return query(""" - SELECT id FROM jam WHERE guild_id = ? AND id = ? + SELECT id FROM jamEntity WHERE guild_id = ? AND id = ? """) .single(call().bind(jamGuild.guildId()).bind(id)) .map(row -> new Jam(jamGuild, id)) diff --git a/bot/src/main/java/de/chojo/gamejam/data/dao/guild/Settings.java b/bot/src/main/java/de/chojo/gamejam/data/dao/guild/Settings.java index 6a8c7d39..c133dc13 100644 --- a/bot/src/main/java/de/chojo/gamejam/data/dao/guild/Settings.java +++ b/bot/src/main/java/de/chojo/gamejam/data/dao/guild/Settings.java @@ -54,7 +54,7 @@ public Long guildId() { private boolean set(String column, Function stmt) { return query(""" - INSERT INTO settings(guild_id, %s) VALUES(?,?) + INSERT INTO settingsEntity(guild_id, %s) VALUES(?,?) ON CONFLICT(guild_id) DO UPDATE SET %s = excluded.%s diff --git a/bot/src/main/java/de/chojo/gamejam/data/dao/guild/jams/jam/JamTeams.java b/bot/src/main/java/de/chojo/gamejam/data/dao/guild/jams/jam/JamTeams.java index 3e49d6c8..03a0bec1 100644 --- a/bot/src/main/java/de/chojo/gamejam/data/dao/guild/jams/jam/JamTeams.java +++ b/bot/src/main/java/de/chojo/gamejam/data/dao/guild/jams/jam/JamTeams.java @@ -4,10 +4,10 @@ * Copyright (C) 2022 DevCord Team and Contributor */ -package de.chojo.gamejam.data.dao.guild.jams.jam; +package de.chojo.gamejam.data.dao.guild.jams.jamEntity; import de.chojo.gamejam.data.dao.guild.jams.Jam; -import de.chojo.gamejam.data.dao.guild.jams.jam.teams.Team; +import de.chojo.gamejam.data.dao.guild.jams.jamEntity.teams.Team; import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.interactions.commands.Command; @@ -19,17 +19,17 @@ import static de.chojo.sadu.queries.api.query.Query.query; public class JamTeams { - private final Jam jam; + private final Jam jamEntity; - public JamTeams(Jam jam) { - this.jam = jam; + public JamTeams(Jam jamEntity) { + this.jamEntity = jamEntity; } public Team create(String name) { var teamId = query(""" INSERT INTO team(jam_id) VALUES(?) RETURNING id AS team_id; """) - .single(call().bind(jam.jamId())) + .single(call().bind(jamEntity.jamId())) .map(row -> row.getInt("team_id")) .first() .orElseThrow(); @@ -38,7 +38,7 @@ public Team create(String name) { """) .single(call().bind(teamId).bind(name)) .insert(); - return new Team(jam, teamId); + return new Team(jamEntity, teamId); } public List teams() { @@ -49,8 +49,8 @@ public List teams() { LEFT JOIN team_meta m ON t.id = m.team_id WHERE jam_id = ? """) - .single(call().bind(jam.jamId())) - .map(r -> new Team(jam, r.getInt("id"))) + .single(call().bind(jamEntity.jamId())) + .map(r -> new Team(jamEntity, r.getInt("id"))) .all(); } @@ -63,11 +63,11 @@ public Optional byMember(User member) { SELECT m.team_id FROM team_member m LEFT JOIN team t ON t.id = m.team_id - LEFT JOIN jam j ON j.id = t.jam_id + LEFT JOIN jamEntity j ON j.id = t.jam_id WHERE j.id = ? AND user_id = ? """) - .single(call().bind(jam.jamId()).bind(member.getIdLong())) + .single(call().bind(jamEntity.jamId()).bind(member.getIdLong())) .map(r -> r.getInt("team_id")) .first() .flatMap(this::byId); @@ -79,7 +79,7 @@ public Optional byName(String name) { WHERE jam_id = ? AND LOWER(m.team_name) = LOWER(?) """) - .single(call().bind(jam.jamId()).bind(name)) + .single(call().bind(jamEntity.jamId()).bind(name)) .map(r -> r.getInt("id")) .first() .flatMap(this::byId); @@ -88,7 +88,7 @@ AND LOWER(m.team_name) = LOWER(?) public Optional byId(int id) { return query("SELECT id, team_name FROM team t LEFT JOIN team_meta m ON t.id = m.team_id WHERE id = ?") .single(call().bind(id)) - .map(r -> new Team(jam, r.getInt("id"))) + .map(r -> new Team(jamEntity, r.getInt("id"))) .first(); } diff --git a/bot/src/main/resources/database/postgresql/1/setup.sql b/bot/src/main/resources/database/postgresql/1/setup.sql index 2fb393ad..07ee412f 100644 --- a/bot/src/main/resources/database/postgresql/1/setup.sql +++ b/bot/src/main/resources/database/postgresql/1/setup.sql @@ -1,7 +1,7 @@ CREATE SEQUENCE gamejam.jam_times_id_seq AS INTEGER; -CREATE TABLE IF NOT EXISTS gamejam.jam +CREATE TABLE IF NOT EXISTS gamejam.jamEntity ( id SERIAL, guild_id BIGINT NOT NULL, @@ -10,7 +10,7 @@ CREATE TABLE IF NOT EXISTS gamejam.jam ); CREATE INDEX IF NOT EXISTS jam_guild_id_index - ON gamejam.jam (guild_id); + ON gamejam.jamEntity (guild_id); CREATE TABLE IF NOT EXISTS gamejam.jam_time ( @@ -23,7 +23,7 @@ CREATE TABLE IF NOT EXISTS gamejam.jam_time CONSTRAINT jam_times_pk PRIMARY KEY (jam_id), CONSTRAINT jam_times_jam_id_fk - FOREIGN KEY (jam_id) REFERENCES gamejam.jam + FOREIGN KEY (jam_id) REFERENCES gamejam.jamEntity ON DELETE CASCADE ); @@ -36,7 +36,7 @@ CREATE TABLE IF NOT EXISTS gamejam.jam_meta CONSTRAINT jam_topic_pk PRIMARY KEY (jam_id), CONSTRAINT jam_topic_jam_id_fk - FOREIGN KEY (jam_id) REFERENCES gamejam.jam + FOREIGN KEY (jam_id) REFERENCES gamejam.jamEntity ON DELETE CASCADE ); @@ -47,7 +47,7 @@ CREATE TABLE IF NOT EXISTS gamejam.team CONSTRAINT team_pk PRIMARY KEY (id), CONSTRAINT team_jam_id_fk - FOREIGN KEY (jam_id) REFERENCES gamejam.jam + FOREIGN KEY (jam_id) REFERENCES gamejam.jamEntity ON DELETE CASCADE ); @@ -96,7 +96,7 @@ CREATE TABLE IF NOT EXISTS gamejam.jam_registrations jam_id INTEGER NOT NULL, user_id BIGINT NOT NULL, CONSTRAINT jam_registrations_jam_id_fk - FOREIGN KEY (jam_id) REFERENCES gamejam.jam + FOREIGN KEY (jam_id) REFERENCES gamejam.jamEntity ON DELETE CASCADE ); @@ -121,7 +121,7 @@ CREATE TABLE IF NOT EXISTS gamejam.jam_state CONSTRAINT jam_state_pk PRIMARY KEY (jam_id), CONSTRAINT jam_state_jam_id_fk - FOREIGN KEY (jam_id) REFERENCES gamejam.jam + FOREIGN KEY (jam_id) REFERENCES gamejam.jamEntity ON DELETE CASCADE ); @@ -131,7 +131,7 @@ CREATE TABLE IF NOT EXISTS gamejam.version patch INTEGER ); -CREATE TABLE IF NOT EXISTS gamejam.settings +CREATE TABLE IF NOT EXISTS gamejam.settingsEntity ( guild_id BIGINT NOT NULL, manager_role BIGINT, diff --git a/bot/src/main/resources/locale.properties b/bot/src/main/resources/locale.properties index f817ba11..18fd87e8 100644 --- a/bot/src/main/resources/locale.properties +++ b/bot/src/main/resources/locale.properties @@ -8,11 +8,11 @@ command.jamadmin.create.options.tagline.description= command.jamadmin.create.options.timezone.description= command.jamadmin.create.options.topic.description= command.jamadmin.description= -command.jamadmin.jam.end.options.confirm.description= -command.jamadmin.jam.description= -command.jamadmin.jam.end.description= -command.jamadmin.jam.end.message.ended= -command.jamadmin.jam.start.description= +command.jamadmin.jamEntity.end.options.confirm.description= +command.jamadmin.jamEntity.description= +command.jamadmin.jamEntity.end.description= +command.jamadmin.jamEntity.end.message.ended= +command.jamadmin.jamEntity.start.description= command.jamadmin.votes.close.description= command.jamadmin.votes.description= command.jamadmin.votes.message.closed= @@ -139,24 +139,24 @@ command.serveradmin.stop.team.description= command.serveradmin.stop.team.options.team.description= command.serveradmin.syncvelocity.description= command.serveradmin.syncvelocity.message.synced= -command.settings.description= -command.settings.info.description= -command.settings.info.embed.jamrole= -command.settings.info.embed.orgarole= -command.settings.info.embed.settings= -command.settings.info.embed.teamsize= -command.settings.jamrole.description= -command.settings.jamrole.message.updated= -command.settings.jamrole.options.role.description= -command.settings.locale.description= -command.settings.locale.options.locale.description= -command.settings.locale.message.invalid= -command.settings.locale.message.updated= -command.settings.orgarole.description= -command.settings.orgarole.options.role.description= -command.settings.teamsize.description= -command.settings.teamsize.message.updated= -command.settings.teamsize.options.size.description= +command.settingsEntity.description= +command.settingsEntity.info.description= +command.settingsEntity.info.embed.jamrole= +command.settingsEntity.info.embed.orgarole= +command.settingsEntity.info.embed.settingsEntity= +command.settingsEntity.info.embed.teamsize= +command.settingsEntity.jamrole.description= +command.settingsEntity.jamrole.message.updated= +command.settingsEntity.jamrole.options.role.description= +command.settingsEntity.locale.description= +command.settingsEntity.locale.options.locale.description= +command.settingsEntity.locale.message.invalid= +command.settingsEntity.locale.message.updated= +command.settingsEntity.orgarole.description= +command.settingsEntity.orgarole.options.role.description= +command.settingsEntity.teamsize.description= +command.settingsEntity.teamsize.message.updated= +command.settingsEntity.teamsize.options.size.description= command.start.message.activated= command.team.create.description= command.team.create.message.alreadymember= diff --git a/bot/src/main/resources/locale_de.properties b/bot/src/main/resources/locale_de.properties index 4019904d..7bbcc824 100644 --- a/bot/src/main/resources/locale_de.properties +++ b/bot/src/main/resources/locale_de.properties @@ -8,11 +8,11 @@ command.jamadmin.create.options.tagline.description=Topic-Tagline als Zusatz zum command.jamadmin.create.options.timezone.description=Die Zeitzone des Game Jams. Zum Beispiel "Europe/Berlin". command.jamadmin.create.options.topic.description=Das Thema des Game-Jam command.jamadmin.description=Verwalte Game-Jams -command.jamadmin.jam.end.options.confirm.description=Bestätige das Ende. -command.jamadmin.jam.description=Verwalte Game-Jams -command.jamadmin.jam.end.description=Beendet den aktuellen Game-Jam. Löscht alle Rollen und Kanäle -command.jamadmin.jam.end.message.ended=Game-Jam beendet. -command.jamadmin.jam.start.description=Startet den nächsten geplanten Game-Jam +command.jamadmin.jamEntity.end.options.confirm.description=Bestätige das Ende. +command.jamadmin.jamEntity.description=Verwalte Game-Jams +command.jamadmin.jamEntity.end.description=Beendet den aktuellen Game-Jam. Löscht alle Rollen und Kanäle +command.jamadmin.jamEntity.end.message.ended=Game-Jam beendet. +command.jamadmin.jamEntity.start.description=Startet den nächsten geplanten Game-Jam command.jamadmin.votes.close.description=Abstimmungen für den aktuellen Game-Jam schließen. command.jamadmin.votes.description=Verwalte Abstimmungen command.jamadmin.votes.message.closed=Abstimmungen für den aktuellen Game-Jam geschlossen. @@ -139,24 +139,24 @@ command.serveradmin.stop.team.description=Einen Teamserver stoppen command.serveradmin.stop.team.options.team.description=team command.serveradmin.syncvelocity.description=Serverstatus mit Velocity-Daten synchronisieren command.serveradmin.syncvelocity.message.synced=Server synchronisiert -command.settings.description=Verwalte die Bot-Einstellungen -command.settings.info.description=Zeige die aktuellen Einstellungen -command.settings.info.embed.jamrole=Game-Jam Rolle -command.settings.info.embed.orgarole=Organisations-Rolle -command.settings.info.embed.settings=Einstellungen -command.settings.info.embed.teamsize=Max Team Size -command.settings.jamrole.description=Stellt die Rolle ein, die den registrierten Mitgliedern zugewiesen wird. -command.settings.jamrole.message.updated=Aktualisiere die Game-Jam Rolle. -command.settings.jamrole.options.role.description=Die Rolle, die nach der Registrierung zugewiesen wird -command.settings.locale.description=Ändert die Bot-Sprache. -command.settings.locale.options.locale.description=Die neue Sprache -command.settings.locale.message.invalid=Ungültige Sprache -command.settings.locale.message.updated=Sprache aktualisiert -command.settings.orgarole.description=Definiere die Rolle des Organisationsteams -command.settings.orgarole.options.role.description=Die Rolle, die den Bot verwalten kann -command.settings.teamsize.description=Definiere die maximale Teamgröße. -command.settings.teamsize.message.updated=Aktualisierte maximale Teamgröße. -command.settings.teamsize.options.size.description=Die maximale Teamgröße. +command.settingsEntity.description=Verwalte die Bot-Einstellungen +command.settingsEntity.info.description=Zeige die aktuellen Einstellungen +command.settingsEntity.info.embed.jamrole=Game-Jam Rolle +command.settingsEntity.info.embed.orgarole=Organisations-Rolle +command.settingsEntity.info.embed.settingsEntity=Einstellungen +command.settingsEntity.info.embed.teamsize=Max Team Size +command.settingsEntity.jamrole.description=Stellt die Rolle ein, die den registrierten Mitgliedern zugewiesen wird. +command.settingsEntity.jamrole.message.updated=Aktualisiere die Game-Jam Rolle. +command.settingsEntity.jamrole.options.role.description=Die Rolle, die nach der Registrierung zugewiesen wird +command.settingsEntity.locale.description=Ändert die Bot-Sprache. +command.settingsEntity.locale.options.locale.description=Die neue Sprache +command.settingsEntity.locale.message.invalid=Ungültige Sprache +command.settingsEntity.locale.message.updated=Sprache aktualisiert +command.settingsEntity.orgarole.description=Definiere die Rolle des Organisationsteams +command.settingsEntity.orgarole.options.role.description=Die Rolle, die den Bot verwalten kann +command.settingsEntity.teamsize.description=Definiere die maximale Teamgröße. +command.settingsEntity.teamsize.message.updated=Aktualisierte maximale Teamgröße. +command.settingsEntity.teamsize.options.size.description=Die maximale Teamgröße. command.start.message.activated=Der Teamstatus wurde auf aktiv geändert. command.team.create.description=Erstelle ein Team command.team.create.message.alreadymember=Du bist bereits Teil eines Teams. Um dein eigenes Team zu erstellen, musst du es erst verlassen. diff --git a/bot/src/main/resources/locale_en_US.properties b/bot/src/main/resources/locale_en_US.properties index af0f124d..10edd147 100644 --- a/bot/src/main/resources/locale_en_US.properties +++ b/bot/src/main/resources/locale_en_US.properties @@ -1,28 +1,28 @@ -command.jamadmin.create.description=Create a new game jam +command.jamadmin.create.description=Create a new game jamEntity command.jamadmin.create.options.jamend.description=Game Jam end. $words.format$: $words.dateformat$ command.jamadmin.create.options.jamstart.description=Game Jam start. $words.format$: $words.dateformat$ command.jamadmin.create.message.created=Jam created. command.jamadmin.create.options.registerend.description=Registrations close. $words.format$: $words.dateformat$ command.jamadmin.create.options.registerstart.description=Registrations opening. $words.format$: $words.dateformat$ command.jamadmin.create.options.tagline.description=Topic tagline as an addition to the topic -command.jamadmin.create.options.timezone.description=The timezone of the game jam. "Europe/Berlin" for example. -command.jamadmin.create.options.topic.description=The topic of the game jam +command.jamadmin.create.options.timezone.description=The timezone of the game jamEntity. "Europe/Berlin" for example. +command.jamadmin.create.options.topic.description=The topic of the game jamEntity command.jamadmin.description=Manage jams -command.jamadmin.jam.end.options.confirm.description=Confirm the end -command.jamadmin.jam.description=Manage jams -command.jamadmin.jam.end.description=Ends the currently active jam. Deletes all roles and channel -command.jamadmin.jam.end.message.ended=Jam ended. -command.jamadmin.jam.start.description=Start the next scheduled jam -command.jamadmin.votes.close.description=Close votes for the current jam +command.jamadmin.jamEntity.end.options.confirm.description=Confirm the end +command.jamadmin.jamEntity.description=Manage jams +command.jamadmin.jamEntity.end.description=Ends the currently active jamEntity. Deletes all roles and channel +command.jamadmin.jamEntity.end.message.ended=Jam ended. +command.jamadmin.jamEntity.start.description=Start the next scheduled jamEntity +command.jamadmin.votes.close.description=Close votes for the current jamEntity command.jamadmin.votes.description=Manage votes -command.jamadmin.votes.message.closed=Votes closed for the current jam. -command.jamadmin.votes.message.opened=Votes for the current jam opened. -command.jamadmin.votes.open.description=Open votes for the current jam +command.jamadmin.votes.message.closed=Votes closed for the current jamEntity. +command.jamadmin.votes.message.opened=Votes for the current jamEntity opened. +command.jamadmin.votes.open.description=Open votes for the current jamEntity command.register.description=Register for an upcomming Game Jam command.register.message.alreadyregistered=You are already registered. command.register.message.notanymore=Registrations are closed. -command.register.message.notyet=You cant register for this game jam yet. You can register at %TIMESTAMP% -command.register.message.registered=You have registered yourself for the next game jam. It will start at %TIMESTAMP%. +command.register.message.notyet=You cant register for this game jamEntity yet. You can register at %TIMESTAMP% +command.register.message.registered=You have registered yourself for the next game jamEntity. It will start at %TIMESTAMP%. command.server.configure.description=Configure server command.server.configure.maxplayers.options.amount.description=Max amount of players. command.server.configure.maxplayers.description=Set the max players of this server @@ -139,24 +139,24 @@ command.serveradmin.stop.team.description=Stop a team server command.serveradmin.stop.team.options.team.description=team command.serveradmin.syncvelocity.description=Sync servers with velocity data command.serveradmin.syncvelocity.message.synced=Synced server -command.settings.description=Manage bot settings -command.settings.info.description=Show the current settings -command.settings.info.embed.jamrole=Game Jam Role -command.settings.info.embed.orgarole=Organization Role -command.settings.info.embed.settings=Settings -command.settings.info.embed.teamsize=Max Team Size -command.settings.jamrole.description=Set the role which will be assigned to registered members. -command.settings.jamrole.message.updated=Updated the game jam role. -command.settings.jamrole.options.role.description=The role to assign after registration -command.settings.locale.description=Change the bot language. -command.settings.locale.options.locale.description=The new language -command.settings.locale.message.invalid=Invalid locale -command.settings.locale.message.updated=Locale updated -command.settings.orgarole.description=Define the organisation team role -command.settings.orgarole.options.role.description=The role which can manage the bot -command.settings.teamsize.description=Define the max team size. -command.settings.teamsize.message.updated=Updated max team size. -command.settings.teamsize.options.size.description=The max team size. +command.settingsEntity.description=Manage bot settingsEntity +command.settingsEntity.info.description=Show the current settingsEntity +command.settingsEntity.info.embed.jamrole=Game Jam Role +command.settingsEntity.info.embed.orgarole=Organization Role +command.settingsEntity.info.embed.settingsEntity=Settings +command.settingsEntity.info.embed.teamsize=Max Team Size +command.settingsEntity.jamrole.description=Set the role which will be assigned to registered members. +command.settingsEntity.jamrole.message.updated=Updated the game jamEntity role. +command.settingsEntity.jamrole.options.role.description=The role to assign after registration +command.settingsEntity.locale.description=Change the bot language. +command.settingsEntity.locale.options.locale.description=The new language +command.settingsEntity.locale.message.invalid=Invalid locale +command.settingsEntity.locale.message.updated=Locale updated +command.settingsEntity.orgarole.description=Define the organisation team role +command.settingsEntity.orgarole.options.role.description=The role which can manage the bot +command.settingsEntity.teamsize.description=Define the max team size. +command.settingsEntity.teamsize.message.updated=Updated max team size. +command.settingsEntity.teamsize.options.size.description=The max team size. command.start.message.activated=Jam state changed to active. command.team.create.description=Create a team command.team.create.message.alreadymember=You are already part of a team. You need to leave first to create your own team. @@ -170,14 +170,14 @@ command.team.disband.description=Disband your team command.team.disband.message.disbanded=Your team was disbanded. command.team.invite.alreadyMember=You are already member of a team. command.team.invite.description=Invite someone to your team -command.team.invite.gameJamOver=The game jam is over +command.team.invite.gameJamOver=The game jamEntity is over command.team.invite.joined=You joined the team. command.team.invite.joinedBroadcast=%USER% joined the team. command.team.invite.message.accept=Accept command.team.invite.message.invitation=%USER% invited you to join their team %TEAM%. -command.team.invite.message.invited=You received a invitation for the game jam on %GUILD% +command.team.invite.message.invited=You received a invitation for the game jamEntity on %GUILD% command.team.invite.message.noleader=Only the group leader can invite people. -command.team.invite.message.notRegistered=This user is not registered for the game jam. +command.team.invite.message.notRegistered=This user is not registered for the game jamEntity. command.team.invite.message.partofteam=This user is already part of a team. command.team.invite.message.send=Invitation send. command.team.invite.options.user.description=The user you want to invite @@ -202,7 +202,7 @@ command.team.rename.message.done=Team name changed. command.team.rename.options.name.description=The new team name command.unregister.description=Withdraw your registration command.unregister.message.inteam=You need to leave your team first. -command.unregister.message.notregistered=You are not registered for a jam. +command.unregister.message.notregistered=You are not registered for a jamEntity. command.unregister.message.unregistered=You are now unregistered. command.votes.description=Cast votes command.votes.info.description=Information about your votes. @@ -218,18 +218,18 @@ command.votes.vote.message.ownteam=You cant vote for your own team. command.votes.vote.options.points.description=The points to give. command.votes.vote.options.team.description=The team you want to vote for. command.team.api.description=Information about the team api -error.alreadyActive=A jam is already active. +error.alreadyActive=A jamEntity is already active. error.invalidpath=Invalid path error.invalidrimeformat=Invalid time format. Format is %FORMAT% error.invalidtimezone=Invalid timezone. error.maxteamsize=The team has reached the max size. -error.noactivejam=There is no active jam. +error.noactivejam=There is no active jamEntity. error.noconfirm=Please confirm by setting the confirm parameter -error.nojamactive=No jam is in progress. Teams are not available. +error.nojamactive=No jamEntity is in progress. Teams are not available. error.noleader=Only the leader can do this. error.noteam=You are not part of a team. -error.notregistered=You are not registered for a jam. -error.noupcomingjam=There is no upcoming jam. +error.notregistered=You are not registered for a jamEntity. +error.noupcomingjam=There is no upcoming jamEntity. error.pluginnotfound=Plugin not found error.servernotrunning=Server is not online. error.unkownteam=This team does not exist. diff --git a/compose.dev.yml b/compose.dev.yml index 84f2b239..47588c5f 100644 --- a/compose.dev.yml +++ b/compose.dev.yml @@ -31,7 +31,7 @@ services: networks: - plugin-jam-network volumes: - - ./data/db:/var/lib/postgresql/data + - postgres-data:/var/lib/postgresql/data velocity: build: context: . @@ -65,3 +65,7 @@ networks: plugin-jam-network: name: plugin-jam-network driver: bridge + +volumes: + postgres-data: + driver: local \ No newline at end of file From ee25322b4031565aa0ed061f5cf7babd58ccdf27 Mon Sep 17 00:00:00 2001 From: TheZexquex Date: Fri, 12 Jun 2026 00:11:28 +0200 Subject: [PATCH 8/8] Readd team and voting commands and fix the placeholders in the language files so they match the project fluent system --- .../main/java/de/chojo/pluginjam/bot/Bot.java | 11 +- .../bot/commands/CommandContextProvider.java | 9 +- .../bot/commands/team/TeamCreateCommand.java | 2 - .../bot/commands/team/TeamInviteCommand.java | 111 ++++++++++++++ .../bot/commands/team/TeamLeaveCommand.java | 56 +++++++ .../bot/commands/team/TeamListCommand.java | 125 ++++++++++++++++ .../bot/commands/team/TeamProfileCommand.java | 81 ++++++++++ .../commands/team/TeamPromoteCommand.java | 41 +++--- .../bot/commands/team/TeamRenameCommand.java | 62 ++++++++ .../bot}/commands/vote/VoteCommand.java | 62 ++++---- .../bot}/commands/vote/VotesInfoCommand.java | 22 +-- .../commands/vote/VotesRankingCommand.java | 127 ++++++++++++++++ .../bot/listener/InviteButtonListener.java | 39 ++--- .../pluginjam/bot/message/DefaultValues.java | 22 +++ .../pluginjam/bot/message/EmbedHelper.java | 10 +- .../bot/message/StringValidator.java | 2 +- .../pluginjam/database/entity/VotingRank.java | 24 +++ .../pluginjam/database/entity/team/Team.java | 8 + .../database/entity/team/TeamMeta.java | 139 +++++++++++++++--- .../repository/VotesRankingRepository.java | 17 +++ .../database/repository/VotesRepository.java | 18 ++- .../chojo/pluginjam/service/TeamService.java | 27 +++- .../chojo/pluginjam/service/VoteService.java | 42 ++++++ backend/src/main/resources/locale/de.ftl | 53 ++++--- backend/src/main/resources/locale/en.ftl | 52 ++++--- .../gamejam/commands/team/TeamApiCommand.java | 52 ------- .../commands/team/TeamCreateCommand.java | 114 -------------- .../commands/team/TeamDisbandCommand.java | 72 --------- .../commands/team/TeamEditCommand.java | 2 +- .../commands/team/TeamInviteCommand.java | 98 ------------ .../commands/team/TeamLeaveCommand.java | 55 ------- .../commands/team/TeamListCommand.java | 80 ---------- .../commands/team/TeamProfileCommand.java | 81 ---------- .../commands/team/TeamRenameCommand.java | 63 -------- .../commands/vote/VotesRankingCommand.java | 95 ------------ 35 files changed, 986 insertions(+), 888 deletions(-) create mode 100644 backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamInviteCommand.java create mode 100644 backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamLeaveCommand.java create mode 100644 backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamListCommand.java create mode 100644 backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamProfileCommand.java rename {bot/src/main/java/de/chojo/gamejam => backend/src/main/java/de/chojo/pluginjam/bot}/commands/team/TeamPromoteCommand.java (50%) create mode 100644 backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamRenameCommand.java rename {bot/src/main/java/de/chojo/gamejam => backend/src/main/java/de/chojo/pluginjam/bot}/commands/vote/VoteCommand.java (60%) rename {bot/src/main/java/de/chojo/gamejam => backend/src/main/java/de/chojo/pluginjam/bot}/commands/vote/VotesInfoCommand.java (55%) create mode 100644 backend/src/main/java/de/chojo/pluginjam/bot/commands/vote/VotesRankingCommand.java create mode 100644 backend/src/main/java/de/chojo/pluginjam/bot/message/DefaultValues.java create mode 100644 backend/src/main/java/de/chojo/pluginjam/database/entity/VotingRank.java create mode 100644 backend/src/main/java/de/chojo/pluginjam/database/repository/VotesRankingRepository.java create mode 100644 backend/src/main/java/de/chojo/pluginjam/service/VoteService.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/team/TeamApiCommand.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/team/TeamCreateCommand.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/team/TeamDisbandCommand.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/team/TeamInviteCommand.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/team/TeamLeaveCommand.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/team/TeamListCommand.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/team/TeamProfileCommand.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/team/TeamRenameCommand.java delete mode 100644 bot/src/main/java/de/chojo/gamejam/commands/vote/VotesRankingCommand.java diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/Bot.java b/backend/src/main/java/de/chojo/pluginjam/bot/Bot.java index 97953a96..d4c0b46b 100644 --- a/backend/src/main/java/de/chojo/pluginjam/bot/Bot.java +++ b/backend/src/main/java/de/chojo/pluginjam/bot/Bot.java @@ -8,10 +8,12 @@ import com.google.inject.Guice; import de.chojo.pluginjam.bot.commands.CommandContextProvider; +import de.chojo.pluginjam.bot.listener.InviteButtonListener; import de.chojo.pluginjam.database.repository.SettingsRepository; import de.chojo.pluginjam.service.JamService; import de.chojo.pluginjam.service.SettingsService; import de.chojo.pluginjam.service.TeamService; +import de.chojo.pluginjam.service.VoteService; import io.github.kaktushose.jdac.JDACommands; import io.github.kaktushose.jdac.annotations.interactions.CommandConfig; import io.github.kaktushose.jdac.annotations.interactions.CommandScope; @@ -42,14 +44,16 @@ public class Bot implements ApplicationEventListener { private final JamService jamService; private final SettingsService settingsService; private final TeamService teamService; + private final VoteService voteService; private final String token; private ShardManager shardManager; - public Bot(JamService jamService, SettingsService settingsService, TeamService teamService, @Value("${bot.token}") String token) { + public Bot(JamService jamService, SettingsService settingsService, TeamService teamService, VoteService voteService, @Value("${bot.token}") String token) { this.jamService = jamService; this.settingsService = settingsService; this.teamService = teamService; + this.voteService = voteService; this.token = token; } @@ -85,7 +89,8 @@ private void buildCommands() { var userContextProvider = new CommandContextProvider( jamService, settingsService, - teamService + teamService, + voteService ); var injector = Guice.createInjector(new BotModule(userContextProvider)); JDACommands.builder(shardManager) @@ -102,7 +107,7 @@ private void buildCommands() { } }) .start(); - //shardManager.addEventListener(new InviteButtonListener(guilds)); + shardManager.addEventListener(new InviteButtonListener(teamService, settingsService, jamService)); } private void initBot() { diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/commands/CommandContextProvider.java b/backend/src/main/java/de/chojo/pluginjam/bot/commands/CommandContextProvider.java index 27a610c6..abadbb76 100644 --- a/backend/src/main/java/de/chojo/pluginjam/bot/commands/CommandContextProvider.java +++ b/backend/src/main/java/de/chojo/pluginjam/bot/commands/CommandContextProvider.java @@ -3,16 +3,19 @@ import de.chojo.pluginjam.service.JamService; import de.chojo.pluginjam.service.SettingsService; import de.chojo.pluginjam.service.TeamService; +import de.chojo.pluginjam.service.VoteService; public class CommandContextProvider { private final JamService jamService; private final SettingsService settingsService; private final TeamService teamService; + private final VoteService voteService; - public CommandContextProvider(JamService jamService, SettingsService settingsService, TeamService teamService) { + public CommandContextProvider(JamService jamService, SettingsService settingsService, TeamService teamService, VoteService voteService) { this.jamService = jamService; this.settingsService = settingsService; this.teamService = teamService; + this.voteService = voteService; } public JamService pluginJamService() { @@ -26,4 +29,8 @@ public SettingsService settingsService() { public TeamService teamService() { return teamService; } + + public VoteService voteService() { + return voteService; + } } diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamCreateCommand.java b/backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamCreateCommand.java index f9bf7af5..b1c37521 100644 --- a/backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamCreateCommand.java +++ b/backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamCreateCommand.java @@ -102,8 +102,6 @@ public void onCommand(CommandEvent event, @Param("name") String teamName) { .getIdLong(), Collections.emptySet(), EnumSet.of(Permission.VIEW_CHANNEL)) .complete(); - //meta.token(Token.generate(40)); - var team = commandContextProvider.teamService().createTeam( jam.id(), teamName, diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamInviteCommand.java b/backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamInviteCommand.java new file mode 100644 index 00000000..eaf890fe --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamInviteCommand.java @@ -0,0 +1,111 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.pluginjam.bot.commands.team; + +import com.google.inject.Inject; +import de.chojo.pluginjam.bot.commands.CommandContextProvider; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.annotations.interactions.Param; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; +import io.github.kaktushose.jdac.message.placeholder.Entry; +import io.github.kaktushose.jdac.message.resolver.MessageResolver; +import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.components.Component; +import net.dv8tion.jda.api.components.actionrow.ActionRow; +import net.dv8tion.jda.api.components.buttons.Button; +import net.dv8tion.jda.api.components.container.Container; +import net.dv8tion.jda.api.components.textdisplay.TextDisplay; +import net.dv8tion.jda.api.entities.User; +import net.dv8tion.jda.api.interactions.commands.OptionType; + +@Bundle("locale") +@Interaction +public final class TeamInviteCommand { + private final CommandContextProvider commandContextProvider; + private final MessageResolver messageResolver; + + @Inject + public TeamInviteCommand(CommandContextProvider commandContextProvider, MessageResolver messageResolver) { + this.commandContextProvider = commandContextProvider; + this.messageResolver = messageResolver; + } + + @Command(value = "team invite") + public void onCommand(CommandEvent event, @Param(value = "user", type = OptionType.USER) User user) { + var guildId = event.getGuild().getIdLong(); + var optJam = commandContextProvider.pluginJamService().getCurrentOrUpcoming(guildId); + + if (optJam.isEmpty()) { + event.with().ephemeral(true).reply("error-nojamactive"); + return; + } + var jam = optJam.get(); + + if (jam.state().voting()) { + event.with().ephemeral(true).reply("error-votingactive"); + return; + } + + var optTeam = commandContextProvider.teamService().getUserTeam(event.getUser().getIdLong()); + + if (optTeam.isEmpty()) { + event.with().ephemeral(true).reply("error-noteam"); + return; + } + + var team = optTeam.get(); + + if (!team.isLeader(event.getUser())) { + event.with().ephemeral(true).reply("command-team-invite-message-noleader"); + return; + } + + var members = team.members(); + var settings = commandContextProvider.settingsService().getSettings(guildId); + + if (members.size() >= settings.getTeamSize()) { + event.with().ephemeral(true).reply("error-maxteamsize"); + return; + } + + if (jam.registrations().stream().noneMatch(r -> r.userId().equals(user.getIdLong()))) { + event.with().ephemeral(true).reply("command-team-invite-message-notRegistered"); + return; + } + + var currTeam = commandContextProvider.teamService().getUserTeam(user.getIdLong()); + + if (currTeam.isPresent()) { + event.reply("command-team-invite-message-partofteam"); + return; + } + + var teamId = team.id(); + var buttonId = "invite-accept:" + guildId + ":" + teamId + ":" + user.getIdLong(); + + var components = Container.of( + TextDisplay.of(messageResolver.resolve("command-team-invite-message-invitation", event.getGuildLocale(), + Entry.entry("USER", user.getAsMention()), + Entry.entry("TEAM", team.meta().getTeamName())) + ), + ActionRow.of(Button.success(buttonId, messageResolver.resolve("command-team-invite-message-accept", event.getGuildLocale()))) + ); + + var embed = new EmbedBuilder() + .setTitle("You have been invited to join a team on " + event.getGuild().getName()) + .setDescription(event.getUser().getAsMention() + " has invited you to join team **" + team.meta().getTeamName() + "**") + .build(); + + user.openPrivateChannel().queue(channel -> + channel.sendMessageComponents(components).useComponentsV2().queue() + ); + + event.with().ephemeral(true).reply("command-team-invite-message-send"); + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamLeaveCommand.java b/backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamLeaveCommand.java new file mode 100644 index 00000000..c301bbb2 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamLeaveCommand.java @@ -0,0 +1,56 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.pluginjam.bot.commands.team; + +import com.google.inject.Inject; +import de.chojo.pluginjam.bot.commands.CommandContextProvider; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; + +@Bundle("locale") +@Interaction +public final class TeamLeaveCommand { + private final CommandContextProvider commandContextProvider; + + @Inject + public TeamLeaveCommand(CommandContextProvider commandContextProvider) { + this.commandContextProvider = commandContextProvider; + } + + @Command(value = "team leave") + public void onCommand(CommandEvent event) { + var guildId = event.getGuild().getIdLong(); + var optJam = commandContextProvider.pluginJamService().getCurrentOrUpcoming(guildId); + + if (optJam.isEmpty()) { + event.with().ephemeral(true).reply("error-nojamactive"); + return; + } + var jam = optJam.get(); + + if (jam.state().voting()) { + event.with().ephemeral(true).reply("error-votingactive"); + return; + } + + var teamOpt = commandContextProvider.teamService().getUserTeam(event.getMember().getIdLong()); + teamOpt.ifPresentOrElse((team) -> { + if (team.isLeader(event.getUser())) { + event.with().ephemeral(true).reply("command-team-leave-message-leaderleave"); + return; + } + + commandContextProvider.teamService().leaveTeam(team, event.getMember(), event.getGuild()); + event.with().ephemeral(true).reply("command-team-leave-left"); + event.getGuild().getTextChannelById(team.meta().getTextChannelId()).sendMessage("command-team-leave-message-left").queue(); + }, () -> { + event.with().ephemeral(true).reply("error-noteam"); + }); + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamListCommand.java b/backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamListCommand.java new file mode 100644 index 00000000..7024ed38 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamListCommand.java @@ -0,0 +1,125 @@ + /* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + + package de.chojo.pluginjam.bot.commands.team; + + import com.google.inject.Inject; + import de.chojo.pluginjam.bot.commands.CommandContextProvider; + import de.chojo.pluginjam.bot.message.DefaultValues; + import de.chojo.pluginjam.bot.util.MentionUtil; + import de.chojo.pluginjam.database.entity.team.Team; + import io.github.kaktushose.jdac.annotations.i18n.Bundle; + import io.github.kaktushose.jdac.annotations.interactions.Button; + import io.github.kaktushose.jdac.annotations.interactions.Command; + import io.github.kaktushose.jdac.annotations.interactions.Interaction; + import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; + import io.github.kaktushose.jdac.dispatching.events.interactions.ComponentEvent; + import io.github.kaktushose.jdac.dispatching.reply.Component; + import io.github.kaktushose.jdac.message.resolver.MessageResolver; + import net.dv8tion.jda.api.components.actionrow.ActionRow; + import net.dv8tion.jda.api.components.buttons.ButtonStyle; + import net.dv8tion.jda.api.components.container.Container; + import net.dv8tion.jda.api.components.container.ContainerChildComponent; + import net.dv8tion.jda.api.components.textdisplay.TextDisplay; + import net.dv8tion.jda.api.interactions.DiscordLocale; + + import java.util.List; + import java.util.UUID; + import java.util.stream.Collectors; + + @Bundle("locale") + @Interaction + public class TeamListCommand { + private final CommandContextProvider commandContextProvider; + private final MessageResolver messageResolver; + + private List pageEntries; + private int currentPage; + + @Inject + public TeamListCommand(CommandContextProvider commandContextProvider, MessageResolver messageResolver) { + this.commandContextProvider = commandContextProvider; + this.messageResolver = messageResolver; + } + + @Command(value = "team list") + public void onCommand(CommandEvent event) { + var guildId = event.getGuild().getIdLong(); + var optJam = commandContextProvider.pluginJamService().getCurrentOrUpcoming(guildId); + + if (optJam.isEmpty()) { + event.with().ephemeral(true).reply("error-nojamactive"); + return; + } + var jam = optJam.get(); + + pageEntries = commandContextProvider.teamService().getTeamsByJamId(jam.id()); + + currentPage = 0; + event.with().ephemeral(true).reply(renderPage(0, pageEntries, event.getUserLocale())); + } + + private Container renderPage(int page, List entries, DiscordLocale locale) { + int pageSize = 1; + + boolean hasNext = page < (entries.size() - 1) / pageSize; + boolean hasPrev = page > 0; + + int start = page * pageSize; + int end = Math.min(start + pageSize, entries.size()); + + List teamContents = entries.subList(start, end).stream().map(team -> { + var membersContent = team.members() + .stream() + .map(teamMember -> String.format("- %s", MentionUtil.user(teamMember.userId()))) + .collect(Collectors.joining(" \n")); + + var contentString = """ + # %s (%s) + ### %s: + *%s* + ### %s: + *%s* + ### %s: + *%s* + """.formatted( + team.meta().getTeamName(), + MentionUtil.role(team.meta().getRoleId()), + messageResolver.resolve("word-description", locale), + DefaultValues.getTeamProjectDescription(team, messageResolver, locale), + messageResolver.resolve("word-project-url", locale), + DefaultValues.getTeamProjectUrl(team, messageResolver, locale), + messageResolver.resolve("word-members", locale), + membersContent); + + return TextDisplay.of(contentString); + }).collect(Collectors.toList()); + + int maxPages = (int) Math.ceil(entries.size() / (double) pageSize); + + teamContents.add( + ActionRow.of( + hasPrev ? Component.enabled("onPrev") : Component.disabled("onPrev"), + net.dv8tion.jda.api.components.buttons.Button.of(ButtonStyle.SECONDARY, UUID.randomUUID().toString(), "%d / %d".formatted(currentPage + 1, maxPages), null), + hasNext ? Component.enabled("onNext") : Component.disabled("onNext") + ) + ); + + return Container.of(teamContents); + } + + @Button(value = "◀", style = ButtonStyle.SECONDARY) + public void onPrev(ComponentEvent event) { + currentPage--; + event.with().keepComponents(false).ephemeral(true).reply(renderPage(currentPage, pageEntries, event.getUserLocale())); + } + + @Button(value = "▶", style = ButtonStyle.SECONDARY) + public void onNext(ComponentEvent event) { + currentPage++; + event.with().keepComponents(false).ephemeral(true).reply(renderPage(currentPage, pageEntries, event.getUserLocale())); + } + } diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamProfileCommand.java b/backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamProfileCommand.java new file mode 100644 index 00000000..f877e682 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamProfileCommand.java @@ -0,0 +1,81 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.pluginjam.bot.commands.team; + +import com.google.inject.Inject; +import de.chojo.pluginjam.bot.commands.CommandContextProvider; +import de.chojo.pluginjam.bot.message.EmbedHelper; +import de.chojo.pluginjam.database.entity.team.Team; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.*; +import io.github.kaktushose.jdac.dispatching.events.interactions.AutoCompleteEvent; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; +import io.github.kaktushose.jdac.embeds.EmbedConfig; +import io.github.kaktushose.jdac.embeds.EmbedDataSource; +import net.dv8tion.jda.api.entities.User; +import net.dv8tion.jda.api.interactions.commands.OptionType; + +import java.util.Collections; +import java.util.List; + +@Bundle("locale") +@Interaction +public final class TeamProfileCommand { + private final CommandContextProvider commandContextProvider; + + @Inject + public TeamProfileCommand(CommandContextProvider commandContextProvider) { + this.commandContextProvider = commandContextProvider; + } + + @Command(value = "team profile") + public void onCommand( + CommandEvent event, + @Param(name = "user", optional = true, type = OptionType.USER) User user, + @Param(value = "team", optional = true) String teamName + ) { + var guildId = event.getGuild().getIdLong(); + var optJam = commandContextProvider.pluginJamService().getCurrentOrUpcoming(guildId); + + if (optJam.isEmpty()) { + event.with().ephemeral(true).reply("error-nojamactive"); + return; + } + var jam = optJam.get(); + var teams = commandContextProvider.teamService().getTeamsByJamId(jam.id()); + + if (user != null) { + var userTeam = commandContextProvider.teamService().getUserTeam(user.getIdLong()); + userTeam.ifPresentOrElse(team -> sendProfile(event, team), + () -> event.with().ephemeral(true).reply("command-team-profile-message-nouserteam")); + return; + } + if (teamName != null) { + var namedTeam = commandContextProvider.teamService().getTeamByName(jam.id(), teamName); + + namedTeam.ifPresentOrElse(team -> sendProfile(event, team), + () -> event.with().ephemeral(true).reply("error-unkownteam")); + return; + } + var userTeam = commandContextProvider.teamService().getUserTeam(event.getMember().getIdLong()); + + userTeam.ifPresentOrElse(team -> sendProfile(event, team), + () -> event.with().ephemeral(true).reply("error-noteam")); + } + + private void sendProfile(CommandEvent event, Team team) { + event.reply(EmbedHelper.teamProfileEmbed(team, event)); + } + + @AutoComplete(value = "team profile", options = "team") + public void onAutoComplete(AutoCompleteEvent event) { + var guildId = event.getGuild().getIdLong(); + var teams = commandContextProvider.teamService().getTeamsByJamId(commandContextProvider.pluginJamService().getCurrentOrUpcoming(guildId).get().id()); + var choices = teams.stream().map(team -> team.meta().getTeamName()).map(s -> new net.dv8tion.jda.api.interactions.commands.Command.Choice(s, s)).toList(); + event.replyChoices(choices); + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/team/TeamPromoteCommand.java b/backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamPromoteCommand.java similarity index 50% rename from bot/src/main/java/de/chojo/gamejam/commands/team/TeamPromoteCommand.java rename to backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamPromoteCommand.java index 9df417e9..ffc78021 100644 --- a/bot/src/main/java/de/chojo/gamejam/commands/team/TeamPromoteCommand.java +++ b/backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamPromoteCommand.java @@ -4,20 +4,16 @@ * Copyright (C) 2022 DevCord Team and Contributor */ -package de.chojo.gamejam.commands.team; +package de.chojo.pluginjam.bot.commands.team; import com.google.inject.Inject; -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.data.dao.JamGuild; -import de.chojo.gamejam.server.CommandContextProvider; -import de.chojo.jdautil.wrapper.EventContext; +import de.chojo.pluginjam.bot.commands.CommandContextProvider; import io.github.kaktushose.jdac.annotations.i18n.Bundle; import io.github.kaktushose.jdac.annotations.interactions.Command; import io.github.kaktushose.jdac.annotations.interactions.Interaction; import io.github.kaktushose.jdac.annotations.interactions.Param; import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; import net.dv8tion.jda.api.entities.User; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.interactions.commands.OptionType; @Bundle("locale") @@ -32,26 +28,29 @@ public TeamPromoteCommand(CommandContextProvider commandContextProvider) { @Command(value = "team promote") public void onCommand(CommandEvent event, @Param(value = "user", type = OptionType.USER) User user) { - JamGuild guild = commandContextProvider.guilds().guild(event); - var optJam = guild.jams().nextOrCurrent(); + var guildId = event.getGuild().getIdLong(); + var optJam = commandContextProvider.pluginJamService().getCurrentOrUpcoming(guildId); + if (optJam.isEmpty()) { - event.with().ephemeral(true).reply("error.nojamactive"); + event.with().ephemeral(true).reply("error-nojamactive"); return; } - var jam = optJam.get(); - var member = guild.guild().getMember(user); + var member = event.getGuild().getMember(user); + + var currentTeam = commandContextProvider.teamService().getUserTeam(user.getIdLong()); - jam.teams().byMember(user).ifPresentOrElse( - targetTeam -> { - if (!targetTeam.isLeader(event.getUser())) { - event.with().ephemeral(true).reply("error.noleader"); - return; - } + currentTeam.ifPresentOrElse((targetTeam) -> { + if (!targetTeam.isLeader(event.getUser())) { + event.with().ephemeral(true).reply("error-noleader"); + return; + } - targetTeam.meta().leader(member); - event.with().ephemeral(true).reply("command.team.promote.message.done"); - }, - () -> event.with().ephemeral(true).reply("error.noteam")); + targetTeam.meta().getLeaderId(member.getIdLong()); + commandContextProvider.teamService().saveTeam(targetTeam); + event.with().ephemeral(true).reply("command.team.promote.message.done"); + }, () -> { + event.with().ephemeral(true).reply("error-noteam"); + }); } } diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamRenameCommand.java b/backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamRenameCommand.java new file mode 100644 index 00000000..8bf691cd --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamRenameCommand.java @@ -0,0 +1,62 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.pluginjam.bot.commands.team; + +import com.google.inject.Inject; +import de.chojo.pluginjam.bot.commands.CommandContextProvider; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.annotations.interactions.Param; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; + +@Bundle("locale") +@Interaction +public class TeamRenameCommand { + private final CommandContextProvider commandContextProvider; + + @Inject + public TeamRenameCommand(CommandContextProvider commandContextProvider) { + this.commandContextProvider = commandContextProvider; + } + + @Command(value = "team rename") + public void onCommand(CommandEvent event, @Param("name") String teamName) { + var guildId = event.getGuild().getIdLong(); + var optJam = commandContextProvider.pluginJamService().getCurrentOrUpcoming(guildId); + if (optJam.isEmpty()) { + event.with().ephemeral(true).reply("error-nojamactive"); + return; + } + var jam = optJam.get(); + + var existingTeam = commandContextProvider.teamService().getTeamByName(jam.id(), teamName); + if (existingTeam.isPresent()) { + event.with().ephemeral(true).reply("command-team-create-message-nametaken"); + return; + } + + var optCurrTeam = commandContextProvider.teamService().getUserTeam(event.getMember().getIdLong()); + + if (optCurrTeam.isEmpty()) { + event.with().ephemeral(true).reply("error-noteam"); + return; + } + + var team = optCurrTeam.get(); + + if (!team.isLeader(event.getUser())) { + event.with().ephemeral(true).reply("error-noleader"); + return; + } + + team.meta().getTeamName(teamName); + + commandContextProvider.teamService().saveTeam(team); + event.with().ephemeral(true).reply("command-team-rename-message-done"); + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/vote/VoteCommand.java b/backend/src/main/java/de/chojo/pluginjam/bot/commands/vote/VoteCommand.java similarity index 60% rename from bot/src/main/java/de/chojo/gamejam/commands/vote/VoteCommand.java rename to backend/src/main/java/de/chojo/pluginjam/bot/commands/vote/VoteCommand.java index 733c3a1a..c6bfdb9e 100644 --- a/bot/src/main/java/de/chojo/gamejam/commands/vote/VoteCommand.java +++ b/backend/src/main/java/de/chojo/pluginjam/bot/commands/vote/VoteCommand.java @@ -4,15 +4,10 @@ * Copyright (C) 2022 DevCord Team and Contributor */ -package de.chojo.gamejam.commands.vote; +package de.chojo.pluginjam.bot.commands.vote; import com.google.inject.Inject; -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.server.CommandContextProvider; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.localization.util.Format; -import de.chojo.jdautil.localization.util.Replacement; -import de.chojo.jdautil.wrapper.EventContext; +import de.chojo.pluginjam.bot.commands.CommandContextProvider; import io.github.kaktushose.jdac.annotations.i18n.Bundle; import io.github.kaktushose.jdac.annotations.interactions.AutoComplete; import io.github.kaktushose.jdac.annotations.interactions.Interaction; @@ -20,7 +15,6 @@ import io.github.kaktushose.jdac.dispatching.events.interactions.AutoCompleteEvent; import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; import io.github.kaktushose.jdac.message.placeholder.Entry; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.interactions.commands.Command; import java.util.Collections; @@ -40,76 +34,76 @@ public VoteCommand(CommandContextProvider commandContextProvider) { @io.github.kaktushose.jdac.annotations.interactions.Command(value = "vote") public void onCommand(CommandEvent event, @Param("team") String team, @Param("points") int points) { - var guild = commandContextProvider.guilds().guild(event); - var optJam = guild.jams().nextOrCurrent(); + var guildId = event.getGuild().getIdLong(); + var optJam = commandContextProvider.pluginJamService().getCurrentOrUpcoming(guildId); if (optJam.isEmpty()) { - event.with().ephemeral(true).reply("error.nojamactive"); + event.with().ephemeral(true).reply("error-nojamactive"); return; } var jam = optJam.get(); event.deferReply(true); - if (!jam.state().isVoting()) { - event.reply("command.votes.vote.message.notactive"); + if (!jam.state().voting()) { + event.reply("command-votes-vote-message-notactive"); return; } if (!jam.registrations().contains(event.getMember().getIdLong())) { - event.reply("error.notregistered"); + event.reply("error-notregistered"); return; } - var teams = jam.teams(); - var teamCount = teams.teams().size(); - var optVoteTeam = teams.byName(team); + var teams = commandContextProvider.teamService().getTeamsByJamId(jam.id()); + var teamCount = teams.size(); + var optVoteTeam = commandContextProvider.teamService().getTeamByName(jam.id(), team); if (optVoteTeam.isEmpty()) { - event.reply("error.unkownteam"); + event.reply("error-unkownteam"); return; } var voteTeam = optVoteTeam.get(); - if (voteTeam.member(event.getMember()).isPresent()) { - event.reply("command.votes.vote.message.ownteam"); + if (voteTeam.isMember(event.getUser())) { + event.reply("command-votes-vote-message-ownteam"); return; } - var user = jam.user(event.getMember()); - - var pointsGiven = user.votesGiven(); + var pointsGiven = commandContextProvider.voteService().getGivenPointsByUser(event.getUser()); //TODO: Max points and max points per team are currently hardcoded. should be configurable in the future. var finalPoints = Math.clamp(points, 0, 5); - var votes = voteTeam.votes(event.getMember()); + var votesForTeam = commandContextProvider.voteService().getPointsByTeam(voteTeam); - if (votes < finalPoints && pointsGiven + finalPoints > teamCount) { - event.reply("command.votes.vote.message.maxpointsreached", Entry.entry("REMAINING", bold(String.valueOf(teamCount - pointsGiven)))); + if (votesForTeam < finalPoints && pointsGiven + finalPoints > teamCount) { + event.reply("command-votes-vote-message-maxpointsreached", Entry.entry("REMAINING", bold(String.valueOf(teamCount - pointsGiven)))); return; } - voteTeam.vote(event.getMember(), finalPoints); + commandContextProvider.voteService().voteForTeam(event.getUser(), voteTeam, finalPoints); + var pointsGivenAfter = commandContextProvider.voteService().getGivenPointsByUser(event.getUser()); - event.reply("command.votes.vote.message.done", - Entry.entry("REMAINING", bold(String.valueOf(teamCount - user.votesGiven()))), + event.reply("command-votes-vote-message-done", + Entry.entry("REMAINING", bold(String.valueOf(teamCount - pointsGivenAfter))), Entry.entry("POINTS", bold(String.valueOf(finalPoints))), - Entry.entry("TEAM", bold(voteTeam.meta().name())) + Entry.entry("TEAM", bold(voteTeam.meta().getTeamName())) ); } @AutoComplete(value = "vote", options = "team") public void onAutoCompleteTeam(AutoCompleteEvent event) { - var jam = commandContextProvider.guilds().guild(event).jams().nextOrCurrent(); + var jam = commandContextProvider.pluginJamService().getActiveJam(event.getGuild().getIdLong()); if (jam.isEmpty()) { event.replyChoices(Collections.emptyList()); return; } - var teams = jam.get().teams().teams().stream() - .filter(team -> team.matchName(event.getValue())) - .map(team -> team.meta().name()) + + var teams = commandContextProvider.teamService().getTeamsByJamId(jam.get().id()).stream() + .filter(team -> team.meta().getTeamName().contains(event.getValue())) + .map(team -> team.meta().getTeamName()) .map(team -> new Command.Choice(team, team)) .toList(); event.replyChoices(teams); diff --git a/bot/src/main/java/de/chojo/gamejam/commands/vote/VotesInfoCommand.java b/backend/src/main/java/de/chojo/pluginjam/bot/commands/vote/VotesInfoCommand.java similarity index 55% rename from bot/src/main/java/de/chojo/gamejam/commands/vote/VotesInfoCommand.java rename to backend/src/main/java/de/chojo/pluginjam/bot/commands/vote/VotesInfoCommand.java index 7b5dc4e4..f5ddde4e 100644 --- a/bot/src/main/java/de/chojo/gamejam/commands/vote/VotesInfoCommand.java +++ b/backend/src/main/java/de/chojo/pluginjam/bot/commands/vote/VotesInfoCommand.java @@ -4,10 +4,10 @@ * Copyright (C) 2022 DevCord Team and Contributor */ -package de.chojo.gamejam.commands.vote; +package de.chojo.pluginjam.bot.commands.vote; import com.google.inject.Inject; -import de.chojo.gamejam.server.CommandContextProvider; +import de.chojo.pluginjam.bot.commands.CommandContextProvider; import io.github.kaktushose.jdac.annotations.i18n.Bundle; import io.github.kaktushose.jdac.annotations.interactions.Command; import io.github.kaktushose.jdac.annotations.interactions.Interaction; @@ -27,24 +27,14 @@ public VotesInfoCommand(CommandContextProvider commandContextProvider) { @Command(value = "votes info") public void onCommand(CommandEvent event) { - var guild = commandContextProvider.guilds().guild(event); - var optJam = guild.jams().nextOrCurrent(); + var guildId = event.getGuild().getIdLong(); + var optJam = commandContextProvider.pluginJamService().getActiveJam(guildId); if (optJam.isEmpty()) { - event.with().ephemeral(true).reply("error.nojamactive"); + event.with().ephemeral(true).reply("error-nojamactive"); return; } var jam = optJam.get(); - var voteEntries = jam.user(event.getMember()).votes(); - var given = voteEntries.stream() - .filter(e -> e.points() != 0) - .map(e -> e.team().meta().name() + ": **" + e.points() + "**") - .collect(Collectors.joining("\n")); - - var embed = event.embed("votes-info"); - embed.title("command.votes.info.embed.title"); - embed.description(given); - - event.with().ephemeral(true).embeds(embed).reply(); + // TODO: implement a nice info message } } diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/commands/vote/VotesRankingCommand.java b/backend/src/main/java/de/chojo/pluginjam/bot/commands/vote/VotesRankingCommand.java new file mode 100644 index 00000000..de8de682 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/bot/commands/vote/VotesRankingCommand.java @@ -0,0 +1,127 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.pluginjam.bot.commands.vote; + +import com.google.inject.Inject; +import de.chojo.pluginjam.bot.commands.CommandContextProvider; +import de.chojo.pluginjam.database.entity.VotingRank; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Button; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; +import io.github.kaktushose.jdac.dispatching.events.interactions.ComponentEvent; +import io.github.kaktushose.jdac.dispatching.reply.Component; +import io.github.kaktushose.jdac.message.resolver.MessageResolver; +import net.dv8tion.jda.api.components.actionrow.ActionRow; +import net.dv8tion.jda.api.components.buttons.ButtonStyle; +import net.dv8tion.jda.api.components.container.Container; +import net.dv8tion.jda.api.components.container.ContainerChildComponent; +import net.dv8tion.jda.api.components.separator.Separator; +import net.dv8tion.jda.api.components.textdisplay.TextDisplay; +import net.dv8tion.jda.api.interactions.DiscordLocale; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +@Bundle("locale") +@Interaction +public class VotesRankingCommand { + private final CommandContextProvider commandContextProvider; + private final MessageResolver messageResolver; + + private List pageEntries; + private int currentPage; + + @Inject + public VotesRankingCommand(CommandContextProvider commandContextProvider, MessageResolver messageResolver) { + this.commandContextProvider = commandContextProvider; + this.messageResolver = messageResolver; + } + + @Command(value = "votes ranking") + public void onCommand(CommandEvent event) { + var guildId = event.getGuild().getIdLong(); + var optJam = commandContextProvider.pluginJamService().getActiveJam(guildId); + + if (optJam.isEmpty()) { + event.with().ephemeral(true).reply("error-nojamactive"); + return; + } + + var jam = optJam.get(); + + if (jam.state().voting()) { + event.with().ephemeral(true).reply("command-votes-ranking-message-voteactive"); + return; + } + + pageEntries = commandContextProvider.voteService().getVotingRanks(jam.id()); + currentPage = 0; + event.with().ephemeral(true).reply(renderPage(0, pageEntries, event.getUserLocale())); + } + + private Container renderPage(int page, List entries, DiscordLocale locale) { + int pageSize = 10; + + boolean hasNext = page < (entries.size() - 1) / pageSize; + boolean hasPrev = page > 0; + + int start = page * pageSize; + int end = Math.min(start + pageSize, entries.size()); + + var contents = new ArrayList(); + + var rankingContent = entries.subList(start, end).stream() + .map(votingRank -> String.format("`%s` **%s** (*%d Votes*)", + getRankFormat(votingRank.rank()), + votingRank.team().meta().getTeamName(), + votingRank.points())) + .collect(Collectors.joining("\n")); + + contents.add(TextDisplay.of("## " + messageResolver.resolve("command-votes-ranking-description", locale))); + contents.add(Separator.create(true, Separator.Spacing.LARGE)); + contents.add(TextDisplay.of(rankingContent)); + contents.add(Separator.create(true, Separator.Spacing.LARGE)); + + int maxPages = (int) Math.ceil(entries.size() / (double) pageSize); + + contents.add( + ActionRow.of( + hasPrev ? Component.enabled("onPrev") : Component.disabled("onPrev"), + net.dv8tion.jda.api.components.buttons.Button.of(ButtonStyle.SECONDARY, UUID.randomUUID().toString(), "%d / %d".formatted(currentPage + 1, maxPages), null), + hasNext ? Component.enabled("onNext") : Component.disabled("onNext") + ) + ); + + return Container.of(contents); + } + + @Button(value = "◀", style = ButtonStyle.SECONDARY) + public void onPrev(ComponentEvent event) { + currentPage--; + event.with().keepComponents(false).ephemeral(true).reply(renderPage(currentPage, pageEntries, event.getUserLocale())); + } + + @Button(value = "▶", style = ButtonStyle.SECONDARY) + public void onNext(ComponentEvent event) { + currentPage++; + event.with().keepComponents(false).ephemeral(true).reply(renderPage(currentPage, pageEntries, event.getUserLocale())); + } + + private String getRankFormat(int rank) { + return switch (rank) { + case 1 -> "🥇1"; + case 2 -> "🥈2"; + case 3 -> "🥉3"; + default -> " #%d".formatted(rank); + }; + } + +} diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/listener/InviteButtonListener.java b/backend/src/main/java/de/chojo/pluginjam/bot/listener/InviteButtonListener.java index e36c8852..e164d53a 100644 --- a/backend/src/main/java/de/chojo/pluginjam/bot/listener/InviteButtonListener.java +++ b/backend/src/main/java/de/chojo/pluginjam/bot/listener/InviteButtonListener.java @@ -6,6 +6,9 @@ package de.chojo.pluginjam.bot.listener; +import de.chojo.pluginjam.service.JamService; +import de.chojo.pluginjam.service.SettingsService; +import de.chojo.pluginjam.service.TeamService; import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; import net.dv8tion.jda.api.hooks.ListenerAdapter; import org.slf4j.Logger; @@ -13,13 +16,17 @@ import static org.slf4j.LoggerFactory.getLogger; public class InviteButtonListener extends ListenerAdapter { - /*private static final Logger log = getLogger(InviteButtonListener.class); + private static final Logger log = getLogger(InviteButtonListener.class); private static final String BUTTON_PREFIX = "invite-accept:"; - private final Guilds guilds; + private final TeamService teamService; + private final SettingsService settingsService; + private final JamService jamService; - public InviteButtonListener(Guilds guilds) { - this.guilds = guilds; + public InviteButtonListener(TeamService teamService, SettingsService settingsService, JamService jamService) { + this.teamService = teamService; + this.settingsService = settingsService; + this.jamService = jamService; } @Override @@ -58,46 +65,40 @@ public void onButtonInteraction(ButtonInteractionEvent event) { var user = manager.retrieveUserById(userId).complete(); var member = guild.retrieveMember(user).complete(); - var jamGuild = guilds.guild(guild); - var settings = jamGuild.jamSettings(); + var settings = settingsService.getSettings(guildId); - var optJam = jamGuild.jams().nextOrCurrent(); + var optJam = jamService.getCurrentOrUpcoming(guildId); if (optJam.isEmpty()) { event.getHook().editOriginal("The game jam is over.").queue(); return; } var jam = optJam.get(); - var optTeam = jam.teams().byId(teamId); + var optTeam = teamService.getTeam(teamId); if (optTeam.isEmpty()) { event.getHook().editOriginal("Team not found.").queue(); return; } var team = optTeam.get(); - var members = team.member(); + var members = team.members(); - if (members.size() >= settings.teamSize()) { + if (members.size() >= settings.getTeamSize()) { event.getHook().editOriginal("The team is already full.").queue(); return; } - var currTeam = jam.teams().byMember(user); + var currTeam = teamService.getUserTeam(user.getIdLong()); if (currTeam.isPresent()) { event.getHook().editOriginal("You are already part of a team.").queue(); return; } - jam.user(member).join(team); - team.meta().role().ifPresent(role -> guild.addRoleToMember(member, role).queue()); + teamService.joinTeam(team, member, guild); event.getHook().editOriginal("You have joined the team!").queue(); - team.meta().textChannel().ifPresent(channel -> - channel.sendMessage(member.getAsMention() + " has joined the team!").queue() - ); - // Disable the button after use + guild.getTextChannelById(team.meta().getTextChannelId()).sendMessage(member.getAsMention() + " has joined the team!").queue(); + event.getMessage().editMessageComponents().queue(); } - - */ } diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/message/DefaultValues.java b/backend/src/main/java/de/chojo/pluginjam/bot/message/DefaultValues.java new file mode 100644 index 00000000..ae59ccc3 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/bot/message/DefaultValues.java @@ -0,0 +1,22 @@ +package de.chojo.pluginjam.bot.message; + +import de.chojo.pluginjam.database.entity.team.Team; +import io.github.kaktushose.jdac.message.resolver.MessageResolver; +import net.dv8tion.jda.api.interactions.DiscordLocale; + +public class DefaultValues { + + public static String getTeamProjectDescription(Team team, MessageResolver resolver, DiscordLocale locale) { + if (team.meta().getProjectDescription() == null || team.meta().getProjectDescription().isBlank()) { + return resolver.resolve("word-no-description", locale); + } + return team.meta().getProjectDescription(); + } + + public static String getTeamProjectUrl(Team team, MessageResolver resolver, DiscordLocale locale) { + if (team.meta().getProjectDescription() == null || team.meta().getProjectDescription().isBlank()) { + return resolver.resolve("word-no-url", locale); + } + return team.meta().getProjectUrl(); + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/message/EmbedHelper.java b/backend/src/main/java/de/chojo/pluginjam/bot/message/EmbedHelper.java index a7ec4055..9e838b46 100644 --- a/backend/src/main/java/de/chojo/pluginjam/bot/message/EmbedHelper.java +++ b/backend/src/main/java/de/chojo/pluginjam/bot/message/EmbedHelper.java @@ -9,6 +9,7 @@ import de.chojo.pluginjam.bot.util.MentionUtil; import de.chojo.pluginjam.database.entity.SettingsEntity; import de.chojo.pluginjam.database.entity.jam.Jam; +import de.chojo.pluginjam.database.entity.team.Team; import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; import io.github.kaktushose.jdac.message.resolver.MessageResolver; import net.dv8tion.jda.api.EmbedBuilder; @@ -17,7 +18,6 @@ import net.dv8tion.jda.api.utils.TimeFormat; import java.awt.*; -import java.time.Instant; import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.List; @@ -56,6 +56,14 @@ public class EmbedHelper { */ + public static MessageEmbed teamProfileEmbed(Team team, CommandEvent event) { + var embed = new EmbedBuilder(); + embed.setAuthor(team.meta().getTeamName(), null, team.meta().getProjectUrl()); + embed.setDescription(team.meta().getProjectDescription()); + team.members().forEach(member -> embed.addField(MentionUtil.user(member.userId()), "", true)); + return embed.build(); + } + public static MessageEmbed buildJamListEmbed(List jams, CommandEvent event, MessageResolver messageResolver, DiscordLocale locale) { var embed = new EmbedBuilder(); embed.setTitle(messageResolver.resolve("word-jams", locale)); diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/message/StringValidator.java b/backend/src/main/java/de/chojo/pluginjam/bot/message/StringValidator.java index a6aa63fc..be84abf8 100644 --- a/backend/src/main/java/de/chojo/pluginjam/bot/message/StringValidator.java +++ b/backend/src/main/java/de/chojo/pluginjam/bot/message/StringValidator.java @@ -1,7 +1,7 @@ package de.chojo.pluginjam.bot.message; public class StringValidator { - private static String allowedCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\\._-"; + private static String allowedCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\\.\s_-"; private static int maxLength = 100; public static boolean isValidTeamName(String input) { diff --git a/backend/src/main/java/de/chojo/pluginjam/database/entity/VotingRank.java b/backend/src/main/java/de/chojo/pluginjam/database/entity/VotingRank.java new file mode 100644 index 00000000..91501efa --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/database/entity/VotingRank.java @@ -0,0 +1,24 @@ +package de.chojo.pluginjam.database.entity; + +import de.chojo.pluginjam.database.entity.team.Team; +import io.micronaut.data.annotation.Id; +import io.micronaut.data.annotation.MappedEntity; +import io.micronaut.data.annotation.MappedProperty; +import io.micronaut.data.annotation.Relation; + +import java.util.List; + +@MappedEntity(value = "team_ranking", schema = "gamejam") +public record VotingRank( + @Id + @MappedProperty("team_id") + Long teamId, + Integer rank, + Integer points, + @MappedProperty("jam_id") + Integer jamId, + @Relation(value = Relation.Kind.ONE_TO_ONE, mappedBy = "id") + Team team +) { + +} diff --git a/backend/src/main/java/de/chojo/pluginjam/database/entity/team/Team.java b/backend/src/main/java/de/chojo/pluginjam/database/entity/team/Team.java index 1370bc93..954c049f 100644 --- a/backend/src/main/java/de/chojo/pluginjam/database/entity/team/Team.java +++ b/backend/src/main/java/de/chojo/pluginjam/database/entity/team/Team.java @@ -2,6 +2,7 @@ import io.micronaut.data.annotation.*; import io.micronaut.serde.annotation.Serdeable; +import net.dv8tion.jda.api.entities.User; import java.util.List; @@ -18,4 +19,11 @@ public record Team( @Relation(value = Relation.Kind.ONE_TO_MANY, mappedBy = "teamId") List members ) { + public boolean isLeader(User user) { + return members.stream().anyMatch(member -> member.userId() == user.getIdLong()); + } + + public boolean isMember(User user) { + return members.stream().anyMatch(member -> member.userId() == user.getIdLong()); + } } diff --git a/backend/src/main/java/de/chojo/pluginjam/database/entity/team/TeamMeta.java b/backend/src/main/java/de/chojo/pluginjam/database/entity/team/TeamMeta.java index 189c6ef9..0c50d3fc 100644 --- a/backend/src/main/java/de/chojo/pluginjam/database/entity/team/TeamMeta.java +++ b/backend/src/main/java/de/chojo/pluginjam/database/entity/team/TeamMeta.java @@ -7,24 +7,123 @@ @Serdeable @MappedEntity(value = "team_meta", schema = "gamejam") -public record TeamMeta( - @Id - @MappedProperty("team_id") - Integer teamId, - @MappedProperty("team_name") - String teamName, - @MappedProperty("leader_id") - Long leaderId, - @MappedProperty("role_id") - Long roleId, - @MappedProperty("text_channel_id") - Long textChannelId, - @MappedProperty("voice_channel_id") - Long voiceChannelId, - @MappedProperty("project_description") - String projectDescription, - @MappedProperty("project_url") - String projectUrl, - String token -) { +public final class TeamMeta { + @Id + @MappedProperty("team_id") + private Integer teamId; + @MappedProperty("team_name") + private String teamName; + @MappedProperty("leader_id") + private Long leaderId; + @MappedProperty("role_id") + private Long roleId; + @MappedProperty("text_channel_id") + private Long textChannelId; + @MappedProperty("voice_channel_id") + private Long voiceChannelId; + @MappedProperty("project_description") + private String projectDescription; + @MappedProperty("project_url") + private String projectUrl; + private String token; + + public TeamMeta( + @Id + Integer teamId, + String teamName, + Long leaderId, + Long roleId, + Long textChannelId, + Long voiceChannelId, + String projectDescription, + String projectUrl, + String token + ) { + this.teamId = teamId; + this.teamName = teamName; + this.leaderId = leaderId; + this.roleId = roleId; + this.textChannelId = textChannelId; + this.voiceChannelId = voiceChannelId; + this.projectDescription = projectDescription; + this.projectUrl = projectUrl; + this.token = token; + } + + @Id + @MappedProperty("team_id") + public Integer getTeamId() { + return teamId; + } + + @MappedProperty("team_name") + public String getTeamName() { + return teamName; + } + + @MappedProperty("leader_id") + public Long getLeaderId() { + return leaderId; + } + + @MappedProperty("role_id") + public Long getRoleId() { + return roleId; + } + + @MappedProperty("text_channel_id") + public Long getTextChannelId() { + return textChannelId; + } + + @MappedProperty("voice_channel_id") + public Long getVoiceChannelId() { + return voiceChannelId; + } + + @MappedProperty("project_description") + public String getProjectDescription() { + return projectDescription; + } + + @MappedProperty("project_url") + public String getProjectUrl() { + return projectUrl; + } + + public String getToken() { + return token; + } + + public void getTeamName(String teamName) { + this.teamName = teamName; + } + + public void getLeaderId(Long leaderId) { + this.leaderId = leaderId; + } + + public void getRoleId(Long roleId) { + this.roleId = roleId; + } + + public void getTextChannelId(Long textChannelId) { + this.textChannelId = textChannelId; + } + + public void getVoiceChannelId(Long voiceChannelId) { + this.voiceChannelId = voiceChannelId; + } + + public void getProjectDescription(String projectDescription) { + this.projectDescription = projectDescription; + } + + public void getProjectUrl(String projectUrl) { + this.projectUrl = projectUrl; + } + + public void token(String token) { + this.token = token; + } } diff --git a/backend/src/main/java/de/chojo/pluginjam/database/repository/VotesRankingRepository.java b/backend/src/main/java/de/chojo/pluginjam/database/repository/VotesRankingRepository.java new file mode 100644 index 00000000..2d972fdd --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/database/repository/VotesRankingRepository.java @@ -0,0 +1,17 @@ +package de.chojo.pluginjam.database.repository; + +import de.chojo.pluginjam.database.entity.VotingRank; +import io.micronaut.data.annotation.Join; +import io.micronaut.data.jdbc.annotation.JdbcRepository; +import io.micronaut.data.model.query.builder.sql.Dialect; +import io.micronaut.data.repository.CrudRepository; + +import java.util.List; + +@JdbcRepository(dialect = Dialect.POSTGRES) +public interface VotesRankingRepository extends CrudRepository { + + @Join(value = "team", type = Join.Type.LEFT_FETCH) + @Join(value = "team.meta", type = Join.Type.LEFT_FETCH) + List findAllByJamId(Integer jamId); +} diff --git a/backend/src/main/java/de/chojo/pluginjam/database/repository/VotesRepository.java b/backend/src/main/java/de/chojo/pluginjam/database/repository/VotesRepository.java index 6b9e1d2d..809c8000 100644 --- a/backend/src/main/java/de/chojo/pluginjam/database/repository/VotesRepository.java +++ b/backend/src/main/java/de/chojo/pluginjam/database/repository/VotesRepository.java @@ -1,6 +1,7 @@ package de.chojo.pluginjam.database.repository; import de.chojo.pluginjam.database.entity.VoteEntity; +import de.chojo.pluginjam.database.entity.VotingRank; import io.micronaut.data.annotation.Query; import io.micronaut.data.jdbc.annotation.JdbcRepository; import io.micronaut.data.model.query.builder.sql.Dialect; @@ -9,14 +10,17 @@ import java.util.List; @JdbcRepository(dialect = Dialect.POSTGRES) -public interface VotesRepository extends GenericRepository { +public interface VotesRepository extends GenericRepository { - @Query("SELECT * FROM gamejam.vote WHERE team_id = :teamId") - List findByTeamId(Long teamId); + @Query("SELECT sum(points) FROM gamejam.votes WHERE team_id = :teamId") + int findByTeamId(int teamId); - @Query("INSERT INTO gamejam.vote (team_id, voter_id, points) VALUES (:teamId, :voterId, :points) ON CONFLICT (team_id, voter_id) DO UPDATE SET points = :points") - void save(Long teamId, Long voterId, Integer points); + @Query("SELECT sum(points) FROM gamejam.votes WHERE voter_id = :voterId") + int findByVoterId(Long voterId); - @Query("DELETE FROM gamejam.vote WHERE team_id = :teamId AND voter_id = :voterId") - void delete(Long teamId, Long voterId); + @Query("INSERT INTO gamejam.votes (team_id, voter_id, points) VALUES (:teamId, :voterId, :points) ON CONFLICT (team_id, voter_id) DO UPDATE SET points = :points") + void save(int teamId, Long voterId, Integer points); + + @Query("DELETE FROM gamejam.votes WHERE team_id = :teamId AND voter_id = :voterId") + void delete(int teamId, Long voterId); } diff --git a/backend/src/main/java/de/chojo/pluginjam/service/TeamService.java b/backend/src/main/java/de/chojo/pluginjam/service/TeamService.java index 275f820a..3b5aad61 100644 --- a/backend/src/main/java/de/chojo/pluginjam/service/TeamService.java +++ b/backend/src/main/java/de/chojo/pluginjam/service/TeamService.java @@ -6,11 +6,10 @@ import de.chojo.pluginjam.database.repository.TeamMemberRepository; import de.chojo.pluginjam.database.repository.TeamMetaRepository; import de.chojo.pluginjam.database.repository.TeamRepository; +import io.micronaut.transaction.annotation.Transactional; import jakarta.inject.Singleton; import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Member; -import net.dv8tion.jda.api.entities.Role; -import net.dv8tion.jda.api.entities.User; import java.util.List; import java.util.Optional; @@ -47,27 +46,39 @@ public void removeUserFromTeam(long userId) { public Optional getTeamByName(int jamId, String teamName) { return teamRepository.findByJamId(jamId).stream() - .filter(team -> team.meta() != null && teamName.equals(team.meta().teamName())) + .filter(team -> team.meta() != null && teamName.equals(team.meta().getTeamName())) .findFirst(); } public void joinTeam(Team team, Member user, Guild guild) { teamMemberRepository.save(new TeamMember(team.id(), user.getIdLong())); - guild.addRoleToMember(user, guild.getRoleById(team.meta().roleId())).queue(); + guild.addRoleToMember(user, guild.getRoleById(team.meta().getRoleId())).queue(); } public void leaveTeam(Team team, Member user, Guild guild) { teamMemberRepository.deleteByTeamIdAndUserId(team.id(), user.getIdLong()); - guild.removeRoleFromMember(user, guild.getRoleById(team.meta().roleId())).queue(); + guild.removeRoleFromMember(user, guild.getRoleById(team.meta().getRoleId())).queue(); } + public List getTeamsByJamId(int jamId) { + return teamRepository.findByJamId(jamId); + } + + @Transactional public void disbandTeam(Team team, Guild guild) { teamMemberRepository.deleteAllByTeamId(team.id()); teamRepository.deleteById(team.id()); teamMetaRepository.deleteById(team.id()); - guild.getRoleById(team.meta().roleId()).delete().queue(); - guild.getTextChannelById(team.meta().textChannelId()).delete().queue(); - guild.getVoiceChannelById(team.meta().voiceChannelId()).delete().queue(); + guild.getRoleById(team.meta().getRoleId()).delete().queue(); + guild.getTextChannelById(team.meta().getTextChannelId()).delete().queue(); + guild.getVoiceChannelById(team.meta().getVoiceChannelId()).delete().queue(); + } + + @Transactional + public void saveTeam(Team team) { + teamRepository.save(team); + teamMetaRepository.save(team.meta()); + teamMemberRepository.saveAll(team.members()); } } diff --git a/backend/src/main/java/de/chojo/pluginjam/service/VoteService.java b/backend/src/main/java/de/chojo/pluginjam/service/VoteService.java new file mode 100644 index 00000000..bd5a8d62 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/service/VoteService.java @@ -0,0 +1,42 @@ +package de.chojo.pluginjam.service; + +import de.chojo.pluginjam.database.entity.VotingRank; +import de.chojo.pluginjam.database.entity.team.Team; +import de.chojo.pluginjam.database.repository.VotesRankingRepository; +import de.chojo.pluginjam.database.repository.VotesRepository; +import jakarta.inject.Singleton; +import net.dv8tion.jda.api.entities.User; + +import java.util.List; +import java.util.Optional; + +@Singleton +public class VoteService { + private final VotesRepository votesRepository; + private final VotesRankingRepository votesRankingRepository; + + public VoteService(VotesRepository votesRepository, VotesRankingRepository votesRankingRepository) { + this.votesRepository = votesRepository; + this.votesRankingRepository = votesRankingRepository; + } + + public int getGivenPointsByUser(User user) { + return votesRepository.findByVoterId(user.getIdLong()); + } + + public int getPointsByTeam(Team team) { + return votesRepository.findByTeamId(team.id()); + } + + public void voteForTeam(User user, Team team, int points) { + votesRepository.save(team.id(), user.getIdLong(), points); + } + + public Optional getVotingRank(int teamId) { + return votesRankingRepository.findById(teamId); + } + + public List getVotingRanks(int jamId) { + return votesRankingRepository.findAllByJamId(jamId); + } +} diff --git a/backend/src/main/resources/locale/de.ftl b/backend/src/main/resources/locale/de.ftl index 4cc74eeb..b86bcc04 100644 --- a/backend/src/main/resources/locale/de.ftl +++ b/backend/src/main/resources/locale/de.ftl @@ -21,8 +21,8 @@ command-jamadmin-votes-open-description=Abtimmung für den aktuellen Game-Jam ö command-register-description=Du hast dich für einen kommenden Game-Jam angemeldet. command-register-message-alreadyregistered=Du bist bereits registriert. command-register-message-notanymore=Registrierungen sind geschlossen. -command-register-message-notyet=Du kannst dich noch nicht für diesen Game-Jam anmelden. Du kannst dich am %TIMESTAMP% anmelden. -command-register-message-registered=Du hast dich für den nächsten Game-Jam angemeldet. Er beginnt am %TIMESTAMP%. +command-register-message-notyet=Du kannst dich noch nicht für diesen Game-Jam anmelden. Du kannst dich am { $TIMESTAMP } anmelden. +command-register-message-registered=Du hast dich für den nächsten Game-Jam angemeldet. Er beginnt am { $TIMESTAMP }. command-server-configure-description=Server konfigurieren command-server-configure-maxplayers-options-amount-description=Maximale Anzahl von Spielern. command-server-configure-maxplayers-description=Die maximale Spieleranzahl für diesen Server festlegen @@ -50,7 +50,7 @@ command-server-download-plugindata-options-path-description=Pfad im Plugin-Verze command-server-plugins-description=Installierte Plugins verwalten command-server-plugins-install-description=Ein anderes Plugin installieren command-server-plugins-install-message-fail=Plugin konnte nicht installiert werden -command-server-plugins-install-message-success=Installiert %NAME%. Neustart, um Änderungen zu übernehmen +command-server-plugins-install-message-success=Installiert { $NAME }. Neustart, um Änderungen zu übernehmen command-server-plugins-install-options-plugin-description=Zu installierendes Plugin command-server-plugins-uninstall-options-deletedata-description=Richtig, um auch die Plugin-Daten zu löschen command-server-plugins-uninstall-description=Zu deinstallierendes Plugin @@ -111,30 +111,30 @@ command-serveradmin-info-detailed-options-team-description=team command-serveradmin-info-short-description=Kurze Informationen über Teamserver command-serveradmin-refresh-all-description=Alle Server aktualisieren command-serveradmin-refresh-description=Aktualisiere die Dateien der Vorlage auf allen Servern. -command-serveradmin-refresh-refreshall-message-refreshed=%AMOUNT% Server aktualisiert. -command-serveradmin-refresh-refreshteam-message-failed=Bei der Aktualisierung von %TEAM% ist ein Fehler aufgetreten. -command-serveradmin-refresh-refreshteam-message-refreshed=Der Server von Team %TEAM% wurde aktualisiert. +command-serveradmin-refresh-refreshall-message-refreshed={ $AMOUNT } Server aktualisiert. +command-serveradmin-refresh-refreshteam-message-failed=Bei der Aktualisierung von { $TEAM } ist ein Fehler aufgetreten. +command-serveradmin-refresh-refreshteam-message-refreshed=Der Server von Team { $TEAM } wurde aktualisiert. command-serveradmin-refresh-team-description=Aktualisiere einen Teamserver command-serveradmin-refresh-team-options-team-description=team command-serveradmin-restart-all-description=Alle Server starten command-serveradmin-restart-description=Server neu starten -command-serveradmin-restart-restartall-message-restarted=Neustart von %AMOUNT% Servern. -command-serveradmin-restart-restartteam-message-failed=Server von Team %TEAM% wurde nicht gestartet. -command-serveradmin-restart-restartteam-message-restarted=Der Server von Team %TEAM% wurde neu gestartet. +command-serveradmin-restart-restartall-message-restarted=Neustart von { $AMOUNT } Servern. +command-serveradmin-restart-restartteam-message-failed=Server von Team { $TEAM } wurde nicht gestartet. +command-serveradmin-restart-restartteam-message-restarted=Der Server von Team { $TEAM } wurde neu gestartet. command-serveradmin-restart-team-description=Start eines Team-Servers command-serveradmin-restart-team-options-team-description=team command-serveradmin-start-all-description=Alle Server starten command-serveradmin-start-description=Server starten -command-serveradmin-start-startall-message-started=Starte %AMOUNT% Server. -command-serveradmin-start-startteam-message-failed=Der Server von Team %TEAM% konnte nicht gestartet werden. -command-serveradmin-start-startteam-message-started=Server von Team %TEAM% wurde gestartet. +command-serveradmin-start-startall-message-started=Starte { $AMOUNT } Server. +command-serveradmin-start-startteam-message-failed=Der Server von Team { $TEAM } konnte nicht gestartet werden. +command-serveradmin-start-startteam-message-started=Server von Team { $TEAM } wurde gestartet. command-serveradmin-start-team-description=Starte einen Teamserver command-serveradmin-start-team-options-team-description=Team command-serveradmin-stop-all-description=Alle Server anhalten command-serveradmin-stop-description=Server anhalten -command-serveradmin-stop-stopall-message-stopped=%AMOUNT% Server gestoppt. -command-serveradmin-stop-stopteam-message-failed=Server von Team %TEAM% läuft nicht. -command-serveradmin-stop-stopteam-message-stopped=Server von Team %TEAM% wurde gestoppt. +command-serveradmin-stop-stopall-message-stopped={ $AMOUNT } Server gestoppt. +command-serveradmin-stop-stopteam-message-failed=Server von Team { $TEAM } läuft nicht. +command-serveradmin-stop-stopteam-message-stopped=Server von Team { $TEAM } wurde gestoppt. command-serveradmin-stop-team-description=Einen Teamserver stoppen command-serveradmin-stop-team-options-team-description=team command-serveradmin-syncvelocity-description=Serverstatus mit Velocity-Daten synchronisieren @@ -172,10 +172,10 @@ command-team-invite-alreadyMember=Du bist bereits Mitglied in einem Team. command-team-invite-description=Lade jemanden in dein Team ein command-team-invite-gameJamOver=Der Game-Jam ist vorbei command-team-invite-joined=Du bist dem Team beigetreten. -command-team-invite-joinedBroadcast=%USER% ist dem Team beigetreten. +command-team-invite-joinedBroadcast={ $USER } ist dem Team beigetreten. command-team-invite-message-accept=Akzeptieren -command-team-invite-message-invitation=%USER% hat dich eingeladen, dem Team %TEAM% beizutreten. -command-team-invite-message-invited=Du hast eine Einladung für den Game-Jam auf %GUILD% erhalten +command-team-invite-message-invitation={ $USER } hat dich eingeladen, dem Team { $TEAM } beizutreten. +command-team-invite-message-invited=Du hast eine Einladung für den Game-Jam auf { $GUILD } erhalten command-team-invite-message-noleader=Nur die/die Gruppenleiter:in kann einladen. command-team-invite-message-notRegistered=Dieser Benutzer ist nicht für den Game-Jam registriert. command-team-invite-message-partofteam=Dieser/diese Benutzer:in ist bereits Teil eines Teams. @@ -183,7 +183,7 @@ command-team-invite-message-send=Einladung senden. command-team-invite-options-user-description=Der/Die Benutzer:in, den/die du einladen möchtest command-team-leave-description=Verlasse dein Team command-team-leave-left=Du hast das Team verlassen. -command-team-leave-leftBroadcast=%USER% hat das Team verlassen. +command-team-leave-leftBroadcast={ $USER } hat das Team verlassen. command-team-leave-message-leaderleave=Der Anführer kann das Team nicht verlassen. command-team-list-description=Erhalte eine Liste alles existierenden Teams. command-team-profile-description=Zeigt das Profil eines Teams oder dein eigenes an @@ -211,8 +211,8 @@ command-votes-ranking-description=Das aktuelle Ranking command-votes-ranking-embed-votes=Punkte command-votes-ranking-message-voteactive=Abstimmung aktiv. Die Rangliste kann erst nach dem Beenden der Abstimmung angezeigt werden. command-votes-vote-description=Stimme für ein Team ab. -command-votes-vote-message-done=Du hast %TEAM% %POINTS% punkte gegeben. Verbleibende Punkte: %REMAINING% -command-votes-vote-message-maxpointsreached=Du hast das maximum an Punkten vergeben. Entferne Punkte von anderen teams wenn du weiter Punkte vergeben willst. Verbleibende Punkte: %REMAINING% +command-votes-vote-message-done=Du hast { $TEAM } { $POINTS } punkte gegeben. Verbleibende Punkte: { $REMAINING } +command-votes-vote-message-maxpointsreached=Du hast das maximum an Punkten vergeben. Entferne Punkte von anderen teams wenn du weiter Punkte vergeben willst. Verbleibende Punkte: { $REMAINING } command-votes-vote-message-notactive=Es ist keine Abstimmung aktiv. command-votes-vote-message-ownteam=Du kannst nicht für dein eigenes Team abstimmen. command-votes-vote-options-points-description=Die Punkte zum vergeben @@ -220,7 +220,7 @@ command-votes-vote-options-team-description=Das Tam für das du abstimmen möcht command-team-api-description=Informationen über die team api error-alreadyActive=Ein Game-Jam ist bereits aktiv. error-invalidpath=Ungültiger Pfad -error-invalidrimeformat=Ungültiges Zeitformat. Das Format ist %FORMAT% +error-invalidrimeformat=Ungültiges Zeitformat. Das Format ist { $FORMAT } error-invalidtimezone=Ungültige Zeitzone. error-maxteamsize=Das Team hat die maximale Größe erreicht. error-noactivejam=Es gibt keinen aktiven Game-Jam. @@ -257,6 +257,15 @@ word-upcoming=Bevorstehend word-ended=Beendet word-active=Aktiv word-jamlist=Jam-Liste +word-members=Mitglieder +word-description=Projekt Beschreibung +word-no-description=Keine Beschreibung +word-project-url=Projekt Url +word-no-url=Keine Url + +button-next=Weiter +button-prev=Zurück + words-dateformat=yyyy.MM.dd HH:mm words-format=Format diff --git a/backend/src/main/resources/locale/en.ftl b/backend/src/main/resources/locale/en.ftl index cc4f701a..95ddc04e 100644 --- a/backend/src/main/resources/locale/en.ftl +++ b/backend/src/main/resources/locale/en.ftl @@ -21,8 +21,8 @@ command-jamadmin-votes-open-description=Open votes for the current jam command-register-description=Register for an upcomming Game Jam command-register-message-alreadyregistered=You are already registered. command-register-message-notanymore=Registrations are closed. -command-register-message-notyet=You cant register for this game jam yet. You can register at %TIMESTAMP% -command-register-message-registered=You have registered yourself for the next game jam. It will start at %TIMESTAMP%. +command-register-message-notyet=You cant register for this game jam yet. You can register at { $TIMESTAMP } +command-register-message-registered=You have registered yourself for the next game jam. It will start at { $TIMESTAMP }. command-server-configure-description=Configure server command-server-configure-maxplayers-options-amount-description=Max amount of players. command-server-configure-maxplayers-description=Set the max players of this server @@ -50,7 +50,7 @@ command-server-download-plugindata-options-path-description=Path in the plugin d command-server-plugins-description=Manage installed plugins command-server-plugins-install-description=Install another plugin command-server-plugins-install-message-fail=Could not install plugin -command-server-plugins-install-message-success=Installed %NAME%. Restart to apply changes +command-server-plugins-install-message-success=Installed { $NAME }. Restart to apply changes command-server-plugins-install-options-plugin-description=Plugin to install command-server-plugins-uninstall-options-deletedata-description=True to delete the plugin data as well command-server-plugins-uninstall-description=Plugin to uninstall @@ -111,30 +111,30 @@ command-serveradmin-info-detailed-options-team-description=team command-serveradmin-info-short-description=Short information about team servers command-serveradmin-refresh-all-description=Refresh all server command-serveradmin-refresh-description=Refresh files of the template in all servers. -command-serveradmin-refresh-refreshall-message-refreshed=Refreshed %AMOUNT% servers. -command-serveradmin-refresh-refreshteam-message-failed=Failed during refresh of %TEAM%. -command-serveradmin-refresh-refreshteam-message-refreshed=Server of team %TEAM% refreshed. +command-serveradmin-refresh-refreshall-message-refreshed=Refreshed { $AMOUNT } servers. +command-serveradmin-refresh-refreshteam-message-failed=Failed during refresh of { $TEAM }. +command-serveradmin-refresh-refreshteam-message-refreshed=Server of team { $TEAM } refreshed. command-serveradmin-refresh-team-description=Refresh a team server command-serveradmin-refresh-team-options-team-description=team command-serveradmin-restart-all-description=Start all server command-serveradmin-restart-description=Restart servers -command-serveradmin-restart-restartall-message-restarted=Restarted %AMOUNT% servers. -command-serveradmin-restart-restartteam-message-failed=Server of team %TEAM% was not running. -command-serveradmin-restart-restartteam-message-restarted=Server of team %TEAM% restarted. +command-serveradmin-restart-restartall-message-restarted=Restarted { $AMOUNT } servers. +command-serveradmin-restart-restartteam-message-failed=Server of team { $TEAM } was not running. +command-serveradmin-restart-restartteam-message-restarted=Server of team { $TEAM } restarted. command-serveradmin-restart-team-description=Start a team server command-serveradmin-restart-team-options-team-description=team command-serveradmin-start-all-description=Start all server command-serveradmin-start-description=Start servers -command-serveradmin-start-startall-message-started=Started %AMOUNT% servers. -command-serveradmin-start-startteam-message-failed=Could not start server of team %TEAM%. -command-serveradmin-start-startteam-message-started=Server of team %TEAM% started. +command-serveradmin-start-startall-message-started=Started { $AMOUNT } servers. +command-serveradmin-start-startteam-message-failed=Could not start server of team { $TEAM }. +command-serveradmin-start-startteam-message-started=Server of team { $TEAM } started. command-serveradmin-start-team-description=Start a team server command-serveradmin-start-team-options-team-description=team command-serveradmin-stop-all-description=Stop all server command-serveradmin-stop-description=Stop servers -command-serveradmin-stop-stopall-message-stopped=Stopped %AMOUNT% servers. -command-serveradmin-stop-stopteam-message-failed=Server of team %TEAM% is not running. -command-serveradmin-stop-stopteam-message-stopped=Server of team %TEAM% stopped. +command-serveradmin-stop-stopall-message-stopped=Stopped { $AMOUNT } servers. +command-serveradmin-stop-stopteam-message-failed=Server of team { $TEAM } is not running. +command-serveradmin-stop-stopteam-message-stopped=Server of team { $TEAM } stopped. command-serveradmin-stop-team-description=Stop a team server command-serveradmin-stop-team-options-team-description=team command-serveradmin-syncvelocity-description=Sync servers with velocity data @@ -172,10 +172,10 @@ command-team-invite-alreadyMember=You are already member of a team. command-team-invite-description=Invite someone to your team command-team-invite-gameJamOver=The game jam is over command-team-invite-joined=You joined the team. -command-team-invite-joinedBroadcast=%USER% joined the team. +command-team-invite-joinedBroadcast={ $USER } joined the team. command-team-invite-message-accept=Accept -command-team-invite-message-invitation=%USER% invited you to join their team %TEAM%. -command-team-invite-message-invited=You received a invitation for the game jam on %GUILD% +command-team-invite-message-invitation={ $USER } invited you to join their team { $TEAM }. +command-team-invite-message-invited=You received a invitation for the game jam on { $GUILD } command-team-invite-message-noleader=Only the group leader can invite people. command-team-invite-message-notRegistered=This user is not registered for the game jam. command-team-invite-message-partofteam=This user is already part of a team. @@ -183,7 +183,7 @@ command-team-invite-message-send=Invitation send. command-team-invite-options-user-description=The user you want to invite command-team-leave-description=Leave your team command-team-leave-left=You left the team. -command-team-leave-leftBroadcast=%USER% left the team. +command-team-leave-leftBroadcast={ $USER } left the team. command-team-leave-message-leaderleave=The leader cant leave the team. command-team-list-description=Get a list of all existing teams. command-team-profile-description=Shows the profile of a team or your own @@ -211,8 +211,8 @@ command-votes-ranking-description=The current ranking command-votes-ranking-embed-votes=Votes command-votes-ranking-message-voteactive=A vote is active. Rankings can be accessed after the polls are closed. command-votes-vote-description=Vote for a team. -command-votes-vote-message-done=You gave %TEAM% %POINTS% points. Remaining points: %REMAINING% -command-votes-vote-message-maxpointsreached=You have reached the max amount of given points. Remove points from other teams if you want to add more. Remaining points: %REMAINING% +command-votes-vote-message-done=You gave { $TEAM } { $POINTS } points. Remaining points: { $REMAINING } +command-votes-vote-message-maxpointsreached=You have reached the max amount of given points. Remove points from other teams if you want to add more. Remaining points: { $REMAINING } command-votes-vote-message-notactive=No voting is active. command-votes-vote-message-ownteam=You cant vote for your own team. command-votes-vote-options-points-description=The points to give. @@ -220,7 +220,7 @@ command-votes-vote-options-team-description=The team you want to vote for. command-team-api-description=Information about the team api error-alreadyActive=A jam is already active. error-invalidpath=Invalid path -error-invalidrimeformat=Invalid time format. Format is %FORMAT% +error-invalidrimeformat=Invalid time format. Format is { $FORMAT } error-invalidtimezone=Invalid timezone. error-maxteamsize=The team has reached the max size. error-noactivejam=There is no active jam. @@ -257,6 +257,14 @@ word-upcoming=Upcoming word-ended=Ended word-active=Active word-jamlist=Jam list +word-members=Members +word-description=Project description +word-no-description=No description +word-project-url=Project url +word-no-url=No url + +button-next=Forward +button-prev=Back words-dateformat=yyyy.MM.dd HH:mm words-format=Format diff --git a/bot/src/main/java/de/chojo/gamejam/commands/team/TeamApiCommand.java b/bot/src/main/java/de/chojo/gamejam/commands/team/TeamApiCommand.java deleted file mode 100644 index ae1f3cfd..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/team/TeamApiCommand.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.team; - -import de.chojo.gamejam.configuration.Configuration; -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.localization.util.LocalizedEmbedBuilder; -import de.chojo.jdautil.wrapper.EventContext; -import io.github.kaktushose.jdac.annotations.i18n.Bundle; -import io.github.kaktushose.jdac.annotations.interactions.Interaction; -import net.dv8tion.jda.api.entities.MessageEmbed; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -public class TeamApiCommand { - /* - private final Configuration configuration; - private final Guilds guilds; - - public TeamApiCommand(Configuration configuration, Guilds guilds) { - this.configuration = configuration; - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var optJam = guilds.guild(event).jams().nextOrCurrent(); - if (optJam.isEmpty()) { - event.with().ephemeral(true).reply("error.nojamactive"); - return; - } - var optTeam = optJam.get().teams().byMember(event.getMember()); - if (optTeam.isEmpty()) { - event.with().ephemeral(true).reply("error.noteam"); - return; - } - - MessageEmbed build = new LocalizedEmbedBuilder(context.guildLocalizer()) - .setTitle("Api") - .addField("Token", "`%s`".formatted(optTeam.get().meta().token()), false) - .addField("Upload", "`POST %s/api/v1/server/plugin/%s`".formatted(configuration.api().url(), optTeam.get().meta().token()), false) - .build(); - - event.replyEmbeds(build).setEphemeral(true).queue(); - } - - */ -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/team/TeamCreateCommand.java b/bot/src/main/java/de/chojo/gamejam/commands/team/TeamCreateCommand.java deleted file mode 100644 index a110e94f..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/team/TeamCreateCommand.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.team; - - -import com.google.inject.Inject; -import de.chojo.gamejam.server.CommandContextProvider; -import de.chojo.gamejam.util.Token; -import io.github.kaktushose.jdac.annotations.i18n.Bundle; -import io.github.kaktushose.jdac.annotations.interactions.Command; -import io.github.kaktushose.jdac.annotations.interactions.Interaction; -import io.github.kaktushose.jdac.annotations.interactions.Param; -import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; -import net.dv8tion.jda.api.Permission; - -import java.util.Collections; -import java.util.EnumSet; - -@Bundle("locale") -@Interaction -public final class TeamCreateCommand { - private final CommandContextProvider commandContextProvider; - - @Inject - public TeamCreateCommand(CommandContextProvider commandContextProvider) { - this.commandContextProvider = commandContextProvider; - } - - @Command(value = "team create") - public void onCommand(CommandEvent event, @Param("name") String teamName) { - var jamGuild = commandContextProvider.guilds().guild(event.getGuild()); - var optJam = jamGuild.jams().nextOrCurrent(); - if (optJam.isEmpty()) { - event.with().ephemeral(true).reply("error.nojamactive"); - return; - } - var jam = optJam.get(); - - if (jam.state().isVoting()) { - event.with().ephemeral(true).reply("error.votingactive"); - return; - } - - if (!jam.registrations().contains(event.getMember().getIdLong())) { - event.with().ephemeral(true).reply("command.team.create.message.unregistered"); - return; - } - var jamUser = jam.user(event.getMember()); - - var userTeam = jamUser.team(); - if (userTeam.isPresent()) { - event.with().ephemeral(true).reply("command.team.create.message.alreadymember"); - return; - } - - // TODO: Enforce constrains of length and allowed chars - - var optTeam = jam.teams().byName(teamName); - - if (optTeam.isPresent()) { - event.with().ephemeral(true).reply("command.team.create.message.nametaken"); - return; - } - - event.deferReply(true); - - var categoryList = event.getGuild().getCategoriesByName("Team", true); - - var optCategory = categoryList.stream().filter(cat -> cat.getChannels().size() < 48).findFirst(); - // This is really hacky and I dont like it. - // All this stuff is blocking atm but in a different thread already - var category = optCategory.orElseGet(() -> event.getGuild().createCategory("Team").complete()); - - var role = event.getGuild() - .createRole() - .setPermissions(0L) - .setMentionable(false) - .setHoisted(false) - .setName(teamName) - .complete(); - - var text = event.getGuild().createTextChannel(teamName.replace(" ", "-"), category) - .addRolePermissionOverride(role.getIdLong(), EnumSet.of(Permission.VIEW_CHANNEL), Collections.emptySet()) - .addMemberPermissionOverride(event.getJDA().getSelfUser() - .getIdLong(), EnumSet.of(Permission.VIEW_CHANNEL, Permission.MANAGE_CHANNEL), Collections.emptySet()) - .addRolePermissionOverride(event.getGuild().getPublicRole() - .getIdLong(), Collections.emptySet(), EnumSet.of(Permission.VIEW_CHANNEL)) - .complete(); - - var voice = event.getGuild().createVoiceChannel(teamName, category) - .addRolePermissionOverride(role.getIdLong(), EnumSet.of(Permission.VIEW_CHANNEL), Collections.emptySet()) - .addMemberPermissionOverride(event.getJDA().getSelfUser() - .getIdLong(), EnumSet.of(Permission.VIEW_CHANNEL, Permission.MANAGE_CHANNEL), Collections.emptySet()) - .addRolePermissionOverride(event.getGuild().getPublicRole() - .getIdLong(), Collections.emptySet(), EnumSet.of(Permission.VIEW_CHANNEL)) - .complete(); - - var team = jam.teams() - .create(teamName); - var meta = team.meta(); - meta.leader(event.getMember()); - meta.textChannel(text); - meta.voiceChannel(voice); - meta.role(role); - meta.token(Token.generate(40)); - jamUser.join(team); - - event.with().ephemeral(true).reply("command.team.create.message.created"); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/team/TeamDisbandCommand.java b/bot/src/main/java/de/chojo/gamejam/commands/team/TeamDisbandCommand.java deleted file mode 100644 index 26d1586d..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/team/TeamDisbandCommand.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.team; - -import com.google.inject.Inject; -import de.chojo.gamejam.data.dao.guild.jams.Jam; -import de.chojo.gamejam.server.CommandContextProvider; -import io.github.kaktushose.jdac.annotations.i18n.Bundle; -import io.github.kaktushose.jdac.annotations.interactions.Command; -import io.github.kaktushose.jdac.annotations.interactions.Interaction; -import io.github.kaktushose.jdac.annotations.interactions.Param; -import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; - -import java.util.Optional; - -@Bundle("locale") -@Interaction -public final class TeamDisbandCommand { - private final CommandContextProvider commandContextProvider; - - @Inject - public TeamDisbandCommand(CommandContextProvider commandContextProvider) { - this.commandContextProvider = commandContextProvider; - } - - @Command(value = "team disband") - public void onCommand(CommandEvent event, @Param("confirm") boolean confirm) { - Optional optJam = commandContextProvider.guilds().guild(event).jams().nextOrCurrent(); - if (optJam.isEmpty()) { - event.with().ephemeral(true).reply("error.nojamactive"); - return; - } - var jam = optJam.get(); - - if (jam.state().isVoting()) { - event.with().ephemeral(true).reply("error.votingactive"); - return; - } - - if (!confirm) { - event.with().ephemeral(true).reply("error.noconfirm"); - return; - } - - var jamTeam = jam.teams().byMember(event.getMember()); - if (jamTeam.isEmpty()) { - event.with().ephemeral(true).reply("error.noteam"); - return; - } - - var team = jamTeam.get(); - - - var members = team.member(); - for (var teamMember : members) { - teamMember.member() - .getUser() - .openPrivateChannel() - //TODO: fix localization - .flatMap(channel -> channel.sendMessage("command.team.disband.message.disbanded")) - .queue(); - } - - if (team.disband()) { - event.with().ephemeral(true).reply("command.team.disband.message.disbanded"); - } - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/team/TeamEditCommand.java b/bot/src/main/java/de/chojo/gamejam/commands/team/TeamEditCommand.java index 3a759515..fc988b74 100644 --- a/bot/src/main/java/de/chojo/gamejam/commands/team/TeamEditCommand.java +++ b/bot/src/main/java/de/chojo/gamejam/commands/team/TeamEditCommand.java @@ -34,7 +34,7 @@ public TeamEditCommand(CommandContextProvider commandContextProvider) { public void onCommand(CommandEvent event) { var optJam = commandContextProvider.guilds().guild(event).jams().nextOrCurrent(); if (optJam.isEmpty()) { - event.with().ephemeral(true).reply("error.nojamactive"); + event.with().ephemeral(true).reply("error-nojamactive"); return; } var optTeam = optJam.get().teams().byMember(event.getMember()); diff --git a/bot/src/main/java/de/chojo/gamejam/commands/team/TeamInviteCommand.java b/bot/src/main/java/de/chojo/gamejam/commands/team/TeamInviteCommand.java deleted file mode 100644 index 277a11bc..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/team/TeamInviteCommand.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.team; - -import com.google.inject.Inject; -import de.chojo.gamejam.data.dao.JamGuild; -import de.chojo.gamejam.server.CommandContextProvider; -import io.github.kaktushose.jdac.annotations.i18n.Bundle; -import io.github.kaktushose.jdac.annotations.interactions.Command; -import io.github.kaktushose.jdac.annotations.interactions.Interaction; -import io.github.kaktushose.jdac.annotations.interactions.Param; -import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; -import net.dv8tion.jda.api.EmbedBuilder; -import net.dv8tion.jda.api.components.buttons.Button; -import net.dv8tion.jda.api.entities.User; -import net.dv8tion.jda.api.components.actionrow.ActionRow; -import net.dv8tion.jda.api.interactions.commands.OptionType; - -@Bundle("locale") -@Interaction -public final class TeamInviteCommand { - private final CommandContextProvider commandContextProvider; - - @Inject - public TeamInviteCommand(CommandContextProvider commandContextProvider) { - this.commandContextProvider = commandContextProvider; - } - - @Command(value = "team invite") - public void onCommand(CommandEvent event, @Param(value = "user", type = OptionType.USER) User user) { - JamGuild guild = commandContextProvider.guilds().guild(event); - var optJam = guild.jams().nextOrCurrent(); - if (optJam.isEmpty()) { - event.with().ephemeral(true).reply("error.nojamactive"); - return; - } - var jam = optJam.get(); - - if (jam.state().isVoting()) { - event.with().ephemeral(true).reply("error.votingactive"); - return; - } - - var optTeam = jam.teams().byMember(event.getMember()); - if (optTeam.isEmpty()) { - event.with().ephemeral(true).reply("error.noteam"); - return; - } - - var team = optTeam.get(); - - if (!team.isLeader(event.getUser())) { - event.with().ephemeral(true).reply("command.team.invite.message.noleader"); - return; - } - - var members = team.member(); - var settings = guild.jamSettings(); - - if (members.size() >= settings.teamSize()) { - event.with().ephemeral(true).reply("error.maxteamsize"); - return; - } - - if (!jam.registrations().contains(user.getIdLong())) { - event.with().ephemeral(true).reply("command.team.invite.message.notRegistered"); - return; - } - - var currTeam = jam.teams().byMember(user); - - if (currTeam.isPresent()) { - event.reply("command.team.invite.message.partofteam"); - return; - } - - var guildId = event.getGuild().getIdLong(); - var teamId = team.id(); - var buttonId = "invite-accept:" + guildId + ":" + teamId + ":" + user.getIdLong(); - - var embed = new EmbedBuilder() - .setTitle("You have been invited to join a team on " + event.getGuild().getName()) - .setDescription(event.getUser().getAsMention() + " has invited you to join team **" + team.meta().name() + "**") - .build(); - - user.openPrivateChannel().queue(channel -> - channel.sendMessageEmbeds(embed) - .setComponents(ActionRow.of(Button.success(buttonId, "Accept"))) - .queue() - ); - - event.with().ephemeral(true).reply("command.team.invite.message.send"); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/team/TeamLeaveCommand.java b/bot/src/main/java/de/chojo/gamejam/commands/team/TeamLeaveCommand.java deleted file mode 100644 index b7d0c673..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/team/TeamLeaveCommand.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.team; - -import com.google.inject.Inject; -import de.chojo.gamejam.server.CommandContextProvider; -import io.github.kaktushose.jdac.annotations.i18n.Bundle; -import io.github.kaktushose.jdac.annotations.interactions.Command; -import io.github.kaktushose.jdac.annotations.interactions.Interaction; -import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; - -@Bundle("locale") -@Interaction -public final class TeamLeaveCommand { - private final CommandContextProvider commandContextProvider; - - @Inject - public TeamLeaveCommand(CommandContextProvider commandContextProvider) { - this.commandContextProvider = commandContextProvider; - } - - @Command(value = "team leave") - public void onCommand(CommandEvent event) { - var jamGuild = commandContextProvider.guilds().guild(event); - var optJam = jamGuild.jams().nextOrCurrent(); - if (optJam.isEmpty()) { - event.with().ephemeral(true).reply("error.nojamactive"); - return; - } - var jam = optJam.get(); - - if (jam.state().isVoting()) { - event.with().ephemeral(true).reply("error.votingactive"); - return; - } - - jam.teams().byMember(event.getMember()).ifPresentOrElse(team -> { - if (!team.isLeader(event.getUser())) { - event.with().ephemeral(true).reply("command.team.leave.message.leaderleave"); - return; - } - team.member(event.getMember()).ifPresent(member -> { - member.leave(); - team.meta().textChannel().ifPresent(channel -> { - //TODO: reimplement message - }); - event.with().ephemeral(true).reply("command.team.leave.left"); - }); - }, () -> event.with().ephemeral(true).reply("error.noteam")); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/team/TeamListCommand.java b/bot/src/main/java/de/chojo/gamejam/commands/team/TeamListCommand.java deleted file mode 100644 index 97c75f79..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/team/TeamListCommand.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.team; - -import com.google.inject.Inject; -import de.chojo.gamejam.data.dao.JamGuild; -import de.chojo.gamejam.server.CommandContextProvider; -import de.chojo.jdautil.pagination.bag.PrivateListPageBag; -import io.github.kaktushose.jdac.annotations.i18n.Bundle; -import io.github.kaktushose.jdac.annotations.interactions.Button; -import io.github.kaktushose.jdac.annotations.interactions.Command; -import io.github.kaktushose.jdac.annotations.interactions.Interaction; -import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; -import io.github.kaktushose.jdac.dispatching.events.interactions.ComponentEvent; -import io.github.kaktushose.jdac.dispatching.reply.Component; -import io.github.kaktushose.jdac.embeds.Embed; -import net.dv8tion.jda.api.EmbedBuilder; -import net.dv8tion.jda.api.components.buttons.ButtonStyle; -import net.dv8tion.jda.api.entities.MessageEmbed; -import net.dv8tion.jda.api.utils.messages.MessageEditData; - -import java.util.List; - -@Bundle("locale") -@Interaction -public class TeamListCommand { - private final CommandContextProvider commandContextProvider; - - private List pages; - private int currentPage; - - @Inject - public TeamListCommand(CommandContextProvider commandContextProvider) { - this.commandContextProvider = commandContextProvider; - } - - @Command(value = "team list") - public void onCommand(CommandEvent event) { - JamGuild guild = commandContextProvider.guilds().guild(event); - var optJam = guild.jams().nextOrCurrent(); - if (optJam.isEmpty()) { - event.with().ephemeral(true).reply("error.nojamactive"); - return; - } - var jam = optJam.get(); - - pages = jam.teams().teams().stream().map(team -> team.profileEmbed(event)).toList(); - currentPage = 0; - event.with().components(Component.disabled("onPrev"), Component.enabled("onNext")) - .embeds(pages.getFirst()).reply(); - } - - //TODO: localization - @Button(value = "◀ Zurück", style = ButtonStyle.SECONDARY) - public void onPrev(ComponentEvent event) { - currentPage--; - event.with() - .components( - currentPage == 0 ? Component.disabled("onPrev") : Component.enabled("onPrev"), - Component.enabled("onNext") - ) - .embeds(pages.get(currentPage)).reply(); - } - - //TODO: localization - @Button(value = "Weiter ▶", style = ButtonStyle.SECONDARY) - public void onNext(ComponentEvent event) { - currentPage++; - event.with() - .components( - Component.enabled("onPrev"), - currentPage == pages.size() - 1 ? Component.disabled("onNext") : Component.enabled("onNext") - ) - .embeds(pages.get(currentPage)).reply(); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/team/TeamProfileCommand.java b/bot/src/main/java/de/chojo/gamejam/commands/team/TeamProfileCommand.java deleted file mode 100644 index 5c236487..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/team/TeamProfileCommand.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.team; - -import com.google.inject.Inject; -import de.chojo.gamejam.data.dao.guild.jams.jam.teams.Team; -import de.chojo.gamejam.server.CommandContextProvider; -import de.chojo.jdautil.wrapper.EventContext; -import io.github.kaktushose.jdac.annotations.i18n.Bundle; -import io.github.kaktushose.jdac.annotations.interactions.AutoComplete; -import io.github.kaktushose.jdac.annotations.interactions.Command; -import io.github.kaktushose.jdac.annotations.interactions.Interaction; -import io.github.kaktushose.jdac.annotations.interactions.Param; -import io.github.kaktushose.jdac.dispatching.events.interactions.AutoCompleteEvent; -import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; -import net.dv8tion.jda.api.entities.User; -import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import net.dv8tion.jda.api.interactions.commands.OptionType; - -import java.util.Collections; - -@Bundle("locale") -@Interaction -public final class TeamProfileCommand { - private final CommandContextProvider commandContextProvider; - - @Inject - public TeamProfileCommand(CommandContextProvider commandContextProvider) { - this.commandContextProvider = commandContextProvider; - } - - @Command(value = "team profile") - public void onCommand( - CommandEvent event, - @Param(name = "user", optional = true, type = OptionType.USER) User user, - @Param(value = "team", optional = true) String teamName - ) { - var optJam = commandContextProvider.guilds().guild(event).jams().nextOrCurrent(); - if (optJam.isEmpty()) { - event.with().ephemeral(true).reply("error.nojamactive"); - return; - } - var jam = optJam.get(); - var teams = jam.teams(); - - if (user != null) { - teams.byMember(user).ifPresentOrElse(team -> - sendProfile(event, team), - () -> event.with().ephemeral(true).reply("command.team.profile.message.nouserteam")); - return; - } - if (teamName != null) { - teams.byName(teamName).ifPresentOrElse(team -> - sendProfile(event, team), - () -> event.with().ephemeral(true).reply("error.unkownteam")); - return; - } - - teams.byMember(event.getMember()).ifPresentOrElse(team -> - sendProfile(event, team), - () -> event.with().ephemeral(true).reply("error.noteam")); - } - - private void sendProfile(CommandEvent event, Team team) { - event.with().ephemeral(true).embeds(team.profileEmbed(event)).reply(); - } - - @AutoComplete(value = "team profile", options = "team") - public void onAutoComplete(AutoCompleteEvent event) { - var guild = commandContextProvider.guilds().guild(event); - var choices = guild.jams().nextOrCurrent() - .map(jam -> jam.teams().completeTeam(event.getValue())) - .orElse(Collections.emptyList()); - event.replyChoices(choices); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/team/TeamRenameCommand.java b/bot/src/main/java/de/chojo/gamejam/commands/team/TeamRenameCommand.java deleted file mode 100644 index 103ade90..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/team/TeamRenameCommand.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.team; - -import com.google.inject.Inject; -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.data.dao.JamGuild; -import de.chojo.gamejam.server.CommandContextProvider; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.wrapper.EventContext; -import io.github.kaktushose.jdac.annotations.i18n.Bundle; -import io.github.kaktushose.jdac.annotations.interactions.Command; -import io.github.kaktushose.jdac.annotations.interactions.Interaction; -import io.github.kaktushose.jdac.annotations.interactions.Param; -import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -@Bundle("locale") -@Interaction -public class TeamRenameCommand { - private final CommandContextProvider commandContextProvider; - - @Inject - public TeamRenameCommand(CommandContextProvider commandContextProvider) { - this.commandContextProvider = commandContextProvider; - } - - @Command(value = "team rename") - public void onCommand(CommandEvent event, @Param("name") String teamName) { - JamGuild guild = commandContextProvider.guilds().guild(event); - var optJam = guild.jams().nextOrCurrent(); - if (optJam.isEmpty()) { - event.with().ephemeral(true).reply("error.nojamactive"); - return; - } - var jam = optJam.get(); - - jam.teams().byName(teamName).ifPresentOrElse( - team -> event.with().ephemeral(true).reply("command.team.create.message.nametaken"), - () -> { - var optCurrTeam = jam.teams().byMember(event.getUser()); - - if (optCurrTeam.isEmpty()) { - event.with().ephemeral(true).reply("error.noteam"); - return; - } - - var team = optCurrTeam.get(); - - if (!team.isLeader(event.getUser())) { - event.with().ephemeral(true).reply("error.noleader"); - return; - } - - team.meta().rename(teamName); - event.with().ephemeral(true).reply("command.team.rename.message.done"); - }); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/vote/VotesRankingCommand.java b/bot/src/main/java/de/chojo/gamejam/commands/vote/VotesRankingCommand.java deleted file mode 100644 index 55d70774..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/vote/VotesRankingCommand.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.vote; - -import com.google.inject.Inject; -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.server.CommandContextProvider; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.localization.util.LocalizedEmbedBuilder; -import de.chojo.jdautil.pagination.bag.ListPageBag; -import de.chojo.jdautil.wrapper.EventContext; -import io.github.kaktushose.jdac.annotations.i18n.Bundle; -import io.github.kaktushose.jdac.annotations.interactions.Button; -import io.github.kaktushose.jdac.annotations.interactions.Command; -import io.github.kaktushose.jdac.annotations.interactions.Interaction; -import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; -import io.github.kaktushose.jdac.dispatching.events.interactions.ComponentEvent; -import io.github.kaktushose.jdac.dispatching.reply.Component; -import io.github.kaktushose.jdac.embeds.Embed; -import net.dv8tion.jda.api.components.buttons.ButtonStyle; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import net.dv8tion.jda.api.utils.messages.MessageEditData; - -import java.util.List; - -@Bundle("locale") -@Interaction -public class VotesRankingCommand { - private final CommandContextProvider commandContextProvider; - - private List pages; - private int currentPage; - - @Inject - public VotesRankingCommand(CommandContextProvider commandContextProvider) { - this.commandContextProvider = commandContextProvider; - } - - @Command(value = "votes ranking") - public void onCommand(CommandEvent event) { - var guild = commandContextProvider.guilds().guild(event); - var optJam = guild.jams().nextOrCurrent(); - if (optJam.isEmpty()) { - event.with().ephemeral(true).reply("error.nojamactive"); - return; - } - - var jam = optJam.get(); - - if (jam.state().isVoting()) { - event.with().ephemeral(true).reply("command.votes.ranking.message.voteactive"); - return; - } - - var ranking = jam.votes(); - - pages = jam.votes().stream().map(team -> { - var embed = event.embed("votes-ranking"); - embed.title(team.rank() + " | " + team.team().meta().name()); - embed.fields().add("command.votes.ranking.embed.votes", String.valueOf(team.votes()), true); - return embed; - }).toList(); - currentPage = 0; - event.with().components(Component.disabled("onPrev"), Component.enabled("onNext")) - .embeds(pages.getFirst()).reply(); - } - - //TODO: localization - @Button(value = "◀ Zurück", style = ButtonStyle.SECONDARY) - public void onPrev(ComponentEvent event) { - currentPage--; - event.with() - .components( - currentPage == 0 ? Component.disabled("onPrev") : Component.enabled("onPrev"), - Component.enabled("onNext") - ) - .embeds(pages.get(currentPage)).reply(); - } - - //TODO: localization - @Button(value = "Weiter ▶", style = ButtonStyle.SECONDARY) - public void onNext(ComponentEvent event) { - currentPage++; - event.with() - .components( - Component.enabled("onPrev"), - currentPage == pages.size() - 1 ? Component.disabled("onNext") : Component.enabled("onNext") - ) - .embeds(pages.get(currentPage)).reply(); - } -}