新增官方API封装,当前仅完成了搜索Mod部分

This commit is contained in:
2022-06-13 22:23:00 +08:00
parent 76d3cf2f9c
commit 7fd31c194b
30 changed files with 710 additions and 0 deletions

View File

@ -0,0 +1,137 @@
package top.jie65535.jcf
import io.ktor.client.*
import io.ktor.client.engine.okhttp.*
import io.ktor.client.features.*
import io.ktor.client.request.*
import io.ktor.http.*
import kotlinx.serialization.*
import kotlinx.serialization.json.Json
import top.jie65535.jcf.model.mod.*
import top.jie65535.jcf.model.request.*
import top.jie65535.jcf.model.request.SortOrder.*
import top.jie65535.jcf.model.response.*
@OptIn(ExperimentalSerializationApi::class)
class CurseforgeApi(apiKey: String) {
companion object {
private const val GAME_ID_MINECRAFT = 432
}
private val json = Json {
isLenient = true
ignoreUnknownKeys = true
serializersModule
}
private val http = HttpClient(OkHttp) {
install(HttpTimeout) {
this.requestTimeoutMillis = 30_0000
this.connectTimeoutMillis = 30_0000
this.socketTimeoutMillis = 30_0000
}
defaultRequest {
url.protocol = URLProtocol.HTTPS
url.host = "api.curseforge.com"
header("accept", "application/json")
header("x-api-key", apiKey)
}
}
//region Mods
/**
* Get all mods that match the search criteria.
* @param gameId Filter by game id.
* @param classId Filter by section id (discoverable via Categories)
* @param categoryId Filter by category id
* @param gameVersion Filter by game version string
* @param searchFilter Filter by free text search in the mod name and author
* @param sortField Filter by ModsSearchSortField enumeration
* @param sortOrder 'asc' if sort is in ascending order, 'desc' if sort is in descending order
* @param modLoaderType Filter only mods associated to a given modloader (Forge, Fabric ...). Must be coupled with gameVersion.
* @param gameVersionTypeId Filter only mods that contain files tagged with versions of the given gameVersionTypeId
* @param slug Filter by slug (coupled with classId will result in a unique result).
* @param index A zero based index of the first item to include in the response,
* @param pageSize The number of items to include in the response,
*/
suspend fun searchMods(
gameId: Int,
classId: Int?,
categoryId: Int?,
gameVersion: String?,
searchFilter: String?,
sortField: ModsSearchSortField?,
sortOrder: SortOrder?,
modLoaderType: ModLoaderType?,
gameVersionTypeId: Int?,
slug: String?,
index: Int?,
pageSize: Int?
): SearchModsResponse {
return json.decodeFromString(
http.get("/v1/mods/search") {
parameter("gameId", gameId)
parameter("classId", classId)
parameter("categoryId", categoryId)
parameter("gameVersion", gameVersion)
parameter("searchFilter", searchFilter)
parameter("sortField", sortField)
parameter("sortOrder", when(sortOrder){
ASC -> "asc"
DESC -> "asc"
null -> null
})
parameter("modLoaderType", modLoaderType)
parameter("gameVersionTypeId", gameVersionTypeId)
parameter("slug", slug)
parameter("index", index)
parameter("pageSize", pageSize)
}
)
}
/**
* Get a single mod.
*/
suspend fun getMod(modId: Int): GetModResponse {
return json.decodeFromString(
http.get("/v1/mods/$modId")
)
}
/**
* Get a list of mods.
*/
suspend fun getMods(modIds: IntArray): GetModsResponse {
return json.decodeFromString(
http.get("/v1/mods") {
body = json.encodeToString(GetModsByIdsListRequestBody(modIds))
}
)
}
/**
* Get a list of featured, popular and recently updated mods.
*/
suspend fun getFeaturedMods(
gameId: Int,
excludedModIds: IntArray,
gameVersionTypeId: Int?
): GetFeaturedModsResponse {
return json.decodeFromString(
http.get("/v1/mods/featured") {
body = json.encodeToString(GetFeaturedModsRequestBody(gameId, excludedModIds, gameVersionTypeId))
}
)
}
/**
* Get the full description of a mod in HTML format.
*/
suspend fun getModDescription(modId: Int): String {
return http.get("/v1/mods/$modId/description")
}
//endregion
}

