From 0561fcf85751efaeb32db4d51025ac09d68929b9 Mon Sep 17 00:00:00 2001 From: tuan041 <30403510+tuan041@users.noreply.github.com> Date: Thu, 20 Jul 2023 15:50:17 +0700 Subject: [PATCH] Add Phim1080 Provider (#197) * bump * fix * new * new * new * new * change version --- Phim1080/build.gradle.kts | 28 ++ Phim1080/src/main/AndroidManifest.xml | 2 + .../src/main/com/hexated/Phim1080Provider.kt | 247 ++++++++++++++++++ .../com/hexated/Phim1080ProviderPlugin.kt | 14 + 4 files changed, 291 insertions(+) create mode 100644 Phim1080/build.gradle.kts create mode 100644 Phim1080/src/main/AndroidManifest.xml create mode 100644 Phim1080/src/main/com/hexated/Phim1080Provider.kt create mode 100644 Phim1080/src/main/com/hexated/Phim1080ProviderPlugin.kt diff --git a/Phim1080/build.gradle.kts b/Phim1080/build.gradle.kts new file mode 100644 index 00000000..bd0faf7c --- /dev/null +++ b/Phim1080/build.gradle.kts @@ -0,0 +1,28 @@ +// use an integer for version numbers +version = 1 + + +cloudstream { + language = "vi" + // All of these properties are optional, you can safely remove them + + description = "Xem Phim Online Chất Lượng Cao" + authors = listOf("TuaSan") + + /** + * Status int as the following: + * 0: Down + * 1: Ok + * 2: Slow + * 3: Beta only + * */ + status = 1 // will be 3 if unspecified + tvTypes = listOf( + "AsianDrama", + "Anime", + "TvSeries", + "Movie", + ) + + iconUrl = "https://www.google.com/s2/favicons?domain=xem1080.com" +} diff --git a/Phim1080/src/main/AndroidManifest.xml b/Phim1080/src/main/AndroidManifest.xml new file mode 100644 index 00000000..a3f95b4f --- /dev/null +++ b/Phim1080/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + diff --git a/Phim1080/src/main/com/hexated/Phim1080Provider.kt b/Phim1080/src/main/com/hexated/Phim1080Provider.kt new file mode 100644 index 00000000..d4d35368 --- /dev/null +++ b/Phim1080/src/main/com/hexated/Phim1080Provider.kt @@ -0,0 +1,247 @@ +package com.hexated + +import com.fasterxml.jackson.annotation.JsonProperty +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer +import com.lagradost.cloudstream3.utils.* +import com.lagradost.cloudstream3.utils.ExtractorLink +import org.jsoup.nodes.Element + +class Phim1080Provider : MainAPI() { + override var mainUrl = "https://xem1080.com" + override var name = "Phim1080" + override val hasMainPage = true + override var lang = "vi" + override val hasDownloadSupport = true + override val supportedTypes = setOf( + TvType.Movie, + TvType.TvSeries, + TvType.Anime, + TvType.AsianDrama + ) + + private fun encodeString(e: String, t: Int): String { + var a = "" + for (i in 0 until e.length) { + val r = e[i].code + val o = r xor t + a += o.toChar() + } + return a + } + + override val mainPage = mainPageOf( + "$mainUrl/phim-de-cu?page=" to "Phim Đề Cử", + "$mainUrl/the-loai/hoat-hinh?page=" to "Phim Hoạt Hình", + "$mainUrl/phim-chieu-rap?page=" to "Phim Chiếu Rạp", + "$mainUrl/phim-bo?page=" to "Phim Bộ", + "$mainUrl/phim-le?page=" to "Phim Lẻ", + "$mainUrl/bang-xep-hang?page=" to "Bảng Xếp Hạng", + "$mainUrl/bo-suu-tap/disney-plus?page=" to "Disney+", + "$mainUrl/bo-suu-tap/netflix-original?page=" to "Netflix", + "$mainUrl/hom-nay-xem-gi?page=" to "Hôm Nay Xem Gì", + "$mainUrl/phim-sap-chieu?page=" to "Phim Sắp Chiếu", + ) + + override suspend fun getMainPage( + page: Int, + request: MainPageRequest + ): HomePageResponse { + val document = app.get(request.data + page).document + val home = document.select("div.tray-item").mapNotNull { + it.toSearchResult() + } + return newHomePageResponse( + list = HomePageList( + name = request.name, + list = home, + ), + hasNext = true + ) + } + + private fun Element.toSearchResult(): SearchResponse { + val title = this.selectFirst("div.tray-item-title")?.text()?.trim().toString() + val href = fixUrl(this.selectFirst("a")!!.attr("href")) + val posterUrl = this.selectFirst("img")!!.attr("data-src") + val temp = this.select("div.tray-film-likes").text() + return if (temp.contains("/")) { + val episode = Regex("((\\d+)\\s)").find(temp)?.groupValues?.map { num -> + num.replace(Regex("\\s"), "") + }?.distinct()?.firstOrNull()?.toIntOrNull() + newAnimeSearchResponse(title, href, TvType.TvSeries) { + this.posterUrl = posterUrl + addSub(episode) + } + } else { + val quality = this.select("span.tray-item-quality").text().replace("FHD", "HD").trim() + newMovieSearchResponse(title, href, TvType.Movie) { + this.posterUrl = posterUrl + addQuality(quality) + } + } + } + + override suspend fun search(query: String): List { + val link = "$mainUrl/tim-kiem/$query" + val document = app.get(link).document + + return document.select("div.tray-item").map { + it.toSearchResult() + } + } + + override suspend fun load( url: String ): LoadResponse { + val document = app.get( + url = url, + referer = "$mainUrl/", + headers = mapOf( + "Sec-Ch-Ua-Mobile" to "?1", + "Sec-Ch-Ua-Platform" to "\"Android\"", + "User-Agent" to "Mozilla/5.0 (Linux; Android 10; SM-G981B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.162 Mobile Safari/537.36 Edg/114.0.0.0", + ) + ).document + val fId = document.select("div.container").attr("data-id") + val filmInfo = app.get( + "$mainUrl/api/v2/films/$fId", + referer = url, + headers = mapOf( + "Content-Type" to "application/json", + "X-Requested-With" to "XMLHttpRequest" + ) + ).parsedSafe() + val title = filmInfo?.name?.trim().toString() + val poster = filmInfo?.thumbnail + val background = filmInfo?.poster + val slug = filmInfo?.slug + val link = "$mainUrl/$slug" + val tags = document.select("div.film-content div.film-info-genre:nth-child(7) a").map { it.text() } + val year = filmInfo?.year + val tvType = if (document.select("div.episode-group-tab").isNotEmpty()) TvType.TvSeries else TvType.Movie + val description = document.select("div.film-info-description").text().trim() + val comingSoon = document.select("button.direction-trailer").isNotEmpty() + val trailerCode = filmInfo?.trailer?.original?.id + val trailer = "https://www.youtube.com/embed/$trailerCode" + val recommendations = document.select("section.tray.index.related div.tray-content.carousel div.tray-item").map { + it.toSearchResult() + } + + return if (tvType == TvType.TvSeries) { + val epsInfo = app.get( + "$mainUrl/api/v2/films/$fId/episodes?sort=name", + referer = link, + headers = mapOf( + "Content-Type" to "application/json", + "X-Requested-With" to "XMLHttpRequest", + ) + ).parsedSafe()?.eps?.map { ep -> + Episode( + data = fixUrl(ep.link.toString()), + name = ep.detailname, + episode = ep.episodeNumber, + ) + } ?: listOf() + newTvSeriesLoadResponse(title, url, TvType.TvSeries, epsInfo) { + this.posterUrl = poster + this.backgroundPosterUrl = background + this.year = year + this.plot = description + this.tags = tags + this.comingSoon = comingSoon + addTrailer(trailer) + this.recommendations = recommendations + } + } else { + newMovieLoadResponse(title, url, TvType.Movie, link) { + this.posterUrl = poster + this.backgroundPosterUrl = background + this.year = year + this.plot = description + this.tags = tags + this.comingSoon = comingSoon + addTrailer(trailer) + this.recommendations = recommendations + } + } + } + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + + val document = app.get(data).document + val fId = document.select("div.container").attr("data-id") + val epId = document.select("div.container").attr("data-episode-id") + val doc = app.get( + "$mainUrl/api/v2/films/$fId/episodes/$epId", + referer = data, + headers = mapOf( + "Content-Type" to "application/json", + "cookie" to "xem1080=%3D", + "X-Requested-With" to "XMLHttpRequest" + ) + ) + val source = doc.text.substringAfter(":{\"hls\":\"").substringBefore("\"},") + val link = encodeString(source, 69) + callback.invoke( + ExtractorLink( + "HS", + "HS", + link, + referer = data, + quality = Qualities.Unknown.value, + isM3u8 = true, + ) + ) + val subId = doc.parsedSafe()?.subtitle?.vi + val isSubIdEmpty = subId.isNullOrBlank() + if (!isSubIdEmpty) { + subtitleCallback.invoke( + SubtitleFile( + "Vietnamese", + "$mainUrl/subtitle/$subId.vtt" + ) + ) + } + return true + } + + data class filmInfo( + @JsonProperty("name") val name: String? = null, + @JsonProperty("poster") val poster: String? = null, + @JsonProperty("thumbnail") val thumbnail: String? = null, + @JsonProperty("slug") val slug: String? = null, + @JsonProperty("year") val year: Int? = null, + @JsonProperty("trailer") val trailer: TrailerInfo? = null, + ) + + data class TrailerInfo( + @JsonProperty("original") val original: TrailerKey? = null, + ) + + data class TrailerKey( + @JsonProperty("id") val id: String? = null, + ) + + data class MediaDetailEpisodes( + @JsonProperty("data") val eps: ArrayList? = arrayListOf(), + ) + + data class Episodes( + @JsonProperty("link") val link: String? = null, + @JsonProperty("detail_name") val detailname: String? = null, + @JsonProperty("name") val episodeNumber: Int? = null, + ) + + data class Media( + @JsonProperty("subtitle") val subtitle: SubInfo? = null, + ) + + data class SubInfo( + @JsonProperty("vi") val vi: String? = null, + ) + +} diff --git a/Phim1080/src/main/com/hexated/Phim1080ProviderPlugin.kt b/Phim1080/src/main/com/hexated/Phim1080ProviderPlugin.kt new file mode 100644 index 00000000..a521b6ac --- /dev/null +++ b/Phim1080/src/main/com/hexated/Phim1080ProviderPlugin.kt @@ -0,0 +1,14 @@ + +package com.hexated + +import com.lagradost.cloudstream3.plugins.CloudstreamPlugin +import com.lagradost.cloudstream3.plugins.Plugin +import android.content.Context + +@CloudstreamPlugin +class Phim1080ProviderPlugin: Plugin() { + override fun load(context: Context) { + // All providers should be added in this manner. Please don't edit the providers list directly. + registerMainAPI(Phim1080Provider()) + } +}