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

View File

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