diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt index 3cf2ee53..185dadf5 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt @@ -107,6 +107,7 @@ object APIHolder { UakinoProvider(), PhimmoichillProvider(), HDrezkaProvider(), + YomoviesProvider(), // Metadata providers @@ -142,6 +143,8 @@ object APIHolder { KuronimeProvider(), OtakudesuProvider(), AnimeIndoProvider(), + AnimeSailProvider(), + TocanimeProvider(), //MultiAnimeProvider(), NginxProvider(), OlgplyProvider(), diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AnimeSailProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AnimeSailProvider.kt new file mode 100644 index 00000000..e5f38e0f --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AnimeSailProvider.kt @@ -0,0 +1,191 @@ +package com.lagradost.cloudstream3.animeproviders + +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.mvvm.safeApiCall +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.Qualities +import com.lagradost.cloudstream3.utils.loadExtractor +import com.lagradost.nicehttp.NiceResponse +import org.jsoup.Jsoup +import org.jsoup.nodes.Element + +class AnimeSailProvider : MainAPI() { + override var mainUrl = "https://111.90.143.42" + override var name = "AnimeSail" + 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 { + return if (t.contains("OVA") || t.contains("Special")) TvType.OVA + else if (t.contains("Movie")) TvType.AnimeMovie + else TvType.Anime + } + + fun getStatus(t: String): ShowStatus { + return when (t) { + "Completed" -> ShowStatus.Completed + "Ongoing" -> ShowStatus.Ongoing + else -> ShowStatus.Completed + } + } + } + + private suspend fun request(url: String, ref: String? = null): NiceResponse { + return app.get( + url, + headers = mapOf("Accept" to "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8"), + cookies = mapOf("_as_ipin_ct" to "ID"), + referer = ref + ) + } + + override suspend fun getMainPage(): HomePageResponse { + val document = request(mainUrl).document + + val homePageList = ArrayList() + + document.select(".bixbox").forEach { block -> + val header = block.select(".releases > h3").text().trim() + val animes = block.select("article").map { + it.toSearchResult() + } + if (animes.isNotEmpty()) homePageList.add(HomePageList(header, animes)) + } + + return HomePageResponse(homePageList) + } + + 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(".tt > h2").text().trim() + val posterUrl = fixUrlNull(this.selectFirst("div.limit img")?.attr("src")) + val epNum = this.selectFirst(".tt > h2")?.text()?.let { + Regex("Episode\\s?([0-9]+)").find(it)?.groupValues?.getOrNull(1)?.toIntOrNull() + } + return newAnimeSearchResponse(title, href, TvType.Anime) { + this.posterUrl = posterUrl + addSub(epNum) + } + + } + + override suspend fun search(query: String): List { + val link = "$mainUrl/?s=$query" + val document = request(link).document + + return document.select("div.listupd article").map { + it.toSearchResult() + } + } + + override suspend fun load(url: String): LoadResponse { + val document = request(url).document + + val title = document.selectFirst("h1.entry-title")?.text().toString().trim() + val type = getType( + document.select("tbody th:contains(Tipe)").next().text() + ) + val episodes = document.select("ul.daftar > li").map { + val header = it.select("a").text().trim() + val name = Regex("(Episode\\s?[0-9]+)").find(header)?.groupValues?.getOrNull(0) ?: header + val link = fixUrl(it.select("a").attr("href")) + Episode(link, name = name) + }.reversed() + + return newAnimeLoadResponse(title, url, type) { + posterUrl = document.selectFirst("div.entry-content > img")?.attr("src") + this.year = + document.select("tbody th:contains(Dirilis)").next().text().trim().toIntOrNull() + addEpisodes(DubStatus.Subbed, episodes) + showStatus = + getStatus(document.select("tbody th:contains(Status)").next().text().trim()) + plot = document.selectFirst("div.entry-content > p")?.text() + this.tags = + document.select("tbody th:contains(Genre)").next().select("a").map { it.text() } + } + } + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + + val document = request(data).document + + document.select(".mobius > .mirror > option").apmap { + safeApiCall { + val iframe = fixUrl( + Jsoup.parse(base64Decode(it.attr("data-em"))).select("iframe").attr("src") + ?: throw ErrorLoadingException("No iframe found") + ) + + when { + iframe.startsWith("$mainUrl/utils/player/arch/") || iframe.startsWith( + "$mainUrl/utils/player/race/" + ) -> request(iframe, ref = data).document.select("source").attr("src") + .let { link -> + val source = + when { + iframe.contains("/arch/") -> "Arch" + iframe.contains("/race/") -> "Race" + else -> this.name + } + val quality = Regex("\\.([0-9]{3,4})\\.").find(link)?.groupValues?.get(1) + callback.invoke( + ExtractorLink( + source = source, + name = source, + url = link, + referer = mainUrl, + quality = quality?.toIntOrNull() ?: Qualities.Unknown.value + ) + ) + } +// skip for now +// iframe.startsWith("$mainUrl/utils/player/fichan/") -> "" +// iframe.startsWith("$mainUrl/utils/player/blogger/") -> "" + iframe.startsWith("$mainUrl/utils/player/framezilla/") || iframe.startsWith("https://uservideo.xyz") -> { + request(iframe, ref = data).document.select("iframe").attr("src") + .let { link -> + loadExtractor(fixUrl(link), mainUrl, callback) + } + } + else -> { + loadExtractor(iframe, mainUrl, callback) + } + } + } + } + + return true + } + + +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/GomunimeProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/GomunimeProvider.kt index 06e1c60a..c3d8d714 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/GomunimeProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/GomunimeProvider.kt @@ -145,7 +145,7 @@ class GomunimeProvider : MainAPI() { document.select(".bixbox.bxcl.epcheck > script").toString().trim() )?.groupValues?.get(1).toString().replace(Regex("""\\"""), "").trim() ).map { - val name = it.epTitle + val name = Regex("(Episode\\s?[0-9]+)").find(it.epTitle.toString())?.groupValues?.getOrNull(0) ?: it.epTitle val link = it.epLink Episode(link, name) }.reversed() diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/NeonimeProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/NeonimeProvider.kt index fa5c1216..cd5d213c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/NeonimeProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/NeonimeProvider.kt @@ -117,46 +117,36 @@ class NeonimeProvider : MainAPI() { if (url.contains("movie") || url.contains("live-action")) { val mTitle = document.selectFirst(".sbox > .data > h1[itemprop = name]")?.text().toString().trim() - val mPoster = - document.selectFirst(".sbox > .imagen > .fix > img[itemprop = image]")?.attr("data-src") - val mTags = document.select("p.meta_dd > a").map { it.text() } - val mYear = document.selectFirst("a[href*=release-year]")!!.text().toIntOrNull() - val mDescription = document.select("div[itemprop = description]").text().trim() - val mRating = document.select("span[itemprop = ratingValue]").text().toIntOrNull() val mTrailer = document.selectFirst("div.youtube_id iframe")?.attr("data-wpfc-original-src")?.substringAfterLast("html#")?.let{ "https://www.youtube.com/embed/$it"} return newMovieLoadResponse(name = mTitle, url = url, type = TvType.Movie, dataUrl = url) { - posterUrl = mPoster - year = mYear - plot = mDescription - rating = mRating - tags = mTags + posterUrl = document.selectFirst(".sbox > .imagen > .fix > img[itemprop = image]")?.attr("data-src") + year = document.selectFirst("a[href*=release-year]")!!.text().toIntOrNull() + plot = document.select("div[itemprop = description]").text().trim() + rating = document.select("span[itemprop = ratingValue]").text().toIntOrNull() + tags = document.select("p.meta_dd > a").map { it.text() } addTrailer(mTrailer) } } else { val title = document.select("h1[itemprop = name]").text().trim() - val poster = document.selectFirst(".imagen > img")?.attr("data-src") - val tags = document.select("#info a[href*=\"-genre/\"]").map { it.text() } - val year = document.select("#info a[href*=\"-year/\"]").text().toIntOrNull() - val status = getStatus(document.select("div.metadatac > span").last()!!.text().trim()) - val description = document.select("div[itemprop = description] > p").text().trim() val trailer = document.selectFirst("div.youtube_id_tv iframe")?.attr("data-wpfc-original-src")?.substringAfterLast("html#")?.let{ "https://www.youtube.com/embed/$it"} val episodes = document.select("ul.episodios > li").mapNotNull { - val name = it.selectFirst(".episodiotitle > a")!!.ownText().trim() + val header = it.selectFirst(".episodiotitle > a")?.ownText().toString() + val name = Regex("(Episode\\s?[0-9]+)").find(header)?.groupValues?.getOrNull(0) ?: header val link = fixUrl(it.selectFirst(".episodiotitle > a")!!.attr("href")) Episode(link, name) }.reversed() return newAnimeLoadResponse(title, url, TvType.Anime) { engName = title - posterUrl = poster - this.year = year + posterUrl = document.selectFirst(".imagen > img")?.attr("data-src") + year = document.select("#info a[href*=\"-year/\"]").text().toIntOrNull() addEpisodes(DubStatus.Subbed, episodes) - showStatus = status - plot = description - this.tags = tags + showStatus = getStatus(document.select("div.metadatac > span").last()!!.text().trim()) + plot = document.select("div[itemprop = description] > p").text().trim() + tags = document.select("#info a[href*=\"-genre/\"]").map { it.text() } addTrailer(trailer) } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/NontonAnimeIDProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/NontonAnimeIDProvider.kt index 6dec98f0..deacbdee 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/NontonAnimeIDProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/NontonAnimeIDProvider.kt @@ -175,16 +175,13 @@ class NontonAnimeIDProvider : MainAPI() { ) ).parsed().content ).select("li").map { - val engName = - document.selectFirst("div.bottomtitle:nth-child(4) > span:nth-child(1)") - ?.ownText() - val name = it.selectFirst("span.t1")!!.text().trim().replace("Episode", "$engName") - val link = it.selectFirst("a")!!.attr("href") + val name = Regex("(Episode\\s?[0-9]+)").find(it.selectFirst("a")?.text().toString())?.groupValues?.getOrNull(0) ?: it.selectFirst("a")?.text() + val link = fixUrl(it.selectFirst("a")!!.attr("href")) Episode(link, name) }.reversed() } else { document.select("ul.misha_posts_wrap2 > li").map { - val name = it.select("span.t1").text().trim() + val name = Regex("(Episode\\s?[0-9]+)").find(it.selectFirst("a")?.text().toString())?.groupValues?.getOrNull(0) ?: it.selectFirst("a")?.text() val link = it.select("a").attr("href") Episode(link, name) }.reversed() diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/OploverzProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/OploverzProvider.kt index b8e6d626..640efead 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/OploverzProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/OploverzProvider.kt @@ -143,7 +143,8 @@ class OploverzProvider : MainAPI() { val trailer = document.selectFirst("a.trailerbutton")?.attr("href") val episodes = document.select(".eplister > ul > li").map { - val name = it.select(".epl-title").text().trim() + val header = it.select(".epl-title").text() + val name = Regex("(Episode\\s?[0-9]+)").find(header)?.groupValues?.getOrNull(0) ?: header val link = fixUrl(it.select("a").attr("href")) Episode(link, name) }.reversed() diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/OtakudesuProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/OtakudesuProvider.kt index 4bd02bf8..883a408e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/OtakudesuProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/OtakudesuProvider.kt @@ -104,7 +104,7 @@ class OtakudesuProvider : MainAPI() { val description = document.select("div.sinopc > p").text() val episodes = document.select("div.episodelist")[1].select("ul > li").mapNotNull { - val name = it.selectFirst("a")!!.text().trim() + val name = Regex("(Episode\\s?[0-9]+)").find(it.selectFirst("a")?.text().toString())?.groupValues?.getOrNull(0) ?: it.selectFirst("a")?.text() val link = fixUrl(it.selectFirst("a")!!.attr("href")) Episode(link, name) }.reversed() diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/TocanimeProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/TocanimeProvider.kt new file mode 100644 index 00000000..62b349fb --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/TocanimeProvider.kt @@ -0,0 +1,178 @@ +package com.lagradost.cloudstream3.animeproviders + +import com.fasterxml.jackson.annotation.JsonProperty +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 +import java.util.* + +class TocanimeProvider : MainAPI() { + override var mainUrl = "https://tocanime.co" + override var name = "Tocanime" + override val hasMainPage = true + override var lang = "vi" + override val hasDownloadSupport = true + + override val supportedTypes = setOf( + TvType.Anime, + TvType.AnimeMovie, + TvType.OVA + ) + + companion object { + fun getType(t: String): TvType { + return when { + t.contains("OVA") || t.contains("Special") -> TvType.OVA + t.contains("Movie") -> TvType.AnimeMovie + else -> TvType.Anime + } + } + + fun getStatus(t: String): ShowStatus { + return when (t) { + "Đã hoàn thành" -> ShowStatus.Completed + "Chưa hoàn thành" -> ShowStatus.Ongoing + else -> ShowStatus.Completed + } + } + } + + override suspend fun getMainPage(): HomePageResponse { + val document = app.get(mainUrl).document + + val homePageList = ArrayList() + + document.select("div#playlists > div").forEach { block -> + val header = block.selectFirst("h2")?.text()?.trim() ?: "" + val items = block.select("div.col-lg-3.col-md-4.col-6").map { + it.toSearchResult() + } + if (items.isNotEmpty()) homePageList.add(HomePageList(header, items)) + } + + return HomePageResponse(homePageList) + } + + private fun Element.toSearchResult(): AnimeSearchResponse { + val title = this.selectFirst("h3 a")?.text()?.trim() ?: "" + val href = fixUrl(this.selectFirst("h3 a")!!.attr("href")) + val posterUrl = fixUrlNull(this.selectFirst("div.card-item-img")?.attr("data-original")) + val epNum = this.selectFirst("div.card-item-badget.rtl")?.text()?.let { eps -> + val num = eps.filter { it.isDigit() }.toIntOrNull() + if(eps.contains("Preview")) { + num?.minus(1) + } else { + num + } + } + return newAnimeSearchResponse(title, href, TvType.Anime) { + this.posterUrl = posterUrl + addSub(epNum) + } + + } + + override suspend fun search(query: String): List { + val document = app.get("$mainUrl/content/search?t=kw&q=$query").document + + return document.select("div.col-lg-3.col-md-4.col-6").map { + it.toSearchResult() + } + + } + + override suspend fun load(url: String): LoadResponse? { + val document = app.get(url).document + + val title = document.selectFirst("h1.title")?.text() ?: return null + val type = + if (document.select("div.me-list.scroller a").size == 1) TvType.AnimeMovie else TvType.Anime + val episodes = document.select("div.me-list.scroller a").mapNotNull { + Episode(fixUrl(it.attr("href")), it.text()) + }.reversed() + val trailer = + document.selectFirst("div#trailer script")?.data()?.substringAfter("