View File

@ -0,0 +1,21 @@
package top.jie65535.jcf.model
import kotlinx.serialization.Serializable
import top.jie65535.jcf.util.OffsetDateTimeSerializer
import java.time.OffsetDateTime
@Serializable
class Category(
val id: Int,
val gameId: Int,
val name: String,
val slug: String,
val url: String,
val iconUrl: String,
@Serializable(OffsetDateTimeSerializer::class)
val dateModified: OffsetDateTime,
val isClass: Boolean?,
val classId: Int?,
val parentCategoryId: Int?,
val displayIndex: Int?
)

View File

@ -0,0 +1,9 @@
package top.jie65535.jcf.model
@kotlinx.serialization.Serializable
class Pagination(
val index: Int,
val pageSize: Int,
val resultCount: Int,
val totalCount: Long,
)

View File

@ -0,0 +1,30 @@
package top.jie65535.jcf.model
import kotlinx.serialization.Serializable
import top.jie65535.jcf.util.OffsetDateTimeSerializer
import java.time.OffsetDateTime
@Serializable
class SortableGameVersion(
/**
* Original version name (e.g. 1.5b)
*/
val gameVersionName: String,
/**
* Used for sorting (e.g. 0000000001.0000000005)
*/
val gameVersionPadded: String,
/**
* game version clean name (e.g. 1.5)
*/
val gameVersion: String,
/**
* Game version release date
*/
@Serializable(OffsetDateTimeSerializer::class)
val gameVersionReleaseDate: OffsetDateTime,
/**
* Game version type id
*/
val gameVersionTypeId: Int?
)

View File

@ -0,0 +1,103 @@
package top.jie65535.jcf.model.file
import kotlinx.serialization.Serializable
import top.jie65535.jcf.model.SortableGameVersion
import top.jie65535.jcf.util.OffsetDateTimeSerializer
import java.time.OffsetDateTime
@Serializable
class File(
/**
* The file id
*/
val id: Int,
/**
* The game id related to the mod that this file belongs to
*/
val gameId: Int,
/**
* The mod id
*/
val modId: Int,
/**
* Whether the file is available to download
*/
val isAvailable: Boolean,
/**
* Display name of the file
*/
val displayName: String,
/**
* Exact file name
*/
val fileName: String,
/**
* The file release type
*/
val releaseType: FileReleaseType,
/**
* Status of the file
*/
val fileStatus: FileStatus,
/**
* The file hash (i.e. md5 or sha1)
*/
val hashes: Array<FileHash>,
/**
* The file timestamp
*/
@Serializable(OffsetDateTimeSerializer::class)
val fileDate: OffsetDateTime,
/**
* The file length in bytes
*/
val fileLength: Long,
/**
* The number of downloads for the file
*/
val downloadCount: Long,
/**
* The file download URL
*/
val downloadUrl: String,
/**
* List of game versions this file is relevant for
*/
val gameVersions: Array<String>,
/**
* Metadata used for sorting by game versions
*/
val sortableGameVersions: Array<SortableGameVersion>,
/**
* List of dependencies files
*/
val dependencies: Array<FileDependency>,
/**
* none
*/
val exposeAsAlternative: Boolean?,
/**
* none
*/
val parentProjectFileId: Int?,
/**
* none
*/
val alternateFileId: Int?,
/**
* none
*/
val isServerPack: Boolean?,
/**
* none
*/
val serverPackFileId: Int?,
/**
* none
*/
val fileFingerprint: Long,
/**
* none
*/
val modules: Array<FileModule>,
)

View File

@ -0,0 +1,7 @@
package top.jie65535.jcf.model.file
@kotlinx.serialization.Serializable
class FileDependency(
val modId: Int,
val relationType: FileRelationType,
)

View File

@ -0,0 +1,7 @@
package top.jie65535.jcf.model.file
@kotlinx.serialization.Serializable
class FileHash(
val value: String,
val algo: HashAlgo,
)

View File

@ -0,0 +1,13 @@
package top.jie65535.jcf.model.file
import top.jie65535.jcf.model.mod.ModLoaderType
@kotlinx.serialization.Serializable
class FileIndex(
val gameVersion: String,
val fileId: Int,
val filename: String,
val releaseType: FileReleaseType,
val gameVersionTypeId: Int?,
val modLoader: ModLoaderType,
)

View File

@ -0,0 +1,7 @@
package top.jie65535.jcf.model.file
@kotlinx.serialization.Serializable
class FileModule(
val name: String,
val fingerprint: Long,
)

View File

@ -0,0 +1,16 @@
package top.jie65535.jcf.model.file
import kotlinx.serialization.KSerializer
import top.jie65535.jcf.util.EnumIndexSerializer
@kotlinx.serialization.Serializable(with = FileRelationType.IndexSerializer::class)
enum class FileRelationType {
EmbeddedLibrary,
OptionalDependency,
RequiredDependency,
Tool,
Incompatible,
Include;
internal object IndexSerializer : KSerializer<FileRelationType> by EnumIndexSerializer()
}

View File

@ -0,0 +1,13 @@
package top.jie65535.jcf.model.file
import kotlinx.serialization.KSerializer
import top.jie65535.jcf.util.EnumIndexSerializer
@kotlinx.serialization.Serializable(with = FileReleaseType.IndexSerializer::class)
enum class FileReleaseType {
Release,
Beta,
Alpha;
internal object IndexSerializer : KSerializer<FileReleaseType> by EnumIndexSerializer()
}

View File

@ -0,0 +1,25 @@
package top.jie65535.jcf.model.file
import kotlinx.serialization.KSerializer
import top.jie65535.jcf.util.EnumIndexSerializer
@kotlinx.serialization.Serializable(with = FileStatus.IndexSerializer::class)
enum class FileStatus{
Processing,
ChangesRequired,
UnderReview,
Approved,
Rejected,
MalwareDetected,
Deleted,
Archived,
Testing,
Released,
ReadyForReview,
Deprecated,
Baking,
AwaitingPublishing,
FailedPublishing;
internal object IndexSerializer : KSerializer<FileStatus> by EnumIndexSerializer()
}

View File

@ -0,0 +1,12 @@
package top.jie65535.jcf.model.file
import kotlinx.serialization.KSerializer
import top.jie65535.jcf.util.EnumIndexSerializer
@kotlinx.serialization.Serializable(with = HashAlgo.IndexSerializer::class)
enum class HashAlgo(val value: Int) {
Sha1(1),
Md5(2);
internal object IndexSerializer : KSerializer<HashAlgo> by EnumIndexSerializer()
}

View File

