Shinigami: Fix cloudflare + pageListParse (#354)

* Fix cloudflare + pageListParse

* remove comment

* fix lint
This commit is contained in:
Secozzi 2024-01-19 10:09:58 +00:00 committed by Draff
parent 27bed17520
commit 29697c086a
2 changed files with 31 additions and 128 deletions

View File

@ -1,28 +1,17 @@
package eu.kanade.tachiyomi.extension.id.shinigami package eu.kanade.tachiyomi.extension.id.shinigami
import android.app.Application
import android.content.SharedPreferences
import android.util.Base64 import android.util.Base64
import android.widget.Toast
import androidx.preference.EditTextPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.lib.cryptoaes.CryptoAES import eu.kanade.tachiyomi.lib.cryptoaes.CryptoAES
import eu.kanade.tachiyomi.lib.synchrony.Deobfuscator import eu.kanade.tachiyomi.lib.synchrony.Deobfuscator
import eu.kanade.tachiyomi.multisrc.madara.Madara import eu.kanade.tachiyomi.multisrc.madara.Madara
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.interceptor.rateLimit
import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapter
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
import okhttp3.Headers
import okhttp3.Interceptor
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Response
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.IOException
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
class Shinigami : Madara("Shinigami", "https://shinigamitoon.com", "id") { class Shinigami : Madara("Shinigami", "https://shinigamitoon.com", "id") {
@ -33,92 +22,30 @@ class Shinigami : Madara("Shinigami", "https://shinigamitoon.com", "id") {
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
private val preferences: SharedPreferences by lazy { override fun headersBuilder() = super.headersBuilder().apply {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) add("Sec-Fetch-Dest", "document")
add("Sec-Fetch-Mode", "navigate")
add("Sec-Fetch-Site", "same-origin")
add("Upgrade-Insecure-Requests", "1")
add("X-Requested-With", "") // added for webview, and removed in interceptor for normal use
} }
private val encodedString = "AAA AaAAAAH QAAAB0 AAAAcA AAAHMAA AA6AAA ALwAAAC8AA " + "AB0AAAAYQA AAGM AAADoAAAAaQAAAH kAAABvAA AAbQAAA GkAAABvAAAA cgAAAGcAAAAuAAA AZwAAAGk " + "AAAB0AAAA aAAAAHUAA ABiAAAALgAAAGkAA ABvAAAAL wAAAHUAAABzA AAAZQAAAHIAAAAtA AAAYQAAAGcA " + "AABlyAtAAAbgA AAHQAAAB6AAAA LwAAAHUAAA BcAAAAZQ AAAHIAAAAtAAA AYQAAAGcAAABl AAAAbgAA AHQAAAB6AAAALgAAAG" + " oAhAntUAABzAA AAbwAAAG4="
private val tachiUaUrl = Base64.decode(encodedString.replace("\\s".toRegex(), "").replace("DoA", "BoA").replace("GoAhAntU", "GoA").replace("BlyAt", "BlA").replace("BcA", "BzA"), Base64.DEFAULT).toString(Charsets.UTF_32).replace("z", "s")
private var secChUaMP: List<String>? = null
private var userAgent: String? = null
private var checkedUa = false
private val uaIntercept = object : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val customUa = preferences.getString(PREF_KEY_CUSTOM_UA, "")
try {
if (customUa!!.isNotBlank()) userAgent = customUa
if (userAgent.isNullOrBlank() && checkedUa.not()) {
val uaResponse = chain.proceed(GET(tachiUaUrl))
if (uaResponse.isSuccessful) {
val parseTachiUa = uaResponse.use { json.decodeFromString<TachiUaResponse>(it.body.string()) }
var listUserAgentString = parseTachiUa.desktop + parseTachiUa.mobile
listUserAgentString = listUserAgentString!!.filter {
listOf("windows", "android").any { filter ->
it.contains(filter, ignoreCase = true)
}
}
userAgent = listUserAgentString!!.random()
checkedUa = true
}
uaResponse.close()
}
if (userAgent.isNullOrBlank().not()) {
secChUaMP = if (userAgent!!.contains("Windows")) {
listOf("?0", "Windows")
} else {
listOf("?1", "Android")
}
val newRequest = chain.request().newBuilder()
.header("User-Agent", userAgent!!.trim())
.header("Sec-CH-UA-Mobile", secChUaMP!![0])
.header("Sec-CH-UA-Platform", secChUaMP!![1])
.removeHeader("X-Requested-With")
.build()
return chain.proceed(newRequest)
}
return chain.proceed(chain.request())
} catch (e: Exception) {
throw IOException(e.message)
}
}
}
@Serializable
data class TachiUaResponse(
val desktop: List<String> = emptyList(),
val mobile: List<String> = emptyList(),
)
// disable random ua in ext setting from multisrc (.setRandomUserAgent)
override val client: OkHttpClient = network.cloudflareClient.newBuilder() override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.addInterceptor(uaIntercept) .addInterceptor { chain ->
val request = chain.request()
val headers = request.headers.newBuilder().apply {
if (request.header("X-Requested-With")?.isBlank() == true) {
removeAll("X-Requested-With")
}
}.build()
chain.proceed(request.newBuilder().headers(headers).build())
}
.connectTimeout(10, TimeUnit.SECONDS) .connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS)
.rateLimit(2)
.build() .build()
override fun headersBuilder(): Headers.Builder {
val builder = super.headersBuilder()
.add("Sec-Fetch-Dest", "document")
.add("Sec-Fetch-Mode", "navigate")
.add("Sec-Fetch-Site", "same-origin")
.add("Upgrade-Insecure-Requests", "1")
.add("X-Requested-With", "") // added for webview, and removed in interceptor for normal use
// used to flush tachi custom ua in webview and use system ua instead
if (userAgent.isNullOrBlank()) builder.removeAll("User-Agent")
return builder
}
override val mangaSubString = "semua-series" override val mangaSubString = "semua-series"
// Tags are useless as they are just SEO keywords. // Tags are useless as they are just SEO keywords.
@ -148,13 +75,15 @@ class Shinigami : Madara("Shinigami", "https://shinigamitoon.com", "id") {
val deobfuscated = Deobfuscator.deobfuscateScript(script) val deobfuscated = Deobfuscator.deobfuscateScript(script)
?: throw Exception("Unable to deobfuscate chapter_data script") ?: throw Exception("Unable to deobfuscate chapter_data script")
val postId = script.substringAfter("var post_id = '").substringBefore("'") val keyMatch = KEY_REGEX.find(deobfuscated)?.groupValues
val chapterData = json.decodeFromString<CDT>( ?: throw Exception("Unable to find key")
script.substringAfter("var chapter_data = '").substringBefore("'"),
)
val keyMatch = KEY_REGEX.find(deobfuscated)!!.groupValues val chapterData = json.decodeFromString<CDT>(
val key = postId + keyMatch[1] + postId + keyMatch[2] + postId CHAPTER_DATA_REGEX.find(script)?.groupValues?.get(1) ?: throw Exception("Unable to get chapter data"),
)
val postId = POST_ID_REGEX.find(script)?.groupValues?.get(1) ?: throw Exception("Unable to get post_id")
val otherId = OTHER_ID_REGEX.findAll(script).firstOrNull { it.groupValues[1] != "post" }?.groupValues?.get(2) ?: throw Exception("Unable to get other id")
val key = otherId + keyMatch[1] + postId + keyMatch[2] + postId
val salt = chapterData.s.decodeHex() val salt = chapterData.s.decodeHex()
val unsaltedCiphertext = Base64.decode(chapterData.ct, Base64.DEFAULT) val unsaltedCiphertext = Base64.decode(chapterData.ct, Base64.DEFAULT)
@ -176,36 +105,10 @@ class Shinigami : Madara("Shinigami", "https://shinigamitoon.com", "id") {
.toByteArray() .toByteArray()
} }
// remove random ua in setting ext from multisrc and use custom one
override fun setupPreferenceScreen(screen: PreferenceScreen) {
val prefCustomUserAgent = EditTextPreference(screen.context).apply {
key = PREF_KEY_CUSTOM_UA
title = TITLE_CUSTOM_UA
summary = (preferences.getString(PREF_KEY_CUSTOM_UA, "")!!.trim() + SUMMARY_STRING_CUSTOM_UA).trim()
setOnPreferenceChangeListener { _, newValue ->
val customUa = newValue as String
preferences.edit().putString(PREF_KEY_CUSTOM_UA, customUa).apply()
if (customUa.isNullOrBlank()) {
Toast.makeText(screen.context, RESTART_APP_STRING, Toast.LENGTH_LONG).show()
} else {
userAgent = null
}
summary = (customUa.trim() + SUMMARY_STRING2_CUSTOM_UA).trim()
true
}
}
screen.addPreference(prefCustomUserAgent)
}
companion object { companion object {
const val TITLE_CUSTOM_UA = "Custom User-Agent" private val KEY_REGEX by lazy { Regex("""_id\s+\+\s+'(.*?)'\s+\+\s+post_id\s+\+\s+'(.*?)'\s+\+\s+post_id""") }
const val PREF_KEY_CUSTOM_UA = "pref_key_custom_ua" private val CHAPTER_DATA_REGEX by lazy { Regex("""var chapter_data\s*=\s*'(.*?)'""") }
const val SUMMARY_STRING_CUSTOM_UA = "\n\nBiarkan kosong untuk menggunakan User-Agent secara random" private val POST_ID_REGEX by lazy { Regex("""var post_id\s*=\s*'(.*?)'""") }
const val SUMMARY_STRING2_CUSTOM_UA = "\n\nKosongkan untuk menggunakan User-Agent secara random" private val OTHER_ID_REGEX by lazy { Regex("""var (\w+)_id\s*=\s*'(.*?)'""") }
const val RESTART_APP_STRING = "Restart Tachiyomi untuk menggunakan pengaturan baru."
private val KEY_REGEX by lazy { Regex("""post_id\s+\+\s+'(.*?)'\s+\+\s+post_id\s+\+\s+'(.*?)'\s+\+\s+post_id""") }
} }
} }

