Skip to content
2 changes: 2 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,8 @@ dependencies {
implementation(libs.jackson.module.kotlin) // JSON Parser
implementation(libs.zipline)

// Temp/deprecated; will be removed once extensions have time to migrate from using it
implementation("com.google.code.gson:gson:2.14.0")
// Deprecated; will be removed once extensions have time to migrate from using it
implementation("me.xdrop:fuzzywuzzy:1.4.0")

Expand Down
2 changes: 0 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ qrcodeKotlin = "4.5.0"
rhino = { strictly = "1.8.1" } # Requires minSdk 26 or later beginning at version 1.9.0
safefile = "0.0.8"
shimmer = "0.5.0"
tmdbJava = "2.13.0"
torrentserver = "7861970"
tvprovider = "1.1.0"
video = "1.0.0"
Expand Down Expand Up @@ -122,7 +121,6 @@ qrcode-kotlin = { module = "io.github.g0dkar:qrcode-kotlin", version.ref = "qrco
rhino = { module = "org.mozilla:rhino", version.ref = "rhino" }
safefile = { module = "com.github.LagradOst:SafeFile", version.ref = "safefile" }
shimmer = { module = "com.facebook.shimmer:shimmer", version.ref = "shimmer" }
tmdb-java = { module = "com.uwetrottmann.tmdb2:tmdb-java", version.ref = "tmdbJava" }
torrentserver = { module = "com.github.recloudstream:torrentserver", version.ref = "torrentserver" }
tvprovider = { module = "androidx.tvprovider:tvprovider", version.ref = "tvprovider" }
video = { module = "com.google.android.mediahome:video", version.ref = "video" }
Expand Down
1 change: 0 additions & 1 deletion library/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ kotlin {
implementation(libs.kotlinx.serialization.json) // JSON Parser
implementation(libs.jsoup) // HTML Parser
implementation(libs.rhino) // Run JavaScript
implementation(libs.tmdb.java) // TMDB API v3 Wrapper Made with RetroFit

// Deprecated; will be removed once extensions have time to migrate from using it
implementation("me.xdrop:fuzzywuzzy:1.4.0")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
package com.lagradost.cloudstream3.extractors

import com.google.gson.Gson
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.M3u8Helper.Companion.generateM3u8
import java.net.URI



class Geodailymotion : Dailymotion() {
override val name = "GeoDailymotion"
override val mainUrl = "https://geo.dailymotion.com"
Expand All @@ -35,8 +33,7 @@ open class Dailymotion : ExtractorApi() {
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 meta = parseJson<MetaData>(response)

meta.qualities?.get("auto")?.forEach { quality ->
val videoUrl = quality.url
Expand All @@ -57,7 +54,6 @@ open class Dailymotion : ExtractorApi() {
}
}


private fun getEmbedUrl(url: String): String? {
if (url.contains("/embed/") || url.contains("/video/")) return url
if (url.contains("geo.dailymotion.com")) {
Expand All @@ -67,7 +63,6 @@ open class Dailymotion : ExtractorApi() {
return null
}


private fun getVideoId(url: String): String? {
val path = URI(url).path
val id = path.substringAfter("/video/")
Expand All @@ -82,7 +77,6 @@ open class Dailymotion : ExtractorApi() {
return generateM3u8(name, streamLink, "").forEach(callback)
}


data class MetaData(
val qualities: Map<String, List<Quality>>?,
val subtitles: SubtitlesWrapper?
Expand All @@ -102,5 +96,4 @@ open class Dailymotion : ExtractorApi() {
val label: String,
val urls: List<String>
)

}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
package com.lagradost.cloudstream3.extractors

import com.google.gson.JsonParser
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 kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import java.net.URI

class Techinmind: GDMirrorbot() {
class Techinmind : GDMirrorbot() {
override var name = "Techinmind Cloud AIO"
override var mainUrl = "https://stream.techinmind.space"
override var requiresReferer = true
Expand All @@ -21,6 +23,22 @@ open class GDMirrorbot : ExtractorApi() {
override var mainUrl = "https://gdmirrorbot.nl"
override val requiresReferer = true

@Serializable
private data class EmbedData(
@SerialName("data") val data: List<FileSlug>? = null,
)

@Serializable
private data class FileSlug(
@SerialName("fileslug") val fileslug: String? = null,
)

@Serializable
private data class EmbedHelper(
@SerialName("siteUrls") val siteUrls: Map<String, String>? = null,
@SerialName("siteFriendlyNames") val siteFriendlyNames: Map<String, String>? = null,
)

override suspend fun getUrl(
url: String,
referer: String?,
Expand Down Expand Up @@ -48,15 +66,9 @@ open class GDMirrorbot : ExtractorApi() {
pageText = app.get(apiUrl).text
}

val jsonElement = JsonParser.parseString(pageText)
if (!jsonElement.isJsonObject) return
val jsonObject = jsonElement.asJsonObject

val embedData = tryParseJson<EmbedData>(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)
Expand All @@ -65,34 +77,40 @@ open class GDMirrorbot : ExtractorApi() {
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<EmbedHelper>(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<String, String>? = run {
val raw = responseText
.substringAfter("\"mresult\":")
.trimStart()
when {
raw.startsWith("\"") -> {
// base64-encoded string
tryParseJson<Map<String, String>>(
try { base64Decode(raw.trim('"')) } catch (_: Exception) { return }
)
}
raw.startsWith("{") -> tryParseJson<Map<String, String>>(
raw.substringBefore("\n}").substringBefore(",\n\"").let { "{$it}" }
.let { responseText.substringAfter("\"mresult\":").trimStart() }
)
else -> null
}
else -> return
}
if (mresult == null) return

siteUrls.keySet().intersect(decodedMresult.keySet()).forEach { key ->
val base = siteUrls[key]?.asString?.trimEnd('/') ?: return@forEach
val path = decodedMresult[key]?.asString?.trimStart('/') ?: return@forEach
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)
}
Expand All @@ -106,4 +124,3 @@ open class GDMirrorbot : ExtractorApi() {
return URI(url).let { "${it.scheme}://${it.host}" }
}
}

Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
package com.lagradost.cloudstream3.extractors

import com.google.gson.JsonObject
import com.google.gson.JsonParser
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.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"
Expand Down Expand Up @@ -66,14 +67,17 @@ open class Voe : ExtractorApi() {
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 m3u8 = decryptedJson?.source
val mp4 = decryptedJson?.directAccessUrl

if (m3u8 != null) {
M3u8Helper.generateM3u8(
Expand All @@ -83,8 +87,7 @@ open class Voe : ExtractorApi() {
headers = mapOf("Origin" to "$mainUrl/")
).forEach(callback)
}
if (mp4!=null)
{
if (mp4 != null) {
callback.invoke(
newExtractorLink(
source = "$name MP4",
Expand All @@ -99,7 +102,13 @@ open class Voe : ExtractorApi() {
}
}

private fun decryptF7(p8: String): JsonObject {
@Serializable
private data class VoeDecrypted(
@SerialName("source") val source: String? = null,
@SerialName("directAccessUrl") val directAccessUrl: String? = null,
)

private fun decryptF7(p8: String): VoeDecrypted? {
return try {
val vF = rot13(p8)
val vF2 = replacePatterns(vF)
Expand All @@ -108,11 +117,10 @@ open class Voe : ExtractorApi() {
val vF5 = charShift(vF4, 3)
val vF6 = reverse(vF5)
val vAtob = base64Decode(vF6)

JsonParser.parseString(vAtob).asJsonObject
tryParseJson<VoeDecrypted>(vAtob)
} catch (e: Exception) {
println("Decryption error: ${e.message}")
JsonObject()
null
}
}

Expand Down Expand Up @@ -140,5 +148,4 @@ open class Voe : ExtractorApi() {
}

private fun reverse(input: String): String = input.reversed()

}
Loading