[update] Remove token from Query param, move to cookies

This commit is contained in:
acite
2025-09-29 01:19:12 +08:00
parent 393419afd7
commit 422da51a74
9 changed files with 61 additions and 105 deletions

View File

@@ -46,6 +46,8 @@ android {
}
dependencies {
implementation(libs.persistentcookiejar)
implementation(libs.fetch2)
implementation(libs.fetch2okhttp)

View File

@@ -4,13 +4,12 @@ import com.acitelight.aether.service.ApiClient
class Comic(
val comic: ComicResponse,
val id: String,
val token: String
val id: String
)
{
fun getPage(pageNumber: Int, api: ApiClient): String
{
return "${api.getBase()}api/image/$id/${comic.list[pageNumber]}?token=$token"
return "${api.getBase()}api/image/$id/${comic.list[pageNumber]}"
}
fun getPage(pageName: String, api: ApiClient): String?

View File

@@ -11,28 +11,27 @@ class Video(
val localBase: String,
val klass: String,
val id: String,
val token: String,
val video: VideoResponse
) {
fun getCover(api: ApiClient): String {
return if (isLocal)
"$localBase/videos/$klass/$id/cover.jpg"
else
"${api.getBase()}api/video/$klass/$id/cover?token=$token"
"${api.getBase()}api/video/$klass/$id/cover"
}
fun getVideo(api: ApiClient): String {
return if (isLocal)
"$localBase/videos/$klass/$id/video.mp4"
else
"${api.getBase()}api/video/$klass/$id/av?token=$token"
"${api.getBase()}api/video/$klass/$id/av"
}
fun getSubtitle(api: ApiClient): String {
return if (isLocal)
"$localBase/videos/$klass/$id/subtitle.vtt"
else
"${api.getBase()}api/video/$klass/$id/subtitle?token=$token"
"${api.getBase()}api/video/$klass/$id/subtitle"
}
fun getGallery(api: ApiClient): List<KeyImage> {
@@ -46,7 +45,7 @@ class Video(
} else video.gallery.map {
KeyImage(
name = it,
url = "${api.getBase()}api/video/$klass/$id/gallery/$it?token=$token",
url = "${api.getBase()}api/video/$klass/$id/gallery/$it",
key = "$klass/$id/gallery/$it"
)
}
@@ -59,7 +58,6 @@ class Video(
localBase = localBase,
klass = klass,
id = id,
token = "",
video = video
)
}

View File

@@ -5,7 +5,11 @@ import android.content.Context
import android.util.Log
import androidx.core.net.toUri
import com.acitelight.aether.AetherApp
import com.franmontiel.persistentcookiejar.PersistentCookieJar
import com.franmontiel.persistentcookiejar.cache.SetCookieCache
import com.franmontiel.persistentcookiejar.persistence.SharedPrefsCookiePersistor
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json
@@ -15,11 +19,13 @@ import okhttp3.CookieJar
import okhttp3.EventListener
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.JavaNetCookieJar
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.io.ByteArrayInputStream
import java.net.CookieManager
import java.net.InetAddress
import java.net.InetSocketAddress
import java.net.Proxy
@@ -35,7 +41,7 @@ import javax.net.ssl.X509TrustManager
@Singleton
class ApiClient @Inject constructor(
@ApplicationContext private val context: Context,
) {
fun getBase(): String{
return replaceAbyssProtocol(base)
@@ -46,11 +52,9 @@ class ApiClient @Inject constructor(
private val json = Json {
ignoreUnknownKeys = true
}
private fun replaceAbyssProtocol(uri: String): String {
return uri.replaceFirst("^abyss://".toRegex(), "https://")
}
private val dnsEventListener = object : EventListener() {
override fun dnsEnd(call: okhttp3.Call, domainName: String, inetAddressList: List<InetAddress>) {
super.dnsEnd(call, domainName, inetAddressList)
@@ -58,7 +62,6 @@ class ApiClient @Inject constructor(
Log.d("OkHttp_DNS", "Domain '$domainName' resolved to IPs: [$ipAddresses]")
}
}
private fun loadCertificateFromString(pemString: String): X509Certificate {
val certificateFactory = CertificateFactory.getInstance("X.509")
val decodedPem = pemString
@@ -72,7 +75,6 @@ class ApiClient @Inject constructor(
return certificateFactory.generateCertificate(inputStream) as X509Certificate
}
}
private fun createOkHttpClientWithDynamicCert(trustedCert: X509Certificate?): OkHttpClient {
try {
val defaultTmFactory = TrustManagerFactory.getInstance(
@@ -163,23 +165,17 @@ class ApiClient @Inject constructor(
throw RuntimeException("Failed to create OkHttpClient with dynamic certificate", e)
}
}
private fun createOkHttp(): OkHttpClient {
return if (cert == "")
if (base.startsWith("abyss://"))
OkHttpClient
.Builder()
.cookieJar(object : CookieJar {
private val cookieStore = mutableMapOf<HttpUrl, List<Cookie>>()
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
cookieStore[url] = cookies
}
override fun loadForRequest(url: HttpUrl): List<Cookie> {
return cookieStore[url] ?: emptyList()
}
})
.cookieJar(
PersistentCookieJar(
SetCookieCache(),
SharedPrefsCookiePersistor(context)
)
)
.proxy(
Proxy(
Proxy.Type.HTTP,
@@ -192,17 +188,12 @@ class ApiClient @Inject constructor(
else
OkHttpClient
.Builder()
.cookieJar(object : CookieJar {
private val cookieStore = mutableMapOf<HttpUrl, List<Cookie>>()
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
cookieStore[url] = cookies
}
override fun loadForRequest(url: HttpUrl): List<Cookie> {
return cookieStore[url] ?: emptyList()
}
})
.cookieJar(
PersistentCookieJar(
SetCookieCache(),
SharedPrefsCookiePersistor(context)
)
)
.connectionSpecs(listOf(ConnectionSpec.MODERN_TLS, ConnectionSpec.COMPATIBLE_TLS))
.eventListener(dnsEventListener)
.build()
@@ -210,7 +201,6 @@ class ApiClient @Inject constructor(
createOkHttpClientWithDynamicCert(loadCertificateFromString(cert))
}
private fun createRetrofit(): Retrofit {
client = createOkHttp()
val b = replaceAbyssProtocol(base)
@@ -222,7 +212,6 @@ class ApiClient @Inject constructor(
.addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
.build()
}
private var client: OkHttpClient? = null
var api: ApiInterface? = null

View File

@@ -4,49 +4,43 @@ import com.acitelight.aether.model.BookMark
import com.acitelight.aether.model.ChallengeResponse
import com.acitelight.aether.model.ComicResponse
import com.acitelight.aether.model.VideoResponse
import okhttp3.Response
import okhttp3.ResponseBody
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.Path
import retrofit2.http.Query
import retrofit2.http.Streaming
interface ApiInterface {
@GET("api/video")
suspend fun getVideoClasses(
@Query("token") token: String
): List<String>
@GET("api/video/{klass}")
suspend fun queryVideoClasses(
@Path("klass") klass: String,
@Query("token") token: String
@Path("klass") klass: String
): List<String>
@GET("api/video/{klass}/{id}")
suspend fun queryVideo(
@Path("klass") klass: String,
@Path("id") id: String,
@Query("token") token: String
@Path("id") id: String
): VideoResponse
@POST("api/video/{klass}/bulkquery")
suspend fun queryVideoBulk(
@Path("klass") klass: String,
@Body() id: List<String>,
@Query("token") token: String
@Body() id: List<String>
): List<VideoResponse>
@GET("api/image")
suspend fun getComics(@Query("token") token: String): List<String>
suspend fun getComics(): List<String>
@GET("api/image/{id}")
suspend fun queryComicInfo(@Path("id") id: String, @Query("token") token: String): ComicResponse
suspend fun queryComicInfo(@Path("id") id: String): ComicResponse
@POST("api/image/bulkquery")
suspend fun queryComicInfoBulk(@Body() id: List<String>, @Query("token") token: String): List<ComicResponse>
suspend fun queryComicInfoBulk(@Body() id: List<String>): List<ComicResponse>
@POST("api/image/{id}/bookmark")
suspend fun postBookmark(@Path("id") id: String, @Query("token") token: String, @Body bookmark: BookMark)
suspend fun postBookmark(@Path("id") id: String, @Body bookmark: BookMark)
@GET("api/user/{user}")
suspend fun getChallenge(

View File

@@ -20,13 +20,11 @@ class MediaManager @Inject constructor(
private val apiClient: ApiClient
)
{
var token: String = "null"
suspend fun listVideoKlasses(): List<String>
{
try
{
val j = apiClient.api!!.getVideoClasses(token)
val j = apiClient.api!!.getVideoClasses()
return j.toList()
}catch(_: Exception)
{
@@ -38,7 +36,7 @@ class MediaManager @Inject constructor(
{
try
{
val j = apiClient.api!!.queryVideoClasses(klass, token)
val j = apiClient.api!!.queryVideoClasses(klass)
return j.toList()
}catch(_: Exception)
{
@@ -58,8 +56,8 @@ class MediaManager @Inject constructor(
}
try {
val j = apiClient.api!!.queryVideo(klass, id, token)
return Video(klass = klass, id = id, token=token, isLocal = false, localBase = "", video = j)
val j = apiClient.api!!.queryVideo(klass, id)
return Video(klass = klass, id = id, isLocal = false, localBase = "", video = j)
}catch (_: Exception)
{
return null
@@ -84,8 +82,8 @@ class MediaManager @Inject constructor(
}
try {
val j = apiClient.api!!.queryVideo(klass, id, token)
return Video(klass = klass, id = id, token=token, isLocal = false, localBase = "", video = j)
val j = apiClient.api!!.queryVideo(klass, id)
return Video(klass = klass, id = id, isLocal = false, localBase = "", video = j)
}catch (_: Exception)
{
return null
@@ -134,12 +132,11 @@ class MediaManager @Inject constructor(
}
val remoteVideos = if (remoteIds.isNotEmpty()) {
val j = apiClient.api!!.queryVideoBulk(klass, remoteIds, token)
val j = apiClient.api!!.queryVideoBulk(klass, remoteIds)
j.zip(remoteIds).map {
Video(
klass = klass,
id = it.second,
token = token,
isLocal = false,
localBase = "",
video = it.first
@@ -158,7 +155,7 @@ class MediaManager @Inject constructor(
suspend fun listComics() : List<String>
{
try{
val j = apiClient.api!!.getComics(token)
val j = apiClient.api!!.getComics()
return j
}catch (_: Exception)
{
@@ -169,8 +166,8 @@ class MediaManager @Inject constructor(
suspend fun queryComicInfoSingle(id: String) : Comic?
{
try{
val j = apiClient.api!!.queryComicInfo(id, token)
return Comic(id = id, comic = j, token = token)
val j = apiClient.api!!.queryComicInfo(id)
return Comic(id = id, comic = j)
}catch (_: Exception)
{
return null
@@ -180,8 +177,8 @@ class MediaManager @Inject constructor(
suspend fun queryComicInfoBulk(id: List<String>) : List<Comic>?
{
try{
val j = apiClient.api!!.queryComicInfoBulk(id, token)
return j.zip(id).map { Comic(id = it.second, comic = it.first, token = token) }
val j = apiClient.api!!.queryComicInfoBulk(id)
return j.zip(id).map { Comic(id = it.second, comic = it.first) }
}catch (_: Exception)
{
return null
@@ -191,7 +188,7 @@ class MediaManager @Inject constructor(
suspend fun postBookmark(id: String, bookMark: BookMark): Boolean
{
try{
apiClient.api!!.postBookmark(id, token, bookMark)
apiClient.api!!.postBookmark(id, bookMark)
return true
}catch (_: Exception)
{

View File

@@ -3,9 +3,7 @@ package com.acitelight.aether.view
import android.widget.Toast
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.combinedClickable
@@ -26,9 +24,6 @@ import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells
@@ -39,20 +34,15 @@ import androidx.compose.foundation.text.BasicTextField
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Menu
import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.CheckboxDefaults.colors
import androidx.compose.material3.DividerDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Tab
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@@ -60,44 +50,29 @@ import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import coil3.compose.AsyncImage
import com.acitelight.aether.model.Video
import com.acitelight.aether.viewModel.VideoScreenViewModel
import androidx.compose.material3.ScrollableTabRow
import androidx.compose.material3.Surface
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.min
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.viewModelScope
import androidx.navigation.NavHostController
import coil3.request.ImageRequest
import com.acitelight.aether.CardPage
import com.acitelight.aether.Global
import com.acitelight.aether.Global.updateRelate
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.nio.charset.Charset
import java.security.KeyPair
import kotlin.collections.sortedWith
fun videoTOView(v: List<Video>): Map<String?, List<Video>>
fun videoToView(v: List<Video>): Map<String?, List<Video>>
{
return v.map { if(it.video.group != null) it else Video(id=it.id, isLocal = it.isLocal, localBase = it.localBase,
klass = it.klass, token = it.token, video = it.video.copy(group = it.video.name)) }.groupBy { it.video.group }
klass = it.klass, video = it.video.copy(group = it.video.name)) }.groupBy { it.video.group }
}
fun String.toHex(): String {
@@ -126,7 +101,7 @@ fun VideoScreen(
var menuVisibility by videoScreenViewModel.menuVisibility
var searchFilter by videoScreenViewModel.searchFilter
var doneInit by videoScreenViewModel.doneInit
val vb = videoTOView(videoScreenViewModel.videoLibrary.classesMap.getOrDefault(
val vb = videoToView(videoScreenViewModel.videoLibrary.classesMap.getOrDefault(
videoScreenViewModel.videoLibrary.classes.getOrNull(
tabIndex
), listOf()

View File

@@ -48,11 +48,10 @@ class MeScreenViewModel @Inject constructor(
try{
apiClient.apply(context, url.value, if(uss.first()) cert.value else "")
if (mediaManager.token == "null")
mediaManager.token = authManager.fetchToken(
username.value,
settingsDataStoreManager.privateKeyFlow.first()
)!!
authManager.fetchToken(
username.value,
settingsDataStoreManager.privateKeyFlow.first()
)!!
Global.loggedIn = true
withContext(Dispatchers.IO)
@@ -95,7 +94,7 @@ class MeScreenViewModel @Inject constructor(
val usedUrl = apiClient.apply(context, u, if(uss.first()) c else "")
(context as AetherApp).abyssService?.proxy?.config(apiClient.getBase().toUri().host!!, 4096)
context.abyssService?.downloader?.init()
mediaManager.token = authManager.fetchToken(
authManager.fetchToken(
us,
p
)!!
@@ -125,7 +124,7 @@ class MeScreenViewModel @Inject constructor(
if (u == "" || p == "" || ur == "") return@launch
try {
mediaManager.token = authManager.fetchToken(
authManager.fetchToken(
u,
p
)!!

View File

@@ -17,7 +17,7 @@ junit = "4.13.2"
junitVersion = "1.3.0"
espressoCore = "3.7.0"
kotlinxSerializationJson = "1.9.0"
lifecycleRuntimeKtx = "2.9.3"
lifecycleRuntimeKtx = "2.9.4"
activityCompose = "1.11.0"
composeBom = "2025.09.00"
media3Common = "1.8.0"
@@ -26,6 +26,8 @@ media3ExoplayerFfmpeg = "1.8.0"
media3Ui = "1.8.0"
navigationCompose = "2.9.4"
okhttp = "5.1.0"
persistentcookiejar = "1.0.1"
repo = "Tag"
retrofit = "3.0.0"
retrofit2KotlinxSerializationConverter = "1.0.0"
media3DatasourceOkhttp = "1.8.0"
@@ -72,6 +74,7 @@ androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
persistentcookiejar = { module = "com.github.franmontiel:PersistentCookieJar", version.ref = "persistentcookiejar" }
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
retrofit2-kotlinx-serialization-converter = { module = "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter", version.ref = "retrofit2KotlinxSerializationConverter" }
androidx-media3-datasource-okhttp = { group = "androidx.media3", name = "media3-datasource-okhttp", version.ref = "media3DatasourceOkhttp" }