[skip ci] refactor(lib): Refactor some libraries (#2454)

This commit is contained in:
Claudemirovsky 2023-11-01 10:44:08 -03:00 committed by GitHub
parent e3afbe20eb
commit 9db910eca1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 152 additions and 329 deletions

View File

@ -6,7 +6,7 @@ plugins {
} }
tasks.register<Delete>("clean") { tasks.register<Delete>("clean") {
delete(rootProject.buildDir) delete(rootProject.layout.buildDirectory.asFile.get())
} }
allprojects { allprojects {

View File

@ -26,7 +26,7 @@ class BloggerExtractor(private val client: OkHttpClient) {
"37" -> "1080p" "37" -> "1080p"
else -> "Unknown" else -> "Unknown"
} }
Video(videoUrl, "Blogger - $quality $suffix".trim(), videoUrl, headers) Video(videoUrl, "Blogger - $quality $suffix".trimEnd(), videoUrl, headers)
} }
} }
} }

View File

@ -15,19 +15,23 @@ class BurstCloudExtractor(private val client: OkHttpClient) {
private val json: Json by injectLazy() private val json: Json by injectLazy()
fun videoFromUrl(url: String, headers: Headers, name: String = "BurstCloud", prefix: String = ""): List<Video> { fun videoFromUrl(url: String, headers: Headers, name: String = "BurstCloud", prefix: String = ""): List<Video> {
val newHeaders = headers.newBuilder().set("referer", BURSTCLOUD_URL).build()
val newHeaders = headers.newBuilder().add("referer", "https://www.burstcloud.co/").build()
return runCatching { return runCatching {
val response = client.newCall(GET(url, headers = newHeaders)).execute() val response = client.newCall(GET(url, newHeaders)).execute()
val document = response.asJsoup() val document = response.use { it.asJsoup() }
val videoId = document.selectFirst("div#player")!!.attr("data-file-id") val videoId = document.selectFirst("div#player")!!.attr("data-file-id")
val formBody = FormBody.Builder() val formBody = FormBody.Builder()
.add("fileId", videoId) .add("fileId", videoId)
.build() .build()
val jsonHeaders = headers.newBuilder().add("referer", document.location()).build()
val jsonString = client.newCall(POST("https://www.burstcloud.co/file/play-request/", jsonHeaders, formBody)).execute().body.string() val jsonHeaders = headers.newBuilder().set("referer", document.location()).build()
val request = POST("$BURSTCLOUD_URL/file/play-request/", jsonHeaders, formBody)
val jsonString = client.newCall(request).execute().use { it.body.string() }
val jsonObj = json.decodeFromString<BurstCloudDto>(jsonString) val jsonObj = json.decodeFromString<BurstCloudDto>(jsonString)
val videoUrl = jsonObj.purchase.cdnUrl val videoUrl = jsonObj.purchase.cdnUrl
if (videoUrl.isNotEmpty()) { if (videoUrl.isNotEmpty()) {
val quality = prefix + name val quality = prefix + name
listOf(Video(videoUrl, quality, videoUrl, newHeaders)) listOf(Video(videoUrl, quality, videoUrl, newHeaders))
@ -35,6 +39,8 @@ class BurstCloudExtractor(private val client: OkHttpClient) {
null null
} }
}.getOrNull() ?: emptyList<Video>() }.getOrNull().orEmpty()
} }
} }
private const val BURSTCLOUD_URL = "https://www.burstcloud.co"

View File

@ -3,11 +3,7 @@ package eu.kanade.tachiyomi.lib.burstcloudextractor
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@Serializable @Serializable
data class BurstCloudDto( data class BurstCloudDto(val purchase: Purchase)
val purchase: Purchase,
)
@Serializable @Serializable
data class Purchase( data class Purchase(val cdnUrl: String)
val cdnUrl: String,
)

View File

@ -13,6 +13,9 @@ android {
} }
dependencies { dependencies {
compileOnly(libs.bundles.common) implementation("dev.datlag.jsunpacker:jsunpacker:1.0.1") {
exclude(group = "org.jetbrains.kotlin", module = "kotlin-stdlib-jdk8")
}
implementation(project(":lib-playlist-utils")) implementation(project(":lib-playlist-utils"))
compileOnly(libs.bundles.common)
} }

View File

