diff --git a/Animasu/build.gradle.kts b/Animasu/build.gradle.kts new file mode 100644 index 00000000..4b81579b --- /dev/null +++ b/Animasu/build.gradle.kts @@ -0,0 +1,27 @@ +// use an integer for version numbers +version = 1 + + +cloudstream { + language = "id" + // All of these properties are optional, you can safely remove them + + // description = "Lorem Ipsum" + authors = listOf("Hexated") + + /** + * Status int as the following: + * 0: Down + * 1: Ok + * 2: Slow + * 3: Beta only + * */ + status = 1 // will be 3 if unspecified + tvTypes = listOf( + "AnimeMovie", + "OVA", + "Anime", + ) + + iconUrl = "https://www.google.com/s2/favicons?domain=animasu.cc&sz=%size%" +} \ No newline at end of file diff --git a/Animasu/src/main/AndroidManifest.xml b/Animasu/src/main/AndroidManifest.xml new file mode 100644 index 00000000..874740e3 --- /dev/null +++ b/Animasu/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/Animasu/src/main/kotlin/com/hexated/Animasu.kt b/Animasu/src/main/kotlin/com/hexated/Animasu.kt new file mode 100644 index 00000000..e693a672 --- /dev/null +++ b/Animasu/src/main/kotlin/com/hexated/Animasu.kt @@ -0,0 +1,167 @@ +package com.hexated + +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer +import com.lagradost.cloudstream3.utils.* +import org.jsoup.Jsoup +import org.jsoup.nodes.Element + +class Animasu : MainAPI() { + override var mainUrl = "https://animasu.cc" + override var name = "Animasu" + override val hasMainPage = true + override var lang = "id" + override val hasDownloadSupport = true + + override val supportedTypes = setOf( + TvType.Anime, + TvType.AnimeMovie, + TvType.OVA + ) + + companion object { + fun getType(t: String?): TvType { + if(t == null) return TvType.Anime + return when { + t.contains("Tv", true) -> TvType.Anime + t.contains("Movie", true) -> TvType.AnimeMovie + t.contains("OVA", true) || t.contains("Special", true) -> TvType.OVA + else -> TvType.Anime + } + } + + fun getStatus(t: String?): ShowStatus { + if(t == null) return ShowStatus.Completed + return when { + t.contains("Sedang Tayang", true) -> ShowStatus.Ongoing + else -> ShowStatus.Completed + } + } + } + + override val mainPage = mainPageOf( + "urutan=update" to "Baru diupdate", + "status=&tipe=&urutan=publikasi" to "Baru ditambahkan", + "status=&tipe=&urutan=populer" to "Terpopuler", + "status=&tipe=&urutan=rating" to "Rating Tertinggi", + "status=&tipe=Movie&urutan=update" to "Movie Terbaru", + "status=&tipe=Movie&urutan=populer" to "Movie Terpopuler", + ) + + override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse { + val document = app.get("$mainUrl/pencarian/?${request.data}&halaman=$page").document + val home = document.select("div.listupd div.bs").map { + it.toSearchResult() + } + return newHomePageResponse(request.name, home) + } + + private fun getProperAnimeLink(uri: String): String { + return if (uri.contains("/anime/")) { + uri + } else { + var title = uri.substringAfter("$mainUrl/") + title = when { + (title.contains("-episode")) && !(title.contains("-movie")) -> title.substringBefore( + "-episode" + ) + + (title.contains("-movie")) -> title.substringBefore("-movie") + else -> title + } + + "$mainUrl/anime/$title" + } + } + + private fun Element.toSearchResult(): AnimeSearchResponse { + val href = getProperAnimeLink(fixUrlNull(this.selectFirst("a")?.attr("href")).toString()) + val title = this.select("div.tt").text().trim() + val posterUrl = fixUrlNull(this.selectFirst("img")?.attr("src")) + val epNum = this.selectFirst("span.epx")?.text()?.filter { it.isDigit() }?.toIntOrNull() + return newAnimeSearchResponse(title, href, TvType.Anime) { + this.posterUrl = posterUrl + addSub(epNum) + } + + } + + override suspend fun search(query: String): List { + return app.get("$mainUrl/?s=$query").document.select("div.listupd div.bs").map { + it.toSearchResult() + } + } + + override suspend fun load(url: String): LoadResponse { + val document = app.get(url).document + + val title = document.selectFirst("div.infox h1")?.text().toString().replace("Sub Indo", "").trim() + val poster = document.selectFirst("div.bigcontent img")?.attr("src")?.replace("\n", "") + + val table = document.selectFirst("div.infox div.spe") + val type = table?.selectFirst("span:contains(Jenis:)")?.ownText() + val year = table?.selectFirst("span:contains(Rilis:)")?.ownText()?.substringAfterLast(",")?.trim()?.toIntOrNull() + val status = table?.selectFirst("span:contains(Status:) font")?.text() + val trailer = document.selectFirst("div.trailer iframe")?.attr("src") + val episodes = document.select("ul#daftarepisode > li").map { + val link = fixUrl(it.selectFirst("a")!!.attr("href")) + val name = it.selectFirst("a")?.text() ?: "" + val episode = Regex("Episode\\s?(\\d+)").find(name)?.groupValues?.getOrNull(0)?.toIntOrNull() + Episode(link, name, episode = episode) + }.reversed() + + return newAnimeLoadResponse(title, url, getType(type)) { + posterUrl = poster + this.year = year + addEpisodes(DubStatus.Subbed, episodes) + showStatus = getStatus(status) + plot = document.select("div.sinopsis p").text() + this.tags = table?.select("span:contains(Genre:) a")?.map { it.text() } + addTrailer(trailer) + } + } + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + val document = app.get(data).document + document.select(".mobius > .mirror > option").mapNotNull { + fixUrl(Jsoup.parse(base64Decode(it.attr("value"))).select("iframe").attr("src")) to it.text() + }.apmap { (iframe, quality) -> + loadFixedExtractor(iframe, quality, "$mainUrl/", subtitleCallback, callback) + } + return true + } + + private suspend fun loadFixedExtractor( + url: String, + quality: String?, + referer: String? = null, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ) { + loadExtractor(url, referer, subtitleCallback) { link -> + callback.invoke( + ExtractorLink( + link.name, + link.name, + link.url, + link.referer, + if(!link.isM3u8) getIndexQuality(quality) else link.quality, + link.isM3u8, + link.headers, + link.extractorData + ) + ) + } + } + + private fun getIndexQuality(str: String?): Int { + return Regex("(\\d{3,4})[pP]").find(str ?: "")?.groupValues?.getOrNull(1)?.toIntOrNull() + ?: Qualities.Unknown.value + } + +} \ No newline at end of file diff --git a/Animasu/src/main/kotlin/com/hexated/AnimasuPlugin.kt b/Animasu/src/main/kotlin/com/hexated/AnimasuPlugin.kt new file mode 100644 index 00000000..845ac534 --- /dev/null +++ b/Animasu/src/main/kotlin/com/hexated/AnimasuPlugin.kt @@ -0,0 +1,14 @@ + +package com.hexated + +import com.lagradost.cloudstream3.plugins.CloudstreamPlugin +import com.lagradost.cloudstream3.plugins.Plugin +import android.content.Context + +@CloudstreamPlugin +class AnimasuPlugin: Plugin() { + override fun load(context: Context) { + // All providers should be added in this manner. Please don't edit the providers list directly. + registerMainAPI(Animasu()) + } +} \ No newline at end of file