All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jdash.client.GDClient Maven / Gradle / Ivy

The newest version!
package jdash.client;

import jdash.client.cache.GDCache;
import jdash.client.exception.ActionFailedException;
import jdash.client.exception.GDClientException;
import jdash.client.request.GDRequest;
import jdash.client.request.GDRouter;
import jdash.common.*;
import jdash.common.entity.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.annotation.Nullable;

import java.util.*;
import java.util.stream.Collectors;

import static jdash.client.request.GDRequests.*;
import static jdash.client.response.GDResponseDeserializers.*;
import static jdash.common.RobTopsWeakEncryption.*;
import static jdash.common.internal.InternalUtils.b64Encode;
import static jdash.common.internal.InternalUtils.randomString;
import static reactor.function.TupleUtils.function;

/**
 * Allows to request Geometry Dash data, such as levels, users, comments, private messages, etc. A {@link GDClient} is
 * immutable: when calling one of the with* methods, a new instance is created with the new properties.
 * This makes the client safe to use in a multi-thread context.
 * 

* Each of the methods may emit {@link GDClientException} if something goes wrong when retrieving data. It can happen if * the request parameters are invalid, if the data is unavailable, or permission to perform some action is denied. In * all cases, the cause of the failure can be accessed using {@link GDClientException#getCause()}. *

* Some methods require the client to be authenticated. It can be done by calling * {@link #withAuthentication(long, long, String)} or {@link #login(String, String)}. Any attempt to use a method that * requires authentication without being authenticated will immediately throw {@link IllegalStateException} at assembly * time. */ public final class GDClient { private final GDRouter router; private final GDCache cache; private final String uniqueDeviceId; private final AuthenticationInfo auth; private final Collection followedAccountIds; private GDClient(GDRouter router, GDCache cache, String uniqueDeviceId, @Nullable AuthenticationInfo auth, Collection followedAccountIds) { this.router = router; this.cache = cache; this.uniqueDeviceId = uniqueDeviceId; this.auth = auth; this.followedAccountIds = followedAccountIds; } /** * Creates a new {@link GDClient} with the {@link GDRouter#defaultRouter() default router}, a * {@link GDCache#disabled() disabled cache}, a {@link UUID#randomUUID() random UUID}, an empty set of followed * account IDs and without authentication. To customize it further, chain one or more * with* methods after this call. * * @return a new {@link GDClient} */ public static GDClient create() { return new GDClient(GDRouter.defaultRouter(), GDCache.disabled(), UUID.randomUUID().toString(), null, Set.of()); } private static Mono validatePositiveInteger(Mono source) { return source.doOnNext(result -> { if (result < 0) { throw new ActionFailedException("" + result, "Action failed"); } }).then(); } private void requireAuthentication() { if (auth == null) { throw new IllegalStateException("Client must be authenticated to perform this request"); } } private Map authParams() { Objects.requireNonNull(auth); return Map.of("accountID", "" + auth.accountId, "gjp2", auth.gjp2()); } /** * Creates a new {@link GDClient} derived from this one, but with the specified router. * * @param router the router to set * @return a new {@link GDClient} */ public GDClient withRouter(GDRouter router) { Objects.requireNonNull(router); return new GDClient(router, cache, uniqueDeviceId, auth, followedAccountIds); } /** * Creates a new {@link GDClient} derived from this one, but with the specified cache. * * @param cache the cache to set * @return a new {@link GDClient} */ public GDClient withCache(GDCache cache) { Objects.requireNonNull(cache); return new GDClient(router, cache, uniqueDeviceId, auth, followedAccountIds); } /** * Creates a new {@link GDClient} derived from this one, but with the cache disabled. It is a shorthand for: *

     *     withCache(GDCache.disabled())
     * 
* * @return a new {@link GDClient} */ public GDClient withCacheDisabled() { return new GDClient(router, GDCache.disabled(), uniqueDeviceId, auth, followedAccountIds); } /** * Creates a new {@link GDClient} derived from this one, but with the cache in write-only mode. When a cache is * write-only, requests will always fail to find data in cache (like with {@link #withCacheDisabled()}) with the * difference that it will properly save the result of the new request in cache. It can be useful when you want to * forcefully refresh some data in cache. * * @return a new {@link GDClient} */ public GDClient withWriteOnlyCache() { return new GDClient(router, cache.writeOnly(), uniqueDeviceId, auth, followedAccountIds); } /** * Creates a new {@link GDClient} derived from this one, but with the specified unique device ID. This ID is used by * some requests to uniquely identify the device used to play Geometry Dash. * * @param uniqueDeviceId the unique device ID to set * @return a new {@link GDClient} */ public GDClient withUniqueDeviceId(String uniqueDeviceId) { Objects.requireNonNull(uniqueDeviceId); return new GDClient(router, cache, uniqueDeviceId, auth, followedAccountIds); } /** * Creates a new {@link GDClient} derived from this one, but with the specified authentication information. This * method allows to manually supply authentication details without calling {@link #login(String, String)}, which * means the credentials will not be validated. But it can be useful to get an authenticated client without having * to perform any request. * * @param playerId the player ID * @param accountId the account ID * @param password the account password (in plain text) * @return a new {@link GDClient} */ public GDClient withAuthentication(long playerId, long accountId, String password) { Objects.requireNonNull(password); return new GDClient(router, cache, uniqueDeviceId, new AuthenticationInfo(playerId, accountId, Optional.empty(), password), followedAccountIds); } /** * Creates a new {@link GDClient} derived from this one, but with the specified collection of followed account IDs. * It will be used when browsing levels with {@link LevelSearchMode#FOLLOWED} via * {@link #searchLevels(LevelSearchMode, String, LevelSearchFilter, int)}. *

* This method makes a defensive copy of the given collection to guarantee the immutability of the resulting * client. * * @param followedAccountIds the followed account IDs to set * @return a new {@link GDClient} */ public GDClient withFollowedAccountIds(Collection followedAccountIds) { Objects.requireNonNull(followedAccountIds); return new GDClient(router, cache, uniqueDeviceId, auth, Set.copyOf(followedAccountIds)); } /** * Gets the router used by this client. * * @return the router */ public GDRouter getRouter() { return router; } /** * Gets the cache used by this client. * * @return the cache */ public GDCache getCache() { return cache; } /** * Gets the unique device ID used by this client. * * @return the unique device ID */ public String getUniqueDeviceId() { return uniqueDeviceId; } /** * Gets whether this client is authenticated. It will be true if and only if the client comes from a previous call * of {@link #login(String, String)} or {@link #withAuthentication(long, long, String)}. * * @return whether the client is authenticated */ public boolean isAuthenticated() { return auth != null; } /** * Gets the authentication info of this client. This includes the player ID, the account ID, the account password, * as well as the account username if the authentication happened via {@link #login(String, String)}. * * @return an {@link Optional} containing an {@link AuthenticationInfo} if present */ public Optional getAuthenticationInfo() { return Optional.ofNullable(auth); } /** * Gets the followed account IDs. The returned collection is unmodifiable. * * @return the followed account IDs */ public Collection getFollowedAccountIds() { return followedAccountIds; } /** * Sends a login request to Geometry Dash servers with the given username and password. If successful, it will * return a new client instance carrying authentication details. * * @param username the username of the GD account * @param password the password of the GD account * @return a Mono emitting a new {@link GDClient} capable of executing requests requiring authentication. A * {@link GDClientException} will be emitted if an error occurs */ public Mono login(String username, String password) { Objects.requireNonNull(username); Objects.requireNonNull(password); return Mono.defer(() -> GDRequest.of(LOGIN_GJ_ACCOUNT) .addParameter("userName", username) .addParameter("gjp2", encodeGjp2(password)) .addParameter("udid", uniqueDeviceId) .addParameter("secret", "Wmfv3899gc9") // Overrides the default one .execute(cache, router) .deserialize(loginResponse()) .map(function((accountId, playerId) -> withAuthentication(playerId, accountId, password)))); } /** * Finds a level by its ID. It is a shorthand for: *

     *     searchLevels(LevelSearchMode.SEARCH, "" + levelId, null, 0).next()
     * 
* * @param levelId the level ID * @return a Mono emitting a {@link GDLevel} corresponding to the level found. A {@link GDClientException} will be * emitted if an error occurs. */ public Mono findLevelById(long levelId) { return searchLevels(LevelSearchMode.SEARCH, "" + levelId, null, 0).next(); } /** * Finds levels by a specific user. It is a shorthand for: *
     *     searchLevels(LevelSearchMode.BY_USER, "" + playerId, null, page)
     * 
* * @param playerId the player ID of the user * @param page the page to load, the first one being 0 * @return a Flux emitting all {@link GDLevel}s found on the selected page. A {@link GDClientException} will be * emitted if an error occurs. */ public Flux findLevelsByUser(long playerId, int page) { return searchLevels(LevelSearchMode.BY_USER, "" + playerId, null, page); } /** * Searches for levels in Geometry Dash. * * @param mode the browsing mode, which can impact how levels are sorted, or how the query is interpreted * @param query if mode is {@link LevelSearchMode#SEARCH}, represents the search query. If mode is * {@link LevelSearchMode#BY_USER}, represents the player ID of the user. If mode is any other mode, * it will be ignored and can be set to null. * @param filter the search filter to apply. Can be null to disable filtering * @param page the page to load, the first one being 0 * @return a Flux emitting all {@link GDLevel}s found on the selected page. A {@link GDClientException} will be * emitted if an error occurs. */ public Flux searchLevels(LevelSearchMode mode, @Nullable String query, @Nullable LevelSearchFilter filter, int page) { return search(mode, query, filter, page, false).cast(GDLevel.class); } /** * Searches for lists in Geometry Dash. It works very similarly to * {@link #searchLevels(LevelSearchMode, String, LevelSearchFilter, int)}, with the only difference that only a * subset of the {@link LevelSearchFilter} attributes will actually have any effect. * * @param mode the browsing mode, which can impact how levels are sorted, or how the query is interpreted * @param query if mode is {@link LevelSearchMode#SEARCH}, represents the search query. If mode is * {@link LevelSearchMode#BY_USER}, represents the player ID of the user. If mode is any other mode, * it will be ignored and can be set to null. * @param filter the search filter to apply. Can be null to disable filtering. Some filters that are * not applicable to lists, such as completed levels, lengths or two player may not have any effect * @param page the page to load, the first one being 0 * @return a Flux emitting all {@link GDList}s found on the selected page. A {@link GDClientException} will be * emitted if an error occurs. */ public Flux searchLists(LevelSearchMode mode, @Nullable String query, @Nullable LevelSearchFilter filter, int page) { return search(mode, query, filter, page, true).cast(GDList.class); } private Flux search(LevelSearchMode mode, @Nullable String query, @Nullable LevelSearchFilter filter, int page, boolean isList) { Objects.requireNonNull(mode); return Flux.defer(() -> { final var request = GDRequest.of(isList ? GET_GJ_LEVEL_LISTS : GET_GJ_LEVELS_21) .addParameters(commonParams()) .addParameters(Objects.requireNonNullElse(filter, LevelSearchFilter.create()).toMap()) .addParameter("page", page) .addParameter("type", mode.getType()) .addParameter("str", Objects.requireNonNullElse(query, "")); if (mode == LevelSearchMode.FOLLOWED) { request.addParameter("followed", followedAccountIds.stream() .map(String::valueOf) .collect(Collectors.joining(","))); } if (isList) { return request.execute(cache, router) .deserialize(listSearchResponse()) .flatMapMany(Flux::fromIterable); } else { return request.execute(cache, router) .deserialize(levelSearchResponse()) .flatMapMany(Flux::fromIterable); } }); } /** * Finds all levels in a list. The result is usually not paginated, as such it may return more than 10 levels at * once. It is a shorthand for: *
     *     searchLevels(LevelSearchMode.LIST_CONTENT, "" + listId, null, 0)
     * 
* * @param listId the ID of the list * @return a Flux emitting all {@link GDLevel}s found in the list. A {@link GDClientException} will be emitted if an * error occurs. */ public Flux findLevelsInList(long listId) { return searchLevels(LevelSearchMode.LIST_CONTENT, "" + listId, null, 0); } /** * Downloads full data of a Geometry Dash level. * * @param levelId the ID of the level to download. Can be -1 to download the current Daily level, or * -2 to download the current Weekly demon * @return a Mono emitting the {@link GDLevelDownload} corresponding to the level. A {@link GDClientException} will * be emitted if an error occurs. */ public Mono downloadLevel(long levelId) { return Mono.defer(() -> GDRequest.of(DOWNLOAD_GJ_LEVEL_22) .addParameters(commonParams()) .addParameter("levelID", levelId) .execute(cache, router) .deserialize(levelDownloadResponse())); } /** * Downloads full data of the current Daily level. It is a shorthand for: *
     *     downloadLevel(-1)
     * 
* * @return a Mono emitting the {@link GDLevelDownload} corresponding to the level. A {@link GDClientException} will * be emitted if an error occurs. */ public Mono downloadDailyLevel() { return downloadLevel(-1); } /** * Downloads full data of the current Weekly demon. It is a shorthand for: *
     *     downloadLevel(-2)
     * 
* * @return a Mono emitting the {@link GDLevelDownload} corresponding to the level. A {@link GDClientException} will * be emitted if an error occurs. */ public Mono downloadWeeklyDemon() { return downloadLevel(-2); } /** * Downloads full data of the current Event level. It is a shorthand for: *
     *     downloadLevel(-3)
     * 
* * @return a Mono emitting the {@link GDLevelDownload} corresponding to the level. A {@link GDClientException} will * be emitted if an error occurs. */ public Mono downloadEventLevel() { return downloadLevel(-3); } private Mono getDailyInfo(int type, @Nullable SecretRewardChkGenerator chkGenerator) { return Mono.defer(() -> { final var request = GDRequest.of(GET_GJ_DAILY_LEVEL) .addParameters(commonParams()) .addParameter("type", type); if (chkGenerator != null) { request.addParameter("chk", chkGenerator.get()); } return request.execute(cache, router).deserialize(dailyInfoResponse()); }); } /** * Requests information on the current Daily level, such as its number or the time left before the next one. * * @return a Mono emitting the {@link GDDailyInfo} of the Daily level. A {@link GDClientException} will be emitted * if an error occurs. */ public Mono getDailyLevelInfo() { return getDailyInfo(0, null); } /** * Requests information on the current Weekly demon, such as its number or the time left before the next one. * * @return a Mono emitting the {@link GDDailyInfo} of the Weekly demon. A {@link GDClientException} will be emitted * if an error occurs. */ public Mono getWeeklyDemonInfo() { return getDailyInfo(1, null); } /** * Requests information on the current Event level, such as its number or the time left before the next one. Rewards * CHK is generated at random. * * @return a Mono emitting the {@link GDDailyInfo} of the Event level. A {@link GDClientException} will be emitted * if an error occurs. */ public Mono getEventLevelInfo() { return getDailyInfo(2, SecretRewardChkGenerator.random()); } /** * Requests information on the current Event level, such as its number or the time left before the next one. * * @param chkGenerator customize the way CHK is generated * @return a Mono emitting the {@link GDDailyInfo} of the Event level. A {@link GDClientException} will be emitted * if an error occurs. */ public Mono getEventLevelInfo(SecretRewardChkGenerator chkGenerator) { return getDailyInfo(2, Objects.requireNonNull(chkGenerator)); } /** * Requests the profile of a user with the specified account ID. * * @param accountId the account ID of the user * @return a Mono emitting the requested {@link GDUserProfile}. A {@link GDClientException} will be emitted if an * error occurs. */ public Mono getUserProfile(long accountId) { return Mono.defer(() -> GDRequest.of(GET_GJ_USER_INFO_20) .addParameters(commonParams()) .addParameter("targetAccountID", accountId) .execute(cache, router) .deserialize(userProfileResponse())); } /** * Searches for users in Geometry Dash. * * @param query the query string. Can be a username or a player ID * @param page the page to load, the first one being 0 * @return a Flux emitting all {@link GDUserStats} found on the selected page. A {@link GDClientException} will be * emitted if an error occurs. */ public Flux searchUsers(String query, int page) { Objects.requireNonNull(query); return Flux.defer(() -> GDRequest.of(GET_GJ_USERS_20) .addParameters(commonParams()) .addParameter("str", query) .addParameter("page", page) .execute(cache, router) .deserialize(userStatsListResponse()) .flatMapMany(Flux::fromIterable)); } /** * Requests information on a song from Newgrounds. * * @param songId the ID of the song * @return a Mono emitting the requested {@link GDSong}. A {@link GDClientException} will be emitted if an error * occurs. */ public Mono getSongInfo(long songId) { return Mono.defer(() -> GDRequest.of(GET_GJ_SONG_INFO) .addParameter("songID", songId) .addParameter("secret", SECRET) .execute(cache, router) .deserialize(songInfoResponse())); } /** * Retrieves comments for a specific level. * * @param levelId the ID of the level to get comments for * @param sorting whether to sort by new or most liked * @param page the page to load, the first one being 0 * @param count the number of comments per page to get * @return a Flux emitting all {@link GDComment}s found on the selected page. A {@link GDClientException} will be * emitted if an error occurs. */ public Flux getCommentsForLevel(long levelId, CommentSortMode sorting, int page, int count) { Objects.requireNonNull(sorting); return Flux.defer(() -> GDRequest.of(GET_GJ_COMMENTS_21) .addParameters(commonParams()) .addParameter("levelID", levelId) .addParameter("total", 0) .addParameter("count", count) .addParameter("page", page) .addParameter("mode", sorting.ordinal()) .execute(cache, router) .deserialize(commentsResponse()) .flatMapMany(Flux::fromIterable)); } /** * Requests the leaderboard of the specified type. Note that choosing {@link LeaderboardType#FRIENDS} as type * requires this client to be authenticated. * * @param type the type of leaderboard to get (top players, top creators...) * @param count the number of users to get * @return a Flux emitting the {@link GDUserStats} corresponding to leaderboard entries, sorted by rank. A * {@link GDClientException} will be emitted if an error occurs. * @throws IllegalStateException if {@link LeaderboardType#FRIENDS} is selected and the client is not * authenticated. */ public Flux getLeaderboard(LeaderboardType type, int count) { Objects.requireNonNull(type); if (type == LeaderboardType.FRIENDS) { requireAuthentication(); } return Flux.defer(() -> { final var request = GDRequest.of(GET_GJ_SCORES_20); if (type == LeaderboardType.FRIENDS) { request.addParameters(authParams()); } return request.addParameters(commonParams()) .addParameter("type", type.name().toLowerCase()) .addParameter("count", count) .execute(cache, router) .deserialize(userStatsListResponse()) .flatMapMany(Flux::fromIterable) .sort(Comparator.comparing(GDUserStats::leaderboardRank, Comparator.comparingInt(Optional::orElseThrow))); }); } /** * Retrieves all private messages of the account this client is logged on. This method requires this client to be * authenticated. * * @param page the page to load, the first one being 0 * @return a Flux emitting all {@link GDPrivateMessage}s found on the selected page. A {@link GDClientException} * will be emitted if an error occurs. * @throws IllegalStateException if this client is not authenticated */ public Flux getPrivateMessages(int page) { requireAuthentication(); return Flux.defer(() -> GDRequest.of(GET_GJ_MESSAGES_20) .addParameters(authParams()) .addParameters(commonParams()) .addParameter("page", page) .addParameter("total", 0) .execute(cache, router) .deserialize(privateMessagesResponse()) .flatMapMany(Flux::fromIterable)); } /** * Downloads the content of a specific private message. This method requires this client to be authenticated. * * @param messageId the ID of the message to download * @return a Mono emitting the {@link GDPrivateMessageDownload}. A {@link GDClientException} will be emitted if an * error occurs. * @throws IllegalStateException if this client is not authenticated */ public Mono downloadPrivateMessage(long messageId) { requireAuthentication(); return Mono.defer(() -> GDRequest.of(DOWNLOAD_GJ_MESSAGE_20) .addParameters(authParams()) .addParameters(commonParams()) .addParameter("messageID", messageId) .execute(cache, router) .deserialize(privateMessageDownloadResponse())); } /** * Sends a private message in-game to the specified recipient. This method requires this client to be * authenticated. * * @param recipientAccountId the account ID of the recipient * @param subject the message subject * @param body the message body * @return a Mono completing when the operation is successful. A {@link GDClientException} will be emitted if an * error occurs. * @throws IllegalStateException if this client is not authenticated */ public Mono sendPrivateMessage(long recipientAccountId, String subject, String body) { Objects.requireNonNull(subject); Objects.requireNonNull(body); requireAuthentication(); return Mono.defer(() -> GDRequest.of(UPLOAD_GJ_MESSAGE_20) .addParameters(authParams()) .addParameters(commonParams()) .addParameter("toAccountID", recipientAccountId) .addParameter("subject", b64Encode(subject)) .addParameter("body", encodePrivateMessageBody(body)) .execute(cache, router) .deserialize(Integer::parseInt) .transform(GDClient::validatePositiveInteger)); } /** * Sends a star rating vote to the target level. This method requires this client to be authenticated. * * @param levelId the ID of the target level * @param stars the number of stars to vote for * @return a Mono completing when the operation is successful. A {@link GDClientException} will be emitted if an * error occurs. * @throws IllegalStateException if this client is not authenticated */ public Mono voteLevelStars(long levelId, int stars) { requireAuthentication(); return Mono.defer(() -> { final var rs = randomString(10); Objects.requireNonNull(auth); return GDRequest.of(RATE_GJ_STARS_211) .addParameters(authParams()) .addParameters(commonParams()) .addParameter("udid", uniqueDeviceId) .addParameter("uuid", auth.playerId) .addParameter("levelID", levelId) .addParameter("stars", stars) .addParameter("rs", rs) .addParameter("chk", encodeChk(levelId, stars, rs, auth.accountId, uniqueDeviceId, auth.playerId, "ysg6pUrtjn0J")) .execute(cache, router) .deserialize(Integer::parseInt) .transform(GDClient::validatePositiveInteger); }); } /** * Sends a demon difficulty vote to the target level. This method requires this client to be authenticated. * * @param levelId the ID of the target level * @param demonDifficulty the demon difficulty to vote for * @return a Mono completing when the operation is successful. A {@link GDClientException} will be emitted if an * error occurs. * @throws IllegalStateException if this client is not authenticated */ public Mono voteLevelDemonDifficulty(long levelId, DemonDifficulty demonDifficulty) { Objects.requireNonNull(demonDifficulty); requireAuthentication(); return Mono.defer(() -> GDRequest.of(RATE_GJ_DEMON_21) .addParameters(authParams()) .addParameters(commonParams()) .addParameter("levelID", levelId) .addParameter("rating", demonDifficulty.ordinal() + 1) .addParameter("secret", "Wmfp3879gc3") // Overrides the default one .execute(cache, router) .deserialize(Integer::parseInt) .transform(GDClient::validatePositiveInteger)); } /** * Gets the friends list of the account this client is logged on. This method requires this client to be * authenticated. * * @return a Flux emitting all friends as {@link GDUser} instances. A {@link GDClientException} will be emitted if * an error occurs. * @throws IllegalStateException if this client is not authenticated */ public Flux getFriends() { return getUserList(0); } /** * Gets the list of blocked users of the account this client is logged on. This method requires this client to be * authenticated. * * @return a Flux emitting all blocked users as {@link GDUser} instances. A {@link GDClientException} will be * emitted if an error occurs. * @throws IllegalStateException if this client is not authenticated */ public Flux getBlockedUsers() { return getUserList(1); } private Flux getUserList(int type) { requireAuthentication(); return Flux.defer(() -> GDRequest.of(GET_GJ_USER_LIST_20) .addParameters(authParams()) .addParameters(commonParams()) .addParameter("type", type) .execute(cache, router) .deserialize(userListResponse()) .flatMapMany(Flux::fromIterable)); } /** * Requests to block a user in Geometry Dash. This method requires this client to be authenticated. * * @param targetAccountId the account ID of the user to block * @return a Mono completing when the operation is successful. A {@link GDClientException} will be emitted if an * error occurs. * @throws IllegalStateException if this client is not authenticated */ public Mono blockUser(long targetAccountId) { return blockUnblockRequest(targetAccountId, BLOCK_GJ_USER_20); } /** * Requests to unblock a user in Geometry Dash. This method requires this client to be authenticated. * * @param targetAccountId the account ID of the user to unblock * @return a Mono completing when the operation is successful. A {@link GDClientException} will be emitted if an * error occurs. * @throws IllegalStateException if this client is not authenticated */ public Mono unblockUser(long targetAccountId) { return blockUnblockRequest(targetAccountId, UNBLOCK_GJ_USER_20); } private Mono blockUnblockRequest(long targetAccountId, String uri) { requireAuthentication(); return Mono.defer(() -> GDRequest.of(uri) .addParameters(authParams()) .addParameters(commonParams()) .addParameter("targetAccountID", targetAccountId) .execute(cache, router) .deserialize(Integer::parseInt) .transform(GDClient::validatePositiveInteger)); } /** * Contains information to authenticate a user for requests. * * @param playerId The player ID of this authenticated client. * @param accountId The account ID of this authenticated client. * @param username The username of this authenticated client. This information is only available if the * authentication happened via {@link #login(String, String)}, it won't be available if done via * {@link #withAuthentication(long, long, String)}. * @param password The password of this authenticated client. Be careful when calling this method as the value * returned is sensitive data. */ public record AuthenticationInfo(long playerId, long accountId, Optional username, String password) { /** * Gets the gjp2 view of the stored password. * * @return the gjp2 string */ public String gjp2() { return encodeGjp2(password); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy