Skip to content

Commit afcb946

Browse files
committed
initial commit
1 parent db7587f commit afcb946

27 files changed

Lines changed: 1960 additions & 13 deletions

File tree

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2025 Project Unified
3+
Copyright (c) 2025 Project Unified & FastStats
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

bukkit/pom.xml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<parent>
6+
<groupId>io.github.projectunified</groupId>
7+
<artifactId>faststats</artifactId>
8+
<version>1.0-SNAPSHOT</version>
9+
</parent>
10+
<modelVersion>4.0.0</modelVersion>
11+
12+
<artifactId>faststats-bukkit</artifactId>
13+
<name>FastStats Bukkit</name>
14+
15+
<repositories>
16+
<repository>
17+
<id>spigotmc-repo</id>
18+
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
19+
</repository>
20+
</repositories>
21+
22+
<dependencies>
23+
<dependency>
24+
<groupId>io.github.projectunified</groupId>
25+
<artifactId>faststats-core</artifactId>
26+
<version>${project.version}</version>
27+
</dependency>
28+
<dependency>
29+
<groupId>org.spigotmc</groupId>
30+
<artifactId>spigot-api</artifactId>
31+
<version>1.12.2-R0.1-SNAPSHOT</version>
32+
<scope>provided</scope>
33+
</dependency>
34+
</dependencies>
35+
</project>
Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
package io.github.projectunified.faststats.bukkit;
2+
3+
import io.github.projectunified.faststats.core.Config;
4+
import io.github.projectunified.faststats.core.DefaultConfig;
5+
import io.github.projectunified.faststats.core.Metric;
6+
import io.github.projectunified.faststats.core.Platform;
7+
import org.bukkit.Server;
8+
import org.bukkit.plugin.Plugin;
9+
10+
import java.io.File;
11+
import java.lang.invoke.MethodHandle;
12+
import java.lang.invoke.MethodHandles;
13+
import java.nio.file.Path;
14+
import java.util.ArrayList;
15+
import java.util.Collection;
16+
import java.util.List;
17+
import java.util.logging.Level;
18+
19+
/**
20+
* Bukkit implementation of {@link Platform}.
21+
*/
22+
public class BukkitPlatform implements Platform {
23+
private static final MethodHandle GET_PLUGINS_FOLDER;
24+
private static final MethodHandle SPIGOT;
25+
private static final MethodHandle GET_MINECRAFT_VERSION;
26+
private static final MethodHandle GET_SERVER_CONFIG;
27+
private static final MethodHandle GET_PLUGIN_META;
28+
private static final MethodHandle GET_PAPER_CONFIG;
29+
private static final MethodHandle GET_SPIGOT_CONFIG;
30+
private static final MethodHandle GET_CONFIGURATION_SECTION;
31+
private static final MethodHandle GET_BOOLEAN;
32+
33+
static {
34+
MethodHandles.Lookup lookup = MethodHandles.publicLookup();
35+
MethodHandle getPluginsFolder = null;
36+
MethodHandle spigot = null;
37+
MethodHandle getMinecraftVersion = null;
38+
MethodHandle getServerConfig = null;
39+
MethodHandle getPluginMeta = null;
40+
MethodHandle getPaperConfig = null;
41+
MethodHandle getSpigotConfig = null;
42+
MethodHandle getConfigurationSection = null;
43+
MethodHandle getBoolean = null;
44+
45+
try {
46+
getPluginsFolder = lookup.unreflect(Server.class.getMethod("getPluginsFolder"));
47+
} catch (Throwable ignored) {
48+
}
49+
50+
try {
51+
spigot = lookup.unreflect(Server.class.getMethod("spigot"));
52+
} catch (Throwable ignored) {
53+
}
54+
55+
try {
56+
getMinecraftVersion = lookup.unreflect(Server.class.getMethod("getMinecraftVersion"));
57+
} catch (Throwable ignored) {
58+
}
59+
60+
try {
61+
getServerConfig = lookup.unreflect(Server.class.getMethod("getServerConfig"));
62+
} catch (Throwable ignored) {
63+
}
64+
65+
try {
66+
getPluginMeta = lookup.unreflect(Plugin.class.getMethod("getPluginMeta"));
67+
} catch (Throwable ignored) {
68+
}
69+
70+
try {
71+
Class<?> configSectionClass = Class.forName("org.bukkit.configuration.ConfigurationSection");
72+
getConfigurationSection = lookup.unreflect(configSectionClass.getMethod("getConfigurationSection", String.class));
73+
getBoolean = lookup.unreflect(configSectionClass.getMethod("getBoolean", String.class));
74+
} catch (Throwable ignored) {
75+
}
76+
77+
try {
78+
Class<?> spigotClass = Class.forName("org.bukkit.Server$Spigot");
79+
getPaperConfig = lookup.unreflect(spigotClass.getMethod("getPaperConfig"));
80+
getSpigotConfig = lookup.unreflect(spigotClass.getMethod("getSpigotConfig"));
81+
} catch (Throwable ignored) {
82+
}
83+
84+
GET_PLUGINS_FOLDER = getPluginsFolder;
85+
SPIGOT = spigot;
86+
GET_MINECRAFT_VERSION = getMinecraftVersion;
87+
GET_SERVER_CONFIG = getServerConfig;
88+
GET_PLUGIN_META = getPluginMeta;
89+
GET_PAPER_CONFIG = getPaperConfig;
90+
GET_SPIGOT_CONFIG = getSpigotConfig;
91+
GET_CONFIGURATION_SECTION = getConfigurationSection;
92+
GET_BOOLEAN = getBoolean;
93+
}
94+
95+
private final Plugin plugin;
96+
private final Config config;
97+
private final List<Metric<?>> defaultMetrics;
98+
99+
/**
100+
* Constructs a new {@link BukkitPlatform} for the given plugin.
101+
*
102+
* @param plugin the plugin instance
103+
*/
104+
public BukkitPlatform(Plugin plugin) {
105+
this.plugin = plugin;
106+
107+
File pluginsFolder = null;
108+
try {
109+
if (GET_PLUGINS_FOLDER != null) {
110+
pluginsFolder = (File) GET_PLUGINS_FOLDER.invoke(plugin.getServer());
111+
}
112+
} catch (Throwable ignored) {
113+
}
114+
115+
if (pluginsFolder == null) {
116+
pluginsFolder = plugin.getDataFolder().getParentFile();
117+
}
118+
119+
Path configPath = pluginsFolder.toPath().resolve("faststats").resolve("config.properties");
120+
this.config = DefaultConfig.read(configPath);
121+
122+
this.defaultMetrics = new ArrayList<>();
123+
setupDefaultMetrics();
124+
}
125+
126+
private void setupDefaultMetrics() {
127+
final Server server = plugin.getServer();
128+
129+
// Minecraft Version
130+
defaultMetrics.add(Metric.string("minecraft_version", () -> {
131+
try {
132+
if (GET_MINECRAFT_VERSION != null) {
133+
return (String) GET_MINECRAFT_VERSION.invoke(server);
134+
}
135+
} catch (Throwable ignored) {
136+
}
137+
138+
try {
139+
return server.getBukkitVersion().split("-", 2)[0];
140+
} catch (Exception ex) {
141+
return server.getVersion().split("\\(MC: |\\)", 3)[1];
142+
}
143+
}));
144+
145+
// Online Mode
146+
defaultMetrics.add(Metric.bool("online_mode", () -> {
147+
try {
148+
if (GET_SERVER_CONFIG != null) {
149+
Object serverConfig = GET_SERVER_CONFIG.invoke(server);
150+
MethodHandle isProxyOnlineMode = MethodHandles.publicLookup()
151+
.unreflect(serverConfig.getClass().getMethod("isProxyOnlineMode"));
152+
return (Boolean) isProxyOnlineMode.invoke(serverConfig);
153+
}
154+
} catch (Throwable ignored) {
155+
}
156+
157+
try {
158+
return isProxyOnlineMode();
159+
} catch (Exception ex) {
160+
return server.getOnlineMode();
161+
}
162+
}));
163+
164+
// Player Count
165+
defaultMetrics.add(Metric.number("player_count", () -> {
166+
try {
167+
return server.getOnlinePlayers().size();
168+
} catch (Throwable t) {
169+
return 0;
170+
}
171+
}));
172+
173+
// Plugin Version
174+
defaultMetrics.add(Metric.string("plugin_version", () -> {
175+
try {
176+
if (GET_PLUGIN_META != null) {
177+
Object pluginMeta = GET_PLUGIN_META.invoke(plugin);
178+
MethodHandle getVersion = MethodHandles.publicLookup()
179+
.unreflect(pluginMeta.getClass().getMethod("getVersion"));
180+
return (String) getVersion.invoke(pluginMeta);
181+
}
182+
} catch (Throwable ignored) {
183+
}
184+
185+
return plugin.getDescription().getVersion();
186+
}));
187+
188+
// Server Type
189+
defaultMetrics.add(Metric.string("server_type", server::getName));
190+
}
191+
192+
private boolean isProxyOnlineMode() {
193+
if (SPIGOT == null) {
194+
return false;
195+
}
196+
197+
Server server = plugin.getServer();
198+
Object spigot;
199+
try {
200+
spigot = SPIGOT.invoke(server);
201+
} catch (Throwable e) {
202+
return false;
203+
}
204+
205+
Object proxies = null;
206+
try {
207+
if (GET_PAPER_CONFIG != null && GET_CONFIGURATION_SECTION != null && GET_BOOLEAN != null) {
208+
Object paperConfig = GET_PAPER_CONFIG.invoke(spigot);
209+
proxies = GET_CONFIGURATION_SECTION.invoke(paperConfig, "proxies");
210+
if (proxies != null) {
211+
boolean velocityEnabled = (boolean) GET_BOOLEAN.invoke(proxies, "velocity.enabled");
212+
boolean velocityOnline = (boolean) GET_BOOLEAN.invoke(proxies, "velocity.online-mode");
213+
if (velocityEnabled && velocityOnline) {
214+
return true;
215+
}
216+
}
217+
}
218+
} catch (Throwable ignored) {
219+
}
220+
221+
try {
222+
if (GET_SPIGOT_CONFIG != null && GET_CONFIGURATION_SECTION != null && GET_BOOLEAN != null) {
223+
Object spigotConfig = GET_SPIGOT_CONFIG.invoke(spigot);
224+
Object settings = GET_CONFIGURATION_SECTION.invoke(spigotConfig, "settings");
225+
if (settings != null) {
226+
boolean bungee = (boolean) GET_BOOLEAN.invoke(settings, "bungeecord");
227+
if (bungee && proxies != null) {
228+
return (boolean) GET_BOOLEAN.invoke(proxies, "bungee-cord.online-mode");
229+
}
230+
}
231+
}
232+
} catch (Throwable ignored) {
233+
}
234+
235+
return false;
236+
}
237+
238+
@Override
239+
public Config getConfig() {
240+
return config;
241+
}
242+
243+
@Override
244+
public Collection<Metric<?>> getMetrics() {
245+
return defaultMetrics;
246+
}
247+
248+
@Override
249+
public void logInfo(String message) {
250+
plugin.getLogger().info(message);
251+
}
252+
253+
@Override
254+
public void logWarning(String message) {
255+
plugin.getLogger().warning(message);
256+
}
257+
258+
@Override
259+
public void logError(String message, Throwable throwable) {
260+
plugin.getLogger().log(Level.SEVERE, message, throwable);
261+
}
262+
}

