Refactor Utility methods and migrate to OkHttp (#179)

This commit is contained in:
Kavin 2022-02-02 21:05:22 +00:00 committed by GitHub
parent b2cf84b41f
commit c07cf5fd1f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 276 additions and 390 deletions

View file

@ -38,6 +38,9 @@ dependencies {
implementation 'com.zaxxer:HikariCP:5.0.1' implementation 'com.zaxxer:HikariCP:5.0.1'
implementation 'org.springframework.security:spring-security-crypto:5.6.1' implementation 'org.springframework.security:spring-security-crypto:5.6.1'
implementation 'commons-logging:commons-logging:1.2' implementation 'commons-logging:commons-logging:1.2'
implementation(platform("com.squareup.okhttp3:okhttp-bom:4.9.3"))
implementation("com.squareup.okhttp3:okhttp")
implementation("com.squareup.okhttp3:okhttp-brotli")
} }
shadowJar { shadowJar {

View file

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://downloads.gradle.org/distributions/gradle-7.2-bin.zip distributionUrl=https\://downloads.gradle.org/distributions/gradle-7.3.3-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View file

@ -1,28 +1,8 @@
package me.kavin.piped; package me.kavin.piped;
import static io.activej.config.converter.ConfigConverters.ofInetSocketAddress;
import static io.activej.http.HttpHeaders.AUTHORIZATION;
import static io.activej.http.HttpHeaders.CACHE_CONTROL;
import static io.activej.http.HttpHeaders.CONTENT_TYPE;
import static io.activej.http.HttpHeaders.LINK;
import static io.activej.http.HttpHeaders.LOCATION;
import static io.activej.http.HttpMethod.GET;
import static io.activej.http.HttpMethod.POST;
import java.io.ByteArrayInputStream;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.Executor;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.hibernate.Session;
import org.jetbrains.annotations.NotNull;
import org.xml.sax.InputSource;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.rometools.rome.feed.synd.SyndFeed; import com.rometools.rome.feed.synd.SyndFeed;
import com.rometools.rome.io.SyndFeedInput; import com.rometools.rome.io.SyndFeedInput;
import io.activej.config.Config; import io.activej.config.Config;
import io.activej.http.AsyncServlet; import io.activej.http.AsyncServlet;
import io.activej.http.HttpMethod; import io.activej.http.HttpMethod;
@ -33,15 +13,25 @@ import io.activej.inject.module.AbstractModule;
import io.activej.inject.module.Module; import io.activej.inject.module.Module;
import io.activej.launchers.http.MultithreadedHttpServerLauncher; import io.activej.launchers.http.MultithreadedHttpServerLauncher;
import me.kavin.piped.consts.Constants; import me.kavin.piped.consts.Constants;
import me.kavin.piped.utils.CustomServletDecorator; import me.kavin.piped.utils.*;
import me.kavin.piped.utils.DatabaseSessionFactory;
import me.kavin.piped.utils.ExceptionHandler;
import me.kavin.piped.utils.Multithreading;
import me.kavin.piped.utils.ResponseHelper;
import me.kavin.piped.utils.SponsorBlockUtils;
import me.kavin.piped.utils.resp.ErrorResponse; import me.kavin.piped.utils.resp.ErrorResponse;
import me.kavin.piped.utils.resp.LoginRequest; import me.kavin.piped.utils.resp.LoginRequest;
import me.kavin.piped.utils.resp.SubscriptionUpdateRequest; import me.kavin.piped.utils.resp.SubscriptionUpdateRequest;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.hibernate.Session;
import org.jetbrains.annotations.NotNull;
import org.xml.sax.InputSource;
import java.io.ByteArrayInputStream;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import java.util.concurrent.Executor;
import static io.activej.config.converter.ConfigConverters.ofInetSocketAddress;
import static io.activej.http.HttpHeaders.*;
import static io.activej.http.HttpMethod.GET;
import static io.activej.http.HttpMethod.POST;
public class ServerLauncher extends MultithreadedHttpServerLauncher { public class ServerLauncher extends MultithreadedHttpServerLauncher {
@ -55,9 +45,8 @@ public class ServerLauncher extends MultithreadedHttpServerLauncher {
RoutingServlet router = RoutingServlet.create() RoutingServlet router = RoutingServlet.create()
.map(HttpMethod.OPTIONS, "/*", request -> HttpResponse.ofCode(200)) .map(HttpMethod.OPTIONS, "/*", request -> HttpResponse.ofCode(200))
.map(GET, "/webhooks/pubsub", request -> { .map(GET, "/webhooks/pubsub", request -> HttpResponse.ok200().withPlainText(Objects.requireNonNull(request.getQueryParameter("hub.challenge"))))
return HttpResponse.ok200().withPlainText(request.getQueryParameter("hub.challenge")); .map(POST, "/webhooks/pubsub", AsyncServlet.ofBlocking(executor, request -> {
}).map(POST, "/webhooks/pubsub", AsyncServlet.ofBlocking(executor, request -> {
try { try {
SyndFeed feed = new SyndFeedInput().build( SyndFeed feed = new SyndFeedInput().build(
@ -320,7 +309,7 @@ public class ServerLauncher extends MultithreadedHttpServerLauncher {
} }
private @NotNull HttpResponse getRawResponse(int code, byte[] body, String contentType, String cache, private @NotNull HttpResponse getRawResponse(int code, byte[] body, String contentType, String cache,
boolean prefetchProxy) { boolean prefetchProxy) {
HttpResponse response = HttpResponse.ofCode(code).withBody(body).withHeader(CONTENT_TYPE, contentType) HttpResponse response = HttpResponse.ofCode(code).withBody(body).withHeader(CONTENT_TYPE, contentType)
.withHeader(CACHE_CONTROL, cache); .withHeader(CACHE_CONTROL, cache);
if (prefetchProxy) if (prefetchProxy)

View file

@ -3,6 +3,8 @@ package me.kavin.piped.consts;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import me.kavin.piped.utils.PageMixin; import me.kavin.piped.utils.PageMixin;
import okhttp3.OkHttpClient;
import okhttp3.brotli.BrotliInterceptor;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.Page; import org.schabi.newpipe.extractor.Page;
@ -11,10 +13,6 @@ import org.schabi.newpipe.extractor.StreamingService;
import java.io.FileReader; import java.io.FileReader;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.ProxySelector; import java.net.ProxySelector;
import java.net.http.HttpClient;
import java.net.http.HttpClient.Builder;
import java.net.http.HttpClient.Redirect;
import java.net.http.HttpClient.Version;
import java.util.Properties; import java.util.Properties;
public class Constants { public class Constants {
@ -36,8 +34,8 @@ public class Constants {
public static final String FRONTEND_URL; public static final String FRONTEND_URL;
public static final HttpClient h2client; public static final OkHttpClient h2client;
public static final HttpClient h2_no_redir_client; public static final OkHttpClient h2_no_redir_client;
public static final boolean COMPROMISED_PASSWORD_CHECK; public static final boolean COMPROMISED_PASSWORD_CHECK;
@ -76,8 +74,12 @@ public class Constants {
if (key.startsWith("hibernate")) if (key.startsWith("hibernate"))
hibernateProperties.put(key, value); hibernateProperties.put(key, value);
}); });
Builder builder = HttpClient.newBuilder().followRedirects(Redirect.NORMAL).version(Version.HTTP_1_1); OkHttpClient.Builder builder = new OkHttpClient.Builder()
Builder builder_noredir = HttpClient.newBuilder().followRedirects(Redirect.NEVER).version(Version.HTTP_1_1); .followRedirects(true)
.addInterceptor(BrotliInterceptor.INSTANCE);
OkHttpClient.Builder builder_noredir = new OkHttpClient.Builder()
.followRedirects(false)
.addInterceptor(BrotliInterceptor.INSTANCE);
if (HTTP_PROXY != null && HTTP_PROXY.contains(":")) { if (HTTP_PROXY != null && HTTP_PROXY.contains(":")) {
String host = StringUtils.substringBefore(HTTP_PROXY, ":"); String host = StringUtils.substringBefore(HTTP_PROXY, ":");
String port = StringUtils.substringAfter(HTTP_PROXY, ":"); String port = StringUtils.substringAfter(HTTP_PROXY, ":");

View file

@ -1,21 +1,18 @@
package me.kavin.piped.utils; 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.JsonObject;
import com.grack.nanojson.JsonParser; import com.grack.nanojson.JsonParser;
import com.grack.nanojson.JsonParserException; import com.grack.nanojson.JsonParserException;
import com.grack.nanojson.JsonWriter; import com.grack.nanojson.JsonWriter;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; 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; import me.kavin.piped.utils.obj.SolvedCaptcha;
import okhttp3.MediaType;
import okhttp3.Request;
import okhttp3.RequestBody;
import java.io.IOException;
import java.util.Map;
public class CaptchaSolver { public class CaptchaSolver {
@ -29,9 +26,8 @@ public class CaptchaSolver {
} }
private static int createTask(String url, String sitekey, String data_s) private static int createTask(String url, String sitekey, String data_s)
throws JsonParserException, IOException, InterruptedException { throws JsonParserException, IOException {
Builder builder = HttpRequest.newBuilder(URI.create(Constants.CAPTCHA_BASE_URL + "createTask"));
JsonObject jObject = new JsonObject(); JsonObject jObject = new JsonObject();
jObject.put("clientKey", Constants.CAPTCHA_API_KEY); jObject.put("clientKey", Constants.CAPTCHA_API_KEY);
{ {
@ -43,17 +39,16 @@ public class CaptchaSolver {
jObject.put("task", task); jObject.put("task", task);
} }
builder.method("POST", BodyPublishers.ofString(JsonWriter.string(jObject))); var builder = new Request.Builder().url(Constants.CAPTCHA_BASE_URL + "createTask")
.post(RequestBody.create(JsonWriter.string(jObject), MediaType.get("application/json")));
builder.header("Content-Type", "application/json");
JsonObject taskObj = JsonParser.object() JsonObject taskObj = JsonParser.object()
.from(Constants.h2client.send(builder.build(), BodyHandlers.ofInputStream()).body()); .from(Constants.h2client.newCall(builder.build()).execute().body().byteStream());
return taskObj.getInt("taskId"); return taskObj.getInt("taskId");
} }
private static final SolvedCaptcha waitForSolve(int taskId) private static SolvedCaptcha waitForSolve(int taskId)
throws JsonParserException, IOException, InterruptedException { throws JsonParserException, IOException, InterruptedException {
String body = JsonWriter.string( String body = JsonWriter.string(
@ -61,15 +56,15 @@ public class CaptchaSolver {
SolvedCaptcha solved = null; SolvedCaptcha solved = null;
outer: while (true) { while (true) {
Builder builder = HttpRequest.newBuilder(URI.create(Constants.CAPTCHA_BASE_URL + "getTaskResult")); var builder = new Request.Builder()
.url(Constants.CAPTCHA_BASE_URL + "getTaskResult")
builder.method("POST", BodyPublishers.ofString(body)); .post(RequestBody.create(body, MediaType.get("application/json")));
builder.header("Content-Type", "application/json"); builder.header("Content-Type", "application/json");
JsonObject captchaObj = JsonParser.object() JsonObject captchaObj = JsonParser.object()
.from(Constants.h2client.send(builder.build(), BodyHandlers.ofInputStream()).body()); .from(Constants.h2client.newCall(builder.build()).execute().body().byteStream());
if (captchaObj.getInt("errorId") != 0) if (captchaObj.getInt("errorId") != 0)
break; break;
@ -87,7 +82,7 @@ public class CaptchaSolver {
}); });
solved = new SolvedCaptcha(cookies, captchaResp); solved = new SolvedCaptcha(cookies, captchaResp);
break outer; break;
} }
} }

View file

@ -1,18 +1,12 @@
package me.kavin.piped.utils; package me.kavin.piped.utils;
import io.activej.http.*;
import io.activej.promise.Promisable;
import org.jetbrains.annotations.NotNull;
import static io.activej.http.HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS; import static io.activej.http.HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS;
import static io.activej.http.HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN; import static io.activej.http.HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN;
import org.jetbrains.annotations.NotNull;
import io.activej.http.AsyncServlet;
import io.activej.http.HttpHeader;
import io.activej.http.HttpHeaderValue;
import io.activej.http.HttpHeaders;
import io.activej.http.HttpRequest;
import io.activej.http.HttpResponse;
import io.activej.promise.Promisable;
public class CustomServletDecorator implements AsyncServlet { public class CustomServletDecorator implements AsyncServlet {
private static final HttpHeader HEADER = HttpHeaders.of("Server-Timing"); private static final HttpHeader HEADER = HttpHeaders.of("Server-Timing");

View file

@ -1,18 +1,13 @@
package me.kavin.piped.utils; package me.kavin.piped.utils;
import java.io.IOException; import com.github.benmanes.caffeine.cache.Caffeine;
import java.net.HttpCookie; import com.github.benmanes.caffeine.cache.LoadingCache;
import java.net.URI;
import java.net.http.HttpRequest;
import java.net.http.HttpRequest.BodyPublisher;
import java.net.http.HttpRequest.BodyPublishers;
import java.net.http.HttpRequest.Builder;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import com.github.benmanes.caffeine.cache.Scheduler; import com.github.benmanes.caffeine.cache.Scheduler;
import com.grack.nanojson.JsonParserException;
import me.kavin.piped.consts.Constants;
import me.kavin.piped.utils.obj.SolvedCaptcha;
import okhttp3.FormBody;
import okhttp3.RequestBody;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jsoup.Jsoup; import org.jsoup.Jsoup;
@ -22,13 +17,9 @@ 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.github.benmanes.caffeine.cache.Caffeine; import java.io.IOException;
import com.github.benmanes.caffeine.cache.LoadingCache; import java.net.HttpCookie;
import com.grack.nanojson.JsonParserException; import java.util.concurrent.TimeUnit;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import me.kavin.piped.consts.Constants;
import me.kavin.piped.utils.obj.SolvedCaptcha;
public class DownloaderImpl extends Downloader { public class DownloaderImpl extends Downloader {
@ -52,30 +43,24 @@ public class DownloaderImpl extends Downloader {
public Response executeRequest(Request request) throws IOException, ReCaptchaException { public Response executeRequest(Request request) throws IOException, ReCaptchaException {
// TODO: HTTP/3 aka QUIC // TODO: HTTP/3 aka QUIC
Builder builder = HttpRequest.newBuilder(URI.create(request.url())); var bytes = request.dataToSend();
RequestBody body = null;
if (bytes != null)
body = RequestBody.create(bytes);
byte[] data = request.dataToSend(); var builder = new okhttp3.Request.Builder()
BodyPublisher publisher = data == null ? HttpRequest.BodyPublishers.noBody() .url(request.url())
: HttpRequest.BodyPublishers.ofByteArray(data); .method(request.httpMethod(), body)
.header("User-Agent", Constants.USER_AGENT);
builder.method(request.httpMethod(), publisher);
builder.setHeader("User-Agent", Constants.USER_AGENT);
if (saved_cookie != null && !saved_cookie.hasExpired()) if (saved_cookie != null && !saved_cookie.hasExpired())
builder.setHeader("Cookie", saved_cookie.getName() + "=" + saved_cookie.getValue()); builder.header("Cookie", saved_cookie.getName() + "=" + saved_cookie.getValue());
request.headers().forEach((name, values) -> values.forEach(value -> builder.header(name, value))); request.headers().forEach((name, values) -> values.forEach(value -> builder.header(name, value)));
HttpResponse<String> response = null; var response = Constants.h2client.newCall(builder.build()).execute();
try { if (response.code() == 429) {
response = Constants.h2client.send(builder.build(), BodyHandlers.ofString());
} catch (InterruptedException e) {
// ignored
}
if (response.statusCode() == 429) {
synchronized (cookie_lock) { synchronized (cookie_lock) {
@ -83,25 +68,25 @@ public class DownloaderImpl extends Downloader {
|| (System.currentTimeMillis() - cookie_received > TimeUnit.MINUTES.toMillis(30))) || (System.currentTimeMillis() - cookie_received > TimeUnit.MINUTES.toMillis(30)))
saved_cookie = null; saved_cookie = null;
String redir_url = String.valueOf(response.request().uri()); String redir_url = String.valueOf(response.request().url());
if (saved_cookie == null && redir_url.startsWith("https://www.google.com/sorry")) { if (saved_cookie == null && redir_url.startsWith("https://www.google.com/sorry")) {
Map<String, String> formParams = new Object2ObjectOpenHashMap<>(); var formBuilder = new FormBody.Builder();
String sitekey = null, data_s = null; String sitekey = null, data_s = null;
for (Element el : Jsoup.parse(response.body()).selectFirst("form").children()) { for (Element el : Jsoup.parse(response.body().string()).selectFirst("form").children()) {
String name; String name;
if (!(name = el.tagName()).equals("script")) { if (!(name = el.tagName()).equals("script")) {
if (name.equals("input")) if (name.equals("input"))
formParams.put(el.attr("name"), el.attr("value")); formBuilder.add(el.attr("name"), el.attr("value"));
else if (name.equals("div") && el.attr("id").equals("recaptcha")) { else if (name.equals("div") && el.attr("id").equals("recaptcha")) {
sitekey = el.attr("data-sitekey"); sitekey = el.attr("data-sitekey");
data_s = el.attr("data-s"); data_s = el.attr("data-s");
} }
} }
} }
if (sitekey == null || data_s == null) if (StringUtils.isEmpty(sitekey) || StringUtils.isEmpty(data_s))
throw new ReCaptchaException("Could not get recaptcha", redir_url); throw new ReCaptchaException("Could not get recaptcha", redir_url);
SolvedCaptcha solved = null; SolvedCaptcha solved = null;
@ -112,35 +97,19 @@ public class DownloaderImpl extends Downloader {
e.printStackTrace(); e.printStackTrace();
} }
formParams.put("g-recaptcha-response", solved.getRecaptchaResponse()); formBuilder.add("g-recaptcha-response", solved.getRecaptchaResponse());
Builder formBuilder = HttpRequest.newBuilder(URI.create("https://www.google.com/sorry/index")); var formReqBuilder = new okhttp3.Request.Builder()
.url("https://www.google.com/sorry/index")
.header("User-Agent", Constants.USER_AGENT)
.post(formBuilder.build());
formBuilder.setHeader("User-Agent", Constants.USER_AGENT); var formResponse = Constants.h2_no_redir_client.newCall(formReqBuilder.build()).execute();
StringBuilder formBody = new StringBuilder(); saved_cookie = HttpCookie.parse(URLUtils.silentDecode(StringUtils
.substringAfter(formResponse.headers().get("Location"), "google_abuse=")))
formParams.forEach((name, value) -> { .get(0);
formBody.append(name + "=" + URLUtils.silentEncode(value) + "&"); cookie_received = System.currentTimeMillis();
});
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);
cookie_received = System.currentTimeMillis();
} catch (InterruptedException e) {
e.printStackTrace();
}
} }
if (saved_cookie != null) // call again as captcha has been solved or cookie has not expired. if (saved_cookie != null) // call again as captcha has been solved or cookie has not expired.
@ -149,7 +118,7 @@ public class DownloaderImpl extends Downloader {
} }
return new Response(response.statusCode(), "UNDEFINED", response.headers().map(), response.body(), return new Response(response.code(), response.message(), response.headers().toMultimap(), response.body().string(),
String.valueOf(response.uri())); String.valueOf(response.request().url()));
} }
} }

View file

@ -1,12 +1,10 @@
package me.kavin.piped.utils; package me.kavin.piped.utils;
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException;
import java.util.concurrent.CompletionException; import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import org.schabi.newpipe.extractor.exceptions.AgeRestrictedContentException;
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException;
import org.schabi.newpipe.extractor.exceptions.GeographicRestrictionException;
public class ExceptionHandler { public class ExceptionHandler {
public static Exception handle(Exception e) { public static Exception handle(Exception e) {
@ -18,8 +16,7 @@ public class ExceptionHandler {
if (e.getCause() != null && (e instanceof ExecutionException || e instanceof CompletionException)) if (e.getCause() != null && (e instanceof ExecutionException || e instanceof CompletionException))
e = (Exception) e.getCause(); e = (Exception) e.getCause();
if (!(e instanceof AgeRestrictedContentException || e instanceof ContentNotAvailableException if (!(e instanceof ContentNotAvailableException)) {
|| e instanceof GeographicRestrictionException)) {
if (path != null) if (path != null)
System.err.println("An error occoured in the path: " + path); System.err.println("An error occoured in the path: " + path);
e.printStackTrace(); e.printStackTrace();

View file

@ -0,0 +1,39 @@
package me.kavin.piped.utils;
import me.kavin.piped.consts.Constants;
import okhttp3.MediaType;
import okhttp3.Request;
import okhttp3.RequestBody;
import org.json.JSONObject;
import java.io.IOException;
public class LbryHelper {
public static String getLBRYId(String videoId) throws IOException, InterruptedException {
return new JSONObject(
RequestUtils.sendGet("https://api.lbry.com/yt/resolve?video_ids=" + URLUtils.silentEncode(videoId))
).getJSONObject("data").getJSONObject("videos").optString(videoId, null);
}
public static String getLBRYStreamURL(String lbryId)
throws IOException {
var request = new Request.Builder()
.url("https://api.lbry.tv/api/v1/proxy?m=get")
.post(RequestBody.create(String.valueOf(
new JSONObject().put("id", System.currentTimeMillis())
.put("jsonrpc", "2.0")
.put("method", "get")
.put("params",
new JSONObject()
.put("uri", "lbry://" + lbryId)
.put("save_file", true)))
, MediaType.get("application/json")))
.build();
return new JSONObject(
Constants.h2client.newCall(request).execute().body().string()
).getJSONObject("result").getString("streaming_url");
}
}

View file

@ -1,23 +1,20 @@
package me.kavin.piped.utils; package me.kavin.piped.utils;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse.BodyHandlers;
import me.kavin.piped.consts.Constants; import me.kavin.piped.consts.Constants;
import okhttp3.Request;
import java.io.IOException;
public class RequestUtils { public class RequestUtils {
public static String sendGet(String url) throws IOException, InterruptedException, URISyntaxException { public static String sendGet(String url) throws IOException {
return sendGet(url, Constants.USER_AGENT); return sendGet(url, Constants.USER_AGENT);
} }
public static String sendGet(String url, String ua) throws IOException, InterruptedException, URISyntaxException { public static String sendGet(String url, String ua) throws IOException {
HttpRequest request = HttpRequest.newBuilder(new URI(url)).GET().setHeader("User-Agent", ua).build(); var request = new Request.Builder().header("User-Agent", ua).url(url).build();
return Constants.h2client.send(request, BodyHandlers.ofString()).body(); return Constants.h2client.newCall(request).execute().body().string();
} }
} }

View file

@ -1,41 +1,28 @@
package me.kavin.piped.utils; package me.kavin.piped.utils;
import static me.kavin.piped.consts.Constants.YOUTUBE_SERVICE; import com.fasterxml.jackson.core.JsonProcessingException;
import com.github.benmanes.caffeine.cache.Caffeine;
import java.io.IOException; import com.github.benmanes.caffeine.cache.LoadingCache;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.http.HttpRequest;
import java.net.http.HttpRequest.BodyPublishers;
import java.net.http.HttpRequest.Builder;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import com.github.benmanes.caffeine.cache.Scheduler; import com.github.benmanes.caffeine.cache.Scheduler;
import com.rometools.rome.feed.synd.*;
import com.rometools.rome.io.FeedException;
import com.rometools.rome.io.SyndFeedOutput;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import me.kavin.piped.consts.Constants;
import me.kavin.piped.ipfs.IPFS;
import me.kavin.piped.utils.obj.*;
import me.kavin.piped.utils.obj.db.PubSub;
import me.kavin.piped.utils.obj.db.User;
import me.kavin.piped.utils.obj.db.Video;
import me.kavin.piped.utils.obj.search.SearchChannel;
import me.kavin.piped.utils.obj.search.SearchPlaylist;
import me.kavin.piped.utils.resp.*;
import okhttp3.FormBody;
import okhttp3.Request;
import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.commons.lang3.exception.ExceptionUtils;
import org.hibernate.Session; import org.hibernate.Session;
import org.json.JSONObject;
import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.ListExtractor.InfoItemsPage; import org.schabi.newpipe.extractor.ListExtractor.InfoItemsPage;
import org.schabi.newpipe.extractor.Page; import org.schabi.newpipe.extractor.Page;
@ -44,7 +31,6 @@ import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
import org.schabi.newpipe.extractor.comments.CommentsInfo; import org.schabi.newpipe.extractor.comments.CommentsInfo;
import org.schabi.newpipe.extractor.comments.CommentsInfoItem; import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
import org.schabi.newpipe.extractor.kiosk.KioskExtractor; import org.schabi.newpipe.extractor.kiosk.KioskExtractor;
import org.schabi.newpipe.extractor.kiosk.KioskInfo; import org.schabi.newpipe.extractor.kiosk.KioskInfo;
import org.schabi.newpipe.extractor.kiosk.KioskList; import org.schabi.newpipe.extractor.kiosk.KioskList;
@ -58,49 +44,19 @@ import org.schabi.newpipe.extractor.stream.StreamType;
import org.springframework.security.crypto.argon2.Argon2PasswordEncoder; import org.springframework.security.crypto.argon2.Argon2PasswordEncoder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import com.fasterxml.jackson.core.JsonProcessingException; import javax.persistence.criteria.CriteriaBuilder;
import com.github.benmanes.caffeine.cache.Caffeine; import javax.persistence.criteria.CriteriaQuery;
import com.github.benmanes.caffeine.cache.LoadingCache; import javax.persistence.criteria.Root;
import com.rometools.rome.feed.synd.SyndEntry; import java.io.IOException;
import com.rometools.rome.feed.synd.SyndEntryImpl; import java.net.URISyntaxException;
import com.rometools.rome.feed.synd.SyndFeed; import java.nio.charset.StandardCharsets;
import com.rometools.rome.feed.synd.SyndFeedImpl; import java.util.*;
import com.rometools.rome.feed.synd.SyndPerson; import java.util.concurrent.CompletableFuture;
import com.rometools.rome.feed.synd.SyndPersonImpl; import java.util.concurrent.TimeUnit;
import com.rometools.rome.io.FeedException;
import com.rometools.rome.io.SyndFeedOutput;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import static me.kavin.piped.consts.Constants.YOUTUBE_SERVICE;
import it.unimi.dsi.fastutil.objects.ObjectArrayList; import static me.kavin.piped.utils.URLUtils.rewriteURL;
import me.kavin.piped.consts.Constants; import static me.kavin.piped.utils.URLUtils.substringYouTube;
import me.kavin.piped.ipfs.IPFS;
import me.kavin.piped.utils.obj.Channel;
import me.kavin.piped.utils.obj.ChapterSegment;
import me.kavin.piped.utils.obj.Comment;
import me.kavin.piped.utils.obj.CommentsPage;
import me.kavin.piped.utils.obj.FeedItem;
import me.kavin.piped.utils.obj.PipedStream;
import me.kavin.piped.utils.obj.Playlist;
import me.kavin.piped.utils.obj.SearchResults;
import me.kavin.piped.utils.obj.StreamItem;
import me.kavin.piped.utils.obj.Streams;
import me.kavin.piped.utils.obj.StreamsPage;
import me.kavin.piped.utils.obj.SubscriptionChannel;
import me.kavin.piped.utils.obj.Subtitle;
import me.kavin.piped.utils.obj.db.PubSub;
import me.kavin.piped.utils.obj.db.User;
import me.kavin.piped.utils.obj.db.Video;
import me.kavin.piped.utils.obj.search.SearchChannel;
import me.kavin.piped.utils.obj.search.SearchPlaylist;
import me.kavin.piped.utils.resp.AcceptedResponse;
import me.kavin.piped.utils.resp.AlreadyRegisteredResponse;
import me.kavin.piped.utils.resp.AuthenticationFailureResponse;
import me.kavin.piped.utils.resp.CompromisedPasswordResponse;
import me.kavin.piped.utils.resp.DisabledRegistrationResponse;
import me.kavin.piped.utils.resp.IncorrectCredentialsResponse;
import me.kavin.piped.utils.resp.InvalidRequestResponse;
import me.kavin.piped.utils.resp.LoginResponse;
import me.kavin.piped.utils.resp.SubscribeStatusResponse;
public class ResponseHelper { public class ResponseHelper {
@ -122,7 +78,7 @@ public class ResponseHelper {
CompletableFuture<String> futureLbryId = CompletableFuture.supplyAsync(() -> { CompletableFuture<String> futureLbryId = CompletableFuture.supplyAsync(() -> {
try { try {
return getLBRYId(videoId); return LbryHelper.getLBRYId(videoId);
} catch (Exception e) { } catch (Exception e) {
ExceptionHandler.handle(e); ExceptionHandler.handle(e);
} }
@ -135,7 +91,7 @@ public class ResponseHelper {
lbryId = futureLbryId.completeOnTimeout(null, 2, TimeUnit.SECONDS).get(); lbryId = futureLbryId.completeOnTimeout(null, 2, TimeUnit.SECONDS).get();
return getLBRYStreamURL(lbryId); return LbryHelper.getLBRYStreamURL(lbryId);
} catch (Exception e) { } catch (Exception e) {
ExceptionHandler.handle(e); ExceptionHandler.handle(e);
} }
@ -194,15 +150,10 @@ public class ResponseHelper {
info.getRelatedItems().forEach(o -> relatedStreams.add(collectRelatedStream(o))); info.getRelatedItems().forEach(o -> relatedStreams.add(collectRelatedStream(o)));
List<ChapterSegment> segments = new ObjectArrayList<>();
info.getStreamSegments().forEach(segment -> segments
.add(new ChapterSegment(segment.getTitle(), segment.getPreviewUrl(), segment.getStartTimeSeconds())));
long time = info.getUploadDate() != null ? info.getUploadDate().offsetDateTime().toInstant().toEpochMilli() long time = info.getUploadDate() != null ? info.getUploadDate().offsetDateTime().toInstant().toEpochMilli()
: System.currentTimeMillis(); : System.currentTimeMillis();
if (info.getUploadDate() != null && System.currentTimeMillis() - time < TimeUnit.DAYS.toMillis(10)) if (info.getUploadDate() != null && System.currentTimeMillis() - time < TimeUnit.DAYS.toMillis(Constants.FEED_RETENTION))
updateViews(info.getId(), info.getViewCount(), time, false); updateViews(info.getId(), info.getViewCount(), time, false);
final Streams streams = new Streams(info.getName(), info.getDescription().getContent(), final Streams streams = new Streams(info.getName(), info.getDescription().getContent(),
@ -243,7 +194,7 @@ public class ResponseHelper {
long time = item.getUploadDate() != null long time = item.getUploadDate() != null
? item.getUploadDate().offsetDateTime().toInstant().toEpochMilli() ? item.getUploadDate().offsetDateTime().toInstant().toEpochMilli()
: System.currentTimeMillis(); : System.currentTimeMillis();
if (System.currentTimeMillis() - time < TimeUnit.DAYS.toMillis(10)) if (System.currentTimeMillis() - time < TimeUnit.DAYS.toMillis(Constants.FEED_RETENTION))
updateViews(item.getUrl().substring("https://www.youtube.com/watch?v=".length()), updateViews(item.getUrl().substring("https://www.youtube.com/watch?v=".length()),
item.getViewCount(), time, true); item.getViewCount(), time, true);
} }
@ -269,7 +220,7 @@ public class ResponseHelper {
} }
public static final byte[] channelPageResponse(String channelId, String prevpageStr) public static final byte[] channelPageResponse(String channelId, String prevpageStr)
throws IOException, ExtractionException, InterruptedException { throws IOException, ExtractionException {
Page prevpage = Constants.mapper.readValue(prevpageStr, Page.class); Page prevpage = Constants.mapper.readValue(prevpageStr, Page.class);
@ -293,7 +244,7 @@ public class ResponseHelper {
} }
public static final byte[] trendingResponse(String region) public static final byte[] trendingResponse(String region)
throws ParsingException, ExtractionException, IOException { throws ExtractionException, IOException {
if (region == null) if (region == null)
return Constants.mapper.writeValueAsBytes(new InvalidRequestResponse()); return Constants.mapper.writeValueAsBytes(new InvalidRequestResponse());
@ -312,7 +263,7 @@ public class ResponseHelper {
} }
public static final byte[] playlistResponse(String playlistId) public static final byte[] playlistResponse(String playlistId)
throws IOException, ExtractionException, InterruptedException { throws IOException, ExtractionException {
final PlaylistInfo info = PlaylistInfo.getInfo("https://www.youtube.com/playlist?list=" + playlistId); final PlaylistInfo info = PlaylistInfo.getInfo("https://www.youtube.com/playlist?list=" + playlistId);
@ -337,7 +288,7 @@ public class ResponseHelper {
} }
public static final byte[] playlistPageResponse(String playlistId, String prevpageStr) public static final byte[] playlistPageResponse(String playlistId, String prevpageStr)
throws IOException, ExtractionException, InterruptedException { throws IOException, ExtractionException {
Page prevpage = Constants.mapper.readValue(prevpageStr, Page.class); Page prevpage = Constants.mapper.readValue(prevpageStr, Page.class);
@ -361,7 +312,7 @@ public class ResponseHelper {
} }
public static final byte[] playlistRSSResponse(String playlistId) public static final byte[] playlistRSSResponse(String playlistId)
throws IOException, ExtractionException, InterruptedException, FeedException { throws IOException, ExtractionException, FeedException {
final PlaylistInfo info = PlaylistInfo.getInfo("https://www.youtube.com/playlist?list=" + playlistId); final PlaylistInfo info = PlaylistInfo.getInfo("https://www.youtube.com/playlist?list=" + playlistId);
@ -392,22 +343,24 @@ public class ResponseHelper {
} }
public static final byte[] suggestionsResponse(String query) public static final byte[] suggestionsResponse(String query)
throws JsonProcessingException, IOException, ExtractionException { throws IOException, ExtractionException {
return Constants.mapper.writeValueAsBytes(YOUTUBE_SERVICE.getSuggestionExtractor().suggestionList(query)); return Constants.mapper.writeValueAsBytes(YOUTUBE_SERVICE.getSuggestionExtractor().suggestionList(query));
} }
public static final byte[] opensearchSuggestionsResponse(String query) public static final byte[] opensearchSuggestionsResponse(String query)
throws JsonProcessingException, IOException, ExtractionException { throws IOException, ExtractionException {
return Constants.mapper.writeValueAsBytes( return Constants.mapper.writeValueAsBytes(Arrays.asList(
Arrays.asList(query, YOUTUBE_SERVICE.getSuggestionExtractor().suggestionList(query))); query,
YOUTUBE_SERVICE.getSuggestionExtractor().suggestionList(query)
));
} }
public static final byte[] searchResponse(String q, String filter) public static final byte[] searchResponse(String q, String filter)
throws IOException, ExtractionException, InterruptedException { throws IOException, ExtractionException {
final SearchInfo info = SearchInfo.getInfo(YOUTUBE_SERVICE, final SearchInfo info = SearchInfo.getInfo(YOUTUBE_SERVICE,
YOUTUBE_SERVICE.getSearchQHFactory().fromQuery(q, Collections.singletonList(filter), null)); YOUTUBE_SERVICE.getSearchQHFactory().fromQuery(q, Collections.singletonList(filter), null));
@ -443,7 +396,7 @@ public class ResponseHelper {
} }
public static final byte[] searchPageResponse(String q, String filter, String prevpageStr) public static final byte[] searchPageResponse(String q, String filter, String prevpageStr)
throws IOException, ExtractionException, InterruptedException { throws IOException, ExtractionException {
Page prevpage = Constants.mapper.readValue(prevpageStr, Page.class); Page prevpage = Constants.mapper.readValue(prevpageStr, Page.class);
@ -552,8 +505,8 @@ public class ResponseHelper {
private static final Argon2PasswordEncoder argon2PasswordEncoder = new Argon2PasswordEncoder(); private static final Argon2PasswordEncoder argon2PasswordEncoder = new Argon2PasswordEncoder();
public static final byte[] registerResponse(String user, String pass) throws IOException, NoSuchAlgorithmException, public static final byte[] registerResponse(String user, String pass) throws IOException,
InvalidKeySpecException, InterruptedException, URISyntaxException { InterruptedException, URISyntaxException {
if (Constants.DISABLE_REGISTRATION) if (Constants.DISABLE_REGISTRATION)
return Constants.mapper.writeValueAsBytes(new DisabledRegistrationResponse()); return Constants.mapper.writeValueAsBytes(new DisabledRegistrationResponse());
@ -602,7 +555,7 @@ public class ResponseHelper {
private static final BCryptPasswordEncoder bcryptPasswordEncoder = new BCryptPasswordEncoder(); private static final BCryptPasswordEncoder bcryptPasswordEncoder = new BCryptPasswordEncoder();
public static final byte[] loginResponse(String user, String pass) public static final byte[] loginResponse(String user, String pass)
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException { throws IOException {
if (user == null || pass == null) if (user == null || pass == null)
return Constants.mapper.writeValueAsBytes(new InvalidRequestResponse()); return Constants.mapper.writeValueAsBytes(new InvalidRequestResponse());
@ -637,7 +590,7 @@ public class ResponseHelper {
} }
public static final byte[] subscribeResponse(String session, String channelId) public static final byte[] subscribeResponse(String session, String channelId)
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException { throws IOException {
Session s = DatabaseSessionFactory.createSession(); Session s = DatabaseSessionFactory.createSession();
@ -684,7 +637,7 @@ public class ResponseHelper {
long time = item.getUploadDate() != null long time = item.getUploadDate() != null
? item.getUploadDate().offsetDateTime().toInstant().toEpochMilli() ? item.getUploadDate().offsetDateTime().toInstant().toEpochMilli()
: System.currentTimeMillis(); : System.currentTimeMillis();
if ((System.currentTimeMillis() - time) < TimeUnit.DAYS.toMillis(10)) if ((System.currentTimeMillis() - time) < TimeUnit.DAYS.toMillis(Constants.FEED_RETENTION))
handleNewVideo(item.getUrl(), time, channel, sess); handleNewVideo(item.getUrl(), time, channel, sess);
} }
} }
@ -703,7 +656,7 @@ public class ResponseHelper {
} }
public static final byte[] unsubscribeResponse(String session, String channelId) public static final byte[] unsubscribeResponse(String session, String channelId)
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException { throws IOException {
Session s = DatabaseSessionFactory.createSession(); Session s = DatabaseSessionFactory.createSession();
@ -725,7 +678,7 @@ public class ResponseHelper {
} }
public static final byte[] isSubscribedResponse(String session, String channelId) public static final byte[] isSubscribedResponse(String session, String channelId)
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException { throws IOException {
Session s = DatabaseSessionFactory.createSession(); Session s = DatabaseSessionFactory.createSession();
@ -747,7 +700,7 @@ public class ResponseHelper {
} }
public static final byte[] feedResponse(String session) public static final byte[] feedResponse(String session)
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException { throws IOException {
Session s = DatabaseSessionFactory.createSession(); Session s = DatabaseSessionFactory.createSession();
@ -774,7 +727,7 @@ public class ResponseHelper {
}); });
Collections.sort(feedItems, (a, b) -> (int) (b.uploaded - a.uploaded)); feedItems.sort((a, b) -> (int) (b.uploaded - a.uploaded));
s.close(); s.close();
@ -788,7 +741,7 @@ public class ResponseHelper {
} }
public static final byte[] feedResponseRSS(String session) public static final byte[] feedResponseRSS(String session)
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, FeedException { throws IOException, FeedException {
Session s = DatabaseSessionFactory.createSession(); Session s = DatabaseSessionFactory.createSession();
@ -811,8 +764,7 @@ public class ResponseHelper {
.setParameter("user", user.getId()).addEntity("Video", Video.class) .setParameter("user", user.getId()).addEntity("Video", Video.class)
.addEntity("Channel", me.kavin.piped.utils.obj.db.Channel.class).getResultList(); .addEntity("Channel", me.kavin.piped.utils.obj.db.Channel.class).getResultList();
Collections.sort(queryResults, queryResults.sort((a, b) -> (int) (((Video) b[0]).getUploaded() - ((Video) a[0]).getUploaded()));
(a, b) -> (int) (((Video) b[0]).getUploaded() - ((Video) a[0]).getUploaded()));
final List<SyndEntry> entries = new ObjectArrayList<>(); final List<SyndEntry> entries = new ObjectArrayList<>();
@ -850,7 +802,7 @@ public class ResponseHelper {
} }
public static final byte[] importResponse(String session, String[] channelIds, boolean override) public static final byte[] importResponse(String session, String[] channelIds, boolean override)
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException { throws IOException {
Session s = DatabaseSessionFactory.createSession(); Session s = DatabaseSessionFactory.createSession();
@ -884,7 +836,7 @@ public class ResponseHelper {
me.kavin.piped.utils.obj.db.Channel channel = DatabaseHelper.getChannelFromId(sess, channelId); me.kavin.piped.utils.obj.db.Channel channel = DatabaseHelper.getChannelFromId(sess, channelId);
if (channel == null) { if (channel == null) {
ChannelInfo info = null; ChannelInfo info;
try { try {
info = ChannelInfo.getInfo("https://youtube.com/channel/" + channelId); info = ChannelInfo.getInfo("https://youtube.com/channel/" + channelId);
@ -910,7 +862,7 @@ public class ResponseHelper {
long time = item.getUploadDate() != null long time = item.getUploadDate() != null
? item.getUploadDate().offsetDateTime().toInstant().toEpochMilli() ? item.getUploadDate().offsetDateTime().toInstant().toEpochMilli()
: System.currentTimeMillis(); : System.currentTimeMillis();
if ((System.currentTimeMillis() - time) < TimeUnit.DAYS.toMillis(10)) if ((System.currentTimeMillis() - time) < TimeUnit.DAYS.toMillis(Constants.FEED_RETENTION))
handleNewVideo(item.getUrl(), time, channel, sess); handleNewVideo(item.getUrl(), time, channel, sess);
} }
@ -939,7 +891,7 @@ public class ResponseHelper {
} }
public static final byte[] subscriptionsResponse(String session) public static final byte[] subscriptionsResponse(String session)
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException { throws IOException {
Session s = DatabaseSessionFactory.createSession(); Session s = DatabaseSessionFactory.createSession();
@ -954,12 +906,10 @@ public class ResponseHelper {
List<me.kavin.piped.utils.obj.db.Channel> channels = DatabaseHelper.getChannelFromIds(s, List<me.kavin.piped.utils.obj.db.Channel> channels = DatabaseHelper.getChannelFromIds(s,
user.getSubscribed()); user.getSubscribed());
channels.forEach(channel -> { channels.forEach(channel -> subscriptionItems.add(new SubscriptionChannel("/channel/" + channel.getUploaderId(),
subscriptionItems.add(new SubscriptionChannel("/channel/" + channel.getUploaderId(), channel.getUploader(), rewriteURL(channel.getUploaderAvatar()), channel.isVerified())));
channel.getUploader(), rewriteURL(channel.getUploaderAvatar()), channel.isVerified()));
});
Collections.sort(subscriptionItems, (a, b) -> (a.name.compareTo(b.name))); subscriptionItems.sort(Comparator.comparing(o -> o.name));
} }
s.close(); s.close();
@ -977,37 +927,11 @@ public class ResponseHelper {
Session s = DatabaseSessionFactory.createSession(); Session s = DatabaseSessionFactory.createSession();
long registered = ((Long) s.createQuery("select count(*) from User").uniqueResult()).longValue(); long registered = (Long) s.createQuery("select count(*) from User").uniqueResult();
s.close(); s.close();
return String.format("https://img.shields.io/badge/Registered%%20Users-%s-blue", String.valueOf(registered)); return String.format("https://img.shields.io/badge/Registered%%20Users-%s-blue", registered);
}
private static final String getLBRYId(String videoId) throws IOException, InterruptedException {
return new JSONObject(Constants.h2client.send(HttpRequest
.newBuilder(URI.create("https://api.lbry.com/yt/resolve?video_ids=" + URLUtils.silentEncode(videoId)))
.setHeader("User-Agent", Constants.USER_AGENT).build(), BodyHandlers.ofString()).body())
.getJSONObject("data").getJSONObject("videos").optString(videoId);
}
private static final String getLBRYStreamURL(String lbryId)
throws IOException, InterruptedException, ExecutionException {
if (lbryId != null && !lbryId.isEmpty())
return new JSONObject(
Constants.h2client.send(
HttpRequest.newBuilder(URI.create("https://api.lbry.tv/api/v1/proxy?m=get"))
.POST(BodyPublishers.ofString(
String.valueOf(new JSONObject().put("id", System.currentTimeMillis())
.put("jsonrpc", "2.0").put("method", "get").put("params",
new JSONObject().put("uri", "lbry://" + lbryId)
.put("save_file", true)))))
.build(),
BodyHandlers.ofString()).body()).getJSONObject("result").getString("streaming_url");
return null;
} }
public static void handleNewVideo(String url, long time, me.kavin.piped.utils.obj.db.Channel channel, Session s) { public static void handleNewVideo(String url, long time, me.kavin.piped.utils.obj.db.Channel channel, Session s) {
@ -1018,8 +942,7 @@ public class ResponseHelper {
} }
} }
private static void handleNewVideo(StreamInfo info, long time, me.kavin.piped.utils.obj.db.Channel channel, private static void handleNewVideo(StreamInfo info, long time, me.kavin.piped.utils.obj.db.Channel channel, Session s) {
Session s) {
if (channel == null) if (channel == null)
channel = DatabaseHelper.getChannelFromId(s, channel = DatabaseHelper.getChannelFromId(s,
@ -1031,7 +954,7 @@ public class ResponseHelper {
Video video = null; Video video = null;
if (channel != null && (video = DatabaseHelper.getVideoFromId(s, info.getId())) == null if (channel != null && (video = DatabaseHelper.getVideoFromId(s, info.getId())) == null
&& (System.currentTimeMillis() - infoTime) < TimeUnit.DAYS.toMillis(10)) { && (System.currentTimeMillis() - infoTime) < TimeUnit.DAYS.toMillis(Constants.FEED_RETENTION)) {
video = new Video(info.getId(), info.getName(), info.getViewCount(), info.getDuration(), video = new Video(info.getId(), info.getName(), info.getViewCount(), info.getDuration(),
Math.max(infoTime, time), info.getThumbnailUrl(), channel); Math.max(infoTime, time), info.getThumbnailUrl(), channel);
@ -1084,29 +1007,22 @@ public class ResponseHelper {
String callback = Constants.PUBLIC_URL + "/webhooks/pubsub"; String callback = Constants.PUBLIC_URL + "/webhooks/pubsub";
String topic = "https://www.youtube.com/xml/feeds/videos.xml?channel_id=" + channelId; String topic = "https://www.youtube.com/xml/feeds/videos.xml?channel_id=" + channelId;
Builder builder = HttpRequest.newBuilder(URI.create("https://pubsubhubbub.appspot.com/subscribe")); var builder = new Request.Builder()
.url("https://pubsubhubbub.appspot.com/subscribe");
Map<String, String> formParams = new Object2ObjectOpenHashMap<>(); var formBuilder = new FormBody.Builder();
StringBuilder formBody = new StringBuilder();
builder.header("content-type", "application/x-www-form-urlencoded"); formBuilder.add("hub.callback", callback);
formBuilder.add("hub.topic", topic);
formBuilder.add("hub.verify", "async");
formBuilder.add("hub.mode", "subscribe");
formBuilder.add("hub.lease_seconds", "432000");
formParams.put("hub.callback", callback); var resp = Constants.h2client
formParams.put("hub.topic", topic); .newCall(builder.post(formBuilder.build())
formParams.put("hub.verify", "async"); .build()).execute();
formParams.put("hub.mode", "subscribe");
formParams.put("hub.lease_seconds", "432000");
formParams.forEach((name, value) -> { if (resp.code() == 202) {
formBody.append(name + "=" + URLUtils.silentEncode(value) + "&");
});
builder.method("POST",
BodyPublishers.ofString(String.valueOf(formBody.substring(0, formBody.length() - 1))));
HttpResponse<InputStream> resp = Constants.h2client.send(builder.build(), BodyHandlers.ofInputStream());
if (resp.statusCode() == 202) {
if (pubsub == null) if (pubsub == null)
pubsub = new PubSub(channelId, System.currentTimeMillis()); pubsub = new PubSub(channelId, System.currentTimeMillis());
else else
@ -1118,16 +1034,11 @@ public class ResponseHelper {
s.getTransaction().begin(); s.getTransaction().begin();
s.getTransaction().commit(); s.getTransaction().commit();
} else } else
System.out.println( System.out.println("Failed to subscribe: " + resp.code() + "\n" + resp.body().string());
"Failed to subscribe: " + resp.statusCode() + "\n" + IOUtils.toString(resp.body(), "UTF-8"));
} }
} }
private static final String substringYouTube(String s) {
return StringUtils.isEmpty(s) ? null : StringUtils.substringAfter(s, "youtube.com");
}
private static StreamItem collectRelatedStream(Object o) { private static StreamItem collectRelatedStream(Object o) {
StreamInfoItem item = (StreamInfoItem) o; StreamInfoItem item = (StreamInfoItem) o;
@ -1137,35 +1048,4 @@ public class ResponseHelper {
rewriteURL(item.getUploaderAvatarUrl()), item.getTextualUploadDate(), item.getDuration(), rewriteURL(item.getUploaderAvatarUrl()), item.getTextualUploadDate(), item.getDuration(),
item.getViewCount(), item.isUploaderVerified()); item.getViewCount(), item.isUploaderVerified());
} }
private static String rewriteURL(final String old) {
if (old == null || old.isEmpty())
return null;
URL url = null;
try {
url = new URL(old);
} catch (MalformedURLException e) {
ExceptionHandler.handle(e);
}
final String host = url.getHost();
String query = url.getQuery();
boolean hasQuery = query != null;
String path = url.getPath();
if (path.contains("=")) {
path = StringUtils.substringBefore(path, "=") + "="
+ StringUtils.substringAfter(path, "=").replace("-rj", "-rw");
}
return Constants.PROXY_PART + path + (hasQuery ? "?" + query + "&host=" : "?host=")
+ URLUtils.silentEncode(host);
}
} }

View file

@ -1,46 +1,36 @@
package me.kavin.piped.utils; package me.kavin.piped.utils;
import com.grack.nanojson.*;
import me.kavin.piped.consts.Constants;
import me.kavin.piped.utils.resp.InvalidRequestResponse;
import org.apache.commons.lang3.StringUtils;
import java.io.IOException; import java.io.IOException;
import java.net.URI;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse.BodyHandlers;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import org.apache.commons.lang3.StringUtils;
import com.grack.nanojson.JsonArray;
import com.grack.nanojson.JsonObject;
import com.grack.nanojson.JsonParser;
import com.grack.nanojson.JsonParserException;
import com.grack.nanojson.JsonWriter;
import me.kavin.piped.consts.Constants;
import me.kavin.piped.utils.resp.InvalidRequestResponse;
public class SponsorBlockUtils { public class SponsorBlockUtils {
public static final String getSponsors(String id, String categories) public static String getSponsors(String id, String categories)
throws IOException, InterruptedException, NoSuchAlgorithmException, JsonParserException { throws IOException, NoSuchAlgorithmException, JsonParserException {
if (StringUtils.isEmpty(categories)) if (StringUtils.isEmpty(categories))
return Constants.mapper.writeValueAsString(new InvalidRequestResponse()); return Constants.mapper.writeValueAsString(new InvalidRequestResponse());
String hash = toSha256(id); String hash = toSha256(id);
URI uri = URI.create("https://sponsor.ajay.app/api/skipSegments/" + URLUtils.silentEncode(hash.substring(0, 4))
+ "?categories=" + URLUtils.silentEncode(categories));
JsonArray jArray = JsonParser.array().from( JsonArray jArray = JsonParser.array().from(
Constants.h2client.send(HttpRequest.newBuilder(uri).build(), BodyHandlers.ofInputStream()).body()); RequestUtils.sendGet("https://sponsor.ajay.app/api/skipSegments/" + URLUtils.silentEncode(hash.substring(0, 4))
+ "?categories=" + URLUtils.silentEncode(categories))
);
jArray.removeIf(jObject -> !((JsonObject) jObject).getString("videoID").equalsIgnoreCase(id)); jArray.removeIf(jObject -> !((JsonObject) jObject).getString("videoID").equalsIgnoreCase(id));
return JsonWriter.string(jArray.getObject(0)); return JsonWriter.string(jArray.getObject(0));
} }
private static final String toSha256(final String videoId) throws NoSuchAlgorithmException { private static String toSha256(final String videoId) throws NoSuchAlgorithmException {
final MessageDigest digest = MessageDigest.getInstance("SHA-256"); final MessageDigest digest = MessageDigest.getInstance("SHA-256");
final byte[] bytes = digest.digest(videoId.getBytes(StandardCharsets.UTF_8)); final byte[] bytes = digest.digest(videoId.getBytes(StandardCharsets.UTF_8));
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();

View file

@ -1,13 +1,19 @@
package me.kavin.piped.utils; package me.kavin.piped.utils;
import me.kavin.piped.consts.Constants;
import org.apache.commons.lang3.StringUtils;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLDecoder; import java.net.URLDecoder;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
public class URLUtils { public class URLUtils {
public static String silentEncode(String s) { public static String silentEncode(String s) {
try { try {
return URLEncoder.encode(s, "UTF-8"); return URLEncoder.encode(s, StandardCharsets.UTF_8);
} catch (Exception e) { } catch (Exception e) {
// ignored // ignored
} }
@ -16,10 +22,42 @@ public class URLUtils {
public static String silentDecode(String s) { public static String silentDecode(String s) {
try { try {
return URLDecoder.decode(s, "UTF-8"); return URLDecoder.decode(s, StandardCharsets.UTF_8);
} catch (Exception e) { } catch (Exception e) {
// ignored // ignored
} }
return s; return s;
} }
public static String substringYouTube(String s) {
return StringUtils.isEmpty(s) ? null : StringUtils.substringAfter(s, "youtube.com");
}
public static String rewriteURL(final String old) {
if (StringUtils.isEmpty(old)) return null;
URL url = null;
try {
url = new URL(old);
} catch (MalformedURLException e) {
ExceptionHandler.handle(e);
}
assert url != null;
final String host = url.getHost();
String query = url.getQuery();
boolean hasQuery = query != null;
String path = url.getPath();
if (path.contains("=")) {
path = StringUtils.substringBefore(path, "=") + "=" + StringUtils.substringAfter(path, "=").replace("-rj", "-rw");
}
return Constants.PROXY_PART + path + (hasQuery ? "?" + query + "&host=" : "?host=") + silentEncode(host);
}
} }

View file

@ -7,7 +7,7 @@ import javax.persistence.Index;
import javax.persistence.Table; import javax.persistence.Table;
@Entity @Entity
@Table(name = "channels", indexes = { @Index(columnList = "uploader_id", name = "channels_uploader_id_idx") }) @Table(name = "channels", indexes = {@Index(columnList = "uploader_id", name = "channels_uploader_id_idx")})
public class Channel { public class Channel {
@Id @Id

View file

@ -1,13 +1,6 @@
package me.kavin.piped.utils.obj.db; package me.kavin.piped.utils.obj.db;
import javax.persistence.Column; import javax.persistence.*;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
@Entity @Entity
@Table(name = "videos", indexes = { @Index(columnList = "id", name = "videos_id_idx"), @Table(name = "videos", indexes = { @Index(columnList = "id", name = "videos_id_idx"),