diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 63940a3..6291ea2 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -46,6 +46,8 @@ android { } dependencies { + implementation(libs.persistentcookiejar) + implementation(libs.fetch2) implementation(libs.fetch2okhttp) diff --git a/app/src/main/java/com/acitelight/aether/model/Comic.kt b/app/src/main/java/com/acitelight/aether/model/Comic.kt index be47664..71ff118 100644 --- a/app/src/main/java/com/acitelight/aether/model/Comic.kt +++ b/app/src/main/java/com/acitelight/aether/model/Comic.kt @@ -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? diff --git a/app/src/main/java/com/acitelight/aether/model/Video.kt b/app/src/main/java/com/acitelight/aether/model/Video.kt index 8966105..3743ffc 100644 --- a/app/src/main/java/com/acitelight/aether/model/Video.kt +++ b/app/src/main/java/com/acitelight/aether/model/Video.kt @@ -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 { @@ -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 ) } diff --git a/app/src/main/java/com/acitelight/aether/service/ApiClient.kt b/app/src/main/java/com/acitelight/aether/service/ApiClient.kt index 64503f8..fc109a5 100644 --- a/app/src/main/java/com/acitelight/aether/service/ApiClient.kt +++ b/app/src/main/java/com/acitelight/aether/service/ApiClient.kt @@ -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) { 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>() - - override fun saveFromResponse(url: HttpUrl, cookies: List) { - cookieStore[url] = cookies - } - - override fun loadForRequest(url: HttpUrl): List { - 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>() - - override fun saveFromResponse(url: HttpUrl, cookies: List) { - cookieStore[url] = cookies - } - - override fun loadForRequest(url: HttpUrl): List { - 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 diff --git a/app/src/main/java/com/acitelight/aether/service/ApiInterface.kt b/app/src/main/java/com/acitelight/aether/service/ApiInterface.kt index af82ff6..aa9c32c 100644 --- a/app/src/main/java/com/acitelight/aether/service/ApiInterface.kt +++ b/app/src/main/java/com/acitelight/aether/service/ApiInterface.kt @@ -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 @GET("api/video/{klass}") suspend fun queryVideoClasses( - @Path("klass") klass: String, - @Query("token") token: String + @Path("klass") klass: String ): List @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, - @Query("token") token: String + @Body() id: List ): List @GET("api/image") - suspend fun getComics(@Query("token") token: String): List + suspend fun getComics(): List @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, @Query("token") token: String): List + suspend fun queryComicInfoBulk(@Body() id: List): List @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( diff --git a/app/src/main/java/com/acitelight/aether/service/MediaManager.kt b/app/src/main/java/com/acitelight/aether/service/MediaManager.kt index c0d0a57..f17b2ca 100644 --- a/app/src/main/java/com/acitelight/aether/service/MediaManager.kt +++ b/app/src/main/java/com/acitelight/aether/service/MediaManager.kt @@ -20,13 +20,11 @@ class MediaManager @Inject constructor( private val apiClient: ApiClient ) { - var token: String = "null" - suspend fun listVideoKlasses(): List { 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 { 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) : List? { 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) { diff --git a/app/src/main/java/com/acitelight/aether/view/VideoScreen.kt b/app/src/main/java/com/acitelight/aether/view/VideoScreen.kt index 9f682ec..9bc4c04 100644 --- a/app/src/main/java/com/acitelight/aether/view/VideoScreen.kt +++ b/app/src/main/java/com/acitelight/aether/view/VideoScreen.kt @@ -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