Fix ColaManga (#10147)

Co-authored-by: Howard Wu <HowardWu20@outlook.com>
This commit is contained in:
stevenyomi 2025-08-15 18:30:12 +00:00 committed by Draff
parent 9ea67f22dd
commit 9f1f449d96
Signed by: Draff
GPG Key ID: E8A89F3211677653
5 changed files with 32 additions and 103 deletions

View File

@ -0,0 +1 @@
function _0x95d1(_0x32014e,_0x469951){const _0x95d1c6=['W6f0W63dUtNcUHxdKCoUWQ4','W5NcLfztWRdcJqq','WPmwWRvCW5e','hgxdUX0fW7qR','fmkGW59KWOS','oCoyWPZcRCoCfW','W7u9zufMWOX3eWuk','xSoDsmodWPC','nXNcJdi','BKmGWO3dTSo0W5pcQ0ZcLW','BKmGWO3dTSo0','DmkpgSoPWPKCW6SPWRlcTW','eqVcLuerCXpcRG','hNldSGKz','jdDDW6vJW5ayDXtcHG','edBcImopW4lcGZJcKG','z8ksoSkfW5FdHmkW','W6ddQWlcNmkAWP96WQGSW5y','W7bagJNcQSot','W5ZdQXpcJ8kxWQvWWQOgW5C','tCkVW4m','kMNdQ18nW78aW4ijWPK','gbKHW6L7W5RdK8oADq','kvK7qaGSWRuQWPdcPa','kqpcNCkoW4pdTSkVWR9FWP4','zCkoc8ohWPev','F8kgWPNcUSkdW7K+W4BcVmk8','W7CCWRtdUL/dICkogCkF','zCojc8kWWOSMzq','W5hdPmk4WQfVxNWAW6NdOq','W4BcGCoHW7mmWP4hW5iTW5O','W53dQHNcJW','FCoVWPBcPmkEk8kmWQmsiW','lCoJrxa','lqfZ','mbNcIW','qSk+W6ZdKSoJWQFdKSkaW6FcLG','owqfW6y','W60nWQC','ds/cSCoOdcRdMa','W7ddP2mYA8oor8o3CG','bmoFWOJcJCowewm','vCkpmCkr','D8oohmkB'];return _0x95d1=function(_0x440613,_0x4b3a2d){_0x440613=_0x440613-0x16c;let _0x7773d6=_0x95d1c6[_0x440613];if(_0x95d1['gWugEI']===undefined){var _0x41c754=function(_0x3b655d){const _0x437ca5='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x1547fb='',_0x1044a1='';for(let _0x251fbf=0x0,_0x2f9372,_0x31bb78,_0x2aad47=0x0;_0x31bb78=_0x3b655d['charAt'](_0x2aad47++);~_0x31bb78&&(_0x2f9372=_0x251fbf%0x4?_0x2f9372*0x40+_0x31bb78:_0x31bb78,_0x251fbf++%0x4)?_0x1547fb+=String['fromCharCode'](0xff&_0x2f9372>>(-0x2*_0x251fbf&0x6)):0x0){_0x31bb78=_0x437ca5['indexOf'](_0x31bb78);}for(let _0x39ad47=0x0,_0x515fdf=_0x1547fb['length'];_0x39ad47<_0x515fdf;_0x39ad47++){_0x1044a1+='%'+('00'+_0x1547fb['charCodeAt'](_0x39ad47)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x1044a1);};const _0x5a8916=function(_0x466652,_0x539550){let _0x5d5f94=[],_0x387a85=0x0,_0x569820,_0x72381b='';_0x466652=_0x41c754(_0x466652);let _0x2fadae;for(_0x2fadae=0x0;_0x2fadae<0x100;_0x2fadae++){_0x5d5f94[_0x2fadae]=_0x2fadae;}for(_0x2fadae=0x0;_0x2fadae<0x100;_0x2fadae++){_0x387a85=(_0x387a85+_0x5d5f94[_0x2fadae]+_0x539550['charCodeAt'](_0x2fadae%_0x539550['length']))%0x100,_0x569820=_0x5d5f94[_0x2fadae],_0x5d5f94[_0x2fadae]=_0x5d5f94[_0x387a85],_0x5d5f94[_0x387a85]=_0x569820;}_0x2fadae=0x0,_0x387a85=0x0;for(let _0x508dc6=0x0;_0x508dc6<_0x466652['length'];_0x508dc6++){_0x2fadae=(_0x2fadae+0x1)%0x100,_0x387a85=(_0x387a85+_0x5d5f94[_0x2fadae])%0x100,_0x569820=_0x5d5f94[_0x2fadae],_0x5d5f94[_0x2fadae]=_0x5d5f94[_0x387a85],_0x5d5f94[_0x387a85]=_0x569820,_0x72381b+=String['fromCharCode'](_0x466652['charCodeAt'](_0x508dc6)^_0x5d5f94[(_0x5d5f94[_0x2fadae]+_0x5d5f94[_0x387a85])%0x100]);}return _0x72381b;};_0x95d1['TkVRYE']=_0x5a8916,_0x32014e=arguments,_0x95d1['gWugEI']=!![];}const _0x30d549=_0x95d1c6[0x0],_0x25f5ad=_0x440613+_0x30d549,_0x309cf2=_0x32014e[_0x25f5ad];return!_0x309cf2?(_0x95d1['jcAPzj']===undefined&&(_0x95d1['jcAPzj']=!![]),_0x7773d6=_0x95d1['TkVRYE'](_0x7773d6,_0x4b3a2d),_0x32014e[_0x25f5ad]=_0x7773d6):_0x7773d6=_0x309cf2,_0x7773d6;},_0x95d1(_0x32014e,_0x469951);}(function(){const _0x9aa92a=_0x95d1;if(!window[_0x9aa92a(0x16c,'g3U%')][_0x9aa92a(0x16d,'OoQ)')])return passData();let _0x309cf2='';const _0x5a8916={};_0x5a8916[_0x9aa92a(0x16e,'D7BR')]=(_0x3b655d,_0x437ca5,_0x1547fb)=>{const _0x35d9a6=_0x9aa92a,_0x1044a1=window[_0x35d9a6(0x16f,'PyD3')][_0x35d9a6(0x170,'*mI1')](_0x3b655d,_0x437ca5,_0x1547fb);return new window[(_0x35d9a6(0x171,'&2E]'))](_0x35d9a6(0x172,'kk&c')+_0x35d9a6(0x173,'DWvx'))[_0x35d9a6(0x174,'%4]i')](_0x1044a1)&&_0x1044a1!==_0x35d9a6(0x175,'b&k%')+_0x35d9a6(0x176,'b&k%')&&(_0x309cf2=_0x1044a1),_0x1044a1;},window[_0x9aa92a(0x177,'vl[)')+_0x9aa92a(0x178,'Uo!D')]=new window[(_0x9aa92a(0x179,'PyD3'))](window[_0x9aa92a(0x17a,'1M@P')+_0x9aa92a(0x17b,'6*ZE')],_0x5a8916),new window[(_0x9aa92a(0x17c,'qFlC'))](_0x251fbf=>{const _0x1dcdf4=_0x9aa92a,_0x2f9372=new window[(_0x1dcdf4(0x17d,'J]ip'))+(_0x1dcdf4(0x17e,'onP6'))](()=>{const _0x19b0f1=_0x1dcdf4;if(document[_0x19b0f1(0x17f,'J]ip')+_0x19b0f1(0x180,'nPJu')](_0x19b0f1(0x181,'PyD3')+_0x19b0f1(0x182,'j1a$'))){_0x2f9372[_0x19b0f1(0x183,'UqZ3')]();const _0x2aad47=document[_0x19b0f1(0x184,'(nWm')+_0x19b0f1(0x185,'vl[)')](_0x19b0f1(0x186,'X8u6')+_0x19b0f1(0x187,')I*8'));_0x2aad47[_0x19b0f1(0x188,'DWvx')](_0x39ad47=>{const _0x6b5b52=_0x19b0f1,_0x515fdf=Object[_0x6b5b52(0x189,'dYDk')+_0x6b5b52(0x18a,'q0PL')+_0x6b5b52(0x18b,'J]ip')](Object[_0x6b5b52(0x18c,'p[Hk')+_0x6b5b52(0x18d,'^k(h')](_0x39ad47),_0x6b5b52(0x18e,'b&k%'));_0x515fdf[_0x6b5b52(0x18f,'%4]i')]=()=>{_0x251fbf(_0x309cf2);},Object[_0x6b5b52(0x190,'^Jdp')+_0x6b5b52(0x191,')a*O')](_0x39ad47,_0x6b5b52(0x192,')I*8'),_0x515fdf);});}}),_0x31bb78={};_0x31bb78[_0x1dcdf4(0x193,'XGIU')]=!![],_0x31bb78[_0x1dcdf4(0x194,'C^iD')]=!![],_0x2f9372[_0x1dcdf4(0x195,'&2E]')](document[_0x1dcdf4(0x196,'qFlC')],_0x31bb78);})[_0x9aa92a(0x197,'DWvx')](passData);}());