@ -3,44 +3,61 @@ package eu.kanade.tachiyomi.lib.fastreamextractor
import eu.kanade.tachiyomi.animesource.model.Video import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.lib.playlistutils.PlaylistUtils import eu.kanade.tachiyomi.lib.playlistutils.PlaylistUtils
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import okhttp3.FormBody
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.json.Json
import okhttp3.Headers import okhttp3.Headers
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import uy.kohesive.injekt.injectLazy import okhttp3.internal.commonEmptyHeaders
import dev.datlag.jsunpacker.JsUnpacker
class FastreamExtractor(private val client: OkHttpClient) { class FastreamExtractor(private val client: OkHttpClient, private val headers: Headers = commonEmptyHeaders) {
private val json: Json by injectLazy() private val videoHeaders by lazy {
private fun fetchUrls(text: String?): List<String> { headers.newBuilder()
if (text.isNullOrEmpty()) return listOf() .set("Referer", "$FASTREAM_URL/")
val linkRegex = "(http|ftp|https):\\/\\/([\\w_-]+(?:(?:\\.[\\w_-]+)+))([\\w.,@?^=%&:\\/~+#-]*[\\w@?^=%&\\/~+#-])".toRegex() .set("Origin", FASTREAM_URL)
return linkRegex.findAll(text).map { it.value.trim().removeSurrounding("\"") }.toList()
}
fun videoFromUrl(url: String, prefix: String = "Fastream:", headers: Headers? = null): List<Video> {
val videoList = mutableListOf<Video>()
try {
val document = client.newCall(GET(url)).execute().asJsoup()
val videoHeaders = (headers?.newBuilder() ?: Headers.Builder())
.set("Referer", "https://fastream.to/")
.set("Origin", "https://fastream.to")
.build() .build()
document.select("script").forEach {
if (it!!.data().contains("jwplayer(jwplayer(\"vplayer\").setup({")) {
val basicUrl = it.data().substringAfter("file: '").substringBefore("',")
videoList.add(Video(basicUrl, prefix, basicUrl, headers = videoHeaders))
} else {
val packedRegex = "eval\\(function\\(p,a,c,k,e,.*\\)\\)".toRegex()
packedRegex.findAll(it.data()).map { packed -> packed.value }.toList().map { eval ->
val unpack = JsUnpacker.unpack(eval)
val serverRegex = "fastream.*?\\.m3u8([^&\">]?)".toRegex()
fetchUrls(unpack.first()).filter { serverRegex.containsMatchIn(it) }.map { url ->
PlaylistUtils(client, videoHeaders).extractFromHls(url, videoNameGen = { "$prefix$it" }).let { videoList.addAll(it) }
} }
private val playlistUtils by lazy { PlaylistUtils(client, videoHeaders) }
fun videosFromUrl(url: String, prefix: String = "Fastream:", needsSleep: Boolean = true): List<Video> {
return runCatching {
val firstDoc = client.newCall(GET(url, videoHeaders)).execute().use { it.asJsoup() }
val form = FormBody.Builder().apply {
firstDoc.select("input[name]").forEach {
add(it.attr("name"), it.attr("value"))
} }
}.build()
if (needsSleep) Thread.sleep(5100L) // 5s is the minimum
val doc = client.newCall(POST(url, videoHeaders, body = form)).execute()
.use { it.asJsoup() }
val scriptElement = doc.selectFirst("script:containsData(jwplayer):containsData(vplayer)")
?: return emptyList()
val scriptData = scriptElement.data().let {
when {
it.contains("eval(function(") -> JsUnpacker.unpackAndCombine(it)
else -> it
} }
} ?: return emptyList()
val videoUrl = scriptData.substringAfter("file:")
.substringBefore('}')
.substringBefore(',')
.trim('"', '\'', ' ')
return when {
videoUrl.contains(".m3u8") -> {
playlistUtils.extractFromHls(videoUrl, videoNameGen = { "$prefix$it" })
} }
} catch (_: Exception) {} else -> listOf(Video(videoUrl, prefix, videoUrl, videoHeaders))
return videoList }
}.getOrElse { emptyList() }
} }
} }
private const val FASTREAM_URL = "https://fastream.to"

View File

