fix(de/aniking): Remove obsolete CloudflareInterceptor + refactor (#1838)

This commit is contained in:
Claudemirovsky
2023-07-05 07:57:15 +00:00
committed by GitHub
parent b018668820
commit ddb9f57eb3
3 changed files with 132 additions and 327 deletions

View File

@ -1,12 +1,14 @@
apply plugin: 'com.android.application' plugins {
apply plugin: 'kotlin-android' alias(libs.plugins.android.application)
apply plugin: 'kotlinx-serialization' alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.serialization)
}
ext { ext {
extName = 'Aniking' extName = 'Aniking'
pkgNameSuffix = 'de.aniking' pkgNameSuffix = 'de.aniking'
extClass = '.Aniking' extClass = '.Aniking'
extVersionCode = 12 extVersionCode = 13
libVersion = '13' libVersion = '13'
} }

View File

@ -17,7 +17,6 @@ import eu.kanade.tachiyomi.lib.streamsbextractor.StreamSBExtractor
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Headers
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
@ -43,77 +42,52 @@ class Aniking : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
} }
override fun popularAnimeSelector(): String = "div.item-container div.item" // ============================== Popular ===============================
override fun popularAnimeSelector() = "div.item-container div.item > a"
override fun popularAnimeRequest(page: Int): Request { override fun popularAnimeRequest(page: Int) = GET("$baseUrl/page/$page/?order=rating", headers = headers)
val interceptor = client.newBuilder().addInterceptor(CloudflareInterceptor()).build()
val headers = interceptor.newCall(GET(baseUrl)).execute().request.headers override fun popularAnimeFromElement(element: Element) = SAnime.create().apply {
return GET("$baseUrl/page/$page/?order=rating", headers = headers) setUrlWithoutDomain(element.attr("href"))
thumbnail_url = element.selectFirst("img")!!.attr("data-src")
title = element.selectFirst("h2")!!.text()
} }
override fun popularAnimeFromElement(element: Element): SAnime { override fun popularAnimeNextPageSelector() = "div.pagination i#nextpagination"
val anime = SAnime.create()
val postid = element.attr("id").replace("post-", "")
anime.setUrlWithoutDomain(element.select("a").attr("href"))
anime.thumbnail_url = element.select("a img").attr("data-src")
anime.title = element.select("a h2").text()
return anime
}
override fun popularAnimeNextPageSelector(): String = "footer"
// episodes
override fun episodeListRequest(anime: SAnime): Request {
val interceptor = client.newBuilder().addInterceptor(CloudflareInterceptor()).build()
val headers = interceptor.newCall(
GET(
"$baseUrl${anime.url}",
headers =
Headers.headersOf("user-agent", "Mozilla/5.0 (Linux; Android 12; SM-T870 Build/SP2A.220305.013; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/106.0.5249.126 Safari/537.36"),
),
)
.execute().request.headers
return GET("$baseUrl${anime.url}", headers = headers)
}
// ============================== Episodes ==============================
override fun episodeListSelector() = throw Exception("not used") override fun episodeListSelector() = throw Exception("not used")
override fun episodeListParse(response: Response): List<SEpisode> { override fun episodeListParse(response: Response): List<SEpisode> {
val document = response.asJsoup() val document = response.use { it.asJsoup() }
val episodeList = mutableListOf<SEpisode>() return if (document.selectFirst("#movie-js-extra") == null) {
if (document.select("#movie-js-extra").isNullOrEmpty()) { val episodeElement = document.selectFirst("script[id=\"tv-js-after\"]")!!
val episodeElement = document.select("script[id=\"tv-js-after\"]") episodeElement.data()
val episodeString = episodeElement.toString() .substringAfter("var streaming = {")
.substringAfter("var streaming = {").substringBefore("}; var").split(",") .substringBefore("}; var")
episodeString.forEach { .split(",")
val episode = episodeFromString(it) .map(::episodeFromString)
episodeList.add(episode) .reversed()
}
} else { } else {
val episode = SEpisode.create() SEpisode.create().apply {
episode.name = document.select("h1.entry-title").text() name = document.selectFirst("h1.entry-title")!!.text()
episode.episode_number = 1F episode_number = 1F
episode.setUrlWithoutDomain(document.select("meta[property=\"og:url\"]").attr("content")) setUrlWithoutDomain(document.location())
episodeList.add(episode) }.let(::listOf)
} }
return episodeList.reversed()
} }
override fun episodeFromElement(element: Element): SEpisode = throw Exception("not Used") override fun episodeFromElement(element: Element): SEpisode = throw Exception("not Used")
private fun episodeFromString(string: String): SEpisode { private fun episodeFromString(string: String) = SEpisode.create().apply {
val episode = SEpisode.create()
val season = string.substringAfter("\"s").substringBefore("_") val season = string.substringAfter("\"s").substringBefore("_")
val ep = string.substringAfter("_").substringBefore("\":") val ep = string.substringAfter("_").substringBefore("\":")
episode.episode_number = ep.toFloat() episode_number = ep.toFloatOrNull() ?: 1F
episode.name = "Staffel $season Folge $ep" name = "Staffel $season Folge $ep"
episode.url = (string.replace("\\", "").replace("\"", "").replace("s${season}_$ep:", "")) url = string.substringAfter(":\"").substringBefore('"').replace("\\", "")
return episode
} }
// Video Extractor // ============================ Video Links =============================
override fun videoListRequest(episode: SEpisode): Request { override fun videoListRequest(episode: SEpisode): Request {
if (!episode.url.contains("https://")) { if (!episode.url.contains("https://")) {
return GET("$baseUrl${episode.url}") return GET("$baseUrl${episode.url}")
@ -124,127 +98,59 @@ class Aniking : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun videoListParse(response: Response): List<Video> { override fun videoListParse(response: Response): List<Video> {
val url = response.request.url.toString() val url = response.request.url.toString()
return videosFromElement(url) val hosterSelection = preferences.getStringSet(PREF_SELECTION_KEY, PREF_SELECTION_DEFAULT)!!
return if (!url.contains(baseUrl)) {
videoListFromUrl(url, hosterSelection)
} else {
val document = response.asJsoup()
document.select("div.multi a").flatMap {
videoListFromUrl(it.attr("href"), hosterSelection)
}
}
} }
private fun videosFromElement(url: String): List<Video> { private fun videoListFromUrl(url: String, hosterSelection: Set<String>): List<Video> {
val videoList = mutableListOf<Video>() return runCatching {
val hosterSelection = preferences.getStringSet("hoster_selection", setOf("dood", "stape", "streamz", "streamsb"))
if (!url.contains(baseUrl)) {
when { when {
url.contains("https://dood") && hosterSelection?.contains("dood") == true -> { "https://dood" in url && "dood" in hosterSelection -> {
val quality = "Doodstream" DoodExtractor(client)
val video = try { .videoFromUrl(url, "Doodstream", redirect = false)
DoodExtractor(client).videoFromUrl(url, quality, redirect = false) ?.let(::listOf)
} catch (e: Exception) {
null
}
if (video != null) {
videoList.add(video)
}
} }
url.contains("https://streamtape") && hosterSelection?.contains("stape") == true -> { "https://streamtape" in url && "stape" in hosterSelection -> {
val quality = "Streamtape" StreamTapeExtractor(client).videoFromUrl(url, "Streamtape")
val video = StreamTapeExtractor(client).videoFromUrl(url, quality) ?.let(::listOf)
if (video != null) {
videoList.add(video)
}
} }
url.contains("https://streamz") && hosterSelection?.contains("streamz") == true -> { ("https://streamz" in url || "https://streamcrypt.net" in url) && "streamz" in hosterSelection -> {
val quality = "StreamZ" val realUrl = when {
val video = StreamZExtractor(client).videoFromUrl(url, quality) "https://streamcrypt.net" in url -> {
if (video != null) { client.newCall(GET(url, headers)).execute().use {
videoList.add(video) it.request.url.toString()
} }
} }
else -> url
}
url.contains("https://viewsb.com") || url.contains("https://watchsb.com") && hosterSelection?.contains("streamsb") == true -> { StreamZExtractor(client).videoFromUrl(realUrl, "StreamZ")
val video = try { ?.let(::listOf)
}
("https://viewsb.com" in url || "https://watchsb.com" in url) && "streamsb" in hosterSelection -> {
StreamSBExtractor(client).videosFromUrl(url, headers) StreamSBExtractor(client).videosFromUrl(url, headers)
} catch (e: Exception) {
null
}
if (video != null) {
videoList.addAll(video)
}
}
}
} else {
val document = client.newCall(GET(url)).execute().asJsoup()
val elements = document.select("div.multi a")
elements.forEach {
when {
it.attr("href").contains("https://dood") && hosterSelection?.contains("dood") == true -> {
val quality = "Doodstream"
val video = try {
DoodExtractor(client).videoFromUrl(it.attr("href"), quality, redirect = false)
} catch (e: Exception) {
null
}
if (video != null) {
videoList.add(video)
}
} }
it.attr("href").contains("https://streamtape") && hosterSelection?.contains("stape") == true -> { else -> null
val quality = "Streamtape"
val video = StreamTapeExtractor(client).videoFromUrl(it.attr("href"), quality)
if (video != null) {
videoList.add(video)
} }
} }.getOrNull() ?: emptyList()
it.attr("href").contains("https://streamz") || it.attr("href").contains("https://streamcrypt.net") && hosterSelection?.contains("streamz") == true -> {
if (it.attr("href").contains("https://streamcrypt.net")) {
val zurl = client.newCall(GET(it.attr("href"))).execute().request.url.toString()
val quality = "StreamZ"
val video = StreamZExtractor(client).videoFromUrl(zurl, quality)
if (video != null) {
videoList.add(video)
}
} else {
val quality = "StreamZ"
val video = StreamZExtractor(client).videoFromUrl(it.attr("href"), quality)
if (video != null) {
videoList.add(video)
}
}
}
it.attr("href").contains("https://viewsb.com") || url.contains("https://watchsb.com") && hosterSelection?.contains("streamsb") == true -> {
val video = try {
StreamSBExtractor(client).videosFromUrl(it.attr("href"), headers)
} catch (e: Exception) {
null
}
if (video != null) {
videoList.addAll(video)
}
}
}
}
}
return videoList.reversed()
} }
override fun List<Video>.sort(): List<Video> { override fun List<Video>.sort(): List<Video> {
val hoster = preferences.getString("preferred_hoster", null) val quality = preferences.getString(PREF_HOSTER_KEY, PREF_HOSTER_DEFAULT)!!
if (hoster != null) { return sortedWith(
val newList = mutableListOf<Video>() compareBy { it.quality.contains(quality) },
var preferred = 0 ).reversed()
for (video in this) {
if (video.quality.contains(hoster)) {
newList.add(preferred, video)
preferred++
} else {
newList.add(video)
}
}
return newList
}
return this
} }
override fun videoListSelector() = throw Exception("not used") override fun videoListSelector() = throw Exception("not used")
@ -253,63 +159,34 @@ class Aniking : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun videoUrlParse(document: Document) = throw Exception("not used") override fun videoUrlParse(document: Document) = throw Exception("not used")
// Search // =============================== Search ===============================
// TODO: Implement search filters
override fun searchAnimeFromElement(element: Element): SAnime { override fun searchAnimeFromElement(element: Element) = popularAnimeFromElement(element)
val anime = SAnime.create() override fun searchAnimeNextPageSelector() = popularAnimeNextPageSelector()
anime.setUrlWithoutDomain(element.select("a").attr("href")) override fun searchAnimeSelector() = popularAnimeSelector()
anime.thumbnail_url = element.select("a img").attr("data-src")
anime.title = element.select("a h2").text()
return anime
}
override fun searchAnimeNextPageSelector(): String = "footer"
override fun searchAnimeSelector(): String = "div.item-container div.item"
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request { override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
if (client.newCall(GET("$baseUrl/page/$page/?s=$query")).execute().code == 404) { return GET("$baseUrl/page/$page/?s=$query", headers)
throw Exception("Ignorieren")
} else {
return GET("$baseUrl/page/$page/?s=$query")
}
} }
// Details // =========================== Anime Details ============================
override fun animeDetailsParse(document: Document) = SAnime.create().apply {
override fun animeDetailsRequest(anime: SAnime): Request { thumbnail_url = document.selectFirst("div.tv-poster img")!!.attr("src")
val interceptor = client.newBuilder().addInterceptor(CloudflareInterceptor()).build() title = document.selectFirst("h1.entry-title")!!.text()
val headers = interceptor.newCall( genre = document.select("span[itemprop=genre] a").eachText().joinToString()
GET( description = document.selectFirst("p.movie-description > span")!!
"$baseUrl${anime.url}", .ownText()
headers = .substringBefore("Tags:")
Headers.headersOf("user-agent", "Mozilla/5.0 (Linux; Android 12; SM-T870 Build/SP2A.220305.013; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/106.0.5249.126 Safari/537.36"), author = document.select("div.name a").eachText().joinToString().takeIf(String::isNotBlank)
), status = parseStatus(document.selectFirst("span.stato")?.text())
)
.execute().request.headers
return GET("$baseUrl${anime.url}", headers = headers)
} }
override fun animeDetailsParse(document: Document): SAnime { private fun parseStatus(status: String?) = when (status) {
val anime = SAnime.create() "Returning Series" -> SAnime.ONGOING
anime.thumbnail_url = document.select("div.tv-poster img").attr("src")
anime.title = document.select("h1.entry-title").text()
anime.genre = document.select("span[itemprop=genre] a").joinToString(", ") { it.text() }
anime.description = document.select("p.movie-description").toString()
.substringAfter("trama\">").substringBefore("<br>")
anime.author = document.select("div.name a").joinToString(", ") { it.text() }
anime.status = parseStatus(document.select("span.stato").text())
return anime
}
private fun parseStatus(status: String?) = when {
status == null -> SAnime.UNKNOWN
status.contains("Returning Series", ignoreCase = true) -> SAnime.ONGOING
else -> SAnime.COMPLETED else -> SAnime.COMPLETED
} }
// Latest // =============================== Latest ===============================
override fun latestUpdatesNextPageSelector(): String = throw Exception("Not used") override fun latestUpdatesNextPageSelector(): String = throw Exception("Not used")
override fun latestUpdatesFromElement(element: Element): SAnime = throw Exception("Not used") override fun latestUpdatesFromElement(element: Element): SAnime = throw Exception("Not used")
@ -318,15 +195,14 @@ class Aniking : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun latestUpdatesSelector(): String = throw Exception("Not used") override fun latestUpdatesSelector(): String = throw Exception("Not used")
// Preferences // ============================== Settings ==============================
override fun setupPreferenceScreen(screen: PreferenceScreen) { override fun setupPreferenceScreen(screen: PreferenceScreen) {
val hosterPref = ListPreference(screen.context).apply { ListPreference(screen.context).apply {
key = "preferred_hoster" key = PREF_HOSTER_KEY
title = "Standard-Hoster" title = PREF_HOSTER_TITLE
entries = arrayOf("Streamtape", "Doodstream", "StreamZ", "StreamSB") entries = PREF_HOSTER_ENTRIES
entryValues = arrayOf("https://streamz.ws", "https://dood", "https://voe.sx", "https://viewsb.com") entryValues = PREF_HOSTER_VALUES
setDefaultValue("https://streamtape.com") setDefaultValue(PREF_HOSTER_DEFAULT)
summary = "%s" summary = "%s"
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
@ -335,19 +211,33 @@ class Aniking : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
val entry = entryValues[index] as String val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit() preferences.edit().putString(key, entry).commit()
} }
} }.also(screen::addPreference)
val subSelection = MultiSelectListPreference(screen.context).apply {
key = "hoster_selection" MultiSelectListPreference(screen.context).apply {
title = "Hoster auswählen" key = PREF_SELECTION_KEY
entries = arrayOf("Streamtape", "Doodstream", "StreamZ", "StreamSB") title = PREF_SELECTION_TITLE
entryValues = arrayOf("stape", "dood", "streamz", "streamsb") entries = PREF_SELECTION_ENTRIES
setDefaultValue(setOf("stape", "dood", "streamz", "streamsb")) entryValues = PREF_SELECTION_VALUES
setDefaultValue(PREF_SELECTION_DEFAULT)
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
@Suppress("UNCHECKED_CAST")
preferences.edit().putStringSet(key, newValue as Set<String>).commit() preferences.edit().putStringSet(key, newValue as Set<String>).commit()
} }
}.also(screen::addPreference)
} }
screen.addPreference(hosterPref)
screen.addPreference(subSelection) companion object {
private const val PREF_HOSTER_KEY = "preferred_hoster"
private const val PREF_HOSTER_TITLE = "Standard-Hoster"
private const val PREF_HOSTER_DEFAULT = "https://streamtape.com"
private val PREF_HOSTER_ENTRIES = arrayOf("Streamtape", "Doodstream", "StreamZ", "StreamSB")
private val PREF_HOSTER_VALUES = arrayOf("https://streamz.ws", "https://dood", "https://voe.sx", "https://viewsb.com")
private const val PREF_SELECTION_KEY = "hoster_selection"
private const val PREF_SELECTION_TITLE = "Hoster auswählen"
private val PREF_SELECTION_ENTRIES = PREF_HOSTER_ENTRIES
private val PREF_SELECTION_VALUES = arrayOf("stape", "dood", "streamz", "streamsb")
private val PREF_SELECTION_DEFAULT = PREF_SELECTION_VALUES.toSet()
} }
} }

