Zerobyw: auto update URL and clean up (#15884)

* Zerobyw: auto update URL and clean up

* Use System.getenv() to check CI

* Use HashMap
This commit is contained in:
stevenyomi 2023-04-02 04:04:09 +08:00 committed by GitHub
parent 4290347286
commit f43811902b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 166 additions and 54 deletions

View File

@ -1,11 +1,12 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
ext {
extName = 'Zerobyw'
pkgNameSuffix = 'zh.zerobyw'
extClass = '.Zerobyw'
extVersionCode = 13
extVersionCode = 14
}
apply from: "$rootDir/common.gradle"

View File

@ -0,0 +1,114 @@
package eu.kanade.tachiyomi.extension.zh.zerobyw
import android.content.Context
import android.content.SharedPreferences
import android.widget.Toast
import androidx.preference.EditTextPreference
import eu.kanade.tachiyomi.network.GET
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Response
import java.io.IOException
private const val DEFAULT_BASE_URL = "http://www.zerobyw4090.com"
private const val BASE_URL_PREF = "ZEROBYW_BASEURL"
private const val DEFAULT_BASE_URL_PREF = "defaultBaseUrl"
private const val JSON_URL = "https://cdn.jsdelivr.net/gh/zerozzz123456/1/url.json"
var SharedPreferences.baseUrl: String
get() = getString(BASE_URL_PREF, DEFAULT_BASE_URL)!!
set(value) = edit().putString(BASE_URL_PREF, value).apply()
fun SharedPreferences.clearOldBaseUrl(): SharedPreferences {
if (getString(DEFAULT_BASE_URL_PREF, "")!! == DEFAULT_BASE_URL) return this
edit()
.remove(BASE_URL_PREF)
.putString(DEFAULT_BASE_URL_PREF, DEFAULT_BASE_URL)
.apply()
return this
}
fun getBaseUrlPreference(context: Context) = EditTextPreference(context).apply {
key = BASE_URL_PREF
title = "网址"
summary = "正常情况下会自动更新。" +
"如果出现错误,请在 GitHub 上报告,并且可以在 $JSON_URL 找到最新网址手动填写。" +
"填写时按照 $DEFAULT_BASE_URL 格式。"
setDefaultValue(DEFAULT_BASE_URL)
setOnPreferenceChangeListener { _, newValue ->
try {
checkBaseUrl(newValue as String)
true
} catch (_: Throwable) {
Toast.makeText(context, "网址格式错误", Toast.LENGTH_LONG).show()
false
}
}
}
fun ciGetUrl(client: OkHttpClient): String {
println("[Zerobyw] CI detected, getting latest URL...")
return try {
val response = client.newCall(GET(JSON_URL)).execute()
parseJson(response).also { println("[Zerobyw] Latest URL is $it") }
} catch (e: Throwable) {
println("[Zerobyw] Failed to fetch latest URL")
e.printStackTrace()
DEFAULT_BASE_URL
}
}
private fun parseJson(response: Response): String {
val string = response.body.string()
val json: HashMap<String, String> = Json.decodeFromString(string)
val newUrl = json["url"]!!.trim()
checkBaseUrl(newUrl)
return newUrl
}
private fun checkBaseUrl(url: String) {
require(url == url.trim() && !url.endsWith('/'))
val pathSegments = url.toHttpUrl().pathSegments
require(pathSegments.size == 1 && pathSegments[0].isEmpty())
}
class UpdateUrlInterceptor(
private val preferences: SharedPreferences,
) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val url = request.url
val baseUrl = preferences.baseUrl
if (!url.toString().startsWith(baseUrl)) return chain.proceed(request)
val failedResult = try {
val response = chain.proceed(request)
if (response.code < 500) return response
Result.success(response)
} catch (e: IOException) {
if (chain.call().isCanceled()) throw e
Result.failure(e)
}
val newUrl = try {
val response = chain.proceed(GET(JSON_URL))
val newUrl = parseJson(response)
require(newUrl != baseUrl)
newUrl
} catch (e: Throwable) {
return failedResult.getOrThrow()
}
preferences.baseUrl = newUrl
val (scheme, host) = newUrl.split("://")
val retryUrl = url.newBuilder().scheme(scheme).host(host).build()
val retryRequest = request.newBuilder().url(retryUrl).build()
return chain.proceed(retryRequest)
}
}

