Skip to content

Commit

Permalink
Add Event levels and new icons (2.207)
Browse files Browse the repository at this point in the history
  • Loading branch information
Alex1304 committed Nov 12, 2024
1 parent a3a9de8 commit 01bc4e5
Show file tree
Hide file tree
Showing 24 changed files with 538 additions and 32 deletions.
8 changes: 0 additions & 8 deletions .idea/modules.xml

This file was deleted.

55 changes: 47 additions & 8 deletions client/src/main/java/jdash/client/GDClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -403,12 +403,29 @@ public Mono<GDLevelDownload> downloadWeeklyDemon() {
return downloadLevel(-2);
}

private Mono<GDDailyInfo> getTimelyInfo(int weekly) {
return Mono.defer(() -> GDRequest.of(GET_GJ_DAILY_LEVEL)
.addParameters(commonParams())
.addParameter("weekly", weekly)
.execute(cache, router)
.deserialize(dailyInfoResponse()));
/**
* Downloads full data of the current Event level. It is a shorthand for:
* <pre>
* downloadLevel(-3)
* </pre>
*
* @return a Mono emitting the {@link GDLevelDownload} corresponding to the level. A {@link GDClientException} will
* be emitted if an error occurs.
*/
public Mono<GDLevelDownload> downloadEventLevel() {
return downloadLevel(-3);
}

private Mono<GDDailyInfo> 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());
});
}

/**
Expand All @@ -418,7 +435,7 @@ private Mono<GDDailyInfo> getTimelyInfo(int weekly) {
* if an error occurs.
*/
public Mono<GDDailyInfo> getDailyLevelInfo() {
return getTimelyInfo(0);
return getDailyInfo(0, null);
}

