feat(src/hi): New source YoMovies (#1935)

This commit is contained in:
Secozzi 2023-07-21 10:01:34 +00:00 committed by GitHub
parent b6660f39de
commit 72fe14e618
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 603 additions and 4 deletions

View File

@ -1,5 +1,6 @@
package eu.kanade.tachiyomi.lib.doodextractor
import eu.kanade.tachiyomi.animesource.model.Track
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.GET
import okhttp3.Headers
@ -11,6 +12,7 @@ class DoodExtractor(private val client: OkHttpClient) {
url: String,
quality: String? = null,
redirect: Boolean = true,
externalSubs: List<Track> = emptyList(),
): Video? {
val newQuality = quality ?: ("Doodstream" + if (redirect) " mirror" else "")
@ -32,7 +34,7 @@ class DoodExtractor(private val client: OkHttpClient) {
),
).execute().body.string()
val videoUrl = "$videoUrlStart$randomString?token=$token&expiry=$expiry"
Video(newUrl, newQuality, videoUrl, headers = doodHeaders(doodHost))
Video(newUrl, newQuality, videoUrl, headers = doodHeaders(doodHost), subtitleTracks = externalSubs)
}.getOrNull()
}

View File

@ -10,7 +10,12 @@ import okhttp3.OkHttpClient
import java.net.URLDecoder
class MixDropExtractor(private val client: OkHttpClient) {
fun videoFromUrl(url: String, lang: String = "", prefix: String = ""): List<Video> {
fun videoFromUrl(
url: String,
lang: String = "",
prefix: String = "",
externalSubs: List<Track> = emptyList(),
): List<Video> {
val doc = client.newCall(GET(url)).execute().asJsoup()
val unpacked = doc.selectFirst("script:containsData(eval):containsData(MDCore)")
?.data()
@ -35,6 +40,6 @@ class MixDropExtractor(private val client: OkHttpClient) {
}
val headers = Headers.headersOf("Referer", "https://mixdrop.co/")
return listOf(Video(videoUrl, quality, videoUrl, headers = headers, subtitleTracks = subs))
return listOf(Video(videoUrl, quality, videoUrl, headers = headers, subtitleTracks = subs + externalSubs))
}
}

View File

@ -74,6 +74,7 @@ class StreamSBExtractor(private val client: OkHttpClient) {
suffix: String = "",
common: Boolean = true,
manualData: Boolean = false,
externalSubs: List<Track> = emptyList(),
): List<Video> {
val trimmedUrl = url.trim() // Prevents some crashes
val newHeaders = if (manualData) {
@ -133,7 +134,7 @@ class StreamSBExtractor(private val client: OkHttpClient) {
}
}
val videoUrl = it.substringAfter("\n").substringBefore("\n")
Video(videoUrl, quality, videoUrl, headers = newHeaders, subtitleTracks = subtitleList, audioTracks = audioList)
Video(videoUrl, quality, videoUrl, headers = newHeaders, subtitleTracks = subtitleList + externalSubs, audioTracks = audioList)
}
}.getOrNull() ?: emptyList<Video>()
}

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest />

View File

