From 70ff14a274d19cf1a2988dc824a6297b9e079f2b Mon Sep 17 00:00:00 2001 From: Luqman Date: Thu, 5 Jan 2023 21:50:27 +0700 Subject: [PATCH] Madara Custom and Random Latest User-Agent (#14688) * Random Latest User-Agent v0.5 * use better checker for ::uaIntercept * update wording * fix lint 1 * rerun * rerun 2 * Random Latest User-Agent v0.5 * use better checker for ::uaIntercept * update wording * fix lint 1 * rerun * rerun 2 * add custom useragent and use IOException * Fix some issue, reuse string * fix lint 1 * fix lint 2 * fix lint 3 * not supported toast, base version * add log if setting is on --- .../madara/adonisfansub/src/AdonisFansub.kt | 1 - .../madara/yugenmangas/src/YugenMangas.kt | 43 +---- .../tachiyomi/multisrc/madara/Madara.kt | 165 +++++++++++++++++- .../multisrc/madara/MadaraGenerator.kt | 2 +- 4 files changed, 161 insertions(+), 50 deletions(-) diff --git a/multisrc/overrides/madara/adonisfansub/src/AdonisFansub.kt b/multisrc/overrides/madara/adonisfansub/src/AdonisFansub.kt index 650964ab5..4c21611b4 100644 --- a/multisrc/overrides/madara/adonisfansub/src/AdonisFansub.kt +++ b/multisrc/overrides/madara/adonisfansub/src/AdonisFansub.kt @@ -5,7 +5,6 @@ import eu.kanade.tachiyomi.network.GET import okhttp3.Request class AdonisFansub : Madara("Adonis Fansub", "https://manga.adonisfansub.com", "tr") { - override val userAgentRandomizer = "" override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/manga/page/$page/?m_orderby=views", headers) override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/manga/page/$page/?m_orderby=latest", headers) } diff --git a/multisrc/overrides/madara/yugenmangas/src/YugenMangas.kt b/multisrc/overrides/madara/yugenmangas/src/YugenMangas.kt index e3aa8cc94..977bac8cb 100644 --- a/multisrc/overrides/madara/yugenmangas/src/YugenMangas.kt +++ b/multisrc/overrides/madara/yugenmangas/src/YugenMangas.kt @@ -1,16 +1,11 @@ package eu.kanade.tachiyomi.extension.pt.yugenmangas 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.SChapter -import kotlinx.serialization.decodeFromString import okhttp3.Headers -import okhttp3.Interceptor import okhttp3.OkHttpClient -import okhttp3.Response import org.jsoup.nodes.Element -import java.io.IOException import java.text.SimpleDateFormat import java.util.Locale import java.util.concurrent.TimeUnit @@ -23,7 +18,7 @@ class YugenMangas : Madara( ) { override val client: OkHttpClient = super.client.newBuilder() - .addInterceptor(::uaIntercept) + .addInterceptor(uaIntercept) .rateLimit(1, 3, TimeUnit.SECONDS) .build() @@ -49,39 +44,5 @@ class YugenMangas : Madara( ) } - private var userAgent: String? = null - private var checkedUa = false - - private fun uaIntercept(chain: Interceptor.Chain): Response { - try { - if (userAgent == null && !checkedUa) { - val uaResponse = chain.proceed(GET(UA_DB_URL)) - - if (uaResponse.isSuccessful) { - val uaMap = - json.decodeFromString>>(uaResponse.body!!.string()) - userAgent = uaMap["desktop"]?.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()) - } catch (e: Exception) { - throw IOException(e.message) - } - } - - companion object { - private const val UA_DB_URL = "https://tachiyomiorg.github.io/user-agents/user-agents.json" - } + override val useRandomUserAgentByDefault: Boolean = true } diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/madara/Madara.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/madara/Madara.kt index e1cf10e3f..ed0f82047 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/madara/Madara.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/madara/Madara.kt @@ -1,8 +1,16 @@ package eu.kanade.tachiyomi.multisrc.madara +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.network.GET import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.asObservable +import eu.kanade.tachiyomi.source.ConfigurableSource import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.MangasPage @@ -11,6 +19,7 @@ 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.jsonObject import kotlinx.serialization.json.jsonPrimitive @@ -18,38 +27,119 @@ 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 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.io.IOException import java.text.ParseException import java.text.SimpleDateFormat import java.util.Calendar import java.util.Locale import java.util.concurrent.TimeUnit -import kotlin.math.absoluteValue -import kotlin.random.Random abstract class Madara( override val name: String, override val baseUrl: String, final override val lang: String, private val dateFormat: SimpleDateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale.US) -) : ParsedHttpSource() { +) : ParsedHttpSource(), ConfigurableSource { + + private val preferences: SharedPreferences by lazy { + Injekt.get().getSharedPreferences("source_$id", 0x0000) + } 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 = listOf() + protected open val filterExcludeUserAgent: List = 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>>(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) .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) .build() - // helps with cloudflare for some sources, makes it worse for others; override with empty string if the latter is true - protected open val userAgentRandomizer = " ${Random.nextInt().absoluteValue}" - protected open val json: Json by injectLazy() /** @@ -79,7 +169,6 @@ abstract class Madara( protected open val fetchGenres: Boolean = true override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:102.0) Gecko/20100101 Firefox/102.0$userAgentRandomizer") .add("Referer", baseUrl) // Popular Manga @@ -1041,8 +1130,70 @@ 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.isNullOrBlank()) { + 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() + } + } + 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" } } diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/madara/MadaraGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/madara/MadaraGenerator.kt index 9944b96fa..1cf3edfc1 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/madara/MadaraGenerator.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/madara/MadaraGenerator.kt @@ -10,7 +10,7 @@ class MadaraGenerator : ThemeSourceGenerator { override val themeClass = "Madara" - override val baseVersionCode: Int = 26 + override val baseVersionCode: Int = 27 override val sources = listOf( MultiLang("Leviatan Scans", "https://leviatanscans.com", listOf("en", "es"), className = "LeviatanScansFactory", overrideVersionCode = 13),