Skip to content

Commit 6446b03

Browse files
committed
Squashed commit of the following:
commit 3a0fa34 Author: Bunny <mia@secvers.org> Date: Thu Sep 25 21:08:38 2025 +0200 Update Telemetry.java commit 74b08af Author: Bunny <mia@secvers.org> Date: Thu Sep 25 21:08:27 2025 +0200 Update Telemetry.java URL commit 57344e2 Author: cjCedddCHe3 <mia@secvers.org> Date: Thu Sep 25 21:01:51 2025 +0200 Update Update to 1.1 for the new Website
1 parent b74ef08 commit 6446b03

File tree

7 files changed

+417
-4
lines changed

7 files changed

+417
-4
lines changed

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,3 @@
2323
hs_err_pid*
2424
replay_pid*
2525
/.idea
26-
/target

README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
<<<<<<< Updated upstream
2+
# EssentailsXMySQL
3+
A Plugin to Sync EssentailsX Home and Money with the Database
4+
=======
15
# EssentialsX MySQL Sync
26

37
Seamlessly synchronize your EssentialsX player data with a MySQL database in real-time, fully configurable and robust.
48

5-
6-
79
---
810

911
## Key Features
@@ -44,3 +46,4 @@ mysql:
4446
# MySQL or MariaDB user credentials
4547
user: "minecraft"
4648
password: "yourPassword"
49+
>>>>>>> Stashed changes
Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
package org.secverse.secVersEssentialsXMySQLConnector.SecVersCom;
2+
3+
import org.bukkit.Bukkit;
4+
import org.bukkit.plugin.Plugin;
5+
import org.bukkit.scheduler.BukkitRunnable;
6+
7+
import java.io.*;
8+
import java.net.HttpURLConnection;
9+
import java.net.URL;
10+
import java.nio.charset.StandardCharsets;
11+
import java.time.Instant;
12+
import java.util.HashMap;
13+
import java.util.Map;
14+
import java.util.UUID;
15+
16+
/**
17+
* Telemetry helper for a Bukkit/Spigot/Paper plugin.
18+
*
19+
* Responsibilities:
20+
* - Generate and persist a unique HWID per plugin installation.
21+
* - Provide server name and basic metadata.
22+
* - Build telemetry JSON payloads.
23+
* - Send telemetry POST requests asynchronously using Bukkit scheduler.
24+
*
25+
* Note: This class keeps network I/O off the main thread by using Bukkit's scheduler.
26+
* Telemetry is opt-in/opt-out via the plugin config (telemetry.enabled).
27+
*/
28+
public class Telemetry {
29+
30+
private static final String HWID_FILENAME = "hwid.txt";
31+
private final Plugin plugin;
32+
private final File dataFolder;
33+
private final UUID hwid;
34+
private final String endpoint;
35+
private final boolean enabled;
36+
37+
/**
38+
* Initialize Telemetry for a plugin. Will create the plugin data folder if missing
39+
* and persist a generated HWID in hwid.txt.
40+
*
41+
* @param plugin your main plugin instance (for scheduler & data folder)
42+
*/
43+
public Telemetry(Plugin plugin) {
44+
this.plugin = plugin;
45+
this.dataFolder = plugin.getDataFolder();
46+
if (!dataFolder.exists()) {
47+
boolean created = dataFolder.mkdirs();
48+
if (!created) {
49+
plugin.getLogger().warning("Could not create plugin data folder for telemetry persistence.");
50+
}
51+
}
52+
53+
// load config values (fall back to safe defaults)
54+
this.enabled = plugin.getConfig().getBoolean("telemetry.enabled", true);
55+
this.endpoint = plugin.getConfig().getString("telemetry.endpoint", "").trim();
56+
57+
// load or generate HWID
58+
this.hwid = loadOrCreateHwid();
59+
}
60+
61+
/**
62+
* Returns the persisted HWID for this plugin installation.
63+
*
64+
* @return UUID representing the installation HWID.
65+
*/
66+
public UUID getHwid() {
67+
return hwid;
68+
}
69+
70+
/**
71+
* Returns the server name as reported by Bukkit. If that is empty or generic,
72+
* additional heuristics could be added.
73+
*
74+
* @return server name string
75+
*/
76+
public String getServerName() {
77+
String name = Bukkit.getServer().getName();
78+
if (name == null || name.isEmpty()) {
79+
// fallback: try to read server.properties server-name variants or return "unknown"
80+
return "unknown";
81+
}
82+
return name;
83+
}
84+
85+
/**
86+
* Build a simple telemetry payload. Extend the map for custom fields.
87+
*
88+
* @param additional optional extra key/value pairs to include
89+
* @return JSON string representing the payload
90+
*/
91+
public String buildPayload(Map<String, Object> additional) {
92+
Map<String, Object> payload = new HashMap<>();
93+
payload.put("hwid", hwid.toString());
94+
payload.put("serverName", getServerName());
95+
payload.put("pluginName", plugin.getDescription().getName());
96+
payload.put("pluginVersion", plugin.getDescription().getVersion());
97+
payload.put("timestamp", Instant.now().toString());
98+
99+
if (additional != null) {
100+
payload.putAll(additional);
101+
}
102+
103+
return toJson(payload);
104+
}
105+
106+
/**
107+
* Send telemetry to configured endpoint asynchronously.
108+
* If telemetry is disabled or endpoint missing, the call returns without network IO.
109+
*
110+
* @param additional optional extra fields to include in the payload
111+
*/
112+
public void sendTelemetryAsync(Map<String, Object> additional) {
113+
if (!enabled) {
114+
plugin.getLogger().info("Telemetry disabled in config; skipping send.");
115+
return;
116+
}
117+
if (endpoint == null || endpoint.isEmpty()) {
118+
plugin.getLogger().warning("Telemetry endpoint not configured; skipping send.");
119+
return;
120+
}
121+
122+
final String payload = buildPayload(additional);
123+
124+
125+
new BukkitRunnable() {
126+
@Override
127+
public void run() {
128+
try {
129+
sendPost(payload);
130+
} catch (Exception e) {
131+
plugin.getLogger().warning("Failed to send telemetry: " + e.getMessage());
132+
}
133+
}
134+
}.runTaskAsynchronously(plugin);
135+
}
136+
137+
/**
138+
* Sends a JSON payload to a URL using POST.
139+
*
140+
* @param jsonPayload the JSON string
141+
* @throws IOException on network or IO errors
142+
*/
143+
private void sendPost(String jsonPayload) throws IOException {
144+
URL url = new URL("https://api.secvers.org/v1/telemetry/EssentailsXMySQL");
145+
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
146+
try {
147+
conn.setRequestMethod("POST");
148+
conn.setDoOutput(true);
149+
conn.setRequestProperty("Content-Type", "application/json; charset=utf-8");
150+
conn.setConnectTimeout(8000);
151+
conn.setReadTimeout(8000);
152+
153+
byte[] out = jsonPayload.getBytes(StandardCharsets.UTF_8);
154+
conn.setFixedLengthStreamingMode(out.length);
155+
conn.connect();
156+
157+
try (OutputStream os = conn.getOutputStream()) {
158+
os.write(out);
159+
}
160+
161+
int status = conn.getResponseCode();
162+
if (status < 200 || status >= 300) {
163+
// read error stream for debugging
164+
String err = readStream(conn.getErrorStream());
165+
plugin.getLogger().warning("Telemetry server returned HTTP " + status + ": " + err);
166+
} else {
167+
plugin.getLogger().fine("Telemetry sent successfully.");
168+
}
169+
} finally {
170+
conn.disconnect();
171+
}
172+
}
173+
174+
/**
175+
* Convert a Map to a minimally escaped JSON string. This avoids external libs.
176+
* This simple serializer supports string/number/boolean values and nested maps.
177+
*
178+
* Note: For complex payloads, prefer a proper JSON library (Gson/Jackson).
179+
*
180+
* @param map data map
181+
* @return JSON string
182+
*/
183+
private static String toJson(Map<String, Object> map) {
184+
StringBuilder sb = new StringBuilder();
185+
sb.append("{");
186+
boolean first = true;
187+
for (Map.Entry<String, Object> e : map.entrySet()) {
188+
if (!first) sb.append(",");
189+
first = false;
190+
sb.append("\"").append(escapeJson(e.getKey())).append("\":");
191+
sb.append(valueToJson(e.getValue()));
192+
}
193+
sb.append("}");
194+
return sb.toString();
195+
}
196+
197+
private static String valueToJson(Object val) {
198+
if (val == null) return "null";
199+
if (val instanceof Number || val instanceof Boolean) return val.toString();
200+
if (val instanceof Map) return toJson((Map<String, Object>) val);
201+
return "\"" + escapeJson(val.toString()) + "\"";
202+
}
203+
204+
private static String escapeJson(String s) {
205+
return s.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n").replace("\r", "\\r");
206+
}
207+
208+
private static String readStream(InputStream is) {
209+
if (is == null) return "";
210+
try (BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
211+
StringBuilder out = new StringBuilder();
212+
String line;
213+
while ((line = br.readLine()) != null) {
214+
out.append(line);
215+
}
216+
return out.toString();
217+
} catch (IOException e) {
218+
return "";
219+
}
220+
}
221+
222+
/**
223+
* Try to load an existing HWID from hwid.txt or create a new UUID and persist it.
224+
*
225+
* @return UUID used as hwid
226+
*/
227+
private UUID loadOrCreateHwid() {
228+
File file = new File(dataFolder, HWID_FILENAME);
229+
if (file.exists()) {
230+
try (BufferedReader br = new BufferedReader(new FileReader(file))) {
231+
String line = br.readLine();
232+
if (line != null && !line.trim().isEmpty()) {
233+
try {
234+
return UUID.fromString(line.trim());
235+
} catch (IllegalArgumentException ignore) {
236+
// fall through -> regenerate
237+
}
238+
}
239+
} catch (IOException ignored) {
240+
// ignore and generate new
241+
}
242+
}
243+
244+
UUID newId = UUID.randomUUID();
245+
try (BufferedWriter bw = new BufferedWriter(new FileWriter(file))) {
246+
bw.write(newId.toString());
247+
} catch (IOException e) {
248+
plugin.getLogger().warning("Failed to persist hwid to file: " + e.getMessage());
249+
}
250+
return newId;
251+
}
252+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package org.secverse.secVersEssentialsXMySQLConnector.SecVersCom;
2+
3+
4+
import org.bukkit.Bukkit;
5+
import org.bukkit.Server;
6+
import org.bukkit.plugin.Plugin;
7+
import org.bukkit.scheduler.BukkitRunnable;
8+
9+
import java.io.BufferedReader;
10+
import java.io.InputStreamReader;
11+
import java.net.HttpURLConnection;
12+
import java.net.URL;
13+
import java.nio.charset.StandardCharsets;
14+
import java.util.Objects;
15+
import java.util.regex.Matcher;
16+
import java.util.regex.Pattern;
17+
18+
/**
19+
* UpdateChecker that fetches latest version from hardcoded secvers API.
20+
*/
21+
public final class UpdateChecker {
22+
23+
// Hardcoded constants
24+
private static final String ENDPOINT_URL = "https://api.secvers.org/v1/plugin/EssentailsXMySQL";
25+
private static final String DOWNLOAD_URL = "https://secvers.org/";
26+
private static final Pattern SIMPLE_JSON_VERSION = Pattern.compile("\"version\"\\s*:\\s*\"([^\"]+)\"");
27+
28+
private final Plugin plugin;
29+
30+
public UpdateChecker(Plugin plugin) {
31+
this.plugin = Objects.requireNonNull(plugin, "plugin");
32+
}
33+
34+
/**
35+
* Run a single async check for update.
36+
*/
37+
public void checkNowAsync() {
38+
new BukkitRunnable() {
39+
@Override
40+
public void run() {
41+
try {
42+
String remoteVersion = fetchRemoteVersion(ENDPOINT_URL);
43+
if (remoteVersion == null || remoteVersion.isEmpty()) {
44+
plugin.getLogger().warning("[Update] Could not parse remote version.");
45+
return;
46+
}
47+
48+
String localVersion = plugin.getDescription().getVersion();
49+
if (isOutdated(localVersion, remoteVersion)) {
50+
broadcastUpdate(remoteVersion, localVersion);
51+
} else {
52+
plugin.getLogger().info("[Update] Plugin is up-to-date. Local " + localVersion + ", Remote " + remoteVersion);
53+
}
54+
} catch (Exception ex) {
55+
plugin.getLogger().warning("[Update] Failed to check updates: " + ex.getMessage());
56+
}
57+
}
58+
}.runTaskAsynchronously(plugin);
59+
}
60+
61+
private String fetchRemoteVersion(String url) throws Exception {
62+
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
63+
conn.setConnectTimeout(6000);
64+
conn.setReadTimeout(6000);
65+
conn.setRequestMethod("GET");
66+
conn.setRequestProperty("Accept", "application/json");
67+
68+
int code = conn.getResponseCode();
69+
if (code != 200) throw new IllegalStateException("HTTP " + code);
70+
71+
try (BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {
72+
StringBuilder sb = new StringBuilder();
73+
String ln;
74+
while ((ln = br.readLine()) != null) sb.append(ln);
75+
String json = sb.toString();
76+
Matcher m = SIMPLE_JSON_VERSION.matcher(json);
77+
if (m.find()) return m.group(1).trim();
78+
return null;
79+
} finally {
80+
conn.disconnect();
81+
}
82+
}
83+
84+
public static boolean isOutdated(String local, String remote) {
85+
return !normalize(local).equals(normalize(remote));
86+
}
87+
88+
private static String normalize(String v) {
89+
if (v == null) return "0";
90+
String core = v.split("[+-]")[0];
91+
core = core.replaceAll("[^0-9.]", "");
92+
return core.isEmpty() ? "0" : core;
93+
}
94+
95+
private void broadcastUpdate(String remoteVersion, String localVersion) {
96+
Server server = Bukkit.getServer();
97+
String legacy = "§c§l[SecVers] New Update available: " + remoteVersion
98+
+ " (current " + localVersion + "). Download: " + DOWNLOAD_URL;
99+
Bukkit.broadcastMessage(legacy);
100+
server.getConsoleSender().sendMessage(legacy);
101+
}
102+
}

0 commit comments

Comments
 (0)