Compare commits

...
22 Commits
Author SHA1 Message Date
Tux 9342f75317 2.3.5 2017-05-08 17:27:55 -04:00
Tux f17bb4e414 Remove the enableExternal option.
This one option is the cause of so many support complaints in Discord and on our forums. As vote forwarding using proxying is now the norm, removing this saves us time and effort down the road.
2017-05-08 17:25:54 -04:00
Tux 5f347e538b Closes #24, fixes #21 2017-04-25 19:53:08 -04:00
Tux b4fb8c69a9 Go represent! 2017-02-26 11:15:15 -05:00
Tux 95ebcf22a8 ibj.io/v -> votifier.inaptbox.com 2017-02-26 05:57:34 -05:00
Tux 302da3c907 Integrated Bintray 2017-02-26 05:46:09 -05:00
Tux f8019debd0 Releasing 2.3.4 2017-02-19 21:48:59 -05:00
Tux ab317e76b3 Catch the correct exception 2017-02-19 21:40:46 -05:00
Tux 15556be265 Better loading code 2017-02-19 21:39:08 -05:00
Tux 26958e35fe Don't attempt to "deduplicate" cached votes.
This is in line with original Votifier behavior.
2017-01-15 18:26:19 -05:00
Tux e905881238 Log successful vote attempt IPs 2017-01-15 02:44:43 -05:00
Tux fde7a941b6 Update usage document 2016-12-19 18:49:23 -05:00
Tux 9d24deabe9 Temporarily alleviate issues with large file caches
See #14
2016-12-17 21:32:19 -05:00
Tux c2f331e7ec Updated dependencies.
Bukkit 1.7.2 -> Spigot 1.8.8
SpongeAPI 4.1 -> SpongeAPI 5.0.0
2016-12-08 23:01:32 -05:00
Joseph Hirschfeld 705bddd062 Update sponge API version to 4.1.0 2016-08-15 19:40:48 -04:00
Joseph Hirschfeld 826609db30 Update nuvotifer to 2.3.3 2016-08-15 19:27:11 -04:00
Joseph Hirschfeld 0eca96bd67 Fix null file vote cache issues 2016-08-15 19:20:03 -04:00
Tux f767387840 Implement constant-time comparison 2016-06-25 11:32:19 -04:00
Tux 8ae3c21a23 Read key with UTF8 encoding 2016-06-25 11:30:11 -04:00
Tux be1c056502 Try fixing onlineServer forwarding again 2016-04-03 20:01:35 -04:00
Tux 500142eb5b add validation 2016-03-31 22:11:02 -04:00
Tux 1e5876bc08 Use try-finally 2016-03-26 23:57:00 -04:00
26 changed files with 375 additions and 362 deletions

No files matched your search

