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("."))
+ }
}