commit 9ea7b9e5cd152bfd011f3aced6aeb7b728c6eb6f Author: yoyzo <122361785+yoyzo@users.noreply.github.com> Date: Tue Jan 10 14:42:08 2023 +0300 Build a27d8587c01f4c78bff897c70dcb52c62e29bd27 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..38a09a3 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,60 @@ +name: Build + +# https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#concurrency +concurrency: + group: "build" + cancel-in-progress: true + +on: + push: + branches: + # choose your default branch + - master + # - main + paths-ignore: + - '*.md' + - '*.html' + - '*.js' + - '*.css' + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@master + with: + path: "src" + + - name: Checkout builds + uses: actions/checkout@master + with: + ref: "builds" + path: "builds" + + - name: Clean old builds + run: rm $GITHUB_WORKSPACE/builds/*.cs3 || true + + - name: Setup JDK 11 + uses: actions/setup-java@v1 + with: + java-version: 11 + + - name: Setup Android SDK + uses: android-actions/setup-android@v2 + + - name: Build Plugins + run: | + cd $GITHUB_WORKSPACE/src + chmod +x gradlew + ./gradlew make makePluginsJson + cp **/build/*.cs3 $GITHUB_WORKSPACE/builds + cp build/plugins.json $GITHUB_WORKSPACE/builds + - name: Push builds + run: | + cd $GITHUB_WORKSPACE/builds + git config --local user.email "actions@github.com" + git config --local user.name "GitHub Actions" + git add . + git commit --amend -m "Build $GITHUB_SHA" || exit 0 # do not error if nothing to commit + git push --force diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..41fd579 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +*.iml +.gradle +/local.properties +/.idea +.DS_Store +/build +**/build +/captures +.externalNativeBuild +.cxx +local.properties +.vscode \ No newline at end of file diff --git a/AkwamProvider.cs3 b/AkwamProvider.cs3 new file mode 100644 index 0000000..5e573a6 Binary files /dev/null and b/AkwamProvider.cs3 differ diff --git a/AkwamProvider/build.gradle.kts b/AkwamProvider/build.gradle.kts new file mode 100644 index 0000000..dffbbc6 --- /dev/null +++ b/AkwamProvider/build.gradle.kts @@ -0,0 +1,14 @@ +version = 1 + +cloudstream { + description = "" + authors = listOf( "Blatzar" ) + + language = "ar" + + status = 1 + + tvTypes = listOf( "TvSeries" , "Movie" , "Anime" , "Cartoon" ) + + iconUrl = "https://www.google.com/s2/favicons?domain=akwam.to&sz=%size%" +} \ No newline at end of file diff --git a/AkwamProvider/src/main/AndroidManifest.xml b/AkwamProvider/src/main/AndroidManifest.xml new file mode 100644 index 0000000..ba79288 --- /dev/null +++ b/AkwamProvider/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/AkwamProvider/src/main/kotlin/com/akwam/AkwamPlugin.kt b/AkwamProvider/src/main/kotlin/com/akwam/AkwamPlugin.kt new file mode 100644 index 0000000..4e8b42e --- /dev/null +++ b/AkwamProvider/src/main/kotlin/com/akwam/AkwamPlugin.kt @@ -0,0 +1,11 @@ +package com.akwam +import com.lagradost.cloudstream3.plugins.CloudstreamPlugin +import com.lagradost.cloudstream3.plugins.Plugin +import android.content.Context + +@CloudstreamPlugin +class AkwamPlugin: Plugin() { + override fun load(context: Context) { + registerMainAPI(Akwam()) + } +} \ No newline at end of file diff --git a/AkwamProvider/src/main/kotlin/com/akwam/AkwamProvider.kt b/AkwamProvider/src/main/kotlin/com/akwam/AkwamProvider.kt new file mode 100644 index 0000000..6c2b2a7 --- /dev/null +++ b/AkwamProvider/src/main/kotlin/com/akwam/AkwamProvider.kt @@ -0,0 +1,225 @@ +package com.akwam + + +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.LoadResponse.Companion.addActors +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.Qualities +import org.jsoup.nodes.Element + +class Akwam : MainAPI() { + override var lang = "ar" + override var mainUrl = "https://akwam.to" + override var name = "Akwam" + override val usesWebView = false + override val hasMainPage = true + override val supportedTypes = setOf(TvType.TvSeries, TvType.Movie, TvType.Anime, TvType.Cartoon) + + private fun Element.toSearchResponse(): SearchResponse? { + val url = select("a.box").attr("href") ?: return null + if (url.contains("/games/") || url.contains("/programs/")) return null + val poster = select("picture > img") + val title = poster.attr("alt") + val posterUrl = poster.attr("data-src") + val year = select(".badge-secondary").text().toIntOrNull() + + // If you need to differentiate use the url. + return MovieSearchResponse( + title, + url, + this@Akwam.name, + TvType.TvSeries, + posterUrl, + year, + null, + ) + } + override val mainPage = mainPageOf( + "$mainUrl/movies?page=" to "Movies", + "$mainUrl/series?page=" to "Series", + "$mainUrl/shows?page=" to "Shows" + ) + + override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse { + val doc = app.get(request.data + page).document + val list = doc.select("div.col-lg-auto.col-md-4.col-6.mb-12").mapNotNull { element -> + element.toSearchResponse() + } + return newHomePageResponse(request.name, list) + } + + override suspend fun search(query: String): List { + val url = "$mainUrl/search?q=$query" + val doc = app.get(url).document + return doc.select("div.col-lg-auto").mapNotNull { + it.toSearchResponse() + } + } + + private fun String.getIntFromText(): Int? { + return Regex("""\d+""").find(this)?.groupValues?.firstOrNull()?.toIntOrNull() + } + + private fun Element.toEpisode(): Episode { + val a = select("a.text-white") + val url = a.attr("href") + val title = a.text() + val thumbUrl = select("picture > img").attr("src") + val date = select("p.entry-date").text() + return newEpisode(url) { + name = title + episode = title.getIntFromText() + posterUrl = thumbUrl + addDate(date) + } + } + + + override suspend fun load(url: String): LoadResponse { + val doc = app.get(url).document + val mesEl = doc.select("#downloads > h2 > span").isNotEmpty() + val mesSt = if(mesEl) true else false + val isMovie = mesSt//url.contains("/movie/") + val title = doc.select("h1.entry-title").text() + val posterUrl = doc.select("picture > img").attr("src") + + val year = + doc.select("div.font-size-16.text-white.mt-2").firstOrNull { + it.text().contains("السنة") + }?.text()?.getIntFromText() + + // A bit iffy to parse twice like this, but it'll do. + val duration = + doc.select("div.font-size-16.text-white.mt-2").firstOrNull { + it.text().contains("مدة الفيلم") + }?.text()?.getIntFromText() + + val synopsis = doc.select("div.widget-body p:first-child").text() + + val rating = doc.select("span.mx-2").text().split("/").lastOrNull()?.toRatingInt() + + val tags = doc.select("div.font-size-16.d-flex.align-items-center.mt-3 > a").map { + it.text() + } + + val actors = doc.select("div.widget-body > div > div.entry-box > a").mapNotNull { + val name = it?.selectFirst("div > .entry-title")?.text() ?: return@mapNotNull null + val image = it.selectFirst("div > img")?.attr("src") ?: return@mapNotNull null + Actor(name, image) + } + + + + val recommendations = + doc.select("div > div.widget-body > div.row > div > div.entry-box").mapNotNull { + val recTitle = it?.selectFirst("div.entry-body > .entry-title > .text-white") + ?: return@mapNotNull null + val href = recTitle.attr("href") ?: return@mapNotNull null + val name = recTitle.text() ?: return@mapNotNull null + val poster = it.selectFirst(".entry-image > a > picture > img")?.attr("data-src") + ?: return@mapNotNull null + MovieSearchResponse(name, href, this.name, TvType.Movie, fixUrl(poster)) + } + + return if (isMovie) { + newMovieLoadResponse( + title, + url, + TvType.Movie, + url + ) { + this.posterUrl = posterUrl + this.year = year + this.plot = synopsis + this.rating = rating + this.tags = tags + this.duration = duration + this.recommendations = recommendations + addActors(actors) + } + } else { + val episodes = doc.select("div.bg-primary2.p-4.col-lg-4.col-md-6.col-12").map { + it.toEpisode() + }.let { + val isReversed = (it.lastOrNull()?.episode ?: 1) < (it.firstOrNull()?.episode ?: 0) + if (isReversed) + it.reversed() + else it + } + + newTvSeriesLoadResponse(title, url, TvType.TvSeries, episodes) { + this.duration = duration + this.posterUrl = posterUrl + this.tags = tags.filterNotNull() + this.rating = rating + this.year = year + this.plot = synopsis + this.recommendations = recommendations + addActors(actors) + } + } + } + + +// // Maybe possible to not use the url shortener but cba investigating that. +// private suspend fun skipUrlShortener(url: String): AppResponse { +// return app.get(app.get(url).document.select("a.download-link").attr("href")) +// } + + private fun getQualityFromId(id: Int?): Qualities { + return when (id) { + 2 -> Qualities.P360 // Extrapolated + 3 -> Qualities.P480 + 4 -> Qualities.P720 + 5 -> Qualities.P1080 + else -> Qualities.Unknown + } + } + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + val doc = app.get(data).document + + val links = doc.select("div.tab-content.quality").map { element -> + val quality = getQualityFromId(element.attr("id").getIntFromText()) + element.select(".col-lg-6 > a:contains(تحميل)").map { linkElement -> + if (linkElement.attr("href").contains("/download/")) { + Pair( + linkElement.attr("href"), + quality, + ) + } else { + val url = "$mainUrl/download${ + linkElement.attr("href").split("/link")[1] + }${data.split("/movie|/episode|/shows|/show/episode".toRegex())[1]}" + Pair( + url, + quality, + ) + // just in case if they add the shorts urls again + } + } + }.flatten() + + links.map { + val linkDoc = app.get(it.first).document + val button = linkDoc.select("div.btn-loader > a") + val url = button.attr("href") + + callback.invoke( + ExtractorLink( + this.name, + this.name, + url, + this.mainUrl, + it.second.value + ) + ) + } + return true + } +} diff --git a/Anime4upPack.cs3 b/Anime4upPack.cs3 new file mode 100644 index 0000000..94cf128 Binary files /dev/null and b/Anime4upPack.cs3 differ diff --git a/Anime4upPack/build.gradle.kts b/Anime4upPack/build.gradle.kts new file mode 100644 index 0000000..c7425b6 --- /dev/null +++ b/Anime4upPack/build.gradle.kts @@ -0,0 +1,14 @@ +version = 1 + +cloudstream { + description = "This pack contains Anime4up and Witanime" + authors = listOf( "ImZaw" ) + + language = "ar" + + status = 1 + + tvTypes = listOf( "Anime", "AnimeMovie", "Others" ) + + iconUrl = "https://www.google.com/s2/favicons?domain=anime4up.tv&sz=%size%" +} \ No newline at end of file diff --git a/Anime4upPack/src/main/AndroidManifest.xml b/Anime4upPack/src/main/AndroidManifest.xml new file mode 100644 index 0000000..d2fc28b --- /dev/null +++ b/Anime4upPack/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/Anime4upPack/src/main/kotlin/com/anime4up/Anime4upPlugin.kt b/Anime4upPack/src/main/kotlin/com/anime4up/Anime4upPlugin.kt new file mode 100644 index 0000000..bad110a --- /dev/null +++ b/Anime4upPack/src/main/kotlin/com/anime4up/Anime4upPlugin.kt @@ -0,0 +1,13 @@ +package com.anime4up + +import com.lagradost.cloudstream3.plugins.CloudstreamPlugin +import com.lagradost.cloudstream3.plugins.Plugin +import android.content.Context + +@CloudstreamPlugin +class Anime4upPlugin: Plugin() { + override fun load(context: Context) { + registerMainAPI(Anime4up()) + registerMainAPI(WitAnime()) + } +} \ No newline at end of file diff --git a/Anime4upPack/src/main/kotlin/com/anime4up/Anime4upProvider.kt b/Anime4upPack/src/main/kotlin/com/anime4up/Anime4upProvider.kt new file mode 100644 index 0000000..1275417 --- /dev/null +++ b/Anime4upPack/src/main/kotlin/com/anime4up/Anime4upProvider.kt @@ -0,0 +1,167 @@ +package com.anime4up + +import com.fasterxml.jackson.annotation.JsonProperty +import com.lagradost.cloudstream3.utils.AppUtils.parseJson +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.LoadResponse.Companion.addMalId +import com.lagradost.cloudstream3.utils.ExtractorLink +import okio.ByteString.Companion.decodeBase64 +import com.lagradost.cloudstream3.utils.loadExtractor +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import java.net.URL + +private fun String.getIntFromText(): Int? { + return Regex("""\d+""").find(this)?.groupValues?.firstOrNull()?.toIntOrNull() +} +class WitAnime : Anime4up() { + override var name = "WitAnime" + override var mainUrl = "https://witanime.com" +} +open class Anime4up : MainAPI() { + override var lang = "ar" + override var mainUrl = "https://anime4up.tv" + override var name = "Anime4up" + override val usesWebView = false + override val hasMainPage = true + override val supportedTypes = + setOf(TvType.Anime, TvType.AnimeMovie, TvType.OVA, TvType.Others ) + + + private fun Element.toSearchResponse(): SearchResponse { + val imgElement = select("div.hover > img") + val url = select("div.hover > a").attr("href") + .replace("-%d8%a7%d9%84%d8%ad%d9%84%d9%82%d8%a9-.*".toRegex(), "") + .replace("episode", "anime") + val title = imgElement.attr("alt") + val posterUrl = imgElement.attr("src") + val typeText = select("div.anime-card-type > a").text() + val type = + if (typeText.contains("TV|Special".toRegex())) TvType.Anime + else if(typeText.contains("OVA|ONA".toRegex())) TvType.OVA + else if(typeText.contains("Movie")) TvType.AnimeMovie + else TvType.Others + return newAnimeSearchResponse( + title, + url, + type, + ) { + this.posterUrl = posterUrl + } + } + + override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse { + val doc = app.get("$mainUrl/").document + val homeList = doc.select(".page-content-container") + .mapNotNull { + val title = it.select("div.main-didget-head h3").text() + val list = + it.select("div.anime-card-container, div.episodes-card-container").map { + anime -> anime.toSearchResponse() + }.distinct() + HomePageList(title, list, isHorizontalImages = title.contains("حلقات")) + } + return newHomePageResponse(homeList, hasNext = false) + } + + override suspend fun search(query: String): List { + return app.get("$mainUrl/?search_param=animes&s=$query").document + .select("div.row.display-flex > div").mapNotNull { + it.toSearchResponse() + } + } + + override suspend fun load(url: String): LoadResponse { + val doc = app.get(url).document + + val title = doc.select("h1.anime-details-title").text() + val poster = doc.select("div.anime-thumbnail img").attr("src") + val description = doc.select("p.anime-story").text() + val year = doc.select("div.anime-info:contains(بداية العرض)").text().replace("بداية العرض: ", "").toIntOrNull() + + val typeText = doc.select(".anime-info:contains(النوع) a").text() + val type = + if (typeText.contains("TV|Special".toRegex())) TvType.Anime + else if(typeText.contains("OVA|ONA".toRegex())) TvType.OVA + else if(typeText.contains("Movie")) TvType.AnimeMovie + else TvType.Others + + val malId = doc.select("a.anime-mal").attr("href").replace(".*e\\/|\\/.*".toRegex(),"").toIntOrNull() + + val episodes = doc.select("div#DivEpisodesList > div").apmap { + val episodeElement = it.select("h3 a") + val episodeUrl = episodeElement.attr("href") + val episodeTitle = episodeElement.text() + val posterUrl = it.select(".hover img").attr("src") + Episode( + episodeUrl, + episodeTitle, + episode = episodeTitle.getIntFromText(), + posterUrl = posterUrl + ) + } + return newAnimeLoadResponse(title, url, type) { + this.apiName = this@Anime4up.name + addMalId(malId) + engName = title + posterUrl = poster + this.year = year + addEpisodes(if(title.contains("مدبلج")) DubStatus.Dubbed else DubStatus.Subbed, episodes) + plot = description + this.rating = rating + + } + } + data class sources ( + @JsonProperty("hd" ) var hd : Map? = null, + @JsonProperty("fhd" ) var fhd : Map? = null, + @JsonProperty("sd" ) var sd : Map? = null + ) + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + val doc = app.get(data).document + if(data.contains("anime4up")) { + val watchJSON = parseJson(doc.select("input[name=\"wl\"]").attr("value").decodeBase64()?.utf8() ?: "") + watchJSON.let { source -> + source.fhd?.apmap { + loadExtractor(it.value, data, subtitleCallback, callback) + } + source.hd?.apmap { + loadExtractor(it.value, data, subtitleCallback, callback) + } + source.sd?.apmap { + loadExtractor(it.value, data, subtitleCallback, callback) + } + } + val moshahdaID = doc.select("input[name=\"moshahda\"]").attr("value").decodeBase64()?.utf8() ?: "" + if(moshahdaID.isNotEmpty()) { + mapOf( + "Original" to "download_o", + "720" to "download_x", + "480" to "download_h", + "360" to "download_n", + "240" to "download_l" + ).apmap { (quality, qualityCode) -> + callback.invoke( + ExtractorLink( + this.name, + this.name + " Moshahda", + "https://moshahda.net/$moshahdaID.html?${qualityCode}", + "https://moshahda.net", + quality.toIntOrNull() ?: 1080 + ) + ) } + } + } else if(data.contains("witanime")) { // witanime + doc.select("ul#episode-servers li a").apmap { + loadExtractor(it.attr("data-ep-url"), data, subtitleCallback, callback) + } + } + return true + } +} diff --git a/AnimeBlkomProvider.cs3 b/AnimeBlkomProvider.cs3 new file mode 100644 index 0000000..99ac72b Binary files /dev/null and b/AnimeBlkomProvider.cs3 differ diff --git a/AnimeBlkomProvider/build.gradle.kts b/AnimeBlkomProvider/build.gradle.kts new file mode 100644 index 0000000..d22dcae --- /dev/null +++ b/AnimeBlkomProvider/build.gradle.kts @@ -0,0 +1,14 @@ +version = 2 + +cloudstream { + description = "" + authors = listOf( "ImZaw" ) + + language = "ar" + + status = 1 + + tvTypes = listOf( "Anime" , "AnimeMovie" , "OVA" ) + + iconUrl = "https://www.google.com/s2/favicons?domain=animeblkom.net&sz=%size%" +} \ No newline at end of file diff --git a/AnimeBlkomProvider/src/main/AndroidManifest.xml b/AnimeBlkomProvider/src/main/AndroidManifest.xml new file mode 100644 index 0000000..2887205 --- /dev/null +++ b/AnimeBlkomProvider/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/AnimeBlkomProvider/src/main/kotlin/com/animeblkom/AnimeBlkomPlugin.kt b/AnimeBlkomProvider/src/main/kotlin/com/animeblkom/AnimeBlkomPlugin.kt new file mode 100644 index 0000000..5278e84 --- /dev/null +++ b/AnimeBlkomProvider/src/main/kotlin/com/animeblkom/AnimeBlkomPlugin.kt @@ -0,0 +1,11 @@ +package com.animeblkom +import com.lagradost.cloudstream3.plugins.CloudstreamPlugin +import com.lagradost.cloudstream3.plugins.Plugin +import android.content.Context + +@CloudstreamPlugin +class AnimeBlkomPlugin: Plugin() { + override fun load(context: Context) { + registerMainAPI(AnimeBlkom()) + } +} \ No newline at end of file diff --git a/AnimeBlkomProvider/src/main/kotlin/com/animeblkom/AnimeBlkomProvider.kt b/AnimeBlkomProvider/src/main/kotlin/com/animeblkom/AnimeBlkomProvider.kt new file mode 100644 index 0000000..2da8483 --- /dev/null +++ b/AnimeBlkomProvider/src/main/kotlin/com/animeblkom/AnimeBlkomProvider.kt @@ -0,0 +1,164 @@ +package com.animeblkom + + +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.LoadResponse.Companion.addMalId +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.Qualities +import com.lagradost.cloudstream3.utils.loadExtractor +import org.jsoup.nodes.Element + +class AnimeBlkom : MainAPI() { + override var mainUrl = "https://animeblkom.net" + override var name = "AnimeBlkom" + override var lang = "ar" + override val hasMainPage = true + + override val supportedTypes = setOf( + TvType.Anime, + TvType.AnimeMovie, + TvType.OVA, + ) + + private fun Element.toSearchResponse(): SearchResponse { + val url = select("div.poster a").attr("href") + val name = select("div.name a").text() + val poster = mainUrl + select("div.poster img").attr("data-original") + val year = select("div[title=\"سنة الانتاج\"]").text().toIntOrNull() + val episodesNumber = select("div[title=\"عدد الحلقات\"]").text().toIntOrNull() + val tvType = select("div[title=\"النوع\"]").text().let { if(it.contains("فيلم|خاصة".toRegex())) TvType.AnimeMovie else if(it.contains("أوفا|أونا".toRegex())) TvType.OVA else TvType.Anime } + return newAnimeSearchResponse( + name, + url, + tvType, + ) { + addDubStatus(false, episodesNumber) + this.year = year + this.posterUrl = poster + } + } + override val mainPage = mainPageOf( + "$mainUrl/anime-list?sort_by=rate&page=" to "Most rated", + "$mainUrl/anime-list?sort_by=created_at&page=" to "Recently added", + "$mainUrl/anime-list?states=finished&page=" to "Completed" + ) + override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse { + val doc = app.get(request.data + page).document + val list = doc.select("div.content-inner") + .mapNotNull { element -> + element.toSearchResponse() + } + return newHomePageResponse(request.name, list) + } + + override suspend fun search(query: String): List { + val q = query.replace(" ","+") + return app.get("$mainUrl/search?query=$q").document.select("div.content.ratable").map { + it.toSearchResponse() + } + } + override suspend fun load(url: String): LoadResponse { + val doc = app.get(url).document + + val title = doc.select("span h1").text().replace("\\(.*".toRegex(),"") + val poster = mainUrl + doc.select("div.poster img").attr("data-original") + val description = doc.select(".story p").text() + val genre = doc.select("p.genres a").map { + it.text() + } + val year = doc.select(".info-table div:contains(تاريخ الانتاج) span.info").text().split("-")[0].toIntOrNull() + val status = doc.select(".info-table div:contains(حالة الأنمي) span.info").text().let { if(it.contains("مستمر")) ShowStatus.Ongoing else ShowStatus.Completed } + val nativeName = doc.select("span[title=\"الاسم باليابانية\"]").text().replace(".*:".toRegex(),"") + val type = doc.select("h1 small").text().let { + if (it.contains("movie")) TvType.AnimeMovie + if (it.contains("ova|ona".toRegex())) TvType.OVA + else TvType.Anime + } + + val malId = doc.select("a.blue.cta:contains(المزيد من المعلومات)").attr("href").replace(".*e\\/|\\/.*".toRegex(),"").toInt() + val episodes = arrayListOf() + val episodeElements = doc.select(".episode-link") + if(episodeElements.isEmpty()) { + episodes.add(Episode( + url, + "Watch", + )) + } else { + episodeElements.map { + val a = it.select("a") + episodes.add(Episode( + mainUrl + a.attr("href"), + a.text().replace(":"," "), + episode = a.select("span").not(".pull-left").last()?.text()?.toIntOrNull() + )) + } + } + return newAnimeLoadResponse(title, url, type) { + addMalId(malId) + japName = nativeName + engName = title + posterUrl = poster + this.year = year + addEpisodes(DubStatus.Subbed, episodes) // TODO CHECK + plot = description + tags = genre + + showStatus = status + } + } + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + val doc = app.get(data).document + doc.select("div.item a[data-src]").map { + it.attr("data-src").let { url -> + if(url.startsWith("https://animetitans.net/")) { + val iframe = app.get(url).document + callback.invoke( + ExtractorLink( + this.name, + "Animetitans " + it.text(), + iframe.select("script").last()?.data()?.substringAfter("source: \"")?.substringBefore("\"").toString(), + this.mainUrl, + Qualities.Unknown.value, + isM3u8 = true + ) + ) + } else if(it.text() == "Blkom") { + val iframe = app.get(url).document + iframe.select("source").forEach { source -> + callback.invoke( + ExtractorLink( + this.name, + it.text(), + source.attr("src"), + this.mainUrl, + source.attr("res").toInt() + ) + ) + } + } else { + var sourceUrl = url + if(it.text().contains("Google")) sourceUrl = "http://gdriveplayer.to/embed2.php?link=$url" + loadExtractor(sourceUrl, mainUrl, subtitleCallback, callback) + } + } + } + doc.select(".panel .panel-body a").apmap { + println(it.text()) + callback.invoke( + ExtractorLink( + this.name, + it.attr("title") + " " + it.select("small").text() + " Download Source", + it.attr("href"), + this.mainUrl, + it.text().replace("p.*| ".toRegex(),"").toInt(), + ) + ) + } + return true + } +} \ No newline at end of file diff --git a/AnimeiatProvider.cs3 b/AnimeiatProvider.cs3 new file mode 100644 index 0000000..5b48525 Binary files /dev/null and b/AnimeiatProvider.cs3 differ diff --git a/AnimeiatProvider/build.gradle.kts b/AnimeiatProvider/build.gradle.kts new file mode 100644 index 0000000..73d67b1 --- /dev/null +++ b/AnimeiatProvider/build.gradle.kts @@ -0,0 +1,14 @@ +version = 2 + +cloudstream { + description = "" + authors = listOf( "ImZaw" ) + + language = "ar" + + status = 1 + + tvTypes = listOf( "Anime" ) + + iconUrl = "https://www.google.com/s2/favicons?domain=animeiat.tv&sz=%size%" +} \ No newline at end of file diff --git a/AnimeiatProvider/src/main/AndroidManifest.xml b/AnimeiatProvider/src/main/AndroidManifest.xml new file mode 100644 index 0000000..72204b5 --- /dev/null +++ b/AnimeiatProvider/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/AnimeiatProvider/src/main/kotlin/com/animeiat/AnimeiatPlugin.kt b/AnimeiatProvider/src/main/kotlin/com/animeiat/AnimeiatPlugin.kt new file mode 100644 index 0000000..fa3c66f --- /dev/null +++ b/AnimeiatProvider/src/main/kotlin/com/animeiat/AnimeiatPlugin.kt @@ -0,0 +1,11 @@ +package com.animeiat +import com.lagradost.cloudstream3.plugins.CloudstreamPlugin +import com.lagradost.cloudstream3.plugins.Plugin +import android.content.Context + +@CloudstreamPlugin +class AnimeiatPlugin: Plugin() { + override fun load(context: Context) { + registerMainAPI(Animeiat()) + } +} \ No newline at end of file diff --git a/AnimeiatProvider/src/main/kotlin/com/animeiat/AnimeiatProvider.kt b/AnimeiatProvider/src/main/kotlin/com/animeiat/AnimeiatProvider.kt new file mode 100644 index 0000000..230bfa3 --- /dev/null +++ b/AnimeiatProvider/src/main/kotlin/com/animeiat/AnimeiatProvider.kt @@ -0,0 +1,172 @@ +package com.animeiat + + +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.loadExtractor +import com.fasterxml.jackson.annotation.JsonProperty +import com.lagradost.cloudstream3.utils.AppUtils.parseJson +import com.lagradost.nicehttp.Requests + +class Animeiat : MainAPI() { + override var lang = "ar" + override var mainUrl = "https://api.animeiat.co/v1" + val pageUrl = "https://www.animeiat.tv" + override var name = "Animeiat" + override val usesWebView = false + override val hasMainPage = true + override val supportedTypes = + setOf(TvType.Anime, TvType.AnimeMovie) + + data class Data ( + @JsonProperty("anime_name" ) var animeName : String? = null, + @JsonProperty("title" ) var title : String? = null, + @JsonProperty("slug" ) var slug : String? = null, + @JsonProperty("story" ) var story : String? = null, + @JsonProperty("other_names" ) var otherNames : String? = null, + @JsonProperty("total_episodes" ) var totalEpisodes : Int? = null, + @JsonProperty("number" ) var number : Int? = null, + @JsonProperty("age" ) var age : String? = null, + @JsonProperty("type" ) var type : String? = null, + @JsonProperty("status" ) var status : String? = null, + @JsonProperty("poster_path" ) var posterPath : String? = null, + ) + data class All ( + @JsonProperty("data" ) var data : ArrayList = arrayListOf(), + ) + + override val mainPage = mainPageOf( + "$mainUrl/home/sticky-episodes?page=" to "Episodes (H)", + "$mainUrl/anime?status=completed&page=" to "Completed", + "$mainUrl/anime?status=ongoing&page=" to "Ongoing", + ) + + override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse { + val json = parseJson(app.get(request.data + page).text) + val list = json.data.map { + newAnimeSearchResponse( + it.animeName ?: it.title.toString(), + mainUrl + "/anime/" + it.slug.toString().replace("-episode.*".toRegex(),""), + if (it.type == "movie") TvType.AnimeMovie else if (it.type == "tv") TvType.Anime else TvType.OVA, + ) { + addDubStatus(false, it.totalEpisodes ?: it.number) + this.otherName = it.otherNames?.split("\n")?.last() + this.posterUrl = "https://api.animeiat.co/storage/" + it.posterPath + } + } + return if(request.name.contains("(H)")) HomePageResponse( + arrayListOf(HomePageList(request.name.replace(" (H)",""), list, request.name.contains("(H)"))) + ) else newHomePageResponse(request.name, list) + } + + override suspend fun search(query: String): List { + val json = parseJson(app.get("$mainUrl/anime?q=$query").text) + return json.data.map { + newAnimeSearchResponse( + it.animeName.toString(), + mainUrl + "/anime/" + it.slug.toString(), + if(it.type == "movie") TvType.AnimeMovie else if(it.type == "tv") TvType.Anime else TvType.OVA, + ) { + addDubStatus(false, it.totalEpisodes) + this.otherName = it.otherNames?.split("\n")?.last() + this.posterUrl = "https://api.animeiat.co/storage/" + it.posterPath + } + } + + } + + data class Year ( + @JsonProperty("name" ) var name : String? = null, + + ) + data class Genres ( + @JsonProperty("name" ) var name : String? = null, + ) + data class LoadData ( + @JsonProperty("anime_name" ) var animeName : String? = null, + @JsonProperty("slug" ) var slug : String? = null, + @JsonProperty("story" ) var story : String? = null, + @JsonProperty("other_names" ) var otherNames : String? = null, + @JsonProperty("age" ) var age : String? = null, + @JsonProperty("type" ) var type : String? = null, + @JsonProperty("status" ) var status : String? = null, + @JsonProperty("poster_path" ) var posterPath : String? = null, + @JsonProperty("year" ) var year : Year? = Year(), + @JsonProperty("genres" ) var genres : ArrayList = arrayListOf(), + + ) + data class Load ( + + @JsonProperty("data" ) var data : LoadData? = LoadData() + + ) + data class Meta ( + @JsonProperty("last_page" ) var lastPage : Int? = null, + ) + data class EpisodeData ( + @JsonProperty("title" ) var title : String? = null, + @JsonProperty("slug" ) var slug : String? = null, + @JsonProperty("number" ) var number : Int? = null, + @JsonProperty("video_id" ) var videoId : Int? = null, + @JsonProperty("poster_path" ) var posterPath : String? = null, + ) + data class Episodes ( + @JsonProperty("data" ) var data : ArrayList = arrayListOf(), + @JsonProperty("meta" ) var meta : Meta = Meta() + ) + override suspend fun load(url: String): LoadResponse { + val loadSession = Requests() + val request = loadSession.get(url.replace(pageUrl, mainUrl)).text + val json = parseJson(request) + val episodes = arrayListOf() + (1..parseJson(loadSession.get("$url/episodes").text).meta.lastPage!!).map { pageNumber -> + parseJson(loadSession.get("$url/episodes?page=$pageNumber").text).data.map { + episodes.add( + Episode( + "$pageUrl/watch/"+json.data?.slug, + it.title, + null, + it.number, + "https://api.animeiat.co/storage/" + it.posterPath, + + ) + ) + } + } + return newAnimeLoadResponse(json.data?.animeName.toString(), "$pageUrl/watch/"+json.data?.slug, if(json.data?.type == "movie") TvType.AnimeMovie else if(json.data?.type == "tv") TvType.Anime else TvType.OVA) { + japName = json.data?.otherNames?.replace("\\n.*".toRegex(), "") + engName = json.data?.animeName + posterUrl = "https://api.animeiat.co/storage/" + json.data?.posterPath + this.year = json.data?.year?.name?.toIntOrNull() + addEpisodes(DubStatus.Subbed, episodes) + plot = json.data?.story + tags = json.data?.genres?.map { it.name.toString() } + this.showStatus = if(json.data?.status == "completed") ShowStatus.Completed else ShowStatus.Ongoing + } + } + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + val url = if(data.contains("-episode")) data else "$data-episode-1" + val doc = app.get(url).document + val script = doc.select("body > script").first()?.html() + val id = script?.replace(".*4\",slug:\"|\",duration:.*".toRegex(),"") + val player = app.get("$pageUrl/player/$id").document + player.select("source").map { + callback.invoke( + ExtractorLink( + this.name, + this.name, + it.attr("src"), + pageUrl, + it.attr("size").toInt(), + ) + ) + } + return true + } +} \ No newline at end of file diff --git a/ArabSeedProvider.cs3 b/ArabSeedProvider.cs3 new file mode 100644 index 0000000..7f974f2 Binary files /dev/null and b/ArabSeedProvider.cs3 differ diff --git a/ArabSeedProvider/build.gradle.kts b/ArabSeedProvider/build.gradle.kts new file mode 100644 index 0000000..2b9098c --- /dev/null +++ b/ArabSeedProvider/build.gradle.kts @@ -0,0 +1,14 @@ +version = 2 + +cloudstream { + description = "" + authors = listOf( "ImZaw" ) + + language = "ar" + + status = 1 + + tvTypes = listOf( "TvSeries" , "Movie" ) + + iconUrl = "https://www.google.com/s2/favicons?domain=arabseed.ink&sz=%size%" +} diff --git a/ArabSeedProvider/src/main/AndroidManifest.xml b/ArabSeedProvider/src/main/AndroidManifest.xml new file mode 100644 index 0000000..2a12952 --- /dev/null +++ b/ArabSeedProvider/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/ArabSeedProvider/src/main/kotlin/com/arabseed/ArabSeedPlugin.kt b/ArabSeedProvider/src/main/kotlin/com/arabseed/ArabSeedPlugin.kt new file mode 100644 index 0000000..1a2210d --- /dev/null +++ b/ArabSeedProvider/src/main/kotlin/com/arabseed/ArabSeedPlugin.kt @@ -0,0 +1,11 @@ +package com.arabseed +import com.lagradost.cloudstream3.plugins.CloudstreamPlugin +import com.lagradost.cloudstream3.plugins.Plugin +import android.content.Context + +@CloudstreamPlugin +class ArabSeedPlugin: Plugin() { + override fun load(context: Context) { + registerMainAPI(ArabSeed()) + } +} \ No newline at end of file diff --git a/ArabSeedProvider/src/main/kotlin/com/arabseed/ArabSeedProvider.kt b/ArabSeedProvider/src/main/kotlin/com/arabseed/ArabSeedProvider.kt new file mode 100644 index 0000000..40f16ab --- /dev/null +++ b/ArabSeedProvider/src/main/kotlin/com/arabseed/ArabSeedProvider.kt @@ -0,0 +1,217 @@ +package com.arabseed + +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.loadExtractor +import org.jsoup.nodes.Element + +class ArabSeed : MainAPI() { + override var lang = "ar" + override var mainUrl = "https://m5.arabseed.ink" + override var name = "ArabSeed" + override val usesWebView = false + override val hasMainPage = true + override val supportedTypes = setOf(TvType.TvSeries, TvType.Movie) + + private fun String.getIntFromText(): Int? { + return Regex("""\d+""").find(this)?.groupValues?.firstOrNull()?.toIntOrNull() + } + + + private fun Element.toSearchResponse(): SearchResponse? { + val title = select("h4").text() + val posterUrl = select("img.imgOptimzer").attr("data-image").ifEmpty { select("div.Poster img").attr("data-src") } + val tvType = if (select("span.category").text().contains("مسلسلات")) TvType.TvSeries else TvType.Movie + return MovieSearchResponse( + title, + select("a").attr("href"), + this@ArabSeed.name, + tvType, + posterUrl, + ) + } + + override val mainPage = mainPageOf( + "$mainUrl/movies/?offset=" to "Movies", + "$mainUrl/series/?offset=" to "Series", + ) + + override suspend fun getMainPage( + page: Int, + request: MainPageRequest + ): HomePageResponse { + val document = app.get(request.data + page, timeout = 120).document + val home = document.select("ul.Blocks-UL > div").mapNotNull { + it.toSearchResponse() + } + return newHomePageResponse(request.name, home) + } + + override suspend fun search(query: String): List { + val list = arrayListOf() + arrayListOf( + mainUrl to "series", + mainUrl to "movies" + ).apmap { (url, type) -> + val doc = app.post( + "$url/wp-content/themes/Elshaikh2021/Ajaxat/SearchingTwo.php", + data = mapOf("search" to query, "type" to type), + referer = mainUrl + ).document + doc.select("ul.Blocks-UL > div").mapNotNull { + it.toSearchResponse()?.let { it1 -> list.add(it1) } + } + } + return list + } + + override suspend fun load(url: String): LoadResponse { + val doc = app.get(url, timeout = 120).document + val title = doc.select("h1.Title").text().ifEmpty { doc.select("div.Title").text() } + val isMovie = title.contains("فيلم") + + val posterUrl = doc.select("div.Poster > img").let{ it.attr("data-src").ifEmpty { it.attr("src") } } + val rating = doc.select("div.RatingImdb em").text().getIntFromText() + val synopsis = doc.select("p.descrip").last()?.text() + val year = doc.select("li:contains(السنه) a").text().getIntFromText() + val tags = doc.select("li:contains(النوع) > a, li:contains(التصنيف) > a")?.map { it.text() } + + val actors = doc.select("div.WorkTeamIteM").mapNotNull { + val name = it.selectFirst("h4 > em")?.text() ?: return@mapNotNull null + val image = it.selectFirst("div.Icon img")?.attr("src") ?: return@mapNotNull null + val roleString = it.select("h4 > span").text() + val mainActor = Actor(name, image) + ActorData(actor = mainActor, roleString = roleString) + } + + val recommendations = doc.select("ul.Blocks-UL > div").mapNotNull { element -> + element.toSearchResponse() + } + + return if (isMovie) { + newMovieLoadResponse( + title, + url, + TvType.Movie, + url + ) { + this.posterUrl = posterUrl + this.recommendations = recommendations + this.plot = synopsis + this.tags = tags + this.actors = actors + this.rating = rating + this.year = year + } + } else { + val seasonList = doc.select("div.SeasonsListHolder ul > li") + val episodes = arrayListOf() + if(seasonList.isNotEmpty()) { + seasonList.apmap { season -> + app.post( + "$mainUrl/wp-content/themes/Elshaikh2021/Ajaxat/Single/Episodes.php", + data = mapOf("season" to season.attr("data-season"), "post_id" to season.attr("data-id")) + ).document.select("a").apmap { + episodes.add(Episode( + it.attr("href"), + it.text(), + season.attr("data-season")[0].toString().toIntOrNull(), + it.text().getIntFromText() + )) + } + } + } else { + doc.select("div.ContainerEpisodesList > a").apmap { + episodes.add(Episode( + it.attr("href"), + it.text(), + 0, + it.text().getIntFromText() + )) + } + } + newTvSeriesLoadResponse(title, url, TvType.TvSeries, episodes.distinct().sortedBy { it.episode }) { + this.posterUrl = posterUrl + this.tags = tags + this.plot = synopsis + this.actors = actors + this.recommendations = recommendations + this.rating = rating + this.year = year + } + } + } + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + val doc = app.get(data).document + val watchUrl = doc.select("a.watchBTn").attr("href") + val watchDoc = app.get(watchUrl, headers = mapOf("Referer" to mainUrl)).document + val indexOperators = arrayListOf() + val list: List = watchDoc.select("ul > li[data-link], ul > h3").mapIndexed { index, element -> + if(element.`is`("h3")) { + indexOperators.add(index) + element + } else element + } + var watchLinks: List>>; + if(indexOperators.isNotEmpty()) { + watchLinks = indexOperators.mapIndexed { index, it -> + var endIndex = list.size + if (index != indexOperators.size - 1) endIndex = (indexOperators[index + 1]) - 1 + list[it].text().getIntFromText() as Int to list.subList(it + 1, endIndex) as List + } + } else { + watchLinks = arrayListOf(0 to list) + } + watchLinks.apmap { (quality, links) -> + links.apmap { + val iframeUrl = it.attr("data-link") + println(iframeUrl) + if(it.text().contains("عرب سيد")) { + val sourceElement = app.get(iframeUrl).document.select("source") + callback.invoke( + ExtractorLink( + this.name, + "ArabSeed", + sourceElement.attr("src"), + data, + if(quality != 0) quality else it.text().replace(".*- ".toRegex(), "").replace("\\D".toRegex(),"").toInt(), + !sourceElement.attr("type").contains("mp4") + ) + ) + } else if (iframeUrl.contains("voe.sx")) { + val doc = app.get(iframeUrl).document + val script = doc.select("script").map { it.data() }.first { it.contains("sources") } + val m3u8 = script.substringAfter("'hls': '").substringBefore("'") + val mp4 = script.substringAfter("'mp4': '").substringBefore("'") + val voeSxquality = script.substringAfter("'video_height': ").substringBefore(",").toInt() + callback.invoke( + ExtractorLink( + this.name, + "Voe.sx m3u8", + m3u8, + data, + voeSxquality, + true + ) + ) + callback.invoke( + ExtractorLink( + this.name, + "Voe.sx mp4", + mp4, + data, + voeSxquality, + ) + ) + } else loadExtractor(iframeUrl, data, subtitleCallback, callback) + } + } + return true + } +} \ No newline at end of file diff --git a/CimaNowProvider.cs3 b/CimaNowProvider.cs3 new file mode 100644 index 0000000..2914345 Binary files /dev/null and b/CimaNowProvider.cs3 differ diff --git a/CimaNowProvider/build.gradle.kts b/CimaNowProvider/build.gradle.kts new file mode 100644 index 0000000..8ef9f8f --- /dev/null +++ b/CimaNowProvider/build.gradle.kts @@ -0,0 +1,14 @@ +version = 1 + +cloudstream { + description = "" + authors = listOf( "ImZaw" ) + + language = "ar" + + status = 1 + + tvTypes = listOf( "TvSeries" , "Movie" ) + + iconUrl = "https://www.google.com/s2/favicons?domain=cimanow.cc&sz=%size%" +} \ No newline at end of file diff --git a/CimaNowProvider/src/main/AndroidManifest.xml b/CimaNowProvider/src/main/AndroidManifest.xml new file mode 100644 index 0000000..95a9f4b --- /dev/null +++ b/CimaNowProvider/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/CimaNowProvider/src/main/kotlin/com/cimanow/CimaNowPlugin.kt b/CimaNowProvider/src/main/kotlin/com/cimanow/CimaNowPlugin.kt new file mode 100644 index 0000000..ddb39b4 --- /dev/null +++ b/CimaNowProvider/src/main/kotlin/com/cimanow/CimaNowPlugin.kt @@ -0,0 +1,11 @@ +package com.cimanow +import com.lagradost.cloudstream3.plugins.CloudstreamPlugin +import com.lagradost.cloudstream3.plugins.Plugin +import android.content.Context + +@CloudstreamPlugin +class CimaNowPlugin: Plugin() { + override fun load(context: Context) { + registerMainAPI(CimaNow()) + } +} \ No newline at end of file diff --git a/CimaNowProvider/src/main/kotlin/com/cimanow/CimaNowProvider.kt b/CimaNowProvider/src/main/kotlin/com/cimanow/CimaNowProvider.kt new file mode 100644 index 0000000..d9c45b7 --- /dev/null +++ b/CimaNowProvider/src/main/kotlin/com/cimanow/CimaNowProvider.kt @@ -0,0 +1,164 @@ +package com.cimanow + + +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.Qualities +import org.jsoup.nodes.Element + +class CimaNow : MainAPI() { + override var lang = "ar" + override var mainUrl = "https://cimanow.cc" + override var name = "CimaNow" + override val usesWebView = false + override val hasMainPage = true + override val supportedTypes = setOf(TvType.TvSeries, TvType.Movie) + + private fun String.getIntFromText(): Int? { + return Regex("""\d+""").find(this)?.groupValues?.firstOrNull()?.toIntOrNull() + } + + private fun Element.toSearchResponse(): SearchResponse? { + val url = this.attr("href") + val posterUrl = select("img")?.attr("data-src") + var title = select("li[aria-label=\"title\"]").html().replace(" .*|\\\\n".toRegex(), "").replace(" ", "") + val year = select("li[aria-label=\"year\"]").text().toIntOrNull() + val tvType = if (url.contains("فيلم|مسرحية|حفلات".toRegex())) TvType.Movie else TvType.TvSeries + val quality = select("li[aria-label=\"ribbon\"]").first()?.text()?.replace(" |-|1080|720".toRegex(), "") + val dubEl = select("li[aria-label=\"ribbon\"]:nth-child(2)").isNotEmpty() + val dubStatus = if(dubEl) select("li[aria-label=\"ribbon\"]:nth-child(2)").text().contains("مدبلج") + else select("li[aria-label=\"ribbon\"]:nth-child(1)").text().contains("مدبلج") + if(dubStatus) title = "$title (مدبلج)" + return MovieSearchResponse( + "$title ${select("li[aria-label=\"ribbon\"]:contains(الموسم)").text()}", + url, + this@CimaNow.name, + tvType, + posterUrl, + year, + null, + quality = getQualityFromString(quality) + ) + } + + override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse { + + val doc = app.get("$mainUrl/home", headers = mapOf("user-agent" to "MONKE")).document + val pages = doc.select("section").not("section:contains(أختر وجهتك المفضلة)").not("section:contains(تم اضافته حديثاً)").apmap { + val name = it.select("span").html().replace(".*| { + val result = arrayListOf() + val doc = app.get("$mainUrl/page/1/?s=$query").document + val paginationElement = doc.select("ul[aria-label=\"pagination\"]") + doc.select("section article a").map { + val postUrl = it.attr("href") + if(it.select("li[aria-label=\"episode\"]").isNotEmpty()) return@map + if(postUrl.contains("$mainUrl/expired-download/|$mainUrl/افلام-اون-لاين/".toRegex())) return@map + result.add(it.toSearchResponse()!!) + } + if(paginationElement.isNotEmpty()) { + val max = paginationElement.select("li").not("li.active").last()?.text()?.toIntOrNull() + if (max != null) { + if(max > 5) return result.distinct().sortedBy { it.name } + (2..max!!).toList().apmap { + app.get("$mainUrl/page/$it/?s=$query\"").document.select("section article a").map { element -> + val postUrl = element.attr("href") + if(element.select("li[aria-label=\"episode\"]").isNotEmpty()) return@map + if(postUrl.contains("$mainUrl/expired-download/|$mainUrl/افلام-اون-لاين/".toRegex())) return@map + result.add(element.toSearchResponse()!!) + } + } + } + } + return result.distinct().sortedBy { it.name } + } + + override suspend fun load(url: String): LoadResponse { + val doc = app.get(url).document + val posterUrl = doc.select("body > script:nth-child(3)").html().replace(".*,\"image\":\"|\".*".toRegex(),"").ifEmpty { doc.select("meta[property=\"og:image\"]").attr("content") } + val year = doc.select("article ul:nth-child(1) li a").last()?.text()?.toIntOrNull() + val title = doc.select("title").text().split(" | ")[0] + val isMovie = title.contains("فيلم|حفلات|مسرحية".toRegex()) + val youtubeTrailer = doc.select("iframe")?.attr("src") + + val synopsis = doc.select("ul#details li:contains(لمحة) p").text() + + val tags = doc.select("article ul").first()?.select("li")?.map { it.text() } + + val recommendations = doc.select("ul#related li").map { element -> + MovieSearchResponse( + apiName = this@CimaNow.name, + url = element.select("a").attr("href"), + name = element.select("img:nth-child(2)").attr("alt"), + posterUrl = element.select("img:nth-child(2)").attr("src") + ) + } + + return if (isMovie) { + newMovieLoadResponse( + title, + url, + TvType.Movie, + "$url/watching" + ) { + this.posterUrl = posterUrl + this.year = year + this.recommendations = recommendations + this.plot = synopsis + this.tags = tags + addTrailer(youtubeTrailer) + } + } else { + val episodes = doc.select("ul#eps li").map { episode -> + Episode( + episode.select("a").attr("href")+"/watching", + episode.select("a img:nth-child(2)").attr("alt"), + doc.select("span[aria-label=\"season-title\"]").html().replace("

.*|\n".toRegex(), "").getIntFromText(), + episode.select("a em").text().toIntOrNull(), + episode.select("a img:nth-child(2)").attr("src") + ) + } + newTvSeriesLoadResponse(title, url, TvType.TvSeries, episodes.distinct().sortedBy { it.episode }) { + this.posterUrl = posterUrl + this.tags = tags + this.year = year + this.plot = synopsis + this.recommendations = recommendations + addTrailer(youtubeTrailer) + } + } + } + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + app.get("$data").document.select("ul#download [aria-label=\"quality\"]").forEach { + val name = if(it.select("span").text().contains("فائق السرعة")) "Fast Servers" else "Servers" + it.select("a").forEach { media -> + callback.invoke( + ExtractorLink( + source = this.name, + name = name, + url = media.attr("href"), + referer = this.mainUrl, + quality = media.text().getIntFromText() ?: Qualities.Unknown.value + ) + ) + } + } + return true + } +} \ No newline at end of file diff --git a/EgyBestProvider/build.gradle.kts b/EgyBestProvider/build.gradle.kts new file mode 100644 index 0000000..c702710 --- /dev/null +++ b/EgyBestProvider/build.gradle.kts @@ -0,0 +1,14 @@ +version = 7 + +cloudstream { + description = "Egybest is broken" + authors = listOf( "ImZaw" ) + + language = "ar" + + status = 0 + + tvTypes = listOf( "TvSeries" , "Movie" , "Anime" ) + + iconUrl = "https://www.google.com/s2/favicons?domain=www.egy.best&sz=%size%" +} diff --git a/EgyBestProvider/src/main/AndroidManifest.xml b/EgyBestProvider/src/main/AndroidManifest.xml new file mode 100644 index 0000000..09206f7 --- /dev/null +++ b/EgyBestProvider/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/EgyBestProvider/src/main/kotlin/com/egybest/EgyBestPlugin.kt b/EgyBestProvider/src/main/kotlin/com/egybest/EgyBestPlugin.kt new file mode 100644 index 0000000..f417ab6 --- /dev/null +++ b/EgyBestProvider/src/main/kotlin/com/egybest/EgyBestPlugin.kt @@ -0,0 +1,11 @@ +package com.egybest +import com.lagradost.cloudstream3.plugins.CloudstreamPlugin +import com.lagradost.cloudstream3.plugins.Plugin +import android.content.Context + +@CloudstreamPlugin +class EgyBestPlugin: Plugin() { + override fun load(context: Context) { + registerMainAPI(EgyBest()) + } +} \ No newline at end of file diff --git a/EgyBestProvider/src/main/kotlin/com/egybest/EgyBestProvider.kt b/EgyBestProvider/src/main/kotlin/com/egybest/EgyBestProvider.kt new file mode 100644 index 0000000..295e5c7 --- /dev/null +++ b/EgyBestProvider/src/main/kotlin/com/egybest/EgyBestProvider.kt @@ -0,0 +1,275 @@ +package com.egybest + + +import android.annotation.TargetApi +import android.os.Build +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.M3u8Helper +import com.lagradost.nicehttp.Requests +import com.lagradost.nicehttp.Session +import okhttp3.HttpUrl.Companion.toHttpUrl +import org.jsoup.nodes.Element +import java.util.Base64 +import org.mozilla.javascript.Context +import org.mozilla.javascript.Scriptable + +fun String.runJS(variableName: String): String { + val rhino = Context.enter() + rhino.initSafeStandardObjects() + rhino.optimizationLevel = -1 + val scope: Scriptable = rhino.initSafeStandardObjects() + val script = this + val result: String + try { + var js = "" + for (i in script.indices) { + js += script[i] + } + rhino.evaluateString(scope, js, "JavaScript", 1, null) + result = Context.toString(scope.get(variableName, scope)) + } finally { + Context.exit() + } + return result +} + +class EgyBest : MainAPI() { + override var lang = "ar" + override var mainUrl = "https://egybest.org" + override var name = "EgyBest" + var pssid = "" + override val usesWebView = false + override val hasMainPage = true + override val supportedTypes = setOf(TvType.TvSeries, TvType.Movie, TvType.Anime) + + private fun String.getIntFromText(): Int? { + return Regex("""\d+""").find(this)?.groupValues?.firstOrNull()?.toIntOrNull() + } + + private fun Element.toSearchResponse(): SearchResponse? { + val url = this.attr("href") ?: return null + val posterUrl = select("img")?.attr("src") + var title = select("span.title").text() + val year = title.getYearFromTitle() + val isMovie = Regex(".*/movie/.*|.*/masrahiya/.*").matches(url) + val tvType = if (isMovie) TvType.Movie else TvType.TvSeries + title = if (year !== null) title else title.split(" (")[0].trim() + val quality = select("span.ribbon span").text().replace("-", "") + // If you need to differentiate use the url. + return MovieSearchResponse( + title, + mainUrl + url, + this@EgyBest.name, + tvType, + posterUrl, + year, + null, + quality = getQualityFromString(quality) + ) + } + + override val mainPage = mainPageOf( + "$mainUrl/trending/?page=" to "الأفلام الأكثر مشاهدة", + "$mainUrl/movies/?page=" to "أفلام جديدة", + "$mainUrl/tv/?page=" to "مسلسلات جديدة ", + "$mainUrl/tv/korean?page=" to "الدراما الكورية ", + "$mainUrl/animes/popular?page=" to "مسلسلات الانمي", + "$mainUrl/wwe/?page=" to "عروض المصارعة ", + "$mainUrl/movies/latest-bluray-2020-2019?page=" to "أفلام جديدة BluRay", + "$mainUrl/masrahiyat/?page=" to "مسرحيات ", + "$mainUrl/movies/latest?page=" to "أحدث الاضافات", + "$mainUrl/movies/comedy?page=" to "أفلام كوميدية", + "$mainUrl/explore/?q=superhero/" to "أفلام سوبر هيرو", + "$mainUrl/movies/animation?page=" to "أفلام انمي و كرتون", + "$mainUrl/movies/romance?page=" to "أفلام رومانسية", + "$mainUrl/movies/drama?page=" to "أفلام دراما", + "$mainUrl/movies/horror?page=" to "أفلام رعب", + "$mainUrl/movies/documentary?page=" to "أفلام وثائقية", + "$mainUrl/World-War-Movies/?page=" to "أفلام عن الحرب العالمية ☢", + "$mainUrl/End-Of-The-World-Movies/?page=" to "أفلام عن نهاية العالم", + "$mainUrl/movies/arab?page=" to "أفلام عربية ", + ) + + override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse { + val doc = app.get(request.data + page).document + val list = doc.select(".movie") + .mapNotNull { element -> + element.toSearchResponse() + } + return newHomePageResponse(request.name, list) + } + + override suspend fun search(query: String): List { + val result = arrayListOf() + listOf("$mainUrl/explore/?q=$query").apmap { url -> + val d = app.get(url).document + d.select("div.movies a").not("a.auto.load.btn.b").mapNotNull { + it.toSearchResponse()?.let { it1 -> result.add(it1) } + } + } + return result.distinct().sortedBy { it.name } + } + + private fun String.getYearFromTitle(): Int? { + return Regex("""\(\d{4}\)""").find(this)?.groupValues?.firstOrNull()?.toIntOrNull() + } + + override suspend fun load(url: String): LoadResponse { + val doc = app.get(url).document + val isMovie = Regex(".*/movie/.*|.*/masrahiya/.*").matches(url) + val posterUrl = doc.select("div.movie_img a img")?.attr("src") + val year = doc.select("div.movie_title h1 a")?.text()?.toIntOrNull() + val title = doc.select("div.movie_title h1 span").text() + val youtubeTrailer = doc.select("div.play")?.attr("url") + + val synopsis = doc.select("div.mbox").firstOrNull { + it.text().contains("القصة") + }?.text()?.replace("القصة ", "") + + val tags = doc.select("table.movieTable tbody tr").firstOrNull { + it.text().contains("النوع") + }?.select("a")?.map { it.text() } + + val actors = doc.select("div.cast_list .cast_item").mapNotNull { + val name = it.selectFirst("div > a > img")?.attr("alt") ?: return@mapNotNull null + val image = it.selectFirst("div > a > img")?.attr("src") ?: return@mapNotNull null + val roleString = it.selectFirst("div > span")!!.text() + val mainActor = Actor(name, image) + ActorData(actor = mainActor, roleString = roleString) + } + + return if (isMovie) { + val recommendations = doc.select(".movies_small .movie").mapNotNull { element -> + element.toSearchResponse() + } + + newMovieLoadResponse( + title, + url, + TvType.Movie, + url + ) { + this.posterUrl = posterUrl + this.year = year + this.recommendations = recommendations + this.plot = synopsis + this.tags = tags + this.actors = actors + addTrailer(youtubeTrailer) + } + } else { + val episodes = ArrayList() + doc.select("#mainLoad > div:nth-child(2) > div.h_scroll > div a").map { + it.attr("href") + }.apmap { + val d = app.get(it).document + val season = Regex("season-(.....)").find(it)?.groupValues?.getOrNull(1)?.getIntFromText() + if(d.select("tr.published").isNotEmpty()) { + d.select("tr.published").map { element -> + val ep = Regex("ep-(.....)").find(element.select(".ep_title a").attr("href"))?.groupValues?.getOrNull(1)?.getIntFromText() + episodes.add( + Episode( + mainUrl + element.select(".ep_title a").attr("href"), + name = element.select("td.ep_title").html().replace(".*|".toRegex(), ""), + season, + ep, + rating = element.select("td.tam:not(.date, .ep_len)").text().getIntFromText() + ) + ) + } + } else { + d.select("#mainLoad > div:nth-child(3) > div.movies_small a").map { eit -> + val ep = Regex("ep-(.....)").find(eit.attr("href"))?.groupValues?.getOrNull(1)?.getIntFromText() + episodes.add( + Episode( + mainUrl + eit.attr("href"), + eit.select("span.title").text(), + season, + ep, + ) + ) + } + } + } + newTvSeriesLoadResponse(title, url, TvType.TvSeries, episodes.distinct().sortedBy { it.episode }) { + this.posterUrl = posterUrl + this.tags = tags + this.year = year + this.plot = synopsis + this.actors = actors + addTrailer(youtubeTrailer) + } + } + } + + @TargetApi(Build.VERSION_CODES.O) + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + val baseURL = data.split("/")[0] + "//" + data.split("/")[2] + val client = Requests().baseClient + val session = Session(client) + println(baseURL) + println(data) + val doc = session.get(data).document + + val vidstreamURL = baseURL + doc.select("iframe.auto-size").attr("src") + + val videoSoup = session.get(vidstreamURL, cookies = mapOf( + "PSSID" to this@EgyBest.pssid, + )).document + videoSoup.select("source").firstOrNull { it.hasAttr("src") }?.attr("src")?.let { + M3u8Helper.generateM3u8( + this.name, + it, + referer = mainUrl, + headers = mapOf("range" to "bytes=0-") + ).forEach(callback) + } ?: run { + var jsCode = videoSoup.select("script")[1].html() + val function = videoSoup.select("script")[2].attr("onload") + val verificationToken = Regex("\\{'[0-9a-zA-Z_]*':'ok'\\}").findAll(jsCode).first().value.replace("\\{'|':.*".toRegex(), "") + val encodedAdLinkVar = Regex("\\([0-9a-zA-Z_]{2,12}\\[Math").findAll(jsCode).first().value.replace("\\(|\\[M.*".toRegex(),"") + val encodingArraysRegEx = Regex(",[0-9a-zA-Z_]{2,12}=\\[]").findAll(jsCode).toList() + val firstEncodingArray = encodingArraysRegEx[1].value.replace(",|=.*".toRegex(),"") + val secondEncodingArray = encodingArraysRegEx[2].value.replace(",|=.*".toRegex(),"") + + jsCode = jsCode.replace("^