2022-12-02 13:00:44 +00:00
|
|
|
|
package com.hexated
|
|
|
|
|
|
2023-02-04 10:58:06 +00:00
|
|
|
|
import android.util.Base64
|
2023-03-05 15:58:53 +00:00
|
|
|
|
import com.fasterxml.jackson.annotation.JsonProperty
|
2023-05-02 19:45:23 +00:00
|
|
|
|
import com.hexated.SoraStream.Companion.allanimeAPI
|
2023-02-24 21:32:34 +00:00
|
|
|
|
import com.hexated.SoraStream.Companion.base64DecodeAPI
|
2023-02-10 08:36:06 +00:00
|
|
|
|
import com.hexated.SoraStream.Companion.baymoviesAPI
|
2023-01-19 10:23:58 +00:00
|
|
|
|
import com.hexated.SoraStream.Companion.consumetCrunchyrollAPI
|
2022-12-07 19:17:24 +00:00
|
|
|
|
import com.hexated.SoraStream.Companion.filmxyAPI
|
2022-12-02 16:50:56 +00:00
|
|
|
|
import com.hexated.SoraStream.Companion.gdbot
|
2023-04-11 17:00:54 +00:00
|
|
|
|
import com.hexated.SoraStream.Companion.putlockerAPI
|
2023-02-18 14:56:08 +00:00
|
|
|
|
import com.hexated.SoraStream.Companion.smashyStreamAPI
|
2022-12-15 17:30:38 +00:00
|
|
|
|
import com.hexated.SoraStream.Companion.tvMoviesAPI
|
2023-03-05 15:58:53 +00:00
|
|
|
|
import com.hexated.SoraStream.Companion.twoEmbedAPI
|
2023-03-14 17:33:43 +00:00
|
|
|
|
import com.hexated.SoraStream.Companion.watchOnlineAPI
|
2023-01-29 03:29:15 +00:00
|
|
|
|
import com.lagradost.cloudstream3.*
|
2023-01-11 12:42:59 +00:00
|
|
|
|
import com.lagradost.cloudstream3.APIHolder.getCaptchaToken
|
2023-03-05 15:58:53 +00:00
|
|
|
|
import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall
|
2023-01-21 06:25:04 +00:00
|
|
|
|
import com.lagradost.cloudstream3.utils.*
|
2022-12-07 19:17:24 +00:00
|
|
|
|
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
|
2023-03-05 15:58:53 +00:00
|
|
|
|
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
2023-01-11 01:28:46 +00:00
|
|
|
|
import com.lagradost.nicehttp.NiceResponse
|
2022-12-09 08:44:01 +00:00
|
|
|
|
import com.lagradost.nicehttp.RequestBodyTypes
|
2022-12-15 17:30:38 +00:00
|
|
|
|
import kotlinx.coroutines.delay
|
2022-12-07 15:06:48 +00:00
|
|
|
|
import okhttp3.FormBody
|
2023-02-14 18:01:07 +00:00
|
|
|
|
import okhttp3.Headers
|
2022-12-02 13:00:44 +00:00
|
|
|
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
2022-12-09 08:44:01 +00:00
|
|
|
|
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
|
|
|
|
import okhttp3.RequestBody.Companion.toRequestBody
|
2023-05-05 09:31:57 +00:00
|
|
|
|
import okio.ByteString.Companion.encode
|
2022-12-10 12:25:28 +00:00
|
|
|
|
import org.jsoup.nodes.Document
|
2022-12-02 13:00:44 +00:00
|
|
|
|
import java.net.URI
|
2023-01-30 03:42:55 +00:00
|
|
|
|
import java.net.URL
|
2023-04-28 17:25:17 +00:00
|
|
|
|
import java.net.URLDecoder
|
2023-02-19 19:57:29 +00:00
|
|
|
|
import java.net.URLEncoder
|
2023-03-05 15:58:53 +00:00
|
|
|
|
import java.nio.charset.StandardCharsets
|
2023-02-04 10:58:06 +00:00
|
|
|
|
import java.security.MessageDigest
|
|
|
|
|
import java.security.SecureRandom
|
|
|
|
|
import java.util.*
|
|
|
|
|
import javax.crypto.Cipher
|
|
|
|
|
import javax.crypto.spec.IvParameterSpec
|
|
|
|
|
import javax.crypto.spec.SecretKeySpec
|
|
|
|
|
import kotlin.collections.ArrayList
|
|
|
|
|
import kotlin.math.min
|
2022-12-02 13:00:44 +00:00
|
|
|
|
|
2023-04-28 17:25:17 +00:00
|
|
|
|
val soraAPI = base64DecodeAPI("cA==YXA=cy8=Y20=di8=LnQ=b2s=a2w=bG8=aS4=YXA=ZS0=aWw=b2I=LW0=Z2E=Ly8=czo=dHA=aHQ=")
|
|
|
|
|
val bflixChipperKey = base64DecodeAPI("Yjc=ejM=TzA=YTk=WHE=WnU=bXU=RFo=")
|
|
|
|
|
val bflixKey = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
2023-05-09 06:54:20 +00:00
|
|
|
|
val kaguyaBaseUrl = "https://kaguya.app/"
|
2023-02-24 21:32:34 +00:00
|
|
|
|
val soraHeaders = mapOf(
|
|
|
|
|
"lang" to "en",
|
2023-03-01 14:29:23 +00:00
|
|
|
|
"versioncode" to "33",
|
2023-04-14 19:22:10 +00:00
|
|
|
|
"clienttype" to "android_Official",
|
2023-02-24 21:32:34 +00:00
|
|
|
|
"deviceid" to getDeviceId(),
|
|
|
|
|
)
|
2023-05-02 19:45:23 +00:00
|
|
|
|
val allanimeSearchQuery = """
|
|
|
|
|
query(
|
|
|
|
|
${'$'}search: SearchInput
|
|
|
|
|
${'$'}limit: Int
|
|
|
|
|
${'$'}page: Int
|
|
|
|
|
${'$'}translationType: VaildTranslationTypeEnumType
|
|
|
|
|
${'$'}countryOrigin: VaildCountryOriginEnumType
|
|
|
|
|
) {
|
|
|
|
|
shows(
|
|
|
|
|
search: ${'$'}search
|
|
|
|
|
limit: ${'$'}limit
|
|
|
|
|
page: ${'$'}page
|
|
|
|
|
translationType: ${'$'}translationType
|
|
|
|
|
countryOrigin: ${'$'}countryOrigin
|
|
|
|
|
) {
|
|
|
|
|
pageInfo {
|
|
|
|
|
total
|
|
|
|
|
}
|
|
|
|
|
edges {
|
|
|
|
|
_id
|
|
|
|
|
name
|
|
|
|
|
thumbnail
|
|
|
|
|
englishName
|
|
|
|
|
nativeName
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
""".trimIndent().trim()
|
|
|
|
|
val allanimeServerQuery = """
|
|
|
|
|
query(
|
|
|
|
|
${'$'}showId: String!,
|
|
|
|
|
${'$'}translationType: VaildTranslationTypeEnumType!,
|
|
|
|
|
${'$'}episodeString: String!
|
|
|
|
|
) {
|
|
|
|
|
episode(
|
|
|
|
|
showId: ${'$'}showId
|
|
|
|
|
translationType: ${'$'}translationType
|
|
|
|
|
episodeString: ${'$'}episodeString
|
|
|
|
|
) {
|
|
|
|
|
sourceUrls
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
""".trimIndent().trim()
|
2023-02-10 08:36:06 +00:00
|
|
|
|
val encodedIndex = arrayOf(
|
|
|
|
|
"GamMovies",
|
|
|
|
|
"JSMovies",
|
|
|
|
|
"BlackMovies",
|
|
|
|
|
"CodexMovies",
|
|
|
|
|
"RinzryMovies",
|
|
|
|
|
"EdithxMovies",
|
|
|
|
|
"XtremeMovies",
|
|
|
|
|
"PapaonMovies[1]",
|
|
|
|
|
"PapaonMovies[2]",
|
2023-02-19 19:57:29 +00:00
|
|
|
|
"JmdkhMovies",
|
2023-02-21 09:02:45 +00:00
|
|
|
|
"RubyMovies",
|
|
|
|
|
"ShinobiMovies",
|
|
|
|
|
"VitoenMovies",
|
2023-02-10 08:36:06 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
val lockedIndex = arrayOf(
|
|
|
|
|
"CodexMovies",
|
|
|
|
|
"EdithxMovies",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
val mkvIndex = arrayOf(
|
2023-02-19 19:57:29 +00:00
|
|
|
|
"EdithxMovies",
|
|
|
|
|
"JmdkhMovies",
|
2023-02-10 08:36:06 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
val untrimmedIndex = arrayOf(
|
|
|
|
|
"PapaonMovies[1]",
|
|
|
|
|
"PapaonMovies[2]",
|
|
|
|
|
"EdithxMovies",
|
|
|
|
|
)
|
|
|
|
|
|
2023-02-24 21:32:34 +00:00
|
|
|
|
val needRefererIndex = arrayOf(
|
|
|
|
|
"ShinobiMovies",
|
|
|
|
|
)
|
|
|
|
|
|
2023-03-31 05:48:57 +00:00
|
|
|
|
val ddomainIndex = arrayOf(
|
|
|
|
|
"RinzryMovies",
|
|
|
|
|
"ShinobiMovies"
|
|
|
|
|
)
|
|
|
|
|
|
2023-02-19 14:49:44 +00:00
|
|
|
|
val mimeType = arrayOf(
|
|
|
|
|
"video/x-matroska",
|
|
|
|
|
"video/mp4",
|
|
|
|
|
"video/x-msvideo"
|
|
|
|
|
)
|
|
|
|
|
|
2022-12-02 13:00:44 +00:00
|
|
|
|
data class FilmxyCookies(
|
|
|
|
|
val phpsessid: String? = null,
|
|
|
|
|
val wLog: String? = null,
|
|
|
|
|
val wSec: String? = null,
|
|
|
|
|
)
|
|
|
|
|
|
2023-02-08 04:50:33 +00:00
|
|
|
|
fun String.filterIframe(seasonNum: Int?, lastSeason: Int?, year: Int?, title: String?): Boolean {
|
|
|
|
|
val slug = title.createSlug()
|
|
|
|
|
val dotSlug = slug?.replace("-", ".")
|
|
|
|
|
val spaceSlug = slug?.replace("-", " ")
|
2022-12-02 13:00:44 +00:00
|
|
|
|
return if (seasonNum != null) {
|
2022-12-05 21:03:26 +00:00
|
|
|
|
if (lastSeason == 1) {
|
2023-02-07 14:44:14 +00:00
|
|
|
|
this.contains(Regex("(?i)(S0?$seasonNum)|(Season\\s0?$seasonNum)|(\\d{3,4}p)")) && !this.contains(
|
2022-12-02 13:00:44 +00:00
|
|
|
|
"Download",
|
|
|
|
|
true
|
|
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
this.contains(Regex("(?i)(S0?$seasonNum)|(Season\\s0?$seasonNum)")) && !this.contains(
|
|
|
|
|
"Download",
|
|
|
|
|
true
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2023-02-11 10:50:38 +00:00
|
|
|
|
this.contains(Regex("(?i)($year)|($dotSlug)|($spaceSlug)")) && !this.contains(
|
|
|
|
|
"Download",
|
|
|
|
|
true
|
|
|
|
|
)
|
2022-12-02 13:00:44 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun String.filterMedia(title: String?, yearNum: Int?, seasonNum: Int?): Boolean {
|
2023-02-08 04:50:33 +00:00
|
|
|
|
val fixTitle = title.createSlug()?.replace("-", " ")
|
2022-12-02 13:00:44 +00:00
|
|
|
|
return if (seasonNum != null) {
|
|
|
|
|
when {
|
|
|
|
|
seasonNum > 1 -> this.contains(Regex("(?i)(Season\\s0?1-0?$seasonNum)|(S0?1-S?0?$seasonNum)")) && this.contains(
|
2023-02-08 04:50:33 +00:00
|
|
|
|
Regex("(?i)($fixTitle)|($title)")
|
2022-12-17 12:53:12 +00:00
|
|
|
|
)
|
2022-12-02 13:00:44 +00:00
|
|
|
|
else -> this.contains(Regex("(?i)(Season\\s0?1)|(S0?1)")) && this.contains(
|
2023-02-08 04:50:33 +00:00
|
|
|
|
Regex("(?i)($fixTitle)|($title)")
|
2022-12-02 13:00:44 +00:00
|
|
|
|
) && this.contains("$yearNum")
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2023-02-08 04:50:33 +00:00
|
|
|
|
this.contains(Regex("(?i)($fixTitle)|($title)")) && this.contains("$yearNum")
|
2022-12-02 13:00:44 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-14 09:40:35 +00:00
|
|
|
|
fun Document.getMirrorLink(): String? {
|
|
|
|
|
return this.select("div.mb-4 a").randomOrNull()
|
|
|
|
|
?.attr("href")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun Document.getMirrorServer(server: Int): String {
|
|
|
|
|
return this.select("div.text-center a:contains(Server $server)").attr("href")
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-07 19:17:24 +00:00
|
|
|
|
suspend fun extractMirrorUHD(url: String, ref: String): String? {
|
2023-01-14 09:40:35 +00:00
|
|
|
|
var baseDoc = app.get(fixUrl(url, ref)).document
|
|
|
|
|
var downLink = baseDoc.getMirrorLink()
|
|
|
|
|
run lit@{
|
|
|
|
|
(1..2).forEach {
|
2023-02-11 10:50:38 +00:00
|
|
|
|
if (downLink != null) return@lit
|
2023-01-14 09:40:35 +00:00
|
|
|
|
val server = baseDoc.getMirrorServer(it.plus(1))
|
|
|
|
|
baseDoc = app.get(fixUrl(server, ref)).document
|
|
|
|
|
downLink = baseDoc.getMirrorLink()
|
|
|
|
|
}
|
2022-12-07 19:17:24 +00:00
|
|
|
|
}
|
2023-03-07 14:05:52 +00:00
|
|
|
|
return if (downLink?.contains("workers.dev") == true) downLink else base64Decode(
|
|
|
|
|
downLink?.substringAfter(
|
|
|
|
|
"download?url="
|
|
|
|
|
) ?: return null
|
|
|
|
|
)
|
2022-12-07 19:17:24 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
suspend fun extractBackupUHD(url: String): String? {
|
|
|
|
|
val resumeDoc = app.get(url)
|
|
|
|
|
|
|
|
|
|
val script = resumeDoc.document.selectFirst("script:containsData(FormData.)")?.data()
|
|
|
|
|
|
|
|
|
|
val ssid = resumeDoc.cookies["PHPSESSID"]
|
|
|
|
|
val baseIframe = getBaseUrl(url)
|
2023-01-14 09:40:35 +00:00
|
|
|
|
val fetchLink =
|
|
|
|
|
script?.substringAfter("fetch('")?.substringBefore("',")?.let { fixUrl(it, baseIframe) }
|
2022-12-07 19:17:24 +00:00
|
|
|
|
val token = script?.substringAfter("'token', '")?.substringBefore("');")
|
|
|
|
|
|
|
|
|
|
val body = FormBody.Builder()
|
|
|
|
|
.addEncoded("token", "$token")
|
|
|
|
|
.build()
|
|
|
|
|
val cookies = mapOf("PHPSESSID" to "$ssid")
|
|
|
|
|
|
|
|
|
|
val result = app.post(
|
|
|
|
|
fetchLink ?: return null,
|
|
|
|
|
requestBody = body,
|
|
|
|
|
headers = mapOf(
|
|
|
|
|
"Accept" to "*/*",
|
|
|
|
|
"Origin" to baseIframe,
|
|
|
|
|
"Sec-Fetch-Site" to "same-origin"
|
|
|
|
|
),
|
|
|
|
|
cookies = cookies,
|
|
|
|
|
referer = url
|
|
|
|
|
).text
|
|
|
|
|
return tryParseJson<UHDBackupUrl>(result)?.url
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-02 16:50:56 +00:00
|
|
|
|
suspend fun extractGdbot(url: String): String? {
|
|
|
|
|
val headers = mapOf(
|
|
|
|
|
"Accept" to "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
|
|
|
|
|
)
|
|
|
|
|
val res = app.get(
|
|
|
|
|
"$gdbot/", headers = headers
|
|
|
|
|
)
|
|
|
|
|
val token = res.document.selectFirst("input[name=_token]")?.attr("value")
|
|
|
|
|
val cookiesSet = res.headers.filter { it.first == "set-cookie" }
|
2022-12-07 15:06:48 +00:00
|
|
|
|
val xsrf =
|
|
|
|
|
cookiesSet.find { it.second.contains("XSRF-TOKEN") }?.second?.substringAfter("XSRF-TOKEN=")
|
|
|
|
|
?.substringBefore(";")
|
|
|
|
|
val session =
|
|
|
|
|
cookiesSet.find { it.second.contains("gdtot_proxy_session") }?.second?.substringAfter("gdtot_proxy_session=")
|
|
|
|
|
?.substringBefore(";")
|
2022-12-02 16:50:56 +00:00
|
|
|
|
|
|
|
|
|
val cookies = mapOf(
|
|
|
|
|
"gdtot_proxy_session" to "$session",
|
|
|
|
|
"XSRF-TOKEN" to "$xsrf"
|
|
|
|
|
)
|
|
|
|
|
val requestFile = app.post(
|
|
|
|
|
"$gdbot/file", data = mapOf(
|
|
|
|
|
"link" to url,
|
|
|
|
|
"_token" to "$token"
|
|
|
|
|
), headers = headers, referer = "$gdbot/", cookies = cookies
|
|
|
|
|
).document
|
|
|
|
|
|
|
|
|
|
return requestFile.selectFirst("div.mt-8 a.float-right")?.attr("href")
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-09 08:44:01 +00:00
|
|
|
|
suspend fun extractDirectDl(url: String): String? {
|
2023-01-14 09:40:35 +00:00
|
|
|
|
val iframe = app.get(url).document.selectFirst("li.flex.flex-col.py-6 a:contains(Direct DL)")
|
|
|
|
|
?.attr("href")
|
2022-12-09 08:44:01 +00:00
|
|
|
|
val request = app.get(iframe ?: return null)
|
|
|
|
|
val driveDoc = request.document
|
|
|
|
|
val token = driveDoc.select("section#generate_url").attr("data-token")
|
|
|
|
|
val uid = driveDoc.select("section#generate_url").attr("data-uid")
|
|
|
|
|
|
|
|
|
|
val ssid = request.cookies["PHPSESSID"]
|
2023-01-14 09:40:35 +00:00
|
|
|
|
val body =
|
|
|
|
|
"""{"type":"DOWNLOAD_GENERATE","payload":{"uid":"$uid","access_token":"$token"}}""".toRequestBody(
|
|
|
|
|
RequestBodyTypes.JSON.toMediaTypeOrNull()
|
|
|
|
|
)
|
2022-12-09 08:44:01 +00:00
|
|
|
|
|
|
|
|
|
val json = app.post(
|
|
|
|
|
"https://rajbetmovies.com/action", requestBody = body, headers = mapOf(
|
|
|
|
|
"Accept" to "application/json, text/plain, */*",
|
|
|
|
|
"Cookie" to "PHPSESSID=$ssid",
|
|
|
|
|
"X-Requested-With" to "xmlhttprequest"
|
|
|
|
|
), referer = request.url
|
|
|
|
|
).text
|
|
|
|
|
return tryParseJson<DirectDl>(json)?.download_url
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-07 15:06:48 +00:00
|
|
|
|
suspend fun extractDrivebot(url: String): String? {
|
2023-01-14 09:40:35 +00:00
|
|
|
|
val iframeDrivebot =
|
|
|
|
|
app.get(url).document.selectFirst("li.flex.flex-col.py-6 a:contains(Drivebot)")
|
2022-12-20 15:24:47 +00:00
|
|
|
|
?.attr("href") ?: return null
|
|
|
|
|
return getDrivebotLink(iframeDrivebot)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
suspend fun extractGdflix(url: String): String? {
|
2023-01-14 09:40:35 +00:00
|
|
|
|
val iframeGdflix =
|
|
|
|
|
app.get(url).document.selectFirst("li.flex.flex-col.py-6 a:contains(GDFlix Direct)")
|
|
|
|
|
?.attr("href") ?: return null
|
2022-12-20 15:24:47 +00:00
|
|
|
|
val base = getBaseUrl(iframeGdflix)
|
|
|
|
|
|
2023-02-22 14:20:48 +00:00
|
|
|
|
val req = app.get(iframeGdflix).document.selectFirst("script:containsData(replace)")?.data()
|
2023-01-14 09:40:35 +00:00
|
|
|
|
?.substringAfter("replace(\"")
|
2022-12-20 15:24:47 +00:00
|
|
|
|
?.substringBefore("\")")?.let {
|
2023-02-22 14:20:48 +00:00
|
|
|
|
app.get(fixUrl(it, base))
|
|
|
|
|
} ?: return null
|
|
|
|
|
|
2023-03-01 20:18:32 +00:00
|
|
|
|
val iframeDrivebot2 = req.document.selectFirst("a.btn.btn-outline-warning")?.attr("href")
|
|
|
|
|
return getDrivebotLink(iframeDrivebot2)
|
|
|
|
|
|
|
|
|
|
// val reqUrl = req.url
|
|
|
|
|
// val ssid = req.cookies["PHPSESSID"]
|
|
|
|
|
// val script = req.document.selectFirst("script:containsData(formData =)")?.data()
|
|
|
|
|
// val key = Regex("append\\(\"key\", \"(\\S+?)\"\\);").find(script ?: return null)?.groupValues?.get(1)
|
|
|
|
|
//
|
|
|
|
|
// val body = FormBody.Builder()
|
|
|
|
|
// .addEncoded("action", "direct")
|
|
|
|
|
// .addEncoded("key", "$key")
|
|
|
|
|
// .addEncoded("action_token", "cf_token")
|
|
|
|
|
// .build()
|
|
|
|
|
//
|
|
|
|
|
// val gdriveUrl = app.post(
|
|
|
|
|
// reqUrl, requestBody = body,
|
|
|
|
|
// cookies = mapOf("PHPSESSID" to "$ssid"),
|
|
|
|
|
// headers = mapOf(
|
|
|
|
|
// "x-token" to URI(reqUrl).host
|
|
|
|
|
// )
|
|
|
|
|
// ).parsedSafe<Gdflix>()?.url
|
|
|
|
|
//
|
|
|
|
|
// return getDirectGdrive(gdriveUrl ?: return null)
|
2022-12-20 15:24:47 +00:00
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
suspend fun getDrivebotLink(url: String?): String? {
|
|
|
|
|
val driveDoc = app.get(url ?: return null)
|
2022-12-07 15:06:48 +00:00
|
|
|
|
|
|
|
|
|
val ssid = driveDoc.cookies["PHPSESSID"]
|
|
|
|
|
val script = driveDoc.document.selectFirst("script:containsData(var formData)")?.data()
|
|
|
|
|
|
2022-12-20 15:24:47 +00:00
|
|
|
|
val baseUrl = getBaseUrl(url)
|
2022-12-07 15:06:48 +00:00
|
|
|
|
val token = script?.substringAfter("'token', '")?.substringBefore("');")
|
|
|
|
|
val link =
|
|
|
|
|
script?.substringAfter("fetch('")?.substringBefore("',").let { "$baseUrl$it" }
|
|
|
|
|
|
|
|
|
|
val body = FormBody.Builder()
|
|
|
|
|
.addEncoded("token", "$token")
|
|
|
|
|
.build()
|
|
|
|
|
val cookies = mapOf("PHPSESSID" to "$ssid")
|
|
|
|
|
|
2023-02-11 07:14:38 +00:00
|
|
|
|
val file = app.post(
|
2022-12-07 15:06:48 +00:00
|
|
|
|
link,
|
|
|
|
|
requestBody = body,
|
|
|
|
|
headers = mapOf(
|
|
|
|
|
"Accept" to "*/*",
|
|
|
|
|
"Origin" to baseUrl,
|
|
|
|
|
"Sec-Fetch-Site" to "same-origin"
|
|
|
|
|
),
|
|
|
|
|
cookies = cookies,
|
2022-12-20 15:24:47 +00:00
|
|
|
|
referer = url
|
2023-02-11 07:14:38 +00:00
|
|
|
|
).parsedSafe<DriveBotLink>()?.url ?: return null
|
|
|
|
|
|
2023-02-11 10:50:38 +00:00
|
|
|
|
return if (file.startsWith("http")) file else app.get(
|
|
|
|
|
fixUrl(
|
|
|
|
|
file,
|
|
|
|
|
baseUrl
|
|
|
|
|
)
|
|
|
|
|
).document.selectFirst("script:containsData(window.open)")
|
2023-02-11 07:14:38 +00:00
|
|
|
|
?.data()?.substringAfter("window.open('")?.substringBefore("')")
|
2022-12-07 15:06:48 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-12-12 13:24:46 +00:00
|
|
|
|
suspend fun extractOiya(url: String, quality: String): String? {
|
|
|
|
|
val doc = app.get(url).document
|
|
|
|
|
return doc.selectFirst("div.wp-block-button a:matches((?i)$quality)")?.attr("href")
|
|
|
|
|
?: doc.selectFirst("div.wp-block-button a")?.attr("href")
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-15 17:30:38 +00:00
|
|
|
|
suspend fun extractCovyn(url: String?): Pair<String?, String?>? {
|
|
|
|
|
val request = session.get(url ?: return null, referer = "${tvMoviesAPI}/")
|
|
|
|
|
val filehosting = session.baseClient.cookieJar.loadForRequest(url.toHttpUrl())
|
|
|
|
|
.find { it.name == "filehosting" }?.value
|
|
|
|
|
val headers = mapOf(
|
|
|
|
|
"Accept" to "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
|
|
|
|
|
"Connection" to "keep-alive",
|
|
|
|
|
"Cookie" to "filehosting=$filehosting",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
val iframe = request.document.findTvMoviesIframe()
|
|
|
|
|
delay(10500)
|
|
|
|
|
val request2 = session.get(
|
|
|
|
|
iframe ?: return null, referer = url, headers = headers
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
val iframe2 = request2.document.findTvMoviesIframe()
|
|
|
|
|
delay(10500)
|
|
|
|
|
val request3 = session.get(
|
|
|
|
|
iframe2 ?: return null, referer = iframe, headers = headers
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
val response = request3.document
|
|
|
|
|
val videoLink = response.selectFirst("button.btn.btn--primary")?.attr("onclick")
|
|
|
|
|
?.substringAfter("location = '")?.substringBefore("';")?.let {
|
|
|
|
|
app.get(
|
|
|
|
|
it, referer = iframe2, headers = headers
|
|
|
|
|
).url
|
|
|
|
|
}
|
|
|
|
|
val size = response.selectFirst("ul.row--list li:contains(Filesize) span:last-child")
|
|
|
|
|
?.text()
|
|
|
|
|
|
|
|
|
|
return Pair(videoLink, size)
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-18 14:56:08 +00:00
|
|
|
|
suspend fun getDirectGdrive(url: String): String {
|
|
|
|
|
val fixUrl = if (url.contains("&export=download")) {
|
2022-12-29 13:19:16 +00:00
|
|
|
|
url
|
2023-01-11 01:28:46 +00:00
|
|
|
|
} else {
|
|
|
|
|
"https://drive.google.com/uc?id=${
|
2023-02-22 14:20:48 +00:00
|
|
|
|
Regex("(?:\\?id=|/d/)(\\S+)/").find("$url/")?.groupValues?.get(1)
|
2023-01-11 01:28:46 +00:00
|
|
|
|
}&export=download"
|
2022-12-29 13:19:16 +00:00
|
|
|
|
}
|
2023-02-18 14:56:08 +00:00
|
|
|
|
|
|
|
|
|
val doc = app.get(fixUrl).document
|
|
|
|
|
val form = doc.select("form#download-form").attr("action")
|
|
|
|
|
val uc = doc.select("input#uc-download-link").attr("value")
|
|
|
|
|
return app.post(
|
|
|
|
|
form, data = mapOf(
|
|
|
|
|
"uc-download-link" to uc
|
|
|
|
|
)
|
|
|
|
|
).url
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-28 17:25:17 +00:00
|
|
|
|
suspend fun invokeVizcloud(
|
|
|
|
|
url: String,
|
|
|
|
|
callback: (ExtractorLink) -> Unit,
|
|
|
|
|
) {
|
|
|
|
|
val id = Regex("(?:embed-|/e/)([^?]*)").find(url)?.groupValues?.getOrNull(1)
|
|
|
|
|
app.get("https://api.consumet.org/anime/9anime/helper?query=${id ?: return}&action=vizcloud")
|
|
|
|
|
.parsedSafe<VizcloudResponses>()?.data?.media?.sources?.map {
|
|
|
|
|
M3u8Helper.generateM3u8(
|
|
|
|
|
"Vizcloud",
|
|
|
|
|
it.file ?: return@map,
|
|
|
|
|
"${getBaseUrl(url)}/"
|
|
|
|
|
).forEach(callback)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-18 14:56:08 +00:00
|
|
|
|
suspend fun invokeSmashyOne(
|
|
|
|
|
name: String,
|
|
|
|
|
url: String,
|
|
|
|
|
callback: (ExtractorLink) -> Unit,
|
|
|
|
|
) {
|
2023-04-19 09:52:31 +00:00
|
|
|
|
val script =
|
|
|
|
|
app.get(url).document.selectFirst("script:containsData(player =)")?.data() ?: return
|
2023-02-18 14:56:08 +00:00
|
|
|
|
|
|
|
|
|
val source =
|
|
|
|
|
Regex("file:\\s['\"](\\S+?)['|\"]").find(script)?.groupValues?.get(
|
|
|
|
|
1
|
|
|
|
|
) ?: return
|
|
|
|
|
|
|
|
|
|
source.split(",").map { links ->
|
|
|
|
|
val quality = Regex("\\[(\\d+)]").find(links)?.groupValues?.getOrNull(1)?.trim()
|
|
|
|
|
val link = links.removePrefix("[$quality]").trim()
|
|
|
|
|
callback.invoke(
|
|
|
|
|
ExtractorLink(
|
|
|
|
|
"Smashy [$name]",
|
|
|
|
|
"Smashy [$name]",
|
|
|
|
|
link,
|
|
|
|
|
smashyStreamAPI,
|
|
|
|
|
quality?.toIntOrNull() ?: return@map,
|
|
|
|
|
isM3u8 = link.contains(".m3u8"),
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
suspend fun invokeSmashyTwo(
|
|
|
|
|
name: String,
|
|
|
|
|
url: String,
|
|
|
|
|
callback: (ExtractorLink) -> Unit
|
|
|
|
|
) {
|
|
|
|
|
val doc = app.get(url).document
|
|
|
|
|
val script = doc.selectFirst("script:containsData(var secret)")?.data() ?: return
|
|
|
|
|
val secret =
|
|
|
|
|
script.substringAfter("secret = \"").substringBefore("\";").let { base64Decode(it) }
|
|
|
|
|
val key = script.substringAfter("token = \"").substringBefore("\";")
|
|
|
|
|
val source = app.get(
|
|
|
|
|
"$secret$key",
|
|
|
|
|
headers = mapOf(
|
|
|
|
|
"X-Requested-With" to "XMLHttpRequest"
|
|
|
|
|
)
|
|
|
|
|
).parsedSafe<Smashy1Source>() ?: return
|
|
|
|
|
|
|
|
|
|
val videoUrl = base64Decode(source.file ?: return)
|
2023-04-19 09:52:31 +00:00
|
|
|
|
if (videoUrl.contains("/bug")) return
|
2023-02-18 14:56:08 +00:00
|
|
|
|
val quality =
|
|
|
|
|
Regex("(\\d{3,4})[Pp]").find(videoUrl)?.groupValues?.getOrNull(1)?.toIntOrNull()
|
|
|
|
|
?: Qualities.P720.value
|
|
|
|
|
callback.invoke(
|
|
|
|
|
ExtractorLink(
|
|
|
|
|
"Smashy [$name]",
|
|
|
|
|
"Smashy [$name]",
|
|
|
|
|
videoUrl,
|
|
|
|
|
"",
|
|
|
|
|
quality,
|
|
|
|
|
videoUrl.contains(".m3u8")
|
|
|
|
|
)
|
|
|
|
|
)
|
2022-12-29 13:19:16 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-04-17 10:01:08 +00:00
|
|
|
|
suspend fun invokeSmashyThree(
|
|
|
|
|
name: String,
|
|
|
|
|
url: String,
|
|
|
|
|
callback: (ExtractorLink) -> Unit
|
|
|
|
|
) {
|
|
|
|
|
val script =
|
|
|
|
|
app.get(url).document.selectFirst("script:containsData(player =)")?.data() ?: return
|
|
|
|
|
|
|
|
|
|
val source = Regex("file:\\s*(\\[.*]),").find(script)?.groupValues?.get(1) ?: return
|
|
|
|
|
|
|
|
|
|
tryParseJson<ArrayList<DudetvSources>>(source)?.filter { it.title == "English" }?.map {
|
|
|
|
|
M3u8Helper.generateM3u8(
|
|
|
|
|
"Smashy [Player 2]",
|
2023-04-19 09:52:31 +00:00
|
|
|
|
it.file ?: return@map,
|
2023-04-17 10:01:08 +00:00
|
|
|
|
""
|
|
|
|
|
).forEach(callback)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-19 09:52:31 +00:00
|
|
|
|
suspend fun getSoraIdAndType(title: String?, year: Int?, season: Int?): Pair<String, String>? {
|
|
|
|
|
val doc =
|
|
|
|
|
app.get("${base64DecodeAPI("b20=LmM=b2s=a2w=bG8=Ly8=czo=dHA=aHQ=")}/search?keyword=$title").document
|
2023-02-24 21:32:34 +00:00
|
|
|
|
val scriptData = doc.select("div.search-list div.search-video-card").map {
|
|
|
|
|
Triple(
|
|
|
|
|
it.selectFirst("h2.title")?.text().toString(),
|
|
|
|
|
it.selectFirst("div.desc")?.text()
|
|
|
|
|
?.substringBefore(".")?.toIntOrNull(),
|
|
|
|
|
it.selectFirst("a")?.attr("href")?.split("/")
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val script = if (scriptData.size == 1) {
|
|
|
|
|
scriptData.firstOrNull()
|
|
|
|
|
} else {
|
|
|
|
|
scriptData.find {
|
|
|
|
|
when (season) {
|
|
|
|
|
null -> {
|
|
|
|
|
it.first.equals(
|
|
|
|
|
title,
|
|
|
|
|
true
|
|
|
|
|
) && it.second == year
|
|
|
|
|
}
|
|
|
|
|
1 -> {
|
|
|
|
|
it.first.contains(
|
|
|
|
|
"$title",
|
|
|
|
|
true
|
|
|
|
|
) && (it.second == year || it.first.contains("Season $season", true))
|
|
|
|
|
}
|
|
|
|
|
else -> {
|
2023-02-26 12:40:53 +00:00
|
|
|
|
it.first.contains(Regex("(?i)$title\\s?($season|${season.toRomanNumeral()}|Season\\s$season)")) && it.second == year
|
2023-02-24 21:32:34 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-17 18:18:50 +00:00
|
|
|
|
val id = script?.third?.last()?.substringBefore("-") ?: return null
|
|
|
|
|
val type = script.third?.get(2)?.let {
|
|
|
|
|
if (it == "drama") "1" else "0"
|
|
|
|
|
} ?: return null
|
2023-02-24 21:32:34 +00:00
|
|
|
|
|
|
|
|
|
return id to type
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-19 09:52:31 +00:00
|
|
|
|
suspend fun fetchSoraEpisodes(id: String, type: String, episode: Int?): EpisodeVo? {
|
2023-02-24 21:32:34 +00:00
|
|
|
|
return app.get(
|
|
|
|
|
"$soraAPI/movieDrama/get?id=${id}&category=${type}",
|
|
|
|
|
headers = soraHeaders
|
|
|
|
|
).parsedSafe<Load>()?.data?.episodeVo?.find {
|
|
|
|
|
it.seriesNo == (episode ?: 0)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-05 09:31:57 +00:00
|
|
|
|
fun upgradeSoraUrl(url: String) : String {
|
2023-05-08 12:44:16 +00:00
|
|
|
|
val expiry = System.currentTimeMillis() + (60 * 60 * 12 * 7)
|
2023-05-05 09:31:57 +00:00
|
|
|
|
val mac = "fuckfuck".encode().hmacSha256("$expiry".encode()).hex()
|
|
|
|
|
return "${url.replace(BuildConfig.SORAXA, BuildConfig.SORATED).substringBefore(".m3u8")}.m3u8?hdntl=exp=$expiry-acl=%2f*-data=hdntl-hmac=$mac"
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-14 09:40:35 +00:00
|
|
|
|
suspend fun bypassOuo(url: String?): String? {
|
2023-01-11 01:28:46 +00:00
|
|
|
|
var res = session.get(url ?: return null)
|
2023-01-11 12:42:59 +00:00
|
|
|
|
run lit@{
|
|
|
|
|
(1..2).forEach { _ ->
|
|
|
|
|
if (res.headers["location"] != null) return@lit
|
|
|
|
|
val document = res.document
|
|
|
|
|
val nextUrl = document.select("form").attr("action")
|
|
|
|
|
val data = document.select("form input").mapNotNull {
|
|
|
|
|
it.attr("name") to it.attr("value")
|
|
|
|
|
}.toMap().toMutableMap()
|
|
|
|
|
val captchaKey =
|
|
|
|
|
document.select("script[src*=https://www.google.com/recaptcha/api.js?render=]")
|
|
|
|
|
.attr("src").substringAfter("render=")
|
|
|
|
|
val token = getCaptchaToken(url, captchaKey)
|
|
|
|
|
data["x-token"] = token ?: ""
|
|
|
|
|
res = session.post(
|
|
|
|
|
nextUrl,
|
|
|
|
|
data = data,
|
|
|
|
|
headers = mapOf("content-type" to "application/x-www-form-urlencoded"),
|
|
|
|
|
allowRedirects = false
|
|
|
|
|
)
|
|
|
|
|
}
|
2023-01-11 01:28:46 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return res.headers["location"]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
suspend fun fetchingKaizoku(
|
|
|
|
|
domain: String,
|
|
|
|
|
postId: String,
|
|
|
|
|
data: List<String>,
|
|
|
|
|
ref: String
|
|
|
|
|
): NiceResponse {
|
|
|
|
|
return app.post(
|
|
|
|
|
"$domain/wp-admin/admin-ajax.php",
|
|
|
|
|
data = mapOf(
|
|
|
|
|
"action" to "DDL",
|
|
|
|
|
"post_id" to postId,
|
|
|
|
|
"div_id" to data.first(),
|
|
|
|
|
"tab_id" to data[1],
|
|
|
|
|
"num" to data[2],
|
|
|
|
|
"folder" to data.last()
|
|
|
|
|
),
|
|
|
|
|
headers = mapOf("X-Requested-With" to "XMLHttpRequest"),
|
|
|
|
|
referer = ref
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun String.splitData(): List<String> {
|
|
|
|
|
return this.substringAfterLast("DDL(").substringBefore(")").split(",")
|
|
|
|
|
.map { it.replace("'", "").trim() }
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-02 17:15:40 +00:00
|
|
|
|
suspend fun bypassFdAds(url: String?): String? {
|
2023-01-14 09:40:35 +00:00
|
|
|
|
val directUrl =
|
|
|
|
|
app.get(url ?: return null, verify = false).document.select("a#link").attr("href")
|
|
|
|
|
.substringAfter("/go/")
|
|
|
|
|
.let { base64Decode(it) }
|
2023-01-03 16:42:50 +00:00
|
|
|
|
val doc = app.get(directUrl, verify = false).document
|
|
|
|
|
val lastDoc = app.post(
|
|
|
|
|
doc.select("form#landing").attr("action"),
|
|
|
|
|
data = mapOf("go" to doc.select("form#landing input").attr("value")),
|
2022-12-07 15:06:48 +00:00
|
|
|
|
verify = false
|
|
|
|
|
).document
|
2023-01-14 09:40:35 +00:00
|
|
|
|
val json = lastDoc.select("form#landing input[name=newwpsafelink]").attr("value")
|
|
|
|
|
.let { base64Decode(it) }
|
|
|
|
|
val finalJson =
|
|
|
|
|
tryParseJson<FDAds>(json)?.linkr?.substringAfter("redirect=")?.let { base64Decode(it) }
|
2023-01-03 23:01:24 +00:00
|
|
|
|
return tryParseJson<Safelink>(finalJson)?.safelink
|
2022-12-07 15:06:48 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-12-15 12:30:01 +00:00
|
|
|
|
suspend fun bypassHrefli(url: String): String? {
|
2023-03-07 14:05:52 +00:00
|
|
|
|
val postUrl = url.substringBefore("?id=").substringAfter("/?")
|
|
|
|
|
val res = app.post(
|
|
|
|
|
postUrl, data = mapOf(
|
|
|
|
|
"_wp_http" to url.substringAfter("?id=")
|
2022-12-15 12:30:01 +00:00
|
|
|
|
)
|
2023-03-07 14:05:52 +00:00
|
|
|
|
).document
|
2022-12-15 12:30:01 +00:00
|
|
|
|
|
2023-03-07 14:05:52 +00:00
|
|
|
|
val link = res.select("form#landing").attr("action")
|
|
|
|
|
val wpHttp = res.select("input[name=_wp_http2]").attr("value")
|
|
|
|
|
val token = res.select("input[name=token]").attr("value")
|
|
|
|
|
|
|
|
|
|
val blogRes = app.post(
|
|
|
|
|
link, data = mapOf(
|
|
|
|
|
"_wp_http2" to wpHttp,
|
|
|
|
|
"token" to token
|
|
|
|
|
)
|
|
|
|
|
).text
|
|
|
|
|
|
|
|
|
|
val skToken = blogRes.substringAfter("?go=").substringBefore("\"")
|
|
|
|
|
val driveUrl = app.get(
|
|
|
|
|
"$postUrl?go=$skToken", cookies = mapOf(
|
|
|
|
|
skToken to wpHttp
|
|
|
|
|
)
|
2022-12-15 12:30:01 +00:00
|
|
|
|
).document.selectFirst("meta[http-equiv=refresh]")?.attr("content")?.substringAfter("url=")
|
2023-03-07 14:05:52 +00:00
|
|
|
|
val path = app.get(driveUrl ?: return null).text.substringAfter("replace(\"")
|
|
|
|
|
.substringBefore("\")")
|
|
|
|
|
if (path == "/404") return null
|
|
|
|
|
return fixUrl(path, getBaseUrl(driveUrl))
|
2022-12-15 12:30:01 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-04-19 09:52:31 +00:00
|
|
|
|
suspend fun bypassTechmny(url: String): String? {
|
2023-03-30 05:21:25 +00:00
|
|
|
|
val postUrl = url.substringBefore("?id=").substringAfter("/?")
|
|
|
|
|
var res = app.post(
|
|
|
|
|
postUrl, data = mapOf(
|
|
|
|
|
"_wp_http_c" to url.substringAfter("?id=")
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
val (longC, catC, _) = getTechmnyCookies(res.text)
|
|
|
|
|
var headers = mapOf("Cookie" to "$longC; $catC")
|
|
|
|
|
var formLink = res.document.selectFirst("center a")?.attr("href")
|
|
|
|
|
|
|
|
|
|
res = app.get(formLink ?: return null, headers = headers)
|
|
|
|
|
val (longC2, _, postC) = getTechmnyCookies(res.text)
|
|
|
|
|
headers = mapOf("Cookie" to "$catC; $longC2; $postC")
|
|
|
|
|
formLink = res.document.selectFirst("center a")?.attr("href")
|
|
|
|
|
|
|
|
|
|
res = app.get(formLink ?: return null, headers = headers)
|
|
|
|
|
val goToken = res.text.substringAfter("?go=").substringBefore("\"")
|
|
|
|
|
val tokenUrl = "$postUrl?go=$goToken"
|
|
|
|
|
val newLongC = "$goToken=" + longC2.substringAfter("=")
|
|
|
|
|
headers = mapOf("Cookie" to "$catC; rdst_post=; $newLongC")
|
|
|
|
|
|
2023-04-19 09:52:31 +00:00
|
|
|
|
val driveUrl =
|
|
|
|
|
app.get(tokenUrl, headers = headers).document.selectFirst("meta[http-equiv=refresh]")
|
|
|
|
|
?.attr("content")?.substringAfter("url=")
|
2023-03-30 05:21:25 +00:00
|
|
|
|
val path = app.get(driveUrl ?: return null).text.substringAfter("replace(\"")
|
|
|
|
|
.substringBefore("\")")
|
|
|
|
|
if (path == "/404") return null
|
|
|
|
|
return fixUrl(path, getBaseUrl(driveUrl))
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-19 09:52:31 +00:00
|
|
|
|
suspend fun bypassDriveleech(url: String): String? {
|
2023-03-30 21:18:33 +00:00
|
|
|
|
val path = app.get(url).text.substringAfter("replace(\"")
|
|
|
|
|
.substringBefore("\")")
|
|
|
|
|
if (path == "/404") return null
|
|
|
|
|
return fixUrl(path, getBaseUrl(url))
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-30 05:21:25 +00:00
|
|
|
|
private fun getTechmnyCookies(page: String): Triple<String, String, String> {
|
|
|
|
|
val cat = "rdst_cat"
|
|
|
|
|
val post = "rdst_post"
|
|
|
|
|
val longC = page.substringAfter(".setTime")
|
|
|
|
|
.substringAfter("document.cookie = \"")
|
|
|
|
|
.substringBefore("\"")
|
|
|
|
|
.substringBefore(";")
|
|
|
|
|
val catC = if (page.contains("$cat=")) {
|
|
|
|
|
page.substringAfterLast("$cat=")
|
|
|
|
|
.substringBefore(";").let {
|
|
|
|
|
"$cat=$it"
|
|
|
|
|
}
|
2023-04-19 09:52:31 +00:00
|
|
|
|
} else {
|
|
|
|
|
""
|
|
|
|
|
}
|
2023-03-30 05:21:25 +00:00
|
|
|
|
|
|
|
|
|
val postC = if (page.contains("$post=")) {
|
|
|
|
|
page.substringAfterLast("$post=")
|
|
|
|
|
.substringBefore(";").let {
|
|
|
|
|
"$post=$it"
|
|
|
|
|
}
|
2023-04-19 09:52:31 +00:00
|
|
|
|
} else {
|
|
|
|
|
""
|
|
|
|
|
}
|
2023-03-30 05:21:25 +00:00
|
|
|
|
|
|
|
|
|
return Triple(longC, catC, postC)
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-10 12:25:28 +00:00
|
|
|
|
suspend fun getTvMoviesServer(url: String, season: Int?, episode: Int?): Pair<String, String?>? {
|
|
|
|
|
|
|
|
|
|
val req = app.get(url)
|
2023-01-14 09:40:35 +00:00
|
|
|
|
if (!req.isSuccessful) return null
|
2022-12-10 12:25:28 +00:00
|
|
|
|
val doc = req.document
|
|
|
|
|
|
|
|
|
|
return if (season == null) {
|
|
|
|
|
doc.select("table.wp-block-table tr:last-child td:first-child").text() to
|
|
|
|
|
doc.selectFirst("table.wp-block-table tr a")?.attr("href").let { link ->
|
|
|
|
|
app.get(link ?: return null).document.select("div#text-url a")
|
|
|
|
|
.mapIndexed { index, element ->
|
|
|
|
|
element.attr("href") to element.parent()?.textNodes()?.getOrNull(index)
|
|
|
|
|
?.text()
|
|
|
|
|
}.filter { it.second?.contains("Subtitles", true) == false }
|
|
|
|
|
.map { it.first }
|
|
|
|
|
}.lastOrNull()
|
|
|
|
|
} else {
|
|
|
|
|
doc.select("div.vc_tta-panels div#Season-$season table.wp-block-table tr:last-child td:first-child")
|
|
|
|
|
.text() to
|
|
|
|
|
doc.select("div.vc_tta-panels div#Season-$season table.wp-block-table tr a")
|
|
|
|
|
.mapNotNull { ele ->
|
|
|
|
|
app.get(ele.attr("href")).document.select("div#text-url a")
|
|
|
|
|
.mapIndexed { index, element ->
|
|
|
|
|
element.attr("href") to element.parent()?.textNodes()
|
|
|
|
|
?.getOrNull(index)?.text()
|
|
|
|
|
}.find { it.second?.contains("Episode $episode", true) == true }?.first
|
|
|
|
|
}.lastOrNull()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-02 13:00:44 +00:00
|
|
|
|
suspend fun getFilmxyCookies(imdbId: String? = null, season: Int? = null): FilmxyCookies? {
|
|
|
|
|
|
|
|
|
|
val url = if (season == null) {
|
2022-12-07 19:17:24 +00:00
|
|
|
|
"${filmxyAPI}/movie/$imdbId"
|
2022-12-02 13:00:44 +00:00
|
|
|
|
} else {
|
2022-12-07 19:17:24 +00:00
|
|
|
|
"${filmxyAPI}/tv/$imdbId"
|
2022-12-02 13:00:44 +00:00
|
|
|
|
}
|
2022-12-07 19:17:24 +00:00
|
|
|
|
val cookieUrl = "${filmxyAPI}/wp-admin/admin-ajax.php"
|
2022-12-02 13:00:44 +00:00
|
|
|
|
|
|
|
|
|
val res = session.get(
|
|
|
|
|
url,
|
|
|
|
|
headers = mapOf(
|
|
|
|
|
"Accept" to "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8"
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if (!res.isSuccessful) return FilmxyCookies()
|
|
|
|
|
|
|
|
|
|
val userNonce =
|
|
|
|
|
res.document.select("script").find { it.data().contains("var userNonce") }?.data()?.let {
|
|
|
|
|
Regex("var\\suserNonce.*?\"(\\S+?)\";").find(it)?.groupValues?.get(1)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var phpsessid = session.baseClient.cookieJar.loadForRequest(url.toHttpUrl())
|
|
|
|
|
.first { it.name == "PHPSESSID" }.value
|
|
|
|
|
|
|
|
|
|
session.post(
|
|
|
|
|
cookieUrl,
|
|
|
|
|
data = mapOf(
|
|
|
|
|
"action" to "guest_login",
|
|
|
|
|
"nonce" to "$userNonce",
|
|
|
|
|
),
|
|
|
|
|
headers = mapOf(
|
|
|
|
|
"Cookie" to "PHPSESSID=$phpsessid; G_ENABLED_IDPS=google",
|
|
|
|
|
"X-Requested-With" to "XMLHttpRequest",
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
val cookieJar = session.baseClient.cookieJar.loadForRequest(cookieUrl.toHttpUrl())
|
|
|
|
|
phpsessid = cookieJar.first { it.name == "PHPSESSID" }.value
|
|
|
|
|
val wLog =
|
|
|
|
|
cookieJar.first { it.name == "wordpress_logged_in_8bf9d5433ac88cc9a3a396d6b154cd01" }.value
|
|
|
|
|
val wSec = cookieJar.first { it.name == "wordpress_sec_8bf9d5433ac88cc9a3a396d6b154cd01" }.value
|
|
|
|
|
|
|
|
|
|
return FilmxyCookies(phpsessid, wLog, wSec)
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-10 12:25:28 +00:00
|
|
|
|
fun Document.findTvMoviesIframe(): String? {
|
|
|
|
|
return this.selectFirst("script:containsData(var seconds)")?.data()?.substringAfter("href='")
|
|
|
|
|
?.substringBefore("'>")
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-14 17:33:43 +00:00
|
|
|
|
suspend fun searchWatchOnline(
|
|
|
|
|
title: String? = null,
|
|
|
|
|
season: Int? = null,
|
|
|
|
|
imdbId: String? = null,
|
|
|
|
|
tmdbId: Int? = null,
|
|
|
|
|
): NiceResponse? {
|
2023-03-15 04:40:12 +00:00
|
|
|
|
val wTitle = title?.dropLast(1) // weird but this will make search working
|
2023-03-14 17:33:43 +00:00
|
|
|
|
val mediaId = app.get(
|
|
|
|
|
if (season == null) {
|
2023-03-15 04:40:12 +00:00
|
|
|
|
"${watchOnlineAPI}/api/v1/movies?filters[q]=$wTitle"
|
2023-03-14 17:33:43 +00:00
|
|
|
|
} else {
|
2023-03-15 04:40:12 +00:00
|
|
|
|
"${watchOnlineAPI}/api/v1/shows?filters[q]=$wTitle"
|
2023-03-14 17:33:43 +00:00
|
|
|
|
}
|
|
|
|
|
).parsedSafe<WatchOnlineSearch>()?.items?.find {
|
|
|
|
|
it.imdb_id == imdbId || it.tmdb_id == tmdbId || it.imdb_id == imdbId?.removePrefix("tt")
|
|
|
|
|
}?.slug
|
|
|
|
|
|
|
|
|
|
return app.get(
|
|
|
|
|
fixUrl(
|
|
|
|
|
mediaId ?: return null, if (season == null) {
|
|
|
|
|
"${watchOnlineAPI}/movies/view"
|
|
|
|
|
} else {
|
|
|
|
|
"${watchOnlineAPI}/shows/view"
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-20 00:33:01 +00:00
|
|
|
|
suspend fun searchCrunchyrollAnimeId(title: String): String? {
|
2023-04-19 09:52:31 +00:00
|
|
|
|
val res = app.get("${consumetCrunchyrollAPI}/search/$title", timeout = 600L)
|
2023-01-20 00:33:01 +00:00
|
|
|
|
.parsedSafe<ConsumetSearchResponse>()?.results
|
|
|
|
|
return (if (res?.size == 1) {
|
|
|
|
|
res.firstOrNull()
|
|
|
|
|
} else {
|
|
|
|
|
res?.find {
|
|
|
|
|
(it.title?.contains(
|
|
|
|
|
title,
|
|
|
|
|
true
|
2023-02-06 04:55:42 +00:00
|
|
|
|
) == true || it.title.createSlug()
|
|
|
|
|
?.contains("${title.createSlug()}", true) == true) && it.type.equals("series")
|
2023-01-20 00:33:01 +00:00
|
|
|
|
}
|
|
|
|
|
})?.id
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-19 02:51:04 +00:00
|
|
|
|
fun CrunchyrollDetails.findCrunchyrollId(
|
|
|
|
|
season: Int?,
|
|
|
|
|
episode: Int?,
|
|
|
|
|
epsTitle: String?
|
2023-03-14 16:13:42 +00:00
|
|
|
|
): List<Pair<CrunchyrollEpisodes?, String?>?> {
|
2023-04-19 09:52:31 +00:00
|
|
|
|
val sub = this.episodes?.filterKeys { it.contains("subbed") }
|
|
|
|
|
.matchingEpisode(epsTitle, season, episode) to "Raw"
|
|
|
|
|
val dub = this.episodes?.filterKeys { it.contains("English Dub") }
|
|
|
|
|
.matchingEpisode(epsTitle, season, episode) to "English Dub"
|
2023-01-19 02:51:04 +00:00
|
|
|
|
return listOf(sub, dub)
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-14 16:13:42 +00:00
|
|
|
|
fun Map<String, List<CrunchyrollEpisodes>>?.matchingEpisode(
|
|
|
|
|
epsTitle: String?,
|
|
|
|
|
season: Int?,
|
|
|
|
|
episode: Int?
|
|
|
|
|
): CrunchyrollEpisodes? {
|
|
|
|
|
return this?.mapNotNull { eps ->
|
|
|
|
|
eps.value.find {
|
|
|
|
|
(it.episode_number == episode && it.season_number == season) || it.title.equals(
|
|
|
|
|
epsTitle,
|
|
|
|
|
true
|
|
|
|
|
)
|
|
|
|
|
} ?: eps.value.find { it.episode_number == episode }
|
|
|
|
|
}?.firstOrNull()
|
2023-01-19 02:51:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-04-11 17:00:54 +00:00
|
|
|
|
suspend fun extractPutlockerSources(url: String?): NiceResponse? {
|
|
|
|
|
val embedHost = url?.substringBefore("/embed-player")
|
|
|
|
|
val player = app.get(
|
|
|
|
|
url ?: return null,
|
|
|
|
|
referer = "${putlockerAPI}/"
|
|
|
|
|
).document.select("div#player")
|
|
|
|
|
|
|
|
|
|
val text = "\"${player.attr("data-id")}\""
|
|
|
|
|
val password = player.attr("data-hash")
|
|
|
|
|
val cipher = CryptoAES.plEncrypt(password, text)
|
|
|
|
|
|
|
|
|
|
return app.get(
|
|
|
|
|
"$embedHost/ajax/getSources/", params = mapOf(
|
|
|
|
|
"id" to cipher.cipherText,
|
|
|
|
|
"h" to cipher.password,
|
|
|
|
|
"a" to cipher.iv,
|
|
|
|
|
"t" to cipher.salt,
|
|
|
|
|
), referer = url
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
suspend fun PutlockerResponses?.callback(
|
|
|
|
|
referer: String,
|
|
|
|
|
server: String,
|
|
|
|
|
callback: (ExtractorLink) -> Unit
|
|
|
|
|
) {
|
|
|
|
|
val ref = getBaseUrl(referer)
|
|
|
|
|
this?.sources?.map { source ->
|
|
|
|
|
val request = app.get(source.file, referer = ref)
|
|
|
|
|
callback.invoke(
|
|
|
|
|
ExtractorLink(
|
|
|
|
|
"Putlocker [$server]",
|
|
|
|
|
"Putlocker [$server]",
|
|
|
|
|
if (!request.isSuccessful) return@map null else source.file,
|
|
|
|
|
ref,
|
|
|
|
|
if (source.file.contains("m3u8")) getPutlockerQuality(request.text) else source.label?.replace(
|
|
|
|
|
Regex("[Pp]"),
|
|
|
|
|
""
|
|
|
|
|
)?.trim()?.toIntOrNull()
|
|
|
|
|
?: Qualities.P720.value,
|
|
|
|
|
source.file.contains("m3u8")
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun getPutlockerQuality(quality: String): Int {
|
|
|
|
|
return when {
|
|
|
|
|
quality.contains("NAME=\"1080p\"") || quality.contains("RESOLUTION=1920x1080") -> Qualities.P1080.value
|
2023-04-19 09:52:31 +00:00
|
|
|
|
quality.contains("NAME=\"720p\"") || quality.contains("RESOLUTION=1280x720") -> Qualities.P720.value
|
2023-04-11 17:00:54 +00:00
|
|
|
|
else -> Qualities.P480.value
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2023-01-30 03:42:55 +00:00
|
|
|
|
fun getEpisodeSlug(
|
|
|
|
|
season: Int? = null,
|
|
|
|
|
episode: Int? = null,
|
|
|
|
|
): Pair<String, String> {
|
|
|
|
|
return if (season == null && episode == null) {
|
|
|
|
|
"" to ""
|
|
|
|
|
} else {
|
|
|
|
|
(if (season!! < 10) "0$season" else "$season") to (if (episode!! < 10) "0$episode" else "$episode")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-12 16:12:15 +00:00
|
|
|
|
fun getTitleSlug(title: String? = null): Pair<String?, String?> {
|
2023-02-19 14:49:44 +00:00
|
|
|
|
val slug = title.createSlug()
|
2023-03-12 16:12:15 +00:00
|
|
|
|
return slug?.replace("-", "\\W") to title?.replace(" ", "_")
|
2023-01-30 03:42:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun getIndexQuery(
|
|
|
|
|
title: String? = null,
|
|
|
|
|
year: Int? = null,
|
|
|
|
|
season: Int? = null,
|
|
|
|
|
episode: Int? = null
|
|
|
|
|
): String {
|
|
|
|
|
val (seasonSlug, episodeSlug) = getEpisodeSlug(season, episode)
|
2023-03-03 22:37:14 +00:00
|
|
|
|
return (if (season == null) {
|
|
|
|
|
"$title ${year ?: ""}"
|
2023-01-30 03:42:55 +00:00
|
|
|
|
} else {
|
|
|
|
|
"$title S${seasonSlug}E${episodeSlug}"
|
2023-03-03 22:37:14 +00:00
|
|
|
|
}).trim()
|
2023-01-30 03:42:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun searchIndex(
|
|
|
|
|
title: String? = null,
|
|
|
|
|
season: Int? = null,
|
|
|
|
|
episode: Int? = null,
|
|
|
|
|
year: Int? = null,
|
2023-02-07 16:45:21 +00:00
|
|
|
|
response: String,
|
|
|
|
|
isTrimmed: Boolean = true,
|
2023-01-30 03:42:55 +00:00
|
|
|
|
): List<IndexMedia>? {
|
2023-02-02 05:17:58 +00:00
|
|
|
|
val files = tryParseJson<IndexSearch>(response)?.data?.files?.filter { media ->
|
2023-03-01 20:18:32 +00:00
|
|
|
|
matchingIndex(
|
|
|
|
|
media.name ?: return null,
|
|
|
|
|
media.mimeType ?: return null,
|
|
|
|
|
title ?: return null,
|
|
|
|
|
year,
|
|
|
|
|
season,
|
|
|
|
|
episode
|
|
|
|
|
)
|
2023-02-02 05:17:58 +00:00
|
|
|
|
}?.distinctBy { it.name }?.sortedByDescending { it.size?.toLongOrNull() ?: 0 } ?: return null
|
|
|
|
|
|
2023-02-07 16:45:21 +00:00
|
|
|
|
return if (isTrimmed) {
|
|
|
|
|
files.let { file ->
|
|
|
|
|
listOfNotNull(
|
|
|
|
|
file.find { it.name?.contains("2160p", true) == true },
|
|
|
|
|
file.find { it.name?.contains("1080p", true) == true }
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
files
|
2023-02-02 05:17:58 +00:00
|
|
|
|
}
|
2023-01-30 03:42:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-03-01 20:18:32 +00:00
|
|
|
|
fun matchingIndex(
|
|
|
|
|
mediaName: String?,
|
|
|
|
|
mediaMimeType: String?,
|
|
|
|
|
title: String?,
|
|
|
|
|
year: Int?,
|
|
|
|
|
season: Int?,
|
|
|
|
|
episode: Int?,
|
|
|
|
|
include720: Boolean = false
|
|
|
|
|
): Boolean {
|
2023-03-12 16:12:15 +00:00
|
|
|
|
val (wSlug, dwSlug) = getTitleSlug(title)
|
2023-03-01 20:18:32 +00:00
|
|
|
|
val (seasonSlug, episodeSlug) = getEpisodeSlug(season, episode)
|
|
|
|
|
return (if (season == null) {
|
2023-03-12 16:12:15 +00:00
|
|
|
|
mediaName?.contains(Regex("(?i)(?:$wSlug|$dwSlug).*$year")) == true
|
2023-03-01 20:18:32 +00:00
|
|
|
|
} else {
|
2023-03-12 16:12:15 +00:00
|
|
|
|
mediaName?.contains(Regex("(?i)(?:$wSlug|$dwSlug).*S${seasonSlug}.?E${episodeSlug}")) == true
|
2023-03-01 20:18:32 +00:00
|
|
|
|
}) && mediaName?.contains(
|
|
|
|
|
if (include720) Regex("(?i)(2160p|1080p|720p)") else Regex("(?i)(2160p|1080p)")
|
2023-03-12 16:12:15 +00:00
|
|
|
|
) == true && ((mediaMimeType in mimeType) || mediaName.contains(Regex("\\.mkv|\\.mp4|\\.avi")))
|
2023-03-01 20:18:32 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-01-29 03:29:15 +00:00
|
|
|
|
suspend fun getConfig(): BaymoviesConfig {
|
|
|
|
|
val regex = """const country = "(.*?)";
|
|
|
|
|
const downloadtime = "(.*?)";
|
|
|
|
|
var arrayofworkers = (.*)""".toRegex()
|
|
|
|
|
val js = app.get(
|
|
|
|
|
"https://geolocation.zindex.eu.org/api.js",
|
2023-02-10 08:36:06 +00:00
|
|
|
|
referer = "$baymoviesAPI/",
|
2023-01-29 03:29:15 +00:00
|
|
|
|
).text
|
|
|
|
|
val match = regex.find(js) ?: throw ErrorLoadingException()
|
|
|
|
|
val country = match.groupValues[1]
|
|
|
|
|
val downloadTime = match.groupValues[2]
|
|
|
|
|
val workers = tryParseJson<List<String>>(match.groupValues[3])
|
|
|
|
|
?: throw ErrorLoadingException()
|
|
|
|
|
|
|
|
|
|
return BaymoviesConfig(country, downloadTime, workers)
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-01 03:10:02 +00:00
|
|
|
|
fun decodeIndexJson(json: String): String {
|
|
|
|
|
val slug = json.reversed().substring(24)
|
|
|
|
|
return base64Decode(slug.substring(0, slug.length - 20))
|
|
|
|
|
}
|
2023-02-11 10:50:38 +00:00
|
|
|
|
|
2023-02-19 11:46:38 +00:00
|
|
|
|
fun String.decryptGomoviesJson(key: String = "123"): String {
|
2023-02-14 18:01:07 +00:00
|
|
|
|
val sb = StringBuilder()
|
|
|
|
|
var i = 0
|
|
|
|
|
while (i < this.length) {
|
|
|
|
|
var j = 0
|
|
|
|
|
while (j < key.length && i < this.length) {
|
|
|
|
|
sb.append((this[i].code xor key[j].code).toChar())
|
|
|
|
|
j++
|
|
|
|
|
i++
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return sb.toString()
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-02 19:45:23 +00:00
|
|
|
|
fun allanimeQueries(variables: String, query: String) : String {
|
|
|
|
|
return "${allanimeAPI}/allanimeapi?variables=$variables&query=$query"
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-19 11:46:38 +00:00
|
|
|
|
fun Headers.getGomoviesCookies(cookieKey: String = "set-cookie"): Map<String, String> {
|
2023-02-14 18:01:07 +00:00
|
|
|
|
val cookieList =
|
|
|
|
|
this.filter { it.first.equals(cookieKey, ignoreCase = true) }.mapNotNull {
|
|
|
|
|
it.second.split(";").firstOrNull()
|
|
|
|
|
}
|
|
|
|
|
return cookieList.associate {
|
|
|
|
|
val split = it.split("=", limit = 2)
|
|
|
|
|
(split.getOrNull(0)?.trim() ?: "") to (split.getOrNull(1)?.trim() ?: "")
|
|
|
|
|
}.filter { it.key.isNotBlank() && it.value.isNotBlank() }
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-06 04:55:42 +00:00
|
|
|
|
fun String?.createSlug(): String? {
|
2023-03-14 17:33:43 +00:00
|
|
|
|
return this?.replace(Regex("[^\\w\\s-]"), "")
|
2023-03-11 20:39:22 +00:00
|
|
|
|
?.replace(" ", "-")
|
|
|
|
|
?.replace(Regex("( – )|( -)|(- )|(--)"), "-")
|
|
|
|
|
?.lowercase()
|
2022-12-02 13:00:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun getLanguage(str: String): String {
|
|
|
|
|
return if (str.contains("(in_ID)")) "Indonesian" else str
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-11 10:50:38 +00:00
|
|
|
|
fun bytesToGigaBytes(number: Double): Double = number / 1024000000
|
2023-01-29 03:29:15 +00:00
|
|
|
|
|
2022-12-02 13:00:44 +00:00
|
|
|
|
fun getKisskhTitle(str: String?): String? {
|
2023-02-07 14:44:14 +00:00
|
|
|
|
return str?.replace(Regex("[^a-zA-Z\\d]"), "-")
|
2022-12-02 13:00:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-04-19 09:52:31 +00:00
|
|
|
|
fun String.getFileSize(): Float? {
|
|
|
|
|
val size = Regex("(?i)(\\d+\\.?\\d+\\sGB|MB)").find(this)?.groupValues?.get(0)?.trim()
|
|
|
|
|
val num = Regex("(\\d+\\.?\\d+)").find(size ?: return null)?.groupValues?.get(0)?.toFloat()
|
|
|
|
|
?: return null
|
2023-03-01 20:18:32 +00:00
|
|
|
|
return when {
|
|
|
|
|
size.contains("GB") -> num * 1000000
|
|
|
|
|
else -> num * 1000
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-19 09:52:31 +00:00
|
|
|
|
fun getIndexQualityTags(str: String?, fullTag: Boolean = false): String {
|
|
|
|
|
return if (fullTag) Regex("(?i)(.*)\\.(?:mkv|mp4|avi)").find(str ?: "")?.groupValues?.get(1)
|
|
|
|
|
?.trim() ?: str ?: "" else Regex("(?i)\\d{3,4}[pP]\\.?(.*?)\\.(mkv|mp4|avi)").find(
|
|
|
|
|
str ?: ""
|
|
|
|
|
)?.groupValues?.getOrNull(1)
|
2023-04-14 08:34:27 +00:00
|
|
|
|
?.replace(".", " ")?.trim() ?: str ?: ""
|
2023-02-10 17:52:02 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun getIndexQuality(str: String?): Int {
|
2023-02-11 10:50:38 +00:00
|
|
|
|
return Regex("(\\d{3,4})[pP]").find(str ?: "")?.groupValues?.getOrNull(1)?.toIntOrNull()
|
|
|
|
|
?: Qualities.Unknown.value
|
2023-02-10 17:52:02 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-04-19 09:52:31 +00:00
|
|
|
|
fun getIndexSize(str: String?): String? {
|
|
|
|
|
return Regex("(?i)([\\d.]+\\s*(?:gb|mb))").find(str ?: "")?.groupValues?.getOrNull(1)?.trim()
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-02 13:00:44 +00:00
|
|
|
|
fun getQuality(str: String): Int {
|
|
|
|
|
return when (str) {
|
|
|
|
|
"360p" -> Qualities.P240.value
|
|
|
|
|
"480p" -> Qualities.P360.value
|
|
|
|
|
"720p" -> Qualities.P480.value
|
|
|
|
|
"1080p" -> Qualities.P720.value
|
|
|
|
|
"1080p Ultra" -> Qualities.P1080.value
|
|
|
|
|
else -> getQualityFromName(str)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-02 16:50:56 +00:00
|
|
|
|
fun getGMoviesQuality(str: String): Int {
|
|
|
|
|
return when {
|
|
|
|
|
str.contains("480P", true) -> Qualities.P480.value
|
|
|
|
|
str.contains("720P", true) -> Qualities.P720.value
|
2022-12-12 13:24:46 +00:00
|
|
|
|
str.contains("1080P", true) -> Qualities.P1080.value
|
2022-12-02 16:50:56 +00:00
|
|
|
|
str.contains("4K", true) -> Qualities.P2160.value
|
|
|
|
|
else -> Qualities.Unknown.value
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-24 21:32:34 +00:00
|
|
|
|
fun getSoraQuality(quality: String): Int {
|
|
|
|
|
return when (quality) {
|
|
|
|
|
"GROOT_FD" -> Qualities.P360.value
|
|
|
|
|
"GROOT_LD" -> Qualities.P480.value
|
|
|
|
|
"GROOT_SD" -> Qualities.P720.value
|
|
|
|
|
"GROOT_HD" -> Qualities.P1080.value
|
|
|
|
|
else -> Qualities.Unknown.value
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-12 13:24:46 +00:00
|
|
|
|
fun getFDoviesQuality(str: String): String {
|
|
|
|
|
return when {
|
|
|
|
|
str.contains("1080P", true) -> "1080P"
|
|
|
|
|
str.contains("4K", true) -> "4K"
|
|
|
|
|
else -> ""
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-03 23:01:24 +00:00
|
|
|
|
fun getVipLanguage(str: String): String {
|
|
|
|
|
return when (str) {
|
|
|
|
|
"in_ID" -> "Indonesian"
|
|
|
|
|
"pt" -> "Portuguese"
|
|
|
|
|
else -> str.split("_").first().let {
|
|
|
|
|
SubtitleHelper.fromTwoLettersToLanguage(it).toString()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-20 22:06:45 +00:00
|
|
|
|
fun getDbgoLanguage(str: String): String {
|
|
|
|
|
return when (str) {
|
|
|
|
|
"Русский" -> "Russian"
|
|
|
|
|
"Українська" -> "Ukrainian"
|
|
|
|
|
else -> str
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-06 01:27:16 +00:00
|
|
|
|
fun fixCrunchyrollLang(language: String?): String? {
|
2023-02-26 12:40:53 +00:00
|
|
|
|
return SubtitleHelper.fromTwoLettersToLanguage(language ?: return null)
|
|
|
|
|
?: SubtitleHelper.fromTwoLettersToLanguage(language.substringBefore("-"))
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-24 21:32:34 +00:00
|
|
|
|
fun getDeviceId(length: Int = 16): String {
|
|
|
|
|
val allowedChars = ('a'..'f') + ('0'..'9')
|
|
|
|
|
return (1..length)
|
|
|
|
|
.map { allowedChars.random() }
|
|
|
|
|
.joinToString("")
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-17 10:01:08 +00:00
|
|
|
|
fun encodeVrf(query: String): String {
|
|
|
|
|
return encode(
|
|
|
|
|
encryptVrf(
|
2023-04-28 17:25:17 +00:00
|
|
|
|
cipherVrf(bflixChipperKey, encode(query)),
|
|
|
|
|
bflixKey
|
2023-04-17 10:01:08 +00:00
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-28 17:25:17 +00:00
|
|
|
|
fun decodeVrf(text: String): String {
|
|
|
|
|
return decode(cipherVrf(bflixChipperKey, decryptVrf(text, bflixKey)))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Suppress("SameParameterValue")
|
|
|
|
|
private fun encryptVrf(input: String, key: String): String {
|
2023-04-17 10:01:08 +00:00
|
|
|
|
if (input.any { it.code > 255 }) throw Exception("illegal characters!")
|
|
|
|
|
var output = ""
|
|
|
|
|
for (i in input.indices step 3) {
|
|
|
|
|
val a = intArrayOf(-1, -1, -1, -1)
|
|
|
|
|
a[0] = input[i].code shr 2
|
|
|
|
|
a[1] = (3 and input[i].code) shl 4
|
|
|
|
|
if (input.length > i + 1) {
|
|
|
|
|
a[1] = a[1] or (input[i + 1].code shr 4)
|
|
|
|
|
a[2] = (15 and input[i + 1].code) shl 2
|
|
|
|
|
}
|
|
|
|
|
if (input.length > i + 2) {
|
|
|
|
|
a[2] = a[2] or (input[i + 2].code shr 6)
|
|
|
|
|
a[3] = 63 and input[i + 2].code
|
|
|
|
|
}
|
|
|
|
|
for (n in a) {
|
|
|
|
|
if (n == -1) output += "="
|
|
|
|
|
else {
|
|
|
|
|
if (n in 0..63) output += key[n]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return output
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-28 17:25:17 +00:00
|
|
|
|
@Suppress("SameParameterValue")
|
|
|
|
|
private fun decryptVrf(input: String, key: String): String {
|
|
|
|
|
val t = if (input.replace("""[\t\n\f\r]""".toRegex(), "").length % 4 == 0) {
|
|
|
|
|
input.replace("""==?$""".toRegex(), "")
|
|
|
|
|
} else input
|
|
|
|
|
if (t.length % 4 == 1 || t.contains("""[^+/0-9A-Za-z]""".toRegex())) throw Exception("bad input")
|
|
|
|
|
var i: Int
|
|
|
|
|
var r = ""
|
|
|
|
|
var e = 0
|
|
|
|
|
var u = 0
|
|
|
|
|
for (o in t.indices) {
|
|
|
|
|
e = e shl 6
|
|
|
|
|
i = key.indexOf(t[o])
|
|
|
|
|
e = e or i
|
|
|
|
|
u += 6
|
|
|
|
|
if (24 == u) {
|
|
|
|
|
r += ((16711680 and e) shr 16).toChar()
|
|
|
|
|
r += ((65280 and e) shr 8).toChar()
|
|
|
|
|
r += (255 and e).toChar()
|
|
|
|
|
e = 0
|
|
|
|
|
u = 0
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return if (12 == u) {
|
|
|
|
|
e = e shr 4
|
|
|
|
|
r + e.toChar()
|
|
|
|
|
} else {
|
|
|
|
|
if (18 == u) {
|
|
|
|
|
e = e shr 2
|
|
|
|
|
r += ((65280 and e) shr 8).toChar()
|
|
|
|
|
r += (255 and e).toChar()
|
|
|
|
|
}
|
|
|
|
|
r
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-17 10:01:08 +00:00
|
|
|
|
fun cipherVrf(key: String, text: String): String {
|
|
|
|
|
val arr = IntArray(256) { it }
|
|
|
|
|
|
|
|
|
|
var u = 0
|
|
|
|
|
var r: Int
|
|
|
|
|
arr.indices.forEach {
|
|
|
|
|
u = (u + arr[it] + key[it % key.length].code) % 256
|
|
|
|
|
r = arr[it]
|
|
|
|
|
arr[it] = arr[u]
|
|
|
|
|
arr[u] = r
|
|
|
|
|
}
|
|
|
|
|
u = 0
|
|
|
|
|
var c = 0
|
|
|
|
|
|
|
|
|
|
return text.indices.map { j ->
|
|
|
|
|
c = (c + 1) % 256
|
|
|
|
|
u = (u + arr[c]) % 256
|
|
|
|
|
r = arr[c]
|
|
|
|
|
arr[c] = arr[u]
|
|
|
|
|
arr[u] = r
|
|
|
|
|
(text[j].code xor arr[(arr[c] + arr[u]) % 256]).toChar()
|
|
|
|
|
}.joinToString("")
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-11 10:50:38 +00:00
|
|
|
|
fun String.encodeUrl(): String {
|
2023-01-30 03:42:55 +00:00
|
|
|
|
val url = URL(this)
|
|
|
|
|
val uri = URI(url.protocol, url.userInfo, url.host, url.port, url.path, url.query, url.ref)
|
|
|
|
|
return uri.toURL().toString()
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-02 13:00:44 +00:00
|
|
|
|
fun getBaseUrl(url: String): String {
|
|
|
|
|
return URI(url).let {
|
|
|
|
|
"${it.scheme}://${it.host}"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-25 21:22:33 +00:00
|
|
|
|
fun String.decodeBase64(): String {
|
|
|
|
|
return Base64.decode(this, Base64.DEFAULT).toString(Charsets.UTF_8)
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-28 17:25:17 +00:00
|
|
|
|
fun decode(input: String): String = URLDecoder.decode(input, "utf-8")
|
|
|
|
|
|
2023-04-17 10:01:08 +00:00
|
|
|
|
fun encode(input: String): String = URLEncoder.encode(input, "utf-8").replace("+", "%20")
|
2023-02-19 19:57:29 +00:00
|
|
|
|
|
2022-12-02 13:00:44 +00:00
|
|
|
|
fun decryptStreamUrl(data: String): String {
|
|
|
|
|
|
|
|
|
|
fun getTrash(arr: List<String>, item: Int): List<String> {
|
|
|
|
|
val trash = ArrayList<List<String>>()
|
|
|
|
|
for (i in 1..item) {
|
|
|
|
|
trash.add(arr)
|
|
|
|
|
}
|
|
|
|
|
return trash.reduce { acc, list ->
|
|
|
|
|
val temp = ArrayList<String>()
|
|
|
|
|
acc.forEach { ac ->
|
|
|
|
|
list.forEach { li ->
|
|
|
|
|
temp.add(ac.plus(li))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return@reduce temp
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val trashList = listOf("@", "#", "!", "^", "$")
|
|
|
|
|
val trashSet = getTrash(trashList, 2) + getTrash(trashList, 3)
|
|
|
|
|
var trashString = data.replace("#2", "").split("//_//").joinToString("")
|
|
|
|
|
|
|
|
|
|
trashSet.forEach {
|
|
|
|
|
val temp = base64Encode(it.toByteArray())
|
|
|
|
|
trashString = trashString.replace(temp, "")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return base64Decode(trashString)
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun fixUrl(url: String, domain: String): String {
|
|
|
|
|
if (url.startsWith("http")) {
|
|
|
|
|
return url
|
|
|
|
|
}
|
|
|
|
|
if (url.isEmpty()) {
|
|
|
|
|
return ""
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val startsWithNoHttp = url.startsWith("//")
|
|
|
|
|
if (startsWithNoHttp) {
|
|
|
|
|
return "https:$url"
|
|
|
|
|
} else {
|
|
|
|
|
if (url.startsWith('/')) {
|
|
|
|
|
return domain + url
|
|
|
|
|
}
|
|
|
|
|
return "$domain/$url"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-02 08:28:22 +00:00
|
|
|
|
fun Int.toRomanNumeral(): String = Symbol.closestBelow(this)
|
|
|
|
|
.let { symbol ->
|
|
|
|
|
if (symbol != null) {
|
|
|
|
|
"$symbol${(this - symbol.decimalValue).toRomanNumeral()}"
|
|
|
|
|
} else {
|
|
|
|
|
""
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private enum class Symbol(val decimalValue: Int) {
|
|
|
|
|
I(1),
|
|
|
|
|
IV(4),
|
|
|
|
|
V(5),
|
|
|
|
|
IX(9),
|
|
|
|
|
X(10);
|
|
|
|
|
|
|
|
|
|
companion object {
|
|
|
|
|
fun closestBelow(value: Int) =
|
|
|
|
|
values()
|
|
|
|
|
.sortedByDescending { it.decimalValue }
|
|
|
|
|
.firstOrNull { value >= it.decimalValue }
|
|
|
|
|
}
|
2023-02-04 10:58:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// code found on https://stackoverflow.com/a/63701411
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Conforming with CryptoJS AES method
|
|
|
|
|
*/
|
|
|
|
|
// see https://gist.github.com/thackerronak/554c985c3001b16810af5fc0eb5c358f
|
|
|
|
|
@Suppress("unused", "FunctionName", "SameParameterValue")
|
|
|
|
|
object CryptoAES {
|
|
|
|
|
|
|
|
|
|
private const val KEY_SIZE = 256
|
|
|
|
|
private const val IV_SIZE = 128
|
|
|
|
|
private const val HASH_CIPHER = "AES/CBC/PKCS5Padding"
|
|
|
|
|
private const val AES = "AES"
|
|
|
|
|
private const val KDF_DIGEST = "MD5"
|
|
|
|
|
|
|
|
|
|
// Seriously crypto-js, what's wrong with you?
|
|
|
|
|
private const val APPEND = "Salted__"
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Encrypt
|
|
|
|
|
* @param password passphrase
|
|
|
|
|
* @param plainText plain string
|
|
|
|
|
*/
|
|
|
|
|
fun encrypt(password: String, plainText: String): String {
|
|
|
|
|
val saltBytes = generateSalt(8)
|
|
|
|
|
val key = ByteArray(KEY_SIZE / 8)
|
|
|
|
|
val iv = ByteArray(IV_SIZE / 8)
|
|
|
|
|
EvpKDF(password.toByteArray(), KEY_SIZE, IV_SIZE, saltBytes, key, iv)
|
|
|
|
|
val keyS = SecretKeySpec(key, AES)
|
|
|
|
|
val cipher = Cipher.getInstance(HASH_CIPHER)
|
|
|
|
|
val ivSpec = IvParameterSpec(iv)
|
|
|
|
|
cipher.init(Cipher.ENCRYPT_MODE, keyS, ivSpec)
|
|
|
|
|
val cipherText = cipher.doFinal(plainText.toByteArray())
|
|
|
|
|
// Thanks kientux for this: https://gist.github.com/kientux/bb48259c6f2133e628ad
|
|
|
|
|
// Create CryptoJS-like encrypted!
|
|
|
|
|
val sBytes = APPEND.toByteArray()
|
|
|
|
|
val b = ByteArray(sBytes.size + saltBytes.size + cipherText.size)
|
|
|
|
|
System.arraycopy(sBytes, 0, b, 0, sBytes.size)
|
|
|
|
|
System.arraycopy(saltBytes, 0, b, sBytes.size, saltBytes.size)
|
|
|
|
|
System.arraycopy(cipherText, 0, b, sBytes.size + saltBytes.size, cipherText.size)
|
|
|
|
|
val bEncode = Base64.encode(b, Base64.NO_WRAP)
|
|
|
|
|
return String(bEncode)
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-11 17:00:54 +00:00
|
|
|
|
fun plEncrypt(password: String, plainText: String): EncryptResult {
|
|
|
|
|
val saltBytes = generateSalt(8)
|
|
|
|
|
val key = ByteArray(KEY_SIZE / 8)
|
|
|
|
|
val iv = ByteArray(IV_SIZE / 8)
|
|
|
|
|
EvpKDF(password.toByteArray(), KEY_SIZE, IV_SIZE, saltBytes, key, iv)
|
|
|
|
|
val keyS = SecretKeySpec(key, AES)
|
|
|
|
|
val cipher = Cipher.getInstance(HASH_CIPHER)
|
|
|
|
|
val ivSpec = IvParameterSpec(iv)
|
|
|
|
|
cipher.init(Cipher.ENCRYPT_MODE, keyS, ivSpec)
|
|
|
|
|
val cipherText = cipher.doFinal(plainText.toByteArray())
|
|
|
|
|
val bEncode = Base64.encode(cipherText, Base64.NO_WRAP)
|
|
|
|
|
return EncryptResult(
|
|
|
|
|
String(bEncode).toHex(),
|
|
|
|
|
password.toHex(),
|
|
|
|
|
saltBytes.toHex(),
|
|
|
|
|
iv.toHex()
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-04 10:58:06 +00:00
|
|
|
|
/**
|
|
|
|
|
* Decrypt
|
|
|
|
|
* Thanks Artjom B. for this: http://stackoverflow.com/a/29152379/4405051
|
|
|
|
|
* @param password passphrase
|
|
|
|
|
* @param cipherText encrypted string
|
|
|
|
|
*/
|
|
|
|
|
fun decrypt(password: String, cipherText: String): String {
|
|
|
|
|
val ctBytes = Base64.decode(cipherText.toByteArray(), Base64.NO_WRAP)
|
|
|
|
|
val saltBytes = Arrays.copyOfRange(ctBytes, 8, 16)
|
|
|
|
|
val cipherTextBytes = Arrays.copyOfRange(ctBytes, 16, ctBytes.size)
|
|
|
|
|
val key = ByteArray(KEY_SIZE / 8)
|
|
|
|
|
val iv = ByteArray(IV_SIZE / 8)
|
|
|
|
|
EvpKDF(password.toByteArray(), KEY_SIZE, IV_SIZE, saltBytes, key, iv)
|
|
|
|
|
val cipher = Cipher.getInstance(HASH_CIPHER)
|
|
|
|
|
val keyS = SecretKeySpec(key, AES)
|
|
|
|
|
cipher.init(Cipher.DECRYPT_MODE, keyS, IvParameterSpec(iv))
|
|
|
|
|
val plainText = cipher.doFinal(cipherTextBytes)
|
|
|
|
|
return String(plainText)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun EvpKDF(
|
|
|
|
|
password: ByteArray,
|
|
|
|
|
keySize: Int,
|
|
|
|
|
ivSize: Int,
|
|
|
|
|
salt: ByteArray,
|
|
|
|
|
resultKey: ByteArray,
|
|
|
|
|
resultIv: ByteArray
|
|
|
|
|
): ByteArray {
|
|
|
|
|
return EvpKDF(password, keySize, ivSize, salt, 1, KDF_DIGEST, resultKey, resultIv)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Suppress("NAME_SHADOWING")
|
|
|
|
|
private fun EvpKDF(
|
|
|
|
|
password: ByteArray,
|
|
|
|
|
keySize: Int,
|
|
|
|
|
ivSize: Int,
|
|
|
|
|
salt: ByteArray,
|
|
|
|
|
iterations: Int,
|
|
|
|
|
hashAlgorithm: String,
|
|
|
|
|
resultKey: ByteArray,
|
|
|
|
|
resultIv: ByteArray
|
|
|
|
|
): ByteArray {
|
|
|
|
|
val keySize = keySize / 32
|
|
|
|
|
val ivSize = ivSize / 32
|
|
|
|
|
val targetKeySize = keySize + ivSize
|
|
|
|
|
val derivedBytes = ByteArray(targetKeySize * 4)
|
|
|
|
|
var numberOfDerivedWords = 0
|
|
|
|
|
var block: ByteArray? = null
|
|
|
|
|
val hash = MessageDigest.getInstance(hashAlgorithm)
|
|
|
|
|
while (numberOfDerivedWords < targetKeySize) {
|
|
|
|
|
if (block != null) {
|
|
|
|
|
hash.update(block)
|
|
|
|
|
}
|
|
|
|
|
hash.update(password)
|
|
|
|
|
block = hash.digest(salt)
|
|
|
|
|
hash.reset()
|
|
|
|
|
// Iterations
|
|
|
|
|
for (i in 1 until iterations) {
|
|
|
|
|
block = hash.digest(block!!)
|
|
|
|
|
hash.reset()
|
|
|
|
|
}
|
|
|
|
|
System.arraycopy(
|
|
|
|
|
block!!, 0, derivedBytes, numberOfDerivedWords * 4,
|
|
|
|
|
min(block.size, (targetKeySize - numberOfDerivedWords) * 4)
|
|
|
|
|
)
|
|
|
|
|
numberOfDerivedWords += block.size / 4
|
|
|
|
|
}
|
|
|
|
|
System.arraycopy(derivedBytes, 0, resultKey, 0, keySize * 4)
|
|
|
|
|
System.arraycopy(derivedBytes, keySize * 4, resultIv, 0, ivSize * 4)
|
|
|
|
|
return derivedBytes // key + iv
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun generateSalt(length: Int): ByteArray {
|
|
|
|
|
return ByteArray(length).apply {
|
|
|
|
|
SecureRandom().nextBytes(this)
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-04-11 17:00:54 +00:00
|
|
|
|
|
2023-04-19 09:52:31 +00:00
|
|
|
|
private fun ByteArray.toHex(): String =
|
|
|
|
|
joinToString(separator = "") { eachByte -> "%02x".format(eachByte) }
|
2023-04-11 17:00:54 +00:00
|
|
|
|
|
|
|
|
|
private fun String.toHex(): String = toByteArray().toHex()
|
|
|
|
|
|
|
|
|
|
data class EncryptResult(
|
|
|
|
|
val cipherText: String,
|
|
|
|
|
val password: String,
|
|
|
|
|
val salt: String,
|
|
|
|
|
val iv: String
|
|
|
|
|
)
|
|
|
|
|
|
2023-03-05 15:58:53 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
object RabbitStream {
|
|
|
|
|
|
|
|
|
|
suspend fun MainAPI.extractRabbitStream(
|
|
|
|
|
url: String,
|
|
|
|
|
subtitleCallback: (SubtitleFile) -> Unit,
|
|
|
|
|
callback: (ExtractorLink) -> Unit,
|
|
|
|
|
useSidAuthentication: Boolean,
|
|
|
|
|
/** Used for extractorLink name, input: Source name */
|
|
|
|
|
extractorData: String? = null,
|
|
|
|
|
decryptKey: String? = null,
|
|
|
|
|
nameTransformer: (String) -> String,
|
|
|
|
|
) = suspendSafeApiCall {
|
|
|
|
|
// https://rapid-cloud.ru/embed-6/dcPOVRE57YOT?z= -> https://rapid-cloud.ru/embed-6
|
|
|
|
|
val mainIframeUrl =
|
|
|
|
|
url.substringBeforeLast("/")
|
|
|
|
|
val mainIframeId = url.substringAfterLast("/")
|
|
|
|
|
.substringBefore("?") // https://rapid-cloud.ru/embed-6/dcPOVRE57YOT?z= -> dcPOVRE57YOT
|
|
|
|
|
var sid: String? = null
|
|
|
|
|
if (useSidAuthentication && extractorData != null) {
|
|
|
|
|
negotiateNewSid(extractorData)?.also { pollingData ->
|
|
|
|
|
app.post(
|
|
|
|
|
"$extractorData&t=${generateTimeStamp()}&sid=${pollingData.sid}",
|
|
|
|
|
requestBody = "40".toRequestBody(),
|
|
|
|
|
timeout = 60
|
|
|
|
|
)
|
|
|
|
|
val text = app.get(
|
|
|
|
|
"$extractorData&t=${generateTimeStamp()}&sid=${pollingData.sid}",
|
|
|
|
|
timeout = 60
|
|
|
|
|
).text.replaceBefore("{", "")
|
|
|
|
|
|
|
|
|
|
sid = AppUtils.parseJson<PollingData>(text).sid
|
|
|
|
|
ioSafe { app.get("$extractorData&t=${generateTimeStamp()}&sid=${pollingData.sid}") }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
val getSourcesUrl = "${
|
|
|
|
|
mainIframeUrl.replace(
|
|
|
|
|
"/embed",
|
|
|
|
|
"/ajax/embed"
|
|
|
|
|
)
|
|
|
|
|
}/getSources?id=$mainIframeId${sid?.let { "$&sId=$it" } ?: ""}"
|
|
|
|
|
val response = app.get(
|
|
|
|
|
getSourcesUrl,
|
|
|
|
|
referer = mainUrl,
|
|
|
|
|
headers = mapOf(
|
|
|
|
|
"X-Requested-With" to "XMLHttpRequest",
|
|
|
|
|
"Accept" to "*/*",
|
|
|
|
|
"Accept-Language" to "en-US,en;q=0.5",
|
|
|
|
|
"Connection" to "keep-alive",
|
|
|
|
|
"TE" to "trailers"
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
val sourceObject = if (decryptKey != null) {
|
|
|
|
|
val encryptedMap = response.parsedSafe<SourceObjectEncrypted>()
|
|
|
|
|
val sources = encryptedMap?.sources
|
|
|
|
|
if (sources == null || encryptedMap.encrypted == false) {
|
|
|
|
|
response.parsedSafe()
|
|
|
|
|
} else {
|
|
|
|
|
val decrypted =
|
|
|
|
|
decryptMapped<List<Sources>>(sources, decryptKey)
|
|
|
|
|
SourceObject(
|
|
|
|
|
sources = decrypted,
|
|
|
|
|
tracks = encryptedMap.tracks
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
response.parsedSafe()
|
|
|
|
|
} ?: return@suspendSafeApiCall
|
|
|
|
|
|
|
|
|
|
sourceObject.tracks?.forEach { track ->
|
|
|
|
|
track?.toSubtitleFile()?.let { subtitleFile ->
|
|
|
|
|
subtitleCallback.invoke(subtitleFile)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val list = listOf(
|
|
|
|
|
sourceObject.sources to "source 1",
|
|
|
|
|
sourceObject.sources1 to "source 2",
|
|
|
|
|
sourceObject.sources2 to "source 3",
|
|
|
|
|
sourceObject.sourcesBackup to "source backup"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
list.forEach { subList ->
|
|
|
|
|
subList.first?.forEach { source ->
|
|
|
|
|
source?.toExtractorLink(
|
|
|
|
|
"Vidcloud",
|
|
|
|
|
"$twoEmbedAPI/",
|
|
|
|
|
extractorData,
|
|
|
|
|
)
|
|
|
|
|
?.forEach {
|
|
|
|
|
// Sets Zoro SID used for video loading
|
|
|
|
|
// (this as? ZoroProvider)?.sid?.set(it.url.hashCode(), sid)
|
|
|
|
|
callback(it)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private suspend fun Sources.toExtractorLink(
|
|
|
|
|
name: String,
|
|
|
|
|
referer: String,
|
|
|
|
|
extractorData: String? = null,
|
|
|
|
|
): List<ExtractorLink>? {
|
|
|
|
|
return this.file?.let { file ->
|
|
|
|
|
//println("FILE::: $file")
|
|
|
|
|
val isM3u8 = URI(this.file).path.endsWith(".m3u8") || this.type.equals(
|
|
|
|
|
"hls",
|
|
|
|
|
ignoreCase = true
|
|
|
|
|
)
|
|
|
|
|
return if (isM3u8) {
|
|
|
|
|
suspendSafeApiCall {
|
|
|
|
|
M3u8Helper().m3u8Generation(
|
|
|
|
|
M3u8Helper.M3u8Stream(
|
|
|
|
|
this.file,
|
|
|
|
|
null,
|
|
|
|
|
mapOf("Referer" to "https://mzzcloud.life/")
|
|
|
|
|
), false
|
|
|
|
|
)
|
|
|
|
|
.map { stream ->
|
|
|
|
|
ExtractorLink(
|
|
|
|
|
name,
|
|
|
|
|
name,
|
|
|
|
|
stream.streamUrl,
|
|
|
|
|
referer,
|
|
|
|
|
getQualityFromName(stream.quality?.toString()),
|
|
|
|
|
true,
|
|
|
|
|
extractorData = extractorData
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}.takeIf { !it.isNullOrEmpty() } ?: listOf(
|
|
|
|
|
// Fallback if m3u8 extractor fails
|
|
|
|
|
ExtractorLink(
|
|
|
|
|
name,
|
|
|
|
|
name,
|
|
|
|
|
this.file,
|
|
|
|
|
referer,
|
|
|
|
|
getQualityFromName(this.label),
|
|
|
|
|
isM3u8,
|
|
|
|
|
extractorData = extractorData
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
listOf(
|
|
|
|
|
ExtractorLink(
|
|
|
|
|
name,
|
|
|
|
|
name,
|
|
|
|
|
file,
|
|
|
|
|
referer,
|
|
|
|
|
getQualityFromName(this.label),
|
|
|
|
|
false,
|
|
|
|
|
extractorData = extractorData
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun Tracks.toSubtitleFile(): SubtitleFile? {
|
|
|
|
|
return this.file?.let {
|
|
|
|
|
SubtitleFile(
|
|
|
|
|
this.label ?: "Unknown",
|
|
|
|
|
it
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Generates a session
|
|
|
|
|
* 1 Get request.
|
|
|
|
|
* */
|
|
|
|
|
private suspend fun negotiateNewSid(baseUrl: String): PollingData? {
|
|
|
|
|
// Tries multiple times
|
|
|
|
|
for (i in 1..5) {
|
|
|
|
|
val jsonText =
|
|
|
|
|
app.get("$baseUrl&t=${generateTimeStamp()}").text.replaceBefore(
|
|
|
|
|
"{",
|
|
|
|
|
""
|
|
|
|
|
)
|
|
|
|
|
// println("Negotiated sid $jsonText")
|
|
|
|
|
AppUtils.parseJson<PollingData?>(jsonText)?.let { return it }
|
|
|
|
|
delay(1000L * i)
|
|
|
|
|
}
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun generateTimeStamp(): String {
|
|
|
|
|
val chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_"
|
|
|
|
|
var code = ""
|
|
|
|
|
var time = APIHolder.unixTimeMS
|
|
|
|
|
while (time > 0) {
|
|
|
|
|
code += chars[(time % (chars.length)).toInt()]
|
|
|
|
|
time /= chars.length
|
|
|
|
|
}
|
|
|
|
|
return code.reversed()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
suspend fun getKey(): String? {
|
|
|
|
|
return app.get("https://raw.githubusercontent.com/enimax-anime/key/e4/key.txt")
|
|
|
|
|
.text
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private inline fun <reified T> decryptMapped(input: String, key: String): T? {
|
|
|
|
|
return tryParseJson(decrypt(input, key))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun decrypt(input: String, key: String): String {
|
|
|
|
|
return decryptSourceUrl(
|
|
|
|
|
generateKey(
|
|
|
|
|
base64DecodeArray(input).copyOfRange(8, 16),
|
|
|
|
|
key.toByteArray()
|
|
|
|
|
), input
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun generateKey(salt: ByteArray, secret: ByteArray): ByteArray {
|
|
|
|
|
var key = md5(secret + salt)
|
|
|
|
|
var currentKey = key
|
|
|
|
|
while (currentKey.size < 48) {
|
|
|
|
|
key = md5(key + secret + salt)
|
|
|
|
|
currentKey += key
|
|
|
|
|
}
|
|
|
|
|
return currentKey
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun md5(input: ByteArray): ByteArray {
|
|
|
|
|
return MessageDigest.getInstance("MD5").digest(input)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun decryptSourceUrl(decryptionKey: ByteArray, sourceUrl: String): String {
|
|
|
|
|
val cipherData = base64DecodeArray(sourceUrl)
|
|
|
|
|
val encrypted = cipherData.copyOfRange(16, cipherData.size)
|
|
|
|
|
val aesCBC = Cipher.getInstance("AES/CBC/PKCS5Padding")
|
|
|
|
|
|
|
|
|
|
Objects.requireNonNull(aesCBC).init(
|
|
|
|
|
Cipher.DECRYPT_MODE, SecretKeySpec(
|
|
|
|
|
decryptionKey.copyOfRange(0, 32),
|
|
|
|
|
"AES"
|
|
|
|
|
),
|
|
|
|
|
IvParameterSpec(decryptionKey.copyOfRange(32, decryptionKey.size))
|
|
|
|
|
)
|
|
|
|
|
val decryptedData = aesCBC!!.doFinal(encrypted)
|
|
|
|
|
return String(decryptedData, StandardCharsets.UTF_8)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
data class PollingData(
|
|
|
|
|
@JsonProperty("sid") val sid: String? = null,
|
|
|
|
|
@JsonProperty("upgrades") val upgrades: ArrayList<String> = arrayListOf(),
|
|
|
|
|
@JsonProperty("pingInterval") val pingInterval: Int? = null,
|
|
|
|
|
@JsonProperty("pingTimeout") val pingTimeout: Int? = null
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
data class Tracks(
|
|
|
|
|
@JsonProperty("file") val file: String?,
|
|
|
|
|
@JsonProperty("label") val label: String?,
|
|
|
|
|
@JsonProperty("kind") val kind: String?
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
data class Sources(
|
|
|
|
|
@JsonProperty("file") val file: String?,
|
|
|
|
|
@JsonProperty("type") val type: String?,
|
|
|
|
|
@JsonProperty("label") val label: String?
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
data class SourceObject(
|
|
|
|
|
@JsonProperty("sources") val sources: List<Sources?>? = null,
|
|
|
|
|
@JsonProperty("sources_1") val sources1: List<Sources?>? = null,
|
|
|
|
|
@JsonProperty("sources_2") val sources2: List<Sources?>? = null,
|
|
|
|
|
@JsonProperty("sourcesBackup") val sourcesBackup: List<Sources?>? = null,
|
|
|
|
|
@JsonProperty("tracks") val tracks: List<Tracks?>? = null
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
data class SourceObjectEncrypted(
|
|
|
|
|
@JsonProperty("sources") val sources: String?,
|
|
|
|
|
@JsonProperty("encrypted") val encrypted: Boolean?,
|
|
|
|
|
@JsonProperty("sources_1") val sources1: String?,
|
|
|
|
|
@JsonProperty("sources_2") val sources2: String?,
|
|
|
|
|
@JsonProperty("sourcesBackup") val sourcesBackup: String?,
|
|
|
|
|
@JsonProperty("tracks") val tracks: List<Tracks?>?
|
|
|
|
|
)
|
|
|
|
|
|
2022-12-02 13:00:44 +00:00
|
|
|
|
}
|