mirror of
https://gitlab.futo.org/videostreaming/grayjay.git
synced 2026-05-16 04:52:39 +02:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ca971a0e77 | |||
| a45a0f9a8a | |||
| c2dce52a5b | |||
| a2c63c59c5 | |||
| 7e54a2ce3d | |||
| 5b7fb2c818 | |||
| da0ac281e2 | |||
| 576b37f64c | |||
| 26c2db5023 | |||
| f344dbf35c | |||
| a04acbd4a5 | |||
| bd48aba8d3 | |||
| 12b73bb248 | |||
| c3ff897ef4 | |||
| 242728fbe7 | |||
| 14df7c8d43 |
@@ -217,6 +217,9 @@ function pluginUpdateTestPlugin(config) {
|
||||
}
|
||||
function pluginLoginTestPlugin() {
|
||||
return syncGET("/plugin/loginTestPlugin", {});
|
||||
}//captchaLoginTestPlugin
|
||||
function pluginCaptchaTestPlugin(url, html) {
|
||||
return syncPOST("/plugin/captchaTestPlugin?url=" + url, {}, html);
|
||||
}
|
||||
function pluginLogoutTestPlugin() {
|
||||
return syncGET("/plugin/logoutTestPlugin", {});
|
||||
|
||||
@@ -681,6 +681,9 @@
|
||||
});
|
||||
}, 1000);
|
||||
},
|
||||
captchaTestPlugin() {
|
||||
captchaLoginTestPlugin();
|
||||
},
|
||||
logoutTestPlugin() {
|
||||
pluginLogoutTestPlugin();
|
||||
},
|
||||
@@ -838,6 +841,12 @@
|
||||
this.Testing.lastResultError = "";
|
||||
}
|
||||
catch(ex) {
|
||||
if(ex.plugin_type == "CaptchaRequiredException") {
|
||||
let shouldCaptcha = confirm("Do you want to request captcha?");
|
||||
if(shouldCaptcha) {
|
||||
pluginCaptchaTestPlugin(ex.url, ex.body);
|
||||
}
|
||||
}
|
||||
console.error("Failed to run test for " + req.title, ex);
|
||||
this.Testing.lastResult = ""
|
||||
if(ex.message)
|
||||
|
||||
@@ -72,6 +72,11 @@ class CaptchaRequiredException extends Error {
|
||||
this.body = body;
|
||||
}
|
||||
}
|
||||
class CriticalException extends ScriptException {
|
||||
constructor(msg) {
|
||||
super("CriticalException", msg);
|
||||
}
|
||||
}
|
||||
class UnavailableException extends ScriptException {
|
||||
constructor(msg) {
|
||||
super("UnavailableException", msg);
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.webkit.CookieManager
|
||||
import androidx.core.content.ContextCompat.startActivity
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.futo.platformplayer.activities.*
|
||||
import com.futo.platformplayer.api.http.ManagedHttpClient
|
||||
@@ -44,7 +45,7 @@ class Settings : FragmentedStorageFileJson() {
|
||||
|
||||
@FormField(
|
||||
"Manage Polycentric identity", FieldForm.BUTTON,
|
||||
"Manage your Polycentric identity", -2
|
||||
"Manage your Polycentric identity", -3
|
||||
)
|
||||
fun managePolycentricIdentity() {
|
||||
SettingsActivity.getActivity()?.let {
|
||||
@@ -56,6 +57,19 @@ class Settings : FragmentedStorageFileJson() {
|
||||
}
|
||||
}
|
||||
|
||||
@FormField(
|
||||
"Open FAQ", FieldForm.BUTTON,
|
||||
"Get answers to common questions", -2
|
||||
)
|
||||
fun openFAQ() {
|
||||
try {
|
||||
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse("https://grayjay.app/faq.html"))
|
||||
SettingsActivity.getActivity()?.startActivity(browserIntent);
|
||||
} catch (e: Throwable) {
|
||||
//Ignored
|
||||
}
|
||||
}
|
||||
|
||||
@FormField(
|
||||
"Submit feedback", FieldForm.BUTTON,
|
||||
"Give feedback on the application", -1
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.futo.platformplayer.logging.Logger
|
||||
import okhttp3.Call
|
||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import okhttp3.Response
|
||||
@@ -28,7 +29,11 @@ open class ManagedHttpClient {
|
||||
|
||||
constructor(builder: OkHttpClient.Builder = OkHttpClient.Builder()) {
|
||||
_builderTemplate = builder;
|
||||
client = builder.build();
|
||||
client = builder.addNetworkInterceptor { chain ->
|
||||
val request = beforeRequest(chain.request());
|
||||
val response = afterRequest(chain.proceed(request));
|
||||
return@addNetworkInterceptor response;
|
||||
}.build();
|
||||
}
|
||||
|
||||
open fun clone(): ManagedHttpClient {
|
||||
@@ -116,7 +121,7 @@ open class ManagedHttpClient {
|
||||
fun execute(request : Request) : Response {
|
||||
ensureNotMainThread();
|
||||
|
||||
beforeRequest(request);
|
||||
//beforeRequest(request);
|
||||
|
||||
Logger.v(TAG, "HTTP Request [${request.method}] ${request.url} - [${if(request.body != null) request.body.size else 0}]");
|
||||
|
||||
@@ -156,23 +161,16 @@ open class ManagedHttpClient {
|
||||
if(true)
|
||||
Logger.v(TAG, "HTTP Response [${request.method}] ${request.url} - [${time}ms]");
|
||||
|
||||
afterRequest(request, resp);
|
||||
//afterRequest(request, resp);
|
||||
return resp;
|
||||
}
|
||||
|
||||
//Set Listeners
|
||||
fun setOnBeforeRequest(listener : (Request)->Unit) {
|
||||
this.onBeforeRequest = listener;
|
||||
open fun beforeRequest(request: okhttp3.Request): okhttp3.Request {
|
||||
return request;
|
||||
}
|
||||
fun setOnAfterRequest(listener : (Request, Response)->Unit) {
|
||||
this.onAfterRequest = listener;
|
||||
}
|
||||
|
||||
open fun beforeRequest(request: Request) {
|
||||
onBeforeRequest?.invoke(request);
|
||||
}
|
||||
open fun afterRequest(request: Request, resp: Response) {
|
||||
onAfterRequest?.invoke(request, resp);
|
||||
open fun afterRequest(resp: okhttp3.Response): okhttp3.Response {
|
||||
return resp;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.futo.platformplayer.api.media.models.comments.IPlatformComment
|
||||
import com.futo.platformplayer.api.media.models.contents.IPlatformContent
|
||||
import com.futo.platformplayer.api.media.models.contents.IPlatformContentDetails
|
||||
import com.futo.platformplayer.api.media.structures.IPager
|
||||
import com.futo.platformplayer.states.StateApp
|
||||
import java.util.*
|
||||
|
||||
class DevJSClient : JSClient {
|
||||
@@ -24,6 +25,10 @@ class DevJSClient : JSClient {
|
||||
_auth = auth;
|
||||
_captcha = captcha;
|
||||
this.devID = devID ?: UUID.randomUUID().toString().substring(0, 5);
|
||||
|
||||
onCaptchaException.subscribe { client, captcha ->
|
||||
StateApp.instance.handleCaptchaException(client, captcha);
|
||||
}
|
||||
}
|
||||
//TODO: Misisng auth/captcha pass on purpose?
|
||||
constructor(context: Context, descriptor: SourcePluginDescriptor, script: String, auth: SourceAuth? = null, captcha: SourceCaptchaData? = null, savedState: String? = null, devID: String? = null): super(context, descriptor, savedState, script) {
|
||||
@@ -31,6 +36,10 @@ class DevJSClient : JSClient {
|
||||
_auth = auth;
|
||||
_captcha = captcha;
|
||||
this.devID = devID ?: UUID.randomUUID().toString().substring(0, 5);
|
||||
|
||||
onCaptchaException.subscribe { client, captcha ->
|
||||
StateApp.instance.handleCaptchaException(client, captcha);
|
||||
}
|
||||
}
|
||||
|
||||
fun setCaptcha(captcha: SourceCaptchaData? = null) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.content.Context
|
||||
import com.caoccao.javet.values.V8Value
|
||||
import com.caoccao.javet.values.primitive.V8ValueBoolean
|
||||
import com.caoccao.javet.values.primitive.V8ValueInteger
|
||||
import com.caoccao.javet.values.primitive.V8ValueNull
|
||||
import com.caoccao.javet.values.primitive.V8ValueString
|
||||
import com.caoccao.javet.values.reference.V8ValueArray
|
||||
import com.caoccao.javet.values.reference.V8ValueObject
|
||||
@@ -23,6 +24,7 @@ import com.futo.platformplayer.api.media.models.playback.IPlaybackTracker
|
||||
import com.futo.platformplayer.api.media.models.playlists.IPlatformPlaylistDetails
|
||||
import com.futo.platformplayer.api.media.platforms.js.internal.*
|
||||
import com.futo.platformplayer.api.media.platforms.js.models.*
|
||||
import com.futo.platformplayer.api.media.structures.EmptyPager
|
||||
import com.futo.platformplayer.api.media.structures.IPager
|
||||
import com.futo.platformplayer.constructs.Event1
|
||||
import com.futo.platformplayer.constructs.Event2
|
||||
@@ -431,8 +433,11 @@ open class JSClient : IPlatformClient {
|
||||
@JSDocsParameter("url", "A content url (this platform)")
|
||||
override fun getComments(url: String): IPager<IPlatformComment> = isBusyWith {
|
||||
ensureEnabled();
|
||||
return@isBusyWith JSCommentPager(config, plugin,
|
||||
plugin.executeTyped("source.getComments(${Json.encodeToString(url)})"));
|
||||
val pager = plugin.executeTyped<V8Value>("source.getComments(${Json.encodeToString(url)})");
|
||||
if (pager !is V8ValueObject) { //TODO: Maybe solve this better
|
||||
return@isBusyWith EmptyPager<IPlatformComment>();
|
||||
}
|
||||
return@isBusyWith JSCommentPager(config, plugin, pager);
|
||||
}
|
||||
@JSDocs(17, "source.getSubComments(comment)", "Gets replies for a given comment")
|
||||
@JSDocsParameter("comment", "Comment object that was returned by getComments")
|
||||
|
||||
+43
-27
@@ -31,8 +31,12 @@ class JSHttpClient : ManagedHttpClient {
|
||||
_currentCookieMap.put(domainCookies.key, HashMap(domainCookies.value));
|
||||
}
|
||||
if(!captcha?.cookieMap.isNullOrEmpty()) {
|
||||
for(domainCookies in captcha!!.cookieMap!!)
|
||||
_currentCookieMap.put(domainCookies.key, HashMap(domainCookies.value));
|
||||
for(domainCookies in captcha!!.cookieMap!!) {
|
||||
if(_currentCookieMap.containsKey(domainCookies.key))
|
||||
_currentCookieMap[domainCookies.key]?.putAll(domainCookies.value);
|
||||
else
|
||||
_currentCookieMap.put(domainCookies.key, HashMap(domainCookies.value));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -46,14 +50,18 @@ class JSHttpClient : ManagedHttpClient {
|
||||
return newClient;
|
||||
}
|
||||
|
||||
override fun beforeRequest(request: Request) {
|
||||
val domain = Uri.parse(request.url).host!!.lowercase();
|
||||
|
||||
override fun beforeRequest(request: okhttp3.Request): okhttp3.Request {
|
||||
val domain = request.url.host.lowercase();
|
||||
val auth = _auth;
|
||||
|
||||
val newBuilder = if(auth != null || doApplyCookies)
|
||||
request.newBuilder();
|
||||
else
|
||||
null;
|
||||
if (auth != null) {
|
||||
//TODO: Possibly add doApplyHeaders
|
||||
for (header in auth.headers.filter { domain.matchesDomain(it.key) }.flatMap { it.value.entries })
|
||||
request.headers[header.key] = header.value;
|
||||
newBuilder?.header(header.key, header.value);
|
||||
}
|
||||
|
||||
if(doApplyCookies) {
|
||||
@@ -68,34 +76,37 @@ class JSHttpClient : ManagedHttpClient {
|
||||
|
||||
if(cookiesToApply.size > 0) {
|
||||
val cookieString = cookiesToApply.map { it.key + "=" + it.value }.joinToString("; ");
|
||||
request.headers["Cookie"] = cookieString;
|
||||
|
||||
val existingCookies = request.headers["Cookie"];
|
||||
if(!existingCookies.isNullOrEmpty())
|
||||
newBuilder?.header("Cookie", existingCookies.trim(';') + "; " + cookieString);
|
||||
else
|
||||
newBuilder?.header("Cookie", cookieString);
|
||||
}
|
||||
//printTestCode(request.url, request.body, auth.headers, cookieString, request.headers.filter { !auth.headers.containsKey(it.key) });
|
||||
}
|
||||
}
|
||||
|
||||
_jsClient?.validateUrlOrThrow(request.url);
|
||||
super.beforeRequest(request)
|
||||
_jsClient?.validateUrlOrThrow(request.url.toString());
|
||||
return newBuilder?.let { it.build() } ?: request;
|
||||
}
|
||||
|
||||
override fun afterRequest(request: Request, resp: Response) {
|
||||
super.afterRequest(request, resp)
|
||||
|
||||
override fun afterRequest(resp: okhttp3.Response): okhttp3.Response {
|
||||
if(doUpdateCookies) {
|
||||
val domain = Uri.parse(request.url).host!!.lowercase();
|
||||
val domainParts = domain!!.split(".");
|
||||
val domain = resp.request.url.host.lowercase();
|
||||
val domainParts = domain.split(".");
|
||||
val defaultCookieDomain =
|
||||
"." + domainParts.drop(domainParts.size - 2).joinToString(".");
|
||||
for (header in resp.headers) {
|
||||
if ((_auth != null || _currentCookieMap.isNotEmpty()) && header.key.lowercase() == "set-cookie") {
|
||||
val newCookies = cookieStringToMap(header.value);
|
||||
for (cookie in newCookies) {
|
||||
val endIndex = cookie.value.indexOf(";");
|
||||
var cookieValue = cookie.value;
|
||||
if ((_auth != null || _currentCookieMap.isNotEmpty()) && header.first.lowercase() == "set-cookie") {
|
||||
//val newCookies = cookieStringToMap(header.second.split("; "));
|
||||
val cookie = cookieStringToPair(header.second);
|
||||
//for (cookie in newCookies) {
|
||||
var cookieValue = cookie.second;
|
||||
var domainToUse = domain;
|
||||
|
||||
if (endIndex > 0) {
|
||||
val cookieParts = cookie.value.split(";");
|
||||
if (!cookie.first.isNullOrEmpty() && !cookie.second.isNullOrEmpty()) {
|
||||
val cookieParts = cookie.second.split(";");
|
||||
if (cookieParts.size == 0)
|
||||
continue;
|
||||
cookieValue = cookieParts[0].trim();
|
||||
@@ -121,24 +132,29 @@ class JSHttpClient : ManagedHttpClient {
|
||||
_currentCookieMap!!.put(domainToUse, newMap)
|
||||
newMap;
|
||||
}
|
||||
if(cookieMap.containsKey(cookie.key) || doAllowNewCookies)
|
||||
cookieMap.put(cookie.key, cookieValue);
|
||||
}
|
||||
if(cookieMap.containsKey(cookie.first) || doAllowNewCookies)
|
||||
cookieMap.put(cookie.first, cookieValue);
|
||||
//}
|
||||
}
|
||||
}
|
||||
}
|
||||
return resp;
|
||||
}
|
||||
|
||||
|
||||
private fun cookieStringToMap(parts: List<String>): Map<String, String> {
|
||||
val map = hashMapOf<String, String>();
|
||||
for(cookie in parts) {
|
||||
val cookieKey = cookie.substring(0, cookie.indexOf("="));
|
||||
val cookieVal = cookie.substring(cookie.indexOf("=") + 1);
|
||||
map.put(cookieKey.trim(), cookieVal.trim());
|
||||
val pair = cookieStringToPair(cookie)
|
||||
map.put(pair.first, pair.second);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
private fun cookieStringToPair(cookie: String): Pair<String, String> {
|
||||
val cookieKey = cookie.substring(0, cookie.indexOf("="));
|
||||
val cookieVal = cookie.substring(cookie.indexOf("=") + 1);
|
||||
return Pair(cookieKey.trim(), cookieVal.trim());
|
||||
}
|
||||
|
||||
//Prints out code for test reproduction..
|
||||
fun printTestCode(url: String, body: ByteArray?, headers: Map<String, String>, cookieString: String, allHeaders: Map<String, String>? = null) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.futo.platformplayer.BuildConfig
|
||||
import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig
|
||||
import com.futo.platformplayer.api.media.structures.IPager
|
||||
import com.futo.platformplayer.engine.V8Plugin
|
||||
import com.futo.platformplayer.getOrDefault
|
||||
import com.futo.platformplayer.getOrThrow
|
||||
import com.futo.platformplayer.warnIfMainThread
|
||||
|
||||
@@ -27,7 +28,7 @@ abstract class JSPager<T> : IPager<T> {
|
||||
this.pager = pager;
|
||||
this.config = config;
|
||||
|
||||
_hasMorePages = pager.getOrThrow(config, "hasMore", "Pager");
|
||||
_hasMorePages = pager.getOrDefault(config, "hasMore", "Pager", false) ?: false;
|
||||
getResults();
|
||||
}
|
||||
|
||||
@@ -45,7 +46,7 @@ abstract class JSPager<T> : IPager<T> {
|
||||
pager = plugin.catchScriptErrors("[${plugin.config.name}] JSPager", "pager.nextPage()") {
|
||||
pager.invoke("nextPage", arrayOf<Any>());
|
||||
};
|
||||
_hasMorePages = pager.getOrThrow(config, "hasMore", "Pager");
|
||||
_hasMorePages = pager.getOrDefault(config, "hasMore", "Pager", false) ?: false;
|
||||
_resultChanged = true;
|
||||
/*
|
||||
try {
|
||||
|
||||
+6
-2
@@ -1,6 +1,7 @@
|
||||
package com.futo.platformplayer.api.media.platforms.js.models
|
||||
|
||||
import com.caoccao.javet.values.V8Value
|
||||
import com.caoccao.javet.values.primitive.V8ValueNull
|
||||
import com.caoccao.javet.values.reference.V8ValueArray
|
||||
import com.caoccao.javet.values.reference.V8ValueObject
|
||||
import com.futo.platformplayer.api.media.IPlatformClient
|
||||
@@ -99,8 +100,11 @@ class JSVideoDetails : JSVideo, IPlatformVideoDetails {
|
||||
return getCommentsJS(client);
|
||||
}
|
||||
|
||||
private fun getCommentsJS(client: JSClient): JSCommentPager {
|
||||
val commentPager = _content.invoke<V8ValueObject>("getComments", arrayOf<Any>());
|
||||
private fun getCommentsJS(client: JSClient): IPager<IPlatformComment>? {
|
||||
val commentPager = _content.invoke<V8Value>("getComments", arrayOf<Any>());
|
||||
if (commentPager !is V8ValueObject) //TODO: Maybe handle this better?
|
||||
return null;
|
||||
|
||||
return JSCommentPager(_pluginConfig, client.getUnderlyingPlugin(), commentPager);
|
||||
}
|
||||
}
|
||||
+10
-2
@@ -69,9 +69,17 @@ abstract class MultiRefreshPager<T>: IRefreshPager<T>, IPager<T> {
|
||||
if(pagerToAdd == null) {
|
||||
if(toReplacePager != null && toReplacePager is PlaceholderPager && error != null) {
|
||||
val pluginId = toReplacePager.placeholderFactory.invoke().id?.pluginId ?: "";
|
||||
_currentPager = PlaceholderPager(5) {
|
||||
|
||||
_pagersReusable.add((PlaceholderPager(5) {
|
||||
return@PlaceholderPager PlatformContentPlaceholder(pluginId, error)
|
||||
} as IPager<T>;
|
||||
} as IPager<T>).asReusable());
|
||||
_currentPager = recreatePager(getCurrentSubPagers());
|
||||
|
||||
if(_currentPager is MultiParallelPager<*>)
|
||||
runBlocking { (_currentPager as MultiParallelPager).initialize(); };
|
||||
else if(_currentPager is MultiPager<*>)
|
||||
(_currentPager as MultiPager).initialize()
|
||||
|
||||
onPagerChanged.emit(_currentPager);
|
||||
}
|
||||
return;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.futo.platformplayer.developer
|
||||
|
||||
import android.content.Context
|
||||
import com.futo.platformplayer.activities.CaptchaActivity
|
||||
import com.futo.platformplayer.activities.LoginActivity
|
||||
import com.futo.platformplayer.api.http.ManagedHttpClient
|
||||
import com.futo.platformplayer.api.http.server.HttpContext
|
||||
@@ -201,6 +202,28 @@ class DeveloperEndpoints(private val context: Context) {
|
||||
context.respondCode(500, (ex::class.simpleName + ":" + ex.message) ?: "", "text/plain")
|
||||
}
|
||||
}
|
||||
@HttpPOST("/plugin/captchaTestPlugin")
|
||||
fun pluginCaptchaTestPlugin(context: HttpContext) {
|
||||
val config = _testPlugin?.config as SourcePluginConfig;
|
||||
val url = context.query.get("url")
|
||||
val html = context.readContentString();
|
||||
try {
|
||||
val captchaConfig = config.captcha;
|
||||
if(captchaConfig == null) {
|
||||
context.respondCode(403, "This plugin doesn't support captcha");
|
||||
return;
|
||||
}
|
||||
CaptchaActivity.showCaptcha(StateApp.instance.context, config, url, html) {
|
||||
_testPluginVariables.clear();
|
||||
_testPlugin = V8Plugin(StateApp.instance.context, config, null, JSHttpClient(null, null, it), JSHttpClient(null, null, it));
|
||||
|
||||
};
|
||||
context.respondCode(200, "Captcha started");
|
||||
}
|
||||
catch(ex: Throwable) {
|
||||
context.respondCode(500, (ex::class.simpleName + ":" + ex.message) ?: "", "text/plain")
|
||||
}
|
||||
}
|
||||
@HttpGET("/plugin/loginTestPlugin")
|
||||
fun pluginLoginTestPlugin(context: HttpContext) {
|
||||
val config = _testPlugin?.config as SourcePluginConfig;
|
||||
|
||||
@@ -298,6 +298,7 @@ class V8Plugin {
|
||||
private fun throwExceptionFromV8(config: IV8PluginConfig, pluginType: String, msg: String, innerEx: Exception? = null, stack: String? = null, code: String? = null) {
|
||||
when(pluginType) {
|
||||
"ScriptException" -> throw ScriptException(config, msg, innerEx, stack, code);
|
||||
"CriticalException" -> throw ScriptCriticalException(config, msg, innerEx, stack, code);
|
||||
"AgeException" -> throw ScriptAgeException(config, msg, innerEx, stack, code);
|
||||
"UnavailableException" -> throw ScriptUnavailableException(config, msg, innerEx, stack, code);
|
||||
"ScriptExecutionException" -> throw ScriptExecutionException(config, msg, innerEx, stack, code);
|
||||
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package com.futo.platformplayer.engine.exceptions
|
||||
|
||||
import com.caoccao.javet.values.reference.V8ValueObject
|
||||
import com.futo.platformplayer.engine.IV8PluginConfig
|
||||
import com.futo.platformplayer.getOrThrow
|
||||
|
||||
open class ScriptCriticalException(config: IV8PluginConfig, error: String, ex: Exception? = null, stack: String? = null, code: String? = null) : ScriptException(config, error, ex, stack, code) {
|
||||
|
||||
|
||||
|
||||
|
||||
companion object {
|
||||
fun fromV8(config: IV8PluginConfig, obj: V8ValueObject) : ScriptException {
|
||||
return ScriptCriticalException(config, obj.getOrThrow(config, "message", "ScriptCriticalException"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import android.widget.TextView
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.futo.futopay.PaymentConfigurations
|
||||
import com.futo.futopay.PaymentManager
|
||||
import com.futo.platformplayer.BuildConfig
|
||||
import com.futo.platformplayer.R
|
||||
import com.futo.platformplayer.UIDialogs
|
||||
import com.futo.platformplayer.logging.Logger
|
||||
@@ -68,9 +69,12 @@ class BuyFragment : MainFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
_buttonBuy.setOnClickListener {
|
||||
buy();
|
||||
}
|
||||
if(!BuildConfig.IS_PLAYSTORE_BUILD)
|
||||
_buttonBuy.setOnClickListener {
|
||||
buy();
|
||||
}
|
||||
else
|
||||
_buttonBuy.visibility = View.GONE;
|
||||
_buttonPaid.setOnClickListener {
|
||||
paid();
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ abstract class FeedView<TFragment, TResult, TConverted, TPager, TViewHolder> : L
|
||||
private val _spinnerSortBy: Spinner;
|
||||
private val _containerSortBy: LinearLayout;
|
||||
private val _tagsView: TagsView;
|
||||
private val _textCentered: TextView;
|
||||
|
||||
protected val _toolbarContentView: LinearLayout;
|
||||
|
||||
@@ -68,6 +69,7 @@ abstract class FeedView<TFragment, TResult, TConverted, TPager, TViewHolder> : L
|
||||
this.fragment = fragment;
|
||||
inflater.inflate(R.layout.fragment_feed, this);
|
||||
|
||||
_textCentered = findViewById(R.id.text_centered);
|
||||
_progress_bar = findViewById(R.id.progress_bar);
|
||||
_progress_bar.inactiveColor = Color.TRANSPARENT;
|
||||
|
||||
@@ -169,6 +171,10 @@ abstract class FeedView<TFragment, TResult, TConverted, TPager, TViewHolder> : L
|
||||
_recyclerResults.addOnScrollListener(_scrollListener);
|
||||
}
|
||||
|
||||
protected fun setTextCentered(text: String?) {
|
||||
_textCentered.text = text;
|
||||
}
|
||||
|
||||
fun onResume() {
|
||||
//Reload the pager if the plugin was killed
|
||||
val pager = recyclerData.pager;
|
||||
|
||||
+15
@@ -69,6 +69,8 @@ class ImportSubscriptionsFragment : MainFragment() {
|
||||
private var _currentLoadIndex = 0;
|
||||
|
||||
private var _taskLoadChannel: TaskHandler<String, IPlatformChannel>;
|
||||
private var _counter: Int = 0;
|
||||
private var _limitToastShown = false;
|
||||
|
||||
constructor(fragment: ImportSubscriptionsFragment, inflater: LayoutInflater) : super(inflater.context) {
|
||||
_fragment = fragment;
|
||||
@@ -104,6 +106,7 @@ class ImportSubscriptionsFragment : MainFragment() {
|
||||
setLoading(false);
|
||||
|
||||
_taskLoadChannel = TaskHandler<String, IPlatformChannel>({_fragment.lifecycleScope}, { link ->
|
||||
_counter++;
|
||||
val channel: IPlatformChannel = StatePlatform.instance.getChannelLive(link, false);
|
||||
return@TaskHandler channel;
|
||||
}).success {
|
||||
@@ -124,6 +127,8 @@ class ImportSubscriptionsFragment : MainFragment() {
|
||||
}
|
||||
|
||||
fun onShown(parameter: Any ?, isBack: Boolean) {
|
||||
_counter = 0;
|
||||
_limitToastShown = false;
|
||||
updateSelected();
|
||||
|
||||
val itemsRemoved = _items.size;
|
||||
@@ -157,6 +162,15 @@ class ImportSubscriptionsFragment : MainFragment() {
|
||||
|
||||
private fun load() {
|
||||
setLoading(true);
|
||||
if (_counter >= MAXIMUM_BATCH_SIZE) {
|
||||
if (!_limitToastShown) {
|
||||
_limitToastShown = true;
|
||||
UIDialogs.toast(context, "Stopped after $MAXIMUM_BATCH_SIZE to avoid rate limit, re-enter to import rest");
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
_taskLoadChannel.run(_links[_currentLoadIndex]);
|
||||
}
|
||||
|
||||
@@ -196,6 +210,7 @@ class ImportSubscriptionsFragment : MainFragment() {
|
||||
|
||||
companion object {
|
||||
val TAG = "ImportSubscriptionsFragment";
|
||||
private const val MAXIMUM_BATCH_SIZE = 75;
|
||||
fun newInstance() = ImportSubscriptionsFragment().apply {}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -601,7 +601,7 @@ class PostDetailFragment : MainFragment {
|
||||
val subscribers = value?.author?.subscribers;
|
||||
if(subscribers != null && subscribers > 0) {
|
||||
_channelMeta.visibility = View.VISIBLE;
|
||||
_channelMeta.text = value.author.subscribers!!.toHumanNumber() + " subscribers";
|
||||
_channelMeta.text = if((value.author?.subscribers ?: 0) > 0) value.author.subscribers!!.toHumanNumber() + " subscribers" else "";
|
||||
} else {
|
||||
_channelMeta.visibility = View.GONE;
|
||||
_channelMeta.text = "";
|
||||
|
||||
+15
-1
@@ -258,11 +258,25 @@ class SourceDetailFragment : MainFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
val clientIfExists = StatePlugins.instance.getPlugin(config.id);
|
||||
groups.add(
|
||||
BigButtonGroup(c, "Management",
|
||||
BigButton(c, "Uninstall", "Removes the plugin from the app", R.drawable.ic_block) {
|
||||
uninstallSource();
|
||||
}.withBackground(R.drawable.background_big_button_red)
|
||||
}.withBackground(R.drawable.background_big_button_red).apply {
|
||||
this.layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT).apply {
|
||||
setMargins(0, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5f, resources.displayMetrics).toInt(), 0, 0);
|
||||
};
|
||||
},
|
||||
if(clientIfExists?.captchaEncrypted != null)
|
||||
BigButton(c, "Delete Captcha", "Deletes stored captcha answer for this plugin", R.drawable.ic_block) {
|
||||
clientIfExists?.updateCaptcha(null);
|
||||
}.apply {
|
||||
this.layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT).apply {
|
||||
setMargins(0, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5f, resources.displayMetrics).toInt(), 0, 0);
|
||||
};
|
||||
}.withBackground(R.drawable.background_big_button_red)
|
||||
else null
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
+7
-1
@@ -13,6 +13,7 @@ import com.futo.platformplayer.api.media.models.contents.ContentType
|
||||
import com.futo.platformplayer.api.media.models.contents.IPlatformContent
|
||||
import com.futo.platformplayer.api.media.models.video.IPlatformVideo
|
||||
import com.futo.platformplayer.api.media.platforms.js.JSClient
|
||||
import com.futo.platformplayer.api.media.structures.EmptyPager
|
||||
import com.futo.platformplayer.api.media.structures.IPager
|
||||
import com.futo.platformplayer.cache.ChannelContentCache
|
||||
import com.futo.platformplayer.constructs.TaskHandler
|
||||
@@ -280,8 +281,12 @@ class SubscriptionsFeedFragment : MainFragment() {
|
||||
Logger.i(TAG, "Subscriptions load");
|
||||
if(recyclerData.results.size == 0) {
|
||||
val cachePager = ChannelContentCache.instance.getSubscriptionCachePager();
|
||||
Logger.i(TAG, "Subscription show cache (${cachePager.getResults().size})");
|
||||
val results = cachePager.getResults();
|
||||
Logger.i(TAG, "Subscription show cache (${results.size})");
|
||||
setTextCentered(if (results.isEmpty()) "No results found\nSwipe down to refresh" else null);
|
||||
setPager(cachePager);
|
||||
} else {
|
||||
setTextCentered(null);
|
||||
}
|
||||
_taskGetPager.run(withRefetch);
|
||||
}
|
||||
@@ -294,6 +299,7 @@ class SubscriptionsFeedFragment : MainFragment() {
|
||||
finishRefreshLayoutLoader();
|
||||
setLoading(false);
|
||||
setPager(pager);
|
||||
setTextCentered(if (pager.getResults().isEmpty()) "No results found\nSwipe down to refresh" else null);
|
||||
} catch (e: Throwable) {
|
||||
Logger.e(TAG, "Failed to finish loading", e)
|
||||
}
|
||||
|
||||
+2
-2
@@ -873,7 +873,7 @@ class VideoDetailView : ConstraintLayout {
|
||||
_channelName.text = video.author.name;
|
||||
_playWhenReady = true;
|
||||
if(video.author.subscribers != null) {
|
||||
_channelMeta.text = video.author.subscribers!!.toHumanNumber() + " subscribers";
|
||||
_channelMeta.text = if((video.author.subscribers ?: 0) > 0) video.author.subscribers!!.toHumanNumber() + " subscribers" else "";
|
||||
(_channelName.layoutParams as MarginLayoutParams).setMargins(0, (DP_5 * -1).toInt(), 0, 0);
|
||||
} else {
|
||||
_channelMeta.text = "";
|
||||
@@ -982,7 +982,7 @@ class VideoDetailView : ConstraintLayout {
|
||||
_title.text = video.name;
|
||||
_channelName.text = video.author.name;
|
||||
if(video.author.subscribers != null) {
|
||||
_channelMeta.text = video.author.subscribers!!.toHumanNumber() + " subscribers";
|
||||
_channelMeta.text = if((video.author.subscribers ?: 0) > 0) video.author.subscribers!!.toHumanNumber() + " subscribers" else "";
|
||||
(_channelName.layoutParams as MarginLayoutParams).setMargins(0, (DP_5 * -1).toInt(), 0, 0);
|
||||
} else {
|
||||
_channelMeta.text = "";
|
||||
|
||||
+2
-4
@@ -57,10 +57,8 @@ abstract class VideoListEditorView : LinearLayout {
|
||||
buttonPlayAll.setOnClickListener { onPlayAllClick(); };
|
||||
buttonShuffle.setOnClickListener { onShuffleClick(); };
|
||||
|
||||
if (canEdit())
|
||||
_buttonEdit.setOnClickListener { onEditClick(); };
|
||||
else
|
||||
_buttonEdit.visibility = View.GONE;
|
||||
_buttonEdit.setOnClickListener { onEditClick(); };
|
||||
setButtonDownloadVisible(canEdit());
|
||||
|
||||
videoListEditorView.onVideoOrderChanged.subscribe(::onVideoOrderChanged);
|
||||
videoListEditorView.onVideoRemoved.subscribe(::onVideoRemoved);
|
||||
|
||||
@@ -61,9 +61,21 @@ public class PolycentricModelLoader implements ModelLoader<String, ByteBuffer> {
|
||||
_deferred.invokeOnCompletion(throwable -> {
|
||||
if (throwable != null) {
|
||||
callback.onLoadFailed(new Exception(throwable));
|
||||
return Unit.INSTANCE;
|
||||
}
|
||||
|
||||
Deferred<ByteBuffer> deferred = _deferred;
|
||||
if (deferred == null) {
|
||||
callback.onLoadFailed(new Exception("Deferred is null"));
|
||||
return Unit.INSTANCE;
|
||||
}
|
||||
|
||||
ByteBuffer completed = deferred.getCompleted();
|
||||
if (completed != null) {
|
||||
callback.onDataReady(completed);
|
||||
} else {
|
||||
callback.onLoadFailed(new Exception("Completed is null"));
|
||||
}
|
||||
final ByteBuffer completed = _deferred.getCompleted();
|
||||
callback.onDataReady(completed);
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.futo.platformplayer.others
|
||||
|
||||
import android.net.Uri
|
||||
import android.webkit.*
|
||||
import com.futo.platformplayer.BuildConfig
|
||||
import com.futo.platformplayer.api.http.ManagedHttpClient
|
||||
import com.futo.platformplayer.api.media.Serializer
|
||||
import com.futo.platformplayer.constructs.Event1
|
||||
@@ -43,6 +44,8 @@ class LoginWebViewClient : WebViewClient {
|
||||
private var urlFound = false;
|
||||
|
||||
override fun onPageFinished(view: WebView?, url: String?) {
|
||||
if(BuildConfig.DEBUG)
|
||||
Logger.i(TAG, "Login Url Page: " + url);
|
||||
super.onPageFinished(view, url);
|
||||
onPageLoaded.emit(view, url);
|
||||
}
|
||||
@@ -56,11 +59,29 @@ class LoginWebViewClient : WebViewClient {
|
||||
return null;
|
||||
}
|
||||
|
||||
var completionUrlExcludeQuery = false
|
||||
var completionUrlToCheck = if(urlFound) null else _authConfig.completionUrl;
|
||||
if(completionUrlToCheck != null) {
|
||||
if(completionUrlToCheck.endsWith("?*")) {
|
||||
completionUrlToCheck = completionUrlToCheck.substring(0, completionUrlToCheck.length - 2);
|
||||
completionUrlExcludeQuery = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val domain = request.url.host;
|
||||
val domainLower = request.url.host?.lowercase();
|
||||
val urlString = request.url.toString();
|
||||
if(_authConfig.completionUrl == null)
|
||||
urlFound = true;
|
||||
else urlFound = urlFound || request.url == Uri.parse(_authConfig.completionUrl);
|
||||
else urlFound = urlFound || (
|
||||
if(completionUrlExcludeQuery)
|
||||
(if(urlString.contains("?"))
|
||||
urlString.substring(0, urlString.indexOf("?")) == completionUrlToCheck
|
||||
else urlString == completionUrlToCheck)
|
||||
else
|
||||
request.url == Uri.parse(_authConfig.completionUrl)
|
||||
);
|
||||
|
||||
//HEADERS
|
||||
if(domainLower != null) {
|
||||
|
||||
@@ -29,6 +29,7 @@ import com.futo.platformplayer.activities.CaptchaActivity
|
||||
import com.futo.platformplayer.activities.IWithResultLauncher
|
||||
import com.futo.platformplayer.activities.MainActivity
|
||||
import com.futo.platformplayer.api.media.Serializer
|
||||
import com.futo.platformplayer.api.media.platforms.js.DevJSClient
|
||||
import com.futo.platformplayer.api.media.platforms.js.JSClient
|
||||
import com.futo.platformplayer.api.media.platforms.js.internal.JSHttpClient
|
||||
import com.futo.platformplayer.background.BackgroundWorker
|
||||
@@ -672,13 +673,20 @@ class StateApp {
|
||||
UIDialogs.showConfirmationDialog(context, "Captcha required\nPlugin [${client.config.name}]", {
|
||||
CaptchaActivity.showCaptcha(context, client.config, exception.url, exception.body) {
|
||||
hasCaptchaDialog = false;
|
||||
StatePlugins.instance.setPluginCaptcha(client.config.id, it);
|
||||
scopeOrNull?.launch(Dispatchers.IO) {
|
||||
try {
|
||||
StatePlatform.instance.reloadClient(context, client.config.id);
|
||||
} catch (e: Throwable) {
|
||||
Logger.e(SourceDetailFragment.TAG, "Failed to reload client.", e)
|
||||
return@launch;
|
||||
|
||||
if(client is DevJSClient) {
|
||||
client.setCaptcha(it);
|
||||
client.recreate(context);
|
||||
}
|
||||
else {
|
||||
StatePlugins.instance.setPluginCaptcha(client.config.id, it);
|
||||
scopeOrNull?.launch(Dispatchers.IO) {
|
||||
try {
|
||||
StatePlatform.instance.reloadClient(context, client.config.id);
|
||||
} catch (e: Throwable) {
|
||||
Logger.e(SourceDetailFragment.TAG, "Failed to reload client.", e)
|
||||
return@launch;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import com.futo.platformplayer.constructs.Event1
|
||||
import com.futo.platformplayer.constructs.Event2
|
||||
import com.futo.platformplayer.engine.exceptions.PluginException
|
||||
import com.futo.platformplayer.engine.exceptions.ScriptCaptchaRequiredException
|
||||
import com.futo.platformplayer.engine.exceptions.ScriptCriticalException
|
||||
import com.futo.platformplayer.exceptions.ChannelException
|
||||
import com.futo.platformplayer.findNonRuntimeException
|
||||
import com.futo.platformplayer.fragment.mainactivity.main.PolycentricProfile
|
||||
@@ -320,7 +321,16 @@ class StateSubscriptions {
|
||||
synchronized(failedPlugins) {
|
||||
//Fail all subscription calls to plugin if it has a captcha issue
|
||||
if(ex.config is SourcePluginConfig && !failedPlugins.contains(ex.config.id)) {
|
||||
Logger.w(TAG, "Subscriptions fetch ignoring plugin [${ex.config.name}] due to Captcha");
|
||||
Logger.w(TAG, "Subscriptionsgnoring plugin [${ex.config.name}] due to Captcha");
|
||||
failedPlugins.add(ex.config.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(ex is ScriptCriticalException) {
|
||||
synchronized(failedPlugins) {
|
||||
//Fail all subscription calls to plugin if it has a critical issue
|
||||
if(ex.config is SourcePluginConfig && !failedPlugins.contains(ex.config.id)) {
|
||||
Logger.w(TAG, "Subscriptions ignoring plugin [${ex.config.name}] due to critical exception:\n" + ex.message);
|
||||
failedPlugins.add(ex.config.id);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -50,7 +50,7 @@ class CreatorViewHolder(private val _viewGroup: ViewGroup, private val _tiny: Bo
|
||||
if(authorLink.subscribers == null || (authorLink.subscribers ?: 0) <= 0L)
|
||||
_textMetadata.visibility = View.GONE;
|
||||
else {
|
||||
_textMetadata.text = authorLink.subscribers!!.toHumanNumber() + " subscribers";
|
||||
_textMetadata.text = if(authorLink?.subscribers ?: 0 > 0) authorLink.subscribers!!.toHumanNumber() + " subscribers" else "";
|
||||
_textMetadata.visibility = View.VISIBLE;
|
||||
}
|
||||
_buttonSubscribe.setSubscribeChannel(authorLink.url);
|
||||
|
||||
@@ -14,13 +14,13 @@ class BigButtonGroup : LinearLayout {
|
||||
_header = findViewById(R.id.header_title);
|
||||
_buttons = findViewById(R.id.buttons);
|
||||
}
|
||||
constructor(context: Context, header: String, vararg buttons: BigButton) : super(context) {
|
||||
constructor(context: Context, header: String, vararg buttons: BigButton?) : super(context) {
|
||||
inflate(context, R.layout.big_button_group, this);
|
||||
_header = findViewById(R.id.header_title);
|
||||
_buttons = findViewById(R.id.buttons);
|
||||
|
||||
_header.text = header;
|
||||
for(button in buttons)
|
||||
for(button in buttons.filterNotNull())
|
||||
_buttons.addView(button);
|
||||
}
|
||||
|
||||
|
||||
@@ -69,6 +69,7 @@
|
||||
android:layout_marginRight="30dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:inputType="textPassword"
|
||||
android:hint="Backup Password" />
|
||||
|
||||
<LinearLayout
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginRight="30dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textPassword"
|
||||
android:singleLine="true"
|
||||
android:hint="Backup Password" />
|
||||
|
||||
|
||||
@@ -76,22 +76,37 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<com.futo.platformplayer.views.others.ProgressBar
|
||||
android:id="@+id/progress_bar"
|
||||
android:layout_height="match_parent">
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp" />
|
||||
android:layout_height="wrap_content">
|
||||
<com.futo.platformplayer.views.others.ProgressBar
|
||||
android:id="@+id/progress_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/list_results"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical" />
|
||||
</LinearLayout>
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/list_results"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_centered"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:fontFamily="@font/inter_regular"
|
||||
android:gravity="center"
|
||||
android:textColor="@color/gray_ac"
|
||||
android:textSize="12dp" />
|
||||
</FrameLayout>
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
Submodule app/src/unstable/assets/sources/soundcloud updated: 3e3f95365a...eff8285222
Submodule app/src/unstable/assets/sources/twitch updated: 7645b88a76...eb198a3d20
Submodule app/src/unstable/assets/sources/youtube updated: 29665c24a1...239960b932
Reference in New Issue
Block a user