Commit changes.

This commit is contained in:
FireMasterK 2021-02-24 15:22:29 +05:30
parent 947d6d5787
commit dd2ee203bb
No known key found for this signature in database
GPG Key ID: 8DFF5DD33E93DB58
22 changed files with 650 additions and 531 deletions

View File

@ -19,7 +19,7 @@ dependencies {
implementation 'org.bouncycastle:bcprov-jdk15on:1.67' implementation 'org.bouncycastle:bcprov-jdk15on:1.67'
implementation 'org.mongodb:mongodb-driver-sync:4.2.1' implementation 'org.mongodb:mongodb-driver-sync:4.2.1'
implementation 'io.projectreactor.netty:reactor-netty:1.0.4' implementation 'io.projectreactor.netty:reactor-netty:1.0.4'
implementation 'com.github.TeamNewPipe:NewPipeExtractor:v0.20.8' implementation 'com.github.TeamNewPipe:NewPipeExtractor:SNAPSHOT'
implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751' implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751'
implementation 'com.fasterxml.jackson.core:jackson-core:2.12.1' implementation 'com.fasterxml.jackson.core:jackson-core:2.12.1'
implementation 'com.fasterxml.jackson.core:jackson-annotations:2.12.1' implementation 'com.fasterxml.jackson.core:jackson-annotations:2.12.1'
@ -27,6 +27,8 @@ dependencies {
implementation 'org.json:json:20201115' implementation 'org.json:json:20201115'
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 'net.java.dev.jna:jna-platform:5.7.0'
} }
shadowJar { shadowJar {

View File

@ -1,6 +1,13 @@
package me.kavin.piped; package me.kavin.piped;
import io.netty.util.AsciiString; import static io.netty.handler.codec.http.HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN;
import static io.netty.handler.codec.http.HttpHeaderNames.CACHE_CONTROL;
import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE;
import static io.netty.handler.codec.http.HttpHeaderValues.APPLICATION_JSON;
import static io.netty.handler.codec.http.HttpHeaderValues.TEXT_PLAIN;
import java.nio.charset.StandardCharsets;
import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.commons.lang3.exception.ExceptionUtils;
import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.localization.Localization; import org.schabi.newpipe.extractor.localization.Localization;
@ -11,6 +18,7 @@ import com.rometools.rome.io.XmlReader;
import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.ByteBufAllocator;
import io.netty.handler.codec.http.QueryStringDecoder; import io.netty.handler.codec.http.QueryStringDecoder;
import io.netty.util.AsciiString;
import me.kavin.piped.consts.Constants; import me.kavin.piped.consts.Constants;
import me.kavin.piped.utils.DownloaderImpl; import me.kavin.piped.utils.DownloaderImpl;
import me.kavin.piped.utils.ResponseHelper; import me.kavin.piped.utils.ResponseHelper;
@ -22,231 +30,233 @@ import reactor.netty.NettyOutbound;
import reactor.netty.http.server.HttpServer; import reactor.netty.http.server.HttpServer;
import reactor.netty.http.server.HttpServerResponse; import reactor.netty.http.server.HttpServerResponse;
import java.nio.charset.StandardCharsets;
import static io.netty.handler.codec.http.HttpHeaderNames.*;
import static io.netty.handler.codec.http.HttpHeaderValues.*;
public class Main { public class Main {
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
NewPipe.init(new DownloaderImpl(), new Localization("en", "US")); NewPipe.init(new DownloaderImpl(), new Localization("en", "US"));
DisposableServer server = HttpServer.create().port(Constants.PORT).route(routes -> { DisposableServer server = HttpServer.create().port(Constants.PORT).route(routes -> {
routes.get("/webhooks/pubsub", (req, res) -> { routes.get("/webhooks/pubsub", (req, res) -> {
long start = System.nanoTime(); long start = System.nanoTime();
QueryStringDecoder query = new QueryStringDecoder(req.uri()); QueryStringDecoder query = new QueryStringDecoder(req.uri());
try { try {
return writeResponse(res, query.parameters().get("hub.challenge").get(0), TEXT_PLAIN, 200, "private", start); return writeResponse(res, query.parameters().get("hub.challenge").get(0), TEXT_PLAIN, 200,
"private", start);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
return writeResponse(res, ExceptionUtils.getStackTrace(e), 500, "private", start); return writeResponse(res, ExceptionUtils.getStackTrace(e), 500, "private", start);
} }
}); });
routes.post("/webhooks/pubsub", (req, res) -> { routes.post("/webhooks/pubsub", (req, res) -> {
long start = System.nanoTime(); long start = System.nanoTime();
try { try {
req.receive().asInputStream().subscribe(in -> { req.receive().asInputStream().subscribe(in -> {
try { try {
SyndFeed feed = new SyndFeedInput().build(new XmlReader(in)); SyndFeed feed = new SyndFeedInput().build(new XmlReader(in));
feed.getEntries().forEach(entry -> { feed.getEntries().forEach(entry -> {
System.out.println(entry.getLinks().get(0).getHref()); System.out.println(entry.getLinks().get(0).getHref());
System.out.println(entry.getAuthors().get(0).getUri()); System.out.println(entry.getAuthors().get(0).getUri());
}); });
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
}); });
return writeResponse(res, "ok", TEXT_PLAIN, 200, "private", start); return writeResponse(res, "ok", TEXT_PLAIN, 200, "private", start);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
return writeResponse(res, ExceptionUtils.getStackTrace(e), 500, "private", start); return writeResponse(res, ExceptionUtils.getStackTrace(e), 500, "private", start);
} }
}); });
routes.get("/sponsors/{videoId}", (req, res) -> { routes.get("/sponsors/{videoId}", (req, res) -> {
long start = System.nanoTime(); long start = System.nanoTime();
QueryStringDecoder query = new QueryStringDecoder(req.uri()); QueryStringDecoder query = new QueryStringDecoder(req.uri());
try { try {
return writeResponse(res, SponsorBlockUtils.getSponsors(req.param("videoId"), return writeResponse(res, SponsorBlockUtils.getSponsors(req.param("videoId"),
query.parameters().get("category").get(0)), 200, "public, max-age=3600", start); query.parameters().get("category").get(0)), 200, "public, max-age=3600", start);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
return writeResponse(res, ExceptionUtils.getStackTrace(e), 500, "private", start); return writeResponse(res, ExceptionUtils.getStackTrace(e), 500, "private", start);
} }
}); });
routes.get("/streams/{videoId}", (req, res) -> { routes.get("/streams/{videoId}", (req, res) -> {
long start = System.nanoTime(); long start = System.nanoTime();
try { try {
// The stream links are valid for 6 hours. // The stream links are valid for 6 hours.
return writeResponse(res, ResponseHelper.streamsResponse(req.param("videoId")), 200, return writeResponse(res, ResponseHelper.streamsResponse(req.param("videoId")), 200,
"public, s-maxage=21540", start); "public, s-maxage=21540", start);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
return writeResponse(res, ExceptionUtils.getStackTrace(e), 500, "private", start); return writeResponse(res, ExceptionUtils.getStackTrace(e), 500, "private", start);
} }
}); });
routes.get("/channels/{channelId}", (req, res) -> { routes.get("/channels/{channelId}", (req, res) -> {
long start = System.nanoTime(); long start = System.nanoTime();
try { try {
return writeResponse(res, ResponseHelper.channelResponse(req.param("channelId")), 200, return writeResponse(res, ResponseHelper.channelResponse(req.param("channelId")), 200,
"public, max-age=600", start); "public, max-age=600", start);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
return writeResponse(res, ExceptionUtils.getStackTrace(e), 500, "private", start); return writeResponse(res, ExceptionUtils.getStackTrace(e), 500, "private", start);
} }
}); });
routes.get("/nextpage/channels/{channelId}", (req, res) -> { routes.get("/nextpage/channels/{channelId}", (req, res) -> {
long start = System.nanoTime(); long start = System.nanoTime();
QueryStringDecoder query = new QueryStringDecoder(req.uri()); QueryStringDecoder query = new QueryStringDecoder(req.uri());
try { try {
return writeResponse(res, ResponseHelper.channelPageResponse(req.param("channelId"), return writeResponse(res, ResponseHelper.channelPageResponse(req.param("channelId"),
query.parameters().get("url").get(0)), 200, "public, max-age=3600", start); query.parameters().get("url").get(0)), 200, "public, max-age=3600", start);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
return writeResponse(res, ExceptionUtils.getStackTrace(e), 500, "private", start); return writeResponse(res, ExceptionUtils.getStackTrace(e), 500, "private", start);
} }
}); });
routes.get("/playlists/{playlistId}", (req, res) -> { routes.get("/playlists/{playlistId}", (req, res) -> {
long start = System.nanoTime(); long start = System.nanoTime();
try { try {
return writeResponse(res, ResponseHelper.playlistResponse(req.param("playlistId")), 200, return writeResponse(res, ResponseHelper.playlistResponse(req.param("playlistId")), 200,
"public, max-age=600", start); "public, max-age=600", start);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
return writeResponse(res, ExceptionUtils.getStackTrace(e), 500, "private", start); return writeResponse(res, ExceptionUtils.getStackTrace(e), 500, "private", start);
} }
}); });
routes.get("/nextpage/playlists/{playlistId}", (req, res) -> { routes.get("/nextpage/playlists/{playlistId}", (req, res) -> {
long start = System.nanoTime(); long start = System.nanoTime();
QueryStringDecoder query = new QueryStringDecoder(req.uri()); QueryStringDecoder query = new QueryStringDecoder(req.uri());
try { try {
return writeResponse(res, ResponseHelper.playlistPageResponse(req.param("playlistId"), return writeResponse(res, ResponseHelper.playlistPageResponse(req.param("playlistId"),
query.parameters().get("url").get(0)), 200, "public, max-age=3600", start); query.parameters().get("url").get(0)), 200, "public, max-age=3600", start);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
return writeResponse(res, ExceptionUtils.getStackTrace(e), 500, "private", start); return writeResponse(res, ExceptionUtils.getStackTrace(e), 500, "private", start);
} }
}); });
routes.get("/suggestions", (req, res) -> { routes.get("/suggestions", (req, res) -> {
long start = System.nanoTime(); long start = System.nanoTime();
QueryStringDecoder query = new QueryStringDecoder(req.uri()); QueryStringDecoder query = new QueryStringDecoder(req.uri());
try { try {
return writeResponse(res, return writeResponse(res,
ResponseHelper.suggestionsResponse(query.parameters().get("query").get(0)), 200, ResponseHelper.suggestionsResponse(query.parameters().get("query").get(0)), 200,
"public, max-age=600", start); "public, max-age=600", start);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
return writeResponse(res, ExceptionUtils.getStackTrace(e), 500, "private", start); return writeResponse(res, ExceptionUtils.getStackTrace(e), 500, "private", start);
} }
}); });
routes.get("/search", (req, res) -> { routes.get("/search", (req, res) -> {
long start = System.nanoTime(); long start = System.nanoTime();
QueryStringDecoder query = new QueryStringDecoder(req.uri()); QueryStringDecoder query = new QueryStringDecoder(req.uri());
try { try {
return writeResponse(res, ResponseHelper.searchResponse(query.parameters().get("q").get(0)), 200, return writeResponse(res, ResponseHelper.searchResponse(query.parameters().get("q").get(0)), 200,
"public, max-age=600", start); "public, max-age=600", start);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
return writeResponse(res, ExceptionUtils.getStackTrace(e), 500, "private", start); return writeResponse(res, ExceptionUtils.getStackTrace(e), 500, "private", start);
} }
}); });
routes.get("/nextpage/search", (req, res) -> { routes.get("/nextpage/search", (req, res) -> {
long start = System.nanoTime(); long start = System.nanoTime();
QueryStringDecoder query = new QueryStringDecoder(req.uri()); QueryStringDecoder query = new QueryStringDecoder(req.uri());
try { try {
return writeResponse(res, return writeResponse(res,
ResponseHelper.searchPageResponse(query.parameters().get("q").get(0), ResponseHelper.searchPageResponse(query.parameters().get("q").get(0),
query.parameters().get("url").get(0), query.parameters().get("id").get(0)), query.parameters().get("url").get(0), query.parameters().get("id").get(0)),
200, "public, max-age=3600", start); 200, "public, max-age=3600", start);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
return writeResponse(res, ExceptionUtils.getStackTrace(e), 500, "private", start); return writeResponse(res, ExceptionUtils.getStackTrace(e), 500, "private", start);
} }
}); });
routes.get("/trending", (req, res) -> { routes.get("/trending", (req, res) -> {
long start = System.nanoTime(); long start = System.nanoTime();
try { try {
return writeResponse(res, ResponseHelper.trendingResponse(), 200, "public, max-age=3600", start); return writeResponse(res, ResponseHelper.trendingResponse(), 200, "public, max-age=3600", start);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
return writeResponse(res, ExceptionUtils.getStackTrace(e), 500, "private", start); return writeResponse(res, ExceptionUtils.getStackTrace(e), 500, "private", start);
} }
}); });
}).compress(true).bindNow(); }).compress(true).bindNow();
server.onDispose().block(); server.onDispose().block();
} }
public static NettyOutbound writeResponse(HttpServerResponse res, String resp, int code, String cache, long time) { }).bindNow();
return writeResponse(res, resp, APPLICATION_JSON, code, cache, time);
}
public static NettyOutbound writeResponse(HttpServerResponse res, String resp, AsciiString mimeType, int code, String cache, long time) { Thread.sleep(Long.MAX_VALUE);
return writeResponse(res, resp, mimeType.toString(), code, cache, time);
}
public static NettyOutbound writeResponse(HttpServerResponse res, String resp, String mimeType, int code, String cache, long time) {
return writeResponse(res, resp.getBytes(StandardCharsets.UTF_8), mimeType, code, cache, time);
}
public static NettyOutbound writeResponse(HttpServerResponse res, byte[] resp, int code, String cache, long time) {
return writeResponse(res, resp, APPLICATION_JSON, code, cache, time);
} }
public static NettyOutbound writeResponse(HttpServerResponse res, byte[] resp, AsciiString mimeType, int code, String cache, long time) { public static NettyOutbound writeResponse(HttpServerResponse res, String resp, int code, String cache, long time) {
return writeResponse(res, resp, mimeType.toString(), code, cache, time); return writeResponse(res, resp, APPLICATION_JSON, code, cache, time);
} }
public static NettyOutbound writeResponse(HttpServerResponse res, String resp, AsciiString mimeType, int code,
String cache, long time) {
return writeResponse(res, resp, mimeType.toString(), code, cache, time);
}
public static NettyOutbound writeResponse(HttpServerResponse res, String resp, String mimeType, int code,
String cache, long time) {
return writeResponse(res, resp.getBytes(StandardCharsets.UTF_8), mimeType, code, cache, time);
}
public static NettyOutbound writeResponse(HttpServerResponse res, byte[] resp, int code, String cache, long time) {
return writeResponse(res, resp, APPLICATION_JSON, code, cache, time);
}
public static NettyOutbound writeResponse(HttpServerResponse res, byte[] resp, AsciiString mimeType, int code,
String cache, long time) {
return writeResponse(res, resp, mimeType.toString(), code, cache, time);
}
public static NettyOutbound writeResponse(HttpServerResponse res, byte[] resp, String mimeType, int code, String cache, long time) { public static NettyOutbound writeResponse(HttpServerResponse res, byte[] resp, String mimeType, int code, String cache, long time) {
return res return res
@ -258,15 +268,14 @@ public class Main {
.sendByteArray(Flux.just(resp)); .sendByteArray(Flux.just(resp));
} }
public static NettyOutbound writeResponse(HttpServerResponse res, Flux<String> resp, int code, String cache) { public static NettyOutbound writeResponse(HttpServerResponse res, Flux<String> resp, int code, String cache) {
return writeResponse(res, resp, APPLICATION_JSON, code, cache); return writeResponse(res, resp, APPLICATION_JSON, code, cache);
} }
public static NettyOutbound writeResponse(HttpServerResponse res, Flux<String> resp, AsciiString mimeType, int code, String cache) { public static NettyOutbound writeResponse(HttpServerResponse res, Flux<String> resp, AsciiString mimeType, int code,
return writeResponse(res, resp, mimeType.toString(), code, cache); String cache) {
} return writeResponse(res, resp, mimeType.toString(), code, cache);
}
public static NettyOutbound writeResponse(HttpServerResponse res, Flux<String> resp, String mimeType, int code, String cache) { public static NettyOutbound writeResponse(HttpServerResponse res, Flux<String> resp, String mimeType, int code, String cache) {
return res return res

View File

@ -10,6 +10,8 @@ import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.extractor.StreamingService;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
public class Constants { public class Constants {
@ -24,21 +26,24 @@ public class Constants {
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 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 ObjectMapper mapper = new ObjectMapper(); public static final ObjectMapper mapper = new ObjectMapper();
static { static {
Properties prop = new Properties(); Properties prop = new Properties();
try { try {
YOUTUBE_SERVICE = NewPipe.getService(0); YOUTUBE_SERVICE = NewPipe.getService(0);
prop.load(new FileReader("config.properties")); prop.load(new FileReader("config.properties"));
PORT = Integer.parseInt(prop.getProperty("PORT")); PORT = Integer.parseInt(prop.getProperty("PORT"));
PROXY_PART = prop.getProperty("PROXY_PART"); PROXY_PART = prop.getProperty("PROXY_PART");
} catch (Exception e) { mongoClient = MongoClients.create(prop.getProperty("MONGO_URI"));
throw new RuntimeException(e); } catch (Exception e) {
} throw new RuntimeException(e);
}
} }
} }

View File

@ -0,0 +1,20 @@
package me.kavin.piped.ipfs;
import me.kavin.piped.utils.Multithreading;
import me.kavin.piped.utils.obj.Channel;
public class IPFS {
// private static final io.ipfs.api.IPFS ipfs = new io.ipfs.api.IPFS(new MultiAddress("/ip4/127.0.0.1/tcp/5001"));
public static void publishData(final Channel channel) {
Multithreading.runAsync(() -> {
try {
// ipfs.pubsub.pub(URLUtils.silentEncode(channel.id),
// URLUtils.silentEncode(Constants.mapper.writeValueAsString(channel)));
} catch (Exception e) {
e.printStackTrace();
}
});
}
}

View File

@ -9,13 +9,13 @@ public class CookieHandler extends java.net.CookieHandler {
@Override @Override
public Map<String, List<String>> get(URI uri, Map<String, List<String>> requestHeaders) throws IOException { public Map<String, List<String>> get(URI uri, Map<String, List<String>> requestHeaders) throws IOException {
// TODO Auto-generated method stub // TODO Auto-generated method stub
return null; return null;
} }
@Override @Override
public void put(URI uri, Map<String, List<String>> responseHeaders) throws IOException { public void put(URI uri, Map<String, List<String>> responseHeaders) throws IOException {
// TODO Auto-generated method stub // TODO Auto-generated method stub
} }
} }

View File

@ -23,32 +23,32 @@ public class DownloaderImpl extends Downloader {
@Override @Override
public Response execute(Request request) throws IOException, ReCaptchaException { public Response execute(Request request) throws IOException, ReCaptchaException {
// TODO: HTTP/3 aka QUIC // TODO: HTTP/3 aka QUIC
Builder builder = HttpRequest.newBuilder(URI.create(request.url())); Builder builder = HttpRequest.newBuilder(URI.create(request.url()));
byte[] data = request.dataToSend(); byte[] data = request.dataToSend();
BodyPublisher publisher = data == null ? HttpRequest.BodyPublishers.noBody() BodyPublisher publisher = data == null ? HttpRequest.BodyPublishers.noBody()
: 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))); 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);
HttpResponse<String> response = null; HttpResponse<String> response = null;
try { try {
response = Constants.h2client.send(builder.build(), BodyHandlers.ofString()); response = Constants.h2client.send(builder.build(), BodyHandlers.ofString());
} catch (InterruptedException e) { } catch (InterruptedException e) {
// ignored // ignored
} }
// TODO: Implement solver // TODO: Implement solver
if (response.statusCode() == 429) { if (response.statusCode() == 429) {
throw new ReCaptchaException("reCaptcha Challenge requested", String.valueOf(response.uri())); throw new ReCaptchaException("reCaptcha Challenge requested", String.valueOf(response.uri()));
} }
return new Response(response.statusCode(), "UNDEFINED", response.headers().map(), response.body(), return new Response(response.statusCode(), "UNDEFINED", response.headers().map(), response.body(),
String.valueOf(response.uri())); String.valueOf(response.uri()));
} }
} }

