validEntitys = new ArrayList<>();
150 | for (EntityType type : EntityType.values()) {
151 | validEntitys.add(type.toString());
152 | }
153 | for (String pair : entityPairs) {
154 | String entityType = pair.split(":")[0].toUpperCase();
155 | int amount = Integer.parseInt(pair.split(":")[1]);
156 | if (validEntitys.contains(entityType)) {
157 | entityIntegerHashMap.put(entityType, amount);
158 | } else {
159 | getLogger().info(ChatColor.RED + "Unknown EntityType " + entityType + " in the EntityAmounts section of the config");
160 | }
161 | }
162 | } catch (Error | Exception throwable) {
163 | getLogger().info(ChatColor.RED + "Error in the EntityAmounts section of the config missing \":\"");
164 | }
165 | }
166 | }
--------------------------------------------------------------------------------
/src/main/java/org/l2x9/l2x9core/Metrics.java:
--------------------------------------------------------------------------------
1 | package org.l2x9.l2x9core;
2 |
3 | import com.google.gson.JsonArray;
4 | import com.google.gson.JsonObject;
5 | import com.google.gson.JsonParser;
6 | import com.google.gson.JsonPrimitive;
7 | import org.bukkit.Bukkit;
8 | import org.bukkit.configuration.file.YamlConfiguration;
9 | import org.bukkit.entity.Player;
10 | import org.bukkit.plugin.Plugin;
11 | import org.bukkit.plugin.RegisteredServiceProvider;
12 | import org.bukkit.plugin.ServicePriority;
13 |
14 | import javax.net.ssl.HttpsURLConnection;
15 | import java.io.*;
16 | import java.lang.reflect.InvocationTargetException;
17 | import java.lang.reflect.Method;
18 | import java.net.URL;
19 | import java.nio.charset.StandardCharsets;
20 | import java.util.*;
21 | import java.util.concurrent.Callable;
22 | import java.util.logging.Level;
23 | import java.util.zip.GZIPOutputStream;
24 |
25 | /**
26 | * bStats collects some data for plugin authors.
27 | *
28 | * Check out https://bStats.org/ to learn more about bStats!
29 | */
30 | @SuppressWarnings({"WeakerAccess", "unused"})
31 | public class Metrics {
32 |
33 | // The version of this bStats class
34 | public static final int B_STATS_VERSION = 1;
35 | // The url to which the data is sent
36 | private static final String URL = "https://bStats.org/submitData/bukkit";
37 | // Should failed requests be logged?
38 | private static boolean logFailedRequests;
39 | // Should the sent data be logged?
40 | private static boolean logSentData;
41 | // Should the response text be logged?
42 | private static boolean logResponseStatusText;
43 | // The uuid of the server
44 | private static String serverUUID;
45 |
46 | static {
47 | // You can use the property to disable the check in your test environment
48 | if (System.getProperty("bstats.relocatecheck") == null || !System.getProperty("bstats.relocatecheck").equals("false")) {
49 | // Maven's Relocate is clever and changes strings, too. So we have to use this little "trick" ... :D
50 | final String defaultPackage = new String(
51 | new byte[]{'o', 'r', 'g', '.', 'b', 's', 't', 'a', 't', 's', '.', 'b', 'u', 'k', 'k', 'i', 't'});
52 | final String examplePackage = new String(new byte[]{'y', 'o', 'u', 'r', '.', 'p', 'a', 'c', 'k', 'a', 'g', 'e'});
53 | // We want to make sure nobody just copy & pastes the example and use the wrong package names
54 | if (Metrics.class.getPackage().getName().equals(defaultPackage) || Metrics.class.getPackage().getName().equals(examplePackage)) {
55 | throw new IllegalStateException("bStats Metrics class has not been relocated correctly!");
56 | }
57 | }
58 | }
59 |
60 | // Is bStats enabled on this server?
61 | private final boolean enabled;
62 | // The plugin
63 | private final Plugin plugin;
64 |
65 | // The plugin id
66 | private final int pluginId;
67 |
68 | // A list with all custom charts
69 | private final List charts = new ArrayList<>();
70 |
71 | /**
72 | * Class constructor.
73 | *
74 | * @param plugin The plugin which stats should be submitted.
75 | * @param pluginId The id of the plugin.
76 | * It can be found at What is my plugin id?
77 | */
78 | public Metrics(Plugin plugin, int pluginId) {
79 | if (plugin == null) {
80 | throw new IllegalArgumentException("Plugin cannot be null!");
81 | }
82 | this.plugin = plugin;
83 | this.pluginId = pluginId;
84 |
85 | // Get the config file
86 | File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats");
87 | File configFile = new File(bStatsFolder, "config.yml");
88 | YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile);
89 |
90 | // Check if the config file exists
91 | if (!config.isSet("serverUuid")) {
92 |
93 | // Add default values
94 | config.addDefault("enabled", true);
95 | // Every server gets it's unique random id.
96 | config.addDefault("serverUuid", UUID.randomUUID().toString());
97 | // Should failed request be logged?
98 | config.addDefault("logFailedRequests", false);
99 | // Should the sent data be logged?
100 | config.addDefault("logSentData", false);
101 | // Should the response text be logged?
102 | config.addDefault("logResponseStatusText", false);
103 |
104 | // Inform the server owners about bStats
105 | config.options().header(
106 | "bStats collects some data for plugin authors like how many servers are using their plugins.\n" +
107 | "To honor their work, you should not disable it.\n" +
108 | "This has nearly no effect on the server performance!\n" +
109 | "Check out https://bStats.org/ to learn more :)"
110 | ).copyDefaults(true);
111 | try {
112 | config.save(configFile);
113 | } catch (IOException ignored) {
114 | }
115 | }
116 |
117 | // Load the data
118 | enabled = config.getBoolean("enabled", true);
119 | serverUUID = config.getString("serverUuid");
120 | logFailedRequests = config.getBoolean("logFailedRequests", false);
121 | logSentData = config.getBoolean("logSentData", false);
122 | logResponseStatusText = config.getBoolean("logResponseStatusText", false);
123 |
124 | if (enabled) {
125 | boolean found = false;
126 | // Search for all other bStats Metrics classes to see if we are the first one
127 | for (Class> service : Bukkit.getServicesManager().getKnownServices()) {
128 | try {
129 | service.getField("B_STATS_VERSION"); // Our identifier :)
130 | found = true; // We aren't the first
131 | break;
132 | } catch (NoSuchFieldException ignored) {
133 | }
134 | }
135 | // Register our service
136 | Bukkit.getServicesManager().register(Metrics.class, this, plugin, ServicePriority.Normal);
137 | if (!found) {
138 | // We are the first!
139 | startSubmitting();
140 | }
141 | }
142 | }
143 |
144 | /**
145 | * Sends the data to the bStats server.
146 | *
147 | * @param plugin Any plugin. It's just used to get a logger instance.
148 | * @param data The data to send.
149 | * @throws Exception If the request failed.
150 | */
151 | private static void sendData(Plugin plugin, JsonObject data) throws Exception {
152 | if (data == null) {
153 | throw new IllegalArgumentException("Data cannot be null!");
154 | }
155 | if (Bukkit.isPrimaryThread()) {
156 | throw new IllegalAccessException("This method must not be called from the main thread!");
157 | }
158 | if (logSentData) {
159 | plugin.getLogger().info("Sending data to bStats: " + data);
160 | }
161 | HttpsURLConnection connection = (HttpsURLConnection) new URL(URL).openConnection();
162 |
163 | // Compress the data to save bandwidth
164 | byte[] compressedData = compress(data.toString());
165 |
166 | // Add headers
167 | connection.setRequestMethod("POST");
168 | connection.addRequestProperty("Accept", "application/json");
169 | connection.addRequestProperty("Connection", "close");
170 | connection.addRequestProperty("Content-Encoding", "gzip"); // We gzip our request
171 | connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length));
172 | connection.setRequestProperty("Content-Type", "application/json"); // We send our data in JSON format
173 | connection.setRequestProperty("User-Agent", "MC-Server/" + B_STATS_VERSION);
174 |
175 | // Send data
176 | connection.setDoOutput(true);
177 | try (DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream())) {
178 | outputStream.write(compressedData);
179 | }
180 |
181 | StringBuilder builder = new StringBuilder();
182 | try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
183 | String line;
184 | while ((line = bufferedReader.readLine()) != null) {
185 | builder.append(line);
186 | }
187 | }
188 |
189 | if (logResponseStatusText) {
190 | plugin.getLogger().info("Sent data to bStats and received response: " + builder);
191 | }
192 | }
193 |
194 | /**
195 | * Gzips the given String.
196 | *
197 | * @param str The string to gzip.
198 | * @return The gzipped String.
199 | * @throws IOException If the compression failed.
200 | */
201 | private static byte[] compress(final String str) throws IOException {
202 | if (str == null) {
203 | return null;
204 | }
205 | ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
206 | try (GZIPOutputStream gzip = new GZIPOutputStream(outputStream)) {
207 | gzip.write(str.getBytes(StandardCharsets.UTF_8));
208 | }
209 | return outputStream.toByteArray();
210 | }
211 |
212 | /**
213 | * Checks if bStats is enabled.
214 | *
215 | * @return Whether bStats is enabled or not.
216 | */
217 | public boolean isEnabled() {
218 | return enabled;
219 | }
220 |
221 | /**
222 | * Adds a custom chart.
223 | *
224 | * @param chart The chart to add.
225 | */
226 | public void addCustomChart(CustomChart chart) {
227 | if (chart == null) {
228 | throw new IllegalArgumentException("Chart cannot be null!");
229 | }
230 | charts.add(chart);
231 | }
232 |
233 | /**
234 | * Starts the Scheduler which submits our data every 30 minutes.
235 | */
236 | private void startSubmitting() {
237 | final Timer timer = new Timer(true); // We use a timer cause the Bukkit scheduler is affected by server lags
238 | timer.scheduleAtFixedRate(new TimerTask() {
239 | @Override
240 | public void run() {
241 | if (!plugin.isEnabled()) { // Plugin was disabled
242 | timer.cancel();
243 | return;
244 | }
245 | // Nevertheless we want our code to run in the Bukkit main thread, so we have to use the Bukkit scheduler
246 | // Don't be afraid! The connection to the bStats server is still async, only the stats collection is sync ;)
247 | Bukkit.getScheduler().runTask(plugin, () -> submitData());
248 | }
249 | }, 1000 * 60 * 5, 1000 * 60 * 30);
250 | // Submit the data every 30 minutes, first time after 5 minutes to give other plugins enough time to start
251 | // WARNING: Changing the frequency has no effect but your plugin WILL be blocked/deleted!
252 | // WARNING: Just don't do it!
253 | }
254 |
255 | /**
256 | * Gets the plugin specific data.
257 | * This method is called using Reflection.
258 | *
259 | * @return The plugin specific data.
260 | */
261 | public JsonObject getPluginData() {
262 | JsonObject data = new JsonObject();
263 |
264 | String pluginName = plugin.getDescription().getName();
265 | String pluginVersion = plugin.getDescription().getVersion();
266 |
267 | data.addProperty("pluginName", pluginName); // Append the name of the plugin
268 | data.addProperty("id", pluginId); // Append the id of the plugin
269 | data.addProperty("pluginVersion", pluginVersion); // Append the version of the plugin
270 | JsonArray customCharts = new JsonArray();
271 | for (CustomChart customChart : charts) {
272 | // Add the data of the custom charts
273 | JsonObject chart = customChart.getRequestJsonObject();
274 | if (chart == null) { // If the chart is null, we skip it
275 | continue;
276 | }
277 | customCharts.add(chart);
278 | }
279 | data.add("customCharts", customCharts);
280 |
281 | return data;
282 | }
283 |
284 | /**
285 | * Gets the server specific data.
286 | *
287 | * @return The server specific data.
288 | */
289 | private JsonObject getServerData() {
290 | // Minecraft specific data
291 | int playerAmount;
292 | try {
293 | // Around MC 1.8 the return type was changed to a collection from an array,
294 | // This fixes java.lang.NoSuchMethodError: org.bukkit.Bukkit.getOnlinePlayers()Ljava/util/Collection;
295 | Method onlinePlayersMethod = Class.forName("org.bukkit.Server").getMethod("getOnlinePlayers");
296 | playerAmount = onlinePlayersMethod.getReturnType().equals(Collection.class)
297 | ? ((Collection>) onlinePlayersMethod.invoke(Bukkit.getServer())).size()
298 | : ((Player[]) onlinePlayersMethod.invoke(Bukkit.getServer())).length;
299 | } catch (Exception e) {
300 | playerAmount = Bukkit.getOnlinePlayers().size(); // Just use the new method if the Reflection failed
301 | }
302 | int onlineMode = Bukkit.getOnlineMode() ? 1 : 0;
303 | String bukkitVersion = Bukkit.getVersion();
304 | String bukkitName = Bukkit.getName();
305 |
306 | // OS/Java specific data
307 | String javaVersion = System.getProperty("java.version");
308 | String osName = System.getProperty("os.name");
309 | String osArch = System.getProperty("os.arch");
310 | String osVersion = System.getProperty("os.version");
311 | int coreCount = Runtime.getRuntime().availableProcessors();
312 |
313 | JsonObject data = new JsonObject();
314 |
315 | data.addProperty("serverUUID", serverUUID);
316 |
317 | data.addProperty("playerAmount", playerAmount);
318 | data.addProperty("onlineMode", onlineMode);
319 | data.addProperty("bukkitVersion", bukkitVersion);
320 | data.addProperty("bukkitName", bukkitName);
321 |
322 | data.addProperty("javaVersion", javaVersion);
323 | data.addProperty("osName", osName);
324 | data.addProperty("osArch", osArch);
325 | data.addProperty("osVersion", osVersion);
326 | data.addProperty("coreCount", coreCount);
327 |
328 | return data;
329 | }
330 |
331 | /**
332 | * Collects the data and sends it afterwards.
333 | */
334 | private void submitData() {
335 | final JsonObject data = getServerData();
336 |
337 | JsonArray pluginData = new JsonArray();
338 | // Search for all other bStats Metrics classes to get their plugin data
339 | for (Class> service : Bukkit.getServicesManager().getKnownServices()) {
340 | try {
341 | service.getField("B_STATS_VERSION"); // Our identifier :)
342 |
343 | for (RegisteredServiceProvider> provider : Bukkit.getServicesManager().getRegistrations(service)) {
344 | try {
345 | Object plugin = provider.getService().getMethod("getPluginData").invoke(provider.getProvider());
346 | if (plugin instanceof JsonObject) {
347 | pluginData.add((JsonObject) plugin);
348 | } else { // old bstats version compatibility
349 | try {
350 | Class> jsonObjectJsonSimple = Class.forName("org.json.simple.JSONObject");
351 | if (plugin.getClass().isAssignableFrom(jsonObjectJsonSimple)) {
352 | Method jsonStringGetter = jsonObjectJsonSimple.getDeclaredMethod("toJSONString");
353 | jsonStringGetter.setAccessible(true);
354 | String jsonString = (String) jsonStringGetter.invoke(plugin);
355 | JsonObject object = new JsonParser().parse(jsonString).getAsJsonObject();
356 | pluginData.add(object);
357 | }
358 | } catch (ClassNotFoundException e) {
359 | // minecraft version 1.14+
360 | if (logFailedRequests) {
361 | this.plugin.getLogger().log(Level.SEVERE, "Encountered unexpected exception", e);
362 | }
363 | }
364 | }
365 | } catch (NullPointerException | NoSuchMethodException | IllegalAccessException | InvocationTargetException ignored) {
366 | }
367 | }
368 | } catch (NoSuchFieldException ignored) {
369 | }
370 | }
371 |
372 | data.add("plugins", pluginData);
373 |
374 | // Create a new thread for the connection to the bStats server
375 | new Thread(() -> {
376 | try {
377 | // Send the data
378 | sendData(plugin, data);
379 | } catch (Exception e) {
380 | // Something went wrong! :(
381 | if (logFailedRequests) {
382 | plugin.getLogger().log(Level.WARNING, "Could not submit plugin stats of " + plugin.getName(), e);
383 | }
384 | }
385 | }).start();
386 | }
387 |
388 | /**
389 | * Represents a custom chart.
390 | */
391 | public static abstract class CustomChart {
392 |
393 | // The id of the chart
394 | final String chartId;
395 |
396 | /**
397 | * Class constructor.
398 | *
399 | * @param chartId The id of the chart.
400 | */
401 | CustomChart(String chartId) {
402 | if (chartId == null || chartId.isEmpty()) {
403 | throw new IllegalArgumentException("ChartId cannot be null or empty!");
404 | }
405 | this.chartId = chartId;
406 | }
407 |
408 | private JsonObject getRequestJsonObject() {
409 | JsonObject chart = new JsonObject();
410 | chart.addProperty("chartId", chartId);
411 | try {
412 | JsonObject data = getChartData();
413 | if (data == null) {
414 | // If the data is null we don't send the chart.
415 | return null;
416 | }
417 | chart.add("data", data);
418 | } catch (Throwable t) {
419 | if (logFailedRequests) {
420 | Bukkit.getLogger().log(Level.WARNING, "Failed to get data for custom chart with id " + chartId, t);
421 | }
422 | return null;
423 | }
424 | return chart;
425 | }
426 |
427 | protected abstract JsonObject getChartData() throws Exception;
428 |
429 | }
430 |
431 | /**
432 | * Represents a custom simple pie.
433 | */
434 | public static class SimplePie extends CustomChart {
435 |
436 | private final Callable callable;
437 |
438 | /**
439 | * Class constructor.
440 | *
441 | * @param chartId The id of the chart.
442 | * @param callable The callable which is used to request the chart data.
443 | */
444 | public SimplePie(String chartId, Callable callable) {
445 | super(chartId);
446 | this.callable = callable;
447 | }
448 |
449 | @Override
450 | protected JsonObject getChartData() throws Exception {
451 | JsonObject data = new JsonObject();
452 | String value = callable.call();
453 | if (value == null || value.isEmpty()) {
454 | // Null = skip the chart
455 | return null;
456 | }
457 | data.addProperty("value", value);
458 | return data;
459 | }
460 | }
461 |
462 | /**
463 | * Represents a custom advanced pie.
464 | */
465 | public static class AdvancedPie extends CustomChart {
466 |
467 | private final Callable