diff options
| author | Cow <cow@volloeko.de> | 2020-07-28 00:12:36 +0200 |
|---|---|---|
| committer | Cow <cow@volloeko.de> | 2020-07-28 00:12:36 +0200 |
| commit | b393636cb3f7e05ef8b34804eeb06357f1b9cfbe (patch) | |
| tree | d754561fd2e2f09ac66f41b2645ac5f351c1cace /src/main/java/de/cowtipper/cowlection/util | |
| parent | 023589c75ae72ddc5ff75fa7235bce4d102b2ad1 (diff) | |
| download | Cowlection-b393636cb3f7e05ef8b34804eeb06357f1b9cfbe.tar.gz Cowlection-b393636cb3f7e05ef8b34804eeb06357f1b9cfbe.tar.bz2 Cowlection-b393636cb3f7e05ef8b34804eeb06357f1b9cfbe.zip | |
Renamed package to match cowtipper.de
Diffstat (limited to 'src/main/java/de/cowtipper/cowlection/util')
8 files changed, 993 insertions, 0 deletions
diff --git a/src/main/java/de/cowtipper/cowlection/util/ApiUtils.java b/src/main/java/de/cowtipper/cowlection/util/ApiUtils.java new file mode 100644 index 0000000..763084d --- /dev/null +++ b/src/main/java/de/cowtipper/cowlection/util/ApiUtils.java @@ -0,0 +1,139 @@ +package de.cowtipper.cowlection.util; + +import com.google.gson.JsonArray; +import com.google.gson.JsonParser; +import com.google.gson.JsonSyntaxException; +import com.mojang.util.UUIDTypeAdapter; +import de.cowtipper.cowlection.Cowlection; +import de.cowtipper.cowlection.command.exception.ThrowingConsumer; +import de.cowtipper.cowlection.config.MooConfig; +import de.cowtipper.cowlection.data.Friend; +import de.cowtipper.cowlection.data.HyPlayerData; +import de.cowtipper.cowlection.data.HySkyBlockStats; +import de.cowtipper.cowlection.data.HyStalkingData; +import org.apache.http.HttpStatus; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class ApiUtils { + public static final String UUID_NOT_FOUND = "UUID-NOT-FOUND"; + private static final String NAME_TO_UUID_URL = "https://api.mojang.com/users/profiles/minecraft/"; + private static final String UUID_TO_NAME_URL = "https://api.mojang.com/user/profiles/%s/names"; + private static final String ONLINE_STATUS_URL = "https://api.hypixel.net/status?key=%s&uuid=%s"; + private static final String SKYBLOCK_STATS_URL = "https://api.hypixel.net/skyblock/profiles?key=%s&uuid=%s"; + private static final String PLAYER_URL = "https://api.hypixel.net/player?key=%s&uuid=%s"; + private static final ExecutorService pool = Executors.newCachedThreadPool(); + + private ApiUtils() { + } + + public static void fetchFriendData(String name, ThrowingConsumer<Friend> action) { + pool.execute(() -> action.accept(getFriend(name))); + } + + private static Friend getFriend(String name) { + try (BufferedReader reader = makeApiCall(NAME_TO_UUID_URL + name)) { + if (reader == null) { + return Friend.FRIEND_NOT_FOUND; + } else { + return GsonUtils.fromJson(reader, Friend.class); + } + } catch (IOException | JsonSyntaxException e) { + e.printStackTrace(); + } + return null; + } + + public static void fetchCurrentName(Friend friend, ThrowingConsumer<String> action) { + pool.execute(() -> action.accept(getCurrentName(friend))); + } + + private static String getCurrentName(Friend friend) { + try (BufferedReader reader = makeApiCall(String.format(UUID_TO_NAME_URL, UUIDTypeAdapter.fromUUID(friend.getUuid())))) { + if (reader == null) { + return UUID_NOT_FOUND; + } else { + JsonArray nameHistoryData = new JsonParser().parse(reader).getAsJsonArray(); + if (nameHistoryData.size() > 0) { + return nameHistoryData.get(nameHistoryData.size() - 1).getAsJsonObject().get("name").getAsString(); + } + } + } catch (IOException | JsonSyntaxException e) { + e.printStackTrace(); + } + return null; + } + + public static void fetchPlayerStatus(Friend friend, ThrowingConsumer<HyStalkingData> action) { + pool.execute(() -> action.accept(stalkPlayer(friend))); + } + + private static HyStalkingData stalkPlayer(Friend friend) { + try (BufferedReader reader = makeApiCall(String.format(ONLINE_STATUS_URL, MooConfig.moo, UUIDTypeAdapter.fromUUID(friend.getUuid())))) { + if (reader != null) { + return GsonUtils.fromJson(reader, HyStalkingData.class); + } + } catch (IOException | JsonSyntaxException e) { + e.printStackTrace(); + } + return null; + } + + public static void fetchSkyBlockStats(Friend friend, ThrowingConsumer<HySkyBlockStats> action) { + pool.execute(() -> action.accept(stalkSkyBlockStats(friend))); + } + + private static HySkyBlockStats stalkSkyBlockStats(Friend friend) { + try (BufferedReader reader = makeApiCall(String.format(SKYBLOCK_STATS_URL, MooConfig.moo, UUIDTypeAdapter.fromUUID(friend.getUuid())))) { + if (reader != null) { + return GsonUtils.fromJson(reader, HySkyBlockStats.class); + } + } catch (IOException | JsonSyntaxException e) { + e.printStackTrace(); + } + return null; + } + + public static void fetchPlayerOfflineStatus(Friend stalkedPlayer, ThrowingConsumer<HyPlayerData> action) { + pool.execute(() -> action.accept(stalkOfflinePlayer(stalkedPlayer))); + } + + private static HyPlayerData stalkOfflinePlayer(Friend stalkedPlayer) { + try (BufferedReader reader = makeApiCall(String.format(PLAYER_URL, MooConfig.moo, UUIDTypeAdapter.fromUUID(stalkedPlayer.getUuid())))) { + if (reader != null) { + return GsonUtils.fromJson(reader, HyPlayerData.class); + } + } catch (IOException | JsonSyntaxException e) { + e.printStackTrace(); + } + return null; + } + + private static BufferedReader makeApiCall(String url) throws IOException { + HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection(); + connection.setConnectTimeout(5000); + connection.setReadTimeout(10000); + connection.addRequestProperty("User-Agent", "Forge Mod " + Cowlection.MODNAME + "/" + Cowlection.VERSION + " (" + Cowlection.GITURL + ")"); + + connection.getResponseCode(); + if (connection.getResponseCode() == HttpStatus.SC_NO_CONTENT) { // http status 204 + return null; + } else { + BufferedReader reader; + InputStream errorStream = connection.getErrorStream(); + if (errorStream != null) { + reader = new BufferedReader(new InputStreamReader(errorStream)); + } else { + reader = new BufferedReader(new InputStreamReader(connection.getInputStream())); + } + return reader; + } + } +} diff --git a/src/main/java/de/cowtipper/cowlection/util/ChatHelper.java b/src/main/java/de/cowtipper/cowlection/util/ChatHelper.java new file mode 100644 index 0000000..28d20ab --- /dev/null +++ b/src/main/java/de/cowtipper/cowlection/util/ChatHelper.java @@ -0,0 +1,82 @@ +package de.cowtipper.cowlection.util; + +import net.minecraft.client.Minecraft; +import net.minecraft.util.ChatComponentText; +import net.minecraft.util.ChatStyle; +import net.minecraft.util.EnumChatFormatting; +import net.minecraft.util.IChatComponent; +import net.minecraftforge.client.event.ClientChatReceivedEvent; +import net.minecraftforge.common.MinecraftForge; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class ChatHelper { + private static final Pattern USELESS_JSON_CONTENT_PATTERN = Pattern.compile("\"[A-Za-z]+\":false,?"); + private static final int DISPLAY_DURATION = 5000; + private final List<IChatComponent> offlineMessages = new ArrayList<>(); + private String[] aboveChatMessage; + private long aboveChatMessageExpiration; + + public ChatHelper() { + } + + public void sendMessage(EnumChatFormatting color, String text) { + sendMessage(new ChatComponentText(text).setChatStyle(new ChatStyle().setColor(color))); + } + + public void sendMessage(IChatComponent chatComponent) { + ClientChatReceivedEvent event = new ClientChatReceivedEvent((byte) 1, chatComponent); + MinecraftForge.EVENT_BUS.post(event); + if (!event.isCanceled()) { + if (Minecraft.getMinecraft().thePlayer == null) { + offlineMessages.add(event.message); + } else { + Minecraft.getMinecraft().thePlayer.addChatMessage(event.message); + } + } + } + + public void sendOfflineMessages() { + if (Minecraft.getMinecraft().thePlayer != null) { + Iterator<IChatComponent> offlineMessages = this.offlineMessages.iterator(); + if (offlineMessages.hasNext()) { + Minecraft.getMinecraft().thePlayer.playSound("random.levelup", 0.4F, 0.8F); + } + while (offlineMessages.hasNext()) { + Minecraft.getMinecraft().thePlayer.addChatMessage(offlineMessages.next()); + offlineMessages.remove(); + } + } + } + + public void sendAboveChatMessage(String... text) { + aboveChatMessage = text; + aboveChatMessageExpiration = Minecraft.getSystemTime() + DISPLAY_DURATION; + } + + public String[] getAboveChatMessage() { + if (aboveChatMessageExpiration < Minecraft.getSystemTime()) { + // message expired + aboveChatMessage = null; + } + return aboveChatMessage; + } + + public String cleanChatComponent(IChatComponent chatComponent) { + String component = IChatComponent.Serializer.componentToJson(chatComponent); + Matcher jsonMatcher = USELESS_JSON_CONTENT_PATTERN.matcher(component); + return jsonMatcher.replaceAll(""); + } + + public void sendShrug(String... args) { + String chatMsg = "\u00AF\\_(\u30C4)_/\u00AF"; // ¯\\_(ツ)_/¯" + if (args.length > 0) { + chatMsg = String.join(" ", args) + " " + chatMsg; + } + Minecraft.getMinecraft().thePlayer.sendChatMessage(chatMsg); + } +} diff --git a/src/main/java/de/cowtipper/cowlection/util/GsonUtils.java b/src/main/java/de/cowtipper/cowlection/util/GsonUtils.java new file mode 100644 index 0000000..c0b2735 --- /dev/null +++ b/src/main/java/de/cowtipper/cowlection/util/GsonUtils.java @@ -0,0 +1,94 @@ +package de.cowtipper.cowlection.util; + +import com.google.gson.*; +import com.mojang.util.UUIDTypeAdapter; +import de.cowtipper.cowlection.data.HyPlayerData; +import net.minecraft.nbt.NBTBase; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; +import net.minecraft.nbt.NBTTagString; +import net.minecraftforge.common.util.Constants; + +import java.io.Reader; +import java.lang.reflect.Type; +import java.util.UUID; + +public final class GsonUtils { + private static final Gson gson = new GsonBuilder().registerTypeAdapter(UUID.class, new UUIDTypeAdapter()).registerTypeAdapter(HyPlayerData.class, new HyPlayerDataDeserializer()).create(); + private static final Gson gsonPrettyPrinter = new GsonBuilder().registerTypeAdapter(UUID.class, new UUIDTypeAdapter()).setPrettyPrinting().create(); + + private GsonUtils() { + } + + public static <T> T fromJson(String json, Type clazz) { + return gson.fromJson(json, clazz); + } + + public static <T> T fromJson(Reader json, Class<T> clazz) { + return gson.fromJson(json, clazz); + } + + public static String toJson(Object object) { + if (object instanceof NBTBase) { + return gsonPrettyPrinter.toJson(nbtToJson((NBTBase) object)); + } else { + return gson.toJson(object); + } + } + + private static JsonElement nbtToJson(NBTBase nbtElement) { + if (nbtElement instanceof NBTBase.NBTPrimitive) { + NBTBase.NBTPrimitive nbtNumber = (NBTBase.NBTPrimitive) nbtElement; + switch (nbtNumber.getId()) { + case Constants.NBT.TAG_BYTE: + return new JsonPrimitive(nbtNumber.getByte()); + case Constants.NBT.TAG_SHORT: + return new JsonPrimitive(nbtNumber.getShort()); + case Constants.NBT.TAG_INT: + return new JsonPrimitive(nbtNumber.getInt()); + case Constants.NBT.TAG_LONG: + return new JsonPrimitive(nbtNumber.getLong()); + case Constants.NBT.TAG_FLOAT: + return new JsonPrimitive(nbtNumber.getFloat()); + case Constants.NBT.TAG_DOUBLE: + return new JsonPrimitive(nbtNumber.getDouble()); + default: + return new JsonObject(); + } + } else if (nbtElement instanceof NBTTagString) { + return new JsonPrimitive(((NBTTagString) nbtElement).getString()); + } else if (nbtElement instanceof NBTTagList) { + NBTTagList nbtList = (NBTTagList) nbtElement; + JsonArray jsonArray = new JsonArray(); + for (int tagId = 0; tagId < nbtList.tagCount(); tagId++) { + jsonArray.add(nbtToJson(nbtList.get(tagId))); + } + return jsonArray; + } else if (nbtElement instanceof NBTTagCompound) { + NBTTagCompound nbtCompound = (NBTTagCompound) nbtElement; + JsonObject jsonObject = new JsonObject(); + for (String nbtEntry : nbtCompound.getKeySet()) { + jsonObject.add(nbtEntry, nbtToJson(nbtCompound.getTag(nbtEntry))); + } + return jsonObject; + } + return new JsonObject(); + } + + public static class HyPlayerDataDeserializer implements JsonDeserializer<HyPlayerData> { + @Override + public HyPlayerData deserialize(JsonElement json, Type type, JsonDeserializationContext jdc) throws JsonParseException { + if (!json.getAsJsonObject().get("success").getAsBoolean()) { + // status: failed + return null; + } + JsonElement player = json.getAsJsonObject().get("player"); + HyPlayerData hyPlayerData = gsonPrettyPrinter.fromJson(player, HyPlayerData.class); + if (hyPlayerData == null) { + // player hasn't played Hypixel before + return new HyPlayerData(); + } + return hyPlayerData; + } + } +} diff --git a/src/main/java/de/cowtipper/cowlection/util/ImageUtils.java b/src/main/java/de/cowtipper/cowlection/util/ImageUtils.java new file mode 100644 index 0000000..6280e65 --- /dev/null +++ b/src/main/java/de/cowtipper/cowlection/util/ImageUtils.java @@ -0,0 +1,64 @@ +package de.cowtipper.cowlection.util; + +import com.mojang.authlib.minecraft.MinecraftProfileTexture; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.ThreadDownloadImageData; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.fml.relauncher.ReflectionHelper; + +import java.awt.image.BufferedImage; + +public class ImageUtils { + public static int getTierFromTexture(String minionSkinId) { + String textureUrl = "http://textures.minecraft.net/texture/" + minionSkinId; + MinecraftProfileTexture minionSkinTextureDetails = new MinecraftProfileTexture(textureUrl, null); + + ResourceLocation minionSkinLocation = Minecraft.getMinecraft().getSkinManager().loadSkin(minionSkinTextureDetails, MinecraftProfileTexture.Type.SKIN); + + ThreadDownloadImageData minionSkinTexture = (ThreadDownloadImageData) Minecraft.getMinecraft().getTextureManager().getTexture(minionSkinLocation); + BufferedImage minionSkinImage = ReflectionHelper.getPrivateValue(ThreadDownloadImageData.class, minionSkinTexture, "bufferedImage", "field_110560_d"); + + // extract relevant part of the minion tier badge (center 2x1 pixel) + BufferedImage minionSkinTierBadge = minionSkinImage.getSubimage(43, 3, 2, 1); + + return MinionTier.getByColors(minionSkinTierBadge.getRGB(0, 0), minionSkinTierBadge.getRGB(1, 0)).getTier(); + } + + private enum MinionTier { + UNKNOWN(-1, -1), + I(0, 0), + II(-2949295, -10566655), + III(-1245259, -10566655), + IV(-8922850, -983608), + V(-8110849, -11790679), + VI(-4681729, -11790679), + VII(-9486653, -3033345), + VIII(-907953, -7208930), + IX(-31330, -7208930), + X(-5046235, -20031), + XI(-15426142, -1769477); + + private final int color1; + private final int color2; + + MinionTier(int color1, int color2) { + this.color1 = color1; + this.color2 = color2; + } + + private static MinionTier getByColors(int color1, int color2) { + MinionTier[] tiers = values(); + for (int i = 1; i < tiers.length; i++) { + MinionTier minionTier = tiers[i]; + if (minionTier.color1 == color1 && minionTier.color2 == color2) { + return minionTier; + } + } + return UNKNOWN; + } + + private int getTier() { + return ordinal(); + } + } +} diff --git a/src/main/java/de/cowtipper/cowlection/util/MooChatComponent.java b/src/main/java/de/cowtipper/cowlection/util/MooChatComponent.java new file mode 100644 index 0000000..062d488 --- /dev/null +++ b/src/main/java/de/cowtipper/cowlection/util/MooChatComponent.java @@ -0,0 +1,186 @@ +package de.cowtipper.cowlection.util; + +import net.minecraft.event.ClickEvent; +import net.minecraft.event.HoverEvent; +import net.minecraft.util.ChatComponentText; +import net.minecraft.util.EnumChatFormatting; +import net.minecraft.util.IChatComponent; + +public class MooChatComponent extends ChatComponentText { + public MooChatComponent(String msg) { + super(msg); + } + + public MooChatComponent black() { + setChatStyle(getChatStyle().setColor(EnumChatFormatting.BLACK)); + return this; + } + + public MooChatComponent darkBlue() { + setChatStyle(getChatStyle().setColor(EnumChatFormatting.DARK_BLUE)); + return this; + } + + public MooChatComponent darkGreen() { + setChatStyle(getChatStyle().setColor(EnumChatFormatting.DARK_GREEN)); + return this; + } + + public MooChatComponent darkAqua() { + setChatStyle(getChatStyle().setColor(EnumChatFormatting.DARK_AQUA)); + return this; + } + + public MooChatComponent darkRed() { + setChatStyle(getChatStyle().setColor(EnumChatFormatting.DARK_RED)); + return this; + } + + public MooChatComponent darkPurple() { + setChatStyle(getChatStyle().setColor(EnumChatFormatting.DARK_PURPLE)); + return this; + } + + public MooChatComponent gold() { + setChatStyle(getChatStyle().setColor(EnumChatFormatting.GOLD)); + return this; + } + + public MooChatComponent gray() { + setChatStyle(getChatStyle().setColor(EnumChatFormatting.GRAY)); + return this; + } + + public MooChatComponent darkGray() { + setChatStyle(getChatStyle().setColor(EnumChatFormatting.DARK_GRAY)); + return this; + } + + public MooChatComponent blue() { + setChatStyle(getChatStyle().setColor(EnumChatFormatting.BLUE)); + return this; + } + + public MooChatComponent green() { + setChatStyle(getChatStyle().setColor(EnumChatFormatting.GREEN)); + return this; + } + + public MooChatComponent aqua() { + setChatStyle(getChatStyle().setColor(EnumChatFormatting.AQUA)); + return this; + } + + public MooChatComponent red() { + setChatStyle(getChatStyle().setColor(EnumChatFormatting.RED)); + return this; + } + + public MooChatComponent lightPurple() { + setChatStyle(getChatStyle().setColor(EnumChatFormatting.LIGHT_PURPLE)); + return this; + } + + public MooChatComponent yellow() { + setChatStyle(getChatStyle().setColor(EnumChatFormatting.YELLOW)); + return this; + } + + public MooChatComponent white() { + setChatStyle(getChatStyle().setColor(EnumChatFormatting.WHITE)); + return this; + } + + public MooChatComponent obfuscated() { + setChatStyle(getChatStyle().setObfuscated(true)); + return this; + } + + public MooChatComponent bold() { + setChatStyle(getChatStyle().setBold(true)); + return this; + } + + public MooChatComponent strikethrough() { + setChatStyle(getChatStyle().setStrikethrough(true)); + return this; + } + + public MooChatComponent underline() { + setChatStyle(getChatStyle().setUnderlined(true)); + return this; + } + + public MooChatComponent italic() { + setChatStyle(getChatStyle().setItalic(true)); + return this; + } + + public MooChatComponent reset() { + setChatStyle(getChatStyle().setParentStyle(null).setBold(false).setItalic(false).setObfuscated(false).setUnderlined(false).setStrikethrough(false)); + return this; + } + + public MooChatComponent setHover(IChatComponent hover) { + setChatStyle(getChatStyle().setChatHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, hover))); + return this; + } + + public MooChatComponent setUrl(String url) { + setUrl(url, new KeyValueTooltipComponent("Click to visit", url)); + return this; + } + + public MooChatComponent setUrl(String url, String hover) { + setUrl(url, new MooChatComponent(hover).yellow()); + return this; + } + + public MooChatComponent setUrl(String url, IChatComponent hover) { + setChatStyle(getChatStyle().setChatClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, url))); + setHover(hover); + return this; + } + + public MooChatComponent setSuggestCommand(String command) { + setChatStyle(getChatStyle().setChatClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, command))); + setHover(new KeyValueChatComponent("Run", command, " ")); + return this; + } + + /** + * Appends the given component in a new line, without inheriting formatting of previous siblings. + * + * @see ChatComponentText#appendSibling appendSibling + */ + public MooChatComponent appendFreshSibling(IChatComponent sibling) { + this.siblings.add(new ChatComponentText("\n").appendSibling(sibling)); + return this; + } + + @Deprecated + public MooChatComponent appendKeyValue(String key, String value) { + appendSibling(new MooChatComponent("\n").appendFreshSibling(new KeyValueChatComponent(key, value))); + return this; + } + + public static class KeyValueChatComponent extends MooChatComponent { + public KeyValueChatComponent(String key, String value) { + this(key, value, ": "); + } + + public KeyValueChatComponent(String key, String value, String separator) { + super(key); + appendText(separator); + gold().appendSibling(new MooChatComponent(value).yellow()); + } + } + + public static class KeyValueTooltipComponent extends MooChatComponent { + public KeyValueTooltipComponent(String key, String value) { + super(key); + appendText(": "); + gray().appendSibling(new MooChatComponent(value).yellow()); + } + } +} diff --git a/src/main/java/de/cowtipper/cowlection/util/TickDelay.java b/src/main/java/de/cowtipper/cowlection/util/TickDelay.java new file mode 100644 index 0000000..cbb21c5 --- /dev/null +++ b/src/main/java/de/cowtipper/cowlection/util/TickDelay.java @@ -0,0 +1,29 @@ +package de.cowtipper.cowlection.util; + +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent; + +public class TickDelay { + private Runnable task; + private int waitingTicks; + + public TickDelay(Runnable task, int ticks) { + this.task = task; + this.waitingTicks = ticks; + + MinecraftForge.EVENT_BUS.register(this); + } + + @SubscribeEvent + public void onTick(TickEvent.ClientTickEvent e) { + if (e.phase == TickEvent.Phase.START) { + if (waitingTicks < 1) { + // we're done waiting! Do stuff and exit. + task.run(); + MinecraftForge.EVENT_BUS.unregister(this); + } + waitingTicks--; + } + } +} diff --git a/src/main/java/de/cowtipper/cowlection/util/Utils.java b/src/main/java/de/cowtipper/cowlection/util/Utils.java new file mode 100644 index 0000000..e833cf8 --- /dev/null +++ b/src/main/java/de/cowtipper/cowlection/util/Utils.java @@ -0,0 +1,253 @@ +package de.cowtipper.cowlection.util; + +import com.mojang.realmsclient.util.Pair; +import net.minecraft.util.EnumChatFormatting; +import org.apache.commons.lang3.text.WordUtils; +import org.apache.commons.lang3.time.DateFormatUtils; +import org.apache.commons.lang3.time.DurationFormatUtils; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.text.DecimalFormat; +import java.util.concurrent.TimeUnit; +import java.util.regex.Pattern; + +public final class Utils { + public static final Pattern VALID_UUID_PATTERN = Pattern.compile("^(\\w{8})-(\\w{4})-(\\w{4})-(\\w{4})-(\\w{12})$"); + private static final Pattern VALID_USERNAME = Pattern.compile("^[\\w]{1,16}$"); + private static final char[] LARGE_NUMBERS = new char[]{'k', 'm', 'b', 't'}; + + private Utils() { + } + + public static boolean isValidUuid(String uuid) { + return VALID_UUID_PATTERN.matcher(uuid).matches(); + } + + public static boolean isValidMcName(String username) { + return VALID_USERNAME.matcher(username).matches(); + } + + public static String fancyCase(String string) { + return WordUtils.capitalizeFully(string.replace('_', ' ')); + } + + /** + * Turn timestamp into pretty-formatted duration and date details. + * + * @param timestamp last login/logout + * @return 1st: duration between timestamp and now in words; 2nd: formatted date if time differences is >24h, otherwise null + */ + public static Pair<String, String> getDurationAsWords(long timestamp) { + long duration = System.currentTimeMillis() - timestamp; + long daysPast = TimeUnit.MILLISECONDS.toDays(duration); + + String dateFormatted = null; + if (daysPast > 1) { + dateFormatted = DateFormatUtils.format(timestamp, "dd-MMM-yyyy"); + } + + if (daysPast > 31) { + return Pair.of( + DurationFormatUtils.formatPeriod(timestamp, System.currentTimeMillis(), (daysPast > 365 ? "y 'years' " : "") + "M 'months' d 'days'"), + dateFormatted); + } else { + return Pair.of( + DurationFormatUtils.formatDurationWords(duration, true, true), + dateFormatted); + } + } + + public static String getDurationAsWord(long timestamp) { + long duration = System.currentTimeMillis() - timestamp; + long secondsPast = TimeUnit.MILLISECONDS.toSeconds(duration); + if (secondsPast < 60) { + return secondsPast + " second" + (secondsPast > 1 ? "s" : ""); + } + long minutesPast = TimeUnit.SECONDS.toMinutes(secondsPast); + if (minutesPast < 60) { + return minutesPast + " minute" + (minutesPast > 1 ? "s" : ""); + } + long hoursPast = TimeUnit.MINUTES.toHours(minutesPast); + if (hoursPast < 24) { + return hoursPast + " hour" + (hoursPast > 1 ? "s" : ""); + } + long daysPast = TimeUnit.HOURS.toDays(hoursPast); + if (daysPast < 31) { + return daysPast + " day" + (daysPast > 1 ? "s" : ""); + } + double monthsPast = daysPast / 30.5d; + if (monthsPast < 12) { + return new DecimalFormat("0.#").format(monthsPast) + " month" + (monthsPast >= 2 ? "s" : ""); + } + double yearsPast = monthsPast / 12d; + return new DecimalFormat("0.#").format(yearsPast) + " year" + (yearsPast >= 2 ? "s" : ""); + } + + public static String toRealPath(Path path) { + try { + return path.toRealPath().toString(); + } catch (IOException e) { + e.printStackTrace(); + return "file not found"; + } + } + + public static String toRealPath(File path) { + return toRealPath(path.toPath()); + } + + /** + * Formats a large number with abbreviations for each factor of a thousand (k, m, ...) + * + * @param number the number to format + * @return a String representing the number n formatted in a cool looking way. + * @see <a href="https://stackoverflow.com/a/4753866">Source</a> + */ + public static String formatNumberWithAbbreviations(double number) { + return formatNumberWithAbbreviations(number, 0); + } + + private static String formatNumberWithAbbreviations(double number, int iteration) { + @SuppressWarnings("IntegerDivisionInFloatingPointContext") double d = ((long) number / 100) / 10.0; + boolean isRound = (d * 10) % 10 == 0; //true if the decimal part is equal to 0 (then it's trimmed anyway) + // this determines the class, i.e. 'k', 'm' etc + // this decides whether to trim the decimals + // (int) d * 10 / 10 drops the decimal + return d < 1000 ? // this determines the class, i.e. 'k', 'm' etc + (d > 99.9 || isRound || d > 9.99 ? // this decides whether to trim the decimals + (int) d * 10 / 10 : d + "" // (int) d * 10 / 10 drops the decimal + ) + "" + LARGE_NUMBERS[iteration] + : formatNumberWithAbbreviations(d, iteration + 1); + } + + /** + * Convert Roman numerals to their corresponding Arabic numeral + * + * @param roman Roman numeral + * @return Arabic numeral + * @see <a href="https://www.w3resource.com/javascript-exercises/javascript-math-exercise-22.php">Source</a> + */ + public static int convertRomanToArabic(String roman) { + if (roman == null) return -1; + int number = romanCharToArabic(roman.charAt(0)); + + for (int i = 1; i < roman.length(); i++) { + int current = romanCharToArabic(roman.charAt(i)); + int previous = romanCharToArabic(roman.charAt(i - 1)); + if (current <= previous) { + number += current; + } else { + number = number - previous * 2 + current; + } + } + return number; + } + + private static int romanCharToArabic(char c) { + switch (c) { + case 'I': + return 1; + case 'V': + return 5; + case 'X': + return 10; + case 'L': + return 50; + case 'C': + return 100; + case 'D': + return 500; + case 'M': + return 1000; + default: + return -1; + } + } + + /** + * Convert Arabic numerals to their corresponding Roman numerals + * + * @param number Arabic numerals + * @return Roman numerals + * @see <a href="https://stackoverflow.com/a/48357180">Source</a> + */ + public static String convertArabicToRoman(int number) { + if (number == 0) { + return "0"; + } + String romanOnes = arabicToRomanChars(number % 10, "I", "V", "X"); + number /= 10; + + String romanTens = arabicToRomanChars(number % 10, "X", "L", "C"); + number /= 10; + + String romanHundreds = arabicToRomanChars(number % 10, "C", "D", "M"); + number /= 10; + + String romanThousands = arabicToRomanChars(number % 10, "M", "", ""); + + return romanThousands + romanHundreds + romanTens + romanOnes; + } + + private static String arabicToRomanChars(int n, String one, String five, String ten) { + switch (n) { + case 1: + return one; + case 2: + return one + one; + case 3: + return one + one + one; + case 4: + return one + five; + case 5: + return five; + case 6: + return five + one; + case 7: + return five + one + one; + case 8: + return five + one + one + one; + case 9: + return one + ten; + } + return ""; + } + + /** + * Get the minion tier's color for chat formatting + * + * @param tier minion tier + * @return color code corresponding to the tier + */ + public static EnumChatFormatting getMinionTierColor(int tier) { + EnumChatFormatting tierColor; + switch (tier) { + case 1: + tierColor = EnumChatFormatting.WHITE; + break; + case 2: + case 3: + case 4: + tierColor = EnumChatFormatting.GREEN; + break; + case 5: + case 6: + case 7: + tierColor = EnumChatFormatting.DARK_PURPLE; + break; + case 8: + case 9: + case 10: + tierColor = EnumChatFormatting.RED; + break; + case 11: + tierColor = EnumChatFormatting.AQUA; + break; + default: + tierColor = EnumChatFormatting.OBFUSCATED; + } + return tierColor; + } +} diff --git a/src/main/java/de/cowtipper/cowlection/util/VersionChecker.java b/src/main/java/de/cowtipper/cowlection/util/VersionChecker.java new file mo |
