In-game commands

This commit is contained in:
Vankka 2022-03-20 13:46:06 +02:00
parent 90150325bf
commit 99a87382bd
No known key found for this signature in database
GPG Key ID: 6E50CB7A29B96AD0
68 changed files with 2874 additions and 327 deletions

View File

@ -35,7 +35,7 @@ import net.dv8tion.jda.api.JDA;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import java.util.Optional;
import java.util.*;
/**
* The DiscordSRV API.
@ -215,4 +215,27 @@ public interface DiscordSRVApi {
}
}
enum ReloadFlag {
CONFIG(false),
LINKED_ACCOUNT_PROVIDER(false),
STORAGE(true),
DISCORD_CONNECTION(true),
MODULES(false),
;
public static final Set<ReloadFlag> ALL = Collections.unmodifiableSet(new LinkedHashSet<>(Arrays.asList(values())));
public static final Set<ReloadFlag> DEFAULT_FLAGS = Collections.unmodifiableSet(new LinkedHashSet<>(Arrays.asList(CONFIG, MODULES)));
private final boolean requiresConfirm;
ReloadFlag(boolean requiresConfirm) {
this.requiresConfirm = requiresConfirm;
}
public boolean requiresConfirm() {
return requiresConfirm;
}
}
}

View File

@ -25,15 +25,9 @@ package com.discordsrv.api.event.events.lifecycle;
import com.discordsrv.api.event.events.Event;
public class DiscordSRVReloadEvent implements Event {
/**
* Indicates that DiscordSRV's Discord connection has been (re-)established. This may run more than once.
*/
public class DiscordSRVConnectedEvent implements Event {
private final boolean config;
public DiscordSRVReloadEvent(boolean config) {
this.config = config;
}
public boolean isConfig() {
return config;
}
}

View File

@ -26,8 +26,7 @@ package com.discordsrv.api.event.events.lifecycle;
import com.discordsrv.api.event.events.Event;
/**
* Indicates that DiscordSRV's systems (including Discord connection) are ready.
* Indicates that DiscordSRV is ready (including Discord connection), this only runs once when DiscordSRV enables.
*/
public class DiscordSRVReadyEvent implements Event {
}

View File

@ -0,0 +1,49 @@
/*
* This file is part of the DiscordSRV API, licensed under the MIT License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.discordsrv.api.event.events.lifecycle;
import com.discordsrv.api.DiscordSRVApi;
import com.discordsrv.api.event.events.Event;
import java.util.Set;
/**
* An event for when DiscordSRV successfully reloads partially or completely.
*/
public class DiscordSRVReloadedEvent implements Event {
private final Set<DiscordSRVApi.ReloadFlag> flags;
public DiscordSRVReloadedEvent(Set<DiscordSRVApi.ReloadFlag> flags) {
this.flags = flags;
}
/**
* Set of DiscordSRV systems that were reloaded.
* @return an unmodifiable set of {@link com.discordsrv.api.DiscordSRVApi.ReloadFlag}s
*/
public Set<DiscordSRVApi.ReloadFlag> flags() {
return flags;
}
}

View File

@ -1,6 +1,6 @@
plugins {
id 'com.github.johnrengelman.shadow' version '7.1.1' apply false
id 'org.cadixdev.licenser' version '0.6.0' apply false
id 'org.cadixdev.licenser' version '0.6.1' apply false
id 'net.kyori.blossom' version '1.2.0' apply false
id 'dev.vankka.dependencydownload.plugin' version '1.1.5-SNAPSHOT' apply false
}
@ -17,8 +17,8 @@ ext {
// Configurate
configurateVersion = '4.1.2'
// Adventure & Adventure Platform
adventureVersion = '4.9.1'
adventurePlatformVersion = '4.0.0'
adventureVersion = '4.10.0'
adventurePlatformVersion = '4.1.0'
}
allprojects {
@ -59,6 +59,12 @@ allprojects {
includeGroup 'me.scarsz'
}
}
maven {
url 'https://libraries.minecraft.net'
content {
includeGroup 'com.mojang'
}
}
// Get dependencies from central last, everything else should be filtered
mavenCentral()

View File

