Kuronime: Fix video extraction (#1238)
* Fix video extraction * Small fix
This commit is contained in:
@ -1,12 +1,17 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlinx-serialization'
|
||||
|
||||
ext {
|
||||
extName = 'Kuronime'
|
||||
pkgNameSuffix = 'id.kuronime'
|
||||
extClass = '.Kuronime'
|
||||
extVersionCode = 4
|
||||
extVersionCode = 5
|
||||
libVersion = '13'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "dev.datlag.jsunpacker:jsunpacker:1.0.1"
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
||||
|
@ -2,8 +2,16 @@ package eu.kanade.tachiyomi.animeextension.id.kuronime
|
||||
|
||||
import android.app.Application
|
||||
import android.content.SharedPreferences
|
||||
import android.util.Base64
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.MultiSelectListPreference
|
||||
import androidx.preference.PreferenceScreen
|
||||
import eu.kanade.tachiyomi.animeextension.id.kuronime.extractors.AnimekuExtractor
|
||||
import eu.kanade.tachiyomi.animeextension.id.kuronime.extractors.HxFileExtractor
|
||||
import eu.kanade.tachiyomi.animeextension.id.kuronime.extractors.LinkBoxExtractor
|
||||
import eu.kanade.tachiyomi.animeextension.id.kuronime.extractors.Mp4uploadExtractor
|
||||
import eu.kanade.tachiyomi.animeextension.id.kuronime.extractors.StreamlareExtractor
|
||||
import eu.kanade.tachiyomi.animeextension.id.kuronime.extractors.YourUploadExtractor
|
||||
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
||||
import eu.kanade.tachiyomi.animesource.model.SAnime
|
||||
@ -14,6 +22,7 @@ import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import uy.kohesive.injekt.Injekt
|
||||
@ -76,7 +85,14 @@ class Kuronime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
||||
private fun getAnimeFromAnimeElement(element: Element): SAnime {
|
||||
val anime = SAnime.create()
|
||||
anime.setUrlWithoutDomain(element.select("div > a").first().attr("href"))
|
||||
anime.thumbnail_url = element.select("div > a > div.limit > img").first().attr("src")
|
||||
|
||||
val thumbnailElement = element.selectFirst("div > a > div.limit > img")
|
||||
val thumbnail = thumbnailElement.attr("src")
|
||||
anime.thumbnail_url = if (thumbnail.startsWith("https:")) {
|
||||
thumbnail
|
||||
} else {
|
||||
if (thumbnailElement.hasAttr("data-src")) thumbnailElement.attr("data-src") else ""
|
||||
}
|
||||
anime.title = element.select("div > a > div.tt > h4").text()
|
||||
return anime
|
||||
}
|
||||
@ -90,7 +106,7 @@ class Kuronime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
||||
|
||||
override fun popularAnimeNextPageSelector(): String = "div.pagination > a.next"
|
||||
|
||||
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/anime/?page=$page")
|
||||
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/anime/page/$page")
|
||||
|
||||
override fun popularAnimeSelector(): String = "div.listupd > article"
|
||||
|
||||
@ -107,39 +123,51 @@ class Kuronime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
||||
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val document = response.asJsoup()
|
||||
val patternZippy = "div.soraddl > div > a:nth-child(3)"
|
||||
val videoList = mutableListOf<Video>()
|
||||
|
||||
val zippy = document.select(patternZippy).mapNotNull {
|
||||
runCatching { zippyFromElement(it) }.getOrNull()
|
||||
val hosterSelection = preferences.getStringSet(
|
||||
"hoster_selection",
|
||||
setOf("animeku", "mp4upload", "yourupload", "streamlare", "linkbox")
|
||||
)!!
|
||||
|
||||
document.select("select.mirror > option[value]").forEach { opt ->
|
||||
val decoded = if (opt.attr("value").isEmpty()) {
|
||||
document.selectFirst("iframe").attr("data-src")
|
||||
} else {
|
||||
Jsoup.parse(
|
||||
String(Base64.decode(opt.attr("value"), Base64.DEFAULT))
|
||||
).select("iframe[data-src~=.]").attr("data-src")
|
||||
}
|
||||
|
||||
when {
|
||||
hosterSelection.contains("animeku") && decoded.contains("animeku.org") -> {
|
||||
videoList.addAll(AnimekuExtractor(client).getVideosFromUrl(decoded, opt.text()))
|
||||
}
|
||||
hosterSelection.contains("mp4upload") && decoded.contains("mp4upload.com") -> {
|
||||
val headers = headers.newBuilder().set("referer", "https://mp4upload.com/").build()
|
||||
videoList.addAll(Mp4uploadExtractor(client).getVideoFromUrl(decoded, headers, opt.text()))
|
||||
}
|
||||
hosterSelection.contains("yourupload") && decoded.contains("yourupload.com") -> {
|
||||
val headers = headers.newBuilder().add("referer", "https://www.yourupload.com/").build()
|
||||
videoList.addAll(YourUploadExtractor(client).videoFromUrl(decoded, headers, opt.text()))
|
||||
}
|
||||
hosterSelection.contains("streamlare") && decoded.contains("streamlare.com") -> {
|
||||
videoList.addAll(StreamlareExtractor(client).videosFromUrl(decoded, opt.text()))
|
||||
}
|
||||
hosterSelection.contains("hxfile") && decoded.contains("hxfile.co") -> {
|
||||
videoList.addAll(HxFileExtractor(client).getVideoFromUrl(decoded, opt.text()))
|
||||
}
|
||||
hosterSelection.contains("linkbox") && decoded.contains("linkbox.to") -> {
|
||||
videoList.addAll(LinkBoxExtractor(client).videosFromUrl(decoded, opt.text()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return zippy
|
||||
return videoList.sort()
|
||||
}
|
||||
|
||||
override fun videoFromElement(element: Element): Video = throw Exception("not used")
|
||||
|
||||
private fun zippyFromElement(element: Element): Video {
|
||||
val res = client.newCall(GET(element.attr("href"))).execute().asJsoup()
|
||||
val scr = res.select("script:containsData(dlbutton)").html()
|
||||
var url = element.attr("href").substringBefore("/v/")
|
||||
val numbs = scr.substringAfter("\" + (").substringBefore(") + \"")
|
||||
val firstString = scr.substringAfter(" = \"").substringBefore("\" + (")
|
||||
val num = numbs.substringBefore(" % ").toInt()
|
||||
val lastString = scr.substringAfter("913) + \"").substringBefore("\";")
|
||||
val nums = num % 51245 + num % 913
|
||||
url += firstString + nums.toString() + lastString
|
||||
val quality = with(lastString) {
|
||||
when {
|
||||
contains("1080p") -> "ZippyShare - 1080p"
|
||||
contains("720p") -> "ZippyShare - 720p"
|
||||
contains("480p") -> "ZippyShare - 480p"
|
||||
contains("360p") -> "ZippyShare - 360p"
|
||||
else -> "ZippyShare - Unknown Resolution"
|
||||
}
|
||||
}
|
||||
return Video(url, quality, url)
|
||||
}
|
||||
|
||||
override fun videoListSelector(): String = throw Exception("not used")
|
||||
|
||||
override fun videoUrlParse(document: Document) = throw Exception("not used")
|
||||
@ -163,11 +191,22 @@ class Kuronime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
||||
}
|
||||
|
||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||
val hostSelection = MultiSelectListPreference(screen.context).apply {
|
||||
key = "hoster_selection"
|
||||
title = "Enable/Disable Hosts"
|
||||
entries = arrayOf("Animeku", "Mp4Upload", "YourUpload", "Streamlare", "Hxfile", "Linkbox")
|
||||
entryValues = arrayOf("animeku", "mp4upload", "yourupload", "streamlare", "hxfile", "linkbox")
|
||||
setDefaultValue(setOf("animeku", "mp4upload", "yourupload", "streamlare", "linkbox"))
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
preferences.edit().putStringSet(key, newValue as Set<String>).commit()
|
||||
}
|
||||
}
|
||||
val videoQualityPref = ListPreference(screen.context).apply {
|
||||
key = "preferred_quality"
|
||||
title = "Preferred quality"
|
||||
entries = arrayOf("1080p", "720p", "480p", "360p")
|
||||
entryValues = arrayOf("1080", "720", "480", "360")
|
||||
entries = arrayOf("1080p", "720p", "480p", "360p", "HD", "SD")
|
||||
entryValues = arrayOf("1080", "720", "480", "360", "HD", "SD")
|
||||
setDefaultValue("1080")
|
||||
summary = "%s"
|
||||
|
||||
@ -178,6 +217,7 @@ class Kuronime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
||||
preferences.edit().putString(key, entry).commit()
|
||||
}
|
||||
}
|
||||
screen.addPreference(hostSelection)
|
||||
screen.addPreference(videoQualityPref)
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,42 @@
|
||||
package eu.kanade.tachiyomi.animeextension.id.kuronime.extractors
|
||||
|
||||
import app.cash.quickjs.QuickJs
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.OkHttpClient
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
@Serializable
|
||||
data class Source(
|
||||
val file: String,
|
||||
val label: String
|
||||
)
|
||||
|
||||
class AnimekuExtractor(private val client: OkHttpClient) {
|
||||
|
||||
private val json: Json by injectLazy()
|
||||
|
||||
fun getVideosFromUrl(url: String, name: String): List<Video> {
|
||||
val document = client.newCall(GET(url)).execute().asJsoup()
|
||||
val script = document.selectFirst("script:containsData(decodeURIComponent)") ?: return emptyList()
|
||||
|
||||
val quickJs = QuickJs.create()
|
||||
val decryped = quickJs.evaluate(
|
||||
script.data().trim().split("\n")[0].replace("eval(function", "function a").replace("decodeURIComponent(escape(r))}(", "r};a(").substringBeforeLast(")")
|
||||
).toString()
|
||||
quickJs.close()
|
||||
|
||||
val srcs = json.decodeFromString<List<Source>>(decryped.substringAfter("var srcs = ").substringBefore(";"))
|
||||
return srcs.map { src ->
|
||||
Video(
|
||||
src.file,
|
||||
"${src.label} - $name",
|
||||
src.file
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package eu.kanade.tachiyomi.animeextension.id.kuronime.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.Headers
|
||||
import okhttp3.OkHttpClient
|
||||
|
||||
class HxFileExtractor(private val client: OkHttpClient) {
|
||||
fun getVideoFromUrl(url: String, name: String): List<Video> {
|
||||
val document = client.newCall(GET(url)).execute().asJsoup()
|
||||
val packed = document.selectFirst("script:containsData(eval\\(function\\()").data()
|
||||
val unpacked = JsUnpacker.unpackAndCombine(packed) ?: return emptyList()
|
||||
val videoUrl = unpacked.substringAfter("\"type\":\"video").substringAfter("\"file\":\"").substringBefore("\"")
|
||||
return listOf(
|
||||
Video(videoUrl, "Original - $name", videoUrl, headers = Headers.headersOf("Referer", "https://hxfile.co/"))
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package eu.kanade.tachiyomi.animeextension.id.kuronime.extractors
|
||||
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.jsonArray
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import okhttp3.OkHttpClient
|
||||
|
||||
class LinkBoxExtractor(private val client: OkHttpClient) {
|
||||
fun videosFromUrl(url: String, name: String): List<Video> {
|
||||
val videoList = mutableListOf<Video>()
|
||||
val id = if (url.contains("/file/")) {
|
||||
url.substringAfter("/file/")
|
||||
} else {
|
||||
url.substringAfter("?id=")
|
||||
}
|
||||
val request = client.newCall(GET("https://www.linkbox.to/api/open/get_url?itemId=$id")).execute().asJsoup()
|
||||
val responseJson = Json.decodeFromString<JsonObject>(request.select("body").text())
|
||||
val data = responseJson["data"]?.jsonObject
|
||||
val resolutions = data!!.jsonObject["rList"]!!.jsonArray
|
||||
resolutions.map {
|
||||
videoList.add(
|
||||
Video(
|
||||
it.jsonObject["url"].toString().replace("\"", ""),
|
||||
"${it.jsonObject["resolution"].toString().replace("\"", "")} - $name",
|
||||
it.jsonObject["url"].toString().replace("\"", "")
|
||||
)
|
||||
)
|
||||
}
|
||||
return videoList
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package eu.kanade.tachiyomi.animeextension.id.kuronime.extractors
|
||||
|
||||
import dev.datlag.jsunpacker.JsUnpacker
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import okhttp3.Headers
|
||||
import okhttp3.OkHttpClient
|
||||
|
||||
class Mp4uploadExtractor(private val client: OkHttpClient) {
|
||||
fun getVideoFromUrl(url: String, headers: Headers, name: String): List<Video> {
|
||||
val body = client.newCall(GET(url, headers = headers)).execute().body!!.string()
|
||||
|
||||
val packed = body.substringAfter("<script type='text/javascript'>eval(function(p,a,c,k,e,d)")
|
||||
.substringBefore("</script>")
|
||||
val unpacked = JsUnpacker.unpackAndCombine("eval(function(p,a,c,k,e,d)" + packed) ?: return emptyList()
|
||||
val videoUrl = unpacked.substringAfter("player.src(\"").substringBefore("\");")
|
||||
return listOf(
|
||||
Video(videoUrl, "Original - $name", videoUrl, headers = Headers.headersOf("Referer", "https://www.mp4upload.com/"))
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package eu.kanade.tachiyomi.animeextension.id.kuronime.extractors
|
||||
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
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, name: String): List<Video> {
|
||||
val id = url.split("/").last()
|
||||
val videoList = mutableListOf<Video>()
|
||||
val playlist = client.newCall(
|
||||
POST(
|
||||
"https://slwatch.co/api/video/stream/get",
|
||||
body = "{\"id\":\"$id\"}"
|
||||
.toRequestBody("application/json".toMediaType())
|
||||
)
|
||||
).execute().body!!.string()
|
||||
|
||||
playlist.substringAfter("\"label\":\"").split("\"label\":\"").forEach {
|
||||
val quality = it.substringAfter("\"label\":\"").substringBefore("\",") + " - $name"
|
||||
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.add(Video(videoUrl, quality, videoUrl))
|
||||
}
|
||||
return videoList
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package eu.kanade.tachiyomi.animeextension.id.kuronime.extractors
|
||||
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import okhttp3.Headers
|
||||
import okhttp3.OkHttpClient
|
||||
|
||||
class YourUploadExtractor(private val client: OkHttpClient) {
|
||||
fun videoFromUrl(url: String, headers: Headers, name: String): List<Video> {
|
||||
val videoList = mutableListOf<Video>()
|
||||
return try {
|
||||
val document = client.newCall(GET(url, headers = headers)).execute()
|
||||
if (document.isSuccessful) {
|
||||
val content = document.asJsoup()
|
||||
val baseData =
|
||||
content!!.selectFirst("script:containsData(jwplayerOptions)")!!.data()
|
||||
if (!baseData.isNullOrEmpty()) {
|
||||
val basicUrl = baseData.substringAfter("file: '").substringBefore("',")
|
||||
videoList.add(Video(basicUrl, "Original - $name", basicUrl, headers = headers))
|
||||
}
|
||||
}
|
||||
videoList
|
||||
} catch (e: Exception) {
|
||||
videoList
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user