mirror of
https://github.com/TeamPiped/Piped-Backend.git
synced 2025-04-29 08:20:30 +05:30
Add in-memory anti-captcha implementation.
This commit is contained in:
parent
167f7791a9
commit
a0759b4722
@ -28,6 +28,7 @@ dependencies {
|
|||||||
implementation 'com.github.ben-manes.caffeine:caffeine:2.8.6'
|
implementation 'com.github.ben-manes.caffeine:caffeine:2.8.6'
|
||||||
implementation 'com.rometools:rome:1.15.0'
|
implementation 'com.rometools:rome:1.15.0'
|
||||||
implementation 'com.github.ipfs:java-ipfs-http-client:v1.3.3'
|
implementation 'com.github.ipfs:java-ipfs-http-client:v1.3.3'
|
||||||
|
implementation 'org.jsoup:jsoup:1.13.1'
|
||||||
implementation 'net.java.dev.jna:jna-platform:5.7.0'
|
implementation 'net.java.dev.jna:jna-platform:5.7.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,5 +3,8 @@
|
|||||||
PORT: 8080
|
PORT: 8080
|
||||||
|
|
||||||
# Proxy
|
# Proxy
|
||||||
PROXY_PART: https://pipedproxy.kavin.rocks
|
PROXY_PART: https://pipedproxy-ams.kavin.rocks
|
||||||
|
|
||||||
|
# Captcha Parameters
|
||||||
|
CAPTCHA_BASE_URL: https://api.capmonster.cloud/
|
||||||
|
CAPTCHA_API_KEY: INSERT_HERE
|
||||||
|
@ -22,10 +22,14 @@ public class Constants {
|
|||||||
|
|
||||||
public static final String PROXY_PART;
|
public static final String PROXY_PART;
|
||||||
|
|
||||||
|
public static final String CAPTCHA_BASE_URL, CAPTCHA_API_KEY;
|
||||||
|
|
||||||
public static final StreamingService YOUTUBE_SERVICE;
|
public static final StreamingService YOUTUBE_SERVICE;
|
||||||
|
|
||||||
public static final HttpClient h2client = HttpClient.newBuilder().followRedirects(Redirect.NORMAL)
|
public static final HttpClient h2client = HttpClient.newBuilder().followRedirects(Redirect.NORMAL)
|
||||||
.version(Version.HTTP_2).build();
|
.version(Version.HTTP_2).build();
|
||||||
|
public static final HttpClient h2_no_redir_client = HttpClient.newBuilder().followRedirects(Redirect.NEVER)
|
||||||
|
.version(Version.HTTP_2).build();
|
||||||
// public static final HttpClient h3client = Http3ClientBuilder.newBuilder().followRedirects(Redirect.NORMAL).build();
|
// public static final HttpClient h3client = Http3ClientBuilder.newBuilder().followRedirects(Redirect.NORMAL).build();
|
||||||
|
|
||||||
public static final MongoClient mongoClient;
|
public static final MongoClient mongoClient;
|
||||||
@ -40,6 +44,8 @@ public class Constants {
|
|||||||
|
|
||||||
PORT = Integer.parseInt(prop.getProperty("PORT"));
|
PORT = Integer.parseInt(prop.getProperty("PORT"));
|
||||||
PROXY_PART = prop.getProperty("PROXY_PART");
|
PROXY_PART = prop.getProperty("PROXY_PART");
|
||||||
|
CAPTCHA_BASE_URL = prop.getProperty("CAPTCHA_BASE_URL");
|
||||||
|
CAPTCHA_API_KEY = prop.getProperty("CAPTCHA_API_KEY");
|
||||||
mongoClient = null/* MongoClients.create(prop.getProperty("MONGO_URI")) */;
|
mongoClient = null/* MongoClients.create(prop.getProperty("MONGO_URI")) */;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
|
99
src/main/java/me/kavin/piped/utils/CaptchaSolver.java
Normal file
99
src/main/java/me/kavin/piped/utils/CaptchaSolver.java
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
package me.kavin.piped.utils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.http.HttpRequest;
|
||||||
|
import java.net.http.HttpRequest.BodyPublishers;
|
||||||
|
import java.net.http.HttpRequest.Builder;
|
||||||
|
import java.net.http.HttpResponse.BodyHandlers;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.grack.nanojson.JsonObject;
|
||||||
|
import com.grack.nanojson.JsonParser;
|
||||||
|
import com.grack.nanojson.JsonParserException;
|
||||||
|
import com.grack.nanojson.JsonWriter;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||||
|
import me.kavin.piped.consts.Constants;
|
||||||
|
import me.kavin.piped.utils.obj.SolvedCaptcha;
|
||||||
|
|
||||||
|
public class CaptchaSolver {
|
||||||
|
|
||||||
|
public static SolvedCaptcha solve(String url, String sitekey, String data_s)
|
||||||
|
throws JsonParserException, IOException, InterruptedException {
|
||||||
|
|
||||||
|
int taskId = createTask(url, sitekey, data_s);
|
||||||
|
|
||||||
|
return waitForSolve(taskId);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int createTask(String url, String sitekey, String data_s)
|
||||||
|
throws JsonParserException, IOException, InterruptedException {
|
||||||
|
|
||||||
|
Builder builder = HttpRequest.newBuilder(URI.create(Constants.CAPTCHA_BASE_URL + "createTask"));
|
||||||
|
JsonObject jObject = new JsonObject();
|
||||||
|
jObject.put("clientKey", Constants.CAPTCHA_API_KEY);
|
||||||
|
{
|
||||||
|
JsonObject task = new JsonObject();
|
||||||
|
task.put("type", "NoCaptchaTaskProxyless");
|
||||||
|
task.put("websiteURL", url);
|
||||||
|
task.put("websiteKey", sitekey);
|
||||||
|
task.put("recaptchaDataSValue", data_s);
|
||||||
|
jObject.put("task", task);
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.method("POST", BodyPublishers.ofString(JsonWriter.string(jObject)));
|
||||||
|
|
||||||
|
builder.header("Content-Type", "application/json");
|
||||||
|
|
||||||
|
JsonObject taskObj = JsonParser.object()
|
||||||
|
.from(Constants.h2client.send(builder.build(), BodyHandlers.ofInputStream()).body());
|
||||||
|
|
||||||
|
return taskObj.getInt("taskId");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final SolvedCaptcha waitForSolve(int taskId)
|
||||||
|
throws JsonParserException, IOException, InterruptedException {
|
||||||
|
|
||||||
|
String body = JsonWriter.string(
|
||||||
|
JsonObject.builder().value("clientKey", Constants.CAPTCHA_API_KEY).value("taskId", taskId).done());
|
||||||
|
|
||||||
|
SolvedCaptcha solved = null;
|
||||||
|
|
||||||
|
outer: while (true) {
|
||||||
|
Builder builder = HttpRequest.newBuilder(URI.create(Constants.CAPTCHA_BASE_URL + "getTaskResult"));
|
||||||
|
|
||||||
|
builder.method("POST", BodyPublishers.ofString(body));
|
||||||
|
|
||||||
|
builder.header("Content-Type", "application/json");
|
||||||
|
|
||||||
|
JsonObject captchaObj = JsonParser.object()
|
||||||
|
.from(Constants.h2client.send(builder.build(), BodyHandlers.ofInputStream()).body());
|
||||||
|
|
||||||
|
if (captchaObj.getInt("errorId") != 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (captchaObj.has("solution")) {
|
||||||
|
JsonObject solution = captchaObj.getObject("solution");
|
||||||
|
String captchaResp = solution.getString("gRecaptchaResponse");
|
||||||
|
JsonObject cookieObj = solution.getObject("cookies");
|
||||||
|
Map<String, String> cookies = new Object2ObjectOpenHashMap<>();
|
||||||
|
|
||||||
|
if (captchaResp != null) {
|
||||||
|
|
||||||
|
cookieObj.keySet().forEach(cookie -> {
|
||||||
|
cookies.put(cookie, cookieObj.getString(cookie));
|
||||||
|
});
|
||||||
|
|
||||||
|
solved = new SolvedCaptcha(cookies, captchaResp);
|
||||||
|
break outer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Thread.sleep(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
return solved;
|
||||||
|
}
|
||||||
|
}
|
@ -1,22 +1,35 @@
|
|||||||
package me.kavin.piped.utils;
|
package me.kavin.piped.utils;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.HttpCookie;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.http.HttpRequest;
|
import java.net.http.HttpRequest;
|
||||||
import java.net.http.HttpRequest.BodyPublisher;
|
import java.net.http.HttpRequest.BodyPublisher;
|
||||||
|
import java.net.http.HttpRequest.BodyPublishers;
|
||||||
import java.net.http.HttpRequest.Builder;
|
import java.net.http.HttpRequest.Builder;
|
||||||
import java.net.http.HttpResponse;
|
import java.net.http.HttpResponse;
|
||||||
import java.net.http.HttpResponse.BodyHandlers;
|
import java.net.http.HttpResponse.BodyHandlers;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.jsoup.Jsoup;
|
||||||
|
import org.jsoup.nodes.Element;
|
||||||
import org.schabi.newpipe.extractor.downloader.Downloader;
|
import org.schabi.newpipe.extractor.downloader.Downloader;
|
||||||
import org.schabi.newpipe.extractor.downloader.Request;
|
import org.schabi.newpipe.extractor.downloader.Request;
|
||||||
import org.schabi.newpipe.extractor.downloader.Response;
|
import org.schabi.newpipe.extractor.downloader.Response;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
|
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
|
||||||
|
|
||||||
|
import com.grack.nanojson.JsonParserException;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||||
import me.kavin.piped.consts.Constants;
|
import me.kavin.piped.consts.Constants;
|
||||||
|
import me.kavin.piped.utils.obj.SolvedCaptcha;
|
||||||
|
|
||||||
public class DownloaderImpl extends Downloader {
|
public class DownloaderImpl extends Downloader {
|
||||||
|
|
||||||
|
private static HttpCookie saved_cookie;
|
||||||
|
private static final Object cookie_lock = new Object();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes a request with HTTP/2.
|
* Executes a request with HTTP/2.
|
||||||
*/
|
*/
|
||||||
@ -31,10 +44,14 @@ public class DownloaderImpl extends Downloader {
|
|||||||
: HttpRequest.BodyPublishers.ofByteArray(data);
|
: HttpRequest.BodyPublishers.ofByteArray(data);
|
||||||
|
|
||||||
builder.method(request.httpMethod(), publisher);
|
builder.method(request.httpMethod(), publisher);
|
||||||
request.headers().forEach((name, values) -> values.forEach(value -> builder.header(name, value)));
|
|
||||||
|
|
||||||
builder.setHeader("User-Agent", Constants.USER_AGENT);
|
builder.setHeader("User-Agent", Constants.USER_AGENT);
|
||||||
|
|
||||||
|
if (saved_cookie != null && !saved_cookie.hasExpired())
|
||||||
|
builder.setHeader("Cookie", saved_cookie.getName() + "=" + saved_cookie.getValue());
|
||||||
|
|
||||||
|
request.headers().forEach((name, values) -> values.forEach(value -> builder.header(name, value)));
|
||||||
|
|
||||||
HttpResponse<String> response = null;
|
HttpResponse<String> response = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -43,9 +60,76 @@ public class DownloaderImpl extends Downloader {
|
|||||||
// ignored
|
// ignored
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Implement solver
|
|
||||||
if (response.statusCode() == 429) {
|
if (response.statusCode() == 429) {
|
||||||
throw new ReCaptchaException("reCaptcha Challenge requested", String.valueOf(response.uri()));
|
|
||||||
|
synchronized (cookie_lock) {
|
||||||
|
|
||||||
|
if (saved_cookie != null && saved_cookie.hasExpired())
|
||||||
|
saved_cookie = null;
|
||||||
|
|
||||||
|
String redir_url = String.valueOf(response.request().uri());
|
||||||
|
|
||||||
|
if (saved_cookie == null && redir_url.startsWith("https://www.google.com/sorry")) {
|
||||||
|
|
||||||
|
Map<String, String> formParams = new Object2ObjectOpenHashMap<>();
|
||||||
|
String sitekey = null, data_s = null;
|
||||||
|
|
||||||
|
for (Element el : Jsoup.parse(response.body()).selectFirst("form").children()) {
|
||||||
|
String name;
|
||||||
|
if (!(name = el.tagName()).equals("script")) {
|
||||||
|
if (name.equals("input"))
|
||||||
|
formParams.put(el.attr("name"), el.attr("value"));
|
||||||
|
else if (name.equals("div") && el.attr("id").equals("recaptcha")) {
|
||||||
|
sitekey = el.attr("data-sitekey");
|
||||||
|
data_s = el.attr("data-s");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sitekey == null || data_s == null)
|
||||||
|
throw new ReCaptchaException("Could not get recaptcha", redir_url);
|
||||||
|
|
||||||
|
SolvedCaptcha solved = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
solved = CaptchaSolver.solve(redir_url, sitekey, data_s);
|
||||||
|
} catch (JsonParserException | InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
formParams.put("g-recaptcha-response", solved.getRecaptchaResponse());
|
||||||
|
|
||||||
|
Builder formBuilder = HttpRequest.newBuilder(URI.create("https://www.google.com/sorry/index"));
|
||||||
|
|
||||||
|
formBuilder.setHeader("User-Agent", Constants.USER_AGENT);
|
||||||
|
|
||||||
|
StringBuilder formBody = new StringBuilder();
|
||||||
|
|
||||||
|
formParams.forEach((name, value) -> {
|
||||||
|
formBody.append(name + "=" + URLUtils.silentEncode(value) + "&");
|
||||||
|
});
|
||||||
|
|
||||||
|
formBuilder.header("content-type", "application/x-www-form-urlencoded");
|
||||||
|
|
||||||
|
formBuilder.method("POST",
|
||||||
|
BodyPublishers.ofString(String.valueOf(formBody.substring(0, formBody.length() - 1))));
|
||||||
|
|
||||||
|
try {
|
||||||
|
HttpResponse<String> formResponse = Constants.h2_no_redir_client.send(formBuilder.build(),
|
||||||
|
BodyHandlers.ofString());
|
||||||
|
|
||||||
|
saved_cookie = HttpCookie.parse(URLUtils.silentDecode(StringUtils
|
||||||
|
.substringAfter(formResponse.headers().firstValue("Location").get(), "google_abuse=")))
|
||||||
|
.get(0);
|
||||||
|
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (saved_cookie != null) // call again as captcha has been solved or cookie has not expired.
|
||||||
|
execute(request);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Response(response.statusCode(), "UNDEFINED", response.headers().map(), response.body(),
|
return new Response(response.statusCode(), "UNDEFINED", response.headers().map(), response.body(),
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package me.kavin.piped.utils;
|
package me.kavin.piped.utils;
|
||||||
|
|
||||||
|
import java.net.URLDecoder;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
|
|
||||||
public class URLUtils {
|
public class URLUtils {
|
||||||
@ -12,4 +13,13 @@ public class URLUtils {
|
|||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String silentDecode(String s) {
|
||||||
|
try {
|
||||||
|
return URLDecoder.decode(s, "UTF-8");
|
||||||
|
} catch (Exception e) {
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
22
src/main/java/me/kavin/piped/utils/obj/SolvedCaptcha.java
Normal file
22
src/main/java/me/kavin/piped/utils/obj/SolvedCaptcha.java
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package me.kavin.piped.utils.obj;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class SolvedCaptcha {
|
||||||
|
|
||||||
|
private Map<String, String> cookies;
|
||||||
|
private String gRecaptchaResponse;
|
||||||
|
|
||||||
|
public SolvedCaptcha(Map<String, String> cookies, String gRecaptchaResponse) {
|
||||||
|
this.cookies = cookies;
|
||||||
|
this.gRecaptchaResponse = gRecaptchaResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getCookies() {
|
||||||
|
return cookies;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRecaptchaResponse() {
|
||||||
|
return gRecaptchaResponse;
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user