View File

@ -0,0 +1,19 @@
package me.kavin.piped.utils;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Multithreading {
private static final ExecutorService es = Executors.newFixedThreadPool(16);
public static void runAsync(final Runnable runnable) {
es.submit(runnable);
}
public static void runAsyncTimeout(final Runnable runnable) {
es.submit(runnable);
}
}

View File

@ -0,0 +1,7 @@
package me.kavin.piped.utils;
public class RegisterRequest {
public String username, password;
}

View File

@ -10,7 +10,6 @@ import java.net.http.HttpResponse.BodyHandlers;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
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;
@ -34,7 +33,9 @@ import com.github.benmanes.caffeine.cache.LoadingCache;
import it.unimi.dsi.fastutil.objects.ObjectArrayList; import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import me.kavin.piped.consts.Constants; import me.kavin.piped.consts.Constants;
import me.kavin.piped.ipfs.IPFS;
import me.kavin.piped.utils.obj.Channel; import me.kavin.piped.utils.obj.Channel;
import me.kavin.piped.utils.obj.ChapterSegment;
import me.kavin.piped.utils.obj.PipedStream; import me.kavin.piped.utils.obj.PipedStream;
import me.kavin.piped.utils.obj.Playlist; import me.kavin.piped.utils.obj.Playlist;
import me.kavin.piped.utils.obj.SearchResults; import me.kavin.piped.utils.obj.SearchResults;
@ -48,353 +49,361 @@ import me.kavin.piped.utils.obj.search.SearchStream;
public class ResponseHelper { public class ResponseHelper {
public static final LoadingCache<String, CommentsInfo> commentsCache = Caffeine.newBuilder() public static final LoadingCache<String, CommentsInfo> commentsCache = Caffeine.newBuilder()
.expireAfterWrite(1, TimeUnit.HOURS).maximumSize(1000) .expireAfterWrite(1, TimeUnit.HOURS).maximumSize(1000)
.build(key -> CommentsInfo.getInfo("https://www.youtube.com/watch?v=" + key)); .build(key -> CommentsInfo.getInfo("https://www.youtube.com/watch?v=" + key));
public static final byte[] streamsResponse(String videoId) throws Exception { public static final byte[] streamsResponse(String videoId) throws Exception {
CompletableFuture<StreamInfo> futureStream = CompletableFuture.supplyAsync(() -> { CompletableFuture<StreamInfo> futureStream = CompletableFuture.supplyAsync(() -> {
try { try {
return StreamInfo.getInfo("https://www.youtube.com/watch?v=" + videoId); return StreamInfo.getInfo("https://www.youtube.com/watch?v=" + videoId);
} catch (Exception e) { } catch (Exception e) {
ExceptionUtils.rethrow(e); ExceptionUtils.rethrow(e);
} }
return null; return null;
}); });
CompletableFuture<String> futureLBRY = CompletableFuture.supplyAsync(() -> { CompletableFuture<String> futureLBRY = CompletableFuture.supplyAsync(() -> {
try { try {
return getLBRYStreamURL(videoId); return getLBRYStreamURL(videoId);
} catch (Exception e) { } catch (Exception e) {
ExceptionUtils.rethrow(e); ExceptionUtils.rethrow(e);
} }
return null; return null;
}); });
final List<Subtitle> subtitles = new ObjectArrayList<>(); final List<Subtitle> subtitles = new ObjectArrayList<>();
final StreamInfo info = futureStream.get(); final StreamInfo info = futureStream.get();
info.getSubtitles().forEach(subtitle -> subtitles // System.out.println(Constants.mapper.writeValueAsString(info.getStreamSegments()));
.add(new Subtitle(rewriteURL(subtitle.getUrl()), subtitle.getFormat().getMimeType()))); info.getSubtitles().forEach(subtitle -> subtitles
.add(new Subtitle(rewriteURL(subtitle.getUrl()), subtitle.getFormat().getMimeType())));
final List<PipedStream> videoStreams = new ObjectArrayList<>(); final List<PipedStream> videoStreams = new ObjectArrayList<>();
final List<PipedStream> audioStreams = new ObjectArrayList<>(); final List<PipedStream> audioStreams = new ObjectArrayList<>();
final String lbryURL = futureLBRY.get(); final String lbryURL = futureLBRY.get();
if (lbryURL != null) if (lbryURL != null)
videoStreams.add(new PipedStream(lbryURL, "MP4", "LBRY", "video/mp4", false)); videoStreams.add(new PipedStream(lbryURL, "MP4", "LBRY", "video/mp4", false));
final String hls; final String hls;
boolean livestream = false; boolean livestream = false;
if ((hls = info.getHlsUrl()) != null && !hls.isEmpty()) if ((hls = info.getHlsUrl()) != null && !hls.isEmpty())
livestream = true; livestream = true;
if (hls != null) { if (hls != null) {
java.util.stream.Stream<String> resp = Constants.h2client java.util.stream.Stream<String> resp = Constants.h2client
.send(HttpRequest.newBuilder(URI.create(hls)).GET().build(), BodyHandlers.ofLines()).body(); .send(HttpRequest.newBuilder(URI.create(hls)).GET().build(), BodyHandlers.ofLines()).body();
ObjectArrayList<String> lines = new ObjectArrayList<>(); ObjectArrayList<String> lines = new ObjectArrayList<>();
resp.forEach(line -> lines.add(line)); resp.forEach(line -> lines.add(line));
for (int i = lines.size() - 1; i > 2; i--) { for (int i = lines.size() - 1; i > 2; i--) {
String line = lines.get(i); String line = lines.get(i);
if (line.startsWith("https://manifest.googlevideo.com")) { if (line.startsWith("https://manifest.googlevideo.com")) {
String prevLine = lines.get(i - 1); String prevLine = lines.get(i - 1);
String height = StringUtils.substringBetween(prevLine, "RESOLUTION=", ",").split("x")[1]; String height = StringUtils.substringBetween(prevLine, "RESOLUTION=", ",").split("x")[1];
int fps = Integer.parseInt(StringUtils.substringBetween(prevLine, "FRAME-RATE=", ",")); int fps = Integer.parseInt(StringUtils.substringBetween(prevLine, "FRAME-RATE=", ","));
String quality = height + "p"; String quality = height + "p";
if (fps > 30) if (fps > 30)
quality += fps; quality += fps;
videoStreams.add(new PipedStream(line, "HLS", quality, "application/x-mpegURL", false)); videoStreams.add(new PipedStream(line, "HLS", quality, "application/x-mpegURL", false));
} }
} }
} }
if (!livestream) { if (!livestream) {
info.getVideoOnlyStreams() info.getVideoOnlyStreams().forEach(stream -> videoStreams.add(new PipedStream(rewriteURL(stream.getUrl()),
.forEach(stream -> videoStreams String.valueOf(stream.getFormat()), stream.getResolution(), stream.getFormat().getMimeType(), true,
.add(new PipedStream(rewriteURL(stream.getUrl()), String.valueOf(stream.getFormat()), stream.getBitrate(), stream.getInitStart(), stream.getInitEnd(), stream.getIndexStart(),
stream.getResolution(), stream.getFormat().getMimeType(), true))); stream.getIndexEnd(), stream.getCodec(), stream.getWidth(), stream.getHeight(), 30)));
info.getVideoStreams() info.getVideoStreams()
.forEach(stream -> videoStreams .forEach(stream -> videoStreams
.add(new PipedStream(rewriteURL(stream.getUrl()), String.valueOf(stream.getFormat()), .add(new PipedStream(rewriteURL(stream.getUrl()), String.valueOf(stream.getFormat()),
stream.getResolution(), stream.getFormat().getMimeType(), false))); stream.getResolution(), stream.getFormat().getMimeType(), false)));
info.getAudioStreams() info.getAudioStreams()
.forEach(stream -> audioStreams .forEach(stream -> audioStreams.add(new PipedStream(rewriteURL(stream.getUrl()),
.add(new PipedStream(rewriteURL(stream.getUrl()), String.valueOf(stream.getFormat()), String.valueOf(stream.getFormat()), stream.getAverageBitrate() + " kbps",
stream.getAverageBitrate() + " kbps", stream.getFormat().getMimeType(), false))); stream.getFormat().getMimeType(), false, stream.getBitrate(), stream.getInitStart(),
} stream.getInitEnd(), stream.getIndexStart(), stream.getIndexEnd(), stream.getCodec())));
}
final List<StreamItem> relatedStreams = new ObjectArrayList<>(); final List<StreamItem> relatedStreams = new ObjectArrayList<>();
info.getRelatedStreams().forEach(o -> { info.getRelatedStreams().forEach(o -> {
StreamInfoItem item = (StreamInfoItem) o; StreamInfoItem item = (StreamInfoItem) o;
relatedStreams.add(new StreamItem(item.getUrl().substring(23), item.getName(), relatedStreams.add(new StreamItem(item.getUrl().substring(23), item.getName(),
rewriteURL(item.getThumbnailUrl()), item.getUploaderName(), item.getUploaderUrl().substring(23), rewriteURL(item.getThumbnailUrl()), item.getUploaderName(), item.getUploaderUrl().substring(23),
item.getTextualUploadDate(), item.getDuration(), item.getViewCount())); item.getTextualUploadDate(), item.getDuration(), item.getViewCount()));
}); });
final Streams streams = new Streams(info.getName(), info.getDescription().getContent(), List<ChapterSegment> segments = new ObjectArrayList<>();
info.getTextualUploadDate(), info.getUploaderName(), info.getUploaderUrl().substring(23),
rewriteURL(info.getUploaderAvatarUrl()), rewriteURL(info.getThumbnailUrl()), info.getDuration(),
info.getViewCount(), info.getLikeCount(), info.getDislikeCount(), audioStreams, videoStreams,
relatedStreams, subtitles, livestream, hls);
return Constants.mapper.writeValueAsBytes(streams); info.getStreamSegments().forEach(
segment -> segments.add(new ChapterSegment(segment.getTitle(), segment.getStartTimeSeconds())));
final Streams streams = new Streams(info.getName(), info.getDescription().getContent(),
info.getTextualUploadDate(), info.getUploaderName(), info.getUploaderUrl().substring(23),
rewriteURL(info.getUploaderAvatarUrl()), rewriteURL(info.getThumbnailUrl()), info.getDuration(),
info.getViewCount(), info.getLikeCount(), info.getDislikeCount(), audioStreams, videoStreams,
relatedStreams, subtitles, livestream, hls);
return Constants.mapper.writeValueAsBytes(streams);
} }
public static final byte[] channelResponse(String channelId) public static final byte[] channelResponse(String channelId) throws Exception {
throws IOException, ExtractionException, InterruptedException {
final ChannelInfo info = ChannelInfo.getInfo("https://youtube.com/channel/" + channelId); final ChannelInfo info = ChannelInfo.getInfo("https://youtube.com/channel/" + channelId);
final List<StreamItem> relatedStreams = new ObjectArrayList<>(); final List<StreamItem> relatedStreams = new ObjectArrayList<>();
info.getRelatedItems().forEach(o -> { info.getRelatedItems().forEach(o -> {
StreamInfoItem item = o; StreamInfoItem item = o;
relatedStreams.add(new StreamItem(item.getUrl().substring(23), item.getName(), relatedStreams.add(new StreamItem(item.getUrl().substring(23), item.getName(),
rewriteURL(item.getThumbnailUrl()), item.getUploaderName(), item.getUploaderUrl().substring(23), rewriteURL(item.getThumbnailUrl()), item.getUploaderName(), item.getUploaderUrl().substring(23),
item.getTextualUploadDate(), item.getDuration(), item.getViewCount())); item.getTextualUploadDate(), item.getDuration(), item.getViewCount()));
}); });
String nextpage = info.hasNextPage() ? info.getNextPage().getUrl() : null; String nextpage = info.hasNextPage() ? info.getNextPage().getUrl() : null;
final Channel channel = new Channel(info.getName(), rewriteURL(info.getAvatarUrl()), final Channel channel = new Channel(info.getId(), info.getName(), rewriteURL(info.getAvatarUrl()),
rewriteURL(info.getBannerUrl()), info.getDescription(), nextpage, relatedStreams); rewriteURL(info.getBannerUrl()), info.getDescription(), nextpage, relatedStreams);
return Constants.mapper.writeValueAsBytes(channel); IPFS.publishData(channel);
return Constants.mapper.writeValueAsBytes(channel);
} }
public static final byte[] channelPageResponse(String channelId, String url) public static final byte[] channelPageResponse(String channelId, String url)
throws IOException, ExtractionException, InterruptedException { throws IOException, ExtractionException, InterruptedException {
InfoItemsPage<StreamInfoItem> page = ChannelInfo.getMoreItems(Constants.YOUTUBE_SERVICE, InfoItemsPage<StreamInfoItem> page = ChannelInfo.getMoreItems(Constants.YOUTUBE_SERVICE,
"https://youtube.com/channel/" + channelId, new Page(url)); "https://youtube.com/channel/" + channelId, new Page(url));
final List<StreamItem> relatedStreams = new ObjectArrayList<>(); final List<StreamItem> relatedStreams = new ObjectArrayList<>();
page.getItems().forEach(o -> { page.getItems().forEach(o -> {
StreamInfoItem item = o; StreamInfoItem item = o;
relatedStreams.add(new StreamItem(item.getUrl().substring(23), item.getName(), relatedStreams.add(new StreamItem(item.getUrl().substring(23), item.getName(),
rewriteURL(item.getThumbnailUrl()), item.getUploaderName(), item.getUploaderUrl().substring(23), rewriteURL(item.getThumbnailUrl()), item.getUploaderName(), item.getUploaderUrl().substring(23),
item.getTextualUploadDate(), item.getDuration(), item.getViewCount())); item.getTextualUploadDate(), item.getDuration(), item.getViewCount()));
}); });
String nextpage = page.hasNextPage() ? page.getNextPage().getUrl() : null; String nextpage = page.hasNextPage() ? page.getNextPage().getUrl() : null;
final StreamsPage streamspage = new StreamsPage(nextpage, relatedStreams); final StreamsPage streamspage = new StreamsPage(nextpage, relatedStreams);
return Constants.mapper.writeValueAsBytes(streamspage); return Constants.mapper.writeValueAsBytes(streamspage);
} }
public static final byte[] trendingResponse() throws ParsingException, ExtractionException, IOException { public static final byte[] trendingResponse() throws ParsingException, ExtractionException, IOException {
final List<StreamItem> relatedStreams = new ObjectArrayList<>(); final List<StreamItem> relatedStreams = new ObjectArrayList<>();
String url = Constants.YOUTUBE_SERVICE.getKioskList().getListLinkHandlerFactoryByType("Trending") String url = Constants.YOUTUBE_SERVICE.getKioskList().getListLinkHandlerFactoryByType("Trending")
.getUrl("Trending"); .getUrl("Trending");
KioskInfo info = KioskInfo.getInfo(Constants.YOUTUBE_SERVICE, url); KioskInfo info = KioskInfo.getInfo(Constants.YOUTUBE_SERVICE, url);
info.getRelatedItems().forEach(o -> { info.getRelatedItems().forEach(o -> {
StreamInfoItem item = o; StreamInfoItem item = o;
relatedStreams.add(new StreamItem(item.getUrl().substring(23), item.getName(), relatedStreams.add(new StreamItem(item.getUrl().substring(23), item.getName(),
rewriteURL(item.getThumbnailUrl()), item.getUploaderName(), item.getUploaderUrl().substring(23), rewriteURL(item.getThumbnailUrl()), item.getUploaderName(), item.getUploaderUrl().substring(23),
item.getTextualUploadDate(), item.getDuration(), item.getViewCount())); item.getTextualUploadDate(), item.getDuration(), item.getViewCount()));
}); });
return Constants.mapper.writeValueAsBytes(relatedStreams); return Constants.mapper.writeValueAsBytes(relatedStreams);
} }
public static final byte[] playlistResponse(String playlistId) public static final byte[] playlistResponse(String playlistId)
throws IOException, ExtractionException, InterruptedException { throws IOException, ExtractionException, InterruptedException {
final PlaylistInfo info = PlaylistInfo.getInfo("https://www.youtube.com/playlist?list=" + playlistId); final PlaylistInfo info = PlaylistInfo.getInfo("https://www.youtube.com/playlist?list=" + playlistId);
final List<StreamItem> relatedStreams = new ObjectArrayList<>(); final List<StreamItem> relatedStreams = new ObjectArrayList<>();
info.getRelatedItems().forEach(o -> { info.getRelatedItems().forEach(o -> {
StreamInfoItem item = o; StreamInfoItem item = o;
relatedStreams.add(new StreamItem(item.getUrl().substring(23), item.getName(), relatedStreams.add(new StreamItem(item.getUrl().substring(23), item.getName(),
rewriteURL(item.getThumbnailUrl()), item.getUploaderName(), item.getUploaderUrl().substring(23), rewriteURL(item.getThumbnailUrl()), item.getUploaderName(), item.getUploaderUrl().substring(23),
item.getTextualUploadDate(), item.getDuration(), item.getViewCount())); item.getTextualUploadDate(), item.getDuration(), item.getViewCount()));
}); });
String nextpage = info.hasNextPage() ? info.getNextPage().getUrl() : null; String nextpage = info.hasNextPage() ? info.getNextPage().getUrl() : null;
final Playlist playlist = new Playlist(info.getName(), rewriteURL(info.getThumbnailUrl()), final Playlist playlist = new Playlist(info.getName(), rewriteURL(info.getThumbnailUrl()),
rewriteURL(info.getBannerUrl()), nextpage, info.getUploaderName(), info.getUploaderUrl().substring(23), rewriteURL(info.getBannerUrl()), nextpage, info.getUploaderName(), info.getUploaderUrl().substring(23),
rewriteURL(info.getUploaderAvatarUrl()), (int) info.getStreamCount(), relatedStreams); rewriteURL(info.getUploaderAvatarUrl()), (int) info.getStreamCount(), relatedStreams);
return Constants.mapper.writeValueAsBytes(playlist); return Constants.mapper.writeValueAsBytes(playlist);
} }
public static final byte[] playlistPageResponse(String playlistId, String url) public static final byte[] playlistPageResponse(String playlistId, String url)
throws IOException, ExtractionException, InterruptedException { throws IOException, ExtractionException, InterruptedException {
InfoItemsPage<StreamInfoItem> page = PlaylistInfo.getMoreItems(Constants.YOUTUBE_SERVICE, InfoItemsPage<StreamInfoItem> page = PlaylistInfo.getMoreItems(Constants.YOUTUBE_SERVICE,
"https://www.youtube.com/playlist?list=" + playlistId, new Page(url)); "https://www.youtube.com/playlist?list=" + playlistId, new Page(url));
final List<StreamItem> relatedStreams = new ObjectArrayList<>(); final List<StreamItem> relatedStreams = new ObjectArrayList<>();
page.getItems().forEach(o -> { page.getItems().forEach(o -> {
StreamInfoItem item = o; StreamInfoItem item = o;
relatedStreams.add(new StreamItem(item.getUrl().substring(23), item.getName(), relatedStreams.add(new StreamItem(item.getUrl().substring(23), item.getName(),
rewriteURL(item.getThumbnailUrl()), item.getUploaderName(), item.getUploaderUrl().substring(23), rewriteURL(item.getThumbnailUrl()), item.getUploaderName(), item.getUploaderUrl().substring(23),
item.getTextualUploadDate(), item.getDuration(), item.getViewCount())); item.getTextualUploadDate(), item.getDuration(), item.getViewCount()));
}); });
String nextpage = page.hasNextPage() ? page.getNextPage().getUrl() : null; String nextpage = page.hasNextPage() ? page.getNextPage().getUrl() : null;
final StreamsPage streamspage = new StreamsPage(nextpage, relatedStreams); final StreamsPage streamspage = new StreamsPage(nextpage, relatedStreams);
return Constants.mapper.writeValueAsBytes(streamspage); return Constants.mapper.writeValueAsBytes(streamspage);
} }
public static final byte[] suggestionsResponse(String query) public static final byte[] suggestionsResponse(String query)
throws JsonProcessingException, IOException, ExtractionException { throws JsonProcessingException, IOException, ExtractionException {
return Constants.mapper return Constants.mapper
.writeValueAsBytes(Constants.YOUTUBE_SERVICE.getSuggestionExtractor().suggestionList(query)); .writeValueAsBytes(Constants.YOUTUBE_SERVICE.getSuggestionExtractor().suggestionList(query));
} }
public static final byte[] searchResponse(String q) throws IOException, ExtractionException, InterruptedException { public static final byte[] searchResponse(String q) throws IOException, ExtractionException, InterruptedException {
final SearchInfo info = SearchInfo.getInfo(Constants.YOUTUBE_SERVICE, final SearchInfo info = SearchInfo.getInfo(Constants.YOUTUBE_SERVICE,
Constants.YOUTUBE_SERVICE.getSearchQHFactory().fromQuery(q)); Constants.YOUTUBE_SERVICE.getSearchQHFactory().fromQuery(q));
ObjectArrayList<SearchItem> items = new ObjectArrayList<>(); ObjectArrayList<SearchItem> items = new ObjectArrayList<>();
info.getRelatedItems().forEach(item -> { info.getRelatedItems().forEach(item -> {
switch (item.getInfoType()) { switch (item.getInfoType()) {
case STREAM: case STREAM:
StreamInfoItem stream = (StreamInfoItem) item; StreamInfoItem stream = (StreamInfoItem) item;
items.add(new SearchStream(item.getName(), rewriteURL(item.getThumbnailUrl()), items.add(new SearchStream(item.getName(), rewriteURL(item.getThumbnailUrl()),
item.getUrl().substring(23), stream.getViewCount(), stream.getDuration())); item.getUrl().substring(23), stream.getViewCount(), stream.getDuration()));
break; break;
case CHANNEL: case CHANNEL:
items.add(new SearchItem(item.getName(), rewriteURL(item.getThumbnailUrl()), items.add(new SearchItem(item.getName(), rewriteURL(item.getThumbnailUrl()),
item.getUrl().substring(23))); item.getUrl().substring(23)));
break; break;
default: default:
break; break;
} }
}); });
Page nextpage = info.getNextPage(); Page nextpage = info.getNextPage();
return nextpage != null return nextpage != null
? Constants.mapper.writeValueAsBytes(new SearchResults(nextpage.getUrl(), nextpage.getId(), items)) ? Constants.mapper.writeValueAsBytes(new SearchResults(nextpage.getUrl(), nextpage.getId(), items))
: Constants.mapper.writeValueAsBytes(new SearchResults(null, null, items)); : Constants.mapper.writeValueAsBytes(new SearchResults(null, null, items));
} }
public static final byte[] searchPageResponse(String q, String url, String id) public static final byte[] searchPageResponse(String q, String url, String id)
throws IOException, ExtractionException, InterruptedException { throws IOException, ExtractionException, InterruptedException {
InfoItemsPage<InfoItem> pages = SearchInfo.getMoreItems(Constants.YOUTUBE_SERVICE, InfoItemsPage<InfoItem> pages = SearchInfo.getMoreItems(Constants.YOUTUBE_SERVICE,
Constants.YOUTUBE_SERVICE.getSearchQHFactory().fromQuery(q), new Page(url, id)); Constants.YOUTUBE_SERVICE.getSearchQHFactory().fromQuery(q), new Page(url, id));
ObjectArrayList<SearchItem> items = new ObjectArrayList<>(); ObjectArrayList<SearchItem> items = new ObjectArrayList<>();
pages.getItems().forEach(item -> { pages.getItems().forEach(item -> {
switch (item.getInfoType()) { switch (item.getInfoType()) {
case STREAM: case STREAM:
StreamInfoItem stream = (StreamInfoItem) item; StreamInfoItem stream = (StreamInfoItem) item;
items.add(new SearchStream(item.getName(), rewriteURL(item.getThumbnailUrl()), items.add(new SearchStream(item.getName(), rewriteURL(item.getThumbnailUrl()),
item.getUrl().substring(23), stream.getViewCount(), stream.getDuration())); item.getUrl().substring(23), stream.getViewCount(), stream.getDuration()));
break; break;
case CHANNEL: case CHANNEL:
items.add(new SearchItem(item.getName(), rewriteURL(item.getThumbnailUrl()), items.add(new SearchItem(item.getName(), rewriteURL(item.getThumbnailUrl()),
item.getUrl().substring(23))); item.getUrl().substring(23)));
break; break;
default: default:
break; break;
} }
}); });
Page nextpage = pages.getNextPage(); Page nextpage = pages.getNextPage();
return nextpage != null return nextpage != null
? Constants.mapper.writeValueAsBytes(new SearchResults(nextpage.getUrl(), nextpage.getId(), items)) ? Constants.mapper.writeValueAsBytes(new SearchResults(nextpage.getUrl(), nextpage.getId(), items))
: Constants.mapper.writeValueAsBytes(new SearchResults(null, null, items)); : Constants.mapper.writeValueAsBytes(new SearchResults(null, null, items));
} }
public static final byte[] registerResponse(String user, String pass) throws IOException { public static final byte[] registerResponse(String user, String pass) throws IOException {
return Constants.mapper.writeValueAsBytes(null); return Constants.mapper.writeValueAsBytes(null);
} }
private static final String getLBRYStreamURL(String videoId) throws IOException, InterruptedException { private static final String getLBRYStreamURL(String videoId) throws IOException, InterruptedException {
String lbryId = new JSONObject(Constants.h2client.send(HttpRequest String lbryId = new JSONObject(Constants.h2client.send(HttpRequest
.newBuilder(URI.create("https://api.lbry.com/yt/resolve?video_ids=" + URLUtils.silentEncode(videoId))) .newBuilder(URI.create("https://api.lbry.com/yt/resolve?video_ids=" + URLUtils.silentEncode(videoId)))
.setHeader("User-Agent", Constants.USER_AGENT).build(), BodyHandlers.ofString()).body()) .setHeader("User-Agent", Constants.USER_AGENT).build(), BodyHandlers.ofString()).body())
.getJSONObject("data").getJSONObject("videos").optString(videoId); .getJSONObject("data").getJSONObject("videos").optString(videoId);
if (!lbryId.isEmpty()) if (!lbryId.isEmpty())
return rewriteURL( return rewriteURL(
new JSONObject( new JSONObject(
Constants.h2client.send( Constants.h2client.send(
HttpRequest.newBuilder(URI.create("https://api.lbry.tv/api/v1/proxy?m=get")) HttpRequest.newBuilder(URI.create("https://api.lbry.tv/api/v1/proxy?m=get"))
.POST(BodyPublishers.ofString(String.valueOf(new JSONObject() .POST(BodyPublishers.ofString(String.valueOf(new JSONObject()
.put("jsonrpc", "2.0").put("method", "get").put("params", .put("jsonrpc", "2.0").put("method", "get").put("params",
new JSONObject().put("uri", "lbry://" + lbryId) new JSONObject().put("uri", "lbry://" + lbryId)
.put("save_file", true))))) .put("save_file", true)))))
.build(), .build(),
BodyHandlers.ofString()).body()).getJSONObject("result") BodyHandlers.ofString()).body()).getJSONObject("result")
.getString("streaming_url")); .getString("streaming_url"));
return null; return null;
} }
private static String rewriteURL(final String old) { private static String rewriteURL(final String old) {
if (Constants.debug) if (Constants.debug)
return old; return old;
if (old == null || old.isEmpty()) if (old == null || old.isEmpty())
return null; return null;
URL url = null; URL url = null;
try { try {
url = new URL(old); url = new URL(old);
} catch (MalformedURLException e) { } catch (MalformedURLException e) {
e.printStackTrace(); e.printStackTrace();
} }
final String host = url.getHost(); final String host = url.getHost();
String query = url.getQuery(); String query = url.getQuery();
boolean hasQuery = query != null; boolean hasQuery = query != null;
String path = url.getPath(); String path = url.getPath();
path = path.replace("-rj", "-rw"); path = path.replace("-rj", "-rw");
if (path.startsWith("/vi/") && !path.contains("_live")) { if (path.startsWith("/vi/") && !path.contains("_live")) {
path = path.replace("/vi/", "/vi_webp/").replace(".jpg", ".webp").replace("hq720", "mqdefault") path = path.replace("/vi/", "/vi_webp/").replace(".jpg", ".webp").replace("hq720", "mqdefault")
.replace("hqdefault", "mqdefault"); .replace("hqdefault", "mqdefault");
hasQuery = false; hasQuery = false;
} }
return Constants.PROXY_PART + path + (hasQuery ? "?" + query + "&host=" : "?host=") return Constants.PROXY_PART + path + (hasQuery ? "?" + query + "&host=" : "?host=")
+ URLUtils.silentEncode(host); + URLUtils.silentEncode(host);
} }
} }