@ -1,193 +0,0 @@
package eu.kanade.tachiyomi.lib.fastreamextractor
import kotlin.math.pow
object JsUnpacker {
/**
* Regex to detect packed functions.
*/
private val PACKED_REGEX = Regex("eval[(]function[(]p,a,c,k,e,[r|d]?", setOf(RegexOption.IGNORE_CASE, RegexOption.MULTILINE))
/**
* Regex to get and group the packed javascript.
* Needed to get information and unpack the code.
*/
private val PACKED_EXTRACT_REGEX = Regex("[}][(]'(.*)', *(\\d+), *(\\d+), *'(.*?)'[.]split[(]'[|]'[)]", setOf(RegexOption.IGNORE_CASE, RegexOption.MULTILINE))
/**
* Matches function names and variables to de-obfuscate the code.
*/
private val UNPACK_REPLACE_REGEX = Regex("\\b\\w+\\b", setOf(RegexOption.IGNORE_CASE, RegexOption.MULTILINE))
/**
* Check if script is packed.
*
* @param scriptBlock the String to check if it is packed.
*
* @return whether the [scriptBlock] contains packed code or not.
*/
fun detect(scriptBlock: String): Boolean {
return scriptBlock.contains(PACKED_REGEX)
}
/**
* Check if scripts are packed.
*
* @param scriptBlock (multiple) String(s) to check if it is packed.
*
* @return the packed scripts passed in [scriptBlock].
*/
fun detect(vararg scriptBlock: String): List<String> {
return scriptBlock.mapNotNull {
if (it.contains(PACKED_REGEX)) {
it
} else {
null
}
}
}
/**
* Check if scripts are packed.
*
* @param scriptBlocks multiple Strings to check if it is packed.
*
* @return the packed scripts passed in [scriptBlocks].
*/
fun detect(scriptBlocks: Collection<String>): List<String> {
return detect(*scriptBlocks.toTypedArray())
}
/**
* Unpack the passed [scriptBlock].
* It matches all found occurrences and returns them as separate Strings in a list.
*
* @param scriptBlock the String to unpack.
*
* @return unpacked code in a list or an empty list if non is packed.
*/
fun unpack(scriptBlock: String): Sequence<String> {
return if (!detect(scriptBlock)) {
emptySequence()
} else {
unpacking(scriptBlock)
}
}
/**
* Unpack the passed [scriptBlock].
* It matches all found occurrences and combines them into a single String.
*
* @param scriptBlock the String to unpack.
*
* @return unpacked code in a list combined by a whitespace to a single String.
*/
fun unpackAndCombine(scriptBlock: String): String? {
val unpacked = unpack(scriptBlock)
return if (unpacked.toList().isEmpty()) {
null
} else {
unpacked.joinToString(" ")
}
}
/**
* Unpack the passed [scriptBlock].
* It matches all found occurrences and returns them as separate Strings in a list.
*
* @param scriptBlock (multiple) String(s) to unpack.
*
* @return unpacked code in a flat list or an empty list if non is packed.
*/
fun unpack(vararg scriptBlock: String): List<String> {
val packedScripts = detect(*scriptBlock)
return packedScripts.flatMap {
unpacking(it)
}
}
/**
* Unpack the passed [scriptBlocks].
* It matches all found occurrences and returns them as separate Strings in a list.
*
* @param scriptBlocks multiple Strings to unpack.
*
* @return unpacked code in a flat list or an empty list if non is packed.
*/
fun unpack(scriptBlocks: Collection<String>): List<String> {
return unpack(*scriptBlocks.toTypedArray())
}
/**
* Unpacking functionality.
* Match all found occurrences, get the information group and unbase it.
* If found symtabs are more or less than the count provided in code, the occurrence will be ignored
* because it cannot be unpacked correctly.
*
* @param scriptBlock the String to unpack.
*
* @return a list of all unpacked code from all found packed and unpackable occurrences found.
*/
private fun unpacking(scriptBlock: String): Sequence<String> {
val unpacked = PACKED_EXTRACT_REGEX.findAll(scriptBlock).mapNotNull { result ->
val payload = result.groups[1]?.value
val symtab = result.groups[4]?.value?.split('|')
val radix = result.groups[2]?.value?.toIntOrNull() ?: 10
val count = result.groups[3]?.value?.toIntOrNull()
val unbaser = Unbaser(radix)
if (symtab == null || count == null || symtab.size != count) {
null
} else {
payload?.replace(UNPACK_REPLACE_REGEX) { match ->
val word = match.value
val unbased = symtab[unbaser.unbase(word)]
unbased.ifEmpty {
word
}
}
}
}
return unpacked
}
internal data class Unbaser(
private val base: Int,
) {
private val selector: Int = when {
base > 62 -> 95
base > 54 -> 62
base > 52 -> 54
else -> 52
}
fun unbase(value: String): Int {
return if (base in 2..36) {
value.toIntOrNull(base) ?: 0
} else {
val dict = ALPHABET[selector]?.toCharArray()?.mapIndexed { index, c ->
c to index
}?.toMap()
var returnVal = 0
val valArray = value.toCharArray().reversed()
for (i in valArray.indices) {
val cipher = valArray[i]
returnVal += (base.toFloat().pow(i) * (dict?.get(cipher) ?: 0)).toInt()
}
returnVal
}
}
companion object {
private val ALPHABET = mapOf<Int, String>(
52 to "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOP",
54 to "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQR",
62 to "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
95 to " !\"#\$%&\\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~",
)
}
}
}

View File

@ -17,5 +17,6 @@ dependencies {
implementation("dev.datlag.jsunpacker:jsunpacker:1.0.1") { implementation("dev.datlag.jsunpacker:jsunpacker:1.0.1") {
exclude(group = "org.jetbrains.kotlin", module = "kotlin-stdlib-jdk8") exclude(group = "org.jetbrains.kotlin", module = "kotlin-stdlib-jdk8")
} }
implementation(project(":lib-playlist-utils"))
compileOnly(libs.bundles.common) compileOnly(libs.bundles.common)
} }

View File

