refactor: split up core into multiple modules

This commit is contained in:
Cilly Leang 2026-02-05 01:05:02 +11:00
parent 2725342c3f
commit 0d84411f14
Signed by: cilly
GPG key ID: 6500251E087653C9
38 changed files with 344 additions and 149 deletions

65
api/rest/build.gradle.kts Normal file
View file

@ -0,0 +1,65 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
alias(libs.plugins.androidLibrary)
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.kotlinSerialization)
}
kotlin {
jvm()
androidTarget {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_11)
}
}
sourceSets {
commonMain.dependencies {
implementation(project(":api:shared"))
implementation(project(":common"))
implementation(libs.kermit)
implementation(libs.ktor.client.core)
implementation(libs.ktor.client.content.negotiation)
implementation(libs.ktor.serialization.kotlinx.json)
}
commonTest.dependencies {
implementation(libs.kotlin.test)
}
jvmMain.dependencies {
implementation(libs.ktor.client.okhttp)
}
androidMain.dependencies {
implementation(libs.ktor.client.okhttp)
}
}
}
dependencies {
coreLibraryDesugaring(libs.desugar)
}
android {
namespace = "moe.lava.neon.api.rest"
compileSdk = libs.versions.android.compileSdk.get().toInt()
defaultConfig {
minSdk = libs.versions.android.minSdk.get().toInt()
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
buildTypes {
getByName("release") {
isMinifyEnabled = false
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
isCoreLibraryDesugaringEnabled = true
}
}

View file

@ -0,0 +1,81 @@
package moe.lava.neon.api
import co.touchlab.kermit.Logger
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.plugins.HttpSend
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.plugins.cookies.HttpCookies
import io.ktor.client.plugins.defaultRequest
import io.ktor.client.plugins.plugin
import io.ktor.client.plugins.websocket.WebSockets
import io.ktor.client.request.header
import io.ktor.client.statement.bodyAsText
import io.ktor.http.userAgent
import io.ktor.serialization.kotlinx.json.json
import io.ktor.util.appendAll
import kotlinx.serialization.ExperimentalSerializationApi
import moe.lava.neon.common.captcha.CaptchaRequest
import moe.lava.neon.common.captcha.CaptchaResponse
class ApiClient {
private val logger = Logger.withTag("neon.core.api/client")
private var captchaHandler: (suspend (CaptchaRequest) -> CaptchaResponse)? = null
fun setCaptchaHandler(handler: suspend (CaptchaRequest) -> CaptchaResponse) {
this.captchaHandler = handler
}
@OptIn(ExperimentalSerializationApi::class)
val client = HttpClient {
expectSuccess = true
install(ContentNegotiation) {
json(ApiConstants.json)
}
install(WebSockets)
install(HttpCookies)
defaultRequest {
url("https://discord.com/api/v9/")
userAgent(ApiConstants.userAgent)
headers.appendAll(ApiConstants.baseHeaders)
}
}.apply {
plugin(HttpSend).intercept { req ->
logger.d { "Intercepting ${req.url.buildString()}" }
val call = execute(req)
if (call.response.status.value != 400) return@intercept call
logger.d { "Found 400 response: ${call.response.bodyAsText()}" }
val captchaRequest = runCatching { call.response.body<CaptchaRequest>() }
.getOrNull()
?: return@intercept call
logger.d { "Starting captcha flow for: $captchaRequest" }
val captcha = captchaHandler
if (captcha == null) {
logger.w { "Captcha handler not found, passing through!" }
return@intercept call
}
val solved = captcha(captchaRequest)
logger.d { "Captcha solved $solved" }
if (solved !is CaptchaResponse.Success) {
val failure = solved as CaptchaResponse.Failed
logger.w(failure.error) { "Captcha failed" }
return@intercept call
}
logger.d { "Refiring" }
req.apply {
header("X-Captcha-Key", solved.token)
if (captchaRequest.captchaSessionId != null) {
header("X-Captcha-Session-Id", captchaRequest.captchaSessionId)
}
if (captchaRequest.captchaRqtoken != null) {
header("X-Captcha-Rqtoken", captchaRequest.captchaRqtoken)
}
}.let { execute(it) }
}
}
}