diff --git a/UAFlixProvider/src/main/kotlin/com/lagradost/UAFlixProvider.kt b/UAFlixProvider/src/main/kotlin/com/lagradost/UAFlixProvider.kt index 58d8390..d02a0db 100644 --- a/UAFlixProvider/src/main/kotlin/com/lagradost/UAFlixProvider.kt +++ b/UAFlixProvider/src/main/kotlin/com/lagradost/UAFlixProvider.kt @@ -21,7 +21,6 @@ import com.lagradost.cloudstream3.newAnimeSearchResponse import com.lagradost.cloudstream3.newHomePageResponse import com.lagradost.cloudstream3.newMovieLoadResponse import com.lagradost.cloudstream3.toRatingInt -import com.lagradost.cloudstream3.utils.AppUtils import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.M3u8Helper diff --git a/UAFlixProvider/src/main/kotlin/com/lagradost/models/PlayerJson.kt b/UAFlixProvider/src/main/kotlin/com/lagradost/models/PlayerJson.kt index b0cbb53..163dd77 100644 --- a/UAFlixProvider/src/main/kotlin/com/lagradost/models/PlayerJson.kt +++ b/UAFlixProvider/src/main/kotlin/com/lagradost/models/PlayerJson.kt @@ -1,7 +1,5 @@ package com.lagradost.models -import com.google.gson.annotations.SerializedName - data class PlayerJson ( val title : String, diff --git a/UATuTFunProvider/build.gradle.kts b/UATuTFunProvider/build.gradle.kts new file mode 100644 index 0000000..6d3f8f0 --- /dev/null +++ b/UATuTFunProvider/build.gradle.kts @@ -0,0 +1,32 @@ +// use an integer for version numbers +version = 1 + +dependencies { + // https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind + implementation("com.fasterxml.jackson.core:jackson-databind:2.19.1") + implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.19.1") + // https://mvnrepository.com/artifact/junit/junit + testImplementation("junit:junit:4.13.2") +// https://mvnrepository.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-test + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.10.1") + testImplementation(kotlin("test")) +} + +cloudstream { + language = "uk" + // All of these properties are optional, you can safely remove them + + description = "uatut.fun - практичний та ексклюзивний кінотеатр для перегляду відео у комфортній обстановці." + authors = listOf("CakesTwix") + + /** + * Status int as the following: + * 0: Down + * 1: Ok + * 2: Slow + * 3: Beta only + * */ + status = 2 // will be 3 if unspecified + + iconUrl = "https://www.google.com/s2/favicons?domain=uatut.fun&sz=%size%" +} diff --git a/UATuTFunProvider/src/main/AndroidManifest.xml b/UATuTFunProvider/src/main/AndroidManifest.xml new file mode 100644 index 0000000..29aec9d --- /dev/null +++ b/UATuTFunProvider/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/UATuTFunProvider/src/main/kotlin/com/lagradost/UATuTFunProvider.kt b/UATuTFunProvider/src/main/kotlin/com/lagradost/UATuTFunProvider.kt new file mode 100644 index 0000000..8537833 --- /dev/null +++ b/UATuTFunProvider/src/main/kotlin/com/lagradost/UATuTFunProvider.kt @@ -0,0 +1,598 @@ +package com.lagradost + +import com.fasterxml.jackson.databind.json.JsonMapper +import com.fasterxml.jackson.module.kotlin.readValue +//import com.lagradost.api.Log +import com.lagradost.cloudstream3.Episode +import com.lagradost.cloudstream3.HomePageResponse +import com.lagradost.cloudstream3.LoadResponse +import com.lagradost.cloudstream3.LoadResponse.Companion.addActors +import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer +import com.lagradost.cloudstream3.MainAPI +import com.lagradost.cloudstream3.MainPageRequest +import com.lagradost.cloudstream3.MovieSearchResponse +import com.lagradost.cloudstream3.SearchResponse +import com.lagradost.cloudstream3.SubtitleFile +import com.lagradost.cloudstream3.TvSeriesLoadResponse +import com.lagradost.cloudstream3.TvType +import com.lagradost.cloudstream3.app +import com.lagradost.cloudstream3.fixUrl +import com.lagradost.cloudstream3.mainPageOf +import com.lagradost.cloudstream3.newHomePageResponse +import com.lagradost.cloudstream3.newMovieLoadResponse +import com.lagradost.cloudstream3.newMovieSearchResponse +import com.lagradost.cloudstream3.newTvSeriesLoadResponse +import com.lagradost.cloudstream3.toRatingInt +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.M3u8Helper +import com.lagradost.model.Season +import com.lagradost.model.SeriesJsonDataModel +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import java.net.URLDecoder +import java.text.SimpleDateFormat + +class UATuTFunProvider : MainAPI() { + + private val movieSelector = "#dle-content" + private val titleSelector = "div.poster__desc > h3 > a > span" + private val videoUrlSelector = "data-url" + private val posterUrlSelector = + "div.poster__img.img-responsive.img-responsive--portrait.img-fit-cover.anim > img" + private val searchMovieSelector = "div.poster.grid-item" + private val otherDataSelector = "div.bslide__desc > ul.bslide__text" + private val simpleDateFormat = SimpleDateFormat("yyyy-MM-dd", java.util.Locale.getDefault()) + private val mapper = JsonMapper.builder() + .configure(com.fasterxml.jackson.core.JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true) + .configure(com.fasterxml.jackson.core.JsonParser.Feature.ALLOW_SINGLE_QUOTES, true) + .build() + + // Basic Info + override var mainUrl = "https://uk.uatut.fun" + override var name = "UATuT" + override val hasMainPage = true + override var lang = "uk" + + override val hasDownloadSupport = false + + override val supportedTypes = setOf( + TvType.TvSeries, + TvType.Cartoon, + TvType.Movie, + TvType.Anime + ) + + // Sections + override val mainPage = mainPageOf( + "$mainUrl/page/" to "Новинки", + "$mainUrl/film/page/" to "Фільми", + "$mainUrl/serie/page/" to "Серіали", + "$mainUrl/cartoon/series/page/" to "Мультсеріали", + "$mainUrl/cartoon/page/" to "Мультфільми", + "$mainUrl/anime/page/" to "Аніме" + ) + + override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse { + val document = app.get(request.data + page).document + val first = document.select(movieSelector) + .firstOrNull() + + + return if (first == null) { + newHomePageResponse(request.name, emptyList()) + } else { + val mainPage = first.children().map { + it.toSearchResponse() + }.filter { !it.posterUrl.isNullOrEmpty() } + newHomePageResponse(request.name, mainPage) + } + } + + override suspend fun search(query: String): List { + val response = + app.post("$mainUrl/index.php?do=search&subaction=search&story=$query").document + return response.select(searchMovieSelector).map { + it.toSearchResponse() + } + } + + override suspend fun load(url: String): LoadResponse { +// Log.d("DEBUG load", "Url: $url") + val document = app.get(url).document + + return when (val tvType = getTvType(url)) { + TvType.Movie, TvType.Cartoon -> {//videos with 1 episode + getNewMovieLoadResponse(document, tvType, url) + } + + TvType.Anime -> { + val episodes = getEpisodes(document) + + if (episodes.isNotEmpty()) {//multiple episodes + getNewTvSeriesLoadResponse(document, tvType, url, episodes) + } else {//one episode + getNewMovieLoadResponse(document, tvType, url) + } + } + + else -> { //TvSeries + val episodes = getEpisodes(document) + + getNewTvSeriesLoadResponse(document, tvType, url, episodes) + } + } + + } + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { +// Log.d("DEBUG loadLinks", "Data: $data") + val tvType = if (data.startsWith("http")) { + TvType.Movie + } else { + TvType.TvSeries + } + + return when (tvType) { + TvType.Movie -> {//movie cartoon + val document = app.get(data).document + val m3uUrl = getM3uUrl(document) + val dubName = getMovieDubName(document) + + + if (m3uUrl.endsWith(".m3u8")) { + M3u8Helper.generateM3u8( + source = dubName, + streamUrl = m3uUrl, + referer = "https://uk.uatut.fun/" + ).last().let(callback) + } else { + val m3u8 = app.get(m3uUrl) + val jsonArray = mapper.readTree(m3u8.text) + val m3uFileUrl = jsonArray.firstOrNull { nodes -> !nodes.get("file").isNull } + + val m3u8DirectFileUrl = m3uFileUrl?.get("file")?.textValue() ?: "" + + M3u8Helper.generateM3u8( + source = dubName, + streamUrl = m3u8DirectFileUrl, + referer = "https://uk.uatut.fun/" + ).forEach(callback) + } + + true + } + + else -> {//series + val (episodeName, episodeSeasonName, seriesUrl) = data.split(";") +// Log.d( +// "DEBUG loadLinks", "EpisodeName: $episodeName, SeasonName:" + +// " $episodeSeasonName, SeriesUrl: $seriesUrl" +// ) + + val jsonDataModel = + getSeriesJsonDataModelByEpisodeName(episodeName, episodeSeasonName, seriesUrl) + if (jsonDataModel.isEmpty()) { + false + } else { + val sourceDubName = jsonDataModel.first().seriesDubName + val m3u8DirectFileUrl = + jsonDataModel.first().seasons.first().episodes.first().file + M3u8Helper.generateM3u8( + source = sourceDubName, + streamUrl = m3u8DirectFileUrl, + referer = "https://uk.uatut.fun/" + ).forEach(callback) + true + } + } + } + } + + private fun getMovieDubName(document: Document): String { + val keyword = "Озвучення:" + val delimiter = "|" + val text = document.select("ul.pmovie__list").select("li") + .firstOrNull { it.text().contains(keyword) }?.text() ?: "" + + if (text.contains(delimiter)) { + return text.substringAfterLast(delimiter).trim() + } + return text.replace(keyword, "").trim() + } + + private fun Element.toSearchResponse(): MovieSearchResponse { + val title = this.select(titleSelector).text() + val url = this.attr(videoUrlSelector) + val posterUrl = fixUrl( + this.select(posterUrlSelector) + .attr("data-src") + ) +// Log.d("DEBUG MovieSearchResponse", "seriesUrl: $posterUrl") + + return newMovieSearchResponse(title, url, TvType.Movie) { + this.posterUrl = posterUrl + } + } + + private suspend fun getNewTvSeriesLoadResponse( + document: Document, + tvType: TvType, + url: String, + episodes: List + ): TvSeriesLoadResponse { + val title = getPageTitle(document) + val engTitle = getPageEngTitle(document) + val posterUrl = getPagePosterUrl(document) + val year = getYear(document) + val description = getDescription(document) + val rating = getRating(document) + val duration = getDuration(document) + val trailerUrl = getTrailerUrL(document) + val tags = getTags(document) + val actors = getActors(document) + + return newTvSeriesLoadResponse(title, url, tvType, episodes) { + this.posterUrl = posterUrl + this.plot = description + this.tags = tags + this.year = year + this.rating = rating + this.name = "$title ($engTitle)" + addActors(actors) + addTrailer(trailerUrl) + this.duration = duration + } + + } + + private suspend fun getNewMovieLoadResponse( + document: Document, + tvType: TvType, + url: String + ): LoadResponse { + + val title = getPageTitle(document) + val engTitle = getPageEngTitle(document) + val posterUrl = getPagePosterUrl(document) + val year = getYear(document) + val description = getDescription(document) + val rating = getRating(document) + val duration = getDuration(document) + val trailerUrl = getTrailerUrL(document) + val tags = getTags(document) + val actors = getActors(document) + + return newMovieLoadResponse(title, url, tvType, url) { + this.posterUrl = posterUrl + this.plot = description + this.tags = tags + this.year = year + this.rating = rating + this.name = "$title ($engTitle)" + addActors(actors) + addTrailer(trailerUrl) + this.duration = duration + } + } + + + private suspend fun getSeriesJsonDataModel( + seriesUrl: String, + ): List { + val document = app.get(seriesUrl).document +// Log.d("DEBUG getSeriesJsonDataModel", "seriesUrl: $seriesUrl document: ${document.childrenSize()}") + val m3uUrl = getM3uUrl(document) + + val text = if (m3uUrl.startsWith("http") && m3uUrl.endsWith(".txt")) { + app.get(m3uUrl).text + } else if (m3uUrl.isNotEmpty()) { + return try { + val stringToJson = stringToJson(m3uUrl) + getObjectFromJson(stringToJson) + } catch (e: Exception) { + System.err.println(e) + emptyList() + } + } else { + return emptyList() + } + + val m3uData = stringToJson(text) + + +// Log.d("DEBUG getSeriesJsonDataModel", "Text: $text") + //find all episodes and seasons + return try { + getObjectFromJson(m3uData) + } catch (e: Exception) { + System.err.println(e) + emptyList() + } + } + + private fun stringToJson(text: String): String { + var m3uData = text.replace("\\", "") + + if (m3uData.startsWith("\"")) { + m3uData = m3uData.replaceFirst("\"", "") + } + if (m3uData.endsWith("\"")) { + m3uData = m3uData.replaceAfterLast("\"", "") + } + return m3uData + } + + private fun getObjectFromJson(m3uData: String): List { + val result = mutableListOf() + val items: List = mapper.readValue>(m3uData) + val seriesDubCheck = items.first()?.seriesDubName ?: "" + + if (seriesDubCheck.isEmpty()) { + val episodesList: List = mapper.readValue(m3uData) + episodesList.forEach { episode -> + val episodeName = episode?.name ?: "" + if (episodeName.isEmpty()) { + episode.name = "1" + } + } + result.add(SeriesJsonDataModel("1", listOf(Season("1", episodesList)))) + } else { + result.addAll(items) + } + + return result + } + + private suspend fun getM3uUrl(document: Document): String { + var sourceUrl = fixUrl(document.select("iframe").attr("data-src")) + + if (sourceUrl.contains("youtube")) { + sourceUrl = document.select("div.video-inside") + .first { !it.select("div[data-iframe]").isEmpty() } + .select("div[data-iframe]").attr("data-iframe") + } + val documentM3u = app.get(sourceUrl).document + var m3uUrl = documentM3u.select("iframe").attr("src") + if (m3uUrl.endsWith(".txt")) { + val url = with(m3uUrl) { + val substringAfterLast = m3uUrl.substringAfterLast("file=") + URLDecoder.decode(substringAfterLast, "UTF-8") + } + return url + } + + + val getJsonData = + "{" + documentM3u.toString().substringAfterLast("var player = new Playerjs({") + .substringBefore(");") + + m3uUrl = mapper.readTree(getJsonData).get("file").asText() + + if (m3uUrl.first() == '"') { + m3uUrl = m3uUrl.replace("\"", "") + } + return m3uUrl + } + + private suspend fun getEpisodes(document: Document): List { + val url = document.select("link[rel=canonical]").attr("href") + val episodes = document.select("div.b-post__schedule_block").flatMap { season -> + + val seasonName = season.select("div.title").text() + season.select("tbody > tr.current-episode").map { episode -> + + val episodeName = episode.select("td.td-1").text() + val episodeSeason = seasonName.filter { it.isDigit() }.toInt() + val episodePosterUrl = getEpisodePosterUrl(url, seasonName, episodeName) + val episodeDate: Long = getEpisodeDate(episode) + val episodeNumber = episodeName.filter { it.isDigit() }.toInt() + val episodeSeasonTag = "$episodeName;$seasonName;$url" + Episode( + data = episodeSeasonTag, + name = episodeName, + season = episodeSeason, + episode = episodeNumber, + posterUrl = episodePosterUrl, + date = episodeDate + ) + } + } + +// Log.d("DEBUG getEpisodes", "Episodes: $episodes") + if (episodes.isEmpty()) {//fix series without episodes description + val seriesJsonDataModel = getSeriesJsonDataModel(url) + val collectionOrObjectNotNull = + seriesJsonDataModel.isNotEmpty() && seriesJsonDataModel.firstOrNull() != null + + val objectFieldsNotNull = if (collectionOrObjectNotNull) { + seriesJsonDataModel.first()?.seasons + } else { + null + } + + + if (collectionOrObjectNotNull && objectFieldsNotNull != null) { + return seriesJsonDataModelToEpisodes(seriesJsonDataModel, url) + } + } + + return episodes + } + + private fun seriesJsonDataModelToEpisodes( + seriesJsonDataModel: List, + url: String + ): List { + return seriesJsonDataModel.flatMap { model -> + model.seasons.flatMap { season -> + val seasonName = season.name + val episodeSeasonNumber = seasonName.filter { it.isDigit() }.toInt() + season.episodes.map { episode -> + val episodePosterUrl = episode.poster + val episodeName = episode.name + val episodeNumber = episode.name.filter { it.isDigit() }.toInt() + val episodeSeasonTag = "$episodeName;$seasonName;$url" + Episode( + data = episodeSeasonTag, + name = episodeName, + season = episodeSeasonNumber, + episode = episodeNumber, + posterUrl = episodePosterUrl, + ) + } + } + } + } + + private suspend fun getEpisodePosterUrl( + seriesUrl: String, + seasonName: String, + episodeName: String + ): String { +// Log.d("DEBUG getEpisodePosterUrl", "seriesUrl: $seriesUrl") + val seriesJsonDataModel = + getSeriesJsonDataModelByEpisodeName(episodeName, seasonName, seriesUrl) + if (seriesJsonDataModel.isEmpty()) { + return "" + } + return seriesJsonDataModel.first().seasons.first().episodes.first().poster + } + + private suspend fun getSeriesJsonDataModelByEpisodeName( + episodeName: String, + episodeSeasonName: String, + seriesUrl: String + ): List { +// Log.d( +// "DEBUG getSeriesJsonDataModelByEpisodeName", +// "EpisodeName: $episodeName, SeasonName: $episodeSeasonName, SeriesUrl: $seriesUrl" +// ) + val seriesJsonDataModel = getSeriesJsonDataModel(seriesUrl) +// Log.d( +// "DEBUG getSeriesJsonDataModelByEpisodeName", +// "SeriesJsonDataModel: $seriesJsonDataModel" +// ) + val seasonsList = seriesJsonDataModel.firstOrNull()?.seasons ?: return emptyList() + +// Log.d("DEBUG getSeriesJsonDataModelByEpisodeName", "SeasonsList: $seasonsList") +// + val season = seasonsList.firstOrNull { season -> + season.name.filter { seasonName -> seasonName.isDigit() } == episodeSeasonName + .filter { episodeSeasonName -> episodeSeasonName.isDigit() } + } +// Log.d("DEBUG getSeriesJsonDataModelByEpisodeName", "Season: $season") +// + val foundEpisode = + season?.episodes?.firstOrNull { episode -> + episode.name + .filter { c -> c.isDigit() } == episodeName.filter { c -> c.isDigit() } + } + + if (foundEpisode == null) { + return emptyList() + } + val seriesDubName = seriesJsonDataModel.first().seriesDubName + return listOf( + SeriesJsonDataModel( + seriesDubName, + listOf(Season("", listOf(foundEpisode))) + ) + ) + } + + private fun getEpisodeDate(episode: Element): Long { +// Log.d("DEBUG getEpisodeDate", "Episode: $episode") + val episodeDateText = episode.select("td.td-4").text() +// Log.d("DEBUG getEpisodeDate", "EpisodeDateText: $episodeDateText") + return if (episodeDateText.isNotEmpty()) { + simpleDateFormat.parse( + episodeDateText + )?.time ?: 0 + } else 0 + } + + private fun getDuration(document: Document): Int { + val firstOrNull = document.select(otherDataSelector).select("li").firstOrNull { + it.text().contains("Тривалість:") + } + val text = firstOrNull?.text() ?: return 0 + + val regex = Regex("""(\d+) год (\d+) хв""") + val match = regex.find(text) + if (match != null) { + val (hours, minutes) = match.destructured + return (hours.toInt() * 60 + minutes.toInt()) + } + return 0 + } + + private fun getRating(document: Document) = + document.select("div.pmovie__rating-content > a")[0].text().toRatingInt() + + private fun getDescription(document: Document) = + document.select("div.page__text").text() + + private fun getActors(document: Document): List { + return document.select("ul.pmovie__list").select("li") + .firstOrNull { it.text().contains("Актори:") }?.select("a") + ?.map { it.text() }?.toList() ?: emptyList() + } + + private fun getTags(document: Document): List { + return document.select(otherDataSelector).select("li") + .firstOrNull { element -> element.text().contains("Жанр:") }?.select("a") + ?.map { it.text().replaceFirstChar { firstChat -> firstChat.uppercase() } }?.toList() + ?: emptyList() + } + + private fun getYear(document: Document): Int { + val yearIndexTag = "Рік виходу:" + return document.select(otherDataSelector).select("li") + .firstOrNull { it.select("span").text() == yearIndexTag }?.text() + ?.replace(yearIndexTag, "")?.trim()?.toInt() ?: 0 + } + + private fun getPagePosterUrl(document: Document) = + fixUrl(document.select("div.bslide__poster > a > img").attr("src")) + + private fun getPageEngTitle(document: Document) = document.select("div.bslide__subtitle").text() + + private fun getPageTitle(document: Document) = document.select("h1.bslide__title").text() + + private fun getTvType(url: String): TvType { + return when { + url.contains("serie") -> TvType.TvSeries + url.contains("cartoon/series") -> TvType.TvSeries + url.contains("cartoon") -> TvType.Cartoon + url.contains("anime") -> TvType.Anime + else -> TvType.Movie + } + } + + private fun getTrailerUrL(document: Document): String { + val listOfPlayers = document.select("div.tabs-block__select span").map { it.text() } + val playersUrl = document.select("div.tabs-block__content.video-inside").map { + val ifrmame = it.select("iframe") + ifrmame.attr("src").ifEmpty { ifrmame.attr("data-src") } + } + + val trailerIndex = listOfPlayers.indexOf("Трейлер") + return if (trailerIndex != -1) { + val rawUrl = playersUrl[trailerIndex] + val delimiter = "https://www.youtube.com/" + val substringAfter = delimiter + rawUrl.substringAfter(delimiter) + if (substringAfter.contains("embed")) { + substringAfter.replace("embed/", "watch?v=") + } else { + substringAfter + } + } else { + "" + } + } +} \ No newline at end of file diff --git a/UATuTFunProvider/src/main/kotlin/com/lagradost/UATuTProviderPlugin.kt b/UATuTFunProvider/src/main/kotlin/com/lagradost/UATuTProviderPlugin.kt new file mode 100644 index 0000000..c174170 --- /dev/null +++ b/UATuTFunProvider/src/main/kotlin/com/lagradost/UATuTProviderPlugin.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 UATuTProviderPlugin : Plugin() { + override fun load(context: Context) { + // All providers should be added in this manner. Please don't edit the providers list directly. + registerMainAPI(UATuTFunProvider()) + } +} diff --git a/UATuTFunProvider/src/main/kotlin/com/lagradost/model/Episode.kt b/UATuTFunProvider/src/main/kotlin/com/lagradost/model/Episode.kt new file mode 100644 index 0000000..9d2687d --- /dev/null +++ b/UATuTFunProvider/src/main/kotlin/com/lagradost/model/Episode.kt @@ -0,0 +1,11 @@ +package com.lagradost.model + +import com.fasterxml.jackson.annotation.JsonProperty + +data class Episode( + @JsonProperty("file") val file: String, + @JsonProperty("title") var name: String, + @JsonProperty("id") val id: String, + @JsonProperty("poster") val poster: String, + @JsonProperty("subtitle") val subtitle: String +) \ No newline at end of file diff --git a/UATuTFunProvider/src/main/kotlin/com/lagradost/model/Season.kt b/UATuTFunProvider/src/main/kotlin/com/lagradost/model/Season.kt new file mode 100644 index 0000000..f0c684a --- /dev/null +++ b/UATuTFunProvider/src/main/kotlin/com/lagradost/model/Season.kt @@ -0,0 +1,8 @@ +package com.lagradost.model + +import com.fasterxml.jackson.annotation.JsonProperty + +data class Season( + @JsonProperty("title") val name: String, + @JsonProperty("folder") val episodes: List +) \ No newline at end of file diff --git a/UATuTFunProvider/src/main/kotlin/com/lagradost/model/SeriesJsonDataModel.kt b/UATuTFunProvider/src/main/kotlin/com/lagradost/model/SeriesJsonDataModel.kt new file mode 100644 index 0000000..1c15bfa --- /dev/null +++ b/UATuTFunProvider/src/main/kotlin/com/lagradost/model/SeriesJsonDataModel.kt @@ -0,0 +1,8 @@ +package com.lagradost.model + +import com.fasterxml.jackson.annotation.JsonProperty + +data class SeriesJsonDataModel( + @JsonProperty("title") var seriesDubName: String, + @JsonProperty("folder") var seasons: List +) \ No newline at end of file diff --git a/UATuTFunProvider/src/test/kotlin/com/lagradost/TestUATuTProvider.kt b/UATuTFunProvider/src/test/kotlin/com/lagradost/TestUATuTProvider.kt new file mode 100644 index 0000000..651a085 --- /dev/null +++ b/UATuTFunProvider/src/test/kotlin/com/lagradost/TestUATuTProvider.kt @@ -0,0 +1,14 @@ +package com.lagradost + +import com.lagradost.cloudstreamtest.ProviderTester +import kotlinx.coroutines.runBlocking +import org.junit.Test + + +class TestUATuTProvider { + @Test + fun testProvider() = runBlocking { + val providerTester = ProviderTester(UATuTFunProvider()) + providerTester.testAll() + } +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index dd7fa33..32cb9f4 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,4 +1,4 @@ -#Mon Jan 13 19:56:56 EET 2025 +#Thu Feb 13 10:39:35 EET 2025 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip diff --git a/repo.json b/repo.json index 2fe3eae..4c989b0 100644 --- a/repo.json +++ b/repo.json @@ -5,4 +5,4 @@ "pluginLists": [ "https://raw.githubusercontent.com/CakesTwix/cloudstream-extensions-uk/builds/plugins.json" ] -} +} \ No newline at end of file