Version 2.0 (Initial commit)

This commit is contained in:
Jaime Martinez Rincon 2017-01-08 22:23:19 +01:00
commit c8399b12e0
46 changed files with 3679 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
target/
*.iml
*.jar
.idea/
dependency-reduced-pom.xml

2
README.md Normal file
View File

@ -0,0 +1,2 @@
# LobbyBalancer
[Spigot](https://www.spigotmc.org/resources/10788/)

103
pom.xml Normal file
View File

@ -0,0 +1,103 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>me.jaimemartz</groupId>
<artifactId>lobbybalancer</artifactId>
<version>2.0</version>
<name>LobbyBalancer</name>
<repositories>
<repository>
<id>bungee-repo</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
</repository>
<repository>
<id>md_5-snapshots</id>
<url>http://repo.md-5.net/content/repositories/snapshots/</url>
</repository>
<repository>
<id>inventive-repo</id>
<url>http://repo.inventivetalent.org/content/groups/public/</url>
</repository>
</repositories>
<build>
<defaultGoal>clean install</defaultGoal>
<plugins>
<plugin>
<version>3.1</version>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<minimizeJar>true</minimizeJar>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>
<dependencies>
<dependency>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-api</artifactId>
<version>1.9-SNAPSHOT</version>
<type>jar</type>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.imaginarycode.minecraft</groupId>
<artifactId>RedisBungee</artifactId>
<version>0.3.8-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.inventivetalent.update</groupId>
<artifactId>bungee</artifactId>
<version>1.0.1</version>
</dependency>
<dependency>
<groupId>me.jaimemartz</groupId>
<artifactId>faucet-bungee</artifactId>
<version>1.1</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>com.maxmind.geoip2</groupId>
<artifactId>geoip2</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,226 @@
package me.jaimemartz.lobbybalancer;
import com.imaginarycode.minecraft.redisbungee.RedisBungee;
import me.jaimemartz.faucet.ConfigFactory;
import me.jaimemartz.lobbybalancer.commands.BackwardCommand;
import me.jaimemartz.lobbybalancer.commands.MainCommand;
import me.jaimemartz.lobbybalancer.configuration.ConfigEntries;
import me.jaimemartz.lobbybalancer.connection.ServerAssignRegistry;
import me.jaimemartz.lobbybalancer.listener.*;
import me.jaimemartz.lobbybalancer.ping.PingManager;
import me.jaimemartz.lobbybalancer.section.SectionManager;
import me.jaimemartz.lobbybalancer.utils.AdapterFix;
import me.jaimemartz.lobbybalancer.utils.GeolocationManager;
import me.jaimemartz.lobbybalancer.utils.PlayerLocker;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.plugin.Command;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.config.Configuration;
import org.inventivetalent.update.bungee.BungeeUpdater;
import java.io.IOException;
public class LobbyBalancer extends Plugin {
public static final String USER_ID = "%%__USER__%%";
public static final String RESOURCE_ID = "%%__RESOURCE__%%";
public static final String NONCE_ID = "%%__NONCE__%%";
private boolean failed = false;
private ConfigFactory factory;
private PingManager pingManager;
private SectionManager sectionManager;
private Command backwardCommand, mainCommand;
private GeolocationManager geolocationManager;
private Listener connectListener, kickListener, messageListener, reloadListener;
@Override
public void onEnable() {
instance = this;
if (factory == null) {
factory = new ConfigFactory(this);
factory.register(0, "config.yml");
factory.submit(ConfigEntries.class);
}
enable();
}
private void enable() {
factory.load(0, true);
mainCommand = new MainCommand(this);
getProxy().getPluginManager().registerCommand(this, mainCommand);
if (ConfigEntries.AUTO_RELOAD_ENABLED.get()) {
reloadListener = new ProxyReloadListener(this);
getProxy().getPluginManager().registerListener(this, reloadListener);
}
if (ConfigEntries.PLUGIN_ENABLED.get()) {
if (ConfigEntries.CHECK_UPDATES_ENABLED.get()) {
try {
new BungeeUpdater(this, 10788);
} catch (IOException e) {
e.printStackTrace();
}
}
sectionManager = new SectionManager(this);
try {
sectionManager.load();
if (ConfigEntries.SERVER_CHECK_ENABLED.get()) {
pingManager = new PingManager(this);
pingManager.start();
}
if (ConfigEntries.BACKWARD_COMMAND_ENABLED.get()) {
backwardCommand = new BackwardCommand(this);
getProxy().getPluginManager().registerCommand(this, backwardCommand);
}
connectListener = new ServerConnectListener(this);
getProxy().getPluginManager().registerListener(this, connectListener);
messageListener = new PluginMessageListener(this);
getProxy().getPluginManager().registerListener(this, messageListener);
getProxy().getPluginManager().registerListener(this, new PlayerDisconnectListener(this));
getProxy().registerChannel("LobbyBalancer");
if (ConfigEntries.RECONNECT_KICK_ENABLED.get()) {
kickListener = new ServerKickListener(this);
getProxy().getPluginManager().registerListener(this, kickListener);
}
if (ConfigEntries.GEOLOCATION_ENABLED.get()) {
LobbyBalancer.printStartupInfo("The geolocation feature has not been tested in depth");
try {
geolocationManager = new GeolocationManager(this);
} catch (IOException e) {
e.printStackTrace();
}
}
getLogger().info("The plugin has finished loading without any problems");
} catch (RuntimeException e) {
failed = true;
getLogger().severe("The plugin could not continue loading due to an unexpected exception");
e.printStackTrace();
}
} else {
getLogger().warning("The plugin is disabled, so nothing will work except the main command");
}
}
@Override
public void onDisable() {
disable();
}
private void disable() {
getProxy().getPluginManager().unregisterCommand(mainCommand);
mainCommand = null;
if (ConfigEntries.AUTO_RELOAD_ENABLED.get()) {
getProxy().getPluginManager().unregisterListener(reloadListener);
reloadListener = null;
}
if (ConfigEntries.PLUGIN_ENABLED.get()) {
//Do not try to do anything if the plugin has not loaded correctly
if (hasFailed()) return;
if (ConfigEntries.BACKWARD_COMMAND_ENABLED.get()) {
getProxy().getPluginManager().unregisterCommand(backwardCommand);
backwardCommand = null;
}
getProxy().getPluginManager().unregisterListener(connectListener);
connectListener = null;
getProxy().getPluginManager().unregisterListener(messageListener);
messageListener = null;
if (ConfigEntries.RECONNECT_KICK_ENABLED.get()) {
getProxy().getPluginManager().unregisterListener(kickListener);
kickListener = null;
}
sectionManager.flush();
AdapterFix.getFakeServers().clear();
if (ConfigEntries.ASSIGN_TARGETS_ENABLED.get()) {
ServerAssignRegistry.getTable().clear();
}
}
PlayerLocker.flush();
failed = false;
}
public void reloadPlugin() {
printStartupInfo("Reloading the plugin...");
long starting = System.currentTimeMillis();
disable();
enable();
long ending = System.currentTimeMillis() - starting;
printStartupInfo("The plugin has been reloaded, took %sms", ending);
}
public static int getPlayerCount(ServerInfo server) {
if (ConfigEntries.REDIS_BUNGEE_ENABLED.get()) {
try {
RedisBungee.getApi().getPlayersOnServer(server.getName()).size();
} catch (Exception e) {
e.printStackTrace();
}
}
return server.getPlayers().size();
}
public static void checkSendMessage(CommandSender sender, String message) {
if (message != null) {
sender.sendMessage(TextComponent.fromLegacyText(message));
}
}
public static boolean printStartupInfo(String format, Object... args) {
if (ConfigEntries.SILENT_STARTUP.get()) {
return false;
}
instance.getLogger().info(String.format(format, args));
return true;
}
public GeolocationManager getGeolocationManager() {
return geolocationManager;
}
public PingManager getPingManager() {
return pingManager;
}
public SectionManager getSectionManager() {
return sectionManager;
}
public boolean hasFailed() {
return failed;
}
public Configuration getConfig() {
return factory.get(0).getHandle();
}
private static LobbyBalancer instance;
public static LobbyBalancer getInstance() {
return instance;
}
}

View File

@ -0,0 +1,80 @@
package me.jaimemartz.lobbybalancer.commands;
import me.jaimemartz.faucet.Messager;
import me.jaimemartz.lobbybalancer.LobbyBalancer;
import me.jaimemartz.lobbybalancer.configuration.ConfigEntries;
import me.jaimemartz.lobbybalancer.connection.ConnectionIntent;
import me.jaimemartz.lobbybalancer.section.ServerSection;
import me.jaimemartz.lobbybalancer.utils.PlayerLocker;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender;
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.config.Configuration;
public class BackwardCommand extends Command {
private final LobbyBalancer plugin;
public BackwardCommand(LobbyBalancer plugin) {
super(ConfigEntries.BACKWARD_COMMAND_NAME.get(), ConfigEntries.BACKWARD_COMMAND_PERMISSION.get(), (ConfigEntries.BACKWARD_COMMAND_ALIASES.get().stream()).toArray(String[]::new));
this.plugin = plugin;
}
@Override
public void execute(CommandSender sender, String[] args) {
Messager msgr = new Messager(sender);
if (sender instanceof ProxiedPlayer) {
ProxiedPlayer player = (ProxiedPlayer) sender;
ServerSection section = plugin.getSectionManager().getByServer(player.getServer().getInfo());
if (section != null) {
if ((ConfigEntries.BACKWARD_COMMAND_IGNORED_SECTIONS.get()).contains(section.getName())) {
msgr.send(ConfigEntries.UNAVAILABLE_MESSAGE.get());
}
PlayerLocker.lock(player);
if (ConfigEntries.BACKWARD_COMMAND_ARGUMENTS.get() && args.length == 1) {
ServerSection target = plugin.getSectionManager().getByName(args[0]);
if (target == null) {
msgr.send(ConfigEntries.UNKNOWN_SECTION_MESSAGE.get());
}
new ConnectionIntent(plugin, player, target) {
@Override
public void connect(ServerInfo server) {
player.connect(server);
PlayerLocker.unlock(player);
}
};
} else {
Configuration rules = plugin.getConfig().getSection("settings.backward-command.rules");
String name = rules.getString(section.getName());
ServerSection target = plugin.getSectionManager().getByName(name);
if (target == null) {
target = section.getParent();
}
if (target == null) {
msgr.send(ConfigEntries.UNAVAILABLE_MESSAGE.get());
}
new ConnectionIntent(plugin, player, target) {
@Override
public void connect(ServerInfo server) {
player.connect(server);
PlayerLocker.unlock(player);
}
};
}
} else {
msgr.send(ConfigEntries.UNAVAILABLE_MESSAGE.get());
}
} else {
msgr.send(ChatColor.RED + "This command can only be executed by a player");
}
}
}

View File

@ -0,0 +1,59 @@
package me.jaimemartz.lobbybalancer.commands;
import me.jaimemartz.faucet.Messager;
import me.jaimemartz.lobbybalancer.LobbyBalancer;
import me.jaimemartz.lobbybalancer.utils.PasteHelper;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.plugin.Command;
public class MainCommand extends Command {
private final LobbyBalancer plugin;
public MainCommand(LobbyBalancer plugin) {
super("balancer");
this.plugin = plugin;
}
@Override
public void execute(final CommandSender sender, String[] args) {
Messager msgr = new Messager(sender);
if (args.length == 0) {
msgr.send(
"&e=====================================================",
"&7Information: LobbyBalancer version " + plugin.getDescription().getVersion(),
"&7Available commands:",
"&3/balancer &7- &cShows this message",
"&3/balancer paste &7- &cCreates a paste with the important files",
"&3/balancer reload &7- &cReloads the plugin completely",
"&e====================================================="
);
} else {
switch (args[0]) {
case "paste": {
if (sender.hasPermission("lobbybalancer.admin")) {
PasteHelper.LOGS.send(plugin, sender, "Last log file paste link: {link}");
PasteHelper.PLUGIN.send(plugin, sender, "Plugin config paste link: {link}");
PasteHelper.BUNGEE.send(plugin, sender, "Bungee config paste link (sensitive): {link}");
} else {
msgr.send(ChatColor.RED + "You do not have permission to execute this command!");
}
break;
}
case "reload": {
if (sender.hasPermission("lobbybalancer.admin")) {
msgr.send(ChatColor.GREEN + "Reloading the configuration, this may take a while...");
plugin.reloadPlugin();
msgr.send(ChatColor.GREEN + "The configuration has been reloaded");
} else {
msgr.send(ChatColor.RED + "You do not have permission to execute this command!");
}
break;
}
default: {
msgr.send(ChatColor.RED + "This is not a valid argument for this command!");
}
}
}
}
}

View File

@ -0,0 +1,47 @@
package me.jaimemartz.lobbybalancer.configuration;
import me.jaimemartz.faucet.ConfigEntry;
import me.jaimemartz.faucet.ConfigEntryHolder;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class ConfigEntries implements ConfigEntryHolder {
public static final ConfigEntry<String> CONFIG_VERSION = new ConfigEntry<>(0, "version", null);
public static final ConfigEntry<Boolean> PLUGIN_ENABLED = new ConfigEntry<>(0, "settings.enabled", false);
public static final ConfigEntry<Boolean> SILENT_STARTUP = new ConfigEntry<>(0, "settings.silent-startup", false);
public static final ConfigEntry<Boolean> CHECK_UPDATES_ENABLED = new ConfigEntry<>(0, "settings.check-updates", true);
public static final ConfigEntry<Boolean> SERVER_CHECK_ENABLED = new ConfigEntry<>(0, "settings.server_check.enabled", true);
public static final ConfigEntry<String> SERVER_CHECK_MODE = new ConfigEntry<>(0, "settings.server_check.tactic", "CUSTOM");
public static final ConfigEntry<Integer> SERVER_CHECK_ATTEMPTS = new ConfigEntry<>(0, "settings.server_check.attempts", 5);
public static final ConfigEntry<Integer> SERVER_CHECK_INTERVAL = new ConfigEntry<>(0, "settings.server_check.interval", 10000);
public static final ConfigEntry<Boolean> SERVER_CHECK_PRINT_INFO = new ConfigEntry<>(0, "settings.server_check.print-info", false);
public static final ConfigEntry<List<String>> SERVER_CHECK_MARKER_MOTDS = new ConfigEntry<>(0, "settings.server_check.marker-motds", Arrays.asList("Server is not accessible", "Gamemode has already started"));
public static final ConfigEntry<Boolean> GEOLOCATION_ENABLED = new ConfigEntry<>(0, "settings.geolocation.enabled", true);
public static final ConfigEntry<Boolean> RECONNECT_KICK_ENABLED = new ConfigEntry<>(0, "settings.reconnect-kick.enabled", true);
public static final ConfigEntry<Boolean> RECONNECT_KICK_INVERTED = new ConfigEntry<>(0, "settings.reconnect-kick.inverted", false);
public static final ConfigEntry<List<String>> RECONNECT_KICK_REASONS = new ConfigEntry<>(0, "settings.reconnect-kick.reasons", Collections.emptyList());
public static final ConfigEntry<Boolean> RECONNECT_KICK_PRINT_INFO = new ConfigEntry<>(0, "settings.reconnect-kick.print-info", false);
public static final ConfigEntry<List<String>> RECONNECT_KICK_IGNORED_SECTIONS = new ConfigEntry<>(0, "settings.reconnect-kick.ignored", Collections.emptyList());
public static final ConfigEntry<String> RECONNECT_KICK_MESSAGE = new ConfigEntry<>(0, "settings.reconnect-kick.message", "&cYou have been kicked from &a{from} &cand you are being moved to &a{to}&c, reason: &a{reason}");
public static final ConfigEntry<Boolean> BACKWARD_COMMAND_ENABLED = new ConfigEntry<>(0, "settings.backward-command.enabled", true);
public static final ConfigEntry<String> BACKWARD_COMMAND_NAME = new ConfigEntry<>(0, "settings.backward-command.name", "backward");
public static final ConfigEntry<List<String>> BACKWARD_COMMAND_ALIASES = new ConfigEntry<>(0, "settings.backward-command.aliases", Arrays.asList("lobby", "hub", "back"));
public static final ConfigEntry<String> BACKWARD_COMMAND_PERMISSION = new ConfigEntry<>(0, "settings.backward-command.permission", "");
public static final ConfigEntry<List<String>> BACKWARD_COMMAND_IGNORED_SECTIONS = new ConfigEntry<>(0, "settings.backward-command.ignored", Collections.emptyList());
public static final ConfigEntry<Boolean> BACKWARD_COMMAND_ARGUMENTS = new ConfigEntry<>(0, "settings.backward-command.arguments", true);
public static final ConfigEntry<Boolean> AUTO_RELOAD_ENABLED = new ConfigEntry<>(0, "settings.auto-reload", true);
public static final ConfigEntry<Boolean> REDIS_BUNGEE_ENABLED = new ConfigEntry<>(0, "settings.redis-bungee", false);
public static final ConfigEntry<Boolean> ASSIGN_TARGETS_ENABLED = new ConfigEntry<>(0, "settings.assign-targets", false);
public static final ConfigEntry<String> CONNECTING_MESSAGE = new ConfigEntry<>(0, "settings.messages.connecting", "&aConnecting to {server}");
public static final ConfigEntry<String> FAILURE_MESSAGE = new ConfigEntry<>(0, "settings.messages.failure", "&cCould not find a server to connect to");
public static final ConfigEntry<String> UNAVAILABLE_MESSAGE = new ConfigEntry<>(0, "settings.messages.unavailable", "&cThis command cannot be executed on this server");
public static final ConfigEntry<String> UNKNOWN_SECTION_MESSAGE = new ConfigEntry<>(0, "settings.messages.unknown", "&cCould not find a section with that name");
}

View File

@ -0,0 +1,83 @@
package me.jaimemartz.lobbybalancer.configuration;
import me.jaimemartz.faucet.ConfigEntry;
import java.util.List;
public class ConfigHelper {
public static Object get(ConfigEntry<Object> entry) {
return entry.get();
}
public static String getString(ConfigEntry<String> entry) {
return entry.get();
}
public static List<String> getStringList(ConfigEntry<List<String>> entry) {
return entry.get();
}
public static boolean getBoolean(ConfigEntry<Boolean> entry) {
return entry.get();
}
public static List<Boolean> getBooleanList(ConfigEntry<List<Boolean>> entry) {
return entry.get();
}
public static byte getByte(ConfigEntry<Byte> entry) {
return entry.get();
}
public static List<Byte> getByteList(ConfigEntry<List<Byte>> entry) {
return entry.get();
}
public static char getChar(ConfigEntry<Character> entry) {
return entry.get();
}
public static List<Character> getCharList(ConfigEntry<List<Character>> entry) {
return entry.get();
}
public static double getDouble(ConfigEntry<Double> entry) {
return entry.get();
}
public static List<Double> getDoubleList(ConfigEntry<List<Double>> entry) {
return entry.get();
}
public static float getFloat(ConfigEntry<Float> entry) {
return entry.get();
}
public static List<Float> getFloatList(ConfigEntry<List<Float>> entry) {
return entry.get();
}
public static int getInt(ConfigEntry<Integer> entry) {
return entry.get();
}
public static List<Integer> getIntList(ConfigEntry<List<Integer>> entry) {
return entry.get();
}
public static long getLong(ConfigEntry<Long> entry) {
return entry.get();
}
public static List<Long> getLongList(ConfigEntry<List<Long>> entry) {
return entry.get();
}
public static short getShort(ConfigEntry<Short> entry) {
return entry.get();
}
public static List<Short> getShortList(ConfigEntry<List<Short>> entry) {
return entry.get();
}
}

View File

@ -0,0 +1,68 @@
package me.jaimemartz.lobbybalancer.connection;
import me.jaimemartz.faucet.Messager;
import me.jaimemartz.lobbybalancer.LobbyBalancer;
import me.jaimemartz.lobbybalancer.configuration.ConfigEntries;
import me.jaimemartz.lobbybalancer.ping.ServerStatus;
import me.jaimemartz.lobbybalancer.section.ServerSection;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import java.util.ArrayList;
import java.util.List;
public abstract class ConnectionIntent {
public ConnectionIntent(LobbyBalancer plugin, ProxiedPlayer player, ServerSection section) {
ServerInfo target = this.findTarget(plugin, player, section);
Messager msgr = new Messager(player);
if (target != null) {
msgr.send((ConfigEntries.CONNECTING_MESSAGE.get()).replace("{server}", target.getName()));
this.connect(target);
} else {
msgr.send(ConfigEntries.FAILURE_MESSAGE.get());
this.failure();
}
}
private ServerInfo findTarget(LobbyBalancer plugin, ProxiedPlayer player, ServerSection section) {
if (ConfigEntries.ASSIGN_TARGETS_ENABLED.get()) {
if (ServerAssignRegistry.hasAssignedServer(player, section)) {
ServerInfo target = ServerAssignRegistry.getAssignedServer(player, section);
ServerStatus status = plugin.getPingManager().getStatus(target);
if (status.isAccessible()) {
return target;
} else {
ServerAssignRegistry.revokeTarget(player, section);
}
}
}
ProviderType provider = section.getProvider();
int intents = ConfigEntries.SERVER_CHECK_ATTEMPTS.get();
List<ServerInfo> servers = new ArrayList<>();
servers.addAll(section.getServers());
while (intents-- >= 1) {
ServerInfo target = provider.requestTarget(plugin, section, servers, player);
if (target == null) continue;
if (servers.size() == 0) return null;
if (servers.size() == 1) return servers.get(0);
ServerStatus status = plugin.getPingManager().getStatus(target);
if (status.isAccessible()) {
return target;
} else {
servers.remove(target);
}
}
return null;
}
public abstract void connect(ServerInfo server);
public void failure() {
//Nothing to do
}
}

View File

@ -0,0 +1,137 @@
package me.jaimemartz.lobbybalancer.connection;
import com.google.common.collect.Iterables;
import com.maxmind.geoip2.exception.GeoIp2Exception;
import com.maxmind.geoip2.model.CountryResponse;
import com.maxmind.geoip2.record.Country;
import me.jaimemartz.lobbybalancer.LobbyBalancer;
import me.jaimemartz.lobbybalancer.configuration.ConfigEntries;
import me.jaimemartz.lobbybalancer.ping.ServerStatus;
import me.jaimemartz.lobbybalancer.section.ServerSection;
import me.jaimemartz.lobbybalancer.utils.ConfigUtils;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.config.Configuration;
import java.io.IOException;
import java.net.InetAddress;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
public enum ProviderType {
NONE(0, "Returns no server") {
@Override
public ServerInfo requestTarget(LobbyBalancer plugin, ServerSection section, List<ServerInfo> list, ProxiedPlayer player) {
return null;
}
},
DIRECT(1, "Returns the only server in the list") {
@Override
public ServerInfo requestTarget(LobbyBalancer plugin, ServerSection section, List<ServerInfo> list, ProxiedPlayer player) {
return Iterables.getOnlyElement(list);
}
},
LOCALIZED(2, "Returns the server that matches a region") {
@Override
public ServerInfo requestTarget(LobbyBalancer plugin, ServerSection section, List<ServerInfo> list, ProxiedPlayer player) {
Configuration rules = plugin.getConfig().getSection("settings.geolocation.rules");
if (ConfigEntries.GEOLOCATION_ENABLED.get() && ConfigUtils.isSet(rules, section.getName())) {
Configuration rule = rules.getSection(section.getName());
InetAddress address = player.getAddress().getAddress();
try {
CountryResponse countryResponse = plugin.getGeolocationManager().getReader().country(address);
Country country = countryResponse.getCountry();
for (String name : rule.getKeys()) {
List<String> countries = rule.getStringList(name);
if (countries.contains(country.getName().toUpperCase())) {
ServerInfo server = plugin.getProxy().getServerInfo(name);
if (server != null) {
return server;
}
break;
}
}
} catch (IOException | GeoIp2Exception e) {
e.printStackTrace();
}
}
return list.get(ThreadLocalRandom.current().nextInt(list.size()));
}
},
LOWEST(3, "Returns the server with the least players online") {
@Override
public ServerInfo requestTarget(LobbyBalancer plugin, ServerSection section, List<ServerInfo> list, ProxiedPlayer player) {
int min = Integer.MAX_VALUE;
ServerInfo target = null;
for (ServerInfo server : list) {
int count = LobbyBalancer.getPlayerCount(server);
if (count < min) {
min = count;
target = server;
}
}
return target;
}
},
RANDOM(4, "Returns a random server") {
@Override
public ServerInfo requestTarget(LobbyBalancer plugin, ServerSection section, List<ServerInfo> list, ProxiedPlayer player) {
return list.get(ThreadLocalRandom.current().nextInt(list.size()));
}
},
PROGRESSIVE(5, "Returns the first server that is not full") {
@Override
public ServerInfo requestTarget(LobbyBalancer plugin, ServerSection section, List<ServerInfo> list, ProxiedPlayer player) {
for (ServerInfo server : list) {
ServerStatus status = plugin.getPingManager().getStatus(server);
if (LobbyBalancer.getPlayerCount(server) < status.getMaximumPlayers()) {
return server;
}
}
return list.get(ThreadLocalRandom.current().nextInt(list.size()));
}
},
FILLER(6, "Returns the server with the most players online that is not full") {
@Override
public ServerInfo requestTarget(LobbyBalancer plugin, ServerSection section, List<ServerInfo> list, ProxiedPlayer player) {
int max = Integer.MIN_VALUE;
ServerInfo target = null;
for (ServerInfo server : list) {
ServerStatus status = plugin.getPingManager().getStatus(server);
int count = LobbyBalancer.getPlayerCount(server);
if (count > max && count <= status.getMaximumPlayers()) {
max = count;
target = server;
}
}
return target;
}
};
private final int id;
private final String description;
ProviderType(int id, String description) {
this.id = id;
this.description = description;
}
public int getId() {
return id;
}
public String getDescription() {
return description;
}
public abstract ServerInfo requestTarget(LobbyBalancer plugin, ServerSection section, List<ServerInfo> list, ProxiedPlayer player);
}

View File

@ -0,0 +1,55 @@
package me.jaimemartz.lobbybalancer.connection;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;
import me.jaimemartz.lobbybalancer.section.ServerSection;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import java.util.Map;
public class ServerAssignRegistry {
private static final Table<ProxiedPlayer, ServerSection, ServerInfo> table = HashBasedTable.create();
public static void assignTarget(ProxiedPlayer player, ServerSection group, ServerInfo server) {
synchronized (table) {
table.put(player, group, server);
}
}
public static void revokeTarget(ProxiedPlayer player, ServerSection group) {
synchronized (table) {
table.remove(player, group);
}
}
public static ServerInfo getAssignedServer(ProxiedPlayer player, ServerSection group) {
synchronized (table) {
return table.get(player, group);
}
}
public static Map<ServerSection, ServerInfo> getAssignments(ProxiedPlayer player) {
synchronized (table) {
return table.row(player);
}
}
public static void clearAsssignedServers(ProxiedPlayer player) {
synchronized (table) {
table.row(player).clear();
}
}
public static boolean hasAssignedServer(ProxiedPlayer player, ServerSection group) {
synchronized (table) {
return table.contains(player, group);
}
}
public static Table<ProxiedPlayer, ServerSection, ServerInfo> getTable() {
synchronized (table) {
return table;
}
}
}

View File

@ -0,0 +1,28 @@
package me.jaimemartz.lobbybalancer.listener;
import me.jaimemartz.lobbybalancer.LobbyBalancer;
import me.jaimemartz.lobbybalancer.configuration.ConfigEntries;
import me.jaimemartz.lobbybalancer.connection.ServerAssignRegistry;
import me.jaimemartz.lobbybalancer.utils.PlayerLocker;
import net.md_5.bungee.api.event.PlayerDisconnectEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.event.EventHandler;
import net.md_5.bungee.event.EventPriority;
public class PlayerDisconnectListener implements Listener {
private final LobbyBalancer plugin;
public PlayerDisconnectListener(LobbyBalancer plugin) {
this.plugin = plugin;
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onDisconnect(PlayerDisconnectEvent event) {
PlayerLocker.unlock(event.getPlayer());
//Delete this if we want to keep assigned groups even when leaving
if (ConfigEntries.ASSIGN_TARGETS_ENABLED.get()) {
ServerAssignRegistry.clearAsssignedServers(event.getPlayer());
}
}
}

View File

@ -0,0 +1,44 @@
package me.jaimemartz.lobbybalancer.listener;
import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteStreams;
import me.jaimemartz.lobbybalancer.LobbyBalancer;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.Server;
import net.md_5.bungee.api.event.PluginMessageEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.event.EventHandler;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
public class PluginMessageListener implements Listener {
private final LobbyBalancer plugin;
public PluginMessageListener(LobbyBalancer plugin) {
this.plugin = plugin;
}
@EventHandler
public void onPluginMessage(PluginMessageEvent event) {
if (event.getTag().equals("LobbyBalancer") && event.getSender() instanceof Server) {
ByteArrayDataInput in = ByteStreams.newDataInput(event.getData());
String request = in.readUTF();
ServerInfo sender = ((Server) event.getSender()).getInfo();
switch (request) {
default: {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream(stream);
try {
out.writeUTF("The plugin message api for LobbyBalancer is not ready yet, it will be implemented in a future version");
} catch (IOException e) {
e.printStackTrace();
}
sender.sendData("LobbyBalancer", stream.toByteArray());
}
}
}
}
}

View File

@ -0,0 +1,21 @@
package me.jaimemartz.lobbybalancer.listener;
import me.jaimemartz.lobbybalancer.LobbyBalancer;
import net.md_5.bungee.api.event.ProxyReloadEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.event.EventHandler;
import net.md_5.bungee.event.EventPriority;
public class ProxyReloadListener implements Listener {
private final LobbyBalancer plugin;
public ProxyReloadListener(LobbyBalancer plugin) {
this.plugin = plugin;
}
@EventHandler(priority = EventPriority.LOWEST)
public void onReload(ProxyReloadEvent event) {
plugin.getLogger().info("BungeeCord has been reloaded, reloading the plugin...");
plugin.reloadPlugin();
}
}

View File

@ -0,0 +1,64 @@
package me.jaimemartz.lobbybalancer.listener;
import me.jaimemartz.faucet.Messager;
import me.jaimemartz.lobbybalancer.LobbyBalancer;
import me.jaimemartz.lobbybalancer.configuration.ConfigEntries;
import me.jaimemartz.lobbybalancer.connection.ConnectionIntent;
import me.jaimemartz.lobbybalancer.connection.ServerAssignRegistry;
import me.jaimemartz.lobbybalancer.section.ServerSection;
import me.jaimemartz.lobbybalancer.utils.PlayerLocker;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.ServerConnectEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.event.EventHandler;
import net.md_5.bungee.event.EventPriority;
public class ServerConnectListener implements Listener {
private final LobbyBalancer plugin;
public ServerConnectListener(LobbyBalancer plugin) {
this.plugin = plugin;
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onConnect(ServerConnectEvent event) {
ProxiedPlayer player = event.getPlayer();
ServerInfo target = event.getTarget();
Messager msgr = new Messager(player);
ServerSection section = plugin.getSectionManager().getByServer(target);
if (section == null) {
return;
}
if (section.getServers().contains(target)) {
if (PlayerLocker.isLocked(player)) {
return;
}
if (player.hasPermission("lobbybalancer.bypass")) {
msgr.send(ChatColor.RED + "You have not been moved because you have the lobbybalancer.bypass permission");
return;
}
if (player.getServer() != null && section.getServers().contains(player.getServer().getInfo())) {
if (ConfigEntries.ASSIGN_TARGETS_ENABLED.get()) {
ServerAssignRegistry.assignTarget(player, section, target);
return;
}
}
}
new ConnectionIntent(plugin, player, section) {
@Override
public void connect(ServerInfo server) {
if (ConfigEntries.ASSIGN_TARGETS_ENABLED.get()) {
ServerAssignRegistry.assignTarget(player, section, server);
}
event.setTarget(server);
}
};
}
}

View File

@ -0,0 +1,89 @@
package me.jaimemartz.lobbybalancer.listener;
import me.jaimemartz.lobbybalancer.LobbyBalancer;
import me.jaimemartz.lobbybalancer.configuration.ConfigEntries;
import me.jaimemartz.lobbybalancer.connection.ConnectionIntent;
import me.jaimemartz.lobbybalancer.section.ServerSection;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.ServerKickEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.config.Configuration;
import net.md_5.bungee.event.EventHandler;
import net.md_5.bungee.event.EventPriority;
import java.util.concurrent.atomic.AtomicBoolean;
public class ServerKickListener implements Listener {
private final LobbyBalancer plugin;
public ServerKickListener(LobbyBalancer plugin) {
this.plugin = plugin;
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onKick(ServerKickEvent event) {
ProxiedPlayer player = event.getPlayer();
ServerInfo from = event.getKickedFrom();
if (player.getServer() == null) {
return;
}
if (!player.getServer().getInfo().equals(from)) {
return;
}
ServerSection section = plugin.getSectionManager().getByServer(from);
if (section != null) {
if ((ConfigEntries.RECONNECT_KICK_IGNORED_SECTIONS.get()).contains(section.getName())) {
return;
}
Configuration rules = plugin.getConfig().getSection("settings.reconnect-kick.rules");
String name = rules.getString(section.getName());
ServerSection target = plugin.getSectionManager().getByName(name);
if (target == null) {
target = section.getParent();
if (target == null) {
return;
}
}
AtomicBoolean matches = new AtomicBoolean(false);
String reason = TextComponent.toPlainText(event.getKickReasonComponent());
for (String pattern : ConfigEntries.RECONNECT_KICK_REASONS.get()) {
if (reason.matches(pattern)) {
matches.set(true);
break;
}
}
if (ConfigEntries.RECONNECT_KICK_INVERTED.get()) {
matches.set(!matches.get());
}
if (matches.get()) {
new ConnectionIntent(plugin, player, target) {
@Override
public void connect(ServerInfo server) {
LobbyBalancer.checkSendMessage(player, ConfigEntries.RECONNECT_KICK_MESSAGE.get()
.replace("{from}", from.getName())
.replace("{to}", server.getName())
.replace("{reason}", reason)
);
event.setCancelled(true);
event.setCancelServer(server);
}
};
}
if (ConfigEntries.RECONNECT_KICK_PRINT_INFO.get()) {
LobbyBalancer.printStartupInfo(String.format("Kick Reason: \"%s\", Found Match: %s", TextComponent.toPlainText(event.getKickReasonComponent()), matches.get()));
}
}
}
}

View File

@ -0,0 +1,5 @@
package me.jaimemartz.lobbybalancer.ping;
public abstract class PingCallback {
public abstract void onPong(ServerStatus info);
}

View File

@ -0,0 +1,78 @@
package me.jaimemartz.lobbybalancer.ping;
import me.jaimemartz.lobbybalancer.LobbyBalancer;
import me.jaimemartz.lobbybalancer.configuration.ConfigEntries;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.scheduler.ScheduledTask;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
public class PingManager {
private final LobbyBalancer plugin;
private boolean stopped = true;
private PingTacticType tactic;
private ScheduledTask task;
private final Map<ServerInfo, ServerStatus> storage = new HashMap<>();
public PingManager(LobbyBalancer plugin) {
this.plugin = plugin;
}
public void start() {
if (task != null) {
stop();
}
stopped = false;
tactic = PingTacticType.valueOf((ConfigEntries.SERVER_CHECK_MODE.get()).toUpperCase());
LobbyBalancer.printStartupInfo(String.format("Starting the ping task, the interval is %s", ConfigEntries.SERVER_CHECK_INTERVAL.get()));
task = plugin.getProxy().getScheduler().schedule(plugin, () -> {
for (ServerInfo server : plugin.getProxy().getServers().values()) {
if (stopped) break;
if (server != null) {
track(server);
}
}
}, 0L, ConfigEntries.SERVER_CHECK_INTERVAL.get(), TimeUnit.MILLISECONDS);
}
public void stop() {
if (task != null) {
task.cancel();
task = null;
stopped = true;
}
}
private void track(ServerInfo server) {
tactic.ping(server, new PingCallback() {
@Override
public void onPong(ServerStatus status) {
/* FIXME: 08/01/2017 Not sure if necessary
if (status.isAccessible()) {
if (LobbyBalancer.getPlayerCount(server) > status.getMaximumPlayers()) {
status.setAccessible(false);
}
}
*/
if (ConfigEntries.SERVER_CHECK_PRINT_INFO.get()) {
plugin.getLogger().info(String.format(
"Tracking server %s, status: [Description: \"%s\", Online Players: %s, Maximum Players: %s, Accessible: %s]",
server.getName(), status.getDescription(), status.getOnlinePlayers(), status.getMaximumPlayers(), status.isAccessible())
);
}
storage.put(server, status);
}
}, plugin);
}
public ServerStatus getStatus(ServerInfo server) {
if (stopped) {
return new ServerStatus(server.getMotd(), server.getPlayers().size(), Integer.MAX_VALUE);
} else {
return storage.get(server);
}
}
}

View File

@ -0,0 +1,53 @@
package me.jaimemartz.lobbybalancer.ping;
import me.jaimemartz.faucet.ServerListPing;
import me.jaimemartz.faucet.StatusResponse;
import me.jaimemartz.lobbybalancer.LobbyBalancer;
import net.md_5.bungee.api.config.ServerInfo;
import java.io.IOException;
public enum PingTacticType {
CUSTOM {
ServerListPing utility = new ServerListPing();
@Override
public void ping(ServerInfo server, PingCallback callback, LobbyBalancer plugin) {
plugin.getProxy().getScheduler().runAsync(plugin, () -> {
try {
StatusResponse response = utility.ping(server.getAddress());
callback.onPong(new ServerStatus(
response.getDescription().toLegacyText(),
response.getPlayers().getOnline(),
response.getPlayers().getMax()
));
} catch (IOException e) {
callback.onPong(new ServerStatus("Server Unreachable", 0, 0));
}
});
}
},
GENERIC {
@Override
public void ping(ServerInfo server, PingCallback callback, LobbyBalancer plugin) {
try {
server.ping((ping, throwable) -> {
if (ping != null && throwable == null) {
callback.onPong(new ServerStatus(
ping.getDescription(),
ping.getPlayers().getOnline(),
ping.getPlayers().getMax()
));
} else {
callback.onPong(new ServerStatus("Server Unreachable", 0, 0));
}
});
} catch (Exception e) {
callback.onPong(new ServerStatus("Server Unreachable", 0, 0));
}
}
};
public abstract void ping(ServerInfo server, PingCallback callback, LobbyBalancer plugin);
}

View File

@ -0,0 +1,50 @@
package me.jaimemartz.lobbybalancer.ping;
import me.jaimemartz.lobbybalancer.configuration.ConfigEntries;
public final class ServerStatus {
private final String description;
private final int online, maximum;
private boolean accessible;
public ServerStatus(String description, int online, int maximum) {
this.description = description;
this.online = online;
this.maximum = maximum;
boolean accessible = true;
if (maximum != 0) {
for (String pattern : ConfigEntries.SERVER_CHECK_MARKER_MOTDS.get()) {
if (description.contains(pattern) || description.matches(pattern)) {
accessible = false;
}
}
if (online >= maximum) {
accessible = false;
}
} else {
accessible = false;
}
this.accessible = accessible;
}
public String getDescription() {
return description;
}
public int getOnlinePlayers() {
return online;
}
public int getMaximumPlayers() {
return maximum;
}
public boolean isAccessible() {
return accessible;
}
public void setAccessible(boolean accessible) {
this.accessible = accessible;
}
}

View File

@ -0,0 +1,40 @@
package me.jaimemartz.lobbybalancer.section;
import me.jaimemartz.faucet.Messager;
import me.jaimemartz.lobbybalancer.LobbyBalancer;
import me.jaimemartz.lobbybalancer.connection.ConnectionIntent;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender;
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 java.util.List;
public class SectionCommand extends Command {
private final LobbyBalancer plugin;
private final ServerSection section;
public SectionCommand(LobbyBalancer plugin, String name, String permission, List<String> aliases, ServerSection section) {
super(name, permission, aliases.stream().toArray(String[]::new));
this.plugin = plugin;
this.section = section;
plugin.getProxy().getPluginManager().registerCommand(plugin, this);
}
@Override
public void execute(CommandSender sender, String[] args) {
Messager msgr = new Messager(sender);
if (sender instanceof ProxiedPlayer) {
ProxiedPlayer player = (ProxiedPlayer) sender;
new ConnectionIntent(plugin, player, section) {
@Override
public void connect(ServerInfo server) {
player.connect(server);
}
};
} else {
msgr.send(ChatColor.RED + "This command can only be executed by a player");
}
}
}

View File

@ -0,0 +1,100 @@
package me.jaimemartz.lobbybalancer.section;
import me.jaimemartz.lobbybalancer.LobbyBalancer;
import me.jaimemartz.lobbybalancer.utils.AdapterFix;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.config.Configuration;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class SectionManager {
private final LobbyBalancer plugin;
private final Map<String, ServerSection> sectionStorage = new ConcurrentHashMap<>();
private final Map<ServerInfo, ServerSection> sectionServers = new ConcurrentHashMap<>();
public SectionManager(LobbyBalancer plugin) {
this.plugin = plugin;
}
public void load() throws RuntimeException {
LobbyBalancer.printStartupInfo("Loading sections from the config, this may take a while...");
long starting = System.currentTimeMillis();
Configuration sections = plugin.getConfig().getSection("sections");
sections.getKeys().forEach(name -> {
LobbyBalancer.printStartupInfo("Construction of section with name \"%s\"", name);
Configuration section = sections.getSection(name);
ServerSection object = new ServerSection(name, section, this);
sectionStorage.put(name, object);
});
sectionStorage.forEach((name, section) -> {
LobbyBalancer.printStartupInfo("Pre-Initialization of section with name \"%s\"", name);
section.preInit(plugin);
});
sectionStorage.forEach((name, section) -> {
LobbyBalancer.printStartupInfo("Initialization of section with name \"%s\"", name);
section.load(plugin);
});
sectionStorage.forEach((name, section) -> {
LobbyBalancer.printStartupInfo("Post-Initialization of section with name \"%s\"", name);
section.postInit(plugin);
});
AdapterFix.inject(plugin.getProxy());
long ending = System.currentTimeMillis() - starting;
LobbyBalancer.printStartupInfo("A total of %s section(s) have been loaded in %sms", sectionStorage.size(), ending);
}
public void flush() {
LobbyBalancer.printStartupInfo("Flushing section storage because of plugin shutdown");
sectionStorage.forEach((key, value) -> {
value.setValid(false);
if (value.hasCommand()) {
SectionCommand command = value.getCommand();
plugin.getProxy().getPluginManager().unregisterCommand(command);
}
if (value.hasServer()) {
AdapterFix.removeFakeServer(value.getServer());
}
});
sectionStorage.clear();
sectionServers.clear();
}
void register(ServerInfo server, ServerSection section) {
if (sectionServers.containsKey(server)) {
ServerSection other = sectionServers.get(server);
throw new IllegalArgumentException(String.format("The server \"%s\" is already in the section \"%s\"", server.getName(), other.getName()));
}
LobbyBalancer.printStartupInfo("Registering server \"%s\" to section \"%s\"", server.getName(), section.getName());
sectionServers.put(server, section);
}
public ServerSection getByName(String name) {
if (name == null) return null;
return sectionStorage.get(name);
}
public ServerSection getByServer(ServerInfo server) {
if (server == null) return null;
return sectionServers.get(server);
}
public Map<String, ServerSection> getSections() {
return Collections.unmodifiableMap(sectionStorage);
}
public boolean hasSection(String name) {
return sectionStorage.containsKey(name);
}
}

View File

@ -0,0 +1,197 @@
package me.jaimemartz.lobbybalancer.section;
import me.jaimemartz.lobbybalancer.LobbyBalancer;
import me.jaimemartz.lobbybalancer.connection.ProviderType;
import me.jaimemartz.lobbybalancer.utils.AdapterFix;
import me.jaimemartz.lobbybalancer.utils.ConfigUtils;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.config.Configuration;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ServerSection {
private final String name;
private final Configuration section;
private final SectionManager manager;
private boolean principal;
private ServerSection parent;
private List<ServerInfo> servers;
private ProviderType provider;
private ServerInfo server;
private SectionCommand command;
private boolean valid = false;
ServerSection(String name, Configuration section, SectionManager manager) {
this.name = name;
this.section = section;
this.manager = manager;
this.servers = new ArrayList<>();
}
void preInit(LobbyBalancer plugin) {
principal = section.getBoolean("principal", false);
if (ConfigUtils.isSet(section, "parent")) {
if (principal) {
throw new IllegalStateException(String.format("The principal section \"%s\" has a parent set", name));
}
parent = manager.getByName(section.getString("parent"));
if (parent == null) {
throw new IllegalArgumentException(String.format("The section \"%s\" has an invalid parent set", name));
}
} else {
if (!principal) {
throw new IllegalArgumentException(String.format("The section \"%s\" does not have a parent set", name));
}
}
if (ConfigUtils.isSet(section, "servers")) {
section.getStringList("servers").forEach(entry -> {
Pattern pattern = Pattern.compile(entry);
AtomicBoolean matches = new AtomicBoolean(false);
plugin.getProxy().getServers().forEach((key, value) -> {
Matcher matcher = pattern.matcher(key);
if (matcher.matches()) {
LobbyBalancer.printStartupInfo("Found a match with \"%s\" for entry \"%s\"", key, entry);
servers.add(value);
manager.register(server, this);
matches.set(true);
}
});
if (!matches.get()) {
plugin.getLogger().warning(String.format("Could not match a server with the entry \"%s\"", entry));
}
});
LobbyBalancer.printStartupInfo("Recognized %s server(s) out of %s entries", servers.size(), section.getStringList("servers").size());
} else {
throw new IllegalArgumentException(String.format("The section \"%s\" does not have any servers set", name));
}
}
void load(LobbyBalancer plugin) {
if (parent != null && parent.parent == this) {
throw new IllegalStateException(String.format("The section \"%s\" and \"%s\" are parents of each other", this.name, parent.name));
}
if (principal) {
manager.getSections().forEach((name, section) -> {
if (section.isPrincipal() && section != this) {
throw new IllegalStateException(String.format("The section \"%s\" is already principal", section.getName()));
}
});
}
if (ConfigUtils.isSet(section, "provider")) {
try {
provider = ProviderType.valueOf(section.getString("provider").toUpperCase());
if (provider == ProviderType.LOCALIZED) {
Configuration rules = plugin.getConfig().getSection("settings.geolocation.rules");
if (!ConfigUtils.isSet(rules, name)) {
throw new IllegalStateException(String.format("The section \"%s\" does not have a rule set in the geolocation section", this.name));
}
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
} else {
if (principal) {
throw new IllegalArgumentException(String.format("The principal section \"%s\" does not have a provider set", name));
}
}
}
void postInit(LobbyBalancer plugin) {
if (provider == null) {
ServerSection sect = this.parent;
while (sect.provider == null) {
sect = sect.parent;
}
LobbyBalancer.printStartupInfo("The section \"%s\" inherits the provider from parent section \"%s\"", this.name, sect.name);
provider = sect.provider;
}
if (provider == null) {
throw new IllegalStateException(String.format("The section \"%s\" does not have a provider", name));
}
if (ConfigUtils.isSet(section, "server")) {
int port = (int) Math.floor(Math.random() * (0xFFFF + 1));
ServerInfo server = plugin.getProxy().constructServerInfo("@" + section.getString("server"), new InetSocketAddress("0.0.0.0", port), String.format("Server of Section %s", name), false);
plugin.getSectionManager().register(server, this);
AdapterFix.addFakeServer(server);
}
if (ConfigUtils.isSet(section, "command")) {
Configuration other = section.getSection("command");
String name = other.getString("name");
String permission = other.getString("permission");
List<String> aliases = other.getStringList("aliases");
command = new SectionCommand(plugin, name, permission, aliases, this);
plugin.getProxy().getPluginManager().registerCommand(plugin, command);
}
this.setValid(true);
}
public String getName() {
return name;
}
protected Configuration getSection() {
return section;
}
public boolean isPrincipal() {
return principal;
}
public ServerSection getParent() {
return parent;
}
public List<ServerInfo> getServers() {
return servers;
}
public ProviderType getProvider() {
return provider;
}
public ServerInfo getServer() {
return server;
}
public SectionCommand getCommand() {
return command;
}
public boolean hasServer() {
return server != null;
}
public boolean hasCommand() {
return command != null;
}
public boolean isValid() {
return valid;
}
void setValid(boolean valid) {
this.valid = valid;
}
}

View File

@ -0,0 +1,87 @@
package me.jaimemartz.lobbybalancer.utils;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.config.ConfigurationAdapter;
import net.md_5.bungee.api.config.ListenerInfo;
import net.md_5.bungee.api.config.ServerInfo;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
public class AdapterFix implements ConfigurationAdapter {
private static final Map<String, ServerInfo> fakeServers = new HashMap<>();
private static AdapterFix instance = null;
public static void inject(ProxyServer server) {
if (instance == null) {
instance = new AdapterFix(server.getConfigurationAdapter());
}
server.setConfigurationAdapter(instance);
}
public static void addFakeServer(ServerInfo server) {
fakeServers.put(server.getName(), server);
}
public static void removeFakeServer(ServerInfo server) {
fakeServers.remove(server.getName());
}
public static Map<String, ServerInfo> getFakeServers() {
return fakeServers;
}
private final ConfigurationAdapter adapter;
public AdapterFix(ConfigurationAdapter adapter) {
this.adapter = adapter;
}
@Override
public void load() {
adapter.load();
}
@Override
public int getInt(String path, int def) {
return adapter.getInt(path, def);
}
@Override
public String getString(String path, String def) {
return adapter.getString(path, def);
}
@Override
public boolean getBoolean(String path, boolean def) {
return adapter.getBoolean(path, def);
}
@Override
public Collection<?> getList(String path, Collection<?> def) {
return adapter.getList(path, def);
}
@Override
public Map<String, ServerInfo> getServers() {
Map<String, ServerInfo> res = adapter.getServers();
res.putAll(fakeServers);
return res;
}
@Override
public Collection<ListenerInfo> getListeners() {
return adapter.getListeners();
}
@Override
public Collection<String> getGroups(String player) {
return adapter.getGroups(player);
}
@Override
public Collection<String> getPermissions(String group) {
return adapter.getPermissions(group);
}
}

View File

@ -0,0 +1,12 @@
package me.jaimemartz.lobbybalancer.utils;
public class ClassUtils {
public static boolean isPresent(String className) {
try {
Class.forName(className);
} catch (ClassNotFoundException e) {
return false;
}
return true;
}
}

View File

@ -0,0 +1,9 @@
package me.jaimemartz.lobbybalancer.utils;
import net.md_5.bungee.config.Configuration;
public class ConfigUtils {
public static boolean isSet(Configuration config, String path) {
return config.get(path) != null;
}
}

View File

@ -0,0 +1,65 @@
package me.jaimemartz.lobbybalancer.utils;
import com.fasterxml.jackson.databind.ext.Java7Support;
import com.maxmind.db.CHMCache;
import com.maxmind.geoip2.DatabaseReader;
import me.jaimemartz.lobbybalancer.LobbyBalancer;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.GZIPInputStream;
public class GeolocationManager {
private final DatabaseReader reader;
public GeolocationManager(LobbyBalancer plugin) throws IOException {
Logger.getLogger(Java7Support.class.getName()).setLevel(Level.SEVERE);
File dir = new File(plugin.getDataFolder(), "database");
if (!dir.exists()) {
dir.mkdir();
}
File packed = new File(dir, "GeoLite2-Country.mmdb.gz");
File database = new File(dir, "GeoLite2-Country.mmdb");
if (!database.exists()) {
LobbyBalancer.printStartupInfo("Downloading database");
URL url = new URL("http://geolite.maxmind.com/download/geoip/database/GeoLite2-Country.mmdb.gz");
try (ReadableByteChannel rbc = Channels.newChannel(url.openStream())) {
try (FileOutputStream fos = new FileOutputStream(packed)) {
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
}
}
LobbyBalancer.printStartupInfo("Unpacking database");
byte[] buffer = new byte[1024];
try (GZIPInputStream in = new GZIPInputStream(new FileInputStream(packed))) {
try (FileOutputStream out = new FileOutputStream(database)) {
int len;
while ((len = in.read(buffer)) > 0) {
out.write(buffer, 0, len);
}
}
}
LobbyBalancer.printStartupInfo("Deleting packed archive, success: " + (packed.delete() ? "yes" : "no"));
} else {
LobbyBalancer.printStartupInfo("Database exists, no need to download again");
}
LobbyBalancer.printStartupInfo("Initializing database");
reader = new DatabaseReader.Builder(database).withCache(new CHMCache()).build();
}
public DatabaseReader getReader() {
return reader;
}
}

View File

@ -0,0 +1,108 @@
package me.jaimemartz.lobbybalancer.utils;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.chat.ComponentBuilder;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.api.scheduler.ScheduledTask;
import org.apache.commons.io.IOUtils;
import org.jpaste.exceptions.PasteException;
import org.jpaste.pastebin.PasteExpireDate;
import org.jpaste.pastebin.PastebinLink;
import org.jpaste.pastebin.PastebinPaste;
import java.io.File;
import java.io.FileInputStream;
import java.nio.charset.Charset;
import java.util.concurrent.TimeUnit;
public enum PasteHelper {
PLUGIN {
@Override
public String paste(Plugin plugin) throws Exception {
File file = new File(plugin.getDataFolder(), "config.yml");
PastebinPaste paste = new PastebinPaste();
paste.setPasteTitle("{name} ({version} on {bungee_version}) Configuration"
.replace("{name}", plugin.getDescription().getName())
.replace("{version}", plugin.getDescription().getVersion())
.replace("{bungee_version}", plugin.getProxy().getVersion())
);
paste.setDeveloperKey(DEVELOPER_KEY);
paste.setPasteExpireDate(PasteExpireDate.ONE_MONTH);
paste.setVisibility(PastebinPaste.VISIBILITY_UNLISTED);
paste.setPasteFormat("yaml");
try (FileInputStream stream = new FileInputStream(file)) {
paste.setContents(IOUtils.toString(stream, Charset.forName("UTF-8")));
}
PastebinLink link = paste.paste();
return link.getLink().toString();
}
},
BUNGEE {
@Override
public String paste(Plugin plugin) throws Exception {
File file = new File(plugin.getDataFolder().getParentFile().getParentFile(), "config.yml");
PastebinPaste paste = new PastebinPaste();
paste.setPasteTitle("{name} ({version}) Configuration"
.replace("{name}", plugin.getProxy().getName())
.replace("{version}", plugin.getProxy().getVersion())
);
paste.setDeveloperKey(DEVELOPER_KEY);
paste.setPasteExpireDate(PasteExpireDate.ONE_MONTH);
paste.setVisibility(PastebinPaste.VISIBILITY_UNLISTED);
paste.setPasteFormat("yaml");
try (FileInputStream stream = new FileInputStream(file)) {
paste.setContents(IOUtils.toString(stream, Charset.forName("UTF-8")));
}
PastebinLink link = paste.paste();
return link.getLink().toString();
}
},
LOGS {
@Override
public String paste(Plugin plugin) throws Exception {
File file = new File(plugin.getDataFolder().getParentFile().getParentFile(), "proxy.log.0");
PastebinPaste paste = new PastebinPaste();
paste.setPasteTitle("{name} ({version}) Last Logs"
.replace("{name}", plugin.getProxy().getName())
.replace("{version}", plugin.getProxy().getVersion())
);
paste.setDeveloperKey(DEVELOPER_KEY);
paste.setPasteExpireDate(PasteExpireDate.ONE_MONTH);
paste.setVisibility(PastebinPaste.VISIBILITY_UNLISTED);
paste.setPasteFormat("text");
try (FileInputStream stream = new FileInputStream(file)) {
paste.setContents(IOUtils.toString(stream, Charset.forName("UTF-8")));
}
PastebinLink link = paste.paste();
return link.getLink().toString();
}
};
private String link;
private ScheduledTask task = null;
public void send(Plugin plugin, CommandSender sender, String message) {
try {
sender.sendMessage(new ComponentBuilder(message.replace("{link}", link == null ? link = paste(plugin) : link)
).color(ChatColor.GREEN).create());
if (task != null) {
plugin.getProxy().getScheduler().cancel(task);
}
task = plugin.getProxy().getScheduler().schedule(plugin, () -> link = null, 5, TimeUnit.MINUTES);
} catch (PasteException e) {
if (e.getMessage().equals("Failed to generate paste: Post limit, maximum pastes per 24h reached")) {
sender.sendMessage(new ComponentBuilder("The file could not be pasted, your ip has reached the 10 pastes per day limit").color(ChatColor.RED).create());
} else {
sender.sendMessage(new ComponentBuilder("An unexpected error occurred while pasting the file").color(ChatColor.RED).create());
}
e.printStackTrace();
} catch(Exception e) {
sender.sendMessage(new ComponentBuilder("An internal error occurred while attempting to perform this command").color(ChatColor.RED).create());
e.printStackTrace();
}
}
public abstract String paste(Plugin plugin) throws Exception;
public static final String DEVELOPER_KEY = "e3ff18d8fb001a3ece08ae0d7d4a87bd";
}

View File

@ -0,0 +1,38 @@
package me.jaimemartz.lobbybalancer.utils;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
public class PlayerLocker {
private static final Set<UUID> storage = Collections.synchronizedSet(new HashSet<UUID>());
public static boolean lock(ProxiedPlayer player) {
if (storage.contains(player.getUniqueId())) {
return false;
} else {
storage.add(player.getUniqueId());
return true;
}
}
public static boolean unlock(ProxiedPlayer player) {
if (storage.contains(player.getUniqueId())) {
storage.remove(player.getUniqueId());
return true;
} else {
return false;
}
}
public static boolean isLocked(ProxiedPlayer player) {
return storage.contains(player.getUniqueId());
}
public static void flush() {
storage.clear();
}
}

View File

@ -0,0 +1,59 @@
package org.jpaste;
import org.jpaste.exceptions.PasteException;
/**
*
* An abstract representation of a paste.
*
* <p>
* An abstract paste holds the paste contents and the {@link #paste()}
* operation.
* </p>
*
* @author Brian B
*
* @param <P>
* PasteResult implementation
*/
public abstract class AbstractPaste<P extends AbstractPasteLink> {
private String contents;
/**
* Creates a new abstract <code>AbstractPaste</code> instance.
*
* @param contents
* the contents of the paste
*/
public AbstractPaste(String contents) {
this.contents = contents;
}
/**
* Gets paste contents
*
* @return paste contents
*/
public String getContents() {
return contents;
}
/**
* Sets the paste contents
*
* @param contents
* contents of the paste
*/
public void setContents(String contents) {
this.contents = contents;
}
/**
* Attempts to paste this paste and returns the results
*
* @return paste result
* @throws PasteException if it failed to paste the paste
*/
public abstract P paste() throws PasteException;
}

View File

@ -0,0 +1,25 @@
package org.jpaste;
import java.net.URL;
/**
*
* An representation of an abstract PasteLink
*
* <p>
* An AbstractPasteLink holds the link/URL to a paste.
* </p>
*
* @author Brian B
*
*/
public abstract class AbstractPasteLink {
/**
* Gets the URL to this paste
*
* @return URL to paste
*/
public abstract URL getLink();
}

View File

@ -0,0 +1,13 @@
package org.jpaste.exceptions;
public class PasteException extends Exception {
private static final long serialVersionUID = -4230960075582953775L;
public PasteException() {
}
public PasteException(String message) {
super(message);
}
}

View File

@ -0,0 +1,60 @@
package org.jpaste.pastebin;
/**
*
* Represents a paste's expire date
*
* <p>
* A list of all expire dates/times can be found at the <a
* href="http://pastebin.com/api">pastebin API manual</a>.
* </p>
*
* @author Brian B
*
*/
public enum PasteExpireDate {
NEVER("N", -1), TEN_MINUTES("10M", 10 * 60), ONE_HOUR("1H", 60 * 60), ONE_DAY(
"1D", 60 * 60 * 24), ONE_WEEK("1W", 60 * 60 * 24 * 7), TWO_WEEKS(
"2W", 60 * 60 * 24 * 14), ONE_MONTH("1M", -1);
private String val;
private int timeSeconds;
/**
* Creates a new <code>PasteExpireDate</code> instance.
*
* @param val
* a valid expire date value
*/
PasteExpireDate(String val, int timeSeconds) {
this.val = val;
this.timeSeconds = timeSeconds;
}
/**
* Get's the valid value for the 'api_paste_expire_date' parameter
*
* @return the valid value for the 'api_paste_expire_date' parameter
*/
public String getValue() {
return val;
}
/**
* Gets PasteExpireDate based on: paste expire date minus paste date (in
* seconds)
*
* @param timeSeconds
* seconds between expire date and paste date
* @return PasteExpireDate
*/
public static PasteExpireDate getExpireDate(int timeSeconds) {
for (PasteExpireDate date : PasteExpireDate.values()) {
if (date.timeSeconds == timeSeconds) {
return date;
}
}
return ONE_MONTH;
}
}

View File

@ -0,0 +1,207 @@
package org.jpaste.pastebin;
import org.jpaste.exceptions.PasteException;
import org.jpaste.pastebin.exceptions.ParseException;
import org.jpaste.utils.web.Post;
import org.jpaste.utils.web.Web;
import org.jpaste.utils.xml.XMLUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.ByteArrayInputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
/**
*
* A global representation of the pastebin site
*
* <p>
* Holds various constants and method shortcuts
* </p>
*
* @author Brian B
*
*/
public class Pastebin {
/**
* Used to interact with the pastebin API
*/
public static final String API_POST_LINK = "http://pastebin.com/api/api_post.php";
/**
* Used for fetching an user session id
*/
public static final String API_LOGIN_LINK = "http://pastebin.com/api/api_login.php";
/**
* Fetches a paste text from pastebin
*
* @param pasteKey
* the unique paste key
* @return contents of the paste
*/
public static String getContents(String pasteKey) {
return PastebinLink.getContents(pasteKey);
}
/**
* Generates a paste on pastebin and returns the URL to it
*
* @param developerKey
* a developer key which can be fetched from the pastebin API
* page
* @param contents
* contents of the paste
* @return URL to paste
* @throws PasteException
* if it failed to push the paste
*/
public static URL pastePaste(String developerKey, String contents)
throws PasteException {
return pastePaste(developerKey, contents, null);
}
/**
* Generates a paste on pastebin and returns the URL to it
*
* @param developerKey
* a developer key which can be fetched from the pastebin API
* page
* @param contents
* contents of the paste
* @param title
* title of the paste
* @return URL to paste
* @throws PasteException
* if it failed to push the paste
*/
public static URL pastePaste(String developerKey, String contents,
String title) throws PasteException {
return newPaste(developerKey, contents, title).paste().getLink();
}
/**
* Generates a new paste and returns it
*
* @param developerKey
* a developer key which can be fetched from the pastebin API
* page
* @param contents
* contents of the paste
* @param title
* title of the paste
* @return a new paste
*/
public static PastebinPaste newPaste(String developerKey, String contents,
String title) {
PastebinPaste paste = new PastebinPaste(developerKey, contents);
paste.setPasteTitle(title);
return paste;
}
/**
* Generates a new paste and returns it
*
* @param developerKey
* a developer key which can be fetched from the pastebin API
* page
* @param contents
* contents of the paste
* @return a new paste
*/
public static PastebinPaste newPaste(String developerKey, String contents) {
return newPaste(developerKey, contents, null);
}
/**
* Gets the current trending pastebin pastes
*
* @param developerKey
* a developer key which can be fetched from the pastebin API
* page
* @return an array of {@link PastebinLink}
* @throws ParseException
* if it failed to parse the trending pastes
*/
public static PastebinLink[] getTrending(String developerKey)
throws ParseException {
if (developerKey == null || developerKey.isEmpty()) {
throw new IllegalArgumentException(
"Developer key can't be null or empty.");
}
Post post = new Post();
post.put("api_dev_key", developerKey);
post.put("api_option", "trends");
String response = Web.getContents(API_POST_LINK, post);
if (response.startsWith("<paste>")) {
// success
try {
DocumentBuilderFactory dbFactory = DocumentBuilderFactory
.newInstance();
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
response = "<dummy>" + response + "</dummy>"; // requires root
// element
Document doc = dBuilder.parse(new InputSource(
new ByteArrayInputStream(response.getBytes("utf-8"))));
doc.getDocumentElement().normalize();
NodeList nodes = doc.getElementsByTagName("paste");
ArrayList<PastebinLink> pastes = new ArrayList<PastebinLink>(
nodes.getLength());
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element element = (Element) node;
String pasteFormat = XMLUtils.getText(element,
"paste_format_short");
String title = XMLUtils.getText(element, "paste_title");
int visibility = Integer.parseInt(XMLUtils.getText(
element, "paste_private"));
int hits = Integer.parseInt(XMLUtils.getText(element,
"paste_hits"));
long expireDate = Long.parseLong(XMLUtils.getText(
element, "paste_expire_date"));
long pasteDate = Long.parseLong(XMLUtils.getText(
element, "paste_date"));
URL pasteURL = new URL(XMLUtils.getText(element,
"paste_url"));
PastebinPaste paste = new PastebinPaste();
paste.setPasteFormat(pasteFormat);
paste.setPasteTitle(title);
paste.setVisibility(visibility);
paste.setPasteExpireDate(expireDate == 0L ? PasteExpireDate.NEVER
: PasteExpireDate
.getExpireDate((int) (expireDate - pasteDate)));
PastebinLink pastebinLink = new PastebinLink(paste,
pasteURL, new Date(pasteDate * 1000));
pastebinLink.setHits(hits);
pastes.add(pastebinLink);
}
}
return pastes.toArray(new PastebinLink[pastes.size()]);
} catch (Exception e) {
throw new ParseException("Failed to parse pastes: "
+ e.getMessage());
}
}
throw new ParseException("Failed to parse pastes: " + response);
}
}

View File

@ -0,0 +1,184 @@
package org.jpaste.pastebin;
import org.jpaste.AbstractPasteLink;
import org.jpaste.exceptions.PasteException;
import org.jpaste.pastebin.account.PastebinAccount;
import org.jpaste.utils.web.Post;
import org.jpaste.utils.web.Web;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Date;
/**
*
* A representation of an online pastebin paste.
*
*
* <p>
* This class represents an existing pastebin paste URL
* </p>
*
* @author Brian B
*
*/
public class PastebinLink extends AbstractPasteLink {
private PastebinPaste paste;
private URL link;
private int hits;
private String key;
private Date pasteDate;
/**
* Creates a new <code>PastebinLink</code> object, representing an existing
* paste
*
* @param paste
* paste details
* @param url
* link to paste
*/
public PastebinLink(PastebinPaste paste, URL url) {
this(paste, url, new Date((System.currentTimeMillis() / 1000) * 1000));
}
/**
* Creates a new <code>PastebinLink</code> object, representing an existing
* paste
*
* @param paste
* paste details
* @param url
* link to paste
* @param pasteDate
* date the paste has been pasted
*/
public PastebinLink(PastebinPaste paste, URL url, Date pasteDate) {
this.paste = paste;
this.link = url;
this.pasteDate = pasteDate;
try {
this.key = url.toURI().getPath().substring(1);
} catch (URISyntaxException e) {
e.printStackTrace();
}
}
/**
* {@inheritDoc}
*/
@Override
public URL getLink() {
return link;
}
/**
* Gets pastebin paste details
*
* @return paste details
*/
public PastebinPaste getPaste() {
return paste;
}
/**
* Gets pastebin unique paste key
* @return unique paste key
*/
public String getKey() {
return this.key;
}
/**
* Fetches the pastebin link content
* <p>
* After parsing use the following methods: {@link #getPaste()} {@link PastebinPaste#getContents()}
*/
public void fetchContent() {
if(getPaste().getContents() != null) {
throw new IllegalStateException("Contents already fetched.");
}
getPaste().setContents(getContents(getKey()));
}
/**
* Sets the paste page hits
*
* @param hits
* amount of times paste has been visited
*/
public void setHits(int hits) {
if (hits < 0) {
throw new IllegalArgumentException("Hits must be positive: " + hits);
}
this.hits = hits;
}
/**
* Gets the paste page hits
*
* @return paste page hits
*/
public int getHits() {
return this.hits;
}
/**
* Gets the paste date
*
* @return paste date
*/
public Date getPasteDate() {
return this.pasteDate;
}
/**
* Deletes this paste
*
* @param account
* the account which was used to create this paste
* @param developerKey
* a developer key which can be fetched from the pastebin API
* page
* @throws PasteException if it failed to delete the paste
*/
public void delete(String developerKey, PastebinAccount account) throws PasteException {
if(developerKey == null || developerKey.isEmpty()) {
throw new IllegalArgumentException("Developer key can't be null or empty.");
}
if(account.getUserSessionId() == null || account.getUserSessionId().isEmpty()) {
throw new IllegalArgumentException("Account user session id is missing.");
}
Post post = new Post();
post.put("api_dev_key", developerKey);
post.put("api_user_key", account.getUserSessionId());
post.put("api_paste_key", getKey());
post.put("api_option", "delete");
String response = Web.getContents(Pastebin.API_POST_LINK, post);
if(response.equals("Paste Removed")) {
return;
}
throw new PasteException("Failed to delete paste: " + response);
}
/**
* Deletes this paste
* @throws PasteException if it failed to delete the paste
*/
public void delete() throws PasteException {
delete(getPaste().getDeveloperKey(), getPaste().getAccount());
}
/**
* Fetches a paste text from pastebin
*
* @param pasteKey
* the unique paste key
* @return contents of the paste
*/
public static String getContents(String pasteKey) {
return Web.getContents("http://pastebin.com/raw.php?i=" + pasteKey);
}
}

View File

@ -0,0 +1,312 @@
package org.jpaste.pastebin;
import org.jpaste.AbstractPaste;
import org.jpaste.exceptions.PasteException;
import org.jpaste.pastebin.account.PastebinAccount;
import org.jpaste.utils.web.Post;
import org.jpaste.utils.web.Web;
import java.net.MalformedURLException;
import java.net.URL;
/**
*
* A representation of a new or existing paste.
*
* <p>
* This class holds the contents of the paste itself. You can get and modify
* settings and then 'push' this paste onto <a
* href="http://pastebin.com/">pastebin.</a>
* </p>
*
* <p>
* This class has been programmed with the help of the <a
* href="http://pastebin.com/api/">pastebin API manual.</a>
* </p>
*
* @author Brian B
*
*/
public class PastebinPaste extends AbstractPaste<PastebinLink> {
/**
* Makes a paste public.
*/
public static final int VISIBILITY_PUBLIC = 0;
/**
* Makes a paste unlisted.
*/
public static final int VISIBILITY_UNLISTED = 1;
/**
* Makes a paste private.
* <p>
* Requires an {@link PastebinAccount
* org.jpaste.pastebin.account.PastebinAccount PastebinAccount}
* </p>
*/
public static final int VISIBILITY_PRIVATE = 2;
private String developerKey;
private PastebinAccount account;
private String pasteTitle;
private String pasteFormat;
private PasteExpireDate expireDate;
private int visibility;
/**
* Creates a new empty <code>PastebinPaste</code> instance.
*/
public PastebinPaste() {
this(null, null, null);
}
/**
* Creates a new <code>PastebinPaste</code> instance.
*
* @param contents
* the paste contents
*/
public PastebinPaste(String contents) {
this(null, contents, null);
}
/**
* Creates a new <code>PastebinPaste</code> instance.
*
* @param account
* a pastebin account
*/
public PastebinPaste(PastebinAccount account) {
this(account.getDeveloperKey(), null, account);
}
/**
* Creates a new <code>PastebinPaste</code> instance.
*
* @param developerKey
* a developer key which can be fetched from the pastebin API
* page
* @param contents
* the contents of the paste
*/
public PastebinPaste(String developerKey, String contents) {
this(developerKey, contents, null);
}
/**
* Creates a new <code>PastebinPaste</code> instance.
*
* @param developerKey
* a developer key which can be fetched from the pastebin API
* page
* @param contents
* the contents of the paste
* @param account
* a pastebin account
*/
public PastebinPaste(String developerKey, String contents,
PastebinAccount account) {
super(contents);
this.developerKey = developerKey;
this.account = account;
}
/**
* Sets the pastebin account If you set an account the pastes will be listed
* on your account.
*
* @param account
* a pastebin account
*/
public void setAccount(PastebinAccount account) {
this.account = account;
}
/**
* Gets the pastebin account
*
* @return pastebin account
*/
public PastebinAccount getAccount() {
return this.account;
}
/**
* Sets the developer key The developer key is required to paste contents on
* pastebin
*
* @param developerKey
* a developer key which can be fetched from the pastebin API
* page
*/
public void setDeveloperKey(String developerKey) {
if (developerKey == null || developerKey.isEmpty()) {
throw new IllegalArgumentException(
"Developer key can not be null or empty.");
}
this.developerKey = developerKey;
}
/**
* Sets the paste expire date
*
* @param date
* date when the paste will be removed
*/
public void setPasteExpireDate(PasteExpireDate date) {
this.expireDate = date;
}
/**
* Gets the developer key
*
* @return developer key
*/
public String getDeveloperKey() {
return this.developerKey;
}
/**
* Sets the paste title
*
* @param title
* title of the paste
*/
public void setPasteTitle(String title) {
this.pasteTitle = title;
}
/**
* Gets paste title
*
* @return paste title
*/
public String getPasteTitle() {
return this.pasteTitle;
}
/**
* Gets paste expire date
* @return paste expire date
*/
public PasteExpireDate getPasteExpireDate() {
return this.expireDate;
}
/**
* Sets the paste format The format is used for syntax highlighting
*
* @see <a href="http://pastebin.com/api#5">available syntax highlighting
* formats</a>
* @param format
* format of the paste
*/
public void setPasteFormat(String format) {
this.pasteFormat = format;
}
/**
* Gets paste format
*
* @return paste format
*/
public String getPasteFormat() {
return this.pasteFormat;
}
/**
* Makes this paste private, unlisted or public Default visibility is public
* <p>
* <strong>Valid visibilities</strong>
* </p>
* <p>
* {@link PastebinPaste#VISIBILITY_PUBLIC}
* </p>
* <p>
* {@link PastebinPaste#VISIBILITY_UNLISTED}
* </p>
* <p>
* {@link PastebinPaste#VISIBILITY_PRIVATE}
* </p>
*
* @param visibility
* the paste it's visibility
*/
public void setVisibility(int visibility) {
if (visibility < 0 || visibility > 2) {
throw new IllegalArgumentException("Unknown visibility: "
+ visibility);
}
this.visibility = visibility;
}
/**
* Gets this paste visibility
* <p>
* <strong>Valid visibilities</strong>
* </p>
* <p>
* {@link PastebinPaste#VISIBILITY_PUBLIC}
* </p>
* <p>
* {@link PastebinPaste#VISIBILITY_UNLISTED}
* </p>
* <p>
* {@link PastebinPaste#VISIBILITY_PRIVATE}
* </p>
*
* @return visibility of this paste
*/
public int getVisibility() {
return this.visibility;
}
/**
* {@inheritDoc}
*/
@Override
public PastebinLink paste() throws PasteException {
if (getContents() == null || getContents().isEmpty()) {
throw new IllegalStateException("Paste can not be null or empty.");
}
if (getDeveloperKey() == null || getDeveloperKey().isEmpty()) {
throw new IllegalStateException("Developer key is missing.");
}
Post post = new Post();
// required parameters
post.put("api_dev_key", getDeveloperKey());
post.put("api_option", "paste");
post.put("api_paste_code", getContents());
// optional parameters
if (this.account != null && this.account.getUserSessionId() != null) {
post.put("api_user_key", this.account.getUserSessionId());
}
if (this.pasteTitle != null) {
post.put("api_paste_name", getPasteTitle());
}
if (this.pasteFormat != null) {
post.put("api_paste_format", getPasteFormat());
}
post.put("api_paste_private", Integer.toString(getVisibility()));
if (this.expireDate != null) {
post.put("api_paste_expire_date", expireDate.getValue());
}
try {
String pageResponse = Web.getContents(Pastebin.API_POST_LINK, post);
if (pageResponse.startsWith("http")) {
// success
PastebinLink result = new PastebinLink(this, new URL(
pageResponse));
return result;
}
throw new PasteException("Failed to generate paste: "
+ pageResponse);
} catch (MalformedURLException e) {
// shouldn't happen
throw new PasteException("Failed to generate paste: " + e);
}
}
}

View File

@ -0,0 +1,369 @@
package org.jpaste.pastebin.account;
import org.jpaste.pastebin.PasteExpireDate;
import org.jpaste.pastebin.Pastebin;
import org.jpaste.pastebin.PastebinLink;
import org.jpaste.pastebin.PastebinPaste;
import org.jpaste.pastebin.exceptions.LoginException;
import org.jpaste.pastebin.exceptions.ParseException;
import org.jpaste.utils.web.Post;
import org.jpaste.utils.web.Web;
import org.jpaste.utils.xml.XMLUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.ByteArrayInputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
/**
*
* Represents an account on <a href="http://pastebin.com/">Pastebin</a>.
*
* <p>
* This class can fetch a non-expiring user session id. This session id is used
* for generating private pasted, fetching user details, fetching user his
* pastes, adding pastes to his account & more account based interactions.
* </p>
*
* <p>
* A reference manual for generating a user session id can be found <a
* href="http://pastebin.com/api#8">here</a>.
*
* @author Brian B
*
*/
public class PastebinAccount {
private String username, password, userSessionId, developerKey;
/**
* Creates a new empty <code>PastebinAccount</code> instance.
*/
public PastebinAccount() {
this(null, null, null);
}
/**
* Creates a new <code>PastebinAccount</code> instance.
*
* When you use this constructor, you'll need to use the {@link #login()} to
* fetch an user session id
*
* @param developerKey
* a developer key which can be fetched from the pastebin API
* page
* @param username
* the username of the pastebin account
* @param password
* the password of the pastebin account
*/
public PastebinAccount(String developerKey, String username, String password) {
this.developerKey = developerKey;
this.username = username;
this.password = password;
}
/**
* Creates a new <code>PastebinAccount</code> instance.
*
* @param userSessionId
* the user session id of the pastebin account.
*/
public PastebinAccount(String userSessionId) {
this(null, userSessionId);
}
/**
* Creates a new <code>PastebinAccount</code> instance.
*
* @param developerKey
* a developer key which can be fetched from the pastebin API
* page
* @param userSessionId
* the user session id of the pastebin account.
*/
public PastebinAccount(String developerKey, String userSessionId) {
this.developerKey = developerKey;
this.userSessionId = userSessionId;
}
/**
* Sets the user session id The user session id can be used to paste private
* pastes and will add pastes to your account
*
* @param userSessionId
* the user session id of the pastebin account
*/
public void setUserSessionId(String userSessionId) {
this.userSessionId = userSessionId;
}
/**
* Gets the user session id
*
* @return user session id
*/
public String getUserSessionId() {
return this.userSessionId;
}
/**
* Sets the username
*
* @param username
* the username of the pastebin account
*/
public void setUsername(String username) {
this.username = username;
}
/**
* Gets username of the pastebin account
*
* @return username of the pastebin account. Could be null if only an user
* session is was provided.
*/
public String getUsername() {
return this.username;
}
/**
* Sets the password
*
* @param password
* the password of the pastebin account
*/
public void setPassword(String password) {
this.password = password;
}
/**
* Gets password of the pastebin account
*
* @return password of the pastebin account. Could be null if only an user
* session id was provided.
*/
public String getPassword() {
return this.password;
}
/**
* Sets the developer key The developer key is required to paste contents on
* pastebin
*
* @param developerKey
* a developer key which can be fetched from the pastebin API
* page
*/
public void setDeveloperKey(String developerKey) {
if (developerKey == null || developerKey.isEmpty()) {
throw new IllegalArgumentException(
"Developer key can not be null or empty.");
}
this.developerKey = developerKey;
}
/**
* Gets the developer key
*
* @return developer key
*/
public String getDeveloperKey() {
return this.developerKey;
}
/**
* Fetches an user session id.
*
* @throws LoginException
* if fetching the user session id failed
*/
public void login() throws LoginException {
if (getUserSessionId() != null) {
throw new IllegalStateException("Already logged in.");
}
if (getUsername() == null || getPassword() == null) {
throw new IllegalStateException("Username or password null.");
}
if (getDeveloperKey() == null || getDeveloperKey().isEmpty()) {
throw new IllegalStateException("Developer key is missing.");
}
Post post = new Post();
post.put("api_dev_key", getDeveloperKey());
post.put("api_user_name", username);
post.put("api_user_password", password);
String response = Web.getContents(Pastebin.API_LOGIN_LINK, post);
if (response == null || response.isEmpty()) {
throw new LoginException("Empty response from login API server.");
}
if (response.toLowerCase().startsWith("bad")) {
throw new LoginException("Failed to login: " + response);
}
this.userSessionId = response;
}
/**
* Gets all pasted pastes by this user
*
* @param limit
* maximum amount of pastes to receive
* <p>
* <code>0 > limit > 1000</code>
* </p>
* @return all pasted pastes made by this user/account
* @throws ParseException
* if it failed to parse the pastes
*/
public PastebinLink[] getPastes(int limit) throws ParseException {
if (limit > 1000) {
limit = 1000;
}
if (limit < 1) {
limit = 1;
}
if (getUserSessionId() == null) {
throw new IllegalStateException("User session id missing.");
}
if (getDeveloperKey() == null || getDeveloperKey().isEmpty()) {
throw new IllegalStateException("Developer key is missing.");
}
Post post = new Post();
post.put("api_dev_key", getDeveloperKey());
post.put("api_user_key", getUserSessionId());
post.put("api_results_limit", Integer.toString(limit));
post.put("api_option", "list");
String response = Web.getContents(Pastebin.API_POST_LINK, post);
if (response.equals("No pastes found.")) {
return null;
}
if (response.startsWith("<paste>")) {
// success
try {
DocumentBuilderFactory dbFactory = DocumentBuilderFactory
.newInstance();
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
response = "<dummy>" + response + "</dummy>"; // requires root
// element
Document doc = dBuilder.parse(new InputSource(
new ByteArrayInputStream(response.getBytes("utf-8"))));
doc.getDocumentElement().normalize();
NodeList nodes = doc.getElementsByTagName("paste");
ArrayList<PastebinLink> pastes = new ArrayList<PastebinLink>(
nodes.getLength());
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element element = (Element) node;
String pasteFormat = XMLUtils.getText(element,
"paste_format_short");
String title = XMLUtils.getText(element, "paste_title");
int visibility = Integer.parseInt(XMLUtils.getText(
element, "paste_private"));
int hits = Integer.parseInt(XMLUtils.getText(element,
"paste_hits"));
long expireDate = Long.parseLong(XMLUtils.getText(
element, "paste_expire_date"));
long pasteDate = Long.parseLong(XMLUtils.getText(
element, "paste_date"));
URL pasteURL = new URL(XMLUtils.getText(element,
"paste_url"));
PastebinPaste paste = new PastebinPaste(this);
paste.setPasteFormat(pasteFormat);
paste.setPasteTitle(title);
paste.setVisibility(visibility);
paste.setPasteExpireDate(expireDate == 0L ? PasteExpireDate.NEVER
: PasteExpireDate
.getExpireDate((int) (expireDate - pasteDate)));
PastebinLink pastebinLink = new PastebinLink(paste,
pasteURL, new Date(pasteDate * 1000));
pastebinLink.setHits(hits);
pastes.add(pastebinLink);
}
}
return pastes.toArray(new PastebinLink[pastes.size()]);
} catch (Exception e) {
throw new ParseException("Failed to parse pastes: "
+ e.getMessage());
}
}
throw new ParseException("Failed to parse pastes: " + response);
}
/**
* Gets pasted pastes (max 50) by this user
*
* @return all pasted pastes made by this user/account
* @throws ParseException
* if it failed to parse the pastes
*/
public PastebinLink[] getPastes() throws ParseException {
return getPastes(50);
}
/**
* Fetches the account details of this account
*
* @return account details
* @throws ParseException
* if it failed to parse the account details
*/
public PastebinAccountDetails getAccountDetails() throws ParseException {
if (getUserSessionId() == null) {
throw new IllegalStateException("User session id missing.");
}
if (getDeveloperKey() == null || getDeveloperKey().isEmpty()) {
throw new IllegalStateException("Developer key is missing.");
}
Post post = new Post();
post.put("api_dev_key", getDeveloperKey());
post.put("api_user_key", getUserSessionId());
post.put("api_option", "userdetails");
String response = Web.getContents(Pastebin.API_POST_LINK, post);
if (!response.startsWith("<user>")) {
throw new ParseException("Failed to parse account details: "
+ response);
}
try {
DocumentBuilderFactory dbFactory = DocumentBuilderFactory
.newInstance();
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
Document doc = dBuilder.parse(new InputSource(
new ByteArrayInputStream(response.getBytes("utf-8"))));
doc.getDocumentElement().normalize();
return new PastebinAccountDetails((Element) doc
.getElementsByTagName("user").item(0));
} catch (Exception e) {
throw new ParseException("Failed to parse account details: " + e);
}
}
}

View File

@ -0,0 +1,134 @@
package org.jpaste.pastebin.account;
import org.jpaste.utils.xml.XMLUtils;
import org.w3c.dom.Element;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
/**
*
* Holds information and settings of a pastebin account.
*
* @author Brian B
*
*/
public class PastebinAccountDetails {
private String username, format, expiration, avatarURL, website, email, location;
private int userPrivate, accountType;
/**
* Creates a new <code>PastebinAccountDetails</code> instance.
*
* @param userElement
* the 'user' xml elements received from the pastebin API
*/
public PastebinAccountDetails(Element userElement) {
this.username = XMLUtils.getText(userElement, "user_name");
this.format = XMLUtils.getText(userElement, "user_format_short");
this.expiration = XMLUtils.getText(userElement, "user_expiration");
this.avatarURL = XMLUtils.getText(userElement, "user_avatar_url");
this.userPrivate = Integer.parseInt(XMLUtils.getText(userElement, "user_private"));
this.website = XMLUtils.getText(userElement, "user_website");
this.email = XMLUtils.getText(userElement, "user_email");
this.location = XMLUtils.getText(userElement, "user_location");
this.accountType = Integer.parseInt(XMLUtils.getText(userElement, "user_account_type"));
}
/**
* Gets the username of this account
* @return username
*/
public String getUsername() {
return this.username;
}
/**
* Gets user text format
* @return user text format
*/
public String getFormat() {
return this.format;
}
/**
* Gets this account expiration time
* <p>
* <code>N = never (default)</code>
* </p>
* @return account expiration time
*/
public String getExpiration() {
return this.expiration;
}
/**
* Gets URL to avatar image
* @return URL to avatar image
* @throws MalformedURLException
*/
public URL getAvatarURL() throws MalformedURLException {
return new URL(this.avatarURL);
}
/**
* Gets user visibility
* <pre>
* 0 = public
* 1 = unlisted
* 2 = private
* </pre>
* @return visibility of account
*/
public int getPrivate() {
return this.userPrivate;
}
/**
* Gets the user's set website
* @return url to website
* @throws MalformedURLException
*/
public URL getWebsite() throws MalformedURLException {
if(this.website.isEmpty()) {
return null;
}
return new URL(this.website);
}
/**
* Gets the user e-mail
* @return user account e-mail
*/
public String getEmail() {
return this.email;
}
/**
* Gets the user's set location
* @return location, city
*/
public String getLocation() {
return this.location;
}
/**
* Determines if this account is a 'pro' account
* @return <code>true</code> if this account is a pro account, otherwise <code>false</code>.
*/
public boolean isPro() {
return accountType == 1;
}
/**
* Fetches the user his avatar from {@link #getAvatarURL()}
* @return image
* @throws IOException if image was not read
*/
public BufferedImage getAvatar() throws IOException {
return ImageIO.read(getAvatarURL());
}
}

View File

@ -0,0 +1,12 @@
package org.jpaste.pastebin.exceptions;
public class LoginException extends Exception {
private static final long serialVersionUID = -4230960075582953775L;
public LoginException() {
}
public LoginException(String message) {
super(message);
}
}

View File

@ -0,0 +1,13 @@
package org.jpaste.pastebin.exceptions;
public class ParseException extends Exception {
private static final long serialVersionUID = -4230960075582953775L;
public ParseException() {
}
public ParseException(String message) {
super(message);
}
}

View File

@ -0,0 +1,66 @@
package org.jpaste.utils.web;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map.Entry;
/**
*
* A representation of a HTTP post
*
* <p>
* Encodes parameters with the UTF-8 Charset.
* </p>
*
* <p>
* <a href="http://en.wikipedia.org/wiki/POST_(HTTP)">Reference manual</a>
* </p>
*
* @author Brian B
*
*/
public class Post {
private static final String ENCODING = "UTF-8";
private HashMap<String, String> post;
/**
* Creates a new <code>Post</code> instance.
*/
public Post() {
post = new HashMap<String, String>();
}
/**
* Adds a key value pair to the post parameters
*
* @param key
* the key
* @param value
* the value
*/
public void put(String key, String value) {
try {
this.post.put(URLEncoder.encode(key, ENCODING),
URLEncoder.encode(value, ENCODING));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
/**
* The HTTP post string representation
*
* @return HTTP Post contents
*/
public String getPost() {
StringBuilder builder = new StringBuilder();
for (Entry<String, String> entry : post.entrySet()) {
builder.append(entry.getKey()).append('=').append(entry.getValue())
.append('&');
}
builder.deleteCharAt(builder.length() - 1);
return new String(builder);
}
}

View File

@ -0,0 +1,75 @@
package org.jpaste.utils.web;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
/**
*
* Web utility class
*
* @author Brian B
*
*/
public class Web {
/**
* Submits a HTTP post and fetches and returns the response
*
* @param link
* The link/URL
* @param post
* the HTTP post representation
* @return response of the web page
*/
public static String getContents(String link, Post post) {
try {
URL url = new URL(link);
URLConnection connection = url.openConnection();
if(post != null) {
connection.setDoOutput(true);
OutputStreamWriter wr = new OutputStreamWriter(
connection.getOutputStream());
wr.write(post.getPost());
wr.flush();
wr.close();
}
BufferedReader reader = new BufferedReader(new InputStreamReader(
connection.getInputStream()));
StringBuilder builder = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
if (builder.length() > 0) {
builder.append('\n');
}
builder.append(line);
}
reader.close();
return new String(builder);
} catch (MalformedURLException e) {
throw new IllegalArgumentException("Malformed link: " + e);
} catch (IOException e) {
throw new RuntimeException("Failed to fetch contents from link: "
+ e);
}
}
/**
* Gets text from a link
*
* @param link
* The link/URL
* @return response of the web page
*/
public static String getContents(String link) {
return getContents(link, null);
}
}

View File

@ -0,0 +1,27 @@
package org.jpaste.utils.xml;
import org.w3c.dom.Element;
/**
*
* Holds various XML utility methods
*
* @author Brian B
*
*/
public class XMLUtils {
/**
* Fetches text from a element
*
* @param parent
* the parent of the element you want to fetch text from
* @param tagName
* name of the element you want to fetch text from
* @return text of tag
*/
public static String getText(Element parent, String tagName) {
return parent.getElementsByTagName(tagName).item(0).getTextContent();
}
}

View File

@ -0,0 +1,142 @@
version: '${project.version}'
# Providers of this plugin
# NONE: Returns no server
# DIRECT: Returns the only server in the list
# LOCALIZED: Returns the server that matches a region (testing)
# LOWEST: Returns the server with the least players online
# RANDOM: Returns a random server
# PROGRESSIVE: Returns the first server that is not full
# FILLER: Returns the server with the most players online that is not full
settings:
# The plugin will be disabled as default
enabled: false
# Disable the messages the plugin prints out when loading
silent-startup: false
# Whether the plugin should ask you to update or not
check-updates: true
# Pings to the servers to see if they can be accessed or not
server-check:
# If this is disabled the players will connect to the first server available
enabled: true
# Use either CUSTOM or GENERIC, the first one generally works the best
tactic: CUSTOM
# The attempts before giving up on getting a server for a player
attempts: 5
# The interval between every round of checks
interval: 10000
# This prints info every time the plugin checks a server and prints its results
print-info: false
# The descriptions that mark a server as non accessible
marker-motds: ["Server is not accessible", "Gamemode has already started"]
# This will connect the players to a server
reconnect-kick:
enabled: true
# If enabled, the reasons will work as a blacklist instead of a whitelist
inverted: false
# The reasons that determine if a player is reconnected or not, supports regex
reasons: []
# Sections where this feature is ignored
ignored: []
# This prints info every time a player gets kicked telling you the reason and if it matches the reasons
print-info: false
# The message the player is sent when kicked, comment it out to remove the message
message: '&cYou have been kicked from &a{from} &cand you are being moved to &a{to}&c, reason: &a{reason}'
# Override the behavior with rules
rules: {}
backward-command:
enabled: true
name: 'backward'
aliases: ['lobby', 'hub', 'back']
permission: ''
# Sections where this command is ignored
ignored: []
# Whether the command can accept the name of a section as a target
arguments: false
# Override the behavior with rules
rules: {}
# This gets the country of a player and decides to which server the player should go
# WARNING: In testing stage
geolocation:
enabled: false
rules: {}
# This will reload the plugin everytime you execute /greload
auto-reload: true
# Support for getting the players on a server with RedisBungee
redis-bungee: false
# Assign a target to a player instead of looking every time for one
assign-targets: false
messages:
connecting: '&aConnecting to {server}'
failure: '&cCould not find a server to connect to'
unavailable: '&cThis command cannot be executed on this server'
unknown: '&cCould not find a section with that name'
# Here you have an example of what you can do with the sections
# It is recommended to create your own sections
# The plugin will print out info telling you if your config is right or not
# The best way to understand this is to play around with it
# You can use regex to match a set of servers
sections:
general-lobbies:
principal: true
provider: RANDOM
server: 'general-lobbies'
servers: ["Lobby1", "Lobby2", "Lobby3"]
eggwars-lobbies:
parent: 'general-lobbies'
server: 'eggwars-lobbies'
servers: ["EWLobby1", "EWLobby2", "EWLobby3"]
eggwars-games:
provider: FILLER
parent: 'eggwars-lobbies'
servers: ["EW1", "EW2", "EW3", "EW4", "EW5"]
command:
name: 'playeggwars'
permission: ''
aliases: []
skywars-lobbies:
parent: 'general-lobbies'
server: 'skywars-lobbies'
servers: ["SWLobby1", "SWLobby2", "SWLobby3"]
skywars-games:
provider: FILLER
parent: 'skywars-lobbies'
servers: ["SW1", "SW2", "SW3", "SW4", "SW5"]
command:
name: 'playskywars'
permission: ''
aliases: []
practice:
provider: LOCALIZED
parent: 'general-lobbies'
servers: ["EUPractice", "USPractice"]
command:
name: 'practice'
permission: ''
aliases: ["rektnoobs"]

View File

@ -0,0 +1,5 @@
name: LobbyBalancer
main: me.jaimemartz.lobbybalancer.LobbyBalancer
version: ${project.version}
author: jaime29010
softdepend: [RedisBungee]

23
src/test/java/Test1.java Normal file
View File

@ -0,0 +1,23 @@
import me.jaimemartz.lobbybalancer.connection.ProviderType;
import org.junit.Test;
public class Test1 {
//@Test
public void test1() {
for (int i = 0; i <= 500; i++) {
int port = (int) Math.floor(Math.random() * (0xFFFF + 1));
System.out.println(port);
if (port < 0 || port > 0xFFFF) {
throw new IllegalArgumentException("port out of range:" + port);
}
}
}
@Test
public void test2() {
for (ProviderType provider : ProviderType.values()) {
System.out.println(String.format("Provider %s: %s", provider.name(), provider.getDescription()));
}
}
}