404 lines
17 KiB
Java
404 lines
17 KiB
Java
package com.songoda.core;
|
|
|
|
import com.songoda.core.commands.CommandManager;
|
|
import com.songoda.core.compatibility.ClientVersion;
|
|
import com.songoda.core.compatibility.CompatibleMaterial;
|
|
import com.songoda.core.core.LocaleModule;
|
|
import com.songoda.core.core.PluginInfo;
|
|
import com.songoda.core.core.PluginInfoModule;
|
|
import com.songoda.core.core.SongodaCoreCommand;
|
|
import com.songoda.core.core.SongodaCoreDiagCommand;
|
|
import com.songoda.core.core.SongodaCoreIPCommand;
|
|
import com.songoda.core.core.SongodaCoreUUIDCommand;
|
|
import org.bukkit.Bukkit;
|
|
import org.bukkit.entity.Player;
|
|
import org.bukkit.event.EventHandler;
|
|
import org.bukkit.event.HandlerList;
|
|
import org.bukkit.event.Listener;
|
|
import org.bukkit.event.player.PlayerLoginEvent;
|
|
import org.bukkit.event.player.PlayerQuitEvent;
|
|
import org.bukkit.event.server.PluginDisableEvent;
|
|
import org.bukkit.event.server.PluginEnableEvent;
|
|
import org.bukkit.plugin.ServicePriority;
|
|
import org.bukkit.plugin.java.JavaPlugin;
|
|
import org.bukkit.scheduler.BukkitTask;
|
|
import org.json.simple.JSONObject;
|
|
import org.json.simple.parser.JSONParser;
|
|
import org.json.simple.parser.ParseException;
|
|
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.InputStreamReader;
|
|
import java.lang.reflect.InvocationTargetException;
|
|
import java.lang.reflect.Method;
|
|
import java.net.HttpURLConnection;
|
|
import java.net.URL;
|
|
import java.util.ArrayList;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Objects;
|
|
import java.util.Set;
|
|
import java.util.UUID;
|
|
import java.util.logging.Level;
|
|
import java.util.logging.Logger;
|
|
|
|
public class SongodaCore {
|
|
private final static Logger logger = Logger.getLogger("SongodaCore");
|
|
|
|
/**
|
|
* Whenever we make a major change to the core GUI, updater,
|
|
* or other function used by the core, increment this number
|
|
*/
|
|
private final static int coreRevision = 9;
|
|
|
|
/**
|
|
* @since coreRevision 6
|
|
*/
|
|
private final static String coreVersion = "2.6.20";
|
|
|
|
/**
|
|
* This is specific to the website api
|
|
*/
|
|
private final static int updaterVersion = 1;
|
|
|
|
private final static Set<PluginInfo> registeredPlugins = new HashSet<>();
|
|
|
|
private static SongodaCore INSTANCE = null;
|
|
private JavaPlugin piggybackedPlugin;
|
|
private CommandManager commandManager;
|
|
private EventListener loginListener;
|
|
private ShadedEventListener shadingListener;
|
|
|
|
public static boolean hasShading() {
|
|
// sneaky hack to check the package name since maven tries to re-shade all references to the package string
|
|
return !SongodaCore.class.getPackage().getName().equals(new String(new char[] {'c', 'o', 'm', '.', 's', 'o', 'n', 'g', 'o', 'd', 'a', '.', 'c', 'o', 'r', 'e'}));
|
|
}
|
|
|
|
public static void registerPlugin(JavaPlugin plugin, int pluginID, CompatibleMaterial icon) {
|
|
registerPlugin(plugin, pluginID, icon == null ? "STONE" : icon.name(), coreVersion);
|
|
}
|
|
|
|
public static void registerPlugin(JavaPlugin plugin, int pluginID, String icon) {
|
|
registerPlugin(plugin, pluginID, icon, "?");
|
|
}
|
|
|
|
public static void registerPlugin(JavaPlugin plugin, int pluginID, String icon, String coreVersion) {
|
|
if (INSTANCE == null) {
|
|
// First: are there any other instances of SongodaCore active?
|
|
for (Class<?> clazz : Bukkit.getServicesManager().getKnownServices()) {
|
|
if (clazz.getSimpleName().equals("SongodaCore")) {
|
|
try {
|
|
// test to see if we're up-to-date
|
|
int otherVersion;
|
|
try {
|
|
otherVersion = (int) clazz.getMethod("getCoreVersion").invoke(null);
|
|
} catch (Exception ignore) {
|
|
otherVersion = -1;
|
|
}
|
|
|
|
if (otherVersion >= getCoreVersion()) {
|
|
// use the active service
|
|
// assuming that the other is greater than R6 if we get here ;)
|
|
clazz.getMethod("registerPlugin", JavaPlugin.class, int.class, String.class, String.class).invoke(null, plugin, pluginID, icon, coreVersion);
|
|
|
|
if (hasShading()) {
|
|
(INSTANCE = new SongodaCore()).piggybackedPlugin = plugin;
|
|
INSTANCE.shadingListener = new ShadedEventListener();
|
|
Bukkit.getPluginManager().registerEvents(INSTANCE.shadingListener, plugin);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// we are newer than the registered service: steal all of its registrations
|
|
// grab the old core's registrations
|
|
List<?> otherPlugins = (List<?>) clazz.getMethod("getPlugins").invoke(null);
|
|
|
|
// destroy the old core
|
|
Object oldCore = clazz.getMethod("getInstance").invoke(null);
|
|
Method destruct = clazz.getDeclaredMethod("destroy");
|
|
destruct.setAccessible(true);
|
|
destruct.invoke(oldCore);
|
|
|
|
// register ourselves as the SongodaCore service!
|
|
INSTANCE = new SongodaCore(plugin);
|
|
INSTANCE.init();
|
|
INSTANCE.register(plugin, pluginID, icon, coreVersion);
|
|
Bukkit.getServicesManager().register(SongodaCore.class, INSTANCE, plugin, ServicePriority.Normal);
|
|
|
|
// we need (JavaPlugin plugin, int pluginID, String icon) for our object
|
|
if (!otherPlugins.isEmpty()) {
|
|
Object testSubject = otherPlugins.get(0);
|
|
Class otherPluginInfo = testSubject.getClass();
|
|
Method otherPluginInfo_getJavaPlugin = otherPluginInfo.getMethod("getJavaPlugin");
|
|
Method otherPluginInfo_getSongodaId = otherPluginInfo.getMethod("getSongodaId");
|
|
Method otherPluginInfo_getCoreIcon = otherPluginInfo.getMethod("getCoreIcon");
|
|
Method otherPluginInfo_getCoreLibraryVersion = otherVersion >= 6 ? otherPluginInfo.getMethod("getCoreLibraryVersion") : null;
|
|
|
|
for (Object other : otherPlugins) {
|
|
INSTANCE.register(
|
|
(JavaPlugin) otherPluginInfo_getJavaPlugin.invoke(other),
|
|
(int) otherPluginInfo_getSongodaId.invoke(other),
|
|
(String) otherPluginInfo_getCoreIcon.invoke(other),
|
|
otherPluginInfo_getCoreLibraryVersion != null ? (String) otherPluginInfo_getCoreLibraryVersion.invoke(other) : "?");
|
|
}
|
|
}
|
|
|
|
return;
|
|
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException ex) {
|
|
plugin.getLogger().log(Level.WARNING, "Error registering core service", ex);
|
|
}
|
|
}
|
|
}
|
|
|
|
// register ourselves as the SongodaCore service!
|
|
INSTANCE = new SongodaCore(plugin);
|
|
INSTANCE.init();
|
|
Bukkit.getServicesManager().register(SongodaCore.class, INSTANCE, plugin, ServicePriority.Normal);
|
|
}
|
|
|
|
INSTANCE.register(plugin, pluginID, icon, coreVersion);
|
|
}
|
|
|
|
SongodaCore() {
|
|
commandManager = null;
|
|
}
|
|
|
|
SongodaCore(JavaPlugin javaPlugin) {
|
|
piggybackedPlugin = javaPlugin;
|
|
commandManager = new CommandManager(piggybackedPlugin);
|
|
loginListener = new EventListener();
|
|
}
|
|
|
|
private void init() {
|
|
shadingListener = new ShadedEventListener();
|
|
commandManager.registerCommandDynamically(new SongodaCoreCommand())
|
|
.addSubCommands(new SongodaCoreDiagCommand(), new SongodaCoreIPCommand(), new SongodaCoreUUIDCommand());
|
|
Bukkit.getPluginManager().registerEvents(loginListener, piggybackedPlugin);
|
|
Bukkit.getPluginManager().registerEvents(shadingListener, piggybackedPlugin);
|
|
|
|
// we aggressively want to own this command
|
|
tasks.add(Bukkit.getScheduler().runTaskLaterAsynchronously(piggybackedPlugin, () ->
|
|
CommandManager.registerCommandDynamically(piggybackedPlugin, "songoda", commandManager, commandManager),
|
|
10 * 60));
|
|
tasks.add(Bukkit.getScheduler().runTaskLaterAsynchronously(piggybackedPlugin, () ->
|
|
CommandManager.registerCommandDynamically(piggybackedPlugin, "songoda", commandManager, commandManager),
|
|
20 * 60));
|
|
tasks.add(Bukkit.getScheduler().runTaskLaterAsynchronously(piggybackedPlugin, () ->
|
|
CommandManager.registerCommandDynamically(piggybackedPlugin, "songoda", commandManager, commandManager),
|
|
20 * 60 * 2));
|
|
}
|
|
|
|
/**
|
|
* Used to yield this core to a newer core
|
|
*/
|
|
private void destroy() {
|
|
Bukkit.getServicesManager().unregister(SongodaCore.class, INSTANCE);
|
|
|
|
tasks.stream().filter(Objects::nonNull)
|
|
.forEach(task -> Bukkit.getScheduler().cancelTask(task.getTaskId()));
|
|
|
|
HandlerList.unregisterAll(loginListener);
|
|
if (!hasShading()) {
|
|
HandlerList.unregisterAll(shadingListener);
|
|
}
|
|
|
|
registeredPlugins.clear();
|
|
commandManager = null;
|
|
loginListener = null;
|
|
}
|
|
|
|
private ArrayList<BukkitTask> tasks = new ArrayList<>();
|
|
|
|
private void register(JavaPlugin plugin, int pluginID, String icon, String libraryVersion) {
|
|
logger.info(getPrefix() + "Hooked " + plugin.getName() + ".");
|
|
PluginInfo info = new PluginInfo(plugin, pluginID, icon, libraryVersion);
|
|
|
|
// don't forget to check for language pack updates ;)
|
|
info.addModule(new LocaleModule());
|
|
registeredPlugins.add(info);
|
|
tasks.add(Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, () -> update(info), 60L));
|
|
}
|
|
|
|
private void update(PluginInfo plugin) {
|
|
try {
|
|
URL url = new URL("https://update.songoda.com/index.php?plugin=" + plugin.getSongodaId()
|
|
+ "&version=" + plugin.getJavaPlugin().getDescription().getVersion()
|
|
+ "&updaterVersion=" + updaterVersion);
|
|
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
|
|
urlConnection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.95 Safari/537.11");
|
|
urlConnection.setRequestProperty("Accept", "*/*");
|
|
urlConnection.setConnectTimeout(5000);
|
|
InputStream is = urlConnection.getInputStream();
|
|
InputStreamReader isr = new InputStreamReader(is);
|
|
|
|
int numCharsRead;
|
|
char[] charArray = new char[1024];
|
|
StringBuilder sb = new StringBuilder();
|
|
while ((numCharsRead = isr.read(charArray)) > 0) {
|
|
sb.append(charArray, 0, numCharsRead);
|
|
}
|
|
urlConnection.disconnect();
|
|
|
|
String jsonString = sb.toString();
|
|
JSONObject json = (JSONObject) new JSONParser().parse(jsonString);
|
|
|
|
plugin.setLatestVersion((String) json.get("latestVersion"));
|
|
plugin.setMarketplaceLink((String) json.get("link"));
|
|
plugin.setNotification((String) json.get("notification"));
|
|
plugin.setChangeLog((String) json.get("changeLog"));
|
|
|
|
plugin.setJson(json);
|
|
|
|
for (PluginInfoModule module : plugin.getModules()) {
|
|
module.run(plugin);
|
|
}
|
|
} catch (IOException ex) {
|
|
final String er = ex.getMessage();
|
|
logger.log(Level.FINE, "Connection with Songoda servers failed: " + (er.contains("URL") ? er.substring(0, er.indexOf("URL") + 3) : er));
|
|
} catch (ParseException ex) {
|
|
logger.log(Level.FINE, "Failed to parse json for " + plugin.getJavaPlugin().getName() + " update check");
|
|
}
|
|
}
|
|
|
|
public static List<PluginInfo> getPlugins() {
|
|
return new ArrayList<>(registeredPlugins);
|
|
}
|
|
|
|
public static int getCoreVersion() {
|
|
return coreRevision;
|
|
}
|
|
|
|
public static String getCoreLibraryVersion() {
|
|
return coreVersion;
|
|
}
|
|
|
|
public static int getUpdaterVersion() {
|
|
return updaterVersion;
|
|
}
|
|
|
|
public static String getPrefix() {
|
|
return "[SongodaCore] ";
|
|
}
|
|
|
|
public static Logger getLogger() {
|
|
return logger;
|
|
}
|
|
|
|
public static boolean isRegistered(String plugin) {
|
|
return registeredPlugins.stream().anyMatch(p -> p.getJavaPlugin().getName().equalsIgnoreCase(plugin));
|
|
}
|
|
|
|
public static JavaPlugin getHijackedPlugin() {
|
|
return INSTANCE == null ? null : INSTANCE.piggybackedPlugin;
|
|
}
|
|
|
|
public static SongodaCore getInstance() {
|
|
return INSTANCE;
|
|
}
|
|
|
|
private static class ShadedEventListener implements Listener {
|
|
boolean via;
|
|
boolean proto = false;
|
|
|
|
ShadedEventListener() {
|
|
via = Bukkit.getPluginManager().isPluginEnabled("ViaVersion");
|
|
|
|
if (via) {
|
|
Bukkit.getOnlinePlayers().forEach(p -> ClientVersion.onLoginVia(p, getHijackedPlugin()));
|
|
return;
|
|
}
|
|
|
|
proto = Bukkit.getPluginManager().isPluginEnabled("ProtocolSupport");
|
|
if (proto) {
|
|
Bukkit.getOnlinePlayers().forEach(p -> ClientVersion.onLoginProtocol(p, getHijackedPlugin()));
|
|
}
|
|
}
|
|
|
|
@EventHandler
|
|
void onLogin(PlayerLoginEvent event) {
|
|
if (via) {
|
|
ClientVersion.onLoginVia(event.getPlayer(), getHijackedPlugin());
|
|
return;
|
|
}
|
|
|
|
if (proto) {
|
|
ClientVersion.onLoginProtocol(event.getPlayer(), getHijackedPlugin());
|
|
}
|
|
}
|
|
|
|
@EventHandler
|
|
void onLogout(PlayerQuitEvent event) {
|
|
if (via) {
|
|
ClientVersion.onLogout(event.getPlayer());
|
|
}
|
|
}
|
|
|
|
@EventHandler
|
|
void onEnable(PluginEnableEvent event) {
|
|
// technically shouldn't have online players here, but idk
|
|
if (!via && (via = event.getPlugin().getName().equals("ViaVersion"))) {
|
|
Bukkit.getOnlinePlayers().forEach(p -> ClientVersion.onLoginVia(p, getHijackedPlugin()));
|
|
} else if (!proto && (proto = event.getPlugin().getName().equals("ProtocolSupport"))) {
|
|
Bukkit.getOnlinePlayers().forEach(p -> ClientVersion.onLoginProtocol(p, getHijackedPlugin()));
|
|
}
|
|
}
|
|
}
|
|
|
|
private class EventListener implements Listener {
|
|
final HashMap<UUID, Long> lastCheck = new HashMap<>();
|
|
|
|
@EventHandler
|
|
void onLogin(PlayerLoginEvent event) {
|
|
final Player player = event.getPlayer();
|
|
|
|
// don't spam players with update checks
|
|
long now = System.currentTimeMillis();
|
|
Long last = lastCheck.get(player.getUniqueId());
|
|
|
|
if (last != null && now - 10000 < last) {
|
|
return;
|
|
}
|
|
|
|
lastCheck.put(player.getUniqueId(), now);
|
|
|
|
// is this player good to revieve update notices?
|
|
if (!event.getPlayer().isOp() && !player.hasPermission("songoda.updatecheck")) return;
|
|
|
|
// check for updates! ;)
|
|
for (PluginInfo plugin : getPlugins()) {
|
|
if (plugin.getNotification() != null && plugin.getJavaPlugin().isEnabled())
|
|
Bukkit.getScheduler().runTaskLaterAsynchronously(plugin.getJavaPlugin(), () ->
|
|
player.sendMessage("[" + plugin.getJavaPlugin().getName() + "] " + plugin.getNotification()), 10L);
|
|
}
|
|
}
|
|
|
|
@EventHandler
|
|
void onDisable(PluginDisableEvent event) {
|
|
// don't track disabled plugins
|
|
PluginInfo pi = registeredPlugins.stream().filter(p -> event.getPlugin() == p.getJavaPlugin()).findFirst().orElse(null);
|
|
|
|
if (pi != null) {
|
|
registeredPlugins.remove(pi);
|
|
}
|
|
|
|
if (event.getPlugin() == piggybackedPlugin) {
|
|
// uh-oh! Abandon ship!!
|
|
Bukkit.getServicesManager().unregisterAll(piggybackedPlugin);
|
|
|
|
// can we move somewhere else?
|
|
if ((pi = registeredPlugins.stream().findFirst().orElse(null)) != null) {
|
|
// move ourselves to this plugin
|
|
piggybackedPlugin = pi.getJavaPlugin();
|
|
|
|
Bukkit.getServicesManager().register(SongodaCore.class, INSTANCE, piggybackedPlugin, ServicePriority.Normal);
|
|
Bukkit.getPluginManager().registerEvents(loginListener, piggybackedPlugin);
|
|
Bukkit.getPluginManager().registerEvents(shadingListener, piggybackedPlugin);
|
|
CommandManager.registerCommandDynamically(piggybackedPlugin, "songoda", commandManager, commandManager);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|