feat: spigot branch

This commit is contained in:
ineanto 2024-12-12 19:22:11 +01:00
parent ff892a7451
commit 4948610860
Signed by: ineanto
GPG key ID: E511F9CAA2F9CE84
35 changed files with 144 additions and 1291 deletions

14
common/build.gradle.kts Normal file
View file

@ -0,0 +1,14 @@
plugins {
id("java")
}
group = "xyz.ineanto.nicko"
version = project.version
repositories {
mavenCentral()
}
dependencies {
compileOnly("org.spigotmc:spigot-api:1.23.3-R0.1-SNAPSHOT")
}

View file

@ -0,0 +1,35 @@
package xyz.ineanto.nicko.action;
public class ActionResult {
private final String errorKey;
private boolean error = false;
public static ActionResult ok() {
return new ActionResult();
}
public static ActionResult error() {
return new ActionResult(null);
}
public static ActionResult error(String errorMessage) {
return new ActionResult(errorMessage);
}
private ActionResult() {
this.errorKey = null;
}
private ActionResult(String errorMessage) {
this.errorKey = errorMessage;
this.error = true;
}
public boolean isError() {
return error;
}
public String getErrorKey() {
return errorKey;
}
}

View file

@ -0,0 +1,155 @@
package xyz.ineanto.nicko.mojang;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
import javax.annotation.Nonnull;
import javax.net.ssl.HttpsURLConnection;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URL;
import java.util.HashMap;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
public class MojangAPI {
public static final String URL_NAME = "https://api.mojang.com/users/profiles/minecraft/{name}";
public static final String URL_SKIN = "https://sessionserver.mojang.com/session/minecraft/profile/{uuid}?unsigned=false";
private final Logger logger = Logger.getLogger("MojangAPI");
private final HashMap<String, String> uuidToName = new HashMap<>();
private final ExecutorService worker = Executors.newFixedThreadPool(6);
private final CacheLoader<String, Optional<MojangSkin>> skinLoader = new CacheLoader<>() {
@Nonnull
public Optional<MojangSkin> load(@Nonnull String uuid) throws Exception {
return getSkinFromMojang(uuid);
}
};
private final LoadingCache<String, Optional<MojangSkin>> skinCache = CacheBuilder
.newBuilder()
.recordStats()
.expireAfterWrite(24, TimeUnit.HOURS)
.build(skinLoader);
private final CacheLoader<String, Optional<String>> uuidLoader = new CacheLoader<>() {
@Nonnull
public Optional<String> load(@Nonnull String name) throws Exception {
return getUUIDFromMojang(name);
}
};
private final LoadingCache<String, Optional<String>> uuidCache = CacheBuilder
.newBuilder()
.expireAfterWrite(2, TimeUnit.DAYS)
.build(uuidLoader);
public Optional<MojangSkin> getSkin(String uuid) throws IOException, ExecutionException {
return skinCache.get(uuid);
}
public Optional<MojangSkin> getSkinWithoutCaching(String uuid) throws IOException, ExecutionException, InterruptedException {
return getSkinFromMojang(uuid);
}
public Optional<String> getUUID(String name) throws IOException, ExecutionException {
return uuidCache.get(name);
}
private Optional<String> getUUIDFromMojang(String name) throws ExecutionException, InterruptedException {
final String parametrizedUrl = URL_NAME.replace("{name}", name);
final JsonObject object = getRequestToUrl(parametrizedUrl);
if (hasNoError(object)) {
final JsonElement idObject = object.get("id");
final String uuid = idObject.getAsString();
final Optional<String> uuidOptional = Optional.of(uuid);
uuidCache.put(name, uuidOptional);
uuidToName.put(uuid, name);
return uuidOptional;
}
return Optional.empty();
}
public void eraseFromCache(String uuid) {
skinCache.invalidate(uuid);
uuidToName.remove(uuid);
uuidCache.invalidate(uuid);
}
private Optional<MojangSkin> getSkinFromMojang(String uuid) throws ExecutionException, InterruptedException {
final String parametrizedUrl = URL_SKIN.replace("{uuid}", uuid);
final JsonObject object = getRequestToUrl(parametrizedUrl);
if (hasNoError(object)) {
final MojangSkin skin = MojangSkin.buildFromJson(object);
return Optional.of(skin);
}
return Optional.empty();
}
public String getUUIDName(String uuid) {
return uuidToName.get(uuid);
}
private JsonObject getRequestToUrl(String parametrizedUrl) throws ExecutionException, InterruptedException {
return worker.submit(() -> {
final URL url = URI.create(parametrizedUrl).toURL();
final HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
con.setDoInput(true);
con.setRequestMethod("GET");
switch (con.getResponseCode()) {
case 404:
case 400:
logger.warning("Failed to parse request: Invalid Name");
return getErrorObject();
case 429:
logger.warning("Failed to parse request: The connection is throttled.");
return getErrorObject();
case 200:
final BufferedReader input = new BufferedReader(new InputStreamReader(con.getInputStream()));
final StringBuilder builder = new StringBuilder();
String line;
while ((line = input.readLine()) != null) {
builder.append(line);
}
try {
final JsonElement jsonElt = JsonParser.parseString(builder.toString());
return jsonElt.getAsJsonObject();
} catch (JsonParseException | IllegalStateException exception) {
logger.warning("Failed to parse request (" + parametrizedUrl + ")!");
return getErrorObject();
}
default:
logger.warning("Unhandled response code from Mojang: " + con.getResponseCode());
return getErrorObject();
}
}).get();
}
private JsonObject getErrorObject() {
final JsonObject errorObject = new JsonObject();
errorObject.addProperty("error", "An error occurred.");
return errorObject;
}
private boolean hasNoError(JsonObject object) {
return object.get("error") == null;
}
public LoadingCache<String, Optional<MojangSkin>> getSkinCache() {
return skinCache;
}
}

View file

@ -0,0 +1,12 @@
package xyz.ineanto.nicko.mojang;
import com.google.gson.JsonObject;
public record MojangSkin(String value, String signature) {
public static MojangSkin buildFromJson(JsonObject object) {
final JsonObject properties = object.get("properties").getAsJsonArray().get(0).getAsJsonObject();
final String value = properties.get("value").getAsString();
final String signature = properties.get("signature").getAsString();
return new MojangSkin(value, signature);
}
}

View file

@ -0,0 +1,26 @@
package xyz.ineanto.nicko.mojang;
import java.util.UUID;
import java.util.regex.Pattern;
public class MojangUtils {
public static boolean isUsernameInvalid(String username) {
return !Pattern.matches("^\\w{3,16}$", username);
}
public static UUID fromTrimmed(String trimmedUUID) throws IllegalArgumentException {
if (trimmedUUID == null) throw new IllegalArgumentException();
StringBuilder builder = new StringBuilder(trimmedUUID.trim());
/* Backwards adding to avoid index adjustments */
try {
builder.insert(20, "-");
builder.insert(16, "-");
builder.insert(12, "-");
builder.insert(8, "-");
} catch (StringIndexOutOfBoundsException e) {
throw new IllegalArgumentException();
}
return UUID.fromString(builder.toString());
}
}