View File

@ -19,36 +19,36 @@ import me.kavin.piped.consts.Constants;
public class SponsorBlockUtils { public class SponsorBlockUtils {
public static final String getSponsors(String id, String categories) public static final String getSponsors(String id, String categories)
throws IOException, InterruptedException, NoSuchAlgorithmException, JsonParserException { throws IOException, InterruptedException, NoSuchAlgorithmException, JsonParserException {
String hash = toSha256(id); String hash = toSha256(id);
URI uri = URI.create("https://sponsor.ajay.app/api/skipSegments/" + URLUtils.silentEncode(hash.substring(0, 4)) URI uri = URI.create("https://sponsor.ajay.app/api/skipSegments/" + URLUtils.silentEncode(hash.substring(0, 4))
+ "?categories=" + URLUtils.silentEncode(categories)); + "?categories=" + URLUtils.silentEncode(categories));
JsonArray jArray = JsonParser.array() JsonArray jArray = JsonParser.array()
.from(Constants.h2client.send(HttpRequest.newBuilder(uri).build(), BodyHandlers.ofString()).body()); .from(Constants.h2client.send(HttpRequest.newBuilder(uri).build(), BodyHandlers.ofString()).body());
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 final 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();
for (final byte b : bytes) { for (final byte b : bytes) {
final String hex = Integer.toHexString(0xff & b); final String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) { if (hex.length() == 1) {
sb.append('0'); sb.append('0');
} }
sb.append(hex); sb.append(hex);
} }
return sb.toString(); return sb.toString();
} }
} }

