fix(en/nineanime): Update mp4upload extractor, add preference to toggle hosts, small refactor (#1752)
This commit is contained in:
@ -6,7 +6,7 @@ ext {
|
|||||||
extName = '9anime'
|
extName = '9anime'
|
||||||
pkgNameSuffix = 'en.nineanime'
|
pkgNameSuffix = 'en.nineanime'
|
||||||
extClass = '.NineAnime'
|
extClass = '.NineAnime'
|
||||||
extVersionCode = 44
|
extVersionCode = 45
|
||||||
libVersion = '13'
|
libVersion = '13'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.animeextension.en.nineanime
|
|||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import androidx.preference.ListPreference
|
import androidx.preference.ListPreference
|
||||||
|
import androidx.preference.MultiSelectListPreference
|
||||||
import androidx.preference.PreferenceScreen
|
import androidx.preference.PreferenceScreen
|
||||||
import androidx.preference.SwitchPreferenceCompat
|
import androidx.preference.SwitchPreferenceCompat
|
||||||
import eu.kanade.tachiyomi.animeextension.en.nineanime.extractors.Mp4uploadExtractor
|
import eu.kanade.tachiyomi.animeextension.en.nineanime.extractors.Mp4uploadExtractor
|
||||||
@ -22,7 +23,6 @@ import kotlinx.coroutines.async
|
|||||||
import kotlinx.coroutines.awaitAll
|
import kotlinx.coroutines.awaitAll
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.decodeFromString
|
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.JsonObject
|
import kotlinx.serialization.json.JsonObject
|
||||||
import kotlinx.serialization.json.jsonPrimitive
|
import kotlinx.serialization.json.jsonPrimitive
|
||||||
@ -44,7 +44,7 @@ class NineAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||||||
|
|
||||||
override val name = "9anime"
|
override val name = "9anime"
|
||||||
|
|
||||||
override val baseUrl by lazy { preferences.getString("preferred_domain", "https://9anime.to")!! }
|
override val baseUrl by lazy { preferences.getString(PREF_DOMAIN_KEY, PREF_DOMAIN_DEFAULT)!! }
|
||||||
|
|
||||||
override val lang = "en"
|
override val lang = "en"
|
||||||
|
|
||||||
@ -62,9 +62,7 @@ class NineAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||||||
|
|
||||||
// ============================== Popular ===============================
|
// ============================== Popular ===============================
|
||||||
|
|
||||||
override fun popularAnimeRequest(page: Int): Request {
|
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/filter?sort=trending&page=$page")
|
||||||
return GET("$baseUrl/filter?sort=trending&page=$page")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun popularAnimeSelector(): String = "div.ani.items > div.item"
|
override fun popularAnimeSelector(): String = "div.ani.items > div.item"
|
||||||
|
|
||||||
@ -79,9 +77,7 @@ class NineAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||||||
|
|
||||||
// =============================== Latest ===============================
|
// =============================== Latest ===============================
|
||||||
|
|
||||||
override fun latestUpdatesRequest(page: Int): Request {
|
override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/filter?sort=recently_updated&page=$page")
|
||||||
return GET("$baseUrl/filter?sort=recently_updated&page=$page")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun latestUpdatesSelector(): String = popularAnimeSelector()
|
override fun latestUpdatesSelector(): String = popularAnimeSelector()
|
||||||
|
|
||||||
@ -128,29 +124,28 @@ class NineAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||||||
|
|
||||||
override fun searchAnimeNextPageSelector(): String = popularAnimeNextPageSelector()
|
override fun searchAnimeNextPageSelector(): String = popularAnimeNextPageSelector()
|
||||||
|
|
||||||
|
// ============================== Filters ===============================
|
||||||
|
|
||||||
override fun getFilterList(): AnimeFilterList = NineAnimeFilters.FILTER_LIST
|
override fun getFilterList(): AnimeFilterList = NineAnimeFilters.FILTER_LIST
|
||||||
|
|
||||||
// =========================== Anime Details ============================
|
// =========================== Anime Details ============================
|
||||||
|
|
||||||
override fun animeDetailsParse(document: Document): SAnime {
|
override fun animeDetailsParse(document: Document): SAnime = SAnime.create().apply {
|
||||||
val anime = SAnime.create()
|
title = document.select("h1.title").text()
|
||||||
anime.title = document.select("h1.title").text()
|
genre = document.select("div:contains(Genre) > span > a").joinToString { it.text() }
|
||||||
anime.genre = document.select("div:contains(Genre) > span > a").joinToString { it.text() }
|
description = document.select("div.synopsis > div.shorting > div.content").text()
|
||||||
anime.description = document.select("div.synopsis > div.shorting > div.content").text()
|
author = document.select("div:contains(Studio) > span > a").text()
|
||||||
anime.author = document.select("div:contains(Studio) > span > a").text()
|
status = parseStatus(document.select("div:contains(Status) > span").text())
|
||||||
anime.status = parseStatus(document.select("div:contains(Status) > span").text())
|
|
||||||
|
|
||||||
// add alternative name to anime description
|
|
||||||
val altName = "Other name(s): "
|
val altName = "Other name(s): "
|
||||||
document.select("h1.title").attr("data-jp").let {
|
document.select("h1.title").attr("data-jp").let {
|
||||||
if (it.isBlank().not()) {
|
if (it.isNotBlank()) {
|
||||||
anime.description = when {
|
description = when {
|
||||||
anime.description.isNullOrBlank() -> altName + it
|
description.isNullOrBlank() -> altName + it
|
||||||
else -> anime.description + "\n\n$altName" + it
|
else -> description + "\n\n$altName" + it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return anime
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================== Episodes ==============================
|
// ============================== Episodes ==============================
|
||||||
@ -178,24 +173,25 @@ class NineAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||||||
override fun episodeFromElement(element: Element): SEpisode = throw Exception("not Used")
|
override fun episodeFromElement(element: Element): SEpisode = throw Exception("not Used")
|
||||||
|
|
||||||
private fun episodeFromElements(element: Element, url: String): SEpisode {
|
private fun episodeFromElements(element: Element, url: String): SEpisode {
|
||||||
val episode = SEpisode.create()
|
|
||||||
val epNum = element.attr("data-num")
|
val epNum = element.attr("data-num")
|
||||||
val ids = element.attr("data-ids")
|
val ids = element.attr("data-ids")
|
||||||
val sub = element.attr("data-sub").toInt().toBoolean()
|
val sub = element.attr("data-sub").toInt().toBoolean()
|
||||||
val dub = element.attr("data-dub").toInt().toBoolean()
|
val dub = element.attr("data-dub").toInt().toBoolean()
|
||||||
val extraInfo = if (element.hasClass("filler") && preferences.getBoolean("mark_fillers", true)) {
|
val extraInfo = if (element.hasClass("filler") && preferences.getBoolean(PREF_MARK_FILLERS_KEY, PREF_MARK_FILLERS_DEFAULT)) {
|
||||||
" • Filler Episode"
|
" • Filler Episode"
|
||||||
} else {
|
} else {
|
||||||
""
|
""
|
||||||
}
|
}
|
||||||
episode.url = "$ids&epurl=$url/ep-$epNum"
|
|
||||||
episode.episode_number = epNum.toFloat()
|
|
||||||
episode.scanlator = ((if (sub) "Sub" else "") + if (dub) ", Dub" else "") + extraInfo
|
|
||||||
val name = element.parent()?.select("span.d-title")?.text().orEmpty()
|
val name = element.parent()?.select("span.d-title")?.text().orEmpty()
|
||||||
val namePrefix = "Episode $epNum"
|
val namePrefix = "Episode $epNum"
|
||||||
episode.name = "Episode $epNum" +
|
|
||||||
if (name.isNotEmpty() && name != namePrefix) ": $name" else ""
|
return SEpisode.create().apply {
|
||||||
return episode
|
this.name = "Episode $epNum" +
|
||||||
|
if (name.isNotEmpty() && name != namePrefix) ": $name" else ""
|
||||||
|
this.url = "$ids&epurl=$url/ep-$epNum"
|
||||||
|
episode_number = epNum.toFloat()
|
||||||
|
scanlator = ((if (sub) "Sub" else "") + if (dub) ", Dub" else "") + extraInfo
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================ Video Links =============================
|
// ============================ Video Links =============================
|
||||||
@ -212,6 +208,7 @@ class NineAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||||||
val epurl = response.request.header("url").toString()
|
val epurl = response.request.header("url").toString()
|
||||||
val responseObject = json.decodeFromString<JsonObject>(response.body.string())
|
val responseObject = json.decodeFromString<JsonObject>(response.body.string())
|
||||||
val document = Jsoup.parse(JSONUtil.unescape(responseObject["result"]!!.jsonPrimitive.content))
|
val document = Jsoup.parse(JSONUtil.unescape(responseObject["result"]!!.jsonPrimitive.content))
|
||||||
|
val hosterSelection = preferences.getStringSet(PREF_HOSTER_KEY, PREF_HOSTER_DEFAULT)!!
|
||||||
val videoList = mutableListOf<Video>()
|
val videoList = mutableListOf<Video>()
|
||||||
|
|
||||||
val servers = mutableListOf<Triple<String, String, String>>()
|
val servers = mutableListOf<Triple<String, String, String>>()
|
||||||
@ -219,15 +216,19 @@ class NineAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||||||
.substringBefore("?")
|
.substringBefore("?")
|
||||||
.split(",")
|
.split(",")
|
||||||
ids.getOrNull(0)?.let { subId ->
|
ids.getOrNull(0)?.let { subId ->
|
||||||
document.select("li[data-ep-id=$subId]").map { serverElement ->
|
document.select("li[data-ep-id=$subId]").forEach { serverElement ->
|
||||||
val server = serverElement.text().lowercase()
|
val server = serverElement.text().lowercase()
|
||||||
|
if (hosterSelection.contains(server).not()) return@forEach
|
||||||
|
|
||||||
val serverId = serverElement.attr("data-link-id")
|
val serverId = serverElement.attr("data-link-id")
|
||||||
servers.add(Triple("Sub", serverId, server))
|
servers.add(Triple("Sub", serverId, server))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ids.getOrNull(1)?.let { dubId ->
|
ids.getOrNull(1)?.let { dubId ->
|
||||||
document.select("li[data-ep-id=$dubId]").map { serverElement ->
|
document.select("li[data-ep-id=$dubId]").forEach { serverElement ->
|
||||||
val server = serverElement.text().lowercase()
|
val server = serverElement.text().lowercase()
|
||||||
|
if (hosterSelection.contains(server).not()) return@forEach
|
||||||
|
|
||||||
val serverId = serverElement.attr("data-link-id")
|
val serverId = serverElement.attr("data-link-id")
|
||||||
servers.add(Triple("Dub", serverId, server))
|
servers.add(Triple("Dub", serverId, server))
|
||||||
}
|
}
|
||||||
@ -242,6 +243,8 @@ class NineAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||||||
// Dub
|
// Dub
|
||||||
ids.getOrNull(1)?.let { videoList.addAll(extractVizVideo("Dub", epurl)) }
|
ids.getOrNull(1)?.let { videoList.addAll(extractVizVideo("Dub", epurl)) }
|
||||||
|
|
||||||
|
require(videoList.isNotEmpty()) { "Failed to fetch videos" }
|
||||||
|
|
||||||
return videoList
|
return videoList
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -305,7 +308,7 @@ class NineAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||||||
.videoFromUrl(embedLink, "StreamTape - ${server.first}")?.let {
|
.videoFromUrl(embedLink, "StreamTape - ${server.first}")?.let {
|
||||||
videoList.add(it)
|
videoList.add(it)
|
||||||
}
|
}
|
||||||
"mp4upload" -> Mp4uploadExtractor(client)
|
"mp4upload" -> Mp4uploadExtractor(client, headers)
|
||||||
.videoFromUrl(embedLink, "Mp4Upload - ${server.first}").let {
|
.videoFromUrl(embedLink, "Mp4Upload - ${server.first}").let {
|
||||||
videoList.addAll(it)
|
videoList.addAll(it)
|
||||||
}
|
}
|
||||||
@ -363,8 +366,8 @@ class NineAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||||||
private fun Int.toBoolean() = this == 1
|
private fun Int.toBoolean() = this == 1
|
||||||
|
|
||||||
override fun List<Video>.sort(): List<Video> {
|
override fun List<Video>.sort(): List<Video> {
|
||||||
val quality = preferences.getString("preferred_quality", "1080")!!
|
val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
|
||||||
val lang = preferences.getString("preferred_language", "Sub")!!
|
val lang = preferences.getString(PREF_LANG_KEY, PREF_LANG_DEFAULT)!!
|
||||||
|
|
||||||
return this.sortedWith(
|
return this.sortedWith(
|
||||||
compareByDescending<Video> { it.quality.contains(quality) }
|
compareByDescending<Video> { it.quality.contains(quality) }
|
||||||
@ -380,6 +383,10 @@ class NineAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun <A, B> Iterable<A>.parallelMap(f: suspend (A) -> B): List<B> = runBlocking {
|
||||||
|
map { async(Dispatchers.Default) { f(it) } }.awaitAll()
|
||||||
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class ServerResponse(
|
data class ServerResponse(
|
||||||
val result: Result,
|
val result: Result,
|
||||||
@ -403,13 +410,46 @@ class NineAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||||||
val rawURL: String,
|
val rawURL: String,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val PREF_DOMAIN_KEY = "preferred_domain"
|
||||||
|
private const val PREF_DOMAIN_DEFAULT = "https://9anime.to"
|
||||||
|
|
||||||
|
private const val PREF_QUALITY_KEY = "preferred_quality"
|
||||||
|
private const val PREF_QUALITY_DEFAULT = "1080"
|
||||||
|
|
||||||
|
private const val PREF_LANG_KEY = "preferred_language"
|
||||||
|
private const val PREF_LANG_DEFAULT = "Sub"
|
||||||
|
|
||||||
|
private const val PREF_MARK_FILLERS_KEY = "mark_fillers"
|
||||||
|
private const val PREF_MARK_FILLERS_DEFAULT = true
|
||||||
|
|
||||||
|
private const val PREF_HOSTER_KEY = "hoster_selection"
|
||||||
|
private val HOSTERS = arrayOf(
|
||||||
|
"Vidstream",
|
||||||
|
"MyCloud",
|
||||||
|
"Filemoon",
|
||||||
|
"StreamTape",
|
||||||
|
"Mp4Upload",
|
||||||
|
)
|
||||||
|
private val HOSTERS_NAMES = arrayOf(
|
||||||
|
"vidstream",
|
||||||
|
"mycloud",
|
||||||
|
"filemoon",
|
||||||
|
"streamtape",
|
||||||
|
"mp4upload",
|
||||||
|
)
|
||||||
|
private val PREF_HOSTER_DEFAULT = HOSTERS_NAMES.toSet()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================== Settings ==============================
|
||||||
|
|
||||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||||
val domainPref = ListPreference(screen.context).apply {
|
ListPreference(screen.context).apply {
|
||||||
key = "preferred_domain"
|
key = PREF_DOMAIN_KEY
|
||||||
title = "Preferred domain (requires app restart)"
|
title = "Preferred domain (requires app restart)"
|
||||||
entries = arrayOf("9anime.to", "9anime.gs", "9anime.pl", "9animehq.to")
|
entries = arrayOf("9anime.to", "9anime.gs", "9anime.pl", "9animehq.to")
|
||||||
entryValues = arrayOf("https://9anime.to", "https://9anime.gs", "https://9anime.pl", "https://9animehq.to")
|
entryValues = arrayOf("https://9anime.to", "https://9anime.gs", "https://9anime.pl", "https://9animehq.to")
|
||||||
setDefaultValue("https://9anime.to")
|
setDefaultValue(PREF_DOMAIN_DEFAULT)
|
||||||
summary = "%s"
|
summary = "%s"
|
||||||
|
|
||||||
setOnPreferenceChangeListener { _, newValue ->
|
setOnPreferenceChangeListener { _, newValue ->
|
||||||
@ -418,13 +458,14 @@ class NineAnime : 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 videoQualityPref = ListPreference(screen.context).apply {
|
|
||||||
key = "preferred_quality"
|
ListPreference(screen.context).apply {
|
||||||
|
key = PREF_QUALITY_KEY
|
||||||
title = "Preferred quality"
|
title = "Preferred quality"
|
||||||
entries = arrayOf("1080p", "720p", "480p", "360p")
|
entries = arrayOf("1080p", "720p", "480p", "360p")
|
||||||
entryValues = arrayOf("1080", "720", "480", "360")
|
entryValues = arrayOf("1080", "720", "480", "360")
|
||||||
setDefaultValue("1080")
|
setDefaultValue(PREF_QUALITY_DEFAULT)
|
||||||
summary = "%s"
|
summary = "%s"
|
||||||
|
|
||||||
setOnPreferenceChangeListener { _, newValue ->
|
setOnPreferenceChangeListener { _, newValue ->
|
||||||
@ -433,13 +474,14 @@ class NineAnime : 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 videoLanguagePref = ListPreference(screen.context).apply {
|
|
||||||
key = "preferred_language"
|
ListPreference(screen.context).apply {
|
||||||
|
key = PREF_LANG_KEY
|
||||||
title = "Preferred language"
|
title = "Preferred language"
|
||||||
entries = arrayOf("Sub", "Dub")
|
entries = arrayOf("Sub", "Dub")
|
||||||
entryValues = arrayOf("Sub", "Dub")
|
entryValues = arrayOf("Sub", "Dub")
|
||||||
setDefaultValue("Sub")
|
setDefaultValue(PREF_LANG_DEFAULT)
|
||||||
summary = "%s"
|
summary = "%s"
|
||||||
|
|
||||||
setOnPreferenceChangeListener { _, newValue ->
|
setOnPreferenceChangeListener { _, newValue ->
|
||||||
@ -448,23 +490,28 @@ class NineAnime : 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 markFillers = SwitchPreferenceCompat(screen.context).apply {
|
|
||||||
key = "mark_fillers"
|
SwitchPreferenceCompat(screen.context).apply {
|
||||||
|
key = PREF_MARK_FILLERS_KEY
|
||||||
title = "Mark filler episodes"
|
title = "Mark filler episodes"
|
||||||
setDefaultValue(true)
|
setDefaultValue(PREF_MARK_FILLERS_DEFAULT)
|
||||||
setOnPreferenceChangeListener { _, newValue ->
|
setOnPreferenceChangeListener { _, newValue ->
|
||||||
preferences.edit().putBoolean(key, newValue as Boolean).commit()
|
preferences.edit().putBoolean(key, newValue as Boolean).commit()
|
||||||
}
|
}
|
||||||
}
|
}.also(screen::addPreference)
|
||||||
|
|
||||||
screen.addPreference(domainPref)
|
MultiSelectListPreference(screen.context).apply {
|
||||||
screen.addPreference(videoQualityPref)
|
key = PREF_HOSTER_KEY
|
||||||
screen.addPreference(videoLanguagePref)
|
title = "Enable/Disable Hosts"
|
||||||
screen.addPreference(markFillers)
|
entries = HOSTERS
|
||||||
}
|
entryValues = HOSTERS_NAMES
|
||||||
|
setDefaultValue(PREF_HOSTER_DEFAULT)
|
||||||
|
|
||||||
private fun <A, B> Iterable<A>.parallelMap(f: suspend (A) -> B): List<B> = runBlocking {
|
setOnPreferenceChangeListener { _, newValue ->
|
||||||
map { async(Dispatchers.Default) { f(it) } }.awaitAll()
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
preferences.edit().putStringSet(key, newValue as Set<String>).commit()
|
||||||
|
}
|
||||||
|
}.also(screen::addPreference)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,16 +5,26 @@ import eu.kanade.tachiyomi.animesource.model.Video
|
|||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
import okhttp3.Headers
|
import okhttp3.Headers
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
|
import org.jsoup.Jsoup
|
||||||
|
|
||||||
class Mp4uploadExtractor(private val client: OkHttpClient) {
|
class Mp4uploadExtractor(private val client: OkHttpClient, private val headers: Headers) {
|
||||||
fun videoFromUrl(url: String, prefix: String = "Original (Mp4upload)"): List<Video> {
|
fun videoFromUrl(url: String, prefix: String = "Original (Mp4upload)"): List<Video> {
|
||||||
val headers = Headers.headersOf("referer", "https://mp4upload.com/")
|
val headers = headers.newBuilder()
|
||||||
|
.add("referer", "https://mp4upload.com/")
|
||||||
|
.build()
|
||||||
val body = client.newCall(GET(url, headers = headers)).execute().body.string()
|
val body = client.newCall(GET(url, headers = headers)).execute().body.string()
|
||||||
val packed = body.substringAfter("eval(function(p,a,c,k,e,d)")
|
val videoUrl = if (body.contains("eval(function(p,a,c,k,e,d)")) {
|
||||||
.substringBefore("</script>")
|
val packed = body.substringAfter("<script type='text/javascript'>eval(function(p,a,c,k,e,d)")
|
||||||
val unpacked = JsUnpacker.unpackAndCombine("eval(function(p,a,c,k,e,d)$packed")
|
.substringBefore("</script>")
|
||||||
?: return emptyList()
|
body.substringAfter("<script type='text/javascript'>eval(function(p,a,c,k,e,d)")
|
||||||
val videoUrl = unpacked.substringAfter("player.src(\"").substringBefore("\");")
|
.substringBefore("</script>")
|
||||||
|
val unpacked = JsUnpacker.unpackAndCombine("eval(function(p,a,c,k,e,d)" + packed) ?: return emptyList()
|
||||||
|
unpacked.substringAfter("player.src(\"").substringBefore("\");")
|
||||||
|
} else {
|
||||||
|
val script = Jsoup.parse(body).selectFirst("script:containsData(player.src)")?.data() ?: return emptyList()
|
||||||
|
script.substringAfter("src: \"").substringBefore("\"")
|
||||||
|
}
|
||||||
|
|
||||||
return listOf(
|
return listOf(
|
||||||
Video(videoUrl, prefix, videoUrl, headers),
|
Video(videoUrl, prefix, videoUrl, headers),
|
||||||
)
|
)
|
||||||
|
Reference in New Issue
Block a user