Show notification if captcha required in background
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
package org.koitharu.kotatsu.browser.cloudflare
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationChannelCompat
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.app.PendingIntentCompat
|
||||
import androidx.core.net.toUri
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
|
||||
import org.koitharu.kotatsu.parsers.model.ContentType
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class CaptchaNotifier @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
) {
|
||||
|
||||
private val mutex = Mutex()
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
suspend fun notify(exception: CloudFlareProtectedException) = mutex.withLock {
|
||||
val manager = NotificationManagerCompat.from(context)
|
||||
if (!manager.areNotificationsEnabled()) {
|
||||
return@withLock
|
||||
}
|
||||
val channel = NotificationChannelCompat.Builder(CHANNEL_ID, NotificationManagerCompat.IMPORTANCE_DEFAULT)
|
||||
.setName(context.getString(R.string.captcha_required))
|
||||
.setShowBadge(true)
|
||||
.setVibrationEnabled(false)
|
||||
.setSound(null, null)
|
||||
.setLightsEnabled(false)
|
||||
.build()
|
||||
manager.createNotificationChannel(channel)
|
||||
|
||||
val intent = CloudFlareActivity.newIntent(context, exception.url, exception.headers)
|
||||
.setData(exception.url.toUri())
|
||||
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
|
||||
.setContentTitle(channel.name)
|
||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||
.setDefaults(NotificationCompat.DEFAULT_SOUND)
|
||||
.setSmallIcon(android.R.drawable.stat_notify_error)
|
||||
.setVisibility(
|
||||
if (exception.source?.contentType == ContentType.HENTAI) {
|
||||
NotificationCompat.VISIBILITY_SECRET
|
||||
} else {
|
||||
NotificationCompat.VISIBILITY_PUBLIC
|
||||
},
|
||||
)
|
||||
.setContentText(
|
||||
context.getString(
|
||||
R.string.captcha_required_summary,
|
||||
exception.source?.title ?: context.getString(R.string.app_name),
|
||||
),
|
||||
)
|
||||
.setContentIntent(PendingIntentCompat.getActivity(context, 0, intent, 0, false))
|
||||
.build()
|
||||
manager.notify(TAG, exception.source.hashCode(), notification)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
||||
private const val CHANNEL_ID = "captcha"
|
||||
private const val TAG = CHANNEL_ID
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,10 @@ package org.koitharu.kotatsu.core.exceptions
|
||||
|
||||
import okhttp3.Headers
|
||||
import okio.IOException
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
|
||||
class CloudFlareProtectedException(
|
||||
val url: String,
|
||||
val source: MangaSource?,
|
||||
@Transient val headers: Headers,
|
||||
) : IOException("Protected by CloudFlare")
|
||||
|
||||
@@ -4,6 +4,7 @@ import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
import okhttp3.internal.closeQuietly
|
||||
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import java.net.HttpURLConnection.HTTP_FORBIDDEN
|
||||
import java.net.HttpURLConnection.HTTP_UNAVAILABLE
|
||||
|
||||
@@ -20,6 +21,7 @@ class CloudFlareInterceptor : Interceptor {
|
||||
response.closeQuietly()
|
||||
throw CloudFlareProtectedException(
|
||||
url = request.url.toString(),
|
||||
source = request.tag(MangaSource::class.java),
|
||||
headers = request.headers,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -38,6 +38,8 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Semaphore
|
||||
import kotlinx.coroutines.sync.withPermit
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.browser.cloudflare.CaptchaNotifier
|
||||
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
|
||||
import org.koitharu.kotatsu.core.model.distinctById
|
||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
@@ -80,6 +82,7 @@ class SuggestionsWorker @AssistedInject constructor(
|
||||
private val appSettings: AppSettings,
|
||||
private val mangaRepositoryFactory: MangaRepository.Factory,
|
||||
private val sourcesRepository: MangaSourcesRepository,
|
||||
private val captchaNotifier: CaptchaNotifier,
|
||||
) : CoroutineWorker(appContext, params) {
|
||||
|
||||
private val notificationManager by lazy { NotificationManagerCompat.from(appContext) }
|
||||
@@ -206,11 +209,17 @@ class SuggestionsWorker @AssistedInject constructor(
|
||||
}
|
||||
list.shuffle()
|
||||
list.take(MAX_SOURCE_RESULTS)
|
||||
}.onFailure {
|
||||
it.printStackTraceDebug()
|
||||
}.onFailure { e ->
|
||||
if (e is CloudFlareProtectedException) {
|
||||
captchaNotifier.notify(e)
|
||||
}
|
||||
e.printStackTraceDebug()
|
||||
}.getOrDefault(emptyList())
|
||||
|
||||
private suspend fun showNotification(manga: Manga) {
|
||||
if (!notificationManager.areNotificationsEnabled()) {
|
||||
return
|
||||
}
|
||||
val channel = NotificationChannelCompat.Builder(MANGA_CHANNEL_ID, NotificationManagerCompat.IMPORTANCE_DEFAULT)
|
||||
.setName(applicationContext.getString(R.string.suggestions))
|
||||
.setDescription(applicationContext.getString(R.string.suggestions_summary))
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.koitharu.kotatsu.tracker.work
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
@@ -42,6 +43,8 @@ import kotlinx.coroutines.sync.Semaphore
|
||||
import kotlinx.coroutines.sync.withPermit
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.browser.cloudflare.CaptchaNotifier
|
||||
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
|
||||
import org.koitharu.kotatsu.core.logs.FileLogger
|
||||
import org.koitharu.kotatsu.core.logs.TrackerLogger
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
@@ -66,6 +69,7 @@ class TrackWorker @AssistedInject constructor(
|
||||
private val settings: AppSettings,
|
||||
private val tracker: Tracker,
|
||||
@TrackerLogger private val logger: FileLogger,
|
||||
private val captchaNotifier: CaptchaNotifier,
|
||||
) : CoroutineWorker(context, workerParams) {
|
||||
private val notificationManager by lazy { NotificationManagerCompat.from(applicationContext) }
|
||||
|
||||
@@ -124,8 +128,11 @@ class TrackWorker @AssistedInject constructor(
|
||||
semaphore.withPermit {
|
||||
runCatchingCancellable {
|
||||
tracker.fetchUpdates(track, commit = true)
|
||||
}.onFailure {
|
||||
logger.log("checkUpdatesAsync", it)
|
||||
}.onFailure { e ->
|
||||
if (e is CloudFlareProtectedException) {
|
||||
captchaNotifier.notify(e)
|
||||
}
|
||||
logger.log("checkUpdatesAsync", e)
|
||||
}.onSuccess { updates ->
|
||||
if (updates.isValid && updates.isNotEmpty()) {
|
||||
showNotification(
|
||||
@@ -141,8 +148,9 @@ class TrackWorker @AssistedInject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
private suspend fun showNotification(manga: Manga, channelId: String?, newChapters: List<MangaChapter>) {
|
||||
if (newChapters.isEmpty() || channelId == null) {
|
||||
if (newChapters.isEmpty() || channelId == null || !notificationManager.areNotificationsEnabled()) {
|
||||
return
|
||||
}
|
||||
val id = manga.url.hashCode()
|
||||
|
||||
@@ -468,4 +468,5 @@
|
||||
<string name="order_added">Added</string>
|
||||
<string name="view_list">View list</string>
|
||||
<string name="show">Show</string>
|
||||
<string name="captcha_required_summary">%s requires a captcha to be resolved to work properly</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user