View File

@ -5,11 +5,11 @@ import java.net.URLEncoder;
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, "UTF-8");
} catch (Exception e) { } catch (Exception e) {
// ignored // ignored
} }
return s; return s;
} }
} }

View File

@ -4,16 +4,17 @@ import java.util.List;
public class Channel { public class Channel {
public String name, avatarUrl, bannerUrl, description, nextpage; public String id, name, avatarUrl, bannerUrl, description, nextpage;
public List<StreamItem> relatedStreams; public List<StreamItem> relatedStreams;
public Channel(String name, String avatarUrl, String bannerUrl, String description, String nextpage, public Channel(String id, String name, String avatarUrl, String bannerUrl, String description, String nextpage,
List<StreamItem> relatedStreams) { List<StreamItem> relatedStreams) {
this.name = name; this.id = id;
this.avatarUrl = avatarUrl; this.name = name;
this.bannerUrl = bannerUrl; this.avatarUrl = avatarUrl;
this.description = description; this.bannerUrl = bannerUrl;
this.nextpage = nextpage; this.description = description;
this.relatedStreams = relatedStreams; this.nextpage = nextpage;
this.relatedStreams = relatedStreams;
} }
} }

View File

@ -0,0 +1,12 @@
package me.kavin.piped.utils.obj;
public class ChapterSegment {
public String title;
public int start;
public ChapterSegment(String title, int start) {
this.title = title;
this.start = start;
}
}