@ -7,6 +7,7 @@ import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import eu.kanade.tachiyomi.lib.playlistutils.PlaylistUtils
import okhttp3.Headers import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
@ -15,9 +16,15 @@ import uy.kohesive.injekt.injectLazy
class FilemoonExtractor(private val client: OkHttpClient) { class FilemoonExtractor(private val client: OkHttpClient) {
private val json: Json by injectLazy() private val json: Json by injectLazy()
fun videosFromUrl(url: String, prefix: String = "Filemoon - ", headers: Headers? = null, useHeadersForHtml: Boolean = false): List<Video> { fun videosFromUrl(url: String, prefix: String = "Filemoon - ", headers: Headers? = null): List<Video> {
return runCatching { return runCatching {
val doc = if (useHeadersForHtml) client.newCall(GET(url, headers = (headers?.newBuilder() ?: Headers.Builder()).build())).execute().asJsoup() else client.newCall(GET(url)).execute().asJsoup() val httpUrl = url.toHttpUrl()
val videoHeaders = (headers?.newBuilder() ?: Headers.Builder())
.set("Referer", url)
.set("Origin", "https://${httpUrl.host}")
.build()
val doc = client.newCall(GET(url, videoHeaders)).execute().use { it.asJsoup() }
val jsEval = doc.selectFirst("script:containsData(eval):containsData(m3u8)")!!.data() val jsEval = doc.selectFirst("script:containsData(eval):containsData(m3u8)")!!.data()
val unpacked = JsUnpacker.unpackAndCombine(jsEval).orEmpty() val unpacked = JsUnpacker.unpackAndCombine(jsEval).orEmpty()
val masterUrl = unpacked.takeIf(String::isNotBlank) val masterUrl = unpacked.takeIf(String::isNotBlank)
@ -26,14 +33,6 @@ class FilemoonExtractor(private val client: OkHttpClient) {
?.takeIf(String::isNotBlank) ?.takeIf(String::isNotBlank)
?: return emptyList() ?: return emptyList()
val masterPlaylist = client.newCall(GET(masterUrl)).execute().body.string()
val httpUrl = url.toHttpUrl()
val videoHeaders = (headers?.newBuilder() ?: Headers.Builder())
.set("Referer", url)
.set("Origin", "https://${httpUrl.host}")
.build()
val subtitleTracks = buildList { val subtitleTracks = buildList {
// Subtitles from a external URL // Subtitles from a external URL
val subUrl = httpUrl.queryParameter("sub.info") val subUrl = httpUrl.queryParameter("sub.info")
@ -48,48 +47,16 @@ class FilemoonExtractor(private val client: OkHttpClient) {
.forEach { add(Track(it.file, it.label)) } .forEach { add(Track(it.file, it.label)) }
} }
} }
SUBTITLES_REGEX // Subtitles from the playlist
.findAll(masterPlaylist)
.forEach { add(Track(it.groupValues[2], it.groupValues[1])) }
} }
val audioTracks = AUDIO_REGEX
.findAll(masterPlaylist)
.map { Track(it.groupValues[2], it.groupValues[1]) }
.toList()
val separator = "#EXT-X-STREAM-INF:" PlaylistUtils(client, videoHeaders).extractFromHls(
masterPlaylist.substringAfter(separator).split(separator).map { masterUrl,
val resolution = it.substringAfter("RESOLUTION=") subtitleList = subtitleTracks,
.substringAfter("x") videoNameGen = { "$prefix$it" },
.substringBefore(",") + "p"
val videoUrl = it.substringAfter("\n").substringBefore("\n")
Video(
videoUrl,
prefix + resolution,
videoUrl,
headers = videoHeaders,
subtitleTracks = subtitleTracks,
audioTracks = audioTracks,
) )
}
}.getOrElse { emptyList() } }.getOrElse { emptyList() }
} }
@Serializable @Serializable
data class SubtitleDto( data class SubtitleDto(val file: String, val label: String)
val file: String,
val label: String,
)
companion object {
private val SUBTITLES_REGEX by lazy {
Regex("""#EXT-X-MEDIA:TYPE=SUBTITLES.*?NAME="(.*?)".*?URI="(.*?)"""")
}
private val AUDIO_REGEX by lazy {
Regex("""#EXT-X-MEDIA:TYPE=AUDIO.*?NAME="(.*?)".*?URI="(.*?)"""")
}
}
} }

View File

