From 7a17f3ca3c1c569dc2221330dcb4422a43381e88 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Wed, 22 Jul 2026 23:06:12 +0200 Subject: [PATCH] nsig solver: solve with js instead of v8 --- youtubeapi/build.gradle | 201 +++++++++--------- youtubeapi/consumer-rules.pro | 4 + .../runtime/ChallengeProviderTest.kt | 8 +- .../youtubeapi/app/potoken/PoToken.kt | 10 +- .../youtubeapi/app/potoken/PoTokenApiTest.kt | 6 +- .../googlecommon/common/js/JSInterpret.kt | 10 +- .../common/js/JavaScriptRuntime.java | 162 ++++++++++++++ .../nsigsolver/impl/JsChallangeProvider.kt | 97 +++++++++ .../ClientPlaybackNonceExtractor.kt | 10 +- .../app/playerdata/PlayerDataExtractor.kt | 14 +- .../app/playerdata/PlayerDataExtractorOld.kt | 10 +- .../youtubeapi/app/playerdata/SigExtractor.kt | 6 +- 12 files changed, 407 insertions(+), 131 deletions(-) create mode 100644 youtubeapi/consumer-rules.pro create mode 100644 youtubeapi/src/main/java/com/liskovsoft/googlecommon/common/js/JavaScriptRuntime.java create mode 100644 youtubeapi/src/main/java/com/liskovsoft/youtubeapi/app/nsigsolver/impl/JsChallangeProvider.kt diff --git a/youtubeapi/build.gradle b/youtubeapi/build.gradle index 1afa3fafe..2283cf15d 100644 --- a/youtubeapi/build.gradle +++ b/youtubeapi/build.gradle @@ -3,106 +3,111 @@ apply plugin: 'kotlin-android' apply plugin: 'com.android.library' android { - compileSdkVersion project.ext.compileSdkVersion - buildToolsVersion project.ext.buildToolsVersion - testOptions.unitTests.includeAndroidResources = true - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - - defaultConfig { - minSdkVersion project.ext.minSdkVersion - targetSdkVersion project.ext.targetSdkVersion - versionCode 15 - versionName "0.1.5" - - // required for androidTest support - // more info: https://developer.android.com/training/testing/unit-testing/instrumented-unit-tests - // small tests: https://stackoverflow.com/questions/4671923/what-is-the-purpose-of-smalltest-mediumtest-and-largetest-annotations-in-an - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - - // More info: http://myhexaville.com/2017/03/10/android-multidex/ - // Additionally, you should extend your application from MultiDexApplication - multiDexEnabled = true - - // consumerProguardFiles 'proguard-rules.txt' // embed this file to aar - } - - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - } - } - - // gradle 4.6 migration: disable dimensions mechanism - // more: https://proandroiddev.com/advanced-android-flavors-part-4-a-new-version-fc2ad80c01bb - flavorDimensions "default" - - productFlavors { - stbeta {} - ststable {} - stfdroid {} + compileSdkVersion project.ext.compileSdkVersion + buildToolsVersion project.ext.buildToolsVersion + testOptions.unitTests.includeAndroidResources = true + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + defaultConfig { + minSdkVersion project.ext.minSdkVersion + targetSdkVersion project.ext.targetSdkVersion + versionCode 15 + versionName "0.1.5" + + // required for androidTest support + // more info: https://developer.android.com/training/testing/unit-testing/instrumented-unit-tests + // small tests: https://stackoverflow.com/questions/4671923/what-is-the-purpose-of-smalltest-mediumtest-and-largetest-annotations-in-an + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + + // More info: http://myhexaville.com/2017/03/10/android-multidex/ + // Additionally, you should extend your application from MultiDexApplication + multiDexEnabled = true + + consumerProguardFiles 'consumer-rules.pro' + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } + } + + // gradle 4.6 migration: disable dimensions mechanism + // more: https://proandroiddev.com/advanced-android-flavors-part-4-a-new-version-fc2ad80c01bb + flavorDimensions "default" + + productFlavors { + stbeta {} + // The active JavaScriptEngine implementation requires API 26. + ststable { minSdkVersion 26 } + stfdroid {} + } } dependencies { - implementation fileTree(dir: 'libs', include: ['*.jar']) - - testImplementation project(':youtubeapi') - testImplementation project(':sharedtests') - testImplementation 'co.infinum:retromock:' + retromockVersion - testImplementation 'junit:junit:' + junitVersion - testImplementation 'org.robolectric:robolectric:' + robolectricVersion - - androidTestImplementation project(':youtubeapi') - androidTestImplementation project(':sharedtests') - androidTestImplementation 'junit:junit:' + junitVersion - androidTestImplementation 'androidx.test:runner:' + testXSupportVersion - androidTestImplementation 'androidx.test:rules:' + testXSupportVersion - - implementation project(':commons-io-2.8.0') // My Android 4.3 fix - implementation project(':sharedutils') - implementation project(':mediaserviceinterfaces') - - implementation 'com.squareup.retrofit2:retrofit:' + retrofitVersion - implementation 'com.squareup.retrofit2:converter-gson:' + retrofitVersion - implementation 'com.google.code.gson:gson:' + gsonVersion - implementation 'com.jayway.jsonpath:json-path:' + jsonPathVersion - - implementation 'com.google.net.cronet:cronet-okhttp:' + cronetOkHttpVersion - implementation 'org.chromium.net:cronet-api:' + cronetApiVersion - - implementation 'io.reactivex.rxjava2:rxandroid:' + rxAndroidVersion - implementation 'io.reactivex.rxjava2:rxjava:' + rxJavaVersion - - implementation 'androidx.annotation:annotation:' + annotationXVersion - - // js interpreter - // duktape fork - // implementation 'app.cash.quickjs:quickjs-android:' + quickjsVersion - //implementation 'com.squareup.duktape:duktape-android:' + duktapeVersion - //implementation 'org.mozilla:rhino:' + rhinoVersion - //implementation 'com.eclipsesource.j2v8:j2v8:' + j2v8Version + '@aar' - // https://mvnrepository.com/artifact/com.eclipsesource.j2v8/j2v8/6.2.1 - //implementation(name: 'j2v8-6.2.1', ext: 'aar') - //implementation files('libs/j2v8-6.2.1-fixed.aar') - // https://stackoverflow.com/questions/60878599/error-building-android-library-direct-local-aar-file-dependencies-are-not-supp - stbetaImplementation project(':j2v8') - ststableImplementation project(':j2v8') - stfdroidImplementation 'com.eclipsesource.j2v8:j2v8:' + j2v8Version + '@aar' - - // Add kotlin support libs - implementation 'androidx.core:core-ktx:' + kotlinCoreXVersion - implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk7:' + kotlinVersion - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:' + kotlinVersion - - // https://github.com/florianingerl/com.florianingerl.util.regex - implementation 'com.github.florianingerl.util:regex:' + florianingerlRegexVersion - - // NewPipe pot - implementation 'androidx.webkit:webkit:' + webkitVersion - implementation 'com.grack:nanojson:' + nanojsonVersion + implementation fileTree(dir: 'libs', include: ['*.jar']) + + testImplementation project(':youtubeapi') + testImplementation project(':sharedtests') + testImplementation 'co.infinum:retromock:' + retromockVersion + testImplementation 'junit:junit:' + junitVersion + testImplementation 'org.robolectric:robolectric:' + robolectricVersion + + androidTestImplementation project(':youtubeapi') + androidTestImplementation project(':sharedtests') + androidTestImplementation 'junit:junit:' + junitVersion + androidTestImplementation 'androidx.test:runner:' + testXSupportVersion + androidTestImplementation 'androidx.test:rules:' + testXSupportVersion + + implementation project(':commons-io-2.8.0') // My Android 4.3 fix + implementation project(':sharedutils') + implementation project(':mediaserviceinterfaces') + + implementation 'com.squareup.retrofit2:retrofit:' + retrofitVersion + implementation 'com.squareup.retrofit2:converter-gson:' + retrofitVersion + implementation 'com.google.code.gson:gson:' + gsonVersion + implementation 'com.jayway.jsonpath:json-path:' + jsonPathVersion + + implementation 'com.google.net.cronet:cronet-okhttp:' + cronetOkHttpVersion + implementation 'org.chromium.net:cronet-api:' + cronetApiVersion + + implementation 'io.reactivex.rxjava2:rxandroid:' + rxAndroidVersion + implementation 'io.reactivex.rxjava2:rxjava:' + rxJavaVersion + + implementation 'androidx.annotation:annotation:' + annotationXVersion + // Beta01 remains compatible with SmartTube's AGP 7.4.2 build. + implementation 'androidx.javascriptengine:javascriptengine:1.0.0-beta01' + + // js interpreter + // duktape fork + // implementation 'app.cash.quickjs:quickjs-android:' + quickjsVersion + //implementation 'com.squareup.duktape:duktape-android:' + duktapeVersion + //implementation 'org.mozilla:rhino:' + rhinoVersion + //implementation 'com.eclipsesource.j2v8:j2v8:' + j2v8Version + '@aar' + // https://mvnrepository.com/artifact/com.eclipsesource.j2v8/j2v8/6.2.1 + //implementation(name: 'j2v8-6.2.1', ext: 'aar') + //implementation files('libs/j2v8-6.2.1-fixed.aar') + // https://stackoverflow.com/questions/60878599/error-building-android-library-direct-local-aar-file-dependencies-are-not-supp + stbetaImplementation project(':j2v8') + // Keep the unused V8 implementation compilable without publishing or + // packaging its native libraries in the stable artifact. + ststableCompileOnly project(':j2v8') + stfdroidImplementation 'com.eclipsesource.j2v8:j2v8:' + j2v8Version + '@aar' + + // Add kotlin support libs + implementation 'androidx.core:core-ktx:' + kotlinCoreXVersion + implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk7:' + kotlinVersion + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:' + kotlinxVersion + + // https://github.com/florianingerl/com.florianingerl.util.regex + implementation 'com.github.florianingerl.util:regex:' + florianingerlRegexVersion + + // NewPipe pot + implementation 'androidx.webkit:webkit:' + webkitVersion + implementation 'com.grack:nanojson:' + nanojsonVersion } diff --git a/youtubeapi/consumer-rules.pro b/youtubeapi/consumer-rules.pro new file mode 100644 index 000000000..7f9084458 --- /dev/null +++ b/youtubeapi/consumer-rules.pro @@ -0,0 +1,4 @@ +# The stable artifact intentionally compiles, but does not package, the legacy +# J2V8 backend. It is unreachable while JavaScriptEngine is the selected +# implementation, so consumers may safely ignore its optional class references. +-dontwarn com.eclipsesource.v8.** diff --git a/youtubeapi/src/androidTest/java/com/liskovsoft/youtubeapi/app/nsigsolver/runtime/ChallengeProviderTest.kt b/youtubeapi/src/androidTest/java/com/liskovsoft/youtubeapi/app/nsigsolver/runtime/ChallengeProviderTest.kt index 9ea187b13..8427d212e 100644 --- a/youtubeapi/src/androidTest/java/com/liskovsoft/youtubeapi/app/nsigsolver/runtime/ChallengeProviderTest.kt +++ b/youtubeapi/src/androidTest/java/com/liskovsoft/youtubeapi/app/nsigsolver/runtime/ChallengeProviderTest.kt @@ -2,7 +2,7 @@ package com.liskovsoft.youtubeapi.app.nsigsolver.runtime import androidx.test.platform.app.InstrumentationRegistry import com.liskovsoft.sharedutils.prefs.GlobalPreferences -import com.liskovsoft.youtubeapi.app.nsigsolver.impl.V8ChallengeProvider +import com.liskovsoft.youtubeapi.app.nsigsolver.impl.JsChallangeProvider import com.liskovsoft.youtubeapi.app.nsigsolver.provider.ChallengeInput import com.liskovsoft.youtubeapi.app.nsigsolver.provider.ChallengeOutput import com.liskovsoft.youtubeapi.app.nsigsolver.provider.JsChallengeProviderResponse @@ -86,13 +86,13 @@ internal class ChallengeProviderTest { @Test fun testNonEmptyResponse() { - val actual = V8ChallengeProvider.bulkSolve(tests).toList() + val actual = JsChallangeProvider.bulkSolve(tests).toList() assertEquals(responses, actual) } @Test fun testSingleNRequest() { - val result = V8ChallengeProvider.bulkSolve(listOf(tests.first())).toList() + val result = JsChallangeProvider.bulkSolve(listOf(tests.first())).toList() assertTrue("not empty", result.isNotEmpty()) } -} \ No newline at end of file +} diff --git a/youtubeapi/src/androidTest/java/com/liskovsoft/youtubeapi/app/potoken/PoToken.kt b/youtubeapi/src/androidTest/java/com/liskovsoft/youtubeapi/app/potoken/PoToken.kt index 0535e639b..154ea5563 100644 --- a/youtubeapi/src/androidTest/java/com/liskovsoft/youtubeapi/app/potoken/PoToken.kt +++ b/youtubeapi/src/androidTest/java/com/liskovsoft/youtubeapi/app/potoken/PoToken.kt @@ -3,7 +3,7 @@ package com.liskovsoft.youtubeapi.app.potoken import com.google.gson.JsonElement import com.liskovsoft.sharedutils.TestHelpers import com.liskovsoft.googlecommon.common.helpers.RetrofitHelper -import com.liskovsoft.googlecommon.common.js.V8Runtime +import com.liskovsoft.googlecommon.common.js.JavaScriptRuntime internal class PoToken { data class Arguments(val privateScript: String?, @@ -39,7 +39,7 @@ internal class PoToken { """.trimIndent() ) - val poToken = V8Runtime.instance().evaluate(script.joinToString("")) + val poToken = JavaScriptRuntime.evaluate(script.joinToString("")) return Result(poToken, bgResult.integrityTokenData) } @@ -102,8 +102,8 @@ internal class PoToken { "result.toString();" ) - val result = V8Runtime.instance().evaluate(script) ?: return null - //val result = V8Runtime.instance().evaluate(script.joinToString("")) + val result = JavaScriptRuntime.evaluate(script) ?: return null + //val result = JavaScriptRuntime.evaluate(script.joinToString("")) val (postProcessFunction, payload) = result.split(RESULT_DELIM) @@ -127,4 +127,4 @@ internal class PoToken { private fun asInt(intElem: JsonElement?) = if (intElem?.isJsonNull == true) null else intElem?.asInt private fun asString(strElem: JsonElement?) = if (strElem?.isJsonNull == true) null else strElem?.asString -} \ No newline at end of file +} diff --git a/youtubeapi/src/androidTest/java/com/liskovsoft/youtubeapi/app/potoken/PoTokenApiTest.kt b/youtubeapi/src/androidTest/java/com/liskovsoft/youtubeapi/app/potoken/PoTokenApiTest.kt index 0e5721607..78eea1b8d 100644 --- a/youtubeapi/src/androidTest/java/com/liskovsoft/youtubeapi/app/potoken/PoTokenApiTest.kt +++ b/youtubeapi/src/androidTest/java/com/liskovsoft/youtubeapi/app/potoken/PoTokenApiTest.kt @@ -2,7 +2,7 @@ package com.liskovsoft.youtubeapi.app.potoken import com.liskovsoft.sharedutils.TestHelpers import com.liskovsoft.googlecommon.common.helpers.RetrofitHelper -import com.liskovsoft.googlecommon.common.js.V8Runtime +import com.liskovsoft.googlecommon.common.js.JavaScriptRuntime import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Assert.assertTrue @@ -78,8 +78,8 @@ class PoTokenApiTest { "var vm = trayride; vm.a('${program}', () => {}, true, undefined, () => {}).toString();" ) - val result = V8Runtime.instance().evaluate(script.joinToString("")) + val result = JavaScriptRuntime.evaluate(script.joinToString("")) assertNotNull(result) } -} \ No newline at end of file +} diff --git a/youtubeapi/src/main/java/com/liskovsoft/googlecommon/common/js/JSInterpret.kt b/youtubeapi/src/main/java/com/liskovsoft/googlecommon/common/js/JSInterpret.kt index 2d9f26f77..dca08b135 100644 --- a/youtubeapi/src/main/java/com/liskovsoft/googlecommon/common/js/JSInterpret.kt +++ b/youtubeapi/src/main/java/com/liskovsoft/googlecommon/common/js/JSInterpret.kt @@ -13,7 +13,7 @@ internal object JSInterpret { return { args: List -> val fullCode = "(function (${argNames.joinToString(separator = ",")}) { $code })(${args.joinToString(separator = ",", prefix = "'", postfix = "'")})" - V8Runtime.instance().evaluateWithErrors(fullCode) + JavaScriptRuntime.evaluate(fullCode) } } @@ -46,7 +46,11 @@ internal object JSInterpret { } fun interpretExpression(jsCode: String): List? { - val result = V8Runtime.instance().evaluate("JSON.stringify($jsCode)") + val result = try { + JavaScriptRuntime.evaluate("JSON.stringify($jsCode)") + } catch (error: JavaScriptRuntime.JavaScriptRuntimeException) { + null + } val gson = Gson() val listType = object : TypeToken>() {}.type @@ -171,4 +175,4 @@ internal object JSInterpret { return null } -} \ No newline at end of file +} diff --git a/youtubeapi/src/main/java/com/liskovsoft/googlecommon/common/js/JavaScriptRuntime.java b/youtubeapi/src/main/java/com/liskovsoft/googlecommon/common/js/JavaScriptRuntime.java new file mode 100644 index 000000000..b54db9683 --- /dev/null +++ b/youtubeapi/src/main/java/com/liskovsoft/googlecommon/common/js/JavaScriptRuntime.java @@ -0,0 +1,162 @@ +package com.liskovsoft.googlecommon.common.js; + +import android.webkit.CookieManager; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.javascriptengine.IsolateStartupParameters; +import androidx.javascriptengine.JavaScriptIsolate; +import androidx.javascriptengine.JavaScriptSandbox; + +import com.liskovsoft.youtubeapi.app.AppService; + +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * Process-wide owner of the System WebView-backed AndroidX JavaScriptEngine. + * Each independent caller can create an isolate through {@link Session}. + */ +public final class JavaScriptRuntime { + private static final long CONNECT_TIMEOUT_SECONDS = 20; + private static final long EVALUATION_TIMEOUT_SECONDS = 60; + private static final int MAX_HEAP_SIZE_BYTES = 128 * 1024 * 1024; + private static final Object SANDBOX_LOCK = new Object(); + + private static JavaScriptSandbox sSandbox; + + private JavaScriptRuntime() { + } + + public static boolean isSupported() { + try { + CookieManager.getInstance(); + return JavaScriptSandbox.isSupported(); + } catch (Throwable ignored) { + return false; + } + } + + @NonNull + public static Session createSession() throws JavaScriptRuntimeException { + synchronized (SANDBOX_LOCK) { + JavaScriptSandbox sandbox = getOrCreateSandbox(); + JavaScriptIsolate isolate; + + if (sandbox.isFeatureSupported(JavaScriptSandbox.JS_FEATURE_ISOLATE_MAX_HEAP_SIZE)) { + IsolateStartupParameters parameters = new IsolateStartupParameters(); + parameters.setMaxHeapSizeBytes(MAX_HEAP_SIZE_BYTES); + isolate = sandbox.createIsolate(parameters); + } else { + isolate = sandbox.createIsolate(); + } + + return new Session(isolate); + } + } + + @NonNull + public static String evaluate(@NonNull String source) throws JavaScriptRuntimeException { + try (Session session = createSession()) { + return session.evaluate(source); + } + } + + @Nullable + public static String evaluate(@NonNull List sources) throws JavaScriptRuntimeException { + try (Session session = createSession()) { + String result = null; + for (String source : sources) { + result = session.evaluate(source); + } + return result; + } + } + + public static void close() { + synchronized (SANDBOX_LOCK) { + if (sSandbox != null) { + try { + sSandbox.close(); + } finally { + sSandbox = null; + } + } + } + } + + @NonNull + private static JavaScriptSandbox getOrCreateSandbox() throws JavaScriptRuntimeException { + if (sSandbox != null) { + return sSandbox; + } + if (!isSupported()) { + throw new JavaScriptRuntimeException( + "AndroidX JavaScriptEngine is not supported by this device/System WebView"); + } + + try { + sSandbox = JavaScriptSandbox.createConnectedInstanceAsync( + AppService.instance().getContext().getApplicationContext()) + .get(CONNECT_TIMEOUT_SECONDS, TimeUnit.SECONDS); + return sSandbox; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new JavaScriptRuntimeException("Interrupted while connecting to JavaScriptEngine", e); + } catch (ExecutionException | TimeoutException e) { + throw new JavaScriptRuntimeException("Could not connect to JavaScriptEngine", unwrap(e)); + } + } + + @NonNull + private static Throwable unwrap(@NonNull Exception error) { + Throwable cause = error instanceof ExecutionException ? error.getCause() : error; + return cause != null ? cause : error; + } + + public static final class Session implements AutoCloseable { + private JavaScriptIsolate mIsolate; + + private Session(@NonNull JavaScriptIsolate isolate) { + mIsolate = isolate; + } + + @NonNull + public synchronized String evaluate(@NonNull String source) + throws JavaScriptRuntimeException { + if (mIsolate == null) { + throw new JavaScriptRuntimeException("JavaScript isolate is closed"); + } + + try { + return mIsolate.evaluateJavaScriptAsync(source) + .get(EVALUATION_TIMEOUT_SECONDS, TimeUnit.SECONDS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new JavaScriptRuntimeException("JavaScript evaluation was interrupted", e); + } catch (ExecutionException | TimeoutException e) { + throw new JavaScriptRuntimeException("JavaScript evaluation failed", unwrap(e)); + } + } + + @Override + public synchronized void close() { + if (mIsolate != null) { + mIsolate.close(); + mIsolate = null; + } + } + } + + public static final class JavaScriptRuntimeException extends Exception { + public JavaScriptRuntimeException(@NonNull String message) { + super(message); + } + + public JavaScriptRuntimeException(@NonNull String message, @NonNull Throwable cause) { + super(message, cause); + } + } +} diff --git a/youtubeapi/src/main/java/com/liskovsoft/youtubeapi/app/nsigsolver/impl/JsChallangeProvider.kt b/youtubeapi/src/main/java/com/liskovsoft/youtubeapi/app/nsigsolver/impl/JsChallangeProvider.kt new file mode 100644 index 000000000..91f0dbec0 --- /dev/null +++ b/youtubeapi/src/main/java/com/liskovsoft/youtubeapi/app/nsigsolver/impl/JsChallangeProvider.kt @@ -0,0 +1,97 @@ +package com.liskovsoft.youtubeapi.app.nsigsolver.impl + +import com.liskovsoft.googlecommon.common.js.JavaScriptRuntime +import com.liskovsoft.googlecommon.common.js.JavaScriptRuntime.JavaScriptRuntimeException +import com.liskovsoft.youtubeapi.app.nsigsolver.common.loadScript +import com.liskovsoft.youtubeapi.app.nsigsolver.provider.JsChallengeProviderError +import com.liskovsoft.youtubeapi.app.nsigsolver.runtime.JsRuntimeChalBaseJCP +import com.liskovsoft.youtubeapi.app.nsigsolver.runtime.Script +import com.liskovsoft.youtubeapi.app.nsigsolver.runtime.ScriptSource +import com.liskovsoft.youtubeapi.app.nsigsolver.runtime.ScriptType +import com.liskovsoft.youtubeapi.app.nsigsolver.runtime.ScriptVariant + +/** Runs player challenges in the System WebView's isolated JavaScript engine. */ +internal object JsChallangeProvider : JsRuntimeChalBaseJCP() { + private val npmLibFilenames = listOf( + "${libPrefix}polyfill.js", + "${libPrefix}meriyah-6.1.4.min.js", + "${libPrefix}astring-1.9.0.min.js" + ) + private val runtimeLock = Any() + private var session: JavaScriptRuntime.Session? = null + + override fun iterScriptSources(): Sequence Script?>> = sequence { + for ((source, func) in super.iterScriptSources()) { + if (source == ScriptSource.WEB || source == ScriptSource.BUILTIN) { + yield(Pair(ScriptSource.BUILTIN, ::npmSource)) + } + yield(Pair(source, func)) + } + } + + private fun npmSource(scriptType: ScriptType): Script? { + if (scriptType != ScriptType.LIB) return null + + val code = loadScript(npmLibFilenames, "Failed to read JS challenge solver lib script") + return Script(scriptType, ScriptVariant.V8_NPM, ScriptSource.BUILTIN, scriptVersion, code) + } + + override fun runJsRuntime(stdin: String): String = synchronized(runtimeLock) { + val activeSession = initRuntime() + + try { + activeSession.evaluate(stdin).takeIf { it.isNotEmpty() } + ?: throw JsChallengeProviderError("JavaScriptEngine returned an empty response") + } catch (error: JavaScriptRuntimeException) { + if (error.message?.contains("Invalid or unexpected token") == true) { + ie.cache.clear(cacheSection) + } + disposeRuntime() + throw JsChallengeProviderError("JavaScriptEngine error: ${error.message}", error) + } + } + + private fun initRuntime(): JavaScriptRuntime.Session { + session?.let { return it } + + val newSession = try { + JavaScriptRuntime.createSession() + } catch (error: JavaScriptRuntimeException) { + throw JsChallengeProviderError("Could not initialize JavaScriptEngine", error) + } + + try { + newSession.evaluate(constructCommonStdin()) + } catch (error: JavaScriptRuntimeException) { + newSession.close() + throw JsChallengeProviderError("Could not initialize the JS challenge solver", error) + } + + session = newSession + return newSession + } + + fun warmup() { + synchronized(runtimeLock) { + initRuntime() + } + } + + fun shutdown() { + synchronized(runtimeLock) { + disposeRuntime() + } + } + + fun forceRecreate() { + synchronized(runtimeLock) { + disposeRuntime() + initRuntime() + } + } + + private fun disposeRuntime() { + session?.close() + session = null + } +} diff --git a/youtubeapi/src/main/java/com/liskovsoft/youtubeapi/app/playerdata/ClientPlaybackNonceExtractor.kt b/youtubeapi/src/main/java/com/liskovsoft/youtubeapi/app/playerdata/ClientPlaybackNonceExtractor.kt index e93a3645e..b053421e1 100644 --- a/youtubeapi/src/main/java/com/liskovsoft/youtubeapi/app/playerdata/ClientPlaybackNonceExtractor.kt +++ b/youtubeapi/src/main/java/com/liskovsoft/youtubeapi/app/playerdata/ClientPlaybackNonceExtractor.kt @@ -1,7 +1,7 @@ package com.liskovsoft.youtubeapi.app.playerdata import com.liskovsoft.sharedutils.helpers.Helpers -import com.liskovsoft.googlecommon.common.js.V8Runtime +import com.liskovsoft.googlecommon.common.js.JavaScriptRuntime import java.util.regex.Pattern internal object ClientPlaybackNonceExtractor { @@ -34,7 +34,11 @@ internal object ClientPlaybackNonceExtractor { } fun createClientPlaybackNonce(clientPlaybackNonceCode: String): String? { - return V8Runtime.instance().evaluate(clientPlaybackNonceCode) + return try { + JavaScriptRuntime.evaluate(clientPlaybackNonceCode) + } catch (error: JavaScriptRuntime.JavaScriptRuntimeException) { + null + } } private fun extractRawClientPlaybackNonceFunction(jsCode: String): String? { @@ -53,4 +57,4 @@ internal object ClientPlaybackNonceExtractor { return null } -} \ No newline at end of file +} diff --git a/youtubeapi/src/main/java/com/liskovsoft/youtubeapi/app/playerdata/PlayerDataExtractor.kt b/youtubeapi/src/main/java/com/liskovsoft/youtubeapi/app/playerdata/PlayerDataExtractor.kt index beb8c6ff4..fda86303c 100644 --- a/youtubeapi/src/main/java/com/liskovsoft/youtubeapi/app/playerdata/PlayerDataExtractor.kt +++ b/youtubeapi/src/main/java/com/liskovsoft/youtubeapi/app/playerdata/PlayerDataExtractor.kt @@ -1,10 +1,10 @@ package com.liskovsoft.youtubeapi.app.playerdata -import com.eclipsesource.v8.V8ScriptExecutionException +import com.liskovsoft.googlecommon.common.js.JavaScriptRuntime.JavaScriptRuntimeException import com.liskovsoft.googlecommon.common.helpers.YouTubeHelper import com.liskovsoft.sharedutils.helpers.Helpers import com.liskovsoft.youtubeapi.app.nsigsolver.common.YouTubeInfoExtractor -import com.liskovsoft.youtubeapi.app.nsigsolver.impl.V8ChallengeProvider +import com.liskovsoft.youtubeapi.app.nsigsolver.impl.JsChallangeProvider import com.liskovsoft.youtubeapi.app.nsigsolver.provider.ChallengeInput import com.liskovsoft.youtubeapi.app.nsigsolver.provider.JsChallengeRequest import com.liskovsoft.youtubeapi.app.nsigsolver.provider.JsChallengeType @@ -108,7 +108,7 @@ internal class PlayerDataExtractor(val playerUrl: String) { JsChallengeRequest(JsChallengeType.SIG, ChallengeInput(fixedPlayerUrl, it)) } - val result = V8ChallengeProvider.bulkSolve(listOfNotNull(nRequest, sRequest)) + val result = JsChallangeProvider.bulkSolve(listOfNotNull(nRequest, sRequest)) for (item in result) { when (item.response?.type) { @@ -157,7 +157,7 @@ internal class PlayerDataExtractor(val playerUrl: String) { val result = createClientPlaybackNonce() if (result == null) cpnCode = null - } catch (error: V8ScriptExecutionException) { + } catch (error: JavaScriptRuntimeException) { cpnCode = null } } @@ -165,14 +165,14 @@ internal class PlayerDataExtractor(val playerUrl: String) { private fun checkSigData() { if (nFuncCode && sFuncCode) { - V8ChallengeProvider.warmup() // enable hot start + JsChallangeProvider.warmup() // enable hot start return } try { val nParam = "5cNpZqIJ7ixNqU68Y7S" val sigParam = "NJAJEij0EwRgIhAI0KExTgjfPk-MPM9MAdzyyPRt=BM8-XO5tm5hlMCSVpAiEAv7eP3CURqZNSPow8BXXAoazVoXgeMP7gH9BdylHCwgw=gwzz" - val result = V8ChallengeProvider.bulkSolve( + val result = JsChallangeProvider.bulkSolve( listOf( JsChallengeRequest(JsChallengeType.N, ChallengeInput(fixedPlayerUrl, listOf(nParam))), JsChallengeRequest(JsChallengeType.SIG, ChallengeInput(fixedPlayerUrl, listOf(sigParam))), @@ -193,4 +193,4 @@ internal class PlayerDataExtractor(val playerUrl: String) { e.printStackTrace() } } -} \ No newline at end of file +} diff --git a/youtubeapi/src/main/java/com/liskovsoft/youtubeapi/app/playerdata/PlayerDataExtractorOld.kt b/youtubeapi/src/main/java/com/liskovsoft/youtubeapi/app/playerdata/PlayerDataExtractorOld.kt index 14bcafa3e..1c1abe864 100644 --- a/youtubeapi/src/main/java/com/liskovsoft/youtubeapi/app/playerdata/PlayerDataExtractorOld.kt +++ b/youtubeapi/src/main/java/com/liskovsoft/youtubeapi/app/playerdata/PlayerDataExtractorOld.kt @@ -1,6 +1,6 @@ package com.liskovsoft.youtubeapi.app.playerdata -import com.eclipsesource.v8.V8ScriptExecutionException +import com.liskovsoft.googlecommon.common.js.JavaScriptRuntime.JavaScriptRuntimeException import com.liskovsoft.sharedutils.mylogger.Log import com.liskovsoft.youtubeapi.app.models.cached.PlayerDataCached import com.liskovsoft.googlecommon.common.js.JSInterpret @@ -153,7 +153,7 @@ internal class PlayerDataExtractorOld(val playerUrl: String) { val result = extractNSigReal(param) if (result == null || result == param) mNFuncCode = null - } catch (error: V8ScriptExecutionException) { + } catch (error: JavaScriptRuntimeException) { mNFuncCode = null } } @@ -164,7 +164,7 @@ internal class PlayerDataExtractorOld(val playerUrl: String) { val result = extractSigReal(param) if (result == null || result == param) mSigFuncCode = null - } catch (error: V8ScriptExecutionException) { + } catch (error: JavaScriptRuntimeException) { mSigFuncCode = null } } @@ -174,9 +174,9 @@ internal class PlayerDataExtractorOld(val playerUrl: String) { val result = createClientPlaybackNonce() if (result == null) mCPNCode = null - } catch (error: V8ScriptExecutionException) { + } catch (error: JavaScriptRuntimeException) { mCPNCode = null } } } -} \ No newline at end of file +} diff --git a/youtubeapi/src/main/java/com/liskovsoft/youtubeapi/app/playerdata/SigExtractor.kt b/youtubeapi/src/main/java/com/liskovsoft/youtubeapi/app/playerdata/SigExtractor.kt index 163ac978e..bb4a213a3 100644 --- a/youtubeapi/src/main/java/com/liskovsoft/youtubeapi/app/playerdata/SigExtractor.kt +++ b/youtubeapi/src/main/java/com/liskovsoft/youtubeapi/app/playerdata/SigExtractor.kt @@ -1,6 +1,6 @@ package com.liskovsoft.youtubeapi.app.playerdata -import com.eclipsesource.v8.V8ScriptExecutionException +import com.liskovsoft.googlecommon.common.js.JavaScriptRuntime.JavaScriptRuntimeException import com.liskovsoft.sharedutils.mylogger.Log import com.liskovsoft.googlecommon.common.js.JSInterpret import java.util.regex.Pattern @@ -62,7 +62,7 @@ internal object SigExtractor { // Test the function works try { extractSig(fixedFuncCode, "5cNpZqIJ7ixNqU68Y7S") - } catch (error: V8ScriptExecutionException) { + } catch (error: JavaScriptRuntimeException) { if (nestedCount > 1) return null @@ -105,4 +105,4 @@ internal object SigExtractor { return func(listOf(signature)) } -} \ No newline at end of file +}