Aniworld: update & reCapBypass v1.0 (#1302)

* AniWorld: add reCapBypass v0.1

First version of my recaptcha bypasser. Total early stage version.

* remove logs

* remove unused

* Aniworld: update & reCapBypass v1.0

- changed way of host url getting
- updated reCapBypass to v1.0 (can still be buggy sometimes)
This commit is contained in:
LuftVerbot
2023-02-20 16:02:04 +01:00
committed by GitHub
parent 800f8abf8e
commit 7594818fcb
4 changed files with 246 additions and 63 deletions

View File

@ -6,7 +6,7 @@ ext {
extName = 'AniWorld (experimental)'
pkgNameSuffix = 'de.aniworld'
extClass = '.AniWorld'
extVersionCode = 11
extVersionCode = 12
libVersion = '13'
}

View File

@ -230,7 +230,7 @@ class AniWorld : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
val videoList = mutableListOf<Video>()
val hosterSelection = preferences.getStringSet(AWConstants.HOSTER_SELECTION, null)
val redirectInterceptor = client.newBuilder().addInterceptor(RedirectInterceptor()).build()
val jsInterceptor = client.newBuilder().addInterceptor(JsInterceptor(network.client)).build()
val jsInterceptor = client.newBuilder().addInterceptor(JsInterceptor()).build()
redirectlink.forEach {
val langkey = it.attr("data-lang-key")
val language = getlanguage(langkey)
@ -241,7 +241,7 @@ class AniWorld : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
hoster.contains("VOE") && hosterSelection.contains(AWConstants.NAME_VOE) -> {
val quality = "Voe $language"
var url = redirectInterceptor.newCall(GET(redirectgs)).execute().request.url.toString()
if (url.contains("payload")) {
if (url.contains("payload") || url.contains(redirectgs)) {
url = recapbypass(jsInterceptor, redirectgs)
}
val video = VoeExtractor(client).videoFromUrl(url, quality)
@ -253,7 +253,7 @@ class AniWorld : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
hoster.contains("Doodstream") && hosterSelection.contains(AWConstants.NAME_DOOD) -> {
val quality = "Doodstream $language"
var url = redirectInterceptor.newCall(GET(redirectgs)).execute().request.url.toString()
if (url.contains("payload")) {
if (url.contains("payload") || url.contains(redirectgs)) {
url = recapbypass(jsInterceptor, redirectgs)
}
val video = DoodExtractor(client).videoFromUrl(url, quality)
@ -265,7 +265,7 @@ class AniWorld : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
hoster.contains("Streamtape") && hosterSelection.contains(AWConstants.NAME_STAPE) -> {
val quality = "Streamtape $language"
var url = redirectInterceptor.newCall(GET(redirectgs)).execute().request.url.toString()
if (url.contains("payload")) {
if (url.contains("payload") || url.contains(redirectgs)) {
url = recapbypass(jsInterceptor, redirectgs)
}
val video = StreamTapeExtractor(client).videoFromUrl(url, quality)
@ -276,7 +276,7 @@ class AniWorld : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
hoster.contains("Vidoza") && hosterSelection.contains(AWConstants.NAME_VIZ) -> {
val quality = "Vidoza $language"
var url = redirectInterceptor.newCall(GET(redirectgs)).execute().request.url.toString()
if (url.contains("payload")) {
if (url.contains("payload") || url.contains(redirectgs)) {
url = recapbypass(jsInterceptor, redirectgs)
}
val video = VidozaExtractor(client).videoFromUrl(url, quality)

View File

@ -5,47 +5,31 @@ 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 eu.kanade.tachiyomi.network.POST
import okhttp3.Headers
import okhttp3.Interceptor
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
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(private val client: OkHttpClient) : Interceptor {
class JsInterceptor : Interceptor {
private val context = Injekt.get<Application>()
private val handler by lazy { Handler(Looper.getMainLooper()) }
class JsObject(private val latch: CountDownLatch, private val client: OkHttpClient, var payload: String = "") {
class JsObject(private val latch: CountDownLatch, var payload: String = "") {
@JavascriptInterface
fun passPayload(passedPayload: String) {
payload = passedPayload
latch.countDown()
}
@JavascriptInterface
fun client(source: String): String? {
val body = "input=${java.net.URLEncoder.encode(source, "utf-8")}&lang=de".toRequestBody("application/x-www-form-urlencoded".toMediaType())
val solved = client.newCall(
POST(
"https://engageub.pythonanywhere.com", body = body,
headers = Headers.headersOf(
"Content-Type", "application/x-www-form-urlencoded",
"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"
)
)
).execute().body?.string()
return solved
}
}
override fun intercept(chain: Interceptor.Chain): Response {
@ -65,58 +49,241 @@ class JsInterceptor(private val client: OkHttpClient) : Interceptor {
val origRequestUrl = request.url.toString()
val jsinterface = JsObject(latch, client)
val jsinterface = JsObject(latch)
// JavaSrcipt bypass recaptcha FUCK GOOGLE RECAPTCHA v0.1
// JavaSrcipt bypass recaptcha FUCK GOOGLE RECAPTCHA v1.0
val jsScript = """
(function(){
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() {
let intervalIdA = setInterval(() => {
let iframewindow = document.querySelector('iframe[title="reCAPTCHA-Aufgabe läuft in zwei Minuten ab"]').contentWindow;
if (iframewindow) {
clearInterval(intervalIdA);
let audiobutton = iframewindow.document.querySelector('#recaptcha-audio-button');
let event = iframewindow.document.createEvent('HTMLEvents');
event.initEvent('click',false,false);
event.initEvent('click', false, false);
audiobutton.dispatchEvent(event);
let intervalIdB = setInterval(() => {
let intervalIdB = setInterval(async () => {
let source = iframewindow.document.querySelector('#audio-source').getAttribute('src');
if (source) {
clearInterval(intervalIdB);
let audioresponse = iframewindow.document.querySelector('#audio-response');
let verifybutton = iframewindow.document.querySelector('#recaptcha-verify-button');
var solved = window.android.client(source);
var tries = 0
while((solved == "0" || solved.includes("<") || solved.includes(">") || solved.length < 2 || solved.length > 50) && tries <= 3) {
solved = window.android.client(source);
if(solved == "0" || solved.includes("<") || solved.includes(">") || solved.length < 2 || solved.length > 50){
tries++;
} else {
tries = 3
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) {
window.android.passPayload("");
}
}
if(solved == "0" || solved.includes("<") || solved.includes(">") || solved.length < 2 || solved.length > 50){
window.android.passPayload("");
} else {
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);
}
};
}
};
}
}, 2000);
}
}, 2000);
}
}, 2000);
})();
})()
"""
val headers = request.headers.toMultimap().mapValues { it.value.getOrNull(0) ?: "" }.toMutableMap()
@ -137,6 +304,13 @@ class JsInterceptor(private val client: OkHttpClient) : Interceptor {
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()
@ -153,8 +327,8 @@ class JsInterceptor(private val client: OkHttpClient) : Interceptor {
webView?.loadUrl(origRequestUrl, headers)
}
}
latch.await(120, TimeUnit.SECONDS)
latch.await(60, TimeUnit.SECONDS)
handler.post {
webView?.stopLoading()

View File

@ -45,6 +45,8 @@ class RedirectInterceptor : Interceptor {
var newRequest: Request? = null
var test = true
handler.post {
val webview = WebView(context)
webView = webview
@ -62,14 +64,21 @@ class RedirectInterceptor : Interceptor {
view: WebView,
request: WebResourceRequest,
): WebResourceResponse? {
if (request.url.toString().contains("payload")) {
newRequest = GET(request.url.toString(), request.requestHeaders.toHeaders())
latch.countDown()
}
if (request.url.toString().contains("https://aniworld.to/redirect/") && request.url.toString().contains("token")) {
} else if (request.url.toString().contains("https://aniworld.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)
}
}