GocTruyenTranhVui: Allows getting token for login in webview (#10887)

* GocTruyenTranhVui: Allows to get token. Support login

Instead of always saving tokens in code.

* Lines 118 to 143 move to getToken(), and not use postDelayed

* Support enter token manually

Support Suwayomi users who can't log in can still enter token manually
This commit is contained in:
are-are-are 2025-10-06 02:47:40 +07:00 committed by Draff
parent 5ef2e36997
commit a4c4090301
Signed by: Draff
GPG Key ID: E8A89F3211677653
2 changed files with 83 additions and 14 deletions

View File

@ -1,7 +1,7 @@
ext {
extName = 'Goc Truyen Tranh Vui'
extClass = '.GocTruyenTranhVui'
extVersionCode = 3
extVersionCode = 4
isNsfw = true
}

View File

@ -1,8 +1,18 @@
package eu.kanade.tachiyomi.extension.vi.goctruyentranhvui
import android.annotation.SuppressLint
import android.app.Application
import android.content.SharedPreferences
import android.os.Handler
import android.os.Looper
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.preference.EditTextPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.Page
@ -10,6 +20,7 @@ import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.asJsoup
import keiyoushi.utils.getPreferences
import keiyoushi.utils.parseAs
import okhttp3.FormBody
import okhttp3.Headers
@ -17,8 +28,12 @@ import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
class GocTruyenTranhVui : HttpSource() {
class GocTruyenTranhVui : HttpSource(), ConfigurableSource {
override val lang = "vi"
override val baseUrl = "https://goctruyentranhvui17.com"
@ -29,6 +44,8 @@ class GocTruyenTranhVui : HttpSource() {
override val supportsLatest: Boolean = true
private val preferences: SharedPreferences = getPreferences()
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.rateLimit(3)
.build()
@ -76,12 +93,12 @@ class GocTruyenTranhVui : HttpSource() {
override fun mangaDetailsParse(response: Response): SManga = SManga.create().apply {
val document = response.asJsoup()
title = document.selectFirst(".v-card-title")!!.text()
title = document.select(".v-card-title").text()
genre = document.select(".group-content > .v-chip-link").joinToString { it.text() }
thumbnail_url = document.selectFirst("img.image")!!.absUrl("src")
status = parseStatus(document.selectFirst(".mb-1:contains(Trạng thái:) span")!!.text())
author = document.selectFirst(".mb-1:contains(Tác giả:) span")!!.text()
description = document.selectFirst(".v-card-text")!!.text()
thumbnail_url = document.selectFirst("img.image")?.absUrl("src")
status = parseStatus(document.selectFirst(".mb-1:contains(Trạng thái:) span")?.text())
author = document.selectFirst(".mb-1:contains(Tác giả:) span")?.text()
description = document.select(".v-card-text").joinToString { it.wholeText() }
}
private fun parseStatus(status: String?) = when {
@ -103,8 +120,11 @@ class GocTruyenTranhVui : HttpSource() {
val mangaId = matchId.groups[1]!!.value
val nameEn = response.request.url.toString().substringAfter("/truyen/").substringBefore("/")
val chapterNumber = response.request.url.toString().substringAfterLast("chuong-")
val body = FormBody.Builder().add("comicId", mangaId)
.add("chapterNumber", chapterNumber).add("nameEn", nameEn).build()
val body = FormBody.Builder()
.add("comicId", mangaId)
.add("chapterNumber", chapterNumber)
.add("nameEn", nameEn)
.build()
val request = POST("$baseUrl/api/chapter/auth", pageHeaders, body)
client.newCall(request).execute().parseAs<ResultDto<ImageListDto>>().result.data
} else {
@ -116,10 +136,49 @@ class GocTruyenTranhVui : HttpSource() {
}
}
private val pageHeaders by lazy {
headersBuilder()
.add("X-Requested-With", "XMLHttpRequest")
.add("Authorization", TOKEN_KEY)
.build()
getToken()?.let {
headersBuilder()
.add("X-Requested-With", "XMLHttpRequest")
.add("Authorization", it)
.build()
} ?: throw Exception("Vui lòng đăng nhập trong WebView")
}
private var _token: String? = null
@SuppressLint("SetJavaScriptEnabled")
private fun getToken(): String? {
_token?.also { return it }
val handler = Handler(Looper.getMainLooper())
val latch = CountDownLatch(1)
val customToken = preferences.getString("custom_token", null)
if (!customToken.isNullOrBlank()) {
return customToken
}
if (_token != null) return _token
handler.post {
val webview = WebView(Injekt.get<Application>())
with(webview.settings) {
javaScriptEnabled = true
domStorageEnabled = true
databaseEnabled = true
blockNetworkImage = true
}
webview.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView?, url: String?) {
// Get token
view!!.evaluateJavascript("window.localStorage.getItem('Authorization')") { token ->
_token = token.takeUnless { it == "null" }?.removeSurrounding("\"")
latch.countDown()
webview.destroy()
}
}
}
webview.loadDataWithBaseURL(baseUrl, " ", "text/html", "UTF-8", null)
}
latch.await(10, TimeUnit.SECONDS)
return _token
}
override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException()
@ -150,5 +209,15 @@ class GocTruyenTranhVui : HttpSource() {
SortByList(getSortByList()),
GenreList(getGenreList()),
)
override fun setupPreferenceScreen(screen: PreferenceScreen) {
val tokenPref = EditTextPreference(screen.context).apply {
key = "custom_token"
title = "Authorization Token"
summary = "Enter token manually"
dialogTitle = "Authorization Token"
dialogMessage = "Token: ${getToken()}"
}
screen.addPreference(tokenPref)
}
}
private const val TOKEN_KEY = "Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJEcmFrZW4iLCJjb21pY0lkcyI6W10sInJvbGVJZCI6bnVsbCwiZ3JvdXBJZCI6bnVsbCwiYWRtaW4iOmZhbHNlLCJyYW5rIjowLCJwZXJtaXNzaW9uIjpbXSwiaWQiOiIwMDAxMTE1OTg2IiwidGVhbSI6ZmFsc2UsImlhdCI6MTc1NzU5NjQxMSwiZW1haWwiOiJudWxsIn0.VcGDaVQvyowtvja04CTUpfCP5XiC5qIdPmANZL0Gjz2kjz__PJ8LATQ9s44FpNohMpgLgPQO0TVs67D_YFlLNw"