Random User-Agent Refactor (#17059)

* lib-randomua

* NHentai: Random mobile ua

* Madara random UA overhaul

* MangaThemesia random UA overhaul

* MangaHub random UA overhaul

* build errors and warnings

* remove preference from Constellar

* change to singleton object

* network.client

* fix copy paste and chapter deep link

* exit early

* use data class and enum options

* missing import

* suggested changes

Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>

* re-add empty check to filters

* convert to interceptor

* update comment

Co-authored-by: Alessandro Jean <14254807+alessandrojean@users.noreply.github.com>

* update error message

* initialize client by lazy

* dont check on excluded Filters

Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>

* newlines

* move preference helper function into lib

* move preference helper function into lib x2

* move check to lib too

* move defaultRandomUserAgentType to constructor

* rename the interceptor

* organize the interceptor and preference stuff in different files

* hide custom ua setting when random ua is enabled

* English

Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>

* catch specific exception

Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>

* setVisible()

fresh stuff

* setVisible()

fresh stuff

* change summary

* workaround

* Update error message

Co-authored-by: Alessandro Jean <14254807+alessandrojean@users.noreply.github.com>

* Update comment

Co-authored-by: Alessandro Jean <14254807+alessandrojean@users.noreply.github.com>

---------

Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
Co-authored-by: Alessandro Jean <14254807+alessandrojean@users.noreply.github.com>
This commit is contained in:
AwkwardPeak7 2023-07-16 03:52:35 +05:00 committed by GitHub
parent 71f69252ad
commit 50b5d33614
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
76 changed files with 370 additions and 563 deletions

View File

@ -0,0 +1,23 @@
plugins {
id("com.android.library")
kotlin("android")
id("kotlinx-serialization")
}
android {
compileSdk = AndroidConfig.compileSdk
namespace = "eu.kanade.tachiyomi.lib.randomua"
defaultConfig {
minSdk = AndroidConfig.minSdk
targetSdk = AndroidConfig.targetSdk
}
}
repositories {
mavenCentral()
}
dependencies {
compileOnly(libs.bundles.common)
}

View File

@ -0,0 +1,121 @@
package eu.kanade.tachiyomi.lib.randomua
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.NetworkHelper
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Response
import uy.kohesive.injekt.injectLazy
import java.io.IOException
private class RandomUserAgentInterceptor(
private val userAgentType: UserAgentType,
private val customUA: String?,
private val filterInclude: List<String>,
private val filterExclude: List<String>,
) : Interceptor {
private var userAgent: String? = null
private val json: Json by injectLazy()
private val network: NetworkHelper by injectLazy()
private val client = network.client
override fun intercept(chain: Interceptor.Chain): Response {
try {
val originalRequest = chain.request()
val newUserAgent = getUserAgent()
?: return chain.proceed(originalRequest)
val originalHeaders = originalRequest.headers
val modifiedHeaders = originalHeaders.newBuilder()
.set("User-Agent", newUserAgent)
.build()
return chain.proceed(
originalRequest.newBuilder()
.headers(modifiedHeaders)
.build()
)
} catch (e: Exception) {
throw IOException(e.message)
}
}
private fun getUserAgent(): String? {
if (userAgentType == UserAgentType.OFF) {
return customUA?.ifBlank { null }
}
if (!userAgent.isNullOrEmpty()) return userAgent
val uaResponse = client.newCall(GET(UA_DB_URL)).execute()
if (!uaResponse.isSuccessful) {
uaResponse.close()
return null
}
val userAgentList = uaResponse.use { json.decodeFromString<UserAgentList>(it.body.string()) }
return when (userAgentType) {
UserAgentType.DESKTOP -> userAgentList.desktop
UserAgentType.MOBILE -> userAgentList.mobile
else -> error("Expected UserAgentType.DESKTOP or UserAgentType.MOBILE but got UserAgentType.${userAgentType.name} instead")
}
.filter {
filterInclude.isEmpty() || filterInclude.any { filter ->
it.contains(filter, ignoreCase = true)
}
}
.filterNot {
filterExclude.any { filter ->
it.contains(filter, ignoreCase = true)
}
}
.randomOrNull()
.also { userAgent = it }
}
companion object {
private const val UA_DB_URL = "https://tachiyomiorg.github.io/user-agents/user-agents.json"
}
}
/**
* Helper function to add a latest random user agent interceptor.
* The interceptor will added at the first position in the chain,
* so the CloudflareInterceptor in the app will be able to make usage of it.
*
* @param userAgentType User Agent type one of (DESKTOP, MOBILE, OFF)
* @param customUA Optional custom user agent used when userAgentType is OFF
* @param filterInclude Filter to only include User Agents containing these strings
* @param filterExclude Filter to exclude User Agents containing these strings
*/
fun OkHttpClient.Builder.setRandomUserAgent(
userAgentType: UserAgentType,
customUA: String? = null,
filterInclude: List<String> = emptyList(),
filterExclude: List<String> = emptyList(),
) = apply {
interceptors().add(0, RandomUserAgentInterceptor(userAgentType, customUA, filterInclude, filterExclude))
}
enum class UserAgentType {
MOBILE,
DESKTOP,
OFF
}
@Serializable
private data class UserAgentList(
val desktop: List<String>,
val mobile: List<String>
)

View File

@ -0,0 +1,83 @@
package eu.kanade.tachiyomi.lib.randomua
import android.content.SharedPreferences
import android.widget.Toast
import androidx.preference.EditTextPreference
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import okhttp3.Headers
class RandomUserAgentPreference(
private val preferences: SharedPreferences,
) {
/**
* Helper function to return UserAgentType based on SharedPreference value
*/
fun getPrefUAType(): UserAgentType {
return when (preferences.getString(PREF_KEY_RANDOM_UA, "off")) {
"mobile" -> UserAgentType.MOBILE
"desktop" -> UserAgentType.DESKTOP
else -> UserAgentType.OFF
}
}
/**
* Helper function to return custom UserAgent from SharedPreference
*/
fun getPrefCustomUA(): String? {
return preferences.getString(PREF_KEY_CUSTOM_UA, null)
}
/**
* Helper function to add Random User-Agent settings to SharedPreference
*
* @param screen, PreferenceScreen from `setupPreferenceScreen`
*/
fun addPreferenceToScreen(
screen: PreferenceScreen,
) {
val customUA = EditTextPreference(screen.context).apply {
key = PREF_KEY_CUSTOM_UA
title = TITLE_CUSTOM_UA
summary = CUSTOM_UA_SUMMARY
setVisible(getPrefUAType() == UserAgentType.OFF)
setOnPreferenceChangeListener { _, newValue ->
try {
Headers.Builder().add("User-Agent", newValue as String).build()
true
} catch (e: IllegalArgumentException) {
Toast.makeText(screen.context, "User Agent invalid${e.message}", Toast.LENGTH_LONG).show()
false
}
}
}
val randomUA = ListPreference(screen.context).apply {
key = PREF_KEY_RANDOM_UA
title = TITLE_RANDOM_UA
entries = RANDOM_UA_ENTRIES
entryValues = RANDOM_UA_VALUES
summary = "%s"
setDefaultValue("off")
setOnPreferenceChangeListener { _, newVal ->
val showCustomUAPref = newVal as String == "off"
customUA.setVisible(showCustomUAPref)
true
}
}
screen.addPreference(randomUA)
screen.addPreference(customUA)
}
companion object {
const val TITLE_RANDOM_UA = "Random User-Agent (Requires Restart)"
const val PREF_KEY_RANDOM_UA = "pref_key_random_ua_"
val RANDOM_UA_ENTRIES = arrayOf("OFF", "Desktop", "Mobile")
val RANDOM_UA_VALUES = arrayOf("off", "desktop", "mobile")
const val TITLE_CUSTOM_UA = "Custom User-Agent"
const val PREF_KEY_CUSTOM_UA = "pref_key_custom_ua_"
const val CUSTOM_UA_SUMMARY = "Leave blank to use application default user-agent. (Requires Restart)"
}
}

View File

@ -1,9 +0,0 @@
package eu.kanade.tachiyomi.extension.tr.anisamanga
import eu.kanade.tachiyomi.multisrc.madara.Madara
import okhttp3.Headers
class AnisaManga : Madara("Anisa Manga", "https://anisamanga.com", "tr") {
override fun headersBuilder(): Headers.Builder = super.headersBuilder()
.add("Referer", "https://anisamanga.com")
}

View File

@ -14,7 +14,6 @@ import uy.kohesive.injekt.api.get
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
class BokugenTranslation : Madara(
"BokugenTranslation",
@ -23,8 +22,7 @@ class BokugenTranslation : Madara(
dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("es")),
) {
private var loadWebView = true
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.addInterceptor(uaIntercept)
override val client: OkHttpClient = super.client.newBuilder()
.addInterceptor { chain ->
val request = chain.request()
val url = request.url.toString()
@ -56,8 +54,6 @@ class BokugenTranslation : Madara(
}
chain.proceed(request)
}
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.rateLimit(1, 1)
.build()

View File

@ -1,3 +1,4 @@
dependencies {
implementation(project(':lib-cryptoaes'))
}
implementation(project(":lib-cryptoaes"))
implementation(project(":lib-randomua"))
}

View File

@ -7,16 +7,12 @@ import okhttp3.OkHttpClient
import org.jsoup.nodes.Document
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.concurrent.TimeUnit
class Doodmanga : Madara("Doodmanga", "https://www.doodmanga.com", "th", SimpleDateFormat("dd MMMMM yyyy", Locale("th"))) {
override val filterNonMangaItems = false
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.addInterceptor(uaIntercept)
override val client: OkHttpClient = super.client.newBuilder()
.addInterceptor(ScrambledImageInterceptor)
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build()
override val pageListParseSelector = "div.text-center > p > img, div.text-center > img, div.text-center > script"

View File

@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.extension.en.firstkissmanga
import eu.kanade.tachiyomi.multisrc.madara.Madara
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import okhttp3.Headers
import java.util.concurrent.TimeUnit
class FirstKissManga : Madara(
@ -10,9 +9,7 @@ class FirstKissManga : Madara(
"https://1stkissmanga.me",
"en",
) {
override fun headersBuilder(): Headers.Builder = super.headersBuilder().add("Referer", baseUrl)
override val client = network.cloudflareClient.newBuilder()
override val client = super.client.newBuilder()
.rateLimit(1, 3, TimeUnit.SECONDS)
.build()
}

View File

@ -10,7 +10,7 @@ class FirstKissMangaClub : Madara(
"en",
) {
override val client = network.cloudflareClient.newBuilder()
override val client = super.client.newBuilder()
.rateLimit(1, 3, TimeUnit.SECONDS)
.build()
}

View File

@ -10,7 +10,7 @@ class FirstKissMangaLove : Madara(
"en",
) {
override val client = network.cloudflareClient.newBuilder()
override val client = super.client.newBuilder()
.rateLimit(1, 3, TimeUnit.SECONDS)
.build()
}

View File

@ -16,7 +16,7 @@ class FirstKissManhua : Madara(
SimpleDateFormat("d MMM yyyy", Locale.US),
) {
override val client = network.cloudflareClient.newBuilder()
override val client = super.client.newBuilder()
.rateLimit(1, 3, TimeUnit.SECONDS)
.build()

View File

@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.extension.pt.fleurblanche
import eu.kanade.tachiyomi.multisrc.madara.Madara
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import okhttp3.Headers
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Response
@ -25,8 +24,6 @@ class FleurBlanche : Madara(
override val useNewChapterEndpoint = true
override fun headersBuilder(): Headers.Builder = Headers.Builder()
private fun authWarningIntercept(chain: Interceptor.Chain): Response {
val response = chain.proceed(chain.request())

View File

@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.extension.pt.hentaiteca
import eu.kanade.tachiyomi.multisrc.madara.Madara
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import okhttp3.Headers
import okhttp3.OkHttpClient
import java.text.SimpleDateFormat
import java.util.Locale
@ -18,7 +17,4 @@ class HentaiTeca : Madara(
override val client: OkHttpClient = super.client.newBuilder()
.rateLimit(1, 2, TimeUnit.SECONDS)
.build()
override fun headersBuilder(): Headers.Builder = Headers.Builder()
.add("Referer", "$baseUrl/")
}

View File

@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.extension.pt.ichirinnohanayuri
import eu.kanade.tachiyomi.multisrc.madara.Madara
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import okhttp3.Headers
import okhttp3.OkHttpClient
import java.io.IOException
import java.text.SimpleDateFormat
@ -30,8 +29,6 @@ class IchirinNoHanaYuri : Madara(
}
.build()
override fun headersBuilder(): Headers.Builder = Headers.Builder()
companion object {
private const val BLOCKING_MESSAGE = "O site está bloqueando o Tachiyomi. " +
"Migre para outra fonte caso o problema persistir."

View File

@ -26,8 +26,6 @@ class Manga18fx : Madara(
) {
override val id = 3157287889751723714
override val client = network.client
override val fetchGenres = false
override val sendViewCount = false

View File

@ -2,14 +2,10 @@ package eu.kanade.tachiyomi.extension.ar.mangastarz
import eu.kanade.tachiyomi.multisrc.madara.Madara
import eu.kanade.tachiyomi.source.model.SManga
import okhttp3.Headers
import org.jsoup.nodes.Element
class MangaStarz : Madara("Manga Starz", "https://mangastarz.com", "ar") {
override fun headersBuilder(): Headers.Builder = Headers.Builder()
.add("Referer", baseUrl)
override fun popularMangaFromElement(element: Element): SManga {
val manga = SManga.create()

View File

@ -2,12 +2,9 @@ package eu.kanade.tachiyomi.extension.en.manhuaga
import eu.kanade.tachiyomi.multisrc.madara.Madara
import okhttp3.OkHttpClient
import java.util.concurrent.TimeUnit
class Manhuaga : Madara("Manhuaga", "https://manhuaga.com", "en") {
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
override val client: OkHttpClient = super.client.newBuilder()
.addInterceptor { chain ->
val originalRequest = chain.request()
chain.proceed(originalRequest).let { response ->

View File

@ -1,10 +1,7 @@
package eu.kanade.tachiyomi.extension.en.mixedmanga
import eu.kanade.tachiyomi.multisrc.madara.Madara
import okhttp3.Headers
import java.text.SimpleDateFormat
import java.util.Locale
class MixedManga : Madara("Mixed Manga", "https://mixedmanga.com", "en", SimpleDateFormat("d MMM yyyy", Locale.US)) {
override fun headersBuilder(): Headers.Builder = super.headersBuilder().add("Referer", baseUrl)
}
class MixedManga : Madara("Mixed Manga", "https://mixedmanga.com", "en", SimpleDateFormat("d MMM yyyy", Locale.US))

View File

@ -3,13 +3,10 @@ package eu.kanade.tachiyomi.extension.en.shieldmanga
import eu.kanade.tachiyomi.multisrc.madara.Madara
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import okhttp3.OkHttpClient
import java.util.concurrent.TimeUnit
class ShieldManga : Madara("Shield Manga", "https://shieldmanga.io", "en") {
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
override val client: OkHttpClient = super.client.newBuilder()
.rateLimit(1)
.build()

View File

@ -1,13 +1,10 @@
package eu.kanade.tachiyomi.extension.en.topmanhua
import eu.kanade.tachiyomi.multisrc.madara.Madara
import okhttp3.Headers
import java.text.SimpleDateFormat
import java.util.Locale
class TopManhua : Madara("Top Manhua", "https://topmanhua.com", "en", SimpleDateFormat("MM/dd/yy", Locale.US)) {
override fun headersBuilder(): Headers.Builder = super.headersBuilder().add("Referer", baseUrl)
// The website does not flag the content.
override val filterNonMangaItems = false
}

View File

@ -20,7 +20,7 @@ class YANPFansub : Madara(
// Scanlator changed the theme from WpMangaReader to Madara.
override val versionId: Int = 2
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
override val client: OkHttpClient = super.client.newBuilder()
.rateLimit(1, 2, TimeUnit.SECONDS)
.addInterceptor(::checkPasswordProtectedIntercept)
.build()

View File

@ -4,7 +4,6 @@ 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 okhttp3.Headers
import okhttp3.OkHttpClient
import okhttp3.Request
import org.jsoup.nodes.Document
@ -19,10 +18,7 @@ class YaoiToshokan : Madara(
SimpleDateFormat("dd MMM yyyy", Locale("pt", "BR")),
) {
override fun headersBuilder(): Headers.Builder = super.headersBuilder()
.removeAll("User-Agent")
override val client: OkHttpClient = network.client.newBuilder()
override val client: OkHttpClient = super.client.newBuilder()
.rateLimit(1, 2, TimeUnit.SECONDS)
.build()

View File

@ -1,9 +1,11 @@
package eu.kanade.tachiyomi.extension.pt.yugenmangas
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.lib.randomua.UserAgentType
import eu.kanade.tachiyomi.lib.randomua.setRandomUserAgent
import eu.kanade.tachiyomi.multisrc.madara.Madara
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import eu.kanade.tachiyomi.source.model.SChapter
import okhttp3.Headers
import okhttp3.OkHttpClient
import org.jsoup.nodes.Element
import java.text.SimpleDateFormat
@ -17,14 +19,17 @@ class YugenMangas : Madara(
SimpleDateFormat("MMMMM dd, yyyy", Locale("pt", "BR")),
) {
override val client: OkHttpClient = super.client.newBuilder()
.addInterceptor(uaIntercept)
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.setRandomUserAgent(
UserAgentType.DESKTOP,
)
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.rateLimit(1, 3, TimeUnit.SECONDS)
.build()
override fun headersBuilder(): Headers.Builder = Headers.Builder()
override fun headersBuilder() = super.headersBuilder()
.add("Origin", baseUrl)
.add("Referer", "$baseUrl/")
override val useNewChapterEndpoint: Boolean = true
@ -41,5 +46,5 @@ class YugenMangas : Madara(
)
}
override val useRandomUserAgentByDefault: Boolean = true
override fun setupPreferenceScreen(screen: PreferenceScreen) { }
}

View File

@ -0,0 +1,3 @@
dependencies {
implementation(project(":lib-randomua"))
}

View File

@ -35,11 +35,8 @@ class AsuraScansEn : MangaThemesia(
private val preferences = Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
override val client: OkHttpClient = super.client.newBuilder()
.addInterceptor(::urlChangeInterceptor)
.addInterceptor(uaIntercept)
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.rateLimit(1, 3, TimeUnit.SECONDS)
.build()
@ -232,7 +229,7 @@ class AsuraScansEn : MangaThemesia(
setDefaultValue(true)
}.also(screen::addPreference)
addRandomAndCustomUserAgentPreferences(screen)
super.setupPreferenceScreen(screen)
}
private val SharedPreferences.permaUrlPref

View File

@ -19,10 +19,7 @@ class AsuraScansTr : MangaThemesia(
"tr",
dateFormat = SimpleDateFormat("MMM d, yyyy", Locale("tr")),
) {
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.addInterceptor(uaIntercept)
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
override val client: OkHttpClient = super.client.newBuilder()
.rateLimit(1, 3, TimeUnit.SECONDS)
.build()

View File

@ -3,13 +3,10 @@ package eu.kanade.tachiyomi.extension.id.boosei
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import okhttp3.OkHttpClient
import java.util.concurrent.TimeUnit
class Boosei : MangaThemesia("Boosei", "https://boosei.net", "id") {
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
override val client: OkHttpClient = super.client.newBuilder()
.rateLimit(4)
.build()

View File

@ -10,6 +10,7 @@ import android.webkit.ConsoleMessage
import android.webkit.JavascriptInterface
import android.webkit.WebChromeClient
import android.webkit.WebView
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.lib.dataimage.DataImageInterceptor
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
import eu.kanade.tachiyomi.network.GET
@ -149,6 +150,8 @@ class ConstellarScans : MangaThemesia("Constellar Scans", "https://constellarsca
.header("Sec-Fetch-Site", "same-origin")
.build()
override fun setupPreferenceScreen(screen: PreferenceScreen) { }
companion object {
const val UA_DB_URL =
"https://cdn.jsdelivr.net/gh/mimmi20/browscap-helper@30a83c095688f40b9eaca0165a479c661e5a7fbe/tests/0002999.json"

View File

@ -0,0 +1,3 @@
dependencies {
implementation(project(":lib-randomua"))
}

View File

@ -3,13 +3,10 @@ package eu.kanade.tachiyomi.extension.id.dojingnet
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import okhttp3.OkHttpClient
import java.util.concurrent.TimeUnit
class DojingNet : MangaThemesia("Dojing.net", "https://dojing.net", "id") {
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
override val client: OkHttpClient = super.client.newBuilder()
.rateLimit(4)
.build()

View File

@ -28,7 +28,6 @@ import uy.kohesive.injekt.api.get
import java.io.ByteArrayOutputStream
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.concurrent.TimeUnit
open class FlameScans(
override val baseUrl: String,
@ -48,9 +47,7 @@ open class FlameScans(
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
override val client: OkHttpClient = super.client.newBuilder()
.addInterceptor(::composedImageIntercept)
.build()

View File

@ -14,7 +14,7 @@ class FranxxMangas : MangaThemesia(
dateFormat = SimpleDateFormat("MMMMM dd, yyyy", Locale("pt", "BR")),
) {
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
override val client: OkHttpClient = super.client.newBuilder()
.rateLimit(1, 2, TimeUnit.SECONDS)
.build()

View File

@ -6,15 +6,12 @@ import okhttp3.OkHttpClient
import org.jsoup.nodes.Document
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.concurrent.TimeUnit
class Kiryuu : MangaThemesia("Kiryuu", "https://kiryuu.id", "id", dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("id"))) {
// Formerly "Kiryuu (WP Manga Stream)"
override val id = 3639673976007021338
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
override val client: OkHttpClient = super.client.newBuilder()
.rateLimit(4)
.build()

View File

@ -8,7 +8,6 @@ import okhttp3.OkHttpClient
import okhttp3.Request
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.concurrent.TimeUnit
class KomikAV : MangaThemesia(
"Komik AV (WP Manga Stream)",
@ -19,9 +18,7 @@ class KomikAV : MangaThemesia(
// Formerly "Komik AV (WP Manga Stream)"
override val id = 7875815514004535629
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
override val client: OkHttpClient = super.client.newBuilder()
.rateLimit(4)
.build()

View File

@ -17,7 +17,6 @@ import org.jsoup.Jsoup
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import java.util.Calendar
import java.util.concurrent.TimeUnit
class KomikCast : MangaThemesia(
"Komik Cast",
@ -28,13 +27,11 @@ class KomikCast : MangaThemesia(
// Formerly "Komik Cast (WP Manga Stream)"
override val id = 972717448578983812
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
override val client: OkHttpClient = super.client.newBuilder()
.rateLimit(3)
.build()
override fun headersBuilder(): Headers.Builder = Headers.Builder()
override fun headersBuilder(): Headers.Builder = super.headersBuilder()
.add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9")
.add("Accept-language", "en-US,en;q=0.9,id;q=0.8")
.add("Referer", baseUrl)
@ -76,7 +73,7 @@ class KomikCast : MangaThemesia(
override fun chapterFromElement(element: Element) = SChapter.create().apply {
val urlElements = element.select("a")
setUrlWithoutDomain(urlElements.attr("href"))
name = element.select(".lch a, .chapternum")!!.text().ifBlank { urlElements.first()!!.text() }
name = element.select(".lch a, .chapternum").text().ifBlank { urlElements.first()!!.text() }
date_upload = parseChapterDate2(element.select(".chapter-link-time").text())
}

View File

@ -3,12 +3,9 @@ package eu.kanade.tachiyomi.extension.id.komikdewasa
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import okhttp3.OkHttpClient
import java.util.concurrent.TimeUnit
class KomikDewasa : MangaThemesia("KomikDewasa", "https://komikdewasa.org", "id", "/komik") {
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
override val client: OkHttpClient = super.client.newBuilder()
.rateLimit(4)
.build()

View File

@ -5,15 +5,12 @@ import eu.kanade.tachiyomi.network.interceptor.rateLimit
import okhttp3.OkHttpClient
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.concurrent.TimeUnit
class KomikindoCo : MangaThemesia("KomikIndo.co", "https://komikindo.co", "id", dateFormat = SimpleDateFormat("MMM dd, yyyy", Locale("id"))) {
// Formerly "Komikindo.co"
override val id = 734619124437406170
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
override val client: OkHttpClient = super.client.newBuilder()
.rateLimit(4)
.build()

View File

@ -3,12 +3,9 @@ package eu.kanade.tachiyomi.extension.id.komikmanhwa
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import okhttp3.OkHttpClient
import java.util.concurrent.TimeUnit
class KomikManhwa : MangaThemesia("KomikManhwa", "https://komikmanhwa.me", "id", "/series") {
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
override val client: OkHttpClient = super.client.newBuilder()
.rateLimit(4)
.build()
}

View File

@ -3,15 +3,12 @@ package eu.kanade.tachiyomi.extension.id.komikstation
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import okhttp3.OkHttpClient
import java.util.concurrent.TimeUnit
class KomikStation : MangaThemesia("Komik Station", "https://komikstation.co", "id") {
// Formerly "Komik Station (WP Manga Stream)"
override val id = 6148605743576635261
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
override val client: OkHttpClient = super.client.newBuilder()
.rateLimit(4)
.build()

View File

@ -3,12 +3,9 @@ package eu.kanade.tachiyomi.extension.id.kumapoi
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import okhttp3.OkHttpClient
import java.util.concurrent.TimeUnit
class KumaPoi : MangaThemesia("KumaPoi", "https://kumapoi.club", "id") {
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
override val client: OkHttpClient = super.client.newBuilder()
.rateLimit(4)
.build()

View File

@ -3,13 +3,10 @@ package eu.kanade.tachiyomi.extension.en.kumascans
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import okhttp3.OkHttpClient
import java.util.concurrent.TimeUnit
class KumaScans : MangaThemesia("Kuma Scans (Kuma Translation)", "https://kumascans.com", "en") {
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
override val client: OkHttpClient = super.client.newBuilder()
.rateLimit(4)
.build()

View File

@ -3,13 +3,10 @@ package eu.kanade.tachiyomi.extension.id.lianscans
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import okhttp3.OkHttpClient
import java.util.concurrent.TimeUnit
class LianScans : MangaThemesia("LianScans", "https://www.lianscans.my.id", "id") {
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
override val client: OkHttpClient = super.client.newBuilder()
.rateLimit(4)
.build()

View File

@ -3,13 +3,10 @@ package eu.kanade.tachiyomi.extension.id.mangakyo
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import okhttp3.OkHttpClient
import java.util.concurrent.TimeUnit
class Mangakyo : MangaThemesia("Mangakyo", "https://mangakyo.id", "id") {
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
override val client: OkHttpClient = super.client.newBuilder()
.rateLimit(4)
.build()

View File

@ -15,14 +15,11 @@ import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import rx.Observable
import java.util.concurrent.TimeUnit
class MangaRawOrg : MangaThemesia("Manga Raw.org", "https://mangaraw.org", "ja") {
override val id = 6223520752496636410
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
override val client: OkHttpClient = super.client.newBuilder()
.rateLimit(4)
.build()

View File

@ -14,7 +14,7 @@ class MangasChan : MangaThemesia(
dateFormat = SimpleDateFormat("MMMMM dd, yyyy", Locale("pt", "BR")),
) {
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
override val client: OkHttpClient = super.client.newBuilder()
.rateLimit(1, 2, TimeUnit.SECONDS)
.build()

View File

@ -3,13 +3,10 @@ package eu.kanade.tachiyomi.extension.id.mangayaro
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import okhttp3.OkHttpClient
import java.util.concurrent.TimeUnit
class Mangayaro : MangaThemesia("Mangayaro", "https://mangayaro.net", "id") {
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
override val client: OkHttpClient = super.client.newBuilder()
.rateLimit(4)
.build()
}

View File

@ -3,15 +3,12 @@ package eu.kanade.tachiyomi.extension.id.mangceh
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import okhttp3.OkHttpClient
import java.util.concurrent.TimeUnit
class Mareceh : MangaThemesia("Mareceh", "https://mareceh.com", "id") {
override val versionId = 2
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
override val client: OkHttpClient = super.client.newBuilder()
.rateLimit(4)
.build()

View File

@ -10,7 +10,7 @@ class MangKomik : MangaThemesia("MangKomik", "https://mangkomik.net", "id") {
override fun pageListParse(document: Document): List<Page> {
// Get external JS for image urls
val scriptEl = document.selectFirst("script[data-minify]")!!
val scriptEl = document.selectFirst("script[data-minify]")
val scriptUrl = scriptEl?.attr("src")
if (scriptUrl.isNullOrEmpty()) {
return super.pageListParse(document)

View File

@ -3,13 +3,10 @@ package eu.kanade.tachiyomi.extension.id.manhwadesu
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import okhttp3.OkHttpClient
import java.util.concurrent.TimeUnit
class ManhwaDesu : MangaThemesia("ManhwaDesu", "https://manhwadesu.org", "id", "/komik") {
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
override val client: OkHttpClient = super.client.newBuilder()
.rateLimit(4)
.build()
}

View File

@ -51,7 +51,7 @@ class ManhwaFreak : MangaThemesia("Manhwa Freak", "https://manhwafreak.com", "en
override fun chapterFromElement(element: Element) = SChapter.create().apply {
val urlElements = element.select("a")
setUrlWithoutDomain(urlElements.attr("href"))
name = element.select(".chapter-info p:nth-child(1)")!!.text().ifBlank { urlElements.first()!!.text() }
name = element.select(".chapter-info p:nth-child(1)").text().ifBlank { urlElements.first()!!.text() }
date_upload = element.selectFirst(".chapter-info p:nth-child(2)")?.text().parseChapterDate()
}

View File

@ -14,7 +14,7 @@ class ManhwaIndo : MangaThemesia(
SimpleDateFormat("MMMM dd, yyyy", Locale("id")),
) {
override fun headersBuilder(): Headers.Builder = Headers.Builder()
override fun headersBuilder(): Headers.Builder = super.headersBuilder()
.add("Referer", baseUrl)
override fun mangaDetailsParse(document: Document) = super.mangaDetailsParse(document).apply {

View File

@ -3,13 +3,10 @@ package eu.kanade.tachiyomi.extension.id.manhwalandmom
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import okhttp3.OkHttpClient
import java.util.concurrent.TimeUnit
class ManhwaLandMom : MangaThemesia("ManhwaLand.mom", "https://manhwaland.us", "id") {
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
override val client: OkHttpClient = super.client.newBuilder()
.rateLimit(4)
.build()
}

View File

@ -3,13 +3,10 @@ package eu.kanade.tachiyomi.extension.id.manhwalist
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import okhttp3.OkHttpClient
import java.util.concurrent.TimeUnit
class ManhwaList : MangaThemesia("ManhwaList", "https://manhwalist.xyz", "id") {
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
override val client: OkHttpClient = super.client.newBuilder()
.rateLimit(4)
.build()

View File

@ -3,13 +3,10 @@ package eu.kanade.tachiyomi.extension.id.masterkomik
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import okhttp3.OkHttpClient
import java.util.concurrent.TimeUnit
class MasterKomik : MangaThemesia("MasterKomik", "https://masterkomik.com", "id") {
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
override val client: OkHttpClient = super.client.newBuilder()
.rateLimit(4)
.build()

View File

@ -3,12 +3,9 @@ package eu.kanade.tachiyomi.extension.id.mirrordesu
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import okhttp3.OkHttpClient
import java.util.concurrent.TimeUnit
class MirrorDesu : MangaThemesia("MirrorDesu", "https://mirrordesu.me", "id", "/komik") {
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
override val client: OkHttpClient = super.client.newBuilder()
.rateLimit(4)
.build()

View File

@ -18,7 +18,7 @@ class ModeScanlator : MangaThemesia(
// Site changed from Madara to WpMangaReader.
override val versionId: Int = 2
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
override val client: OkHttpClient = super.client.newBuilder()
.rateLimit(1, 2, TimeUnit.SECONDS)
.build()

View File

@ -3,13 +3,10 @@ package eu.kanade.tachiyomi.extension.id.nekomik
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import okhttp3.OkHttpClient
import java.util.concurrent.TimeUnit
class Nekomik : MangaThemesia("Nekomik", "https://nekomik.com", "id") {
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
override val client: OkHttpClient = super.client.newBuilder()
.rateLimit(4)
.build()

View File

@ -17,7 +17,7 @@ class OrigamiOrpheans : MangaThemesia(
// Scanlator migrated from Madara to WpMangaReader.
override val versionId = 2
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
override val client: OkHttpClient = super.client.newBuilder()
.rateLimit(1, 2, TimeUnit.SECONDS)
.build()

View File

@ -3,13 +3,10 @@ package eu.kanade.tachiyomi.extension.ja.rawkuma
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import okhttp3.OkHttpClient
import java.util.concurrent.TimeUnit
class Rawkuma : MangaThemesia("Rawkuma", "https://rawkuma.com/", "ja") {
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
override val client: OkHttpClient = super.client.newBuilder()
.rateLimit(4)
.build()
}

View File

@ -3,13 +3,10 @@ package eu.kanade.tachiyomi.extension.en.readkomik
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import okhttp3.OkHttpClient
import java.util.concurrent.TimeUnit
class ReadKomik : MangaThemesia("Readkomik", "https://readkomik.com", "en") {
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
override val client: OkHttpClient = super.client.newBuilder()
.rateLimit(4)
.build()

View File

@ -3,13 +3,10 @@ package eu.kanade.tachiyomi.extension.id.ryukonesia
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import okhttp3.OkHttpClient
import java.util.concurrent.TimeUnit
class Ryukonesia : MangaThemesia("Ryukonesia", "https://ryukonesia.net", "id") {
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
override val client: OkHttpClient = super.client.newBuilder()
.rateLimit(4)
.build()
}

View File

@ -5,13 +5,10 @@ import eu.kanade.tachiyomi.network.interceptor.rateLimit
import okhttp3.OkHttpClient
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.concurrent.TimeUnit
class SekteDoujin : MangaThemesia("Sekte Doujin", "https://sektedoujin.lol", "id", dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale.forLanguageTag("id"))) {
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
override val client: OkHttpClient = super.client.newBuilder()
.rateLimit(4)
.build()
}

View File

@ -6,7 +6,6 @@ import okhttp3.Dns
import okhttp3.OkHttpClient
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.concurrent.TimeUnit
class SheaManga : MangaThemesia(
"Shea Manga",
@ -15,9 +14,7 @@ class SheaManga : MangaThemesia(
dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale.forLanguageTag("id")),
) {
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
override val client: OkHttpClient = super.client.newBuilder()
.rateLimit(4)
.dns(Dns.SYSTEM)
.build()

View File

@ -17,9 +17,7 @@ class SilenceScan : MangaThemesia(
override val versionId: Int = 2
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
override val client: OkHttpClient = super.client.newBuilder()
.rateLimit(1, 2, TimeUnit.SECONDS)
.build()

View File

@ -14,7 +14,7 @@ class TsundokuTraducoes : MangaThemesia(
dateFormat = SimpleDateFormat("MMMMM d, yyyy", Locale("pt", "BR")),
) {
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
override val client: OkHttpClient = super.client.newBuilder()
.rateLimit(1, 2, TimeUnit.SECONDS)
.build()

View File

@ -3,15 +3,12 @@ package eu.kanade.tachiyomi.extension.id.westmanga
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import okhttp3.OkHttpClient
import java.util.concurrent.TimeUnit
class WestManga : MangaThemesia("West Manga", "https://westmanga.info", "id") {
// Formerly "West Manga (WP Manga Stream)"
override val id = 8883916630998758688
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
override val client: OkHttpClient = super.client.newBuilder()
.rateLimit(4)
.build()

View File

@ -8,13 +8,10 @@ import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import java.util.concurrent.TimeUnit
class xCaliBRScans : MangaThemesia("xCaliBR Scans", "https://xcalibrscans.com", "en") {
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
override val client: OkHttpClient = super.client.newBuilder()
.addInterceptor(AntiScrapInterceptor())
.rateLimit(2)
.build()

View File

@ -3,12 +3,10 @@ package eu.kanade.tachiyomi.multisrc.madara
import android.app.Application
import android.content.SharedPreferences
import android.util.Base64
import android.util.Log
import android.widget.Toast
import androidx.preference.EditTextPreference
import androidx.preference.PreferenceScreen
import androidx.preference.SwitchPreferenceCompat
import eu.kanade.tachiyomi.lib.cryptoaes.CryptoAES
import eu.kanade.tachiyomi.lib.randomua.RandomUserAgentPreference
import eu.kanade.tachiyomi.lib.randomua.setRandomUserAgent
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.asObservable
@ -21,17 +19,13 @@ import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.CacheControl
import okhttp3.FormBody
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
@ -40,7 +34,6 @@ import rx.Observable
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.io.IOException
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.Calendar
@ -58,91 +51,24 @@ abstract class Madara(
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
private val randomUAPrefHelper: RandomUserAgentPreference by lazy {
RandomUserAgentPreference(preferences)
}
override val supportsLatest = true
// override with true if you want useRandomUserAgentByDefault to be on by default for some source
protected open val useRandomUserAgentByDefault: Boolean = false
/**
* override include/exclude user-agent string if needed
* some example:
* listOf("chrome")
* listOf("linux", "windows")
* listOf("108")
*/
protected open val filterIncludeUserAgent: List<String> = listOf()
protected open val filterExcludeUserAgent: List<String> = listOf()
private var userAgent: String? = null
private var checkedUa = false
private val hasUaIntercept by lazy {
client.interceptors.toString().contains("uaIntercept")
}
protected val uaIntercept = object : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val useRandomUa = preferences.getBoolean(PREF_KEY_RANDOM_UA, false)
val customUa = preferences.getString(PREF_KEY_CUSTOM_UA, "")
try {
if (hasUaIntercept && (useRandomUa || customUa!!.isNotBlank())) {
Log.i("Extension_setting", "$TITLE_RANDOM_UA or $TITLE_CUSTOM_UA option is ENABLED")
if (customUa!!.isNotBlank() && useRandomUa.not()) {
userAgent = customUa
}
if (userAgent.isNullOrBlank() && !checkedUa) {
val uaResponse = chain.proceed(GET(UA_DB_URL))
if (uaResponse.isSuccessful) {
var listUserAgentString =
json.decodeFromString<Map<String, List<String>>>(uaResponse.body.string())["desktop"]
if (filterIncludeUserAgent.isNotEmpty()) {
listUserAgentString = listUserAgentString!!.filter {
filterIncludeUserAgent.any { filter ->
it.contains(filter, ignoreCase = true)
}
}
}
if (filterExcludeUserAgent.isNotEmpty()) {
listUserAgentString = listUserAgentString!!.filterNot {
filterExcludeUserAgent.any { filter ->
it.contains(filter, ignoreCase = true)
}
}
}
userAgent = listUserAgentString!!.random()
checkedUa = true
}
uaResponse.close()
}
if (userAgent.isNullOrBlank().not()) {
val newRequest = chain.request().newBuilder()
.header("User-Agent", userAgent!!.trim())
.build()
return chain.proceed(newRequest)
}
}
return chain.proceed(chain.request())
} catch (e: Exception) {
throw IOException(e.message)
}
}
}
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.addInterceptor(uaIntercept)
override val client = network.cloudflareClient.newBuilder()
.setRandomUserAgent(
randomUAPrefHelper.getPrefUAType(),
randomUAPrefHelper.getPrefCustomUA(),
)
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build()
override fun headersBuilder() = super.headersBuilder()
.add("Referer", "$baseUrl/")
protected open val json: Json by injectLazy()
/**
@ -187,9 +113,6 @@ abstract class Madara(
*/
protected open val mangaSubString = "manga"
override fun headersBuilder(): Headers.Builder = Headers.Builder()
.add("Referer", "$baseUrl/")
// Popular Manga
override fun popularMangaParse(response: Response): MangasPage {
@ -1017,57 +940,6 @@ abstract class Madara(
}
}
override fun setupPreferenceScreen(screen: PreferenceScreen) {
if (hasUaIntercept) {
val prefUserAgent = SwitchPreferenceCompat(screen.context).apply {
key = PREF_KEY_RANDOM_UA
title = TITLE_RANDOM_UA
summary = if (preferences.getBoolean(PREF_KEY_RANDOM_UA, useRandomUserAgentByDefault)) userAgent else ""
setDefaultValue(useRandomUserAgentByDefault)
setOnPreferenceChangeListener { _, newValue ->
val useRandomUa = newValue as Boolean
preferences.edit().putBoolean(PREF_KEY_RANDOM_UA, useRandomUa).apply()
if (!useRandomUa) {
Toast.makeText(screen.context, RESTART_APP_STRING, Toast.LENGTH_LONG).show()
} else {
userAgent = null
if (preferences.getString(PREF_KEY_CUSTOM_UA, "").isNullOrBlank().not()) {
Toast.makeText(screen.context, SUMMARY_CLEANING_CUSTOM_UA, Toast.LENGTH_LONG).show()
}
}
preferences.edit().putString(PREF_KEY_CUSTOM_UA, "").apply()
// prefCustomUserAgent.summary = ""
true
}
}
screen.addPreference(prefUserAgent)
val prefCustomUserAgent = EditTextPreference(screen.context).apply {
key = PREF_KEY_CUSTOM_UA
title = TITLE_CUSTOM_UA
summary = preferences.getString(PREF_KEY_CUSTOM_UA, "")!!.trim()
setOnPreferenceChangeListener { _, newValue ->
val customUa = newValue as String
preferences.edit().putString(PREF_KEY_CUSTOM_UA, customUa).apply()
if (customUa.isBlank()) {
Toast.makeText(screen.context, RESTART_APP_STRING, Toast.LENGTH_LONG).show()
} else {
userAgent = null
}
summary = customUa.trim()
prefUserAgent.summary = ""
prefUserAgent.isChecked = false
true
}
}
screen.addPreference(prefCustomUserAgent)
} else {
Toast.makeText(screen.context, DOESNOT_SUPPORT_STRING, Toast.LENGTH_LONG).show()
}
}
// https://stackoverflow.com/a/66614516
private fun String.decodeHex(): ByteArray {
check(length % 2 == 0) { "Must have an even length" }
@ -1077,20 +949,12 @@ abstract class Madara(
.toByteArray()
}
override fun setupPreferenceScreen(screen: PreferenceScreen) {
randomUAPrefHelper.addPreferenceToScreen(screen)
}
companion object {
const val TITLE_RANDOM_UA = "Use Random Latest User-Agent"
const val PREF_KEY_RANDOM_UA = "pref_key_random_ua"
const val TITLE_CUSTOM_UA = "Custom User-Agent"
const val PREF_KEY_CUSTOM_UA = "pref_key_custom_ua"
const val SUMMARY_CLEANING_CUSTOM_UA = "$TITLE_CUSTOM_UA cleared."
const val RESTART_APP_STRING = "Restart Tachiyomi to apply new setting."
const val DOESNOT_SUPPORT_STRING = "This extension doesn't support User-Agent options."
const val URL_SEARCH_PREFIX = "slug:"
private const val UA_DB_URL = "https://tachiyomiorg.github.io/user-agents/user-agents.json"
val SALTED = "Salted__".toByteArray(Charsets.UTF_8)
}
}

View File

@ -10,7 +10,7 @@ class MadaraGenerator : ThemeSourceGenerator {
override val themeClass = "Madara"
override val baseVersionCode: Int = 30
override val baseVersionCode: Int = 31
override val sources = listOf(
MultiLang("Atlantis Scan", "https://atlantisscan.com", listOf("es", "pt-BR"), isNsfw = true),

View File

@ -1,5 +1,7 @@
package eu.kanade.tachiyomi.multisrc.mangahub
import eu.kanade.tachiyomi.lib.randomua.UserAgentType
import eu.kanade.tachiyomi.lib.randomua.setRandomUserAgent
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.interceptor.rateLimit
@ -48,7 +50,10 @@ abstract class MangaHub(
private var baseCdnUrl = "https://imgx.mghubcdn.com"
override val client: OkHttpClient = super.client.newBuilder()
.addInterceptor(::uaIntercept)
.setRandomUserAgent(
userAgentType = UserAgentType.DESKTOP,
filterInclude = listOf("chrome"),
)
.addInterceptor(::apiAuthInterceptor)
.rateLimit(1)
.build()
@ -65,35 +70,6 @@ abstract class MangaHub(
open val json: Json by injectLazy()
private var userAgent: String? = null
private var checkedUa = false
private fun uaIntercept(chain: Interceptor.Chain): Response {
if (userAgent == null && !checkedUa) {
val uaResponse = chain.proceed(GET(UA_DB_URL))
if (uaResponse.isSuccessful) {
// only using desktop chromium-based browsers, apparently they refuse to load(403) if not chrome(ium)
val uaList = json.decodeFromString<Map<String, List<String>>>(uaResponse.body.string())
val chromeUserAgentString = uaList["desktop"]!!.filter { it.contains("chrome", ignoreCase = true) }
userAgent = chromeUserAgentString.random()
checkedUa = true
}
uaResponse.close()
}
if (userAgent != null) {
val newRequest = chain.request().newBuilder()
.header("User-Agent", userAgent!!)
.build()
return chain.proceed(newRequest)
}
return chain.proceed(chain.request())
}
private fun apiAuthInterceptor(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
@ -514,8 +490,4 @@ abstract class MangaHub(
Genre("Wuxia", "wuxia"),
Genre("Yuri", "yuri"),
)
companion object {
private const val UA_DB_URL = "https://tachiyomiorg.github.io/user-agents/user-agents.json"
}
}

View File

@ -9,7 +9,7 @@ class MangaHubGenerator : ThemeSourceGenerator {
override val themeClass = "MangaHub"
override val baseVersionCode: Int = 21
override val baseVersionCode: Int = 22
override val sources = listOf(
// SingleLang("1Manga.co", "https://1manga.co", "en", isNsfw = true, className = "OneMangaCo"),

View File

@ -2,11 +2,9 @@ package eu.kanade.tachiyomi.multisrc.mangathemesia
import android.app.Application
import android.content.SharedPreferences
import android.util.Log
import android.widget.Toast
import androidx.preference.EditTextPreference
import androidx.preference.PreferenceScreen
import androidx.preference.SwitchPreferenceCompat
import eu.kanade.tachiyomi.lib.randomua.RandomUserAgentPreference
import eu.kanade.tachiyomi.lib.randomua.setRandomUserAgent
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.source.ConfigurableSource
@ -18,7 +16,6 @@ import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonPrimitive
@ -26,8 +23,6 @@ import okhttp3.FormBody
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
@ -37,7 +32,6 @@ import rx.Observable
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.io.IOException
import java.lang.IllegalArgumentException
import java.text.SimpleDateFormat
import java.util.Locale
@ -56,82 +50,19 @@ abstract class MangaThemesia(
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
private val randomUAPrefHelper: RandomUserAgentPreference by lazy {
RandomUserAgentPreference(preferences)
}
protected open val json: Json by injectLazy()
override val supportsLatest = true
// override with true if you want useRandomUserAgentByDefault to be on by default for some source
protected open val useRandomUserAgentByDefault: Boolean = false
protected open val filterIncludeUserAgent: List<String> = listOf()
protected open val filterExcludeUserAgent: List<String> = listOf()
private var userAgent: String? = null
private var checkedUa = false
protected val hasUaIntercept by lazy {
client.interceptors.toString().contains("uaIntercept")
}
protected val uaIntercept = object : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val useRandomUa = preferences.getBoolean(PREF_KEY_RANDOM_UA, false)
val customUa = preferences.getString(PREF_KEY_CUSTOM_UA, "")
try {
if (hasUaIntercept && (useRandomUa || customUa!!.isNotBlank())) {
Log.i("Extension_setting", "$TITLE_RANDOM_UA or $TITLE_CUSTOM_UA option is ENABLED")
if (customUa!!.isNotBlank() && useRandomUa.not()) {
userAgent = customUa
}
if (userAgent.isNullOrBlank() && !checkedUa) {
val uaResponse = chain.proceed(GET(UA_DB_URL))
if (uaResponse.isSuccessful) {
var listUserAgentString =
json.decodeFromString<Map<String, List<String>>>(uaResponse.body.string())["desktop"]
if (filterIncludeUserAgent.isNotEmpty()) {
listUserAgentString = listUserAgentString!!.filter {
filterIncludeUserAgent.any { filter ->
it.contains(filter, ignoreCase = true)
}
}
}
if (filterExcludeUserAgent.isNotEmpty()) {
listUserAgentString = listUserAgentString!!.filterNot {
filterExcludeUserAgent.any { filter ->
it.contains(filter, ignoreCase = true)
}
}
}
userAgent = listUserAgentString!!.random()
checkedUa = true
}
uaResponse.close()
}
if (userAgent.isNullOrBlank().not()) {
val newRequest = chain.request().newBuilder()
.header("User-Agent", userAgent!!.trim())
.build()
return chain.proceed(newRequest)
}
}
return chain.proceed(chain.request())
} catch (e: Exception) {
throw IOException(e.message)
}
}
}
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.addInterceptor(uaIntercept)
override val client = network.cloudflareClient.newBuilder()
.setRandomUserAgent(
randomUAPrefHelper.getPrefUAType(),
randomUAPrefHelper.getPrefCustomUA(),
)
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build()
@ -524,7 +455,9 @@ abstract class MangaThemesia(
val links = response.asJsoup().select("a[itemprop=item]")
// near the top of page: home > manga > current chapter
if (links.size == 3) {
return links[1].attr("href").toHttpUrlOrNull()?.encodedPath
val newUrl = links[1].attr("href").toHttpUrlOrNull() ?: return null
val isNewMangaUrl = (baseMangaUrl.host == newUrl.host && pathLengthIs(newUrl, 2) && newUrl.pathSegments[0] == baseMangaUrl.pathSegments[0])
if (isNewMangaUrl) return newUrl.pathSegments[1]
}
}
}
@ -566,58 +499,7 @@ abstract class MangaThemesia(
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not Used")
override fun setupPreferenceScreen(screen: PreferenceScreen) {
addRandomAndCustomUserAgentPreferences(screen)
}
protected fun addRandomAndCustomUserAgentPreferences(screen: PreferenceScreen) {
if (!hasUaIntercept) {
return // Unable to change the user agent. Therefore the preferences won't be displayed.
}
val prefRandomUserAgent = SwitchPreferenceCompat(screen.context).apply {
key = PREF_KEY_RANDOM_UA
title = TITLE_RANDOM_UA
summary = if (preferences.getBoolean(PREF_KEY_RANDOM_UA, useRandomUserAgentByDefault)) userAgent else ""
setDefaultValue(useRandomUserAgentByDefault)
setOnPreferenceChangeListener { _, newValue ->
val useRandomUa = newValue as Boolean
preferences.edit().putBoolean(PREF_KEY_RANDOM_UA, useRandomUa).apply()
if (!useRandomUa) {
Toast.makeText(screen.context, RESTART_APP_STRING, Toast.LENGTH_LONG).show()
} else {
userAgent = null
if (preferences.getString(PREF_KEY_CUSTOM_UA, "").isNullOrBlank().not()) {
Toast.makeText(screen.context, SUMMARY_CLEANING_CUSTOM_UA, Toast.LENGTH_LONG).show()
}
}
preferences.edit().putString(PREF_KEY_CUSTOM_UA, "").apply()
true
}
}
val prefCustomUserAgent = EditTextPreference(screen.context).apply {
key = PREF_KEY_CUSTOM_UA
title = TITLE_CUSTOM_UA
summary = preferences.getString(PREF_KEY_CUSTOM_UA, "")!!.trim()
setOnPreferenceChangeListener { _, newValue ->
val customUa = newValue as String
preferences.edit().putString(PREF_KEY_CUSTOM_UA, customUa).apply()
if (customUa.isBlank()) {
Toast.makeText(screen.context, RESTART_APP_STRING, Toast.LENGTH_LONG).show()
} else {
userAgent = null
}
summary = customUa.trim()
prefRandomUserAgent.summary = ""
prefRandomUserAgent.isChecked = false
true
}
}
screen.addPreference(prefRandomUserAgent)
screen.addPreference(prefCustomUserAgent)
randomUAPrefHelper.addPreferenceToScreen(screen)
}
companion object {
@ -629,16 +511,5 @@ abstract class MangaThemesia(
private val CHAPTER_PAGE_ID_REGEX = "chapter_id\\s*=\\s*(\\d+);".toRegex()
val JSON_IMAGE_LIST_REGEX = "\"images\"\\s*:\\s*(\\[.*?])".toRegex()
const val TITLE_RANDOM_UA = "Use Random Latest User-Agent"
const val PREF_KEY_RANDOM_UA = "pref_key_random_ua"
const val TITLE_CUSTOM_UA = "Custom User-Agent"
const val PREF_KEY_CUSTOM_UA = "pref_key_custom_ua"
const val SUMMARY_CLEANING_CUSTOM_UA = "$TITLE_CUSTOM_UA cleared."
const val RESTART_APP_STRING = "Restart Tachiyomi to apply new setting."
private const val UA_DB_URL = "https://tachiyomiorg.github.io/user-agents/user-agents.json"
}
}

View File

@ -11,7 +11,7 @@ class MangaThemesiaGenerator : ThemeSourceGenerator {
override val themeClass = "MangaThemesia"
override val baseVersionCode: Int = 25
override val baseVersionCode: Int = 26
override val sources = listOf(
MultiLang("Asura Scans", "https://www.asurascans.com", listOf("en", "tr"), className = "AsuraScansFactory", pkgName = "asurascans", overrideVersionCode = 23),

View File

@ -1,8 +1,10 @@
include(":core")
listOf("dataimage", "unpacker", "cryptoaes", "textinterceptor", "synchrony").forEach {
include(":lib-$it")
project(":lib-$it").projectDir = File("lib/$it")
// all the directories under /lib instead of manually adding each to a list
File(rootDir, "lib").eachDir {
val libName = it.name
include(":lib-$libName")
project(":lib-$libName").projectDir = File("lib/$libName")
}
if (System.getenv("CI") == null || System.getenv("CI_MODULE_GEN") == "true") {

View File

@ -5,8 +5,12 @@ ext {
extName = 'NHentai'
pkgNameSuffix = 'all.nhentai'
extClass = '.NHFactory'
extVersionCode = 37
extVersionCode = 38
isNsfw = true
}
dependencies {
implementation(project(":lib-randomua"))
}
apply from: "$rootDir/common.gradle"

View File

@ -2,12 +2,16 @@ package eu.kanade.tachiyomi.extension.all.nhentai
import android.app.Application
import android.content.SharedPreferences
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.extension.all.nhentai.NHUtils.getArtists
import eu.kanade.tachiyomi.extension.all.nhentai.NHUtils.getGroups
import eu.kanade.tachiyomi.extension.all.nhentai.NHUtils.getNumPages
import eu.kanade.tachiyomi.extension.all.nhentai.NHUtils.getTagDescription
import eu.kanade.tachiyomi.extension.all.nhentai.NHUtils.getTags
import eu.kanade.tachiyomi.extension.all.nhentai.NHUtils.getTime
import eu.kanade.tachiyomi.lib.randomua.UserAgentType
import eu.kanade.tachiyomi.lib.randomua.setRandomUserAgent
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.network.interceptor.rateLimit
@ -22,7 +26,6 @@ import eu.kanade.tachiyomi.source.model.UpdateStrategy
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
@ -44,7 +47,11 @@ open class NHentai(
override val supportsLatest = true
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
override val client = network.cloudflareClient.newBuilder()
.setRandomUserAgent(
userAgentType = UserAgentType.MOBILE,
filterInclude = listOf("chrome"),
)
.rateLimit(4)
.build()
@ -60,13 +67,14 @@ open class NHentai(
private val shortenTitleRegex = Regex("""(\[[^]]*]|[({][^)}]*[)}])""")
private fun String.shortenTitle() = this.replace(shortenTitleRegex, "").trim()
override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) {
val serverPref = androidx.preference.ListPreference(screen.context).apply {
override fun setupPreferenceScreen(screen: PreferenceScreen) {
ListPreference(screen.context).apply {
key = TITLE_PREF
title = TITLE_PREF
entries = arrayOf("Full Title", "Short Title")
entryValues = arrayOf("full", "short")
summary = "%s"
setDefaultValue("full")
setOnPreferenceChangeListener { _, newValue ->
displayFullTitle = when (newValue) {
@ -75,13 +83,7 @@ open class NHentai(
}
true
}
}
if (!preferences.contains(TITLE_PREF)) {
preferences.edit().putString(TITLE_PREF, "full").apply()
}
screen.addPreference(serverPref)
}.also(screen::addPreference)
}
override fun latestUpdatesRequest(page: Int) = GET(if (nhLang.isBlank()) "$baseUrl/?page=$page" else "$baseUrl/language/$nhLang/?page=$page", headers)