├── .github └── workflows │ └── build.yml ├── .gitignore ├── AkwamProvider ├── build.gradle.kts └── src │ └── main │ ├── AndroidManifest.xml │ └── kotlin │ └── com │ └── akwam │ ├── AkwamPlugin.kt │ └── AkwamProvider.kt ├── Anime4upPack ├── build.gradle.kts └── src │ └── main │ ├── AndroidManifest.xml │ └── kotlin │ └── com │ └── anime4up │ ├── Anime4upPlugin.kt │ └── Anime4upProvider.kt ├── AnimeBlkomProvider ├── build.gradle.kts └── src │ └── main │ ├── AndroidManifest.xml │ └── kotlin │ └── com │ └── animeblkom │ ├── AnimeBlkomPlugin.kt │ └── AnimeBlkomProvider.kt ├── AnimeiatProvider ├── build.gradle.kts └── src │ └── main │ ├── AndroidManifest.xml │ └── kotlin │ └── com │ └── animeiat │ ├── AnimeiatPlugin.kt │ └── AnimeiatProvider.kt ├── ArabSeedProvider ├── build.gradle.kts └── src │ └── main │ ├── AndroidManifest.xml │ └── kotlin │ └── com │ └── arabseed │ ├── ArabSeedPlugin.kt │ └── ArabSeedProvider.kt ├── CimaNowProvider ├── build.gradle.kts └── src │ └── main │ ├── AndroidManifest.xml │ └── kotlin │ └── com │ └── cimanow │ ├── CimaNowPlugin.kt │ └── CimaNowProvider.kt ├── EgyBestProvider ├── build.gradle.kts └── src │ └── main │ ├── AndroidManifest.xml │ └── kotlin │ └── com │ └── egybest │ ├── EgyBestPlugin.kt │ └── EgyBestProvider.kt ├── EgyDeadProvider ├── build.gradle.kts └── src │ └── main │ ├── AndroidManifest.xml │ └── kotlin │ └── com │ └── egydead │ ├── EgyDeadPlugin.kt │ └── EgyDeadProvider.kt ├── Extractors ├── build.gradle.kts └── src │ └── main │ ├── AndroidManifest.xml │ └── kotlin │ └── com │ └── extractors │ ├── ExtractorsPlugin.kt │ ├── GoStream.kt │ ├── Govad.kt │ ├── JWPlayerExtractor.kt │ ├── LinkBox.kt │ ├── Moshahda.kt │ ├── MyVid.kt │ ├── VidHD.kt │ ├── Vidmoly.kt │ └── VoeSx.kt ├── FajerShowProvider ├── build.gradle.kts └── src │ └── main │ ├── AndroidManifest.xml │ └── kotlin │ └── com │ └── fajershow │ ├── FajerShowPlugin.kt │ └── FajerShowProvider.kt ├── FaselHDProvider ├── build.gradle.kts └── src │ └── main │ ├── AndroidManifest.xml │ └── kotlin │ └── com │ └── faselhd │ ├── FaselHDPlugin.kt │ └── FaselHDProvider.kt ├── FushaarProvider ├── build.gradle.kts └── src │ └── main │ ├── AndroidManifest.xml │ └── kotlin │ └── com │ └── fushaar │ ├── FushaarPlugin.kt │ └── FushaarProvider.kt ├── GateAnimeProvider ├── build.gradle.kts └── src │ └── main │ ├── AndroidManifest.xml │ └── kotlin │ └── com │ └── gateanime │ ├── GateAnimePlugin.kt │ └── GateAnimeProvider.kt ├── MovizlandProvider ├── build.gradle.kts └── src │ └── main │ ├── AndroidManifest.xml │ └── kotlin │ └── com │ └── movizland │ ├── MovizlandPlugin.kt │ └── MovizlandProvider.kt ├── MyCimaProvider ├── build.gradle.kts └── src │ └── main │ ├── AndroidManifest.xml │ └── kotlin │ └── com │ └── mycima │ ├── MyCimaPlugin.kt │ └── MyCimaProvider.kt ├── README.md ├── Shahid4uProvider ├── build.gradle.kts └── src │ └── main │ ├── AndroidManifest.xml │ └── kotlin │ └── com │ └── shahid4u │ ├── Shahid4uPlugin.kt │ └── Shahid4uProvider.kt ├── build.gradle.kts ├── convert.py ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── requirements.txt └── settings.gradle.kts /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | # https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#concurrency 4 | concurrency: 5 | group: "build" 6 | cancel-in-progress: true 7 | 8 | on: 9 | push: 10 | branches: 11 | # choose your default branch 12 | - master 13 | # - main 14 | paths-ignore: 15 | - '*.md' 16 | - '*.html' 17 | - '*.js' 18 | - '*.css' 19 | 20 | jobs: 21 | build: 22 | runs-on: ubuntu-latest 23 | steps: 24 | - name: Checkout 25 | uses: actions/checkout@master 26 | with: 27 | path: "src" 28 | 29 | - name: Checkout builds 30 | uses: actions/checkout@master 31 | with: 32 | ref: "builds" 33 | path: "builds" 34 | 35 | - name: Clean old builds 36 | run: rm $GITHUB_WORKSPACE/builds/*.cs3 || true 37 | 38 | - name: Setup JDK 11 39 | uses: actions/setup-java@v1 40 | with: 41 | java-version: 11 42 | 43 | - name: Setup Android SDK 44 | uses: android-actions/setup-android@v2 45 | 46 | - name: Build Plugins 47 | run: | 48 | cd $GITHUB_WORKSPACE/src 49 | chmod +x gradlew 50 | ./gradlew make makePluginsJson 51 | cp **/build/*.cs3 $GITHUB_WORKSPACE/builds 52 | cp build/plugins.json $GITHUB_WORKSPACE/builds 53 | - name: Push builds 54 | run: | 55 | cd $GITHUB_WORKSPACE/builds 56 | git config --local user.email "actions@github.com" 57 | git config --local user.name "GitHub Actions" 58 | git add . 59 | git commit --amend -m "Build $GITHUB_SHA" || exit 0 # do not error if nothing to commit 60 | git push --force 61 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | .DS_Store 6 | /build 7 | **/build 8 | /captures 9 | .externalNativeBuild 10 | .cxx 11 | local.properties 12 | .vscode -------------------------------------------------------------------------------- /AkwamProvider/build.gradle.kts: -------------------------------------------------------------------------------- 1 | version = 4 2 | 3 | cloudstream { 4 | authors = listOf( "Blatzar" ) 5 | 6 | language = "ar" 7 | 8 | status = 1 9 | 10 | tvTypes = listOf( "TvSeries" , "Movie" , "Anime" , "Cartoon" ) 11 | 12 | iconUrl = "https://www.google.com/s2/favicons?domain=akwam.to&sz=%size%" 13 | } 14 | -------------------------------------------------------------------------------- /AkwamProvider/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /AkwamProvider/src/main/kotlin/com/akwam/AkwamPlugin.kt: -------------------------------------------------------------------------------- 1 | package com.akwam 2 | import com.lagradost.cloudstream3.plugins.CloudstreamPlugin 3 | import com.lagradost.cloudstream3.plugins.Plugin 4 | import android.content.Context 5 | 6 | @CloudstreamPlugin 7 | class AkwamPlugin: Plugin() { 8 | override fun load(context: Context) { 9 | registerMainAPI(Akwam()) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /AkwamProvider/src/main/kotlin/com/akwam/AkwamProvider.kt: -------------------------------------------------------------------------------- 1 | package com.akwam 2 | 3 | 4 | import com.lagradost.cloudstream3.* 5 | import com.lagradost.cloudstream3.LoadResponse.Companion.addActors 6 | import com.lagradost.cloudstream3.utils.ExtractorLink 7 | import com.lagradost.cloudstream3.utils.Qualities 8 | import org.jsoup.nodes.Element 9 | 10 | class Akwam : MainAPI() { 11 | override var lang = "ar" 12 | override var mainUrl = "https://ak.sv" 13 | override var name = "Akwam" 14 | override val usesWebView = false 15 | override val hasMainPage = true 16 | override val supportedTypes = setOf(TvType.TvSeries, TvType.Movie, TvType.Anime, TvType.Cartoon) 17 | 18 | private fun Element.toSearchResponse(): SearchResponse? { 19 | val url = select("a.box").attr("href") ?: return null 20 | if (url.contains("/games/") || url.contains("/programs/")) return null 21 | val poster = select("picture > img") 22 | val title = poster.attr("alt") 23 | val posterUrl = poster.attr("data-src") 24 | val year = select(".badge-secondary").text().toIntOrNull() 25 | 26 | // If you need to differentiate use the url. 27 | return MovieSearchResponse( 28 | title, 29 | url, 30 | this@Akwam.name, 31 | TvType.TvSeries, 32 | posterUrl, 33 | year, 34 | null, 35 | ) 36 | } 37 | override val mainPage = mainPageOf( 38 | "$mainUrl/movies?page=" to "Movies", 39 | "$mainUrl/series?page=" to "Series", 40 | "$mainUrl/shows?page=" to "Shows" 41 | ) 42 | 43 | override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse { 44 | val doc = app.get(request.data + page).document 45 | val list = doc.select("div.col-lg-auto.col-md-4.col-6.mb-12").mapNotNull { element -> 46 | element.toSearchResponse() 47 | } 48 | return newHomePageResponse(request.name, list) 49 | } 50 | 51 | override suspend fun search(query: String): List { 52 | val url = "$mainUrl/search?q=$query" 53 | val doc = app.get(url).document 54 | return doc.select("div.col-lg-auto").mapNotNull { 55 | it.toSearchResponse() 56 | } 57 | } 58 | 59 | private fun String.getIntFromText(): Int? { 60 | return Regex("""\d+""").find(this)?.groupValues?.firstOrNull()?.toIntOrNull() 61 | } 62 | 63 | private fun Element.toEpisode(): Episode { 64 | val a = select("a.text-white") 65 | val url = a.attr("href") 66 | val title = a.text() 67 | val thumbUrl = select("picture > img").attr("src") 68 | val date = select("p.entry-date").text() 69 | return newEpisode(url) { 70 | name = title 71 | episode = title.getIntFromText() 72 | posterUrl = thumbUrl 73 | addDate(date) 74 | } 75 | } 76 | 77 | 78 | override suspend fun load(url: String): LoadResponse { 79 | val doc = app.get(url).document 80 | val isMovie = doc.select("#downloads > h2 > span").isNotEmpty()//url.contains("/movie/") 81 | val title = doc.select("h1.entry-title").text() 82 | val posterUrl = doc.select("picture > img").attr("src") 83 | 84 | val year = 85 | doc.select("div.font-size-16.text-white.mt-2").firstOrNull { 86 | it.text().contains("السنة") 87 | }?.text()?.getIntFromText() 88 | 89 | // A bit iffy to parse twice like this, but it'll do. 90 | val duration = 91 | doc.select("div.font-size-16.text-white.mt-2").firstOrNull { 92 | it.text().contains("مدة الفيلم") 93 | }?.text()?.getIntFromText() 94 | 95 | val synopsis = doc.select("div.widget-body p:first-child").text() 96 | 97 | val rating = doc.select("span.mx-2").text().split("/").lastOrNull()?.toRatingInt() 98 | 99 | val tags = doc.select("div.font-size-16.d-flex.align-items-center.mt-3 > a").map { 100 | it.text() 101 | } 102 | 103 | val actors = doc.select("div.widget-body > div > div.entry-box > a").mapNotNull { 104 | val name = it?.selectFirst("div > .entry-title")?.text() ?: return@mapNotNull null 105 | val image = it.selectFirst("div > img")?.attr("src") ?: return@mapNotNull null 106 | Actor(name, image) 107 | } 108 | 109 | 110 | 111 | val recommendations = 112 | doc.select("div > div.widget-body > div.row > div > div.entry-box").mapNotNull { 113 | val recTitle = it?.selectFirst("div.entry-body > .entry-title > .text-white") 114 | ?: return@mapNotNull null 115 | val href = recTitle.attr("href") ?: return@mapNotNull null 116 | val name = recTitle.text() ?: return@mapNotNull null 117 | val poster = it.selectFirst(".entry-image > a > picture > img")?.attr("data-src") 118 | ?: return@mapNotNull null 119 | MovieSearchResponse(name, href, this.name, TvType.Movie, fixUrl(poster)) 120 | } 121 | 122 | return if (isMovie) { 123 | newMovieLoadResponse( 124 | title, 125 | url, 126 | TvType.Movie, 127 | url 128 | ) { 129 | this.posterUrl = posterUrl 130 | this.year = year 131 | this.plot = synopsis 132 | this.rating = rating 133 | this.tags = tags 134 | this.duration = duration 135 | this.recommendations = recommendations 136 | addActors(actors) 137 | } 138 | } else { 139 | val episodes = doc.select("div.bg-primary2.p-4.col-lg-4.col-md-6.col-12").map { 140 | it.toEpisode() 141 | }.let { 142 | val isReversed = (it.lastOrNull()?.episode ?: 1) < (it.firstOrNull()?.episode ?: 0) 143 | if (isReversed) 144 | it.reversed() 145 | else it 146 | } 147 | 148 | newTvSeriesLoadResponse(title, url, TvType.TvSeries, episodes) { 149 | this.duration = duration 150 | this.posterUrl = posterUrl 151 | this.tags = tags.filterNotNull() 152 | this.rating = rating 153 | this.year = year 154 | this.plot = synopsis 155 | this.recommendations = recommendations 156 | addActors(actors) 157 | } 158 | } 159 | } 160 | 161 | 162 | // // Maybe possible to not use the url shortener but cba investigating that. 163 | // private suspend fun skipUrlShortener(url: String): AppResponse { 164 | // return app.get(app.get(url).document.select("a.download-link").attr("href")) 165 | // } 166 | 167 | private fun getQualityFromId(id: Int?): Qualities { 168 | return when (id) { 169 | 2 -> Qualities.P360 // Extrapolated 170 | 3 -> Qualities.P480 171 | 4 -> Qualities.P720 172 | 5 -> Qualities.P1080 173 | else -> Qualities.Unknown 174 | } 175 | } 176 | 177 | override suspend fun loadLinks( 178 | data: String, 179 | isCasting: Boolean, 180 | subtitleCallback: (SubtitleFile) -> Unit, 181 | callback: (ExtractorLink) -> Unit 182 | ): Boolean { 183 | val doc = app.get(data).document 184 | 185 | val links = doc.select("div.tab-content.quality").map { element -> 186 | val quality = getQualityFromId(element.attr("id").getIntFromText()) 187 | element.select(".col-lg-6 > a:contains(تحميل)").map { linkElement -> 188 | if (linkElement.attr("href").contains("/download/")) { 189 | Pair( 190 | linkElement.attr("href"), 191 | quality, 192 | ) 193 | } else { 194 | val url = "$mainUrl/download${ 195 | linkElement.attr("href").split("/link")[1] 196 | }${data.split("/movie|/episode|/shows|/show/episode".toRegex())[1]}" 197 | Pair( 198 | url, 199 | quality, 200 | ) 201 | // just in case if they add the shorts urls again 202 | } 203 | } 204 | }.flatten() 205 | 206 | links.map { 207 | val linkDoc = app.get(it.first).document 208 | val button = linkDoc.select("div.btn-loader > a") 209 | val url = button.attr("href") 210 | 211 | callback.invoke( 212 | ExtractorLink( 213 | this.name, 214 | this.name, 215 | url, 216 | this.mainUrl, 217 | it.second.value 218 | ) 219 | ) 220 | } 221 | return true 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /Anime4upPack/build.gradle.kts: -------------------------------------------------------------------------------- 1 | version = 1 2 | 3 | cloudstream { 4 | description = "This pack contains Anime4up and Witanime" 5 | authors = listOf( "ImZaw" ) 6 | 7 | language = "ar" 8 | 9 | status = 1 10 | 11 | tvTypes = listOf( "Anime", "AnimeMovie", "Others" ) 12 | 13 | iconUrl = "https://www.google.com/s2/favicons?domain=anime4up.tv&sz=%size%" 14 | } -------------------------------------------------------------------------------- /Anime4upPack/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Anime4upPack/src/main/kotlin/com/anime4up/Anime4upPlugin.kt: -------------------------------------------------------------------------------- 1 | package com.anime4up 2 | 3 | import com.lagradost.cloudstream3.plugins.CloudstreamPlugin 4 | import com.lagradost.cloudstream3.plugins.Plugin 5 | import android.content.Context 6 | 7 | @CloudstreamPlugin 8 | class Anime4upPlugin: Plugin() { 9 | override fun load(context: Context) { 10 | registerMainAPI(Anime4up()) 11 | registerMainAPI(WitAnime()) 12 | } 13 | } -------------------------------------------------------------------------------- /Anime4upPack/src/main/kotlin/com/anime4up/Anime4upProvider.kt: -------------------------------------------------------------------------------- 1 | package com.anime4up 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty 4 | import com.lagradost.cloudstream3.utils.AppUtils.parseJson 5 | import com.lagradost.cloudstream3.* 6 | import com.lagradost.cloudstream3.LoadResponse.Companion.addMalId 7 | import com.lagradost.cloudstream3.utils.ExtractorLink 8 | import okio.ByteString.Companion.decodeBase64 9 | import com.lagradost.cloudstream3.utils.loadExtractor 10 | import org.jsoup.nodes.Document 11 | import org.jsoup.nodes.Element 12 | import java.net.URL 13 | 14 | private fun String.getIntFromText(): Int? { 15 | return Regex("""\d+""").find(this)?.groupValues?.firstOrNull()?.toIntOrNull() 16 | } 17 | class WitAnime : Anime4up() { 18 | override var name = "WitAnime" 19 | override var mainUrl = "https://witanime.com" 20 | } 21 | open class Anime4up : MainAPI() { 22 | override var lang = "ar" 23 | override var mainUrl = "https://anime4up.tv" 24 | override var name = "Anime4up" 25 | override val usesWebView = false 26 | override val hasMainPage = true 27 | override val supportedTypes = 28 | setOf(TvType.Anime, TvType.AnimeMovie, TvType.OVA, TvType.Others ) 29 | 30 | 31 | private fun Element.toSearchResponse(): SearchResponse { 32 | val imgElement = select("div.hover > img") 33 | val url = select("div.hover > a").attr("href") 34 | .replace("-%d8%a7%d9%84%d8%ad%d9%84%d9%82%d8%a9-.*".toRegex(), "") 35 | .replace("episode", "anime") 36 | val title = imgElement.attr("alt") 37 | val posterUrl = imgElement.attr("src") 38 | val typeText = select("div.anime-card-type > a").text() 39 | val type = 40 | if (typeText.contains("TV|Special".toRegex())) TvType.Anime 41 | else if(typeText.contains("OVA|ONA".toRegex())) TvType.OVA 42 | else if(typeText.contains("Movie")) TvType.AnimeMovie 43 | else TvType.Others 44 | return newAnimeSearchResponse( 45 | title, 46 | url, 47 | type, 48 | ) { 49 | this.posterUrl = posterUrl 50 | } 51 | } 52 | 53 | override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse { 54 | val doc = app.get("$mainUrl/").document 55 | val homeList = doc.select(".page-content-container") 56 | .mapNotNull { 57 | val title = it.select("div.main-didget-head h3").text() 58 | val list = 59 | it.select("div.anime-card-container, div.episodes-card-container").map { 60 | anime -> anime.toSearchResponse() 61 | }.distinct() 62 | HomePageList(title, list, isHorizontalImages = title.contains("حلقات")) 63 | } 64 | return newHomePageResponse(homeList, hasNext = false) 65 | } 66 | 67 | override suspend fun search(query: String): List { 68 | return app.get("$mainUrl/?search_param=animes&s=$query").document 69 | .select("div.row.display-flex > div").mapNotNull { 70 | it.toSearchResponse() 71 | } 72 | } 73 | 74 | override suspend fun load(url: String): LoadResponse { 75 | val doc = app.get(url).document 76 | 77 | val title = doc.select("h1.anime-details-title").text() 78 | val poster = doc.select("div.anime-thumbnail img").attr("src") 79 | val description = doc.select("p.anime-story").text() 80 | val year = doc.select("div.anime-info:contains(بداية العرض)").text().replace("بداية العرض: ", "").toIntOrNull() 81 | 82 | val typeText = doc.select(".anime-info:contains(النوع) a").text() 83 | val type = 84 | if (typeText.contains("TV|Special".toRegex())) TvType.Anime 85 | else if(typeText.contains("OVA|ONA".toRegex())) TvType.OVA 86 | else if(typeText.contains("Movie")) TvType.AnimeMovie 87 | else TvType.Others 88 | 89 | val malId = doc.select("a.anime-mal").attr("href").replace(".*e\\/|\\/.*".toRegex(),"").toIntOrNull() 90 | 91 | val episodes = doc.select("div#DivEpisodesList > div").apmap { 92 | val episodeElement = it.select("h3 a") 93 | val episodeUrl = episodeElement.attr("href") 94 | val episodeTitle = episodeElement.text() 95 | val posterUrl = it.select(".hover img").attr("src") 96 | Episode( 97 | episodeUrl, 98 | episodeTitle, 99 | episode = episodeTitle.getIntFromText(), 100 | posterUrl = posterUrl 101 | ) 102 | } 103 | return newAnimeLoadResponse(title, url, type) { 104 | this.apiName = this@Anime4up.name 105 | addMalId(malId) 106 | engName = title 107 | posterUrl = poster 108 | this.year = year 109 | addEpisodes(if(title.contains("مدبلج")) DubStatus.Dubbed else DubStatus.Subbed, episodes) 110 | plot = description 111 | this.rating = rating 112 | 113 | } 114 | } 115 | data class sources ( 116 | @JsonProperty("hd" ) var hd : Map? = null, 117 | @JsonProperty("fhd" ) var fhd : Map? = null, 118 | @JsonProperty("sd" ) var sd : Map? = null 119 | ) 120 | 121 | override suspend fun loadLinks( 122 | data: String, 123 | isCasting: Boolean, 124 | subtitleCallback: (SubtitleFile) -> Unit, 125 | callback: (ExtractorLink) -> Unit 126 | ): Boolean { 127 | val doc = app.get(data).document 128 | if(data.contains("anime4up")) { 129 | val watchJSON = parseJson(doc.select("input[name=\"wl\"]").attr("value").decodeBase64()?.utf8() ?: "") 130 | watchJSON.let { source -> 131 | source.fhd?.apmap { 132 | loadExtractor(it.value, data, subtitleCallback, callback) 133 | } 134 | source.hd?.apmap { 135 | loadExtractor(it.value, data, subtitleCallback, callback) 136 | } 137 | source.sd?.apmap { 138 | loadExtractor(it.value, data, subtitleCallback, callback) 139 | } 140 | } 141 | val moshahdaID = doc.select("input[name=\"moshahda\"]").attr("value").decodeBase64()?.utf8() ?: "" 142 | if(moshahdaID.isNotEmpty()) { 143 | mapOf( 144 | "Original" to "download_o", 145 | "720" to "download_x", 146 | "480" to "download_h", 147 | "360" to "download_n", 148 | "240" to "download_l" 149 | ).apmap { (quality, qualityCode) -> 150 | callback.invoke( 151 | ExtractorLink( 152 | this.name, 153 | this.name + " Moshahda", 154 | "https://moshahda.net/$moshahdaID.html?${qualityCode}", 155 | "https://moshahda.net", 156 | quality.toIntOrNull() ?: 1080 157 | ) 158 | ) } 159 | } 160 | } else if(data.contains("witanime")) { // witanime 161 | doc.select("ul#episode-servers li a").apmap { 162 | loadExtractor(it.attr("data-ep-url"), data, subtitleCallback, callback) 163 | } 164 | } 165 | return true 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /AnimeBlkomProvider/build.gradle.kts: -------------------------------------------------------------------------------- 1 | version = 2 2 | 3 | cloudstream { 4 | authors = listOf( "ImZaw" ) 5 | 6 | language = "ar" 7 | 8 | status = 1 9 | 10 | tvTypes = listOf( "Anime" , "AnimeMovie" , "OVA" ) 11 | 12 | iconUrl = "https://www.google.com/s2/favicons?domain=animeblkom.net&sz=%size%" 13 | } -------------------------------------------------------------------------------- /AnimeBlkomProvider/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /AnimeBlkomProvider/src/main/kotlin/com/animeblkom/AnimeBlkomPlugin.kt: -------------------------------------------------------------------------------- 1 | package com.animeblkom 2 | import com.lagradost.cloudstream3.plugins.CloudstreamPlugin 3 | import com.lagradost.cloudstream3.plugins.Plugin 4 | import android.content.Context 5 | 6 | @CloudstreamPlugin 7 | class AnimeBlkomPlugin: Plugin() { 8 | override fun load(context: Context) { 9 | registerMainAPI(AnimeBlkom()) 10 | } 11 | } -------------------------------------------------------------------------------- /AnimeBlkomProvider/src/main/kotlin/com/animeblkom/AnimeBlkomProvider.kt: -------------------------------------------------------------------------------- 1 | package com.animeblkom 2 | 3 | 4 | import com.lagradost.cloudstream3.* 5 | import com.lagradost.cloudstream3.LoadResponse.Companion.addMalId 6 | import com.lagradost.cloudstream3.utils.ExtractorLink 7 | import com.lagradost.cloudstream3.utils.Qualities 8 | import com.lagradost.cloudstream3.utils.loadExtractor 9 | import org.jsoup.nodes.Element 10 | 11 | class AnimeBlkom : MainAPI() { 12 | override var mainUrl = "https://animeblkom.net" 13 | override var name = "AnimeBlkom" 14 | override var lang = "ar" 15 | override val hasMainPage = true 16 | 17 | override val supportedTypes = setOf( 18 | TvType.Anime, 19 | TvType.AnimeMovie, 20 | TvType.OVA, 21 | ) 22 | 23 | private fun Element.toSearchResponse(): SearchResponse { 24 | val url = select("div.poster a").attr("href") 25 | val name = select("div.name a").text() 26 | val poster = mainUrl + select("div.poster img").attr("data-original") 27 | val year = select("div[title=\"سنة الانتاج\"]").text().toIntOrNull() 28 | val episodesNumber = select("div[title=\"عدد الحلقات\"]").text().toIntOrNull() 29 | val tvType = select("div[title=\"النوع\"]").text().let { if(it.contains("فيلم|خاصة".toRegex())) TvType.AnimeMovie else if(it.contains("أوفا|أونا".toRegex())) TvType.OVA else TvType.Anime } 30 | return newAnimeSearchResponse( 31 | name, 32 | url, 33 | tvType, 34 | ) { 35 | addDubStatus(false, episodesNumber) 36 | this.year = year 37 | this.posterUrl = poster 38 | } 39 | } 40 | override val mainPage = mainPageOf( 41 | "$mainUrl/anime-list?sort_by=rate&page=" to "Most rated", 42 | "$mainUrl/anime-list?sort_by=created_at&page=" to "Recently added", 43 | "$mainUrl/anime-list?states=finished&page=" to "Completed" 44 | ) 45 | override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse { 46 | val doc = app.get(request.data + page).document 47 | val list = doc.select("div.content-inner") 48 | .mapNotNull { element -> 49 | element.toSearchResponse() 50 | } 51 | return newHomePageResponse(request.name, list) 52 | } 53 | 54 | override suspend fun search(query: String): List { 55 | val q = query.replace(" ","+") 56 | return app.get("$mainUrl/search?query=$q").document.select("div.content.ratable").map { 57 | it.toSearchResponse() 58 | } 59 | } 60 | override suspend fun load(url: String): LoadResponse { 61 | val doc = app.get(url).document 62 | 63 | val title = doc.select("span h1").text().replace("\\(.*".toRegex(),"") 64 | val poster = mainUrl + doc.select("div.poster img").attr("data-original") 65 | val description = doc.select(".story p").text() 66 | val genre = doc.select("p.genres a").map { 67 | it.text() 68 | } 69 | val year = doc.select(".info-table div:contains(تاريخ الانتاج) span.info").text().split("-")[0].toIntOrNull() 70 | val status = doc.select(".info-table div:contains(حالة الأنمي) span.info").text().let { if(it.contains("مستمر")) ShowStatus.Ongoing else ShowStatus.Completed } 71 | val nativeName = doc.select("span[title=\"الاسم باليابانية\"]").text().replace(".*:".toRegex(),"") 72 | val type = doc.select("h1 small").text().let { 73 | if (it.contains("movie")) TvType.AnimeMovie 74 | if (it.contains("ova|ona".toRegex())) TvType.OVA 75 | else TvType.Anime 76 | } 77 | 78 | val malId = doc.select("a.blue.cta:contains(المزيد من المعلومات)").attr("href").replace(".*e\\/|\\/.*".toRegex(),"").toInt() 79 | val episodes = arrayListOf() 80 | val episodeElements = doc.select(".episode-link") 81 | if(episodeElements.isEmpty()) { 82 | episodes.add(Episode( 83 | url, 84 | "Watch", 85 | )) 86 | } else { 87 | episodeElements.map { 88 | val a = it.select("a") 89 | episodes.add(Episode( 90 | mainUrl + a.attr("href"), 91 | a.text().replace(":"," "), 92 | episode = a.select("span").not(".pull-left").last()?.text()?.toIntOrNull() 93 | )) 94 | } 95 | } 96 | return newAnimeLoadResponse(title, url, type) { 97 | addMalId(malId) 98 | japName = nativeName 99 | engName = title 100 | posterUrl = poster 101 | this.year = year 102 | addEpisodes(DubStatus.Subbed, episodes) // TODO CHECK 103 | plot = description 104 | tags = genre 105 | 106 | showStatus = status 107 | } 108 | } 109 | override suspend fun loadLinks( 110 | data: String, 111 | isCasting: Boolean, 112 | subtitleCallback: (SubtitleFile) -> Unit, 113 | callback: (ExtractorLink) -> Unit 114 | ): Boolean { 115 | val doc = app.get(data).document 116 | doc.select("div.item a[data-src]").map { 117 | it.attr("data-src").let { url -> 118 | if(url.startsWith("https://animetitans.net/")) { 119 | val iframe = app.get(url).document 120 | callback.invoke( 121 | ExtractorLink( 122 | this.name, 123 | "Animetitans " + it.text(), 124 | iframe.select("script").last()?.data()?.substringAfter("source: \"")?.substringBefore("\"").toString(), 125 | this.mainUrl, 126 | Qualities.Unknown.value, 127 | isM3u8 = true 128 | ) 129 | ) 130 | } else if(it.text() == "Blkom") { 131 | val iframe = app.get(url).document 132 | iframe.select("source").forEach { source -> 133 | callback.invoke( 134 | ExtractorLink( 135 | this.name, 136 | it.text(), 137 | source.attr("src"), 138 | this.mainUrl, 139 | source.attr("res").toInt() 140 | ) 141 | ) 142 | } 143 | } else { 144 | var sourceUrl = url 145 | if(it.text().contains("Google")) sourceUrl = "http://gdriveplayer.to/embed2.php?link=$url" 146 | loadExtractor(sourceUrl, mainUrl, subtitleCallback, callback) 147 | } 148 | } 149 | } 150 | doc.select(".panel .panel-body a").apmap { 151 | println(it.text()) 152 | callback.invoke( 153 | ExtractorLink( 154 | this.name, 155 | it.attr("title") + " " + it.select("small").text() + " Download Source", 156 | it.attr("href"), 157 | this.mainUrl, 158 | it.text().replace("p.*| ".toRegex(),"").toInt(), 159 | ) 160 | ) 161 | } 162 | return true 163 | } 164 | } -------------------------------------------------------------------------------- /AnimeiatProvider/build.gradle.kts: -------------------------------------------------------------------------------- 1 | version = 6 2 | 3 | cloudstream { 4 | authors = listOf( "ImZaw" ) 5 | 6 | language = "ar" 7 | 8 | status = 1 9 | 10 | tvTypes = listOf( "Anime" ) 11 | 12 | iconUrl = "https://www.google.com/s2/favicons?domain=animeiat.tv&sz=%size%" 13 | } 14 | -------------------------------------------------------------------------------- /AnimeiatProvider/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /AnimeiatProvider/src/main/kotlin/com/animeiat/AnimeiatPlugin.kt: -------------------------------------------------------------------------------- 1 | package com.animeiat 2 | import com.lagradost.cloudstream3.plugins.CloudstreamPlugin 3 | import com.lagradost.cloudstream3.plugins.Plugin 4 | import android.content.Context 5 | 6 | @CloudstreamPlugin 7 | class AnimeiatPlugin: Plugin() { 8 | override fun load(context: Context) { 9 | registerMainAPI(Animeiat()) 10 | } 11 | } -------------------------------------------------------------------------------- /AnimeiatProvider/src/main/kotlin/com/animeiat/AnimeiatProvider.kt: -------------------------------------------------------------------------------- 1 | package com.animeiat 2 | 3 | 4 | import com.lagradost.cloudstream3.* 5 | import com.lagradost.cloudstream3.utils.ExtractorLink 6 | import com.lagradost.cloudstream3.utils.loadExtractor 7 | import com.fasterxml.jackson.annotation.JsonProperty 8 | import com.lagradost.cloudstream3.utils.AppUtils.parseJson 9 | import com.lagradost.nicehttp.Requests 10 | 11 | class Animeiat : MainAPI() { 12 | override var lang = "ar" 13 | override var mainUrl = "https://api.animeiat.co/v1" 14 | val pageUrl = "https://www.animeiat.tv" 15 | override var name = "Animeiat" 16 | override val usesWebView = false 17 | override val hasMainPage = true 18 | override val supportedTypes = 19 | setOf(TvType.Anime, TvType.AnimeMovie) 20 | 21 | data class Data ( 22 | @JsonProperty("anime_name" ) var animeName : String? = null, 23 | @JsonProperty("title" ) var title : String? = null, 24 | @JsonProperty("slug" ) var slug : String? = null, 25 | @JsonProperty("story" ) var story : String? = null, 26 | @JsonProperty("other_names" ) var otherNames : String? = null, 27 | @JsonProperty("total_episodes" ) var totalEpisodes : Int? = null, 28 | @JsonProperty("number" ) var number : Int? = null, 29 | @JsonProperty("age" ) var age : String? = null, 30 | @JsonProperty("type" ) var type : String? = null, 31 | @JsonProperty("status" ) var status : String? = null, 32 | @JsonProperty("poster_path" ) var posterPath : String? = null, 33 | ) 34 | data class All ( 35 | @JsonProperty("data" ) var data : ArrayList = arrayListOf(), 36 | ) 37 | 38 | override val mainPage = mainPageOf( 39 | "$mainUrl/home/sticky-episodes?page=" to "Episodes (H)", 40 | "$mainUrl/anime?status=completed&page=" to "Completed", 41 | "$mainUrl/anime?status=ongoing&page=" to "Ongoing", 42 | ) 43 | 44 | override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse { 45 | val json = parseJson(app.get(request.data + page).text) 46 | val list = json.data.map { 47 | newAnimeSearchResponse( 48 | it.animeName ?: it.title.toString(), 49 | mainUrl + "/anime/" + it.slug.toString().replace("-episode.*".toRegex(),""), 50 | if (it.type == "movie") TvType.AnimeMovie else if (it.type == "tv") TvType.Anime else TvType.OVA, 51 | ) { 52 | addDubStatus(false, it.totalEpisodes ?: it.number) 53 | this.otherName = it.otherNames?.split("\n")?.last() 54 | this.posterUrl = "https://api.animeiat.co/storage/" + it.posterPath 55 | } 56 | } 57 | return if(request.name.contains("(H)")) HomePageResponse( 58 | arrayListOf(HomePageList(request.name.replace(" (H)",""), list, request.name.contains("(H)"))) 59 | ) else newHomePageResponse(request.name, list) 60 | } 61 | 62 | override suspend fun search(query: String): List { 63 | val json = parseJson(app.get("$mainUrl/anime?q=$query").text) 64 | return json.data.map { 65 | newAnimeSearchResponse( 66 | it.animeName.toString(), 67 | mainUrl + "/anime/" + it.slug.toString(), 68 | if(it.type == "movie") TvType.AnimeMovie else if(it.type == "tv") TvType.Anime else TvType.OVA, 69 | ) { 70 | addDubStatus(false, it.totalEpisodes) 71 | this.otherName = it.otherNames?.split("\n")?.last() 72 | this.posterUrl = "https://api.animeiat.co/storage/" + it.posterPath 73 | } 74 | } 75 | 76 | } 77 | 78 | data class Year ( 79 | @JsonProperty("name" ) var name : String? = null, 80 | 81 | ) 82 | data class Genres ( 83 | @JsonProperty("name" ) var name : String? = null, 84 | ) 85 | data class LoadData ( 86 | @JsonProperty("anime_name" ) var animeName : String? = null, 87 | @JsonProperty("slug" ) var slug : String? = null, 88 | @JsonProperty("story" ) var story : String? = null, 89 | @JsonProperty("other_names" ) var otherNames : String? = null, 90 | @JsonProperty("age" ) var age : String? = null, 91 | @JsonProperty("type" ) var type : String? = null, 92 | @JsonProperty("status" ) var status : String? = null, 93 | @JsonProperty("poster_path" ) var posterPath : String? = null, 94 | @JsonProperty("year" ) var year : Year? = Year(), 95 | @JsonProperty("genres" ) var genres : ArrayList = arrayListOf(), 96 | 97 | ) 98 | data class Load ( 99 | 100 | @JsonProperty("data" ) var data : LoadData? = LoadData() 101 | 102 | ) 103 | data class Meta ( 104 | @JsonProperty("last_page" ) var lastPage : Int? = null, 105 | ) 106 | data class EpisodeData ( 107 | @JsonProperty("title" ) var title : String? = null, 108 | @JsonProperty("slug" ) var slug : String? = null, 109 | @JsonProperty("number" ) var number : Int? = null, 110 | @JsonProperty("video_id" ) var videoId : Int? = null, 111 | @JsonProperty("poster_path" ) var posterPath : String? = null, 112 | ) 113 | data class Episodes ( 114 | @JsonProperty("data" ) var data : ArrayList = arrayListOf(), 115 | @JsonProperty("meta" ) var meta : Meta = Meta() 116 | ) 117 | override suspend fun load(url: String): LoadResponse { 118 | val loadSession = Requests() 119 | val request = loadSession.get(url.replace(pageUrl, mainUrl)).text 120 | val json = parseJson(request) 121 | val episodes = arrayListOf() 122 | (1..parseJson(loadSession.get("$url/episodes").text).meta.lastPage!!).map { pageNumber -> 123 | parseJson(loadSession.get("$url/episodes?page=$pageNumber").text).data.map { 124 | episodes.add( 125 | Episode( 126 | "$pageUrl/watch/"+it.slug, 127 | it.title, 128 | null, 129 | it.number, 130 | "https://api.animeiat.co/storage/" + it.posterPath, 131 | 132 | ) 133 | ) 134 | } 135 | } 136 | return newAnimeLoadResponse(json.data?.animeName.toString(), "$mainUrl/anime/"+json.data?.slug, if(json.data?.type == "movie") TvType.AnimeMovie else if(json.data?.type == "tv") TvType.Anime else TvType.OVA) { 137 | japName = json.data?.otherNames?.replace("\\n.*".toRegex(), "") 138 | engName = json.data?.animeName 139 | posterUrl = "https://api.animeiat.co/storage/" + json.data?.posterPath 140 | this.year = json.data?.year?.name?.toIntOrNull() 141 | addEpisodes(DubStatus.Subbed, episodes) 142 | plot = json.data?.story 143 | tags = json.data?.genres?.map { it.name.toString() } 144 | this.showStatus = if(json.data?.status == "completed") ShowStatus.Completed else ShowStatus.Ongoing 145 | } 146 | } 147 | 148 | override suspend fun loadLinks( 149 | data: String, 150 | isCasting: Boolean, 151 | subtitleCallback: (SubtitleFile) -> Unit, 152 | callback: (ExtractorLink) -> Unit 153 | ): Boolean { 154 | val url = if(data.contains("-episode")) data else "$data-episode-1" 155 | println(url) 156 | val doc = app.get(url).document 157 | val script = doc.select("body > script").first()?.html() 158 | val id = script?.replace(".*4\",slug:\"|\",duration:.*".toRegex(),"") 159 | val player = app.get("$pageUrl/player/$id").document 160 | player.select("source").map { 161 | callback.invoke( 162 | ExtractorLink( 163 | this.name, 164 | this.name, 165 | it.attr("src"), 166 | pageUrl, 167 | it.attr("size").toInt(), 168 | ) 169 | ) 170 | } 171 | return true 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /ArabSeedProvider/build.gradle.kts: -------------------------------------------------------------------------------- 1 | version = 3 2 | 3 | cloudstream { 4 | authors = listOf( "ImZaw" ) 5 | 6 | language = "ar" 7 | 8 | status = 1 9 | 10 | tvTypes = listOf( "TvSeries" , "Movie" ) 11 | 12 | iconUrl = "https://www.google.com/s2/favicons?domain=arabseed.ink&sz=%size%" 13 | } 14 | -------------------------------------------------------------------------------- /ArabSeedProvider/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /ArabSeedProvider/src/main/kotlin/com/arabseed/ArabSeedPlugin.kt: -------------------------------------------------------------------------------- 1 | package com.arabseed 2 | import com.lagradost.cloudstream3.plugins.CloudstreamPlugin 3 | import com.lagradost.cloudstream3.plugins.Plugin 4 | import android.content.Context 5 | 6 | @CloudstreamPlugin 7 | class ArabSeedPlugin: Plugin() { 8 | override fun load(context: Context) { 9 | registerMainAPI(ArabSeed()) 10 | } 11 | } -------------------------------------------------------------------------------- /ArabSeedProvider/src/main/kotlin/com/arabseed/ArabSeedProvider.kt: -------------------------------------------------------------------------------- 1 | package com.arabseed 2 | 3 | import com.lagradost.cloudstream3.* 4 | import com.lagradost.cloudstream3.utils.ExtractorLink 5 | import com.lagradost.cloudstream3.utils.loadExtractor 6 | import org.jsoup.nodes.Element 7 | 8 | class ArabSeed : MainAPI() { 9 | override var lang = "ar" 10 | override var mainUrl = "https://e.arabseed.ink" 11 | override var name = "ArabSeed" 12 | override val usesWebView = false 13 | override val hasMainPage = true 14 | override val supportedTypes = setOf(TvType.TvSeries, TvType.Movie) 15 | 16 | private fun String.getIntFromText(): Int? { 17 | return Regex("""\d+""").find(this)?.groupValues?.firstOrNull()?.toIntOrNull() 18 | } 19 | 20 | 21 | private fun Element.toSearchResponse(): SearchResponse? { 22 | val title = select("h4").text() 23 | val posterUrl = select("img.imgOptimzer").attr("data-image").ifEmpty { select("div.Poster img").attr("data-src") } 24 | val tvType = if (select("span.category").text().contains("مسلسلات")) TvType.TvSeries else TvType.Movie 25 | return MovieSearchResponse( 26 | title, 27 | select("a").attr("href"), 28 | this@ArabSeed.name, 29 | tvType, 30 | posterUrl, 31 | ) 32 | } 33 | 34 | override val mainPage = mainPageOf( 35 | "$mainUrl/movies/?offset=" to "Movies", 36 | "$mainUrl/series/?offset=" to "Series", 37 | ) 38 | 39 | override suspend fun getMainPage( 40 | page: Int, 41 | request: MainPageRequest 42 | ): HomePageResponse { 43 | val document = app.get(request.data + page, timeout = 120).document 44 | val home = document.select("ul.Blocks-UL > div").mapNotNull { 45 | it.toSearchResponse() 46 | } 47 | return newHomePageResponse(request.name, home) 48 | } 49 | 50 | override suspend fun search(query: String): List { 51 | val list = arrayListOf() 52 | arrayListOf( 53 | mainUrl to "series", 54 | mainUrl to "movies" 55 | ).apmap { (url, type) -> 56 | val doc = app.post( 57 | "$url/wp-content/themes/Elshaikh2021/Ajaxat/SearchingTwo.php", 58 | data = mapOf("search" to query, "type" to type), 59 | referer = mainUrl 60 | ).document 61 | doc.select("ul.Blocks-UL > div").mapNotNull { 62 | it.toSearchResponse()?.let { it1 -> list.add(it1) } 63 | } 64 | } 65 | return list 66 | } 67 | 68 | override suspend fun load(url: String): LoadResponse { 69 | val doc = app.get(url, timeout = 120).document 70 | val title = doc.select("h1.Title").text().ifEmpty { doc.select("div.Title").text() } 71 | val isMovie = title.contains("فيلم") 72 | 73 | val posterUrl = doc.select("div.Poster > img").let{ it.attr("data-src").ifEmpty { it.attr("src") } } 74 | val rating = doc.select("div.RatingImdb em").text().getIntFromText() 75 | val synopsis = doc.select("p.descrip").last()?.text() 76 | val year = doc.select("li:contains(السنه) a").text().getIntFromText() 77 | val tags = doc.select("li:contains(النوع) > a, li:contains(التصنيف) > a")?.map { it.text() } 78 | 79 | val actors = doc.select("div.WorkTeamIteM").mapNotNull { 80 | val name = it.selectFirst("h4 > em")?.text() ?: return@mapNotNull null 81 | val image = it.selectFirst("div.Icon img")?.attr("src") ?: return@mapNotNull null 82 | val roleString = it.select("h4 > span").text() 83 | val mainActor = Actor(name, image) 84 | ActorData(actor = mainActor, roleString = roleString) 85 | } 86 | 87 | val recommendations = doc.select("ul.Blocks-UL > div").mapNotNull { element -> 88 | element.toSearchResponse() 89 | } 90 | 91 | return if (isMovie) { 92 | newMovieLoadResponse( 93 | title, 94 | url, 95 | TvType.Movie, 96 | url 97 | ) { 98 | this.posterUrl = posterUrl 99 | this.recommendations = recommendations 100 | this.plot = synopsis 101 | this.tags = tags 102 | this.actors = actors 103 | this.rating = rating 104 | this.year = year 105 | } 106 | } else { 107 | val seasonList = doc.select("div.SeasonsListHolder ul > li") 108 | val episodes = arrayListOf() 109 | if(seasonList.isNotEmpty()) { 110 | seasonList.apmap { season -> 111 | app.post( 112 | "$mainUrl/wp-content/themes/Elshaikh2021/Ajaxat/Single/Episodes.php", 113 | data = mapOf("season" to season.attr("data-season"), "post_id" to season.attr("data-id")) 114 | ).document.select("a").apmap { 115 | episodes.add(Episode( 116 | it.attr("href"), 117 | it.text(), 118 | season.attr("data-season")[0].toString().toIntOrNull(), 119 | it.text().getIntFromText() 120 | )) 121 | } 122 | } 123 | } else { 124 | doc.select("div.ContainerEpisodesList > a").apmap { 125 | episodes.add(Episode( 126 | it.attr("href"), 127 | it.text(), 128 | 0, 129 | it.text().getIntFromText() 130 | )) 131 | } 132 | } 133 | newTvSeriesLoadResponse(title, url, TvType.TvSeries, episodes.distinct().sortedBy { it.episode }) { 134 | this.posterUrl = posterUrl 135 | this.tags = tags 136 | this.plot = synopsis 137 | this.actors = actors 138 | this.recommendations = recommendations 139 | this.rating = rating 140 | this.year = year 141 | } 142 | } 143 | } 144 | 145 | override suspend fun loadLinks( 146 | data: String, 147 | isCasting: Boolean, 148 | subtitleCallback: (SubtitleFile) -> Unit, 149 | callback: (ExtractorLink) -> Unit 150 | ): Boolean { 151 | val doc = app.get(data).document 152 | val watchUrl = doc.select("a.watchBTn").attr("href") 153 | val watchDoc = app.get(watchUrl, headers = mapOf("Referer" to mainUrl)).document 154 | val indexOperators = arrayListOf() 155 | val list: List = watchDoc.select("ul > li[data-link], ul > h3").mapIndexed { index, element -> 156 | if(element.`is`("h3")) { 157 | indexOperators.add(index) 158 | element 159 | } else element 160 | } 161 | var watchLinks: List>>; 162 | if(indexOperators.isNotEmpty()) { 163 | watchLinks = indexOperators.mapIndexed { index, it -> 164 | var endIndex = list.size 165 | if (index != indexOperators.size - 1) endIndex = (indexOperators[index + 1]) - 1 166 | list[it].text().getIntFromText() as Int to list.subList(it + 1, endIndex) as List 167 | } 168 | } else { 169 | watchLinks = arrayListOf(0 to list) 170 | } 171 | watchLinks.apmap { (quality, links) -> 172 | links.apmap { 173 | val iframeUrl = it.attr("data-link") 174 | println(iframeUrl) 175 | if(it.text().contains("عرب سيد")) { 176 | val sourceElement = app.get(iframeUrl).document.select("source") 177 | callback.invoke( 178 | ExtractorLink( 179 | this.name, 180 | "ArabSeed", 181 | sourceElement.attr("src"), 182 | data, 183 | if(quality != 0) quality else it.text().replace(".*- ".toRegex(), "").replace("\\D".toRegex(),"").toInt(), 184 | !sourceElement.attr("type").contains("mp4") 185 | ) 186 | ) 187 | } else loadExtractor(iframeUrl, data, subtitleCallback, callback) 188 | } 189 | } 190 | return true 191 | } 192 | } -------------------------------------------------------------------------------- /CimaNowProvider/build.gradle.kts: -------------------------------------------------------------------------------- 1 | version = 1 2 | 3 | cloudstream { 4 | authors = listOf( "ImZaw" ) 5 | 6 | language = "ar" 7 | 8 | status = 1 9 | 10 | tvTypes = listOf( "TvSeries" , "Movie" ) 11 | 12 | iconUrl = "https://www.google.com/s2/favicons?domain=cimanow.cc&sz=%size%" 13 | } -------------------------------------------------------------------------------- /CimaNowProvider/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /CimaNowProvider/src/main/kotlin/com/cimanow/CimaNowPlugin.kt: -------------------------------------------------------------------------------- 1 | package com.cimanow 2 | import com.lagradost.cloudstream3.plugins.CloudstreamPlugin 3 | import com.lagradost.cloudstream3.plugins.Plugin 4 | import android.content.Context 5 | 6 | @CloudstreamPlugin 7 | class CimaNowPlugin: Plugin() { 8 | override fun load(context: Context) { 9 | registerMainAPI(CimaNow()) 10 | } 11 | } -------------------------------------------------------------------------------- /CimaNowProvider/src/main/kotlin/com/cimanow/CimaNowProvider.kt: -------------------------------------------------------------------------------- 1 | package com.cimanow 2 | 3 | 4 | import com.lagradost.cloudstream3.* 5 | import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer 6 | import com.lagradost.cloudstream3.utils.ExtractorLink 7 | import com.lagradost.cloudstream3.utils.Qualities 8 | import org.jsoup.nodes.Element 9 | 10 | class CimaNow : MainAPI() { 11 | override var lang = "ar" 12 | override var mainUrl = "https://cimanow.cc" 13 | override var name = "CimaNow" 14 | override val usesWebView = false 15 | override val hasMainPage = true 16 | override val supportedTypes = setOf(TvType.TvSeries, TvType.Movie) 17 | 18 | private fun String.getIntFromText(): Int? { 19 | return Regex("""\d+""").find(this)?.groupValues?.firstOrNull()?.toIntOrNull() 20 | } 21 | 22 | private fun Element.toSearchResponse(): SearchResponse? { 23 | val url = this.attr("href") 24 | val posterUrl = select("img")?.attr("data-src") 25 | var title = select("li[aria-label=\"title\"]").html().replace(" .*|\\\\n".toRegex(), "").replace(" ", "") 26 | val year = select("li[aria-label=\"year\"]").text().toIntOrNull() 27 | val tvType = if (url.contains("فيلم|مسرحية|حفلات".toRegex())) TvType.Movie else TvType.TvSeries 28 | val quality = select("li[aria-label=\"ribbon\"]").first()?.text()?.replace(" |-|1080|720".toRegex(), "") 29 | val dubEl = select("li[aria-label=\"ribbon\"]:nth-child(2)").isNotEmpty() 30 | val dubStatus = if(dubEl) select("li[aria-label=\"ribbon\"]:nth-child(2)").text().contains("مدبلج") 31 | else select("li[aria-label=\"ribbon\"]:nth-child(1)").text().contains("مدبلج") 32 | if(dubStatus) title = "$title (مدبلج)" 33 | return MovieSearchResponse( 34 | "$title ${select("li[aria-label=\"ribbon\"]:contains(الموسم)").text()}", 35 | url, 36 | this@CimaNow.name, 37 | tvType, 38 | posterUrl, 39 | year, 40 | null, 41 | quality = getQualityFromString(quality) 42 | ) 43 | } 44 | 45 | override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse { 46 | 47 | val doc = app.get("$mainUrl/home", headers = mapOf("user-agent" to "MONKE")).document 48 | val pages = doc.select("section").not("section:contains(أختر وجهتك المفضلة)").not("section:contains(تم اضافته حديثاً)").apmap { 49 | val name = it.select("span").html().replace(".*| { 60 | val result = arrayListOf() 61 | val doc = app.get("$mainUrl/page/1/?s=$query").document 62 | val paginationElement = doc.select("ul[aria-label=\"pagination\"]") 63 | doc.select("section article a").map { 64 | val postUrl = it.attr("href") 65 | if(it.select("li[aria-label=\"episode\"]").isNotEmpty()) return@map 66 | if(postUrl.contains("$mainUrl/expired-download/|$mainUrl/افلام-اون-لاين/".toRegex())) return@map 67 | result.add(it.toSearchResponse()!!) 68 | } 69 | if(paginationElement.isNotEmpty()) { 70 | val max = paginationElement.select("li").not("li.active").last()?.text()?.toIntOrNull() 71 | if (max != null) { 72 | if(max > 5) return result.distinct().sortedBy { it.name } 73 | (2..max!!).toList().apmap { 74 | app.get("$mainUrl/page/$it/?s=$query\"").document.select("section article a").map { element -> 75 | val postUrl = element.attr("href") 76 | if(element.select("li[aria-label=\"episode\"]").isNotEmpty()) return@map 77 | if(postUrl.contains("$mainUrl/expired-download/|$mainUrl/افلام-اون-لاين/".toRegex())) return@map 78 | result.add(element.toSearchResponse()!!) 79 | } 80 | } 81 | } 82 | } 83 | return result.distinct().sortedBy { it.name } 84 | } 85 | 86 | override suspend fun load(url: String): LoadResponse { 87 | val doc = app.get(url).document 88 | val posterUrl = doc.select("body > script:nth-child(3)").html().replace(".*,\"image\":\"|\".*".toRegex(),"").ifEmpty { doc.select("meta[property=\"og:image\"]").attr("content") } 89 | val year = doc.select("article ul:nth-child(1) li a").last()?.text()?.toIntOrNull() 90 | val title = doc.select("title").text().split(" | ")[0] 91 | val isMovie = title.contains("فيلم|حفلات|مسرحية".toRegex()) 92 | val youtubeTrailer = doc.select("iframe")?.attr("src") 93 | 94 | val synopsis = doc.select("ul#details li:contains(لمحة) p").text() 95 | 96 | val tags = doc.select("article ul").first()?.select("li")?.map { it.text() } 97 | 98 | val recommendations = doc.select("ul#related li").map { element -> 99 | MovieSearchResponse( 100 | apiName = this@CimaNow.name, 101 | url = element.select("a").attr("href"), 102 | name = element.select("img:nth-child(2)").attr("alt"), 103 | posterUrl = element.select("img:nth-child(2)").attr("src") 104 | ) 105 | } 106 | 107 | return if (isMovie) { 108 | newMovieLoadResponse( 109 | title, 110 | url, 111 | TvType.Movie, 112 | "$url/watching" 113 | ) { 114 | this.posterUrl = posterUrl 115 | this.year = year 116 | this.recommendations = recommendations 117 | this.plot = synopsis 118 | this.tags = tags 119 | addTrailer(youtubeTrailer) 120 | } 121 | } else { 122 | val episodes = doc.select("ul#eps li").map { episode -> 123 | Episode( 124 | episode.select("a").attr("href")+"/watching", 125 | episode.select("a img:nth-child(2)").attr("alt"), 126 | doc.select("span[aria-label=\"season-title\"]").html().replace("

.*|\n".toRegex(), "").getIntFromText(), 127 | episode.select("a em").text().toIntOrNull(), 128 | episode.select("a img:nth-child(2)").attr("src") 129 | ) 130 | } 131 | newTvSeriesLoadResponse(title, url, TvType.TvSeries, episodes.distinct().sortedBy { it.episode }) { 132 | this.posterUrl = posterUrl 133 | this.tags = tags 134 | this.year = year 135 | this.plot = synopsis 136 | this.recommendations = recommendations 137 | addTrailer(youtubeTrailer) 138 | } 139 | } 140 | } 141 | 142 | override suspend fun loadLinks( 143 | data: String, 144 | isCasting: Boolean, 145 | subtitleCallback: (SubtitleFile) -> Unit, 146 | callback: (ExtractorLink) -> Unit 147 | ): Boolean { 148 | app.get(data).document.select("ul#download [aria-label=\"quality\"]").forEach { 149 | val name = if(it.select("span").text().contains("فائق السرعة")) "Fast Servers" else "Servers" 150 | it.select("a").forEach { media -> 151 | callback.invoke( 152 | ExtractorLink( 153 | source = this.name, 154 | name = name, 155 | url = media.attr("href"), 156 | referer = this.mainUrl, 157 | quality = media.text().getIntFromText() ?: Qualities.Unknown.value 158 | ) 159 | ) 160 | } 161 | } 162 | return true 163 | } 164 | } -------------------------------------------------------------------------------- /EgyBestProvider/build.gradle.kts: -------------------------------------------------------------------------------- 1 | version = 7 2 | 3 | cloudstream { 4 | description = "Egybest is broken" 5 | authors = listOf( "ImZaw" ) 6 | 7 | language = "ar" 8 | 9 | status = 0 10 | 11 | tvTypes = listOf( "TvSeries" , "Movie" , "Anime" ) 12 | 13 | iconUrl = "https://www.google.com/s2/favicons?domain=www.egy.best&sz=%size%" 14 | } 15 | -------------------------------------------------------------------------------- /EgyBestProvider/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /EgyBestProvider/src/main/kotlin/com/egybest/EgyBestPlugin.kt: -------------------------------------------------------------------------------- 1 | package com.egybest 2 | import com.lagradost.cloudstream3.plugins.CloudstreamPlugin 3 | import com.lagradost.cloudstream3.plugins.Plugin 4 | import android.content.Context 5 | 6 | @CloudstreamPlugin 7 | class EgyBestPlugin: Plugin() { 8 | override fun load(context: Context) { 9 | registerMainAPI(EgyBest()) 10 | } 11 | } -------------------------------------------------------------------------------- /EgyBestProvider/src/main/kotlin/com/egybest/EgyBestProvider.kt: -------------------------------------------------------------------------------- 1 | package com.egybest 2 | 3 | 4 | import android.annotation.TargetApi 5 | import android.os.Build 6 | import com.lagradost.cloudstream3.* 7 | import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer 8 | import com.lagradost.cloudstream3.utils.ExtractorLink 9 | import com.lagradost.cloudstream3.utils.M3u8Helper 10 | import com.lagradost.nicehttp.Requests 11 | import com.lagradost.nicehttp.Session 12 | import okhttp3.HttpUrl.Companion.toHttpUrl 13 | import org.jsoup.nodes.Element 14 | import java.util.Base64 15 | import org.mozilla.javascript.Context 16 | import org.mozilla.javascript.Scriptable 17 | 18 | fun String.runJS(variableName: String): String { 19 | val rhino = Context.enter() 20 | rhino.initSafeStandardObjects() 21 | rhino.optimizationLevel = -1 22 | val scope: Scriptable = rhino.initSafeStandardObjects() 23 | val script = this 24 | val result: String 25 | try { 26 | var js = "" 27 | for (i in script.indices) { 28 | js += script[i] 29 | } 30 | rhino.evaluateString(scope, js, "JavaScript", 1, null) 31 | result = Context.toString(scope.get(variableName, scope)) 32 | } finally { 33 | Context.exit() 34 | } 35 | return result 36 | } 37 | 38 | class EgyBest : MainAPI() { 39 | override var lang = "ar" 40 | override var mainUrl = "https://egybest.org" 41 | override var name = "EgyBest" 42 | var pssid = "" 43 | override val usesWebView = false 44 | override val hasMainPage = true 45 | override val supportedTypes = setOf(TvType.TvSeries, TvType.Movie, TvType.Anime) 46 | 47 | private fun String.getIntFromText(): Int? { 48 | return Regex("""\d+""").find(this)?.groupValues?.firstOrNull()?.toIntOrNull() 49 | } 50 | 51 | private fun Element.toSearchResponse(): SearchResponse? { 52 | val url = this.attr("href") ?: return null 53 | val posterUrl = select("img")?.attr("src") 54 | var title = select("span.title").text() 55 | val year = title.getYearFromTitle() 56 | val isMovie = Regex(".*/movie/.*|.*/masrahiya/.*").matches(url) 57 | val tvType = if (isMovie) TvType.Movie else TvType.TvSeries 58 | title = if (year !== null) title else title.split(" (")[0].trim() 59 | val quality = select("span.ribbon span").text().replace("-", "") 60 | // If you need to differentiate use the url. 61 | return MovieSearchResponse( 62 | title, 63 | mainUrl + url, 64 | this@EgyBest.name, 65 | tvType, 66 | posterUrl, 67 | year, 68 | null, 69 | quality = getQualityFromString(quality) 70 | ) 71 | } 72 | 73 | override val mainPage = mainPageOf( 74 | "$mainUrl/trending/?page=" to "الأفلام الأكثر مشاهدة", 75 | "$mainUrl/movies/?page=" to "أفلام جديدة", 76 | "$mainUrl/tv/?page=" to "مسلسلات جديدة ", 77 | "$mainUrl/tv/korean?page=" to "الدراما الكورية ", 78 | "$mainUrl/animes/popular?page=" to "مسلسلات الانمي", 79 | "$mainUrl/wwe/?page=" to "عروض المصارعة ", 80 | "$mainUrl/movies/latest-bluray-2020-2019?page=" to "أفلام جديدة BluRay", 81 | "$mainUrl/masrahiyat/?page=" to "مسرحيات ", 82 | "$mainUrl/movies/latest?page=" to "أحدث الاضافات", 83 | "$mainUrl/movies/comedy?page=" to "أفلام كوميدية", 84 | "$mainUrl/explore/?q=superhero/" to "أفلام سوبر هيرو", 85 | "$mainUrl/movies/animation?page=" to "أفلام انمي و كرتون", 86 | "$mainUrl/movies/romance?page=" to "أفلام رومانسية", 87 | "$mainUrl/movies/drama?page=" to "أفلام دراما", 88 | "$mainUrl/movies/horror?page=" to "أفلام رعب", 89 | "$mainUrl/movies/documentary?page=" to "أفلام وثائقية", 90 | "$mainUrl/World-War-Movies/?page=" to "أفلام عن الحرب العالمية ☢", 91 | "$mainUrl/End-Of-The-World-Movies/?page=" to "أفلام عن نهاية العالم", 92 | "$mainUrl/movies/arab?page=" to "أفلام عربية ", 93 | ) 94 | 95 | override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse { 96 | val doc = app.get(request.data + page).document 97 | val list = doc.select(".movie") 98 | .mapNotNull { element -> 99 | element.toSearchResponse() 100 | } 101 | return newHomePageResponse(request.name, list) 102 | } 103 | 104 | override suspend fun search(query: String): List { 105 | val result = arrayListOf() 106 | listOf("$mainUrl/explore/?q=$query").apmap { url -> 107 | val d = app.get(url).document 108 | d.select("div.movies a").not("a.auto.load.btn.b").mapNotNull { 109 | it.toSearchResponse()?.let { it1 -> result.add(it1) } 110 | } 111 | } 112 | return result.distinct().sortedBy { it.name } 113 | } 114 | 115 | private fun String.getYearFromTitle(): Int? { 116 | return Regex("""\(\d{4}\)""").find(this)?.groupValues?.firstOrNull()?.toIntOrNull() 117 | } 118 | 119 | override suspend fun load(url: String): LoadResponse { 120 | val doc = app.get(url).document 121 | val isMovie = Regex(".*/movie/.*|.*/masrahiya/.*").matches(url) 122 | val posterUrl = doc.select("div.movie_img a img")?.attr("src") 123 | val year = doc.select("div.movie_title h1 a")?.text()?.toIntOrNull() 124 | val title = doc.select("div.movie_title h1 span").text() 125 | val youtubeTrailer = doc.select("div.play")?.attr("url") 126 | 127 | val synopsis = doc.select("div.mbox").firstOrNull { 128 | it.text().contains("القصة") 129 | }?.text()?.replace("القصة ", "") 130 | 131 | val tags = doc.select("table.movieTable tbody tr").firstOrNull { 132 | it.text().contains("النوع") 133 | }?.select("a")?.map { it.text() } 134 | 135 | val actors = doc.select("div.cast_list .cast_item").mapNotNull { 136 | val name = it.selectFirst("div > a > img")?.attr("alt") ?: return@mapNotNull null 137 | val image = it.selectFirst("div > a > img")?.attr("src") ?: return@mapNotNull null 138 | val roleString = it.selectFirst("div > span")!!.text() 139 | val mainActor = Actor(name, image) 140 | ActorData(actor = mainActor, roleString = roleString) 141 | } 142 | 143 | return if (isMovie) { 144 | val recommendations = doc.select(".movies_small .movie").mapNotNull { element -> 145 | element.toSearchResponse() 146 | } 147 | 148 | newMovieLoadResponse( 149 | title, 150 | url, 151 | TvType.Movie, 152 | url 153 | ) { 154 | this.posterUrl = posterUrl 155 | this.year = year 156 | this.recommendations = recommendations 157 | this.plot = synopsis 158 | this.tags = tags 159 | this.actors = actors 160 | addTrailer(youtubeTrailer) 161 | } 162 | } else { 163 | val episodes = ArrayList() 164 | doc.select("#mainLoad > div:nth-child(2) > div.h_scroll > div a").map { 165 | it.attr("href") 166 | }.apmap { 167 | val d = app.get(it).document 168 | val season = Regex("season-(.....)").find(it)?.groupValues?.getOrNull(1)?.getIntFromText() 169 | if(d.select("tr.published").isNotEmpty()) { 170 | d.select("tr.published").map { element -> 171 | val ep = Regex("ep-(.....)").find(element.select(".ep_title a").attr("href"))?.groupValues?.getOrNull(1)?.getIntFromText() 172 | episodes.add( 173 | Episode( 174 | mainUrl + element.select(".ep_title a").attr("href"), 175 | name = element.select("td.ep_title").html().replace(".*|".toRegex(), ""), 176 | season, 177 | ep, 178 | rating = element.select("td.tam:not(.date, .ep_len)").text().getIntFromText() 179 | ) 180 | ) 181 | } 182 | } else { 183 | d.select("#mainLoad > div:nth-child(3) > div.movies_small a").map { eit -> 184 | val ep = Regex("ep-(.....)").find(eit.attr("href"))?.groupValues?.getOrNull(1)?.getIntFromText() 185 | episodes.add( 186 | Episode( 187 | mainUrl + eit.attr("href"), 188 | eit.select("span.title").text(), 189 | season, 190 | ep, 191 | ) 192 | ) 193 | } 194 | } 195 | } 196 | newTvSeriesLoadResponse(title, url, TvType.TvSeries, episodes.distinct().sortedBy { it.episode }) { 197 | this.posterUrl = posterUrl 198 | this.tags = tags 199 | this.year = year 200 | this.plot = synopsis 201 | this.actors = actors 202 | addTrailer(youtubeTrailer) 203 | } 204 | } 205 | } 206 | 207 | @TargetApi(Build.VERSION_CODES.O) 208 | override suspend fun loadLinks( 209 | data: String, 210 | isCasting: Boolean, 211 | subtitleCallback: (SubtitleFile) -> Unit, 212 | callback: (ExtractorLink) -> Unit 213 | ): Boolean { 214 | val baseURL = data.split("/")[0] + "//" + data.split("/")[2] 215 | val client = Requests().baseClient 216 | val session = Session(client) 217 | println(baseURL) 218 | println(data) 219 | val doc = session.get(data).document 220 | 221 | val vidstreamURL = baseURL + doc.select("iframe.auto-size").attr("src") 222 | 223 | val videoSoup = session.get(vidstreamURL, cookies = mapOf( 224 | "PSSID" to this@EgyBest.pssid, 225 | )).document 226 | videoSoup.select("source").firstOrNull { it.hasAttr("src") }?.attr("src")?.let { 227 | M3u8Helper.generateM3u8( 228 | this.name, 229 | it, 230 | referer = mainUrl, 231 | headers = mapOf("range" to "bytes=0-") 232 | ).forEach(callback) 233 | } ?: run { 234 | var jsCode = videoSoup.select("script")[1].html() 235 | val function = videoSoup.select("script")[2].attr("onload") 236 | val verificationToken = Regex("\\{'[0-9a-zA-Z_]*':'ok'\\}").findAll(jsCode).first().value.replace("\\{'|':.*".toRegex(), "") 237 | val encodedAdLinkVar = Regex("\\([0-9a-zA-Z_]{2,12}\\[Math").findAll(jsCode).first().value.replace("\\(|\\[M.*".toRegex(),"") 238 | val encodingArraysRegEx = Regex(",[0-9a-zA-Z_]{2,12}=\\[]").findAll(jsCode).toList() 239 | val firstEncodingArray = encodingArraysRegEx[1].value.replace(",|=.*".toRegex(),"") 240 | val secondEncodingArray = encodingArraysRegEx[2].value.replace(",|=.*".toRegex(),"") 241 | 242 | jsCode = jsCode.replace("^