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

add SkinResult class

This commit is contained in:
2024-06-26 17:23:05 +03:00
parent 257068d37f
commit a15cbdd512
9 changed files with 141 additions and 71 deletions

View File

@@ -15,7 +15,7 @@ public class MineskinSkinProvider {
private static final String USER_AGENT = "SkinRestorer";
private static final String TYPE = "application/json";
public static Property getSkin(String url, SkinVariant variant) {
public static SkinResult getSkin(String url, SkinVariant variant) {
try {
String input = ("{\"variant\":\"%s\",\"name\":\"%s\",\"visibility\":%d,\"url\":\"%s\"}")
.formatted(variant.toString(), "none", 1, url);
@@ -23,9 +23,9 @@ public class MineskinSkinProvider {
JsonObject texture = JsonUtils.parseJson(WebUtils.POSTRequest(new URL(API), USER_AGENT, TYPE, TYPE, input))
.getAsJsonObject("data").getAsJsonObject("texture");
return new Property("textures", texture.get("value").getAsString(), texture.get("signature").getAsString());
return SkinResult.success(new Property("textures", texture.get("value").getAsString(), texture.get("signature").getAsString()));
} catch (IOException e) {
return null;
return SkinResult.error();
}
}
}

View File

@@ -14,15 +14,15 @@ public class MojangSkinProvider {
private static final String API = "https://api.mojang.com/users/profiles/minecraft/";
private static final String SESSION_SERVER = "https://sessionserver.mojang.com/session/minecraft/profile/";
public static Property getSkin(String name) {
public static SkinResult getSkin(String name) {
try {
UUID uuid = getUUID(name);
JsonObject texture = JsonUtils.parseJson(WebUtils.GETRequest(new URL(SESSION_SERVER + uuid + "?unsigned=false")))
.getAsJsonArray("properties").get(0).getAsJsonObject();
return new Property("textures", texture.get("value").getAsString(), texture.get("signature").getAsString());
return SkinResult.success(new Property("textures", texture.get("value").getAsString(), texture.get("signature").getAsString()));
} catch (Exception e) {
return null;
return SkinResult.error();
}
}

View File

@@ -17,6 +17,10 @@ public class SkinIO {
this.savePath = savePath;
}
public boolean skinExists(UUID uuid) {
return savePath.resolve(uuid + FILE_EXTENSION).toFile().exists();
}
public Property loadSkin(UUID uuid) {
return JsonUtils.fromJson(FileUtils.readFile(savePath.resolve(uuid + FILE_EXTENSION).toFile()), Property.class);
}

View File

@@ -13,7 +13,6 @@ import net.minecraft.server.PlayerManager;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerChunkManager;
import net.minecraft.server.world.ServerWorld;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -67,34 +66,33 @@ public class SkinRestorer implements DedicatedServerModInitializer {
playerManager.sendStatusEffects(player);
}
public static CompletableFuture<Pair<Collection<ServerPlayerEntity>, Collection<GameProfile>>> setSkinAsync(MinecraftServer server, Collection<GameProfile> targets, Supplier<Property> skinSupplier) {
return CompletableFuture.<Pair<Property, Collection<GameProfile>>>supplyAsync(() -> {
public static CompletableFuture<Pair<Collection<ServerPlayerEntity>, Collection<GameProfile>>> setSkinAsync(MinecraftServer server, Collection<GameProfile> targets, Supplier<SkinResult> skinSupplier) {
return CompletableFuture.<Pair<Optional<Property>, Collection<GameProfile>>>supplyAsync(() -> {
HashSet<GameProfile> acceptedProfiles = new HashSet<>();
Property skin = skinSupplier.get();
if (Objects.isNull(skin)) {
SkinResult result = skinSupplier.get();
if (result.isError()) {
SkinRestorer.LOGGER.error("Cannot get the skin for {}", targets.stream().findFirst().orElseThrow());
return Pair.of(null, Collections.emptySet());
}
Optional<Property> skin = result.getSkin();
for (GameProfile profile : targets) {
SkinRestorer.getSkinStorage().setSkin(profile.getId(), skin);
SkinRestorer.getSkinStorage().setSkin(profile.getId(), skin.orElse(null));
acceptedProfiles.add(profile);
}
return Pair.of(skin, acceptedProfiles);
}).<Pair<Collection<ServerPlayerEntity>, Collection<GameProfile>>>thenApplyAsync(pair -> {
Property skin = pair.left();
if (Objects.isNull(skin))
return Pair.of(Collections.emptySet(), Collections.emptySet());
Property skin = pair.left().orElse(null);
Collection<GameProfile> acceptedProfiles = pair.right();
HashSet<ServerPlayerEntity> acceptedPlayers = new HashSet<>();
JsonObject newSkinJson = gson.fromJson(new String(Base64.getDecoder().decode(skin.value()), StandardCharsets.UTF_8), JsonObject.class);
newSkinJson.remove("timestamp");
for (GameProfile profile : acceptedProfiles) {
ServerPlayerEntity player = server.getPlayerManager().getPlayer(profile.getId());
if (player == null || arePropertiesEquals(newSkinJson, player.getGameProfile()))
if (player == null || areSkinPropertiesEquals(skin, getPlayerSkin(player)))
continue;
applyRestoredSkin(player.getGameProfile(), skin);
@@ -107,23 +105,44 @@ public class SkinRestorer implements DedicatedServerModInitializer {
public static void applyRestoredSkin(GameProfile profile, Property skin) {
profile.getProperties().removeAll("textures");
profile.getProperties().put("textures", skin);
if (skin != null)
profile.getProperties().put("textures", skin);
}
private static Property getPlayerSkin(ServerPlayerEntity player) {
return player.getGameProfile().getProperties().get("textures").stream().findFirst().orElse(null);
}
private static final Gson gson = new Gson();
private static boolean arePropertiesEquals(@NotNull JsonObject x, @NotNull GameProfile y) {
Property py = y.getProperties().get("textures").stream().findFirst().orElse(null);
if (py == null)
return false;
private static JsonObject skinPropertyToJson(Property property) {
try {
JsonObject jy = gson.fromJson(new String(Base64.getDecoder().decode(py.value()), StandardCharsets.UTF_8), JsonObject.class);
jy.remove("timestamp");
return x.equals(jy);
JsonObject json = gson.fromJson(new String(Base64.getDecoder().decode(property.value()), StandardCharsets.UTF_8), JsonObject.class);
if (!Objects.isNull(json))
json.remove("timestamp");
return json;
} catch (Exception ex) {
SkinRestorer.LOGGER.info("Can not compare skin", ex);
return false;
return null;
}
}
private static boolean areSkinPropertiesEquals(Property x, Property y) {
if (x == y)
return true;
if (x == null || y == null)
return false;
if (x.equals(y))
return true;
JsonObject xJson = skinPropertyToJson(x);
JsonObject yJson = skinPropertyToJson(y);
if (xJson == null || yJson == null)
return false;
return xJson.equals(yJson);
}
}

View File

@@ -0,0 +1,43 @@
package net.lionarius.skinrestorer;
import com.mojang.authlib.properties.Property;
import org.jetbrains.annotations.NotNull;
import java.util.Optional;
public class SkinResult {
private final Property skin;
private final boolean isError;
private SkinResult(Property skin, boolean isError) {
this.skin = skin;
this.isError = isError;
}
public Optional<Property> getSkin() {
return Optional.ofNullable(this.skin);
}
public boolean isError() {
return this.isError;
}
public static SkinResult empty() {
return new SkinResult(null, false);
}
public static SkinResult error() {
return new SkinResult(null, true);
}
public static SkinResult success(@NotNull Property skin) {
return new SkinResult(skin, false);
}
public static SkinResult ofNullable(Property skin) {
if (skin == null)
return SkinResult.empty();
return SkinResult.success(skin);
}
}

View File

@@ -8,9 +8,6 @@ import java.util.UUID;
public class SkinStorage {
public static final Property DEFAULT_SKIN = new Property("textures", "ewogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJpZCIgOiAiZWZhMTFjN2U1YThlNGIwM2JjMDQ0MWRmNzk1YjE0YjIiLAogICAgICAidHlwZSIgOiAiU0tJTiIsCiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOTYzNDM4OTUzYTc4MmRhNzY5NDgwYjBhNDkxMjVhOTJlMjU5MjA3NzAwY2I4ZTNlMWFhYzM4ZTQ3MWUyMDMwOCIsCiAgICAgICJwcm9maWxlSWQiIDogImZkNjBmMzZmNTg2MTRmMTJiM2NkNDdjMmQ4NTUyOTlhIiwKICAgICAgInRleHR1cmVJZCIgOiAiOTYzNDM4OTUzYTc4MmRhNzY5NDgwYjBhNDkxMjVhOTJlMjU5MjA3NzAwY2I4ZTNlMWFhYzM4ZTQ3MWUyMDMwOCIKICAgIH0KICB9LAogICJza2luIiA6IHsKICAgICJpZCIgOiAiZWZhMTFjN2U1YThlNGIwM2JjMDQ0MWRmNzk1YjE0YjIiLAogICAgInR5cGUiIDogIlNLSU4iLAogICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85NjM0Mzg5NTNhNzgyZGE3Njk0ODBiMGE0OTEyNWE5MmUyNTkyMDc3MDBjYjhlM2UxYWFjMzhlNDcxZTIwMzA4IiwKICAgICJwcm9maWxlSWQiIDogImZkNjBmMzZmNTg2MTRmMTJiM2NkNDdjMmQ4NTUyOTlhIiwKICAgICJ0ZXh0dXJlSWQiIDogIjk2MzQzODk1M2E3ODJkYTc2OTQ4MGIwYTQ5MTI1YTkyZTI1OTIwNzcwMGNiOGUzZTFhYWMzOGU0NzFlMjAzMDgiCiAgfSwKICAiY2FwZSIgOiBudWxsCn0=",
"T6Czh1iATQTwG/ppZyY9N7cNASVHfGkiicrFykYAve4C7vG36ql0EPf6gMfMIS2eL0FdGLznnWiEC2dUxwNCJwiEyzTo/chlxZMk4TSzkBdBU3KTUZdNZrS/YhTzhi7C4eUVaEtXMRlCVtLQUa8Nb18SFYz243C9tlDsONNk42+xHPN1vRCRGIxfJbcU/mk4/XZzS4zHwPCkB6N4dKX2F6LA+a2P+CUMBluXKF56UiT1j7DjWs8B+6ES0kkmZUGkRaxTtcyN2Rqpx/2wCroohxkyVRAdlkcnwbEHOEKGoYMKdjUWpSm8QrsLkUiyLL3IK/hgd5ET2nI/aE1AloAwr1fotmvf9KF1JIfZljoefYZIaYZ1PpvduwIkAaeeIC4FFcdcBIheHitYyXOBAr/t5E+pTzCJOttDfYggFSyGxOj5yxgXTT4gSwTKp5zkQqiCKdAQQPmgFqxhWkZ2UaE9zq+E5jSOD0OJj3FmBscdZWKoOm+mWZkXbw9z2ZvuqXAKHsi6uVJyGeUzt2hJL8eqOyAmfYsJgfxhGZen5oOlxZra8OxIYlp8TcTwzEIDievgp0dfsGPObGVgtA8D39QiwLXs6e/o0qnzl3+wQJDa/ZqDMISULkBNhPx/TvhYW5MJw3hZIj2gsbf73n+jId1GOUfTVMaFlVf7pvPNqW0PieY=");
private final Map<UUID, Property> skinMap = new HashMap<>();
private final SkinIO skinIO;
@@ -18,6 +15,10 @@ public class SkinStorage {
this.skinIO = skinIO;
}
public boolean hasSavedSkin(UUID uuid) {
return this.skinIO.skinExists(uuid);
}
public Property getSkin(UUID uuid) {
if (!skinMap.containsKey(uuid)) {
Property skin = skinIO.loadSkin(uuid);
@@ -34,9 +35,6 @@ public class SkinStorage {
}
public void setSkin(UUID uuid, Property skin) {
if (skin == null)
skin = DEFAULT_SKIN;
skinMap.put(uuid, skin);
}
}

View File

@@ -1,12 +1,12 @@
package net.lionarius.skinrestorer.command;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType;
import net.lionarius.skinrestorer.MineskinSkinProvider;
import net.lionarius.skinrestorer.MojangSkinProvider;
import net.lionarius.skinrestorer.SkinRestorer;
import net.lionarius.skinrestorer.SkinResult;
import net.lionarius.skinrestorer.enums.SkinVariant;
import net.lionarius.skinrestorer.util.TranslationUtils;
import net.minecraft.command.argument.GameProfileArgumentType;
@@ -18,7 +18,6 @@ import java.util.Collection;
import java.util.Collections;
import java.util.function.Supplier;
import static net.lionarius.skinrestorer.SkinStorage.DEFAULT_SKIN;
import static net.minecraft.server.command.CommandManager.argument;
import static net.minecraft.server.command.CommandManager.literal;
@@ -58,26 +57,29 @@ public class SkinCommand {
.then(literal("clear")
.executes(context ->
skinAction(context.getSource(),
() -> DEFAULT_SKIN))
SkinResult::empty))
.then(argument("targets", GameProfileArgumentType.gameProfile()).requires(source -> source.hasPermissionLevel(2)).executes(context ->
skinAction(context.getSource(), GameProfileArgumentType.getProfileArgument(context, "targets"), true,
() -> DEFAULT_SKIN))))
SkinResult::empty))))
);
}
private static int skinAction(ServerCommandSource src, Collection<GameProfile> targets, boolean setByOperator, Supplier<Property> skinSupplier) {
private static int skinAction(ServerCommandSource src, Collection<GameProfile> targets, boolean setByOperator, Supplier<SkinResult> skinSupplier) {
SkinRestorer.setSkinAsync(src.getServer(), targets, skinSupplier).thenAccept(pair -> {
Collection<GameProfile> profiles = pair.right();
Collection<ServerPlayerEntity> players = pair.left();
if (profiles.size() == 0) {
if (profiles.isEmpty()) {
src.sendError(Text.of(TranslationUtils.translation.skinActionFailed));
return;
}
if (setByOperator) {
src.sendFeedback(() -> Text.of(
String.format(TranslationUtils.translation.skinActionAffectedProfile,
String.join(", ", profiles.stream().map(GameProfile::getName).toList()))), true);
if (players.size() != 0) {
if (!players.isEmpty()) {
src.sendFeedback(() -> Text.of(
String.format(TranslationUtils.translation.skinActionAffectedPlayer,
String.join(", ", players.stream().map(p -> p.getGameProfile().getName()).toList()))), true);
@@ -89,7 +91,7 @@ public class SkinCommand {
return targets.size();
}
private static int skinAction(ServerCommandSource src, Supplier<Property> skinSupplier) {
private static int skinAction(ServerCommandSource src, Supplier<SkinResult> skinSupplier) {
if (src.getPlayer() == null)
return 0;

View File

@@ -1,6 +1,7 @@
package net.lionarius.skinrestorer.mixin;
import net.lionarius.skinrestorer.SkinRestorer;
import net.lionarius.skinrestorer.SkinResult;
import net.minecraft.network.ClientConnection;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.PlayerManager;
@@ -21,6 +22,7 @@ public abstract class PlayerManagerMixin {
@Shadow
public abstract List<ServerPlayerEntity> getPlayerList();
@Shadow @Final
private MinecraftServer server;
@@ -39,6 +41,6 @@ public abstract class PlayerManagerMixin {
@Inject(method = "onPlayerConnect", at = @At("HEAD"))
private void onPlayerConnected(ClientConnection connection, ServerPlayerEntity player, ConnectedClientData clientData, CallbackInfo ci) {
if (player.getClass() != ServerPlayerEntity.class) // if the player isn't a server player entity, it must be someone's fake player
SkinRestorer.setSkinAsync(server, Collections.singleton(player.getGameProfile()), () -> SkinRestorer.getSkinStorage().getSkin(player.getUuid()));
SkinRestorer.setSkinAsync(server, Collections.singleton(player.getGameProfile()), () -> SkinResult.ofNullable(SkinRestorer.getSkinStorage().getSkin(player.getUuid())));
}
}

View File

@@ -1,10 +1,9 @@
package net.lionarius.skinrestorer.mixin;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import net.lionarius.skinrestorer.MojangSkinProvider;
import net.lionarius.skinrestorer.SkinRestorer;
import net.lionarius.skinrestorer.SkinStorage;
import net.lionarius.skinrestorer.SkinResult;
import net.minecraft.server.network.ServerLoginNetworkHandler;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
@@ -21,34 +20,37 @@ import java.util.concurrent.CompletableFuture;
@Mixin(ServerLoginNetworkHandler.class)
public abstract class ServerLoginNetworkHandlerMixin {
@Shadow @Nullable
private GameProfile profile;
@Shadow @Final
static Logger LOGGER;
@Shadow @Nullable
private GameProfile profile;
@Shadow @Final
static Logger LOGGER;
@Unique
private CompletableFuture<Property> skinrestorer_pendingSkin;
@Unique
private CompletableFuture<SkinResult> skinrestorer_pendingSkin;
@Inject(method = "tickVerify", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/PlayerManager;checkCanJoin(Ljava/net/SocketAddress;Lcom/mojang/authlib/GameProfile;)Lnet/minecraft/text/Text;"), cancellable = true)
public void waitForSkin(CallbackInfo ci) {
if (skinrestorer_pendingSkin == null) {
skinrestorer_pendingSkin = CompletableFuture.supplyAsync(() -> {
LOGGER.debug("Fetching {}'s skin", profile.getName());
if (SkinRestorer.getSkinStorage().getSkin(profile.getId()) == SkinStorage.DEFAULT_SKIN)
SkinRestorer.getSkinStorage().setSkin(profile.getId(), MojangSkinProvider.getSkin(profile.getName()));
@Inject(method = "tickVerify", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/PlayerManager;checkCanJoin(Ljava/net/SocketAddress;Lcom/mojang/authlib/GameProfile;)Lnet/minecraft/text/Text;"), cancellable = true)
public void waitForSkin(CallbackInfo ci) {
if (skinrestorer_pendingSkin == null) {
skinrestorer_pendingSkin = CompletableFuture.supplyAsync(() -> {
LOGGER.debug("Fetching {}'s skin", profile.getName());
if (!SkinRestorer.getSkinStorage().hasSavedSkin(profile.getId())) {
SkinResult result = MojangSkinProvider.getSkin(profile.getName());
if (!result.isError())
SkinRestorer.getSkinStorage().setSkin(profile.getId(), result.getSkin().orElse(null));
}
return SkinRestorer.getSkinStorage().getSkin(profile.getId());
});
}
return SkinResult.ofNullable(SkinRestorer.getSkinStorage().getSkin(profile.getId()));
});
}
if (!skinrestorer_pendingSkin.isDone()) {
ci.cancel();
}
}
if (!skinrestorer_pendingSkin.isDone()) {
ci.cancel();
}
}
@Inject(method = "sendSuccessPacket", at = @At("HEAD"))
public void applyRestoredSkinHook(GameProfile profile, CallbackInfo ci) {
if (skinrestorer_pendingSkin != null)
SkinRestorer.applyRestoredSkin(profile, skinrestorer_pendingSkin.getNow(SkinStorage.DEFAULT_SKIN));
}
@Inject(method = "sendSuccessPacket", at = @At("HEAD"))
public void applyRestoredSkinHook(GameProfile profile, CallbackInfo ci) {
if (skinrestorer_pendingSkin != null)
SkinRestorer.applyRestoredSkin(profile, skinrestorer_pendingSkin.getNow(SkinResult.empty()).getSkin().orElse(null));
}
}