mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
feat(media-hosting): add multi-provider configurable upload with fallback
Add Random/Preferred/None modes, preferred provider selection, and provider-order fallback. Catbox/Imgur/PostImages on Android via WebView; Electron CDP automation for Imgur/PostImages. Hide upload controls on web runtime.
This commit is contained in:
@@ -3,21 +3,27 @@ package fivechan.android;
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.provider.MediaStore;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.activity.result.ActivityResult;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import com.getcapacitor.JSArray;
|
||||
import com.getcapacitor.JSObject;
|
||||
import com.getcapacitor.Plugin;
|
||||
import com.getcapacitor.PluginCall;
|
||||
import com.getcapacitor.PluginMethod;
|
||||
import com.getcapacitor.annotation.CapacitorPlugin;
|
||||
import com.getcapacitor.annotation.ActivityCallback;
|
||||
import com.getcapacitor.PluginCall;
|
||||
import androidx.activity.result.ActivityResult;
|
||||
import com.getcapacitor.annotation.CapacitorPlugin;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.json.JSONArray;
|
||||
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.MultipartBody;
|
||||
@@ -30,9 +36,16 @@ import okhttp3.Response;
|
||||
public class FileUploaderPlugin extends Plugin {
|
||||
private static final String TAG = "FileUploaderPlugin";
|
||||
|
||||
private static final String PROVIDER_CATBOX = "catbox";
|
||||
private static final long CATBOX_TIMEOUT_SEC = 30;
|
||||
|
||||
@PluginMethod
|
||||
public void pickAndUploadMedia(PluginCall call) {
|
||||
Log.d(TAG, "pickAndUploadMedia called");
|
||||
List<String> providerOrder = parseProviderOrder(call);
|
||||
if (providerOrder.isEmpty()) {
|
||||
providerOrder.add(PROVIDER_CATBOX);
|
||||
}
|
||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
intent.setType("*/*");
|
||||
String[] mimeTypes = {"image/jpeg", "image/png", "video/mp4", "video/webm"};
|
||||
@@ -40,6 +53,29 @@ public class FileUploaderPlugin extends Plugin {
|
||||
startActivityForResult(call, intent, "pickFileResult");
|
||||
}
|
||||
|
||||
private List<String> parseProviderOrder(PluginCall call) {
|
||||
List<String> order = new ArrayList<>();
|
||||
JSArray arr = call.getArray("providerOrder");
|
||||
if (arr != null) {
|
||||
for (int i = 0; i < arr.length(); i++) {
|
||||
try {
|
||||
Object o = arr.get(i);
|
||||
if (o instanceof String) {
|
||||
String p = (String) o;
|
||||
if (PROVIDER_CATBOX.equals(p)
|
||||
|| MediaUploadRecipes.PROVIDER_IMGUR.equals(p)
|
||||
|| MediaUploadRecipes.PROVIDER_POSTIMAGES.equals(p)) {
|
||||
order.add(p);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Skip invalid provider at " + i, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return order;
|
||||
}
|
||||
|
||||
@ActivityCallback
|
||||
private void pickFileResult(PluginCall call, ActivityResult result) {
|
||||
Log.d(TAG, "pickFileResult callback received");
|
||||
@@ -47,64 +83,188 @@ public class FileUploaderPlugin extends Plugin {
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.getResultCode() == Activity.RESULT_OK) {
|
||||
Intent data = result.getData();
|
||||
if (data != null) {
|
||||
Uri uri = data.getData();
|
||||
uploadToCatbox(uri, call);
|
||||
} else {
|
||||
call.reject("No data received");
|
||||
}
|
||||
} else {
|
||||
if (result.getResultCode() != Activity.RESULT_OK) {
|
||||
call.reject("File selection cancelled");
|
||||
return;
|
||||
}
|
||||
|
||||
Intent data = result.getData();
|
||||
if (data == null) {
|
||||
call.reject("No data received");
|
||||
return;
|
||||
}
|
||||
|
||||
Uri uri = data.getData();
|
||||
if (uri == null) {
|
||||
call.reject("No URI received");
|
||||
return;
|
||||
}
|
||||
|
||||
List<String> providerOrder = parseProviderOrder(call);
|
||||
if (providerOrder.isEmpty()) {
|
||||
providerOrder.add(PROVIDER_CATBOX);
|
||||
}
|
||||
|
||||
new Thread(
|
||||
() -> {
|
||||
try {
|
||||
tryProvidersSequentially(uri, providerOrder, call);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Upload failed", e);
|
||||
if (!call.getData().has("_resolved")) {
|
||||
call.reject("Upload failed: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
})
|
||||
.start();
|
||||
}
|
||||
|
||||
private void tryProvidersSequentially(Uri fileUri, List<String> providerOrder, PluginCall call) {
|
||||
List<JSObject> attempts = new ArrayList<>();
|
||||
StringBuilder errorSummary = new StringBuilder();
|
||||
|
||||
for (String provider : providerOrder) {
|
||||
JSObject attempt = new JSObject();
|
||||
attempt.put("provider", provider);
|
||||
|
||||
if (PROVIDER_CATBOX.equals(provider)) {
|
||||
MediaUploadResult res = uploadToCatboxSync(fileUri);
|
||||
attempt.put("success", res.success);
|
||||
if (res.success) {
|
||||
attempt.put("url", res.url);
|
||||
resolveWithSuccess(call, res.url, getFileName(fileUri), provider, attempts);
|
||||
return;
|
||||
}
|
||||
attempt.put("error", res.error);
|
||||
errorSummary.append(provider).append(": ").append(res.error).append("; ");
|
||||
} else if (MediaUploadRecipes.PROVIDER_IMGUR.equals(provider)
|
||||
|| MediaUploadRecipes.PROVIDER_POSTIMAGES.equals(provider)) {
|
||||
MediaUploadResult res = uploadViaWebViewSync(fileUri, provider);
|
||||
attempt.put("success", res.success);
|
||||
if (res.success) {
|
||||
attempt.put("url", res.url);
|
||||
resolveWithSuccess(call, res.url, getFileName(fileUri), provider, attempts);
|
||||
return;
|
||||
}
|
||||
attempt.put("error", res.error);
|
||||
errorSummary.append(provider).append(": ").append(res.error).append("; ");
|
||||
}
|
||||
attempts.add(attempt);
|
||||
}
|
||||
|
||||
JSObject data = new JSObject();
|
||||
JSONArray arr = new JSONArray();
|
||||
for (JSObject a : attempts) {
|
||||
arr.put(a);
|
||||
}
|
||||
data.put("attempts", arr);
|
||||
call.reject("All providers failed: " + errorSummary.toString(), null, null, data);
|
||||
}
|
||||
|
||||
private void resolveWithSuccess(
|
||||
PluginCall call, String url, String fileName, String provider, List<JSObject> attempts) {
|
||||
JSObject ret = new JSObject();
|
||||
ret.put("url", url);
|
||||
ret.put("fileName", fileName);
|
||||
ret.put("provider", provider);
|
||||
JSONArray arr = new JSONArray();
|
||||
for (JSObject a : attempts) {
|
||||
arr.put(a);
|
||||
}
|
||||
ret.put("attempts", arr);
|
||||
call.resolve(ret);
|
||||
}
|
||||
|
||||
private String getFileName(Uri uri) {
|
||||
try {
|
||||
File f = FileUtils.getFileFromUri(getContext(), uri);
|
||||
return f != null ? f.getName() : "unknown";
|
||||
} catch (Exception e) {
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
private void uploadToCatbox(Uri fileUri, PluginCall call) {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
Log.d(TAG, "Starting file conversion from URI");
|
||||
File file = FileUtils.getFileFromUri(getContext(), fileUri);
|
||||
Log.d(TAG, "File name: " + file.getName());
|
||||
|
||||
JSObject statusUpdate = new JSObject();
|
||||
statusUpdate.put("status", "Uploading to catbox.moe...");
|
||||
notifyListeners("uploadStatus", statusUpdate);
|
||||
|
||||
OkHttpClient client = new OkHttpClient.Builder()
|
||||
.connectTimeout(30, TimeUnit.SECONDS)
|
||||
.writeTimeout(30, TimeUnit.SECONDS)
|
||||
.readTimeout(30, TimeUnit.SECONDS)
|
||||
.build();
|
||||
|
||||
RequestBody requestBody = new MultipartBody.Builder()
|
||||
.setType(MultipartBody.FORM)
|
||||
.addFormDataPart("reqtype", "fileupload")
|
||||
.addFormDataPart("fileToUpload", file.getName(),
|
||||
RequestBody.create(MediaType.parse("application/octet-stream"), file))
|
||||
.build();
|
||||
|
||||
Request request = new Request.Builder()
|
||||
.url("https://catbox.moe/user/api.php")
|
||||
.post(requestBody)
|
||||
.build();
|
||||
|
||||
try (Response response = client.newCall(request).execute()) {
|
||||
if (!response.isSuccessful()) throw new IOException("Unexpected response " + response);
|
||||
|
||||
String url = response.body().string();
|
||||
Log.d(TAG, "Upload successful. URL: " + url);
|
||||
|
||||
JSObject ret = new JSObject();
|
||||
ret.put("url", url);
|
||||
ret.put("fileName", file.getName());
|
||||
ret.put("status", "Upload complete!");
|
||||
call.resolve(ret);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Upload failed", e);
|
||||
call.reject("Upload failed: " + e.getMessage());
|
||||
private MediaUploadResult uploadToCatboxSync(Uri fileUri) {
|
||||
try {
|
||||
File file = FileUtils.getFileFromUri(getContext(), fileUri);
|
||||
if (file == null) {
|
||||
return new MediaUploadResult(false, null, "Could not resolve file");
|
||||
}
|
||||
}).start();
|
||||
|
||||
JSObject statusUpdate = new JSObject();
|
||||
statusUpdate.put("status", "Uploading to catbox.moe...");
|
||||
notifyListeners("uploadStatus", statusUpdate);
|
||||
|
||||
OkHttpClient client =
|
||||
new OkHttpClient.Builder()
|
||||
.connectTimeout(CATBOX_TIMEOUT_SEC, TimeUnit.SECONDS)
|
||||
.writeTimeout(CATBOX_TIMEOUT_SEC, TimeUnit.SECONDS)
|
||||
.readTimeout(CATBOX_TIMEOUT_SEC, TimeUnit.SECONDS)
|
||||
.build();
|
||||
|
||||
RequestBody requestBody =
|
||||
new MultipartBody.Builder()
|
||||
.setType(MultipartBody.FORM)
|
||||
.addFormDataPart("reqtype", "fileupload")
|
||||
.addFormDataPart(
|
||||
"fileToUpload",
|
||||
file.getName(),
|
||||
RequestBody.create(
|
||||
MediaType.parse("application/octet-stream"), file))
|
||||
.build();
|
||||
|
||||
Request request =
|
||||
new Request.Builder().url("https://catbox.moe/user/api.php").post(requestBody).build();
|
||||
|
||||
try (Response response = client.newCall(request).execute()) {
|
||||
if (!response.isSuccessful()) {
|
||||
return new MediaUploadResult(
|
||||
false, null, "Unexpected response " + response.code());
|
||||
}
|
||||
String url = response.body().string();
|
||||
Log.d(TAG, "Catbox upload successful. URL: " + url);
|
||||
return new MediaUploadResult(true, url.trim(), null);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Catbox upload failed", e);
|
||||
return new MediaUploadResult(false, null, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private MediaUploadResult uploadViaWebViewSync(Uri fileUri, String provider) {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
AtomicReference<MediaUploadResult> resultRef = new AtomicReference<>();
|
||||
|
||||
AppCompatActivity activity = getActivity();
|
||||
if (activity == null) {
|
||||
return new MediaUploadResult(false, null, "Activity unavailable");
|
||||
}
|
||||
|
||||
String fileName = getFileName(fileUri);
|
||||
MediaUploadCallback callback =
|
||||
res -> {
|
||||
resultRef.set(res);
|
||||
latch.countDown();
|
||||
};
|
||||
|
||||
activity.runOnUiThread(
|
||||
() -> {
|
||||
MediaUploadAutomationRunner runner =
|
||||
new MediaUploadAutomationRunner(
|
||||
getContext(), fileUri, fileName, provider, callback);
|
||||
runner.run();
|
||||
});
|
||||
|
||||
try {
|
||||
boolean ok = latch.await(MediaUploadRecipes.UPLOAD_TIMEOUT_MS + 5000, TimeUnit.MILLISECONDS);
|
||||
if (!ok) {
|
||||
return new MediaUploadResult(false, null, "WebView upload timeout");
|
||||
}
|
||||
MediaUploadResult r = resultRef.get();
|
||||
return r != null ? r : new MediaUploadResult(false, null, "No result");
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return new MediaUploadResult(false, null, "Interrupted");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
package fivechan.android;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
import android.webkit.ValueCallback;
|
||||
import android.webkit.WebChromeClient;
|
||||
import android.webkit.WebSettings;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
|
||||
/** Result of a WebView-based upload attempt. */
|
||||
final class MediaUploadResult {
|
||||
public final boolean success;
|
||||
public final String url;
|
||||
public final String error;
|
||||
|
||||
public MediaUploadResult(boolean success, String url, String error) {
|
||||
this.success = success;
|
||||
this.url = url;
|
||||
this.error = error;
|
||||
}
|
||||
}
|
||||
|
||||
/** Callback for MediaUploadAutomationRunner. */
|
||||
interface MediaUploadCallback {
|
||||
void onComplete(MediaUploadResult result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Non-interactive WebView automation for imgur/postimages uploads.
|
||||
* Intercepts file chooser, feeds selected URI, polls for success/blocked, then tears down.
|
||||
*/
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
public class MediaUploadAutomationRunner {
|
||||
private static final String TAG = "MediaUploadAutomation";
|
||||
|
||||
private final Context context;
|
||||
private final Uri fileUri;
|
||||
private final String fileName;
|
||||
private final String provider;
|
||||
private final MediaUploadCallback callback;
|
||||
|
||||
private WebView webView;
|
||||
private ValueCallback<Uri[]> filePathCallback;
|
||||
private final Handler mainHandler;
|
||||
private final Runnable pollRunnable;
|
||||
private boolean finished;
|
||||
private long startTime;
|
||||
|
||||
public MediaUploadAutomationRunner(
|
||||
Context context, Uri fileUri, String fileName, String provider, MediaUploadCallback callback) {
|
||||
this.context = context.getApplicationContext();
|
||||
this.fileUri = fileUri;
|
||||
this.fileName = fileName;
|
||||
this.provider = provider;
|
||||
this.callback = callback;
|
||||
this.mainHandler = new Handler(Looper.getMainLooper());
|
||||
this.pollRunnable = this::pollForResult;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
mainHandler.post(this::startWebView);
|
||||
}
|
||||
|
||||
private void startWebView() {
|
||||
webView = new WebView(context);
|
||||
webView.setVisibility(android.view.View.GONE);
|
||||
WebSettings settings = webView.getSettings();
|
||||
settings.setJavaScriptEnabled(true);
|
||||
settings.setDomStorageEnabled(true);
|
||||
settings.setAllowFileAccess(true);
|
||||
settings.setAllowContentAccess(true);
|
||||
settings.setLoadWithOverviewMode(true);
|
||||
settings.setUseWideViewPort(true);
|
||||
settings.setUserAgentString(
|
||||
"Mozilla/5.0 (Linux; Android 10) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.120 Mobile Safari/537.36");
|
||||
|
||||
webView.setWebChromeClient(
|
||||
new WebChromeClient() {
|
||||
@Override
|
||||
public boolean onShowFileChooser(
|
||||
WebView webView,
|
||||
ValueCallback<Uri[]> filePathCallback,
|
||||
FileChooserParams fileChooserParams) {
|
||||
MediaUploadAutomationRunner.this.filePathCallback = filePathCallback;
|
||||
if (fileUri != null) {
|
||||
filePathCallback.onReceiveValue(new Uri[] {fileUri});
|
||||
MediaUploadAutomationRunner.this.filePathCallback = null;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
webView.setWebViewClient(
|
||||
new WebViewClient() {
|
||||
@Override
|
||||
public void onPageFinished(WebView view, String url) {
|
||||
MediaUploadAutomationRunner.this.mainHandler.postDelayed(
|
||||
MediaUploadAutomationRunner.this::triggerFileInput, 1500);
|
||||
}
|
||||
});
|
||||
|
||||
String uploadUrl = MediaUploadRecipes.getUploadUrl(provider);
|
||||
if (uploadUrl == null) {
|
||||
finish(new MediaUploadResult(false, null, "Unknown provider: " + provider));
|
||||
return;
|
||||
}
|
||||
startTime = System.currentTimeMillis();
|
||||
webView.loadUrl(uploadUrl);
|
||||
Log.d(TAG, "Loaded " + uploadUrl + " for provider " + provider);
|
||||
}
|
||||
|
||||
private void triggerFileInput() {
|
||||
String js = MediaUploadRecipes.getTriggerFileInputJs(provider);
|
||||
if (js == null) {
|
||||
finish(new MediaUploadResult(false, null, "No trigger JS for " + provider));
|
||||
return;
|
||||
}
|
||||
webView.evaluateJavascript(js, value -> schedulePoll());
|
||||
}
|
||||
|
||||
private void schedulePoll() {
|
||||
mainHandler.removeCallbacks(pollRunnable);
|
||||
mainHandler.postDelayed(pollRunnable, MediaUploadRecipes.POLL_INTERVAL_MS);
|
||||
}
|
||||
|
||||
private void pollForResult() {
|
||||
if (finished) return;
|
||||
|
||||
long elapsed = System.currentTimeMillis() - startTime;
|
||||
if (elapsed >= MediaUploadRecipes.UPLOAD_TIMEOUT_MS) {
|
||||
finish(new MediaUploadResult(false, null, "Upload timeout"));
|
||||
return;
|
||||
}
|
||||
|
||||
String successJs = MediaUploadRecipes.getSuccessJs(provider);
|
||||
String blockedJs = MediaUploadRecipes.getBlockedJs(provider);
|
||||
if (successJs == null || blockedJs == null) {
|
||||
finish(new MediaUploadResult(false, null, "Missing recipe for " + provider));
|
||||
return;
|
||||
}
|
||||
|
||||
webView.evaluateJavascript(
|
||||
blockedJs,
|
||||
blocked -> {
|
||||
if ("true".equals(blocked)) {
|
||||
finish(new MediaUploadResult(false, null, "Provider blocked (CAPTCHA/rate limit)"));
|
||||
return;
|
||||
}
|
||||
webView.evaluateJavascript(
|
||||
successJs,
|
||||
url -> {
|
||||
if (url != null && !"null".equals(url) && url.length() > 2) {
|
||||
String cleaned = url.replaceAll("^\"|\"$", "").replace("\\u003d", "=");
|
||||
if (cleaned.startsWith("http")) {
|
||||
finish(new MediaUploadResult(true, cleaned, null));
|
||||
return;
|
||||
}
|
||||
}
|
||||
schedulePoll();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void finish(MediaUploadResult result) {
|
||||
if (finished) return;
|
||||
finished = true;
|
||||
mainHandler.removeCallbacks(pollRunnable);
|
||||
if (filePathCallback != null) {
|
||||
try {
|
||||
filePathCallback.onReceiveValue(null);
|
||||
} catch (Exception ignored) {}
|
||||
filePathCallback = null;
|
||||
}
|
||||
tearDown();
|
||||
callback.onComplete(result);
|
||||
}
|
||||
|
||||
private void tearDown() {
|
||||
mainHandler.post(
|
||||
() -> {
|
||||
if (webView != null) {
|
||||
try {
|
||||
webView.stopLoading();
|
||||
webView.clearHistory();
|
||||
webView.clearCache(true);
|
||||
webView.clearSslPreferences();
|
||||
webView.destroy();
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Teardown warning", e);
|
||||
}
|
||||
webView = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
package fivechan.android;
|
||||
|
||||
/**
|
||||
* Provider-specific recipes for automated media upload via WebView.
|
||||
* Mirrors electron/media-upload-recipes.js for imgur/postimages.
|
||||
* Non-interactive automation; detects blocked states (captcha/login/challenge);
|
||||
* extracts direct media URL candidates via selector candidate arrays.
|
||||
*/
|
||||
public final class MediaUploadRecipes {
|
||||
|
||||
public static final String PROVIDER_IMGUR = "imgur";
|
||||
public static final String PROVIDER_POSTIMAGES = "postimages";
|
||||
|
||||
/** Max time to wait for upload completion (ms). */
|
||||
public static final long UPLOAD_TIMEOUT_MS = 45_000;
|
||||
/** Poll interval for success/blocked checks (ms). */
|
||||
public static final long POLL_INTERVAL_MS = 500;
|
||||
|
||||
private MediaUploadRecipes() {}
|
||||
|
||||
public static String getUploadUrl(String provider) {
|
||||
if (PROVIDER_IMGUR.equals(provider)) {
|
||||
return "https://imgur.com/upload";
|
||||
}
|
||||
if (PROVIDER_POSTIMAGES.equals(provider)) {
|
||||
return "https://postimages.org";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* JS to trigger file input click so WebChromeClient.onShowFileChooser fires.
|
||||
* Uses candidate selectors; first match wins.
|
||||
*/
|
||||
public static String getTriggerFileInputJs(String provider) {
|
||||
String[] candidates;
|
||||
if (PROVIDER_IMGUR.equals(provider)) {
|
||||
candidates =
|
||||
new String[] {
|
||||
"input[type=\"file\"]",
|
||||
"input[type=file]",
|
||||
"[data-file-input]",
|
||||
};
|
||||
} else if (PROVIDER_POSTIMAGES.equals(provider)) {
|
||||
candidates =
|
||||
new String[] {
|
||||
"input[type=\"file\"]",
|
||||
"input[type=file]",
|
||||
"#uploadFile",
|
||||
".fileinput",
|
||||
};
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return buildTriggerFileInputJs(candidates);
|
||||
}
|
||||
|
||||
private static String buildTriggerFileInputJs(String[] selectors) {
|
||||
StringBuilder sb = new StringBuilder("(function(){var s=[");
|
||||
for (int i = 0; i < selectors.length; i++) {
|
||||
if (i > 0) sb.append(",");
|
||||
sb.append("\"").append(escapeJs(selectors[i])).append("\"");
|
||||
}
|
||||
sb.append("];for(var i=0;i<s.length;i++){var el=document.querySelector(s[i]);if(el){el.click();return;}}})();");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* JS to extract direct media URL from page. Returns URL string or null.
|
||||
*/
|
||||
public static String getSuccessJs(String provider) {
|
||||
String[] selectorCandidates;
|
||||
String attribute;
|
||||
if (PROVIDER_IMGUR.equals(provider)) {
|
||||
selectorCandidates =
|
||||
new String[] {
|
||||
"a[href*=\"i.imgur.com\"]",
|
||||
"input[value*=\"i.imgur.com\"]",
|
||||
"[class*=\"copy-link\"] input",
|
||||
"[data-link]",
|
||||
};
|
||||
attribute = "href";
|
||||
} else if (PROVIDER_POSTIMAGES.equals(provider)) {
|
||||
selectorCandidates =
|
||||
new String[] {
|
||||
"input[readonly][value*=\"postimg\"]",
|
||||
"a[href*=\"i.postimg.cc\"]",
|
||||
"[class*=\"direct-link\"]",
|
||||
"textarea",
|
||||
};
|
||||
attribute = "value";
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return buildSuccessJs(selectorCandidates, attribute);
|
||||
}
|
||||
|
||||
private static String buildSuccessJs(String[] selectors, String attr) {
|
||||
StringBuilder sb = new StringBuilder("(function(){var s=[");
|
||||
for (int i = 0; i < selectors.length; i++) {
|
||||
if (i > 0) sb.append(",");
|
||||
sb.append("\"").append(escapeJs(selectors[i])).append("\"");
|
||||
}
|
||||
sb.append("];for(var i=0;i<s.length;i++){try{var el=document.querySelector(s[i]);if(!el)continue;var v=(el.value||el.getAttribute(\"value\")||\"\").trim();var h=(el.getAttribute(\"href\")||el.href||\"\").trim();var t=(el.textContent||\"\").trim();if(h&&h.indexOf(\"http\")===0)return h;if(v&&v.indexOf(\"http\")===0)return v;if(t&&t.indexOf(\"http\")===0)return t}catch(e){}}return null})()");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* JS to detect blocked state (captcha/login/challenge). Returns true if blocked.
|
||||
*/
|
||||
public static String getBlockedJs(String provider) {
|
||||
String[] blockedIndicators;
|
||||
if (PROVIDER_IMGUR.equals(provider)) {
|
||||
blockedIndicators =
|
||||
new String[] {
|
||||
"#challenge", ".captcha", ".g-recaptcha", "#recaptcha", ".signin", ".login",
|
||||
};
|
||||
} else if (PROVIDER_POSTIMAGES.equals(provider)) {
|
||||
blockedIndicators =
|
||||
new String[] {
|
||||
"#challenge", ".captcha", ".g-recaptcha", "#recaptcha",
|
||||
};
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return buildBlockedJs(blockedIndicators);
|
||||
}
|
||||
|
||||
private static String buildBlockedJs(String[] selectors) {
|
||||
StringBuilder sb = new StringBuilder("(function(){var s=[");
|
||||
for (int i = 0; i < selectors.length; i++) {
|
||||
if (i > 0) sb.append(",");
|
||||
sb.append("\"").append(escapeJs(selectors[i])).append("\"");
|
||||
}
|
||||
sb.append("];for(var i=0;i<s.length;i++){if(document.querySelector(s[i]))return true}return false})()");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static String escapeJs(String s) {
|
||||
if (s == null) return "";
|
||||
return s.replace("\\", "\\\\").replace("\"", "\\\"");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user