feat(src/hi): New source YoMovies (#1935)
This commit is contained in:
parent
b6660f39de
commit
72fe14e618
@ -1,5 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.lib.doodextractor
|
package eu.kanade.tachiyomi.lib.doodextractor
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.animesource.model.Track
|
||||||
import eu.kanade.tachiyomi.animesource.model.Video
|
import eu.kanade.tachiyomi.animesource.model.Video
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
import okhttp3.Headers
|
import okhttp3.Headers
|
||||||
@ -11,6 +12,7 @@ class DoodExtractor(private val client: OkHttpClient) {
|
|||||||
url: String,
|
url: String,
|
||||||
quality: String? = null,
|
quality: String? = null,
|
||||||
redirect: Boolean = true,
|
redirect: Boolean = true,
|
||||||
|
externalSubs: List<Track> = emptyList(),
|
||||||
): Video? {
|
): Video? {
|
||||||
val newQuality = quality ?: ("Doodstream" + if (redirect) " mirror" else "")
|
val newQuality = quality ?: ("Doodstream" + if (redirect) " mirror" else "")
|
||||||
|
|
||||||
@ -32,7 +34,7 @@ class DoodExtractor(private val client: OkHttpClient) {
|
|||||||
),
|
),
|
||||||
).execute().body.string()
|
).execute().body.string()
|
||||||
val videoUrl = "$videoUrlStart$randomString?token=$token&expiry=$expiry"
|
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()
|
}.getOrNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,12 @@ import okhttp3.OkHttpClient
|
|||||||
import java.net.URLDecoder
|
import java.net.URLDecoder
|
||||||
|
|
||||||
class MixDropExtractor(private val client: OkHttpClient) {
|
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 doc = client.newCall(GET(url)).execute().asJsoup()
|
||||||
val unpacked = doc.selectFirst("script:containsData(eval):containsData(MDCore)")
|
val unpacked = doc.selectFirst("script:containsData(eval):containsData(MDCore)")
|
||||||
?.data()
|
?.data()
|
||||||
@ -35,6 +40,6 @@ class MixDropExtractor(private val client: OkHttpClient) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val headers = Headers.headersOf("Referer", "https://mixdrop.co/")
|
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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -74,6 +74,7 @@ class StreamSBExtractor(private val client: OkHttpClient) {
|
|||||||
suffix: String = "",
|
suffix: String = "",
|
||||||
common: Boolean = true,
|
common: Boolean = true,
|
||||||
manualData: Boolean = false,
|
manualData: Boolean = false,
|
||||||
|
externalSubs: List<Track> = emptyList(),
|
||||||
): List<Video> {
|
): List<Video> {
|
||||||
val trimmedUrl = url.trim() // Prevents some crashes
|
val trimmedUrl = url.trim() // Prevents some crashes
|
||||||
val newHeaders = if (manualData) {
|
val newHeaders = if (manualData) {
|
||||||
@ -133,7 +134,7 @@ class StreamSBExtractor(private val client: OkHttpClient) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
val videoUrl = it.substringAfter("\n").substringBefore("\n")
|
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>()
|
}.getOrNull() ?: emptyList<Video>()
|
||||||
}
|
}
|
||||||
|
2
src/hi/yomovies/AndroidManifest.xml
Normal file
2
src/hi/yomovies/AndroidManifest.xml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest />
|
21
src/hi/yomovies/build.gradle
Normal file
21
src/hi/yomovies/build.gradle
Normal 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"
|
BIN
src/hi/yomovies/res/mipmap-hdpi/ic_launcher.png
Normal file
BIN
src/hi/yomovies/res/mipmap-hdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.1 KiB |
BIN
src/hi/yomovies/res/mipmap-mdpi/ic_launcher.png
Normal file
BIN
src/hi/yomovies/res/mipmap-mdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.6 KiB |
BIN
src/hi/yomovies/res/mipmap-xhdpi/ic_launcher.png
Normal file
BIN
src/hi/yomovies/res/mipmap-xhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
BIN
src/hi/yomovies/res/mipmap-xxhdpi/ic_launcher.png
Normal file
BIN
src/hi/yomovies/res/mipmap-xxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.8 KiB |
BIN
src/hi/yomovies/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
BIN
src/hi/yomovies/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
BIN
src/hi/yomovies/res/web_hi_res_512.png
Normal file
BIN
src/hi/yomovies/res/web_hi_res_512.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 62 KiB |
@ -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
|
||||||
|
}
|
||||||
|
}
|
@ -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",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -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" },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user