3
0
Mirror von https://github.com/TheSilentPro/HeadDB.git synchronisiert 2024-12-25 18:40:07 +01:00

lots of improvements and bugfixes.

Dieser Commit ist enthalten in:
Silent 2023-11-14 23:28:56 +01:00
Ursprung 7ef5e30694
Commit e4691102ae
18 geänderte Dateien mit 312 neuen und 285 gelöschten Zeilen

22
pom.xml
Datei anzeigen

@ -6,7 +6,7 @@
<groupId>tsp.headdb</groupId>
<artifactId>HeadDB</artifactId>
<version>5.0.0-rc.7</version>
<version>5.0.0-rc.8</version>
<packaging>jar</packaging>
<name>HeadDB</name>
@ -55,13 +55,13 @@
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.20.1-R0.1-SNAPSHOT</version>
<version>1.20.2-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.mojang</groupId>
<artifactId>authlib</artifactId>
<version>1.5.21</version>
<version>4.0.43</version>
<scope>provided</scope>
</dependency>
<dependency>
@ -75,20 +75,26 @@
<dependency>
<groupId>com.github.TheSilentPro</groupId>
<artifactId>NexusLib</artifactId>
<version>c01a0a0a7d</version>
<version>9d8b16b770</version>
</dependency>
<dependency>
<groupId>com.github.TheSilentPro</groupId>
<artifactId>Warehouse</artifactId>
<version>882b42fc75</version>
<version>b228e4f8b1</version>
</dependency>
<dependency>
<groupId>net.wesjd</groupId>
<artifactId>anvilgui</artifactId>
<version>1.7.0-SNAPSHOT</version>
<version>1.9.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.github.TheSilentPro</groupId>
<artifactId>HelperLite</artifactId>
<version>775582f23b</version>
</dependency>
<!-- Soft Dependencies -->
<!--suppress VulnerableLibrariesLocal -->
<dependency>
<groupId>com.github.MilkBowl</groupId>
<artifactId>VaultAPI</artifactId>
@ -98,7 +104,7 @@
<dependency>
<groupId>me.clip</groupId>
<artifactId>placeholderapi</artifactId>
<version>2.11.1</version>
<version>2.11.5</version>
<scope>provided</scope>
</dependency>
</dependencies>
@ -183,7 +189,7 @@
<additionalDependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.20.1-R0.1-SNAPSHOT</version>
<version>1.20.2-R0.1-SNAPSHOT</version>
</additionalDependency>
</additionalDependencies>
</configuration>

Datei anzeigen

@ -7,17 +7,25 @@ import tsp.headdb.core.economy.VaultProvider;
import tsp.headdb.core.storage.Storage;
import tsp.headdb.core.task.UpdateTask;
import tsp.headdb.core.util.HeadDBLogger;
import tsp.helperlite.HelperLite;
import tsp.helperlite.Schedulers;
import tsp.helperlite.scheduler.promise.Promise;
import tsp.helperlite.scheduler.task.Task;
import tsp.nexuslib.NexusPlugin;
import tsp.nexuslib.inventory.PaneListener;
import tsp.nexuslib.localization.TranslatableLocalization;
import tsp.nexuslib.util.PluginUtils;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.text.DecimalFormat;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
public class HeadDB extends NexusPlugin {
@ -27,22 +35,27 @@ public class HeadDB extends NexusPlugin {
private Storage storage;
private BasicEconomyProvider economyProvider;
private CommandManager commandManager;
private Task updateTask;
@Override
public void onStart(NexusPlugin nexusPlugin) {
instance = this;
HelperLite.init(this);
instance.saveDefaultConfig();
instance.logger = new HeadDBLogger(getConfig().getBoolean("debug"));
instance.logger.info("Loading HeadDB - " + instance.getDescription().getVersion());
new UpdateTask(getConfig().getLong("refresh", 86400L)).schedule(this);
instance.logger.info("Loaded " + loadLocalization() + " languages!");
instance.initStorage();
instance.initEconomy();
startUpdateTask();
new PaneListener(this);
// TODO: Commands helperlite
instance.commandManager = new CommandManager();
loadCommands();
@ -66,8 +79,38 @@ public class HeadDB extends NexusPlugin {
}
}
}
updateTask.stop();
}
private void startUpdateTask() {
updateTask = Schedulers.builder()
.async()
.every(getConfig().getLong("refresh", 86400L), TimeUnit.SECONDS)
.run(new UpdateTask());
}
private void ensureLatestVersion() {
Promise.start().thenApplyAsync(a -> {
try {
URLConnection connection = new URL("https://api.spigotmc.org/legacy/update.php?resource=" + 84967).openConnection();
connection.setConnectTimeout(5000);
connection.setRequestProperty("User-Agent", this.getName() + "-VersionChecker");
return new BufferedReader(new InputStreamReader(connection.getInputStream())).readLine().equals(this.getDescription().getVersion());
} catch (IOException ex) {
return false;
}
}).thenAcceptAsync(latest -> {
if (latest) {
instance.logger.warning("There is a new update available for HeadDB on spigot!");
instance.logger.warning("Download: https://www.spigotmc.org/resources/84967");
}
});
}
// Loaders
private void initMetrics() {
Metrics metrics = new Metrics(this, 9152);
@ -80,19 +123,8 @@ public class HeadDB extends NexusPlugin {
}));
}
private void ensureLatestVersion() {
PluginUtils.isLatestVersion(this, 84967, latest -> {
if (Boolean.FALSE.equals(latest)) {
instance.logger.warning("There is a new update available for HeadDB on spigot!");
instance.logger.warning("Download: https://www.spigotmc.org/resources/84967");
}
});
}
// Loaders
private void initStorage() {
storage = new Storage(getConfig().getInt("storage.threads"));
storage = new Storage();
storage.getPlayerStorage().init();
}
@ -156,6 +188,10 @@ public class HeadDB extends NexusPlugin {
// Getters
public Optional<Task> getUpdateTask() {
return Optional.ofNullable(updateTask);
}
public Storage getStorage() {
return storage;
}

Datei anzeigen

@ -5,6 +5,7 @@ import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin;
import tsp.headdb.core.storage.HeadDBThreadFactory;
import javax.net.ssl.HttpsURLConnection;
import java.io.*;
@ -132,8 +133,7 @@ class Metrics {
/** The version of the Metrics class. */
public static final String METRICS_VERSION = "3.0.0";
private static final ScheduledExecutorService scheduler =
Executors.newScheduledThreadPool(1, task -> new Thread(task, "bStats-Metrics"));
private static final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(HeadDBThreadFactory.FACTORY);
private static final String REPORT_URL = "https://bStats.org/api/v2/data/%s";

Datei anzeigen

@ -9,6 +9,7 @@ import tsp.headdb.implementation.head.Head;
import tsp.headdb.implementation.head.HeadDatabase;
import tsp.headdb.implementation.head.LocalHead;
import tsp.headdb.implementation.requester.HeadProvider;
import tsp.helperlite.scheduler.promise.Promise;
import javax.annotation.Nonnull;
import java.util.*;
@ -163,14 +164,16 @@ public final class HeadAPI {
* @return {@link Set<Head> Favorite Heads}
*/
@Nonnull
public static List<Head> getFavoriteHeads(UUID player) {
List<Head> result = new ArrayList<>();
Optional<PlayerData> data = HeadDB.getInstance().getStorage().getPlayerStorage().get(player);
data.ifPresent(playerData -> playerData.favorites()
.forEach(texture -> getHeadByTexture(texture)
.ifPresent(result::add))
);
return result;
public static Promise<List<Head>> getFavoriteHeads(UUID player) {
return Promise.supplyingAsync(() -> {
List<Head> result = new ArrayList<>();
Optional<PlayerData> data = HeadDB.getInstance().getStorage().getPlayerStorage().get(player);
data.ifPresent(playerData -> playerData.favorites()
.forEach(texture -> getHeadByTexture(texture)
.ifPresent(result::add))
);
return result;
});
}
/**

Datei anzeigen

@ -1,8 +1,6 @@
package tsp.headdb.core.command;
import java.util.Arrays;
import net.wesjd.anvilgui.AnvilGUI;
import net.wesjd.anvilgui.AnvilGUI.Builder;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.command.Command;
@ -24,10 +22,7 @@ import tsp.nexuslib.util.StringUtils;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.*;
import java.util.stream.Collectors;
public class CommandMain extends HeadDBCommand implements CommandExecutor, TabCompleter {

Datei anzeigen

@ -1,9 +1,10 @@
package tsp.headdb.core.command;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import tsp.headdb.HeadDB;
import tsp.headdb.core.api.HeadAPI;
import tsp.headdb.implementation.head.HeadResult;
import tsp.helperlite.scheduler.promise.Promise;
public class CommandUpdate extends SubCommand {
@ -14,10 +15,14 @@ public class CommandUpdate extends SubCommand {
@Override
public void handle(CommandSender sender, String[] args) {
getLocalization().sendMessage(sender, "updateDatabase");
HeadAPI.getDatabase().update((time, result) -> {
HeadDB.getInstance().getLog().debug("Database Updated! Heads: " + result.values().size() + " | Took: " + time + "ms");
Bukkit.getScheduler().runTask(HeadDB.getInstance(), () -> getLocalization().sendMessage(sender, "updateDatabaseDone", msg -> msg.replace("%size%", String.valueOf(result.values().size()))));
});
try (Promise<HeadResult> promise = HeadAPI.getDatabase().update()) {
promise.thenAcceptSync(result -> {
HeadDB.getInstance().getLog().debug("Database Updated! Heads: " + result.heads().values().size() + " | Took: " + result.elapsed() + "ms");
getLocalization().sendMessage(sender, "updateDatabaseDone", msg -> msg.replace("%size%", String.valueOf(result.heads().values().size())));
});
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
}

Datei anzeigen

@ -1,18 +1,18 @@
package tsp.headdb.core.economy;
import org.bukkit.entity.Player;
import tsp.helperlite.scheduler.promise.Promise;
import java.math.BigDecimal;
import java.util.concurrent.CompletableFuture;
public interface BasicEconomyProvider {
CompletableFuture<Boolean> canPurchase(Player player, BigDecimal cost);
Promise<Boolean> canPurchase(Player player, BigDecimal cost);
CompletableFuture<Boolean> withdraw(Player player, BigDecimal amount);
Promise<Boolean> withdraw(Player player, BigDecimal amount);
default CompletableFuture<Boolean> purchase(Player player, BigDecimal amount) {
return canPurchase(player, amount).thenCompose(result -> result ? withdraw(player, amount) : CompletableFuture.completedFuture(false));
default Promise<Boolean> purchase(Player player, BigDecimal amount) {
return canPurchase(player, amount).thenComposeAsync(result -> result ? withdraw(player, amount) : Promise.completed(false));
}
void init();

Datei anzeigen

@ -5,24 +5,24 @@ import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.RegisteredServiceProvider;
import tsp.headdb.HeadDB;
import tsp.helperlite.scheduler.promise.Promise;
import java.math.BigDecimal;
import java.util.concurrent.CompletableFuture;
public class VaultProvider implements BasicEconomyProvider {
private Economy economy;
@Override
public CompletableFuture<Boolean> canPurchase(Player player, BigDecimal cost) {
public Promise<Boolean> canPurchase(Player player, BigDecimal cost) {
double effectiveCost = cost.doubleValue();
return CompletableFuture.supplyAsync(() -> economy.has(player, effectiveCost >= 0 ? effectiveCost : 0));
return Promise.supplyingAsync(() -> economy.has(player, effectiveCost >= 0 ? effectiveCost : 0));
}
@Override
public CompletableFuture<Boolean> withdraw(Player player, BigDecimal amount) {
public Promise<Boolean> withdraw(Player player, BigDecimal amount) {
double effectiveCost = amount.doubleValue();
return CompletableFuture.supplyAsync(() -> economy.withdrawPlayer(player, effectiveCost >= 0 ? effectiveCost : 0).transactionSuccess());
return Promise.supplyingAsync(() -> economy.withdrawPlayer(player, effectiveCost >= 0 ? effectiveCost : 0).transactionSuccess());
}

Datei anzeigen

@ -44,7 +44,7 @@ public class PlayerStorage extends SerializableFileDataManager<HashSet<PlayerDat
public void init() {
load().whenComplete((data, ex) -> {
for (PlayerData entry : data) {
for (PlayerData entry : data.orElse(new HashSet<>())) {
players.put(entry.uniqueId(), entry);
}

Datei anzeigen

@ -1,6 +1,7 @@
package tsp.headdb.core.storage;
import tsp.headdb.HeadDB;
import tsp.helperlite.Schedulers;
import java.io.File;
import java.util.concurrent.Executor;
@ -11,8 +12,8 @@ public class Storage {
private final Executor executor;
private final PlayerStorage playerStorage;
public Storage(int threads) {
executor = Executors.newFixedThreadPool(threads, HeadDBThreadFactory.FACTORY);
public Storage() {
executor = Schedulers.async();
validateDataDirectory();
playerStorage = new PlayerStorage(HeadDB.getInstance(), this);
}

Datei anzeigen

@ -4,51 +4,44 @@ import org.bukkit.Bukkit;
import tsp.headdb.HeadDB;
import tsp.headdb.core.api.HeadAPI;
import tsp.headdb.core.api.events.AsyncHeadsFetchedEvent;
import tsp.headdb.implementation.category.Category;
import tsp.headdb.implementation.head.Head;
import tsp.nexuslib.task.Task;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
public class UpdateTask implements Task {
private final long interval;
public UpdateTask(long interval) {
this.interval = interval;
}
public class UpdateTask implements Runnable {
@Override
public void run() {
HeadAPI.getDatabase().update((time, heads) -> {
int size = 0;
for (List<Head> list : heads.values()) {
for (Head ignored : list) {
size++;
}
}
HeadAPI.getDatabase().update().thenAcceptAsync(result -> {
HeadDB instance = HeadDB.getInstance();
String providerName = HeadAPI.getDatabase().getRequester().getProvider().name();
HeadDB.getInstance().getLog().debug("Fetched: " + size + " Heads | Provider: " + providerName + " | Time: " + time + "ms (" + TimeUnit.MILLISECONDS.toSeconds(time) + "s)");
instance.getLog().debug("Fetched: " + getHeadsCount(result.heads()) + " Heads | Provider: " + providerName + " | Time: " + result.elapsed() + "ms (" + TimeUnit.MILLISECONDS.toSeconds(result.elapsed()) + "s)");
Bukkit.getPluginManager().callEvent(
new AsyncHeadsFetchedEvent(
heads,
result.heads(),
providerName,
time));
result.elapsed()));
instance.getStorage().getPlayerStorage().backup();
instance.getUpdateTask().ifPresentOrElse(task -> {
instance.getLog().debug("UpdateTask completed! Times ran: " + task.getTimesRan());
}, () -> instance.getLog().debug("Initial UpdateTask completed!"));
});
HeadDB.getInstance().getStorage().getPlayerStorage().backup();
HeadDB.getInstance().getLog().debug("UpdateTask finished!");
}
@Override
public long getRepeatInterval() {
return interval;
}
private int getHeadsCount(Map<Category, List<Head>> heads) {
int n = 0;
for (List<Head> list : heads.values()) {
for (int i = 0; i < list.size(); i++) {
n++;
}
}
@Override
public boolean isAsync() {
return true;
return n;
}
}

Datei anzeigen

@ -1,63 +1,12 @@
package tsp.headdb.core.util;
import org.bukkit.Bukkit;
import tsp.nexuslib.util.StringUtils;
import tsp.nexuslib.logger.NexusLogger;
@SuppressWarnings("unused")
public class HeadDBLogger {
private final boolean debug;
public class HeadDBLogger extends NexusLogger {
public HeadDBLogger(boolean debug) {
this.debug = debug;
}
public void info(String message) {
this.log(LogLevel.INFO, message);
}
public void warning(String message) {
this.log(LogLevel.WARNING, message);
}
public void error(String message) {
this.log(LogLevel.ERROR, message);
}
public void debug(String message) {
this.log(LogLevel.DEBUG, message);
}
public void trace(String message) {
this.log(LogLevel.TRACE, message);
}
public void log(LogLevel level, String message) {
if ((level == LogLevel.DEBUG || level == LogLevel.TRACE) && !debug) {
return;
}
Bukkit.getConsoleSender().sendMessage(StringUtils.colorize("&cHeadDB &8>> " + level.getColor() + "[" + level.name() + "]: " + message));
}
public boolean isDebug() {
return this.debug;
}
public enum LogLevel {
INFO,
WARNING,
ERROR,
DEBUG,
TRACE;
public String getColor() {
return switch (this) {
case INFO -> "\u001b[32m";
case WARNING -> "\u001b[33m";
case ERROR -> "\u001b[31m";
case DEBUG -> "\u001b[36m";
case TRACE -> "\u001b[35m";
};
}
super("HeadDB", debug);
}
}

Datei anzeigen

@ -10,15 +10,22 @@ import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.SkullMeta;
import org.bukkit.profile.PlayerProfile;
import org.bukkit.profile.PlayerTextures;
import tsp.headdb.HeadDB;
import tsp.headdb.core.api.HeadAPI;
import tsp.headdb.core.economy.BasicEconomyProvider;
import tsp.headdb.core.hook.Hooks;
import tsp.headdb.implementation.category.Category;
import tsp.headdb.implementation.head.Head;
import tsp.helperlite.scheduler.promise.Promise;
import tsp.nexuslib.builder.ItemBuilder;
import tsp.nexuslib.inventory.Button;
import tsp.nexuslib.inventory.PagedPane;
import tsp.nexuslib.inventory.Pane;
import tsp.nexuslib.localization.TranslatableLocalization;
import tsp.nexuslib.server.ServerVersion;
import tsp.nexuslib.util.StringUtils;
import tsp.nexuslib.util.Validate;
@ -27,8 +34,9 @@ import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
import java.util.concurrent.CompletableFuture;
public class Utils {
@ -108,31 +116,36 @@ public class Utils {
}
public static void openFavoritesMenu(Player player) {
List<Head> heads = HeadAPI.getFavoriteHeads(player.getUniqueId());
PagedPane main = Utils.createPaged(player, Utils.translateTitle(HeadDB.getInstance().getLocalization().getMessage(player.getUniqueId(), "menu.main.favorites.name").orElse("Favorites"), heads.size(), "Favorites"));
for (Head head : heads) {
main.addButton(new Button(head.getItem(player.getUniqueId()), fe -> {
if (!player.hasPermission("headdb.favorites")) {
HeadDB.getInstance().getLocalization().sendMessage(player, "noAccessFavorites");
return;
try (Promise<List<Head>> promise = HeadAPI.getFavoriteHeads(player.getUniqueId())) {
promise.thenAcceptSync(heads -> {
PagedPane main = Utils.createPaged(player, Utils.translateTitle(HeadDB.getInstance().getLocalization().getMessage(player.getUniqueId(), "menu.main.favorites.name").orElse("Favorites"), heads.size(), "Favorites"));
for (Head head : heads) {
main.addButton(new Button(head.getItem(player.getUniqueId()), fe -> {
if (!player.hasPermission("headdb.favorites")) {
HeadDB.getInstance().getLocalization().sendMessage(player, "noAccessFavorites");
return;
}
if (fe.isLeftClick()) {
int amount = 1;
if (fe.isShiftClick()) {
amount = 64;
}
Utils.purchase(player, head, amount);
} else if (fe.isRightClick()) {
HeadDB.getInstance().getStorage().getPlayerStorage().removeFavorite(player.getUniqueId(), head.getTexture());
HeadDB.getInstance().getLocalization().sendMessage(player, "removedFavorite", msg -> msg.replace("%name%", head.getName()));
openFavoritesMenu(player);
}
}));
}
if (fe.isLeftClick()) {
int amount = 1;
if (fe.isShiftClick()) {
amount = 64;
}
Utils.purchase(player, head, amount);
} else if (fe.isRightClick()) {
HeadDB.getInstance().getStorage().getPlayerStorage().removeFavorite(player.getUniqueId(), head.getTexture());
HeadDB.getInstance().getLocalization().sendMessage(player, "removedFavorite", msg -> msg.replace("%name%", head.getName()));
openFavoritesMenu(player);
}
}));
main.open(player);
});
} catch (Exception ex) {
ex.printStackTrace();
}
main.open(player);
}
@ParametersAreNonnullByDefault
@ -166,10 +179,10 @@ public class Utils {
}
}
private static CompletableFuture<Boolean> processPayment(Player player, Head head, int amount) {
private static Promise<Boolean> processPayment(Player player, Head head, int amount) {
Optional<BasicEconomyProvider> optional = HeadDB.getInstance().getEconomyProvider();
if (optional.isEmpty()) {
return CompletableFuture.completedFuture(true); // No economy, the head is free
return Promise.completed(true); // No economy, the head is free
} else {
BigDecimal cost = BigDecimal.valueOf(HeadDB.getInstance().getConfig().getDouble("economy.cost." + head.getCategory().getName()) * amount);
HeadDB.getInstance().getLocalization().sendMessage(player.getUniqueId(), "processPayment", msg -> msg
@ -177,8 +190,10 @@ public class Utils {
.replace("%amount%", String.valueOf(amount))
.replace("%cost%", HeadDB.getInstance().getDecimalFormat().format(cost))
);
return optional.get().purchase(player, cost).thenApply(success -> {
Bukkit.getScheduler().runTask(HeadDB.getInstance(), () -> {
try (Promise<Boolean> economyPromise = optional.get().purchase(player, cost)) {
// TODO: Might not need to be sync.
return economyPromise.thenApplySync((success) -> {
if (success) {
HeadDB.getInstance().getLocalization().sendMessage(player, "completePayment", msg -> msg
.replace("%name%", head.getName())
@ -186,46 +201,38 @@ public class Utils {
} else {
HeadDB.getInstance().getLocalization().sendMessage(player, "invalidFunds", msg -> msg.replace("%name%", head.getName()));
}
return success;
});
return success;
/* Note: Issues caused by sync call to async event but when run async above method fucks up.
Bukkit.getScheduler().runTaskAsynchronously(HeadDB.getInstance(), () -> {
HeadPurchaseEvent event = new HeadPurchaseEvent(player, head, cost, success);
Bukkit.getPluginManager().callEvent(event);
});
return true;
*/
});
} catch (Exception ex) {
HeadDB.getInstance().getLog().severe("Failed to process payment: " + ex.getMessage());
return Promise.exceptionally(ex);
}
}
}
public static void purchase(Player player, Head head, int amount) {
processPayment(player, head, amount).whenComplete((success, ex) -> {
if (ex != null) {
HeadDB.getInstance().getLog().error("Failed to purchase head '" + head.getName() + "' for player: " + player.getName());
ex.printStackTrace();
} else {
// Bukkit API, therefore task is ran sync.
Bukkit.getScheduler().runTask(HeadDB.getInstance(), () -> {
if (success) {
ItemStack item = head.getItem(player.getUniqueId());
item.setAmount(amount);
player.getInventory().addItem(item);
HeadDB.getInstance().getConfig().getStringList("commands.purchase").forEach(command -> {
if (command.isEmpty()) {
return;
}
if (Hooks.PAPI.enabled()) {
command = PlaceholderAPI.setPlaceholders(player, command);
}
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command);
});
// Bukkit API - Has to be sync.
try (Promise<Boolean> paymentPromise = processPayment(player, head, amount)) {
paymentPromise.thenAcceptSync((success) -> {
if (success) {
ItemStack item = head.getItem(player.getUniqueId());
item.setAmount(amount);
player.getInventory().addItem(item);
HeadDB.getInstance().getConfig().getStringList("commands.purchase").forEach(command -> {
if (command.isEmpty()) {
return;
}
if (Hooks.PAPI.enabled()) {
command = PlaceholderAPI.setPlaceholders(player, command);
}
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command);
});
}
});
}
});
} catch (Exception ex) {
ex.printStackTrace();
}
}
public static Optional<String> getTexture(ItemStack head) {
@ -252,6 +259,51 @@ public class Utils {
}
}
public static ItemStack asItem(UUID receiver, Head head) {
TranslatableLocalization localization = HeadDB.getInstance().getLocalization();
ItemStack item = new ItemBuilder(Material.PLAYER_HEAD)
.name(localization.getMessage(receiver, "menu.head.name").orElse("&e" + head.getName().toUpperCase(Locale.ROOT)).replace("%name%", head.getName()))
.setLore("&cID: " + head.getId(), "&7Tags: &e" + head.getTags())
.build();
ItemMeta meta = item.getItemMeta();
// if version < 1.20.1 use reflection, else (1.20.2+) use PlayerProfile because spigot bitches otherwise.
if (ServerVersion.getVersion().orElse(ServerVersion.v_1_20_1).isOlderThan(ServerVersion.v_1_20_2)) {
try {
GameProfile profile = new GameProfile(head.getUniqueId(), head.getName());
profile.getProperties().put("textures", new Property("textures", head.getTexture()));
//noinspection DataFlowIssue
Field profileField = meta.getClass().getDeclaredField("profile");
profileField.setAccessible(true);
profileField.set(meta, profile);
item.setItemMeta(meta);
} catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException ex) {
//Log.error("Could not set skull owner for " + uuid.toString() + " | Stack Trace:");
ex.printStackTrace();
}
} else {
try {
PlayerProfile profile = Bukkit.createPlayerProfile(head.getUniqueId(), head.getName());
PlayerTextures textures = profile.getTextures();
String url = new String(Base64.getDecoder().decode(head.getTexture()));
textures.setSkin(new URL(url.substring("{\"textures\":{\"SKIN\":{\"url\":\"".length(), url.length() - "\"}}}".length())));
profile.setTextures(textures);
SkullMeta skullMeta = (SkullMeta) meta;
if (skullMeta != null) {
skullMeta.setOwnerProfile(profile);
}
item.setItemMeta(skullMeta);
} catch (MalformedURLException ex) {
ex.printStackTrace();
}
}
return item;
}
public static int resolveInt(String raw) {
try {
return Integer.parseInt(raw);

Datei anzeigen

@ -1,19 +1,11 @@
package tsp.headdb.implementation.head;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import tsp.headdb.HeadDB;
import tsp.headdb.core.util.Utils;
import tsp.headdb.implementation.category.Category;
import tsp.nexuslib.builder.ItemBuilder;
import tsp.nexuslib.localization.TranslatableLocalization;
import tsp.nexuslib.util.Validate;
import javax.annotation.ParametersAreNonnullByDefault;
import java.lang.reflect.Field;
import java.util.Locale;
import java.util.UUID;
public class Head {
@ -47,26 +39,7 @@ public class Head {
public ItemStack getItem(UUID receiver) {
if (item == null) {
TranslatableLocalization localization = HeadDB.getInstance().getLocalization();
item = new ItemBuilder(Material.PLAYER_HEAD)
.name(localization.getMessage(receiver, "menu.head.name").orElse("&e" + name.toUpperCase(Locale.ROOT)).replace("%name%", name))
.setLore("&cID: " + id, "&7Tags: &e" + tags)
.build();
ItemMeta meta = item.getItemMeta();
GameProfile profile = new GameProfile(uniqueId, name);
profile.getProperties().put("textures", new Property("textures", texture));
try {
//noinspection DataFlowIssue
Field profileField = meta.getClass().getDeclaredField("profile");
profileField.setAccessible(true);
profileField.set(meta, profile);
} catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException ex) {
//Log.error("Could not set skull owner for " + uuid.toString() + " | Stack Trace:");
ex.printStackTrace();
}
item.setItemMeta(meta);
item = Utils.asItem(receiver, this);
}
return item.clone(); // Return clone that changes are not reflected

Datei anzeigen

@ -1,53 +1,59 @@
package tsp.headdb.implementation.head;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitScheduler;
import tsp.headdb.implementation.category.Category;
import tsp.headdb.implementation.requester.HeadProvider;
import tsp.headdb.implementation.requester.Requester;
import tsp.helperlite.scheduler.promise.Promise;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiConsumer;
public class HeadDatabase {
private final JavaPlugin plugin;
private final BukkitScheduler scheduler;
private final Requester requester;
private final ConcurrentHashMap<Category, List<Head>> heads;
private long timestamp;
public HeadDatabase(JavaPlugin plugin, HeadProvider provider) {
this.plugin = plugin;
this.scheduler = plugin.getServer().getScheduler();
this.requester = new Requester(plugin, provider);
this.heads = new ConcurrentHashMap<>();
// Fill empty
for (Category cat : Category.VALUES) {
heads.put(cat, new ArrayList<>());
}
}
public Map<Category, List<Head>> getHeads() {
return heads;
}
public void getHeadsNoCache(BiConsumer<Long, Map<Category, List<Head>>> heads) {
getScheduler().runTaskAsynchronously(plugin, () -> {
public Promise<HeadResult> getHeadsNoCache() {
return Promise.supplyingAsync(() -> {
long start = System.currentTimeMillis();
Map<Category, List<Head>> result = new HashMap<>();
for (Category category : Category.VALUES) {
requester.fetchAndResolve(category, response -> result.put(category, response));
result.put(category, requester.fetchAndResolve(category));
}
heads.accept(System.currentTimeMillis() - start, result);
return new HeadResult(System.currentTimeMillis() - start, result);
});
}
public void update(BiConsumer<Long, Map<Category, List<Head>>> fetched) {
getHeadsNoCache((elapsed, result) -> {
heads.putAll(result);
timestamp = System.currentTimeMillis();
fetched.accept(elapsed, result);
public Promise<HeadResult> update() {
return Promise.start()
.thenComposeAsync(compose -> getHeadsNoCache())
.thenApplyAsync(result -> {
heads.clear();
heads.putAll(result.heads());
timestamp = System.currentTimeMillis();
return result;
});
}
@ -59,10 +65,6 @@ public class HeadDatabase {
return plugin;
}
public BukkitScheduler getScheduler() {
return scheduler;
}
public Requester getRequester() {
return requester;
}

Datei anzeigen

@ -0,0 +1,11 @@
package tsp.headdb.implementation.head;
import tsp.headdb.implementation.category.Category;
import java.util.List;
import java.util.Map;
/**
* @author TheSilentPro (Silent)
*/
public record HeadResult(long elapsed, Map<Category, List<Head>> heads) {}

Datei anzeigen

@ -18,8 +18,14 @@ import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.function.Consumer;
/**
* Responsible for requesting heads from providers.
*
* @author TheSilentPro
* @see tsp.headdb.core.api.HeadAPI
* @see tsp.headdb.implementation.head.HeadDatabase
*/
public class Requester {
private final JavaPlugin plugin;
@ -30,51 +36,52 @@ public class Requester {
this.provider = provider;
}
public void fetchAndResolve(Category category, Consumer<List<Head>> heads) {
public List<Head> fetchAndResolve(Category category) {
try {
fetch(category, response -> {
List<Head> result = new ArrayList<>();
if (response.code() != 200) {
heads.accept(result);
return;
}
Response response = fetch(category);
List<Head> result = new ArrayList<>();
if (response.code() != 200) {
return result;
}
JsonArray main = JsonParser.parseString(response.response()).getAsJsonArray();
for (JsonElement entry : main) {
JsonObject obj = entry.getAsJsonObject();
int id = obj.get("id").getAsInt();
JsonArray main = JsonParser.parseString(response.response()).getAsJsonArray();
for (JsonElement entry : main) {
JsonObject obj = entry.getAsJsonObject();
int id = obj.get("id").getAsInt();
if (plugin.getConfig().contains("blockedHeads.ids")) {
List<Integer> blockedIds = plugin.getConfig().getIntegerList("blockedHeads.ids");
if (blockedIds.contains(id)) {
HeadDB.getInstance().getLog().debug("Skipped blocked head: " + obj.get("name").getAsString() + "(" + id + ")");
continue;
}
if (plugin.getConfig().contains("blockedHeads.ids")) {
List<Integer> blockedIds = plugin.getConfig().getIntegerList("blockedHeads.ids");
if (blockedIds.contains(id)) {
HeadDB.getInstance().getLog().debug("Skipped blocked head: " + obj.get("name").getAsString() + "(" + id + ")");
continue;
}
result.add(new Head(
id,
Utils.validateUniqueId(obj.get("uuid").getAsString()).orElse(UUID.randomUUID()),
obj.get("name").getAsString(),
obj.get("value").getAsString(),
obj.get("tags").getAsString(),
response.date(),
category
));
}
heads.accept(result);
});
result.add(new Head(
id,
Utils.validateUniqueId(obj.get("uuid").getAsString()).orElse(UUID.randomUUID()),
obj.get("name").getAsString(),
obj.get("value").getAsString(),
obj.get("tags").getAsString(),
response.date(),
category
));
}
return result;
} catch (IOException ex) {
HeadDB.getInstance().getLog().debug("Failed to load from provider: " + provider.name());
if (HeadDB.getInstance().getConfig().getBoolean("fallback") && provider != HeadProvider.HEAD_ARCHIVE) { // prevent recursion. Maybe switch to an attempts counter down in the future
provider = HeadProvider.HEAD_ARCHIVE;
fetchAndResolve(category, heads);
return fetchAndResolve(category);
} else {
HeadDB.getInstance().getLog().error("Could not fetch heads from any provider!");
return new ArrayList<>();
}
}
}
public void fetch(Category category, Consumer<Response> response) throws IOException {
public Response fetch(Category category) throws IOException {
HttpURLConnection connection = (HttpURLConnection) new URL(provider.getFormattedUrl(category)).openConnection();
connection.setConnectTimeout(5000);
connection.setRequestMethod("GET");
@ -88,10 +95,9 @@ public class Requester {
builder.append(line);
}
response.accept(new Response(builder.toString(), connection.getResponseCode(), connection.getHeaderField("date")));
connection.disconnect();
return new Response(builder.toString(), connection.getResponseCode(), connection.getHeaderField("date"));
}
connection.disconnect();
}
public HeadProvider getProvider() {

Datei anzeigen

@ -90,10 +90,5 @@ blockedHeads:
ids:
- -1
# Storage Options
storage:
# Amount of threads in the executor pool used for storage.
threads: 2
# Debug Mode
debug: false