This commit is contained in:
Suraj 2023-07-11 19:07:52 +05:30
commit 8c386159ec
27 changed files with 593 additions and 600 deletions

View file

@ -1,7 +1,7 @@
import org.jetbrains.kotlin.konan.properties.Properties
// use an integer for version numbers
version = 2
version = 4
android {
defaultConfig {

View file

@ -12,6 +12,7 @@ import com.lagradost.cloudstream3.mvvm.safeApiCall
import com.lagradost.cloudstream3.utils.*
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
import com.lagradost.cloudstream3.utils.AppUtils.toJson
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
import com.lagradost.nicehttp.RequestBodyTypes
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.RequestBody.Companion.toRequestBody
@ -42,73 +43,54 @@ class Anichi : MainAPI() {
override val supportedTypes = setOf(TvType.Anime, TvType.AnimeMovie)
private val popularTitle = "Popular"
private val recentTitle = "Latest Updated"
override val mainPage = listOf(
MainPageData(
recentTitle,
"""$apiUrl?variables={"search":{"sortBy":"Latest_Update","allowAdult":${settingsForProvider.enableAdult},"allowUnknown":false},"limit":26,"page":%d,"translationType":"sub","countryOrigin":"ALL"}&extensions={"persistedQuery":{"version":1,"sha256Hash":"$mainHash"}}"""
),
MainPageData(
popularTitle,
"""$apiUrl?variables={"type":"anime","size":30,"dateRange":1,"page":%d,"allowAdult":${settingsForProvider.enableAdult},"allowUnknown":false}&extensions={"persistedQuery":{"version":1,"sha256Hash":"$popularHash"}}"""
)
private val animeRecentTitle = "Latest Anime"
private val donghuaRecentTitle = "Latest Donghua"
private val movieTitle = "Movie"
override val mainPage = mainPageOf(
"""$apiUrl?variables={"search":{"sortBy":"Latest_Update","allowAdult":${settingsForProvider.enableAdult},"allowUnknown":false},"limit":26,"page":%d,"translationType":"sub","countryOrigin":"JP"}&extensions={"persistedQuery":{"version":1,"sha256Hash":"$mainHash"}}""" to animeRecentTitle,
"""$apiUrl?variables={"search":{"sortBy":"Latest_Update","allowAdult":${settingsForProvider.enableAdult},"allowUnknown":false},"limit":26,"page":%d,"translationType":"sub","countryOrigin":"CN"}&extensions={"persistedQuery":{"version":1,"sha256Hash":"$mainHash"}}""" to donghuaRecentTitle,
"""$apiUrl?variables={"type":"anime","size":30,"dateRange":1,"page":%d,"allowAdult":${settingsForProvider.enableAdult},"allowUnknown":false}&extensions={"persistedQuery":{"version":1,"sha256Hash":"$popularHash"}}""" to popularTitle,
"""$apiUrl?variables={"search":{"slug":"movie-anime","format":"anime","tagType":"upcoming","name":"Trending Movies"}}&extensions={"persistedQuery":{"version":1,"sha256Hash":"$slugHash"}}""" to movieTitle
)
override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse {
val url = request.data.format(page)
val test = app.get(url, headers = headers).text
val home = when (request.name) {
recentTitle -> {
val json = parseJson<AnichiQuery>(test)
val results = json.data.shows.edges.filter {
val res = app.get(url, headers = headers).parsedSafe<AnichiQuery>()?.data
val query = res?.shows ?: res?.queryPopular ?: res?.queryListForTag
val card = if(request.name == popularTitle) query?.recommendations?.map { it.anyCard } else query?.edges
val home = card?.filter {
// filtering in case there is an anime with 0 episodes available on the site.
!(it.availableEpisodes?.raw == 0 && it.availableEpisodes.sub == 0 && it.availableEpisodes.dub == 0)
}
results.map {
newAnimeSearchResponse(it.name ?: "", "${it.Id}", fix = false) {
this.posterUrl = it.thumbnail
this.year = it.airedStart?.year
this.otherName = it.englishName
addDub(it.availableEpisodes?.dub)
addSub(it.availableEpisodes?.sub)
}
}
}
popularTitle -> {
val json = parseJson<PopularQuery>(test)
val results = json.data?.queryPopular?.recommendations?.filter {
// filtering in case there is an anime with 0 episodes available on the site.
!(it.anyCard?.availableEpisodes?.raw == 0 && it.anyCard.availableEpisodes.sub == 0 && it.anyCard.availableEpisodes.dub == 0)
}
results?.mapNotNull {
newAnimeSearchResponse(
it.anyCard?.name ?: return@mapNotNull null,
"${it.anyCard.Id ?: it.pageStatus?.Id}",
fix = false
) {
this.posterUrl = it.anyCard.thumbnail
this.otherName = it.anyCard.englishName
addDub(it.anyCard.availableEpisodes?.dub)
addSub(it.anyCard.availableEpisodes?.sub)
}
!(it?.availableEpisodes?.raw == 0 && it.availableEpisodes.sub == 0 && it.availableEpisodes.dub == 0)
}?.mapNotNull { media ->
media?.toSearchResponse()
} ?: emptyList()
}
else -> emptyList()
}
return HomePageResponse(
listOf(
HomePageList(request.name, home)
), hasNext = home.isNotEmpty()
return newHomePageResponse(
list = HomePageList(
name = request.name,
list = home,
),
hasNext = request.name != movieTitle
)
}
override suspend fun search(query: String): List<SearchResponse> {
private fun Edges.toSearchResponse(): AnimeSearchResponse? {
return newAnimeSearchResponse(
name ?: englishName ?: nativeName ?: "",
Id ?: return null,
fix = false
) {
this.posterUrl = thumbnail
this.year = airedStart?.year
this.otherName = englishName
addDub(availableEpisodes?.dub)
addSub(availableEpisodes?.sub)
}
}
override suspend fun search(query: String): List<SearchResponse>? {
val link =
"""$apiUrl?variables={"search":{"allowAdult":false,"allowUnknown":false,"query":"$query"},"limit":26,"page":1,"translationType":"sub","countryOrigin":"ALL"}&extensions={"persistedQuery":{"version":1,"sha256Hash":"$mainHash"}}"""
@ -125,12 +107,12 @@ class Anichi : MainAPI() {
val response = parseJson<AnichiQuery>(res)
val results = response.data.shows.edges.filter {
val results = response.data?.shows?.edges?.filter {
// filtering in case there is an anime with 0 episodes available on the site.
!(it.availableEpisodes?.raw == 0 && it.availableEpisodes.sub == 0 && it.availableEpisodes.dub == 0)
}
return results.map {
return results?.map {
newAnimeSearchResponse(it.name ?: "", "${it.Id}", fix = false) {
this.posterUrl = it.thumbnail
this.year = it.airedStart?.year
@ -161,19 +143,13 @@ class Anichi : MainAPI() {
val poster = showData.thumbnail
val type = getType(showData.type ?: "")
val episodes = showData.availableEpisodes.let {
val episodes = showData.availableEpisodesDetail.let {
if (it == null) return@let Pair(null, null)
if (showData.Id == null) return@let Pair(null, null)
Pair(if (it.sub != 0) ((1..it.sub).map { epNum ->
Episode(
AnichiLoadData(showData.Id, "sub", epNum).toJson(), episode = epNum
Pair(
it.getEpisode("sub", showData.Id),
it.getEpisode("dub", showData.Id),
)
}) else null, if (it.dub != 0) ((1..it.dub).map { epNum ->
Episode(
AnichiLoadData(showData.Id, "dub", epNum).toJson(), episode = epNum
)
}) else null)
}
val characters = showData.characters?.map {
@ -191,7 +167,7 @@ class Anichi : MainAPI() {
val names = showData.altNames?.plus(title)?.filterNotNull() ?: emptyList()
val trackers = getTracker(names, TrackerType.getTypes(type), showData.airedStart?.year)
return newAnimeLoadResponse(title ?: "", url, type) {
return newAnimeLoadResponse(title ?: "", url, TvType.Anime) {
engName = showData.altNames?.firstOrNull()
posterUrl = trackers?.image ?: poster
backgroundPosterUrl = trackers?.cover ?: showData.banner
@ -229,7 +205,7 @@ class Anichi : MainAPI() {
apiResponse.data?.episode?.sourceUrls?.apmap { source ->
safeApiCall {
val link = source.sourceUrl?.replace(" ", "%20") ?: return@safeApiCall
val link = fixSourceUrls(source.sourceUrl ?: return@safeApiCall, source.sourceName) ?: return@safeApiCall
if (URI(link).isAbsolute || link.startsWith("//")) {
val fixedLink = if (link.startsWith("//")) "https:$link" else link
val host = link.getHost()
@ -258,7 +234,7 @@ class Anichi : MainAPI() {
}
}
} else {
val fixedLink = apiEndPoint + URI(link).path + ".json?" + URI(link).query
val fixedLink = link.fixUrlPath()
val links = app.get(fixedLink).parsedSafe<AnichiVideoApiResponse>()?.links
?: emptyList()
links.forEach { server ->
@ -293,7 +269,8 @@ class Anichi : MainAPI() {
).path),
server.resolutionStr.removeSuffix("p").toIntOrNull()
?: Qualities.P1080.value,
false
false,
isDash = server.resolutionStr == "Dash 1"
)
)
}
@ -330,6 +307,19 @@ class Anichi : MainAPI() {
return false
}
private fun AvailableEpisodesDetail.getEpisode(
lang: String,
id: String
): List<com.lagradost.cloudstream3.Episode> {
val meta = if (lang == "sub") this.sub else this.dub
return meta.map { eps ->
Episode(
AnichiLoadData(id, lang, eps).toJson(),
"Ep $eps"
)
}.reversed()
}
private suspend fun getM3u8Qualities(
m3u8Link: String,
referer: String,
@ -380,6 +370,18 @@ class Anichi : MainAPI() {
return fixTitle(URI(this).host.substringBeforeLast(".").substringAfterLast("."))
}
private fun String.fixUrlPath() : String {
return if(this.contains(".json?")) apiEndPoint + this else apiEndPoint + URI(this).path + ".json?" + URI(this).query
}
private fun fixSourceUrls(url: String, source: String?) : String? {
return if(source == "Ak" || url.contains("/player/vitemb")) {
tryParseJson<AkIframe>(base64Decode(url.substringAfter("=")))?.idUrl
} else {
url.replace(" ", "%20")
}
}
companion object {
private const val apiUrl = BuildConfig.ANICHI_API
private const val serverUrl = BuildConfig.ANICHI_SERVER
@ -387,6 +389,7 @@ class Anichi : MainAPI() {
private const val mainHash = "e42a4466d984b2c0a2cecae5dd13aa68867f634b16ee0f17b380047d14482406"
private const val popularHash = "31a117653812a2547fd981632e8c99fa8bf8a75c4ef1a77a1567ef1741a7ab9c"
private const val slugHash = "bf603205eb2533ca21d0324a11f623854d62ed838a27e1b3fcfb712ab98b03f4"
private const val detailHash = "bb263f91e5bdd048c1c978f324613aeccdfe2cbc694a419466a31edb58c0cc0b"
private const val serverHash = "5e7e17cdd0166af5a2d8f43133d9ce3ce9253d1fdb5160a0cfd515564f98d061"
@ -400,7 +403,11 @@ class Anichi : MainAPI() {
data class AnichiLoadData(
val hash: String,
val dubStatus: String,
val episode: Int
val episode: String
)
data class AkIframe(
@JsonProperty("idUrl") val idUrl: String? = null,
)
data class Stream(
@ -414,12 +421,19 @@ class Anichi : MainAPI() {
@JsonProperty("streams") val streams: ArrayList<Stream>? = arrayListOf(),
)
data class Subtitles(
@JsonProperty("lang") val lang: String?,
@JsonProperty("label") val label: String?,
@JsonProperty("src") val src: String?,
)
data class Links(
@JsonProperty("link") val link: String,
@JsonProperty("hls") val hls: Boolean?,
@JsonProperty("resolutionStr") val resolutionStr: String,
@JsonProperty("src") val src: String?,
@JsonProperty("portData") val portData: PortData? = null,
@JsonProperty("subtitles") val subtitles: ArrayList<Subtitles>? = arrayListOf(),
)
data class AnichiVideoApiResponse(
@ -427,13 +441,18 @@ class Anichi : MainAPI() {
)
data class Data(
@JsonProperty("shows") val shows: Shows
@JsonProperty("shows") val shows: Shows? = null,
@JsonProperty("queryListForTag") val queryListForTag: Shows? = null,
@JsonProperty("queryPopular") val queryPopular: Shows? = null,
)
data class Shows(
@JsonProperty("pageInfo") val pageInfo: PageInfo,
@JsonProperty("edges") val edges: List<Edges>,
@JsonProperty("__typename") val _typename: String?
@JsonProperty("edges") val edges: List<Edges>? = arrayListOf(),
@JsonProperty("recommendations") val recommendations: List<EdgesCard>? = arrayListOf(),
)
data class EdgesCard(
@JsonProperty("anyCard") val anyCard: Edges? = null,
)
data class CharacterImage(
@ -493,13 +512,8 @@ class Anichi : MainAPI() {
@JsonProperty("year") val year: Int
)
data class PageInfo(
@JsonProperty("total") val total: Int?,
@JsonProperty("__typename") val _typename: String?
)
data class AnichiQuery(
@JsonProperty("data") val data: Data
@JsonProperty("data") val data: Data? = null
)
data class Detail(
@ -537,10 +551,6 @@ class Anichi : MainAPI() {
@JsonProperty("sourceUrls") val sourceUrls: ArrayList<SourceUrls> = arrayListOf(),
)
data class PopularQuery(
@JsonProperty("data") val data: DataPopular? = DataPopular()
)
data class Sub(
@JsonProperty("hour") val hour: Int? = null,
@JsonProperty("minute") val minute: Int? = null,

View file

@ -1,5 +1,5 @@
// use an integer for version numbers
version = 5
version = 6
cloudstream {

View file

@ -129,7 +129,7 @@ class Gomunimeis : MainAPI() {
.let { id ->
app.get("https://gomunimes.com/stream?id=$id")
.parsedSafe<Sources>()?.server?.streamsb?.link?.let { link ->
loadExtractor(link, "https://vidgomunime.xyz/", subtitleCallback, callback)
loadExtractor(link.replace("vidgomunimesb.xyz", "watchsb.com"), mainUrl, subtitleCallback, callback)
}
}

View file

@ -1,5 +1,5 @@
// use an integer for version numbers
version = 2
version = 3
cloudstream {

View file

@ -165,14 +165,20 @@ class Minioppai : MainAPI() {
val sources = script.substringAfter("sources:[").substringBefore("]").replace("'", "\"")
val subtitles = script.substringAfter("\"tracks\":[").substringBefore("]")
tryParseJson<List<Sources>>("[$sources]")?.map {
tryParseJson<List<Sources>>("[$sources]")?.map { source ->
val pStream = fixLink(source.file ?: return@map, paistream).takeIf {
app.get(
it,
referer = "$paistream/"
).isSuccessful
}
callback.invoke(
ExtractorLink(
server,
server,
fixLink(it.file ?: return@map, if(server == "Stream 1") libPaistream else paistream),
pStream ?: fixLink(source.file ?: return@map, libPaistream),
"$paistream/",
getQualityFromName(it.label)
getQualityFromName(source.label)
)
)
}

View file

@ -1,5 +1,5 @@
// use an integer for version numbers
version = 37
version = 36
cloudstream {

View file

@ -12,7 +12,7 @@ import org.jsoup.nodes.Element
import java.net.URI
class Movierulzhd : MainAPI() {
override var mainUrl = "https://movierulzhd.help"
override var mainUrl = "https://movierulzhd.trade"
private var directUrl = mainUrl
override var name = "Movierulzhd"
override val hasMainPage = true

View file

@ -1,5 +1,5 @@
// use an integer for version numbers
version = 2
version = 3
cloudstream {

View file

@ -1,149 +0,0 @@
package com.hexated
import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.extractors.DoodLaExtractor
import com.lagradost.cloudstream3.extractors.Filesim
import com.lagradost.cloudstream3.extractors.StreamSB
import com.lagradost.cloudstream3.utils.AppUtils
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.Qualities
import java.net.URI
import javax.crypto.Cipher
import javax.crypto.SecretKeyFactory
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.PBEKeySpec
import javax.crypto.spec.SecretKeySpec
class Dooood : DoodLaExtractor() {
override var mainUrl = "https://dooood.com"
}
class Guccihide : Filesim() {
override val name = "Guccihide"
override var mainUrl = "https://guccihide.com"
}
class Ahvsh : Filesim() {
override val name = "Ahvsh"
override var mainUrl = "https://ahvsh.com"
}
class Lvturbo : StreamSB() {
override var name = "Lvturbo"
override var mainUrl = "https://lvturbo.com"
}
class Sbrapid : StreamSB() {
override var name = "Sbrapid"
override var mainUrl = "https://sbrapid.com"
}
class Sbface : StreamSB() {
override var name = "Sbface"
override var mainUrl = "https://sbface.com"
}
class Sbsonic : StreamSB() {
override var name = "Sbsonic"
override var mainUrl = "https://sbsonic.com"
}
object LocalServer {
private const val KEY = "4VqE3#N7zt&HEP^a"
private fun getBaseUrl(url: String): String {
return URI(url).let {
"${it.scheme}://${it.host}"
}
}
suspend fun getUrl(
url: String,
referer: String?,
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit
) {
val mainUrl = getBaseUrl(url)
val master = Regex("MasterJS\\s*=\\s*'([^']+)").find(
app.get(
url,
referer = referer
).text
)?.groupValues?.get(1)
val encData = AppUtils.tryParseJson<AESData>(base64Decode(master ?: return))
val decrypt = cryptoAESHandler(encData ?: return, KEY, false)
val source = Regex(""""?file"?:\s*"([^"]+)""").find(decrypt)?.groupValues?.get(1)
// required
val headers = mapOf(
"Accept" to "*/*",
"Connection" to "keep-alive",
"Sec-Fetch-Dest" to "empty",
"Sec-Fetch-Mode" to "cors",
"Sec-Fetch-Site" to "cross-site",
"Origin" to mainUrl,
)
callback.invoke(
ExtractorLink(
Ngefilm().name,
Ngefilm().name,
source ?: return,
"$mainUrl/",
Qualities.P1080.value,
headers = headers,
isM3u8 = true
)
)
}
private fun cryptoAESHandler(
data: AESData,
pass: String,
encrypt: Boolean = true
): String {
val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512")
val spec = PBEKeySpec(
pass.toCharArray(),
data.salt?.hexToByteArray(),
data.iterations?.toIntOrNull() ?: 1,
256
)
val key = factory.generateSecret(spec)
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
return if (!encrypt) {
cipher.init(
Cipher.DECRYPT_MODE,
SecretKeySpec(key.encoded, "AES"),
IvParameterSpec(data.iv?.hexToByteArray())
)
String(cipher.doFinal(base64DecodeArray(data.ciphertext.toString())))
} else {
cipher.init(
Cipher.ENCRYPT_MODE,
SecretKeySpec(key.encoded, "AES"),
IvParameterSpec(data.iv?.hexToByteArray())
)
base64Encode(cipher.doFinal(data.ciphertext?.toByteArray()))
}
}
private fun String.hexToByteArray(): ByteArray {
check(length % 2 == 0) { "Must have an even length" }
return chunked(2)
.map { it.toInt(16).toByte() }
.toByteArray()
}
data class AESData(
@JsonProperty("ciphertext") val ciphertext: String? = null,
@JsonProperty("iv") val iv: String? = null,
@JsonProperty("salt") val salt: String? = null,
@JsonProperty("iterations") val iterations: String? = null,
)
}

View file

@ -19,14 +19,6 @@ class Ngefilm : MainAPI() {
TvType.AsianDrama
)
companion object {
private val localServer = arrayOf(
"https://bestx.stream",
"https://chillx.top",
"https://watchx.top",
)
}
override val mainPage = mainPageOf(
"?s&search=advanced&post_type=movie&index&orderby&genre&movieyear&country&quality=" to "Movies Terbaru",
"?s=&search=advanced&post_type=tv&index=&orderby=&genre=&movieyear=&country=&quality=" to "Series Terbaru",
@ -128,12 +120,8 @@ class Ngefilm : MainAPI() {
document.select("ul.muvipro-player-tabs li a").apmap { server ->
val iframe = app.get(fixUrl(server.attr("href"))).document.selectFirst("div.gmr-embed-responsive iframe")
?.attr("src")?.let { fixUrl(it) } ?: return@apmap
if (localServer.any { iframe.startsWith(it) }) {
LocalServer.getUrl(iframe, "$mainUrl/", subtitleCallback, callback)
} else {
loadExtractor(iframe, "$mainUrl/", subtitleCallback, callback)
}
}
return true

View file

@ -10,12 +10,5 @@ class NgefilmPlugin: Plugin() {
override fun load(context: Context) {
// All providers should be added in this manner. Please don't edit the providers list directly.
registerMainAPI(Ngefilm())
registerExtractorAPI(Sbsonic())
registerExtractorAPI(Sbface())
registerExtractorAPI(Sbrapid())
registerExtractorAPI(Lvturbo())
registerExtractorAPI(Ahvsh())
registerExtractorAPI(Guccihide())
registerExtractorAPI(Dooood())
}
}

View file

@ -1,5 +1,5 @@
// use an integer for version numbers
version = 18
version = 20
cloudstream {
@ -23,5 +23,5 @@ cloudstream {
"OVA",
)
iconUrl = "https://www.google.com/s2/favicons?domain=65.108.132.145&sz=%size%"
iconUrl = "https://www.google.com/s2/favicons?domain=oploverz.care&sz=%size%"
}

View file

@ -1,11 +1,8 @@
package com.hexated
import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
import com.lagradost.cloudstream3.extractors.Filesim
import com.lagradost.cloudstream3.utils.*
import org.jsoup.Jsoup
import org.jsoup.nodes.Element
class OploverzProvider : MainAPI() {
@ -23,159 +20,120 @@ class OploverzProvider : MainAPI() {
companion object {
const val acefile = "https://acefile.co"
const val lbx = "https://lbx.to"
const val linkbox = "https://www.linkbox.to"
fun getType(t: String): TvType {
return when {
t.contains("TV") -> TvType.Anime
t.contains("Movie") -> TvType.AnimeMovie
else -> TvType.OVA
}
return if (t.contains("OVA", true) || t.contains("Special")) TvType.OVA
else if (t.contains("Movie", true)) TvType.AnimeMovie
else TvType.Anime
}
fun getStatus(t: String): ShowStatus {
fun getStatus(t: String?): ShowStatus {
return when (t) {
"Finished Airing" -> ShowStatus.Completed
"Completed" -> ShowStatus.Completed
"Currently Airing" -> ShowStatus.Ongoing
"Ongoing" -> ShowStatus.Ongoing
else -> ShowStatus.Completed
}
}
}
override val mainPage = mainPageOf(
"&status=&type=&order=update" to "Episode Terbaru",
"&status=&type=&order=latest" to "Anime Terbaru",
"&sub=&order=popular" to "Popular Anime",
"update" to "Latest Update",
"latest" to "Latest Added",
"popular" to "Popular Anime",
"rating" to "Top Rated",
)
override suspend fun getMainPage(
page: Int,
request: MainPageRequest
): HomePageResponse {
val document = app.get("$mainUrl/anime/?page=$page${request.data}").document
val home = document.select("article[itemscope=itemscope]").mapNotNull {
val document = app.get("$mainUrl/anime-list/page/$page/?title&order=${request.data}&status&type").document
val home = document.select("div.relat > article").mapNotNull {
it.toSearchResult()
}
return newHomePageResponse(request.name, home)
}
private fun getProperAnimeLink(uri: String): String {
return if (uri.contains("/anime/")) {
uri
} else {
var title = uri.substringAfter("$mainUrl/")
title = when {
(title.contains("-episode")) && !(title.contains("-ova")) -> Regex("(.+)-episode").find(
(title.contains("-episode")) && !(title.contains("-movie")) -> Regex("(.+)-episode").find(
title
)?.groupValues?.get(1).toString()
(title.contains("-ova")) -> Regex("(.+)-ova").find(title)?.groupValues?.get(1)
.toString()
(title.contains("-movie")) -> Regex("(.+)-subtitle").find(title)?.groupValues?.get(1)
.toString()
else -> Regex("(.+)-subtitle").find(title)?.groupValues?.get(1).toString()
.replace(Regex("-\\d+"), "")
(title.contains("-movie")) -> Regex("(.+)-movie").find(title)?.groupValues?.get(
1
).toString()
else -> title
}
when {
title.contains("overlord") -> {
title = title.replace("s", "season-")
}
title.contains("kaguya-sama") -> {
title = title.replace("s3", "ultra-romantic")
}
}
"$mainUrl/anime/$title"
}
}
private fun Element.toSearchResult(): AnimeSearchResponse? {
val href = getProperAnimeLink(this.selectFirst("a.tip")!!.attr("href"))
val title = this.selectFirst("h2[itemprop=headline]")?.text()?.trim() ?: return null
val posterUrl = fixUrlNull(this.selectFirst("img")?.attr("src"))
val type = getType(this.selectFirst(".eggtype, .typez")?.text()?.trim().toString())
val title = this.selectFirst("div.title")?.text()?.trim() ?: return null
val href = getProperAnimeLink(this.selectFirst("a")!!.attr("href"))
val posterUrl = this.select("img[itemprop=image]").attr("src").toString()
val type = getType(this.select("div.type").text().trim())
val epNum =
this.selectFirst("span.episode")?.ownText()?.replace(Regex("\\D"), "")?.trim()
?.toIntOrNull()
return newAnimeSearchResponse(title, href, type) {
this.posterUrl = posterUrl
addSub(epNum)
}
}
override suspend fun search(query: String): List<SearchResponse> {
val link = "$mainUrl/?s=$query"
val anime = mutableListOf<SearchResponse>()
(1..2).forEach { page ->
val link = "$mainUrl/page/$page/?s=$query"
val document = app.get(link).document
return document.select("article[itemscope=itemscope]").map {
val title = it.selectFirst(".tt")?.ownText()?.trim().toString()
val poster = fixUrlNull(it.selectFirst("img")?.attr("src"))
val tvType = getType(it.selectFirst(".typez")?.text().toString())
val href = fixUrl(it.selectFirst("a.tip")!!.attr("href"))
newAnimeSearchResponse(title, href, tvType) {
this.posterUrl = poster
addDubStatus(dubExist = false, subExist = true)
val media = document.select(".site-main.relat > article").mapNotNull {
val title = it.selectFirst("div.title > h2")!!.ownText().trim()
val href = it.selectFirst("a")!!.attr("href")
val posterUrl = it.selectFirst("img")!!.attr("src").toString()
val type = getType(it.select("div.type").text().trim())
newAnimeSearchResponse(title, href, type) {
this.posterUrl = posterUrl
}
}
if(media.isNotEmpty()) anime.addAll(media)
}
return anime
}
override suspend fun load(url: String): LoadResponse {
val document = app.get(url).document
val title = document.selectFirst("h1.entry-title")!!.text().trim()
val poster = document.select(".thumb > img").attr("src")
val tags = document.select(".genxed > a").map { it.text() }
val title = document.selectFirst("h1.entry-title")?.text()
?.replace("Subtitle Indonesia", "")?.trim() ?: ""
val type = document.selectFirst("div.alternati span.type")?.text() ?: ""
val year = Regex("\\d, (\\d*)").find(
document.selectFirst(".info-content > .spe > span > time")!!.text().trim()
)?.groupValues?.get(1).toString().toIntOrNull()
val status = getStatus(
document.select(".info-content > .spe > span:nth-child(1)")
.text().trim().replace("Status: ", "")
)
val typeCheck =
when (document.select(".info-content > .spe > span:nth-child(5), .info-content > .spe > span")
.text().trim()) {
"OVA" -> "OVA"
"Movie" -> "Movie"
else -> "TV"
}
val description = document.select(".entry-content > p").text().trim()
val trailer = document.selectFirst("a.trailerbutton")?.attr("href")
val episodes = document.select(".eplister > ul > li").map {
val link = fixUrl(it.select("a").attr("href"))
val name = it.select(".epl-title").text()
val episode = Regex("Episode\\s?(\\d+[.,]?\\d*)").find(name)?.groupValues?.getOrNull(1)?.toIntOrNull()
Episode(link, name, episode = episode)
val episodes = document.select("div.lstepsiode.listeps ul li").mapNotNull {
val header = it.selectFirst("a") ?: return@mapNotNull null
val episode = header.text().trim().toIntOrNull()
val link = fixUrl(header.attr("href"))
Episode(link, header.text(), episode = episode)
}.reversed()
val recommendations =
document.select(".listupd > article[itemscope=itemscope]").mapNotNull { rec ->
val epTitle = rec.selectFirst(".tt")!!.ownText().trim()
val epPoster = rec.selectFirst("img")!!.attr("src")
val epType = getType(rec.selectFirst(".typez")?.text().toString())
val epHref = fixUrl(rec.selectFirst("a.tip")!!.attr("href"))
newAnimeSearchResponse(epTitle, epHref, epType) {
this.posterUrl = epPoster
addDubStatus(dubExist = false, subExist = true)
}
}
return newAnimeLoadResponse(title, url, getType(typeCheck)) {
engName = title
posterUrl = poster
this.year = year
return newAnimeLoadResponse(title, url, getType(type)) {
posterUrl = document.selectFirst("div.thumb > img")?.attr("src")
this.year = document.selectFirst("div.alternati a")?.text()?.filter { it.isDigit() }?.toIntOrNull()
addEpisodes(DubStatus.Subbed, episodes)
showStatus = status
plot = description
this.tags = tags
this.recommendations = recommendations
addTrailer(trailer)
showStatus =
getStatus(
document.selectFirst("div.alternati span:nth-child(2)")?.text()?.trim()
)
plot = document.selectFirst("div.entry-content > p")?.text()?.trim()
this.tags =
document.select("div.genre-info a").map { it.text() }
}
}
override suspend fun loadLinks(
@ -184,41 +142,59 @@ class OploverzProvider : MainAPI() {
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit
): Boolean {
val document = app.get(data).document
val sources = mutableListOf<Pair<String?, String>>()
val streamingSources = document.select(".mobius > .mirror > option").mapNotNull {
"" to fixUrl(Jsoup.parse(base64Decode(it.attr("value"))).select("iframe").attr("src"))
}
if (streamingSources.isNotEmpty()) sources.addAll(streamingSources)
val downloadSources =
document.select("div.mctnx div.soraurlx").mapNotNull { item ->
item.select("a").map { item.select("strong").text() to it.attr("href") }
}.flatten()
if (downloadSources.isNotEmpty()) sources.addAll(downloadSources)
sources.filter { it.second.startsWith("https") }.
apmap { (quality, source) ->
val video = fixedIframe(source)
val videoQuality = getQualityFromName(quality)
if(video.endsWith(".mp4") || video.endsWith(".mkv")) {
callback.invoke(
ExtractorLink(
"Direct",
"Direct",
video,
"",
videoQuality
val document = app.get(data).document
argamap(
{
document.select("div#server ul li div").apmap {
val dataPost = it.attr("data-post")
val dataNume = it.attr("data-nume")
val dataType = it.attr("data-type")
val iframe = app.post(
url = "$mainUrl/wp-admin/admin-ajax.php",
data = mapOf(
"action" to "player_ajax",
"post" to dataPost,
"nume" to dataNume,
"type" to dataType
),
referer = data,
headers = mapOf("X-Requested-With" to "XMLHttpRequest")
).document.select("iframe").attr("src")
loadExtractor(fixedIframe(iframe), "$mainUrl/", subtitleCallback, callback)
}
},
{
document.select("div#download tr").map { el ->
el.select("a").apmap {
loadFixedExtractor(fixedIframe(it.attr("href")), el.select("strong").text(), "$mainUrl/", subtitleCallback, callback)
}
}
}
)
)
} else {
loadExtractor(video, data, subtitleCallback) { link ->
return true
}
private suspend fun loadFixedExtractor(
url: String,
name: String,
referer: String? = null,
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit
) {
loadExtractor(url, referer, subtitleCallback) { link ->
callback.invoke(
ExtractorLink(
link.name,
link.name,
link.url,
link.referer,
videoQuality,
name.fixQuality(),
link.isM3u8,
link.headers,
link.extractorData
@ -226,64 +202,21 @@ class OploverzProvider : MainAPI() {
)
}
}
private fun String.fixQuality() : Int {
return when(this) {
"MP4HD" -> Qualities.P720.value
"FULLHD" -> Qualities.P1080.value
else -> Regex("(\\d{3,4})p").find(this)?.groupValues?.get(1)?.toIntOrNull() ?: Qualities.Unknown.value
}
}
return true
}
private suspend fun fixedIframe(url: String): String {
private fun fixedIframe(url: String): String {
val id = Regex("""(?:/f/|/file/)(\w+)""").find(url)?.groupValues?.getOrNull(1)
return when {
url.startsWith(acefile) -> "$acefile/player/$id"
url.startsWith(lbx) -> {
val itemId = app.get("$linkbox/api/file/share_out_list/?sortField=utime&sortAsc=0&pageNo=1&pageSize=50&shareToken=$id&scene=singleItem&needTpInfo=1&lan=en").parsedSafe<Responses>()?.data?.itemId
"$linkbox/a/f/$itemId"
}
else -> url
url.startsWith(acefile) -> "${acefile}/player/$id"
else -> fixUrl(url)
}
}
data class RList(
@JsonProperty("url") val url: String,
@JsonProperty("resolution") val resolution: String?,
)
data class Data(
@JsonProperty("rList") val rList: List<RList>? = arrayListOf(),
@JsonProperty("itemId") val itemId: String? = null,
)
data class Responses(
@JsonProperty("data") val data: Data?,
)
}
class Streamhide : Filesim() {
override val mainUrl = "https://streamhide.to"
override val name = "Streamhide"
}
open class Pixeldrain : ExtractorApi() {
override val name = "Pixeldrain"
override val mainUrl = "https://pixeldrain.com"
override val requiresReferer = false
override suspend fun getUrl(
url: String,
referer: String?,
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit
) {
val mId = Regex("/([ul]/[\\da-zA-Z\\-]+)").find(url)?.groupValues?.get(1)?.split("/")
callback.invoke(
ExtractorLink(
this.name,
this.name,
"$mainUrl/api/file/${mId?.last() ?: return}?download",
url,
Qualities.Unknown.value,
)
)
}
}

View file

@ -10,7 +10,5 @@ class OploverzProviderPlugin: Plugin() {
override fun load(context: Context) {
// All providers should be added in this manner. Please don't edit the providers list directly.
registerMainAPI(OploverzProvider())
registerExtractorAPI(Streamhide())
registerExtractorAPI(Pixeldrain())
}
}

View file

@ -1,5 +1,5 @@
// use an integer for version numbers
version = 4
version = 5
cloudstream {

View file

@ -9,7 +9,7 @@ import org.jsoup.nodes.Element
import java.net.URLDecoder
class PhimmoichillProvider : MainAPI() {
override var mainUrl = "https://phimmoichilla.net"
override var mainUrl = "https://phimmoichilld.net"
override var name = "Phimmoichill"
override val hasMainPage = true
override var lang = "vi"
@ -26,9 +26,11 @@ class PhimmoichillProvider : MainAPI() {
"$mainUrl/list/phim-le/page-" to "Phim Lẻ",
"$mainUrl/list/phim-bo/page-" to "Phim Bộ",
"$mainUrl/genre/phim-hoat-hinh/page-" to "Phim Hoạt Hình",
"$mainUrl/genre/phim-anime/page-" to "Phim Anime",
"$mainUrl/country/phim-han-quoc/page-" to "Phim Hàn Quốc",
"$mainUrl/country/phim-trung-quoc/page-" to "Phim Trung Quốc",
"$mainUrl/country/phim-thai-lan/page-" to "Phim Thái Lan",
"$mainUrl/genre/phim-sap-chieu/page-" to "Phim Sắp Chiếu",
)
override suspend fun getMainPage(
@ -64,6 +66,10 @@ class PhimmoichillProvider : MainAPI() {
this.posterUrl = posterUrl
addSub(episode)
}
} else if (temp.contains(Regex("Trailer"))) {
newMovieSearchResponse(title, href, TvType.Movie) {
this.posterUrl = posterUrl
}
} else {
val quality =
temp.replace(Regex("(-.*)|(\\|.*)|(?i)(VietSub.*)|(?i)(Thuyết.*)"), "").trim()
@ -89,15 +95,14 @@ class PhimmoichillProvider : MainAPI() {
val title = document.selectFirst("h1[itemprop=name]")?.text()?.trim().toString()
val link = document.select("ul.list-button li:last-child a").attr("href")
val poster = document.selectFirst("div.image img[itemprop=image]")?.attr("src")
val tags = document.select("ul.entry-meta.block-film li:nth-child(4) a").map { it.text() }
val tags = document.select("ul.entry-meta.block-film li:nth-child(4) a").map { it.text()!!.substringAfter("Phim") }
val year = document.select("ul.entry-meta.block-film li:nth-child(2) a").text().trim()
.toIntOrNull()
val tvType = if (document.select("div.latest-episode").isNotEmpty()
) TvType.TvSeries else TvType.Movie
val description = document.select("div#film-content").text().trim()
val trailer =
document.select("div#trailer script").last()?.data()?.substringAfter("file: \"")
?.substringBefore("\",")
val description = document.select("div#film-content").text().substringAfter("Full HD Vietsub Thuyết Minh").substringBefore("@phimmoi").trim()
val trailer = document.select("body script")
.find { it.data().contains("youtube.com") }?.data()?.substringAfterLast("file: \"")?.substringBefore("\",")
val rating =
document.select("ul.entry-meta.block-film li:nth-child(7) span").text().toRatingInt()
val actors = document.select("ul.entry-meta.block-film li:last-child a").map { it.text() }
@ -156,8 +161,7 @@ class PhimmoichillProvider : MainAPI() {
.find { it.data().contains("filmInfo.episodeID =") }?.data()?.let { script ->
val id = script.substringAfter("filmInfo.episodeID = parseInt('")
app.post(
// Not mainUrl
url = "https://phimmoichills.net/pmplayer.php",
url = "${this.mainUrl}/chillsplayer.php",
data = mapOf("qcao" to id, "sv" to "0"),
referer = data,
headers = mapOf(
@ -171,6 +175,7 @@ class PhimmoichillProvider : MainAPI() {
listOf(
Pair("https://so-trym.topphimmoi.org/raw/$key/index.m3u8", "PMFAST"),
Pair("https://dash.megacdn.xyz/raw/$key/index.m3u8", "PMHLS"),
Pair("https://so-trym.phimchill.net/dash/$key/index.m3u8", "PMPRO"),
Pair("https://dash.megacdn.xyz/dast/$key/index.m3u8", "PMBK")
).apmap { (link, source) ->
safeApiCall {

View file

@ -1,5 +1,5 @@
// use an integer for version numbers
version = 10
version = 11
cloudstream {

View file

@ -1,43 +0,0 @@
package com.hexated
import com.lagradost.cloudstream3.SubtitleFile
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.extractors.XStreamCdn
import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.Qualities
import java.net.URI
class Suzihaza: XStreamCdn() {
override val name: String = "Suzihaza"
override val mainUrl: String = "https://suzihaza.com"
}
open class Wibufile : ExtractorApi() {
override val name: String = "Wibufile"
override val mainUrl: String = "https://wibufile.com"
override val requiresReferer = false
override suspend fun getUrl(
url: String,
referer: String?,
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit
) {
val res = app.get(url).text
val video = Regex("src: ['\"](.*?)['\"]").find(res)?.groupValues?.get(1)
callback.invoke(
ExtractorLink(
name,
name,
video ?: return,
"$mainUrl/",
Qualities.Unknown.value,
URI(url).path.endsWith(".m3u8")
)
)
}
}

View file

@ -10,7 +10,5 @@ class SamehadakuPlugin: Plugin() {
override fun load(context: Context) {
// All providers should be added in this manner. Please don't edit the providers list directly.
registerMainAPI(Samehadaku())
registerExtractorAPI(Suzihaza())
registerExtractorAPI(Wibufile())
}
}

View file

@ -1,7 +1,7 @@
import org.jetbrains.kotlin.konan.properties.Properties
// use an integer for version numbers
version = 141
version = 144
android {
defaultConfig {

View file

@ -831,31 +831,36 @@ object SoraExtractor : SoraStream() {
title: String? = null,
season: Int? = null,
episode: Int? = null,
isAnime: Boolean = false,
lastSeason: Int? = null,
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit
) {
val fixTitle = title?.replace("", "-")
val slug = title.createSlug() ?: return
val type = when {
isAnime -> "3"
season == null -> "2"
else -> "1"
}
val res = app.get(
"$kissKhAPI/api/DramaList/Search?q=$title&type=0", referer = "$kissKhAPI/"
"$kissKhAPI/api/DramaList/Search?q=$title&type=$type", referer = "$kissKhAPI/"
).text.let {
tryParseJson<ArrayList<KisskhResults>>(it)
} ?: return
val (id, contentTitle) = if (res.size == 1) {
res.first().id to res.first().title
} else {
if (season == null) {
val data = res.find { it.title.equals(fixTitle, true) }
data?.id to data?.title
} else {
val data = res.find {
it.title?.contains(
"$fixTitle", true
) == true && it.title.contains("Season $season", true)
val slugTitle = it.title.createSlug()
when {
season == null -> slugTitle?.equals(slug) == true
lastSeason == 1 -> slugTitle?.contains(slug) == true
else -> slugTitle?.contains(slug) == true && it.title?.contains("Season $season", true) == true
}
}
data?.id to data?.title
}
}
val resDetail = app.get(
"$kissKhAPI/api/DramaList/Drama/$id?isq=false", referer = "$kissKhAPI/Drama/${
@ -955,7 +960,7 @@ object SoraExtractor : SoraStream() {
) {
val res = app.get(
"$biliBiliAPI/anime/episodes?id=${aniId ?: return}&source_id=bilibili",
referer = kaguyaBaseUrl
referer = otakuzBaseUrl
)
.parsedSafe<BiliBiliDetails>()?.episodes?.find {
it.episodeNumber == episode
@ -964,7 +969,7 @@ object SoraExtractor : SoraStream() {
val sources =
app.get(
"$biliBiliAPI/source?episode_id=${res.sourceEpisodeId}&source_media_id=${res.sourceMediaId}&source_id=${res.sourceId}",
referer = kaguyaBaseUrl
referer = otakuzBaseUrl
)
.parsedSafe<BiliBiliSourcesResponse>()
@ -972,7 +977,7 @@ object SoraExtractor : SoraStream() {
val quality =
app.get(
source.file ?: return@apmap null,
referer = kaguyaBaseUrl
referer = otakuzBaseUrl
).document.selectFirst("Representation")
?.attr("height")
callback.invoke(
@ -980,7 +985,7 @@ object SoraExtractor : SoraStream() {
"BiliBili",
"BiliBili",
source.file,
kaguyaBaseUrl,
"",
quality?.toIntOrNull() ?: Qualities.Unknown.value,
isDash = true
)
@ -1008,16 +1013,18 @@ object SoraExtractor : SoraStream() {
val animeId =
app.get("https://raw.githubusercontent.com/MALSync/MAL-Sync-Backup/master/data/anilist/anime/${aniId ?: return}.json")
.parsedSafe<MALSyncResponses>()?.pages?.zoro?.keys?.map { it }
val headers = mapOf(
"X-Requested-With" to "XMLHttpRequest",
)
animeId?.apmap { id ->
val episodeId = app.get("$zoroAPI/ajax/v2/episode/list/${id ?: return@apmap}")
val episodeId = app.get("$zoroAPI/ajax/episode/list/${id ?: return@apmap}", headers = headers)
.parsedSafe<ZoroResponses>()?.html?.let {
Jsoup.parse(it)
}?.select("div.ss-list a")?.find { it.attr("data-number") == "${episode ?: 1}" }
?.attr("data-id")
val servers =
app.get("$zoroAPI/ajax/v2/episode/servers?episodeId=${episodeId ?: return@apmap}")
app.get("$zoroAPI/ajax/episode/servers?episodeId=${episodeId ?: return@apmap}", headers = headers)
.parsedSafe<ZoroResponses>()?.html?.let { Jsoup.parse(it) }
?.select("div.item.server-item")?.map {
Triple(
@ -1029,10 +1036,10 @@ object SoraExtractor : SoraStream() {
servers?.apmap servers@{ server ->
val iframe =
app.get("$zoroAPI/ajax/v2/episode/sources?id=${server.second ?: return@servers}")
app.get("$zoroAPI/ajax/episode/sources?id=${server.second ?: return@servers}", headers = headers)
.parsedSafe<ZoroResponses>()?.link ?: return@servers
val audio = if (server.third == "sub") "Raw" else "English Dub"
if (server.first == "Vidstreaming" || server.first == "Vidcloud") {
if (server.first.contains(Regex("Vidstreaming|MegaCloud|Vidcloud"))) {
extractRabbitStream(
"${server.first} [$audio]",
iframe,
@ -1255,20 +1262,13 @@ object SoraExtractor : SoraStream() {
extractMirrorUHD(bitLink, base)
}
val tags =
Regex("\\d{3,4}[Pp]\\.?(.*?)\\[").find(quality)?.groupValues?.getOrNull(1)
?.replace(".", " ")?.trim()
?: ""
val qualities =
Regex("(\\d{3,4})[Pp]").find(quality)?.groupValues?.getOrNull(1)?.toIntOrNull()
?: Qualities.Unknown.value
val size =
Regex("(?i)\\[(\\S+\\s?(gb|mb))[]/]").find(quality)?.groupValues?.getOrNull(1)
?.let { "[$it]" } ?: quality
val tags = getUhdTags(quality)
val qualities = getIndexQuality(quality)
val size = getIndexSize(quality)
callback.invoke(
ExtractorLink(
"UHDMovies",
"UHDMovies $tags $size",
"UHDMovies $tags [$size]",
downloadLink ?: return@apmap,
"",
qualities
@ -1280,6 +1280,72 @@ object SoraExtractor : SoraStream() {
}
suspend fun invokePobmovies(
title: String? = null,
year: Int? = null,
callback: (ExtractorLink) -> Unit
) {
val detailDoc = app.get("$pobmoviesAPI/${title.createSlug()}-$year").document
val iframeList = detailDoc.select("div.entry-content p").map { it }
.filter { it.text().filterIframe(year = year, title = title) }.mapNotNull {
it.text() to it.nextElementSibling()?.select("a")?.attr("href")
}.filter { it.second?.contains(Regex("(https:)|(http:)")) == true }
val sources = mutableListOf<Pair<String, String?>>()
if (iframeList.any {
it.first.contains(
"2160p",
true
)
}) {
sources.addAll(iframeList.filter {
it.first.contains(
"2160p",
true
)
})
sources.add(iframeList.first {
it.first.contains(
"1080p",
true
)
})
} else {
sources.addAll(iframeList.filter { it.first.contains("1080p", true) })
}
sources.apmap { (name, link) ->
if (link.isNullOrEmpty()) return@apmap
val videoLink = when {
link.contains("gdtot") -> {
val gdBotLink = extractGdbot(link)
extractGdflix(gdBotLink ?: return@apmap)
}
link.contains("gdflix") -> {
extractGdflix(link)
}
else -> {
return@apmap
}
}
val tags = getUhdTags(name)
val qualities = getIndexQuality(name)
val size = getIndexSize(name)
callback.invoke(
ExtractorLink(
"Pobmovies",
"Pobmovies $tags [${size}]",
videoLink ?: return@apmap,
"",
qualities
)
)
}
}
suspend fun invokeFwatayako(
imdbId: String? = null,
season: Int? = null,
@ -3033,6 +3099,118 @@ object SoraExtractor : SoraStream() {
}
suspend fun invokeEmovies(
title: String? = null,
year: Int? = null,
season: Int? = null,
episode: Int? = null,
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit,
) {
val slug = title.createSlug()
val url = if (season == null) {
"$emoviesAPI/watch-$slug-$year-1080p-hd-online-free/watching.html"
} else {
val first = "$emoviesAPI/watch-$slug-season-$season-$year-1080p-hd-online-free.html"
val second = "$emoviesAPI/watch-$slug-$year-1080p-hd-online-free.html"
if (app.get(first).isSuccessful) first else second
}
val res = app.get(url).document
val id = (if (season == null) {
res.selectFirst("select#selectServer option[sv=oserver]")?.attr("value")
} else {
res.select("div.le-server a").find {
val num =
Regex("Episode (\\d+)").find(it.text())?.groupValues?.get(1)?.toIntOrNull()
num == episode
}?.attr("href")
})?.substringAfter("id=")?.substringBefore("&")
val server =
app.get(
"$emoviesAPI/ajax/v4_get_sources?s=oserver&id=${id ?: return}&_=${APIHolder.unixTimeMS}",
headers = mapOf(
"X-Requested-With" to "XMLHttpRequest"
)
).parsedSafe<EMovieServer>()?.value
val script = app.get(server ?: return, referer = "$emoviesAPI/").document.selectFirst("script:containsData(sources:)")?.data() ?: return
val sources = Regex("sources:\\s*\\[(.*)],").find(script)?.groupValues?.get(1)?.let {
tryParseJson<List<EMovieSources>>("[$it]")
}
val tracks = Regex("tracks:\\s*\\[(.*)],").find(script)?.groupValues?.get(1)?.let {
tryParseJson<List<EMovieTraks>>("[$it]")
}
sources?.map { source ->
M3u8Helper.generateM3u8(
"Emovies",
source.file ?: return@map,
"https://embed.vodstream.xyz/"
).forEach(callback)
}
tracks?.map { track ->
subtitleCallback.invoke(
SubtitleFile(
track.label ?: "",
track.file ?: return@map,
)
)
}
}
suspend fun invokeFourCartoon(
title: String? = null,
year: Int? = null,
season: Int? = null,
episode: Int? = null,
callback: (ExtractorLink) -> Unit
) {
val fixTitle = title.createSlug()
val headers = mapOf(
"X-Requested-With" to "XMLHttpRequest"
)
val url = if (season == null) {
"$fourCartoonAPI/movies/$fixTitle-$year"
} else {
"$fourCartoonAPI/episode/$fixTitle-season-$season-episode-$episode"
}
val document = app.get(url).document
val id = document.selectFirst("input[name=idpost]")?.attr("value")
val server = app.get(
"$fourCartoonAPI/ajax-get-link-stream/?server=streamango&filmId=${id ?: return}",
headers = headers
).text
val hash =
getAndUnpack(app.get(server, referer = fourCartoonAPI).text).substringAfter("(\"")
.substringBefore("\",")
val iframeUrl = getBaseUrl(server)
val source = app.post(
"$iframeUrl/player/index.php?data=$hash&do=getVideo", data = mapOf(
"hast" to hash,
"r" to "$fourCartoonAPI/",
),
headers = headers
).parsedSafe<FourCartoonSources>()?.videoSource
callback.invoke(
ExtractorLink(
"4Cartoon",
"4Cartoon",
source ?: return,
"$iframeUrl/",
Qualities.P720.value,
true,
)
)
}
}

View file

@ -487,3 +487,20 @@ data class EpisodeVo(
data class DumpMediaDetail(
@JsonProperty("episodeVo") val episodeVo: ArrayList<EpisodeVo>? = arrayListOf(),
)
data class EMovieServer(
@JsonProperty("value") val value: String? = null,
)
data class EMovieSources(
@JsonProperty("file") val file: String? = null,
)
data class EMovieTraks(
@JsonProperty("file") val file: String? = null,
@JsonProperty("label") val label: String? = null,
)
data class FourCartoonSources(
@JsonProperty("videoSource") val videoSource: String? = null,
)

View file

@ -49,6 +49,9 @@ import com.hexated.SoraExtractor.invokeShinobiMovies
import com.hexated.SoraExtractor.invokeShivamhw
import com.hexated.SoraExtractor.invokeSmashyStream
import com.hexated.SoraExtractor.invokeDumpStream
import com.hexated.SoraExtractor.invokeEmovies
import com.hexated.SoraExtractor.invokeFourCartoon
import com.hexated.SoraExtractor.invokePobmovies
import com.hexated.SoraExtractor.invokeTvMovies
import com.hexated.SoraExtractor.invokeUhdmovies
import com.hexated.SoraExtractor.invokeVitoenMovies
@ -97,11 +100,11 @@ open class SoraStream : TmdbProvider() {
const val filmxyAPI = "https://www.filmxy.vip"
const val kimcartoonAPI = "https://kimcartoon.li"
const val xMovieAPI = "https://xemovies.to"
const val zoroAPI = "https://sanji.to"
const val zoroAPI = "https://kaido.to"
const val crunchyrollAPI = "https://beta-api.crunchyroll.com"
const val kissKhAPI = "https://kisskh.co"
const val lingAPI = "https://ling-online.net"
const val uhdmoviesAPI = "https://uhdmovies.cc"
const val uhdmoviesAPI = "https://uhdmovies.life"
const val fwatayakoAPI = "https://5100.svetacdn.in"
const val gMoviesAPI = "https://gdrivemovies.xyz"
const val fdMoviesAPI = "https://freedrivemovie.lol"
@ -118,7 +121,7 @@ open class SoraStream : TmdbProvider() {
const val watchSomuchAPI = "https://watchsomuch.tv" // sub only
val gomoviesAPI = base64DecodeAPI("bQ==Y28=ZS4=aW4=bmw=LW8=ZXM=dmk=bW8=Z28=Ly8=czo=dHA=aHQ=")
const val ask4MoviesAPI = "https://ask4movie.net"
const val biliBiliAPI = "https://api-vn.kaguya.app/server"
const val biliBiliAPI = "https://api-vn.otakuz.live/server"
const val watchOnlineAPI = "https://watchonline.ag"
const val nineTvAPI = "https://api.9animetv.live"
const val putlockerAPI = "https://ww7.putlocker.vip"
@ -127,6 +130,9 @@ open class SoraStream : TmdbProvider() {
const val gokuAPI = "https://goku.sx"
const val ridomoviesAPI = "https://ridomovies.pw"
const val navyAPI = "https://navy-issue-i-239.site"
const val emoviesAPI = "https://emovies.si"
const val pobmoviesAPI = "https://pobmovies.cam"
const val fourCartoonAPI = "https://4cartoon.net"
// INDEX SITE
const val blackMoviesAPI = "https://dl.blacklistedbois.workers.dev/0:"
@ -261,8 +267,7 @@ open class SoraStream : TmdbProvider() {
val year = releaseDate?.split("-")?.first()?.toIntOrNull()
val rating = res.vote_average.toString().toRatingInt()
val genres = res.genres?.mapNotNull { it.name }
val isAnime =
genres?.contains("Animation") == true && (res.original_language == "zh" || res.original_language == "ja")
val isAnime = genres?.contains("Animation") == true && (res.original_language == "zh" || res.original_language == "ja")
val keywords = res.keywords?.results?.mapNotNull { it.name }.orEmpty()
.ifEmpty { res.keywords?.keywords?.mapNotNull { it.name } }
@ -302,7 +307,7 @@ open class SoraStream : TmdbProvider() {
epsTitle = eps.name,
jpTitle = res.alternative_titles?.results?.find { it.iso_3166_1 == "JP" }?.title,
date = season.airDate,
airedDate = res.releaseDate ?: res.firstAirDate
airedDate = res.releaseDate ?: res.firstAirDate,
).toJson(),
name = eps.name + if(isUpcoming(eps.airDate)) " - [UPCOMING]" else "",
season = eps.seasonNumber,
@ -346,7 +351,7 @@ open class SoraStream : TmdbProvider() {
orgTitle = orgTitle,
isAnime = isAnime,
jpTitle = res.alternative_titles?.results?.find { it.iso_3166_1 == "JP" }?.title,
airedDate = res.releaseDate ?: res.firstAirDate
airedDate = res.releaseDate ?: res.firstAirDate,
).toJson(),
) {
this.posterUrl = poster
@ -493,18 +498,18 @@ open class SoraStream : TmdbProvider() {
)
},
{
invokeKimcartoon(res.title, res.season, res.episode, subtitleCallback, callback)
},
{
invokeXmovies(
res.title,
res.year,
res.season,
res.episode,
subtitleCallback,
callback
)
if(!res.isAnime) invokeKimcartoon(res.title, res.season, res.episode, subtitleCallback, callback)
},
// {
// invokeXmovies(
// res.title,
// res.year,
// res.season,
// res.episode,
// subtitleCallback,
// callback
// )
// },
{
if (!res.isAnime) invokeFmovies(
res.title,
@ -516,7 +521,7 @@ open class SoraStream : TmdbProvider() {
)
},
{
invokeKisskh(res.title, res.season, res.episode, subtitleCallback, callback)
invokeKisskh(res.title, res.season, res.episode, res.isAnime, res.lastSeason, subtitleCallback, callback)
},
{
invokeLing(
@ -803,6 +808,15 @@ open class SoraStream : TmdbProvider() {
{
invokeNavy(res.imdbId, res.season, res.episode, callback)
},
{
if (!res.isAnime) invokeEmovies(res.title, res.year, res.season, res.episode, subtitleCallback, callback)
},
{
if(!res.isAnime && res.season == null) invokePobmovies(res.title, res.year, callback)
},
{
if(!res.isAnime) invokeFourCartoon(res.title, res.year, res.season, res.episode, callback)
}
)
return true

View file

@ -27,6 +27,8 @@ import com.hexated.SoraExtractor.invokeRidomovies
import com.hexated.SoraExtractor.invokeSeries9
import com.hexated.SoraExtractor.invokeSmashyStream
import com.hexated.SoraExtractor.invokeDumpStream
import com.hexated.SoraExtractor.invokeEmovies
import com.hexated.SoraExtractor.invokeFourCartoon
import com.hexated.SoraExtractor.invokeVidSrc
import com.hexated.SoraExtractor.invokeWatchOnline
import com.hexated.SoraExtractor.invokeWatchsomuch
@ -186,7 +188,7 @@ class SoraStreamLite : SoraStream() {
)
},
{
invokeKimcartoon(res.title, res.season, res.episode, subtitleCallback, callback)
if(!res.isAnime) invokeKimcartoon(res.title, res.season, res.episode, subtitleCallback, callback)
},
{
invokeSmashyStream(
@ -198,16 +200,16 @@ class SoraStreamLite : SoraStream() {
callback
)
},
{
invokeXmovies(
res.title,
res.year,
res.season,
res.episode,
subtitleCallback,
callback
)
},
// {
// invokeXmovies(
// res.title,
// res.year,
// res.season,
// res.episode,
// subtitleCallback,
// callback
// )
// },
{
if (!res.isAnime) invokeFmovies(
res.title,
@ -219,7 +221,7 @@ class SoraStreamLite : SoraStream() {
)
},
{
invokeKisskh(res.title, res.season, res.episode, subtitleCallback, callback)
invokeKisskh(res.title, res.season, res.episode, res.isAnime, res.lastSeason, subtitleCallback, callback)
},
{
invokeLing(
@ -287,6 +289,25 @@ class SoraStreamLite : SoraStream() {
res.year,
callback
)
},
{
if (!res.isAnime) invokeEmovies(
res.title,
res.year,
res.season,
res.episode,
subtitleCallback,
callback
)
},
{
if(!res.isAnime) invokeFourCartoon(
res.title,
res.year,
res.season,
res.episode,
callback
)
}
)

View file

@ -2,7 +2,6 @@ package com.hexated
import android.util.Base64
import com.fasterxml.jackson.annotation.JsonProperty
import com.hexated.DumpUtils.createHeaders
import com.hexated.DumpUtils.queryApi
import com.hexated.SoraStream.Companion.anilistAPI
import com.hexated.SoraStream.Companion.base64DecodeAPI
@ -51,7 +50,7 @@ import kotlin.math.min
val bflixChipperKey = base64DecodeAPI("Yjc=ejM=TzA=YTk=WHE=WnU=bXU=RFo=")
const val bflixKey = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
const val kaguyaBaseUrl = "https://kaguya.app/"
const val otakuzBaseUrl = "https://otakuz.live/"
val soraHeaders = mapOf(
"lang" to "en",
"versioncode" to "33",
@ -111,7 +110,12 @@ data class FilmxyCookies(
val wSec: String? = null,
)
fun String.filterIframe(seasonNum: Int?, lastSeason: Int?, year: Int?, title: String?): Boolean {
fun String.filterIframe(
seasonNum: Int? = null,
lastSeason: Int? = null,
year: Int?,
title: String?
): Boolean {
val slug = title.createSlug()
val dotSlug = slug?.replace("-", ".")
val spaceSlug = slug?.replace("-", " ")
@ -271,8 +275,8 @@ suspend fun extractDrivebot(url: String): String? {
suspend fun extractGdflix(url: String): String? {
val iframeGdflix =
app.get(url).document.selectFirst("li.flex.flex-col.py-6 a:contains(GDFlix Direct)")
?.attr("href") ?: return null
if (!url.contains("gdflix")) app.get(url).document.selectFirst("li.flex.flex-col.py-6 a:contains(GDFlix Direct)")
?.attr("href") ?: return null else url
val base = getBaseUrl(iframeGdflix)
val req = app.get(iframeGdflix).document.selectFirst("script:containsData(replace)")?.data()
@ -586,7 +590,10 @@ suspend fun getDumpIdAndType(title: String?, year: Int?, season: Int?): Pair<Str
it.name?.contains(
"$title",
true
) == true && (it.releaseTime == "$year" || it.name.contains("Season $season", true)) && it.domainType == 1
) == true && (it.releaseTime == "$year" || it.name.contains(
"Season $season",
true
)) && it.domainType == 1
}
else -> {
it.name?.contains(Regex("(?i)$title\\s?($season|${season.toRomanNumeral()}|Season\\s$season)")) == true && it.releaseTime == "$year" && it.domainType == 1
@ -744,7 +751,11 @@ suspend fun bypassTechmny(url: String): String? {
val thirdPage = secondPage.getNextTechPage().text
val goToken = thirdPage.substringAfter("?go=").substringBefore("\"")
val tokenUrl = "$postUrl?go=$goToken"
val headers = mapOf("Cookie" to "$goToken=${secondPage.select("form#landing input[name=_wp_http2]").attr("value")}")
val headers = mapOf(
"Cookie" to "$goToken=${
secondPage.select("form#landing input[name=_wp_http2]").attr("value")
}"
)
Pair(tokenUrl, headers)
}
val driveUrl =
@ -1279,6 +1290,12 @@ fun String.getFileSize(): Float? {
}
}
fun getUhdTags(str: String?): String {
return Regex("\\d{3,4}[Pp]\\.?(.*?)\\[").find(str ?: "")?.groupValues?.getOrNull(1)
?.replace(".", " ")?.trim()
?: str ?: ""
}
fun getIndexQualityTags(str: String?, fullTag: Boolean = false): String {
return if (fullTag) Regex("(?i)(.*)\\.(?:mkv|mp4|avi)").find(str ?: "")?.groupValues?.get(1)
?.trim() ?: str ?: "" else Regex("(?i)\\d{3,4}[pP]\\.?(.*?)\\.(mkv|mp4|avi)").find(
@ -1492,6 +1509,7 @@ fun isUpcoming(dateString: String?) : Boolean {
val dateTime = format.parse(dateString)?.time ?: return false
return unixTimeMS < dateTime
}
fun decode(input: String): String = URLDecoder.decode(input, "utf-8")
fun encode(input: String): String = URLEncoder.encode(input, "utf-8").replace("+", "%20")
@ -1761,12 +1779,16 @@ object RabbitStream {
ioSafe { app.get("$extractorData&t=${generateTimeStamp()}&sid=${pollingData.sid}") }
}
}
val getSourcesUrl = "${
mainIframeUrl.replace(
val mainIframeAjax = mainIframeUrl.let {
if(it.contains("/embed-2/e-1")) it.replace(
"/embed-2/e-1",
"/embed-2/ajax/e-1"
) else it.replace(
"/embed",
"/ajax/embed"
)
}/getSources?id=$mainIframeId${sid?.let { "$&sId=$it" } ?: ""}"
}
val getSourcesUrl = "$mainIframeAjax/getSources?id=$mainIframeId${sid?.let { "$&sId=$it" } ?: ""}"
val response = app.get(
getSourcesUrl,
referer = mainUrl,
@ -1929,7 +1951,7 @@ object RabbitStream {
}
suspend fun getZoroKey(): String {
return app.get("https://raw.githubusercontent.com/enimax-anime/key/e6/key.txt").text
return app.get("https://raw.githubusercontent.com/enimax-anime/key/e0/key.txt").text
}
private inline fun <reified T> decryptMapped(input: String, key: String): T? {
@ -2021,7 +2043,8 @@ object DumpUtils {
return app.custom(
method,
url,
requestBody = if(method == "POST") params.toJson().toRequestBody(RequestBodyTypes.JSON.toMediaTypeOrNull()) else null,
requestBody = if (method == "POST") params.toJson()
.toRequestBody(RequestBodyTypes.JSON.toMediaTypeOrNull()) else null,
params = if (method == "GET") params else emptyMap(),
headers = createHeaders(params)
).parsedSafe<HashMap<String, String>>()?.get("data").let {
@ -2062,7 +2085,8 @@ object DumpUtils {
}
private fun getAesKey(): String? {
val publicKey = RSAEncryptionHelper.getPublicKeyFromString(BuildConfig.DUMP_KEY) ?: return null
val publicKey =
RSAEncryptionHelper.getPublicKeyFromString(BuildConfig.DUMP_KEY) ?: return null
return RSAEncryptionHelper.encryptText(deviceId, publicKey)
}

View file

@ -6,7 +6,7 @@ cloudstream {
language = "en"
// All of these properties are optional, you can safely remove them
description = "- StremioX allows you to use stream addons \n- StremioC allows you to use catalog addons \n<!> Requires Setup"
description = "[!] Requires Setup \n- StremioX allows you to use stream addons \n- StremioC allows you to use catalog addons"
authors = listOf("Hexated")
/**