public interface VoteHandler {

    void onVoteReceived(Vote vote, VotifierSession.ProtocolVersion protocolVersion, String remoteAddress) throws Exception;

    default void onError(Throwable throwable, boolean voteAlreadyCompleted, String remoteAddress) {
        throw new RuntimeException("Unimplemented onError handler");
    }

} public class Vote {

    /**
     * The name of the vote service.
     */
    private String serviceName;

    /**
     * The username of the voter.
     */
    private String username;

    /**
     * The address of the voter.
     */
    private String address;

    /**
     * The date and time of the vote.
     */
    private String timeStamp;

    private byte[] additionalData;

    @Deprecated
    public Vote() {
    }

    public Vote(String serviceName, String username, String address, String timeStamp) {
        this.serviceName = serviceName;
        this.username = username;
        this.address = address;
        this.timeStamp = timeStamp;
        this.additionalData = null;
    }

    public Vote(String serviceName, String username, String address, String timeStamp, byte[] additionalData) {
        this.serviceName = serviceName;
        this.username = username;
        this.address = address;
        this.timeStamp = timeStamp;
        this.additionalData = additionalData == null ? null : additionalData.clone();
    }

    public Vote(Vote vote) {
        this(vote.getServiceName(), vote.getUsername(), vote.getAddress(), vote.getTimeStamp(),
                vote.getAdditionalData() == null ? null : vote.getAdditionalData().clone());
    }

    private static String getTimestamp(JsonElement object) {
        try {
            return Long.toString(object.getAsLong());
        } catch (Exception e) {
            return object.getAsString();
        }
    }

    public Vote(JsonObject jsonObject) {
        this(jsonObject.get("serviceName").getAsString(),
                jsonObject.get("username").getAsString(),
                jsonObject.get("address").getAsString(),
                getTimestamp(jsonObject.get("timestamp")));
        if (jsonObject.has("additionalData"))
            additionalData = Base64.getDecoder().decode(jsonObject.get("additionalData").getAsString());
    }

    @Override
    public String toString() {
        String data;
        if (additionalData == null)
            data = "null";
        else
            data = Base64.getEncoder().encodeToString(additionalData);

        return "Vote (from:" + serviceName + " username:" + username
                + " address:" + address + " timeStamp:" + timeStamp
                + " additionalData:" + data + ")";
    }

    /**
     * Sets the serviceName.
     *
     * @param serviceName The new serviceName
     */
    @Deprecated
    public void setServiceName(String serviceName) {
        this.serviceName = serviceName;
    }

    /**
     * Gets the serviceName.
     *
     * @return The serviceName
     */
    public String getServiceName() {
        return serviceName;
    }

    /**
     * Sets the username.
     *
     * @param username The new username
     */
    @Deprecated
    public void setUsername(String username) {
        this.username = username;
    }

    /**
     * Gets the username.
     *
     * @return The username
     */
    public String getUsername() {
        return username;
    }

    /**
     * Sets the address.
     *
     * @param address The new address
     */
    @Deprecated
    public void setAddress(String address) {
        this.address = address;
    }

    /**
     * Gets the address.
     *
     * @return The address
     */
    public String getAddress() {
        return address;
    }

    /**
     * Sets the time stamp.
     *
     * @param timeStamp The new time stamp
     */
    @Deprecated
    public void setTimeStamp(String timeStamp) {
        this.timeStamp = timeStamp;
    }

    /**
     * Gets the time stamp.
     *
     * @return The time stamp
     */
    public String getTimeStamp() {
        return timeStamp;
    }

    /**
     * Returns additional data sent with the vote, if it exists.
     *
     * @return Additional data sent with the vote
     */
    public byte[] getAdditionalData() {
        return additionalData == null ? null : additionalData.clone();
    }

    public JsonObject serialize() {
        JsonObject ret = new JsonObject();
        ret.addProperty("serviceName", serviceName);
        ret.addProperty("username", username);
        ret.addProperty("address", address);
        ret.addProperty("timestamp", timeStamp);
        if (additionalData != null)
            ret.addProperty("additionalData", Base64.getEncoder().encodeToString(additionalData));
        return ret;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Vote)) return false;

        Vote vote = (Vote) o;

        if (!serviceName.equals(vote.serviceName)) return false;
        if (!username.equals(vote.username)) return false;
        if (!address.equals(vote.address)) return false;
        if (!timeStamp.equals(vote.timeStamp)) return false;
        return Arrays.equals(additionalData, vote.additionalData);
    }

    @Override
    public int hashCode() {
        int result = serviceName.hashCode();
        result = 31 * result + username.hashCode();
        result = 31 * result + address.hashCode();
        result = 31 * result + timeStamp.hashCode();
        return result;
    }
} public class VotifierServerBootstrap {
    private static final boolean USE_EPOLL = Epoll.isAvailable();

    private final String host;
    private final int port;
    private final EventLoopGroup bossLoopGroup;
    private final EventLoopGroup eventLoopGroup;
    private final VotifierPlugin plugin;
    private final boolean v1Disable;

    private Channel serverChannel;

    public VotifierServerBootstrap(String host, int port, VotifierPlugin plugin, boolean v1Disable) {
        this.host = host;
        this.port = port;
        this.plugin = plugin;
        this.v1Disable = v1Disable;
        if (USE_EPOLL) {
            this.bossLoopGroup = new EpollEventLoopGroup(1, createThreadFactory("Votifier epoll boss"));
            this.eventLoopGroup = new EpollEventLoopGroup(3, createThreadFactory("Votifier epoll worker"));
            plugin.getPluginLogger().info("Using epoll transport to accept votes.");
        } else {
            this.bossLoopGroup = new NioEventLoopGroup(1, createThreadFactory("Votifier NIO boss"));
            this.eventLoopGroup = new NioEventLoopGroup(3, createThreadFactory("Votifier NIO worker"));
            plugin.getPluginLogger().info("Using NIO transport to accept votes.");
        }
    }

    private static ThreadFactory createThreadFactory(String name) {
        return runnable -> {
            FastThreadLocalThread thread = new FastThreadLocalThread(runnable, name);
            thread.setDaemon(true);
            return thread;
        };
    }

    public void start(Consumer<Throwable> error) {
        Objects.requireNonNull(error, "error");

        VoteInboundHandler voteInboundHandler = new VoteInboundHandler(plugin);

        new ServerBootstrap()
                .channel(USE_EPOLL ? EpollServerSocketChannel.class : NioServerSocketChannel.class)
                .group(bossLoopGroup, eventLoopGroup)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel channel) {
                        channel.attr(VotifierSession.KEY).set(new VotifierSession());
                        channel.attr(VotifierPlugin.KEY).set(plugin);
                        channel.pipeline().addLast("greetingHandler", VotifierGreetingHandler.INSTANCE);
                        channel.pipeline().addLast("protocolDifferentiator", new VotifierProtocolDifferentiator(false, !v1Disable));
                        channel.pipeline().addLast("voteHandler", voteInboundHandler);
                    }
                })
                .bind(host, port)
                .addListener((ChannelFutureListener) future -> {
                    if (future.isSuccess()) {
                        serverChannel = future.channel();
                        plugin.getPluginLogger().info("Votifier enabled on socket " + serverChannel.localAddress() + ".");
                        error.accept(null);
                    } else {
                        SocketAddress socketAddress = future.channel().localAddress();
                        if (socketAddress == null) {
                            socketAddress = new InetSocketAddress(host, port);
                        }
                        plugin.getPluginLogger().error("Votifier was not able to bind to " + socketAddress.toString(), future.cause());
                        error.accept(future.cause());
                    }
                });
    }

    private Bootstrap client() {
        return new Bootstrap()
                .channel(USE_EPOLL ? EpollSocketChannel.class : NioSocketChannel.class)
                .group(eventLoopGroup);
    }

    public ProxyForwardingVoteSource createForwardingSource(List<ProxyForwardingVoteSource.BackendServer> backendServers,
                                                            VoteCache voteCache) {
        return new ProxyForwardingVoteSource(plugin, this::client, backendServers, voteCache);
    }

    public void shutdown() {
        if (serverChannel != null) {
            try {
                serverChannel.close().syncUninterruptibly();
            } catch (Exception e) {
                plugin.getPluginLogger().error("Unable to shutdown server channel", e);
            }
        }
        eventLoopGroup.shutdownGracefully();
        bossLoopGroup.shutdownGracefully();

        try {
            bossLoopGroup.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
            eventLoopGroup.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
} public class VotifierSession {
    public static final AttributeKey<VotifierSession> KEY = AttributeKey.valueOf("votifier_session");
    private ProtocolVersion version = ProtocolVersion.UNKNOWN;
    private final String challenge;
    private boolean hasCompletedVote = false;

    public VotifierSession() {
        challenge = TokenUtil.newToken();
    }

    public void setVersion(ProtocolVersion version) {
        if (this.version != ProtocolVersion.UNKNOWN)
            throw new IllegalStateException("Protocol version already switched");

        this.version = version;
    }

    public ProtocolVersion getVersion() {
        return version;
    }

    public String getChallenge() {
        return challenge;
    }

    public void completeVote() {
        if (hasCompletedVote)
            throw new IllegalStateException("Protocol completed vote twice!");

        hasCompletedVote = true;
    }

    public boolean hasCompletedVote() {
        return hasCompletedVote;
    }

    public enum ProtocolVersion {
        UNKNOWN("unknown"),
        ONE("protocol v1"),
        TWO("protocol v2"),
        TEST("test");

        public final String humanReadable;
        ProtocolVersion(String hr) {
            this.humanReadable = hr;
        }
    }
} public class RSAIO {

    /**
     * Saves the key pair to the disk.
     *
     * @param directory The directory to save to
     * @param keyPair   The key pair to save
     * @throws Exception If an error occurs
     */
    public static void save(File directory, KeyPair keyPair) throws Exception {
        PrivateKey privateKey = keyPair.getPrivate();
        PublicKey publicKey = keyPair.getPublic();

        // Store the public and private keys.
        X509EncodedKeySpec publicSpec = new X509EncodedKeySpec(
                publicKey.getEncoded());
        PKCS8EncodedKeySpec privateSpec = new PKCS8EncodedKeySpec(
                privateKey.getEncoded());
        try (FileOutputStream publicOut = new FileOutputStream(directory + "/public.key");
             FileOutputStream privateOut = new FileOutputStream(directory + "/private.key")) {

            publicOut.write(Base64.getEncoder().encode(publicSpec.getEncoded()));
            privateOut.write(Base64.getEncoder().encode(privateSpec.getEncoded()));
        }
    }

    public static byte[] readB64File(File directory, String name) throws IOException {
        File f = new File(directory, name);
        byte[] contents = Files.readAllBytes(f.toPath());
        String strContents = new String(contents, StandardCharsets.US_ASCII);
        strContents = strContents.trim();
        try {
            return Base64.getDecoder().decode(strContents);
        } catch (IllegalArgumentException e) {
            throw new IllegalArgumentException("Base64 decoding exception: This is probably due to a corrupted file, " +
                    "but in case it isn't, here is a b64 representation of what we read: " + new String(Base64.getEncoder().encode(contents), StandardCharsets.UTF_8), e);
        }
    }

    /**
     * Loads an RSA key pair from a directory. The directory must have the files
     * "public.key" and "private.key".
     *
     * @param directory The directory to load from
     * @return The key pair
     * @throws Exception If an error occurs
     */
    public static KeyPair load(File directory) throws Exception {
        // Read the public key file.
        byte[] encodedPublicKey = readB64File(directory, "public.key");

        // Read the private key file.
        byte[] encodedPrivateKey = readB64File(directory, "private.key");

        // Instantiate and return the key pair.
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(
                encodedPublicKey);
        PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
        PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(
                encodedPrivateKey);
        PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);
        return new KeyPair(publicKey, privateKey);
    }

} public class RSAKeygen {
    /**
     * Generates an RSA key pair.
     *
     * @param bits The amount of bits
     * @return The key pair
     */
    public static KeyPair generate(int bits) throws Exception {
        KeyPairGenerator keygen = KeyPairGenerator.getInstance("RSA");
        RSAKeyGenParameterSpec spec = new RSAKeyGenParameterSpec(bits,
                RSAKeyGenParameterSpec.F4);
        keygen.initialize(spec);
        return keygen.generateKeyPair();
    }

} public interface LoggingAdapter {
    void error(String s);
    void error(String s, Object... o);
    void error(String s, Throwable e, Object... o);

    void warn(String s);
    void warn(String s, Object... o);

    void info(String s);
    void info(String s, Object... o);
} public interface VotifierPlugin extends VoteHandler {
    AttributeKey<VotifierPlugin> KEY = AttributeKey.valueOf("votifier_plugin");

    Map<String, Key> getTokens();

    KeyPair getProtocolV1Key();

    LoggingAdapter getPluginLogger();

    VotifierScheduler getScheduler();

    default boolean isDebug() {
        return false;
    }
} public interface VotifierScheduler {
    ScheduledVotifierTask sync(Runnable runnable);

    ScheduledVotifierTask onPool(Runnable runnable);

    ScheduledVotifierTask delayedSync(Runnable runnable, int delay, TimeUnit unit);

    ScheduledVotifierTask delayedOnPool(Runnable runnable, int delay, TimeUnit unit);

    ScheduledVotifierTask repeatOnPool(Runnable runnable, int delay, int repeat, TimeUnit unit);
} public class NVReloadCmd implements CommandExecutor {

    private final NuVotifier plugin;

    public NVReloadCmd(NuVotifier plugin) {
        this.plugin = plugin;
    }

    @Override
    public CommandResult execute(CommandSource src, CommandContext args) throws CommandException {
        src.sendMessage(Text.builder("Reloading NuVotifier...").color(TextColors.GRAY).build());
        if (plugin.reload())
            return CommandResult.success();
        else
            return CommandResult.empty();
    }
} public class TestVoteCmd implements CommandExecutor {

    private final NuVotifier plugin;

    public TestVoteCmd(NuVotifier plugin) {
        this.plugin = plugin;
    }

    @Override
    public CommandResult execute(CommandSource sender, CommandContext args) throws CommandException {
        Vote v;
        try {
            Collection<String> a = args.getAll("args");
            v = ArgsToVote.parse(a.toArray(new String[0]));
        } catch (IllegalArgumentException e) {
            sender.sendMessage(Text.builder("Error while parsing arguments to create test vote: " + e.getMessage()).color(TextColors.DARK_RED).build());
            sender.sendMessage(Text.builder("Usage hint: /testvote [username] [serviceName=?] [username=?] [address=?] [localTimestamp=?] [timestamp=?]").color(TextColors.GRAY).build());
            return CommandResult.empty();
        }

        plugin.onVoteReceived(v, VotifierSession.ProtocolVersion.TEST, "localhost.test");
        return CommandResult.success();
    }
} public class ConfigLoader {

    private static SpongeConfig spongeConfig;

    public static void loadConfig(NuVotifier pl) {
        if (!pl.getConfigDir().exists()) {
            if (!pl.getConfigDir().mkdirs()) {
                throw new RuntimeException("Unable to create the plugin data folder " + pl.getConfigDir());
            }
        }
        try {
            File config = new File(pl.getConfigDir(), "config.yml");
            if (!config.exists() && !config.createNewFile()) {
                throw new IOException("Unable to create the config file at " + config);
            }
            ConfigurationLoader loader = YAMLConfigurationLoader.builder().setFile(config).build();
            ConfigurationNode configNode = loader.load(ConfigurationOptions.defaults().setShouldCopyDefaults(true));
            spongeConfig = configNode.getValue(TypeToken.of(SpongeConfig.class), new SpongeConfig());
            loader.save(configNode);
        } catch (Exception e) {
            pl.getLogger().error("Could not load config.", e);
        }
    }

    public static SpongeConfig getSpongeConfig() {
        return spongeConfig;
    }
} public class VotifierEvent extends AbstractEvent {
    private final Vote vote;
    private final Cause cause;

    public VotifierEvent(Vote vote, Cause cause) {
        this.vote = vote;
        this.cause = cause;
    }

    @Override
    public Cause getCause() {
        return cause;
    }

    public Vote getVote() {
        return vote;
    }
} public class SpongePluginMessagingForwardingSink extends AbstractPluginMessagingForwardingSink implements RawDataListener {

    private final NuVotifier p;
    private final ChannelBinding.RawDataChannel channelBinding;

    public SpongePluginMessagingForwardingSink(NuVotifier p, String channel, ForwardedVoteListener listener) {
        super(listener);

        Optional<ChannelBinding> binding = Sponge.getChannelRegistrar().getChannel(channel);
        if (binding.isPresent()) {
            if (binding.get() instanceof ChannelBinding.RawDataChannel) {
                this.channelBinding = (ChannelBinding.RawDataChannel) binding.get();
            } else {
                throw new IllegalStateException("Found an indexed channel - this is a problem.");
            }
        } else {
            this.channelBinding = Sponge.getChannelRegistrar().createRawChannel(p, channel);
        }

        this.channelBinding.addListener(Platform.Type.SERVER, this);
        this.p = p;
    }

    @Override
    public void halt() {
        channelBinding.removeListener(this);
    }

    @Override
    public void handlePayload(ChannelBuf channelBuf, RemoteConnection remoteConnection, Platform.Type type) {
        byte[] msgDirBuf = channelBuf.readBytes(channelBuf.available());
        try {
            this.handlePluginMessage(msgDirBuf);
        } catch (Exception e) {
            p.getLogger().error("There was an unknown error when processing a forwarded vote.", e);
        }
    }
} public interface ForwardedVoteListener {

    /**
     * Called whenever a vote is forwarded from a {@link ForwardingVoteSink}
     *
     * @param v Vote that was forwarded
     */
    void onForward(Vote v);

} public interface ForwardingVoteSink {

    /**
     * Stop or close any oustanding network interfaces. Occurs on the onDisable method of a plugin.
     */
    void halt();

} public class KeyCreator {
    public static Key createKeyFrom(String token) {
        return new SecretKeySpec(token.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
    }
}
import com.google.inject.Inject;
import com.vexsoftware.votifier.VoteHandler;
import com.vexsoftware.votifier.model.Vote;
import com.vexsoftware.votifier.net.VotifierServerBootstrap;
import com.vexsoftware.votifier.net.VotifierSession;
import com.vexsoftware.votifier.net.protocol.v1crypto.RSAIO;
import com.vexsoftware.votifier.net.protocol.v1crypto.RSAKeygen;
import com.vexsoftware.votifier.platform.LoggingAdapter;
import com.vexsoftware.votifier.platform.VotifierPlugin;
import com.vexsoftware.votifier.platform.scheduler.VotifierScheduler;
import com.vexsoftware.votifier.sponge.cmd.NVReloadCmd;
import com.vexsoftware.votifier.sponge.cmd.TestVoteCmd;
import com.vexsoftware.votifier.sponge.config.ConfigLoader;
import com.vexsoftware.votifier.sponge.event.VotifierEvent;
import com.vexsoftware.votifier.sponge.forwarding.SpongePluginMessagingForwardingSink;
import com.vexsoftware.votifier.support.forwarding.ForwardedVoteListener;
import com.vexsoftware.votifier.support.forwarding.ForwardingVoteSink;
import com.vexsoftware.votifier.util.KeyCreator;
import org.slf4j.Logger;
import org.spongepowered.api.Sponge;
import org.spongepowered.api.command.args.GenericArguments;
import org.spongepowered.api.command.spec.CommandSpec;
import org.spongepowered.api.config.ConfigDir;
import org.spongepowered.api.event.Listener;
import org.spongepowered.api.event.game.GameReloadEvent;
import org.spongepowered.api.event.game.state.GameStartedServerEvent;
import org.spongepowered.api.event.game.state.GameStoppingServerEvent;
import org.spongepowered.api.plugin.Plugin;
import org.spongepowered.api.text.Text;
import java.io.File;
import java.security.Key;
import java.security.KeyPair;
import java.util.HashMap;
import java.util.Map;
package com.vexsoftware.votifier.sponge;



@Plugin(id = "nuvotifier", name = "NuVotifier", version = "@version@", authors = "Ichbinjoe",
        description = "Safe, smart, and secure Votifier server plugin")
public class NuVotifier implements VoteHandler, VotifierPlugin, ForwardedVoteListener {

    @Inject
    public Logger logger;

    private SLF4JLogger loggerAdapter;

    @Inject
    @ConfigDir(sharedRoot = false)
    public File configDir;

    private VotifierScheduler scheduler;

    private boolean loadAndBind() {
        // Load configuration.
        ConfigLoader.loadConfig(this);

        /*
         * Create RSA directory and keys if it does not exist; otherwise, read
         * keys.
         */
        File rsaDirectory = new File(configDir, "rsa");
        try {
            if (!rsaDirectory.exists()) {
                if (!rsaDirectory.mkdir()) {
                    throw new RuntimeException("Unable to create the RSA key folder " + rsaDirectory);
                }
                keyPair = RSAKeygen.generate(2048);
                RSAIO.save(rsaDirectory, keyPair);
            } else {
                keyPair = RSAIO.load(rsaDirectory);
            }
        } catch (Exception ex) {
            logger.error("Error creating or reading RSA tokens", ex);
            return false;
        }

        debug = ConfigLoader.getSpongeConfig().debug;

        // Load Votifier tokens.
        ConfigLoader.getSpongeConfig().tokens.forEach((s, s2) -> {
            tokens.put(s, KeyCreator.createKeyFrom(s2));
            logger.info("Loaded token for website: " + s);
        });

        // Initialize the receiver.
        final String host = ConfigLoader.getSpongeConfig().host;
        final int port = ConfigLoader.getSpongeConfig().port;

        if (!debug)
            logger.info("QUIET mode enabled!");

        if (port >= 0) {
            final boolean disablev1 = ConfigLoader.getSpongeConfig().disableV1Protocol;
            if (disablev1) {
                logger.info("------------------------------------------------------------------------------");
                logger.info("Votifier protocol v1 parsing has been disabled. Most voting websites do not");
                logger.info("currently support the modern Votifier protocol in NuVotifier.");
                logger.info("------------------------------------------------------------------------------");
            }

            this.bootstrap = new VotifierServerBootstrap(host, port, this, disablev1);
            this.bootstrap.start(err -> {
            });
        } else {
            getLogger().info("------------------------------------------------------------------------------");
            getLogger().info("Your Votifier port is less than 0, so we assume you do NOT want to start the");
            getLogger().info("votifier port server! Votifier will not listen for votes over any port, and");
            getLogger().info("will only listen for pluginMessaging forwarded votes!");
            getLogger().info("------------------------------------------------------------------------------");
        }

        if (ConfigLoader.getSpongeConfig().forwarding != null) {
            String method = ConfigLoader.getSpongeConfig().forwarding.method.toLowerCase(); //Default to lower case for case-insensitive searches
            if ("none".equals(method)) {
                getLogger().info("Method none selected for vote forwarding: Votes will not be received from a forwarder.");
            } else if ("pluginmessaging".equals(method)) {
                String channel = ConfigLoader.getSpongeConfig().forwarding.pluginMessaging.channel;
                try {
                    forwardingMethod = new SpongePluginMessagingForwardingSink(this, channel, this);
                    getLogger().info("Receiving votes over PluginMessaging channel '" + channel + "'.");
                } catch (RuntimeException e) {
                    logger.error("NuVotifier could not set up PluginMessaging for vote forwarding!", e);
                }
            } else {
                logger.error("No vote forwarding method '" + method + "' known. Defaulting to noop implementation.");
            }
        }
        return true;
    }

    private void halt() {
        // Shut down the network handlers.
        if (bootstrap != null) {
            bootstrap.shutdown();
            bootstrap = null;
        }

        if (forwardingMethod != null) {
            forwardingMethod.halt();
            forwardingMethod = null;
        }
    }

    public boolean reload() {
        try {
            halt();
        } catch (Exception ex) {
            logger.error("On halt, an exception was thrown. This may be fine!", ex);
        }

        if (loadAndBind()) {
            logger.info("Reload was successful.");
            return true;
        } else {
            try {
                halt();
                logger.error("On reload, there was a problem with the configuration. Votifier currently does nothing!");
            } catch (Exception ex) {
                logger.error("On reload, there was a problem loading, and we could not re-halt the server. Votifier is in an unstable state!", ex);
            }
            return false;
        }
    }

    @Listener
    public void onServerStart(GameStartedServerEvent event) {
        this.scheduler = new SpongeScheduler(this);
        this.loggerAdapter = new SLF4JLogger(logger);

        CommandSpec nvreloadSpec = CommandSpec.builder()
                .description(Text.of("Reloads NuVotifier"))
                .permission("nuvotifier.reload")
                .executor(new NVReloadCmd(this)).build();

        Sponge.getCommandManager().register(this, nvreloadSpec, "nvreload");

        CommandSpec testvoteSpec = CommandSpec.builder()
                .arguments(GenericArguments.allOf(GenericArguments.string(Text.of("args"))))
                .description(Text.of("Sends a test vote to the server's listeners"))
                .permission("nuvotifier.testvote")
                .executor(new TestVoteCmd(this)).build();