View File

@ -2,14 +2,49 @@ package me.kavin.piped.utils.obj;
public class PipedStream { public class PipedStream {
public String url, format, quality, mimeType; public String url, format, quality, mimeType, codec;
public boolean videoOnly; public boolean videoOnly;
public int bitrate, initStart, initEnd, indexStart, indexEnd, width, height, fps;
public PipedStream(String url, String format, String quality, String mimeType, boolean videoOnly) { public PipedStream(String url, String format, String quality, String mimeType, boolean videoOnly) {
this.url = url; this.url = url;
this.format = format; this.format = format;
this.quality = quality; this.quality = quality;
this.mimeType = mimeType; this.mimeType = mimeType;
this.videoOnly = videoOnly; this.videoOnly = videoOnly;
}
public PipedStream(String url, String format, String quality, String mimeType, boolean videoOnly, int bitrate,
int initStart, int initEnd, int indexStart, int indexEnd, String codec) {
this.url = url;
this.format = format;
this.quality = quality;
this.mimeType = mimeType;
this.videoOnly = videoOnly;
this.bitrate = bitrate;
this.initStart = initStart;
this.initEnd = initEnd;
this.indexStart = indexStart;
this.indexEnd = indexEnd;
this.codec = codec;
}
public PipedStream(String url, String format, String quality, String mimeType, boolean videoOnly, int bitrate,
int initStart, int initEnd, int indexStart, int indexEnd, String codec, int width, int height, int fps) {
this.url = url;
this.format = format;
this.quality = quality;
this.mimeType = mimeType;
this.videoOnly = videoOnly;
this.bitrate = bitrate;
this.initStart = initStart;
this.initEnd = initEnd;
this.indexStart = indexStart;
this.indexEnd = indexEnd;
this.codec = codec;
this.width = width;
this.height = height;
this.fps = fps;
} }
} }