@ -0,0 +1,21 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
ext {
extName = 'YoMovies'
pkgNameSuffix = 'hi.yomovies'
extClass = '.YoMovies'
extVersionCode = 1
libVersion = '13'
containsNsfw = true
}
dependencies {
implementation(project(':lib-streamsb-extractor'))
implementation(project(':lib-dood-extractor'))
implementation(project(':lib-mixdrop-extractor'))
implementation "dev.datlag.jsunpacker:jsunpacker:1.0.1"
implementation(project(':lib-playlist-utils'))
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

View File

@ -0,0 +1,448 @@
package eu.kanade.tachiyomi.animeextension.hi.yomovies
import android.app.Application
import android.content.SharedPreferences
import android.widget.Toast
import androidx.preference.EditTextPreference
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.AppInfo
import eu.kanade.tachiyomi.animeextension.hi.yomovies.extractors.MovembedExtractor
import eu.kanade.tachiyomi.animeextension.hi.yomovies.extractors.SpeedostreamExtractor
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
import eu.kanade.tachiyomi.animesource.model.SAnime
import eu.kanade.tachiyomi.animesource.model.SEpisode
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.runBlocking
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.lang.Exception
class YoMovies : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override val name = "YoMovies"
override val baseUrl by lazy { preferences.getString(PREF_DOMAIN_KEY, PREF_DOMAIN_DEFAULT)!! }
override val lang = "hi"
override val supportsLatest = false
override val client: OkHttpClient = network.cloudflareClient
private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
// ============================== Popular ===============================
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/most-favorites/".addPage(page), headers)
override fun popularAnimeSelector(): String = "div.movies-list > div.ml-item"
override fun popularAnimeFromElement(element: Element): SAnime = SAnime.create().apply {
setUrlWithoutDomain(element.selectFirst("a[href]")!!.attr("href"))
thumbnail_url = element.selectFirst("img[data-original]")?.attr("abs:data-original") ?: ""
title = element.selectFirst("div.qtip-title")!!.text()
}
override fun popularAnimeNextPageSelector(): String = "ul.pagination > li.active + li"
// =============================== Latest ===============================
override fun latestUpdatesRequest(page: Int): Request = throw Exception("Not used")
override fun latestUpdatesSelector(): String = throw Exception("Not used")
override fun latestUpdatesFromElement(element: Element): SAnime = throw Exception("Not used")
override fun latestUpdatesNextPageSelector(): String = throw Exception("Not used")
// =============================== Search ===============================
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val filterList = if (filters.isEmpty()) getFilterList() else filters
val firstSelected = filterList.firstOrNull {
it.state != 0
}?.let { it as UriPartFilter }
return when {
query.isNotBlank() -> GET("$baseUrl/?s=$query".addPage(page), headers)
firstSelected != null -> GET("$baseUrl${firstSelected.toUriPart()}".addPage(page), headers)
else -> throw Exception("Either search or")
}
}
override fun searchAnimeSelector(): String = popularAnimeSelector()
override fun searchAnimeFromElement(element: Element): SAnime = popularAnimeFromElement(element)
override fun searchAnimeNextPageSelector(): String = popularAnimeNextPageSelector()
// =========================== Anime Details ============================
override fun animeDetailsParse(document: Document): SAnime {
val infoDiv = document.selectFirst("div.mvi-content")!!
return SAnime.create().apply {
description = infoDiv.selectFirst("p.f-desc")?.text()
genre = infoDiv.select("div.mvici-left > p:contains(Genre:) a").joinToString(", ") { it.text() }
author = infoDiv.select("div.mvici-left > p:contains(Studio:) a").joinToString(", ") { it.text() }
}
}
// ============================== Episodes ==============================
override fun episodeListParse(response: Response): List<SEpisode> {
val document = response.asJsoup()
val episodeList = mutableListOf<SEpisode>()
val seasonList = document.select("div#seasons > div.tvseason")
// For movies
if (seasonList.size == 0) {
episodeList.add(
SEpisode.create().apply {
setUrlWithoutDomain(response.request.url.toString())
name = "Movie"
episode_number = 1F
},
)
} else {
seasonList.forEach { season ->
val seasonText = season.selectFirst("div.les-title")!!.text().trim()
season.select(episodeListSelector()).forEachIndexed { index, ep ->
val epNumber = ep.text().trim().substringAfter("pisode ")
episodeList.add(
SEpisode.create().apply {
setUrlWithoutDomain(ep.attr("abs:href"))
name = "$seasonText Ep. $epNumber"
episode_number = epNumber.toFloatOrNull() ?: (index + 1).toFloat()
},
)
}
}
}
return episodeList.reversed()
}
override fun episodeListSelector() = "div.les-content > a"
override fun episodeFromElement(element: Element): SEpisode = throw Exception("Not used")
// ============================ Video Links =============================
override fun videoListParse(response: Response): List<Video> {
val document = response.asJsoup()
val videoList = document.select("div[id*=tab]:has(div.movieplay > iframe)").parallelMap { server ->
val iframe = server.selectFirst("div.movieplay > iframe")!!
val name = document.selectFirst("ul.idTabs > li:has(a[href=#${server.id()}]) div.les-title")?.text()?.let { "[$it] - " } ?: ""
runCatching {
extractVideosFromIframe(iframe.attr("abs:src"), name)
}.getOrElse { emptyList() }
}.flatten()
require(videoList.isNotEmpty()) { "Failed to fetch videos" }
return videoList.sort()
}
private fun extractVideosFromIframe(iframeUrl: String, name: String): List<Video> {
return when {
iframeUrl.contains("speedostream") -> {
SpeedostreamExtractor(client, headers).videosFromUrl(iframeUrl, "$baseUrl/", prefix = name)
}
// Prepending the server name probably isn't needed for this,
// since it doesn't do different episodes for different servers from what I can tell
iframeUrl.contains("movembed.cc") -> {
MovembedExtractor(client, headers).videosFromUrl(iframeUrl)
}
else -> emptyList()
}
}
override fun videoListSelector() = throw Exception("not used")
override fun videoFromElement(element: Element) = throw Exception("not used")
override fun videoUrlParse(document: Document) = throw Exception("not used")
// ============================= Utilities ==============================
private fun String.addPage(page: Int): String {
return if (page == 1) {
this
} else {
this.toHttpUrl().newBuilder()
.addPathSegment("page")
.addPathSegment(page.toString())
.toString()
}
}
override fun List<Video>.sort(): List<Video> {
val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
return this.sortedWith(
compareBy(
{ it.quality.contains(quality) },
{ Regex("""(\d+)p""").find(it.quality)?.groupValues?.get(1)?.toIntOrNull() ?: 0 },
),
).reversed()
}
// From Dopebox
private fun <A, B> Iterable<A>.parallelMap(f: suspend (A) -> B): List<B> =
runBlocking {
map { async(Dispatchers.Default) { f(it) } }.awaitAll()
}
companion object {
private val PREF_DOMAIN_KEY = "preferred_domain_name_v${AppInfo.getVersionName()}"
private const val PREF_DOMAIN_TITLE = "Override BaseUrl"
private const val PREF_DOMAIN_DEFAULT = "https://yomovies.baby"
private const val PREF_DOMAIN_SUMMARY = "For temporary uses. Updating the extension will erase this setting."
private const val PREF_QUALITY_KEY = "preferred_quality"
private const val PREF_QUALITY_DEFAULT = "1080"
}
// ============================== Settings ==============================
override fun setupPreferenceScreen(screen: PreferenceScreen) {
EditTextPreference(screen.context).apply {
key = PREF_DOMAIN_KEY
title = PREF_DOMAIN_TITLE
summary = PREF_DOMAIN_SUMMARY
dialogTitle = PREF_DOMAIN_TITLE
dialogMessage = "Default: $PREF_DOMAIN_DEFAULT"
setDefaultValue(PREF_DOMAIN_DEFAULT)
setOnPreferenceChangeListener { _, newValue ->
val newValueString = newValue as String
Toast.makeText(screen.context, "Restart Aniyomi to apply new setting.", Toast.LENGTH_LONG).show()
preferences.edit().putString(key, newValueString.trim()).commit()
}
}.also(screen::addPreference)
ListPreference(screen.context).apply {
key = PREF_QUALITY_KEY
title = "Preferred quality"
entries = arrayOf("1080p", "720p", "480p", "360p")
entryValues = arrayOf("1080", "720", "480", "360")
setDefaultValue(PREF_QUALITY_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
}
}.also(screen::addPreference)
}
// ============================== Filters ===============================
override fun getFilterList(): AnimeFilterList = AnimeFilterList(
AnimeFilter.Header("Note: Only one selection at a time works, and it ignores text search"),
AnimeFilter.Separator(),
BollywoodFilter(),
DualAudioFilter(),
HollywoodFilter(),
EnglishSeriesFilter(),
HindiSeriesFilter(),
GenreFilter(),
ExtraMoviesFilter(),
EroticFilter(),
HotSeriesFilter(),
)
// Note for filters, clicking on the "category selector" yields its own page
// $("ul.top-menu > li:has(a:contains(Bollywood)) ul > li").map((i,el) => `Pair("${$(el).text().trim()}", "${$(el).find("a").first().attr('href').trim().replace('https://yomovies.baby', '')}")`).get().join(',\n')
// on /
private class BollywoodFilter : UriPartFilter(
"Bollywood",
arrayOf(
Pair("<select>", ""),
Pair("Bollywood", "/genre/bollywood/"),
Pair("Trending", "/genre/top-rated/"),
Pair("Bollywood (2023)", "/account/?ptype=post&tax_category%5B%5D=bollywood&tax_release-year=2023&wpas=1"),
Pair("Bollywood (2022)", "/account/?ptype=post&tax_category%5B%5D=bollywood&tax_release-year=2022&wpas=1"),
Pair("Bollywood (2021)", "/account/?ptype=post&tax_category%5B%5D=bollywood&tax_release-year=2021&wpas=1"),
),
)
// $("ul.top-menu > li:has(a:contains(Dual audio)) ul > li").map((i,el) => `Pair("${$(el).text().trim()}", "${$(el).find("a").first().attr('href').trim().replace('https://yomovies.baby', '')}")`).get().join(',\n')
// on /
private class DualAudioFilter : UriPartFilter(
"Dual Audio",
arrayOf(
Pair("<select>", ""),
Pair("Dual Audio", "/genre/dual-audio/"),
Pair("Hollywood Dubbed", "/account/?ptype=post&tax_category%5B%5D=dual-audio&wpas=1"),
Pair("South Dubbed", "/account/?ptype=post&tax_category%5B%5D=dual-audio&tax_category%5B%5D=south-special&wpas=1"),
),
)
// $("ul.top-menu > li:has(a:contains(Hollywood)) ul > li").map((i,el) => `Pair("${$(el).text().trim()}", "${$(el).find("a").first().attr('href').trim().replace('https://yomovies.baby', '')}")`).get().join(',\n')
// on /
private class HollywoodFilter : UriPartFilter(
"Hollywood",
arrayOf(
Pair("<select>", ""),
Pair("Hollywood", "/genre/hollywood/"),
Pair("Hollywood (2023)", "/account/?ptype=post&tax_category%5B%5D=hollywood&tax_release-year=2023&wpas=1"),
Pair("Hollywood (2022)", "/account/?ptype=post&tax_category%5B%5D=hollywood&tax_release-year=2022&wpas=1"),
Pair("Hollywood (2021)", "/account/?ptype=post&tax_category%5B%5D=hollywood&tax_release-year=2021&wpas=1"),
),
)
private class EnglishSeriesFilter : UriPartFilter(
"English Series",
arrayOf(
Pair("<select>", ""),
Pair("English Series", "/series/"),
),
)
// $("ul.top-menu > li:has(a:contains(Hindi Series)) ul > li").map((i,el) => `Pair("${$(el).text().trim()}", "${$(el).find("a").first().attr('href').trim().replace('https://yomovies.baby', '')}")`).get().join(',\n')
// on /
private class HindiSeriesFilter : UriPartFilter(
"Hindi Series",
arrayOf(
Pair("<select>", ""),
Pair("Hindi Series", "/genre/web-series/"),
Pair("Netflix", "/director/netflix/"),
Pair("Amazon", "/director/amazon-prime/"),
Pair("Altbalaji", "/director/altbalaji/"),
Pair("Zee5", "/director/zee5/"),
Pair("Voot", "/director/voot-originals/"),
Pair("Mx Player", "/director/mx-player/"),
Pair("Hotstar", "/director/hotstar/"),
Pair("Viu", "/director/viu-originals/"),
Pair("Sony Liv", "/director/sonyliv-original/"),
),
)
// $("ul.top-menu > li:has(a:contains(Genre)) ul > li").map((i,el) => `Pair("${$(el).text().trim()}", "${$(el).find("a").first().attr('href').trim().replace('https://yomovies.baby', '')}")`).get().join(',\n')
// on /
private class GenreFilter : UriPartFilter(
"Genre",
arrayOf(
Pair("<select>", ""),
Pair("Action", "/genre/action/"),
Pair("Adventure", "/genre/adventure/"),
Pair("Animation", "/genre/animation/"),
Pair("Biography", "/genre/biography/"),
Pair("Comedy", "/genre/comedy/"),
Pair("Crime", "/genre/crime/"),
Pair("Drama", "/genre/drama/"),
Pair("Music", "/genre/music/"),
Pair("Mystery", "/genre/mystery/"),
Pair("Family", "/genre/family/"),
Pair("Fantasy", "/genre/fantasy/"),
Pair("Horror", "/genre/horror/"),
Pair("History", "/genre/history/"),
Pair("Romance", "/genre/romantic/"),
Pair("Science Fiction", "/genre/science-fiction/"),
Pair("Thriller", "/genre/thriller/"),
Pair("War", "/genre/war/"),
),
)
// $("ul.top-menu > li:has(a:contains(ExtraMovies)) ul > li").map((i,el) => `Pair("${$(el).text().trim()}", "${$(el).find("a").first().attr('href').trim().replace('https://yomovies.baby', '')}")`).get().join(',\n')
// on /
private class ExtraMoviesFilter : UriPartFilter(
"ExtraMovies",
arrayOf(
Pair("<select>", ""),
Pair("ExtraMovies", "/genre/south-special/"),
Pair("Bengali", "/genre/bengali/"),
Pair("Marathi", "/genre/marathi/"),
Pair("Gujarati", "/genre/gujarati/"),
Pair("Punjabi", "/genre/punjabi/"),
Pair("Tamil", "/genre/tamil/"),
Pair("Telugu", "/genre/telugu/"),
Pair("Malayalam", "/genre/malayalam/"),
Pair("Kannada", "/genre/kannada/"),
Pair("Pakistani", "/genre/pakistani/"),
),
)
private class EroticFilter : UriPartFilter(
"Erotic",
arrayOf(
Pair("<select>", ""),
Pair("Erotic", "/genre/erotic-movies/"),
),
)
// $("ul.top-menu > li:has(a:contains(Hot Series)) ul > li").map((i,el) => `Pair("${$(el).text().trim()}", "${$(el).find("a").first().attr('href').trim().replace('https://yomovies.baby', '')}")`).get().join(',\n')
// on /
private class HotSeriesFilter : UriPartFilter(
"Hot Series",
arrayOf(
Pair("<select>", ""),
Pair("Hot Series", "/genre/tv-shows/"),
Pair("Uncut", "/?s=uncut"),
Pair("Fliz Movies", "/director/fliz-movies/"),
Pair("Nuefliks", "/director/nuefliks-exclusive/"),
Pair("Hotshots", "/director/hotshots/"),
Pair("Ullu Originals", "/?s=ullu"),
Pair("Kooku", "/director/kooku-originals/"),
Pair("Gupchup", "/director/gupchup-exclusive/"),
Pair("Feneomovies", "/director/feneomovies/"),
Pair("Cinemadosti", "/director/cinemadosti/"),
Pair("Primeflix", "/director/primeflix/"),
Pair("Gemplex", "/director/gemplex/"),
Pair("Rabbit", "/director/rabbit-original/"),
Pair("HotMasti", "/director/hotmasti-originals/"),
Pair("BoomMovies", "/director/boommovies-original/"),
Pair("CliffMovies", "/director/cliff-movies/"),
Pair("MastiPrime", "/director/masti-prime-originals/"),
Pair("Ek Night Show", "/director/ek-night-show/"),
Pair("Flixsksmovies", "/director/flixsksmovies/"),
Pair("Lootlo", "/director/lootlo-original/"),
Pair("Hootzy", "/director/hootzy-channel/"),
Pair("Balloons", "/director/balloons-originals/"),
Pair("Big Movie Zoo", "/director/big-movie-zoo-originals/"),
Pair("Bambooflix", "/director/bambooflix/"),
Pair("Piliflix", "/director/piliflix-originals/"),
Pair("11upmovies", "/director/11upmovies-originals/"),
Pair("Eightshots", "/director/eightshots-originals/"),
Pair("I-Entertainment", "/director/i-entertainment-exclusive/"),
Pair("Hotprime", "/director/hotprime-originals/"),
Pair("BananaPrime", "/director/banana-prime/"),
Pair("HotHitFilms", "/director/hothitfilms/"),
Pair("Chikooflix", "/director/chikooflix-originals/"),
Pair("Glamheart", "/?s=glamheart"),
Pair("Worldprime", "/director/worldprime-originals/"),
),
)
private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) :
AnimeFilter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
fun toUriPart() = vals[state].second
}
}

