diff --git a/src/de/serienstream/build.gradle b/src/de/serienstream/build.gradle index 6f30f1312..e9b060569 100644 --- a/src/de/serienstream/build.gradle +++ b/src/de/serienstream/build.gradle @@ -6,7 +6,7 @@ ext { extName = 'Serienstream' pkgNameSuffix = 'de.serienstream' extClass = '.Serienstream' - extVersionCode = 13 + extVersionCode = 14 libVersion = '13' } diff --git a/src/de/serienstream/src/eu/kanade/tachiyomi/animeextension/de/serienstream/JsInterceptor.kt b/src/de/serienstream/src/eu/kanade/tachiyomi/animeextension/de/serienstream/JsInterceptor.kt deleted file mode 100644 index 888bd60c4..000000000 --- a/src/de/serienstream/src/eu/kanade/tachiyomi/animeextension/de/serienstream/JsInterceptor.kt +++ /dev/null @@ -1,525 +0,0 @@ -package eu.kanade.tachiyomi.animeextension.de.serienstream - -import android.annotation.SuppressLint -import android.app.Application -import android.os.Handler -import android.os.Looper -import android.webkit.JavascriptInterface -import android.webkit.WebResourceRequest -import android.webkit.WebResourceResponse -import android.webkit.WebView -import android.webkit.WebViewClient -import eu.kanade.tachiyomi.network.GET -import okhttp3.Headers -import okhttp3.Interceptor -import okhttp3.Request -import okhttp3.Response -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -import java.util.concurrent.CountDownLatch -import java.util.concurrent.TimeUnit - -class JsInterceptor : Interceptor { - - private val context = Injekt.get() - 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() - } - } - - override fun intercept(chain: Interceptor.Chain): Response { - val originalRequest = chain.request() - - val newRequest = resolveWithWebView(originalRequest) ?: throw Exception("Please reload Episode List") - - return chain.proceed(newRequest) - } - - @SuppressLint("SetJavaScriptEnabled") - private fun resolveWithWebView(request: Request): Request? { - val latch = CountDownLatch(1) - - var webView: WebView? = null - - val origRequestUrl = request.url.toString() - - val jsinterface = JsObject(latch) - - // JavaSrcipt bypass recaptcha FUCK GOOGLE RECAPTCHA v1.0 - val jsScript = """ - function audioBufferToWav(buffer, opt) { - opt = opt || {} - - var numChannels = buffer.numberOfChannels - var sampleRate = buffer.sampleRate - var format = opt.float32 ? 3 : 1 - var bitDepth = format === 3 ? 32 : 16 - - var result - if (numChannels === 2) { - result = interleave(buffer.getChannelData(0), buffer.getChannelData(1)) - } else { - result = buffer.getChannelData(0) - } - - return encodeWAV(result, format, sampleRate, numChannels, bitDepth) - } - - function encodeWAV(samples, format, sampleRate, numChannels, bitDepth) { - var bytesPerSample = bitDepth / 8 - var blockAlign = numChannels * bytesPerSample - - var buffer = new ArrayBuffer(44 + samples.length * bytesPerSample) - var view = new DataView(buffer) - - /* RIFF identifier */ - writeString(view, 0, 'RIFF') - /* RIFF chunk length */ - view.setUint32(4, 36 + samples.length * bytesPerSample, true) - /* RIFF type */ - writeString(view, 8, 'WAVE') - /* format chunk identifier */ - writeString(view, 12, 'fmt ') - /* format chunk length */ - view.setUint32(16, 16, true) - /* sample format (raw) */ - view.setUint16(20, format, true) - /* channel count */ - view.setUint16(22, numChannels, true) - /* sample rate */ - view.setUint32(24, sampleRate, true) - /* byte rate (sample rate * block align) */ - view.setUint32(28, sampleRate * blockAlign, true) - /* block align (channel count * bytes per sample) */ - view.setUint16(32, blockAlign, true) - /* bits per sample */ - view.setUint16(34, bitDepth, true) - /* data chunk identifier */ - writeString(view, 36, 'data') - /* data chunk length */ - view.setUint32(40, samples.length * bytesPerSample, true) - if (format === 1) { // Raw PCM - floatTo16BitPCM(view, 44, samples) - } else { - writeFloat32(view, 44, samples) - } - - return buffer - } - - function interleave(inputL, inputR) { - var length = inputL.length + inputR.length - var result = new Float32Array(length) - - var index = 0 - var inputIndex = 0 - - while (index < length) { - result[index++] = inputL[inputIndex] - result[index++] = inputR[inputIndex] - inputIndex++ - } - return result - } - - function writeFloat32(output, offset, input) { - for (var i = 0; i < input.length; i++, offset += 4) { - output.setFloat32(offset, input[i], true) - } - } - - function floatTo16BitPCM(output, offset, input) { - for (var i = 0; i < input.length; i++, offset += 2) { - var s = Math.max(-1, Math.min(1, input[i])) - output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true) - } - } - - function writeString(view, offset, string) { - for (var i = 0; i < string.length; i++) { - view.setUint8(offset + i, string.charCodeAt(i)) - } - } - - async function normalizeAudio(buffer) { - const ctx = new AudioContext(); - const audioBuffer = await ctx.decodeAudioData(buffer); - ctx.close(); - - const offlineCtx = new OfflineAudioContext( - 1, - audioBuffer.duration * 16000, - 16000 - ); - const source = offlineCtx.createBufferSource(); - source.connect(offlineCtx.destination); - source.buffer = audioBuffer; - source.start(); - - return offlineCtx.startRendering(); - } - - async function sliceAudio({ - audioBuffer, - start, - end - }) { - const sampleRate = audioBuffer.sampleRate; - const channels = audioBuffer.numberOfChannels; - - const startOffset = sampleRate * start; - const endOffset = sampleRate * end; - const frameCount = endOffset - startOffset; - - const ctx = new AudioContext(); - const audioSlice = ctx.createBuffer(channels, frameCount, sampleRate); - ctx.close(); - - const tempArray = new Float32Array(frameCount); - for (var channel = 0; channel < channels; channel++) { - audioBuffer.copyFromChannel(tempArray, channel, startOffset); - audioSlice.copyToChannel(tempArray, channel, 0); - } - - return audioSlice; - } - - async function prepareAudio(audio) { - const audioBuffer = await normalizeAudio(audio); - - const audioSlice = await sliceAudio({ - audioBuffer, - start: 1.5, - end: audioBuffer.duration - 1.5 - }); - - return audioBufferToWav(audioSlice); - } - - async function getWitSpeechApiResult(audioUrl) { - var audioRsp = await fetch(audioUrl); - var t = 0; - while(audioRsp.status === 404 && t <= 2){ - t++; - audioRsp = await fetch(audioUrl); - } - const audioContent = await prepareAudio(await audioRsp.arrayBuffer()); - const result = {}; - - const rsp = await fetch('https://api.wit.ai/speech?v=20221114', { - mode: 'cors', - method: 'POST', - headers: { - Authorization: 'Bearer ' + 'YZLWLZHOWH7MZR636L5IGJW66R43CEID' - }, - body: new Blob([audioContent], { - type: 'audio/wav' - }) - }); - - if (rsp.status !== 200) { - if (rsp.status === 429) { - result.errorId = 'error_apiQuotaExceeded'; - result.errorTimeout = 6000; - } else { - throw new Error('API response:' + rsp.status + ',' + await rsp.text()); - } - } else { - const data = JSON.parse((await rsp.text()).split('\r\n').at(-1)).text; - if (data) { - result.text = data.trim(); - } - } - return result.text; - } - - async function main() { - let intervalIdA = setInterval(() => { - let iframewindow = document.querySelector("iframe[src*='recaptcha/api2']:not([src*=anchor])").contentWindow; - if (iframewindow) { - clearInterval(intervalIdA); - let audiobutton = iframewindow.document.querySelector('#recaptcha-audio-button'); - let event = iframewindow.document.createEvent('HTMLEvents'); - event.initEvent('click', false, false); - audiobutton.dispatchEvent(event); - let intervalIdB = setInterval(async () => { - let audio = iframewindow.document.querySelector('#audio-source'); - let source = audio.getAttribute('src'); - if (source) { - clearInterval(intervalIdB); - // Prevent 404 status loop - const response = await fetch(source); - if (response.ok) { - let audioresponse = iframewindow.document.querySelector('#audio-response'); - let verifybutton = iframewindow.document.querySelector('#recaptcha-verify-button'); - var tries = 0; - let intervalIdC = setInterval(async () => { - var solved = null - solved = await getWitSpeechApiResult(source); - tries++; - if (solved != null) { - clearInterval(intervalIdC); - audioresponse.value = solved; - verifybutton.dispatchEvent(event); - const originalOpen = iframewindow.XMLHttpRequest.prototype.open; - iframewindow.XMLHttpRequest.prototype.open = function (method, url, async) { - if (url.includes('userverify')) { - originalOpen.apply(this, arguments); // call the original open method - this.onreadystatechange = function () { - if (this.readyState === 4 && this.status === 200) { - const responseBody = this.responseText; - window.android.passPayload(responseBody); - } - }; - } - }; - } else if (tries >= 2) { - clearInterval(intervalIdC); - window.android.passPayload(""); - } - }, 2000); - } else { - window.android.passPayload("Audio file not found"); - } - } - }, 2000); - } - }, 2000); - }; - - // Prevent async-related problems with stupid webviews - setTimeout(async () => await main(), 0); - """ - - val headers = request.headers.toMultimap().mapValues { it.value.getOrNull(0) ?: "" }.toMutableMap() - - var newRequest: Request? = null - - var isDataLoaded = false - - handler.post { - val webview = WebView(context) - webView = webview - with(webview.settings) { - javaScriptEnabled = true - domStorageEnabled = true - databaseEnabled = true - useWideViewPort = false - loadWithOverviewMode = false - userAgentString = "Mozilla/5.0 (Linux; Android 12; Pixel 5 Build/SP2A.220405.004; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/100.0.4896.127 Safari/537.36" - webview.addJavascriptInterface(jsinterface, "android") - webview.webViewClient = object : WebViewClient() { - override fun shouldInterceptRequest(view: WebView?, request: WebResourceRequest?): WebResourceResponse? { - if (request?.url.toString().contains("https://www.google.com/?token=")) { - jsinterface.payload = request?.url.toString().substringAfter("?token=").substringBefore("&original=") - latch.countDown() - } - return super.shouldInterceptRequest(view, request) - } - override fun onPageFinished(view: WebView?, url: String?) { - view?.clearCache(true) - view?.clearFormData() - val customHtml = """ - - - - Überprüfung - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
-

Dein Stream wird überprüft...

-

Falls nötig, löse bitte das Captcha, um die Serie weiterschauen zu können.

-
-
-
-
- -
- - -
-
- - -""" - if (!isDataLoaded) { - view?.loadDataWithBaseURL("https://www.google.com/", customHtml, "text/html", "UTF-8", null) - isDataLoaded = true - } - view?.evaluateJavascript(jsScript) {} - } - } - webView?.loadUrl(origRequestUrl, headers) - } - } - - latch.await(60, TimeUnit.SECONDS) - - handler.post { - webView?.stopLoading() - webView?.destroy() - webView = null - } - newRequest = GET(request.url.toString(), headers = Headers.headersOf("url", jsinterface.payload.substringAfter("uvresp\",\"").substringBefore("\","))) - return newRequest - } -} diff --git a/src/de/serienstream/src/eu/kanade/tachiyomi/animeextension/de/serienstream/RedirectInterceptor.kt b/src/de/serienstream/src/eu/kanade/tachiyomi/animeextension/de/serienstream/RedirectInterceptor.kt deleted file mode 100644 index 96d42094c..000000000 --- a/src/de/serienstream/src/eu/kanade/tachiyomi/animeextension/de/serienstream/RedirectInterceptor.kt +++ /dev/null @@ -1,99 +0,0 @@ -package eu.kanade.tachiyomi.animeextension.de.serienstream - -import android.annotation.SuppressLint -import android.app.Application -import android.os.Handler -import android.os.Looper -import android.webkit.WebResourceRequest -import android.webkit.WebResourceResponse -import android.webkit.WebView -import android.webkit.WebViewClient -import eu.kanade.tachiyomi.network.GET -import okhttp3.Headers.Companion.toHeaders -import okhttp3.Interceptor -import okhttp3.Request -import okhttp3.Response -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -import java.util.concurrent.CountDownLatch -import java.util.concurrent.TimeUnit - -class RedirectInterceptor : Interceptor { - - private val context = Injekt.get() - private val handler by lazy { Handler(Looper.getMainLooper()) } - - override fun intercept(chain: Interceptor.Chain): Response { - val originalRequest = chain.request() - - val newRequest = resolveWithWebView(originalRequest) ?: throw Exception("Versuche es später nochmal") - - return chain.proceed(newRequest) - } - - @SuppressLint("SetJavaScriptEnabled") - private fun resolveWithWebView(request: Request): Request? { - // We need to lock this thread until the WebView finds the challenge solution url, because - // OkHttp doesn't support asynchronous interceptors. - val latch = CountDownLatch(1) - - var webView: WebView? = null - - val origRequestUrl = request.url.toString() - - val headers = request.headers.toMultimap().mapValues { it.value.getOrNull(0) ?: "" }.toMutableMap() - - var newRequest: Request? = null - - var test = true - - handler.post { - val webview = WebView(context) - webView = webview - with(webview.settings) { - javaScriptEnabled = true - domStorageEnabled = true - databaseEnabled = true - useWideViewPort = false - loadWithOverviewMode = false - userAgentString = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:106.0) Gecko/20100101 Firefox/106.0" - } - - webview.webViewClient = object : WebViewClient() { - override fun shouldInterceptRequest( - view: WebView, - request: WebResourceRequest, - ): WebResourceResponse? { - if (request.url.toString().contains("payload")) { - newRequest = GET(request.url.toString(), request.requestHeaders.toHeaders()) - latch.countDown() - } else if (request.url.toString().contains("https://s.to/redirect/") && request.url.toString().contains("token")) { - newRequest = GET(request.url.toString(), request.requestHeaders.toHeaders()) - latch.countDown() - } else { - test = false - } - if (test == false) { - newRequest = GET(origRequestUrl, headers.toHeaders()) - latch.countDown() - } - - return super.shouldInterceptRequest(view, request) - } - } - webView?.loadUrl(origRequestUrl, headers) - } - - // Wait a reasonable amount of time to retrieve the solution. The minimum should be - // around 4 seconds but it can take more due to slow networks or server issues. - latch.await(12, TimeUnit.SECONDS) - - handler.post { - webView?.stopLoading() - webView?.destroy() - webView = null - } - - return newRequest - } -} diff --git a/src/de/serienstream/src/eu/kanade/tachiyomi/animeextension/de/serienstream/SConstants.kt b/src/de/serienstream/src/eu/kanade/tachiyomi/animeextension/de/serienstream/SConstants.kt index 6f84dd7ce..d8f0b8a7e 100644 --- a/src/de/serienstream/src/eu/kanade/tachiyomi/animeextension/de/serienstream/SConstants.kt +++ b/src/de/serienstream/src/eu/kanade/tachiyomi/animeextension/de/serienstream/SConstants.kt @@ -33,7 +33,7 @@ object SConstants { const val PASSWORD_TITLE = "Passwort" const val PASSWORD_DEFAULT = "" - const val LOGIN_URL = "https://s.to/login" + const val LOGIN_URL = "http://186.2.175.5/login" fun getPrefBaseLogin(preferences: SharedPreferences): String = preferences.getString(LOGIN_TITLE, LOGIN_DEFAULT)!! fun getPrefBasePassword(preferences: SharedPreferences): String = preferences.getString(PASSWORD_TITLE, PASSWORD_DEFAULT)!! diff --git a/src/de/serienstream/src/eu/kanade/tachiyomi/animeextension/de/serienstream/Serienstream.kt b/src/de/serienstream/src/eu/kanade/tachiyomi/animeextension/de/serienstream/Serienstream.kt index f551d20cf..a2a6353f1 100644 --- a/src/de/serienstream/src/eu/kanade/tachiyomi/animeextension/de/serienstream/Serienstream.kt +++ b/src/de/serienstream/src/eu/kanade/tachiyomi/animeextension/de/serienstream/Serienstream.kt @@ -43,7 +43,7 @@ class Serienstream : ConfigurableAnimeSource, ParsedAnimeHttpSource() { override val name = "Serienstream" - override val baseUrl = "https://s.to" + override val baseUrl = "http://186.2.175.5" private val baseLogin by lazy { SConstants.getPrefBaseLogin(preferences) } private val basePassword by lazy { SConstants.getPrefBasePassword(preferences) } @@ -56,11 +56,11 @@ class Serienstream : ConfigurableAnimeSource, ParsedAnimeHttpSource() { Injekt.get().getSharedPreferences("source_$id", 0x0000) } - override val client: OkHttpClient = network.client.newBuilder() + override val client: OkHttpClient = network.cloudflareClient.newBuilder() .addInterceptor(DdosGuardInterceptor(network.client)) .build() - private val authClient = network.client.newBuilder() + private val authClient = network.cloudflareClient.newBuilder() .addInterceptor(SerienstreamInterceptor(client, preferences)) .build() @@ -107,7 +107,7 @@ class Serienstream : ConfigurableAnimeSource, ParsedAnimeHttpSource() { override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request { val headers = Headers.Builder() - .add("Referer", "https://s.to/search") + .add("Referer", "http://186.2.175.5/search") .add("origin", baseUrl) .add("connection", "keep-alive") .add("user-agent", "Mozilla/5.0 (Linux; Android 12; Pixel 5 Build/SP2A.220405.004; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/100.0.4896.127 Safari/537.36") @@ -226,8 +226,6 @@ class Serienstream : ConfigurableAnimeSource, ParsedAnimeHttpSource() { val redirectlink = document.select("ul.row li") val videoList = mutableListOf