diff --git a/Anichi/build.gradle.kts b/Anichi/build.gradle.kts index 24eea240..1a2f6d64 100644 --- a/Anichi/build.gradle.kts +++ b/Anichi/build.gradle.kts @@ -1,7 +1,7 @@ import org.jetbrains.kotlin.konan.properties.Properties // use an integer for version numbers -version = 4 +version = 6 android { defaultConfig { diff --git a/Anichi/src/main/kotlin/com/hexated/Anichi.kt b/Anichi/src/main/kotlin/com/hexated/Anichi.kt index 02d0fe98..01c161d1 100644 --- a/Anichi/src/main/kotlin/com/hexated/Anichi.kt +++ b/Anichi/src/main/kotlin/com/hexated/Anichi.kt @@ -1,24 +1,19 @@ package com.hexated -import com.fasterxml.jackson.annotation.JsonProperty +import com.hexated.AnichiExtractors.invokeExternalSources +import com.hexated.AnichiExtractors.invokeInternalSources import com.lagradost.cloudstream3.* -import com.lagradost.cloudstream3.APIHolder.getTracker import com.lagradost.cloudstream3.LoadResponse.Companion.addActors import com.lagradost.cloudstream3.LoadResponse.Companion.addAniListId import com.lagradost.cloudstream3.LoadResponse.Companion.addMalId import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer -import com.lagradost.cloudstream3.extractors.helper.GogoHelper -import com.lagradost.cloudstream3.mvvm.safeApiCall import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.AppUtils.parseJson -import com.lagradost.cloudstream3.utils.AppUtils.toJson -import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson import com.lagradost.nicehttp.RequestBodyTypes import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.RequestBody.Companion.toRequestBody -import java.net.URI -class Anichi : MainAPI() { +open class Anichi : MainAPI() { override var name = "Anichi" override val instantLinkLoading = true override val hasQuickSearch = false @@ -32,14 +27,6 @@ class Anichi : MainAPI() { } } - private fun getType(t: String?): TvType { - return when { - t.equals("OVA", true) || t.equals("Special") -> TvType.OVA - t.equals("Movie", true) -> TvType.AnimeMovie - else -> TvType.Anime - } - } - override val supportedTypes = setOf(TvType.Anime, TvType.AnimeMovie) private val popularTitle = "Popular" @@ -141,14 +128,21 @@ class Anichi : MainAPI() { val title = showData.name val description = showData.description val poster = showData.thumbnail - val type = getType(showData.type ?: "") + + val trackers = getTracker( + title, + showData.altNames?.firstOrNull(), + showData.airedStart?.year, + showData.season?.quarter, + showData.type + ) val episodes = showData.availableEpisodesDetail.let { if (it == null) return@let Pair(null, null) if (showData.Id == null) return@let Pair(null, null) Pair( - it.getEpisode("sub", showData.Id), - it.getEpisode("dub", showData.Id), + it.getEpisode("sub", showData.Id, trackers?.idMal), + it.getEpisode("dub", showData.Id, trackers?.idMal), ) } @@ -164,28 +158,24 @@ class Anichi : MainAPI() { Pair(Actor(name, image), role) } - val names = showData.altNames?.plus(title)?.filterNotNull() ?: emptyList() - val trackers = getTracker(names, TrackerType.getTypes(type), showData.airedStart?.year) - return newAnimeLoadResponse(title ?: "", url, TvType.Anime) { engName = showData.altNames?.firstOrNull() - posterUrl = trackers?.image ?: poster - backgroundPosterUrl = trackers?.cover ?: showData.banner + posterUrl = trackers?.coverImage?.extraLarge ?: trackers?.coverImage?.large ?: poster + backgroundPosterUrl = trackers?.bannerImage ?: showData.banner rating = showData.averageScore?.times(100) tags = showData.genres year = showData.airedStart?.year duration = showData.episodeDuration?.div(60_000) addTrailer(showData.prevideos.filter { it.isNotBlank() } .map { "https://www.youtube.com/watch?v=$it" }) - addEpisodes(DubStatus.Subbed, episodes.first) addEpisodes(DubStatus.Dubbed, episodes.second) addActors(characters) //this.recommendations = recommendations showStatus = getStatus(showData.status.toString()) - addMalId(trackers?.malId) - addAniListId(trackers?.aniId?.toIntOrNull()) + addMalId(trackers?.idMal) + addAniListId(trackers?.id) plot = description?.replace(Regex("""<(.*?)>"""), "") } } @@ -199,411 +189,48 @@ class Anichi : MainAPI() { val loadData = parseJson(data) - val apiUrl = - """$apiUrl?variables={"showId":"${loadData.hash}","translationType":"${loadData.dubStatus}","episodeString":"${loadData.episode}"}&extensions={"persistedQuery":{"version":1,"sha256Hash":"$serverHash"}}""" - val apiResponse = app.get(apiUrl, headers = headers).parsed() - - apiResponse.data?.episode?.sourceUrls?.apmap { source -> - safeApiCall { - val link = fixSourceUrls(source.sourceUrl ?: return@safeApiCall, source.sourceName) ?: return@safeApiCall - if (URI(link).isAbsolute || link.startsWith("//")) { - val fixedLink = if (link.startsWith("//")) "https:$link" else link - val host = link.getHost() - - when { - fixedLink.contains(Regex("(?i)playtaku|gogo")) || source.sourceName == "Vid-mp4" -> { - invokeGogo(fixedLink, subtitleCallback, callback) - } - embedIsBlacklisted(fixedLink) -> { - loadExtractor(fixedLink, subtitleCallback, callback) - } - URI(fixedLink).path.contains(".m3u") -> { - getM3u8Qualities(fixedLink, serverUrl, host).forEach(callback) - } - else -> { - callback( - ExtractorLink( - name, - host, - fixedLink, - serverUrl, - Qualities.P1080.value, - false - ) - ) - } - } - } else { - val fixedLink = link.fixUrlPath() - val links = app.get(fixedLink).parsedSafe()?.links - ?: emptyList() - links.forEach { server -> - val host = server.link.getHost() - when { - source.sourceName == "Default" -> { - if (server.resolutionStr == "SUB" || server.resolutionStr == "Alt vo_SUB") { - getM3u8Qualities( - server.link, - "https://static.crunchyroll.com/", - host, - ).forEach(callback) - } - } - server.hls != null && server.hls -> { - getM3u8Qualities( - server.link, - "$apiEndPoint/player?uri=" + (if (URI(server.link).host.isNotEmpty()) server.link else apiEndPoint + URI( - server.link - ).path), - host - ).forEach(callback) - } - else -> { - callback( - ExtractorLink( - host, - host, - server.link, - "$apiEndPoint/player?uri=" + (if (URI(server.link).host.isNotEmpty()) server.link else apiEndPoint + URI( - server.link - ).path), - server.resolutionStr.removeSuffix("p").toIntOrNull() - ?: Qualities.P1080.value, - false, - isDash = server.resolutionStr == "Dash 1" - ) - ) - } - } - } - } + argamap( + { + invokeInternalSources( + loadData.hash, + loadData.dubStatus, + loadData.episode, + subtitleCallback, + callback + ) + }, + { + invokeExternalSources( + loadData.idMal, + loadData.dubStatus, + loadData.episode, + subtitleCallback, + callback + ) } - } + ) + return true } - private val embedBlackList = listOf( - "https://mp4upload.com/", - "https://streamsb.net/", - "https://dood.to/", - "https://videobin.co/", - "https://ok.ru", - "https://streamlare.com", - "streaming.php", - ) - - private fun embedIsBlacklisted(url: String): Boolean { - embedBlackList.forEach { - if (it.javaClass.name == "kotlin.text.Regex") { - if ((it as Regex).matches(url)) { - return true - } - } else { - if (url.contains(it)) { - return true - } - } - } - return false - } - - private fun AvailableEpisodesDetail.getEpisode( - lang: String, - id: String - ): List { - val meta = if (lang == "sub") this.sub else this.dub - return meta.map { eps -> - Episode( - AnichiLoadData(id, lang, eps).toJson(), - "Ep $eps" - ) - }.reversed() - } - - private suspend fun getM3u8Qualities( - m3u8Link: String, - referer: String, - qualityName: String, - ): List { - return M3u8Helper.generateM3u8( - this.name, - m3u8Link, - referer, - name = qualityName - ) - } - - private suspend fun invokeGogo( - link: String, - subtitleCallback: (SubtitleFile) -> Unit, - callback: (ExtractorLink) -> Unit - ) { - val iframe = app.get(link) - val iframeDoc = iframe.document - argamap({ - iframeDoc.select(".list-server-items > .linkserver") - .forEach { element -> - val status = element.attr("data-status") ?: return@forEach - if (status != "1") return@forEach - val extractorData = element.attr("data-video") ?: return@forEach - loadExtractor(extractorData, iframe.url, subtitleCallback, callback) - } - }, { - val iv = "3134003223491201" - val secretKey = "37911490979715163134003223491201" - val secretDecryptKey = "54674138327930866480207815084989" - GogoHelper.extractVidstream( - iframe.url, - "Gogoanime", - callback, - iv, - secretKey, - secretDecryptKey, - isUsingAdaptiveKeys = false, - isUsingAdaptiveData = true, - iframeDocument = iframeDoc - ) - }) - } - - private fun String.getHost(): String { - return fixTitle(URI(this).host.substringBeforeLast(".").substringAfterLast(".")) - } - - private fun String.fixUrlPath() : String { - return if(this.contains(".json?")) apiEndPoint + this else apiEndPoint + URI(this).path + ".json?" + URI(this).query - } - - private fun fixSourceUrls(url: String, source: String?) : String? { - return if(source == "Ak" || url.contains("/player/vitemb")) { - tryParseJson(base64Decode(url.substringAfter("=")))?.idUrl - } else { - url.replace(" ", "%20") - } - } - companion object { - private const val apiUrl = BuildConfig.ANICHI_API - private const val serverUrl = BuildConfig.ANICHI_SERVER - private const val apiEndPoint = BuildConfig.ANICHI_ENDPOINT + const val apiUrl = BuildConfig.ANICHI_API + const val serverUrl = BuildConfig.ANICHI_SERVER + const val apiEndPoint = BuildConfig.ANICHI_ENDPOINT + + const val marinHost = "https://marin.moe" private const val mainHash = "e42a4466d984b2c0a2cecae5dd13aa68867f634b16ee0f17b380047d14482406" private const val popularHash = "31a117653812a2547fd981632e8c99fa8bf8a75c4ef1a77a1567ef1741a7ab9c" private const val slugHash = "bf603205eb2533ca21d0324a11f623854d62ed838a27e1b3fcfb712ab98b03f4" private const val detailHash = "bb263f91e5bdd048c1c978f324613aeccdfe2cbc694a419466a31edb58c0cc0b" - private const val serverHash = "5e7e17cdd0166af5a2d8f43133d9ce3ce9253d1fdb5160a0cfd515564f98d061" + const val serverHash = "5e7e17cdd0166af5a2d8f43133d9ce3ce9253d1fdb5160a0cfd515564f98d061" - private val headers = mapOf( + val headers = mapOf( "app-version" to "android_c-247", "from-app" to BuildConfig.ANICHI_APP, "platformstr" to "android_c", ) } - data class AnichiLoadData( - val hash: String, - val dubStatus: String, - val episode: String - ) - - data class AkIframe( - @JsonProperty("idUrl") val idUrl: String? = null, - ) - - data class Stream( - @JsonProperty("format") val format: String? = null, - @JsonProperty("audio_lang") val audio_lang: String? = null, - @JsonProperty("hardsub_lang") val hardsub_lang: String? = null, - @JsonProperty("url") val url: String? = null, - ) - - data class PortData( - @JsonProperty("streams") val streams: ArrayList? = arrayListOf(), - ) - - data class Subtitles( - @JsonProperty("lang") val lang: String?, - @JsonProperty("label") val label: String?, - @JsonProperty("src") val src: String?, - ) - - data class Links( - @JsonProperty("link") val link: String, - @JsonProperty("hls") val hls: Boolean?, - @JsonProperty("resolutionStr") val resolutionStr: String, - @JsonProperty("src") val src: String?, - @JsonProperty("portData") val portData: PortData? = null, - @JsonProperty("subtitles") val subtitles: ArrayList? = arrayListOf(), - ) - - data class AnichiVideoApiResponse( - @JsonProperty("links") val links: List - ) - - data class Data( - @JsonProperty("shows") val shows: Shows? = null, - @JsonProperty("queryListForTag") val queryListForTag: Shows? = null, - @JsonProperty("queryPopular") val queryPopular: Shows? = null, - ) - - data class Shows( - @JsonProperty("edges") val edges: List? = arrayListOf(), - @JsonProperty("recommendations") val recommendations: List? = arrayListOf(), - ) - - data class EdgesCard( - @JsonProperty("anyCard") val anyCard: Edges? = null, - ) - - data class CharacterImage( - @JsonProperty("large") val large: String?, - @JsonProperty("medium") val medium: String? - ) - - data class CharacterName( - @JsonProperty("full") val full: String?, - @JsonProperty("native") val native: String? - ) - - data class Characters( - @JsonProperty("image") val image: CharacterImage?, - @JsonProperty("role") val role: String?, - @JsonProperty("name") val name: CharacterName?, - ) - - data class Edges( - @JsonProperty("_id") val Id: String?, - @JsonProperty("name") val name: String?, - @JsonProperty("englishName") val englishName: String?, - @JsonProperty("nativeName") val nativeName: String?, - @JsonProperty("thumbnail") val thumbnail: String?, - @JsonProperty("type") val type: String?, - @JsonProperty("season") val season: Season?, - @JsonProperty("score") val score: Double?, - @JsonProperty("airedStart") val airedStart: AiredStart?, - @JsonProperty("availableEpisodes") val availableEpisodes: AvailableEpisodes?, - @JsonProperty("availableEpisodesDetail") val availableEpisodesDetail: AvailableEpisodesDetail?, - @JsonProperty("studios") val studios: List?, - @JsonProperty("genres") val genres: List?, - @JsonProperty("averageScore") val averageScore: Int?, - @JsonProperty("characters") val characters: List?, - @JsonProperty("altNames") val altNames: List?, - @JsonProperty("description") val description: String?, - @JsonProperty("status") val status: String?, - @JsonProperty("banner") val banner: String?, - @JsonProperty("episodeDuration") val episodeDuration: Int?, - @JsonProperty("prevideos") val prevideos: List = emptyList(), - ) - - data class AvailableEpisodes( - @JsonProperty("sub") val sub: Int, - @JsonProperty("dub") val dub: Int, - @JsonProperty("raw") val raw: Int - ) - - data class AiredStart( - @JsonProperty("year") val year: Int, - @JsonProperty("month") val month: Int, - @JsonProperty("date") val date: Int - ) - - data class Season( - @JsonProperty("quarter") val quarter: String, - @JsonProperty("year") val year: Int - ) - - data class AnichiQuery( - @JsonProperty("data") val data: Data? = null - ) - - data class Detail( - @JsonProperty("data") val data: DetailShow - ) - - data class DetailShow( - @JsonProperty("show") val show: Edges - ) - - data class AvailableEpisodesDetail( - @JsonProperty("sub") val sub: List, - @JsonProperty("dub") val dub: List, - @JsonProperty("raw") val raw: List - ) - - data class LinksQuery( - @JsonProperty("data") val data: LinkData? = LinkData() - ) - - data class LinkData( - @JsonProperty("episode") val episode: Episode? = Episode() - ) - - data class SourceUrls( - @JsonProperty("sourceUrl") val sourceUrl: String? = null, - @JsonProperty("priority") val priority: Int? = null, - @JsonProperty("sourceName") val sourceName: String? = null, - @JsonProperty("type") val type: String? = null, - @JsonProperty("className") val className: String? = null, - @JsonProperty("streamerId") val streamerId: String? = null - ) - - data class Episode( - @JsonProperty("sourceUrls") val sourceUrls: ArrayList = arrayListOf(), - ) - - data class Sub( - @JsonProperty("hour") val hour: Int? = null, - @JsonProperty("minute") val minute: Int? = null, - @JsonProperty("year") val year: Int? = null, - @JsonProperty("month") val month: Int? = null, - @JsonProperty("date") val date: Int? = null - ) - - data class LastEpisodeDate( - @JsonProperty("dub") val dub: Sub? = Sub(), - @JsonProperty("sub") val sub: Sub? = Sub(), - @JsonProperty("raw") val raw: Sub? = Sub() - ) - - data class AnyCard( - @JsonProperty("_id") val Id: String? = null, - @JsonProperty("name") val name: String? = null, - @JsonProperty("englishName") val englishName: String? = null, - @JsonProperty("nativeName") val nativeName: String? = null, - @JsonProperty("availableEpisodes") val availableEpisodes: AvailableEpisodes? = null, - @JsonProperty("score") val score: Double? = null, - @JsonProperty("lastEpisodeDate") val lastEpisodeDate: LastEpisodeDate? = LastEpisodeDate(), - @JsonProperty("thumbnail") val thumbnail: String? = null, - @JsonProperty("lastChapterDate") val lastChapterDate: String? = null, - @JsonProperty("availableChapters") val availableChapters: String? = null, - @JsonProperty("__typename") val _typename: String? = null - ) - - data class PageStatus( - @JsonProperty("_id") val Id: String? = null, - @JsonProperty("views") val views: String? = null, - @JsonProperty("showId") val showId: String? = null, - @JsonProperty("rangeViews") val rangeViews: String? = null, - @JsonProperty("isManga") val isManga: Boolean? = null, - @JsonProperty("__typename") val _typename: String? = null - ) - - - data class Recommendations( - @JsonProperty("anyCard") val anyCard: AnyCard? = null, - @JsonProperty("pageStatus") val pageStatus: PageStatus? = PageStatus(), - @JsonProperty("__typename") val _typename: String? = null - ) - - data class QueryPopular( - @JsonProperty("total") val total: Int? = null, - @JsonProperty("recommendations") val recommendations: ArrayList = arrayListOf(), - @JsonProperty("__typename") val _typename: String? = null - ) - - data class DataPopular( - @JsonProperty("queryPopular") val queryPopular: QueryPopular? = QueryPopular() - ) - - } \ No newline at end of file diff --git a/Anichi/src/main/kotlin/com/hexated/AnichiExtractors.kt b/Anichi/src/main/kotlin/com/hexated/AnichiExtractors.kt new file mode 100644 index 00000000..5fe9ab9f --- /dev/null +++ b/Anichi/src/main/kotlin/com/hexated/AnichiExtractors.kt @@ -0,0 +1,217 @@ +package com.hexated + +import com.lagradost.cloudstream3.SubtitleFile +import com.lagradost.cloudstream3.apmap +import com.lagradost.cloudstream3.app +import com.lagradost.cloudstream3.argamap +import com.lagradost.cloudstream3.extractors.helper.GogoHelper +import com.lagradost.cloudstream3.mvvm.safeApiCall +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.Qualities +import com.lagradost.cloudstream3.utils.SubtitleHelper +import com.lagradost.cloudstream3.utils.httpsify +import com.lagradost.cloudstream3.utils.loadExtractor +import java.net.URI + +object AnichiExtractors : Anichi() { + + suspend fun invokeInternalSources( + hash: String, + dubStatus: String, + episode: String, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit, + ) { + val apiUrl = + """$apiUrl?variables={"showId":"$hash","translationType":"$dubStatus","episodeString":"$episode"}&extensions={"persistedQuery":{"version":1,"sha256Hash":"$serverHash"}}""" + val apiResponse = app.get(apiUrl, headers = headers).parsed() + + apiResponse.data?.episode?.sourceUrls?.apmap { source -> + safeApiCall { + val link = fixSourceUrls(source.sourceUrl ?: return@safeApiCall, source.sourceName) + ?: return@safeApiCall + if (URI(link).isAbsolute || link.startsWith("//")) { + val fixedLink = if (link.startsWith("//")) "https:$link" else link + val host = link.getHost() + + when { + fixedLink.contains(Regex("(?i)playtaku|gogo")) || source.sourceName == "Vid-mp4" -> { + invokeGogo(fixedLink, subtitleCallback, callback) + } + + embedIsBlacklisted(fixedLink) -> { + loadExtractor(fixedLink, subtitleCallback, callback) + } + + URI(fixedLink).path.contains(".m3u") -> { + getM3u8Qualities(fixedLink, serverUrl, host).forEach(callback) + } + + else -> { + callback( + ExtractorLink( + name, + host, + fixedLink, + serverUrl, + Qualities.P1080.value, + false + ) + ) + } + } + } else { + val fixedLink = link.fixUrlPath() + val links = app.get(fixedLink).parsedSafe()?.links + ?: emptyList() + links.forEach { server -> + val host = server.link.getHost() + when { + source.sourceName == "Default" -> { + if (server.resolutionStr == "SUB" || server.resolutionStr == "Alt vo_SUB") { + getM3u8Qualities( + server.link, + "https://static.crunchyroll.com/", + host, + ).forEach(callback) + } + } + + server.hls != null && server.hls -> { + getM3u8Qualities( + server.link, + "$apiEndPoint/player?uri=" + (if (URI(server.link).host.isNotEmpty()) server.link else apiEndPoint + URI( + server.link + ).path), + host + ).forEach(callback) + } + + else -> { + callback( + ExtractorLink( + host, + host, + server.link, + "$apiEndPoint/player?uri=" + (if (URI(server.link).host.isNotEmpty()) server.link else apiEndPoint + URI( + server.link + ).path), + server.resolutionStr.removeSuffix("p").toIntOrNull() + ?: Qualities.P1080.value, + false, + isDash = server.resolutionStr == "Dash 1" + ) + ) + server.subtitles?.map { sub -> + subtitleCallback.invoke( + SubtitleFile( + SubtitleHelper.fromTwoLettersToLanguage(sub.lang ?: "") + ?: sub.lang ?: "", + httpsify(sub.src ?: return@map) + ) + ) + } + } + } + } + } + } + } + } + + suspend fun invokeExternalSources( + idMal: Int? = null, + dubStatus: String, + episode: String, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit, + ) { + val ids = app.get("https://api.malsync.moe/mal/anime/${idMal ?: return}") + .parsedSafe()?.sites + + if (dubStatus == "sub") invokeMarin(ids?.marin?.keys?.firstOrNull(), episode, callback) + + } + + private suspend fun invokeMarin( + id: String? = null, + episode: String, + callback: (ExtractorLink) -> Unit + ) { + val url = "$marinHost/anime/${id ?: return}/$episode" + val cookies = app.get( + "$marinHost/anime", + headers = mapOf( + "Cookie" to "__ddg1_=;__ddg2_=;" + ), + referer = "$marinHost/anime", + ).cookies.let { + it["XSRF-TOKEN"] to it["marin_session"] + } + + app.get( + url, + headers = mapOf( + "Referer" to "$marinHost/", + "Cookie" to "__ddg1=;__ddg2_=; XSRF-TOKEN=${cookies.first}; marin_session=${cookies.second};", + "x-inertia" to "true", + "x-inertia-version" to "5ee7503af8c9844b1e8d34466b727694", + "X-Requested-With" to "XMLHttpRequest", + "X-XSRF-TOKEN" to decode(cookies.first.toString()) + ) + ).parsedSafe()?.props?.video?.data?.mirror?.map { video -> + callback.invoke( + ExtractorLink( + "Marin", + "Marin", + video.code?.file ?: return@map, + url, + video.code.height ?: Qualities.Unknown.value, + headers = mapOf( + "Accept" to "video/webm,video/ogg,video/*;q=0.9,application/ogg;q=0.7,audio/*;q=0.6,*/*;q=0.5", + "Accept-Language" to "en-US,en;q=0.5", + "Cookie" to "__ddg1=;__ddg2_=; XSRF-TOKEN=${cookies.first}; marin_session=${cookies.second};", + "Connection" to "keep-alive", + "Sec-Fetch-Dest" to "video", + "Sec-Fetch-Mode" to "cors", + "Sec-Fetch-Site" to "cross-site", + ) + ) + ) + } + } + + private suspend fun invokeGogo( + link: String, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ) { + val iframe = app.get(link) + val iframeDoc = iframe.document + argamap({ + iframeDoc.select(".list-server-items > .linkserver") + .forEach { element -> + val status = element.attr("data-status") ?: return@forEach + if (status != "1") return@forEach + val extractorData = element.attr("data-video") ?: return@forEach + loadExtractor(extractorData, iframe.url, subtitleCallback, callback) + } + }, { + val iv = "3134003223491201" + val secretKey = "37911490979715163134003223491201" + val secretDecryptKey = "54674138327930866480207815084989" + GogoHelper.extractVidstream( + iframe.url, + "Gogoanime", + callback, + iv, + secretKey, + secretDecryptKey, + isUsingAdaptiveKeys = false, + isUsingAdaptiveData = true, + iframeDocument = iframeDoc + ) + }) + } + +} \ No newline at end of file diff --git a/Anichi/src/main/kotlin/com/hexated/AnichiParser.kt b/Anichi/src/main/kotlin/com/hexated/AnichiParser.kt new file mode 100644 index 00000000..0952180a --- /dev/null +++ b/Anichi/src/main/kotlin/com/hexated/AnichiParser.kt @@ -0,0 +1,267 @@ +package com.hexated + +import com.fasterxml.jackson.annotation.JsonProperty + +data class AnichiLoadData( + val hash: String, + val dubStatus: String, + val episode: String, + val idMal: Int? = null, +) + +data class CoverImage( + @JsonProperty("extraLarge") var extraLarge: String? = null, + @JsonProperty("large") var large: String? = null, +) + +data class AniMedia( + @JsonProperty("id") var id: Int? = null, + @JsonProperty("idMal") var idMal: Int? = null, + @JsonProperty("coverImage") var coverImage: CoverImage? = null, + @JsonProperty("bannerImage") var bannerImage: String? = null, +) + +data class AniPage( + @JsonProperty("media") var media: ArrayList = arrayListOf() +) + +data class AniData( + @JsonProperty("Page") var Page: AniPage? = AniPage() +) + +data class AniSearch( + @JsonProperty("data") var data: AniData? = AniData() +) + +data class AkIframe( + @JsonProperty("idUrl") val idUrl: String? = null, +) + +data class Stream( + @JsonProperty("format") val format: String? = null, + @JsonProperty("audio_lang") val audio_lang: String? = null, + @JsonProperty("hardsub_lang") val hardsub_lang: String? = null, + @JsonProperty("url") val url: String? = null, +) + +data class PortData( + @JsonProperty("streams") val streams: ArrayList? = arrayListOf(), +) + +data class Subtitles( + @JsonProperty("lang") val lang: String?, + @JsonProperty("label") val label: String?, + @JsonProperty("src") val src: String?, +) + +data class Links( + @JsonProperty("link") val link: String, + @JsonProperty("hls") val hls: Boolean?, + @JsonProperty("resolutionStr") val resolutionStr: String, + @JsonProperty("src") val src: String?, + @JsonProperty("portData") val portData: PortData? = null, + @JsonProperty("subtitles") val subtitles: ArrayList? = arrayListOf(), +) + +data class AnichiVideoApiResponse( + @JsonProperty("links") val links: List +) + +data class Data( + @JsonProperty("shows") val shows: Shows? = null, + @JsonProperty("queryListForTag") val queryListForTag: Shows? = null, + @JsonProperty("queryPopular") val queryPopular: Shows? = null, +) + +data class Shows( + @JsonProperty("edges") val edges: List? = arrayListOf(), + @JsonProperty("recommendations") val recommendations: List? = arrayListOf(), +) + +data class EdgesCard( + @JsonProperty("anyCard") val anyCard: Edges? = null, +) + +data class CharacterImage( + @JsonProperty("large") val large: String?, + @JsonProperty("medium") val medium: String? +) + +data class CharacterName( + @JsonProperty("full") val full: String?, + @JsonProperty("native") val native: String? +) + +data class Characters( + @JsonProperty("image") val image: CharacterImage?, + @JsonProperty("role") val role: String?, + @JsonProperty("name") val name: CharacterName?, +) + +data class Edges( + @JsonProperty("_id") val Id: String?, + @JsonProperty("name") val name: String?, + @JsonProperty("englishName") val englishName: String?, + @JsonProperty("nativeName") val nativeName: String?, + @JsonProperty("thumbnail") val thumbnail: String?, + @JsonProperty("type") val type: String?, + @JsonProperty("season") val season: Season?, + @JsonProperty("score") val score: Double?, + @JsonProperty("airedStart") val airedStart: AiredStart?, + @JsonProperty("availableEpisodes") val availableEpisodes: AvailableEpisodes?, + @JsonProperty("availableEpisodesDetail") val availableEpisodesDetail: AvailableEpisodesDetail?, + @JsonProperty("studios") val studios: List?, + @JsonProperty("genres") val genres: List?, + @JsonProperty("averageScore") val averageScore: Int?, + @JsonProperty("characters") val characters: List?, + @JsonProperty("altNames") val altNames: List?, + @JsonProperty("description") val description: String?, + @JsonProperty("status") val status: String?, + @JsonProperty("banner") val banner: String?, + @JsonProperty("episodeDuration") val episodeDuration: Int?, + @JsonProperty("prevideos") val prevideos: List = emptyList(), +) + +data class AvailableEpisodes( + @JsonProperty("sub") val sub: Int, + @JsonProperty("dub") val dub: Int, + @JsonProperty("raw") val raw: Int +) + +data class AiredStart( + @JsonProperty("year") val year: Int, + @JsonProperty("month") val month: Int, + @JsonProperty("date") val date: Int +) + +data class Season( + @JsonProperty("quarter") val quarter: String, + @JsonProperty("year") val year: Int +) + +data class AnichiQuery( + @JsonProperty("data") val data: Data? = null +) + +data class Detail( + @JsonProperty("data") val data: DetailShow +) + +data class DetailShow( + @JsonProperty("show") val show: Edges +) + +data class AvailableEpisodesDetail( + @JsonProperty("sub") val sub: List, + @JsonProperty("dub") val dub: List, + @JsonProperty("raw") val raw: List +) + +data class LinksQuery( + @JsonProperty("data") val data: LinkData? = LinkData() +) + +data class LinkData( + @JsonProperty("episode") val episode: Episode? = Episode() +) + +data class SourceUrls( + @JsonProperty("sourceUrl") val sourceUrl: String? = null, + @JsonProperty("priority") val priority: Int? = null, + @JsonProperty("sourceName") val sourceName: String? = null, + @JsonProperty("type") val type: String? = null, + @JsonProperty("className") val className: String? = null, + @JsonProperty("streamerId") val streamerId: String? = null +) + +data class Episode( + @JsonProperty("sourceUrls") val sourceUrls: ArrayList = arrayListOf(), +) + +data class Sub( + @JsonProperty("hour") val hour: Int? = null, + @JsonProperty("minute") val minute: Int? = null, + @JsonProperty("year") val year: Int? = null, + @JsonProperty("month") val month: Int? = null, + @JsonProperty("date") val date: Int? = null +) + +data class LastEpisodeDate( + @JsonProperty("dub") val dub: Sub? = Sub(), + @JsonProperty("sub") val sub: Sub? = Sub(), + @JsonProperty("raw") val raw: Sub? = Sub() +) + +data class AnyCard( + @JsonProperty("_id") val Id: String? = null, + @JsonProperty("name") val name: String? = null, + @JsonProperty("englishName") val englishName: String? = null, + @JsonProperty("nativeName") val nativeName: String? = null, + @JsonProperty("availableEpisodes") val availableEpisodes: AvailableEpisodes? = null, + @JsonProperty("score") val score: Double? = null, + @JsonProperty("lastEpisodeDate") val lastEpisodeDate: LastEpisodeDate? = LastEpisodeDate(), + @JsonProperty("thumbnail") val thumbnail: String? = null, + @JsonProperty("lastChapterDate") val lastChapterDate: String? = null, + @JsonProperty("availableChapters") val availableChapters: String? = null, + @JsonProperty("__typename") val _typename: String? = null +) + +data class PageStatus( + @JsonProperty("_id") val Id: String? = null, + @JsonProperty("views") val views: String? = null, + @JsonProperty("showId") val showId: String? = null, + @JsonProperty("rangeViews") val rangeViews: String? = null, + @JsonProperty("isManga") val isManga: Boolean? = null, + @JsonProperty("__typename") val _typename: String? = null +) + + +data class Recommendations( + @JsonProperty("anyCard") val anyCard: AnyCard? = null, + @JsonProperty("pageStatus") val pageStatus: PageStatus? = PageStatus(), + @JsonProperty("__typename") val _typename: String? = null +) + +data class QueryPopular( + @JsonProperty("total") val total: Int? = null, + @JsonProperty("recommendations") val recommendations: ArrayList = arrayListOf(), + @JsonProperty("__typename") val _typename: String? = null +) + +data class DataPopular( + @JsonProperty("queryPopular") val queryPopular: QueryPopular? = QueryPopular() +) + +data class MALSyncSites( + @JsonProperty("Zoro") val zoro: HashMap>? = hashMapOf(), + @JsonProperty("Marin") val marin: HashMap>? = hashMapOf(), +) + +data class MALSyncResponses( + @JsonProperty("Sites") val sites: MALSyncSites? = null, +) + +data class MarinCode( + @JsonProperty("file") val file: String? = null, + @JsonProperty("height") val height: Int? = null, +) + +data class MarinMirror( + @JsonProperty("code") val code: MarinCode? = null, +) + +data class MarinData( + @JsonProperty("mirror") val mirror: ArrayList? = arrayListOf(), +) + +data class MarinVideos( + @JsonProperty("data") val data: MarinData? = null, +) + +data class MarinProps( + @JsonProperty("video") val video: MarinVideos? = null, +) + +data class MarinResponses( + @JsonProperty("props") val props: MarinProps? = null, +) \ No newline at end of file diff --git a/Anichi/src/main/kotlin/com/hexated/AnichiUtils.kt b/Anichi/src/main/kotlin/com/hexated/AnichiUtils.kt new file mode 100644 index 00000000..0656e2d2 --- /dev/null +++ b/Anichi/src/main/kotlin/com/hexated/AnichiUtils.kt @@ -0,0 +1,138 @@ +package com.hexated + +import com.hexated.Anichi.Companion.apiEndPoint +import com.lagradost.cloudstream3.Episode +import com.lagradost.cloudstream3.app +import com.lagradost.cloudstream3.base64Decode +import com.lagradost.cloudstream3.fixTitle +import com.lagradost.cloudstream3.mvvm.logError +import com.lagradost.cloudstream3.utils.AppUtils +import com.lagradost.cloudstream3.utils.AppUtils.toJson +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.M3u8Helper +import com.lagradost.nicehttp.RequestBodyTypes +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.RequestBody.Companion.toRequestBody +import java.net.URI +import java.net.URLDecoder + +suspend fun getTracker(name: String?, altName: String?, year: Int?, season: String?, type: String?): AniMedia? { + return fetchId(name, year, season, type).takeIf { it?.id != null } ?: fetchId( + altName, + year, + season, + type + ) +} + +suspend fun fetchId(title: String?, year: Int?, season: String?, type: String?): AniMedia? { + val query = """ + query ( + ${'$'}page: Int = 1 + ${'$'}search: String + ${'$'}sort: [MediaSort] = [POPULARITY_DESC, SCORE_DESC] + ${'$'}type: MediaType + ${'$'}season: MediaSeason + ${'$'}year: String + ${'$'}format: [MediaFormat] + ) { + Page(page: ${'$'}page, perPage: 20) { + media( + search: ${'$'}search + sort: ${'$'}sort + type: ${'$'}type + season: ${'$'}season + startDate_like: ${'$'}year + format_in: ${'$'}format + ) { + id + idMal + coverImage { extraLarge large } + bannerImage + } + } + } + """.trimIndent().trim() + + val variables = mapOf( + "search" to title, + "sort" to "SEARCH_MATCH", + "type" to "ANIME", + "season" to if(type.equals("ona", true)) "" else season?.uppercase(), + "year" to "$year%", + "format" to listOf(type?.uppercase()) + ).filterValues { value -> value != null && value.toString().isNotEmpty() } + + val data = mapOf( + "query" to query, + "variables" to variables + ).toJson().toRequestBody(RequestBodyTypes.JSON.toMediaTypeOrNull()) + + return try { + app.post("https://graphql.anilist.co", requestBody = data) + .parsedSafe()?.data?.Page?.media?.firstOrNull() + } catch (t: Throwable) { + logError(t) + null + } + +} + +fun decode(input: String): String = URLDecoder.decode(input, "utf-8") + +private val embedBlackList = listOf( + "https://mp4upload.com/", + "https://streamsb.net/", + "https://dood.to/", + "https://videobin.co/", + "https://ok.ru", + "https://streamlare.com", + "streaming.php", +) + +fun embedIsBlacklisted(url: String): Boolean { + return embedBlackList.any { url.contains(it) } +} + +fun AvailableEpisodesDetail.getEpisode( + lang: String, + id: String, + malId: Int?, +): List { + val meta = if (lang == "sub") this.sub else this.dub + return meta.map { eps -> + Episode( + AnichiLoadData(id, lang, eps, malId).toJson(), + "Ep $eps", + episode = eps.toIntOrNull() + ) + }.reversed() +} + +suspend fun getM3u8Qualities( + m3u8Link: String, + referer: String, + qualityName: String, +): List { + return M3u8Helper.generateM3u8( + qualityName, + m3u8Link, + referer, + ) +} + +fun String.getHost(): String { + return fixTitle(URI(this).host.substringBeforeLast(".").substringAfterLast(".")) +} + +fun String.fixUrlPath() : String { + return if(this.contains(".json?")) apiEndPoint + this else apiEndPoint + URI(this).path + ".json?" + URI(this).query +} + +fun fixSourceUrls(url: String, source: String?) : String? { + return if(source == "Ak" || url.contains("/player/vitemb")) { + AppUtils.tryParseJson(base64Decode(url.substringAfter("=")))?.idUrl + } else { + url.replace(" ", "%20") + } +} \ No newline at end of file diff --git a/AnimeSailProvider/build.gradle.kts b/AnimeSailProvider/build.gradle.kts index bbfed124..d0914891 100644 --- a/AnimeSailProvider/build.gradle.kts +++ b/AnimeSailProvider/build.gradle.kts @@ -23,5 +23,5 @@ cloudstream { "OVA", ) - iconUrl = "https://www.google.com/s2/favicons?domain=111.90.143.42&sz=%size%" + iconUrl = "https://aghanim.xyz/wp-content/themes/animesail/assets/images/ico.png" } \ No newline at end of file diff --git a/Anizm/build.gradle.kts b/Anizm/build.gradle.kts index 6c69805d..618896c5 100644 --- a/Anizm/build.gradle.kts +++ b/Anizm/build.gradle.kts @@ -16,7 +16,7 @@ cloudstream { * 2: Slow * 3: Beta only * */ - status = 1 // will be 3 if unspecified + status = 0 // will be 3 if unspecified tvTypes = listOf( "AnimeMovie", "Anime", diff --git a/Anroll/build.gradle.kts b/Anroll/build.gradle.kts index bbbb0839..7d575610 100644 --- a/Anroll/build.gradle.kts +++ b/Anroll/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 1 +version = 2 cloudstream { language = "pt-pt" diff --git a/Anroll/src/main/kotlin/com/hexated/Anroll.kt b/Anroll/src/main/kotlin/com/hexated/Anroll.kt index 178db598..d397caa0 100644 --- a/Anroll/src/main/kotlin/com/hexated/Anroll.kt +++ b/Anroll/src/main/kotlin/com/hexated/Anroll.kt @@ -35,7 +35,7 @@ class Anroll : MainAPI() { ): HomePageResponse { val document = app.get("$mainUrl/home").document val home = mutableListOf() - document.select("div.sc-f5d5b250-1.iJHcsI").map { div -> + document.select("div.hAbQAe").map { div -> val header = div.selectFirst("h2")?.text() ?: return@map val child = HomePageList( header, @@ -91,11 +91,11 @@ class Anroll : MainAPI() { val fixUrl = getProperAnimeLink(url) ?: throw ErrorLoadingException() val document = app.get(fixUrl).document - val article = document.selectFirst("article.sc-f5d5b250-9") ?: return null + val article = document.selectFirst("article.animedetails") ?: return null val title = article.selectFirst("h2")?.text() ?: return null - val poster = fixUrlNull(document.select("article.sc-f5d5b250-8 img").attr("src")) + val poster = fixUrlNull(document.select("section.animecontent img").attr("src")) val tags = article.select("div#generos a").map { it.text() } - val year = article.selectFirst("div.sc-f5d5b250-4")?.nextElementSibling()?.text() + val year = article.selectFirst("div.dfuefM")?.nextElementSibling()?.text() ?.toIntOrNull() val description = document.select("div.sinopse").text().trim() val type = if (fixUrl.contains("/a/")) TvType.Anime else TvType.AnimeMovie 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..98047d9f 100644 --- a/DramaSerial/src/main/kotlin/com/hexated/DramaSerial.kt +++ b/DramaSerial/src/main/kotlin/com/hexated/DramaSerial.kt @@ -1,12 +1,13 @@ 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 class DramaSerial : MainAPI() { - override var mainUrl = "https://dramaserial.wiki" + override var mainUrl = "https://dramaserial.sbs" override var name = "DramaSerial" override val hasMainPage = true override var lang = "id" @@ -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/Hdfilmcehennemi/build.gradle.kts b/Hdfilmcehennemi/build.gradle.kts index 008bccce..991be08d 100644 --- a/Hdfilmcehennemi/build.gradle.kts +++ b/Hdfilmcehennemi/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 9 +version = 10 cloudstream { diff --git a/Hdfilmcehennemi/src/main/kotlin/com/hexated/Hdfilmcehennemi.kt b/Hdfilmcehennemi/src/main/kotlin/com/hexated/Hdfilmcehennemi.kt index ff9b2b60..9e80daaa 100644 --- a/Hdfilmcehennemi/src/main/kotlin/com/hexated/Hdfilmcehennemi.kt +++ b/Hdfilmcehennemi/src/main/kotlin/com/hexated/Hdfilmcehennemi.kt @@ -83,7 +83,7 @@ class Hdfilmcehennemi : MainAPI() { val title = document.selectFirst("div.card-header > h1, div.card-header > h2")?.text() ?.removeSuffix("Filminin Bilgileri")?.trim() ?: return null - val poster = fixUrlNull(document.selectFirst("img.img-fluid")?.attr("src")) + val poster = fixUrlNull(document.select("img.img-fluid").lastOrNull()?.attr("src")) val tags = document.select("div.mb-0.lh-lg div:nth-child(5) a").map { it.text() } val year = document.selectFirst("div.mb-0.lh-lg div:nth-child(4) a")?.text()?.trim()?.toIntOrNull() @@ -154,10 +154,6 @@ class Hdfilmcehennemi : MainAPI() { } } - private fun String.addMarks(str: String): String { - return this.replace(Regex("\"?$str\"?"), "\"$str\"") - } - private suspend fun invokeLocalSource( source: String, url: String, @@ -205,11 +201,24 @@ class Hdfilmcehennemi : MainAPI() { }.apmap { (url, source) -> safeApiCall { app.get(url).document.select("div.card-video > iframe").attr("data-src") - .let { link -> - if (link.startsWith(mainUrl)) { - invokeLocalSource(source, link, subtitleCallback, callback) + .let { url -> + if (url.startsWith(mainUrl)) { + invokeLocalSource(source, url, subtitleCallback, callback) } else { - loadExtractor(link, "$mainUrl/", subtitleCallback, callback) + loadExtractor(url, "$mainUrl/", subtitleCallback) { link -> + callback.invoke( + ExtractorLink( + source, + source, + link.url, + link.referer, + link.quality, + link.isM3u8, + link.headers, + link.extractorData + ) + ) + } } } } @@ -217,10 +226,6 @@ class Hdfilmcehennemi : MainAPI() { return true } - private data class Source( - @JsonProperty("file") val file: String? = null, - ) - private data class SubSource( @JsonProperty("file") val file: String? = null, @JsonProperty("label") val label: String? = null, diff --git a/IdlixProvider/build.gradle.kts b/IdlixProvider/build.gradle.kts index e2fe47d4..cc7d7b0c 100644 --- a/IdlixProvider/build.gradle.kts +++ b/IdlixProvider/build.gradle.kts @@ -24,5 +24,5 @@ cloudstream { "AsianDrama", ) - iconUrl = "https://www.google.com/s2/favicons?domain=109.234.36.69&sz=%size%" + iconUrl = "https://t0.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=http://idlixian.com&size=16" } \ No newline at end of file diff --git a/LayarKacaProvider/build.gradle.kts b/LayarKacaProvider/build.gradle.kts index 1f1549b9..b1cd5e23 100644 --- a/LayarKacaProvider/build.gradle.kts +++ b/LayarKacaProvider/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 13 +version = 14 cloudstream { diff --git a/LayarKacaProvider/src/main/kotlin/com/hexated/LayarKacaProvider.kt b/LayarKacaProvider/src/main/kotlin/com/hexated/LayarKacaProvider.kt index 8b7c03a5..535214aa 100644 --- a/LayarKacaProvider/src/main/kotlin/com/hexated/LayarKacaProvider.kt +++ b/LayarKacaProvider/src/main/kotlin/com/hexated/LayarKacaProvider.kt @@ -6,11 +6,10 @@ import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer import com.lagradost.cloudstream3.utils.* import org.jsoup.nodes.Element import java.net.URLDecoder -import java.net.URI class LayarKacaProvider : MainAPI() { - override var mainUrl = "https://d21.fun" - private var seriesUrl = "https://tv.nontondrama.click" + override var mainUrl = "https://tv.lk21official.pro" + private var seriesUrl = "https://tv1.nontondrama.click" override var name = "LayarKaca" override val hasMainPage = true override var lang = "id" @@ -57,7 +56,7 @@ class LayarKacaProvider : MainAPI() { private fun Element.toSearchResult(): SearchResponse? { val title = this.selectFirst("h1.grid-title > a")?.ownText()?.trim() ?: return null val href = fixUrl(this.selectFirst("a")!!.attr("href")) - val posterUrl = fixUrlNull(this.selectFirst(".grid-poster > a > img")?.attr("src")) + val posterUrl = fixUrlNull(this.selectFirst("img")?.attr("src")) val type = if (this.selectFirst("div.last-episode") == null) TvType.Movie else TvType.TvSeries return if (type == TvType.TvSeries) { @@ -176,33 +175,34 @@ class LayarKacaProvider : MainAPI() { it } } - invokeCast(link, callback) + loadExtractor(link, bananalicious, subtitleCallback, callback) } return true } - private suspend fun invokeCast( - url: String, - callback: (ExtractorLink) -> Unit - ) { - val response = app.get(url, referer = bananalicious).document - response.select("script[type=text/javascript]").map { script -> - if (script.data().contains(Regex("eval\\(function\\(p,a,c,k,e,[rd]"))) { - val unpackedscript = getAndUnpack(script.data()) - val m3u8Regex = Regex("file.\"(.*?m3u8.*?)\"") - val m3u8 = m3u8Regex.find(unpackedscript)?.destructured?.component1() ?: "" - if (m3u8.isNotEmpty()) { - M3u8Helper.generateM3u8( - fixTitle(URI(url).host).substringBefore("."), - m3u8, - mainUrl - ).forEach(callback) - } - } - } - } - private fun decode(input: String): String = URLDecoder.decode(input, "utf-8").replace(" ", "%20") } + +open class Emturbovid : ExtractorApi() { + override val name = "Emturbovid" + override val mainUrl = "https://emturbovid.com" + override val requiresReferer = true + + override suspend fun getUrl( + url: String, + referer: String?, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ) { + val response = app.get(url, referer = referer) + val m3u8 = Regex("[\"'](.*?master\\.m3u8.*?)[\"']").find(response.text)?.groupValues?.getOrNull(1) + M3u8Helper.generateM3u8( + name, + m3u8 ?: return, + mainUrl + ).forEach(callback) + } + +} diff --git a/LayarKacaProvider/src/main/kotlin/com/hexated/LayarKacaProviderPlugin.kt b/LayarKacaProvider/src/main/kotlin/com/hexated/LayarKacaProviderPlugin.kt index 9743b456..bd3389cb 100644 --- a/LayarKacaProvider/src/main/kotlin/com/hexated/LayarKacaProviderPlugin.kt +++ b/LayarKacaProvider/src/main/kotlin/com/hexated/LayarKacaProviderPlugin.kt @@ -10,5 +10,6 @@ class LayarKacaProviderPlugin: Plugin() { override fun load(context: Context) { // All providers should be added in this manner. Please don't edit the providers list directly. registerMainAPI(LayarKacaProvider()) + registerExtractorAPI(Emturbovid()) } } \ No newline at end of file diff --git a/Movierulzhd/build.gradle.kts b/Movierulzhd/build.gradle.kts index e25e9c0e..1d596ef2 100644 --- a/Movierulzhd/build.gradle.kts +++ b/Movierulzhd/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 36 +version = 37 cloudstream { diff --git a/Movierulzhd/src/main/kotlin/com/hexated/Extractors.kt b/Movierulzhd/src/main/kotlin/com/hexated/Extractors.kt index d30ef49b..87ae7b6d 100644 --- a/Movierulzhd/src/main/kotlin/com/hexated/Extractors.kt +++ b/Movierulzhd/src/main/kotlin/com/hexated/Extractors.kt @@ -11,6 +11,11 @@ import kotlin.random.Random const val twoEmbedAPI = "https://www.2embed.to" +class Sbnmp : Sbflix() { + override val name = "Sbnmp" + override var mainUrl = "https://sbnmp.bar" +} + class Sbrulz : Sbflix() { override val name = "Sbrulz" override var mainUrl = "https://sbrulz.xyz" diff --git a/Movierulzhd/src/main/kotlin/com/hexated/Movierulzhd.kt b/Movierulzhd/src/main/kotlin/com/hexated/Movierulzhd.kt index 26bc47d1..263375b7 100644 --- a/Movierulzhd/src/main/kotlin/com/hexated/Movierulzhd.kt +++ b/Movierulzhd/src/main/kotlin/com/hexated/Movierulzhd.kt @@ -12,7 +12,7 @@ import org.jsoup.nodes.Element import java.net.URI class Movierulzhd : MainAPI() { - override var mainUrl = "https://movierulzhd.trade" + override var mainUrl = "https://movierulzhd.help" private var directUrl = mainUrl override var name = "Movierulzhd" override val hasMainPage = true diff --git a/Movierulzhd/src/main/kotlin/com/hexated/MovierulzhdPlugin.kt b/Movierulzhd/src/main/kotlin/com/hexated/MovierulzhdPlugin.kt index fb2bf567..21710b9d 100644 --- a/Movierulzhd/src/main/kotlin/com/hexated/MovierulzhdPlugin.kt +++ b/Movierulzhd/src/main/kotlin/com/hexated/MovierulzhdPlugin.kt @@ -13,5 +13,6 @@ class MovierulzhdPlugin: Plugin() { registerExtractorAPI(Sbflix()) registerExtractorAPI(Sbrulz()) registerExtractorAPI(Sbmiz()) + registerExtractorAPI(Sbnmp()) } } \ 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/Ngefilm/build.gradle.kts b/Ngefilm/build.gradle.kts index 3187c097..0903f827 100644 --- a/Ngefilm/build.gradle.kts +++ b/Ngefilm/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 3 +version = 4 cloudstream { diff --git a/Ngefilm/src/main/kotlin/com/hexated/Ngefilm.kt b/Ngefilm/src/main/kotlin/com/hexated/Ngefilm.kt index 7e330489..84a14d7d 100644 --- a/Ngefilm/src/main/kotlin/com/hexated/Ngefilm.kt +++ b/Ngefilm/src/main/kotlin/com/hexated/Ngefilm.kt @@ -8,7 +8,7 @@ import com.lagradost.cloudstream3.utils.loadExtractor import org.jsoup.nodes.Element class Ngefilm : MainAPI() { - override var mainUrl = "https://ngefilm21.club" + override var mainUrl = "https://ngefilm21.cfd" override var name = "Ngefilm" override val hasMainPage = true override var lang = "id" diff --git a/RebahinProvider/build.gradle.kts b/RebahinProvider/build.gradle.kts index 6749964c..32b9dd7e 100644 --- a/RebahinProvider/build.gradle.kts +++ b/RebahinProvider/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 5 +version = 6 cloudstream { diff --git a/RebahinProvider/src/main/kotlin/com/hexated/Kitanonton.kt b/RebahinProvider/src/main/kotlin/com/hexated/Kitanonton.kt index aae0b2fb..658f02be 100644 --- a/RebahinProvider/src/main/kotlin/com/hexated/Kitanonton.kt +++ b/RebahinProvider/src/main/kotlin/com/hexated/Kitanonton.kt @@ -3,7 +3,7 @@ package com.hexated import com.lagradost.cloudstream3.* class Kitanonton : RebahinProvider() { - override var mainUrl = "http://kitanonton.org" + override var mainUrl = "http://kitanonton.site" override var name = "KitaNonton" override var mainServer = "https://199.87.210.226" diff --git a/RebahinProvider/src/main/kotlin/com/hexated/RebahinProviderPlugin.kt b/RebahinProvider/src/main/kotlin/com/hexated/RebahinProviderPlugin.kt index 3824f170..ab58bb2d 100644 --- a/RebahinProvider/src/main/kotlin/com/hexated/RebahinProviderPlugin.kt +++ b/RebahinProvider/src/main/kotlin/com/hexated/RebahinProviderPlugin.kt @@ -10,6 +10,6 @@ class RebahinProviderPlugin: Plugin() { override fun load(context: Context) { // All providers should be added in this manner. Please don't edit the providers list directly. registerMainAPI(RebahinProvider()) -// registerMainAPI(Kitanonton()) +registerMainAPI(Kitanonton()) } } \ No newline at end of file diff --git a/TimefourTv/src/main/kotlin/com/hexated/TimefourTv.kt b/TimefourTv/src/main/kotlin/com/hexated/TimefourTv.kt index 0c8aa34b..d1a32510 100644 --- a/TimefourTv/src/main/kotlin/com/hexated/TimefourTv.kt +++ b/TimefourTv/src/main/kotlin/com/hexated/TimefourTv.kt @@ -19,7 +19,7 @@ open class TimefourTv : MainAPI() { ) companion object { - const val daddyUrl = "https://daddylivehd.sx" + const val daddyUrl = "https://daddylive.watch" val daddyHost: String = URI(daddyUrl).host.split(".").first() } 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(".")) + } }