1
0
mirror of https://github.com/Suiranoil/SkinRestorer.git synced 2026-01-16 04:42:12 +00:00

Compare commits

...

2 Commits

Author SHA1 Message Date
3a8a513180 add caching to skin providers 2024-08-29 20:31:35 +03:00
9e0c41482f add refreshSkinOnJoin config 2024-08-29 06:43:28 +03:00
9 changed files with 126 additions and 45 deletions

View File

@@ -118,7 +118,7 @@ public final class SkinRestorer {
boolean save
) {
return CompletableFuture.supplyAsync(
() -> SkinRestorer.getProvider(context.name()).map(provider -> provider.getSkin(context.argument(), context.variant()))
() -> SkinRestorer.getProvider(context.name()).map(provider -> provider.fetchSkin(context.argument(), context.variant()))
)
.thenApplyAsync(result -> {
if (result.isEmpty())

View File

@@ -14,6 +14,8 @@ public final class Config {
private String language = "en_us";
private boolean refreshSkinOnJoin = false;
private boolean fetchSkinOnFirstJoin = true;
private FirstJoinSkinProvider firstJoinSkinProvider = FirstJoinSkinProvider.MOJANG;
@@ -27,6 +29,10 @@ public final class Config {
return this.language;
}
public boolean refreshSkinOnJoin() {
return this.refreshSkinOnJoin;
}
public boolean fetchSkinOnFirstJoin() {
return this.fetchSkinOnFirstJoin;
}

View File

@@ -24,14 +24,14 @@ public abstract class ServerLoginPacketListenerImplMixin {
private GameProfile authenticatedProfile;
@Unique
private CompletableFuture<Void> skinrestorer_pendingSkin;
private CompletableFuture<Void> skinrestorer$pendingSkin;
@Inject(method = "verifyLoginAndFinishConnectionSetup", at = @At(value = "INVOKE",
target = "Lnet/minecraft/server/players/PlayerList;canPlayerLogin(Ljava/net/SocketAddress;Lcom/mojang/authlib/GameProfile;)Lnet/minecraft/network/chat/Component;"),
cancellable = true)
public void waitForSkin(CallbackInfo ci) {
if (skinrestorer_pendingSkin == null) {
skinrestorer_pendingSkin = CompletableFuture.supplyAsync(() -> {
if (skinrestorer$pendingSkin == null) {
skinrestorer$pendingSkin = CompletableFuture.supplyAsync(() -> {
assert authenticatedProfile != null;
var originalSkin = PlayerUtils.getPlayerSkin(authenticatedProfile);
@@ -41,34 +41,46 @@ public abstract class ServerLoginPacketListenerImplMixin {
SkinRestorer.getSkinStorage().setSkin(authenticatedProfile.getId(), value.setOriginalValue(originalSkin));
}
if (SkinRestorer.getConfig().refreshSkinOnJoin()) {
var currentSkin = SkinRestorer.getSkinStorage().getSkin(authenticatedProfile.getId());
var context = currentSkin.toProviderContext();
skinrestorer$fetchSkin(authenticatedProfile, context);
}
return null;
}
if (originalSkin == null && SkinRestorer.getConfig().fetchSkinOnFirstJoin()) {
SkinRestorer.LOGGER.debug("Fetching {}'s skin", authenticatedProfile.getName());
var context = new SkinProviderContext(
SkinRestorer.getConfig().getFirstJoinSkinProvider().getName(),
authenticatedProfile.getName(),
null
);
var result = SkinRestorer.getProvider(context.name()).map(
provider -> provider.getSkin(context.argument(), context.variant())
).orElse(Result.ofNullable(null));
if (!result.isError()) {
var value = SkinValue.fromProviderContextWithValue(context, result.getSuccessValue().orElse(null));
SkinRestorer.getSkinStorage().setSkin(authenticatedProfile.getId(), value);
} else {
SkinRestorer.LOGGER.warn("failed to fetch skin on first join", result.getErrorValue());
}
skinrestorer$fetchSkin(authenticatedProfile, context);
}
return null;
});
}
if (!skinrestorer_pendingSkin.isDone())
if (!skinrestorer$pendingSkin.isDone())
ci.cancel();
}
@Unique
private static void skinrestorer$fetchSkin(GameProfile profile, SkinProviderContext context) {
SkinRestorer.LOGGER.debug("fetching {}'s skin", profile.getName());
var result = SkinRestorer.getProvider(context.name()).map(
provider -> provider.fetchSkin(context.argument(), context.variant())
).orElseGet(() -> Result.error(new IllegalArgumentException("skin provider is not registered: " + context.name())));
if (!result.isError()) {
var value = SkinValue.fromProviderContextWithValue(context, result.getSuccessValue().orElse(null));
SkinRestorer.getSkinStorage().setSkin(profile.getId(), value);
} else {
SkinRestorer.LOGGER.warn("failed to fetch skin", result.getErrorValue());
}
}
}

View File

@@ -1,5 +1,8 @@
package net.lionarius.skinrestorer.skin.provider;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import com.mojang.authlib.yggdrasil.response.MinecraftProfilePropertiesResponse;
@@ -9,12 +12,15 @@ import net.lionarius.skinrestorer.util.PlayerUtils;
import net.lionarius.skinrestorer.util.Result;
import net.lionarius.skinrestorer.util.WebUtils;
import net.minecraft.util.StringUtil;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.http.HttpRequest;
import java.util.Locale;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
public final class ElyBySkinProvider implements SkinProvider {
@@ -22,12 +28,23 @@ public final class ElyBySkinProvider implements SkinProvider {
private static final URI API_URI;
private static final LoadingCache<String, Optional<Property>> SKIN_CACHE;
static {
try {
API_URI = new URI("http://skinsystem.ely.by");
} catch (URISyntaxException e) {
throw new IllegalArgumentException(e);
}
SKIN_CACHE = CacheBuilder.newBuilder()
.expireAfterWrite(60, TimeUnit.SECONDS)
.build(new CacheLoader<>() {
@Override
public @NotNull Optional<Property> load(@NotNull String key) throws Exception {
return ElyBySkinProvider.loadSkin(key);
}
});
}
@Override
@@ -41,20 +58,25 @@ public final class ElyBySkinProvider implements SkinProvider {
}
@Override
public Result<Optional<Property>, Exception> getSkin(String username, SkinVariant variant) {
if (!StringUtil.isValidPlayerName(username))
return Result.error(new IllegalArgumentException("invalid username"));
public Result<Optional<Property>, Exception> fetchSkin(String username, SkinVariant variant) {
try {
var profile = ElyBySkinProvider.getElyByProfile(username);
var textures = PlayerUtils.getPlayerSkin(profile);
if (!StringUtil.isValidPlayerName(username))
throw new IllegalArgumentException("invalid username");
return Result.ofNullable(textures);
var usernameLowerCase = username.toLowerCase(Locale.ROOT);
return Result.success(SKIN_CACHE.get(usernameLowerCase));
} catch (Exception e) {
return Result.error(e);
}
}
private static Optional<Property> loadSkin(String username) throws Exception {
var profile = ElyBySkinProvider.getElyByProfile(username);
var textures = PlayerUtils.getPlayerSkin(profile);
return Optional.ofNullable(textures);
}
private static GameProfile getElyByProfile(String username) throws IOException {
var request = HttpRequest.newBuilder()
.uri(ElyBySkinProvider.API_URI

View File

@@ -21,11 +21,11 @@ public final class EmptySkinProvider implements SkinProvider {
}
@Override
public Result<Optional<Property>, Exception> getSkin(String argument, SkinVariant variant) {
return this.getSkin();
public Result<Optional<Property>, Exception> fetchSkin(String argument, SkinVariant variant) {
return this.fetchSkin();
}
public Result<Optional<Property>, Exception> getSkin() {
public Result<Optional<Property>, Exception> fetchSkin() {
return Result.ofNullable(null);
}
}

View File

@@ -1,18 +1,24 @@
package net.lionarius.skinrestorer.skin.provider;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.gson.JsonObject;
import com.mojang.authlib.properties.Property;
import it.unimi.dsi.fastutil.Pair;
import net.lionarius.skinrestorer.skin.SkinVariant;
import net.lionarius.skinrestorer.util.JsonUtils;
import net.lionarius.skinrestorer.util.PlayerUtils;
import net.lionarius.skinrestorer.util.Result;
import net.lionarius.skinrestorer.util.WebUtils;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.http.HttpRequest;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
public final class MineskinSkinProvider implements SkinProvider {
@@ -20,12 +26,23 @@ public final class MineskinSkinProvider implements SkinProvider {
private static final URI API_URI;
private static final LoadingCache<Pair<URI, SkinVariant>, Optional<Property>> SKIN_CACHE;
static {
try {
API_URI = new URI("https://api.mineskin.org");
} catch (URISyntaxException e) {
throw new IllegalArgumentException(e);
}
SKIN_CACHE = CacheBuilder.newBuilder()
.expireAfterWrite(300, TimeUnit.SECONDS)
.build(new CacheLoader<>() {
@Override
public @NotNull Optional<Property> load(@NotNull Pair<URI, SkinVariant> key) throws Exception {
return MineskinSkinProvider.loadSkin(key.first(), key.second());
}
});
}
@Override
@@ -39,20 +56,24 @@ public final class MineskinSkinProvider implements SkinProvider {
}
@Override
public Result<Optional<Property>, Exception> getSkin(String url, SkinVariant variant) {
public Result<Optional<Property>, Exception> fetchSkin(String url, SkinVariant variant) {
try {
var uri = new URI(url);
var result = MineskinSkinProvider.uploadToMineskin(uri, variant);
var texture = result.getAsJsonObject("data").getAsJsonObject("texture");
var textures = new Property(PlayerUtils.TEXTURES_KEY, texture.get("value").getAsString(), texture.get("signature").getAsString());
return Result.ofNullable(textures);
return Result.success(SKIN_CACHE.get(Pair.of(uri, variant)));
} catch (Exception e) {
return Result.error(e);
}
}
private static Optional<Property> loadSkin(URI uri, SkinVariant variant) throws Exception {
var result = MineskinSkinProvider.uploadToMineskin(uri, variant);
var texture = result.getAsJsonObject("data").getAsJsonObject("texture");
var textures = new Property(PlayerUtils.TEXTURES_KEY, texture.get("value").getAsString(), texture.get("signature").getAsString());
return Optional.of(textures);
}
private static JsonObject uploadToMineskin(URI url, SkinVariant variant) throws IOException {
var body = ("{\"variant\":\"%s\",\"name\":\"%s\",\"visibility\":%d,\"url\":\"%s\"}")
.formatted(variant.toString(), "none", 0, url);

View File

@@ -1,5 +1,8 @@
package net.lionarius.skinrestorer.skin.provider;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import com.mojang.authlib.yggdrasil.response.MinecraftProfilePropertiesResponse;
@@ -12,6 +15,7 @@ import net.lionarius.skinrestorer.util.Result;
import net.lionarius.skinrestorer.util.WebUtils;
import net.minecraft.server.players.GameProfileCache;
import net.minecraft.util.StringUtil;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.net.URI;
@@ -19,6 +23,7 @@ import java.net.URISyntaxException;
import java.net.http.HttpRequest;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
public final class MojangSkinProvider implements SkinProvider {
@@ -27,9 +32,11 @@ public final class MojangSkinProvider implements SkinProvider {
private static final URI API_URI;
private static final URI SESSION_SERVER_URI;
public static final String CACHE_FILENAME = "mojang_profile_cache.json";
public static final String PROFILE_CACHE_FILENAME = "mojang_profile_cache.json";
private static final GameProfileCache PROFILE_CACHE;
private static final LoadingCache<UUID, Optional<Property>> SKIN_CACHE;
static {
try {
API_URI = new URI("https://api.mojang.com");
@@ -47,7 +54,16 @@ public final class MojangSkinProvider implements SkinProvider {
callback.onProfileLookupFailed(name, e);
}
}
}, SkinRestorer.getConfigDir().resolve(CACHE_FILENAME).toFile());
}, SkinRestorer.getConfigDir().resolve(PROFILE_CACHE_FILENAME).toFile());
SKIN_CACHE = CacheBuilder.newBuilder()
.expireAfterWrite(60, TimeUnit.SECONDS)
.build(new CacheLoader<>() {
@Override
public @NotNull Optional<Property> load(@NotNull UUID key) throws Exception {
return MojangSkinProvider.loadSkin(key);
}
});
}
public static SkinProviderContext skinProviderContextFromProfile(GameProfile gameProfile) {
@@ -65,24 +81,28 @@ public final class MojangSkinProvider implements SkinProvider {
}
@Override
public Result<Optional<Property>, Exception> getSkin(String username, SkinVariant variant) {
if (!StringUtil.isValidPlayerName(username))
return Result.error(new IllegalArgumentException("invalid username"));
public Result<Optional<Property>, Exception> fetchSkin(String username, SkinVariant variant) {
try {
if (!StringUtil.isValidPlayerName(username))
throw new IllegalArgumentException("invalid username");
var cachedProfile = MojangSkinProvider.PROFILE_CACHE.get(username);
if (cachedProfile.isEmpty())
throw new IllegalArgumentException("no profile found for " + username);
var profile = MojangSkinProvider.getProfileWithProperties(cachedProfile.get().getId());
var textures = PlayerUtils.getPlayerSkin(profile);
return Result.ofNullable(textures);
return Result.success(SKIN_CACHE.get(cachedProfile.get().getId()));
} catch (Exception e) {
return Result.error(e);
}
}
private static Optional<Property> loadSkin(UUID uuid) throws Exception {
var profile = MojangSkinProvider.getProfileWithProperties(uuid);
var textures = PlayerUtils.getPlayerSkin(profile);
return Optional.ofNullable(textures);
}
private static GameProfile getProfile(final String name) throws IOException {
var request = HttpRequest.newBuilder()
.uri(MojangSkinProvider.API_URI

View File

@@ -16,5 +16,5 @@ public interface SkinProvider {
boolean hasVariantSupport();
Result<Optional<Property>, Exception> getSkin(String argument, SkinVariant variant);
Result<Optional<Property>, Exception> fetchSkin(String argument, SkinVariant variant);
}

View File

@@ -26,7 +26,7 @@ public final class FileUtils {
return Files.isRegularFile(file)
&& !name.startsWith(Translation.LEGACY_TRANSLATION_FILENAME)
&& !name.startsWith(Config.CONFIG_FILENAME)
&& !name.startsWith(MojangSkinProvider.CACHE_FILENAME)
&& !name.startsWith(MojangSkinProvider.PROFILE_CACHE_FILENAME)
&& name.endsWith(SkinIO.FILE_EXTENSION);
}).toList();