diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Dailymotion.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Dailymotion.kt index 2b2a820fe94..88b407f3bc0 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Dailymotion.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Dailymotion.kt @@ -1,15 +1,18 @@ package com.lagradost.cloudstream3.extractors -import com.google.gson.Gson +import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.SubtitleFile import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.newSubtitleFile +import com.lagradost.cloudstream3.utils.AppUtils.parseJson import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.newExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLinkType import io.ktor.http.Url import io.ktor.http.decodeURLPart +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable class Geodailymotion : Dailymotion() { override val name = "GeoDailymotion" @@ -28,25 +31,25 @@ open class Dailymotion : ExtractorApi() { url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, - callback: (ExtractorLink) -> Unit + callback: (ExtractorLink) -> Unit, ) { val embedUrl = getEmbedUrl(url) ?: return val id = getVideoId(embedUrl) ?: return - val metaDataUrl = "$baseUrl/player/metadata/video/$id" - - val response = app.get(metaDataUrl, referer = embedUrl).text - val gson = Gson() - val meta = gson.fromJson(response, MetaData::class.java) + val metadataUrl = "$baseUrl/player/metadata/video/$id" + val response = app.get(metadataUrl, referer = embedUrl).text + val meta = parseJson(response) meta.qualities?.get("auto")?.forEach { quality -> val videoUrl = quality.url if (!videoUrl.isNullOrEmpty() && videoUrl.contains(".m3u8")) { - callback.invoke(newExtractorLink( - name, - name, - videoUrl, - ExtractorLinkType.M3U8 - )) + callback.invoke( + newExtractorLink( + name, + name, + videoUrl, + ExtractorLinkType.M3U8, + ) + ) } } @@ -55,7 +58,7 @@ open class Dailymotion : ExtractorApi() { subtitleCallback( newSubtitleFile( subData.label, - subUrl + subUrl, ) ) } @@ -68,6 +71,7 @@ open class Dailymotion : ExtractorApi() { val videoId = url.substringAfter("video=") return "$baseUrl/embed/video/$videoId" } + return null } @@ -77,23 +81,27 @@ open class Dailymotion : ExtractorApi() { return if (id.matches(videoIdRegex)) id else null } - data class MetaData( - val qualities: Map>?, - val subtitles: SubtitlesWrapper? + @Serializable + data class Metadata( + @JsonProperty("qualities") @SerialName("qualities") val qualities: Map>?, + @JsonProperty("subtitles") @SerialName("subtitles") val subtitles: SubtitlesWrapper?, ) + @Serializable data class Quality( - val type: String?, - val url: String? + @JsonProperty("type") @SerialName("type") val type: String?, + @JsonProperty("url") @SerialName("url") val url: String?, ) + @Serializable data class SubtitlesWrapper( - val enable: Boolean, - val data: Map? + @JsonProperty("enable") @SerialName("enable") val enable: Boolean, + @JsonProperty("data") @SerialName("data") val data: Map?, ) + @Serializable data class SubtitleData( - val label: String, - val urls: List + @JsonProperty("label") @SerialName("label") val label: String, + @JsonProperty("urls") @SerialName("urls") val urls: List, ) -} \ No newline at end of file +} diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/GDMirrorbot.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/GDMirrorbot.kt index ba297067e73..b6563fd6313 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/GDMirrorbot.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/GDMirrorbot.kt @@ -1,31 +1,34 @@ package com.lagradost.cloudstream3.extractors -import com.google.gson.JsonParser +import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.api.Log import com.lagradost.cloudstream3.SubtitleFile import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.base64Decode +import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.loadExtractor import io.ktor.http.Url +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable -class Techinmind: GDMirrorbot() { - override var name = "Techinmind Cloud AIO" - override var mainUrl = "https://stream.techinmind.space" - override var requiresReferer = true +class Techinmind : GDMirrorbot() { + override val name = "Techinmind Cloud AIO" + override val mainUrl = "https://stream.techinmind.space" + override val requiresReferer = true } open class GDMirrorbot : ExtractorApi() { - override var name = "GDMirrorbot" - override var mainUrl = "https://gdmirrorbot.nl" + override val name = "GDMirrorbot" + override val mainUrl = "https://gdmirrorbot.nl" override val requiresReferer = true override suspend fun getUrl( url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, - callback: (ExtractorLink) -> Unit + callback: (ExtractorLink) -> Unit, ) { val (sid, host) = if (!url.contains("key=")) { Pair(url.substringAfterLast("embed/"), getBaseUrl(app.get(url).url)) @@ -36,63 +39,58 @@ open class GDMirrorbot : ExtractorApi() { val idType = Regex("""idType\s*=\s*"([^"]+)"""").find(pageText)?.groupValues?.get(1) ?: "imdbid" val baseUrl = Regex("""let\s+baseUrl\s*=\s*"([^"]+)"""").find(pageText)?.groupValues?.get(1) val hostUrl = baseUrl?.let { getBaseUrl(it) } - if (finalId != null && myKey != null) { val apiUrl = if (url.contains("/tv/")) { val season = Regex("""/tv/\d+/(\d+)/""").find(url)?.groupValues?.get(1) ?: "1" val episode = Regex("""/tv/\d+/\d+/(\d+)""").find(url)?.groupValues?.get(1) ?: "1" "$mainUrl/myseriesapi?tmdbid=$finalId&season=$season&epname=$episode&key=$myKey" - } else { - "$mainUrl/mymovieapi?$idType=$finalId&key=$myKey" - } + } else "$mainUrl/mymovieapi?$idType=$finalId&key=$myKey" pageText = app.get(apiUrl).text } - val jsonElement = JsonParser.parseString(pageText) - if (!jsonElement.isJsonObject) return - val jsonObject = jsonElement.asJsonObject - + val embedData = tryParseJson(pageText) val embedId = url.substringAfterLast("/") - val sidValue = jsonObject["data"]?.asJsonArray - ?.takeIf { it.size() > 0 } - ?.get(0)?.asJsonObject - ?.get("fileslug")?.asString + val sidValue = embedData?.data?.firstOrNull()?.fileSlug ?.takeIf { it.isNotBlank() } ?: embedId - Pair(sidValue, hostUrl) } val postData = mapOf("sid" to sid) val responseText = app.post("$host/embedhelper.php", data = postData).text - val rootElement = JsonParser.parseString(responseText) - if (!rootElement.isJsonObject) return - val root = rootElement.asJsonObject - - val siteUrls = root["siteUrls"]?.asJsonObject ?: return - val siteFriendlyNames = root["siteFriendlyNames"]?.asJsonObject - - val decodedMresult = when { - root["mresult"]?.isJsonObject == true -> root["mresult"]!!.asJsonObject - root["mresult"]?.isJsonPrimitive == true -> try { - base64Decode(root["mresult"]!!.asString) - .let { JsonParser.parseString(it).asJsonObject } - } catch (e: Exception) { - Log.e("GDMirrorbot", "Failed to decode mresult: $e") - return + val root = tryParseJson(responseText) ?: return + val siteUrls = root.siteUrls ?: return + val siteFriendlyNames = root.siteFriendlyNames + + // mresult can arrive as a JSON object or a base64-encoded string + val mresult: Map? = run { + val raw = responseText + .substringAfter("\"mresult\":") + .trimStart() + when { + raw.startsWith("\"") -> { + // base64-encoded string + tryParseJson>( + try { base64Decode(raw.trim('"')) } catch (_: Exception) { return } + ) + } + raw.startsWith("{") -> tryParseJson>( + raw.substringBefore("\n}").substringBefore(",\n\"").let { "$it" } + .let { responseText.substringAfter("\"mresult\":").trimStart() } + ) + else -> null } - else -> return } - siteUrls.keySet().intersect(decodedMresult.keySet()).forEach { key -> - val base = siteUrls[key]?.asString?.trimEnd('/') ?: return@forEach - val path = decodedMresult[key]?.asString?.trimStart('/') ?: return@forEach + if (mresult == null) return + siteUrls.keys.intersect(mresult.keys).forEach { key -> + val base = siteUrls[key]?.trimEnd('/') ?: return@forEach + val path = mresult[key]?.trimStart('/') ?: return@forEach val fullUrl = "$base/$path" - val friendlyName = siteFriendlyNames?.get(key)?.asString ?: key - + val friendlyName = siteFriendlyNames?.get(key) ?: key try { when (friendlyName) { - "StreamHG","EarnVids" -> VidHidePro().getUrl(fullUrl, referer, subtitleCallback, callback) + "StreamHG", "EarnVids" -> VidHidePro().getUrl(fullUrl, referer, subtitleCallback, callback) "RpmShare", "UpnShare", "StreamP2p" -> VidStack().getUrl(fullUrl, referer, subtitleCallback, callback) else -> loadExtractor(fullUrl, referer ?: mainUrl, subtitleCallback, callback) } @@ -105,5 +103,20 @@ open class GDMirrorbot : ExtractorApi() { private fun getBaseUrl(url: String): String { return Url(url).let { "${it.protocol.name}://${it.host}" } } -} + @Serializable + private data class EmbedData( + @JsonProperty("data") @SerialName("data") val data: List? = null, + ) + + @Serializable + private data class FileSlug( + @JsonProperty("fileslug") @SerialName("fileslug") val fileSlug: String? = null, + ) + + @Serializable + private data class EmbedHelper( + @JsonProperty("siteUrls") @SerialName("siteUrls") val siteUrls: Map? = null, + @JsonProperty("siteFriendlyNames") @SerialName("siteFriendlyNames") val siteFriendlyNames: Map? = null, + ) +} diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Voe.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Voe.kt index 67eb49c9a55..b3ea4df1b5b 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Voe.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Voe.kt @@ -1,16 +1,18 @@ package com.lagradost.cloudstream3.extractors -import com.google.gson.JsonObject -import com.google.gson.JsonParser +import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.SubtitleFile import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.base64Decode +import com.lagradost.cloudstream3.utils.AppUtils.parseJson import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.INFER_TYPE import com.lagradost.cloudstream3.utils.M3u8Helper import com.lagradost.cloudstream3.utils.Qualities import com.lagradost.cloudstream3.utils.newExtractorLink +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable class Tubeless : Voe() { override val name = "Tubeless" @@ -19,12 +21,12 @@ class Tubeless : Voe() { class Simpulumlamerop : Voe() { override val name = "Simplum" - override var mainUrl = "https://simpulumlamerop.com" + override val mainUrl = "https://simpulumlamerop.com" } class Urochsunloath : Voe() { override val name = "Uroch" - override var mainUrl = "https://urochsunloath.com" + override val mainUrl = "https://urochsunloath.com" } class NathanFromSubject : Voe() { @@ -33,7 +35,7 @@ class NathanFromSubject : Voe() { class Yipsu : Voe() { override val name = "Yipsu" - override var mainUrl = "https://yip.su" + override val mainUrl = "https://yip.su" } class MetaGnathTuggers : Voe() { @@ -59,38 +61,43 @@ open class Voe : ExtractorApi() { url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, - callback: (ExtractorLink) -> Unit + callback: (ExtractorLink) -> Unit, ) { var res = app.get(url, referer = referer) val redirectUrl = redirectRegex.find(res.document.data())?.groupValues?.get(1) if (redirectUrl != null) { res = app.get(redirectUrl, referer = referer) } - val encodedString = res.document.selectFirst("script[type=application/json]")?.data()?.trim()?.substringAfter("[\"")?.substringBeforeLast("\"]") + + val encodedString = res.document.selectFirst("script[type=application/json]") + ?.data()?.trim() + ?.substringAfter("[\"") + ?.substringBeforeLast("\"]") + if (encodedString == null) { println("encoded string not found.") return } - val decryptedJson = decryptF7(encodedString) - val m3u8 = decryptedJson.get("source")?.asString - val mp4 = decryptedJson.get("direct_access_url")?.asString + val decryptedJson = decryptF7(encodedString) + val m3u8 = decryptedJson?.source + val mp4 = decryptedJson?.directAccessUrl if (m3u8 != null) { M3u8Helper.generateM3u8( name, m3u8, "$mainUrl/", - headers = mapOf("Origin" to "$mainUrl/") + headers = mapOf("Origin" to "$mainUrl/"), ).forEach(callback) } - if (mp4!=null) - { + + if (mp4 != null) { callback.invoke( newExtractorLink( source = "$name MP4", name = "$name MP4", url = mp4, - INFER_TYPE + INFER_TYPE, ) { this.referer = url this.quality = Qualities.Unknown.value @@ -99,20 +106,19 @@ open class Voe : ExtractorApi() { } } - private fun decryptF7(p8: String): JsonObject { + private fun decryptF7(p8: String): VoeDecrypted? { return try { val vF = rot13(p8) val vF2 = replacePatterns(vF) val vF3 = removeUnderscores(vF2) val vF4 = base64Decode(vF3) val vF5 = charShift(vF4, 3) - val vF6 = reverse(vF5) + val vF6 = vF5.reversed() val vAtob = base64Decode(vF6) - - JsonParser.parseString(vAtob).asJsonObject + parseJson(vAtob) } catch (e: Exception) { println("Decryption error: ${e.message}") - JsonObject() + null } } @@ -139,6 +145,9 @@ open class Voe : ExtractorApi() { return input.map { (it.code - shift).toChar() }.joinToString("") } - private fun reverse(input: String): String = input.reversed() - + @Serializable + private data class VoeDecrypted( + @JsonProperty("source") @SerialName("source") val source: String? = null, + @JsonProperty("direct_access_url") @SerialName("direct_access_url") val directAccessUrl: String? = null, + ) }