Zaimanhua: Support search by ID & proactively acquire token (#10916)
* Zaimanhua: Support search by ID Add the ability to directly open a manga by searching for its ID. A new filter option is added to allow searching for numbers instead of jumping to the manga page. * Zaimanhua: proactively acquire token Attempt login when credentials are available instead of waiting for chapter fetch failure to refresh.
This commit is contained in:
parent
f2b1f92502
commit
85d3008fb8
@ -1,7 +1,7 @@
|
|||||||
ext {
|
ext {
|
||||||
extName = 'Zaimanhua'
|
extName = 'Zaimanhua'
|
||||||
extClass = '.Zaimanhua'
|
extClass = '.Zaimanhua'
|
||||||
extVersionCode = 14
|
extVersionCode = 15
|
||||||
isNsfw = false
|
isNsfw = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,8 @@ package eu.kanade.tachiyomi.extension.zh.zaimanhua
|
|||||||
import eu.kanade.tachiyomi.source.model.Filter
|
import eu.kanade.tachiyomi.source.model.Filter
|
||||||
import okhttp3.HttpUrl
|
import okhttp3.HttpUrl
|
||||||
|
|
||||||
|
class SearchByIdFilter : Filter.CheckBox("启用ID跳转(搜索纯数字时)")
|
||||||
|
|
||||||
open class QueryFilter(name: String, private val key: String, private val params: Array<Pair<String, String>>) :
|
open class QueryFilter(name: String, private val key: String, private val params: Array<Pair<String, String>>) :
|
||||||
Filter.Select<String>(name, params.map { it.first }.toTypedArray()) {
|
Filter.Select<String>(name, params.map { it.first }.toTypedArray()) {
|
||||||
fun addQuery(builder: HttpUrl.Builder) {
|
fun addQuery(builder: HttpUrl.Builder) {
|
||||||
@ -13,7 +15,7 @@ open class QueryFilter(name: String, private val key: String, private val params
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
class RankingGroup : Filter.Group<Filter<*>>(
|
class RankingGroup : Filter.Group<Filter<*>>(
|
||||||
"排行榜(搜索文本时无效)",
|
"排行榜(搜索时无效)",
|
||||||
listOf<Filter<*>>(
|
listOf<Filter<*>>(
|
||||||
TimeFilter(),
|
TimeFilter(),
|
||||||
SortFilter(),
|
SortFilter(),
|
||||||
|
|||||||
@ -62,33 +62,50 @@ class Zaimanhua : HttpSource(), ConfigurableSource {
|
|||||||
.build()
|
.build()
|
||||||
|
|
||||||
private fun authIntercept(chain: Interceptor.Chain): Response {
|
private fun authIntercept(chain: Interceptor.Chain): Response {
|
||||||
val request = chain.request()
|
var request = chain.request()
|
||||||
|
val username = preferences.getString(USERNAME_PREF, "")!!
|
||||||
|
val password = preferences.getString(PASSWORD_PREF, "")!!
|
||||||
|
var token = preferences.getString(TOKEN_PREF, "")!!
|
||||||
|
var hasTriedLogin = false
|
||||||
|
|
||||||
|
if (request.url.toString().startsWith(apiUrl) && request.header("authorization") == null && username.isNotBlank() && password.isNotBlank()) {
|
||||||
|
token = getToken(username, password)
|
||||||
|
apiHeaders = apiHeaders.newBuilder().setToken(token).build()
|
||||||
|
hasTriedLogin = true
|
||||||
|
preferences.edit().apply {
|
||||||
|
if (token.isBlank()) {
|
||||||
|
putString(TOKEN_PREF, "")
|
||||||
|
putString(USERNAME_PREF, "")
|
||||||
|
putString(PASSWORD_PREF, "").apply()
|
||||||
|
} else {
|
||||||
|
putString(TOKEN_PREF, token).apply()
|
||||||
|
request = request.newBuilder().headers(apiHeaders).build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
val response = chain.proceed(request)
|
val response = chain.proceed(request)
|
||||||
// Only intercept chapter api requests that need token
|
// Only intercept chapter api requests that need token
|
||||||
if (!request.url.toString().contains(checkTokenRegex)) return response
|
if (!request.url.toString().contains(checkTokenRegex)) return response
|
||||||
|
|
||||||
// If chapter can read, return directly
|
// If chapter can read, return directly
|
||||||
if (response.peekBody(Long.MAX_VALUE).string().parseAs<ResponseDto<DataWrapperDto<CanReadDto>>>().data.data?.canRead != false) {
|
val canRead = response.peekBody(Long.MAX_VALUE).string()
|
||||||
return response
|
.parseAs<ResponseDto<DataWrapperDto<CanReadDto>>>().data.data?.canRead != false
|
||||||
}
|
if (canRead) return response
|
||||||
// If can not read, need login or user permission is not enough
|
|
||||||
var token: String = preferences.getString(TOKEN_PREF, "")!!
|
if (!isValid(token) && !hasTriedLogin) {
|
||||||
if (!isValid(token)) {
|
|
||||||
// Token is invalid, need login
|
|
||||||
val username = preferences.getString(USERNAME_PREF, "")!!
|
|
||||||
val password = preferences.getString(PASSWORD_PREF, "")!!
|
|
||||||
token = getToken(username, password)
|
token = getToken(username, password)
|
||||||
if (token.isBlank()) {
|
apiHeaders = apiHeaders.newBuilder().setToken(token).build()
|
||||||
preferences.edit().putString(TOKEN_PREF, "")
|
preferences.edit().apply {
|
||||||
.putString(USERNAME_PREF, "")
|
if (token.isBlank()) {
|
||||||
.putString(PASSWORD_PREF, "").apply()
|
putString(TOKEN_PREF, "")
|
||||||
return response
|
putString(USERNAME_PREF, "")
|
||||||
} else {
|
putString(PASSWORD_PREF, "")
|
||||||
preferences.edit().putString(TOKEN_PREF, token).apply()
|
} else {
|
||||||
apiHeaders = apiHeaders.newBuilder().setToken(token).build()
|
putString(TOKEN_PREF, token)
|
||||||
}
|
}
|
||||||
|
}.apply()
|
||||||
|
if (token.isBlank()) return response
|
||||||
} else if (request.header("authorization") == "Bearer $token") {
|
} else if (request.header("authorization") == "Bearer $token") {
|
||||||
// The request has already used a valid token, return directly
|
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,8 +162,9 @@ class Zaimanhua : HttpSource(), ConfigurableSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// path: "/comic/detail/mangaId"
|
// path: "/comic/detail/mangaId"
|
||||||
|
private fun getMangaUrl(id: String): HttpUrl = "$apiUrl/comic/detail/$id?_v=2.2.5".toHttpUrl()
|
||||||
override fun mangaDetailsRequest(manga: SManga): Request =
|
override fun mangaDetailsRequest(manga: SManga): Request =
|
||||||
GET("$apiUrl/comic/detail/${manga.url}?_v=2.2.5", apiHeaders)
|
GET(getMangaUrl(manga.url), apiHeaders)
|
||||||
|
|
||||||
override fun mangaDetailsParse(response: Response): SManga {
|
override fun mangaDetailsParse(response: Response): SManga {
|
||||||
val result = response.parseAs<ResponseDto<DataWrapperDto<MangaDto>>>()
|
val result = response.parseAs<ResponseDto<DataWrapperDto<MangaDto>>>()
|
||||||
@ -259,6 +277,7 @@ class Zaimanhua : HttpSource(), ConfigurableSource {
|
|||||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||||
val ranking = filters.firstInstanceOrNull<RankingGroup>()
|
val ranking = filters.firstInstanceOrNull<RankingGroup>()
|
||||||
val genres = filters.firstInstanceOrNull<GenreGroup>()
|
val genres = filters.firstInstanceOrNull<GenreGroup>()
|
||||||
|
val searchById = filters.firstInstanceOrNull<SearchByIdFilter>()?.state ?: false
|
||||||
val url = when {
|
val url = when {
|
||||||
query.isEmpty() && ranking != null && (ranking.state[0] as TimeFilter).state != 0 -> rankApiUrl().apply {
|
query.isEmpty() && ranking != null && (ranking.state[0] as TimeFilter).state != 0 -> rankApiUrl().apply {
|
||||||
ranking.state.filterIsInstance<QueryFilter>().forEach { it.addQuery(this) }
|
ranking.state.filterIsInstance<QueryFilter>().forEach { it.addQuery(this) }
|
||||||
@ -270,6 +289,8 @@ class Zaimanhua : HttpSource(), ConfigurableSource {
|
|||||||
addQueryParameter("page", page.toString())
|
addQueryParameter("page", page.toString())
|
||||||
}.build()
|
}.build()
|
||||||
|
|
||||||
|
query.isNotBlank() && searchById && query.toIntOrNull()?.let { it > 0 } ?: false -> getMangaUrl(query)
|
||||||
|
|
||||||
else -> searchApiUrl().apply {
|
else -> searchApiUrl().apply {
|
||||||
addQueryParameter("keyword", query)
|
addQueryParameter("keyword", query)
|
||||||
addQueryParameter("page", page.toString())
|
addQueryParameter("page", page.toString())
|
||||||
@ -282,6 +303,8 @@ class Zaimanhua : HttpSource(), ConfigurableSource {
|
|||||||
val url = response.request.url
|
val url = response.request.url
|
||||||
return if (url.toString().startsWith("$apiUrl/comic/rank/list")) {
|
return if (url.toString().startsWith("$apiUrl/comic/rank/list")) {
|
||||||
latestUpdatesParse(response)
|
latestUpdatesParse(response)
|
||||||
|
} else if (url.toString().startsWith("$apiUrl/comic/detail")) {
|
||||||
|
MangasPage(listOf(mangaDetailsParse(response)), false)
|
||||||
} else {
|
} else {
|
||||||
// "$apiUrl/comic/filter/list" or "$apiUrl/search/index"
|
// "$apiUrl/comic/filter/list" or "$apiUrl/search/index"
|
||||||
response.parseAs<ResponseDto<PageDto>>().data.toMangasPage(url.queryParameter("page")!!.toInt())
|
response.parseAs<ResponseDto<PageDto>>().data.toMangasPage(url.queryParameter("page")!!.toInt())
|
||||||
@ -302,6 +325,7 @@ class Zaimanhua : HttpSource(), ConfigurableSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getFilterList() = FilterList(
|
override fun getFilterList() = FilterList(
|
||||||
|
SearchByIdFilter(),
|
||||||
RankingGroup(),
|
RankingGroup(),
|
||||||
Filter.Separator(),
|
Filter.Separator(),
|
||||||
Filter.Header("分类(搜索/查看排行榜时无效)"),
|
Filter.Header("分类(搜索/查看排行榜时无效)"),
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user