fix(de/Cinemathek): Fix popular & latest updates page, add more extractors (#1651)
This commit is contained in:
@ -1,3 +1,5 @@
|
|||||||
dependencies {
|
dependencies {
|
||||||
|
implementation(project(':lib-dood-extractor'))
|
||||||
implementation(project(':lib-streamsb-extractor'))
|
implementation(project(':lib-streamsb-extractor'))
|
||||||
|
implementation("dev.datlag.jsunpacker:jsunpacker:1.0.1")
|
||||||
}
|
}
|
||||||
|
@ -4,14 +4,14 @@ 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.FilemoonExtractor
|
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.animeextension.de.cinemathek.extractors.StreamlareExtractor
|
||||||
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.streamsbextractor.StreamSBExtractor
|
import eu.kanade.tachiyomi.lib.streamsbextractor.StreamSBExtractor
|
||||||
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.network.POST
|
|
||||||
import eu.kanade.tachiyomi.util.asJsoup
|
import eu.kanade.tachiyomi.util.asJsoup
|
||||||
import okhttp3.FormBody
|
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import org.jsoup.nodes.Document
|
import org.jsoup.nodes.Document
|
||||||
@ -26,12 +26,13 @@ class Cinemathek : DooPlay(
|
|||||||
// ============================== Popular ===============================
|
// ============================== Popular ===============================
|
||||||
override fun popularAnimeSelector(): String = "article.movies div.poster"
|
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()
|
override fun popularAnimeNextPageSelector() = latestUpdatesNextPageSelector()
|
||||||
|
|
||||||
// =============================== Latest ===============================
|
// =============================== Latest ===============================
|
||||||
override fun latestUpdatesNextPageSelector(): String = "#nextpagination"
|
override fun latestUpdatesNextPageSelector(): String = "#nextpagination"
|
||||||
|
override fun latestUpdatesRequest(page: Int): Request = 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")
|
||||||
@ -48,27 +49,23 @@ class Cinemathek : DooPlay(
|
|||||||
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.mapNotNull { player ->
|
||||||
runCatching {
|
runCatching {
|
||||||
val url = getPlayerUrl(player)
|
val url = getPlayerUrl(player).ifEmpty { return@mapNotNull null }
|
||||||
getPlayerVideos(url, hosterSelection)
|
getPlayerVideos(url, hosterSelection)
|
||||||
}.getOrDefault(emptyList<Video>())
|
}.getOrNull()
|
||||||
}.flatten()
|
}.flatten()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getPlayerUrl(player: Element): String {
|
private fun getPlayerUrl(player: Element): String {
|
||||||
val body = FormBody.Builder()
|
val type = player.attr("data-type")
|
||||||
.add("action", "doo_player_ajax")
|
val id = player.attr("data-post")
|
||||||
.add("post", player.attr("data-post"))
|
val num = player.attr("data-nume")
|
||||||
.add("nume", player.attr("data-nume"))
|
if (num == "trailer") return ""
|
||||||
.add("type", player.attr("data-type"))
|
return client.newCall(GET("$baseUrl/wp-json/dooplayer/v2/$id/$type/$num"))
|
||||||
.build()
|
|
||||||
return client.newCall(POST("$baseUrl/wp-admin/admin-ajax.php", headers, body))
|
|
||||||
.execute()
|
.execute()
|
||||||
.use { response ->
|
.body.string()
|
||||||
response.body.string()
|
.substringAfter("\"embed_url\":\"")
|
||||||
.substringAfter("\"embed_url\":\"")
|
.substringBefore("\",")
|
||||||
.substringBefore("\",")
|
.replace("\\", "")
|
||||||
.replace("\\", "")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getPlayerVideos(url: String, hosterSelection: Set<String>): List<Video>? {
|
private fun getPlayerVideos(url: String, hosterSelection: Set<String>): List<Video>? {
|
||||||
@ -77,10 +74,16 @@ class Cinemathek : DooPlay(
|
|||||||
StreamlareExtractor(client).videosFromUrl(url)
|
StreamlareExtractor(client).videosFromUrl(url)
|
||||||
}
|
}
|
||||||
url.contains("https://streamsb") && hosterSelection.contains("streamsb") -> {
|
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") -> {
|
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
|
else -> null
|
||||||
}
|
}
|
||||||
@ -155,13 +158,13 @@ class Cinemathek : DooPlay(
|
|||||||
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://viewsb.com"
|
||||||
private val PREF_HOSTER_ENTRIES = arrayOf("Streamlare", "StreamSB", "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")
|
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_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", "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 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,28 +1,28 @@
|
|||||||
package eu.kanade.tachiyomi.animeextension.de.cinemathek.extractors
|
package eu.kanade.tachiyomi.animeextension.de.cinemathek.extractors
|
||||||
|
|
||||||
|
import dev.datlag.jsunpacker.JsUnpacker
|
||||||
import eu.kanade.tachiyomi.animesource.model.Video
|
import eu.kanade.tachiyomi.animesource.model.Video
|
||||||
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.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
|
|
||||||
class FilemoonExtractor(private val client: 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 masterPlaylist = client.newCall(GET(masterUrl)).execute().body.string()
|
||||||
val videoList = mutableListOf<Video>()
|
val separator = "#EXT-X-STREAM-INF:"
|
||||||
masterPlaylist.substringAfter("#EXT-X-STREAM-INF:").split("#EXT-X-STREAM-INF:")
|
masterPlaylist.substringAfter(separator).split(separator).map {
|
||||||
.forEach {
|
val quality = "Filemoon:" + it.substringAfter("RESOLUTION=").substringAfter("x").substringBefore(",") + "p "
|
||||||
val quality = "Filemoon:" + it.substringAfter("RESOLUTION=").substringAfter("x").substringBefore(",") + "p "
|
val videoUrl = it.substringAfter("\n").substringBefore("\n")
|
||||||
val videoUrl = it.substringAfter("\n").substringBefore("\n")
|
Video(videoUrl, quality, videoUrl)
|
||||||
videoList.add(Video(videoUrl, quality, videoUrl))
|
}
|
||||||
}
|
}.getOrElse { emptyList() }
|
||||||
return videoList
|
|
||||||
} catch (e: Exception) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
@ -1,33 +1,50 @@
|
|||||||
package eu.kanade.tachiyomi.animeextension.de.cinemathek.extractors
|
package eu.kanade.tachiyomi.animeextension.de.cinemathek.extractors
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.animesource.model.Video
|
import eu.kanade.tachiyomi.animesource.model.Video
|
||||||
|
import eu.kanade.tachiyomi.network.GET
|
||||||
import eu.kanade.tachiyomi.network.POST
|
import eu.kanade.tachiyomi.network.POST
|
||||||
import okhttp3.MediaType.Companion.toMediaType
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
|
|
||||||
class StreamlareExtractor(private val client: OkHttpClient) {
|
class StreamlareExtractor(private val client: OkHttpClient) {
|
||||||
|
|
||||||
fun videosFromUrl(url: String): List<Video> {
|
fun videosFromUrl(url: String): List<Video> {
|
||||||
val id = url.substringAfter("/e/").substringBefore("/")
|
val id = url.split("/").last()
|
||||||
val videoList = mutableListOf<Video>()
|
|
||||||
val playlist = client.newCall(
|
val playlist = client.newCall(
|
||||||
POST(
|
POST(
|
||||||
"https://slwatch.co/api/video/stream/get",
|
"https://slwatch.co/api/video/stream/get",
|
||||||
body = "{\"id\":\"$id\"}"
|
body = "{\"id\":\"$id\"}"
|
||||||
.toRequestBody("application/json".toMediaType()),
|
.toRequestBody("application/json".toMediaType()),
|
||||||
),
|
),
|
||||||
)
|
).execute().body.string()
|
||||||
.execute().body.string()
|
|
||||||
|
|
||||||
playlist.substringAfter("\"label\":\"").split("\"label\":\"").forEach {
|
val type = playlist.substringAfter("\"type\":\"").substringBefore("\"")
|
||||||
val quality = "Streamlare:" + it.substringAfter("\"label\":\"").substringBefore("\",")
|
return if (type == "hls") {
|
||||||
val token = it.substringAfter("\"file\":\"https:\\/\\/larecontent.com\\/video?token=")
|
val masterPlaylistUrl = playlist.substringAfter("\"file\":\"").substringBefore("\"").replace("\\/", "/")
|
||||||
.substringBefore("\",")
|
val masterPlaylist = client.newCall(GET(masterPlaylistUrl)).execute().body.string()
|
||||||
val response = client.newCall(POST("https://larecontent.com/video?token=$token")).execute()
|
|
||||||
val videoUrl = response.request.url.toString()
|
val separator = "#EXT-X-STREAM-INF"
|
||||||
videoList.addAll((listOf(Video(videoUrl, quality, videoUrl))))
|
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ class DooPlayGenerator : ThemeSourceGenerator {
|
|||||||
SingleLang("AnimePlayer", "https://animeplayer.com.br", "pt-BR", isNsfw = true),
|
SingleLang("AnimePlayer", "https://animeplayer.com.br", "pt-BR", isNsfw = true),
|
||||||
SingleLang("AnimesFox BR", "https://animesfoxbr.com", "pt-BR", isNsfw = false, overrideVersionCode = 1),
|
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("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("CineVision", "https://cinevisionv3.online", "pt-BR", isNsfw = true, overrideVersionCode = 5),
|
||||||
SingleLang("DonghuaX", "https://donghuax.com", "pt-BR", isNsfw = false),
|
SingleLang("DonghuaX", "https://donghuax.com", "pt-BR", isNsfw = false),
|
||||||
SingleLang("GoAnimes", "https://goanimes.net", "pt-BR", isNsfw = true),
|
SingleLang("GoAnimes", "https://goanimes.net", "pt-BR", isNsfw = true),
|
||||||
|
@ -76,7 +76,7 @@ class Jellyfin : ConfigurableAnimeSource, AnimeHttpSource() {
|
|||||||
username = JFConstants.getPrefUsername(preferences)
|
username = JFConstants.getPrefUsername(preferences)
|
||||||
password = JFConstants.getPrefPassword(preferences)
|
password = JFConstants.getPrefPassword(preferences)
|
||||||
if (username.isEmpty() || password.isEmpty()) {
|
if (username.isEmpty() || password.isEmpty()) {
|
||||||
return null
|
if (username != "demo") return null
|
||||||
}
|
}
|
||||||
val (newKey, newUid) = runBlocking {
|
val (newKey, newUid) = runBlocking {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
|
Reference in New Issue
Block a user