Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8380ffce9a | ||
|
|
b9e648bba8 | ||
|
|
c34551e630 | ||
|
|
d931cc2464 | ||
|
|
07f81eb781 | ||
|
|
c0ef61c819 | ||
|
|
09bbfa8629 | ||
|
|
aef038ef3a | ||
|
|
ac3827625d | ||
|
|
acc092f955 | ||
|
|
b494e1dc9d | ||
|
|
27a368bd08 | ||
|
|
3c5b533a44 | ||
|
|
f569c725d0 | ||
|
|
a09bc5a131 | ||
|
|
c4e4f6e416 | ||
|
|
6ed181e835 |
No files matched your search
@@ -46,3 +46,4 @@ debug.txt
|
||||
|
||||
# gradle stuff
|
||||
.gradle/
|
||||
.gradletasknamecache
|
||||
+4
-1
@@ -3,7 +3,7 @@ import org.apache.tools.ant.filters.ReplaceTokens
|
||||
|
||||
allprojects {
|
||||
group 'com.vexsoftware'
|
||||
version '2.3.7'
|
||||
version '2.4.1'
|
||||
}
|
||||
|
||||
subprojects {
|
||||
@@ -13,6 +13,9 @@ subprojects {
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
url 'https://repo.parallelblock.com/repository/maven-public/'
|
||||
}
|
||||
maven {
|
||||
url 'https://hub.spigotmc.org/nexus/content/groups/public/'
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.vexsoftware.votifier;
|
||||
|
||||
import com.vexsoftware.votifier.platform.scheduler.ScheduledVotifierTask;
|
||||
import com.vexsoftware.votifier.platform.scheduler.VotifierScheduler;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
class BukkitScheduler implements VotifierScheduler {
|
||||
private final NuVotifierBukkit plugin;
|
||||
|
||||
public BukkitScheduler(NuVotifierBukkit plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
private int toTicks(int time, TimeUnit unit) {
|
||||
return (int) (unit.toMillis(time) / 50);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask sync(Runnable runnable) {
|
||||
return new BukkitTaskWrapper(plugin.getServer().getScheduler().runTask(plugin, runnable));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask onPool(Runnable runnable) {
|
||||
return new BukkitTaskWrapper(plugin.getServer().getScheduler().runTaskAsynchronously(plugin, runnable));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask delayedSync(Runnable runnable, int delay, TimeUnit unit) {
|
||||
return new BukkitTaskWrapper(plugin.getServer().getScheduler().runTaskLater(plugin, runnable, toTicks(delay, unit)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask delayedOnPool(Runnable runnable, int delay, TimeUnit unit) {
|
||||
return new BukkitTaskWrapper(plugin.getServer().getScheduler().runTaskLaterAsynchronously(plugin, runnable, toTicks(delay, unit)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask repeatOnPool(Runnable runnable, int delay, int repeat, TimeUnit unit) {
|
||||
return new BukkitTaskWrapper(plugin.getServer().getScheduler().runTaskTimerAsynchronously(plugin, runnable, toTicks(delay, unit), toTicks(repeat, unit)));
|
||||
}
|
||||
|
||||
private static class BukkitTaskWrapper implements ScheduledVotifierTask {
|
||||
private final BukkitTask task;
|
||||
|
||||
private BukkitTaskWrapper(BukkitTask task) {
|
||||
this.task = task;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
task.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,31 +25,29 @@ import com.vexsoftware.votifier.forwarding.ForwardedVoteListener;
|
||||
import com.vexsoftware.votifier.forwarding.ForwardingVoteSink;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.model.VotifierEvent;
|
||||
import com.vexsoftware.votifier.net.VotifierServerBootstrap;
|
||||
import com.vexsoftware.votifier.net.VotifierSession;
|
||||
import com.vexsoftware.votifier.net.protocol.VoteInboundHandler;
|
||||
import com.vexsoftware.votifier.net.protocol.VotifierGreetingHandler;
|
||||
import com.vexsoftware.votifier.net.protocol.VotifierProtocolDifferentiator;
|
||||
import com.vexsoftware.votifier.net.protocol.v1crypto.RSAIO;
|
||||
import com.vexsoftware.votifier.net.protocol.v1crypto.RSAKeygen;
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.platform.scheduler.VotifierScheduler;
|
||||
import com.vexsoftware.votifier.util.KeyCreator;
|
||||
import com.vexsoftware.votifier.util.TokenUtil;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.ConsoleCommandSender;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.slf4j.ILoggerFactory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.impl.JDK14LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.Key;
|
||||
import java.security.KeyPair;
|
||||
@@ -65,6 +63,8 @@ import java.util.logging.Level;
|
||||
*/
|
||||
public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, VotifierPlugin, ForwardedVoteListener {
|
||||
|
||||
private static final ILoggerFactory FACTORY = new JDK14LoggerFactory();
|
||||
|
||||
/**
|
||||
* The Votifier instance.
|
||||
*/
|
||||
@@ -76,14 +76,9 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
private String version;
|
||||
|
||||
/**
|
||||
* The server channel.
|
||||
* The server bootstrap.
|
||||
*/
|
||||
private Channel serverChannel;
|
||||
|
||||
/**
|
||||
* The event group handling the channel.
|
||||
*/
|
||||
private NioEventLoopGroup serverGroup;
|
||||
private VotifierServerBootstrap bootstrap;
|
||||
|
||||
/**
|
||||
* The RSA key pair.
|
||||
@@ -101,22 +96,18 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
private Map<String, Key> tokens = new HashMap<>();
|
||||
|
||||
private ForwardingVoteSink forwardingMethod;
|
||||
private VotifierScheduler scheduler;
|
||||
private Logger pluginLogger;
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
NuVotifierBukkit.instance = this;
|
||||
|
||||
// Set the plugin version.
|
||||
version = getDescription().getVersion();
|
||||
|
||||
private boolean loadAndBind() {
|
||||
scheduler = new BukkitScheduler(this);
|
||||
pluginLogger = FACTORY.getLogger(getLogger().getName());
|
||||
if (!getDataFolder().exists()) {
|
||||
getDataFolder().mkdir();
|
||||
}
|
||||
|
||||
// Handle configuration.
|
||||
File config = new File(getDataFolder() + "/config.yml");
|
||||
YamlConfiguration cfg;
|
||||
File rsaDirectory = new File(getDataFolder() + "/rsa");
|
||||
|
||||
/*
|
||||
* Use IP address from server.properties as a default for
|
||||
@@ -143,7 +134,7 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
String cfgStr = new String(ByteStreams.toByteArray(getResource("bukkitConfig.yml")), StandardCharsets.UTF_8);
|
||||
String token = TokenUtil.newToken();
|
||||
cfgStr = cfgStr.replace("%default_token%", token).replace("%ip%", hostAddr);
|
||||
Files.write(cfgStr, config, StandardCharsets.UTF_8);
|
||||
Files.asCharSink(config, StandardCharsets.UTF_8).write(cfgStr);
|
||||
|
||||
/*
|
||||
* Remind hosted server admins to be sure they have the right
|
||||
@@ -161,11 +152,13 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
getLogger().info("------------------------------------------------------------------------------");
|
||||
} catch (Exception ex) {
|
||||
getLogger().log(Level.SEVERE, "Error creating configuration file", ex);
|
||||
gracefulExit();
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
YamlConfiguration cfg;
|
||||
File rsaDirectory = new File(getDataFolder() + "/rsa");
|
||||
|
||||
// Load configuration.
|
||||
cfg = YamlConfiguration.loadConfiguration(config);
|
||||
|
||||
@@ -184,13 +177,12 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
} catch (Exception ex) {
|
||||
getLogger().log(Level.SEVERE,
|
||||
"Error reading configuration file or RSA tokens", ex);
|
||||
gracefulExit();
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
debug = cfg.getBoolean("debug", false);
|
||||
|
||||
// Load Votifier tokens.
|
||||
// Load Votifier tokens.
|
||||
ConfigurationSection tokenSection = cfg.getConfigurationSection("tokens");
|
||||
|
||||
if (tokenSection != null) {
|
||||
@@ -209,8 +201,7 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
} catch (IOException e) {
|
||||
getLogger().log(Level.SEVERE,
|
||||
"Error generating Votifier token", e);
|
||||
gracefulExit();
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
getLogger().info("------------------------------------------------------------------------------");
|
||||
getLogger().info("No tokens were found in your configuration, so we've generated one for you.");
|
||||
@@ -234,37 +225,8 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
getLogger().info("------------------------------------------------------------------------------");
|
||||
}
|
||||
|
||||
serverGroup = new NioEventLoopGroup(1);
|
||||
|
||||
new ServerBootstrap()
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.group(serverGroup)
|
||||
.childHandler(new ChannelInitializer<NioSocketChannel>() {
|
||||
@Override
|
||||
protected void initChannel(NioSocketChannel channel) throws Exception {
|
||||
channel.attr(VotifierSession.KEY).set(new VotifierSession());
|
||||
channel.attr(VotifierPlugin.KEY).set(NuVotifierBukkit.this);
|
||||
channel.pipeline().addLast("greetingHandler", new VotifierGreetingHandler());
|
||||
channel.pipeline().addLast("protocolDifferentiator", new VotifierProtocolDifferentiator(false, !disablev1));
|
||||
channel.pipeline().addLast("voteHandler", new VoteInboundHandler(NuVotifierBukkit.this));
|
||||
}
|
||||
})
|
||||
.bind(host, port)
|
||||
.addListener(new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future) throws Exception {
|
||||
if (future.isSuccess()) {
|
||||
serverChannel = future.channel();
|
||||
getLogger().info("Votifier enabled on socket " + serverChannel.localAddress() + ".");
|
||||
} else {
|
||||
SocketAddress socketAddress = future.channel().localAddress();
|
||||
if (socketAddress == null) {
|
||||
socketAddress = new InetSocketAddress(host, port);
|
||||
}
|
||||
getLogger().log(Level.SEVERE, "Votifier was not able to bind to " + socketAddress, future.cause());
|
||||
}
|
||||
}
|
||||
});
|
||||
this.bootstrap = new VotifierServerBootstrap(host, port, this, disablev1);
|
||||
this.bootstrap.start(error -> {});
|
||||
} else {
|
||||
getLogger().info("------------------------------------------------------------------------------");
|
||||
getLogger().info("Your Votifier port is less than 0, so we assume you do NOT want to start the");
|
||||
@@ -290,22 +252,70 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
getLogger().severe("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;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
NuVotifierBukkit.instance = this;
|
||||
|
||||
// Set the plugin version.
|
||||
version = getDescription().getVersion();
|
||||
|
||||
if (!loadAndBind()) {
|
||||
gracefulExit();
|
||||
setEnabled(false); // safer to just bomb out
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
// Shut down the network handlers.
|
||||
if (serverGroup != null) {
|
||||
if (serverChannel != null)
|
||||
serverChannel.close();
|
||||
serverGroup.shutdownGracefully();
|
||||
}
|
||||
if (forwardingMethod != null) {
|
||||
forwardingMethod.halt();
|
||||
}
|
||||
halt();
|
||||
getLogger().info("Votifier disabled.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (sender instanceof ConsoleCommandSender) {
|
||||
reload();
|
||||
} else {
|
||||
sender.sendMessage(ChatColor.RED + "For security and stability, only console may run this command!");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void reload() {
|
||||
try {
|
||||
halt();
|
||||
} catch (Exception ex) {
|
||||
getLogger().log(Level.SEVERE, "On halt, an exception was thrown. This may be fine!", ex);
|
||||
}
|
||||
|
||||
if (loadAndBind()) {
|
||||
getLogger().info("Reload was successful.");
|
||||
} else {
|
||||
try {
|
||||
halt();
|
||||
getLogger().log(Level.SEVERE, "On reload, there was a problem with the configuration. Votifier currently does nothing!");
|
||||
} catch (Exception ex) {
|
||||
getLogger().log(Level.SEVERE, "On reload, there was a problem loading, and we could not re-halt the server. Votifier is in an unstable state!", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void gracefulExit() {
|
||||
getLogger().log(Level.SEVERE, "Votifier did not initialize properly!");
|
||||
}
|
||||
@@ -319,13 +329,14 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the version.
|
||||
*
|
||||
* @return The version
|
||||
*/
|
||||
public String getVersion() {
|
||||
return version;
|
||||
@Override
|
||||
public Logger getPluginLogger() {
|
||||
return pluginLogger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VotifierScheduler getScheduler() {
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
public boolean isDebug() {
|
||||
@@ -343,7 +354,7 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVoteReceived(Channel channel, final Vote vote, VotifierSession.ProtocolVersion protocolVersion) throws Exception {
|
||||
public void onVoteReceived(Channel channel, final Vote vote, VotifierSession.ProtocolVersion protocolVersion) {
|
||||
if (debug) {
|
||||
if (protocolVersion == VotifierSession.ProtocolVersion.ONE) {
|
||||
getLogger().info("Got a protocol v1 vote record from " + channel.remoteAddress() + " -> " + vote);
|
||||
@@ -351,12 +362,7 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
getLogger().info("Got a protocol v2 vote record from " + channel.remoteAddress() + " -> " + vote);
|
||||
}
|
||||
}
|
||||
Bukkit.getScheduler().runTask(this, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Bukkit.getPluginManager().callEvent(new VotifierEvent(vote));
|
||||
}
|
||||
});
|
||||
Bukkit.getScheduler().runTask(this, () -> Bukkit.getPluginManager().callEvent(new VotifierEvent(vote)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -378,11 +384,6 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
if (debug) {
|
||||
getLogger().info("Got a forwarded vote -> " + v);
|
||||
}
|
||||
Bukkit.getScheduler().runTask(this, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Bukkit.getPluginManager().callEvent(new VotifierEvent(v));
|
||||
}
|
||||
});
|
||||
Bukkit.getScheduler().runTask(this, () -> Bukkit.getPluginManager().callEvent(new VotifierEvent(v)));
|
||||
}
|
||||
}
|
||||
@@ -26,4 +26,4 @@ forwarding:
|
||||
# - pluginMessaging - Sets up plugin messaging
|
||||
method: none
|
||||
pluginMessaging:
|
||||
channel: NuVotifier
|
||||
channel: nuvotifier:votes
|
||||
@@ -2,4 +2,12 @@ name: Votifier
|
||||
main: com.vexsoftware.votifier.NuVotifierBukkit
|
||||
version: @app.version@
|
||||
description: A plugin that gets notified when votes are made for the server on toplists.
|
||||
authors: [ParallelBlock, blakeman8192, Kramer, tuxed]
|
||||
authors: [ParallelBlock, blakeman8192, Kramer, tuxed]
|
||||
api-version: 1.13
|
||||
|
||||
commands:
|
||||
nvreload:
|
||||
description: Reloads the NuVotifier configuration
|
||||
permission: nuvotifier.console
|
||||
permission-message: "You do not have permission to run this command."
|
||||
usage: "/nvreload"
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.vexsoftware.votifier.bungee;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.vexsoftware.votifier.platform.BackendServer;
|
||||
import net.md_5.bungee.api.config.ServerInfo;
|
||||
|
||||
public class BungeeBackendServer implements BackendServer {
|
||||
private final ServerInfo info;
|
||||
|
||||
public BungeeBackendServer(ServerInfo info) {
|
||||
this.info = Preconditions.checkNotNull(info, "info");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return info.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sendPluginMessage(String channel, byte[] data) {
|
||||
return info.sendData(channel, data, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return info.getName();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.vexsoftware.votifier.bungee;
|
||||
|
||||
import com.vexsoftware.votifier.platform.scheduler.ScheduledVotifierTask;
|
||||
import com.vexsoftware.votifier.platform.scheduler.VotifierScheduler;
|
||||
import net.md_5.bungee.api.scheduler.ScheduledTask;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
class BungeeScheduler implements VotifierScheduler {
|
||||
private final NuVotifier plugin;
|
||||
|
||||
public BungeeScheduler(NuVotifier plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask sync(Runnable runnable) {
|
||||
return onPool(runnable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask onPool(Runnable runnable) {
|
||||
return new BungeeTaskWrapper(plugin.getProxy().getScheduler().runAsync(plugin, runnable));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask delayedSync(Runnable runnable, int delay, TimeUnit unit) {
|
||||
return delayedOnPool(runnable, delay, unit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask delayedOnPool(Runnable runnable, int delay, TimeUnit unit) {
|
||||
return new BungeeTaskWrapper(plugin.getProxy().getScheduler().schedule(plugin, runnable, delay, unit));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask repeatOnPool(Runnable runnable, int delay, int repeat, TimeUnit unit) {
|
||||
return new BungeeTaskWrapper(plugin.getProxy().getScheduler().schedule(plugin, runnable, delay, repeat, unit));
|
||||
}
|
||||
|
||||
private static class BungeeTaskWrapper implements ScheduledVotifierTask {
|
||||
private final ScheduledTask task;
|
||||
|
||||
private BungeeTaskWrapper(ScheduledTask task) {
|
||||
this.task = task;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
this.task.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,67 +4,57 @@ import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.google.common.io.Files;
|
||||
import com.vexsoftware.votifier.VoteHandler;
|
||||
import com.vexsoftware.votifier.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.net.VotifierServerBootstrap;
|
||||
import com.vexsoftware.votifier.platform.BackendServer;
|
||||
import com.vexsoftware.votifier.platform.ProxyVotifierPlugin;
|
||||
import com.vexsoftware.votifier.bungee.events.VotifierEvent;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.ForwardingVoteSource;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.OnlineForwardPluginMessagingForwardingSource;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.PluginMessagingForwardingSource;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.cache.FileVoteCache;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.cache.MemoryVoteCache;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.cache.VoteCache;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.proxy.ProxyForwardingVoteSource;
|
||||
import com.vexsoftware.votifier.platform.scheduler.VotifierScheduler;
|
||||
import com.vexsoftware.votifier.support.forwarding.ForwardingVoteSource;
|
||||
import com.vexsoftware.votifier.support.forwarding.cache.FileVoteCache;
|
||||
import com.vexsoftware.votifier.support.forwarding.cache.MemoryVoteCache;
|
||||
import com.vexsoftware.votifier.support.forwarding.cache.VoteCache;
|
||||
import com.vexsoftware.votifier.support.forwarding.proxy.ProxyForwardingVoteSource;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.VotifierSession;
|
||||
import com.vexsoftware.votifier.net.protocol.VoteInboundHandler;
|
||||
import com.vexsoftware.votifier.net.protocol.VotifierGreetingHandler;
|
||||
import com.vexsoftware.votifier.net.protocol.VotifierProtocolDifferentiator;
|
||||
import com.vexsoftware.votifier.net.protocol.v1crypto.RSAIO;
|
||||
import com.vexsoftware.votifier.net.protocol.v1crypto.RSAKeygen;
|
||||
import com.vexsoftware.votifier.util.KeyCreator;
|
||||
import com.vexsoftware.votifier.util.TokenUtil;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.config.ServerInfo;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.plugin.Command;
|
||||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
import net.md_5.bungee.config.Configuration;
|
||||
import net.md_5.bungee.config.ConfigurationProvider;
|
||||
import net.md_5.bungee.config.YamlConfiguration;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.impl.DirtyTricks;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.Key;
|
||||
import java.security.KeyPair;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.FutureTask;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class NuVotifier extends Plugin implements VoteHandler, VotifierPlugin {
|
||||
public class NuVotifier extends Plugin implements VoteHandler, ProxyVotifierPlugin {
|
||||
|
||||
/**
|
||||
* The server channel.
|
||||
*/
|
||||
private Channel serverChannel;
|
||||
|
||||
/**
|
||||
* The event group handling the channel.
|
||||
*/
|
||||
private NioEventLoopGroup serverGroup;
|
||||
private VotifierServerBootstrap bootstrap;
|
||||
|
||||
/**
|
||||
* The RSA key pair.
|
||||
@@ -86,8 +76,12 @@ public class NuVotifier extends Plugin implements VoteHandler, VotifierPlugin {
|
||||
*/
|
||||
private ForwardingVoteSource forwardingMethod;
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
private VotifierScheduler scheduler;
|
||||
private Logger pluginLogger;
|
||||
|
||||
private void loadAndBind() {
|
||||
scheduler = new BungeeScheduler(this);
|
||||
pluginLogger = DirtyTricks.getLogger(getLogger());
|
||||
if (!getDataFolder().exists()) {
|
||||
getDataFolder().mkdir();
|
||||
}
|
||||
@@ -108,7 +102,7 @@ public class NuVotifier extends Plugin implements VoteHandler, VotifierPlugin {
|
||||
String cfgStr = new String(ByteStreams.toByteArray(getResourceAsStream("bungeeConfig.yml")), StandardCharsets.UTF_8);
|
||||
String token = TokenUtil.newToken();
|
||||
cfgStr = cfgStr.replace("%default_token%", token);
|
||||
Files.write(cfgStr, config, StandardCharsets.UTF_8);
|
||||
Files.asCharSink(config, StandardCharsets.UTF_8).write(cfgStr);
|
||||
|
||||
/*
|
||||
* Remind hosted server admins to be sure they have the right
|
||||
@@ -197,41 +191,9 @@ public class NuVotifier extends Plugin implements VoteHandler, VotifierPlugin {
|
||||
}
|
||||
|
||||
// Must set up server asynchronously due to BungeeCord goofiness.
|
||||
FutureTask<?> initTask = new FutureTask<>(Executors.callable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
serverGroup = new NioEventLoopGroup(2);
|
||||
|
||||
new ServerBootstrap()
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.group(serverGroup)
|
||||
.childHandler(new ChannelInitializer<NioSocketChannel>() {
|
||||
@Override
|
||||
protected void initChannel(NioSocketChannel channel) throws Exception {
|
||||
channel.attr(VotifierSession.KEY).set(new VotifierSession());
|
||||
channel.attr(VotifierPlugin.KEY).set(NuVotifier.this);
|
||||
channel.pipeline().addLast("greetingHandler", new VotifierGreetingHandler());
|
||||
channel.pipeline().addLast("protocolDifferentiator", new VotifierProtocolDifferentiator(false, !disablev1));
|
||||
channel.pipeline().addLast("voteHandler", new VoteInboundHandler(NuVotifier.this));
|
||||
}
|
||||
})
|
||||
.bind(host, port)
|
||||
.addListener(new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future) throws Exception {
|
||||
if (future.isSuccess()) {
|
||||
serverChannel = future.channel();
|
||||
getLogger().info("Votifier enabled on socket " + serverChannel.localAddress() + ".");
|
||||
} else {
|
||||
SocketAddress socketAddress = future.channel().localAddress();
|
||||
if (socketAddress == null) {
|
||||
socketAddress = new InetSocketAddress(host, port);
|
||||
}
|
||||
getLogger().log(Level.SEVERE, "Votifier was not able to bind to " + socketAddress, future.cause());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
FutureTask<?> initTask = new FutureTask<>(Executors.callable(() -> {
|
||||
this.bootstrap = new VotifierServerBootstrap(host, port, NuVotifier.this, disablev1);
|
||||
this.bootstrap.start(err -> {});
|
||||
}));
|
||||
getProxy().getScheduler().runAsync(this, initTask);
|
||||
try {
|
||||
@@ -312,30 +274,70 @@ public class NuVotifier extends Plugin implements VoteHandler, VotifierPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
forwardingMethod = new ProxyForwardingVoteSource(this, serverGroup, serverList, null);
|
||||
forwardingMethod = new ProxyForwardingVoteSource(this, bootstrap::client, serverList, null);
|
||||
getLogger().info("Forwarding votes from this NuVotifier instance to another NuVotifier server.");
|
||||
} else {
|
||||
getLogger().severe("No vote forwarding method '" + fwdMethod + "' known. Defaulting to noop implementation.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
public void onEnable() {
|
||||
loadAndBind();
|
||||
|
||||
getProxy().getPluginManager().registerCommand(this, new Command("nvreload") {
|
||||
@Override
|
||||
public void execute(CommandSender sender, String[] args) {
|
||||
if (sender instanceof ProxiedPlayer)
|
||||
sender.sendMessage(ChatColor.RED + "For security and stability, only console may run this command!");
|
||||
else
|
||||
reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void halt() {
|
||||
// Shut down the network handlers.
|
||||
if (serverChannel != null)
|
||||
serverChannel.close();
|
||||
serverGroup.shutdownGracefully();
|
||||
if (bootstrap != null) {
|
||||
bootstrap.shutdown();
|
||||
bootstrap = null;
|
||||
}
|
||||
|
||||
if (forwardingMethod != null) {
|
||||
forwardingMethod.halt();
|
||||
forwardingMethod = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void reload() {
|
||||
try {
|
||||
halt();
|
||||
} catch (Exception ex) {
|
||||
getLogger().log(Level.SEVERE, "On halt, an exception was thrown. This may be fine!", ex);
|
||||
}
|
||||
|
||||
try {
|
||||
loadAndBind();
|
||||
getLogger().info("Reload was successful.");
|
||||
} catch (Exception ex) {
|
||||
try {
|
||||
halt();
|
||||
getLogger().log(Level.SEVERE, "On reload, there was a problem with the configuration. Votifier currently does nothing!", ex);
|
||||
} catch (Exception ex2) {
|
||||
getLogger().log(Level.SEVERE, "On reload, there was a problem loading, and we could not re-halt the server. Votifier is in an unstable state!", ex);
|
||||
getLogger().log(Level.SEVERE, "(halt exception)", ex2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
halt();
|
||||
getLogger().info("Votifier disabled.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVoteReceived(Channel channel, final Vote vote, VotifierSession.ProtocolVersion protocolVersion) throws Exception {
|
||||
public void onVoteReceived(Channel channel, final Vote vote, VotifierSession.ProtocolVersion protocolVersion) {
|
||||
if (debug) {
|
||||
if (protocolVersion == VotifierSession.ProtocolVersion.ONE) {
|
||||
getLogger().info("Got a protocol v1 vote record from " + channel.remoteAddress() + " -> " + vote);
|
||||
@@ -344,20 +346,10 @@ public class NuVotifier extends Plugin implements VoteHandler, VotifierPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
getProxy().getScheduler().runAsync(this, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
getProxy().getPluginManager().callEvent(new VotifierEvent(vote));
|
||||
}
|
||||
});
|
||||
getProxy().getScheduler().runAsync(this, () -> getProxy().getPluginManager().callEvent(new VotifierEvent(vote)));
|
||||
|
||||
if (forwardingMethod != null) {
|
||||
getProxy().getScheduler().runAsync(this, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
forwardingMethod.forward(vote);
|
||||
}
|
||||
});
|
||||
getProxy().getScheduler().runAsync(this, () -> forwardingMethod.forward(vote));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -386,11 +378,29 @@ public class NuVotifier extends Plugin implements VoteHandler, VotifierPlugin {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVersion() {
|
||||
return getDescription().getVersion();
|
||||
public Logger getPluginLogger() {
|
||||
return pluginLogger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VotifierScheduler getScheduler() {
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
public boolean isDebug() {
|
||||
return debug;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<BackendServer> getAllBackendServers() {
|
||||
return getProxy().getServers().values().stream()
|
||||
.map(BungeeBackendServer::new)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<BackendServer> getServer(String name) {
|
||||
ServerInfo info = getProxy().getServerInfo(name);
|
||||
return Optional.ofNullable(info).map(BungeeBackendServer::new);
|
||||
}
|
||||
}
|
||||
+19
-20
@@ -1,7 +1,8 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding;
|
||||
package com.vexsoftware.votifier.bungee;
|
||||
|
||||
import com.vexsoftware.votifier.bungee.NuVotifier;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.cache.VoteCache;
|
||||
import com.vexsoftware.votifier.platform.BackendServer;
|
||||
import com.vexsoftware.votifier.support.forwarding.AbstractPluginMessagingForwardingSource;
|
||||
import com.vexsoftware.votifier.support.forwarding.cache.VoteCache;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.config.ServerInfo;
|
||||
@@ -27,20 +28,21 @@ public final class OnlineForwardPluginMessagingForwardingSource extends Abstract
|
||||
@Override
|
||||
public void forward(Vote v) {
|
||||
ProxiedPlayer p = ProxyServer.getInstance().getPlayer(v.getUsername());
|
||||
if (p == null || !forwardSpecific(p.getServer().getInfo(), v)) {
|
||||
ServerInfo serverInfo = ProxyServer.getInstance().getServers().get(fallbackServer);
|
||||
|
||||
// nowhere to fall back to, yet still not online. lets save this vote yet!
|
||||
if (serverInfo == null)
|
||||
attempToAddToPlayerCache(v, v.getUsername());
|
||||
|
||||
else if (!forwardSpecific(serverInfo, v))
|
||||
attemptToAddToCache(v, fallbackServer);
|
||||
if (p != null && p.getServer() != null) {
|
||||
if (forwardSpecific(new BungeeBackendServer(p.getServer().getInfo()), v)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ServerInfo serverInfo = ProxyServer.getInstance().getServers().get(fallbackServer);
|
||||
|
||||
// nowhere to fall back to, yet still not online. lets save this vote yet!
|
||||
if (serverInfo == null)
|
||||
attemptToAddToPlayerCache(v, v.getUsername());
|
||||
else if (!forwardSpecific(new BungeeBackendServer(serverInfo), v))
|
||||
attemptToAddToCache(v, fallbackServer);
|
||||
}
|
||||
|
||||
// The below is to allow us to share code since the event handlers from the parent class don't get
|
||||
// picked up.
|
||||
@EventHandler
|
||||
public void onPluginMessage(PluginMessageEvent e) {
|
||||
if (e.getTag().equals(channel)) e.setCancelled(true);
|
||||
@@ -48,11 +50,8 @@ public final class OnlineForwardPluginMessagingForwardingSource extends Abstract
|
||||
|
||||
@EventHandler
|
||||
public void onServerConnected(final ServerConnectedEvent e) { //Attempt to resend any votes that were previously cached.
|
||||
handleServerConnected(e);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerSwitch(ServerConnectedEvent e) {
|
||||
handlePlayerSwitch(e);
|
||||
BackendServer server = new BungeeBackendServer(e.getServer().getInfo());
|
||||
handlePlayerSwitch(server, e.getPlayer().getName());
|
||||
onServerConnect(server);
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -1,7 +1,7 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding;
|
||||
package com.vexsoftware.votifier.bungee;
|
||||
|
||||
import com.vexsoftware.votifier.bungee.NuVotifier;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.cache.VoteCache;
|
||||
import com.vexsoftware.votifier.support.forwarding.AbstractPluginMessagingForwardingSource;
|
||||
import com.vexsoftware.votifier.support.forwarding.cache.VoteCache;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.event.PluginMessageEvent;
|
||||
import net.md_5.bungee.api.event.ServerConnectedEvent;
|
||||
@@ -29,6 +29,6 @@ public class PluginMessagingForwardingSource extends AbstractPluginMessagingForw
|
||||
|
||||
@EventHandler
|
||||
public void onServerConnected(final ServerConnectedEvent e) { //Attempt to resend any votes that were previously cached.
|
||||
handleServerConnected(e);
|
||||
onServerConnect(new BungeeBackendServer(e.getServer().getInfo()));
|
||||
}
|
||||
}
|
||||
-141
@@ -1,141 +0,0 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding;
|
||||
|
||||
import com.vexsoftware.votifier.bungee.NuVotifier;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.cache.FileVoteCache;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.cache.VoteCache;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.config.ServerInfo;
|
||||
import net.md_5.bungee.api.event.PluginMessageEvent;
|
||||
import net.md_5.bungee.api.event.ServerConnectedEvent;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public abstract class AbstractPluginMessagingForwardingSource implements ForwardingVoteSource {
|
||||
|
||||
public AbstractPluginMessagingForwardingSource(String channel, List<String> ignoredServers, NuVotifier nuVotifier, VoteCache cache) {
|
||||
ProxyServer.getInstance().registerChannel(channel);
|
||||
this.channel = channel;
|
||||
this.nuVotifier = nuVotifier;
|
||||
this.cache = cache;
|
||||
this.ignoredServers = ignoredServers;
|
||||
}
|
||||
|
||||
protected AbstractPluginMessagingForwardingSource(String channel, NuVotifier nuVotifier, VoteCache voteCache) {
|
||||
this(channel, null, nuVotifier, voteCache);
|
||||
}
|
||||
|
||||
protected final NuVotifier nuVotifier;
|
||||
protected final String channel;
|
||||
protected final VoteCache cache;
|
||||
protected final List<String> ignoredServers;
|
||||
|
||||
@Override
|
||||
public void forward(Vote v) {
|
||||
byte[] rawData = v.serialize().toString().getBytes(StandardCharsets.UTF_8);
|
||||
for (ServerInfo s : ProxyServer.getInstance().getServers().values()) {
|
||||
if (ignoredServers != null && ignoredServers.contains(s.getName())) continue;
|
||||
if (!forwardSpecific(s, rawData)) attemptToAddToCache(v, s.getName());
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean forwardSpecific(ServerInfo connection, Vote vote) {
|
||||
byte[] rawData = vote.serialize().toString().getBytes(StandardCharsets.UTF_8);
|
||||
return connection.sendData(channel, rawData, false);
|
||||
}
|
||||
|
||||
private boolean forwardSpecific(ServerInfo connection, byte[] data) {
|
||||
return connection.sendData(channel, data, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void halt() {
|
||||
ProxyServer.getInstance().unregisterChannel(channel);
|
||||
|
||||
if (cache != null && cache instanceof FileVoteCache) {
|
||||
try {
|
||||
((FileVoteCache) cache).halt();
|
||||
} catch (IOException e) {
|
||||
nuVotifier.getLogger().log(Level.SEVERE, "Unable to save cached votes, votes will be lost.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void handleServerConnected(final ServerConnectedEvent e) {
|
||||
if (cache == null) return;
|
||||
final String serverName = e.getServer().getInfo().getName();
|
||||
final Collection<Vote> cachedVotes = cache.evict(serverName);
|
||||
final Collection<Vote> failedVotes = dumpVotesToServer(cachedVotes, e.getServer().getInfo(), "server '" + serverName + "'");
|
||||
for (Vote v : failedVotes)
|
||||
cache.addToCache(v, serverName);
|
||||
}
|
||||
|
||||
protected void attemptToAddToCache(Vote v, String server) {
|
||||
if (cache != null) {
|
||||
cache.addToCache(v, server);
|
||||
if (nuVotifier.isDebug())
|
||||
nuVotifier.getLogger().info("Added to forwarding cache: " + v + " -> " + server);
|
||||
} else if (nuVotifier.isDebug())
|
||||
nuVotifier.getLogger().severe("Could not immediately send vote to backend, vote lost! " + v + " -> " + server);
|
||||
}
|
||||
|
||||
protected void attempToAddToPlayerCache(Vote v, String player) {
|
||||
if (cache != null) {
|
||||
cache.addToCachePlayer(v, player);
|
||||
if (nuVotifier.isDebug())
|
||||
nuVotifier.getLogger().info("Added to forwarding cache: " + v + " -> (player) " + player);
|
||||
} else if (nuVotifier.isDebug())
|
||||
nuVotifier.getLogger().severe("Could not immediately send vote to backend, vote lost! " + v + " -> (player) " + player);
|
||||
|
||||
}
|
||||
|
||||
// returns a collection of failed votes
|
||||
private Collection<Vote> dumpVotesToServer(Collection<Vote> cachedVotes, ServerInfo target, String identifier) {
|
||||
List<Vote> failures = new ArrayList<>();
|
||||
|
||||
if (!cachedVotes.isEmpty()) {
|
||||
nuVotifier.getProxy().getScheduler().schedule(nuVotifier, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
int evicted = 0;
|
||||
int unsuccessfulEvictions = 0;
|
||||
for (Vote v : cachedVotes) {
|
||||
if (forwardSpecific(target, v)) {
|
||||
evicted++;
|
||||
} else {
|
||||
// Re-add to cache to send later.
|
||||
failures.add(v);
|
||||
unsuccessfulEvictions++;
|
||||
}
|
||||
}
|
||||
if (nuVotifier.isDebug()) {
|
||||
nuVotifier.getLogger().info("Successfully evicted " + evicted + " votes to " + identifier + ".");
|
||||
if (unsuccessfulEvictions > 0) {
|
||||
nuVotifier.getLogger().info("Held " + unsuccessfulEvictions + " votes for " + identifier + ".");
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 1, TimeUnit.SECONDS);
|
||||
}
|
||||
return failures;
|
||||
}
|
||||
|
||||
protected void handlePlayerSwitch(ServerConnectedEvent e) {
|
||||
if (cache == null) return;
|
||||
if (ignoredServers != null && ignoredServers.contains(e.getServer().getInfo().getName())) return;
|
||||
|
||||
String playerName = e.getPlayer().getName();
|
||||
|
||||
final Collection<Vote> cachedVotes = cache.evictPlayer(playerName);
|
||||
final Collection<Vote> failedVotes = dumpVotesToServer(cachedVotes, e.getServer().getInfo(), "player '" + playerName);
|
||||
for (Vote v : failedVotes)
|
||||
cache.addToCachePlayer(v, playerName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.slf4j.impl;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
||||
public class DirtyTricks {
|
||||
private DirtyTricks() {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
public static Logger getLogger(java.util.logging.Logger source) {
|
||||
return new JDK14LoggerAdapter(source);
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ forwarding:
|
||||
# - proxy - Proxies votes to other NuVotifier servers from this server.
|
||||
method: none
|
||||
pluginMessaging:
|
||||
channel: NuVotifier
|
||||
channel: nuvotifier:votes
|
||||
|
||||
#Only send votes to certain servers. If a server is present on excludedServers, then votes will NOT be forwarded
|
||||
#to them. If excludedServers is not present, votes will be sent to all servers.
|
||||
|
||||
+3
-1
@@ -1,7 +1,9 @@
|
||||
dependencies {
|
||||
compile group: "io.netty", name: "netty-handler", version: "4.1.16.Final"
|
||||
compile 'com.google.code.gson:gson:2.8.2'
|
||||
compile 'com.google.code.gson:gson:2.8.0'
|
||||
compile 'com.google.guava:guava:24.1-jre'
|
||||
compile 'org.slf4j:slf4j-api:1.7.25'
|
||||
compile 'org.slf4j:slf4j-jdk14:1.7.25'
|
||||
testCompile group: "junit", name: "junit", version: "4.12"
|
||||
testCompile group: "org.json", name: "json", version: "20180130" // retain this for testing reasons
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package com.vexsoftware.votifier;
|
||||
|
||||
import io.netty.util.AttributeKey;
|
||||
|
||||
import java.security.Key;
|
||||
import java.security.KeyPair;
|
||||
import java.util.Map;
|
||||
|
||||
public interface VotifierPlugin {
|
||||
AttributeKey<VotifierPlugin> KEY = AttributeKey.valueOf("votifier_plugin");
|
||||
|
||||
Map<String, Key> getTokens();
|
||||
|
||||
KeyPair getProtocolV1Key();
|
||||
|
||||
String getVersion();
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.vexsoftware.votifier.net;
|
||||
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import com.vexsoftware.votifier.net.protocol.VoteInboundHandler;
|
||||
import com.vexsoftware.votifier.net.protocol.VotifierGreetingHandler;
|
||||
import com.vexsoftware.votifier.net.protocol.VotifierProtocolDifferentiator;
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class VotifierServerBootstrap {
|
||||
private final String host;
|
||||
private final int port;
|
||||
private final NioEventLoopGroup bossLoopGroup;
|
||||
private final NioEventLoopGroup 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;
|
||||
this.bossLoopGroup = new NioEventLoopGroup(1, new ThreadFactoryBuilder().setDaemon(true).setNameFormat("Votifier NIO boss").build());
|
||||
this.eventLoopGroup = new NioEventLoopGroup(1, new ThreadFactoryBuilder().setDaemon(true).setNameFormat("Votifier NIO worker").build());
|
||||
}
|
||||
|
||||
public void start(Consumer<Throwable> error) {
|
||||
Objects.requireNonNull(error, "error");
|
||||
new ServerBootstrap()
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.group(bossLoopGroup, eventLoopGroup)
|
||||
.childHandler(new ChannelInitializer<NioSocketChannel>() {
|
||||
@Override
|
||||
protected void initChannel(NioSocketChannel channel) {
|
||||
channel.attr(VotifierSession.KEY).set(new VotifierSession());
|
||||
channel.attr(VotifierPlugin.KEY).set(plugin);
|
||||
channel.pipeline().addLast("greetingHandler", new VotifierGreetingHandler());
|
||||
channel.pipeline().addLast("protocolDifferentiator", new VotifierProtocolDifferentiator(false, !v1Disable));
|
||||
channel.pipeline().addLast("voteHandler", new VoteInboundHandler(plugin));
|
||||
}
|
||||
})
|
||||
.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, future.cause());
|
||||
error.accept(future.cause());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public Bootstrap client() {
|
||||
return new Bootstrap()
|
||||
.channel(NioSocketChannel.class)
|
||||
.group(eventLoopGroup);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,7 @@ public class VoteInboundHandler extends SimpleChannelInboundHandler<Vote> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
VotifierSession session = ctx.channel().attr(VotifierSession.KEY).get();
|
||||
|
||||
handler.onError(ctx.channel(), session.hasCompletedVote(), cause);
|
||||
|
||||
+2
-5
@@ -1,6 +1,5 @@
|
||||
package com.vexsoftware.votifier.net.protocol;
|
||||
|
||||
import com.vexsoftware.votifier.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.net.VotifierSession;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
@@ -14,11 +13,9 @@ import java.nio.charset.StandardCharsets;
|
||||
*/
|
||||
public class VotifierGreetingHandler extends ChannelInboundHandlerAdapter {
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||
public void channelActive(ChannelHandlerContext ctx) {
|
||||
VotifierSession session = ctx.channel().attr(VotifierSession.KEY).get();
|
||||
VotifierPlugin plugin = ctx.channel().attr(VotifierPlugin.KEY).get();
|
||||
|
||||
String version = "VOTIFIER " + plugin.getVersion() + " " + session.getChallenge() + "\n";
|
||||
String version = "VOTIFIER 2 " + session.getChallenge() + "\n";
|
||||
ByteBuf versionBuf = Unpooled.copiedBuffer(version, StandardCharsets.UTF_8);
|
||||
ctx.writeAndFlush(versionBuf);
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
package com.vexsoftware.votifier.net.protocol;
|
||||
|
||||
import com.vexsoftware.votifier.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.protocol.v1crypto.RSA;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
@@ -16,7 +16,7 @@ import java.util.List;
|
||||
*/
|
||||
public class VotifierProtocol1Decoder extends ByteToMessageDecoder {
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> list) throws Exception {
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> list) {
|
||||
if (buf.readableBytes() < 256) {
|
||||
return;
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
package com.vexsoftware.votifier.net.protocol;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.vexsoftware.votifier.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.VotifierSession;
|
||||
import com.vexsoftware.votifier.util.GsonInst;
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ public class VotifierProtocolDifferentiator extends ByteToMessageDecoder {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> list) throws Exception {
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> list) {
|
||||
// Determine the number of bytes that are available.
|
||||
int readable = buf.readableBytes();
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.vexsoftware.votifier.platform;
|
||||
|
||||
public interface BackendServer {
|
||||
String getName();
|
||||
|
||||
boolean sendPluginMessage(String channel, byte[] data);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.vexsoftware.votifier.platform;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface ProxyVotifierPlugin extends VotifierPlugin {
|
||||
Collection<BackendServer> getAllBackendServers();
|
||||
|
||||
Optional<BackendServer> getServer(String name);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.vexsoftware.votifier.platform;
|
||||
|
||||
import com.vexsoftware.votifier.VoteHandler;
|
||||
import com.vexsoftware.votifier.platform.scheduler.VotifierScheduler;
|
||||
import io.netty.util.AttributeKey;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.security.Key;
|
||||
import java.security.KeyPair;
|
||||
import java.util.Map;
|
||||
|
||||
public interface VotifierPlugin extends VoteHandler {
|
||||
AttributeKey<VotifierPlugin> KEY = AttributeKey.valueOf("votifier_plugin");
|
||||
|
||||
Map<String, Key> getTokens();
|
||||
|
||||
KeyPair getProtocolV1Key();
|
||||
|
||||
Logger getPluginLogger();
|
||||
|
||||
VotifierScheduler getScheduler();
|
||||
|
||||
default boolean isDebug() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
package com.vexsoftware.votifier.platform.scheduler;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class ScheduledExecutorServiceVotifierScheduler implements VotifierScheduler {
|
||||
private final ScheduledExecutorService service;
|
||||
|
||||
public ScheduledExecutorServiceVotifierScheduler(ScheduledExecutorService service) {
|
||||
this.service = Preconditions.checkNotNull(service, "service");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask sync(Runnable runnable) {
|
||||
return new ScheduledVotifierTaskWrapper(service.submit(runnable));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask onPool(Runnable runnable) {
|
||||
return new ScheduledVotifierTaskWrapper(service.submit(runnable));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask delayedSync(Runnable runnable, int delay, TimeUnit unit) {
|
||||
return new ScheduledVotifierTaskWrapper(service.schedule(runnable, delay, unit));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask delayedOnPool(Runnable runnable, int delay, TimeUnit unit) {
|
||||
return new ScheduledVotifierTaskWrapper(service.schedule(runnable, delay, unit));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask repeatOnPool(Runnable runnable, int delay, int repeat, TimeUnit unit) {
|
||||
return new ScheduledVotifierTaskWrapper(service.scheduleAtFixedRate(runnable, delay, repeat, unit));
|
||||
}
|
||||
|
||||
private static class ScheduledVotifierTaskWrapper implements ScheduledVotifierTask {
|
||||
private final Future<?> future;
|
||||
|
||||
private ScheduledVotifierTaskWrapper(Future<?> future) {
|
||||
this.future = future;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
future.cancel(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package com.vexsoftware.votifier.platform.scheduler;
|
||||
|
||||
public interface ScheduledVotifierTask {
|
||||
void cancel();
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package com.vexsoftware.votifier.platform.scheduler;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
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);
|
||||
}
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
package com.vexsoftware.votifier.support.forwarding;
|
||||
|
||||
import com.vexsoftware.votifier.platform.BackendServer;
|
||||
import com.vexsoftware.votifier.platform.ProxyVotifierPlugin;
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.support.forwarding.cache.FileVoteCache;
|
||||
import com.vexsoftware.votifier.support.forwarding.cache.VoteCache;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public abstract class AbstractPluginMessagingForwardingSource implements ForwardingVoteSource {
|
||||
|
||||
public AbstractPluginMessagingForwardingSource(String channel, List<String> ignoredServers, ProxyVotifierPlugin plugin, VoteCache cache) {
|
||||
this.channel = channel;
|
||||
this.plugin = plugin;
|
||||
this.cache = cache;
|
||||
this.ignoredServers = ignoredServers;
|
||||
}
|
||||
|
||||
protected AbstractPluginMessagingForwardingSource(String channel, ProxyVotifierPlugin plugin, VoteCache voteCache) {
|
||||
this(channel, null, plugin, voteCache);
|
||||
}
|
||||
|
||||
protected final ProxyVotifierPlugin plugin;
|
||||
protected final String channel;
|
||||
protected final VoteCache cache;
|
||||
protected final List<String> ignoredServers;
|
||||
|
||||
@Override
|
||||
public void forward(Vote v) {
|
||||
byte[] rawData = v.serialize().toString().getBytes(StandardCharsets.UTF_8);
|
||||
for (BackendServer server : plugin.getAllBackendServers()) {
|
||||
if (ignoredServers != null && ignoredServers.contains(server.getName())) continue;
|
||||
if (!forwardSpecific(server, rawData)) attemptToAddToCache(v, server.getName());
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean forwardSpecific(BackendServer connection, Vote vote) {
|
||||
byte[] rawData = vote.serialize().toString().getBytes(StandardCharsets.UTF_8);
|
||||
return connection.sendPluginMessage(channel, rawData);
|
||||
}
|
||||
|
||||
private boolean forwardSpecific(BackendServer connection, byte[] data) {
|
||||
return connection.sendPluginMessage(channel, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void halt() {
|
||||
if (cache instanceof FileVoteCache) {
|
||||
try {
|
||||
((FileVoteCache) cache).halt();
|
||||
} catch (IOException e) {
|
||||
plugin.getPluginLogger().error("Unable to save cached votes, votes will be lost.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void onServerConnect(BackendServer server) {
|
||||
if (cache == null) return;
|
||||
final Collection<Vote> cachedVotes = cache.evict(server.getName());
|
||||
dumpVotesToServer(cachedVotes, server, "server '" + server + "'", failedVotes -> {
|
||||
for (Vote v : failedVotes)
|
||||
cache.addToCache(v, server.getName());
|
||||
});
|
||||
}
|
||||
|
||||
protected void attemptToAddToCache(Vote v, String server) {
|
||||
if (cache != null) {
|
||||
cache.addToCache(v, server);
|
||||
if (plugin.isDebug())
|
||||
plugin.getPluginLogger().info("Added to forwarding cache: " + v + " -> " + server);
|
||||
} else if (plugin.isDebug())
|
||||
plugin.getPluginLogger().error("Could not immediately send vote to backend, vote lost! " + v + " -> " + server);
|
||||
}
|
||||
|
||||
protected void attemptToAddToPlayerCache(Vote v, String player) {
|
||||
if (cache != null) {
|
||||
cache.addToCachePlayer(v, player);
|
||||
if (plugin.isDebug())
|
||||
plugin.getPluginLogger().info("Added to forwarding cache: " + v + " -> (player) " + player);
|
||||
} else if (plugin.isDebug())
|
||||
plugin.getPluginLogger().error("Could not immediately send vote to backend, vote lost! " + v + " -> (player) " + player);
|
||||
|
||||
}
|
||||
|
||||
// returns a collection of failed votes
|
||||
private void dumpVotesToServer(Collection<Vote> cachedVotes, BackendServer target, String identifier, Consumer<List<Vote>> cb) {
|
||||
List<Vote> failures = new ArrayList<>();
|
||||
|
||||
if (!cachedVotes.isEmpty()) {
|
||||
plugin.getScheduler().delayedOnPool(() -> {
|
||||
int evicted = 0;
|
||||
int unsuccessfulEvictions = 0;
|
||||
for (Vote v : cachedVotes) {
|
||||
if (forwardSpecific(target, v)) {
|
||||
evicted++;
|
||||
} else {
|
||||
// Re-add to cache to send later.
|
||||
failures.add(v);
|
||||
unsuccessfulEvictions++;
|
||||
}
|
||||
}
|
||||
if (plugin.isDebug()) {
|
||||
plugin.getPluginLogger().info("Successfully evicted " + evicted + " votes to " + identifier + ".");
|
||||
if (unsuccessfulEvictions > 0) {
|
||||
plugin.getPluginLogger().info("Held " + unsuccessfulEvictions + " votes for " + identifier + ".");
|
||||
}
|
||||
}
|
||||
cb.accept(failures);
|
||||
}, 3, TimeUnit.SECONDS);
|
||||
} else {
|
||||
cb.accept(failures);
|
||||
}
|
||||
}
|
||||
|
||||
protected void handlePlayerSwitch(BackendServer server, String playerName) {
|
||||
if (cache == null) return;
|
||||
if (ignoredServers != null && ignoredServers.contains(server.getName())) return;
|
||||
|
||||
final Collection<Vote> cachedVotes = cache.evictPlayer(playerName);
|
||||
dumpVotesToServer(cachedVotes, server, "player '" + playerName + "'", failedVotes -> {
|
||||
for (Vote v : failedVotes)
|
||||
cache.addToCachePlayer(v, playerName);
|
||||
});
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding;
|
||||
package com.vexsoftware.votifier.support.forwarding;
|
||||
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
|
||||
+29
-24
@@ -1,48 +1,53 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding.cache;
|
||||
package com.vexsoftware.votifier.support.forwarding.cache;
|
||||
|
||||
import com.google.common.io.Files;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.platform.scheduler.ScheduledVotifierTask;
|
||||
import com.vexsoftware.votifier.util.GsonInst;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
import net.md_5.bungee.api.scheduler.ScheduledTask;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class FileVoteCache extends MemoryVoteCache {
|
||||
|
||||
private final Logger l;
|
||||
private final File cacheFile;
|
||||
private final int voteTTL;
|
||||
private final ScheduledTask saveTask;
|
||||
private final long voteTTL;
|
||||
private final ScheduledVotifierTask saveTask;
|
||||
|
||||
public FileVoteCache(int initialMemorySize, final Plugin plugin, File cacheFile, int voteTTL) throws IOException {
|
||||
public FileVoteCache(int initialMemorySize, final VotifierPlugin plugin, File cacheFile, long voteTTL) throws IOException {
|
||||
super(initialMemorySize);
|
||||
this.cacheFile = cacheFile;
|
||||
this.voteTTL = voteTTL;
|
||||
this.l = plugin.getLogger();
|
||||
this.l = plugin.getPluginLogger();
|
||||
|
||||
load();
|
||||
|
||||
saveTask = ProxyServer.getInstance().getScheduler().schedule(plugin, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
save();
|
||||
} catch (IOException e) {
|
||||
l.log(Level.SEVERE, "Unable to save cached votes, votes will be lost if you restart.", e);
|
||||
}
|
||||
saveTask = plugin.getScheduler().repeatOnPool(() -> {
|
||||
try {
|
||||
save();
|
||||
} catch (IOException e) {
|
||||
l.error("Unable to save cached votes, votes will be lost if you restart.", e);
|
||||
}
|
||||
}, 3, 3, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
private static Set<String> keySet(JsonObject object) {
|
||||
Set<String> set = new HashSet<>();
|
||||
for (Map.Entry<String, JsonElement> entry : object.entrySet()) {
|
||||
set.add(entry.getKey());
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
private void load() throws IOException {
|
||||
// Load the cache from disk
|
||||
JsonObject object;
|
||||
@@ -70,11 +75,11 @@ public class FileVoteCache extends MemoryVoteCache {
|
||||
JsonObject players = object.getAsJsonObject("players");
|
||||
JsonObject servers = object.getAsJsonObject("servers");
|
||||
|
||||
for (String player : players.keySet()) {
|
||||
for (String player : keySet(players)) {
|
||||
playerVoteCache.put(player, readVotes(players.getAsJsonArray(player)));
|
||||
}
|
||||
|
||||
for (String server : servers.keySet()) {
|
||||
for (String server : keySet(servers)) {
|
||||
voteCache.put(server, readVotes(servers.getAsJsonArray(server)));
|
||||
}
|
||||
|
||||
@@ -87,11 +92,11 @@ public class FileVoteCache extends MemoryVoteCache {
|
||||
}
|
||||
|
||||
if (!cacheFile.renameTo(replacementFile)) {
|
||||
l.log(Level.SEVERE, "Backup movement failed! Will not save.");
|
||||
l.error("Backup movement failed! Will not save.");
|
||||
return;
|
||||
}
|
||||
|
||||
l.log(Level.WARNING, "Saving new vote cache format to file - backup moved to " + replacementFile.getAbsolutePath());
|
||||
l.warn("Saving new vote cache format to file - backup moved to " + replacementFile.getAbsolutePath());
|
||||
save();
|
||||
}
|
||||
}
|
||||
@@ -102,7 +107,7 @@ public class FileVoteCache extends MemoryVoteCache {
|
||||
JsonObject voteObject = voteArray.get(i).getAsJsonObject();
|
||||
Vote v = new Vote(voteObject);
|
||||
if (hasTimedOut(v))
|
||||
l.log(Level.WARNING, "Purging out of date vote.", v);
|
||||
l.warn("Purging out of date vote.", v);
|
||||
else
|
||||
votes.add(v);
|
||||
}
|
||||
@@ -138,7 +143,7 @@ public class FileVoteCache extends MemoryVoteCache {
|
||||
|
||||
// if the vote is no longer valid, notify and remove
|
||||
if (hasTimedOut(vote)) {
|
||||
l.log(Level.WARNING, "Purging out of date vote.", vote);
|
||||
l.warn("Purging out of date vote.", vote);
|
||||
voteItr.remove();
|
||||
} else {
|
||||
array.add(vote.serialize());
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding.cache;
|
||||
package com.vexsoftware.votifier.support.forwarding.cache;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding.cache;
|
||||
package com.vexsoftware.votifier.support.forwarding.cache;
|
||||
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
|
||||
+22
-34
@@ -1,17 +1,15 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding.proxy;
|
||||
package com.vexsoftware.votifier.support.forwarding.proxy;
|
||||
|
||||
import com.vexsoftware.votifier.bungee.NuVotifier;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.ForwardingVoteSource;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.cache.VoteCache;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.proxy.client.VotifierProtocol2Encoder;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.proxy.client.VotifierProtocol2HandshakeHandler;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.proxy.client.VotifierResponseHandler;
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.support.forwarding.ForwardingVoteSource;
|
||||
import com.vexsoftware.votifier.support.forwarding.cache.VoteCache;
|
||||
import com.vexsoftware.votifier.support.forwarding.proxy.client.VotifierProtocol2Encoder;
|
||||
import com.vexsoftware.votifier.support.forwarding.proxy.client.VotifierProtocol2HandshakeHandler;
|
||||
import com.vexsoftware.votifier.support.forwarding.proxy.client.VotifierResponseHandler;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
|
||||
import io.netty.handler.codec.Delimiters;
|
||||
@@ -23,20 +21,20 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.security.Key;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class ProxyForwardingVoteSource implements ForwardingVoteSource {
|
||||
private static final int MAX_RETRIES = 5;
|
||||
private final NuVotifier plugin;
|
||||
private final NioEventLoopGroup eventLoopGroup;
|
||||
private final VotifierPlugin plugin;
|
||||
private final Supplier<Bootstrap> nettyBootstrap;
|
||||
private final List<BackendServer> backendServers;
|
||||
private final VoteCache voteCache;
|
||||
|
||||
private static final StringDecoder STRING_DECODER = new StringDecoder(StandardCharsets.US_ASCII);
|
||||
|
||||
public ProxyForwardingVoteSource(NuVotifier plugin, NioEventLoopGroup eventLoopGroup, List<BackendServer> backendServers, VoteCache voteCache) {
|
||||
public ProxyForwardingVoteSource(VotifierPlugin plugin, Supplier<Bootstrap> nettyBootstrap, List<BackendServer> backendServers, VoteCache voteCache) {
|
||||
this.plugin = plugin;
|
||||
this.eventLoopGroup = eventLoopGroup;
|
||||
this.nettyBootstrap = nettyBootstrap;
|
||||
this.backendServers = backendServers;
|
||||
this.voteCache = voteCache;
|
||||
}
|
||||
@@ -54,21 +52,19 @@ public class ProxyForwardingVoteSource implements ForwardingVoteSource {
|
||||
}
|
||||
|
||||
private void forwardVote(final BackendServer server, final Vote v, final int tries) {
|
||||
new Bootstrap()
|
||||
.channel(NioSocketChannel.class)
|
||||
.group(eventLoopGroup)
|
||||
nettyBootstrap.get()
|
||||
.handler(new ChannelInitializer<NioSocketChannel>() {
|
||||
@Override
|
||||
protected void initChannel(NioSocketChannel channel) throws Exception {
|
||||
protected void initChannel(NioSocketChannel channel) {
|
||||
channel.pipeline().addLast(new DelimiterBasedFrameDecoder(256, true, Delimiters.lineDelimiter()));
|
||||
channel.pipeline().addLast(new ReadTimeoutHandler(8, TimeUnit.SECONDS));
|
||||
channel.pipeline().addLast(new ReadTimeoutHandler(5, TimeUnit.SECONDS));
|
||||
channel.pipeline().addLast(STRING_DECODER);
|
||||
channel.pipeline().addLast(new VotifierProtocol2Encoder(server.key));
|
||||
channel.pipeline().addLast(new VotifierProtocol2HandshakeHandler(v, new VotifierResponseHandler() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
if (plugin.isDebug()) {
|
||||
plugin.getLogger().info("Successfully forwarded vote " + v + " to " + server.address + ".");
|
||||
plugin.getPluginLogger().info("Successfully forwarded vote " + v + " to " + server.address + ".");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,12 +76,9 @@ public class ProxyForwardingVoteSource implements ForwardingVoteSource {
|
||||
}
|
||||
})
|
||||
.connect(server.address)
|
||||
.addListener(new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future) throws Exception {
|
||||
if (!future.isSuccess()) {
|
||||
handleFailure(server, v, future.cause(), tries);
|
||||
}
|
||||
.addListener((ChannelFutureListener) future -> {
|
||||
if (!future.isSuccess()) {
|
||||
handleFailure(server, v, future.cause(), tries);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -106,18 +99,13 @@ public class ProxyForwardingVoteSource implements ForwardingVoteSource {
|
||||
}
|
||||
|
||||
if (plugin.isDebug()) {
|
||||
plugin.getLogger().log(Level.SEVERE, msg, cause);
|
||||
plugin.getPluginLogger().error(msg, cause);
|
||||
} else {
|
||||
plugin.getLogger().log(Level.SEVERE, msg);
|
||||
plugin.getPluginLogger().error(msg);
|
||||
}
|
||||
|
||||
if (willRetry) {
|
||||
plugin.getProxy().getScheduler().schedule(plugin, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
forwardVote(server, v, tries + 1);
|
||||
}
|
||||
}, nextDelay, TimeUnit.SECONDS);
|
||||
plugin.getScheduler().delayedOnPool(() -> forwardVote(server, v, tries + 1), nextDelay, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding.proxy.client;
|
||||
package com.vexsoftware.votifier.support.forwarding.proxy.client;
|
||||
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding.proxy.client;
|
||||
package com.vexsoftware.votifier.support.forwarding.proxy.client;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
+7
-7
@@ -1,6 +1,6 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding.proxy.client;
|
||||
package com.vexsoftware.votifier.support.forwarding.proxy.client;
|
||||
|
||||
import com.vexsoftware.votifier.bungee.NuVotifier;
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
@@ -9,16 +9,16 @@ import io.netty.handler.codec.CorruptedFrameException;
|
||||
public class VotifierProtocol2HandshakeHandler extends SimpleChannelInboundHandler<String> {
|
||||
private final Vote toSend;
|
||||
private final VotifierResponseHandler responseHandler;
|
||||
private final NuVotifier nuVotifier;
|
||||
private final VotifierPlugin nuVotifier;
|
||||
|
||||
public VotifierProtocol2HandshakeHandler(Vote toSend, VotifierResponseHandler responseHandler, NuVotifier nuVotifier) {
|
||||
public VotifierProtocol2HandshakeHandler(Vote toSend, VotifierResponseHandler responseHandler, VotifierPlugin nuVotifier) {
|
||||
this.toSend = toSend;
|
||||
this.responseHandler = responseHandler;
|
||||
this.nuVotifier = nuVotifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, String s) throws Exception {
|
||||
protected void channelRead0(ChannelHandlerContext ctx, String s) {
|
||||
String[] handshakeContents = s.split(" ");
|
||||
if (handshakeContents.length != 3) {
|
||||
throw new CorruptedFrameException("Handshake is not valid.");
|
||||
@@ -26,7 +26,7 @@ public class VotifierProtocol2HandshakeHandler extends SimpleChannelInboundHandl
|
||||
|
||||
VoteRequest request = new VoteRequest(handshakeContents[2], toSend);
|
||||
if (nuVotifier.isDebug()) {
|
||||
nuVotifier.getLogger().info("Sent request: " + request.toString());
|
||||
nuVotifier.getPluginLogger().info("Sent request: " + request.toString());
|
||||
}
|
||||
ctx.writeAndFlush(request);
|
||||
ctx.pipeline().addLast(new VotifierProtocol2ResponseHandler(responseHandler));
|
||||
@@ -35,7 +35,7 @@ public class VotifierProtocol2HandshakeHandler extends SimpleChannelInboundHandl
|
||||
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
responseHandler.onFailure(cause);
|
||||
ctx.close();
|
||||
}
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding.proxy.client;
|
||||
package com.vexsoftware.votifier.support.forwarding.proxy.client;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.vexsoftware.votifier.util.GsonInst;
|
||||
@@ -13,7 +13,7 @@ public class VotifierProtocol2ResponseHandler extends SimpleChannelInboundHandle
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
|
||||
protected void channelRead0(ChannelHandlerContext ctx, String msg) {
|
||||
JsonObject object = GsonInst.gson.fromJson(msg, JsonObject.class);
|
||||
String status = object.get("status").getAsString();
|
||||
if (status.equals("ok")) {
|
||||
@@ -26,7 +26,7 @@ public class VotifierProtocol2ResponseHandler extends SimpleChannelInboundHandle
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
responseHandler.onFailure(cause);
|
||||
ctx.close();
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding.proxy.client;
|
||||
package com.vexsoftware.votifier.support.forwarding.proxy.client;
|
||||
|
||||
public interface VotifierResponseHandler {
|
||||
void onSuccess();
|
||||
+27
-37
@@ -1,19 +1,14 @@
|
||||
package com.vexsoftware.votifier.util.standalone;
|
||||
|
||||
import com.vexsoftware.votifier.VoteHandler;
|
||||
import com.vexsoftware.votifier.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.net.VotifierServerBootstrap;
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.VotifierSession;
|
||||
import com.vexsoftware.votifier.net.protocol.VoteInboundHandler;
|
||||
import com.vexsoftware.votifier.net.protocol.VotifierGreetingHandler;
|
||||
import com.vexsoftware.votifier.net.protocol.VotifierProtocolDifferentiator;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import com.vexsoftware.votifier.platform.scheduler.ScheduledExecutorServiceVotifierScheduler;
|
||||
import com.vexsoftware.votifier.platform.scheduler.VotifierScheduler;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.security.Key;
|
||||
@@ -21,45 +16,35 @@ import java.security.KeyPair;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class StandaloneVotifierPlugin implements VotifierPlugin, VoteHandler {
|
||||
public class StandaloneVotifierPlugin implements VotifierPlugin {
|
||||
private final Map<String, Key> tokens;
|
||||
private final VoteReceiver receiver;
|
||||
private final KeyPair v1Key;
|
||||
private final InetSocketAddress bind;
|
||||
private NioEventLoopGroup group;
|
||||
private final VotifierScheduler scheduler;
|
||||
private VotifierServerBootstrap bootstrap;
|
||||
|
||||
public StandaloneVotifierPlugin(Map<String, Key> tokens, VoteReceiver receiver, KeyPair v1Key, InetSocketAddress bind) {
|
||||
this.receiver = receiver;
|
||||
this.bind = bind;
|
||||
this.tokens = Collections.unmodifiableMap(new HashMap<>(tokens));
|
||||
this.v1Key = v1Key;
|
||||
this.scheduler = new ScheduledExecutorServiceVotifierScheduler(Executors.newScheduledThreadPool(1));
|
||||
}
|
||||
|
||||
public Throwable start() throws Throwable {
|
||||
group = new NioEventLoopGroup(2);
|
||||
public void start() {
|
||||
start(o -> {});
|
||||
}
|
||||
|
||||
ChannelFuture future = new ServerBootstrap()
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.group(group)
|
||||
.childHandler(new ChannelInitializer<NioSocketChannel>() {
|
||||
@Override
|
||||
protected void initChannel(NioSocketChannel channel) throws Exception {
|
||||
channel.attr(VotifierSession.KEY).set(new VotifierSession());
|
||||
channel.attr(VotifierPlugin.KEY).set(StandaloneVotifierPlugin.this);
|
||||
channel.pipeline().addLast("greetingHandler", new VotifierGreetingHandler());
|
||||
channel.pipeline().addLast("protocolDifferentiator", new VotifierProtocolDifferentiator(false, v1Key != null));
|
||||
channel.pipeline().addLast("voteHandler", new VoteInboundHandler(StandaloneVotifierPlugin.this));
|
||||
}
|
||||
})
|
||||
.bind(bind)
|
||||
.syncUninterruptibly();
|
||||
|
||||
if (!future.isSuccess()) {
|
||||
return future.cause();
|
||||
} else {
|
||||
return null;
|
||||
public void start(Consumer<Throwable> error) {
|
||||
if (bootstrap != null) {
|
||||
bootstrap.shutdown();
|
||||
}
|
||||
this.bootstrap = new VotifierServerBootstrap(bind.getHostString(), bind.getPort(), this, v1Key == null);
|
||||
this.bootstrap.start(error);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -73,8 +58,13 @@ public class StandaloneVotifierPlugin implements VotifierPlugin, VoteHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVersion() {
|
||||
return "2.3.5";
|
||||
public Logger getPluginLogger() {
|
||||
return LoggerFactory.getLogger(StandaloneVotifierPlugin.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VotifierScheduler getScheduler() {
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
package com.vexsoftware.votifier.net.protocol;
|
||||
|
||||
import com.vexsoftware.votifier.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.VotifierSession;
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.platform.scheduler.VotifierScheduler;
|
||||
import com.vexsoftware.votifier.util.KeyCreator;
|
||||
import io.netty.channel.Channel;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@@ -71,8 +76,13 @@ public class TestVotifierPlugin implements VotifierPlugin {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVersion() {
|
||||
return "2.3";
|
||||
public Logger getPluginLogger() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VotifierScheduler getScheduler() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void specificKeysOnly() {
|
||||
@@ -84,4 +94,9 @@ public class TestVotifierPlugin implements VotifierPlugin {
|
||||
keyMap.clear();
|
||||
keyMap.put("default", KeyCreator.createKeyFrom("test"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVoteReceived(Channel channel, Vote vote, VotifierSession.ProtocolVersion protocolVersion) throws Exception {
|
||||
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
package com.vexsoftware.votifier.net.protocol;
|
||||
|
||||
import com.vexsoftware.votifier.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.VotifierSession;
|
||||
import com.vexsoftware.votifier.net.protocol.v1crypto.RSA;
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
package com.vexsoftware.votifier.net.protocol;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.vexsoftware.votifier.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.VotifierSession;
|
||||
import com.vexsoftware.votifier.util.GsonInst;
|
||||
@@ -62,7 +62,7 @@ public class VotifierProtocol2DecoderTest {
|
||||
}
|
||||
|
||||
@Test(expected = DecoderException.class)
|
||||
public void testFailureDecodeBadPacket() throws Exception {
|
||||
public void testFailureDecodeBadPacket() {
|
||||
// Create a well-formed request
|
||||
EmbeddedChannel channel = createChannel();
|
||||
|
||||
|
||||
+1
-1
@@ -64,7 +64,7 @@ repositories {
|
||||
}
|
||||
...
|
||||
dependencies {
|
||||
compileOnly "com.vexsoftware:nuvotifier-universal:verison"
|
||||
compileOnly "com.vexsoftware:nuvotifier-universal:version"
|
||||
...
|
||||
}
|
||||
...
|
||||
|
||||
+46
-39
@@ -62,23 +62,23 @@ vote to your server!
|
||||
# Multi-server / Network configuration
|
||||
|
||||
You will want to use these instructions when you have more than one server and
|
||||
are using a Minecraft proxy (Bungeecord / Lilypad)
|
||||
are using a Minecraft proxy (such as BungeeCord, LilyPad, or Velocity).
|
||||
|
||||
## Single Bungeecord Networks
|
||||
## Single-Proxy BungeeCord/Velocity Networks
|
||||
|
||||
When running a single-bungeecord network, NuVotifier is able to operate as both
|
||||
a Bungeecord vote receiver (emitting events for Bungeecord based vote listeners)
|
||||
as well as a vote forwarder. NuVotifier's vote forwarding logic is also advanced
|
||||
enough to be used in a wide variety of ways.
|
||||
When running a single-proxy network, NuVotifier is able to operate as both
|
||||
a vote receiver (emitting events for proxy based vote listeners) as well as
|
||||
a vote forwarder. NuVotifier's vote forwarding logic is also advanced enough
|
||||
to be used in a wide variety of ways.
|
||||
|
||||
### Installing the plugin on the servers
|
||||
|
||||
NuVotifier should be installed on each of the servers you wish to process votes
|
||||
on, as well as the Bungeecord server. The Bungeecord server will act as the
|
||||
on, as well as the proxy server. The proxy's NuVotifier server will act as the
|
||||
public facing Votifier instance, where as the Minecraft servers will instead
|
||||
receive instructions from the Bungeecord Nuvotifier for forwarding votes.
|
||||
receive votes forwarded from the proxy's NuVotifier.
|
||||
|
||||
Like, the single server installation, you can download NuVotifier at any one of
|
||||
Like the single server installation, you can download NuVotifier at any one of
|
||||
the following locations:
|
||||
|
||||
+ [Github](https://github.com/parallelblock/NuVotifier/releases) - Official
|
||||
@@ -98,30 +98,32 @@ correct.
|
||||
|
||||
### Plugin messaging based forwarding
|
||||
|
||||
This section describes how to set up plugin messaging forwarding. This will only
|
||||
work correctly if you have one Bungeecord server, however may also work with
|
||||
some exceptions in multi Bungeecord networks.
|
||||
This section describes how to set up plugin messaging forwarding. This is
|
||||
recommended if you have just one proxy, however this may also work with
|
||||
some exceptions in multi-proxy networks.
|
||||
|
||||
First off, we should configure each of the Minecraft servers. Open each of the
|
||||
servers' NuVotifier configurations. Each one should have `method` under the
|
||||
`forwarding` configuration section set to `pluginMessaging`. Under the
|
||||
`pluginMessaging` configuration section, `channel` is set to `NuVotifier`. This
|
||||
is a safe default (and is protected from injection by a hacked Minecraft
|
||||
client), but feel free to change it. If you choose to change it, make sure you
|
||||
change it everywhere all around your network!
|
||||
`pluginMessaging` configuration section, `channel` is set to `nuvotifier:votes`.
|
||||
Since 1.13, Spigot has decided that channels must now contain a colon. While
|
||||
this is a completely senseless change, it is easier to simply conform and change
|
||||
the channel name. This is a safe default (and is protected from injection by a
|
||||
hacked Minecraft client), but feel free to change it. If you choose to change
|
||||
it, make sure you change it everywhere all around your network!
|
||||
|
||||
Since we are receiving forwarded votes through plugin messaging and not through
|
||||
network ports, we should set `port` on each of the Minecraft servers to -1. This
|
||||
will disable NuVotifier's port, while still allowing it to receive forwarded
|
||||
plugin messaging votes. This will only work when using pluginMessaging!
|
||||
plugin messaging votes. This will only work when using plugin messaging!
|
||||
|
||||
Since we have all of that configured, we can now move to the Bungeecord
|
||||
NuVotifier configuration. It is once again located under `plugins/NuVotifier/`
|
||||
directory (`config.yml`). Like the single server, `port` should be set to an
|
||||
unbound port. The default `8192` works well, however you may have to change it
|
||||
depending on your hosting provider.
|
||||
Since we have all of that configured, we can now move to the proxy's NuVotifier
|
||||
configuration. It is once again located under `plugins/NuVotifier/` directory
|
||||
(`config.yml` for BungeeCord, `config.toml` for Velocity). Like the single server,
|
||||
`port` should be set to an unbound port. The default `8192` works well, however
|
||||
you may have to change it depending on your hosting provider.
|
||||
|
||||
Since we are using pluginMessaging based forwarding, lets set `method` under
|
||||
Since we are using plugin messaging based forwarding, lets set `method` under
|
||||
`forwarding` to `pluginMessaging`. Make sure that `channel` is the same channel
|
||||
that you configured your servers to listen to - if you didn't change this value,
|
||||
then the default will work.
|
||||
@@ -129,7 +131,7 @@ then the default will work.
|
||||
NuVotifier saves when it can't immediately forward in a user defined cache. In
|
||||
almost all installations this should be `file`, however, some installations may
|
||||
instead want `none` or `memory`. If you don't know, the `cache` should be set to
|
||||
it's default, `file`.
|
||||
its default, `file`.
|
||||
|
||||
By default, NuVotifier will forward the vote to all of the connected Minecraft
|
||||
servers (all servers you can get to when typing /server). You can manually
|
||||
@@ -164,11 +166,11 @@ Proxy based forwarding does not support vote caching or advanced forwarding
|
||||
configurations. If you cannot use plugin messaging forwarding, then the
|
||||
following section will describe how to set up proxy based vote forwarding. If
|
||||
you still require advanced forwarding features or more advanced behaviors, it
|
||||
may be worthwhile to write your own Bungeecord plugin to perform the proper
|
||||
forwarding logic (you will know if you need to do this).
|
||||
may be worthwhile to write your own plugin to perform the proper forwarding logic
|
||||
(you will know if you need to do this).
|
||||
|
||||
In proxy based vote forwarding, one of your Bungeecord servers acts as the head
|
||||
vote receiver. This server will be the one you configure the voting websites to
|
||||
In proxy based vote forwarding, one of your proxy servers acts as the head vote
|
||||
receiver. This server will be the one you configure the voting websites to
|
||||
vote to. You can set up multiple vote receivers and load balance between them,
|
||||
however this configuration has limited benefit and is probably more work than it
|
||||
is worth. Instead of communicating through plugin messaging like the plugin
|
||||
@@ -178,18 +180,17 @@ each of the backend servers it is configured with.
|
||||
|
||||
First, we will set up all of the Minecraft servers before setting up the head
|
||||
vote receiver. For each of the servers, NuVotifier should be configured to bind
|
||||
to an unused port. This port must be valid. If you have multiple votifiers on
|
||||
to an unused port. This port must be valid. If you have multiple Votifiers on
|
||||
the same IP/server, make sure that the port which you are using between
|
||||
NuVotifier instances are different. Remember where to find the port number as we
|
||||
will need it later to insert into the vote receiver Bungeecord NuVotifier
|
||||
configuration. In addition to the port number, you will also need the default
|
||||
token - this is the string auto generated by NuVotifier under the `default` key
|
||||
under `tokens`. Since we are using proxy forwarding, change
|
||||
`disable-v1-protocol` to true - we will only be using the new NuVotifier
|
||||
votifier protocol within our network (vote receiver -> minecraft server). Once
|
||||
all configured, that is all we need to do. The rest of the defaults will work.
|
||||
NuVotifier instances are different. Remember the the port number as we will need
|
||||
it later to insert into the proxy NuVotifier configuration. In addition to the
|
||||
port number, you will also need the default token - this is the string auto
|
||||
generated by NuVotifier under the `default` key under `tokens`. Since we are using
|
||||
proxy forwarding, change `disable-v1-protocol` to true - we will only be using the
|
||||
new NuVotifier protocol within our network (vote receiver -> minecraft server).
|
||||
Once all configured, that is all we need to do. The rest of the defaults will work.
|
||||
|
||||
Now we will configure the vote receiving NuVotifier Bungeecord server. The port
|
||||
Now we will configure the vote receiving NuVotifier proxy server. The port
|
||||
should be configured as described many times above, being a unique port. Instead
|
||||
of setting `method` to `pluginMessaging` like above, we will set `method` to
|
||||
`proxy`. This will tell NuVotifier that we instead want to proxy the votes
|
||||
@@ -197,7 +198,7 @@ instead of forward them through plugin messaging channels.
|
||||
|
||||
The last step is to set up the proxy forwaring settings. Under the `proxy`
|
||||
section is a place to enter each server. Create a unique name for each of the
|
||||
server's you will be forwarding votes to. The default Bungeecord NuVotifier
|
||||
server's you will be forwarding votes to. The default proxy NuVotifier
|
||||
configuration already has two Minecraft servers filled in - Hub and Hub2. You
|
||||
should replace these with your own servers - the names of the servers do not
|
||||
matter. Under each of these servers, we need to configure `address`, `port`, and
|
||||
@@ -217,6 +218,12 @@ don't know what is wrong, you may want to try setting `debug` in each of the
|
||||
server's NuVotifier configurations to `true`. This will increase logging within
|
||||
your console.
|
||||
|
||||
# Reloading NuVotifier
|
||||
|
||||
For Bukkit and Bungee, you may reload NuVotifier through the `/nvreload` command
|
||||
in your console only - this command will not work in-game by design. Make sure to
|
||||
ensure that it completed correctly before assuming NuVotifier works!
|
||||
|
||||
# Default Configurations
|
||||
|
||||
If you find you have messed up NuVotifier beyond repair, you can grab a default
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
#Sat Feb 24 13:39:00 EST 2018
|
||||
#Thu May 31 03:56:27 EDT 2018
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.0-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.0-all.zip
|
||||
@@ -12,3 +12,6 @@ findProject(':sponge')?.name = 'nuvotifier-sponge'
|
||||
|
||||
include 'universal'
|
||||
findProject(':universal')?.name = 'nuvotifier-universal'
|
||||
include 'velocity'
|
||||
findProject(':velocity')?.name = 'nuvotifier-velocity'
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.vexsoftware.votifier.sponge;
|
||||
|
||||
import com.vexsoftware.votifier.platform.scheduler.ScheduledVotifierTask;
|
||||
import com.vexsoftware.votifier.platform.scheduler.VotifierScheduler;
|
||||
import org.spongepowered.api.Sponge;
|
||||
import org.spongepowered.api.scheduler.Task;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
class SpongeScheduler implements VotifierScheduler {
|
||||
private final VotifierPlugin plugin;
|
||||
|
||||
SpongeScheduler(VotifierPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
private Task.Builder taskBuilder(Runnable runnable) {
|
||||
return Sponge.getScheduler().createTaskBuilder().execute(runnable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask sync(Runnable runnable) {
|
||||
return new TaskWrapper(taskBuilder(runnable).submit(plugin));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask onPool(Runnable runnable) {
|
||||
return new TaskWrapper(taskBuilder(runnable).async().submit(plugin));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask delayedSync(Runnable runnable, int delay, TimeUnit unit) {
|
||||
return new TaskWrapper(taskBuilder(runnable).delay(delay, unit).submit(plugin));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask delayedOnPool(Runnable runnable, int delay, TimeUnit unit) {
|
||||
return new TaskWrapper(taskBuilder(runnable).delay(delay, unit).async().submit(plugin));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask repeatOnPool(Runnable runnable, int delay, int repeat, TimeUnit unit) {
|
||||
return new TaskWrapper(taskBuilder(runnable).delay(delay, unit).interval(repeat, unit).submit(plugin));
|
||||
}
|
||||
|
||||
private static class TaskWrapper implements ScheduledVotifierTask {
|
||||
private final Task task;
|
||||
|
||||
private TaskWrapper(Task task) {
|
||||
this.task = task;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
task.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +1,20 @@
|
||||
package com.vexsoftware.votifier.sponge;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.io.ByteStreams;
|
||||
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.VoteInboundHandler;
|
||||
import com.vexsoftware.votifier.net.protocol.VotifierGreetingHandler;
|
||||
import com.vexsoftware.votifier.net.protocol.VotifierProtocolDifferentiator;
|
||||
import com.vexsoftware.votifier.net.protocol.v1crypto.RSAIO;
|
||||
import com.vexsoftware.votifier.net.protocol.v1crypto.RSAKeygen;
|
||||
import com.vexsoftware.votifier.platform.scheduler.VotifierScheduler;
|
||||
import com.vexsoftware.votifier.sponge.config.ConfigLoader;
|
||||
import com.vexsoftware.votifier.sponge.event.VotifierEvent;
|
||||
import com.vexsoftware.votifier.sponge.forwarding.ForwardedVoteListener;
|
||||
import com.vexsoftware.votifier.sponge.forwarding.ForwardingVoteSink;
|
||||
import com.vexsoftware.votifier.sponge.forwarding.SpongePluginMessagingForwardingSink;
|
||||
import com.vexsoftware.votifier.util.KeyCreator;
|
||||
import com.vexsoftware.votifier.util.TokenUtil;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import ninja.leaping.configurate.ConfigurationNode;
|
||||
import ninja.leaping.configurate.loader.ConfigurationLoader;
|
||||
import ninja.leaping.configurate.yaml.YAMLConfigurationLoader;
|
||||
import org.slf4j.Logger;
|
||||
import org.spongepowered.api.Sponge;
|
||||
import org.spongepowered.api.config.ConfigDir;
|
||||
@@ -38,114 +24,36 @@ import org.spongepowered.api.event.game.state.GameStoppingServerEvent;
|
||||
import org.spongepowered.api.plugin.Plugin;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.Key;
|
||||
import java.security.KeyPair;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
@Plugin(id = "nuvotifier", name = "NuVotifier", version = "2.3.7", authors = "ParallelBlock LLC",
|
||||
description = "Safe, smart, and secure Votifier server plugin")
|
||||
public class VotifierPlugin implements VoteHandler, com.vexsoftware.votifier.VotifierPlugin, ForwardedVoteListener {
|
||||
private Logger logger;
|
||||
@Inject
|
||||
@ConfigDir(sharedRoot = false)
|
||||
private Path baseDirectory;
|
||||
public class VotifierPlugin implements VoteHandler, com.vexsoftware.votifier.platform.VotifierPlugin, ForwardedVoteListener {
|
||||
|
||||
@Inject
|
||||
public VotifierPlugin(Logger logger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
public Logger logger;
|
||||
|
||||
@Inject
|
||||
@ConfigDir(sharedRoot = false)
|
||||
public File configDir;
|
||||
|
||||
private VotifierScheduler scheduler;
|
||||
|
||||
@Listener
|
||||
public void onServerStart(GameStartedServerEvent event) {
|
||||
// Set the plugin version.
|
||||
version = this.getClass().getAnnotation(Plugin.class).version();
|
||||
|
||||
// Handle configuration.
|
||||
Path config = baseDirectory.resolve("config.yml");
|
||||
File rsaDirectory = baseDirectory.resolve("rsa").toFile();
|
||||
|
||||
/*
|
||||
* Use IP address from server.properties as a default for
|
||||
* configurations. Do not use InetAddress.getLocalHost() as it most
|
||||
* likely will return the main server address instead of the address
|
||||
* assigned to the server.
|
||||
*/
|
||||
String hostAddr;
|
||||
Optional<InetSocketAddress> address = Sponge.getServer().getBoundAddress();
|
||||
if (address.isPresent())
|
||||
hostAddr = address.get().getAddress().getHostAddress();
|
||||
else
|
||||
hostAddr = "0.0.0.0";
|
||||
|
||||
/*
|
||||
* Create configuration file if it does not exists; otherwise, load it
|
||||
*/
|
||||
try {
|
||||
Files.createDirectories(baseDirectory);
|
||||
} catch (FileAlreadyExistsException ignored) {
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Can't create base directory", e);
|
||||
}
|
||||
|
||||
if (!Files.exists(config)) {
|
||||
try {
|
||||
// First time run - do some initialization.
|
||||
logger.info("Configuring Votifier for the first time...");
|
||||
|
||||
// Load and manually replace variables in the configuration.
|
||||
String cfgStr = new String(ByteStreams.toByteArray(this.getClass().getResourceAsStream("spongeConfig.yml")),
|
||||
StandardCharsets.UTF_8);
|
||||
String token = TokenUtil.newToken();
|
||||
cfgStr = cfgStr.replace("%default_token%", token).replace("%ip%", hostAddr);
|
||||
Files.write(config, ImmutableList.<CharSequence>of(cfgStr), StandardCharsets.UTF_8);
|
||||
|
||||
/*
|
||||
* Remind hosted server admins to be sure they have the right
|
||||
* port number.
|
||||
*/
|
||||
logger.info("------------------------------------------------------------------------------");
|
||||
logger.info("Assigning NuVotifier to listen on port 8192. If you are hosting Craftbukkit on a");
|
||||
logger.info("shared server please check with your hosting provider to verify that this port");
|
||||
logger.info("is available for your use. Chances are that your hosting provider will assign");
|
||||
logger.info("a different port, which you need to specify in config.yml");
|
||||
logger.info("------------------------------------------------------------------------------");
|
||||
logger.info("Your default NuVotifier token is " + token + ".");
|
||||
logger.info("You will need to provide this token when you submit your server to a voting");
|
||||
logger.info("list.");
|
||||
logger.info("------------------------------------------------------------------------------");
|
||||
} catch (Exception ex) {
|
||||
logger.error("Error creating configuration file", ex);
|
||||
gracefulExit();
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.scheduler = new SpongeScheduler(this);
|
||||
|
||||
// Load configuration.
|
||||
ConfigurationLoader loader = YAMLConfigurationLoader.builder()
|
||||
.setPath(config)
|
||||
.build();
|
||||
ConfigurationNode node;
|
||||
try {
|
||||
node = loader.load();
|
||||
} catch (IOException e) {
|
||||
logger.error("Error loading configuration file", e);
|
||||
gracefulExit();
|
||||
return;
|
||||
}
|
||||
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()) {
|
||||
rsaDirectory.mkdir();
|
||||
@@ -160,45 +68,22 @@ public class VotifierPlugin implements VoteHandler, com.vexsoftware.votifier.Vot
|
||||
return;
|
||||
}
|
||||
|
||||
debug = node.getNode("debug").getBoolean(false);
|
||||
debug = ConfigLoader.getSpongeConfig().debug;
|
||||
|
||||
// Load Votifier tokens.
|
||||
ConfigurationNode tokenSection = node.getNode("tokens");
|
||||
|
||||
if (tokenSection.hasMapChildren()) {
|
||||
for (Map.Entry<Object, ? extends ConfigurationNode> entry : tokenSection.getChildrenMap().entrySet()) {
|
||||
if (entry.getKey() instanceof String) {
|
||||
tokens.put((String) entry.getKey(), KeyCreator.createKeyFrom(entry.getValue().getString()));
|
||||
logger.info("Loaded token for website: " + entry.getKey());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
String token = TokenUtil.newToken();
|
||||
tokenSection.setValue(ImmutableMap.of("default", token));
|
||||
tokens.put("default", KeyCreator.createKeyFrom(token));
|
||||
try {
|
||||
loader.save(node);
|
||||
} catch (IOException e) {
|
||||
logger.error("Error generating Votifier token", e);
|
||||
gracefulExit();
|
||||
return;
|
||||
}
|
||||
logger.info("------------------------------------------------------------------------------");
|
||||
logger.info("No tokens were found in your configuration, so we've generated one for you.");
|
||||
logger.info("Your default Votifier token is " + token + ".");
|
||||
logger.info("You will need to provide this token when you submit your server to a voting");
|
||||
logger.info("list.");
|
||||
logger.info("------------------------------------------------------------------------------");
|
||||
}
|
||||
// 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 = node.getNode("host").getString(hostAddr);
|
||||
final int port = node.getNode("port").getInt(8192);
|
||||
final String host = ConfigLoader.getSpongeConfig().host;
|
||||
final int port = ConfigLoader.getSpongeConfig().port;
|
||||
if (debug)
|
||||
logger.info("DEBUG mode enabled!");
|
||||
|
||||
if (port >= 0) {
|
||||
final boolean disablev1 = node.getNode("disable-v1-protocol").getBoolean(false);
|
||||
final boolean disablev1 = ConfigLoader.getSpongeConfig().disableV1Protocol;
|
||||
if (disablev1) {
|
||||
logger.info("------------------------------------------------------------------------------");
|
||||
logger.info("Votifier protocol v1 parsing has been disabled. Most voting websites do not");
|
||||
@@ -206,37 +91,8 @@ public class VotifierPlugin implements VoteHandler, com.vexsoftware.votifier.Vot
|
||||
logger.info("------------------------------------------------------------------------------");
|
||||
}
|
||||
|
||||
serverGroup = new NioEventLoopGroup(1);
|
||||
|
||||
new ServerBootstrap()
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.group(serverGroup)
|
||||
.childHandler(new ChannelInitializer<NioSocketChannel>() {
|
||||
@Override
|
||||
protected void initChannel(NioSocketChannel channel) throws Exception {
|
||||
channel.attr(VotifierSession.KEY).set(new VotifierSession());
|
||||
channel.attr(com.vexsoftware.votifier.VotifierPlugin.KEY).set(VotifierPlugin.this);
|
||||
channel.pipeline().addLast("greetingHandler", new VotifierGreetingHandler());
|
||||
channel.pipeline().addLast("protocolDifferentiator", new VotifierProtocolDifferentiator(false, !disablev1));
|
||||
channel.pipeline().addLast("voteHandler", new VoteInboundHandler(VotifierPlugin.this));
|
||||
}
|
||||
})
|
||||
.bind(host, port)
|
||||
.addListener(new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future) throws Exception {
|
||||
if (future.isSuccess()) {
|
||||
serverChannel = future.channel();
|
||||
logger.info("Votifier enabled on socket " + serverChannel.localAddress() + ".");
|
||||
} else {
|
||||
SocketAddress socketAddress = future.channel().localAddress();
|
||||
if (socketAddress == null) {
|
||||
socketAddress = new InetSocketAddress(host, port);
|
||||
}
|
||||
logger.error("Votifier was not able to bind to " + socketAddress, future.cause());
|
||||
}
|
||||
}
|
||||
});
|
||||
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");
|
||||
@@ -245,13 +101,12 @@ public class VotifierPlugin implements VoteHandler, com.vexsoftware.votifier.Vot
|
||||
getLogger().info("------------------------------------------------------------------------------");
|
||||
}
|
||||
|
||||
ConfigurationNode forwardingConfig = node.getNode("forwarding");
|
||||
if (forwardingConfig != null) {
|
||||
String method = forwardingConfig.getNode("method").getString("none").toLowerCase(); //Default to lower case for case-insensitive searches
|
||||
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 = forwardingConfig.getNode("pluginMessaging").getNode("channel").getString("NuVotifier");
|
||||
String channel = ConfigLoader.getSpongeConfig().forwarding.pluginMessaging.channel;
|
||||
try {
|
||||
forwardingMethod = new SpongePluginMessagingForwardingSink(this, channel, this);
|
||||
getLogger().info("Receiving votes over PluginMessaging channel '" + channel + "'.");
|
||||
@@ -266,11 +121,11 @@ public class VotifierPlugin implements VoteHandler, com.vexsoftware.votifier.Vot
|
||||
|
||||
@Listener
|
||||
public void onServerStop(GameStoppingServerEvent event) {
|
||||
if (serverGroup != null) {
|
||||
if (serverChannel != null)
|
||||
serverChannel.close();
|
||||
serverGroup.shutdownGracefully();
|
||||
if (bootstrap != null) {
|
||||
bootstrap.shutdown();
|
||||
bootstrap = null;
|
||||
}
|
||||
|
||||
if (forwardingMethod != null)
|
||||
forwardingMethod.halt();
|
||||
|
||||
@@ -287,14 +142,9 @@ public class VotifierPlugin implements VoteHandler, com.vexsoftware.votifier.Vot
|
||||
private String version;
|
||||
|
||||
/**
|
||||
* The server channel.
|
||||
* The server bootstrap.
|
||||
*/
|
||||
private Channel serverChannel;
|
||||
|
||||
/**
|
||||
* The event group handling the channel.
|
||||
*/
|
||||
private NioEventLoopGroup serverGroup;
|
||||
private VotifierServerBootstrap bootstrap;
|
||||
|
||||
/**
|
||||
* The RSA key pair.
|
||||
@@ -317,13 +167,14 @@ public class VotifierPlugin implements VoteHandler, com.vexsoftware.votifier.Vot
|
||||
logger.error("Votifier did not initialize properly!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the version.
|
||||
*
|
||||
* @return The version
|
||||
*/
|
||||
public String getVersion() {
|
||||
return version;
|
||||
@Override
|
||||
public Logger getPluginLogger() {
|
||||
return logger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VotifierScheduler getScheduler() {
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
public boolean isDebug() {
|
||||
@@ -340,8 +191,12 @@ public class VotifierPlugin implements VoteHandler, com.vexsoftware.votifier.Vot
|
||||
return keyPair;
|
||||
}
|
||||
|
||||
public File getConfigDir() {
|
||||
return configDir;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVoteReceived(Channel channel, final Vote vote, VotifierSession.ProtocolVersion protocolVersion) throws Exception {
|
||||
public void onVoteReceived(Channel channel, final Vote vote, VotifierSession.ProtocolVersion protocolVersion) {
|
||||
if (debug) {
|
||||
if (protocolVersion == VotifierSession.ProtocolVersion.ONE) {
|
||||
logger.info("Got a protocol v1 vote record from " + channel.remoteAddress() + " -> " + vote);
|
||||
@@ -350,12 +205,9 @@ public class VotifierPlugin implements VoteHandler, com.vexsoftware.votifier.Vot
|
||||
}
|
||||
}
|
||||
Sponge.getScheduler().createTaskBuilder()
|
||||
.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
VotifierEvent event = new VotifierEvent(vote, Sponge.getCauseStackManager().getCurrentCause());
|
||||
Sponge.getEventManager().post(event);
|
||||
}
|
||||
.execute(() -> {
|
||||
VotifierEvent event = new VotifierEvent(vote, Sponge.getCauseStackManager().getCurrentCause());
|
||||
Sponge.getEventManager().post(event);
|
||||
})
|
||||
.submit(this);
|
||||
}
|
||||
@@ -380,12 +232,9 @@ public class VotifierPlugin implements VoteHandler, com.vexsoftware.votifier.Vot
|
||||
logger.info("Got a forwarded vote -> " + v);
|
||||
}
|
||||
Sponge.getScheduler().createTaskBuilder()
|
||||
.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
VotifierEvent event = new VotifierEvent(v, Sponge.getCauseStackManager().getCurrentCause());
|
||||
Sponge.getEventManager().post(event);
|
||||
}
|
||||
.execute(() -> {
|
||||
VotifierEvent event = new VotifierEvent(v, Sponge.getCauseStackManager().getCurrentCause());
|
||||
Sponge.getEventManager().post(event);
|
||||
})
|
||||
.submit(this);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.vexsoftware.votifier.sponge.config;
|
||||
|
||||
import com.google.common.reflect.TypeToken;
|
||||
import com.vexsoftware.votifier.sponge.VotifierPlugin;
|
||||
import ninja.leaping.configurate.ConfigurationNode;
|
||||
import ninja.leaping.configurate.ConfigurationOptions;
|
||||
import ninja.leaping.configurate.loader.ConfigurationLoader;
|
||||
import ninja.leaping.configurate.yaml.YAMLConfigurationLoader;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class ConfigLoader {
|
||||
|
||||
private static VotifierPlugin plugin;
|
||||
private static SpongeConfig spongeConfig;
|
||||
|
||||
public static void loadConfig(VotifierPlugin pl) {
|
||||
plugin = pl;
|
||||
if (!plugin.getConfigDir().exists()) {
|
||||
plugin.getConfigDir().mkdirs();
|
||||
}
|
||||
try {
|
||||
File file = new File(plugin.getConfigDir(), "config.yml");
|
||||
if (!file.exists()) {
|
||||
file.createNewFile();
|
||||
}
|
||||
ConfigurationLoader loader = YAMLConfigurationLoader.builder().setFile(file).build();
|
||||
ConfigurationNode config = loader.load(ConfigurationOptions.defaults().setShouldCopyDefaults(true));
|
||||
spongeConfig = config.getValue(TypeToken.of(SpongeConfig.class), new SpongeConfig());
|
||||
loader.save(config);
|
||||
} catch (Exception e) {
|
||||
plugin.getLogger().error("Could not load config.", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static SpongeConfig getSpongeConfig() {
|
||||
return spongeConfig;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.vexsoftware.votifier.sponge.config;
|
||||
|
||||
import com.vexsoftware.votifier.util.TokenUtil;
|
||||
import ninja.leaping.configurate.objectmapping.Setting;
|
||||
import ninja.leaping.configurate.objectmapping.serialize.ConfigSerializable;
|
||||
import org.spongepowered.api.Sponge;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
@ConfigSerializable
|
||||
public class SpongeConfig {
|
||||
|
||||
@Setting(comment = "The IP to listen to. Use 0.0.0.0 if you wish to listen to all interfaces on your server. (All IP addresses)\n" +
|
||||
"This defaults to the IP you have configured your server to listen on, or 0.0.0.0 if you have not configured this.")
|
||||
public String host = Sponge.getServer().getBoundAddress().isPresent() ? Sponge.getServer().getBoundAddress().get().getAddress().getHostAddress() : "0.0.0.0";
|
||||
|
||||
@Setting(comment = "Port to listen for new votes on")
|
||||
public int port = 8192;
|
||||
|
||||
@Setting(comment = "Whether or not to print debug messages. In a production system, this should be set to false.\n" +
|
||||
"This is useful when initially setting up NuVotifier to ensure votes are being delivered.")
|
||||
public boolean debug = false;
|
||||
|
||||
@Setting( value = "disable-v1-protocol", comment = "Setting this option to true will disable handling of Protocol v1 packets. While the old protocol is not secure, this\n" +
|
||||
"option is currently not recommended as most voting sites only support the old protocol at present. However, if you are\n" +
|
||||
"using NuVotifier's proxy forwarding mechanism, enabling this option will increase your server's security.")
|
||||
public boolean disableV1Protocol = false;
|
||||
|
||||
@Setting(comment = "All tokens, labeled by the serviceName of each server list.\n" +
|
||||
"Default token for all server lists, if another isn't supplied.")
|
||||
public Map<String, String> tokens = Collections.singletonMap("default", TokenUtil.newToken());
|
||||
|
||||
@Setting(comment = "Configuration section for all vote forwarding to NuVotifier")
|
||||
public Forwarding forwarding = new Forwarding();
|
||||
|
||||
@ConfigSerializable
|
||||
public static class Forwarding {
|
||||
|
||||
@Setting(comment = "Sets whether to set up a remote method for fowarding. Supported methods:\n" +
|
||||
"- none - Does not set up a forwarding method.\n" +
|
||||
"- pluginMessaging - Sets up plugin messaging")
|
||||
public String method = "none";
|
||||
|
||||
@Setting
|
||||
public PluginMessaging pluginMessaging = new PluginMessaging();
|
||||
|
||||
@ConfigSerializable
|
||||
public static class PluginMessaging {
|
||||
|
||||
@Setting
|
||||
public String channel = "NuVotifier:votes";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,4 +26,4 @@ forwarding:
|
||||
# - pluginMessaging - Sets up plugin messaging
|
||||
method: none
|
||||
pluginMessaging:
|
||||
channel: NuVotifier
|
||||
channel: nuvotifier:votes
|
||||
@@ -3,7 +3,7 @@ buildscript {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.github.jengelman.gradle.plugins:shadow:2.0.2'
|
||||
classpath 'com.github.jengelman.gradle.plugins:shadow:2.0.4'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ dependencies {
|
||||
compile project(":nuvotifier-bukkit")
|
||||
compile project(":nuvotifier-bungeecord")
|
||||
compile project(":nuvotifier-sponge")
|
||||
compile project(":nuvotifier-velocity")
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
@@ -20,7 +21,8 @@ shadowJar {
|
||||
classifier = null
|
||||
relocate "io.netty", "com.vexsoftware.votifier.netty"
|
||||
relocate "org.json", "com.vexsoftware.votifier.json"
|
||||
relocate "com.google", "com.vexsoftware.votifier.google"
|
||||
relocate "com.google.code", "com.vexsoftware.votifier.google"
|
||||
relocate "com.google.guava", "com.vexsoftware.votifier.google"
|
||||
relocate "org.apache.commons.io", "com.vexsoftware.votifier.commons.io"
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
dependencies {
|
||||
compileOnly group: "com.velocitypowered", name: "velocity-api", version: "1.0-SNAPSHOT"
|
||||
compile project(":nuvotifier-common")
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
package com.vexsoftware.votifier.velocity;
|
||||
|
||||
import com.velocitypowered.api.event.Subscribe;
|
||||
import com.velocitypowered.api.event.player.ServerConnectedEvent;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.proxy.ServerConnection;
|
||||
import com.velocitypowered.api.proxy.messages.MessageHandler;
|
||||
import com.velocitypowered.api.proxy.server.ServerInfo;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.platform.BackendServer;
|
||||
import com.vexsoftware.votifier.support.forwarding.AbstractPluginMessagingForwardingSource;
|
||||
import com.vexsoftware.votifier.support.forwarding.cache.VoteCache;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public final class OnlineForwardPluginMessagingForwardingSource extends AbstractPluginMessagingForwardingSource {
|
||||
|
||||
private final String fallbackServer;
|
||||
private final VotifierPlugin plugin;
|
||||
|
||||
public OnlineForwardPluginMessagingForwardingSource(String channel, VotifierPlugin plugin, VoteCache cache, String fallbackServer) {
|
||||
super(channel, plugin, cache);
|
||||
this.fallbackServer = fallbackServer;
|
||||
this.plugin = plugin;
|
||||
plugin.getServer().getEventManager().register(plugin, this);
|
||||
plugin.getServer().getChannelRegistrar().register((source, side, identifier, data) -> MessageHandler.ForwardStatus.HANDLED, VelocityUtil.getId(channel));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forward(Vote v) {
|
||||
Optional<Player> p = plugin.getServer().getPlayer(v.getUsername());
|
||||
Optional<ServerConnection> sc = p.flatMap(Player::getCurrentServer);
|
||||
if (sc.isPresent()) {
|
||||
if (forwardSpecific(new VelocityBackendServer(plugin.getServer(), sc.get().getServerInfo()), v)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Optional<ServerInfo> fs = plugin.getServer().getServerInfo(fallbackServer);
|
||||
// nowhere to fall back to, yet still not online. lets save this vote yet!
|
||||
if (!fs.isPresent())
|
||||
attemptToAddToPlayerCache(v, v.getUsername());
|
||||
else if (!forwardSpecific(new VelocityBackendServer(plugin.getServer(), fs.get()), v))
|
||||
attemptToAddToCache(v, fallbackServer);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onServerConnected(final ServerConnectedEvent e) { //Attempt to resend any votes that were previously cached.
|
||||
BackendServer server = new VelocityBackendServer(plugin.getServer(), e.getServer());
|
||||
onServerConnect(server);
|
||||
handlePlayerSwitch(server, e.getPlayer().getUsername());
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package com.vexsoftware.votifier.velocity;
|
||||
|
||||
import com.velocitypowered.api.event.Subscribe;
|
||||
import com.velocitypowered.api.event.player.ServerConnectedEvent;
|
||||
import com.velocitypowered.api.proxy.messages.MessageHandler;
|
||||
import com.vexsoftware.votifier.support.forwarding.AbstractPluginMessagingForwardingSource;
|
||||
import com.vexsoftware.votifier.support.forwarding.cache.VoteCache;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class PluginMessagingForwardingSource extends AbstractPluginMessagingForwardingSource {
|
||||
|
||||
private final VotifierPlugin plugin;
|
||||
|
||||
public PluginMessagingForwardingSource(String channel, List<String> ignoredServers, VotifierPlugin plugin, VoteCache cache) {
|
||||
super(channel, ignoredServers, plugin, cache);
|
||||
this.plugin = plugin;
|
||||
plugin.getServer().getEventManager().register(plugin, this);
|
||||
plugin.getServer().getChannelRegistrar().register((source, side, identifier, data) -> MessageHandler.ForwardStatus.HANDLED, VelocityUtil.getId(channel));
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onServerConnected(final ServerConnectedEvent e) { //Attempt to resend any votes that were previously cached.
|
||||
onServerConnect(new VelocityBackendServer(plugin.getServer(), e.getServer()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.vexsoftware.votifier.velocity;
|
||||
|
||||
import com.velocitypowered.api.proxy.ProxyServer;
|
||||
import com.velocitypowered.api.proxy.ServerConnection;
|
||||
import com.velocitypowered.api.proxy.server.ServerInfo;
|
||||
import com.vexsoftware.votifier.platform.BackendServer;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
class VelocityBackendServer implements BackendServer {
|
||||
private final ProxyServer server;
|
||||
private final ServerInfo info;
|
||||
|
||||
VelocityBackendServer(ProxyServer server, ServerInfo info) {
|
||||
this.server = server;
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return info.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sendPluginMessage(String channel, byte[] data) {
|
||||
Optional<ServerConnection> connection = server.getAllPlayers().stream()
|
||||
.map(p -> p.getCurrentServer().filter(s -> s.getServerInfo().equals(info)))
|
||||
.filter(Optional::isPresent)
|
||||
.findAny()
|
||||
.flatMap(o -> o);
|
||||
if (connection.isPresent()) {
|
||||
connection.get().sendPluginMessage(VelocityUtil.getId(channel), data);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return info.getName();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.vexsoftware.votifier.velocity;
|
||||
|
||||
import com.velocitypowered.api.proxy.ProxyServer;
|
||||
import com.velocitypowered.api.scheduler.ScheduledTask;
|
||||
import com.velocitypowered.api.scheduler.Scheduler;
|
||||
import com.vexsoftware.votifier.platform.scheduler.ScheduledVotifierTask;
|
||||
import com.vexsoftware.votifier.platform.scheduler.VotifierScheduler;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
class VelocityScheduler implements VotifierScheduler {
|
||||
private final ProxyServer server;
|
||||
private final VotifierPlugin plugin;
|
||||
|
||||
public VelocityScheduler(ProxyServer server, VotifierPlugin plugin) {
|
||||
this.server = server;
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
private Scheduler.TaskBuilder builder(Runnable runnable) {
|
||||
return server.getScheduler().buildTask(plugin, runnable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask sync(Runnable runnable) {
|
||||
return onPool(runnable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask onPool(Runnable runnable) {
|
||||
return new TaskWrapper(builder(runnable).schedule());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask delayedSync(Runnable runnable, int delay, TimeUnit unit) {
|
||||
return delayedOnPool(runnable, delay, unit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask delayedOnPool(Runnable runnable, int delay, TimeUnit unit) {
|
||||
return new TaskWrapper(builder(runnable).delay(delay, unit).schedule());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask repeatOnPool(Runnable runnable, int delay, int repeat, TimeUnit unit) {
|
||||
return new TaskWrapper(builder(runnable).delay(delay, unit).repeat(repeat, unit).schedule());
|
||||
}
|
||||
|
||||
private static class TaskWrapper implements ScheduledVotifierTask {
|
||||
private final ScheduledTask task;
|
||||
|
||||
private TaskWrapper(ScheduledTask task) {
|
||||
this.task = task;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
task.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.vexsoftware.votifier.velocity;
|
||||
|
||||
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
|
||||
import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier;
|
||||
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
|
||||
|
||||
public class VelocityUtil {
|
||||
private VelocityUtil() {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
public static ChannelIdentifier getId(String channel) {
|
||||
if (channel.contains(":")) {
|
||||
String[] split = channel.split(":");
|
||||
return MinecraftChannelIdentifier.create(split[0], split[1]);
|
||||
}
|
||||
return new LegacyChannelIdentifier(channel);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,364 @@
|
||||
package com.vexsoftware.votifier.velocity;
|
||||
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.google.inject.Inject;
|
||||
import com.moandjiezana.toml.Toml;
|
||||
import com.velocitypowered.api.event.Subscribe;
|
||||
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
|
||||
import com.velocitypowered.api.event.proxy.ProxyShutdownEvent;
|
||||
import com.velocitypowered.api.plugin.Plugin;
|
||||
import com.velocitypowered.api.plugin.annotation.DataDirectory;
|
||||
import com.velocitypowered.api.proxy.ProxyServer;
|
||||
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.BackendServer;
|
||||
import com.vexsoftware.votifier.platform.ProxyVotifierPlugin;
|
||||
import com.vexsoftware.votifier.platform.scheduler.VotifierScheduler;
|
||||
import com.vexsoftware.votifier.support.forwarding.ForwardingVoteSource;
|
||||
import com.vexsoftware.votifier.support.forwarding.cache.FileVoteCache;
|
||||
import com.vexsoftware.votifier.support.forwarding.cache.MemoryVoteCache;
|
||||
import com.vexsoftware.votifier.support.forwarding.cache.VoteCache;
|
||||
import com.vexsoftware.votifier.support.forwarding.proxy.ProxyForwardingVoteSource;
|
||||
import com.vexsoftware.votifier.util.KeyCreator;
|
||||
import com.vexsoftware.votifier.util.TokenUtil;
|
||||
import com.vexsoftware.votifier.velocity.event.VotifierEvent;
|
||||
import io.netty.channel.Channel;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.security.Key;
|
||||
import java.security.KeyPair;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Plugin(id = "nuvotifier", name = "NuVotifier", version = "2.3.7", authors = "ParallelBlock LLC",
|
||||
description = "Safe, smart, and secure Votifier server plugin")
|
||||
public class VotifierPlugin implements VoteHandler, ProxyVotifierPlugin {
|
||||
|
||||
@Inject
|
||||
public Logger logger;
|
||||
|
||||
@Inject
|
||||
@DataDirectory
|
||||
public Path configDir;
|
||||
|
||||
@Inject
|
||||
public ProxyServer server;
|
||||
|
||||
private VotifierScheduler scheduler;
|
||||
|
||||
@Subscribe
|
||||
public void onServerStart(ProxyInitializeEvent event) {
|
||||
this.scheduler = new VelocityScheduler(server, this);
|
||||
|
||||
// Load configuration.
|
||||
Toml config;
|
||||
try {
|
||||
config = loadConfig();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Unable to load configuration.", e);
|
||||
}
|
||||
|
||||
/*
|
||||
* Create RSA directory and keys if it does not exist; otherwise, read
|
||||
* keys.
|
||||
*/
|
||||
File rsaDirectory = new File(configDir.toFile(), "rsa");
|
||||
try {
|
||||
if (!rsaDirectory.exists()) {
|
||||
rsaDirectory.mkdir();
|
||||
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);
|
||||
gracefulExit();
|
||||
return;
|
||||
}
|
||||
|
||||
debug = config.getBoolean("debug");
|
||||
|
||||
// Load Votifier tokens.
|
||||
config.getTable("tokens").toMap().forEach((service, key) -> {
|
||||
if (key instanceof String) {
|
||||
tokens.put(service, KeyCreator.createKeyFrom((String) key));
|
||||
logger.info("Loaded token for website: " + service);
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize the receiver.
|
||||
final String host = config.getString("host");
|
||||
final int port = Math.toIntExact(config.getLong("port"));
|
||||
if (debug)
|
||||
logger.info("DEBUG mode enabled!");
|
||||
|
||||
final boolean disablev1 = config.getBoolean("disable-v1-protocol", false);
|
||||
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 -> {});
|
||||
|
||||
Toml fwdCfg = config.getTable("forwarding");
|
||||
String fwdMethod = fwdCfg.getString("method", "none").toLowerCase();
|
||||
if ("none".equals(fwdMethod)) {
|
||||
getLogger().info("Method none selected for vote forwarding: Votes will not be forwarded to backend servers.");
|
||||
} else if ("pluginmessaging".equals(fwdMethod)) {
|
||||
Toml pmCfg = fwdCfg.getTable("pluginMessaging");
|
||||
String channel = pmCfg.getString("channel", "NuVotifier");
|
||||
String cacheMethod = pmCfg.getString("cache", "file").toLowerCase();
|
||||
VoteCache voteCache = null;
|
||||
if ("none".equals(cacheMethod)) {
|
||||
getLogger().info("Vote cache none selected for caching: votes that cannot be immediately delivered will be lost.");
|
||||
} else if ("memory".equals(cacheMethod)) {
|
||||
voteCache = new MemoryVoteCache(server.getAllServers().size());
|
||||
getLogger().info("Using in-memory cache for votes that are not able to be delivered.");
|
||||
} else if ("file".equals(cacheMethod)) {
|
||||
try {
|
||||
voteCache = new FileVoteCache(
|
||||
server.getAllServers().size(), this,
|
||||
configDir.resolve(fwdCfg.getTable("file-cache").getString("name")).toFile(),
|
||||
fwdCfg.getTable("file-cache").getLong("cacheTime", -1L));
|
||||
} catch (IOException e) {
|
||||
getLogger().error("Unload to load file cache. Votes will be lost!", e);
|
||||
}
|
||||
}
|
||||
if (!pmCfg.getBoolean("onlySendToJoinedServer")) {
|
||||
List<String> ignoredServers = pmCfg.getList("excludedServers");
|
||||
|
||||
try {
|
||||
forwardingMethod = new PluginMessagingForwardingSource(channel, ignoredServers, this, voteCache);
|
||||
getLogger().info("Forwarding votes over PluginMessaging channel '" + channel + "' for vote forwarding!");
|
||||
} catch (RuntimeException e) {
|
||||
getLogger().error("NuVotifier could not set up PluginMessaging for vote forwarding!", e);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
String fallbackServer = pmCfg.getString("joinedServerFallback", null);
|
||||
if (fallbackServer != null && fallbackServer.isEmpty()) fallbackServer = null;
|
||||
forwardingMethod = new OnlineForwardPluginMessagingForwardingSource(channel, this, voteCache, fallbackServer);
|
||||
} catch (RuntimeException e) {
|
||||
getLogger().error("NuVotifier could not set up PluginMessaging for vote forwarding!", e);
|
||||
}
|
||||
}
|
||||
} else if ("proxy".equals(fwdMethod)) {
|
||||
Toml serverSection = fwdCfg.getTable("proxy");
|
||||
List<ProxyForwardingVoteSource.BackendServer> serverList = new ArrayList<>();
|
||||
for (String s : serverSection.toMap().keySet()) {
|
||||
Toml section = serverSection.getTable(s);
|
||||
InetAddress address;
|
||||
try {
|
||||
address = InetAddress.getByName(section.getString("address"));
|
||||
} catch (UnknownHostException e) {
|
||||
getLogger().info("Address " + section.getString("address") + " couldn't be looked up. Ignoring!");
|
||||
continue;
|
||||
}
|
||||
|
||||
Key token = null;
|
||||
try {
|
||||
token = KeyCreator.createKeyFrom(section.getString("token", section.getString("key")));
|
||||
} catch (IllegalArgumentException e) {
|
||||
getLogger().error(
|
||||
"An exception occurred while attempting to add proxy target '" + s + "' - maybe your token is wrong? " +
|
||||
"Votes will not be forwarded to this server!", e);
|
||||
}
|
||||
|
||||
if (token != null) {
|
||||
ProxyForwardingVoteSource.BackendServer server = new ProxyForwardingVoteSource.BackendServer(s,
|
||||
new InetSocketAddress(address, Math.toIntExact(section.getLong("port"))),
|
||||
token);
|
||||
serverList.add(server);
|
||||
}
|
||||
}
|
||||
|
||||
forwardingMethod = new ProxyForwardingVoteSource(this, bootstrap::client, serverList, null);
|
||||
getLogger().info("Forwarding votes from this NuVotifier instance to another NuVotifier server.");
|
||||
} else {
|
||||
getLogger().error("No vote forwarding method '" + fwdMethod + "' known. Defaulting to noop implementation.");
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onServerStop(ProxyShutdownEvent event) {
|
||||
if (bootstrap != null) {
|
||||
bootstrap.shutdown();
|
||||
bootstrap = null;
|
||||
}
|
||||
|
||||
if (forwardingMethod != null) {
|
||||
forwardingMethod.halt();
|
||||
forwardingMethod = null;
|
||||
}
|
||||
|
||||
logger.info("Votifier disabled.");
|
||||
}
|
||||
|
||||
public ProxyServer getServer() {
|
||||
return server;
|
||||
}
|
||||
|
||||
private Toml loadConfig() throws IOException {
|
||||
if (!Files.exists(configDir)) {
|
||||
Files.createDirectory(configDir);
|
||||
}
|
||||
Path configPath = configDir.resolve("config.toml");
|
||||
try (Reader reader = Files.newBufferedReader(configPath, StandardCharsets.UTF_8)) {
|
||||
return new Toml().read(reader);
|
||||
} catch (NoSuchFileException e) {
|
||||
// This is ok. Just copy the default and load that.
|
||||
// First time run - do some initialization.
|
||||
getLogger().info("Configuring Votifier for the first time...");
|
||||
|
||||
// Initialize the configuration file.
|
||||
String cfgStr = new String(ByteStreams.toByteArray(VotifierPlugin.class.getResourceAsStream("/config.toml")), StandardCharsets.UTF_8);
|
||||
String token = TokenUtil.newToken();
|
||||
// TODO: Velocity doesn't expose any bind address, so use a temporary workaround
|
||||
cfgStr = cfgStr.replace("%ip%", "0.0.0.0");
|
||||
cfgStr = cfgStr.replace("%default_token%", token);
|
||||
|
||||
/*
|
||||
* Remind hosted server admins to be sure they have the right
|
||||
* port number.
|
||||
*/
|
||||
getLogger().info("------------------------------------------------------------------------------");
|
||||
getLogger().info("Assigning NuVotifier to listen on port 8192. If you are hosting BungeeCord on a");
|
||||
getLogger().info("shared server please check with your hosting provider to verify that this port");
|
||||
getLogger().info("is available for your use. Chances are that your hosting provider will assign");
|
||||
getLogger().info("a different port, which you need to specify in config.yml");
|
||||
getLogger().info("------------------------------------------------------------------------------");
|
||||
getLogger().info("Assigning NuVotifier to listen to interface 0.0.0.0. This is usually alright,");
|
||||
getLogger().info("however, if you want NuVotifier to only listen to one interface for security ");
|
||||
getLogger().info("reasons (or you use a shared host), you may change this in the config.yml.");
|
||||
getLogger().info("------------------------------------------------------------------------------");
|
||||
getLogger().info("Your default Votifier token is " + token + ".");
|
||||
getLogger().info("You will need to provide this token when you submit your server to a voting");
|
||||
getLogger().info("list.");
|
||||
getLogger().info("------------------------------------------------------------------------------");
|
||||
|
||||
Files.copy(new ByteArrayInputStream(cfgStr.getBytes(StandardCharsets.UTF_8)), configPath);
|
||||
return new Toml().read(cfgStr);
|
||||
}
|
||||
}
|
||||
|
||||
public Logger getLogger() {
|
||||
return logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* The current Votifier version.
|
||||
*/
|
||||
private String version;
|
||||
|
||||
/**
|
||||
* The server bootstrap.
|
||||
*/
|
||||
private VotifierServerBootstrap bootstrap;
|
||||
|
||||
/**
|
||||
* The RSA key pair.
|
||||
*/
|
||||
private KeyPair keyPair;
|
||||
|
||||
/**
|
||||
* Debug mode flag
|
||||
*/
|
||||
private boolean debug;
|
||||
|
||||
/**
|
||||
* Keys used for websites.
|
||||
*/
|
||||
private Map<String, Key> tokens = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Method used to forward votes to downstream servers
|
||||
*/
|
||||
private ForwardingVoteSource forwardingMethod;
|
||||
|
||||
private void gracefulExit() {
|
||||
logger.error("Votifier did not initialize properly!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Logger getPluginLogger() {
|
||||
return logger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VotifierScheduler getScheduler() {
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
public boolean isDebug() {
|
||||
return debug;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Key> getTokens() {
|
||||
return tokens;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyPair getProtocolV1Key() {
|
||||
return keyPair;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVoteReceived(Channel channel, final Vote vote, VotifierSession.ProtocolVersion protocolVersion) {
|
||||
if (debug) {
|
||||
if (protocolVersion == VotifierSession.ProtocolVersion.ONE) {
|
||||
logger.info("Got a protocol v1 vote record from " + channel.remoteAddress() + " -> " + vote);
|
||||
} else {
|
||||
logger.info("Got a protocol v2 vote record from " + channel.remoteAddress() + " -> " + vote);
|
||||
}
|
||||
}
|
||||
|
||||
server.getEventManager().fireAndForget(new VotifierEvent(vote));
|
||||
if (forwardingMethod != null) {
|
||||
forwardingMethod.forward(vote);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Channel channel, boolean alreadyHandledVote, Throwable throwable) {
|
||||
if (debug) {
|
||||
if (alreadyHandledVote) {
|
||||
logger.error("Vote processed, however an exception " +
|
||||
"occurred with a vote from " + channel.remoteAddress(), throwable);
|
||||
} else {
|
||||
logger.error("Unable to process vote from " + channel.remoteAddress(), throwable);
|
||||
}
|
||||
} else if (!alreadyHandledVote) {
|
||||
logger.error("Unable to process vote from " + channel.remoteAddress());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<BackendServer> getAllBackendServers() {
|
||||
return server.getAllServers().stream().map(s -> new VelocityBackendServer(server, s)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<BackendServer> getServer(String name) {
|
||||
return server.getServerInfo(name).map(s -> new VelocityBackendServer(server, s));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.vexsoftware.votifier.velocity.event;
|
||||
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
|
||||
public class VotifierEvent {
|
||||
private final Vote vote;
|
||||
|
||||
public VotifierEvent(Vote vote) {
|
||||
this.vote = vote;
|
||||
}
|
||||
|
||||
public Vote getVote() {
|
||||
return vote;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VotifierEvent{" +
|
||||
"vote=" + vote +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
# The IP to listen to. Use 0.0.0.0 if you wish to listen to all interfaces on your server. (All IP addresses)
|
||||
# This defaults to the IP you have configured your server to listen on, or 0.0.0.0 if you have not configured this.
|
||||
host = "%ip%"
|
||||
|
||||
# Port to listen for new votes on
|
||||
port = 8192
|
||||
|
||||
# Whether or not to print debug messages. In a production system, this should be set to false.
|
||||
# This is useful when initially setting up NuVotifier to ensure votes are being delivered.
|
||||
debug = false
|
||||
|
||||
# Setting this option to true will disable handling of Protocol v1 packets. While the old protocol is not secure, this
|
||||
# option is currently not recommended as most voting sites only support the old protocol at present. However, if you are
|
||||
# using NuVotifier's proxy forwarding mechanism, enabling this option will increase your server's security.
|
||||
disable-v1-protocol = false
|
||||
|
||||
# All tokens, labeled by the serviceName of each server list.
|
||||
[tokens]
|
||||
# Default token for all server lists, if another isn't supplied.
|
||||
default = "%default_token%"
|
||||
|
||||
# Configuration section for all vote forwarding to NuVotifier
|
||||
[forwarding]
|
||||
# Sets whether to set up a remote method for fowarding. Supported methods:
|
||||
# - none - Does not set up a forwarding method.
|
||||
# - pluginMessaging - Sets up plugin messaging.
|
||||
# - proxy - Proxies votes to other NuVotifier servers from this server.
|
||||
method = "none"
|
||||
|
||||
[forwarding.pluginMessaging]
|
||||
channel = "nuvotifier:votes"
|
||||
|
||||
# Only send votes to certain servers. If a server is present on excludedServers, then votes will NOT be forwarded
|
||||
# to them. If excludedServers is not present, votes will be sent to all servers.
|
||||
# If onlySendToJoinedServer is true, this option is ignored.
|
||||
# excludedServers = ["server1", "server2"]
|
||||
|
||||
# Sets which cache to use if there are no players on the server that the vote is being sent to. Supported caches:
|
||||
# - none - Votes will simply be lost when no players are present on the server.
|
||||
# - memory - Votes will be cached in memory when no players are present. When Bungee is restarted, the votes will be lost.
|
||||
# This method is NOT recommended for production systems!
|
||||
# - file - Votes will be saved to a local file every 3 minutes and on shutdown and reloaded when Bungee is restarted. This is suitable for
|
||||
# most set ups.
|
||||
cache = "file"
|
||||
# Only sends the vote to the server in which the player is currently joined.
|
||||
onlySendToJoinedServer = false
|
||||
|
||||
# Sets which server to send the vote in the event the player is not online when the vote is cast
|
||||
# If you do not want the vote forwarded to a fallback, set this value to empty ('')
|
||||
# ONLY USED IF onlySendToJoinedServer is true!!
|
||||
joinedServerFallback = "Hub"
|
||||
|
||||
[forwarding.file-cache]
|
||||
# Options for file caching.
|
||||
name = "cached-votes.json"
|
||||
# days before a vote is considered 'dead' - removed from cache with a console warning
|
||||
voteTTL = 10
|
||||
|
||||
[forwarding.proxy]
|
||||
# Specify servers to proxy votes for.
|
||||
|
||||
[forwarding.proxy.Hub]
|
||||
address = "127.0.0.1"
|
||||
port = 8193
|
||||
token = "MyKeyHere"
|
||||
|
||||
[forwarding.proxy.Hub2]
|
||||
address = "127.0.0.1"
|
||||
port = 8194
|
||||
token = "MyKeyHere"
|
||||
Reference in new issue
Block a user