View File

@ -1,12 +1,8 @@
ext {
extName = 'COLAMANGA'
extClass = '.Onemanhua'
extVersionCode = 24
extVersionCode = 25
isNsfw = true
}
apply from: "$rootDir/common.gradle"
dependencies {
implementation(project(":lib:synchrony"))
}

View File

@ -9,7 +9,6 @@ import android.webkit.JavascriptInterface
import android.webkit.WebView
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.lib.synchrony.Deobfuscator
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.interceptor.rateLimitHost
import eu.kanade.tachiyomi.source.ConfigurableSource
@ -21,11 +20,10 @@ import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
import eu.kanade.tachiyomi.util.asJsoup
import keiyoushi.utils.getPreferencesLazy
import keiyoushi.utils.parseAs
import keiyoushi.utils.tryParse
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import okhttp3.Response
@ -34,7 +32,6 @@ import org.jsoup.nodes.Element
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.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
@ -47,10 +44,6 @@ abstract class ColaManga(
override val supportsLatest = true
private val json: Json by injectLazy()
private val intl = ColaMangaIntl(lang)
private val preferences by getPreferencesLazy()
override val client = network.cloudflareClient.newBuilder()
@ -206,7 +199,11 @@ abstract class ColaManga(
const key = CryptoJS.enc.Utf8.stringify(__js.getDataParse());
window.$interfaceName.passData(JSON.stringify({ images, key }), window.image_info.keyType || "0");
const passData = (keyData = key) => {
window.$interfaceName.passData(JSON.stringify({ images, key: keyData }));
};
$webviewScript
}();
</script>
""".trimIndent(),
@ -214,7 +211,7 @@ abstract class ColaManga(
val handler = Handler(Looper.getMainLooper())
val latch = CountDownLatch(1)
val jsInterface = JsInterface(latch, json)
val jsInterface = JsInterface(latch)
var webView: WebView? = null
handler.post {
@ -233,15 +230,10 @@ abstract class ColaManga(
handler.post { webView?.destroy() }
if (latch.count == 1L) {
throw Exception(intl.timedOutDecryptingImageLinks)
throw Exception("加载图片超时")
}
val key = if (jsInterface.keyType.isNotEmpty()) {
keyMapping[jsInterface.keyType]
?: throw Exception(intl.couldNotFindKey(jsInterface.keyType))
} else {
jsInterface.key
}
val key = jsInterface.key
return jsInterface.images.mapIndexed { i, it ->
val imageUrl = buildString(it.length + 6) {
@ -265,14 +257,14 @@ abstract class ColaManga(
override fun imageUrlParse(document: Document) = throw UnsupportedOperationException()
override fun getFilterList() = FilterList(
SearchTypeFilter(intl),
SearchTypeFilter(),
)
override fun setupPreferenceScreen(screen: PreferenceScreen) {
ListPreference(screen.context).apply {
key = RATE_LIMIT_PREF_KEY
title = intl.rateLimitPrefTitle
summary = intl.rateLimitPrefSummary(RATE_LIMIT_PREF_DEFAULT)
title = "主站连接限制"
summary = "此值影响主站的连接请求量。降低此值可以减少获得HTTP 403错误的几率但加载速度也会变慢。需要重启软件以生效。\n默认值:$RATE_LIMIT_PREF_DEFAULT\n当前值:%s"
entries = RATE_LIMIT_PREF_ENTRIES
entryValues = RATE_LIMIT_PREF_ENTRIES
@ -281,8 +273,8 @@ abstract class ColaManga(
ListPreference(screen.context).apply {
key = RATE_LIMIT_PERIOD_PREF_KEY
title = intl.rateLimitPeriodPrefTitle
summary = intl.rateLimitPeriodPrefSummary(RATE_LIMIT_PERIOD_PREF_DEFAULT)
title = "主站连接限制期"
summary = "此值影响主站点连接限制时的延迟毫秒。增加这个值可能会减少出现HTTP 403错误的机会但加载速度也会变慢。需要重启软件以生效。\n默认值:$RATE_LIMIT_PERIOD_PREF_DEFAULT\n当前值:%s"
entries = RATE_LIMIT_PERIOD_PREF_ENTRIES
entryValues = RATE_LIMIT_PERIOD_PREF_ENTRIES
@ -290,14 +282,9 @@ abstract class ColaManga(
}.also(screen::addPreference)
}
private val keyMappingRegex = Regex("""if\s*\(\s*([a-zA-Z0-9_]+)\s*==\s*(\d+)\s*\)\s*\{\s*return\s*'([a-zA-Z0-9_]+)'\s*;""")
private val keyMapping by lazy {
val obfuscatedReadJs = client.newCall(GET("$baseUrl/js/manga.read.js")).execute().body.string()
val readJs = Deobfuscator.deobfuscateScript(obfuscatedReadJs)
?: throw Exception(intl.couldNotDeobufscateScript)
keyMappingRegex.findAll(readJs).associate { it.groups[2]!!.value to it.groups[3]!!.value }
private val webviewScript by lazy {
javaClass.getResource("/assets/webview-script.js")?.readText()
?: throw Exception("WebView 脚本不存在")
}
private fun randomString() = buildString(15) {
@ -309,29 +296,25 @@ abstract class ColaManga(
}
@Suppress("UNUSED")
private class JsInterface(private val latch: CountDownLatch, private val json: Json) {
private class JsInterface(private val latch: CountDownLatch) {
var images: List<String> = listOf()
private set
var key: String = ""
private set
var keyType: String = ""
private set
@JavascriptInterface
fun passData(rawData: String, keyType: String) {
val data = json.parseToJsonElement(rawData).jsonObject
fun passData(rawData: String) {
val data = rawData.parseAs<Data>(Json)
images = data["images"]!!.jsonArray.map { it.jsonPrimitive.content }
key = data["key"]!!.jsonPrimitive.content
if (keyType != "0") {
this.keyType = keyType
}
images = data.images
key = data.key
latch.countDown()
}
@Serializable
private class Data(val images: List<String>, val key: String)
}
companion object {

View File

@ -22,11 +22,11 @@ open class UriPartFilter(
}
}
class SearchTypeFilter(intl: ColaMangaIntl) : UriPartFilter(
intl.searchType,
class SearchTypeFilter() : UriPartFilter(
"搜索类型",
"type",
arrayOf(
intl.searchTypeFuzzy to "1",
intl.searchTypeExact to "2",
"模糊" to "1",
"精确" to "2",
),
)

View File

@ -1,51 +0,0 @@
package eu.kanade.tachiyomi.extension.zh.onemanhua
class ColaMangaIntl(private val lang: String) {
val rateLimitPrefTitle = when (lang) {
"zh" -> "主站连接限制"
else -> "Rate limit"
}
fun rateLimitPrefSummary(defaultValue: String) = when (lang) {
"zh" -> "此值影响主站的连接请求量。降低此值可以减少获得HTTP 403错误的几率但加载速度也会变慢。需要重启软件以生效。\n默认值:$defaultValue\n当前值:%s"
else -> "Number of requests made to the website. Lowering this value may reduce the chance of getting HTTP 403. Tachiyomi restart required.\nDefault value: $defaultValue\nCurrent value: %s"
}
val rateLimitPeriodPrefTitle = when (lang) {
"zh" -> "主站连接限制期"
else -> "Rate limit period"
}
fun rateLimitPeriodPrefSummary(defaultValue: String) = when (lang) {
"zh" -> "此值影响主站点连接限制时的延迟毫秒。增加这个值可能会减少出现HTTP 403错误的机会但加载速度也会变慢。需要重启软件以生效。\n默认值:$defaultValue\n当前值:%s"
else -> "Time in milliseconds to wait after using up all allowed requests. Lowering this value may reduce the chance of getting HTTP 403. Tachiyomi restart required.\nDefault value: $defaultValue\nCurrent value: %s"
}
val timedOutDecryptingImageLinks = when (lang) {
else -> "Timed out decrypting image links"
}
val couldNotDeobufscateScript = when (lang) {
else -> "Could not deobfuscate script"
}
fun couldNotFindKey(forKeyType: String) = when (lang) {
else -> "Could not find key for keyType $forKeyType"
}
val searchType = when (lang) {
"zh" -> "搜索类型"
else -> "Search type"
}
val searchTypeFuzzy = when (lang) {
"zh" -> "模糊"
else -> "Fuzzy"
}
val searchTypeExact = when (lang) {
"zh" -> "精确"
else -> "Exact"
}
}