View File

@ -9,15 +9,15 @@ public class Playlist {
public List<StreamItem> relatedStreams; public List<StreamItem> relatedStreams;
public Playlist(String name, String thumbnailUrl, String bannerUrl, String nextpage, String uploader, public Playlist(String name, String thumbnailUrl, String bannerUrl, String nextpage, String uploader,
String uploaderUrl, String uploaderAvatar, int videos, List<StreamItem> relatedStreams) { String uploaderUrl, String uploaderAvatar, int videos, List<StreamItem> relatedStreams) {
this.name = name; this.name = name;
this.thumbnailUrl = thumbnailUrl; this.thumbnailUrl = thumbnailUrl;
this.bannerUrl = bannerUrl; this.bannerUrl = bannerUrl;
this.nextpage = nextpage; this.nextpage = nextpage;
this.videos = videos; this.videos = videos;
this.uploader = uploader; this.uploader = uploader;
this.uploaderUrl = uploaderUrl; this.uploaderUrl = uploaderUrl;
this.uploaderAvatar = uploaderAvatar; this.uploaderAvatar = uploaderAvatar;
this.relatedStreams = relatedStreams; this.relatedStreams = relatedStreams;
} }
} }

View File

@ -9,8 +9,8 @@ public class SearchResults {
public ObjectArrayList<SearchItem> items; public ObjectArrayList<SearchItem> items;
public SearchResults(String nextpage, String id, ObjectArrayList<SearchItem> items) { public SearchResults(String nextpage, String id, ObjectArrayList<SearchItem> items) {
this.nextpage = nextpage; this.nextpage = nextpage;
this.id = id; this.id = id;
this.items = items; this.items = items;
} }
} }