@ -0,0 +1,116 @@
package top.jie65535.jcf.model.mod
import kotlinx.serialization.Serializable
import top.jie65535.jcf.model.Category
import top.jie65535.jcf.model.file.File
import top.jie65535.jcf.model.file.FileIndex
import top.jie65535.jcf.util.OffsetDateTimeSerializer
import java.time.OffsetDateTime
@kotlinx.serialization.Serializable
class Mod(
/**
* The mod id
*/
val id: Int,
/**
* The game id this mod is for
*/
val gameId: Int,
/**
* The name of the mod
*/
val name: String,
/**
* The mod slug that would appear in the URL
*/
val slug: String,
/**
* Relevant links for the mod such as Issue tracker and Wiki
*/
val links: ModLinks,
/**
* Mod summary
*/
val summary: String,
/**
* Current mod status
*/
val status: ModStatus,
/**
* Number of downloads for the mod
*/
val downloadCount: Long,
/**
* Whether the mod is included in the featured mods list
*/
val isFeatured: Boolean,
/**
* The main category of the mod as it was chosen by the mod author
*/
val primaryCategoryId: Int,
/**
* List of categories that this mod is related to
*/
val categories: Array<Category>,
/**
* The class id this mod belongs to
*/
val classId: Int?,
/**
* List of the mod's authors
*/
val authors: Array<ModAuthor>,
/**
* The mod's logo asset
*/
val logo: ModAsset,
/**
* List of screenshots assets
*/
val screenshots: Array<ModAsset>,
/**
* The id of the main file of the mod
*/
val mainFileId: Int,
/**
* List of latest files of the mod
*/
val latestFiles: Array<File>,
/**
* List of file related details for the latest files of the mod
*/
val latestFilesIndexes: Array<FileIndex>,
/**
* The creation date of the mod
*/
@Serializable(OffsetDateTimeSerializer::class)
val dateCreated: OffsetDateTime,
/**
* The last time the mod was modified
*/
@Serializable(OffsetDateTimeSerializer::class)
val dateModified: OffsetDateTime,
/**
* The release date of the mod
*/
@Serializable(OffsetDateTimeSerializer::class)
val dateReleased: OffsetDateTime,
/**
* Is mod allowed to be distributed
*/
val allowModDistribution: Boolean?,
/**
* The mod popularity rank for the game
*/
val gamePopularityRank: Int,
/**
* Is the mod available for search. This can be false when a mod is experimental,
* in a deleted state or has only alpha files
*/
val isAvailable: Boolean,
/**
* The mod's thumbs up count
*/
val thumbsUpCount: Int,
)

View File

@ -0,0 +1,11 @@
package top.jie65535.jcf.model.mod
@kotlinx.serialization.Serializable
class ModAsset(
val id: Int,
val modId: Int,
val title: String,
val description: String,
val thumbnailUrl: String,
val url: String,
)

View File

@ -0,0 +1,8 @@
package top.jie65535.jcf.model.mod
@kotlinx.serialization.Serializable
class ModAuthor(
val id: Int,
val name: String,
val url: String,
)

View File

@ -0,0 +1,9 @@
package top.jie65535.jcf.model.mod
@kotlinx.serialization.Serializable
class ModLinks(
val websiteUrl: String,
val wikiUrl: String,
val issuesUrl: String,
val sourceUrl: String
)

View File

@ -0,0 +1,16 @@
package top.jie65535.jcf.model.mod
import kotlinx.serialization.KSerializer
import top.jie65535.jcf.util.EnumIndexSerializer
@kotlinx.serialization.Serializable(with = ModLoaderType.IndexSerializer::class)
enum class ModLoaderType {
Any,
Forge,
Cauldron,
LiteLoader,
Fabric,
Quilt;
internal object IndexSerializer : KSerializer<ModLoaderType> by EnumIndexSerializer()
}

View File

@ -0,0 +1,20 @@
package top.jie65535.jcf.model.mod
import kotlinx.serialization.KSerializer
import top.jie65535.jcf.util.EnumIndexSerializer
@kotlinx.serialization.Serializable(with = ModStatus.IndexSerializer::class)
enum class ModStatus {
New,
ChangesRequired,
UnderSoftReview,
Approved,
Rejected,
ChangesMade,
Inactive,
Abandoned,
Deleted,
UnderReview;
internal object IndexSerializer : KSerializer<ModStatus> by EnumIndexSerializer()
}

View File

@ -0,0 +1,8 @@
package top.jie65535.jcf.model.request
@kotlinx.serialization.Serializable
class GetFeaturedModsRequestBody(
val gameId: Int,
val excludedModIds: IntArray,
val gameVersionTypeId: Int?,
)

