feat(de/cinemathek): Add more video extractors (#2469)
This commit is contained in:
@ -2,5 +2,6 @@ dependencies {
|
|||||||
implementation(project(':lib-filemoon-extractor'))
|
implementation(project(':lib-filemoon-extractor'))
|
||||||
implementation(project(':lib-dood-extractor'))
|
implementation(project(':lib-dood-extractor'))
|
||||||
implementation(project(':lib-streamlare-extractor'))
|
implementation(project(':lib-streamlare-extractor'))
|
||||||
implementation("dev.datlag.jsunpacker:jsunpacker:1.0.1")
|
implementation(project(':lib-streamtape-extractor'))
|
||||||
|
implementation(project(':lib-streamwish-extractor'))
|
||||||
}
|
}
|
||||||
|
@ -3,19 +3,22 @@ package eu.kanade.tachiyomi.animeextension.de.cinemathek
|
|||||||
import androidx.preference.ListPreference
|
import androidx.preference.ListPreference
|
||||||
import androidx.preference.MultiSelectListPreference
|
import androidx.preference.MultiSelectListPreference
|
||||||
import androidx.preference.PreferenceScreen
|
import androidx.preference.PreferenceScreen
|
||||||
import eu.kanade.tachiyomi.animeextension.de.cinemathek.extractors.StreamHideExtractor
|
|
||||||
import eu.kanade.tachiyomi.animesource.model.Video
|
import eu.kanade.tachiyomi.animesource.model.Video
|
||||||
import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor
|
import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor
|
||||||
import eu.kanade.tachiyomi.lib.filemoonextractor.FilemoonExtractor
|
import eu.kanade.tachiyomi.lib.filemoonextractor.FilemoonExtractor
|
||||||
import eu.kanade.tachiyomi.lib.streamlareextractor.StreamlareExtractor
|
import eu.kanade.tachiyomi.lib.streamlareextractor.StreamlareExtractor
|
||||||
|
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
|
||||||
|
import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor
|
||||||
import eu.kanade.tachiyomi.multisrc.dooplay.DooPlay
|
import eu.kanade.tachiyomi.multisrc.dooplay.DooPlay
|
||||||
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.Request
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.awaitAll
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import org.jsoup.nodes.Document
|
import org.jsoup.nodes.Document
|
||||||
import org.jsoup.nodes.Element
|
import org.jsoup.nodes.Element
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
|
|
||||||
class Cinemathek : DooPlay(
|
class Cinemathek : DooPlay(
|
||||||
"de",
|
"de",
|
||||||
@ -23,15 +26,15 @@ class Cinemathek : DooPlay(
|
|||||||
"https://cinemathek.net",
|
"https://cinemathek.net",
|
||||||
) {
|
) {
|
||||||
// ============================== Popular ===============================
|
// ============================== Popular ===============================
|
||||||
override fun popularAnimeSelector(): String = "article.movies div.poster"
|
override fun popularAnimeSelector() = "article.movies div.poster"
|
||||||
|
|
||||||
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/filme/page/$page/")
|
override fun popularAnimeRequest(page: Int) = GET("$baseUrl/filme/page/$page/")
|
||||||
|
|
||||||
override fun popularAnimeNextPageSelector() = latestUpdatesNextPageSelector()
|
override fun popularAnimeNextPageSelector() = latestUpdatesNextPageSelector()
|
||||||
|
|
||||||
// =============================== Latest ===============================
|
// =============================== Latest ===============================
|
||||||
override fun latestUpdatesNextPageSelector(): String = "#nextpagination"
|
override fun latestUpdatesNextPageSelector() = "#nextpagination"
|
||||||
override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/episoden/page/$page")
|
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/episoden/page/$page")
|
||||||
|
|
||||||
// =========================== Anime Details ============================
|
// =========================== Anime Details ============================
|
||||||
override val additionalInfoItems = listOf("Original", "Start", "Staffeln", "letzte", "Episoden")
|
override val additionalInfoItems = listOf("Original", "Start", "Staffeln", "letzte", "Episoden")
|
||||||
@ -46,9 +49,9 @@ class Cinemathek : DooPlay(
|
|||||||
override fun videoListParse(response: Response): List<Video> {
|
override fun videoListParse(response: Response): List<Video> {
|
||||||
val players = response.use { it.asJsoup().select("ul#playeroptionsul li") }
|
val players = response.use { it.asJsoup().select("ul#playeroptionsul li") }
|
||||||
val hosterSelection = preferences.getStringSet(PREF_HOSTER_SELECTION_KEY, PREF_HOSTER_SELECTION_DEFAULT)!!
|
val hosterSelection = preferences.getStringSet(PREF_HOSTER_SELECTION_KEY, PREF_HOSTER_SELECTION_DEFAULT)!!
|
||||||
return players.mapNotNull { player ->
|
return players.parallelMapNotNull { player ->
|
||||||
runCatching {
|
runCatching {
|
||||||
val url = getPlayerUrl(player).ifEmpty { return@mapNotNull null }
|
val url = getPlayerUrl(player).ifEmpty { return@parallelMapNotNull null }
|
||||||
getPlayerVideos(url, hosterSelection)
|
getPlayerVideos(url, hosterSelection)
|
||||||
}.getOrNull()
|
}.getOrNull()
|
||||||
}.flatten()
|
}.flatten()
|
||||||
@ -61,26 +64,35 @@ class Cinemathek : DooPlay(
|
|||||||
if (num == "trailer") return ""
|
if (num == "trailer") return ""
|
||||||
return client.newCall(GET("$baseUrl/wp-json/dooplayer/v2/$id/$type/$num"))
|
return client.newCall(GET("$baseUrl/wp-json/dooplayer/v2/$id/$type/$num"))
|
||||||
.execute()
|
.execute()
|
||||||
.body.string()
|
.use { it.body.string() }
|
||||||
.substringAfter("\"embed_url\":\"")
|
.substringAfter("\"embed_url\":\"")
|
||||||
.substringBefore("\",")
|
.substringBefore("\",")
|
||||||
.replace("\\", "")
|
.replace("\\", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val streamlareExtractor by lazy { StreamlareExtractor(client) }
|
||||||
|
private val filemoonExtractor by lazy { FilemoonExtractor(client) }
|
||||||
|
private val doodExtractor by lazy { DoodExtractor(client) }
|
||||||
|
private val streamtapeExtractor by lazy { StreamTapeExtractor(client) }
|
||||||
|
private val streamwishExtractor by lazy { StreamWishExtractor(client, headers) }
|
||||||
|
|
||||||
private fun getPlayerVideos(url: String, hosterSelection: Set<String>): List<Video>? {
|
private fun getPlayerVideos(url: String, hosterSelection: Set<String>): List<Video>? {
|
||||||
return when {
|
return when {
|
||||||
url.contains("https://streamlare.com") && hosterSelection.contains("slare") -> {
|
url.contains("https://streamlare.com") && hosterSelection.contains("slare") -> {
|
||||||
StreamlareExtractor(client).videosFromUrl(url)
|
streamlareExtractor.videosFromUrl(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
url.contains("https://filemoon") && hosterSelection.contains("fmoon") -> {
|
url.contains("https://filemoon") && hosterSelection.contains("fmoon") -> {
|
||||||
FilemoonExtractor(client).videosFromUrl(url)
|
filemoonExtractor.videosFromUrl(url)
|
||||||
}
|
}
|
||||||
url.contains("https://dooood") && hosterSelection.contains("dood") -> {
|
(url.contains("ds2play") || url.contains("https://doo")) && hosterSelection.contains("dood") -> {
|
||||||
DoodExtractor(client).videosFromUrl(url)
|
doodExtractor.videosFromUrl(url)
|
||||||
}
|
}
|
||||||
url.contains("https://streamhide") && hosterSelection.contains("shide") -> {
|
url.contains("streamtape") && hosterSelection.contains("stape") -> {
|
||||||
StreamHideExtractor(client).videosFromUrl(url)
|
streamtapeExtractor.videosFromUrl(url)
|
||||||
|
}
|
||||||
|
(url.contains("filelions") || url.contains("streamwish")) && hosterSelection.contains("swish") -> {
|
||||||
|
streamwishExtractor.videosFromUrl(url)
|
||||||
}
|
}
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
@ -151,17 +163,22 @@ class Cinemathek : DooPlay(
|
|||||||
).reversed()
|
).reversed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private inline fun <A, B> Iterable<A>.parallelMapNotNull(crossinline f: suspend (A) -> B?): List<B> =
|
||||||
|
runBlocking {
|
||||||
|
map { async(Dispatchers.Default) { f(it) } }.awaitAll().filterNotNull()
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val PREF_HOSTER_KEY = "preferred_hoster"
|
private const val PREF_HOSTER_KEY = "preferred_hoster"
|
||||||
private const val PREF_HOSTER_TITLE = "Standard-Hoster"
|
private const val PREF_HOSTER_TITLE = "Standard-Hoster"
|
||||||
private const val PREF_HOSTER_DEFAULT = "https://viewsb.com"
|
private const val PREF_HOSTER_DEFAULT = "https://filemoon"
|
||||||
private val PREF_HOSTER_ENTRIES = arrayOf("Streamlare", "Filemoon", "DoodStream", "StreamHide")
|
private val PREF_HOSTER_ENTRIES = arrayOf("Streamlare", "Filemoon", "DoodStream", "StreamTape", "StreamWish/Filelions")
|
||||||
private val PREF_HOSTER_VALUES = arrayOf("https://streamlare", "https://viewsb.com", "https://filemoon", "https://dooood", "https://streamhide")
|
private val PREF_HOSTER_VALUES = arrayOf("https://streamlare", "https://filemoon", "https://doo", "https://streamtape", "https://streamwish")
|
||||||
|
|
||||||
private const val PREF_HOSTER_SELECTION_KEY = "hoster_selection"
|
private const val PREF_HOSTER_SELECTION_KEY = "hoster_selection"
|
||||||
private const val PREF_HOSTER_SELECTION_TITLE = "Hoster auswählen"
|
private const val PREF_HOSTER_SELECTION_TITLE = "Hoster auswählen"
|
||||||
private val PREF_HOSTER_SELECTION_ENTRIES = PREF_HOSTER_ENTRIES
|
private val PREF_HOSTER_SELECTION_ENTRIES = PREF_HOSTER_ENTRIES
|
||||||
private val PREF_HOSTER_SELECTION_VALUES = arrayOf("slare", "fmoon", "dood", "shide")
|
private val PREF_HOSTER_SELECTION_VALUES = arrayOf("slare", "fmoon", "dood", "stape", "swish")
|
||||||
private val PREF_HOSTER_SELECTION_DEFAULT = PREF_HOSTER_SELECTION_VALUES.toSet()
|
private val PREF_HOSTER_SELECTION_DEFAULT = PREF_HOSTER_SELECTION_VALUES.toSet()
|
||||||
|
|
||||||
private const val PREF_QUALITY_KEY = "preferred_quality"
|
private const val PREF_QUALITY_KEY = "preferred_quality"
|
||||||
|
@ -1,45 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.animeextension.de.cinemathek.extractors
|
|
||||||
|
|
||||||
import dev.datlag.jsunpacker.JsUnpacker
|
|
||||||
import eu.kanade.tachiyomi.animesource.model.Track
|
|
||||||
import eu.kanade.tachiyomi.animesource.model.Video
|
|
||||||
import eu.kanade.tachiyomi.network.GET
|
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
|
|
||||||
class StreamHideExtractor(private val client: OkHttpClient) {
|
|
||||||
// from nineanime / ask4movie FilemoonExtractor
|
|
||||||
private val subtitleRegex = Regex("""#EXT-X-MEDIA:TYPE=SUBTITLES.*?NAME="(.*?)".*?URI="(.*?)"""")
|
|
||||||
|
|
||||||
fun videosFromUrl(url: String): List<Video> {
|
|
||||||
val page = client.newCall(GET(url)).execute().body.string()
|
|
||||||
val unpacked = JsUnpacker.unpackAndCombine(page) ?: return emptyList()
|
|
||||||
val playlistUrl = unpacked.substringAfter("sources:")
|
|
||||||
.substringAfter("file:\"")
|
|
||||||
.substringBefore('"')
|
|
||||||
|
|
||||||
val playlistData = client.newCall(GET(playlistUrl)).execute().body.string()
|
|
||||||
|
|
||||||
val subs = subtitleRegex.findAll(playlistData).map {
|
|
||||||
val subUrl = fixUrl(it.groupValues[2], playlistUrl)
|
|
||||||
Track(subUrl, it.groupValues[1])
|
|
||||||
}.toList()
|
|
||||||
|
|
||||||
val separator = "#EXT-X-STREAM-INF"
|
|
||||||
return playlistData.substringAfter(separator).split(separator).map {
|
|
||||||
val resolution = it.substringAfter("RESOLUTION=")
|
|
||||||
.substringBefore("\n")
|
|
||||||
.substringAfter("x")
|
|
||||||
.substringBefore(",") + "p"
|
|
||||||
|
|
||||||
val urlPart = it.substringAfter("\n").substringBefore("\n")
|
|
||||||
val videoUrl = fixUrl(urlPart, playlistUrl)
|
|
||||||
Video(videoUrl, "StreamHide:$resolution", videoUrl, subtitleTracks = subs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun fixUrl(urlPart: String, playlistUrl: String) =
|
|
||||||
when {
|
|
||||||
!urlPart.startsWith("https:") -> playlistUrl.substringBeforeLast("/") + "/$urlPart"
|
|
||||||
else -> urlPart
|
|
||||||
}
|
|
||||||
}
|
|
@ -18,7 +18,7 @@ class DooPlayGenerator : ThemeSourceGenerator {
|
|||||||
SingleLang("AnimeSAGA", "https://www.animesaga.in", "hi", isNsfw = false, overrideVersionCode = 7),
|
SingleLang("AnimeSAGA", "https://www.animesaga.in", "hi", isNsfw = false, overrideVersionCode = 7),
|
||||||
SingleLang("AnimesFox BR", "https://animesfox.net", "pt-BR", isNsfw = false, overrideVersionCode = 2),
|
SingleLang("AnimesFox BR", "https://animesfox.net", "pt-BR", isNsfw = false, overrideVersionCode = 2),
|
||||||
SingleLang("Animes House", "https://animeshouse.net", "pt-BR", isNsfw = false, overrideVersionCode = 7),
|
SingleLang("Animes House", "https://animeshouse.net", "pt-BR", isNsfw = false, overrideVersionCode = 7),
|
||||||
SingleLang("Cinemathek", "https://cinemathek.net", "de", isNsfw = true, overrideVersionCode = 16),
|
SingleLang("Cinemathek", "https://cinemathek.net", "de", isNsfw = true, overrideVersionCode = 17),
|
||||||
SingleLang("DonghuaX", "https://donghuax.com", "pt-BR", isNsfw = false, overrideVersionCode = 1),
|
SingleLang("DonghuaX", "https://donghuax.com", "pt-BR", isNsfw = false, overrideVersionCode = 1),
|
||||||
SingleLang("GoAnimes", "https://goanimes.net", "pt-BR", isNsfw = true, overrideVersionCode = 5),
|
SingleLang("GoAnimes", "https://goanimes.net", "pt-BR", isNsfw = true, overrideVersionCode = 5),
|
||||||
SingleLang("JetAnime", "https://ssl.jetanimes.com", "fr", isNsfw = false, overrideVersionCode = 2),
|
SingleLang("JetAnime", "https://ssl.jetanimes.com", "fr", isNsfw = false, overrideVersionCode = 2),
|
||||||
|
Reference in New Issue
Block a user