@ -6,13 +6,18 @@ import eu.kanade.tachiyomi.network.GET
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
class StreamHubExtractor(private val client: OkHttpClient) { class StreamHubExtractor(private val client: OkHttpClient) {
private val playlistUtils by lazy { PlaylistUtils(client) }
fun videosFromUrl(url: String, prefix: String = ""): List<Video> { fun videosFromUrl(url: String, prefix: String = ""): List<Video> {
val document = client.newCall(GET(url)).execute().use { it.body.string() } val document = client.newCall(GET(url)).execute().use { it.body.string() }
val id = Regex("urlset\\|(.*?)\\|").find(document)?.groupValues?.get(1) val id = REGEX_ID.find(document)?.groupValues?.get(1)
val sub = Regex("width\\|(.*?)\\|").find(document)?.groupValues?.get(1) val sub = REGEX_SUB.find(document)?.groupValues?.get(1)
val masterUrl = "https://${sub}.streamhub.ink/hls/,${id},.urlset/master.m3u8" val masterUrl = "https://${sub}.streamhub.ink/hls/,${id},.urlset/master.m3u8"
return PlaylistUtils(client).extractFromHls(masterUrl, videoNameGen = { "${prefix}StreamHub - (${it})" }) return playlistUtils.extractFromHls(masterUrl, videoNameGen = { "${prefix}StreamHub - (${it})" })
} }
companion object {
private val REGEX_ID = Regex("urlset\\|(.*?)\\|")
private val REGEX_SUB = Regex("width\\|(.*?)\\|")
}
} }

View File

@ -14,7 +14,8 @@ class StreamTapeExtractor(private val client: OkHttpClient) {
val id = url.split("/").getOrNull(4) ?: return null val id = url.split("/").getOrNull(4) ?: return null
baseUrl + id baseUrl + id
} else { url } } else { url }
val document = client.newCall(GET(newUrl)).execute().asJsoup()
val document = client.newCall(GET(newUrl)).execute().use { it.asJsoup() }
val targetLine = "document.getElementById('robotlink')" val targetLine = "document.getElementById('robotlink')"
val script = document.selectFirst("script:containsData($targetLine)") val script = document.selectFirst("script:containsData($targetLine)")
?.data() ?.data()
@ -22,6 +23,11 @@ class StreamTapeExtractor(private val client: OkHttpClient) {
?: return null ?: return null
val videoUrl = "https:" + script.substringBefore("'") + val videoUrl = "https:" + script.substringBefore("'") +
script.substringAfter("+ ('xcd").substringBefore("'") script.substringAfter("+ ('xcd").substringBefore("'")
return Video(videoUrl, quality, videoUrl, subtitleTracks = subtitleList) return Video(videoUrl, quality, videoUrl, subtitleTracks = subtitleList)
} }
fun videosFromUrl(url: String, quality: String = "StreamTape", subtitleList: List<Track> = emptyList()): List<Video> {
return videoFromUrl(url, quality, subtitleList)?.let(::listOf).orEmpty()
}
} }

View File

@ -6,12 +6,17 @@ import eu.kanade.tachiyomi.network.GET
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
class VidoExtractor(private val client: OkHttpClient) { class VidoExtractor(private val client: OkHttpClient) {
companion object {
private const val VIDO_URL = "https://pink.vido.lol"
private val REGEX_ID = Regex("master\\|(.*?)\\|")
}
private val playlistUtils by lazy { PlaylistUtils(client) }
fun videosFromUrl(url: String, prefix: String = ""): List<Video> { fun videosFromUrl(url: String, prefix: String = ""): List<Video> {
val document = client.newCall(GET(url)).execute().use { it.body.string() } val document = client.newCall(GET(url)).execute().use { it.body.string() }
val id = Regex("master\\|(.*?)\\|").find(document)?.groupValues?.get(1) val id = REGEX_ID.find(document)?.groupValues?.get(1)
val masterUrl = "https://pink.vido.lol/hls/${id}/master.m3u8" val masterUrl = "$VIDO_URL/hls/${id}/master.m3u8"
return PlaylistUtils(client).extractFromHls(masterUrl, videoNameGen = { "${prefix}Vido - (${it})" }) return playlistUtils.extractFromHls(masterUrl, videoNameGen = { "${prefix}Vido - (${it})" })
} }
} }

View File

@ -7,27 +7,33 @@ import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
class VkExtractor(private val client: OkHttpClient, private val headers: Headers) { class VkExtractor(private val client: OkHttpClient, private val headers: Headers) {
fun videosFromUrl(url: String, prefix: String = ""): List<Video> { private val documentHeaders by lazy {
val documentHeaders = headers.newBuilder() headers.newBuilder()
.add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8") .add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8")
.add("Host", "vk.com")
.build() .build()
}
val data = client.newCall( private val videoHeaders by lazy {
GET(url, headers = documentHeaders), headers.newBuilder()
).execute().body.string() .add("Accept", "*/*")
.add("Origin", VK_URL)
.add("Referer", "$VK_URL/")
.build()
}
val videoRegex = """"url(\d+)":"(.*?)"""".toRegex() fun videosFromUrl(url: String, prefix: String = ""): List<Video> {
return videoRegex.findAll(data).map { val data = client.newCall(GET(url, documentHeaders)).execute()
.use { it.body.string() }
return REGEX_VIDEO.findAll(data).map {
val quality = it.groupValues[1] val quality = it.groupValues[1]
val videoUrl = it.groupValues[2].replace("\\/", "/") val videoUrl = it.groupValues[2].replace("\\/", "/")
val videoHeaders = headers.newBuilder() Video(videoUrl, "${prefix}vk.com - ${quality}p", videoUrl, videoHeaders)
.add("Accept", "*/*")
.add("Host", videoUrl.toHttpUrl().host)
.add("Origin", "https://vk.com")
.add("Referer", "https://vk.com/")
.build()
Video(videoUrl, "${prefix}vk.com - ${quality}p", videoUrl, headers = videoHeaders)
}.toList() }.toList()
} }
companion object {
private const val VK_URL = "https://vk.com"
private val REGEX_VIDEO = """"url(\d+)":"(.*?)"""".toRegex()
}
} }