View File

@ -6,14 +6,14 @@ public class StreamItem {
public long duration, views; public long duration, views;
public StreamItem(String url, String title, String thumbnail, String uploaderName, String uploaderUrl, public StreamItem(String url, String title, String thumbnail, String uploaderName, String uploaderUrl,
String uploadedDate, long duration, long views) { String uploadedDate, long duration, long views) {
this.url = url; this.url = url;
this.title = title; this.title = title;
this.thumbnail = thumbnail; this.thumbnail = thumbnail;
this.uploaderName = uploaderName; this.uploaderName = uploaderName;
this.uploaderUrl = uploaderUrl; this.uploaderUrl = uploaderUrl;
this.uploadedDate = uploadedDate; this.uploadedDate = uploadedDate;
this.duration = duration; this.duration = duration;
this.views = views; this.views = views;
} }
} }

View File

@ -17,25 +17,25 @@ public class Streams {
public boolean livestream; public boolean livestream;
public Streams(String title, String description, String uploadDate, String uploader, String uploaderUrl, public Streams(String title, String description, String uploadDate, String uploader, String uploaderUrl,
String uploaderAvatar, String thumbnailUrl, long duration, long views, long likes, long dislikes, String uploaderAvatar, String thumbnailUrl, long duration, long views, long likes, long dislikes,
List<PipedStream> audioStreams, List<PipedStream> videoStreams, List<StreamItem> relatedStreams, List<PipedStream> audioStreams, List<PipedStream> videoStreams, List<StreamItem> relatedStreams,
List<Subtitle> subtitles, boolean livestream, String hls) { List<Subtitle> subtitles, boolean livestream, String hls) {
this.title = title; this.title = title;
this.description = description; this.description = description;
this.uploadDate = uploadDate; this.uploadDate = uploadDate;
this.uploader = uploader; this.uploader = uploader;
this.uploaderUrl = uploaderUrl; this.uploaderUrl = uploaderUrl;
this.uploaderAvatar = uploaderAvatar; this.uploaderAvatar = uploaderAvatar;
this.thumbnailUrl = thumbnailUrl; this.thumbnailUrl = thumbnailUrl;
this.duration = duration; this.duration = duration;
this.views = views; this.views = views;
this.likes = likes; this.likes = likes;
this.dislikes = dislikes; this.dislikes = dislikes;
this.audioStreams = audioStreams; this.audioStreams = audioStreams;
this.videoStreams = videoStreams; this.videoStreams = videoStreams;
this.relatedStreams = relatedStreams; this.relatedStreams = relatedStreams;
this.subtitles = subtitles; this.subtitles = subtitles;
this.livestream = livestream; this.livestream = livestream;
this.hls = hls; this.hls = hls;
} }
} }

