AdBlock for WebView
This commit is contained in:
@@ -5,7 +5,9 @@
|
|||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission
|
||||||
|
android:name="android.permission.FOREGROUND_SERVICE"
|
||||||
|
tools:ignore="ForegroundServicesPolicy" />
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
<uses-permission android:name="android.permission.VIBRATE" />
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||||
@@ -19,17 +21,19 @@
|
|||||||
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
|
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
||||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
<uses-permission
|
||||||
|
android:name="android.permission.REQUEST_INSTALL_PACKAGES"
|
||||||
|
tools:ignore="RequestInstallPackagesPolicy" />
|
||||||
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
|
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
|
||||||
<uses-permission
|
<uses-permission
|
||||||
android:name="android.permission.QUERY_ALL_PACKAGES"
|
android:name="android.permission.QUERY_ALL_PACKAGES"
|
||||||
tools:ignore="QueryAllPackagesPermission" />
|
tools:ignore="PackageVisibilityPolicy,QueryAllPackagesPermission" />
|
||||||
<uses-permission
|
<uses-permission
|
||||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||||
android:maxSdkVersion="29" />
|
android:maxSdkVersion="29" />
|
||||||
<uses-permission
|
<uses-permission
|
||||||
android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
|
android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
|
||||||
tools:ignore="ScopedStorage" />
|
tools:ignore="AllFilesAccessPolicy,ScopedStorage" />
|
||||||
|
|
||||||
<queries>
|
<queries>
|
||||||
<intent>
|
<intent>
|
||||||
@@ -338,6 +342,9 @@
|
|||||||
android:name="org.koitharu.kotatsu.details.service.MangaPrefetchService"
|
android:name="org.koitharu.kotatsu.details.service.MangaPrefetchService"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:label="@string/prefetch_content" />
|
android:label="@string/prefetch_content" />
|
||||||
|
<service
|
||||||
|
android:name="org.koitharu.kotatsu.browser.AdListUpdateService"
|
||||||
|
android:exported="false" />
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name="org.koitharu.kotatsu.search.ui.MangaSuggestionsProvider"
|
android:name="org.koitharu.kotatsu.search.ui.MangaSuggestionsProvider"
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package org.koitharu.kotatsu.browser
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import org.koitharu.kotatsu.core.network.webview.adblock.AdBlock
|
||||||
|
import org.koitharu.kotatsu.core.ui.CoroutineIntentService
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
|
class AdListUpdateService : CoroutineIntentService() {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var updater: AdBlock.Updater
|
||||||
|
|
||||||
|
override suspend fun IntentJobContext.processIntent(intent: Intent) {
|
||||||
|
updater.updateList()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun IntentJobContext.onError(error: Throwable) = Unit
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ import org.koitharu.kotatsu.core.model.MangaSource
|
|||||||
import org.koitharu.kotatsu.core.nav.AppRouter
|
import org.koitharu.kotatsu.core.nav.AppRouter
|
||||||
import org.koitharu.kotatsu.core.network.CommonHeaders
|
import org.koitharu.kotatsu.core.network.CommonHeaders
|
||||||
import org.koitharu.kotatsu.core.network.proxy.ProxyProvider
|
import org.koitharu.kotatsu.core.network.proxy.ProxyProvider
|
||||||
|
import org.koitharu.kotatsu.core.network.webview.adblock.AdBlock
|
||||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||||
import org.koitharu.kotatsu.core.parser.ParserMangaRepository
|
import org.koitharu.kotatsu.core.parser.ParserMangaRepository
|
||||||
import org.koitharu.kotatsu.core.ui.BaseActivity
|
import org.koitharu.kotatsu.core.ui.BaseActivity
|
||||||
@@ -29,6 +30,9 @@ abstract class BaseBrowserActivity : BaseActivity<ActivityBrowserBinding>(), Bro
|
|||||||
@Inject
|
@Inject
|
||||||
lateinit var mangaRepositoryFactory: MangaRepository.Factory
|
lateinit var mangaRepositoryFactory: MangaRepository.Factory
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var adBlock: AdBlock
|
||||||
|
|
||||||
private lateinit var onBackPressedCallback: WebViewBackPressedCallback
|
private lateinit var onBackPressedCallback: WebViewBackPressedCallback
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class BrowserActivity : BaseBrowserActivity() {
|
|||||||
|
|
||||||
override fun onCreate2(savedInstanceState: Bundle?, source: MangaSource, repository: ParserMangaRepository?) {
|
override fun onCreate2(savedInstanceState: Bundle?, source: MangaSource, repository: ParserMangaRepository?) {
|
||||||
setDisplayHomeAsUp(isEnabled = true, showUpAsClose = true)
|
setDisplayHomeAsUp(isEnabled = true, showUpAsClose = true)
|
||||||
viewBinding.webView.webViewClient = BrowserClient(this)
|
viewBinding.webView.webViewClient = BrowserClient(this, adBlock)
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
try {
|
try {
|
||||||
proxyProvider.applyWebViewConfig()
|
proxyProvider.applyWebViewConfig()
|
||||||
|
|||||||
@@ -1,11 +1,17 @@
|
|||||||
package org.koitharu.kotatsu.browser
|
package org.koitharu.kotatsu.browser
|
||||||
|
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
|
import android.webkit.WebResourceRequest
|
||||||
|
import android.webkit.WebResourceResponse
|
||||||
import android.webkit.WebView
|
import android.webkit.WebView
|
||||||
import android.webkit.WebViewClient
|
import android.webkit.WebViewClient
|
||||||
|
import androidx.annotation.WorkerThread
|
||||||
|
import org.koitharu.kotatsu.core.network.webview.adblock.AdBlock
|
||||||
|
import java.io.ByteArrayInputStream
|
||||||
|
|
||||||
open class BrowserClient(
|
open class BrowserClient(
|
||||||
private val callback: BrowserCallback
|
private val callback: BrowserCallback,
|
||||||
|
private val adBlock: AdBlock,
|
||||||
) : WebViewClient() {
|
) : WebViewClient() {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -31,4 +37,29 @@ open class BrowserClient(
|
|||||||
super.doUpdateVisitedHistory(view, url, isReload)
|
super.doUpdateVisitedHistory(view, url, isReload)
|
||||||
callback.onHistoryChanged()
|
callback.onHistoryChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
@Deprecated("Deprecated in Java")
|
||||||
|
override fun shouldInterceptRequest(
|
||||||
|
view: WebView?,
|
||||||
|
url: String?
|
||||||
|
): WebResourceResponse? = if (url.isNullOrEmpty() || adBlock.shouldLoadUrl(url, view?.url)) {
|
||||||
|
super.shouldInterceptRequest(view, url)
|
||||||
|
} else {
|
||||||
|
emptyResponse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
override fun shouldInterceptRequest(
|
||||||
|
view: WebView?,
|
||||||
|
request: WebResourceRequest?
|
||||||
|
): WebResourceResponse? = if (request == null || adBlock.shouldLoadUrl(request.url.toString(), view?.url)) {
|
||||||
|
view?.originalUrl
|
||||||
|
super.shouldInterceptRequest(view, request)
|
||||||
|
} else {
|
||||||
|
emptyResponse()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun emptyResponse(): WebResourceResponse =
|
||||||
|
WebResourceResponse("text/plain", "utf-8", ByteArrayInputStream(byteArrayOf()))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ class CloudFlareActivity : BaseBrowserActivity(), CloudFlareCallback {
|
|||||||
finishAfterTransition()
|
finishAfterTransition()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cfClient = CloudFlareClient(cookieJar, this, url)
|
cfClient = CloudFlareClient(cookieJar, this, adBlock, url)
|
||||||
viewBinding.webView.webViewClient = cfClient
|
viewBinding.webView.webViewClient = cfClient
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import android.graphics.Bitmap
|
|||||||
import android.webkit.WebView
|
import android.webkit.WebView
|
||||||
import org.koitharu.kotatsu.browser.BrowserClient
|
import org.koitharu.kotatsu.browser.BrowserClient
|
||||||
import org.koitharu.kotatsu.core.network.cookies.MutableCookieJar
|
import org.koitharu.kotatsu.core.network.cookies.MutableCookieJar
|
||||||
|
import org.koitharu.kotatsu.core.network.webview.adblock.AdBlock
|
||||||
import org.koitharu.kotatsu.parsers.network.CloudFlareHelper
|
import org.koitharu.kotatsu.parsers.network.CloudFlareHelper
|
||||||
|
|
||||||
private const val LOOP_COUNTER = 3
|
private const val LOOP_COUNTER = 3
|
||||||
@@ -11,8 +12,9 @@ private const val LOOP_COUNTER = 3
|
|||||||
class CloudFlareClient(
|
class CloudFlareClient(
|
||||||
private val cookieJar: MutableCookieJar,
|
private val cookieJar: MutableCookieJar,
|
||||||
private val callback: CloudFlareCallback,
|
private val callback: CloudFlareCallback,
|
||||||
|
adBlock: AdBlock,
|
||||||
private val targetUrl: String,
|
private val targetUrl: String,
|
||||||
) : BrowserClient(callback) {
|
) : BrowserClient(callback, adBlock) {
|
||||||
|
|
||||||
private val oldClearance = getClearance()
|
private val oldClearance = getClearance()
|
||||||
private var counter = 0
|
private var counter = 0
|
||||||
|
|||||||
@@ -16,8 +16,12 @@ object CommonHeaders {
|
|||||||
const val CACHE_CONTROL = "Cache-Control"
|
const val CACHE_CONTROL = "Cache-Control"
|
||||||
const val PROXY_AUTHORIZATION = "Proxy-Authorization"
|
const val PROXY_AUTHORIZATION = "Proxy-Authorization"
|
||||||
const val RETRY_AFTER = "Retry-After"
|
const val RETRY_AFTER = "Retry-After"
|
||||||
|
const val LAST_MODIFIED = "Last-Modified"
|
||||||
|
const val IF_MODIFIED_SINCE = "If-Modified-Since"
|
||||||
const val MANGA_SOURCE = "X-Manga-Source"
|
const val MANGA_SOURCE = "X-Manga-Source"
|
||||||
|
|
||||||
val CACHE_CONTROL_NO_STORE: CacheControl
|
val CACHE_CONTROL_NO_STORE: CacheControl
|
||||||
get() = CacheControl.Builder().noStore().build()
|
get() = CacheControl.Builder().noStore().build()
|
||||||
|
|
||||||
|
const val DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss zzz"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,122 @@
|
|||||||
|
package org.koitharu.kotatsu.core.network.webview.adblock
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.annotation.WorkerThread
|
||||||
|
import dagger.Reusable
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
import okhttp3.HttpUrl
|
||||||
|
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.Request
|
||||||
|
import okio.sink
|
||||||
|
import org.koitharu.kotatsu.core.network.BaseHttpClient
|
||||||
|
import org.koitharu.kotatsu.core.network.CommonHeaders
|
||||||
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.isNotEmpty
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||||
|
import org.koitharu.kotatsu.parsers.util.await
|
||||||
|
import org.koitharu.kotatsu.parsers.util.requireBody
|
||||||
|
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||||
|
import java.io.File
|
||||||
|
import java.net.HttpURLConnection
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Date
|
||||||
|
import java.util.Locale
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@Reusable
|
||||||
|
class AdBlock @Inject constructor(
|
||||||
|
@ApplicationContext private val context: Context,
|
||||||
|
private val settings: AppSettings,
|
||||||
|
) {
|
||||||
|
|
||||||
|
private var rules: RulesList? = null
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
fun shouldLoadUrl(url: String, baseUrl: String?): Boolean {
|
||||||
|
return shouldLoadUrl(
|
||||||
|
url.lowercase().toHttpUrlOrNull() ?: return true,
|
||||||
|
baseUrl?.lowercase()?.toHttpUrlOrNull(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
fun shouldLoadUrl(url: HttpUrl, baseUrl: HttpUrl?): Boolean {
|
||||||
|
if (!settings.isAdBlockEnabled) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return synchronized(this) {
|
||||||
|
rules ?: parseRules().also { rules = it }
|
||||||
|
}?.let {
|
||||||
|
val rule = it[url, baseUrl]
|
||||||
|
if (rule != null) {
|
||||||
|
Log.i(TAG, "Blocked $url by $rule")
|
||||||
|
}
|
||||||
|
rule == null
|
||||||
|
} ?: true
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
private fun parseRules() = runCatchingCancellable {
|
||||||
|
listFile(context).useLines { lines ->
|
||||||
|
val rules = RulesList()
|
||||||
|
lines.forEach { line -> rules.add(line) }
|
||||||
|
rules.trimToSize()
|
||||||
|
rules
|
||||||
|
}
|
||||||
|
}.onFailure { e ->
|
||||||
|
e.printStackTraceDebug()
|
||||||
|
}.getOrNull()
|
||||||
|
|
||||||
|
class Updater @Inject constructor(
|
||||||
|
@ApplicationContext private val context: Context,
|
||||||
|
@BaseHttpClient private val okHttpClient: OkHttpClient,
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend fun updateList() {
|
||||||
|
val file = listFile(context)
|
||||||
|
val dateFormat = SimpleDateFormat(CommonHeaders.DATE_FORMAT, Locale.ENGLISH)
|
||||||
|
val requestBuilder = Request.Builder()
|
||||||
|
.url(EASYLIST_URL)
|
||||||
|
.get()
|
||||||
|
if (file.exists() && file.isNotEmpty()) {
|
||||||
|
val lastModified = file.lastModified()
|
||||||
|
requestBuilder.header(CommonHeaders.IF_MODIFIED_SINCE, dateFormat.format(Date(lastModified)))
|
||||||
|
}
|
||||||
|
okHttpClient.newCall(
|
||||||
|
requestBuilder.build(),
|
||||||
|
).await().use { response ->
|
||||||
|
if (response.code == HttpURLConnection.HTTP_NOT_MODIFIED) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val lastModified = response.header(CommonHeaders.LAST_MODIFIED)?.let {
|
||||||
|
runCatching {
|
||||||
|
dateFormat.parse(it)
|
||||||
|
}.getOrNull()
|
||||||
|
}?.time ?: System.currentTimeMillis()
|
||||||
|
response.requireBody().source().use { source ->
|
||||||
|
file.sink().use { sink ->
|
||||||
|
source.readAll(sink)
|
||||||
|
}
|
||||||
|
file.setLastModified(lastModified)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
|
||||||
|
fun listFile(context: Context): File {
|
||||||
|
val root = File(context.externalCacheDir ?: context.cacheDir, LIST_DIR)
|
||||||
|
root.mkdir()
|
||||||
|
return File(root, LIST_FILENAME)
|
||||||
|
}
|
||||||
|
|
||||||
|
private const val LIST_FILENAME = "easylist.txt"
|
||||||
|
private const val LIST_DIR = "adblock"
|
||||||
|
private const val EASYLIST_URL = "https://easylist.to/easylist/easylist.txt"
|
||||||
|
private const val TAG = "AdBlock"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package org.koitharu.kotatsu.core.network.webview.adblock
|
||||||
|
|
||||||
|
import androidx.collection.ArraySet
|
||||||
|
|
||||||
|
class CSSRuleBuilder {
|
||||||
|
|
||||||
|
private val selectors = ArraySet<String>()
|
||||||
|
|
||||||
|
fun add(selector: String) {
|
||||||
|
selectors.add(selector)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun build() = buildString {
|
||||||
|
append("<style> {")
|
||||||
|
for (selector in selectors) {
|
||||||
|
append(selector)
|
||||||
|
append(";")
|
||||||
|
}
|
||||||
|
append("}!important</style>")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package org.koitharu.kotatsu.core.network.webview.adblock
|
||||||
|
|
||||||
|
import okhttp3.HttpUrl
|
||||||
|
|
||||||
|
sealed interface Rule {
|
||||||
|
|
||||||
|
operator fun invoke(url: HttpUrl, baseUrl: HttpUrl?): Boolean
|
||||||
|
|
||||||
|
data class Domain(private val domain: String) : Rule {
|
||||||
|
|
||||||
|
override fun invoke(url: HttpUrl, baseUrl: HttpUrl?): Boolean = (url.topPrivateDomain() ?: url.host) == domain
|
||||||
|
}
|
||||||
|
|
||||||
|
data class ExactUrl(private val url: HttpUrl) : Rule {
|
||||||
|
|
||||||
|
override operator fun invoke(url: HttpUrl, baseUrl: HttpUrl?): Boolean = url == this.url
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Path(private val path: String, private val contains: Boolean) : Rule {
|
||||||
|
|
||||||
|
override fun invoke(url: HttpUrl, baseUrl: HttpUrl?): Boolean {
|
||||||
|
val fullPath = url.host + "/" + url.encodedPath
|
||||||
|
return if (contains) {
|
||||||
|
fullPath.contains(path)
|
||||||
|
} else {
|
||||||
|
fullPath.endsWith(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class WithModifiers(
|
||||||
|
private val baseRule: Rule,
|
||||||
|
private val script: Boolean?,
|
||||||
|
private val thirdParty: Boolean?,
|
||||||
|
private val domains: Set<String>?,
|
||||||
|
private val domainsNot: Set<String>?,
|
||||||
|
) : Rule {
|
||||||
|
|
||||||
|
override fun invoke(url: HttpUrl, baseUrl: HttpUrl?): Boolean {
|
||||||
|
if (!baseRule.invoke(url, baseUrl)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (baseUrl == null) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
thirdParty?.let {
|
||||||
|
val isThirdPartyRequest =
|
||||||
|
(url.topPrivateDomain() ?: url.host) != (baseUrl.topPrivateDomain() ?: baseUrl.host)
|
||||||
|
if (isThirdPartyRequest != it) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO check other modifiers
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
package org.koitharu.kotatsu.core.network.webview.adblock
|
||||||
|
|
||||||
|
import androidx.annotation.CheckResult
|
||||||
|
import okhttp3.HttpUrl
|
||||||
|
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Very simple implementation of adblock list parser
|
||||||
|
* Not all features are supported
|
||||||
|
*/
|
||||||
|
class RulesList {
|
||||||
|
|
||||||
|
private val blockRules = ArrayList<Rule>()
|
||||||
|
private val allowRules = ArrayList<Rule>()
|
||||||
|
|
||||||
|
operator fun get(url: HttpUrl, baseUrl: HttpUrl?): Rule? {
|
||||||
|
val rule = blockRules.find { x -> x(url, baseUrl) }
|
||||||
|
return rule?.takeIf { allowRules.none { x -> x(url, baseUrl) } }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun add(line: String) {
|
||||||
|
val parts = line.lowercase().trim().split('$')
|
||||||
|
parts.first().addImpl(isWhitelist = false, modifiers = parts.getOrNull(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun trimToSize() {
|
||||||
|
blockRules.trimToSize()
|
||||||
|
allowRules.trimToSize()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun String.addImpl(isWhitelist: Boolean, modifiers: String?) {
|
||||||
|
val list = if (isWhitelist) allowRules else blockRules
|
||||||
|
|
||||||
|
when {
|
||||||
|
startsWith('!') || startsWith('[') -> {
|
||||||
|
// Comment, do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
startsWith("||") -> {
|
||||||
|
// domain
|
||||||
|
list += Rule.Domain(substring(2).substringBefore('^').trim()).withModifiers(modifiers)
|
||||||
|
}
|
||||||
|
|
||||||
|
startsWith('|') -> {
|
||||||
|
val url = substring(1).substringBefore('^').trim().toHttpUrlOrNull()
|
||||||
|
if (url != null) {
|
||||||
|
list += Rule.ExactUrl(url).withModifiers(modifiers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
startsWith("@@") -> {
|
||||||
|
substring(2).substringBefore('^').trim().addImpl(!isWhitelist, modifiers)
|
||||||
|
}
|
||||||
|
|
||||||
|
startsWith("##") -> {
|
||||||
|
// TODO css rules
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
if (endsWith('*')) {
|
||||||
|
list += Rule.Path(this.dropLast(1), contains = true).withModifiers(modifiers)
|
||||||
|
} else if (!contains('*')) { // wildcards is not supported yet
|
||||||
|
list += Rule.Path(this, contains = false).withModifiers(modifiers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@CheckResult
|
||||||
|
private fun Rule.withModifiers(options: String?): Rule {
|
||||||
|
if (options.isNullOrEmpty()) {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
var script: Boolean? = null
|
||||||
|
var thirdParty: Boolean? = null
|
||||||
|
options.split(',').forEach {
|
||||||
|
val isNot = it.startsWith('~')
|
||||||
|
when (it.removePrefix("~")) {
|
||||||
|
"script" -> script = !isNot
|
||||||
|
"third-party" -> thirdParty = !isNot
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Rule.WithModifiers(
|
||||||
|
baseRule = this,
|
||||||
|
script = script,
|
||||||
|
thirdParty = thirdParty,
|
||||||
|
domains = null, //TODO
|
||||||
|
domainsNot = null, //TODO
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -320,6 +320,9 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
|||||||
val screenshotsPolicy: ScreenshotsPolicy
|
val screenshotsPolicy: ScreenshotsPolicy
|
||||||
get() = prefs.getEnumValue(KEY_SCREENSHOTS_POLICY, ScreenshotsPolicy.ALLOW)
|
get() = prefs.getEnumValue(KEY_SCREENSHOTS_POLICY, ScreenshotsPolicy.ALLOW)
|
||||||
|
|
||||||
|
val isAdBlockEnabled: Boolean
|
||||||
|
get() = prefs.getBoolean(KEY_ADBLOCK, false)
|
||||||
|
|
||||||
var userSpecifiedMangaDirectories: Set<File>
|
var userSpecifiedMangaDirectories: Set<File>
|
||||||
get() {
|
get() {
|
||||||
val set = prefs.getStringSet(KEY_LOCAL_MANGA_DIRS, emptySet()).orEmpty()
|
val set = prefs.getStringSet(KEY_LOCAL_MANGA_DIRS, emptySet()).orEmpty()
|
||||||
@@ -598,6 +601,7 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
|||||||
const val TRACK_HISTORY = "history"
|
const val TRACK_HISTORY = "history"
|
||||||
const val TRACK_FAVOURITES = "favourites"
|
const val TRACK_FAVOURITES = "favourites"
|
||||||
|
|
||||||
|
const val KEY_ADBLOCK = "adblock"
|
||||||
const val KEY_LIST_MODE = "list_mode_2"
|
const val KEY_LIST_MODE = "list_mode_2"
|
||||||
const val KEY_LIST_MODE_HISTORY = "list_mode_history"
|
const val KEY_LIST_MODE_HISTORY = "list_mode_history"
|
||||||
const val KEY_LIST_MODE_FAVORITES = "list_mode_favorites"
|
const val KEY_LIST_MODE_FAVORITES = "list_mode_favorites"
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ import kotlinx.coroutines.flow.map
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
|
import org.koitharu.kotatsu.browser.AdListUpdateService
|
||||||
import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
|
import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
|
||||||
import org.koitharu.kotatsu.core.nav.router
|
import org.koitharu.kotatsu.core.nav.router
|
||||||
import org.koitharu.kotatsu.core.os.VoiceInputContract
|
import org.koitharu.kotatsu.core.os.VoiceInputContract
|
||||||
@@ -290,6 +291,9 @@ class MainActivity : BaseActivity<ActivityMainBinding>(), AppBarOwner, BottomNav
|
|||||||
}
|
}
|
||||||
startService(Intent(this@MainActivity, LocalIndexUpdateService::class.java))
|
startService(Intent(this@MainActivity, LocalIndexUpdateService::class.java))
|
||||||
startService(Intent(this@MainActivity, PeriodicalBackupService::class.java))
|
startService(Intent(this@MainActivity, PeriodicalBackupService::class.java))
|
||||||
|
if (settings.isAdBlockEnabled) {
|
||||||
|
startService(Intent(this@MainActivity, AdListUpdateService::class.java))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ class SourceAuthActivity : BaseBrowserActivity(), BrowserCallback {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
setDisplayHomeAsUp(isEnabled = true, showUpAsClose = true)
|
setDisplayHomeAsUp(isEnabled = true, showUpAsClose = true)
|
||||||
viewBinding.webView.webViewClient = BrowserClient(this)
|
viewBinding.webView.webViewClient = BrowserClient(this, adBlock)
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
try {
|
try {
|
||||||
proxyProvider.applyWebViewConfig()
|
proxyProvider.applyWebViewConfig()
|
||||||
|
|||||||
@@ -842,4 +842,6 @@
|
|||||||
<string name="changelog_summary">Changes history for recently released versions</string>
|
<string name="changelog_summary">Changes history for recently released versions</string>
|
||||||
<string name="collapse">Collapse</string>
|
<string name="collapse">Collapse</string>
|
||||||
<string name="expand">Expand</string>
|
<string name="expand">Expand</string>
|
||||||
|
<string name="adblock">Block ads in browser</string>
|
||||||
|
<string name="adblock_summary">Block advertisement in the built-in browser (beta)</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
<PreferenceScreen
|
<PreferenceScreen
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:title="@string/network"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
android:title="@string/network">
|
||||||
|
|
||||||
<ListPreference
|
<ListPreference
|
||||||
android:defaultValue="0"
|
android:defaultValue="0"
|
||||||
@@ -34,6 +34,12 @@
|
|||||||
android:title="@string/dns_over_https"
|
android:title="@string/dns_over_https"
|
||||||
app:useSimpleSummaryProvider="true" />
|
app:useSimpleSummaryProvider="true" />
|
||||||
|
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
android:defaultValue="false"
|
||||||
|
android:key="adblock"
|
||||||
|
android:summary="@string/adblock_summary"
|
||||||
|
android:title="@string/adblock" />
|
||||||
|
|
||||||
<ListPreference
|
<ListPreference
|
||||||
android:defaultValue="-1"
|
android:defaultValue="-1"
|
||||||
android:entries="@array/image_proxies"
|
android:entries="@array/image_proxies"
|
||||||
|
|||||||
Reference in New Issue
Block a user