View File

@ -7,8 +7,8 @@ import okhttp3.OkHttpClient
class VoeExtractor(private val client: OkHttpClient) { class VoeExtractor(private val client: OkHttpClient) {
fun videoFromUrl(url: String, quality: String? = null, prefix: String = ""): Video? { fun videoFromUrl(url: String, quality: String? = null, prefix: String = ""): Video? {
val document = client.newCall(GET(url)).execute().asJsoup() val document = client.newCall(GET(url)).execute().use { it.asJsoup() }
val script = document.selectFirst("script:containsData(const sources),script:containsData(var sources)") val script = document.selectFirst("script:containsData(const sources), script:containsData(var sources)")
?.data() ?.data()
?: return null ?: return null
val videoUrl = script.substringAfter("hls': '").substringBefore("'") val videoUrl = script.substringAfter("hls': '").substringBefore("'")
@ -19,4 +19,8 @@ class VoeExtractor(private val client: OkHttpClient) {
} }
return Video(url, qualityStr, videoUrl) return Video(url, qualityStr, videoUrl)
} }
fun videosFromUrl(url: String, quality: String? = null, prefix: String = ""): List<Video> {
return videoFromUrl(url, quality, prefix)?.let(::listOf).orEmpty()
}
} }

View File

@ -61,7 +61,7 @@ class Animefenix : ConfigurableAnimeSource, AnimeHttpSource() {
"YourUpload", "Voe", "Mp4Upload", "Doodstream", "YourUpload", "Voe", "Mp4Upload", "Doodstream",
"Upload", "BurstCloud", "Upstream", "StreamTape", "Upload", "BurstCloud", "Upstream", "StreamTape",
"Fastream", "Filemoon", "StreamWish", "Okru", "Fastream", "Filemoon", "StreamWish", "Okru",
"Amazon", "AmazonES", "Fireload", "FileLions" "Amazon", "AmazonES", "Fireload", "FileLions",
) )
} }
@ -210,7 +210,7 @@ class Animefenix : ConfigurableAnimeSource, AnimeHttpSource() {
BurstCloudExtractor(client).videoFromUrl(url, headers = headers).let { videoList.addAll(it) } BurstCloudExtractor(client).videoFromUrl(url, headers = headers).let { videoList.addAll(it) }
} }
if (embedUrl.contains("fastream")) { if (embedUrl.contains("fastream")) {
FastreamExtractor(client).videoFromUrl(url).let { videoList.addAll(it) } FastreamExtractor(client, headers).videosFromUrl(url).also(videoList::addAll)
} }
if (embedUrl.contains("upstream")) { if (embedUrl.contains("upstream")) {
UpstreamExtractor(client).videosFromUrl(url).let { videoList.addAll(it) } UpstreamExtractor(client).videosFromUrl(url).let { videoList.addAll(it) }

View File

@ -69,7 +69,7 @@ class AnimeMovil : ConfigurableAnimeSource, AnimeHttpSource() {
"PlusTube", "PlusVid", "PlusIm", "PlusWish", "PlusHub", "PlusDex", "PlusTube", "PlusVid", "PlusIm", "PlusWish", "PlusHub", "PlusDex",
"YourUpload", "Voe", "StreamWish", "Mp4Upload", "Doodstream", "YourUpload", "Voe", "StreamWish", "Mp4Upload", "Doodstream",
"Uqload", "BurstCloud", "Upstream", "StreamTape", "PlusFilm", "Uqload", "BurstCloud", "Upstream", "StreamTape", "PlusFilm",
"Fastream", "FileLions" "Fastream", "FileLions",
) )
} }
@ -268,7 +268,7 @@ class AnimeMovil : ConfigurableAnimeSource, AnimeHttpSource() {
BurstCloudExtractor(client).videoFromUrl(url, headers = headers).also(videoList::addAll) BurstCloudExtractor(client).videoFromUrl(url, headers = headers).also(videoList::addAll)
} }
if (embedUrl.contains("fastream")) { if (embedUrl.contains("fastream")) {
FastreamExtractor(client).videoFromUrl(url).also(videoList::addAll) FastreamExtractor(client, headers).videosFromUrl(url).also(videoList::addAll)
} }
if (embedUrl.contains("upstream")) { if (embedUrl.contains("upstream")) {
UpstreamExtractor(client).videosFromUrl(url).also(videoList::addAll) UpstreamExtractor(client).videosFromUrl(url).also(videoList::addAll)

View File

@ -96,7 +96,7 @@ class Animeyt : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
val key = url.split("/").last() val key = url.split("/").last()
url = "https://fastream.to/embed-$key.html" url = "https://fastream.to/embed-$key.html"
} }
FastreamExtractor(client).videoFromUrl(url).forEach { videoList.add(it) } FastreamExtractor(client, headers).videosFromUrl(url).also(videoList::addAll)
} }
} }
return videoList return videoList

