diff --git a/ExampleProvider/build.gradle.kts b/ExampleProvider/build.gradle.kts deleted file mode 100644 index 58645fe..0000000 --- a/ExampleProvider/build.gradle.kts +++ /dev/null @@ -1,24 +0,0 @@ -// use an integer for version numbers -version = 1 - - -cloudstream { - // All of these properties are optional, you can safely remove them - - description = "Lorem Ipsum" - authors = listOf("Cloudburst") - - /** - * Status int as the following: - * 0: Down - * 1: Ok - * 2: Slow - * 3: Beta only - * */ - status = 1 // will be 3 if unspecified - - // List of video source types. Users are able to filter for extensions in a given category. - // You can find a list of avaliable types here: - // https://recloudstream.github.io/cloudstream/html/app/com.lagradost.cloudstream3/-tv-type/index.html - tvTypes = listOf("Others") -} diff --git a/ExampleProvider/src/main/kotlin/com/example/ExampleProvider.kt b/ExampleProvider/src/main/kotlin/com/example/ExampleProvider.kt deleted file mode 100644 index e44a21b..0000000 --- a/ExampleProvider/src/main/kotlin/com/example/ExampleProvider.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.example - -import com.lagradost.cloudstream3.TvType -import com.lagradost.cloudstream3.MainAPI -import com.lagradost.cloudstream3.SearchResponse - -class ExampleProvider : MainAPI() { // all providers must be an instance of MainAPI - override var mainUrl = "https://example.com/" - override var name = "Example provider" - override val supportedTypes = setOf(TvType.Movie) - - override var lang = "en" - - // enable this when your provider has a main page - override val hasMainPage = true - - // this function gets called when you search for something - override suspend fun search(query: String): List { - return listOf() - } -} \ No newline at end of file diff --git a/UakinoProvider/build.gradle.kts b/UakinoProvider/build.gradle.kts new file mode 100644 index 0000000..49b997d --- /dev/null +++ b/UakinoProvider/build.gradle.kts @@ -0,0 +1,27 @@ +// use an integer for version numbers +version = 1 + + +cloudstream { + language = "uk" + // All of these properties are optional, you can safely remove them + + description = "Дивитися фільми та серіали онлайн в HD якості. У нас можна дивитися кіно онлайн безкоштовно, у високій якості та з якісним українським дубляжем." + authors = listOf("CakesTwix") + + /** + * Status int as the following: + * 0: Down + * 1: Ok + * 2: Slow + * 3: Beta only + * */ + status = 1 // will be 3 if unspecified + tvTypes = listOf( + "Anime", + "TvSeries", + "Movie", + ) + + iconUrl = "https://www.google.com/s2/favicons?domain=uakino.club&sz=%size%" +} \ No newline at end of file diff --git a/ExampleProvider/src/main/AndroidManifest.xml b/UakinoProvider/src/main/AndroidManifest.xml similarity index 52% rename from ExampleProvider/src/main/AndroidManifest.xml rename to UakinoProvider/src/main/AndroidManifest.xml index 1863f02..29aec9d 100644 --- a/ExampleProvider/src/main/AndroidManifest.xml +++ b/UakinoProvider/src/main/AndroidManifest.xml @@ -1,2 +1,2 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/UakinoProvider/src/main/kotlin/com/lagradost/UakinoProvider.kt b/UakinoProvider/src/main/kotlin/com/lagradost/UakinoProvider.kt new file mode 100644 index 0000000..09656cb --- /dev/null +++ b/UakinoProvider/src/main/kotlin/com/lagradost/UakinoProvider.kt @@ -0,0 +1,188 @@ +package com.lagradost + +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.LoadResponse.Companion.addActors +import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer +import com.lagradost.cloudstream3.mvvm.safeApiCall +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 UakinoProvider : MainAPI() { + + // Basic Info + override var mainUrl = "https://uakino.club" + override var name = "Uakino" + override val hasMainPage = true + override var lang = "uk" + override val hasDownloadSupport = true + override val supportedTypes = setOf( + TvType.Movie, + TvType.TvSeries, + TvType.Anime + ) + + // Sections + override val mainPage = mainPageOf( + "$mainUrl/filmy/page/" to "Фільми", + "$mainUrl/seriesss/page/" to "Серіали", + "$mainUrl/seriesss/doramy/page/" to "Дорами", + "$mainUrl/animeukr/page/" to "Аніме", + "$mainUrl/cartoon/page/" to "Мультфільми", + "$mainUrl/cartoon/cartoonseries/page/" to "Мультсеріали", + ) + + override suspend fun getMainPage( + page: Int, + request: MainPageRequest + ): HomePageResponse { + val document = app.get(request.data + page).document + val home = document.select("div.owl-item, div.movie-item").map { + it.toSearchResponse() + } + return newHomePageResponse(request.name, home) + } + + private fun Element.toSearchResponse(): SearchResponse { + val title = this.selectFirst("a.movie-title")?.text()?.trim().toString() + val href = this.selectFirst("a.movie-title")?.attr("href").toString() + val posterUrl = fixUrlNull(this.selectFirst("img")?.attr("src")) + return newMovieSearchResponse(title, href, TvType.Movie) { + 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("div.movie-item.short-item").map { + it.toSearchResponse() + } + } + + override suspend fun load(url: String): LoadResponse { + val document = app.get(url).document + + // Parse info + val title = document.selectFirst("h1 span.solototle")?.text()?.trim().toString() + val poster = fixUrl(document.selectFirst("div.film-poster img")?.attr("src").toString()) + val tags = document.select("div.film-info > div:nth-child(4) a").map { it.text() } + val year = document.select("div.film-info > div:nth-child(2) a").text().toIntOrNull() + // TODO: Fix Naruto. Its series, not Movie Lol + // https://uakino.club/animeukr/6268-naruto-1-sezon.html + val tvType = + if (url.contains(Regex("(/anime-series)|(/seriesss)|(/cartoonseries)"))) TvType.TvSeries else TvType.Movie + val description = document.selectFirst("div[itemprop=description]")?.text()?.trim() + val trailer = document.selectFirst("iframe#pre")?.attr("data-src") + val rating = document.selectFirst("div.film-info > div:nth-child(8) div.fi-desc")?.text() + ?.substringBefore("/").toRatingInt() + val actors = document.select("div.film-info > div:nth-child(6) a").map { it.text() } + + val recommendations = document.select("div#full-slides div.owl-item").map { + it.toSearchResponse() + } + + // Return to app + // Parse Episodes as Series + return if (tvType == TvType.TvSeries) { + val id = url.split("/").last().split("-").first() + val episodes = + app.get("$mainUrl/engine/ajax/playlists.php?news_id=$id&xfield=playlist&time=${Date().time}") + .parsedSafe()?.response.let { + Jsoup.parse(it.toString()).select("ul > li").mapNotNull { eps -> + val href = fixUrl(eps.attr("data-file")) // ashdi.vip/... + val name = "${eps.text().trim()} (${eps.attr("data-voice")})" // Серія 1 (Озвучення) + if (href.isNotEmpty()) { + Episode( + href, + name, + ) + } else { + null + } + } + } + newTvSeriesLoadResponse(title, url, TvType.TvSeries, episodes) { + this.posterUrl = poster + this.year = year + this.plot = description + this.tags = tags + this.rating = rating + addActors(actors) + this.recommendations = recommendations + addTrailer(trailer) + } + } else { // Parse as Movie. + newMovieLoadResponse(title, url, TvType.Movie, url) { + this.posterUrl = poster + this.year = year + this.plot = description + this.tags = tags + this.rating = rating + addActors(actors) + this.recommendations = recommendations + addTrailer(trailer) + } + } + } + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + + val links = ArrayList() + + if (data.startsWith("https://ashdi.vip")) { + links.add(data) + } else { + val iframeUrl = app.get(data).document.selectFirst("iframe#pre")?.attr("src") + if (iframeUrl.isNullOrEmpty()) { + val id = data.split("/").last().split("-").first() + app.get("$mainUrl/engine/ajax/playlists.php?news_id=$id&xfield=playlist&time=${Date().time}") + .parsedSafe()?.response.let { + Jsoup.parse(it.toString()).select("ul > li").mapNotNull { mirror -> + links.add(fixUrl(mirror.attr("data-file"))) + } + } + } else { + links.add(iframeUrl) + } + } + + links.apmap { link -> + safeApiCall { + app.get(link, referer = "$mainUrl/").document.select("script").map { script -> + if (script.data().contains("var player = new Playerjs({")) { + val m3uLink = + script.data().substringAfterLast("file:\"").substringBefore("\",") + M3u8Helper.generateM3u8( + source = this.name, + streamUrl = m3uLink, + referer = "https://ashdi.vip/" + ).forEach(callback) + } + } + } + } + + return true + } + + data class Responses( + val success: Boolean?, + val response: String, + ) + +} \ No newline at end of file diff --git a/ExampleProvider/src/main/kotlin/com/example/ExamplePlugin.kt b/UakinoProvider/src/main/kotlin/com/lagradost/UakinoProviderPlugin.kt similarity index 75% rename from ExampleProvider/src/main/kotlin/com/example/ExamplePlugin.kt rename to UakinoProvider/src/main/kotlin/com/lagradost/UakinoProviderPlugin.kt index 8ddce48..47f1bcc 100644 --- a/ExampleProvider/src/main/kotlin/com/example/ExamplePlugin.kt +++ b/UakinoProvider/src/main/kotlin/com/lagradost/UakinoProviderPlugin.kt @@ -1,13 +1,14 @@ -package com.example + +package com.lagradost import com.lagradost.cloudstream3.plugins.CloudstreamPlugin import com.lagradost.cloudstream3.plugins.Plugin import android.content.Context @CloudstreamPlugin -class TestPlugin: Plugin() { +class UakinoProviderPlugin: Plugin() { override fun load(context: Context) { // All providers should be added in this manner. Please don't edit the providers list directly. - registerMainAPI(ExampleProvider()) + registerMainAPI(UakinoProvider()) } } \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index b19ff67..55f36bf 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -37,15 +37,15 @@ subprojects { cloudstream { // when running through github workflow, GITHUB_REPOSITORY should contain current repository name // you can modify it to use other git hosting services, like gitlab - setRepo(System.getenv("GITHUB_REPOSITORY") ?: "https://github.com/user/repo") + setRepo(System.getenv("GITHUB_REPOSITORY") ?: "https://github.com/CakesTwix/cloudstream-extensions-uk") } android { - compileSdkVersion(30) + compileSdkVersion(33) defaultConfig { minSdk = 21 - targetSdk = 30 + targetSdk = 33 } compileOptions { @@ -77,7 +77,7 @@ subprojects { // https://github.com/recloudstream/cloudstream/blob/master/app/build.gradle implementation(kotlin("stdlib")) // adds standard kotlin features, like listOf, mapOf etc implementation("com.github.Blatzar:NiceHttp:0.3.2") // http library - implementation("org.jsoup:jsoup:1.13.1") // html parser + implementation("org.jsoup:jsoup:1.15.1") // html parser } }