Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4df84efdcd | ||
|
|
95fc2368a5 | ||
|
|
9d69da2dab | ||
|
|
65007e248e | ||
|
|
f1f564d67a | ||
|
|
f80de12ae2 | ||
|
|
acb19a520a | ||
|
|
9c27071213 | ||
|
|
0f6020818d | ||
|
|
fcf6c93404 | ||
|
|
3d0f255ab4 | ||
|
|
ede5cae333 | ||
|
|
b8f9cb937a | ||
|
|
1fae11c0ff | ||
|
|
41eb6327e6 | ||
|
|
c723fc7eac | ||
|
|
7812823367 | ||
|
|
2d5c2c78d2 | ||
|
|
6e64890c04 | ||
|
|
d269e9da6b | ||
|
|
5466876f41 | ||
|
|
4003006fa4 | ||
|
|
13d0cb1d57 | ||
|
|
15ad7d5abc | ||
|
|
8966fbf3d8 | ||
|
|
616d25683f | ||
|
|
669c8a769e | ||
|
|
7b10eca263 | ||
|
|
d22f4cd58b | ||
|
|
8ba7e87067 | ||
|
|
5776acaf34 | ||
|
|
90f6ed8b55 | ||
|
|
616bd4ad01 | ||
|
|
448baf0329 | ||
|
|
13fe852bcd | ||
|
|
c996e36b97 | ||
|
|
ad9e6f5fe8 | ||
|
|
53eb9c8fec | ||
|
|
6dbda167d3 | ||
|
|
be9f92d5cd | ||
|
|
96bf715fbc | ||
|
|
628273413b | ||
|
|
bbfa9e4aa9 | ||
|
|
a34b07f2d1 | ||
|
|
1d90a59d5a | ||
|
|
2734836e58 | ||
|
|
768daf099f | ||
|
|
b6292667b8 | ||
|
|
8519535566 | ||
|
|
08e907678f | ||
|
|
27b93ac045 | ||
|
|
97f4c91c24 | ||
|
|
e919c845f7 | ||
|
|
1fb35276f7 | ||
|
|
f5f596f21c | ||
|
|
88523db213 | ||
|
|
3599b0fa1b | ||
|
|
950b543e98 | ||
|
|
e66927dd1a | ||
|
|
98d4f69ffb | ||
|
|
ad44c76309 | ||
|
|
58de66699e | ||
|
|
b81052327d | ||
|
|
59a3eff421 | ||
|
|
411a529e18 | ||
|
|
4d6946ab2c | ||
|
|
d5ae57b28e | ||
|
|
aa1d9b3d71 | ||
|
|
c250048bf7 | ||
|
|
1db78a5bea | ||
|
|
2ad0fa118f | ||
|
|
38afe59db0 | ||
|
|
b173930c14 | ||
|
|
61c4529df6 | ||
|
|
dd48af5b50 | ||
|
|
bc3c667d2a | ||
|
|
1041bdb6b7 | ||
|
|
fc7a04547c | ||
|
|
dd2a1c4ed5 | ||
|
|
c7667abf24 | ||
|
|
df484523c2 | ||
|
|
81fca5b218 | ||
|
|
c520fd6650 |
No files matched your search
@@ -0,0 +1 @@
|
||||
custom: https://www.paypal.me/ibj
|
||||
@@ -0,0 +1,73 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
Thank you for submitting an issue for NuVotifier. We appreciate
|
||||
any and all bug reports.
|
||||
|
||||
Before you continue, please ensure you are reporting
|
||||
a real bug and are not suffering from a configuration issue
|
||||
or a plugin problem. Once NuVotifier has received and
|
||||
called your vote listeners, it's not our issue any more.
|
||||
|
||||
If you are having difficulty setting up NuVotifier to receive
|
||||
votes, you are in the wrong place. Please view our wiki
|
||||
at https://github.com/NuVotifier/NuVotifier/wiki for a
|
||||
comprehensive set of instructions for configuring and
|
||||
diagnosing common problems with NuVotifier.
|
||||
|
||||
If you are certain that there is a problem with
|
||||
NuVotifier, please continue filling the form below.
|
||||
Make sure to follow these instructions:
|
||||
|
||||
1. Tell the exact NuVotifier version you're using.
|
||||
If you just say "latest" your issue will get closed.
|
||||
2. Please include all relevant logs and configuration
|
||||
files. Make sure to redact any tokens in the config
|
||||
file.
|
||||
3. Don't write anything between these arrows.
|
||||
We won't see it otherwise.
|
||||
4. Before you create your issue, delete this block of
|
||||
text and the code block at the very bottom. -->
|
||||
|
||||
### Information
|
||||
|
||||
**Platform on which you run NuVotifier**: <!-- Either Bukkit, Sponge, BungeeCord, or Velocity. -->
|
||||
|
||||
**Version of NuVotifier**:
|
||||
|
||||
```
|
||||
<!--
|
||||
Getting this depends on your platform:
|
||||
|
||||
Bukkit: Use /version Votifier
|
||||
|
||||
Sponge: Use /sponge plugins and find NuVotifier
|
||||
|
||||
BungeeCord: No such command exists.
|
||||
|
||||
Velocity: Use /velocity plugins from in-game and hover over "nuvotifier"
|
||||
-->
|
||||
```
|
||||
|
||||
**Relevant log**: <!-- Paste the log on https://gist.github.com/, replace this with link -->
|
||||
### Details
|
||||
|
||||
**Description**
|
||||
<!-- Replace this with a brief summary of the bug. -->
|
||||
|
||||
**Steps to reproduce**
|
||||
<!-- Replace this with exactly what you did to cause the bug. -->
|
||||
|
||||
**Expected behavior**
|
||||
<!-- Replace this with what you expected to happen. -->
|
||||
|
||||
```
|
||||
Please make sure to delete this line of text and the three ``` characters surrounding it, or your issue will be closed.
|
||||
```
|
||||
@@ -47,3 +47,6 @@ debug.txt
|
||||
# gradle stuff
|
||||
.gradle/
|
||||
.gradletasknamecache
|
||||
|
||||
# fabric
|
||||
fabric/run/
|
||||
@@ -1,35 +1,25 @@
|
||||
# NuVotifier
|
||||
|
||||
NuVotifier is a Bukkit plugin whose purpose is to be notified (aka *votified*)
|
||||
when a vote is made on a Minecraft server top list for the server. NuVotifier
|
||||
creates a *lightweight* server that waits for connections by Minecraft server
|
||||
lists and uses a simple protocol to get the required information. NuVotifier is
|
||||
*secure*, and makes sure that all vote notifications are delivered by authentic
|
||||
top lists.
|
||||
NuVotifier is a secure alternative to using the original Votifier project.
|
||||
NuVotifier will work in place of Votifier - any vote listener that supports
|
||||
Votifier will also support NuVotifier.
|
||||
|
||||
## Why fork Votifier?
|
||||
NuVotifier supports many different platforms using the same universal JAR:
|
||||
|
||||
Votifier has lagged in activity, has severe security flaws in its protocol, and
|
||||
is vulnerable to denial of service attacks.
|
||||
+ Bukkit / Spigot / Paper >=1.7.10
|
||||
+ Sponge 7
|
||||
+ BungeeCord / Waterfall
|
||||
+ Velocity
|
||||
|
||||
## Is NuVotifier a drop-in replacement for Votifier?
|
||||
NuVotifier also adds forwarding and listener test commands not present in the
|
||||
original version.
|
||||
|
||||
Yes! Except for some minor non-breaking changes (look into the [technical qa
|
||||
docs](docs/technical_qa.md) for more info), NuVotifier is 100% compatible with
|
||||
the old Votifier with many more features.
|
||||
[Setup Guide](https://github.com/NuVotifier/NuVotifier/wiki/Setup-Guide)
|
||||
|
||||
# Documentation / Configuration Guide
|
||||
[Troubleshooting Guide](https://github.com/NuVotifier/NuVotifier/wiki/Troubleshooting-Guide)
|
||||
|
||||
All documentation is located under the [docs](docs/) directory. A server admin
|
||||
configuration guide is available [here](docs/usage.md).
|
||||
|
||||
# Support
|
||||
|
||||
Feel free to join the MOSS Discord server using the below link:
|
||||
|
||||
[](https://discord.gg/anUtuAC)
|
||||
[Developer Information](https://github.com/NuVotifier/NuVotifier/wiki/Developer-Documentation)
|
||||
|
||||
# License
|
||||
|
||||
NuVotifier is GNU GPLv3 licensed. This project's license can be viewed
|
||||
[here](LICENSE).
|
||||
NuVotifier is GNU GPLv3 licensed. This project's license can be viewed [here](LICENSE).
|
||||
@@ -0,0 +1,4 @@
|
||||
# nuvotifier-api
|
||||
|
||||
Files in this directory have a strong API promise - they really shouldn't
|
||||
change over time. Feel free to depend against it!
|
||||
@@ -0,0 +1,10 @@
|
||||
plugins {
|
||||
`java-library`
|
||||
}
|
||||
|
||||
applyPlatformAndCoreConfiguration()
|
||||
applyCommonArtifactoryConfig()
|
||||
|
||||
dependencies {
|
||||
"implementation"("com.google.code.gson:gson:${Versions.GSON}")
|
||||
}
|
||||
+37
-21
@@ -21,6 +21,9 @@ package com.vexsoftware.votifier.model;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* A model for a vote.
|
||||
*
|
||||
@@ -48,26 +51,31 @@ public class Vote {
|
||||
*/
|
||||
private String timeStamp;
|
||||
|
||||
/**
|
||||
* Timestamp (unix-millis) normalized and taken from a known source
|
||||
*/
|
||||
private final long localTimestamp;
|
||||
private byte[] additionalData;
|
||||
|
||||
@Deprecated
|
||||
public Vote() {
|
||||
localTimestamp = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public Vote(String serviceName, String username, String address, String timeStamp) {
|
||||
this(serviceName, username, address, timeStamp, System.currentTimeMillis());
|
||||
}
|
||||
|
||||
public Vote(String serviceName, String username, String address, String timeStamp, long localTimestamp) {
|
||||
this.serviceName = serviceName;
|
||||
this.username = username;
|
||||
this.address = address;
|
||||
this.timeStamp = timeStamp;
|
||||
this.localTimestamp = localTimestamp;
|
||||
this.additionalData = null;
|
||||
}
|
||||
|
||||
public Vote(String serviceName, String username, String address, String timeStamp, byte[] additionalData) {
|
||||
this.serviceName = serviceName;
|
||||
this.username = username;
|
||||
this.address = address;
|
||||
this.timeStamp = timeStamp;
|
||||
this.additionalData = additionalData == null ? null : additionalData.clone();
|
||||
}
|
||||
|
||||
public Vote(Vote vote) {
|
||||
this(vote.getServiceName(), vote.getUsername(), vote.getAddress(), vote.getTimeStamp(),
|
||||
vote.getAdditionalData() == null ? null : vote.getAdditionalData().clone());
|
||||
}
|
||||
|
||||
private static String getTimestamp(JsonElement object) {
|
||||
@@ -82,16 +90,22 @@ public class Vote {
|
||||
this(jsonObject.get("serviceName").getAsString(),
|
||||
jsonObject.get("username").getAsString(),
|
||||
jsonObject.get("address").getAsString(),
|
||||
getTimestamp(jsonObject.get("timestamp")),
|
||||
// maintained for backwards compatibility with <2.3.6 peers
|
||||
(jsonObject.has("localTimestamp") ? jsonObject.get("localTimestamp").getAsLong() : System.currentTimeMillis()));
|
||||
getTimestamp(jsonObject.get("timestamp")));
|
||||
if (jsonObject.has("additionalData"))
|
||||
additionalData = Base64.getDecoder().decode(jsonObject.get("additionalData").getAsString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String data;
|
||||
if (additionalData == null)
|
||||
data = "null";
|
||||
else
|
||||
data = Base64.getEncoder().encodeToString(additionalData);
|
||||
|
||||
return "Vote (from:" + serviceName + " username:" + username
|
||||
+ " address:" + address + " timeStamp:" + timeStamp
|
||||
+ " localTimestamp:" + localTimestamp + ")";
|
||||
+ " additionalData:" + data + ")";
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -171,12 +185,12 @@ public class Vote {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the local timestamp, in unix-millis. Calculated locally by a NuVotifier instance
|
||||
* Returns additional data sent with the vote, if it exists.
|
||||
*
|
||||
* @return The local timestamp
|
||||
* @return Additional data sent with the vote
|
||||
*/
|
||||
public long getLocalTimestamp() {
|
||||
return localTimestamp;
|
||||
public byte[] getAdditionalData() {
|
||||
return additionalData == null ? null : additionalData.clone();
|
||||
}
|
||||
|
||||
public JsonObject serialize() {
|
||||
@@ -185,21 +199,23 @@ public class Vote {
|
||||
ret.addProperty("username", username);
|
||||
ret.addProperty("address", address);
|
||||
ret.addProperty("timestamp", timeStamp);
|
||||
ret.addProperty("localTimestamp", localTimestamp);
|
||||
if (additionalData != null)
|
||||
ret.addProperty("additionalData", Base64.getEncoder().encodeToString(additionalData));
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
if (!(o instanceof Vote)) return false;
|
||||
|
||||
Vote vote = (Vote) o;
|
||||
|
||||
if (!serviceName.equals(vote.serviceName)) return false;
|
||||
if (!username.equals(vote.username)) return false;
|
||||
if (!address.equals(vote.address)) return false;
|
||||
return timeStamp.equals(vote.timeStamp);
|
||||
if (!timeStamp.equals(vote.timeStamp)) return false;
|
||||
return Arrays.equals(additionalData, vote.additionalData);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.vexsoftware.votifier.model;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class VoteTest {
|
||||
@Test
|
||||
public void testVoteConstructFromJsonObject() {
|
||||
|
||||
String serviceName = "serviceNameA";
|
||||
String username = "usernameA";
|
||||
String address = "home";
|
||||
String timestamp = "1234";
|
||||
|
||||
JsonObject o = new JsonObject();
|
||||
o.addProperty("serviceName", serviceName);
|
||||
o.addProperty("username", username);
|
||||
o.addProperty("address", address);
|
||||
o.addProperty("timestamp", timestamp);
|
||||
|
||||
Vote v = new Vote(o);
|
||||
|
||||
assertEquals(serviceName, v.getServiceName());
|
||||
assertEquals(username, v.getUsername());
|
||||
assertEquals(address, v.getAddress());
|
||||
assertEquals(timestamp, v.getTimeStamp());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVoteConstructFromJsonObjectLongTS() {
|
||||
|
||||
String serviceName = "serviceNameA";
|
||||
String username = "usernameA";
|
||||
String address = "home";
|
||||
|
||||
JsonObject o = new JsonObject();
|
||||
o.addProperty("serviceName", serviceName);
|
||||
o.addProperty("username", username);
|
||||
o.addProperty("address", address);
|
||||
o.addProperty("timestamp", 1234);
|
||||
|
||||
Vote v = new Vote(o);
|
||||
|
||||
assertEquals(serviceName, v.getServiceName());
|
||||
assertEquals(username, v.getUsername());
|
||||
assertEquals(address, v.getAddress());
|
||||
assertEquals("1234", v.getTimeStamp());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVoteSerialize() {
|
||||
Vote v = new Vote("service", "username", "address", "1234");
|
||||
JsonObject o = v.serialize();
|
||||
|
||||
assertEquals(4, o.size());
|
||||
assertEquals("service", o.get("serviceName").getAsString());
|
||||
assertEquals("username", o.get("username").getAsString());
|
||||
assertEquals("address", o.get("address").getAsString());
|
||||
assertEquals("1234", o.get("timestamp").getAsString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVoteEquals() {
|
||||
Vote v1 = new Vote("service", "username", "address", "1234");
|
||||
assertEquals(v1, v1);
|
||||
assertEquals(v1.hashCode(), new Vote("service", "username", "address", "1234").hashCode());
|
||||
assertEquals(v1, new Vote("service", "username", "address", "1234"));
|
||||
assertNotEquals(v1, new Object());
|
||||
assertNotEquals(v1, null);
|
||||
assertNotEquals(v1, new Vote("service2", "username", "address", "1234"));
|
||||
assertNotEquals(v1, new Vote("service", "username1", "address", "1234"));
|
||||
assertNotEquals(v1, new Vote("service", "username", "address1", "1234"));
|
||||
assertNotEquals(v1, new Vote("service", "username", "address", "12345"));
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
|
||||
import org.apache.tools.ant.filters.ReplaceTokens
|
||||
|
||||
allprojects {
|
||||
group 'com.vexsoftware'
|
||||
version '3.0.0-SNAPSHOT'
|
||||
}
|
||||
|
||||
subprojects {
|
||||
apply plugin: 'java'
|
||||
|
||||
sourceCompatibility = 1.8
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
url 'https://nexus.ibj.io/repository/maven-public/'
|
||||
}
|
||||
}
|
||||
|
||||
processResources {
|
||||
filter(ReplaceTokens, tokens: ["app.version": this.project.version])
|
||||
}
|
||||
}
|
||||
|
||||
if (System.env.PUBLISH) {
|
||||
apply from: 'publish.gradle'
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import org.ajoberstar.grgit.Grgit
|
||||
|
||||
plugins {
|
||||
id("net.kyori.blossom") version "1.2.0" apply false
|
||||
}
|
||||
|
||||
logger.lifecycle("""
|
||||
*******************************************
|
||||
You are building NuVotifier!
|
||||
If you encounter trouble:
|
||||
1) Try running 'build' in a separate Gradle run
|
||||
2) Use gradlew and not gradle
|
||||
3) If you still need help, you should reconsider building NuVotifier!
|
||||
|
||||
Output files will be in [subproject]/build/libs
|
||||
*******************************************
|
||||
""")
|
||||
|
||||
|
||||
applyRootArtifactoryConfig()
|
||||
|
||||
if (!project.hasProperty("gitCommitHash")) {
|
||||
apply(plugin = "org.ajoberstar.grgit")
|
||||
ext["gitCommitHash"] = try {
|
||||
Grgit.open(mapOf("currentDir" to project.rootDir))?.head()?.abbreviatedId
|
||||
} catch (e: Exception) {
|
||||
logger.warn("Error getting commit hash", e)
|
||||
|
||||
"no.git.id"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
plugins {
|
||||
`kotlin-dsl`
|
||||
kotlin("jvm") version embeddedKotlinVersion
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
maven {
|
||||
name = "sponge"
|
||||
url = uri("https://repo.spongepowered.org/repository/maven-public/")
|
||||
}
|
||||
maven {
|
||||
name = "fabric"
|
||||
url = uri("https://maven.fabricmc.net/")
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(gradleApi())
|
||||
implementation("org.ajoberstar.grgit:grgit-gradle:4.1.1")
|
||||
implementation("gradle.plugin.com.github.johnrengelman:shadow:7.1.2")
|
||||
implementation("org.jfrog.buildinfo:build-info-extractor-gradle:4.27.1")
|
||||
implementation("org.spongepowered:spongegradle-plugin-development:2.0.0")
|
||||
implementation("net.fabricmc:fabric-loom:1.0-SNAPSHOT")
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.publish.PublishingExtension
|
||||
import org.gradle.api.publish.maven.MavenPublication
|
||||
import org.gradle.kotlin.dsl.*
|
||||
import org.jfrog.gradle.plugin.artifactory.dsl.ArtifactoryPluginConvention
|
||||
import org.jfrog.gradle.plugin.artifactory.task.ArtifactoryTask
|
||||
|
||||
private const val ARTIFACTORY_CONTEXT_URL = "artifactory_contextUrl"
|
||||
private const val ARTIFACTORY_USER = "artifactory_user"
|
||||
private const val ARTIFACTORY_PASSWORD = "artifactory_password"
|
||||
|
||||
fun Project.applyRootArtifactoryConfig() {
|
||||
if (!project.hasProperty(ARTIFACTORY_CONTEXT_URL)) ext[ARTIFACTORY_CONTEXT_URL] = "http://localhost"
|
||||
if (!project.hasProperty(ARTIFACTORY_USER)) ext[ARTIFACTORY_USER] = "guest"
|
||||
if (!project.hasProperty(ARTIFACTORY_PASSWORD)) ext[ARTIFACTORY_PASSWORD] = ""
|
||||
|
||||
apply(plugin = "com.jfrog.artifactory")
|
||||
configure<ArtifactoryPluginConvention> {
|
||||
setContextUrl("${project.property(ARTIFACTORY_CONTEXT_URL)}")
|
||||
clientConfig.publisher.run {
|
||||
repoKey = when {
|
||||
"${project.version}".contains("SNAPSHOT") -> "private"
|
||||
else -> "public"
|
||||
}
|
||||
username = "${project.property(ARTIFACTORY_USER)}"
|
||||
password = "${project.property(ARTIFACTORY_PASSWORD)}"
|
||||
isMaven = true
|
||||
isIvy = false
|
||||
}
|
||||
}
|
||||
tasks.named<ArtifactoryTask>("artifactoryPublish") {
|
||||
isSkip = true
|
||||
}
|
||||
}
|
||||
|
||||
fun Project.applyCommonArtifactoryConfig() {
|
||||
configure<PublishingExtension> {
|
||||
publications {
|
||||
register<MavenPublication>("maven") {
|
||||
from(components["java"])
|
||||
versionMapping {
|
||||
usage("java-api") {
|
||||
fromResolutionOf("runtimeClasspath")
|
||||
}
|
||||
usage("java-runtime") {
|
||||
fromResolutionResult()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.named<ArtifactoryTask>("artifactoryPublish") {
|
||||
publications("maven")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.plugins.JavaPluginExtension
|
||||
import org.gradle.jvm.toolchain.JavaLanguageVersion
|
||||
import org.gradle.kotlin.dsl.apply
|
||||
import org.gradle.kotlin.dsl.configure
|
||||
import org.gradle.kotlin.dsl.repositories
|
||||
import org.gradle.kotlin.dsl.the
|
||||
|
||||
fun Project.applyCommonConfiguration() {
|
||||
group = rootProject.group
|
||||
version = rootProject.version
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven { url = uri("https://oss.sonatype.org/content/repositories/snapshots/") }
|
||||
maven {
|
||||
name = "Fabric"
|
||||
url = uri("https://maven.fabricmc.net/")
|
||||
}
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
resolutionStrategy {
|
||||
cacheChangingModulesFor(5, "MINUTES")
|
||||
}
|
||||
}
|
||||
|
||||
plugins.withId("java") {
|
||||
the<JavaPluginExtension>().toolchain {
|
||||
languageVersion.set(JavaLanguageVersion.of(17))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.plugins.JavaPluginExtension
|
||||
import org.gradle.api.tasks.compile.JavaCompile
|
||||
import org.gradle.api.tasks.javadoc.Javadoc
|
||||
import org.gradle.api.tasks.testing.Test
|
||||
import org.gradle.external.javadoc.StandardJavadocDocletOptions
|
||||
import org.gradle.kotlin.dsl.apply
|
||||
import org.gradle.kotlin.dsl.configure
|
||||
import org.gradle.kotlin.dsl.dependencies
|
||||
import org.gradle.kotlin.dsl.get
|
||||
import org.gradle.kotlin.dsl.withType
|
||||
|
||||
fun Project.applyCommonJavaConfiguration(sourcesJar: Boolean, javaRelease: Int = 8, banSlf4j: Boolean = true) {
|
||||
applyCommonConfiguration()
|
||||
apply(plugin = "eclipse")
|
||||
apply(plugin = "idea")
|
||||
|
||||
tasks
|
||||
.withType<JavaCompile>()
|
||||
.matching { it.name == "compileJava" || it.name == "compileTestJava" }
|
||||
.configureEach {
|
||||
val disabledLint = listOf(
|
||||
"processing", "path", "fallthrough", "serial"
|
||||
)
|
||||
options.release.set(javaRelease)
|
||||
options.compilerArgs.addAll(listOf("-Xlint:all") + disabledLint.map { "-Xlint:-$it" })
|
||||
options.isDeprecation = true
|
||||
options.encoding = "UTF-8"
|
||||
options.compilerArgs.add("-parameters")
|
||||
}
|
||||
|
||||
tasks.withType<Test>().configureEach {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
"compileOnly"("com.google.code.findbugs:jsr305:${Versions.FINDBUGS}")
|
||||
"testImplementation"("org.junit.jupiter:junit-jupiter-api:${Versions.JUNIT}")
|
||||
"testImplementation"("org.junit.jupiter:junit-jupiter-params:${Versions.JUNIT}")
|
||||
"testImplementation"("org.mockito:mockito-core:${Versions.MOCKITO}")
|
||||
"testImplementation"("org.mockito:mockito-junit-jupiter:${Versions.MOCKITO}")
|
||||
"testRuntimeOnly"("org.junit.jupiter:junit-jupiter-engine:${Versions.JUNIT}")
|
||||
}
|
||||
|
||||
// Java 8 turns on doclint which we fail
|
||||
tasks.withType<Javadoc>().configureEach {
|
||||
options.encoding = "UTF-8"
|
||||
(options as StandardJavadocDocletOptions).apply {
|
||||
addStringOption("Xdoclint:none", "-quiet")
|
||||
tags(
|
||||
"apiNote:a:API Note:",
|
||||
"implSpec:a:Implementation Requirements:",
|
||||
"implNote:a:Implementation Note:"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
configure<JavaPluginExtension> {
|
||||
disableAutoTargetJvm()
|
||||
withJavadocJar()
|
||||
if (sourcesJar) {
|
||||
withSourcesJar()
|
||||
}
|
||||
}
|
||||
|
||||
if (banSlf4j) {
|
||||
configurations["compileClasspath"].apply {
|
||||
resolutionStrategy.componentSelection {
|
||||
withModule("org.slf4j:slf4j-api") {
|
||||
reject("No SLF4J allowed on compile classpath")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.plugins.ExtraPropertiesExtension
|
||||
import org.gradle.api.plugins.JavaPluginExtension
|
||||
import org.gradle.api.tasks.SourceSetContainer
|
||||
import org.gradle.kotlin.dsl.getByType
|
||||
import org.gradle.kotlin.dsl.the
|
||||
|
||||
val Project.ext: ExtraPropertiesExtension
|
||||
get() = extensions.getByType()
|
||||
|
||||
val Project.sourceSets: SourceSetContainer
|
||||
get() = the<JavaPluginExtension>().sourceSets
|
||||
@@ -0,0 +1,42 @@
|
||||
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.component.AdhocComponentWithVariants
|
||||
import org.gradle.kotlin.dsl.apply
|
||||
import org.gradle.kotlin.dsl.get
|
||||
import org.gradle.kotlin.dsl.named
|
||||
|
||||
fun Project.applyPlatformAndCoreConfiguration(javaRelease: Int = 8) {
|
||||
applyCommonConfiguration()
|
||||
apply(plugin = "java")
|
||||
apply(plugin = "maven-publish")
|
||||
apply(plugin = "com.jfrog.artifactory")
|
||||
applyCommonJavaConfiguration(
|
||||
sourcesJar = true,
|
||||
javaRelease = javaRelease,
|
||||
banSlf4j = false
|
||||
)
|
||||
|
||||
ext["internalVersion"] = "$version+${rootProject.ext["gitCommitHash"]}"
|
||||
}
|
||||
|
||||
fun Project.applyShadowConfiguration() {
|
||||
apply(plugin = "com.github.johnrengelman.shadow")
|
||||
tasks.named<ShadowJar>("shadowJar") {
|
||||
archiveClassifier.set("dist")
|
||||
dependencies {
|
||||
include(project(":nuvotifier-api"))
|
||||
include(project(":nuvotifier-common"))
|
||||
|
||||
exclude("com.google.code.findbugs:jsr305")
|
||||
}
|
||||
exclude("GradleStart**")
|
||||
exclude(".cache")
|
||||
exclude("LICENSE*")
|
||||
exclude("META-INF/maven/**")
|
||||
}
|
||||
val javaComponent = components["java"] as AdhocComponentWithVariants
|
||||
// I don't think we want this published (it's the shadow jar)
|
||||
javaComponent.withVariantsFromConfiguration(configurations["shadowRuntimeElements"]) {
|
||||
skip()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
object Versions {
|
||||
const val JUNIT = "5.8.1"
|
||||
const val MOCKITO = "4.3.1"
|
||||
const val FINDBUGS = "3.0.2"
|
||||
const val NETTYIO = "4.1.72.Final"
|
||||
const val GSON = "2.9.0"
|
||||
const val VELOCITY = "3.1.0"
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
dependencies {
|
||||
compileOnly group: "org.spigotmc", name: "spigot-api", version: "1.8.8-R0.1-SNAPSHOT"
|
||||
compile project(":nuvotifier-common")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
||||
|
||||
plugins {
|
||||
`java-library`
|
||||
}
|
||||
|
||||
applyPlatformAndCoreConfiguration()
|
||||
applyCommonArtifactoryConfig()
|
||||
applyShadowConfiguration()
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
name = "paper"
|
||||
url = uri("https://papermc.io/repo/repository/maven-public/")
|
||||
}
|
||||
}
|
||||
|
||||
configurations {
|
||||
compileClasspath.get().extendsFrom(create("shadeOnly"))
|
||||
}
|
||||
|
||||
dependencies {
|
||||
"compileOnly"("io.papermc.paper:paper-api:1.18.2-R0.1-SNAPSHOT")
|
||||
"api"(project(":nuvotifier-api"))
|
||||
"api"(project(":nuvotifier-common"))
|
||||
}
|
||||
|
||||
|
||||
tasks.named<Copy>("processResources") {
|
||||
val internalVersion = project.ext["internalVersion"]
|
||||
inputs.property("internalVersion", internalVersion)
|
||||
filesMatching("plugin.yml") {
|
||||
expand("internalVersion" to internalVersion)
|
||||
}
|
||||
}
|
||||
|
||||
tasks.named<Jar>("jar") {
|
||||
val projectVersion = project.version
|
||||
inputs.property("projectVersion", projectVersion)
|
||||
manifest {
|
||||
attributes("Implementation-Version" to projectVersion)
|
||||
}
|
||||
}
|
||||
|
||||
tasks.named<ShadowJar>("shadowJar") {
|
||||
configurations = listOf(project.configurations["shadeOnly"], project.configurations["runtimeClasspath"])
|
||||
|
||||
dependencies {
|
||||
include(dependency(":nuvotifier-api"))
|
||||
include(dependency(":nuvotifier-common"))
|
||||
}
|
||||
}
|
||||
|
||||
tasks.named("assemble").configure {
|
||||
dependsOn("shadowJar")
|
||||
}
|
||||
@@ -17,21 +17,6 @@ class BukkitScheduler implements VotifierScheduler {
|
||||
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)));
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
|
||||
package com.vexsoftware.votifier;
|
||||
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.google.common.io.Files;
|
||||
import com.vexsoftware.votifier.cmd.NVReloadCmd;
|
||||
import com.vexsoftware.votifier.cmd.TestVoteCmd;
|
||||
import com.vexsoftware.votifier.forwarding.BukkitPluginMessagingForwardingSink;
|
||||
import com.vexsoftware.votifier.support.forwarding.ForwardedVoteListener;
|
||||
import com.vexsoftware.votifier.support.forwarding.ForwardingVoteSink;
|
||||
@@ -33,20 +33,20 @@ import com.vexsoftware.votifier.platform.JavaUtilLogger;
|
||||
import com.vexsoftware.votifier.platform.LoggingAdapter;
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.platform.scheduler.VotifierScheduler;
|
||||
import com.vexsoftware.votifier.util.IOUtil;
|
||||
import com.vexsoftware.votifier.util.KeyCreator;
|
||||
import com.vexsoftware.votifier.util.TokenUtil;
|
||||
import io.netty.channel.Channel;
|
||||
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 java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.security.Key;
|
||||
import java.security.KeyPair;
|
||||
import java.util.HashMap;
|
||||
@@ -61,16 +61,6 @@ import java.util.logging.Level;
|
||||
*/
|
||||
public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, VotifierPlugin, ForwardedVoteListener {
|
||||
|
||||
/**
|
||||
* The Votifier instance.
|
||||
*/
|
||||
private static NuVotifierBukkit instance;
|
||||
|
||||
/**
|
||||
* The current Votifier version.
|
||||
*/
|
||||
private String version;
|
||||
|
||||
/**
|
||||
* The server bootstrap.
|
||||
*/
|
||||
@@ -94,12 +84,24 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
private ForwardingVoteSink forwardingMethod;
|
||||
private VotifierScheduler scheduler;
|
||||
private LoggingAdapter pluginLogger;
|
||||
private boolean isFolia;
|
||||
|
||||
private boolean loadAndBind() {
|
||||
try {
|
||||
Class.forName("io.papermc.paper.threadedregions.scheduler.AsyncScheduler");
|
||||
isFolia = true;
|
||||
|
||||
getLogger().info("Using Folia; VotifierEvent will be fired asynchronously.");
|
||||
} catch (ClassNotFoundException e) {
|
||||
isFolia = false;
|
||||
}
|
||||
|
||||
scheduler = new BukkitScheduler(this);
|
||||
pluginLogger = new JavaUtilLogger(getLogger());
|
||||
if (!getDataFolder().exists()) {
|
||||
getDataFolder().mkdir();
|
||||
if (!getDataFolder().mkdir()) {
|
||||
throw new RuntimeException("Unable to create the plugin data folder " + getDataFolder());
|
||||
}
|
||||
}
|
||||
|
||||
// Handle configuration.
|
||||
@@ -124,13 +126,15 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
getLogger().info("Configuring Votifier for the first time...");
|
||||
|
||||
// Initialize the configuration file.
|
||||
config.createNewFile();
|
||||
if (!config.createNewFile()) {
|
||||
throw new IOException("Unable to create the config file at " + config);
|
||||
}
|
||||
|
||||
// Load and manually replace variables in the configuration.
|
||||
String cfgStr = new String(ByteStreams.toByteArray(getResource("bukkitConfig.yml")), StandardCharsets.UTF_8);
|
||||
String cfgStr = new String(IOUtil.readAllBytes(getResource("bukkitConfig.yml")), StandardCharsets.UTF_8);
|
||||
String token = TokenUtil.newToken();
|
||||
cfgStr = cfgStr.replace("%default_token%", token).replace("%ip%", hostAddr);
|
||||
Files.asCharSink(config, StandardCharsets.UTF_8).write(cfgStr);
|
||||
Files.copy(new ByteArrayInputStream(cfgStr.getBytes(StandardCharsets.UTF_8)), config.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||
|
||||
/*
|
||||
* Remind hosted server admins to be sure they have the right
|
||||
@@ -164,7 +168,9 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
*/
|
||||
try {
|
||||
if (!rsaDirectory.exists()) {
|
||||
rsaDirectory.mkdir();
|
||||
if (!rsaDirectory.mkdir()) {
|
||||
throw new RuntimeException("Unable to create the RSA key folder " + rsaDirectory);
|
||||
}
|
||||
keyPair = RSAKeygen.generate(2048);
|
||||
RSAIO.save(rsaDirectory, keyPair);
|
||||
} else {
|
||||
@@ -273,10 +279,8 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
NuVotifierBukkit.instance = this;
|
||||
|
||||
// Set the plugin version.
|
||||
version = getDescription().getVersion();
|
||||
getCommand("nvreload").setExecutor(new NVReloadCmd(this));
|
||||
getCommand("testvote").setExecutor(new TestVoteCmd(this));
|
||||
|
||||
if (!loadAndBind()) {
|
||||
gracefulExit();
|
||||
@@ -290,17 +294,7 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
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() {
|
||||
public boolean reload() {
|
||||
try {
|
||||
halt();
|
||||
} catch (Exception ex) {
|
||||
@@ -309,6 +303,7 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
|
||||
if (loadAndBind()) {
|
||||
getLogger().info("Reload was successful.");
|
||||
return true;
|
||||
} else {
|
||||
try {
|
||||
halt();
|
||||
@@ -316,6 +311,7 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
} 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);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,15 +319,6 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
getLogger().log(Level.SEVERE, "Votifier did not initialize properly!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the instance.
|
||||
*
|
||||
* @return The instance
|
||||
*/
|
||||
public static NuVotifierBukkit getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoggingAdapter getPluginLogger() {
|
||||
return pluginLogger;
|
||||
@@ -357,28 +344,24 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVoteReceived(Channel channel, final Vote vote, VotifierSession.ProtocolVersion protocolVersion) {
|
||||
public void onVoteReceived(final Vote vote, VotifierSession.ProtocolVersion protocolVersion, String remoteAddress) {
|
||||
if (debug) {
|
||||
if (protocolVersion == VotifierSession.ProtocolVersion.ONE) {
|
||||
getLogger().info("Got a protocol v1 vote record from " + channel.remoteAddress() + " -> " + vote);
|
||||
} else {
|
||||
getLogger().info("Got a protocol v2 vote record from " + channel.remoteAddress() + " -> " + vote);
|
||||
}
|
||||
getLogger().info("Got a " + protocolVersion.humanReadable + " vote record from " + remoteAddress + " -> " + vote);
|
||||
}
|
||||
Bukkit.getScheduler().runTask(this, () -> Bukkit.getPluginManager().callEvent(new VotifierEvent(vote)));
|
||||
fireVotifierEvent(vote);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Channel channel, boolean alreadyHandledVote, Throwable throwable) {
|
||||
public void onError(Throwable throwable, boolean alreadyHandledVote, String remoteAddress) {
|
||||
if (debug) {
|
||||
if (alreadyHandledVote) {
|
||||
getLogger().log(Level.SEVERE, "Vote processed, however an exception " +
|
||||
"occurred with a vote from " + channel.remoteAddress(), throwable);
|
||||
getLogger().log(Level.WARNING, "Vote processed, however an exception " +
|
||||
"occurred with a vote from " + remoteAddress, throwable);
|
||||
} else {
|
||||
getLogger().log(Level.SEVERE, "Unable to process vote from " + channel.remoteAddress(), throwable);
|
||||
getLogger().log(Level.WARNING, "Unable to process vote from " + remoteAddress, throwable);
|
||||
}
|
||||
} else if (!alreadyHandledVote) {
|
||||
getLogger().log(Level.SEVERE, "Unable to process vote from " + channel.remoteAddress());
|
||||
getLogger().log(Level.WARNING, "Unable to process vote from " + remoteAddress);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -387,6 +370,24 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
if (debug) {
|
||||
getLogger().info("Got a forwarded vote -> " + v);
|
||||
}
|
||||
Bukkit.getScheduler().runTask(this, () -> Bukkit.getPluginManager().callEvent(new VotifierEvent(v)));
|
||||
fireVotifierEvent(v);
|
||||
}
|
||||
|
||||
private void fireVotifierEvent(Vote vote) {
|
||||
if (VotifierEvent.getHandlerList().getRegisteredListeners().length == 0) {
|
||||
getLogger().log(Level.SEVERE, "A vote was received, but you don't have any listeners available to listen for it.");
|
||||
getLogger().log(Level.SEVERE, "See https://github.com/NuVotifier/NuVotifier/wiki/Setup-Guide#vote-listeners for");
|
||||
getLogger().log(Level.SEVERE, "a list of listeners you can configure.");
|
||||
}
|
||||
|
||||
if (!isFolia) {
|
||||
getServer().getScheduler().runTask(
|
||||
this, () -> getServer().getPluginManager().callEvent(new VotifierEvent(vote))
|
||||
);
|
||||
} else {
|
||||
getServer().getScheduler().runTaskAsynchronously(
|
||||
this, () -> getServer().getPluginManager().callEvent(new VotifierEvent(vote, true))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.vexsoftware.votifier.cmd;
|
||||
|
||||
import com.vexsoftware.votifier.NuVotifierBukkit;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
public class NVReloadCmd implements CommandExecutor {
|
||||
|
||||
private final NuVotifierBukkit plugin;
|
||||
|
||||
public NVReloadCmd(NuVotifierBukkit plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (sender.hasPermission("nuvotifier.reload")) {
|
||||
sender.sendMessage(ChatColor.GRAY + "Reloading NuVotifier...");
|
||||
if (plugin.reload()) {
|
||||
sender.sendMessage(ChatColor.DARK_GREEN + "NuVotifier has been reloaded!");
|
||||
} else {
|
||||
sender.sendMessage(ChatColor.DARK_RED + "Looks like there was a problem reloading NuVotifier, check the console!");
|
||||
}
|
||||
} else {
|
||||
sender.sendMessage(ChatColor.DARK_RED + "You do not have permission to do this!");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.vexsoftware.votifier.cmd;
|
||||
|
||||
import com.vexsoftware.votifier.NuVotifierBukkit;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.VotifierSession;
|
||||
import com.vexsoftware.votifier.util.ArgsToVote;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
public class TestVoteCmd implements CommandExecutor {
|
||||
|
||||
private final NuVotifierBukkit plugin;
|
||||
|
||||
public TestVoteCmd(NuVotifierBukkit plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (sender.hasPermission("nuvotifier.testvote")) {
|
||||
Vote v;
|
||||
try {
|
||||
v = ArgsToVote.parse(args);
|
||||
} catch (IllegalArgumentException e) {
|
||||
sender.sendMessage(ChatColor.DARK_RED + "Error while parsing arguments to create test vote: " + e.getMessage());
|
||||
sender.sendMessage(ChatColor.GRAY + "Usage hint: /testvote [username] [serviceName=?] [username=?] [address=?] [localTimestamp=?] [timestamp=?]");
|
||||
return true;
|
||||
}
|
||||
|
||||
plugin.onVoteReceived(v, VotifierSession.ProtocolVersion.TEST, "localhost.test");
|
||||
sender.sendMessage(ChatColor.GREEN + "Test vote executed: " + v.toString());
|
||||
} else {
|
||||
sender.sendMessage(ChatColor.DARK_RED + "You do not have permission to do this!");
|
||||
}
|
||||
return true;
|
||||
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -37,7 +37,7 @@ public class BukkitPluginMessagingForwardingSink extends AbstractPluginMessaging
|
||||
try {
|
||||
this.handlePluginMessage(bytes);
|
||||
} catch (Exception e) {
|
||||
NuVotifierBukkit.getInstance().getLogger().log(Level.SEVERE, "There was an unknown error when processing a forwarded vote.", e);
|
||||
p.getLogger().log(Level.SEVERE, "There was an unknown error when processing a forwarded vote.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import org.bukkit.event.HandlerList;
|
||||
|
||||
/**
|
||||
* {@code VotifierEvent} is a custom Bukkit event class that is sent
|
||||
* synchronously to CraftBukkit's main thread allowing other plugins to listener
|
||||
* synchronously to CraftBukkit's main thread allowing other plugins to listen
|
||||
* for votes.
|
||||
*
|
||||
* @author frelling
|
||||
@@ -30,6 +30,17 @@ public class VotifierEvent extends Event {
|
||||
this.vote = vote;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a vote event that encapsulated the given vote record.
|
||||
*
|
||||
* @param vote vote record
|
||||
* @param async whether the event will be fired asynchronously
|
||||
*/
|
||||
public VotifierEvent(final Vote vote, final boolean async) {
|
||||
super(async);
|
||||
this.vote = vote;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the encapsulated vote record.
|
||||
*
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
name: Votifier
|
||||
main: com.vexsoftware.votifier.NuVotifierBukkit
|
||||
version: @app.version@
|
||||
version: "${internalVersion}"
|
||||
description: A plugin that gets notified when votes are made for the server on toplists.
|
||||
authors: [Ichbinjoe, blakeman8192, Kramer, tuxed]
|
||||
api-version: 1.13
|
||||
@@ -8,6 +8,19 @@ api-version: 1.13
|
||||
commands:
|
||||
nvreload:
|
||||
description: Reloads the NuVotifier configuration
|
||||
permission: nuvotifier.console
|
||||
permission: nuvotifier.reload
|
||||
permission-message: "You do not have permission to run this command."
|
||||
usage: "/nvreload"
|
||||
usage: "/nvreload"
|
||||
testvote:
|
||||
description: Sends a test vote to the server
|
||||
permission: nuvotifier.testvote
|
||||
permission-message: "You do not have permission to run this command."
|
||||
usage: "/testvote [username] [serviceName=?] [username=?] [address=?] [localTimestamp=?] [timestamp=?]"
|
||||
|
||||
permissions:
|
||||
nuvotifier.reload:
|
||||
description: Allows you to reload the NuVotifier plugin
|
||||
default: op
|
||||
nuvotifier.testvote:
|
||||
description: Allows you to send a test vote
|
||||
default: op
|
||||
@@ -1,4 +0,0 @@
|
||||
dependencies {
|
||||
compileOnly group: "net.md-5", name: "bungeecord-api", version: "1.8-SNAPSHOT"
|
||||
compile project(":nuvotifier-common")
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
||||
|
||||
plugins {
|
||||
`java-library`
|
||||
}
|
||||
|
||||
applyPlatformAndCoreConfiguration()
|
||||
applyCommonArtifactoryConfig()
|
||||
applyShadowConfiguration()
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
name = "bungeecord"
|
||||
url = uri("https://oss.sonatype.org/content/repositories/snapshots/")
|
||||
}
|
||||
}
|
||||
|
||||
configurations {
|
||||
compileClasspath.get().extendsFrom(create("shadeOnly"))
|
||||
}
|
||||
|
||||
dependencies {
|
||||
"compileOnly"("net.md-5:bungeecord-api:1.18-R0.1-SNAPSHOT")
|
||||
"api"(project(":nuvotifier-api"))
|
||||
"api"(project(":nuvotifier-common"))
|
||||
}
|
||||
|
||||
|
||||
tasks.named<Copy>("processResources") {
|
||||
val internalVersion = project.ext["internalVersion"]
|
||||
inputs.property("internalVersion", internalVersion)
|
||||
filesMatching("bungee.yml") {
|
||||
expand("internalVersion" to internalVersion)
|
||||
}
|
||||
}
|
||||
|
||||
tasks.named<Jar>("jar") {
|
||||
val projectVersion = project.version
|
||||
inputs.property("projectVersion", projectVersion)
|
||||
manifest {
|
||||
attributes("Implementation-Version" to projectVersion)
|
||||
}
|
||||
}
|
||||
|
||||
tasks.named<ShadowJar>("shadowJar") {
|
||||
configurations = listOf(project.configurations["shadeOnly"], project.configurations["runtimeClasspath"])
|
||||
|
||||
dependencies {
|
||||
include(dependency(":nuvotifier-api"))
|
||||
include(dependency(":nuvotifier-common"))
|
||||
}
|
||||
}
|
||||
|
||||
tasks.named("assemble").configure {
|
||||
dependsOn("shadowJar")
|
||||
}
|
||||
@@ -1,14 +1,15 @@
|
||||
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;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class BungeeBackendServer implements BackendServer {
|
||||
private final ServerInfo info;
|
||||
|
||||
public BungeeBackendServer(ServerInfo info) {
|
||||
this.info = Preconditions.checkNotNull(info, "info");
|
||||
this.info = Objects.requireNonNull(info, "info");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -13,21 +13,6 @@ class BungeeScheduler implements VotifierScheduler {
|
||||
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));
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package com.vexsoftware.votifier.bungee;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.google.common.io.Files;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.vexsoftware.votifier.VoteHandler;
|
||||
import com.vexsoftware.votifier.bungee.cmd.NVReloadCmd;
|
||||
import com.vexsoftware.votifier.bungee.cmd.TestVoteCmd;
|
||||
import com.vexsoftware.votifier.net.VotifierServerBootstrap;
|
||||
import com.vexsoftware.votifier.platform.BackendServer;
|
||||
import com.vexsoftware.votifier.platform.JavaUtilLogger;
|
||||
@@ -21,26 +21,26 @@ import com.vexsoftware.votifier.model.Vote;
|
||||
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.util.IOUtil;
|
||||
import com.vexsoftware.votifier.util.KeyCreator;
|
||||
import com.vexsoftware.votifier.util.TokenUtil;
|
||||
import io.netty.channel.Channel;
|
||||
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.api.plugin.PluginManager;
|
||||
import net.md_5.bungee.config.Configuration;
|
||||
import net.md_5.bungee.config.ConfigurationProvider;
|
||||
import net.md_5.bungee.config.YamlConfiguration;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
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.StandardCopyOption;
|
||||
import java.security.Key;
|
||||
import java.security.KeyPair;
|
||||
import java.util.*;
|
||||
@@ -81,10 +81,10 @@ public class NuVotifier extends Plugin implements VoteHandler, ProxyVotifierPlug
|
||||
private LoggingAdapter pluginLogger;
|
||||
|
||||
private void loadAndBind() {
|
||||
scheduler = new BungeeScheduler(this);
|
||||
pluginLogger = new JavaUtilLogger(getLogger());
|
||||
if (!getDataFolder().exists()) {
|
||||
getDataFolder().mkdir();
|
||||
if (!getDataFolder().mkdir()) {
|
||||
throw new RuntimeException("Unable to create the plugin data folder " + getDataFolder());
|
||||
}
|
||||
}
|
||||
|
||||
// Handle configuration.
|
||||
@@ -98,12 +98,14 @@ public class NuVotifier extends Plugin implements VoteHandler, ProxyVotifierPlug
|
||||
getLogger().info("Configuring Votifier for the first time...");
|
||||
|
||||
// Initialize the configuration file.
|
||||
config.createNewFile();
|
||||
if (!config.createNewFile()) {
|
||||
throw new IOException("Unable to create the config file at " + config);
|
||||
}
|
||||
|
||||
String cfgStr = new String(ByteStreams.toByteArray(getResourceAsStream("bungeeConfig.yml")), StandardCharsets.UTF_8);
|
||||
String cfgStr = new String(IOUtil.readAllBytes(getResourceAsStream("bungeeConfig.yml")), StandardCharsets.UTF_8);
|
||||
String token = TokenUtil.newToken();
|
||||
cfgStr = cfgStr.replace("%default_token%", token);
|
||||
Files.asCharSink(config, StandardCharsets.UTF_8).write(cfgStr);
|
||||
Files.copy(new ByteArrayInputStream(cfgStr.getBytes(StandardCharsets.UTF_8)), config.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||
|
||||
/*
|
||||
* Remind hosted server admins to be sure they have the right
|
||||
@@ -141,7 +143,9 @@ public class NuVotifier extends Plugin implements VoteHandler, ProxyVotifierPlug
|
||||
*/
|
||||
try {
|
||||
if (!rsaDirectory.exists()) {
|
||||
rsaDirectory.mkdir();
|
||||
if (!rsaDirectory.mkdir()) {
|
||||
throw new RuntimeException("Unable to create the RSA key folder " + rsaDirectory);
|
||||
}
|
||||
keyPair = RSAKeygen.generate(2048);
|
||||
RSAIO.save(rsaDirectory, keyPair);
|
||||
} else {
|
||||
@@ -161,7 +165,7 @@ public class NuVotifier extends Plugin implements VoteHandler, ProxyVotifierPlug
|
||||
}
|
||||
} else {
|
||||
String token = TokenUtil.newToken();
|
||||
configuration.set("tokens", ImmutableMap.of("default", token));
|
||||
configuration.set("tokens", Collections.singletonMap("default", token));
|
||||
tokens.put("default", KeyCreator.createKeyFrom(token));
|
||||
try {
|
||||
ConfigurationProvider.getProvider(YamlConfiguration.class).save(configuration, config);
|
||||
@@ -220,13 +224,13 @@ public class NuVotifier extends Plugin implements VoteHandler, ProxyVotifierPlug
|
||||
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(
|
||||
ProxyServer.getInstance().getServers().size(), this,
|
||||
this,
|
||||
fwdCfg.getInt("pluginMessaging.memory.cacheTime", -1));
|
||||
getLogger().info("Using in-memory cache for votes that are not able to be delivered.");
|
||||
} else if ("file".equals(cacheMethod)) {
|
||||
try {
|
||||
voteCache = new FileVoteCache(
|
||||
ProxyServer.getInstance().getServers().size(), this,
|
||||
this,
|
||||
new File(getDataFolder(), fwdCfg.getString("pluginMessaging.file.name")),
|
||||
fwdCfg.getInt("pluginMessaging.file.cacheTime", -1));
|
||||
} catch (IOException e) {
|
||||
@@ -253,6 +257,7 @@ public class NuVotifier extends Plugin implements VoteHandler, ProxyVotifierPlug
|
||||
String fallbackServer = fwdCfg.getString("pluginMessaging.joinedServerFallback", null);
|
||||
if (fallbackServer != null && fallbackServer.isEmpty()) fallbackServer = null;
|
||||
forwardingMethod = new OnlineForwardPluginMessagingForwardingSource(channel, this, filter, voteCache, fallbackServer, dumpRate);
|
||||
getLogger().info("Forwarding votes over PluginMessaging channel '" + channel + "' for vote forwarding for online players!");
|
||||
} catch (RuntimeException e) {
|
||||
getLogger().log(Level.SEVERE, "NuVotifier could not set up PluginMessaging for vote forwarding!", e);
|
||||
}
|
||||
@@ -287,7 +292,7 @@ public class NuVotifier extends Plugin implements VoteHandler, ProxyVotifierPlug
|
||||
}
|
||||
}
|
||||
|
||||
forwardingMethod = new ProxyForwardingVoteSource(this, bootstrap::client, serverList, null);
|
||||
forwardingMethod = bootstrap.createForwardingSource(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.");
|
||||
@@ -296,17 +301,15 @@ public class NuVotifier extends Plugin implements VoteHandler, ProxyVotifierPlug
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
loadAndBind();
|
||||
scheduler = new BungeeScheduler(this);
|
||||
pluginLogger = new JavaUtilLogger(getLogger());
|
||||
|
||||
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();
|
||||
}
|
||||
});
|
||||
PluginManager pm = ProxyServer.getInstance().getPluginManager();
|
||||
pm.registerCommand(this, new NVReloadCmd(this));
|
||||
pm.registerCommand(this, new TestVoteCmd(this));
|
||||
pm.registerListener(this, new ReloadListener(this));
|
||||
|
||||
loadAndBind();
|
||||
}
|
||||
|
||||
private void halt() {
|
||||
@@ -322,7 +325,7 @@ public class NuVotifier extends Plugin implements VoteHandler, ProxyVotifierPlug
|
||||
}
|
||||
}
|
||||
|
||||
public void reload() {
|
||||
public boolean reload() {
|
||||
try {
|
||||
halt();
|
||||
} catch (Exception ex) {
|
||||
@@ -332,6 +335,7 @@ public class NuVotifier extends Plugin implements VoteHandler, ProxyVotifierPlug
|
||||
try {
|
||||
loadAndBind();
|
||||
getLogger().info("Reload was successful.");
|
||||
return true;
|
||||
} catch (Exception ex) {
|
||||
try {
|
||||
halt();
|
||||
@@ -340,6 +344,7 @@ public class NuVotifier extends Plugin implements VoteHandler, ProxyVotifierPlug
|
||||
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);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -350,33 +355,34 @@ public class NuVotifier extends Plugin implements VoteHandler, ProxyVotifierPlug
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVoteReceived(Channel channel, final Vote vote, VotifierSession.ProtocolVersion protocolVersion) {
|
||||
public void onVoteReceived(final Vote vote, VotifierSession.ProtocolVersion protocolVersion, String remoteAddress) {
|
||||
if (debug) {
|
||||
if (protocolVersion == VotifierSession.ProtocolVersion.ONE) {
|
||||
getLogger().info("Got a protocol v1 vote record from " + channel.remoteAddress() + " -> " + vote);
|
||||
getLogger().info("Got a protocol v1 vote record from " + remoteAddress + " -> " + vote);
|
||||
} else {
|
||||
getLogger().info("Got a protocol v2 vote record from " + channel.remoteAddress() + " -> " + vote);
|
||||
getLogger().info("Got a protocol v2 vote record from " + remoteAddress + " -> " + vote);
|
||||
}
|
||||
}
|
||||
|
||||
getProxy().getScheduler().runAsync(this, () -> getProxy().getPluginManager().callEvent(new VotifierEvent(vote)));
|
||||
|
||||
if (forwardingMethod != null) {
|
||||
getProxy().getScheduler().runAsync(this, () -> forwardingMethod.forward(vote));
|
||||
}
|
||||
getProxy().getScheduler().runAsync(this, () -> {
|
||||
VotifierEvent event = getProxy().getPluginManager().callEvent(new VotifierEvent(vote));
|
||||
if (!event.isCancelled() && forwardingMethod != null) {
|
||||
forwardingMethod.forward(vote);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Channel channel, boolean alreadyHandledVote, Throwable throwable) {
|
||||
public void onError(Throwable throwable, boolean alreadyHandledVote, String remoteAddress) {
|
||||
if (debug) {
|
||||
if (alreadyHandledVote) {
|
||||
getLogger().log(Level.SEVERE, "Vote processed, however an exception " +
|
||||
"occurred with a vote from " + channel.remoteAddress(), throwable);
|
||||
getLogger().log(Level.WARNING, "Vote processed, however an exception " +
|
||||
"occurred with a vote from " + remoteAddress, throwable);
|
||||
} else {
|
||||
getLogger().log(Level.SEVERE, "Unable to process vote from " + channel.remoteAddress(), throwable);
|
||||
getLogger().log(Level.WARNING, "Unable to process vote from " + remoteAddress, throwable);
|
||||
}
|
||||
} else if (!alreadyHandledVote) {
|
||||
getLogger().log(Level.SEVERE, "Unable to process vote from " + channel.remoteAddress());
|
||||
getLogger().log(Level.WARNING, "Unable to process vote from " + remoteAddress);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
@@ -33,6 +33,9 @@ public final class OnlineForwardPluginMessagingForwardingSource extends Abstract
|
||||
if (p != null && p.getServer() != null &&
|
||||
serverFilter.isAllowed(p.getServer().getInfo().getName())) {
|
||||
if (forwardSpecific(new BungeeBackendServer(p.getServer().getInfo()), v)) {
|
||||
if (plugin.isDebug()) {
|
||||
plugin.getPluginLogger().info("Successfully forwarded vote " + v + " to server " + p.getServer().getInfo().getName());
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.vexsoftware.votifier.bungee;
|
||||
|
||||
import net.md_5.bungee.api.event.ProxyReloadEvent;
|
||||
import net.md_5.bungee.api.plugin.Listener;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
|
||||
public class ReloadListener implements Listener {
|
||||
private final NuVotifier nuVotifier;
|
||||
|
||||
public ReloadListener(NuVotifier nuVotifier) {
|
||||
this.nuVotifier = nuVotifier;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onProxyReload(ProxyReloadEvent event) {
|
||||
this.nuVotifier.reload();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.vexsoftware.votifier.bungee.cmd;
|
||||
|
||||
import com.vexsoftware.votifier.bungee.NuVotifier;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.api.plugin.Command;
|
||||
|
||||
public class NVReloadCmd extends Command {
|
||||
|
||||
private final NuVotifier plugin;
|
||||
|
||||
private static final BaseComponent reloadingNuVotifier = new TextComponent("Reloading NuVotifier...");
|
||||
private static final BaseComponent nuvotifierHasBeenReloaded = new TextComponent("NuVotifier has been reloaded!");
|
||||
private static final BaseComponent problem = new TextComponent("Looks like there was a problem reloading NuVotifier, check the console!");
|
||||
private static final BaseComponent permission = new TextComponent("You do not have permission to do this!");
|
||||
|
||||
static {
|
||||
reloadingNuVotifier.setColor(ChatColor.GRAY);
|
||||
nuvotifierHasBeenReloaded.setColor(ChatColor.DARK_GREEN);
|
||||
problem.setColor(ChatColor.DARK_RED);
|
||||
permission.setColor(ChatColor.DARK_RED);
|
||||
}
|
||||
|
||||
public NVReloadCmd(NuVotifier plugin) {
|
||||
super("pnvreload", "nuvotifier.reload");
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandSender sender, String[] args) {
|
||||
if (sender.hasPermission("nuvotifier.reload")) {
|
||||
sender.sendMessage(reloadingNuVotifier);
|
||||
if (plugin.reload()) {
|
||||
sender.sendMessage(nuvotifierHasBeenReloaded);
|
||||
} else {
|
||||
sender.sendMessage(problem);
|
||||
}
|
||||
} else {
|
||||
sender.sendMessage(permission);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.vexsoftware.votifier.bungee.cmd;
|
||||
|
||||
import com.vexsoftware.votifier.bungee.NuVotifier;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.VotifierSession;
|
||||
import com.vexsoftware.votifier.util.ArgsToVote;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.api.plugin.Command;
|
||||
|
||||
public class TestVoteCmd extends Command {
|
||||
|
||||
private final NuVotifier plugin;
|
||||
|
||||
private static final BaseComponent permission = new TextComponent("You do not have permission to do this!");
|
||||
private static final BaseComponent usage = new TextComponent("Usage hint: /ptestvote [username] [serviceName=?] [username=?] [address=?] [localTimestamp=?] [timestamp=?]");
|
||||
|
||||
static {
|
||||
usage.setColor(ChatColor.GRAY);
|
||||
}
|
||||
|
||||
public TestVoteCmd(NuVotifier plugin) {
|
||||
super("ptestvote", "nuvotifier.testvote");
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandSender sender, String[] args) {
|
||||
if (sender.hasPermission("nuvotifier.testvote")) {
|
||||
Vote v;
|
||||
try {
|
||||
v = ArgsToVote.parse(args);
|
||||
} catch (IllegalArgumentException e) {
|
||||
TextComponent c = new TextComponent("Error while parsing arguments to create test vote: " + e.getMessage());
|
||||
c.setColor(ChatColor.DARK_RED);
|
||||
sender.sendMessage(c);
|
||||
sender.sendMessage(usage);
|
||||
return;
|
||||
}
|
||||
|
||||
plugin.onVoteReceived(v, VotifierSession.ProtocolVersion.TEST, "localhost.test");
|
||||
TextComponent c = new TextComponent("Test vote executed: " + v.toString());
|
||||
c.setColor(ChatColor.GREEN);
|
||||
sender.sendMessage(c);
|
||||
} else {
|
||||
sender.sendMessage(permission);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,21 @@
|
||||
package com.vexsoftware.votifier.bungee.events;
|
||||
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import net.md_5.bungee.api.plugin.Cancellable;
|
||||
import net.md_5.bungee.api.plugin.Event;
|
||||
|
||||
/**
|
||||
* {@code VotifierEvent} is a custom BungeeCord event class that is sent
|
||||
* asynchronously allowing other plugins to listen for votes.
|
||||
*/
|
||||
public class VotifierEvent extends Event {
|
||||
public class VotifierEvent extends Event implements Cancellable {
|
||||
/**
|
||||
* The vote.
|
||||
**/
|
||||
private final Vote vote;
|
||||
|
||||
private boolean cancelled;
|
||||
|
||||
public VotifierEvent(Vote vote) {
|
||||
this.vote = vote;
|
||||
}
|
||||
@@ -20,4 +23,14 @@ public class VotifierEvent extends Event {
|
||||
public Vote getVote() {
|
||||
return vote;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return cancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCancelled(boolean cancel) {
|
||||
this.cancelled = cancel;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
name: NuVotifier
|
||||
main: com.vexsoftware.votifier.bungee.NuVotifier
|
||||
version: @app.version@
|
||||
version: "${internalVersion}"
|
||||
author: Ichbinjoe, blakeman8192, Kramer, tuxed
|
||||
@@ -1,7 +0,0 @@
|
||||
dependencies {
|
||||
compile group: "io.netty", name: "netty-handler", version: "4.1.32.Final"
|
||||
compile 'com.google.code.gson:gson:2.8.0'
|
||||
compile 'com.google.guava:guava:24.1-jre'
|
||||
testCompile group: "junit", name: "junit", version: "4.12"
|
||||
testCompile group: "org.json", name: "json", version: "20180130" // retain this for testing reasons
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
plugins {
|
||||
`java-library`
|
||||
}
|
||||
|
||||
applyPlatformAndCoreConfiguration()
|
||||
applyCommonArtifactoryConfig()
|
||||
|
||||
dependencies {
|
||||
"api"(project(":nuvotifier-api"))
|
||||
"implementation"("io.netty:netty-handler:${Versions.NETTYIO}")
|
||||
"implementation"("io.netty:netty-transport-native-epoll:${Versions.NETTYIO}:linux-x86_64")
|
||||
"implementation"("com.google.code.gson:gson:${Versions.GSON}")
|
||||
"testImplementation"("org.json:json:20180130") // retain this for testing reasons
|
||||
"testImplementation"("com.google.guava:guava:28.1-jre")
|
||||
}
|
||||
@@ -5,16 +5,11 @@ import com.vexsoftware.votifier.net.VotifierSession;
|
||||
import io.netty.channel.Channel;
|
||||
|
||||
public interface VoteHandler {
|
||||
void onVoteReceived(Channel channel, Vote vote, VotifierSession.ProtocolVersion protocolVersion) throws Exception;
|
||||
|
||||
// these default members are kept for compatibility sake - there probably isn't anyone out there implementing these
|
||||
// on their own, but we still deprecate them before removing them because we are nice!
|
||||
default void onError(Channel channel, boolean voteAlreadyCompleted, Throwable throwable) {
|
||||
onError(channel, throwable);
|
||||
}
|
||||
void onVoteReceived(Vote vote, VotifierSession.ProtocolVersion protocolVersion, String remoteAddress) throws Exception;
|
||||
|
||||
@Deprecated
|
||||
default void onError(Channel channel, Throwable throwable) {
|
||||
default void onError(Throwable throwable, boolean voteAlreadyCompleted, String remoteAddress) {
|
||||
throw new RuntimeException("Unimplemented onError handler");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,29 +1,43 @@
|
||||
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 com.vexsoftware.votifier.support.forwarding.cache.VoteCache;
|
||||
import com.vexsoftware.votifier.support.forwarding.proxy.ProxyForwardingVoteSource;
|
||||
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.EventLoopGroup;
|
||||
import io.netty.channel.epoll.Epoll;
|
||||
import io.netty.channel.epoll.EpollEventLoopGroup;
|
||||
import io.netty.channel.epoll.EpollServerSocketChannel;
|
||||
import io.netty.channel.epoll.EpollSocketChannel;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import io.netty.util.concurrent.FastThreadLocalThread;
|
||||
import io.netty.util.concurrent.GlobalEventExecutor;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class VotifierServerBootstrap {
|
||||
private static final boolean USE_EPOLL = Epoll.isAvailable();
|
||||
|
||||
private final String host;
|
||||
private final int port;
|
||||
private final NioEventLoopGroup bossLoopGroup;
|
||||
private final NioEventLoopGroup eventLoopGroup;
|
||||
private final EventLoopGroup bossLoopGroup;
|
||||
private final EventLoopGroup eventLoopGroup;
|
||||
private final VotifierPlugin plugin;
|
||||
private final boolean v1Disable;
|
||||
|
||||
@@ -34,23 +48,41 @@ public class VotifierServerBootstrap {
|
||||
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());
|
||||
if (USE_EPOLL) {
|
||||
this.bossLoopGroup = new EpollEventLoopGroup(1, createThreadFactory("Votifier epoll boss"));
|
||||
this.eventLoopGroup = new EpollEventLoopGroup(3, createThreadFactory("Votifier epoll worker"));
|
||||
plugin.getPluginLogger().info("Using epoll transport to accept votes.");
|
||||
} else {
|
||||
this.bossLoopGroup = new NioEventLoopGroup(1, createThreadFactory("Votifier NIO boss"));
|
||||
this.eventLoopGroup = new NioEventLoopGroup(3, createThreadFactory("Votifier NIO worker"));
|
||||
plugin.getPluginLogger().info("Using NIO transport to accept votes.");
|
||||
}
|
||||
}
|
||||
|
||||
private static ThreadFactory createThreadFactory(String name) {
|
||||
return runnable -> {
|
||||
FastThreadLocalThread thread = new FastThreadLocalThread(runnable, name);
|
||||
thread.setDaemon(true);
|
||||
return thread;
|
||||
};
|
||||
}
|
||||
|
||||
public void start(Consumer<Throwable> error) {
|
||||
Objects.requireNonNull(error, "error");
|
||||
|
||||
VoteInboundHandler voteInboundHandler = new VoteInboundHandler(plugin);
|
||||
|
||||
new ServerBootstrap()
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.channel(USE_EPOLL ? EpollServerSocketChannel.class : NioServerSocketChannel.class)
|
||||
.group(bossLoopGroup, eventLoopGroup)
|
||||
.childHandler(new ChannelInitializer<NioSocketChannel>() {
|
||||
.childHandler(new ChannelInitializer<SocketChannel>() {
|
||||
@Override
|
||||
protected void initChannel(NioSocketChannel channel) {
|
||||
protected void initChannel(SocketChannel channel) {
|
||||
channel.attr(VotifierSession.KEY).set(new VotifierSession());
|
||||
channel.attr(VotifierPlugin.KEY).set(plugin);
|
||||
channel.pipeline().addLast("greetingHandler", new VotifierGreetingHandler());
|
||||
channel.pipeline().addLast("greetingHandler", VotifierGreetingHandler.INSTANCE);
|
||||
channel.pipeline().addLast("protocolDifferentiator", new VotifierProtocolDifferentiator(false, !v1Disable));
|
||||
channel.pipeline().addLast("voteHandler", new VoteInboundHandler(plugin));
|
||||
channel.pipeline().addLast("voteHandler", voteInboundHandler);
|
||||
}
|
||||
})
|
||||
.bind(host, port)
|
||||
@@ -70,12 +102,17 @@ public class VotifierServerBootstrap {
|
||||
});
|
||||
}
|
||||
|
||||
public Bootstrap client() {
|
||||
private Bootstrap client() {
|
||||
return new Bootstrap()
|
||||
.channel(NioSocketChannel.class)
|
||||
.channel(USE_EPOLL ? EpollSocketChannel.class : NioSocketChannel.class)
|
||||
.group(eventLoopGroup);
|
||||
}
|
||||
|
||||
public ProxyForwardingVoteSource createForwardingSource(List<ProxyForwardingVoteSource.BackendServer> backendServers,
|
||||
VoteCache voteCache) {
|
||||
return new ProxyForwardingVoteSource(plugin, this::client, backendServers, voteCache);
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
if (serverChannel != null) {
|
||||
try {
|
||||
@@ -86,5 +123,12 @@ public class VotifierServerBootstrap {
|
||||
}
|
||||
eventLoopGroup.shutdownGracefully();
|
||||
bossLoopGroup.shutdownGracefully();
|
||||
|
||||
try {
|
||||
bossLoopGroup.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
|
||||
eventLoopGroup.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,8 +40,14 @@ public class VotifierSession {
|
||||
}
|
||||
|
||||
public enum ProtocolVersion {
|
||||
UNKNOWN,
|
||||
ONE,
|
||||
TWO
|
||||
UNKNOWN("unknown"),
|
||||
ONE("protocol v1"),
|
||||
TWO("protocol v2"),
|
||||
TEST("test");
|
||||
|
||||
public final String humanReadable;
|
||||
ProtocolVersion(String hr) {
|
||||
this.humanReadable = hr;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,21 +6,29 @@ import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.VotifierSession;
|
||||
import com.vexsoftware.votifier.util.GsonInst;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
@ChannelHandler.Sharable
|
||||
public class VoteInboundHandler extends SimpleChannelInboundHandler<Vote> {
|
||||
private final VoteHandler handler;
|
||||
private final AtomicLong lastError;
|
||||
private final AtomicLong errorsSent;
|
||||
|
||||
public VoteInboundHandler(VoteHandler handler) {
|
||||
this.handler = handler;
|
||||
this.lastError = new AtomicLong();
|
||||
this.errorsSent = new AtomicLong();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, final Vote vote) throws Exception {
|
||||
VotifierSession session = ctx.channel().attr(VotifierSession.KEY).get();
|
||||
|
||||
handler.onVoteReceived(ctx.channel(), vote, session.getVersion());
|
||||
handler.onVoteReceived(vote, session.getVersion(), ctx.channel().remoteAddress().toString());
|
||||
session.completeVote();
|
||||
|
||||
if (session.getVersion() == VotifierSession.ProtocolVersion.ONE) {
|
||||
@@ -36,7 +44,8 @@ public class VoteInboundHandler extends SimpleChannelInboundHandler<Vote> {
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
VotifierSession session = ctx.channel().attr(VotifierSession.KEY).get();
|
||||
|
||||
handler.onError(ctx.channel(), session.hasCompletedVote(), cause);
|
||||
String remoteAddr = ctx.channel().remoteAddress().toString();
|
||||
boolean hasCompletedVote = session.hasCompletedVote();
|
||||
|
||||
if (session.getVersion() == VotifierSession.ProtocolVersion.TWO) {
|
||||
JsonObject object = new JsonObject();
|
||||
@@ -47,5 +56,22 @@ public class VoteInboundHandler extends SimpleChannelInboundHandler<Vote> {
|
||||
} else {
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
if (!willThrottleErrorLogging()) {
|
||||
handler.onError(cause, hasCompletedVote, remoteAddr);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean willThrottleErrorLogging() {
|
||||
long lastErrorAt = this.lastError.get();
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
if (lastErrorAt + 2000 >= now) {
|
||||
return this.errorsSent.incrementAndGet() >= 5;
|
||||
} else {
|
||||
this.lastError.set(now);
|
||||
this.errorsSent.set(0);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
@@ -3,6 +3,7 @@ package com.vexsoftware.votifier.net.protocol;
|
||||
import com.vexsoftware.votifier.net.VotifierSession;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
|
||||
@@ -11,7 +12,12 @@ import java.nio.charset.StandardCharsets;
|
||||
/**
|
||||
* Handles the Votifier greeting.
|
||||
*/
|
||||
@ChannelHandler.Sharable
|
||||
public class VotifierGreetingHandler extends ChannelInboundHandlerAdapter {
|
||||
public static final VotifierGreetingHandler INSTANCE = new VotifierGreetingHandler();
|
||||
|
||||
private VotifierGreetingHandler() {}
|
||||
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) {
|
||||
VotifierSession session = ctx.channel().attr(VotifierSession.KEY).get();
|
||||
|
||||
+24
-9
@@ -3,7 +3,9 @@ package com.vexsoftware.votifier.net.protocol;
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.protocol.v1crypto.RSA;
|
||||
import com.vexsoftware.votifier.util.QuietException;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
@@ -16,33 +18,46 @@ import java.util.List;
|
||||
*/
|
||||
public class VotifierProtocol1Decoder extends ByteToMessageDecoder {
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> list) {
|
||||
if (buf.readableBytes() < 256) {
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> list) throws Exception {
|
||||
if (!ctx.channel().isActive()) {
|
||||
buf.skipBytes(buf.readableBytes());
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] block = new byte[buf.readableBytes()];
|
||||
buf.getBytes(0, block);
|
||||
// "Drain" the whole buffer
|
||||
buf.readerIndex(buf.readableBytes());
|
||||
if (buf.readableBytes() < 256) {
|
||||
// The client might have not sent all the data yet, so don't eject the connection.
|
||||
return;
|
||||
}
|
||||
|
||||
if (buf.readableBytes() > 256) {
|
||||
// They sent too much data.
|
||||
throw new QuietException("Could not decrypt data from " + ctx.channel().remoteAddress() + " as it is too long. Attack?");
|
||||
}
|
||||
|
||||
byte[] block = ByteBufUtil.getBytes(buf);
|
||||
buf.skipBytes(buf.readableBytes());
|
||||
|
||||
VotifierPlugin plugin = ctx.channel().attr(VotifierPlugin.KEY).get();
|
||||
|
||||
try {
|
||||
block = RSA.decrypt(block, plugin.getProtocolV1Key().getPrivate());
|
||||
} catch (Exception e) {
|
||||
throw new CorruptedFrameException("Could not decrypt data (is your key correct?)", e);
|
||||
if (plugin.isDebug()) {
|
||||
throw new CorruptedFrameException("Could not decrypt data from " + ctx.channel().remoteAddress() + ". Make sure the public key on the list is correct.", e);
|
||||
} else {
|
||||
throw new QuietException("Could not decrypt data from " + ctx.channel().remoteAddress() + ". Make sure the public key on the list is correct.");
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the string we received.
|
||||
String all = new String(block, StandardCharsets.US_ASCII);
|
||||
String[] split = all.split("\n");
|
||||
if (split.length < 5) {
|
||||
throw new CorruptedFrameException("Not enough fields specified in vote.");
|
||||
throw new QuietException("Not enough fields specified in vote. This is not a NuVotifier issue. Got " + split.length + " fields, but needed 5.");
|
||||
}
|
||||
|
||||
if (!split[0].equals("VOTE")) {
|
||||
throw new CorruptedFrameException("VOTE opcode not found");
|
||||
throw new QuietException("The VOTE opcode was not present. This is not a NuVotifier issue, but a bug with the server list.");
|
||||
}
|
||||
|
||||
// Create the vote.
|
||||
|
||||
+6
-8
@@ -1,10 +1,10 @@
|
||||
package com.vexsoftware.votifier.net.protocol;
|
||||
|
||||
import com.vexsoftware.votifier.net.VotifierSession;
|
||||
import com.vexsoftware.votifier.util.QuietException;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
|
||||
import io.netty.handler.codec.string.StringDecoder;
|
||||
import io.netty.handler.codec.string.StringEncoder;
|
||||
@@ -16,6 +16,8 @@ import java.util.List;
|
||||
* Attempts to determine if original protocol or protocol v2 is being used.
|
||||
*/
|
||||
public class VotifierProtocolDifferentiator extends ByteToMessageDecoder {
|
||||
private static final QuietException V2_ONLY = new QuietException("This server only accepts well-formed Votifier v2 packets.");
|
||||
|
||||
private static final short PROTOCOL_2_MAGIC = 0x733A;
|
||||
private final boolean testMode;
|
||||
private final boolean allowv1;
|
||||
@@ -26,7 +28,7 @@ public class VotifierProtocolDifferentiator extends ByteToMessageDecoder {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> list) {
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> list) throws Exception {
|
||||
// Determine the number of bytes that are available.
|
||||
int readable = buf.readableBytes();
|
||||
|
||||
@@ -35,11 +37,7 @@ public class VotifierProtocolDifferentiator extends ByteToMessageDecoder {
|
||||
return;
|
||||
}
|
||||
|
||||
short readMagic = buf.readShort();
|
||||
|
||||
// Reset reader index again
|
||||
buf.readerIndex(0);
|
||||
|
||||
short readMagic = buf.getShort(0);
|
||||
VotifierSession session = ctx.channel().attr(VotifierSession.KEY).get();
|
||||
|
||||
if (readMagic == PROTOCOL_2_MAGIC) {
|
||||
@@ -55,7 +53,7 @@ public class VotifierProtocolDifferentiator extends ByteToMessageDecoder {
|
||||
}
|
||||
} else {
|
||||
if (!allowv1) {
|
||||
throw new CorruptedFrameException("This server only accepts well-formed Votifier v2 packets.");
|
||||
throw V2_ONLY;
|
||||
}
|
||||
// Probably Protocol v1 Vote Message
|
||||
session.setVersion(VotifierSession.ProtocolVersion.ONE);
|
||||
|
||||
@@ -63,7 +63,7 @@ public class RSAIO {
|
||||
public static byte[] readB64File(File directory, String name) throws IOException {
|
||||
File f = new File(directory, name);
|
||||
byte[] contents = Files.readAllBytes(f.toPath());
|
||||
String strContents = new String(contents);
|
||||
String strContents = new String(contents, StandardCharsets.US_ASCII);
|
||||
strContents = strContents.trim();
|
||||
try {
|
||||
return Base64.getDecoder().decode(strContents);
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package com.vexsoftware.votifier.platform;
|
||||
|
||||
import com.vexsoftware.votifier.platform.LoggingAdapter;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.LogRecord;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class JavaUtilLogger implements LoggingAdapter {
|
||||
@@ -23,6 +22,14 @@ public class JavaUtilLogger implements LoggingAdapter {
|
||||
logger.log(Level.SEVERE, s, o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(String s, Throwable e, Object... o) {
|
||||
LogRecord lr = new LogRecord(Level.SEVERE, s);
|
||||
lr.setParameters(o);
|
||||
lr.setThrown(e);
|
||||
logger.log(lr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(String s) {
|
||||
logger.warning(s);
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.vexsoftware.votifier.platform;
|
||||
public interface LoggingAdapter {
|
||||
void error(String s);
|
||||
void error(String s, Object... o);
|
||||
void error(String s, Throwable e, Object... o);
|
||||
|
||||
void warn(String s);
|
||||
void warn(String s, Object... o);
|
||||
|
||||
+2
-18
@@ -1,7 +1,6 @@
|
||||
package com.vexsoftware.votifier.platform.scheduler;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -10,22 +9,7 @@ public class ScheduledExecutorServiceVotifierScheduler implements VotifierSchedu
|
||||
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));
|
||||
this.service = Objects.requireNonNull(service, "service");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
-5
@@ -3,11 +3,6 @@ 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);
|
||||
|
||||
|
||||
+6
-2
@@ -39,7 +39,11 @@ public abstract class AbstractPluginMessagingForwardingSource implements Forward
|
||||
byte[] rawData = v.serialize().toString().getBytes(StandardCharsets.UTF_8);
|
||||
for (BackendServer server : plugin.getAllBackendServers()) {
|
||||
if (!serverFilter.isAllowed(server.getName())) continue;
|
||||
if (!forwardSpecific(server, rawData)) attemptToAddToCache(v, server.getName());
|
||||
if (!forwardSpecific(server, rawData)) {
|
||||
attemptToAddToCache(v, server.getName());
|
||||
} else if (plugin.isDebug()) {
|
||||
plugin.getPluginLogger().info("Successfully forwarded vote " + v + " to server " + server.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,7 +141,7 @@ public abstract class AbstractPluginMessagingForwardingSource implements Forward
|
||||
|
||||
// cachedVotes contains any votes which have yet to be evicted
|
||||
cb.accept(cachedVotes);
|
||||
}, 1, TimeUnit.SECONDS);
|
||||
}, evictedAlready == 0 ? 3 : 1, TimeUnit.SECONDS);
|
||||
} else {
|
||||
cb.accept(cachedVotes);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ public class ServerFilter {
|
||||
}
|
||||
|
||||
public ServerFilter() {
|
||||
this(Collections.EMPTY_SET, true);
|
||||
this(Collections.emptySet(), true);
|
||||
}
|
||||
|
||||
private final Collection<String> names;
|
||||
|
||||
+26
-21
@@ -1,10 +1,8 @@
|
||||
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.LoggingAdapter;
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.platform.scheduler.ScheduledVotifierTask;
|
||||
@@ -12,6 +10,8 @@ import com.vexsoftware.votifier.util.GsonInst;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@@ -21,8 +21,8 @@ public class FileVoteCache extends MemoryVoteCache {
|
||||
private final File cacheFile;
|
||||
private final ScheduledVotifierTask saveTask;
|
||||
|
||||
public FileVoteCache(int initialMemorySize, final VotifierPlugin plugin, File cacheFile, long voteTTL) throws IOException {
|
||||
super(initialMemorySize, plugin, voteTTL);
|
||||
public FileVoteCache(final VotifierPlugin plugin, File cacheFile, long voteTTL) throws IOException {
|
||||
super(plugin, voteTTL);
|
||||
this.cacheFile = cacheFile;
|
||||
this.l = plugin.getPluginLogger();
|
||||
|
||||
@@ -48,9 +48,13 @@ public class FileVoteCache extends MemoryVoteCache {
|
||||
private void load() throws IOException {
|
||||
// Load the cache from disk
|
||||
JsonObject object;
|
||||
try (BufferedReader reader = Files.newReader(cacheFile, StandardCharsets.UTF_8)) {
|
||||
try (BufferedReader reader = Files.newBufferedReader(cacheFile.toPath(), StandardCharsets.UTF_8)) {
|
||||
object = GsonInst.gson.fromJson(reader, JsonObject.class);
|
||||
} catch (FileNotFoundException e) {
|
||||
if (object == null) {
|
||||
// When the input is not malformed but instead empty, this returns null. Simply assume it is empty.
|
||||
object = new JsonObject();
|
||||
}
|
||||
} catch (NoSuchFileException e) {
|
||||
object = new JsonObject();
|
||||
}
|
||||
|
||||
@@ -73,11 +77,11 @@ public class FileVoteCache extends MemoryVoteCache {
|
||||
JsonObject servers = object.getAsJsonObject("servers");
|
||||
|
||||
for (String player : keySet(players)) {
|
||||
playerVoteCache.putAll(player, readVotes(players.getAsJsonArray(player)));
|
||||
playerVoteCache.put(player, readVotes(players.getAsJsonArray(player)));
|
||||
}
|
||||
|
||||
for (String server : keySet(servers)) {
|
||||
voteCache.putAll(server, readVotes(servers.getAsJsonArray(server)));
|
||||
voteCache.put(server, readVotes(servers.getAsJsonArray(server)));
|
||||
}
|
||||
|
||||
if (resave) {
|
||||
@@ -98,11 +102,11 @@ public class FileVoteCache extends MemoryVoteCache {
|
||||
}
|
||||
}
|
||||
|
||||
private Collection<Vote> readVotes(JsonArray voteArray) {
|
||||
List<Vote> votes = new ArrayList<>(voteArray.size());
|
||||
private Collection<VoteWithRecordedTimestamp> readVotes(JsonArray voteArray) {
|
||||
Collection<VoteWithRecordedTimestamp> votes = new HashSet<>(voteArray.size());
|
||||
for (int i = 0; i < voteArray.size(); i++) {
|
||||
JsonObject voteObject = voteArray.get(i).getAsJsonObject();
|
||||
Vote v = new Vote(voteObject);
|
||||
VoteWithRecordedTimestamp v = new VoteWithRecordedTimestamp(voteObject);
|
||||
if (hasTimedOut(v))
|
||||
l.warn("Purging out of date vote.", v);
|
||||
else
|
||||
@@ -112,31 +116,32 @@ public class FileVoteCache extends MemoryVoteCache {
|
||||
}
|
||||
|
||||
public void save() throws IOException {
|
||||
cacheLock.lock();
|
||||
JsonObject votesObject = new JsonObject();
|
||||
votesObject.addProperty("version", 2);
|
||||
|
||||
cacheLock.lock();
|
||||
try {
|
||||
votesObject.add("players", serializeMap(playerVoteCache.asMap()));
|
||||
votesObject.add("servers", serializeMap(voteCache.asMap()));
|
||||
votesObject.addProperty("version", 2);
|
||||
votesObject.add("players", serializeMap(playerVoteCache));
|
||||
votesObject.add("servers", serializeMap(voteCache));
|
||||
} finally {
|
||||
cacheLock.unlock();
|
||||
}
|
||||
|
||||
try (BufferedWriter writer = Files.newWriter(cacheFile, StandardCharsets.UTF_8)) {
|
||||
try (BufferedWriter writer = Files.newBufferedWriter(cacheFile.toPath(), StandardCharsets.UTF_8)) {
|
||||
GsonInst.gson.toJson(votesObject, writer);
|
||||
}
|
||||
}
|
||||
|
||||
public JsonObject serializeMap(Map<String, Collection<Vote>> map) {
|
||||
public JsonObject serializeMap(Map<String, Collection<VoteWithRecordedTimestamp>> map) {
|
||||
JsonObject o = new JsonObject();
|
||||
|
||||
Iterator<Map.Entry<String, Collection<Vote>>> entryItr = map.entrySet().iterator();
|
||||
Iterator<Map.Entry<String, Collection<VoteWithRecordedTimestamp>>> entryItr = map.entrySet().iterator();
|
||||
while (entryItr.hasNext()) {
|
||||
Map.Entry<String, Collection<Vote>> entry = entryItr.next();
|
||||
Map.Entry<String, Collection<VoteWithRecordedTimestamp>> entry = entryItr.next();
|
||||
JsonArray array = new JsonArray();
|
||||
Iterator<Vote> voteItr = entry.getValue().iterator();
|
||||
Iterator<VoteWithRecordedTimestamp> voteItr = entry.getValue().iterator();
|
||||
while (voteItr.hasNext()) {
|
||||
Vote vote = voteItr.next();
|
||||
VoteWithRecordedTimestamp vote = voteItr.next();
|
||||
|
||||
// if the vote is no longer valid, notify and remove
|
||||
if (hasTimedOut(vote)) {
|
||||
|
||||
Vendored
+78
-23
@@ -1,8 +1,6 @@
|
||||
package com.vexsoftware.votifier.support.forwarding.cache;
|
||||
|
||||
import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.platform.LoggingAdapter;
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
@@ -20,16 +18,16 @@ public class MemoryVoteCache implements VoteCache {
|
||||
private final LoggingAdapter l;
|
||||
private final long voteTTL;
|
||||
|
||||
protected final Multimap<String, Vote> voteCache;
|
||||
protected final Multimap<String, Vote> playerVoteCache;
|
||||
protected final Map<String, Collection<VoteWithRecordedTimestamp>> voteCache;
|
||||
protected final Map<String, Collection<VoteWithRecordedTimestamp>> playerVoteCache;
|
||||
|
||||
protected final ReentrantLock cacheLock = new ReentrantLock();
|
||||
|
||||
private final ScheduledVotifierTask sweepTask;
|
||||
|
||||
public MemoryVoteCache(int initialSize, VotifierPlugin p, long voteTTL) {
|
||||
voteCache = HashMultimap.create(initialSize, 10); // 10 is a complete guess. I have no idea .-.
|
||||
playerVoteCache = HashMultimap.create();
|
||||
public MemoryVoteCache(VotifierPlugin p, long voteTTL) {
|
||||
voteCache = new HashMap<>();
|
||||
playerVoteCache = new HashMap<>();
|
||||
|
||||
this.voteTTL = voteTTL;
|
||||
|
||||
@@ -41,7 +39,7 @@ public class MemoryVoteCache implements VoteCache {
|
||||
public Collection<String> getCachedServers() {
|
||||
cacheLock.lock();
|
||||
try {
|
||||
return ImmutableList.copyOf(voteCache.keySet());
|
||||
return Collections.unmodifiableCollection(new ArrayList<>(voteCache.keySet()));
|
||||
} finally {
|
||||
cacheLock.unlock();
|
||||
}
|
||||
@@ -52,7 +50,7 @@ public class MemoryVoteCache implements VoteCache {
|
||||
if (server == null) throw new NullPointerException();
|
||||
cacheLock.lock();
|
||||
try {
|
||||
voteCache.put(server, v);
|
||||
voteCache.computeIfAbsent(server, k -> new HashSet<>()).add(new VoteWithRecordedTimestamp(v));
|
||||
} finally {
|
||||
cacheLock.unlock();
|
||||
}
|
||||
@@ -63,7 +61,7 @@ public class MemoryVoteCache implements VoteCache {
|
||||
if (player == null) throw new NullPointerException();
|
||||
cacheLock.lock();
|
||||
try {
|
||||
playerVoteCache.put(player, v);
|
||||
playerVoteCache.computeIfAbsent(player, k -> new HashSet<>()).add(new VoteWithRecordedTimestamp(v));
|
||||
} finally {
|
||||
cacheLock.unlock();
|
||||
}
|
||||
@@ -74,7 +72,12 @@ public class MemoryVoteCache implements VoteCache {
|
||||
if (player == null) throw new NullPointerException();
|
||||
cacheLock.lock();
|
||||
try {
|
||||
return new HashSet<>(playerVoteCache.removeAll(player));
|
||||
Collection<VoteWithRecordedTimestamp> playerVotes = playerVoteCache.remove(player);
|
||||
if (playerVotes != null) {
|
||||
return new HashSet<>(playerVotes);
|
||||
} else {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
} finally {
|
||||
cacheLock.unlock();
|
||||
}
|
||||
@@ -85,7 +88,12 @@ public class MemoryVoteCache implements VoteCache {
|
||||
if (server == null) throw new NullPointerException();
|
||||
cacheLock.lock();
|
||||
try {
|
||||
return new HashSet<>(voteCache.removeAll(server));
|
||||
Collection<VoteWithRecordedTimestamp> serverVotes = voteCache.remove(server);
|
||||
if (serverVotes != null) {
|
||||
return new HashSet<>(serverVotes);
|
||||
} else {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
} finally {
|
||||
cacheLock.unlock();
|
||||
}
|
||||
@@ -101,20 +109,67 @@ public class MemoryVoteCache implements VoteCache {
|
||||
}
|
||||
}
|
||||
|
||||
private void sweep(Multimap<?, Vote> m) {
|
||||
Iterator<Vote> vi = m.values().iterator();
|
||||
while (vi.hasNext()) {
|
||||
Vote v = vi.next();
|
||||
if (hasTimedOut(v)) {
|
||||
l.warn("Purging out of date vote.", v);
|
||||
vi.remove();
|
||||
private void sweep(Map<String, Collection<VoteWithRecordedTimestamp>> m) {
|
||||
for (Map.Entry<String, Collection<VoteWithRecordedTimestamp>> entry : m.entrySet()) {
|
||||
Iterator<VoteWithRecordedTimestamp> vi = entry.getValue().iterator();
|
||||
while (vi.hasNext()) {
|
||||
VoteWithRecordedTimestamp v = vi.next();
|
||||
if (hasTimedOut(v)) {
|
||||
l.warn("Purging out of date vote.", v);
|
||||
vi.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean hasTimedOut(Vote v) {
|
||||
protected boolean hasTimedOut(VoteWithRecordedTimestamp v) {
|
||||
if (voteTTL == -1) return false;
|
||||
// scale voteTTL to milliseconds
|
||||
return v.getLocalTimestamp() + voteTTL * 24 * 60 * 60 * 1000 < System.currentTimeMillis();
|
||||
|
||||
long daysAsMillis = TimeUnit.DAYS.toMillis(voteTTL);
|
||||
return v.recorded + daysAsMillis < System.currentTimeMillis();
|
||||
}
|
||||
|
||||
static class VoteWithRecordedTimestamp extends Vote {
|
||||
private final long recorded;
|
||||
|
||||
VoteWithRecordedTimestamp(Vote vote) {
|
||||
super(vote);
|
||||
this.recorded = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
VoteWithRecordedTimestamp(JsonObject object) {
|
||||
super(object);
|
||||
if (object.has("recorded")) {
|
||||
this.recorded = object.get("recorded").getAsLong();
|
||||
} else {
|
||||
// Assign the current time, in the hope that the stale votes will eventually get purged.
|
||||
this.recorded = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonObject serialize() {
|
||||
JsonObject object = super.serialize();
|
||||
object.addProperty("recorded", this.recorded);
|
||||
return object;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
if (!super.equals(o)) return false;
|
||||
|
||||
VoteWithRecordedTimestamp that = (VoteWithRecordedTimestamp) o;
|
||||
|
||||
return recorded == that.recorded;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = super.hashCode();
|
||||
result = 31 * result + (int) (recorded ^ (recorded >>> 32));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -51,6 +51,6 @@ public interface VoteCache {
|
||||
* @return A collection of all votes which were previously in the cache under the player
|
||||
*/
|
||||
default Collection<Vote> evictPlayer(String player) {
|
||||
return Collections.EMPTY_LIST;
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -10,7 +10,7 @@ import com.vexsoftware.votifier.model.Vote;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
|
||||
import io.netty.handler.codec.Delimiters;
|
||||
import io.netty.handler.codec.string.StringDecoder;
|
||||
@@ -53,9 +53,9 @@ public class ProxyForwardingVoteSource implements ForwardingVoteSource {
|
||||
|
||||
private void forwardVote(final BackendServer server, final Vote v, final int tries) {
|
||||
nettyBootstrap.get()
|
||||
.handler(new ChannelInitializer<NioSocketChannel>() {
|
||||
.handler(new ChannelInitializer<SocketChannel>() {
|
||||
@Override
|
||||
protected void initChannel(NioSocketChannel channel) {
|
||||
protected void initChannel(SocketChannel channel) {
|
||||
channel.pipeline().addLast(new DelimiterBasedFrameDecoder(256, true, Delimiters.lineDelimiter()));
|
||||
channel.pipeline().addLast(new ReadTimeoutHandler(5, TimeUnit.SECONDS));
|
||||
channel.pipeline().addLast(STRING_DECODER);
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.vexsoftware.votifier.util;
|
||||
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class ArgsToVote {
|
||||
|
||||
private static final Pattern argumentPattern = Pattern.compile("([a-zA-Z]+)=(\\S*)");
|
||||
|
||||
public static Vote parse(String[] arguments) {
|
||||
return parse(arguments, null);
|
||||
}
|
||||
|
||||
public static Vote parse(String[] arguments, Map<String, String> additionalArgs) {
|
||||
String serviceName = "TestVote";
|
||||
String username = null;
|
||||
String address = "127.0.0.1";
|
||||
long localTimestamp = System.currentTimeMillis();
|
||||
String timestamp = Long.toString(localTimestamp, 10);
|
||||
|
||||
for (String s : arguments) {
|
||||
Matcher m = argumentPattern.matcher(s);
|
||||
if (m.matches()) {
|
||||
String key = m.group(1).toLowerCase();
|
||||
String v = m.group(2);
|
||||
switch (key) {
|
||||
case "servicename":
|
||||
serviceName = v;
|
||||
break;
|
||||
case "username":
|
||||
if (v.length() > 16)
|
||||
throw new IllegalArgumentException("Illegal username - must be less than 16 characters long.");
|
||||
username = v;
|
||||
break;
|
||||
case "address":
|
||||
address = v;
|
||||
break;
|
||||
case "timestamp":
|
||||
timestamp = v;
|
||||
break;
|
||||
default:
|
||||
if (additionalArgs != null)
|
||||
additionalArgs.put(key, v);
|
||||
break;
|
||||
}
|
||||
|
||||
} else {
|
||||
if (s.length() > 16)
|
||||
throw new IllegalArgumentException("Illegal username - must be less than 16 characters long.");
|
||||
username = s; // ezpz
|
||||
}
|
||||
}
|
||||
|
||||
if (username == null)
|
||||
throw new IllegalArgumentException("Username not specified!");
|
||||
|
||||
return new Vote(serviceName, username, address, timestamp);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.vexsoftware.votifier.util;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class IOUtil {
|
||||
public static byte[] readAllBytes(InputStream from) throws IOException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(8192);
|
||||
byte[] buf = new byte[8192];
|
||||
int read;
|
||||
while ((read = from.read(buf)) != -1) {
|
||||
out.write(buf, 0, read);
|
||||
}
|
||||
return out.toByteArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.vexsoftware.votifier.util;
|
||||
|
||||
public class QuietException extends Exception {
|
||||
public QuietException() {
|
||||
}
|
||||
|
||||
public QuietException(String s) {
|
||||
super(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized Throwable fillInStackTrace() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+4
-5
@@ -1,14 +1,13 @@
|
||||
package com.vexsoftware.votifier.util.standalone;
|
||||
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.VotifierServerBootstrap;
|
||||
import com.vexsoftware.votifier.net.VotifierSession;
|
||||
import com.vexsoftware.votifier.platform.JavaUtilLogger;
|
||||
import com.vexsoftware.votifier.platform.LoggingAdapter;
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.VotifierSession;
|
||||
import com.vexsoftware.votifier.platform.scheduler.ScheduledExecutorServiceVotifierScheduler;
|
||||
import com.vexsoftware.votifier.platform.scheduler.VotifierScheduler;
|
||||
import io.netty.channel.Channel;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.security.Key;
|
||||
@@ -69,12 +68,12 @@ public class StandaloneVotifierPlugin implements VotifierPlugin {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVoteReceived(Channel channel, Vote vote, VotifierSession.ProtocolVersion protocolVersion) throws Exception {
|
||||
public void onVoteReceived(Vote vote, VotifierSession.ProtocolVersion protocolVersion, String remoteAddress) throws Exception {
|
||||
receiver.onVote(vote);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Channel channel, boolean alreadyHandledVote, Throwable throwable) {
|
||||
public void onError(Throwable throwable, boolean alreadyHandledVote, String remoteAddress) {
|
||||
receiver.onException(throwable);
|
||||
}
|
||||
}
|
||||
@@ -85,6 +85,11 @@ public class TestVotifierPlugin implements VotifierPlugin {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDebug() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void specificKeysOnly() {
|
||||
keyMap.clear();
|
||||
keyMap.put("Test", KeyCreator.createKeyFrom("test"));
|
||||
@@ -96,7 +101,7 @@ public class TestVotifierPlugin implements VotifierPlugin {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVoteReceived(Channel channel, Vote vote, VotifierSession.ProtocolVersion protocolVersion) throws Exception {
|
||||
public void onVoteReceived(Vote vote, VotifierSession.ProtocolVersion protocolVersion, String remoteAddress) throws Exception {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,6 @@ public class VoteUtil {
|
||||
builder.append(s).append('\n'); // naive join needed!
|
||||
}
|
||||
|
||||
return RSA.encrypt(builder.toString().getBytes(StandardCharsets.UTF_8), key);
|
||||
return RSA.encrypt(builder.toString().getBytes(StandardCharsets.US_ASCII), key);
|
||||
}
|
||||
}
|
||||
+11
-15
@@ -4,18 +4,20 @@ 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;
|
||||
import com.vexsoftware.votifier.util.QuietException;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.embedded.EmbeddedChannel;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
import org.junit.Test;
|
||||
import io.netty.handler.codec.DecoderException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.PublicKey;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class VotifierProtocol1DecoderTest {
|
||||
private static final VotifierSession SESSION = new VotifierSession();
|
||||
@@ -50,24 +52,21 @@ public class VotifierProtocol1DecoderTest {
|
||||
byte[] encrypted = RSA.encrypt(bad.getBytes(StandardCharsets.UTF_8), TestVotifierPlugin.getI().getProtocolV1Key().getPublic());
|
||||
ByteBuf encryptedByteBuf = Unpooled.wrappedBuffer(encrypted);
|
||||
|
||||
try {
|
||||
channel.writeInbound(encryptedByteBuf);
|
||||
} finally {
|
||||
channel.close();
|
||||
}
|
||||
assertThrows(DecoderException.class, () -> channel.writeInbound(encryptedByteBuf));
|
||||
channel.close();
|
||||
}
|
||||
|
||||
@Test(expected = CorruptedFrameException.class)
|
||||
@Test
|
||||
public void testFailureDecodeMissingField() throws Exception {
|
||||
verifyFailure("VOTE\nTest\ntest\ntest"); // missing field
|
||||
}
|
||||
|
||||
@Test(expected = CorruptedFrameException.class)
|
||||
@Test
|
||||
public void testFailureDecodeBadOpcode() throws Exception {
|
||||
verifyFailure("TEST\nTest\ntest\ntest\ntest\n");
|
||||
}
|
||||
|
||||
@Test(expected = CorruptedFrameException.class)
|
||||
@Test
|
||||
public void testFailureDecodeBadRsa() throws Exception {
|
||||
// Decode our bad RSA key
|
||||
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
||||
@@ -80,10 +79,7 @@ public class VotifierProtocol1DecoderTest {
|
||||
byte[] encrypted = VoteUtil.encodePOJOv1(new Vote("Test", "test", "test", "test"), badPublicKey);
|
||||
ByteBuf encryptedByteBuf = Unpooled.wrappedBuffer(encrypted);
|
||||
|
||||
try {
|
||||
channel.writeInbound(encryptedByteBuf);
|
||||
} finally {
|
||||
channel.close();
|
||||
}
|
||||
assertThrows(DecoderException.class, ()->channel.writeInbound(encryptedByteBuf));
|
||||
channel.close();
|
||||
}
|
||||
}
|
||||
+18
-29
@@ -10,14 +10,14 @@ import io.netty.channel.embedded.EmbeddedChannel;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
import io.netty.handler.codec.DecoderException;
|
||||
import org.json.JSONObject;
|
||||
import org.junit.Test;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.Key;
|
||||
import java.util.Base64;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class VotifierProtocol2DecoderTest {
|
||||
private static final VotifierSession SESSION = new VotifierSession();
|
||||
@@ -61,7 +61,7 @@ public class VotifierProtocol2DecoderTest {
|
||||
sendVote(new Vote("Test", "test", "test", "0"), TestVotifierPlugin.getI().getTokens().get("default"), true);
|
||||
}
|
||||
|
||||
@Test(expected = DecoderException.class)
|
||||
@Test
|
||||
public void testFailureDecodeBadPacket() {
|
||||
// Create a well-formed request
|
||||
EmbeddedChannel channel = createChannel();
|
||||
@@ -73,14 +73,11 @@ public class VotifierProtocol2DecoderTest {
|
||||
object.put("payload", GsonInst.gson.toJson(payload));
|
||||
// We "forget" the signature.
|
||||
|
||||
try {
|
||||
channel.writeInbound(object.toString());
|
||||
} finally {
|
||||
channel.close();
|
||||
}
|
||||
assertThrows(DecoderException.class, () -> channel.writeInbound(object.toString()));
|
||||
channel.close();
|
||||
}
|
||||
|
||||
@Test(expected = DecoderException.class)
|
||||
@Test
|
||||
public void testFailureDecodeBadVoteField() throws Exception {
|
||||
// Create a well-formed request
|
||||
EmbeddedChannel channel = createChannel();
|
||||
@@ -96,14 +93,11 @@ public class VotifierProtocol2DecoderTest {
|
||||
object.put("signature",
|
||||
Base64.getEncoder().encodeToString(mac.doFinal(payloadEncoded.getBytes(StandardCharsets.UTF_8))));
|
||||
|
||||
try {
|
||||
channel.writeInbound(object.toString());
|
||||
} finally {
|
||||
channel.close();
|
||||
}
|
||||
assertThrows(DecoderException.class, () -> channel.writeInbound(object.toString()));
|
||||
channel.close();
|
||||
}
|
||||
|
||||
@Test(expected = DecoderException.class)
|
||||
@Test
|
||||
public void testFailureDecodeBadChallenge() throws Exception {
|
||||
// Create a well-formed request
|
||||
EmbeddedChannel channel = createChannel();
|
||||
@@ -120,29 +114,24 @@ public class VotifierProtocol2DecoderTest {
|
||||
object.put("signature",
|
||||
Base64.getEncoder().encode(mac.doFinal(payloadEncoded.getBytes(StandardCharsets.UTF_8))));
|
||||
|
||||
try {
|
||||
channel.writeInbound(object.toString());
|
||||
} finally {
|
||||
channel.close();
|
||||
}
|
||||
assertThrows(DecoderException.class, () -> channel.writeInbound(object.toString()));
|
||||
channel.close();
|
||||
}
|
||||
|
||||
@Test(expected = DecoderException.class)
|
||||
@Test
|
||||
public void testFailureDecodeNonExistentKey() throws Exception {
|
||||
TestVotifierPlugin.getI().specificKeysOnly();
|
||||
|
||||
Vote vote = new Vote("Bad Service", "test", "test", "0");
|
||||
|
||||
try {
|
||||
sendVote(vote, TestVotifierPlugin.getI().getTokens().get("Test"), false);
|
||||
} finally {
|
||||
TestVotifierPlugin.getI().restoreDefault();
|
||||
}
|
||||
assertThrows(DecoderException.class, () -> sendVote(vote, TestVotifierPlugin.getI().getTokens().get("Test"), false));
|
||||
|
||||
TestVotifierPlugin.getI().restoreDefault();
|
||||
}
|
||||
|
||||
@Test(expected = CorruptedFrameException.class)
|
||||
public void testFailureDecodeBadSignature() throws Exception {
|
||||
@Test
|
||||
public void testFailureDecodeBadSignature() {
|
||||
Vote vote = new Vote("Bad Service", "test", "test", "0");
|
||||
sendVote(vote, KeyCreator.createKeyFrom("BadKey"), false);
|
||||
assertThrows(CorruptedFrameException.class, () -> sendVote(vote, KeyCreator.createKeyFrom("BadKey"), false));
|
||||
}
|
||||
}
|
||||
+10
-12
@@ -2,13 +2,16 @@ package com.vexsoftware.votifier.net.protocol;
|
||||
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.VotifierSession;
|
||||
import com.vexsoftware.votifier.util.QuietException;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.embedded.EmbeddedChannel;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
import io.netty.handler.codec.DecoderException;
|
||||
import org.junit.Test;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
public class VotifierProtocolDifferentiatorTest {
|
||||
@Test
|
||||
@@ -43,7 +46,7 @@ public class VotifierProtocolDifferentiatorTest {
|
||||
channel.close();
|
||||
}
|
||||
|
||||
@Test(expected = DecoderException.class)
|
||||
@Test
|
||||
public void failIfv1NotSupported() {
|
||||
EmbeddedChannel channel = new EmbeddedChannel(new VotifierProtocolDifferentiator(true, false));
|
||||
|
||||
@@ -54,13 +57,11 @@ public class VotifierProtocolDifferentiatorTest {
|
||||
for (int i = 0; i < 256; i++) {
|
||||
test.writeByte(0);
|
||||
}
|
||||
channel.writeInbound(test);
|
||||
|
||||
assertEquals(VotifierSession.ProtocolVersion.ONE, session.getVersion());
|
||||
assertThrows(DecoderException.class, () -> channel.writeInbound(test));
|
||||
channel.close();
|
||||
}
|
||||
|
||||
@Test(expected = DecoderException.class)
|
||||
@Test
|
||||
public void failOnBadPacketTest() {
|
||||
EmbeddedChannel channel = new EmbeddedChannel(new VotifierProtocolDifferentiator(true, false));
|
||||
|
||||
@@ -69,11 +70,8 @@ public class VotifierProtocolDifferentiatorTest {
|
||||
buf.writeByte(0);
|
||||
}
|
||||
|
||||
try {
|
||||
channel.writeInbound(buf);
|
||||
} finally {
|
||||
channel.close();
|
||||
}
|
||||
assertThrows(DecoderException.class, () -> channel.writeInbound(buf));
|
||||
channel.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+10
-10
@@ -1,26 +1,26 @@
|
||||
package com.vexsoftware.votifier.support.forwarding;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.support.forwarding.ForwardedVoteListener;
|
||||
import org.junit.Test;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public class PMForwardingSinkTest {
|
||||
|
||||
@Test
|
||||
public void testSuccessfulMultiDecode() throws Exception {
|
||||
List<Vote> receivedVotes = Lists.newArrayList();
|
||||
List<Vote> receivedVotes = new ArrayList<>();
|
||||
ForwardedVoteListener vl = receivedVotes::add;
|
||||
|
||||
List<Vote> sentVotes = Lists.newArrayList(
|
||||
List<Vote> sentVotes = new ArrayList<>(Arrays.asList(
|
||||
new Vote("serviceA", "usernameA", "1.1.1.1", "1546300800"),
|
||||
new Vote("serviceB", "usernameBBBBBBB", "1.2.23.4", "1514764800", System.currentTimeMillis())
|
||||
);
|
||||
new Vote("serviceB", "usernameBBBBBBB", "1.2.23.4", "1514764800")
|
||||
));
|
||||
|
||||
StringBuilder message = new StringBuilder();
|
||||
|
||||
@@ -28,7 +28,7 @@ public class PMForwardingSinkTest {
|
||||
message.append(v.serialize());
|
||||
}
|
||||
|
||||
byte[] messageBytes = message.toString().getBytes();
|
||||
byte[] messageBytes = message.toString().getBytes(StandardCharsets.UTF_8);
|
||||
System.out.println(message.toString());
|
||||
|
||||
AbstractPluginMessagingForwardingSink sink = new AbstractPluginMessagingForwardingSink(vl) {
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.vexsoftware.votifier.util;
|
||||
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
public class ArgsToVoteTest {
|
||||
@Test
|
||||
void handlesInvalidRequest() {
|
||||
assertThrows(IllegalArgumentException.class, () -> {
|
||||
ArgsToVote.parse(new String[0]);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void handlesSingleUsername() {
|
||||
Vote vote = ArgsToVote.parse(new String[] { "dummy_user" });
|
||||
assertEquals("dummy_user", vote.getUsername());
|
||||
}
|
||||
|
||||
@Test
|
||||
void handlesSingleUsernameTooLong() {
|
||||
assertThrows(IllegalArgumentException.class, () -> {
|
||||
ArgsToVote.parse(new String[] { "AVeryVeryVeryLongUsername" });
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void handleServiceNameGetter() {
|
||||
Vote vote = ArgsToVote.parse(new String[] { "username=abc123", "servicename=dummy" });
|
||||
assertEquals("abc123", vote.getUsername());
|
||||
assertEquals("dummy", vote.getServiceName());
|
||||
}
|
||||
@Test
|
||||
void handleAddressGetter() {
|
||||
Vote vote = ArgsToVote.parse(new String[] { "username=abc123", "address=dummy" });
|
||||
assertEquals("abc123", vote.getUsername());
|
||||
assertEquals("dummy", vote.getAddress());
|
||||
}
|
||||
@Test
|
||||
void handleTimestampGetter() {
|
||||
Vote vote = ArgsToVote.parse(new String[] { "username=abc123", "timestamp=dummy" });
|
||||
assertEquals("abc123", vote.getUsername());
|
||||
assertEquals("dummy", vote.getTimeStamp());
|
||||
}
|
||||
|
||||
@Test
|
||||
void handlesUsernameKeywordArgument() {
|
||||
Vote vote = ArgsToVote.parse(new String[] { "username=dummy_user" });
|
||||
assertEquals("dummy_user", vote.getUsername());
|
||||
}
|
||||
}
|
||||
+16
-6
@@ -1,22 +1,32 @@
|
||||
|
||||
.PHONY: clean, all, run
|
||||
.PHONY: clean all run
|
||||
|
||||
all : resources/paper.jar resources/proxy.jar
|
||||
all : resources/paper.jar resources/waterfall.jar resources/velocity.jar resources/sponge.jar resources/viaversion.jar
|
||||
|
||||
resources :
|
||||
mkdir resources
|
||||
|
||||
resources/paper.jar : resources
|
||||
wget -O resources/paper.jar https://papermc.io/ci/job/Paper-1.13/lastSuccessfulBuild/artifact/paperclip.jar
|
||||
wget -O resources/paper.jar https://papermc.io/api/v1/paper/1.16.2/182/download
|
||||
|
||||
resources/proxy.jar : resources
|
||||
wget -O resources/proxy.jar https://papermc.io/ci/job/Waterfall/lastSuccessfulBuild/artifact/Waterfall-Proxy/bootstrap/target/Waterfall.jar
|
||||
resources/waterfall.jar : resources
|
||||
wget -O resources/waterfall.jar https://papermc.io/ci/job/Waterfall/lastSuccessfulBuild/artifact/Waterfall-Proxy/bootstrap/target/Waterfall.jar
|
||||
|
||||
resources/velocity.jar : resources
|
||||
wget -O resources/velocity.jar https://ci.velocitypowered.com/job/velocity-1.1.0/lastSuccessfulBuild/artifact/proxy/build/libs/velocity-proxy-1.1.0-SNAPSHOT-all.jar
|
||||
|
||||
resources/sponge.jar : resources
|
||||
wget -O resources/sponge.jar https://repo.spongepowered.org/maven/org/spongepowered/spongevanilla/1.12.2-7.3.0/spongevanilla-1.12.2-7.3.0.jar
|
||||
|
||||
resources/viaversion.jar : resources
|
||||
wget -O resources/viaversion.jar https://ci.viaversion.com/job/ViaVersion/459/artifact/jar/target/ViaVersion-3.1.1-SNAPSHOT.jar
|
||||
|
||||
clean :
|
||||
rm -r resources/
|
||||
|
||||
run :
|
||||
tmux new-session './proxy/run.sh' \; \
|
||||
tmux new-session './p1/run.sh' \; \
|
||||
split-window -h './p2/run.sh' \; \
|
||||
split-window -h './s1/run.sh' \; \
|
||||
split-window -h './s2/run.sh' \; \
|
||||
select-layout even-horizontal
|
||||
+10
-7
@@ -1,7 +1,7 @@
|
||||
## Using this testbed
|
||||
|
||||
This testbed runs one Bungeecord as well as two Paper servers behind it, labeled
|
||||
s1 and s2. Ports for where everything is bound is listed below.
|
||||
This testbed runs two proxies, labeled p1 and p2, as well as two servers behind it,
|
||||
labeled s1 and s2. Ports for where everything is bound is listed below.
|
||||
|
||||
First, download required sources using the following command:
|
||||
|
||||
@@ -19,8 +19,11 @@ FYI, this only works in Linux afaik because thats what I do my development on.
|
||||
|
||||
# Ports
|
||||
|
||||
| Server | MC Port | Votifier Port |
|
||||
| ------ | ------- | ------------- |
|
||||
| proxy | 25577 | 8192 |
|
||||
| s1 | 25565 | 8193 |
|
||||
| s2 | 25566 | 8194 |
|
||||
| Server | Server Software | MC Port | Votifier Port |
|
||||
| ------ | ---------------------- | ------- | ------------- |
|
||||
| p1 | Waterfall/BungeeCord | 25577 | 8192 |
|
||||
| p2 | Velocity | 25578 | 8193 |
|
||||
| s1 | Paper (1.16.1) | 25565 | 8194 |
|
||||
| s2 | SpongeVanilla (1.12.2) | 25566 | 8195 |
|
||||
|
||||
For compatibility with 1.16.1, s2 includes ViaVersion.
|
||||
File renamed without changes.
@@ -1,7 +1,4 @@
|
||||
forge_support: true
|
||||
timeout: 30000
|
||||
online_mode: true
|
||||
log_commands: false
|
||||
server_connect_timeout: 5000
|
||||
listeners:
|
||||
- query_port: 25577
|
||||
motd: '&4its a bungee cord surbur'
|
||||
@@ -17,16 +14,8 @@ listeners:
|
||||
max_players: 1
|
||||
tab_size: 60
|
||||
force_default_server: false
|
||||
connection_throttle: 1
|
||||
groups:
|
||||
md_5:
|
||||
- admin
|
||||
log_pings: true
|
||||
ip_forward: true
|
||||
prevent_proxy_connections: true
|
||||
remote_ping_cache: -1
|
||||
network_compression_threshold: 256
|
||||
disabled_commands: []
|
||||
stats: bf8a54ea-2917-4b9e-b463-2c05bd7f1ac6
|
||||
permissions:
|
||||
default:
|
||||
- bungeecord.command.server
|
||||
@@ -36,6 +25,22 @@ permissions:
|
||||
- bungeecord.command.end
|
||||
- bungeecord.command.ip
|
||||
- bungeecord.command.reload
|
||||
log_pings: true
|
||||
connection_throttle_limit: 3
|
||||
prevent_proxy_connections: true
|
||||
timeout: 30000
|
||||
player_limit: -1
|
||||
ip_forward: true
|
||||
groups:
|
||||
md_5:
|
||||
- admin
|
||||
remote_ping_timeout: 5000
|
||||
connection_throttle: 1
|
||||
log_commands: false
|
||||
stats: bf8a54ea-2917-4b9e-b463-2c05bd7f1ac6
|
||||
online_mode: true
|
||||
forge_support: true
|
||||
disabled_commands: []
|
||||
servers:
|
||||
s1:
|
||||
motd: SERVER 1
|
||||
@@ -45,4 +50,3 @@ servers:
|
||||
motd: SERVER 2
|
||||
address: localhost:25566
|
||||
restricted: false
|
||||
player_limit: -1
|
||||
File renamed without changes.
@@ -0,0 +1,2 @@
|
||||
rsa/
|
||||
cached-votes.json
|
||||
@@ -7,12 +7,12 @@ 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
|
||||
debug: true
|
||||
|
||||
# All tokens, labeled by the serviceName of each server list.
|
||||
tokens:
|
||||
# Default token for all server lists, if another isn't supplied.
|
||||
default: jqpli3gnd8l67qsg2d81vje09h
|
||||
default: proxy1token
|
||||
|
||||
# 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.
|
||||
@@ -24,7 +24,7 @@ forwarding:
|
||||
# - 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
|
||||
method: pluginMessaging
|
||||
pluginMessaging:
|
||||
channel: nuvotifier:votes
|
||||
|
||||
@@ -46,11 +46,11 @@ forwarding:
|
||||
# most set ups.
|
||||
cache: file
|
||||
# Only sends the vote to the server in which the player is currently joined.
|
||||
onlySendToJoinedServer: false
|
||||
onlySendToJoinedServer: true
|
||||
#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'
|
||||
joinedServerFallback: 's1'
|
||||
# Defines how quickly to dump votes over a player's connection when offloading a cache in votes per second
|
||||
dumpRate: 5
|
||||
# Options for file caching.
|
||||
@@ -66,9 +66,9 @@ forwarding:
|
||||
proxy:
|
||||
Hub:
|
||||
address: 127.0.0.1
|
||||
port: 8193
|
||||
token: MyKeyHere
|
||||
port: 8194
|
||||
token: server1token
|
||||
Hub2:
|
||||
address: 127.0.0.1
|
||||
port: 8194
|
||||
token: AnotherKeyHere
|
||||
port: 8195
|
||||
token: server2token
|
||||
File renamed without changes.
Symlink
+1
@@ -0,0 +1 @@
|
||||
../resources/waterfall.jar
|
||||
File renamed without changes.
@@ -1,8 +1,9 @@
|
||||
use_netty_dns_resolver: true
|
||||
disable_modern_tab_limiter: true
|
||||
log_initial_handler_connections: true
|
||||
allow_empty_packets: false
|
||||
throttling:
|
||||
tab_complete: 1000
|
||||
game_version: ''
|
||||
disable_modern_tab_limiter: true
|
||||
use_netty_dns_resolver: true
|
||||
disable_entity_metadata_rewrite: false
|
||||
log_initial_handler_connections: true
|
||||
log_server_list_ping: false
|
||||
allow_empty_packets: false
|
||||
@@ -0,0 +1 @@
|
||||
logs/
|
||||
File renamed without changes.
@@ -0,0 +1,2 @@
|
||||
rsa/
|
||||
cached-votes.json
|
||||
@@ -0,0 +1,79 @@
|
||||
# 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 = "0.0.0.0"
|
||||
|
||||
# Port to listen for new votes on
|
||||
port = 8193
|
||||
|
||||
# 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 = "proxy2token"
|
||||
|
||||
# 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 = "pluginMessaging"
|
||||
|
||||
[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 and a player is on one of these servers, NuVotifier will process the vote as if the
|
||||
# player was not on the network at all.
|
||||
# excludedServers = ["server1", "server2"]
|
||||
|
||||
# If uncommented, turns excludedServers into an includedServers, where only servers on the list will be used.
|
||||
# whitelist = true
|
||||
|
||||
# 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.
|
||||
# - 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 = "s1"
|
||||
|
||||
# Defines how quickly to dump votes over a player's connection while offloading a cache in votes per second
|
||||
dumpRate = 5
|
||||
|
||||
[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. -1 signifies no cache.
|
||||
cacheTime = 10
|
||||
|
||||
[forwarding.memory-cache]
|
||||
# Options for file caching.
|
||||
name = "cached-votes.json"
|
||||
# days before a vote is considered 'dead' - removed from cache with a console warning. -1 signifies no cache. Votes are
|
||||
# also removed on restart
|
||||
cacheTime = 10
|
||||
|
||||
[forwarding.proxy]
|
||||
# Specify servers to proxy votes for.
|
||||
|
||||
[forwarding.proxy.Hub]
|
||||
address = "127.0.0.1"
|
||||
port = 8194
|
||||
token = "server1token"
|
||||
|
||||
[forwarding.proxy.Hub2]
|
||||
address = "127.0.0.1"
|
||||
port = 8195
|
||||
token = "server2token"
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../resources/velocity.jar
|
||||
Executable
+6
@@ -0,0 +1,6 @@
|
||||
#!/bin/sh
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
|
||||
cd $DIR
|
||||
|
||||
java -jar proxy.jar
|
||||
@@ -0,0 +1,135 @@
|
||||
# Config version. Do not change this
|
||||
config-version = "1.0"
|
||||
|
||||
# What port should the proxy be bound to? By default, we'll bind to all addresses on port 25577.
|
||||
bind = "0.0.0.0:25578"
|
||||
|
||||
# What should be the MOTD? This gets displayed when the player adds your server to
|
||||
# their server list. Legacy color codes and JSON are accepted.
|
||||
motd = "&3doot doot imma velocity serbur"
|
||||
|
||||
# What should we display for the maximum number of players? (Velocity does not support a cap
|
||||
# on the number of players online.)
|
||||
show-max-players = 500
|
||||
|
||||
# Should we authenticate players with Mojang? By default, this is on.
|
||||
online-mode = true
|
||||
|
||||
# If client's ISP/AS sent from this proxy is different from the one from Mojang's
|
||||
# authentication server, the player is kicked. This disallows some VPN and proxy
|
||||
# connections but is a weak form of protection.
|
||||
prevent-client-proxy-connections = false
|
||||
|
||||
# Should we forward IP addresses and other data to backend servers?
|
||||
# Available options:
|
||||
# - "none": No forwarding will be done. All players will appear to be connecting
|
||||
# from the proxy and will have offline-mode UUIDs.
|
||||
# - "legacy": Forward player IPs and UUIDs in a BungeeCord-compatible format. Use this
|
||||
# if you run servers using Minecraft 1.12 or lower.
|
||||
# - "bungeeguard": Forward player IPs and UUIDs in a format supported by the BungeeGuard
|
||||
# plugin. Use this if you run servers using Minecraft 1.12 or lower, and are
|
||||
# unable to implement network level firewalling (on a shared host).
|
||||
# - "modern": Forward player IPs and UUIDs as part of the login process using
|
||||
# Velocity's native forwarding. Only applicable for Minecraft 1.13 or higher.
|
||||
player-info-forwarding-mode = "LEGACY"
|
||||
|
||||
# If you are using modern or BungeeGuard IP forwarding, configure an unique secret here.
|
||||
forwarding-secret = "not at all secret lolololol"
|
||||
|
||||
# Announce whether or not your server supports Forge. If you run a modded server, we
|
||||
# suggest turning this on.
|
||||
#
|
||||
# If your network runs one modpack consistently, consider using ping-passthrough = "mods"
|
||||
# instead for a nicer display in the server list.
|
||||
announce-forge = false
|
||||
|
||||
# If enabled (default is false) and the proxy is in online mode, Velocity will kick
|
||||
# any existing player who is online if a duplicate connection attempt is made.
|
||||
kick-existing-players = false
|
||||
|
||||
# Should Velocity pass server list ping requests to a backend server?
|
||||
# Available options:
|
||||
# - "disabled": No pass-through will be done. The velocity.toml and server-icon.png
|
||||
# will determine the initial server list ping response.
|
||||
# - "mods": Passes only the mod list from your backend server into the response.
|
||||
# The first server in your try list (or forced host) with a mod list will be
|
||||
# used. If no backend servers can be contacted, Velocity won't display any
|
||||
# mod information.
|
||||
# - "description": Uses the description and mod list from the backend server. The first
|
||||
# server in the try (or forced host) list that responds is used for the
|
||||
# description and mod list.
|
||||
# - "all": Uses the backend server's response as the proxy response. The Velocity
|
||||
# configuration is used if no servers could be contacted.
|
||||
ping-passthrough = "DISABLED"
|
||||
|
||||
[servers]
|
||||
# Configure your servers here. Each key represents the server's name, and the value
|
||||
# represents the IP address of the server to connect to.
|
||||
s1 = "127.0.0.1:25565"
|
||||
s2 = "127.0.0.1:25566"
|
||||
|
||||
# In what order we should try servers when a player logs in or is kicked from aserver.
|
||||
try = [
|
||||
"s1"
|
||||
]
|
||||
|
||||
[forced-hosts]
|
||||
# Configure your forced hosts here.
|
||||
|
||||
[advanced]
|
||||
# How large a Minecraft packet has to be before we compress it. Setting this to zero will
|
||||
# compress all packets, and setting it to -1 will disable compression entirely.
|
||||
compression-threshold = 256
|
||||
|
||||
# How much compression should be done (from 0-9). The default is -1, which uses the
|
||||
# default level of 6.
|
||||
compression-level = -1
|
||||
|
||||
# How fast (in milliseconds) are clients allowed to connect after the last connection? By
|
||||
# default, this is three seconds. Disable this by setting this to 0.
|
||||
login-ratelimit = 3000
|
||||
|
||||
# Specify a custom timeout for connection timeouts here. The default is five seconds.
|
||||
connection-timeout = 5000
|
||||
|
||||
# Specify a read timeout for connections here. The default is 30 seconds.
|
||||
read-timeout = 30000
|
||||
|
||||
# Enables compatibility with HAProxy.
|
||||
proxy-protocol = false
|
||||
|
||||
# Enables TCP fast open support on the proxy. Requires the proxy to run on Linux.
|
||||
tcp-fast-open = false
|
||||
|
||||
# Enables BungeeCord plugin messaging channel support on Velocity.
|
||||
bungee-plugin-message-channel = true
|
||||
|
||||
# Shows ping requests to the proxy from clients.
|
||||
show-ping-requests = false
|
||||
|
||||
[query]
|
||||
# Whether to enable responding to GameSpy 4 query responses or not.
|
||||
enabled = false
|
||||
|
||||
# If query is enabled, on what port should the query protocol listen on?
|
||||
port = 25577
|
||||
|
||||
# This is the map name that is reported to the query services.
|
||||
map = "Velocity"
|
||||
|
||||
# Whether plugins should be shown in query response by default or not
|
||||
show-plugins = false
|
||||
|
||||
[metrics]
|
||||
# Whether metrics will be reported to bStats (https://bstats.org).
|
||||
# bStats collects some basic information, like how many people use Velocity and their
|
||||
# player count. We recommend keeping bStats enabled, but if you're not comfortable with
|
||||
# this, you can turn this setting off. There is no performance penalty associated with
|
||||
# having metrics enabled, and data sent to bStats can't identify your server.
|
||||
enabled = true
|
||||
|
||||
# A unique, anonymous ID to identify this proxy with.
|
||||
id = "729fe923-b383-4e5b-b456-5adf554bfe69"
|
||||
|
||||
log-failure = false
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
rsa/
|
||||
@@ -1 +0,0 @@
|
||||
../resources/proxy.jar
|
||||
+1
-1
@@ -7,4 +7,4 @@ well as overriding the eula check. If you don't agree to the Mojang EULA
|
||||
thingie, then uh, smash your computer or something because you totally agreed
|
||||
to it so just fyi
|
||||
WARNING
|
||||
java -DIReallyKnowWhatIAmDoingISwear -Dcom.mojang.eula.agree=true -jar paper.jar
|
||||
java -DIReallyKnowWhatIAmDoingISwear -Dcom.mojang.eula.agree=true -jar paper.jar nogui
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
|
||||
settings:
|
||||
minimum-api: none
|
||||
allow-end: true
|
||||
warn-on-overload: true
|
||||
permissions-file: permissions.yml
|
||||
@@ -23,6 +24,7 @@ settings:
|
||||
deprecated-verbose: default
|
||||
shutdown-message: Server closed
|
||||
spawn-limits:
|
||||
water-ambient: 20
|
||||
monsters: 70
|
||||
animals: 10
|
||||
water-animals: 15
|
||||
@@ -31,6 +33,9 @@ chunk-gc:
|
||||
period-in-ticks: 600
|
||||
load-threshold: 0
|
||||
ticks-per:
|
||||
water-ambient-spawns: 1
|
||||
water-spawns: 1
|
||||
ambient-spawns: 1
|
||||
animal-spawns: 400
|
||||
monster-spawns: 1
|
||||
autosave: 6000
|
||||
|
||||
+52
-6
@@ -11,8 +11,11 @@
|
||||
# Docs: https://paper.readthedocs.org/
|
||||
|
||||
verbose: false
|
||||
config-version: 16
|
||||
config-version: 20
|
||||
settings:
|
||||
max-joins-per-tick: 3
|
||||
console-has-all-permissions: false
|
||||
chunk-tasks-per-tick: 1000
|
||||
load-permissions-yml-before-plugins: true
|
||||
bungee-online-mode: true
|
||||
use-versioned-world: false
|
||||
@@ -41,10 +44,20 @@ settings:
|
||||
max-player-auto-save-per-tick: -1
|
||||
async-chunks:
|
||||
enable: true
|
||||
generation: true
|
||||
thread-per-world-generation: true
|
||||
load-threads: -1
|
||||
threads: -1
|
||||
use-alternative-luck-formula: false
|
||||
unsupported-settings:
|
||||
allow-headless-pistons: false
|
||||
allow-permanent-block-break-exploits: false
|
||||
allow-piston-duplication: false
|
||||
allow-permanent-block-break-exploits-readme: This setting controls if players
|
||||
should be able to break bedrock, end portals and other intended to be permanent
|
||||
blocks.
|
||||
allow-piston-duplication-readme: This setting controls if player should be able
|
||||
to use TNT duplication, but this also allows duplicating carpet, rails and potentially
|
||||
other items
|
||||
allow-headless-pistons-readme: This setting controls if players should be able
|
||||
to create headless pistons.
|
||||
messages:
|
||||
no-permission: '&cI''m sorry, but you do not have permission to perform this command.
|
||||
Please contact the server administrators if you believe that this is in error.'
|
||||
@@ -54,6 +67,7 @@ messages:
|
||||
flying-vehicle: Flying is not enabled on this server
|
||||
connection-throttle: Connection throttled! Please wait before reconnecting.
|
||||
timings:
|
||||
server-name: Unknown Server
|
||||
enabled: true
|
||||
verbose: true
|
||||
server-name-privacy: false
|
||||
@@ -64,8 +78,24 @@ timings:
|
||||
history-length: 3600
|
||||
world-settings:
|
||||
default:
|
||||
zombies-target-turtle-eggs: true
|
||||
per-player-mob-spawns: false
|
||||
phantoms-do-not-spawn-on-creative-players: true
|
||||
phantoms-only-attack-insomniacs: true
|
||||
light-queue-size: 20
|
||||
count-all-mobs-for-spawning: false
|
||||
seed-based-feature-search: true
|
||||
nether-ceiling-void-damage-height: 0
|
||||
fix-zero-tick-instant-grow-farms: true
|
||||
portal-create-radius: 16
|
||||
iron-golems-can-spawn-in-air: false
|
||||
entities-target-with-follow-range: false
|
||||
zombie-villager-infection-chance: -1.0
|
||||
portal-search-radius: 128
|
||||
game-mechanics:
|
||||
disable-pillager-patrols: false
|
||||
nerf-pigmen-from-nether-portals: false
|
||||
disable-relative-projectile-velocity: false
|
||||
villages-load-chunks: false
|
||||
scan-for-legacy-ender-dragon: true
|
||||
disable-chest-cat-detection: false
|
||||
@@ -74,6 +104,14 @@ world-settings:
|
||||
disable-sprint-interruption-on-attack: false
|
||||
shield-blocking-delay: 5
|
||||
disable-unloaded-chunk-enderpearl-exploit: true
|
||||
pillager-patrols:
|
||||
spawn-chance: 0.2
|
||||
spawn-delay:
|
||||
per-player: false
|
||||
ticks: 12000
|
||||
start:
|
||||
per-player: false
|
||||
day: 5
|
||||
queue-light-updates: false
|
||||
optimize-explosions: false
|
||||
disable-teleportation-suffocation-check: false
|
||||
@@ -141,13 +179,12 @@ world-settings:
|
||||
- chest
|
||||
- diamond_ore
|
||||
- redstone_ore
|
||||
- lit_redstone_ore
|
||||
- clay
|
||||
- emerald_ore
|
||||
- ender_chest
|
||||
replacement-blocks:
|
||||
- stone
|
||||
- planks
|
||||
- oak_planks
|
||||
skip-entity-ticking-in-chunks-scheduled-for-unload: true
|
||||
armor-stands-do-collision-entity-lookups: true
|
||||
keep-spawn-loaded-range: 8
|
||||
@@ -176,3 +213,12 @@ world-settings:
|
||||
mob-spawner-tick-rate: 1
|
||||
squid-spawn-height:
|
||||
maximum: 0.0
|
||||
alt-item-despawn-rate:
|
||||
enabled: false
|
||||
items:
|
||||
COBBLESTONE: 300
|
||||
viewdistances:
|
||||
no-tick-view-distance: -1
|
||||
generator-settings:
|
||||
flat-bedrock: false
|
||||
baby-zombie-movement-modifier: 0.5
|
||||
@@ -3,7 +3,7 @@
|
||||
host: 0.0.0.0
|
||||
|
||||
# Port to listen for new votes on
|
||||
port: 8193
|
||||
port: 8194
|
||||
|
||||
# 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.
|
||||
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../../../universal/build/libs/nuvotifier.jar
|
||||
@@ -1,26 +1,36 @@
|
||||
#Minecraft server properties
|
||||
#Fri Dec 21 15:15:00 EST 2018
|
||||
#Thu Sep 03 15:49:51 EDT 2020
|
||||
spawn-protection=16
|
||||
max-tick-time=60000
|
||||
query.port=25565
|
||||
generator-settings=
|
||||
sync-chunk-writes=true
|
||||
force-gamemode=false
|
||||
allow-nether=true
|
||||
enforce-whitelist=false
|
||||
gamemode=0
|
||||
gamemode=survival
|
||||
broadcast-console-to-ops=true
|
||||
enable-query=false
|
||||
player-idle-timeout=0
|
||||
difficulty=1
|
||||
difficulty=easy
|
||||
spawn-monsters=true
|
||||
broadcast-rcon-to-ops=true
|
||||
op-permission-level=4
|
||||
pvp=true
|
||||
entity-broadcast-range-percentage=100
|
||||
snooper-enabled=true
|
||||
level-type=DEFAULT
|
||||
level-type=default
|
||||
hardcore=false
|
||||
enable-status=true
|
||||
enable-command-block=false
|
||||
max-players=20
|
||||
network-compression-threshold=256
|
||||
resource-pack-sha1=
|
||||
max-world-size=29999984
|
||||
function-permission-level=2
|
||||
rcon.port=25575
|
||||
server-port=25565
|
||||
debug=false
|
||||
server-ip=
|
||||
spawn-npcs=true
|
||||
allow-flight=false
|
||||
@@ -29,11 +39,14 @@ view-distance=10
|
||||
resource-pack=
|
||||
spawn-animals=true
|
||||
white-list=false
|
||||
rcon.password=
|
||||
generate-structures=true
|
||||
online-mode=false
|
||||
max-build-height=256
|
||||
online-mode=false
|
||||
level-seed=
|
||||
prevent-proxy-connections=false
|
||||
use-native-transport=true
|
||||
motd=A Minecraft Server
|
||||
enable-jmx-monitoring=false
|
||||
rate-limit=0
|
||||
enable-rcon=false
|
||||
motd=A Minecraft Server
|
||||
+37
-2
@@ -10,8 +10,9 @@
|
||||
# IRC: #spigot @ irc.spi.gt ( http://www.spigotmc.org/pages/irc/ )
|
||||
# Forums: http://www.spigotmc.org/
|
||||
|
||||
config-version: 11
|
||||
config-version: 12
|
||||
settings:
|
||||
log-villager-deaths: true
|
||||
debug: false
|
||||
save-user-cache-on-stop-only: false
|
||||
sample-count: 12
|
||||
@@ -60,6 +61,16 @@ advancements:
|
||||
- minecraft:story/disabled
|
||||
world-settings:
|
||||
default:
|
||||
seed-bastion: 30084232
|
||||
seed-fortress: 30084232
|
||||
seed-mansion: 10387319
|
||||
seed-fossil: 14357921
|
||||
seed-portal: 34222645
|
||||
seed-outpost: 165745296
|
||||
seed-endcity: 10387313
|
||||
view-distance: default
|
||||
trident-despawn-rate: 1200
|
||||
end-portal-sound-radius: 0
|
||||
verbose: true
|
||||
merge-radius:
|
||||
exp: 3.0
|
||||
@@ -91,6 +102,12 @@ world-settings:
|
||||
min: 45.0
|
||||
mob-spawn-range: 8
|
||||
growth:
|
||||
beetroot-modifier: 100
|
||||
carrot-modifier: 100
|
||||
potato-modifier: 100
|
||||
bamboo-modifier: 100
|
||||
sweetberry-modifier: 100
|
||||
kelp-modifier: 100
|
||||
cactus-modifier: 100
|
||||
cane-modifier: 100
|
||||
melon-modifier: 100
|
||||
@@ -102,11 +119,30 @@ world-settings:
|
||||
vine-modifier: 100
|
||||
cocoa-modifier: 100
|
||||
entity-activation-range:
|
||||
raiders: 48
|
||||
villagers: 32
|
||||
flying-monsters: 32
|
||||
villagers-work-immunity-after: 100
|
||||
villagers-work-immunity-for: 20
|
||||
villagers-active-for-panic: true
|
||||
animals: 32
|
||||
monsters: 32
|
||||
misc: 16
|
||||
water: 16
|
||||
tick-inactive-villagers: true
|
||||
wake-up-inactive:
|
||||
animals-max-per-tick: 4
|
||||
animals-every: 1200
|
||||
animals-for: 100
|
||||
monsters-max-per-tick: 8
|
||||
monsters-every: 400
|
||||
monsters-for: 100
|
||||
villagers-max-per-tick: 4
|
||||
villagers-every: 600
|
||||
villagers-for: 100
|
||||
flying-monsters-max-per-tick: 8
|
||||
flying-monsters-every: 200
|
||||
flying-monsters-for: 100
|
||||
entity-tracking-range:
|
||||
players: 48
|
||||
animals: 48
|
||||
@@ -120,7 +156,6 @@ world-settings:
|
||||
wither-spawn-sound-radius: 0
|
||||
enable-zombie-pigmen-portal-spawns: true
|
||||
item-despawn-rate: 6000
|
||||
view-distance: 10
|
||||
arrow-despawn-rate: 1200
|
||||
hanging-tick-frequency: 100
|
||||
zombie-aggressive-towards-villager: true
|
||||
|
||||
+2
-4
@@ -3,17 +3,15 @@ banned-players.json
|
||||
|
||||
cache/
|
||||
|
||||
eula.txt
|
||||
|
||||
logs/
|
||||
|
||||
permissions.yml
|
||||
|
||||
ops.json
|
||||
usercache.json
|
||||
usernamecache.json
|
||||
version_history.json
|
||||
whitelist.json
|
||||
|
||||
world/
|
||||
world_nether/
|
||||
world_the_end/
|
||||
.mixin.out
|
||||
@@ -1,37 +0,0 @@
|
||||
# This is the main configuration file for Bukkit.
|
||||
# As you can see, there's actually not that much to configure without any plugins.
|
||||
# For a reference for any variable inside this file, check out the Bukkit Wiki at
|
||||
# https://www.spigotmc.org/go/bukkit-yml
|
||||
#
|
||||
# If you need help on this file, feel free to join us on irc or leave a message
|
||||
# on the forums asking for advice.
|
||||
#
|
||||
# IRC: #spigot @ irc.spi.gt
|
||||
# (If this means nothing to you, just go to https://www.spigotmc.org/go/irc )
|
||||
# Forums: https://www.spigotmc.org/
|
||||
# Bug tracker: https://www.spigotmc.org/go/bugs
|
||||
|
||||
|
||||
settings:
|
||||
allow-end: true
|
||||
warn-on-overload: true
|
||||
permissions-file: permissions.yml
|
||||
update-folder: update
|
||||
plugin-profiling: false
|
||||
connection-throttle: 4000
|
||||
query-plugins: true
|
||||
deprecated-verbose: default
|
||||
shutdown-message: Server closed
|
||||
spawn-limits:
|
||||
monsters: 70
|
||||
animals: 10
|
||||
water-animals: 15
|
||||
ambient: 15
|
||||
chunk-gc:
|
||||
period-in-ticks: 600
|
||||
load-threshold: 0
|
||||
ticks-per:
|
||||
animal-spawns: 400
|
||||
monster-spawns: 1
|
||||
autosave: 6000
|
||||
aliases: now-in-commands.yml
|
||||
@@ -1,17 +0,0 @@
|
||||
# This is the commands configuration file for Bukkit.
|
||||
# For documentation on how to make use of this file, check out the Bukkit Wiki at
|
||||
# https://www.spigotmc.org/go/commands-yml
|
||||
#
|
||||
# If you need help on this file, feel free to join us on irc or leave a message
|
||||
# on the forums asking for advice.
|
||||
#
|
||||
# IRC: #spigot @ irc.spi.gt
|
||||
# (If this means nothing to you, just go to https://www.spigotmc.org/go/irc )
|
||||
# Forums: https://www.spigotmc.org/
|
||||
# Bug tracker: https://www.spigotmc.org/go/bugs
|
||||
|
||||
command-block-overrides: []
|
||||
ignore-vanilla-permissions: false
|
||||
aliases:
|
||||
icanhasbukkit:
|
||||
- version $1-
|
||||
@@ -0,0 +1 @@
|
||||
rsa/
|
||||
@@ -0,0 +1,8 @@
|
||||
host: 127.0.0.1
|
||||
port: 8195
|
||||
debug: true
|
||||
disable-v1-protocol: false
|
||||
tokens: {default: server2token}
|
||||
forwarding:
|
||||
method: pluginMessaging
|
||||
pluginMessaging: {channel: 'nuvotifier:votes'}
|
||||
Loaded 100 of 163 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user