diff --git a/AnitubeinuaProvider/build.gradle.kts b/AnitubeinuaProvider/build.gradle.kts new file mode 100644 index 0000000..c4228cc --- /dev/null +++ b/AnitubeinuaProvider/build.gradle.kts @@ -0,0 +1,25 @@ +// use an integer for version numbers +version = 1 + + +cloudstream { + language = "uk" + // All of these properties are optional, you can safely remove them + + description = "На нашому сайті ви можете подивитися аніме онлайн українською безкоштовно. Великий список аніме онлайн тільки у нас." + authors = listOf("CakesTwix") + + /** + * Status int as the following: + * 0: Down + * 1: Ok + * 2: Slow + * 3: Beta only + * */ + status = 3 // will be 3 if unspecified + tvTypes = listOf( + "Anime", + ) + + iconUrl = "https://www.google.com/s2/favicons?domain=anitube.in.ua&sz=%size%" +} \ No newline at end of file diff --git a/AnitubeinuaProvider/src/main/AndroidManifest.xml b/AnitubeinuaProvider/src/main/AndroidManifest.xml new file mode 100644 index 0000000..29aec9d --- /dev/null +++ b/AnitubeinuaProvider/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/AnitubeinuaProvider/src/main/kotlin/com/lagradost/AnitubeinuaProvider.kt b/AnitubeinuaProvider/src/main/kotlin/com/lagradost/AnitubeinuaProvider.kt new file mode 100644 index 0000000..1494e72 --- /dev/null +++ b/AnitubeinuaProvider/src/main/kotlin/com/lagradost/AnitubeinuaProvider.kt @@ -0,0 +1,253 @@ +package com.lagradost + +import com.lagradost.models.PlayerJson +import com.lagradost.extractors.AshdiExtractor +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.M3u8Helper +import org.jsoup.Jsoup +import org.jsoup.nodes.Element +import java.util.* + +class AnitubeinuaProvider : MainAPI() { + + // Basic Info + override var mainUrl = "https://anitube.in.ua" + override var name = "Anitubeinua Beta" + override val hasMainPage = true + override var lang = "uk" + override val hasDownloadSupport = true + override val supportedTypes = setOf( + TvType.AnimeMovie, + TvType.Anime, + ) + + // Sections + override val mainPage = mainPageOf( + "$mainUrl/anime/page/" to "Нові", + "$mainUrl/f/sort=rating/order=desc/page/" to "Популярне", + ) + + override suspend fun getMainPage( + page: Int, + request: MainPageRequest + ): HomePageResponse { + val document = app.get(request.data + page).document + + val home = document.select(".story").map { + it.toSearchResponse() + } + return newHomePageResponse(request.name, home) + } + + private fun Element.toSearchResponse(): SearchResponse { + val title = this.selectFirst(".story_c h2 a")?.text()?.trim().toString() + val href = this.selectFirst(".story_c h2 a")?.attr("href").toString() + val posterUrl = mainUrl + this.selectFirst(".story_c_l span.story_post img")?.attr("src") + + return newMovieSearchResponse(title, href, TvType.Anime) { + this.posterUrl = posterUrl + } + + } + + override suspend fun search(query: String): List { + val document = app.post( + url = mainUrl, + data = mapOf( + "do" to "search", + "subaction" to "search", + "story" to query.replace(" ", "+") + ) + ).document + + return document.select("article.story").map { + it.toSearchResponse() + } + } + + // Detailed information + override suspend fun load(url: String): LoadResponse { + val document = app.get(url).document + + val someInfo = document.select("div.story_c_r")[1] + + // Parse info + val title = document.selectFirst(".story_c h2")?.text()?.trim().toString() + val poster = mainUrl + document.selectFirst(".story_c_left span.story_post img")?.attr("src") + val tags = someInfo.select("noindex a").html().split("\n").map { it } + val year = someInfo.select("strong:contains(Рік випуску аніме:)").next().html().toIntOrNull() + + val tvType = TvType.Anime + val description = document.selectFirst("div.my-text")?.text()?.trim() + // val author = someInfo.select("strong:contains(Студія:)").next().html() + val rating = document.selectFirst(".lexington-box > div:last-child span")?.text().toRatingInt() + + val recommendations = document.select(".horizontal ul").map { + it.toSearchResponse() + } + + // Return to app + // Players, Episodes, Number of episodes + var episodes: List = emptyList() + val id = url.split("/").last().split("-").first() + val responseGet = app.get("$mainUrl/engine/ajax/playlists.php?news_id=$id&xfield=playlist&time=${Date().time}").parsedSafe()!! + if (responseGet.success == true) { // First type players + episodes = + app.get("$mainUrl/engine/ajax/playlists.php?news_id=$id&xfield=playlist&time=${Date().time}") + .parsedSafe()?.response.let { + Jsoup.parse(it.toString()).select("div.playlists-videos li") + .mapIndexedNotNull() { index, eps -> + val href = + "$mainUrl/engine/ajax/playlists.php?news_id=$id&xfield=playlist&time=${Date().time}" + val name = eps.text().trim() // Серія 1 + if (href.isNotEmpty()) { + Episode( + "$href, $index", // link, Серія 1 + name, + ) + } else { + null + } + } + } + } else { + document.select("script").map{ script -> + if (script.data().contains("RalodePlayer.init(")) { + val playerScriptRawJson = script.data().substringAfterLast(".init(").substringBefore(");") + val playerEpisodesRawJson = playerScriptRawJson.substringAfter("],").substringBeforeLast(",") + // val playerNamesArray = (playerScriptRawJson.substringBefore("],") + "]").dropLast(1).drop(1).replace("\",\"", ",,,").split(",,,") + // val numberOfEpisodesInt = playerScriptRawJson.substringAfterLast(",").toIntOrNull() + + val playerJson = tryParseJson>>(playerEpisodesRawJson)!! + for(item in playerJson) { + item.forEachIndexed { index, item2 -> + if(!item2.name.contains("ПЛЕЙЛИСТ")) // UFDub player + { + episodes = episodes.plus( + Episode( + "$index, $url", + item2.name, + episode = item2.name.replace("Серія ","").toIntOrNull(), + ) + ) + } + } + } + } + + } + } + + return newTvSeriesLoadResponse(title, url, tvType, episodes.distinctBy{ it.name }) { + this.posterUrl = poster + this.year = year + this.plot = description + this.tags = tags + this.rating = rating + this.recommendations = recommendations + } + } + + + // It works when I click to view the series + override suspend fun loadLinks( + data: String, // (First) link, index | (Two) index, url title + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + val dataList = data.split(", ") + + if(dataList[0].contains("https://")){ // Its First type player + + // Get episodes list (as json) + val responseGet = app.get(dataList[0]).parsedSafe() // ajax link + responseGet?.response?.let { it -> + + // List Players + val playersTab = Jsoup.parse(it).select("div.playlists-items") + + // Parse all episodes by name + var index = 0 + var playerTabId = "" + Jsoup.parse(it).select("div.playlists-videos li") + .mapNotNull { eps -> + // 0 - idk, 1 - dub, 2 - player + // if with sub + // 0 - idk 1 - dub 2 - sub or dub 3 - player + // dataList[1] - index + // 0_1_2 + if(playerTabId != eps.attr("data-id")){ + index = -1 + playerTabId = eps.attr("data-id") + } + + if(dataList[1].toInt() == index){ + var href = eps.attr("data-file") // m3u url + // Can be without https: + if (!href.contains("https://")) { + href = "https:$href" + } + + val dubName = playersTab[0].select(" li[data-id=${ playerTabId.take(3) }]").text() // G&M + var playerName = playersTab[1].select(" li[data-id=$playerTabId]").text() // ПЛЕЄР ASHDI + + if(playerTabId.count { it == '_' } == 3) { + playerName = + playersTab[2].selectFirst(" li[data-id=$playerTabId]")!! + .text() // ПЛЕЄР ASHDI + } + + if (href.contains("https://ashdi.vip/vod")) { + // Add as source + M3u8Helper.generateM3u8( + source = "$playerName ($dubName)", + streamUrl = AshdiExtractor().ParseM3U8(href), + referer = "https://qeruya.cyou" + ).forEach(callback) + } + } + index += 1 + } + } + } else { + val document = app.get(dataList[1]).document + document.select("script").map { script -> + if (script.data().contains("RalodePlayer.init(")) { + val playerScriptRawJson = script.data().substringAfterLast(".init(").substringBefore(");") + val playerEpisodesRawJson = playerScriptRawJson.substringAfter("],").substringBeforeLast(",") + val playerNamesArray = (playerScriptRawJson.substringBefore("],") + "]").dropLast(1).drop(1).replace("\",\"", ",,,").split(",,,") + + val playerJson = tryParseJson>>(playerEpisodesRawJson)!! + playerJson.forEachIndexed { index, dub -> + if(dub[dataList[0].toInt()].code.contains("https://ashdi.vip")){ + M3u8Helper.generateM3u8( + source = decode(playerNamesArray[index]), + streamUrl = AshdiExtractor().ParseM3U8(Jsoup.parse(dub[dataList[0].toInt()].code).select("iframe").attr("src")), + referer = "https://qeruya.cyou" + ).forEach(callback) + } + + } + } + } + } + return true + } + + private fun decode(input: String): String{ + // Decoded string, thanks to Secozzi + val hexRegex = Regex("\\\\u([0-9a-fA-F]{4})") + return hexRegex.replace(input) { matchResult -> + Integer.parseInt(matchResult.groupValues[1], 16).toChar().toString() + } + } + + data class Responses( + val success: Boolean?, + val response: String?, + val message: String? + ) +} \ No newline at end of file diff --git a/AnitubeinuaProvider/src/main/kotlin/com/lagradost/AnitubeinuaProviderPlugin.kt b/AnitubeinuaProvider/src/main/kotlin/com/lagradost/AnitubeinuaProviderPlugin.kt new file mode 100644 index 0000000..e67f482 --- /dev/null +++ b/AnitubeinuaProvider/src/main/kotlin/com/lagradost/AnitubeinuaProviderPlugin.kt @@ -0,0 +1,13 @@ +package com.lagradost + +import com.lagradost.cloudstream3.plugins.CloudstreamPlugin +import com.lagradost.cloudstream3.plugins.Plugin +import android.content.Context + +@CloudstreamPlugin +class AnitubeinuaProviderPlugin: Plugin() { + override fun load(context: Context) { + // All providers should be added in this manner. Please don't edit the providers list directly. + registerMainAPI(AnitubeinuaProvider()) + } +} \ No newline at end of file diff --git a/AnitubeinuaProvider/src/main/kotlin/com/lagradost/extractors/AshdiExtractor.kt b/AnitubeinuaProvider/src/main/kotlin/com/lagradost/extractors/AshdiExtractor.kt new file mode 100644 index 0000000..eb765fc --- /dev/null +++ b/AnitubeinuaProvider/src/main/kotlin/com/lagradost/extractors/AshdiExtractor.kt @@ -0,0 +1,12 @@ +package com.lagradost.extractors; + +import com.lagradost.cloudstream3.app + +class AshdiExtractor() { + suspend fun ParseM3U8(url: String): String{ + return app.get(url).document.select("script").html() + .substringAfterLast("file:\"") + .substringBefore("\",") + + } +} diff --git a/AnitubeinuaProvider/src/main/kotlin/com/lagradost/models/PlayerJson.kt b/AnitubeinuaProvider/src/main/kotlin/com/lagradost/models/PlayerJson.kt new file mode 100644 index 0000000..c6cdcbc --- /dev/null +++ b/AnitubeinuaProvider/src/main/kotlin/com/lagradost/models/PlayerJson.kt @@ -0,0 +1,9 @@ +package com.lagradost.models + +data class PlayerJson ( + + val name : String, // Серія 0 + val code : String, // iframe block + val zid : Int, + val sid : Int +)