@ -1,5 +1,30 @@
import dev.vankka.dependencydownload.task.GenerateDependencyDownloadResourceTask
apply from: rootProject.file('buildscript/runtime.gradle')
configurations {
commodore
compileOnly.extendsFrom commodore
}
task generateResourceForCommodore(type: GenerateDependencyDownloadResourceTask) {
var conf = configurations.commodore
configuration = conf
file = 'dependencies/' + conf.name + '.txt'
}
shadowJar {
archiveFileName = 'bukkit.jarinjar'
[
'net.kyori',
'me.lucko.commodore'
].each {
relocate it, 'com.discordsrv.dependencies.' + it
}
// More relocations in buildscript/relocations.gradle
}
allprojects {
repositories {
exclusiveContent {
@ -41,13 +66,18 @@ dependencies {
// Adventure
runtimeDownloadApi 'net.kyori:adventure-platform-bukkit:' + rootProject.adventurePlatformVersion
// Commodore
commodore('me.lucko:commodore:1.10') {
// We only use commodore when it's included in the server, so we don't want to download it
exclude module: 'brigadier'
// We don't use the commodore file format
exclude module: 'commodore-file'
}
// Integrations
compileOnly 'net.milkbowl.vault:VaultAPI:1.7'
}
shadowJar {
archiveFileName = 'bukkit.jarinjar'
relocate 'net.kyori', 'com.discordsrv.dependencies.net.kyori'
// More relocations in buildscript/relocations.gradle
processResources {
dependsOn(generateResourceForCommodore)
}

View File

@ -5,3 +5,14 @@ description: ""
authors: [Scarsz, Vankka]
load: STARTUP
api-version: 1.13
softdepend: [
# Permission + group providers
Vault, LuckPerms,
# Adventure
ViaVersion
]
commands:
discordsrv:
description: "DiscordSRV's primary command"

View File

@ -19,6 +19,7 @@
package com.discordsrv.bukkit;
import com.discordsrv.api.DiscordSRVApi;
import com.discordsrv.bukkit.command.game.handler.AbstractBukkitCommandHandler;
import com.discordsrv.bukkit.config.connection.BukkitConnectionConfig;
import com.discordsrv.bukkit.config.main.BukkitConfig;
import com.discordsrv.bukkit.config.manager.BukkitConfigManager;
@ -32,6 +33,7 @@ import com.discordsrv.bukkit.listener.BukkitStatusMessageListener;
import com.discordsrv.bukkit.player.BukkitPlayerProvider;
import com.discordsrv.bukkit.plugin.BukkitPluginManager;
import com.discordsrv.bukkit.scheduler.BukkitScheduler;
import com.discordsrv.common.command.game.handler.ICommandHandler;
import com.discordsrv.common.config.manager.ConnectionConfigManager;
import com.discordsrv.common.config.manager.MainConfigManager;
import com.discordsrv.common.debug.data.OnlineMode;
@ -60,6 +62,7 @@ public class BukkitDiscordSRV extends ServerDiscordSRV<BukkitConfig, BukkitConne
private final BukkitConsole console;
private final BukkitPlayerProvider playerProvider;
private final BukkitPluginManager pluginManager;
private AbstractBukkitCommandHandler commandHandler;
private final BukkitConnectionConfigManager connectionConfigManager;
private final BukkitConfigManager configManager;
@ -157,6 +160,11 @@ public class BukkitDiscordSRV extends ServerDiscordSRV<BukkitConfig, BukkitConne
return bootstrap.getClasspathAppender();
}
@Override
public ICommandHandler commandHandler() {
return commandHandler;
}
@Override
public ConnectionConfigManager<BukkitConnectionConfig> connectionConfigManager() {
return connectionConfigManager;
@ -176,15 +184,19 @@ public class BukkitDiscordSRV extends ServerDiscordSRV<BukkitConfig, BukkitConne
this.audiences = BukkitAudiences.create(bootstrap.getPlugin());
server().getPluginManager().registerEvents(new BukkitConnectionListener(this), plugin());
super.enable();
// Command handler
commandHandler = AbstractBukkitCommandHandler.get(this);
// Register listeners
server().getPluginManager().registerEvents(BukkitChatListener.get(this), plugin());
server().getPluginManager().registerEvents(new BukkitDeathListener(this), plugin());
server().getPluginManager().registerEvents(new BukkitStatusMessageListener(this), plugin());
// Modules
registerModule(VaultIntegration::new);
registerModule(MinecraftToDiscordChatModule::new);
super.enable();
}
}

View File

@ -26,6 +26,8 @@ import dev.vankka.mcdependencydownload.classloader.JarInJarClassLoader;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class DiscordSRVBukkitBootstrap extends BukkitBootstrap {
@ -40,11 +42,25 @@ public class DiscordSRVBukkitBootstrap extends BukkitBootstrap {
this.dependencies = new InitialDependencyLoader(
logger,
plugin.getDataFolder().toPath(),
new String[] {"dependencies/runtimeDownload-bukkit.txt"},
getDependencyResources(),
getClasspathAppender()
);
}
private static String[] getDependencyResources() {
List<String> resources = new ArrayList<>();
resources.add("dependencies/runtimeDownload-bukkit.txt");
try {
Class.forName("com.mojang.brigadier.CommandDispatcher");
resources.add("dependencies/commodore.txt");
} catch (ClassNotFoundException ignored) {
// CommandDispatches not present, don't need to bother downloading commodore
}
return resources.toArray(new String[0]);
}
@Override
public void onEnable() {
dependencies.loadAndEnable(() -> this.discordSRV = new BukkitDiscordSRV(this, logger));

View File

@ -0,0 +1,49 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.bukkit.command.game.handler;
import com.discordsrv.bukkit.BukkitDiscordSRV;
import com.discordsrv.common.command.game.handler.BasicCommandHandler;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
public abstract class AbstractBukkitCommandExecutor extends AbstractBukkitCommandHandler implements CommandExecutor {
protected final BasicCommandHandler handler = new BasicCommandHandler();
public AbstractBukkitCommandExecutor(BukkitDiscordSRV discordSRV) {
super(discordSRV);
}
@Override
public boolean onCommand(
@NotNull CommandSender sender,
@NotNull Command command,
@NotNull String label,
@NotNull String[] args
) {
handler.execute(sender(sender), label, Arrays.asList(args));
return true;
}
}

View File

@ -0,0 +1,91 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.bukkit.command.game.handler;
import com.discordsrv.bukkit.BukkitDiscordSRV;
import com.discordsrv.bukkit.command.game.sender.BukkitCommandSender;
import com.discordsrv.common.command.game.abstraction.GameCommand;
import com.discordsrv.common.command.game.handler.ICommandHandler;
import com.discordsrv.common.command.game.sender.ICommandSender;
import com.discordsrv.common.logging.Logger;
import com.discordsrv.common.logging.NamedLogger;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.command.PluginCommand;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import java.lang.reflect.Constructor;
import java.util.Locale;
public abstract class AbstractBukkitCommandHandler implements ICommandHandler {
public static AbstractBukkitCommandHandler get(BukkitDiscordSRV discordSRV) {
try {
Class.forName("me.lucko.commodore.Commodore");
return new CommodoreHandler(discordSRV);
} catch (Throwable e) {
BukkitBasicCommandHandler handler = new BukkitBasicCommandHandler(discordSRV);
if (e instanceof ClassNotFoundException) {
handler.logger.debug("Brigadier classes not present, not using commodore");
} else {
handler.logger.debug("Failed to initialize Commodore", e);
}
return handler;
}
}
protected final BukkitDiscordSRV discordSRV;
protected final Logger logger;
public AbstractBukkitCommandHandler(BukkitDiscordSRV discordSRV) {
this.discordSRV = discordSRV;
this.logger = new NamedLogger(discordSRV, "COMMAND_HANDLER");
}
protected ICommandSender sender(CommandSender commandSender) {
if (commandSender instanceof Player) {
return discordSRV.playerProvider().player((Player) commandSender);
} else if (commandSender instanceof ConsoleCommandSender) {
return discordSRV.console();
} else {
return new BukkitCommandSender(discordSRV, commandSender, () -> discordSRV.audiences().sender(commandSender));
}
}
protected PluginCommand command(GameCommand gameCommand) {
String label = gameCommand.getLabel();
PluginCommand pluginCommand = discordSRV.plugin().getCommand(label);
if (pluginCommand != null) {
return pluginCommand;
}
PluginCommand command = null;
try {
Constructor<?> constructor = PluginCommand.class.getDeclaredConstructor(String.class, Plugin.class);
if (!constructor.isAccessible()) {
constructor.setAccessible(true);
}
command = (PluginCommand) constructor.newInstance(label, discordSRV.plugin());
discordSRV.server().getCommandMap().register(label, discordSRV.plugin().getName().toLowerCase(Locale.ROOT), command);
} catch (ReflectiveOperationException ignored) {}
return command;
}
}

View File

@ -0,0 +1,63 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.bukkit.command.game.handler;
import com.discordsrv.bukkit.BukkitDiscordSRV;
import com.discordsrv.common.command.game.abstraction.GameCommand;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.PluginCommand;
import org.bukkit.command.TabCompleter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.List;
public class BukkitBasicCommandHandler extends AbstractBukkitCommandExecutor implements TabCompleter {
public BukkitBasicCommandHandler(BukkitDiscordSRV discordSRV) {
super(discordSRV);
}
@Override
public @Nullable List<String> onTabComplete(
@NotNull CommandSender sender,
@NotNull Command command,
@NotNull String alias,
@NotNull String[] args
) {
return handler.suggest(sender(sender), alias, Arrays.asList(args));
}
@Override
public void registerCommand(GameCommand command) {
PluginCommand pluginCommand = command(command);
if (pluginCommand == null) {
logger.error("Failed to create command " + command.getLabel());
return;
}
logger.debug("Registering command " + command.getLabel() + " with basic handler");
handler.registerCommand(command);
pluginCommand.setExecutor(this);
pluginCommand.setTabCompleter(this);
}
}

View File

@ -0,0 +1,73 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.bukkit.command.game.handler;
import com.discordsrv.bukkit.BukkitDiscordSRV;
import com.discordsrv.common.command.game.abstraction.GameCommand;
import com.discordsrv.common.command.game.handler.util.BrigadierUtil;
import com.mojang.brigadier.tree.CommandNode;
import com.mojang.brigadier.tree.LiteralCommandNode;
import me.lucko.commodore.Commodore;
import me.lucko.commodore.CommodoreProvider;
import org.bukkit.command.PluginCommand;
/**
* No avoiding basic handler on bukkit. Unfortunately it isn't possible to use brigadier for executing.
*/
public class CommodoreHandler extends AbstractBukkitCommandExecutor {
private final Commodore commodore;
public CommodoreHandler(BukkitDiscordSRV discordSRV) {
super(discordSRV);
this.commodore = CommodoreProvider.getCommodore(discordSRV.plugin());
}
@Override
public void registerCommand(GameCommand command) {
logger.debug("Registering command " + command.getLabel() + " with commodore");
LiteralCommandNode<?> argumentBuilder = BrigadierUtil.convertToBrigadier(
command,
wrapper -> sender(commodore.getBukkitSender(wrapper))
);
CommandNode<?> redirection = argumentBuilder.getRedirect();
if (redirection != null) {
// Commodore handles the label being different fine
argumentBuilder = (LiteralCommandNode<?>) redirection;
}
PluginCommand pluginCommand = command(command);
if (pluginCommand == null) {
logger.error("Failed to create command " + command.getLabel());
return;
}
handler.registerCommand(command);
pluginCommand.setExecutor(this);
String requiredPermission = command.getRequiredPermission();
LiteralCommandNode<?> finalArgumentBuilder = argumentBuilder;
discordSRV.scheduler().runOnMainThread(() -> commodore.register(
pluginCommand,
finalArgumentBuilder,
player -> requiredPermission == null || player.hasPermission(requiredPermission)
));
}
}

View File

@ -0,0 +1,55 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.bukkit.command.game.sender;
import com.discordsrv.bukkit.BukkitDiscordSRV;
import com.discordsrv.common.command.game.sender.ICommandSender;
import net.kyori.adventure.audience.Audience;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import java.util.function.Supplier;
public class BukkitCommandSender implements ICommandSender {
protected final BukkitDiscordSRV discordSRV;
protected final CommandSender commandSender;
protected final Supplier<Audience> audienceSupplier;
public BukkitCommandSender(BukkitDiscordSRV discordSRV, CommandSender commandSender, Supplier<Audience> audienceSupplier) {
this.discordSRV = discordSRV;
this.commandSender = commandSender;
this.audienceSupplier = audienceSupplier;
}
@Override
public boolean hasPermission(String permission) {
return commandSender.hasPermission(permission);
}
@Override
public void runCommand(String command) {
discordSRV.scheduler().runOnMainThread(() -> discordSRV.server().dispatchCommand(commandSender, command));
}
@Override
public @NotNull Audience audience() {
return audienceSupplier.get();
}
}

View File

@ -19,21 +19,19 @@
package com.discordsrv.bukkit.console;
import com.discordsrv.bukkit.BukkitDiscordSRV;
import com.discordsrv.bukkit.command.game.sender.BukkitCommandSender;
import com.discordsrv.common.console.Console;
import com.discordsrv.common.logging.NamedLogger;
import com.discordsrv.common.logging.backend.LoggingBackend;
import com.discordsrv.common.logging.backend.impl.JavaLoggerImpl;
import com.discordsrv.common.logging.backend.impl.Log4JLoggerImpl;
import net.kyori.adventure.audience.Audience;
import org.jetbrains.annotations.NotNull;
public class BukkitConsole implements Console {
public class BukkitConsole extends BukkitCommandSender implements Console {
private final BukkitDiscordSRV discordSRV;
private final LoggingBackend loggingBackend;
public BukkitConsole(BukkitDiscordSRV discordSRV) {
this.discordSRV = discordSRV;
super(discordSRV, discordSRV.server().getConsoleSender(), () -> discordSRV.audiences().console());
LoggingBackend logging;
try {
@ -49,25 +47,8 @@ public class BukkitConsole implements Console {
this.loggingBackend = logging;
}
@Override
public boolean hasPermission(String permission) {
return discordSRV.server().getConsoleSender().hasPermission(permission);
}
@Override
public void runCommand(String command) {
discordSRV.scheduler().runOnMainThread(() ->
discordSRV.server().dispatchCommand(
discordSRV.server().getConsoleSender(), command));
}
@Override
public LoggingBackend loggingBackend() {
return loggingBackend;
}
@Override
public @NotNull Audience audience() {
return discordSRV.audiences().console();
}
}

View File

@ -42,7 +42,6 @@ public class BukkitOfflinePlayer implements IOfflinePlayer {
return discordSRV;
}
@SuppressWarnings("NullabilityProblems")
@Override
public String username() {
return offlinePlayer.getName();

View File

@ -19,36 +19,25 @@
package com.discordsrv.bukkit.player;
import com.discordsrv.bukkit.BukkitDiscordSRV;
import com.discordsrv.bukkit.command.game.sender.BukkitCommandSender;
import com.discordsrv.bukkit.component.util.PaperComponentUtil;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.component.util.ComponentUtil;
import com.discordsrv.common.player.IPlayer;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.identity.Identity;
import net.kyori.adventure.text.Component;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
@SuppressWarnings("NullableProblems") // BukkitOfflinePlayer nullability
public class BukkitPlayer extends BukkitOfflinePlayer implements IPlayer {
public class BukkitPlayer extends BukkitCommandSender implements IPlayer {
private final Player player;
private final Audience audience;
private final Identity identity;
public BukkitPlayer(BukkitDiscordSRV discordSRV, Player player) {
super(discordSRV, player);
super(discordSRV, player, () -> discordSRV.audiences().player(player));
this.player = player;
this.audience = discordSRV.audiences().player(player);
}
@Override
public boolean hasPermission(String permission) {
return player.hasPermission(permission);
}
@Override
public void runCommand(String command) {
discordSRV.scheduler().runOnMainThread(() ->
discordSRV.server().dispatchCommand(player, command));
this.identity = Identity.identity(player.getUniqueId());
}
@Override
@ -56,6 +45,11 @@ public class BukkitPlayer extends BukkitOfflinePlayer implements IPlayer {
return discordSRV;
}
@Override
public @NotNull String username() {
return player.getName();
}
@SuppressWarnings("deprecation") // Paper
@Override
public @NotNull Component displayName() {
@ -65,7 +59,7 @@ public class BukkitPlayer extends BukkitOfflinePlayer implements IPlayer {
}
@Override
public @NotNull Audience audience() {
return audience;
public @NotNull Identity identity() {
return identity;
}
}

View File

@ -26,7 +26,7 @@ import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerLoginEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import java.util.Optional;
@ -52,8 +52,8 @@ public class BukkitPlayerProvider extends ServerPlayerProvider<BukkitPlayer, Buk
}
}
@EventHandler(priority = EventPriority.LOWEST)
public void onPlayerJoin(PlayerJoinEvent event) {
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerLogin(PlayerLoginEvent event) {
addPlayer(event.getPlayer(), false);
}

View File

@ -1,5 +1,16 @@
apply from: rootProject.file('buildscript/runtime.gradle')
shadowJar {
archiveFileName = 'bungee.jarinjar'
[
'net.kyori'
].each {
relocate it, 'com.discordsrv.dependencies.' + it
}
// More relocations in buildscript/relocations.gradle
}
allprojects {
repositories {
exclusiveContent {
@ -32,10 +43,3 @@ dependencies {
runtimeDownloadApi 'net.kyori:adventure-platform-bungeecord:' + rootProject.adventurePlatformVersion
runtimeDownloadApi 'net.kyori:adventure-text-serializer-bungeecord:' + rootProject.adventurePlatformVersion
}
shadowJar {
archiveFileName = 'bungee.jarinjar'
relocate 'net.kyori', 'com.discordsrv.dependencies.net.kyori'
// More relocations in buildscript/relocations.gradle
}

View File

@ -18,9 +18,11 @@
package com.discordsrv.bungee;
import com.discordsrv.bungee.command.game.handler.BungeeCommandHandler;
import com.discordsrv.bungee.console.BungeeConsole;
import com.discordsrv.bungee.player.BungeePlayerProvider;
import com.discordsrv.bungee.plugin.BungeePluginManager;
import com.discordsrv.common.command.game.handler.ICommandHandler;
import com.discordsrv.common.config.connection.ConnectionConfig;
import com.discordsrv.common.config.main.MainConfig;
import com.discordsrv.common.config.manager.ConnectionConfigManager;
@ -49,6 +51,7 @@ public class BungeeDiscordSRV extends ProxyDiscordSRV<MainConfig, ConnectionConf
private final BungeeConsole console;
private final BungeePlayerProvider playerProvider;
private final BungeePluginManager pluginManager;
private BungeeCommandHandler commandHandler;
public BungeeDiscordSRV(DiscordSRVBungeeBootstrap bootstrap, Logger logger) {
this.bootstrap = bootstrap;
@ -120,6 +123,11 @@ public class BungeeDiscordSRV extends ProxyDiscordSRV<MainConfig, ConnectionConf
return bootstrap.getClasspathAppender();
}
@Override
public ICommandHandler commandHandler() {
return commandHandler;
}
@Override
public ConnectionConfigManager<ConnectionConfig> connectionConfigManager() {
return null;
@ -135,6 +143,8 @@ public class BungeeDiscordSRV extends ProxyDiscordSRV<MainConfig, ConnectionConf
// Player related
this.audiences = BungeeAudiences.create(bootstrap.getPlugin());
this.commandHandler = new BungeeCommandHandler(this);
super.enable();
}
}

View File

@ -0,0 +1,80 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.bungee.command.game.handler;
import com.discordsrv.bungee.BungeeDiscordSRV;
import com.discordsrv.bungee.command.game.sender.BungeeCommandSender;
import com.discordsrv.common.command.game.abstraction.GameCommand;
import com.discordsrv.common.command.game.handler.BasicCommandHandler;
import com.discordsrv.common.command.game.sender.ICommandSender;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Command;
import net.md_5.bungee.api.plugin.TabExecutor;
import java.util.Arrays;
/**
* BungeeCord has not concept of Brigadier.
*/
public class BungeeCommandHandler extends BasicCommandHandler {
private final BungeeDiscordSRV discordSRV;
public BungeeCommandHandler(BungeeDiscordSRV discordSRV) {
this.discordSRV = discordSRV;
}
@Override
public void registerCommand(GameCommand command) {
super.registerCommand(command);
discordSRV.proxy().getPluginManager().registerCommand(discordSRV.plugin(), new BungeeCommand(command));
}
public ICommandSender getSender(CommandSender sender) {
if (sender instanceof ProxiedPlayer) {
return discordSRV.playerProvider().player((ProxiedPlayer) sender);
} else if (sender == discordSRV.proxy().getConsole()) {
return discordSRV.console();
} else {
return new BungeeCommandSender(discordSRV, sender, () -> discordSRV.audiences().sender(sender));
}
}
public class BungeeCommand extends Command implements TabExecutor {
private final GameCommand command;
public BungeeCommand(GameCommand command) {
super(command.getLabel(), command.getRequiredPermission());
this.command = command;
}
@Override
public void execute(CommandSender sender, String[] args) {
BungeeCommandHandler.this.execute(getSender(sender), command.getLabel(), Arrays.asList(args));
}
@Override
public Iterable<String> onTabComplete(CommandSender sender, String[] args) {
return BungeeCommandHandler.this.suggest(getSender(sender), command.getLabel(), Arrays.asList(args));
}
}
}

View File

@ -0,0 +1,55 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.bungee.command.game.sender;
import com.discordsrv.bungee.BungeeDiscordSRV;
import com.discordsrv.common.command.game.sender.ICommandSender;
import net.kyori.adventure.audience.Audience;
import net.md_5.bungee.api.CommandSender;
import org.jetbrains.annotations.NotNull;
import java.util.function.Supplier;
public class BungeeCommandSender implements ICommandSender {
protected final BungeeDiscordSRV discordSRV;
protected final CommandSender commandSender;
protected final Supplier<Audience> audience;
public BungeeCommandSender(BungeeDiscordSRV discordSRV, CommandSender commandSender, Supplier<Audience> audience) {
this.discordSRV = discordSRV;
this.commandSender = commandSender;
this.audience = audience;
}
@Override
public boolean hasPermission(String permission) {
return commandSender.hasPermission(permission);
}
@Override
public void runCommand(String command) {
discordSRV.proxy().getPluginManager().dispatchCommand(commandSender, command);
}
@Override
public @NotNull Audience audience() {
return audience.get();
}
}

View File

@ -19,40 +19,22 @@
package com.discordsrv.bungee.console;
import com.discordsrv.bungee.BungeeDiscordSRV;
import com.discordsrv.bungee.command.game.sender.BungeeCommandSender;
import com.discordsrv.common.console.Console;
import com.discordsrv.common.logging.backend.LoggingBackend;
import com.discordsrv.common.logging.backend.impl.JavaLoggerImpl;
import net.kyori.adventure.audience.Audience;
import org.jetbrains.annotations.NotNull;
public class BungeeConsole implements Console {
public class BungeeConsole extends BungeeCommandSender implements Console {
private final BungeeDiscordSRV discordSRV;
private final LoggingBackend loggingBackend;
public BungeeConsole(BungeeDiscordSRV discordSRV) {
this.discordSRV = discordSRV;
super(discordSRV, discordSRV.proxy().getConsole(), () -> discordSRV.audiences().console());
this.loggingBackend = JavaLoggerImpl.getRoot();
}
@Override
public boolean hasPermission(String permission) {
return discordSRV.proxy().getConsole().hasPermission(permission);
}
@Override
public void runCommand(String command) {
discordSRV.proxy().getPluginManager().dispatchCommand(
discordSRV.proxy().getConsole(), command);
}
@Override
public LoggingBackend loggingBackend() {
return loggingBackend;
}
@Override
public @NotNull Audience audience() {
return discordSRV.audiences().console();
}
}

View File

@ -19,37 +19,24 @@
package com.discordsrv.bungee.player;
import com.discordsrv.bungee.BungeeDiscordSRV;
import com.discordsrv.bungee.command.game.sender.BungeeCommandSender;
import com.discordsrv.bungee.component.util.BungeeComponentUtil;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.player.IPlayer;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.identity.Identity;
import net.kyori.adventure.text.Component;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import org.jetbrains.annotations.NotNull;
public class BungeePlayer implements IPlayer {
public class BungeePlayer extends BungeeCommandSender implements IPlayer {
private final BungeeDiscordSRV discordSRV;
private final ProxiedPlayer player;
private final Identity identity;
private final Audience audience;
public BungeePlayer(BungeeDiscordSRV discordSRV, ProxiedPlayer player) {
this.discordSRV = discordSRV;
super(discordSRV, player, () -> discordSRV.audiences().player(player));
this.player = player;
this.identity = Identity.identity(player.getUniqueId());
this.audience = discordSRV.audiences().player(player);
}
@Override
public boolean hasPermission(String permission) {
return player.hasPermission(permission);
}
@Override
public void runCommand(String command) {
discordSRV.proxy().getPluginManager().dispatchCommand(player, command);
}
@Override
@ -59,7 +46,7 @@ public class BungeePlayer implements IPlayer {
@Override
public @NotNull String username() {
return player.getName();
return commandSender.getName();
}
@Override
@ -72,8 +59,4 @@ public class BungeePlayer implements IPlayer {
return BungeeComponentUtil.fromLegacy(player.getDisplayName());
}
@Override
public @NotNull Audience audience() {
return audience;
}
}

View File

@ -64,6 +64,9 @@ dependencies {
runtimeDownloadApi 'dev.vankka:mcdiscordreserializer:4.2.4-SNAPSHOT'
runtimeDownloadApi 'dev.vankka:enhancedlegacytext:1.0.0-SNAPSHOT'
// Brigadier
compileOnlyApi 'com.mojang:brigadier:1.0.18'
// Database
hikari('com.zaxxer:HikariCP:4.0.3') { exclude group: 'org.slf4j' }
h2Driver 'com.h2database:h2:2.1.210'

View File

@ -20,13 +20,16 @@ package com.discordsrv.common;
import com.discordsrv.api.discord.connection.DiscordConnectionDetails;
import com.discordsrv.api.event.bus.EventBus;
import com.discordsrv.api.event.events.lifecycle.DiscordSRVReloadEvent;
import com.discordsrv.api.event.events.lifecycle.DiscordSRVConnectedEvent;
import com.discordsrv.api.event.events.lifecycle.DiscordSRVReadyEvent;
import com.discordsrv.api.event.events.lifecycle.DiscordSRVReloadedEvent;
import com.discordsrv.api.event.events.lifecycle.DiscordSRVShuttingDownEvent;
import com.discordsrv.api.module.type.Module;
import com.discordsrv.common.api.util.ApiInstanceUtil;
import com.discordsrv.common.channel.ChannelConfigHelper;
import com.discordsrv.common.channel.ChannelUpdaterModule;
import com.discordsrv.common.channel.GlobalChannelLookupModule;
import com.discordsrv.common.command.game.GameCommandModule;
import com.discordsrv.common.component.ComponentFactory;
import com.discordsrv.common.config.connection.ConnectionConfig;
import com.discordsrv.common.config.main.LinkedAccountConfig;
@ -45,6 +48,7 @@ import com.discordsrv.common.function.CheckedFunction;
import com.discordsrv.common.function.CheckedRunnable;
import com.discordsrv.common.groupsync.GroupSyncModule;
import com.discordsrv.common.integration.LuckPermsIntegration;
import com.discordsrv.common.invite.DiscordInviteModule;
import com.discordsrv.common.linking.LinkProvider;
import com.discordsrv.common.linking.impl.MemoryLinker;
import com.discordsrv.common.linking.impl.StorageLinker;
@ -69,6 +73,7 @@ import org.jetbrains.annotations.NotNull;
import javax.annotation.OverridingMethodsMustInvokeSuper;
import java.util.Locale;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
@ -96,11 +101,12 @@ public abstract class AbstractDiscordSRV<C extends MainConfig, CC extends Connec
// DiscordSRV
private DiscordSRVLogger logger;
private ModuleManager moduleManager;
private JDAConnectionManager discordConnectionManager;
private ChannelConfigHelper channelConfig;
private Storage storage;
private boolean hikariLoaded = false;
private LinkProvider linkProvider;
private DiscordConnectionManager discordConnectionManager;
// Internal
private final ReentrantLock lifecycleLock = new ReentrantLock();
@ -121,6 +127,7 @@ public abstract class AbstractDiscordSRV<C extends MainConfig, CC extends Connec
this.componentFactory = new ComponentFactory(this);
this.discordAPI = new DiscordAPIImpl(this);
this.discordConnectionDetails = new DiscordConnectionDetailsImpl(this);
this.discordConnectionManager = new JDAConnectionManager(this);
this.channelConfig = new ChannelConfigHelper(this);
}
@ -225,9 +232,7 @@ public abstract class AbstractDiscordSRV<C extends MainConfig, CC extends Connec
@SuppressWarnings("unchecked")
protected <T extends DiscordSRV> void registerModule(CheckedFunction<T, AbstractModule<?>> function) {
try {
registerModule(function.apply((T) this));
} catch (Throwable ignored) {}
moduleManager.registerModule((T) this, function);
}
@Override
@ -249,6 +254,9 @@ public abstract class AbstractDiscordSRV<C extends MainConfig, CC extends Connec
this.status.set(status);
this.status.notifyAll();
}
if (status == Status.CONNECTED) {
eventBus().publish(new DiscordSRVConnectedEvent());
}
}
@Override
@ -299,7 +307,11 @@ public abstract class AbstractDiscordSRV<C extends MainConfig, CC extends Connec
@Override
public final CompletableFuture<Void> invokeEnable() {
return invokeLifecycle(this::enable, "Failed to enable", true);
return invokeLifecycle(() -> {
this.enable();
waitForStatus(Status.CONNECTED);
eventBus().publish(new DiscordSRVReadyEvent());
}, "Failed to enable", true);
}
@Override
@ -308,8 +320,8 @@ public abstract class AbstractDiscordSRV<C extends MainConfig, CC extends Connec
}
@Override
public final CompletableFuture<Void> invokeReload() {
return invoke(this::reload, "Failed to reload", false);
public final CompletableFuture<Void> invokeReload(Set<ReloadFlag> flags, boolean silent) {
return invoke(() -> reload(flags, silent), "Failed to reload", false);
}
@OverridingMethodsMustInvokeSuper
@ -323,76 +335,16 @@ public abstract class AbstractDiscordSRV<C extends MainConfig, CC extends Connec
// Logging
DependencyLoggerAdapter.setAppender(new DependencyLoggingHandler(this));
// Config
try {
connectionConfigManager().load();
configManager().load();
eventBus().publish(new DiscordSRVReloadEvent(true));
} catch (Throwable t) {
setStatus(Status.FAILED_TO_LOAD_CONFIG);
throw t;
}
// Link provider
LinkedAccountConfig linkedAccountConfig = config().linkedAccounts;
if (linkedAccountConfig != null && linkedAccountConfig.enabled) {
String provider = linkedAccountConfig.provider;
switch (provider) {
case "auto":
case "storage":
linkProvider = new StorageLinker(this);
break;
case "memory": {
linkProvider = new MemoryLinker();
logger().warning("Using memory for linked accounts");
logger().warning("Linked accounts will be lost upon restart");
break;
}
default: {
logger().error("Unknown linked account provider: \"" + provider + "\", linked accounts will not be used");
break;
}
}
} else {
logger().info("Linked accounts are disabled");
}
// Storage
try {
try {
StorageType storageType = getStorageType();
logger().info("Using " + storageType.prettyName() + " as storage");
if (storageType.hikari()) {
DependencyLoader.hikari(this).process(classpathAppender()).get();
}
storage = storageType.storageFunction().apply(this);
storage.initialize();
logger().info("Storage connection successfully established");
} catch (ExecutionException e) {
throw new StorageException(e.getCause());
} catch (StorageException e) {
throw e;
} catch (Throwable t) {
throw new StorageException(t);
}
} catch (StorageException e) {
e.log(this);
logger().error("Failed to connect to storage");
setStatus(Status.FAILED_TO_START);
return;
}
discordConnectionManager = new JDAConnectionManager(this);
discordConnectionManager.connect().join();
// Register PlayerProvider listeners
playerProvider().subscribe();
// Placeholder result stringifiers & global contexts
placeholderService().addResultMapper(new ComponentResultStringifier(this));
placeholderService().addGlobalContext(new GlobalTextHandlingContext(this));
// Register PlayerProvider listeners
playerProvider().subscribe();
// Modules
registerModule(ChannelUpdaterModule::new);
registerModule(GameCommandModule::new);
registerModule(GlobalChannelLookupModule::new);
registerModule(DiscordAPIEventModule::new);
registerModule(GroupSyncModule::new);
@ -401,6 +353,14 @@ public abstract class AbstractDiscordSRV<C extends MainConfig, CC extends Connec
registerModule(DiscordMessageMirroringModule::new);
registerModule(JoinMessageModule::new);
registerModule(LeaveMessageModule::new);
registerModule(DiscordInviteModule::new);
// Initial load
try {
invokeReload(ReloadFlag.ALL, true).get();
} catch (ExecutionException e) {
throw e.getCause();
}
}
private StorageType getStorageType() {
@ -425,7 +385,102 @@ public abstract class AbstractDiscordSRV<C extends MainConfig, CC extends Connec
}
@OverridingMethodsMustInvokeSuper
protected void reload() {
protected void reload(Set<ReloadFlag> flags, boolean initial) throws Throwable {
if (!initial) {
logger().info("Reloading DiscordSRV...");
}
if (flags.contains(ReloadFlag.CONFIG)) {
try {
connectionConfigManager().load();
configManager().load();
} catch (Throwable t) {
setStatus(Status.FAILED_TO_LOAD_CONFIG);
throw t;
}
channelConfig().reload();
}
if (flags.contains(ReloadFlag.LINKED_ACCOUNT_PROVIDER)) {
LinkedAccountConfig linkedAccountConfig = config().linkedAccounts;
if (linkedAccountConfig != null && linkedAccountConfig.enabled) {
String provider = linkedAccountConfig.provider;
switch (provider) {
case "auto":
case "storage":
linkProvider = new StorageLinker(this);
logger().info("Using storage for linked accounts");
break;
case "memory": {
linkProvider = new MemoryLinker();
logger().warning("Using memory for linked accounts");
logger().warning("Linked accounts will be lost upon restart");
break;
}
default: {
linkProvider = null;
logger().error("Unknown linked account provider: \"" + provider + "\", linked accounts will not be used");
break;
}
}
} else {
linkProvider = null;
logger().info("Linked accounts are disabled");
}
}
if (flags.contains(ReloadFlag.STORAGE)) {
if (storage != null) {
storage.close();
}
try {
try {
StorageType storageType = getStorageType();
logger().info("Using " + storageType.prettyName() + " as storage");
if (storageType.hikari() && !hikariLoaded) {
hikariLoaded = true;
DependencyLoader.hikari(this).process(classpathAppender()).get();
}
storage = storageType.storageFunction().apply(this);
storage.initialize();
logger().info("Storage connection successfully established");
} catch (ExecutionException e) {
throw new StorageException(e.getCause());
} catch (StorageException e) {
throw e;
} catch (Throwable t) {
throw new StorageException(t);
}
} catch (StorageException e) {
e.log(this);
logger().error("Failed to connect to storage");
setStatus(Status.FAILED_TO_START);
return;
}
}
if (flags.contains(ReloadFlag.DISCORD_CONNECTION)) {
try {
if (discordConnectionManager.instance() != null) {
discordConnectionManager.reconnect().get();
} else {
discordConnectionManager.connect().get();
}
waitForStatus(Status.CONNECTED, 20, TimeUnit.SECONDS);
} catch (ExecutionException e) {
throw e.getCause();
}
}
if (flags.contains(ReloadFlag.MODULES)) {
moduleManager.reload();
}
if (!initial) {
eventBus().publish(new DiscordSRVReloadedEvent(flags));
logger().info("Reload complete.");
}
}
}

View File

@ -21,6 +21,7 @@ package com.discordsrv.common;
import com.discordsrv.api.DiscordSRVApi;
import com.discordsrv.api.module.type.Module;
import com.discordsrv.common.channel.ChannelConfigHelper;
import com.discordsrv.common.command.game.handler.ICommandHandler;
import com.discordsrv.common.component.ComponentFactory;
import com.discordsrv.common.config.connection.ConnectionConfig;
import com.discordsrv.common.config.main.MainConfig;
@ -47,7 +48,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.nio.file.Path;
import java.util.Locale;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
@ -62,6 +63,7 @@ public interface DiscordSRV extends DiscordSRVApi {
PluginManager pluginManager();
OnlineMode onlineMode();
ClasspathAppender classpathAppender();
ICommandHandler commandHandler();
@NotNull AbstractPlayerProvider<?, ?> playerProvider();
// DiscordSRVApi
@ -126,6 +128,6 @@ public interface DiscordSRV extends DiscordSRVApi {
// Lifecycle
CompletableFuture<Void> invokeEnable();
CompletableFuture<Void> invokeDisable();
CompletableFuture<Void> invokeReload();
CompletableFuture<Void> invokeReload(Set<ReloadFlag> flags, boolean silent);
}

View File

@ -22,9 +22,7 @@ import com.discordsrv.api.channel.GameChannel;
import com.discordsrv.api.discord.api.entity.channel.DiscordMessageChannel;
import com.discordsrv.api.discord.api.entity.channel.DiscordTextChannel;
import com.discordsrv.api.discord.api.entity.channel.DiscordThreadChannel;
import com.discordsrv.api.event.bus.Subscribe;
import com.discordsrv.api.event.events.channel.GameChannelLookupEvent;
import com.discordsrv.api.event.events.lifecycle.DiscordSRVReloadEvent;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.config.main.channels.base.BaseChannelConfig;
import com.discordsrv.common.config.main.channels.base.ChannelConfig;
@ -94,16 +92,9 @@ public class ChannelConfigHelper {
}
return map;
});
discordSRV.eventBus().subscribe(this);
}
@Subscribe
public void onReload(DiscordSRVReloadEvent event) {
if (!event.isConfig()) {
return;
}
public void reload() {
Map<Long, Map<String, BaseChannelConfig>> newMap = new HashMap<>();
for (Map.Entry<String, BaseChannelConfig> entry : channels().entrySet()) {
String channelName = entry.getKey();

View File

@ -115,5 +115,4 @@ public class ChannelUpdaterModule extends AbstractModule<DiscordSRV> {
}
}
}

View File

@ -0,0 +1,77 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.command.game;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.command.game.abstraction.GameCommand;
import com.discordsrv.common.command.game.command.DiscordSRVCommand;
import com.discordsrv.common.command.game.command.subcommand.LinkCommand;
import com.discordsrv.common.command.game.handler.ICommandHandler;
import com.discordsrv.common.config.main.CommandConfig;
import com.discordsrv.common.module.type.AbstractModule;
import java.util.HashSet;
import java.util.Set;
public class GameCommandModule extends AbstractModule<DiscordSRV> {
private final Set<GameCommand> commands = new HashSet<>();
private final GameCommand primaryCommand;
private final GameCommand discordAlias;
private final GameCommand linkCommand;
public GameCommandModule(DiscordSRV discordSRV) {
super(discordSRV);
this.primaryCommand = DiscordSRVCommand.get(discordSRV);
this.discordAlias = GameCommand.literal("discord").redirect(primaryCommand);
this.linkCommand = LinkCommand.get(discordSRV);
registerCommand(primaryCommand);
}
@Override
public void reload() {
CommandConfig config = discordSRV.config().command;
if (config == null) {
return;
}
registerCommand(primaryCommand);
if (config.useDiscordCommand) {
registerCommand(discordAlias);
}
if (config.useLinkAlias) {
registerCommand(linkCommand);
}
}
private void registerCommand(GameCommand command) {
ICommandHandler handler = discordSRV.commandHandler();
if (handler == null) {
return;
}
if (!commands.add(command)) {
return;
}
handler.registerCommand(command);
}
}

View File

@ -0,0 +1,473 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.command.game.abstraction;
import com.discordsrv.common.command.game.sender.ICommandSender;
import com.discordsrv.common.function.CheckedFunction;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.format.NamedTextColor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class GameCommand {
private static final GameCommandSuggester BOOLEANS_SUGGESTER = (player, previous, current) -> {
if (current.isEmpty()) {
return Arrays.asList("true", "false");
} else if ("true".startsWith(current)) {
return Collections.singletonList("true");
} else if ("false".startsWith(current)) {
return Collections.singletonList("false");
} else {
return Collections.emptyList();
}
};
public static GameCommand literal(String label) {
return new GameCommand(label, ArgumentType.LITERAL);
}
public static GameCommand booleanArgument(String label) {
return new GameCommand(label, ArgumentType.BOOLEAN).suggester(BOOLEANS_SUGGESTER);
}
public static GameCommand doubleArgument(String label) {
return new GameCommand(label, ArgumentType.DOUBLE);
}
public static GameCommand floatArgument(String label) {
return new GameCommand(label, ArgumentType.FLOAT);
}
public static GameCommand integerArgument(String label) {
return new GameCommand(label, ArgumentType.INTEGER);
}
public static GameCommand longArgument(String label) {
return new GameCommand(label, ArgumentType.LONG);
}
public static GameCommand string(String label) {
return new GameCommand(label, ArgumentType.STRING);
}
public static GameCommand stringWord(String label) {
return new GameCommand(label, ArgumentType.STRING_WORD);
}
public static GameCommand stringGreedy(String label) {
return new GameCommand(label, ArgumentType.STRING_GREEDY);
}
private final ExecutorProxy executorProxy = new ExecutorProxy();
private final SuggesterProxy suggesterProxy = new SuggesterProxy();
// Command info
private GameCommand parent = null;
private final String label;
private final ArgumentType argumentType;
private final List<GameCommand> children;
private GameCommand redirection = null;
// Permission
private String requiredPermission;
private Component noPermissionMessage = null;
// Executor & suggestor
private GameCommandExecutor commandExecutor = null;
private GameCommandSuggester commandSuggester = null;
// Argument type bounds
private double maxValue = Double.MAX_VALUE;
private double minValue = Double.MIN_VALUE;
private GameCommand(String label, ArgumentType argumentType) {
this.label = label;
this.argumentType = argumentType;
this.children = new ArrayList<>();
}
private GameCommand(GameCommand original) {
this.parent = original.parent;
this.label = original.label;
this.argumentType = original.argumentType;
this.children = original.children;
this.redirection = original.redirection;
this.requiredPermission = original.requiredPermission;
this.noPermissionMessage = original.noPermissionMessage;
this.commandExecutor = original.commandExecutor;
this.commandSuggester = original.commandSuggester;
this.maxValue = original.maxValue;
this.minValue = original.minValue;
}
public String getLabel() {
return label;
}
public ArgumentType getArgumentType() {
return argumentType;
}
/**
* Adds a sub command. You can only have multiple literals (with unique labels) or a single non-literal one.
*/
public GameCommand then(GameCommand child) {
if (redirection != null) {
throw new IllegalStateException("Cannot add children to a redirected node");
}
for (GameCommand builder : children) {
if (builder.getArgumentType() == ArgumentType.LITERAL
&& child.getArgumentType() == ArgumentType.LITERAL
&& builder.getLabel().equals(child.getLabel())) {
throw new IllegalArgumentException("Duplicate literal with label \"" + child.label + "\"");
}
if (child.getArgumentType() == ArgumentType.LITERAL && builder.getArgumentType() != ArgumentType.LITERAL) {
throw new IllegalStateException("A non-literal is already present, cannot add literal");
}
if (child.getArgumentType() != ArgumentType.LITERAL) {
throw new IllegalStateException("Cannot add non-literal when another child is already present");
}
}
if (child.getNoPermissionMessage() == null && noPermissionMessage != null) {
child.noPermissionMessage(noPermissionMessage);
}
child.parent = this;
this.children.add(child);
return this;
}
public List<GameCommand> getChildren() {
return children;
}
public GameCommand redirect(GameCommand redirection) {
if (!children.isEmpty()) {
throw new IllegalStateException("Cannot redirect a node with children");
}
if (requiredPermission != null) {
throw new IllegalStateException("Cannot redirect a node with a required permission");
}
this.redirection = redirection;
return this;
}
public GameCommand getRedirection() {
return redirection;
}
public GameCommand requiredPermission(String permission) {
if (redirection != null) {
throw new IllegalStateException("Cannot required permissions on a node with a redirection");
}
this.requiredPermission = permission;
return this;
}
public String getRequiredPermission() {
if (redirection != null) {
return redirection.getRequiredPermission();
}
return requiredPermission;
}
public GameCommand noPermissionMessage(Component noPermissionMessage) {
this.noPermissionMessage = noPermissionMessage;
return this;
}
public Component getNoPermissionMessage() {
return noPermissionMessage;
}
public GameCommand executor(GameCommandExecutor executor) {
this.commandExecutor = executor;
return this;
}
public GameCommandExecutor getExecutor() {
return executorProxy;
}
/**
* Cannot be used with {@link #literal(String)}.
*/
public GameCommand suggester(GameCommandSuggester suggester) {
if (argumentType == ArgumentType.LITERAL) {
throw new IllegalArgumentException("Cannot use on argument type literal");
}
this.commandSuggester = suggester;
return this;
}
public GameCommandSuggester getSuggester() {
return suggesterProxy;
}
/**
* Can only be used on number argument types.
*/
public GameCommand minValue(double minValue) {
mustBeNumber();
this.minValue = minValue;
return this;
}
public double getMinValue() {
return minValue;
}
/**
* Can only be used on number argument types.
*/
public GameCommand maxValue(double maxValue) {
mustBeNumber();
this.maxValue = maxValue;
return this;
}
public double getMaxValue() {
return maxValue;
}
private void mustBeNumber() {
if (!argumentType.number()) {
throw new IllegalArgumentException("Cannot be used on this argument type");
}
}
public boolean hasPermission(ICommandSender sender) {
String requiredPermission = getRequiredPermission();
return requiredPermission == null || sender.hasPermission(requiredPermission);
}
public void sendNoPermission(ICommandSender sender) {
sender.sendMessage(noPermissionMessage != null
? noPermissionMessage
: Component.text("No permission", NamedTextColor.RED));
}
public ArgumentResult checkArgument(String argument) {
switch (getArgumentType()) {
case LITERAL: return ArgumentResult.fromBoolean(argument.equals(getLabel()));
case STRING: {
if (argument.startsWith("\"")) {
boolean single = argument.length() == 1;
if (argument.endsWith("\"") && !single) {
return new ArgumentResult(argument.substring(1, argument.length() - 1), MatchResult.MATCHES);
}
return new ArgumentResult(single ? "" : argument.substring(1), single ? MatchResult.END : MatchResult.CONTINUE);
}
if (argument.endsWith("\"")) {
return new ArgumentResult(argument.substring(0, argument.length() - 1), MatchResult.END);
}
return new ArgumentResult(argument, MatchResult.MATCHES);
}
case STRING_GREEDY: return new ArgumentResult(argument, GameCommand.MatchResult.CONTINUE);
default: {
try {
return new ArgumentResult(getArgumentType().function().apply(argument), MatchResult.MATCHES);
} catch (Throwable ignored) {
return ArgumentResult.NO_MATCH;
}
}
}
}
public String getArgumentLabel() {
if (argumentType == ArgumentType.LITERAL) {
return label;
} else {
return "<" + label + ">";
}
}
public Component describe() {
StringBuilder stringBuilder = new StringBuilder();
GameCommand current = this;
while (current != null) {
stringBuilder.insert(0, current.getArgumentLabel() + " ");
current = current.parent;
}
String command = "/" + stringBuilder.substring(1);
return Component.text(command, NamedTextColor.AQUA);
}
public void sendCommandInstructions(ICommandSender sender) {
if (children.isEmpty()) {
throw new IllegalStateException("No children");
}
TextComponent.Builder builder = Component.text();
builder.append(describe().color(NamedTextColor.GRAY).append(Component.text(" available subcommands:")));
boolean anyAvailable = false;
for (GameCommand child : children) {
if (!child.hasPermission(sender)) {
continue;
}
anyAvailable = true;
builder.append(Component.newline()).append(child.describe());
}
if (!anyAvailable) {
builder.append(Component.newline())
.append(Component.text("No available subcommands", NamedTextColor.RED));
}
sender.sendMessage(builder.build());
}
@SuppressWarnings({"CloneDoesntDeclareCloneNotSupportedException", "MethodDoesntCallSuperMethod"})
@Override
protected GameCommand clone() {
return new GameCommand(this);
}
public static class ArgumentResult {
public static final ArgumentResult EMPTY_MATCH = new ArgumentResult(MatchResult.MATCHES);
public static final ArgumentResult NO_MATCH = new ArgumentResult(MatchResult.NO_MATCH);
public static ArgumentResult fromBoolean(boolean value) {
return value ? EMPTY_MATCH : NO_MATCH;
}
private final Object value;
private final MatchResult result;
private ArgumentResult(MatchResult result) {
this(null, result);
}
public ArgumentResult(Object value, MatchResult result) {
this.value = value;
this.result = result;
}
public Object value() {
return value;
}
public MatchResult result() {
return result;
}
}
public enum MatchResult {
MATCHES,
CONTINUE,
END,
NO_MATCH
}
public enum ArgumentType {
LITERAL(),
BOOLEAN(false, input -> {
boolean value;
if ((value = input.equals("true")) || input.equals("false")) {
return value;
}
throw new IllegalArgumentException();
}),
DOUBLE(true, Double::parseDouble),
FLOAT(true, Float::parseFloat),
INTEGER(true, Integer::parseInt),
LONG(true, Long::parseLong),
STRING_WORD(false, input -> input),
STRING(false, true),
STRING_GREEDY(false, true);
private final boolean number;
private final boolean multi;
private final CheckedFunction<String, Object> function;
ArgumentType() {
this(false, null);
}
ArgumentType(boolean number, CheckedFunction<String, Object> function) {
this(number, false, function);
}
ArgumentType(boolean number, boolean multi) {
this(number, multi, null);
}
ArgumentType(boolean number, boolean multi, CheckedFunction<String, Object> function) {
this.number = number;
this.multi = multi;
this.function = function;
}
public boolean number() {
return number;
}
public boolean multi() {
return multi;
}
public CheckedFunction<String, Object> function() {
return function;
}
}
private class ExecutorProxy implements GameCommandExecutor {
@Override
public void execute(ICommandSender sender, GameCommandArguments arguments) {
if (!hasPermission(sender)) {
sendNoPermission(sender);
return;
}
if (commandExecutor != null) {
commandExecutor.execute(sender, arguments);
} else if (!children.isEmpty()) {
sendCommandInstructions(sender);
} else {
throw new IllegalStateException("Command (" + GameCommand.this + ") doesn't have children and has no executor");
}
}
}
private class SuggesterProxy implements GameCommandSuggester {
@Override
public List<String> suggestValues(
ICommandSender sender,
GameCommandArguments previousArguments,
String currentInput
) {
if (!hasPermission(sender)) {
return Collections.emptyList();
}
if (commandSuggester != null) {
return commandSuggester.suggestValues(sender, previousArguments, currentInput);
} else {
return Collections.emptyList();
}
}
}
}

View File

@ -0,0 +1,29 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.command.game.abstraction;
@FunctionalInterface
public interface GameCommandArguments {
<T> T get(String label, Class<T> type);
default Integer getInt(String label) {
return get(label, Integer.class);
}
}

View File

@ -0,0 +1,27 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.command.game.abstraction;
import com.discordsrv.common.command.game.sender.ICommandSender;
@FunctionalInterface
public interface GameCommandExecutor {
void execute(ICommandSender sender, GameCommandArguments arguments);
}

View File

@ -0,0 +1,30 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.command.game.abstraction;
import com.discordsrv.common.command.game.sender.ICommandSender;
import java.util.List;
@FunctionalInterface
public interface GameCommandSuggester {
List<String> suggestValues(ICommandSender sender, GameCommandArguments previousArguments, String currentInput);
}

View File

@ -0,0 +1,64 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.command.game.command;
import com.discordsrv.api.component.MinecraftComponent;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.command.game.abstraction.GameCommand;
import com.discordsrv.common.command.game.abstraction.GameCommandArguments;
import com.discordsrv.common.command.game.abstraction.GameCommandExecutor;
import com.discordsrv.common.command.game.command.subcommand.LinkCommand;
import com.discordsrv.common.command.game.command.subcommand.ReloadCommand;
import com.discordsrv.common.command.game.command.subcommand.VersionCommand;
import com.discordsrv.common.command.game.sender.ICommandSender;
import com.discordsrv.common.component.util.ComponentUtil;
public class DiscordSRVCommand implements GameCommandExecutor {
private static GameCommand INSTANCE;
public static GameCommand get(DiscordSRV discordSRV) {
if (INSTANCE == null) {
INSTANCE = GameCommand.literal("discordsrv")
.requiredPermission("discordsrv.player.command")
.executor(new DiscordSRVCommand(discordSRV))
.then(LinkCommand.get(discordSRV))
.then(ReloadCommand.get(discordSRV))
.then(VersionCommand.get(discordSRV));
}
return INSTANCE;
}
private final DiscordSRV discordSRV;
public DiscordSRVCommand(DiscordSRV discordSRV) {
this.discordSRV = discordSRV;
}
@Override
public void execute(ICommandSender sender, GameCommandArguments arguments) {
MinecraftComponent component = discordSRV.componentFactory()
.enhancedBuilder(discordSRV.config().command.discordFormat)
.addContext(sender)
.applyPlaceholderService()
.build();
sender.sendMessage(ComponentUtil.fromAPI(component));
}
}

View File

@ -0,0 +1,51 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.command.game.command.subcommand;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.command.game.abstraction.GameCommand;
import com.discordsrv.common.command.game.abstraction.GameCommandArguments;
import com.discordsrv.common.command.game.abstraction.GameCommandExecutor;
import com.discordsrv.common.command.game.sender.ICommandSender;
public class LinkCommand implements GameCommandExecutor {
private static GameCommand INSTANCE;
public static GameCommand get(DiscordSRV discordSRV) {
if (INSTANCE == null) {
INSTANCE = GameCommand.literal("link")
.requiredPermission("discordsrv.player.link")
.executor(new LinkCommand(discordSRV));
}
return INSTANCE;
}
private final DiscordSRV discordSRV;
public LinkCommand(DiscordSRV discordSRV) {
this.discordSRV = discordSRV;
}
@Override
public void execute(ICommandSender sender, GameCommandArguments arguments) {
}
}

View File

@ -0,0 +1,148 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.command.game.command.subcommand;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.command.game.abstraction.GameCommand;
import com.discordsrv.common.command.game.abstraction.GameCommandArguments;
import com.discordsrv.common.command.game.abstraction.GameCommandExecutor;
import com.discordsrv.common.command.game.abstraction.GameCommandSuggester;
import com.discordsrv.common.command.game.sender.ICommandSender;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextDecoration;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
public class ReloadCommand implements GameCommandExecutor, GameCommandSuggester {
private static GameCommand INSTANCE;
public static GameCommand get(DiscordSRV discordSRV) {
if (INSTANCE == null) {
ReloadCommand cmd = new ReloadCommand(discordSRV);
INSTANCE = GameCommand.literal("reload")
.requiredPermission("discordsrv.admin.reload")
.executor(cmd)
.then(
GameCommand.stringGreedy("flags")
.executor(cmd).suggester(cmd)
);
}
return INSTANCE;
}
private final DiscordSRV discordSRV;
public ReloadCommand(DiscordSRV discordSRV) {
this.discordSRV = discordSRV;
}
@Override
public void execute(ICommandSender sender, GameCommandArguments arguments) {
AtomicBoolean dangerousFlags = new AtomicBoolean(false);
Set<DiscordSRV.ReloadFlag> flags = getFlagsFromArguments(sender, arguments, dangerousFlags);
if (flags == null) {
flags = DiscordSRV.ReloadFlag.DEFAULT_FLAGS;
}
if (dangerousFlags.get()) {
sender.sendMessage(Component.text("You can add -confirm to the end of the command if you wish to proceed anyway", NamedTextColor.DARK_RED));
return;
}
if (flags.isEmpty()) {
sender.sendMessage(Component.text("Please specify at least one valid flag", NamedTextColor.RED));
return;
}
discordSRV.invokeReload(flags, false).whenComplete((v, t) -> {
if (t != null) {
discordSRV.logger().error("Failed to reload", t);
sender.sendMessage(
Component.text()
.append(Component.text("Reload failed.", NamedTextColor.DARK_RED, TextDecoration.BOLD))
.append(Component.text("Please check the server console/log for more details."))
);
} else {
sender.sendMessage(Component.text("Reload successful", NamedTextColor.GRAY));
}
});
}
private Set<DiscordSRV.ReloadFlag> getFlagsFromArguments(ICommandSender sender, GameCommandArguments arguments, AtomicBoolean dangerousFlags) {
String argument = null;
try {
argument = arguments.get("flags", String.class);
} catch (IllegalArgumentException ignored) {}
if (argument == null) {
return null;
}
List<String> parts = new ArrayList<>(Arrays.asList(argument.split(" ")));
boolean confirm = parts.remove("-confirm");
Set<DiscordSRV.ReloadFlag> flags = new LinkedHashSet<>();
for (String part : parts) {
try {
DiscordSRV.ReloadFlag flag = DiscordSRV.ReloadFlag.valueOf(part.toUpperCase(Locale.ROOT));
if (flag.requiresConfirm() && !confirm) {
dangerousFlags.set(true);
sender.sendMessage(
Component.text("Reloading ", NamedTextColor.RED)
.append(Component.text(part, NamedTextColor.GRAY))
.append(Component.text(" might cause DiscordSRV to end up in a unrecoverable state", NamedTextColor.RED))
);
continue;
}
flags.add(flag);
} catch (IllegalArgumentException ignored) {
sender.sendMessage(Component.text("Flag ", NamedTextColor.RED)
.append(Component.text(part, NamedTextColor.GRAY))
.append(Component.text(" is not known", NamedTextColor.RED)));
}
}
return flags;
}
@Override
public List<String> suggestValues(
ICommandSender sender,
GameCommandArguments previousArguments,
String currentInput
) {
int lastSpace = currentInput.lastIndexOf(' ') + 1;
String last = currentInput.substring(lastSpace);
String beforeLastSpace = currentInput.substring(0, lastSpace);
List<String> options = DiscordSRV.ReloadFlag.ALL.stream()
.map(flag -> flag.name().toLowerCase(Locale.ROOT))
.filter(flag -> flag.startsWith(last))
.collect(Collectors.toList());
for (String part : currentInput.split(" ")) {
options.remove(part);
}
return options.stream().map(flag -> beforeLastSpace + flag).collect(Collectors.toList());
}
}

View File

@ -0,0 +1,58 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.command.game.command.subcommand;
import com.discordsrv.api.color.Color;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.command.game.abstraction.GameCommandArguments;
import com.discordsrv.common.command.game.abstraction.GameCommand;
import com.discordsrv.common.command.game.abstraction.GameCommandExecutor;
import com.discordsrv.common.command.game.sender.ICommandSender;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextColor;
public class VersionCommand implements GameCommandExecutor {
private static GameCommand INSTANCE;
public static GameCommand get(DiscordSRV discordSRV) {
if (INSTANCE == null) {
INSTANCE = GameCommand.literal("version")
.requiredPermission("discordsrv.admin.version")
.executor(new VersionCommand(discordSRV));
}
return INSTANCE;
}
private final DiscordSRV discordSRV;
public VersionCommand(DiscordSRV discordSRV) {
this.discordSRV = discordSRV;
}
@Override
public void execute(ICommandSender sender, GameCommandArguments arguments) {
sender.sendMessage(
Component.text().content("Running DiscordSRV ").color(TextColor.color(Color.BLURPLE.rgb()))
.append(Component.text("v" + discordSRV.version(), NamedTextColor.GRAY))
);
}
}

View File

@ -0,0 +1,237 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.command.game.handler;
import com.discordsrv.common.command.game.abstraction.GameCommand;
import com.discordsrv.common.command.game.abstraction.GameCommandArguments;
import com.discordsrv.common.command.game.sender.ICommandSender;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextDecoration;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
public class BasicCommandHandler implements ICommandHandler {
private final Map<String, GameCommand> commands = new HashMap<>();
@Override
public void registerCommand(GameCommand command) {
commands.put(command.getLabel(), command);
}
private GameCommand processCommand(
GameCommand command,
List<String> arguments,
Arguments argumentValues,
BiConsumer<GameCommand, Arguments> tooManyArguments,
BiConsumer<GameCommand, Arguments> unclosedQuote,
List<String> literalOptions
) {
GameCommand redirection = command.getRedirection();
if (redirection != null) {
command = redirection;
}
if (!arguments.isEmpty()) {
List<String> currentOptions = new ArrayList<>();
for (GameCommand child : command.getChildren()) {
if (child.getArgumentType() == GameCommand.ArgumentType.LITERAL) {
currentOptions.add(child.getLabel());
}
GameCommand.ArgumentResult result = child.checkArgument(arguments.get(0));
GameCommand.MatchResult matchResult = result.result();
if (matchResult == GameCommand.MatchResult.NO_MATCH) {
continue;
}
arguments.remove(0);
Object value;
if (child.getArgumentType().multi()) {
StringBuilder content = new StringBuilder((String) result.value());
if (matchResult == GameCommand.MatchResult.END) {
// Premature end
if (unclosedQuote != null) {
unclosedQuote.accept(command, argumentValues);
}
return unclosedQuote == null ? command : null;
}
boolean stringStart = matchResult == GameCommand.MatchResult.CONTINUE;
while (stringStart && !arguments.isEmpty()) {
result = child.checkArgument(arguments.remove(0));
Object resultValue = result.value();
content.append(' ').append(resultValue != null ? resultValue : "");
matchResult = result.result();
if (matchResult == GameCommand.MatchResult.END) {
stringStart = false;
}
}
if (stringStart && child.getArgumentType() == GameCommand.ArgumentType.STRING) {
// Unclosed quote
if (unclosedQuote != null) {
unclosedQuote.accept(command, argumentValues);
} else {
argumentValues.put(child.getLabel(), "\"" + content);
}
return unclosedQuote == null ? child : null;
}
value = content.toString();
} else {
value = result.value();
}
argumentValues.put(child.getLabel(), value);
return processCommand(child, arguments, argumentValues, tooManyArguments, unclosedQuote, literalOptions);
}
if (literalOptions != null) {
literalOptions.addAll(currentOptions);
return command;
}
}
if (!arguments.isEmpty() && tooManyArguments != null) {
tooManyArguments.accept(command, argumentValues);
return null;
}
return command;
}
private <T> T useCommand(
String command,
List<String> arguments,
BiConsumer<GameCommand, Arguments> tooManyArguments,
BiConsumer<GameCommand, Arguments> unclosedQuote,
BiFunction<GameCommand, Arguments, T> function,
List<String> literalOptions
) {
command = command.substring(command.lastIndexOf(':') + 1);
GameCommand commandBuilder = commands.get(command);
if (commandBuilder == null) {
return null;
}
Arguments args = new Arguments();
GameCommand subCommand = processCommand(commandBuilder, new ArrayList<>(arguments), args, tooManyArguments, unclosedQuote, literalOptions);
if (subCommand == null) {
return null;
}
return function.apply(subCommand, args);
}
public void execute(ICommandSender sender, String command, List<String> arguments) {
useCommand(
command,
arguments,
(cmd, args) -> {
if (!cmd.hasPermission(sender)) {
cmd.sendNoPermission(sender);
return;
}
error(sender, command, arguments, args, "Incorrect argument for command");
},
(cmd, args) -> {
if (!cmd.hasPermission(sender)) {
cmd.sendNoPermission(sender);
return;
}
error(sender, command, arguments, args, "Unclosed quoted string");
},
(cmd, args) -> {
cmd.getExecutor().execute(sender, args);
return null;
},
null);
}
private void error(ICommandSender sender, String command, List<String> arguments, Arguments args, String title) {
// Mimic brigadier behaviour (en-US)
StringBuilder builder = new StringBuilder(command);
for (Object value : args.getValues().values()) {
builder.append(" ").append(value);
}
int length = builder.length();
String pre = length >= 10 ? "..." + builder.substring(length - 9, length) : builder.toString();
sender.sendMessage(
Component.text()
.append(Component.text(title, NamedTextColor.RED))
.append(Component.newline())
.append(Component.text(pre + " ", NamedTextColor.GRAY))
.append(Component.text(String.join(" ", arguments), NamedTextColor.RED, TextDecoration.UNDERLINED))
.append(Component.text("<--[HERE]", NamedTextColor.RED, TextDecoration.ITALIC))
);
}
public List<String> suggest(ICommandSender sender, String command, List<String> arguments) {
List<String> suggestions = new ArrayList<>();
return useCommand(
command,
arguments,
null,
null,
(cmd, args) -> {
suggestions.addAll(
cmd.getSuggester().suggestValues(sender, args, String.valueOf(args.get(cmd.getLabel(), Object.class)))
.stream()
.map(value -> value.substring(value.lastIndexOf(' ') + 1))
.collect(Collectors.toList())
);
return suggestions;
},
suggestions
);
}
private static class Arguments implements GameCommandArguments {
private final Map<String, Object> values = new LinkedHashMap<>();
@SuppressWarnings("unchecked")
@Override
public <T> T get(String label, Class<T> type) {
return (T) values.get(label);
}
public void put(String label, Object value) {
values.put(label, value);
}
public Map<String, Object> getValues() {
return values;
}
@Override
public String toString() {
return "Arguments{" +
"values=" + values +
'}';
}
}
}

View File

@ -0,0 +1,26 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.command.game.handler;
import com.discordsrv.common.command.game.abstraction.GameCommand;
public interface ICommandHandler {
void registerCommand(GameCommand command);
}

View File

@ -0,0 +1,136 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.command.game.handler.util;
import com.discordsrv.common.command.game.abstraction.GameCommand;
import com.discordsrv.common.command.game.abstraction.GameCommandExecutor;
import com.discordsrv.common.command.game.abstraction.GameCommandSuggester;
import com.discordsrv.common.command.game.sender.ICommandSender;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.arguments.*;
import com.mojang.brigadier.builder.ArgumentBuilder;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import com.mojang.brigadier.tree.CommandNode;
import com.mojang.brigadier.tree.LiteralCommandNode;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
/**
* Helper class to convert DiscordSRV's abstract command tree into a brigadier one.
*/
public final class BrigadierUtil {
private static final Map<GameCommand, CommandNode<?>> CACHE = new ConcurrentHashMap<>();
private BrigadierUtil() {}
public static <S> LiteralCommandNode<S> convertToBrigadier(GameCommand command, Function<S, ICommandSender> commandSenderMapper) {
return (LiteralCommandNode<S>) convert(command, commandSenderMapper);
}
@SuppressWarnings("unchecked")
private static <S> CommandNode<S> convert(GameCommand commandBuilder, Function<S, ICommandSender> commandSenderMapper) {
CommandNode<S> alreadyConverted = (CommandNode<S>) CACHE.get(commandBuilder);
if (alreadyConverted != null) {
return alreadyConverted;
}
GameCommand.ArgumentType type = commandBuilder.getArgumentType();
String label = commandBuilder.getLabel();
GameCommandExecutor executor = commandBuilder.getExecutor();
GameCommandSuggester suggester = commandBuilder.getSuggester();
GameCommand redirection = commandBuilder.getRedirection();
String requiredPermission = commandBuilder.getRequiredPermission();
ArgumentBuilder<S, ?> argumentBuilder;
if (type == GameCommand.ArgumentType.LITERAL) {
argumentBuilder = LiteralArgumentBuilder.literal(label);
} else {
argumentBuilder = RequiredArgumentBuilder.argument(label, convertType(commandBuilder));
}
for (GameCommand child : commandBuilder.getChildren()) {
argumentBuilder.then(convert(child, commandSenderMapper));
}
if (redirection != null) {
CommandNode<S> redirectNode = (CommandNode<S>) CACHE.get(redirection);
if (redirectNode == null) {
redirectNode = convert(redirection, commandSenderMapper);
}
argumentBuilder.redirect(redirectNode);
}
if (requiredPermission != null) {
argumentBuilder.requires(sender -> {
ICommandSender commandSender = commandSenderMapper.apply(sender);
return commandSender.hasPermission(requiredPermission);
});
}
if (executor != null) {
argumentBuilder.executes(context -> {
executor.execute(
commandSenderMapper.apply(context.getSource()),
context::getArgument
);
return Command.SINGLE_SUCCESS;
});
}
if (suggester != null && argumentBuilder instanceof RequiredArgumentBuilder) {
((RequiredArgumentBuilder<S, ?>) argumentBuilder).suggests((context, builder) -> {
try {
List<?> suggestions = suggester.suggestValues(
commandSenderMapper.apply(context.getSource()),
context::getArgument,
builder.getRemaining()
);
suggestions.forEach(suggestion -> builder.suggest(suggestion.toString()));
} catch (Throwable t) {
t.printStackTrace();
}
return CompletableFuture.completedFuture(builder.build());
});
}
CommandNode<S> node = argumentBuilder.build();
CACHE.put(commandBuilder, node);
return node;
}
private static ArgumentType<?> convertType(GameCommand builder) {
GameCommand.ArgumentType argumentType = builder.getArgumentType();
double min = builder.getMinValue();
double max = builder.getMaxValue();
switch (argumentType) {
case LONG: return LongArgumentType.longArg((long) min, (long) max);
case FLOAT: return FloatArgumentType.floatArg((float) min, (float) max);
case DOUBLE: return DoubleArgumentType.doubleArg(min, max);
case STRING: return StringArgumentType.string();
case STRING_WORD: return StringArgumentType.word();
case STRING_GREEDY: return StringArgumentType.greedyString();
case BOOLEAN: return BoolArgumentType.bool();
case INTEGER: return IntegerArgumentType.integer((int) min, (int) max);
}
throw new IllegalStateException();
}
}

View File

@ -0,0 +1,35 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.config.main;
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
import org.spongepowered.configurate.objectmapping.meta.Comment;
@ConfigSerializable
public class CommandConfig {
@Comment("If the /discord command should be set by DiscordSRV")
public boolean useDiscordCommand = true;
@Comment("Use /link as a alias for /discord link")
public boolean useLinkAlias = false;
@Comment("Discord command format, player placeholders may be used")
public String discordFormat = "[click:open_url:%discord_invite%]&b&lClick here &r&ato join our Discord server!";
}

View File

@ -0,0 +1,38 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.config.main;
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
import org.spongepowered.configurate.objectmapping.meta.Comment;
@ConfigSerializable
public class DiscordInviteConfig {
@Comment("Manually enter a invite url here, if this isn't set this is ignored and the options below will take effect")
public String inviteUrl = "";
@Comment("If the bot is only in one Discord server, it will attempt to get it's vanity url")
public boolean attemptToUseVanityUrl = true;
@Comment("If the bot is only in one Discord server, it will attempt to automatically create a invite for it.\n"
+ "The bot will only attempt to do so if it has permission to \"Create Invite\"\n"
+ "The server must also have a rules channel (available for community servers) or default channel (automatically determined by Discord)")
public boolean autoCreateInvite = true;
}

View File

@ -49,4 +49,10 @@ public class MainConfig implements Config {
@Comment("Configuration options for group-role synchronization")
public GroupSyncConfig groupSync = new GroupSyncConfig();
@Comment("Command configuration")
public CommandConfig command = new CommandConfig();
@Comment("The %discord_invite% placeholder configuration. The below options will be attempted in the order they are in")
public DiscordInviteConfig invite = new DiscordInviteConfig();
}

View File

@ -33,5 +33,5 @@ public class MirroringConfig {
@Comment("The format of the username of mirrored messages\n"
+ "It's recommended to include some special character if in-game messages use webhooks,\n"
+ "in order to prevent Discord users and in-game players being grouped together")
public String usernameFormat = "%user_effective_name% [M]";
public String usernameFormat = "%user_effective_name% \uD83D\uDD03";
}

View File

@ -18,6 +18,7 @@
package com.discordsrv.common.dependency;
import com.discordsrv.api.DiscordSRVApi;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.logging.Logger;
import com.discordsrv.common.scheduler.threadfactory.CountingForkJoinWorkerThreadFactory;
@ -91,7 +92,7 @@ public class InitialDependencyLoader {
if (discordSRV == null) {
return;
}
discordSRV.invokeReload();
discordSRV.invokeReload(DiscordSRVApi.ReloadFlag.DEFAULT_FLAGS, false);
}
public void disable(DiscordSRV discordSRV) {

View File

@ -69,7 +69,11 @@ public class GroupSyncModule extends AbstractModule<DiscordSRV> {
@Override
public void reload() {
synchronized (pairs) {
pairs.values().forEach(future -> future.cancel(false));
pairs.values().forEach(future -> {
if (future != null) {
future.cancel(false);
}
});
pairs.clear();
groupsToPairs.clear();
rolesToPairs.clear();

View File

@ -0,0 +1,98 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.invite;
import com.discordsrv.api.placeholder.annotation.Placeholder;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.config.main.DiscordInviteConfig;
import com.discordsrv.common.module.type.AbstractModule;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.TextChannel;
import java.util.List;
public class DiscordInviteModule extends AbstractModule<DiscordSRV> {
private static final String UNKNOWN_INVITE = "https://discord.gg/#Could_not_get_invite,_please_check_your_configuration";
private String invite;
public DiscordInviteModule(DiscordSRV discordSRV) {
super(discordSRV);
discordSRV.placeholderService().addGlobalContext(this);
}
@Override
public void reload() {
JDA jda = discordSRV.jda().orElse(null);
if (jda == null) {
return;
}
DiscordInviteConfig config = discordSRV.config().invite;
// Manual
String invite = config.inviteUrl;
if (invite != null && !invite.isEmpty()) {
this.invite = invite;
return;
}
List<Guild> guilds = jda.getGuilds();
if (guilds.size() != 1) {
return;
}
Guild guild = guilds.get(0);
// Vanity url
if (config.attemptToUseVanityUrl) {
String vanityUrl = guild.getVanityUrl();
if (vanityUrl != null) {
this.invite = vanityUrl;
return;
}
}
// Auto create
if (config.autoCreateInvite) {
Member selfMember = guild.getSelfMember();
if (!selfMember.hasPermission(Permission.CREATE_INSTANT_INVITE)) {
return;
}
TextChannel channel = guild.getRulesChannel();
if (channel == null) {
channel = guild.getDefaultChannel();
}
if (channel == null) {
return;
}
channel.createInvite().setMaxAge(0).setUnique(true).queue(inv -> this.invite = inv.getUrl());
}
}
@Placeholder("discord_invite")
public String getInvite() {
return invite != null ? invite : UNKNOWN_INVITE;
}
}

View File

@ -31,7 +31,6 @@ public class StorageLinker extends CachedLinkProvider implements LinkProvider, L
public StorageLinker(DiscordSRV discordSRV) {
super(discordSRV);
discordSRV.logger().info("Using storage for linked accounts");
}
@Override

View File

@ -20,13 +20,16 @@ package com.discordsrv.common.module;
import com.discordsrv.api.event.bus.EventPriority;
import com.discordsrv.api.event.bus.Subscribe;
import com.discordsrv.api.event.events.lifecycle.DiscordSRVReloadEvent;
import com.discordsrv.api.event.events.lifecycle.DiscordSRVShuttingDownEvent;
import com.discordsrv.api.module.type.Module;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.debug.DebugGenerateEvent;
import com.discordsrv.common.debug.file.TextDebugFile;
import com.discordsrv.common.function.CheckedFunction;
import com.discordsrv.common.logging.Logger;
import com.discordsrv.common.logging.NamedLogger;
import com.discordsrv.common.module.type.AbstractModule;
import com.discordsrv.common.module.type.ModuleDelegate;
import java.util.ArrayList;
import java.util.List;
@ -39,13 +42,20 @@ public class ModuleManager {
private final Set<Module> modules = new CopyOnWriteArraySet<>();
private final Map<String, Module> moduleLookupTable = new ConcurrentHashMap<>();
private final Map<Module, AbstractModule<?>> delegates = new ConcurrentHashMap<>();
private final DiscordSRV discordSRV;
private final Logger logger;
public ModuleManager(DiscordSRV discordSRV) {
this.discordSRV = discordSRV;
this.logger = new NamedLogger(discordSRV, "MODULE_MANAGER");
discordSRV.eventBus().subscribe(this);
}
public Logger logger() {
return logger;
}
@SuppressWarnings("unchecked")
public <T extends Module> T getModule(Class<T> moduleType) {
return (T) moduleLookupTable.computeIfAbsent(moduleType.getName(), key -> {
@ -62,37 +72,66 @@ public class ModuleManager {
});
}
public void register(AbstractModule<?> module) {
private AbstractModule<?> getAbstract(Module module) {
return module instanceof AbstractModule
? (AbstractModule<?>) module
: delegates.computeIfAbsent(module, mod -> new ModuleDelegate(discordSRV, mod));
}
public <DT extends DiscordSRV> void registerModule(DT discordSRV, CheckedFunction<DT, AbstractModule<?>> function) {
try {
register(function.apply(discordSRV));
} catch (Throwable t) {
logger.debug("Module initialization failed", t);
}
}
public void register(Module module) {
if (module instanceof ModuleDelegate) {
throw new IllegalArgumentException("Cannot register a delegate");
}
AbstractModule<?> abstractModule = getAbstract(module);
logger.debug(module + " registered");
this.modules.add(module);
this.moduleLookupTable.put(module.getClass().getName(), module);
enable(module, true);
if (discordSRV.config() != null) {
// Check if config is ready, if it is already we'll enable the module
enable(abstractModule);
}
}
private void enable(Module module, boolean enableNonAbstract) {
private void enable(AbstractModule<?> module) {
try {
if (module instanceof AbstractModule) {
((AbstractModule<?>) module).enableModule();
} else if (enableNonAbstract) {
module.enable();
if (module.enableModule()) {
logger.debug(module + " enabled");
}
} catch (Throwable t) {
discordSRV.logger().error("Failed to enable " + module.getClass().getSimpleName(), t);
discordSRV.logger().error("Failed to enable " + module.toString(), t);
}
}
public void unregister(Module module) {
this.modules.remove(module);
this.moduleLookupTable.values().removeIf(mod -> mod == module);
if (module instanceof ModuleDelegate) {
throw new IllegalArgumentException("Cannot unregister a delegate");
}
disable(module);
this.modules.remove(module);
this.moduleLookupTable.values().removeIf(mod -> mod == module);
this.delegates.remove(module);
}
private void disable(Module module) {
AbstractModule<?> abstractModule = getAbstract(module);
try {
module.disable();
logger.debug(module + " disabling");
abstractModule.disable();
} catch (Throwable t) {
discordSRV.logger().error("Failed to disable " + module.getClass().getSimpleName(), t);
discordSRV.logger().error("Failed to disable " + abstractModule.toString(), t);
}
}
@ -103,14 +142,15 @@ public class ModuleManager {
}
}
@Subscribe(priority = EventPriority.EARLY)
public void onReload(DiscordSRVReloadEvent event) {
public void reload() {
for (Module module : modules) {
AbstractModule<?> abstractModule = getAbstract(module);
// Check if the module needs to be enabled due to reload
enable(module, false);
enable(abstractModule);
try {
module.reload();
abstractModule.reload();
} catch (Throwable t) {
discordSRV.logger().error("Failed to reload " + module.getClass().getSimpleName(), t);
}
@ -124,11 +164,13 @@ public class ModuleManager {
builder.append("Enabled modules:");
List<Module> disabled = new ArrayList<>();
for (Module module : modules) {
if (!module.isEnabled()) {
disabled.add(module);
AbstractModule<?> abstractModule = getAbstract(module);
if (!abstractModule.isEnabled()) {
disabled.add(abstractModule);
continue;
}
appendModule(builder, module);
appendModule(builder, abstractModule);
}
builder.append("\n\nDisabled modules:");

View File

@ -44,9 +44,9 @@ public abstract class AbstractModule<DT extends DiscordSRV> implements Module {
return logger;
}
public final void enableModule() {
public final boolean enableModule() {
if (hasBeenEnabled || !isEnabled()) {
return;
return false;
}
hasBeenEnabled = true;
@ -56,6 +56,12 @@ public abstract class AbstractModule<DT extends DiscordSRV> implements Module {
discordSRV.eventBus().subscribe(this);
// Ignore not having listener methods exception
} catch (IllegalArgumentException ignored) {}
return true;
}
@Override
public String toString() {
return getClass().getName() + "{enabled=" + isEnabled() + "}";
}
// Utility

View File

@ -0,0 +1,59 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.module.type;
import com.discordsrv.api.module.type.Module;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.logging.NamedLogger;
public class ModuleDelegate extends AbstractModule<DiscordSRV> {
private final Module module;
public ModuleDelegate(DiscordSRV discordSRV, Module module) {
super(discordSRV, new NamedLogger(discordSRV, module.getClass().getName()));
this.module = module;
}
@Override
public boolean isEnabled() {
return module.isEnabled();
}
@Override
public void enable() {
module.enable();
}
@Override
public void reload() {
module.reload();
}
@Override
public void disable() {
module.disable();
}
@Override
public String toString() {
String original = super.toString();
return original.substring(0, original.length() - 1) + ",module=" + module.getClass().getName() + "(" + module + ")}";
}
}

View File

@ -18,6 +18,7 @@
package com.discordsrv.common;
import com.discordsrv.common.command.game.handler.ICommandHandler;
import com.discordsrv.common.config.connection.ConnectionConfig;
import com.discordsrv.common.config.main.MainConfig;
import com.discordsrv.common.config.manager.ConnectionConfigManager;
@ -38,6 +39,7 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@SuppressWarnings("ConstantConditions")
public class MockDiscordSRV extends AbstractDiscordSRV<MainConfig, ConnectionConfig> {
public static final MockDiscordSRV INSTANCE = new MockDiscordSRV();
@ -102,6 +104,11 @@ public class MockDiscordSRV extends AbstractDiscordSRV<MainConfig, ConnectionCon
return null;
}
@Override
public ICommandHandler commandHandler() {
return null;
}
@Override
public @NotNull AbstractPlayerProvider<?, ?> playerProvider() {
return null;

View File

@ -19,6 +19,7 @@
package com.discordsrv.config;
import com.discordsrv.common.AbstractDiscordSRV;
import com.discordsrv.common.command.game.handler.ICommandHandler;
import com.discordsrv.common.config.connection.ConnectionConfig;
import com.discordsrv.common.config.main.MainConfig;
import com.discordsrv.common.config.manager.ConnectionConfigManager;
@ -35,6 +36,7 @@ import org.jetbrains.annotations.NotNull;
import java.nio.file.Path;
import java.nio.file.Paths;
@SuppressWarnings("ConstantConditions")
public class MockDiscordSRV extends AbstractDiscordSRV<MainConfig, ConnectionConfig> {
@Override
@ -77,6 +79,11 @@ public class MockDiscordSRV extends AbstractDiscordSRV<MainConfig, ConnectionCon
return null;
}
@Override
public ICommandHandler commandHandler() {
return null;
}
@Override
public @NotNull AbstractPlayerProvider<?, ?> playerProvider() {
return null;

View File

@ -1,5 +1,11 @@
apply from: rootProject.file('buildscript/runtime.gradle')
shadowJar {
archiveFileName = 'sponge.jarinjar'
// Relocations in buildscript/relocations.gradle
}
var spongeVersion = '8.0.0'
allprojects {
repositories {
@ -31,9 +37,3 @@ dependencies {
implementation 'dev.vankka:dependencydownload-jarinjar-bootstrap:' + rootProject.ddVersion
compileOnlyApi project(':sponge:sponge-loader')
}
shadowJar {
archiveFileName = 'sponge.jarinjar'
// Relocations in buildscript/relocations.gradle
}

View File

@ -22,6 +22,7 @@ import com.discordsrv.common.dependency.InitialDependencyLoader;
import com.discordsrv.common.logging.Logger;
import com.discordsrv.common.logging.backend.impl.Log4JLoggerImpl;
import com.discordsrv.sponge.bootstrap.ISpongeBootstrap;
import com.discordsrv.sponge.command.game.handler.SpongeCommandHandler;
import dev.vankka.dependencydownload.classpath.ClasspathAppender;
import dev.vankka.mcdependencydownload.bootstrap.AbstractBootstrap;
import dev.vankka.mcdependencydownload.bootstrap.classpath.JarInJarClasspathAppender;
@ -39,6 +40,7 @@ public class DiscordSRVSpongeBootstrap extends AbstractBootstrap implements ISpo
private final ClasspathAppender classpathAppender;
private final InitialDependencyLoader dependencies;
private SpongeDiscordSRV discordSRV;
private SpongeCommandHandler commandHandler;
private final PluginContainer pluginContainer;
private final Game game;
@ -63,11 +65,14 @@ public class DiscordSRVSpongeBootstrap extends AbstractBootstrap implements ISpo
@Override
public void onConstruct() {
dependencies.load();
this.commandHandler = new SpongeCommandHandler(() -> discordSRV, pluginContainer);
game.eventManager().registerListeners(pluginContainer, commandHandler);
}
@Override
public void onStarted() {
dependencies.enable(() -> this.discordSRV = new SpongeDiscordSRV(logger, classpathAppender, dataDirectory, pluginContainer, game));
dependencies.enable(() -> this.discordSRV = new SpongeDiscordSRV(logger, classpathAppender, dataDirectory, pluginContainer, game, commandHandler));
if (discordSRV != null) {
discordSRV.invokeServerStarted();
}

View File

@ -19,6 +19,7 @@
package com.discordsrv.sponge;
import com.discordsrv.api.DiscordSRVApi;
import com.discordsrv.common.command.game.handler.ICommandHandler;
import com.discordsrv.common.config.connection.ConnectionConfig;
import com.discordsrv.common.config.main.MainConfig;
import com.discordsrv.common.config.manager.ConnectionConfigManager;
@ -27,6 +28,7 @@ import com.discordsrv.common.debug.data.OnlineMode;
import com.discordsrv.common.logging.Logger;
import com.discordsrv.common.plugin.PluginManager;
import com.discordsrv.common.server.ServerDiscordSRV;
import com.discordsrv.sponge.command.game.handler.SpongeCommandHandler;
import com.discordsrv.sponge.console.SpongeConsole;
import com.discordsrv.sponge.player.SpongePlayerProvider;
import com.discordsrv.sponge.plugin.SpongePluginManager;
@ -52,8 +54,16 @@ public class SpongeDiscordSRV extends ServerDiscordSRV<MainConfig, ConnectionCon
private final SpongeConsole console;
private final SpongePlayerProvider playerProvider;
private final SpongePluginManager pluginManager;
private final SpongeCommandHandler commandHandler;
public SpongeDiscordSRV(Logger logger, ClasspathAppender classpathAppender, Path dataDirectory, PluginContainer pluginContainer, Game game) {
public SpongeDiscordSRV(
Logger logger,
ClasspathAppender classpathAppender,
Path dataDirectory,
PluginContainer pluginContainer,
Game game,
SpongeCommandHandler commandHandler
) {
this.logger = logger;
this.classpathAppender = classpathAppender;
this.dataDirectory = dataDirectory;
@ -64,6 +74,7 @@ public class SpongeDiscordSRV extends ServerDiscordSRV<MainConfig, ConnectionCon
this.console = new SpongeConsole(this);
this.playerProvider = new SpongePlayerProvider(this);
this.pluginManager = new SpongePluginManager(this);
this.commandHandler = commandHandler;
load();
}
@ -123,6 +134,11 @@ public class SpongeDiscordSRV extends ServerDiscordSRV<MainConfig, ConnectionCon
return classpathAppender;
}
@Override
public ICommandHandler commandHandler() {
return commandHandler;
}
@Override
public ConnectionConfigManager<ConnectionConfig> connectionConfigManager() {
return null;

View File

@ -0,0 +1,145 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.sponge.command.game.handler;
import com.discordsrv.common.command.game.abstraction.GameCommand;
import com.discordsrv.common.command.game.abstraction.GameCommandArguments;
import com.discordsrv.common.command.game.abstraction.GameCommandExecutor;
import com.discordsrv.common.command.game.handler.ICommandHandler;
import com.discordsrv.common.command.game.sender.ICommandSender;
import com.discordsrv.sponge.SpongeDiscordSRV;
import com.discordsrv.sponge.command.game.sender.SpongeCommandSender;
import net.kyori.adventure.audience.Audience;
import org.spongepowered.api.SystemSubject;
import org.spongepowered.api.command.Command;
import org.spongepowered.api.command.CommandResult;
import org.spongepowered.api.command.parameter.CommandContext;
import org.spongepowered.api.command.parameter.Parameter;
import org.spongepowered.api.command.parameter.managed.Flag;
import org.spongepowered.api.entity.living.player.server.ServerPlayer;
import org.spongepowered.api.event.Listener;
import org.spongepowered.api.event.lifecycle.RegisterCommandEvent;
import org.spongepowered.api.service.permission.Subject;
import org.spongepowered.plugin.PluginContainer;
import java.lang.reflect.Type;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Supplier;
/**
* Sponge has its own api for interacting with Brigadier...
*/
public class SpongeCommandHandler implements ICommandHandler {
private final Map<String, Command.Parameterized> commands = new LinkedHashMap<>();
private final Supplier<SpongeDiscordSRV> discordSRV;
private final PluginContainer container;
public SpongeCommandHandler(Supplier<SpongeDiscordSRV> discordSRV, PluginContainer container) {
this.discordSRV = discordSRV;
this.container = container;
}
@Override
public void registerCommand(GameCommand command) {
commands.put(command.getLabel(), remap(command));
}
private ICommandSender getSender(Subject subject, Audience audience) {
SpongeDiscordSRV discordSRV = this.discordSRV.get();
if (discordSRV != null) {
if (subject instanceof ServerPlayer) {
return discordSRV.playerProvider().player((ServerPlayer) subject);
} else if (subject instanceof SystemSubject) {
return discordSRV.console();
}
}
return new SpongeCommandSender(discordSRV, () -> subject, () -> audience);
}
private ICommandSender getSender(CommandContext context) {
return getSender(context.subject(), context.cause().audience());
}
private Command.Parameterized remap(GameCommand command) {
GameCommand redirection = command.getRedirection();
if (redirection != null) {
command = redirection;
}
//GameCommandSuggester suggester = command.getSuggester();
GameCommandExecutor executor = command.getExecutor();
String permission = command.getRequiredPermission();
Command.Builder builder = Command.builder();
builder.addFlag(Flag.builder().alias(command.getLabel()).build());
if (permission != null) {
builder.permission(permission);
}
for (GameCommand child : command.getChildren()) {
builder.addChild(remap(child));
}
if (executor != null) {
builder.executor(context -> {
executor.execute(getSender(context), new GameCommandArguments() {
@Override
public <T> T get(String label, Class<T> type) {
return context.one(new Parameter.Key<T>() {
@Override
public String key() {
return label;
}
@Override
public Type type() {
return type;
}
@Override
public boolean isInstance(Object value) {
return type.isInstance(value);
}
@SuppressWarnings("unchecked")
@Override
public T cast(Object value) {
return (T) value;
}
}).orElse(null);
}
});
return CommandResult.success();
});
}
return builder.build();
}
@Listener
public void onRegister(RegisterCommandEvent<Command.Parameterized> event) {
for (Map.Entry<String, Command.Parameterized> entry : commands.entrySet()) {
event.register(container, entry.getValue(), entry.getKey());
}
}
}

View File

@ -0,0 +1,59 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.sponge.command.game.sender;
import com.discordsrv.common.command.game.sender.ICommandSender;
import com.discordsrv.sponge.SpongeDiscordSRV;
import net.kyori.adventure.audience.Audience;
import org.jetbrains.annotations.NotNull;
import org.spongepowered.api.command.exception.CommandException;
import org.spongepowered.api.service.permission.Subject;
import java.util.function.Supplier;
public class SpongeCommandSender implements ICommandSender {
protected final SpongeDiscordSRV discordSRV;
protected final Supplier<Subject> subjectSupplier;
protected final Supplier<Audience> audienceSupplier;
public SpongeCommandSender(SpongeDiscordSRV discordSRV, Supplier<Subject> subjectSupplier, Supplier<Audience> audienceSupplier) {
this.discordSRV = discordSRV;
this.subjectSupplier = subjectSupplier;
this.audienceSupplier = audienceSupplier;
}
@Override
public boolean hasPermission(String permission) {
return subjectSupplier.get().hasPermission(permission);
}
@Override
public void runCommand(String command) {
try {
Subject subject = subjectSupplier.get();
discordSRV.game().server().commandManager().process((Subject & Audience) subject, command);
} catch (CommandException ignored) {}
}
@Override
public @NotNull Audience audience() {
return audienceSupplier.get();
}
}

View File

@ -22,40 +22,19 @@ import com.discordsrv.common.console.Console;
import com.discordsrv.common.logging.backend.LoggingBackend;
import com.discordsrv.common.logging.backend.impl.Log4JLoggerImpl;
import com.discordsrv.sponge.SpongeDiscordSRV;
import net.kyori.adventure.audience.Audience;
import org.jetbrains.annotations.NotNull;
import org.spongepowered.api.command.exception.CommandException;
import com.discordsrv.sponge.command.game.sender.SpongeCommandSender;
public class SpongeConsole implements Console {
public class SpongeConsole extends SpongeCommandSender implements Console {
private final SpongeDiscordSRV discordSRV;
private final LoggingBackend loggingBackend;
public SpongeConsole(SpongeDiscordSRV discordSRV) {
this.discordSRV = discordSRV;
super(discordSRV, () -> discordSRV.game().systemSubject(), () -> discordSRV.game().systemSubject());
this.loggingBackend = Log4JLoggerImpl.getRoot();
}
@Override
public boolean hasPermission(String permission) {
return discordSRV.game().systemSubject().hasPermission(permission);
}
@Override
public void runCommand(String command) {
try {
discordSRV.game().server().commandManager().process(
discordSRV.game().systemSubject(), command);
} catch (CommandException ignored) {}
}
@Override
public LoggingBackend loggingBackend() {
return loggingBackend;
}
@Override
public @NotNull Audience audience() {
return discordSRV.game().systemSubject();
}
}

View File

@ -21,31 +21,21 @@ package com.discordsrv.sponge.player;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.player.IPlayer;
import com.discordsrv.sponge.SpongeDiscordSRV;
import net.kyori.adventure.audience.Audience;
import com.discordsrv.sponge.command.game.sender.SpongeCommandSender;
import net.kyori.adventure.identity.Identity;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.NotNull;
import org.spongepowered.api.command.exception.CommandException;
import org.spongepowered.api.entity.living.player.server.ServerPlayer;
public class SpongePlayer extends SpongeOfflinePlayer implements IPlayer {
public class SpongePlayer extends SpongeCommandSender implements IPlayer {
private final ServerPlayer player;
private final Identity identity;
public SpongePlayer(SpongeDiscordSRV discordSRV, ServerPlayer player) {
super(discordSRV, player.user());
super(discordSRV, () -> player, () -> player);
this.player = player;
}
@Override
public boolean hasPermission(String permission) {
return player.hasPermission(permission);
}
@Override
public void runCommand(String command) {
try {
discordSRV.game().server().commandManager().process(player, command);
} catch (CommandException ignored) {}
this.identity = Identity.identity(player.uniqueId());
}
@Override
@ -53,13 +43,18 @@ public class SpongePlayer extends SpongeOfflinePlayer implements IPlayer {
return discordSRV;
}
@Override
public @NotNull String username() {
return player.name();
}
@Override
public @NotNull Component displayName() {
return player.displayName().get();
}
@Override
public @NotNull Audience audience() {
return player;
public @NotNull Identity identity() {
return identity;
}
}

View File

@ -21,7 +21,6 @@ package com.discordsrv.sponge.player;
import com.discordsrv.common.player.IOfflinePlayer;
import com.discordsrv.common.server.player.ServerPlayerProvider;
import com.discordsrv.sponge.SpongeDiscordSRV;
import org.spongepowered.api.entity.living.player.Player;
import org.spongepowered.api.entity.living.player.User;
import org.spongepowered.api.entity.living.player.server.ServerPlayer;
import org.spongepowered.api.event.Listener;
@ -64,7 +63,7 @@ public class SpongePlayerProvider extends ServerPlayerProvider<SpongePlayer, Spo
removePlayer(event.player().uniqueId());
}
public SpongePlayer player(Player player) {
public SpongePlayer player(ServerPlayer player) {
return player(player.uniqueId()).orElseThrow(() -> new IllegalStateException("Player not available"));
}

View File

@ -1,6 +1,12 @@
apply from: rootProject.file('buildscript/standalone.gradle')
apply plugin: 'net.kyori.blossom'
shadowJar {
archiveBaseName = 'DiscordSRV-Velocity'
// Relocations in buildscript/relocations.gradle
}
repositories {
exclusiveContent {
forRepository {
@ -33,12 +39,6 @@ jar {
dependsOn blossomSourceReplacementJava
}
shadowJar {
archiveBaseName = 'DiscordSRV-Velocity'
// Relocations in buildscript/relocations.gradle
}
blossom {
var mainClass = 'src/main/java/com/discordsrv/velocity/DiscordSRVVelocityBootstrap.java'
replaceToken('@VERSION@', project.version, mainClass)

View File

@ -18,6 +18,7 @@
package com.discordsrv.velocity;
import com.discordsrv.common.command.game.handler.ICommandHandler;
import com.discordsrv.common.config.connection.ConnectionConfig;
import com.discordsrv.common.config.main.MainConfig;
import com.discordsrv.common.config.manager.ConnectionConfigManager;
@ -27,6 +28,7 @@ import com.discordsrv.common.logging.Logger;
import com.discordsrv.common.plugin.PluginManager;
import com.discordsrv.common.scheduler.StandardScheduler;
import com.discordsrv.proxy.ProxyDiscordSRV;
import com.discordsrv.velocity.command.game.handler.VelocityCommandHandler;
import com.discordsrv.velocity.console.VelocityConsole;
import com.discordsrv.velocity.player.VelocityPlayerProvider;
import com.discordsrv.velocity.plugin.VelocityPluginManager;
@ -50,6 +52,7 @@ public class VelocityDiscordSRV extends ProxyDiscordSRV<MainConfig, ConnectionCo
private final VelocityConsole console;
private final VelocityPlayerProvider playerProvider;
private final VelocityPluginManager pluginManager;
private final VelocityCommandHandler commandHandler;
public VelocityDiscordSRV(Object plugin, Logger logger, ClasspathAppender classpathAppender, ProxyServer proxyServer, PluginContainer pluginContainer, Path dataDirectory) {
this.plugin = plugin;
@ -63,6 +66,7 @@ public class VelocityDiscordSRV extends ProxyDiscordSRV<MainConfig, ConnectionCo
this.console = new VelocityConsole(this);
this.playerProvider = new VelocityPlayerProvider(this);
this.pluginManager = new VelocityPluginManager(this);
this.commandHandler = new VelocityCommandHandler(this);
load();
}
@ -124,6 +128,11 @@ public class VelocityDiscordSRV extends ProxyDiscordSRV<MainConfig, ConnectionCo
return classpathAppender;
}
@Override
public ICommandHandler commandHandler() {
return commandHandler;
}
@Override
public ConnectionConfigManager<ConnectionConfig> connectionConfigManager() {
return null;

View File

@ -0,0 +1,56 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.velocity.command.game.handler;
import com.discordsrv.common.command.game.abstraction.GameCommand;
import com.discordsrv.common.command.game.handler.ICommandHandler;
import com.discordsrv.common.command.game.handler.util.BrigadierUtil;
import com.discordsrv.common.command.game.sender.ICommandSender;
import com.discordsrv.velocity.VelocityDiscordSRV;
import com.discordsrv.velocity.command.game.sender.VelocityCommandSender;
import com.mojang.brigadier.tree.LiteralCommandNode;
import com.velocitypowered.api.command.BrigadierCommand;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.proxy.ConsoleCommandSource;
import com.velocitypowered.api.proxy.Player;
public class VelocityCommandHandler implements ICommandHandler {
private final VelocityDiscordSRV discordSRV;
public VelocityCommandHandler(VelocityDiscordSRV discordSRV) {
this.discordSRV = discordSRV;
}
private ICommandSender getSender(CommandSource source) {
if (source instanceof Player) {
return discordSRV.playerProvider().player((Player) source);
} else if (source instanceof ConsoleCommandSource) {
return discordSRV.console();
} else {
return new VelocityCommandSender(discordSRV, source);
}
}
@Override
public void registerCommand(GameCommand command) {
LiteralCommandNode<CommandSource> node = BrigadierUtil.convertToBrigadier(command, this::getSender);
discordSRV.proxy().getCommandManager().register(new BrigadierCommand(node));
}
}

View File

@ -0,0 +1,51 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.velocity.command.game.sender;
import com.discordsrv.common.command.game.sender.ICommandSender;
import com.discordsrv.velocity.VelocityDiscordSRV;
import com.velocitypowered.api.command.CommandSource;
import net.kyori.adventure.audience.Audience;
import org.jetbrains.annotations.NotNull;
public class VelocityCommandSender implements ICommandSender {
protected final VelocityDiscordSRV discordSRV;
protected final CommandSource commandSource;
public VelocityCommandSender(VelocityDiscordSRV discordSRV, CommandSource commandSource) {
this.discordSRV = discordSRV;
this.commandSource = commandSource;
}
@Override
public boolean hasPermission(String permission) {
return commandSource.hasPermission(permission);
}
@Override
public void runCommand(String command) {
discordSRV.proxy().getCommandManager().executeAsync(commandSource, command);
}
@Override
public @NotNull Audience audience() {
return commandSource;
}
}

View File

@ -22,37 +22,19 @@ import com.discordsrv.common.console.Console;
import com.discordsrv.common.logging.backend.LoggingBackend;
import com.discordsrv.common.logging.backend.impl.Log4JLoggerImpl;
import com.discordsrv.velocity.VelocityDiscordSRV;
import net.kyori.adventure.audience.Audience;
import org.jetbrains.annotations.NotNull;
import com.discordsrv.velocity.command.game.sender.VelocityCommandSender;
public class VelocityConsole implements Console {
public class VelocityConsole extends VelocityCommandSender implements Console {
private final VelocityDiscordSRV discordSRV;
private final LoggingBackend loggingBackend;
public VelocityConsole(VelocityDiscordSRV discordSRV) {
this.discordSRV = discordSRV;
super(discordSRV, discordSRV.proxy().getConsoleCommandSource());
this.loggingBackend = Log4JLoggerImpl.getRoot();
}
@Override
public boolean hasPermission(String permission) {
return discordSRV.proxy().getConsoleCommandSource().hasPermission(permission);
}
@Override
public void runCommand(String command) {
discordSRV.proxy().getCommandManager().executeAsync(
discordSRV.proxy().getConsoleCommandSource(), command);
}
@Override
public LoggingBackend loggingBackend() {
return loggingBackend;
}
@Override
public @NotNull Audience audience() {
return discordSRV.proxy().getConsoleCommandSource();
}
}

View File

@ -21,32 +21,21 @@ package com.discordsrv.velocity.player;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.player.IPlayer;
import com.discordsrv.velocity.VelocityDiscordSRV;
import com.discordsrv.velocity.command.game.sender.VelocityCommandSender;
import com.velocitypowered.api.proxy.Player;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.identity.Identity;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.NotNull;
public class VelocityPlayer implements IPlayer {
public class VelocityPlayer extends VelocityCommandSender implements IPlayer {
private final VelocityDiscordSRV discordSRV;
private final Player player;
public VelocityPlayer(VelocityDiscordSRV discordSRV, Player player) {
this.discordSRV = discordSRV;
super(discordSRV, player);
this.player = player;
}
@Override
public boolean hasPermission(String permission) {
return player.hasPermission(permission);
}
@Override
public void runCommand(String command) {
discordSRV.proxy().getCommandManager().executeAsync(player, command);
}
@Override
public DiscordSRV discordSRV() {
return discordSRV;
@ -70,9 +59,4 @@ public class VelocityPlayer implements IPlayer {
() -> Component.text(player.getUsername())
);
}
@Override
public @NotNull Audience audience() {
return player;
}
}