View File

@ -0,0 +1,84 @@
package eu.kanade.tachiyomi.animeextension.hi.yomovies.extractors
import eu.kanade.tachiyomi.animesource.model.Track
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor
import eu.kanade.tachiyomi.lib.mixdropextractor.MixDropExtractor
import eu.kanade.tachiyomi.lib.streamsbextractor.StreamSBExtractor
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.runBlocking
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
class MovembedExtractor(private val client: OkHttpClient, private val headers: Headers) {
fun videosFromUrl(url: String): List<Video> {
val document = client.newCall(
GET(url, headers),
).execute().asJsoup()
return document.select("ul.list-server-items > li.linkserver").parallelMap { server ->
runCatching {
extractVideosFromIframe(server.attr("abs:data-video"))
}.getOrElse { emptyList() }
}.flatten()
}
private fun extractVideosFromIframe(iframeUrl: String): List<Video> {
return when {
STREAM_SB_DOMAINS.any { iframeUrl.contains(it) } -> {
val url = iframeUrl.toHttpUrl()
val subtitleList = url.queryParameter("caption_1")?.let { t ->
listOf(Track(t, url.queryParameter("sub_1") ?: "English"))
} ?: emptyList()
StreamSBExtractor(client).videosFromUrl(iframeUrl, headers, prefix = "(movembed) StreamSB - ", externalSubs = subtitleList)
}
MIXDROP_DOMAINS.any { iframeUrl.contains(it) } -> {
val url = iframeUrl.toHttpUrl()
val subtitleList = url.queryParameter("sub1")?.let { t ->
listOf(Track(t, url.queryParameter("sub1_label") ?: "English"))
} ?: emptyList()
MixDropExtractor(client).videoFromUrl(iframeUrl, prefix = "(movembed) - ", externalSubs = subtitleList)
}
iframeUrl.startsWith("https://doo") -> {
val url = iframeUrl.toHttpUrl()
val subtitleList = url.queryParameter("c1_file")?.let { t ->
listOf(Track(t, url.queryParameter("c1_label") ?: "English"))
} ?: emptyList()
DoodExtractor(client).videoFromUrl(iframeUrl, "(movembed) Dood", externalSubs = subtitleList)?.let(::listOf) ?: emptyList()
}
else -> emptyList()
}
}
// From Dopebox
private fun <A, B> Iterable<A>.parallelMap(f: suspend (A) -> B): List<B> =
runBlocking {
map { async(Dispatchers.Default) { f(it) } }.awaitAll()
}
companion object {
private val STREAM_SB_DOMAINS = listOf(
"sbhight", "sbrity", "sbembed.com", "sbembed1.com", "sbplay.org",
"sbvideo.net", "streamsb.net", "sbplay.one", "cloudemb.com",
"playersb.com", "tubesb.com", "sbplay1.com", "embedsb.com",
"watchsb.com", "sbplay2.com", "japopav.tv", "viewsb.com",
"sbfast", "sbfull.com", "javplaya.com", "ssbstream.net",
"p1ayerjavseen.com", "sbthe.com", "vidmovie.xyz", "sbspeed.com",
"streamsss.net", "sblanh.com", "tvmshow.com", "sbanh.com",
"streamovies.xyz", "sblona.com", "sbnet.one",
)
private val MIXDROP_DOMAINS = listOf(
"mixdrop",
"mixdroop",
)
}
}

View File

@ -0,0 +1,36 @@
package eu.kanade.tachiyomi.animeextension.hi.yomovies.extractors
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.lib.playlistutils.PlaylistUtils
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
class SpeedostreamExtractor(private val client: OkHttpClient, private val headers: Headers) {
fun videosFromUrl(url: String, referer: String, prefix: String = ""): List<Video> {
val docHeaders = headers.newBuilder()
.add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8")
.add("Host", url.toHttpUrl().host)
.add("Referer", referer)
.build()
val document = client.newCall(
GET(url, headers = docHeaders),
).execute().asJsoup()
val masterUrl = document.selectFirst("script:containsData(file:)")
?.data()
?.substringAfter("file:")
?.substringAfter("\"")
?.substringBefore("\"")
?: return emptyList()
return PlaylistUtils(client, headers).extractFromHls(
masterUrl,
referer = "https://${url.toHttpUrl().host}/",
videoNameGen = { "${prefix}Speedostream - $it" },
)
}
}