From 85f2cd7dd0cb334594ec2fc50bfd37f760504673 Mon Sep 17 00:00:00 2001 From: hexated Date: Wed, 21 Sep 2022 19:45:32 +0700 Subject: [PATCH] added 123tv --- OnetwothreeTv/build.gradle.kts | 25 +++ OnetwothreeTv/src/main/AndroidManifest.xml | 2 + .../main/kotlin/com/hexated/OnetwothreeTv.kt | 174 ++++++++++++++++++ .../kotlin/com/hexated/OnetwothreeTvPlugin.kt | 14 ++ 4 files changed, 215 insertions(+) create mode 100644 OnetwothreeTv/build.gradle.kts create mode 100644 OnetwothreeTv/src/main/AndroidManifest.xml create mode 100644 OnetwothreeTv/src/main/kotlin/com/hexated/OnetwothreeTv.kt create mode 100644 OnetwothreeTv/src/main/kotlin/com/hexated/OnetwothreeTvPlugin.kt diff --git a/OnetwothreeTv/build.gradle.kts b/OnetwothreeTv/build.gradle.kts new file mode 100644 index 00000000..e6e4a51c --- /dev/null +++ b/OnetwothreeTv/build.gradle.kts @@ -0,0 +1,25 @@ +// use an integer for version numbers +version = 1 + + +cloudstream { + language = "en" + // All of these properties are optional, you can safely remove them + + // description = "Lorem Ipsum" + authors = listOf("Hexated") + + /** + * Status int as the following: + * 0: Down + * 1: Ok + * 2: Slow + * 3: Beta only + * */ + status = 1 // will be 3 if unspecified + tvTypes = listOf( + "Live", + ) + + iconUrl = "https://www.google.com/s2/favicons?domain=123tv.live&sz=%size%" +} diff --git a/OnetwothreeTv/src/main/AndroidManifest.xml b/OnetwothreeTv/src/main/AndroidManifest.xml new file mode 100644 index 00000000..c98063f8 --- /dev/null +++ b/OnetwothreeTv/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/OnetwothreeTv/src/main/kotlin/com/hexated/OnetwothreeTv.kt b/OnetwothreeTv/src/main/kotlin/com/hexated/OnetwothreeTv.kt new file mode 100644 index 00000000..3139733a --- /dev/null +++ b/OnetwothreeTv/src/main/kotlin/com/hexated/OnetwothreeTv.kt @@ -0,0 +1,174 @@ +package com.hexated + +import com.fasterxml.jackson.annotation.JsonProperty +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.M3u8Helper +import com.lagradost.cloudstream3.utils.Qualities +import org.jsoup.nodes.Element +import javax.crypto.Cipher +import javax.crypto.SecretKeyFactory +import javax.crypto.spec.IvParameterSpec +import javax.crypto.spec.PBEKeySpec +import javax.crypto.spec.SecretKeySpec + +class OnetwothreeTv : MainAPI() { + override var mainUrl = "http://123tv.live" + override var name = "123tv" + override val hasDownloadSupport = false + override val hasMainPage = true + override val supportedTypes = setOf( + TvType.Live + ) + + override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse { + val homePageList = ArrayList() + listOf( + Pair("$mainUrl/category/united-states-usa/", "United States (USA)"), + Pair("$mainUrl/top-streams/", "Top Streams"), + Pair("$mainUrl/latest-streams/", "Latest Streams") + ).apmap { + val home = + app.get(it.first).document.select("div.videos-latest-list.row div.col-md-3.col-sm-6") + .mapNotNull { item -> + item.toSearchResult() + } + if (home.isNotEmpty()) homePageList.add(HomePageList(it.second, home, true)) + } + return HomePageResponse(homePageList) + } + + private fun Element.toSearchResult(): LiveSearchResponse? { + return LiveSearchResponse( + this.selectFirst("div.video-title h4")?.text() ?: return null, + fixUrl(this.selectFirst("a")!!.attr("href")), + this@OnetwothreeTv.name, + TvType.Live, + fixUrlNull(this.selectFirst("img")?.attr("src")), + ) + + } + + override suspend fun search(query: String): List { + return app.get( + "$mainUrl/?s=$query" + ).document.select("div.videos-latest-list.row div.col-md-3.col-sm-6").mapNotNull { + it.toSearchResult() + } + } + + override suspend fun load(url: String): LoadResponse? { + val document = app.get(url).document + return LiveStreamLoadResponse( + document.selectFirst("div.video-big-title h1")?.text() ?: return null, + url, + this.name, + document.selectFirst("div.embed-responsive iframe")?.attr("src") ?: url, + fixUrlNull(document.selectFirst("meta[name=\"twitter:image\"]")?.attr("content")), + plot = document.select("div.watch-video-description p").text() ?: return null + ) + } + + private fun String.decodeHex(): ByteArray { + check(length % 2 == 0) { "Must have an even length" } + return chunked(2) + .map { it.toInt(16).toByte() } + .toByteArray() + } + + private fun cryptojsAESHandler( + data: AesData, + pass: String, + encrypt: Boolean = true + ): String { + val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512") + val spec = PBEKeySpec(pass.toCharArray(), data.s.decodeHex(), 999, 256) + val key = factory.generateSecret(spec) + val cipher = Cipher.getInstance("AES/CBC/NoPadding") + return if (!encrypt) { + cipher.init( + Cipher.DECRYPT_MODE, + SecretKeySpec(key.encoded, "AES"), + IvParameterSpec(data.iv.decodeHex()) + ) + String(cipher.doFinal(base64DecodeArray(data.ct))) + } else { + cipher.init( + Cipher.ENCRYPT_MODE, + SecretKeySpec(key.encoded, "AES"), + IvParameterSpec(data.iv.decodeHex()) + ) + base64Encode(cipher.doFinal(data.ct.toByteArray())) + + } + } + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + if (data.contains(".m3u8")) { + app.get( + data, + referer = "$mainUrl/" + ).document.select("script").find { it.data().contains("var player=") }?.data() + ?.substringAfter("source:'")?.substringBefore("',")?.let { link -> + callback.invoke( + ExtractorLink( + source = name, + name = name, + url = link, + referer = "http://azureedge.xyz/", + quality = Qualities.Unknown.value, + isM3u8 = true, + headers = mapOf("Origin" to "http://azureedge.xyz") + ) + ) + } + } else { + val script = + app.get(data).document.select("script").find { it.data().contains("var post_id") } + ?.data() ?: throw ErrorLoadingException("No data found") + val encodeData = + Regex("\\w{6,10}=\\[(\\S+)];return").find(script)?.groupValues?.getOrNull(1) + ?.split(",")?.joinToString("") { it.replace("'", "") } + .let { base64Decode("$it") } + val aesData = tryParseJson(encodeData) + ?: throw ErrorLoadingException("Invalid json responses") + val pass = Regex("\\[((\\d{2,3},?\\s?){4})];").findAll(script).map { it.groupValues[1] } + .toList().flatMap { it.split(",") }.map { it.toInt().toChar() }.reversed() + .joinToString("") + val decryptData = cryptojsAESHandler(aesData, pass, false) + val jsonData = Regex("[\"|'](\\?1&json=\\S+)[\"|'];").find(script)?.groupValues?.get(1) + val m3uLink = "${decryptData.substringBefore(".m3u8")}.m3u8$jsonData" + app.get(m3uLink, referer = data).let { + tryParseJson>(it.text)?.map { res -> + M3u8Helper.generateM3u8( + this.name, + res.file, + "$mainUrl/", + headers = mapOf("Origin" to mainUrl) + ).forEach(callback) + } + } + + } + + return true + + } + + data class Source( + @JsonProperty("file") val file: String, + ) + + data class AesData( + @JsonProperty("ciphertext") val ct: String, + @JsonProperty("salt") val s: String, + @JsonProperty("iv") val iv: String, + @JsonProperty("iterations") val iterations: Int? = null, + ) +} \ No newline at end of file diff --git a/OnetwothreeTv/src/main/kotlin/com/hexated/OnetwothreeTvPlugin.kt b/OnetwothreeTv/src/main/kotlin/com/hexated/OnetwothreeTvPlugin.kt new file mode 100644 index 00000000..5b764581 --- /dev/null +++ b/OnetwothreeTv/src/main/kotlin/com/hexated/OnetwothreeTvPlugin.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 OnetwothreeTvPlugin: Plugin() { + override fun load(context: Context) { + // All providers should be added in this manner. Please don't edit the providers list directly. + registerMainAPI(OnetwothreeTv()) + } +} \ No newline at end of file