Skip to content

Commit

Permalink
Support 2.206: NCS songs, binaryVersion 42
Browse files Browse the repository at this point in the history
  • Loading branch information
Alex1304 committed Jun 15, 2024
1 parent 032b007 commit 98e7e33
Show file tree
Hide file tree
Showing 16 changed files with 923 additions and 624 deletions.
1 change: 1 addition & 0 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions client/src/main/java/jdash/client/request/GDRequests.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
public final class GDRequests {

/* Routes */
public static final String BASE_URL = "http://www.boomlings.com/database";
public static final String BASE_URL = "https://www.boomlings.com/database";
public static final String GET_GJ_USER_INFO_20 = "/getGJUserInfo20.php";
public static final String GET_GJ_USERS_20 = "/getGJUsers20.php";
public static final String GET_GJ_USER_LIST_20 = "/getGJUserList20.php";
Expand All @@ -31,7 +31,7 @@ public final class GDRequests {

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

private GDRequests() {
Expand Down
22 changes: 12 additions & 10 deletions client/src/test/java/jdash/client/GDClientTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import java.util.Optional;
import java.util.stream.Collectors;

import static jdash.common.internal.InternalUtils.urlDecode;
import static org.junit.jupiter.api.Assertions.*;

public final class GDClientTest {
Expand All @@ -24,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.downloadLevel(104043964).block());
System.out.println(client.getSongInfo(10007009).block());
}

@BeforeEach
Expand Down Expand Up @@ -103,13 +104,14 @@ public void findLevelByIdTest() {
Optional.of(7679228L),
0,
Optional.of(467339L),
Optional.of(new GDSong(
Optional.of(GDSong.fromNewgrounds(
467339,
"At the Speed of Light",
52,
"Dimrain47",
Optional.of("9.56"),
Optional.ofNullable(InternalUtils.urlDecode("http%3A%2F%2Faudio.ngfiles" +
".com%2F467000%2F467339_At_the_Speed_of_Light_FINA.mp3")))),
"9.56",
true,
urlDecode("http%3A%2F%2Faudio.ngfiles.com%2F467000%2F467339_At_the_Speed_of_Light_FINA.mp3"))),
Optional.of("Riot"),
Optional.of(37415L),
false,
Expand Down Expand Up @@ -251,14 +253,14 @@ public void searchUsersTest() {

@Test
public void getSongInfoTest() {
final var expected = new GDSong(
final var expected = GDSong.fromNewgrounds(
844899,
"~:Space soup:~",
28916,
"lchavasse",
Optional.of("8.79"),
Optional.ofNullable(InternalUtils.urlDecode("https%3A%2F%2Faudio.ngfiles" +
".com%2F844000%2F844899_Space-soup" +
".mp3%3Ff1548488779"))
"8.79",
false,
urlDecode("https%3A%2F%2Faudio.ngfiles.com%2F844000%2F844899_Space-soup.mp3%3Ff1548488779")
);
final var actual = client.getSongInfo(844899).block();
assertEquals(expected, actual);
Expand Down
4 changes: 4 additions & 0 deletions common/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@
</build>

<dependencies>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
Expand Down
15 changes: 15 additions & 0 deletions common/src/main/java/jdash/common/MusicLibraryProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package jdash.common;

import java.util.Objects;

/**
* Enumerates the possible song providers for the music library.
*/
public enum MusicLibraryProvider {
OTHER,
NCS;

public static MusicLibraryProvider parse(String value) {
return Objects.equals(value, "1") ? NCS : OTHER;
}
}
138 changes: 127 additions & 11 deletions common/src/main/java/jdash/common/entity/GDSong.java
Original file line number Diff line number Diff line change
@@ -1,26 +1,44 @@
package jdash.common.entity;

import jdash.common.MusicLibraryProvider;

import javax.annotation.Nullable;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

/**
* Represents a song that can be used in-game. It can be a custom song from Newgrounds, a song from the Music Library,
* or one of the official game songs.
*
* @param id The ID of the song. For a custom song, this is the Newgrounds ID. For an official song, this is
* its in-game ID.
* @param title The title of the song.
* @param artist The display name of the artist of the song.
* @param size A string that indicates the size of the song. The structure of the string is not guaranteed. Only
* present for Newgrounds and Music Library songs.
* @param downloadUrl The download URL of the song. Only present for Newgrounds songs.
* @param id The ID of the song. For a custom song, this is the Newgrounds ID. For an official song,
* this is its in-game ID.
* @param title The title of the song.
* @param artistId The ID of the artist of the song.
* @param artist The display name of the artist of the song.
* @param size A string that indicates the size of the song. The structure of the string is not
* guaranteed. Not present for official songs.
* @param youtubeArtist The YouTube channel ID of the artist. Not always present.
* @param isArtistScouted Whether the artist is scouted on Newgrounds. Always false if the song is not from
* Newgrounds.
* @param downloadUrl The download URL of the song. Not always present. May be equal to "CUSTOMURL" to indicate
* this information may be fetched elsewhere.
* @param musicLibraryProvider The song provider if the song is from music library, otherwise always returns
* {@link MusicLibraryProvider#OTHER}.
* @param otherArtistIds The list of other artist IDs that collaborated on the song.
*/
public record GDSong(
long id,
String title,
long artistId,
String artist,
Optional<String> size,
Optional<String> downloadUrl
Optional<String> youtubeArtist,
boolean isArtistScouted,
Optional<String> downloadUrl,
MusicLibraryProvider musicLibraryProvider,
List<Long> otherArtistIds
) {

private static final Map<Integer, GDSong> OFFICIAL_SONGS = initOfficialSongs();
Expand Down Expand Up @@ -52,7 +70,22 @@ private static Map<Integer, GDSong> initOfficialSongs() {
}

private static Map.Entry<Integer, GDSong> officialSongEntry(int id, String artist, String title) {
return Map.entry(id, new GDSong(id, title, artist, Optional.empty(), Optional.empty()));
return Map.entry(id, new GDSong(
id,
title,
0L,
artist,
Optional.empty(),
Optional.empty(),
false,
Optional.empty(),
MusicLibraryProvider.OTHER,
List.of()
));
}

private static boolean isMusicLibraryId(long id) {
return id >= 10_000_000;
}

/**
Expand All @@ -65,6 +98,80 @@ public static Optional<GDSong> getOfficialSong(int id) {
return Optional.ofNullable(OFFICIAL_SONGS.get(id));
}

/**
* Construct a {@link GDSong} with only the data that is relevant for a Newgrounds song.
*
* @param id The song ID. Must be < 10_000_000
* @param title The song title
* @param artistId The artist ID
* @param artist The artist name
* @param size The formatted size in MB
* @param isArtistScouted Whether the artist is scouted on Newgrounds
* @param downloadUrl The download URL of the song. May be null or equal to "CUSTOMURL", otherwise a valid URL
* @return a {@link GDSong}
* @throws IllegalArgumentException if ID doesn't fit a Newgrounds song (< 10_000_000)
*/
public static GDSong fromNewgrounds(long id, String title, long artistId, String artist, String size,
boolean isArtistScouted, @Nullable String downloadUrl) {
if (isMusicLibraryId(id)) {
throw new IllegalArgumentException("id must be < 10_000_000 for a Newgrounds song");
}
Objects.requireNonNull(title);
Objects.requireNonNull(artist);
Objects.requireNonNull(size);
return new GDSong(
id,
title,
artistId,
artist,
Optional.of(size),
Optional.empty(),
isArtistScouted,
Optional.ofNullable(downloadUrl),
MusicLibraryProvider.OTHER,
List.of()
);
}

/**
* Construct a {@link GDSong} with only the data that is relevant for a Music Library song.
*
* @param id The song ID
* @param title The song title
* @param artistId The artist ID
* @param artist The artist name
* @param size The formatted size in MB
* @param youtubeArtist The YouTube channel ID of the artist, may be null
* @param musicLibraryProvider The music library provider
* @param otherArtistIds The list of other artist IDs that collaborated on the song.
* @return a {@link GDSong}
* @throws IllegalArgumentException if ID doesn't fit a Music Library song (>= 10_000_000)
*/
public static GDSong fromMusicLibrary(long id, String title, long artistId, String artist, String size,
@Nullable String youtubeArtist, MusicLibraryProvider musicLibraryProvider,
List<Long> otherArtistIds) {
if (!isMusicLibraryId(id)) {
throw new IllegalArgumentException("id must be >= 10_000_000 for a Music Library song");
}
Objects.requireNonNull(title);
Objects.requireNonNull(artist);
Objects.requireNonNull(size);
Objects.requireNonNull(musicLibraryProvider);
Objects.requireNonNull(otherArtistIds);
return new GDSong(
id,
title,
artistId,
artist,
Optional.of(size),
Optional.ofNullable(youtubeArtist),
false,
Optional.of("CUSTOMURL"),
musicLibraryProvider,
otherArtistIds
);
}

/**
* Convenience method to check whether this song represents an official in-game song.
*
Expand All @@ -80,7 +187,7 @@ public boolean isOfficial() {
* @return a boolean
*/
public boolean isFromNewgrounds() {
return !OFFICIAL_SONGS.containsKey((int) id) && id < 10_000_000;
return !OFFICIAL_SONGS.containsKey((int) id) && !isMusicLibraryId(id);
}

/**
Expand All @@ -89,6 +196,15 @@ public boolean isFromNewgrounds() {
* @return a boolean
*/
public boolean isFromMusicLibrary() {
return !OFFICIAL_SONGS.containsKey((int) id) && id >= 10_000_000;
return !OFFICIAL_SONGS.containsKey((int) id) && isMusicLibraryId(id);
}

/**
* Convenience method to check whether this song is provided by NCS.
*
* @return a boolean
*/
public boolean isNCS() {
return musicLibraryProvider == MusicLibraryProvider.NCS;
}
}
5 changes: 4 additions & 1 deletion common/src/main/java/jdash/common/entity/package-info.java
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
/**
* Contains value objects representing core entities of Geometry Dash.
*/
package jdash.common.entity;
@Nonnull
package jdash.common.entity;

import javax.annotation.Nonnull;
6 changes: 6 additions & 0 deletions common/src/main/java/jdash/common/internal/Indexes.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,15 @@ public final class Indexes {
/* Song indexes */
public static final int SONG_ID = 1;
public static final int SONG_TITLE = 2;
public static final int SONG_ARTIST_ID = 3;
public static final int SONG_ARTIST = 4;
public static final int SONG_SIZE = 5;
public static final int SONG_YOUTUBE_ARTIST = 7;
public static final int SONG_NG_SCOUTED = 8;
public static final int SONG_PRIORITY_ID = 9;
public static final int SONG_URL = 10;
public static final int SONG_PROVIDER_ID = 11;
public static final int SONG_OTHER_ARTIST_IDS = 12;

/* User indexes */
public static final int USER_NAME = 1;
Expand Down
14 changes: 12 additions & 2 deletions common/src/main/java/jdash/common/internal/InternalUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,15 @@ public static Map<Long, GDSong> structureSongsInfo(String songsInfoRD) {
result.put(songID, new GDSong(
songID,
data.get(SONG_TITLE),
Long.parseLong(data.get(SONG_ARTIST_ID)),
data.get(SONG_ARTIST),
Optional.ofNullable(data.get(SONG_SIZE)),
Optional.ofNullable(urlDecode(data.get(SONG_URL)))));
Optional.ofNullable(data.get(SONG_YOUTUBE_ARTIST)),
Objects.equals(data.get(SONG_NG_SCOUTED), "1"),
Optional.ofNullable(urlDecode(data.get(SONG_URL))),
MusicLibraryProvider.parse(data.get(SONG_PROVIDER_ID)),
toList(data.get(SONG_OTHER_ARTIST_IDS), 0, Long::parseLong).orElse(List.of())
));
}

return result;
Expand Down Expand Up @@ -307,10 +313,14 @@ public static GDUserProfile buildUserProfile(Map<Integer, String> data) {
}

private static Optional<List<Integer>> toIntList(String str, int minSize) {
return toList(str, minSize, Integer::parseInt);
}

private static <T> Optional<List<T>> toList(String str, int minSize, Function<String, T> parser) {
if (str == null || str.isEmpty()) {
return Optional.empty();
}
return Optional.of(Arrays.stream(str.split(",")).map(Integer::parseInt).toList())
return Optional.of(Arrays.stream(str.split(",")).map(parser).toList())
.filter(values -> values.size() >= minSize);
}

Expand Down
5 changes: 4 additions & 1 deletion common/src/main/java/jdash/common/internal/package-info.java
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
/**
* Contains utilities for internal use only.
*/
package jdash.common.internal;
@Nonnull
package jdash.common.internal;

import javax.annotation.Nonnull;
5 changes: 4 additions & 1 deletion common/src/main/java/jdash/common/package-info.java
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
/**
* Contains common types and utilities when dealing with Geometry Dash data.
*/
package jdash.common;
@Nonnull
package jdash.common;

import javax.annotation.Nonnull;
2 changes: 2 additions & 0 deletions common/src/main/java/module-info.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
module jdash.common {

requires jsr305;

exports jdash.common;
exports jdash.common.entity;
exports jdash.common.internal to jdash.client, jdash.events, jdash.graphics;
Expand Down
Loading

0 comments on commit 98e7e33

Please sign in to comment.