From 4a1597dc2f558ff7b9c8665743e949de180d9bd3 Mon Sep 17 00:00:00 2001 From: sora Date: Wed, 19 Jul 2023 20:54:20 +0700 Subject: [PATCH] added Nekopoi and fix some providers --- DramaSerial/build.gradle.kts | 2 +- .../main/kotlin/com/hexated/DramaSerial.kt | 8 +- .../kotlin/com/hexated/DramaSerialPlugin.kt | 2 +- .../src/main/kotlin/com/hexated/Lkctwoone.kt | 8 - Nekopoi/build.gradle.kts | 25 ++ Nekopoi/src/main/AndroidManifest.xml | 2 + .../src/main/kotlin/com/hexated/Nekopoi.kt | 276 ++++++++++++++++++ .../main/kotlin/com/hexated/NekopoiPlugin.kt | 13 + YomoviesProvider/build.gradle.kts | 2 +- .../kotlin/com/hexated/YomoviesProvider.kt | 7 +- 10 files changed, 331 insertions(+), 14 deletions(-) delete mode 100644 DramaSerial/src/main/kotlin/com/hexated/Lkctwoone.kt create mode 100644 Nekopoi/build.gradle.kts create mode 100644 Nekopoi/src/main/AndroidManifest.xml create mode 100644 Nekopoi/src/main/kotlin/com/hexated/Nekopoi.kt create mode 100644 Nekopoi/src/main/kotlin/com/hexated/NekopoiPlugin.kt diff --git a/DramaSerial/build.gradle.kts b/DramaSerial/build.gradle.kts index 37e56657..f198565b 100644 --- a/DramaSerial/build.gradle.kts +++ b/DramaSerial/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 3 +version = 4 cloudstream { diff --git a/DramaSerial/src/main/kotlin/com/hexated/DramaSerial.kt b/DramaSerial/src/main/kotlin/com/hexated/DramaSerial.kt index 881debc5..9a158a80 100644 --- a/DramaSerial/src/main/kotlin/com/hexated/DramaSerial.kt +++ b/DramaSerial/src/main/kotlin/com/hexated/DramaSerial.kt @@ -1,6 +1,7 @@ package com.hexated import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.extractors.Filesim import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.loadExtractor import org.jsoup.nodes.Element @@ -113,7 +114,7 @@ class DramaSerial : MainAPI() { mLink.attr("onclick").substringAfter("frame('").substringBefore("')").let { iLink -> val uLink = app.get(iLink, referer = iframe).document.select("script").find { it.data().contains("(document).ready") }?.data()?.substringAfter("replace(\"")?.substringBefore("\");") ?: return@apmap null val link = app.get(uLink, referer = iLink).document.selectFirst("iframe")?.attr("src") ?: return@apmap null - loadExtractor(fixUrl(link), "$mainUrl/", subtitleCallback, callback) + loadExtractor(fixUrl(link), "https://juraganfilm.info/", subtitleCallback, callback) } } @@ -122,3 +123,8 @@ class DramaSerial : MainAPI() { } } + +class Bk21 : Filesim() { + override val name = "Bk21" + override var mainUrl = "https://bk21.net" +} diff --git a/DramaSerial/src/main/kotlin/com/hexated/DramaSerialPlugin.kt b/DramaSerial/src/main/kotlin/com/hexated/DramaSerialPlugin.kt index 781dda9e..5d5de498 100644 --- a/DramaSerial/src/main/kotlin/com/hexated/DramaSerialPlugin.kt +++ b/DramaSerial/src/main/kotlin/com/hexated/DramaSerialPlugin.kt @@ -10,6 +10,6 @@ class DramaSerialPlugin: Plugin() { override fun load(context: Context) { // All providers should be added in this manner. Please don't edit the providers list directly. registerMainAPI(DramaSerial()) - registerExtractorAPI(Lkctwoone()) + registerExtractorAPI(Bk21()) } } \ No newline at end of file diff --git a/DramaSerial/src/main/kotlin/com/hexated/Lkctwoone.kt b/DramaSerial/src/main/kotlin/com/hexated/Lkctwoone.kt deleted file mode 100644 index 1957e026..00000000 --- a/DramaSerial/src/main/kotlin/com/hexated/Lkctwoone.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.hexated - -import com.lagradost.cloudstream3.extractors.XStreamCdn - -class Lkctwoone: XStreamCdn() { - override val name: String = "LKC21" - override val mainUrl: String = "https://lkc21.net" -} \ No newline at end of file diff --git a/Nekopoi/build.gradle.kts b/Nekopoi/build.gradle.kts new file mode 100644 index 00000000..2eb99748 --- /dev/null +++ b/Nekopoi/build.gradle.kts @@ -0,0 +1,25 @@ +// 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("Sora") + + /** + * Status int as the following: + * 0: Down + * 1: Ok + * 2: Slow + * 3: Beta only + * */ + status = 1 // will be 3 if unspecified + tvTypes = listOf( + "NSFW", + ) + + iconUrl = "https://www.google.com/s2/favicons?domain=nekopoi.care&sz=%size%" +} \ No newline at end of file diff --git a/Nekopoi/src/main/AndroidManifest.xml b/Nekopoi/src/main/AndroidManifest.xml new file mode 100644 index 00000000..c98063f8 --- /dev/null +++ b/Nekopoi/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/Nekopoi/src/main/kotlin/com/hexated/Nekopoi.kt b/Nekopoi/src/main/kotlin/com/hexated/Nekopoi.kt new file mode 100644 index 00000000..9c0a4cb7 --- /dev/null +++ b/Nekopoi/src/main/kotlin/com/hexated/Nekopoi.kt @@ -0,0 +1,276 @@ +package com.hexated + +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.utils.* +import com.lagradost.nicehttp.Requests +import com.lagradost.nicehttp.Session +import org.jsoup.nodes.Element +import java.net.URI + +class Nekopoi : MainAPI() { + override var mainUrl = "https://nekopoi.care" + override var name = "Nekopoi" + override val hasMainPage = true + override var lang = "id" + + override val supportedTypes = setOf( + TvType.NSFW, + ) + + companion object { + val session = Session(Requests().baseClient) + val mirrorBlackList = arrayOf( + "MegaupNet", + "DropApk", + "Racaty", + "ZippyShare", + "ZippySha", + "VideobinCo", + "DropApk", + "SendCm", + "GoogleDrive", + ) + + fun getStatus(t: String?): ShowStatus { + return when (t) { + "Completed" -> ShowStatus.Completed + "Ongoing" -> ShowStatus.Ongoing + else -> ShowStatus.Completed + } + } + + } + + override val mainPage = mainPageOf( + "$mainUrl/category/hentai/" to "Hentai", + "$mainUrl/category/jav/" to "Jav", + "$mainUrl/category/3d-hentai/" to "3D Hentai", + "$mainUrl/category/jav-cosplay/" to "Jav Cosplay", + ) + + override suspend fun getMainPage( + page: Int, + request: MainPageRequest + ): HomePageResponse { + val document = app.get("${request.data}/page/$page").document + val home = document.select("div.result ul li").mapNotNull { + it.toSearchResult() + } + return newHomePageResponse( + list = HomePageList( + name = request.name, + list = home, + isHorizontalImages = true + ), + hasNext = true + ) + } + + private fun getProperAnimeLink(uri: String): String { + return if (uri.contains("-episode-")) { + val title = uri.substringAfter("$mainUrl/").substringBefore("-episode-") + .removePrefix("new-release-").removePrefix("uncensored-") + "$mainUrl/hentai/$title" + } else { + uri + } + } + + private fun Element.toSearchResult(): AnimeSearchResponse? { + val title = this.selectFirst("h2 a")?.text()?.trim() ?: return null + val href = getProperAnimeLink(this.selectFirst("a")?.attr("href") ?: return null) + val posterUrl = fixUrlNull(this.selectFirst("img")?.attr("src")) + val epNum = this.selectFirst("i.dot")?.text()?.filter { it.isDigit() }?.toIntOrNull() + return newAnimeSearchResponse(title, href, TvType.NSFW) { + this.posterUrl = posterUrl + addSub(epNum) + } + + } + + override suspend fun search(query: String): List { + return app.get("$mainUrl/search/$query").document.select("div.result ul li") + .mapNotNull { + it.toSearchResult() + } + } + + override suspend fun load(url: String): LoadResponse { + val document = app.get(url).document + + val title = document.selectFirst("span.desc b, div.eroinfo h1")?.text()?.trim() ?: "" + val poster = fixUrlNull(document.selectFirst("div.imgdesc img, div.thm img")?.attr("src")) + val table = document.select("div.listinfo ul, div.konten") + val tags = + table.select("li:contains(Genres) a").map { it.text() }.takeIf { it.isNotEmpty() } + ?: table.select("p:contains(Genre)").text().substringAfter(":").split(",") + .map { it.trim() } + val year = + document.selectFirst("li:contains(Tayang)")?.text()?.substringAfterLast(",") + ?.filter { it.isDigit() }?.toIntOrNull() + val status = getStatus( + document.selectFirst("li:contains(Status)")?.text()?.substringAfter(":")?.trim() + ) + val duration = document.selectFirst("li:contains(Durasi)")?.text()?.substringAfterLast(":") + ?.filter { it.isDigit() }?.toIntOrNull() + val description = document.selectFirst("span.desc p")?.text() + + val episodes = document.select("div.episodelist ul li").mapNotNull { + val name = it.selectFirst("a")?.text() + val link = fixUrlNull(it.selectFirst("a")?.attr("href")) ?: return@mapNotNull null + Episode(link, name = name) + }.takeIf { it.isNotEmpty() } ?: listOf(Episode(url, title)) + + return newAnimeLoadResponse(title, url, TvType.NSFW) { + engName = title + posterUrl = poster + this.year = year + this.duration = duration + addEpisodes(DubStatus.Subbed, episodes) + showStatus = status + plot = description + this.tags = tags + } + } + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + + val res = app.get(data).document + + argamap( + { + res.select("div#show-stream iframe").apmap { iframe -> + loadExtractor(iframe.attr("src"), "$mainUrl/", subtitleCallback, callback) + } + }, + { + res.select("div.boxdownload div.liner").map { ele -> + getIndexQuality( + ele.select("div.name").text() + ) to ele.selectFirst("a:contains(ouo)") + ?.attr("href") + }.filter { it.first != Qualities.P360.value }.map { + val bypassedAds = bypassMirrored(bypassOuo(it.second ?: return@map) ?: return@map) + bypassedAds.apmap ads@{ adsLink -> + loadExtractor( + fixEmbed(adsLink) ?: return@ads, + "$mainUrl/", + subtitleCallback, + ) { link -> + callback.invoke( + ExtractorLink( + link.name, + link.name, + link.url, + link.referer, + if(link.isM3u8) link.quality else it.first, + link.isM3u8, + link.headers, + link.extractorData + ) + ) + } + } + } + } + ) + + return true + } + + private fun fixEmbed(url: String?): String? { + if (url == null) return null + val host = getBaseUrl(url) + return when { + url.contains("streamsb", true) -> url.replace("$host/", "$host/e/") + else -> url + } + } + + private fun getBaseUrl(url: String): String { + return URI(url).let { + "${it.scheme}://${it.host}" + } + } + + private suspend fun bypassOuo(url: String?): String? { + var res = session.get(url ?: return null) + run lit@{ + (1..2).forEach { _ -> + if (res.headers["location"] != null) return@lit + val document = res.document + val nextUrl = document.select("form").attr("action") + val data = document.select("form input").mapNotNull { + it.attr("name") to it.attr("value") + }.toMap().toMutableMap() + val captchaKey = + document.select("script[src*=https://www.google.com/recaptcha/api.js?render=]") + .attr("src").substringAfter("render=") + val token = APIHolder.getCaptchaToken(url, captchaKey) + data["x-token"] = token ?: "" + res = session.post( + nextUrl, + data = data, + headers = mapOf("content-type" to "application/x-www-form-urlencoded"), + allowRedirects = false + ) + } + } + + return res.headers["location"] + } + + private suspend fun bypassMirrored(url: String): List { + val request = app.get(url) + val hostUrl = getBaseUrl(request.url) + var nextUrl = request.document.selectFirst("div.row div.centered a")?.attr("href") + nextUrl = app.get(nextUrl ?: return emptyList()).text.substringAfter("\"GET\", \"") + .substringBefore("\"") + return app.get(fixUrl(nextUrl, hostUrl)).document.select("table.hoverable tbody tr") + .filter { mirror -> + !mirrorIsBlackList(mirror.selectFirst("img")?.attr("alt")) + }.apmap { + val fileLink = it.selectFirst("a")?.attr("href") + app.get( + fixUrl( + fileLink.toString(), + hostUrl + ) + ).document.selectFirst("div.code_wrap code")?.text() + } + } + + private fun mirrorIsBlackList(host: String?) : Boolean { + return mirrorBlackList.any { it.equals(host, true) } + } + + private fun fixUrl(url: String, domain: String): String { + if (url.startsWith("http")) { + return url + } + if (url.isEmpty()) { + return "" + } + + val startsWithNoHttp = url.startsWith("//") + if (startsWithNoHttp) { + return "https:$url" + } else { + if (url.startsWith('/')) { + return domain + url + } + return "$domain/$url" + } + } + + 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/Nekopoi/src/main/kotlin/com/hexated/NekopoiPlugin.kt b/Nekopoi/src/main/kotlin/com/hexated/NekopoiPlugin.kt new file mode 100644 index 00000000..a6fe53a3 --- /dev/null +++ b/Nekopoi/src/main/kotlin/com/hexated/NekopoiPlugin.kt @@ -0,0 +1,13 @@ +package com.hexated + +import com.lagradost.cloudstream3.plugins.CloudstreamPlugin +import com.lagradost.cloudstream3.plugins.Plugin +import android.content.Context + +@CloudstreamPlugin +class NekopoiPlugin: Plugin() { + override fun load(context: Context) { + // All providers should be added in this manner. Please don't edit the providers list directly. + registerMainAPI(Nekopoi()) + } +} \ No newline at end of file diff --git a/YomoviesProvider/build.gradle.kts b/YomoviesProvider/build.gradle.kts index 4def964d..8b53037f 100644 --- a/YomoviesProvider/build.gradle.kts +++ b/YomoviesProvider/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 14 +version = 15 cloudstream { diff --git a/YomoviesProvider/src/main/kotlin/com/hexated/YomoviesProvider.kt b/YomoviesProvider/src/main/kotlin/com/hexated/YomoviesProvider.kt index 6c46d869..ac5e991f 100644 --- a/YomoviesProvider/src/main/kotlin/com/hexated/YomoviesProvider.kt +++ b/YomoviesProvider/src/main/kotlin/com/hexated/YomoviesProvider.kt @@ -10,7 +10,7 @@ import org.jsoup.nodes.Element import java.net.URI class YomoviesProvider : MainAPI() { - override var mainUrl = "https://yomovies.team" + override var mainUrl = "https://yomovies.baby" private var directUrl = mainUrl override var name = "Yomovies" override val hasMainPage = true @@ -142,7 +142,7 @@ class YomoviesProvider : MainAPI() { callback: (ExtractorLink) -> Unit ): Boolean { - if (data.contains("yomovies")) { + if (data.contains(directUrl.getHost(), true)) { val doc = app.get(data).document doc.select("div.movieplay iframe").map { fixUrl(it.attr("src")) } .apmap { source -> @@ -171,5 +171,8 @@ class YomoviesProvider : MainAPI() { return true } + private fun String.getHost(): String { + return fixTitle(URI(this).host.substringBeforeLast(".").substringAfterLast(".")) + } }