Advertisement
lu6id

Untitled

Nov 17th, 2024 (edited)
10
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 24.22 KB | None | 0 0
  1. With the below kotlin files, recreate the method to download mp4 video files from animepahe.ru in python. There are 4 files "AnimePaheDto.kt", "AnimePahe.kt", "DdosGaurdInterceptor", and "KwikExtractor.kt" and their code has been pasted accordingly.
  2.  
  3. ```AnimePaheDto.kt
  4. package eu.kanade.tachiyomi.animeextension.en.animepahe.dto
  5.  
  6. import kotlinx.serialization.EncodeDefault
  7. import kotlinx.serialization.SerialName
  8. import kotlinx.serialization.Serializable
  9.  
  10. @Serializable
  11. data class ResponseDto<T>(
  12. @SerialName("current_page")
  13. val currentPage: Int,
  14. @SerialName("last_page")
  15. val lastPage: Int,
  16. @EncodeDefault
  17. @SerialName("data")
  18. val items: List<T> = emptyList(),
  19. )
  20.  
  21. @Serializable
  22. data class LatestAnimeDto(
  23. @SerialName("anime_title")
  24. val title: String,
  25. val snapshot: String,
  26. @SerialName("anime_id")
  27. val id: Int,
  28. val fansub: String,
  29. )
  30.  
  31. @Serializable
  32. data class SearchResultDto(
  33. val title: String,
  34. val poster: String,
  35. val id: Int,
  36. )
  37.  
  38. @Serializable
  39. data class EpisodeDto(
  40. @SerialName("created_at")
  41. val createdAt: String,
  42. val session: String,
  43. @SerialName("episode")
  44. val episodeNumber: Float,
  45. )
  46. ```
  47. ```AnimePahe.kt
  48. package eu.kanade.tachiyomi.animeextension.en.animepahe
  49.  
  50. import android.app.Application
  51. import android.content.SharedPreferences
  52. import androidx.preference.ListPreference
  53. import androidx.preference.PreferenceScreen
  54. import androidx.preference.SwitchPreferenceCompat
  55. import eu.kanade.tachiyomi.animeextension.en.animepahe.dto.EpisodeDto
  56. import eu.kanade.tachiyomi.animeextension.en.animepahe.dto.LatestAnimeDto
  57. import eu.kanade.tachiyomi.animeextension.en.animepahe.dto.ResponseDto
  58. import eu.kanade.tachiyomi.animeextension.en.animepahe.dto.SearchResultDto
  59. import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
  60. import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
  61. import eu.kanade.tachiyomi.animesource.model.AnimesPage
  62. import eu.kanade.tachiyomi.animesource.model.SAnime
  63. import eu.kanade.tachiyomi.animesource.model.SEpisode
  64. import eu.kanade.tachiyomi.animesource.model.Video
  65. import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
  66. import eu.kanade.tachiyomi.network.GET
  67. import eu.kanade.tachiyomi.util.asJsoup
  68. import eu.kanade.tachiyomi.util.parseAs
  69. import kotlinx.coroutines.Dispatchers
  70. import kotlinx.coroutines.runBlocking
  71. import kotlinx.coroutines.withContext
  72. import kotlinx.serialization.json.Json
  73. import okhttp3.Headers
  74. import okhttp3.Request
  75. import okhttp3.Response
  76. import uy.kohesive.injekt.Injekt
  77. import uy.kohesive.injekt.api.get
  78. import java.text.SimpleDateFormat
  79. import java.util.Locale
  80. import kotlin.math.ceil
  81. import kotlin.math.floor
  82.  
  83. class AnimePahe : ConfigurableAnimeSource, AnimeHttpSource() {
  84.  
  85. private val preferences: SharedPreferences by lazy {
  86. Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
  87. }
  88.  
  89. private val interceptor = DdosGuardInterceptor(network.client)
  90.  
  91. override val client = network.client.newBuilder()
  92. .addInterceptor(interceptor)
  93. .build()
  94.  
  95. override val name = "AnimePahe"
  96.  
  97. override val baseUrl by lazy {
  98. preferences.getString(PREF_DOMAIN_KEY, PREF_DOMAIN_DEFAULT)!!
  99. }
  100.  
  101. override val lang = "en"
  102.  
  103. override val supportsLatest = true
  104.  
  105. private val json = Json {
  106. ignoreUnknownKeys = true
  107. }
  108.  
  109. // =========================== Anime Details ============================
  110. /**
  111. * This override is necessary because AnimePahe does not provide permanent
  112. * URLs to its animes, so we need to fetch the anime session every time.
  113. *
  114. * @see episodeListRequest
  115. */
  116. override fun animeDetailsRequest(anime: SAnime): Request {
  117. val animeId = anime.getId()
  118. // We're using coroutines here to run it inside another thread and
  119. // prevent android.os.NetworkOnMainThreadException when trying to open
  120. // webview or share it.
  121. val session = runBlocking {
  122. withContext(Dispatchers.IO) {
  123. fetchSession(anime.title, animeId)
  124. }
  125. }
  126. return GET("$baseUrl/anime/$session?anime_id=$animeId")
  127. }
  128.  
  129. override fun animeDetailsParse(response: Response): SAnime {
  130. val document = response.asJsoup()
  131. return SAnime.create().apply {
  132. title = document.selectFirst("div.title-wrapper > h1 > span")!!.text()
  133. author = document.selectFirst("div.col-sm-4.anime-info p:contains(Studio:)")
  134. ?.text()
  135. ?.replace("Studio: ", "")
  136. status = parseStatus(document.selectFirst("div.col-sm-4.anime-info p:contains(Status:) a")!!.text())
  137. thumbnail_url = document.selectFirst("div.anime-poster a")!!.attr("href")
  138. genre = document.select("div.anime-genre ul li").joinToString { it.text() }
  139. val synonyms = document.selectFirst("div.col-sm-4.anime-info p:contains(Synonyms:)")
  140. ?.text()
  141. description = document.select("div.anime-summary").text() +
  142. if (synonyms.isNullOrEmpty()) "" else "\n\n$synonyms"
  143. }
  144. }
  145.  
  146. // =============================== Latest ===============================
  147. override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/api?m=airing&page=$page")
  148.  
  149. override fun latestUpdatesParse(response: Response): AnimesPage {
  150. val latestData = response.parseAs<ResponseDto<LatestAnimeDto>>()
  151. val hasNextPage = latestData.currentPage < latestData.lastPage
  152. val animeList = latestData.items.map { anime ->
  153. SAnime.create().apply {
  154. title = anime.title
  155. thumbnail_url = anime.snapshot
  156. val animeId = anime.id
  157. setUrlWithoutDomain("/anime/?anime_id=$animeId")
  158. artist = anime.fansub
  159. }
  160. }
  161. return AnimesPage(animeList, hasNextPage)
  162. }
  163.  
  164. // =============================== Search ===============================
  165. override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request =
  166. GET("$baseUrl/api?m=search&l=8&q=$query")
  167.  
  168. override fun searchAnimeParse(response: Response): AnimesPage {
  169. val searchData = response.parseAs<ResponseDto<SearchResultDto>>()
  170. val animeList = searchData.items.map { anime ->
  171. SAnime.create().apply {
  172. title = anime.title
  173. thumbnail_url = anime.poster
  174. val animeId = anime.id
  175. setUrlWithoutDomain("/anime/?anime_id=$animeId")
  176. }
  177. }
  178. return AnimesPage(animeList, false)
  179. }
  180.  
  181. // ============================== Popular ===============================
  182. // This source doesnt have a popular animes page,
  183. // so we use latest animes page instead.
  184. override suspend fun getPopularAnime(page: Int) = getLatestUpdates(page)
  185. override fun popularAnimeParse(response: Response): AnimesPage = TODO()
  186. override fun popularAnimeRequest(page: Int): Request = TODO()
  187.  
  188. // ============================== Episodes ==============================
  189. /**
  190. * This override is necessary because AnimePahe does not provide permanent
  191. * URLs to its animes, so we need to fetch the anime session every time.
  192. *
  193. * @see animeDetailsRequest
  194. */
  195. override fun episodeListRequest(anime: SAnime): Request {
  196. val session = fetchSession(anime.title, anime.getId())
  197. return GET("$baseUrl/api?m=release&id=$session&sort=episode_desc&page=1")
  198. }
  199.  
  200. override fun episodeListParse(response: Response): List<SEpisode> {
  201. val url = response.request.url.toString()
  202. val session = url.substringAfter("&id=").substringBefore("&")
  203. return recursivePages(response, session)
  204. }
  205.  
  206. private fun parseEpisodePage(episodes: List<EpisodeDto>, animeSession: String): MutableList<SEpisode> {
  207. return episodes.map { episode ->
  208. SEpisode.create().apply {
  209. date_upload = episode.createdAt.toDate()
  210. val session = episode.session
  211. setUrlWithoutDomain("/play/$animeSession/$session")
  212. val epNum = episode.episodeNumber
  213. episode_number = epNum
  214. val epName = if (floor(epNum) == ceil(epNum)) {
  215. epNum.toInt().toString()
  216. } else {
  217. epNum.toString()
  218. }
  219. name = "Episode $epName"
  220. }
  221. }.toMutableList()
  222. }
  223.  
  224. private fun recursivePages(response: Response, animeSession: String): List<SEpisode> {
  225. val episodesData = response.parseAs<ResponseDto<EpisodeDto>>()
  226. val page = episodesData.currentPage
  227. val hasNextPage = page < episodesData.lastPage
  228. val returnList = parseEpisodePage(episodesData.items, animeSession)
  229. if (hasNextPage) {
  230. val nextPage = nextPageRequest(response.request.url.toString(), page + 1)
  231. returnList += recursivePages(nextPage, animeSession)
  232. }
  233. return returnList
  234. }
  235.  
  236. private fun nextPageRequest(url: String, page: Int): Response {
  237. val request = GET(url.substringBeforeLast("&page=") + "&page=$page")
  238. return client.newCall(request).execute()
  239. }
  240.  
  241. // ============================ Video Links =============================
  242. override fun videoListParse(response: Response): List<Video> {
  243. val document = response.asJsoup()
  244. val downloadLinks = document.select("div#pickDownload > a")
  245. return document.select("div#resolutionMenu > button").mapIndexed { index, btn ->
  246. val kwikLink = btn.attr("data-src")
  247. val quality = btn.text()
  248. val paheWinLink = downloadLinks[index].attr("href")
  249. getVideo(paheWinLink, kwikLink, quality)
  250. }
  251. }
  252.  
  253. private fun getVideo(paheUrl: String, kwikUrl: String, quality: String): Video {
  254. return if (preferences.getBoolean(PREF_LINK_TYPE_KEY, PREF_LINK_TYPE_DEFAULT)) {
  255. val videoUrl = KwikExtractor(client).getHlsStreamUrl(kwikUrl, referer = baseUrl)
  256. Video(
  257. videoUrl,
  258. quality,
  259. videoUrl,
  260. headers = Headers.headersOf("referer", "https://kwik.cx"),
  261. )
  262. } else {
  263. val videoUrl = KwikExtractor(client).getStreamUrlFromKwik(paheUrl)
  264. Video(videoUrl, quality, videoUrl)
  265. }
  266. }
  267.  
  268. override fun List<Video>.sort(): List<Video> {
  269. val subPreference = preferences.getString(PREF_SUB_KEY, PREF_SUB_DEFAULT)!!
  270. val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
  271. val shouldBeAv1 = preferences.getBoolean(PREF_AV1_KEY, PREF_AV1_DEFAULT)
  272. val shouldEndWithEng = subPreference == "eng"
  273.  
  274. return this.sortedWith(
  275. compareBy(
  276. { it.quality.contains(quality) },
  277. { Regex("""\beng\b""").containsMatchIn(it.quality.lowercase()) == shouldEndWithEng },
  278. { it.quality.lowercase().contains("av1") == shouldBeAv1 },
  279. ),
  280. ).reversed()
  281. }
  282.  
  283. // ============================== Settings ==============================
  284. override fun setupPreferenceScreen(screen: PreferenceScreen) {
  285. val videoQualityPref = ListPreference(screen.context).apply {
  286. key = PREF_QUALITY_KEY
  287. title = PREF_QUALITY_TITLE
  288. entries = PREF_QUALITY_ENTRIES
  289. entryValues = PREF_QUALITY_ENTRIES
  290. setDefaultValue(PREF_QUALITY_DEFAULT)
  291. summary = "%s"
  292.  
  293. setOnPreferenceChangeListener { _, newValue ->
  294. val selected = newValue as String
  295. val index = findIndexOfValue(selected)
  296. val entry = entryValues[index] as String
  297. preferences.edit().putString(key, entry).commit()
  298. }
  299. }
  300. val domainPref = ListPreference(screen.context).apply {
  301. key = PREF_DOMAIN_KEY
  302. title = PREF_DOMAIN_TITLE
  303. entries = PREF_DOMAIN_ENTRIES
  304. entryValues = PREF_DOMAIN_VALUES
  305. setDefaultValue(PREF_DOMAIN_DEFAULT)
  306. summary = "%s"
  307.  
  308. setOnPreferenceChangeListener { _, newValue ->
  309. val selected = newValue as String
  310. val index = findIndexOfValue(selected)
  311. val entry = entryValues[index] as String
  312. preferences.edit().putString(key, entry).commit()
  313. }
  314. }
  315. val subPref = ListPreference(screen.context).apply {
  316. key = PREF_SUB_KEY
  317. title = PREF_SUB_TITLE
  318. entries = PREF_SUB_ENTRIES
  319. entryValues = PREF_SUB_VALUES
  320. setDefaultValue(PREF_SUB_DEFAULT)
  321. summary = "%s"
  322.  
  323. setOnPreferenceChangeListener { _, newValue ->
  324. val selected = newValue as String
  325. val index = findIndexOfValue(selected)
  326. val entry = entryValues[index] as String
  327. preferences.edit().putString(key, entry).commit()
  328. }
  329. }
  330. val linkPref = SwitchPreferenceCompat(screen.context).apply {
  331. key = PREF_LINK_TYPE_KEY
  332. title = PREF_LINK_TYPE_TITLE
  333. summary = PREF_LINK_TYPE_SUMMARY
  334. setDefaultValue(PREF_LINK_TYPE_DEFAULT)
  335.  
  336. setOnPreferenceChangeListener { _, newValue ->
  337. val new = newValue as Boolean
  338. preferences.edit().putBoolean(key, new).commit()
  339. }
  340. }
  341. val av1Pref = SwitchPreferenceCompat(screen.context).apply {
  342. key = PREF_AV1_KEY
  343. title = PREF_AV1_TITLE
  344. summary = PREF_AV1_SUMMARY
  345. setDefaultValue(PREF_AV1_DEFAULT)
  346.  
  347. setOnPreferenceChangeListener { _, newValue ->
  348. val new = newValue as Boolean
  349. preferences.edit().putBoolean(key, new).commit()
  350. }
  351. }
  352. screen.addPreference(videoQualityPref)
  353. screen.addPreference(domainPref)
  354. screen.addPreference(subPref)
  355. screen.addPreference(linkPref)
  356. screen.addPreference(av1Pref)
  357. }
  358.  
  359. // ============================= Utilities ==============================
  360. private fun fetchSession(title: String, animeId: String): String {
  361. return client.newCall(GET("$baseUrl/api?m=search&q=$title"))
  362. .execute()
  363. .body.string()
  364. .substringAfter("\"id\":$animeId")
  365. .substringAfter("\"session\":\"")
  366. .substringBefore("\"")
  367. }
  368.  
  369. private fun parseStatus(statusString: String): Int {
  370. return when (statusString) {
  371. "Currently Airing" -> SAnime.ONGOING
  372. "Finished Airing" -> SAnime.COMPLETED
  373. else -> SAnime.UNKNOWN
  374. }
  375. }
  376.  
  377. private fun SAnime.getId() = url.substringAfterLast("?anime_id=").substringBefore("\"")
  378.  
  379. private fun String.toDate(): Long {
  380. return runCatching {
  381. DATE_FORMATTER.parse(this)?.time ?: 0L
  382. }.getOrNull() ?: 0L
  383. }
  384.  
  385. companion object {
  386. private val DATE_FORMATTER by lazy {
  387. SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ENGLISH)
  388. }
  389.  
  390. private const val PREF_QUALITY_KEY = "preffered_quality"
  391. private const val PREF_QUALITY_TITLE = "Preferred quality"
  392. private const val PREF_QUALITY_DEFAULT = "1080p"
  393. private val PREF_QUALITY_ENTRIES = arrayOf("1080p", "720p", "360p")
  394.  
  395. private const val PREF_DOMAIN_KEY = "preffered_domain"
  396. private const val PREF_DOMAIN_TITLE = "Preferred domain (requires app restart)"
  397. private const val PREF_DOMAIN_DEFAULT = "https://animepahe.com"
  398. private val PREF_DOMAIN_ENTRIES = arrayOf("animepahe.com", "animepahe.ru", "animepahe.org")
  399. private val PREF_DOMAIN_VALUES by lazy {
  400. PREF_DOMAIN_ENTRIES.map { "https://" + it }.toTypedArray()
  401. }
  402.  
  403. private const val PREF_SUB_KEY = "preffered_sub"
  404. private const val PREF_SUB_TITLE = "Prefer subs or dubs?"
  405. private const val PREF_SUB_DEFAULT = "jpn"
  406. private val PREF_SUB_ENTRIES = arrayOf("sub", "dub")
  407. private val PREF_SUB_VALUES = arrayOf("jpn", "eng")
  408.  
  409. private const val PREF_LINK_TYPE_KEY = "preffered_link_type"
  410. private const val PREF_LINK_TYPE_TITLE = "Use HLS links"
  411. private const val PREF_LINK_TYPE_DEFAULT = false
  412. private val PREF_LINK_TYPE_SUMMARY by lazy {
  413. """Enable this if you are having Cloudflare issues.
  414. |Note that this will break the ability to seek inside of the video unless the episode is downloaded in advance.
  415. """.trimMargin()
  416. }
  417.  
  418. // Big slap to whoever misspelled `preferred`
  419. private const val PREF_AV1_KEY = "preffered_av1"
  420. private const val PREF_AV1_TITLE = "Use AV1 codec"
  421. private const val PREF_AV1_DEFAULT = false
  422. private val PREF_AV1_SUMMARY by lazy {
  423. """Enable to use AV1 if available
  424. |Turn off to never select av1 as preferred codec
  425. """.trimMargin()
  426. }
  427. }
  428. }
  429. ```
  430.  
  431. ```DDosInterceptor.kt
  432. package eu.kanade.tachiyomi.animeextension.en.animepahe
  433.  
  434. import android.webkit.CookieManager
  435. import eu.kanade.tachiyomi.network.GET
  436. import okhttp3.Cookie
  437. import okhttp3.HttpUrl
  438. import okhttp3.Interceptor
  439. import okhttp3.OkHttpClient
  440. import okhttp3.Response
  441.  
  442. class DdosGuardInterceptor(private val client: OkHttpClient) : Interceptor {
  443.  
  444. private val cookieManager by lazy { CookieManager.getInstance() }
  445.  
  446. override fun intercept(chain: Interceptor.Chain): Response {
  447. val originalRequest = chain.request()
  448. val response = chain.proceed(originalRequest)
  449.  
  450. // Check if DDos-GUARD is on
  451. if (response.code !in ERROR_CODES || response.header("Server") !in SERVER_CHECK) {
  452. return response
  453. }
  454.  
  455. response.close()
  456. val cookies = cookieManager.getCookie(originalRequest.url.toString())
  457. val oldCookie = if (cookies != null && cookies.isNotEmpty()) {
  458. cookies.split(";").mapNotNull { Cookie.parse(originalRequest.url, it) }
  459. } else {
  460. emptyList()
  461. }
  462. val ddg2Cookie = oldCookie.firstOrNull { it.name == "__ddg2_" }
  463. if (!ddg2Cookie?.value.isNullOrEmpty()) {
  464. return chain.proceed(originalRequest)
  465. }
  466.  
  467. val newCookie = getNewCookie(originalRequest.url) ?: return chain.proceed(originalRequest)
  468. val newCookieHeader = (oldCookie + newCookie).joinToString("; ") {
  469. "${it.name}=${it.value}"
  470. }
  471.  
  472. return chain.proceed(originalRequest.newBuilder().addHeader("cookie", newCookieHeader).build())
  473. }
  474.  
  475. fun getNewCookie(url: HttpUrl): Cookie? {
  476. val cookies = cookieManager.getCookie(url.toString())
  477. val oldCookie = if (cookies != null && cookies.isNotEmpty()) {
  478. cookies.split(";").mapNotNull { Cookie.parse(url, it) }
  479. } else {
  480. emptyList()
  481. }
  482. val ddg2Cookie = oldCookie.firstOrNull { it.name == "__ddg2_" }
  483. if (!ddg2Cookie?.value.isNullOrEmpty()) {
  484. return ddg2Cookie
  485. }
  486. val wellKnown = client.newCall(GET("https://check.ddos-guard.net/check.js"))
  487. .execute().body.string()
  488. .substringAfter("'", "")
  489. .substringBefore("'", "")
  490. val checkUrl = "${url.scheme}://${url.host + wellKnown}"
  491. return client.newCall(GET(checkUrl)).execute().header("set-cookie")?.let {
  492. Cookie.parse(url, it)
  493. }
  494. }
  495.  
  496. companion object {
  497. private val ERROR_CODES = listOf(403)
  498. private val SERVER_CHECK = listOf("ddos-guard")
  499. }
  500. }
  501. ```
  502.  
  503. ```KwikExtractor.kt
  504. package eu.kanade.tachiyomi.animeextension.en.animepahe
  505.  
  506. import dev.datlag.jsunpacker.JsUnpacker
  507. import eu.kanade.tachiyomi.network.GET
  508. import eu.kanade.tachiyomi.network.POST
  509. import eu.kanade.tachiyomi.util.asJsoup
  510. import okhttp3.FormBody
  511. import okhttp3.Headers
  512. import okhttp3.OkHttpClient
  513. import okhttp3.Response
  514. import kotlin.math.pow
  515.  
  516. class KwikExtractor(private val client: OkHttpClient) {
  517. private var cookies: String = ""
  518.  
  519. private val kwikParamsRegex = Regex("""\("(\w+)",\d+,"(\w+)",(\d+),(\d+),\d+\)""")
  520. private val kwikDUrl = Regex("action=\"([^\"]+)\"")
  521. private val kwikDToken = Regex("value=\"([^\"]+)\"")
  522.  
  523. private fun isNumber(s: String?): Boolean {
  524. return s?.toIntOrNull() != null
  525. }
  526.  
  527. fun getHlsStreamUrl(kwikUrl: String, referer: String): String {
  528. val eContent = client.newCall(GET(kwikUrl, Headers.headersOf("referer", referer)))
  529. .execute().asJsoup()
  530. val script = eContent.selectFirst("script:containsData(eval\\(function)")!!.data().substringAfterLast("eval(function(")
  531. val unpacked = JsUnpacker.unpackAndCombine("eval(function($script")!!
  532. return unpacked.substringAfter("const source=\\'").substringBefore("\\';")
  533. }
  534.  
  535. fun getStreamUrlFromKwik(paheUrl: String): String {
  536. val noRedirects = client.newBuilder()
  537. .followRedirects(false)
  538. .followSslRedirects(false)
  539. .build()
  540. val kwikUrl = "https://" + noRedirects.newCall(GET("$paheUrl/i")).execute()
  541. .header("location")!!.substringAfterLast("https://")
  542. val fContent =
  543. client.newCall(GET(kwikUrl, Headers.headersOf("referer", "https://kwik.cx/"))).execute()
  544. cookies += fContent.header("set-cookie")!!
  545. val fContentString = fContent.body.string()
  546.  
  547. val (fullString, key, v1, v2) = kwikParamsRegex.find(fContentString)!!.destructured
  548. val decrypted = decrypt(fullString, key, v1.toInt(), v2.toInt())
  549. val uri = kwikDUrl.find(decrypted)!!.destructured.component1()
  550. val tok = kwikDToken.find(decrypted)!!.destructured.component1()
  551. var content: Response? = null
  552.  
  553. var code = 419
  554. var tries = 0
  555.  
  556. val noRedirectClient = OkHttpClient().newBuilder()
  557. .followRedirects(false)
  558. .followSslRedirects(false)
  559. .cookieJar(client.cookieJar)
  560. .build()
  561.  
  562. while (code != 302 && tries < 20) {
  563. content = noRedirectClient.newCall(
  564. POST(
  565. uri,
  566. Headers.headersOf(
  567. "referer",
  568. fContent.request.url.toString(),
  569. "cookie",
  570. fContent.header("set-cookie")!!.replace("path=/;", ""),
  571. ),
  572. FormBody.Builder().add("_token", tok).build(),
  573. ),
  574. ).execute()
  575. code = content.code
  576. ++tries
  577. }
  578. if (tries > 19) {
  579. throw Exception("Failed to extract the stream uri from kwik.")
  580. }
  581. val location = content?.header("location").toString()
  582. content?.close()
  583. return location
  584. }
  585.  
  586. private fun decrypt(fullString: String, key: String, v1: Int, v2: Int): String {
  587. var r = ""
  588. var i = 0
  589.  
  590. while (i < fullString.length) {
  591. var s = ""
  592.  
  593. while (fullString[i] != key[v2]) {
  594. s += fullString[i]
  595. ++i
  596. }
  597. var j = 0
  598.  
  599. while (j < key.length) {
  600. s = s.replace(key[j].toString(), j.toString())
  601. ++j
  602. }
  603. r += (getString(s, v2).toInt() - v1).toChar()
  604. ++i
  605. }
  606. return r
  607. }
  608.  
  609. private fun getString(content: String, s1: Int): String {
  610. val s2 = 10
  611. val characterMap = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/"
  612.  
  613. val slice2 = characterMap.slice(0 until s2)
  614. var acc: Long = 0
  615.  
  616. for ((n, i) in content.reversed().withIndex()) {
  617. acc += when (isNumber("$i")) {
  618. true -> "$i".toLong()
  619. false -> 0L
  620. } * s1.toDouble().pow(n.toDouble()).toInt()
  621. }
  622.  
  623. var k = ""
  624.  
  625. while (acc > 0) {
  626. k = slice2[(acc % s2).toInt()] + k
  627. acc = (acc - (acc % s2)) / s2
  628. }
  629.  
  630. return when (k != "") {
  631. true -> k
  632. false -> "0"
  633. }
  634. }
  635. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement