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..eed9c20 --- /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 != "null") { + 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 + } +} \ No newline at end of file