View File

@ -218,7 +218,7 @@ class AsiaLiveAction : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
BurstCloudExtractor(client).videoFromUrl(url, headers = headers).let { videoList.addAll(it) } BurstCloudExtractor(client).videoFromUrl(url, headers = headers).let { videoList.addAll(it) }
} }
if (embedUrl.contains("fastream")) { if (embedUrl.contains("fastream")) {
FastreamExtractor(client).videoFromUrl(url).forEach { videoList.add(it) } FastreamExtractor(client, headers).videosFromUrl(url).also(videoList::addAll)
} }
if (embedUrl.contains("upstream")) { if (embedUrl.contains("upstream")) {
UpstreamExtractor(client).videosFromUrl(url).let { videoList.addAll(it) } UpstreamExtractor(client).videosFromUrl(url).let { videoList.addAll(it) }

View File

@ -218,7 +218,7 @@ class CuevanaCh(override val name: String, override val baseUrl: String) : Confi
BurstCloudExtractor(client).videoFromUrl(url, headers = headers, prefix = prefix).let { videoList.addAll(it) } BurstCloudExtractor(client).videoFromUrl(url, headers = headers, prefix = prefix).let { videoList.addAll(it) }
} }
if (embedUrl.contains("fastream")) { if (embedUrl.contains("fastream")) {
FastreamExtractor(client).videoFromUrl(url, prefix = "$prefix Fastream:").forEach { videoList.add(it) } FastreamExtractor(client, headers).videosFromUrl(url, prefix = "$prefix Fastream:").also(videoList::addAll)
} }
if (embedUrl.contains("upstream")) { if (embedUrl.contains("upstream")) {
UpstreamExtractor(client).videosFromUrl(url, prefix = prefix).let { videoList.addAll(it) } UpstreamExtractor(client).videosFromUrl(url, prefix = prefix).let { videoList.addAll(it) }

View File

@ -426,7 +426,7 @@ class Doramasflix : ConfigurableAnimeSource, AnimeHttpSource() {
BurstCloudExtractor(client).videoFromUrl(url, headers = headers, prefix = "$prefix ").let { videoList.addAll(it) } BurstCloudExtractor(client).videoFromUrl(url, headers = headers, prefix = "$prefix ").let { videoList.addAll(it) }
} }
if (embedUrl.contains("fastream")) { if (embedUrl.contains("fastream")) {
FastreamExtractor(client).videoFromUrl(url, prefix = "$prefix Fastream:").forEach { videoList.add(it) } FastreamExtractor(client, headers).videosFromUrl(url, prefix = "$prefix Fastream:").also(videoList::addAll)
} }
if (embedUrl.contains("upstream")) { if (embedUrl.contains("upstream")) {
UpstreamExtractor(client).videosFromUrl(url, prefix = "$prefix ").let { videoList.addAll(it) } UpstreamExtractor(client).videosFromUrl(url, prefix = "$prefix ").let { videoList.addAll(it) }

View File

@ -272,7 +272,7 @@ class Gnula : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
BurstCloudExtractor(client).videoFromUrl(url, headers = headers, prefix = prefix).let { videoList.addAll(it) } BurstCloudExtractor(client).videoFromUrl(url, headers = headers, prefix = prefix).let { videoList.addAll(it) }
} }
if (embedUrl.contains("fastream")) { if (embedUrl.contains("fastream")) {
FastreamExtractor(client).videoFromUrl(url, prefix = "$prefix Fastream:").forEach { videoList.add(it) } FastreamExtractor(client, headers).videosFromUrl(url, prefix = "$prefix Fastream:").also(videoList::addAll)
} }
if (embedUrl.contains("upstream")) { if (embedUrl.contains("upstream")) {
UpstreamExtractor(client).videosFromUrl(url, prefix = prefix).let { videoList.addAll(it) } UpstreamExtractor(client).videosFromUrl(url, prefix = prefix).let { videoList.addAll(it) }

View File

@ -218,7 +218,7 @@ class MetroSeries : ConfigurableAnimeSource, AnimeHttpSource() {
val key = src.split("/").last() val key = src.split("/").last()
src = "https://fastream.to/embed-$key.html" src = "https://fastream.to/embed-$key.html"
} }
FastreamExtractor(client).videoFromUrl(src).let { videoList.addAll(it) } FastreamExtractor(client, headers).videosFromUrl(src).also(videoList::addAll)
} }
if (src.contains("upstream")) { if (src.contains("upstream")) {

View File

@ -120,7 +120,7 @@ class MundoDonghua : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
.add("referer", "$baseUrl/") .add("referer", "$baseUrl/")
.add("Origin", "https://${url.toHttpUrl().host}") .add("Origin", "https://${url.toHttpUrl().host}")
.build() .build()
FilemoonExtractor(client).videosFromUrl(url, prefix = "Filemoon:", headers = newHeaders, useHeadersForHtml = true).let { videoList.addAll(it) } FilemoonExtractor(client).videosFromUrl(url, prefix = "Filemoon:", headers = newHeaders).also(videoList::addAll)
} catch (_: Exception) {} } catch (_: Exception) {}
} }
} }

View File

@ -218,7 +218,7 @@ open class PelisForte : ConfigurableAnimeSource, AnimeHttpSource() {
BurstCloudExtractor(client).videoFromUrl(url, headers = headers, prefix = prefix).let { videoList.addAll(it) } BurstCloudExtractor(client).videoFromUrl(url, headers = headers, prefix = prefix).let { videoList.addAll(it) }
} }
if (embedUrl.contains("fastream")) { if (embedUrl.contains("fastream")) {
FastreamExtractor(client).videoFromUrl(url, prefix = "${prefix}Fastream:").forEach { videoList.add(it) } FastreamExtractor(client, headers).videosFromUrl(url, prefix = "${prefix}Fastream:").also(videoList::addAll)
} }
if (embedUrl.contains("upstream")) { if (embedUrl.contains("upstream")) {
UpstreamExtractor(client).videosFromUrl(url, prefix = prefix).let { videoList.addAll(it) } UpstreamExtractor(client).videosFromUrl(url, prefix = prefix).let { videoList.addAll(it) }

View File

@ -228,7 +228,7 @@ open class Pelisplushd(override val name: String, override val baseUrl: String)
BurstCloudExtractor(client).videoFromUrl(url, headers = headers).let { videoList.addAll(it) } BurstCloudExtractor(client).videoFromUrl(url, headers = headers).let { videoList.addAll(it) }
} }
if (embedUrl.contains("fastream")) { if (embedUrl.contains("fastream")) {
FastreamExtractor(client).videoFromUrl(url).forEach { videoList.add(it) } FastreamExtractor(client, headers).videosFromUrl(url).also(videoList::addAll)
} }
if (embedUrl.contains("upstream")) { if (embedUrl.contains("upstream")) {
UpstreamExtractor(client).videosFromUrl(url).let { videoList.addAll(it) } UpstreamExtractor(client).videosFromUrl(url).let { videoList.addAll(it) }

View File

@ -206,7 +206,7 @@ class Pelisplusph(override val name: String, override val baseUrl: String) : Pel
BurstCloudExtractor(client).videoFromUrl(url, headers = headers, prefix = prefix).let { videoList.addAll(it) } BurstCloudExtractor(client).videoFromUrl(url, headers = headers, prefix = prefix).let { videoList.addAll(it) }
} }
if (embedUrl.contains("fastream")) { if (embedUrl.contains("fastream")) {
FastreamExtractor(client).videoFromUrl(url, prefix = "$prefix Fastream:").forEach { videoList.add(it) } FastreamExtractor(client, headers).videosFromUrl(url, prefix = "$prefix Fastream:").also(videoList::addAll)
} }
if (embedUrl.contains("upstream")) { if (embedUrl.contains("upstream")) {
UpstreamExtractor(client).videosFromUrl(url, prefix = prefix).let { videoList.addAll(it) } UpstreamExtractor(client).videosFromUrl(url, prefix = prefix).let { videoList.addAll(it) }

View File

@ -231,7 +231,7 @@ class Pelisplusto(override val name: String, override val baseUrl: String) : Pel
BurstCloudExtractor(client).videoFromUrl(url, headers = headers).let { videoList.addAll(it) } BurstCloudExtractor(client).videoFromUrl(url, headers = headers).let { videoList.addAll(it) }
} }
if (embedUrl.contains("fastream")) { if (embedUrl.contains("fastream")) {
FastreamExtractor(client).videoFromUrl(url).forEach { videoList.add(it) } FastreamExtractor(client, headers).videosFromUrl(url).also(videoList::addAll)
} }
if (embedUrl.contains("upstream")) { if (embedUrl.contains("upstream")) {
UpstreamExtractor(client).videosFromUrl(url).let { videoList.addAll(it) } UpstreamExtractor(client).videosFromUrl(url).let { videoList.addAll(it) }