mirror of
https://github.com/ReVanced/revanced-api.git
synced 2026-01-29 14:11:04 +00:00
feat: Initialize project
This commit is contained in:
140
src/main/kotlin/app/revanced/api/backend/Backend.kt
Normal file
140
src/main/kotlin/app/revanced/api/backend/Backend.kt
Normal file
@@ -0,0 +1,140 @@
|
||||
package app.revanced.api.backend
|
||||
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.engine.okhttp.*
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* The backend of the application used to get data for the API.
|
||||
*
|
||||
* @param httpClientConfig The configuration of the HTTP client.
|
||||
*/
|
||||
abstract class Backend(
|
||||
httpClientConfig: HttpClientConfig<OkHttpConfig>.() -> Unit = {}
|
||||
) {
|
||||
protected val client: HttpClient = HttpClient(OkHttp, httpClientConfig)
|
||||
|
||||
/**
|
||||
* A user.
|
||||
*
|
||||
* @property name The name of the user.
|
||||
* @property avatarUrl The URL to the avatar of the user.
|
||||
* @property profileUrl The URL to the profile of the user.
|
||||
*/
|
||||
interface User {
|
||||
val name: String
|
||||
val avatarUrl: String
|
||||
val profileUrl: String
|
||||
}
|
||||
|
||||
/**
|
||||
* An organization.
|
||||
*
|
||||
* @property members The members of the organization.
|
||||
*/
|
||||
class Organization(
|
||||
val members: Set<Member>
|
||||
) {
|
||||
/**
|
||||
* A member of an organization.
|
||||
*
|
||||
* @property name The name of the member.
|
||||
* @property avatarUrl The URL to the avatar of the member.
|
||||
* @property profileUrl The URL to the profile of the member.
|
||||
* @property bio The bio of the member.
|
||||
* @property gpgKeysUrl The URL to the GPG keys of the member.
|
||||
*/
|
||||
@Serializable
|
||||
class Member (
|
||||
override val name: String,
|
||||
override val avatarUrl: String,
|
||||
override val profileUrl: String,
|
||||
val bio: String?,
|
||||
val gpgKeysUrl: String?
|
||||
) : User
|
||||
|
||||
/**
|
||||
* A repository of an organization.
|
||||
*
|
||||
* @property contributors The contributors of the repository.
|
||||
*/
|
||||
class Repository(
|
||||
val contributors: Set<Contributor>
|
||||
) {
|
||||
/**
|
||||
* A contributor of a repository.
|
||||
*
|
||||
* @property name The name of the contributor.
|
||||
* @property avatarUrl The URL to the avatar of the contributor.
|
||||
* @property profileUrl The URL to the profile of the contributor.
|
||||
*/
|
||||
@Serializable
|
||||
class Contributor(
|
||||
override val name: String,
|
||||
override val avatarUrl: String,
|
||||
override val profileUrl: String
|
||||
) : User
|
||||
|
||||
/**
|
||||
* A release of a repository.
|
||||
*
|
||||
* @property tag The tag of the release.
|
||||
* @property assets The assets of the release.
|
||||
* @property createdAt The date and time the release was created.
|
||||
* @property releaseNote The release note of the release.
|
||||
*/
|
||||
@Serializable
|
||||
class Release(
|
||||
val tag: String,
|
||||
val releaseNote: String,
|
||||
val createdAt: String,
|
||||
val assets: Set<Asset>
|
||||
) {
|
||||
/**
|
||||
* An asset of a release.
|
||||
*
|
||||
* @property downloadUrl The URL to download the asset.
|
||||
*/
|
||||
@Serializable
|
||||
class Asset(
|
||||
val downloadUrl: String
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a release of a repository.
|
||||
*
|
||||
* @param owner The owner of the repository.
|
||||
* @param repository The name of the repository.
|
||||
* @param tag The tag of the release. If null, the latest release is returned.
|
||||
* @param preRelease Whether to return a pre-release.
|
||||
* If no pre-release exists, the latest release is returned.
|
||||
* If tag is not null, this parameter is ignored.
|
||||
* @return The release.
|
||||
*/
|
||||
abstract suspend fun getRelease(
|
||||
owner: String,
|
||||
repository: String,
|
||||
tag: String? = null,
|
||||
preRelease: Boolean = false
|
||||
): Organization.Repository.Release
|
||||
|
||||
/**
|
||||
* Get the contributors of a repository.
|
||||
*
|
||||
* @param owner The owner of the repository.
|
||||
* @param repository The name of the repository.
|
||||
* @return The contributors.
|
||||
*/
|
||||
abstract suspend fun getContributors(owner: String, repository: String): Set<Organization.Repository.Contributor>
|
||||
|
||||
/**
|
||||
* Get the members of an organization.
|
||||
*
|
||||
* @param organization The name of the organization.
|
||||
* @return The members.
|
||||
*/
|
||||
abstract suspend fun getMembers(organization: String): Set<Organization.Member>
|
||||
}
|
||||
116
src/main/kotlin/app/revanced/api/backend/github/GitHubBackend.kt
Normal file
116
src/main/kotlin/app/revanced/api/backend/github/GitHubBackend.kt
Normal file
@@ -0,0 +1,116 @@
|
||||
package app.revanced.api.backend.github
|
||||
|
||||
import app.revanced.api.backend.Backend
|
||||
import app.revanced.api.backend.github.api.Request
|
||||
import io.ktor.client.call.*
|
||||
import io.ktor.client.plugins.*
|
||||
import io.ktor.client.plugins.auth.*
|
||||
import io.ktor.client.plugins.auth.providers.*
|
||||
import io.ktor.client.plugins.cache.*
|
||||
import io.ktor.client.plugins.contentnegotiation.*
|
||||
import io.ktor.client.plugins.resources.*
|
||||
import app.revanced.api.backend.github.api.Request.Organization.Repository.Releases
|
||||
import app.revanced.api.backend.github.api.Request.Organization.Repository.Contributors
|
||||
import app.revanced.api.backend.github.api.Request.Organization.Members
|
||||
import app.revanced.api.backend.github.api.Response
|
||||
import app.revanced.api.backend.github.api.Response.Organization.Repository.Release
|
||||
import app.revanced.api.backend.github.api.Response.Organization.Repository.Contributor
|
||||
import app.revanced.api.backend.github.api.Response.Organization.Member
|
||||
import io.ktor.client.plugins.resources.Resources
|
||||
import io.ktor.serialization.kotlinx.json.*
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonNamingStrategy
|
||||
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
class GitHubBackend(token: String? = null) : Backend({
|
||||
install(HttpCache)
|
||||
install(Resources)
|
||||
install(ContentNegotiation) {
|
||||
json(Json {
|
||||
ignoreUnknownKeys = true
|
||||
namingStrategy = JsonNamingStrategy.SnakeCase
|
||||
})
|
||||
}
|
||||
|
||||
defaultRequest { url("https://api.github.com") }
|
||||
|
||||
token?.let {
|
||||
install(Auth) {
|
||||
bearer {
|
||||
loadTokens {
|
||||
BearerTokens(
|
||||
accessToken = it,
|
||||
refreshToken = "" // Required dummy value
|
||||
)
|
||||
}
|
||||
|
||||
sendWithoutRequest { true }
|
||||
}
|
||||
}
|
||||
}
|
||||
}) {
|
||||
override suspend fun getRelease(
|
||||
owner: String,
|
||||
repository: String,
|
||||
tag: String?,
|
||||
preRelease: Boolean
|
||||
): Organization.Repository.Release {
|
||||
val release = if (preRelease) {
|
||||
val releases: Set<Release> = client.get(Releases(owner, repository)).body()
|
||||
releases.firstOrNull { it.preReleases } ?: releases.first() // Latest pre-release or latest release
|
||||
} else {
|
||||
client.get(
|
||||
tag?.let { Releases.Tag(owner, repository, it) }
|
||||
?: Releases.Latest(owner, repository)
|
||||
).body()
|
||||
}
|
||||
|
||||
return Organization.Repository.Release(
|
||||
tag = release.tagName,
|
||||
releaseNote = release.body,
|
||||
createdAt = release.createdAt,
|
||||
assets = release.assets.map {
|
||||
Organization.Repository.Release.Asset(
|
||||
downloadUrl = it.browserDownloadUrl
|
||||
)
|
||||
}.toSet()
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getContributors(owner: String, repository: String): Set<Organization.Repository.Contributor> {
|
||||
val contributors: Set<Contributor> = client.get(Contributors(owner, repository)).body()
|
||||
|
||||
return contributors.map {
|
||||
Organization.Repository.Contributor(
|
||||
name = it.login,
|
||||
avatarUrl = it.avatarUrl,
|
||||
profileUrl = it.url
|
||||
)
|
||||
}.toSet()
|
||||
}
|
||||
|
||||
override suspend fun getMembers(organization: String): Set<Organization.Member> {
|
||||
// Get the list of members of the organization.
|
||||
val members: Set<Member> = client.get(Members(organization)).body<Set<Member>>()
|
||||
|
||||
return runBlocking(Dispatchers.Default) {
|
||||
members.map { member ->
|
||||
// Map the member to a user in order to get the bio.
|
||||
async {
|
||||
client.get(Request.User(member.login)).body<Response.User>()
|
||||
}
|
||||
}
|
||||
}.awaitAll().map { user ->
|
||||
// Map the user back to a member.
|
||||
Organization.Member(
|
||||
name = user.login,
|
||||
avatarUrl = user.avatarUrl,
|
||||
profileUrl = user.url,
|
||||
bio = user.bio,
|
||||
gpgKeysUrl = "https://github.com/${user.login}.gpg",
|
||||
)
|
||||
}.toSet()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package app.revanced.api.backend.github.api
|
||||
|
||||
import io.ktor.resources.*
|
||||
|
||||
class Request {
|
||||
@Resource("/users/{username}")
|
||||
class User(val username: String)
|
||||
class Organization {
|
||||
@Resource("/orgs/{org}/members")
|
||||
class Members(val org: String)
|
||||
|
||||
class Repository {
|
||||
@Resource("/repos/{owner}/{repo}/contributors")
|
||||
class Contributors(val owner: String, val repo: String)
|
||||
|
||||
@Resource("/repos/{owner}/{repo}/releases")
|
||||
class Releases(val owner: String, val repo: String) {
|
||||
@Resource("/repos/{owner}/{repo}/releases/tags/{tag}")
|
||||
class Tag(val owner: String, val repo: String, val tag: String)
|
||||
|
||||
@Resource("/repos/{owner}/{repo}/releases/latest")
|
||||
class Latest(val owner: String, val repo: String)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package app.revanced.api.backend.github.api
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
|
||||
class Response {
|
||||
interface IUser {
|
||||
val login: String
|
||||
val avatarUrl: String
|
||||
val url: String
|
||||
}
|
||||
|
||||
@Serializable
|
||||
class User (
|
||||
override val login: String,
|
||||
override val avatarUrl: String,
|
||||
override val url: String,
|
||||
val bio: String?,
|
||||
) : IUser
|
||||
|
||||
class Organization {
|
||||
@Serializable
|
||||
class Member(
|
||||
override val login: String,
|
||||
override val avatarUrl: String,
|
||||
override val url: String,
|
||||
) : IUser
|
||||
|
||||
class Repository {
|
||||
@Serializable
|
||||
class Contributor(
|
||||
override val login: String,
|
||||
override val avatarUrl: String,
|
||||
override val url: String,
|
||||
) : IUser
|
||||
|
||||
@Serializable
|
||||
class Release(
|
||||
val tagName: String,
|
||||
val assets: Set<Asset>,
|
||||
val preReleases: Boolean,
|
||||
val createdAt: String,
|
||||
val body: String
|
||||
) {
|
||||
@Serializable
|
||||
class Asset(
|
||||
val browserDownloadUrl: String
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user