View File

@ -1,87 +0,0 @@
package eu.kanade.tachiyomi.animeextension.de.aniking
import android.annotation.SuppressLint
import android.app.Application
import android.os.Handler
import android.os.Looper
import android.webkit.WebResourceRequest
import android.webkit.WebResourceResponse
import android.webkit.WebView
import android.webkit.WebViewClient
import eu.kanade.tachiyomi.network.GET
import okhttp3.Headers.Companion.toHeaders
import okhttp3.Interceptor
import okhttp3.Request
import okhttp3.Response
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
class CloudflareInterceptor() : Interceptor {
private val context = Injekt.get<Application>()
private val handler by lazy { Handler(Looper.getMainLooper()) }
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
val newRequest = resolveWithWebView(originalRequest) ?: throw Exception("bruh")
return chain.proceed(newRequest)
}
@SuppressLint("SetJavaScriptEnabled")
private fun resolveWithWebView(request: Request): Request? {
// We need to lock this thread until the WebView finds the challenge solution url, because
// OkHttp doesn't support asynchronous interceptors.
val latch = CountDownLatch(1)
var webView: WebView? = null
val origRequestUrl = request.url.toString()
val headers = request.headers.toMultimap().mapValues { it.value.getOrNull(0) ?: "" }.toMutableMap()
var newRequest: Request? = null
handler.post {
val webview = WebView(context)
webView = webview
with(webview.settings) {
javaScriptEnabled = true
domStorageEnabled = true
databaseEnabled = true
useWideViewPort = false
loadWithOverviewMode = false
userAgentString = "Mozilla/5.0 (Linux; Android 12; SM-T870 Build/SP2A.220305.013; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/106.0.5249.126 Safari/537.36"
}
webview.webViewClient = object : WebViewClient() {
override fun shouldInterceptRequest(
view: WebView,
request: WebResourceRequest,
): WebResourceResponse? {
if (request.url.toString().contains("wp-content/themes/moviewp")) {
newRequest = GET(request.url.toString(), request.requestHeaders.toHeaders())
latch.countDown()
}
return super.shouldInterceptRequest(view, request)
}
}
webView?.loadUrl(origRequestUrl, headers)
}
// Wait a reasonable amount of time to retrieve the solution. The minimum should be
// around 4 seconds but it can take more due to slow networks or server issues.
latch.await(12, TimeUnit.SECONDS)
handler.post {
webView?.stopLoading()
webView?.destroy()
webView = null
}
return newRequest
}
}