mirror of
https://github.com/hexated/cloudstream-extensions-hexated.git
synced 2024-08-15 00:03:22 +00:00
Merge branch 'master' into adityajd-patch-1
This commit is contained in:
commit
67d8853f1e
110 changed files with 4572 additions and 2162 deletions
19
.github/workflows/build.yml
vendored
19
.github/workflows/build.yml
vendored
|
@ -19,12 +19,12 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@master
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: "src"
|
||||
|
||||
- name: Checkout builds
|
||||
uses: actions/checkout@master
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: "builds"
|
||||
path: "builds"
|
||||
|
@ -33,8 +33,9 @@ jobs:
|
|||
run: rm $GITHUB_WORKSPACE/builds/*.cs3 || true
|
||||
|
||||
- name: Setup JDK 11
|
||||
uses: actions/setup-java@v1
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: "adopt"
|
||||
java-version: 11
|
||||
|
||||
- name: Setup Android SDK
|
||||
|
@ -54,7 +55,11 @@ jobs:
|
|||
ZSHOW_API: ${{ secrets.ZSHOW_API }}
|
||||
SFMOVIES_API: ${{ secrets.SFMOVIES_API }}
|
||||
CINEMATV_API: ${{ secrets.CINEMATV_API }}
|
||||
OMOVIES_API: ${{ secrets.OMOVIES_API }}
|
||||
GHOSTX_API: ${{ secrets.GHOSTX_API }}
|
||||
SUPERSTREAM_FIRST_API: ${{ secrets.SUPERSTREAM_FIRST_API }}
|
||||
SUPERSTREAM_SECOND_API: ${{ secrets.SUPERSTREAM_SECOND_API }}
|
||||
SUPERSTREAM_THIRD_API: ${{ secrets.SUPERSTREAM_THIRD_API }}
|
||||
SUPERSTREAM_FOURTH_API: ${{ secrets.SUPERSTREAM_FOURTH_API }}
|
||||
run: |
|
||||
cd $GITHUB_WORKSPACE/src
|
||||
echo TMDB_API=$TMDB_API >> local.properties
|
||||
|
@ -69,7 +74,11 @@ jobs:
|
|||
echo ZSHOW_API=$ZSHOW_API >> local.properties
|
||||
echo SFMOVIES_API=$SFMOVIES_API >> local.properties
|
||||
echo CINEMATV_API=$CINEMATV_API >> local.properties
|
||||
echo OMOVIES_API=$OMOVIES_API >> local.properties
|
||||
echo GHOSTX_API=$GHOSTX_API >> local.properties
|
||||
echo SUPERSTREAM_FIRST_API=$SUPERSTREAM_FIRST_API >> local.properties
|
||||
echo SUPERSTREAM_SECOND_API=$SUPERSTREAM_SECOND_API >> local.properties
|
||||
echo SUPERSTREAM_THIRD_API=$SUPERSTREAM_THIRD_API >> local.properties
|
||||
echo SUPERSTREAM_FOURTH_API=$SUPERSTREAM_FOURTH_API >> local.properties
|
||||
|
||||
- name: Build Plugins
|
||||
run: |
|
||||
|
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -10,4 +10,4 @@
|
|||
.cxx
|
||||
local.properties
|
||||
.vscode
|
||||
/ExampleProvider/
|
||||
/ExampleProvider/
|
|
@ -1,7 +1,7 @@
|
|||
import org.jetbrains.kotlin.konan.properties.Properties
|
||||
|
||||
// use an integer for version numbers
|
||||
version = 9
|
||||
version = 10
|
||||
|
||||
android {
|
||||
defaultConfig {
|
||||
|
@ -38,5 +38,5 @@ cloudstream {
|
|||
"OVA",
|
||||
)
|
||||
|
||||
iconUrl = "https://media.discordapp.net/attachments/1059306855865782282/1123970193274712096/Anichi.png"
|
||||
iconUrl = "https://cdn.discordapp.com/attachments/1109266606292488297/1200425504432472176/Anichi.png"
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
package com.hexated
|
||||
|
||||
import com.hexated.AnichiExtractors.invokeExternalSources
|
||||
import com.hexated.AnichiExtractors.invokeInternalSources
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addActors
|
||||
|
@ -214,25 +213,12 @@ open class Anichi : MainAPI() {
|
|||
|
||||
val loadData = parseJson<AnichiLoadData>(data)
|
||||
|
||||
argamap(
|
||||
{
|
||||
invokeInternalSources(
|
||||
loadData.hash,
|
||||
loadData.dubStatus,
|
||||
loadData.episode,
|
||||
subtitleCallback,
|
||||
callback
|
||||
)
|
||||
},
|
||||
{
|
||||
invokeExternalSources(
|
||||
loadData.idMal,
|
||||
loadData.dubStatus,
|
||||
loadData.episode,
|
||||
subtitleCallback,
|
||||
callback
|
||||
)
|
||||
}
|
||||
invokeInternalSources(
|
||||
loadData.hash,
|
||||
loadData.dubStatus,
|
||||
loadData.episode,
|
||||
subtitleCallback,
|
||||
callback
|
||||
)
|
||||
|
||||
return true
|
||||
|
@ -245,7 +231,6 @@ open class Anichi : MainAPI() {
|
|||
|
||||
const val anilistApi = "https://graphql.anilist.co"
|
||||
const val jikanApi = "https://api.jikan.moe/v4"
|
||||
const val marinHost = "https://marin.moe"
|
||||
|
||||
private const val mainHash = "e42a4466d984b2c0a2cecae5dd13aa68867f634b16ee0f17b380047d14482406"
|
||||
private const val popularHash = "31a117653812a2547fd981632e8c99fa8bf8a75c4ef1a77a1567ef1741a7ab9c"
|
||||
|
|
|
@ -121,67 +121,6 @@ object AnichiExtractors : Anichi() {
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun invokeExternalSources(
|
||||
idMal: Int? = null,
|
||||
dubStatus: String,
|
||||
episode: String,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit,
|
||||
) {
|
||||
val ids = app.get("https://api.malsync.moe/mal/anime/${idMal ?: return}")
|
||||
.parsedSafe<MALSyncResponses>()?.sites
|
||||
|
||||
if (dubStatus == "sub") invokeMarin(ids?.marin?.keys?.firstOrNull(), episode, callback)
|
||||
|
||||
}
|
||||
|
||||
private suspend fun invokeMarin(
|
||||
id: String? = null,
|
||||
episode: String,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
) {
|
||||
val url = "$marinHost/anime/${id ?: return}/$episode"
|
||||
val cookies = app.get(
|
||||
"$marinHost/anime",
|
||||
headers = mapOf(
|
||||
"Cookie" to "__ddg1_=;__ddg2_=;"
|
||||
),
|
||||
referer = "$marinHost/anime",
|
||||
).cookies.let {
|
||||
decode(it["XSRF-TOKEN"].toString()) to decode(it["marin_session"].toString())
|
||||
}
|
||||
|
||||
val json = app.get(
|
||||
url,
|
||||
headers = mapOf(
|
||||
"Accept" to "text/html, application/xhtml+xml",
|
||||
"Cookie" to "__ddg1=;__ddg2_=;XSRF-TOKEN=${cookies.first};marin_session=${cookies.second};",
|
||||
"X-XSRF-TOKEN" to cookies.first
|
||||
),
|
||||
referer = "$marinHost/anime/$id"
|
||||
).document.selectFirst("div#app")?.attr("data-page")
|
||||
tryParseJson<MarinResponses>(json)?.props?.video?.data?.mirror?.map { video ->
|
||||
callback.invoke(
|
||||
ExtractorLink(
|
||||
"Marin",
|
||||
"Marin",
|
||||
video.code?.file ?: return@map,
|
||||
url,
|
||||
video.code.height ?: Qualities.Unknown.value,
|
||||
headers = mapOf(
|
||||
"Accept" to "video/webm,video/ogg,video/*;q=0.9,application/ogg;q=0.7,audio/*;q=0.6,*/*;q=0.5",
|
||||
"Accept-Language" to "en-US,en;q=0.5",
|
||||
"Cookie" to "__ddg1=;__ddg2_=; XSRF-TOKEN=${cookies.first}; marin_session=${cookies.second};",
|
||||
"Connection" to "keep-alive",
|
||||
"Sec-Fetch-Dest" to "video",
|
||||
"Sec-Fetch-Mode" to "cors",
|
||||
"Sec-Fetch-Site" to "cross-site",
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun invokeGogo(
|
||||
link: String,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
|
|
|
@ -240,7 +240,6 @@ data class PageStatus(
|
|||
@JsonProperty("__typename") val _typename: String? = null
|
||||
)
|
||||
|
||||
|
||||
data class Recommendations(
|
||||
@JsonProperty("anyCard") val anyCard: AnyCard? = null,
|
||||
@JsonProperty("pageStatus") val pageStatus: PageStatus? = PageStatus(),
|
||||
|
@ -255,38 +254,4 @@ data class QueryPopular(
|
|||
|
||||
data class DataPopular(
|
||||
@JsonProperty("queryPopular") val queryPopular: QueryPopular? = QueryPopular()
|
||||
)
|
||||
|
||||
data class MALSyncSites(
|
||||
@JsonProperty("Zoro") val zoro: HashMap<String?, HashMap<String, String?>>? = hashMapOf(),
|
||||
@JsonProperty("Marin") val marin: HashMap<String?, HashMap<String, String?>>? = hashMapOf(),
|
||||
)
|
||||
|
||||
data class MALSyncResponses(
|
||||
@JsonProperty("Sites") val sites: MALSyncSites? = null,
|
||||
)
|
||||
|
||||
data class MarinCode(
|
||||
@JsonProperty("file") val file: String? = null,
|
||||
@JsonProperty("height") val height: Int? = null,
|
||||
)
|
||||
|
||||
data class MarinMirror(
|
||||
@JsonProperty("code") val code: MarinCode? = null,
|
||||
)
|
||||
|
||||
data class MarinData(
|
||||
@JsonProperty("mirror") val mirror: ArrayList<MarinMirror>? = arrayListOf(),
|
||||
)
|
||||
|
||||
data class MarinVideos(
|
||||
@JsonProperty("data") val data: MarinData? = null,
|
||||
)
|
||||
|
||||
data class MarinProps(
|
||||
@JsonProperty("video") val video: MarinVideos? = null,
|
||||
)
|
||||
|
||||
data class MarinResponses(
|
||||
@JsonProperty("props") val props: MarinProps? = null,
|
||||
)
|
|
@ -1,5 +1,5 @@
|
|||
// use an integer for version numbers
|
||||
version = 2
|
||||
version = 3
|
||||
|
||||
|
||||
cloudstream {
|
||||
|
|
|
@ -84,17 +84,13 @@ class Anilibria : MainAPI() {
|
|||
val document = app.get(url).document
|
||||
|
||||
val title = document.selectFirst("h1.release-title")?.text() ?: return null
|
||||
val poster = fixUrlNull(document.selectFirst("img#adminPoster")?.attr("src"))
|
||||
val trackTitle = (document.selectFirst("h1.release-title br")?.nextSibling()
|
||||
val enTitle = (document.selectFirst("h1.release-title br")?.nextSibling()
|
||||
?: document.selectFirst("h1.release-title")?.text()?.substringAfter("/")?.trim()).toString()
|
||||
val poster = fixUrlNull(document.selectFirst("img#adminPoster")?.attr("src"))
|
||||
val type = document.selectFirst("div#xreleaseInfo b:contains(Тип:)")?.nextSibling()
|
||||
.toString().substringBefore(",").trim()
|
||||
val trackType = type.let {
|
||||
if(it.contains("Фильм", true)) "movie" else "tv"
|
||||
}
|
||||
val year = document.selectFirst("div#xreleaseInfo b:contains(Сезон:)")?.nextElementSibling()
|
||||
?.text()?.filter { it.isDigit() }?.toIntOrNull()
|
||||
val (malId, anilistId, image, cover) = getTracker(trackTitle, trackType, year)
|
||||
val episodes = document.select("script").find { it.data().contains("var player =") }?.data()
|
||||
?.substringAfter("file:[")?.substringBefore("],")?.let { data ->
|
||||
tryParseJson<List<Episodes>>("[$data]")?.mapNotNull { eps ->
|
||||
|
@ -106,15 +102,14 @@ class Anilibria : MainAPI() {
|
|||
}
|
||||
}
|
||||
return newAnimeLoadResponse(title, url, getType(type)) {
|
||||
posterUrl = image ?: poster
|
||||
backgroundPosterUrl = cover ?: image ?: poster
|
||||
japName = enTitle
|
||||
posterUrl = poster
|
||||
backgroundPosterUrl = poster
|
||||
this.year = year
|
||||
addEpisodes(DubStatus.Subbed, episodes)
|
||||
plot = document.select("p.detail-description").text().trim()
|
||||
this.tags = document.selectFirst("div#xreleaseInfo b:contains(Жанры:)")?.nextSibling()
|
||||
.toString().split(",").map { it.trim() }
|
||||
addMalId(malId)
|
||||
addAniListId(anilistId?.toIntOrNull())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -143,43 +138,6 @@ class Anilibria : MainAPI() {
|
|||
return true
|
||||
}
|
||||
|
||||
private suspend fun getTracker(title: String?, type: String?, year: Int?): Tracker {
|
||||
val res = app.get("https://api.consumet.org/meta/anilist/$title")
|
||||
.parsedSafe<AniSearch>()?.results?.find { media ->
|
||||
(media.title?.english.equals(title, true) || media.title?.romaji.equals(
|
||||
title,
|
||||
true
|
||||
)) || (media.type.equals(type, true) && media.releaseDate == year)
|
||||
}
|
||||
return Tracker(res?.malId, res?.aniId, res?.image, res?.cover)
|
||||
}
|
||||
|
||||
data class Tracker(
|
||||
val malId: Int? = null,
|
||||
val aniId: String? = null,
|
||||
val image: String? = null,
|
||||
val cover: String? = null,
|
||||
)
|
||||
|
||||
data class Title(
|
||||
@JsonProperty("romaji") val romaji: String? = null,
|
||||
@JsonProperty("english") val english: String? = null,
|
||||
)
|
||||
|
||||
data class Results(
|
||||
@JsonProperty("id") val aniId: String? = null,
|
||||
@JsonProperty("malId") val malId: Int? = null,
|
||||
@JsonProperty("title") val title: Title? = null,
|
||||
@JsonProperty("releaseDate") val releaseDate: Int? = null,
|
||||
@JsonProperty("type") val type: String? = null,
|
||||
@JsonProperty("image") val image: String? = null,
|
||||
@JsonProperty("cover") val cover: String? = null,
|
||||
)
|
||||
|
||||
data class AniSearch(
|
||||
@JsonProperty("results") val results: ArrayList<Results>? = arrayListOf(),
|
||||
)
|
||||
|
||||
private data class Episodes(
|
||||
@JsonProperty("file") val file: String? = null,
|
||||
@JsonProperty("title") val title: String? = null,
|
||||
|
@ -194,4 +152,4 @@ class Anilibria : MainAPI() {
|
|||
@JsonProperty("mes") val mes: String? = null,
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
|
|
23
AnimeDekhoProvider/build.gradle.kts
Normal file
23
AnimeDekhoProvider/build.gradle.kts
Normal file
|
@ -0,0 +1,23 @@
|
|||
version = 4
|
||||
|
||||
cloudstream {
|
||||
language = "hi"
|
||||
authors = listOf("anon")
|
||||
|
||||
/**
|
||||
* Status int as the following:
|
||||
* 0: Down
|
||||
* 1: Ok
|
||||
* 2: Slow
|
||||
* 3: Beta only
|
||||
* */
|
||||
status = 1 // will be 3 if unspecified
|
||||
tvTypes = listOf(
|
||||
"TvSeries",
|
||||
"Movie",
|
||||
"Anime",
|
||||
"Cartoon"
|
||||
)
|
||||
|
||||
iconUrl = "https://animedekho.com/wp-content/uploads/2023/07/AnimeDekho-Logo-300x-1.pngg"
|
||||
}
|
2
AnimeDekhoProvider/src/main/AndroidManifest.xml
Normal file
2
AnimeDekhoProvider/src/main/AndroidManifest.xml
Normal file
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest package="com.anon"/>
|
|
@ -0,0 +1,13 @@
|
|||
package com.anon
|
||||
|
||||
import com.lagradost.cloudstream3.plugins.CloudstreamPlugin
|
||||
import com.lagradost.cloudstream3.plugins.Plugin
|
||||
import android.content.Context
|
||||
|
||||
@CloudstreamPlugin
|
||||
class AnimeDekhoPlugin: Plugin() {
|
||||
override fun load(context: Context) {
|
||||
registerMainAPI(AnimeDekhoProvider())
|
||||
//addExtractor(MultiQualityXYZ())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,148 @@
|
|||
package com.anon
|
||||
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.extractors.helper.AesHelper.cryptoAESHandler
|
||||
import com.lagradost.cloudstream3.utils.*
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.toJson
|
||||
import org.jsoup.nodes.Element
|
||||
|
||||
class AnimeDekhoProvider : MainAPI() {
|
||||
override var mainUrl = "https://animedekho.com"
|
||||
override var name = "Anime Dekho"
|
||||
override val hasMainPage = true
|
||||
override var lang = "hi"
|
||||
override val hasDownloadSupport = true
|
||||
private val serverUrl = "https://vidxstream.xyz"
|
||||
|
||||
override val supportedTypes =
|
||||
setOf(
|
||||
TvType.Cartoon,
|
||||
TvType.Anime,
|
||||
TvType.AnimeMovie,
|
||||
TvType.Movie,
|
||||
)
|
||||
|
||||
override val mainPage =
|
||||
mainPageOf(
|
||||
"/series/" to "Series",
|
||||
"/movie/" to "Movies",
|
||||
"/category/anime/" to "Anime",
|
||||
"/category/cartoon/" to "Cartoon",
|
||||
)
|
||||
|
||||
override suspend fun getMainPage(
|
||||
page: Int,
|
||||
request: MainPageRequest,
|
||||
): HomePageResponse {
|
||||
val link = "$mainUrl${request.data}"
|
||||
val document = app.get(link).document
|
||||
val home =
|
||||
document.select("article").mapNotNull {
|
||||
it.toSearchResult()
|
||||
}
|
||||
return newHomePageResponse(request.name, home)
|
||||
}
|
||||
|
||||
private fun Element.toSearchResult(): AnimeSearchResponse? {
|
||||
val href = this.selectFirst("a.lnk-blk")?.attr("href") ?: return null
|
||||
val title = this.selectFirst("header h2")?.text() ?: "null"
|
||||
val posterUrl = this.selectFirst("div figure img")?.attr("src")
|
||||
|
||||
return newAnimeSearchResponse(title, Media(href, posterUrl).toJson(), TvType.Anime, false) {
|
||||
this.posterUrl = posterUrl
|
||||
addDubStatus(dubExist = true, subExist = true)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun search(query: String): List<AnimeSearchResponse> {
|
||||
val document = app.get("$mainUrl/?s=$query").document
|
||||
return document.select("ul[data-results] li article").mapNotNull {
|
||||
it.toSearchResult()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun load(url: String): LoadResponse {
|
||||
|
||||
val media = parseJson<Media>(url)
|
||||
val document = app.get(media.url).document
|
||||
|
||||
val title = document.selectFirst("h1.entry-title")?.text()?.trim()
|
||||
?: document.selectFirst("meta[property=og:image:alt]")?.attr("content") ?: "No Title"
|
||||
val poster = fixUrlNull(document.selectFirst("div.post-thumbnail figure img")?.attr("src") ?: media.poster)
|
||||
val plot = document.selectFirst("div.entry-content p")?.text()?.trim()
|
||||
?: document.selectFirst("meta[name=twitter:description]")?.attr("content")
|
||||
val year = (document.selectFirst("span.year")?.text()?.trim()
|
||||
?: document.selectFirst("meta[property=og:updated_time]")?.attr("content")
|
||||
?.substringBefore("-"))?.toIntOrNull()
|
||||
val lst = document.select("ul.seasons-lst li")
|
||||
|
||||
return if (lst.isEmpty()) {
|
||||
newMovieLoadResponse(title, url, TvType.Movie, Media(media.url, mediaType = 1).toJson()) {
|
||||
this.posterUrl = poster
|
||||
this.plot = plot
|
||||
this.year = year
|
||||
}
|
||||
} else {
|
||||
val episodes = document.select("ul.seasons-lst li").mapNotNull {
|
||||
val name = it.selectFirst("h3.title")?.text() ?: "null"
|
||||
val href = it.selectFirst("a")?.attr("href") ?: return@mapNotNull null
|
||||
Episode(Media(href, mediaType = 2).toJson(), name)
|
||||
}
|
||||
newTvSeriesLoadResponse(title, url, TvType.TvSeries, episodes) {
|
||||
this.posterUrl = poster
|
||||
this.plot = plot
|
||||
this.year = year
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit,
|
||||
): Boolean {
|
||||
val media = parseJson<Media>(data)
|
||||
val body = app.get(media.url).document.selectFirst("body")?.attr("class") ?: return false
|
||||
val term = Regex("""(?:term|postid)-(\d+)""").find(body)?.groupValues?.get(1) ?: throw ErrorLoadingException("no id found")
|
||||
val vidLink = app.get("$mainUrl/?trembed=0&trid=$term&trtype=${media.mediaType}")
|
||||
.document.selectFirst("iframe")?.attr("src")
|
||||
?: throw ErrorLoadingException("no iframe found")
|
||||
|
||||
val doc = app.get(vidLink).text
|
||||
val master = Regex("""JScript[\w+]?\s*=\s*'([^']+)""").find(doc)!!.groupValues[1]
|
||||
val decrypt = cryptoAESHandler(master, "a7igbpIApajDyNe".toByteArray(), false)?.replace("\\", "")
|
||||
?: throw ErrorLoadingException("error decrypting")
|
||||
val vidFinal = Regex("""file:\s*"(https:[^"]+)"""").find(decrypt)!!.groupValues[1]
|
||||
|
||||
val headers =
|
||||
mapOf(
|
||||
"accept" to "*/*",
|
||||
"accept-language" to "en-US,en;q=0.5",
|
||||
"Origin" to serverUrl,
|
||||
"Accept-Encoding" to "gzip, deflate, br",
|
||||
"Connection" to "keep-alive",
|
||||
// "Referer" to "https://vidxstream.xyz/",
|
||||
"Sec-Fetch-Dest" to "empty",
|
||||
"Sec-Fetch-Mode" to "cors",
|
||||
"Sec-Fetch-Site" to "cross-site",
|
||||
"user-agent" to "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/116.0",
|
||||
)
|
||||
|
||||
callback.invoke(
|
||||
ExtractorLink(
|
||||
source = "Toon",
|
||||
name = "Toon",
|
||||
url = vidFinal,
|
||||
referer = "$serverUrl/",
|
||||
quality = Qualities.Unknown.value,
|
||||
isM3u8 = true,
|
||||
headers = headers,
|
||||
),
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
data class Media(val url: String, val poster: String? = null, val mediaType: Int? = null)
|
||||
}
|
|
@ -23,5 +23,5 @@ cloudstream {
|
|||
"Anime",
|
||||
)
|
||||
|
||||
iconUrl = "https://www.google.com/s2/favicons?domain=animeindo.fun&sz=%size%"
|
||||
iconUrl = "https://animeindo.quest/wp-content/uploads/2023/05/favicon-300x300.png"
|
||||
}
|
26
Cinemathek/build.gradle.kts
Normal file
26
Cinemathek/build.gradle.kts
Normal file
|
@ -0,0 +1,26 @@
|
|||
// use an integer for version numbers
|
||||
version = 2
|
||||
|
||||
|
||||
cloudstream {
|
||||
language = "de"
|
||||
// All of these properties are optional, you can safely remove them
|
||||
|
||||
// description = "Lorem Ipsum"
|
||||
authors = listOf("Hexated")
|
||||
|
||||
/**
|
||||
* Status int as the following:
|
||||
* 0: Down
|
||||
* 1: Ok
|
||||
* 2: Slow
|
||||
* 3: Beta only
|
||||
* */
|
||||
status = 1 // will be 3 if unspecified
|
||||
tvTypes = listOf(
|
||||
"TvSeries",
|
||||
"Movie",
|
||||
)
|
||||
|
||||
iconUrl = "https://www.google.com/s2/favicons?domain=cinemathek.net&sz=%size%"
|
||||
}
|
2
Cinemathek/src/main/AndroidManifest.xml
Normal file
2
Cinemathek/src/main/AndroidManifest.xml
Normal file
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest package="com.hexated"/>
|
202
Cinemathek/src/main/kotlin/com/hexated/Cinemathek.kt
Normal file
202
Cinemathek/src/main/kotlin/com/hexated/Cinemathek.kt
Normal file
|
@ -0,0 +1,202 @@
|
|||
package com.hexated
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addActors
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
|
||||
import com.lagradost.cloudstream3.extractors.DoodLaExtractor
|
||||
import com.lagradost.cloudstream3.extractors.Filesim
|
||||
import com.lagradost.cloudstream3.utils.*
|
||||
import org.jsoup.nodes.Element
|
||||
|
||||
class Cinemathek : MainAPI() {
|
||||
override var mainUrl = "https://cinemathek.net"
|
||||
override var name = "Cinemathek"
|
||||
override val hasMainPage = true
|
||||
override var lang = "de"
|
||||
override val supportedTypes = setOf(
|
||||
TvType.Movie,
|
||||
TvType.TvSeries,
|
||||
)
|
||||
|
||||
override val mainPage = mainPageOf(
|
||||
"filme" to "Filme",
|
||||
"serien" to "TV Shows",
|
||||
"episoden" to "Episodes",
|
||||
)
|
||||
|
||||
override suspend fun getMainPage(
|
||||
page: Int,
|
||||
request: MainPageRequest
|
||||
): HomePageResponse {
|
||||
val document = app.get("$mainUrl/${request.data}/page/$page/").document
|
||||
val home =
|
||||
document.select("div.items.full article, div#archive-content article").mapNotNull {
|
||||
it.toSearchResult()
|
||||
}
|
||||
return newHomePageResponse(
|
||||
list = HomePageList(
|
||||
name = request.name,
|
||||
list = home,
|
||||
request.data == "episoden"
|
||||
),
|
||||
hasNext = true
|
||||
)
|
||||
}
|
||||
|
||||
private fun getProperLink(uri: String): String {
|
||||
return when {
|
||||
uri.contains("/episoden/") -> {
|
||||
uri.replace(Regex("-\\d+x\\d+"), "").replace("/episoden/", "/serien/")
|
||||
}
|
||||
|
||||
else -> {
|
||||
uri
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Element.toSearchResult(): SearchResponse {
|
||||
val title = this.selectFirst("h3 > a")!!.text()
|
||||
val href = getProperLink(this.selectFirst("h3 > a")!!.attr("href"))
|
||||
val posterUrl = this.select("div.poster > img").attr("src").toString()
|
||||
val quality = getQualityFromString(this.select("span.quality").text())
|
||||
return newMovieSearchResponse(title, href, TvType.Movie) {
|
||||
this.posterUrl = posterUrl
|
||||
this.quality = quality
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun search(query: String): List<SearchResponse> {
|
||||
val document = app.get("$mainUrl/search/$query").document
|
||||
return document.select("div.result-item").map {
|
||||
val title = it.selectFirst("div.title > a")!!.text()
|
||||
val href = getProperLink(it.selectFirst("div.title > a")!!.attr("href"))
|
||||
val posterUrl = it.selectFirst("img")!!.attr("src").toString()
|
||||
newMovieSearchResponse(title, href, TvType.TvSeries) {
|
||||
this.posterUrl = posterUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun load(url: String): LoadResponse {
|
||||
val document = app.get(url).document
|
||||
val title = document.selectFirst("div.data > h1")?.text() ?: ""
|
||||
val poster = document.select("div.poster > img").attr("src").toString()
|
||||
val tags = document.select("div.sgeneros > a").map { it.text() }
|
||||
|
||||
val year = Regex(",\\s?(\\d+)").find(
|
||||
document.select("span.date").text().trim()
|
||||
)?.groupValues?.get(1).toString().toIntOrNull()
|
||||
val tvType = if (document.select("ul#section > li:nth-child(1)").text().contains("Episodes")
|
||||
) TvType.TvSeries else TvType.Movie
|
||||
val description = document.select("div.wp-content > p").text().trim()
|
||||
val trailer = document.selectFirst("div.embed iframe")?.attr("src")
|
||||
val rating =
|
||||
document.selectFirst("span.dt_rating_vgs")?.text()?.toRatingInt()
|
||||
val actors = document.select("div.persons > div[itemprop=actor]").map {
|
||||
Actor(it.select("meta[itemprop=name]").attr("content"), it.select("img").attr("src"))
|
||||
}
|
||||
|
||||
val recommendations = document.select("div.owl-item").map {
|
||||
val recName =
|
||||
it.selectFirst("a")!!.attr("href").toString().removeSuffix("/").split("/").last()
|
||||
val recHref = it.selectFirst("a")!!.attr("href")
|
||||
val recPosterUrl = it.selectFirst("img")?.attr("src").toString()
|
||||
newTvSeriesSearchResponse(recName, recHref, TvType.TvSeries) {
|
||||
this.posterUrl = recPosterUrl
|
||||
}
|
||||
}
|
||||
|
||||
return if (tvType == TvType.TvSeries) {
|
||||
val episodes = document.select("ul.episodios > li").map {
|
||||
val href = it.select("a").attr("href")
|
||||
val name = fixTitle(it.select("div.episodiotitle > a").text().trim())
|
||||
val image = it.select("div.imagen > img").attr("src")
|
||||
val episode = it.select("div.numerando").text().replace(" ", "").split("-").last()
|
||||
.toIntOrNull()
|
||||
val season = it.select("div.numerando").text().replace(" ", "").split("-").first()
|
||||
.toIntOrNull()
|
||||
Episode(
|
||||
href,
|
||||
name,
|
||||
season,
|
||||
episode,
|
||||
image
|
||||
)
|
||||
}
|
||||
newTvSeriesLoadResponse(title, url, TvType.TvSeries, episodes) {
|
||||
this.posterUrl = poster
|
||||
this.year = year
|
||||
this.plot = description
|
||||
this.tags = tags
|
||||
this.rating = rating
|
||||
addActors(actors)
|
||||
this.recommendations = recommendations
|
||||
addTrailer(trailer)
|
||||
}
|
||||
} else {
|
||||
newMovieLoadResponse(title, url, TvType.Movie, url) {
|
||||
this.posterUrl = poster
|
||||
this.year = year
|
||||
this.plot = description
|
||||
this.tags = tags
|
||||
this.rating = rating
|
||||
addActors(actors)
|
||||
this.recommendations = recommendations
|
||||
addTrailer(trailer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
val document = app.get(data).document
|
||||
document.select("ul#playeroptionsul > li").map {
|
||||
Triple(
|
||||
it.attr("data-post"),
|
||||
it.attr("data-nume"),
|
||||
it.attr("data-type")
|
||||
)
|
||||
}.apmap { (id, nume, type) ->
|
||||
val iframe = app.get(
|
||||
url = "$mainUrl/wp-json/dooplayer/v2/$id/$type/$nume",
|
||||
referer = data,
|
||||
headers = mapOf("Accept" to "*/*", "X-Requested-With" to "XMLHttpRequest")
|
||||
).parsedSafe<ResponseHash>()?.embedUrl ?: return@apmap
|
||||
|
||||
if (!iframe.contains("youtube")) loadExtractor(iframe, "$mainUrl/", subtitleCallback, callback)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
data class ResponseHash(
|
||||
@JsonProperty("embed_url") val embedUrl: String,
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
class StreamwishCom : Filesim() {
|
||||
override val name = "Streamwish"
|
||||
override var mainUrl = "https://streamwish.com"
|
||||
}
|
||||
|
||||
class Ds2play : DoodLaExtractor() {
|
||||
override var name = "Ds2play"
|
||||
override var mainUrl = "https://ds2play.com"
|
||||
}
|
||||
|
||||
class Do0od : DoodLaExtractor() {
|
||||
override var name = "Do0od"
|
||||
override var mainUrl = "https://do0od.com"
|
||||
}
|
||||
|
||||
class Filelions : Filesim() {
|
||||
override val name = "Filelions"
|
||||
override var mainUrl = "https://filelions.live"
|
||||
}
|
18
Cinemathek/src/main/kotlin/com/hexated/CinemathekPlugin.kt
Normal file
18
Cinemathek/src/main/kotlin/com/hexated/CinemathekPlugin.kt
Normal file
|
@ -0,0 +1,18 @@
|
|||
|
||||
package com.hexated
|
||||
|
||||
import com.lagradost.cloudstream3.plugins.CloudstreamPlugin
|
||||
import com.lagradost.cloudstream3.plugins.Plugin
|
||||
import android.content.Context
|
||||
|
||||
@CloudstreamPlugin
|
||||
class CinemathekPlugin: Plugin() {
|
||||
override fun load(context: Context) {
|
||||
// All providers should be added in this manner. Please don't edit the providers list directly.
|
||||
registerMainAPI(Cinemathek())
|
||||
registerExtractorAPI(StreamwishCom())
|
||||
registerExtractorAPI(Ds2play())
|
||||
registerExtractorAPI(Do0od())
|
||||
registerExtractorAPI(Filelions())
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
// use an integer for version numbers
|
||||
version = 5
|
||||
version = 6
|
||||
|
||||
|
||||
cloudstream {
|
||||
|
|
|
@ -6,13 +6,14 @@ import com.lagradost.cloudstream3.extractors.Filesim
|
|||
import com.lagradost.cloudstream3.utils.AppUtils
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.INFER_TYPE
|
||||
import com.lagradost.cloudstream3.utils.Qualities
|
||||
import com.lagradost.cloudstream3.utils.getQualityFromName
|
||||
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||
import org.jsoup.nodes.Element
|
||||
import java.net.URI
|
||||
|
||||
class DramaSerial : MainAPI() {
|
||||
override var mainUrl = "https://tv3.dramaserial.id"
|
||||
private var serverUrl = "http://31.220.73.179/"
|
||||
private var serverUrl = "https://juraganfilm.info"
|
||||
override var name = "DramaSerial"
|
||||
override val hasMainPage = true
|
||||
override var lang = "id"
|
||||
|
@ -72,8 +73,12 @@ class DramaSerial : MainAPI() {
|
|||
val duration =
|
||||
document.selectFirst("div.gmr-movie-innermeta span:contains(Duration:)")?.text()
|
||||
?.filter { it.isDigit() }?.toIntOrNull()
|
||||
val description = document.select("div.entry-content.entry-content-single div.entry-content.entry-content-single").text().trim()
|
||||
val type = if(document.select("div.page-links").isNullOrEmpty()) TvType.Movie else TvType.AsianDrama
|
||||
val description =
|
||||
document.select("div.entry-content.entry-content-single div.entry-content.entry-content-single")
|
||||
.text().trim()
|
||||
val type = if (document.select("div.page-links")
|
||||
.isNullOrEmpty()
|
||||
) TvType.Movie else TvType.AsianDrama
|
||||
|
||||
if (type == TvType.Movie) {
|
||||
return newMovieLoadResponse(title, url, TvType.Movie, url) {
|
||||
|
@ -84,18 +89,19 @@ class DramaSerial : MainAPI() {
|
|||
this.duration = duration
|
||||
}
|
||||
} else {
|
||||
val episodes = document.select("div.page-links span.page-link-number").mapNotNull { eps ->
|
||||
val episode = eps.text().filter { it.isDigit() }.toIntOrNull()
|
||||
val link = if(episode == 1) {
|
||||
url
|
||||
} else {
|
||||
eps.parent()?.attr("href")
|
||||
val episodes =
|
||||
document.select("div.page-links span.page-link-number").mapNotNull { eps ->
|
||||
val episode = eps.text().filter { it.isDigit() }.toIntOrNull()
|
||||
val link = if (episode == 1) {
|
||||
url
|
||||
} else {
|
||||
eps.parent()?.attr("href")
|
||||
}
|
||||
Episode(
|
||||
link ?: return@mapNotNull null,
|
||||
episode = episode,
|
||||
)
|
||||
}
|
||||
Episode(
|
||||
link ?: return@mapNotNull null,
|
||||
episode = episode,
|
||||
)
|
||||
}
|
||||
return newTvSeriesLoadResponse(title, url, TvType.AsianDrama, episodes = episodes) {
|
||||
posterUrl = poster
|
||||
this.year = year
|
||||
|
@ -107,6 +113,7 @@ class DramaSerial : MainAPI() {
|
|||
}
|
||||
|
||||
private suspend fun invokeGetbk(
|
||||
name: String,
|
||||
url: String,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
) {
|
||||
|
@ -115,12 +122,12 @@ class DramaSerial : MainAPI() {
|
|||
referer = "$serverUrl/"
|
||||
).document.selectFirst("script:containsData(sources)")?.data() ?: return
|
||||
|
||||
val json = "\"sources\":\\s*\\[(.*)]".toRegex().find(script)?.groupValues?.get(1)
|
||||
val json = "sources:\\s*\\[(.*)]".toRegex().find(script)?.groupValues?.get(1)
|
||||
AppUtils.tryParseJson<ArrayList<Sources>>("[$json]")?.map {
|
||||
callback.invoke(
|
||||
ExtractorLink(
|
||||
"Getbk",
|
||||
"Getbk",
|
||||
name,
|
||||
name,
|
||||
it.file ?: return@map,
|
||||
"$serverUrl/",
|
||||
getQualityFromName(it.label),
|
||||
|
@ -131,6 +138,34 @@ class DramaSerial : MainAPI() {
|
|||
|
||||
}
|
||||
|
||||
private suspend fun invokeGdrive(
|
||||
name: String,
|
||||
url: String,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
) {
|
||||
|
||||
val embedUrl = app.get(
|
||||
url,
|
||||
referer = "$serverUrl/"
|
||||
).document.selectFirst("iframe")?.attr("src")?.let { fixUrl(it) } ?: return
|
||||
|
||||
val req = app.get(embedUrl)
|
||||
val host = getBaseUrl(embedUrl)
|
||||
val token = req.document.selectFirst("div#token")?.text() ?: return
|
||||
|
||||
callback.invoke(
|
||||
ExtractorLink(
|
||||
name,
|
||||
name,
|
||||
"$host/hlsplaylist.php?idhls=${token.trim()}.m3u8",
|
||||
"$host/",
|
||||
Qualities.Unknown.value,
|
||||
true
|
||||
)
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
override suspend fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
|
@ -142,19 +177,23 @@ class DramaSerial : MainAPI() {
|
|||
val iframe = document.select("iframe[name=juraganfilm]").attr("src")
|
||||
app.get(iframe, referer = "$mainUrl/").document.select("div#header-slider ul li")
|
||||
.apmap { mLink ->
|
||||
mLink.attr("onclick").substringAfter("frame('").substringBefore("')").let { iLink ->
|
||||
val iMovie = iLink.substringAfter("movie=").substringBefore("&")
|
||||
val mIframe = iLink.substringAfter("iframe=")
|
||||
val iUrl = "$serverUrl/stream/$mIframe.php?movie=$iMovie"
|
||||
if(mIframe == "getbk") {
|
||||
invokeGetbk(iUrl, callback)
|
||||
} else {
|
||||
val link = app.get(
|
||||
iUrl,
|
||||
referer = "$serverUrl/"
|
||||
).document.selectFirst("iframe")?.attr("src") ?: return@apmap null
|
||||
loadExtractor(fixUrl(link), "$serverUrl/", subtitleCallback, callback)
|
||||
val iLink = mLink.attr("onclick").substringAfter("frame('").substringBefore("')")
|
||||
serverUrl = getBaseUrl(iLink)
|
||||
val iMovie = iLink.substringAfter("movie=").substringBefore("&")
|
||||
val mIframe = iLink.substringAfter("iframe=")
|
||||
val serverName = fixTitle(mIframe)
|
||||
when (mIframe) {
|
||||
"getbk" -> {
|
||||
invokeGetbk(
|
||||
serverName,
|
||||
"$serverUrl/stream/$mIframe.php?movie=$iMovie",
|
||||
callback
|
||||
)
|
||||
}
|
||||
"gdrivehls", "gdriveplayer" -> {
|
||||
invokeGdrive(serverName, iLink, callback)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -162,6 +201,12 @@ class DramaSerial : MainAPI() {
|
|||
|
||||
}
|
||||
|
||||
private fun getBaseUrl(url: String): String {
|
||||
return URI(url).let {
|
||||
"${it.scheme}://${it.host}"
|
||||
}
|
||||
}
|
||||
|
||||
private data class Sources(
|
||||
@JsonProperty("file") val file: String? = null,
|
||||
@JsonProperty("label") val label: String? = null,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// use an integer for version numbers
|
||||
version = 1
|
||||
version = 3
|
||||
|
||||
|
||||
cloudstream {
|
||||
|
|
|
@ -3,13 +3,14 @@ package com.hexated
|
|||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addActors
|
||||
import com.lagradost.cloudstream3.utils.*
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.M3u8Helper
|
||||
import org.jsoup.nodes.Element
|
||||
import java.net.URLDecoder
|
||||
|
||||
class DubokuProvider : MainAPI() {
|
||||
override var mainUrl = "https://www.duboku.tv"
|
||||
private var serverUrl = "https://w.duboku.io"
|
||||
override var name = "Duboku"
|
||||
override val hasMainPage = true
|
||||
override var lang = "zh"
|
||||
|
@ -106,27 +107,41 @@ class DubokuProvider : MainAPI() {
|
|||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
|
||||
app.get(data).document.select("script").map { script ->
|
||||
if (script.data().contains("var player_data={")) {
|
||||
val dataJson =
|
||||
script.data().substringAfter("var player_data={").substringBefore("}")
|
||||
tryParseJson<Sources>("{$dataJson}")?.let { source ->
|
||||
M3u8Helper.generateM3u8(
|
||||
this.name,
|
||||
source.url ?: return@map,
|
||||
referer = "https://w.duboku.io/",
|
||||
headers = mapOf("Origin" to "https://w.duboku.io")
|
||||
).forEach(callback)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val dataJson =
|
||||
app.get(data).document.selectFirst("script:containsData(var player_data={)")?.data()
|
||||
?.substringAfter("var player_data={")?.substringBefore("}")
|
||||
?: throw IllegalArgumentException()
|
||||
val source = tryParseJson<Sources>("{$dataJson}")
|
||||
callback.invoke(
|
||||
ExtractorLink(
|
||||
this.name,
|
||||
this.name,
|
||||
"${decode(base64Decode(source?.url ?: return false))}${getSign(source.from, data)}",
|
||||
"$serverUrl/",
|
||||
Qualities.Unknown.value,
|
||||
INFER_TYPE,
|
||||
headers = mapOf(
|
||||
"Accept-Language" to "en-US,en;q=0.5",
|
||||
"Origin" to serverUrl
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private suspend fun getSign(server: String? = "vidjs24", ref: String): String {
|
||||
return app.get(
|
||||
"$serverUrl/static/player/$server.php",
|
||||
referer = ref
|
||||
).text.substringAfter("PlayUrl+'").substringBefore("'")
|
||||
}
|
||||
|
||||
private fun decode(input: String): String = URLDecoder.decode(input, "utf-8")
|
||||
|
||||
data class Sources(
|
||||
@JsonProperty("url") val url: String?,
|
||||
@JsonProperty("from") val from: String?,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -21,4 +21,6 @@ cloudstream {
|
|||
// You can find a list of avaliable types here:
|
||||
// https://recloudstream.github.io/cloudstream/html/app/com.lagradost.cloudstream3/-tv-type/index.html
|
||||
tvTypes = listOf("TvSeries", "Movie")
|
||||
|
||||
iconUrl = "https://www.google.com/s2/favicons?domain=filmpalast.to&sz=%size%"
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import org.jetbrains.kotlin.konan.properties.Properties
|
||||
|
||||
// use an integer for version numbers
|
||||
version = 29
|
||||
version = 31
|
||||
|
||||
android {
|
||||
defaultConfig {
|
||||
|
|
|
@ -6,8 +6,9 @@ import com.lagradost.cloudstream3.utils.httpsify
|
|||
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||
|
||||
class DutaMovie : Gomov() {
|
||||
|
||||
override var mainUrl = "https://viral.dutamovie21.tech"
|
||||
override var name = "DutaMovie"
|
||||
override var name = "DutaMovie"
|
||||
override val mainPage = mainPageOf(
|
||||
"category/box-office/page/%d/" to "Box Office",
|
||||
"category/serial-tv/page/%d/" to "Serial TV",
|
||||
|
|
|
@ -10,7 +10,9 @@ import org.jsoup.nodes.Element
|
|||
import java.net.URI
|
||||
|
||||
open class Gomov : MainAPI() {
|
||||
|
||||
override var mainUrl = "https://gomov.info"
|
||||
|
||||
private var directUrl: String? = null
|
||||
override var name = "Gomov"
|
||||
override val hasMainPage = true
|
||||
|
|
|
@ -3,7 +3,9 @@ package com.hexated
|
|||
import com.lagradost.cloudstream3.mainPageOf
|
||||
|
||||
class Multiplex : Gomov() {
|
||||
|
||||
override var mainUrl = "http://95.111.236.109"
|
||||
|
||||
override var name = "Multiplex"
|
||||
override val mainPage = mainPageOf(
|
||||
"country/usa/page/%d/" to "Movie",
|
||||
|
|
|
@ -3,7 +3,9 @@ package com.hexated
|
|||
import com.lagradost.cloudstream3.mainPageOf
|
||||
|
||||
class Ngefilm : Gomov() {
|
||||
|
||||
override var mainUrl = "https://ngefilm21.pics"
|
||||
|
||||
override var name = "Ngefilm"
|
||||
override val mainPage = mainPageOf(
|
||||
"/page/%d/?s&search=advanced&post_type=movie&index&orderby&genre&movieyear&country&quality=" to "Movies Terbaru",
|
||||
|
|
|
@ -9,7 +9,7 @@ import org.jsoup.nodes.Element
|
|||
import java.net.URI
|
||||
|
||||
class Nodrakorid : Gomov() {
|
||||
override var mainUrl = "https://no-dra-kor-id.shop"
|
||||
override var mainUrl = "https://no1.nodrakor.store"
|
||||
override var name = "Nodrakorid"
|
||||
|
||||
override val mainPage = mainPageOf(
|
||||
|
|
|
@ -6,7 +6,9 @@ import com.lagradost.cloudstream3.TvSeriesLoadResponse
|
|||
import com.lagradost.cloudstream3.*
|
||||
|
||||
class Pusatfilm : Gomov() {
|
||||
|
||||
override var mainUrl = "http://37.60.238.37/Genre/pusatfilm21"
|
||||
|
||||
override var name = "Pusatfilm"
|
||||
override val mainPage = mainPageOf(
|
||||
"film-terbaru/page/%d/" to "Film Terbaru",
|
||||
|
|
|
@ -24,5 +24,5 @@ cloudstream {
|
|||
"Movie",
|
||||
)
|
||||
|
||||
iconUrl = "https://www.google.com/s2/favicons?domain=hdrezka19139.org&sz=%size%"
|
||||
iconUrl = "https://www.google.com/s2/favicons?domain=rezka.ag&sz=%size%"
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
// use an integer for version numbers
|
||||
version = 3
|
||||
version = 4
|
||||
|
||||
|
||||
cloudstream {
|
||||
|
|
|
@ -55,7 +55,7 @@ class Hentaiheaven : MainAPI() {
|
|||
val link = "$mainUrl/?s=$query&post_type=wp-manga"
|
||||
val document = app.get(link).document
|
||||
|
||||
return document.select("div.c-tabs-item div.row.c-tabs-item__content").mapNotNull {
|
||||
return document.select("div.c-tabs-item > div.c-tabs-item__content").mapNotNull {
|
||||
it.toSearchResult()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,8 +35,8 @@ open class Kickassanime : MainAPI() {
|
|||
|
||||
companion object {
|
||||
const val kaast = "https://kaast1.com"
|
||||
private const val consumetAnilist = "https://api.consumet.org/meta/anilist"
|
||||
private const val consumetMal = "https://api.consumet.org/meta/mal"
|
||||
private const val consumetAnilist = "https://consumet-instance.vercel.app/meta/anilist"
|
||||
private const val consumetMal = "https://consumet-instance.vercel.app/meta/mal"
|
||||
fun getType(t: String): TvType {
|
||||
return when {
|
||||
t.contains("Ova", true) -> TvType.OVA
|
||||
|
@ -379,4 +379,4 @@ open class Kickassanime : MainAPI() {
|
|||
@JsonProperty("title") val title: SyncTitle? = null,
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
|
|
26
Kinoger/build.gradle.kts
Normal file
26
Kinoger/build.gradle.kts
Normal file
|
@ -0,0 +1,26 @@
|
|||
// use an integer for version numbers
|
||||
version = 3
|
||||
|
||||
|
||||
cloudstream {
|
||||
language = "de"
|
||||
// All of these properties are optional, you can safely remove them
|
||||
|
||||
// description = "Lorem Ipsum"
|
||||
authors = listOf("Hexated")
|
||||
|
||||
/**
|
||||
* Status int as the following:
|
||||
* 0: Down
|
||||
* 1: Ok
|
||||
* 2: Slow
|
||||
* 3: Beta only
|
||||
* */
|
||||
status = 1 // will be 3 if unspecified
|
||||
tvTypes = listOf(
|
||||
"TvSeries",
|
||||
"Movie",
|
||||
)
|
||||
|
||||
iconUrl = "https://www.google.com/s2/favicons?domain=kinoger.com&sz=%size%"
|
||||
}
|
2
Kinoger/src/main/AndroidManifest.xml
Normal file
2
Kinoger/src/main/AndroidManifest.xml
Normal file
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest package="com.hexated"/>
|
157
Kinoger/src/main/kotlin/com/hexated/Kinoger.kt
Normal file
157
Kinoger/src/main/kotlin/com/hexated/Kinoger.kt
Normal file
|
@ -0,0 +1,157 @@
|
|||
package com.hexated
|
||||
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.extractors.Chillx
|
||||
import com.lagradost.cloudstream3.network.CloudflareKiller
|
||||
import com.lagradost.cloudstream3.utils.AppUtils
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLinkType
|
||||
import com.lagradost.cloudstream3.utils.Qualities
|
||||
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||
import org.jsoup.nodes.Element
|
||||
|
||||
class Kinoger : MainAPI() {
|
||||
override var name = "Kinoger"
|
||||
override var mainUrl = "https://kinoger.to"
|
||||
override var lang = "de"
|
||||
override val hasMainPage = true
|
||||
override val supportedTypes = setOf(TvType.TvSeries, TvType.Movie)
|
||||
|
||||
override val mainPage = mainPageOf(
|
||||
"" to "Alle Filme",
|
||||
"stream/action" to "Action",
|
||||
"stream/fantasy" to "Fantasy",
|
||||
"stream/drama" to "Drama",
|
||||
"stream/mystery" to "Mystery",
|
||||
"stream/romance" to "Romance",
|
||||
"stream/animation" to "Animation",
|
||||
"stream/horror" to "Horror",
|
||||
"stream/familie" to "Familie",
|
||||
"stream/komdie" to "Komdie",
|
||||
)
|
||||
|
||||
override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse {
|
||||
val document = app.get("$mainUrl/${request.data}/page/$page").document
|
||||
val home = document.select("div#dle-content div.short").mapNotNull {
|
||||
it.toSearchResult()
|
||||
}
|
||||
return newHomePageResponse(request.name, home)
|
||||
}
|
||||
|
||||
private fun getProperLink(uri: String): String {
|
||||
return if (uri.contains("-episode-")) {
|
||||
"$mainUrl/series/" + Regex("$mainUrl/(.+)-ep.+").find(uri)?.groupValues?.get(1)
|
||||
} else {
|
||||
uri
|
||||
}
|
||||
}
|
||||
|
||||
private fun Element.toSearchResult(): SearchResponse? {
|
||||
val href = getProperLink(this.selectFirst("a")?.attr("href") ?: return null)
|
||||
val title = this.selectFirst("a")?.text() ?: this.selectFirst("img")?.attr("alt")
|
||||
?: this.selectFirst("a")?.attr("title") ?: return null
|
||||
val posterUrl = fixUrlNull(
|
||||
(this.selectFirst("div.content_text img") ?: this.nextElementSibling()?.selectFirst("div.content_text img") ?: this.selectFirst("img"))?.getImageAttr()
|
||||
)
|
||||
|
||||
return newTvSeriesSearchResponse(title, href, TvType.AsianDrama) {
|
||||
this.posterUrl = posterUrl
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun search(query: String): List<SearchResponse> {
|
||||
return app.get("$mainUrl/?do=search&subaction=search&titleonly=3&story=$query&x=0&y=0&submit=submit").document.select(
|
||||
"div#dle-content div.titlecontrol"
|
||||
).mapNotNull { it.toSearchResult() }
|
||||
}
|
||||
|
||||
override suspend fun load(url: String): LoadResponse {
|
||||
val document = app.get(url).document
|
||||
val title = document.selectFirst("h1#news-title")?.text() ?: ""
|
||||
val poster = fixUrlNull(document.selectFirst("div.images-border img")?.getImageAttr())
|
||||
val description = document.select("div.images-border").text()
|
||||
val year = """\((\d{4})\)""".toRegex().find(title)?.groupValues?.get(1)?.toIntOrNull()
|
||||
val tags = document.select("li.category a").map { it.text() }
|
||||
|
||||
val recommendations = document.select("ul.ul_related li").mapNotNull {
|
||||
it.toSearchResult()
|
||||
}
|
||||
|
||||
val script = document.selectFirst("script:containsData(pw.show)")?.data()
|
||||
val data = script?.substringAfter("[")?.substringBeforeLast("]")?.replace("\'", "\"")
|
||||
val json = AppUtils.tryParseJson<List<List<String>>>("[$data]")
|
||||
|
||||
val type = if(script?.substringBeforeLast(")")?.substringAfterLast(",") == "0.2") TvType.Movie else TvType.TvSeries
|
||||
|
||||
val episodes = json?.flatMapIndexed { season: Int, iframes: List<String> ->
|
||||
iframes.mapIndexed { episode, iframe ->
|
||||
Episode(
|
||||
iframe.trim(),
|
||||
season = season + 1,
|
||||
episode = episode + 1
|
||||
)
|
||||
}
|
||||
} ?: emptyList()
|
||||
|
||||
return newTvSeriesLoadResponse(title, url, type, episodes) {
|
||||
this.posterUrl = poster
|
||||
this.year = year
|
||||
this.plot = description
|
||||
this.tags = tags
|
||||
this.recommendations = recommendations
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
loadCustomExtractor(data, "$mainUrl/", subtitleCallback, callback)
|
||||
return true
|
||||
}
|
||||
|
||||
private suspend fun loadCustomExtractor(
|
||||
url: String,
|
||||
referer: String? = null,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit,
|
||||
quality: Int? = null,
|
||||
) {
|
||||
loadExtractor(url, referer, subtitleCallback) { link ->
|
||||
if(link.quality == Qualities.Unknown.value) {
|
||||
callback.invoke(
|
||||
ExtractorLink(
|
||||
link.source,
|
||||
link.name,
|
||||
link.url,
|
||||
link.referer,
|
||||
when (link.type) {
|
||||
ExtractorLinkType.M3U8 -> link.quality
|
||||
else -> quality ?: link.quality
|
||||
},
|
||||
link.type,
|
||||
link.headers,
|
||||
link.extractorData
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Element.getImageAttr(): String? {
|
||||
return when {
|
||||
this.hasAttr("data-src") -> this.attr("data-src")
|
||||
this.hasAttr("data-lazy-src") -> this.attr("data-lazy-src")
|
||||
this.hasAttr("srcset") -> this.attr("srcset").substringBefore(" ")
|
||||
else -> this.attr("src")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Kinogeru : Chillx() {
|
||||
override val name = "Kinoger"
|
||||
override val mainUrl = "https://kinoger.ru"
|
||||
}
|
15
Kinoger/src/main/kotlin/com/hexated/KinogerPlugin.kt
Normal file
15
Kinoger/src/main/kotlin/com/hexated/KinogerPlugin.kt
Normal file
|
@ -0,0 +1,15 @@
|
|||
|
||||
package com.hexated
|
||||
|
||||
import com.lagradost.cloudstream3.plugins.CloudstreamPlugin
|
||||
import com.lagradost.cloudstream3.plugins.Plugin
|
||||
import android.content.Context
|
||||
|
||||
@CloudstreamPlugin
|
||||
class KinogerPlugin: Plugin() {
|
||||
override fun load(context: Context) {
|
||||
// All providers should be added in this manner. Please don't edit the providers list directly.
|
||||
registerMainAPI(Kinoger())
|
||||
registerExtractorAPI(Kinogeru())
|
||||
}
|
||||
}
|
|
@ -24,5 +24,5 @@ cloudstream {
|
|||
"Movie",
|
||||
)
|
||||
|
||||
iconUrl = "https://www.google.com/s2/favicons?domain=kisskh.me&sz=%size%"
|
||||
iconUrl = "https://www.google.com/s2/favicons?domain=kisskh.co&sz=%size%"
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
// use an integer for version numbers
|
||||
version = 34
|
||||
version = 39
|
||||
|
||||
|
||||
cloudstream {
|
||||
|
|
|
@ -3,13 +3,7 @@ package com.hexated
|
|||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addAniListId
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addMalId
|
||||
import com.lagradost.cloudstream3.network.WebViewResolver
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.Qualities
|
||||
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||
import com.lagradost.nicehttp.requestCreator
|
||||
import okhttp3.Headers
|
||||
import okhttp3.HttpUrl
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Element
|
||||
|
||||
|
@ -20,13 +14,10 @@ class KuramanimeProvider : MainAPI() {
|
|||
override val hasMainPage = true
|
||||
override var lang = "id"
|
||||
override val hasDownloadSupport = true
|
||||
private var params: AuthParams? = null
|
||||
private var headers: Map<String,String> = mapOf()
|
||||
private var cookies: Map<String,String> = mapOf()
|
||||
override val supportedTypes = setOf(
|
||||
TvType.Anime,
|
||||
TvType.AnimeMovie,
|
||||
TvType.OVA
|
||||
TvType.Anime,
|
||||
TvType.AnimeMovie,
|
||||
TvType.OVA
|
||||
)
|
||||
|
||||
companion object {
|
||||
|
@ -46,15 +37,15 @@ class KuramanimeProvider : MainAPI() {
|
|||
}
|
||||
|
||||
override val mainPage = mainPageOf(
|
||||
"$mainUrl/anime/ongoing?order_by=updated&page=" to "Sedang Tayang",
|
||||
"$mainUrl/anime/finished?order_by=updated&page=" to "Selesai Tayang",
|
||||
"$mainUrl/properties/season/summer-2022?order_by=most_viewed&page=" to "Dilihat Terbanyak Musim Ini",
|
||||
"$mainUrl/anime/movie?order_by=updated&page=" to "Film Layar Lebar",
|
||||
"$mainUrl/anime/ongoing?order_by=updated&page=" to "Sedang Tayang",
|
||||
"$mainUrl/anime/finished?order_by=updated&page=" to "Selesai Tayang",
|
||||
"$mainUrl/properties/season/summer-2022?order_by=most_viewed&page=" to "Dilihat Terbanyak Musim Ini",
|
||||
"$mainUrl/anime/movie?order_by=updated&page=" to "Film Layar Lebar",
|
||||
)
|
||||
|
||||
override suspend fun getMainPage(
|
||||
page: Int,
|
||||
request: MainPageRequest
|
||||
page: Int,
|
||||
request: MainPageRequest
|
||||
): HomePageResponse {
|
||||
val document = app.get(request.data + page).document
|
||||
|
||||
|
@ -102,35 +93,40 @@ class KuramanimeProvider : MainAPI() {
|
|||
|
||||
val title = document.selectFirst(".anime__details__title > h3")!!.text().trim()
|
||||
val poster = document.selectFirst(".anime__details__pic")?.attr("data-setbg")
|
||||
val tags = document.select("div.anime__details__widget > div > div:nth-child(2) > ul > li:nth-child(1)")
|
||||
.text().trim().replace("Genre: ", "").split(", ")
|
||||
val tags =
|
||||
document.select("div.anime__details__widget > div > div:nth-child(2) > ul > li:nth-child(1)")
|
||||
.text().trim().replace("Genre: ", "").split(", ")
|
||||
|
||||
val year = Regex("\\D").replace(
|
||||
document.select("div.anime__details__widget > div > div:nth-child(1) > ul > li:nth-child(5)")
|
||||
.text().trim().replace("Musim: ", ""), ""
|
||||
document.select("div.anime__details__widget > div > div:nth-child(1) > ul > li:nth-child(5)")
|
||||
.text().trim().replace("Musim: ", ""), ""
|
||||
).toIntOrNull()
|
||||
val status = getStatus(
|
||||
document.select("div.anime__details__widget > div > div:nth-child(1) > ul > li:nth-child(3)")
|
||||
.text().trim().replace("Status: ", "")
|
||||
document.select("div.anime__details__widget > div > div:nth-child(1) > ul > li:nth-child(3)")
|
||||
.text().trim().replace("Status: ", "")
|
||||
)
|
||||
val description = document.select(".anime__details__text > p").text().trim()
|
||||
|
||||
val episodes = mutableListOf<Episode>()
|
||||
|
||||
for (i in 1..6) {
|
||||
for (i in 1..10) {
|
||||
val doc = app.get("$url?page=$i").document
|
||||
val eps = Jsoup.parse(doc.select("#episodeLists").attr("data-content")).select("a.btn.btn-sm.btn-danger")
|
||||
.mapNotNull {
|
||||
val name = it.text().trim()
|
||||
val episode = Regex("(\\d+[.,]?\\d*)").find(name)?.groupValues?.getOrNull(0)
|
||||
?.toIntOrNull()
|
||||
val link = it.attr("href")
|
||||
Episode(link, episode = episode)
|
||||
}
|
||||
if(eps.isEmpty()) break else episodes.addAll(eps)
|
||||
val eps = Jsoup.parse(doc.select("#episodeLists").attr("data-content"))
|
||||
.select("a.btn.btn-sm.btn-danger")
|
||||
.mapNotNull {
|
||||
val name = it.text().trim()
|
||||
val episode = Regex("(\\d+[.,]?\\d*)").find(name)?.groupValues?.getOrNull(0)
|
||||
?.toIntOrNull()
|
||||
val link = it.attr("href")
|
||||
Episode(link, episode = episode)
|
||||
}
|
||||
if (eps.isEmpty()) break else episodes.addAll(eps)
|
||||
}
|
||||
|
||||
val type = getType(document.selectFirst("div.col-lg-6.col-md-6 ul li:contains(Tipe:) a")?.text()?.lowercase() ?: "tv", episodes.size)
|
||||
val type = getType(
|
||||
document.selectFirst("div.col-lg-6.col-md-6 ul li:contains(Tipe:) a")?.text()
|
||||
?.lowercase() ?: "tv", episodes.size
|
||||
)
|
||||
val recommendations = document.select("div#randomList > a").mapNotNull {
|
||||
val epHref = it.attr("href")
|
||||
val epTitle = it.select("h5.sidebar-title-h5.px-2.py-2").text()
|
||||
|
@ -141,7 +137,7 @@ class KuramanimeProvider : MainAPI() {
|
|||
}
|
||||
}
|
||||
|
||||
val tracker = APIHolder.getTracker(listOf(title),TrackerType.getTypes(type),year,true)
|
||||
val tracker = APIHolder.getTracker(listOf(title), TrackerType.getTypes(type), year, true)
|
||||
|
||||
return newAnimeLoadResponse(title, url, type) {
|
||||
engName = title
|
||||
|
@ -159,112 +155,14 @@ class KuramanimeProvider : MainAPI() {
|
|||
|
||||
}
|
||||
|
||||
private suspend fun invokeLocalSource(
|
||||
url: String,
|
||||
server: String,
|
||||
ref: String,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
) {
|
||||
val document = app.get(
|
||||
url,
|
||||
referer = ref,
|
||||
headers = headers,
|
||||
cookies = cookies
|
||||
).document
|
||||
document.select("video#player > source").map {
|
||||
val link = fixUrl(it.attr("src"))
|
||||
val quality = it.attr("size").toIntOrNull()
|
||||
callback.invoke(
|
||||
ExtractorLink(
|
||||
fixTitle(server),
|
||||
fixTitle(server),
|
||||
link,
|
||||
referer = "",
|
||||
quality = quality ?: Qualities.Unknown.value,
|
||||
headers = mapOf(
|
||||
"Accept" to "video/webm,video/ogg,video/*;q=0.9,application/ogg;q=0.7,audio/*;q=0.6,*/*;q=0.5",
|
||||
"Range" to "bytes=0-",
|
||||
"Sec-Fetch-Dest" to "video",
|
||||
"Sec-Fetch-Mode" to "no-cors",
|
||||
),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
|
||||
val req = app.get(data)
|
||||
val res = req.document
|
||||
|
||||
argamap(
|
||||
{
|
||||
val auth = getAuth(data)
|
||||
headers = auth.authHeader?.associate { it.first to it.second }?.filter { it.key != "Cookie" }!!
|
||||
cookies = req.cookies
|
||||
res.select("select#changeServer option").apmap { source ->
|
||||
val server = source.attr("value")
|
||||
val query = auth.serverUrl?.queryParameterNames?.map { it } ?: return@apmap
|
||||
val link = "$data?${query[0]}=${getMisc(auth.authUrl)}&${query[1]}=$server"
|
||||
if (server.contains(Regex("(?i)kuramadrive|archive"))) {
|
||||
invokeLocalSource(link, server, data, callback)
|
||||
} else {
|
||||
app.get(
|
||||
link,
|
||||
referer = data,
|
||||
headers = headers,
|
||||
cookies = cookies
|
||||
).document.select("div.iframe-container iframe").attr("src").let { videoUrl ->
|
||||
loadExtractor(fixUrl(videoUrl), "$mainUrl/", subtitleCallback, callback)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
res.select("div#animeDownloadLink a").apmap {
|
||||
loadExtractor(it.attr("href"), "$mainUrl/", subtitleCallback, callback)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private suspend fun fetchAuth(url: String) : AuthParams {
|
||||
val regex = Regex("""$mainUrl/\S+""")
|
||||
val found = WebViewResolver(
|
||||
Regex("""$url(?!\?page=)\?"""),
|
||||
additionalUrls = listOf(regex)
|
||||
).resolveUsingWebView(
|
||||
requestCreator(
|
||||
"GET", url
|
||||
)
|
||||
)
|
||||
val addition = found.second.findLast { it.headers["X-Requested-With"] == "XMLHttpRequest" }
|
||||
return AuthParams(found.first?.url, addition?.url.toString(), addition?.headers)
|
||||
}
|
||||
|
||||
private suspend fun getAuth(url: String) = params ?: fetchAuth(url).also { params = it }
|
||||
|
||||
private suspend fun getMisc(url: String?): String {
|
||||
val misc = app.get(
|
||||
"$url",
|
||||
headers = headers,
|
||||
cookies = cookies
|
||||
)
|
||||
cookies = misc.cookies
|
||||
return misc.parsed()
|
||||
}
|
||||
|
||||
data class AuthParams (
|
||||
val serverUrl: HttpUrl?,
|
||||
val authUrl: String?,
|
||||
val authHeader: Headers?,
|
||||
)
|
||||
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
// use an integer for version numbers
|
||||
version = 19
|
||||
version = 20
|
||||
|
||||
|
||||
cloudstream {
|
||||
|
|
|
@ -16,7 +16,7 @@ import java.net.URI
|
|||
import java.util.ArrayList
|
||||
|
||||
class KuronimeProvider : MainAPI() {
|
||||
override var mainUrl = "https://tv.kuronime.vip"
|
||||
override var mainUrl = "https://tv1.kuronime.vip"
|
||||
private var animekuUrl = "https://animeku.org"
|
||||
override var name = "Kuronime"
|
||||
override val hasQuickSearch = true
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// use an integer for version numbers
|
||||
version = 17
|
||||
version = 20
|
||||
|
||||
|
||||
cloudstream {
|
||||
|
@ -23,5 +23,7 @@ cloudstream {
|
|||
"Movie",
|
||||
)
|
||||
|
||||
|
||||
iconUrl = "https://www.google.com/s2/favicons?domain=amp.lk21official.mom&sz=%size%"
|
||||
|
||||
}
|
||||
|
|
86
LayarKacaProvider/src/main/kotlin/com/hexated/Extractors.kt
Normal file
86
LayarKacaProvider/src/main/kotlin/com/hexated/Extractors.kt
Normal file
|
@ -0,0 +1,86 @@
|
|||
package com.hexated
|
||||
|
||||
import com.lagradost.cloudstream3.SubtitleFile
|
||||
import com.lagradost.cloudstream3.app
|
||||
import com.lagradost.cloudstream3.extractors.Filesim
|
||||
import com.lagradost.cloudstream3.utils.ExtractorApi
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.INFER_TYPE
|
||||
import com.lagradost.cloudstream3.utils.M3u8Helper
|
||||
import com.lagradost.cloudstream3.utils.getQualityFromName
|
||||
|
||||
open class Emturbovid : ExtractorApi() {
|
||||
override val name = "Emturbovid"
|
||||
override val mainUrl = "https://emturbovid.com"
|
||||
override val requiresReferer = true
|
||||
|
||||
override suspend fun getUrl(
|
||||
url: String,
|
||||
referer: String?,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
) {
|
||||
val response = app.get(url, referer = referer)
|
||||
val m3u8 = Regex("[\"'](.*?master\\.m3u8.*?)[\"']").find(response.text)?.groupValues?.getOrNull(1)
|
||||
M3u8Helper.generateM3u8(
|
||||
name,
|
||||
m3u8 ?: return,
|
||||
mainUrl
|
||||
).forEach(callback)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
open class Hownetwork : ExtractorApi() {
|
||||
override val name = "Hownetwork"
|
||||
override val mainUrl = "https://stream.hownetwork.xyz"
|
||||
override val requiresReferer = true
|
||||
|
||||
override suspend fun getUrl(
|
||||
url: String,
|
||||
referer: String?,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
) {
|
||||
val id = url.substringAfter("id=")
|
||||
val res = app.post(
|
||||
"$mainUrl/api.php?id=$id",
|
||||
data = mapOf(
|
||||
"r" to "https://playeriframe.shop/",
|
||||
"d" to "stream.hownetwork.xyz",
|
||||
),
|
||||
referer = url,
|
||||
headers = mapOf(
|
||||
"X-Requested-With" to "XMLHttpRequest"
|
||||
)
|
||||
).parsedSafe<Sources>()
|
||||
|
||||
res?.data?.map {
|
||||
callback.invoke(
|
||||
ExtractorLink(
|
||||
this.name,
|
||||
this.name,
|
||||
it.file,
|
||||
url,
|
||||
getQualityFromName(it.label),
|
||||
INFER_TYPE
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
data class Sources(
|
||||
val data: ArrayList<Data>
|
||||
) {
|
||||
data class Data(
|
||||
val file: String,
|
||||
val label: String?,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class Furher : Filesim() {
|
||||
override val name = "Furher"
|
||||
override var mainUrl = "https://furher.in"
|
||||
}
|
|
@ -8,29 +8,31 @@ import com.lagradost.cloudstream3.utils.*
|
|||
import org.jsoup.nodes.Element
|
||||
|
||||
class LayarKacaProvider : MainAPI() {
|
||||
|
||||
override var mainUrl = "https://amp.lk21official.mom"
|
||||
private var seriesUrl = "https://tv12.nontondrama.click/"
|
||||
|
||||
override var name = "LayarKaca"
|
||||
override val hasMainPage = true
|
||||
override var lang = "id"
|
||||
override val supportedTypes = setOf(
|
||||
TvType.Movie,
|
||||
TvType.TvSeries,
|
||||
TvType.AsianDrama
|
||||
TvType.Movie,
|
||||
TvType.TvSeries,
|
||||
TvType.AsianDrama
|
||||
)
|
||||
|
||||
override val mainPage = mainPageOf(
|
||||
"$mainUrl/populer/page/" to "Film Terplopuler",
|
||||
"$mainUrl/rating/page/" to "Film Berdasarkan IMDb Rating",
|
||||
"$mainUrl/most-commented/page/" to "Film Dengan Komentar Terbanyak",
|
||||
"$seriesUrl/latest/page/" to "Series Terbaru",
|
||||
"$seriesUrl/series/asian/page/" to "Film Asian Terbaru",
|
||||
"$mainUrl/latest/page/" to "Film Upload Terbaru",
|
||||
"$mainUrl/populer/page/" to "Film Terplopuler",
|
||||
"$mainUrl/rating/page/" to "Film Berdasarkan IMDb Rating",
|
||||
"$mainUrl/most-commented/page/" to "Film Dengan Komentar Terbanyak",
|
||||
"$seriesUrl/latest-series/page/" to "Series Terbaru",
|
||||
"$seriesUrl/series/asian/page/" to "Film Asian Terbaru",
|
||||
"$mainUrl/latest/page/" to "Film Upload Terbaru",
|
||||
)
|
||||
|
||||
override suspend fun getMainPage(
|
||||
page: Int,
|
||||
request: MainPageRequest
|
||||
page: Int,
|
||||
request: MainPageRequest
|
||||
): HomePageResponse {
|
||||
val document = app.get(request.data + page).document
|
||||
val home = document.select("article.mega-item").mapNotNull {
|
||||
|
@ -40,6 +42,7 @@ class LayarKacaProvider : MainAPI() {
|
|||
}
|
||||
|
||||
private suspend fun getProperLink(url: String): String? {
|
||||
if(url.startsWith(seriesUrl)) return url
|
||||
val res = app.get(url).document
|
||||
return if (res.select("title").text().contains("- Nontondrama", true)) {
|
||||
res.selectFirst("div#content a")?.attr("href")
|
||||
|
@ -53,10 +56,10 @@ class LayarKacaProvider : MainAPI() {
|
|||
val href = fixUrl(this.selectFirst("a")!!.attr("href"))
|
||||
val posterUrl = fixUrlNull(this.selectFirst("img")?.attr("src"))
|
||||
val type =
|
||||
if (this.selectFirst("div.last-episode") == null) TvType.Movie else TvType.TvSeries
|
||||
if (this.selectFirst("div.last-episode") == null) TvType.Movie else TvType.TvSeries
|
||||
return if (type == TvType.TvSeries) {
|
||||
val episode = this.selectFirst("div.last-episode span")?.text()?.filter { it.isDigit() }
|
||||
?.toIntOrNull()
|
||||
?.toIntOrNull()
|
||||
newAnimeSearchResponse(title, href, TvType.TvSeries) {
|
||||
this.posterUrl = posterUrl
|
||||
addSub(episode)
|
||||
|
@ -91,23 +94,23 @@ class LayarKacaProvider : MainAPI() {
|
|||
val tags = document.select("div.content > div:nth-child(5) > h3 > a").map { it.text() }
|
||||
|
||||
val year = Regex("\\d, (\\d+)").find(
|
||||
document.select("div.content > div:nth-child(7) > h3").text().trim()
|
||||
document.select("div.content > div:nth-child(7) > h3").text().trim()
|
||||
)?.groupValues?.get(1).toString().toIntOrNull()
|
||||
val tvType = if (document.select("div.serial-wrapper")
|
||||
.isNotEmpty()
|
||||
.isNotEmpty()
|
||||
) TvType.TvSeries else TvType.Movie
|
||||
val description = document.select("div.content > blockquote").text().trim()
|
||||
val trailer = document.selectFirst("div.action-player li > a.fancybox")?.attr("href")
|
||||
val rating =
|
||||
document.selectFirst("div.content > div:nth-child(6) > h3")?.text()?.toRatingInt()
|
||||
document.selectFirst("div.content > div:nth-child(6) > h3")?.text()?.toRatingInt()
|
||||
val actors =
|
||||
document.select("div.col-xs-9.content > div:nth-child(3) > h3 > a").map { it.text() }
|
||||
document.select("div.col-xs-9.content > div:nth-child(3) > h3 > a").map { it.text() }
|
||||
|
||||
val recommendations = document.select("div.row.item-media").map {
|
||||
val recName = it.selectFirst("h3")?.text()?.trim().toString()
|
||||
val recHref = it.selectFirst(".content-media > a")!!.attr("href")
|
||||
val recPosterUrl =
|
||||
fixUrl(it.selectFirst(".poster-media > a > img")?.attr("src").toString())
|
||||
fixUrl(it.selectFirst(".poster-media > a > img")?.attr("src").toString())
|
||||
newTvSeriesSearchResponse(recName, recHref, TvType.TvSeries) {
|
||||
this.posterUrl = recPosterUrl
|
||||
}
|
||||
|
@ -118,12 +121,12 @@ class LayarKacaProvider : MainAPI() {
|
|||
val href = fixUrl(it.attr("href"))
|
||||
val episode = it.text().toIntOrNull()
|
||||
val season =
|
||||
it.attr("href").substringAfter("season-").substringBefore("-").toIntOrNull()
|
||||
it.attr("href").substringAfter("season-").substringBefore("-").toIntOrNull()
|
||||
Episode(
|
||||
href,
|
||||
"Episode $episode",
|
||||
season,
|
||||
episode,
|
||||
href,
|
||||
"Episode $episode",
|
||||
season,
|
||||
episode,
|
||||
)
|
||||
}.reversed()
|
||||
newTvSeriesLoadResponse(title, url, TvType.TvSeries, episodes) {
|
||||
|
@ -151,10 +154,10 @@ class LayarKacaProvider : MainAPI() {
|
|||
}
|
||||
|
||||
override suspend fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
|
||||
val document = app.get(data).document
|
||||
|
@ -172,30 +175,3 @@ class LayarKacaProvider : MainAPI() {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
open class Emturbovid : ExtractorApi() {
|
||||
override val name = "Emturbovid"
|
||||
override val mainUrl = "https://emturbovid.com"
|
||||
override val requiresReferer = true
|
||||
|
||||
override suspend fun getUrl(
|
||||
url: String,
|
||||
referer: String?,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
) {
|
||||
val response = app.get(url, referer = referer)
|
||||
val m3u8 = Regex("[\"'](.*?master\\.m3u8.*?)[\"']").find(response.text)?.groupValues?.getOrNull(1)
|
||||
M3u8Helper.generateM3u8(
|
||||
name,
|
||||
m3u8 ?: return,
|
||||
mainUrl
|
||||
).forEach(callback)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Furher : Filesim() {
|
||||
override val name = "Furher"
|
||||
override var mainUrl = "https://furher.in"
|
||||
}
|
||||
|
|
|
@ -12,5 +12,6 @@ class LayarKacaProviderPlugin: Plugin() {
|
|||
registerMainAPI(LayarKacaProvider())
|
||||
registerExtractorAPI(Emturbovid())
|
||||
registerExtractorAPI(Furher())
|
||||
registerExtractorAPI(Hownetwork())
|
||||
}
|
||||
}
|
|
@ -268,7 +268,7 @@ class Loklok : MainAPI() {
|
|||
}
|
||||
|
||||
private suspend fun getTracker(title: String?, type: String?, year: Int?): Tracker {
|
||||
val res = app.get("https://api.consumet.org/meta/anilist/$title")
|
||||
val res = app.get("https://consumet-instance.vercel.app/meta/anilist/$title")
|
||||
.parsedSafe<AniSearch>()?.results?.find { media ->
|
||||
(media.title?.english.equals(title, true) || media.title?.romaji.equals(
|
||||
title,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// use an integer for version numbers
|
||||
version = 9
|
||||
version = 11
|
||||
|
||||
|
||||
cloudstream {
|
||||
|
|
|
@ -7,6 +7,7 @@ import com.lagradost.cloudstream3.fixTitle
|
|||
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
|
||||
import com.lagradost.cloudstream3.utils.ExtractorApi
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.Qualities
|
||||
import com.lagradost.cloudstream3.utils.fixUrl
|
||||
import com.lagradost.cloudstream3.utils.getAndUnpack
|
||||
import com.lagradost.cloudstream3.utils.getQualityFromName
|
||||
|
@ -32,28 +33,30 @@ open class Streampai : ExtractorApi() {
|
|||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
) {
|
||||
val res = app.get(url, referer = referer).text
|
||||
val data = getAndUnpack(res)
|
||||
val res = app.get(url, referer = referer).document
|
||||
val data = res.selectFirst("script:containsData(player =)")?.data() ?: return
|
||||
|
||||
val sources = data.substringAfter("sources: [").substringBefore("]")
|
||||
.addMarks("src")
|
||||
.addMarks("type")
|
||||
.addMarks("size")
|
||||
.replace("\'", "\"")
|
||||
|
||||
val tracks = data.substringAfter("tracks: [").substringBefore("]")
|
||||
.replace("\'", "\"")
|
||||
|
||||
val sources = data.substringAfter("sources:[").substringBefore("]").replace("\'", "\"")
|
||||
val tracks = data.substringAfter("\"tracks\":[").substringBefore("]")
|
||||
|
||||
tryParseJson<List<Responses>>("[$sources]")?.forEach {
|
||||
callback.invoke(
|
||||
ExtractorLink(
|
||||
this.name,
|
||||
this.name,
|
||||
fixUrl(it.file),
|
||||
fixUrl(it.src),
|
||||
url,
|
||||
getQualityFromName(it.label),
|
||||
it.size ?: Qualities.Unknown.value,
|
||||
headers = mapOf(
|
||||
"Accept" to "video/webm,video/ogg,video/*;q=0.9,application/ogg;q=0.7,audio/*;q=0.6,*/*;q=0.5",
|
||||
"Accept-Language" to "en-US,en;q=0.5",
|
||||
"DNT" to "1",
|
||||
"Range" to "bytes=0-",
|
||||
"Sec-Fetch-Dest" to "video",
|
||||
"Sec-Fetch-Mode" to "no-cors",
|
||||
)
|
||||
),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -61,17 +64,22 @@ open class Streampai : ExtractorApi() {
|
|||
tryParseJson<List<Responses>>("[$tracks]")?.forEach {
|
||||
subtitleCallback.invoke(
|
||||
SubtitleFile(
|
||||
fixTitle(it.label ?: ""),
|
||||
fixUrl(it.file),
|
||||
fixTitle(it.label ?: return@forEach),
|
||||
fixUrl(it.src)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.addMarks(str: String): String {
|
||||
return this.replace(Regex("\"?$str\"?"), "\"$str\"")
|
||||
}
|
||||
|
||||
data class Responses(
|
||||
@JsonProperty("file") val file: String,
|
||||
@JsonProperty("src") val src: String,
|
||||
@JsonProperty("type") val type: String?,
|
||||
@JsonProperty("label") val label: String?
|
||||
@JsonProperty("label") val label: String?,
|
||||
@JsonProperty("size") val size: Int?
|
||||
)
|
||||
|
||||
}
|
26
Moflix/build.gradle.kts
Normal file
26
Moflix/build.gradle.kts
Normal file
|
@ -0,0 +1,26 @@
|
|||
// use an integer for version numbers
|
||||
version = 3
|
||||
|
||||
|
||||
cloudstream {
|
||||
language = "de"
|
||||
// All of these properties are optional, you can safely remove them
|
||||
|
||||
description = "Include: Cineclix"
|
||||
authors = listOf("Hexated")
|
||||
|
||||
/**
|
||||
* Status int as the following:
|
||||
* 0: Down
|
||||
* 1: Ok
|
||||
* 2: Slow
|
||||
* 3: Beta only
|
||||
* */
|
||||
status = 1 // will be 3 if unspecified
|
||||
tvTypes = listOf(
|
||||
"TvSeries",
|
||||
"Movie",
|
||||
)
|
||||
|
||||
iconUrl = "https://www.google.com/s2/favicons?domain=moflix-stream.xyz&sz=%size%"
|
||||
}
|
2
Moflix/src/main/AndroidManifest.xml
Normal file
2
Moflix/src/main/AndroidManifest.xml
Normal file
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest package="com.hexated"/>
|
14
Moflix/src/main/kotlin/com/hexated/Cineclix.kt
Normal file
14
Moflix/src/main/kotlin/com/hexated/Cineclix.kt
Normal file
|
@ -0,0 +1,14 @@
|
|||
package com.hexated
|
||||
|
||||
import com.lagradost.cloudstream3.mainPageOf
|
||||
|
||||
class Cineclix : Moflix() {
|
||||
override var name = "Cineclix"
|
||||
override var mainUrl = "https://cineclix.in"
|
||||
override val mainPage = mainPageOf(
|
||||
"77/created_at:desc" to "Neuerscheinungen Filme",
|
||||
"82/created_at:desc" to "Neuerscheinungen Serien",
|
||||
"77/popularity:desc" to "Filme",
|
||||
"82/popularity:desc" to "Serien",
|
||||
)
|
||||
}
|
94
Moflix/src/main/kotlin/com/hexated/Extractors.kt
Normal file
94
Moflix/src/main/kotlin/com/hexated/Extractors.kt
Normal file
|
@ -0,0 +1,94 @@
|
|||
package com.hexated
|
||||
|
||||
import com.lagradost.cloudstream3.SubtitleFile
|
||||
import com.lagradost.cloudstream3.app
|
||||
import com.lagradost.cloudstream3.utils.*
|
||||
import java.net.URI
|
||||
|
||||
class MoflixLink : MoflixClick() {
|
||||
override val name = "MoflixLink"
|
||||
override val mainUrl = "https://moflix-stream.link"
|
||||
}
|
||||
|
||||
class MoflixFans : MoflixClick() {
|
||||
override val name = "MoflixFans"
|
||||
override val mainUrl = "https://moflix-stream.fans"
|
||||
}
|
||||
|
||||
class Highstream : MoflixClick() {
|
||||
override val name = "Highstream"
|
||||
override val mainUrl = "https://highstream.tv"
|
||||
}
|
||||
|
||||
open class MoflixClick : ExtractorApi() {
|
||||
override val name = "MoflixClick"
|
||||
override val mainUrl = "https://moflix-stream.click"
|
||||
override val requiresReferer = true
|
||||
|
||||
override suspend fun getUrl(
|
||||
url: String,
|
||||
referer: String?,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
) {
|
||||
val response = app.get(url, referer = referer)
|
||||
val script = if (!getPacked(response.text).isNullOrEmpty()) {
|
||||
getAndUnpack(response.text)
|
||||
} else {
|
||||
response.document.selectFirst("script:containsData(sources:)")?.data()
|
||||
}
|
||||
val m3u8 =
|
||||
Regex("file:\\s*\"(.*?m3u8.*?)\"").find(script ?: return)?.groupValues?.getOrNull(1)
|
||||
callback.invoke(
|
||||
ExtractorLink(
|
||||
name,
|
||||
name,
|
||||
m3u8 ?: return,
|
||||
"$mainUrl/",
|
||||
Qualities.Unknown.value,
|
||||
INFER_TYPE
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
open class Doodstream : ExtractorApi() {
|
||||
override val name = "Doodstream"
|
||||
override val mainUrl = "https://doodstream.com"
|
||||
override val requiresReferer = false
|
||||
|
||||
override suspend fun getUrl(
|
||||
url: String,
|
||||
referer: String?,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
) {
|
||||
val req = app.get(url)
|
||||
val host = getBaseUrl(req.url)
|
||||
val response0 = req.text
|
||||
val md5 = host + (Regex("/pass_md5/[^']*").find(response0)?.value ?: return)
|
||||
val trueUrl =
|
||||
app.get(md5, referer = req.url).text + "qWMG3yc6F5?token=" + md5.substringAfterLast("/")
|
||||
val quality = Regex("\\d{3,4}p").find(
|
||||
response0.substringAfter("<title>").substringBefore("</title>")
|
||||
)?.groupValues?.get(0)
|
||||
callback.invoke(
|
||||
ExtractorLink(
|
||||
this.name,
|
||||
this.name,
|
||||
trueUrl,
|
||||
mainUrl,
|
||||
getQualityFromName(quality),
|
||||
false
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun getBaseUrl(url: String): String {
|
||||
return URI(url).let {
|
||||
"${it.scheme}://${it.host}"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
341
Moflix/src/main/kotlin/com/hexated/Moflix.kt
Normal file
341
Moflix/src/main/kotlin/com/hexated/Moflix.kt
Normal file
|
@ -0,0 +1,341 @@
|
|||
package com.hexated
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addImdbId
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addTMDbId
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
|
||||
import com.lagradost.cloudstream3.utils.*
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.toJson
|
||||
import org.jsoup.Jsoup
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
open class Moflix : MainAPI() {
|
||||
override var name = "Moflix"
|
||||
override var mainUrl = "https://moflix-stream.xyz"
|
||||
override var lang = "de"
|
||||
override val hasMainPage = true
|
||||
override val hasQuickSearch = true
|
||||
override val supportedTypes = setOf(TvType.TvSeries, TvType.Movie)
|
||||
|
||||
companion object {
|
||||
fun getType(isSeries: Boolean?): TvType {
|
||||
return when (isSeries) {
|
||||
true -> TvType.TvSeries
|
||||
else -> TvType.Movie
|
||||
}
|
||||
}
|
||||
|
||||
fun getStatus(t: String?): ShowStatus {
|
||||
return when (t) {
|
||||
"ongoing" -> ShowStatus.Ongoing
|
||||
else -> ShowStatus.Completed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override val mainPage = mainPageOf(
|
||||
"351/channelables.order:asc" to "Kürzlich hinzugefügt",
|
||||
"345/popularity:desc" to "Movie-Datenbank",
|
||||
"352/channelables.order:asc" to "Angesagte Serien",
|
||||
"358/channelables.order:asc" to "Kinder & Familien",
|
||||
)
|
||||
|
||||
override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse {
|
||||
val query = request.data.split("/")
|
||||
val home = app.get(
|
||||
"$mainUrl/api/v1/channel/${query.first()}?returnContentOnly=true&restriction=&order=${query.last()}&paginate=simple&perPage=50&query=&page=$page",
|
||||
referer = "$mainUrl/"
|
||||
).parsedSafe<Responses>()?.pagination?.data?.mapNotNull { it.toSearchResponse() }
|
||||
?: emptyList()
|
||||
|
||||
return newHomePageResponse(request.name, home)
|
||||
}
|
||||
|
||||
private fun Data.toSearchResponse(): SearchResponse? {
|
||||
return newTvSeriesSearchResponse(
|
||||
this.name ?: return null,
|
||||
"${this.id}",
|
||||
TvType.TvSeries,
|
||||
false
|
||||
) {
|
||||
posterUrl = this@toSearchResponse.poster?.compress()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun quickSearch(query: String): List<SearchResponse>? = search(query)
|
||||
|
||||
override suspend fun search(query: String): List<SearchResponse>? {
|
||||
return app.get("$mainUrl/api/v1/search/$query?loader=searchPage", referer = "$mainUrl/")
|
||||
.parsedSafe<Responses>()?.results?.mapNotNull { it.toSearchResponse() }
|
||||
}
|
||||
|
||||
override suspend fun load(url: String): LoadResponse {
|
||||
val res = app.get(
|
||||
"$mainUrl/api/v1/titles/${url.fixId()}?loader=titlePage",
|
||||
referer = "$mainUrl/"
|
||||
).parsedSafe<Responses>()
|
||||
|
||||
val uri = Jsoup.parse(res?.seo.toString()).selectFirst("link[rel=canonical]")?.attr("href")
|
||||
val id = res?.title?.id
|
||||
val title = res?.title?.name ?: ""
|
||||
val poster = res?.title?.poster
|
||||
val backdrop = res?.title?.backdrop
|
||||
val tags = res?.title?.keywords?.mapNotNull { it.displayName }
|
||||
val year = res?.title?.year
|
||||
val isSeries = res?.title?.isSeries
|
||||
val certification = res?.title?.certification
|
||||
val duration = res?.title?.runtime
|
||||
val type = getType(isSeries)
|
||||
val description = res?.title?.description
|
||||
val trailers = res?.title?.videos?.filter { it.category.equals("trailer", true) }
|
||||
?.mapNotNull { it.src }
|
||||
val rating = "${res?.title?.rating}".toRatingInt()
|
||||
val actors = res?.credits?.actors?.mapNotNull {
|
||||
ActorData(
|
||||
Actor(it.name ?: return@mapNotNull null, it.poster),
|
||||
roleString = it.pivot?.character
|
||||
)
|
||||
}
|
||||
val recommendations = app.get("$mainUrl/api/v1/titles/$id/related", referer = "$mainUrl/")
|
||||
.parsedSafe<Responses>()?.titles?.mapNotNull { it.toSearchResponse() }
|
||||
|
||||
return if (type == TvType.TvSeries) {
|
||||
val episodes = res?.seasons?.data?.mapNotNull { season ->
|
||||
app.get(
|
||||
"$mainUrl/api/v1/titles/${res.title?.id}/seasons/${season.number}?loader=seasonPage",
|
||||
referer = "$mainUrl/"
|
||||
).parsedSafe<Responses>()?.episodes?.data?.map { episode ->
|
||||
val status =
|
||||
if (episode.status.equals("upcoming", true)) " • [UPCOMING]" else ""
|
||||
Episode(
|
||||
LoadData(
|
||||
id,
|
||||
episode.seasonNumber,
|
||||
episode.episodeNumber,
|
||||
res.title?.isSeries
|
||||
).toJson(),
|
||||
episode.name + status,
|
||||
episode.seasonNumber,
|
||||
episode.episodeNumber,
|
||||
episode.poster,
|
||||
episode.rating?.times(10)?.roundToInt(),
|
||||
episode.description,
|
||||
).apply {
|
||||
this.addDate(episode.releaseDate?.substringBefore("T"))
|
||||
}
|
||||
}
|
||||
}?.flatten() ?: emptyList()
|
||||
newTvSeriesLoadResponse(title, uri ?: url, TvType.TvSeries, episodes) {
|
||||
this.posterUrl = poster
|
||||
this.backgroundPosterUrl = backdrop
|
||||
this.year = year
|
||||
this.showStatus = getStatus(res?.title?.status)
|
||||
this.plot = description
|
||||
this.tags = tags
|
||||
this.rating = rating
|
||||
this.actors = actors
|
||||
this.duration = duration
|
||||
this.recommendations = recommendations
|
||||
this.contentRating = certification
|
||||
addTrailer(trailers)
|
||||
addImdbId(res?.title?.imdbId)
|
||||
addTMDbId(res?.title?.tmdbId)
|
||||
}
|
||||
} else {
|
||||
val urls = res?.title?.videos?.filter { it.category.equals("full", true) }
|
||||
|
||||
newMovieLoadResponse(
|
||||
title,
|
||||
uri ?: url,
|
||||
TvType.Movie,
|
||||
LoadData(isSeries = isSeries, urls = urls)
|
||||
) {
|
||||
this.posterUrl = poster
|
||||
this.backgroundPosterUrl = backdrop
|
||||
this.year = year
|
||||
this.comingSoon = res?.title?.status.equals("upcoming", true)
|
||||
this.plot = description
|
||||
this.tags = tags
|
||||
this.rating = rating
|
||||
this.actors = actors
|
||||
this.duration = duration
|
||||
this.recommendations = recommendations
|
||||
this.contentRating = certification
|
||||
addTrailer(trailers)
|
||||
addImdbId(res?.title?.imdbId)
|
||||
addTMDbId(res?.title?.tmdbId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
|
||||
val json = parseJson<LoadData>(data)
|
||||
|
||||
val iframes = if (json.isSeries == true) {
|
||||
app.get(
|
||||
"$mainUrl/api/v1/titles/${json.id}/seasons/${json.season}/episodes/${json.episode}?loader=episodePage",
|
||||
referer = "$mainUrl/"
|
||||
).parsedSafe<Episodes>()?.episode?.videos?.filter { it.category.equals("full", true) }
|
||||
} else {
|
||||
json.urls
|
||||
}
|
||||
|
||||
iframes?.apmap { iframe ->
|
||||
loadCustomExtractor(
|
||||
iframe.src ?: return@apmap,
|
||||
"$mainUrl/",
|
||||
subtitleCallback,
|
||||
callback,
|
||||
iframe.quality?.substringBefore("/")?.filter { it.isDigit() }?.toIntOrNull()
|
||||
)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private fun String.fixId(): String {
|
||||
val chunk = "/titles/"
|
||||
return if (this.contains(chunk)) this.substringAfter(chunk)
|
||||
.substringBefore("/") else this.substringAfterLast("/")
|
||||
}
|
||||
|
||||
private suspend fun loadCustomExtractor(
|
||||
url: String,
|
||||
referer: String? = null,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit,
|
||||
quality: Int? = null,
|
||||
) {
|
||||
loadExtractor(url, referer, subtitleCallback) { link ->
|
||||
if (link.quality == Qualities.Unknown.value || !link.isM3u8) {
|
||||
callback.invoke(
|
||||
ExtractorLink(
|
||||
link.source,
|
||||
link.name,
|
||||
link.url,
|
||||
link.referer,
|
||||
quality ?: link.quality,
|
||||
link.type,
|
||||
link.headers,
|
||||
link.extractorData
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.compress(): String {
|
||||
return this.replace("/original/", "/w500/")
|
||||
}
|
||||
|
||||
data class LoadData(
|
||||
val id: Int? = null,
|
||||
val season: Int? = null,
|
||||
val episode: Int? = null,
|
||||
val isSeries: Boolean? = null,
|
||||
val urls: List<Videos>? = listOf(),
|
||||
)
|
||||
|
||||
data class Responses(
|
||||
@JsonProperty("pagination") val pagination: Pagination? = null,
|
||||
@JsonProperty("title") val title: Title? = null,
|
||||
@JsonProperty("seo") val seo: String? = null,
|
||||
@JsonProperty("credits") val credits: Credits? = null,
|
||||
@JsonProperty("seasons") val seasons: Seasons? = null,
|
||||
@JsonProperty("episodes") val episodes: Episodes? = null,
|
||||
@JsonProperty("titles") val titles: ArrayList<Data>? = arrayListOf(),
|
||||
@JsonProperty("results") val results: ArrayList<Data>? = arrayListOf(),
|
||||
)
|
||||
|
||||
data class Seasons(
|
||||
@JsonProperty("data") val data: ArrayList<Data>? = arrayListOf(),
|
||||
) {
|
||||
data class Data(
|
||||
@JsonProperty("id") val id: Int? = null,
|
||||
@JsonProperty("number") val number: Int? = null,
|
||||
@JsonProperty("poster") val poster: String? = null,
|
||||
@JsonProperty("release_date") val releaseDate: String? = null,
|
||||
)
|
||||
}
|
||||
|
||||
data class Episodes(
|
||||
@JsonProperty("data") val data: ArrayList<Data>? = arrayListOf(),
|
||||
@JsonProperty("episode") val episode: Data? = null,
|
||||
) {
|
||||
data class Data(
|
||||
@JsonProperty("id") val id: Int? = null,
|
||||
@JsonProperty("name") val name: String? = null,
|
||||
@JsonProperty("description") val description: String? = null,
|
||||
@JsonProperty("season_number") val seasonNumber: Int? = null,
|
||||
@JsonProperty("episode_number") val episodeNumber: Int? = null,
|
||||
@JsonProperty("rating") val rating: Float? = null,
|
||||
@JsonProperty("poster") val poster: String? = null,
|
||||
@JsonProperty("release_date") val releaseDate: String? = null,
|
||||
@JsonProperty("status") val status: String? = null,
|
||||
@JsonProperty("videos") val videos: ArrayList<Videos>? = arrayListOf(),
|
||||
)
|
||||
}
|
||||
|
||||
data class Pagination(
|
||||
@JsonProperty("data") val data: ArrayList<Data>? = arrayListOf(),
|
||||
)
|
||||
|
||||
data class Data(
|
||||
@JsonProperty("id") val id: Any? = null,
|
||||
@JsonProperty("name") val name: String? = null,
|
||||
@JsonProperty("poster") val poster: String? = null,
|
||||
@JsonProperty("backdrop") val backdrop: String? = null,
|
||||
)
|
||||
|
||||
data class Credits(
|
||||
@JsonProperty("actors") val actors: ArrayList<Actors>? = arrayListOf(),
|
||||
) {
|
||||
data class Actors(
|
||||
@JsonProperty("name") val name: String? = null,
|
||||
@JsonProperty("poster") val poster: String? = null,
|
||||
@JsonProperty("pivot") val pivot: Pivot? = null,
|
||||
) {
|
||||
data class Pivot(
|
||||
@JsonProperty("character") val character: String? = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class Videos(
|
||||
@JsonProperty("category") val category: String? = null,
|
||||
@JsonProperty("src") val src: String? = null,
|
||||
@JsonProperty("quality") val quality: String? = null,
|
||||
)
|
||||
|
||||
data class Title(
|
||||
@JsonProperty("id") val id: Int? = null,
|
||||
@JsonProperty("name") val name: String? = null,
|
||||
@JsonProperty("release_date") val releaseDate: String? = null,
|
||||
@JsonProperty("year") val year: Int? = null,
|
||||
@JsonProperty("runtime") val runtime: Int? = null,
|
||||
@JsonProperty("poster") val poster: String? = null,
|
||||
@JsonProperty("backdrop") val backdrop: String? = null,
|
||||
@JsonProperty("description") val description: String? = null,
|
||||
@JsonProperty("certification") val certification: String? = null,
|
||||
@JsonProperty("rating") val rating: Float? = null,
|
||||
@JsonProperty("imdb_id") val imdbId: String? = null,
|
||||
@JsonProperty("tmdb_id") val tmdbId: String? = null,
|
||||
@JsonProperty("status") val status: String? = null,
|
||||
@JsonProperty("is_series") val isSeries: Boolean? = null,
|
||||
@JsonProperty("videos") val videos: ArrayList<Videos>? = arrayListOf(),
|
||||
@JsonProperty("keywords") val keywords: ArrayList<Keywords>? = arrayListOf(),
|
||||
) {
|
||||
data class Keywords(
|
||||
@JsonProperty("display_name") val displayName: String? = null,
|
||||
)
|
||||
}
|
||||
|
||||
}
|
20
Moflix/src/main/kotlin/com/hexated/MoflixPlugin.kt
Normal file
20
Moflix/src/main/kotlin/com/hexated/MoflixPlugin.kt
Normal file
|
@ -0,0 +1,20 @@
|
|||
|
||||
package com.hexated
|
||||
|
||||
import com.lagradost.cloudstream3.plugins.CloudstreamPlugin
|
||||
import com.lagradost.cloudstream3.plugins.Plugin
|
||||
import android.content.Context
|
||||
|
||||
@CloudstreamPlugin
|
||||
class MoflixPlugin: Plugin() {
|
||||
override fun load(context: Context) {
|
||||
// All providers should be added in this manner. Please don't edit the providers list directly.
|
||||
registerMainAPI(Moflix())
|
||||
registerMainAPI(Cineclix())
|
||||
registerExtractorAPI(MoflixClick())
|
||||
registerExtractorAPI(Highstream())
|
||||
registerExtractorAPI(MoflixFans())
|
||||
registerExtractorAPI(MoflixLink())
|
||||
registerExtractorAPI(Doodstream())
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
// use an integer for version numbers
|
||||
version = 55
|
||||
version = 63
|
||||
|
||||
|
||||
cloudstream {
|
||||
|
|
|
@ -5,7 +5,7 @@ import com.lagradost.cloudstream3.mainPageOf
|
|||
|
||||
class Animesaga : Movierulzhd() {
|
||||
|
||||
override var mainUrl = "https://www.animesaga.in"
|
||||
override var mainUrl = "https://anplay.in"
|
||||
override var name = "Animesaga"
|
||||
override val supportedTypes = setOf(
|
||||
TvType.Anime,
|
||||
|
@ -18,6 +18,4 @@ class Animesaga : Movierulzhd() {
|
|||
"tvshows" to "TV-Shows",
|
||||
"genre/hindi-dub" to "Hindi Dub",
|
||||
)
|
||||
|
||||
|
||||
}
|
|
@ -68,5 +68,5 @@ open class Akamaicdn : ExtractorApi() {
|
|||
|
||||
class AnimesagaStream : Chillx() {
|
||||
override val name = "AnimesagaStream"
|
||||
override val mainUrl = "https://stream.animesaga.in"
|
||||
override val mainUrl = "https://stream.anplay.in"
|
||||
}
|
||||
|
|
|
@ -12,14 +12,14 @@ import org.jsoup.Jsoup
|
|||
|
||||
class Hdmovie2 : Movierulzhd() {
|
||||
|
||||
override var mainUrl = "https://hdmovie2.li"
|
||||
override var mainUrl = "https://hdmovie2.tax"
|
||||
override var name = "Hdmovie2"
|
||||
override val mainPage = mainPageOf(
|
||||
"trending" to "Trending",
|
||||
"movies" to "Movies",
|
||||
"genre/tv-series" to "TV-Series",
|
||||
"genre/tv-series" to "TV Shows",
|
||||
"genre/netflix" to "Netflix",
|
||||
"genre/zee5-tv-series" to "Zee5 TV Series",
|
||||
"genre/zee5-tv-series" to "Zee5",
|
||||
)
|
||||
|
||||
override suspend fun loadLinks(
|
||||
|
|
|
@ -11,7 +11,7 @@ import java.net.URI
|
|||
|
||||
open class Movierulzhd : MainAPI() {
|
||||
|
||||
override var mainUrl = "https://movierulzhd.bet"
|
||||
override var mainUrl = "https://movierulzhd.cafe"
|
||||
var directUrl = ""
|
||||
override var name = "Movierulzhd"
|
||||
override val hasMainPage = true
|
||||
|
|
|
@ -24,5 +24,5 @@ cloudstream {
|
|||
"OVA",
|
||||
)
|
||||
|
||||
iconUrl = "https://www.google.com/s2/favicons?domain=neonime.watch&sz=%size%"
|
||||
iconUrl = "https://www.google.com/s2/favicons?domain=neonime.ink&sz=%size%"
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
// use an integer for version numbers
|
||||
version = 7
|
||||
version = 11
|
||||
|
||||
|
||||
cloudstream {
|
||||
|
|
|
@ -1,12 +1,8 @@
|
|||
package com.hexated
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.SubtitleFile
|
||||
import com.lagradost.cloudstream3.app
|
||||
import com.lagradost.cloudstream3.utils.ExtractorApi
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.INFER_TYPE
|
||||
import com.lagradost.cloudstream3.utils.Qualities
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.utils.*
|
||||
|
||||
open class Mitedrive : ExtractorApi() {
|
||||
override val name = "Mitedrive"
|
||||
|
@ -50,4 +46,50 @@ open class Mitedrive : ExtractorApi() {
|
|||
@JsonProperty("data") val data: Data? = null,
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
open class Berkasdrive : ExtractorApi() {
|
||||
override val name = "Berkasdrive"
|
||||
override val mainUrl = "https://dl.berkasdrive.com"
|
||||
override val requiresReferer = true
|
||||
|
||||
override suspend fun getUrl(
|
||||
url: String,
|
||||
referer: String?,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
) {
|
||||
val res = app.get(url, referer = referer).document
|
||||
val video = res.select("video#player source").attr("src")
|
||||
|
||||
callback.invoke(
|
||||
ExtractorLink(
|
||||
this.name,
|
||||
this.name,
|
||||
video,
|
||||
"$mainUrl/",
|
||||
Qualities.Unknown.value,
|
||||
INFER_TYPE
|
||||
)
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
open class Videogami : ExtractorApi() {
|
||||
override val name = "Videogami"
|
||||
override val mainUrl = "https://video.nimegami.id"
|
||||
override val requiresReferer = false
|
||||
|
||||
override suspend fun getUrl(
|
||||
url: String,
|
||||
referer: String?,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
) {
|
||||
val id = base64Decode(url.substringAfter("url=")).substringAfterLast("/")
|
||||
loadExtractor("https://hxfile.co/embed-$id.html", "$mainUrl/", subtitleCallback, callback)
|
||||
}
|
||||
|
||||
}
|
|
@ -9,7 +9,6 @@ import com.lagradost.cloudstream3.utils.*
|
|||
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
|
||||
import org.jsoup.nodes.Element
|
||||
import org.jsoup.select.Elements
|
||||
import java.net.URI
|
||||
|
||||
class Nimegami : MainAPI() {
|
||||
override var mainUrl = "https://nimegami.id"
|
||||
|
@ -77,14 +76,18 @@ class Nimegami : MainAPI() {
|
|||
this.posterUrl = posterUrl
|
||||
addSub(episode)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override suspend fun search(query: String): List<SearchResponse> {
|
||||
return app.get("$mainUrl/?s=$query&post_type=post").document.select("div.archive article")
|
||||
.mapNotNull {
|
||||
it.toSearchResult()
|
||||
}
|
||||
val searchResponse = mutableListOf<SearchResponse>()
|
||||
for (i in 1..2) {
|
||||
val res = app.get("$mainUrl/page/$i/?s=$query&post_type=post").document.select("div.archive article")
|
||||
.mapNotNull {
|
||||
it.toSearchResult()
|
||||
}
|
||||
searchResponse.addAll(res)
|
||||
}
|
||||
return searchResponse
|
||||
}
|
||||
|
||||
override suspend fun load(url: String): LoadResponse {
|
||||
|
@ -150,7 +153,7 @@ class Nimegami : MainAPI() {
|
|||
tryParseJson<ArrayList<Sources>>(base64Decode(data))?.map { sources ->
|
||||
sources.url?.apmap { url ->
|
||||
loadFixedExtractor(
|
||||
url.fixIframe(),
|
||||
url,
|
||||
sources.format,
|
||||
"$mainUrl/",
|
||||
subtitleCallback,
|
||||
|
@ -185,32 +188,10 @@ class Nimegami : MainAPI() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun getBaseUrl(url: String): String {
|
||||
return URI(url).let {
|
||||
"${it.scheme}://${it.host}"
|
||||
}
|
||||
}
|
||||
|
||||
private fun Elements.getContent(css: String): Elements {
|
||||
return this.select("tr:contains($css) td:last-child")
|
||||
}
|
||||
|
||||
private fun String.fixIframe(): String {
|
||||
val url = base64Decode(this.substringAfter("url=").substringAfter("id="))
|
||||
return when {
|
||||
url.contains("hxfile") -> {
|
||||
val host = getBaseUrl(url)
|
||||
val id = url.substringAfterLast("/")
|
||||
"$host/embed-$id.html"
|
||||
}
|
||||
url.startsWith("https://mitedrive.my.id") -> url.replace(
|
||||
"https://mitedrive.my.id",
|
||||
"https://mitedrive.com"
|
||||
)
|
||||
else -> fixUrl(url)
|
||||
}
|
||||
}
|
||||
|
||||
data class Sources(
|
||||
@JsonProperty("format") val format: String? = null,
|
||||
@JsonProperty("url") val url: ArrayList<String>? = arrayListOf(),
|
||||
|
|
|
@ -11,5 +11,7 @@ class NimegamiPlugin: Plugin() {
|
|||
// All providers should be added in this manner. Please don't edit the providers list directly.
|
||||
registerMainAPI(Nimegami())
|
||||
registerExtractorAPI(Mitedrive())
|
||||
registerExtractorAPI(Berkasdrive())
|
||||
registerExtractorAPI(Videogami())
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
// use an integer for version numbers
|
||||
version = 19
|
||||
version = 21
|
||||
|
||||
|
||||
cloudstream {
|
||||
|
@ -23,5 +23,5 @@ cloudstream {
|
|||
"OVA",
|
||||
)
|
||||
|
||||
iconUrl = "https://www.google.com/s2/favicons?domain=nontonanimeid.site&sz=%size%"
|
||||
iconUrl = "https://www.google.com/s2/favicons?domain=nontonanimeid.org&sz=%size%"
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
package com.hexated
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.APIHolder
|
||||
import com.lagradost.cloudstream3.SubtitleFile
|
||||
import com.lagradost.cloudstream3.app
|
||||
import com.lagradost.cloudstream3.extractors.Hxfile
|
||||
import com.lagradost.cloudstream3.utils.ExtractorApi
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.Qualities
|
||||
|
||||
open class Gdplayer : ExtractorApi() {
|
||||
override val name = "Gdplayer"
|
||||
override val mainUrl = "https://gdplayer.to"
|
||||
override val requiresReferer = true
|
||||
|
||||
override suspend fun getUrl(
|
||||
url: String,
|
||||
referer: String?,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
) {
|
||||
val res = app.get(url, referer = referer).document
|
||||
val script = res.selectFirst("script:containsData(player = \"\")")?.data()
|
||||
val kaken = script?.substringAfter("kaken = \"")?.substringBefore("\"")
|
||||
|
||||
val json = app.get(
|
||||
"$mainUrl/api/?${kaken ?: return}=&_=${APIHolder.unixTimeMS}",
|
||||
headers = mapOf(
|
||||
"X-Requested-With" to "XMLHttpRequest"
|
||||
)
|
||||
).parsedSafe<Response>()
|
||||
|
||||
json?.sources?.map {
|
||||
callback.invoke(
|
||||
ExtractorLink(
|
||||
this.name,
|
||||
this.name,
|
||||
it.file ?: return@map,
|
||||
"",
|
||||
getQuality(json.title)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getQuality(str: String?): Int {
|
||||
return Regex("(\\d{3,4})[pP]").find(str ?: "")?.groupValues?.getOrNull(1)?.toIntOrNull()
|
||||
?: Qualities.Unknown.value
|
||||
}
|
||||
|
||||
data class Response(
|
||||
@JsonProperty("title") val title: String? = null,
|
||||
@JsonProperty("sources") val sources: ArrayList<Sources>? = null,
|
||||
) {
|
||||
data class Sources(
|
||||
@JsonProperty("file") val file: String? = null,
|
||||
@JsonProperty("type") val type: String? = null,
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Nontonanimeid : Hxfile() {
|
||||
override val name = "Nontonanimeid"
|
||||
override val mainUrl = "https://nontonanimeid.com"
|
||||
override val requiresReferer = true
|
||||
}
|
||||
|
||||
class EmbedKotakAnimeid : Hxfile() {
|
||||
override val name = "EmbedKotakAnimeid"
|
||||
override val mainUrl = "https://embed2.kotakanimeid.com"
|
||||
override val requiresReferer = true
|
||||
}
|
||||
|
||||
class Kotaksb : Hxfile() {
|
||||
override val name = "Kotaksb"
|
||||
override val mainUrl = "https://kotaksb.fun"
|
||||
override val requiresReferer = true
|
||||
}
|
||||
|
||||
class KotakAnimeidCom : Hxfile() {
|
||||
override val name = "KotakAnimeid"
|
||||
override val mainUrl = "https://kotakanimeid.com"
|
||||
override val requiresReferer = true
|
||||
}
|
|
@ -5,7 +5,7 @@ import com.lagradost.cloudstream3.*
|
|||
import com.lagradost.cloudstream3.LoadResponse.Companion.addAniListId
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addMalId
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
|
||||
import com.lagradost.cloudstream3.extractors.Hxfile
|
||||
import com.lagradost.cloudstream3.utils.AppUtils
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||
import org.jsoup.Jsoup
|
||||
|
@ -13,7 +13,7 @@ import org.jsoup.nodes.Element
|
|||
import java.net.URI
|
||||
|
||||
class NontonAnimeIDProvider : MainAPI() {
|
||||
override var mainUrl = "https://nontonanimeid.top"
|
||||
override var mainUrl = "https://nontonanimeid.org"
|
||||
override var name = "NontonAnimeID"
|
||||
override val hasQuickSearch = false
|
||||
override val hasMainPage = true
|
||||
|
@ -29,8 +29,8 @@ class NontonAnimeIDProvider : MainAPI() {
|
|||
companion object {
|
||||
fun getType(t: String): TvType {
|
||||
return when {
|
||||
t.contains("TV",true) -> TvType.Anime
|
||||
t.contains("Movie",true) -> TvType.AnimeMovie
|
||||
t.contains("TV", true) -> TvType.Anime
|
||||
t.contains("Movie", true) -> TvType.AnimeMovie
|
||||
else -> TvType.OVA
|
||||
}
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ class NontonAnimeIDProvider : MainAPI() {
|
|||
"popular-series/" to "Popular Series",
|
||||
)
|
||||
|
||||
override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse {
|
||||
override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse {
|
||||
val document = app.get("$mainUrl/${request.data}").document
|
||||
val home = document.select(".animeseries").mapNotNull {
|
||||
it.toSearchResult()
|
||||
|
@ -61,7 +61,7 @@ class NontonAnimeIDProvider : MainAPI() {
|
|||
private fun Element.toSearchResult(): AnimeSearchResponse {
|
||||
val href = fixUrl(this.selectFirst("a")!!.attr("href"))
|
||||
val title = this.selectFirst(".title")?.text() ?: ""
|
||||
val posterUrl = fixUrlNull(this.selectFirst("img")?.attr("data-src"))
|
||||
val posterUrl = fixUrlNull(this.selectFirst("img")?.getImageAttr())
|
||||
|
||||
return newAnimeSearchResponse(title, href, TvType.Anime) {
|
||||
this.posterUrl = posterUrl
|
||||
|
@ -76,7 +76,7 @@ class NontonAnimeIDProvider : MainAPI() {
|
|||
|
||||
return document.select(".result > ul > li").mapNotNull {
|
||||
val title = it.selectFirst("h2")!!.text().trim()
|
||||
val poster = it.selectFirst("img")!!.attr("src")
|
||||
val poster = it.selectFirst("img")?.getImageAttr()
|
||||
val tvType = getType(
|
||||
it.selectFirst(".boxinfores > span.typeseries")!!.text().toString()
|
||||
)
|
||||
|
@ -100,8 +100,9 @@ class NontonAnimeIDProvider : MainAPI() {
|
|||
mainUrl = getBaseUrl(req.url)
|
||||
val document = req.document
|
||||
|
||||
val title = document.selectFirst("h1.entry-title.cs")!!.text().removeSurrounding("Nonton Anime", "Sub Indo").trim()
|
||||
val poster = document.selectFirst(".poster > img")?.attr("data-src")
|
||||
val title = document.selectFirst("h1.entry-title.cs")!!.text()
|
||||
.removeSurrounding("Nonton Anime", "Sub Indo").trim()
|
||||
val poster = document.selectFirst(".poster > img")?.getImageAttr()
|
||||
val tags = document.select(".tagline > a").map { it.text() }
|
||||
|
||||
val year = Regex("\\d, (\\d*)").find(
|
||||
|
@ -150,14 +151,14 @@ class NontonAnimeIDProvider : MainAPI() {
|
|||
val recommendations = document.select(".result > li").mapNotNull {
|
||||
val epHref = it.selectFirst("a")!!.attr("href")
|
||||
val epTitle = it.selectFirst("h3")!!.text()
|
||||
val epPoster = it.select(".top > img").attr("data-src")
|
||||
val epPoster = it.selectFirst(".top > img")?.getImageAttr()
|
||||
newAnimeSearchResponse(epTitle, epHref, TvType.Anime) {
|
||||
this.posterUrl = epPoster
|
||||
addDubStatus(dubExist = false, subExist = true)
|
||||
}
|
||||
}
|
||||
|
||||
val tracker = APIHolder.getTracker(listOf(title),TrackerType.getTypes(type),year,true)
|
||||
val tracker = APIHolder.getTracker(listOf(title), TrackerType.getTypes(type), year, true)
|
||||
|
||||
return newAnimeLoadResponse(title, url, type) {
|
||||
engName = title
|
||||
|
@ -186,6 +187,12 @@ class NontonAnimeIDProvider : MainAPI() {
|
|||
|
||||
val document = app.get(data).document
|
||||
|
||||
val nonce =
|
||||
document.select("script#ajax_video-js-extra").attr("src").substringAfter("base64,")
|
||||
.let {
|
||||
AppUtils.parseJson<Map<String, String>>(base64Decode(it).substringAfter("="))["nonce"]
|
||||
}
|
||||
|
||||
document.select(".container1 > ul > li:not(.boxtab)").apmap {
|
||||
val dataPost = it.attr("data-post")
|
||||
val dataNume = it.attr("data-nume")
|
||||
|
@ -198,13 +205,13 @@ class NontonAnimeIDProvider : MainAPI() {
|
|||
"post" to dataPost,
|
||||
"nume" to dataNume,
|
||||
"type" to dataType,
|
||||
"nonce" to "e4dd8e45c2"
|
||||
"nonce" to "$nonce"
|
||||
),
|
||||
referer = data,
|
||||
headers = mapOf("X-Requested-With" to "XMLHttpRequest")
|
||||
).document.selectFirst("iframe")?.attr("src")
|
||||
|
||||
loadExtractor(iframe ?: return@apmap , "$mainUrl/", subtitleCallback, callback)
|
||||
loadExtractor(iframe ?: return@apmap, "$mainUrl/", subtitleCallback, callback)
|
||||
}
|
||||
|
||||
return true
|
||||
|
@ -215,6 +222,16 @@ class NontonAnimeIDProvider : MainAPI() {
|
|||
"${it.scheme}://${it.host}"
|
||||
}
|
||||
}
|
||||
|
||||
private fun Element.getImageAttr(): String? {
|
||||
return when {
|
||||
this.hasAttr("data-src") -> this.attr("abs:data-src")
|
||||
this.hasAttr("data-lazy-src") -> this.attr("abs:data-lazy-src")
|
||||
this.hasAttr("srcset") -> this.attr("abs:srcset").substringBefore(" ")
|
||||
else -> this.attr("abs:src")
|
||||
}
|
||||
}
|
||||
|
||||
private data class EpResponse(
|
||||
@JsonProperty("posts") val posts: String?,
|
||||
@JsonProperty("max_page") val max_page: Int?,
|
||||
|
@ -223,21 +240,3 @@ class NontonAnimeIDProvider : MainAPI() {
|
|||
)
|
||||
|
||||
}
|
||||
|
||||
class KotakAnimeid2 : Hxfile() {
|
||||
override val name = "KotakAnimeid2"
|
||||
override val mainUrl = "https://embed2.kotakanimeid.com"
|
||||
override val requiresReferer = true
|
||||
}
|
||||
|
||||
class KotakAnimeidCom : Hxfile() {
|
||||
override val name = "KotakAnimeid"
|
||||
override val mainUrl = "https://nontonanimeid.com"
|
||||
override val requiresReferer = true
|
||||
}
|
||||
|
||||
class EmbedKotakAnimeid : Hxfile() {
|
||||
override val name = "EmbedKotakAnimeid"
|
||||
override val mainUrl = "https://embed2.kotakanimeid.com"
|
||||
override val requiresReferer = true
|
||||
}
|
||||
|
|
|
@ -10,8 +10,10 @@ class NontonAnimeIDProviderPlugin: Plugin() {
|
|||
override fun load(context: Context) {
|
||||
// All providers should be added in this manner. Please don't edit the providers list directly.
|
||||
registerMainAPI(NontonAnimeIDProvider())
|
||||
registerExtractorAPI(KotakAnimeid2())
|
||||
registerExtractorAPI(KotakAnimeidCom())
|
||||
registerExtractorAPI(Nontonanimeid())
|
||||
registerExtractorAPI(EmbedKotakAnimeid())
|
||||
registerExtractorAPI(KotakAnimeidCom())
|
||||
registerExtractorAPI(Gdplayer())
|
||||
registerExtractorAPI(Kotaksb())
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
// use an integer for version numbers
|
||||
version = 24
|
||||
version = 30
|
||||
|
||||
|
||||
cloudstream {
|
||||
|
|
|
@ -27,10 +27,7 @@ open class Qiwi : ExtractorApi() {
|
|||
"$mainUrl/",
|
||||
getIndexQuality(title),
|
||||
headers = mapOf(
|
||||
"Accept" to "video/webm,video/ogg,video/*;q=0.9,application/ogg;q=0.7,audio/*;q=0.6,*/*;q=0.5",
|
||||
"Range" to "bytes=0-",
|
||||
"Sec-Fetch-Dest" to "video",
|
||||
"Sec-Fetch-Mode" to "no-cors",
|
||||
)
|
||||
)
|
||||
)
|
||||
|
|
|
@ -7,7 +7,7 @@ import com.lagradost.cloudstream3.utils.*
|
|||
import org.jsoup.nodes.Element
|
||||
|
||||
class OploverzProvider : MainAPI() {
|
||||
override var mainUrl = "https://oploverz.red"
|
||||
override var mainUrl = "https://oploverz.gold"
|
||||
override var name = "Oploverz"
|
||||
override val hasMainPage = true
|
||||
override var lang = "id"
|
||||
|
@ -20,7 +20,6 @@ class OploverzProvider : MainAPI() {
|
|||
)
|
||||
|
||||
companion object {
|
||||
const val acefile = "https://acefile.co"
|
||||
fun getType(t: String): TvType {
|
||||
return if (t.contains("OVA", true) || t.contains("Special")) TvType.OVA
|
||||
else if (t.contains("Movie", true)) TvType.AnimeMovie
|
||||
|
@ -170,14 +169,14 @@ class OploverzProvider : MainAPI() {
|
|||
headers = mapOf("X-Requested-With" to "XMLHttpRequest")
|
||||
).document.select("iframe").attr("src")
|
||||
|
||||
loadExtractor(fixedIframe(iframe), "$mainUrl/", subtitleCallback, callback)
|
||||
loadExtractor(fixUrl(iframe), "$mainUrl/", subtitleCallback, callback)
|
||||
|
||||
}
|
||||
},
|
||||
{
|
||||
document.select("div#download tr").map { el ->
|
||||
document.select("div#download tr").apmap { el ->
|
||||
el.select("a").apmap {
|
||||
loadFixedExtractor(fixedIframe(it.attr("href")), el.select("strong").text(), "$mainUrl/", subtitleCallback, callback)
|
||||
loadFixedExtractor(fixUrl(it.attr("href")), el.select("strong").text(), "$mainUrl/", subtitleCallback, callback)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -217,12 +216,4 @@ class OploverzProvider : MainAPI() {
|
|||
}
|
||||
}
|
||||
|
||||
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"
|
||||
else -> fixUrl(url)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// use an integer for version numbers
|
||||
version = 6
|
||||
version = 7
|
||||
|
||||
|
||||
cloudstream {
|
||||
|
@ -24,5 +24,5 @@ cloudstream {
|
|||
"Movie",
|
||||
)
|
||||
|
||||
iconUrl = "https://www.google.com/s2/favicons?domain=phimmoichilla.net&sz=%size%"
|
||||
iconUrl = "https://www.google.com/s2/favicons?domain=phimmoichillk.net&sz=%size%"
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import java.net.URI
|
|||
import java.net.URLDecoder
|
||||
|
||||
class PhimmoichillProvider : MainAPI() {
|
||||
override var mainUrl = "https://phimmoichillh.net"
|
||||
override var mainUrl = "https://phimmoichillk.net"
|
||||
private var directUrl = mainUrl
|
||||
override var name = "Phimmoichill"
|
||||
override val hasMainPage = true
|
||||
|
|
27
Raveeflix/build.gradle.kts
Normal file
27
Raveeflix/build.gradle.kts
Normal file
|
@ -0,0 +1,27 @@
|
|||
// use an integer for version numbers
|
||||
version = 3
|
||||
|
||||
|
||||
cloudstream {
|
||||
language = "id"
|
||||
// All of these properties are optional, you can safely remove them
|
||||
|
||||
// description = "Lorem Ipsum"
|
||||
authors = listOf("Hexated")
|
||||
|
||||
/**
|
||||
* Status int as the following:
|
||||
* 0: Down
|
||||
* 1: Ok
|
||||
* 2: Slow
|
||||
* 3: Beta only
|
||||
* */
|
||||
status = 1 // will be 3 if unspecified
|
||||
tvTypes = listOf(
|
||||
"AsianDrama",
|
||||
"TvSeries",
|
||||
"Movie",
|
||||
)
|
||||
|
||||
iconUrl = "https://raveeflix.my.id/favicon.ico"
|
||||
}
|
2
Raveeflix/src/main/AndroidManifest.xml
Normal file
2
Raveeflix/src/main/AndroidManifest.xml
Normal file
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest package="com.hexated"/>
|
227
Raveeflix/src/main/kotlin/com/hexated/Raveeflix.kt
Normal file
227
Raveeflix/src/main/kotlin/com/hexated/Raveeflix.kt
Normal file
|
@ -0,0 +1,227 @@
|
|||
package com.hexated
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addActors
|
||||
import com.lagradost.cloudstream3.utils.*
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.toJson
|
||||
import org.jsoup.nodes.Element
|
||||
import org.jsoup.select.Elements
|
||||
|
||||
class Raveeflix : MainAPI() {
|
||||
override var mainUrl = "https://raveeflix.my.id"
|
||||
override var name = "Raveeflix"
|
||||
override val hasMainPage = true
|
||||
override var lang = "id"
|
||||
override val supportedTypes =
|
||||
setOf(
|
||||
TvType.Movie,
|
||||
TvType.TvSeries,
|
||||
TvType.AsianDrama,
|
||||
)
|
||||
|
||||
override val mainPage =
|
||||
mainPageOf(
|
||||
"categories/trending" to "Trending",
|
||||
"movies" to "Movies",
|
||||
"tv" to "Tv-Shows",
|
||||
"drakor" to "Drakor",
|
||||
"categories/anime" to "Anime",
|
||||
)
|
||||
|
||||
override suspend fun getMainPage(
|
||||
page: Int,
|
||||
request: MainPageRequest,
|
||||
): HomePageResponse {
|
||||
val pages = if (page > 1) "page/$page/" else ""
|
||||
val document = app.get("$mainUrl/${request.data}/$pages").document
|
||||
val home = document.select("section.w-full a.min-w-full").mapNotNull { it.toSearchResult() }
|
||||
return newHomePageResponse(
|
||||
HomePageList(
|
||||
request.name, home, true
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun Element.toSearchResult(): SearchResponse? {
|
||||
val title = this.selectFirst("div.text-xl")?.text() ?: return null
|
||||
val href = fixUrl(this.attr("href"))
|
||||
val posterUrl = this.selectFirst("div.thumbnail_card")?.attr("style")?.getPoster()
|
||||
|
||||
return newMovieSearchResponse(title, Media(href, posterUrl).toJson(), TvType.Movie, false) {
|
||||
this.posterUrl = posterUrl
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun search(query: String): List<SearchResponse>? {
|
||||
val res =
|
||||
app.get("$mainUrl/index.json").text.let { AppUtils.tryParseJson<ArrayList<Index>>(it) }
|
||||
return res?.filter {
|
||||
it.title?.contains(
|
||||
query,
|
||||
true
|
||||
) == true && !it.section.equals("Categories", true) && !it.section.equals(
|
||||
"Tags",
|
||||
true
|
||||
) && it.permalink?.contains("/episode") == false
|
||||
}?.mapNotNull {
|
||||
newMovieSearchResponse(
|
||||
it.title ?: return@mapNotNull null,
|
||||
Media(
|
||||
fixUrl(
|
||||
it.permalink?.substringBefore("episode")?.substringBefore("season")
|
||||
?: return@mapNotNull null
|
||||
)
|
||||
).toJson(),
|
||||
TvType.Movie,
|
||||
false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun load(url: String): LoadResponse {
|
||||
val media = parseJson<Media>(url)
|
||||
val document = app.get(media.url).document
|
||||
val title = document.selectFirst("h1.text-4xl")?.text() ?: "No Title"
|
||||
val poster = media.poster ?: document.selectFirst("div.thumbnail_card, div.w-full.thumbnail_card_related")
|
||||
?.attr("style")?.getPoster()
|
||||
val type =
|
||||
if (document.select("mux-player").isNullOrEmpty()) TvType.TvSeries else TvType.Movie
|
||||
val tags =
|
||||
if (type == TvType.TvSeries) {
|
||||
document.selectFirst("div.movie-details > p:nth-child(1)")
|
||||
?.ownText()?.split(",")
|
||||
?.map { it.trim() }
|
||||
} else {
|
||||
document.select("span.mr-2")
|
||||
.map { it.text() }.distinct()
|
||||
}
|
||||
|
||||
val year =
|
||||
document.selectFirst("div.movie-details > p:nth-child(2), div.max-w-prose.mb-20 > ul > li:nth-child(2) span")
|
||||
?.ownText()?.substringAfter(",")?.toIntOrNull()
|
||||
val description =
|
||||
document.selectFirst("div.lead.text-neutral-500, span#storyline")
|
||||
?.text()?.trim()
|
||||
val rating =
|
||||
document.selectFirst("span#rating")?.text()
|
||||
?.toRatingInt()
|
||||
val actors =
|
||||
document.select("span#cast").text().split(", ")
|
||||
.map { it.trim() }
|
||||
|
||||
val recommendations =
|
||||
document.select("section.w-full a.min-w-full").mapNotNull { it.toSearchResult() }
|
||||
|
||||
return if (type == TvType.TvSeries) {
|
||||
val sectionSelector = "div.relative > section.w-full a.min-w-full"
|
||||
val section = document.select(sectionSelector)
|
||||
val hasMultipleSeason = section.any { it.attr("href").contains("/season-") }
|
||||
val episodes = if (hasMultipleSeason) {
|
||||
section.apmap { ss ->
|
||||
fetchEpisodesFromPages(
|
||||
ss.attr("href"),
|
||||
5,
|
||||
sectionSelector,
|
||||
true,
|
||||
ss.selectFirst("div.text-xl")?.text()?.filter { it.isDigit() }
|
||||
?.toIntOrNull()
|
||||
)
|
||||
}.toMutableList().flatten()
|
||||
} else {
|
||||
fetchEpisodesFromPages(media.url, 5, sectionSelector, false)
|
||||
}
|
||||
newTvSeriesLoadResponse(title, url, TvType.TvSeries, episodes.reversed()) {
|
||||
this.posterUrl = poster
|
||||
this.year = year
|
||||
this.seasonNames
|
||||
this.plot = description
|
||||
this.tags = tags
|
||||
this.rating = rating
|
||||
addActors(actors)
|
||||
this.recommendations = recommendations
|
||||
}
|
||||
} else {
|
||||
newMovieLoadResponse(title, url, TvType.Movie, media.url) {
|
||||
this.posterUrl = poster
|
||||
this.year = year
|
||||
this.plot = description
|
||||
this.tags = tags
|
||||
this.rating = rating
|
||||
addActors(actors)
|
||||
this.recommendations = recommendations
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit,
|
||||
): Boolean {
|
||||
|
||||
val video = app.get(data).document.select("mux-player").attr("src")
|
||||
|
||||
callback.invoke(
|
||||
ExtractorLink(
|
||||
name,
|
||||
name,
|
||||
video,
|
||||
"",
|
||||
Qualities.Unknown.value,
|
||||
)
|
||||
)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private suspend fun fetchEpisodesFromPages(
|
||||
baseUrl: String,
|
||||
maxPages: Int,
|
||||
sectionSelector: String,
|
||||
hasMultipleSeasons: Boolean,
|
||||
season: Int? = null
|
||||
): MutableList<Episode> {
|
||||
val epsData = mutableListOf<Episode>()
|
||||
for (index in 1..maxPages) {
|
||||
val pageUrl = if (index == 1) baseUrl else "${baseUrl.removeSuffix("/")}/page/$index/"
|
||||
val episodeVo = app.get(fixUrl(pageUrl)).document.select(sectionSelector)
|
||||
.getEpisodes(if (hasMultipleSeasons) season else null)
|
||||
if (episodeVo.isEmpty()) break
|
||||
epsData.addAll(episodeVo)
|
||||
}
|
||||
return epsData
|
||||
}
|
||||
|
||||
private fun Elements.getEpisodes(season: Int? = 1): List<Episode> {
|
||||
return this.mapNotNull { eps ->
|
||||
val name = eps.selectFirst("div.text-xl")?.text() ?: return@mapNotNull null
|
||||
val href = fixUrl(eps.attr("href"))
|
||||
val posterUrl =
|
||||
eps.selectFirst("div.thumbnail_card")?.attr("style")?.getPoster()
|
||||
Episode(
|
||||
href,
|
||||
name,
|
||||
posterUrl = posterUrl,
|
||||
season = season
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.getPoster(): String? {
|
||||
return fixUrlNull(
|
||||
this.substringAfter("(")
|
||||
.substringBefore(")"),
|
||||
)
|
||||
}
|
||||
|
||||
data class Media(val url: String, val poster: String? = null)
|
||||
|
||||
data class Index(
|
||||
@JsonProperty("title") val title: String? = null,
|
||||
@JsonProperty("permalink") val permalink: String? = null,
|
||||
@JsonProperty("section") val section: String? = null,
|
||||
)
|
||||
}
|
14
Raveeflix/src/main/kotlin/com/hexated/RaveeflixPlugin.kt
Normal file
14
Raveeflix/src/main/kotlin/com/hexated/RaveeflixPlugin.kt
Normal file
|
@ -0,0 +1,14 @@
|
|||
|
||||
package com.hexated
|
||||
|
||||
import com.lagradost.cloudstream3.plugins.CloudstreamPlugin
|
||||
import com.lagradost.cloudstream3.plugins.Plugin
|
||||
import android.content.Context
|
||||
|
||||
@CloudstreamPlugin
|
||||
class RaveeflixPlugin: Plugin() {
|
||||
override fun load(context: Context) {
|
||||
// All providers should be added in this manner. Please don't edit the providers list directly.
|
||||
registerMainAPI(Raveeflix())
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
// use an integer for version numbers
|
||||
version = 8
|
||||
version = 9
|
||||
|
||||
|
||||
cloudstream {
|
||||
|
|
|
@ -3,7 +3,9 @@ package com.hexated
|
|||
import com.lagradost.cloudstream3.TvType
|
||||
|
||||
class Cgvindo : RebahinProvider() {
|
||||
|
||||
override var mainUrl = "http://51.68.185.138/"
|
||||
|
||||
override var name = "Cgvindo"
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// use an integer for version numbers
|
||||
version = 17
|
||||
version = 22
|
||||
|
||||
|
||||
cloudstream {
|
||||
|
|
|
@ -10,7 +10,7 @@ import com.lagradost.cloudstream3.utils.loadExtractor
|
|||
import org.jsoup.nodes.Element
|
||||
|
||||
class Samehadaku : MainAPI() {
|
||||
override var mainUrl = "https://samehadaku.digital/"
|
||||
override var mainUrl = "https://samehadaku.show"
|
||||
override var name = "Samehadaku"
|
||||
override val hasMainPage = true
|
||||
override var lang = "id"
|
||||
|
@ -20,10 +20,7 @@ class Samehadaku : MainAPI() {
|
|||
TvType.AnimeMovie,
|
||||
TvType.OVA
|
||||
)
|
||||
|
||||
companion object {
|
||||
const val acefile = "https://acefile.co"
|
||||
|
||||
fun getType(t: String): TvType {
|
||||
return if (t.contains("OVA", true) || t.contains("Special", true)) TvType.OVA
|
||||
else if (t.contains("Movie", true)) TvType.AnimeMovie
|
||||
|
@ -63,9 +60,9 @@ class Samehadaku : MainAPI() {
|
|||
|
||||
if (request.name == "Episode Terbaru") {
|
||||
val home = app.get(request.data + page).document.selectFirst("div.post-show")?.select("ul li")
|
||||
?.mapNotNull {
|
||||
it.toSearchResult()
|
||||
} ?: throw ErrorLoadingException("No Media Found")
|
||||
?.mapNotNull {
|
||||
it.toSearchResult()
|
||||
} ?: throw ErrorLoadingException("No Media Found")
|
||||
items.add(HomePageList(request.name, home, true))
|
||||
}
|
||||
|
||||
|
@ -158,37 +155,11 @@ class Samehadaku : MainAPI() {
|
|||
|
||||
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")
|
||||
|
||||
loadFixedExtractor(fixedIframe(iframe), it.text(), "$mainUrl/", subtitleCallback, callback)
|
||||
|
||||
}
|
||||
},
|
||||
{
|
||||
document.select("div#downloadb li").map { el ->
|
||||
el.select("a").apmap {
|
||||
loadFixedExtractor(fixedIframe(it.attr("href")), el.select("strong").text(), "$mainUrl/", subtitleCallback, callback)
|
||||
}
|
||||
}
|
||||
document.select("div#downloadb li").map { el ->
|
||||
el.select("a").apmap {
|
||||
loadFixedExtractor(fixUrl(it.attr("href")), el.select("strong").text(), "$mainUrl/", subtitleCallback, callback)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
@ -217,21 +188,14 @@ class Samehadaku : MainAPI() {
|
|||
}
|
||||
|
||||
private fun String.fixQuality() : Int {
|
||||
return when(this) {
|
||||
"MP4HD" -> Qualities.P720.value
|
||||
return when(this.uppercase()) {
|
||||
"4K" -> Qualities.P2160.value
|
||||
"FULLHD" -> Qualities.P1080.value
|
||||
"MP4HD" -> Qualities.P720.value
|
||||
else -> this.filter { it.isDigit() }.toIntOrNull() ?: Qualities.Unknown.value
|
||||
}
|
||||
}
|
||||
|
||||
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"
|
||||
else -> fixUrl(url)
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.removeBloat(): String {
|
||||
return this.replace(Regex("(Nonton)|(Anime)|(Subtitle\\sIndonesia)"), "").trim()
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import org.jetbrains.kotlin.konan.properties.Properties
|
||||
|
||||
// use an integer for version numbers
|
||||
version = 206
|
||||
version = 225
|
||||
|
||||
android {
|
||||
defaultConfig {
|
||||
|
@ -9,7 +9,7 @@ android {
|
|||
properties.load(project.rootProject.file("local.properties").inputStream())
|
||||
|
||||
buildConfigField("String", "TMDB_API", "\"${properties.getProperty("TMDB_API")}\"")
|
||||
buildConfigField("String", "OMOVIES_API", "\"${properties.getProperty("OMOVIES_API")}\"")
|
||||
buildConfigField("String", "GHOSTX_API", "\"${properties.getProperty("GHOSTX_API")}\"")
|
||||
buildConfigField("String", "CINEMATV_API", "\"${properties.getProperty("CINEMATV_API")}\"")
|
||||
buildConfigField("String", "SFMOVIES_API", "\"${properties.getProperty("SFMOVIES_API")}\"")
|
||||
buildConfigField("String", "ZSHOW_API", "\"${properties.getProperty("ZSHOW_API")}\"")
|
||||
|
@ -42,5 +42,5 @@ cloudstream {
|
|||
"Movie",
|
||||
)
|
||||
|
||||
iconUrl = "https://raw.githubusercontent.com/hexated/cloudstream-extensions-hexated/master/SoraStream/Icon.png"
|
||||
iconUrl = "https://cdn.discordapp.com/attachments/1109266606292488297/1193122096159674448/2-modified.png"
|
||||
}
|
||||
|
|
|
@ -10,8 +10,8 @@ import com.lagradost.cloudstream3.SubtitleFile
|
|||
import com.lagradost.cloudstream3.apmap
|
||||
import com.lagradost.cloudstream3.app
|
||||
import com.lagradost.cloudstream3.base64Decode
|
||||
import com.lagradost.cloudstream3.extractors.Pixeldrain
|
||||
import com.lagradost.cloudstream3.extractors.Vidplay
|
||||
import com.lagradost.cloudstream3.extractors.Jeniusplay
|
||||
import com.lagradost.cloudstream3.extractors.PixelDrain
|
||||
import com.lagradost.cloudstream3.utils.*
|
||||
import java.math.BigInteger
|
||||
import java.security.MessageDigest
|
||||
|
@ -124,8 +124,8 @@ open class Playm4u : ExtractorApi() {
|
|||
return "$this\\s*=\\s*[\"'](\\S+)[\"'];".toRegex().find(data)?.groupValues?.get(1) ?: ""
|
||||
}
|
||||
|
||||
private fun String.toLanguage() : String {
|
||||
return if(this == "EN") "English" else this
|
||||
private fun String.toLanguage(): String {
|
||||
return if (this == "EN") "English" else this
|
||||
}
|
||||
|
||||
data class Source(
|
||||
|
@ -209,7 +209,10 @@ open class VCloud : ExtractorApi() {
|
|||
)
|
||||
).document.select("p.text-success ~ a").apmap {
|
||||
val link = it.attr("href")
|
||||
if (link.contains("workers.dev") || it.text().contains("[Server : 1]") || link.contains("/dl.php?")) {
|
||||
if (link.contains("workers.dev") || it.text().contains("[Server : 1]") || link.contains(
|
||||
"/dl.php?"
|
||||
)
|
||||
) {
|
||||
callback.invoke(
|
||||
ExtractorLink(
|
||||
this.name,
|
||||
|
@ -221,7 +224,7 @@ open class VCloud : ExtractorApi() {
|
|||
)
|
||||
)
|
||||
} else {
|
||||
val direct = if(link.contains("gofile.io")) app.get(link).url else link
|
||||
val direct = if (link.contains("gofile.io")) app.get(link).url else link
|
||||
loadExtractor(direct, referer, subtitleCallback, callback)
|
||||
}
|
||||
}
|
||||
|
@ -247,19 +250,20 @@ open class Streamruby : ExtractorApi() {
|
|||
callback: (ExtractorLink) -> Unit
|
||||
) {
|
||||
val id = "/e/(\\w+)".toRegex().find(url)?.groupValues?.get(1) ?: return
|
||||
val response = app.post("$mainUrl/dl", data = mapOf(
|
||||
"op" to "embed",
|
||||
"file_code" to id,
|
||||
"auto" to "1",
|
||||
"referer" to "",
|
||||
), referer = referer)
|
||||
val response = app.post(
|
||||
"$mainUrl/dl", data = mapOf(
|
||||
"op" to "embed",
|
||||
"file_code" to id,
|
||||
"auto" to "1",
|
||||
"referer" to "",
|
||||
), referer = referer
|
||||
)
|
||||
val script = if (!getPacked(response.text).isNullOrEmpty()) {
|
||||
getAndUnpack(response.text)
|
||||
} else {
|
||||
response.document.selectFirst("script:containsData(sources:)")?.data()
|
||||
}
|
||||
val m3u8 =
|
||||
Regex("file:\\s*\"(.*?m3u8.*?)\"").find(script ?: return)?.groupValues?.getOrNull(1)
|
||||
val m3u8 = Regex("file:\\s*\"(.*?m3u8.*?)\"").find(script ?: return)?.groupValues?.getOrNull(1)
|
||||
M3u8Helper.generateM3u8(
|
||||
name,
|
||||
m3u8 ?: return,
|
||||
|
@ -282,17 +286,25 @@ open class Uploadever : ExtractorApi() {
|
|||
) {
|
||||
var res = app.get(url, referer = referer).document
|
||||
val formUrl = res.select("form").attr("action")
|
||||
var formData = res.select("form input").associate { it.attr("name") to it.attr("value") }.filterKeys { it != "go" }
|
||||
var formData = res.select("form input").associate { it.attr("name") to it.attr("value") }
|
||||
.filterKeys { it != "go" }
|
||||
.toMutableMap()
|
||||
val formReq = app.post(formUrl, data = formData)
|
||||
|
||||
res = formReq.document
|
||||
val captchaKey = res.select("script[src*=https://www.google.com/recaptcha/api.js?render=]").attr("src").substringAfter("render=")
|
||||
val captchaKey =
|
||||
res.select("script[src*=https://www.google.com/recaptcha/api.js?render=]").attr("src")
|
||||
.substringAfter("render=")
|
||||
val token = getCaptchaToken(url, captchaKey, referer = "$mainUrl/")
|
||||
formData = res.select("form#down input").associate { it.attr("name") to it.attr("value") }.toMutableMap()
|
||||
formData = res.select("form#down input").associate { it.attr("name") to it.attr("value") }
|
||||
.toMutableMap()
|
||||
formData["adblock_detected"] = "0"
|
||||
formData["referer"] = url
|
||||
res = app.post(formReq.url, data = formData + mapOf("g-recaptcha-response" to "$token"), cookies = formReq.cookies).document
|
||||
res = app.post(
|
||||
formReq.url,
|
||||
data = formData + mapOf("g-recaptcha-response" to "$token"),
|
||||
cookies = formReq.cookies
|
||||
).document
|
||||
val video = res.select("div.download-button a.btn.btn-dow.recaptchav2").attr("href")
|
||||
|
||||
callback.invoke(
|
||||
|
@ -325,31 +337,130 @@ open class Netembed : ExtractorApi() {
|
|||
val script = getAndUnpack(response.text)
|
||||
val m3u8 = Regex("((https:|http:)//.*\\.m3u8)").find(script)?.groupValues?.getOrNull(1) ?: return
|
||||
|
||||
if(m3u8.startsWith("https://www.febbox.com")) {
|
||||
callback.invoke(
|
||||
ExtractorLink(
|
||||
this.name,
|
||||
this.name,
|
||||
m3u8,
|
||||
"$mainUrl/",
|
||||
getQuality(m3u8),
|
||||
INFER_TYPE
|
||||
)
|
||||
)
|
||||
M3u8Helper.generateM3u8(this.name, m3u8, "$mainUrl/").forEach(callback)
|
||||
}
|
||||
}
|
||||
|
||||
open class Ridoo : ExtractorApi() {
|
||||
override val name = "Ridoo"
|
||||
override var mainUrl = "https://ridoo.net"
|
||||
override val requiresReferer = true
|
||||
open val defaulQuality = Qualities.P1080.value
|
||||
|
||||
override suspend fun getUrl(
|
||||
url: String,
|
||||
referer: String?,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
) {
|
||||
val response = app.get(url, referer = referer)
|
||||
val script = if (!getPacked(response.text).isNullOrEmpty()) {
|
||||
getAndUnpack(response.text)
|
||||
} else {
|
||||
M3u8Helper.generateM3u8(
|
||||
response.document.selectFirst("script:containsData(sources:)")?.data()
|
||||
}
|
||||
val m3u8 = Regex("file:\\s*\"(.*?m3u8.*?)\"").find(script ?: return)?.groupValues?.getOrNull(1)
|
||||
val quality = "qualityLabels.*\"(\\d{3,4})[pP]\"".toRegex().find(script)?.groupValues?.get(1)
|
||||
callback.invoke(
|
||||
ExtractorLink(
|
||||
this.name,
|
||||
m3u8,
|
||||
"$mainUrl/",
|
||||
).forEach(callback)
|
||||
this.name,
|
||||
m3u8 ?: return,
|
||||
mainUrl,
|
||||
quality?.toIntOrNull() ?: defaulQuality,
|
||||
INFER_TYPE
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
open class Gdmirrorbot : ExtractorApi() {
|
||||
override val name = "Gdmirrorbot"
|
||||
override val mainUrl = "https://gdmirrorbot.nl"
|
||||
override val requiresReferer = true
|
||||
|
||||
override suspend fun getUrl(
|
||||
url: String,
|
||||
referer: String?,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
) {
|
||||
app.get(url, referer = referer).document.select("ul#videoLinks li").apmap {
|
||||
loadExtractor(it.attr("data-link"), "$mainUrl/", subtitleCallback, callback)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getQuality(url: String) : Int {
|
||||
val res = app.get(url, referer = "$mainUrl/").text
|
||||
val regex = "#quality:\\s*(\\S+)".toRegex().find(res)?.groupValues?.get(1)
|
||||
return getQualityFromName(regex)
|
||||
}
|
||||
|
||||
open class Streamvid : ExtractorApi() {
|
||||
override val name = "Streamvid"
|
||||
override val mainUrl = "https://streamvid.net"
|
||||
override val requiresReferer = true
|
||||
|
||||
override suspend fun getUrl(
|
||||
url: String,
|
||||
referer: String?,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
) {
|
||||
val response = app.get(url, referer = referer)
|
||||
val script = if (!getPacked(response.text).isNullOrEmpty()) {
|
||||
getAndUnpack(response.text)
|
||||
} else {
|
||||
response.document.selectFirst("script:containsData(sources:)")?.data()
|
||||
}
|
||||
val m3u8 =
|
||||
Regex("src:\\s*\"(.*?m3u8.*?)\"").find(script ?: return)?.groupValues?.getOrNull(1)
|
||||
M3u8Helper.generateM3u8(
|
||||
name,
|
||||
m3u8 ?: return,
|
||||
mainUrl
|
||||
).forEach(callback)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
open class Embedrise : ExtractorApi() {
|
||||
override val name = "Embedrise"
|
||||
override val mainUrl = "https://embedrise.com"
|
||||
override val requiresReferer = true
|
||||
|
||||
override suspend fun getUrl(
|
||||
url: String,
|
||||
referer: String?,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
) {
|
||||
val res = app.get(url, referer = referer).document
|
||||
val title = res.select("title").text()
|
||||
val video = res.select("video#player source").attr("src")
|
||||
|
||||
callback.invoke(
|
||||
ExtractorLink(
|
||||
this.name,
|
||||
this.name,
|
||||
video,
|
||||
"$mainUrl/",
|
||||
getIndexQuality(title),
|
||||
INFER_TYPE
|
||||
)
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class FilemoonNl : Ridoo() {
|
||||
override val name = "FilemoonNl"
|
||||
override var mainUrl = "https://filemoon.nl"
|
||||
override val defaulQuality = Qualities.Unknown.value
|
||||
}
|
||||
|
||||
class Alions : Ridoo() {
|
||||
override val name = "Alions"
|
||||
override var mainUrl = "https://alions.pro"
|
||||
override val defaulQuality = Qualities.Unknown.value
|
||||
}
|
||||
|
||||
class Streamwish : Filesim() {
|
||||
|
@ -357,9 +468,9 @@ class Streamwish : Filesim() {
|
|||
override var mainUrl = "https://streamwish.to"
|
||||
}
|
||||
|
||||
class Wishfast : Filesim() {
|
||||
override val name = "Wishfast"
|
||||
override var mainUrl = "https://wishfast.top"
|
||||
class UqloadsXyz : Filesim() {
|
||||
override val name = "Uqloads"
|
||||
override var mainUrl = "https://uqloads.xyz"
|
||||
}
|
||||
|
||||
class FilelionsTo : Filesim() {
|
||||
|
@ -367,7 +478,7 @@ class FilelionsTo : Filesim() {
|
|||
override var mainUrl = "https://filelions.to"
|
||||
}
|
||||
|
||||
class Pixeldra : Pixeldrain() {
|
||||
class Pixeldra : PixelDrain() {
|
||||
override val mainUrl = "https://pixeldra.in"
|
||||
}
|
||||
|
||||
|
@ -386,7 +497,7 @@ class Animefever : Filesim() {
|
|||
override var mainUrl = "https://animefever.fun"
|
||||
}
|
||||
|
||||
class Multimovies : Filesim() {
|
||||
class Multimovies : Ridoo() {
|
||||
override val name = "Multimovies"
|
||||
override var mainUrl = "https://multimovies.cloud"
|
||||
}
|
||||
|
@ -405,12 +516,13 @@ class Embedwish : Filesim() {
|
|||
override val name = "Embedwish"
|
||||
override var mainUrl = "https://embedwish.com"
|
||||
}
|
||||
|
||||
class Vidplay2 : Vidplay() {
|
||||
override val mainUrl = "https://vidplay.online"
|
||||
}
|
||||
|
||||
class Flaswish : Filesim() {
|
||||
class Flaswish : Ridoo() {
|
||||
override val name = "Flaswish"
|
||||
override var mainUrl = "https://flaswish.com"
|
||||
override val defaulQuality = Qualities.Unknown.value
|
||||
}
|
||||
|
||||
class Comedyshow : Jeniusplay() {
|
||||
override val mainUrl = "https://comedyshow.to"
|
||||
override val name = "Comedyshow"
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,17 @@
|
|||
package com.hexated
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Document
|
||||
|
||||
data class CrunchyrollAccessToken(
|
||||
val accessToken: String? = null,
|
||||
val tokenType: String? = null,
|
||||
val bucket: String? = null,
|
||||
val policy: String? = null,
|
||||
val signature: String? = null,
|
||||
val key_pair_id: String? = null,
|
||||
)
|
||||
|
||||
data class FDMovieIFrame(
|
||||
val link: String,
|
||||
|
@ -9,32 +20,60 @@ data class FDMovieIFrame(
|
|||
val type: String,
|
||||
)
|
||||
|
||||
data class AniIds(
|
||||
var id: Int? = null,
|
||||
var idMal: Int? = null
|
||||
)
|
||||
data class AniIds(var id: Int? = null, var idMal: Int? = null)
|
||||
|
||||
data class TmdbDate(
|
||||
val today: String,
|
||||
val nextWeek: String,
|
||||
)
|
||||
|
||||
data class AniwaveResponse(
|
||||
val result: String
|
||||
) {
|
||||
fun asJsoup(): Document {
|
||||
return Jsoup.parse(result)
|
||||
}
|
||||
}
|
||||
|
||||
data class AniwaveServer(
|
||||
val result: Result
|
||||
) {
|
||||
data class Result(
|
||||
val url: String
|
||||
) {
|
||||
fun decrypt(): String {
|
||||
return AniwaveUtils.decodeVrf(url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class MoflixResponse(
|
||||
@JsonProperty("title") val title: Episode? = null,
|
||||
@JsonProperty("episode") val episode: Episode? = null,
|
||||
) {
|
||||
data class Episode(
|
||||
@JsonProperty("id") val id: Int? = null,
|
||||
@JsonProperty("videos") val videos: ArrayList<Videos>? = arrayListOf(),
|
||||
) {
|
||||
data class Videos(
|
||||
@JsonProperty("name") val name: String? = null,
|
||||
@JsonProperty("category") val category: String? = null,
|
||||
@JsonProperty("src") val src: String? = null,
|
||||
@JsonProperty("quality") val quality: String? = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class AniMedia(
|
||||
@JsonProperty("id") var id: Int? = null,
|
||||
@JsonProperty("idMal") var idMal: Int? = null
|
||||
)
|
||||
|
||||
data class AniPage(
|
||||
@JsonProperty("media") var media: java.util.ArrayList<AniMedia> = arrayListOf()
|
||||
)
|
||||
data class AniPage(@JsonProperty("media") var media: java.util.ArrayList<AniMedia> = arrayListOf())
|
||||
|
||||
data class AniData(
|
||||
@JsonProperty("Page") var Page: AniPage? = AniPage()
|
||||
)
|
||||
data class AniData(@JsonProperty("Page") var Page: AniPage? = AniPage())
|
||||
|
||||
data class AniSearch(
|
||||
@JsonProperty("data") var data: AniData? = AniData()
|
||||
)
|
||||
data class AniSearch(@JsonProperty("data") var data: AniData? = AniData())
|
||||
|
||||
data class GpressSources(
|
||||
@JsonProperty("src") val src: String,
|
||||
|
@ -152,16 +191,6 @@ data class JikanResponse(
|
|||
@JsonProperty("data") val data: JikanData? = null,
|
||||
)
|
||||
|
||||
data class CinemaTvSubtitles(
|
||||
@JsonProperty("language") val language: String? = null,
|
||||
@JsonProperty("file") val file: Any? = null,
|
||||
)
|
||||
|
||||
data class CinemaTvResponse(
|
||||
@JsonProperty("streams") val streams: HashMap<String, String>? = null,
|
||||
@JsonProperty("subtitles") val subtitles: ArrayList<CinemaTvSubtitles>? = arrayListOf(),
|
||||
)
|
||||
|
||||
data class VidsrctoResult(
|
||||
@JsonProperty("id") val id: String? = null,
|
||||
@JsonProperty("title") val title: String? = null,
|
||||
|
@ -188,25 +217,24 @@ data class AnilistExternalLinks(
|
|||
@JsonProperty("type") var type: String? = null,
|
||||
)
|
||||
|
||||
data class AnilistMedia(
|
||||
@JsonProperty("externalLinks") var externalLinks: ArrayList<AnilistExternalLinks> = arrayListOf()
|
||||
)
|
||||
data class AnilistMedia(@JsonProperty("externalLinks") var externalLinks: ArrayList<AnilistExternalLinks> = arrayListOf())
|
||||
|
||||
data class AnilistData(
|
||||
@JsonProperty("Media") var Media: AnilistMedia? = AnilistMedia()
|
||||
)
|
||||
data class AnilistData(@JsonProperty("Media") var Media: AnilistMedia? = AnilistMedia())
|
||||
|
||||
data class AnilistResponses(
|
||||
@JsonProperty("data") var data: AnilistData? = AnilistData()
|
||||
)
|
||||
data class AnilistResponses(@JsonProperty("data") var data: AnilistData? = AnilistData())
|
||||
|
||||
data class CrunchyrollToken(
|
||||
@JsonProperty("access_token") val accessToken: String? = null,
|
||||
@JsonProperty("expires_in") val expiresIn: Int? = null,
|
||||
@JsonProperty("token_type") val tokenType: String? = null,
|
||||
@JsonProperty("scope") val scope: String? = null,
|
||||
@JsonProperty("country") val country: String? = null
|
||||
)
|
||||
@JsonProperty("cms") val cms: Cms? = null,
|
||||
) {
|
||||
data class Cms(
|
||||
@JsonProperty("bucket") var bucket: String? = null,
|
||||
@JsonProperty("policy") var policy: String? = null,
|
||||
@JsonProperty("signature") var signature: String? = null,
|
||||
@JsonProperty("key_pair_id") var key_pair_id: String? = null,
|
||||
)
|
||||
}
|
||||
|
||||
data class CrunchyrollVersions(
|
||||
@JsonProperty("audio_locale") val audio_locale: String? = null,
|
||||
|
@ -221,25 +249,25 @@ data class CrunchyrollData(
|
|||
@JsonProperty("episode_number") val episode_number: Int? = null,
|
||||
@JsonProperty("versions") val versions: ArrayList<CrunchyrollVersions>? = null,
|
||||
@JsonProperty("streams_link") val streams_link: String? = null,
|
||||
@JsonProperty("adaptive_hls") val adaptive_hls: HashMap<String, HashMap<String, String>>? = hashMapOf(),
|
||||
@JsonProperty("vo_adaptive_hls") val vo_adaptive_hls: HashMap<String, HashMap<String, String>>? = hashMapOf(),
|
||||
)
|
||||
|
||||
data class CrunchyrollResponses(
|
||||
@JsonProperty("data") val data: ArrayList<CrunchyrollData>? = arrayListOf(),
|
||||
)
|
||||
|
||||
data class CrunchyrollMeta(
|
||||
@JsonProperty("subtitles") val subtitles: HashMap<String, HashMap<String, String>>? = hashMapOf(),
|
||||
)
|
||||
|
||||
data class CrunchyrollSourcesResponses(
|
||||
@JsonProperty("data") val data: ArrayList<CrunchyrollData>? = arrayListOf(),
|
||||
@JsonProperty("meta") val meta: CrunchyrollMeta? = null,
|
||||
)
|
||||
@JsonProperty("streams") val streams: Streams? = Streams(),
|
||||
@JsonProperty("subtitles") val subtitles: HashMap<String, HashMap<String, String>>? = hashMapOf(),
|
||||
) {
|
||||
data class Streams(
|
||||
@JsonProperty("adaptive_hls") val adaptive_hls: HashMap<String, HashMap<String, String>>? = hashMapOf(),
|
||||
@JsonProperty("vo_adaptive_hls") val vo_adaptive_hls: HashMap<String, HashMap<String, String>>? = hashMapOf(),
|
||||
)
|
||||
}
|
||||
|
||||
data class MALSyncSites(
|
||||
@JsonProperty("Zoro") val zoro: HashMap<String?, HashMap<String, String?>>? = hashMapOf(),
|
||||
@JsonProperty("9anime") val nineAnime: HashMap<String?, HashMap<String, String?>>? = hashMapOf(),
|
||||
)
|
||||
|
||||
data class MALSyncResponses(
|
||||
|
@ -263,26 +291,26 @@ data class GokuServer(
|
|||
@JsonProperty("data") val data: GokuData? = GokuData(),
|
||||
)
|
||||
|
||||
data class NavyEpisodeFolder(
|
||||
data class AllMovielandEpisodeFolder(
|
||||
@JsonProperty("title") val title: String? = null,
|
||||
@JsonProperty("id") val id: String? = null,
|
||||
@JsonProperty("file") val file: String? = null,
|
||||
)
|
||||
|
||||
data class NavySeasonFolder(
|
||||
data class AllMovielandSeasonFolder(
|
||||
@JsonProperty("episode") val episode: String? = null,
|
||||
@JsonProperty("id") val id: String? = null,
|
||||
@JsonProperty("folder") val folder: ArrayList<NavyEpisodeFolder>? = arrayListOf(),
|
||||
@JsonProperty("folder") val folder: ArrayList<AllMovielandEpisodeFolder>? = arrayListOf(),
|
||||
)
|
||||
|
||||
data class NavyServer(
|
||||
data class AllMovielandServer(
|
||||
@JsonProperty("title") val title: String? = null,
|
||||
@JsonProperty("id") val id: String? = null,
|
||||
@JsonProperty("file") val file: String? = null,
|
||||
@JsonProperty("folder") val folder: ArrayList<NavySeasonFolder>? = arrayListOf(),
|
||||
@JsonProperty("folder") val folder: ArrayList<AllMovielandSeasonFolder>? = arrayListOf(),
|
||||
)
|
||||
|
||||
data class NavyPlaylist(
|
||||
data class AllMovielandPlaylist(
|
||||
@JsonProperty("file") val file: String? = null,
|
||||
@JsonProperty("key") val key: String? = null,
|
||||
@JsonProperty("href") val href: String? = null,
|
||||
|
@ -334,26 +362,6 @@ data class EMovieTraks(
|
|||
@JsonProperty("label") val label: String? = null,
|
||||
)
|
||||
|
||||
data class BlackvidSubtitles(
|
||||
@JsonProperty("language") val language: String? = null,
|
||||
@JsonProperty("url") val url: String? = null,
|
||||
)
|
||||
|
||||
data class BlackvidSource(
|
||||
@JsonProperty("quality") var quality: String? = null,
|
||||
@JsonProperty("url") var url: String? = null,
|
||||
)
|
||||
|
||||
data class BlackvidSources(
|
||||
@JsonProperty("label") var label: String? = null,
|
||||
@JsonProperty("sources") var sources: ArrayList<BlackvidSource> = arrayListOf()
|
||||
)
|
||||
|
||||
data class BlackvidResponses(
|
||||
@JsonProperty("sources") var sources: ArrayList<BlackvidSources> = arrayListOf(),
|
||||
@JsonProperty("subtitles") var subtitles: ArrayList<BlackvidSubtitles> = arrayListOf()
|
||||
)
|
||||
|
||||
data class ShowflixResultsMovies(
|
||||
@JsonProperty("movieName") val movieName: String? = null,
|
||||
@JsonProperty("streamwish") val streamwish: String? = null,
|
||||
|
@ -426,15 +434,6 @@ data class SmashySources(
|
|||
@JsonProperty("subtitleUrls") var subtitleUrls: String? = null,
|
||||
)
|
||||
|
||||
data class SmashyDSources(
|
||||
@JsonProperty("sourceUrls") var sourceUrls: ArrayList<SmashyDSourcesUrls>? = arrayListOf(),
|
||||
)
|
||||
|
||||
data class SmashyDSourcesUrls(
|
||||
@JsonProperty("file") var file: String? = null,
|
||||
@JsonProperty("title") var title: String? = null,
|
||||
)
|
||||
|
||||
data class AoneroomResponse(
|
||||
@JsonProperty("data") val data: Data? = null,
|
||||
) {
|
||||
|
@ -461,4 +460,24 @@ data class AoneroomResponse(
|
|||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class CinemaTvResponse(
|
||||
@JsonProperty("streams") val streams: HashMap<String, String>? = null,
|
||||
@JsonProperty("subtitles") val subtitles: ArrayList<Subtitles>? = arrayListOf(),
|
||||
) {
|
||||
data class Subtitles(
|
||||
@JsonProperty("language") val language: String? = null,
|
||||
@JsonProperty("file") val file: Any? = null,
|
||||
)
|
||||
}
|
||||
|
||||
data class NepuSearch(
|
||||
@JsonProperty("data") val data: ArrayList<Data>? = arrayListOf(),
|
||||
) {
|
||||
data class Data(
|
||||
@JsonProperty("url") val url: String? = null,
|
||||
@JsonProperty("name") val name: String? = null,
|
||||
@JsonProperty("type") val type: String? = null,
|
||||
)
|
||||
}
|
|
@ -2,11 +2,9 @@ package com.hexated
|
|||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.hexated.SoraExtractor.invoke2embed
|
||||
import com.hexated.SoraExtractor.invokeAllMovieland
|
||||
import com.hexated.SoraExtractor.invokeAnimes
|
||||
import com.hexated.SoraExtractor.invokeAoneroom
|
||||
import com.hexated.SoraExtractor.invokeBlackvid
|
||||
import com.hexated.SoraExtractor.invokeBollyMaza
|
||||
import com.hexated.SoraExtractor.invokeDbgo
|
||||
import com.hexated.SoraExtractor.invokeFilmxy
|
||||
import com.hexated.SoraExtractor.invokeKimcartoon
|
||||
import com.hexated.SoraExtractor.invokeVidSrc
|
||||
|
@ -20,13 +18,10 @@ import com.hexated.SoraExtractor.invokeDramaday
|
|||
import com.hexated.SoraExtractor.invokeDreamfilm
|
||||
import com.hexated.SoraExtractor.invokeFDMovies
|
||||
import com.hexated.SoraExtractor.invokeFlixon
|
||||
import com.hexated.SoraExtractor.invokeGMovies
|
||||
import com.hexated.SoraExtractor.invokeGoku
|
||||
import com.hexated.SoraExtractor.invokeKisskh
|
||||
import com.hexated.SoraExtractor.invokeLing
|
||||
import com.hexated.SoraExtractor.invokeM4uhd
|
||||
import com.hexated.SoraExtractor.invokeMoviezAdd
|
||||
import com.hexated.SoraExtractor.invokeNavy
|
||||
import com.hexated.SoraExtractor.invokeNinetv
|
||||
import com.hexated.SoraExtractor.invokeNowTv
|
||||
import com.hexated.SoraExtractor.invokeRStream
|
||||
|
@ -35,21 +30,24 @@ import com.hexated.SoraExtractor.invokeSmashyStream
|
|||
import com.hexated.SoraExtractor.invokeDumpStream
|
||||
import com.hexated.SoraExtractor.invokeEmovies
|
||||
import com.hexated.SoraExtractor.invokeHdmovies4u
|
||||
import com.hexated.SoraExtractor.invokeMoment
|
||||
import com.hexated.SoraExtractor.invokeMultimovies
|
||||
import com.hexated.SoraExtractor.invokeNetmovies
|
||||
import com.hexated.SoraExtractor.invokeSFMovies
|
||||
import com.hexated.SoraExtractor.invokeShowflix
|
||||
import com.hexated.SoraExtractor.invokeTvMovies
|
||||
import com.hexated.SoraExtractor.invokeUhdmovies
|
||||
import com.hexated.SoraExtractor.invokeVegamovies
|
||||
import com.hexated.SoraExtractor.invokeVidsrcto
|
||||
import com.hexated.SoraExtractor.invokeCinemaTv
|
||||
import com.hexated.SoraExtractor.invokeOmovies
|
||||
import com.hexated.SoraExtractor.invokeMoflix
|
||||
import com.hexated.SoraExtractor.invokeGhostx
|
||||
import com.hexated.SoraExtractor.invokeNepu
|
||||
import com.hexated.SoraExtractor.invokeWatchCartoon
|
||||
import com.hexated.SoraExtractor.invokeWatchsomuch
|
||||
import com.hexated.SoraExtractor.invokeZoechip
|
||||
import com.hexated.SoraExtractor.invokeZshow
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addImdbId
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addTMDbId
|
||||
import com.lagradost.cloudstream3.network.CloudflareKiller
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.toJson
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
|
@ -67,6 +65,8 @@ open class SoraStream : TmdbProvider() {
|
|||
TvType.Anime,
|
||||
)
|
||||
|
||||
val wpRedisInterceptor by lazy { CloudflareKiller() }
|
||||
|
||||
/** AUTHOR : Hexated & Sora */
|
||||
companion object {
|
||||
/** TOOLS */
|
||||
|
@ -81,16 +81,16 @@ open class SoraStream : TmdbProvider() {
|
|||
/** ALL SOURCES */
|
||||
const val twoEmbedAPI = "https://www.2embed.cc"
|
||||
const val vidSrcAPI = "https://vidsrc.me"
|
||||
const val dbgoAPI = "https://dbgo.fun"
|
||||
const val dreamfilmAPI = "https://dreamfilmsw.net"
|
||||
const val noverseAPI = "https://www.nollyverse.com"
|
||||
const val filmxyAPI = "https://www.filmxy.vip"
|
||||
const val kimcartoonAPI = "https://kimcartoon.li"
|
||||
const val aniwatchAPI = "https://aniwatch.to"
|
||||
const val aniwaveAPI = "https://aniwave.to"
|
||||
const val crunchyrollAPI = "https://beta-api.crunchyroll.com"
|
||||
const val kissKhAPI = "https://kisskh.co"
|
||||
const val lingAPI = "https://ling-online.net"
|
||||
const val m4uhdAPI = "https://ww2.m4ufree.com"
|
||||
const val m4uhdAPI = "https://m4umv.org"
|
||||
const val rStreamAPI = "https://remotestream.cc"
|
||||
const val flixonAPI = "https://flixon.lol"
|
||||
const val smashyStreamAPI = "https://embed.smashystream.com"
|
||||
|
@ -100,31 +100,30 @@ open class SoraStream : TmdbProvider() {
|
|||
const val nowTvAPI = "https://myfilestorage.xyz"
|
||||
const val gokuAPI = "https://goku.sx"
|
||||
const val zshowAPI = BuildConfig.ZSHOW_API
|
||||
const val ridomoviesAPI = "https://ridomovies.pw"
|
||||
const val navyAPI = "https://navy-issue-i-239.site"
|
||||
const val ridomoviesAPI = "https://ridomovies.tv"
|
||||
const val emoviesAPI = "https://emovies.si"
|
||||
const val multimoviesAPI = "https://multimovies.top"
|
||||
const val multimovies2API = "https://multimovies.click"
|
||||
const val netmoviesAPI = "https://netmovies.to"
|
||||
const val momentAPI = "https://izzillent-dickstonyx-i-262.site"
|
||||
const val allmovielandAPI = "https://allmovieland.fun"
|
||||
const val doomoviesAPI = "https://doomovies.net"
|
||||
const val vidsrctoAPI = "https://vidsrc.to"
|
||||
const val dramadayAPI = "https://dramaday.me"
|
||||
const val animetoshoAPI = "https://animetosho.org"
|
||||
const val watchflxAPI = "https://watchflx.tv"
|
||||
const val blackvidAPI = "https://prod.api.blackvid.space"
|
||||
const val showflixAPI = "https://showflix.space"
|
||||
const val showflixAPI = "https://showflix.lol"
|
||||
const val aoneroomAPI = "https://api3.aoneroom.com"
|
||||
|
||||
const val fdMoviesAPI = "https://freedrivemovie.lol"
|
||||
const val uhdmoviesAPI = "https://uhdmovies.zip"
|
||||
const val gMoviesAPI = "https://gdrivemovies.xyz"
|
||||
const val hdmovies4uAPI = "https://hdmovies4u.band"
|
||||
const val vegaMoviesAPI = "https://vegamovies.dad"
|
||||
const val dotmoviesAPI = "https://dotmovies.bet"
|
||||
const val mMoviesAPI = "https://multimovies.uno"
|
||||
const val watchCartoonAPI = "https://www1.watchcartoononline.bz"
|
||||
const val moflixAPI = "https://moflix-stream.xyz"
|
||||
const val moviefictionAPI = "https://moviefiction.com"
|
||||
const val zoechipAPI = "https://zoechip.org"
|
||||
const val nepuAPI = "https://nepu.to"
|
||||
const val fdMoviesAPI = "https://freedrivemovie.com"
|
||||
const val uhdmoviesAPI = "https://uhdmovies.asia"
|
||||
const val hdmovies4uAPI = "https://hdmovies4u.day"
|
||||
const val vegaMoviesAPI = "https://vegamovies.ong"
|
||||
const val dotmoviesAPI = "https://dotmovies.one"
|
||||
const val tvMoviesAPI = "https://www.tvseriesnmovies.com"
|
||||
const val moviezAddAPI = "https://ww3.moviezaddiction.click"
|
||||
const val bollyMazaAPI = "https://ww3.bollymaza.click"
|
||||
const val dahmerMoviesAPI = "https://odd-bird-1319.zwuhygoaqe.workers.dev"
|
||||
|
||||
fun getType(t: String?): TvType {
|
||||
|
@ -176,16 +175,12 @@ open class SoraStream : TmdbProvider() {
|
|||
return if (link.startsWith("/")) "https://image.tmdb.org/t/p/original/$link" else link
|
||||
}
|
||||
|
||||
override suspend fun getMainPage(
|
||||
page: Int,
|
||||
request: MainPageRequest
|
||||
): HomePageResponse {
|
||||
override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse {
|
||||
val adultQuery =
|
||||
if (settingsForProvider.enableAdult) "" else "&without_keywords=190370|13059|226161|195669"
|
||||
val type = if (request.data.contains("/movie")) "movie" else "tv"
|
||||
val home = app.get("${request.data}$adultQuery&page=$page")
|
||||
.parsedSafe<Results>()?.results
|
||||
?.mapNotNull { media ->
|
||||
.parsedSafe<Results>()?.results?.mapNotNull { media ->
|
||||
media.toSearchResponse(type)
|
||||
} ?: throw ErrorLoadingException("Invalid Json reponse")
|
||||
return newHomePageResponse(request.name, home)
|
||||
|
@ -204,11 +199,10 @@ open class SoraStream : TmdbProvider() {
|
|||
override suspend fun quickSearch(query: String): List<SearchResponse>? = search(query)
|
||||
|
||||
override suspend fun search(query: String): List<SearchResponse>? {
|
||||
return app.get(
|
||||
"$tmdbAPI/search/multi?api_key=$apiKey&language=en-US&query=$query&page=1&include_adult=${settingsForProvider.enableAdult}"
|
||||
).parsedSafe<Results>()?.results?.mapNotNull { media ->
|
||||
media.toSearchResponse()
|
||||
}
|
||||
return app.get("$tmdbAPI/search/multi?api_key=$apiKey&language=en-US&query=$query&page=1&include_adult=${settingsForProvider.enableAdult}")
|
||||
.parsedSafe<Results>()?.results?.mapNotNull { media ->
|
||||
media.toSearchResponse()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun load(url: String): LoadResponse? {
|
||||
|
@ -243,17 +237,15 @@ open class SoraStream : TmdbProvider() {
|
|||
val actors = res.credits?.cast?.mapNotNull { cast ->
|
||||
ActorData(
|
||||
Actor(
|
||||
cast.name ?: cast.originalName ?: return@mapNotNull null,
|
||||
getImageUrl(cast.profilePath)
|
||||
),
|
||||
roleString = cast.character
|
||||
cast.name ?: cast.originalName
|
||||
?: return@mapNotNull null, getImageUrl(cast.profilePath)
|
||||
), roleString = cast.character
|
||||
)
|
||||
} ?: return null
|
||||
val recommendations =
|
||||
res.recommendations?.results?.mapNotNull { media -> media.toSearchResponse() }
|
||||
|
||||
val trailer = res.videos?.results?.map { "https://www.youtube.com/watch?v=${it.key}" }
|
||||
?.randomOrNull()
|
||||
|
||||
return if (type == TvType.TvSeries) {
|
||||
val lastSeason = res.last_episode_to_air?.season_number
|
||||
|
@ -277,12 +269,13 @@ 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,
|
||||
isAsian = isAsian,
|
||||
isBollywood = isBollywood,
|
||||
isCartoon = isCartoon
|
||||
).toJson(),
|
||||
name = eps.name + if (isUpcoming(eps.airDate)) " - [UPCOMING]" else "",
|
||||
name = eps.name + if (isUpcoming(eps.airDate)) " • [UPCOMING]" else "",
|
||||
season = eps.seasonNumber,
|
||||
episode = eps.episodeNumber,
|
||||
posterUrl = getImageUrl(eps.stillPath),
|
||||
|
@ -303,7 +296,7 @@ open class SoraStream : TmdbProvider() {
|
|||
this.backgroundPosterUrl = bgPoster
|
||||
this.year = year
|
||||
this.plot = res.overview
|
||||
this.tags = if (isAnime) keywords else genres
|
||||
this.tags = keywords.takeIf { !it.isNullOrEmpty() } ?: genres
|
||||
this.rating = rating
|
||||
this.showStatus = getStatus(res.status)
|
||||
this.recommendations = recommendations
|
||||
|
@ -328,7 +321,8 @@ 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,
|
||||
isAsian = isAsian,
|
||||
isBollywood = isBollywood
|
||||
).toJson(),
|
||||
|
@ -339,7 +333,7 @@ open class SoraStream : TmdbProvider() {
|
|||
this.year = year
|
||||
this.plot = res.overview
|
||||
this.duration = res.runtime
|
||||
this.tags = if (isAnime) keywords else genres
|
||||
this.tags = keywords.takeIf { !it.isNullOrEmpty() } ?: genres
|
||||
this.rating = rating
|
||||
this.recommendations = recommendations
|
||||
this.actors = actors
|
||||
|
@ -361,15 +355,6 @@ open class SoraStream : TmdbProvider() {
|
|||
val res = parseJson<LinkData>(data)
|
||||
|
||||
argamap(
|
||||
{
|
||||
if (!res.isAnime) invokeBlackvid(
|
||||
res.id,
|
||||
res.season,
|
||||
res.episode,
|
||||
subtitleCallback,
|
||||
callback
|
||||
)
|
||||
},
|
||||
{
|
||||
invokeDumpStream(
|
||||
res.title,
|
||||
|
@ -392,19 +377,12 @@ open class SoraStream : TmdbProvider() {
|
|||
)
|
||||
},
|
||||
{
|
||||
invokeVidSrc(res.id, res.season, res.episode, subtitleCallback, callback)
|
||||
},
|
||||
{
|
||||
invokeDbgo(res.imdbId, res.season, res.episode, subtitleCallback, callback)
|
||||
invokeVidSrc(res.id, res.season, res.episode, callback)
|
||||
},
|
||||
{
|
||||
if (!res.isAnime) invokeAoneroom(
|
||||
res.title,
|
||||
res.airedYear ?: res.year,
|
||||
res.season,
|
||||
res.episode,
|
||||
subtitleCallback,
|
||||
callback
|
||||
res.title, res.airedYear
|
||||
?: res.year, res.season, res.episode, subtitleCallback, callback
|
||||
)
|
||||
},
|
||||
{
|
||||
|
@ -449,6 +427,16 @@ open class SoraStream : TmdbProvider() {
|
|||
callback
|
||||
)
|
||||
},
|
||||
{
|
||||
if (!res.isAnime && res.isCartoon) invokeWatchCartoon(
|
||||
res.title,
|
||||
res.year,
|
||||
res.season,
|
||||
res.episode,
|
||||
subtitleCallback,
|
||||
callback
|
||||
)
|
||||
},
|
||||
{
|
||||
if (!res.isAnime) invokeVidsrcto(
|
||||
res.imdbId,
|
||||
|
@ -470,7 +458,7 @@ open class SoraStream : TmdbProvider() {
|
|||
)
|
||||
},
|
||||
{
|
||||
if (!res.isAnime) invokeOmovies (
|
||||
if (!res.isAnime) invokeGhostx(
|
||||
res.title,
|
||||
res.year,
|
||||
res.season,
|
||||
|
@ -480,12 +468,8 @@ open class SoraStream : TmdbProvider() {
|
|||
},
|
||||
{
|
||||
if (!res.isAnime) invokeLing(
|
||||
res.title,
|
||||
res.airedYear ?: res.year,
|
||||
res.season,
|
||||
res.episode,
|
||||
subtitleCallback,
|
||||
callback
|
||||
res.title, res.airedYear
|
||||
?: res.year, res.season, res.episode, subtitleCallback, callback
|
||||
)
|
||||
},
|
||||
{
|
||||
|
@ -498,58 +482,17 @@ open class SoraStream : TmdbProvider() {
|
|||
)
|
||||
},
|
||||
{
|
||||
if (!res.isAnime) invokeGMovies(
|
||||
res.title,
|
||||
res.year,
|
||||
res.season,
|
||||
res.episode,
|
||||
subtitleCallback,
|
||||
callback
|
||||
)
|
||||
},
|
||||
{
|
||||
if (!res.isAnime) invokeFDMovies(
|
||||
res.title,
|
||||
res.season,
|
||||
res.episode,
|
||||
callback
|
||||
)
|
||||
if (!res.isAnime) invokeFDMovies(res.title, res.season, res.episode, callback)
|
||||
},
|
||||
{
|
||||
if (!res.isAnime) invokeM4uhd(
|
||||
res.title,
|
||||
res.airedYear ?: res.year,
|
||||
res.season,
|
||||
res.episode,
|
||||
subtitleCallback,
|
||||
callback
|
||||
res.title, res.airedYear
|
||||
?: res.year, res.season, res.episode, subtitleCallback, callback
|
||||
)
|
||||
},
|
||||
{
|
||||
if (!res.isAnime) invokeTvMovies(res.title, res.season, res.episode, callback)
|
||||
},
|
||||
{
|
||||
if (!res.isAnime) invokeMoviezAdd(
|
||||
moviezAddAPI,
|
||||
"MoviezAdd",
|
||||
res.title,
|
||||
res.year,
|
||||
res.season,
|
||||
res.episode,
|
||||
callback
|
||||
)
|
||||
},
|
||||
{
|
||||
if (!res.isAnime && res.isBollywood) invokeBollyMaza(
|
||||
bollyMazaAPI,
|
||||
"BollyMaza",
|
||||
res.title,
|
||||
res.year,
|
||||
res.season,
|
||||
res.episode,
|
||||
callback
|
||||
)
|
||||
},
|
||||
{
|
||||
if (!res.isAnime) invokeRStream(res.id, res.season, res.episode, callback)
|
||||
},
|
||||
|
@ -564,7 +507,7 @@ open class SoraStream : TmdbProvider() {
|
|||
},
|
||||
{
|
||||
if (!res.isAnime) invokeSmashyStream(
|
||||
res.imdbId,
|
||||
res.id,
|
||||
res.season,
|
||||
res.episode,
|
||||
subtitleCallback,
|
||||
|
@ -589,19 +532,21 @@ open class SoraStream : TmdbProvider() {
|
|||
)
|
||||
},
|
||||
{
|
||||
invokeDahmerMovies(
|
||||
res.title,
|
||||
res.year,
|
||||
res.season,
|
||||
res.episode,
|
||||
callback
|
||||
)
|
||||
invokeDahmerMovies(res.title, res.year, res.season, res.episode, callback)
|
||||
},
|
||||
{
|
||||
invokeCinemaTv(
|
||||
res.imdbId, res.title, res.airedYear
|
||||
?: res.year, res.season, res.episode, subtitleCallback, callback
|
||||
)
|
||||
},
|
||||
{
|
||||
if (!res.isAnime) invokeNowTv(res.id, res.imdbId, res.season, res.episode, callback)
|
||||
},
|
||||
{
|
||||
if (!res.isAnime) invokeRidomovies(
|
||||
res.id,
|
||||
res.imdbId,
|
||||
res.title,
|
||||
res.airedYear ?: res.year,
|
||||
res.season,
|
||||
res.episode,
|
||||
subtitleCallback,
|
||||
|
@ -609,20 +554,7 @@ open class SoraStream : TmdbProvider() {
|
|||
)
|
||||
},
|
||||
{
|
||||
if (!res.isAnime) invokeNowTv(res.id, res.imdbId, res.season, res.episode, callback)
|
||||
},
|
||||
{
|
||||
if (!res.isAnime && res.season == null) invokeRidomovies(
|
||||
res.id,
|
||||
res.imdbId,
|
||||
callback
|
||||
)
|
||||
},
|
||||
{
|
||||
invokeNavy(res.imdbId, res.season, res.episode, callback)
|
||||
},
|
||||
{
|
||||
invokeMoment(res.imdbId, res.season, res.episode, callback)
|
||||
if (!res.isAnime) invokeAllMovieland(res.imdbId, res.season, res.episode, callback)
|
||||
},
|
||||
{
|
||||
if (!res.isAnime) invokeEmovies(
|
||||
|
@ -657,10 +589,24 @@ open class SoraStream : TmdbProvider() {
|
|||
)
|
||||
},
|
||||
{
|
||||
if(res.isBollywood) invokeMultimovies(multimoviesAPI, res.title, res.season, res.episode, subtitleCallback, callback)
|
||||
if (res.isBollywood) invokeMultimovies(
|
||||
multimoviesAPI,
|
||||
res.title,
|
||||
res.season,
|
||||
res.episode,
|
||||
subtitleCallback,
|
||||
callback
|
||||
)
|
||||
},
|
||||
{
|
||||
if(res.isBollywood) invokeMultimovies(multimovies2API, res.title, res.season, res.episode, subtitleCallback, callback)
|
||||
if (res.isBollywood) invokeMultimovies(
|
||||
multimovies2API,
|
||||
res.title,
|
||||
res.season,
|
||||
res.episode,
|
||||
subtitleCallback,
|
||||
callback
|
||||
)
|
||||
},
|
||||
{
|
||||
invokeNetmovies(
|
||||
|
@ -690,7 +636,13 @@ open class SoraStream : TmdbProvider() {
|
|||
)
|
||||
},
|
||||
{
|
||||
if (!res.isAnime) invoke2embed(res.imdbId, res.season, res.episode, subtitleCallback, callback)
|
||||
if (!res.isAnime) invoke2embed(
|
||||
res.imdbId,
|
||||
res.season,
|
||||
res.episode,
|
||||
subtitleCallback,
|
||||
callback
|
||||
)
|
||||
},
|
||||
{
|
||||
if (!res.isAnime) invokeHdmovies4u(
|
||||
|
@ -723,8 +675,13 @@ open class SoraStream : TmdbProvider() {
|
|||
)
|
||||
},
|
||||
{
|
||||
if (!res.isAnime) invokeSFMovies(
|
||||
res.id,
|
||||
if (!res.isAnime) invokeMoflix(res.id, res.season, res.episode, callback)
|
||||
},
|
||||
{
|
||||
if (!res.isAnime) invokeZoechip(res.title, res.year, res.season, res.episode, callback)
|
||||
},
|
||||
{
|
||||
if (!res.isAnime) invokeNepu(
|
||||
res.title,
|
||||
res.airedYear ?: res.year,
|
||||
res.season,
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
package com.hexated
|
||||
|
||||
import com.hexated.SoraExtractor.invoke2embed
|
||||
import com.hexated.SoraExtractor.invokeAllMovieland
|
||||
import com.hexated.SoraExtractor.invokeAnimes
|
||||
import com.hexated.SoraExtractor.invokeAoneroom
|
||||
import com.hexated.SoraExtractor.invokeBlackvid
|
||||
import com.hexated.SoraExtractor.invokeDbgo
|
||||
import com.hexated.SoraExtractor.invokeDoomovies
|
||||
import com.hexated.SoraExtractor.invokeDramaday
|
||||
import com.hexated.SoraExtractor.invokeDreamfilm
|
||||
|
@ -15,7 +14,6 @@ import com.hexated.SoraExtractor.invokeKimcartoon
|
|||
import com.hexated.SoraExtractor.invokeKisskh
|
||||
import com.hexated.SoraExtractor.invokeLing
|
||||
import com.hexated.SoraExtractor.invokeM4uhd
|
||||
import com.hexated.SoraExtractor.invokeNavy
|
||||
import com.hexated.SoraExtractor.invokeNinetv
|
||||
import com.hexated.SoraExtractor.invokeNowTv
|
||||
import com.hexated.SoraExtractor.invokeRStream
|
||||
|
@ -23,16 +21,19 @@ import com.hexated.SoraExtractor.invokeRidomovies
|
|||
import com.hexated.SoraExtractor.invokeSmashyStream
|
||||
import com.hexated.SoraExtractor.invokeDumpStream
|
||||
import com.hexated.SoraExtractor.invokeEmovies
|
||||
import com.hexated.SoraExtractor.invokeMoment
|
||||
import com.hexated.SoraExtractor.invokeMultimovies
|
||||
import com.hexated.SoraExtractor.invokeNetmovies
|
||||
import com.hexated.SoraExtractor.invokeSFMovies
|
||||
import com.hexated.SoraExtractor.invokeShowflix
|
||||
import com.hexated.SoraExtractor.invokeVidSrc
|
||||
import com.hexated.SoraExtractor.invokeVidsrcto
|
||||
import com.hexated.SoraExtractor.invokeCinemaTv
|
||||
import com.hexated.SoraExtractor.invokeOmovies
|
||||
import com.hexated.SoraExtractor.invokeMoflix
|
||||
import com.hexated.SoraExtractor.invokeGhostx
|
||||
import com.hexated.SoraExtractor.invokeMoviefiction
|
||||
import com.hexated.SoraExtractor.invokeNepu
|
||||
import com.hexated.SoraExtractor.invokeWatchCartoon
|
||||
import com.hexated.SoraExtractor.invokeWatchsomuch
|
||||
import com.hexated.SoraExtractor.invokeZoechip
|
||||
import com.hexated.SoraExtractor.invokeZshow
|
||||
import com.lagradost.cloudstream3.SubtitleFile
|
||||
import com.lagradost.cloudstream3.argamap
|
||||
|
@ -53,13 +54,7 @@ class SoraStreamLite : SoraStream() {
|
|||
|
||||
argamap(
|
||||
{
|
||||
if (!res.isAnime) invokeBlackvid(
|
||||
res.id,
|
||||
res.season,
|
||||
res.episode,
|
||||
subtitleCallback,
|
||||
callback
|
||||
)
|
||||
if (!res.isAnime) invokeMoflix(res.id, res.season, res.episode, callback)
|
||||
},
|
||||
{
|
||||
if (!res.isAnime) invokeWatchsomuch(
|
||||
|
@ -100,10 +95,17 @@ class SoraStreamLite : SoraStream() {
|
|||
)
|
||||
},
|
||||
{
|
||||
invokeVidSrc(res.id, res.season, res.episode, subtitleCallback, callback)
|
||||
invokeVidSrc(res.id, res.season, res.episode, callback)
|
||||
},
|
||||
{
|
||||
invokeDbgo(res.imdbId, res.season, res.episode, subtitleCallback, callback)
|
||||
if (!res.isAnime && res.isCartoon) invokeWatchCartoon(
|
||||
res.title,
|
||||
res.year,
|
||||
res.season,
|
||||
res.episode,
|
||||
subtitleCallback,
|
||||
callback
|
||||
)
|
||||
},
|
||||
{
|
||||
if (res.isAnime) invokeAnimes(
|
||||
|
@ -136,7 +138,7 @@ class SoraStreamLite : SoraStream() {
|
|||
)
|
||||
},
|
||||
{
|
||||
if (!res.isAnime) invokeOmovies(
|
||||
if (!res.isAnime) invokeGhostx(
|
||||
res.title,
|
||||
res.year,
|
||||
res.season,
|
||||
|
@ -155,7 +157,7 @@ class SoraStreamLite : SoraStream() {
|
|||
},
|
||||
{
|
||||
if (!res.isAnime) invokeSmashyStream(
|
||||
res.imdbId,
|
||||
res.id,
|
||||
res.season,
|
||||
res.episode,
|
||||
subtitleCallback,
|
||||
|
@ -184,39 +186,32 @@ class SoraStreamLite : SoraStream() {
|
|||
},
|
||||
{
|
||||
if (!res.isAnime) invokeLing(
|
||||
res.title,
|
||||
res.airedYear ?: res.year,
|
||||
res.season,
|
||||
res.episode,
|
||||
subtitleCallback,
|
||||
callback
|
||||
res.title, res.airedYear
|
||||
?: res.year, res.season, res.episode, subtitleCallback, callback
|
||||
)
|
||||
},
|
||||
{
|
||||
if(!res.isAnime) invokeM4uhd(
|
||||
res.title,
|
||||
res.airedYear ?: res.year,
|
||||
res.season,
|
||||
res.episode,
|
||||
subtitleCallback,
|
||||
callback
|
||||
if (!res.isAnime) invokeM4uhd(
|
||||
res.title, res.airedYear
|
||||
?: res.year, res.season, res.episode, subtitleCallback, callback
|
||||
)
|
||||
},
|
||||
{
|
||||
if (!res.isAnime) invokeRStream(res.id, res.season, res.episode, callback)
|
||||
},
|
||||
{
|
||||
if (!res.isAnime) invokeFlixon(res.id, res.imdbId, res.season, res.episode, callback)
|
||||
if (!res.isAnime) invokeFlixon(
|
||||
res.id,
|
||||
res.imdbId,
|
||||
res.season,
|
||||
res.episode,
|
||||
callback
|
||||
)
|
||||
},
|
||||
{
|
||||
invokeCinemaTv(
|
||||
res.imdbId,
|
||||
res.title,
|
||||
res.airedYear ?: res.year,
|
||||
res.season,
|
||||
res.episode,
|
||||
subtitleCallback,
|
||||
callback
|
||||
res.imdbId, res.title, res.airedYear
|
||||
?: res.year, res.season, res.episode, subtitleCallback, callback
|
||||
)
|
||||
},
|
||||
{
|
||||
|
@ -224,21 +219,17 @@ class SoraStreamLite : SoraStream() {
|
|||
},
|
||||
{
|
||||
if (!res.isAnime) invokeAoneroom(
|
||||
res.title,
|
||||
res.airedYear ?: res.year,
|
||||
res.season,
|
||||
res.episode,
|
||||
subtitleCallback,
|
||||
callback
|
||||
res.title, res.airedYear
|
||||
?: res.year, res.season, res.episode, subtitleCallback, callback
|
||||
)
|
||||
},
|
||||
{
|
||||
invokeNavy(res.imdbId, res.season, res.episode, callback)
|
||||
},
|
||||
{
|
||||
if (!res.isAnime && res.season == null) invokeRidomovies(
|
||||
if (!res.isAnime) invokeRidomovies(
|
||||
res.id,
|
||||
res.imdbId,
|
||||
res.season,
|
||||
res.episode,
|
||||
subtitleCallback,
|
||||
callback
|
||||
)
|
||||
},
|
||||
|
@ -253,10 +244,24 @@ class SoraStreamLite : SoraStream() {
|
|||
)
|
||||
},
|
||||
{
|
||||
if(res.isBollywood) invokeMultimovies(multimoviesAPI, res.title, res.season, res.episode, subtitleCallback, callback)
|
||||
if (res.isBollywood) invokeMultimovies(
|
||||
multimoviesAPI,
|
||||
res.title,
|
||||
res.season,
|
||||
res.episode,
|
||||
subtitleCallback,
|
||||
callback
|
||||
)
|
||||
},
|
||||
{
|
||||
if(res.isBollywood) invokeMultimovies(multimovies2API, res.title, res.season, res.episode, subtitleCallback, callback)
|
||||
if (res.isBollywood) invokeMultimovies(
|
||||
multimovies2API,
|
||||
res.title,
|
||||
res.season,
|
||||
res.episode,
|
||||
subtitleCallback,
|
||||
callback
|
||||
)
|
||||
},
|
||||
{
|
||||
invokeNetmovies(
|
||||
|
@ -269,7 +274,7 @@ class SoraStreamLite : SoraStream() {
|
|||
)
|
||||
},
|
||||
{
|
||||
invokeMoment(res.imdbId, res.season, res.episode, callback)
|
||||
if (!res.isAnime) invokeAllMovieland(res.imdbId, res.season, res.episode, callback)
|
||||
},
|
||||
{
|
||||
if (!res.isAnime && res.season == null) invokeDoomovies(
|
||||
|
@ -279,7 +284,7 @@ class SoraStreamLite : SoraStream() {
|
|||
)
|
||||
},
|
||||
{
|
||||
if(res.isAsian) invokeDramaday(
|
||||
if (res.isAsian) invokeDramaday(
|
||||
res.title,
|
||||
res.year,
|
||||
res.season,
|
||||
|
@ -289,7 +294,7 @@ class SoraStreamLite : SoraStream() {
|
|||
)
|
||||
},
|
||||
{
|
||||
if(!res.isAnime) invoke2embed(
|
||||
if (!res.isAnime) invoke2embed(
|
||||
res.imdbId,
|
||||
res.season,
|
||||
res.episode,
|
||||
|
@ -318,8 +323,16 @@ class SoraStreamLite : SoraStream() {
|
|||
)
|
||||
},
|
||||
{
|
||||
if(!res.isAnime) invokeSFMovies(
|
||||
res.id,
|
||||
if (!res.isAnime) invokeZoechip(
|
||||
res.title,
|
||||
res.year,
|
||||
res.season,
|
||||
res.episode,
|
||||
callback
|
||||
)
|
||||
},
|
||||
{
|
||||
if (!res.isAnime) invokeNepu(
|
||||
res.title,
|
||||
res.airedYear ?: res.year,
|
||||
res.season,
|
||||
|
|
|
@ -25,10 +25,16 @@ class SoraStreamPlugin: Plugin() {
|
|||
registerExtractorAPI(Streamwish())
|
||||
registerExtractorAPI(FilelionsTo())
|
||||
registerExtractorAPI(Embedwish())
|
||||
registerExtractorAPI(Wishfast())
|
||||
registerExtractorAPI(UqloadsXyz())
|
||||
registerExtractorAPI(Uploadever())
|
||||
registerExtractorAPI(Netembed())
|
||||
registerExtractorAPI(Vidplay2())
|
||||
registerExtractorAPI(Flaswish())
|
||||
registerExtractorAPI(Comedyshow())
|
||||
registerExtractorAPI(Ridoo())
|
||||
registerExtractorAPI(Streamvid())
|
||||
registerExtractorAPI(Embedrise())
|
||||
registerExtractorAPI(Gdmirrorbot())
|
||||
registerExtractorAPI(FilemoonNl())
|
||||
registerExtractorAPI(Alions())
|
||||
}
|
||||
}
|
|
@ -9,7 +9,6 @@ import com.hexated.SoraStream.Companion.gdbot
|
|||
import com.hexated.SoraStream.Companion.hdmovies4uAPI
|
||||
import com.hexated.SoraStream.Companion.malsyncAPI
|
||||
import com.hexated.SoraStream.Companion.tvMoviesAPI
|
||||
import com.hexated.SoraStream.Companion.watchflxAPI
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.APIHolder.getCaptchaToken
|
||||
import com.lagradost.cloudstream3.APIHolder.unixTimeMS
|
||||
|
@ -19,16 +18,12 @@ import com.lagradost.cloudstream3.utils.AppUtils.toJson
|
|||
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
|
||||
import com.lagradost.nicehttp.NiceResponse
|
||||
import com.lagradost.nicehttp.RequestBodyTypes
|
||||
import com.lagradost.nicehttp.Requests.Companion.await
|
||||
import com.lagradost.nicehttp.requestCreator
|
||||
import kotlinx.coroutines.delay
|
||||
import okhttp3.FormBody
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import okhttp3.Response
|
||||
import org.jsoup.nodes.Document
|
||||
import java.math.BigInteger
|
||||
import java.net.*
|
||||
|
@ -38,7 +33,6 @@ import java.security.spec.PKCS8EncodedKeySpec
|
|||
import java.security.spec.X509EncodedKeySpec
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.spec.GCMParameterSpec
|
||||
import javax.crypto.spec.IvParameterSpec
|
||||
|
@ -46,7 +40,6 @@ import javax.crypto.spec.SecretKeySpec
|
|||
import kotlin.collections.ArrayList
|
||||
import kotlin.math.min
|
||||
|
||||
var watchflxCookies: Map<String, String>? = null
|
||||
var filmxyCookies: Map<String, String>? = null
|
||||
var sfServer: String? = null
|
||||
|
||||
|
@ -330,10 +323,8 @@ suspend fun getDrivebotLink(url: String?): String? {
|
|||
?.data()?.substringAfter("window.open('")?.substringBefore("')")
|
||||
}
|
||||
|
||||
suspend fun extractOiya(url: String, quality: String): String? {
|
||||
val doc = app.get(url).document
|
||||
return doc.selectFirst("div.wp-block-button a:matches((?i)$quality)")?.attr("href")
|
||||
?: doc.selectFirst("div.wp-block-button a")?.attr("href")
|
||||
suspend fun extractOiya(url: String): String? {
|
||||
return app.get(url).document.selectFirst("div.wp-block-button a")?.attr("href")
|
||||
}
|
||||
|
||||
fun deobfstr(hash: String, index: String): String {
|
||||
|
@ -404,6 +395,7 @@ suspend fun invokeSmashyFfix(
|
|||
name: String,
|
||||
url: String,
|
||||
ref: String,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit,
|
||||
) {
|
||||
val json = app.get(url, referer = ref, headers = mapOf("X-Requested-With" to "XMLHttpRequest"))
|
||||
|
@ -416,23 +408,41 @@ suspend fun invokeSmashyFfix(
|
|||
).forEach(callback)
|
||||
}
|
||||
|
||||
json?.subtitleUrls?.split(",")?.map { sub ->
|
||||
val lang = "\\[(.*)]".toRegex().find(sub)?.groupValues?.get(1)
|
||||
val subUrl = sub.replace("[$lang]", "").trim()
|
||||
subtitleCallback.invoke(
|
||||
SubtitleFile(
|
||||
lang ?: return@map,
|
||||
subUrl
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
suspend fun invokeSmashyD(
|
||||
suspend fun invokeSmashySu(
|
||||
name: String,
|
||||
url: String,
|
||||
ref: String,
|
||||
callback: (ExtractorLink) -> Unit,
|
||||
) {
|
||||
val json = app.get(url, referer = ref, headers = mapOf("X-Requested-With" to "XMLHttpRequest"))
|
||||
.parsedSafe<SmashyDSources>()
|
||||
json?.sourceUrls?.apmap {
|
||||
M3u8Helper.generateM3u8(
|
||||
"Smashy [Player D ${it.title}]",
|
||||
it.file ?: return@apmap,
|
||||
""
|
||||
).forEach(callback)
|
||||
.parsedSafe<SmashySources>()
|
||||
json?.sourceUrls?.firstOrNull()?.removeSuffix(",")?.split(",")?.forEach { links ->
|
||||
val quality = Regex("\\[(\\S+)]").find(links)?.groupValues?.getOrNull(1) ?: return@forEach
|
||||
val trimmedLink = links.removePrefix("[$quality]").trim()
|
||||
callback.invoke(
|
||||
ExtractorLink(
|
||||
"Smashy [$name]",
|
||||
"Smashy [$name]",
|
||||
trimmedLink,
|
||||
"",
|
||||
getQualityFromName(quality),
|
||||
INFER_TYPE
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
suspend fun getDumpIdAndType(title: String?, year: Int?, season: Int?): Pair<String?, Int?> {
|
||||
|
@ -588,10 +598,11 @@ suspend fun bypassFdAds(url: String?): String? {
|
|||
}
|
||||
|
||||
suspend fun bypassHrefli(url: String): String? {
|
||||
fun Document.getFormUrl() : String {
|
||||
fun Document.getFormUrl(): String {
|
||||
return this.select("form#landing").attr("action")
|
||||
}
|
||||
fun Document.getFormData() : Map<String,String> {
|
||||
|
||||
fun Document.getFormData(): Map<String, String> {
|
||||
return this.select("form#landing input").associate { it.attr("name") to it.attr("value") }
|
||||
}
|
||||
|
||||
|
@ -605,7 +616,8 @@ suspend fun bypassHrefli(url: String): String? {
|
|||
formData = res.getFormData()
|
||||
|
||||
res = app.post(formUrl, data = formData).document
|
||||
val skToken = res.selectFirst("script:containsData(?go=)")?.data()?.substringAfter("?go=")?.substringBefore("\"") ?: return null
|
||||
val skToken = res.selectFirst("script:containsData(?go=)")?.data()?.substringAfter("?go=")
|
||||
?.substringBefore("\"") ?: return null
|
||||
val driveUrl = app.get(
|
||||
"$host?go=$skToken", cookies = mapOf(
|
||||
skToken to "${formData["_wp_http2"]}"
|
||||
|
@ -699,30 +711,13 @@ suspend fun fetchFilmxyCookies(url: String): Map<String, String> {
|
|||
return cookieJar.plus(defaultCookies)
|
||||
}
|
||||
|
||||
suspend fun getWatchflxCookies() =
|
||||
watchflxCookies ?: fetchWatchflxCookies().also { watchflxCookies = it }
|
||||
|
||||
suspend fun fetchWatchflxCookies(): Map<String, String> {
|
||||
session.get(watchflxAPI)
|
||||
val cookies = session.baseClient.cookieJar.loadForRequest(watchflxAPI.toHttpUrl())
|
||||
.associate { it.name to it.value }
|
||||
val loginUrl = "$watchflxAPI/cookie-based-login"
|
||||
session.post(
|
||||
loginUrl, data = mapOf(
|
||||
"continue_as_temp" to "true"
|
||||
), cookies = cookies, headers = mapOf("X-Requested-With" to "XMLHttpRequest")
|
||||
)
|
||||
return session.baseClient.cookieJar.loadForRequest(loginUrl.toHttpUrl())
|
||||
.associate { it.name to it.value }
|
||||
}
|
||||
|
||||
fun Document.findTvMoviesIframe(): String? {
|
||||
return this.selectFirst("script:containsData(var seconds)")?.data()?.substringAfter("href='")
|
||||
?.substringBefore("'>")
|
||||
}
|
||||
|
||||
//modified code from https://github.com/jmir1/aniyomi-extensions/blob/master/src/all/kamyroll/src/eu/kanade/tachiyomi/animeextension/all/kamyroll/AccessTokenInterceptor.kt
|
||||
suspend fun getCrunchyrollToken(): Map<String, String> {
|
||||
suspend fun getCrunchyrollToken(): CrunchyrollAccessToken {
|
||||
val client = app.baseClient.newBuilder()
|
||||
.proxy(Proxy(Proxy.Type.SOCKS, InetSocketAddress("cr-unblocker.us.to", 1080)))
|
||||
.build()
|
||||
|
@ -748,9 +743,18 @@ suspend fun getCrunchyrollToken(): Map<String, String> {
|
|||
)
|
||||
)
|
||||
|
||||
val response = tryParseJson<CrunchyrollToken>(client.newCall(request).execute().body.string())
|
||||
return mapOf("Authorization" to "${response?.tokenType} ${response?.accessToken}")
|
||||
|
||||
val token = tryParseJson<CrunchyrollToken>(client.newCall(request).execute().body.string())
|
||||
val headers = mapOf("Authorization" to "${token?.tokenType} ${token?.accessToken}")
|
||||
val cms =
|
||||
app.get("$crunchyrollAPI/index/v2", headers = headers).parsedSafe<CrunchyrollToken>()?.cms
|
||||
return CrunchyrollAccessToken(
|
||||
token?.accessToken,
|
||||
token?.tokenType,
|
||||
cms?.bucket,
|
||||
cms?.policy,
|
||||
cms?.signature,
|
||||
cms?.key_pair_id,
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun getCrunchyrollId(aniId: String?): String? {
|
||||
|
@ -784,7 +788,7 @@ suspend fun getCrunchyrollId(aniId: String?): String? {
|
|||
|
||||
return (externalLinks?.find { it.site == "VRV" }
|
||||
?: externalLinks?.find { it.site == "Crunchyroll" })?.url?.let {
|
||||
Regex("series/(\\w+)/?").find(it)?.groupValues?.get(1)
|
||||
app.get(it).url.substringAfter("/series/").substringBefore("/")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -797,6 +801,10 @@ suspend fun getCrunchyrollIdFromMalSync(aniId: String?): String? {
|
|||
?: regex.find("$crunchyroll")?.groupValues?.getOrNull(1)
|
||||
}
|
||||
|
||||
suspend fun String.haveDub(referer: String) : Boolean {
|
||||
return app.get(this,referer=referer).text.contains("TYPE=AUDIO")
|
||||
}
|
||||
|
||||
suspend fun convertTmdbToAnimeId(
|
||||
title: String?,
|
||||
date: String?,
|
||||
|
@ -1031,7 +1039,7 @@ fun decodeIndexJson(json: String): String {
|
|||
return base64Decode(slug.substring(0, slug.length - 20))
|
||||
}
|
||||
|
||||
fun String.decodePrimewireXor(key: String): String {
|
||||
fun String.xorDecrypt(key: String): String {
|
||||
val sb = StringBuilder()
|
||||
var i = 0
|
||||
while (i < this.length) {
|
||||
|
@ -1057,9 +1065,9 @@ fun vidsrctoDecrypt(text: String): String {
|
|||
}
|
||||
|
||||
fun String?.createSlug(): String? {
|
||||
return this?.replace(Regex("[^\\w\\s-]"), "")
|
||||
?.replace(" ", "-")
|
||||
?.replace(Regex("( – )|( -)|(- )|(--)"), "-")
|
||||
return this?.filter { it.isWhitespace() || it.isLetterOrDigit() }
|
||||
?.trim()
|
||||
?.replace("\\s+".toRegex(), "-")
|
||||
?.lowercase()
|
||||
}
|
||||
|
||||
|
@ -1145,14 +1153,6 @@ fun getVipLanguage(str: String): String {
|
|||
}
|
||||
}
|
||||
|
||||
fun getDbgoLanguage(str: String): String {
|
||||
return when (str) {
|
||||
"Русский" -> "Russian"
|
||||
"Українська" -> "Ukrainian"
|
||||
else -> str
|
||||
}
|
||||
}
|
||||
|
||||
fun fixCrunchyrollLang(language: String?): String? {
|
||||
return SubtitleHelper.fromTwoLettersToLanguage(language ?: return null)
|
||||
?: SubtitleHelper.fromTwoLettersToLanguage(language.substringBefore("-"))
|
||||
|
@ -1209,37 +1209,6 @@ fun base64DecodeAPI(api: String): String {
|
|||
return api.chunked(4).map { base64Decode(it) }.reversed().joinToString("")
|
||||
}
|
||||
|
||||
fun decryptStreamUrl(data: String): String {
|
||||
|
||||
fun getTrash(arr: List<String>, item: Int): List<String> {
|
||||
val trash = ArrayList<List<String>>()
|
||||
for (i in 1..item) {
|
||||
trash.add(arr)
|
||||
}
|
||||
return trash.reduce { acc, list ->
|
||||
val temp = ArrayList<String>()
|
||||
acc.forEach { ac ->
|
||||
list.forEach { li ->
|
||||
temp.add(ac.plus(li))
|
||||
}
|
||||
}
|
||||
return@reduce temp
|
||||
}
|
||||
}
|
||||
|
||||
val trashList = listOf("@", "#", "!", "^", "$")
|
||||
val trashSet = getTrash(trashList, 2) + getTrash(trashList, 3)
|
||||
var trashString = data.replace("#2", "").split("//_//").joinToString("")
|
||||
|
||||
trashSet.forEach {
|
||||
val temp = base64Encode(it.toByteArray())
|
||||
trashString = trashString.replace(temp, "")
|
||||
}
|
||||
|
||||
return base64Decode(trashString)
|
||||
|
||||
}
|
||||
|
||||
fun fixUrl(url: String, domain: String): String {
|
||||
if (url.startsWith("http")) {
|
||||
return url
|
||||
|
@ -1283,23 +1252,53 @@ private enum class Symbol(val decimalValue: Int) {
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun request(
|
||||
url: String,
|
||||
allowRedirects: Boolean = true,
|
||||
timeout: Long = 60L
|
||||
): Response {
|
||||
val client = OkHttpClient().newBuilder()
|
||||
.connectTimeout(timeout, TimeUnit.SECONDS)
|
||||
.readTimeout(timeout, TimeUnit.SECONDS)
|
||||
.writeTimeout(timeout, TimeUnit.SECONDS)
|
||||
.followRedirects(allowRedirects)
|
||||
.followSslRedirects(allowRedirects)
|
||||
.build()
|
||||
// steal from https://github.com/aniyomiorg/aniyomi-extensions/blob/master/src/en/aniwave/src/eu/kanade/tachiyomi/animeextension/en/nineanime/AniwaveUtils.kt
|
||||
// credits to @samfundev
|
||||
object AniwaveUtils {
|
||||
|
||||
val request: Request = Request.Builder()
|
||||
.url(url)
|
||||
.build()
|
||||
return client.newCall(request).await()
|
||||
fun encodeVrf(input: String): String {
|
||||
val rc4Key = SecretKeySpec("ysJhV6U27FVIjjuk".toByteArray(), "RC4")
|
||||
val cipher = Cipher.getInstance("RC4")
|
||||
cipher.init(Cipher.DECRYPT_MODE, rc4Key, cipher.parameters)
|
||||
var vrf = cipher.doFinal(input.toByteArray())
|
||||
vrf = Base64.encode(vrf, Base64.URL_SAFE or Base64.NO_WRAP)
|
||||
vrf = Base64.encode(vrf, Base64.DEFAULT or Base64.NO_WRAP)
|
||||
vrf = vrfShift(vrf)
|
||||
vrf = Base64.encode(vrf, Base64.DEFAULT)
|
||||
vrf = rot13(vrf)
|
||||
val stringVrf = vrf.toString(Charsets.UTF_8)
|
||||
return encode(stringVrf)
|
||||
}
|
||||
|
||||
fun decodeVrf(input: String): String {
|
||||
var vrf = input.toByteArray()
|
||||
vrf = Base64.decode(vrf, Base64.URL_SAFE)
|
||||
val rc4Key = SecretKeySpec("hlPeNwkncH0fq9so".toByteArray(), "RC4")
|
||||
val cipher = Cipher.getInstance("RC4")
|
||||
cipher.init(Cipher.DECRYPT_MODE, rc4Key, cipher.parameters)
|
||||
vrf = cipher.doFinal(vrf)
|
||||
return decode(vrf.toString(Charsets.UTF_8))
|
||||
}
|
||||
|
||||
private fun rot13(vrf: ByteArray): ByteArray {
|
||||
for (i in vrf.indices) {
|
||||
val byte = vrf[i]
|
||||
if (byte in 'A'.code..'Z'.code) {
|
||||
vrf[i] = ((byte - 'A'.code + 13) % 26 + 'A'.code).toByte()
|
||||
} else if (byte in 'a'.code..'z'.code) {
|
||||
vrf[i] = ((byte - 'a'.code + 13) % 26 + 'a'.code).toByte()
|
||||
}
|
||||
}
|
||||
return vrf
|
||||
}
|
||||
|
||||
private fun vrfShift(vrf: ByteArray): ByteArray {
|
||||
for (i in vrf.indices) {
|
||||
val shift = arrayOf(-3, 3, -4, 2, -2, 5, 4, 5)[i % 8]
|
||||
vrf[i] = vrf[i].plus(shift).toByte()
|
||||
}
|
||||
return vrf
|
||||
}
|
||||
}
|
||||
|
||||
object DumpUtils {
|
||||
|
|
|
@ -1,6 +1,16 @@
|
|||
// use an integer for version numbers
|
||||
version = 12
|
||||
import org.jetbrains.kotlin.konan.properties.Properties
|
||||
|
||||
// use an integer for version numbers
|
||||
version = 13
|
||||
|
||||
android {
|
||||
defaultConfig {
|
||||
val properties = Properties()
|
||||
properties.load(project.rootProject.file("local.properties").inputStream())
|
||||
|
||||
buildConfigField("String", "TMDB_API", "\"${properties.getProperty("TMDB_API")}\"")
|
||||
}
|
||||
}
|
||||
|
||||
cloudstream {
|
||||
language = "en"
|
||||
|
|
|
@ -10,24 +10,23 @@ import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
|
|||
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 java.net.URI
|
||||
|
||||
private const val TRACKER_LIST_URL =
|
||||
"https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_best.txt"
|
||||
|
||||
class StremioC : MainAPI() {
|
||||
override var mainUrl = "https://stremio.github.io/stremio-static-addon-example"
|
||||
override var name = "StremioC"
|
||||
override val supportedTypes = setOf(TvType.Others)
|
||||
override val hasMainPage = true
|
||||
private val cinemataUrl = "https://v3-cinemeta.strem.io"
|
||||
|
||||
override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse? {
|
||||
companion object {
|
||||
private const val cinemataUrl = "https://v3-cinemeta.strem.io"
|
||||
private const val TRACKER_LIST_URL = "https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_best.txt"
|
||||
}
|
||||
|
||||
override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse {
|
||||
mainUrl = mainUrl.fixSourceUrl()
|
||||
val res = tryParseJson<Manifest>(request("${mainUrl}/manifest.json").body.string()) ?: return null
|
||||
val res = app.get("${mainUrl}/manifest.json").parsedSafe<Manifest>()
|
||||
val lists = mutableListOf<HomePageList>()
|
||||
res.catalogs.apmap { catalog ->
|
||||
res?.catalogs?.apmap { catalog ->
|
||||
catalog.toHomePageList(this).let {
|
||||
if (it.list.isNotEmpty()) lists.add(it)
|
||||
}
|
||||
|
@ -38,11 +37,11 @@ class StremioC : MainAPI() {
|
|||
)
|
||||
}
|
||||
|
||||
override suspend fun search(query: String): List<SearchResponse>? {
|
||||
override suspend fun search(query: String): List<SearchResponse> {
|
||||
mainUrl = mainUrl.fixSourceUrl()
|
||||
val res = tryParseJson<Manifest>(request("${mainUrl}/manifest.json").body.string()) ?: return null
|
||||
val res = app.get("${mainUrl}/manifest.json").parsedSafe<Manifest>()
|
||||
val list = mutableListOf<SearchResponse>()
|
||||
res.catalogs.apmap { catalog ->
|
||||
res?.catalogs?.apmap { catalog ->
|
||||
list.addAll(catalog.search(query, this))
|
||||
}
|
||||
return list.distinct()
|
||||
|
@ -64,10 +63,13 @@ class StremioC : MainAPI() {
|
|||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
val loadData = parseJson<LoadData>(data)
|
||||
val request = request("${mainUrl}/stream/${loadData.type}/${loadData.id}.json")
|
||||
if (request.code.isSuccessful()) {
|
||||
val res = tryParseJson<StreamsResponse>(request.body.string()) ?: return false
|
||||
res.streams.forEach { stream ->
|
||||
val request = app.get(
|
||||
"${mainUrl}/stream/${loadData.type}/${loadData.id}.json",
|
||||
timeout = 120L
|
||||
)
|
||||
if (request.isSuccessful) {
|
||||
val res = request.parsedSafe<StreamsResponse>()
|
||||
res?.streams?.forEach { stream ->
|
||||
stream.runCallback(subtitleCallback, callback)
|
||||
}
|
||||
} else {
|
||||
|
@ -103,15 +105,14 @@ class StremioC : MainAPI() {
|
|||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
) {
|
||||
val sites =
|
||||
AcraApplication.getKey<Array<CustomSite>>(USER_PROVIDER_API)?.toMutableList()
|
||||
?: mutableListOf()
|
||||
val sites = AcraApplication.getKey<Array<CustomSite>>(USER_PROVIDER_API)?.toMutableList()
|
||||
?: mutableListOf()
|
||||
sites.filter { it.parentJavaClass == "StremioX" }.apmap { site ->
|
||||
val request = request("${site.url.fixSourceUrl()}/stream/${type}/${id}.json").body.string()
|
||||
val res =
|
||||
tryParseJson<StreamsResponse>(request)
|
||||
?: return@apmap
|
||||
res.streams.forEach { stream ->
|
||||
val res = app.get(
|
||||
"${site.url.fixSourceUrl()}/stream/${type}/${id}.json",
|
||||
timeout = 120L
|
||||
).parsedSafe<StreamsResponse>()
|
||||
res?.streams?.forEach { stream ->
|
||||
stream.runCallback(subtitleCallback, callback)
|
||||
}
|
||||
}
|
||||
|
@ -151,11 +152,11 @@ class StremioC : MainAPI() {
|
|||
suspend fun search(query: String, provider: StremioC): List<SearchResponse> {
|
||||
val entries = mutableListOf<SearchResponse>()
|
||||
types.forEach { type ->
|
||||
val json = request("${provider.mainUrl}/catalog/${type}/${id}/search=${query}.json").body.string()
|
||||
val res =
|
||||
tryParseJson<CatalogResponse>(json)
|
||||
?: return@forEach
|
||||
res.metas?.forEach { entry ->
|
||||
val res = app.get(
|
||||
"${provider.mainUrl}/catalog/${type}/${id}/search=${query}.json",
|
||||
timeout = 120L
|
||||
).parsedSafe<CatalogResponse>()
|
||||
res?.metas?.forEach { entry ->
|
||||
entries.add(entry.toSearchResponse(provider))
|
||||
}
|
||||
}
|
||||
|
@ -165,11 +166,11 @@ class StremioC : MainAPI() {
|
|||
suspend fun toHomePageList(provider: StremioC): HomePageList {
|
||||
val entries = mutableListOf<SearchResponse>()
|
||||
types.forEach { type ->
|
||||
val json = request("${provider.mainUrl}/catalog/${type}/${id}.json").body.string()
|
||||
val res =
|
||||
tryParseJson<CatalogResponse>(json)
|
||||
?: return@forEach
|
||||
res.metas?.forEach { entry ->
|
||||
val res = app.get(
|
||||
"${provider.mainUrl}/catalog/${type}/${id}.json",
|
||||
timeout = 120L
|
||||
).parsedSafe<CatalogResponse>()
|
||||
res?.metas?.forEach { entry ->
|
||||
entries.add(entry.toSearchResponse(provider))
|
||||
}
|
||||
}
|
||||
|
@ -186,6 +187,7 @@ class StremioC : MainAPI() {
|
|||
val source: String?,
|
||||
val type: String?
|
||||
)
|
||||
|
||||
private data class CatalogEntry(
|
||||
@JsonProperty("name") val name: String,
|
||||
@JsonProperty("id") val id: String,
|
||||
|
@ -226,7 +228,7 @@ class StremioC : MainAPI() {
|
|||
year = yearNum?.toIntOrNull()
|
||||
tags = genre ?: genres
|
||||
addActors(cast)
|
||||
addTrailer(trailersSources?.map { "https://www.youtube.com/watch?v=${it.source}" }?.randomOrNull())
|
||||
addTrailer(trailersSources?.map { "https://www.youtube.com/watch?v=${it.source}" })
|
||||
addImdbId(imdbId)
|
||||
}
|
||||
} else {
|
||||
|
@ -245,7 +247,8 @@ class StremioC : MainAPI() {
|
|||
year = yearNum?.toIntOrNull()
|
||||
tags = genre ?: genres
|
||||
addActors(cast)
|
||||
addTrailer(trailersSources?.map { "https://www.youtube.com/watch?v=${it.source}" }?.randomOrNull())
|
||||
addTrailer(trailersSources?.map { "https://www.youtube.com/watch?v=${it.source}" }
|
||||
?.randomOrNull())
|
||||
addImdbId(imdbId)
|
||||
}
|
||||
}
|
||||
|
@ -285,13 +288,14 @@ class StremioC : MainAPI() {
|
|||
)
|
||||
|
||||
private data class ProxyHeaders(
|
||||
val request: Map<String,String>?,
|
||||
val request: Map<String, String>?,
|
||||
)
|
||||
|
||||
private data class BehaviorHints(
|
||||
val proxyHeaders: ProxyHeaders?,
|
||||
val headers: Map<String,String>?,
|
||||
val headers: Map<String, String>?,
|
||||
)
|
||||
|
||||
private data class Stream(
|
||||
val name: String?,
|
||||
val title: String?,
|
||||
|
@ -312,12 +316,13 @@ class StremioC : MainAPI() {
|
|||
callback.invoke(
|
||||
ExtractorLink(
|
||||
name ?: "",
|
||||
fixRDSourceName(name, title),
|
||||
fixSourceName(name, title),
|
||||
url,
|
||||
"",
|
||||
getQualityFromName(description),
|
||||
headers = behaviorHints?.proxyHeaders?.request ?: behaviorHints?.headers ?: mapOf(),
|
||||
isM3u8 = URI(url).path.endsWith(".m3u8")
|
||||
getQuality(listOf(description,title,name)),
|
||||
headers = behaviorHints?.proxyHeaders?.request ?: behaviorHints?.headers
|
||||
?: mapOf(),
|
||||
type = INFER_TYPE
|
||||
)
|
||||
)
|
||||
subtitles.map { sub ->
|
||||
|
|
|
@ -10,26 +10,21 @@ import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
|
|||
import com.lagradost.cloudstream3.utils.*
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.toJson
|
||||
import java.net.URI
|
||||
import java.util.ArrayList
|
||||
import kotlin.math.roundToInt
|
||||
import com.lagradost.cloudstream3.metaproviders.TmdbProvider
|
||||
|
||||
open class StremioX : TmdbProvider() {
|
||||
class StremioX : TmdbProvider() {
|
||||
override var mainUrl = "https://torrentio.strem.fun"
|
||||
override var name = "StremioX"
|
||||
override val hasMainPage = true
|
||||
override val hasQuickSearch = true
|
||||
override val supportedTypes = setOf(
|
||||
TvType.Others,
|
||||
)
|
||||
override val supportedTypes = setOf(TvType.Others)
|
||||
|
||||
companion object {
|
||||
const val TRACKER_LIST_URL =
|
||||
"https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_best.txt"
|
||||
const val TRACKER_LIST_URL = "https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_best.txt"
|
||||
private const val tmdbAPI = "https://api.themoviedb.org/3"
|
||||
private val apiKey =
|
||||
base64DecodeAPI("ZTM=NTg=MjM=MjM=ODc=MzI=OGQ=MmE=Nzk=Nzk=ZjI=NTA=NDY=NDA=MzA=YjA=") // PLEASE DON'T STEAL
|
||||
private const val apiKey = BuildConfig.TMDB_API
|
||||
|
||||
fun getType(t: String?): TvType {
|
||||
return when (t) {
|
||||
|
@ -44,11 +39,6 @@ open class StremioX : TmdbProvider() {
|
|||
else -> ShowStatus.Completed
|
||||
}
|
||||
}
|
||||
|
||||
private fun base64DecodeAPI(api: String): String {
|
||||
return api.chunked(4).map { base64Decode(it) }.reversed().joinToString("")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override val mainPage = mainPageOf(
|
||||
|
@ -159,7 +149,7 @@ open class StremioX : TmdbProvider() {
|
|||
eps.seasonNumber,
|
||||
eps.episodeNumber
|
||||
).toJson(),
|
||||
name = eps.name + if (isUpcoming(eps.airDate)) " - [UPCOMING]" else "",
|
||||
name = eps.name + if (isUpcoming(eps.airDate)) " • [UPCOMING]" else "",
|
||||
season = eps.seasonNumber,
|
||||
episode = eps.episodeNumber,
|
||||
posterUrl = getImageUrl(eps.stillPath),
|
||||
|
@ -177,7 +167,7 @@ open class StremioX : TmdbProvider() {
|
|||
this.backgroundPosterUrl = bgPoster
|
||||
this.year = year
|
||||
this.plot = res.overview
|
||||
this.tags = if (isAnime) keywords else genres
|
||||
this.tags = keywords.takeIf { !it.isNullOrEmpty() } ?: genres
|
||||
this.rating = rating
|
||||
this.showStatus = getStatus(res.status)
|
||||
this.recommendations = recommendations
|
||||
|
@ -200,7 +190,7 @@ open class StremioX : TmdbProvider() {
|
|||
this.year = year
|
||||
this.plot = res.overview
|
||||
this.duration = res.runtime
|
||||
this.tags = if (isAnime) keywords else genres
|
||||
this.tags = keywords.takeIf { !it.isNullOrEmpty() } ?: genres
|
||||
this.rating = rating
|
||||
this.recommendations = recommendations
|
||||
this.actors = actors
|
||||
|
@ -243,13 +233,13 @@ open class StremioX : TmdbProvider() {
|
|||
callback: (ExtractorLink) -> Unit
|
||||
) {
|
||||
val fixMainUrl = mainUrl.fixSourceUrl()
|
||||
val url = if(season == null) {
|
||||
val url = if (season == null) {
|
||||
"$fixMainUrl/stream/movie/$imdbId.json"
|
||||
} else {
|
||||
"$fixMainUrl/stream/series/$imdbId:$season:$episode.json"
|
||||
}
|
||||
val res = AppUtils.tryParseJson<StreamsResponse>(request(url).body.string()) ?: return
|
||||
res.streams.forEach { stream ->
|
||||
val res = app.get(url, timeout = 120L).parsedSafe<StreamsResponse>()
|
||||
res?.streams?.forEach { stream ->
|
||||
stream.runCallback(subtitleCallback, callback)
|
||||
}
|
||||
}
|
||||
|
@ -262,12 +252,12 @@ open class StremioX : TmdbProvider() {
|
|||
)
|
||||
|
||||
private data class ProxyHeaders(
|
||||
val request: Map<String,String>?,
|
||||
val request: Map<String, String>?,
|
||||
)
|
||||
|
||||
private data class BehaviorHints(
|
||||
val proxyHeaders: ProxyHeaders?,
|
||||
val headers: Map<String,String>?,
|
||||
val headers: Map<String, String>?,
|
||||
)
|
||||
|
||||
private data class Stream(
|
||||
|
@ -290,12 +280,13 @@ open class StremioX : TmdbProvider() {
|
|||
callback.invoke(
|
||||
ExtractorLink(
|
||||
name ?: "",
|
||||
fixRDSourceName(name, title),
|
||||
fixSourceName(name, title),
|
||||
url,
|
||||
"",
|
||||
getQualityFromName(description),
|
||||
headers = behaviorHints?.proxyHeaders?.request ?: behaviorHints?.headers ?: mapOf(),
|
||||
isM3u8 = URI(url).path.endsWith(".m3u8")
|
||||
getQuality(listOf(description,title,name)),
|
||||
headers = behaviorHints?.proxyHeaders?.request ?: behaviorHints?.headers
|
||||
?: mapOf(),
|
||||
type = INFER_TYPE
|
||||
)
|
||||
)
|
||||
subtitles.map { sub ->
|
||||
|
|
|
@ -3,10 +3,9 @@ package com.hexated
|
|||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.SubtitleFile
|
||||
import com.lagradost.cloudstream3.app
|
||||
import com.lagradost.cloudstream3.base64Encode
|
||||
import com.lagradost.cloudstream3.utils.SubtitleHelper
|
||||
|
||||
const val openSubAPI = "https://opensubtitles.strem.io/stremio/v1"
|
||||
const val openSubAPI = "https://opensubtitles-v3.strem.io"
|
||||
const val watchSomuchAPI = "https://watchsomuch.tv"
|
||||
|
||||
object SubsExtractors {
|
||||
|
@ -16,22 +15,20 @@ object SubsExtractors {
|
|||
episode: Int? = null,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
) {
|
||||
val id = if(season == null) {
|
||||
imdbId
|
||||
val slug = if(season == null) {
|
||||
"movie/$imdbId"
|
||||
} else {
|
||||
"$imdbId $season $episode"
|
||||
"series/$imdbId:$season:$episode"
|
||||
}
|
||||
val data = base64Encode("""{"id":1,"jsonrpc":"2.0","method":"subtitles.find","params":[null,{"query":{"itemHash":"$id"}}]}""".toByteArray())
|
||||
app.get("${openSubAPI}/q.json?b=$data").parsedSafe<OsResult>()?.result?.all?.map { sub ->
|
||||
app.get("${openSubAPI}/subtitles/$slug.json", timeout = 120L).parsedSafe<OsResult>()?.subtitles?.map { sub ->
|
||||
subtitleCallback.invoke(
|
||||
SubtitleFile(
|
||||
SubtitleHelper.fromThreeLettersToLanguage(sub.lang ?: "") ?: sub.lang
|
||||
?: "",
|
||||
?: return@map,
|
||||
sub.url ?: return@map
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
suspend fun invokeWatchsomuch(
|
||||
|
@ -45,7 +42,7 @@ object SubsExtractors {
|
|||
"${watchSomuchAPI}/Watch/ajMovieTorrents.aspx", data = mapOf(
|
||||
"index" to "0",
|
||||
"mid" to "$id",
|
||||
"wsk" to "f6ea6cde-e42b-4c26-98d3-b4fe48cdd4fb",
|
||||
"wsk" to "30fb68aa-1c71-4b8c-b5d4-4ca9222cfb45",
|
||||
"lid" to "",
|
||||
"liu" to ""
|
||||
), headers = mapOf("X-Requested-With" to "XMLHttpRequest")
|
||||
|
@ -81,12 +78,8 @@ object SubsExtractors {
|
|||
@JsonProperty("lang") val lang: String? = null,
|
||||
)
|
||||
|
||||
data class OsAll(
|
||||
@JsonProperty("all") val all: ArrayList<OsSubtitles>? = arrayListOf(),
|
||||
)
|
||||
|
||||
data class OsResult(
|
||||
@JsonProperty("result") val result: OsAll? = null,
|
||||
@JsonProperty("subtitles") val subtitles: ArrayList<OsSubtitles>? = arrayListOf(),
|
||||
)
|
||||
|
||||
data class WatchsomuchTorrents(
|
||||
|
|
|
@ -1,53 +1,35 @@
|
|||
package com.hexated
|
||||
|
||||
import com.lagradost.cloudstream3.APIHolder
|
||||
import com.lagradost.cloudstream3.APIHolder.unixTimeMS
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.nicehttp.Requests.Companion.await
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import com.lagradost.cloudstream3.utils.getQualityFromName
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
const val defaultTimeOut = 30L
|
||||
suspend fun request(
|
||||
url: String,
|
||||
allowRedirects: Boolean = true,
|
||||
timeout: Long = defaultTimeOut
|
||||
): Response {
|
||||
val client = OkHttpClient().newBuilder()
|
||||
.connectTimeout(timeout, TimeUnit.SECONDS)
|
||||
.readTimeout(timeout, TimeUnit.SECONDS)
|
||||
.writeTimeout(timeout, TimeUnit.SECONDS)
|
||||
.followRedirects(allowRedirects)
|
||||
.followSslRedirects(allowRedirects)
|
||||
.build()
|
||||
|
||||
val request: Request = Request.Builder()
|
||||
.url(url)
|
||||
.build()
|
||||
return client.newCall(request).await()
|
||||
}
|
||||
|
||||
fun Int.isSuccessful() : Boolean {
|
||||
return this in 200..299
|
||||
}
|
||||
|
||||
fun String.fixSourceUrl(): String {
|
||||
return this.replace("/manifest.json", "").replace("stremio://", "https://")
|
||||
}
|
||||
|
||||
fun fixRDSourceName(name: String?, title: String?): String {
|
||||
fun fixSourceName(name: String?, title: String?): String {
|
||||
return when {
|
||||
name?.contains("[RD+]", true) == true -> "[RD+] $title"
|
||||
name?.contains("[RD download]", true) == true -> "[RD] $title"
|
||||
name?.contains("[RD download]", true) == true -> "[RD download] $title"
|
||||
!name.isNullOrEmpty() && !title.isNullOrEmpty() -> "$name $title"
|
||||
else -> title ?: name ?: ""
|
||||
}
|
||||
}
|
||||
|
||||
fun getQuality(qualities: List<String?>): Int {
|
||||
fun String.getQuality(): String? {
|
||||
return Regex("(\\d{3,4}[pP])").find(this)?.groupValues?.getOrNull(1)
|
||||
}
|
||||
val quality = qualities.firstNotNullOfOrNull { it?.getQuality() }
|
||||
return getQualityFromName(quality)
|
||||
}
|
||||
|
||||
fun getEpisodeSlug(
|
||||
season: Int? = null,
|
||||
episode: Int? = null,
|
||||
|
|
42
Superstream/build.gradle.kts
Normal file
42
Superstream/build.gradle.kts
Normal file
|
@ -0,0 +1,42 @@
|
|||
import org.jetbrains.kotlin.konan.properties.Properties
|
||||
|
||||
// use an integer for version numbers
|
||||
version = 3
|
||||
|
||||
android {
|
||||
defaultConfig {
|
||||
val properties = Properties()
|
||||
properties.load(project.rootProject.file("local.properties").inputStream())
|
||||
|
||||
buildConfigField("String", "SUPERSTREAM_FIRST_API", "\"${properties.getProperty("SUPERSTREAM_FIRST_API")}\"")
|
||||
buildConfigField("String", "SUPERSTREAM_SECOND_API", "\"${properties.getProperty("SUPERSTREAM_SECOND_API")}\"")
|
||||
buildConfigField("String", "SUPERSTREAM_THIRD_API", "\"${properties.getProperty("SUPERSTREAM_THIRD_API")}\"")
|
||||
buildConfigField("String", "SUPERSTREAM_FOURTH_API", "\"${properties.getProperty("SUPERSTREAM_FOURTH_API")}\"")
|
||||
}
|
||||
}
|
||||
|
||||
cloudstream {
|
||||
language = "en"
|
||||
// All of these properties are optional, you can safely remove them
|
||||
|
||||
// description = "Lorem Ipsum"
|
||||
authors = listOf("Blatzar")
|
||||
|
||||
/**
|
||||
* Status int as the following:
|
||||
* 0: Down
|
||||
* 1: Ok
|
||||
* 2: Slow
|
||||
* 3: Beta only
|
||||
* */
|
||||
status = 1 // will be 3 if unspecified
|
||||
tvTypes = listOf(
|
||||
"AsianDrama",
|
||||
"Anime",
|
||||
"TvSeries",
|
||||
"Movie",
|
||||
)
|
||||
|
||||
|
||||
iconUrl = "https://cdn.discordapp.com/attachments/1109266606292488297/1196694385061003334/icon.png"
|
||||
}
|
2
Superstream/src/main/AndroidManifest.xml
Normal file
2
Superstream/src/main/AndroidManifest.xml
Normal file
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest package="com.hexated"/>
|
243
Superstream/src/main/kotlin/com/hexated/Extractors.kt
Normal file
243
Superstream/src/main/kotlin/com/hexated/Extractors.kt
Normal file
|
@ -0,0 +1,243 @@
|
|||
package com.hexated
|
||||
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
||||
import com.lagradost.cloudstream3.utils.*
|
||||
import java.net.URL
|
||||
|
||||
object Extractors : Superstream() {
|
||||
|
||||
suspend fun invokeInternalSource(
|
||||
id: Int? = null,
|
||||
type: Int? = null,
|
||||
season: Int? = null,
|
||||
episode: Int? = null,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit,
|
||||
) {
|
||||
fun LinkList.toExtractorLink(): ExtractorLink? {
|
||||
if (this.path.isNullOrBlank()) return null
|
||||
return ExtractorLink(
|
||||
"Internal",
|
||||
"Internal [${this.size}]",
|
||||
this.path.replace("\\/", ""),
|
||||
"",
|
||||
getQualityFromName(this.quality),
|
||||
)
|
||||
}
|
||||
|
||||
// No childmode when getting links
|
||||
// New api does not return video links :(
|
||||
val query = if (type == ResponseTypes.Movies.value) {
|
||||
"""{"childmode":"0","uid":"","app_version":"11.5","appid":"$appId","module":"Movie_downloadurl_v3","channel":"Website","mid":"$id","lang":"","expired_date":"${getExpiryDate()}","platform":"android","oss":"1","group":""}"""
|
||||
} else {
|
||||
"""{"childmode":"0","app_version":"11.5","module":"TV_downloadurl_v3","channel":"Website","episode":"$episode","expired_date":"${getExpiryDate()}","platform":"android","tid":"$id","oss":"1","uid":"","appid":"$appId","season":"$season","lang":"en","group":""}"""
|
||||
}
|
||||
|
||||
val linkData = queryApiParsed<LinkDataProp>(query, false)
|
||||
linkData.data?.list?.forEach {
|
||||
callback.invoke(it.toExtractorLink() ?: return@forEach)
|
||||
}
|
||||
|
||||
// Should really run this query for every link :(
|
||||
val fid = linkData.data?.list?.firstOrNull { it.fid != null }?.fid
|
||||
|
||||
val subtitleQuery = if (type == ResponseTypes.Movies.value) {
|
||||
"""{"childmode":"0","fid":"$fid","uid":"","app_version":"11.5","appid":"$appId","module":"Movie_srt_list_v2","channel":"Website","mid":"$id","lang":"en","expired_date":"${getExpiryDate()}","platform":"android"}"""
|
||||
} else {
|
||||
"""{"childmode":"0","fid":"$fid","app_version":"11.5","module":"TV_srt_list_v2","channel":"Website","episode":"$episode","expired_date":"${getExpiryDate()}","platform":"android","tid":"$id","uid":"","appid":"$appId","season":"$season","lang":"en"}"""
|
||||
}
|
||||
|
||||
val subtitles = queryApiParsed<SubtitleDataProp>(subtitleQuery).data
|
||||
subtitles?.list?.forEach { subs ->
|
||||
val sub = subs.subtitles.maxByOrNull { it.support_total ?: 0 }
|
||||
subtitleCallback.invoke(
|
||||
SubtitleFile(
|
||||
sub?.language ?: sub?.lang ?: return@forEach,
|
||||
sub?.filePath ?: return@forEach
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun invokeExternalSource(
|
||||
mediaId: Int? = null,
|
||||
type: Int? = null,
|
||||
season: Int? = null,
|
||||
episode: Int? = null,
|
||||
callback: (ExtractorLink) -> Unit,
|
||||
) {
|
||||
val (seasonSlug, episodeSlug) = getEpisodeSlug(season, episode)
|
||||
val shareKey = app.get("$fourthAPI/index/share_link?id=${mediaId}&type=$type")
|
||||
.parsedSafe<ExternalResponse>()?.data?.link?.substringAfterLast("/") ?: return
|
||||
|
||||
val headers = mapOf("Accept-Language" to "en")
|
||||
val shareRes = app.get("$thirdAPI/file/file_share_list?share_key=$shareKey", headers = headers)
|
||||
.parsedSafe<ExternalResponse>()?.data ?: return
|
||||
|
||||
val fids = if (season == null) {
|
||||
shareRes.file_list
|
||||
} else {
|
||||
val parentId = shareRes.file_list?.find { it.file_name.equals("season $season", true) }?.fid
|
||||
app.get("$thirdAPI/file/file_share_list?share_key=$shareKey&parent_id=$parentId&page=1", headers = headers)
|
||||
.parsedSafe<ExternalResponse>()?.data?.file_list?.filter {
|
||||
it.file_name?.contains("s${seasonSlug}e${episodeSlug}", true) == true
|
||||
}
|
||||
} ?: return
|
||||
|
||||
fids.apmapIndexed { index, fileList ->
|
||||
val player = app.get("$thirdAPI/file/player?fid=${fileList.fid}&share_key=$shareKey").text
|
||||
val sources = "sources\\s*=\\s*(.*);".toRegex().find(player)?.groupValues?.get(1)
|
||||
val qualities = "quality_list\\s*=\\s*(.*);".toRegex().find(player)?.groupValues?.get(1)
|
||||
listOf(sources, qualities).forEach {
|
||||
AppUtils.tryParseJson<ArrayList<ExternalSources>>(it)?.forEach org@{ source ->
|
||||
val format = if (source.type == "video/mp4") ExtractorLinkType.VIDEO else ExtractorLinkType.M3U8
|
||||
val label = if (format == ExtractorLinkType.M3U8) "Hls" else "Mp4"
|
||||
if(!(source.label == "AUTO" || format == ExtractorLinkType.VIDEO)) return@org
|
||||
callback.invoke(
|
||||
ExtractorLink(
|
||||
"External",
|
||||
"External $label [Server ${index + 1}]",
|
||||
(source.m3u8_url ?: source.file)?.replace("\\/", "/") ?: return@org,
|
||||
"",
|
||||
getIndexQuality(if (format == ExtractorLinkType.M3U8) fileList.file_name else source.label),
|
||||
type = format,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun invokeWatchsomuch(
|
||||
imdbId: String? = null,
|
||||
season: Int? = null,
|
||||
episode: Int? = null,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
) {
|
||||
val id = imdbId?.removePrefix("tt")
|
||||
val epsId = app.post(
|
||||
"$watchSomuchAPI/Watch/ajMovieTorrents.aspx",
|
||||
data = mapOf(
|
||||
"index" to "0",
|
||||
"mid" to "$id",
|
||||
"wsk" to "30fb68aa-1c71-4b8c-b5d4-4ca9222cfb45",
|
||||
"lid" to "",
|
||||
"liu" to ""
|
||||
), headers = mapOf("X-Requested-With" to "XMLHttpRequest")
|
||||
).parsedSafe<WatchsomuchResponses>()?.movie?.torrents?.let { eps ->
|
||||
if (season == null) {
|
||||
eps.firstOrNull()?.id
|
||||
} else {
|
||||
eps.find { it.episode == episode && it.season == season }?.id
|
||||
}
|
||||
} ?: return
|
||||
|
||||
val (seasonSlug, episodeSlug) = getEpisodeSlug(
|
||||
season,
|
||||
episode
|
||||
)
|
||||
|
||||
val subUrl = if (season == null) {
|
||||
"$watchSomuchAPI/Watch/ajMovieSubtitles.aspx?mid=$id&tid=$epsId&part="
|
||||
} else {
|
||||
"$watchSomuchAPI/Watch/ajMovieSubtitles.aspx?mid=$id&tid=$epsId&part=S${seasonSlug}E${episodeSlug}"
|
||||
}
|
||||
|
||||
app.get(subUrl)
|
||||
.parsedSafe<WatchsomuchSubResponses>()?.subtitles
|
||||
?.map { sub ->
|
||||
subtitleCallback.invoke(
|
||||
SubtitleFile(
|
||||
sub.label ?: "",
|
||||
fixUrl(sub.url ?: return@map null, watchSomuchAPI)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
suspend fun invokeOpenSubs(
|
||||
imdbId: String? = null,
|
||||
season: Int? = null,
|
||||
episode: Int? = null,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
) {
|
||||
val slug = if(season == null) {
|
||||
"movie/$imdbId"
|
||||
} else {
|
||||
"series/$imdbId:$season:$episode"
|
||||
}
|
||||
app.get("${openSubAPI}/subtitles/$slug.json", timeout = 120L).parsedSafe<OsResult>()?.subtitles?.map { sub ->
|
||||
subtitleCallback.invoke(
|
||||
SubtitleFile(
|
||||
SubtitleHelper.fromThreeLettersToLanguage(sub.lang ?: "") ?: sub.lang
|
||||
?: return@map,
|
||||
sub.url ?: return@map
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun invokeVidsrcto(
|
||||
imdbId: String?,
|
||||
season: Int?,
|
||||
episode: Int?,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
) {
|
||||
val url = if (season == null) {
|
||||
"$vidsrctoAPI/embed/movie/$imdbId"
|
||||
} else {
|
||||
"$vidsrctoAPI/embed/tv/$imdbId/$season/$episode"
|
||||
}
|
||||
|
||||
val mediaId = app.get(url).document.selectFirst("ul.episodes li a")?.attr("data-id") ?: return
|
||||
val subtitles = app.get("$vidsrctoAPI/ajax/embed/episode/$mediaId/subtitles").text
|
||||
AppUtils.tryParseJson<List<VidsrcSubtitles>>(subtitles)?.map {
|
||||
subtitleCallback.invoke(
|
||||
SubtitleFile(
|
||||
it.label ?: "",
|
||||
it.file ?: return@map
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun fixUrl(url: String, domain: String): String {
|
||||
if (url.startsWith("http")) {
|
||||
return url
|
||||
}
|
||||
if (url.isEmpty()) {
|
||||
return ""
|
||||
}
|
||||
|
||||
val startsWithNoHttp = url.startsWith("//")
|
||||
if (startsWithNoHttp) {
|
||||
return "https:$url"
|
||||
} else {
|
||||
if (url.startsWith('/')) {
|
||||
return domain + url
|
||||
}
|
||||
return "$domain/$url"
|
||||
}
|
||||
}
|
||||
|
||||
private fun getIndexQuality(str: String?): Int {
|
||||
return Regex("(\\d{3,4})[pP]").find(str ?: "")?.groupValues?.getOrNull(1)?.toIntOrNull()
|
||||
?: Qualities.Unknown.value
|
||||
}
|
||||
|
||||
private fun getEpisodeSlug(
|
||||
season: Int? = null,
|
||||
episode: Int? = null,
|
||||
): Pair<String, String> {
|
||||
return if (season == null && episode == null) {
|
||||
"" to ""
|
||||
} else {
|
||||
(if (season!! < 10) "0$season" else "$season") to (if (episode!! < 10) "0$episode" else "$episode")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
823
Superstream/src/main/kotlin/com/hexated/Superstream.kt
Normal file
823
Superstream/src/main/kotlin/com/hexated/Superstream.kt
Normal file
|
@ -0,0 +1,823 @@
|
|||
package com.hexated
|
||||
|
||||
import android.util.Base64
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.hexated.Extractors.invokeExternalSource
|
||||
import com.hexated.Extractors.invokeInternalSource
|
||||
import com.hexated.Extractors.invokeOpenSubs
|
||||
import com.hexated.Extractors.invokeVidsrcto
|
||||
import com.hexated.Extractors.invokeWatchsomuch
|
||||
import com.hexated.Superstream.CipherUtils.getVerify
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.APIHolder.capitalize
|
||||
import com.lagradost.cloudstream3.APIHolder.unixTime
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addImdbId
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.toJson
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.nicehttp.NiceResponse
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.security.MessageDigest
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.Cipher.DECRYPT_MODE
|
||||
import javax.crypto.Cipher.ENCRYPT_MODE
|
||||
import javax.crypto.spec.IvParameterSpec
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
open class Superstream : MainAPI() {
|
||||
private val timeout = 60L
|
||||
override var name = "SuperStream"
|
||||
override val hasMainPage = true
|
||||
override val hasChromecastSupport = true
|
||||
override val supportedTypes = setOf(
|
||||
TvType.Movie,
|
||||
TvType.TvSeries,
|
||||
TvType.Anime,
|
||||
TvType.AnimeMovie,
|
||||
)
|
||||
|
||||
enum class ResponseTypes(val value: Int) {
|
||||
Series(2),
|
||||
Movies(1);
|
||||
|
||||
fun toTvType(): TvType {
|
||||
return if (this == Series) TvType.TvSeries else TvType.Movie
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun getResponseType(value: Int?): ResponseTypes {
|
||||
return values().firstOrNull { it.value == value } ?: Movies
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override val instantLinkLoading = true
|
||||
|
||||
private val interceptor = UserAgentInterceptor()
|
||||
|
||||
private val headers = mapOf(
|
||||
"Platform" to "android",
|
||||
"Accept" to "charset=utf-8",
|
||||
)
|
||||
|
||||
private class UserAgentInterceptor : Interceptor {
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
return chain.proceed(
|
||||
chain.request()
|
||||
.newBuilder()
|
||||
.removeHeader("user-agent")
|
||||
.build()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Random 32 length string
|
||||
private fun randomToken(): String {
|
||||
return (0..31).joinToString("") {
|
||||
(('0'..'9') + ('a'..'f')).random().toString()
|
||||
}
|
||||
}
|
||||
|
||||
private val token = randomToken()
|
||||
|
||||
private object CipherUtils {
|
||||
private const val ALGORITHM = "DESede"
|
||||
private const val TRANSFORMATION = "DESede/CBC/PKCS5Padding"
|
||||
fun encrypt(str: String, key: String, iv: String): String? {
|
||||
return try {
|
||||
val cipher: Cipher = Cipher.getInstance(TRANSFORMATION)
|
||||
val bArr = ByteArray(24)
|
||||
val bytes: ByteArray = key.toByteArray()
|
||||
var length = if (bytes.size <= 24) bytes.size else 24
|
||||
System.arraycopy(bytes, 0, bArr, 0, length)
|
||||
while (length < 24) {
|
||||
bArr[length] = 0
|
||||
length++
|
||||
}
|
||||
cipher.init(
|
||||
ENCRYPT_MODE,
|
||||
SecretKeySpec(bArr, ALGORITHM),
|
||||
IvParameterSpec(iv.toByteArray())
|
||||
)
|
||||
|
||||
String(Base64.encode(cipher.doFinal(str.toByteArray()), 2), StandardCharsets.UTF_8)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
// Useful for deobfuscation
|
||||
fun decrypt(str: String, key: String, iv: String): String? {
|
||||
return try {
|
||||
val cipher: Cipher = Cipher.getInstance(TRANSFORMATION)
|
||||
val bArr = ByteArray(24)
|
||||
val bytes: ByteArray = key.toByteArray()
|
||||
var length = if (bytes.size <= 24) bytes.size else 24
|
||||
System.arraycopy(bytes, 0, bArr, 0, length)
|
||||
while (length < 24) {
|
||||
bArr[length] = 0
|
||||
length++
|
||||
}
|
||||
cipher.init(
|
||||
DECRYPT_MODE,
|
||||
SecretKeySpec(bArr, ALGORITHM),
|
||||
IvParameterSpec(iv.toByteArray())
|
||||
)
|
||||
val inputStr = Base64.decode(str.toByteArray(), Base64.DEFAULT)
|
||||
cipher.doFinal(inputStr).decodeToString()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun md5(str: String): String? {
|
||||
return MD5Util.md5(str)?.let { HexDump.toHexString(it).lowercase() }
|
||||
}
|
||||
|
||||
fun getVerify(str: String?, str2: String, str3: String): String? {
|
||||
if (str != null) {
|
||||
return md5(md5(str2) + str3 + str)
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private object HexDump {
|
||||
private val HEX_DIGITS = charArrayOf(
|
||||
'0',
|
||||
'1',
|
||||
'2',
|
||||
'3',
|
||||
'4',
|
||||
'5',
|
||||
'6',
|
||||
'7',
|
||||
'8',
|
||||
'9',
|
||||
'A',
|
||||
'B',
|
||||
'C',
|
||||
'D',
|
||||
'E',
|
||||
'F'
|
||||
)
|
||||
|
||||
@JvmOverloads
|
||||
fun toHexString(bArr: ByteArray, i: Int = 0, i2: Int = bArr.size): String {
|
||||
val cArr = CharArray(i2 * 2)
|
||||
var i3 = 0
|
||||
for (i4 in i until i + i2) {
|
||||
val b = bArr[i4].toInt()
|
||||
val i5 = i3 + 1
|
||||
val cArr2 = HEX_DIGITS
|
||||
cArr[i3] = cArr2[b ushr 4 and 15]
|
||||
i3 = i5 + 1
|
||||
cArr[i5] = cArr2[b and 15]
|
||||
}
|
||||
return String(cArr)
|
||||
}
|
||||
}
|
||||
|
||||
private object MD5Util {
|
||||
fun md5(str: String): ByteArray? {
|
||||
return md5(str.toByteArray())
|
||||
}
|
||||
|
||||
fun md5(bArr: ByteArray?): ByteArray? {
|
||||
return try {
|
||||
val digest = MessageDigest.getInstance("MD5")
|
||||
digest.update(bArr ?: return null)
|
||||
digest.digest()
|
||||
} catch (e: NoSuchAlgorithmException) {
|
||||
e.printStackTrace()
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun queryApi(query: String, useAlternativeApi: Boolean): NiceResponse {
|
||||
val encryptedQuery = CipherUtils.encrypt(query, key, iv)!!
|
||||
val appKeyHash = CipherUtils.md5(appKey)!!
|
||||
val newBody =
|
||||
"""{"app_key":"$appKeyHash","verify":"${
|
||||
getVerify(
|
||||
encryptedQuery,
|
||||
appKey,
|
||||
key
|
||||
)
|
||||
}","encrypt_data":"$encryptedQuery"}"""
|
||||
val base64Body = String(Base64.encode(newBody.toByteArray(), Base64.DEFAULT))
|
||||
|
||||
val data = mapOf(
|
||||
"data" to base64Body,
|
||||
"appid" to "27",
|
||||
"platform" to "android",
|
||||
"version" to appVersionCode,
|
||||
// Probably best to randomize this
|
||||
"medium" to "Website&token$token"
|
||||
)
|
||||
|
||||
val url = if (useAlternativeApi) secondAPI else firstAPI
|
||||
return app.post(
|
||||
url,
|
||||
headers = headers,
|
||||
data = data,
|
||||
timeout = timeout,
|
||||
interceptor = interceptor
|
||||
)
|
||||
}
|
||||
|
||||
suspend inline fun <reified T : Any> queryApiParsed(
|
||||
query: String,
|
||||
useAlternativeApi: Boolean = true
|
||||
): T {
|
||||
return queryApi(query, useAlternativeApi).parsed()
|
||||
}
|
||||
|
||||
fun getExpiryDate(): Long {
|
||||
// Current time + 12 hours
|
||||
return unixTime + 60 * 60 * 12
|
||||
}
|
||||
|
||||
private data class PostJSON(
|
||||
@JsonProperty("id") val id: Int? = null,
|
||||
@JsonProperty("title") val title: String? = null,
|
||||
@JsonProperty("poster") val poster: String? = null,
|
||||
@JsonProperty("poster_2") val poster2: String? = null,
|
||||
@JsonProperty("box_type") val boxType: Int? = null,
|
||||
@JsonProperty("imdb_rating") val imdbRating: String? = null,
|
||||
@JsonProperty("quality_tag") val quality_tag: String? = null,
|
||||
)
|
||||
|
||||
private data class ListJSON(
|
||||
@JsonProperty("code") val code: Int? = null,
|
||||
@JsonProperty("type") val type: String? = null,
|
||||
@JsonProperty("name") val name: String? = null,
|
||||
@JsonProperty("box_type") val boxType: Int? = null,
|
||||
@JsonProperty("list") val list: ArrayList<PostJSON> = arrayListOf(),
|
||||
)
|
||||
|
||||
private data class DataJSON(
|
||||
@JsonProperty("data") val data: ArrayList<ListJSON> = arrayListOf()
|
||||
)
|
||||
|
||||
// We do not want content scanners to notice this scraping going on so we've hidden all constants
|
||||
// The source has its origins in China so I added some extra security with banned words
|
||||
// Mayhaps a tiny bit unethical, but this source is just too good :)
|
||||
// If you are copying this code please use precautions so they do not change their api.
|
||||
|
||||
// Free Tibet, The Tienanmen Square protests of 1989
|
||||
private val iv = base64Decode("d0VpcGhUbiE=")
|
||||
private val key = base64Decode("MTIzZDZjZWRmNjI2ZHk1NDIzM2FhMXc2")
|
||||
|
||||
private val firstAPI = BuildConfig.SUPERSTREAM_FIRST_API
|
||||
|
||||
// Another url because the first one sucks at searching
|
||||
// This one was revealed to me in a dream
|
||||
private val secondAPI = BuildConfig.SUPERSTREAM_SECOND_API
|
||||
|
||||
val thirdAPI = BuildConfig.SUPERSTREAM_THIRD_API
|
||||
val fourthAPI = BuildConfig.SUPERSTREAM_FOURTH_API
|
||||
|
||||
val watchSomuchAPI = "https://watchsomuch.tv"
|
||||
val openSubAPI = "https://opensubtitles-v3.strem.io"
|
||||
val vidsrctoAPI = "https://vidsrc.to"
|
||||
|
||||
private val appKey = base64Decode("bW92aWVib3g=")
|
||||
val appId = base64Decode("Y29tLnRkby5zaG93Ym94")
|
||||
private val appIdSecond = base64Decode("Y29tLm1vdmllYm94cHJvLmFuZHJvaWQ=")
|
||||
private val appVersion = "11.5"
|
||||
private val appVersionCode = "129"
|
||||
|
||||
override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse {
|
||||
val hideNsfw = if (settingsForProvider.enableAdult) 0 else 1
|
||||
val data = queryApiParsed<DataJSON>(
|
||||
"""{"childmode":"$hideNsfw","app_version":"$appVersion","appid":"$appIdSecond","module":"Home_list_type_v5","channel":"Website","page":"$page","lang":"en","type":"all","pagelimit":"10","expired_date":"${getExpiryDate()}","platform":"android"}
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
// Cut off the first row (featured)
|
||||
val pages = data.data.let { it.subList(minOf(it.size, 1), it.size) }
|
||||
.mapNotNull {
|
||||
var name = it.name
|
||||
if (name.isNullOrEmpty()) name = "Featured"
|
||||
val postList = it.list.mapNotNull second@{ post ->
|
||||
val type = if (post.boxType == 1) TvType.Movie else TvType.TvSeries
|
||||
newMovieSearchResponse(
|
||||
name = post.title ?: return@second null,
|
||||
url = LoadData(post.id ?: return@mapNotNull null, post.boxType).toJson(),
|
||||
type = type,
|
||||
fix = false
|
||||
) {
|
||||
posterUrl = post.poster ?: post.poster2
|
||||
quality = getQualityFromString(post.quality_tag ?: "")
|
||||
}
|
||||
}
|
||||
if (postList.isEmpty()) return@mapNotNull null
|
||||
HomePageList(name, postList)
|
||||
}
|
||||
return HomePageResponse(pages, hasNext = !pages.any { it.list.isEmpty() })
|
||||
}
|
||||
|
||||
private data class Data(
|
||||
@JsonProperty("id") val id: Int? = null,
|
||||
@JsonProperty("mid") val mid: Int? = null,
|
||||
@JsonProperty("box_type") val boxType: Int? = null,
|
||||
@JsonProperty("title") val title: String? = null,
|
||||
@JsonProperty("poster_org") val posterOrg: String? = null,
|
||||
@JsonProperty("poster") val poster: String? = null,
|
||||
@JsonProperty("cats") val cats: String? = null,
|
||||
@JsonProperty("year") val year: Int? = null,
|
||||
@JsonProperty("imdb_rating") val imdbRating: String? = null,
|
||||
@JsonProperty("quality_tag") val qualityTag: String? = null,
|
||||
) {
|
||||
fun toSearchResponse(api: MainAPI): MovieSearchResponse? {
|
||||
return api.newMovieSearchResponse(
|
||||
this.title ?: "",
|
||||
LoadData(
|
||||
this.id ?: this.mid ?: return null,
|
||||
this.boxType ?: ResponseTypes.Movies.value
|
||||
).toJson(),
|
||||
ResponseTypes.getResponseType(this.boxType).toTvType(),
|
||||
false
|
||||
) {
|
||||
posterUrl = this@Data.posterOrg ?: this@Data.poster
|
||||
year = this@Data.year
|
||||
quality = getQualityFromString(this@Data.qualityTag?.replace("-", "") ?: "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private data class MainDataList(
|
||||
@JsonProperty("list") val list: ArrayList<Data> = arrayListOf()
|
||||
)
|
||||
|
||||
private data class MainData(
|
||||
@JsonProperty("data") val data: MainDataList
|
||||
)
|
||||
|
||||
override suspend fun search(query: String): List<SearchResponse> {
|
||||
val hideNsfw = if (settingsForProvider.enableAdult) 0 else 1
|
||||
val apiQuery =
|
||||
// Originally 8 pagelimit
|
||||
"""{"childmode":"$hideNsfw","app_version":"$appVersion","appid":"$appIdSecond","module":"Search4","channel":"Website","page":"1","lang":"en","type":"all","keyword":"$query","pagelimit":"20","expired_date":"${getExpiryDate()}","platform":"android"}"""
|
||||
val searchResponse = queryApiParsed<MainData>(apiQuery, true).data.list.mapNotNull {
|
||||
it.toSearchResponse(this)
|
||||
}
|
||||
return searchResponse
|
||||
}
|
||||
|
||||
private data class LoadData(
|
||||
val id: Int,
|
||||
val type: Int?
|
||||
)
|
||||
|
||||
private data class MovieData(
|
||||
@JsonProperty("id") val id: Int? = null,
|
||||
@JsonProperty("title") val title: String? = null,
|
||||
@JsonProperty("director") val director: String? = null,
|
||||
@JsonProperty("writer") val writer: String? = null,
|
||||
@JsonProperty("actors") val actors: String? = null,
|
||||
@JsonProperty("runtime") val runtime: Int? = null,
|
||||
@JsonProperty("poster") val poster: String? = null,
|
||||
@JsonProperty("description") val description: String? = null,
|
||||
@JsonProperty("cats") val cats: String? = null,
|
||||
@JsonProperty("year") val year: Int? = null,
|
||||
@JsonProperty("imdb_id") val imdbId: String? = null,
|
||||
@JsonProperty("imdb_rating") val imdbRating: String? = null,
|
||||
@JsonProperty("trailer") val trailer: String? = null,
|
||||
@JsonProperty("released") val released: String? = null,
|
||||
@JsonProperty("content_rating") val contentRating: String? = null,
|
||||
@JsonProperty("tmdb_id") val tmdbId: Int? = null,
|
||||
@JsonProperty("tomato_meter") val tomatoMeter: Int? = null,
|
||||
@JsonProperty("poster_org") val posterOrg: String? = null,
|
||||
@JsonProperty("trailer_url") val trailerUrl: String? = null,
|
||||
@JsonProperty("imdb_link") val imdbLink: String? = null,
|
||||
@JsonProperty("box_type") val boxType: Int? = null,
|
||||
@JsonProperty("recommend") val recommend: List<Data> = listOf(),
|
||||
)
|
||||
|
||||
private data class MovieDataProp(
|
||||
@JsonProperty("data") val data: MovieData? = MovieData()
|
||||
)
|
||||
|
||||
|
||||
private data class SeriesDataProp(
|
||||
@JsonProperty("code") val code: Int? = null,
|
||||
@JsonProperty("msg") val msg: String? = null,
|
||||
@JsonProperty("data") val data: SeriesData? = SeriesData()
|
||||
)
|
||||
|
||||
private data class SeriesSeasonProp(
|
||||
@JsonProperty("code") val code: Int? = null,
|
||||
@JsonProperty("msg") val msg: String? = null,
|
||||
@JsonProperty("data") val data: ArrayList<SeriesEpisode>? = arrayListOf()
|
||||
)
|
||||
// data class PlayProgress (
|
||||
//
|
||||
// @JsonProperty("over" ) val over : Int? = null,
|
||||
// @JsonProperty("seconds" ) val seconds : Int? = null,
|
||||
// @JsonProperty("mp4_id" ) val mp4Id : Int? = null,
|
||||
// @JsonProperty("last_time" ) val lastTime : Int? = null
|
||||
//
|
||||
//)
|
||||
|
||||
private data class SeriesEpisode(
|
||||
@JsonProperty("id") val id: Int? = null,
|
||||
@JsonProperty("tid") val tid: Int? = null,
|
||||
@JsonProperty("mb_id") val mbId: Int? = null,
|
||||
@JsonProperty("imdb_id") val imdbId: String? = null,
|
||||
@JsonProperty("imdb_id_status") val imdbIdStatus: Int? = null,
|
||||
@JsonProperty("srt_status") val srtStatus: Int? = null,
|
||||
@JsonProperty("season") val season: Int? = null,
|
||||
@JsonProperty("episode") val episode: Int? = null,
|
||||
@JsonProperty("state") val state: Int? = null,
|
||||
@JsonProperty("title") val title: String? = null,
|
||||
@JsonProperty("thumbs") val thumbs: String? = null,
|
||||
@JsonProperty("thumbs_bak") val thumbsBak: String? = null,
|
||||
@JsonProperty("thumbs_original") val thumbsOriginal: String? = null,
|
||||
@JsonProperty("poster_imdb") val posterImdb: Int? = null,
|
||||
@JsonProperty("synopsis") val synopsis: String? = null,
|
||||
@JsonProperty("runtime") val runtime: Int? = null,
|
||||
@JsonProperty("view") val view: Int? = null,
|
||||
@JsonProperty("download") val download: Int? = null,
|
||||
@JsonProperty("source_file") val sourceFile: Int? = null,
|
||||
@JsonProperty("code_file") val codeFile: Int? = null,
|
||||
@JsonProperty("add_time") val addTime: Int? = null,
|
||||
@JsonProperty("update_time") val updateTime: Int? = null,
|
||||
@JsonProperty("released") val released: String? = null,
|
||||
@JsonProperty("released_timestamp") val releasedTimestamp: Long? = null,
|
||||
@JsonProperty("audio_lang") val audioLang: String? = null,
|
||||
@JsonProperty("quality_tag") val qualityTag: String? = null,
|
||||
@JsonProperty("3d") val _3d: Int? = null,
|
||||
@JsonProperty("remark") val remark: String? = null,
|
||||
@JsonProperty("pending") val pending: String? = null,
|
||||
@JsonProperty("imdb_rating") val imdbRating: String? = null,
|
||||
@JsonProperty("display") val display: Int? = null,
|
||||
@JsonProperty("sync") val sync: Int? = null,
|
||||
@JsonProperty("tomato_meter") val tomatoMeter: Int? = null,
|
||||
@JsonProperty("tomato_meter_count") val tomatoMeterCount: Int? = null,
|
||||
@JsonProperty("tomato_audience") val tomatoAudience: Int? = null,
|
||||
@JsonProperty("tomato_audience_count") val tomatoAudienceCount: Int? = null,
|
||||
@JsonProperty("thumbs_min") val thumbsMin: String? = null,
|
||||
@JsonProperty("thumbs_org") val thumbsOrg: String? = null,
|
||||
@JsonProperty("imdb_link") val imdbLink: String? = null,
|
||||
// @JsonProperty("quality_tags") val qualityTags: ArrayList<String> = arrayListOf(),
|
||||
// @JsonProperty("play_progress" ) val playProgress : PlayProgress? = PlayProgress()
|
||||
|
||||
)
|
||||
|
||||
private data class SeriesLanguage(
|
||||
@JsonProperty("title") val title: String? = null,
|
||||
@JsonProperty("lang") val lang: String? = null
|
||||
)
|
||||
|
||||
private data class SeriesData(
|
||||
@JsonProperty("id") val id: Int? = null,
|
||||
@JsonProperty("mb_id") val mbId: Int? = null,
|
||||
@JsonProperty("title") val title: String? = null,
|
||||
@JsonProperty("display") val display: Int? = null,
|
||||
@JsonProperty("state") val state: Int? = null,
|
||||
@JsonProperty("vip_only") val vipOnly: Int? = null,
|
||||
@JsonProperty("code_file") val codeFile: Int? = null,
|
||||
@JsonProperty("director") val director: String? = null,
|
||||
@JsonProperty("writer") val writer: String? = null,
|
||||
@JsonProperty("actors") val actors: String? = null,
|
||||
@JsonProperty("add_time") val addTime: Int? = null,
|
||||
@JsonProperty("poster") val poster: String? = null,
|
||||
@JsonProperty("poster_imdb") val posterImdb: Int? = null,
|
||||
@JsonProperty("banner_mini") val bannerMini: String? = null,
|
||||
@JsonProperty("description") val description: String? = null,
|
||||
@JsonProperty("imdb_id") val imdbId: String? = null,
|
||||
@JsonProperty("cats") val cats: String? = null,
|
||||
@JsonProperty("year") val year: Int? = null,
|
||||
@JsonProperty("collect") val collect: Int? = null,
|
||||
@JsonProperty("view") val view: Int? = null,
|
||||
@JsonProperty("download") val download: Int? = null,
|
||||
@JsonProperty("update_time") val updateTime: String? = null,
|
||||
@JsonProperty("released") val released: String? = null,
|
||||
@JsonProperty("released_timestamp") val releasedTimestamp: Int? = null,
|
||||
@JsonProperty("episode_released") val episodeReleased: String? = null,
|
||||
@JsonProperty("episode_released_timestamp") val episodeReleasedTimestamp: Int? = null,
|
||||
@JsonProperty("max_season") val maxSeason: Int? = null,
|
||||
@JsonProperty("max_episode") val maxEpisode: Int? = null,
|
||||
@JsonProperty("remark") val remark: String? = null,
|
||||
@JsonProperty("imdb_rating") val imdbRating: String? = null,
|
||||
@JsonProperty("content_rating") val contentRating: String? = null,
|
||||
@JsonProperty("tmdb_id") val tmdbId: Int? = null,
|
||||
@JsonProperty("tomato_url") val tomatoUrl: String? = null,
|
||||
@JsonProperty("tomato_meter") val tomatoMeter: Int? = null,
|
||||
@JsonProperty("tomato_meter_count") val tomatoMeterCount: Int? = null,
|
||||
@JsonProperty("tomato_meter_state") val tomatoMeterState: String? = null,
|
||||
@JsonProperty("reelgood_url") val reelgoodUrl: String? = null,
|
||||
@JsonProperty("audience_score") val audienceScore: Int? = null,
|
||||
@JsonProperty("audience_score_count") val audienceScoreCount: Int? = null,
|
||||
@JsonProperty("no_tomato_url") val noTomatoUrl: Int? = null,
|
||||
@JsonProperty("order_year") val orderYear: Int? = null,
|
||||
@JsonProperty("episodate_id") val episodateId: String? = null,
|
||||
@JsonProperty("weights_day") val weightsDay: Double? = null,
|
||||
@JsonProperty("poster_min") val posterMin: String? = null,
|
||||
@JsonProperty("poster_org") val posterOrg: String? = null,
|
||||
@JsonProperty("banner_mini_min") val bannerMiniMin: String? = null,
|
||||
@JsonProperty("banner_mini_org") val bannerMiniOrg: String? = null,
|
||||
@JsonProperty("trailer_url") val trailerUrl: String? = null,
|
||||
@JsonProperty("years") val years: ArrayList<Int> = arrayListOf(),
|
||||
@JsonProperty("season") val season: ArrayList<Int> = arrayListOf(),
|
||||
@JsonProperty("history") val history: ArrayList<String> = arrayListOf(),
|
||||
@JsonProperty("imdb_link") val imdbLink: String? = null,
|
||||
@JsonProperty("episode") val episode: ArrayList<SeriesEpisode> = arrayListOf(),
|
||||
// @JsonProperty("is_collect") val isCollect: Int? = null,
|
||||
@JsonProperty("language") val language: ArrayList<SeriesLanguage> = arrayListOf(),
|
||||
@JsonProperty("box_type") val boxType: Int? = null,
|
||||
@JsonProperty("year_year") val yearYear: String? = null,
|
||||
@JsonProperty("season_episode") val seasonEpisode: String? = null
|
||||
)
|
||||
|
||||
|
||||
override suspend fun load(url: String): LoadResponse {
|
||||
val loadData = parseJson<LoadData>(url)
|
||||
// val module = if(type === "TvType.Movie") "Movie_detail" else "*tv series module*"
|
||||
|
||||
val isMovie = loadData.type == ResponseTypes.Movies.value
|
||||
val hideNsfw = if (settingsForProvider.enableAdult) 0 else 1
|
||||
if (isMovie) { // 1 = Movie
|
||||
val apiQuery =
|
||||
"""{"childmode":"$hideNsfw","uid":"","app_version":"$appVersion","appid":"$appIdSecond","module":"Movie_detail","channel":"Website","mid":"${loadData.id}","lang":"en","expired_date":"${getExpiryDate()}","platform":"android","oss":"","group":""}"""
|
||||
val data = (queryApiParsed<MovieDataProp>(apiQuery)).data
|
||||
?: throw RuntimeException("API error")
|
||||
|
||||
return newMovieLoadResponse(
|
||||
data.title ?: "",
|
||||
url,
|
||||
TvType.Movie,
|
||||
LinkData(
|
||||
data.id ?: throw RuntimeException("No movie ID"),
|
||||
ResponseTypes.Movies.value,
|
||||
null,
|
||||
null,
|
||||
data.id,
|
||||
data.imdbId
|
||||
),
|
||||
) {
|
||||
this.recommendations =
|
||||
data.recommend.mapNotNull { it.toSearchResponse(this@Superstream) }
|
||||
this.posterUrl = data.posterOrg ?: data.poster
|
||||
this.year = data.year
|
||||
this.plot = data.description
|
||||
this.tags = data.cats?.split(",")?.map { it.capitalize() }
|
||||
this.rating = data.imdbRating?.split("/")?.get(0)?.toIntOrNull()
|
||||
addTrailer(data.trailerUrl)
|
||||
this.addImdbId(data.imdbId)
|
||||
}
|
||||
} else { // 2 Series
|
||||
val apiQuery =
|
||||
"""{"childmode":"$hideNsfw","uid":"","app_version":"$appVersion","appid":"$appIdSecond","module":"TV_detail_1","display_all":"1","channel":"Website","lang":"en","expired_date":"${getExpiryDate()}","platform":"android","tid":"${loadData.id}"}"""
|
||||
val data = (queryApiParsed<SeriesDataProp>(apiQuery)).data
|
||||
?: throw RuntimeException("API error")
|
||||
|
||||
val episodes = data.season.mapNotNull {
|
||||
val seasonQuery =
|
||||
"""{"childmode":"$hideNsfw","app_version":"$appVersion","year":"0","appid":"$appIdSecond","module":"TV_episode","display_all":"1","channel":"Website","season":"$it","lang":"en","expired_date":"${getExpiryDate()}","platform":"android","tid":"${loadData.id}"}"""
|
||||
(queryApiParsed<SeriesSeasonProp>(seasonQuery)).data
|
||||
}.flatten()
|
||||
|
||||
return newTvSeriesLoadResponse(
|
||||
data.title ?: "",
|
||||
url,
|
||||
TvType.TvSeries,
|
||||
episodes.mapNotNull {
|
||||
Episode(
|
||||
LinkData(
|
||||
it.tid ?: it.id ?: return@mapNotNull null,
|
||||
ResponseTypes.Series.value,
|
||||
it.season,
|
||||
it.episode,
|
||||
data.id,
|
||||
data.imdbId
|
||||
).toJson(),
|
||||
it.title,
|
||||
it.season,
|
||||
it.episode,
|
||||
it.thumbs ?: it.thumbsBak ?: it.thumbsMin ?: it.thumbsOriginal
|
||||
?: it.thumbsOrg,
|
||||
it.imdbRating?.toDoubleOrNull()?.times(10)?.roundToInt(),
|
||||
it.synopsis,
|
||||
it.releasedTimestamp
|
||||
)
|
||||
}
|
||||
) {
|
||||
this.year = data.year
|
||||
this.plot = data.description
|
||||
this.posterUrl = data.posterOrg ?: data.poster
|
||||
this.rating = data.imdbRating?.split("/")?.get(0)?.toIntOrNull()
|
||||
this.tags = data.cats?.split(",")?.map { it.capitalize() }
|
||||
this.addImdbId(data.imdbId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private data class LinkData(
|
||||
val id: Int,
|
||||
val type: Int,
|
||||
val season: Int?,
|
||||
val episode: Int?,
|
||||
val mediaId: Int?,
|
||||
val imdbId: String?,
|
||||
)
|
||||
|
||||
data class LinkDataProp(
|
||||
@JsonProperty("code") val code: Int? = null,
|
||||
@JsonProperty("msg") val msg: String? = null,
|
||||
@JsonProperty("data") val data: ParsedLinkData? = ParsedLinkData()
|
||||
)
|
||||
|
||||
data class LinkList(
|
||||
@JsonProperty("path") val path: String? = null,
|
||||
@JsonProperty("quality") val quality: String? = null,
|
||||
@JsonProperty("real_quality") val realQuality: String? = null,
|
||||
@JsonProperty("format") val format: String? = null,
|
||||
@JsonProperty("size") val size: String? = null,
|
||||
@JsonProperty("size_bytes") val sizeBytes: Long? = null,
|
||||
@JsonProperty("count") val count: Int? = null,
|
||||
@JsonProperty("dateline") val dateline: Long? = null,
|
||||
@JsonProperty("fid") val fid: Int? = null,
|
||||
@JsonProperty("mmfid") val mmfid: Int? = null,
|
||||
@JsonProperty("h265") val h265: Int? = null,
|
||||
@JsonProperty("hdr") val hdr: Int? = null,
|
||||
@JsonProperty("filename") val filename: String? = null,
|
||||
@JsonProperty("original") val original: Int? = null,
|
||||
@JsonProperty("colorbit") val colorbit: Int? = null,
|
||||
@JsonProperty("success") val success: Int? = null,
|
||||
@JsonProperty("timeout") val timeout: Int? = null,
|
||||
@JsonProperty("vip_link") val vipLink: Int? = null,
|
||||
@JsonProperty("fps") val fps: Int? = null,
|
||||
@JsonProperty("bitstream") val bitstream: String? = null,
|
||||
@JsonProperty("width") val width: Int? = null,
|
||||
@JsonProperty("height") val height: Int? = null
|
||||
)
|
||||
|
||||
data class ParsedLinkData(
|
||||
@JsonProperty("seconds") val seconds: Int? = null,
|
||||
@JsonProperty("quality") val quality: ArrayList<String> = arrayListOf(),
|
||||
@JsonProperty("list") val list: ArrayList<LinkList> = arrayListOf()
|
||||
)
|
||||
|
||||
data class SubtitleDataProp(
|
||||
@JsonProperty("code") val code: Int? = null,
|
||||
@JsonProperty("msg") val msg: String? = null,
|
||||
@JsonProperty("data") val data: PrivateSubtitleData? = PrivateSubtitleData()
|
||||
)
|
||||
|
||||
data class Subtitles(
|
||||
@JsonProperty("sid") val sid: Int? = null,
|
||||
@JsonProperty("mid") val mid: String? = null,
|
||||
@JsonProperty("file_path") val filePath: String? = null,
|
||||
@JsonProperty("lang") val lang: String? = null,
|
||||
@JsonProperty("language") val language: String? = null,
|
||||
@JsonProperty("delay") val delay: Int? = null,
|
||||
@JsonProperty("point") val point: String? = null,
|
||||
@JsonProperty("order") val order: Int? = null,
|
||||
@JsonProperty("support_total") val support_total: Int? = null,
|
||||
@JsonProperty("admin_order") val adminOrder: Int? = null,
|
||||
@JsonProperty("myselect") val myselect: Int? = null,
|
||||
@JsonProperty("add_time") val addTime: Long? = null,
|
||||
@JsonProperty("count") val count: Int? = null
|
||||
)
|
||||
|
||||
data class SubtitleList(
|
||||
@JsonProperty("language") val language: String? = null,
|
||||
@JsonProperty("subtitles") val subtitles: ArrayList<Subtitles> = arrayListOf()
|
||||
)
|
||||
|
||||
data class PrivateSubtitleData(
|
||||
@JsonProperty("select") val select: ArrayList<String> = arrayListOf(),
|
||||
@JsonProperty("list") val list: ArrayList<SubtitleList> = arrayListOf()
|
||||
)
|
||||
|
||||
override suspend fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
|
||||
val parsed = parseJson<LinkData>(data)
|
||||
|
||||
argamap(
|
||||
{
|
||||
invokeVidsrcto(
|
||||
parsed.imdbId,
|
||||
parsed.season,
|
||||
parsed.episode,
|
||||
subtitleCallback
|
||||
)
|
||||
},
|
||||
{
|
||||
invokeExternalSource(
|
||||
parsed.mediaId,
|
||||
parsed.type,
|
||||
parsed.season,
|
||||
parsed.episode,
|
||||
callback
|
||||
)
|
||||
},
|
||||
{
|
||||
invokeInternalSource(
|
||||
parsed.id,
|
||||
parsed.type,
|
||||
parsed.season,
|
||||
parsed.episode,
|
||||
subtitleCallback,
|
||||
callback
|
||||
)
|
||||
},
|
||||
{
|
||||
invokeOpenSubs(
|
||||
parsed.imdbId,
|
||||
parsed.season,
|
||||
parsed.episode,
|
||||
subtitleCallback
|
||||
)
|
||||
},
|
||||
{
|
||||
invokeWatchsomuch(
|
||||
parsed.imdbId,
|
||||
parsed.season,
|
||||
parsed.episode,
|
||||
subtitleCallback
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
data class ExternalResponse(
|
||||
@JsonProperty("data") val data: Data? = null,
|
||||
) {
|
||||
data class Data(
|
||||
@JsonProperty("link") val link: String? = null,
|
||||
@JsonProperty("file_list") val file_list: ArrayList<FileList>? = arrayListOf(),
|
||||
) {
|
||||
data class FileList(
|
||||
@JsonProperty("fid") val fid: Long? = null,
|
||||
@JsonProperty("file_name") val file_name: String? = null,
|
||||
@JsonProperty("oss_fid") val oss_fid: Long? = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class ExternalSources(
|
||||
@JsonProperty("m3u8_url") val m3u8_url: String? = null,
|
||||
@JsonProperty("file") val file: String? = null,
|
||||
@JsonProperty("label") val label: String? = null,
|
||||
@JsonProperty("type") val type: String? = null,
|
||||
)
|
||||
|
||||
data class WatchsomuchTorrents(
|
||||
@JsonProperty("id") val id: Int? = null,
|
||||
@JsonProperty("movieId") val movieId: Int? = null,
|
||||
@JsonProperty("season") val season: Int? = null,
|
||||
@JsonProperty("episode") val episode: Int? = null,
|
||||
)
|
||||
|
||||
data class WatchsomuchMovies(
|
||||
@JsonProperty("torrents") val torrents: ArrayList<WatchsomuchTorrents>? = arrayListOf(),
|
||||
)
|
||||
|
||||
data class WatchsomuchResponses(
|
||||
@JsonProperty("movie") val movie: WatchsomuchMovies? = null,
|
||||
)
|
||||
|
||||
data class WatchsomuchSubtitles(
|
||||
@JsonProperty("url") val url: String? = null,
|
||||
@JsonProperty("label") val label: String? = null,
|
||||
)
|
||||
|
||||
data class WatchsomuchSubResponses(
|
||||
@JsonProperty("subtitles") val subtitles: ArrayList<WatchsomuchSubtitles>? = arrayListOf(),
|
||||
)
|
||||
|
||||
data class OsSubtitles(
|
||||
@JsonProperty("url") val url: String? = null,
|
||||
@JsonProperty("lang") val lang: String? = null,
|
||||
)
|
||||
|
||||
data class OsResult(
|
||||
@JsonProperty("subtitles") val subtitles: ArrayList<OsSubtitles>? = arrayListOf(),
|
||||
)
|
||||
|
||||
data class VidsrcSubtitles(
|
||||
@JsonProperty("label") val label: String? = null,
|
||||
@JsonProperty("file") val file: String? = null,
|
||||
)
|
||||
|
||||
}
|
||||
|
14
Superstream/src/main/kotlin/com/hexated/SuperstreamPlugin.kt
Normal file
14
Superstream/src/main/kotlin/com/hexated/SuperstreamPlugin.kt
Normal file
|
@ -0,0 +1,14 @@
|
|||
|
||||
package com.hexated
|
||||
|
||||
import com.lagradost.cloudstream3.plugins.CloudstreamPlugin
|
||||
import com.lagradost.cloudstream3.plugins.Plugin
|
||||
import android.content.Context
|
||||
|
||||
@CloudstreamPlugin
|
||||
class SuperstreamPlugin: Plugin() {
|
||||
override fun load(context: Context) {
|
||||
// All providers should be added in this manner. Please don't edit the providers list directly.
|
||||
registerMainAPI(Superstream())
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue