interaction) {
+ super(channel);
+ this.supplier = supplier;
+ this.interaction = interaction;
+
+ if(getChannel().hasLatestMessage())
+ message = getChannel().getIterableHistory().complete().stream().filter(m -> m.getAuthor().isBot()).findFirst().orElse(null);
+
+ update();
+ }
+
+ public void update() {
+ if (message == null) {
+ getChannel().sendMessage(supplier.get().build()).queue(m -> message = m);
+ } else {
+ message.editMessage(supplier.get().build()).queue();
+ }
+ }
+
+ @Override
+ public void received(GenericComponentInteractionCreateEvent event) {
+ interaction.accept(event);
+ }
+}
diff --git a/VelocityCore/src/de/steamwar/velocitycore/discord/listeners/ChannelListener.java b/VelocityCore/src/de/steamwar/velocitycore/discord/listeners/ChannelListener.java
new file mode 100644
index 00000000..7665c40f
--- /dev/null
+++ b/VelocityCore/src/de/steamwar/velocitycore/discord/listeners/ChannelListener.java
@@ -0,0 +1,91 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2021 SteamWar.de-Serverteam
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package de.steamwar.velocitycore.discord.listeners;
+
+import de.steamwar.command.SWCommand;
+import de.steamwar.sql.UserPerm;
+import de.steamwar.velocitycore.VelocityCore;
+import de.steamwar.velocitycore.discord.DiscordBot;
+import de.steamwar.velocitycore.discord.channels.DiscordChannel;
+import de.steamwar.velocitycore.discord.channels.InteractionReply;
+import lombok.Getter;
+import net.dv8tion.jda.api.entities.ChannelType;
+import net.dv8tion.jda.api.entities.MessageChannel;
+import net.dv8tion.jda.api.events.interaction.GenericComponentInteractionCreateEvent;
+import net.dv8tion.jda.api.events.interaction.SlashCommandEvent;
+import net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent;
+import net.dv8tion.jda.api.hooks.ListenerAdapter;
+import net.dv8tion.jda.api.interactions.InteractionType;
+import net.dv8tion.jda.api.interactions.commands.OptionMapping;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Level;
+
+public class ChannelListener extends ListenerAdapter {
+ @Getter
+ private static final Map channels = new HashMap<>();
+
+ @Override
+ public void onGuildMessageReceived(@NotNull GuildMessageReceivedEvent event) {
+ if(event.getAuthor().isBot())
+ return;
+
+ DiscordChannel channel = channels.get(event.getChannel());
+ if(channel != null)
+ channel.received(event);
+ }
+
+ @Override
+ public void onGenericComponentInteractionCreate(@NotNull GenericComponentInteractionCreateEvent event) {
+ if(event.getType() != InteractionType.COMPONENT)
+ return;
+
+ DiscordChannel channel = channels.get(event.getChannel());
+ if(channel != null) {
+ channel.received(event);
+ return;
+ }
+
+ if(event.getChannelType() == ChannelType.PRIVATE && event.getComponentId().equals("tada"))
+ event.reply(":tada:").queue();
+ }
+
+ @Override
+ public void onSlashCommand(@NotNull SlashCommandEvent event) {
+ InteractionReply.reply(event, sender -> {
+ if(sender.user().getDiscordId() == null)
+ return;
+
+ OptionMapping option = event.getOption(DiscordBot.ARGUMENT_NAME);
+ String args = "";
+ if(option != null)
+ args = option.getAsString();
+
+ VelocityCore.getLogger().log(Level.INFO, "%s -> executed Discord command /%s %s".formatted(sender.user().getUserName(), event.getName(), args));
+ SWCommand command = DiscordBot.getCommands().get(event.getName());
+ UserPerm permission = command.getPermission();
+ if(permission != null && !sender.user().perms().contains(permission))
+ return;
+
+ command.execute(sender, args.split(" "));
+ });
+ }
+}
diff --git a/VelocityCore/src/de/steamwar/velocitycore/discord/listeners/DiscordSchemUpload.java b/VelocityCore/src/de/steamwar/velocitycore/discord/listeners/DiscordSchemUpload.java
new file mode 100644
index 00000000..e8969a7c
--- /dev/null
+++ b/VelocityCore/src/de/steamwar/velocitycore/discord/listeners/DiscordSchemUpload.java
@@ -0,0 +1,90 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2024 SteamWar.de-Serverteam
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package de.steamwar.velocitycore.discord.listeners;
+
+import de.steamwar.velocitycore.VelocityCore;
+import de.steamwar.velocitycore.discord.channels.DiscordChannel;
+import de.steamwar.sql.NodeData;
+import de.steamwar.sql.Punishment;
+import de.steamwar.sql.SchematicNode;
+import de.steamwar.sql.SteamwarUser;
+import net.dv8tion.jda.api.entities.Message;
+import net.dv8tion.jda.api.events.message.priv.PrivateMessageReceivedEvent;
+import net.dv8tion.jda.api.hooks.ListenerAdapter;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.logging.Level;
+
+public class DiscordSchemUpload extends ListenerAdapter {
+
+ private static final List SCHEM_FILE_ENDINGS = Arrays.asList(".schem", ".schematic");
+
+ @Override
+ public void onPrivateMessageReceived(PrivateMessageReceivedEvent event) {
+ Message message = event.getMessage();
+ if(message.getAttachments().isEmpty())
+ return;
+
+ DiscordChannel sender = new DiscordChannel(event.getAuthor());
+ SteamwarUser user = sender.user();
+ if(user.getId() == 0) {
+ sender.system("DC_UNLINKED");
+ return;
+ }
+
+ if(user.isPunished(Punishment.PunishmentType.NoSchemReceiving)) {
+ sender.system("DC_SCHEMUPLOAD_NOPERM");
+ return;
+ }
+
+ for (Message.Attachment attachment : message.getAttachments()) {
+ String fileName = attachment.getFileName();
+ int dot = fileName.lastIndexOf('.');
+ if(dot == -1 || !SCHEM_FILE_ENDINGS.contains(fileName.substring(dot).toLowerCase())) {
+ sender.system("DC_SCHEMUPLOAD_IGNORED", attachment.getFileName());
+ continue;
+ }
+
+ String name = fileName.substring(0, dot);
+ if(SchematicNode.invalidSchemName(new String[] {name})) {
+ sender.system("DC_SCHEMUPLOAD_INVCHAR", name);
+ continue;
+ }
+
+ SchematicNode node = SchematicNode.getSchematicNode(user.getId(), name, (Integer) null);
+ if(node == null)
+ node = SchematicNode.createSchematic(user.getId(), name, null);
+
+ try (InputStream in = attachment.retrieveInputStream().get()) {
+ NodeData.get(node).saveFromStream(in, fileName.substring(dot).equalsIgnoreCase(".schem"));
+ sender.system("DC_SCHEMUPLOAD_SUCCESS", name);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ } catch (ExecutionException | IOException e) {
+ VelocityCore.getLogger().log(Level.SEVERE, "Could not upload schem \"%s\" for user \"%s\"".formatted(name, user.getUserName()), e);
+ sender.system("DC_SCHEMUPLOAD_ERROR", name);
+ }
+ }
+ }
+}
diff --git a/VelocityCore/src/de/steamwar/velocitycore/discord/listeners/DiscordTeamEvent.java b/VelocityCore/src/de/steamwar/velocitycore/discord/listeners/DiscordTeamEvent.java
new file mode 100644
index 00000000..d18de226
--- /dev/null
+++ b/VelocityCore/src/de/steamwar/velocitycore/discord/listeners/DiscordTeamEvent.java
@@ -0,0 +1,67 @@
+/*
+ This file is a part of the SteamWar software.
+
+ Copyright (C) 2020 SteamWar.de-Serverteam
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+ */
+
+package de.steamwar.velocitycore.discord.listeners;
+
+import de.steamwar.velocitycore.VelocityCore;
+import de.steamwar.velocitycore.discord.DiscordBot;
+import de.steamwar.velocitycore.discord.channels.InteractionReply;
+import de.steamwar.sql.Event;
+import net.dv8tion.jda.api.events.interaction.SelectionMenuEvent;
+import net.dv8tion.jda.api.hooks.ListenerAdapter;
+import org.jetbrains.annotations.NotNull;
+
+public class DiscordTeamEvent extends ListenerAdapter {
+
+ private final String eventsChannel = DiscordBot.getInstance().getConfig().channel("events");
+
+ @Override
+ public void onSelectionMenu(@NotNull SelectionMenuEvent event) {
+ if(!event.getChannel().getId().equals(eventsChannel))
+ return;
+
+ if(event.getSelectedOptions().isEmpty()) {
+ event.deferReply(true).queue();
+ return;
+ }
+
+ InteractionReply.reply(event, reply -> {
+ int eventId;
+ try {
+ eventId = Integer.parseInt(event.getSelectedOptions().get(0).getValue());
+ } catch (NumberFormatException e) {
+ reply.system("UNKNOWN_EVENT");
+ return;
+ }
+
+ if(reply.user().getId() == 0) {
+ reply.system("DC_UNLINKED");
+ return;
+ }
+
+ Event tournament = Event.get(eventId);
+ if(tournament == null){
+ reply.system("UNKNOWN_EVENT");
+ return;
+ }
+
+ VelocityCore.get().getTeamCommand().event(reply, tournament);
+ });
+ }
+}
diff --git a/VelocityCore/src/de/steamwar/velocitycore/discord/listeners/DiscordTicketHandler.java b/VelocityCore/src/de/steamwar/velocitycore/discord/listeners/DiscordTicketHandler.java
new file mode 100644
index 00000000..692d475a
--- /dev/null
+++ b/VelocityCore/src/de/steamwar/velocitycore/discord/listeners/DiscordTicketHandler.java
@@ -0,0 +1,165 @@
+/*
+ This file is a part of the SteamWar software.
+
+ Copyright (C) 2020 SteamWar.de-Serverteam
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+ */
+
+package de.steamwar.velocitycore.discord.listeners;
+
+import de.steamwar.messages.Chatter;
+import de.steamwar.messages.ChatterGroup;
+import de.steamwar.messages.Message;
+import de.steamwar.sql.SteamwarUser;
+import de.steamwar.sql.UserPerm;
+import de.steamwar.velocitycore.discord.DiscordBot;
+import de.steamwar.velocitycore.discord.DiscordTicketType;
+import de.steamwar.velocitycore.discord.channels.DiscordChannel;
+import de.steamwar.velocitycore.discord.channels.InteractionReply;
+import net.dv8tion.jda.api.EmbedBuilder;
+import net.dv8tion.jda.api.MessageBuilder;
+import net.dv8tion.jda.api.Permission;
+import net.dv8tion.jda.api.entities.Emoji;
+import net.dv8tion.jda.api.entities.MessageChannel;
+import net.dv8tion.jda.api.entities.TextChannel;
+import net.dv8tion.jda.api.entities.User;
+import net.dv8tion.jda.api.events.interaction.GenericComponentInteractionCreateEvent;
+import net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent;
+import net.dv8tion.jda.api.hooks.ListenerAdapter;
+import net.dv8tion.jda.api.interactions.components.ActionRow;
+import net.dv8tion.jda.api.interactions.components.Button;
+import net.kyori.adventure.text.event.ClickEvent;
+import org.jetbrains.annotations.NotNull;
+
+import java.awt.*;
+import java.time.Instant;
+import java.util.LinkedList;
+import java.util.stream.Collectors;
+
+public class DiscordTicketHandler extends ListenerAdapter {
+
+ private static final String TICKET_CATEGORY = DiscordBot.getInstance().getConfig().getTicketcategory();
+ private static final String TICKET_LOG = DiscordBot.getInstance().getConfig().channel("ticketlog");
+ private static final String TICKET_CHANNEL = DiscordBot.getInstance().getConfig().channel("ticket");
+
+ public static void openTicket(GenericComponentInteractionCreateEvent event) {
+ DiscordTicketType ticketType = DiscordTicketType.valueOf(event.getComponentId().toUpperCase());
+ SteamwarUser user = SteamwarUser.get(event.getUser().getIdLong());
+
+ TextChannel ticketChannel = event.getGuild().getCategoryById(TICKET_CATEGORY).createTextChannel((user == null ? event.getUser().getName() : user.getUserName()) + "-" + event.getComponentId() + "-" + System.currentTimeMillis() % 1000).complete();
+ ticketChannel.createPermissionOverride(event.getMember()).setAllow(
+ Permission.VIEW_CHANNEL,
+ Permission.MESSAGE_WRITE,
+ Permission.MESSAGE_ATTACH_FILES,
+ Permission.MESSAGE_ADD_REACTION,
+ Permission.MESSAGE_READ,
+ Permission.MESSAGE_EMBED_LINKS,
+ Permission.MESSAGE_HISTORY).complete();
+ ticketChannel.getManager().setTopic(event.getUser().getId()).complete();
+
+ DiscordChannel channel = new DiscordChannel(DiscordChannel.userOrPublic(event.getUser()), ticketChannel);
+ channel.send(new MessageBuilder()
+ .setEmbeds(new EmbedBuilder()
+ .setTitle(channel.parseToPlain("DC_TICKET_TITLE"))
+ .setDescription(channel.parseToPlain(ticketType.introduction()))
+ .setColor(Color.GREEN)
+ .build())
+ .setActionRows(ActionRow.of(Button.danger("close-" + ticketChannel.getName(), channel.parseToPlain("DC_TICKET_CLOSE")).withEmoji(Emoji.fromUnicode("U+26A0")))));
+
+ InteractionReply.reply(event, reply -> reply.system("DC_TICKET_CREATED", ticketChannel.getAsMention()));
+ Chatter.serverteam().prefixless("DISCORD_TICKET_NEW", ticketChannel.getName());
+ }
+
+ @Override
+ public void onGenericComponentInteractionCreate(@NotNull GenericComponentInteractionCreateEvent event) {
+ MessageChannel messageChannel = event.getChannel();
+ if(messageChannel instanceof TextChannel channel && channel.getParent() != null && channel.getParent().getId().equals(TICKET_CATEGORY) && event.getComponentId().startsWith("close-")) {
+ LinkedList messages = channel.getIterableHistory().complete().stream()
+ .filter(message -> !message.getAuthor().isSystem() && !message.getAuthor().isBot())
+ .map(message -> {
+ StringBuilder stringBuilder = new StringBuilder()
+ .append(" ")
+ .append("**").append(message.getAuthor().getName()).append("**: ")
+ .append(message.getContentRaw())
+ .append("\n");
+
+ if(!message.getAttachments().isEmpty()) {
+ message.getAttachments().forEach(attachment -> stringBuilder.append(attachment.getUrl()));
+ stringBuilder.append("\n");
+ }
+
+ return stringBuilder;
+ })
+ .collect(Collectors.toCollection(LinkedList::new));
+
+ messages.addFirst(new StringBuilder().append(" **").append(event.getUser().getName()).append("**: Ticket closed"));
+
+ LinkedList messageBuilders = new LinkedList<>();
+ messageBuilders.add(new StringBuilder());
+ messages.descendingIterator()
+ .forEachRemaining(stringBuilder -> {
+ if(stringBuilder.length() >= 4096) {
+ messageBuilders.getLast().append(stringBuilder.substring(0, 4090));
+ messageBuilders.add(new StringBuilder(stringBuilder.substring(4090, stringBuilder.length() - 1)));
+ } else if (stringBuilder.length() + messageBuilders.getLast().length() >= 4096) {
+ messageBuilders.add(new StringBuilder(stringBuilder.toString()));
+ } else {
+ messageBuilders.getLast().append(stringBuilder);
+ }
+ });
+
+ EmbedBuilder embedBuilder = new EmbedBuilder()
+ .setColor(Color.GREEN)
+ .setTimestamp(Instant.now())
+ .setTitle(event.getTextChannel().getName());
+
+ if(channel.getTopic() != null && !channel.getTopic().isEmpty()) {
+ User user = event.getJDA().retrieveUserById(channel.getTopic()).complete();
+ embedBuilder.setAuthor(user.getName(), null, user.getAvatarUrl());
+ }
+
+ TextChannel logChannel = event.getGuild().getTextChannelById(TICKET_LOG);
+ messageBuilders.forEach(stringBuilder -> logChannel.sendMessage(new MessageBuilder().setEmbeds(embedBuilder.setDescription(stringBuilder.toString()).build()).build()).queue());
+
+ Chatter.serverteam().prefixless("DISCORD_TICKET_CLOSED", channel.getName());
+ channel.delete().reason("Closed").queue();
+ }
+ }
+
+ @Override
+ public void onGuildMessageReceived(@NotNull GuildMessageReceivedEvent event) {
+ TextChannel channel = event.getChannel();
+ if(
+ channel.getParent() != null &&
+ channel.getParent().getId().equals(TICKET_CATEGORY) &&
+ !channel.getId().equals(TICKET_CHANNEL) &&
+ !channel.getId().equals(TICKET_LOG)
+ ) {
+ if(event.getAuthor().isBot())
+ return;
+
+ ChatterGroup receivers = new ChatterGroup(Chatter.allStream().filter(player -> player.user().hasPerm(UserPerm.TICKET_LOG)));
+ try {
+ SteamwarUser user = SteamwarUser.get(Long.parseLong(channel.getTopic()));
+ if(user != null && !user.perms().contains(UserPerm.TEAM))
+ receivers = new ChatterGroup(receivers, Chatter.of(user));
+ } catch(NumberFormatException e) {
+ //ignored
+ }
+
+ receivers.prefixless("DISCORD_TICKET_MESSAGE", new Message("DISCORD_TICKET_HOVER"), ClickEvent.openUrl(event.getMessage().getJumpUrl()), event.getChannel().getName(), event.getAuthor().getName(), event.getMessage().getContentRaw());
+ }
+ }
+}
diff --git a/VelocityCore/src/de/steamwar/velocitycore/discord/util/AuthManager.java b/VelocityCore/src/de/steamwar/velocitycore/discord/util/AuthManager.java
new file mode 100644
index 00000000..972f77f1
--- /dev/null
+++ b/VelocityCore/src/de/steamwar/velocitycore/discord/util/AuthManager.java
@@ -0,0 +1,72 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2024 SteamWar.de-Serverteam
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package de.steamwar.velocitycore.discord.util;
+
+import de.steamwar.velocitycore.VelocityCore;
+import de.steamwar.velocitycore.discord.channels.DiscordChannel;
+import de.steamwar.sql.SteamwarUser;
+import lombok.experimental.UtilityClass;
+import net.dv8tion.jda.api.MessageBuilder;
+import net.dv8tion.jda.api.entities.Emoji;
+import net.dv8tion.jda.api.entities.User;
+import net.dv8tion.jda.api.interactions.components.ActionRow;
+import net.dv8tion.jda.api.interactions.components.Button;
+
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+
+@UtilityClass
+public class AuthManager {
+
+ private final Map TOKENS = new HashMap<>();
+ private final Random rand = new Random();
+
+ public String createDiscordAuthToken(User user) {
+ TOKENS.values().removeIf(user::equals);
+
+ byte[] randBytes = new byte[16];
+ rand.nextBytes(randBytes);
+ String code = Base64.getEncoder().encodeToString(randBytes);
+
+ TOKENS.put(code, user);
+ VelocityCore.getLogger().log(Level.INFO, "Created Discord Auth-Token: %s for: %s".formatted(code, user.getAsTag()));
+ VelocityCore.schedule(() -> TOKENS.remove(code)).delay(10, TimeUnit.MINUTES).schedule();
+ return code;
+ }
+
+ public User connectAuth(SteamwarUser user, String code) {
+ User dcUser = TOKENS.remove(code);
+ if(dcUser == null)
+ return null;
+
+ user.setDiscordId(dcUser.getIdLong());
+
+ DiscordChannel channel = new DiscordChannel(dcUser);
+ channel.send(new MessageBuilder()
+ .setContent(channel.parseToPlain("DC_AUTH_SUCCESS", user))
+ .setActionRows(ActionRow.of(Button.success("tada", Emoji.fromUnicode("U+1F389")))));
+
+ return dcUser;
+ }
+}
diff --git a/VelocityCore/src/de/steamwar/velocitycore/discord/util/DiscordAlert.java b/VelocityCore/src/de/steamwar/velocitycore/discord/util/DiscordAlert.java
new file mode 100644
index 00000000..0b115d41
--- /dev/null
+++ b/VelocityCore/src/de/steamwar/velocitycore/discord/util/DiscordAlert.java
@@ -0,0 +1,64 @@
+/*
+ This file is a part of the SteamWar software.
+
+ Copyright (C) 2020 SteamWar.de-Serverteam
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+ */
+
+package de.steamwar.velocitycore.discord.util;
+
+import de.steamwar.messages.Chatter;
+import de.steamwar.messages.Message;
+import de.steamwar.velocitycore.discord.DiscordBot;
+import de.steamwar.velocitycore.discord.channels.DiscordChannel;
+import lombok.experimental.UtilityClass;
+import net.dv8tion.jda.api.EmbedBuilder;
+import net.dv8tion.jda.api.MessageBuilder;
+import net.dv8tion.jda.api.entities.Emoji;
+import net.dv8tion.jda.api.entities.User;
+import net.dv8tion.jda.api.interactions.components.ActionRow;
+import net.dv8tion.jda.api.interactions.components.Button;
+
+import java.awt.*;
+import java.time.Instant;
+
+@UtilityClass
+public class DiscordAlert {
+
+ public static void send(Chatter player, Color color, Message title, Message description, boolean success) {
+ DiscordBot.withBot(bot -> {
+ Long discordId = player.user().getDiscordId();
+ if(discordId == null)
+ return;
+
+ User user = DiscordBot.getInstance().getJda().getUserById(discordId);
+ if(user == null)
+ return;
+
+ MessageBuilder builder = new MessageBuilder()
+ .setEmbeds(new EmbedBuilder()
+ .setAuthor("SteamWar", "https://steamwar.de", "https://cdn.discordapp.com/app-icons/869606970099904562/60c884000407c02671d91d8e7182b8a1.png")
+ .setColor(color)
+ .setTitle(player.parseToPlain(title))
+ .setDescription(player.parseToPlain(description))
+ .setTimestamp(Instant.now())
+ .build());
+ if(success)
+ builder.setActionRows(ActionRow.of(Button.success("tada", Emoji.fromUnicode("U+1F389"))));
+
+ new DiscordChannel(user).send(builder);
+ });
+ }
+}
diff --git a/VelocityCore/src/de/steamwar/velocitycore/discord/util/DiscordRanks.java b/VelocityCore/src/de/steamwar/velocitycore/discord/util/DiscordRanks.java
new file mode 100644
index 00000000..d96e9fe8
--- /dev/null
+++ b/VelocityCore/src/de/steamwar/velocitycore/discord/util/DiscordRanks.java
@@ -0,0 +1,59 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2020 SteamWar.de-Serverteam
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package de.steamwar.velocitycore.discord.util;
+
+import de.steamwar.velocitycore.discord.DiscordBot;
+import de.steamwar.sql.SteamwarUser;
+import de.steamwar.sql.UserPerm;
+import lombok.experimental.UtilityClass;
+import net.dv8tion.jda.api.entities.Guild;
+import net.dv8tion.jda.api.exceptions.ErrorResponseException;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+
+@UtilityClass
+public class DiscordRanks {
+
+ private final Map prefixToPermName = UserPerm.prefixes.entrySet().stream().collect(Collectors.toMap(Map.Entry::getValue, entry -> entry.getKey().name().toLowerCase()));
+
+ public void update(SteamwarUser user) {
+ if (user.getDiscordId() == null)
+ return;
+
+ Set swRoles = new HashSet<>(DiscordBot.getInstance().getConfig().getRanks().values());
+
+ Guild guild = DiscordBot.getGuild();
+ guild.retrieveMemberById(user.getDiscordId()).queue(member -> {
+ String prefixRole = DiscordBot.getInstance().getConfig().getRanks().get(prefixToPermName.get(user.prefix()));
+ member.getRoles().stream()
+ .filter(role -> swRoles.contains(role.getId()))
+ .filter(role -> !role.getId().equals(prefixRole))
+ .forEach(role -> guild.removeRoleFromMember(member, role).queue());
+
+ if (prefixRole != null && member.getRoles().stream().noneMatch(role -> role.getId().equals(prefixRole)))
+ guild.addRoleToMember(member, guild.getRoleById(prefixRole)).queue();
+ }, e -> {
+ if(e instanceof ErrorResponseException err && err.getErrorCode() == 10007)
+ user.setDiscordId(null);
+ });
+ }
+}
diff --git a/VelocityCore/src/de/steamwar/velocitycore/inventory/InvCallback.java b/VelocityCore/src/de/steamwar/velocitycore/inventory/InvCallback.java
new file mode 100644
index 00000000..1e034313
--- /dev/null
+++ b/VelocityCore/src/de/steamwar/velocitycore/inventory/InvCallback.java
@@ -0,0 +1,61 @@
+/*
+ This file is a part of the SteamWar software.
+
+ Copyright (C) 2020 SteamWar.de-Serverteam
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+*/
+
+package de.steamwar.velocitycore.inventory;
+
+public interface InvCallback {
+
+ void clicked(ClickType click);
+
+ enum ClickType {
+ LEFT,
+ SHIFT_LEFT,
+ RIGHT,
+ SHIFT_RIGHT,
+ WINDOW_BORDER_LEFT,
+ WINDOW_BORDER_RIGHT,
+ MIDDLE,
+ NUMBER_KEY,
+ DOUBLE_CLICK,
+ DROP,
+ CONTROL_DROP,
+ CREATIVE,
+ UNKNOWN;
+
+ public boolean isKeyboardClick() {
+ return this == NUMBER_KEY || this == DROP || this == CONTROL_DROP;
+ }
+
+ public boolean isCreativeAction() {
+ return this == MIDDLE || this == CREATIVE;
+ }
+
+ public boolean isRightClick() {
+ return this == RIGHT || this == SHIFT_RIGHT;
+ }
+
+ public boolean isLeftClick() {
+ return this == LEFT || this == SHIFT_LEFT || this == DOUBLE_CLICK || this == CREATIVE;
+ }
+
+ public boolean isShiftClick() {
+ return this == SHIFT_LEFT || this == SHIFT_RIGHT || this == CONTROL_DROP;
+ }
+ }
+}
diff --git a/VelocityCore/src/de/steamwar/velocitycore/inventory/SWInventory.java b/VelocityCore/src/de/steamwar/velocitycore/inventory/SWInventory.java
new file mode 100644
index 00000000..36034011
--- /dev/null
+++ b/VelocityCore/src/de/steamwar/velocitycore/inventory/SWInventory.java
@@ -0,0 +1,105 @@
+/*
+ This file is a part of the SteamWar software.
+
+ Copyright (C) 2020 SteamWar.de-Serverteam
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+*/
+
+package de.steamwar.velocitycore.inventory;
+
+import de.steamwar.messages.Message;
+import de.steamwar.velocitycore.network.NetworkSender;
+import de.steamwar.velocitycore.network.handlers.InventoryCallbackHandler;
+import de.steamwar.messages.Chatter;
+import de.steamwar.messages.PlayerChatter;
+import de.steamwar.network.packets.server.CloseInventoryPacket;
+import de.steamwar.network.packets.server.InventoryPacket;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.BiFunction;
+
+public class SWInventory {
+
+ private final Map itemMap = new HashMap<>();
+ private final Chatter player;
+ private final int size;
+ private final Message title;
+ @Setter
+ private InvCallback close;
+ @Getter
+ @Setter
+ private boolean next = false;
+
+ private final AtomicBoolean processingClick = new AtomicBoolean();
+
+ public SWInventory(PlayerChatter player, int size, Message title) {
+ InventoryCallbackHandler.inventoryHashMap.put(player.user().getId(), this);
+ this.player = player;
+ this.size = size;
+ this.title = title;
+ }
+
+ public void addItem(int pos, SWItem item, InvCallback callback) {
+ item.setCallback(callback);
+ addItem(pos, item);
+ }
+
+ public void addItem(int pos, SWItem item) {
+ itemMap.put(pos, item);
+ }
+
+ public void setCallback(int pos, InvCallback callback) {
+ itemMap.get(pos).setCallback(callback);
+ }
+
+ public void handleCallback(InvCallback.ClickType type, int pos) {
+ if(processingClick.compareAndSet(false, true)) {
+ itemMap.get(pos).getCallback().clicked(type);
+ processingClick.set(false);
+ }
+ }
+
+ public void handleClose() {
+ if(processingClick.compareAndSet(false, true)) {
+ InventoryCallbackHandler.inventoryHashMap.remove(player.user().getId(), this);
+ if(close != null)
+ close.clicked(null);
+ processingClick.set(false);
+ }
+ }
+
+ public void open() {
+ InventoryPacket inv = new InventoryPacket(player.parseToLegacy(title), player.user().getId(), size, map(itemMap, (integer, swItem) -> swItem.writeToString(player, integer).toString()));
+ player.withPlayer(p -> NetworkSender.send(p, inv));
+ }
+
+ private static Map map(Map map, BiFunction function) {
+ Map result = new HashMap<>();
+ map.forEach((key, value) -> result.put(key, function.apply(key, value)));
+ return result;
+ }
+
+ public void close() {
+ close(player);
+ }
+
+ public static void close(Chatter player) {
+ player.withPlayer(p -> NetworkSender.send(p, new CloseInventoryPacket(player.user().getId())));
+ }
+}
diff --git a/VelocityCore/src/de/steamwar/velocitycore/inventory/SWItem.java b/VelocityCore/src/de/steamwar/velocitycore/inventory/SWItem.java
new file mode 100644
index 00000000..c2db126f
--- /dev/null
+++ b/VelocityCore/src/de/steamwar/velocitycore/inventory/SWItem.java
@@ -0,0 +1,90 @@
+/*
+ This file is a part of the SteamWar software.
+
+ Copyright (C) 2020 SteamWar.de-Serverteam
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+*/
+
+package de.steamwar.velocitycore.inventory;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import de.steamwar.messages.Message;
+import de.steamwar.messages.Chatter;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Setter
+public class SWItem {
+
+ @Getter
+ private String material = "DYE";
+ private Message title;
+ private String skullOwner;
+ private boolean enchanted;
+ private boolean hideAttributes;
+ private List lore = new ArrayList<>();
+ @Getter
+ private InvCallback callback;
+ private int color = 0;
+
+ public SWItem(String material, Message title) {
+ this.material = material.toUpperCase();
+ this.title = title;
+ }
+
+ public SWItem(Message title, int color) {
+ this.title = title;
+ this.color = color;
+ }
+
+ public static SWItem getSkull(String skullOwner) {
+ SWItem item = new SWItem("SKULL", new Message("PLAIN_STRING", skullOwner));
+ item.setSkullOwner(skullOwner);
+ return item;
+ }
+
+ public SWItem addLore(Message lore) {
+ this.lore.add(lore);
+ return this;
+ }
+
+ public JsonObject writeToString(Chatter player, int position) {
+ JsonObject object = new JsonObject();
+ object.addProperty("material", material);
+ object.addProperty("position", position);
+ object.addProperty("title", player.parseToLegacy(title));
+ if(skullOwner != null)
+ object.addProperty("skullOwner", skullOwner);
+ if(enchanted)
+ object.addProperty("enchanted", true);
+ if(hideAttributes)
+ object.addProperty("hideAttributes", true);
+ if(color != 0)
+ object.addProperty("color", color);
+ if(lore != null) {
+ JsonArray array = new JsonArray();
+ for (Message lores : lore) {
+ array.add(player.parseToLegacy(lores));
+ }
+ object.add("lore", array);
+ }
+
+ return object;
+ }
+}
diff --git a/VelocityCore/src/de/steamwar/velocitycore/inventory/SWListInv.java b/VelocityCore/src/de/steamwar/velocitycore/inventory/SWListInv.java
new file mode 100644
index 00000000..44f3deb6
--- /dev/null
+++ b/VelocityCore/src/de/steamwar/velocitycore/inventory/SWListInv.java
@@ -0,0 +1,88 @@
+/*
+ This file is a part of the SteamWar software.
+
+ Copyright (C) 2020 SteamWar.de-Serverteam
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+*/
+
+package de.steamwar.velocitycore.inventory;
+
+import de.steamwar.messages.Message;
+import de.steamwar.messages.PlayerChatter;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.List;
+
+public class SWListInv extends SWInventory {
+ @Setter
+ private ListCallback callback;
+ private final List> elements;
+ private int page;
+
+ public SWListInv(PlayerChatter p, Message t, List> l, ListCallback c){
+ super(p, (l.size()>45) ? 54 : (l.size() + 9-l.size()%9), t);
+ callback = c;
+ elements = l;
+ page = 0;
+ }
+
+ @Override
+ public void open(){
+ if(elements.size() > 54){
+ if(page != 0)
+ addItem(45, new SWItem(new Message("INV_PAGE_BACK", "e"), 10), (InvCallback.ClickType click) -> {
+ page--;
+ open();
+ });
+ else
+ addItem(45, new SWItem(new Message("INV_PAGE_BACK", "7"), 8), (InvCallback.ClickType click) -> {});
+ if(page < elements.size()/45)
+ addItem(53, new SWItem(new Message("INV_PAGE_NEXT", "e"), 10), (InvCallback.ClickType click) -> {
+ page++;
+ open();
+ });
+ else
+ addItem(53, new SWItem(new Message("INV_PAGE_NEXT", "7"), 8), (InvCallback.ClickType click) -> {});
+ }
+
+ int ipageLimit = elements.size() - page*45;
+ if(ipageLimit > 45 && elements.size() > 54){
+ ipageLimit = 45;
+ }
+ int i = page*45;
+ for(int ipage=0; ipage < ipageLimit; ipage++ ){
+ SWItem e = elements.get(i).getItem();
+
+ final int pos = i;
+ addItem(ipage, e);
+ setCallback(ipage, (InvCallback.ClickType click) -> callback.clicked(click, elements.get(pos).getObject()));
+ i++;
+ }
+ super.open();
+ }
+
+ public interface ListCallback{
+ void clicked(InvCallback.ClickType click, T element);
+ }
+
+ @Getter
+ @AllArgsConstructor
+ public static class SWListEntry {
+ final SWItem item;
+ final T object;
+ }
+}
diff --git a/VelocityCore/src/de/steamwar/velocitycore/inventory/SWStreamInv.java b/VelocityCore/src/de/steamwar/velocitycore/inventory/SWStreamInv.java
new file mode 100644
index 00000000..2d82e352
--- /dev/null
+++ b/VelocityCore/src/de/steamwar/velocitycore/inventory/SWStreamInv.java
@@ -0,0 +1,71 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2024 SteamWar.de-Serverteam
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package de.steamwar.velocitycore.inventory;
+
+import de.steamwar.messages.Message;
+import de.steamwar.messages.PlayerChatter;
+
+import java.util.List;
+import java.util.function.Function;
+
+public class SWStreamInv extends SWInventory {
+ private final SWListInv.ListCallback callback;
+ private final Function>> constructor;
+ private int page;
+
+ public SWStreamInv(PlayerChatter chatter, Message title, SWListInv.ListCallback callback, Function>> constructor) {
+ super(chatter, 54, title);
+ this.callback = callback;
+ this.constructor = constructor;
+ page = 0;
+ }
+
+ @Override
+ public void open(){
+ List> entries = constructor.apply(page);
+
+ if(page != 0)
+ addItem(45, new SWItem(new Message("INV_PAGE_BACK", "e"), 10), (InvCallback.ClickType click) -> {
+ page--;
+ open();
+ });
+ else
+ addItem(45, new SWItem(new Message("INV_PAGE_BACK", "7"), 8), (InvCallback.ClickType click) -> {});
+
+ if(entries.size() == 45)
+ addItem(53, new SWItem(new Message("INV_PAGE_NEXT", "e"), 10), (InvCallback.ClickType click) -> {
+ page++;
+ open();
+ });
+ else
+ addItem(53, new SWItem(new Message("INV_PAGE_NEXT", "7"), 8), (InvCallback.ClickType click) -> {});
+
+ for(int i = 0; i < entries.size(); i++) {
+ SWListInv.SWListEntry item = entries.get(i);
+ addItem(i, item.getItem());
+ setCallback(i, (InvCallback.ClickType click) -> {
+ close();
+ callback.clicked(click, item.getObject());
+ });
+ }
+
+ super.open();
+ }
+}
diff --git a/VelocityCore/src/de/steamwar/velocitycore/listeners/BanListener.java b/VelocityCore/src/de/steamwar/velocitycore/listeners/BanListener.java
new file mode 100644
index 00000000..1d205cc8
--- /dev/null
+++ b/VelocityCore/src/de/steamwar/velocitycore/listeners/BanListener.java
@@ -0,0 +1,85 @@
+/*
+ This file is a part of the SteamWar software.
+
+ Copyright (C) 2020 SteamWar.de-Serverteam
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+*/
+
+package de.steamwar.velocitycore.listeners;
+
+import com.velocitypowered.api.event.Subscribe;
+import com.velocitypowered.api.event.connection.LoginEvent;
+import com.velocitypowered.api.proxy.Player;
+import de.steamwar.messages.Chatter;
+import de.steamwar.messages.Message;
+import de.steamwar.sql.BannedUserIPs;
+import de.steamwar.sql.Punishment;
+import de.steamwar.sql.SteamwarUser;
+import de.steamwar.velocitycore.commands.PunishmentCommand;
+import net.kyori.adventure.text.event.ClickEvent;
+
+import java.sql.Timestamp;
+import java.time.format.DateTimeFormatter;
+import java.util.List;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+public class BanListener extends BasicListener {
+
+ @Subscribe
+ public void onLogin(LoginEvent event) {
+ Player player = event.getPlayer();
+ SteamwarUser user = SteamwarUser.get(player.getUniqueId());
+ String ip = IPSanitizer.getTrueAddress(player).getHostAddress();
+ if (user.isPunished(Punishment.PunishmentType.Ban)) {
+ BannedUserIPs.banIP(user.getId(), ip);
+ Chatter.of(event).system(PunishmentCommand.punishmentMessage(user, Punishment.PunishmentType.Ban));
+ return;
+ }
+
+ List ips = BannedUserIPs.get(ip);
+ if(!ips.isEmpty()){
+ Timestamp highestBan = ips.get(0).getTimestamp();
+ boolean perma = false;
+ for(BannedUserIPs banned : ips) {
+ SteamwarUser bannedUser = SteamwarUser.get(banned.getUserID());
+ if (bannedUser.isPunished(Punishment.PunishmentType.Ban)) {
+ Punishment ban = bannedUser.getPunishment(Punishment.PunishmentType.Ban);
+ if(ban.isPerma()) {
+ perma = true;
+ break;
+ }
+ if(ban.getEndTime().after(highestBan))
+ highestBan = ban.getEndTime();
+ }
+ }
+ ClickEvent clickEvent = ClickEvent.runCommand("/ban " + user.getUserName() + " "
+ + (perma ? "perma" : highestBan.toLocalDateTime().format(DateTimeFormatter.ofPattern("dd.MM.yyyy_HH:mm")))
+ + " Ban Evasion - Bannumgehung");
+
+ Chatter.serverteam().system(
+ "BAN_AVOIDING_ALERT",
+ new Message("BAN_AVOIDING_BAN_HOVER"),
+ clickEvent,
+ user.getUserName(),
+ (Function) ((Chatter sender) -> ips.stream().map(banned -> {
+ SteamwarUser bannedUser = SteamwarUser.get(banned.getUserID());
+ return sender.parseToLegacy("BAN_AVOIDING_LIST", bannedUser.getUserName(),
+ banned.getTimestamp().toLocalDateTime().format(DateTimeFormatter.ofPattern(sender.parseToPlain("TIMEFORMAT"))));
+ }).collect(Collectors.joining(" ")))
+ );
+ }
+ }
+}
diff --git a/VelocityCore/src/de/steamwar/velocitycore/listeners/BasicListener.java b/VelocityCore/src/de/steamwar/velocitycore/listeners/BasicListener.java
new file mode 100644
index 00000000..34e2c766
--- /dev/null
+++ b/VelocityCore/src/de/steamwar/velocitycore/listeners/BasicListener.java
@@ -0,0 +1,29 @@
+/*
+ This file is a part of the SteamWar software.
+
+ Copyright (C) 2020 SteamWar.de-Serverteam
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+*/
+
+package de.steamwar.velocitycore.listeners;
+
+import de.steamwar.velocitycore.VelocityCore;
+
+public abstract class BasicListener {
+
+ protected BasicListener() {
+ VelocityCore.getProxy().getEventManager().register(VelocityCore.get(), this);
+ }
+}
diff --git a/VelocityCore/src/de/steamwar/velocitycore/listeners/ChatListener.java b/VelocityCore/src/de/steamwar/velocitycore/listeners/ChatListener.java
new file mode 100644
index 00000000..74429c13
--- /dev/null
+++ b/VelocityCore/src/de/steamwar/velocitycore/listeners/ChatListener.java
@@ -0,0 +1,265 @@
+/*
+ This file is a part of the SteamWar software.
+
+ Copyright (C) 2022 SteamWar.de-Serverteam
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+*/
+
+package de.steamwar.velocitycore.listeners;
+
+import com.velocitypowered.api.command.CommandSource;
+import com.velocitypowered.api.event.PostOrder;
+import com.velocitypowered.api.event.Subscribe;
+import com.velocitypowered.api.event.command.CommandExecuteEvent;
+import com.velocitypowered.api.event.player.PlayerChatEvent;
+import com.velocitypowered.api.event.player.TabCompleteEvent;
+import com.velocitypowered.api.proxy.ConsoleCommandSource;
+import com.velocitypowered.api.proxy.Player;
+import de.steamwar.messages.Chatter;
+import de.steamwar.messages.ChatterGroup;
+import de.steamwar.messages.Message;
+import de.steamwar.messages.PlayerChatter;
+import de.steamwar.network.packets.server.PingPacket;
+import de.steamwar.persistent.Servertype;
+import de.steamwar.persistent.Subserver;
+import de.steamwar.sql.*;
+import de.steamwar.velocitycore.ArenaMode;
+import de.steamwar.velocitycore.VelocityCore;
+import de.steamwar.velocitycore.commands.PunishmentCommand;
+import de.steamwar.velocitycore.discord.DiscordBot;
+import de.steamwar.velocitycore.network.NetworkSender;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+
+public class ChatListener extends BasicListener {
+
+ private static final Logger cmdLogger = Logger.getLogger("Command logger");
+
+ private static final List rankedModes = ArenaMode.getAllModes().stream().filter(ArenaMode::isRanked).map(ArenaMode::getSchemType).toList();
+
+ @Subscribe(order = PostOrder.FIRST)
+ public void fixCommands(CommandExecuteEvent e) {
+ String command = e.getCommand();
+ if(command.startsWith("7")) {
+ command = "/" + command.substring(1);
+
+ CommandExecuteEvent.CommandResult result = e.getResult();
+ if(result.isForwardToServer())
+ result = CommandExecuteEvent.CommandResult.forwardToServer(command);
+ else if(result.isAllowed())
+ result = CommandExecuteEvent.CommandResult.command(command);
+
+ e.setResult(result);
+ }
+ }
+
+ @Subscribe(order = PostOrder.LAST)
+ public void logCommands(CommandExecuteEvent e) {
+ String command = e.getCommand();
+ int space = command.indexOf(' ');
+ if(VelocityCore.getProxy().getCommandManager().hasCommand(space != -1 ? command.substring(0, space) : command)) {
+ CommandSource source = e.getCommandSource();
+ String name;
+ if(source instanceof Player player)
+ name = player.getUsername();
+ else if(source instanceof ConsoleCommandSource)
+ name = "«CONSOLE»";
+ else
+ name = source.toString();
+
+ cmdLogger.log(Level.INFO, "%s -> executed command /%s".formatted(name, command));
+ } else if (e.getCommandSource() instanceof Player player) {
+ player.spoofChatInput("/" + command);
+ e.setResult(CommandExecuteEvent.CommandResult.denied());
+ }
+ }
+
+ @Subscribe
+ public void onChatEvent(PlayerChatEvent e) {
+ Player player = e.getPlayer();
+ String message = e.getMessage();
+
+ e.setResult(PlayerChatEvent.ChatResult.denied());
+
+ if (message.contains("jndi:ldap")) {
+ SteamwarUser user = SteamwarUser.get(player.getUniqueId());
+ PunishmentCommand.ban(user, Punishment.PERMA_TIME, "Versuchte Exploit-Ausnutzung", SteamwarUser.get(-1), true);
+ VelocityCore.getLogger().log(Level.SEVERE, "%s %s wurde automatisch wegen jndi:ldap gebannt.".formatted(user.getUserName(), user.getId()));
+ return;
+ }
+
+ if (isMistypedCommand(player, message))
+ return;
+
+ Subserver subserver = Subserver.getSubserver(player);
+ if(subserver != null && subserver.getType() == Servertype.ARENA && subserver.getServer() == player.getCurrentServer().orElseThrow().getServerInfo()) {
+ localChat(Chatter.of(player), message);
+ } else if (message.startsWith("+")) {
+ localChat(Chatter.of(player), message.substring(1));
+ } else {
+ sendChat(Chatter.of(player), Chatter.globalChat(), "CHAT_GLOBAL", null, message);
+ }
+ }
+
+ private static boolean isMistypedCommand(Player player, String message) {
+ String command = message.substring(1);
+ boolean isCommand = message.startsWith("7") && command.matches("^[7/]?[A-Za-z]+");
+ if(isCommand && Boolean.FALSE.equals(VelocityCore.getProxy().getCommandManager().executeAsync(player, command).join())) {
+ if(command.startsWith("7"))
+ command = "/" + command.substring(1);
+ message = "/" + command;
+
+ if(filteredCommand(Chatter.of(player), message))
+ return true;
+
+ player.spoofChatInput(message);
+ }
+
+ return isCommand;
+ }
+
+ public static void sendChat(Chatter sender, ChatterGroup receivers, String format, Chatter msgReceiver, String message) {
+ SteamwarUser user = sender.user();
+ final String coloredMessage = user.hasPerm(UserPerm.COLOR_CHAT) ? message.replace('&', '§') : message;
+ if(chatFilter(sender, coloredMessage))
+ return;
+
+ boolean noReceiver = true;
+ for(Chatter player : receivers.getChatters()) {
+ if(player.chatShown()) {
+ chatToReciever(player, msgReceiver, user, format, coloredMessage);
+ if(sender.user().getId() != player.user().getId())
+ noReceiver = false;
+ }
+
+ }
+
+ if(format.equals("CHAT_GLOBAL")) {
+ DiscordBot.withBot(bot -> chatToReciever(bot.getIngameChat(), msgReceiver, user, format, coloredMessage));
+ } else if (format.equals("CHAT_SERVERTEAM")) {
+ DiscordBot.withBot(bot -> chatToReciever(bot.getServerTeamChat(), msgReceiver, user, format, coloredMessage));
+ } else if (noReceiver) {
+ sender.system("CHAT_NO_RECEIVER");
+ }
+ }
+
+ public static void localChat(PlayerChatter sender, String message) {
+ if(message.isEmpty()){
+ sender.system("CHAT_BC_USAGE");
+ return;
+ }
+
+ if(ChatListener.filteredCommand(sender, message))
+ return;
+
+ if(!message.startsWith("/") && chatFilter(sender, message))
+ return;
+
+ sender.getPlayer().spoofChatInput(message);
+ }
+
+ private static boolean chatFilter(Chatter sender, String message) {
+ if(!sender.chatShown()) {
+ sender.system("CHAT_RECEIVE");
+ return true;
+ }
+
+ if(message.replace("§[a-f0-9klmno]", "").trim().isEmpty()) {
+ sender.system("CHAT_EMPTY");
+ return true;
+ }
+
+ SteamwarUser user = sender.user();
+ if(!user.hasPerm(UserPerm.TEAM) && (message.contains("http:") || message.contains("https:") || message.contains("www."))){
+ sender.system("CHAT_NO_LINKS");
+ return true;
+ }
+
+ if (PunishmentCommand.isPunishedWithMessage(sender, Punishment.PunishmentType.Mute))
+ return true;
+
+ if (message.contains("LIXFEL"))
+ specialAlert(sender, "Lixfel", "CHAT_LIXFEL_", 3, 6, 11, 12, 15);
+ if (message.contains("YOYONOW"))
+ specialAlert(sender, "YoyoNow", "CHAT_YOYONOW_", 3, 6, 11, 12);
+ if (message.contains("CHAOSCAOT"))
+ specialAlert(sender, "Chaoscaot", "CHAT_CHAOSCAOT_", 3, 6, 11, 12, 15, 17);
+
+ return false;
+ }
+
+ private static void chatToReciever(Chatter receiver, Chatter msgReceiver, SteamwarUser sender, String format, String message) {
+ UserPerm.Prefix prefix = sender.prefix();
+ String chatColorCode = sender.hasPerm(UserPerm.TEAM) ? "§f" : "§7";
+ receiver.prefixless(format,
+ sender,
+ msgReceiver == null ? receiver : msgReceiver,
+ highlightMentions(message, chatColorCode, receiver),
+ sender.getTeam() == 0 ? "" : "§" + Team.get(sender.getTeam()).getTeamColor() + Team.get(sender.getTeam()).getTeamKuerzel() + " ",
+ UserElo.getEmblem(sender, rankedModes),
+ prefix.getColorCode(),
+ prefix.getChatPrefix().length() == 0 ? "§f" : prefix.getChatPrefix() + " ",
+ chatColorCode);
+ }
+
+ private static boolean filteredCommand(Chatter sender, String message) {
+ String command = message.split(" ", 2)[0];
+ if(command.startsWith("/") && command.contains(":")) {
+ sender.system("UNKNOWN_COMMAND");
+ return true;
+ }
+ return false;
+ }
+
+ private static void specialAlert(Chatter sender, String name, String baseMessage, int... delay) {
+ sender.system("CHAT_LIXFEL_ACTION_BAR");
+ for(int i = 0; i < delay.length; i++) {
+ int finalI = i;
+ VelocityCore.schedule( () -> sender.prefixless("CHAT_MSG", name, sender.user(), new Message(baseMessage + (finalI+1)))).delay(delay[i], TimeUnit.SECONDS).schedule();
+ }
+ }
+
+ private static String highlightMentions(String message, String returnColor, Chatter player) {
+ if(!message.contains("@"))
+ return message;
+
+ String mark = "@" + player.user().getUserName();
+ return Arrays.stream(message.split(" ")).map(cur -> {
+ if(cur.equalsIgnoreCase(mark) && player.getPlayer() != null) {
+ NetworkSender.send(player.getPlayer(), new PingPacket(player.user().getId()));
+ return "§e" + cur + returnColor;
+ }
+ return cur;
+ }).collect(Collectors.joining(" "));
+ }
+
+ @Subscribe
+ public void onTabCompleteResponseEvent(TabCompleteEvent e){
+ List