diff --git a/src/pt/taiyo/build.gradle b/src/pt/taiyo/build.gradle index eafbd5d83..7ca8de0ca 100644 --- a/src/pt/taiyo/build.gradle +++ b/src/pt/taiyo/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'Taiyō' extClass = '.Taiyo' - extVersionCode = 2 + extVersionCode = 3 isNsfw = true } diff --git a/src/pt/taiyo/src/eu/kanade/tachiyomi/extension/pt/taiyo/Taiyo.kt b/src/pt/taiyo/src/eu/kanade/tachiyomi/extension/pt/taiyo/Taiyo.kt index c62157b20..68efdd2bc 100644 --- a/src/pt/taiyo/src/eu/kanade/tachiyomi/extension/pt/taiyo/Taiyo.kt +++ b/src/pt/taiyo/src/eu/kanade/tachiyomi/extension/pt/taiyo/Taiyo.kt @@ -1,5 +1,7 @@ package eu.kanade.tachiyomi.extension.pt.taiyo +import android.app.Application +import android.content.SharedPreferences import eu.kanade.tachiyomi.extension.pt.taiyo.dto.AdditionalInfoDto import eu.kanade.tachiyomi.extension.pt.taiyo.dto.ChapterListDto import eu.kanade.tachiyomi.extension.pt.taiyo.dto.MediaChapterDto @@ -25,13 +27,19 @@ import kotlinx.serialization.json.decodeFromStream import kotlinx.serialization.json.put import kotlinx.serialization.json.putJsonObject import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.Interceptor import okhttp3.MediaType.Companion.toMediaType import okhttp3.Request import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.Response +import okhttp3.internal.http.HTTP_FORBIDDEN +import okhttp3.internal.http.HTTP_UNAUTHORIZED import org.jsoup.nodes.Document import org.jsoup.nodes.Element +import org.jsoup.select.Elements import rx.Observable +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy import java.text.SimpleDateFormat import java.util.Locale @@ -47,41 +55,28 @@ class Taiyo : ParsedHttpSource() { // The source doesn't show the title on the home page override val supportsLatest = false + private val preferences: SharedPreferences = + Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) + + private var bearerToken: String = preferences.getString(BEARER_TOKEN_PREF, "").toString() + override val client = network.client.newBuilder() .rateLimitHost(baseUrl.toHttpUrl(), 2) .rateLimitHost(IMG_CDN.toHttpUrl(), 2) + .addInterceptor(::authorizationInterceptor) .build() private val json: Json by injectLazy() // ============================== Popular =============================== - var bearerToken = "" - override fun popularMangaRequest(page: Int): Request { - if (bearerToken.isBlank()) { - getBearerToken() - } - return searchMangaRequest(page, "", FilterList()) - } + override fun popularMangaRequest(page: Int) = searchMangaRequest(page, "", FilterList()) override fun popularMangaParse(response: Response) = searchMangaParse(response) override fun popularMangaSelector() = throw UnsupportedOperationException() override fun popularMangaFromElement(element: Element) = throw UnsupportedOperationException() override fun popularMangaNextPageSelector() = null - private fun getBearerToken() { - val scriptUrl = client.newCall(GET(baseUrl, headers)) - .execute().asJsoup() - .selectFirst("script[src*=ee07d8437723d9f5]") - ?.attr("src") ?: throw Exception("Não foi possivel localizar o token") - - val script = client.newCall(GET("$baseUrl$scriptUrl", headers)) - .execute().body.string() - - bearerToken = TOKEN_REGEX.find(script)?.groups?.get("token")?.value - ?: throw Exception("Não foi possivel extrair o token") - } - // =============================== Latest =============================== override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException() override fun latestUpdatesSelector() = throw UnsupportedOperationException() @@ -128,13 +123,13 @@ class Taiyo : ParsedHttpSource() { val requestBody = json.encodeToString(jsonObj).toRequestBody(MEDIA_TYPE) - val apiHeaders = headers.newBuilder() - .set("Authorization", "Bearer $bearerToken") - .build() - - return POST("https://meilisearch.${baseUrl.substringAfterLast("/")}/multi-search", apiHeaders, requestBody) + return POST("https://meilisearch.${baseUrl.substringAfterLast("/")}/multi-search", getApiHeaders(), requestBody) } + private fun getApiHeaders() = headers.newBuilder() + .set("Authorization", "Bearer $bearerToken") + .build() + override fun searchMangaParse(response: Response): MangasPage { val obj = response.parseAs<SearchResultDto>() val mangas = obj.mangas.map { item -> @@ -290,10 +285,63 @@ class Taiyo : ParsedHttpSource() { }.onFailure { it.printStackTrace() }.getOrNull() } + // ============================= Authorization ======================== + + private fun authorizationInterceptor(chain: Interceptor.Chain): Response { + val request = chain.request() + val response = chain.proceed(request) + return when (response.code) { + in arrayOf(HTTP_UNAUTHORIZED, HTTP_FORBIDDEN) -> updateTokenAndContinueRequest(request, chain) + else -> response + } + } + + private fun updateTokenAndContinueRequest(request: Request, chain: Interceptor.Chain): Response { + bearerToken = getToken() + val req = request.newBuilder() + .headers(getApiHeaders()) + .build() + return chain.proceed(req) + } + + private fun getToken(): String { + return fetchBearerToken().also { + preferences.edit() + .putString(BEARER_TOKEN_PREF, it) + .apply() + } + } + + private fun fetchBearerToken(): String { + val scripts = client.newCall(GET(baseUrl, headers)) + .execute().asJsoup() + .select("script[src*=next]:not([nomodule]):not([src*=app])") + + val script = getScriptContainingToken(scripts) + ?: throw Exception("Não foi possivel localizar o token") + + return TOKEN_REGEX.find(script)?.groups?.get("token")?.value + ?: throw Exception("Não foi possivel extrair o token") + } + + private fun getScriptContainingToken(scripts: Elements): String? { + val elements = scripts.toList().reversed() + for (element in elements) { + val scriptUrl = element.attr("src") + val script = client.newCall(GET("$baseUrl$scriptUrl", headers)) + .execute().body.string() + if (TOKEN_REGEX.containsMatchIn(script)) { + return script + } + } + return null + } + companion object { const val PREFIX_SEARCH = "id:" val CHAPTER_REGEX = """(?<chapters>\{"chapters".+"totalPages":\d+\})""".toRegex() val TOKEN_REGEX = """NEXT_PUBLIC_MEILISEARCH_PUBLIC_KEY:(\s+)?"(?<token>[^"]+)""".toRegex() + const val BEARER_TOKEN_PREF = "TAIYO_BEARER_TOKEN" private const val IMG_CDN = "https://cdn.taiyo.moe/medias"