From a346d9d3ac5393dcee5e77b6ad2def3a138f0774 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 27 Aug 2026 04:28:40 +0000 Subject: [PATCH 1/3] Remove social features, PluralKit and badges Deletes the entire "Social Options" settings tree and the features behind it: friends, chats/channels and invites, status updates, the news/notes screen, account management (username history, data export, deletion), PluralKit support and client badges. Also removes the entry points that reached those screens outside the settings menu: the title screen and pause menu Friends/Chats buttons, the Friends tab on the multiplayer screen, the chat sidebar keybind, the gallery's "shared by your friends" tab, and the e4mc/Hypixel presence feeds that only existed to publish status updates. The API client itself stays: screenshot sharing, the image gallery and the Hypixel modules still need it. It is trimmed to plain authenticated HTTP - no websocket, handlers or status thread - and the terms gate that used to live in Social Options moves to a standalone api.json, still answered through the privacy notice screen. Only acceptance is persisted now, so declining no longer locks those features off with no settings entry left to change the decision through. V10Migration carries an existing acceptance over and drops the stale category; V6Migration (PluralKit token) is removed along with the badge half of V8Migration. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_011WNEt6sbzGydQzo6TB7AA5 --- .../axolotlclient/DolphinClientCommon.java | 2 - .../DolphinClientConfigCommon.java | 17 - .../java/io/github/axolotlclient/api/API.java | 242 ++----- .../axolotlclient/api/ClientEndpoint.java | 72 -- .../github/axolotlclient/api/Constants.java | 1 - .../io/github/axolotlclient/api/Options.java | 99 +-- .../io/github/axolotlclient/api/Request.java | 14 - .../api/handlers/ChannelInviteHandler.java | 63 -- .../api/handlers/ChatHandler.java | 120 ---- .../api/handlers/FriendRequestHandler.java | 47 -- .../FriendRequestReactionHandler.java | 44 -- .../api/handlers/StatusUpdateHandler.java | 71 -- .../api/requests/AccountDataRequest.java | 50 -- .../api/requests/AccountSettingsRequest.java | 57 -- .../api/requests/AccountUsernameRequest.java | 42 -- .../api/requests/ChannelRequest.java | 154 ----- .../api/requests/FriendRequest.java | 160 ----- .../api/requests/GlobalDataRequest.java | 2 +- .../api/requests/StatusUpdate.java | 116 ---- .../api/requests/UserRequest.java | 124 ---- .../api/types/AccountSettings.java | 27 - .../axolotlclient/api/types/Channel.java | 115 ---- .../api/types/ChannelInvite.java | 26 - .../axolotlclient/api/types/ChatMessage.java | 30 - .../axolotlclient/api/types/Persistence.java | 161 ----- .../axolotlclient/api/types/PkSystem.java | 317 --------- .../axolotlclient/api/types/Relation.java | 47 -- .../axolotlclient/api/types/Status.java | 233 ------- .../github/axolotlclient/api/types/User.java | 96 --- .../api/util/SocketMessageHandler.java | 44 -- .../api/util/StatusUpdateProvider.java | 32 - .../axolotlclient/api/util/UUIDHelper.java | 4 +- .../config/migration/ConfigMigration.java | 4 +- .../{V6Migration.java => V10Migration.java} | 36 +- .../config/migration/impl/V8Migration.java | 9 - .../modules/hypixel/HypixelModApi.java | 64 -- .../modules/hypixel/HypixelMods.java | 9 - .../modules/hypixel/StatsMod.java | 2 +- .../modules/hypixel/bedwars/StatsOverlay.java | 2 +- .../util/compat/BetterTabCompat.java | 57 -- .../assets/dolphinclient/lang/en_us.json | 191 +----- .../assets/dolphinclient/lang/fr_fr.json | 189 +----- .../assets/dolphinclient/lang/pt_pt.json | 181 +---- versions/1.8.9/build.gradle.kts | 2 - .../github/axolotlclient/DolphinClient.java | 3 +- .../github/axolotlclient/api/APIOptions.java | 93 --- .../axolotlclient/api/AddFriendScreen.java | 42 -- .../axolotlclient/api/ChatsSidebar.java | 409 ------------ .../github/axolotlclient/api/ContextMenu.java | 210 ------ .../api/ContextMenuContainer.java | 74 --- .../axolotlclient/api/ContextMenuScreen.java | 42 -- .../axolotlclient/api/FriendsScreen.java | 326 --------- .../github/axolotlclient/api/NewsScreen.java | 311 --------- .../api/PrivacyNoticeScreen.java | 1 - .../api/StatusUpdateProviderImpl.java | 88 --- .../axolotlclient/api/UserListWidget.java | 181 ----- .../api/UsernameManagementScreen.java | 180 ----- .../api/chat/ChannelInvitesScreen.java | 174 ----- .../api/chat/ChannelSettingsScreen.java | 249 ------- .../api/chat/ChatListScreen.java | 110 ---- .../api/chat/ChatListWidget.java | 164 ----- .../axolotlclient/api/chat/ChatScreen.java | 189 ------ .../api/chat/ChatUserListWidget.java | 182 ----- .../axolotlclient/api/chat/ChatWidget.java | 295 --------- .../api/chat/CreateChannelScreen.java | 256 -------- .../api/e4mc/DolphinClientE4mcPlugin.java | 56 -- .../api/e4mc/E4mcMixinPlugin.java | 68 -- .../api/e4mc/E4mcStatusProvider.java | 36 - .../e4mc/mixin/QuiclimeSessionInnerMixin.java | 40 -- .../api/e4mc/mixin/QuiclimeSessionMixin.java | 39 -- .../multiplayer/FriendsMultiplayerScreen.java | 200 ------ .../FriendsMultiplayerSelectionList.java | 620 ------------------ .../api/multiplayer/ServerInfoUtil.java | 112 ---- .../axolotlclient/config/ui/MenuCatalog.java | 2 +- .../mixin/EntityRendererMixin.java | 7 - .../mixin/GameMenuScreenMixin.java | 17 - .../mixin/MinecraftClientMixin.java | 2 +- .../mixin/PlayerListHudMixin.java | 40 -- .../axolotlclient/mixin/TitleScreenMixin.java | 59 +- .../mixin/api/JoinMulitplayerScreenMixin.java | 101 --- .../axolotlclient/modules/auth/Auth.java | 5 - .../modules/auth/AuthWidget.java | 2 +- .../screenshotUtils/GalleryScreen.java | 52 +- .../modules/screenshotUtils/ImageScreen.java | 2 +- .../axolotlclient/util/BadgeRenderer.java | 69 -- .../resources/axolotlclient.e4mc.mixins.json | 13 - .../main/resources/axolotlclient.mixins.json | 1 - .../1.8.9/src/main/resources/fabric.mod.json | 1 - 88 files changed, 122 insertions(+), 8376 deletions(-) delete mode 100644 common/src/main/java/io/github/axolotlclient/api/ClientEndpoint.java delete mode 100644 common/src/main/java/io/github/axolotlclient/api/handlers/ChannelInviteHandler.java delete mode 100644 common/src/main/java/io/github/axolotlclient/api/handlers/ChatHandler.java delete mode 100644 common/src/main/java/io/github/axolotlclient/api/handlers/FriendRequestHandler.java delete mode 100644 common/src/main/java/io/github/axolotlclient/api/handlers/FriendRequestReactionHandler.java delete mode 100644 common/src/main/java/io/github/axolotlclient/api/handlers/StatusUpdateHandler.java delete mode 100644 common/src/main/java/io/github/axolotlclient/api/requests/AccountDataRequest.java delete mode 100644 common/src/main/java/io/github/axolotlclient/api/requests/AccountSettingsRequest.java delete mode 100644 common/src/main/java/io/github/axolotlclient/api/requests/AccountUsernameRequest.java delete mode 100644 common/src/main/java/io/github/axolotlclient/api/requests/ChannelRequest.java delete mode 100644 common/src/main/java/io/github/axolotlclient/api/requests/FriendRequest.java delete mode 100644 common/src/main/java/io/github/axolotlclient/api/requests/StatusUpdate.java delete mode 100644 common/src/main/java/io/github/axolotlclient/api/requests/UserRequest.java delete mode 100644 common/src/main/java/io/github/axolotlclient/api/types/AccountSettings.java delete mode 100644 common/src/main/java/io/github/axolotlclient/api/types/Channel.java delete mode 100644 common/src/main/java/io/github/axolotlclient/api/types/ChannelInvite.java delete mode 100644 common/src/main/java/io/github/axolotlclient/api/types/ChatMessage.java delete mode 100644 common/src/main/java/io/github/axolotlclient/api/types/Persistence.java delete mode 100644 common/src/main/java/io/github/axolotlclient/api/types/PkSystem.java delete mode 100644 common/src/main/java/io/github/axolotlclient/api/types/Relation.java delete mode 100644 common/src/main/java/io/github/axolotlclient/api/types/Status.java delete mode 100644 common/src/main/java/io/github/axolotlclient/api/types/User.java delete mode 100644 common/src/main/java/io/github/axolotlclient/api/util/SocketMessageHandler.java delete mode 100644 common/src/main/java/io/github/axolotlclient/api/util/StatusUpdateProvider.java rename common/src/main/java/io/github/axolotlclient/config/migration/impl/{V6Migration.java => V10Migration.java} (51%) delete mode 100644 common/src/main/java/io/github/axolotlclient/modules/hypixel/HypixelModApi.java delete mode 100644 common/src/main/java/io/github/axolotlclient/util/compat/BetterTabCompat.java delete mode 100644 versions/1.8.9/src/main/java/io/github/axolotlclient/api/AddFriendScreen.java delete mode 100644 versions/1.8.9/src/main/java/io/github/axolotlclient/api/ChatsSidebar.java delete mode 100644 versions/1.8.9/src/main/java/io/github/axolotlclient/api/ContextMenu.java delete mode 100644 versions/1.8.9/src/main/java/io/github/axolotlclient/api/ContextMenuContainer.java delete mode 100644 versions/1.8.9/src/main/java/io/github/axolotlclient/api/ContextMenuScreen.java delete mode 100644 versions/1.8.9/src/main/java/io/github/axolotlclient/api/FriendsScreen.java delete mode 100644 versions/1.8.9/src/main/java/io/github/axolotlclient/api/NewsScreen.java delete mode 100644 versions/1.8.9/src/main/java/io/github/axolotlclient/api/StatusUpdateProviderImpl.java delete mode 100644 versions/1.8.9/src/main/java/io/github/axolotlclient/api/UserListWidget.java delete mode 100644 versions/1.8.9/src/main/java/io/github/axolotlclient/api/UsernameManagementScreen.java delete mode 100644 versions/1.8.9/src/main/java/io/github/axolotlclient/api/chat/ChannelInvitesScreen.java delete mode 100644 versions/1.8.9/src/main/java/io/github/axolotlclient/api/chat/ChannelSettingsScreen.java delete mode 100644 versions/1.8.9/src/main/java/io/github/axolotlclient/api/chat/ChatListScreen.java delete mode 100644 versions/1.8.9/src/main/java/io/github/axolotlclient/api/chat/ChatListWidget.java delete mode 100644 versions/1.8.9/src/main/java/io/github/axolotlclient/api/chat/ChatScreen.java delete mode 100644 versions/1.8.9/src/main/java/io/github/axolotlclient/api/chat/ChatUserListWidget.java delete mode 100644 versions/1.8.9/src/main/java/io/github/axolotlclient/api/chat/ChatWidget.java delete mode 100644 versions/1.8.9/src/main/java/io/github/axolotlclient/api/chat/CreateChannelScreen.java delete mode 100644 versions/1.8.9/src/main/java/io/github/axolotlclient/api/e4mc/DolphinClientE4mcPlugin.java delete mode 100644 versions/1.8.9/src/main/java/io/github/axolotlclient/api/e4mc/E4mcMixinPlugin.java delete mode 100644 versions/1.8.9/src/main/java/io/github/axolotlclient/api/e4mc/E4mcStatusProvider.java delete mode 100644 versions/1.8.9/src/main/java/io/github/axolotlclient/api/e4mc/mixin/QuiclimeSessionInnerMixin.java delete mode 100644 versions/1.8.9/src/main/java/io/github/axolotlclient/api/e4mc/mixin/QuiclimeSessionMixin.java delete mode 100644 versions/1.8.9/src/main/java/io/github/axolotlclient/api/multiplayer/FriendsMultiplayerScreen.java delete mode 100644 versions/1.8.9/src/main/java/io/github/axolotlclient/api/multiplayer/FriendsMultiplayerSelectionList.java delete mode 100644 versions/1.8.9/src/main/java/io/github/axolotlclient/api/multiplayer/ServerInfoUtil.java delete mode 100644 versions/1.8.9/src/main/java/io/github/axolotlclient/mixin/api/JoinMulitplayerScreenMixin.java delete mode 100644 versions/1.8.9/src/main/java/io/github/axolotlclient/util/BadgeRenderer.java delete mode 100644 versions/1.8.9/src/main/resources/axolotlclient.e4mc.mixins.json diff --git a/common/src/main/java/io/github/axolotlclient/DolphinClientCommon.java b/common/src/main/java/io/github/axolotlclient/DolphinClientCommon.java index 4f817a9b8..1776df435 100644 --- a/common/src/main/java/io/github/axolotlclient/DolphinClientCommon.java +++ b/common/src/main/java/io/github/axolotlclient/DolphinClientCommon.java @@ -56,7 +56,6 @@ import io.github.axolotlclient.util.FeatureDisablerCommon; import io.github.axolotlclient.util.Logger; import io.github.axolotlclient.util.OSUtil; -import io.github.axolotlclient.util.compat.BetterTabCompat; import io.github.axolotlclient.util.notifications.NotificationProvider; import lombok.Getter; import net.fabricmc.loader.api.FabricLoader; @@ -162,7 +161,6 @@ private void addBuiltinCommonModules() { registerModule(DiscordRPC.getInstance()); registerModule(getApiOptions()); registerModule(Zoom.getInstance()); - registerModule(new BetterTabCompat()); } // init logic diff --git a/common/src/main/java/io/github/axolotlclient/DolphinClientConfigCommon.java b/common/src/main/java/io/github/axolotlclient/DolphinClientConfigCommon.java index 969736725..95ac132f6 100644 --- a/common/src/main/java/io/github/axolotlclient/DolphinClientConfigCommon.java +++ b/common/src/main/java/io/github/axolotlclient/DolphinClientConfigCommon.java @@ -66,29 +66,12 @@ public boolean showButton() { } } - public enum TabBadgeMode { - BEFORE_NAME, - BEFORE_NAME_ALIGNED, - BEFORE_PING; - - @Override - public String toString() { - return "tab_badge_mode." + super.toString().toLowerCase(Locale.ROOT); - } - - } - // options public final BooleanOption showOwnNametag = new BooleanOption("showOwnNametag", false); public final BooleanOption useShadows = new BooleanOption("useShadows", false); public final BooleanOption nametagBackground = new BooleanOption("nametagBackground", true); - public final BooleanOption showBadges = new BooleanOption("showBadges", true); - public final BooleanOption customBadge = new BooleanOption("customBadge", false); - public final StringOption badgeText = new StringOption("badgeText", ""); - public final EnumOption tabBadgeMode = new EnumOption<>("tab_badge_mode", TabBadgeMode.class, TabBadgeMode.BEFORE_NAME); - public final ForceableBooleanOption timeChangerEnabled = new ForceableBooleanOption("enabled", false); public final IntegerOption customTime = new IntegerOption("time", 0, 0, 24000); diff --git a/common/src/main/java/io/github/axolotlclient/api/API.java b/common/src/main/java/io/github/axolotlclient/api/API.java index 6e485528a..d9e7a8702 100644 --- a/common/src/main/java/io/github/axolotlclient/api/API.java +++ b/common/src/main/java/io/github/axolotlclient/api/API.java @@ -25,19 +25,22 @@ import java.net.ConnectException; import java.net.URI; -import java.net.http.*; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.net.http.HttpTimeoutException; import java.time.Instant; -import java.util.*; -import java.util.concurrent.*; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.stream.Collectors; import io.github.axolotlclient.DolphinClientCommon; -import io.github.axolotlclient.api.handlers.*; -import io.github.axolotlclient.api.requests.AccountSettingsRequest; import io.github.axolotlclient.api.requests.GlobalDataRequest; -import io.github.axolotlclient.api.types.*; -import io.github.axolotlclient.api.util.*; +import io.github.axolotlclient.api.util.Authentication; +import io.github.axolotlclient.api.util.MojangAuth; import io.github.axolotlclient.modules.auth.Account; import io.github.axolotlclient.util.GsonHelper; import io.github.axolotlclient.util.Logger; @@ -45,8 +48,10 @@ import io.github.axolotlclient.util.ThreadExecuter; import io.github.axolotlclient.util.notifications.NotificationProvider; import lombok.Getter; -import lombok.Setter; +/** + * Client for the backend the Hypixel modules and image sharing talk to. + */ public class API { @Getter @@ -56,60 +61,27 @@ public class API { @Getter private final NotificationProvider notificationProvider = DolphinClientCommon.getInstance().getNotificationProvider(); - private final StatusUpdateProvider statusUpdateProvider; @Getter private final Options apiOptions; - private final Collection handlers; - private WebSocket socket; + /** + * The undashed UUID of the account this session is authenticated as, or {@code null} while unauthenticated. + */ @Getter - private User self; + private String selfUuid; private Account account; private Authentication auth; - @Getter - @Setter - private AccountSettings settings; private HttpClient client; private CompletableFuture restartingFuture; private int nextRestartSecs; - private Future statusUpdateFuture; - private final ScheduledExecutorService statusUpdateExecutor; - private static final List> afterStartupListeners = new ArrayList<>(); private final AtomicBoolean starting = new AtomicBoolean(); - public API(StatusUpdateProvider statusUpdateProvider) { + public API() { if (Instance != null) { throw new IllegalStateException("API may only be instantiated once!"); } this.logger = DolphinClientCommon.getInstance().getLogger(); - this.statusUpdateProvider = statusUpdateProvider; this.apiOptions = DolphinClientCommon.getInstance().getApiOptions(); - handlers = new HashSet<>(); - handlers.add(ChatHandler.getInstance()); - handlers.add(new FriendRequestHandler()); - handlers.add(new FriendRequestReactionHandler()); - handlers.add(new StatusUpdateHandler()); - handlers.add(new ChannelInviteHandler()); Instance = this; - statusUpdateExecutor = Executors.newSingleThreadScheduledExecutor(); - } - - public static void addStartupListener(Runnable listener) { - afterStartupListeners.add(BiContainer.of(listener, ListenerType.REPEATING)); - } - - public static void addStartupListener(Runnable listener, ListenerType type) { - afterStartupListeners.add(BiContainer.of(listener, type)); - } - - public enum ListenerType { - ONCE, REPEATING - } - - public void onOpen(WebSocket channel) { - logger.debug("API connected!"); - afterStartupListeners.forEach(p -> p.getLeft().run()); - afterStartupListeners.removeIf(p -> p.getRight() == ListenerType.ONCE); - this.socket = channel; } private CompletableFuture authenticate() { @@ -156,30 +128,8 @@ private CompletableFuture authenticate() { } auth = new Authentication(response.getBody("access_token")); + selfUuid = sanitizeUUID(account.getUuid()); logDetailed("Obtained token!"); - CompletableFuture.allOf(get(Request.Route.ACCOUNT.builder().build()) - .thenAccept(r -> { - self = new User(sanitizeUUID(r.getBody("uuid")), - r.getBody("username"), Relation.NONE, - r.getBody("registered", Instant::parse), - Status.UNKNOWN, - r.ifBodyHas("previous_usernames", () -> { - List> previous = r.getBody("previous_usernames"); - return previous.stream().map(m -> new User.OldUsername((String) m.get("username"), (boolean) m.get("public"))) - .collect(Collectors.toList()); - })); - self.setSystem(PkSystem.fromToken(apiOptions.pkToken.get()).join()); - logDetailed("Created self user!"); - }), - AccountSettingsRequest.get().thenAccept(r -> { - apiOptions.retainUsernames.set(r.retainUsernames()); - apiOptions.showActivity.set(r.showActivity()); - apiOptions.showLastOnline.set(r.showLastOnline()); - apiOptions.showRegistered.set(r.showRegistered()); - apiOptions.allowFriendsImageAccess.set(r.allowFriendsImageAccess()); - })).thenRun(() -> logDetailed("completed data requests")).join(); - createSession(); - startStatusUpdateThread(); }); } @@ -200,9 +150,6 @@ public CompletableFuture delete(Request request) { } private CompletableFuture request(Request request, String method) { - if (!getApiOptions().enabled.get()) { - return CompletableFuture.completedFuture(Response.builder().status(0).body("{\"description\":\"Integration disabled!\"}").build()); - } if (!getApiOptions().privacyAccepted.get().isAccepted()) { return CompletableFuture.completedFuture(Response.CLIENT_ERROR); } @@ -253,12 +200,10 @@ private CompletableFuture request(URI url, Map payload, byt String body = response.body(); int code = response.statusCode(); - if (apiOptions.detailedLogging.get()) { - if (!url.getPath().endsWith(Request.Route.AUTHENTICATE.getPath())) { - logDetailed("Response: code: " + code + " body: " + body); - } else { - logDetailed("Response: code: " + code + " body: " + String.valueOf(body).replaceAll("(\"access_token\": ?\")[^\"]+(\")", "$1[token redacted]$2")); - } + if (!url.getPath().endsWith(Request.Route.AUTHENTICATE.getPath())) { + logDetailed("Response: code: " + code + " body: " + body); + } else { + logDetailed("Response: code: " + code + " body: " + String.valueOf(body).replaceAll("(\"access_token\": ?\")[^\"]+(\")", "$1[token redacted]$2")); } return Response.builder().body(body).status(code).headers(response.headers().map()).build(); } catch (ConnectException | HttpTimeoutException e) { @@ -296,80 +241,33 @@ public void shutdown() { restartingFuture.cancel(true); restartingFuture = null; } - if (statusUpdateFuture != null) { - statusUpdateFuture.cancel(true); - statusUpdateExecutor.shutdown(); - statusUpdateFuture = null; - } if (isAuthenticated()) { logger.debug("Shutting down API"); - if (isSocketConnected()) { - socket.sendClose(WebSocket.NORMAL_CLOSURE, "Shutdown"); - socket = null; - } // We have to rely on the gc to collect previous client objects as close() was only implemented in java 21. // However, we are currently compiling against java 17. //client.close(); auth = null; + selfUuid = null; } client = null; } - public boolean isSocketConnected() { - return socket != null; - } - public boolean isAuthenticated() { return auth != null; } public int getIndicatorColor() { - int color = isAuthenticated() ? 0xFFFFCC00 : 0xFFFF0000; - if (isSocketConnected()) { - color = 0xFF009000; - } - return color; + return isAuthenticated() ? 0xFF009000 : 0xFFFF0000; } public void logDetailed(String message, Object... args) { - if (apiOptions.detailedLogging.get()) { - logger.debug("[DETAIL] " + message, args); - } - } - - public void onMessage(String message) { - logDetailed("Handling socket message: {}", message); - - Response res = Response.builder().status(200).body(message).build(); - String target = res.getBody("target"); - boolean handled = false; - for (SocketMessageHandler handler : handlers) { - if (handler.isApplicable(target)) { - handler.handle(res); - handled = true; - } - } - if (!handled) { - logger.warn("Unhandled socket message target {}! This may be caused by using an outdated client.", target); - } + logger.debug("[DETAIL] " + message, args); } public void onError(Throwable throwable) { logger.error("Error while handling API traffic:", throwable); } - public void onClose(int statusCode, String reason) { - logDetailed("Session closed! code: " + statusCode + " reason: " + reason); - int[] error_codes = new int[]{ - 1011, - 1007, - 1014 - }; - if (Arrays.stream(error_codes).anyMatch(i -> i == statusCode) && apiOptions.enabled.get()) { - scheduleRestart(); - } - } - private CompletableFuture scheduleRestart() { if (restartingFuture != null) { restartingFuture.cancel(true); @@ -384,31 +282,10 @@ private CompletableFuture scheduleRestart() { return restartingFuture; } - private void createSession() { - if (!Constants.TESTING) { - try { - logDetailed("Connecting to websocket.."); - URI gateway = Request.Route.GATEWAY.create().resolve(); - String uri = (gateway.getScheme().endsWith("s") ? "wss" : "ws") + gateway.toString().substring(gateway.getScheme().length()); - socket = client.newWebSocketBuilder() - .header("Authorization", auth.token()) - .header("User-Agent", NetworkUtil.getUserAgent()) - .buildAsync(URI.create(uri), new ClientEndpoint()).join(); - logDetailed("Socket connected"); - } catch (Exception e) { - logger.error("Failed to start Socket! ", e); - } - } - } - public void restart() { - if (isSocketConnected()) { - shutdown(); - } + shutdown(); if (account != null) { startup(account); - } else { - apiOptions.enabled.set(false); } } @@ -422,58 +299,35 @@ public CompletableFuture startup(Account account) { return CompletableFuture.failedFuture(new UnsupportedOperationException("Account is offline")); } - - if (apiOptions.enabled.get()) { - switch (apiOptions.privacyAccepted.get()) { - case UNSET -> { - return apiOptions.openPrivacyNoteScreen.get().thenCompose(v -> - v ? startupAPI() : CompletableFuture.failedStage(new UnsupportedOperationException("Terms not accepted"))); - } - case ACCEPTED -> { - return startupAPI(); - } + switch (apiOptions.privacyAccepted.get()) { + case UNSET -> { + return apiOptions.openPrivacyNoteScreen.get().thenCompose(v -> + v ? startupAPI() : CompletableFuture.failedStage(new UnsupportedOperationException("Terms not accepted"))); + } + case ACCEPTED -> { + return startupAPI(); } } return CompletableFuture.failedFuture(new UnsupportedOperationException("API is disabled")); } private CompletableFuture startupAPI() { - if (!isSocketConnected()) { - if (Constants.TESTING) { - return CompletableFuture.failedFuture(new UnsupportedOperationException("API is disabled for testing!")); - } - if (starting.getAndSet(true)) { - return CompletableFuture.completedFuture(null); - } - - logger.info("Starting API..."); - return CompletableFuture.runAsync(() -> { - this.authenticate().join(); - starting.set(false); - }, ThreadExecuter.service()); - } else { + if (isAuthenticated()) { logger.warn("API is already running!"); + return CompletableFuture.failedFuture(new UnsupportedOperationException("API is already running")); } - return CompletableFuture.failedFuture(new UnsupportedOperationException("API is already running")); - } - - private void startStatusUpdateThread() { - statusUpdateProvider.initialize(); - if (statusUpdateFuture != null) { - statusUpdateFuture.cancel(true); + if (Constants.TESTING) { + return CompletableFuture.failedFuture(new UnsupportedOperationException("API is disabled for testing!")); } - statusUpdateFuture = statusUpdateExecutor.scheduleAtFixedRate(() -> { - try { - if (apiOptions.sendStatusUpdates.get()) { - Request request = statusUpdateProvider.getStatus(); - if (request != null) { - post(request); - } - } - } catch (Throwable e) { - logger.warn("Failed to send status update! Skipping... ", e); - } - }, 50, Constants.STATUS_UPDATE_DELAY * 1000, TimeUnit.MILLISECONDS); + if (starting.getAndSet(true)) { + return CompletableFuture.completedFuture(null); + } + + logger.info("Starting API..."); + return CompletableFuture.runAsync(() -> { + this.authenticate().join(); + starting.set(false); + }, ThreadExecuter.service()); } public static String sanitizeUUID(String uuid) { diff --git a/common/src/main/java/io/github/axolotlclient/api/ClientEndpoint.java b/common/src/main/java/io/github/axolotlclient/api/ClientEndpoint.java deleted file mode 100644 index 8d312498d..000000000 --- a/common/src/main/java/io/github/axolotlclient/api/ClientEndpoint.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api; - -import java.net.http.WebSocket; -import java.nio.ByteBuffer; -import java.util.concurrent.CompletionStage; - -import io.github.axolotlclient.util.ThreadExecuter; - - -public class ClientEndpoint implements WebSocket.Listener { - - private final StringBuilder buf = new StringBuilder(); - - @Override - public CompletionStage onText(WebSocket webSocket, CharSequence data, boolean last) { - buf.append(data); - if (last) { - String s = buf.toString(); - ThreadExecuter.scheduleTask(() -> API.getInstance().onMessage(s)); - buf.setLength(0); - } - webSocket.request(1); - return WebSocket.Listener.super.onText(webSocket, data, last); - } - - @Override - public CompletionStage onClose(WebSocket webSocket, int statusCode, String reason) { - webSocket.request(1); - API.getInstance().onClose(statusCode, reason); - return WebSocket.Listener.super.onClose(webSocket, statusCode, reason); - } - - @Override - public void onOpen(WebSocket webSocket) { - webSocket.request(1); - API.getInstance().onOpen(webSocket); - } - - @Override - public void onError(WebSocket webSocket, Throwable error) { - API.getInstance().onError(error); - WebSocket.Listener.super.onError(webSocket, error); - } - - @Override - public CompletionStage onPing(WebSocket webSocket, ByteBuffer message) { - webSocket.request(1); - return WebSocket.Listener.super.onPing(webSocket, message); - } -} diff --git a/common/src/main/java/io/github/axolotlclient/api/Constants.java b/common/src/main/java/io/github/axolotlclient/api/Constants.java index 850c4215e..8189ced3b 100644 --- a/common/src/main/java/io/github/axolotlclient/api/Constants.java +++ b/common/src/main/java/io/github/axolotlclient/api/Constants.java @@ -30,6 +30,5 @@ public class Constants { private final boolean DEVELOPMENT = false; public final String API_URL = DEVELOPMENT ? "http://localhost:8000" : "https://api.axolotlclient.com/v1/"; public final String TERMS = "https://axolotlclient.com/terms"; - public final int STATUS_UPDATE_DELAY = 15; // The Delay between Status updates, in seconds. Discord uses 15 seconds so we will as well. public final boolean TESTING = false; // When set to true, no requests will be sent } diff --git a/common/src/main/java/io/github/axolotlclient/api/Options.java b/common/src/main/java/io/github/axolotlclient/api/Options.java index e677711c8..d1e772f2b 100644 --- a/common/src/main/java/io/github/axolotlclient/api/Options.java +++ b/common/src/main/java/io/github/axolotlclient/api/Options.java @@ -25,103 +25,46 @@ import java.util.Locale; import java.util.concurrent.CompletableFuture; -import java.util.function.Consumer; import java.util.function.Supplier; import io.github.axolotlclient.DolphinClientCommon; import io.github.axolotlclient.AxolotlClientConfig.api.manager.ConfigManager; import io.github.axolotlclient.AxolotlClientConfig.api.options.OptionCategory; import io.github.axolotlclient.AxolotlClientConfig.impl.managers.JsonConfigManager; -import io.github.axolotlclient.AxolotlClientConfig.impl.options.BooleanOption; import io.github.axolotlclient.AxolotlClientConfig.impl.options.EnumOption; -import io.github.axolotlclient.AxolotlClientConfig.impl.options.StringOption; -import io.github.axolotlclient.api.requests.AccountSettingsRequest; -import io.github.axolotlclient.api.types.AccountSettings; -import io.github.axolotlclient.api.types.PkSystem; import io.github.axolotlclient.modules.Module; -import io.github.axolotlclient.util.ThreadExecuter; +/** + * Configuration of the backend connection. + *

+ * The social features this used to configure have been removed; what remains is + * the consent gate for the requests the client still makes (Hypixel data and + * image sharing). It is answered through {@code openPrivacyNoteScreen} rather + * than through a settings entry, and only acceptance is remembered. + */ public abstract class Options implements Module { protected Supplier> openPrivacyNoteScreen = () -> CompletableFuture.completedFuture(false); - public EnumOption privacyAccepted = new EnumOption<>("api.privacy_policy_accepted", PrivacyPolicyState.class, PrivacyPolicyState.UNSET, val -> { - if (!val.isAccepted() && API.getInstance().isAuthenticated()) { + + private final OptionCategory apiConfig = OptionCategory.create("api"); + private final ConfigManager apiConfigManager = new JsonConfigManager(DolphinClientCommon.resolveConfigFile("api.json"), apiConfig); + + public final EnumOption privacyAccepted = new EnumOption<>("api.privacy_policy_accepted", PrivacyPolicyState.class, PrivacyPolicyState.UNSET, val -> { + if (!val.isAccepted() && API.getInstance() != null && API.getInstance().isAuthenticated()) { API.getInstance().shutdown(); } - DolphinClientCommon.getInstance().saveConfig(); - }); - public final BooleanOption sendStatusUpdates = new BooleanOption("api.send_status_updates", true); - public final BooleanOption statusUpdateNotifs = new BooleanOption("statusUpdateNotifs", true); - public final BooleanOption friendRequestsEnabled = new BooleanOption("friendRequestsEnabled", true); - public final BooleanOption channelInvitesEnabled = new BooleanOption("api.channels.invites.enabled", false); - public final BooleanOption detailedLogging = new BooleanOption("detailedLogging", false); - public final BooleanOption enabled = new BooleanOption("enabled", true, value -> { - if (value) { - if (!privacyAccepted.get().isAccepted()) { - openPrivacyNoteScreen.get().thenAccept(v -> { - if (v) ThreadExecuter.scheduleTask(() -> API.getInstance().restart()); - }); - } else { - ThreadExecuter.scheduleTask(() -> API.getInstance().restart()); - } - } else { - ThreadExecuter.scheduleTask(() -> API.getInstance().shutdown()); - } - }); - public final BooleanOption updateNotifications = new BooleanOption("api.update_notifications", false); - public final BooleanOption displayNotes = new BooleanOption("api.display_notes", true); - public final BooleanOption addShortcutButtons = new BooleanOption("api.add_shortcut_buttons", true); - public final BooleanOption allowFriendsServerJoin = new BooleanOption("api.allow_friends_server_join", false); - private final OptionCategory pkConfig = OptionCategory.create("pluralkit_sensitive"); - private final ConfigManager pkConfigManager = new JsonConfigManager(DolphinClientCommon.resolveConfigFile("pluralkit_sensitive.json"), pkConfig); - public final StringOption pkToken = new StringOption("api.pk_token", "", s -> { - PkSystem.fromToken(s).thenAccept(sys -> { - if (sys != null) { - API.getInstance().getSelf().setSystem(sys); - } - }); - pkConfigManager.save(); - }); - public final BooleanOption autoproxy = new BooleanOption("api.pk_autoproxy", false); - public final EnumOption autoproxyMode = new EnumOption<>("api.pk_proxymode", PkSystem.ProxyMode.class, PkSystem.ProxyMode.PROXY_OFF); - public final StringOption autoproxyMember = new StringOption("api.pk_autoproxy_member", "", s -> { - if (API.getInstance().getSelf() != null && API.getInstance().getSelf().getSystem() != null) { - API.getInstance().getSelf().getSystem().updateAutoproxyMember(s); + // Only acceptance is remembered. Declining means "not right now": since there is no + // settings entry left to revisit the decision through, persisting it would turn the + // remaining online features off for good. + if (val.isAccepted()) { + apiConfigManager.save(); } }); - Consumer settingUpdated = b -> { - }; - public final BooleanOption showRegistered = new BooleanOption("api.account.settings.show_registered", true, settingUpdated::accept); - public final BooleanOption retainUsernames = new BooleanOption("api.account.settings.retain_usernames", true, settingUpdated::accept); - public final BooleanOption showLastOnline = new BooleanOption("api.account.settings.show_last_online", true, settingUpdated::accept); - public final BooleanOption showActivity = new BooleanOption("api.account.settings.show_activity", true, settingUpdated::accept); - public final BooleanOption allowFriendsImageAccess = new BooleanOption("api.account.settings.allow_friends_image_access", true, settingUpdated::accept); - - protected final OptionCategory category = OptionCategory.create("api.category"); - protected final OptionCategory badgeOptions = OptionCategory.create("api.badge_options"); - protected final OptionCategory pluralkit = OptionCategory.create("api.pluralkit"); - protected final OptionCategory account = OptionCategory.create("api.account"); @Override public void init() { - settingUpdated = b -> AccountSettingsRequest.update(new AccountSettings( - showRegistered.get(), - retainUsernames.get(), - showLastOnline.get(), - showActivity.get(), - allowFriendsImageAccess.get() - )); - pkToken.setMaxLength(65); - pkConfig.add(pkToken); - pkConfigManager.load(); - pluralkit.add(pkToken, false); - pluralkit.add(autoproxy, autoproxyMode, autoproxyMember); - account.add(showRegistered, retainUsernames, showLastOnline, showActivity, allowFriendsImageAccess); - var globalConfig = DolphinClientCommon.getInstance().getConfig(); - badgeOptions.add(globalConfig.showBadges, globalConfig.customBadge, globalConfig.badgeText, globalConfig.tabBadgeMode); - category.add(pluralkit, badgeOptions); - category.add(account, false); - category.add(enabled, privacyAccepted, friendRequestsEnabled, sendStatusUpdates, statusUpdateNotifs, channelInvitesEnabled, detailedLogging, updateNotifications, displayNotes, addShortcutButtons, allowFriendsServerJoin); + apiConfig.add(privacyAccepted); + apiConfigManager.load(); } public enum PrivacyPolicyState { diff --git a/common/src/main/java/io/github/axolotlclient/api/Request.java b/common/src/main/java/io/github/axolotlclient/api/Request.java index e4bd12784..1340988ed 100644 --- a/common/src/main/java/io/github/axolotlclient/api/Request.java +++ b/common/src/main/java/io/github/axolotlclient/api/Request.java @@ -41,20 +41,6 @@ public URI resolve() { @Getter public enum Route { AUTHENTICATE("authenticate"), - USER("user"), - ACCOUNT("account", true), - GATEWAY("gateway", true), - CHANNELS("channels", true), - CHANNELS_INVITES("channels/invites", true), - CHANNEL("channel", true), - ACCOUNT_SETTINGS("account/settings", true), - ACCOUNT_ACTIVITY("account/activity", true), - ACCOUNT_DATA("account/data", true), - ACCOUNT_USERNAME("account/username", true), - ACCOUNT_RELATIONS_FRIENDS("account/relations/friends", true), - ACCOUNT_RELATIONS_BLOCKED("account/relations/blocked", true), - ACCOUNT_RELATIONS_REQUESTS("account/relations/requests", true), - REPORT("report", true), GLOBAL_DATA("global_data"), IMAGE("image", true), HYPIXEL("hypixel", true); diff --git a/common/src/main/java/io/github/axolotlclient/api/handlers/ChannelInviteHandler.java b/common/src/main/java/io/github/axolotlclient/api/handlers/ChannelInviteHandler.java deleted file mode 100644 index 09fa8c568..000000000 --- a/common/src/main/java/io/github/axolotlclient/api/handlers/ChannelInviteHandler.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api.handlers; - -import io.github.axolotlclient.api.API; -import io.github.axolotlclient.api.Response; -import io.github.axolotlclient.api.requests.ChannelRequest; -import io.github.axolotlclient.api.types.ChannelInvite; -import io.github.axolotlclient.api.util.SocketMessageHandler; -import io.github.axolotlclient.api.util.UUIDHelper; - -public class ChannelInviteHandler implements SocketMessageHandler { - @Override - public boolean isApplicable(String target) { - return "channel_invite".equals(target) || "channel_invite_reaction".equals(target); - } - - @Override - public void handle(Response response) { - String id = response.getBody("channel", l -> Long.toUnsignedString((long) l)); - String target = response.getBody("target"); - String channelName = response.getBody("channel_name"); - switch (target) { - case "channel_invite" -> { - String from = response.getBody("from"); - if (API.getInstance().getApiOptions().channelInvitesEnabled.get()) { - notification("api.channels.invite", "api.channels.invite.desc", UUIDHelper.tryGetUsernameAsync(from).join(), channelName); - } else { - ChannelRequest.ignoreChannelInvite(new ChannelInvite(id, channelName, from)); - } - } - case "channel_invite_reaction" -> { - String player = response.getBody("player"); - boolean accepted = response.getBody("accepted"); - if (accepted) { - notification("api.channels.invite.accepted", "api.channels.invite.accepted.desc", UUIDHelper.tryGetUsernameAsync(player).join(), channelName); - } else { - notification("api.channels.invite.denied", "api.channels.invite.denied.desc", UUIDHelper.tryGetUsernameAsync(player).join(), channelName); - } - } - } - } -} diff --git a/common/src/main/java/io/github/axolotlclient/api/handlers/ChatHandler.java b/common/src/main/java/io/github/axolotlclient/api/handlers/ChatHandler.java deleted file mode 100644 index 870f164fe..000000000 --- a/common/src/main/java/io/github/axolotlclient/api/handlers/ChatHandler.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api.handlers; - -import java.time.Instant; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.function.Consumer; - -import io.github.axolotlclient.api.API; -import io.github.axolotlclient.api.Request; -import io.github.axolotlclient.api.Response; -import io.github.axolotlclient.api.requests.UserRequest; -import io.github.axolotlclient.api.types.Channel; -import io.github.axolotlclient.api.types.ChatMessage; -import io.github.axolotlclient.api.util.SocketMessageHandler; -import io.github.axolotlclient.bridge.util.AxoI18n; -import lombok.Getter; -import lombok.Setter; - -public class ChatHandler implements SocketMessageHandler { - - public static final Consumer DEFAULT_MESSAGE_CONSUMER = message -> { - }; - public static final Consumer> DEFAULT_MESSAGES_CONSUMER = messages -> { - }; - public static final NotificationsEnabler DEFAULT = message -> true; - @Getter - private static final ChatHandler Instance = new ChatHandler(); - @Setter - private Consumer messageConsumer = DEFAULT_MESSAGE_CONSUMER; - @Setter - private Consumer> messagesConsumer = DEFAULT_MESSAGES_CONSUMER; - @Setter - private NotificationsEnabler enableNotifications = DEFAULT; - - @Override - public boolean isApplicable(String target) { - return "chat_message".equals(target); - } - - @Override - public void handle(Response response) { - Instant time = Instant.now(); - String channelId = response.getBody("channel", d -> Long.toUnsignedString((long) d)); - String id = response.getBody("id", d -> Long.toUnsignedString((long) d)); - String sender = response.getBody("sender"); - String senderName = response.getBody("sender_name"); - String content = response.getBody("content"); - ChatMessage message = new ChatMessage(id, channelId, UserRequest.get(sender).join().orElseThrow(), senderName, content, time); - if (enableNotifications.showNotification(message)) { - notification(AxoI18n.translate("api.chat.newMessageFrom", message.sender().getName()), message.content()); - } - messageConsumer.accept(message); - } - - public void sendMessage(Channel channel, String message) { - if (message == null || message.isBlank()) { - return; - } - String displayName = API.getInstance().getSelf().getDisplayName(message); - - API.getInstance().post(Request.Route.CHANNEL.builder().path(channel.getId()).field("content", message) - .field("display_name", displayName).build()) - .whenComplete((res, th) -> { - ChatMessage msg = new ChatMessage(res.getPlainBody(), channel.getId(), API.getInstance().getSelf(), displayName, message, Instant.now()); - messageConsumer.accept(msg); - channel.getMessages().add(msg); - }); - } - - @SuppressWarnings("unchecked") - public void getMessagesBefore(Channel channel, long getBefore) { - API.getInstance().get(Request.Route.CHANNEL.builder().path(channel.getId()).path("messages") - .query("before", Instant.ofEpochSecond(getBefore).toString()).build()) - .thenAccept(res -> { - List> messages = (List>) res.getBody(); - - List deserialized = new ArrayList<>(); - - for (Map o : messages) { - deserialized.add(new ChatMessage(Long.toUnsignedString((long) o.get("id")), - Long.toUnsignedString((long) o.get("channel_id")), - UserRequest.get((String) o.get("sender")).join().orElseThrow(), (String) o.get("sender_name"), - (String) o.get("content"), Instant.parse((CharSequence) o.get("timestamp")))); - } - - messagesConsumer.accept(deserialized); - }); - } - - public void reportMessage(ChatMessage message) { - API.getInstance().post(Request.Route.REPORT.builder().path(message.id()).build()); - } - - public interface NotificationsEnabler { - boolean showNotification(ChatMessage message); - } -} diff --git a/common/src/main/java/io/github/axolotlclient/api/handlers/FriendRequestHandler.java b/common/src/main/java/io/github/axolotlclient/api/handlers/FriendRequestHandler.java deleted file mode 100644 index 6058b4f89..000000000 --- a/common/src/main/java/io/github/axolotlclient/api/handlers/FriendRequestHandler.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api.handlers; - -import io.github.axolotlclient.api.API; -import io.github.axolotlclient.api.Response; -import io.github.axolotlclient.api.requests.FriendRequest; -import io.github.axolotlclient.api.types.Relation; -import io.github.axolotlclient.api.util.SocketMessageHandler; -import io.github.axolotlclient.api.util.UUIDHelper; - -public class FriendRequestHandler implements SocketMessageHandler { - @Override - public boolean isApplicable(String target) { - return "friend_request".equals(target); - } - - @Override - public void handle(Response response) { - String from = response.getBody("from"); - if (API.getInstance().getApiOptions().friendRequestsEnabled.get()) { - notification("api.friends", "api.friends.request", UUIDHelper.tryGetUsernameAsync(from).join()); - } else { - FriendRequest.getInstance().setRelation(from, Relation.NONE); - } - } -} diff --git a/common/src/main/java/io/github/axolotlclient/api/handlers/FriendRequestReactionHandler.java b/common/src/main/java/io/github/axolotlclient/api/handlers/FriendRequestReactionHandler.java deleted file mode 100644 index 87cf52dd6..000000000 --- a/common/src/main/java/io/github/axolotlclient/api/handlers/FriendRequestReactionHandler.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api.handlers; - -import io.github.axolotlclient.api.Response; -import io.github.axolotlclient.api.util.SocketMessageHandler; -import io.github.axolotlclient.api.util.UUIDHelper; - -public class FriendRequestReactionHandler implements SocketMessageHandler { - @Override - public boolean isApplicable(String target) { - return "friend_request_accept".equals(target) || "friend_request_deny".equals(target); - } - - @Override - public void handle(Response response) { - String from = response.getBody("from"); - if ("friend_request_accept".equals(response.getBody("target"))) { - notification("api.friends", "api.friends.request.accepted", UUIDHelper.tryGetUsernameAsync(from).join()); - } else { - notification("api.friends", "api.friends.request.declined", UUIDHelper.tryGetUsernameAsync(from).join()); - } - } -} diff --git a/common/src/main/java/io/github/axolotlclient/api/handlers/StatusUpdateHandler.java b/common/src/main/java/io/github/axolotlclient/api/handlers/StatusUpdateHandler.java deleted file mode 100644 index 3cf8ba618..000000000 --- a/common/src/main/java/io/github/axolotlclient/api/handlers/StatusUpdateHandler.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api.handlers; - -import java.util.HashMap; -import java.util.Map; -import java.util.function.Consumer; - -import io.github.axolotlclient.api.API; -import io.github.axolotlclient.api.Response; -import io.github.axolotlclient.api.requests.UserRequest; -import io.github.axolotlclient.api.types.Status; -import io.github.axolotlclient.api.types.User; -import io.github.axolotlclient.api.util.SocketMessageHandler; -import io.github.axolotlclient.api.util.UUIDHelper; -import io.github.axolotlclient.util.GsonHelper; - -public class StatusUpdateHandler implements SocketMessageHandler { - - private static final Map> updateListeners = new HashMap<>(); - - public static void addUpdateListener(String id, Consumer listener) { - updateListeners.put(id, listener); - } - - public static void removeUpdateListener(String id) { - updateListeners.remove(id); - } - - @Override - public boolean isApplicable(String target) { - return "activity_update".equals(target) && API.getInstance().getApiOptions().statusUpdateNotifs.get(); - } - - private record StatusUpdateMessage(String user, Status.Activity activity) { - } - - @Override - public void handle(Response response) { - var status = GsonHelper.GSON.fromJson(response.getPlainBody(), StatusUpdateMessage.class); - - Status.Activity activity = status.activity(); - notification("api.friends.activity.update", translate(activity.title()) + ": " + translate(activity.description()), UUIDHelper.tryGetUsernameAsync(status.user()).join()); - UserRequest.get(status.user()).thenAccept(u -> { - User user = u.orElseThrow(); - user.getStatus().setOnline(true); - user.getStatus().setActivity(activity); - updateListeners.values().forEach(c -> c.accept(user)); - }); - } -} diff --git a/common/src/main/java/io/github/axolotlclient/api/requests/AccountDataRequest.java b/common/src/main/java/io/github/axolotlclient/api/requests/AccountDataRequest.java deleted file mode 100644 index abf823374..000000000 --- a/common/src/main/java/io/github/axolotlclient/api/requests/AccountDataRequest.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api.requests; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; - -import io.github.axolotlclient.api.API; -import io.github.axolotlclient.api.Request; - -public class AccountDataRequest { - - public static void get(Path target) { - API.getInstance().get(Request.Route.ACCOUNT_DATA.create()) - .thenAccept(res -> { - if (!res.isError()) { - try { - Files.writeString(target, res.getPlainBody()); - API.getInstance().getNotificationProvider().addStatus("api.account.export.success.title", "api.account.export.success.description", target); - } catch (IOException e) { - API.getInstance().getLogger().warn("Failed to write export file: ", e); - API.getInstance().getNotificationProvider().addStatus("api.account.export.failure.title", "api.account.export.failure.description_write"); - } - } else { - API.getInstance().getNotificationProvider().addStatus("api.account.export.failure.title", "api.account.export.failure.description_server"); - } - }); - } -} diff --git a/common/src/main/java/io/github/axolotlclient/api/requests/AccountSettingsRequest.java b/common/src/main/java/io/github/axolotlclient/api/requests/AccountSettingsRequest.java deleted file mode 100644 index cf4ce1fd4..000000000 --- a/common/src/main/java/io/github/axolotlclient/api/requests/AccountSettingsRequest.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api.requests; - -import java.util.concurrent.CompletableFuture; - -import io.github.axolotlclient.api.API; -import io.github.axolotlclient.api.Request; -import io.github.axolotlclient.api.types.AccountSettings; - -public class AccountSettingsRequest { - - public static CompletableFuture get() { - return API.getInstance().get(Request.Route.ACCOUNT_SETTINGS.builder().build()) - .thenApply(response -> - new AccountSettings( - response.getBody("show_registered"), - response.getBody("retain_usernames"), - response.getBody("show_last_online"), - response.getBody("show_activity"), - response.getBodyOrElse("allow_friends_image_access", true))); - } - - public static void update(AccountSettings settings) { - API.getInstance().patch(Request.Route.ACCOUNT_SETTINGS.builder() - .field("show_registered", settings.showRegistered()) - .field("retain_usernames", settings.retainUsernames()) - .field("show_last_online", settings.showLastOnline()) - .field("show_activity", settings.showActivity()) - .field("allow_friends_image_access", settings.allowFriendsImageAccess()) - .build()); - } - - public static CompletableFuture deleteAccount() { - return API.getInstance().delete(Request.Route.ACCOUNT.create()).thenApply(res -> !res.isError()); - } -} diff --git a/common/src/main/java/io/github/axolotlclient/api/requests/AccountUsernameRequest.java b/common/src/main/java/io/github/axolotlclient/api/requests/AccountUsernameRequest.java deleted file mode 100644 index 167f0b86a..000000000 --- a/common/src/main/java/io/github/axolotlclient/api/requests/AccountUsernameRequest.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api.requests; - -import java.util.concurrent.CompletableFuture; - -import io.github.axolotlclient.api.API; -import io.github.axolotlclient.api.Request; -import io.github.axolotlclient.api.Response; - -public class AccountUsernameRequest { - - public static void post(String username, boolean pub) { - API.getInstance().post(Request.Route.ACCOUNT_USERNAME.builder() - .path(username).query("public", pub).build()); - } - - public static CompletableFuture delete(String username) { - return API.getInstance().delete(Request.Route.ACCOUNT_USERNAME.builder() - .path(username).build()); - } -} diff --git a/common/src/main/java/io/github/axolotlclient/api/requests/ChannelRequest.java b/common/src/main/java/io/github/axolotlclient/api/requests/ChannelRequest.java deleted file mode 100644 index 27ace0ff4..000000000 --- a/common/src/main/java/io/github/axolotlclient/api/requests/ChannelRequest.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api.requests; - -import java.time.Instant; -import java.util.*; -import java.util.concurrent.CompletableFuture; - -import io.github.axolotlclient.api.API; -import io.github.axolotlclient.api.Request; -import io.github.axolotlclient.api.Response; -import io.github.axolotlclient.api.types.*; -import io.github.axolotlclient.api.util.UUIDHelper; - -public class ChannelRequest { - - public static CompletableFuture getById(String id) { - if (id.isEmpty()) { - return CompletableFuture.failedFuture(new IllegalArgumentException("Empty channel id")); - } - return API.getInstance().get(Request.Route.CHANNEL.builder().path(id).build()).thenApply(ChannelRequest::parseChannel); - } - - @SuppressWarnings("unchecked") - private static Channel parseChannel(Response response) { - String id = Long.toUnsignedString(response.getBody("id")); - String name = response.getBody("name"); - List participantUuids = response.getBody("participants"); - User owner = UserRequest.get(response.getBody("owner")).join().orElseThrow(); - List participants = participantUuids.stream().map(UserRequest::get) - .map(CompletableFuture::join).map(Optional::orElseThrow).toList(); - - List deserialized = new ArrayList<>(); - Persistence persistence = Persistence.fromJson(response.getBody("persistence")); - API.getInstance().get(Request.Route.CHANNEL.builder().path(id).path("messages") - .build()) - .thenAccept(res -> { - List> messages = (List>) res.getBody(); - - for (Map o : messages) { - deserialized.add(new ChatMessage(Long.toUnsignedString((long) o.get("id")), Long.toUnsignedString((long) o.get("channel_id")), - UserRequest.get((String) o.get("sender")).join().orElseThrow(), (String) o.get("sender_name"), - (String) o.get("content"), Instant.parse((CharSequence) o.get("timestamp")))); - } - }).join(); - if (participants.size() == 1) { - User other = owner.equals(API.getInstance().getSelf()) ? participants.get(0) : owner; - if (createDmChannelName(other.getUuid()).equals(name)) { - return new Channel.DM(id, name, persistence, participants, owner, deserialized); - } - } - return new Channel.Group(id, name, persistence, participants, owner, deserialized); - } - - @SuppressWarnings("unchecked") - public static CompletableFuture> getChannelList() { - return API.getInstance().get(Request.Route.CHANNELS.create()) - .thenApply(response -> { - List ids = (List) response.getBody(); - return ids.stream().map(Long::toUnsignedString) - .map(ChannelRequest::getById).map(CompletableFuture::join).toList(); - }); - } - - public static CompletableFuture createChannel(String name, Persistence persistence, String... users) { - if (name.isEmpty()) { - return CompletableFuture.failedFuture(new Throwable("name is empty")); - } - List participants = new ArrayList<>(); - for (String username : users) { - participants.add(UUIDHelper.ensureUuid(username).join()); - } - return API.getInstance().post(Request.Route.CHANNEL.builder() - .field("name", name).field("persistence", persistence.toJson()) - .field("participants", participants).build()); - //.thenApply(Response::getPlainBody).thenCompose(ChannelRequest::getById); - } - - public static void updateChannel(String id, String name, Persistence persistence, String... additionalUsers) { - API.getInstance().patch(Request.Route.CHANNEL.builder().path(id).field("name", name) - .field("persistence", persistence.toJson()).field("participants", additionalUsers).build()); - } - - public static CompletableFuture leaveOrDeleteChannel(Channel channel) { - return API.getInstance().delete(Request.Route.CHANNEL.builder().path(channel.getId()).build()); - } - - public static CompletableFuture getOrCreateDM(User user) { - - return getChannelList().thenApply(list -> list.stream().filter(c -> { - if (!(c instanceof Channel.DM dm)) { - return false; - } - if (dm.getOwner().equals(API.getInstance().getSelf()) && dm.getReceiver().equals(user)) { - return true; - } - return c.getOwner().equals(user) && dm.getParticipants().get(0).equals(API.getInstance().getSelf()); - }).findFirst()).thenApply(opt -> opt.orElseGet(() -> API.getInstance().post(Request.Route.CHANNEL.builder() - .field("name", createDmChannelName(user.getUuid())).field("persistence", Persistence.of(Persistence.Type.CHANNEL, 0, 0).toJson()) - .field("participants", List.of(user.getUuid())).build()) - .thenApply(Response::getPlainBody).thenCompose(ChannelRequest::getById).join())); - } - - private static String createDmChannelName(String otherUuid) { - UUID self = UUIDHelper.fromUndashed(API.getInstance().getSelf().getUuid()); - UUID other = UUIDHelper.fromUndashed(otherUuid); - int prime = 53; - - long leastSig = (self.getLeastSignificantBits() ^ other.getLeastSignificantBits()) * prime; - long mostSig = (self.getMostSignificantBits() ^ other.getMostSignificantBits()) * prime; - UUID name = new UUID(mostSig, leastSig); - return UUIDHelper.toUndashed(name); - } - - public static void removeUserFromChannel(Channel channel, User user) { - API.getInstance().post(Request.Route.CHANNEL.builder().path(channel.getId()).path("remove").query("user", user.getUuid()).build()); - } - - @SuppressWarnings("unchecked") - public static CompletableFuture> getChannelInvites() { - return API.getInstance().get(Request.Route.CHANNELS_INVITES.create()).thenApply(res -> { - List> invites = (List>) res.getBody(); - return invites.stream().map(m -> new ChannelInvite(Long.toUnsignedString((long) m.get("id")), (String) m.get("channel_name"), (String) m.get("from"))).toList(); - }); - } - - public static CompletableFuture acceptChannelInvite(ChannelInvite invite) { - return API.getInstance().post(Request.Route.CHANNELS_INVITES.builder().query("id", invite.channelId()).query("accept", true).build()); - } - - public static CompletableFuture ignoreChannelInvite(ChannelInvite invite) { - return API.getInstance().post(Request.Route.CHANNELS_INVITES.builder().query("id", invite.channelId()).query("accept", false).build()); - } -} diff --git a/common/src/main/java/io/github/axolotlclient/api/requests/FriendRequest.java b/common/src/main/java/io/github/axolotlclient/api/requests/FriendRequest.java deleted file mode 100644 index 22b3a7676..000000000 --- a/common/src/main/java/io/github/axolotlclient/api/requests/FriendRequest.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api.requests; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; - -import io.github.axolotlclient.api.API; -import io.github.axolotlclient.api.Request; -import io.github.axolotlclient.api.Response; -import io.github.axolotlclient.api.types.Relation; -import io.github.axolotlclient.api.types.User; -import io.github.axolotlclient.api.util.BiContainer; -import io.github.axolotlclient.api.util.UUIDHelper; -import lombok.Getter; - -public class FriendRequest { - - @Getter - private final static FriendRequest Instance = new FriendRequest(); - private final API api; - - protected FriendRequest() { - this.api = API.getInstance(); - } - - public CompletableFuture setRelation(String uuid, Relation relation) { - return api.post(Request.Route.USER.builder().path(uuid).query("relation", relation.getId()).build()); - } - - public CompletableFuture addFriend(String uuid) { - return setRelation(uuid, Relation.REQUEST).thenAcceptBoth(UUIDHelper.tryGetUsernameAsync(uuid), (response, name) -> { - if (!response.isError()) { - api.getNotificationProvider() - .addStatus("api.success.request_sent", "api.success.request_sent.desc", name); - } else if (response.getError().httpCode() == 404) { - api.getNotificationProvider().addStatus("api.failure.request_sent", "api.failure.request_sent.not_found", name); - } else if (response.getError().httpCode() == 403) { - api.getNotificationProvider().addStatus("api.failure.request_sent", "api.failure.request_sent.forbidden", name); - } - }); - } - - public CompletableFuture removeFriend(User user) { - return setRelation(API.sanitizeUUID(user.getUuid()), Relation.NONE).whenCompleteAsync((response, t) -> { - if (!response.isError()) { - api.getNotificationProvider().addStatus("api.success.removeFriend", "api.success.removeFriend.desc", user.getName()); - user.setRelation(Relation.NONE); - } - }); - } - - public void blockUser(User user) { - setRelation(user.getUuid(), Relation.BLOCKED).whenCompleteAsync((response, t) -> { - if (!response.isError()) { - api.getNotificationProvider().addStatus("api.success.blockUser", "api.success.blockUser.desc", user.getName()); - user.setRelation(Relation.BLOCKED); - } - }); - } - - public CompletableFuture unblockUser(User user) { - return setRelation(user.getUuid(), Relation.NONE).whenCompleteAsync((response, t) -> { - if (!response.isError()) { - api.getNotificationProvider().addStatus("api.success.unblockUser", "api.success.unblockUser.desc", user.getName()); - user.setRelation(Relation.NONE); - } - }); - } - - @SuppressWarnings("unchecked") - public CompletableFuture> getFriendUuids() { - return api.get(Request.Route.ACCOUNT_RELATIONS_FRIENDS.builder().build()) - .thenApply(r -> { - if (!r.isError()) { - return (List) r.getBody(); - } - return new ArrayList<>(); - }); - } - - public CompletableFuture> getFriends() { - return getFriendUuids() - .thenApply(r -> r.stream().map(UserRequest::get).map(CompletableFuture::join).map(Optional::orElseThrow).toList()); - } - - public CompletableFuture getOnlineFriendCount() { - return getFriends().thenApply(list -> list.stream().filter(u -> u.getStatus().isOnline()).count()); - } - - public CompletableFuture, List>> getFriendRequests() { - return api.get(Request.Route.ACCOUNT_RELATIONS_REQUESTS.create()) - .thenApply(res -> { - List in = res.getBody("in"); - List out = res.getBody("out"); - List incoming = in.stream().map(UserRequest::get).map(CompletableFuture::join).map(Optional::orElseThrow).toList(); - List outgoing = out.stream().map(UserRequest::get).map(CompletableFuture::join).map(Optional::orElseThrow).toList(); - return BiContainer.of(incoming, outgoing); - }); - } - - @SuppressWarnings("unchecked") - public CompletableFuture> getBlocked() { - return api.get(Request.Route.ACCOUNT_RELATIONS_BLOCKED.create()) - .thenApply(res -> { - List uuids = (List) res.getBody(); - return uuids.stream().map(UserRequest::get).map(CompletableFuture::join).map(Optional::orElseThrow).toList(); - }); - } - - public CompletableFuture acceptFriendRequest(User from) { - return setRelation(from.getUuid(), Relation.FRIEND) - .thenAccept(res -> { - if (!res.isError()) { - api.getNotificationProvider().addStatus("api.success.acceptFriend", "api.success.acceptFriend.desc", from.getName()); - from.setRelation(Relation.FRIEND); - } - }); - } - - public CompletableFuture denyFriendRequest(User from) { - return setRelation(from.getUuid(), Relation.NONE).thenAccept(res -> { - if (!res.isError()) { - api.getNotificationProvider().addStatus("api.success.denyFriend", "api.success.denyFriend.desc", from.getName()); - from.setRelation(Relation.NONE); - } - }); - } - - public CompletableFuture cancelFriendRequest(User to) { - return setRelation(to.getUuid(), Relation.NONE).thenAccept(res -> { - if (!res.isError()) { - api.getNotificationProvider().addStatus("api.friends", "api.friends.request.cancelled", to.getName()); - to.setRelation(Relation.NONE); - } - }); - } -} diff --git a/common/src/main/java/io/github/axolotlclient/api/requests/GlobalDataRequest.java b/common/src/main/java/io/github/axolotlclient/api/requests/GlobalDataRequest.java index 8ef1fa17f..db5b29979 100644 --- a/common/src/main/java/io/github/axolotlclient/api/requests/GlobalDataRequest.java +++ b/common/src/main/java/io/github/axolotlclient/api/requests/GlobalDataRequest.java @@ -42,7 +42,7 @@ public static CompletableFuture get() { public static CompletableFuture get(boolean forceRequest) { return CompletableFuture.supplyAsync(() -> { - if (API.getInstance().getApiOptions().enabled.get()) { + if (API.getInstance().getApiOptions().privacyAccepted.get().isAccepted()) { try { lock.acquire(); } catch (InterruptedException ignored) { diff --git a/common/src/main/java/io/github/axolotlclient/api/requests/StatusUpdate.java b/common/src/main/java/io/github/axolotlclient/api/requests/StatusUpdate.java deleted file mode 100644 index 669b0901a..000000000 --- a/common/src/main/java/io/github/axolotlclient/api/requests/StatusUpdate.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api.requests; - -import java.nio.charset.StandardCharsets; -import java.util.regex.Pattern; - -import io.github.axolotlclient.api.API; -import io.github.axolotlclient.api.Request; -import io.github.axolotlclient.api.types.Status; -import io.github.axolotlclient.bridge.util.AxoI18n; -import io.github.axolotlclient.util.GsonHelper; -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -public class StatusUpdate { - - private static Request createStatusUpdate(Status.Activity activity) { - Status status = API.getInstance().getSelf().getStatus(); - if (status.getActivity() != null) { - Status.Activity prev = status.getActivity(); - if (prev.equals(activity)) { - return null; - } - } - status.setActivity(activity); - String json = GsonHelper.GSON.toJson(activity); - API.getInstance().logDetailed("Updating status: {}", json); - return Request.Route.ACCOUNT_ACTIVITY.builder().rawBody(json.getBytes(StandardCharsets.UTF_8)) - .build(); - } - - private static Request createStatusUpdate(String title, String description) { - return createStatusUpdate(new Status.Activity(title, description)); - } - - public static Request online(MenuId menuId) { - return createStatusUpdate("api.status.title.online", "api.status.description.menu." + menuId.getIdentifier()); - } - - public static Request inGame(SupportedServer server, String gameType, String gameMode, String map) { - boolean gm = !gameMode.isEmpty(); - boolean mp = !map.isEmpty(); - String description; - if (gm && mp) { - description = AxoI18n.translate("api.status.description.in_game.game_mode_map", server.name, gameType, gameMode, map); - } else if (gm) { - description = AxoI18n.translate("api.status.description.in_game.game_mode_map", server.name, gameType, gameMode, map); - } else if (mp) { - description = AxoI18n.translate("api.status.description.in_game.map", server.name, gameType, map); - } else { - description = AxoI18n.translate("api.status.description.in_game", server.name, gameType); - } - var metadata = new Status.Activity.ExternalServerMetadata(server.name, server.ip); - return createStatusUpdate(new Status.Activity("api.status.title.in_game", description, API.getInstance().getApiOptions().allowFriendsServerJoin.get() ? metadata : null)); - } - - public static Request inGameUnknown(String description) { - return createStatusUpdate("api.status.title.in_game_unknown", description); - } - - public static Request worldHostStatusUpdate(Status.Activity.WorldHostMetadata metadata) { - return createStatusUpdate(new Status.Activity("api.status.title.world_host", metadata.serverInfo().levelName(), metadata)); - } - - public static Request e4mcStatusUpdate(Status.Activity.E4mcMetadata metadata) { - return createStatusUpdate(new Status.Activity("api.status.title.e4mc", metadata.serverInfo().levelName(), metadata)); - } - - public static Request inGameServer(String name, String ip) { - if (API.getInstance().getApiOptions().allowFriendsServerJoin.get()) { - return createStatusUpdate(new Status.Activity("api.status.title.unknown_server", name, new Status.Activity.Metadata(new Status.Activity.ExternalServerMetadata(name, ip)))); - } - return createStatusUpdate("api.status.title.unknown_server", name); - } - - @Getter - @RequiredArgsConstructor - public enum MenuId { - IN_MENU("in_menu"), - MAIN_MENU("main_menu"), - SERVER_LIST("server_list"), - SETTINGS("settings"); - private final String identifier; - } - - @RequiredArgsConstructor - @Getter - public enum SupportedServer { - HYPIXEL("Hypixel", Pattern.compile("^(?:mc\\.)?hypixel\\.net$"), "mc.hypixel.net"), - MCC_ISLAND("MCC Island", Pattern.compile("^play\\.mccisland\\.net$"), "play.mccisland.net"); - private final String name; - private final Pattern address; - private final String ip; - } -} diff --git a/common/src/main/java/io/github/axolotlclient/api/requests/UserRequest.java b/common/src/main/java/io/github/axolotlclient/api/requests/UserRequest.java deleted file mode 100644 index 1b909a612..000000000 --- a/common/src/main/java/io/github/axolotlclient/api/requests/UserRequest.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api.requests; - -import java.time.Instant; -import java.util.*; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; - -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; -import com.google.common.util.concurrent.RateLimiter; -import io.github.axolotlclient.api.API; -import io.github.axolotlclient.api.Request; -import io.github.axolotlclient.api.types.Relation; -import io.github.axolotlclient.api.types.Status; -import io.github.axolotlclient.api.types.User; -import io.github.axolotlclient.util.GsonHelper; - -@SuppressWarnings("UnstableApiUsage") -public class UserRequest { - - private static final Cache> userCache = CacheBuilder.newBuilder().expireAfterWrite(5, TimeUnit.MINUTES) - .maximumSize(400).build(); - private static final RateLimiter limiter = RateLimiter.create(2); - private static final Cache onlineCache = CacheBuilder.newBuilder().maximumSize(100).expireAfterAccess(10, TimeUnit.SECONDS) - .expireAfterWrite(2, TimeUnit.MINUTES).build(); - private static final Set onlineRequests = Collections.synchronizedSet(new TreeSet<>()); - - public static boolean getOnline(String uuid) { - - if (uuid == null || !API.getInstance().isAuthenticated()) { - return false; - } - - String sanitized = API.sanitizeUUID(uuid); - - if (API.getInstance().getSelf() != null && sanitized.equals(API.getInstance().getSelf().getUuid())) { - return true; - } - - var isOnline = onlineCache.getIfPresent(sanitized); - if (isOnline == null) { - if (!onlineRequests.contains(sanitized)) { - onlineRequests.add(sanitized); - CompletableFuture.runAsync(() -> { - limiter.acquire(); - get(sanitized).thenApply(u -> u.isPresent() && u.get().getStatus().isOnline()).thenAccept(b -> onlineCache.put(sanitized, b)) - .thenRun(() -> onlineRequests.remove(sanitized)); - }); - } - return false; - } - return isOnline; - } - - private static class UserResponse { - public String uuid; - public String username; - public String relation; - public String registered; - public Status status; - public List previousUsernames; - - public User toUser() { - Instant registered = this.registered != null ? Instant.parse(this.registered) : null; - List previousNames = previousUsernames != null ? previousUsernames.stream().map(s -> new User.OldUsername(s, true)).toList() : null; - return new User(uuid, username, Relation.get(relation), registered, this.status, previousNames); - } - } - - public static CompletableFuture> get(String dUuid) { - final String uuid = API.sanitizeUUID(dUuid); - var cached = userCache.getIfPresent(uuid); - //noinspection OptionalAssignedToNull - if (cached != null) { - return CompletableFuture.completedFuture(cached); - } - return API.getInstance().get(Request.Route.USER.builder().path(uuid).build()).thenApply(response -> { - if (response.isError()) { - return null; - } - UserResponse res = GsonHelper.GSON.fromJson(response.getPlainBody(), UserResponse.class); - return res.toUser(); - }).thenApply(u -> { - Optional opt = Optional.ofNullable(u); - userCache.put(uuid, opt); - return opt; - }); - } - - @SuppressWarnings("unchecked") - public static CompletableFuture>> getUploadedImages(String userUuid) { - return API.getInstance().get(Request.Route.USER.builder().path(userUuid).path("images").build()) - .thenApply(r -> { - if (!r.isError()) { - List list = (List) r.getBody(); - return Optional.of(list.stream().map(Long::toUnsignedString).toList()); - } - return Optional.empty(); - }); - } - -} diff --git a/common/src/main/java/io/github/axolotlclient/api/types/AccountSettings.java b/common/src/main/java/io/github/axolotlclient/api/types/AccountSettings.java deleted file mode 100644 index b367cb4ab..000000000 --- a/common/src/main/java/io/github/axolotlclient/api/types/AccountSettings.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api.types; - -public record AccountSettings(boolean showRegistered, boolean retainUsernames, boolean showLastOnline, - boolean showActivity, boolean allowFriendsImageAccess) { -} diff --git a/common/src/main/java/io/github/axolotlclient/api/types/Channel.java b/common/src/main/java/io/github/axolotlclient/api/types/Channel.java deleted file mode 100644 index 3d31bd457..000000000 --- a/common/src/main/java/io/github/axolotlclient/api/types/Channel.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api.types; - -import java.time.Instant; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -import io.github.axolotlclient.api.API; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Getter; -import org.jetbrains.annotations.NotNull; - -@AllArgsConstructor(access = AccessLevel.PROTECTED) -@Getter -public abstract class Channel implements Comparable { - - private final String id; - protected String name; - protected Persistence persistence; - private List participants; - private User owner; - private final List messages; - - public List getAllUsers() { - List list = new ArrayList<>(participants); - list.add(0, owner); - return list; - } - - public String getRealName() { - return name; - } - - @Override - public boolean equals(Object o) { - if (!(o instanceof Channel channel)) return false; - return Objects.equals(getId(), channel.getId()); - } - - @Override - public int hashCode() { - return Objects.hashCode(getId()); - } - - public abstract boolean isDM(); - - public static class Group extends Channel { - - public Group(String id, String name, Persistence persistence, List participants, User owner, List messages) { - super(id, name, persistence, participants, owner, messages); - } - - public boolean isDM() { - return false; - } - } - - @Getter - public static class DM extends Channel { - - private final User receiver; - - public DM(String id, String name, Persistence persistence, List participants, User owner, List messages) { - super(id, name, persistence, participants, owner, messages); - receiver = API.getInstance().getSelf().equals(owner) ? participants.get(0) : owner; - } - - public boolean isDM() { - return true; - } - - @Override - public String getName() { - return receiver.getName(); - } - } - - @Override - public int compareTo(@NotNull Channel o) { - if (getId().equals(o.getId())) { - return 0; - } - var o1LatestMessage = messages.stream().map(ChatMessage::timestamp).max(Instant::compareTo); - var o2LatestMessage = o.messages.stream().map(ChatMessage::timestamp).max(Instant::compareTo); - var o1Present = o1LatestMessage.isPresent(); - var o2Present = o2LatestMessage.isPresent(); - if (o1Present && o2Present) { - return o1LatestMessage.get().compareTo(o2LatestMessage.get()); - } - return o1Present ? 1 : (o2Present ? -1 : 0); - } -} diff --git a/common/src/main/java/io/github/axolotlclient/api/types/ChannelInvite.java b/common/src/main/java/io/github/axolotlclient/api/types/ChannelInvite.java deleted file mode 100644 index 51ba2e325..000000000 --- a/common/src/main/java/io/github/axolotlclient/api/types/ChannelInvite.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api.types; - -public record ChannelInvite(String channelId, String channelName, String fromUuid) { -} diff --git a/common/src/main/java/io/github/axolotlclient/api/types/ChatMessage.java b/common/src/main/java/io/github/axolotlclient/api/types/ChatMessage.java deleted file mode 100644 index 48883ddea..000000000 --- a/common/src/main/java/io/github/axolotlclient/api/types/ChatMessage.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api.types; - -import java.time.Instant; - -public record ChatMessage(String id, String channelId, User sender, String senderDisplayName, String content, - Instant timestamp) { - -} diff --git a/common/src/main/java/io/github/axolotlclient/api/types/Persistence.java b/common/src/main/java/io/github/axolotlclient/api/types/Persistence.java deleted file mode 100644 index 8d9fbfcd8..000000000 --- a/common/src/main/java/io/github/axolotlclient/api/types/Persistence.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api.types; - -import java.util.HashMap; -import java.util.Map; - -import lombok.AllArgsConstructor; -import lombok.Getter; - -public abstract class Persistence { - public abstract Type type(); - - public int count() { - return 0; - } - - public long duration() { - return 0; - } - - public static class Channel extends Persistence { - @Override - public Type type() { - return Type.CHANNEL; - } - } - - @AllArgsConstructor - private static class Duration extends Persistence { - - protected final long duration; - - @Override - public Type type() { - return Type.DURATION; - } - - @Override - public long duration() { - return duration; - } - - @Override - public Map toJson() { - Map map = super.toJson(); - map.put("duration", duration); - return map; - } - } - - @AllArgsConstructor - private static class Count extends Persistence { - - private final int count; - - @Override - public Type type() { - return Type.COUNT; - } - - @Override - public int count() { - return count; - } - - @Override - public Map toJson() { - Map map = super.toJson(); - map.put("count", count); - return map; - } - } - - @AllArgsConstructor - private static class CountDuration extends Persistence { - private final int count; - private final long duration; - - @Override - public Type type() { - return Type.COUNT_DURATION; - } - - @Override - public int count() { - return count; - } - - @Override - public long duration() { - return duration; - } - - @Override - public Map toJson() { - Map map = super.toJson(); - map.put("count", count); - map.put("duration", duration); - return map; - } - } - - @AllArgsConstructor - @Getter - public enum Type { - CHANNEL("channel"), - COUNT("count"), - DURATION("duration"), - COUNT_DURATION("count_duration"); - private final String id; - } - - public static Persistence of(Type type, int count, long duration) { - return switch (type) { - case CHANNEL -> new Channel(); - case COUNT -> new Count(count); - case DURATION -> new Duration(duration); - case COUNT_DURATION -> new CountDuration(count, duration); - }; - } - - @SuppressWarnings("unchecked") - public static Persistence fromJson(Object json) { - Map map = (Map) json; - - return switch ((String) map.get("type")) { - case "channel" -> new Channel(); - case "count" -> new Count((int) map.get("count")); - case "duration" -> new Duration((long) map.get("duration")); - case "count_duration" -> new CountDuration((int) map.get("count"), (long) map.get("duration")); - default -> throw new IllegalArgumentException(json.toString()); - }; - } - - public Map toJson() { - Map map = new HashMap<>(); - map.put("type", type().id); - return map; - } -} diff --git a/common/src/main/java/io/github/axolotlclient/api/types/PkSystem.java b/common/src/main/java/io/github/axolotlclient/api/types/PkSystem.java deleted file mode 100644 index bb27a6f40..000000000 --- a/common/src/main/java/io/github/axolotlclient/api/types/PkSystem.java +++ /dev/null @@ -1,317 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api.types; - -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; -import java.util.regex.Pattern; - -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonPrimitive; -import io.github.axolotlclient.api.API; -import io.github.axolotlclient.util.GsonHelper; -import io.github.axolotlclient.util.NetworkUtil; -import io.github.axolotlclient.util.ThreadExecuter; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; - -@Getter -public class PkSystem { - private static String token; - private final String id; - private final String name; - private final List members; - private final List fronters; - private final Member firstFronter; - private final String tag; - private final int latchTimeout; - private final String avatarUrl; - - private Member lastLatchProxy; - private long lastLatchProxyTime; - private Member autoproxyMember; - - @Builder(builderClassName = "SystemBuilder") - public PkSystem(String id, String name, List members, List fronters, Member firstFronter, String tag, int latchTimeout, String avatarUrl) { - this.id = id; - this.name = name; - this.members = members; - this.fronters = fronters; - this.firstFronter = firstFronter; - this.tag = tag; - this.latchTimeout = latchTimeout; - this.avatarUrl = avatarUrl; - updateAutoproxyMember(API.getInstance().getApiOptions().autoproxyMember.get()); - } - - public void updateAutoproxyMember(String value) { - this.autoproxyMember = members.stream().filter(m -> value.toLowerCase(Locale.ROOT).equals(m.getDisplayName()) || - value.toLowerCase(Locale.ROOT).equals(m.getId())) - .findFirst().orElse(null); - } - - private static CompletableFuture create(JsonObject system) { - - SystemBuilder builder = builder(); - builder.id(getString(system, "id")); - builder.name(getString(system, "name")); - builder.tag(getString(system, "tag")); - builder.avatarUrl(getString(system, "avatar_url")); - - return queryPkAPI("systems/@me/fronters") - .thenApply(JsonElement::getAsJsonObject).thenAccept(object -> { - JsonArray fronters = object.getAsJsonArray("members"); - List list = new ArrayList<>(); - fronters.forEach(e -> - list.add(Member.fromObject(e.getAsJsonObject())) - ); - builder.fronters(list); - builder.firstFronter(list.get(0)); - log("Fetched fronters list"); - }).thenCompose(v -> queryPkAPI("systems/@me/members").thenAccept(object -> { - JsonArray array = object.getAsJsonArray(); - List list = new ArrayList<>(); - array.forEach(e -> - list.add(Member.fromObject(e.getAsJsonObject())) - ); - builder.members(list); - log("Fetched Members list"); - })).thenCompose(v -> queryPkAPI("systems/@me/settings").thenApply(JsonElement::getAsJsonObject) - .thenAccept(object -> { - log("Fetched Latch Timeout Settings"); - if (object.has("latch_timeout") && !object.get("latch_timeout").isJsonNull()) { - builder.latchTimeout(object.get("latch_timeout").getAsInt()); - } - })) - .thenApply(v -> { - PkSystem sys = builder.build(); - log("Logged in as system: " + sys.getName()); - return sys; - }); - } - - private static void log(String message) { - API.getInstance().getLogger().debug("[PluralKit] " + message); - } - - public static CompletableFuture queryPkAPI(String route) { - return PluralKitApi.request("https://api.pluralkit.me/v2/" + route); - } - - public static CompletableFuture fromToken(String token) { - if (token.length() != 64) { - return CompletableFuture.completedFuture(null); - } - PkSystem.token = token; - return queryPkAPI("systems/@me").thenApply(JsonElement::getAsJsonObject) - .thenCompose(object -> { - if (object.has("id")) { - return create(object); - } - return null; - }); - } - - public Optional getProxy(String message) { - return getProxyMember(message) - .map(this::decorateMemberName); - } - - private Optional getProxyMember(String message) { - if (!API.getInstance().getApiOptions().autoproxy.get()) { - return Optional.empty(); - } - Optional proxy = members.stream().filter(m -> m.proxyTags.stream() - .anyMatch(p -> p.matcher(message).matches())).findFirst(); - switch (API.getInstance().getApiOptions().autoproxyMode.get()) { - case PROXY_FRONT: - return Optional.ofNullable(proxy.orElse(getFirstFronter())); - case PROXY_LATCH: - if ((lastLatchProxyTime - System.currentTimeMillis()) / 1000 > latchTimeout) { - lastLatchProxy = null; - return proxy; - } - lastLatchProxyTime = System.currentTimeMillis(); - proxy.ifPresent(member -> lastLatchProxy = member); - if (lastLatchProxy != null) { - return Optional.of(lastLatchProxy); - } - return proxy; - case PROXY_MEMBER: - return Optional.ofNullable(proxy.orElse(autoproxyMember)); - case PROXY_OFF: - default: - return Optional.empty(); - } - } - - public String decorateMemberName(Member member) { - String name = member.getDisplayName(); - if (name.isEmpty()) { - name = member.getName(); - } - return name + getTag(); - } - - public Optional getProxyAvatarUrl(Member member) { - String[] urls = new String[]{ - member.webhookAvatarUrl, - member.avatarUrl, - avatarUrl - }; - for (String url : urls) { - if (!url.isEmpty()) { - return Optional.of(url); - } - } - return Optional.empty(); - } - - @AllArgsConstructor - @Getter - public static class Member { - private String id; - private String name; - private String displayName; - private List proxyTags; - private boolean autoProxy; - private String avatarUrl; - private String webhookAvatarUrl; - - public static Member fromId(String id) { - return fromObject(queryPkAPI("members/" + id) - .thenApply(JsonElement::getAsJsonObject).join()); - } - - public static Member fromObject(JsonObject object) { - String id = getString(object, "id"); - String name = getString(object, "name"); - JsonArray tags = object.get("proxy_tags").getAsJsonArray(); - List proxyTags = new ArrayList<>(); - tags.forEach(e -> { - if (e.isJsonObject()) { - JsonObject o = e.getAsJsonObject(); - String prefix = getString(o, "prefix"); - String suffix = getString(o, "suffix"); - proxyTags.add(Pattern.compile(Pattern.quote(prefix) + ".*" + Pattern.quote(suffix))); - } - }); - boolean autoProxy = object.has("autoproxy_enabled") && object.get("autoproxy_enabled").getAsBoolean(); - return new Member(id, name, getString(object, "display_name"), proxyTags, autoProxy, - getString(object, "avatar_url"), getString(object, "webhook_avatar_url")); - } - } - - public enum ProxyMode { - PROXY_OFF, - PROXY_FRONT, - PROXY_LATCH, - PROXY_MEMBER, - ; - - static ProxyMode ofMode(String mode) { - return valueOf(mode.toUpperCase(Locale.ROOT)); - } - } - - private static String getString(JsonObject object, String key) { - if (object.has(key)) { - JsonElement e = object.get(key); - if (e.isJsonPrimitive() && ((JsonPrimitive) e).isString()) { - return e.getAsString(); - } - } - return ""; - } - - static class PluralKitApi { - - @Getter - private static final PluralKitApi instance = new PluralKitApi(); - - private PluralKitApi() { - } - - private final HttpClient client = NetworkUtil.createHttpClient(true); - private int remaining = 1; - private long resetsInMillis = 0; - private int limit = 2; - - - public CompletableFuture request(HttpRequest request) { - CompletableFuture cF = new CompletableFuture<>(); - ThreadExecuter.scheduleTask(() -> cF.complete(schedule(request))); - return cF; - } - - public static CompletableFuture request(String url) { - HttpRequest.Builder builder = HttpRequest.newBuilder(URI.create(url)); - if (!token.isEmpty()) { - builder.header("Authorization", token); - } - return getInstance().request(builder.build()); - } - - private synchronized JsonElement schedule(HttpRequest request) { - if (remaining == 0) { - try { - Thread.sleep(resetsInMillis); - } catch (InterruptedException ignored) { - } - remaining = limit; - } - return query(request); - } - - private JsonElement query(HttpRequest request) { - try { - log("Requesting: " + request); - HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - - String responseBody = response.body(); - log("Response: " + responseBody); - - remaining = Integer.parseInt(response.headers().firstValue("X-RateLimit-Remaining").orElseThrow()); - long resetsInMillisHeader = (response.headers().firstValueAsLong("X-RateLimit-Reset") - .orElseThrow() * 1000) - System.currentTimeMillis(); - // If the header value is bogus just reset in 0.5 seconds - this.resetsInMillis = resetsInMillisHeader < 0 ? 500 : resetsInMillisHeader; - limit = Integer.parseInt(response.headers().firstValue("X-RateLimit-Limit").orElseThrow()); - - return GsonHelper.GSON.fromJson(responseBody, JsonElement.class); - } catch (Exception e) { - return new JsonObject(); - } - } - } -} diff --git a/common/src/main/java/io/github/axolotlclient/api/types/Relation.java b/common/src/main/java/io/github/axolotlclient/api/types/Relation.java deleted file mode 100644 index 3247a6fed..000000000 --- a/common/src/main/java/io/github/axolotlclient/api/types/Relation.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api.types; - -import java.util.Arrays; -import java.util.Map; -import java.util.function.Function; -import java.util.stream.Collectors; - -import lombok.AllArgsConstructor; -import lombok.Getter; - -@Getter -@AllArgsConstructor -public enum Relation { - NONE("none"), - REQUEST("request"), - FRIEND("friend"), - BLOCKED("blocked"); - private final String id; - - private static final Map idMap = Arrays.stream(values()).collect(Collectors.toMap(r -> r.id, Function.identity())); - - public static Relation get(String id) { - return idMap.get(id); - } -} diff --git a/common/src/main/java/io/github/axolotlclient/api/types/Status.java b/common/src/main/java/io/github/axolotlclient/api/types/Status.java deleted file mode 100644 index 72b32dd0a..000000000 --- a/common/src/main/java/io/github/axolotlclient/api/types/Status.java +++ /dev/null @@ -1,233 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * Copyright © 2026 DSNS - * - * This file is part of DolphinClient, a fork of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api.types; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.time.Instant; -import java.time.ZoneId; -import java.util.Base64; -import java.util.List; - -import com.google.gson.JsonObject; -import com.google.gson.TypeAdapter; -import com.google.gson.annotations.JsonAdapter; -import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonToken; -import com.google.gson.stream.JsonWriter; -import io.github.axolotlclient.DolphinClientCommon; -import io.github.axolotlclient.bridge.util.AxoI18n; -import io.github.axolotlclient.util.GsonHelper; -import lombok.*; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -@Getter -@Setter -@EqualsAndHashCode -@ToString -@AllArgsConstructor -public class Status { - - public static final Status UNKNOWN = new Status("offline", null, Activity.UNKNOWN); - - private String type; - @Nullable - private final Instant lastOnline; - @Nullable - private Activity activity; - - public boolean isOnline() { - return "online".equals(type); - } - - public String getDescription() { - return activity == null || activity.description.isEmpty() ? "" : AxoI18n.translate(activity.description); - } - - public String getTitle() { - if (!isOnline()) { - return AxoI18n.translate("api.status.title.offline"); - } - return AxoI18n.translate(activity == null || activity.title.isEmpty() ? "api.status.title.online" : activity.title); - } - - public String getLastOnline() { - return lastOnline == null ? null : AxoI18n.translate("api.status.last_online", lastOnline.atZone(ZoneId.systemDefault()).format(DolphinClientCommon.getInstance().getConfig().getDateTimeFormatter())); - } - - public void setOnline(boolean online) { - if (online) { - type = "online"; - } else { - type = "offline"; - } - } - - public record Activity(String title, String description, Metadata metadata, - @EqualsAndHashCode.Exclude Instant started) { - private static final Activity UNKNOWN = new Activity("", "", null, Instant.EPOCH); - - public Activity(String title, String description) { - this(title, description, (Metadata) null); - } - - public Activity(String title, String description, Metadata metadata) { - this(title, description, metadata, Instant.now()); - } - - public Activity(String title, String description, MetadataAttributes attributes) { - this(title, description, attributes != null ? new Metadata(attributes) : null); - } - - public boolean hasMetadata() { - return metadata != null; - } - - public boolean hasMetadata(String id) { - return hasMetadata() && metadata.type.equals(id); - } - - public interface MetadataAttributes { - String typeId(); - } - - @JsonAdapter(Metadata.MetadataTypeAdapter.class) - public record Metadata(String type, MetadataAttributes attributes) { - public Metadata(MetadataAttributes attributes) { - this(attributes.typeId(), attributes); - } - - public static class MetadataTypeAdapter extends TypeAdapter { - - @Override - public void write(JsonWriter out, Metadata value) throws IOException { - if (value == null) { - out.nullValue(); - return; - } - out.beginObject(); - out.name("type").value(value.type); - out.name("attributes").jsonValue(GsonHelper.GSON.toJson(value.attributes)); - out.endObject(); - } - - @Override - public Metadata read(JsonReader in) throws IOException { - if (in.peek() == JsonToken.NULL) { - return null; - } - JsonObject metadataObj = GsonHelper.GSON.fromJson(in, JsonObject.class); - String type = metadataObj.get("type").getAsString(); - MetadataAttributes attributes = switch (type) { - case WorldHostMetadata.ID -> - GsonHelper.GSON.fromJson(metadataObj.get("attributes"), WorldHostMetadata.class); - case E4mcMetadata.ID -> - GsonHelper.GSON.fromJson(metadataObj.get("attributes"), E4mcMetadata.class); - case ExternalServerMetadata.ID -> - GsonHelper.GSON.fromJson(metadataObj.get("attributes"), ExternalServerMetadata.class); - default -> throw new IllegalArgumentException("Unsupported attributes id: " + type); - }; - return new Metadata(type, attributes); - } - } - } - - public record WorldHostMetadata(String connectionId, String externalIp, - ServerInfo serverInfo) implements MetadataAttributes { - public static final String ID = "world_host"; - - @Override - public String typeId() { - return ID; - } - - public E4mcMetadata asE4mcMetadata() { - return new E4mcMetadata(externalIp, serverInfo); - } - } - - public record E4mcMetadata(String domain, ServerInfo serverInfo) implements MetadataAttributes { - public static final String ID = "e4mc"; - - @Override - public String typeId() { - return ID; - } - } - - public record ExternalServerMetadata(String serverName, String address) implements MetadataAttributes { - public static final String ID = "external_server"; - - @Override - public String typeId() { - return ID; - } - } - - public record ServerInfo(String levelName, String description, Favicon icon, Players players, Version version) { - @JsonAdapter(Favicon.FaviconTypeAdapter.class) - public record Favicon(byte[] iconBytes) { - private static final String PREFIX = "data:image/png;base64,"; - - @Override - public @NotNull String toString() { - return PREFIX + new String(Base64.getEncoder().encode(iconBytes), StandardCharsets.UTF_8); - } - - public static Favicon fromString(String base64String) { - if (!base64String.startsWith(PREFIX)) { - return new Favicon(null); - } - return new Favicon(Base64.getDecoder().decode(base64String.substring(PREFIX.length()).replaceAll("\n", "").getBytes(StandardCharsets.UTF_8))); - } - - public static class FaviconTypeAdapter extends TypeAdapter { - - @Override - public void write(JsonWriter out, Favicon value) throws IOException { - if (value.iconBytes() == null) { - out.nullValue(); - return; - } - out.value(value.toString()); - } - - @Override - public Favicon read(JsonReader in) throws IOException { - return fromString(in.nextString()); - } - } - } - - public record Players(int max, int online, List sample) { - public record Player(String name, String uuid) { - } - } - - public record Version(String name, int protocol) { - } - } - } -} diff --git a/common/src/main/java/io/github/axolotlclient/api/types/User.java b/common/src/main/java/io/github/axolotlclient/api/types/User.java deleted file mode 100644 index 604a42a21..000000000 --- a/common/src/main/java/io/github/axolotlclient/api/types/User.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api.types; - -import java.time.Instant; -import java.util.List; - -import io.github.axolotlclient.api.API; -import lombok.*; -import org.jetbrains.annotations.Nullable; - -@Getter -@Setter -@ToString -public class User { - - private String uuid; - protected String name; - private Relation relation; - private Instant registered; - private Status status; - private List previousUsernames; - @Nullable - private PkSystem system; - - public User(String uuid, String name, Relation relation, Instant registered, Status status, List previousUsernames) { - this.uuid = API.sanitizeUUID(uuid); - this.status = status; - this.name = name; - this.relation = relation; - this.registered = registered; - this.previousUsernames = previousUsernames; - } - - public boolean isSystem() { - return system != null; - } - - @Override - public int hashCode() { - int result = 1; - String $name = this.getName(); - result = result * 59 + ($name == null ? 43 : $name.hashCode()); - String $uuid = this.getUuid(); - result = result * 59 + ($uuid == null ? 43 : $uuid.hashCode()); - Status $status = this.getStatus(); - result = result * 59 + ($status == null ? 43 : $status.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (obj instanceof User) { - return uuid.equals(((User) obj).getUuid()); - } - return false; - } - - public String getDisplayName(String message) { - if (!isSystem()) { - return getName(); - } - //noinspection DataFlowIssue - return getSystem().getProxy(message).orElse(getName()); - } - - @Data - @AllArgsConstructor - public static class OldUsername { - private String name; - private boolean pub; - } -} diff --git a/common/src/main/java/io/github/axolotlclient/api/util/SocketMessageHandler.java b/common/src/main/java/io/github/axolotlclient/api/util/SocketMessageHandler.java deleted file mode 100644 index 5f767be77..000000000 --- a/common/src/main/java/io/github/axolotlclient/api/util/SocketMessageHandler.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api.util; - -import io.github.axolotlclient.api.API; -import io.github.axolotlclient.api.Response; -import io.github.axolotlclient.bridge.util.AxoI18n; - -public interface SocketMessageHandler { - - boolean isApplicable(String target); - - default void handle(Response response) { - - } - - default String translate(String key, Object... args) { - return AxoI18n.translate(key, args); - } - - default void notification(String titleKey, String descKey, Object... args) { - API.getInstance().getNotificationProvider().addStatus(titleKey, descKey, args); - } -} diff --git a/common/src/main/java/io/github/axolotlclient/api/util/StatusUpdateProvider.java b/common/src/main/java/io/github/axolotlclient/api/util/StatusUpdateProvider.java deleted file mode 100644 index 53109a35b..000000000 --- a/common/src/main/java/io/github/axolotlclient/api/util/StatusUpdateProvider.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api.util; - -import io.github.axolotlclient.api.Request; - -public interface StatusUpdateProvider { - - void initialize(); - - Request getStatus(); -} diff --git a/common/src/main/java/io/github/axolotlclient/api/util/UUIDHelper.java b/common/src/main/java/io/github/axolotlclient/api/util/UUIDHelper.java index 3eeaa380b..dffb918fa 100644 --- a/common/src/main/java/io/github/axolotlclient/api/util/UUIDHelper.java +++ b/common/src/main/java/io/github/axolotlclient/api/util/UUIDHelper.java @@ -57,9 +57,7 @@ private static CachedAPI create(String endpoint, String jsonKey, return Optional.of(obj.get(jsonKey).getAsString()); } - if (API.getInstance().getApiOptions().detailedLogging.get()) { - API.getInstance().getLogger().warn("Conversion {} failed: {}", log, obj); - } + API.getInstance().getLogger().debug("Conversion {} failed: {}", log, obj); return Optional.empty(); }); diff --git a/common/src/main/java/io/github/axolotlclient/config/migration/ConfigMigration.java b/common/src/main/java/io/github/axolotlclient/config/migration/ConfigMigration.java index 68c66ce07..01866f654 100644 --- a/common/src/main/java/io/github/axolotlclient/config/migration/ConfigMigration.java +++ b/common/src/main/java/io/github/axolotlclient/config/migration/ConfigMigration.java @@ -40,10 +40,10 @@ public interface ConfigMigration { new V3Migration(), new V4Migration(), new V5Migration(), - new V6Migration(), new V7Migration(), new V8Migration(), - new V9Migration() + new V9Migration(), + new V10Migration() )); static void apply(int oldVersion, JsonObject config) { diff --git a/common/src/main/java/io/github/axolotlclient/config/migration/impl/V6Migration.java b/common/src/main/java/io/github/axolotlclient/config/migration/impl/V10Migration.java similarity index 51% rename from common/src/main/java/io/github/axolotlclient/config/migration/impl/V6Migration.java rename to common/src/main/java/io/github/axolotlclient/config/migration/impl/V10Migration.java index 5a78efc1c..5d5031bfb 100644 --- a/common/src/main/java/io/github/axolotlclient/config/migration/impl/V6Migration.java +++ b/common/src/main/java/io/github/axolotlclient/config/migration/impl/V10Migration.java @@ -1,8 +1,7 @@ /* - * Copyright © 2025 moehreag & Contributors * Copyright © 2026 DSNS * - * This file is part of DolphinClient, a fork of AxolotlClient. + * This file is part of DolphinClient. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -23,21 +22,40 @@ package io.github.axolotlclient.config.migration.impl; +import java.util.Arrays; +import java.util.Optional; + import com.google.gson.JsonObject; import io.github.axolotlclient.DolphinClientCommon; +import io.github.axolotlclient.api.Options; import io.github.axolotlclient.config.migration.ConfigMigration; -public class V6Migration implements ConfigMigration { +/** + * Drops the removed "Social Options" category, carrying the privacy policy + * decision over to the standalone api config so users are not asked again. + */ +public class V10Migration implements ConfigMigration { + private static final String CATEGORY = "api.category"; + private static final String PRIVACY_POLICY = "api.privacy_policy_accepted"; + @Override public int version() { - return 6; + return 10; } @Override - public void apply(JsonObject json) { - getObject(json, "api.category") - .flatMap(api -> getObject(api, "api.pluralkit")) - .flatMap(pk -> getString(pk, "api.pk_token")) - .ifPresent(DolphinClientCommon.getInstance().getApiOptions().pkToken::set); + public void apply(JsonObject config) { + getObject(config, CATEGORY).ifPresent(api -> { + getString(api, PRIVACY_POLICY) + .flatMap(V10Migration::parseState) + .ifPresent(DolphinClientCommon.getInstance().getApiOptions().privacyAccepted::set); + config.remove(CATEGORY); + }); + } + + private static Optional parseState(String serialized) { + return Arrays.stream(Options.PrivacyPolicyState.values()) + .filter(state -> state.toString().equals(serialized) || state.name().equalsIgnoreCase(serialized)) + .findFirst(); } } diff --git a/common/src/main/java/io/github/axolotlclient/config/migration/impl/V8Migration.java b/common/src/main/java/io/github/axolotlclient/config/migration/impl/V8Migration.java index 09308bda6..f7927b8d6 100644 --- a/common/src/main/java/io/github/axolotlclient/config/migration/impl/V8Migration.java +++ b/common/src/main/java/io/github/axolotlclient/config/migration/impl/V8Migration.java @@ -37,21 +37,12 @@ public void apply(JsonObject config) { var showOwnNametag = getBooleanOrFalse(n, "showOwnNametag"); var shadows = getBooleanOrFalse(n, "useShadows"); var nametagBackground = getBooleanOrFalse(n, "nametagBackground"); - var badges = getBooleanOrFalse(n, "showBadges"); - var customBadge = getBooleanOrFalse(n, "customBadge"); - var customBadgeText = getString(n, "customBadgeText"); var rendering = getOrAddObject(config, "rendering"); var nametagRenderingOptions = new JsonObject(); rendering.add("nametagOptions", nametagRenderingOptions); nametagRenderingOptions.addProperty("showOwnNametag", showOwnNametag); nametagRenderingOptions.addProperty("useShadows", shadows); nametagRenderingOptions.addProperty("nametagBackground", nametagBackground); - var apiOptions = getOrAddObject(config, "api.category"); - var badgeOptions = new JsonObject(); - apiOptions.add("api.badge_options", badgeOptions); - badgeOptions.addProperty("showBadges", badges); - badgeOptions.addProperty("customBadge", customBadge); - customBadgeText.ifPresent(s -> badgeOptions.addProperty("customBadgeText", s)); }); } } diff --git a/common/src/main/java/io/github/axolotlclient/modules/hypixel/HypixelModApi.java b/common/src/main/java/io/github/axolotlclient/modules/hypixel/HypixelModApi.java deleted file mode 100644 index 45a2eb363..000000000 --- a/common/src/main/java/io/github/axolotlclient/modules/hypixel/HypixelModApi.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.modules.hypixel; - -import java.util.Arrays; - -import io.github.axolotlclient.api.Request; -import io.github.axolotlclient.api.requests.StatusUpdate; -import io.github.axolotlclient.bridge.events.Events; -import net.hypixel.data.type.ServerType; -import net.hypixel.modapi.HypixelModAPI; -import net.hypixel.modapi.packet.impl.clientbound.event.ClientboundLocationPacket; - -public class HypixelModApi { - private ClientboundLocationPacket current; - - public void init() { - try { - Class.forName("net.hypixel.modapi.HypixelModAPI"); - } catch (ClassNotFoundException e) { - return; - } - - init0(); - } - - private void init0() { - HypixelModAPI.getInstance().createHandler(ClientboundLocationPacket.class, packet -> current = packet); - Events.CONNECTION_PLAY_READY.register(info -> { - if (info == null) return; - var address = info.br$getIp(); - if (address != null && Arrays.stream(StatusUpdate.SupportedServer.values()) - .filter(s -> s.getAddress().matcher(address).matches()) - .anyMatch(s -> s.equals(StatusUpdate.SupportedServer.HYPIXEL))) { - HypixelModAPI.getInstance().subscribeToEventPacket(ClientboundLocationPacket.class); - } - }); - } - - public Request getStatus() { - if (current == null) return null; - return StatusUpdate.inGame(StatusUpdate.SupportedServer.HYPIXEL, current.getServerType().map(ServerType::getName).orElse(""), current.getMode().orElse(""), current.getMap().orElse("")); - } -} diff --git a/common/src/main/java/io/github/axolotlclient/modules/hypixel/HypixelMods.java b/common/src/main/java/io/github/axolotlclient/modules/hypixel/HypixelMods.java index dd2a713b3..ad55e8be6 100644 --- a/common/src/main/java/io/github/axolotlclient/modules/hypixel/HypixelMods.java +++ b/common/src/main/java/io/github/axolotlclient/modules/hypixel/HypixelMods.java @@ -30,7 +30,6 @@ import io.github.axolotlclient.AxolotlClientConfig.impl.options.BooleanOption; import io.github.axolotlclient.AxolotlClientConfig.impl.options.EnumOption; import io.github.axolotlclient.DolphinClientConfigCommon; -import io.github.axolotlclient.api.Request; import io.github.axolotlclient.bridge.events.Events; import io.github.axolotlclient.modules.AbstractCommonModule; import io.github.axolotlclient.modules.hypixel.bedwars.BedwarsMod; @@ -45,8 +44,6 @@ public class HypixelMods extends AbstractCommonModule { private final List subModules = new ArrayList<>(); private final BooleanOption removeLobbyJoinMessages = new BooleanOption("removeLobbyJoinMessages", false); - private final HypixelModApi modApi = new HypixelModApi(); - public static HypixelMods getInstance() { return INSTANCE; } @@ -75,8 +72,6 @@ public void init() { AutoBoop.getInstance().handleMessage(event.getOriginalMessage()); HypixelMessages.getInstance().process(removeLobbyJoinMessages, "lobby_join", event); }); - - modApi.init(); } @Override @@ -98,8 +93,4 @@ private void addSubModule(AbstractHypixelMod mod) { public enum HypixelApiCacheMode { ON_CLIENT_DISCONNECT, ON_PLAYER_DISCONNECT, } - - public Request getStatus() { - return modApi.getStatus(); - } } diff --git a/common/src/main/java/io/github/axolotlclient/modules/hypixel/StatsMod.java b/common/src/main/java/io/github/axolotlclient/modules/hypixel/StatsMod.java index b56481242..0bf7dd936 100644 --- a/common/src/main/java/io/github/axolotlclient/modules/hypixel/StatsMod.java +++ b/common/src/main/java/io/github/axolotlclient/modules/hypixel/StatsMod.java @@ -197,7 +197,7 @@ private void register(Commands commands) { for (Entry handler : HANDLERS) { command.then(Commands.literal(handler.name()).then(argument("player", PlayerArgument.player()).executes(c -> { - if (!API.getInstance().getApiOptions().enabled.get()) { + if (!API.getInstance().getApiOptions().privacyAccepted.get().isAccepted()) { c.getSource().br$sendError(translatable("playerstats.error.api_disabled").br$color(RED)); return -1; } diff --git a/common/src/main/java/io/github/axolotlclient/modules/hypixel/bedwars/StatsOverlay.java b/common/src/main/java/io/github/axolotlclient/modules/hypixel/bedwars/StatsOverlay.java index 9c4ef4f29..3ffb0895e 100644 --- a/common/src/main/java/io/github/axolotlclient/modules/hypixel/bedwars/StatsOverlay.java +++ b/common/src/main/java/io/github/axolotlclient/modules/hypixel/bedwars/StatsOverlay.java @@ -196,7 +196,7 @@ private void onStart(BedwarsGame g) { stats = new HashMap<>(); shouldRender = toggleKey.get() && autoActivate.get(); - if (!API.getInstance().getApiOptions().enabled.get()) { + if (!API.getInstance().getApiOptions().privacyAccepted.get().isAccepted()) { errorMessage = "API Not Enabled!"; return; } diff --git a/common/src/main/java/io/github/axolotlclient/util/compat/BetterTabCompat.java b/common/src/main/java/io/github/axolotlclient/util/compat/BetterTabCompat.java deleted file mode 100644 index 02f13a1f8..000000000 --- a/common/src/main/java/io/github/axolotlclient/util/compat/BetterTabCompat.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright © 2026 moehreag & Contributors - * Copyright © 2026 DSNS - * - * This file is part of DolphinClient, a fork of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.util.compat; - -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; -import java.util.List; -import java.util.function.BiConsumer; - -import io.github.axolotlclient.DolphinClientCommon; -import io.github.axolotlclient.api.requests.UserRequest; -import io.github.axolotlclient.bridge.AxoPlayerListEntry; -import io.github.axolotlclient.bridge.util.AxoIdentifier; -import io.github.axolotlclient.modules.Module; -import net.fabricmc.loader.api.FabricLoader; - -public class BetterTabCompat implements Module { - @Override - public void init() { - if (FabricLoader.getInstance().isModLoaded("bettertab")) { - var logger = DolphinClientCommon.getInstance().getLogger(); - try { - var badgeManagerClass = Class.forName("tab.bettertab.tabList.BadgeManager"); - var handle = MethodHandles.lookup().findStatic(badgeManagerClass,"registerBadgeProvider", MethodType.methodType(void.class, BiConsumer.class)); - var consumer = (BiConsumer>)(entry, list) -> { - if (DolphinClientCommon.getInstance().getConfig().showBadges.get() && UserRequest.getOnline(entry.br$getId().toString())) { - list.add(DolphinClientCommon.BADGE_PATH); - } - }; - handle.invoke(consumer); - } catch (Throwable e) { - logger.warn("Failed to register badge provider for BetterTab"); - } - } - } -} diff --git a/common/src/main/resources/assets/dolphinclient/lang/en_us.json b/common/src/main/resources/assets/dolphinclient/lang/en_us.json index 6bf1695a2..f8bc995dd 100644 --- a/common/src/main/resources/assets/dolphinclient/lang/en_us.json +++ b/common/src/main/resources/assets/dolphinclient/lang/en_us.json @@ -101,7 +101,6 @@ "back": "Back", "background": "Background", "backgroundcolor": "Background Color", - "badgeText": "Custom Badge Text", "ban_reason": "This option is disabled
because it is known to be bannable on your current server.", "bar": "Bar", "barColor": "Bar Color", @@ -172,7 +171,6 @@ "creditsBGM": "BGM", "crosshair_type": "Type", "crosshairhud": "Crosshair", - "customBadge": "Custom Badges", "customCloudHeight": "Custom Cloud Height", "customCloudHeight.tooltip": "Whether to use a custom cloud height.", "customColor": "Custom Color", @@ -365,8 +363,6 @@ "showActivity": "Show Activity", "showActivity.tooltip": "Whether to show your current In-Game Activity.", "showAllocated": "Show Allocated", - "showBadges": "Show Badges", - "showBadges.tooltip": "Whether Nametag and Tablist Badges are enabled.
Requires Social Features to be enabled.", "showBeaconBeams": "Show Beacon Beams", "showD": "Show D", "showEffectNames": "Show Effect Names", @@ -439,80 +435,13 @@ "zoomScrolling.tooltip": "Whether to allow scrolling to modify the zoom factor.", "removeLobbyJoinMessages": "Remove Lobby Join Messages", "zoomSpeed": "Animation Speed", - "api.error.userNotFound": "The specified user could not be found.", - "api.error.friendRequestNotFound": "You do not have a friend request from this user.", - "api.error.userBlocked": "You blocked this user.", - "api.error.userAlreadyBlocked": "You already blocked this user.", - "api.error.userAlreadyFriends": "You are already friends with this user.", - "api.screen.friends": "Friends", - "api.friends.pending.incoming": "Incoming Request", - "api.friends.pending.outgoing": "Outgoing Request", - "api.friends.tab.online": "Online", - "api.friends.tab.all": "All", - "api.friends.tab.pending": "Pending", - "api.friends.tab.blocked": "Blocked", - "api.friends.add": "Add Friend...", - "api.friends.remove": "Remove", - "api.friends.request.deny": "Deny", - "api.friends.request.accept": "Accept", - "api.friends.chat": "Open Chat...", - "api.category": "Social Options", - "friendRequestsEnabled": "Allow Friend Requests", - "detailedLogging": "Detailed Logging", "detailedLogging.tooltip": "In case you need even more verbose logs.", - "viewFriends": "View Friends", - "api.screen.friends.add": "Add Friend", - "api.screen.friends.add.name": "Enter Name or UUID:", - "api.success.request_sent": "Friend request sent", - "api.success.request_sent.desc": "Sent a friend request to %s!", - "api.failure.request_sent": "Failed to send friend request", - "api.failure.request_sent.not_found": "User %s couldn't be found!", - "api.failure.request_sent.forbidden": "You aren't allowed to send a friend request to %s!", - "api.success.removeFriend": "Friend removed", - "api.success.removeFriend.desc": "Removed %s from friends list!", - "api.success.blockUser": "User Blocked", - "api.success.blockUser.desc": "Added %s to blocked Users.", - "api.success.unblockUser": "User Unblocked", - "api.success.unblockUser.desc": "Unblocked %s!", - "api.success.acceptFriend": "Friend Request Accepted", - "api.success.acceptFriend.desc": "Accepted friend request from %s!", - "api.success.denyFriend": "Friend Request Denied", - "api.success.denyFriend.desc": "Denied friend request from %s.", - "api.friends": "Friends", - "api.friends.request": "New Friend Request from %s!", - "api.friends.request.accepted": "%s accepted your Friend Request!", - "api.friends.request.declined": "%s declined your Friend Request.", - "api.friends.request.cancelled": "Cancelled friend request to %s!", - "statusUpdateNotifs": "Status Update Notifications", - "api.chats.sidebar": "Chats Sidebar", - "api.friends.chat.input": "Enter Message...", - "api.chats.sidebar.open": "Open Chats Sidebar", - "api.chat.newMessageFrom": "Chat: %s", - "api.chat.enterMessage": "Enter Message", - "api.chat.messageUser": "Message @%s", - "api.chat.messageGroup": "Message %s", - "api.error.requestGeneric": "API Error", "action.copy": "Copy...", "api.privacyNotice": "Privacy Notice", "api.privacyNotice.accept": "Accept", "api.privacyNotice.deny": "Deny/Decide later", "api.privacyNotice.openPolicy": "View Terms", - "api.privacyNotice.description": "The social features of DolphinClient are subject to our terms of service and our privacy policy. \n\nYou are free to accept or deny them, but in case of the latter some features will not be available.\n You can update this decision at any time.", - "api.users.block": "Block User", - "api.users.unblock": "Unblock User", - "api.friends.createGroup": "Create Group", - "viewChats": "View Chats", - "api.chats": "Chats", - "api.chat.dms": "Direct Messages", - "api.chat.groups": "Groups", - "api.chat.groups.create": "Create Group", - "api.chat.groups.create.label": "Enter group member name(s) (Comma-separated list):", - "api.chat.report.message": "Report Message", - "api.chat.report.user": "Report User", - "api.new_version_available": "New Version Available!", - "api.update_notifications": "Update Notifications", - "api.notes": "News", - "api.notes.title": "News", + "api.privacyNotice.description": "DolphinClient's online features - Hypixel stats and image sharing - are subject to our terms of service and our privacy policy. \n\nYou are free to accept or decline them, but in case of the latter those features will not be available.\n If you decline, you will be asked again the next time you start the game.", "configStyle": "Config UI Style", "configStyle.rounded": "Rounded", "configStyle.vanilla": "Vanilla", @@ -522,103 +451,17 @@ "auth.time_left": "Time Left: %s", "auth.working": "Working...", "auth.finished": "Done!", - "api.display_notes": "Display Notes", - "api.pk_token": "PluralKit Token", - "api.pluralkit": "PluralKit Options", - "api.pk_autoproxy": "Autoproxy", - "api.pk_proxymode": "Autoproxy Mode", - "api.pk_autoproxy_member": "Autoproxy Member", "PROXY_FRONT": "Front", "PROXY_OFF": "OFF", "PROXY_MEMBER": "Member", "PROXY_LATCH": "Latch", - "api.status.last_online": "Last online: %s", - "api.account.delete": "Delete Account", - "api.account.delete_account": "Click to delete your account", - "api.account.confirm_deletion": "Confirm account deletion", - "api.account.confirm_deletion.desc": "Your account data will be immediately and irreversibly deleted.\nAfterwards, all online functionality will be disabled.", - "api.account.deletion.success": "Deletion Success", - "api.account.deletion.success.desc": "Successfully deleted your account!", - "api.account.deletion.failure": "Deletion Failure", - "api.account.deletion.failure.desc": "Failed to delete your account!", - "api.account": "Account Management", - "api.account.tooltip": "These are your account settings.
Options in this category are synchronized to the server and across clients.", - "api.account.settings.show_registered": "Show registered", - "api.account.settings.retain_usernames": "Retain previous usernames", - "api.account.settings.show_last_online": "Show last online", - "api.account.settings.show_activity": "Show activity", - "api.account.usernames": "Username History", - "api.account.usernames.public": "Public: %s", - "api.account.usernames.delete": "Delete", - "api.account.usernames.delete.desc": "Deleting entries from your username history is irreversible.\nAre you sure you want to proceed?", - "api.channel.configure": "Configure Channel", "slider.text_input": "Toggle Text/Slider input..", - "api.chat.groups.name": "Channel Name", - "api.chat.persistence.channel": "Channel", - "api.chat.persistence.count": "Count", - "api.chat.persistence.duration": "Duration", - "api.chat.persistence.count_duration": "Count and Duration", - "api.chat.groups.persistence": "Persistence", - "api.chat.groups.persistence.count": "Persistence: Message Count", - "api.chat.groups.persistence.count.tooltip": "The amount of messages to be kept. Messages older than the newest amount will be deleted automatically.", - "api.chat.groups.persistence.duration": "Persistence: Duration", - "api.chat.groups.persistence.duration.tooltip": "The time to keep messages for. Messages older than this duration will be deleted automatically.", - "api.chat.groups.participants": "Participants", - "api.chat.groups.add_participants": "Add Participants", - "api.channel.delete": "Delete Channel", - "api.channel.leave": "Leave Channel", - "api.channel.remove_user": "Remove from channel", - "api.friends.sidebar.open": "Open Friends Sidebar", - "api.status.title.online": "Online", - "api.status.title.offline": "Offline", - "api.status.description.menu": "Browsing the menus...", - "api.status.description.menu.settings": "In the settings...", - "api.status.description.menu.main_menu": "Watching the Main menu panorama...", - "api.status.description.menu.server_list": "Choosing a server to play on", - "api.status.title.in_game": "Playing on a Server", - "api.status.title.in_game_unknown": "Enjoying Minecraft", - "api.friends.activity.update": "New Activity from %s", - "api.channels.invite": "New Channel Invite", - "api.channels.invite.desc": "%s invites you to join %s.", - "api.channels.invite.accepted": "Invite Accepted", - "api.channels.invite.accepted.desc": "%s accepted your invite to %s.", - "api.channels.invite.denied": "Invite Denied", - "api.channels.invite.denied.desc": "%s denied your invite to %s.", - "api.channels.invite.accept": "Accept", - "api.channels.invite.ignore": "Ignore", - "api.channels.invites.enabled": "Allow Channel Invites", - "api.channels.invites": "Channel Invitations", - "api.channels.invite.name": "Name: %s", - "api.channels.invite.from": "From: %s", - "api.channels.invites.view": "View Channel Invitations", - "api.channels.confirm_report": "Confirm Report", - "api.channels.confirm_report.desc": "Are you sure you want to report \"%s\"? Please note that each message is attached to the channel it was sent in, allowing us to gather context after reports.", - "api.channels.delete.confirm": "Confirm Channel Deletion", - "api.channels.delete.confirm.desc": "%s will be irreversibly deleted. Are you sure?", - "api.channels.leave.confirm": "Confirm Leaving channel", - "api.channels.leave.confirm.desc_add": "You will not be able to access %s unless you are added back.", - "api.channels.leave.confirm.desc_invite": "You will not be able to access %s unless you are invited again.", - "api.status.title.world_host": "Playing with WorldHost", - "api.worldhost.joinability.not_published": "World isn't published!", "autoboop.filterlist": "Filter List", "autoboop.filterlist.mode": "Filter Mode", "autoboop.filterlist.mode.disabled": "Disabled", "autoboop.filterlist.mode.whitelist": "Whitelist", "autoboop.filterlist.mode.blacklist": "Blacklist", "text_alpha": "Text Transparency", - "api.status.description.in_game": "%s %s", - "api.status.description.in_game.game_mode": "%s %s: %s", - "api.status.description.in_game.map": "%s %s on %s", - "api.status.description.in_game.game_mode_map": "%s %s: %s on %s", - "api.add_shortcut_buttons": "Add shortcut buttons", - "api.add_shortcut_buttons.tooltip": "Adds buttons to the title screen and the game menu
to access social features more quickly.", - "api.account.export": "Export account data", - "api.account.export_data": "Choose destination file", - "api.account.export.success.title": "Export successful", - "api.account.export.success.description": "Exported account data to %s", - "api.account.export.failure.title": "Export failed", - "api.account.export.failure.description_write": "Failed to write file, you may find more information in the log file!", - "api.account.export.failure.description_server": "Failed to request data from the server, please contact DolphinClient with a log file to get support!", "feature.broken": "This feature is currently not working. If you know how to fix it please let us know!", "inventory.potion_effect_offset": "Inventory Potion Offset", "gallery.title": "Gallery", @@ -639,9 +482,6 @@ "gallery.image.open.external.browser": "Open in browser", "gallery.image.copy_url": "Copy image url", "gallery.title.local": "Local screenshots", - "gallery.title.shared": "Shared by your friends", - "gallery.tab.local": "View local images", - "gallery.tab.shared": "View shared images", "gallery.download_external": "View image from url", "gallery.image.view": "View image", "gallery.image.loading.title": "Loading image...", @@ -654,8 +494,6 @@ "key.freelook.alt": "Freelook (Alternative)", "perspective.alt": "Perspective (Alternative Key)", "toggle.alt": "Toggle (Alternative Key)", - "api.account.settings.allow_friends_image_access": "Allow Friends Image Access", - "api.account.settings.allow_friends_image_access.tooltip": "Allow friends to access images you upload directly in their gallery screen.
Disabling this does NOT stop images from being available via their url or id,
it only prevents friends from seeing your images in the \"Shared by friends\" gallery tab.", "show_biome": "Show Biome", "coordshud.biome": "Biome", "keystrokes": "Keystrokes", @@ -700,8 +538,8 @@ "debugcounters.pcount": "Particle Debug Info", "bedwars_resources.renderWhenRelevant": "Only Render When Relevant", "bedwars_resources": "Resources HUD", - "levelhead.enabled.tooltip": "Whether this module is enabled or not.
This module requires the social features to be enabled
in order to communicate with the backend and the Hypixel API.", - "bedwars.enabled.tooltip": "Whether this module is enabled or not.
Some functions of this module require the social features to be enabled
in order to communicate with the backend and the Hypixel API.", + "levelhead.enabled.tooltip": "Whether this module is enabled or not.
This module requires DolphinClient's online features
in order to communicate with the backend and the Hypixel API.", + "bedwars.enabled.tooltip": "Whether this module is enabled or not.
Some functions of this module require DolphinClient's online features
in order to communicate with the backend and the Hypixel API.", "daycounterhud": "Day Counter", "daycounterhud.days": "%s Days", "keystrokes.animation_time": "Animation Time (Milliseconds)", @@ -718,9 +556,8 @@ "playerstats.bedwars.beds": "» Beds Broken/Lost Ratio: %s/%s (%s)", "playerstats.bedwars.summary": "» Wins %s - Losses %s (%s) | Winstreak: %s", "playerstats.bedwars.summary_short": "» Win Loss Ratio: %s/%s (%s)", - "playerstats.error.api_disabled": "API is not enabled!", + "playerstats.error.api_disabled": "Online features are not enabled!", "playerstats.error.unknown_player": "Unknown Player!", - "api.error.unauthenticated": "API is not authenticated!", "playerstats.error.query_failed": "Failed to fetch stats!", "playerstats.error.failed_data": "Failed to retrieve player data!", "playerstats.bedwars.solo": "Solo", @@ -766,24 +603,11 @@ "armorhud.main_hand_item_position.disabled": "Disabled", "tablist.small_ping_text": "Small Ping Text", "tablist.small_ping_text.tooltip": "Use Unicode subscript number characters instead of regular numbers", - "api.status.title.e4mc": "Playing with e4mc", - "api.servers": "Servers", - "api.servers.friends": "Friends [%s]", - "api.servers.friends.title": "Online Friends", - "api.allow_friends_server_join": "Allow Friends to join servers", - "api.allow_friends_server_join.tooltip": "This will expose your current server's address.
Includes worlds shared using e4mc & WorldHost!", - "api.status.title.unknown_server": "Playing on a Server", - "api.servers.friends.no_online_friends": "No online friends!", - "api.worldhost.joinability.not_joinable": "Player does not allow joining!", "menu_button_mode.disabled": "Disabled", "menu_button_mode.modmenu": "Only without ModMenu", "menu_button_mode.always": "Always", "title_screen_button_mode": "Title Screen Options Button Mode", "game_menu_screen_button_mode": "Game Menu Screen Options Button Mode", - "privacy_policy_state.accepted": "Accepted", - "privacy_policy_state.denied": "Denied", - "privacy_policy_state.unset": "Unset", - "api.privacy_policy_accepted": "Terms Acceptance", "hud.entry.name": "HUD Entry: %s", "hud.entry.usage": "Press Space to enter movement mode. Press Enter to open this module's options.", "hud.entry.usage.move": "Press Escape or Space to exit movement mode, use arrow keys to move this HUD. Press Enter to open this module's options.", @@ -805,7 +629,6 @@ "profiles.profile.duplicate": "Duplicate", "profiles.profile.current": "Current!", "profiles.profile.add": "Add empty Profile", - "api.send_status_updates": "Send Status Updates", "bedwars.stats_overlay.header.player": "Player", "bedwars.stats_overlay.header.fkdr": "FKDR", "bedwars.stats_overlay.header.kdr": "KDR", @@ -884,7 +707,6 @@ "hud.background_padding": "Background Padding", "hide_chat": "Hide Chat", "toggle_hide_chat": "Toggle Hide Chat", - "api.context_menu": "Context Menu", "simple_text_hud.order": "Content Order", "simple_text_hud.separator": "Separator", "simple_text_hud.order.value_description": "Value - Description", @@ -985,14 +807,9 @@ "gallery.image.crop.inputs.height": "Crop Area Height", "hud_link_creation_enabled": "Allow Creating HUD Links", "screenshot_utils.screenshot_and_crop": "Take Screenshot Crop", - "api.badge_options": "Badge Options", "coordshud.show_nether_conversions": "Show Nether Conversions", "coordshud.dimension.nether": "Nether", "coordshud.dimension.overworld": "Overworld", - "tab_badge_mode": "Tab Display Mode", - "tab_badge_mode.before_name": "Before Name", - "tab_badge_mode.before_name_aligned": "Before Name (Aligned)", - "tab_badge_mode.before_ping": "Before Ping", "tab_overlay_hud": "Tab List", "subtitleshud": "Subtitles", "subtitles.entry_background": "Entry Background", diff --git a/common/src/main/resources/assets/dolphinclient/lang/fr_fr.json b/common/src/main/resources/assets/dolphinclient/lang/fr_fr.json index 414e418ef..35b52ff18 100644 --- a/common/src/main/resources/assets/dolphinclient/lang/fr_fr.json +++ b/common/src/main/resources/assets/dolphinclient/lang/fr_fr.json @@ -101,7 +101,6 @@ "back": "Revenir", "background": "Arrière-Plan", "backgroundcolor": "Couleur d'Arrière-Plan (Principal)", - "badgeText": "Texte de Badge Personnalisé", "ban_reason": "Cette option est désactivée
car elle est connue pour être bannable sur votre serveur actuel.", "bar": "Barre", "barColor": "Couleur de la Barre", @@ -171,7 +170,6 @@ "creditsBGM": "Musique", "crosshair_type": "Type", "crosshairhud": "Crosshair HUD", - "customBadge": "Badges Personnalisés", "customCloudHeight": "Hauteur des Nuages Custom", "customCloudHeight.tooltip": "Personnaliser la hauteur des nuages.", "customColor": "Couleur personnalisée", @@ -365,8 +363,6 @@ "showActivity": "Afficher votre Activité", "showActivity.tooltip": "Afficher votre Activité dans le Jeu.", "showAllocated": "Afficher la RAM allouée", - "showBadges": "Afficher les Badges", - "showBadges.tooltip": "Indique si les Badges des Pseudos et de la Tablist sont activés.
Nécessite l'activation des Fonctionnalités Sociales.", "showBeaconBeams": "Afficher les Rayons de Balises", "showD": "Afficher D", "showEffectNames": "Afficher le nom des Effets", @@ -439,80 +435,13 @@ "zoomScrolling.tooltip": "Modifier le niveau du Zoom avec la Roulette de Souris", "removeLobbyJoinMessages": "Retirer les Messages d'Entrée dans le Lobby", "zoomSpeed": "Vitesse d'Animation", - "api.error.userNotFound": "L'utilisateur spécifié est introuvable.", - "api.error.friendRequestNotFound": "Vous n'avez pas de demande d'ami de cet utilisateur.", - "api.error.userBlocked": "Vous avez bloqué cet utilisateur.", - "api.error.userAlreadyBlocked": "Vous avez déjà bloqué cet utilisateur.", - "api.error.userAlreadyFriends": "Vous êtes déjà ami avec cet utilisateur.", - "api.screen.friends": "Amis", - "api.friends.pending.incoming": "Invitation", - "api.friends.pending.outgoing": "Invitation sortante", - "api.friends.tab.online": "En ligne", - "api.friends.tab.all": "Tous", - "api.friends.tab.pending": "En attente", - "api.friends.tab.blocked": "Bloqué", - "api.friends.add": "Ajouter un ami...", - "api.friends.remove": "Retirer", - "api.friends.request.deny": "Refuser", - "api.friends.request.accept": "Accepter", - "api.friends.chat": "Ouvrir le tchat...", - "api.category": "Options Sociales", - "friendRequestsEnabled": "Autoriser les demandes d'amis", - "detailedLogging": "Journalisation détaillée", "detailedLogging.tooltip": "Au cas où vous auriez besoin de journaux encore plus détaillés.", - "viewFriends": "Voir les amis", - "api.screen.friends.add": "Ajouter un ami", - "api.screen.friends.add.name": "Entrer un nom ou UUID :", - "api.success.request_sent": "Demande d'ami envoyée", - "api.success.request_sent.desc": "Demande d'ami envoyée à %s !", - "api.failure.request_sent": "Échec de l'envoi de la demande d'ami", - "api.failure.request_sent.not_found": "Utilisateur %s introuvable !", - "api.failure.request_sent.forbidden": "Vous n'êtes pas autorisé à envoyer une demande d'ami à %s !", - "api.success.removeFriend": "Ami retiré", - "api.success.removeFriend.desc": "%s retiré de la liste d'amis !", - "api.success.blockUser": "Utilisateur bloqué", - "api.success.blockUser.desc": "%s ajouté aux utilisateurs bloqués.", - "api.success.unblockUser": "Utilisateur débloqué", - "api.success.unblockUser.desc": "%s débloqué !", - "api.success.acceptFriend": "Demande d'ami acceptée", - "api.success.acceptFriend.desc": "Demande d'ami de %s acceptée !", - "api.success.denyFriend": "Demande d'ami refusée", - "api.success.denyFriend.desc": "Demande d'ami de %s refusée.", - "api.friends": "Amis", - "api.friends.request": "Nouvelle demande d'ami de %s !", - "api.friends.request.accepted": "%s a accepté votre demande d'ami !", - "api.friends.request.declined": "%s a refusé votre demande d'ami.", - "api.friends.request.cancelled": "Demande d'ami à %s annulée !", - "statusUpdateNotifs": "Notifications de mise à jour de statut", - "api.chats.sidebar": "Barre latérale des tchats", - "api.friends.chat.input": "Entrer un message...", - "api.chats.sidebar.open": "Ouvrir la barre latérale des tchats", - "api.chat.newMessageFrom": "Tchat : %s", - "api.chat.enterMessage": "Entrer un message", - "api.chat.messageUser": "Écrire à @%s", - "api.chat.messageGroup": "Écrire à %s", - "api.error.requestGeneric": "Erreur d'API", "action.copy": "Copier...", "api.privacyNotice": "Avis de confidentialité", "api.privacyNotice.accept": "Accepter", "api.privacyNotice.deny": "Refuser/Décider plus tard", "api.privacyNotice.openPolicy": "Voir les conditions", - "api.privacyNotice.description": "Les fonctionnalités sociales de DolphinClient sont soumises à nos conditions générales et à notre politique de confidentialité. \n\nVous êtes libre de les accepter ou de les refuser, mais dans ce dernier cas, certaines fonctionnalités ne seront pas disponibles.\n Vous pouvez mettre à jour cette décision à tout moment.", - "api.users.block": "Bloquer l'utilisateur", - "api.users.unblock": "Débloquer l'utilisateur", - "api.friends.createGroup": "Créer un groupe", - "viewChats": "Voir les tchats", - "api.chats": "Tchats", - "api.chat.dms": "Messages privés", - "api.chat.groups": "Groupes", - "api.chat.groups.create": "Créer un groupe", - "api.chat.groups.create.label": "Saisissez le(s) nom(s) des membres du groupe (liste séparée par des virgules) :", - "api.chat.report.message": "Signaler un message", - "api.chat.report.user": "Signaler un utilisateur", - "api.new_version_available": "Nouvelle version disponible !", - "api.update_notifications": "Notifications de mise à jour", - "api.notes": "Actualités", - "api.notes.title": "Actualités", + "api.privacyNotice.description": "Les fonctionnalités en ligne de DolphinClient - les statistiques Hypixel et le partage d'images - sont soumises à nos conditions générales et à notre politique de confidentialité. \n\nVous êtes libre de les accepter ou de les refuser, mais dans ce dernier cas, ces fonctionnalités ne seront pas disponibles.\n Si vous refusez, la question vous sera posée à nouveau au prochain démarrage du jeu.", "configStyle": "Configurer le style d'interface utilisateur", "configStyle.rounded": "Arrondi", "configStyle.vanilla": "Vanille", @@ -522,103 +451,17 @@ "auth.time_left": "Temps restant : %s", "auth.working": "En cours...", "auth.finished": "Terminé !", - "api.display_notes": "Afficher les notes", - "api.pk_token": "Token PluralKit", - "api.pluralkit": "Options PluralKit", - "api.pk_autoproxy": "Autoproxy", - "api.pk_proxymode": "Mode Autoproxy", - "api.pk_autoproxy_member": "Membre Autoproxy", "PROXY_FRONT": "Front", "PROXY_OFF": "Désactivé", "PROXY_MEMBER": "Membre", "PROXY_LATCH": "Latch", - "api.status.last_online": "Dernière connexion : %s", - "api.account.delete": "Supprimer le compte", - "api.account.delete_account": "Cliquez pour supprimer votre compte", - "api.account.confirm_deletion": "Confirmer la suppression du compte", - "api.account.confirm_deletion.desc": "Les données de votre compte seront immédiatement et irréversiblement supprimées.\nEnsuite, toutes les fonctionnalités en ligne seront désactivées.", - "api.account.deletion.success": "Suppression réussie", - "api.account.deletion.success.desc": "Votre compte a été supprimé avec succès !", - "api.account.deletion.failure": "Échec de la suppression", - "api.account.deletion.failure.desc": "Échec de la suppression de votre compte !", - "api.account": "Gestion de compte", - "api.account.tooltip": "Ce sont vos paramètres de compte.
Les options de cette catégorie sont synchronisées avec le serveur et entre les clients.", - "api.account.settings.show_registered": "Afficher enregistrés", - "api.account.settings.retain_usernames": "Conserver les anciens noms d'utilisateur", - "api.account.settings.show_last_online": "Afficher la dernière connexion", - "api.account.settings.show_activity": "Afficher l'activité", - "api.account.usernames": "Historique des noms d'utilisateur", - "api.account.usernames.public": "Public : %s", - "api.account.usernames.delete": "Supprimer", - "api.account.usernames.delete.desc": "La suppression des entrées de l'historique des noms d'utilisateur est irréversible.\nÊtes-vous sûr de vouloir continuer ?", - "api.channel.configure": "Configurer le salon", "slider.text_input": "Basculer entre saisie texte/curseur...", - "api.chat.groups.name": "Nom du salon", - "api.chat.persistence.channel": "Salon", - "api.chat.persistence.count": "Nombre", - "api.chat.persistence.duration": "Durée", - "api.chat.persistence.count_duration": "Nombre et durée", - "api.chat.groups.persistence": "Persistance", - "api.chat.groups.persistence.count": "Persistance : Nombre de messages", - "api.chat.groups.persistence.count.tooltip": "Nombre de messages à conserver. Les messages plus anciens que ce nombre seront automatiquement supprimés.", - "api.chat.groups.persistence.duration": "Persistance : Durée", - "api.chat.groups.persistence.duration.tooltip": "Durée de conservation des messages. Les messages plus anciens que cette durée seront automatiquement supprimés.", - "api.chat.groups.participants": "Participants", - "api.chat.groups.add_participants": "Ajouter des participants", - "api.channel.delete": "Supprimer le salon", - "api.channel.leave": "Quitter le salon", - "api.channel.remove_user": "Retirer du salon", - "api.friends.sidebar.open": "Ouvrir la barre latérale des amis", - "api.status.title.online": "En ligne", - "api.status.title.offline": "Hors ligne", - "api.status.description.menu": "Navigation dans les menus...", - "api.status.description.menu.settings": "Dans les paramètres...", - "api.status.description.menu.main_menu": "Regarde le panorama du menu principal...", - "api.status.description.menu.server_list": "Choisir un serveur pour jouer", - "api.status.title.in_game": "Joue sur un Serveur", - "api.status.title.in_game_unknown": "S'amuse sur Minecraft", - "api.friends.activity.update": "Nouvelle activité de %s", - "api.channels.invite": "Nouvelle invitation de salon", - "api.channels.invite.desc": "%s vous invite à rejoindre %s.", - "api.channels.invite.accepted": "Invitation acceptée", - "api.channels.invite.accepted.desc": "%s a accepté votre invitation à %s.", - "api.channels.invite.denied": "Invitation refusée", - "api.channels.invite.denied.desc": "%s a refusé votre invitation à %s.", - "api.channels.invite.accept": "Accepter", - "api.channels.invite.ignore": "Ignorer", - "api.channels.invites.enabled": "Autoriser les invitations de salon", - "api.channels.invites": "Invitations de salon", - "api.channels.invite.name": "Nom : %s", - "api.channels.invite.from": "De : %s", - "api.channels.invites.view": "Voir les invitations de salon", - "api.channels.confirm_report": "Confirmer le signalement", - "api.channels.confirm_report.desc": "Êtes-vous sûr de vouloir signaler \"%s\" ? Notez que chaque message est attaché au salon où il a été envoyé, ce qui nous permet de recueillir du contexte après les signalements.", - "api.channels.delete.confirm": "Confirmer la suppression du salon", - "api.channels.delete.confirm.desc": "%s sera irréversiblement supprimé. Êtes-vous sûr ?", - "api.channels.leave.confirm": "Confirmer la sortie du salon", - "api.channels.leave.confirm.desc_add": "Vous ne pourrez pas accéder à %s à moins d'y être ajouté à nouveau.", - "api.channels.leave.confirm.desc_invite": "Vous ne pourrez pas accéder à %s à moins d'y être invité à nouveau.", - "api.status.title.world_host": "Joue avec WorldHost", - "api.worldhost.joinability.not_published": "Le monde n'est pas publié !", "autoboop.filterlist": "Liste de filtres", "autoboop.filterlist.mode": "Mode de filtre", "autoboop.filterlist.mode.disabled": "Désactivé", "autoboop.filterlist.mode.whitelist": "Liste blanche", "autoboop.filterlist.mode.blacklist": "Liste noire", "text_alpha": "Transparence du texte", - "api.status.description.in_game": "%s %s", - "api.status.description.in_game.game_mode": "%s %s: %s", - "api.status.description.in_game.map": "%s %s sur %s", - "api.status.description.in_game.game_mode_map": "%s %s: %s sur %s", - "api.add_shortcut_buttons": "Ajouter des boutons raccourcis", - "api.add_shortcut_buttons.tooltip": "Ajoute des boutons à l'écran titre et au menu du jeu
pour accéder plus rapidement aux fonctionnalités sociales.", - "api.account.export": "Exporter les données du compte", - "api.account.export_data": "Choisir le fichier de destination", - "api.account.export.success.title": "Exportation réussie", - "api.account.export.success.description": "Données de compte exportées vers %s", - "api.account.export.failure.title": "Échec de l'exportation", - "api.account.export.failure.description_write": "Impossible d'écrire le fichier, vous pouvez trouver plus d'informations dans le fichier journal !", - "api.account.export.failure.description_server": "Échec de la demande de données au serveur, veuillez contacter DolphinClient avec un fichier journal pour obtenir de l'aide !", "feature.broken": "Cette fonctionnalité ne fonctionne pas actuellement. Si vous savez comment la corriger, merci de nous le faire savoir !", "inventory.potion_effect_offset": "Décalage des Potions dans l'Inventaire", "gallery.title": "Galerie", @@ -639,9 +482,6 @@ "gallery.image.open.external.browser": "Ouvrir dans le navigateur", "gallery.image.copy_url": "Copier l'url de l'image", "gallery.title.local": "Captures d'écran locales", - "gallery.title.shared": "Partagé par vos amis", - "gallery.tab.local": "Voir les images locales", - "gallery.tab.shared": "Voir les images partagées", "gallery.download_external": "Afficher l'image à partir de l'url", "gallery.image.view": "Afficher l'image", "gallery.image.loading.title": "Chargement de l'image...", @@ -654,8 +494,6 @@ "key.freelook.alt": "Freelook (Alternative)", "perspective.alt": "Perspective (Touche Alternative)", "toggle.alt": "Basculer (Touche Alternative)", - "api.account.settings.allow_friends_image_access": "Autoriser l'accès des amis à l'image", - "api.account.settings.allow_friends_image_access.tooltip": "Autoriser vos amis à accéder à vos images mises en ligne dans l'onglet galerie directement.
La désactivation de cette option n'empêche PAS les images d'être disponibles via leur adresse URL ou leur identifiant,
elle empêche seulement les amis de voir vos images dans l'onglet « Partagé par vos amis » de la galerie.", "show_biome": "Afficher le Biome", "coordshud.biome": "Biome", "keystrokes": "Keystrokes", @@ -699,8 +537,8 @@ "debugcounters.pcount": "Infos de débogage des particules", "bedwars_resources.renderWhenRelevant": "Afficher uniquement lorsque pertinent", "bedwars_resources": "HUD des ressources", - "levelhead.enabled.tooltip": "Indique si ce module est activé ou non.
Ce module nécessite l'activation des fonctionnalités sociales
pour communiquer avec le backend et l'API Hypixel.", - "bedwars.enabled.tooltip": "Indique si ce module est activé ou non.
Certaines fonctionnalités de ce module nécessitent l'activation des fonctionnalités sociales
pour communiquer avec le backend et l'API Hypixel.", + "levelhead.enabled.tooltip": "Indique si ce module est activé ou non.
Ce module nécessite les fonctionnalités en ligne de DolphinClient
pour communiquer avec le backend et l'API Hypixel.", + "bedwars.enabled.tooltip": "Indique si ce module est activé ou non.
Certaines fonctionnalités de ce module nécessitent les fonctionnalités en ligne de DolphinClient
pour communiquer avec le backend et l'API Hypixel.", "daycounterhud": "HUD du compteur de jours", "daycounterhud.days": "%s Jours", "keystrokes.animation_time": "Temps d'animation (millisecondes)", @@ -719,7 +557,6 @@ "playerstats.bedwars.summary_short": "» Ratio Victoires/Défaites : %s/%s (%s)", "playerstats.error.api_disabled": "L'API n'est pas activée !", "playerstats.error.unknown_player": "Joueur inconnu !", - "api.error.unauthenticated": "L'API n'est pas authentifiée !", "playerstats.error.query_failed": "Échec de la récupération des statistiques!", "playerstats.error.failed_data": "Échec de la récupération des données du joueur !", "playerstats.bedwars.solo": "Solo", @@ -765,24 +602,11 @@ "armorhud.main_hand_item_position.disabled": "Désactivé", "tablist.small_ping_text": "Texte de ping réduit", "tablist.small_ping_text.tooltip": "Utiliser les caractères Unicode en indice au lieu des chiffres normaux", - "api.status.title.e4mc": "Joue avec e4mc", - "api.servers": "Serveurs", - "api.servers.friends": "Amis [%s]", - "api.servers.friends.title": "Amis en ligne", - "api.allow_friends_server_join": "Autoriser les amis à rejoindre les serveurs", - "api.allow_friends_server_join.tooltip": "Cela exposera l'adresse de votre serveur actuel.
Inclut les mondes partagés via e4mc & WorldHost !", - "api.status.title.unknown_server": "Joue sur un serveur", - "api.servers.friends.no_online_friends": "Aucun ami en ligne !", - "api.worldhost.joinability.not_joinable": "Le joueur n'autorise pas les connexions !", "menu_button_mode.disabled": "Désactivé", "menu_button_mode.modmenu": "Seulement sans ModMenu", "menu_button_mode.always": "Toujours", "title_screen_button_mode": "Mode du bouton Options sur l'écran titre", "game_menu_screen_button_mode": "Mode du bouton Options dans le menu du jeu", - "privacy_policy_state.accepted": "Accepté", - "privacy_policy_state.denied": "Refusé", - "privacy_policy_state.unset": "Non défini", - "api.privacy_policy_accepted": "Acceptation des conditions", "hud.entry.name": "Élément HUD : %s", "hud.entry.usage": "Appuyez sur Espace pour activer le mode déplacement. Appuyez sur Entrée pour ouvrir les options de ce module.", "hud.entry.usage.move": "Appuyez sur Échap ou Espace pour quitter le mode déplacement, utilisez les flèches pour déplacer cet élément HUD. Appuyez sur Entrée pour ouvrir les options de ce module.", @@ -804,7 +628,6 @@ "profiles.profile.duplicate": "Dupliquer", "profiles.profile.current": "Actuel !", "profiles.profile.add": "Ajouter un profil vide", - "api.send_status_updates": "Envoyer les mises à jour de statut", "bedwars.stats_overlay.header.player": "Joueur", "bedwars.stats_overlay.header.fkdr": "FKDR", "bedwars.stats_overlay.header.kdr": "KDR", @@ -883,7 +706,6 @@ "hud.background_padding": "Marge intérieure de l'arrière-plan", "hide_chat": "Masquer le tchat", "toggle_hide_chat": "Basculer la visibilité du tchat", - "api.context_menu": "Menu contextuel", "simple_text_hud.order": "Ordre du contents", "simple_text_hud.separator": "Séparateur", "simple_text_hud.order.value_description": "Valeur - Description", @@ -984,14 +806,9 @@ "gallery.image.crop.inputs.height": "Hauteur de la zone de rognage", "hud_link_creation_enabled": "Autoriser la création de liens HUD", "screenshot_utils.screenshot_and_crop": "Prendre une capture et rogner", - "api.badge_options": "Options des badges", "coordshud.show_nether_conversions": "Afficher les conversions du Nether", "coordshud.dimension.nether": "Nether", "coordshud.dimension.overworld": "Monde normal", - "tab_badge_mode": "Mode d'affichage en Tab", - "tab_badge_mode.before_name": "Avant le nom", - "tab_badge_mode.before_name_aligned": "Avant le nom (aligné)", - "tab_badge_mode.before_ping": "Avant le ping", "tab_overlay_hud": "HUD de superposition Tab", "subtitleshud": "HUD des sous-titres", "subtitles.entry_background": "Arrière-plan des entrées", diff --git a/common/src/main/resources/assets/dolphinclient/lang/pt_pt.json b/common/src/main/resources/assets/dolphinclient/lang/pt_pt.json index 11966ab76..37bae6ab6 100644 --- a/common/src/main/resources/assets/dolphinclient/lang/pt_pt.json +++ b/common/src/main/resources/assets/dolphinclient/lang/pt_pt.json @@ -101,7 +101,6 @@ "back": "Back", "background": "Background", "backgroundcolor": "Backgroundcolor (Main)", - "badgeText": "Custom Badge Text", "ban_reason": "This option is disabled
because it is known to be bannable on your current server.", "bar": "Bar", "barColor": "Bar Color", @@ -171,7 +170,6 @@ "creditsBGM": "BGM", "crosshair_type": "Type", "crosshairhud": "Crosshair HUD", - "customBadge": "Custom Badges", "customCloudHeight": "Custom Cloud Height", "customCloudHeight.tooltip": "Whether to use a custom cloud height.", "customColor": "Custom Color", @@ -353,8 +351,6 @@ "showActivity": "Show Activity", "showActivity.tooltip": "Whether to show your current In-Game Activity.", "showAllocated": "Show Allocated", - "showBadges": "Show Badges", - "showBadges.tooltip": "Whether Nametag and Tablist Badges are enabled.
Requires Social Features to be enabled.", "showBeaconBeams": "Show Beacon Beams", "showD": "Show D", "showEffectNames": "Show Effect Names", @@ -427,80 +423,13 @@ "zoomScrolling.tooltip": "Whether to allow scrolling to modify the zoom factor.", "removeLobbyJoinMessages": "Remove Lobby Join Messages", "zoomSpeed": "Animation Speed", - "api.error.userNotFound": "The specified user could not be found.", - "api.error.friendRequestNotFound": "Não tem um pedido de amizade deste utilizador.", - "api.error.userBlocked": "Bloqueou este utilizador.", - "api.error.userAlreadyBlocked": "Já bloqueou este utilizador.", - "api.error.userAlreadyFriends": "Já é amigo com este utilizador.", - "api.screen.friends": "Amigos", - "api.friends.pending.incoming": "Incoming Request", - "api.friends.pending.outgoing": "Outgoing Request", - "api.friends.tab.online": "Online", - "api.friends.tab.all": "All", - "api.friends.tab.pending": "Pending", - "api.friends.tab.blocked": "Blocked", - "api.friends.add": "Adicionar Amigo...", - "api.friends.remove": "Excluir", - "api.friends.request.deny": "Rejeitar", - "api.friends.request.accept": "Aceitar", - "api.friends.chat": "Abrir Chat...", - "api.category": "Social Options", - "friendRequestsEnabled": "Allow Friend Requests", - "detailedLogging": "Detailed Logging", "detailedLogging.tooltip": "In case you need even more verbose logs.", - "viewFriends": "View Friends", - "api.screen.friends.add": "Adicionar Amigo", - "api.screen.friends.add.name": "Enter Name or UUID:", - "api.success.request_sent": "Pedido de amizade enviado", - "api.success.request_sent.desc": "Enviou um pedido de amizade a %s!", - "api.failure.request_sent": "Failed to send friend request", - "api.failure.request_sent.not_found": "User %s couldn't be found!", - "api.failure.request_sent.forbidden": "You aren't allowed to send a friend request to %s!", - "api.success.removeFriend": "Amigo removido", - "api.success.removeFriend.desc": "Removeu %s da lista de amigos!", - "api.success.blockUser": "Utilizador Bloqueado", - "api.success.blockUser.desc": "Adicionou %s aos Utilizadores bloqueados.", - "api.success.unblockUser": "Utilizador Desbloqueado", - "api.success.unblockUser.desc": "Desbloqueou %s!", - "api.success.acceptFriend": "Friend Request Accepted", - "api.success.acceptFriend.desc": "Accepted friend request from %s!", - "api.success.denyFriend": "Pedido de Amizade Rejeitado", - "api.success.denyFriend.desc": "Rejeitou um pedido de amizade de %s.", - "api.friends": "Amigos", - "api.friends.request": "Novo Pedido de Amizade de %s!", - "api.friends.request.accepted": "%s aceitou o seu Pedido de Amizade!", - "api.friends.request.declined": "%s rejeitou o seu Pedido de Amizade.", - "api.friends.request.cancelled": "Cancelou o seu pedido de amizade para %s!", - "statusUpdateNotifs": "Status Update Notifications", - "api.chats.sidebar": "Chats Sidebar", - "api.friends.chat.input": "Enter Message...", - "api.chats.sidebar.open": "Open Chats Sidebar", - "api.chat.newMessageFrom": "Chat: %s", - "api.chat.enterMessage": "Enter Message", - "api.chat.messageUser": "Message @%s", - "api.chat.messageGroup": "Message %s", - "api.error.requestGeneric": "Erro de API", "action.copy": "Copiar...", "api.privacyNotice": "Privacy Notice", "api.privacyNotice.accept": "Aceitar", "api.privacyNotice.deny": "Rejeitar/Decidir depois", "api.privacyNotice.openPolicy": "Ver Termos", - "api.privacyNotice.description": "As funcionalidades sociais do DolphinClient estão sujeitas aos nossos termos de utilizador e a nossa política de privacidade. \n\nVocê pode aceitar ou rejeitar esses termos, mas no último caso, algumas funcionalidades não vão estar disponíveis.\n Poderá mudar a sua decisão a qualquer tempo.", - "api.users.block": "Bloquear Utilizador", - "api.users.unblock": "Desbloquear Utilizador", - "api.friends.createGroup": "Criar Grupo", - "viewChats": "View Chats", - "api.chats": "Chats", - "api.chat.dms": "Direct Messages", - "api.chat.groups": "Grupos", - "api.chat.groups.create": "Criar Grupo", - "api.chat.groups.create.label": "Enter group member name(s) (Comma-separated list):", - "api.chat.report.message": "Reportar Mensagem", - "api.chat.report.user": "Reportar Utilizador", - "api.new_version_available": "Nova Versão Disponível!", - "api.update_notifications": "Notificações de Atualizações", - "api.notes": "Notícias", - "api.notes.title": "Notícias", + "api.privacyNotice.description": "As funcionalidades online do DolphinClient - estatísticas Hypixel e partilha de imagens - estão sujeitas aos nossos termos de utilizador e a nossa política de privacidade. \n\nVocê pode aceitar ou rejeitar esses termos, mas no último caso, essas funcionalidades não vão estar disponíveis.\n Se rejeitar, a pergunta será feita novamente no próximo arranque do jogo.", "configStyle": "Config UI Style", "configStyle.rounded": "Rounded", "configStyle.vanilla": "Vanilla", @@ -510,103 +439,17 @@ "auth.time_left": "Tempo Restante: %s", "auth.working": "Working...", "auth.finished": "Done!", - "api.display_notes": "Display Notes", - "api.pk_token": "PluralKit Token", - "api.pluralkit": "PluralKit Options", - "api.pk_autoproxy": "Autoproxy", - "api.pk_proxymode": "Autoproxy Mode", - "api.pk_autoproxy_member": "Autoproxy Member", "PROXY_FRONT": "Front", "PROXY_OFF": "OFF", "PROXY_MEMBER": "Member", "PROXY_LATCH": "Latch", - "api.status.last_online": "Last online: %s", - "api.account.delete": "Excluir Conta", - "api.account.delete_account": "Clique para excluir a sua conta", - "api.account.confirm_deletion": "Confirmar exclusão de conta", - "api.account.confirm_deletion.desc": "Your account data will be immediately and irreversibly deleted.\nAfterwards, all online functionality will be disabled.", - "api.account.deletion.success": "Succeso de Exclução", - "api.account.deletion.success.desc": "Successfully deleted your account!", - "api.account.deletion.failure": "Falhanço de Exclução", - "api.account.deletion.failure.desc": "Failed to delete your account!", - "api.account": "Account Management", - "api.account.tooltip": "These are your account settings.
Options in this category are synchronized to the server and across clients.", - "api.account.settings.show_registered": "Show registered", - "api.account.settings.retain_usernames": "Retain previous usernames", - "api.account.settings.show_last_online": "Show last online", - "api.account.settings.show_activity": "Mostrar atividade", - "api.account.usernames": "Histórico de nomes de Utilizador", - "api.account.usernames.public": "Publico: %s", - "api.account.usernames.delete": "Excluir", - "api.account.usernames.delete.desc": "Deleting entries from your username history is irreversible.\nAre you sure you want to proceed?", - "api.channel.configure": "Configurar Canal", "slider.text_input": "Toggle Text/Slider input..", - "api.chat.groups.name": "Nome de Canal", - "api.chat.persistence.channel": "Channel", - "api.chat.persistence.count": "Count", - "api.chat.persistence.duration": "Duração", - "api.chat.persistence.count_duration": "Count and Duration", - "api.chat.groups.persistence": "Persistence", - "api.chat.groups.persistence.count": "Persistence: Message Count", - "api.chat.groups.persistence.count.tooltip": "The amount of messages to be kept. Messages older than the newest amount will be deleted automatically.", - "api.chat.groups.persistence.duration": "Persistence: Duration", - "api.chat.groups.persistence.duration.tooltip": "The time to keep messages for. Messages older than this duration will be deleted automatically.", - "api.chat.groups.participants": "Participants", - "api.chat.groups.add_participants": "Add Participants", - "api.channel.delete": "Excluir Canal", - "api.channel.leave": "Sair de Canal", - "api.channel.remove_user": "Remove from channel", - "api.friends.sidebar.open": "Open Friends Sidebar", - "api.status.title.online": "Online", - "api.status.title.offline": "Offline", - "api.status.description.menu": "Browsing the menus...", - "api.status.description.menu.settings": "In the settings...", - "api.status.description.menu.main_menu": "Watching the Main menu panorama...", - "api.status.description.menu.server_list": "Choosing a server to play on", - "api.status.title.in_game": "Playing on a Server", - "api.status.title.in_game_unknown": "Desfrutando do Minecraft", - "api.friends.activity.update": "New Activity from %s", - "api.channels.invite": "New Channel Invite", - "api.channels.invite.desc": "%s invites you to join %s.", - "api.channels.invite.accepted": "Convite Aceito", - "api.channels.invite.accepted.desc": "%s accepted your invite to %s.", - "api.channels.invite.denied": "Convie Rejeitado", - "api.channels.invite.denied.desc": "%s denied your invite to %s.", - "api.channels.invite.accept": "Aceitar", - "api.channels.invite.ignore": "Ignorar", - "api.channels.invites.enabled": "Allow Channel Invites", - "api.channels.invites": "Channel Invitations", - "api.channels.invite.name": "Nome: %s", - "api.channels.invite.from": "De: %s", - "api.channels.invites.view": "View Channel Invitations", - "api.channels.confirm_report": "Confirm Report", - "api.channels.confirm_report.desc": "Are you sure you want to report \"%s\"? Please note that each message is attached to the channel it was sent in, allowing us to gather context after reports.", - "api.channels.delete.confirm": "Confirm Channel Deletion", - "api.channels.delete.confirm.desc": "%s will be irreversibly deleted. Are you sure?", - "api.channels.leave.confirm": "Confirm Leaving channel", - "api.channels.leave.confirm.desc_add": "You will not be able to access %s unless you are added back.", - "api.channels.leave.confirm.desc_invite": "You will not be able to access %s unless you are invited again.", - "api.status.title.world_host": "Playing with WorldHost", - "api.worldhost.joinability.not_published": "World isn't published!", "autoboop.filterlist": "Filter List", "autoboop.filterlist.mode": "Filter Mode", "autoboop.filterlist.mode.disabled": "Disabled", "autoboop.filterlist.mode.whitelist": "Whitelist", "autoboop.filterlist.mode.blacklist": "Blacklist", "text_alpha": "Text Transparency", - "api.status.description.in_game": "%s %s", - "api.status.description.in_game.game_mode": "%s %s: %s", - "api.status.description.in_game.map": "%s %s on %s", - "api.status.description.in_game.game_mode_map": "%s %s: %s on %s", - "api.add_shortcut_buttons": "Add shortcut buttons", - "api.add_shortcut_buttons.tooltip": "Adds buttons to the title screen and the game menu
to access social features more quickly.", - "api.account.export": "Export account data", - "api.account.export_data": "Choose destination file", - "api.account.export.success.title": "Exportação sucedida", - "api.account.export.success.description": "Informação da conta exportada para %s", - "api.account.export.failure.title": "Exportação falhou", - "api.account.export.failure.description_write": "Failed to write file, you may find more information in the log file!", - "api.account.export.failure.description_server": "Failed to request data from the server, please contact DolphinClient with a log file to get support!", "feature.broken": "This feature is currently not working. If you know how to fix it please let us know!", "inventory.potion_effect_offset": "Inventory Potion Offset", "gallery.title": "Galeria", @@ -627,9 +470,6 @@ "gallery.image.open.external.browser": "Abrir num navegador", "gallery.image.copy_url": "Copy image url", "gallery.title.local": "Local screenshots", - "gallery.title.shared": "Shared by your friends", - "gallery.tab.local": "Ver imagens locais", - "gallery.tab.shared": "Ver imagens partilhadas", "gallery.download_external": "Ver imagem de um url", "gallery.image.view": "Ver imagem", "gallery.image.loading.title": "Loading image...", @@ -642,8 +482,6 @@ "key.freelook.alt": "Freelook (Alternative)", "perspective.alt": "Perspective (Alternative Key)", "toggle.alt": "Toggle (Alternative Key)", - "api.account.settings.allow_friends_image_access": "Allow Friends Image Access", - "api.account.settings.allow_friends_image_access.tooltip": "Allow friends to access images you upload directly in their gallery screen.
Disabling this does NOT stop images from being available via their url or id,
it only prevents friends from seeing your images in the \"Shared by friends\" gallery tab.", "show_biome": "Show Biome", "coordshud.biome": "Biome", "keystrokes": "Keystrokes", @@ -688,8 +526,8 @@ "debugcounters.pcount": "Particle Debug Info", "bedwars_resources.renderWhenRelevant": "Only Render When Relevant", "bedwars_resources": "Resources HUD", - "levelhead.enabled.tooltip": "Whether this module is enabled or not.
This module requires the social features to be enabled
in order to communicate with the backend and the Hypixel API.", - "bedwars.enabled.tooltip": "Whether this module is enabled or not.
Some functions of this module require the social features to be enabled
in order to communicate with the backend and the Hypixel API.", + "levelhead.enabled.tooltip": "Whether this module is enabled or not.
This module requires DolphinClient's online features
in order to communicate with the backend and the Hypixel API.", + "bedwars.enabled.tooltip": "Whether this module is enabled or not.
Some functions of this module require DolphinClient's online features
in order to communicate with the backend and the Hypixel API.", "daycounterhud": "Day Counter HUD", "daycounterhud.days": "%s Days", "keystrokes.animation_time": "Tempo de Animação (Millisegundos)", @@ -753,24 +591,11 @@ "armorhud.main_hand_item_position.disabled": "Desligado", "tablist.small_ping_text": "Small Ping Text", "tablist.small_ping_text.tooltip": "Use Unicode subscript number characters instead of regular numbers", - "api.status.title.e4mc": "Playing with e4mc", - "api.servers": "Servers", - "api.servers.friends": "Friends [%s]", - "api.servers.friends.title": "Online Friends", - "api.allow_friends_server_join": "Allow Friends to join servers", - "api.allow_friends_server_join.tooltip": "This will expose your current server's address.
Includes worlds shared using e4mc & WorldHost!", - "api.status.title.unknown_server": "Jogando num Servidor", - "api.servers.friends.no_online_friends": "Nenhum amigo online!", - "api.worldhost.joinability.not_joinable": "Player does not allow joining!", "menu_button_mode.disabled": "Desligado", "menu_button_mode.modmenu": "Só sem ModMenu", "menu_button_mode.always": "Sempre", "title_screen_button_mode": "Title Screen Options Button Mode", "game_menu_screen_button_mode": "Game Menu Screen Options Button Mode", - "privacy_policy_state.accepted": "Aceitado", - "privacy_policy_state.denied": "Rejeitado", - "privacy_policy_state.unset": "Unset", - "api.privacy_policy_accepted": "Terms Acceptance", "hud.entry.name": "HUD Entry: %s", "hud.entry.usage": "Prima Espaço para entrar no modo de movimento. Prima Enter para abrir as opções deste módulo.", "hud.entry.usage.move": "Prima Escape ou Espaço para sair do modo de movimento, use teclas de seta para mover este HUD. Prima Enter para abrir as opções deste módulo.", diff --git a/versions/1.8.9/build.gradle.kts b/versions/1.8.9/build.gradle.kts index 3dd1dbc57..9088957a0 100644 --- a/versions/1.8.9/build.gradle.kts +++ b/versions/1.8.9/build.gradle.kts @@ -66,8 +66,6 @@ dependencies { api("net.hypixel:mod-api:1.0.2") include(modImplementation("io.github.moehreag.hypixel:mod-api-fabric:1.0.2+build.1+mc1.8.9")!!) include(implementation("com.mojang:brigadier:1.0.18")!!) - - modCompileOnly("maven.modrinth:e4mc-retro:R6GoyDZn") } configurations.configureEach { diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/DolphinClient.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/DolphinClient.java index d42e5465e..8c74452ed 100644 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/DolphinClient.java +++ b/versions/1.8.9/src/main/java/io/github/axolotlclient/DolphinClient.java @@ -26,7 +26,6 @@ import io.github.axolotlclient.api.API; import io.github.axolotlclient.api.APIOptions; import io.github.axolotlclient.api.Options; -import io.github.axolotlclient.api.StatusUpdateProviderImpl; import io.github.axolotlclient.bridge.impl.Bridge; import io.github.axolotlclient.config.AxolotlClientConfig; import io.github.axolotlclient.modules.ModuleLoader; @@ -75,7 +74,7 @@ public void onInitializeClient() { addExternalModules(); init(Notifications.getInstance()); - new API(new StatusUpdateProviderImpl()); + new API(); getLogger().debug("Debug Output enabled, Logs will be quite verbose!"); getLogger().info("DolphinClient Initialized"); diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/APIOptions.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/api/APIOptions.java index a27c70eac..7b85ac811 100644 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/APIOptions.java +++ b/versions/1.8.9/src/main/java/io/github/axolotlclient/api/APIOptions.java @@ -23,32 +23,10 @@ package io.github.axolotlclient.api; -import java.nio.file.Path; import java.util.concurrent.CompletableFuture; -import java.util.function.Consumer; -import io.github.axolotlclient.DolphinClient; -import io.github.axolotlclient.DolphinClientCommon; -import io.github.axolotlclient.AxolotlClientConfig.api.ui.screen.ConfigScreen; -import io.github.axolotlclient.api.chat.ChannelInvitesScreen; -import io.github.axolotlclient.api.chat.ChatListScreen; -import io.github.axolotlclient.api.requests.AccountDataRequest; -import io.github.axolotlclient.api.requests.AccountSettingsRequest; -import io.github.axolotlclient.util.ThreadExecuter; -import io.github.axolotlclient.util.options.GenericOption; import lombok.Getter; -import net.fabricmc.loader.api.FabricLoader; import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.screen.ConfirmScreen; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.options.KeyBinding; -import net.minecraft.client.resource.language.I18n; -import net.ornithemc.osl.keybinds.api.KeybindEvents; -import net.ornithemc.osl.keybinds.api.KeybindRegistry; -import net.ornithemc.osl.lifecycle.api.client.MinecraftClientEvents; -import org.lwjgl.input.Keyboard; -import org.lwjgl.system.MemoryStack; -import org.lwjgl.util.tinyfd.TinyFileDialogs; public class APIOptions extends Options { @@ -66,76 +44,5 @@ public void init() { client.executeTask(() -> client.openScreen(new PrivacyNoticeScreen(parent, fut))); return fut; }; - KeyBinding openSidebar = new KeyBinding("api.chats.sidebar.open", Keyboard.KEY_O, DolphinClientCommon.KEY_CATEGORY); - KeybindEvents.REGISTER_KEYBINDS.register(() -> KeybindRegistry.register(openSidebar)); - MinecraftClientEvents.TICK_END.register(minecraft -> { - if (openSidebar.consumeClick() && API.getInstance().isAuthenticated()) { - var parent = client.screen; - minecraft.openScreen(new ChatsSidebar(parent)); - } - }); - category.add(new GenericOption("viewFriends", "clickToOpen", - () -> { - var parent = client.screen; - client.openScreen(new FriendsScreen(parent)); - })); - category.add(new GenericOption("viewChats", "clickToOpen", - () -> { - var parent = client.screen; - client.openScreen(new ChatListScreen(parent)); - })); - category.add(new GenericOption("api.channels.invites.view", "clickToOpen", - () -> { - var parent = client.screen; - client.openScreen(new ChannelInvitesScreen(parent)); - })); - account.add(new GenericOption("api.account.usernames", "clickToOpen", - () -> { - var parent = client.screen; - client.openScreen(new UsernameManagementScreen(parent)); - })); - account.add(new GenericOption("api.account.export", "api.account.export_data", () -> ThreadExecuter.scheduleTask(() -> { - if (!API.getInstance().isAuthenticated()) { - API.getInstance().getNotificationProvider().addStatus("api.account.export.failure.title", "api.error.unauthenticated"); - return; - } - try (MemoryStack stack = MemoryStack.stackPush()) { - var pointers = stack.pointers(stack.UTF8("*.json")); - var result = TinyFileDialogs.tinyfd_saveFileDialog("Choose export destination", FabricLoader.getInstance().getGameDir().toString(), pointers, null); - if (result != null) { - AccountDataRequest.get(Path.of(result)); - } - } - }))); - account.add(new GenericOption("api.account.delete", "api.account.delete_account", () -> { - Screen previous = client.screen; - client.openScreen(new ConfirmScreen((b, i) -> { - if (b) { - if (!API.getInstance().isAuthenticated()) { - API.getInstance().getNotificationProvider().addStatus("api.account.deletion.failure", "api.error.unauthenticated"); - } else { - AccountSettingsRequest.deleteAccount().thenAccept(r -> { - if (r) { - API.getInstance().getNotificationProvider().addStatus("api.account.deletion.success", "api.account.deletion.success.desc"); - } else { - API.getInstance().getNotificationProvider().addStatus("api.account.deletion.failure", "api.account.deletion.failure.desc"); - } - enabled.set(false); - }); - } - } - client.openScreen(previous); - }, I18n.translate("api.account.confirm_deletion"), - I18n.translate("api.account.confirm_deletion.desc"), 0)); - })); - Consumer consumer = settingUpdated; - settingUpdated = b -> { - if (client.screen instanceof ConfigScreen) { - consumer.accept(b); - } - }; - if (Constants.ENABLED) { - DolphinClient.config().addCategory(category); - } } } diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/AddFriendScreen.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/api/AddFriendScreen.java deleted file mode 100644 index 49c45a8c1..000000000 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/AddFriendScreen.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api; - -import io.github.axolotlclient.api.requests.FriendRequest; -import io.github.axolotlclient.api.util.UUIDHelper; -import io.github.axolotlclient.util.notifications.Notifications; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.resource.language.I18n; - -public class AddFriendScreen extends SimpleTextInputScreen { - public AddFriendScreen(Screen parent) { - super(parent, I18n.translate("api.screen.friends.add"), I18n.translate("api.screen.friends.add.name"), - s -> { - if (API.getInstance().isSocketConnected()) { - UUIDHelper.ensureUuid(s).thenAccept(FriendRequest.getInstance()::addFriend); - } else { - Notifications.getInstance().addStatus("api.error.notLoggedIn", "api.error.notLoggedIn.desc"); - } - }); - } -} diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/ChatsSidebar.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/api/ChatsSidebar.java deleted file mode 100644 index 2e6da5672..000000000 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/ChatsSidebar.java +++ /dev/null @@ -1,409 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api; - -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.stream.Collectors; - -import io.github.axolotlclient.api.chat.ChatWidget; -import io.github.axolotlclient.api.handlers.ChatHandler; -import io.github.axolotlclient.api.requests.ChannelRequest; -import io.github.axolotlclient.api.types.Channel; -import io.github.axolotlclient.api.types.User; -import io.github.axolotlclient.api.util.AlphabeticalComparator; -import io.github.axolotlclient.mixin.ScreenAccessor; -import io.github.axolotlclient.util.DrawUtil; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.gui.widget.ButtonWidget; -import net.minecraft.client.gui.widget.EntryListWidget; -import net.minecraft.client.gui.widget.TextFieldWidget; -import net.minecraft.client.render.platform.GlStateManager; -import net.minecraft.client.resource.language.I18n; -import net.minecraft.text.Formatting; -import org.lwjgl.input.Keyboard; - -public class ChatsSidebar extends Screen implements ContextMenuScreen { - - private static final int ANIM_STEP = 8; - private final Screen parent; - private int sidebarAnimX; - private int sidebarWidth; - private boolean remove; - private boolean hasChat; - private ListWidget list; - private TextFieldWidget input; - private Channel channel; - - private ChatWidget chatWidget; - - private ContextMenuContainer contextMenu; - private final String title; - - public ChatsSidebar(Screen parent) { - super(); - this.title = I18n.translate("api.chats.sidebar"); - this.parent = parent; - } - - @Override - public void render(int mouseX, int mouseY, float delta) { - if (parent != null) { - parent.render(mouseX, mouseY, delta); - } - GlStateManager.pushMatrix(); - GlStateManager.translatef(0, 0, 1000); - io.github.axolotlclient.AxolotlClientConfig.impl.util.DrawUtil.pushScissor(0, 0, Math.max(0, sidebarWidth + sidebarAnimX), height); - fill(sidebarAnimX, 0, sidebarWidth + sidebarAnimX, height, 0x99000000); - - textRenderer.drawWithShadow(I18n.translate("api.chats"), 10 + sidebarAnimX, 10, -1); - - if (list != null) { - list.render(mouseX, mouseY, delta); - } - - super.render(mouseX, mouseY, delta); - - if (input != null) { - input.render(); - } - - if (hasChat) { - fill(70 + sidebarAnimX, 0, 70 + sidebarAnimX + 1, height, 0xFF000000); - textRenderer.drawWithShadow(channel.getName(), sidebarAnimX + 75, 20, -1); - if (channel.isDM() && ((Channel.DM) channel).getReceiver().getStatus().isOnline()) { - textRenderer.drawWithShadow(Formatting.ITALIC + ((Channel.DM) channel).getReceiver().getStatus().getTitle() + ": " + ((Channel.DM) channel).getReceiver().getStatus().getDescription(), - sidebarAnimX + 80, 30, 8421504); - } - chatWidget.render(mouseX, mouseY, delta); - } - - contextMenu.render(minecraft, mouseX, mouseY); - animate(); - io.github.axolotlclient.AxolotlClientConfig.impl.util.DrawUtil.popScissor(); - GlStateManager.popMatrix(); - } - - @Override - public void init() { - removeChat(); - sidebarWidth = 70; - sidebarAnimX = -sidebarWidth; - - if (parent != null) { - ((ScreenAccessor) parent).getButtons().stream() - .filter(e -> e.message.equals(I18n.translate("api.chats"))).forEach(e -> e.visible = false); - } - - ChannelRequest.getChannelList().whenCompleteAsync((list, t) -> - this.list = new ListWidget(list, 10, 30, 50, height - 70) - ); - - buttons.add(new ButtonWidget(0, 10 - sidebarWidth, height - 30, 50, 20, I18n.translate("gui.back"))); - contextMenu = new ContextMenuContainer(); - } - - public void remove() { - remove = true; - } - - @Override - public void tick() { - if (input != null) { - input.tick(); - } - } - - @Override - public void removed() { - if (chatWidget != null) { - chatWidget.remove(); - } - } - - @Override - public boolean shouldPauseGame() { - return parent != null && parent.shouldPauseGame(); - } - - private void animate() { - if (sidebarAnimX < 0 && !remove) { - if (sidebarAnimX > -ANIM_STEP) { - sidebarAnimX = -ANIM_STEP; - } - sidebarAnimX += ANIM_STEP; - if (list != null) { - list.visible = false; - } - buttons.forEach(button -> button.x = (button.x + ANIM_STEP)); - } else if (remove) { - if (sidebarAnimX <= -sidebarWidth) { - close(); - } - sidebarAnimX -= ANIM_STEP; - if (list != null) { - list.setX(list.getX() - ANIM_STEP); - } - buttons.forEach(button -> button.x = (button.x - ANIM_STEP)); - if (chatWidget != null) { - chatWidget.setX(chatWidget.getX() - ANIM_STEP); - } - if (input != null) { - input.x -= ANIM_STEP; - } - } else { - if (list != null && !list.visible) { - list.visible = true; - } - } - } - - private void close() { - minecraft.openScreen(parent); - if (chatWidget != null) { - chatWidget.remove(); - } - } - - @Override - public void mouseClicked(int mouseX, int mouseY, int button) { - if (contextMenu.getMenu() != null) { - if (contextMenu.mouseClicked(mouseX, mouseY, button)) { - return; - } - contextMenu.removeMenu(); - } - if (mouseX > sidebarWidth) { - remove(); - return; - } - if (list != null) { - list.mouseClicked(mouseX, mouseY, button); - } - - if (input != null) { - input.mouseClicked(mouseX, mouseY, button); - } - if (chatWidget != null) { - chatWidget.mouseClicked(mouseX, mouseY, button); - } - super.mouseClicked(mouseX, mouseY, button); - } - - private void removeChat() { - hasChat = false; - chatWidget = null; - input = null; - } - - @Override - protected void buttonClicked(ButtonWidget buttonWidget) { - if (buttonWidget.id == 0) { - remove(); - } - } - - private void addChat(Channel channel) { - if (hasChat) { - removeChat(); - } - hasChat = true; - list.elements.forEach(b -> b.active = true); - this.channel = channel; - int w; - if (channel.isDM()) { - User chatUser = ((Channel.DM) channel).getReceiver(); - w = Math.max(minecraft.textRenderer.getWidth(chatUser.getStatus().getTitle() + ": " + chatUser.getStatus().getDescription()) + 5, - minecraft.textRenderer.getWidth(channel.getName())); - } else { - w = minecraft.textRenderer.getWidth(channel.getName()); - } - sidebarWidth = Math.min(Math.max(width * 5 / 12, w + 5), width / 2); - chatWidget = new ChatWidget(channel, 75, 50, sidebarWidth - 80, height - 100, this); - input = new TextFieldWidget(2, textRenderer, 75, height - 30, sidebarWidth - 80, 20) { - @Override - public boolean keyPressed(char c, int i) { - if (i == Keyboard.KEY_RETURN) { - ChatHandler.getInstance().sendMessage(ChatsSidebar.this.channel, input.getText()); - input.setText(""); - return true; - } - return super.keyPressed(c, i); - } - - @Override - public void render() { - super.render(); - if (getText().isEmpty() && !isFocused()) { - drawString(textRenderer, I18n.translate("api.friends.chat.input"), - x + 2, y + 6, -8355712); - } - } - }; - input.setMaxLength(1024); - } - - @Override - public ContextMenuContainer getMenuContainer() { - return contextMenu; - } - - @Override - public Screen getSelf() { - return this; - } - - @Override - public Screen getParent() { - return parent; - } - - @Override - public void handleMouse() { - super.handleMouse(); - if (list != null) { - list.handleMouse(); - } - if (chatWidget != null) { - chatWidget.handleMouse(); - } - } - - @Override - protected void keyPressed(char c, int i) { - if (input != null) { - input.keyPressed(c, i); - } - super.keyPressed(c, i); - } - - public interface Action { - void onPress(ListWidget.UserButton button); - } - - private class ListWidget extends EntryListWidget { - private final List elements; - private final int entryHeight = 25; - private boolean visible = true; - - public ListWidget(List list, int x, int y, int width, int height) { - super(Minecraft.getInstance(), width, height, y, ChatsSidebar.this.height - y, 25); - minX = x; - maxX = x + width; - minY = y; - maxY = y + height; - AtomicInteger buttonY = new AtomicInteger(y); - elements = list.stream().sorted((u1, u2) -> new AlphabeticalComparator().compare(u1.getName(), u2.getName())) - .map(channel -> new UserButton(x, buttonY.getAndAdd(entryHeight), width, entryHeight - 5, - channel.getName(), buttonWidget -> { - addChat(channel); - buttonWidget.active = false; - })).collect(Collectors.toList()); - } - - public int getX() { - return minX; - } - - public void setX(int x) { - minX = x; - maxX = x + width; - elements.forEach(e -> e.x = x); - } - - @Override - protected int size() { - return elements.size(); - } - - @Override - public void render(int mouseX, int mouseY, float delta) { - if (this.visible) { - this.mouseX = mouseX; - this.mouseY = mouseY; - this.capScrolling(); - int m = this.minX + this.width / 2 - this.getRowWidth() / 2 + 2; - int n = this.minY + 4 - (int) this.scrollAmount; - - this.renderList(m, n, mouseX, mouseY); - } - } - - @Override - protected void renderList(int x, int y, int mouseX, int mouseY) { - DrawUtil.enableScissor(minX, minY, minX + this.width, maxY); - super.renderList(x, y, mouseX, mouseY); - DrawUtil.disableScissor(); - } - - @Override - public Entry getEntry(int i) { - return elements.get(i); - } - - public class UserButton extends ButtonWidget implements Entry { - private final Action action; - - public UserButton(int x, int y, int width, int height, String string, Action action) { - super(0, x, y, width, height, string); - this.action = action; - visible = true; - } - - public void mouseClicked(int mouseX, int mouseY) { - if (mouseClicked(minecraft, mouseX, mouseY)) { - playClickSound(minecraft.getSoundManager()); - action.onPress(this); - } - } - - @Override - public void renderOutOfBounds(int i, int j, int k) { - - } - - @Override - public void render(int index, int x, int y, int rowWidth, int rowHeight, int mouseX, int mouseY, - boolean hovered) { - this.y = y; - render(minecraft, mouseX, mouseY); - } - - @Override - public boolean mouseClicked(int index, int mouseX, int mouseY, int button, int x, int y) { - if (super.mouseClicked(minecraft, mouseX, mouseY)) { - playClickSound(minecraft.getSoundManager()); - action.onPress(this); - return true; - } - return false; - } - - @Override - public void mouseReleased(int i, int j, int k, int l, int m, int n) { - - } - } - } -} diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/ContextMenu.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/api/ContextMenu.java deleted file mode 100644 index b63f5d090..000000000 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/ContextMenu.java +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api; - -import java.util.ArrayList; -import java.util.List; - -import io.github.axolotlclient.util.DrawUtil; -import io.github.axolotlclient.util.Util; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiElement; -import net.minecraft.client.gui.widget.ButtonWidget; -import net.minecraft.client.render.TextRenderer; -import net.minecraft.client.render.platform.GlStateManager; -import net.minecraft.client.resource.language.I18n; - -public class ContextMenu { - - private final List children; - private int x; - private int y; - - private int width, height = 0; - private boolean rendering; - - protected ContextMenu(List items) { - children = items; - int width = 0; - int height = 0; - for (ButtonWidget d : children) { - d.y = height; - height += 11; - width = Math.max(width, d.getWidth()); - } - this.width = width; - this.height = height; - } - - public static Builder builder() { - return new Builder(); - } - - public void addEntry(ButtonWidget entry) { - children.add(entry); - } - - public void render(Minecraft client, int mouseX, int mouseY) { - if (!rendering) { - y = mouseY; - x = mouseX; - rendering = true; - } - final int yStart = (int) Math.min(y + 2, Util.getWindow().getScaledHeight() - height - 2); - final int xStart = (int) Math.min(x + 2, Util.getWindow().getScaledWidth() - width - 2); - int y = yStart + 1; - width = 0; - for (ButtonWidget d : children) { - d.x = (xStart + 1); - d.y = (y); - y += 11; - width = Math.max(width, d.getWidth()); - } - height = y; - GlStateManager.pushMatrix(); - GlStateManager.translatef(0, 0, 200); - GuiElement.fill(xStart, yStart, xStart + width + 1, y, 0xDD1E1F22); - DrawUtil.outlineRect(xStart, yStart, width + 1, y - yStart + 1, -1); - for (ButtonWidget c : children) { - c.setWidth(width); - c.render(client, mouseX, mouseY); - } - GlStateManager.popMatrix(); - } - - public boolean mouseClicked(double mouseX, double mouseY, int button) { - List stream = children.stream().filter(b -> b instanceof Pressable) - .map(b -> (Pressable) b).toList(); - boolean clicked = false; - for (Pressable c : stream) { - clicked = c.onPress(mouseX, mouseY, button); - if (clicked) { - break; - } - } - return clicked; - } - - public boolean isMouseOver(double mouseX, double mouseY) { - return mouseX >= x && mouseX <= x + width && mouseY >= y && mouseY <= y + height; - } - - public interface PressAction { - void onPress(ButtonWidget buttonWidget); - } - - public static class Builder { - - private final List elements = new ArrayList<>(); - - public Builder() { - - } - - public Builder entry(String name, PressAction action) { - elements.add(new ContextMenuEntryWithAction(I18n.translate(name), action)); - return this; - } - - public Builder entry(ButtonWidget widget) { - elements.add(widget); - return this; - } - - public Builder spacer() { - elements.add(new ContextMenuEntry("-----")); - return this; - } - - public Builder title(String title) { - elements.add(new ContextMenuEntry(title)); - return this; - } - - public ContextMenu build() { - return new ContextMenu(elements); - } - - } - - public static class ContextMenuEntry extends ButtonWidget implements Pressable { - - public ContextMenuEntry(String content) { - super(0, 0, 0, Minecraft.getInstance().textRenderer.getWidth(content), 11, content); - } - - @Override - public void render(Minecraft client, int mouseX, int mouseY) { - drawCenteredString(client.textRenderer, message, x + getWidth() / 2, y, 0xDDDDDD); - } - - @Override - public boolean onPress(double mouseX, double mouseY, int button) { - return false; - } - } - - public static class ContextMenuEntryWithAction extends ButtonWidget implements Pressable { - - final PressAction action; - - private final Minecraft client = Minecraft.getInstance(); - - public ContextMenuEntryWithAction(String message, PressAction onPress) { - super(0, 0, 0, Minecraft.getInstance().textRenderer.getWidth(message) + 4, 11, message); - this.action = onPress; - } - - @Override - public void render(Minecraft client, int mouseX, int mouseY) { - this.hovered = mouseX >= this.x && mouseY >= this.y && mouseX < this.x + this.width && mouseY < this.y + this.height; - - if (isHovered()) { - fill(x, y, x + getWidth(), y + height, 0x55ffffff); - } - - GlStateManager.color4f(1.0F, 1.0F, 1.0F, 1.0F); - int i = this.active ? 16777215 : 10526880; - drawScrollableText(Minecraft.getInstance().textRenderer, 2, i); - } - - protected void drawScrollableText(TextRenderer textRenderer, int xOffset, int color) { - int x = this.x + xOffset; - int xEnd = this.x + this.getWidth() - xOffset; - DrawUtil.drawScrollableText(textRenderer, message, x, this.y, xEnd, this.y + height, color); - } - - public boolean onPress(double mouseX, double mouseY, int button) { - if (mouseClicked(client, (int) mouseX, (int) mouseY) && button == 0) { - playClickSound(client.getSoundManager()); - action.onPress(this); - return true; - } - return false; - } - } - - private interface Pressable { - boolean onPress(double mouseX, double mouseY, int button); - } -} diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/ContextMenuContainer.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/api/ContextMenuContainer.java deleted file mode 100644 index 437a6080d..000000000 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/ContextMenuContainer.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api; - -import lombok.Getter; -import lombok.Setter; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiElement; -import org.jetbrains.annotations.Nullable; - -public class ContextMenuContainer extends GuiElement { - - @Getter - @Setter - @Nullable - private ContextMenu menu; - - public ContextMenuContainer() { - - } - - public void removeMenu() { - menu = null; - } - - public boolean hasMenu() { - return menu != null; - } - - public void render(Minecraft client, int mouseX, int mouseY) { - if (menu != null) { - menu.render(client, mouseX, mouseY); - } - } - - public boolean mouseClicked(double mouseX, double mouseY, int button) { - if (menu != null) { - if (!menu.isMouseOver(mouseX, mouseY)) { - removeMenu(); - return true; - } - if (menu.mouseClicked(mouseX, mouseY, button)) removeMenu(); - return true; - } - return false; - } - - public boolean isMouseOver(double mouseX, double mouseY) { - if (menu != null) { - return menu.isMouseOver(mouseX, mouseY); - } - return false; - } -} diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/ContextMenuScreen.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/api/ContextMenuScreen.java deleted file mode 100644 index 392e06536..000000000 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/ContextMenuScreen.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api; - -import net.minecraft.client.gui.screen.Screen; - -public interface ContextMenuScreen { - - default void setContextMenu(ContextMenu menu) { - getMenuContainer().setMenu(menu); - } - - default boolean hasContextMenu() { - return getMenuContainer().hasMenu(); - } - - ContextMenuContainer getMenuContainer(); - - Screen getParent(); - - Screen getSelf(); -} diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/FriendsScreen.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/api/FriendsScreen.java deleted file mode 100644 index 0d07e50f7..000000000 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/FriendsScreen.java +++ /dev/null @@ -1,326 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api; - -import java.util.stream.Collectors; - -import io.github.axolotlclient.api.chat.ChatScreen; -import io.github.axolotlclient.api.requests.ChannelRequest; -import io.github.axolotlclient.api.requests.FriendRequest; -import io.github.axolotlclient.api.util.AlphabeticalComparator; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.gui.screen.multiplayer.LanScanWidget; -import net.minecraft.client.gui.widget.ButtonWidget; -import net.minecraft.client.gui.widget.EntryListWidget; -import net.minecraft.client.resource.language.I18n; - -public class FriendsScreen extends Screen { - - private final Screen parent; - - private UserListWidget widget; - - private ButtonWidget chatButton, removeButton, onlineTab, allTab, pendingTab, blockedTab; - private ButtonWidget denyButton, acceptButton, unblockButton, cancelButton; - - private Tab current = Tab.ONLINE; - - protected FriendsScreen(Screen parent, Tab tab) { - this(parent); - current = tab; - } - - public FriendsScreen(Screen parent) { - super(); - this.parent = parent; - } - - @Override - public void render(int mouseX, int mouseY, float delta) { - this.renderBackground(); - this.widget.render(mouseX, mouseY, delta); - drawCenteredString(this.textRenderer, I18n.translate("api.screen.friends"), this.width / 2, 20, 16777215); - super.render(mouseX, mouseY, delta); - } - - @Override - protected void keyPressed(char c, int i) { - int j = this.widget.getSelected(); - EntryListWidget.Entry entry = j < 0 ? null : this.widget.getEntry(j); - if (i == 63) { - this.refresh(); - } else { - if (j >= 0) { - if (i == 200) { - if (isShiftDown()) { - if (j > 0 && entry instanceof UserListWidget.UserListEntry) { - this.select(this.widget.getSelected() - 1); - this.widget.scroll(-this.widget.getEntryHeight()); - } - } else if (j > 0) { - this.select(this.widget.getSelected() - 1); - this.widget.scroll(-this.widget.getEntryHeight()); - if (this.widget.getEntry(this.widget.getSelected()) instanceof LanScanWidget) { - if (this.widget.getSelected() > 0) { - this.select(this.widget.size() - 1); - this.widget.scroll(-this.widget.getEntryHeight()); - } else { - this.select(-1); - } - } - } else { - this.select(-1); - } - } else if (i == 208) { - if (isShiftDown()) { - this.select(j + 1); - this.widget.scroll(this.widget.getEntryHeight()); - } else if (j < this.widget.size()) { - this.select(this.widget.getSelected() + 1); - this.widget.scroll(this.widget.getEntryHeight()); - if (this.widget.getEntry(this.widget.getSelected()) instanceof LanScanWidget) { - if (this.widget.getSelected() < this.widget.size() - 1) { - this.select(this.widget.size() + 1); - this.widget.scroll(this.widget.getEntryHeight()); - } else { - this.select(-1); - } - } - } else { - this.select(-1); - } - } else if (i != 28 && i != 156) { - super.keyPressed(c, i); - } else { - this.buttonClicked(this.buttons.get(2)); - } - } else { - super.keyPressed(c, i); - } - } - } - - private void refresh() { - minecraft.openScreen(new FriendsScreen(parent)); - } - - public void select(int i) { - this.widget.setSelected(i); - this.updateButtonActivationStates(); - } - - public void openChat() { - if (!chatButton.active) { - return; - } - UserListWidget.UserListEntry entry = widget.getSelectedEntry(); - if (entry != null) { - chatButton.active = false; - ChannelRequest.getOrCreateDM(entry.getUser()) - .thenAccept(c -> minecraft.executeTask(() -> minecraft.openScreen(new ChatScreen(this, c)))); - } - } - - private void acceptRequest() { - UserListWidget.UserListEntry entry = widget.getSelectedEntry(); - if (entry != null) { - acceptButton.active = false; - FriendRequest.getInstance().acceptFriendRequest(entry.getUser()).thenRun(() -> minecraft.executeTask(this::refresh)); - } - } - - private void denyRequest() { - UserListWidget.UserListEntry entry = widget.getSelectedEntry(); - if (entry != null) { - denyButton.active = false; - FriendRequest.getInstance().denyFriendRequest(entry.getUser()).thenRun(() -> minecraft.executeTask(this::refresh)); - } - } - - @Override - protected void mouseClicked(int i, int j, int k) { - super.mouseClicked(i, j, k); - this.widget.mouseClicked(i, j, k); - } - - @Override - protected void mouseReleased(int i, int j, int k) { - super.mouseReleased(i, j, k); - this.widget.mouseReleased(i, j, k); - } - - @Override - protected void buttonClicked(ButtonWidget buttonWidget) { - switch (buttonWidget.id) { - case 0: - this.minecraft.openScreen(this.parent); - break; - case 1: - openChat(); - break; - case 2: - acceptRequest(); - break; - case 3: - denyRequest(); - break; - case 4: - UserListWidget.UserListEntry entry = this.widget.getSelectedEntry(); - if (entry != null) { - removeButton.active = false; - FriendRequest.getInstance().removeFriend(entry.getUser()).thenRun(() -> minecraft.executeTask(this::refresh)); - } - break; - case 5: - minecraft.openScreen(new AddFriendScreen(this)); - break; - case 6: - minecraft.openScreen(new FriendsScreen(parent, Tab.ONLINE)); - break; - case 7: - minecraft.openScreen(new FriendsScreen(parent, Tab.ALL)); - break; - case 8: - minecraft.openScreen(new FriendsScreen(parent, Tab.PENDING)); - break; - case 9: - minecraft.openScreen(new FriendsScreen(parent, Tab.BLOCKED)); - break; - case 10: - buttonWidget.active = false; - FriendRequest.getInstance().unblockUser(widget.getSelectedEntry().getUser()).thenRun(() -> minecraft.executeTask(this::refresh)); - break; - case 11: - buttonWidget.active = false; - FriendRequest.getInstance().cancelFriendRequest(widget.getSelectedEntry().getUser()).thenRun(() -> minecraft.executeTask(this::refresh)); - break; - } - } - - @Override - public void init() { - widget = new UserListWidget(this, minecraft, width, height, 32, height - 64, 35); - - if (current == Tab.ALL || current == Tab.ONLINE) { - FriendRequest.getInstance().getFriends().whenCompleteAsync((list, t) -> widget.setUsers(list.stream().filter(user -> { - if (current == Tab.ONLINE) { - return user.getStatus().isOnline(); - } - return true; - }).sorted((u1, u2) -> new AlphabeticalComparator().compare(u1.getName(), u2.getName())).collect(Collectors.toList()))); - } else if (current == Tab.PENDING) { - FriendRequest.getInstance().getFriendRequests().whenCompleteAsync((con, th) -> { - - con.getLeft().stream().sorted((u1, u2) -> new AlphabeticalComparator().compare(u1.getName(), u2.getName())) - .forEach(user -> widget.addEntry(new UserListWidget.UserListEntry(user, I18n.translate("api.friends.pending.incoming")))); - con.getRight().stream().sorted((u1, u2) -> new AlphabeticalComparator().compare(u1.getName(), u2.getName())) - .forEach(user -> widget.addEntry(new UserListWidget.UserListEntry(user, I18n.translate("api.friends.pending.outgoing")).outgoing())); - }); - } else if (current == Tab.BLOCKED) { - FriendRequest.getInstance().getBlocked().whenCompleteAsync((list, th) -> widget.setUsers(list.stream().sorted((u1, u2) -> - new AlphabeticalComparator().compare(u1.getName(), u2.getName())).collect(Collectors.toList()))); - } - - this.buttons.add(blockedTab = new ButtonWidget(9, this.width / 2 + 24, this.height - 52, 57, 20, - I18n.translate("api.friends.tab.blocked"))); - - this.buttons.add(pendingTab = new ButtonWidget(8, this.width / 2 - 34, this.height - 52, 57, 20, - I18n.translate("api.friends.tab.pending"))); - - this.buttons.add(allTab = new ButtonWidget(7, this.width / 2 - 94, this.height - 52, 57, 20, - I18n.translate("api.friends.tab.all"))); - - this.buttons.add(onlineTab = new ButtonWidget(6, this.width / 2 - 154, this.height - 52, 57, 20, - I18n.translate("api.friends.tab.online"))); - - this.buttons.add(new ButtonWidget(5, this.width / 2 + 88, this.height - 52, 66, 20, - I18n.translate("api.friends.add"))); - - this.buttons.add(removeButton = new ButtonWidget(4, this.width / 2 - 50, this.height - 28, 100, 20, - I18n.translate("api.friends.remove"))); - - buttons.add(denyButton = new ButtonWidget(3, this.width / 2 - 50, this.height - 28, 48, 20, - I18n.translate("api.friends.request.deny"))); - - buttons.add(acceptButton = new ButtonWidget(2, this.width / 2 + 2, this.height - 28, 48, 20, - I18n.translate("api.friends.request.accept"))); - - buttons.add(unblockButton = new ButtonWidget(10, this.width / 2 - 50, this.height - 28, 100, 20, I18n.translate("api.users.unblock"))); - buttons.add(cancelButton = new ButtonWidget(11, this.width / 2 - 50, this.height - 28, 100, 20, I18n.translate("gui.cancel"))); - - this.buttons.add(chatButton = new ButtonWidget(1, this.width / 2 - 154, this.height - 28, 100, 20, - I18n.translate("api.friends.chat"))); - - this.buttons.add( - new ButtonWidget(0, this.width / 2 + 4 + 50, this.height - 28, 100, 20, - I18n.translate("gui.back"))); - updateButtonActivationStates(); - } - - private void updateButtonActivationStates() { - UserListWidget.UserListEntry entry = widget.getSelectedEntry(); - chatButton.active = entry != null && (current == Tab.ALL || current == Tab.ONLINE); - - removeButton.visible = true; - unblockButton.active = removeButton.active = entry != null; - denyButton.visible = false; - acceptButton.visible = unblockButton.visible = cancelButton.visible = false; - if (current == Tab.ONLINE) { - onlineTab.active = false; - allTab.active = pendingTab.active = blockedTab.active = true; - } else if (current == Tab.ALL) { - allTab.active = false; - onlineTab.active = pendingTab.active = blockedTab.active = true; - } else if (current == Tab.PENDING) { - pendingTab.active = false; - onlineTab.active = allTab.active = blockedTab.active = true; - removeButton.visible = false; - - if (entry != null && entry.isOutgoingRequest()) { - cancelButton.visible = true; - } else { - denyButton.visible = true; - acceptButton.visible = true; - } - denyButton.active = acceptButton.active = entry != null; - } else if (current == Tab.BLOCKED) { - blockedTab.active = false; - onlineTab.active = allTab.active = pendingTab.active = true; - removeButton.visible = false; - unblockButton.visible = true; - } - } - - @Override - public void handleMouse() { - super.handleMouse(); - this.widget.handleMouse(); - } - - public enum Tab { - ONLINE, - ALL, - PENDING, - BLOCKED - } -} diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/NewsScreen.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/api/NewsScreen.java deleted file mode 100644 index 732a6a62e..000000000 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/NewsScreen.java +++ /dev/null @@ -1,311 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api; - -import java.util.List; - -import io.github.axolotlclient.AxolotlClientConfig.impl.ui.ClickableWidget; -import io.github.axolotlclient.AxolotlClientConfig.impl.ui.vanilla.widgets.VanillaButtonWidget; -import io.github.axolotlclient.api.requests.GlobalDataRequest; -import io.github.axolotlclient.util.DrawUtil; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.render.platform.GlStateManager; -import net.minecraft.client.resource.language.I18n; -import net.minecraft.client.sound.system.SoundManager; -import net.minecraft.util.math.MathHelper; - -public class NewsScreen extends io.github.axolotlclient.AxolotlClientConfig.impl.ui.Screen { - - - private final Screen parent; - - public NewsScreen(Screen parent) { - super(I18n.translate("api.notes.title")); - - this.parent = parent; - } - - @Override - public void render(int mouseX, int mouseY, float delta) { - super.render(mouseX, mouseY, delta); - - drawCenteredString(textRenderer, title, width / 2, 20, -1); - } - - @Override - public void init() { - GlobalDataRequest.get().thenAccept(data -> addDrawableChild(new NewsWidget(25, 35, width - 50, height - 100, data.notes().trim().replaceAll("([^\n])\n([^\n])", "$1 $2")))); - - addDrawableChild(new VanillaButtonWidget(width / 2 - 100, height - 45, 200, 20, I18n.translate("gui.back"), buttonWidget -> minecraft.openScreen(parent))); - } - - private class NewsWidget extends AbstractTextAreaWidget { - - private final List lines; - private final int contentHeight; - - public NewsWidget(int x, int y, int width, int height, String component) { - super(x, y, width, height, component); - lines = textRenderer.split(getMessage(), getWidth() - 4); - contentHeight = lines.size() * textRenderer.fontHeight; - } - - @Override - protected int getInnerHeight() { - return contentHeight; - } - - @Override - protected void renderContents(int mouseX, int mouseY, float partialTick) { - int y = getY() + 2; - for (String chsq : lines) { - drawString(textRenderer, chsq, getX() + 2, y, -1); - y += textRenderer.fontHeight; - } - } - - @Override - protected double scrollRate() { - return textRenderer.fontHeight; - } - - @Override - protected void renderBackground() { - } - } - - public abstract static class AbstractTextAreaWidget extends AbstractScrollArea { - private static final int INNER_PADDING = 4; - - public AbstractTextAreaWidget(int x, int y, int width, int height, String component) { - super(x, y, width, height, component); - } - - @Override - public boolean mouseClicked(double mouseX, double mouseY, int button) { - boolean bl = this.updateScrolling(mouseX, mouseY, button); - return super.mouseClicked(mouseX, mouseY, button) || bl; - } - - @Override - public boolean keyPressed(int keyCode, int scanCode, int modifiers) { - boolean bl = keyCode == 265; - boolean bl2 = keyCode == 264; - if (bl || bl2) { - double d = this.scrollAmount(); - this.setScrollAmount(this.scrollAmount() + (double) (bl ? -1 : 1) * this.scrollRate()); - if (d != this.scrollAmount()) { - return true; - } - } - - return super.keyPressed(keyCode, scanCode, modifiers); - } - - @Override - public void drawWidget(int mouseX, int mouseY, float partialTick) { - if (this.visible) { - this.renderBackground(); - DrawUtil.enableScissor(this.getX() + 1, this.getY() + 1, this.getX() + this.getWidth() - 1, this.getY() + this.getHeight() - 1); - GlStateManager.pushMatrix(); - GlStateManager.translated(0.0, -this.scrollAmount(), 0.0); - this.renderContents(mouseX, mouseY, partialTick); - GlStateManager.popMatrix(); - DrawUtil.disableScissor(); - renderDecorations(); - } - } - - protected void renderDecorations() { - this.renderScrollbar(); - } - - protected int innerPadding() { - return INNER_PADDING; - } - - protected int totalInnerPadding() { - return this.innerPadding() * 2; - } - - @Override - public boolean isMouseOver(double mouseX, double mouseY) { - return this.active - && this.visible - && mouseX >= (double) this.getX() - && mouseY >= (double) this.getY() - && mouseX < (double) (this.getXEnd() + 6) - && mouseY < (double) this.getYEnd(); - } - - @Override - protected int scrollBarX() { - return this.getXEnd(); - } - - @Override - protected int contentHeight() { - return this.getInnerHeight() + this.totalInnerPadding(); - } - - protected void renderBackground() { - this.renderBorder(this.getX(), this.getY(), this.getWidth(), this.getHeight()); - } - - protected void renderBorder(int x, int y, int width, int height) { - int i = this.isFocused() ? -1 : -6250336; - fill(this.getX(), this.getY(), this.getXEnd(), this.getYEnd(), i); - fill(this.getX() + 1, this.getY() + 1, this.getXEnd() - 1, this.getYEnd() - 1, -16777216); - } - - protected boolean withinContentAreaTopBottom(int top, int bottom) { - return (double) bottom - this.scrollAmount() >= (double) this.getY() && (double) top - this.scrollAmount() <= (double) (this.getY() + this.getHeight()); - } - - protected abstract int getInnerHeight(); - - protected abstract void renderContents(int mouseX, int mouseY, float partialTick); - - protected int getInnerLeft() { - return this.getX() + this.innerPadding(); - } - - protected int getInnerTop() { - return this.getY() + this.innerPadding(); - } - - @Override - public void playDownSound(SoundManager handler) { - } - } - - public abstract static class AbstractScrollArea extends ClickableWidget { - public static final int SCROLLBAR_WIDTH = 6; - private double scrollAmount; - private boolean scrolling; - - public AbstractScrollArea(int x, int y, int width, int height, String component) { - super(x, y, width, height, component); - } - - @Override - public boolean mouseScrolled(double mouseX, double mouseY, double amountX, double scrollY) { - if (!this.visible) { - return false; - } else { - this.setScrollAmount(this.scrollAmount() - scrollY * this.scrollRate()); - return true; - } - } - - @Override - public boolean mouseDragged(double mouseX, double mouseY, int button, double dragX, double dragY) { - if (this.scrolling) { - if (mouseY < (double) this.getY()) { - this.setScrollAmount(0.0); - } else if (mouseY > (double) this.getYEnd()) { - this.setScrollAmount(this.maxScrollAmount()); - } else { - double d = Math.max(1, this.maxScrollAmount()); - int i = this.scrollerHeight(); - double e = Math.max(1.0, d / (double) (this.getHeight() - i)); - this.setScrollAmount(this.scrollAmount() + dragY * e); - } - - return true; - } else { - return super.mouseDragged(mouseX, mouseY, button, dragX, dragY); - } - } - - @Override - public void onRelease(double mouseX, double mouseY) { - this.scrolling = false; - } - - public double scrollAmount() { - return this.scrollAmount; - } - - public void setScrollAmount(double scrollAmount) { - this.scrollAmount = MathHelper.clamp(scrollAmount, 0.0, this.maxScrollAmount()); - } - - public boolean updateScrolling(double mouseX, double mouseY, int button) { - this.scrolling = this.scrollbarVisible() - && this.isValidClickButton(button) - && mouseX >= (double) this.scrollBarX() - && mouseX <= (double) (this.scrollBarX() + 6) - && mouseY >= (double) this.getY() - && mouseY < (double) this.getYEnd(); - return this.scrolling; - } - - public void refreshScrollAmount() { - this.setScrollAmount(this.scrollAmount); - } - - public int maxScrollAmount() { - return Math.max(0, this.contentHeight() - this.getHeight()); - } - - protected boolean scrollbarVisible() { - return this.maxScrollAmount() > 0; - } - - protected int scrollerHeight() { - return MathHelper.clamp((int) ((float) (this.getHeight() * this.getHeight()) / (float) this.contentHeight()), 32, this.getHeight() - 8); - } - - protected int scrollBarX() { - return this.getXEnd() - 6; - } - - protected int scrollBarY() { - return Math.max(this.getY(), (int) this.scrollAmount * (this.getHeight() - this.scrollerHeight()) / this.maxScrollAmount() + this.getY()); - } - - protected void renderScrollbar() { - if (this.scrollbarVisible()) { - int i = this.scrollBarX(); - int j = this.scrollerHeight(); - int k = this.scrollBarY(); - fill(i, getY(), i + SCROLLBAR_WIDTH, getYEnd(), -16777216); - fill(i, k, i + SCROLLBAR_WIDTH, k + j, -8355712); - fill(i, k, i + SCROLLBAR_WIDTH - 1, k + j - 1, -4144960); - } - } - - protected int getYEnd() { - return getY() + getHeight(); - } - - protected abstract int contentHeight(); - - protected abstract double scrollRate(); - - protected int getXEnd() { - return getX() + getWidth(); - } - } -} diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/PrivacyNoticeScreen.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/api/PrivacyNoticeScreen.java index f99313080..a72698457 100644 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/PrivacyNoticeScreen.java +++ b/versions/1.8.9/src/main/java/io/github/axolotlclient/api/PrivacyNoticeScreen.java @@ -81,7 +81,6 @@ private void addButtons(int y) { protected void buttonClicked(ButtonWidget buttonWidget) { if (buttonWidget.id == 0) { minecraft.openScreen(parent); - APIOptions.getInstance().enabled.set(false); APIOptions.getInstance().privacyAccepted.set(Options.PrivacyPolicyState.DENIED); accepted.complete(false); } else if (buttonWidget.id == 1) { diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/StatusUpdateProviderImpl.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/api/StatusUpdateProviderImpl.java deleted file mode 100644 index 234f637e8..000000000 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/StatusUpdateProviderImpl.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api; - -import java.util.Arrays; -import java.util.Optional; - -import com.google.gson.JsonObject; -import io.github.axolotlclient.api.e4mc.E4mcStatusProvider; -import io.github.axolotlclient.api.requests.StatusUpdate; -import io.github.axolotlclient.api.util.StatusUpdateProvider; -import io.github.axolotlclient.modules.hypixel.HypixelMods; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.gui.screen.TitleScreen; -import net.minecraft.client.gui.screen.multiplayer.MultiplayerScreen; -import net.minecraft.client.options.ServerListEntry; -import net.minecraft.entity.living.player.PlayerEntity; -import net.minecraft.world.WorldSettings; - -public class StatusUpdateProviderImpl implements StatusUpdateProvider { - - @Override - public void initialize() { - } - - @Override - public Request getStatus() { - ServerListEntry entry = Minecraft.getInstance().getCurrentServerEntry(); - if (entry != null) { - - if (!entry.isLocal()) { - Optional optional = Arrays.stream(StatusUpdate.SupportedServer.values()).filter(s -> s.getAddress().matcher(entry.ip).matches()).findFirst(); - if (optional.isPresent()) { - StatusUpdate.SupportedServer server = optional.get(); - if (server.equals(StatusUpdate.SupportedServer.HYPIXEL)) { - return HypixelMods.getInstance().getStatus(); - } - } - return StatusUpdate.inGameServer(entry.name, entry.ip); - } - return StatusUpdate.inGameUnknown(entry.name); - } else if (Minecraft.getInstance().isSingleplayer()) { - var e4mcStatus = E4mcStatusProvider.getStatusDescription(); - if (e4mcStatus != null) { - return StatusUpdate.e4mcStatusUpdate(e4mcStatus); - } - return StatusUpdate.inGameUnknown(Minecraft.getInstance().getServer().getWorldName()); - } - Screen current = Minecraft.getInstance().screen; - if (current instanceof TitleScreen) { - return StatusUpdate.online(StatusUpdate.MenuId.MAIN_MENU); - } else if (current instanceof MultiplayerScreen) { - return StatusUpdate.online(StatusUpdate.MenuId.SERVER_LIST); - } else if (current != null) { - return StatusUpdate.online(StatusUpdate.MenuId.SETTINGS); - } - return null; - } - - private String getOrEmpty(JsonObject object, String name) { - return object.has(name) ? object.get(name).getAsString() : ""; - } - - private WorldSettings.GameMode getGameMode(PlayerEntity entity) { - return Minecraft.getInstance().getNetworkHandler().getOnlinePlayer(entity.getUuid()).getGameMode(); - } -} diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/UserListWidget.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/api/UserListWidget.java deleted file mode 100644 index adca6f1f4..000000000 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/UserListWidget.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api; - -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - -import io.github.axolotlclient.api.types.PkSystem; -import io.github.axolotlclient.api.types.User; -import io.github.axolotlclient.modules.auth.Auth; -import lombok.Getter; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiElement; -import net.minecraft.client.gui.widget.EntryListWidget; -import net.minecraft.client.render.platform.GlStateManager; -import net.minecraft.text.Formatting; - -public class UserListWidget extends EntryListWidget { - - private final FriendsScreen screen; - private final List entries = new ArrayList<>(); - private int selectedEntry = -1; - - public UserListWidget(FriendsScreen screen, Minecraft client, int width, int height, int top, int bottom, int entryHeight) { - super(client, width, height, top, bottom, entryHeight); - this.screen = screen; - } - - public void setUsers(List users) { - users.forEach(user -> addEntry(new UserListEntry(user))); - } - - public void addEntry(UserListEntry entry) { - entries.add(entry.init(screen)); - } - - @Override - protected int size() { - return entries.size(); - } - - @Override - public int getRowWidth() { - return super.getRowWidth() + 85; - } - - @Override - protected int getScrollbarPosition() { - return super.getScrollbarPosition() + 30; - } - - @Override - protected boolean isEntrySelected(int i) { - return i == this.selectedEntry; - } - - @Override - public Entry getEntry(int i) { - return entries.get(i); - } - - public UserListEntry getSelectedEntry() { - if (getSelected() < 0) { - return null; - } - return entries.get(getSelected()); - } - - public int getSelected() { - return this.selectedEntry; - } - - public void setSelected(int i) { - this.selectedEntry = i; - } - - public static class UserListEntry extends GuiElement implements EntryListWidget.Entry { - - @Getter - private final User user; - private final Minecraft client; - private long time; - private String note; - private FriendsScreen screen; - - @Getter - private boolean outgoingRequest; - - public UserListEntry(User user, String note) { - this(user); - this.note = Formatting.ITALIC + note; - } - - public UserListEntry(User user) { - this.client = Minecraft.getInstance(); - this.user = user; - } - - public UserListEntry init(FriendsScreen screen) { - this.screen = screen; - return this; - } - - public UserListEntry outgoing() { - outgoingRequest = true; - return this; - } - - @Override - public void renderOutOfBounds(int i, int j, int k) { - - } - - @Override - public void render(int index, int x, int y, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered) { - if (user.isSystem()) { - String fronters = user.getSystem().getFronters().stream() - .map(PkSystem.Member::getDisplayName).collect(Collectors.joining("/")); - String tag = Formatting.ITALIC + Formatting.GRAY.toString() + "(" + user.getSystem().getName() + "/" + user.getName() + ")"; - client.textRenderer.draw(fronters + " " + tag, x + 3, y + 1, -1); - } else { - client.textRenderer.draw(user.getName(), x + 3 + 33, y + 1, -1); - } - - if (user.getStatus().isOnline() && user.getStatus().getActivity() != null) { - client.textRenderer.draw(user.getStatus().getTitle(), x + 3 + 33, y + 12, 8421504); - client.textRenderer.draw(user.getStatus().getDescription(), x + 3 + 40, y + 23, 8421504); - } else if (user.getStatus().getLastOnline() != null) { - client.textRenderer.draw(user.getStatus().getLastOnline(), x + 3 + 33, y + 12, 8421504); - } - - if (note != null) { - client.textRenderer.draw(note, x + entryWidth - client.textRenderer.getWidth(note) - 4, y + entryHeight - 10, 8421504); - } - - GlStateManager.color4f(1, 1, 1, 1); - client.getTextureManager().bind(Auth.getInstance().getSkinTexture(user.getUuid(), user.getName())); - GlStateManager.enableBlend(); - drawTexture(x - 1, y - 1, 8, 8, 8, 8, 33, 33, 64, 64); - drawTexture(x - 1, y - 1, 40, 8, 8, 8, 33, 33, 64, 64); - GlStateManager.disableBlend(); - } - - @Override - public boolean mouseClicked(int i, int j, int k, int l, int m, int n) { - this.screen.select(i); - if (Minecraft.getTime() - this.time < 250L && client.world == null) { - screen.openChat(); - } - - this.time = Minecraft.getTime(); - return false; - } - - @Override - public void mouseReleased(int i, int j, int k, int l, int m, int n) { - - } - } -} diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/UsernameManagementScreen.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/api/UsernameManagementScreen.java deleted file mode 100644 index d2ffee20d..000000000 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/UsernameManagementScreen.java +++ /dev/null @@ -1,180 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import io.github.axolotlclient.api.requests.AccountUsernameRequest; -import io.github.axolotlclient.api.types.User; -import net.minecraft.client.gui.screen.ConfirmScreen; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.gui.widget.ButtonWidget; -import net.minecraft.client.gui.widget.EntryListWidget; -import net.minecraft.client.resource.language.I18n; - -public class UsernameManagementScreen extends Screen { - - private final Screen parent; - private UsernameListWidget widget; - private final String title = I18n.translate("api.account.usernames"); - - public UsernameManagementScreen(Screen parent) { - super(); - this.parent = parent; - } - - @Override - public void init() { - if (API.getInstance().isAuthenticated()) { - widget = new UsernameListWidget(API.getInstance().getSelf().getPreviousUsernames()); - } else { - widget = new UsernameListWidget(Collections.emptyList()); - } - - buttons.add(new ButtonWidget(0, width / 2 - 75, height - 25 - 20, 150, 20, I18n.translate("gui.back"))); - } - - @Override - protected void buttonClicked(ButtonWidget buttonWidget) { - if (buttonWidget.id == 0) { - minecraft.openScreen(parent); - } - } - - @Override - public void render(int mouseX, int mouseY, float delta) { - renderBackground(); - widget.render(mouseX, mouseY, delta); - super.render(mouseX, mouseY, delta); - textRenderer.drawWithShadow(title, width / 2f - textRenderer.getWidth(title) / 2f, 25, -1); - } - - @Override - public void handleMouse() { - super.handleMouse(); - widget.handleMouse(); - } - - @Override - protected void mouseClicked(int i, int j, int k) { - if (!widget.mouseClicked(i, j, k)) { - super.mouseClicked(i, j, k); - } - } - - @Override - protected void mouseReleased(int i, int j, int k) { - if (!widget.mouseReleased(i, j, k)) { - super.mouseReleased(i, j, k); - } - } - - private class UsernameListWidget extends EntryListWidget { - - private final List entries = new ArrayList<>(); - - public UsernameListWidget(List names) { - super(UsernameManagementScreen.this.minecraft, UsernameManagementScreen.this.width, UsernameManagementScreen.this.height, 45, UsernameManagementScreen.this.height - 55, 20); - - names.forEach(n -> entries.add(new UsernameListEntry(n))); - } - - @Override - protected int getScrollbarPosition() { - return (minX + this.width / 2 - this.getRowWidth() / 2) + getRowWidth() + 10; - } - - @Override - public int getRowWidth() { - return 310; - } - - @Override - public Entry getEntry(int i) { - return entries.get(i); - } - - @Override - protected int size() { - return entries.size(); - } - - private class UsernameListEntry implements Entry { - - private final ButtonWidget visibility; - private final ButtonWidget delete; - private final User.OldUsername name; - - public UsernameListEntry(User.OldUsername name) { - visibility = new ButtonWidget(1, 0, 0, 100, 20, I18n.translate("api.account.usernames.public", name.isPub())); - delete = new ButtonWidget(1, 0, 0, 100, 20, I18n.translate("api.account.usernames.delete")); - this.name = name; - } - - @Override - public void render(int index, int x, int y, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered) { - int deleteX = UsernameListWidget.this.getScrollbarPosition() - delete.getWidth() - 10; - delete.x = deleteX; - delete.y = y - 2; - visibility.x = deleteX - visibility.getWidth() - 5; - visibility.y = y - 2; - delete.render(minecraft, mouseX, mouseY); - visibility.render(minecraft, mouseX, mouseY); - textRenderer.drawWithShadow(name.getName(), x, y + entryHeight / 2f - 9 / 2f, -1); - } - - @Override - public void renderOutOfBounds(int i, int j, int k) { - - } - - @Override - public boolean mouseClicked(int i, int mouseX, int mouseY, int l, int m, int n) { - if (delete.mouseClicked(minecraft, mouseX, mouseY)) { - minecraft.openScreen(new ConfirmScreen((b, un) -> { - if (b) { - AccountUsernameRequest.delete(name.getName()).thenRun(() -> - UsernameListWidget.this.entries.remove(this)); - } - minecraft.openScreen(UsernameManagementScreen.this); - }, I18n.translate("api.account.confirm_deletion"), - I18n.translate("api.account.usernames.delete.desc"), 0)); - return true; - } else if (visibility.mouseClicked(minecraft, mouseX, mouseY)) { - name.setPub(!name.isPub()); - visibility.message = I18n.translate("api.account.usernames.public", name.isPub()); - AccountUsernameRequest.post(name.getName(), name.isPub()); - return true; - } - return false; - } - - @Override - public void mouseReleased(int i, int j, int k, int l, int m, int n) { - - } - } - } -} diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/chat/ChannelInvitesScreen.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/api/chat/ChannelInvitesScreen.java deleted file mode 100644 index e6df87d32..000000000 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/chat/ChannelInvitesScreen.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api.chat; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CompletableFuture; - -import io.github.axolotlclient.api.requests.ChannelRequest; -import io.github.axolotlclient.api.types.ChannelInvite; -import io.github.axolotlclient.api.util.UUIDHelper; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.gui.widget.ButtonWidget; -import net.minecraft.client.gui.widget.EntryListWidget; -import net.minecraft.client.resource.language.I18n; -import net.minecraft.text.Formatting; - -public class ChannelInvitesScreen extends Screen { - private final Screen parent; - private ButtonWidget acceptButton; - private ButtonWidget denyButton; - private InvitesListWidget invites; - private final String title; - - public ChannelInvitesScreen(Screen parent) { - super(); - this.title = I18n.translate("api.channels.invites"); - this.parent = parent; - } - - @Override - public void init() { - invites = new InvitesListWidget(minecraft, height, 33, width, height - 88, 25); - - - buttons.add(acceptButton = new ButtonWidget(1, width / 2 - 75, height - 55 / 2 - 2 - 20, 73, 20, I18n.translate("api.channels.invite.accept"))); - buttons.add(denyButton = new ButtonWidget(2, width / 2 + 2, height - 55 / 2 - 2 - 20, 73, 20, I18n.translate("api.channels.invite.ignore"))); - buttons.add(new ButtonWidget(0, width / 2 - 75, height - 55 / 2 + 2, 150, 20, I18n.translate("gui.back"))); - - updateButtons(); - } - - @Override - public void render(int mouseX, int mouseY, float delta) { - invites.render(mouseX, mouseY, delta); - super.render(mouseX, mouseY, delta); - drawCenteredString(textRenderer, title, width / 2, 33 / 2, -1); - } - - @Override - protected void mouseClicked(int i, int j, int k) { - invites.mouseClicked(i, j, k); - super.mouseClicked(i, j, k); - } - - @Override - public void handleMouse() { - invites.handleMouse(); - super.handleMouse(); - } - - @Override - protected void buttonClicked(ButtonWidget buttonWidget) { - if (buttonWidget.id == 0) { - minecraft.openScreen(parent); - } else if (buttonWidget.id == 1) { - if (invites.getSelected() != null) { - buttonWidget.active = false; - ChannelRequest.acceptChannelInvite(invites.getSelected().invite).thenRun(() -> minecraft.executeTask(() -> init(minecraft, width, height))); - } - } else if (buttonWidget.id == 2) { - if (invites.getSelected() != null) { - buttonWidget.active = false; - ChannelRequest.ignoreChannelInvite(invites.getSelected().invite).thenRun(() -> minecraft.executeTask(() -> init(minecraft, width, height))); - } - } - } - - private void updateButtons() { - denyButton.active = acceptButton.active = invites.getSelected() != null; - } - - private class InvitesListWidget extends EntryListWidget { - - private final List entries = new ArrayList<>(); - private int selectedEntry = -1; - - public InvitesListWidget(Minecraft client, int screenHeight, int y, int width, int height, int entryHeight) { - super(client, width, screenHeight, y, y + height, entryHeight); - ChannelRequest.getChannelInvites().thenAccept(list -> - list.stream().map(InvitesListEntry::new).forEach(entries::add)); - } - - @Override - public boolean mouseClicked(int mouseX, int mouseY, int button) { - boolean bl = super.mouseClicked(mouseX, mouseY, button); - updateButtons(); - return bl; - } - - @Override - public Entry getEntry(int i) { - return entries.get(i); - } - - @Override - protected int size() { - return entries.size(); - } - - public InvitesListEntry getSelected() { - if (selectedEntry >= 0) { - return entries.get(selectedEntry); - } - return null; - } - - private class InvitesListEntry implements Entry { - - private final ChannelInvite invite; - private final CompletableFuture fromName; - - public InvitesListEntry(ChannelInvite invite) { - this.invite = invite; - this.fromName = UUIDHelper.tryGetUsernameAsync(invite.fromUuid()); - } - - @Override - public void renderOutOfBounds(int i, int j, int k) { - - } - - @Override - public void render(int index, int x, int y, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovere) { - drawString(textRenderer, I18n.translate("api.channels.invite.name", invite.channelName()), x + 2, y + 2, -1); - if (fromName.isDone()) { - drawString(textRenderer, Formatting.ITALIC + I18n.translate("api.channels.invite.from", fromName.join()), x + 15, y + entryHeight - textRenderer.fontHeight - 1, 0x808080); - } - } - - @Override - public boolean mouseClicked(int i, int j, int k, int l, int m, int n) { - selectedEntry = i; - return true; - } - - @Override - public void mouseReleased(int i, int j, int k, int l, int m, int n) { - - } - } - } -} diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/chat/ChannelSettingsScreen.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/api/chat/ChannelSettingsScreen.java deleted file mode 100644 index 7b5520327..000000000 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/chat/ChannelSettingsScreen.java +++ /dev/null @@ -1,249 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * Copyright © 2026 DSNS - * - * This file is part of DolphinClient, a fork of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api.chat; - -import java.util.Arrays; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Supplier; - -import io.github.axolotlclient.DolphinClientCommon; -import io.github.axolotlclient.AxolotlClientConfig.api.util.Colors; -import io.github.axolotlclient.AxolotlClientConfig.impl.options.DoubleOption; -import io.github.axolotlclient.AxolotlClientConfig.impl.ui.ButtonWidget; -import io.github.axolotlclient.AxolotlClientConfig.impl.ui.ClickableWidget; -import io.github.axolotlclient.AxolotlClientConfig.impl.ui.Element; -import io.github.axolotlclient.AxolotlClientConfig.impl.ui.TextFieldWidget; -import io.github.axolotlclient.AxolotlClientConfig.impl.ui.vanilla.widgets.SliderWidget; -import io.github.axolotlclient.AxolotlClientConfig.impl.ui.vanilla.widgets.VanillaButtonWidget; -import io.github.axolotlclient.AxolotlClientConfig.impl.util.DrawUtil; -import io.github.axolotlclient.api.requests.ChannelRequest; -import io.github.axolotlclient.api.types.Channel; -import io.github.axolotlclient.api.types.Persistence; -import io.github.axolotlclient.api.util.UUIDHelper; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.render.platform.GlStateManager; -import net.minecraft.client.resource.language.I18n; -import net.minecraft.resource.Identifier; - -public class ChannelSettingsScreen extends io.github.axolotlclient.AxolotlClientConfig.impl.ui.Screen { - private final Screen parent; - private final Channel channel; - private String tooltip; - private Element tooltipElement; - - protected ChannelSettingsScreen(Screen parent, Channel channel) { - super(I18n.translate("api.channel.configure")); - this.parent = parent; - this.channel = channel; - } - - @Override - public void render(int mouseX, int mouseY, float delta) { - super.render(mouseX, mouseY, delta); - drawCenteredString(textRenderer, title, width / 2, 36 / 2 - textRenderer.fontHeight / 2, -1); - - if (tooltip != null) { - tooltipElement = hoveredElement(mouseX, mouseY).orElse(null); - renderTooltip(textRenderer.split(tooltip, 170), mouseX, mouseY); - } - if (hoveredElement(mouseX, mouseY).map(e -> e != tooltipElement).orElse(true)) { - tooltip = null; - } - } - - @Override - public void init() { - int leftColX = width / 2 - 4 - 150; - int leftColYStep = textRenderer.fontHeight + 20 - textRenderer.fontHeight + 8 - 5 + 5; - int leftColY = 36 + 5 + 30; - int rightColX = width / 2 + 4; - int rightColYStep = 20 + 8; - int rightColY = 36 + 30; - - var nameField = new TextFieldWidget(textRenderer, rightColX, rightColY, 150, 20, ""); - nameField.setText(channel.getRealName()); - addDrawableChild(text("api.chat.groups.name", leftColX, leftColY)); - addDrawableChild(nameField); - rightColY += rightColYStep; - leftColY += leftColYStep; - - AtomicReference> count = new AtomicReference<>(); - AtomicReference> duration = new AtomicReference<>(); - - AtomicReference> countDisabler = new AtomicReference<>(); - AtomicReference> durationDisabler = new AtomicReference<>(); - var persistence = new VanillaButtonWidget(rightColX, rightColY, 150, 20, "", a -> { - }) { - final Persistence.Type[] persistenceValues = Persistence.Type.values(); - int current = channel.getPersistence().type().ordinal(); - - @Override - public void drawWidget(int mouseX, int mouseY, float delta) { - setMessage(I18n.translate("api.chat.persistence." + persistenceValues[current].getId())); - super.drawWidget(mouseX, mouseY, delta); - } - - @Override - public void onPress() { - current++; - if (current >= persistenceValues.length) { - current = 0; - } - Persistence.Type object = persistenceValues[current]; - switch (object) { - case COUNT_DURATION -> { - countDisabler.get().accept(true); - countDisabler.get().accept(true); - } - case DURATION -> { - durationDisabler.get().accept(true); - countDisabler.get().accept(false); - } - case COUNT -> { - countDisabler.get().accept(true); - durationDisabler.get().accept(false); - } - case CHANNEL -> { - countDisabler.get().accept(false); - durationDisabler.get().accept(false); - } - } - } - - public Persistence.Type getValue() { - return persistenceValues[current]; - } - }; - rightColY += rightColYStep; - addDrawableChild(text("api.chat.groups.persistence", leftColX, leftColY)); - leftColY += leftColYStep; - addDrawableChild(persistence); - addDrawableChild(text("api.chat.groups.persistence.count", "api.chat.groups.persistence.count.tooltip", leftColX, leftColY)); - leftColY += leftColYStep; - countDisabler.set(sliderAssembly(rightColX, rightColY, val -> (int) (val * 100d), count::set, channel.getPersistence().count() / 100d)); - rightColY += rightColYStep; - addDrawableChild(text("api.chat.groups.persistence.duration", "api.chat.groups.persistence.duration.tooltip", leftColX, leftColY)); - leftColY += leftColYStep; - durationDisabler.set(sliderAssembly(rightColX, rightColY, val -> (long) (val * 100d), duration::set, channel.getPersistence().duration() / 100d)); - rightColY += rightColYStep; - countDisabler.get().accept(false); - durationDisabler.get().accept(false); - - var namesInput = addDrawableChild(new TextFieldWidget(textRenderer, rightColX, rightColY, 150, 20, "")); - addDrawableChild(text("api.chat.groups.participants", leftColX, leftColY)); - - int footerY = height - 36 / 2 - 20 / 2; - addDrawableChild(new VanillaButtonWidget(width / 2 - 150 - 4, footerY, 150, 20, I18n.translate("gui.cancel"), widget -> minecraft.openScreen(parent))); - addDrawableChild(new VanillaButtonWidget(width / 2 + 4, footerY, 150, 20, I18n.translate("gui.done"), widget -> { - ChannelRequest.updateChannel(channel.getId(), nameField.getText(), - Persistence.of(persistence.getValue(), count.get().get(), duration.get().get()), - Arrays.stream(namesInput.getText().split(",")).filter(s -> !s.isEmpty()).map(UUIDHelper::ensureUuid) - .map(CompletableFuture::join).toArray(String[]::new)); - minecraft.openScreen(parent); - })); - } - - private Consumer sliderAssembly(int x, int y, Function valueFunc, Consumer> value, double initialValue) { - - AtomicReference currentVal = new AtomicReference<>(); - DoubleOption opt = new DoubleOption("", initialValue, d -> currentVal.set(valueFunc.apply(d)), 0d, 1d); - var slider = new SliderWidget<>(x, y, 128, 20, opt); - opt.set(initialValue); - value.accept(currentVal::get); - var text = new TextFieldWidget(textRenderer, x, y, 128, 20, ""); - var textButton = new ButtonWidget(x + 130, y, 20, 20, I18n.translate("slider.text_input"), w -> { - if (slider.visible) { - text.setX(slider.getX()); - text.setY(slider.getY()); - text.setWidth(slider.getWidth()); - text.setText(String.valueOf(currentVal.get())); - } else { - try { - opt.set(Double.parseDouble(text.getText())); - } catch (Exception ignored) { - } - } - slider.visible = !slider.visible; - text.visible = !slider.visible; - }) { - @Override - public void drawWidget(int mouseX, int mouseY, float delta) { - this.client.getTextureManager().bind(WIDGETS_LOCATION); - GlStateManager.color4f(1.0F, 1.0F, 1.0F, 1.0F); - int k = this.active ? (this.hovered ? 2 : 1) : 0; - GlStateManager.enableBlend(); - GlStateManager.blendFuncSeparate(770, 771, 1, 0); - GlStateManager.blendFunc(770, 771); - this.drawTexture(this.getX(), this.getY(), 0, 46 + k * 20, this.getWidth() / 2, this.getHeight()); - this.drawTexture(this.getX() + this.getWidth() / 2, this.getY(), 200 - this.getWidth() / 2, 46 + k * 20, this.getWidth() / 2, this.getHeight()); - client.getTextureManager().bind(new Identifier(DolphinClientCommon.MODID, "textures/gui/sprites/cursor.png")); - drawTexture(getX() + getWidth() / 2 - 4, getY() + getHeight() / 2 - 4, - 8, 8, 8, 8, 8, 8, 8, 8); - - } - }; - text.visible = false; - addDrawableChild(text); - addDrawableChild(textButton); - addDrawableChild(slider); - return val -> slider.active = textButton.active = text.active = val; - } - - private ClickableWidget text(String translationKey, int x, int y) { - String text = I18n.translate(translationKey); - return new ClickableWidget(x, y, 150, textRenderer.fontHeight, text) { - - @Override - public void render(int mouseX, int mouseY, float delta) { - DrawUtil.drawScrollingText(getMessage(), getX(), getY(), getWidth(), getHeight(), Colors.WHITE); - } - - public boolean mouseClicked(double mouseX, double mouseY, int button) { - return false; - } - }; - } - - private ClickableWidget text(String translationKey, String tooltipKey, int x, int y) { - String text = I18n.translate(translationKey); - return new ClickableWidget(x, y, 150, textRenderer.fontHeight, text) { - - @Override - public void drawWidget(int mouseX, int mouseY, float delta) { - DrawUtil.drawScrollingText(getMessage(), getX(), getY(), getWidth(), getHeight(), Colors.WHITE); - if (isHovered()) { - ChannelSettingsScreen.this.tooltip = I18n.translate(tooltipKey); - } - } - - @Override - public boolean mouseClicked(double mouseX, double mouseY, int button) { - return false; - } - }; - } -} diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/chat/ChatListScreen.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/api/chat/ChatListScreen.java deleted file mode 100644 index 8b58dc6a1..000000000 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/chat/ChatListScreen.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api.chat; - -import io.github.axolotlclient.api.ContextMenuContainer; -import io.github.axolotlclient.api.ContextMenuScreen; -import io.github.axolotlclient.api.requests.ChannelRequest; -import io.github.axolotlclient.api.types.Channel; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.gui.widget.ButtonWidget; -import net.minecraft.client.resource.language.I18n; - -public class ChatListScreen extends Screen implements ContextMenuScreen { - - private final Screen parent; - private final ContextMenuContainer container; - private ChatListWidget dms; - private ChatListWidget groups; - - public ChatListScreen(Screen parent) { - super(); - this.parent = parent; - container = new ContextMenuContainer(); - } - - @Override - public void render(int mouseX, int mouseY, float delta) { - drawBackgroundTexture(0); - dms.render(mouseX, mouseY, delta); - groups.render(mouseX, mouseY, delta); - super.render(mouseX, mouseY, delta); - container.render(minecraft, mouseX, mouseY); - - drawCenteredString(minecraft.textRenderer, I18n.translate("api.chats"), width / 2, 20, -1); - drawCenteredString(minecraft.textRenderer, I18n.translate("api.chat.dms"), width / 2 + 80, 40, -1); - drawCenteredString(minecraft.textRenderer, I18n.translate("api.chat.groups"), width / 2 - 80, 40, -1); - } - - @Override - public void init() { - groups = new ChatListWidget(this, width, height, width / 2 - 155, 55, 150, height - 105, c -> !c.isDM()); - dms = new ChatListWidget(this, width, height, width / 2 + 5, 55, 150, height - 105, Channel::isDM); - - buttons.add(new ButtonWidget(0, this.width / 2 + 5, this.height - 40, 150, 20, I18n.translate("gui.back"))); - buttons.add(new ButtonWidget(1, this.width / 2 - 155, this.height - 40, 150, 20, - I18n.translate("api.chat.groups.create"))); - ChannelRequest.getChannelList().thenAccept(list -> { - groups.addChannels(list); - dms.addChannels(list); - }); - } - - @Override - public ContextMenuContainer getMenuContainer() { - return container; - } - - protected void buttonClicked(ButtonWidget buttonWidget) { - if (buttonWidget.id == 0) { - minecraft.openScreen(parent); - } else if (buttonWidget.id == 1) { - minecraft.openScreen(new CreateChannelScreen(this)); - } - } - - @Override - public Screen getParent() { - return parent; - } - - protected void mouseClicked(int i, int j, int k) { - if (container.mouseClicked(i, j, k)) { - return; - } - super.mouseClicked(i, j, k); - dms.mouseClicked(i, j, k); - groups.mouseClicked(i, j, k); - } - - @Override - public Screen getSelf() { - return this; - } - - public void handleMouse() { - super.handleMouse(); - dms.handleMouse(); - groups.handleMouse(); - } -} diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/chat/ChatListWidget.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/api/chat/ChatListWidget.java deleted file mode 100644 index 710895cb4..000000000 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/chat/ChatListWidget.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api.chat; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Predicate; - -import io.github.axolotlclient.api.API; -import io.github.axolotlclient.api.ContextMenu; -import io.github.axolotlclient.api.ContextMenuScreen; -import io.github.axolotlclient.api.requests.ChannelRequest; -import io.github.axolotlclient.api.types.Channel; -import io.github.axolotlclient.api.types.Relation; -import io.github.axolotlclient.util.DrawUtil; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.screen.ConfirmScreen; -import net.minecraft.client.gui.widget.ButtonWidget; -import net.minecraft.client.gui.widget.EntryListWidget; -import net.minecraft.client.render.TextRenderer; -import net.minecraft.client.resource.language.I18n; - -public class ChatListWidget extends EntryListWidget { - - protected final ContextMenuScreen screen; - private final Predicate predicate; - - private final List entries = new ArrayList<>(); - - public ChatListWidget(ContextMenuScreen screen, int screenWidth, int screenHeight, int x, int y, int width, int height, Predicate filter) { - super(Minecraft.getInstance(), width, height, y, y + height, 25); - setX(x); - this.screen = screen; - this.predicate = filter; - } - - public void addChannels(List channels) { - channels.stream().filter(predicate).sorted().forEach(c -> entries.add(0, new ChatListEntry(c))); - scrollAmount = 0; - } - - public void setActiveChannel(Channel channel) { - entries.stream().filter(e -> e.channel.equals(channel)) - .findFirst().ifPresent(c -> c.widget.active = false); - } - - @Override - public int getRowWidth() { - return width - 8; - } - - public ChatListWidget(ContextMenuScreen screen, int screenWidth, int screenHeight, int x, int y, int width, int height) { - this(screen, screenWidth, screenHeight, x, y, width, height, c -> true); - } - - @Override - protected int getScrollbarPosition() { - return this.minX + this.width / 2 - this.getRowWidth() / 2 + 2 + width - 8; - } - - @Override - protected void renderList(int i, int j, int k, int l) { - DrawUtil.enableScissor(minX, minY, maxX, maxY); - super.renderList(i, j, k, l); - DrawUtil.disableScissor(); - } - - @Override - public Entry getEntry(int i) { - return entries.get(i); - } - - @Override - protected int size() { - return entries.size(); - } - - public class ChatListEntry implements Entry { - - private final Channel channel; - private final ButtonWidget widget; - - public ChatListEntry(Channel channel) { - this.channel = channel; - widget = new ButtonWidget(-1, 0, 0, getRowWidth(), 20, channel.getName()) { - @Override - public void drawCenteredString(TextRenderer textRenderer, String string, int i, int j, int k) { - DrawUtil.drawScrollableText(textRenderer, string, x + 2, y, x + width - 2, y + height, k); - } - }; - } - - @Override - public void renderOutOfBounds(int i, int j, int k) { - - } - - @Override - public void render(int index, int x, int y, int rowWidth, int rowHeight, int mouseX, int mouseY, boolean hovered) { - widget.x = (x); - widget.y = (y); - widget.render(minecraft, mouseX, mouseY); - } - - @Override - public boolean mouseClicked(int index, int mouseX, int mouseY, int button, int m, int n) { - if (widget.mouseClicked(minecraft, mouseX, mouseY)) { - if (button == 0) { - minecraft.openScreen(new ChatScreen(screen.getParent(), channel)); - return true; - } else if (button == 1) { - ContextMenu.Builder builder = ContextMenu.builder() - .title(channel.getName()) - .spacer() - .entry(I18n.translate("api.channel.configure"), w -> minecraft.openScreen(new ChannelSettingsScreen(ChatListWidget.this.screen.getSelf(), channel))) - .spacer(); - if (channel.getOwner().equals(API.getInstance().getSelf())) { - builder.entry(I18n.translate("api.channel.delete"), w -> minecraft.openScreen(new ConfirmScreen((bl, i) -> { - if (bl) { - ChannelRequest.leaveOrDeleteChannel(channel).whenComplete((o, throwable) -> minecraft.executeTask(() -> minecraft.openScreen(screen.getSelf()))); - } - }, I18n.translate("api.channels.delete.confirm"), I18n.translate("api.channels.delete.confirm.desc", channel.getName()), 0))); - } else { - builder.entry(I18n.translate("api.channel.leave"), w -> minecraft.openScreen(new ConfirmScreen((bl, i) -> { - if (bl) { - ChannelRequest.leaveOrDeleteChannel(channel).whenComplete((o, throwable) -> minecraft.executeTask(() -> minecraft.openScreen(screen.getSelf()))); - } - }, I18n.translate("api.channels.leave.confirm"), channel.getOwner().getRelation() == Relation.FRIEND ? - I18n.translate("api.channels.leave.confirm.desc_add", channel.getName()) : - I18n.translate("api.channels.leave.confirm.desc_invite", channel.getName()), 0))); - } - ChatListWidget.this.screen.setContextMenu(builder.build()); - return true; - } - } - return false; - } - - @Override - public void mouseReleased(int i, int j, int k, int l, int m, int n) { - - } - } -} diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/chat/ChatScreen.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/api/chat/ChatScreen.java deleted file mode 100644 index fefd9f6ca..000000000 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/chat/ChatScreen.java +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api.chat; - -import io.github.axolotlclient.api.API; -import io.github.axolotlclient.api.ContextMenuContainer; -import io.github.axolotlclient.api.ContextMenuScreen; -import io.github.axolotlclient.api.handlers.ChatHandler; -import io.github.axolotlclient.api.requests.ChannelRequest; -import io.github.axolotlclient.api.types.Channel; -import io.github.axolotlclient.util.DrawUtil; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.gui.widget.ButtonWidget; -import net.minecraft.client.gui.widget.TextFieldWidget; -import net.minecraft.client.render.TextRenderer; -import net.minecraft.client.resource.language.I18n; -import org.lwjgl.input.Keyboard; - -public class ChatScreen extends Screen implements ContextMenuScreen { - - private final Channel channel; - private final Screen parent; - private final ContextMenuContainer contextMenu = new ContextMenuContainer(); - private ChatWidget widget; - private ChatListWidget chatListWidget; - private ChatUserListWidget users; - private TextFieldWidget input; - - public ChatScreen(Screen parent, Channel channel) { - super(); - this.channel = channel; - this.parent = parent; - } - - @Override - public void render(int mouseX, int mouseY, float delta) { - drawBackgroundTexture(0); - - if (users != null) { - users.render(mouseX, mouseY, delta); - } - - chatListWidget.render(mouseX, mouseY, delta); - widget.render(mouseX, mouseY, delta); - input.render(); - - super.render(mouseX, mouseY, delta); - - drawCenteredString(this.textRenderer, channel.getName(), this.width / 2, 20, 16777215); - - contextMenu.render(minecraft, mouseX, mouseY); - } - - @Override - public void init() { - chatListWidget = new ChatListWidget(this, width, height, 0, 30, 55, height - 90); - ChannelRequest.getChannelList().thenAccept(chatListWidget::addChannels).thenRun(() -> chatListWidget.setActiveChannel(channel)); - - widget = new ChatWidget(channel, 65, 30, width - 155, height - 90, this); - - users = new ChatUserListWidget(this, minecraft, 80, height - 20, 30, height - 60, 25); - users.setX(width - 80); - users.setUsers(channel.getAllUsers(), channel); - - input = new TextFieldWidget(5, minecraft.textRenderer, width / 2 - 150, height - 50, - 300, 20) { - - @Override - public boolean keyPressed(char c, int i) { - if (i == Keyboard.KEY_RETURN && !getText().isEmpty()) { - ChatHandler.getInstance().sendMessage(channel, getText()); - setText(""); - return true; - } - return super.keyPressed(c, i); - } - - @Override - public void render() { - super.render(); - if (getText().isEmpty() && !isFocused()) { - drawString(textRenderer, I18n.translate(channel.isDM() ? "api.chat.messageUser" : "api.chat.messageGroup", channel.getName()), - x + 2, y + 6, -8355712); - } - } - }; - input.setMaxLength(1024); - - if (channel.getOwner().equals(API.getInstance().getSelf())) { - buttons.add(new ButtonWidget(2, width - 60, 5, 50, 20, I18n.translate("api.channel.configure")) { - @Override - public void drawCenteredString(TextRenderer textRenderer, String string, int i, int j, int k) { - DrawUtil.drawScrollableText(textRenderer, string, x + 2, y, x + width - 2, y + height, k); - } - }); - } - - this.buttons.add(new ButtonWidget(1, this.width / 2 - 75, this.height - 28, 150, 20, - I18n.translate("gui.back"))); - Keyboard.enableRepeatEvents(true); - } - - @Override - public void tick() { - input.tick(); - } - - @Override - public void removed() { - if (widget != null) { - widget.remove(); - } - Keyboard.enableRepeatEvents(false); - } - - @Override - protected void buttonClicked(ButtonWidget buttonWidget) { - if (buttonWidget.id == 1) { - this.minecraft.openScreen(this.parent); - } else if (buttonWidget.id == 2) { - minecraft.openScreen(new ChannelSettingsScreen(this, channel)); - } - } - - @Override - public void mouseClicked(int mouseX, int mouseY, int button) { - if (contextMenu.getMenu() != null) { - if (contextMenu.mouseClicked(mouseX, mouseY, button)) { - return; - } - contextMenu.removeMenu(); - } - super.mouseClicked(mouseX, mouseY, button); - widget.mouseClicked(mouseX, mouseY, button); - input.mouseClicked(mouseX, mouseY, button); - users.mouseClicked(mouseX, mouseY, button); - chatListWidget.mouseClicked(mouseX, mouseY, button); - } - - @Override - public void handleMouse() { - super.handleMouse(); - - widget.handleMouse(); - users.handleMouse(); - chatListWidget.handleMouse(); - } - - @Override - protected void keyPressed(char c, int i) { - super.keyPressed(c, i); - input.keyPressed(c, i); - } - - @Override - public ContextMenuContainer getMenuContainer() { - return contextMenu; - } - - @Override - public Screen getParent() { - return parent; - } - - @Override - public Screen getSelf() { - return this; - } -} diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/chat/ChatUserListWidget.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/api/chat/ChatUserListWidget.java deleted file mode 100644 index a3bbb8ddb..000000000 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/chat/ChatUserListWidget.java +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api.chat; - -import java.util.ArrayList; -import java.util.List; - -import io.github.axolotlclient.api.API; -import io.github.axolotlclient.api.ContextMenu; -import io.github.axolotlclient.api.requests.ChannelRequest; -import io.github.axolotlclient.api.requests.FriendRequest; -import io.github.axolotlclient.api.types.Channel; -import io.github.axolotlclient.api.types.Relation; -import io.github.axolotlclient.api.types.User; -import io.github.axolotlclient.api.util.AlphabeticalComparator; -import io.github.axolotlclient.modules.auth.Auth; -import io.github.axolotlclient.util.DrawUtil; -import lombok.Getter; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiElement; -import net.minecraft.client.gui.widget.EntryListWidget; -import net.minecraft.client.render.platform.GlStateManager; - -public class ChatUserListWidget extends EntryListWidget { - - private final List entries = new ArrayList<>(); - private final ChatScreen screen; - private int selectedEntry = -1; - - public ChatUserListWidget(ChatScreen screen, Minecraft client, int width, int height, int top, int bottom, int entryHeight) { - super(client, width, height, top, bottom, entryHeight); - this.screen = screen; - } - - public void setUsers(List users, Channel channel) { - users.stream().sorted((u1, u2) -> new AlphabeticalComparator().compare(u1.getName(), u2.getName())).forEach(user -> addEntry(new UserListEntry(user, channel))); - } - - @Override - protected int getScrollbarPosition() { - return this.minX + this.width / 2 - this.getRowWidth() / 2 + 2 + width - 8; - } - - @Override - protected int size() { - return entries.size(); - } - - @Override - public int getRowWidth() { - return width - 5; - } - - public void addEntry(UserListEntry entry) { - entries.add(entry.init(screen)); - } - - @Override - public Entry getEntry(int i) { - return entries.get(i); - } - - @Override - protected boolean isEntrySelected(int i) { - return i == selectedEntry; - } - - @Override - protected void renderDecorations(int i, int j) { - super.renderDecorations(i, j); - GlStateManager.enableTexture(); - minecraft.getTextureManager().bind(GuiElement.BACKGROUND_LOCATION); - - } - - public class UserListEntry extends GuiElement implements EntryListWidget.Entry { - - @Getter - private final User user; - private final Minecraft client; - private final Channel channel; - private long time; - private ChatScreen screen; - - public UserListEntry(User user, Channel channel) { - this.client = Minecraft.getInstance(); - this.user = user; - this.channel = channel; - } - - public UserListEntry init(ChatScreen screen) { - this.screen = screen; - return this; - } - - @Override - public void renderOutOfBounds(int i, int j, int k) { - - } - - @Override - public void render(int index, int x, int y, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered) { - if (hovered) { - fill(x - 2, y - 1, x + entryWidth - 3, y + entryHeight + 1, 0x55ffffff); - } - DrawUtil.drawScrollableText(client.textRenderer, user.getName(), x + 3 + entryHeight, y + 1, x + entryWidth - 6, y + 1 + client.textRenderer.fontHeight + 2, -1); - DrawUtil.drawScrollableText(client.textRenderer, user.getStatus().getTitle(), x + 3 + entryHeight, - y + 12, x + entryWidth - 6, y + 12 + client.textRenderer.fontHeight + 2, 8421504); - - client.getTextureManager().bind(Auth.getInstance().getSkinTexture(user.getUuid(), user.getName())); - GlStateManager.enableBlend(); - GlStateManager.color3f(1, 1, 1); - drawTexture(x, y, 8, 8, 8, 8, entryHeight, entryHeight, 64, 64); - drawTexture(x, y, 40, 8, 8, 8, entryHeight, entryHeight, 64, 64); - GlStateManager.disableBlend(); - } - - @Override - public boolean mouseClicked(int index, int mouseX, int mouseY, int button, int x, int y) { - ChatUserListWidget.this.selectedEntry = index; - if (button == 0) { // left click - /*if (Minecraft.getTime() - this.time < 250L && client.world == null) { // left *double* click - - }*/ - this.time = Minecraft.getTime(); - } else if (button == 1) { // right click - - if (!user.equals(API.getInstance().getSelf())) { - ContextMenu.Builder menu = ContextMenu.builder().title(user.getName()) - .spacer(); - if (!channel.isDM()) { - menu.entry("api.friends.chat", buttonWidget -> ChannelRequest.getOrCreateDM(user) - .whenCompleteAsync((channel, throwable) -> client.executeTask(() -> - client.openScreen(new ChatScreen(screen.getParent(), channel))))).spacer(); - } - if (user.getRelation() != Relation.BLOCKED) { - if (user.getRelation() != Relation.FRIEND) { - menu.entry("api.friends.add", b -> FriendRequest.getInstance().addFriend(user.getUuid())).spacer(); - } - menu.entry("api.users.block", buttonWidget -> - FriendRequest.getInstance().blockUser(user)); - } else { - menu.entry("api.users.unblock", buttonWidget -> - FriendRequest.getInstance().unblockUser(user)); - } - if (channel.getOwner().equals(API.getInstance().getSelf())) { - menu.spacer().entry("api.channel.remove_user", b -> ChannelRequest.removeUserFromChannel(channel, user)); - } - screen.setContextMenu(menu.build()); - return true; - } - } - - return false; - } - - @Override - public void mouseReleased(int i, int j, int k, int l, int m, int n) { - - } - } -} diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/chat/ChatWidget.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/api/chat/ChatWidget.java deleted file mode 100644 index e8f335775..000000000 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/chat/ChatWidget.java +++ /dev/null @@ -1,295 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api.chat; - -import java.time.Instant; -import java.time.ZoneId; -import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; - -import io.github.axolotlclient.api.API; -import io.github.axolotlclient.api.ChatsSidebar; -import io.github.axolotlclient.api.ContextMenu; -import io.github.axolotlclient.api.ContextMenuScreen; -import io.github.axolotlclient.api.handlers.ChatHandler; -import io.github.axolotlclient.api.requests.ChannelRequest; -import io.github.axolotlclient.api.types.Channel; -import io.github.axolotlclient.api.types.ChatMessage; -import io.github.axolotlclient.modules.auth.Auth; -import io.github.axolotlclient.util.DrawUtil; -import io.github.axolotlclient.util.ClientColors; -import lombok.Getter; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.screen.ConfirmScreen; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.gui.widget.EntryListWidget; -import net.minecraft.client.render.platform.GlStateManager; -import net.minecraft.client.resource.language.I18n; -import net.minecraft.text.LiteralText; -import net.minecraft.text.Style; - -public class ChatWidget extends EntryListWidget { - - private final List messages = new ArrayList<>(); - private final List entries = new ArrayList<>(); - private final Channel channel; - private final Minecraft client; - private final ContextMenuScreen screen; - private int selectedEntry = -1; - - public ChatWidget(Channel channel, int x, int y, int width, int height, ContextMenuScreen screen) { - super(Minecraft.getInstance(), width, height, y, y + height, 13); - this.channel = channel; - this.client = Minecraft.getInstance(); - super.setX(x + 5); - - setHeader(false, 0); - this.screen = screen; - channel.getMessages().forEach(this::addMessage); - - ChatHandler.getInstance().setMessagesConsumer(chatMessages -> chatMessages.forEach(this::addMessage)); - ChatHandler.getInstance().setMessageConsumer(this::addMessage); - ChatHandler.getInstance().setEnableNotifications(message -> !message.channelId().equals(channel.getId())); - - scroll(getMaxScroll()); - renderSelectionHighlight = false; - } - - @Override - protected int getScrollbarPosition() { - return minX + width - 6; - } - - @Override - public int getRowWidth() { - return width - 60; - } - - public int getX() { - return minX; - } - - protected boolean isEntrySelected(int i) { - return i == selectedEntry; - } - - private void addMessage(ChatMessage message) { - List list = client.textRenderer.split(message.content(), getRowWidth()); - - boolean scrollToBottom = getScrollAmount() == getMaxScroll(); - - if (!messages.isEmpty()) { - ChatMessage prev = messages.get(messages.size() - 1); - if (!(prev.sender().equals(message.sender()) && prev.senderDisplayName().equals(message.senderDisplayName()))) { - entries.add(new NameChatLine(message)); - } else { - if (message.timestamp().getEpochSecond() - prev.timestamp().getEpochSecond() > 150) { - entries.add(new NameChatLine(message)); - } - } - } else { - entries.add(new NameChatLine(message)); - } - - list.forEach(t -> entries.add(new ChatLine(t, message))); - messages.add(message); - - entries.sort(Comparator.comparingLong(c -> c.getOrigin().timestamp().getEpochSecond())); - - if (scrollToBottom) { - scroll(getMaxScroll()); - } - messages.sort(Comparator.comparingLong(value -> value.timestamp().getEpochSecond())); - } - - private void loadMessages() { - long before; - if (!messages.isEmpty()) { - before = messages.get(0).timestamp().getEpochSecond(); - } else { - before = Instant.now().getEpochSecond(); - } - ChatHandler.getInstance().getMessagesBefore(channel, before); - } - - @Override - public void scroll(int i) { - this.scrollAmount += (float) i; - if (scrollAmount < 0) { - loadMessages(); - } - this.capScrolling(); - this.mouseYStart = -2; - } - - public void remove() { - ChatHandler.getInstance().setMessagesConsumer(ChatHandler.DEFAULT_MESSAGES_CONSUMER); - ChatHandler.getInstance().setMessageConsumer(ChatHandler.DEFAULT_MESSAGE_CONSUMER); - ChatHandler.getInstance().setEnableNotifications(ChatHandler.DEFAULT); - } - - @Override - protected void renderList(int i, int j, int k, int l) { - DrawUtil.enableScissor(minX, minY, maxX, maxY); - super.renderList(i, j, k, l); - DrawUtil.disableScissor(); - } - - @Override - public void render(int mouseX, int mouseY, float delta) { - if (screen.getSelf() instanceof ChatsSidebar) { - if (this.visible) { - this.mouseX = mouseX; - this.mouseY = mouseY; - this.capScrolling(); - int m = this.minX + this.width / 2 - this.getRowWidth() / 2 + 2; - int n = this.minY + 4 - (int) this.scrollAmount; - - this.renderList(m, n, mouseX, mouseY); - } - } else { - super.render(mouseX, mouseY, delta); - } - } - - @Override - public Entry getEntry(int i) { - return entries.get(i); - } - - @Override - protected int size() { - return entries.size(); - } - - public class ChatLine extends DrawUtil implements Entry { - protected final Minecraft client = Minecraft.getInstance(); - @Getter - private final String content; - @Getter - private final ChatMessage origin; - - public ChatLine(String content, ChatMessage origin) { - super(); - this.content = content; - this.origin = origin; - } - - @Override - public void renderOutOfBounds(int i, int j, int k) { - - } - - @Override - public boolean mouseClicked(int index, int mouseX, int mouseY, int button, int x, int y) { - if (button == 0) { - ChatWidget.this.selectedEntry = index; - return true; - } - if (button == 1) { - ContextMenu.Builder builder = ContextMenu.builder() - .title(origin.sender().getName()) - .spacer(); - if (!origin.sender().equals(API.getInstance().getSelf())) { - builder.entry("api.friends.chat", buttonWidget -> ChannelRequest.getOrCreateDM(origin.sender()) - .whenCompleteAsync((channel, throwable) -> client.executeTask(() -> client.openScreen(new ChatScreen(screen.getParent(), channel))))) - .spacer(); - } - builder.entry("api.chat.report.message", buttonWidget -> { - Screen previous = client.screen; - client.openScreen(new ConfirmScreen((b, i) -> { - if (b) { - ChatHandler.getInstance().reportMessage(origin); - } - client.openScreen(previous); - }, I18n.translate("api.channels.confirm_report"), - I18n.translate("api.channels.confirm_report.desc", origin.content()), 0)); - }) - .spacer() - .entry("action.copy", buttonWidget -> Screen.setClipboard(origin.content())); - screen.setContextMenu(builder.build()); - return true; - } - return false; - } - - @Override - public void mouseReleased(int i, int j, int k, int l, int m, int n) { - - } - - protected void renderExtras(int x, int y, int mouseX, int mouseY) { - } - - @Override - public void render(int index, int x, int y, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered) { - for (int i = 0; i < entries.size(); i++) { - ChatLine l = entries.get(i); - if (l.getOrigin().equals(origin)) { - if (getEntryAt(mouseX, mouseY) == i) { - hovered = true; - break; - } - } - } - if (hovered && !screen.hasContextMenu()) { - fill(x - 2 - 22, y - 2, x + entryWidth + 20, y + entryHeight - 1, 0x33FFFFFF); - if (index < entries.size() - 1 && entries.get(index + 1).getOrigin().equals(origin)) { - fill(x - 2 - 22, y + entryHeight - 1, x + entryWidth + 20, y + entryHeight + 2, 0x33FFFFFF); - } - if ((index < entries.size() - 1 && !entries.get(index + 1).getOrigin().equals(origin)) || index == entries.size() - 1) { - fill(x - 2 - 22, y + entryHeight - 1, x + entryWidth + 20, y + entryHeight, 0x33FFFFFF); - } - } - renderExtras(x, y, mouseX, mouseY); - client.textRenderer.draw(content, x, y, -1); - } - } - - public class NameChatLine extends ChatLine { - - private final String formattedTime; - - public NameChatLine(ChatMessage message) { - super(new LiteralText(message.senderDisplayName()) - .setStyle(new Style().setBold(true)).getFormattedString(), message); - - DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("d/M/yyyy H:mm"); - formattedTime = DATE_FORMAT.format(message.timestamp().atZone(ZoneId.systemDefault())); - } - - @Override - protected void renderExtras(int x, int y, int mouseX, int mouseY) { - GlStateManager.disableBlend(); - GlStateManager.enableTexture(); - client.getTextureManager().bind(Auth.getInstance().getSkinTexture(getOrigin().sender().getUuid(), - getOrigin().sender().getName())); - drawTexture(x - 22, y, 8, 8, 8, 8, 18, 18, 64, 64); - drawTexture(x - 22, y, 40, 8, 8, 8, 18, 18, 64, 64); - GlStateManager.enableBlend(); - client.textRenderer.draw(formattedTime, client.textRenderer.getWidth(getContent()) + x + 5, y, ClientColors.GRAY.toInt()); - } - } -} diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/chat/CreateChannelScreen.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/api/chat/CreateChannelScreen.java deleted file mode 100644 index a93db6559..000000000 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/chat/CreateChannelScreen.java +++ /dev/null @@ -1,256 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * Copyright © 2026 DSNS - * - * This file is part of DolphinClient, a fork of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api.chat; - -import java.util.Arrays; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Supplier; - -import io.github.axolotlclient.DolphinClientCommon; -import io.github.axolotlclient.AxolotlClientConfig.api.util.Colors; -import io.github.axolotlclient.AxolotlClientConfig.impl.options.DoubleOption; -import io.github.axolotlclient.AxolotlClientConfig.impl.ui.ButtonWidget; -import io.github.axolotlclient.AxolotlClientConfig.impl.ui.ClickableWidget; -import io.github.axolotlclient.AxolotlClientConfig.impl.ui.Element; -import io.github.axolotlclient.AxolotlClientConfig.impl.ui.TextFieldWidget; -import io.github.axolotlclient.AxolotlClientConfig.impl.ui.vanilla.widgets.SliderWidget; -import io.github.axolotlclient.AxolotlClientConfig.impl.ui.vanilla.widgets.VanillaButtonWidget; -import io.github.axolotlclient.AxolotlClientConfig.impl.util.DrawUtil; -import io.github.axolotlclient.api.requests.ChannelRequest; -import io.github.axolotlclient.api.types.Persistence; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.render.platform.GlStateManager; -import net.minecraft.client.resource.language.I18n; -import net.minecraft.resource.Identifier; - -public class CreateChannelScreen extends io.github.axolotlclient.AxolotlClientConfig.impl.ui.Screen { - private final Screen parent; - private String tooltip; - private Element tooltipElement; - - protected CreateChannelScreen(Screen parent) { - super(I18n.translate("api.chat.groups.create")); - this.parent = parent; - } - - @Override - public void render(int mouseX, int mouseY, float delta) { - super.render(mouseX, mouseY, delta); - drawCenteredString(textRenderer, title, width / 2, 36 / 2 - textRenderer.fontHeight / 2, -1); - - - if (tooltip != null) { - tooltipElement = hoveredElement(mouseX, mouseY).orElse(null); - renderTooltip(textRenderer.split(tooltip, 170), mouseX, mouseY); - } - if (hoveredElement(mouseX, mouseY).map(e -> e != tooltipElement).orElse(true)) { - tooltip = null; - } - } - - @Override - public void init() { - int leftColX = width / 2 - 4 - 150; - int leftColYStep = textRenderer.fontHeight + 20 - textRenderer.fontHeight + 8 - 5 + 5; - int leftColY = 36 + 5 + 30; - int rightColX = width / 2 + 4; - int rightColYStep = 20 + 8; - int rightColY = 36 + 30; - - var nameField = new io.github.axolotlclient.AxolotlClientConfig.impl.ui.TextFieldWidget(textRenderer, rightColX, rightColY, 150, 20, ""); - addDrawableChild(text("api.chat.groups.name", leftColX, leftColY)); - addDrawableChild(nameField); - rightColY += rightColYStep; - leftColY += leftColYStep; - - AtomicReference> count = new AtomicReference<>(); - AtomicReference> duration = new AtomicReference<>(); - - AtomicReference> countDisabler = new AtomicReference<>(); - AtomicReference> durationDisabler = new AtomicReference<>(); - var persistence = new VanillaButtonWidget(rightColX, rightColY, 150, 20, "", a -> { - }) { - final Persistence.Type[] persistenceValues = Persistence.Type.values(); - int current = 0; - - @Override - public void drawWidget(int mouseX, int mouseY, float delta) { - setMessage(I18n.translate("api.chat.persistence." + persistenceValues[current].getId())); - super.drawWidget(mouseX, mouseY, delta); - } - - @Override - public void onPress() { - current++; - if (current >= persistenceValues.length) { - current = 0; - } - Persistence.Type object = persistenceValues[current]; - switch (object) { - case COUNT_DURATION -> { - countDisabler.get().accept(true); - countDisabler.get().accept(true); - } - case DURATION -> { - durationDisabler.get().accept(true); - countDisabler.get().accept(false); - } - case COUNT -> { - countDisabler.get().accept(true); - durationDisabler.get().accept(false); - } - case CHANNEL -> { - countDisabler.get().accept(false); - durationDisabler.get().accept(false); - } - } - } - - public Persistence.Type getValue() { - return persistenceValues[current]; - } - }; - rightColY += rightColYStep; - addDrawableChild(text("api.chat.groups.persistence", leftColX, leftColY)); - leftColY += leftColYStep; - addDrawableChild(persistence); - addDrawableChild(text("api.chat.groups.persistence.count", "api.chat.groups.persistence.count.tooltip", leftColX, leftColY)); - leftColY += leftColYStep; - countDisabler.set(sliderAssembly(rightColX, rightColY, val -> (int) (val * 100d), count::set)); - rightColY += rightColYStep; - addDrawableChild(text("api.chat.groups.persistence.duration", "api.chat.groups.persistence.duration.tooltip", leftColX, leftColY)); - leftColY += leftColYStep; - durationDisabler.set(sliderAssembly(rightColX, rightColY, val -> (long) (val * 100d), duration::set)); - rightColY += rightColYStep; - countDisabler.get().accept(false); - durationDisabler.get().accept(false); - - var namesInput = addDrawableChild(new TextFieldWidget(textRenderer, rightColX, rightColY, 150, 20, "")); - addDrawableChild(text("api.chat.groups.participants", leftColX, leftColY)); - - int footerY = height - 36 / 2 - 20 / 2; - addDrawableChild(new VanillaButtonWidget(width / 2 - 150 - 4, footerY, 150, 20, I18n.translate("gui.cancel"), widget -> minecraft.openScreen(parent))); - addDrawableChild(new VanillaButtonWidget(width / 2 + 4, footerY, 150, 20, I18n.translate("gui.done"), widget -> - ChannelRequest.createChannel(nameField.getText(), - Persistence.of(persistence.getValue(), count.get().get(), duration.get().get()), - Arrays.stream(namesInput.getText().split(",")).filter(s -> !s.isEmpty()).toArray(String[]::new)) - .thenRun(() -> minecraft.executeTask(() -> minecraft.openScreen(parent))))); - } - - private Consumer sliderAssembly(int x, int y, Function valueFunc, Consumer> value) { - - AtomicReference currentVal = new AtomicReference<>(); - DoubleOption opt = new DoubleOption("", 0d, d -> currentVal.set(valueFunc.apply(d)), 0d, 1d); - var slider = new SliderWidget<>(x, y, 128, 20, opt) { - @Override - public String getMessage() { - return String.valueOf(currentVal.get()); - } - }; - opt.set(0d); - value.accept(currentVal::get); - var text = new TextFieldWidget(textRenderer, x, y, 128, 20, ""); - var textButton = new ButtonWidget(x + 130, y, 20, 20, I18n.translate("slider.text_input"), w -> { - if (slider.visible) { - text.setX(slider.getX()); - text.setY(slider.getY()); - text.setWidth(slider.getWidth()); - text.setText(String.valueOf(currentVal.get())); - } else { - try { - opt.set(Double.parseDouble(text.getText())); - } catch (Exception ignored) { - } - } - slider.visible = !slider.visible; - text.visible = !slider.visible; - }) { - @Override - public void drawWidget(int mouseX, int mouseY, float delta) { - this.client.getTextureManager().bind(WIDGETS_LOCATION); - GlStateManager.color4f(1.0F, 1.0F, 1.0F, 1.0F); - int k = this.active ? (this.hovered ? 2 : 1) : 0; - GlStateManager.enableBlend(); - GlStateManager.blendFuncSeparate(770, 771, 1, 0); - GlStateManager.blendFunc(770, 771); - this.drawTexture(this.getX(), this.getY(), 0, 46 + k * 20, this.getWidth() / 2, this.getHeight()); - this.drawTexture(this.getX() + this.getWidth() / 2, this.getY(), 200 - this.getWidth() / 2, 46 + k * 20, this.getWidth() / 2, this.getHeight()); - client.getTextureManager().bind(new Identifier(DolphinClientCommon.MODID, "textures/gui/sprites/cursor.png")); - drawTexture(getX() + getWidth() / 2 - 4, getY() + getHeight() / 2 - 4, - 8, 8, 8, 8, 8, 8, 8, 8); - - } - }; - text.visible = false; - addDrawableChild(text); - addDrawableChild(textButton); - addDrawableChild(slider); - return val -> slider.active = textButton.active = text.active = val; - } - - private ClickableWidget text(String translationKey, int x, int y) { - String text = I18n.translate(translationKey); - return new ClickableWidget(x, y, 150, textRenderer.fontHeight, text) { - - @Override - public void drawWidget(int mouseX, int mouseY, float delta) { - DrawUtil.drawScrollingText(getMessage(), getX(), getY(), getWidth(), getHeight(), Colors.WHITE); - } - - public boolean mouseClicked(double mouseX, double mouseY, int button) { - return false; - } - }; - } - - private ClickableWidget text(String translationKey, String tooltipKey, int x, int y) { - String text = I18n.translate(translationKey); - return new ClickableWidget(x, y, 150, textRenderer.fontHeight, text) { - - @Override - public void drawWidget(int mouseX, int mouseY, float delta) { - DrawUtil.drawScrollingText(getMessage(), getX(), getY(), getWidth(), getHeight(), Colors.WHITE); - if (isHovered()) { - CreateChannelScreen.this.tooltip = I18n.translate(tooltipKey); - /*GlStateManager.enableBlend(); - GlStateManager.disableDepthTest(); - GlStateManager.pushMatrix(); - GlStateManager.translatef(0, 0, 200); - CreateChannelScreen.this.renderTooltip(I18n.translate(tooltipKey), mouseX, mouseY); - GlStateManager.disableLighting(); - GlStateManager.disableDepthTest(); - GlStateManager.popMatrix(); - GlStateManager.color3f(1, 1, 1);*/ - } - } - - @Override - public boolean mouseClicked(double mouseX, double mouseY, int button) { - return false; - } - }; - } -} diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/e4mc/DolphinClientE4mcPlugin.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/api/e4mc/DolphinClientE4mcPlugin.java deleted file mode 100644 index 33ff1e222..000000000 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/e4mc/DolphinClientE4mcPlugin.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright © 2025 moehreag & Contributors - * Copyright © 2026 DSNS - * - * This file is part of DolphinClient, a fork of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api.e4mc; - -import io.github.axolotlclient.api.API; -import io.github.axolotlclient.api.multiplayer.ServerInfoUtil; -import io.github.axolotlclient.api.types.Status; -import link.e4mc.E4mcClient; -import link.e4mc.QuiclimeSession; -import lombok.Setter; -import net.minecraft.client.Minecraft; - -@Setter -public class DolphinClientE4mcPlugin { - - private String e4mcDomain = null; - - public static final DolphinClientE4mcPlugin INSTANCE = new DolphinClientE4mcPlugin(); - - public Status.Activity.E4mcMetadata getStatusDescription() { - Minecraft mc = Minecraft.getInstance(); - var levelName = mc.getServer().getWorldName(); - if (E4mcClient.session == null || E4mcClient.session.state != QuiclimeSession.State.STARTED) { - if (mc.getServer().getStatus() != null) { - return new Status.Activity.E4mcMetadata(null, ServerInfoUtil.getServerInfo(levelName, mc.getServer().getStatus())); - } - return new Status.Activity.E4mcMetadata(null, ServerInfoUtil.getServerInfo(levelName, null)); - } - if (!API.getInstance().getApiOptions().allowFriendsServerJoin.get()) { - return new Status.Activity.E4mcMetadata(null, ServerInfoUtil.getServerInfo(levelName, mc.getServer().getStatus())); - } - return new Status.Activity.E4mcMetadata(e4mcDomain, ServerInfoUtil.getServerInfo(levelName, mc.getServer().getStatus())); - } - -} diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/e4mc/E4mcMixinPlugin.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/api/e4mc/E4mcMixinPlugin.java deleted file mode 100644 index 5cac3bd02..000000000 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/e4mc/E4mcMixinPlugin.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright © 2025 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api.e4mc; - -import java.util.List; -import java.util.Set; - -import net.fabricmc.loader.api.FabricLoader; -import org.objectweb.asm.tree.ClassNode; -import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; -import org.spongepowered.asm.mixin.extensibility.IMixinInfo; - -public class E4mcMixinPlugin implements IMixinConfigPlugin { - @Override - public void onLoad(String mixinPackage) { - - } - - @Override - public String getRefMapperConfig() { - return "e4mc.axolotlclient.refmap.json"; - } - - @Override - public boolean shouldApplyMixin(String targetClassName, String mixinClassName) { - return FabricLoader.getInstance().isModLoaded("e4mc_minecraft") || FabricLoader.getInstance().isModLoaded("e4mc"); - } - - @Override - public void acceptTargets(Set myTargets, Set otherTargets) { - - } - - @Override - public List getMixins() { - return List.of(); - } - - @Override - public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { - - } - - @Override - public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { - - } -} diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/e4mc/E4mcStatusProvider.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/api/e4mc/E4mcStatusProvider.java deleted file mode 100644 index 7c4f3a973..000000000 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/e4mc/E4mcStatusProvider.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright © 2025 moehreag & Contributors - * Copyright © 2026 DSNS - * - * This file is part of DolphinClient, a fork of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ -package io.github.axolotlclient.api.e4mc; - -import io.github.axolotlclient.api.types.Status; -import net.fabricmc.loader.api.FabricLoader; - -public class E4mcStatusProvider { - - public static Status.Activity.E4mcMetadata getStatusDescription() { - if (FabricLoader.getInstance().isModLoaded("e4mc_minecraft")) { - return DolphinClientE4mcPlugin.INSTANCE.getStatusDescription(); - } - return null; - } -} diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/e4mc/mixin/QuiclimeSessionInnerMixin.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/api/e4mc/mixin/QuiclimeSessionInnerMixin.java deleted file mode 100644 index 80b84c621..000000000 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/e4mc/mixin/QuiclimeSessionInnerMixin.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright © 2025 moehreag & Contributors - * Copyright © 2026 DSNS - * - * This file is part of DolphinClient, a fork of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api.e4mc.mixin; - -import com.llamalad7.mixinextras.sugar.Local; -import io.github.axolotlclient.api.e4mc.DolphinClientE4mcPlugin; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.ModifyArg; - -@Mixin(targets = "link/e4mc/QuiclimeSession$2$1", remap = false) -public class QuiclimeSessionInnerMixin { - - @ModifyArg(method = "channelRead0(Lio/netty/channel/ChannelHandlerContext;Llink/e4mc/QuiclimeSession$ControlMessageCodec$ControlMessage;)V", at = @At(value = "INVOKE", target = "Lorg/slf4j/Logger;info(Ljava/lang/String;Ljava/lang/Object;)V"), index = 1) - private Object axolotlclient$hookE4mcDomain(Object o, @Local(name = "domain") String domain) { - DolphinClientE4mcPlugin.INSTANCE.setE4mcDomain(domain); - return o; - } -} diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/e4mc/mixin/QuiclimeSessionMixin.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/api/e4mc/mixin/QuiclimeSessionMixin.java deleted file mode 100644 index 378113b71..000000000 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/e4mc/mixin/QuiclimeSessionMixin.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright © 2025 moehreag & Contributors - * Copyright © 2026 DSNS - * - * This file is part of DolphinClient, a fork of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api.e4mc.mixin; - -import io.github.axolotlclient.api.e4mc.DolphinClientE4mcPlugin; -import link.e4mc.QuiclimeSession; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -@Mixin(value = QuiclimeSession.class, remap = false) -public class QuiclimeSessionMixin { - @Inject(method = "stop", at = @At("HEAD")) - private void axolotlclient$stopListener(CallbackInfo ci) { - DolphinClientE4mcPlugin.INSTANCE.setE4mcDomain(null); - } -} diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/multiplayer/FriendsMultiplayerScreen.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/api/multiplayer/FriendsMultiplayerScreen.java deleted file mode 100644 index 37de436c4..000000000 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/multiplayer/FriendsMultiplayerScreen.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Copyright © 2025 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api.multiplayer; - -import java.util.Collections; -import java.util.List; - -import io.github.axolotlclient.AxolotlClientConfig.impl.ui.ButtonWidget; -import io.github.axolotlclient.AxolotlClientConfig.impl.ui.vanilla.widgets.VanillaButtonWidget; -import io.github.axolotlclient.api.FriendsScreen; -import io.github.axolotlclient.api.handlers.StatusUpdateHandler; -import io.github.axolotlclient.api.requests.FriendRequest; -import lombok.Getter; -import net.minecraft.client.gui.screen.ConfirmationListener; -import net.minecraft.client.gui.screen.ConnectScreen; -import net.minecraft.client.gui.screen.DirectConnectScreen; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.gui.screen.multiplayer.MultiplayerScreen; -import net.minecraft.client.network.MultiplayerServerListPinger; -import net.minecraft.client.options.ServerListEntry; -import net.minecraft.client.resource.language.I18n; -import org.lwjgl.input.Keyboard; - -public class FriendsMultiplayerScreen extends io.github.axolotlclient.AxolotlClientConfig.impl.ui.Screen implements ConfirmationListener { - @Getter - private final MultiplayerServerListPinger pinger = new MultiplayerServerListPinger(); - private final Screen lastScreen; - protected FriendsMultiplayerSelectionList serverSelectionList; - private ButtonWidget selectButton; - private ServerListEntry editingServer; - private boolean initialized; - private List tooltipText; - private final ButtonWidget friendsCountButton = new VanillaButtonWidget(0, 0, 150, 20, I18n.translate("api.servers.friends", "..."), button -> { - }); - private boolean directConnectDialog = false; - private static final String NO_ONLINE_FRIENDS = I18n.translate("api.servers.friends.no_online_friends"); - - public FriendsMultiplayerScreen(Screen lastScreen) { - super(I18n.translate("api.servers.friends.title")); - this.lastScreen = lastScreen; - } - - @Override - public void init() { - super.init(); - Keyboard.enableRepeatEvents(true); - if (this.initialized) { - this.serverSelectionList.updateSize(this.width, this.height, 60, this.height - 64); - } else { - this.serverSelectionList = new FriendsMultiplayerSelectionList(this, this.minecraft, this.width, this.height - 64 - 60, 60, 36); - } - StatusUpdateHandler.addUpdateListener("friends_multiplayer_screen", serverSelectionList::updateEntry); - this.addDrawableChild(this.serverSelectionList); - addDrawableChild(new VanillaButtonWidget(this.width / 2 - 102, 32, 100, 20, I18n.translate("api.servers"), button -> - minecraft.openScreen(new MultiplayerScreen(lastScreen)))); - addDrawableChild(friendsCountButton); - friendsCountButton.setX(width / 2 + 2); - friendsCountButton.setY(32); - friendsCountButton.setWidth(100); - friendsCountButton.active = false; - - if (!initialized) { - initialized = true; - FriendRequest.getInstance().getFriends().thenAccept(friends -> { - friendsCountButton.setMessage(I18n.translate("api.servers.friends", friends.stream().filter(u -> u.getStatus().isOnline()).count())); - this.serverSelectionList.updateList(friends); - }); - } - - this.selectButton = this.addDrawableChild( - new VanillaButtonWidget(width / 2 - 154, height - 64 + 12, 100, 20, - I18n.translate("selectServer.select"), buttonx -> this.joinSelectedServer())); - this.addDrawableChild(new VanillaButtonWidget(width / 2 - 50, height - 64 + 12, 100, 20, - I18n.translate("selectServer.direct"), buttonx -> { - directConnectDialog = true; - this.editingServer = new ServerListEntry(I18n.translate("selectServer.defaultName"), "", false); - this.minecraft.openScreen(new DirectConnectScreen(this, this.editingServer)); - })); - this.addDrawableChild(new VanillaButtonWidget(width / 2 + 50 + 4, height - 64 + 12, 100, 20, - I18n.translate("api.friends"), buttonx -> - this.minecraft.openScreen(new FriendsScreen(this)))); - ButtonWidget editButton = this.addDrawableChild(new VanillaButtonWidget(width / 2 - 154, height - 64 + 12 + 20 + 4, 74, 20, - I18n.translate("selectServer.edit"), buttonx -> { - })); - editButton.active = false; - ButtonWidget deleteButton = this.addDrawableChild(new VanillaButtonWidget(width / 2 - 76, height - 64 + 12 + 20 + 4, 74, 20, - I18n.translate("selectServer.delete"), buttonx -> { - })); - deleteButton.active = false; - this.addDrawableChild( - new VanillaButtonWidget(width / 2 + 2, height - 64 + 12 + 20 + 4, 74, 20, - I18n.translate("selectServer.refresh"), buttonx -> this.refreshServerList())); - this.addDrawableChild(new VanillaButtonWidget(width / 2 + 80, height - 64 + 12 + 20 + 4, 74, 20, - I18n.translate("gui.back"), buttonx -> this.minecraft.openScreen(this.lastScreen))); - - this.onSelectedChange(); - } - - @Override - public void tick() { - super.tick(); - this.pinger.tick(); - } - - @Override - public void removed() { - Keyboard.enableRepeatEvents(false); - this.pinger.cancel(); - StatusUpdateHandler.removeUpdateListener("friends_multiplayer_screen"); - } - - @Override - public void render(int mouseX, int mouseY, float partialTick) { - this.tooltipText = null; - super.render(mouseX, mouseY, partialTick); - drawCenteredString(textRenderer, getTitle(), width / 2, 15, -1); - - if (serverSelectionList.children().isEmpty()) { - drawCenteredString(textRenderer, NO_ONLINE_FRIENDS, width / 2, height / 2 - textRenderer.fontHeight / 2, -1); - } - if (this.tooltipText != null) { - this.renderTooltip(this.tooltipText, mouseX, mouseY); - } - } - - private void refreshServerList() { - this.minecraft.openScreen(new FriendsMultiplayerScreen(this.lastScreen)); - } - - private void directJoinCallback(boolean confirmed) { - if (confirmed) { - this.join(this.editingServer); - } else { - this.minecraft.openScreen(this); - } - } - - public void joinSelectedServer() { - FriendsMultiplayerSelectionList.Entry entry = this.serverSelectionList.getSelectedOrNull(); - this.join(entry.getServerData()); - } - - private void join(ServerListEntry server) { - if (server == null) { - return; - } - this.minecraft.openScreen(new ConnectScreen(this, this.minecraft, server)); - } - - public void setSelected(FriendsMultiplayerSelectionList.Entry selected) { - this.serverSelectionList.setSelected(selected); - this.onSelectedChange(); - } - - protected void onSelectedChange() { - this.selectButton.active = false; - FriendsMultiplayerSelectionList.Entry entry = this.serverSelectionList.getSelectedOrNull(); - if (entry != null && !(entry instanceof FriendsMultiplayerSelectionList.LoadingHeader)) { - this.selectButton.active = entry.canJoin(); - } - } - - public void setDeferredTooltip(List text) { - this.tooltipText = text; - } - - public void setDeferredTooltip(String text) { - if (text != null && !text.isEmpty()) { - this.tooltipText = Collections.singletonList(text); - } - } - - @Override - public void confirmResult(boolean bl, int i) { - if (this.directConnectDialog) { - this.directConnectDialog = false; - directJoinCallback(bl); - } - } -} diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/multiplayer/FriendsMultiplayerSelectionList.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/api/multiplayer/FriendsMultiplayerSelectionList.java deleted file mode 100644 index 8282d84cb..000000000 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/multiplayer/FriendsMultiplayerSelectionList.java +++ /dev/null @@ -1,620 +0,0 @@ -/* - * Copyright © 2025 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api.multiplayer; - -import java.net.UnknownHostException; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.concurrent.ScheduledThreadPoolExecutor; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.stream.Collectors; - -import com.google.common.hash.Hashing; -import com.google.common.util.concurrent.ThreadFactoryBuilder; -import io.github.axolotlclient.AxolotlClientConfig.api.util.Color; -import io.github.axolotlclient.AxolotlClientConfig.impl.ui.vanilla.EntryListWidget; -import io.github.axolotlclient.api.types.PkSystem; -import io.github.axolotlclient.api.types.Status; -import io.github.axolotlclient.api.types.User; -import io.github.axolotlclient.modules.auth.Auth; -import io.github.axolotlclient.util.Util; -import io.netty.buffer.ByteBufInputStream; -import io.netty.buffer.Unpooled; -import io.netty.handler.codec.base64.Base64; -import lombok.Getter; -import lombok.Setter; -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiElement; -import net.minecraft.client.options.ServerListEntry; -import net.minecraft.client.render.platform.GlStateManager; -import net.minecraft.client.render.texture.DynamicTexture; -import net.minecraft.client.render.texture.TextureUtil; -import net.minecraft.client.resource.language.I18n; -import net.minecraft.resource.Identifier; -import net.minecraft.text.Formatting; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.jetbrains.annotations.Nullable; - -public class FriendsMultiplayerSelectionList extends EntryListWidget { - private static final int PROTOCOL_VERSION = 47; - private static final Identifier UNKNOWN_SERVER_TEXTURE = new Identifier("textures/misc/unknown_server.png"); - static final Identifier SERVER_SELECTION_TEXTURE = new Identifier("textures/gui/server_selection.png"); - static final Identifier ICONS = new Identifier("textures/gui/icons.png"); - static final Logger LOGGER = LogManager.getLogger(); - static final ThreadPoolExecutor THREAD_POOL = new ScheduledThreadPoolExecutor( - 5, - new ThreadFactoryBuilder() - .setNameFormat("Friends Server Pinger #%d") - .setDaemon(true) - .setUncaughtExceptionHandler((t, e) -> LOGGER.error("Caught previously unhandled exception :", e)).build() - ); - static final String CANT_RESOLVE_TEXT = Util.getFormatCode(new Color(-65536)) + I18n.translate("multiplayer.status.cannot_resolve"); - static final String CANT_CONNECT_TEXT = Util.getFormatCode(new Color(-65536)) + I18n.translate("multiplayer.status.cannot_connect"); - static final String INCOMPATIBLE_STATUS = I18n.translate("multiplayer.status.incompatible"); - static final String NO_CONNECTION_STATUS = I18n.translate("multiplayer.status.no_connection"); - static final String PINGING_STATUS = I18n.translate("multiplayer.status.pinging"); - static final String NOT_PUBLISHED_STATUS = Formatting.RED + I18n.translate("api.worldhost.joinability.not_published"); - private final FriendsMultiplayerScreen screen; - private final List friendEntries = new ArrayList<>(); - private final LoadingHeader loadingHeader = new LoadingHeader(); - - public FriendsMultiplayerSelectionList(FriendsMultiplayerScreen screen, Minecraft minecraft, int width, int height, int y, int itemHeight) { - super(minecraft, screen.width, screen.height, y, y + height, itemHeight); - this.screen = screen; - addEntry(loadingHeader); - } - - private void refreshEntries() { - this.clearEntries(); - this.friendEntries.forEach(this::addEntry); - } - - public void setSelected(@Nullable FriendsMultiplayerSelectionList.Entry entry) { - super.setSelected(entry); - this.screen.onSelectedChange(); - } - - @Override - public boolean keyPressed(int keyCode, int scanCode, int modifiers) { - Entry entry = this.getSelectedOrNull(); - return entry != null && entry.keyPressed(keyCode, scanCode, modifiers) || super.keyPressed(keyCode, scanCode, modifiers); - } - - public void updateList(List friends) { - this.friendEntries.clear(); - - for (User friend : friends) { - if (friend.getStatus().isOnline()) { - this.friendEntries.add(createEntry(friend)); - } - } - - this.refreshEntries(); - } - - private Entry createEntry(User friend) { - if (friend.getStatus().getActivity() != null && friend.getStatus().getActivity().hasMetadata()) { - if (friend.getStatus().getActivity().hasMetadata(Status.Activity.ExternalServerMetadata.ID)) { - return externalServerEntry(this.screen, friend); - } else { - return e4mcServerFriendEntry(this.screen, friend); - } - } - return new StatusFriendEntry(screen, friend); - } - - public void updateEntry(User user) { - this.friendEntries.stream().filter(e1 -> { - if (e1 instanceof StatusFriendEntry statusFriendEntry) { - return statusFriendEntry.getUser().equals(user); - } else if (e1 instanceof ServerEntry serverEntry) { - return serverEntry.getUser().equals(user); - } - return false; - }).findFirst().ifPresent(e -> { - this.friendEntries.set(friendEntries.indexOf(e), createEntry(user)); - refreshEntries(); - }); - } - - @Override - public int getRowWidth() { - return 305; - } - - @Environment(EnvType.CLIENT) - public abstract static class Entry extends EntryListWidget.Entry implements AutoCloseable { - private boolean focused; - - public void close() { - } - - public boolean canJoin() { - return false; - } - - public ServerListEntry getServerData() { - return null; - } - - @Override - public boolean isFocused() { - return focused; - } - - @Override - public void setFocused(boolean b) { - this.focused = b; - } - } - - @Getter - public class StatusFriendEntry extends Entry { - - private final User user; - - protected StatusFriendEntry(final FriendsMultiplayerScreen screen, final User friend) { - this.user = friend; - } - - @Override - public void render(int index, int top, int left, int width, int height, int mouseX, int mouseY, boolean hovering, float partialTick) { - if (user.isSystem()) { - String fronters = - user.getSystem().getFronters().stream().map(PkSystem.Member::getDisplayName) - .collect(Collectors.joining("/")); - String tag = Formatting.ITALIC + Formatting.GRAY.toString() + "(" + user.getSystem().getName() + "/" + user.getName() + ")" + Formatting.RESET; - client.textRenderer.draw(fronters + " " + tag, left + 3, top + 1, -1); - } else { - client.textRenderer.draw(user.getName(), left + 3 + 32, top + 1, -1); - } - - if (user.getStatus().isOnline() && user.getStatus().getActivity() != null) { - client.textRenderer.draw(user.getStatus().getTitle(), left + 3 + 32, top + 12, 8421504); - client.textRenderer.draw(user.getStatus().getDescription(), left + 3 + 40, top + 23, 8421504); - } else if (user.getStatus().getLastOnline() != null) { - client.textRenderer.draw(user.getStatus().getLastOnline(), left + 3 + 32, top + 12, 8421504); - } - - client.getTextureManager().bind(Auth.getInstance().getSkinTexture(user)); - GlStateManager.enableBlend(); - GlStateManager.color4f(1, 1, 1, 1); - drawTexture(left - 1, top - 1, 8, 8, 8, 8, 32, 32, 64, 64); - drawTexture(left - 1, top - 1, 40, 8, 8, 8, 32, 32, 64, 64); - GlStateManager.disableBlend(); - } - } - - private static final int STATUS_ICON_HEIGHT = 8; - private static final int STATUS_ICON_WIDTH = 10; - - protected class ServerEntry extends Entry { - private static final int ICON_WIDTH = 32; - private static final int ICON_HEIGHT = 32; - private static final int SPACING = 5; - private static final int STATUS_ICON_WIDTH = 10; - private static final int STATUS_ICON_HEIGHT = 8; - private final FriendsMultiplayerScreen screen; - private final Minecraft minecraft; - protected final ServerInfoEx serverData; - private final Identifier iconId; - private DynamicTexture icon; - private String lastIconBytes; - private long lastClickTime; - @Nullable - private List onlinePlayersTooltip; - private Sprite statusIcon; - @Nullable - private String statusIconTooltip; - @Getter - protected final User user; - - @SuppressWarnings("UnstableApiUsage") - protected ServerEntry(FriendsMultiplayerScreen screen, ServerListEntry serverData, User user) { - this.screen = screen; - this.minecraft = Minecraft.getInstance(); - this.serverData = new ServerInfoEx(serverData); - this.iconId = new Identifier("servers/" + Hashing.sha1().hashUnencodedChars(serverData.ip != null ? serverData.ip : user.getUuid() + "_" + serverData.name) + "/icon"); - this.user = user; - } - - @Override - public ServerListEntry getServerData() { - return serverData.serverInfo(); - } - - protected void refreshStatus() { - this.onlinePlayersTooltip = null; - if (!isPublished()) { - this.serverData.setPingResult(PingResult.UNREACHABLE); - } - switch (this.serverData.pingResult()) { - case INITIAL: - case PINGING: - this.statusIcon = Sprite.PING_1_SPRITE; - this.statusIconTooltip = FriendsMultiplayerSelectionList.PINGING_STATUS; - break; - case INCOMPATIBLE: - this.statusIcon = Sprite.INCOMPATIBLE_SPRITE; - var tooltipString = this.serverData.serverInfo.onlinePlayers; - if (tooltipString != null) { - this.onlinePlayersTooltip = List.of(tooltipString.split("\n")); - } else { - this.onlinePlayersTooltip = null; - } - this.statusIconTooltip = FriendsMultiplayerSelectionList.INCOMPATIBLE_STATUS; - break; - case UNREACHABLE: - this.statusIcon = Sprite.UNREACHABLE_SPRITE; - if (!isPublished()) { - break; - } - this.statusIconTooltip = FriendsMultiplayerSelectionList.NO_CONNECTION_STATUS; - break; - case SUCCESSFUL: - if (this.serverData.serverInfo.ping < 150L) { - this.statusIcon = Sprite.PING_5_SPRITE; - } else if (this.serverData.serverInfo.ping < 300L) { - this.statusIcon = Sprite.PING_4_SPRITE; - } else if (this.serverData.serverInfo.ping < 600L) { - this.statusIcon = Sprite.PING_3_SPRITE; - } else if (this.serverData.serverInfo.ping < 1000L) { - this.statusIcon = Sprite.PING_2_SPRITE; - } else { - this.statusIcon = Sprite.PING_1_SPRITE; - } - - this.statusIconTooltip = I18n.translate("multiplayer.status.ping", this.serverData.serverInfo.ping); - var tooltipStr = this.serverData.serverInfo.onlinePlayers; - if (tooltipStr != null) { - this.onlinePlayersTooltip = List.of(tooltipStr.split("\n")); - } else { - this.onlinePlayersTooltip = null; - } - } - } - - @Override - public void render(int index, int top, int left, int width, int height, int mouseX, int mouseY, boolean hovering, float partialTick) { - if (this.serverData.pingResult() == PingResult.INITIAL) { - this.serverData.setPingResult(PingResult.PINGING); - this.serverData.serverInfo.motd = ""; - this.serverData.serverInfo.onlinePlayers = ""; - FriendsMultiplayerSelectionList.THREAD_POOL - .submit( - () -> { - try { - this.screen - .getPinger() - .add( - this.serverData.serverInfo - ); - } catch (UnknownHostException var2) { - this.serverData.setPingResult(PingResult.UNREACHABLE); - this.serverData.serverInfo.motd = FriendsMultiplayerSelectionList.CANT_RESOLVE_TEXT; - this.minecraft.executeTask(this::refreshStatus); - } catch (Exception var3) { - this.serverData.setPingResult(PingResult.UNREACHABLE); - this.serverData.serverInfo.motd = FriendsMultiplayerSelectionList.CANT_CONNECT_TEXT; - this.minecraft.executeTask(this::refreshStatus); - } - } - ); - } - - if (serverData.pingResult == PingResult.PINGING && serverData.serverInfo.ping != -2) { - this.serverData.setPingResult( - this.serverData.serverInfo.protocol == PROTOCOL_VERSION ? PingResult.SUCCESSFUL : PingResult.INCOMPATIBLE - ); - } - refreshStatus(); - - minecraft.textRenderer.drawWithShadow(this.serverData.serverInfo.name, left + ICON_WIDTH + 3, top + 1, -1); - List list = this.minecraft.textRenderer.split(this.serverData.serverInfo.motd, width - ICON_WIDTH - 2); - - for (int i = 0; i < Math.min(list.size(), 2); i++) { - minecraft.textRenderer.drawWithShadow(list.get(i), left + ICON_WIDTH + 3, top + 12 + 9 * i, -8355712); - } - - GlStateManager.color4f(1, 1, 1, 1); - client.getTextureManager().bind(this.icon != null ? this.iconId : UNKNOWN_SERVER_TEXTURE); - drawTexture(left, top, 0.0F, 0.0F, 32, 32, 32, 32); - client.getTextureManager().bind(Auth.getInstance().getSkinTexture(user)); - GlStateManager.enableBlend(); - drawTexture(left + ICON_WIDTH - 10, top + ICON_HEIGHT - 10, 8, 8, 8, 8, 10, 10, 64, 64); - drawTexture(left + ICON_WIDTH - 10, top + ICON_HEIGHT - 10, 40, 8, 8, 8, 10, 10, 64, 64); - GlStateManager.disableBlend(); - if (this.serverData.pingResult() == PingResult.PINGING) { - int i = (int) (Minecraft.getTime() / 100L + index * 2 & 7L); - if (i > 4) { - i = 8 - i; - } - this.statusIcon = switch (i) { - case 1 -> Sprite.PINGING_2_SPRITE; - case 2 -> Sprite.PINGING_3_SPRITE; - case 3 -> Sprite.PINGING_4_SPRITE; - case 4 -> Sprite.PINGING_5_SPRITE; - default -> Sprite.PINGING_1_SPRITE; - }; - } - - int i = left + width - STATUS_ICON_WIDTH - SPACING; - if (this.statusIcon != null) { - statusIcon.draw(i, top); - } - - String bs = this.serverData.serverInfo.getIcon(); - if (!Objects.equals(bs, this.lastIconBytes)) { - if (this.uploadIcon(bs)) { - this.lastIconBytes = bs; - } else { - this.serverData.serverInfo.setIcon(null); - } - } - - String component; - if (!isPublished()) { - component = NOT_PUBLISHED_STATUS; - } else { - if (this.serverData.pingResult() == PingResult.INCOMPATIBLE) { - component = Formatting.RED + this.serverData.serverInfo.version + Formatting.RESET; - } else { - component = this.serverData.serverInfo.onlinePlayers; - } - } - int j = this.minecraft.textRenderer.getWidth(component); - int k = i - j - SPACING; - minecraft.textRenderer.drawWithShadow(component, k, top + 1, -8355712); - if (this.statusIconTooltip != null && mouseX >= i && mouseX <= i + STATUS_ICON_WIDTH && mouseY >= top && mouseY <= top + STATUS_ICON_HEIGHT) { - this.screen.setDeferredTooltip(this.statusIconTooltip); - } else if (this.onlinePlayersTooltip != null && mouseX >= k && mouseX <= k + j && mouseY >= top && mouseY <= top - 1 + 9) { - this.screen.setDeferredTooltip(this.onlinePlayersTooltip); - } - - if (this.minecraft.options.touchscreen || hovering) { - int l = mouseX - left; - int m = mouseY - top; - if (this.canJoin()) { - GuiElement.fill(left, top, left + ICON_WIDTH, top + ICON_HEIGHT, -1601138544); - if (l < ICON_WIDTH && l > ICON_WIDTH / 2) { - Sprite.JOIN_HIGHLIGHTED_SPRITE.draw(left, top); - } else { - Sprite.JOIN_SPRITE.draw(left, top); - } - } - } - } - - protected boolean isPublished() { - return true; - } - - @Override - public boolean canJoin() { - return serverData.pingResult() == PingResult.SUCCESSFUL && isPublished(); - } - - private boolean uploadIcon(String iconBytes) { - if (iconBytes == null) { - this.minecraft.getTextureManager().close(this.iconId); - this.icon = null; - } else { - var buf = Unpooled.copiedBuffer(iconBytes, StandardCharsets.UTF_8); - var decoded = Base64.decode(buf); - try { - - var image = TextureUtil.readImage(new ByteBufInputStream(decoded)); - if (image.getWidth() != 64 || image.getHeight() != 64) { - throw new IllegalArgumentException("Icon must be 64x64, but was " + image.getWidth() + "x" + image.getHeight()); - } - if (this.icon == null) { - this.icon = new DynamicTexture(image); - this.minecraft.getTextureManager().register(this.iconId, this.icon); - } - - image.getRGB(0, 0, image.getWidth(), image.getHeight(), this.icon.getPixels(), 0, image.getWidth()); - this.icon.upload(); - - } catch (Throwable var3) { - FriendsMultiplayerSelectionList.LOGGER.error("Invalid icon for server {} ({})", this.serverData.serverInfo.name, this.serverData.serverInfo.ip, var3); - return false; - } finally { - buf.release(); - decoded.release(); - } - } - - return true; - } - - @Override - public boolean mouseClicked(double mouseX, double mouseY, int button) { - double d = mouseX - FriendsMultiplayerSelectionList.this.getRowLeft(); - double e = mouseY - FriendsMultiplayerSelectionList.this.getRowTop(FriendsMultiplayerSelectionList.this.children().indexOf(this)); - if (d <= 32.0) { - if (d < 32.0 && d > 16.0 && this.canJoin()) { - this.screen.setSelected(this); - this.screen.joinSelectedServer(); - return true; - } - } - - this.screen.setSelected(this); - if (Minecraft.getTime() - this.lastClickTime < 250L && canJoin()) { - this.screen.joinSelectedServer(); - } - - this.lastClickTime = Minecraft.getTime(); - return super.mouseClicked(mouseX, mouseY, button); - } - - @Override - public void close() { - this.minecraft.getTextureManager().close(this.iconId); - } - } - - private ExternalServerFriendEntry externalServerEntry(FriendsMultiplayerScreen screen, User friend) { - Status.Activity.ExternalServerMetadata metadata = (Status.Activity.ExternalServerMetadata) friend.getStatus().getActivity().metadata().attributes(); - return new ExternalServerFriendEntry(screen, metadata, new ServerListEntry(metadata.serverName(), metadata.address(), false), friend); - } - - public class ExternalServerFriendEntry extends ServerEntry { - private final Status.Activity.ExternalServerMetadata statusDescription; - - private ExternalServerFriendEntry(FriendsMultiplayerScreen screen, Status.Activity.ExternalServerMetadata statusDescription, ServerListEntry serverData, User friend) { - super(screen, serverData, friend); - this.statusDescription = statusDescription; - refreshStatus(); - } - - @Override - public boolean canJoin() { - return statusDescription.address() != null; - } - - } - - private E4mcServerFriendEntry e4mcServerFriendEntry(FriendsMultiplayerScreen screen, User friend) { - var activity = friend.getStatus().getActivity(); - Status.Activity.E4mcMetadata metadata; - if (activity.hasMetadata(Status.Activity.WorldHostMetadata.ID)) { - metadata = ((Status.Activity.WorldHostMetadata) activity.metadata().attributes()).asE4mcMetadata(); - } else { - metadata = (Status.Activity.E4mcMetadata) activity.metadata().attributes(); - } - return new E4mcServerFriendEntry(screen, metadata, ServerInfoUtil.getServerData(friend.getName(), metadata), friend); - } - - public class E4mcServerFriendEntry extends ServerEntry { - - private final Status.Activity.E4mcMetadata statusDescription; - - protected E4mcServerFriendEntry(FriendsMultiplayerScreen screen, Status.Activity.E4mcMetadata statusDescription, ServerListEntry serverData, User friend) { - super(screen, serverData, friend); - this.statusDescription = statusDescription; - refreshStatus(); - } - - @Override - protected boolean isPublished() { - return statusDescription.domain() != null; - } - - @Override - protected void refreshStatus() { - super.refreshStatus(); - serverData.serverInfo.motd = statusDescription.serverInfo().levelName(); - } - } - - @Environment(EnvType.CLIENT) - public static class LoadingHeader extends Entry { - private final Minecraft minecraft = Minecraft.getInstance(); - - @Override - public void render(int index, int top, int left, int width, int height, int mouseX, int mouseY, boolean hovering, float partialTick) { - int i = top + height / 2 - 9 / 2; - - String string = switch ((int) (Minecraft.getTime() / 300L % 4L)) { - case 1, 3 -> "o O o"; - case 2 -> "o o O"; - default -> "O o o"; - }; - minecraft.textRenderer.drawWithShadow(string, this.minecraft.screen.width / 2f - this.minecraft.textRenderer.getWidth(string) / 2f, i, -8355712); - } - } - - protected static final class ServerInfoEx { - private final ServerListEntry serverInfo; - @Setter - private PingResult pingResult; - - private ServerInfoEx(ServerListEntry serverInfo) { - this.serverInfo = serverInfo; - this.pingResult = PingResult.INITIAL; - } - - public ServerListEntry serverInfo() { - return serverInfo; - } - - public PingResult pingResult() { - return pingResult; - } - - } - - protected enum PingResult { - INITIAL, - PINGING, - UNREACHABLE, - INCOMPATIBLE, - SUCCESSFUL - } - - private enum Sprite { - UNREACHABLE_SPRITE(0, 5), - INCOMPATIBLE_SPRITE(0, 5), - PING_1_SPRITE(0, 4), - PING_2_SPRITE(0, 3), - PING_3_SPRITE(0, 2), - PING_4_SPRITE(0, 3), - PING_5_SPRITE(0, 0), - PINGING_1_SPRITE(10, 0), - PINGING_2_SPRITE(10, 1), - PINGING_3_SPRITE(10, 2), - PINGING_4_SPRITE(10, 3), - PINGING_5_SPRITE(10, 4), - JOIN_HIGHLIGHTED_SPRITE(0, 32, 32, 32, SERVER_SELECTION_TEXTURE), - JOIN_SPRITE(0, 0, 32, 32, SERVER_SELECTION_TEXTURE), - - ; - private final int u, v, texWidth, texHeight; - private final Identifier atlas; - - Sprite(int u, int v, int texWidth, int texHeight, Identifier texture) { - this.u = u; - this.v = v; - this.texWidth = texWidth; - this.texHeight = texHeight; - this.atlas = texture; - } - - Sprite(int u, int v) { - this(u, 176 + v * 8, STATUS_ICON_WIDTH, STATUS_ICON_HEIGHT, ICONS); - } - - public void draw(int x, int y) { - GlStateManager.color4f(1, 1, 1, 1); - Minecraft.getInstance().getTextureManager().bind(atlas); - GuiElement.drawTexture(x, y, u, v, texWidth, texHeight, 256, 256); - } - - } -} diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/multiplayer/ServerInfoUtil.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/api/multiplayer/ServerInfoUtil.java deleted file mode 100644 index 10303bab6..000000000 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/multiplayer/ServerInfoUtil.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright © 2025 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api.multiplayer; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; - -import com.mojang.authlib.GameProfile; -import io.github.axolotlclient.api.types.Status; -import io.github.axolotlclient.api.util.UUIDHelper; -import net.minecraft.client.options.ServerListEntry; -import net.minecraft.client.resource.language.I18n; -import net.minecraft.server.ServerStatus; -import net.minecraft.text.Formatting; -import net.minecraft.text.LiteralText; -import org.apache.commons.lang3.StringUtils; - -public class ServerInfoUtil { - public static Status.Activity.ServerInfo getServerInfo(String levelName, ServerStatus status) { - if (status == null) { - return new Status.Activity.ServerInfo(levelName, null, null, null, null); - } - return new Status.Activity.ServerInfo(levelName, status.getDescription().getString(), - Optional.ofNullable(status.getFavicon()).map(Status.Activity.ServerInfo.Favicon::fromString).orElse(null), - Optional.ofNullable(status.getPlayers()).map(p -> - new Status.Activity.ServerInfo.Players(p.getMax(), p.getOnline(), - Arrays.stream(p.get()).map(prof -> new Status.Activity.ServerInfo.Players.Player(prof.getName(), UUIDHelper.toUndashed(prof.getId()))).toList()) - ).orElse(null), - Optional.ofNullable(status.getVersion()).map(v -> new Status.Activity.ServerInfo.Version(v.getName(), v.getProtocol())).orElse(null)); - } - - public static ServerStatus getServerStatus(Status.Activity.ServerInfo info) { - var metadata = new ServerStatus(); - metadata.setDescription(new LiteralText(info.levelName())); - Optional.ofNullable(info.players()).map(p -> { - var players = new ServerStatus.Players(p.max(), - p.online()); - players.set(p.sample().stream().map(prof -> new GameProfile(UUIDHelper.fromUndashed(prof.uuid()), prof.name())).toArray(GameProfile[]::new)); - return players; - }).ifPresent(metadata::setPlayers); - Optional.ofNullable(info.icon()).map(Status.Activity.ServerInfo.Favicon::toString).ifPresent(metadata::setFavicon); - Optional.ofNullable(info.version()).map(v -> new ServerStatus.Version(v.name(), v.protocol())).ifPresent(metadata::setVersion); - return metadata; - } - - public static ServerListEntry getServerData(String username, Status.Activity.E4mcMetadata metadata) { - ServerStatus serverMetadata = metadata.serverInfo() != null ? getServerStatus(metadata.serverInfo()) : null; - var data = new ServerListEntry(username, metadata.domain(), false); - if (serverMetadata != null) { - if (serverMetadata.getFavicon().startsWith("data:image/png;base64,")) { - data.setIcon(serverMetadata.getFavicon().substring("data:image/png;base64,".length())); - } else { - data.setIcon(null); - } - data.motd = metadata.serverInfo().levelName(); - Optional.ofNullable(serverMetadata.getVersion()).ifPresentOrElse(ver -> { - data.version = ver.getName(); - data.protocol = ver.getProtocol(); - }, () -> { - data.version = I18n.translate("multiplayer.status.old"); - data.protocol = 0; - }); - Optional.ofNullable(serverMetadata.getPlayers()).ifPresentOrElse(player -> { - data.onlinePlayers = Formatting.GRAY - + "" - + player.getOnline() - + Formatting.DARK_GRAY - + "/" - + Formatting.GRAY - + player.getMax(); - if (player.get().length > 0) { - List list = new ArrayList<>(player.get().length); - - for (GameProfile gameProfile : player.get()) { - list.add(gameProfile.getName()); - } - - if (player.get().length < player.getOnline()) { - list.add(I18n.translate("multiplayer.status.and_more", player.getOnline() - player.get().length)); - } - - data.onlinePlayers = StringUtils.join(list, "\n"); - } else { - data.onlinePlayers = ""; - } - }, () -> data.motd = I18n.translate("multiplayer.status.unknown").formatted(Formatting.DARK_GRAY)); - } - return data; - } -} diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/config/ui/MenuCatalog.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/config/ui/MenuCatalog.java index d548aa380..00d3f4c21 100644 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/config/ui/MenuCatalog.java +++ b/versions/1.8.9/src/main/java/io/github/axolotlclient/config/ui/MenuCatalog.java @@ -244,7 +244,7 @@ private static boolean isPosition(Option option) { } private static boolean isSettings(String name) { - return "general".equals(name) || "api.category".equals(name); + return "general".equals(name); } private static OptionCategory find(Collection categories, String name) { diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/mixin/EntityRendererMixin.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/mixin/EntityRendererMixin.java index 1086af7c1..05ddda834 100644 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/mixin/EntityRendererMixin.java +++ b/versions/1.8.9/src/main/java/io/github/axolotlclient/mixin/EntityRendererMixin.java @@ -30,7 +30,6 @@ import io.github.axolotlclient.bridge.AxoPerspective; import io.github.axolotlclient.modules.hypixel.LevelHead; import io.github.axolotlclient.modules.hypixel.bedwars.BedwarsMod; -import io.github.axolotlclient.util.BadgeRenderer; import io.github.axolotlclient.util.Util; import net.minecraft.client.Minecraft; import net.minecraft.client.entity.living.player.ClientPlayerEntity; @@ -65,12 +64,6 @@ public abstract class EntityRendererMixin { } } - @Inject(method = "renderNameTag(Lnet/minecraft/entity/Entity;Ljava/lang/String;DDDI)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/TextRenderer;draw(Ljava/lang/String;III)I", ordinal = 1)) - public void axolotlclient$addBadges(T entity, String string, double d, double e, double f, int i, CallbackInfo ci) { - if (entity instanceof ClientPlayerEntity && string.equals(entity.getDisplayName().getFormattedString())) - BadgeRenderer.renderNametagBadge(entity); - } - @WrapOperation(method = "renderNameTag(Lnet/minecraft/entity/Entity;Ljava/lang/String;DDDI)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/TextRenderer;draw(Ljava/lang/String;III)I", ordinal = 1)) public int axolotlclient$forceShadows(TextRenderer instance, String string, int x, int y, int color, Operation original, Entity entity) { if (DolphinClient.config().useShadows.get() && !entity.isSneaking()) { diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/mixin/GameMenuScreenMixin.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/mixin/GameMenuScreenMixin.java index d6c56a8ab..50e250c3b 100644 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/mixin/GameMenuScreenMixin.java +++ b/versions/1.8.9/src/main/java/io/github/axolotlclient/mixin/GameMenuScreenMixin.java @@ -29,10 +29,6 @@ import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import io.github.axolotlclient.DolphinClient; import io.github.axolotlclient.DolphinClientConfigCommon; -import io.github.axolotlclient.api.API; -import io.github.axolotlclient.api.APIOptions; -import io.github.axolotlclient.api.ChatsSidebar; -import io.github.axolotlclient.api.FriendsScreen; import io.github.axolotlclient.modules.hud.HudEditScreen; import io.github.axolotlclient.modules.hypixel.HypixelAbstractionLayer; import io.github.axolotlclient.modules.hypixel.HypixelMods; @@ -63,15 +59,6 @@ public abstract class GameMenuScreenMixin extends Screen { @Inject(method = "init", at = @At("RETURN")) public void axolotlclient$addConfigButton(CallbackInfo ci) { - if (API.getInstance().isAuthenticated()) { - int buttonY = height - 30; - if (APIOptions.getInstance().addShortcutButtons.get()) { - buttons.add(new ButtonWidget(134, 10, buttonY, 75, 20, I18n.translate("api.friends"))); - buttonY -= 25; - } - buttons.add(new ButtonWidget(234, 10, buttonY, 75, 20, I18n.translate("api.chats"))); - } - if (!DolphinClientConfigCommon.instance().gameMenuScreenOptionButtonMode.get().showButton()) return; @@ -118,10 +105,6 @@ public abstract class GameMenuScreenMixin extends Screen { HypixelMods.HypixelApiCacheMode.ON_CLIENT_DISCONNECT)) { HypixelAbstractionLayer.getInstance().clearPlayerData(); } - } else if (button.id == 234) { - minecraft.openScreen(new ChatsSidebar(this)); - } else if (button.id == 134) { - minecraft.openScreen(new FriendsScreen(this)); } } diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/mixin/MinecraftClientMixin.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/mixin/MinecraftClientMixin.java index 21b5beba8..b4e2a4c6d 100644 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/mixin/MinecraftClientMixin.java +++ b/versions/1.8.9/src/main/java/io/github/axolotlclient/mixin/MinecraftClientMixin.java @@ -136,7 +136,7 @@ protected MinecraftClientMixin(TextureManager textureManager) { @Inject(method = "init", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/texture/TextureManager;close(Lnet/minecraft/resource/Identifier;)V")) private void axolotlclient$onLaunch(CallbackInfo ci) { HudManager.getInstance().refreshAllBounds(); - if (!API.getInstance().isSocketConnected() && !Auth.getInstance().getCurrent().isOffline()) { + if (!API.getInstance().isAuthenticated() && !Auth.getInstance().getCurrent().isOffline()) { API.getInstance().startup(Auth.getInstance().getCurrent()); } } diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/mixin/PlayerListHudMixin.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/mixin/PlayerListHudMixin.java index 9c90db48e..dd617eff0 100644 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/mixin/PlayerListHudMixin.java +++ b/versions/1.8.9/src/main/java/io/github/axolotlclient/mixin/PlayerListHudMixin.java @@ -34,11 +34,6 @@ import com.llamalad7.mixinextras.sugar.Local; import com.llamalad7.mixinextras.sugar.Share; import com.llamalad7.mixinextras.sugar.ref.LocalRef; -import com.mojang.authlib.GameProfile; -import io.github.axolotlclient.DolphinClient; -import io.github.axolotlclient.DolphinClientCommon; -import io.github.axolotlclient.DolphinClientConfigCommon; -import io.github.axolotlclient.api.requests.UserRequest; import io.github.axolotlclient.bridge.impl.AxoRenderContextImpl; import io.github.axolotlclient.modules.hud.HudManagerCommon; import io.github.axolotlclient.modules.hud.gui.hud.vanilla.PlayerTabOverlayHud; @@ -52,7 +47,6 @@ import net.minecraft.client.network.PlayerInfo; import net.minecraft.client.render.TextRenderer; import net.minecraft.network.Connection; -import net.minecraft.resource.Identifier; import net.minecraft.scoreboard.Scoreboard; import net.minecraft.scoreboard.ScoreboardObjective; import net.minecraft.text.Text; @@ -65,8 +59,6 @@ @Mixin(PlayerTabOverlay.class) public abstract class PlayerListHudMixin extends GuiElement { - @Unique - private final Minecraft axolotlclient$client = Minecraft.getInstance(); @Shadow private Text header; @Shadow @@ -86,45 +78,13 @@ public abstract class PlayerListHudMixin extends GuiElement { @WrapOperation(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/TextRenderer;getWidth(Ljava/lang/String;)I", ordinal = 0)) private int axolotlclient$moveName(TextRenderer instance, String string, Operation original, @Local PlayerInfo entry) { var width = original.call(instance, string); - if (DolphinClient.config().showBadges.get()) { - if (DolphinClient.config().tabBadgeMode.get() == DolphinClientConfigCommon.TabBadgeMode.BEFORE_NAME_ALIGNED || UserRequest.getOnline(entry.getProfile().getId().toString())) { - width += 9; - } - } if (((PlayerTabOverlayHud) HudManagerCommon.getInstance().get(PlayerTabOverlayHud.ID)).numericalPing.get()) width += instance.getWidth(String.valueOf(entry.getPing())) - 10; return width; } - @WrapOperation(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/TextRenderer;drawWithShadow(Ljava/lang/String;FFI)I", ordinal = 1)) - public int axolotlclient$moveName2(TextRenderer instance, String string, float x, float y, int color, Operation original, @Local GameProfile entry) { - if (DolphinClient.config().showBadges.get() && - (DolphinClient.config().tabBadgeMode.get() == DolphinClientConfigCommon.TabBadgeMode.BEFORE_NAME || - DolphinClient.config().tabBadgeMode.get() == DolphinClientConfigCommon.TabBadgeMode.BEFORE_NAME_ALIGNED)) { - if (UserRequest.getOnline(entry.getId().toString())) { - axolotlclient$client.getTextureManager().bind((Identifier) DolphinClientCommon.BADGE_PATH); - GuiElement.drawTexture((int) x, (int) y, 0, 0, 8, 8, 8, 8); - x += 9; - } else if (DolphinClient.config().tabBadgeMode.get() == DolphinClientConfigCommon.TabBadgeMode.BEFORE_NAME_ALIGNED) { - x += 9; - } - } - return original.call(instance, string, x, y, color); - } - - @WrapOperation(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/TextRenderer;drawWithShadow(Ljava/lang/String;FFI)I", ordinal = 2)) - public int axolotlclient$moveName3(TextRenderer instance, String string, float x, float y, int color, Operation original, @Local GameProfile entry) { - return axolotlclient$moveName2(instance, string, x, y, color, original, entry); - } - @Inject(method = "renderPing", at = @At("HEAD"), cancellable = true) private void axolotlclient$numericalPing(int width, int x, int y, PlayerInfo entry, CallbackInfo ci) { - if (DolphinClient.config().showBadges.get() && DolphinClient.config().tabBadgeMode.get() == DolphinClientConfigCommon.TabBadgeMode.BEFORE_PING - && UserRequest.getOnline(entry.getProfile().getId().toString())) { - var pingWidth = (((PlayerTabOverlayHud) HudManagerCommon.getInstance().get(PlayerTabOverlayHud.ID)).numericalPing.get()) ? Minecraft.getInstance().textRenderer.br$getWidth(String.valueOf(entry.getPing())) + 1 : 11; - axolotlclient$client.getTextureManager().bind((Identifier) DolphinClientCommon.BADGE_PATH); - GuiElement.drawTexture(x + width - pingWidth - 9, y, 0, 0, 8, 8, 8, 8); - } if (BedwarsMod.getInstance().isEnabled() && BedwarsMod.getInstance().customTabList.get() && BedwarsMod.getInstance().blockLatencyIcon() && (BedwarsMod.getInstance().isWaiting() || BedwarsMod.getInstance().inGame())) { ci.cancel(); diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/mixin/TitleScreenMixin.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/mixin/TitleScreenMixin.java index 8869c18d8..2905a5943 100644 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/mixin/TitleScreenMixin.java +++ b/versions/1.8.9/src/main/java/io/github/axolotlclient/mixin/TitleScreenMixin.java @@ -28,28 +28,18 @@ import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; -import java.net.URI; import java.util.ArrayList; import java.util.List; import com.llamalad7.mixinextras.sugar.Local; import io.github.axolotlclient.DolphinClientCommon; import io.github.axolotlclient.DolphinClientConfigCommon; -import io.github.axolotlclient.api.API; -import io.github.axolotlclient.api.APIOptions; -import io.github.axolotlclient.api.FriendsScreen; -import io.github.axolotlclient.api.NewsScreen; -import io.github.axolotlclient.api.chat.ChatListScreen; -import io.github.axolotlclient.api.requests.GlobalDataRequest; import io.github.axolotlclient.modules.auth.AccountsScreen; import io.github.axolotlclient.modules.auth.Auth; import io.github.axolotlclient.modules.auth.AuthWidget; import io.github.axolotlclient.modules.hud.HudEditScreen; -import io.github.axolotlclient.util.OSUtil; -import io.github.axolotlclient.util.ThreadExecuter; import net.fabricmc.loader.api.FabricLoader; import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.screen.ConfirmChatLinkScreen; import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.screen.TitleScreen; import net.minecraft.client.gui.widget.ButtonWidget; @@ -74,46 +64,10 @@ public abstract class TitleScreenMixin extends Screen { @Inject(method = "initWidgetsNormal", at = @At("TAIL")) private void axolotlclient$replaceRealmsButton(int i, int j, CallbackInfo ci) { List buttons = new ArrayList<>(); - int leftButtonY = 10; if (Auth.getInstance().showButton.get()) { - buttons.add(new AuthWidget(10, leftButtonY)); - leftButtonY += 24; + buttons.add(new AuthWidget(10, 10)); } this.buttons.addAll(buttons); - if (APIOptions.getInstance().addShortcutButtons.get()) { - int y = leftButtonY; - Runnable addApiButtons = () -> { - ButtonWidget friends = new ButtonWidget(142, 10, y, 50, 20, I18n.translate("api.friends")); - this.buttons.add(friends); - buttons.add(friends); - ButtonWidget chats = new ButtonWidget(42, 10, y + 24, 50, 20, I18n.translate("api.chats")); - this.buttons.add(chats); - buttons.add(chats); - }; - if (API.getInstance().isSocketConnected()) { - addApiButtons.run(); - } else { - API.addStartupListener(() -> minecraft.executeTask(addApiButtons), API.ListenerType.ONCE); - } - } - ThreadExecuter.scheduleTask(() -> GlobalDataRequest.get().thenAccept(data -> { - int buttonY = 10; - if (APIOptions.getInstance().updateNotifications.get() && - data.success() && - data.latestVersion().isNewerThan(DolphinClientCommon.VERSION)) { - ButtonWidget newVersion = new ButtonWidget(182, width - 90, buttonY, 80, 20, I18n.translate("api.new_version_available")); - this.buttons.add(newVersion); - buttons.add(newVersion); - buttonY += 24; - } - if (APIOptions.getInstance().displayNotes.get() && - data.success() && !data.notes().isEmpty()) { - ButtonWidget notes = new ButtonWidget(253, width - 90, buttonY, 80, 20, - I18n.translate("api.notes")); - this.buttons.add(notes); - buttons.add(notes); - } - })); if (FabricLoader.getInstance().isModLoaded("modmenu")) { try { @@ -158,17 +112,6 @@ public abstract class TitleScreenMixin extends Screen { Minecraft.getInstance().openScreen(new HudEditScreen(this)); else if (button.id == 242) Minecraft.getInstance().openScreen(new AccountsScreen(Minecraft.getInstance().screen)); - else if (button.id == 182) - Minecraft.getInstance().openScreen(new ConfirmChatLinkScreen((bl, i) -> { - if (bl && i == 353) { - OSUtil.getOS().open(URI.create("https://modrinth.com/mod/axolotlclient/versions")); - } - Minecraft.getInstance().openScreen(this); - }, "https://modrinth.com/mod/axolotlclient/versions", 353, true)); - else if (button.id == 253) - Minecraft.getInstance().openScreen(new NewsScreen(this)); - else if (button.id == 142) minecraft.openScreen(new FriendsScreen(this)); - else if (button.id == 42) minecraft.openScreen(new ChatListScreen(this)); } @Redirect(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/TitleScreen;drawString(Lnet/minecraft/client/render/TextRenderer;Ljava/lang/String;III)V", ordinal = 0)) diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/mixin/api/JoinMulitplayerScreenMixin.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/mixin/api/JoinMulitplayerScreenMixin.java deleted file mode 100644 index 5247c9193..000000000 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/mixin/api/JoinMulitplayerScreenMixin.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright © 2025 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.mixin.api; - -import com.llamalad7.mixinextras.injector.wrapoperation.Operation; -import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; -import io.github.axolotlclient.api.API; -import io.github.axolotlclient.api.multiplayer.FriendsMultiplayerScreen; -import io.github.axolotlclient.api.requests.FriendRequest; -import net.fabricmc.loader.api.FabricLoader; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.gui.screen.multiplayer.MultiplayerScreen; -import net.minecraft.client.gui.screen.multiplayer.MultiplayerServerListWidget; -import net.minecraft.client.gui.widget.ButtonWidget; -import net.minecraft.client.resource.language.I18n; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.Unique; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.ModifyArg; -import org.spongepowered.asm.mixin.injection.ModifyArgs; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import org.spongepowered.asm.mixin.injection.invoke.arg.Args; - -@Mixin(MultiplayerScreen.class) -public abstract class JoinMulitplayerScreenMixin extends Screen { - - @Shadow - private Screen parent; - @Unique - private static final boolean WORLD_HOST_INSTALLED = FabricLoader.getInstance().isModLoaded("world-host"); - - protected JoinMulitplayerScreenMixin() { - super(); - } - - @Inject(method = "addButtons", at = @At(value = "HEAD")) - private void addFriendsMultiplayerScreenButtons(CallbackInfo ci) { - if (API.getInstance().isAuthenticated() && !WORLD_HOST_INSTALLED) { - var serversButton = new ButtonWidget(-1, this.width / 2 - 102, 32, 100, 20, I18n.translate("api.servers")); - buttons.add(serversButton); - serversButton.active = false; - ButtonWidget friendsCountButton = new ButtonWidget(274, width / 2 + 2, 32, 100, 20, I18n.translate("api.servers.friends", "...")); - buttons.add(friendsCountButton); - FriendRequest.getInstance().getOnlineFriendCount().thenAccept(count -> friendsCountButton.message = (I18n.translate("api.servers.friends", count))); - } - } - - @Inject(method = "buttonClicked", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/multiplayer/MultiplayerServerListWidget;getCurrentServerIndex()I", ordinal = 0), cancellable = true) - private void onButtonClick(ButtonWidget buttonWidget, CallbackInfo ci) { - if (buttonWidget.id == 274) { - minecraft.openScreen(new FriendsMultiplayerScreen(this.parent)); - ci.cancel(); - } - } - - @WrapOperation(method = "init", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/multiplayer/MultiplayerServerListWidget;setBounds(IIII)V")) - private void increaseHeaderSize(MultiplayerServerListWidget instance, int width, int height, int top, int bottom, Operation original) { - if (API.getInstance().isAuthenticated() && !WORLD_HOST_INSTALLED) { - top -= 32; - top += 60; - } - original.call(instance, width, height, top, bottom); - } - - @ModifyArgs(method = "init", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/multiplayer/MultiplayerServerListWidget;(Lnet/minecraft/client/gui/screen/multiplayer/MultiplayerScreen;Lnet/minecraft/client/Minecraft;IIIII)V")) - private void increaseHeaderSize$2(Args args) { - if (API.getInstance().isAuthenticated() && !WORLD_HOST_INSTALLED) { - args.set(4, ((Integer) args.get(4)) - 32 + 60); - } - } - - @ModifyArg(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/multiplayer/MultiplayerScreen;drawCenteredString(Lnet/minecraft/client/render/TextRenderer;Ljava/lang/String;III)V"), index = 3) - private int shiftTitle(int par3) { - if (API.getInstance().isAuthenticated() && !WORLD_HOST_INSTALLED) { - return 15; - } - return par3; - } -} diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/auth/Auth.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/auth/Auth.java index 302379471..34f79dbb9 100644 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/auth/Auth.java +++ b/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/auth/Auth.java @@ -32,7 +32,6 @@ import io.github.axolotlclient.DolphinClientCommon; import io.github.axolotlclient.AxolotlClientConfig.impl.options.BooleanOption; import io.github.axolotlclient.api.API; -import io.github.axolotlclient.api.types.User; import io.github.axolotlclient.api.util.UUIDHelper; import io.github.axolotlclient.mixin.MinecraftClientAccessor; import io.github.axolotlclient.modules.Module; @@ -166,10 +165,6 @@ public Identifier getSkinTexture(Account account) { return getSkinTexture(account.getUuid(), account.getName()); } - public Identifier getSkinTexture(User user) { - return getSkinTexture(user.getUuid(), user.getName()); - } - public Identifier getSkinTexture(String uuid, String name) { loadTextures(uuid, name); Identifier id; diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/auth/AuthWidget.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/auth/AuthWidget.java index 51966dbf2..7a56c334f 100644 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/auth/AuthWidget.java +++ b/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/auth/AuthWidget.java @@ -44,7 +44,7 @@ public void render(Minecraft minecraftClient, int i, int j) { drawTexture(x + 1, y + 1, 8, 8, 8, 8, height - 2, height - 2, 64, 64); drawTexture(x + 1, y + 1, 40, 8, 8, 8, height - 2, height - 2, 64, 64); GlStateManager.disableBlend(); - if (API.getInstance().getApiOptions().enabled.get()) { + if (API.getInstance().getApiOptions().privacyAccepted.get().isAccepted()) { GlStateManager.pushMatrix(); GlStateManager.translatef(x + height - 1, y + height - 1, 0); GlStateManager.scalef(0.25f, 0.25f, 1); diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/screenshotUtils/GalleryScreen.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/screenshotUtils/GalleryScreen.java index bddb97ad9..7d5c1e167 100644 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/screenshotUtils/GalleryScreen.java +++ b/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/screenshotUtils/GalleryScreen.java @@ -31,9 +31,6 @@ import java.util.stream.Stream; import io.github.axolotlclient.AxolotlClientConfig.api.util.Colors; -import io.github.axolotlclient.api.API; -import io.github.axolotlclient.api.requests.FriendRequest; -import io.github.axolotlclient.api.requests.UserRequest; import io.github.axolotlclient.util.DrawUtil; import io.github.axolotlclient.util.MathUtil; import io.github.axolotlclient.util.Watcher; @@ -81,17 +78,6 @@ private static Tab of(String title, Callable> list, Loader loa } }, ImageInstance.LocalImpl::new); - private static final Tab SHARED = of(I18n.translate("gallery.title.shared"), () -> - FriendRequest.getInstance().getFriendUuids() - .thenApply(res -> res.stream().map(UserRequest::getUploadedImages) - .map(CompletableFuture::join) - .filter(Optional::isPresent) - .map(Optional::get) - .reduce(new ArrayList<>(), (l1, l2) -> { - l1.addAll(l2); - return l1; - })).join(), url -> ImageShare.getInstance().downloadImage(url).join()); - interface Loader { ImageInstance load(T obj) throws Exception; } @@ -120,7 +106,7 @@ public GalleryScreen(Screen parent) { entryHeight = 75, marginLeftRight = 10; - private boolean isError, online; + private boolean isError; private ImageList area; @Override @@ -129,12 +115,7 @@ public void render(int mouseX, int mouseY, float delta) { area.render(mouseX, mouseY, delta); super.render(mouseX, mouseY, delta); - if (online) { - drawCenteredString(textRenderer, title, width / 2, 40 / 2 - 2 - textRenderer.fontHeight, -1); - drawCenteredString(textRenderer, current.title(), width / 2, 40 / 2 + 2, -1); - } else { - drawCenteredString(textRenderer, title, width / 2, 40 / 2 - textRenderer.fontHeight / 2, -1); - } + drawCenteredString(textRenderer, title, width / 2, 40 / 2 - textRenderer.fontHeight / 2, -1); if (isError) { drawCenteredString(textRenderer, I18n.translate("gallery.error.loading"), width / 2, 36, -1); @@ -161,8 +142,6 @@ protected void mouseReleased(int i, int j, int k) { @Override public void init() { - online = API.getInstance().isAuthenticated(); - int columnCount = (width - (marginLeftRight * 2) + entrySpacing - 13) / (entryWidth + entrySpacing); // -13 to always have enough space for the scrollbar area = new ImageList(minecraft, width, height, 33, height - 40, entryHeight + entrySpacing, columnCount); @@ -177,19 +156,9 @@ public void init() { } }); - int buttonWidth = columnCount <= 5 && online ? 100 : 150; - int footerButtonX = online ? width / 2 - buttonWidth - buttonWidth / 2 - 4 : width / 2 - buttonWidth - 2; + int buttonWidth = 150; + int footerButtonX = width / 2 - buttonWidth - 2; int footerButtonY = height - 33 / 2 - 10; - if (online) { - ButtonWidget switchTab; - if (current == Tab.SHARED) { - switchTab = new ButtonWidget(1, footerButtonX, footerButtonY, buttonWidth, 20, I18n.translate("gallery.tab.local")); - } else { - switchTab = new ButtonWidget(2, footerButtonX, footerButtonY, buttonWidth, 20, I18n.translate("gallery.tab.shared")); - } - buttons.add(switchTab); - footerButtonX += buttonWidth + 4; - } buttons.add(new ButtonWidget(3, footerButtonX, footerButtonY, buttonWidth, 20, I18n.translate("gallery.download_external"))); footerButtonX += buttonWidth + 4; buttons.add(new ButtonWidget(4, footerButtonX, footerButtonY, buttonWidth, 20, I18n.translate("gui.back"))); @@ -199,8 +168,6 @@ public void init() { protected void buttonClicked(ButtonWidget buttonWidget) { switch (buttonWidget.id) { case 0 -> init(minecraft, width, height); - case 1 -> setTab(Tab.LOCAL); - case 2 -> setTab(Tab.SHARED); case 3 -> minecraft.openScreen(new DownloadImageScreen(this)); case 4 -> { Tab.LOCAL.loadingCache().forEach((path, instance) -> { @@ -209,23 +176,12 @@ protected void buttonClicked(ButtonWidget buttonWidget) { } }); Tab.LOCAL.loadingCache().clear(); - Tab.SHARED.loadingCache().forEach((s, instance) -> { - if (instance != null) { - minecraft.getTextureManager().close(instance.id()); - } - }); - Tab.SHARED.loadingCache().clear(); Watcher.close(watcher); minecraft.openScreen(parent); } } } - private void setTab(Tab tab) { - current = tab; - init(minecraft, width, height); - } - private void loadTab(Tab tab, int columnCount, ImageList area) throws Exception { List images = tab.list.call(); int size = images.size(); diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/screenshotUtils/ImageScreen.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/screenshotUtils/ImageScreen.java index a06ecf9ae..bb36fd9dd 100644 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/screenshotUtils/ImageScreen.java +++ b/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/screenshotUtils/ImageScreen.java @@ -160,7 +160,7 @@ protected void buttonClicked(ButtonWidget b) { if (s.isEmpty()) { Notifications.getInstance().addStatus("gallery.image.upload.failure", "gallery.image.upload.failure.description"); } else { - minecraft.executeTask(() -> minecraft.openScreen(new ImageScreen(parent, local.toShared(s, API.getInstance().getSelf().getUuid(), Instant.now()), freeOnClose))); + minecraft.executeTask(() -> minecraft.openScreen(new ImageScreen(parent, local.toShared(s, API.getInstance().getSelfUuid(), Instant.now()), freeOnClose))); setClipboard(s); Notifications.getInstance().addStatus("gallery.image.upload.success", "gallery.image.upload.success.description", s); } diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/util/BadgeRenderer.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/util/BadgeRenderer.java deleted file mode 100644 index d0450451e..000000000 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/util/BadgeRenderer.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * Copyright © 2026 DSNS - * - * This file is part of DolphinClient, a fork of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.util; - -import io.github.axolotlclient.DolphinClient; -import io.github.axolotlclient.DolphinClientCommon; -import io.github.axolotlclient.api.requests.UserRequest; -import io.github.axolotlclient.modules.hypixel.NickHider; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiElement; -import net.minecraft.client.render.TextRenderer; -import net.minecraft.client.render.platform.GlStateManager; -import net.minecraft.entity.Entity; -import net.minecraft.entity.living.player.PlayerEntity; -import net.minecraft.resource.Identifier; - -public class BadgeRenderer { - public static void renderNametagBadge(Entity entity) { - if (!(entity instanceof PlayerEntity) || entity.isSneaking()) { - return; - } - - if (!DolphinClient.config().showBadges.get() || !UserRequest.getOnline(entity.getUuid().toString())) { - return; - } - - TextRenderer textRenderer = Minecraft.getInstance().textRenderer; - - int x = -(textRenderer - .getWidth(entity.getUuid() == Minecraft.getInstance().player.getUuid() - ? (NickHider.getInstance().hideOwnName.get() ? NickHider.getInstance().hiddenNameSelf.get() - : entity.getDisplayName().getFormattedString()) - : (NickHider.getInstance().hideOtherNames.get() ? NickHider.getInstance().hiddenNameOthers.get() - : entity.getDisplayName().getFormattedString())) - / 2 - + (DolphinClient.config().customBadge.get() ? textRenderer - .getWidth(" " + DolphinClient.config().badgeText.get()) : 10)); - - GlStateManager.color4f(1, 1, 1, 1); - - if (DolphinClient.config().customBadge.get()) - textRenderer.draw(DolphinClient.config().badgeText.get(), x, 0, -1, DolphinClient.config().useShadows.get()); - else { - Minecraft.getInstance().getTextureManager().bind((Identifier) DolphinClientCommon.BADGE_PATH); - GuiElement.drawTexture(x, 0, 0, 0, 8, 8, 8, 8); - } - } -} diff --git a/versions/1.8.9/src/main/resources/axolotlclient.e4mc.mixins.json b/versions/1.8.9/src/main/resources/axolotlclient.e4mc.mixins.json deleted file mode 100644 index d82534e51..000000000 --- a/versions/1.8.9/src/main/resources/axolotlclient.e4mc.mixins.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "required": true, - "package": "io.github.axolotlclient.api.e4mc.mixin", - "plugin": "io.github.axolotlclient.api.e4mc.E4mcMixinPlugin", - "compatibilityLevel": "JAVA_17", - "client": [ - "QuiclimeSessionInnerMixin", - "QuiclimeSessionMixin" - ], - "injectors": { - "defaultRequire": 1 - } -} diff --git a/versions/1.8.9/src/main/resources/axolotlclient.mixins.json b/versions/1.8.9/src/main/resources/axolotlclient.mixins.json index 78d4fb4a2..b5575b66e 100644 --- a/versions/1.8.9/src/main/resources/axolotlclient.mixins.json +++ b/versions/1.8.9/src/main/resources/axolotlclient.mixins.json @@ -68,7 +68,6 @@ "VertexBufferMixin", "WorldRendererAccessor", "WorldRendererMixin", - "api.JoinMulitplayerScreenMixin", "commands.ChatScreenMixin", "commands.LocalClientPlayerEntityMixin", "translation.LanguageMixin", diff --git a/versions/1.8.9/src/main/resources/fabric.mod.json b/versions/1.8.9/src/main/resources/fabric.mod.json index e0b6532a0..5034688e2 100644 --- a/versions/1.8.9/src/main/resources/fabric.mod.json +++ b/versions/1.8.9/src/main/resources/fabric.mod.json @@ -32,7 +32,6 @@ "mixins": [ "axolotlclient.mixins.json", "axolotlclient-bridge.mixins.json", - "axolotlclient.e4mc.mixins.json", "oldanimations.mixins.json" ], "provides": [ From 11c9dfcee885088ce7775f38f58d2b13b96b5813 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 27 Aug 2026 04:55:47 +0000 Subject: [PATCH 2/3] Remove the online API, terms gate, and Microsoft account tools Drop the backend client and privacy notice so the mod no longer makes AxolotlClient, Mojang, or Microsoft HTTP requests. Account switching and skin management come out of the config, screenshot upload/download is local-only, and Level Head stays in the menu but is forced off. Co-authored-by: Dominic Seung --- common/build.gradle.kts | 6 - .../axolotlclient/DolphinClientCommon.java | 9 - .../java/io/github/axolotlclient/api/API.java | 346 ------- .../io/github/axolotlclient/api/Options.java | 89 -- .../io/github/axolotlclient/api/Request.java | 167 ---- .../io/github/axolotlclient/api/Response.java | 162 ---- .../api/requests/GlobalDataRequest.java | 75 -- .../axolotlclient/api/types/GlobalData.java | 29 - .../axolotlclient/api/types/SemVer.java | 149 --- .../api/util/Authentication.java | 32 - .../axolotlclient/api/util/MojangAuth.java | 148 --- .../axolotlclient/api/util/UUIDHelper.java | 100 -- .../bridge/commands/PlayerArgument.java | 9 +- .../config/migration/ConfigMigration.java | 5 +- .../config/migration/impl/V10Migration.java | 24 +- .../migration/impl/V11Migration.java} | 31 +- .../axolotlclient/modules/auth/Account.java | 122 --- .../axolotlclient/modules/auth/Accounts.java | 128 --- .../modules/auth/DeviceFlowData.java | 79 -- .../axolotlclient/modules/auth/MSApi.java | 533 ---------- .../modules/auth/skin/Asset.java | 52 - .../axolotlclient/modules/auth/skin/Cape.java | 29 - .../axolotlclient/modules/auth/skin/Skin.java | 303 ------ .../modules/auth/skin/SkinImportUtil.java | 49 - .../modules/hud/snapping/SnappingHelper.java | 2 +- .../hypixel/HypixelAbstractionLayer.java | 146 +-- .../modules/hypixel/LevelHead.java | 11 +- .../modules/hypixel/NickHider.java | 2 +- .../modules/hypixel/StatsMod.java | 32 +- .../hypixel/bedwars/BedwarsPlayer.java | 2 +- .../modules/hypixel/bedwars/StatsOverlay.java | 43 +- .../screenshotUtils/ImageNetworking.java | 104 -- .../{api => }/util/BiContainer.java | 2 +- .../github/axolotlclient/util/GsonHelper.java | 1 - .../{api => }/util/InstantTypeAdapter.java | 2 +- .../axolotlclient/util/NetworkUtil.java | 59 -- .../Constants.java => util/UUIDHelper.java} | 33 +- .../util/options/ForceableBooleanOption.java | 4 + .../assets/dolphinclient/lang/en_us.json | 73 +- .../assets/dolphinclient/lang/fr_fr.json | 73 +- .../assets/dolphinclient/lang/pt_pt.json | 48 +- .../axolotlclient/util/UUIDHelperTest.java | 54 ++ .../github/axolotlclient/DolphinClient.java | 11 - .../github/axolotlclient/api/APIOptions.java | 48 - .../api/PrivacyNoticeScreen.java | 107 --- .../api/SimpleTextInputScreen.java | 99 -- .../mixin/MinecraftClientMixin.java | 5 - .../axolotlclient/mixin/TitleScreenMixin.java | 49 +- .../modules/auth/AccountsListWidget.java | 158 --- .../modules/auth/AccountsScreen.java | 227 ----- .../modules/auth/AddOfflineScreen.java | 84 -- .../axolotlclient/modules/auth/Auth.java | 181 ---- .../modules/auth/AuthWidget.java | 59 -- .../modules/auth/DeviceCodeDisplayScreen.java | 117 --- .../modules/auth/skin/LoadingScreen.java | 53 - .../auth/skin/SkinManagementScreen.java | 909 ------------------ .../modules/auth/skin/SkinManager.java | 131 --- .../modules/auth/skin/SkinRenderer.java | 110 --- .../modules/auth/skin/SkinWidget.java | 119 --- .../modules/hud/ModsMenuIcons.java | 2 - .../screenshotUtils/DownloadImageScreen.java | 119 --- .../screenshotUtils/GalleryScreen.java | 6 +- .../modules/screenshotUtils/ImageScreen.java | 31 +- .../modules/screenshotUtils/ImageShare.java | 92 -- .../screenshotUtils/ScreenshotUtils.java | 5 - 65 files changed, 160 insertions(+), 5929 deletions(-) delete mode 100644 common/src/main/java/io/github/axolotlclient/api/API.java delete mode 100644 common/src/main/java/io/github/axolotlclient/api/Options.java delete mode 100644 common/src/main/java/io/github/axolotlclient/api/Request.java delete mode 100644 common/src/main/java/io/github/axolotlclient/api/Response.java delete mode 100644 common/src/main/java/io/github/axolotlclient/api/requests/GlobalDataRequest.java delete mode 100644 common/src/main/java/io/github/axolotlclient/api/types/GlobalData.java delete mode 100644 common/src/main/java/io/github/axolotlclient/api/types/SemVer.java delete mode 100644 common/src/main/java/io/github/axolotlclient/api/util/Authentication.java delete mode 100644 common/src/main/java/io/github/axolotlclient/api/util/MojangAuth.java delete mode 100644 common/src/main/java/io/github/axolotlclient/api/util/UUIDHelper.java rename common/src/main/java/io/github/axolotlclient/{api/util/AlphabeticalComparator.java => config/migration/impl/V11Migration.java} (60%) delete mode 100644 common/src/main/java/io/github/axolotlclient/modules/auth/Account.java delete mode 100644 common/src/main/java/io/github/axolotlclient/modules/auth/Accounts.java delete mode 100644 common/src/main/java/io/github/axolotlclient/modules/auth/DeviceFlowData.java delete mode 100644 common/src/main/java/io/github/axolotlclient/modules/auth/MSApi.java delete mode 100644 common/src/main/java/io/github/axolotlclient/modules/auth/skin/Asset.java delete mode 100644 common/src/main/java/io/github/axolotlclient/modules/auth/skin/Cape.java delete mode 100644 common/src/main/java/io/github/axolotlclient/modules/auth/skin/Skin.java delete mode 100644 common/src/main/java/io/github/axolotlclient/modules/auth/skin/SkinImportUtil.java delete mode 100644 common/src/main/java/io/github/axolotlclient/modules/screenshotUtils/ImageNetworking.java rename common/src/main/java/io/github/axolotlclient/{api => }/util/BiContainer.java (97%) rename common/src/main/java/io/github/axolotlclient/{api => }/util/InstantTypeAdapter.java (97%) delete mode 100644 common/src/main/java/io/github/axolotlclient/util/NetworkUtil.java rename common/src/main/java/io/github/axolotlclient/{api/Constants.java => util/UUIDHelper.java} (57%) create mode 100644 common/src/test/java/io/github/axolotlclient/util/UUIDHelperTest.java delete mode 100644 versions/1.8.9/src/main/java/io/github/axolotlclient/api/APIOptions.java delete mode 100644 versions/1.8.9/src/main/java/io/github/axolotlclient/api/PrivacyNoticeScreen.java delete mode 100644 versions/1.8.9/src/main/java/io/github/axolotlclient/api/SimpleTextInputScreen.java delete mode 100644 versions/1.8.9/src/main/java/io/github/axolotlclient/modules/auth/AccountsListWidget.java delete mode 100644 versions/1.8.9/src/main/java/io/github/axolotlclient/modules/auth/AccountsScreen.java delete mode 100644 versions/1.8.9/src/main/java/io/github/axolotlclient/modules/auth/AddOfflineScreen.java delete mode 100644 versions/1.8.9/src/main/java/io/github/axolotlclient/modules/auth/Auth.java delete mode 100644 versions/1.8.9/src/main/java/io/github/axolotlclient/modules/auth/AuthWidget.java delete mode 100644 versions/1.8.9/src/main/java/io/github/axolotlclient/modules/auth/DeviceCodeDisplayScreen.java delete mode 100644 versions/1.8.9/src/main/java/io/github/axolotlclient/modules/auth/skin/LoadingScreen.java delete mode 100644 versions/1.8.9/src/main/java/io/github/axolotlclient/modules/auth/skin/SkinManagementScreen.java delete mode 100644 versions/1.8.9/src/main/java/io/github/axolotlclient/modules/auth/skin/SkinManager.java delete mode 100644 versions/1.8.9/src/main/java/io/github/axolotlclient/modules/auth/skin/SkinRenderer.java delete mode 100644 versions/1.8.9/src/main/java/io/github/axolotlclient/modules/auth/skin/SkinWidget.java delete mode 100644 versions/1.8.9/src/main/java/io/github/axolotlclient/modules/screenshotUtils/DownloadImageScreen.java delete mode 100644 versions/1.8.9/src/main/java/io/github/axolotlclient/modules/screenshotUtils/ImageShare.java diff --git a/common/build.gradle.kts b/common/build.gradle.kts index b6d3726a0..dcbe343b4 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -36,9 +36,6 @@ dependencies { shadow(runtimeOnly(compileOnly("com.kohlschutter.junixsocket:junixsocket-common:2.10.1")!!)!!) shadow(runtimeOnly(compileOnly("com.kohlschutter.junixsocket:junixsocket-native-common:2.10.1")!!)!!) - shadow(runtimeOnly(compileOnly("com.github.mizosoft.methanol:methanol:1.9.0")!!)!!) - shadow(runtimeOnly(compileOnly("io.nayuki:qrcodegen:1.8.0")!!)!!) - compileOnly("net.hypixel:mod-api:1.0.1") compileOnly("com.mojang:brigadier:1.0.18") @@ -128,15 +125,12 @@ tasks.shadowJar { archiveClassifier.set("") mergeServiceFiles() minimize { - exclude(dependency("com.github.mizosoft.methanol:.*:.*")) exclude(dependency("io.github.CDAGaming:DiscordIPC:.*")) exclude(dependency("com.kohlschutter.junixsocket:junixsocket-common:.*")) exclude(dependency("com.kohlschutter.junixsocket:junixsocket-native-common:.*")) } relocate("com.jagrosh", "io.github.axolotlclient.shadow.jagrosh") - relocate("com.github.mizosoft", "io.github.axolotlclient.shadow.mizosoft") - relocate("io.nayuki", "io.github.axolotlclient.shadow.nayuki") append("../LICENSE") } diff --git a/common/src/main/java/io/github/axolotlclient/DolphinClientCommon.java b/common/src/main/java/io/github/axolotlclient/DolphinClientCommon.java index 1776df435..7ea506eca 100644 --- a/common/src/main/java/io/github/axolotlclient/DolphinClientCommon.java +++ b/common/src/main/java/io/github/axolotlclient/DolphinClientCommon.java @@ -36,8 +36,6 @@ import io.github.axolotlclient.AxolotlClientConfig.api.ui.ConfigUI; import io.github.axolotlclient.AxolotlClientConfig.impl.managers.JsonConfigManager; import io.github.axolotlclient.AxolotlClientConfig.impl.managers.VersionedJsonConfigManager; -import io.github.axolotlclient.api.API; -import io.github.axolotlclient.api.Options; import io.github.axolotlclient.bridge.events.Events; import io.github.axolotlclient.bridge.util.AxoIdentifier; import io.github.axolotlclient.bridge.util.AxoProfiler; @@ -159,7 +157,6 @@ private void addBuiltinCommonModules() { registerModule(Freelook.getInstance()); registerModule(TntTime.getInstance()); registerModule(DiscordRPC.getInstance()); - registerModule(getApiOptions()); registerModule(Zoom.getInstance()); } @@ -230,10 +227,7 @@ protected final void init(NotificationProvider provider) { getFeatureDisabler().init(); - // register events - Events.END_RESOURCE_RELOAD.register(() -> HypixelAbstractionLayer.getInstance().clearPlayerData()); - Events.CLIENT_STOP.register(() -> API.getInstance().shutdown()); } protected final void registerModule(Module module) { @@ -245,8 +239,6 @@ protected final void registerModule(Module module) { protected abstract DolphinClientConfigCommon createConfig(); - public abstract Options getApiOptions(); - // random stuff public void saveConfig() { @@ -280,6 +272,5 @@ public void reloadConfig() { } } lateModuleInit(); - API.getInstance().restart(); } } diff --git a/common/src/main/java/io/github/axolotlclient/api/API.java b/common/src/main/java/io/github/axolotlclient/api/API.java deleted file mode 100644 index d9e7a8702..000000000 --- a/common/src/main/java/io/github/axolotlclient/api/API.java +++ /dev/null @@ -1,346 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * Copyright © 2026 DSNS - * - * This file is part of DolphinClient, a fork of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api; - -import java.net.ConnectException; -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.net.http.HttpTimeoutException; -import java.time.Instant; -import java.util.Map; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicBoolean; - -import io.github.axolotlclient.DolphinClientCommon; -import io.github.axolotlclient.api.requests.GlobalDataRequest; -import io.github.axolotlclient.api.util.Authentication; -import io.github.axolotlclient.api.util.MojangAuth; -import io.github.axolotlclient.modules.auth.Account; -import io.github.axolotlclient.util.GsonHelper; -import io.github.axolotlclient.util.Logger; -import io.github.axolotlclient.util.NetworkUtil; -import io.github.axolotlclient.util.ThreadExecuter; -import io.github.axolotlclient.util.notifications.NotificationProvider; -import lombok.Getter; - -/** - * Client for the backend the Hypixel modules and image sharing talk to. - */ -public class API { - - @Getter - private static API Instance; - @Getter - private final Logger logger; - @Getter - private final NotificationProvider notificationProvider = DolphinClientCommon.getInstance().getNotificationProvider(); - - @Getter - private final Options apiOptions; - /** - * The undashed UUID of the account this session is authenticated as, or {@code null} while unauthenticated. - */ - @Getter - private String selfUuid; - private Account account; - private Authentication auth; - private HttpClient client; - private CompletableFuture restartingFuture; - private int nextRestartSecs; - private final AtomicBoolean starting = new AtomicBoolean(); - - public API() { - if (Instance != null) { - throw new IllegalStateException("API may only be instantiated once!"); - } - this.logger = DolphinClientCommon.getInstance().getLogger(); - this.apiOptions = DolphinClientCommon.getInstance().getApiOptions(); - Instance = this; - } - - private CompletableFuture authenticate() { - //if (client != null) { - // We have to rely on the gc to collect previous client objects as close() was only implemented in java 21. - // However, we are currently compiling against java 17. - //client.close(); - //} - - try { - if (!GlobalDataRequest.get(true).get(1, TimeUnit.MINUTES).success()) { - logger.warn("Not trying to start API as it couldn't be reached!"); - return scheduleRestart(); - } - } catch (InterruptedException | ExecutionException | TimeoutException e) { - logger.warn("Not trying to start API as it couldn't be reached within the timeout of 1 minute!"); - return scheduleRestart(); - } - - nextRestartSecs = 2; - logDetailed("Authenticating with Mojang..."); - - MojangAuth.Result result = MojangAuth.authenticate(account); - - if (result.getStatus() != MojangAuth.Status.SUCCESS) { - logger.error("Failed to authenticate with Mojang! Status: ", result.getStatus()); - return CompletableFuture.failedFuture(new UnsupportedOperationException("Failed to authenticate with mojang, status: " + result.getStatus())); - } - - logDetailed("Requesting authentication from backend..."); - - return get(Request.Route.AUTHENTICATE.builder() - .query("username", account.getName()) - .query("server_id", result.getServerId()) - .build()).whenComplete((response, throwable) -> { - - if (throwable != null) { - logger.error("Failed to authenticate!", throwable); - return; - } - if (response.isError()) { - logger.error("Failed to authenticate!", response.getError().description()); - return; - } - - auth = new Authentication(response.getBody("access_token")); - selfUuid = sanitizeUUID(account.getUuid()); - logDetailed("Obtained token!"); - }); - } - - public CompletableFuture get(Request request) { - return request(request, "GET"); - } - - public CompletableFuture patch(Request request) { - return request(request, "PATCH"); - } - - public CompletableFuture post(Request request) { - return request(request, "POST"); - } - - public CompletableFuture delete(Request request) { - return request(request, "DELETE"); - } - - private CompletableFuture request(Request request, String method) { - if (!getApiOptions().privacyAccepted.get().isAccepted()) { - return CompletableFuture.completedFuture(Response.CLIENT_ERROR); - } - if (request.requiresAuthentication() && !isAuthenticated()) { - logger.debug("Tried to request {} {} without authentication, but this request requires it!", method, request); - return CompletableFuture.completedFuture(Response.CLIENT_ERROR); - } - URI route = getUrl(request); - return request(route, request.bodyFields(), request.rawBody(), method, request.headers()); - } - - private CompletableFuture request(URI url, Map payload, byte[] rawBody, String method, Map headers) { - return CompletableFuture.supplyAsync(() -> { - try { - logDetailed("Starting request to " + method + " " + url); - - HttpRequest.Builder builder = HttpRequest.newBuilder(url) - .header("Content-Type", "application/json") - .header("Accept", "application/json"); - - if (auth != null) { - if (auth.expiration().isBefore(Instant.now())) { - authenticate().join(); - } - builder.header("Authorization", auth.token()); - } - - if (headers != null) { - headers.forEach(builder::header); - } - - if (rawBody != null) { - builder.method(method, HttpRequest.BodyPublishers.ofByteArray(rawBody)); - } else if (!(payload == null || payload.isEmpty())) { - StringBuilder body = new StringBuilder(); - GsonHelper.GSON.toJson(payload, body); - logDetailed("Sending payload: \n" + body); - builder.method(method, HttpRequest.BodyPublishers.ofString(body.toString())); - } else { - builder.method(method, HttpRequest.BodyPublishers.noBody()); - } - if (client == null) { - client = NetworkUtil.createHttpClient(); - } - - HttpResponse response = client.send(builder.build(), HttpResponse.BodyHandlers.ofString()); - - String body = response.body(); - - int code = response.statusCode(); - if (!url.getPath().endsWith(Request.Route.AUTHENTICATE.getPath())) { - logDetailed("Response: code: " + code + " body: " + body); - } else { - logDetailed("Response: code: " + code + " body: " + String.valueOf(body).replaceAll("(\"access_token\": ?\")[^\"]+(\")", "$1[token redacted]$2")); - } - return Response.builder().body(body).status(code).headers(response.headers().map()).build(); - } catch (ConnectException | HttpTimeoutException e) { - logger.warn("Backend unreachable!"); - return Response.CLIENT_ERROR; - } catch (Exception e) { - onError(e); - return Response.CLIENT_ERROR; - } - }, ThreadExecuter.service()); - } - - URI getUrl(Request request) { - StringBuilder url = new StringBuilder(Constants.API_URL.endsWith("/") ? Constants.API_URL : Constants.API_URL + "/"); - url.append(request.route().getPath()); - if (request.path() != null) { - for (String p : request.path()) { - url.append("/").append(p); - } - } - if (request.query() != null && !request.query().isEmpty()) { - url.append("?"); - request.query().forEach((v) -> { - if (url.charAt(url.length() - 1) != '?') { - url.append("&"); - } - url.append(v); - }); - } - return URI.create(url.toString()); - } - - public void shutdown() { - if (restartingFuture != null) { - restartingFuture.cancel(true); - restartingFuture = null; - } - if (isAuthenticated()) { - logger.debug("Shutting down API"); - // We have to rely on the gc to collect previous client objects as close() was only implemented in java 21. - // However, we are currently compiling against java 17. - //client.close(); - auth = null; - selfUuid = null; - } - client = null; - } - - public boolean isAuthenticated() { - return auth != null; - } - - public int getIndicatorColor() { - return isAuthenticated() ? 0xFF009000 : 0xFFFF0000; - } - - public void logDetailed(String message, Object... args) { - logger.debug("[DETAIL] " + message, args); - } - - public void onError(Throwable throwable) { - logger.error("Error while handling API traffic:", throwable); - } - - private CompletableFuture scheduleRestart() { - if (restartingFuture != null) { - restartingFuture.cancel(true); - } - nextRestartSecs = Math.max(2, Math.min(nextRestartSecs * 2, 60*3)); - - logger.info("Trying restart in " + nextRestartSecs + " seconds."); - restartingFuture = CompletableFuture.supplyAsync(() -> { - logDetailed("Restarting API session..."); - return startup(account).join(); - }, CompletableFuture.delayedExecutor(nextRestartSecs, TimeUnit.SECONDS, ThreadExecuter.service())); - return restartingFuture; - } - - public void restart() { - shutdown(); - if (account != null) { - startup(account); - } - } - - public CompletableFuture startup(Account account) { - this.account = account; - if (!Constants.ENABLED) { - return CompletableFuture.failedFuture(new UnsupportedOperationException("API is disabled at compile-time")); - } - - if (account.isOffline()) { - return CompletableFuture.failedFuture(new UnsupportedOperationException("Account is offline")); - } - - switch (apiOptions.privacyAccepted.get()) { - case UNSET -> { - return apiOptions.openPrivacyNoteScreen.get().thenCompose(v -> - v ? startupAPI() : CompletableFuture.failedStage(new UnsupportedOperationException("Terms not accepted"))); - } - case ACCEPTED -> { - return startupAPI(); - } - } - return CompletableFuture.failedFuture(new UnsupportedOperationException("API is disabled")); - } - - private CompletableFuture startupAPI() { - if (isAuthenticated()) { - logger.warn("API is already running!"); - return CompletableFuture.failedFuture(new UnsupportedOperationException("API is already running")); - } - if (Constants.TESTING) { - return CompletableFuture.failedFuture(new UnsupportedOperationException("API is disabled for testing!")); - } - if (starting.getAndSet(true)) { - return CompletableFuture.completedFuture(null); - } - - logger.info("Starting API..."); - return CompletableFuture.runAsync(() -> { - this.authenticate().join(); - starting.set(false); - }, ThreadExecuter.service()); - } - - public static String sanitizeUUID(String uuid) { - if (uuid.contains("-")) { - return validateUUID(uuid.replace("-", "")); - } - return validateUUID(uuid); - } - - private static String validateUUID(String uuid) { - if (uuid.length() != 32) { - throw new IllegalArgumentException("Not a valid UUID (undashed): " + uuid); - } - return uuid; - } -} diff --git a/common/src/main/java/io/github/axolotlclient/api/Options.java b/common/src/main/java/io/github/axolotlclient/api/Options.java deleted file mode 100644 index d1e772f2b..000000000 --- a/common/src/main/java/io/github/axolotlclient/api/Options.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * Copyright © 2026 DSNS - * - * This file is part of DolphinClient, a fork of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api; - -import java.util.Locale; -import java.util.concurrent.CompletableFuture; -import java.util.function.Supplier; - -import io.github.axolotlclient.DolphinClientCommon; -import io.github.axolotlclient.AxolotlClientConfig.api.manager.ConfigManager; -import io.github.axolotlclient.AxolotlClientConfig.api.options.OptionCategory; -import io.github.axolotlclient.AxolotlClientConfig.impl.managers.JsonConfigManager; -import io.github.axolotlclient.AxolotlClientConfig.impl.options.EnumOption; -import io.github.axolotlclient.modules.Module; - -/** - * Configuration of the backend connection. - *

- * The social features this used to configure have been removed; what remains is - * the consent gate for the requests the client still makes (Hypixel data and - * image sharing). It is answered through {@code openPrivacyNoteScreen} rather - * than through a settings entry, and only acceptance is remembered. - */ -public abstract class Options implements Module { - - protected Supplier> openPrivacyNoteScreen = () -> CompletableFuture.completedFuture(false); - - private final OptionCategory apiConfig = OptionCategory.create("api"); - private final ConfigManager apiConfigManager = new JsonConfigManager(DolphinClientCommon.resolveConfigFile("api.json"), apiConfig); - - public final EnumOption privacyAccepted = new EnumOption<>("api.privacy_policy_accepted", PrivacyPolicyState.class, PrivacyPolicyState.UNSET, val -> { - if (!val.isAccepted() && API.getInstance() != null && API.getInstance().isAuthenticated()) { - API.getInstance().shutdown(); - } - // Only acceptance is remembered. Declining means "not right now": since there is no - // settings entry left to revisit the decision through, persisting it would turn the - // remaining online features off for good. - if (val.isAccepted()) { - apiConfigManager.save(); - } - }); - - @Override - public void init() { - apiConfig.add(privacyAccepted); - apiConfigManager.load(); - } - - public enum PrivacyPolicyState { - UNSET, - ACCEPTED() { - @Override - public boolean isAccepted() { - return true; - } - }, - DENIED; - - public boolean isAccepted() { - return false; - } - - @Override - public String toString() { - return "privacy_policy_state." + super.toString().toLowerCase(Locale.ROOT); - } - } -} diff --git a/common/src/main/java/io/github/axolotlclient/api/Request.java b/common/src/main/java/io/github/axolotlclient/api/Request.java deleted file mode 100644 index 1340988ed..000000000 --- a/common/src/main/java/io/github/axolotlclient/api/Request.java +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api; - - -import java.net.URI; -import java.util.*; - -import lombok.Getter; -import lombok.ToString; - -public record Request(Route route, List path, List query, - Map bodyFields, byte[] rawBody, Map headers, - boolean requiresAuthentication) { - - public URI resolve() { - return API.getInstance().getUrl(this); - } - - @ToString - @Getter - public enum Route { - AUTHENTICATE("authenticate"), - GLOBAL_DATA("global_data"), - IMAGE("image", true), - HYPIXEL("hypixel", true); - - private final String path; - private final boolean requiresAuthentication; - - Route(String path) { - this.path = path; - requiresAuthentication = false; - } - - Route(String path, boolean requiresAuthentication) { - this.path = path; - this.requiresAuthentication = requiresAuthentication; - } - - public Request create() { - return builder().build(); - } - - public RequestBuilder builder() { - return new RequestBuilder(this); - } - } - - public static class RequestBuilder { - private final Request.Route route; - private List path; - private List query; - private Map bodyFields; - private Map headers; - private boolean requiresAuthentication; - private byte[] rawBody; - - RequestBuilder(Route route) { - this.route = route; - this.requiresAuthentication = route.requiresAuthentication; - } - - public RequestBuilder path(String parameter) { - if (path == null) { - path = new ArrayList<>(); - } - path.add(parameter); - return this; - } - - public Request.RequestBuilder query(String key, String value) { - if (query == null) { - query = new ArrayList<>(); - } - query.add(key + "=" + value); - return this; - } - - public Request.RequestBuilder query(String key, Object value) { - if (query == null) { - query = new ArrayList<>(); - } - query.add(key + "=" + value); - return this; - } - - public Request.RequestBuilder field(String key, Object value) { - if (bodyFields == null) { - bodyFields = new HashMap<>(); - } - bodyFields.put(key, value); - return this; - } - - public RequestBuilder rawBody(byte[] body) { - this.rawBody = body; - return this; - } - - public RequestBuilder header(String key, String value) { - if (headers == null) { - headers = new HashMap<>(); - } - headers.put(key, value); - return this; - } - - public RequestBuilder requiresAuthentication() { - requiresAuthentication = true; - return this; - } - - public RequestBuilder requiresAuthentication(boolean requiresAuthentication) { - this.requiresAuthentication = requiresAuthentication; - return this; - } - - public Request build() { - if (rawBody != null && bodyFields != null) { - throw new IllegalArgumentException("Request cannot have json body and raw body at the same time!"); - } - return new Request(this.route, this.path, this.query, this.bodyFields, this.rawBody, this.headers, this.requiresAuthentication); - } - - public String toString() { - return "Request.RequestBuilder(route=" + this.route + ", path=" + listToString(path) + ", query=" + listToString(this.query) + ", bodyFields=" + mapToString(this.bodyFields) + ")"; - } - - private String mapToString(Map map) { - StringBuilder builder = new StringBuilder("{"); - map.forEach((o, o2) -> - builder.append(o).append(": ").append(o2).append(",\n")); - int length = builder.length(); - builder.delete(length - 2, length - 1); - builder.append("}"); - return builder.toString(); - } - - private String listToString(Collection c) { - StringBuilder builder = new StringBuilder("["); - c.forEach(o -> builder.append(o).append(",\n")); - builder.append("]"); - return builder.toString(); - } - } -} diff --git a/common/src/main/java/io/github/axolotlclient/api/Response.java b/common/src/main/java/io/github/axolotlclient/api/Response.java deleted file mode 100644 index b308444be..000000000 --- a/common/src/main/java/io/github/axolotlclient/api/Response.java +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api; - -import java.io.IOException; -import java.util.*; -import java.util.function.Function; -import java.util.function.Supplier; - -import io.github.axolotlclient.util.GsonHelper; -import lombok.*; - -@Getter -@EqualsAndHashCode -@AllArgsConstructor(access = AccessLevel.PRIVATE) -public class Response { - public static final Response CLIENT_ERROR = new Response(Collections.emptyMap(), "", 0, Collections.emptyMap(), new Error(0, "Client Request Error!")); - - private Object body; - private final String plainBody; - private final int status; - private final Map> headers; - private final Error error; - - @Builder - private Response(String body, int status, Map> headers) { - try { - this.body = parseJson(body); - } catch (IOException ignored) { - } - plainBody = body; - this.status = status; - this.headers = headers; - this.error = Error.of(this); - } - - public boolean isError() { - return error != null; - } - - private String mapToString(Map map) { - StringBuilder builder = new StringBuilder(map.toString() + "{"); - map.forEach((o, o2) -> builder.append(o).append(": ").append(o2).append("\n")); - builder.append("}"); - return builder.toString(); - } - - private String listToString(Collection c) { - StringBuilder builder = new StringBuilder(c.toString() + "{"); - c.forEach(o -> builder.append(o).append("\n")); - builder.append("}"); - return builder.toString(); - } - - private String bodyToString(Object body) { - if (body instanceof Collection) { - return listToString((Collection) body); - } else if (body instanceof Map) { - return mapToString((Map) body); - } - return String.valueOf(body); - } - - private Object parseJson(String json) throws IOException { - if (!json.isEmpty()) { - return GsonHelper.read(json); - } - return Collections.emptyMap(); - } - - public String toString() { - return "Response(headers=" + this.getHeaders() + ", body=" + bodyToString(this.getBody()) + ", plainBody=" + getPlainBody() + ", status=" + this.getStatus() + ", error=" + this.getError() + ")"; - } - - @SuppressWarnings("unchecked") - public T getBody(String path) { - path = path.replace("\\.", "_#+#_"); - String[] elements = path.split("\\."); - - Object o = getBody(); - for (String s : elements) { - s = s.replace("_#+#_", "."); - if (o instanceof Map map) { - if (map.containsKey(s)) { - o = map.get(s); - continue; - } - } else if (o instanceof List list) { - try { - int i = Integer.parseInt(s); - o = list.get(i); - continue; - } catch (Exception ignored) { - } - } - return null; - } - return (T) o; - } - - public T getBodyOrElse(String path, T other) { - T element = getBody(path); - return element != null ? element : other; - } - - public Optional getBodyOpt(String path) { - return Optional.ofNullable(getBody(path)); - } - - public U getBody(String path, Function mapper) { - T element = getBody(path); - if (element != null) { - return mapper.apply(element); - } - return null; - } - - public boolean bodyHas(String path) { - return getBody(path) != null; - } - - public T ifBodyHas(String path, Supplier supplier) { - Object content = getBody(path); - if (content != null) { - return supplier.get(); - } - return null; - } - - public Optional firstHeader(String name) { - return Optional.ofNullable(headers.getOrDefault(name, null)).map(index -> index.get(0)); - } - - public record Error(int httpCode, String description) { - public static Error of(Response response) { - if (response.status >= 200 && response.status < 300) { - return null; - } - return new Error(response.status, response.getBodyOrElse("description", "Unexpected internal error")); - } - } -} diff --git a/common/src/main/java/io/github/axolotlclient/api/requests/GlobalDataRequest.java b/common/src/main/java/io/github/axolotlclient/api/requests/GlobalDataRequest.java deleted file mode 100644 index db5b29979..000000000 --- a/common/src/main/java/io/github/axolotlclient/api/requests/GlobalDataRequest.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api.requests; - -import java.time.Instant; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Semaphore; - -import io.github.axolotlclient.api.API; -import io.github.axolotlclient.api.Request; -import io.github.axolotlclient.api.types.GlobalData; -import io.github.axolotlclient.api.types.SemVer; - -public class GlobalDataRequest { - private static GlobalData cachedData = null; - private static Instant nextRequest = null; - private static final Semaphore lock = new Semaphore(1); - - public static CompletableFuture get() { - return get(false); - } - - public static CompletableFuture get(boolean forceRequest) { - return CompletableFuture.supplyAsync(() -> { - if (API.getInstance().getApiOptions().privacyAccepted.get().isAccepted()) { - try { - lock.acquire(); - } catch (InterruptedException ignored) { - } - if (cachedData != null) { - var now = Instant.now(); - if (!forceRequest && nextRequest.isAfter(now)) { - lock.release(); - return cachedData; - } - } - return API.getInstance().get(Request.Route.GLOBAL_DATA.create()) - .thenApply(res -> { - if (res.isError()) { - return GlobalData.EMPTY; - } - return new GlobalData(true, res.getBody("total_players"), - res.getBody("online_players"), SemVer.parse(res.getBody("latest_version")), res.getBodyOrElse("notes", "")); - }) - .thenApply(d -> { - nextRequest = Instant.now().plusSeconds(300); - cachedData = d; - lock.release(); - return d; - }).join(); - } - return GlobalData.EMPTY; - }); - } -} diff --git a/common/src/main/java/io/github/axolotlclient/api/types/GlobalData.java b/common/src/main/java/io/github/axolotlclient/api/types/GlobalData.java deleted file mode 100644 index 6c12a5474..000000000 --- a/common/src/main/java/io/github/axolotlclient/api/types/GlobalData.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api.types; - -public record GlobalData(boolean success, long totalPlayers, long onlinePlayers, SemVer latestVersion, - String notes) { - public static final GlobalData EMPTY = new GlobalData(false, 0, 0, SemVer.EMPTY, ""); - -} diff --git a/common/src/main/java/io/github/axolotlclient/api/types/SemVer.java b/common/src/main/java/io/github/axolotlclient/api/types/SemVer.java deleted file mode 100644 index ba7e171f3..000000000 --- a/common/src/main/java/io/github/axolotlclient/api/types/SemVer.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api.types; - -import java.util.List; -import java.util.Objects; -import java.util.function.IntSupplier; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import lombok.NonNull; -import org.jetbrains.annotations.Nullable; - -public record SemVer(int major, int minor, int patch, String prerelease, String build) { - public static final SemVer EMPTY = new SemVer(0, 0, 0, null, null); - // Adapted from https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string - private static final Pattern SEMVER_PATTERN = Pattern.compile("^(?0|[1-9]\\d*)\\." + - "(?0|[1-9]\\d*)" + - "(\\.(?0|[1-9]\\d*))?" + - "(?:-(?(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)" + - "(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?" + - "(?:\\+(?[0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$"); - - public static SemVer parse(String version) { - Matcher matcher = SEMVER_PATTERN.matcher(version); - if (!matcher.find()) { - return EMPTY; - } - - int major = Integer.parseInt(matcher.group("major")); - int minor = Integer.parseInt(matcher.group("minor")); - // minecraft treats the `patch` component as optional... - @Nullable String patchString = matcher.group("patch"); - int patch = patchString == null ? 0 : Integer.parseInt(patchString); - String prerelease = matcher.group("prerelease"); - String buildmetadata = matcher.group("buildmetadata"); - return new SemVer(major, minor, patch, prerelease, buildmetadata); - } - - @Override - public String toString() { - StringBuilder b = new StringBuilder(); - b.append(major).append(".").append(minor).append(".").append(patch); - if (prerelease != null) { - b.append("-").append(prerelease); - } - if (build != null) { - b.append("+").append(build); - } - return b.toString(); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof SemVer s) { - return compareTo(s) == 0; - } - return toString().equals(obj.toString()); - } - - @Override - public int hashCode() { - return Objects.hash(major, minor, patch, prerelease); - } - - public int compareTo(@NonNull SemVer o) { - int i; - List suppliers = List.of( - () -> Integer.compare(major, o.major()), - () -> Integer.compare(minor, o.minor()), - () -> Integer.compare(patch, o.patch()), - () -> prerelease != null ? o.prerelease() != null ? 0 : -1 : o.prerelease() != null ? 1 : 0 - ); - for (IntSupplier comparison : suppliers) { - if ((i = comparison.getAsInt()) != 0) { - return i; - } - } - - if (prerelease == null) { - return 0; - } - - String[] self = prerelease.split("\\."); - String[] other = o.prerelease().split("\\."); - - for (int index = 0; index < Math.min(self.length, other.length); index++) { - boolean selfNumeric = self[index].matches("\\d+"); - boolean otherNumeric = other[index].matches("\\d+"); - if (selfNumeric != otherNumeric) { - return selfNumeric ? -1 : 1; - } else if (!selfNumeric) { - if ((i = self[index].compareTo(other[index])) != 0) { - return i; - } - } - } - return Integer.compare(self.length, other.length); - } - - public boolean isNewerThan(String other) { - String[] parts = other.split("\\."); - int major = Integer.parseInt(parts[0]); - - if (this.major > major) { - return true; - } else if (this.major < major) { - return false; - } - - int minor = Integer.parseInt(parts[1]); - - if (this.minor > minor) { - return true; - } else if (this.minor < minor) { - return false; - } - - int patch = Integer.parseInt(parts[2].split("-")[0].split("\\+")[0]); - - if (this.patch > patch) { - return true; - } else if (this.patch < patch) { - return false; - } - - return false; - } -} diff --git a/common/src/main/java/io/github/axolotlclient/api/util/Authentication.java b/common/src/main/java/io/github/axolotlclient/api/util/Authentication.java deleted file mode 100644 index 145955e7a..000000000 --- a/common/src/main/java/io/github/axolotlclient/api/util/Authentication.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright © 2025 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api.util; - -import java.time.Instant; -import java.time.temporal.ChronoUnit; - -public record Authentication(String token, Instant expiration) { - public Authentication(String token) { - this(token, Instant.now().plus(24, ChronoUnit.HOURS)); - } -} diff --git a/common/src/main/java/io/github/axolotlclient/api/util/MojangAuth.java b/common/src/main/java/io/github/axolotlclient/api/util/MojangAuth.java deleted file mode 100644 index 69c4606d6..000000000 --- a/common/src/main/java/io/github/axolotlclient/api/util/MojangAuth.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api.util; - -import javax.crypto.KeyGenerator; -import javax.crypto.SecretKey; -import java.math.BigInteger; -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.time.Duration; -import java.util.Arrays; - -import com.google.gson.JsonObject; -import io.github.axolotlclient.api.API; -import io.github.axolotlclient.modules.auth.Account; -import io.github.axolotlclient.util.GsonHelper; -import io.github.axolotlclient.util.NetworkUtil; -import lombok.Builder; -import lombok.Data; -import org.apache.commons.lang3.RandomStringUtils; - -public class MojangAuth { - - public static Result authenticate(Account account) { - Result.Builder result = Result.builder(); - //try (HttpClient client = NetworkUtil.createHttpClient("MojangAuth")) { - try { // Can't use try-with-resources because java 17's HttpClient doesn't implement AutoCloseable... - //noinspection resource - HttpClient client = NetworkUtil.createHttpClient(); - - HttpRequest.Builder builder = HttpRequest.newBuilder().timeout(Duration.ofSeconds(10)); - builder.header("Content-Type", "application/json; charset=utf-8"); - builder.header("Accept", "application/json"); - - JsonObject body = new JsonObject(); - body.addProperty("accessToken", account.getAuthToken()); - body.addProperty("selectedProfile", account.getUuid()); - - String serverId = minecraftSha1(RandomStringUtils.random(40).getBytes(StandardCharsets.UTF_8)); - - result.serverId(serverId); - body.addProperty("serverId", serverId); - builder.header("Authorization", "Bearer " + account.getAuthToken()); - - builder.POST(HttpRequest.BodyPublishers.ofByteArray(body.toString().getBytes(StandardCharsets.UTF_8))); - builder.uri(URI.create("https://sessionserver.mojang.com/session/minecraft/join")); - - HttpResponse response = client.send(builder.build(), HttpResponse.BodyHandlers.ofString()); - - if ((response.body() == null || response.body().isEmpty()) && response.statusCode() == 204) { - return result.status(Status.SUCCESS).build(); - } else { - JsonObject element = GsonHelper.fromJson(response.body()); - - if (!element.has("error")) { - API.getInstance().logDetailed("Don't know how to handle response: " + element); - } - - String error = element.get("error").getAsString(); - /*if ("ForbiddenOperationException".equals(error) && "INVALID_SIGNATURE".equals(element.get("errorMessage").getAsString())) { - return account.refresh(MSAuth.INSTANCE).thenApply(MojangAuth::authenticate).join(); - }*/ - - API.getInstance().logDetailed("Response code: " + response.statusCode()); - API.getInstance().logDetailed(element.toString()); - - if (error.equals("InsufficientPrivilegesException")) { - return result.status(Status.MULTIPLAYER_DISABLED).build(); - } else if (error.equals("UserBannedException")) { - return result.status(Status.USER_BANNED).build(); - } - } - - } catch (Exception e) { - API.getInstance().logDetailed("MojangAuth Exception: ", e); - } - return result.status(Status.FAILURE).build(); - } - - private static String minecraftSha1(byte[]... bytes) { - int length = Arrays.stream(bytes).mapToInt(a -> a.length).sum(); - byte[] data = new byte[length]; - - int index = 0; - - for (byte[] arr : bytes) { - int size = arr.length; - System.arraycopy(arr, 0, data, index, size); - index += size; - } - - try { - return new BigInteger(MessageDigest.getInstance("SHA-1").digest(data)).toString(16); - } catch (NoSuchAlgorithmException ignored) { - } - return null; - } - - private static SecretKey generateSecretKey() { - try { - KeyGenerator keyGenerator = KeyGenerator.getInstance("AES"); - keyGenerator.init(128); - return keyGenerator.generateKey(); - } catch (Exception ignored) { - } - return null; - } - - public enum Status { - SUCCESS, - MULTIPLAYER_DISABLED, - USER_BANNED, - FAILURE - } - - @Data - @Builder(builderClassName = "Builder") - public static class Result { - private final Status status; - private final String serverId; - } - -} diff --git a/common/src/main/java/io/github/axolotlclient/api/util/UUIDHelper.java b/common/src/main/java/io/github/axolotlclient/api/util/UUIDHelper.java deleted file mode 100644 index dffb918fa..000000000 --- a/common/src/main/java/io/github/axolotlclient/api/util/UUIDHelper.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api.util; - -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.util.Optional; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; - -import io.github.axolotlclient.api.API; -import io.github.axolotlclient.util.CachedAPI; -import io.github.axolotlclient.util.GsonHelper; -import io.github.axolotlclient.util.NetworkUtil; - -public class UUIDHelper { - private static final HttpClient CLIENT = NetworkUtil.createHttpClient(); - - private static CachedAPI create(String endpoint, String jsonKey, String log) { - return new CachedAPI<>(val -> { - URI uri; - try { - uri = new URI(endpoint + val); - } catch (Exception e) { - return CompletableFuture.completedFuture(Optional.empty()); - } - HttpRequest req = HttpRequest.newBuilder(uri) - .GET() - .build(); - - return CLIENT.sendAsync(req, HttpResponse.BodyHandlers.ofString()) - .thenApply(res -> { - final var obj = GsonHelper.fromJson(res.body()); - if (obj.has(jsonKey)) { - return Optional.of(obj.get(jsonKey).getAsString()); - } - - API.getInstance().getLogger().debug("Conversion {} failed: {}", log, obj); - - return Optional.empty(); - }); - }, 4096, false); - } - - public static final CachedAPI USERNAME_TO_UUID = - create("https://api.mojang.com/users/profiles/minecraft/", "id", "username -> uuid"); - - public static final CachedAPI UUID_TO_USERNAME = - create("https://sessionserver.mojang.com/session/minecraft/profile/", "name", "uuid -> username"); - - public static CompletableFuture tryGetUsernameAsync(String uuid) { - return UUID_TO_USERNAME.getAsync(uuid).thenApply(s -> s.orElse(uuid)); - } - - public static UUID fromUndashed(String uuid) { - return UUID.fromString(uuid.trim().replaceFirst("(\\w{8})(\\w{4})(\\w{4})(\\w{4})(\\w{12})", "$1-$2-$3-$4-$5" - )); - } - - public static CompletableFuture> ensureUuidOpt(String nameOrUuid) { - if (nameOrUuid == null || nameOrUuid.isBlank()) { - return CompletableFuture.completedFuture(Optional.empty()); - } - try { - return CompletableFuture.completedFuture(Optional.of(API.sanitizeUUID(nameOrUuid))); - } catch (IllegalArgumentException e) { - return USERNAME_TO_UUID.getAsync(nameOrUuid); - } - } - - public static CompletableFuture ensureUuid(String nameOrUuid) { - return ensureUuidOpt(nameOrUuid).thenApply(s -> s.orElse(nameOrUuid)); - } - - public static String toUndashed(UUID uuid) { - return API.sanitizeUUID(uuid.toString()); - } -} diff --git a/common/src/main/java/io/github/axolotlclient/bridge/commands/PlayerArgument.java b/common/src/main/java/io/github/axolotlclient/bridge/commands/PlayerArgument.java index 9c693a7af..2b495039a 100644 --- a/common/src/main/java/io/github/axolotlclient/bridge/commands/PlayerArgument.java +++ b/common/src/main/java/io/github/axolotlclient/bridge/commands/PlayerArgument.java @@ -33,7 +33,7 @@ import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.suggestion.Suggestions; import com.mojang.brigadier.suggestion.SuggestionsBuilder; -import io.github.axolotlclient.api.util.UUIDHelper; +import io.github.axolotlclient.util.UUIDHelper; import io.github.axolotlclient.bridge.AxoMinecraftClient; import io.github.axolotlclient.bridge.AxoPlayerListEntry; import org.jetbrains.annotations.Nullable; @@ -54,7 +54,12 @@ public String playerName() { public CompletableFuture> uuid() { if (uuid == null) { - uuid = UUIDHelper.USERNAME_TO_UUID.getAsync(playerName); + uuid = CompletableFuture.completedFuture( + AxoMinecraftClient.getInstance().br$getOnlinePlayers().stream() + .filter(player -> playerName.equalsIgnoreCase(player.br$getName())) + .map(player -> UUIDHelper.toUndashed(player.br$getId())) + .findFirst() + ); } return uuid; diff --git a/common/src/main/java/io/github/axolotlclient/config/migration/ConfigMigration.java b/common/src/main/java/io/github/axolotlclient/config/migration/ConfigMigration.java index 01866f654..56f19500a 100644 --- a/common/src/main/java/io/github/axolotlclient/config/migration/ConfigMigration.java +++ b/common/src/main/java/io/github/axolotlclient/config/migration/ConfigMigration.java @@ -34,7 +34,7 @@ import net.fabricmc.loader.api.FabricLoader; public interface ConfigMigration { - int CONFIG_VERSION = 8; + int CONFIG_VERSION = 11; List MIGRATIONS = new ArrayList<>(List.of( new V2Migration(), new V3Migration(), @@ -43,7 +43,8 @@ public interface ConfigMigration { new V7Migration(), new V8Migration(), new V9Migration(), - new V10Migration() + new V10Migration(), + new V11Migration() )); static void apply(int oldVersion, JsonObject config) { diff --git a/common/src/main/java/io/github/axolotlclient/config/migration/impl/V10Migration.java b/common/src/main/java/io/github/axolotlclient/config/migration/impl/V10Migration.java index 5d5031bfb..5bce01edc 100644 --- a/common/src/main/java/io/github/axolotlclient/config/migration/impl/V10Migration.java +++ b/common/src/main/java/io/github/axolotlclient/config/migration/impl/V10Migration.java @@ -22,22 +22,13 @@ package io.github.axolotlclient.config.migration.impl; -import java.util.Arrays; -import java.util.Optional; - import com.google.gson.JsonObject; -import io.github.axolotlclient.DolphinClientCommon; -import io.github.axolotlclient.api.Options; import io.github.axolotlclient.config.migration.ConfigMigration; /** - * Drops the removed "Social Options" category, carrying the privacy policy - * decision over to the standalone api config so users are not asked again. + * Drops the removed Social Options / API category. */ public class V10Migration implements ConfigMigration { - private static final String CATEGORY = "api.category"; - private static final String PRIVACY_POLICY = "api.privacy_policy_accepted"; - @Override public int version() { return 10; @@ -45,17 +36,6 @@ public int version() { @Override public void apply(JsonObject config) { - getObject(config, CATEGORY).ifPresent(api -> { - getString(api, PRIVACY_POLICY) - .flatMap(V10Migration::parseState) - .ifPresent(DolphinClientCommon.getInstance().getApiOptions().privacyAccepted::set); - config.remove(CATEGORY); - }); - } - - private static Optional parseState(String serialized) { - return Arrays.stream(Options.PrivacyPolicyState.values()) - .filter(state -> state.toString().equals(serialized) || state.name().equalsIgnoreCase(serialized)) - .findFirst(); + config.remove("api.category"); } } diff --git a/common/src/main/java/io/github/axolotlclient/api/util/AlphabeticalComparator.java b/common/src/main/java/io/github/axolotlclient/config/migration/impl/V11Migration.java similarity index 60% rename from common/src/main/java/io/github/axolotlclient/api/util/AlphabeticalComparator.java rename to common/src/main/java/io/github/axolotlclient/config/migration/impl/V11Migration.java index 3a0d3de62..beb4c863d 100644 --- a/common/src/main/java/io/github/axolotlclient/api/util/AlphabeticalComparator.java +++ b/common/src/main/java/io/github/axolotlclient/config/migration/impl/V11Migration.java @@ -1,7 +1,7 @@ /* - * Copyright © 2024 moehreag & Contributors + * Copyright © 2026 DSNS * - * This file is part of AxolotlClient. + * This file is part of DolphinClient. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -20,23 +20,22 @@ * For more information, see the LICENSE file. */ -package io.github.axolotlclient.api.util; +package io.github.axolotlclient.config.migration.impl; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; +import com.google.gson.JsonObject; +import io.github.axolotlclient.config.migration.ConfigMigration; -public class AlphabeticalComparator implements Comparator { +/** + * Drops Microsoft / Mojang account management settings. + */ +public class V11Migration implements ConfigMigration { @Override - public int compare(String s1, String s2) { - if (s1.equals(s2)) - return 0; - String[] strings = {s1, s2}; - Arrays.sort(strings, Collections.reverseOrder()); + public int version() { + return 11; + } - if (strings[0].equals(s1)) - return 1; - else - return -1; + @Override + public void apply(JsonObject config) { + getObject(config, "general").ifPresent(general -> general.remove("auth")); } } diff --git a/common/src/main/java/io/github/axolotlclient/modules/auth/Account.java b/common/src/main/java/io/github/axolotlclient/modules/auth/Account.java deleted file mode 100644 index 19b9e7f1c..000000000 --- a/common/src/main/java/io/github/axolotlclient/modules/auth/Account.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * Copyright © 2026 DSNS - * - * This file is part of DolphinClient, a fork of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.modules.auth; - -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.concurrent.CompletableFuture; - -import com.google.gson.JsonObject; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.Setter; - -public class Account { - - public static final String OFFLINE_TOKEN = "DolphinClient/Offline"; - private static final String LEGACY_OFFLINE_TOKEN = "AxolotlClient/Offline"; - - @Getter - private final String uuid; - @Getter - @Setter(AccessLevel.PACKAGE) - private String name; - - @Getter(AccessLevel.PACKAGE) - @Setter(AccessLevel.PACKAGE) - private String msaToken; - - @Getter - @Setter(AccessLevel.PACKAGE) - private String authToken; - @Getter(AccessLevel.PACKAGE) - @Setter(AccessLevel.PACKAGE) - private String refreshToken; - @Setter(AccessLevel.PACKAGE) - @Getter - private Instant expiration; - - public Account(String name, String uuid, String accessToken, Instant expiration, String refreshToken, String msaToken) { - this.name = name; - this.uuid = uuid; - this.authToken = accessToken; - this.expiration = expiration; - this.refreshToken = refreshToken; - this.msaToken = msaToken; - } - - public Account(String name, String uuid, String accessToken) { - this(name, uuid.replace("-", ""), accessToken, Instant.EPOCH, "", ""); - } - - private Account(String uuid, String name, String authToken, String msaToken, String refreshToken, long expiration) { - this(name, uuid, authToken, Instant.ofEpochSecond(expiration), refreshToken, msaToken); - } - - public static Account deserialize(JsonObject object) { - String uuid = object.get("uuid").getAsString(); - String name = object.get("name").getAsString(); - String authToken = object.get("authToken").getAsString(); - String refreshToken = object.get("refreshToken").getAsString(); - String msaToken = object.has("msToken") ? object.get("msToken").getAsString() : ""; - long expiration = object.get("expiration").getAsLong(); - return new Account(uuid, name, authToken, msaToken, refreshToken, expiration); - } - - public CompletableFuture refresh(MSApi auth) { - if (isOffline()) return CompletableFuture.failedFuture(new UnsupportedOperationException()); - return auth.refresh(this); - } - - public boolean isOffline() { - return authToken.equals(OFFLINE_TOKEN) || authToken.equals(LEGACY_OFFLINE_TOKEN) || authToken.length() < 400; - } - - public JsonObject serialize() { - JsonObject object = new JsonObject(); - object.addProperty("uuid", uuid); - object.addProperty("name", name); - object.addProperty("authToken", authToken); - object.addProperty("msToken", msaToken); - object.addProperty("refreshToken", refreshToken == null ? "" : refreshToken); - object.addProperty("expiration", expiration == null ? 0 : expiration.getEpochSecond()); - return object; - } - - public boolean isExpired() { - return expiration.isBefore(Instant.now()); - } - - public boolean needsRefresh() { - return Instant.now().isAfter(expiration.minus(2, ChronoUnit.HOURS)); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof Account other) { - return uuid.equals(other.uuid); - } - return false; - } -} diff --git a/common/src/main/java/io/github/axolotlclient/modules/auth/Accounts.java b/common/src/main/java/io/github/axolotlclient/modules/auth/Accounts.java deleted file mode 100644 index 2bc685734..000000000 --- a/common/src/main/java/io/github/axolotlclient/modules/auth/Accounts.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * Copyright © 2026 DSNS - * - * This file is part of DolphinClient, a fork of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.modules.auth; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CompletableFuture; - -import com.google.gson.JsonArray; -import com.google.gson.JsonObject; -import io.github.axolotlclient.DolphinClientCommon; -import io.github.axolotlclient.AxolotlClientConfig.api.options.OptionCategory; -import io.github.axolotlclient.util.GsonHelper; -import io.github.axolotlclient.util.Logger; -import lombok.Getter; - -@Getter -public abstract class Accounts { - - public final OptionCategory category = OptionCategory.create("auth"); - - private final List accounts = new ArrayList<>(); - protected Account current; - protected MSApi msApi; - - public void load() { - Path legacy = DolphinClientCommon.resolveConfigFile("../accounts.json"); - Path saveFile = getAccountsSaveFile(); - if (Files.exists(legacy)) { - try { - if (Files.exists(saveFile)) { - Files.move(legacy, legacy.resolveSibling("accounts.json.old")); - getLogger().info("Renaming legacy accounts save file since new one exists already."); - } else { - Files.createDirectories(saveFile.getParent()); - Files.move(legacy, saveFile); - } - } catch (IOException e) { - getLogger().warn("Failed to move legacy accounts file to new location", e); - } - } - if (Files.exists(saveFile)) { - try { - JsonObject list = GsonHelper.GSON.fromJson(Files.newBufferedReader(saveFile), JsonObject.class); - if (list != null) { - list.get("accounts").getAsJsonArray().forEach(jsonElement -> accounts.add(Account.deserialize(jsonElement.getAsJsonObject()))); - } - } catch (IOException e) { - getLogger().warn("Failed to load accounts file!", e); - } - } else { - try { - Files.createDirectories(getAccountsSaveFile().getParent()); - Files.createFile(getAccountsSaveFile()); - } catch (IOException e) { - getLogger().warn("Failed to create accounts file", e); - } - } - } - - protected Path getAccountsSaveFile() { - return DolphinClientCommon.resolveConfigFile("accounts.json"); - } - - public void addAccount(Account account) { - accounts.add(account); - } - - protected abstract void login(Account account); - - public void removeAccount(Account account) { - accounts.remove(account); - save(); - } - - public void save() { - JsonArray array = new JsonArray(); - accounts.forEach(account -> array.add(account.serialize())); - JsonObject object = new JsonObject(); - object.add("accounts", array); - try { - Files.createDirectories(getAccountsSaveFile().getParent()); - Files.writeString(getAccountsSaveFile(), GsonHelper.GSON.toJson(object)); - } catch (IOException e) { - getLogger().error("Failed to save acounts config!", e); - } - } - - private Logger getLogger() { - return DolphinClientCommon.getInstance().getLogger(); - } - - protected boolean isContained(String uuid) { - return accounts.stream().anyMatch(account -> account.getUuid().equals(uuid)); - } - - public boolean allowOfflineAccounts() { - return !accounts.isEmpty() && !accounts.stream().allMatch(Account::isOffline); - } - - abstract CompletableFuture showAccountsExpiredScreen(Account account); - - abstract void displayDeviceCode(DeviceFlowData data); -} diff --git a/common/src/main/java/io/github/axolotlclient/modules/auth/DeviceFlowData.java b/common/src/main/java/io/github/axolotlclient/modules/auth/DeviceFlowData.java deleted file mode 100644 index ef9b7a87c..000000000 --- a/common/src/main/java/io/github/axolotlclient/modules/auth/DeviceFlowData.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.modules.auth; - -import io.github.axolotlclient.AxolotlClientConfig.api.util.Colors; -import io.github.axolotlclient.AxolotlClientConfig.api.util.Graphics; -import io.github.axolotlclient.AxolotlClientConfig.impl.util.GraphicsImpl; -import io.nayuki.qrcodegen.QrCode; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.Setter; - -@RequiredArgsConstructor -public class DeviceFlowData { - @Getter - private final String message; - @Getter - private final String verificationUri; - @Getter - private final String deviceCode; - @Getter - private final String userCode; - @Getter - private final int expiresIn; - @Getter - private final int interval; - - @Setter - private StatusConsumer statusConsumer; - @Getter - private boolean cancelled; - - public void setStatus(String status) { - if (statusConsumer != null) { - statusConsumer.emit(status); - } - } - - public Graphics getQrCode() { - QrCode qr = QrCode.encodeText(getVerificationUri() + "?code=" + getUserCode(), QrCode.Ecc.MEDIUM); - int size = qr.size; - int borderWidth = 1; - Graphics gr = new GraphicsImpl(size + borderWidth * 2, size + borderWidth * 2); - for (int x = 0; x < gr.getWidth(); x++) { - for (int y = 0; y < gr.getHeight(); y++) { - gr.setPixelColor(x, y, qr.getModule(x - borderWidth, y - borderWidth) ? Colors.BLACK : Colors.WHITE); - } - } - return gr; - } - - public void cancel() { - cancelled = true; - } - - public interface StatusConsumer { - void emit(String status); - } -} diff --git a/common/src/main/java/io/github/axolotlclient/modules/auth/MSApi.java b/common/src/main/java/io/github/axolotlclient/modules/auth/MSApi.java deleted file mode 100644 index a2e844a80..000000000 --- a/common/src/main/java/io/github/axolotlclient/modules/auth/MSApi.java +++ /dev/null @@ -1,533 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * Copyright © 2026 DSNS - * - * This file is part of DolphinClient, a fork of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.modules.auth; - -import java.io.FileNotFoundException; -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.nio.charset.StandardCharsets; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.Base64; -import java.util.List; -import java.util.Locale; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeoutException; -import java.util.function.Supplier; - -import com.github.mizosoft.methanol.FormBodyPublisher; -import com.github.mizosoft.methanol.MediaType; -import com.github.mizosoft.methanol.MultipartBodyPublisher; -import com.google.common.hash.Hashing; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import io.github.axolotlclient.DolphinClientCommon; -import io.github.axolotlclient.modules.auth.skin.Cape; -import io.github.axolotlclient.modules.auth.skin.Skin; -import io.github.axolotlclient.util.GsonHelper; -import io.github.axolotlclient.util.JsonBuilders; -import io.github.axolotlclient.util.Logger; -import io.github.axolotlclient.util.NetworkUtil; -import lombok.ToString; - -// Partly oriented on In-Game-Account-Switcher by The-Fireplace, VidTu -public class MSApi { - - private static final String CLIENT_ID = "938592fc-8e01-4c6d-b56d-428c7d9cf5ea"; // AxolotlClient MSA ClientID - private static final String SCOPES = "XboxLive.signin offline_access"; - private static final String XBL_AUTH_URL = "https://user.auth.xboxlive.com/user/authenticate"; - private static final String MS_DEVICE_CODE_LOGIN_URL = "https://login.microsoftonline.com/consumers/oauth2/v2.0/devicecode?mkt="; - private static final String MS_TOKEN_LOGIN_URL = "https://login.microsoftonline.com/consumers/oauth2/v2.0/token"; - private static final String XBL_XSTS_AUTH_URL = "https://xsts.auth.xboxlive.com/xsts/authorize"; - private static final String MC_LOGIN_WITH_XBOX_URL = "https://api.minecraftservices.com/authentication/login_with_xbox"; - - private final Supplier languageSupplier; - private final Logger logger; - private final Accounts accounts; - private final HttpClient client; - - public static MSApi INSTANCE; - - public MSApi(Accounts accounts, Supplier languageSupplier) { - this.logger = DolphinClientCommon.getInstance().getLogger(); - this.client = NetworkUtil.createHttpClient(); - this.accounts = accounts; - this.languageSupplier = languageSupplier; - INSTANCE = this; - } - - public CompletableFuture startDeviceAuth() { - - String[] lang = languageSupplier.get().replace("_", "-").split("-"); - logger.debug("starting ms device auth flow"); - // https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-device-code#device-authorization-response - HttpRequest.Builder builder = HttpRequest.newBuilder() - .POST(FormBodyPublisher.newBuilder() - .query("client_id", CLIENT_ID) - .query("scope", SCOPES).build()) - .header("content-type", "application/x-www-form-urlencoded") - .uri(URI.create(MS_DEVICE_CODE_LOGIN_URL + lang[0] + "-" + lang[1].toUpperCase(Locale.ROOT))); - return requestJson(builder.build()) - .thenApplyAsync(object -> { - int expiresIn = object.get("expires_in").getAsInt(); - String deviceCode = object.get("device_code").getAsString(); - String userCode = object.get("user_code").getAsString(); - String verificationUri = object.get("verification_uri").getAsString(); - int interval = object.get("interval").getAsInt(); - String message = object.get("message").getAsString(); - logger.debug("displaying device code to user"); - DeviceFlowData data = new DeviceFlowData(message, verificationUri, deviceCode, userCode, expiresIn, interval); - accounts.displayDeviceCode(data); - return data; - }).thenComposeAsync(data -> { - logger.debug("waiting for user authorization..."); - long start = System.currentTimeMillis(); - while (System.currentTimeMillis() - start < data.getExpiresIn() * 1000L && !data.isCancelled()) { - if ((System.currentTimeMillis() - start) % data.getInterval() == 0) { - HttpRequest.Builder requestBuilder = HttpRequest.newBuilder().POST( - FormBodyPublisher.newBuilder().query("client_id", CLIENT_ID) - .query("device_code", data.getDeviceCode()) - .query("grant_type", "urn:ietf:params:oauth:grant-type:device_code") - .build() - ) - .uri(URI.create(MS_TOKEN_LOGIN_URL)); - JsonObject response = requestJson(requestBuilder.build()).join(); - - if (response.has("refresh_token") && response.has("access_token")) { - data.setStatus("auth.working"); - return authenticateFromMSTokens(response.get("access_token").getAsString(), - response.get("refresh_token").getAsString()) - .thenApply(a -> { - int index = accounts.getAccounts().indexOf(a); - Account loginAccount; - if (index == -1) { - accounts.getAccounts().add(a); - loginAccount = a; - } else { - var prev = accounts.getAccounts().get(index); - prev.setAuthToken(a.getAuthToken()); - prev.setExpiration(a.getExpiration()); - prev.setMsaToken(a.getMsaToken()); - prev.setName(a.getName()); - prev.setRefreshToken(a.getRefreshToken()); - loginAccount = prev; - } - accounts.login(loginAccount); - accounts.save(); - data.setStatus("auth.finished"); - return loginAccount; - }); - } - - if (response.has("error")) { - String error = response.get("error").getAsString(); - switch (error) { - case "authorization_pending": - continue; - case "bad_verification_code": - throw new IllegalStateException("Bad verification code! " + response); - case "authorization_declined": - case "expired_token": - default: - break; - } - } - } - } - return CompletableFuture.failedStage(new TimeoutException()); - }); - } - - private CompletableFuture authenticateFromMSTokens(String accessToken, String refreshToken) { - return CompletableFuture.supplyAsync(() -> { - logger.debug("getting xbl token... "); - XblData xbl = authXbl(accessToken).join(); - logger.debug("getting xsts token..."); - XblData xsts = authXstsMC(xbl.token()).join(); - logger.debug("getting mc auth token..."); - MCXblData mc = authMC(xsts.displayClaims().uhs(), xsts.token()).join(); - - JsonObject profileJson = getMCProfile(mc.accessToken()).join(); - if (profileJson.has("error") && "NOT_FOUND".equals(profileJson.get("error").getAsString())) { - DolphinClientCommon.getInstance().getNotificationProvider().addStatus("auth.notif.login.failed", "auth.notif.login.failed.no_profile"); - throw new IllegalStateException(); - } - logger.debug("retrieving entitlements..."); - if (!checkOwnership(mc.accessToken()).join()) { - DolphinClientCommon.getInstance().getNotificationProvider().addStatus("auth.notif.login.failed", "auth.notif.login.failed.no_entitlement"); - logger.warn("Failed to check for game ownership!"); - throw new IllegalStateException(); - } - logger.debug("getting profile..."); - MCProfile profile = MCProfile.get(profileJson); - return new Account(profile.name(), profile.id(), mc.accessToken(), mc.expiration(), refreshToken, accessToken); - }); - } - - public record MCProfile(String id, String name, List skins, List capes) { - public static MCProfile get(JsonObject json) { - var skinList = GsonHelper.jsonArrayToStream(json.getAsJsonArray("skins")) - .map(s -> OnlineSkin.get(s.getAsJsonObject())) - .toList(); - var capesList = GsonHelper.jsonArrayToStream(json.getAsJsonArray("capes")) - .map(s -> OnlineCape.get(s.getAsJsonObject())) - .toList(); - CompletableFuture.allOf(skinList.toArray(CompletableFuture[]::new)); - CompletableFuture.allOf(capesList.toArray(CompletableFuture[]::new)); - return new MCProfile(json.get("id").getAsString(), json.get("name").getAsString(), - skinList.stream().map(CompletableFuture::join).toList(), - capesList.stream().map(CompletableFuture::join).toList()); - } - - @ToString - public static final class OnlineSkin implements Skin.Online { - public static final String VARIANT_CLASSIC = "CLASSIC"; - public static final String VARIANT_SLIM = "SLIM"; - public static final String STATE_ACTIVE = "ACTIVE"; - private final String id; - private final String state; - private final String url; - private boolean classicVariant; - private final byte[] image; - private final String textureKey; - - public OnlineSkin(String id, String state, String url, String variant, - byte[] image, String textureKey) { - this.id = id; - this.state = state; - this.url = url; - this.classicVariant = VARIANT_CLASSIC.equals(variant); - this.image = image; - this.textureKey = textureKey; - } - - @SuppressWarnings("UnstableApiUsage") - public static CompletableFuture get(JsonObject object) { - String url = object.get("url").getAsString(); - return INSTANCE.client.sendAsync(HttpRequest.newBuilder(URI.create(url)).GET().build(), HttpResponse.BodyHandlers.ofByteArray()) - .thenApplyAsync(res -> { - if (res.statusCode() == 200) { - return res.body(); - } - throw new IllegalArgumentException("abnormal status: " + res.statusCode()); - }).thenApply(bytes -> new OnlineSkin(object.get("id").getAsString(), - object.get("state").getAsString(), - url, - object.get("variant").getAsString(), bytes, - Hashing.sha256().hashBytes(bytes).toString())); - } - - public byte[] image() { - return image; - } - - public boolean classicVariant() { - return classicVariant; - } - - @Override - public void classicVariant(boolean classic) { - this.classicVariant = classic; - } - - public boolean active() { - return STATE_ACTIVE.equals(state()); - } - - @Override - public CompletableFuture equip(MSApi api, Account account) { - return api.setSkin(account, this); - } - - @Override - public boolean supportsDownload() { - return true; - } - - public String id() { - return id; - } - - public String state() { - return state; - } - - @Override - public String url() { - return url; - } - - @Override - public String sha256() { - return textureKey; - } - } - - public record OnlineCape(String id, String state, String url, String alias, byte[] image, - String sha256) implements Cape { - public static final String STATE_ACTIVE = "ACTIVE"; - - @SuppressWarnings("UnstableApiUsage") - public static CompletableFuture get(JsonObject object) { - String url = object.get("url").getAsString(); - return INSTANCE.client.sendAsync(HttpRequest.newBuilder(URI.create(url)).GET().build(), HttpResponse.BodyHandlers.ofByteArray()) - .thenApplyAsync(res -> { - if (res.statusCode() == 200) { - return res.body(); - } - throw new IllegalArgumentException("abnormal status: " + res.statusCode()); - }).thenApply(bytes -> new OnlineCape(object.get("id").getAsString(), object.get("state").getAsString(), - url, object.get("alias").getAsString(), bytes, Hashing.sha256().hashBytes(bytes).toString())); - } - - public boolean active() { - return STATE_ACTIVE.equals(state()); - } - - @Override - public CompletableFuture equip(MSApi api, Account account) { - return api.showCape(account, this); - } - } - } - - private CompletableFuture authXbl(String code) { - HttpRequest.Builder requestBuilder = HttpRequest.newBuilder() - .uri(URI.create(XBL_AUTH_URL)) - .POST(HttpRequest.BodyPublishers.ofString(JsonBuilders.JsonObject.create() - .field("Properties", JsonBuilders.JsonObject.create() - .field("AuthMethod", "RPS") - .field("SiteName", "user.auth.xboxlive.com") - .field("RpsTicket", "d=" + code)) - .field("RelyingParty", "http://auth.xboxlive.com") - .field("TokenType", "JWT").asString())) - .header("content-type", "application/json") - .header("accept", "application/json"); - - return requestJson(requestBuilder.build()).thenApply(response -> new XblData(Instant.parse(response.get("IssueInstant").getAsString()), Instant.parse(response.get("NotAfter").getAsString()), - response.get("Token").getAsString(), new XblData.DisplayClaims(response.get("DisplayClaims").getAsJsonObject().get("xui").getAsJsonArray().get(0).getAsJsonObject().get("uhs").getAsString()))); - } - - private record XblData(Instant issueInstant, Instant notAfter, String token, DisplayClaims displayClaims) { - private record DisplayClaims(String uhs) { - } - } - - private CompletableFuture authXstsMC(String xblToken) { - var body = JsonBuilders.JsonObject.create() - .field("Properties", JsonBuilders.JsonObject.create() - .field("SandboxId", "RETAIL") - .field("UserTokens", JsonBuilders.JsonArray.create().field(xblToken))) - .field("RelyingParty", "rp://api.minecraftservices.com/") - .field("TokenType", "JWT"); - return requestJson(HttpRequest.newBuilder().POST(HttpRequest.BodyPublishers.ofString(body.asString())).uri(URI.create(XBL_XSTS_AUTH_URL)).build()) - .thenApply(response -> new XblData(Instant.parse(response.get("IssueInstant").getAsString()), Instant.parse(response.get("NotAfter").getAsString()), - response.get("Token").getAsString(), new XblData.DisplayClaims(response.get("DisplayClaims").getAsJsonObject().get("xui").getAsJsonArray().get(0).getAsJsonObject().get("uhs").getAsString()))); - } - - private CompletableFuture authMC(String userhash, String xsts) { - var body = JsonBuilders.JsonObject.create().field("identityToken", "XBL3.0 x=" + userhash + ";" + xsts); - return requestJson(HttpRequest.newBuilder(URI.create(MC_LOGIN_WITH_XBOX_URL)).POST(HttpRequest.BodyPublishers.ofString(body.asString())).build()) - .thenApply(response -> new MCXblData(response.get("username").getAsString(), - response.get("access_token").getAsString(), - Instant.now().plus(response.get("expires_in").getAsLong(), ChronoUnit.SECONDS))); - } - - private record MCXblData(String username, String accessToken, Instant expiration) { - } - - private CompletableFuture checkOwnership(String accessToken) { - return requestJson(HttpRequest - .newBuilder(URI.create("https://api.minecraftservices.com/entitlements/mcstore")) - .header("Authorization", "Bearer " + accessToken).build()) - .thenApply(res -> GsonHelper.jsonArrayToStream(res.get("items").getAsJsonArray()) - .anyMatch(e -> e.isJsonObject() && e.getAsJsonObject().has("name") - && "game_minecraft".equals(e.getAsJsonObject().get("name").getAsString()))); - } - - private CompletableFuture getMCProfile(String accessToken) { - return requestJson(HttpRequest.newBuilder().GET() - .uri(URI.create("https://api.minecraftservices.com/minecraft/profile")) - .header("Authorization", "Bearer " + accessToken).build()); - } - - public CompletableFuture refresh(Account account) { - logger.debug("refreshing auth code..."); - return requestJson(HttpRequest - .newBuilder(URI.create(MS_TOKEN_LOGIN_URL)) - .POST(FormBodyPublisher.newBuilder() - .query("client_id", CLIENT_ID) - .query("refresh_token", account.getRefreshToken()) - .query("scope", SCOPES) - .query("grant_type", "refresh_token").build()) - .header("Accept", "application/json").build()) - .thenCompose(response -> { - if (response.has("error_codes")) { - int errorCode = response.get("error_codes").getAsJsonArray().get(0).getAsInt(); - if (errorCode == 70000 || errorCode == 70012) { - return accounts.showAccountsExpiredScreen(account); - } else { - logger.warn("Login error, unexpected response: " + response); - DolphinClientCommon.getInstance().getNotificationProvider().addStatus("auth.notif.refresh.error", "auth.notif.refresh.error.unexpected_response"); - throw new IllegalArgumentException(); - } - } - logger.debug("authenticating..."); - return authenticateFromMSTokens(response.get("access_token").getAsString(), - response.get("refresh_token").getAsString()).thenApply(refreshed -> { - account.setRefreshToken(refreshed.getRefreshToken()); - account.setAuthToken(refreshed.getAuthToken()); - account.setName(refreshed.getName()); - account.setMsaToken(refreshed.getMsaToken()); - account.setExpiration(refreshed.getExpiration()); - accounts.save(); - return account; - }); - }); - } - - private CompletableFuture requestJson(HttpRequest request) { - return client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) - .thenApply(res -> GsonHelper.fromJson(res.body())); - } - - public CompletableFuture getProfile(Account account) { - return getMCProfile(account.getAuthToken()).thenApply(this::extractProfile); - } - - public record SkinBundle(String name, String id, CompletableFuture skin, String skinKey, - boolean classicModel) { - } - - public CompletableFuture getTextures(String uuid) { - return requestJson(HttpRequest.newBuilder().GET() - .uri(URI.create("https://sessionserver.mojang.com/session/minecraft/profile/" + uuid)) - .build()).thenApply(json -> { - var name = json.get("name").getAsString(); - var id = json.get("id").getAsString(); - var properties = json.get("properties").getAsJsonArray(); - for (JsonElement e : properties) { - if (e.isJsonObject()) { - var obj = e.getAsJsonObject(); - if (obj.has("name") && "textures".equals(obj.get("name").getAsString())) { - var b64 = obj.get("value").getAsString(); - var props = GsonHelper.fromJson(new String(Base64.getDecoder().decode(b64), StandardCharsets.UTF_8)); - var textures = props.get("textures").getAsJsonObject(); - if (textures.has("SKIN")) { - var skinObj = textures.get("SKIN").getAsJsonObject(); - var skinUrl = skinObj.get("url").getAsString(); - var skin = client.sendAsync(HttpRequest.newBuilder().uri(URI.create(skinUrl)).GET().build(), HttpResponse.BodyHandlers.ofByteArray()) - .thenApply(HttpResponse::body); - var skinKey = skinUrl.substring(skinUrl.lastIndexOf("/") + 1); - var classicModel = true; - if (skinObj.has("metadata")) { - var metadata = skinObj.get("metadata").getAsJsonObject(); - var model = metadata.get("model").getAsString(); - classicModel = MCProfile.OnlineSkin.VARIANT_CLASSIC.toLowerCase(Locale.ROOT).equals(model.toLowerCase(Locale.ROOT)); - } - return new SkinBundle(name, id, skin, skinKey, classicModel); - } - } - } - } - return null; - }); - } - - private MCProfile extractProfile(JsonObject profileJson) { - if (profileJson.has("error") && "NOT_FOUND".equals(profileJson.get("error").getAsString())) { - throw new IllegalStateException("profile not found"); - } - if (!profileJson.has("id")) { - logger.warn("Unexpected profile response: {}", profileJson); - throw new IllegalStateException("unexpected error"); - } - return MCProfile.get(profileJson); - } - - - public CompletableFuture setSkin(Account account, Skin.Online skin) { - return setSkin(account, skin.classicVariant(), skin.url()); - } - - public CompletableFuture setSkin(Account account, boolean wide, String url) { - return requestJson(HttpRequest.newBuilder() - .uri(URI.create("https://api.minecraftservices.com/minecraft/profile/skins")) - .header("Authorization", "Bearer " + account.getAuthToken()) - .POST(HttpRequest.BodyPublishers.ofString(JsonBuilders.JsonObject.create() - .field("variant", wide ? MCProfile.OnlineSkin.VARIANT_CLASSIC : MCProfile.OnlineSkin.VARIANT_SLIM) - .field("url", url).asString())).build()) - .thenApply(this::extractProfile); - } - - public CompletableFuture uploadAndSetSkin(Account account, boolean wide, byte[] image) { - return uploadAndSetSkin(account, MultipartBodyPublisher.newBuilder() - .textPart("variant", wide ? "classic" : "slim") - .formPart("file", HttpRequest.BodyPublishers.ofByteArray(image)).build()); - } - - public CompletableFuture uploadAndSetSkin(Account account, Skin.LocalSkin skin) { - try { - return uploadAndSetSkin(account, MultipartBodyPublisher.newBuilder() - .textPart("variant", skin.classicVariant() ? "classic" : "slim") - .filePart("file", skin.file(), MediaType.IMAGE_PNG).build()); - } catch (FileNotFoundException e) { - throw new RuntimeException(e); - } - } - - private CompletableFuture uploadAndSetSkin(Account account, MultipartBodyPublisher publisher) { - return requestJson(HttpRequest.newBuilder() - .uri(URI.create("https://api.minecraftservices.com/minecraft/profile/skins")) - .header("Authorization", "Bearer " + account.getAuthToken()) - .POST(publisher).build()) - .thenApply(this::extractProfile); - } - - public CompletableFuture resetSkin(Account account) { - return requestJson(HttpRequest.newBuilder().DELETE() - .uri(URI.create("https://api.minecraftservices.com/minecraft/profile/skins/active")) - .header("Authorization", "Bearer " + account.getAuthToken()) - .build()) - .thenApply(this::extractProfile); - } - - public CompletableFuture hideCape(Account account) { - return requestJson(HttpRequest.newBuilder().DELETE() - .uri(URI.create("https://api.minecraftservices.com/minecraft/profile/capes/active")) - .header("Authorization", "Bearer " + account.getAuthToken()) - .build()) - .thenApply(this::extractProfile); - } - - public CompletableFuture showCape(Account account, MCProfile.OnlineCape cape) { - return requestJson(HttpRequest.newBuilder() - .uri(URI.create("https://api.minecraftservices.com/minecraft/profile/capes/active")) - .header("Authorization", "Bearer " + account.getAuthToken()) - .PUT(HttpRequest.BodyPublishers.ofString(JsonBuilders.JsonObject.create().field("capeId", cape.id()).asString())).build()) - .thenApply(this::extractProfile); - } -} diff --git a/common/src/main/java/io/github/axolotlclient/modules/auth/skin/Asset.java b/common/src/main/java/io/github/axolotlclient/modules/auth/skin/Asset.java deleted file mode 100644 index 42c953c43..000000000 --- a/common/src/main/java/io/github/axolotlclient/modules/auth/skin/Asset.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright © 2025 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.modules.auth.skin; - -import java.nio.file.Path; -import java.util.concurrent.CompletableFuture; - -import io.github.axolotlclient.modules.auth.Account; -import io.github.axolotlclient.modules.auth.MSApi; - -public interface Asset { - - byte[] image(); - - boolean active(); - - CompletableFuture equip(MSApi api, Account account); - - String sha256(); - - interface Local extends Asset { - Path file(); - } - - interface Online extends Asset { - String url(); - - default boolean supportsDownload() { - return false; - } - } -} diff --git a/common/src/main/java/io/github/axolotlclient/modules/auth/skin/Cape.java b/common/src/main/java/io/github/axolotlclient/modules/auth/skin/Cape.java deleted file mode 100644 index a563558e1..000000000 --- a/common/src/main/java/io/github/axolotlclient/modules/auth/skin/Cape.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright © 2025 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.modules.auth.skin; - -public interface Cape extends Asset.Online { - String id(); - - String alias(); -} diff --git a/common/src/main/java/io/github/axolotlclient/modules/auth/skin/Skin.java b/common/src/main/java/io/github/axolotlclient/modules/auth/skin/Skin.java deleted file mode 100644 index ce7f8b9ed..000000000 --- a/common/src/main/java/io/github/axolotlclient/modules/auth/skin/Skin.java +++ /dev/null @@ -1,303 +0,0 @@ -/* - * Copyright © 2025 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.modules.auth.skin; - -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; - -import com.google.common.hash.Hashing; -import io.github.axolotlclient.api.util.UUIDHelper; -import io.github.axolotlclient.modules.auth.Account; -import io.github.axolotlclient.modules.auth.MSApi; -import io.github.axolotlclient.util.GsonHelper; -import io.github.axolotlclient.util.NetworkUtil; -import org.jetbrains.annotations.NotNull; - -public interface Skin extends Asset { - boolean classicVariant(); - - void classicVariant(boolean classic); - - interface Local extends Skin, Asset.Local { - - } - - interface Online extends Skin, Asset.Online { - } - - final class LocalSkin implements Local { - public static final String META_DIR = ".meta"; - public static final String METADATA_SUFFIX = ".meta"; - public static final String CLASSIC_METADATA_KEY = "variant_classic"; - private boolean classic; - private final Path file; - private final byte[] data; - private final String textureKey; - - public LocalSkin(boolean classic, Path file, byte[] image, String textureKey) { - this.classic = classic; - this.file = file; - this.data = image; - this.textureKey = textureKey; - } - - @SuppressWarnings("unchecked") - public static Map readMetadata(Path skinFile) throws IOException { - var metadataFile = getMetadataFile(skinFile); - if (!Files.exists(metadataFile)) return null; - - try (var in = Files.newInputStream(metadataFile)) { - return (Map) GsonHelper.read(in); - } - } - - public static void writeMetadata(Path skinFile, Map metadata) throws IOException { - var metadataFile = getMetadataFile(skinFile); - Files.createDirectories(metadataFile.getParent()); - try (var out = Files.newOutputStream(metadataFile); - var writer = new OutputStreamWriter(out)) { - GsonHelper.GSON.toJson(metadata, writer); - } - } - - public static void deleteMetadata(Path skinFile) throws IOException { - Files.deleteIfExists(getMetadataFile(skinFile)); - } - - private static @NotNull Path getMetadataFile(Path skinFile) { - return skinFile.resolveSibling(META_DIR).resolve(skinFile.getFileName().toString() + METADATA_SUFFIX); - } - - @Override - public boolean classicVariant() { - return classic; - } - - @Override - public void classicVariant(boolean classic) { - if (classic != this.classic) { - try { - var metadata = readMetadata(file()); - if (metadata == null) metadata = new HashMap<>(); - metadata.put(CLASSIC_METADATA_KEY, classic); - writeMetadata(file(), metadata); - } catch (IOException ignored) { - } - } - this.classic = classic; - } - - @Override - public byte[] image() { - return data; - } - - @Override - public boolean active() { - return false; - } - - @Override - public CompletableFuture equip(MSApi api, Account account) { - return api.uploadAndSetSkin(account, this); - } - - @Override - public Path file() { - return file; - } - - @Override - public String sha256() { - return textureKey; - } - } - - record Shared(Skin.Local local, MSApi.MCProfile.OnlineSkin online) implements Local, Online { - - @Override - public boolean classicVariant() { - return local.classicVariant(); - } - - @Override - public void classicVariant(boolean classic) { - local.classicVariant(classic); - } - - @Override - public byte[] image() { - return local.image(); - } - - @Override - public boolean active() { - return online.active(); - } - - @Override - public CompletableFuture equip(MSApi api, Account account) { - return online.equip(api, account); - } - - @Override - public String sha256() { - return online.sha256(); - } - - @Override - public Path file() { - return local.file(); - } - - @Override - public String url() { - return online.url(); - } - - @Override - public boolean supportsDownload() { - return true; - } - } - - static Skin getDefaultSkin(Account account) { - return getDefaultSkin(UUIDHelper.fromUndashed(account.getUuid())); - } - - static Skin getDefaultSkin(UUID uuid) { - return DefaultSkin.get(uuid); - } -} - -record DefaultSkin(String name, String stringUrl, URI url, boolean wide) { - private static final HttpClient client = NetworkUtil.createHttpClient(); - private static final Map DEFAULT_SKIN_CACHE = new HashMap<>(); - - DefaultSkin(String name, String url, boolean wide) { - this(name, url, URI.create(url), wide); - } - - private static final DefaultSkin[] SKINS = new DefaultSkin[]{ - new DefaultSkin("alex", "https://minecraft.wiki/images/Alex_%28slim_texture%29_JE3.png", false), - new DefaultSkin("ari", "https://minecraft.wiki/images/Ari_%28slim_texture%29_JE1.png", false), - new DefaultSkin("efe", "https://minecraft.wiki/images/Efe_%28slim_texture%29_JE1.png", false), - new DefaultSkin("kai", "https://minecraft.wiki/images/Kai_%28slim_texture%29_JE1.png", false), - new DefaultSkin("makena", "https://minecraft.wiki/images/Makena_%28slim_texture%29_JE1.png", false), - new DefaultSkin("noor", "https://minecraft.wiki/images/Noor_%28slim_texture%29_JE1.png", false), - new DefaultSkin("steve", "https://minecraft.wiki/images/Steve_%28slim_texture%29_JE2.png", false), - new DefaultSkin("sunny", "https://minecraft.wiki/images/Sunny_%28slim_texture%29_JE1.png", false), - new DefaultSkin("zuri", "https://minecraft.wiki/images/Zuri_%28slim_texture%29_JE1.png", false), - new DefaultSkin("alex", "https://minecraft.wiki/images/Alex_%28classic_texture%29_JE2.png", true), - new DefaultSkin("ari", "https://minecraft.wiki/images/Ari_%28classic_texture%29_JE1.png", true), - new DefaultSkin("efe", "https://minecraft.wiki/images/Efe_%28classic_texture%29_JE1.png", true), - new DefaultSkin("kai", "https://minecraft.wiki/images/Kai_%28classic_texture%29_JE1.png", true), - new DefaultSkin("makena", "https://minecraft.wiki/images/Makena_%28classic_texture%29_JE1.png", true), - new DefaultSkin("noor", "https://minecraft.wiki/images/Noor_%28classic_texture%29_JE1.png", true), - new DefaultSkin("steve", "https://minecraft.wiki/images/Steve_%28classic_texture%29_JE6.png", true), - new DefaultSkin("sunny", "https://minecraft.wiki/images/Sunny_%28classic_texture%29_JE1.png", true), - new DefaultSkin("zuri", "https://minecraft.wiki/images/Zuri_%28classic_texture%29_JE1.png", true) - }; - - @SuppressWarnings("UnstableApiUsage") - public Skin getSkin() { - return DEFAULT_SKIN_CACHE.computeIfAbsent(this, unused -> { - var wrapper = new Skin.Online() { - private DefaultSkin wrapped = DefaultSkin.this; - private byte[] data = null; - private String hash = null; - - private void load() { - data = client.sendAsync(HttpRequest.newBuilder().GET().uri(wrapped.url()).build(), HttpResponse.BodyHandlers.ofByteArray()) - .thenApply(HttpResponse::body).join(); - hash = Hashing.sha256().hashBytes(data).toString(); - } - - @Override - public boolean classicVariant() { - return wrapped.wide(); - } - - @Override - public void classicVariant(boolean classic) { - int i = switch (name()) { - case "alex" -> 0; - case "ari" -> 1; - case "efe" -> 2; - case "kai" -> 3; - case "makena" -> 4; - case "noor" -> 5; - case "steve" -> 6; - case "sunny" -> 7; - case "zuri" -> 8; - default -> throw new IllegalStateException(); - }; - if (classic) i += 9; - wrapped = SKINS[i]; - load(); - } - - @Override - public byte[] image() { - return data; - } - - @Override - public boolean active() { - return false; - } - - @Override - public CompletableFuture equip(MSApi api, Account account) { - return api.setSkin(account, this); - } - - @Override - public String sha256() { - return hash; - } - - @Override - public String url() { - return wrapped.stringUrl(); - } - }; - wrapper.load(); - return wrapper; - }); - } - - static Skin get(UUID uuid) { - return SKINS[Math.floorMod(uuid.hashCode(), SKINS.length)].getSkin(); - } -} diff --git a/common/src/main/java/io/github/axolotlclient/modules/auth/skin/SkinImportUtil.java b/common/src/main/java/io/github/axolotlclient/modules/auth/skin/SkinImportUtil.java deleted file mode 100644 index a2a8fc8e4..000000000 --- a/common/src/main/java/io/github/axolotlclient/modules/auth/skin/SkinImportUtil.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright © 2025 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.modules.auth.skin; - -import java.nio.file.Path; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.CompletableFuture; - -import net.fabricmc.loader.api.FabricLoader; -import org.lwjgl.system.MemoryStack; -import org.lwjgl.util.tinyfd.TinyFileDialogs; - -public class SkinImportUtil { - public static CompletableFuture> openImportSkinDialog() { - return CompletableFuture.supplyAsync(() -> { - try (MemoryStack stack = MemoryStack.stackPush()) { - var pointers = stack.pointers(stack.UTF8("*.png")); - @SuppressWarnings("DataFlowIssue") var result = TinyFileDialogs.tinyfd_openFileDialog("Import Skins", - FabricLoader.getInstance().getGameDir().toString(), pointers, null, true); - if (result != null) { - return Arrays.stream(result.split("\\|")) - .map(Path::of).toList(); - } - return List.of(); - } - }); - } -} diff --git a/common/src/main/java/io/github/axolotlclient/modules/hud/snapping/SnappingHelper.java b/common/src/main/java/io/github/axolotlclient/modules/hud/snapping/SnappingHelper.java index 81fa40eca..c5a6c53b5 100644 --- a/common/src/main/java/io/github/axolotlclient/modules/hud/snapping/SnappingHelper.java +++ b/common/src/main/java/io/github/axolotlclient/modules/hud/snapping/SnappingHelper.java @@ -26,7 +26,6 @@ import com.google.common.collect.Sets; import io.github.axolotlclient.AxolotlClientConfig.api.util.Color; -import io.github.axolotlclient.api.util.BiContainer; import io.github.axolotlclient.bridge.render.AxoRenderContext; import io.github.axolotlclient.bridge.render.AxoSprites; import io.github.axolotlclient.bridge.render.AxoWindow; @@ -34,6 +33,7 @@ import io.github.axolotlclient.modules.hud.gui.component.HudEntry; import io.github.axolotlclient.modules.hud.gui.layout.SnapAnchorType; import io.github.axolotlclient.modules.hud.util.Rectangle; +import io.github.axolotlclient.util.BiContainer; import io.github.axolotlclient.util.ClientColors; import lombok.Setter; diff --git a/common/src/main/java/io/github/axolotlclient/modules/hypixel/HypixelAbstractionLayer.java b/common/src/main/java/io/github/axolotlclient/modules/hypixel/HypixelAbstractionLayer.java index 6ff41ef98..a076967d3 100644 --- a/common/src/main/java/io/github/axolotlclient/modules/hypixel/HypixelAbstractionLayer.java +++ b/common/src/main/java/io/github/axolotlclient/modules/hypixel/HypixelAbstractionLayer.java @@ -25,161 +25,51 @@ import java.util.Optional; import java.util.UUID; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import java.util.function.Function; -import com.google.gson.FieldNamingPolicy; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import io.github.axolotlclient.api.API; -import io.github.axolotlclient.api.Request; -import io.github.axolotlclient.api.Response; import io.github.axolotlclient.util.CachedAPI; -import lombok.AllArgsConstructor; import lombok.Getter; +/** + * Local cache for Hypixel stats. Online lookups were removed with the backend, + * so every request completes empty and callers fall back to in-game data. + */ public class HypixelAbstractionLayer { - private static final Gson GSON = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create(); - private static final int MAX_ATTEMPTS = 5; - - @AllArgsConstructor - @Getter - private enum RequestDataType { - NETWORK_LEVEL("network_level"), - BEDWARS_LEVEL("bedwars_level"), - SKYWARS_EXPERIENCE("skywars_experience"), - BEDWARS_DATA("bedwars_data"), - PLAYER_DATA("player_data"); - private final String id; - } - - private final ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor(); - @Getter private static final HypixelAbstractionLayer instance = new HypixelAbstractionLayer(); - private void queueRetry(int attempts, String desc, Request request, CompletableFuture> future, long count, TimeUnit unit) { - service.schedule(() -> queueRequest0(attempts - 1, desc, request, future), count, unit); - } - - private void queueRequest0(int attempts, String desc, Request request, CompletableFuture> future) { - if (attempts <= 0) { - future.complete(Optional.empty()); - return; - } - - API.getInstance().get(request).whenComplete((response, throwable) -> { - if (response == Response.CLIENT_ERROR || (response != null && response.getStatus() == 0)) { - future.complete(Optional.empty()); - return; - } - if (response == null) { - API.getInstance().getLogger().warn("Failed to process request {}: ", desc, throwable); - future.complete(Optional.empty()); - return; - } - - if (response.getStatus() == 429) { - API.getInstance().getLogger().warn("Failed to process request {}: rate limited", desc, throwable); - - queueRetry( - attempts, desc, request, future, - response.firstHeader("RateLimit-Reset").map(Long::parseLong).orElse(2L), - TimeUnit.SECONDS - ); - } else if (response.getStatus() != 200 || response.isError()) { - API.getInstance().getLogger().debug("Failed to process request {} ({}): {}", desc, response.getStatus(), response.getBody()); - future.complete(Optional.empty()); - } else { - future.complete(Optional.of(response)); - } - }); - } - - private CompletableFuture> queueRequest(String desc, Request request) { - CompletableFuture> future = new CompletableFuture<>(); - queueRequest0(MAX_ATTEMPTS, desc, request, future); - return future; - } - - private CachedAPI create(RequestDataType type, Function app) { - return new CachedAPI<>(uuid -> { - Request request = Request.Route.HYPIXEL - .builder() - .field("request_type", type.getId()) - .field("target_player", uuid) - .build(); - - return queueRequest("[%s, %s]".formatted(type.getId(), uuid), request).thenApply(opt -> opt.flatMap(res -> { - try { - return Optional.ofNullable(app.apply(res)); - } catch (Throwable e) { - API.getInstance().getLogger().warn("Failed to parse request for {} (uuid={})", type.getId(), uuid); - return Optional.empty(); - } - })); - }, 128, true); - } - - private CachedAPI createLevel(RequestDataType type) { - return create(type, res -> { - var level = res.getBody(type.getId()).intValue(); - return level == -1 ? null : level; - }); - } - - private void freePlayerData(String uuid) { - bedwarsDataApi.invalidate(uuid); - networkLevelApi.invalidate(uuid); - bedwarsLevelApi.invalidate(uuid); - skywarsExpApi.invalidate(uuid); + private static CachedAPI empty() { + return new CachedAPI<>(uuid -> CompletableFuture.completedFuture(Optional.empty()), 1, true); } @Getter - private final CachedAPI bedwarsDataApi = create(RequestDataType.BEDWARS_DATA, - res -> new BedwarsData( - res.getBody("final_kills_bedwars").intValue(), - res.getBody("final_deaths_bedwars").intValue(), - res.getBody("beds_broken_bedwars").intValue(), - res.getBody("deaths_bedwars").intValue(), - res.getBody("kills_bedwars").intValue(), - res.getBody("losses_bedwars").intValue(), - res.getBody("wins_bedwars").intValue(), - res.getBody("winstreak").intValue() - ) - ); + private final CachedAPI bedwarsDataApi = empty(); @Getter - private final CachedAPI networkLevelApi = createLevel(RequestDataType.NETWORK_LEVEL); + private final CachedAPI networkLevelApi = empty(); @Getter - private final CachedAPI bedwarsLevelApi = createLevel(RequestDataType.BEDWARS_LEVEL); + private final CachedAPI bedwarsLevelApi = empty(); @Getter - private final CachedAPI skywarsExpApi = create(RequestDataType.SKYWARS_EXPERIENCE, - res -> { - var exp = res.getBody(RequestDataType.SKYWARS_EXPERIENCE.getId()).intValue(); - if (exp == -1) { - return null; - } - return Math.round(ExpCalculator.getLevelForExp(exp)); - }); + private final CachedAPI skywarsExpApi = empty(); @Getter - private final CachedAPI playerDataApi = create(RequestDataType.PLAYER_DATA, - response -> GSON.fromJson(response.getPlainBody(), PlayerData.class) - ); + private final CachedAPI playerDataApi = empty(); public void clearPlayerData() { bedwarsDataApi.invalidate(); networkLevelApi.invalidate(); bedwarsLevelApi.invalidate(); skywarsExpApi.invalidate(); + playerDataApi.invalidate(); } public void handleDisconnectEvents(UUID uuid) { - freePlayerData(uuid.toString()); + String key = uuid.toString(); + bedwarsDataApi.invalidate(key); + networkLevelApi.invalidate(key); + bedwarsLevelApi.invalidate(key); + skywarsExpApi.invalidate(key); + playerDataApi.invalidate(key); } } diff --git a/common/src/main/java/io/github/axolotlclient/modules/hypixel/LevelHead.java b/common/src/main/java/io/github/axolotlclient/modules/hypixel/LevelHead.java index d6ac4da19..741175bc6 100644 --- a/common/src/main/java/io/github/axolotlclient/modules/hypixel/LevelHead.java +++ b/common/src/main/java/io/github/axolotlclient/modules/hypixel/LevelHead.java @@ -1,7 +1,8 @@ /* * Copyright © 2024 moehreag & Contributors + * Copyright © 2026 DSNS * - * This file is part of AxolotlClient. + * This file is part of DolphinClient, a fork of AxolotlClient. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -26,16 +27,17 @@ import io.github.axolotlclient.AxolotlClientConfig.impl.options.BooleanOption; import io.github.axolotlclient.AxolotlClientConfig.impl.options.ColorOption; import io.github.axolotlclient.AxolotlClientConfig.impl.options.EnumOption; -import io.github.axolotlclient.api.API; import io.github.axolotlclient.util.CachedAPI; import io.github.axolotlclient.util.ClientColors; +import io.github.axolotlclient.util.UUIDHelper; +import io.github.axolotlclient.util.options.ForceableBooleanOption; import lombok.AllArgsConstructor; import lombok.Getter; public class LevelHead implements AbstractHypixelMod { @Getter private static final LevelHead Instance = new LevelHead(); - public final BooleanOption enabled = new BooleanOption("enabled", "levelhead.enabled.tooltip", false); + public final ForceableBooleanOption enabled = new ForceableBooleanOption("enabled", "levelhead.enabled.tooltip", false); public final BooleanOption background = new BooleanOption("background", false); public final ColorOption textColor = new ColorOption("textcolor", ClientColors.GOLD); public final EnumOption mode = new EnumOption<>("levelHeadMode", Mode.class, Mode.NETWORK); @@ -52,12 +54,13 @@ public static String getDisplayString(Mode mode, String uuid) { } public String getDisplayString(String uuid) { - return getDisplayString(mode.get(), API.sanitizeUUID(uuid)); + return getDisplayString(mode.get(), UUIDHelper.sanitizeUUID(uuid)); } @Override public void init() { + enabled.setForceOff(true, "levelhead.enabled.tooltip"); category.add(enabled); category.add(textColor); category.add(background); diff --git a/common/src/main/java/io/github/axolotlclient/modules/hypixel/NickHider.java b/common/src/main/java/io/github/axolotlclient/modules/hypixel/NickHider.java index 47344621e..9691ba98e 100644 --- a/common/src/main/java/io/github/axolotlclient/modules/hypixel/NickHider.java +++ b/common/src/main/java/io/github/axolotlclient/modules/hypixel/NickHider.java @@ -28,7 +28,7 @@ import io.github.axolotlclient.AxolotlClientConfig.api.options.OptionCategory; import io.github.axolotlclient.AxolotlClientConfig.impl.options.BooleanOption; import io.github.axolotlclient.AxolotlClientConfig.impl.options.StringOption; -import io.github.axolotlclient.api.util.BiContainer; +import io.github.axolotlclient.util.BiContainer; import io.github.axolotlclient.bridge.AxoMinecraftClient; import io.github.axolotlclient.bridge.util.AxoText; import lombok.Getter; diff --git a/common/src/main/java/io/github/axolotlclient/modules/hypixel/StatsMod.java b/common/src/main/java/io/github/axolotlclient/modules/hypixel/StatsMod.java index 0bf7dd936..f938c7396 100644 --- a/common/src/main/java/io/github/axolotlclient/modules/hypixel/StatsMod.java +++ b/common/src/main/java/io/github/axolotlclient/modules/hypixel/StatsMod.java @@ -27,8 +27,6 @@ import java.util.Map; import io.github.axolotlclient.AxolotlClientConfig.api.options.OptionCategory; -import io.github.axolotlclient.api.API; -import io.github.axolotlclient.bridge.AxoMinecraftClient; import io.github.axolotlclient.bridge.commands.AxoClientCmdSrcStack; import io.github.axolotlclient.bridge.commands.Commands; import io.github.axolotlclient.bridge.commands.PlayerArgument; @@ -197,34 +195,8 @@ private void register(Commands commands) { for (Entry handler : HANDLERS) { command.then(Commands.literal(handler.name()).then(argument("player", PlayerArgument.player()).executes(c -> { - if (!API.getInstance().getApiOptions().privacyAccepted.get().isAccepted()) { - c.getSource().br$sendError(translatable("playerstats.error.api_disabled").br$color(RED)); - return -1; - } - - if (!API.getInstance().isAuthenticated()) { - c.getSource().br$sendError(translatable("playerstats.error.api_unauthenticated").br$color(RED)); - return -1; - } - - final var res = PlayerArgument.get(c, "player"); - - res.uuid().whenCompleteAsync((s, ex) -> { - if (s.isEmpty()) { - c.getSource().br$sendError(translatable("playerstats.error.unknown_player").br$color(RED)); - } else { - HypixelAbstractionLayer.getInstance().getPlayerDataApi().getAsync(s.get()).whenCompleteAsync((playerData, throwable) -> { - if (playerData.isEmpty()) { - c.getSource().br$sendError(translatable("playerstats.error.failed_data").br$color(RED)); - return; - } - - handler.handler().accept(c.getSource(), s.get(), res.playerName(), playerData.get()); - }, AxoMinecraftClient.getInstance()); - } - }); - - return 0; + c.getSource().br$sendError(translatable("playerstats.error.api_disabled").br$color(RED)); + return -1; }))); } diff --git a/common/src/main/java/io/github/axolotlclient/modules/hypixel/bedwars/BedwarsPlayer.java b/common/src/main/java/io/github/axolotlclient/modules/hypixel/bedwars/BedwarsPlayer.java index 581a3db2d..6984b5630 100644 --- a/common/src/main/java/io/github/axolotlclient/modules/hypixel/bedwars/BedwarsPlayer.java +++ b/common/src/main/java/io/github/axolotlclient/modules/hypixel/bedwars/BedwarsPlayer.java @@ -22,7 +22,7 @@ package io.github.axolotlclient.modules.hypixel.bedwars; -import io.github.axolotlclient.api.util.UUIDHelper; +import io.github.axolotlclient.util.UUIDHelper; import io.github.axolotlclient.bridge.AxoPlayerListEntry; import io.github.axolotlclient.bridge.Platform; import lombok.Data; diff --git a/common/src/main/java/io/github/axolotlclient/modules/hypixel/bedwars/StatsOverlay.java b/common/src/main/java/io/github/axolotlclient/modules/hypixel/bedwars/StatsOverlay.java index 3ffb0895e..a765507e0 100644 --- a/common/src/main/java/io/github/axolotlclient/modules/hypixel/bedwars/StatsOverlay.java +++ b/common/src/main/java/io/github/axolotlclient/modules/hypixel/bedwars/StatsOverlay.java @@ -31,7 +31,6 @@ import io.github.axolotlclient.AxolotlClientConfig.api.options.Option; import io.github.axolotlclient.AxolotlClientConfig.impl.options.BooleanOption; import io.github.axolotlclient.AxolotlClientConfig.impl.options.IntegerOption; -import io.github.axolotlclient.api.API; import io.github.axolotlclient.bridge.AxoMinecraftClient; import io.github.axolotlclient.bridge.AxoPlayerListEntry; import io.github.axolotlclient.bridge.key.AxoKeybinding; @@ -41,10 +40,8 @@ import io.github.axolotlclient.bridge.util.AxoIdentifier; import io.github.axolotlclient.bridge.util.AxoText; import io.github.axolotlclient.modules.hud.gui.entry.TextHudEntry; -import io.github.axolotlclient.modules.hypixel.HypixelAbstractionLayer; import io.github.axolotlclient.modules.hypixel.PlayerData; import io.github.axolotlclient.modules.hypixel.PlayerData.Bedwars.CombinedGameData; -import org.jetbrains.annotations.Nullable; public class StatsOverlay extends TextHudEntry { @FunctionalInterface @@ -166,8 +163,6 @@ private static PlayerData.Bedwars createFake(int level, int winstreak, CombinedG private final Map> playersByTeam = new EnumMap<>(BedwarsTeam.class); private final AxoKeybinding toggle = AxoKeybinding.create(AxoKeys.KEY_UNKNOWN, "bedwars_stats_overlay"); private boolean shouldRender = false; - @Nullable - private String errorMessage = null; public StatsOverlay(BedwarsMod mod) { super(400, 600, true); @@ -183,46 +178,16 @@ public StatsOverlay(BedwarsMod mod) { var index = Math.min(namesSize, player.getNumber() - 1); teamNames.add(index, player.getProfile().br$getName()); } - HypixelAbstractionLayer.getInstance().getPlayerDataApi() - .getAsync(player.getProfile().br$getId().toString()) - .thenAcceptAsync(o -> - o.ifPresent(data -> stats.put(player.getProfile().br$getName(), data.bedwars())), client); }); } private void onStart(BedwarsGame g) { playersByTeam.clear(); - // can't call clear here, since we need a fresh map to avoid requests from writing stats = new HashMap<>(); shouldRender = toggleKey.get() && autoActivate.get(); - if (!API.getInstance().getApiOptions().privacyAccepted.get().isAccepted()) { - errorMessage = "API Not Enabled!"; - return; - } - - if (!API.getInstance().isAuthenticated()) { - errorMessage = "API Not Authenticated!"; - return; - } - - final var api = HypixelAbstractionLayer.getInstance().getPlayerDataApi(); - - // need to use capturedStats since this map could've been "retired" - final var capturedStats = this.stats; - - g.getPlayersByTeam().forEach((t, e) -> { - playersByTeam.put(t, e.stream().map(AxoPlayerListEntry::br$getName).collect(Collectors.toCollection(ArrayList::new))); // explicit creation because we require mutability - e.forEach(entry -> - api.getAsync(entry.br$getId().toString()) - .whenCompleteAsync((playerData, throwable) -> { - if (playerData.isEmpty()) { - return; - } - - capturedStats.put(entry.br$getName(), playerData.get().bedwars()); - }, client)); - }); + g.getPlayersByTeam().forEach((t, e) -> + playersByTeam.put(t, e.stream().map(AxoPlayerListEntry::br$getName).collect(Collectors.toCollection(ArrayList::new)))); } private void onEnd() { @@ -231,10 +196,6 @@ private void onEnd() { @Override public void render(AxoRenderContext ctx, float delta) { - if (errorMessage != null) { - ctx.br$drawString(AxoText.Color.RED + errorMessage, getContentPos().x, getContentPos().y, 0xffffffff, shadow.get()); - } - if (mod.inGame() && shouldRender) { super.render(ctx, delta); } diff --git a/common/src/main/java/io/github/axolotlclient/modules/screenshotUtils/ImageNetworking.java b/common/src/main/java/io/github/axolotlclient/modules/screenshotUtils/ImageNetworking.java deleted file mode 100644 index dfb91ce6a..000000000 --- a/common/src/main/java/io/github/axolotlclient/modules/screenshotUtils/ImageNetworking.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * Copyright © 2026 DSNS - * - * This file is part of DolphinClient, a fork of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.modules.screenshotUtils; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.time.Instant; -import java.util.Base64; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import io.github.axolotlclient.DolphinClientCommon; -import io.github.axolotlclient.api.API; -import io.github.axolotlclient.api.Constants; -import io.github.axolotlclient.api.Request; - -public abstract class ImageNetworking { - - private static final Pattern URL_PATTERN = Pattern.compile("(?:.+/)?(\\d+)(?:/?.+)?"); - - public abstract void uploadImage(Path file); - - protected CompletableFuture upload(Path file) { - try { - return upload(file.getFileName().toString(), Files.readAllBytes(file)); - } catch (IOException e) { - DolphinClientCommon.getInstance().getLogger().error("Failed to upload image", e); - return CompletableFuture.completedFuture(""); - } - } - - protected CompletableFuture upload(String name, byte[] data) { - return API.getInstance().post(Request.Route.IMAGE.builder().path(name).rawBody(data).build()) - .thenApply(response -> { - if (response.isError()) { - DolphinClientCommon.getInstance().getLogger().error("Failed to upload image, server responded with " + response); - return ""; - } - return idToUrl(response.getPlainBody()); - }); - } - - protected static String idToUrl(String id) { - return Request.Route.IMAGE.builder().path(id).path("view").build().resolve().toString(); - } - - protected static Optional urlToId(String url) { - if (url.contains("/") && !url.startsWith(Constants.API_URL)) { - return Optional.empty(); - } - Matcher matcher = URL_PATTERN.matcher(url); - if (!matcher.matches()) { - return Optional.empty(); - } - return Optional.of(matcher.group(1)); - } - - protected static Optional ensureUrl(String urlOrId) { - return urlToId(urlOrId).map(ImageNetworking::idToUrl); - } - - protected CompletableFuture download(String url) { - Optional id = urlToId(url); - return id.map(s -> API.getInstance().get(Request.Route.IMAGE.builder().requiresAuthentication(false).path(s).build()) - .thenApply(res -> { - if (res.isError()) { - return ImageData.EMPTY; - } - String name = res.getBody("filename"); - String base64 = res.getBody("file"); - String uploader = res.getBody("uploader"); - Instant sharedAt = res.getBody("shared_at", Instant::parse); - return new ImageData(name, Base64.getDecoder().decode(base64), uploader, sharedAt); - })).orElseGet(() -> CompletableFuture.completedFuture(ImageData.EMPTY)); - } - - public record ImageData(String name, byte[] data, String uploader, Instant sharedAt) { - public static final ImageData EMPTY = new ImageData("", new byte[0], null, null); - } -} diff --git a/common/src/main/java/io/github/axolotlclient/api/util/BiContainer.java b/common/src/main/java/io/github/axolotlclient/util/BiContainer.java similarity index 97% rename from common/src/main/java/io/github/axolotlclient/api/util/BiContainer.java rename to common/src/main/java/io/github/axolotlclient/util/BiContainer.java index 0db00971c..36007331a 100644 --- a/common/src/main/java/io/github/axolotlclient/api/util/BiContainer.java +++ b/common/src/main/java/io/github/axolotlclient/util/BiContainer.java @@ -20,7 +20,7 @@ * For more information, see the LICENSE file. */ -package io.github.axolotlclient.api.util; +package io.github.axolotlclient.util; import lombok.Data; diff --git a/common/src/main/java/io/github/axolotlclient/util/GsonHelper.java b/common/src/main/java/io/github/axolotlclient/util/GsonHelper.java index 974e301d8..22c7d6e30 100644 --- a/common/src/main/java/io/github/axolotlclient/util/GsonHelper.java +++ b/common/src/main/java/io/github/axolotlclient/util/GsonHelper.java @@ -35,7 +35,6 @@ import com.google.gson.*; import com.google.gson.stream.JsonReader; -import io.github.axolotlclient.api.util.InstantTypeAdapter; public class GsonHelper { diff --git a/common/src/main/java/io/github/axolotlclient/api/util/InstantTypeAdapter.java b/common/src/main/java/io/github/axolotlclient/util/InstantTypeAdapter.java similarity index 97% rename from common/src/main/java/io/github/axolotlclient/api/util/InstantTypeAdapter.java rename to common/src/main/java/io/github/axolotlclient/util/InstantTypeAdapter.java index 5706ce81c..e3def76de 100644 --- a/common/src/main/java/io/github/axolotlclient/api/util/InstantTypeAdapter.java +++ b/common/src/main/java/io/github/axolotlclient/util/InstantTypeAdapter.java @@ -20,7 +20,7 @@ * For more information, see the LICENSE file. */ -package io.github.axolotlclient.api.util; +package io.github.axolotlclient.util; import java.io.IOException; import java.time.Instant; diff --git a/common/src/main/java/io/github/axolotlclient/util/NetworkUtil.java b/common/src/main/java/io/github/axolotlclient/util/NetworkUtil.java deleted file mode 100644 index 40095b4d0..000000000 --- a/common/src/main/java/io/github/axolotlclient/util/NetworkUtil.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * Copyright © 2026 DSNS - * - * This file is part of DolphinClient, a fork of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.util; - -import java.net.http.HttpClient; -import java.time.Duration; - -import com.github.mizosoft.methanol.Methanol; -import io.github.axolotlclient.DolphinClientCommon; -import lombok.experimental.UtilityClass; - -@UtilityClass -public class NetworkUtil { - private static final String UA_PATTERN = "DolphinClient/%s Minecraft/%s"; - - public String getUserAgent() { - return getUserAgent(false); - } - - public String getUserAgent(boolean includeContact) { - var ua = UA_PATTERN.formatted(DolphinClientCommon.VERSION, DolphinClientCommon.GAME_VERSION); - if (includeContact) { - return ua + " contact: moehreaggmail.com"; - } - return ua; - } - - public HttpClient createHttpClient() { - return createHttpClient(false); - } - - public HttpClient createHttpClient(boolean includeContact) { - return Methanol.newBuilder().userAgent(getUserAgent(includeContact)) - .followRedirects(HttpClient.Redirect.NORMAL) - .requestTimeout(Duration.ofMinutes(1)) - .executor(ThreadExecuter.service()).build(); - } -} diff --git a/common/src/main/java/io/github/axolotlclient/api/Constants.java b/common/src/main/java/io/github/axolotlclient/util/UUIDHelper.java similarity index 57% rename from common/src/main/java/io/github/axolotlclient/api/Constants.java rename to common/src/main/java/io/github/axolotlclient/util/UUIDHelper.java index 8189ced3b..a29a06e66 100644 --- a/common/src/main/java/io/github/axolotlclient/api/Constants.java +++ b/common/src/main/java/io/github/axolotlclient/util/UUIDHelper.java @@ -20,15 +20,30 @@ * For more information, see the LICENSE file. */ -package io.github.axolotlclient.api; +package io.github.axolotlclient.util; -import lombok.experimental.UtilityClass; +import java.util.UUID; -@UtilityClass -public class Constants { - public final boolean ENABLED = true; // Completely disable these features and hide their existence - private final boolean DEVELOPMENT = false; - public final String API_URL = DEVELOPMENT ? "http://localhost:8000" : "https://api.axolotlclient.com/v1/"; - public final String TERMS = "https://axolotlclient.com/terms"; - public final boolean TESTING = false; // When set to true, no requests will be sent +public class UUIDHelper { + public static UUID fromUndashed(String uuid) { + return UUID.fromString(uuid.trim().replaceFirst("(\\w{8})(\\w{4})(\\w{4})(\\w{4})(\\w{12})", "$1-$2-$3-$4-$5")); + } + + public static String toUndashed(UUID uuid) { + return sanitizeUUID(uuid.toString()); + } + + public static String sanitizeUUID(String uuid) { + if (uuid.contains("-")) { + return validateUUID(uuid.replace("-", "")); + } + return validateUUID(uuid); + } + + private static String validateUUID(String uuid) { + if (uuid.length() != 32) { + throw new IllegalArgumentException("Not a valid UUID (undashed): " + uuid); + } + return uuid; + } } diff --git a/common/src/main/java/io/github/axolotlclient/util/options/ForceableBooleanOption.java b/common/src/main/java/io/github/axolotlclient/util/options/ForceableBooleanOption.java index 0c8289545..d0c032722 100644 --- a/common/src/main/java/io/github/axolotlclient/util/options/ForceableBooleanOption.java +++ b/common/src/main/java/io/github/axolotlclient/util/options/ForceableBooleanOption.java @@ -34,6 +34,10 @@ public ForceableBooleanOption(String name, Boolean defaultValue) { super(name, defaultValue); } + public ForceableBooleanOption(String name, String tooltip, Boolean defaultValue) { + super(name, tooltip, defaultValue); + } + public ForceableBooleanOption(String name, Boolean defaultValue, ChangeListener changeListener) { super(name, defaultValue, changeListener); } diff --git a/common/src/main/resources/assets/dolphinclient/lang/en_us.json b/common/src/main/resources/assets/dolphinclient/lang/en_us.json index f8bc995dd..f8197fc90 100644 --- a/common/src/main/resources/assets/dolphinclient/lang/en_us.json +++ b/common/src/main/resources/assets/dolphinclient/lang/en_us.json @@ -72,25 +72,6 @@ "arrowhud": "Arrows", "attackindicatorbg": "Attack Indicator (Background)", "attackindicatorfg": "Attack Indicator (Foreground)", - "auth": "Authentication", - "auth.accountExpiredNotice": "Your Account %s has expired. Would you like to log in again?", - "auth.add": "Add Account", - "auth.add.choose": "Choose Account Type to add", - "auth.add.ms": "Add Microsoft Account", - "auth.add.offline": "Add Offline Account", - "auth.add.offline.name": "Enter name for the new account...", - "auth.login": "Login", - "auth.notif.login.failed": "Failed to log in!", - "auth.notif.login.failed.no_profile": "Could not retrieve Minecraft profile attached to account!", - "auth.notif.login.failed.no_entitlement": "Failed to check for game ownership, does the selected account own the game?", - "auth.notif.refresh.error": "Failed to refresh account tokens!", - "auth.notif.refresh.error.unexpected_response": "Unexpected response. The log may contain additional information.", - "auth.notif.login.successful": "Successfully logged in as %s!", - "auth.notif.refreshing": "Refreshing tokens for %s!", - "auth.notif.title": "Authentication", - "auth.refresh": "Refresh Account", - "auth.showButton": "Modify Title screen", - "auth.showButton.tooltip": "Whether to show the current user on the Title Screen.
Also doubles as a shortcut to view saved accounts.", "autoboop": "Auto Boop", "autoboop.enabled.tooltip": "Whether to run /boop when a friend of yours joins hypixel.", "autoExec": "Automatic Execution", @@ -238,9 +219,6 @@ "iconhud": "Icon", "iconsonly": "Icons Only", "imageSaved": "Saved!", - "imageUploadFailure": "Failed to upload image! See the log for more details.", - "imageUploadStarted": "Uploading Image...", - "imageUploadSuccess": "Successfully uploaded image! Get it here:", "imageViewer": "Image Viewer", "inGuis": "Apply to GUIs", "inverse": "Inverse", @@ -315,7 +293,6 @@ "packdisplayhud": "Resource Pack", "particles": "Particles", "particles.color.tooltip": "The color of the particle.
Needs \"Custom Color\" enabled.", - "pasteURL": "Paste URL or ID...", "perspective": "Perspective", "pingColor0": "Ping Color (< 0 ms)", "pingColor1": "Ping Color (< 150 ms)", @@ -422,11 +399,7 @@ "topbackgroundcolor": "Top Background Color", "toppadding": "Top Padding", "tpshud": "TPS", - "uploadAction": "[Upload]", - "upload_image": "Upload this image.", "useShadows": "Force Shadows", - "viewAccounts": "View Accounts", - "viewScreenshot": "View shared Screenshot", "width": "Width", "zoom": "Zoom", "zoomDivisor": "FOV Divisor", @@ -437,20 +410,11 @@ "zoomSpeed": "Animation Speed", "detailedLogging.tooltip": "In case you need even more verbose logs.", "action.copy": "Copy...", - "api.privacyNotice": "Privacy Notice", - "api.privacyNotice.accept": "Accept", - "api.privacyNotice.deny": "Deny/Decide later", - "api.privacyNotice.openPolicy": "View Terms", - "api.privacyNotice.description": "DolphinClient's online features - Hypixel stats and image sharing - are subject to our terms of service and our privacy policy. \n\nYou are free to accept or decline them, but in case of the latter those features will not be available.\n If you decline, you will be asked again the next time you start the game.", "configStyle": "Config UI Style", "configStyle.rounded": "Rounded", "configStyle.vanilla": "Vanilla", "configStyle.rounded-quicktoggles": "Rounded (QuickToggles)", "configStyle.vanilla-quicktoggles": "Vanilla (QuickToggles)", - "auth.copy_and_open": "Copy and Open", - "auth.time_left": "Time Left: %s", - "auth.working": "Working...", - "auth.finished": "Done!", "PROXY_FRONT": "Front", "PROXY_OFF": "OFF", "PROXY_MEMBER": "Member", @@ -473,16 +437,10 @@ "gallery.image.copy": "Copy", "gallery.image.copy.failure": "Failed to copy image", "gallery.image.copy.failure.description": "%s\nThe log may contain additional information.", - "gallery.image.upload": "Upload", - "gallery.image.upload.failure": "Failed to upload image", - "gallery.image.upload.failure.description": "You may find additional information in the game log.", - "gallery.image.upload.success": "Image Uploaded", - "gallery.image.upload.success.description": "A link to the uploaded image has been copied to your clipboard.", "gallery.image.open.external": "Open in image viewer", "gallery.image.open.external.browser": "Open in browser", "gallery.image.copy_url": "Copy image url", "gallery.title.local": "Local screenshots", - "gallery.download_external": "View image from url", "gallery.image.view": "View image", "gallery.image.loading.title": "Loading image...", "viewInGalleryAction": "[Gallery]", @@ -538,7 +496,7 @@ "debugcounters.pcount": "Particle Debug Info", "bedwars_resources.renderWhenRelevant": "Only Render When Relevant", "bedwars_resources": "Resources HUD", - "levelhead.enabled.tooltip": "Whether this module is enabled or not.
This module requires DolphinClient's online features
in order to communicate with the backend and the Hypixel API.", + "levelhead.enabled.tooltip": "Level Head is currently unavailable.
It required DolphinClient's online features to fetch Hypixel levels.", "bedwars.enabled.tooltip": "Whether this module is enabled or not.
Some functions of this module require DolphinClient's online features
in order to communicate with the backend and the Hypixel API.", "daycounterhud": "Day Counter", "daycounterhud.days": "%s Days", @@ -650,33 +608,6 @@ "inventoryhud.item_background": "Item Background", "inventoryhud.item_background_color": "Item Background Color", "inventoryhud.always_show_item_backgrounds": "Always Show Item Backgrounds", - "skins.manage": "Manage Skins", - "skins.nav.skins": "Skins", - "skins.nav.capes": "Capes", - "skins.error.failed_to_load": "Failed to load skins", - "skins.error.failed_to_load_desc": "Your log file may include more information.", - "skins.capes.no_cape": "No Cape", - "skins.manage.equipped": "Equipped", - "skins.manage.equip": "Equip", - "skins.loading": "Loading Skins...", - "skins.manage.equipping": "Equipping...", - "skins.manage.delete": "Delete Skin", - "skins.manage.delete.confirm": "Confirm Deletion", - "skins.manage.delete.confirm.desc": "This Skin's file will be deleted permanently!\n Are you sure?", - "skins.manage.delete.confirm.desc_active": "This Skin's file will be deleted permanently!\n Are you sure? The skin will not be un-equipped.", - "skins.manage.animations": "Skin Manger Animations", - "skins.manage.download": "Download Skin", - "skins.manage.import.local": "Import Skins", - "skins.manage.import.online": "Download Skin", - "skins.notification.title": "Skin Management", - "skins.notification.not_copied": "Skipped file %s because it does not seem like a valid skin file!", - "skins.manage.variant.classic": "Use Classic (Wide) Variant", - "skins.manage.variant.slim": "Use Slim Variant", - "skins.manage.import.online.input": "Enter Name or UUID:", - "skins.notification.import.online.failed_to_download": "Failed to download Skin of %s!", - "skins.notification.import.online.downloaded": "Downloaded Skin of %s!", - "skins.notification.import.online.failed_to_save": "Failed to save Skin of %s!", - "skins.notification.import.online.not_found": "Could not find user %s!", "profiles.profile.import": "Import Profiles", "profiles.profile.export": "Export", "profiles.profile.export.notification.failed": "Export Failed", @@ -690,8 +621,6 @@ "profiles.profile.import.notification.success": "Import Complete", "profiles.profile.import.notification.success.desc.one": "Imported 1 profile", "profiles.profile.import.notification.success.desc.more": "Imported %s profiles", - "skins.manage.equip.confirm": "Confirm Equipping", - "skins.manage.equip.download_current": "Your current skin is not downloaded and will get overridden by equipping a different one. Do you want to download your current skin before equipping this one?", "gallery.error.loading": "Failed to load Gallery", "gallery.reload": "Reload", "iconhud.mode": "Icon Display Mode", diff --git a/common/src/main/resources/assets/dolphinclient/lang/fr_fr.json b/common/src/main/resources/assets/dolphinclient/lang/fr_fr.json index 35b52ff18..c99153e27 100644 --- a/common/src/main/resources/assets/dolphinclient/lang/fr_fr.json +++ b/common/src/main/resources/assets/dolphinclient/lang/fr_fr.json @@ -72,25 +72,6 @@ "arrowhud": "Flèche HUD", "attackindicatorbg": "Indicateur d'Attaque (Arrière-plan)", "attackindicatorfg": "Indicateur d'Attaque (Premier plan)", - "auth": "Authentication", - "auth.accountExpiredNotice": "Votre compte %s a expiré. Souhaitez-vous vous reconnecter ?", - "auth.add": "Ajouter un Compte", - "auth.add.choose": "Choisissez le Type de Compte à ajouter", - "auth.add.ms": "Ajouter un Compte Microsoft", - "auth.add.offline": "Ajouter un Compte Hors-ligne", - "auth.add.offline.name": "Entrez le nom du nouveau compte...", - "auth.login": "Connexion", - "auth.notif.login.failed": "Échec de la connexion !", - "auth.notif.login.failed.no_profile": "Impossible de récupérer le profil Minecraft associé au compte !", - "auth.notif.login.failed.no_entitlement": "La vérification de l'achat du jeu a échoué. Le compte sélectionné possède-t-il le jeu ?", - "auth.notif.refresh.error": "Échec de l'actualisation des jetons de compte!", - "auth.notif.refresh.error.unexpected_response": "Réponse inattendue. Le journal peut contenir des informations supplémentaires.", - "auth.notif.login.successful": "Connecté avec succès en tant que %s !", - "auth.notif.refreshing": "Rafraîchissement des tokens pour %s !", - "auth.notif.title": "Authentication", - "auth.refresh": "Rafraîchir le Compte", - "auth.showButton": "Modifier l'Écran du Menu", - "auth.showButton.tooltip": "Afficher l'utilisateur actuel sur l'écran du menu/de titre.
Sert également de raccourci pour afficher les comptes enregistrés.", "autoboop": "AutoBoop", "autoboop.enabled.tooltip": "Exécuter /boop lorsqu'un de vos amis rejoint Hypixel.", "autoExec": "Exécution Automatique", @@ -237,9 +218,6 @@ "iconhud": "Icône HUD", "iconsonly": "Icônes Seulement", "imageSaved": "Sauvegardé !", - "imageUploadFailure": "Échec de l'upload de l'image ! Voir le log pour plus de détails.", - "imageUploadStarted": "Upload de l'image...", - "imageUploadSuccess": "Upload de l'image avec succès ! Obtenez-la ici :", "imageViewer": "Viewer d'Images", "inGuis": "Appliquer à l'Interface Graphique", "inverse": "Inverse", @@ -315,7 +293,6 @@ "packdisplayhud": "Affichage du Pack HUD", "particles": "Particules", "particles.color.tooltip": "La couleur de la particule.
Nécessite \"Couleur personnalisée\" activé.", - "pasteURL": "Copier URL ou ID...", "perspective": "Perspective", "pingColor0": "Couleur du Ping (< 0 ms)", "pingColor1": "Couleur du Ping (< 150 ms)", @@ -422,11 +399,7 @@ "topbackgroundcolor": "Couleur d'Arrière-Plan (Haut)", "toppadding": "Padding en Haut", "tpshud": "TPS HUD", - "uploadAction": "[Upload]", - "upload_image": "Upload cette image.", "useShadows": "Forcer les Ombres", - "viewAccounts": "Afficher les Comptes", - "viewScreenshot": "Voir la Capture d'Écran partagée", "width": "Largeur", "zoom": "Zoom", "zoomDivisor": "Diviseur du Champ de Vision", @@ -437,20 +410,11 @@ "zoomSpeed": "Vitesse d'Animation", "detailedLogging.tooltip": "Au cas où vous auriez besoin de journaux encore plus détaillés.", "action.copy": "Copier...", - "api.privacyNotice": "Avis de confidentialité", - "api.privacyNotice.accept": "Accepter", - "api.privacyNotice.deny": "Refuser/Décider plus tard", - "api.privacyNotice.openPolicy": "Voir les conditions", - "api.privacyNotice.description": "Les fonctionnalités en ligne de DolphinClient - les statistiques Hypixel et le partage d'images - sont soumises à nos conditions générales et à notre politique de confidentialité. \n\nVous êtes libre de les accepter ou de les refuser, mais dans ce dernier cas, ces fonctionnalités ne seront pas disponibles.\n Si vous refusez, la question vous sera posée à nouveau au prochain démarrage du jeu.", "configStyle": "Configurer le style d'interface utilisateur", "configStyle.rounded": "Arrondi", "configStyle.vanilla": "Vanille", "configStyle.rounded-quicktoggles": "Arrondi (QuickToggles)", "configStyle.vanilla-quicktoggles": "Vanille (QuickToggles)", - "auth.copy_and_open": "Copier et ouvrir", - "auth.time_left": "Temps restant : %s", - "auth.working": "En cours...", - "auth.finished": "Terminé !", "PROXY_FRONT": "Front", "PROXY_OFF": "Désactivé", "PROXY_MEMBER": "Membre", @@ -473,16 +437,10 @@ "gallery.image.copy": "Copier", "gallery.image.copy.failure": "Échec de la copie de l'image", "gallery.image.copy.failure.description": "%s\nLe journal peut contenir des informations supplémentaires.", - "gallery.image.upload": "Mettre en ligne", - "gallery.image.upload.failure": "Échec de la mise en ligne de l'image", - "gallery.image.upload.failure.description": "Vous pouvez trouver des informations supplémentaires dans le journal de bord.", - "gallery.image.upload.success": "Image Mise en Ligne", - "gallery.image.upload.success.description": "Un lien vers l'image a été copié dans votre presse-papiers.", "gallery.image.open.external": "Ouvrir dans la visionneuse d'images", "gallery.image.open.external.browser": "Ouvrir dans le navigateur", "gallery.image.copy_url": "Copier l'url de l'image", "gallery.title.local": "Captures d'écran locales", - "gallery.download_external": "Afficher l'image à partir de l'url", "gallery.image.view": "Afficher l'image", "gallery.image.loading.title": "Chargement de l'image...", "viewInGalleryAction": "[Galerie]", @@ -537,7 +495,7 @@ "debugcounters.pcount": "Infos de débogage des particules", "bedwars_resources.renderWhenRelevant": "Afficher uniquement lorsque pertinent", "bedwars_resources": "HUD des ressources", - "levelhead.enabled.tooltip": "Indique si ce module est activé ou non.
Ce module nécessite les fonctionnalités en ligne de DolphinClient
pour communiquer avec le backend et l'API Hypixel.", + "levelhead.enabled.tooltip": "Level Head is currently unavailable.
It required DolphinClient's online features to fetch Hypixel levels.", "bedwars.enabled.tooltip": "Indique si ce module est activé ou non.
Certaines fonctionnalités de ce module nécessitent les fonctionnalités en ligne de DolphinClient
pour communiquer avec le backend et l'API Hypixel.", "daycounterhud": "HUD du compteur de jours", "daycounterhud.days": "%s Jours", @@ -649,33 +607,6 @@ "inventoryhud.item_background": "Arrière-plan des objets", "inventoryhud.item_background_color": "Couleur de l'arrière-plan des objets", "inventoryhud.always_show_item_backgrounds": "Toujours afficher l'arrière-plan des objets", - "skins.manage": "Gérer les skins", - "skins.nav.skins": "Skins", - "skins.nav.capes": "Capes", - "skins.error.failed_to_load": "Échec du chargement des skins", - "skins.error.failed_to_load_desc": "Votre fichier journal peut contenir plus d'informations.", - "skins.capes.no_cape": "Aucune cape", - "skins.manage.equipped": "Équipé", - "skins.manage.equip": "Équiper", - "skins.loading": "Chargement des skins...", - "skins.manage.equipping": "Équipement...", - "skins.manage.delete": "Supprimer le skin", - "skins.manage.delete.confirm": "Confirmer la suppression", - "skins.manage.delete.confirm.desc": "Le fichier de ce skin sera définitivement supprimé !\n Êtes-vous sûr ?", - "skins.manage.delete.confirm.desc_active": "Le fichier de ce skin sera définitivement supprimé !\n Êtes-vous sûr ? Le skin ne sera pas déséquipé.", - "skins.manage.animations": "Animations du gestionnaire de skins", - "skins.manage.download": "Télécharger le skin", - "skins.manage.import.local": "Importer des skins", - "skins.manage.import.online": "Télécharger un skin", - "skins.notification.title": "Gestion des skins", - "skins.notification.not_copied": "Fichier %s ignoré car il ne semble pas être un fichier de skin valide !", - "skins.manage.variant.classic": "Utiliser la variante classique (large)", - "skins.manage.variant.slim": "Utiliser la variante fine", - "skins.manage.import.online.input": "Entrez un nom ou un UUID :", - "skins.notification.import.online.failed_to_download": "Échec du téléchargement du skin de %s !", - "skins.notification.import.online.downloaded": "Skin de %s téléchargé !", - "skins.notification.import.online.failed_to_save": "Échec de l'enregistrement du skin de %s !", - "skins.notification.import.online.not_found": "Impossible de trouver l'utilisateur %s !", "profiles.profile.import": "Importer des profils", "profiles.profile.export": "Exporter", "profiles.profile.export.notification.failed": "Échec de l'exportation", @@ -689,8 +620,6 @@ "profiles.profile.import.notification.success": "Importation terminée", "profiles.profile.import.notification.success.desc.one": "1 profil importé", "profiles.profile.import.notification.success.desc.more": "%s profils importés", - "skins.manage.equip.confirm": "Confirmer l'équipement", - "skins.manage.equip.download_current": "Votre skin actuel n'est pas téléchargé et sera écrasé en équipant un autre skin. Voulez-vous télécharger votre skin actuel avant d'équiper celui-ci ?", "gallery.error.loading": "Échec du chargement de la galerie", "gallery.reload": "Recharger", "iconhud.mode": "Mode d'affichage des icônes", diff --git a/common/src/main/resources/assets/dolphinclient/lang/pt_pt.json b/common/src/main/resources/assets/dolphinclient/lang/pt_pt.json index 37bae6ab6..c3911d438 100644 --- a/common/src/main/resources/assets/dolphinclient/lang/pt_pt.json +++ b/common/src/main/resources/assets/dolphinclient/lang/pt_pt.json @@ -28,7 +28,7 @@ "LEFT": "Left", "LEFT_RIGHT": "Esquerda → Direita", "LMB": "LMB", - "LOSSES": "Perdas", + "LOSSES": "Perdas", "MIDDLE_LEFT": "Centro Esquerda §b←§d", "MIDDLE_LEFT.tooltip": "Middle left point", "MIDDLE_MIDDLE": "Centro Total §b•§d", @@ -72,25 +72,6 @@ "arrowhud": "Arrow HUD", "attackindicatorbg": "Attack Indicator (Background)", "attackindicatorfg": "Attack Indicator (Foreground)", - "auth": "Autenticação", - "auth.accountExpiredNotice": "Your Account %s has expired. Would you like to log in again?", - "auth.add": "Adicionar Conta", - "auth.add.choose": "Escolha o Tipo de Conta para adicionar", - "auth.add.ms": "Adicionar Conta da Microsoft", - "auth.add.offline": "Adicionar Conta Oflline", - "auth.add.offline.name": "Introduza o nome para a nova conta offline...", - "auth.login": "Login", - "auth.notif.login.failed": "Failed to log in!", - "auth.notif.login.failed.no_profile": "Could not retrieve Minecraft profile attached to account!", - "auth.notif.login.failed.no_entitlement": "Failed to check for game ownership, does the selected account own the game?", - "auth.notif.refresh.error": "Failed to refresh account tokens!", - "auth.notif.refresh.error.unexpected_response": "Unexpected response. The log may contain additional information.", - "auth.notif.login.successful": "Successfully logged in as %s!", - "auth.notif.refreshing": "Refreshing tokens for %s!", - "auth.notif.title": "Authentication", - "auth.refresh": "Refresh Account", - "auth.showButton": "Modify Title screen", - "auth.showButton.tooltip": "Whether to show the current user on the Title Screen.
Also doubles as a shortcut to view saved accounts.", "autoboop": "AutoBoop", "autoboop.enabled.tooltip": "Whether to run /boop when a friend of yours joins hypixel.", "autoExec": "Automatic Execution", @@ -145,7 +126,7 @@ "chromaSpeed": "Chroma Speed", "clear": "Limpar", "clickToCopy": "Clique para Copiar", - "clickToOpen": "Clique para Abrir", + "clickToOpen": "Clique para Abrir", "close": "Fechar", "cloudHeight": "Cloud Height", "cloudHeight.tooltip": "The custom cloud height to use
Needs custom cloud height enabled.", @@ -236,9 +217,6 @@ "iconhud": "Icon HUD", "iconsonly": "Icons only", "imageSaved": "Salvo!", - "imageUploadFailure": "Failed to upload image! See the log for more details.", - "imageUploadStarted": "Uploading Image...", - "imageUploadSuccess": "Successfully uploaded image! Get it here:", "imageViewer": "Visualizador de Imagens", "inGuis": "Apply to GUIs", "inverse": "Inverse", @@ -303,7 +281,6 @@ "packdisplayhud": "Pack Display HUD", "particles": "Particles", "particles.color.tooltip": "The color of the particle.
Needs \"Custom Color\" enabled.", - "pasteURL": "Paste URL or ID...", "perspective": "Perspective", "pingColor0": "Ping Color (< 0 ms)", "pingColor1": "Ping Color (< 150 ms)", @@ -410,11 +387,7 @@ "topbackgroundcolor": "Backgroundcolor (Top)", "toppadding": "Top Padding", "tpshud": "TPS HUD", - "uploadAction": "[Upload]", - "upload_image": "Upload this image.", "useShadows": "Force Shadows", - "viewAccounts": "Ver Contas", - "viewScreenshot": "View shared Screenshot", "width": "Width", "zoom": "Zoom", "zoomDivisor": "FOV Divisor", @@ -425,20 +398,11 @@ "zoomSpeed": "Animation Speed", "detailedLogging.tooltip": "In case you need even more verbose logs.", "action.copy": "Copiar...", - "api.privacyNotice": "Privacy Notice", - "api.privacyNotice.accept": "Aceitar", - "api.privacyNotice.deny": "Rejeitar/Decidir depois", - "api.privacyNotice.openPolicy": "Ver Termos", - "api.privacyNotice.description": "As funcionalidades online do DolphinClient - estatísticas Hypixel e partilha de imagens - estão sujeitas aos nossos termos de utilizador e a nossa política de privacidade. \n\nVocê pode aceitar ou rejeitar esses termos, mas no último caso, essas funcionalidades não vão estar disponíveis.\n Se rejeitar, a pergunta será feita novamente no próximo arranque do jogo.", "configStyle": "Config UI Style", "configStyle.rounded": "Rounded", "configStyle.vanilla": "Vanilla", "configStyle.rounded-quicktoggles": "Rounded (QuickToggles)", "configStyle.vanilla-quicktoggles": "Vanilla (QuickToggles)", - "auth.copy_and_open": "Copiar e Abrir", - "auth.time_left": "Tempo Restante: %s", - "auth.working": "Working...", - "auth.finished": "Done!", "PROXY_FRONT": "Front", "PROXY_OFF": "OFF", "PROXY_MEMBER": "Member", @@ -461,16 +425,10 @@ "gallery.image.copy": "Copiar", "gallery.image.copy.failure": "Failed to copy image", "gallery.image.copy.failure.description": "%s\nThe log may contain additional information.", - "gallery.image.upload": "Upload", - "gallery.image.upload.failure": "Failed to upload image", - "gallery.image.upload.failure.description": "You may find additional information in the game log.", - "gallery.image.upload.success": "Image Uploaded", - "gallery.image.upload.success.description": "A link to the uploaded image has been copied to your clipboard.", "gallery.image.open.external": "Open in image viewer", "gallery.image.open.external.browser": "Abrir num navegador", "gallery.image.copy_url": "Copy image url", "gallery.title.local": "Local screenshots", - "gallery.download_external": "Ver imagem de um url", "gallery.image.view": "Ver imagem", "gallery.image.loading.title": "Loading image...", "viewInGalleryAction": "[Gallery]", @@ -526,7 +484,7 @@ "debugcounters.pcount": "Particle Debug Info", "bedwars_resources.renderWhenRelevant": "Only Render When Relevant", "bedwars_resources": "Resources HUD", - "levelhead.enabled.tooltip": "Whether this module is enabled or not.
This module requires DolphinClient's online features
in order to communicate with the backend and the Hypixel API.", + "levelhead.enabled.tooltip": "Level Head is currently unavailable.
It required DolphinClient's online features to fetch Hypixel levels.", "bedwars.enabled.tooltip": "Whether this module is enabled or not.
Some functions of this module require DolphinClient's online features
in order to communicate with the backend and the Hypixel API.", "daycounterhud": "Day Counter HUD", "daycounterhud.days": "%s Days", diff --git a/common/src/test/java/io/github/axolotlclient/util/UUIDHelperTest.java b/common/src/test/java/io/github/axolotlclient/util/UUIDHelperTest.java new file mode 100644 index 000000000..d2df8fb8d --- /dev/null +++ b/common/src/test/java/io/github/axolotlclient/util/UUIDHelperTest.java @@ -0,0 +1,54 @@ +/* + * Copyright © 2026 DSNS + * + * This file is part of DolphinClient. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * For more information, see the LICENSE file. + */ + +package io.github.axolotlclient.util; + +import java.util.UUID; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class UUIDHelperTest { + + private static final String DASHED = "069a79f4-44e9-4726-a5be-fca90e38aaf5"; + private static final String UNDASHED = "069a79f444e94726a5befca90e38aaf5"; + + @Test + void sanitizesDashedAndUndashedUuids() { + assertEquals(UNDASHED, UUIDHelper.sanitizeUUID(DASHED)); + assertEquals(UNDASHED, UUIDHelper.sanitizeUUID(UNDASHED)); + } + + @Test + void roundTripsUndashedUuids() { + UUID uuid = UUID.fromString(DASHED); + assertEquals(UNDASHED, UUIDHelper.toUndashed(uuid)); + assertEquals(uuid, UUIDHelper.fromUndashed(UNDASHED)); + } + + @Test + void rejectsInvalidUuids() { + assertThrows(IllegalArgumentException.class, () -> UUIDHelper.sanitizeUUID("not-a-uuid")); + } +} diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/DolphinClient.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/DolphinClient.java index 8c74452ed..51b00463a 100644 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/DolphinClient.java +++ b/versions/1.8.9/src/main/java/io/github/axolotlclient/DolphinClient.java @@ -23,13 +23,9 @@ package io.github.axolotlclient; -import io.github.axolotlclient.api.API; -import io.github.axolotlclient.api.APIOptions; -import io.github.axolotlclient.api.Options; import io.github.axolotlclient.bridge.impl.Bridge; import io.github.axolotlclient.config.AxolotlClientConfig; import io.github.axolotlclient.modules.ModuleLoader; -import io.github.axolotlclient.modules.auth.Auth; import io.github.axolotlclient.modules.blur.MenuBlur; import io.github.axolotlclient.modules.blur.MotionBlur; import io.github.axolotlclient.modules.hud.HudManager; @@ -59,7 +55,6 @@ private void addBuiltinModules() { registerModule(Particles.getInstance()); registerModule(ScreenshotUtils.getInstance()); registerModule(UnfocusedFpsLimiter.getInstance()); - registerModule(Auth.getInstance()); registerModule(new OldAnimations()); } @@ -74,7 +69,6 @@ public void onInitializeClient() { addExternalModules(); init(Notifications.getInstance()); - new API(); getLogger().debug("Debug Output enabled, Logs will be quite verbose!"); getLogger().info("DolphinClient Initialized"); @@ -98,11 +92,6 @@ protected DolphinClientConfigCommon createConfig() { return new AxolotlClientConfig(); } - @Override - public Options getApiOptions() { - return APIOptions.getInstance(); - } - public static AxolotlClientConfig config() { return (AxolotlClientConfig) getInstance().getConfig(); } diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/APIOptions.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/api/APIOptions.java deleted file mode 100644 index 7b85ac811..000000000 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/APIOptions.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * Copyright © 2026 DSNS - * - * This file is part of DolphinClient, a fork of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api; - -import java.util.concurrent.CompletableFuture; - -import lombok.Getter; -import net.minecraft.client.Minecraft; - -public class APIOptions extends Options { - - @Getter - private static final Options Instance = new APIOptions(); - - @Override - public void init() { - super.init(); - Minecraft client = Minecraft.getInstance(); - - openPrivacyNoteScreen = () -> { - var fut = new CompletableFuture(); - var parent = client.screen; - client.executeTask(() -> client.openScreen(new PrivacyNoticeScreen(parent, fut))); - return fut; - }; - } -} diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/PrivacyNoticeScreen.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/api/PrivacyNoticeScreen.java deleted file mode 100644 index a72698457..000000000 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/PrivacyNoticeScreen.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api; - -import java.net.URI; -import java.util.List; -import java.util.concurrent.CompletableFuture; - -import io.github.axolotlclient.util.OSUtil; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.gui.widget.ButtonWidget; -import net.minecraft.client.resource.language.I18n; -import net.minecraft.util.math.MathHelper; - -public class PrivacyNoticeScreen extends Screen { - - private static final URI TERMS_URI = URI.create(Constants.TERMS); - - private final Screen parent; - private final CompletableFuture accepted; - private List message; - - protected PrivacyNoticeScreen(Screen parent, CompletableFuture accepted) { - super(); - this.parent = parent; - this.accepted = accepted; - } - - @Override - public void render(int mouseX, int mouseY, float delta) { - renderBackground(); - drawCenteredString(this.textRenderer, I18n.translate("api.privacyNotice"), this.width / 2, getTitleY(), -1); - int k = 90; - - for (String string : this.message) { - this.drawCenteredString(this.textRenderer, string, this.width / 2, k, 16777215); - k += this.textRenderer.fontHeight; - } - super.render(mouseX, mouseY, delta); - } - - @Override - public void init() { - - message = minecraft.textRenderer.split( - I18n.translate("api.privacyNotice.description"), width - 50); - int y = MathHelper.clamp(this.getMessageY() + this.getMessagesHeight() + 20, this.height / 6 + 96, this.height - 24); - this.addButtons(y); - } - - private void addButtons(int y) { - buttons.add(new ButtonWidget(1, width / 2 - 50 - 104, y, 100, 20, - I18n.translate("api.privacyNotice.accept"))); - buttons.add(new ButtonWidget(0, width / 2 - 50 + 104, y, 100, 20, - I18n.translate("api.privacyNotice.deny"))); - buttons.add(new ButtonWidget(2, width / 2 - 50, y, 100, 20, - I18n.translate("api.privacyNotice.openPolicy"))); - } - - @Override - protected void buttonClicked(ButtonWidget buttonWidget) { - if (buttonWidget.id == 0) { - minecraft.openScreen(parent); - APIOptions.getInstance().privacyAccepted.set(Options.PrivacyPolicyState.DENIED); - accepted.complete(false); - } else if (buttonWidget.id == 1) { - minecraft.openScreen(parent); - APIOptions.getInstance().privacyAccepted.set(Options.PrivacyPolicyState.ACCEPTED); - accepted.complete(true); - } else if (buttonWidget.id == 2) { - OSUtil.getOS().open(TERMS_URI); - } - } - - private int getTitleY() { - int i = (this.height - this.getMessagesHeight()) / 2; - return MathHelper.clamp(i - 20 - 9, 10, 80); - } - - private int getMessageY() { - return this.getTitleY() + 20; - } - - private int getMessagesHeight() { - return this.message.size() * 9; - } -} diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/SimpleTextInputScreen.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/api/SimpleTextInputScreen.java deleted file mode 100644 index 0b95e1fb6..000000000 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/api/SimpleTextInputScreen.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.api; - -import java.util.function.Consumer; - -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.gui.widget.ButtonWidget; -import net.minecraft.client.gui.widget.TextFieldWidget; -import net.minecraft.client.resource.language.I18n; - -public class SimpleTextInputScreen extends Screen { - - private final Screen parent; - private final String inputLabel; - private final String title; - private final Consumer consumer; - private TextFieldWidget input; - private ButtonWidget done; - - public SimpleTextInputScreen(Screen parent, String title, String inputLabel, Consumer consumer) { - super(); - this.parent = parent; - this.inputLabel = inputLabel; - this.title = title; - this.consumer = consumer; - } - - @Override - public void init() { - input = new TextFieldWidget(3, textRenderer, width / 2 - 100, height / 2 - 10, 200, 20); - - buttons.add(new ButtonWidget(0, width / 2 - 155, height - 50, 150, 20, I18n.translate("gui.cancel"))); - buttons.add(done = new ButtonWidget(1, width / 2 + 5, height - 50, 150, 20, I18n.translate("gui.done"))); - done.active = false; - } - - @Override - public void tick() { - input.tick(); - done.active = !input.getText().isBlank(); - } - - @Override - public void render(int i, int j, float f) { - renderBackground(); - super.render(i, j, f); - textRenderer.drawWithShadow(inputLabel, width / 2F - 100, height / 2f - 20, -1); - drawCenteredString(this.textRenderer, this.title, this.width / 2, 20, 16777215); - input.render(); - } - - @Override - public void mouseClicked(int mouseX, int mouseY, int button) { - super.mouseClicked(mouseX, mouseY, button); - input.mouseClicked(mouseX, mouseY, button); - } - - @Override - protected void keyPressed(char c, int i) { - input.keyPressed(c, i); - super.keyPressed(c, i); - } - - @Override - protected void buttonClicked(ButtonWidget buttonWidget) { - switch (buttonWidget.id) { - case 0: - minecraft.openScreen(parent); - break; - case 1: - if (!input.getText().isBlank()) { - consumer.accept(input.getText()); - } - minecraft.openScreen(parent); - break; - } - } -} diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/mixin/MinecraftClientMixin.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/mixin/MinecraftClientMixin.java index b4e2a4c6d..fb3943e67 100644 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/mixin/MinecraftClientMixin.java +++ b/versions/1.8.9/src/main/java/io/github/axolotlclient/mixin/MinecraftClientMixin.java @@ -27,9 +27,7 @@ import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import com.llamalad7.mixinextras.sugar.Local; import io.github.axolotlclient.DolphinClient; -import io.github.axolotlclient.api.API; import io.github.axolotlclient.bridge.events.types.WorldLoadEvent; -import io.github.axolotlclient.modules.auth.Auth; import io.github.axolotlclient.modules.blur.MenuBlur; import io.github.axolotlclient.modules.hud.HudManager; import io.github.axolotlclient.modules.rpc.DiscordRPC; @@ -136,9 +134,6 @@ protected MinecraftClientMixin(TextureManager textureManager) { @Inject(method = "init", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/texture/TextureManager;close(Lnet/minecraft/resource/Identifier;)V")) private void axolotlclient$onLaunch(CallbackInfo ci) { HudManager.getInstance().refreshAllBounds(); - if (!API.getInstance().isAuthenticated() && !Auth.getInstance().getCurrent().isOffline()) { - API.getInstance().startup(Auth.getInstance().getCurrent()); - } } @Redirect(method = "", at = @At(value = "FIELD", target = "Lnet/minecraft/client/main/RunArgs$Game;version:Ljava/lang/String;", opcode = Opcodes.GETFIELD)) diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/mixin/TitleScreenMixin.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/mixin/TitleScreenMixin.java index 2905a5943..a2c40a065 100644 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/mixin/TitleScreenMixin.java +++ b/versions/1.8.9/src/main/java/io/github/axolotlclient/mixin/TitleScreenMixin.java @@ -25,20 +25,12 @@ import java.io.IOException; import java.io.InputStream; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; -import java.util.ArrayList; import java.util.List; import com.llamalad7.mixinextras.sugar.Local; import io.github.axolotlclient.DolphinClientCommon; import io.github.axolotlclient.DolphinClientConfigCommon; -import io.github.axolotlclient.modules.auth.AccountsScreen; -import io.github.axolotlclient.modules.auth.Auth; -import io.github.axolotlclient.modules.auth.AuthWidget; import io.github.axolotlclient.modules.hud.HudEditScreen; -import net.fabricmc.loader.api.FabricLoader; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.screen.TitleScreen; @@ -49,7 +41,11 @@ import org.apache.commons.io.IOUtils; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.injection.*; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Constant; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.ModifyConstant; +import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(TitleScreen.class) @@ -61,36 +57,6 @@ public abstract class TitleScreenMixin extends Screen { @Shadow private boolean realmsEnabled; - @Inject(method = "initWidgetsNormal", at = @At("TAIL")) - private void axolotlclient$replaceRealmsButton(int i, int j, CallbackInfo ci) { - List buttons = new ArrayList<>(); - if (Auth.getInstance().showButton.get()) { - buttons.add(new AuthWidget(10, 10)); - } - this.buttons.addAll(buttons); - - if (FabricLoader.getInstance().isModLoaded("modmenu")) { - try { - Class booleanConfigOpt = MethodHandles.lookup().findClass("com.terraformersmc.modmenu.config.option.BooleanConfigOption"); - Class enumConfigOpt = MethodHandles.lookup().findClass("com.terraformersmc.modmenu.config.option.EnumConfigOption"); - Class titleMenuButtonStyle = MethodHandles.lookup().findClass("com.terraformersmc.modmenu.config.ModMenuConfig$TitleMenuButtonStyle"); - Class modmenuConfig = MethodHandles.lookup().findClass("com.terraformersmc.modmenu.config.ModMenuConfig"); - MethodHandle modifyTitleScreenHandle = MethodHandles.lookup().findStaticGetter(modmenuConfig, "MODIFY_TITLE_SCREEN", booleanConfigOpt); - MethodHandle getValueB = MethodHandles.lookup().findVirtual(booleanConfigOpt, "getValue", MethodType.methodType(boolean.class)); - MethodHandle getValueE = MethodHandles.lookup().findVirtual(enumConfigOpt, "getValue", MethodType.methodType(Enum.class)); - var modifyTitleScreen = modifyTitleScreenHandle.invoke(); - boolean isModifyTitleScreen = (boolean) getValueB.invoke(modifyTitleScreen); - MethodHandle modsButtonStyleHandle = MethodHandles.lookup().findStaticGetter(modmenuConfig, "MODS_BUTTON_STYLE", enumConfigOpt); - var modsButtonStyle = getValueE.invoke(modsButtonStyleHandle.invoke()); - var classic = titleMenuButtonStyle.getEnumConstants()[0]; - if (isModifyTitleScreen && modsButtonStyle == classic) { - buttons.forEach(r -> r.y -= 24 / 2); - } - } catch (Throwable ignored) { - } - } - } - @Inject(method = "initWidgetsNormal", at = @At("TAIL")) private void axolotlclient$addOptionsButton(int y, int spacingY, CallbackInfo ci) { if (DolphinClientConfigCommon.instance().titleScreenOptionButtonMode.get().showButton()) { @@ -108,10 +74,9 @@ public abstract class TitleScreenMixin extends Screen { @Inject(method = "buttonClicked", at = @At("TAIL")) public void axolotlclient$onClick(ButtonWidget button, CallbackInfo ci) { - if (button.id == 192) + if (button.id == 192) { Minecraft.getInstance().openScreen(new HudEditScreen(this)); - else if (button.id == 242) - Minecraft.getInstance().openScreen(new AccountsScreen(Minecraft.getInstance().screen)); + } } @Redirect(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/TitleScreen;drawString(Lnet/minecraft/client/render/TextRenderer;Ljava/lang/String;III)V", ordinal = 0)) diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/auth/AccountsListWidget.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/auth/AccountsListWidget.java deleted file mode 100644 index cbf36543c..000000000 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/auth/AccountsListWidget.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * Copyright © 2026 DSNS - * - * This file is part of DolphinClient, a fork of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.modules.auth; - -import java.util.ArrayList; -import java.util.List; - -import io.github.axolotlclient.DolphinClientCommon; -import io.github.axolotlclient.util.DrawUtil; -import lombok.Getter; -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.widget.EntryListWidget; -import net.minecraft.client.render.platform.GlStateManager; -import net.minecraft.resource.Identifier; - -public class AccountsListWidget extends EntryListWidget { - - private final AccountsScreen screen; - private final List entries = new ArrayList<>(); - private int selectedEntry = -1; - - public AccountsListWidget(AccountsScreen screen, Minecraft client, int width, int height, int top, int bottom, int entryHeight) { - super(client, width, height, top, bottom, entryHeight); - this.screen = screen; - } - - public void setAccounts(List accounts) { - accounts.forEach(account -> entries.add(new Entry(screen, account))); - } - - @Override - protected int size() { - return entries.size(); - } - - @Override - public int getRowWidth() { - return super.getRowWidth() + 85; - } - - @Override - protected int getScrollbarPosition() { - return super.getScrollbarPosition() + 30; - } - - @Override - protected boolean isEntrySelected(int i) { - return i == this.selectedEntry; - } - - @Override - public EntryListWidget.Entry getEntry(int i) { - return entries.get(i); - } - - public Entry getSelectedEntry() { - if (getSelected() < 0) { - return null; - } - return entries.get(getSelected()); - } - - public int getSelected() { - return this.selectedEntry; - } - - public void setSelected(int i) { - this.selectedEntry = i; - } - - @Environment(EnvType.CLIENT) - public static class Entry extends DrawUtil implements EntryListWidget.Entry { - - private static final Identifier checkmark = new Identifier(DolphinClientCommon.MODID, "textures/check.png"); - private static final Identifier warningSign = new Identifier(DolphinClientCommon.MODID, "textures/warning.png"); - - private final AccountsScreen screen; - @Getter - private final Account account; - private final Minecraft client; - private long time; - - public Entry(AccountsScreen screen, Account account) { - this.screen = screen; - this.account = account; - this.client = Minecraft.getInstance(); - } - - @Override - public void renderOutOfBounds(int i, int j, int k) { - - } - - @Override - public void render(int index, int x, int y, int rowWidth, int rowHeight, int mouseX, int mouseY, boolean hovered) { - client.textRenderer.draw(account.getName(), x + 3 + 33, y + 1, -1); - client.textRenderer.draw(account.getUuid(), x + 3 + 33, y + 12, 8421504); - GlStateManager.color4f(1, 1, 1, 1); - if (Auth.getInstance().getCurrent().equals(account)) { - client.getTextureManager().bind(checkmark); - drawTexture(x - 35, y + 1, 0, 0, 32, 32, 32, 32); - } else if (account.isExpired()) { - client.getTextureManager().bind(warningSign); - drawTexture(x - 35, y + 1, 0, 0, 32, 32, 32, 32); - } - GlStateManager.color4f(1, 1, 1, 1); - client.getTextureManager().bind(Auth.getInstance().getSkinTexture(account)); - GlStateManager.enableBlend(); - drawTexture(x - 1, y - 1, 8, 8, 8, 8, 33, 33, 64, 64); - drawTexture(x - 1, y - 1, 40, 8, 8, 8, 33, 33, 64, 64); - GlStateManager.disableBlend(); - - } - - @Override - public boolean mouseClicked(int index, int mouseX, int mouseY, int button, int x, int y) { - this.screen.select(index); - if (Minecraft.getTime() - this.time < 250L && client.world == null) { - if (!getAccount().equals(Auth.getInstance().getCurrent())) { - screen.select(-1); - Auth.getInstance().login(account); - } - } - - this.time = Minecraft.getTime(); - return false; - } - - @Override - public void mouseReleased(int i, int j, int k, int l, int m, int n) { - - } - - } -} diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/auth/AccountsScreen.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/auth/AccountsScreen.java deleted file mode 100644 index 1cb48f6c9..000000000 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/auth/AccountsScreen.java +++ /dev/null @@ -1,227 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.modules.auth; - -import io.github.axolotlclient.modules.auth.skin.SkinManagementScreen; -import net.minecraft.client.gui.screen.ConfirmScreen; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.gui.widget.ButtonWidget; -import net.minecraft.client.gui.widget.EntryListWidget; -import net.minecraft.client.resource.language.I18n; - -public class AccountsScreen extends Screen { - private final Screen parent; - private final String title; - protected AccountsListWidget accountsListWidget; - private ButtonWidget loginButton; - private ButtonWidget deleteButton; - private ButtonWidget refreshButton; - private ButtonWidget skinsButton; - - public AccountsScreen(Screen currentScreen) { - title = I18n.translate("accounts"); - this.parent = currentScreen; - } - - @Override - public void render(int mouseX, int mouseY, float delta) { - this.renderBackground(); - this.accountsListWidget.render(mouseX, mouseY, delta); - drawCenteredString(this.textRenderer, this.title, this.width / 2, 20, 16777215); - super.render(mouseX, mouseY, delta); - } - - @Override - protected void keyPressed(char c, int i) { - int j = this.accountsListWidget.getSelected(); - EntryListWidget.Entry entry = j < 0 ? null : this.accountsListWidget.getEntry(j); - if (i == 63) { - this.refresh(); - } else { - if (j >= 0) { - if (i == 200) { - if (isShiftDown()) { - if (j > 0 && entry instanceof AccountsListWidget.Entry) { - this.select(this.accountsListWidget.getSelected() - 1); - this.accountsListWidget.scroll(-this.accountsListWidget.getEntryHeight()); - } - } else if (j > 0) { - this.select(this.accountsListWidget.getSelected() - 1); - this.accountsListWidget.scroll(-this.accountsListWidget.getEntryHeight()); - } else { - this.select(-1); - } - } else if (i == 208) { - if (isShiftDown()) { - this.select(j + 1); - this.accountsListWidget.scroll(this.accountsListWidget.getEntryHeight()); - } else if (j < this.accountsListWidget.size()) { - this.select(this.accountsListWidget.getSelected() + 1); - this.accountsListWidget.scroll(this.accountsListWidget.getEntryHeight()); - } else { - this.select(-1); - } - } else if (i != 28 && i != 156) { - super.keyPressed(c, i); - } else { - this.buttonClicked(this.buttons.get(2)); - } - } else { - super.keyPressed(c, i); - } - } - } - - @Override - protected void mouseClicked(int i, int j, int k) { - super.mouseClicked(i, j, k); - this.accountsListWidget.mouseClicked(i, j, k); - } - - @Override - protected void mouseReleased(int i, int j, int k) { - super.mouseReleased(i, j, k); - this.accountsListWidget.mouseReleased(i, j, k); - } - - @Override - protected void buttonClicked(ButtonWidget buttonWidget) { - switch (buttonWidget.id) { - case 0: - this.minecraft.openScreen(this.parent); - break; - case 1: - login(); - break; - case 2: - minecraft.openScreen(new SkinManagementScreen( - this, accountsListWidget.getSelectedEntry().getAccount())); - break; - case 3: - if (Auth.getInstance().allowOfflineAccounts()) { - minecraft.openScreen(new ConfirmScreen(this, I18n.translate("auth.add.choose"), "", I18n.translate("auth.add.offline"), I18n.translate("auth.add.ms"), 234)); - } else { - initMSAuth(); - } - break; - case 4: - AccountsListWidget.Entry entry = this.accountsListWidget.getSelectedEntry(); - if (entry != null) { - buttonWidget.active = false; - Auth.getInstance().removeAccount(entry.getAccount()); - refresh(); - } - break; - case 5: - refreshAccount(); - break; - } - } - - @Override - public void init() { - - accountsListWidget = new AccountsListWidget(this, minecraft, width, height, 32, height - 64, 35); - - accountsListWidget.setAccounts(Auth.getInstance().getAccounts()); - - buttons.add(loginButton = new ButtonWidget(1, this.width / 2 - 154, this.height - 52, 100, 20, I18n.translate("auth.login"))); - - buttons.add(skinsButton = new ButtonWidget(2, this.width / 2 - 50, this.height - 52, 100, 20, I18n.translate("skins.manage"))); - - this.buttons.add(new ButtonWidget(3, this.width / 2 + 4 + 50, this.height - 52, 100, 20, I18n.translate("auth.add"))); - - this.buttons.add(this.deleteButton = new ButtonWidget(4, this.width / 2 - 50, this.height - 28, 100, 20, I18n.translate("selectServer.delete"))); - - - this.buttons.add(refreshButton = new ButtonWidget(5, this.width / 2 - 154, this.height - 28, 100, 20, - I18n.translate("auth.refresh"))); - - this.buttons.add(new ButtonWidget(0, this.width / 2 + 4 + 50, this.height - 28, 100, 20, - I18n.translate("gui.back"))); - updateButtonActivationStates(); - } - - @Override - public void handleMouse() { - super.handleMouse(); - this.accountsListWidget.handleMouse(); - } - - @Override - public void removed() { - Auth.getInstance().save(); - } - - @Override - public void confirmResult(boolean bl, int i) { - if (i == 234) { - if (!bl) { - minecraft.openScreen(this); - initMSAuth(); - } else { - minecraft.openScreen(new AddOfflineScreen(this)); - } - } - } - - private void refresh() { - this.minecraft.openScreen(new AccountsScreen(this.parent)); - } - - public void select(int index) { - this.accountsListWidget.setSelected(index); - this.updateButtonActivationStates(); - } - - private void updateButtonActivationStates() { - AccountsListWidget.Entry entry = accountsListWidget.getSelectedEntry(); - if (minecraft.world == null && entry != null) { - loginButton.active = entry.getAccount().isExpired() || !entry.getAccount().equals(Auth.getInstance().getCurrent()); - refreshButton.active = skinsButton.active = !entry.getAccount().isOffline(); - deleteButton.active = true; - } else { - loginButton.active = deleteButton.active = refreshButton.active = skinsButton.active = false; - } - } - - private void login() { - loginButton.active = false; - AccountsListWidget.Entry entry = accountsListWidget.getSelectedEntry(); - if (entry != null) { - Auth.getInstance().login(entry.getAccount()); - } - } - - private void initMSAuth() { - Auth.getInstance().getMsApi().startDeviceAuth().thenRun(() -> minecraft.executeTask(this::refresh)); - } - - private void refreshAccount() { - refreshButton.active = false; - AccountsListWidget.Entry entry = accountsListWidget.getSelectedEntry(); - if (entry != null) { - entry.getAccount().refresh(Auth.getInstance().getMsApi()); - } - } -} diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/auth/AddOfflineScreen.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/auth/AddOfflineScreen.java deleted file mode 100644 index 11a7c320d..000000000 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/auth/AddOfflineScreen.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.modules.auth; - -import java.util.UUID; - -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.gui.widget.ButtonWidget; -import net.minecraft.client.gui.widget.TextFieldWidget; -import net.minecraft.client.resource.language.I18n; - -public class AddOfflineScreen extends Screen { - - private final Screen parent; - private TextFieldWidget nameInput; - - public AddOfflineScreen(Screen parent) { - this.parent = parent; - } - - - @Override - public void render(int i, int j, float f) { - renderBackground(); - super.render(i, j, f); - textRenderer.drawWithShadow(I18n.translate("auth.add.offline.name"), width / 2F - 100, height / 2f - 20, -1); - drawCenteredString(this.textRenderer, I18n.translate("auth.add.offline"), this.width / 2, 20, 16777215); - nameInput.render(); - } - - @Override - protected void keyPressed(char c, int i) { - nameInput.keyPressed(c, i); - } - - @Override - protected void mouseClicked(int i, int j, int k) { - super.mouseClicked(i, j, k); - nameInput.mouseClicked(i, j, k); - } - - @Override - protected void buttonClicked(ButtonWidget buttonWidget) { - if (buttonWidget.id == 1) { - minecraft.openScreen(parent); - } else if (buttonWidget.id == 2) { - Auth.getInstance().addAccount(new Account(nameInput.getText(), UUID.randomUUID().toString(), Account.OFFLINE_TOKEN)); - minecraft.openScreen(parent); - } - } - - @Override - public void init() { - nameInput = new TextFieldWidget(0, textRenderer, width / 2 - 100, height / 2 - 10, 200, 20); - - buttons.add(new ButtonWidget(1, width / 2 - 155, height - 50, 150, 20, I18n.translate("gui.cancel"))); - buttons.add(new ButtonWidget(2, width / 2 + 5, height - 50, 150, 20, I18n.translate("gui.done"))); - } - - @Override - public void tick() { - nameInput.tick(); - } -} diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/auth/Auth.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/auth/Auth.java deleted file mode 100644 index 34f79dbb9..000000000 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/auth/Auth.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * Copyright © 2026 DSNS - * - * This file is part of DolphinClient, a fork of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.modules.auth; - -import java.util.*; -import java.util.concurrent.CompletableFuture; - -import com.mojang.authlib.GameProfile; -import com.mojang.authlib.minecraft.MinecraftProfileTexture; -import io.github.axolotlclient.DolphinClient; -import io.github.axolotlclient.DolphinClientCommon; -import io.github.axolotlclient.AxolotlClientConfig.impl.options.BooleanOption; -import io.github.axolotlclient.api.API; -import io.github.axolotlclient.api.util.UUIDHelper; -import io.github.axolotlclient.mixin.MinecraftClientAccessor; -import io.github.axolotlclient.modules.Module; -import io.github.axolotlclient.modules.auth.skin.SkinManager; -import io.github.axolotlclient.util.ThreadExecuter; -import io.github.axolotlclient.util.notifications.Notifications; -import io.github.axolotlclient.util.options.GenericOption; -import lombok.Getter; -import net.minecraft.client.Minecraft; -import net.minecraft.client.Session; -import net.minecraft.client.gui.screen.ConfirmScreen; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.resource.language.I18n; -import net.minecraft.client.resource.skin.DefaultSkinUtils; -import net.minecraft.resource.Identifier; - -public class Auth extends Accounts implements Module { - - @Getter - private static final Auth Instance = new Auth(); - public final BooleanOption showButton = new BooleanOption("auth.showButton", false); - public final BooleanOption skinManagerAnimations = new BooleanOption("skins.manage.animations", true); - private final Minecraft client = Minecraft.getInstance(); - private final GenericOption viewAccounts = new GenericOption("viewAccounts", "clickToOpen", () -> client.openScreen(new AccountsScreen(client.screen))); - private final Map textures = new HashMap<>(); - private final Set loadingTexture = new HashSet<>(); - private final Map profileCache = new WeakHashMap<>(); - @Getter - private final SkinManager skinManager = new SkinManager(); - - @Override - public void init() { - load(); - this.msApi = new MSApi(this, () -> client.options.language); - if (isContained(client.getSession().getUuid())) { - current = getAccounts().stream().filter(account -> account.getUuid().equals(client.getSession().getUuid())).toList().get(0); - current.setAuthToken(client.getSession().getAccessToken()); - current.setName(client.getSession().getUsername()); - /*if (current.needsRefresh()) { - current.refresh(auth).thenRun(this::save); - }*/ - } else { - current = new Account(client.getSession().getUsername(), client.getSession().getUuid(), client.getSession().getAccessToken()); - } - - category.add(showButton, viewAccounts); - DolphinClient.config().general.add(category); - } - - @Override - protected void login(Account account) { - if (client.world != null) { - return; - } - - if (account.needsRefresh() && !account.isOffline()) { - if (account.isExpired()) { - Notifications.getInstance().addStatus("auth.notif.title", "auth.notif.refreshing", account.getName()); - } - account.refresh(msApi).thenAccept(a -> { - if (!a.isExpired()) { - login(a); - } - }).thenRun(this::save); - } else { - try { - API.getInstance().shutdown(); - ((MinecraftClientAccessor) client).setSession(new Session(account.getName(), account.getUuid(), account.getAuthToken(), Session.Type.MOJANG.name())); - client.getProfileProperties().clear(); - client.getProfileProperties(); - save(); - current = account; - Notifications.getInstance().addStatus("auth.notif.title", "auth.notif.login.successful", current.getName()); - DolphinClientCommon.getInstance().getLogger().info("Successfully logged in as " + current.getName()); - API.getInstance().startup(account); - } catch (Exception e) { - DolphinClientCommon.getInstance().getLogger().error("Failed to log in! ", e); - Notifications.getInstance().addStatus("auth.notif.title", "auth.notif.login.failed"); - } - } - } - - public void loadTextures(String uuid, String name) { - if (!textures.containsKey(uuid) && !loadingTexture.contains(uuid)) { - ThreadExecuter.scheduleTask(() -> { - loadingTexture.add(uuid); - GameProfile gameProfile; - if (profileCache.containsKey(uuid)) { - gameProfile = profileCache.get(uuid); - } else { - try { - UUID uUID = UUIDHelper.fromUndashed(uuid); - gameProfile = new GameProfile(uUID, name); - client.getSessionService().fillProfileProperties(gameProfile, false); - } catch (IllegalArgumentException var2) { - gameProfile = new GameProfile(null, name); - } - profileCache.put(uuid, gameProfile); - } - client.getSkinManager().register(gameProfile, (type, identifier, minecraftProfileTexture) -> { - if (type == MinecraftProfileTexture.Type.SKIN) { - textures.put(uuid, identifier); - loadingTexture.remove(uuid); - } - }, false); - }); - } - } - - @Override - CompletableFuture showAccountsExpiredScreen(Account account) { - Screen current = client.screen; - var fut = new CompletableFuture(); - client.executeTask(() -> client.openScreen(new ConfirmScreen((bl, i) -> { - if (bl) { - msApi.startDeviceAuth().thenRun(() -> fut.complete(account)); - } else { - fut.cancel(true); - } - client.openScreen(current); - }, I18n.translate("auth"), I18n.translate("auth.accountExpiredNotice", account.getName()), 1))); - return fut; - } - - @Override - void displayDeviceCode(DeviceFlowData data) { - client.executeTask(() -> client.openScreen(new DeviceCodeDisplayScreen(client.screen, data))); - } - - public Identifier getSkinTexture(Account account) { - return getSkinTexture(account.getUuid(), account.getName()); - } - - public Identifier getSkinTexture(String uuid, String name) { - loadTextures(uuid, name); - Identifier id; - if ((id = textures.get(uuid)) != null) { - return id; - } - try { - UUID uUID = UUIDHelper.fromUndashed(uuid); - return DefaultSkinUtils.getDefaultSkin(uUID); - } catch (IllegalArgumentException ignored) { - return DefaultSkinUtils.getDefaultSkin(); - } - } -} diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/auth/AuthWidget.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/auth/AuthWidget.java deleted file mode 100644 index 7a56c334f..000000000 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/auth/AuthWidget.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.modules.auth; - -import io.github.axolotlclient.api.API; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.widget.ButtonWidget; -import net.minecraft.client.render.platform.GlStateManager; - -public class AuthWidget extends ButtonWidget { - - public AuthWidget(int x, int y) { - super(242, x, y, - Minecraft.getInstance().textRenderer.getWidth(Auth.getInstance().getCurrent().getName()) + 28, - 20, " " + Auth.getInstance().getCurrent().getName()); - } - - @Override - public void render(Minecraft minecraftClient, int i, int j) { - super.render(minecraftClient, i, j); - GlStateManager.color4f(1, 1, 1, 1); - Minecraft.getInstance().getTextureManager().bind(Auth.getInstance().getSkinTexture(Auth.getInstance().getCurrent())); - GlStateManager.enableBlend(); - drawTexture(x + 1, y + 1, 8, 8, 8, 8, height - 2, height - 2, 64, 64); - drawTexture(x + 1, y + 1, 40, 8, 8, 8, height - 2, height - 2, 64, 64); - GlStateManager.disableBlend(); - if (API.getInstance().getApiOptions().privacyAccepted.get().isAccepted()) { - GlStateManager.pushMatrix(); - GlStateManager.translatef(x + height - 1, y + height - 1, 0); - GlStateManager.scalef(0.25f, 0.25f, 1); - GlStateManager.translatef(-8, -8, 0); - int color = API.getInstance().getIndicatorColor(); - fill(0, 4, 16, 12, color); - fill(4, 0, 12, 16, color); - fill(2, 2, 14, 14, color); - GlStateManager.popMatrix(); - } - } -} diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/auth/DeviceCodeDisplayScreen.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/auth/DeviceCodeDisplayScreen.java deleted file mode 100644 index 00188e454..000000000 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/auth/DeviceCodeDisplayScreen.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.modules.auth; - -import java.net.URI; -import java.util.List; - -import io.github.axolotlclient.util.OSUtil; -import io.github.axolotlclient.util.Util; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.gui.widget.ButtonWidget; -import net.minecraft.client.resource.language.I18n; -import net.minecraft.resource.Identifier; - -public class DeviceCodeDisplayScreen extends Screen { - private final Screen parent; - private final String verificationUri, userCode; - private final List message; - private final String title; - private int ticksLeft; - private String status; - private boolean working; - private final Identifier qrCode; - private final DeviceFlowData data; - - public DeviceCodeDisplayScreen(Screen parent, DeviceFlowData data) { - super(); - this.title = I18n.translate("auth.add"); - this.parent = parent; - this.message = Minecraft.getInstance().textRenderer.split(data.getMessage(), 400); - this.verificationUri = data.getVerificationUri(); - this.userCode = data.getUserCode(); - this.ticksLeft = data.getExpiresIn() * 20; - this.qrCode = Util.getTexture(data.getQrCode(), "device_auth_" + data.getUserCode()); - this.status = I18n.translate("auth.time_left", - ((ticksLeft / 20) / 60) + "m" + ((ticksLeft / 20) % 60) + "s"); - data.setStatusConsumer(s -> { - if (s.equals("auth.finished")) { - Minecraft.getInstance().executeTask(() -> Minecraft.getInstance().openScreen(parent)); - } - working = true; - buttons.clear(); - status = I18n.translate(s); - }); - this.data = data; - } - - @Override - public void removed() { - data.cancel(); - } - - @Override - public void init() { - buttons.add(new ButtonWidget(1, width / 2 - 100, height / 2, - 200, 20, I18n.translate("auth.copy_and_open"))); - } - - @Override - public void render(int mouseX, int mouseY, float delta) { - renderBackground(); - super.render(mouseX, mouseY, delta); - - drawCenteredString(minecraft.textRenderer, title, width / 2, 25, -1); - - int y = height / 4; - for (String text : message) { - minecraft.textRenderer.drawWithShadow(text, width / 2f - minecraft.textRenderer.getWidth(text) / 2f, y, -1); - y += 10; - } - drawCenteredString(minecraft.textRenderer, working ? status : I18n.translate("auth.time_left", - ((ticksLeft / 20) / 60) + "m" + ((ticksLeft / 20) % 60) + "s"), - width / 2, y + 10, -1); - - - y = height / 2 + 30; - if (height - y > 40) { - int qrImageSize = height - y - 20; - minecraft.getTextureManager().bind(qrCode); - drawTexture(width / 2 - qrImageSize / 2, y, 0, 0, qrImageSize, qrImageSize, qrImageSize, qrImageSize); - } - } - - @Override - public void tick() { - ticksLeft--; - } - - @Override - protected void buttonClicked(ButtonWidget buttonWidget) { - if (buttonWidget.id == 1) { - setClipboard(userCode); - OSUtil.getOS().open(URI.create(verificationUri)); - } - } -} diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/auth/skin/LoadingScreen.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/auth/skin/LoadingScreen.java deleted file mode 100644 index e087668d6..000000000 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/auth/skin/LoadingScreen.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright © 2025 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.modules.auth.skin; - -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.screen.Screen; - -public class LoadingScreen extends Screen { - private final String description; - private final String title; - - public LoadingScreen(String title, String description) { - super(); - this.title = title; - this.description = description; - } - - @Override - public void render(int mouseX, int mouseY, float delta) { - renderBackground(); - drawCenteredString(textRenderer, title, width / 2, 33 / 2 - textRenderer.fontHeight / 2, -1); - int centerX = width / 2; - int centerY = height / 2; - var text = description; - textRenderer.draw(text, centerX - textRenderer.getWidth(text) / 2, centerY - 9, -1); - String string = switch ((int) (Minecraft.getTime() / 300L % 4L)) { - case 1, 3 -> "o O o"; - case 2 -> "o o O"; - default -> "O o o"; - }; - textRenderer.draw(string, centerX - textRenderer.getWidth(string) / 2, centerY + 9, 0xFF808080); - } -} diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/auth/skin/SkinManagementScreen.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/auth/skin/SkinManagementScreen.java deleted file mode 100644 index b90ea7b4d..000000000 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/auth/skin/SkinManagementScreen.java +++ /dev/null @@ -1,909 +0,0 @@ -/* - * Copyright © 2025 moehreag & Contributors - * Copyright © 2026 DSNS - * - * This file is part of DolphinClient, a fork of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.modules.auth.skin; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.time.Instant; -import java.util.*; -import java.util.concurrent.CancellationException; -import java.util.concurrent.CompletableFuture; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import io.github.axolotlclient.DolphinClientCommon; -import io.github.axolotlclient.AxolotlClientConfig.api.util.Color; -import io.github.axolotlclient.AxolotlClientConfig.api.util.Colors; -import io.github.axolotlclient.AxolotlClientConfig.impl.ui.ClickableWidget; -import io.github.axolotlclient.AxolotlClientConfig.impl.ui.Element; -import io.github.axolotlclient.AxolotlClientConfig.impl.ui.ParentElement; -import io.github.axolotlclient.AxolotlClientConfig.impl.ui.vanilla.ElementListWidget; -import io.github.axolotlclient.AxolotlClientConfig.impl.ui.vanilla.widgets.VanillaButtonWidget; -import io.github.axolotlclient.api.SimpleTextInputScreen; -import io.github.axolotlclient.api.util.UUIDHelper; -import io.github.axolotlclient.bridge.util.AxoText; -import io.github.axolotlclient.modules.auth.Account; -import io.github.axolotlclient.modules.auth.Auth; -import io.github.axolotlclient.modules.auth.MSApi; -import io.github.axolotlclient.util.DrawUtil; -import io.github.axolotlclient.util.ButtonWidgetTextures; -import io.github.axolotlclient.util.ClientColors; -import io.github.axolotlclient.util.Watcher; -import io.github.axolotlclient.util.notifications.Notifications; -import net.fabricmc.loader.api.FabricLoader; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.screen.ConfirmScreen; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.render.TextRenderer; -import net.minecraft.client.render.platform.GlStateManager; -import net.minecraft.client.render.platform.Lighting; -import net.minecraft.client.render.vertex.DefaultVertexFormat; -import net.minecraft.client.render.vertex.Tesselator; -import net.minecraft.client.resource.language.I18n; -import net.minecraft.resource.Identifier; -import net.minecraft.text.Text; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public class SkinManagementScreen extends io.github.axolotlclient.AxolotlClientConfig.impl.ui.Screen { - private static final Path SKINS_DIR = FabricLoader.getInstance().getGameDir().resolve("skins"); - private static final int LIST_SKIN_WIDTH = 75; - private static final int LIST_SKIN_HEIGHT = 110; - private static final String TEXT_EQUIPPING = I18n.translate("skins.manage.equipping"); - private final Screen parent; - private final Account account; - private MSApi.MCProfile cachedProfile; - private SkinListWidget skinList; - private SkinListWidget capesList; - private boolean capesTab; - private SkinWidget current; - private final Watcher skinDirWatcher; - private final CompletableFuture loadingFuture; - private String tooltip = null; - - public SkinManagementScreen(Screen parent, Account account) { - super(I18n.translate("skins.manage")); - this.parent = parent; - this.account = account; - skinDirWatcher = Watcher.createSelfTicking(SKINS_DIR, () -> { - DolphinClientCommon.getInstance().getLogger().info("Reloading screen as local files changed!"); - loadSkinsList(); - }); - loadingFuture = (account.needsRefresh() ? account.refresh(Auth.getInstance().getMsApi()) - : CompletableFuture.completedFuture(null)) - .thenComposeAsync(unused -> Auth.getInstance().getMsApi().getProfile(account)); - } - - @Override - public void render(int mouseX, int mouseY, float delta) { - tooltip = null; - super.render(mouseX, mouseY, delta); - if (tooltip != null) { - renderTooltip(tooltip, mouseX, mouseY + 20); - Lighting.turnOff(); - } - } - - @Override - public void init() { - int headerHeight = 33; - int contentHeight = height - headerHeight * 2; - class TextWidget extends ClickableWidget { - - public TextWidget(int x, int y, int width, int height, String message) { - super(x, y, width, height, message); - active = false; - } - - @Override - public void drawWidget(int mouseX, int mouseY, float delta) { - drawCenteredString(textRenderer, getMessage(), getX() + getWidth() / 2, getY() + getHeight() / 2 - textRenderer.fontHeight / 2, -1); - } - } - - var titleWidget = new TextWidget(0, headerHeight / 2 - textRenderer.fontHeight / 2, width, textRenderer.fontHeight, getTitle()); - addDrawableChild(titleWidget); - - var back = addDrawableChild(new VanillaButtonWidget(width / 2 - 75, height - headerHeight / 2 - 10, 150, 20, I18n.translate("gui.back"), btn -> closeScreen())); - - var loadingPlaceholder = new ClickableWidget(0, headerHeight, width, contentHeight, I18n.translate("skins.loading")) { - @Override - protected void drawWidget(int mouseX, int mouseY, float delta) { - int centerX = this.getX() + this.getWidth() / 2; - int centerY = this.getY() + this.getHeight() / 2; - var text = this.getMessage(); - textRenderer.draw(text, centerX - textRenderer.getWidth(text) / 2f, centerY - 9, -1, false); - String string = switch ((int) (System.currentTimeMillis() / 300L % 4L)) { - case 1, 3 -> "o O o"; - case 2 -> "o o O"; - default -> "O o o"; - }; - textRenderer.draw(string, centerX - textRenderer.getWidth(string) / 2f, centerY + 9, 0xFF808080, false); - } - }; - loadingPlaceholder.active = false; - addDrawableChild(loadingPlaceholder); - addDrawableChild(back); - - skinList = new SkinListWidget(minecraft, width / 2, contentHeight - 24, headerHeight + 24, LIST_SKIN_HEIGHT + 34); - capesList = new SkinListWidget(minecraft, width / 2, contentHeight - 24, headerHeight + 24, skinList.getEntryContentsHeight() + 24); - skinList.setLeftPos(width / 2); - capesList.setLeftPos(width / 2); - var currentHeight = Math.min((width / 2f) * 120 / 85, contentHeight); - var currentWidth = currentHeight * 85 / 120; - current = new SkinWidget((int) currentWidth, (int) currentHeight, null, account); - current.setPosition((int) (width / 4f - currentWidth / 2), (int) (height / 2f - currentHeight / 2)); - - if (!capesTab) { - capesList.visible = capesList.active = false; - } else { - skinList.visible = skinList.active = false; - } - List navBar = new ArrayList<>(); - var skinsTab = new VanillaButtonWidget(Math.max(width * 3 / 4 - 102, width / 2 + 2), headerHeight, Math.min(100, width / 4 - 2), 20, I18n.translate("skins.nav.skins"), btn -> { - navBar.forEach(w -> { - if (w != btn) w.active = true; - }); - btn.active = false; - skinList.visible = skinList.active = true; - capesList.visible = capesList.active = false; - capesTab = false; - }); - navBar.add(skinsTab); - var capesTab = new VanillaButtonWidget(width * 3 / 4 + 2, headerHeight, Math.min(100, width / 4 - 2), 20, I18n.translate("skins.nav.capes"), btn -> { - navBar.forEach(w -> { - if (w != btn) w.active = true; - }); - btn.active = false; - skinList.visible = skinList.active = false; - capesList.visible = capesList.active = true; - this.capesTab = true; - }); - navBar.add(capesTab); - var importButton = new SpriteButton(I18n.translate("skins.manage.import.local"), btn -> { - btn.active = false; - SkinImportUtil.openImportSkinDialog().thenAccept(this::onFileDrop).thenRun(() -> btn.active = true); - }, new Identifier(DolphinClientCommon.MODID, "textures/gui/sprites/folder.png")); - var downloadButton = new SpriteButton(I18n.translate("skins.manage.import.online"), btn -> { - btn.active = false; - promptForSkinDownload(); - }, new Identifier(DolphinClientCommon.MODID, "textures/gui/sprites/download.png")); - if (width - (capesTab.getX() + capesTab.getWidth()) > 28) { - importButton.setX(width - importButton.getWidth() - 2); - downloadButton.setX(importButton.getX() - downloadButton.getWidth() - 2); - importButton.setY(capesTab.getY() + capesTab.getHeight() - 11); - downloadButton.setY(importButton.getY()); - } else { - importButton.setX(capesTab.getX() + capesTab.getWidth() - 11); - importButton.setY(capesTab.getY() - 13); - downloadButton.setX(importButton.getX() - 2 - 11); - downloadButton.setY(importButton.getY()); - } - skinsTab.active = this.capesTab; - capesTab.active = !this.capesTab; - Runnable addWidgets = () -> { - clearChildren(); - addDrawableChild(titleWidget); - addDrawableChild(current); - addDrawableChild(skinList); - addDrawableChild(capesList); - addDrawableChild(skinsTab); - addDrawableChild(capesTab); - addDrawableChild(downloadButton); - addDrawableChild(importButton); - addDrawableChild(back); - }; - if (cachedProfile != null) { - initDisplay(); - addWidgets.run(); - return; - } - loadingFuture.thenAcceptAsync(profile -> { - cachedProfile = profile; - initDisplay(); - addWidgets.run(); - }).exceptionally(t -> { - if (t.getCause() instanceof CancellationException) { - minecraft.openScreen(parent); - return null; - } - DolphinClientCommon.getInstance().getLogger().error("Failed to load skins!", t); - var error = I18n.translate("skins.error.failed_to_load"); - var errorDesc = I18n.translate("skins.error.failed_to_load_desc"); - clearChildren(); - addDrawableChild(titleWidget); - - addDrawableChild(new TextWidget(width / 2 - textRenderer.getWidth(error) / 2, height / 2 - textRenderer.fontHeight - 2, textRenderer.getWidth(error), textRenderer.fontHeight, error)); - addDrawableChild(new TextWidget(width / 2 - textRenderer.getWidth(errorDesc) / 2, height / 2 + 1, textRenderer.getWidth(errorDesc), textRenderer.fontHeight, errorDesc)); - addDrawableChild(back); - return null; - }); - } - - private void promptForSkinDownload() { - minecraft.openScreen(new SimpleTextInputScreen(this, I18n.translate("skins.manage.import.online"), I18n.translate("skins.manage.import.online.input"), s -> - UUIDHelper.ensureUuidOpt(s).thenAccept(o -> { - if (o.isPresent()) { - DolphinClientCommon.getInstance().getLogger().info("Downloading skin of {} ({})", s, o.get()); - Auth.getInstance().getMsApi().getTextures(o.get()) - .exceptionally(th -> { - DolphinClientCommon.getInstance().getLogger().info("Failed to download skin of {} ({})", s, o.get(), th); - return null; - }).thenAccept(t -> { - if (t == null) { - Notifications.getInstance().addStatus("skins.notification.title", "skins.notification.import.online.failed_to_download", s); - return; - } - try { - var bytes = t.skin().join(); - var out = ensureNonexistent(SKINS_DIR.resolve(t.skinKey())); - Skin.LocalSkin.writeMetadata(out, Map.of(Skin.LocalSkin.CLASSIC_METADATA_KEY, t.classicModel(), "name", t.name(), "uuid", t.id(), "download_time", Instant.now())); - Files.write(out, bytes); - minecraft.executeTask(this::loadSkinsList); - Notifications.getInstance().addStatus("skins.notification.title", "skins.notification.import.online.downloaded", t.name()); - DolphinClientCommon.getInstance().getLogger().info("Downloaded skin of {} ({})", t.name(), o.get()); - } catch (IOException e) { - DolphinClientCommon.getInstance().getLogger().warn("Failed to write skin file", e); - Notifications.getInstance().addStatus("skins.notification.title", "skins.notification.import.online.failed_to_save", t.name()); - } - }); - } else { - Notifications.getInstance().addStatus("skins.notification.title", "skins.notification.import.online.not_found", s); - } - }))); - } - - private void initDisplay() { - loadSkinsList(); - loadCapesList(); - } - - private void refreshCurrentList() { - if (capesTab) { - var scroll = capesList.getScrollAmount(); - loadCapesList(); - capesList.setScrollAmount(scroll); - } else { - var scroll = skinList.getScrollAmount(); - loadSkinsList(); - skinList.setScrollAmount(scroll); - } - } - - private void loadCapesList() { - List rows = new ArrayList<>(); - var profile = cachedProfile; - int columns = Math.max(2, (width / 2 - 25) / LIST_SKIN_WIDTH); - var capes = profile.capes(); - var deselectCape = createWidgetForCape(current.getSkin(), null); - var activeCape = capes.stream().filter(Cape::active).findFirst(); - current.setCape(activeCape.orElse(null)); - deselectCape.noCape(activeCape.isEmpty()); - for (int i = 0; i < capes.size() + 1; i += columns) { - Entry widget; - if (i == 0) { - widget = createEntry(capesList.getEntryContentsHeight(), deselectCape, I18n.translate("skins.capes.no_cape")); - } else { - var cape = capes.get(i - 1); - widget = createEntryForCape(current.getSkin(), cape, capesList.getEntryContentsHeight()); - } - List widgets = new ArrayList<>(); - widgets.add(widget); - for (int c = 1; c < columns; c++) { - if (!(i < capes.size() + 1 - c)) continue; - var cape2 = capes.get(i + c - 1); - Entry widget2 = createEntryForCape(current.getSkin(), cape2, capesList.getEntryContentsHeight()); - - widgets.add(widget2); - } - rows.add(new Row(widgets)); - } - minecraft.executeTask(() -> capesList.replaceEntries(rows)); - } - - private void loadSkinsList() { - var profile = cachedProfile; - int columns = Math.max(2, (width / 2 - 25) / LIST_SKIN_WIDTH); - List skins = new ArrayList<>(profile.skins()); - var hashes = skins.stream().map(Asset::sha256).collect(Collectors.toSet()); - var defaultSkin = Skin.getDefaultSkin(account); - var local = new ArrayList<>(loadLocalSkins()); - var localHashes = local.stream().collect(Collectors.toMap(Asset::sha256, Function.identity(), (skin, skin2) -> skin)); - local.removeIf(s -> !localHashes.containsValue(s)); - skins.replaceAll(s -> { - if (s instanceof MSApi.MCProfile.OnlineSkin online) { - if (localHashes.containsKey(s.sha256()) && localHashes.get(s.sha256()) instanceof Skin.LocalSkin file) { - local.remove(localHashes.remove(s.sha256())); - return new Skin.Shared(file, online); - } - } - return s; - }); - skins.addAll(local); - if (!hashes.contains(defaultSkin.sha256())) { - skins.add(defaultSkin); - } - populateSkinList(skins, columns); - } - - private List loadLocalSkins() { - try { - Files.createDirectories(SKINS_DIR); - try (Stream skins = Files.list(SKINS_DIR)) { - return skins.filter(Files::isRegularFile).sorted(Comparator.comparingLong(p -> { - try { - return Files.getLastModifiedTime(p).toMillis(); - } catch (IOException e) { - return 0L; - } - }).reversed()).map(Auth.getInstance().getSkinManager()::read).filter(Objects::nonNull).toList(); - } - } catch (IOException e) { - DolphinClientCommon.getInstance().getLogger().warn("Failed to read skins dir!", e); - } - return Collections.emptyList(); - } - - private void populateSkinList(List skins, int columns) { - int entryHeight = skinList.getEntryContentsHeight(); - List rows = new ArrayList<>(); - for (int i = 0; i < skins.size(); i += columns) { - var s = skins.get(i); - if (s != null && s.active()) { - current.setSkin(s); - } - var widget = createEntryForSkin(s, entryHeight); - List widgets = new ArrayList<>(); - widgets.add(widget); - for (int c = 1; c < columns; c++) { - if (!(i < skins.size() - c)) continue; - var s2 = skins.get(i + c); - if (s2 != null && s2.active()) { - current.setSkin(s2); - } - var widget2 = createEntryForSkin(s2, entryHeight); - widgets.add(widget2); - } - rows.add(new Row(widgets)); - } - minecraft.executeTask(() -> skinList.replaceEntries(rows)); - } - - private Path ensureNonexistent(Path p) { - if (Files.exists(p)) { - int counter = 0; - do { - counter++; - p = p.resolveSibling(p.getFileName().toString() + "_" + counter); - } while (Files.exists(p)); - } - return p; - } - - @Override - public void onFileDrop(List packs) { - if (packs.isEmpty()) return; - - CompletableFuture[] futs = new CompletableFuture[packs.size()]; - for (int i = 0; i < packs.size(); i++) { - Path p = packs.get(i); - futs[i] = CompletableFuture.runAsync(() -> { - try { - var target = ensureNonexistent(SKINS_DIR.resolve(p.getFileName())); - var skin = Auth.getInstance().getSkinManager().read(p, false); - if (skin != null) { - Files.write(target, skin.image()); - } else { - DolphinClientCommon.getInstance().getLogger().info("Skipping dragged file {} because it does not seem to be a valid skin!", p); - Notifications.getInstance().addStatus("skins.notification.title", "skins.notification.not_copied", p.getFileName()); - } - } catch (IOException e) { - DolphinClientCommon.getInstance().getLogger().warn("Failed to copy skin file: ", e); - } - }, minecraft); - } - CompletableFuture.allOf(futs).thenRun(this::loadSkinsList); - } - - private @NotNull Entry createEntryForSkin(Skin skin, int entryHeight) { - return createEntry(entryHeight, new SkinWidget(LIST_SKIN_WIDTH, LIST_SKIN_HEIGHT, skin, account)); - } - - private @NotNull Entry createEntryForCape(Skin currentSkin, Cape cape, int entryHeight) { - return createEntry(entryHeight, createWidgetForCape(currentSkin, cape), I18n.translate(cape.alias())); - } - - private SkinWidget createWidgetForCape(Skin currentSkin, Cape cape) { - SkinWidget widget2 = new SkinWidget(LIST_SKIN_WIDTH, LIST_SKIN_HEIGHT, currentSkin, cape, account); - widget2.setRotationY(210); - return widget2; - } - - @Override - public void clearAndInit() { - Auth.getInstance().getSkinManager().releaseAll(); - super.clearAndInit(); - } - - @Override - public void removed() { - Auth.getInstance().getSkinManager().releaseAll(); - Watcher.close(skinDirWatcher); - } - - public void closeScreen() { - minecraft.openScreen(parent); - } - - private SkinListWidget getCurrentList() { - return capesTab ? capesList : skinList; - } - - private class SkinListWidget extends ElementListWidget { - public boolean active = true, visible = true; - - public SkinListWidget(Minecraft minecraft, int width, int height, int y, int entryHeight) { - super(minecraft, width, SkinManagementScreen.this.height, y, y + height, entryHeight); - setRenderHeader(false, 0); - setRenderBackground(false); - setRenderHorizontalShadows(false); - } - - @Override - protected int getScrollbarPositionX() { - return right - 8; - } - - @Override - public int getRowLeft() { - return left + 3; - } - - @Override - public int getRowWidth() { - if (!(getMaxScroll() > 0)) { - return width - 4; - } - return width - 14; - } - - public int getEntryContentsHeight() { - return itemHeight - 4; - } - - @Override - public void replaceEntries(Collection newEntries) { - super.replaceEntries(newEntries); - } - - @Override - public void centerScrollOn(Row entry) { - super.centerScrollOn(entry); - } - - @Override - public boolean mouseScrolled(double mouseX, double mouseY, double amountX, double amountY) { - if (!visible) return false; - return super.mouseScrolled(mouseX, mouseY, amountX, amountY); - } - - @Override - public boolean isMouseOver(double mouseX, double mouseY) { - return active && visible && super.isMouseOver(mouseX, mouseY); - } - - @Override - public void render(int mouseX, int mouseY, float delta) { - if (!visible) return; - super.render(mouseX, mouseY, delta); - renderGradient(); - } - - private void renderGradient() { - GlStateManager.depthFunc(515); - GlStateManager.disableDepthTest(); - GlStateManager.enableBlend(); - GlStateManager.blendFuncSeparate(770, 771, 0, 1); - GlStateManager.disableAlphaTest(); - GlStateManager.shadeModel(7425); - GlStateManager.disableTexture(); - GlStateManager.enableBlend(); - GlStateManager.disableTexture(); - var tesselator = Tesselator.getInstance(); - var bufferBuilder = tesselator.getBuffer(); - bufferBuilder.begin(7, DefaultVertexFormat.POSITION_TEX_COLOR); - bufferBuilder.vertex(left, top + 4, 0.0F).texture(0.0F, 1.0F).color(0, 0, 0, 0).nextVertex(); - bufferBuilder.vertex(right, top + 4, 0.0F).texture(1.0F, 1.0F).color(0, 0, 0, 0).nextVertex(); - bufferBuilder.vertex(right, top, 0.0F).texture(1.0F, 0.0F).color(0, 0, 0, 255).nextVertex(); - bufferBuilder.vertex(left, top, 0.0F).texture(0.0F, 0.0F).color(0, 0, 0, 255).nextVertex(); - tesselator.end(); - bufferBuilder.begin(7, DefaultVertexFormat.POSITION_TEX_COLOR); - bufferBuilder.vertex(this.left, this.bottom, 0.0F).texture(0.0F, 1.0F).color(0, 0, 0, 255).nextVertex(); - bufferBuilder.vertex(this.right, this.bottom, 0.0F).texture(1.0F, 1.0F).color(0, 0, 0, 255).nextVertex(); - bufferBuilder.vertex(this.right, this.bottom - 4, 0.0F).texture(1.0F, 0.0F).color(0, 0, 0, 0).nextVertex(); - bufferBuilder.vertex(this.left, this.bottom - 4, 0.0F).texture(0.0F, 0.0F).color(0, 0, 0, 0).nextVertex(); - tesselator.end(); - GlStateManager.enableTexture(); - } - } - - private class Row extends ElementListWidget.Entry { - private final List widgets; - - public Row(List entries) { - this.widgets = entries; - } - - @Override - public void render(int index, int top, int left, int width, int height, int mouseX, int mouseY, boolean hovering, float partialTick) { - int x = left; - if (widgets.isEmpty()) return; - int count = widgets.size(); - int padding = ((width - 5 * (count - 1)) / count); - for (var w : widgets) { - w.setPosition(x, top); - w.setWidth(padding); - w.render(mouseX, mouseY, partialTick); - x += w.getWidth() + 5; - } - } - - @Override - public @NotNull List children() { - return widgets; - } - - @Override - public void setFocusedChild(@Nullable Element focused) { - super.setFocusedChild(focused); - if (focused != null) { - getCurrentList().centerScrollOn(this); - } - } - } - - Entry createEntry(int height, SkinWidget widget) { - return createEntry(height, widget, null); - } - - Entry createEntry(int height, SkinWidget widget, String label) { - return new Entry(height, widget, label); - } - - private class Entry extends ClickableWidget implements ParentElement { - private final SkinWidget skinWidget; - private final @Nullable ClickableWidget label; - private final List actionButtons = new ArrayList<>(); - private final ClickableWidget equipButton; - private boolean equipping; - private long equippingStart; - @Nullable - private Element focused; - private boolean dragging; - - public Entry(int height, SkinWidget widget, @Nullable String label) { - super(0, 0, widget.getWidth(), height, ""); - widget.setWidth(getWidth() - 4); - var asset = widget.getFocusedAsset(); - if (asset != null) { - if (asset instanceof Skin skin) { - var wideSprite = new Identifier(DolphinClientCommon.MODID, "textures/gui/sprites/wide.png"); - var slimSprite = new Identifier(DolphinClientCommon.MODID, "textures/gui/sprites/slim.png"); - var slimText = I18n.translate("skins.manage.variant.classic"); - var wideText = I18n.translate("skins.manage.variant.slim"); - actionButtons.add(new SpriteButton(skin.classicVariant() ? wideText : slimText, btn -> { - var self = (SpriteButton) btn; - skin.classicVariant(!skin.classicVariant()); - self.sprite = skin.classicVariant() ? slimSprite : wideSprite; - self.setMessage(skin.classicVariant() ? wideText : slimText); - }, skin.classicVariant() ? slimSprite : wideSprite)); - } - if (asset instanceof Asset.Local local) { - this.actionButtons.add(new SpriteButton(I18n.translate("skins.manage.delete"), btn -> { - btn.active = false; - client.openScreen(new ConfirmScreen((confirmed, i) -> { - client.openScreen(new LoadingScreen(getTitle(), I18n.translate("menu.working"))); - if (confirmed) { - try { - Files.delete(local.file()); - Skin.LocalSkin.deleteMetadata(local.file()); - } catch (IOException e) { - DolphinClientCommon.getInstance().getLogger().warn("Failed to delete: ", e); - } - } - client.openScreen(SkinManagementScreen.this); - btn.active = true; - }, I18n.translate("skins.manage.delete.confirm"), ((Text) (asset.active() ? - AxoText.translatable("skins.manage.delete.confirm.desc_active") : - AxoText.translatable("skins.manage.delete.confirm.desc") - ).br$color(Colors.RED.toInt())).getFormattedString(), 0)); - }, new Identifier(DolphinClientCommon.MODID, "textures/gui/sprites/delete.png"))); - } - if (asset instanceof Asset.Online online && online.supportsDownload() && !(asset instanceof Asset.Local)) { - this.actionButtons.add(new SpriteButton(I18n.translate("skins.manage.download"), btn -> { - btn.active = false; - download(asset).thenRun(() -> { - refreshCurrentList(); - btn.active = true; - }); - }, new Identifier(DolphinClientCommon.MODID, "textures/gui/sprites/download.png"))); - } - } - if (label != null) { - this.label = new ClickableWidget(0, 0, widget.getWidth(), 16, label) { - @Override - protected void drawWidget(int mouseX, int mouseY, float partialTick) { - DrawUtil.drawScrollableText(textRenderer, getMessage(), getX() + 2, getY(), getX() + getWidth() - 2, getY() + getHeight(), -1); - } - }; - this.label.active = false; - } else { - this.label = null; - } - this.equipButton = new VanillaButtonWidget(0, 0, widget.getWidth(), 20, I18n.translate( - widget.isEquipped() ? "skins.manage.equipped" : "skins.manage.equip"), - btn -> { - equippingStart = System.currentTimeMillis(); - equipping = true; - btn.setMessage(TEXT_EQUIPPING); - btn.active = false; - Consumer> consumer = f -> f.thenAcceptAsync(p -> { - cachedProfile = p; - if (client.screen == SkinManagementScreen.this) { - refreshCurrentList(); - } else { - client.executeTask(() -> client.openScreen(SkinManagementScreen.this)); - } - }).exceptionally(t -> { - DolphinClientCommon.getInstance().getLogger().warn("Failed to equip asset!", t); - equipping = false; - return null; - }); - if (asset instanceof Skin && !(current.getSkin() instanceof Skin.Local)) { - client.openScreen(new ConfirmScreen((confirmed, i) -> { - client.openScreen(new LoadingScreen(getTitle(), TEXT_EQUIPPING)); - if (confirmed) { - consumer.accept(download(current.getSkin()).thenCompose(a -> widget.equip())); - } else { - consumer.accept(widget.equip()); - } - }, I18n.translate("skins.manage.equip.confirm"), I18n.translate("skins.manage.equip.download_current"), 0)); - } else { - consumer.accept(widget.equip()); - } - }); - this.equipButton.active = !widget.isEquipped(); - this.skinWidget = widget; - } - - private @NotNull CompletableFuture download(Asset asset) { - return CompletableFuture.runAsync(() -> { - try { - var out = SKINS_DIR.resolve(asset.sha256()); - Files.createDirectories(out.getParent()); - Files.write(out, asset.image()); - if (asset instanceof Skin skin) { - Skin.LocalSkin.writeMetadata(out, Map.of(Skin.LocalSkin.CLASSIC_METADATA_KEY, skin.classicVariant())); - } - } catch (IOException e) { - DolphinClientCommon.getInstance().getLogger().warn("Failed to download: ", e); - } - }); - } - - @Override - public final boolean isDragging() { - return this.dragging; - } - - @Override - public final void setDragging(boolean dragging) { - this.dragging = dragging; - } - - @Nullable - @Override - public Element getFocused() { - return this.focused; - } - - @Override - public void setFocusedChild(@Nullable Element child) { - if (this.focused != null) { - this.focused.setFocused(false); - } - - if (child != null) { - child.setFocused(true); - } - - this.focused = child; - } - - @Override - public boolean mouseClicked(double mouseX, double mouseY, int button) { - return ParentElement.super.mouseClicked(mouseX, mouseY, button); - } - - @Override - public boolean mouseReleased(double mouseX, double mouseY, int button) { - return ParentElement.super.mouseReleased(mouseX, mouseY, button); - } - - @Override - public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) { - return ParentElement.super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY); - } - - @Override - public boolean isFocused() { - return ParentElement.super.isFocused(); - } - - @Override - public void setFocused(boolean focused) { - ParentElement.super.setFocused(focused); - } - - @Override - public @NotNull List children() { - return Stream.concat(actionButtons.stream(), Stream.of(skinWidget, label, equipButton)).filter(Objects::nonNull).toList(); - } - - private float applyEasing(float x) { - return x * x * x; - } - - @Override - protected void drawWidget(int mouseX, int mouseY, float partialTick) { - int y = getY() + 4; - int x = getX() + 2; - skinWidget.setPosition(x, y); - skinWidget.setWidth(getWidth() - 4); - if (skinWidget.isEquipped() || equipping) { - long prog; - if (Auth.getInstance().skinManagerAnimations.get()) { - if (equipping) prog = (System.currentTimeMillis() - equippingStart) / 20 % 100; - else prog = Math.abs((System.currentTimeMillis() / 30 % 200) - 100); - } else prog = 100; - var percent = (prog / 100f); - float gradientWidth; - if (equipping) { - gradientWidth = percent * Math.min(getWidth() / 3f, getHeight() / 3f); - } else { - gradientWidth = Math.min(getWidth() / 15f, getHeight() / 6f) + applyEasing(percent) * Math.min(getWidth() * 2 / 15f, getHeight() / 6f); - } - GradientHoleRectangleRenderState.render(getX() + 2, getY() + 2, getX() + getWidth() - 2, - skinWidget.getY() + skinWidget.getHeight() + 2, - gradientWidth, - equipping ? 0xFFFF0088 : ClientColors.SELECTOR_GREEN.toInt(), 0); - } - skinWidget.render(mouseX, mouseY, partialTick); - int actionButtonY = getY() + 2; - for (var button : actionButtons) { - button.setPosition(skinWidget.getX() + skinWidget.getWidth() - button.getWidth(), actionButtonY); - if (isHovered() || button.isHovered()) { - button.render(mouseX, mouseY, partialTick); - } - actionButtonY += button.getHeight() + 2; - } - if (label != null) { - label.setPosition(x, skinWidget.getY() + skinWidget.getHeight() + 6); - label.render(mouseX, mouseY, partialTick); - label.setWidth(getWidth() - 4); - equipButton.setPosition(x, label.getY() + label.getHeight() + 2); - } else { - equipButton.setPosition(x, skinWidget.getY() + skinWidget.getHeight() + 4); - } - equipButton.setWidth(getWidth() - 4); - equipButton.render(mouseX, mouseY, partialTick); - - if (isHovered()) { - DrawUtil.outlineRect(getX(), getY(), getWidth(), getHeight(), -1); - } - } - - private static class GradientHoleRectangleRenderState { - - public static void render(int x0, int y0, int x1, int y1, float gradientWidth, int col1, int col2) { - var tess = Tesselator.getInstance(); - var vertexConsumer = tess.getBuffer(); - float z = 0; - int a1 = ClientColors.ARGB.alpha(col1); - int r1 = ClientColors.ARGB.red(col1); - int g1 = ClientColors.ARGB.green(col1); - int b1 = ClientColors.ARGB.blue(col1); - int a2 = ClientColors.ARGB.alpha(col2); - int r2 = ClientColors.ARGB.red(col2); - int g2 = ClientColors.ARGB.green(col2); - int b2 = ClientColors.ARGB.blue(col2); - GlStateManager.disableTexture(); - GlStateManager.enableBlend(); - GlStateManager.disableAlphaTest(); - GlStateManager.blendFuncSeparate(770, 771, 1, 0); - GlStateManager.shadeModel(7425); - //top - vertexConsumer.begin(7, DefaultVertexFormat.POSITION_COLOR); - vertexConsumer.vertex(x0, y0, z).color(r1, g1, b1, a1).nextVertex(); - vertexConsumer.vertex(x0 + gradientWidth, y0 + gradientWidth, z).color(r2, g2, b2, a2).nextVertex(); - vertexConsumer.vertex(x1 - gradientWidth, y0 + gradientWidth, z).color(r2, g2, b2, a2).nextVertex(); - vertexConsumer.vertex(x1, y0, z).color(r1, g1, b1, a1).nextVertex(); - //left - vertexConsumer.vertex(x0, y1, z).color(r1, g1, b1, a1).nextVertex(); - vertexConsumer.vertex(x0 + gradientWidth, y1 - gradientWidth, z).color(r2, g2, b2, a2).nextVertex(); - vertexConsumer.vertex(x0 + gradientWidth, y0 + gradientWidth, z).color(r2, g2, b2, a2).nextVertex(); - vertexConsumer.vertex(x0, y0, z).color(r1, g1, b1, a1).nextVertex(); - //bottom - vertexConsumer.vertex(x1, y1, z).color(r1, g1, b1, a1).nextVertex(); - vertexConsumer.vertex(x1 - gradientWidth, y1 - gradientWidth, z).color(r2, g2, b2, a2).nextVertex(); - vertexConsumer.vertex(x0 + gradientWidth, y1 - gradientWidth, z).color(r2, g2, b2, a2).nextVertex(); - vertexConsumer.vertex(x0, y1, z).color(r1, g1, b1, a1).nextVertex(); - //right - vertexConsumer.vertex(x1, y0, z).color(r1, g1, b1, a1).nextVertex(); - vertexConsumer.vertex(x1 - gradientWidth, y0 + gradientWidth, z).color(r2, g2, b2, a2).nextVertex(); - vertexConsumer.vertex(x1 - gradientWidth, y1 - gradientWidth, z).color(r2, g2, b2, a2).nextVertex(); - vertexConsumer.vertex(x1, y1, z).color(r1, g1, b1, a1).nextVertex(); - tess.end(); - GlStateManager.shadeModel(7424); - GlStateManager.disableBlend(); - GlStateManager.enableAlphaTest(); - GlStateManager.enableTexture(); - } - } - } - - private class SpriteButton extends VanillaButtonWidget { - private static final DrawUtil.NineSlice SLICE = new DrawUtil.NineSlice(200, 20, 3); - private Identifier sprite; - - SpriteButton(String message, PressAction action, Identifier sprite) { - super(0, 0, 11, 11, message, action); - this.sprite = sprite; - } - - @Override - protected void drawWidget(int mouseX, int mouseY, float delta) { - int i = 1; - if (!this.active) { - i = 0; - } else if (hovered) { - i = 2; - tooltip = getMessage(); - } - - Identifier tex = ButtonWidgetTextures.get(i); - DrawUtil.blitSprite(tex, getX(), getY(), getWidth(), getHeight(), SLICE); - minecraft.getTextureManager().bind(sprite); - DrawUtil.drawTexture(getX() + 2, getY() + 2, 0, 0, 7, 7, 7, 7); - } - - @Override - protected void drawScrollingText(TextRenderer renderer, int offset, Color color) { - - } - } -} diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/auth/skin/SkinManager.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/auth/skin/SkinManager.java deleted file mode 100644 index 43ae4ff1a..000000000 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/auth/skin/SkinManager.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright © 2025 moehreag & Contributors - * Copyright © 2026 DSNS - * - * This file is part of DolphinClient, a fork of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.modules.auth.skin; - -import javax.imageio.ImageIO; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Comparator; -import java.util.Set; -import java.util.concurrent.ConcurrentSkipListSet; - -import com.google.common.hash.Hashing; -import io.github.axolotlclient.DolphinClientCommon; -import io.github.axolotlclient.bridge.util.AxoIdentifier; -import io.github.axolotlclient.util.ClientColors; -import net.minecraft.client.Minecraft; -import net.minecraft.client.render.texture.DynamicTexture; -import net.minecraft.client.render.texture.SkinImageProcessor; -import net.minecraft.resource.Identifier; - -public class SkinManager { - - private final Set loadedTextures = new ConcurrentSkipListSet<>(Comparator.comparing(Object::toString)); - - public Skin read(Path p) { - return read(p, true); - } - - @SuppressWarnings("UnstableApiUsage") - public Skin read(Path p, boolean fix) { - boolean slim; - String sha256; - try { - var in = Files.readAllBytes(p); - sha256 = Hashing.sha256().hashBytes(in).toString(); - try (var bs = new ByteArrayInputStream(in)) { - var img = ImageIO.read(bs); - int height = img.getHeight(); - int width = img.getWidth(); - if (width != 64) return null; - if (height == 32) { - if (fix) { - try (var out = Files.newOutputStream(p)) { - ImageIO.write(new SkinImageProcessor().process(img), "png", out); - } - } - slim = false; - } else if (height != 64) { - return null; - } else { - slim = ClientColors.ARGB.alpha(img.getRGB(50, 16)) == 0; - } - var metadata = Skin.LocalSkin.readMetadata(p); - if (metadata != null && metadata.containsKey(Skin.LocalSkin.CLASSIC_METADATA_KEY)) { - slim = !(boolean) metadata.get(Skin.LocalSkin.CLASSIC_METADATA_KEY); - } - } - return new Skin.LocalSkin(!slim, p, in, sha256); - } catch (Exception e) { - DolphinClientCommon.getInstance().getLogger().warn("Failed to probe skin: ", e); - } - return null; - } - - public AxoIdentifier loadSkin(Skin skin) { - var rl = AxoIdentifier.of(DolphinClientCommon.MODID, "skins/" + skin.sha256()); - if (loadedTextures.contains(rl)) { - return rl; - } - - try (var bs = new ByteArrayInputStream(skin.image())) { - var img = ImageIO.read(bs); - var tex = new DynamicTexture(img.getWidth(), img.getHeight()); - img.getRGB(0, 0, img.getWidth(), img.getHeight(), tex.getPixels(), 0, img.getWidth()); - tex.upload(); - Minecraft.getInstance().getTextureManager().register((Identifier) rl, tex); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - loadedTextures.add(rl); - return rl; - } - - public AxoIdentifier loadCape(Cape cape) { - var rl = AxoIdentifier.of(DolphinClientCommon.MODID, "capes/" + cape.id()); - if (loadedTextures.contains(rl)) { - return rl; - } - - try (var bs = new ByteArrayInputStream(cape.image())) { - var img = ImageIO.read(bs); - var tex = new DynamicTexture(img.getWidth(), img.getHeight()); - img.getRGB(0, 0, img.getWidth(), img.getHeight(), tex.getPixels(), 0, img.getWidth()); - tex.upload(); - Minecraft.getInstance().getTextureManager().register((Identifier) rl, tex); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - loadedTextures.add(rl); - return rl; - } - - public void releaseAll() { - loadedTextures.forEach(id -> Minecraft.getInstance().getTextureManager().close((Identifier) id)); - loadedTextures.clear(); - } -} diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/auth/skin/SkinRenderer.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/auth/skin/SkinRenderer.java deleted file mode 100644 index 57cb75d4d..000000000 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/auth/skin/SkinRenderer.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright © 2025 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.modules.auth.skin; - -import net.minecraft.client.Minecraft; -import net.minecraft.client.render.model.entity.PlayerModel; -import net.minecraft.client.render.platform.GLX; -import net.minecraft.client.render.platform.GlStateManager; -import net.minecraft.resource.Identifier; -import org.jetbrains.annotations.Nullable; - -public class SkinRenderer { - private static PlayerModel classicModel, slimModel; - private static final Minecraft minecraft = Minecraft.getInstance(); - - private SkinRenderer() { - } - - public static void render(boolean classicVariant, - Identifier skinTexture, - @Nullable Identifier cape, - float rotationX, - float rotationY, - float pivotY, - int x0, - int y0, - int x1, - int y1, - float scale) { - - if (classicModel == null && classicVariant) { - classicModel = new PlayerModel(0, false); - classicModel.isBaby = false; - classicModel.setVisible(true); - } - if (slimModel == null && !classicVariant) { - slimModel = new PlayerModel(0, true); - slimModel.isBaby = false; - slimModel.setVisible(true); - } - - int width = x1 - x0; - int light = 15728880; - GLX.multiTexCoord2f(GLX.GL_TEXTURE1, light % 65536, light / 65536f); - GlStateManager.color4f(1.0F, 1.0F, 1.0F, 1.0F); - GlStateManager.pushMatrix(); - GlStateManager.translatef(x0 + width / 2.0F, (float) (y1), 00.0F); - GlStateManager.scalef(scale, scale, 1); - GlStateManager.translatef(0.0F, -0.0625F, 0.0F); - GlStateManager.translatef(0, pivotY, 0); - GlStateManager.rotatef(rotationX, 1, 0, 0); - GlStateManager.translatef(0, -pivotY, 0); - GlStateManager.rotatef(rotationY, 0, 1, 0); - GlStateManager.pushMatrix(); - GlStateManager.scalef(1.0F, 1.0F, -1.0F); - GlStateManager.translatef(0.0F, -1.5F, 0.0F); - var model = classicVariant ? classicModel : slimModel; - minecraft.getTextureManager().bind(skinTexture); - GlStateManager.enableBlend(); - GlStateManager.enableDepthTest(); - GlStateManager.pushMatrix(); - float k = 0.0625F; - model.head.render(k); - model.body.render(k); - model.rightLeg.render(k); - model.leftLeg.render(k); - model.hat.render(k); - model.leftPants.render(k); - model.rightPants.render(k); - model.jacket.render(k); - model.renderLeftArm(); - model.rightArm.render(0.0625F); - GlStateManager.translatef(0, 0, -0.62F); // why? - model.rightSleeve.render(0.0625F); - GlStateManager.popMatrix(); - if (cape != null) { - GlStateManager.pushMatrix(); - minecraft.getTextureManager().bind(cape); - GlStateManager.translatef(0.0F, 0.0F, 0.125F); - GlStateManager.rotatef(6.0F, 1, 0, 0); - GlStateManager.rotatef(180.0F, 0, 1, 0); - model.renderCape(0.0625F); - GlStateManager.popMatrix(); - } - GlStateManager.popMatrix(); - GlStateManager.popMatrix(); - GlStateManager.disableBlend(); - GlStateManager.disableDepthTest(); - } -} diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/auth/skin/SkinWidget.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/auth/skin/SkinWidget.java deleted file mode 100644 index ab149d099..000000000 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/auth/skin/SkinWidget.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright © 2025 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.modules.auth.skin; - -import java.util.concurrent.CompletableFuture; - -import io.github.axolotlclient.AxolotlClientConfig.impl.ui.ClickableWidget; -import io.github.axolotlclient.bridge.util.AxoIdentifier; -import io.github.axolotlclient.modules.auth.Account; -import io.github.axolotlclient.modules.auth.Auth; -import io.github.axolotlclient.modules.auth.MSApi; -import lombok.Getter; -import lombok.Setter; -import net.minecraft.client.sound.system.SoundManager; -import net.minecraft.resource.Identifier; -import net.minecraft.util.math.MathHelper; -import org.jetbrains.annotations.Nullable; - -public class SkinWidget extends ClickableWidget { - private static final float MODEL_HEIGHT = 2.125F; - private static final float FIT_SCALE = 0.97F; - private static final float ROTATION_SENSITIVITY = 2.5F; - private static final float DEFAULT_ROTATION_X = -5.0F; - private static final float DEFAULT_ROTATION_Y = 30.0F; - private static final float ROTATION_X_LIMIT = 50.0F; - private float rotationX = DEFAULT_ROTATION_X; - @Setter - private float rotationY = DEFAULT_ROTATION_Y; - @Getter - @Setter - private Skin skin; - @Getter - @Setter - private Cape cape; - private final Account owner; - private boolean noCape, noCapeActive; - - public SkinWidget(int width, int height, Skin skin, @Nullable Cape cape, Account owner) { - super(0, 0, width, height, ""); - this.skin = skin; - this.cape = cape; - this.owner = owner; - } - - public SkinWidget(int width, int height, Skin skin, Account owner) { - this(width, height, skin, null, owner); - } - - public void noCape(boolean noCapeActive) { - noCape = true; - this.noCapeActive = noCapeActive; - } - - @Override - protected void drawWidget(int mouseX, int mouseY, float partialTick) { - float scale = FIT_SCALE * this.getHeight() / MODEL_HEIGHT; - float pivotY = -1.0625F; - - SkinManager skinManager = Auth.getInstance().getSkinManager(); - AxoIdentifier skinRl = skinManager.loadSkin(skin); - boolean classic = skin.classicVariant(); - var capeRl = cape == null ? null : skinManager.loadCape(cape); - - SkinRenderer.render(classic, (Identifier) skinRl, (Identifier) capeRl, this.rotationX, this.rotationY, pivotY, this.getX(), this.getY(), this.getX() + getWidth(), this.getY() + getHeight(), scale); - } - - @Override - protected void onDrag(double mouseX, double mouseY, double dragX, double dragY) { - this.rotationX = MathHelper.clamp(this.rotationX - (float) dragY * ROTATION_SENSITIVITY, -ROTATION_X_LIMIT, ROTATION_X_LIMIT); - this.rotationY += (float) dragX * ROTATION_SENSITIVITY; - } - - @Override - public void playDownSound(SoundManager soundManager) { - - } - - public boolean isEquipped() { - return noCape ? noCapeActive : (cape != null ? cape.active() : skin != null && skin.active()); - } - - public CompletableFuture equip() { - var msApi = Auth.getInstance().getMsApi(); - if (noCape) { - return msApi.hideCape(owner); - } - if (cape != null) { - return cape.equip(msApi, owner); - } - if (skin != null) { - return skin.equip(msApi, owner); - } - return msApi.resetSkin(owner); - } - - public Asset getFocusedAsset() { - return noCape ? null : cape != null ? cape : skin; - } -} diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/hud/ModsMenuIcons.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/hud/ModsMenuIcons.java index d9a822f46..1031c53d9 100644 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/hud/ModsMenuIcons.java +++ b/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/hud/ModsMenuIcons.java @@ -80,7 +80,6 @@ final class ModsMenuIcons { put("nametagOptions", Items.NAME_TAG); put("hud", Items.PAINTING); put("hypixel-mods", Items.GOLDEN_SWORD); - put("api.category", Items.ENDER_EYE); put("zoom", Item.byBlock(Blocks.GLASS)); put("motionBlur", Items.POTION); put("menublur", Item.byBlock(Blocks.GLASS)); @@ -91,7 +90,6 @@ final class ModsMenuIcons { put("scrollableTooltips", Items.BOOK); put("screenshotUtils", Items.PAINTING); put("fpsLimiter", Items.CLOCK); - put("auth", Items.SKULL); put("sky", Items.ENDER_EYE); put("blockOutlines", Item.byBlock(Blocks.STONE)); put("timeChanger", Items.CLOCK); diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/screenshotUtils/DownloadImageScreen.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/screenshotUtils/DownloadImageScreen.java deleted file mode 100644 index 485b3210e..000000000 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/screenshotUtils/DownloadImageScreen.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * Copyright © 2026 DSNS - * - * This file is part of DolphinClient, a fork of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.modules.screenshotUtils; - -import io.github.axolotlclient.DolphinClientCommon; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.gui.widget.ButtonWidget; -import net.minecraft.client.gui.widget.TextFieldWidget; -import net.minecraft.client.render.platform.GlStateManager; -import net.minecraft.client.resource.language.I18n; -import net.minecraft.resource.Identifier; - -public class DownloadImageScreen extends Screen { - private static final Identifier SPRITE = new Identifier(DolphinClientCommon.MODID, "textures/gui/sprites/go.png"); - - private final Screen parent; - - private final String title; - private TextFieldWidget urlBox; - - public DownloadImageScreen(Screen parent) { - super(); - this.title = I18n.translate("viewScreenshot"); - this.parent = parent; - } - - @Override - public void render(int mouseX, int mouseY, float delta) { - renderBackground(); - urlBox.render(); - super.render(mouseX, mouseY, delta); - drawCenteredString(textRenderer, title, width / 2, 33 / 2 - textRenderer.fontHeight / 2, -1); - } - - @Override - public void init() { - urlBox = new TextFieldWidget(0, textRenderer, width / 2 - 100, height / 2 - 10, 200, 20) { - @Override - public void render() { - super.render(); - - if (getText().isEmpty()) { - drawString(Minecraft.getInstance().textRenderer, I18n.translate("pasteURL"), x + 3, y + 6, -8355712); - } - } - }; - urlBox.setMaxLength(63); - buttons.add(new ButtonWidget(1, width / 2 + 100 + 4, height / 2 - 10, 20, 20, I18n.translate("download")) { - @Override - public void render(Minecraft client, int mouseX, int mouseY) { - client.getTextureManager().bind(WIDGETS_LOCATION); - GlStateManager.color4f(1.0F, 1.0F, 1.0F, 1.0F); - this.hovered = mouseX >= this.x && mouseY >= this.y && mouseX < this.x + this.width && mouseY < this.y + this.height; - int i = this.getYImage(this.hovered); - GlStateManager.enableBlend(); - GlStateManager.blendFuncSeparate(770, 771, 1, 0); - GlStateManager.blendFunc(770, 771); - this.drawTexture(this.x, this.y, 0, 46 + i * 20, this.width / 2, this.height); - this.drawTexture(this.x + this.width / 2, this.y, 200 - this.width / 2, 46 + i * 20, this.width / 2, this.height); - Minecraft.getInstance().getTextureManager().bind(SPRITE); - drawTexture(x, y, 0, 0, this.getWidth(), this.height, this.getWidth(), this.height); - } - }); - - buttons.add(new ButtonWidget(2, width / 2 - 75, height - 33 / 2 - 10, 150, 20, I18n.translate("gui.back"))); - } - - @Override - protected void buttonClicked(ButtonWidget buttonWidget) { - if (buttonWidget.id == 1) { - String url = urlBox.getText().trim(); - if (url.isEmpty()) { - return; - } - minecraft.openScreen(ImageScreen.create(this, ImageShare.getInstance().downloadImage(url), true)); - } else if (buttonWidget.id == 2) { - minecraft.openScreen(parent); - } - } - - @Override - protected void keyPressed(char character, int code) { - super.keyPressed(character, code); - urlBox.keyPressed(character, code); - } - - @Override - protected void mouseClicked(int mouseX, int mouseY, int button) { - super.mouseClicked(mouseX, mouseY, button); - urlBox.mouseClicked(mouseX, mouseY, button); - } - - @Override - public void tick() { - urlBox.tick(); - } -} diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/screenshotUtils/GalleryScreen.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/screenshotUtils/GalleryScreen.java index 7d5c1e167..6ae528959 100644 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/screenshotUtils/GalleryScreen.java +++ b/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/screenshotUtils/GalleryScreen.java @@ -157,18 +157,14 @@ public void init() { }); int buttonWidth = 150; - int footerButtonX = width / 2 - buttonWidth - 2; int footerButtonY = height - 33 / 2 - 10; - buttons.add(new ButtonWidget(3, footerButtonX, footerButtonY, buttonWidth, 20, I18n.translate("gallery.download_external"))); - footerButtonX += buttonWidth + 4; - buttons.add(new ButtonWidget(4, footerButtonX, footerButtonY, buttonWidth, 20, I18n.translate("gui.back"))); + buttons.add(new ButtonWidget(4, width / 2 - buttonWidth / 2, footerButtonY, buttonWidth, 20, I18n.translate("gui.back"))); } @Override protected void buttonClicked(ButtonWidget buttonWidget) { switch (buttonWidget.id) { case 0 -> init(minecraft, width, height); - case 3 -> minecraft.openScreen(new DownloadImageScreen(this)); case 4 -> { Tab.LOCAL.loadingCache().forEach((path, instance) -> { if (instance != null) { diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/screenshotUtils/ImageScreen.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/screenshotUtils/ImageScreen.java index bb36fd9dd..8950745b3 100644 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/screenshotUtils/ImageScreen.java +++ b/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/screenshotUtils/ImageScreen.java @@ -29,15 +29,12 @@ import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.Path; -import java.time.Instant; import java.time.ZoneId; import java.util.ArrayList; import java.util.Map; import java.util.concurrent.CompletableFuture; import io.github.axolotlclient.DolphinClientCommon; -import io.github.axolotlclient.api.API; -import io.github.axolotlclient.api.util.UUIDHelper; import io.github.axolotlclient.util.GsonHelper; import io.github.axolotlclient.util.OSUtil; import io.github.axolotlclient.util.notifications.Notifications; @@ -54,7 +51,7 @@ public class ImageScreen extends Screen { private final boolean freeOnClose; private final boolean isRemote; private final String title; - private final CompletableFuture uploader; + private final String uploader; static Screen create(Screen parent, CompletableFuture future, boolean freeOnClose) { if (future.isDone()) { @@ -80,22 +77,18 @@ private ImageScreen(Screen parent, ImageInstance instance, boolean freeOnClose) this.image = instance; this.freeOnClose = freeOnClose; this.isRemote = image instanceof ImageInstance.Remote; - if (isRemote) { - this.uploader = UUIDHelper.tryGetUsernameAsync(((ImageInstance.Remote) image).uploader()); - } else { - this.uploader = null; - } + this.uploader = isRemote ? ((ImageInstance.Remote) image).uploader() : null; } @Override public void render(int mouseX, int mouseY, float delta) { renderBackground(); super.render(mouseX, mouseY, delta); - if (isRemote && uploader.isDone()) { + if (isRemote) { ImageInstance.Remote r = (ImageInstance.Remote) image; drawCenteredString(textRenderer, title, width / 2, 38 / 2 - textRenderer.fontHeight - 2, -1); drawCenteredString(textRenderer, - I18n.translate("gallery.image.upload_details", uploader.join(), + I18n.translate("gallery.image.upload_details", uploader, r.sharedAt().atZone(ZoneId.systemDefault()).format(DolphinClientCommon.getInstance().getConfig().getDateTimeFormatter())), width / 2, 38 / 2 + 2, -1); } else { @@ -123,9 +116,6 @@ public void init() { int actionX = element.x + imageWidth + 4; var actions = new ArrayList(); if (image instanceof ImageInstance.Local) { - if (API.getInstance().isAuthenticated() && !(image instanceof ImageInstance.Remote)) { - actions.add(new ButtonWidget(0, 0, 0, buttonWidth, 20, I18n.translate("gallery.image.upload"))); - } actions.add(new ButtonWidget(1, 0, 0, buttonWidth, 20, I18n.translate("gallery.image.copy"))); actions.add(new ButtonWidget(2, 0, 0, buttonWidth, 20, I18n.translate("gallery.image.open.external"))); actions.add(new ButtonWidget(8, 0, 0, buttonWidth, 20, I18n.translate("gallery.image.delete"))); @@ -153,19 +143,6 @@ public void init() { @Override protected void buttonClicked(ButtonWidget b) { switch (b.id) { - case 0 -> { - b.active = false; - ImageInstance.Local local = (ImageInstance.Local) image; - ImageShare.getInstance().upload(local.location()).thenAccept(s -> { - if (s.isEmpty()) { - Notifications.getInstance().addStatus("gallery.image.upload.failure", "gallery.image.upload.failure.description"); - } else { - minecraft.executeTask(() -> minecraft.openScreen(new ImageScreen(parent, local.toShared(s, API.getInstance().getSelfUuid(), Instant.now()), freeOnClose))); - setClipboard(s); - Notifications.getInstance().addStatus("gallery.image.upload.success", "gallery.image.upload.success.description", s); - } - }); - } case 1 -> ScreenshotCopying.copy(((ImageInstance.Local) image).location()); case 2 -> OSUtil.getOS().open(((ImageInstance.Local) image).location().toUri()); case 3 -> { diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/screenshotUtils/ImageShare.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/screenshotUtils/ImageShare.java deleted file mode 100644 index afd952a5f..000000000 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/screenshotUtils/ImageShare.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright © 2024 moehreag & Contributors - * - * This file is part of AxolotlClient. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * For more information, see the LICENSE file. - */ - -package io.github.axolotlclient.modules.screenshotUtils; - -import javax.imageio.ImageIO; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.concurrent.CompletableFuture; - -import com.google.common.hash.HashFunction; -import com.google.common.hash.Hashing; -import io.github.axolotlclient.util.Util; -import lombok.Getter; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.resource.language.I18n; -import net.minecraft.text.*; - -@SuppressWarnings("UnstableApiUsage") -public class ImageShare extends ImageNetworking { - - @Getter - private static final ImageShare Instance = new ImageShare(); - - private ImageShare() { - } - - public void uploadImage(Path file) { - Util.addMessageToChatHud(new TranslatableText("imageUploadStarted")); - upload(file).whenCompleteAsync((downloadUrl, throwable) -> { - if (downloadUrl.isEmpty()) { - Util.addMessageToChatHud(new TranslatableText("imageUploadFailure")); - } else { - Util.addMessageToChatHud(new LiteralText(I18n.translate("imageUploadSuccess") + " ") - .append(new LiteralText(downloadUrl) - .setStyle(new Style() - .setUnderlined(true) - .setColor(Formatting.DARK_PURPLE) - .setClickEvent(new ScreenshotUtils.CustomClickEvent(null, null) { - @Override - public void doAction() { - Screen.setClipboard(downloadUrl); - } - } - ) - .setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new LiteralText(I18n.translate("clickToCopy"))))))); - } - }); - } - - public CompletableFuture downloadImage(String url) { - return download(url).thenApply(data -> { - if (data != ImageData.EMPTY) { - try (var in = new ByteArrayInputStream(data.data())) { - ImageInstance.Remote remote = new ImageInstance.RemoteImpl(ImageIO.read(in), data.name(), data.uploader(), data.sharedAt(), ensureUrl(url).orElseThrow()); - try { - Path local = GalleryScreen.SCREENSHOTS_DIR.resolve(remote.filename()); - HashFunction hash = Hashing.goodFastHash(32); - if (Files.exists(local) && hash.hashBytes(data.data()).equals(hash.hashBytes(Files.readAllBytes(local)))) { - return remote.toShared(local); - } - } catch (IOException ignored) { - } - return remote; - } catch (IOException ignored) { - } - } - return null; - }); - } -} diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/screenshotUtils/ScreenshotUtils.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/screenshotUtils/ScreenshotUtils.java index eff908e78..b651788a5 100644 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/screenshotUtils/ScreenshotUtils.java +++ b/versions/1.8.9/src/main/java/io/github/axolotlclient/modules/screenshotUtils/ScreenshotUtils.java @@ -40,7 +40,6 @@ import io.github.axolotlclient.AxolotlClientConfig.impl.options.ColorOption; import io.github.axolotlclient.AxolotlClientConfig.impl.options.EnumOption; import io.github.axolotlclient.AxolotlClientConfig.impl.options.StringArrayOption; -import io.github.axolotlclient.api.API; import io.github.axolotlclient.bridge.AxoMinecraftClient; import io.github.axolotlclient.bridge.key.AxoKeybinding; import io.github.axolotlclient.bridge.key.AxoKeys; @@ -90,10 +89,6 @@ public class ScreenshotUtils extends AbstractModule { Util.addMessageToChatHud(new TranslatableText("screenshot.gallery.view.error")); } })); - - actions.put(() -> API.getInstance().isAuthenticated(), new Action("uploadAction", Formatting.AQUA, - "upload_image", - ImageShare.getInstance()::uploadImage)); }); private final OptionCategory category = OptionCategory.create("screenshotUtils"); private final BooleanOption enabled = new BooleanOption("enabled", false); From 147394cfa62d8e1831f1506aea281fc4737e6263 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 27 Aug 2026 05:32:53 +0000 Subject: [PATCH 3/3] Show Screenshots as its own Settings tile again General only has Screenshots as a child after Authentication was removed, so the catalog had nested it under General. Promote Settings children even when there is just one so Screenshots is a sibling tile. Co-authored-by: Dominic Seung --- .../axolotlclient/config/ui/MenuCatalog.java | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/versions/1.8.9/src/main/java/io/github/axolotlclient/config/ui/MenuCatalog.java b/versions/1.8.9/src/main/java/io/github/axolotlclient/config/ui/MenuCatalog.java index 8a5c5bab2..44d0ab991 100644 --- a/versions/1.8.9/src/main/java/io/github/axolotlclient/config/ui/MenuCatalog.java +++ b/versions/1.8.9/src/main/java/io/github/axolotlclient/config/ui/MenuCatalog.java @@ -173,7 +173,7 @@ private static void absorbAnimations(List modules, OptionCategory catego private static void absorb(List modules, OptionCategory category, Tab tab) { Collection children = category.getSubCategories(); - if (shouldPromote(children)) { + if (shouldPromote(children, tab)) { List own = optionNodes(category.getOptions()); if (!own.isEmpty()) { modules.add(moduleFrom(category.getName(), tab, firstEnabled(category.getOptions()), own)); @@ -190,9 +190,17 @@ private static Module moduleFrom(String nameKey, Tab tab, BooleanOption enabled, return new Module(tab.name().toLowerCase(Locale.ROOT) + ":" + nameKey, nameKey, tab, enabled, null, nodes); } - private static boolean shouldPromote(Collection children) { + /** + * Settings keeps a tile per child even when only Screenshots remains + * under General, matching the layout from before Authentication was + * removed. Mods still need at least two children before splitting. + */ + private static boolean shouldPromote(Collection children, Tab tab) { int size = children.size(); - return size >= 2 && size <= 12; + if (size == 0 || size > 12) { + return false; + } + return tab == Tab.SETTINGS || size >= 2; } private static List nodesFrom(OptionCategory category, boolean skipNestedHudEntries) {