PlayTimes-OG v1.0

This commit is contained in:
NotAlexNoyle committed 2024-07-04 21:33:28 -04:00
1 parent f01ec9a0d9
commit 8acf6ee912
12 files changed
+925 -1862

No files matched your search

@@ -1,982 +0,0 @@
/*
* This Metrics class was auto-generated and can be copied into your project if you are
* not using a build tool like Gradle or Maven for dependency management.
*
* IMPORTANT: You are not allowed to modify this class, except changing the package.
*
* Disallowed modifications include but are not limited to:
* - Remove the option for users to opt-out
* - Change the frequency for data submission
* - Obfuscate the code (every obfuscator should allow you to make an exception for specific files)
* - Reformat the code (if you use a linter, add an exception)
*
* Violations will result in a ban of your plugin and account from bStats.
*/
package me.codedred.playtimes;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.stream.Collectors;
import java.util.zip.GZIPOutputStream;
import javax.net.ssl.HttpsURLConnection;
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin;
public class Metrics {
private final Plugin plugin;
private final MetricsBase metricsBase;
/**
* Creates a new Metrics instance.
*
* @param plugin Your plugin instance.
* @param serviceId The id of the service. It can be found at <a
* href="https://bstats.org/what-is-my-plugin-id">What is my plugin id?</a>
*/
public Metrics(JavaPlugin plugin, int serviceId) {
this.plugin = plugin;
// Get the config file
File bStatsFolder = new File(
plugin.getDataFolder().getParentFile(),
"bStats"
);
File configFile = new File(bStatsFolder, "config.yml");
YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile);
if (!config.isSet("serverUuid")) {
config.addDefault("enabled", true);
config.addDefault("serverUuid", UUID.randomUUID().toString());
config.addDefault("logFailedRequests", false);
config.addDefault("logSentData", false);
config.addDefault("logResponseStatusText", false);
// Inform the server owners about bStats
config
.options()
.header(
"bStats (https://bStats.org) collects some basic information for plugin authors, like how\n" +
"many people use their plugin and their total player count. It's recommended to keep bStats\n" +
"enabled, but if you're not comfortable with this, you can turn this setting off. There is no\n" +
"performance penalty associated with having metrics enabled, and data sent to bStats is fully\n" +
"anonymous."
)
.copyDefaults(true);
try {
config.save(configFile);
} catch (IOException ignored) {}
}
// Load the data
boolean enabled = config.getBoolean("enabled", true);
String serverUUID = config.getString("serverUuid");
boolean logErrors = config.getBoolean("logFailedRequests", false);
boolean logSentData = config.getBoolean("logSentData", false);
boolean logResponseStatusText = config.getBoolean(
"logResponseStatusText",
false
);
metricsBase =
new MetricsBase(
"bukkit",
serverUUID,
serviceId,
enabled,
this::appendPlatformData,
this::appendServiceData,
submitDataTask -> Bukkit.getScheduler().runTask(plugin, submitDataTask),
plugin::isEnabled,
(message, error) ->
this.plugin.getLogger().log(Level.WARNING, message, error),
message -> this.plugin.getLogger().log(Level.INFO, message),
logErrors,
logSentData,
logResponseStatusText
);
}
/** Shuts down the underlying scheduler service. */
public void shutdown() {
metricsBase.shutdown();
}
/**
* Adds a custom chart.
*
* @param chart The chart to add.
*/
public void addCustomChart(CustomChart chart) {
metricsBase.addCustomChart(chart);
}
private void appendPlatformData(JsonObjectBuilder builder) {
builder.appendField("playerAmount", getPlayerAmount());
builder.appendField("onlineMode", Bukkit.getOnlineMode() ? 1 : 0);
builder.appendField("bukkitVersion", Bukkit.getVersion());
builder.appendField("bukkitName", Bukkit.getName());
builder.appendField("javaVersion", System.getProperty("java.version"));
builder.appendField("osName", System.getProperty("os.name"));
builder.appendField("osArch", System.getProperty("os.arch"));
builder.appendField("osVersion", System.getProperty("os.version"));
builder.appendField(
"coreCount",
Runtime.getRuntime().availableProcessors()
);
}
private void appendServiceData(JsonObjectBuilder builder) {
builder.appendField("pluginVersion", plugin.getDescription().getVersion());
}
private int getPlayerAmount() {
try {
// Around MC 1.8 the return type was changed from an array to a collection,
// This fixes java.lang.NoSuchMethodError:
// org.bukkit.Bukkit.getOnlinePlayers()Ljava/util/Collection;
Method onlinePlayersMethod = Class
.forName("org.bukkit.Server")
.getMethod("getOnlinePlayers");
return onlinePlayersMethod.getReturnType().equals(Collection.class)
? (
(Collection<?>) onlinePlayersMethod.invoke(Bukkit.getServer())
).size()
: ((Player[]) onlinePlayersMethod.invoke(Bukkit.getServer())).length;
} catch (Exception e) {
// Just use the new method if the reflection failed
return Bukkit.getOnlinePlayers().size();
}
}
public static class MetricsBase {
/** The version of the Metrics class. */
public static final String METRICS_VERSION = "3.0.2";
private static final String REPORT_URL =
"https://bStats.org/api/v2/data/%s";
private final ScheduledExecutorService scheduler;
private final String platform;
private final String serverUuid;
private final int serviceId;
private final Consumer<JsonObjectBuilder> appendPlatformDataConsumer;
private final Consumer<JsonObjectBuilder> appendServiceDataConsumer;
private final Consumer<Runnable> submitTaskConsumer;
private final Supplier<Boolean> checkServiceEnabledSupplier;
private final BiConsumer<String, Throwable> errorLogger;
private final Consumer<String> infoLogger;
private final boolean logErrors;
private final boolean logSentData;
private final boolean logResponseStatusText;
private final Set<CustomChart> customCharts = new HashSet<>();
private final boolean enabled;
/**
* Creates a new MetricsBase class instance.
*
* @param platform The platform of the service.
* @param serviceId The id of the service.
* @param serverUuid The server uuid.
* @param enabled Whether or not data sending is enabled.
* @param appendPlatformDataConsumer A consumer that receives a {@code JsonObjectBuilder} and
* appends all platform-specific data.
* @param appendServiceDataConsumer A consumer that receives a {@code JsonObjectBuilder} and
* appends all service-specific data.
* @param submitTaskConsumer A consumer that takes a runnable with the submit task. This can be
* used to delegate the data collection to a another thread to prevent errors caused by
* concurrency. Can be {@code null}.
* @param checkServiceEnabledSupplier A supplier to check if the service is still enabled.
* @param errorLogger A consumer that accepts log message and an error.
* @param infoLogger A consumer that accepts info log messages.
* @param logErrors Whether or not errors should be logged.
* @param logSentData Whether or not the sent data should be logged.
* @param logResponseStatusText Whether or not the response status text should be logged.
*/
public MetricsBase(
String platform,
String serverUuid,
int serviceId,
boolean enabled,
Consumer<JsonObjectBuilder> appendPlatformDataConsumer,
Consumer<JsonObjectBuilder> appendServiceDataConsumer,
Consumer<Runnable> submitTaskConsumer,
Supplier<Boolean> checkServiceEnabledSupplier,
BiConsumer<String, Throwable> errorLogger,
Consumer<String> infoLogger,
boolean logErrors,
boolean logSentData,
boolean logResponseStatusText
) {
ScheduledThreadPoolExecutor scheduler = new ScheduledThreadPoolExecutor(
1,
task -> new Thread(task, "bStats-Metrics")
);
// We want delayed tasks (non-periodic) that will execute in the future to be
// cancelled when the scheduler is shutdown.
// Otherwise, we risk preventing the server from shutting down even when
// MetricsBase#shutdown() is called
scheduler.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
this.scheduler = scheduler;
this.platform = platform;
this.serverUuid = serverUuid;
this.serviceId = serviceId;
this.enabled = enabled;
this.appendPlatformDataConsumer = appendPlatformDataConsumer;
this.appendServiceDataConsumer = appendServiceDataConsumer;
this.submitTaskConsumer = submitTaskConsumer;
this.checkServiceEnabledSupplier = checkServiceEnabledSupplier;
this.errorLogger = errorLogger;
this.infoLogger = infoLogger;
this.logErrors = logErrors;
this.logSentData = logSentData;
this.logResponseStatusText = logResponseStatusText;
checkRelocation();
if (enabled) {
// WARNING: Removing the option to opt-out will get your plugin banned from
// bStats
startSubmitting();
}
}
public void addCustomChart(CustomChart chart) {
this.customCharts.add(chart);
}
public void shutdown() {
scheduler.shutdown();
}
private void startSubmitting() {
final Runnable submitTask = () -> {
if (!enabled || !checkServiceEnabledSupplier.get()) {
// Submitting data or service is disabled
scheduler.shutdown();
return;
}
if (submitTaskConsumer != null) {
submitTaskConsumer.accept(this::submitData);
} else {
this.submitData();
}
};
// Many servers tend to restart at a fixed time at xx:00 which causes an uneven
// distribution of requests on the
// bStats backend. To circumvent this problem, we introduce some randomness into
// the initial and second delay.
// WARNING: You must not modify and part of this Metrics class, including the
// submit delay or frequency!
// WARNING: Modifying this code will get your plugin banned on bStats. Just
// don't do it!
long initialDelay = (long) (1000 * 60 * (3 + Math.random() * 3));
long secondDelay = (long) (1000 * 60 * (Math.random() * 30));
scheduler.schedule(submitTask, initialDelay, TimeUnit.MILLISECONDS);
scheduler.scheduleAtFixedRate(
submitTask,
initialDelay + secondDelay,
1000 * 60 * 30,
TimeUnit.MILLISECONDS
);
}
private void submitData() {
final JsonObjectBuilder baseJsonBuilder = new JsonObjectBuilder();
appendPlatformDataConsumer.accept(baseJsonBuilder);
final JsonObjectBuilder serviceJsonBuilder = new JsonObjectBuilder();
appendServiceDataConsumer.accept(serviceJsonBuilder);
JsonObjectBuilder.JsonObject[] chartData = customCharts
.stream()
.map(customChart ->
customChart.getRequestJsonObject(errorLogger, logErrors)
)
.filter(Objects::nonNull)
.toArray(JsonObjectBuilder.JsonObject[]::new);
serviceJsonBuilder.appendField("id", serviceId);
serviceJsonBuilder.appendField("customCharts", chartData);
baseJsonBuilder.appendField("service", serviceJsonBuilder.build());
baseJsonBuilder.appendField("serverUUID", serverUuid);
baseJsonBuilder.appendField("metricsVersion", METRICS_VERSION);
JsonObjectBuilder.JsonObject data = baseJsonBuilder.build();
scheduler.execute(() -> {
try {
// Send the data
sendData(data);
} catch (Exception e) {
// Something went wrong! :(
if (logErrors) {
errorLogger.accept("Could not submit bStats metrics data", e);
}
}
});
}
private void sendData(JsonObjectBuilder.JsonObject data) throws Exception {
if (logSentData) {
infoLogger.accept("Sent bStats metrics data: " + data.toString());
}
String url = String.format(REPORT_URL, platform);
HttpsURLConnection connection = (HttpsURLConnection) new URL(url)
.openConnection();
// Compress the data to save bandwidth
byte[] compressedData = compress(data.toString());
connection.setRequestMethod("POST");
connection.addRequestProperty("Accept", "application/json");
connection.addRequestProperty("Connection", "close");
connection.addRequestProperty("Content-Encoding", "gzip");
connection.addRequestProperty(
"Content-Length",
String.valueOf(compressedData.length)
);
connection.setRequestProperty("Content-Type", "application/json");
connection.setRequestProperty("User-Agent", "Metrics-Service/1");
connection.setDoOutput(true);
try (
DataOutputStream outputStream = new DataOutputStream(
connection.getOutputStream()
)
) {
outputStream.write(compressedData);
}
StringBuilder builder = new StringBuilder();
try (
BufferedReader bufferedReader = new BufferedReader(
new InputStreamReader(connection.getInputStream())
)
) {
String line;
while ((line = bufferedReader.readLine()) != null) {
builder.append(line);
}
}
if (logResponseStatusText) {
infoLogger.accept(
"Sent data to bStats and received response: " + builder
);
}
}
/** Checks that the class was properly relocated. */
private void checkRelocation() {
// You can use the property to disable the check in your test environment
if (
System.getProperty("bstats.relocatecheck") == null ||
!System.getProperty("bstats.relocatecheck").equals("false")
) {
// Maven's Relocate is clever and changes strings, too. So we have to use this
// little "trick" ... :D
final String defaultPackage = new String(
new byte[] { 'o', 'r', 'g', '.', 'b', 's', 't', 'a', 't', 's' }
);
final String examplePackage = new String(
new byte[] {
'y',
'o',
'u',
'r',
'.',
'p',
'a',
'c',
'k',
'a',
'g',
'e',
}
);
// We want to make sure no one just copy & pastes the example and uses the wrong
// package names
if (
MetricsBase.class.getPackage().getName().startsWith(defaultPackage) ||
MetricsBase.class.getPackage().getName().startsWith(examplePackage)
) {
throw new IllegalStateException(
"bStats Metrics class has not been relocated correctly!"
);
}
}
}
/**
* Gzips the given string.
*
* @param str The string to gzip.
* @return The gzipped string.
*/
private static byte[] compress(final String str) throws IOException {
if (str == null) {
return null;
}
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try (GZIPOutputStream gzip = new GZIPOutputStream(outputStream)) {
gzip.write(str.getBytes(StandardCharsets.UTF_8));
}
return outputStream.toByteArray();
}
}
public static class SimplePie extends CustomChart {
private final Callable<String> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public SimplePie(String chartId, Callable<String> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
String value = callable.call();
if (value == null || value.isEmpty()) {
// Null = skip the chart
return null;
}
return new JsonObjectBuilder().appendField("value", value).build();
}
}
public static class MultiLineChart extends CustomChart {
private final Callable<Map<String, Integer>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public MultiLineChart(
String chartId,
Callable<Map<String, Integer>> callable
) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
Map<String, Integer> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
boolean allSkipped = true;
for (Map.Entry<String, Integer> entry : map.entrySet()) {
if (entry.getValue() == 0) {
// Skip this invalid
continue;
}
allSkipped = false;
valuesBuilder.appendField(entry.getKey(), entry.getValue());
}
if (allSkipped) {
// Null = skip the chart
return null;
}
return new JsonObjectBuilder()
.appendField("values", valuesBuilder.build())
.build();
}
}
public static class AdvancedPie extends CustomChart {
private final Callable<Map<String, Integer>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public AdvancedPie(
String chartId,
Callable<Map<String, Integer>> callable
) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
Map<String, Integer> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
boolean allSkipped = true;
for (Map.Entry<String, Integer> entry : map.entrySet()) {
if (entry.getValue() == 0) {
// Skip this invalid
continue;
}
allSkipped = false;
valuesBuilder.appendField(entry.getKey(), entry.getValue());
}
if (allSkipped) {
// Null = skip the chart
return null;
}
return new JsonObjectBuilder()
.appendField("values", valuesBuilder.build())
.build();
}
}
public static class SimpleBarChart extends CustomChart {
private final Callable<Map<String, Integer>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public SimpleBarChart(
String chartId,
Callable<Map<String, Integer>> callable
) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
Map<String, Integer> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
for (Map.Entry<String, Integer> entry : map.entrySet()) {
valuesBuilder.appendField(
entry.getKey(),
new int[] { entry.getValue() }
);
}
return new JsonObjectBuilder()
.appendField("values", valuesBuilder.build())
.build();
}
}
public static class AdvancedBarChart extends CustomChart {
private final Callable<Map<String, int[]>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public AdvancedBarChart(
String chartId,
Callable<Map<String, int[]>> callable
) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
Map<String, int[]> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
boolean allSkipped = true;
for (Map.Entry<String, int[]> entry : map.entrySet()) {
if (entry.getValue().length == 0) {
// Skip this invalid
continue;
}
allSkipped = false;
valuesBuilder.appendField(entry.getKey(), entry.getValue());
}
if (allSkipped) {
// Null = skip the chart
return null;
}
return new JsonObjectBuilder()
.appendField("values", valuesBuilder.build())
.build();
}
}
public static class DrilldownPie extends CustomChart {
private final Callable<Map<String, Map<String, Integer>>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public DrilldownPie(
String chartId,
Callable<Map<String, Map<String, Integer>>> callable
) {
super(chartId);
this.callable = callable;
}
@Override
public JsonObjectBuilder.JsonObject getChartData() throws Exception {
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
Map<String, Map<String, Integer>> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
boolean reallyAllSkipped = true;
for (Map.Entry<String, Map<String, Integer>> entryValues : map.entrySet()) {
JsonObjectBuilder valueBuilder = new JsonObjectBuilder();
boolean allSkipped = true;
for (Map.Entry<String, Integer> valueEntry : map
.get(entryValues.getKey())
.entrySet()) {
valueBuilder.appendField(valueEntry.getKey(), valueEntry.getValue());
allSkipped = false;
}
if (!allSkipped) {
reallyAllSkipped = false;
valuesBuilder.appendField(entryValues.getKey(), valueBuilder.build());
}
}
if (reallyAllSkipped) {
// Null = skip the chart
return null;
}
return new JsonObjectBuilder()
.appendField("values", valuesBuilder.build())
.build();
}
}
public abstract static class CustomChart {
private final String chartId;
protected CustomChart(String chartId) {
if (chartId == null) {
throw new IllegalArgumentException("chartId must not be null");
}
this.chartId = chartId;
}
public JsonObjectBuilder.JsonObject getRequestJsonObject(
BiConsumer<String, Throwable> errorLogger,
boolean logErrors
) {
JsonObjectBuilder builder = new JsonObjectBuilder();
builder.appendField("chartId", chartId);
try {
JsonObjectBuilder.JsonObject data = getChartData();
if (data == null) {
// If the data is null we don't send the chart.
return null;
}
builder.appendField("data", data);
} catch (Throwable t) {
if (logErrors) {
errorLogger.accept(
"Failed to get data for custom chart with id " + chartId,
t
);
}
return null;
}
return builder.build();
}
protected abstract JsonObjectBuilder.JsonObject getChartData()
throws Exception;
}
public static class SingleLineChart extends CustomChart {
private final Callable<Integer> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public SingleLineChart(String chartId, Callable<Integer> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
int value = callable.call();
if (value == 0) {
// Null = skip the chart
return null;
}
return new JsonObjectBuilder().appendField("value", value).build();
}
}
/**
* An extremely simple JSON builder.
*
* <p>While this class is neither feature-rich nor the most performant one, it's sufficient enough
* for its use-case.
*/
public static class JsonObjectBuilder {
private StringBuilder builder = new StringBuilder();
private boolean hasAtLeastOneField = false;
public JsonObjectBuilder() {
builder.append("{");
}
/**
* Appends a null field to the JSON.
*
* @param key The key of the field.
* @return A reference to this object.
*/
public JsonObjectBuilder appendNull(String key) {
appendFieldUnescaped(key, "null");
return this;
}
/**
* Appends a string field to the JSON.
*
* @param key The key of the field.
* @param value The value of the field.
* @return A reference to this object.
*/
public JsonObjectBuilder appendField(String key, String value) {
if (value == null) {
throw new IllegalArgumentException("JSON value must not be null");
}
appendFieldUnescaped(key, "\"" + escape(value) + "\"");
return this;
}
/**
* Appends an integer field to the JSON.
*
* @param key The key of the field.
* @param value The value of the field.
* @return A reference to this object.
*/
public JsonObjectBuilder appendField(String key, int value) {
appendFieldUnescaped(key, String.valueOf(value));
return this;
}
/**
* Appends an object to the JSON.
*
* @param key The key of the field.
* @param object The object.
* @return A reference to this object.
*/
public JsonObjectBuilder appendField(String key, JsonObject object) {
if (object == null) {
throw new IllegalArgumentException("JSON object must not be null");
}
appendFieldUnescaped(key, object.toString());
return this;
}
/**
* Appends a string array to the JSON.
*
* @param key The key of the field.
* @param values The string array.
* @return A reference to this object.
*/
public JsonObjectBuilder appendField(String key, String[] values) {
if (values == null) {
throw new IllegalArgumentException("JSON values must not be null");
}
String escapedValues = Arrays
.stream(values)
.map(value -> "\"" + escape(value) + "\"")
.collect(Collectors.joining(","));
appendFieldUnescaped(key, "[" + escapedValues + "]");
return this;
}
/**
* Appends an integer array to the JSON.
*
* @param key The key of the field.
* @param values The integer array.
* @return A reference to this object.
*/
public JsonObjectBuilder appendField(String key, int[] values) {
if (values == null) {
throw new IllegalArgumentException("JSON values must not be null");
}
String escapedValues = Arrays
.stream(values)
.mapToObj(String::valueOf)
.collect(Collectors.joining(","));
appendFieldUnescaped(key, "[" + escapedValues + "]");
return this;
}
/**
* Appends an object array to the JSON.
*
* @param key The key of the field.
* @param values The integer array.
* @return A reference to this object.
*/
public JsonObjectBuilder appendField(String key, JsonObject[] values) {
if (values == null) {
throw new IllegalArgumentException("JSON values must not be null");
}
String escapedValues = Arrays
.stream(values)
.map(JsonObject::toString)
.collect(Collectors.joining(","));
appendFieldUnescaped(key, "[" + escapedValues + "]");
return this;
}
/**
* Appends a field to the object.
*
* @param key The key of the field.
* @param escapedValue The escaped value of the field.
*/
private void appendFieldUnescaped(String key, String escapedValue) {
if (builder == null) {
throw new IllegalStateException("JSON has already been built");
}
if (key == null) {
throw new IllegalArgumentException("JSON key must not be null");
}
if (hasAtLeastOneField) {
builder.append(",");
}
builder
.append("\"")
.append(escape(key))
.append("\":")
.append(escapedValue);
hasAtLeastOneField = true;
}
/**
* Builds the JSON string and invalidates this builder.
*
* @return The built JSON string.
*/
public JsonObject build() {
if (builder == null) {
throw new IllegalStateException("JSON has already been built");
}
JsonObject object = new JsonObject(builder.append("}").toString());
builder = null;
return object;
}
/**
* Escapes the given string like stated in https://www.ietf.org/rfc/rfc4627.txt.
*
* <p>This method escapes only the necessary characters '"', '\'. and '\u0000' - '\u001F'.
* Compact escapes are not used (e.g., '\n' is escaped as "\u000a" and not as "\n").
*
* @param value The value to escape.
* @return The escaped value.
*/
private static String escape(String value) {
final StringBuilder builder = new StringBuilder();
for (int i = 0; i < value.length(); i++) {
char c = value.charAt(i);
if (c == '"') {
builder.append("\\\"");
} else if (c == '\\') {
builder.append("\\\\");
} else if (c <= '\u000F') {
builder.append("\\u000").append(Integer.toHexString(c));
} else if (c <= '\u001F') {
builder.append("\\u00").append(Integer.toHexString(c));
} else {
builder.append(c);
}
}
return builder.toString();
}
/**
* A super simple representation of a JSON object.
*
* <p>This class only exists to make methods of the {@link JsonObjectBuilder} type-safe and not
* allow a raw string inputs for methods like {@link JsonObjectBuilder#appendField(String,
* JsonObject)}.
*/
public static class JsonObject {
private final String value;
private JsonObject(String value) {
this.value = value;
}
@Override
public String toString() {
return value;
}
}
}
}
@@ -1,8 +1,12 @@
package me.codedred.playtimes;
import java.io.IOException;
import java.sql.SQLException;
import java.util.Objects;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import me.codedred.playtimes.afk.AFKManager;
import me.codedred.playtimes.afk.listeners.onChat;
import me.codedred.playtimes.afk.listeners.onInteract;
@@ -19,145 +23,102 @@ import me.codedred.playtimes.listeners.Quit;
import me.codedred.playtimes.server.ServerManager;
import me.codedred.playtimes.statistics.StatManager;
import me.codedred.playtimes.time.TimeManager;
import me.codedred.playtimes.utils.ChatUtil;
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
public class PlayTimes extends JavaPlugin {
@Override
public void onEnable() {
checkForUpdate();
@Override
public void onEnable() {
ServerManager.getInstance().register();
StatManager.getInstance().registerStatistics();
TimeManager.getInstance().registerTimings();
ServerManager.getInstance().register();
StatManager.getInstance().registerStatistics();
TimeManager.getInstance().registerTimings();
loadDatabase();
registerEvents();
registerCommands();
loadDatabase();
registerEvents();
registerCommands();
@SuppressWarnings("unused")
Metrics metrics = new Metrics(this, 5289);
AFKManager.getInstance().startAFKChecker();
getLogger().info("Successfully loaded.");
}
AFKManager.getInstance().startAFKChecker();
getLogger().info("Successfully loaded.");
}
@Override
public void onDisable() {
AFKManager.getInstance().endAFKChecker();
getLogger().info("PlayTimes shutting down");
}
@Override
public void onDisable() {
AFKManager.getInstance().endAFKChecker();
getLogger().info("PlayTimes shutting down");
}
private void registerEvents() {
PluginManager pm = getServer().getPluginManager();
DataManager data = DataManager.getInstance();
private void registerEvents() {
PluginManager pm = getServer().getPluginManager();
DataManager data = DataManager.getInstance();
pm.registerEvents(new Join(), this);
pm.registerEvents(new Quit(), this);
pm.registerEvents(new Join(), this);
pm.registerEvents(new Quit(), this);
if (data.hasAfkEnabled()) {
pm.registerEvents(new onJoinQuit(), this);
if (data.hasAfkEnabled()) {
pm.registerEvents(new onJoinQuit(), this);
FileConfiguration config = data.getConfig();
boolean cancelAfkOnMove = config.getBoolean(
"afk-settings.cancel-afk.on-player-move"
);
boolean cancelAfkOnChat = config.getBoolean(
"afk-settings.cancel-afk.on-player-chat"
);
boolean cancelAfkOnInteract = config.getBoolean(
"afk-settings.cancel-afk.on-player-interact"
);
FileConfiguration config = data.getConfig();
boolean cancelAfkOnMove = config.getBoolean(
"afk-settings.cancel-afk.on-player-move"
);
boolean cancelAfkOnChat = config.getBoolean(
"afk-settings.cancel-afk.on-player-chat"
);
boolean cancelAfkOnInteract = config.getBoolean(
"afk-settings.cancel-afk.on-player-interact"
);
if (cancelAfkOnMove || cancelAfkOnChat || cancelAfkOnInteract) {
if (cancelAfkOnMove) pm.registerEvents(new onMove(), this);
if (cancelAfkOnChat) pm.registerEvents(new onChat(), this);
if (cancelAfkOnInteract) pm.registerEvents(new onInteract(), this);
}
}
}
if (cancelAfkOnMove || cancelAfkOnChat || cancelAfkOnInteract) {
if (cancelAfkOnMove) pm.registerEvents(new onMove(), this);
if (cancelAfkOnChat) pm.registerEvents(new onChat(), this);
if (cancelAfkOnInteract) pm.registerEvents(new onInteract(), this);
}
}
}
private void registerCommands() {
Objects.requireNonNull(getCommand("playtime")).setExecutor(new Time());
Objects.requireNonNull(getCommand("uptime")).setExecutor(new Uptime());
Objects
.requireNonNull(getCommand("topplaytime"))
.setExecutor(new TopTime());
Objects
.requireNonNull(this.getCommand("pt"))
.setTabCompleter(new TimeTabCompleter());
}
private void registerCommands() {
Objects.requireNonNull(getCommand("playtime")).setExecutor(new Time());
Objects.requireNonNull(getCommand("uptime")).setExecutor(new Uptime());
Objects
.requireNonNull(getCommand("topplaytime"))
.setExecutor(new TopTime());
Objects
.requireNonNull(this.getCommand("pt"))
.setTabCompleter(new TimeTabCompleter());
}
public void loadDatabase() {
if (
DataManager
.getInstance()
.getDBConfig()
.getBoolean("database-settings.enabled")
) {
getLogger().info("Connecting to Database...");
DatabaseManager databaseManager = DatabaseManager.getInstance();
public void loadDatabase() {
if (
DataManager
.getInstance()
.getDBConfig()
.getBoolean("database-settings.enabled")
) {
getLogger().info("Connecting to Database...");
DatabaseManager databaseManager = DatabaseManager.getInstance();
try {
if (databaseManager.getDataSource().getConnection() != null) {
try {
databaseManager.getDataSource().closeConnection();
} catch (Exception e) {
getLogger()
.warning("Error while trying to close Database connection..");
}
databaseManager.load();
try {
if (databaseManager.getDataSource().getConnection() != null) {
try {
databaseManager.getDataSource().closeConnection();
} catch (Exception e) {
getLogger()
.warning("Error while trying to close Database connection..");
}
databaseManager.load();
if (
DataManager
.getInstance()
.getDBConfig()
.getBoolean("purge-database.enabled")
) databaseManager.purgeOldPlaytimeData();
}
} catch (SQLException e) {
getLogger().severe("Error loading the database: " + e.getMessage());
e.printStackTrace();
}
}
}
if (
DataManager
.getInstance()
.getDBConfig()
.getBoolean("purge-database.enabled")
) databaseManager.purgeOldPlaytimeData();
}
} catch (SQLException e) {
getLogger().severe("Error loading the database: " + e.getMessage());
e.printStackTrace();
}
}
}
private void checkForUpdate() {
Bukkit
.getScheduler()
.runTaskAsynchronously(
this,
() -> {
UpdateChecker updater = new UpdateChecker(this, 58858);
try {
if (updater.hasUpdatesAvailable()) {
getLogger()
.warning(
ChatUtil.format(
"You are using an older version of PlayTimes!"
)
);
getLogger()
.info(ChatUtil.format("Download the newest version here:"));
getLogger()
.info(
ChatUtil.format("https://www.spigotmc.org/resources/58858/")
);
} else {
getLogger()
.info(
"[PlayTimes] Plugin is up to date! - " +
getDescription().getVersion()
);
}
} catch (IOException e) {
getLogger().warning("[PlayTimes] Could not check for updates!");
}
}
);
}
}
}
@@ -1,48 +0,0 @@
package me.codedred.playtimes;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.Objects;
import javax.net.ssl.HttpsURLConnection;
import org.bukkit.plugin.java.JavaPlugin;
public class UpdateChecker {
private static final String UPDATE_URL =
"https://api.spigotmc.org/legacy/update.php?resource=%d";
private final JavaPlugin plugin;
private final int projectId;
public UpdateChecker(JavaPlugin plugin, int projectId) {
this.plugin = Objects.requireNonNull(plugin, "Plugin cannot be null.");
this.projectId = projectId;
}
public boolean hasUpdatesAvailable() throws IOException {
String latestVersion;
URL url = new URL(String.format(UPDATE_URL, projectId));
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(5000);
connection.setReadTimeout(5000);
connection.setUseCaches(true);
try (
BufferedReader reader = new BufferedReader(
new InputStreamReader(connection.getInputStream())
)
) {
latestVersion = reader.readLine();
}
if (latestVersion == null || latestVersion.isEmpty()) {
throw new IOException("Failed to retrieve latest version.");
}
return !plugin.getDescription().getVersion().equals(latestVersion);
}
}
@@ -1,14 +1,16 @@
package me.codedred.playtimes.afk.listeners;
import me.codedred.playtimes.afk.AFKManager;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.AsyncPlayerChatEvent;
import io.papermc.paper.event.player.AsyncChatEvent;
import me.codedred.playtimes.afk.AFKManager;
public class onChat implements Listener {
@EventHandler
public void onPlayerChat(AsyncPlayerChatEvent event) {
AFKManager.getInstance().updateActivity(event.getPlayer());
}
}
@EventHandler
public void onPlayerChat(AsyncChatEvent event) {
AFKManager.getInstance().updateActivity(event.getPlayer());
}
}
@@ -2,18 +2,11 @@ package me.codedred.playtimes.commands;
import java.util.Objects;
import java.util.UUID;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import me.codedred.playtimes.PlayTimes;
import me.codedred.playtimes.data.DataManager;
import me.codedred.playtimes.models.Message;
import me.codedred.playtimes.server.ServerManager;
import me.codedred.playtimes.statistics.StatManager;
import me.codedred.playtimes.time.TimeConstants;
import me.codedred.playtimes.time.TimeManager;
import me.codedred.playtimes.utils.ChatUtil;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
@@ -23,289 +16,336 @@ import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitRunnable;
import org.jetbrains.annotations.NotNull;
import me.codedred.playtimes.PlayTimes;
import me.codedred.playtimes.data.DataManager;
import me.codedred.playtimes.models.Message;
import me.codedred.playtimes.server.ServerManager;
import me.codedred.playtimes.statistics.StatManager;
import me.codedred.playtimes.time.TimeConstants;
import me.codedred.playtimes.time.TimeManager;
import me.codedred.playtimes.utils.ChatUtil;
import me.codedred.playtimes.utils.OGUtils;
public class Time implements CommandExecutor {
private final DataManager data = DataManager.getInstance();
private final DataManager data = DataManager.getInstance();
public boolean onCommand(
CommandSender sender,
@NotNull Command cmd,
@NotNull String label,
String[] args
) {
if (!sender.hasPermission("pt.use")) {
ChatUtil.errno(sender, ChatUtil.ChatTypes.NO_PERMISSION);
return true;
}
public boolean onCommand(
CommandSender sender,
@NotNull Command cmd,
@NotNull String label,
String[] args
) {
if (! sender.hasPermission("pt.use")) {
ChatUtil.errno(sender, ChatUtil.ChatTypes.NO_PERMISSION);
return true;
}
String arg = args.length > 0 ? args[0].toLowerCase() : "";
String arg = args.length > 0 ? args[0].toLowerCase() : "";
switch (arg) {
case "":
handlePlayerCommand(sender);
break;
case "reload":
handleReloadCommand(sender);
break;
case "reloaddatabase":
handleReloadDatabaseCommand(sender);
break;
case "top":
handleTopCommand(sender);
break;
case "block":
handleBlockCommand(sender, args, true);
break;
case "unblock":
handleBlockCommand(sender, args, false);
break;
case "version":
handleVersionCommand(sender);
break;
case "help":
handleHelpCommand(sender);
break;
default:
handleOtherPlayerCommand(sender, args);
break;
}
switch (arg) {
case "":
handlePlayerCommand(sender);
break;
case "reload":
handleReloadCommand(sender);
break;
case "reloaddatabase":
handleReloadDatabaseCommand(sender);
break;
case "top":
handleTopCommand(sender);
break;
case "block":
handleBlockCommand(sender, args, true);
break;
case "unblock":
handleBlockCommand(sender, args, false);
break;
case "version":
handleVersionCommand(sender);
break;
case "help":
handleHelpCommand(sender);
break;
default:
handleOtherPlayerCommand(sender, args);
break;
}
return true;
}
return true;
}
private void handleHelpCommand(CommandSender sender) {
if (!sender.hasPermission("pt.reload")) {
ChatUtil.errno(sender, ChatUtil.ChatTypes.NO_PERMISSION);
return;
}
sender.sendMessage(ChatUtil.format("&6&l*** PlayTimes Help ***"));
sender.sendMessage(ChatUtil.format("&e/pt: &7Displays your play time"));
sender.sendMessage(
ChatUtil.format("&e/pt <player>: &7Displays the play time of <player>")
);
sender.sendMessage(
ChatUtil.format(
"&e/pt reload: &7Reloads the PlayTimes plugin configurations"
)
);
sender.sendMessage(
ChatUtil.format("&e/pt top: &7Shows the top players by play time")
);
sender.sendMessage(
ChatUtil.format(
"&e/pt block <player>: &7Blocks a player from leaderboards"
)
);
sender.sendMessage(
ChatUtil.format(
"&e/pt unblock <player>: &7Unblocks a player from leaderboards"
)
);
sender.sendMessage(
ChatUtil.format("&e/pt version: &7Displays the version info")
);
}
private void handleHelpCommand(CommandSender sender) {
if (! sender.hasPermission("pt.reload")) {
ChatUtil.errno(sender, ChatUtil.ChatTypes.NO_PERMISSION);
return;
}
sender.sendMessage(ChatUtil.format("&6&l*** PlayTimes Help ***"));
sender.sendMessage(ChatUtil.format("&e/pt: &7Displays your play time"));
sender.sendMessage(
ChatUtil.format("&e/pt <player>: &7Displays the play time of <player>")
);
sender.sendMessage(
ChatUtil.format(
"&e/pt reload: &7Reloads the PlayTimes plugin configurations"
)
);
sender.sendMessage(
ChatUtil.format("&e/pt top: &7Shows the top players by play time")
);
sender.sendMessage(
ChatUtil.format(
"&e/pt block <player>: &7Blocks a player from leaderboards"
)
);
sender.sendMessage(
ChatUtil.format(
"&e/pt unblock <player>: &7Unblocks a player from leaderboards"
)
);
sender.sendMessage(
ChatUtil.format("&e/pt version: &7Displays the version info")
);
}
private void handleVersionCommand(CommandSender sender) {
if (!sender.hasPermission("pt.reload")) {
ChatUtil.errno(sender, ChatUtil.ChatTypes.NO_PERMISSION);
return;
}
boolean isOnline = ServerManager.getInstance().isOnline();
String serverStatus = isOnline ? "Online" : "Offline";
private void handleVersionCommand(CommandSender sender) {
if (! sender.hasPermission("pt.reload")) {
ChatUtil.errno(sender, ChatUtil.ChatTypes.NO_PERMISSION);
return;
}
boolean isOnline = ServerManager.getInstance().isOnline();
String serverStatus = isOnline ? "Online" : "Offline";
sender.sendMessage(ChatUtil.format("&6&l*** PlayTimes Version Info ***"));
sender.sendMessage(
ChatColor.GOLD +
"Server version: " +
ChatColor.WHITE +
Bukkit.getServer().getVersion() +
ChatColor.GOLD +
"\nPlayTimes version: " +
ChatColor.WHITE +
JavaPlugin.getPlugin(PlayTimes.class).getDescription().getVersion() +
ChatColor.GOLD +
"\nStat Manager: " +
ChatColor.WHITE +
StatManager.getInstance().name +
ChatColor.GOLD +
"\nServer Status: " +
ChatColor.WHITE +
serverStatus +
ChatColor.GOLD +
"\nDatabase connected: " +
ChatColor.WHITE +
DataManager.getInstance().hasDatabase() +
ChatColor.GOLD +
"\nAFK Status: " +
ChatColor.WHITE +
DataManager.getInstance().hasAfkEnabled() +
ChatColor.GOLD +
"\nUUID Lookup Type: " +
ChatColor.WHITE +
ServerManager.getInstance().lookupType()
);
}
sender.sendMessage(ChatUtil.format("&6&l*** PlayTimes Version Info ***"));
if (sender instanceof Player) {
OGUtils.playTimesOGMessage((Player) sender,
"&6" +
"Server version: " +
"&f" +
Bukkit.getServer().getVersion() +
"&6" +
"\nPlayTimes version: " +
"&f" +
"v1.0" +
"&6" +
"\nStat Manager: " +
"&f" +
StatManager.getInstance().name +
"&6" +
"\nServer Status: " +
"&f" +
serverStatus +
"&6" +
"\nDatabase connected: " +
"&f" +
DataManager.getInstance().hasDatabase() +
"&6" +
"\nAFK Status: " +
"&f" +
DataManager.getInstance().hasAfkEnabled() +
"&6" +
"\nUUID Lookup Type: " +
"&f" +
ServerManager.getInstance().lookupType()
);
}
else {
Logger logger = Bukkit.getServer().getLogger();
logger.info(OGUtils.legacySerializerAnyCase(
"&6" +
"Server version: " +
"&f" +
Bukkit.getServer().getVersion() +
"&6" +
"\nPlayTimes version: " +
"&f" +
"v1.0" +
"&6" +
"\nStat Manager: " +
"&f" +
StatManager.getInstance().name +
"&6" +
"\nServer Status: " +
"&f" +
serverStatus +
"&6" +
"\nDatabase connected: " +
"&f" +
DataManager.getInstance().hasDatabase() +
"&6" +
"\nAFK Status: " +
"&f" +
DataManager.getInstance().hasAfkEnabled() +
"&6" +
"\nUUID Lookup Type: " +
"&f" +
ServerManager.getInstance().lookupType()
).content());
private void handlePlayerCommand(CommandSender sender) {
if (!(sender instanceof Player)) {
StatManager stats = StatManager.getInstance();
sender.sendMessage(
ChatUtil.format("&cThe console has been up for " + stats.getUptime())
);
return;
}
}
new BukkitRunnable() {
@Override
public void run() {
Player player = (Player) sender;
Message message = new Message(
sender,
player.getUniqueId(),
player.getName()
);
message.sendMessageToTarget();
}
}
.runTaskAsynchronously(JavaPlugin.getPlugin(PlayTimes.class));
}
}
private void handleReloadCommand(CommandSender sender) {
if (!sender.hasPermission("pt.reload")) {
ChatUtil.errno(sender, ChatUtil.ChatTypes.NO_PERMISSION);
return;
}
data.reloadAll();
TimeConstants.reload();
TimeManager.getInstance().registerTimings();
sender.sendMessage(ChatUtil.format("&aPlayTimes Configurations Reloaded!"));
if (DataManager.getInstance().hasDatabase()) {
sender.sendMessage(
ChatUtil.format("&aTrying to reload the Database settings?")
);
sender.sendMessage(ChatUtil.format(" &e&oTry, /pt reloadDatabase"));
}
}
private void handlePlayerCommand(CommandSender sender) {
if (! (sender instanceof Player)) {
StatManager stats = StatManager.getInstance();
sender.sendMessage(
ChatUtil.format("&cThe console has been up for " + stats.getUptime())
);
return;
}
private void handleReloadDatabaseCommand(CommandSender sender) {
if (!sender.hasPermission("pt.reload")) {
ChatUtil.errno(sender, ChatUtil.ChatTypes.NO_PERMISSION);
return;
}
data.reloadDatabase();
PlayTimes.getPlugin(PlayTimes.class).loadDatabase();
sender.sendMessage(ChatUtil.format("&aDatabase Reloaded."));
sender.sendMessage(
ChatUtil.format(
"&oIf you are still encountering issues try restarting your server."
)
);
}
new BukkitRunnable() {
@Override
public void run() {
Player player = (Player) sender;
Message message = new Message(
sender,
player.getUniqueId(),
player.getName()
);
message.sendMessageToTarget();
}
}
.runTaskAsynchronously(JavaPlugin.getPlugin(PlayTimes.class));
}
private void handleTopCommand(CommandSender sender) {
Bukkit.dispatchCommand(sender, "toppt");
}
private void handleReloadCommand(CommandSender sender) {
if (! sender.hasPermission("pt.reload")) {
ChatUtil.errno(sender, ChatUtil.ChatTypes.NO_PERMISSION);
return;
}
data.reloadAll();
TimeConstants.reload();
TimeManager.getInstance().registerTimings();
sender.sendMessage(ChatUtil.format("&aPlayTimes Configurations Reloaded!"));
if (DataManager.getInstance().hasDatabase()) {
sender.sendMessage(
ChatUtil.format("&aTrying to reload the Database settings?")
);
sender.sendMessage(ChatUtil.format(" &e&oTry, /pt reloadDatabase"));
}
}
private void handleBlockCommand(
CommandSender sender,
String[] args,
boolean block
) {
String blockAction = block ? "block" : "unblock";
if (!sender.hasPermission("pt.block")) {
ChatUtil.errno(sender, ChatUtil.ChatTypes.NO_PERMISSION);
return;
}
if (args.length < 2) {
sender.sendMessage(
ChatUtil.formatWithPrefix(
"&c&lIncorrect Usage&c, try &7/pt " + blockAction + " <player>"
)
);
return;
}
String playerName = args[1];
String blockedKey = "blocked." + playerName.toLowerCase();
if (block == data.getData().contains(blockedKey)) {
sender.sendMessage(
ChatUtil.formatWithPrefix("&cUser is already " + blockAction + "ed!")
);
sender.sendMessage(
ChatUtil.formatWithPrefix(
"&c&oMaybe try, &7/pt " + (block ? "unblock" : "block") + " <player>"
)
);
return;
}
UUID uuid;
if (Bukkit.getPlayer(playerName) == null) {
try {
uuid = ServerManager.getInstance().getUUID(playerName.toLowerCase());
} catch (NullPointerException e) {
ChatUtil.errno(sender, ChatUtil.ChatTypes.PLAYER_NOT_FOUND);
return;
}
} else {
uuid = Objects.requireNonNull(Bukkit.getPlayer(playerName)).getUniqueId();
}
if (uuid == null) {
sender.sendMessage(ChatUtil.formatWithPrefix("&c&lUser not found."));
return;
}
if (block) {
data.getData().set(blockedKey, uuid.toString());
if (data.getData().contains("leaderboard." + uuid)) {
data.getData().set("leaderboard." + uuid, null);
}
sender.sendMessage(
ChatUtil.formatWithPrefix("&aUser &c&lBLOCKED &afrom leaderboards!")
);
} else {
data.getData().set(blockedKey, null);
sender.sendMessage(
ChatUtil.formatWithPrefix("&aUser &b&lUNBLOCKED &afrom leaderboards!")
);
}
data.saveData();
}
private void handleReloadDatabaseCommand(CommandSender sender) {
if (!sender.hasPermission("pt.reload")) {
ChatUtil.errno(sender, ChatUtil.ChatTypes.NO_PERMISSION);
return;
}
data.reloadDatabase();
PlayTimes.getPlugin(PlayTimes.class).loadDatabase();
sender.sendMessage(ChatUtil.format("&aDatabase Reloaded."));
sender.sendMessage(
ChatUtil.format(
"&oIf you are still encountering issues try restarting your server."
)
);
}
private void handleOtherPlayerCommand(CommandSender sender, String[] args) {
if (!sender.hasPermission("pt.others")) {
ChatUtil.errno(sender, ChatUtil.ChatTypes.NO_PERMISSION);
return;
}
new BukkitRunnable() {
@Override
public void run() {
Pattern p = Pattern.compile("[^a-z0-9_]", Pattern.CASE_INSENSITIVE);
Matcher m = p.matcher(args[0]);
boolean b = m.find();
private void handleTopCommand(CommandSender sender) {
Bukkit.dispatchCommand(sender, "toppt");
}
if (b) {
ChatUtil.errno(sender, ChatUtil.ChatTypes.PLAYER_NOT_FOUND);
return;
}
private void handleBlockCommand(
CommandSender sender,
String[] args,
boolean block
) {
String blockAction = block ? "block" : "unblock";
if (! sender.hasPermission("pt.block")) {
ChatUtil.errno(sender, ChatUtil.ChatTypes.NO_PERMISSION);
return;
}
if (args.length < 2) {
sender.sendMessage(
ChatUtil.formatWithPrefix(
"&c&lIncorrect Usage&c, try &7/pt " + blockAction + " <player>"
)
);
return;
}
String playerName = args[1];
String blockedKey = "blocked." + playerName.toLowerCase();
if (block == data.getData().contains(blockedKey)) {
sender.sendMessage(
ChatUtil.formatWithPrefix("&cUser is already " + blockAction + "ed!")
);
sender.sendMessage(
ChatUtil.formatWithPrefix(
"&c&oMaybe try, &7/pt " + (block ? "unblock" : "block") + " <player>"
)
);
return;
}
UUID uuid;
if (Bukkit.getPlayer(playerName) == null) {
try {
uuid = ServerManager.getInstance().getUUID(playerName.toLowerCase());
} catch (NullPointerException e) {
ChatUtil.errno(sender, ChatUtil.ChatTypes.PLAYER_NOT_FOUND);
return;
}
} else {
uuid = Objects.requireNonNull(Bukkit.getPlayer(playerName)).getUniqueId();
}
if (uuid == null) {
sender.sendMessage(ChatUtil.formatWithPrefix("&c&lUser not found."));
return;
}
if (block) {
data.getData().set(blockedKey, uuid.toString());
if (data.getData().contains("leaderboard." + uuid)) {
data.getData().set("leaderboard." + uuid, null);
}
sender.sendMessage(
ChatUtil.formatWithPrefix("&aUser &c&lBLOCKED &afrom leaderboards!")
);
} else {
data.getData().set(blockedKey, null);
sender.sendMessage(
ChatUtil.formatWithPrefix("&aUser &b&lUNBLOCKED &afrom leaderboards!")
);
}
data.saveData();
}
UUID target;
if (Bukkit.getPlayer(args[0]) == null) {
target = ServerManager.getInstance().getUUID(args[0]);
if (target == null) {
ChatUtil.errno(sender, ChatUtil.ChatTypes.PLAYER_NOT_FOUND);
return;
}
} else {
target =
Objects.requireNonNull(Bukkit.getPlayer(args[0])).getUniqueId();
}
private void handleOtherPlayerCommand(CommandSender sender, String[] args) {
if (! sender.hasPermission("pt.others")) {
ChatUtil.errno(sender, ChatUtil.ChatTypes.NO_PERMISSION);
return;
}
new BukkitRunnable() {
@Override
public void run() {
Pattern p = Pattern.compile("[^a-z0-9_]", Pattern.CASE_INSENSITIVE);
Matcher m = p.matcher(args[0]);
boolean b = m.find();
OfflinePlayer offlinePlayer = Bukkit
.getServer()
.getOfflinePlayer(target);
Message message = new Message(sender, target, offlinePlayer.getName());
message.sendMessageToTarget();
}
}
.runTaskAsynchronously(JavaPlugin.getPlugin(PlayTimes.class));
}
if (b) {
ChatUtil.errno(sender, ChatUtil.ChatTypes.PLAYER_NOT_FOUND);
return;
}
UUID target;
if (Bukkit.getPlayer(args[0]) == null) {
target = ServerManager.getInstance().getUUID(args[0]);
if (target == null) {
ChatUtil.errno(sender, ChatUtil.ChatTypes.PLAYER_NOT_FOUND);
return;
}
} else {
target =
Objects.requireNonNull(Bukkit.getPlayer(args[0])).getUniqueId();
}
OfflinePlayer offlinePlayer = Bukkit
.getServer()
.getOfflinePlayer(target);
Message message = new Message(sender, target, offlinePlayer.getName());
message.sendMessageToTarget();
}
}
.runTaskAsynchronously(JavaPlugin.getPlugin(PlayTimes.class));
}
}
@@ -3,11 +3,7 @@ package me.codedred.playtimes.commands;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import me.codedred.playtimes.data.DataManager;
import me.codedred.playtimes.models.Leaderboard;
import me.codedred.playtimes.statistics.StatManager;
import me.codedred.playtimes.time.TimeManager;
import me.codedred.playtimes.utils.*;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
@@ -15,102 +11,110 @@ import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import me.codedred.playtimes.data.DataManager;
import me.codedred.playtimes.models.Leaderboard;
import me.codedred.playtimes.statistics.StatManager;
import me.codedred.playtimes.time.TimeManager;
import me.codedred.playtimes.utils.ChatUtil;
import me.codedred.playtimes.utils.CoolDownUtil;
import net.kyori.adventure.text.TextComponent;
public class TopTime implements CommandExecutor {
@Override
public boolean onCommand(
CommandSender sender,
@NotNull Command cmd,
@NotNull String label,
String[] args
) {
if (!sender.hasPermission("pt.top")) {
ChatUtil.errno(sender, ChatUtil.ChatTypes.NO_PERMISSION);
return true;
}
@Override
public boolean onCommand(
CommandSender sender,
@NotNull Command cmd,
@NotNull String label,
String[] args
) {
if (!sender.hasPermission("pt.top")) {
ChatUtil.errno(sender, ChatUtil.ChatTypes.NO_PERMISSION);
return true;
}
DataManager data = DataManager.getInstance();
DataManager data = DataManager.getInstance();
if (
data.getConfig().getBoolean("top-playtime.enable-cooldown") &&
!sender.hasPermission("pt.block-cooldown") &&
sender instanceof Player
) {
Player player = (Player) sender;
if (CoolDownUtil.contains(player.getUniqueId())) {
String cooldownMessage = Objects
.requireNonNull(data.getConfig().getString("messages.cooldown"))
.replace(
"%timeleft%",
Integer.toString(CoolDownUtil.left(player.getUniqueId()))
);
sender.sendMessage(ChatUtil.formatWithPrefix(cooldownMessage));
return true;
}
CoolDownUtil.add(
player.getUniqueId(),
System.currentTimeMillis() +
(data.getConfig().getInt("top-playtime.cooldown-seconds") * 1000L)
);
}
if (
data.getConfig().getBoolean("top-playtime.enable-cooldown") &&
!sender.hasPermission("pt.block-cooldown") &&
sender instanceof Player
) {
Player player = (Player) sender;
if (CoolDownUtil.contains(player.getUniqueId())) {
String cooldownMessage = Objects
.requireNonNull(data.getConfig().getString("messages.cooldown"))
.replace(
"%timeleft%",
Integer.toString(CoolDownUtil.left(player.getUniqueId()))
);
sender.sendMessage(ChatUtil.formatWithPrefix(cooldownMessage));
return true;
}
CoolDownUtil.add(
player.getUniqueId(),
System.currentTimeMillis() +
(data.getConfig().getInt("top-playtime.cooldown-seconds") * 1000L)
);
}
Leaderboard board = new Leaderboard();
Map<String, Integer> map = board.getTopTen();
Leaderboard board = new Leaderboard();
Map<String, Integer> map = board.getTopTen();
if (map.isEmpty()) {
sender.sendMessage(
ChatUtil.format("&cRejoin the server to fill the leaderboard!")
);
return true;
}
if (map.isEmpty()) {
sender.sendMessage(
ChatUtil.format("&cRejoin the server to fill the leaderboard!")
);
return true;
}
StatManager statManager = StatManager.getInstance();
TimeManager timeManager = TimeManager.getInstance();
String header = ChatUtil.format(
data.getConfig().getString("top-playtime.header")
);
String footer = ChatUtil.format(
data.getConfig().getString("top-playtime.footer")
);
String content = data.getConfig().getString("top-playtime.content");
StatManager statManager = StatManager.getInstance();
TimeManager timeManager = TimeManager.getInstance();
TextComponent header = ChatUtil.format(
data.getConfig().getString("top-playtime.header")
);
TextComponent footer = ChatUtil.format(
data.getConfig().getString("top-playtime.footer")
);
String content = data.getConfig().getString("top-playtime.content");
sender.sendMessage(header);
sender.sendMessage(header);
for (int i = 0; i < map.size(); i++) {
UUID uuid = UUID.fromString(map.keySet().toArray()[i].toString());
org.bukkit.OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(uuid);
for (int i = 0; i < map.size(); i++) {
UUID uuid = UUID.fromString(map.keySet().toArray()[i].toString());
org.bukkit.OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(uuid);
String defaultPlayerName = "Unknown";
String defaultJoinDate = "N/A";
String defaultTime = "0h 0m 0s";
String defaultPlayerName = "Unknown";
String defaultJoinDate = "N/A";
String defaultTime = "0h 0m 0s";
String offlinePlayerName = offlinePlayer.getName() != null
? offlinePlayer.getName()
: defaultPlayerName;
String place = String.valueOf(i + 1);
String time = timeManager.buildFormat(
map.get(offlinePlayer.getUniqueId().toString()) / 20
);
String joinDate = statManager.getJoinDate(uuid) != null
? statManager.getJoinDate(uuid)
: defaultJoinDate;
String offlinePlayerName = offlinePlayer.getName() != null
? offlinePlayer.getName()
: defaultPlayerName;
String place = String.valueOf(i + 1);
String time = timeManager.buildFormat(
map.get(offlinePlayer.getUniqueId().toString()) / 20
);
String joinDate = statManager.getJoinDate(uuid) != null
? statManager.getJoinDate(uuid)
: defaultJoinDate;
if (time == null) {
time = defaultTime;
}
if (time == null) {
time = defaultTime;
}
String formattedContent = content
.replace("%player%", offlinePlayerName)
.replace("%place%", place)
.replace("%time%", time)
.replace("%joindate%", joinDate);
String formattedContent = content
.replace("%player%", offlinePlayerName)
.replace("%place%", place)
.replace("%time%", time)
.replace("%joindate%", joinDate);
sender.sendMessage(ChatUtil.format(formattedContent));
sender.sendMessage(ChatUtil.format(formattedContent));
content = data.getConfig().getString("top-playtime.content");
}
content = data.getConfig().getString("top-playtime.content");
}
sender.sendMessage(footer);
return true;
}
sender.sendMessage(footer);
return true;
}
}
@@ -1,10 +1,7 @@
package me.codedred.playtimes.commands;
import java.util.List;
import me.codedred.playtimes.data.DataManager;
import me.codedred.playtimes.statistics.StatManager;
import me.codedred.playtimes.utils.ChatUtil;
import me.codedred.playtimes.utils.ServerUtils;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
@@ -12,63 +9,67 @@ import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import me.codedred.playtimes.data.DataManager;
import me.codedred.playtimes.statistics.StatManager;
import me.codedred.playtimes.utils.ChatUtil;
public class Uptime implements CommandExecutor {
@Override
public boolean onCommand(
@NotNull CommandSender sender,
@NotNull Command cmd,
String cmdL,
String[] args
) {
// command must be uptime,serveruptime,or serverupt
if (
!cmdL.equalsIgnoreCase("uptime") &&
!cmdL.equalsIgnoreCase("serveruptime") &&
!cmdL.equalsIgnoreCase("serverupt")
) {
return false;
}
@Override
public boolean onCommand(
@NotNull CommandSender sender,
@NotNull Command cmd,
String cmdL,
String[] args
) {
// command must be uptime,serveruptime,or serverupt
if (
!cmdL.equalsIgnoreCase("uptime") &&
!cmdL.equalsIgnoreCase("serveruptime") &&
!cmdL.equalsIgnoreCase("serverupt")
) {
return false;
}
// check for permission
if (!sender.hasPermission("pt.uptime")) {
ChatUtil.errno(sender, ChatUtil.ChatTypes.NO_PERMISSION);
return true;
}
// check for permission
if (!sender.hasPermission("pt.uptime")) {
ChatUtil.errno(sender, ChatUtil.ChatTypes.NO_PERMISSION);
return true;
}
// get the uptime
String uptime = StatManager.getInstance().getUptime();
List<String> messages = DataManager
.getInstance()
.getConfig()
.getStringList("uptime.message");
// get the uptime
String uptime = StatManager.getInstance().getUptime();
List<String> messages = DataManager
.getInstance()
.getConfig()
.getStringList("uptime.message");
// iterate through the messages and replace the placeholders
for (String message : messages) {
//1: replace them using placeholder api
// TODO: use MMAPI
/*
// iterate through the messages and replace the placeholders
for (String message : messages) {
//1: replace them using placeholder api
// TODO: use MMAPI
/*
if (ServerUtils.hasPAPI() && sender instanceof Player) {
Player player = (Player) sender;
message = PlaceholderAPI.setPlaceholders(player, message);
}
*/
// 2. replace them using java string methods
if (message.contains("{\"text\":") && sender instanceof Player) {
String consoleCommand =
"tellraw " +
sender.getName() +
" " +
message.replace("%serveruptime%", uptime);
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), consoleCommand);
//3. if all else fails dont send name
} else if(sender instanceof Player) {
sender.sendMessage(
ChatUtil.format(message.replace("%serveruptime%", uptime))
);
}
}
*/
// 2. replace them using java string methods
if (message.contains("{\"text\":") && sender instanceof Player) {
String consoleCommand =
"tellraw " +
sender.getName() +
" " +
message.replace("%serveruptime%", uptime);
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), consoleCommand);
//3. if all else fails dont send name
} else if(sender instanceof Player) {
sender.sendMessage(
ChatUtil.format(message.replace("%serveruptime%", uptime))
);
}
}
return true;
}
return true;
}
}
@@ -3,54 +3,53 @@ package me.codedred.playtimes.data.database.datasource.impl;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import me.codedred.playtimes.PlayTimes;
import me.codedred.playtimes.data.DataManager;
import me.codedred.playtimes.data.database.datasource.DataSource;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
public class MySQL implements DataSource {
private final String connectionUrl;
private final String user;
private final String password;
private final String connectionUrl;
private final String user;
private final String password;
public MySQL(PlayTimes plugin) {
var config = DataManager
.getInstance()
.getDBConfig()
.getConfigurationSection("database-settings");
String host = config.getString("host");
String port = config.getString("port");
this.user = config.getString("user");
this.password = config.getString("password");
String database = config.getString("database");
boolean SSL = config.getBoolean("useSSL");
public MySQL(PlayTimes plugin) {
var config = DataManager
.getInstance()
.getDBConfig()
.getConfigurationSection("database-settings");
String host = config.getString("host");
String port = config.getString("port");
this.user = config.getString("user");
this.password = config.getString("password");
String database = config.getString("database");
boolean SSL = config.getBoolean("useSSL");
this.connectionUrl =
String.format(
"jdbc:mysql://%s:%s/%s?autoReconnect=true&useSSL=" + SSL,
host,
port,
database
);
this.connectionUrl =
String.format(
"jdbc:mysql://%s:%s/%s?autoReconnect=true&useSSL=" + SSL,
host,
port,
database
);
try {
Class.forName("com.mysql.cj.jdbc.Driver");
plugin.getLogger().info("Successfully connected to database.");
} catch (ClassNotFoundException e) {
plugin
.getLogger()
.severe("MySQL JDBC Driver not found: " + e.getMessage());
}
}
try {
Class.forName("com.mysql.cj.jdbc.Driver");
plugin.getLogger().info("Successfully connected to database.");
} catch (ClassNotFoundException e) {
plugin
.getLogger()
.severe("MySQL JDBC Driver not found: " + e.getMessage());
}
}
@Override
public Connection getConnection() throws SQLException {
return DriverManager.getConnection(connectionUrl, user, password);
}
@Override
public Connection getConnection() throws SQLException {
return DriverManager.getConnection(connectionUrl, user, password);
}
@Override
public void closeConnection() throws SQLException {
// connection.close();
}
@Override
public void closeConnection() throws SQLException {
// connection.close();
}
}
@@ -1,8 +1,16 @@
package me.codedred.playtimes.models;
import java.util.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import me.codedred.playtimes.afk.AFKManager;
import me.codedred.playtimes.data.DataManager;
import me.codedred.playtimes.data.database.manager.DatabaseManager;
@@ -10,197 +18,196 @@ import me.codedred.playtimes.statistics.StatManager;
import me.codedred.playtimes.statistics.StatisticType;
import me.codedred.playtimes.time.TimeManager;
import me.codedred.playtimes.utils.ChatUtil;
import me.codedred.playtimes.utils.ServerUtils;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import net.kyori.adventure.text.TextComponent;
public class Message {
private final CommandSender sender;
private final UUID targetUUID;
private final String targetName;
private List<String> message;
private final CommandSender sender;
private final UUID targetUUID;
private final String targetName;
private List<String> message;
private final DataManager dataManager;
private final StatManager statManager;
private final TimeManager timeManager;
private final DataManager dataManager;
private final StatManager statManager;
private final TimeManager timeManager;
/**
* Constructor for Message.
* Initializes the message for the target player.
*
* @param sender The command sender.
* @param targetUUID The UUID of the offline player.
* @param targetName The name of the offline player.
*/
public Message(CommandSender sender, UUID targetUUID, String targetName) {
this.sender = sender;
this.targetUUID = targetUUID;
this.targetName = targetName;
/**
* Constructor for Message.
* Initializes the message for the target player.
*
* @param sender The command sender.
* @param targetUUID The UUID of the offline player.
* @param targetName The name of the offline player.
*/
public Message(CommandSender sender, UUID targetUUID, String targetName) {
this.sender = sender;
this.targetUUID = targetUUID;
this.targetName = targetName;
this.dataManager = DataManager.getInstance();
this.statManager = StatManager.getInstance();
this.timeManager = TimeManager.getInstance();
this.dataManager = DataManager.getInstance();
this.statManager = StatManager.getInstance();
this.timeManager = TimeManager.getInstance();
this.message = buildMessage();
}
this.message = buildMessage();
}
/**
* Builds the message to be sent to the sender about the target player.
* This involves fetching playtime statistics, formatting them,
* and applying any necessary replacements and PlaceholderAPI placeholders.
*
* @return A list of formatted strings representing the message.
*/
private List<String> buildMessage() {
List<String> builtMessage = dataManager
.getConfig()
.getStringList("playtime.message");
long rawTime = statManager.getPlayerStat(
targetUUID,
StatisticType.PLAYTIME
);
long afkTime = AFKManager.getInstance().getAFKTime(targetUUID);
/**
* Builds the message to be sent to the sender about the target player.
* This involves fetching playtime statistics, formatting them,
* and applying any necessary replacements and PlaceholderAPI placeholders.
*
* @return A list of formatted strings representing the message.
*/
private List<String> buildMessage() {
List<String> builtMessage = dataManager
.getConfig()
.getStringList("playtime.message");
long rawTime = statManager.getPlayerStat(
targetUUID,
StatisticType.PLAYTIME
);
long afkTime = AFKManager.getInstance().getAFKTime(targetUUID);
if (dataManager.hasDatabase()) {
DatabaseManager dbManager = DatabaseManager.getInstance();
dbManager.updatePlaytime(targetUUID, rawTime / 20, afkTime);
}
if (dataManager.hasDatabase()) {
DatabaseManager dbManager = DatabaseManager.getInstance();
dbManager.updatePlaytime(targetUUID, rawTime / 20, afkTime);
}
return processMessageWithReplacements(
builtMessage,
prepareReplacements(rawTime, afkTime)
);
}
/**
* Prepares a map of replacements to be used in the message.
*
* @param rawTime The playtime statistic to format and include in the replacements.
* @param afkTime The afktime statistic to format and include in the replacements.
* @return A map of placeholder keys and their replacement values.
*/
private Map<String, String> prepareReplacements(long rawTime, long afkTime) {
Map<String, String> replacements = new HashMap<>();
replacements.put("%time%", timeManager.buildFormat(rawTime / 20 - afkTime));
replacements.put(
"%playtime%",
timeManager.buildFormat(rawTime / 20 - afkTime)
);
replacements.put("%rawtime%", timeManager.buildFormat(rawTime / 20));
replacements.put("%afktime%", timeManager.buildFormat(afkTime));
replacements.put("%player%", targetName);
replacements.put(
"%timesjoined%",
Long.toString(
statManager.getPlayerStat(targetUUID, StatisticType.TIMES_JOINED)
)
);
replacements.put("%joindate%", statManager.getJoinDate(targetUUID));
return processMessageWithReplacements(
builtMessage,
prepareReplacements(rawTime, afkTime)
);
}
/**
* Prepares a map of replacements to be used in the message.
*
* @param rawTime The playtime statistic to format and include in the replacements.
* @param afkTime The afktime statistic to format and include in the replacements.
* @return A map of placeholder keys and their replacement values.
*/
private Map<String, String> prepareReplacements(long rawTime, long afkTime) {
Map<String, String> replacements = new HashMap<>();
replacements.put("%time%", timeManager.buildFormat(rawTime / 20 - afkTime));
replacements.put(
"%playtime%",
timeManager.buildFormat(rawTime / 20 - afkTime)
);
replacements.put("%rawtime%", timeManager.buildFormat(rawTime / 20));
replacements.put("%afktime%", timeManager.buildFormat(afkTime));
replacements.put("%player%", targetName);
replacements.put(
"%timesjoined%",
Long.toString(
statManager.getPlayerStat(targetUUID, StatisticType.TIMES_JOINED)
)
);
replacements.put("%joindate%", statManager.getJoinDate(targetUUID));
if (dataManager.hasDatabase()) {
DatabaseManager dbManager = DatabaseManager.getInstance();
if (dataManager.hasDatabase()) {
DatabaseManager dbManager = DatabaseManager.getInstance();
long globalPlaytime = dbManager.getTotalPlaytime(targetUUID);
long globalRawtime = dbManager.getTotalRawtime(targetUUID);
long globalAfktime = dbManager.getTotalAfktime(targetUUID);
long globalPlaytime = dbManager.getTotalPlaytime(targetUUID);
long globalRawtime = dbManager.getTotalRawtime(targetUUID);
long globalAfktime = dbManager.getTotalAfktime(targetUUID);
replacements.put(
"%global_playtime%",
timeManager.buildFormat(globalPlaytime)
);
replacements.put(
"%global_rawtime%",
timeManager.buildFormat(globalRawtime)
);
replacements.put(
"%global_afktime%",
timeManager.buildFormat(globalAfktime)
);
}
replacements.put(
"%global_playtime%",
timeManager.buildFormat(globalPlaytime)
);
replacements.put(
"%global_rawtime%",
timeManager.buildFormat(globalRawtime)
);
replacements.put(
"%global_afktime%",
timeManager.buildFormat(globalAfktime)
);
}
return replacements;
}
return replacements;
}
/**
* Processes the message by applying the necessary replacements.
*
* @param message The original message.
* @param replacements The map of placeholders and their replacements.
* @return A list of processed messages.
*/
private List<String> processMessageWithReplacements(
List<String> message,
Map<String, String> replacements
) {
List<String> newMessage = new ArrayList<>();
boolean hasDatabase = dataManager.hasDatabase();
DatabaseManager dbManager = hasDatabase
? DatabaseManager.getInstance()
: null;
Pattern pattern = Pattern.compile("%(rawtime|afktime|playtime)_(\\w+)%");
/**
* Processes the message by applying the necessary replacements.
*
* @param message The original message.
* @param replacements The map of placeholders and their replacements.
* @return A list of processed messages.
*/
private List<String> processMessageWithReplacements(
List<String> message,
Map<String, String> replacements
) {
List<String> newMessage = new ArrayList<>();
boolean hasDatabase = dataManager.hasDatabase();
DatabaseManager dbManager = hasDatabase
? DatabaseManager.getInstance()
: null;
Pattern pattern = Pattern.compile("%(rawtime|afktime|playtime)_(\\w+)%");
for (String msg : message) {
StringBuffer sb = new StringBuffer();
if (hasDatabase && dbManager != null) {
Matcher matcher = pattern.matcher(msg);
while (matcher.find()) {
String placeholderType = matcher.group(1);
String serverId = matcher.group(2);
Long timeValue = 0L;
for (String msg : message) {
StringBuffer sb = new StringBuffer();
if (hasDatabase && dbManager != null) {
Matcher matcher = pattern.matcher(msg);
while (matcher.find()) {
String placeholderType = matcher.group(1);
String serverId = matcher.group(2);
Long timeValue = 0L;
if (dbManager.hasTimeForServer(targetUUID, serverId)) {
Map<String, Long> timeForServer = dbManager.getTimeForServer(
targetUUID,
serverId
);
timeValue =
timeForServer.getOrDefault(
placeholderType.equals("rawtime")
? "playtime"
: placeholderType,
0L
);
if (placeholderType.equals("playtime")) {
timeValue -= timeForServer.getOrDefault("afktime", 0L);
}
}
if (dbManager.hasTimeForServer(targetUUID, serverId)) {
Map<String, Long> timeForServer = dbManager.getTimeForServer(
targetUUID,
serverId
);
timeValue =
timeForServer.getOrDefault(
placeholderType.equals("rawtime")
? "playtime"
: placeholderType,
0L
);
if (placeholderType.equals("playtime")) {
timeValue -= timeForServer.getOrDefault("afktime", 0L);
}
}
matcher.appendReplacement(sb, timeManager.buildFormat(timeValue));
}
matcher.appendTail(sb);
msg = sb.toString();
}
matcher.appendReplacement(sb, timeManager.buildFormat(timeValue));
}
matcher.appendTail(sb);
msg = sb.toString();
}
for (Map.Entry<String, String> entry : replacements.entrySet()) {
msg =
msg.replace(
entry.getKey(),
entry.getValue() != null ? entry.getValue() : entry.getKey()
);
}
for (Map.Entry<String, String> entry : replacements.entrySet()) {
msg =
msg.replace(
entry.getKey(),
entry.getValue() != null ? entry.getValue() : entry.getKey()
);
}
newMessage.add(msg);
}
newMessage.add(msg);
}
return newMessage;
}
return newMessage;
}
/**
* Sends the formatted message to the sender.
* This method determines the appropriate method to send the message
* (e.g., standard chat message or JSON formatted tellraw command).
*/
public void sendMessageToTarget() {
for (String msg : message) {
String formattedMsg = ChatUtil.format(msg);
if (formattedMsg.contains("{\"text\":")) {
Bukkit.dispatchCommand(
Bukkit.getConsoleSender(),
"tellraw " + sender.getName() + " " + formattedMsg
);
} else {
sender.sendMessage(formattedMsg);
}
}
}
}
/**
* Sends the formatted message to the sender.
* This method determines the appropriate method to send the message
* (e.g., standard chat message or JSON formatted tellraw command).
*/
public void sendMessageToTarget() {
for (String msg : message) {
TextComponent formattedMsg = ChatUtil.format(msg);
if (formattedMsg.content().contains("{\"text\":")) {
Bukkit.dispatchCommand(
Bukkit.getConsoleSender(),
"tellraw " + sender.getName() + " " + formattedMsg
);
} else {
sender.sendMessage(formattedMsg);
}
}
}
}
@@ -1,36 +1,32 @@
package me.codedred.playtimes.server;
import com.google.gson.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
import me.codedred.playtimes.statistics.StatManager;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull;
import me.codedred.playtimes.statistics.StatManager;
public class ServerOnline implements ServerStatus {
@Override
public UUID getUUID(@NotNull String name) {
UUID uuid = Bukkit.getServer().getPlayer(name).getUniqueId();
if (!StatManager.getInstance().hasJoinedBefore(uuid)) {
return null;
}
return uuid;
}
@Override
public UUID getUUID(@NotNull String name) {
UUID uuid = Bukkit.getServer().getPlayer(name).getUniqueId();
if (!StatManager.getInstance().hasJoinedBefore(uuid)) {
return null;
}
return uuid;
}
@Override
public boolean isOnline() {
return true;
}
@Override
public boolean isOnline() {
return true;
}
@Override
public String getName(UUID uuid) throws IOException {
String new_name = Bukkit.getPlayer(uuid).getDisplayName();
return new_name;
}
@Override
public String getName(UUID uuid) throws IOException {
String new_name = Bukkit.getPlayer(uuid).getName();
return new_name;
}
}
@@ -1,57 +1,50 @@
package me.codedred.playtimes.utils;
import me.codedred.playtimes.data.DataManager;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import me.codedred.playtimes.data.DataManager;
import net.kyori.adventure.text.TextComponent;
public class ChatUtil {
private static final DataManager DATA_MANAGER = DataManager.getInstance();
private static final boolean IS_NEWER_VERSION = ServerUtils.isNewerVersion();
private static final DataManager DATA_MANAGER = DataManager.getInstance();
public enum ChatTypes {
NO_PERMISSION,
PLAYER_NOT_FOUND,
PLAYER_NEVER_PLAYED,
}
public enum ChatTypes {
NO_PERMISSION,
PLAYER_NOT_FOUND,
PLAYER_NEVER_PLAYED,
}
public static void errno(CommandSender sender, ChatTypes type) {
switch (type) {
case NO_PERMISSION:
sender.sendMessage(
format(DATA_MANAGER.getConfig().getString("messages.noPermission"))
);
break;
case PLAYER_NOT_FOUND:
sender.sendMessage(
formatWithPrefix(
DATA_MANAGER.getConfig().getString("messages.player-not-found")
)
);
break;
case PLAYER_NEVER_PLAYED:
sender.sendMessage(
formatWithPrefix(
DATA_MANAGER.getConfig().getString("messages.player-never-joined")
)
);
break;
}
}
public static void errno(CommandSender sender, ChatTypes type) {
switch (type) {
case NO_PERMISSION:
sender.sendMessage(
format(DATA_MANAGER.getConfig().getString("messages.noPermission"))
);
break;
case PLAYER_NOT_FOUND:
sender.sendMessage(
formatWithPrefix(
DATA_MANAGER.getConfig().getString("messages.player-not-found")
)
);
break;
case PLAYER_NEVER_PLAYED:
sender.sendMessage(
formatWithPrefix(
DATA_MANAGER.getConfig().getString("messages.player-never-joined")
)
);
break;
}
}
public static String format(String msg) {
msg = ChatColor.translateAlternateColorCodes('&', msg);
if (IS_NEWER_VERSION) msg = HexUtil.hex(msg);
return msg;
}
public static TextComponent format(String msg) {
return OGUtils.legacySerializerAnyCase(msg);
}
public static String formatWithPrefix(String msg) {
msg =
ChatColor.translateAlternateColorCodes(
'&',
DATA_MANAGER.getConfig().getString("prefix") + " " + msg
);
if (IS_NEWER_VERSION) msg = HexUtil.hex(msg);
return msg;
}
}
public static TextComponent formatWithPrefix(String msg) {
return OGUtils.legacySerializerAnyCase(DATA_MANAGER.getConfig().getString("prefix") + msg);
}
}
@@ -0,0 +1,90 @@
package me.codedred.playtimes.utils;
import org.bukkit.entity.Player;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
public class OGUtils {
// Sends a formatted message to the player (including name replacement).
public static void playTimesOGMessage(Player p, String message) {
p.sendMessage(legacySerializerAnyCase((message)));
}
public static TextComponent legacySerializerAnyCase(String subject) {
int count = 0;
// Count the number of '&' characters to determine the size of the array
for (char c : subject.toCharArray()) {
if (c == '&') {
count++;
}
}
// Create an array to store the positions of '&' characters
int[] positions = new int[count];
int index = 0;
// Find the positions of '&' characters and store in the array
for (int i = 0; i < subject.length(); i++) {
if (subject.charAt(i) == '&') {
if (isUpperBukkitCode(subject.charAt(i + 1))) {
subject = replaceCharAtIndex(subject, (i + 1), Character.toLowerCase(subject.charAt(i + 1)));
}
positions[index++] = i;
}
}
return LegacyComponentSerializer.legacyAmpersand().deserialize(subject);
}
private static boolean isUpperBukkitCode(char input) {
char[] bukkitColorCodes = {'A', 'B', 'C', 'D', 'E', 'F', 'K', 'L', 'M', 'N', 'O', 'R'};
boolean match = false;
// Loop through each character in the array.
for (char c : bukkitColorCodes) {
// Check if the current character in the array is equal to the input character.
if (c == input) {
match = true;
}
}
return match;
}
private static String replaceCharAtIndex(String original, int index, char newChar) {
// Check if the index is valid
if (index >= 0 && index < original.length()) {
// Create a new string with the replaced character
return original.substring(0, index) + newChar + original.substring(index + 1);
}
// If the index is invalid, return the original string
return original;
}
}