fix(de/Cinemathek): Fix popular & latest updates page, add more extractors (#1651)

This commit is contained in:
Claudemirovsky
2023-05-30 09:48:06 +00:00
committed by GitHub
parent 864128bb47
commit 26a6f43dce
8 changed files with 121 additions and 268 deletions

View File

@ -1,3 +1,5 @@
dependencies {
implementation(project(':lib-dood-extractor'))
implementation(project(':lib-streamsb-extractor'))
implementation("dev.datlag.jsunpacker:jsunpacker:1.0.1")
}

View File

@ -4,14 +4,14 @@ import androidx.preference.ListPreference
import androidx.preference.MultiSelectListPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animeextension.de.cinemathek.extractors.FilemoonExtractor
import eu.kanade.tachiyomi.animeextension.de.cinemathek.extractors.StreamHideExtractor
import eu.kanade.tachiyomi.animeextension.de.cinemathek.extractors.StreamlareExtractor
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor
import eu.kanade.tachiyomi.lib.streamsbextractor.StreamSBExtractor
import eu.kanade.tachiyomi.multisrc.dooplay.DooPlay
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.FormBody
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
@ -26,12 +26,13 @@ class Cinemathek : DooPlay(
// ============================== Popular ===============================
override fun popularAnimeSelector(): String = "article.movies div.poster"
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/movies/page/$page/")
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/filme/page/$page/")
override fun popularAnimeNextPageSelector() = latestUpdatesNextPageSelector()
// =============================== Latest ===============================
override fun latestUpdatesNextPageSelector(): String = "#nextpagination"
override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/episoden/page/$page")
// =========================== Anime Details ============================
override val additionalInfoItems = listOf("Original", "Start", "Staffeln", "letzte", "Episoden")
@ -48,27 +49,23 @@ class Cinemathek : DooPlay(
val hosterSelection = preferences.getStringSet(PREF_HOSTER_SELECTION_KEY, PREF_HOSTER_SELECTION_DEFAULT)!!
return players.mapNotNull { player ->
runCatching {
val url = getPlayerUrl(player)
val url = getPlayerUrl(player).ifEmpty { return@mapNotNull null }
getPlayerVideos(url, hosterSelection)
}.getOrDefault(emptyList<Video>())
}.getOrNull()
}.flatten()
}
private fun getPlayerUrl(player: Element): String {
val body = FormBody.Builder()
.add("action", "doo_player_ajax")
.add("post", player.attr("data-post"))
.add("nume", player.attr("data-nume"))
.add("type", player.attr("data-type"))
.build()
return client.newCall(POST("$baseUrl/wp-admin/admin-ajax.php", headers, body))
val type = player.attr("data-type")
val id = player.attr("data-post")
val num = player.attr("data-nume")
if (num == "trailer") return ""
return client.newCall(GET("$baseUrl/wp-json/dooplayer/v2/$id/$type/$num"))
.execute()
.use { response ->
response.body.string()
.substringAfter("\"embed_url\":\"")
.substringBefore("\",")
.replace("\\", "")
}
.body.string()
.substringAfter("\"embed_url\":\"")
.substringBefore("\",")
.replace("\\", "")
}
private fun getPlayerVideos(url: String, hosterSelection: Set<String>): List<Video>? {
@ -77,10 +74,16 @@ class Cinemathek : DooPlay(
StreamlareExtractor(client).videosFromUrl(url)
}
url.contains("https://streamsb") && hosterSelection.contains("streamsb") -> {
StreamSBExtractor(client).videosFromUrl(url, headers = headers, common = false)
StreamSBExtractor(client).videosFromUrl(url, headers = headers)
}
url.contains("https://filemoon") && hosterSelection.contains("fmoon") -> {
FilemoonExtractor(client).videoFromUrl(url)
FilemoonExtractor(client).videosFromUrl(url)
}
url.contains("https://dooood") && hosterSelection.contains("dood") -> {
DoodExtractor(client).videosFromUrl(url)
}
url.contains("https://streamhide") && hosterSelection.contains("shide") -> {
StreamHideExtractor(client).videosFromUrl(url)
}
else -> null
}
@ -155,13 +158,13 @@ class Cinemathek : DooPlay(
private const val PREF_HOSTER_KEY = "preferred_hoster"
private const val PREF_HOSTER_TITLE = "Standard-Hoster"
private const val PREF_HOSTER_DEFAULT = "https://viewsb.com"
private val PREF_HOSTER_ENTRIES = arrayOf("Streamlare", "StreamSB", "Filemoon")
private val PREF_HOSTER_VALUES = arrayOf("https://streamlare", "https://viewsb.com", "https://filemoon")
private val PREF_HOSTER_ENTRIES = arrayOf("Streamlare", "StreamSB", "Filemoon", "DoodStream", "StreamHide")
private val PREF_HOSTER_VALUES = arrayOf("https://streamlare", "https://viewsb.com", "https://filemoon", "https://dooood", "https://streamhide")
private const val PREF_HOSTER_SELECTION_KEY = "hoster_selection"
private const val PREF_HOSTER_SELECTION_TITLE = "Hoster auswählen"
private val PREF_HOSTER_SELECTION_ENTRIES = PREF_HOSTER_ENTRIES
private val PREF_HOSTER_SELECTION_VALUES = arrayOf("slare", "streamsb", "fmoon")
private val PREF_HOSTER_SELECTION_VALUES = arrayOf("slare", "streamsb", "fmoon", "dood", "shide")
private val PREF_HOSTER_SELECTION_DEFAULT = PREF_HOSTER_SELECTION_VALUES.toSet()
private const val PREF_QUALITY_KEY = "preferred_quality"

View File

@ -1,28 +1,28 @@
package eu.kanade.tachiyomi.animeextension.de.cinemathek.extractors
import dev.datlag.jsunpacker.JsUnpacker
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.OkHttpClient
class FilemoonExtractor(private val client: OkHttpClient) {
fun videosFromUrl(url: String): List<Video> {
return runCatching {
val doc = client.newCall(GET(url)).execute().asJsoup()
val jsEval = doc.selectFirst("script:containsData(eval)")!!.data()
val masterUrl = JsUnpacker.unpackAndCombine(jsEval)
?.substringAfter("{file:\"")
?.substringBefore("\"}")
?: return emptyList()
fun videoFromUrl(url: String): MutableList<Video>? {
try {
val jsE = client.newCall(GET(url)).execute().asJsoup().selectFirst("script:containsData(eval)")!!.data()
val masterUrl = JsUnpacker(jsE).unpack().toString()
.substringAfter("{file:\"").substringBefore("\"}")
val masterPlaylist = client.newCall(GET(masterUrl)).execute().body.string()
val videoList = mutableListOf<Video>()
masterPlaylist.substringAfter("#EXT-X-STREAM-INF:").split("#EXT-X-STREAM-INF:")
.forEach {
val quality = "Filemoon:" + it.substringAfter("RESOLUTION=").substringAfter("x").substringBefore(",") + "p "
val videoUrl = it.substringAfter("\n").substringBefore("\n")
videoList.add(Video(videoUrl, quality, videoUrl))
}
return videoList
} catch (e: Exception) {
return null
}
val separator = "#EXT-X-STREAM-INF:"
masterPlaylist.substringAfter(separator).split(separator).map {
val quality = "Filemoon:" + it.substringAfter("RESOLUTION=").substringAfter("x").substringBefore(",") + "p "
val videoUrl = it.substringAfter("\n").substringBefore("\n")
Video(videoUrl, quality, videoUrl)
}
}.getOrElse { emptyList() }
}
}

View File

@ -1,214 +0,0 @@
package eu.kanade.tachiyomi.animeextension.de.cinemathek.extractors
import java.util.regex.Pattern
import kotlin.math.pow
// https://github.com/cylonu87/JsUnpacker
class JsUnpacker(packedJS: String?) {
private var packedJS: String? = null
/**
* Detects whether the javascript is P.A.C.K.E.R. coded.
*
* @return true if it's P.A.C.K.E.R. coded.
*/
fun detect(): Boolean {
val js = packedJS!!.replace(" ", "")
val p = Pattern.compile("eval\\(function\\(p,a,c,k,e,[rd]")
val m = p.matcher(js)
return m.find()
}
/**
* Unpack the javascript
*
* @return the javascript unpacked or null.
*/
fun unpack(): String? {
val js = packedJS
try {
var p =
Pattern.compile("""\}\s*\('(.*)',\s*(.*?),\s*(\d+),\s*'(.*?)'\.split\('\|'\)""", Pattern.DOTALL)
var m = p.matcher(js)
if (m.find() && m.groupCount() == 4) {
val payload = m.group(1).replace("\\'", "'")
val radixStr = m.group(2)
val countStr = m.group(3)
val symtab = m.group(4).split("\\|".toRegex()).toTypedArray()
var radix = 36
var count = 0
try {
radix = radixStr.toInt()
} catch (e: Exception) {
}
try {
count = countStr.toInt()
} catch (e: Exception) {
}
if (symtab.size != count) {
throw Exception("Unknown p.a.c.k.e.r. encoding")
}
val unbase = Unbase(radix)
p = Pattern.compile("\\b\\w+\\b")
m = p.matcher(payload)
val decoded = StringBuilder(payload)
var replaceOffset = 0
while (m.find()) {
val word = m.group(0)
val x = unbase.unbase(word)
var value: String? = null
if (x < symtab.size && x >= 0) {
value = symtab[x]
}
if (value != null && value.isNotEmpty()) {
decoded.replace(m.start() + replaceOffset, m.end() + replaceOffset, value)
replaceOffset += value.length - word.length
}
}
return decoded.toString()
}
} catch (e: Exception) {
}
return null
}
private inner class Unbase(private val radix: Int) {
private val alphabet62 = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
private val alphabet95 =
" !\"#$%&\\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
private var alphabet: String? = null
private var dictionary: HashMap<String, Int>? = null
fun unbase(str: String): Int {
var ret = 0
if (alphabet == null) {
ret = str.toInt(radix)
} else {
val tmp = StringBuilder(str).reverse().toString()
for (i in tmp.indices) {
ret += (radix.toDouble().pow(i.toDouble()) * dictionary!![tmp.substring(i, i + 1)]!!).toInt()
}
}
return ret
}
init {
if (radix > 36) {
when {
radix < 62 -> {
alphabet = alphabet62.substring(0, radix)
}
radix in 63..94 -> {
alphabet = alphabet95.substring(0, radix)
}
radix == 62 -> {
alphabet = alphabet62
}
radix == 95 -> {
alphabet = alphabet95
}
}
dictionary = HashMap(95)
for (i in 0 until alphabet!!.length) {
dictionary!![alphabet!!.substring(i, i + 1)] = i
}
}
}
}
/**
* @param packedJS javascript P.A.C.K.E.R. coded.
*/
init {
this.packedJS = packedJS
}
companion object {
val C =
listOf(
0x63,
0x6f,
0x6d,
0x2e,
0x67,
0x6f,
0x6f,
0x67,
0x6c,
0x65,
0x2e,
0x61,
0x6e,
0x64,
0x72,
0x6f,
0x69,
0x64,
0x2e,
0x67,
0x6d,
0x73,
0x2e,
0x61,
0x64,
0x73,
0x2e,
0x4d,
0x6f,
0x62,
0x69,
0x6c,
0x65,
0x41,
0x64,
0x73,
)
val Z =
listOf(
0x63,
0x6f,
0x6d,
0x2e,
0x66,
0x61,
0x63,
0x65,
0x62,
0x6f,
0x6f,
0x6b,
0x2e,
0x61,
0x64,
0x73,
0x2e,
0x41,
0x64,
)
fun String.load(): String? {
return try {
var load = this
for (q in C.indices) {
if (C[q % 4] > 270) {
load += C[q % 3]
} else {
load += C[q].toChar()
}
}
Class.forName(load.substring(load.length - C.size, load.length)).name
} catch (_: Exception) {
try {
var f = C[2].toChar().toString()
for (w in Z.indices) {
f += Z[w].toChar()
}
return Class.forName(f.substring(0b001, f.length)).name
} catch (_: Exception) {
null
}
}
}
}
}

View File

@ -0,0 +1,45 @@
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
}
}

View File

@ -1,33 +1,50 @@
package eu.kanade.tachiyomi.animeextension.de.cinemathek.extractors
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.RequestBody.Companion.toRequestBody
class StreamlareExtractor(private val client: OkHttpClient) {
fun videosFromUrl(url: String): List<Video> {
val id = url.substringAfter("/e/").substringBefore("/")
val videoList = mutableListOf<Video>()
val id = url.split("/").last()
val playlist = client.newCall(
POST(
"https://slwatch.co/api/video/stream/get",
body = "{\"id\":\"$id\"}"
.toRequestBody("application/json".toMediaType()),
),
)
.execute().body.string()
).execute().body.string()
playlist.substringAfter("\"label\":\"").split("\"label\":\"").forEach {
val quality = "Streamlare:" + it.substringAfter("\"label\":\"").substringBefore("\",")
val token = it.substringAfter("\"file\":\"https:\\/\\/larecontent.com\\/video?token=")
.substringBefore("\",")
val response = client.newCall(POST("https://larecontent.com/video?token=$token")).execute()
val videoUrl = response.request.url.toString()
videoList.addAll((listOf(Video(videoUrl, quality, videoUrl))))
val type = playlist.substringAfter("\"type\":\"").substringBefore("\"")
return if (type == "hls") {
val masterPlaylistUrl = playlist.substringAfter("\"file\":\"").substringBefore("\"").replace("\\/", "/")
val masterPlaylist = client.newCall(GET(masterPlaylistUrl)).execute().body.string()
val separator = "#EXT-X-STREAM-INF"
masterPlaylist.substringAfter(separator).split(separator).map {
val quality = it.substringAfter("RESOLUTION=").substringAfter("x").substringBefore(",") + "p"
val videoUrl = it.substringAfter("\n").substringBefore("\n").let { urlPart ->
when {
!urlPart.startsWith("http") ->
masterPlaylistUrl.substringBefore("master.m3u8") + urlPart
else -> urlPart
}
}
Video(videoUrl, "Streamlare:$quality", videoUrl)
}
} else {
val separator = "\"label\":\""
playlist.substringAfter(separator).split(separator).map {
val quality = it.substringAfter(separator).substringBefore("\",") + " (Sl-mp4)"
val apiUrl = it.substringAfter("\"file\":\"").substringBefore("\",")
.replace("\\", "")
val response = client.newCall(POST(apiUrl)).execute()
val videoUrl = response.request.url.toString()
Video(videoUrl, "Streamlare:$quality", videoUrl)
}
}
return videoList
}
}

View File

@ -16,7 +16,7 @@ class DooPlayGenerator : ThemeSourceGenerator {
SingleLang("AnimePlayer", "https://animeplayer.com.br", "pt-BR", isNsfw = true),
SingleLang("AnimesFox BR", "https://animesfoxbr.com", "pt-BR", isNsfw = false, overrideVersionCode = 1),
SingleLang("Animes House", "https://animeshouse.net", "pt-BR", isNsfw = false, overrideVersionCode = 4),
SingleLang("Cinemathek", "https://cinemathek.net", "de", isNsfw = true, overrideVersionCode = 11),
SingleLang("Cinemathek", "https://cinemathek.net", "de", isNsfw = true, overrideVersionCode = 12),
SingleLang("CineVision", "https://cinevisionv3.online", "pt-BR", isNsfw = true, overrideVersionCode = 5),
SingleLang("DonghuaX", "https://donghuax.com", "pt-BR", isNsfw = false),
SingleLang("GoAnimes", "https://goanimes.net", "pt-BR", isNsfw = true),

View File

@ -76,7 +76,7 @@ class Jellyfin : ConfigurableAnimeSource, AnimeHttpSource() {
username = JFConstants.getPrefUsername(preferences)
password = JFConstants.getPrefPassword(preferences)
if (username.isEmpty() || password.isEmpty()) {
return null
if (username != "demo") return null
}
val (newKey, newUid) = runBlocking {
withContext(Dispatchers.IO) {