View File

@ -440,7 +440,7 @@ class MadaraGenerator : ThemeSourceGenerator {
SingleLang("Shayami", "https://shayami.com", "es"), SingleLang("Shayami", "https://shayami.com", "es"),
SingleLang("Shiba Manga", "https://shibamanga.com", "en"), SingleLang("Shiba Manga", "https://shibamanga.com", "en"),
SingleLang("Shield Manga", "https://shieldmanga.io", "en", overrideVersionCode = 3), SingleLang("Shield Manga", "https://shieldmanga.io", "en", overrideVersionCode = 3),
SingleLang("Shinigami", "https://shinigamitoon.com", "id", overrideVersionCode = 11), SingleLang("Shinigami", "https://shinigamitoon.com", "id", overrideVersionCode = 12),
SingleLang("Shooting Star Scans", "https://shootingstarscans.com", "en"), SingleLang("Shooting Star Scans", "https://shootingstarscans.com", "en"),
SingleLang("ShoujoHearts", "https://shoujohearts.com", "en", overrideVersionCode = 2), SingleLang("ShoujoHearts", "https://shoujohearts.com", "en", overrideVersionCode = 2),
SingleLang("Sinensis Scan", "https://sinensisscan.net", "pt-BR", pkgName = "sinensis", overrideVersionCode = 6), SingleLang("Sinensis Scan", "https://sinensisscan.net", "pt-BR", pkgName = "sinensis", overrideVersionCode = 6),