+2 -1
View File
@@ -11,7 +11,8 @@
/build.xml
# maven
/target
/*/target/
dependency-reduced-pom.xml
# vim
.*.sw[a-p]
+1 -1
View File
@@ -6,6 +6,6 @@ deploy:
provider: releases
api_key:
secure: j8y4qeDOXAYjmf6WU6EbpUA6AKTz26yJ4zNb6oAuB4h9Y3PpBRjFjCIBL6jJQnMxZMLjdgk72/3uqS7+//jJ8/iiY/OJhO2L+2us8TJDcQhH5EI1USe/lcZKf60iJjmd9Q9pjh1eHNYhxr6kyNLnpa6fw5b/hIBT6CvNZAJV1qbhRpHwtzbgdlMFaHGnpX192vJNw7E3uuJuSwue5XNK2ybDp81oTrM0Q16JCq/UsQhGq4Ba3JffOhdBhpfg4EI2jJfe3vN8CFqojCYKNSp7eL6DPFPaV4x8Ly8cRIOZ5Zb60m5SyWAcagPKJuUq3ZOTHfeVTjkI9ymZMHe1pla2Mq6eitIBwZIGnonjyMxcTdkR9EJgq9jo+1PzdStR/NlGLakBiwPzk8Ov0DZ+iOwpYUkISUkMU0UgKWwoFoRu18CHtp+ALEsoYZhBQGlcShq+4EGv9VMNBYk9vN3KXNRMVGKqXd6O3T6shvSQgoUn/3mvksOTAuhS5J4U3woHecDrXwl/RfP2aqiHhX/S7p7yY4hVcHu8kiSYpNQMbay+rNd6HxFcRe/U9HOUt6kW8/yo1ROyCxs3ze+N6LtGDHspgzkFaay6I6wMHYNDnzzRP4MC3Zc73zxViXol6dGBk4Tawsm16VaI6b1uyWLBGKvuU23azkmMTHUmGFXl2/BTaYw=
file: universal/target/nuvotifier-universal-2.3.2.2.jar
file: universal/target/nuvotifier-universal-2.3.5.jar
on:
tags: true
+1 -1
View File
@@ -74,7 +74,7 @@ The message is JSON-encoded containing the following data:
A response is given after the vote has been received and parsed, but not yet processed.
For your convenience, the NuVotifier team has provided client libraries in [JavaScript](https://github.com/NuVotifier/votifier2-js) and [PHP](https://github.com/NuVotifier/votifier2-php). If you are stuck, these libraries may help you.
For your convenience, the NuVotifier team has provided client libraries in [JavaScript](https://github.com/NuVotifier/votifier2-js), [PHP](https://github.com/NuVotifier/votifier2-php) and [Go](https://github.com/minecrafter/go-votifier). If you are stuck, these libraries may help you.
### Protocol v1 (deprecated)
+4 -5
View File
@@ -5,7 +5,7 @@
<parent>
<artifactId>nuvotifier</artifactId>
<groupId>com.vexsoftware</groupId>
<version>2.3.2.2</version>
<version>2.3.5</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -56,10 +56,9 @@
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.bukkit</groupId>
<artifactId>bukkit</artifactId>
<version>1.7.2-R0.1</version>
<type>jar</type>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.8.8-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
</dependencies>
@@ -190,88 +190,82 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
debug = cfg.getBoolean("debug", false);
boolean setUpPort = cfg.getBoolean("enableExternal", true); //Always default to running the external port
// Load Votifier tokens.
ConfigurationSection tokenSection = cfg.getConfigurationSection("tokens");
if (setUpPort) {
// Load Votifier tokens.
ConfigurationSection tokenSection = cfg.getConfigurationSection("tokens");
if (tokenSection != null) {
Map<String, Object> websites = tokenSection.getValues(false);
for (Map.Entry<String, Object> website : websites.entrySet()) {
tokens.put(website.getKey(), KeyCreator.createKeyFrom(website.getValue().toString()));
getLogger().info("Loaded token for website: " + website.getKey());
}
} else {
String token = TokenUtil.newToken();
tokenSection = cfg.createSection("tokens");
tokenSection.set("default", token);
tokens.put("default", KeyCreator.createKeyFrom(token));
try {
cfg.save(config);
} catch (IOException e) {
getLogger().log(Level.SEVERE,
"Error generating Votifier token", e);
gracefulExit();
return;
}
getLogger().info("------------------------------------------------------------------------------");
getLogger().info("No tokens were found in your configuration, so we've generated one for you.");
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("------------------------------------------------------------------------------");
if (tokenSection != null) {
Map<String, Object> websites = tokenSection.getValues(false);
for (Map.Entry<String, Object> website : websites.entrySet()) {
tokens.put(website.getKey(), KeyCreator.createKeyFrom(website.getValue().toString()));
getLogger().info("Loaded token for website: " + website.getKey());
}
// Initialize the receiver.
final String host = cfg.getString("host", hostAddr);
final int port = cfg.getInt("port", 8192);
if (debug)
getLogger().info("DEBUG mode enabled!");
final boolean disablev1 = cfg.getBoolean("disable-v1-protocol");
if (disablev1) {
getLogger().info("------------------------------------------------------------------------------");
getLogger().info("Votifier protocol v1 parsing has been disabled. Most voting websites do not");
getLogger().info("currently support the modern Votifier protocol in NuVotifier.");
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());
}
}
});
} else {
getLogger().info("You have enableExternal set to false in your config.yml. NuVotifier will NOT listen to votes coming in from an external voting list.");
String token = TokenUtil.newToken();
tokenSection = cfg.createSection("tokens");
tokenSection.set("default", token);
tokens.put("default", KeyCreator.createKeyFrom(token));
try {
cfg.save(config);
} catch (IOException e) {
getLogger().log(Level.SEVERE,
"Error generating Votifier token", e);
gracefulExit();
return;
}
getLogger().info("------------------------------------------------------------------------------");
getLogger().info("No tokens were found in your configuration, so we've generated one for you.");
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("------------------------------------------------------------------------------");
}
// Initialize the receiver.
final String host = cfg.getString("host", hostAddr);
final int port = cfg.getInt("port", 8192);
if (debug)
getLogger().info("DEBUG mode enabled!");
final boolean disablev1 = cfg.getBoolean("disable-v1-protocol");
if (disablev1) {
getLogger().info("------------------------------------------------------------------------------");
getLogger().info("Votifier protocol v1 parsing has been disabled. Most voting websites do not");
getLogger().info("currently support the modern Votifier protocol in NuVotifier.");
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());
}
}
});
ConfigurationSection forwardingConfig = cfg.getConfigurationSection("forwarding");
if (forwardingConfig != null) {
String method = forwardingConfig.getString("method", "none").toLowerCase(); //Default to lower case for case-insensitive searches
@@ -342,12 +336,12 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
}
@Override
public void onVoteReceived(final Vote vote, VotifierSession.ProtocolVersion protocolVersion) throws Exception {
public void onVoteReceived(Channel channel, final Vote vote, VotifierSession.ProtocolVersion protocolVersion) throws Exception {
if (debug) {
if (protocolVersion == VotifierSession.ProtocolVersion.ONE) {
getLogger().info("Got a protocol v1 vote record -> " + vote);
getLogger().info("Got a protocol v1 vote record from " + channel.remoteAddress() + " -> " + vote);
} else {
getLogger().info("Got a protocol v2 vote record -> " + vote);
getLogger().info("Got a protocol v2 vote record from " + channel.remoteAddress() + " -> " + vote);
}
}
Bukkit.getScheduler().runTask(this, new Runnable() {
@@ -9,10 +9,6 @@ port: 8192
# This is useful when initially setting up NuVotifier to ensure votes are being delivered.
debug: false
# Setting this value to false will turn off the votifier port listening for external votes. This is beneficial if the server
# is only listening for votes coming from NuVotifier running on your BungeeCord.
enableExternal: true
# 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.
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<artifactId>nuvotifier</artifactId>
<groupId>com.vexsoftware</groupId>
<version>2.3.2.2</version>
<version>2.3.5</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -321,12 +321,12 @@ public class NuVotifier extends Plugin implements VoteHandler, VotifierPlugin {
}
@Override
public void onVoteReceived(final Vote vote, VotifierSession.ProtocolVersion protocolVersion) throws Exception {
public void onVoteReceived(Channel channel, final Vote vote, VotifierSession.ProtocolVersion protocolVersion) throws Exception {
if (debug) {
if (protocolVersion == VotifierSession.ProtocolVersion.ONE) {
getLogger().info("Got a protocol v1 vote record -> " + vote);
getLogger().info("Got a protocol v1 vote record from " + channel.remoteAddress() + " -> " + vote);
} else {
getLogger().info("Got a protocol v2 vote record -> " + vote);
getLogger().info("Got a protocol v2 vote record from " + channel.remoteAddress() + " -> " + vote);
}
}
@@ -0,0 +1,108 @@
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.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);
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(e.getServer().getInfo(), v)) {
evicted++;
} else {
// Re-add to cache to send later.
cache.addToCache(v, serverName);
unsuccessfulEvictions++;
}
}
if (nuVotifier.isDebug()) {
nuVotifier.getLogger().info("Successfully evicted " + evicted + " votes to server '" + serverName + "'.");
if (unsuccessfulEvictions > 0) {
nuVotifier.getLogger().info("Held " + unsuccessfulEvictions + " votes for server '" + serverName + "'.");
}
}
}
}, 1, TimeUnit.SECONDS);
}
}
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);
}
}
@@ -8,16 +8,18 @@ import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.PluginMessageEvent;
import net.md_5.bungee.api.event.ServerConnectedEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.event.EventHandler;
/**
* @author Joseph Hirschfeld
* @date 12/31/2015
*/
public final class OnlineForwardPluginMessagingForwardingSource extends PluginMessagingForwardingSource {
public final class OnlineForwardPluginMessagingForwardingSource extends AbstractPluginMessagingForwardingSource implements Listener {
public OnlineForwardPluginMessagingForwardingSource(String channel, NuVotifier nuVotifier, VoteCache cache, String fallbackServer) {
super(channel, nuVotifier, cache);
this.fallbackServer = fallbackServer;
ProxyServer.getInstance().getPluginManager().registerListener(nuVotifier, this);
}
private final String fallbackServer;
@@ -27,7 +29,7 @@ public final class OnlineForwardPluginMessagingForwardingSource extends PluginMe
ProxiedPlayer p = ProxyServer.getInstance().getPlayer(v.getUsername());
if (p == null || !forwardSpecific(p.getServer().getInfo(), v)) {
ServerInfo serverInfo = ProxyServer.getInstance().getServers().get(fallbackServer);
if (serverInfo == null || !forwardSpecific(serverInfo, v)) {
if (serverInfo != null && !forwardSpecific(serverInfo, v)) {
attemptToAddToCache(v, fallbackServer);
}
}
@@ -1,75 +1,25 @@
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.api.plugin.Listener;
import net.md_5.bungee.event.EventHandler;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
/**
* Created by Joe Hirschfeld on 10/20/2015.
*/
public class PluginMessagingForwardingSource implements ForwardingVoteSource, Listener {
public class PluginMessagingForwardingSource extends AbstractPluginMessagingForwardingSource implements Listener {
public PluginMessagingForwardingSource(String channel, List<String> ignoredServers, NuVotifier nuVotifier, VoteCache cache) {
ProxyServer.getInstance().registerChannel(channel);
super(channel, ignoredServers, nuVotifier, cache);
ProxyServer.getInstance().getPluginManager().registerListener(nuVotifier, this);
this.channel = channel;
this.nuVotifier = nuVotifier;
this.cache = cache;
this.ignoredServers = ignoredServers;
}
protected PluginMessagingForwardingSource(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);
}
}
super(channel, nuVotifier, voteCache);
ProxyServer.getInstance().getPluginManager().registerListener(nuVotifier, this);
}
@EventHandler
@@ -81,43 +31,4 @@ public class PluginMessagingForwardingSource implements ForwardingVoteSource, Li
public void onServerConnected(final ServerConnectedEvent e) { //Attempt to resend any votes that were previously cached.
handleServerConnected(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);
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(e.getServer().getInfo(), v)) {
evicted++;
} else {
// Re-add to cache to send later.
cache.addToCache(v, serverName);
unsuccessfulEvictions++;
}
}
if (nuVotifier.isDebug()) {
nuVotifier.getLogger().info("Successfully evicted " + evicted + " votes to server '" + serverName + "'.");
if (unsuccessfulEvictions > 0) {
nuVotifier.getLogger().info("Held " + unsuccessfulEvictions + " votes for server '" + serverName + "'.");
}
}
}
}, 1, TimeUnit.SECONDS);
}
}
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);
}
}
@@ -7,15 +7,11 @@ import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.api.scheduler.ScheduledTask;
import org.json.JSONArray;
import org.json.JSONObject;
import org.json.JSONTokener;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
@@ -42,37 +38,41 @@ public class FileVoteCache extends MemoryVoteCache {
}
private void load() throws IOException {
if (cacheFile.exists()) {
// Load the cache from disk
JSONObject object = new JSONObject(Files.toString(cacheFile, StandardCharsets.UTF_8));
// Load the cache from disk
JSONObject object;
try (BufferedReader reader = Files.newReader(cacheFile, StandardCharsets.UTF_8)) {
object = new JSONObject(new JSONTokener(reader));
} catch (FileNotFoundException e) {
object = new JSONObject();
}
// Deserialize all votes contained
for (Object server : object.keySet()) {
JSONArray voteArray = object.optJSONArray(((String) server));
if (voteArray == null) continue;
Set<Vote> votes = new LinkedHashSet<>(voteArray.length());
for (int i = 0; i < voteArray.length(); i++) {
votes.add(new Vote(voteArray.getJSONObject(i)));
}
voteCache.put(((String) server), votes);
// Deserialize all votes contained
for (Object server : object.keySet()) {
JSONArray voteArray = object.getJSONArray(((String) server));
List<Vote> votes = new ArrayList<>(voteArray.length());
for (int i = 0; i < voteArray.length(); i++) {
JSONObject voteObject = voteArray.getJSONObject(i);
votes.add(new Vote(voteObject));
}
} else {
cacheFile.createNewFile();
voteCache.put(((String) server), votes);
}
}
public void save() throws IOException {
cacheLock.lock();
// Create a copy of the votes.
JSONObject votesObject = new JSONObject();
for (Map.Entry<String, Collection<Vote>> entry : voteCache.entrySet()) {
JSONArray array = new JSONArray();
for (Vote vote : entry.getValue()) {
array.put(vote.serialize());
try {
// Create a copy of the votes.
for (Map.Entry<String, Collection<Vote>> entry : voteCache.entrySet()) {
JSONArray array = new JSONArray();
for (Vote vote : entry.getValue()) {
array.put(vote.serialize());
}
votesObject.put(entry.getKey(), array);
}
votesObject.put(entry.getKey(), array);
} finally {
cacheLock.unlock();
}
cacheLock.unlock();
try (BufferedWriter writer = Files.newWriter(cacheFile, StandardCharsets.UTF_8)) {
votesObject.write(writer);
@@ -1,5 +1,6 @@
package com.vexsoftware.votifier.bungee.forwarding.cache;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.vexsoftware.votifier.model.Vote;
@@ -22,28 +23,38 @@ public class MemoryVoteCache implements VoteCache {
@Override
public Collection<String> getCachedServers() {
cacheLock.lock();
Set<String> servers = ImmutableSet.copyOf(voteCache.keySet());
cacheLock.unlock();
return servers;
try {
return ImmutableList.copyOf(voteCache.keySet());
} finally {
cacheLock.unlock();
}
}
@Override
public void addToCache(Vote v, String server) {
if (server == null) throw new NullPointerException();
cacheLock.lock();
Collection<Vote> voteCollection = voteCache.get(server);
if (voteCollection == null) {
voteCollection = new LinkedHashSet<>();
voteCache.put(server, voteCollection);
try {
Collection<Vote> voteCollection = voteCache.get(server);
if (voteCollection == null) {
voteCollection = new ArrayList<>();
voteCache.put(server, voteCollection);
}
voteCollection.add(v);
} finally {
cacheLock.unlock();
}
voteCollection.add(v);
cacheLock.unlock();
}
@Override
public Collection<Vote> evict(String server) {
if (server == null) throw new NullPointerException();
cacheLock.lock();
Collection<Vote> fromCollection = voteCache.remove(server);
cacheLock.unlock();
return fromCollection != null ? ImmutableSet.copyOf(fromCollection) : ImmutableSet.<Vote>of();
try {
Collection<Vote> fromCollection = voteCache.remove(server);
return fromCollection != null ? fromCollection : ImmutableList.<Vote>of();
} finally {
cacheLock.unlock();
}
}
}
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<artifactId>nuvotifier</artifactId>
<groupId>com.vexsoftware</groupId>
<version>2.3.2.2</version>
<version>2.3.5</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -5,7 +5,7 @@ import com.vexsoftware.votifier.net.VotifierSession;
import io.netty.channel.Channel;
public interface VoteHandler {
void onVoteReceived(Vote vote, VotifierSession.ProtocolVersion protocolVersion) throws Exception;
void onVoteReceived(Channel channel, Vote vote, VotifierSession.ProtocolVersion protocolVersion) throws Exception;
void onError(Channel channel, Throwable throwable);
}
@@ -19,7 +19,7 @@ public class VoteInboundHandler extends SimpleChannelInboundHandler<Vote> {
protected void channelRead0(ChannelHandlerContext ctx, final Vote vote) throws Exception {
VotifierSession session = ctx.channel().attr(VotifierSession.KEY).get();
handler.onVoteReceived(vote, session.getVersion());
handler.onVoteReceived(ctx.channel(), vote, session.getVersion());
if (session.getVersion() == VotifierSession.ProtocolVersion.ONE) {
ctx.close();
@@ -12,10 +12,7 @@ import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.*;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
@@ -92,6 +89,6 @@ public class VotifierProtocol2Decoder extends MessageToMessageDecoder<String> {
mac2.reset();
byte[] realSig = mac2.doFinal(calculatedSig);
return Arrays.equals(clientSig, realSig);
return MessageDigest.isEqual(clientSig, realSig);
}
}
@@ -21,6 +21,7 @@ package com.vexsoftware.votifier.net.protocol.v1crypto;
import javax.xml.bind.DatatypeConverter;
import java.io.File;
import java.io.FileOutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.security.KeyFactory;
import java.security.KeyPair;
@@ -45,21 +46,18 @@ public class RSAIO {
PrivateKey privateKey = keyPair.getPrivate();
PublicKey publicKey = keyPair.getPublic();
// Store the public key.
// Store the public and private keys.
X509EncodedKeySpec publicSpec = new X509EncodedKeySpec(
publicKey.getEncoded());
FileOutputStream out = new FileOutputStream(directory + "/public.key");
out.write(DatatypeConverter.printBase64Binary(publicSpec.getEncoded())
.getBytes());
out.close();
// Store the private key.
PKCS8EncodedKeySpec privateSpec = new PKCS8EncodedKeySpec(
privateKey.getEncoded());
out = new FileOutputStream(directory + "/private.key");
out.write(DatatypeConverter.printBase64Binary(privateSpec.getEncoded())
.getBytes());
out.close();
try (FileOutputStream publicOut = new FileOutputStream(directory + "/public.key");
FileOutputStream privateOut = new FileOutputStream(directory + "/private.key")) {
publicOut.write(DatatypeConverter.printBase64Binary(publicSpec.getEncoded())
.getBytes(StandardCharsets.UTF_8));
privateOut.write(DatatypeConverter.printBase64Binary(privateSpec.getEncoded())
.getBytes(StandardCharsets.UTF_8));
}
}
/**
@@ -75,13 +73,13 @@ public class RSAIO {
File publicKeyFile = new File(directory + "/public.key");
byte[] encodedPublicKey = Files.readAllBytes(publicKeyFile.toPath());
encodedPublicKey = DatatypeConverter.parseBase64Binary(new String(
encodedPublicKey));
encodedPublicKey, StandardCharsets.UTF_8));
// Read the private key file.
File privateKeyFile = new File(directory + "/private.key");
byte[] encodedPrivateKey = Files.readAllBytes(privateKeyFile.toPath());
encodedPrivateKey = DatatypeConverter.parseBase64Binary(new String(
encodedPrivateKey));
encodedPrivateKey, StandardCharsets.UTF_8));
// Instantiate and return the key pair.
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
@@ -74,11 +74,11 @@ public class StandaloneVotifierPlugin implements VotifierPlugin, VoteHandler {
@Override
public String getVersion() {
return "2.3.2.2";
return "2.3.5";
}
@Override
public void onVoteReceived(Vote vote, VotifierSession.ProtocolVersion protocolVersion) throws Exception {
public void onVoteReceived(Channel channel, Vote vote, VotifierSession.ProtocolVersion protocolVersion) throws Exception {
receiver.onVote(vote);
}
@@ -7,6 +7,7 @@ import java.io.File;
import java.net.InetSocketAddress;
import java.security.Key;
import java.security.KeyPair;
import java.security.interfaces.RSAKey;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
@@ -27,6 +28,9 @@ public class VotifierServerBuilder {
public VotifierServerBuilder v1Key(KeyPair v1Key) {
this.v1Key = Objects.requireNonNull(v1Key, "v1Key");
if (!(v1Key.getPrivate() instanceof RSAKey)) {
throw new IllegalArgumentException("Provided key is not an RSA key.");
}
return this;
}
+6 -9
View File
@@ -4,7 +4,7 @@
<groupId>com.vexsoftware</groupId>
<artifactId>nuvotifier</artifactId>
<packaging>pom</packaging>
<version>2.3.2.2</version>
<version>2.3.5</version>
<modules>
<module>common</module>
<module>bukkit</module>
@@ -23,20 +23,17 @@
<url>https://oss.sonatype.org/content/repositories/snapshots/</url>
</repository>
<repository>
<id>bukkit</id>
<url>http://repo.bukkit.org/content/groups/public/</url>
<id>spigotmc-repo</id>
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
</repository>
</repositories>
<distributionManagement>
<repository>
<id>minimumio-repo</id>
<url>http://repo.minimum.io/content/repositories/releases/</url>
<id>bintray-nuvotifier-maven</id>
<name>nuvotifier-maven</name>
<url>https://api.bintray.com/maven/nuvotifier/maven/NuVotifier/;publish=1</url>
</repository>
<snapshotRepository>
<id>minimumio-repo-snapshots</id>
<url>http://repo.minimum.io/content/repositories/snapshots/</url>
</snapshotRepository>
</distributionManagement>
<build>
+7 -2
View File
@@ -5,12 +5,17 @@
<parent>
<artifactId>nuvotifier</artifactId>
<groupId>com.vexsoftware</groupId>
<version>2.3.2.2</version>
<version>2.3.5</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>nuvotifier-sponge</artifactId>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<repositories>
<repository>
<id>sponge-maven-repo</id>
@@ -50,7 +55,7 @@
<dependency>
<groupId>org.spongepowered</groupId>
<artifactId>spongeapi</artifactId>
<version>4.0.1</version>
<version>5.0.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
@@ -53,7 +53,7 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
@Plugin(id = "nuvotifier", name = "NuVotifier", version = "2.3.2.2")
@Plugin(id = "nuvotifier", name = "NuVotifier", version = "2.3.5")
public class VotifierPlugin implements VoteHandler, com.vexsoftware.votifier.VotifierPlugin, ForwardedVoteListener {
private Logger logger;
@Inject
@@ -163,87 +163,81 @@ public class VotifierPlugin implements VoteHandler, com.vexsoftware.votifier.Vot
debug = node.getNode("debug").getBoolean(false);
boolean setUpPort = node.getNode("enableExternal").getBoolean(true); //Always default to running the external port
// Load Votifier tokens.
ConfigurationNode tokenSection = node.getNode("tokens");
if (setUpPort) {
// 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());
}
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("------------------------------------------------------------------------------");
}
// Initialize the receiver.
final String host = node.getNode("host").getString(hostAddr);
final int port = node.getNode("port").getInt(8192);
if (debug)
logger.info("DEBUG mode enabled!");
final boolean disablev1 = node.getNode("disable-v1-protocol").getBoolean(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("------------------------------------------------------------------------------");
}
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());
}
}
});
} else {
logger.info("You have enableExternal set to false in your config.yml. NuVotifier will NOT listen to votes coming in from an external voting list.");
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("------------------------------------------------------------------------------");
}
// Initialize the receiver.
final String host = node.getNode("host").getString(hostAddr);
final int port = node.getNode("port").getInt(8192);
if (debug)
logger.info("DEBUG mode enabled!");
final boolean disablev1 = node.getNode("disable-v1-protocol").getBoolean(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("------------------------------------------------------------------------------");
}
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());
}
}
});
ConfigurationNode forwardingConfig = node.getNode("forwarding");
if (forwardingConfig != null) {
String method = forwardingConfig.getNode("method").getString("none").toLowerCase(); //Default to lower case for case-insensitive searches
@@ -340,12 +334,12 @@ public class VotifierPlugin implements VoteHandler, com.vexsoftware.votifier.Vot
}
@Override
public void onVoteReceived(final Vote vote, VotifierSession.ProtocolVersion protocolVersion) throws Exception {
public void onVoteReceived(Channel channel, final Vote vote, VotifierSession.ProtocolVersion protocolVersion) throws Exception {
if (debug) {
if (protocolVersion == VotifierSession.ProtocolVersion.ONE) {
logger.info("Got a protocol v1 vote record -> " + vote);
logger.info("Got a protocol v1 vote record from " + channel.remoteAddress() + " -> " + vote);
} else {
logger.info("Got a protocol v2 vote record -> " + vote);
logger.info("Got a protocol v2 vote record from " + channel.remoteAddress() + " -> " + vote);
}
}
Sponge.getScheduler().createTaskBuilder()
@@ -9,10 +9,6 @@ port: 8192
# This is useful when initially setting up NuVotifier to ensure votes are being delivered.
debug: false
# Setting this value to false will turn off the votifier port listening for external votes. This is beneficial if the server
# is only listening for votes coming from NuVotifier running on your BungeeCord.
enableExternal: true
# 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.
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<artifactId>nuvotifier</artifactId>
<groupId>com.vexsoftware</groupId>
<version>2.3.2.2</version>
<version>2.3.5</version>
</parent>
<modelVersion>4.0.0</modelVersion>
+13 -13
View File
@@ -4,7 +4,7 @@ networks.
# Installation
The NuVotifier Universal jar (the default jar that is distributed on spigotmc.org) can be installed on Bukkit, BungeeCord,
and Sponge servers as-is without modification.
and Sponge servers as-is.
The Universal JAR can be downloaded off our [SpigotMC resource page](https://www.spigotmc.org/resources/nuvotifier.13449/).
@@ -18,7 +18,7 @@ the legacy protocol will be referred to v1. As of writing, most if not all vote
## Enabling the NuVotifier Server port
NuVotifier allows for the server port to be turned off when NuVotifier is only being used to forward votes. This is only
NuVotifier allows for the server port to be turned off when NuVotifier is only receiving forwarded votes. This is only
an option on Bukkit based servers, since it would not make logical sense for the port to be turned off on Bungee servers.
If you are receiving votes from a server list directly on the server in question (votes are not being forwarded through plugin
@@ -28,10 +28,10 @@ be set to false.
## Port Configuration
One of NuVotifier's functions is acting as a vote server (receiver). The IP for NuVotifier to bind to is configured
NuVotifier's main function is acting as a vote server (receiver). The IP for NuVotifier to bind to is configured
by the `host` and `ip` configuration values within the config.yml files within the NuVotifier plugin folder.
Most times, you will want this `host` value to be set to `0.0.0.0` or your server's ip. `0.0.0.0` will bind to all
ip ports on your machine, while specifying a certain IP will only bind to that ip. If you don't know what to set this
network addresses on your machine, while specifying a certain IP will only bind to that ip. If you don't know what to set this
to, `0.0.0.0` is a safe bet. By default, NuVotifier listens on port 8192. This is the same as the default Votifier port.
If you change this, you will have to remember this when setting up forwarding or server lists.
@@ -39,7 +39,7 @@ If you change this, you will have to remember this when setting up forwarding or
When first setting up NuVotifier, you will probably want debug mode to be enabled. This prints extra information to the
console, ensuring that votes are being processed as they should. If you are experiencing an error, or having issues with
votes being processed, you should first look to enable this. Debug mode can be turned on by setting `debug` in the config.yml
votes being processed, you should first enable this. Debug mode can be turned on by setting `debug` in the config.yml
to true.
## Tokens vs Public Key
@@ -51,7 +51,7 @@ should be used. When setting up the `proxy` forwarding method, tokens should be
Making a new token is as easy as adding another entry into `tokens` with the serviceName as the key and whatever string you want
as the value. You can usually find the serviceName of a vote provider on their setup page if they use v2.
Unless a website specifies it, you should assume that the website is using v1, and you should enter your `rsa/public.key`.
Unless a website specifies it, you should assume that the website is using v1, and you should use `rsa/public.key`.
## Forwarding
@@ -90,22 +90,22 @@ This is suitable for testing, but not for production system.
Votes can also be cached in a file located in the NuVotifier plugin folder. To enable this, you can set `cache` to file.
If you want to change the file location from the default, you can set this under the `name` config under the `file` section.
This caching method is suitable for most people.
This caching method is suitable for most people. This is the default option.
##### No Caching
Caching can also be turned off. This is the default option. You can do this by setting `cache` to `none`. This is also not
recommended, and is only suitable for a certain number of setups.
Caching can also be turned off. You can do this by setting `cache` to `none`. This is also not recommended, and is only
suitable for a certain number of setups.
### Proxy Based Forwarding
You can also forward votes using the proxy method. This will cause the BungeeCord to act as a voting websites, sending votes
You can also forward votes using the proxy method. This will cause the BungeeCord to act like a voting website, sending votes
to each server's NuVotifier port. To enable this method, you must set the `method` under `forwarding` to `proxy` in your
BungeeCord config.yml. Within the server's config.yml, `forwarding` should be set to `none`.
For each server you wish to forward to, you must add each to your BungeeCord config.yml under the `proxy` section of `forwarding`.
For each, you must specify the ip address (`address`), port (`port`) and token (`key`). Each backend Bukkit server should have
1 default token under the `tokens` section. This is the token that should be used as the token for the `key` in that server's
For each, you must specify the ip address (`address`), port (`port`) and token (`token`). Each backend Bukkit server should have
1 default token under the `tokens` section. This is the token that should be used as the token for the `token` in that server's
section within the `proxy` section. If the server is on the same server (same ip) as the BungeeCord server, you may
use the `127.0.0.1` or `localhost` address, setting both the `proxy` forwarding address and Bukkit config.yml address to it.
@@ -122,7 +122,7 @@ and for Sponge [here](https://github.com/NuVotifier/NuVotifier/blob/master/spong
## Testing
There is a free resource for testing your NuVotifier setups at [ibj.io/v](https://ibj.io/v). If you are setting up your
There is a free resource for testing your NuVotifier setups at [votifier.inaptbox.com](https://votifier.inaptbox.com). If you are setting up your
network or server and want a server to get test votes on, you are more than welcome to use it.