From 0558bbfd07be18305a35491473f0d54d73359530 Mon Sep 17 00:00:00 2001 From: jie65535 Date: Fri, 17 Jun 2022 23:41:26 +0800 Subject: [PATCH] Added Curseforge Api Tests Upgraded kotlin version to 1.6.21 Upgraded mirai console version to 2.11.1 --- .gitignore | 1 + build.gradle.kts | 13 +- .../kotlin/top/jie65535/jcf/CurseforgeApi.kt | 57 ++++++- .../kotlin/top/jie65535/jcf/model/Category.kt | 14 +- .../jie65535/jcf/model/SortableGameVersion.kt | 2 +- .../top/jie65535/jcf/model/file/File.kt | 12 +- .../top/jie65535/jcf/model/file/FileIndex.kt | 4 +- .../jcf/model/file/FileRelationType.kt | 2 +- .../jcf/model/file/FileReleaseType.kt | 2 +- .../top/jie65535/jcf/model/file/FileStatus.kt | 2 +- .../top/jie65535/jcf/model/file/HashAlgo.kt | 2 +- .../jie65535/jcf/model/game/CoreApiStatus.kt | 12 ++ .../top/jie65535/jcf/model/game/CoreStatus.kt | 16 ++ .../top/jie65535/jcf/model/game/Game.kt | 17 +++ .../top/jie65535/jcf/model/game/GameAssets.kt | 8 + .../jcf/model/game/GameVersionType.kt | 9 ++ .../kotlin/top/jie65535/jcf/model/mod/Mod.kt | 6 +- .../top/jie65535/jcf/model/mod/ModAsset.kt | 4 +- .../top/jie65535/jcf/model/mod/ModAuthor.kt | 2 +- .../top/jie65535/jcf/model/mod/ModLinks.kt | 8 +- .../jie65535/jcf/model/mod/ModLoaderType.kt | 2 +- .../top/jie65535/jcf/model/mod/ModStatus.kt | 2 +- .../request/GetFeaturedModsRequestBody.kt | 2 +- .../jcf/model/request/ModsSearchSortField.kt | 2 +- .../jcf/model/response/GameVersionsByType.kt | 7 + .../jcf/model/response/GetGameResponse.kt | 8 + .../jcf/model/response/GetGamesResponse.kt | 10 ++ .../model/response/GetVersionTypesResponse.kt | 8 + .../jcf/model/response/GetVersionsResponse.kt | 6 + .../jie65535/jcf/util/EnumIndexSerializer.kt | 6 +- .../jcf/util/OffsetDateTimeSerializer.kt | 16 +- src/test/kotlin/CurseforgeApiTest.kt | 143 ++++++++++++++++++ 32 files changed, 356 insertions(+), 49 deletions(-) create mode 100644 src/main/kotlin/top/jie65535/jcf/model/game/CoreApiStatus.kt create mode 100644 src/main/kotlin/top/jie65535/jcf/model/game/CoreStatus.kt create mode 100644 src/main/kotlin/top/jie65535/jcf/model/game/Game.kt create mode 100644 src/main/kotlin/top/jie65535/jcf/model/game/GameAssets.kt create mode 100644 src/main/kotlin/top/jie65535/jcf/model/game/GameVersionType.kt create mode 100644 src/main/kotlin/top/jie65535/jcf/model/response/GameVersionsByType.kt create mode 100644 src/main/kotlin/top/jie65535/jcf/model/response/GetGameResponse.kt create mode 100644 src/main/kotlin/top/jie65535/jcf/model/response/GetGamesResponse.kt create mode 100644 src/main/kotlin/top/jie65535/jcf/model/response/GetVersionTypesResponse.kt create mode 100644 src/main/kotlin/top/jie65535/jcf/model/response/GetVersionsResponse.kt create mode 100644 src/test/kotlin/CurseforgeApiTest.kt diff --git a/.gitignore b/.gitignore index 3542e01..b84b90b 100644 --- a/.gitignore +++ b/.gitignore @@ -119,3 +119,4 @@ run/ # Local Test Launch point src/test/kotlin/RunTerminal.kt +/api_key.txt diff --git a/build.gradle.kts b/build.gradle.kts index 5b6ded8..23fb57f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,15 +1,20 @@ plugins { - val kotlinVersion = "1.5.10" + val kotlinVersion = "1.6.21" kotlin("jvm") version kotlinVersion kotlin("plugin.serialization") version kotlinVersion - id("net.mamoe.mirai-console") version "2.7.0" + id("net.mamoe.mirai-console") version "2.11.1" } -group = "me.jie65535" -version = "0.1.1" +group = "top.jie65535" +version = "1.0.0" repositories { maven("https://maven.aliyun.com/repository/public") mavenCentral() +} + +dependencies { + testImplementation(kotlin("test")) + testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.2") } \ No newline at end of file diff --git a/src/main/kotlin/top/jie65535/jcf/CurseforgeApi.kt b/src/main/kotlin/top/jie65535/jcf/CurseforgeApi.kt index 44db1cd..7914415 100644 --- a/src/main/kotlin/top/jie65535/jcf/CurseforgeApi.kt +++ b/src/main/kotlin/top/jie65535/jcf/CurseforgeApi.kt @@ -9,6 +9,8 @@ import kotlinx.serialization.* import kotlinx.serialization.json.Json import top.jie65535.jcf.model.Category import top.jie65535.jcf.model.file.File +import top.jie65535.jcf.model.game.Game +import top.jie65535.jcf.model.game.GameVersionType import top.jie65535.jcf.model.mod.* import top.jie65535.jcf.model.request.* import top.jie65535.jcf.model.request.SortOrder.* @@ -46,8 +48,54 @@ class CurseforgeApi(apiKey: String) { //region - Game - - // Minecraft Game ID is 432 - // Ignore Game APIs + /** + * Get all games that are available to the provided API key. + */ + suspend fun getGames(index: Int? = null, pageSize: Int? = null): GetGamesResponse { + return json.decodeFromString( + http.get("/v1/games") { + parameter("index", index) + parameter("pageSize", pageSize) + } + ) + } + + /** + * Get a single game. A private game is only accessible by its respective API key. + */ + suspend fun getGame(gameId: Int): Game { + return json.decodeFromString( + http.get("/v1/games/$gameId") + ).data + } + + /** + * Get all available versions for each known version type of the specified game. + * A private game is only accessible to its respective API key. + */ + suspend fun getVersions(gameId: Int): Array { + return json.decodeFromString( + http.get("/v1/games/$gameId/versions") + ).data + } + + /** + * Get all available version types of the specified game. + * + * A private game is only accessible to its respective API key. + * + * Currently, when creating games via the CurseForge Core Console, + * you are limited to a single game version type. + * This means that this endpoint is probably not useful in most cases + * and is relevant mostly when handling existing games that have + * multiple game versions such as World of Warcraft and Minecraft + * (e.g. 517 for wow_retail). + */ + suspend fun getVersionTypes(gameId: Int): Array { + return json.decodeFromString( + http.get("/v1/games/$gameId/versions") + ).data + } //endregion @@ -137,6 +185,7 @@ class CurseforgeApi(apiKey: String) { suspend fun getMods(modIds: IntArray): Array { return json.decodeFromString( http.post("/v1/mods") { + headers.append("Content-Type", "application/json") body = json.encodeToString(GetModsByIdsListRequestBody(modIds)) } ).data @@ -147,11 +196,12 @@ class CurseforgeApi(apiKey: String) { */ suspend fun getFeaturedMods( gameId: Int, - excludedModIds: IntArray, + excludedModIds: IntArray = intArrayOf(), gameVersionTypeId: Int? = null ): FeaturedModsResponse { return json.decodeFromString( http.get("/v1/mods/featured") { + headers.append("Content-Type", "application/json") body = json.encodeToString(GetFeaturedModsRequestBody(gameId, excludedModIds, gameVersionTypeId)) } ).data @@ -207,6 +257,7 @@ class CurseforgeApi(apiKey: String) { suspend fun getFiles(fileIds: IntArray): Array { return json.decodeFromString( http.post("/v1/mods/files") { + headers.append("Content-Type", "application/json") body = json.encodeToString(GetModFilesRequestBody(fileIds)) } ).data diff --git a/src/main/kotlin/top/jie65535/jcf/model/Category.kt b/src/main/kotlin/top/jie65535/jcf/model/Category.kt index 66935ce..527ac6c 100644 --- a/src/main/kotlin/top/jie65535/jcf/model/Category.kt +++ b/src/main/kotlin/top/jie65535/jcf/model/Category.kt @@ -24,17 +24,17 @@ class Category( /** * The category slug as it appear in the URL */ - val slug: String, + val slug: String?, /** * The category URL */ - val url: String, + val url: String?, /** * URL for the category icon */ - val iconUrl: String, + val iconUrl: String?, /** * Last modified date of the category @@ -45,20 +45,20 @@ class Category( /** * A top level category for other categories */ - val isClass: Boolean?, + val isClass: Boolean? = null, /** * The class id of the category, meaning - the class of which this category is under */ - val classId: Int?, + val classId: Int? = null, /** * The parent category for this category */ - val parentCategoryId: Int?, + val parentCategoryId: Int? = null, /** * The display index for this category */ - val displayIndex: Int? + val displayIndex: Int? = null ) diff --git a/src/main/kotlin/top/jie65535/jcf/model/SortableGameVersion.kt b/src/main/kotlin/top/jie65535/jcf/model/SortableGameVersion.kt index b3acd22..42e1b05 100644 --- a/src/main/kotlin/top/jie65535/jcf/model/SortableGameVersion.kt +++ b/src/main/kotlin/top/jie65535/jcf/model/SortableGameVersion.kt @@ -30,5 +30,5 @@ class SortableGameVersion( /** * Game version type id */ - val gameVersionTypeId: Int? + val gameVersionTypeId: Int? = null ) diff --git a/src/main/kotlin/top/jie65535/jcf/model/file/File.kt b/src/main/kotlin/top/jie65535/jcf/model/file/File.kt index f325d15..a285b42 100644 --- a/src/main/kotlin/top/jie65535/jcf/model/file/File.kt +++ b/src/main/kotlin/top/jie65535/jcf/model/file/File.kt @@ -59,7 +59,7 @@ class File( /** * The file download URL */ - val downloadUrl: String, + val downloadUrl: String?, /** * List of game versions this file is relevant for */ @@ -75,23 +75,23 @@ class File( /** * none */ - val exposeAsAlternative: Boolean?, + val exposeAsAlternative: Boolean? = null, /** * none */ - val parentProjectFileId: Int?, + val parentProjectFileId: Int? = null, /** * none */ - val alternateFileId: Int?, + val alternateFileId: Int? = null, /** * none */ - val isServerPack: Boolean?, + val isServerPack: Boolean? = null, /** * none */ - val serverPackFileId: Int?, + val serverPackFileId: Int? = null, /** * none */ diff --git a/src/main/kotlin/top/jie65535/jcf/model/file/FileIndex.kt b/src/main/kotlin/top/jie65535/jcf/model/file/FileIndex.kt index 7a51815..203ba6f 100644 --- a/src/main/kotlin/top/jie65535/jcf/model/file/FileIndex.kt +++ b/src/main/kotlin/top/jie65535/jcf/model/file/FileIndex.kt @@ -8,6 +8,6 @@ class FileIndex( val fileId: Int, val filename: String, val releaseType: FileReleaseType, - val gameVersionTypeId: Int?, - val modLoader: ModLoaderType, + val gameVersionTypeId: Int? = null, + val modLoader: ModLoaderType? = null, ) diff --git a/src/main/kotlin/top/jie65535/jcf/model/file/FileRelationType.kt b/src/main/kotlin/top/jie65535/jcf/model/file/FileRelationType.kt index c28a5f8..9c200b8 100644 --- a/src/main/kotlin/top/jie65535/jcf/model/file/FileRelationType.kt +++ b/src/main/kotlin/top/jie65535/jcf/model/file/FileRelationType.kt @@ -12,5 +12,5 @@ enum class FileRelationType { Incompatible, Include; - internal object IndexSerializer : KSerializer by EnumIndexSerializer() + internal object IndexSerializer : KSerializer by EnumIndexSerializer(values()) } diff --git a/src/main/kotlin/top/jie65535/jcf/model/file/FileReleaseType.kt b/src/main/kotlin/top/jie65535/jcf/model/file/FileReleaseType.kt index acbbd1e..e7108b7 100644 --- a/src/main/kotlin/top/jie65535/jcf/model/file/FileReleaseType.kt +++ b/src/main/kotlin/top/jie65535/jcf/model/file/FileReleaseType.kt @@ -9,5 +9,5 @@ enum class FileReleaseType { Beta, Alpha; - internal object IndexSerializer : KSerializer by EnumIndexSerializer() + internal object IndexSerializer : KSerializer by EnumIndexSerializer(values()) } diff --git a/src/main/kotlin/top/jie65535/jcf/model/file/FileStatus.kt b/src/main/kotlin/top/jie65535/jcf/model/file/FileStatus.kt index a351924..4ba0d62 100644 --- a/src/main/kotlin/top/jie65535/jcf/model/file/FileStatus.kt +++ b/src/main/kotlin/top/jie65535/jcf/model/file/FileStatus.kt @@ -21,5 +21,5 @@ enum class FileStatus{ AwaitingPublishing, FailedPublishing; - internal object IndexSerializer : KSerializer by EnumIndexSerializer() + internal object IndexSerializer : KSerializer by EnumIndexSerializer(values()) } diff --git a/src/main/kotlin/top/jie65535/jcf/model/file/HashAlgo.kt b/src/main/kotlin/top/jie65535/jcf/model/file/HashAlgo.kt index 3a92961..0c8040e 100644 --- a/src/main/kotlin/top/jie65535/jcf/model/file/HashAlgo.kt +++ b/src/main/kotlin/top/jie65535/jcf/model/file/HashAlgo.kt @@ -8,5 +8,5 @@ enum class HashAlgo(val value: Int) { Sha1(1), Md5(2); - internal object IndexSerializer : KSerializer by EnumIndexSerializer() + internal object IndexSerializer : KSerializer by EnumIndexSerializer(values()) } diff --git a/src/main/kotlin/top/jie65535/jcf/model/game/CoreApiStatus.kt b/src/main/kotlin/top/jie65535/jcf/model/game/CoreApiStatus.kt new file mode 100644 index 0000000..6844f8c --- /dev/null +++ b/src/main/kotlin/top/jie65535/jcf/model/game/CoreApiStatus.kt @@ -0,0 +1,12 @@ +package top.jie65535.jcf.model.game + +import kotlinx.serialization.KSerializer +import top.jie65535.jcf.util.EnumIndexSerializer + +@kotlinx.serialization.Serializable(with = CoreApiStatus.IndexSerializer::class) +enum class CoreApiStatus { + Private, + Public; + + internal object IndexSerializer : KSerializer by EnumIndexSerializer(values()) +} diff --git a/src/main/kotlin/top/jie65535/jcf/model/game/CoreStatus.kt b/src/main/kotlin/top/jie65535/jcf/model/game/CoreStatus.kt new file mode 100644 index 0000000..ec7944a --- /dev/null +++ b/src/main/kotlin/top/jie65535/jcf/model/game/CoreStatus.kt @@ -0,0 +1,16 @@ +package top.jie65535.jcf.model.game + +import kotlinx.serialization.KSerializer +import top.jie65535.jcf.util.EnumIndexSerializer + +@kotlinx.serialization.Serializable(with = CoreStatus.IndexSerializer::class) +enum class CoreStatus { + Draft, + Test, + PendingReview, + Rejected, + Approved, + Live; + + internal object IndexSerializer : KSerializer by EnumIndexSerializer(values()) +} diff --git a/src/main/kotlin/top/jie65535/jcf/model/game/Game.kt b/src/main/kotlin/top/jie65535/jcf/model/game/Game.kt new file mode 100644 index 0000000..fa7dff0 --- /dev/null +++ b/src/main/kotlin/top/jie65535/jcf/model/game/Game.kt @@ -0,0 +1,17 @@ +package top.jie65535.jcf.model.game + +import kotlinx.serialization.Serializable +import top.jie65535.jcf.util.OffsetDateTimeSerializer +import java.time.OffsetDateTime + +@Serializable +class Game( + val id: Int, + val name: String, + val slug: String?, + @Serializable(OffsetDateTimeSerializer::class) + val dateModified: OffsetDateTime, + val assets: GameAssets, + val status: CoreStatus, + val apiStatus: CoreApiStatus, +) \ No newline at end of file diff --git a/src/main/kotlin/top/jie65535/jcf/model/game/GameAssets.kt b/src/main/kotlin/top/jie65535/jcf/model/game/GameAssets.kt new file mode 100644 index 0000000..621a6be --- /dev/null +++ b/src/main/kotlin/top/jie65535/jcf/model/game/GameAssets.kt @@ -0,0 +1,8 @@ +package top.jie65535.jcf.model.game + +@kotlinx.serialization.Serializable +class GameAssets( + val iconUrl: String?, + val tileUrl: String?, + val coverUrl: String?, +) \ No newline at end of file diff --git a/src/main/kotlin/top/jie65535/jcf/model/game/GameVersionType.kt b/src/main/kotlin/top/jie65535/jcf/model/game/GameVersionType.kt new file mode 100644 index 0000000..6ee68f3 --- /dev/null +++ b/src/main/kotlin/top/jie65535/jcf/model/game/GameVersionType.kt @@ -0,0 +1,9 @@ +package top.jie65535.jcf.model.game + +@kotlinx.serialization.Serializable +class GameVersionType( + val id: Int? = null, + val gameId: Int? = null, + val name: String? = null, + val slug: String? = null, +) \ No newline at end of file diff --git a/src/main/kotlin/top/jie65535/jcf/model/mod/Mod.kt b/src/main/kotlin/top/jie65535/jcf/model/mod/Mod.kt index 91d9110..4117dbf 100644 --- a/src/main/kotlin/top/jie65535/jcf/model/mod/Mod.kt +++ b/src/main/kotlin/top/jie65535/jcf/model/mod/Mod.kt @@ -27,7 +27,7 @@ class Mod( /** * The mod slug that would appear in the URL */ - val slug: String, + val slug: String?, /** * Relevant links for the mod such as Issue tracker and Wiki @@ -67,7 +67,7 @@ class Mod( /** * The class id this mod belongs to */ - val classId: Int?, + val classId: Int? = null, /** * List of the mod's authors @@ -120,7 +120,7 @@ class Mod( /** * Is mod allowed to be distributed */ - val allowModDistribution: Boolean?, + val allowModDistribution: Boolean? = null, /** * The mod popularity rank for the game diff --git a/src/main/kotlin/top/jie65535/jcf/model/mod/ModAsset.kt b/src/main/kotlin/top/jie65535/jcf/model/mod/ModAsset.kt index 2b12abd..5da33ad 100644 --- a/src/main/kotlin/top/jie65535/jcf/model/mod/ModAsset.kt +++ b/src/main/kotlin/top/jie65535/jcf/model/mod/ModAsset.kt @@ -6,6 +6,6 @@ class ModAsset( val modId: Int, val title: String, val description: String, - val thumbnailUrl: String, - val url: String, + val thumbnailUrl: String?, + val url: String?, ) \ No newline at end of file diff --git a/src/main/kotlin/top/jie65535/jcf/model/mod/ModAuthor.kt b/src/main/kotlin/top/jie65535/jcf/model/mod/ModAuthor.kt index ebfeafc..d0a025a 100644 --- a/src/main/kotlin/top/jie65535/jcf/model/mod/ModAuthor.kt +++ b/src/main/kotlin/top/jie65535/jcf/model/mod/ModAuthor.kt @@ -4,5 +4,5 @@ package top.jie65535.jcf.model.mod class ModAuthor( val id: Int, val name: String, - val url: String, + val url: String?, ) diff --git a/src/main/kotlin/top/jie65535/jcf/model/mod/ModLinks.kt b/src/main/kotlin/top/jie65535/jcf/model/mod/ModLinks.kt index 00506c5..bbcdedf 100644 --- a/src/main/kotlin/top/jie65535/jcf/model/mod/ModLinks.kt +++ b/src/main/kotlin/top/jie65535/jcf/model/mod/ModLinks.kt @@ -2,8 +2,8 @@ package top.jie65535.jcf.model.mod @kotlinx.serialization.Serializable class ModLinks( - val websiteUrl: String, - val wikiUrl: String, - val issuesUrl: String, - val sourceUrl: String + val websiteUrl: String?, + val wikiUrl: String?, + val issuesUrl: String?, + val sourceUrl: String? ) diff --git a/src/main/kotlin/top/jie65535/jcf/model/mod/ModLoaderType.kt b/src/main/kotlin/top/jie65535/jcf/model/mod/ModLoaderType.kt index 8cc9cc7..7e5930d 100644 --- a/src/main/kotlin/top/jie65535/jcf/model/mod/ModLoaderType.kt +++ b/src/main/kotlin/top/jie65535/jcf/model/mod/ModLoaderType.kt @@ -12,5 +12,5 @@ enum class ModLoaderType { Fabric, Quilt; - internal object IndexSerializer : KSerializer by EnumIndexSerializer() + internal object IndexSerializer : KSerializer by EnumIndexSerializer(values()) } \ No newline at end of file diff --git a/src/main/kotlin/top/jie65535/jcf/model/mod/ModStatus.kt b/src/main/kotlin/top/jie65535/jcf/model/mod/ModStatus.kt index bf793e7..0724cef 100644 --- a/src/main/kotlin/top/jie65535/jcf/model/mod/ModStatus.kt +++ b/src/main/kotlin/top/jie65535/jcf/model/mod/ModStatus.kt @@ -16,5 +16,5 @@ enum class ModStatus { Deleted, UnderReview; - internal object IndexSerializer : KSerializer by EnumIndexSerializer() + internal object IndexSerializer : KSerializer by EnumIndexSerializer(values()) } diff --git a/src/main/kotlin/top/jie65535/jcf/model/request/GetFeaturedModsRequestBody.kt b/src/main/kotlin/top/jie65535/jcf/model/request/GetFeaturedModsRequestBody.kt index 3bd2be0..c5c35f8 100644 --- a/src/main/kotlin/top/jie65535/jcf/model/request/GetFeaturedModsRequestBody.kt +++ b/src/main/kotlin/top/jie65535/jcf/model/request/GetFeaturedModsRequestBody.kt @@ -4,5 +4,5 @@ package top.jie65535.jcf.model.request class GetFeaturedModsRequestBody( val gameId: Int, val excludedModIds: IntArray, - val gameVersionTypeId: Int?, + val gameVersionTypeId: Int? = null, ) diff --git a/src/main/kotlin/top/jie65535/jcf/model/request/ModsSearchSortField.kt b/src/main/kotlin/top/jie65535/jcf/model/request/ModsSearchSortField.kt index d8021f3..89b9d0c 100644 --- a/src/main/kotlin/top/jie65535/jcf/model/request/ModsSearchSortField.kt +++ b/src/main/kotlin/top/jie65535/jcf/model/request/ModsSearchSortField.kt @@ -14,5 +14,5 @@ enum class ModsSearchSortField { Category, GameVersion; - internal object IndexSerializer : KSerializer by EnumIndexSerializer() + internal object IndexSerializer : KSerializer by EnumIndexSerializer(values()) } \ No newline at end of file diff --git a/src/main/kotlin/top/jie65535/jcf/model/response/GameVersionsByType.kt b/src/main/kotlin/top/jie65535/jcf/model/response/GameVersionsByType.kt new file mode 100644 index 0000000..d5b2ec5 --- /dev/null +++ b/src/main/kotlin/top/jie65535/jcf/model/response/GameVersionsByType.kt @@ -0,0 +1,7 @@ +package top.jie65535.jcf.model.response + +@kotlinx.serialization.Serializable +class GameVersionsByType( + val type: Int, + val versions: Array +) diff --git a/src/main/kotlin/top/jie65535/jcf/model/response/GetGameResponse.kt b/src/main/kotlin/top/jie65535/jcf/model/response/GetGameResponse.kt new file mode 100644 index 0000000..16f8669 --- /dev/null +++ b/src/main/kotlin/top/jie65535/jcf/model/response/GetGameResponse.kt @@ -0,0 +1,8 @@ +package top.jie65535.jcf.model.response + +import top.jie65535.jcf.model.game.Game + +@kotlinx.serialization.Serializable +class GetGameResponse( + val data: Game +) \ No newline at end of file diff --git a/src/main/kotlin/top/jie65535/jcf/model/response/GetGamesResponse.kt b/src/main/kotlin/top/jie65535/jcf/model/response/GetGamesResponse.kt new file mode 100644 index 0000000..f12869b --- /dev/null +++ b/src/main/kotlin/top/jie65535/jcf/model/response/GetGamesResponse.kt @@ -0,0 +1,10 @@ +package top.jie65535.jcf.model.response + +import top.jie65535.jcf.model.game.Game +import top.jie65535.jcf.model.Pagination + +@kotlinx.serialization.Serializable +class GetGamesResponse( + val data: Array, + val pagination: Pagination +) \ No newline at end of file diff --git a/src/main/kotlin/top/jie65535/jcf/model/response/GetVersionTypesResponse.kt b/src/main/kotlin/top/jie65535/jcf/model/response/GetVersionTypesResponse.kt new file mode 100644 index 0000000..c6c3951 --- /dev/null +++ b/src/main/kotlin/top/jie65535/jcf/model/response/GetVersionTypesResponse.kt @@ -0,0 +1,8 @@ +package top.jie65535.jcf.model.response + +import top.jie65535.jcf.model.game.GameVersionType + +@kotlinx.serialization.Serializable +class GetVersionTypesResponse( + val data: Array +) \ No newline at end of file diff --git a/src/main/kotlin/top/jie65535/jcf/model/response/GetVersionsResponse.kt b/src/main/kotlin/top/jie65535/jcf/model/response/GetVersionsResponse.kt new file mode 100644 index 0000000..693cda2 --- /dev/null +++ b/src/main/kotlin/top/jie65535/jcf/model/response/GetVersionsResponse.kt @@ -0,0 +1,6 @@ +package top.jie65535.jcf.model.response + +@kotlinx.serialization.Serializable +class GetVersionsResponse( + val data: Array +) \ No newline at end of file diff --git a/src/main/kotlin/top/jie65535/jcf/util/EnumIndexSerializer.kt b/src/main/kotlin/top/jie65535/jcf/util/EnumIndexSerializer.kt index 1a9d77c..74a2ab1 100644 --- a/src/main/kotlin/top/jie65535/jcf/util/EnumIndexSerializer.kt +++ b/src/main/kotlin/top/jie65535/jcf/util/EnumIndexSerializer.kt @@ -8,7 +8,7 @@ import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder @Suppress("FunctionName") -inline fun > EnumIndexSerializer(offset: Int = 1): KSerializer { +inline fun > EnumIndexSerializer(values: Array, offset: Int = 1): KSerializer { return object : KSerializer { override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor(T::class.qualifiedName!!, PrimitiveKind.INT) @@ -17,8 +17,6 @@ inline fun > EnumIndexSerializer(offset: Int = 1): KSerializ encoder.encodeInt(value.ordinal + offset) override fun deserialize(decoder: Decoder): T = - requireNotNull(enumValues().getOrNull(decoder.decodeInt())) { - "index: ${decoder.decodeInt()} not in ${enumValues()}" - } + values[decoder.decodeInt() - offset] } } \ No newline at end of file diff --git a/src/main/kotlin/top/jie65535/jcf/util/OffsetDateTimeSerializer.kt b/src/main/kotlin/top/jie65535/jcf/util/OffsetDateTimeSerializer.kt index be4f286..82c17da 100644 --- a/src/main/kotlin/top/jie65535/jcf/util/OffsetDateTimeSerializer.kt +++ b/src/main/kotlin/top/jie65535/jcf/util/OffsetDateTimeSerializer.kt @@ -6,9 +6,11 @@ import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder +import java.time.Instant +import java.time.LocalDateTime import java.time.OffsetDateTime +import java.time.ZoneId import java.time.format.DateTimeFormatter - object OffsetDateTimeSerializer : KSerializer { private val formatter: DateTimeFormatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME @@ -16,10 +18,16 @@ object OffsetDateTimeSerializer : KSerializer { override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor(OffsetDateTime::class.qualifiedName!!, PrimitiveKind.STRING) - override fun deserialize(decoder: Decoder): OffsetDateTime = - OffsetDateTime.parse(decoder.decodeString(), formatter) + override fun deserialize(decoder: Decoder): OffsetDateTime { + return try { + OffsetDateTime.parse(decoder.decodeString(), formatter) + } catch(ignore: Throwable) { + OffsetDateTime.MIN + } + } override fun serialize(encoder: Encoder, value: OffsetDateTime) = encoder.encodeString(formatter.format(value)) -} \ No newline at end of file +} + diff --git a/src/test/kotlin/CurseforgeApiTest.kt b/src/test/kotlin/CurseforgeApiTest.kt new file mode 100644 index 0000000..6daf572 --- /dev/null +++ b/src/test/kotlin/CurseforgeApiTest.kt @@ -0,0 +1,143 @@ +package top.jie65535 + +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import org.junit.Test +import top.jie65535.jcf.CurseforgeApi +import top.jie65535.jcf.model.request.ModsSearchSortField +import top.jie65535.jcf.model.request.SortOrder +import java.io.File +import kotlin.test.assertEquals +import kotlin.test.assertNotNull + +@OptIn(ExperimentalCoroutinesApi::class) +class CurseforgeApiTest { + companion object { + private const val GAME_ID_MINECRAFT = 432 + private const val MOD_ID_JEI = 238222 + private const val FILE_ID_JEI = 3835406 + private const val CLASS_ID_WORLDS = 17 + private const val CLASS_ID_BUKKIT_PLUGINS = 5 + private const val CLASS_ID_CUSTOMIZATION = 4546 + private const val CLASS_ID_MODPACKS = 4471 + private const val CLASS_ID_RESOURCE_PACKS = 12 + private const val CLASS_ID_ADDONS = 4559 + private const val CLASS_ID_MODS = 6 + } + + // 从 api_key.txt 文件中读取 + private val api = CurseforgeApi(File("api_key.txt").readText()) + + @Test + fun getGames() = runTest { + val games = api.getGames() + val game = assertNotNull(games.data.find { it.name == "Minecraft" }) + assert(game.id == GAME_ID_MINECRAFT) + printResult("getGames", games) + } + + @Test + fun getGame() = runTest { + val game = api.getGame(GAME_ID_MINECRAFT) + assertEquals(game.name, "Minecraft") + printResult("getGame", game) + } + + @Test + fun getVersions() = runTest { + val versions = api.getVersions(GAME_ID_MINECRAFT) + printResult("getVersions", versions) + } + + @Test + fun getVersionTypes() = runTest { + val versionTypes = api.getVersionTypes(GAME_ID_MINECRAFT) + printResult("getVersionTypes", versionTypes) + } + + @Test + fun getCategories() = runTest { + val categories = api.getCategories(GAME_ID_MINECRAFT) + val classes = categories.filter { it.isClass == true } + for (gameClass in classes) { + println("[${gameClass.id}] ${gameClass.name}") + for (category in categories.filter { it.classId == gameClass.id }) { + println(" | [${category.id}] ${category.name}") + } + } + printResult("getCategories", categories) + } + + @Test + fun searchMods() = runTest { + val response = api.searchMods(GAME_ID_MINECRAFT, searchFilter = "create", sortField = ModsSearchSortField.TotalDownloads, sortOrder = SortOrder.DESC, pageSize = 10) + for (mod in response.data) { + println("[${mod.id}] ${mod.name}\t ${mod.summary}\t DownloadCount: ${mod.downloadCount}") + } + printResult("searchMods", response) + } + + @Test + fun getMod() = runTest { + val mod = api.getMod(MOD_ID_JEI) + assertEquals(mod.id, MOD_ID_JEI) + printResult("getMod", mod) + } + + @Test + fun getMods() = runTest { + val mods = api.getMods(intArrayOf(MOD_ID_JEI)) + assertEquals(mods.size, 1) + assertEquals(mods[0].id, MOD_ID_JEI) + printResult("getMods", mods) + } + + @Test + fun getFeaturedMods() = runTest { + // Error: HTTP 404 + val featuredMods = api.getFeaturedMods(GAME_ID_MINECRAFT) + printResult("getFeaturedMods", featuredMods) + } + + @Test + fun getModDescription() = runTest { + val modDescription = api.getModDescription(MOD_ID_JEI) + printResult("getModDescription", modDescription) + } + + @Test + fun getModFile() = runTest { + val modFile = api.getModFile(MOD_ID_JEI, FILE_ID_JEI) + printResult("getModFile", modFile) + } + + @Test + fun getModFiles() = runTest { + val modFiles = api.getModFiles(GAME_ID_MINECRAFT) + printResult("getModFiles", modFiles) + } + + @Test + fun getFiles() = runTest { + val files = api.getFiles(intArrayOf(FILE_ID_JEI)) + printResult("getFiles", files) + } + + @Test + fun getModFileChangelog() = runTest { + val modFileChangelog = api.getModFileChangelog(MOD_ID_JEI, FILE_ID_JEI) + printResult("getModFileChangelog", modFileChangelog) + } + + @Test + fun getModFileDownloadURL() = runTest { + val modFileDownloadUrl = api.getModFileDownloadURL(MOD_ID_JEI, FILE_ID_JEI) + printResult("getModFileDownloadURL", modFileDownloadUrl) + } + + private inline fun printResult(name: String, obj: T) { + println("$name result: ${Json.encodeToString(obj)}") + } +} \ No newline at end of file