core/pom.xml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
<parent>
7+
<groupId>io.github.projectunified</groupId>
8+
<artifactId>faststats</artifactId>
9+
<version>1.0-SNAPSHOT</version>
10+
</parent>
11+
12+
<artifactId>faststats-core</artifactId>
13+
<name>FastStats Core</name>
14+
</project>
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package io.github.projectunified.faststats.core;
2+
3+
import java.util.UUID;
4+
5+
/**
6+
* Interface representing the configuration settings for metrics collection.
7+
*/
8+
public interface Config {
9+
/**
10+
* Retrieves the unique identifier of the server.
11+
*
12+
* @return the server identifier UUID
13+
*/
14+
UUID getServerId();
15+
16+
/**
17+
* Checks if metrics submission is enabled.
18+
*
19+
* @return true if enabled, false otherwise
20+
*/
21+
boolean isEnabled();
22+
23+
/**
24+
* Checks if additional/custom metrics are enabled for submission.
25+
*
26+
* @return true if enabled, false otherwise
27+
*/
28+
boolean isSubmitAdditionalMetrics();
29+
30+
/**
31+
* Checks if debug logging is enabled.
32+
*
33+
* @return true if enabled, false otherwise
34+
*/
35+
boolean isDebug();
36+
37+
/**
38+
* Checks if this is the first time the metrics are running.
39+
*
40+
* @return true if first run, false otherwise
41+
*/
42+
default boolean isFirstRun() {
43+
return false;
44+
}
45+
46+
/**
47+
* Checks if the configuration is externally managed.
48+
*
49+
* @return true if externally managed, false otherwise
50+
*/
51+
default boolean isExternallyManaged() {
52+
return false;
53+
}
54+
}

0 commit comments

Comments
 (0)