2022-12-02 13:00:44 +00:00
package com.hexated
2023-02-04 10:58:06 +00:00
import android.util.Base64
2023-06-28 11:11:42 +00:00
import com.hexated.DumpUtils.queryApi
2023-05-22 15:46:24 +00:00
import com.hexated.SoraStream.Companion.anilistAPI
2023-05-21 13:20:19 +00:00
import com.hexated.SoraStream.Companion.crunchyrollAPI
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-09-15 14:24:05 +00:00
import com.hexated.SoraStream.Companion.hdmovies4uAPI
2023-05-29 03:35:47 +00:00
import com.hexated.SoraStream.Companion.malsyncAPI
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-14 17:33:43 +00:00
import com.hexated.SoraStream.Companion.watchOnlineAPI
2023-09-15 14:24:05 +00:00
import com.hexated.SoraStream.Companion.watchflxAPI
2023-09-14 18:39:07 +00:00
import com.hexated.SoraStream.Companion.watchhubApi
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-06-22 01:26:45 +00:00
import com.lagradost.cloudstream3.APIHolder.unixTimeMS
2023-09-21 07:43:38 +00:00
import com.lagradost.cloudstream3.mvvm.logError
2023-01-21 06:25:04 +00:00
import com.lagradost.cloudstream3.utils.*
2023-05-21 13:20:19 +00:00
import com.lagradost.cloudstream3.utils.AppUtils.toJson
2022-12-07 19:17:24 +00:00
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
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
2023-05-21 13:20:19 +00:00
import com.lagradost.nicehttp.requestCreator
2022-12-15 17:30:38 +00:00
import kotlinx.coroutines.delay
2022-12-07 15:06:48 +00:00
import okhttp3.FormBody
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
2022-12-10 12:25:28 +00:00
import org.jsoup.nodes.Document
2023-06-26 05:11:27 +00:00
import java.math.BigInteger
2023-05-21 13:20:19 +00:00
import java.net.*
2023-06-26 05:11:27 +00:00
import java.security.*
import java.security.spec.PKCS8EncodedKeySpec
import java.security.spec.X509EncodedKeySpec
2023-06-22 01:26:45 +00:00
import java.text.SimpleDateFormat
2023-02-04 10:58:06 +00:00
import java.util.*
import javax.crypto.Cipher
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
import kotlin.collections.ArrayList
2023-09-09 12:16:04 +00:00
import kotlin.math.min
2022-12-02 13:00:44 +00:00
2023-09-15 14:24:05 +00:00
var watchflxCookies : Map < String , String > ? = null
var filmxyCookies : Map < String , String > ? = null
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 "
)
2023-07-04 12:57:53 +00:00
fun String . filterIframe (
seasonNum : Int ? = null ,
lastSeason : Int ? = null ,
year : Int ? ,
title : String ?
) : Boolean {
2023-02-08 04:50:33 +00:00
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
}
2023-09-11 11:34:11 +00:00
suspend fun extractInstantUHD ( url : String ) : String ? {
val host = getBaseUrl ( url )
val body = FormBody . Builder ( )
. addEncoded ( " keys " , url . substringAfter ( " url= " ) )
. build ( )
return app . post (
" $host /api " , requestBody = body , headers = mapOf (
2023-09-11 15:56:36 +00:00
" x-token " to URI ( url ) . host
2023-09-11 11:34:11 +00:00
) , referer = " $host / "
) . parsedSafe < Map < String , String > > ( ) ?. get ( " url " )
}
2023-08-18 20:48:57 +00:00
suspend fun extractDirectUHD ( url : String , niceResponse : NiceResponse ) : String ? {
val document = niceResponse . document
val script = document . selectFirst ( " script:containsData(cf_token) " ) ?. data ( ) ?: return null
val actionToken = script . substringAfter ( " \" key \" , \" " ) . substringBefore ( " \" ); " )
val cfToken = script . substringAfter ( " cf_token = \" " ) . substringBefore ( " \" ; " )
val body = FormBody . Builder ( )
. addEncoded ( " action " , " direct " )
. addEncoded ( " key " , actionToken )
. addEncoded ( " action_token " , cfToken )
. build ( )
val cookies = mapOf ( " PHPSESSID " to " ${niceResponse.cookies["PHPSESSID"]} " )
val direct = app . post (
url ,
requestBody = body ,
cookies = cookies ,
referer = url ,
headers = mapOf (
" x-token " to " driveleech.org "
)
) . parsedSafe < Map < String , String > > ( ) ?. get ( " url " )
return app . get (
direct ?: return null , cookies = cookies ,
referer = url
) . text . substringAfter ( " worker_url = ' " ) . substringBefore ( " '; " )
}
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 =
2023-07-04 12:57:53 +00:00
if ( ! url . contains ( " gdflix " ) ) app . get ( url ) . document . selectFirst ( " li.flex.flex-col.py-6 a:contains(GDFlix Direct) " )
?. attr ( " href " ) ?: return null else url
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-05-19 23:35:52 +00:00
suspend fun invokeSmashyFfix (
2023-02-18 14:56:08 +00:00
name : String ,
url : String ,
2023-05-30 14:53:48 +00:00
ref : String ,
2023-02-18 14:56:08 +00:00
callback : ( ExtractorLink ) -> Unit ,
) {
2023-08-18 20:48:57 +00:00
val res = app . get ( url , referer = ref ) . text
val source = Regex ( " [' \" ]?file[' \" ]?: \\ s* \" ([^ \" ]+) " ) . find ( res ) ?. groupValues ?. get ( 1 ) ?: return
2023-02-18 14:56:08 +00:00
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 ] " ,
2023-08-06 01:10:12 +00:00
decode ( link ) . replace ( " \\ / " , " / " ) ,
2023-02-18 14:56:08 +00:00
smashyStreamAPI ,
quality ?. toIntOrNull ( ) ?: return @map ,
isM3u8 = link . contains ( " .m3u8 " ) ,
)
)
}
}
2023-09-15 14:24:05 +00:00
suspend fun invokeSmashyFm (
2023-07-21 09:55:03 +00:00
name : String ,
url : String ,
2023-09-15 14:24:05 +00:00
ref : String ,
2023-07-21 09:55:03 +00:00
callback : ( ExtractorLink ) -> Unit ,
) {
2023-09-15 14:24:05 +00:00
fun String . removeProxy ( ) : String {
return if ( this . contains ( " proxy " ) ) {
" https ${this.substringAfterLast("https")} "
} else {
this
}
2023-07-21 09:55:03 +00:00
}
2023-09-15 14:24:05 +00:00
val res = app . get ( url , referer = ref ) . text
val source = Regex ( " [' \" ]?file[' \" ]?: \\ s* \" ([^ \" ]+) " ) . find ( res ) ?. groupValues ?. get ( 1 ) ?: return
2023-07-26 07:31:20 +00:00
M3u8Helper . generateM3u8 (
" Smashy [ $name ] " ,
2023-09-15 14:24:05 +00:00
source . removeProxy ( ) ,
2023-09-20 13:03:43 +00:00
" https://vidplay.site/ "
2023-07-26 07:31:20 +00:00
) . forEach ( callback )
}
2023-06-26 05:11:27 +00:00
suspend fun getDumpIdAndType ( title : String ? , year : Int ? , season : Int ? ) : Pair < String ? , Int ? > {
2023-06-28 11:11:42 +00:00
val res = tryParseJson < DumpQuickSearchData > (
queryApi (
" POST " ,
" ${BuildConfig.DUMP_API} /search/searchWithKeyWord " ,
mapOf (
" searchKeyWord " to " $title " ,
" size " to " 50 " ,
)
)
) ?. searchResults
2023-02-24 21:32:34 +00:00
2023-06-26 05:11:27 +00:00
val media = if ( res ?. size == 1 ) {
res . firstOrNull ( )
2023-02-24 21:32:34 +00:00
} else {
2023-06-26 05:11:27 +00:00
res ?. find {
2023-02-24 21:32:34 +00:00
when ( season ) {
null -> {
2023-06-26 05:11:27 +00:00
it . name . equals (
2023-02-24 21:32:34 +00:00
title ,
true
2023-06-26 05:11:27 +00:00
) && it . releaseTime == " $year " && it . domainType == 0
2023-02-24 21:32:34 +00:00
}
1 -> {
2023-06-26 05:11:27 +00:00
it . name ?. contains (
2023-02-24 21:32:34 +00:00
" $title " ,
true
2023-07-04 12:57:53 +00:00
) == true && ( it . releaseTime == " $year " || it . name . contains (
" Season $season " ,
true
) ) && it . domainType == 1
2023-02-24 21:32:34 +00:00
}
else -> {
2023-06-26 05:11:27 +00:00
it . name ?. contains ( Regex ( " (?i) $title \\ s?( $season | ${season.toRomanNumeral()} |Season \\ s $season ) " ) ) == true && it . releaseTime == " $year " && it . domainType == 1
2023-02-24 21:32:34 +00:00
}
}
}
}
2023-06-26 05:11:27 +00:00
return media ?. id to media ?. domainType
2023-02-24 21:32:34 +00:00
}
2023-06-26 05:11:27 +00:00
suspend fun fetchDumpEpisodes ( id : String , type : String , episode : Int ? ) : EpisodeVo ? {
2023-06-28 11:11:42 +00:00
return tryParseJson < DumpMediaDetail > (
queryApi (
" GET " ,
" ${BuildConfig.DUMP_API} /movieDrama/get " ,
mapOf (
" category " to type ,
" id " to id ,
)
)
) ?. episodeVo ?. find {
2023-02-24 21:32:34 +00:00
it . seriesNo == ( episode ?: 0 )
}
}
2023-09-15 14:24:05 +00:00
suspend fun invokeDrivetot (
url : String ,
tags : String ? = null ,
size : String ? = null ,
subtitleCallback : ( SubtitleFile ) -> Unit ,
callback : ( ExtractorLink ) -> Unit ,
) {
val res = app . get ( url )
val data = res . document . select ( " form input " ) . associate { it . attr ( " name " ) to it . attr ( " value " ) }
app . post ( res . url , data = data , cookies = res . cookies ) . document . select ( " div.card-body a " ) . apmap { ele ->
val href = base64Decode ( ele . attr ( " href " ) . substringAfterLast ( " / " ) ) . let {
if ( it . contains ( " hubcloud.lol " ) ) it . replace ( " hubcloud.lol " , " hubcloud.in " ) else it
}
loadExtractor ( href , " $hdmovies4uAPI / " , subtitleCallback ) { link ->
callback . invoke (
ExtractorLink (
link . source ,
" ${link.name} $tags [ $size ] " ,
link . url ,
link . referer ,
link . quality ,
link . type ,
link . headers ,
link . extractorData
)
)
}
}
}
suspend fun bypassBqrecipes ( url : String ) : String ? {
var res = app . get ( url )
var location = res . text . substringAfter ( " .replace(' " ) . substringBefore ( " '); " )
var cookies = res . cookies
res = app . get ( location , cookies = cookies )
cookies = cookies + res . cookies
val document = res . document
location = document . select ( " form#recaptcha " ) . attr ( " action " )
val data =
document . select ( " form#recaptcha input " ) . associate { it . attr ( " name " ) to it . attr ( " value " ) }
res = app . post ( location , data = data , cookies = cookies )
location = res . document . selectFirst ( " a#messagedown " ) ?. attr ( " href " ) ?: return null
cookies = ( cookies + res . cookies ) . minus ( " var " )
return app . get ( location , cookies = cookies , allowRedirects = false ) . headers [ " location " ]
}
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 " ]
}
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-06-09 18:44:28 +00:00
val techRes = app . get ( url ) . document
2023-03-30 05:21:25 +00:00
val postUrl = url . substringBefore ( " ?id= " ) . substringAfter ( " /? " )
2023-06-09 18:44:28 +00:00
val ( goUrl , goHeader ) = if ( techRes . selectFirst ( " form#landing input[name=_wp_http_c] " ) != null ) {
var res = app . post (
postUrl , data = mapOf (
" _wp_http_c " to url . substringAfter ( " ?id= " )
)
2023-03-30 05:21:25 +00:00
)
2023-06-09 18:44:28 +00:00
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 " )
Pair ( tokenUrl , headers )
} else {
val secondPage = techRes . getNextTechPage ( ) . document
val thirdPage = secondPage . getNextTechPage ( ) . text
val goToken = thirdPage . substringAfter ( " ?go= " ) . substringBefore ( " \" " )
val tokenUrl = " $postUrl ?go= $goToken "
2023-07-04 12:57:53 +00:00
val headers = mapOf (
" Cookie " to " $goToken = $ {
secondPage . select ( " form#landing input[name=_wp_http2] " ) . attr ( " value " )
} "
)
2023-06-09 18:44:28 +00:00
Pair ( tokenUrl , headers )
}
2023-04-19 09:52:31 +00:00
val driveUrl =
2023-06-09 18:44:28 +00:00
app . get ( goUrl , headers = goHeader ) . document . selectFirst ( " meta[http-equiv=refresh] " )
2023-04-19 09:52:31 +00:00
?. 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-06-09 18:44:28 +00:00
private suspend fun Document . getNextTechPage ( ) : NiceResponse {
return app . post (
this . select ( " form " ) . attr ( " action " ) ,
data = this . select ( " form input " ) . mapNotNull {
it . attr ( " name " ) to it . attr ( " value " )
} . toMap ( ) . toMutableMap ( )
)
}
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 ( )
}
}
2023-08-19 02:38:32 +00:00
suspend fun getFilmxyCookies ( url : String ) = filmxyCookies ?: fetchFilmxyCookies ( url ) . also { filmxyCookies = it }
suspend fun fetchFilmxyCookies ( url : String ) : Map < String , String > {
2022-12-02 13:00:44 +00:00
2023-08-19 02:38:32 +00:00
val defaultCookies = mutableMapOf ( " G_ENABLED_IDPS " to " google " , " true_checker " to " 1 " , " XID " to " 1 " )
session . get (
2022-12-02 13:00:44 +00:00
url ,
headers = mapOf (
" Accept " to " text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8 "
) ,
2023-08-19 02:38:32 +00:00
cookies = defaultCookies ,
2022-12-02 13:00:44 +00:00
)
2023-08-19 02:38:32 +00:00
val phpsessid = session . baseClient . cookieJar . loadForRequest ( url . toHttpUrl ( ) )
. first { it . name == " PHPSESSID " } . value
defaultCookies [ " PHPSESSID " ] = phpsessid
2022-12-02 13:00:44 +00:00
val userNonce =
2023-08-19 02:38:32 +00:00
app . get ( " $filmxyAPI /login/?redirect_to= $filmxyAPI / " , cookies = defaultCookies ) . document . select ( " script " )
. find { it . data ( ) . contains ( " var userNonce " ) } ?. data ( ) ?. let {
Regex ( " var \\ suserNonce.*? \" ( \\ S+?) \" ; " ) . find ( it ) ?. groupValues ?. get ( 1 )
}
2022-12-02 13:00:44 +00:00
2023-08-19 02:38:32 +00:00
val cookieUrl = " ${filmxyAPI} /wp-admin/admin-ajax.php "
2022-12-02 13:00:44 +00:00
session . post (
cookieUrl ,
data = mapOf (
" action " to " guest_login " ,
" nonce " to " $userNonce " ,
) ,
headers = mapOf (
" X-Requested-With " to " XMLHttpRequest " ,
2023-08-19 02:38:32 +00:00
) ,
cookies = defaultCookies
2022-12-02 13:00:44 +00:00
)
2023-08-19 02:38:32 +00:00
val cookieJar = session . baseClient . cookieJar . loadForRequest ( cookieUrl . toHttpUrl ( ) )
. associate { it . name to it . value } . toMutableMap ( )
return cookieJar . plus ( defaultCookies )
2022-12-02 13:00:44 +00:00
}
2023-09-15 14:24:05 +00:00
suspend fun getWatchflxCookies ( ) = watchflxCookies ?: fetchWatchflxCookies ( ) . also { watchflxCookies = it }
suspend fun fetchWatchflxCookies ( ) : Map < String , String > {
session . get ( watchflxAPI )
val cookies = session . baseClient . cookieJar . loadForRequest ( watchflxAPI . toHttpUrl ( ) )
. associate { it . name to it . value }
val loginUrl = " $watchflxAPI /cookie-based-login "
session . post (
loginUrl , data = mapOf (
" continue_as_temp " to " true "
) , cookies = cookies , headers = mapOf ( " X-Requested-With " to " XMLHttpRequest " )
)
return session . baseClient . cookieJar . loadForRequest ( loginUrl . toHttpUrl ( ) ) . associate { it . name to it . value }
}
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-05-21 13:20:19 +00:00
//modified code from https://github.com/jmir1/aniyomi-extensions/blob/master/src/all/kamyroll/src/eu/kanade/tachiyomi/animeextension/all/kamyroll/AccessTokenInterceptor.kt
2023-07-23 18:05:02 +00:00
suspend fun getCrunchyrollToken ( ) : Map < String , String > {
2023-05-21 13:20:19 +00:00
val client = app . baseClient . newBuilder ( )
. proxy ( Proxy ( Proxy . Type . SOCKS , InetSocketAddress ( " cr-unblocker.us.to " , 1080 ) ) )
. build ( )
Authenticator . setDefault ( object : Authenticator ( ) {
override fun getPasswordAuthentication ( ) : PasswordAuthentication {
return PasswordAuthentication ( " crunblocker " , " crunblocker " . toCharArray ( ) )
2023-01-20 00:33:01 +00:00
}
2023-05-21 13:20:19 +00:00
} )
val request = requestCreator (
method = " POST " ,
url = " $crunchyrollAPI /auth/v1/token " ,
headers = mapOf (
" User-Agent " to " Crunchyroll/3.26.1 Android/11 okhttp/4.9.2 " ,
" Content-Type " to " application/x-www-form-urlencoded " ,
" Authorization " to " Basic ${BuildConfig.CRUNCHYROLL_BASIC_TOKEN} "
) ,
data = mapOf (
2023-07-23 18:05:02 +00:00
" refresh_token " to app . get ( BuildConfig . CRUNCHYROLL _REFRESH _TOKEN ) . text ,
2023-05-21 13:20:19 +00:00
" grant_type " to " refresh_token " ,
" scope " to " offline_access "
)
)
val response = tryParseJson < CrunchyrollToken > ( client . newCall ( request ) . execute ( ) . body . string ( ) )
return mapOf ( " Authorization " to " ${response?.tokenType} ${response?.accessToken} " )
2023-01-20 00:33:01 +00:00
2023-01-19 02:51:04 +00:00
}
2023-05-21 13:20:19 +00:00
suspend fun getCrunchyrollId ( aniId : String ? ) : String ? {
val query = """
query media ( $ { '$' } id : Int , $ { '$' } type : MediaType , $ { '$' } isAdult : Boolean ) {
Media ( id : $ { '$' } id , type : $ { '$' } type , isAdult : $ { '$' } isAdult ) {
id
externalLinks {
id
site
url
type
}
}
}
""" .trimIndent().trim()
val variables = mapOf (
" id " to aniId ,
" isAdult " to false ,
" type " to " ANIME " ,
)
val data = mapOf (
" query " to query ,
" variables " to variables
) . toJson ( ) . toRequestBody ( RequestBodyTypes . JSON . toMediaTypeOrNull ( ) )
2023-05-22 15:46:24 +00:00
val externalLinks = app . post ( anilistAPI , requestBody = data )
2023-05-21 16:49:00 +00:00
. parsedSafe < AnilistResponses > ( ) ?. data ?. Media ?. externalLinks
2023-05-22 15:46:24 +00:00
return ( externalLinks ?. find { it . site == " VRV " }
?: externalLinks ?. find { it . site == " Crunchyroll " } ) ?. url ?. let {
2023-05-21 16:49:00 +00:00
Regex ( " series/( \\ w+)/? " ) . find ( it ) ?. groupValues ?. get ( 1 )
}
2023-01-19 02:51:04 +00:00
}
2023-05-29 03:34:57 +00:00
suspend fun getCrunchyrollIdFromMalSync ( aniId : String ? ) : String ? {
val res = app . get ( " $malsyncAPI /mal/anime/ $aniId " ) . parsedSafe < MalSyncRes > ( ) ?. Sites
val vrv = res ?. get ( " Vrv " ) ?. map { it . value } ?. firstOrNull ( ) ?. get ( " url " )
val crunchyroll = res ?. get ( " Vrv " ) ?. map { it . value } ?. firstOrNull ( ) ?. get ( " url " )
val regex = Regex ( " series/( \\ w+)/? " )
2023-06-09 18:44:28 +00:00
return regex . find ( " $vrv " ) ?. groupValues ?. getOrNull ( 1 )
?: regex . find ( " $crunchyroll " ) ?. groupValues ?. getOrNull ( 1 )
2023-05-29 03:34:57 +00:00
}
2023-05-22 15:46:24 +00:00
suspend fun convertTmdbToAnimeId (
title : String ? ,
date : String ? ,
airedDate : String ? ,
type : TvType
) : AniIds {
val sDate = date ?. split ( " - " )
val sAiredDate = airedDate ?. split ( " - " )
val year = sDate ?. firstOrNull ( ) ?. toIntOrNull ( )
val airedYear = sAiredDate ?. firstOrNull ( ) ?. toIntOrNull ( )
val season = getSeason ( sDate ?. get ( 1 ) ?. toIntOrNull ( ) )
val airedSeason = getSeason ( sAiredDate ?. get ( 1 ) ?. toIntOrNull ( ) )
return if ( type == TvType . AnimeMovie ) {
tmdbToAnimeId ( title , airedYear , " " , type )
} else {
val ids = tmdbToAnimeId ( title , year , season , type )
if ( ids . id == null && ids . idMal == null ) tmdbToAnimeId (
title ,
airedYear ,
airedSeason ,
type
) else ids
}
}
suspend fun tmdbToAnimeId ( title : String ? , year : Int ? , season : String ? , type : TvType ) : AniIds {
val query = """
query (
$ { '$' } page : Int = 1
$ { '$' } search : String
$ { '$' } sort : [ MediaSort ] = [ POPULARITY _DESC , SCORE _DESC ]
$ { '$' } type : MediaType
$ { '$' } season : MediaSeason
$ { '$' } seasonYear : Int
$ { '$' } format : [ MediaFormat ]
) {
Page ( page : $ { '$' } page , perPage : 20 ) {
media (
search : $ { '$' } search
sort : $ { '$' } sort
type : $ { '$' } type
season : $ { '$' } season
seasonYear : $ { '$' } seasonYear
format _in : $ { '$' } format
) {
id
idMal
}
}
}
""" .trimIndent().trim()
val variables = mapOf (
" search " to title ,
" sort " to " SEARCH_MATCH " ,
" type " to " ANIME " ,
" season " to season ?. uppercase ( ) ,
" seasonYear " to year ,
" format " to listOf ( if ( type == TvType . AnimeMovie ) " MOVIE " else " TV " )
) . filterValues { value -> value != null && value . toString ( ) . isNotEmpty ( ) }
val data = mapOf (
" query " to query ,
" variables " to variables
) . toJson ( ) . toRequestBody ( RequestBodyTypes . JSON . toMediaTypeOrNull ( ) )
val res = app . post ( anilistAPI , requestBody = data )
. parsedSafe < AniSearch > ( ) ?. data ?. Page ?. media ?. firstOrNull ( )
return AniIds ( res ?. id , res ?. idMal )
}
2023-09-14 18:39:07 +00:00
suspend fun imdbToNetflixId ( imdbId : String ? , season : Int ? ) : String ? {
val url = if ( season == null ) {
" $watchhubApi /stream/movie/ $imdbId .json "
} else {
" $watchhubApi /stream/series/ $imdbId :1:1.json "
}
return app . get ( url )
. parsedSafe < WatchhubResponse > ( ) ?. streams ?. find { it . name == " Netflix " } ?. externalUrl
?. substringAfterLast ( " / " )
}
2023-09-18 07:36:12 +00:00
fun generateWpKey ( r : String , m : String ) : String {
val rList = r . split ( " \\ x " ) . toTypedArray ( )
var n = " "
val decodedM = String ( base64Decode ( m . split ( " " ) . reversed ( ) . joinToString ( " " ) ) . toCharArray ( ) )
for ( s in decodedM . split ( " | " ) ) {
n += " \\ x " + rList [ Integer . parseInt ( s ) + 1 ]
}
return n
}
2023-09-20 13:03:43 +00:00
suspend fun loadCustomTagExtractor (
tag : String ? = null ,
url : String ,
referer : String ? = null ,
subtitleCallback : ( SubtitleFile ) -> Unit ,
callback : ( ExtractorLink ) -> Unit ,
quality : Int ? = null ,
) {
loadExtractor ( url , referer , subtitleCallback ) { link ->
callback . invoke (
ExtractorLink (
link . source ,
" ${link.name} $tag " ,
link . url ,
link . referer ,
when ( link . type ) {
ExtractorLinkType . M3U8 -> link . quality
else -> quality ?: link . quality
} ,
link . type ,
link . headers ,
link . extractorData
)
)
}
}
2023-08-05 09:50:35 +00:00
suspend fun loadCustomExtractor (
2023-08-05 18:59:19 +00:00
name : String ? = null ,
2023-08-05 09:50:35 +00:00
url : String ,
referer : String ? = null ,
subtitleCallback : ( SubtitleFile ) -> Unit ,
callback : ( ExtractorLink ) -> Unit ,
quality : Int ? = null ,
) {
loadExtractor ( url , referer , subtitleCallback ) { link ->
callback . invoke (
ExtractorLink (
2023-08-05 18:59:19 +00:00
name ?: link . source ,
name ?: link . name ,
2023-08-05 09:50:35 +00:00
link . url ,
link . referer ,
2023-09-14 18:39:07 +00:00
when ( link . type ) {
ExtractorLinkType . M3U8 -> link . quality
2023-08-05 09:50:35 +00:00
else -> quality ?: link . quality
} ,
2023-09-09 12:16:04 +00:00
link . type ,
2023-08-05 09:50:35 +00:00
link . headers ,
link . extractorData
)
)
}
}
2023-05-22 15:46:24 +00:00
fun getSeason ( month : Int ? ) : String ? {
val seasons = arrayOf (
" Winter " , " Winter " , " Spring " , " Spring " , " Spring " , " Summer " ,
" Summer " , " Summer " , " Fall " , " Fall " , " Fall " , " Winter "
)
2023-06-09 18:44:28 +00:00
if ( month == null ) return null
2023-05-22 15:46:24 +00:00
return seasons [ month - 1 ]
}
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-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-09-18 08:38:09 +00:00
fun String . decodePrimewireXor ( key : String ) : 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-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-07-04 12:57:53 +00:00
fun getUhdTags ( str : String ? ) : String {
return Regex ( " \\ d{3,4}[Pp] \\ .?(.*?) \\ [ " ) . find ( str ?: " " ) ?. groupValues ?. getOrNull ( 1 )
?. replace ( " . " , " " ) ?. trim ( )
?: str ?: " "
}
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
}
}
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-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-07-04 12:57:53 +00:00
fun isUpcoming ( dateString : String ? ) : Boolean {
2023-09-21 07:43:38 +00:00
return try {
val format = SimpleDateFormat ( " yyyy-MM-dd " , Locale . getDefault ( ) )
val dateTime = dateString ?. let { format . parse ( it ) ?. time } ?: return false
2023-09-21 07:50:29 +00:00
unixTimeMS < dateTime
2023-09-21 07:43:38 +00:00
} catch ( t : Throwable ) {
logError ( t )
false
}
2023-06-22 01:26:45 +00:00
}
2023-07-04 12:57:53 +00:00
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
}
2023-06-26 05:11:27 +00:00
object DumpUtils {
private val deviceId = getDeviceId ( )
2023-06-28 11:11:42 +00:00
suspend fun queryApi ( method : String , url : String , params : Map < String , String > ) : String {
return app . custom (
method ,
url ,
2023-07-04 12:57:53 +00:00
requestBody = if ( method == " POST " ) params . toJson ( )
. toRequestBody ( RequestBodyTypes . JSON . toMediaTypeOrNull ( ) ) else null ,
params = if ( method == " GET " ) params else emptyMap ( ) ,
2023-06-28 11:11:42 +00:00
headers = createHeaders ( params )
) . parsedSafe < HashMap < String , String > > ( ) ?. get ( " data " ) . let {
cryptoHandler (
it . toString ( ) ,
deviceId ,
false
)
}
}
private fun createHeaders (
2023-06-26 05:11:27 +00:00
params : Map < String , String > ,
currentTime : String = System . currentTimeMillis ( ) . toString ( ) ,
) : Map < String , String > {
return mapOf (
" lang " to " en " ,
" currentTime " to currentTime ,
2023-06-28 11:11:42 +00:00
" sign " to getSign ( currentTime , params ) . toString ( ) ,
" aesKey " to getAesKey ( ) . toString ( ) ,
2023-06-26 05:11:27 +00:00
)
}
private fun cryptoHandler (
string : String ,
secretKeyString : String ,
encrypt : Boolean = true
) : String {
val secretKey = SecretKeySpec ( secretKeyString . toByteArray ( ) , " AES " )
val cipher = Cipher . getInstance ( " AES/ECB/PKCS5PADDING " )
return if ( ! encrypt ) {
cipher . init ( Cipher . DECRYPT _MODE , secretKey )
String ( cipher . doFinal ( base64DecodeArray ( string ) ) )
} else {
cipher . init ( Cipher . ENCRYPT _MODE , secretKey )
base64Encode ( cipher . doFinal ( string . toByteArray ( ) ) )
}
}
2023-06-28 11:11:42 +00:00
private fun getAesKey ( ) : String ? {
2023-07-04 12:57:53 +00:00
val publicKey =
RSAEncryptionHelper . getPublicKeyFromString ( BuildConfig . DUMP _KEY ) ?: return null
2023-06-26 05:11:27 +00:00
return RSAEncryptionHelper . encryptText ( deviceId , publicKey )
}
2023-06-28 11:11:42 +00:00
private fun getSign ( currentTime : String , params : Map < String , String > ) : String ? {
val chipper = listOf (
currentTime ,
params . map { it . value } . reversed ( ) . joinToString ( " " )
. let { base64Encode ( it . toByteArray ( ) ) } ) . joinToString ( " " )
2023-06-26 05:11:27 +00:00
val enc = cryptoHandler ( chipper , deviceId )
return md5 ( enc )
}
private fun md5 ( input : String ) : String {
val md = MessageDigest . getInstance ( " MD5 " )
return BigInteger ( 1 , md . digest ( input . toByteArray ( ) ) ) . toString ( 16 ) . padStart ( 32 , '0' )
}
}
object RSAEncryptionHelper {
private const val RSA _ALGORITHM = " RSA "
private const val CIPHER _TYPE _FOR _RSA = " RSA/ECB/PKCS1Padding "
private val keyFactory = KeyFactory . getInstance ( RSA _ALGORITHM )
private val cipher = Cipher . getInstance ( CIPHER _TYPE _FOR _RSA )
fun getPublicKeyFromString ( publicKeyString : String ) : PublicKey ? =
try {
val keySpec =
X509EncodedKeySpec ( Base64 . decode ( publicKeyString . toByteArray ( ) , Base64 . NO _WRAP ) )
keyFactory . generatePublic ( keySpec )
} catch ( exception : Exception ) {
exception . printStackTrace ( )
null
}
fun getPrivateKeyFromString ( privateKeyString : String ) : PrivateKey ? =
try {
val keySpec =
PKCS8EncodedKeySpec ( Base64 . decode ( privateKeyString . toByteArray ( ) , Base64 . DEFAULT ) )
keyFactory . generatePrivate ( keySpec )
} catch ( exception : Exception ) {
exception . printStackTrace ( )
null
}
fun encryptText ( plainText : String , publicKey : PublicKey ) : String ? =
try {
cipher . init ( Cipher . ENCRYPT _MODE , publicKey )
Base64 . encodeToString ( cipher . doFinal ( plainText . toByteArray ( ) ) , Base64 . NO _WRAP )
} catch ( exception : Exception ) {
exception . printStackTrace ( )
null
}
fun decryptText ( encryptedText : String , privateKey : PrivateKey ) : String ? =
try {
cipher . init ( Cipher . DECRYPT _MODE , privateKey )
String ( cipher . doFinal ( Base64 . decode ( encryptedText , Base64 . DEFAULT ) ) )
} catch ( exception : Exception ) {
exception . printStackTrace ( )
null
}
2023-08-29 13:28:15 +00:00
}
2023-09-09 12:16:04 +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 CryptoJS {
private const val KEY _SIZE = 256
private const val IV _SIZE = 128
private const val HASH _CIPHER = " AES/CBC/PKCS7Padding "
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-08-29 13:28:15 +00:00
2023-09-09 12:16:04 +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 )
2023-08-29 13:28:15 +00:00
}
2023-09-09 12:16:04 +00:00
private fun EvpKDF (
2023-08-29 13:28:15 +00:00
password : ByteArray ,
2023-09-09 12:16:04 +00:00
keySize : Int ,
ivSize : Int ,
2023-08-29 13:28:15 +00:00
salt : ByteArray ,
2023-09-09 12:16:04 +00:00
resultKey : ByteArray ,
resultIv : ByteArray
) : ByteArray {
return EvpKDF ( password , keySize , ivSize , salt , 1 , KDF _DIGEST , resultKey , resultIv )
}
2023-08-29 13:28:15 +00:00
2023-09-09 12:16:04 +00:00
@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 )
2023-08-29 13:28:15 +00:00
}
2023-09-09 12:16:04 +00:00
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 )
2023-08-29 13:28:15 +00:00
)
2023-09-09 12:16:04 +00:00
numberOfDerivedWords += block . size / 4
2023-08-29 13:28:15 +00:00
}
2023-09-09 12:16:04 +00:00
System . arraycopy ( derivedBytes , 0 , resultKey , 0 , keySize * 4 )
System . arraycopy ( derivedBytes , keySize * 4 , resultIv , 0 , ivSize * 4 )
return derivedBytes // key + iv
2023-08-29 13:28:15 +00:00
}
2023-09-09 12:16:04 +00:00
private fun generateSalt ( length : Int ) : ByteArray {
return ByteArray ( length ) . apply {
SecureRandom ( ) . nextBytes ( this )
}
2023-08-29 13:28:15 +00:00
}
2022-12-02 13:00:44 +00:00
}