View File

@ -8,7 +8,7 @@ public class StreamsPage {
public List<StreamItem> relatedStreams; public List<StreamItem> relatedStreams;
public StreamsPage(String nextpage, List<StreamItem> relatedStreams) { public StreamsPage(String nextpage, List<StreamItem> relatedStreams) {
this.nextpage = nextpage; this.nextpage = nextpage;
this.relatedStreams = relatedStreams; this.relatedStreams = relatedStreams;
} }
} }

View File

@ -5,7 +5,7 @@ public class Subtitle {
public final String url, mimeType; public final String url, mimeType;
public Subtitle(String url, String mimeType) { public Subtitle(String url, String mimeType) {
this.url = url; this.url = url;
this.mimeType = mimeType; this.mimeType = mimeType;
}; };
} }

View File

@ -5,20 +5,20 @@ public class SearchItem {
private String name, thumbnail, url; private String name, thumbnail, url;
public SearchItem(String name, String thumbnail, String url) { public SearchItem(String name, String thumbnail, String url) {
this.name = name; this.name = name;
this.thumbnail = thumbnail; this.thumbnail = thumbnail;
this.url = url; this.url = url;
} }
public String getName() { public String getName() {
return name; return name;
} }
public String getThumbnail() { public String getThumbnail() {
return thumbnail; return thumbnail;
} }
public String getUrl() { public String getUrl() {
return url; return url;
} }
} }

View File

@ -5,16 +5,16 @@ public class SearchStream extends SearchItem {
private long views, duration; private long views, duration;
public SearchStream(String name, String thumbnail, String url, long views, long duration) { public SearchStream(String name, String thumbnail, String url, long views, long duration) {
super(name, thumbnail, url); super(name, thumbnail, url);
this.views = views; this.views = views;
this.duration = duration; this.duration = duration;
} }
public long getViews() { public long getViews() {
return views; return views;
} }
public long getDuration() { public long getDuration() {
return duration; return duration;
} }
} }