View File

@ -2,8 +2,6 @@ package eu.kanade.tachiyomi.extension.zh.zerobyw
import android.app.Application
import android.content.SharedPreferences
import android.net.Uri
import androidx.preference.EditTextPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.source.ConfigurableSource
@ -13,6 +11,7 @@ import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
@ -23,76 +22,79 @@ import uy.kohesive.injekt.api.get
class Zerobyw : ParsedHttpSource(), ConfigurableSource {
override val name: String = "zero搬运网"
override val lang: String = "zh"
override val supportsLatest: Boolean = false
override val supportsLatest: Boolean get() = false
private val preferences: SharedPreferences =
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
// Url can be found at https://cdn.jsdelivr.net/gh/zerozzz123456/1/url.json
// or just search for "zerobyw" in google
private val defaultBaseUrl = "http://www.zerobywblac.com"
override val client = network.cloudflareClient.newBuilder()
.addInterceptor(UpdateUrlInterceptor(preferences))
.build()
override val baseUrl = preferences.getString("ZEROBYW_BASEURL", defaultBaseUrl)!!
override val baseUrl get() = when {
isCi -> ciGetUrl(client)
else -> preferences.baseUrl
}
private val isCi = System.getenv("CI") == "true"
// Popular
// Website does not provide popular manga, this is actually latest manga
override fun popularMangaRequest(page: Int) = GET("$baseUrl/plugin.php?id=jameson_manhua&c=index&a=ku&&page=$page", headers)
override fun popularMangaRequest(page: Int) = GET("$baseUrl/plugin.php?id=jameson_manhua&c=index&a=ku&page=$page", headers)
override fun popularMangaNextPageSelector(): String = "div.pg > a.nxt"
override fun popularMangaSelector(): String = "div.uk-card"
override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply {
title = getTitle(element.select("p.mt5 > a").text())
setUrlWithoutDomain(element.select("p.mt5 > a").attr("abs:href"))
thumbnail_url = element.select("img").attr("src")
val link = element.selectFirst("p.mt5 > a")!!
title = getTitle(link.text())
setUrlWithoutDomain(link.absUrl("href"))
thumbnail_url = element.selectFirst("img")!!.attr("src")
}
// Latest
override fun latestUpdatesRequest(page: Int) = throw Exception("Not used")
override fun latestUpdatesNextPageSelector() = throw Exception("Not used")
override fun latestUpdatesSelector() = throw Exception("Not used")
override fun latestUpdatesFromElement(element: Element) = throw Exception("Not used")
override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException()
override fun latestUpdatesNextPageSelector() = throw UnsupportedOperationException()
override fun latestUpdatesSelector() = throw UnsupportedOperationException()
override fun latestUpdatesFromElement(element: Element) = throw UnsupportedOperationException()
// Search
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val uri = Uri.parse(baseUrl).buildUpon()
val builder = "$baseUrl/plugin.php".toHttpUrl().newBuilder()
.addEncodedQueryParameter("id", "jameson_manhua")
if (query.isNotBlank()) {
uri.appendPath("plugin.php")
.appendQueryParameter("id", "jameson_manhua")
.appendQueryParameter("a", "search")
.appendQueryParameter("c", "index")
.appendQueryParameter("keyword", query)
.appendQueryParameter("page", page.toString())
builder
.addEncodedQueryParameter("a", "search")
.addEncodedQueryParameter("c", "index")
.addQueryParameter("keyword", query)
} else {
uri.appendPath("plugin.php")
.appendQueryParameter("id", "jameson_manhua")
.appendQueryParameter("c", "index")
.appendQueryParameter("a", "ku")
builder
.addEncodedQueryParameter("c", "index")
.addEncodedQueryParameter("a", "ku")
filters.forEach {
if (it is UriSelectFilterPath && it.toUri().second.isNotEmpty()) {
uri.appendQueryParameter(it.toUri().first, it.toUri().second)
builder.addQueryParameter(it.toUri().first, it.toUri().second)
}
}
uri.appendQueryParameter("page", page.toString())
}
return GET(uri.toString(), headers)
builder.addEncodedQueryParameter("page", page.toString())
return GET(builder.build(), headers)
}
override fun searchMangaNextPageSelector(): String = "div.pg > a.nxt"
override fun searchMangaSelector(): String = "a.uk-card, div.uk-card"
override fun searchMangaFromElement(element: Element): SManga = SManga.create().apply {
title = getTitle(element.select("p.mt5").text())
setUrlWithoutDomain(element.select("a").attr("abs:href"))
thumbnail_url = element.select("img").attr("src")
title = getTitle(element.selectFirst("p.mt5")!!.text())
setUrlWithoutDomain(element.selectFirst("a")!!.absUrl("href"))
thumbnail_url = element.selectFirst("img")!!.attr("src")
}
// Details
override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply {
title = getTitle(document.select("li.uk-active > h3.uk-heading-line").text())
thumbnail_url = document.select("div.uk-width-medium > img").attr("abs:src")
title = getTitle(document.selectFirst("h3.uk-heading-line")!!.text())
thumbnail_url = document.selectFirst("div.uk-width-medium > img")!!.absUrl("src")
author = document.selectFirst("div.cl > a.uk-label")!!.text().substring(3)
artist = author
genre = document.select("div.cl > a.uk-label, div.cl > span.uk-label").eachText().joinToString(", ")
description = document.select("li > div.uk-alert").html().replace("<br>", "")
status = when (document.select("div.cl > span.uk-label").last()!!.text()) {
@ -106,16 +108,16 @@ class Zerobyw : ParsedHttpSource(), ConfigurableSource {
override fun chapterListSelector(): String = "div.uk-grid-collapse > div.muludiv"
override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply {
setUrlWithoutDomain(element.select("a.uk-button-default").attr("abs:href"))
name = element.select("a.uk-button-default").text()
setUrlWithoutDomain(element.selectFirst("a.uk-button-default")!!.absUrl("href"))
name = element.selectFirst("a.uk-button-default")!!.text()
}
override fun chapterListParse(response: Response): List<SChapter> {
return super.chapterListParse(response).reversed()
return super.chapterListParse(response).asReversed()
}
// Pages
override fun pageListParse(document: Document): List<Page> = mutableListOf<Page>().apply {
override fun pageListParse(document: Document): List<Page> {
val images = document.select("div.uk-text-center > img")
if (images.size == 0) {
var message = document.select("div#messagetext > p")
@ -126,12 +128,12 @@ class Zerobyw : ParsedHttpSource(), ConfigurableSource {
throw Exception(message.text())
}
}
images.forEach {
add(Page(size, "", it.attr("src")))
return images.mapIndexed { index, img ->
Page(index, imageUrl = img.attr("src"))
}
}
override fun imageUrlParse(document: Document): String = throw Exception("Not Used")
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException()
// Filters
@ -199,23 +201,18 @@ class Zerobyw : ParsedHttpSource(), ConfigurableSource {
fun toUri() = Pair(key, vals[state].first)
}
private val commentRegex = Regex("\\d+")
private fun getTitle(title: String): String {
val result = Regex("\\d+").find(title)
val result = commentRegex.find(title)
return if (result != null) {
title.substringBefore(result.value)
title.substring(0, result.range.first)
} else {
title.substringBefore("")
title.substringBefore('【')
}
}
override fun setupPreferenceScreen(screen: PreferenceScreen) {
EditTextPreference(screen.context)
.apply {
key = "ZEROBYW_BASEURL"
title = "zerobyw网址"
setDefaultValue(defaultBaseUrl)
summary = "可在 https://cdn.jsdelivr.net/gh/zerozzz123456/1/url.json 中找到网址或者通过google搜索\"zerobyw\"得到"
}
.let { screen.addPreference(it) }
screen.addPreference(getBaseUrlPreference(screen.context))
}
}