View File

@ -0,0 +1,6 @@
package top.jie65535.jcf.model.request
@kotlinx.serialization.Serializable
class GetModsByIdsListRequestBody(
val modIds: IntArray
)

View File

@ -0,0 +1,18 @@
package top.jie65535.jcf.model.request
import kotlinx.serialization.KSerializer
import top.jie65535.jcf.util.EnumIndexSerializer
@kotlinx.serialization.Serializable(with = ModsSearchSortField.IndexSerializer::class)
enum class ModsSearchSortField {
Featured,
Popularity,
LastUpdated,
Name,
Author,
TotalDownloads,
Category,
GameVersion;
internal object IndexSerializer : KSerializer<ModsSearchSortField> by EnumIndexSerializer()
}

View File

@ -0,0 +1,7 @@
package top.jie65535.jcf.model.request
@kotlinx.serialization.Serializable
enum class SortOrder {
ASC,
DESC,
}

View File

@ -0,0 +1,10 @@
package top.jie65535.jcf.model.response
import top.jie65535.jcf.model.mod.Mod
@kotlinx.serialization.Serializable
class FeaturedModsResponse(
val featured: Array<Mod>,
val popular: Array<Mod>,
val recentlyUpdated: Array<Mod>,
)

View File

@ -0,0 +1,6 @@
package top.jie65535.jcf.model.response
@kotlinx.serialization.Serializable
class GetFeaturedModsResponse(
val data: FeaturedModsResponse
)

View File

@ -0,0 +1,8 @@
package top.jie65535.jcf.model.response
import top.jie65535.jcf.model.mod.Mod
@kotlinx.serialization.Serializable
class GetModResponse(
val data: Mod
)

View File

@ -0,0 +1,8 @@
package top.jie65535.jcf.model.response
import top.jie65535.jcf.model.mod.Mod
@kotlinx.serialization.Serializable
class GetModsResponse(
val data: Array<Mod>
)

View File

@ -0,0 +1,10 @@
package top.jie65535.jcf.model.response
import top.jie65535.jcf.model.Pagination
import top.jie65535.jcf.model.mod.Mod
@kotlinx.serialization.Serializable
class SearchModsResponse(
val data: Mod,
val pagination: Pagination,
)

View File

@ -0,0 +1,24 @@
package top.jie65535.jcf.util
import kotlinx.serialization.KSerializer
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
@Suppress("FunctionName")
inline fun <reified T : Enum<T>> EnumIndexSerializer(offset: Int = 1): KSerializer<T> {
return object : KSerializer<T> {
override val descriptor: SerialDescriptor =
PrimitiveSerialDescriptor(T::class.qualifiedName!!, PrimitiveKind.INT)
override fun serialize(encoder: Encoder, value: T) =
encoder.encodeInt(value.ordinal + offset)
override fun deserialize(decoder: Decoder): T =
requireNotNull(enumValues<T>().getOrNull(decoder.decodeInt())) {
"index: ${decoder.decodeInt()} not in ${enumValues<T>()}"
}
}
}

View File

@ -0,0 +1,25 @@
package top.jie65535.jcf.util
import kotlinx.serialization.KSerializer
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import java.time.OffsetDateTime
import java.time.format.DateTimeFormatter
object OffsetDateTimeSerializer : KSerializer<OffsetDateTime> {
private val formatter: DateTimeFormatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME
override val descriptor: SerialDescriptor =
PrimitiveSerialDescriptor(OffsetDateTime::class.qualifiedName!!, PrimitiveKind.STRING)
override fun deserialize(decoder: Decoder): OffsetDateTime =
OffsetDateTime.parse(decoder.decodeString(), formatter)
override fun serialize(encoder: Encoder, value: OffsetDateTime) =
encoder.encodeString(formatter.format(value))
}