/**
Expand All @@ -428,7 +445,29 @@ public Mono<GDDailyInfo> getDailyLevelInfo() {
* if an error occurs.
*/
public Mono<GDDailyInfo> getWeeklyDemonInfo() {
return getTimelyInfo(1);
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<GDDailyInfo> 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<GDDailyInfo> getEventLevelInfo(SecretRewardChkGenerator chkGenerator) {
return getDailyInfo(2, Objects.requireNonNull(chkGenerator));
}

/**
Expand Down
2 changes: 1 addition & 1 deletion client/src/main/java/jdash/client/request/GDRequests.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public final class GDRequests {

/* Params */
public static final String GAME_VERSION = "22";
public static final String BINARY_VERSION = "42";
public static final String BINARY_VERSION = "45";
public static final String SECRET = "Wmfd2893gb7";

private GDRequests() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public GDRouterImpl(RequestLimiter limiter, Duration timeout, String baseUrl, Sc
.headers(h -> {
h.add("Content-Type", "application/x-www-form-urlencoded");
h.add("User-Agent", "");
h.add("Cookie", "gd=1;");
});
if (baseUrl.startsWith("https://")) {
httpClient = httpClient.secure();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class DailyInfoResponseDeserializer implements Function<String, GDDailyInfo> {

@Override
public GDDailyInfo apply(String response) {
if (!response.matches("\\d+\\|\\d+")) {
if (!response.matches("\\d+\\|\\d+(|.*)*")) {
throw new ActionFailedException(response, "Failed to load daily level info");
}
final var tokens = response.split("\\|");
Expand Down
9 changes: 8 additions & 1 deletion client/src/test/java/jdash/client/GDClientTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public final class GDClientTest {
/* Not part of unit tests, this is only to test the real router implementation */
public static void main(String[] args) {
final var client = GDClient.create();
System.out.println(client.getSongInfo(10010067).block());
System.out.println(client.getEventLevelInfo().block());
}

@BeforeEach
Expand Down Expand Up @@ -329,6 +329,13 @@ public void getWeeklyDemonInfoTest() {
assertEquals(expected, actual);
}

@Test
public void getEventLevelTest() {
final var expected = new GDDailyInfo(1, Duration.ofSeconds(10));
final var actual = client.getEventLevelInfo(SecretRewardChkGenerator.fixed("ALEX1", "123456")).block();
assertEquals(expected, actual);
}

@Test
public void getCommentsForLevelTest() {
final var expectedFirstComment = new GDComment(
Expand Down
8 changes: 6 additions & 2 deletions client/src/test/java/jdash/client/GDRouterMock.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,14 @@ public final class GDRouterMock implements GDRouter {
.addParameter("levelID", 10565740), "downloadLevel"),
Map.entry(GDRequest.of(GET_GJ_DAILY_LEVEL)
.addParameters(commonParams())
.addParameter("weekly", 0), "getDailyLevelInfo"),
.addParameter("type", 0), "getDailyLevelInfo"),
Map.entry(GDRequest.of(GET_GJ_DAILY_LEVEL)
.addParameters(commonParams())
.addParameter("weekly", 1), "getWeeklyDemonInfo"),
.addParameter("type", 1), "getWeeklyDemonInfo"),
Map.entry(GDRequest.of(GET_GJ_DAILY_LEVEL)
.addParameters(commonParams())
.addParameter("type", 2)
.addParameter("chk", "ALEX1BAsCDAcD"), "getEventLevelInfo"),
Map.entry(GDRequest.of(GET_GJ_COMMENTS_21)
.addParameters(commonParams())
.addParameter("levelID", 10565740)
Expand Down
1 change: 1 addition & 0 deletions client/src/test/resources/getEventLevelInfo.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
200001|10|SXDEsWE8DW2YPCAMLBgAPCwoCDwoLAB4BCR0OHgQVAAseBA==|2add3c41e4f44884d7200dd98c57feb1b07296df
26 changes: 18 additions & 8 deletions common/src/main/java/jdash/common/RobTopsWeakEncryption.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,31 @@
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Random;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import static jdash.common.internal.InternalUtils.b64Decode;
import static jdash.common.internal.InternalUtils.b64Encode;
import static jdash.common.internal.InternalUtils.*;

/**
* Provides methods to encode and decode some data such as private message body, level passcodes and account passwords.
*/
public final class RobTopsWeakEncryption {

private static final XORCipher GD_MESSAGE_BODY_XOR_CIPHER = new XORCipher("14251");
private static final XORCipher LEVEL_PASSCODE_XOR_CIPHER = new XORCipher("26364");
private static final XORCipher ACCOUNT_PASSWORD_XOR_CIPHER = new XORCipher("37526");
private static final XORCipher CHK_XOR_CIPHER = new XORCipher("58281");
static final XORCipher GD_MESSAGE_BODY_XOR_CIPHER = new XORCipher("14251");
static final XORCipher LEVEL_PASSCODE_XOR_CIPHER = new XORCipher("26364");
static final XORCipher ACCOUNT_PASSWORD_XOR_CIPHER = new XORCipher("37526");
static final XORCipher CHK_XOR_CIPHER = new XORCipher("58281");
static final XORCipher CHK_REWARDS_XOR_CIPHER = new XORCipher("59182");

private RobTopsWeakEncryption() {
}

private static String decode(String str, XORCipher algorithm) {
static String decode(String str, XORCipher algorithm) {
return algorithm.cipher(b64Decode(str));
}

private static String encode(String str, XORCipher algorithm) {
static String encode(String str, XORCipher algorithm) {
return b64Encode(algorithm.cipher(str));
}

Expand Down Expand Up @@ -103,6 +104,15 @@ public static String encodeChk(Object... params) {
.collect(Collectors.joining())), CHK_XOR_CIPHER);
}

/**
* Generates a valid CHK to get secret rewards (chests or Event level rewards).
*
* @return the encoded string
*/
public static String generateSecretRewardChk() {
return randomString(5) + encode("" + (new Random().nextInt(900000) + 100000), CHK_REWARDS_XOR_CIPHER);
}

/**
* Encodes the given password into a string that can be passed as the gjp2 parameter for authenticated requests.
*
Expand Down
39 changes: 39 additions & 0 deletions common/src/main/java/jdash/common/SecretRewardChkGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package jdash.common;

import java.util.function.Supplier;

import static jdash.common.RobTopsWeakEncryption.CHK_REWARDS_XOR_CIPHER;
import static jdash.common.RobTopsWeakEncryption.encode;

/**
* Function that generates a client CHK for secret rewards. Use {@link #random()} or {@link #fixed(String, String)} to
* customize
* CHK generation behavior.
*/
@FunctionalInterface
public interface SecretRewardChkGenerator extends Supplier<String> {

/**
* Function that generates a CHK at random, using a key representing a random number between 100_000 (inclusive) and
* 900_000 (exclusive).
*
* @return a random {@link SecretRewardChkGenerator}
*/
static SecretRewardChkGenerator random() {
return RobTopsWeakEncryption::generateSecretRewardChk;
}

/**
* Function that generates a CHK with a fixed input. Use this when you need deterministic behavior.
*
* @param prefix a string that will be prepended to the CHK. Only the first 5 characters are taken, and will be
* padded with 'X' if provided prefix is less than 5 characters.
* @param key the key encoded in the CHK. It is recommended to be 6 characters or highers for the server to
* accept it.
* @return a {@link SecretRewardChkGenerator} with fixed input
*/
static SecretRewardChkGenerator fixed(String prefix, String key) {
return () -> String.format("%5s", prefix.substring(0, 5)).replace(' ', 'X') +
encode(key, CHK_REWARDS_XOR_CIPHER);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package jdash.common;

import jdash.common.internal.InternalUtils;
import org.junit.jupiter.api.Test;

import static jdash.common.RobTopsWeakEncryption.*;
Expand Down Expand Up @@ -39,8 +40,12 @@ public void encodePrivateMessageBodyTest() {

@Test
public void encodeChkTest() {
System.out.println(new XORCipher("59482").cipher(InternalUtils.b64Decode("BgkBCgYNCAEIAgUJ")));

assertEquals("BFxRDAIFDlZeV1cKAAFVBwoADANRXQoKAAZbAAkAVA9UCwBTCgpeCQ==",
encodeChk(69101896, 2, "0ej5u5v6Nl", 98006, "00000000-24d6-c259-ffff-ffffddb46189", 4063664,
"ysg6pUrtjn0J"));
encodeChk(69101896, 2, "0ej5u5v6Nl", 98006,
"00000000-24d6-c259-ffff" + "-ffffddb46189", 4063664, "ysg6pUrtjn0J"));
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package jdash.common;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;

public final class SecretRewardChkGeneratorTest {

@Test
public void testFixedChkGenerator() {
assertEquals("ALEX1BAsCDAcD", SecretRewardChkGenerator.fixed("ALEX1", "123456").get());
}
}
11 changes: 11 additions & 0 deletions events/src/main/java/jdash/events/object/EventLevelChange.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package jdash.events.object;

import jdash.common.entity.GDDailyInfo;

/**
* Event emitted when the Event level changes.
*
* @param before The Event level info before the change.
* @param after The Event level info after the change.
*/
public record EventLevelChange(GDDailyInfo before, GDDailyInfo after) {}
25 changes: 25 additions & 0 deletions events/src/main/java/jdash/events/producer/EventLevelProducer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package jdash.events.producer;

import jdash.client.GDClient;
import jdash.common.entity.GDDailyInfo;
import jdash.events.object.EventLevelChange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

class EventLevelProducer implements GDEventProducer {

private GDDailyInfo previousEventLevel;

@Override
public Flux<Object> produce(GDClient client) {
return client.getEventLevelInfo()
.flatMapMany(info -> {
final var previousEventLevel = this.previousEventLevel;
this.previousEventLevel = info;
return Mono.justOrEmpty(previousEventLevel)
.filter(p -> p.number() < info.number())
.map(p -> new EventLevelChange(p, info));
});
}

}
10 changes: 10 additions & 0 deletions events/src/main/java/jdash/events/producer/GDEventProducer.java
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,16 @@ static GDEventProducer dailyLevels() {
return new DailyEventProducer();
}

/**
* An event producer that requests information on the current Event level on each iteration in
* order to detect when the Event level changes.
*
* @return a {@link GDEventProducer}
*/
static GDEventProducer eventLevels() {
return new EventLevelProducer();
}

/**
* Emits zero, one or more events by making requests using the given {@link GDClient}. The results of those requests
* can be processed and memorized between two calls of this method in order to generate relevant events, for example
Expand Down
Loading

0 comments on commit 01bc4e5

Please sign in to comment.