feat(src/de): New source: Moflix-Stream (#2364)

This commit is contained in:
LuftVerbot 2023-10-14 16:35:22 +02:00 committed by GitHub
parent bcf55beb43
commit 4a530a7704
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 558 additions and 7 deletions

View File

@ -97,14 +97,17 @@ class PlaylistUtils(private val client: OkHttpClient, private val headers: Heade
val playlistHttpUrl = playlistUrl.toHttpUrl()
val masterBase = playlistHttpUrl.newBuilder().apply {
val masterUrlBasePath = playlistHttpUrl.newBuilder().apply {
removePathSegment(playlistHttpUrl.pathSize - 1)
}.build().toString() + "/"
addPathSegment("")
query(null)
fragment(null)
}.build().toString()
// Get subtitles
val subtitleTracks = subtitleList + SUBTITLE_REGEX.findAll(masterPlaylist).mapNotNull {
Track(
getAbsoluteUrl(it.groupValues[2], playlistUrl, masterBase) ?: return@mapNotNull null,
getAbsoluteUrl(it.groupValues[2], playlistUrl, masterUrlBasePath ) ?: return@mapNotNull null,
it.groupValues[1]
)
}.toList()
@ -112,7 +115,7 @@ class PlaylistUtils(private val client: OkHttpClient, private val headers: Heade
// Get audio tracks
val audioTracks = audioList + AUDIO_REGEX.findAll(masterPlaylist).mapNotNull {
Track(
getAbsoluteUrl(it.groupValues[2], playlistUrl, masterBase) ?: return@mapNotNull null,
getAbsoluteUrl(it.groupValues[2], playlistUrl, masterUrlBasePath ) ?: return@mapNotNull null,
it.groupValues[1]
)
}.toList()
@ -124,7 +127,7 @@ class PlaylistUtils(private val client: OkHttpClient, private val headers: Heade
.substringBefore(",") + "p"
val videoUrl = it.substringAfter("\n").substringBefore("\n").let { url ->
getAbsoluteUrl(url, playlistUrl, masterBase)
getAbsoluteUrl(url, playlistUrl, masterUrlBasePath )
} ?: return@mapNotNull null

View File

@ -8,14 +8,18 @@ import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.OkHttpClient
class StreamVidExtractor(private val client: OkHttpClient) {
fun videosFromUrl(url: String, prefix: String = ""): List<Video> {
fun videosFromUrl(url: String, prefix: String = "", sourceChange: Boolean = false): List<Video> {
return runCatching {
val doc = client.newCall(GET(url)).execute().asJsoup()
val script = doc.selectFirst("script:containsData(eval):containsData(p,a,c,k,e,d)")?.data()
?.let(JsUnpacker::unpackAndCombine)
?: return emptyList()
val masterUrl = script.substringAfter("sources:[{src:\"").substringBefore("\",")
val masterUrl = if(!sourceChange) {
script.substringAfter("sources:[{src:\"").substringBefore("\",")
} else {
script.substringAfter("sources:[{file:\"").substringBefore("\"")
}
PlaylistUtils(client).extractFromHls(masterUrl, videoNameGen = { "${prefix}StreamVid - (${it}p)" })
}.getOrElse { emptyList() }
}

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest />

View File

@ -0,0 +1,22 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
ext {
extName = 'Moflix-Stream'
pkgNameSuffix = 'de.moflixstream'
extClass = '.MoflixStream'
extVersionCode = 1
libVersion = '13'
}
dependencies {
implementation(project(':lib-streamvid-extractor'))
implementation(project(':lib-streamwish-extractor'))
implementation(project(':lib-streamtape-extractor'))
implementation(project(':lib-playlist-utils'))
implementation "dev.datlag.jsunpacker:jsunpacker:1.0.1"
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

View File

@ -0,0 +1,365 @@
package eu.kanade.tachiyomi.animeextension.de.moflixstream
import android.app.Application
import android.content.SharedPreferences
import androidx.preference.ListPreference
import androidx.preference.MultiSelectListPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animeextension.de.moflixstream.extractors.UnpackerExtractor
import eu.kanade.tachiyomi.animeextension.de.moflixstream.extractors.VidGuardExtractor
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
import eu.kanade.tachiyomi.animesource.model.AnimesPage
import eu.kanade.tachiyomi.animesource.model.SAnime
import eu.kanade.tachiyomi.animesource.model.SEpisode
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
import eu.kanade.tachiyomi.lib.streamvidextractor.StreamVidExtractor
import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor
import eu.kanade.tachiyomi.network.GET
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.int
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.Headers
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import kotlin.Exception
class MoflixStream : ConfigurableAnimeSource, AnimeHttpSource() {
override val name = "Moflix-Stream"
override val baseUrl = "https://moflix-stream.xyz"
override val lang = "de"
override val supportsLatest = false
override val client: OkHttpClient = network.cloudflareClient
private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
private val json = Json {
isLenient = true
ignoreUnknownKeys = true
}
override fun popularAnimeRequest(page: Int): Request = GET(
"$baseUrl/api/v1/channel/345?returnContentOnly=true&restriction=&order=rating:desc&paginate=simple&perPage=50&query=&page=$page",
headers = Headers.headersOf("referer", "$baseUrl/movies?order=rating%3Adesc"),
)
override fun popularAnimeParse(response: Response): AnimesPage {
val responseString = response.body.string()
return parsePopularAnimeJson(responseString)
}
private fun parsePopularAnimeJson(jsonLine: String?): AnimesPage {
val jsonData = jsonLine ?: return AnimesPage(emptyList(), false)
val jObject = json.decodeFromString<JsonObject>(jsonData)
val jO = jObject.jsonObject["pagination"]!!.jsonObject
val nextPage = jO.jsonObject["next_page_url"]!!.jsonPrimitive.content
.substringAfter("page=").toInt()
val page = jO.jsonObject["current_page"]!!.jsonPrimitive.int
val hasNextPage = page < nextPage
val array = jO["data"]!!.jsonArray
val animeList = mutableListOf<SAnime>()
for (item in array) {
val anime = SAnime.create()
anime.title = item.jsonObject["name"]!!.jsonPrimitive.content
val animeId = item.jsonObject["id"]!!.jsonPrimitive.content
anime.setUrlWithoutDomain("$baseUrl/api/v1/titles/$animeId?load=images,genres,productionCountries,keywords,videos,primaryVideo,seasons,compactCredits")
anime.thumbnail_url = item.jsonObject["poster"]?.jsonPrimitive?.content ?: item.jsonObject["backdrop"]?.jsonPrimitive?.content
animeList.add(anime)
}
return AnimesPage(animeList, hasNextPage)
}
// episodes
override fun episodeListRequest(anime: SAnime): Request = GET(baseUrl + anime.url, headers = Headers.headersOf("referer", baseUrl))
override fun episodeListParse(response: Response): List<SEpisode> {
val responseString = response.body.string()
val url = response.request.url.toString()
return parseEpisodeAnimeJson(responseString, url)
}
private fun parseEpisodeAnimeJson(jsonLine: String?, url: String): List<SEpisode> {
val jsonData = jsonLine ?: return emptyList()
val jObject = json.decodeFromString<JsonObject>(jsonData)
val episodeList = mutableListOf<SEpisode>()
val mId = jObject.jsonObject["title"]!!.jsonObject["id"]!!.jsonPrimitive.content
val season = jObject.jsonObject["seasons"]?.jsonObject
if (season != null) {
val dataArray = season.jsonObject["data"]!!.jsonArray
val next = season.jsonObject["next_page"]?.jsonPrimitive?.content
if (next != null) {
val seNextJsonData = client.newCall(GET("$baseUrl/api/v1/titles/$mId/seasons?perPage=8&query=&page=$next", headers = Headers.headersOf("referer", baseUrl))).execute().body.string()
val seNextJObject = json.decodeFromString<JsonObject>(seNextJsonData)
val seasonNext = seNextJObject.jsonObject["pagination"]!!.jsonObject
val dataNextArray = seasonNext.jsonObject["data"]!!.jsonArray
val dataAllArray = dataArray.plus(dataNextArray)
for (item in dataAllArray) {
val id = item.jsonObject["title_id"]!!.jsonPrimitive.content
val num = item.jsonObject["number"]!!.jsonPrimitive.content
val seUrl = "$baseUrl/api/v1/titles/$id/seasons/$num?load=episodes,primaryVideo"
val seJsonData = client.newCall(GET(seUrl, headers = Headers.headersOf("referer", baseUrl))).execute().body.string()
val seJObject = json.decodeFromString<JsonObject>(seJsonData)
val epObject = seJObject.jsonObject["episodes"]!!.jsonObject
val epDataArray = epObject.jsonObject["data"]!!.jsonArray.reversed()
for (epItem in epDataArray) {
val episode = SEpisode.create()
val seNum = epItem.jsonObject["season_number"]!!.jsonPrimitive.content
val epNum = epItem.jsonObject["episode_number"]!!.jsonPrimitive.content
episode.name = "Staffel $seNum Folge $epNum : " + epItem.jsonObject["name"]!!.jsonPrimitive.content
episode.episode_number = epNum.toFloat()
val epId = epItem.jsonObject["title_id"]!!.jsonPrimitive.content
episode.setUrlWithoutDomain("$baseUrl/api/v1/titles/$epId/seasons/$seNum/episodes/$epNum?load=videos,compactCredits,primaryVideo")
episodeList.add(episode)
}
}
} else {
for (item in dataArray) {
val id = item.jsonObject["title_id"]!!.jsonPrimitive.content
val num = item.jsonObject["number"]!!.jsonPrimitive.content
val seUrl = "$baseUrl/api/v1/titles/$id/seasons/$num?load=episodes,primaryVideo"
val seJsonData = client.newCall(GET(seUrl, headers = Headers.headersOf("referer", baseUrl))).execute().body.string()
val seJObject = json.decodeFromString<JsonObject>(seJsonData)
val epObject = seJObject.jsonObject["episodes"]!!.jsonObject
val epDataArray = epObject.jsonObject["data"]!!.jsonArray.reversed()
for (epItem in epDataArray) {
val episode = SEpisode.create()
val seNum = epItem.jsonObject["season_number"]!!.jsonPrimitive.content
val epNum = epItem.jsonObject["episode_number"]!!.jsonPrimitive.content
episode.name = "Staffel $seNum Folge $epNum : " + epItem.jsonObject["name"]!!.jsonPrimitive.content
episode.episode_number = epNum.toFloat()
val epId = epItem.jsonObject["title_id"]!!.jsonPrimitive.content
episode.setUrlWithoutDomain("$baseUrl/api/v1/titles/$epId/seasons/$seNum/episodes/$epNum?load=videos,compactCredits,primaryVideo")
episodeList.add(episode)
}
}
}
} else {
val episode = SEpisode.create()
episode.episode_number = 1F
episode.name = "Film"
episode.setUrlWithoutDomain(url)
episodeList.add(episode)
}
return episodeList
}
// Video Extractor
override fun videoListRequest(episode: SEpisode): Request {
return GET(baseUrl + episode.url, headers = Headers.headersOf("referer", baseUrl))
}
override fun videoListParse(response: Response): List<Video> {
val responseString = response.body.string()
val url = response.request.url.toString()
return videosFromJson(responseString, url)
}
private fun videosFromJson(jsonLine: String?, url: String): List<Video> {
val videoList = mutableListOf<Video>()
val hosterSelection = preferences.getStringSet("hoster_selection", setOf("stape", "vidg", "svid", "hstream", "flions", "lstream"))
val jsonData = jsonLine ?: return emptyList()
val jObject = json.decodeFromString<JsonObject>(jsonData)
if (url.contains("episodes")) {
val epObject = jObject.jsonObject["episode"]!!.jsonObject
val videoArray = epObject.jsonObject["videos"]!!.jsonArray
for (item in videoArray) {
val host = item.jsonObject["name"]!!.jsonPrimitive.content
val eUrl = item.jsonObject["src"]!!.jsonPrimitive.content
when {
host.contains("Streamtape") && hosterSelection?.contains("stape") == true -> {
val quality = "Streamtape"
val video = StreamTapeExtractor(client).videoFromUrl(eUrl, quality)
if (video != null) {
videoList.add(video)
}
}
host.contains("Streamvid") && hosterSelection?.contains("svid") == true -> {
val video = StreamVidExtractor(client).videosFromUrl(eUrl)
videoList.addAll(video)
}
host.contains("Highstream") && hosterSelection?.contains("hstream") == true -> {
val videos = StreamVidExtractor(client).videosFromUrl(eUrl, prefix = "Highstream - ")
videoList.addAll(videos)
}
host.contains("VidGuard") && hosterSelection?.contains("vidg") == true -> {
val videos = VidGuardExtractor(client).videosFromUrl(eUrl)
videoList.addAll(videos)
}
host.contains("Filelions") && hosterSelection?.contains("flions") == true -> {
val videos = StreamWishExtractor(client, headers).videosFromUrl(eUrl, videoNameGen = { quality -> "FileLions - $quality" })
videoList.addAll(videos)
}
host.contains("LuluStream") && hosterSelection?.contains("lstream") == true -> {
val videos = UnpackerExtractor(client, headers).videosFromUrl(eUrl, "LuluStream")
videoList.addAll(videos)
}
}
}
} else {
val titleObject = jObject.jsonObject["title"]!!.jsonObject
val videoArray = titleObject.jsonObject["videos"]!!.jsonArray
for (item in videoArray) {
val host = item.jsonObject["name"]!!.jsonPrimitive.content
val fUrl = item.jsonObject["src"]!!.jsonPrimitive.content
when {
host.contains("Streamtape") && hosterSelection?.contains("stape") == true -> {
val quality = "Streamtape"
val video = StreamTapeExtractor(client).videoFromUrl(fUrl, quality)
if (video != null) {
videoList.add(video)
}
}
host.contains("Streamvid") && hosterSelection?.contains("svid") == true -> {
val video = StreamVidExtractor(client).videosFromUrl(fUrl)
videoList.addAll(video)
}
host.contains("Highstream") && hosterSelection?.contains("hstream") == true -> {
val videos = StreamVidExtractor(client).videosFromUrl(fUrl, prefix = "Highstream - ", sourceChange = true)
videoList.addAll(videos)
}
host.contains("Filelions") && hosterSelection?.contains("flions") == true -> {
val videos = StreamWishExtractor(client, headers).videosFromUrl(fUrl, videoNameGen = { quality -> "FileLions - $quality" })
videoList.addAll(videos)
}
host.contains("VidGuard") && hosterSelection?.contains("vidg") == true -> {
val videos = VidGuardExtractor(client).videosFromUrl(fUrl)
videoList.addAll(videos)
}
host.contains("LuluStream") && hosterSelection?.contains("lstream") == true -> {
val videos = UnpackerExtractor(client, headers).videosFromUrl(fUrl, "LuluStream")
videoList.addAll(videos)
}
}
}
}
return videoList
}
override fun List<Video>.sort(): List<Video> {
val hoster = preferences.getString("preferred_hoster", null)
if (hoster != null) {
val newList = mutableListOf<Video>()
var preferred = 0
for (video in this) {
if (video.url.contains(hoster)) {
newList.add(preferred, video)
preferred++
} else {
newList.add(video)
}
}
return newList
}
return this
}
// Search
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request = GET(
"$baseUrl/api/v1/search/$query?query=$query",
headers = Headers.headersOf("referer", "$baseUrl/search/$query"),
)
override fun searchAnimeParse(response: Response): AnimesPage {
val responseString = response.body.string()
return parseSearchAnimeJson(responseString)
}
private fun parseSearchAnimeJson(jsonLine: String?): AnimesPage {
val jsonData = jsonLine ?: return AnimesPage(emptyList(), false)
val jObject = json.decodeFromString<JsonObject>(jsonData)
val array = jObject["results"]!!.jsonArray
val animeList = mutableListOf<SAnime>()
for (item in array) {
val anime = SAnime.create()
anime.title = item.jsonObject["name"]!!.jsonPrimitive.content
val animeId = item.jsonObject["id"]!!.jsonPrimitive.content
anime.setUrlWithoutDomain("$baseUrl/api/v1/titles/$animeId?load=images,genres,productionCountries,keywords,videos,primaryVideo,seasons,compactCredits")
anime.thumbnail_url = item.jsonObject["poster"]?.jsonPrimitive?.content ?: item.jsonObject["backdrop"]?.jsonPrimitive?.content
animeList.add(anime)
}
return AnimesPage(animeList, hasNextPage = false)
}
// Details
override fun animeDetailsRequest(anime: SAnime): Request = GET(baseUrl + anime.url, headers = Headers.headersOf("referer", baseUrl))
override fun animeDetailsParse(response: Response): SAnime {
val responseString = response.body.string()
return parseAnimeDetailsParseJson(responseString)
}
private fun parseAnimeDetailsParseJson(jsonLine: String?): SAnime {
val anime = SAnime.create()
val jsonData = jsonLine ?: return anime
val jObject = json.decodeFromString<JsonObject>(jsonData)
val jO = jObject.jsonObject["title"]!!.jsonObject
anime.title = jO.jsonObject["name"]!!.jsonPrimitive.content
anime.description = jO.jsonObject["description"]!!.jsonPrimitive.content
val genArray = jO.jsonObject["genres"]!!.jsonArray
val genres = mutableListOf<String>()
for (item in genArray) {
val genre = item.jsonObject["display_name"]!!.jsonPrimitive.content
genres.add(genre)
}
anime.genre = genres.joinToString { it }
anime.thumbnail_url = jO.jsonObject["poster"]?.jsonPrimitive?.content ?: jO.jsonObject["backdrop"]?.jsonPrimitive?.content
return anime
}
// Latest
override fun latestUpdatesParse(response: Response): AnimesPage = throw Exception("not Used")
override fun latestUpdatesRequest(page: Int): Request = throw Exception("Not used")
// Preferences
override fun setupPreferenceScreen(screen: PreferenceScreen) {
val hosterPref = ListPreference(screen.context).apply {
key = "preferred_hoster"
title = "Standard-Hoster"
entries = arrayOf("Streamtape", "VidGuard", "Streamvid", "Highstream", "Filelions", "LuluStream")
entryValues = arrayOf("https://streamtape", "https://moflix-stream", "https://streamvid", "https://highstream", "https://moflix-stream", "https://luluvdo")
setDefaultValue("https://streamtape")
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
}
}
val subSelection = MultiSelectListPreference(screen.context).apply {
key = "hoster_selection"
title = "Hoster auswählen"
entries = arrayOf("Streamtape", "VidGuard", "Streamvid", "Highstream", "Filelions", "LuluStream")
entryValues = arrayOf("stape", "vidg", "svid", "hstream", "flions", "lstream")
setDefaultValue(setOf("stape", "vidg", "svid", "hstream", "flions", "lstream"))
setOnPreferenceChangeListener { _, newValue ->
preferences.edit().putStringSet(key, newValue as Set<String>).commit()
}
}
screen.addPreference(hosterPref)
screen.addPreference(subSelection)
}
}

View File

@ -0,0 +1,31 @@
package eu.kanade.tachiyomi.animeextension.de.moflixstream.extractors
import dev.datlag.jsunpacker.JsUnpacker
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.lib.playlistutils.PlaylistUtils
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Headers
import okhttp3.OkHttpClient
class UnpackerExtractor(private val client: OkHttpClient, private val headers: Headers) {
private val playlistUtils by lazy { PlaylistUtils(client, headers) }
fun videosFromUrl(url: String, hoster: String): List<Video> {
val doc = client.newCall(GET(url, headers)).execute()
.use { it.asJsoup() }
val script = doc.selectFirst("script:containsData(eval)")
?.data()
?.let(JsUnpacker::unpackAndCombine)
?: return emptyList()
val playlistUrl = script.substringAfter("file:\"").substringBefore('"')
return playlistUtils.extractFromHls(
playlistUrl,
referer = playlistUrl,
videoNameGen = { "$hoster - $it" },
)
}
}

View File

@ -0,0 +1,124 @@
package eu.kanade.tachiyomi.animeextension.de.moflixstream.extractors
import android.app.Application
import android.os.Handler
import android.os.Looper
import android.util.Base64
import android.webkit.JavascriptInterface
import android.webkit.WebSettings
import android.webkit.WebView
import android.webkit.WebViewClient
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import uy.kohesive.injekt.injectLazy
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
class VidGuardExtractor(private val client: OkHttpClient) {
private val context: Application by injectLazy()
private val handler by lazy { Handler(Looper.getMainLooper()) }
class JsObject(private val latch: CountDownLatch) {
var payload: String = ""
@JavascriptInterface
fun passPayload(passedPayload: String) {
payload = passedPayload
latch.countDown()
}
}
fun videosFromUrl(url: String): List<Video> {
val doc = client.newCall(GET(url)).execute().use { it.asJsoup() }
val scriptUrl = doc.selectFirst("script[src*=ad/plugin]")
?.absUrl("src")
?: return emptyList()
val headers = Headers.headersOf("Referer", url)
val script = client.newCall(GET(scriptUrl, headers)).execute()
.use { it.body.string() }
val sources = getSourcesFromScript(script, url)
.takeIf { it.isNotBlank() && it != "undefined" }
?: return emptyList()
return sources.substringAfter("stream:[").substringBefore("}]")
.split('{')
.drop(1)
.mapNotNull { line ->
val resolution = line.substringAfter("Label\":\"").substringBefore('"')
val videoUrl = line.substringAfter("URL\":\"").substringBefore('"')
.takeIf(String::isNotBlank)
?.let(::fixUrl)
?: return@mapNotNull null
Video(videoUrl, "VidGuard - $resolution", videoUrl, headers)
}
}
private fun getSourcesFromScript(script: String, url: String): String {
val latch = CountDownLatch(1)
var webView: WebView? = null
val jsinterface = JsObject(latch)
handler.post {
val webview = WebView(context)
webView = webview
with(webview.settings) {
javaScriptEnabled = true
domStorageEnabled = true
databaseEnabled = true
useWideViewPort = false
loadWithOverviewMode = false
cacheMode = WebSettings.LOAD_NO_CACHE
}
webview.addJavascriptInterface(jsinterface, "android")
webview.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView?, url: String?) {
view?.clearCache(true)
view?.clearFormData()
view?.evaluateJavascript(script) {}
view?.evaluateJavascript("window.android.passPayload(JSON.stringify(window.svg))") {}
}
}
webview.loadDataWithBaseURL(url, "<html></html>", "text/html", "UTF-8", null)
}
latch.await(5, TimeUnit.SECONDS)
handler.post {
webView?.stopLoading()
webView?.destroy()
webView = null
}
return jsinterface.payload
}
private fun fixUrl(url: String): String {
val httpUrl = url.toHttpUrl()
val originalSign = httpUrl.queryParameter("sig")!!
val newSign = originalSign.chunked(2).joinToString("") {
Char(it.toInt(16) xor 2).toString()
}
.let { String(Base64.decode(it, Base64.DEFAULT)) }
.substring(5)
.chunked(2)
.reversed()
.joinToString("")
.substring(5)
return httpUrl.newBuilder()
.removeAllQueryParameters("sig")
.addQueryParameter("sig", newSign)
.build()
.toString()
}
}