mirror of
https://github.com/TeamPiped/Piped-Backend.git
synced 2024-12-12 21:30:29 +05:30
Use Hibernate as a database (#32)
* Basic hibernate structure. * Commit stash. * Improve a lot of things. * Implement Feed, PubSub, query optimizations. * Update video views on channel visits. * Allow mass importing subscriptions. * Allow configuring database in config.properties. * Major multi-threading and pubsub improvements. * PubSub query improvements and fix for postgres. * Revert docker-compose change. * Disable showing sql statements.
This commit is contained in:
parent
5ab5635fc4
commit
dfca88952a
@ -6,3 +6,4 @@ Dockerfile
|
||||
LICENSE
|
||||
*.md
|
||||
config.properties
|
||||
data/
|
||||
|
5
.gitignore
vendored
5
.gitignore
vendored
@ -15,8 +15,11 @@ bin/
|
||||
### Gradle ###
|
||||
/build/
|
||||
|
||||
# Database Data
|
||||
/data/
|
||||
|
||||
# TxT File
|
||||
*.txt
|
||||
|
||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||
hs_err_pid*
|
||||
hs_err_pid*
|
||||
|
@ -16,7 +16,6 @@ dependencies {
|
||||
implementation 'it.unimi.dsi:fastutil-core:8.5.4'
|
||||
implementation 'commons-codec:commons-codec:1.15'
|
||||
implementation 'org.bouncycastle:bcprov-jdk15on:1.69'
|
||||
implementation 'org.mongodb:mongodb-driver-sync:4.2.2'
|
||||
implementation 'com.github.TiA4f8R.NewPipeExtractor:NewPipeExtractor:df53170023ff5b97ad1d7ccc74bfce74c3998bcc'
|
||||
implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751'
|
||||
implementation 'com.fasterxml.jackson.core:jackson-core:2.12.4'
|
||||
@ -32,7 +31,11 @@ dependencies {
|
||||
implementation 'io.activej:activej-boot:4.3'
|
||||
implementation 'io.activej:activej-specializer:4.3'
|
||||
implementation 'io.activej:activej-launchers-http:4.3'
|
||||
implementation 'net.java.dev.jna:jna-platform:5.8.0'
|
||||
implementation 'org.postgresql:postgresql:42.2.19'
|
||||
implementation 'org.hibernate:hibernate-core:5.4.30.Final'
|
||||
implementation 'org.hibernate:hibernate-hikaricp:5.4.30.Final'
|
||||
implementation 'org.springframework.security:spring-security-crypto:5.5.1'
|
||||
implementation 'commons-logging:commons-logging:1.2'
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
|
@ -1,13 +1,22 @@
|
||||
|
||||
# The port to Listen on.
|
||||
PORT: 8080
|
||||
|
||||
# The number of workers to use for the server
|
||||
HTTP_WORKERS: 2
|
||||
|
||||
# Proxy
|
||||
PROXY_PART: https://pipedproxy-ams.kavin.rocks
|
||||
# The number of workers to use for the server
|
||||
HTTP_WORKERS: 2
|
||||
|
||||
# Captcha Parameters
|
||||
CAPTCHA_BASE_URL: https://api.capmonster.cloud/
|
||||
CAPTCHA_API_KEY: INSERT_HERE
|
||||
# Proxy
|
||||
PROXY_PART: https://pipedproxy-ams.kavin.rocks
|
||||
|
||||
# Captcha Parameters
|
||||
CAPTCHA_BASE_URL: https://api.capmonster.cloud/
|
||||
CAPTCHA_API_KEY: INSERT_HERE
|
||||
|
||||
# Public API URL
|
||||
API_URL: https://pipedapi.kavin.rocks
|
||||
|
||||
# Hibernate properties
|
||||
hibernate.connection.url: jdbc:postgresql://postgres:5432/piped
|
||||
hibernate.connection.driver_class: org.postgresql.Driver
|
||||
hibernate.dialect: org.hibernate.dialect.PostgreSQL10Dialect
|
||||
hibernate.connection.username: piped
|
||||
hibernate.connection.password: changeme
|
||||
|
@ -1,9 +1,19 @@
|
||||
version: "3.8"
|
||||
services:
|
||||
piped:
|
||||
image: 1337kavin/piped:latest
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "127.0.0.1:8080:8080"
|
||||
volumes:
|
||||
- ./config.properties:/app/config.properties
|
||||
piped:
|
||||
image: 1337kavin/piped:latest
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "127.0.0.1:8080:8080"
|
||||
volumes:
|
||||
- ./config.properties:/app/config.properties
|
||||
depends_on:
|
||||
- postgres
|
||||
postgres:
|
||||
image: postgres:13-alpine
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ./data/db:/var/lib/postgresql/data
|
||||
environment:
|
||||
- POSTGRES_DB=piped
|
||||
- POSTGRES_USER=piped
|
||||
- POSTGRES_PASSWORD=changeme
|
||||
|
@ -1,10 +1,24 @@
|
||||
package me.kavin.piped;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.Transaction;
|
||||
import org.hibernate.query.Query;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.localization.Localization;
|
||||
|
||||
import io.activej.inject.Injector;
|
||||
import me.kavin.piped.utils.DatabaseHelper;
|
||||
import me.kavin.piped.utils.DatabaseSessionFactory;
|
||||
import me.kavin.piped.utils.DownloaderImpl;
|
||||
import me.kavin.piped.utils.Multithreading;
|
||||
import me.kavin.piped.utils.ResponseHelper;
|
||||
|
||||
public class Main {
|
||||
|
||||
@ -14,6 +28,63 @@ public class Main {
|
||||
|
||||
Injector.useSpecializer();
|
||||
|
||||
new Timer().scheduleAtFixedRate(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Session s = DatabaseSessionFactory.createSession();
|
||||
|
||||
List<String> channels = DatabaseHelper.getGlobalSubscribedChannelIds(s);
|
||||
|
||||
DatabaseHelper.getPubSubFromIds(s, channels).forEach(pubsub -> {
|
||||
if (System.currentTimeMillis() - pubsub.getSubbedAt() < TimeUnit.DAYS.toMillis(4))
|
||||
channels.remove(pubsub.getId());
|
||||
});
|
||||
|
||||
Collections.shuffle(channels);
|
||||
|
||||
for (String channelId : channels)
|
||||
Multithreading.runAsyncLimitedPubSub(() -> {
|
||||
Session sess = DatabaseSessionFactory.createSession();
|
||||
try {
|
||||
ResponseHelper.subscribePubSub(channelId, sess);
|
||||
} catch (IOException | InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
sess.close();
|
||||
});
|
||||
|
||||
s.close();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}, 0, TimeUnit.MINUTES.toMillis(90));
|
||||
|
||||
new Timer().scheduleAtFixedRate(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Session s = DatabaseSessionFactory.createSession();
|
||||
|
||||
Transaction tr = s.getTransaction();
|
||||
|
||||
tr.begin();
|
||||
|
||||
Query<?> query = s.createQuery("delete from Video where uploaded < :time").setParameter("time",
|
||||
System.currentTimeMillis() - TimeUnit.DAYS.toMillis(10));
|
||||
|
||||
System.out.println(String.format("Cleanup: Removed %o old videos", query.executeUpdate()));
|
||||
|
||||
tr.commit();
|
||||
|
||||
s.close();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}, 0, TimeUnit.MINUTES.toMillis(60));
|
||||
|
||||
new ServerLauncher().launch(args);
|
||||
|
||||
}
|
||||
|
@ -1,17 +1,20 @@
|
||||
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.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.ExecutionException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.hibernate.Session;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.schabi.newpipe.extractor.exceptions.AgeRestrictedContentException;
|
||||
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException;
|
||||
@ -32,148 +35,225 @@ import io.activej.inject.module.Module;
|
||||
import io.activej.launchers.http.MultithreadedHttpServerLauncher;
|
||||
import me.kavin.piped.consts.Constants;
|
||||
import me.kavin.piped.utils.CustomServletDecorator;
|
||||
import me.kavin.piped.utils.DatabaseSessionFactory;
|
||||
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.LoginRequest;
|
||||
import me.kavin.piped.utils.resp.SubscriptionUpdateRequest;
|
||||
|
||||
public class ServerLauncher extends MultithreadedHttpServerLauncher {
|
||||
|
||||
@Provides
|
||||
Executor executor() {
|
||||
return Executors.newCachedThreadPool();
|
||||
return Multithreading.getCachedExecutor();
|
||||
}
|
||||
|
||||
@Provides
|
||||
AsyncServlet mainServlet(Executor executor) {
|
||||
|
||||
RoutingServlet router = RoutingServlet.create().map(HttpMethod.GET, "/webhooks/pubsub", request -> {
|
||||
return HttpResponse.ok200().withPlainText(request.getQueryParameter("hub.challenge"));
|
||||
}).map(HttpMethod.POST, "/webhooks/pubsub", AsyncServlet.ofBlocking(executor, request -> {
|
||||
try {
|
||||
RoutingServlet router = RoutingServlet.create()
|
||||
.map(HttpMethod.OPTIONS, "/*", request -> HttpResponse.ofCode(200))
|
||||
.map(GET, "/webhooks/pubsub", request -> {
|
||||
return HttpResponse.ok200().withPlainText(request.getQueryParameter("hub.challenge"));
|
||||
}).map(POST, "/webhooks/pubsub", AsyncServlet.ofBlocking(executor, request -> {
|
||||
try {
|
||||
|
||||
SyndFeed feed = new SyndFeedInput()
|
||||
.build(new InputSource(new ByteArrayInputStream(request.loadBody().getResult().asArray())));
|
||||
SyndFeed feed = new SyndFeedInput().build(
|
||||
new InputSource(new ByteArrayInputStream(request.loadBody().getResult().asArray())));
|
||||
|
||||
feed.getEntries().forEach(entry -> {
|
||||
System.out.println(entry.getLinks().get(0).getHref());
|
||||
System.out.println(entry.getAuthors().get(0).getUri());
|
||||
});
|
||||
Multithreading.runAsync(() -> {
|
||||
Session s = DatabaseSessionFactory.createSession();
|
||||
feed.getEntries().forEach(entry -> {
|
||||
System.out.println(entry.getLinks().get(0).getHref());
|
||||
ResponseHelper.handleNewVideo(entry.getLinks().get(0).getHref(),
|
||||
entry.getPublishedDate().getTime(), null, s);
|
||||
});
|
||||
s.close();
|
||||
});
|
||||
|
||||
return HttpResponse.ofCode(204);
|
||||
return HttpResponse.ofCode(204);
|
||||
|
||||
} catch (Exception e) {
|
||||
return getErrorResponse(e);
|
||||
}
|
||||
})).map("/sponsors/:videoId", AsyncServlet.ofBlocking(executor, request -> {
|
||||
try {
|
||||
return getJsonResponse(SponsorBlockUtils
|
||||
.getSponsors(request.getPathParameter("videoId"), request.getQueryParameter("category"))
|
||||
.getBytes(StandardCharsets.UTF_8), "public, max-age=3600");
|
||||
} catch (Exception e) {
|
||||
return getErrorResponse(e);
|
||||
}
|
||||
})).map("/streams/:videoId", AsyncServlet.ofBlocking(executor, request -> {
|
||||
try {
|
||||
return getJsonResponse(ResponseHelper.streamsResponse(request.getPathParameter("videoId")),
|
||||
"public, s-maxage=21540");
|
||||
} catch (Exception e) {
|
||||
return getErrorResponse(e);
|
||||
}
|
||||
})).map("/channel/:channelId", AsyncServlet.ofBlocking(executor, request -> {
|
||||
try {
|
||||
return getJsonResponse(
|
||||
ResponseHelper.channelResponse("channel/" + request.getPathParameter("channelId")),
|
||||
"public, max-age=600");
|
||||
} catch (Exception e) {
|
||||
return getErrorResponse(e);
|
||||
}
|
||||
})).map("/c/:name", AsyncServlet.ofBlocking(executor, request -> {
|
||||
try {
|
||||
return getJsonResponse(ResponseHelper.channelResponse("c/" + request.getPathParameter("name")),
|
||||
"public, max-age=600");
|
||||
} catch (Exception e) {
|
||||
return getErrorResponse(e);
|
||||
}
|
||||
})).map("/user/:name", AsyncServlet.ofBlocking(executor, request -> {
|
||||
try {
|
||||
return getJsonResponse(ResponseHelper.channelResponse("user/" + request.getPathParameter("name")),
|
||||
"public, max-age=600");
|
||||
} catch (Exception e) {
|
||||
return getErrorResponse(e);
|
||||
}
|
||||
})).map("/nextpage/channel/:channelId", AsyncServlet.ofBlocking(executor, request -> {
|
||||
try {
|
||||
return getJsonResponse(ResponseHelper.channelPageResponse(request.getPathParameter("channelId"),
|
||||
request.getQueryParameter("nextpage")), "public, max-age=3600");
|
||||
} catch (Exception e) {
|
||||
return getErrorResponse(e);
|
||||
}
|
||||
})).map("/playlists/:playlistId", AsyncServlet.ofBlocking(executor, request -> {
|
||||
try {
|
||||
return getJsonResponse(ResponseHelper.playlistResponse(request.getPathParameter("playlistId")),
|
||||
"public, max-age=600");
|
||||
} catch (Exception e) {
|
||||
return getErrorResponse(e);
|
||||
}
|
||||
})).map("/nextpage/playlists/:playlistId", AsyncServlet.ofBlocking(executor, request -> {
|
||||
try {
|
||||
return getJsonResponse(ResponseHelper.playlistPageResponse(request.getPathParameter("playlistId"),
|
||||
request.getQueryParameter("nextpage")), "public, max-age=3600");
|
||||
} catch (Exception e) {
|
||||
return getErrorResponse(e);
|
||||
}
|
||||
})).map("/rss/playlists/:playlistId", AsyncServlet.ofBlocking(executor, request -> {
|
||||
try {
|
||||
return getJsonResponse(ResponseHelper.playlistRSSResponse(request.getPathParameter("playlistId")),
|
||||
"public, s-maxage=600");
|
||||
} catch (Exception e) {
|
||||
return getErrorResponse(e);
|
||||
}
|
||||
})).map("/suggestions", AsyncServlet.ofBlocking(executor, request -> {
|
||||
try {
|
||||
return getJsonResponse(ResponseHelper.suggestionsResponse(request.getQueryParameter("query")),
|
||||
"public, max-age=600");
|
||||
} catch (Exception e) {
|
||||
return getErrorResponse(e);
|
||||
}
|
||||
})).map("/search", AsyncServlet.ofBlocking(executor, request -> {
|
||||
try {
|
||||
return getJsonResponse(ResponseHelper.searchResponse(request.getQueryParameter("q"),
|
||||
request.getQueryParameter("filter")), "public, max-age=600");
|
||||
} catch (Exception e) {
|
||||
return getErrorResponse(e);
|
||||
}
|
||||
})).map("/nextpage/search", AsyncServlet.ofBlocking(executor, request -> {
|
||||
try {
|
||||
return getJsonResponse(
|
||||
ResponseHelper.searchPageResponse(request.getQueryParameter("q"),
|
||||
request.getQueryParameter("filter"), request.getQueryParameter("nextpage")),
|
||||
"public, max-age=3600");
|
||||
} catch (Exception e) {
|
||||
return getErrorResponse(e);
|
||||
}
|
||||
})).map("/trending", AsyncServlet.ofBlocking(executor, request -> {
|
||||
try {
|
||||
return getJsonResponse(ResponseHelper.trendingResponse(request.getQueryParameter("region")),
|
||||
"public, max-age=3600");
|
||||
} catch (Exception e) {
|
||||
return getErrorResponse(e);
|
||||
}
|
||||
})).map("/comments/:videoId", AsyncServlet.ofBlocking(executor, request -> {
|
||||
try {
|
||||
return getJsonResponse(ResponseHelper.commentsResponse(request.getPathParameter("videoId")),
|
||||
"public, max-age=1200");
|
||||
} catch (Exception e) {
|
||||
return getErrorResponse(e);
|
||||
}
|
||||
})).map("/nextpage/comments/:videoId", AsyncServlet.ofBlocking(executor, request -> {
|
||||
try {
|
||||
return getJsonResponse(ResponseHelper.commentsPageResponse(request.getPathParameter("videoId"),
|
||||
request.getQueryParameter("url")), "public, max-age=3600");
|
||||
} catch (Exception e) {
|
||||
return getErrorResponse(e);
|
||||
}
|
||||
}));
|
||||
} catch (Exception e) {
|
||||
return getErrorResponse(e);
|
||||
}
|
||||
})).map(GET, "/sponsors/:videoId", AsyncServlet.ofBlocking(executor, request -> {
|
||||
try {
|
||||
return getJsonResponse(
|
||||
SponsorBlockUtils.getSponsors(request.getPathParameter("videoId"),
|
||||
request.getQueryParameter("category")).getBytes(StandardCharsets.UTF_8),
|
||||
"public, max-age=3600");
|
||||
} catch (Exception e) {
|
||||
return getErrorResponse(e);
|
||||
}
|
||||
})).map(GET, "/streams/:videoId", AsyncServlet.ofBlocking(executor, request -> {
|
||||
try {
|
||||
return getJsonResponse(ResponseHelper.streamsResponse(request.getPathParameter("videoId")),
|
||||
"public, s-maxage=21540");
|
||||
} catch (Exception e) {
|
||||
return getErrorResponse(e);
|
||||
}
|
||||
})).map(GET, "/channel/:channelId", AsyncServlet.ofBlocking(executor, request -> {
|
||||
try {
|
||||
return getJsonResponse(
|
||||
ResponseHelper.channelResponse("channel/" + request.getPathParameter("channelId")),
|
||||
"public, max-age=600");
|
||||
} catch (Exception e) {
|
||||
return getErrorResponse(e);
|
||||
}
|
||||
})).map(GET, "/c/:name", AsyncServlet.ofBlocking(executor, request -> {
|
||||
try {
|
||||
return getJsonResponse(ResponseHelper.channelResponse("c/" + request.getPathParameter("name")),
|
||||
"public, max-age=600");
|
||||
} catch (Exception e) {
|
||||
return getErrorResponse(e);
|
||||
}
|
||||
})).map(GET, "/user/:name", AsyncServlet.ofBlocking(executor, request -> {
|
||||
try {
|
||||
return getJsonResponse(
|
||||
ResponseHelper.channelResponse("user/" + request.getPathParameter("name")),
|
||||
"public, max-age=600");
|
||||
} catch (Exception e) {
|
||||
return getErrorResponse(e);
|
||||
}
|
||||
})).map(GET, "/nextpage/channel/:channelId", AsyncServlet.ofBlocking(executor, request -> {
|
||||
try {
|
||||
return getJsonResponse(ResponseHelper.channelPageResponse(request.getPathParameter("channelId"),
|
||||
request.getQueryParameter("nextpage")), "public, max-age=3600");
|
||||
} catch (Exception e) {
|
||||
return getErrorResponse(e);
|
||||
}
|
||||
})).map(GET, "/playlists/:playlistId", AsyncServlet.ofBlocking(executor, request -> {
|
||||
try {
|
||||
return getJsonResponse(ResponseHelper.playlistResponse(request.getPathParameter("playlistId")),
|
||||
"public, max-age=600");
|
||||
} catch (Exception e) {
|
||||
return getErrorResponse(e);
|
||||
}
|
||||
})).map(GET, "/nextpage/playlists/:playlistId", AsyncServlet.ofBlocking(executor, request -> {
|
||||
try {
|
||||
return getJsonResponse(
|
||||
ResponseHelper.playlistPageResponse(request.getPathParameter("playlistId"),
|
||||
request.getQueryParameter("nextpage")),
|
||||
"public, max-age=3600");
|
||||
} catch (Exception e) {
|
||||
return getErrorResponse(e);
|
||||
}
|
||||
})).map(GET, "/rss/playlists/:playlistId", AsyncServlet.ofBlocking(executor, request -> {
|
||||
try {
|
||||
return getJsonResponse(
|
||||
ResponseHelper.playlistRSSResponse(request.getPathParameter("playlistId")),
|
||||
"public, s-maxage=600");
|
||||
} catch (Exception e) {
|
||||
return getErrorResponse(e);
|
||||
}
|
||||
})).map(GET, "/suggestions", AsyncServlet.ofBlocking(executor, request -> {
|
||||
try {
|
||||
return getJsonResponse(ResponseHelper.suggestionsResponse(request.getQueryParameter("query")),
|
||||
"public, max-age=600");
|
||||
} catch (Exception e) {
|
||||
return getErrorResponse(e);
|
||||
}
|
||||
})).map(GET, "/search", AsyncServlet.ofBlocking(executor, request -> {
|
||||
try {
|
||||
return getJsonResponse(ResponseHelper.searchResponse(request.getQueryParameter("q"),
|
||||
request.getQueryParameter("filter")), "public, max-age=600");
|
||||
} catch (Exception e) {
|
||||
return getErrorResponse(e);
|
||||
}
|
||||
})).map(GET, "/nextpage/search", AsyncServlet.ofBlocking(executor, request -> {
|
||||
try {
|
||||
return getJsonResponse(
|
||||
ResponseHelper.searchPageResponse(request.getQueryParameter("q"),
|
||||
request.getQueryParameter("filter"), request.getQueryParameter("nextpage")),
|
||||
"public, max-age=3600");
|
||||
} catch (Exception e) {
|
||||
return getErrorResponse(e);
|
||||
}
|
||||
})).map(GET, "/trending", AsyncServlet.ofBlocking(executor, request -> {
|
||||
try {
|
||||
return getJsonResponse(ResponseHelper.trendingResponse(request.getQueryParameter("region")),
|
||||
"public, max-age=3600");
|
||||
} catch (Exception e) {
|
||||
return getErrorResponse(e);
|
||||
}
|
||||
})).map(GET, "/comments/:videoId", AsyncServlet.ofBlocking(executor, request -> {
|
||||
try {
|
||||
return getJsonResponse(ResponseHelper.commentsResponse(request.getPathParameter("videoId")),
|
||||
"public, max-age=1200");
|
||||
} catch (Exception e) {
|
||||
return getErrorResponse(e);
|
||||
}
|
||||
})).map(GET, "/nextpage/comments/:videoId", AsyncServlet.ofBlocking(executor, request -> {
|
||||
try {
|
||||
return getJsonResponse(ResponseHelper.commentsPageResponse(request.getPathParameter("videoId"),
|
||||
request.getQueryParameter("url")), "public, max-age=3600");
|
||||
} catch (Exception e) {
|
||||
return getErrorResponse(e);
|
||||
}
|
||||
})).map(POST, "/register", AsyncServlet.ofBlocking(executor, request -> {
|
||||
try {
|
||||
LoginRequest body = Constants.mapper.readValue(request.loadBody().getResult().asArray(),
|
||||
LoginRequest.class);
|
||||
return getJsonResponse(ResponseHelper.registerResponse(body.username, body.password),
|
||||
"private");
|
||||
} catch (Exception e) {
|
||||
return getErrorResponse(e);
|
||||
}
|
||||
})).map(POST, "/login", AsyncServlet.ofBlocking(executor, request -> {
|
||||
try {
|
||||
LoginRequest body = Constants.mapper.readValue(request.loadBody().getResult().asArray(),
|
||||
LoginRequest.class);
|
||||
return getJsonResponse(ResponseHelper.loginResponse(body.username, body.password), "private");
|
||||
} catch (Exception e) {
|
||||
return getErrorResponse(e);
|
||||
}
|
||||
})).map(POST, "/subscribe", AsyncServlet.ofBlocking(executor, request -> {
|
||||
try {
|
||||
SubscriptionUpdateRequest body = Constants.mapper
|
||||
.readValue(request.loadBody().getResult().asArray(), SubscriptionUpdateRequest.class);
|
||||
return getJsonResponse(
|
||||
ResponseHelper.subscribeResponse(request.getHeader(AUTHORIZATION), body.channelId),
|
||||
"private");
|
||||
} catch (Exception e) {
|
||||
return getErrorResponse(e);
|
||||
}
|
||||
})).map(POST, "/unsubscribe", AsyncServlet.ofBlocking(executor, request -> {
|
||||
try {
|
||||
SubscriptionUpdateRequest body = Constants.mapper
|
||||
.readValue(request.loadBody().getResult().asArray(), SubscriptionUpdateRequest.class);
|
||||
return getJsonResponse(
|
||||
ResponseHelper.unsubscribeResponse(request.getHeader(AUTHORIZATION), body.channelId),
|
||||
"private");
|
||||
} catch (Exception e) {
|
||||
return getErrorResponse(e);
|
||||
}
|
||||
})).map(GET, "/subscribed", AsyncServlet.ofBlocking(executor, request -> {
|
||||
try {
|
||||
return getJsonResponse(ResponseHelper.isSubscribedResponse(request.getHeader(AUTHORIZATION),
|
||||
request.getQueryParameter("channelId")), "private");
|
||||
} catch (Exception e) {
|
||||
return getErrorResponse(e);
|
||||
}
|
||||
})).map(GET, "/feed", AsyncServlet.ofBlocking(executor, request -> {
|
||||
try {
|
||||
return getJsonResponse(ResponseHelper.feedResponse(request.getQueryParameter("authToken")),
|
||||
"private");
|
||||
} catch (Exception e) {
|
||||
return getErrorResponse(e);
|
||||
}
|
||||
})).map(POST, "/import", AsyncServlet.ofBlocking(executor, request -> {
|
||||
try {
|
||||
String[] subscriptions = Constants.mapper.readValue(request.loadBody().getResult().asArray(),
|
||||
String[].class);
|
||||
return getJsonResponse(
|
||||
ResponseHelper.importResponse(request.getHeader(AUTHORIZATION), subscriptions),
|
||||
"private");
|
||||
} catch (Exception e) {
|
||||
return getErrorResponse(e);
|
||||
}
|
||||
}));
|
||||
|
||||
return new CustomServletDecorator(router);
|
||||
}
|
||||
|
@ -11,8 +11,8 @@ import org.schabi.newpipe.extractor.Page;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.mongodb.client.MongoClient;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import me.kavin.piped.utils.PageMixin;
|
||||
|
||||
public class Constants {
|
||||
@ -30,16 +30,18 @@ public class Constants {
|
||||
|
||||
public static final StreamingService YOUTUBE_SERVICE;
|
||||
|
||||
public static final String PUBLIC_URL;
|
||||
|
||||
public static final HttpClient h2client = HttpClient.newBuilder().followRedirects(Redirect.NORMAL)
|
||||
.version(Version.HTTP_2).build();
|
||||
public static final HttpClient h2_no_redir_client = HttpClient.newBuilder().followRedirects(Redirect.NEVER)
|
||||
.version(Version.HTTP_2).build();
|
||||
// public static final HttpClient h3client = Http3ClientBuilder.newBuilder().followRedirects(Redirect.NORMAL).build();
|
||||
|
||||
public static final MongoClient mongoClient;
|
||||
|
||||
public static final ObjectMapper mapper = new ObjectMapper().addMixIn(Page.class, PageMixin.class);
|
||||
|
||||
public static final Object2ObjectOpenHashMap<String, String> hibernateProperties = new Object2ObjectOpenHashMap<>();
|
||||
|
||||
static {
|
||||
Properties prop = new Properties();
|
||||
try {
|
||||
@ -51,7 +53,12 @@ public class Constants {
|
||||
PROXY_PART = prop.getProperty("PROXY_PART");
|
||||
CAPTCHA_BASE_URL = prop.getProperty("CAPTCHA_BASE_URL");
|
||||
CAPTCHA_API_KEY = prop.getProperty("CAPTCHA_API_KEY");
|
||||
mongoClient = null/* MongoClients.create(prop.getProperty("MONGO_URI")) */;
|
||||
PUBLIC_URL = prop.getProperty("API_URL");
|
||||
prop.forEach((_key, _value) -> {
|
||||
String key = String.valueOf(_key), value = String.valueOf(_value);
|
||||
if (key.startsWith("hibernate"))
|
||||
hibernateProperties.put(key, value);
|
||||
});
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package me.kavin.piped.utils;
|
||||
|
||||
import static io.activej.http.HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS;
|
||||
import static io.activej.http.HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@ -29,7 +30,8 @@ public class CustomServletDecorator implements AsyncServlet {
|
||||
|
||||
HttpHeaderValue headerValue = HttpHeaderValue.of("app;dur=" + (System.nanoTime() - before) / 1000000.0);
|
||||
|
||||
return response.withHeader(HEADER, headerValue).withHeader(ACCESS_CONTROL_ALLOW_ORIGIN, "*");
|
||||
return response.withHeader(HEADER, headerValue).withHeader(ACCESS_CONTROL_ALLOW_ORIGIN, "*")
|
||||
.withHeader(ACCESS_CONTROL_ALLOW_HEADERS, "*");
|
||||
|
||||
});
|
||||
}
|
||||
|
94
src/main/java/me/kavin/piped/utils/DatabaseHelper.java
Normal file
94
src/main/java/me/kavin/piped/utils/DatabaseHelper.java
Normal file
@ -0,0 +1,94 @@
|
||||
package me.kavin.piped.utils;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
|
||||
import javax.persistence.criteria.CriteriaBuilder;
|
||||
import javax.persistence.criteria.CriteriaQuery;
|
||||
import javax.persistence.criteria.JoinType;
|
||||
import javax.persistence.criteria.Root;
|
||||
|
||||
import org.hibernate.Session;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
import me.kavin.piped.utils.obj.db.Channel;
|
||||
import me.kavin.piped.utils.obj.db.PubSub;
|
||||
import me.kavin.piped.utils.obj.db.User;
|
||||
import me.kavin.piped.utils.obj.db.Video;
|
||||
|
||||
public class DatabaseHelper {
|
||||
|
||||
public static final User getUserFromSession(Session s, String session) {
|
||||
CriteriaBuilder cb = s.getCriteriaBuilder();
|
||||
CriteriaQuery<User> cr = cb.createQuery(User.class);
|
||||
Root<User> root = cr.from(User.class);
|
||||
cr.select(root).where(root.get("sessionId").in(session));
|
||||
|
||||
return s.createQuery(cr).uniqueResult();
|
||||
}
|
||||
|
||||
public static final User getUserFromSessionWithSubscribed(Session s, String session) {
|
||||
CriteriaBuilder cb = s.getCriteriaBuilder();
|
||||
CriteriaQuery<User> cr = cb.createQuery(User.class);
|
||||
Root<User> root = cr.from(User.class);
|
||||
root.fetch("subscribed_ids", JoinType.LEFT);
|
||||
cr.select(root).where(root.get("sessionId").in(session));
|
||||
|
||||
return s.createQuery(cr).uniqueResult();
|
||||
}
|
||||
|
||||
public static final Channel getChannelFromId(Session s, String id) {
|
||||
CriteriaBuilder cb = s.getCriteriaBuilder();
|
||||
CriteriaQuery<Channel> cr = cb.createQuery(Channel.class);
|
||||
Root<Channel> root = cr.from(Channel.class);
|
||||
cr.select(root).where(root.get("uploader_id").in(id));
|
||||
|
||||
return s.createQuery(cr).uniqueResult();
|
||||
}
|
||||
|
||||
public static final List<Video> getVideosFromChannelIds(Session s, List<String> id) {
|
||||
CriteriaBuilder cb = s.getCriteriaBuilder();
|
||||
CriteriaQuery<Video> cr = cb.createQuery(Video.class);
|
||||
Root<Video> root = cr.from(Video.class);
|
||||
root.fetch("channel", JoinType.LEFT);
|
||||
cr.select(root).where(root.get("channel").get("uploader_id").in(id));
|
||||
|
||||
return s.createQuery(cr).getResultList();
|
||||
}
|
||||
|
||||
public static final List<String> getGlobalSubscribedChannelIds(Session s) {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> subscriptions = new ObjectArrayList<>(
|
||||
new LinkedHashSet<>(s.createNativeQuery("select channel from users_subscribed").getResultList()));
|
||||
|
||||
return subscriptions;
|
||||
}
|
||||
|
||||
public static final Video getVideoFromId(Session s, String id) {
|
||||
CriteriaBuilder cb = s.getCriteriaBuilder();
|
||||
CriteriaQuery<Video> cr = cb.createQuery(Video.class);
|
||||
Root<Video> root = cr.from(Video.class);
|
||||
cr.select(root).where(root.get("id").in(id));
|
||||
|
||||
return s.createQuery(cr).uniqueResult();
|
||||
}
|
||||
|
||||
public static final PubSub getPubSubFromId(Session s, String id) {
|
||||
CriteriaBuilder cb = s.getCriteriaBuilder();
|
||||
CriteriaQuery<PubSub> cr = cb.createQuery(PubSub.class);
|
||||
Root<PubSub> root = cr.from(PubSub.class);
|
||||
cr.select(root).where(root.get("id").in(id));
|
||||
|
||||
return s.createQuery(cr).uniqueResult();
|
||||
}
|
||||
|
||||
public static final List<PubSub> getPubSubFromIds(Session s, List<String> id) {
|
||||
CriteriaBuilder cb = s.getCriteriaBuilder();
|
||||
CriteriaQuery<PubSub> cr = cb.createQuery(PubSub.class);
|
||||
Root<PubSub> root = cr.from(PubSub.class);
|
||||
cr.select(root).where(root.get("id").in(id));
|
||||
|
||||
return s.createQuery(cr).getResultList();
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package me.kavin.piped.utils;
|
||||
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.SessionFactory;
|
||||
import org.hibernate.cfg.Configuration;
|
||||
|
||||
import me.kavin.piped.consts.Constants;
|
||||
import me.kavin.piped.utils.obj.db.Channel;
|
||||
import me.kavin.piped.utils.obj.db.PubSub;
|
||||
import me.kavin.piped.utils.obj.db.User;
|
||||
import me.kavin.piped.utils.obj.db.Video;
|
||||
|
||||
public class DatabaseSessionFactory {
|
||||
|
||||
private static final SessionFactory sessionFactory;
|
||||
|
||||
static {
|
||||
|
||||
final Configuration configuration = new Configuration();
|
||||
|
||||
Constants.hibernateProperties.forEach((key, value) -> configuration.setProperty(key, value));
|
||||
configuration.setProperty("hibernate.temp.use_jdbc_metadata_defaults", "false");
|
||||
configuration.configure();
|
||||
|
||||
sessionFactory = configuration.addAnnotatedClass(User.class).addAnnotatedClass(Video.class)
|
||||
.addAnnotatedClass(Channel.class).addAnnotatedClass(PubSub.class).buildSessionFactory();
|
||||
}
|
||||
|
||||
public static final Session createSession() {
|
||||
return sessionFactory.openSession();
|
||||
}
|
||||
}
|
@ -5,15 +5,25 @@ import java.util.concurrent.Executors;
|
||||
|
||||
public class Multithreading {
|
||||
|
||||
private static final ExecutorService es = Executors.newFixedThreadPool(16);
|
||||
private static final ExecutorService es = Executors.newCachedThreadPool();
|
||||
private static final ExecutorService esLimited = Executors
|
||||
.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
|
||||
private static final ExecutorService esLimitedPubSub = Executors
|
||||
.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
|
||||
|
||||
public static void runAsync(final Runnable runnable) {
|
||||
es.submit(runnable);
|
||||
}
|
||||
|
||||
public static void runAsyncTimeout(final Runnable runnable) {
|
||||
public static void runAsyncLimited(final Runnable runnable) {
|
||||
esLimited.submit(runnable);
|
||||
}
|
||||
|
||||
es.submit(runnable);
|
||||
public static void runAsyncLimitedPubSub(final Runnable runnable) {
|
||||
esLimited.submit(runnable);
|
||||
}
|
||||
|
||||
public static ExecutorService getCachedExecutor() {
|
||||
return es;
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
package me.kavin.piped.utils;
|
||||
|
||||
public class RegisterRequest {
|
||||
|
||||
public String username, password;
|
||||
|
||||
}
|
@ -6,14 +6,23 @@ import java.net.URI;
|
||||
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.BodyHandlers;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.persistence.criteria.CriteriaBuilder;
|
||||
import javax.persistence.criteria.CriteriaQuery;
|
||||
import javax.persistence.criteria.Root;
|
||||
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.hibernate.Session;
|
||||
import org.json.JSONObject;
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.ListExtractor.InfoItemsPage;
|
||||
@ -33,6 +42,7 @@ import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem;
|
||||
import org.schabi.newpipe.extractor.search.SearchInfo;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.springframework.security.crypto.argon2.Argon2PasswordEncoder;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
@ -44,6 +54,7 @@ import com.rometools.rome.feed.synd.SyndFeedImpl;
|
||||
import com.rometools.rome.io.FeedException;
|
||||
import com.rometools.rome.io.SyndFeedOutput;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
import me.kavin.piped.consts.Constants;
|
||||
import me.kavin.piped.ipfs.IPFS;
|
||||
@ -51,6 +62,7 @@ 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;
|
||||
@ -58,10 +70,19 @@ 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.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.SearchItem;
|
||||
import me.kavin.piped.utils.obj.search.SearchPlaylist;
|
||||
import me.kavin.piped.utils.obj.search.SearchStream;
|
||||
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.IncorrectCredentialsResponse;
|
||||
import me.kavin.piped.utils.resp.LoginResponse;
|
||||
import me.kavin.piped.utils.resp.SubscribeStatusResponse;
|
||||
|
||||
public class ResponseHelper {
|
||||
|
||||
@ -144,6 +165,11 @@ public class ResponseHelper {
|
||||
info.getStreamSegments().forEach(
|
||||
segment -> segments.add(new ChapterSegment(segment.getTitle(), segment.getStartTimeSeconds())));
|
||||
|
||||
long time = info.getUploadDate().offsetDateTime().toInstant().toEpochMilli();
|
||||
|
||||
if (info.getUploadDate() != null && System.currentTimeMillis() - time < TimeUnit.DAYS.toMillis(10))
|
||||
updateViews(info.getId(), info.getViewCount(), time, false);
|
||||
|
||||
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(),
|
||||
@ -167,6 +193,25 @@ public class ResponseHelper {
|
||||
item.getTextualUploadDate(), item.getDuration(), item.getViewCount()));
|
||||
});
|
||||
|
||||
Multithreading.runAsync(() -> {
|
||||
Session s = DatabaseSessionFactory.createSession();
|
||||
|
||||
me.kavin.piped.utils.obj.db.Channel channel = DatabaseHelper.getChannelFromId(s, info.getId());
|
||||
|
||||
if (channel != null) {
|
||||
for (StreamInfoItem item : info.getRelatedItems()) {
|
||||
long time = item.getUploadDate() != null
|
||||
? item.getUploadDate().offsetDateTime().toInstant().toEpochMilli()
|
||||
: System.currentTimeMillis();
|
||||
if (System.currentTimeMillis() - time < TimeUnit.DAYS.toMillis(10))
|
||||
updateViews(item.getUrl().substring("https://www.youtube.com/watch?v=".length()),
|
||||
item.getViewCount(), time, true);
|
||||
}
|
||||
}
|
||||
|
||||
s.close();
|
||||
});
|
||||
|
||||
String nextpage = null;
|
||||
if (info.hasNextPage()) {
|
||||
Page page = info.getNextPage();
|
||||
@ -461,9 +506,288 @@ public class ResponseHelper {
|
||||
|
||||
}
|
||||
|
||||
public static final byte[] registerResponse(String user, String pass) throws IOException {
|
||||
private static final Argon2PasswordEncoder argon2PasswordEncoder = new Argon2PasswordEncoder();
|
||||
|
||||
return Constants.mapper.writeValueAsBytes(null);
|
||||
public static final byte[] registerResponse(String user, String pass)
|
||||
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
|
||||
|
||||
user = user.toLowerCase();
|
||||
|
||||
Session s = DatabaseSessionFactory.createSession();
|
||||
CriteriaBuilder cb = s.getCriteriaBuilder();
|
||||
CriteriaQuery<User> cr = cb.createQuery(User.class);
|
||||
Root<User> root = cr.from(User.class);
|
||||
cr.select(root).where(root.get("username").in(user));
|
||||
boolean registered = s.createQuery(cr).uniqueResult() != null;
|
||||
|
||||
if (registered) {
|
||||
s.close();
|
||||
return Constants.mapper.writeValueAsBytes(new AlreadyRegisteredResponse());
|
||||
}
|
||||
|
||||
User newuser = new User(user, argon2PasswordEncoder.encode(pass), Collections.emptyList());
|
||||
|
||||
s.save(newuser);
|
||||
s.getTransaction().begin();
|
||||
s.getTransaction().commit();
|
||||
|
||||
s.close();
|
||||
|
||||
return Constants.mapper.writeValueAsBytes(new LoginResponse(newuser.getSessionId()));
|
||||
|
||||
}
|
||||
|
||||
public static final byte[] loginResponse(String user, String pass)
|
||||
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
|
||||
|
||||
user = user.toLowerCase();
|
||||
|
||||
Session s = DatabaseSessionFactory.createSession();
|
||||
CriteriaBuilder cb = s.getCriteriaBuilder();
|
||||
CriteriaQuery<User> cr = cb.createQuery(User.class);
|
||||
Root<User> root = cr.from(User.class);
|
||||
cr.select(root).where(root.get("username").in(user));
|
||||
|
||||
User dbuser = s.createQuery(cr).uniqueResult();
|
||||
|
||||
if (dbuser != null && argon2PasswordEncoder.matches(pass, dbuser.getPassword())) {
|
||||
s.close();
|
||||
return Constants.mapper.writeValueAsBytes(new LoginResponse(dbuser.getSessionId()));
|
||||
}
|
||||
|
||||
s.close();
|
||||
|
||||
return Constants.mapper.writeValueAsBytes(new IncorrectCredentialsResponse());
|
||||
|
||||
}
|
||||
|
||||
public static final byte[] subscribeResponse(String session, String channelId)
|
||||
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
|
||||
|
||||
Session s = DatabaseSessionFactory.createSession();
|
||||
|
||||
User user = DatabaseHelper.getUserFromSessionWithSubscribed(s, session);
|
||||
|
||||
if (user != null) {
|
||||
if (!user.getSubscribed().contains(channelId)) {
|
||||
|
||||
s.getTransaction().begin();
|
||||
s.createNativeQuery("insert into users_subscribed (subscriber, channel) values (?,?)")
|
||||
.setParameter(1, user.getId()).setParameter(2, channelId).executeUpdate();
|
||||
s.getTransaction().commit();
|
||||
s.close();
|
||||
|
||||
Multithreading.runAsync(() -> {
|
||||
Session sess = DatabaseSessionFactory.createSession();
|
||||
me.kavin.piped.utils.obj.db.Channel channel = DatabaseHelper.getChannelFromId(sess, channelId);
|
||||
|
||||
if (channel == null) {
|
||||
ChannelInfo info = null;
|
||||
|
||||
try {
|
||||
info = ChannelInfo.getInfo("https://youtube.com/channel/" + channelId);
|
||||
} catch (IOException | ExtractionException e) {
|
||||
ExceptionUtils.rethrow(e);
|
||||
}
|
||||
|
||||
channel = new me.kavin.piped.utils.obj.db.Channel(channelId, info.getName(),
|
||||
info.getAvatarUrl(), false);
|
||||
sess.save(channel);
|
||||
sess.beginTransaction().commit();
|
||||
|
||||
try {
|
||||
Session sessSub = DatabaseSessionFactory.createSession();
|
||||
subscribePubSub(channelId, sessSub);
|
||||
sessSub.close();
|
||||
} catch (IOException | InterruptedException e) {
|
||||
ExceptionUtils.rethrow(e);
|
||||
}
|
||||
|
||||
for (StreamInfoItem item : info.getRelatedItems()) {
|
||||
long time = item.getUploadDate() != null
|
||||
? item.getUploadDate().offsetDateTime().toInstant().toEpochMilli()
|
||||
: System.currentTimeMillis();
|
||||
if ((System.currentTimeMillis() - time) < TimeUnit.DAYS.toMillis(10))
|
||||
handleNewVideo(item.getUrl(), time, channel, s);
|
||||
}
|
||||
}
|
||||
|
||||
sess.close();
|
||||
});
|
||||
}
|
||||
|
||||
return Constants.mapper.writeValueAsBytes(new AcceptedResponse());
|
||||
}
|
||||
|
||||
s.close();
|
||||
|
||||
return Constants.mapper.writeValueAsBytes(new AuthenticationFailureResponse());
|
||||
|
||||
}
|
||||
|
||||
public static final byte[] unsubscribeResponse(String session, String channelId)
|
||||
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
|
||||
|
||||
Session s = DatabaseSessionFactory.createSession();
|
||||
|
||||
User user = DatabaseHelper.getUserFromSession(s, session);
|
||||
|
||||
if (user != null) {
|
||||
s.getTransaction().begin();
|
||||
s.createNativeQuery("delete from users_subscribed where subscriber = :id and channel = :channel")
|
||||
.setParameter("id", user.getId()).setParameter("channel", channelId).executeUpdate();
|
||||
s.getTransaction().commit();
|
||||
s.close();
|
||||
return Constants.mapper.writeValueAsBytes(new AcceptedResponse());
|
||||
}
|
||||
|
||||
s.close();
|
||||
|
||||
return Constants.mapper.writeValueAsBytes(new AuthenticationFailureResponse());
|
||||
|
||||
}
|
||||
|
||||
public static final byte[] isSubscribedResponse(String session, String channelId)
|
||||
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
|
||||
|
||||
Session s = DatabaseSessionFactory.createSession();
|
||||
|
||||
User user = DatabaseHelper.getUserFromSessionWithSubscribed(s, session);
|
||||
|
||||
if (user != null) {
|
||||
if (user.getSubscribed().contains(channelId)) {
|
||||
s.close();
|
||||
return Constants.mapper.writeValueAsBytes(new SubscribeStatusResponse(true));
|
||||
}
|
||||
s.close();
|
||||
return Constants.mapper.writeValueAsBytes(new SubscribeStatusResponse(false));
|
||||
}
|
||||
|
||||
s.close();
|
||||
|
||||
return Constants.mapper.writeValueAsBytes(new AuthenticationFailureResponse());
|
||||
|
||||
}
|
||||
|
||||
public static final byte[] feedResponse(String session)
|
||||
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
|
||||
|
||||
Session s = DatabaseSessionFactory.createSession();
|
||||
|
||||
User user = DatabaseHelper.getUserFromSessionWithSubscribed(s, session);
|
||||
|
||||
if (user != null) {
|
||||
|
||||
List<FeedItem> feedItems = new ObjectArrayList<>();
|
||||
|
||||
if (user.getSubscribed() != null && !user.getSubscribed().isEmpty()) {
|
||||
|
||||
List<Video> videos = DatabaseHelper.getVideosFromChannelIds(s, user.getSubscribed());
|
||||
|
||||
videos.forEach(video -> {
|
||||
feedItems.add(new FeedItem(video.getId(), video.getTitle(), rewriteURL(video.getThumbnail()),
|
||||
video.getChannel().getUploaderId(), video.getChannel().getUploader(),
|
||||
rewriteURL(video.getChannel().getUploaderAvatar()), video.getViews(), video.getDuration(),
|
||||
video.getUploaded(), video.getChannel().isVerified()));
|
||||
});
|
||||
|
||||
Collections.sort(feedItems, (a, b) -> (int) (b.uploaded - a.uploaded));
|
||||
}
|
||||
|
||||
s.close();
|
||||
|
||||
return Constants.mapper.writeValueAsBytes(feedItems);
|
||||
}
|
||||
|
||||
s.close();
|
||||
|
||||
return Constants.mapper.writeValueAsBytes(new AuthenticationFailureResponse());
|
||||
|
||||
}
|
||||
|
||||
public static final byte[] importResponse(String session, String[] channelIds)
|
||||
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
|
||||
|
||||
Session s = DatabaseSessionFactory.createSession();
|
||||
|
||||
User user = DatabaseHelper.getUserFromSessionWithSubscribed(s, session);
|
||||
|
||||
if (user != null) {
|
||||
|
||||
Multithreading.runAsync(() -> {
|
||||
for (String channelId : channelIds)
|
||||
if (!user.getSubscribed().contains(channelId))
|
||||
user.getSubscribed().add(channelId);
|
||||
|
||||
if (channelIds.length > 0) {
|
||||
s.update(user);
|
||||
s.beginTransaction().commit();
|
||||
}
|
||||
|
||||
s.close();
|
||||
});
|
||||
|
||||
for (String channelId : channelIds) {
|
||||
|
||||
Multithreading.runAsyncLimited(() -> {
|
||||
try {
|
||||
|
||||
Session sess = DatabaseSessionFactory.createSession();
|
||||
|
||||
me.kavin.piped.utils.obj.db.Channel channel = DatabaseHelper.getChannelFromId(sess, channelId);
|
||||
|
||||
if (channel == null) {
|
||||
ChannelInfo info = null;
|
||||
|
||||
try {
|
||||
info = ChannelInfo.getInfo("https://youtube.com/channel/" + channelId);
|
||||
} catch (Exception e) {
|
||||
return;
|
||||
}
|
||||
|
||||
channel = new me.kavin.piped.utils.obj.db.Channel(channelId, info.getName(),
|
||||
info.getAvatarUrl(), false);
|
||||
sess.save(channel);
|
||||
|
||||
Multithreading.runAsync(() -> {
|
||||
try {
|
||||
Session sessSub = DatabaseSessionFactory.createSession();
|
||||
subscribePubSub(channelId, sessSub);
|
||||
sessSub.close();
|
||||
} catch (IOException | InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
|
||||
for (StreamInfoItem item : info.getRelatedItems()) {
|
||||
long time = item.getUploadDate() != null
|
||||
? item.getUploadDate().offsetDateTime().toInstant().toEpochMilli()
|
||||
: System.currentTimeMillis();
|
||||
if ((System.currentTimeMillis() - time) < TimeUnit.DAYS.toMillis(10))
|
||||
handleNewVideo(item.getUrl(), time, channel, sess);
|
||||
}
|
||||
|
||||
if (!sess.getTransaction().isActive())
|
||||
sess.getTransaction().begin();
|
||||
sess.getTransaction().commit();
|
||||
}
|
||||
|
||||
sess.close();
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
return Constants.mapper.writeValueAsBytes(new AcceptedResponse());
|
||||
}
|
||||
|
||||
s.close();
|
||||
|
||||
return Constants.mapper.writeValueAsBytes(new AuthenticationFailureResponse());
|
||||
|
||||
}
|
||||
|
||||
@ -486,6 +810,115 @@ public class ResponseHelper {
|
||||
|
||||
}
|
||||
|
||||
public static void handleNewVideo(String url, long time, me.kavin.piped.utils.obj.db.Channel channel, Session s) {
|
||||
try {
|
||||
handleNewVideo(StreamInfo.getInfo(url), time, channel, s);
|
||||
} catch (IOException | ExtractionException e) {
|
||||
ExceptionUtils.rethrow(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void handleNewVideo(StreamInfo info, long time, me.kavin.piped.utils.obj.db.Channel channel,
|
||||
Session s) {
|
||||
|
||||
if (channel == null)
|
||||
channel = DatabaseHelper.getChannelFromId(s,
|
||||
info.getUploaderUrl().substring("https://www.youtube.com/channel/".length()));
|
||||
|
||||
long infoTime = info.getUploadDate() != null ? info.getUploadDate().offsetDateTime().toInstant().toEpochMilli()
|
||||
: System.currentTimeMillis();
|
||||
|
||||
Video video = null;
|
||||
|
||||
if (channel != null && (video = DatabaseHelper.getVideoFromId(s, info.getId())) == null
|
||||
&& (System.currentTimeMillis() - infoTime) < TimeUnit.DAYS.toMillis(10)) {
|
||||
|
||||
video = new Video(info.getId(), info.getName(), info.getViewCount(), info.getDuration(),
|
||||
Math.max(infoTime, time), info.getThumbnailUrl(), channel);
|
||||
|
||||
s.save(video);
|
||||
|
||||
if (!s.getTransaction().isActive())
|
||||
s.getTransaction().begin();
|
||||
s.getTransaction().commit();
|
||||
} else if (video != null) {
|
||||
video.setViews(info.getViewCount());
|
||||
|
||||
s.update(video);
|
||||
|
||||
s.getTransaction().commit();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static void updateViews(String id, long views, long time, boolean addIfNonExistent) {
|
||||
Multithreading.runAsync(() -> {
|
||||
try {
|
||||
Session s = DatabaseSessionFactory.createSession();
|
||||
|
||||
Video video = DatabaseHelper.getVideoFromId(s, id);
|
||||
|
||||
if (video != null) {
|
||||
video.setViews(views);
|
||||
s.update(video);
|
||||
s.beginTransaction().commit();
|
||||
} else if (addIfNonExistent)
|
||||
handleNewVideo("https://www.youtube.com/watch?v=" + id, time, null, s);
|
||||
|
||||
s.close();
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void subscribePubSub(String channelId, Session s) throws IOException, InterruptedException {
|
||||
|
||||
PubSub pubsub = DatabaseHelper.getPubSubFromId(s, channelId);
|
||||
|
||||
if (pubsub == null || System.currentTimeMillis() - pubsub.getSubbedAt() > TimeUnit.DAYS.toMillis(4)) {
|
||||
System.out.println(String.format("PubSub: Subscribing to %s", channelId));
|
||||
|
||||
String callback = Constants.PUBLIC_URL + "/webhooks/pubsub";
|
||||
String topic = "https://www.youtube.com/xml/feeds/videos.xml?channel_id=" + channelId;
|
||||
|
||||
Builder builder = HttpRequest.newBuilder(URI.create("https://pubsubhubbub.appspot.com/subscribe"));
|
||||
|
||||
Map<String, String> formParams = new Object2ObjectOpenHashMap<>();
|
||||
StringBuilder formBody = new StringBuilder();
|
||||
|
||||
builder.header("content-type", "application/x-www-form-urlencoded");
|
||||
|
||||
formParams.put("hub.callback", callback);
|
||||
formParams.put("hub.topic", topic);
|
||||
formParams.put("hub.verify", "async");
|
||||
formParams.put("hub.mode", "subscribe");
|
||||
formParams.put("hub.lease_seconds", "432000");
|
||||
|
||||
formParams.forEach((name, value) -> {
|
||||
formBody.append(name + "=" + URLUtils.silentEncode(value) + "&");
|
||||
});
|
||||
|
||||
builder.method("POST",
|
||||
BodyPublishers.ofString(String.valueOf(formBody.substring(0, formBody.length() - 1))));
|
||||
|
||||
Constants.h2client.send(builder.build(), BodyHandlers.ofInputStream());
|
||||
|
||||
if (pubsub == null)
|
||||
pubsub = new PubSub(channelId, System.currentTimeMillis());
|
||||
else
|
||||
pubsub.setSubbedAt(System.currentTimeMillis());
|
||||
|
||||
s.saveOrUpdate(pubsub);
|
||||
|
||||
if (!s.getTransaction().isActive())
|
||||
s.getTransaction().begin();
|
||||
s.getTransaction().commit();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final String optionalSubstring(String s, int index) {
|
||||
return s == null || s.isEmpty() ? null : s.substring(index);
|
||||
}
|
||||
|
24
src/main/java/me/kavin/piped/utils/obj/FeedItem.java
Normal file
24
src/main/java/me/kavin/piped/utils/obj/FeedItem.java
Normal file
@ -0,0 +1,24 @@
|
||||
package me.kavin.piped.utils.obj;
|
||||
|
||||
public class FeedItem {
|
||||
|
||||
public String id, title, thumbnail, uploader_id, uploader, uploaderAvatar;
|
||||
|
||||
public long views, duration, uploaded;
|
||||
|
||||
public boolean verified;
|
||||
|
||||
public FeedItem(String id, String title, String thumbnail, String uploader_id, String uploader,
|
||||
String uploaderAvatar, long views, long duration, long uploaded, boolean verified) {
|
||||
this.id = id;
|
||||
this.title = title;
|
||||
this.thumbnail = thumbnail;
|
||||
this.uploader_id = uploader_id;
|
||||
this.uploader = uploader;
|
||||
this.uploaderAvatar = uploaderAvatar;
|
||||
this.views = views;
|
||||
this.duration = duration;
|
||||
this.uploaded = uploaded;
|
||||
this.verified = verified;
|
||||
}
|
||||
}
|
67
src/main/java/me/kavin/piped/utils/obj/db/Channel.java
Normal file
67
src/main/java/me/kavin/piped/utils/obj/db/Channel.java
Normal file
@ -0,0 +1,67 @@
|
||||
package me.kavin.piped.utils.obj.db;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Index;
|
||||
import javax.persistence.Table;
|
||||
|
||||
@Entity
|
||||
@Table(name = "channels", indexes = { @Index(columnList = "uploader_id", name = "channels_uploader_id_idx") })
|
||||
public class Channel {
|
||||
|
||||
@Id
|
||||
@Column(name = "uploader_id", length = 30)
|
||||
private String uploader_id;
|
||||
|
||||
@Column(name = "uploader", length = 80)
|
||||
private String uploader;
|
||||
|
||||
@Column(name = "uploader_avatar", length = 150)
|
||||
private String uploaderAvatar;
|
||||
|
||||
@Column(name = "verified")
|
||||
private boolean verified;
|
||||
|
||||
public Channel() {
|
||||
}
|
||||
|
||||
public Channel(String uploader_id, String uploader, String uploaderAvatar, boolean verified) {
|
||||
this.uploader_id = uploader_id;
|
||||
this.uploader = uploader;
|
||||
this.uploaderAvatar = uploaderAvatar;
|
||||
this.verified = verified;
|
||||
}
|
||||
|
||||
public String getUploaderId() {
|
||||
return uploader_id;
|
||||
}
|
||||
|
||||
public void setUploaderId(String uploader_id) {
|
||||
this.uploader_id = uploader_id;
|
||||
}
|
||||
|
||||
public String getUploader() {
|
||||
return uploader;
|
||||
}
|
||||
|
||||
public void setUploader(String uploader) {
|
||||
this.uploader = uploader;
|
||||
}
|
||||
|
||||
public String getUploaderAvatar() {
|
||||
return uploaderAvatar;
|
||||
}
|
||||
|
||||
public void setUploaderAvatar(String uploaderAvatar) {
|
||||
this.uploaderAvatar = uploaderAvatar;
|
||||
}
|
||||
|
||||
public boolean isVerified() {
|
||||
return verified;
|
||||
}
|
||||
|
||||
public void setVerified(boolean verified) {
|
||||
this.verified = verified;
|
||||
}
|
||||
}
|
43
src/main/java/me/kavin/piped/utils/obj/db/PubSub.java
Normal file
43
src/main/java/me/kavin/piped/utils/obj/db/PubSub.java
Normal file
@ -0,0 +1,43 @@
|
||||
package me.kavin.piped.utils.obj.db;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Index;
|
||||
import javax.persistence.Table;
|
||||
|
||||
@Entity
|
||||
@Table(name = "pubsub", indexes = { @Index(columnList = "id", name = "id_idx") })
|
||||
public class PubSub {
|
||||
|
||||
@Id
|
||||
@Column(name = "id", unique = true, length = 24)
|
||||
private String id;
|
||||
|
||||
@Column(name = "subbed_at")
|
||||
private long subbedAt;
|
||||
|
||||
public PubSub() {
|
||||
}
|
||||
|
||||
public PubSub(String id, long subbedAt) {
|
||||
this.id = id;
|
||||
this.subbedAt = subbedAt;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public long getSubbedAt() {
|
||||
return subbedAt;
|
||||
}
|
||||
|
||||
public void setSubbedAt(long subbedAt) {
|
||||
this.subbedAt = subbedAt;
|
||||
}
|
||||
}
|
93
src/main/java/me/kavin/piped/utils/obj/db/User.java
Normal file
93
src/main/java/me/kavin/piped/utils/obj/db/User.java
Normal file
@ -0,0 +1,93 @@
|
||||
package me.kavin.piped.utils.obj.db;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.persistence.CollectionTable;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.ElementCollection;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Index;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.Table;
|
||||
|
||||
@Entity
|
||||
@Table(name = "users", indexes = { @Index(columnList = "id", name = "users_id_idx"),
|
||||
@Index(columnList = "username", name = "username_idx") })
|
||||
public class User implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "id")
|
||||
private long id;
|
||||
|
||||
@Column(name = "username", unique = true, length = 24)
|
||||
private String username;
|
||||
|
||||
@Column(name = "password", columnDefinition = "text")
|
||||
private String password;
|
||||
|
||||
@Column(name = "session_id", length = 36)
|
||||
private String sessionId;
|
||||
|
||||
@ElementCollection
|
||||
@CollectionTable(name = "users_subscribed", joinColumns = @JoinColumn(name = "subscriber"), indexes = @Index(columnList = "subscriber", name = "subscriber_idx"))
|
||||
@Column(name = "channel", length = 30)
|
||||
private List<String> subscribed_ids;
|
||||
|
||||
public User() {
|
||||
}
|
||||
|
||||
public User(String username, String password, List<String> subscribed_ids) {
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.subscribed_ids = subscribed_ids;
|
||||
this.sessionId = String.valueOf(UUID.randomUUID());
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public String getSessionId() {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
public void setSessionId(String sessionId) {
|
||||
this.sessionId = sessionId;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public List<String> getSubscribed() {
|
||||
return subscribed_ids;
|
||||
}
|
||||
|
||||
public void setSubscribed(List<String> subscribed_ids) {
|
||||
this.subscribed_ids = subscribed_ids;
|
||||
}
|
||||
}
|
108
src/main/java/me/kavin/piped/utils/obj/db/Video.java
Normal file
108
src/main/java/me/kavin/piped/utils/obj/db/Video.java
Normal file
@ -0,0 +1,108 @@
|
||||
package me.kavin.piped.utils.obj.db;
|
||||
|
||||
import javax.persistence.Column;
|
||||
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
|
||||
@Table(name = "videos", indexes = { @Index(columnList = "id", name = "videos_id_idx"),
|
||||
@Index(columnList = "uploader_id", name = "video_uploader_id_idx") })
|
||||
public class Video {
|
||||
|
||||
@Id
|
||||
@Column(name = "id", unique = true, length = 16)
|
||||
private String id;
|
||||
|
||||
@Column(name = "title", length = 100)
|
||||
private String title;
|
||||
|
||||
@Column(name = "views")
|
||||
private long views;
|
||||
|
||||
@Column(name = "duration")
|
||||
private long duration;
|
||||
|
||||
@Column(name = "uploaded")
|
||||
private long uploaded;
|
||||
|
||||
@Column(name = "thumbnail", length = 150)
|
||||
private String thumbnail;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "uploader_id")
|
||||
private Channel channel;
|
||||
|
||||
public Video() {
|
||||
}
|
||||
|
||||
public Video(String id, String title, long views, long duration, long uploaded, String thumbnail, Channel channel) {
|
||||
this.id = id;
|
||||
this.title = title;
|
||||
this.views = views;
|
||||
this.duration = duration;
|
||||
this.uploaded = uploaded;
|
||||
this.thumbnail = thumbnail;
|
||||
this.channel = channel;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public long getViews() {
|
||||
return views;
|
||||
}
|
||||
|
||||
public void setViews(long views) {
|
||||
this.views = views;
|
||||
}
|
||||
|
||||
public long getDuration() {
|
||||
return duration;
|
||||
}
|
||||
|
||||
public void setDuration(long duration) {
|
||||
this.duration = duration;
|
||||
}
|
||||
|
||||
public long getUploaded() {
|
||||
return uploaded;
|
||||
}
|
||||
|
||||
public void setUploaded(long uploaded) {
|
||||
this.uploaded = uploaded;
|
||||
}
|
||||
|
||||
public String getThumbnail() {
|
||||
return thumbnail;
|
||||
}
|
||||
|
||||
public void setThumbnail(String thumbnail) {
|
||||
this.thumbnail = thumbnail;
|
||||
}
|
||||
|
||||
public Channel getChannel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
public void setChannel(Channel channel) {
|
||||
this.channel = channel;
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package me.kavin.piped.utils.resp;
|
||||
|
||||
public class AcceptedResponse {
|
||||
|
||||
public String message = "ok";
|
||||
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package me.kavin.piped.utils.resp;
|
||||
|
||||
public class AlreadyRegisteredResponse {
|
||||
|
||||
public String error = "The username you have used is already taken.";
|
||||
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package me.kavin.piped.utils.resp;
|
||||
|
||||
public class AuthenticationFailureResponse {
|
||||
|
||||
public String error = "An invalid Session ID was provided.";
|
||||
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package me.kavin.piped.utils.resp;
|
||||
|
||||
public class IncorrectCredentialsResponse {
|
||||
|
||||
public String error = "The username or password you have entered is incorrect.";
|
||||
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package me.kavin.piped.utils.resp;
|
||||
|
||||
public class LoginRequest {
|
||||
|
||||
public String username, password;
|
||||
|
||||
}
|
10
src/main/java/me/kavin/piped/utils/resp/LoginResponse.java
Normal file
10
src/main/java/me/kavin/piped/utils/resp/LoginResponse.java
Normal file
@ -0,0 +1,10 @@
|
||||
package me.kavin.piped.utils.resp;
|
||||
|
||||
public class LoginResponse {
|
||||
|
||||
public String token;
|
||||
|
||||
public LoginResponse(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package me.kavin.piped.utils.resp;
|
||||
|
||||
public class SubscribeStatusResponse {
|
||||
|
||||
public boolean subscribed;
|
||||
|
||||
public SubscribeStatusResponse(boolean subscribed) {
|
||||
this.subscribed = subscribed;
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package me.kavin.piped.utils.resp;
|
||||
|
||||
public class SubscriptionUpdateRequest {
|
||||
|
||||
public String channelId;
|
||||
|
||||
}
|
16
src/main/resources/hibernate.cfg.xml
Normal file
16
src/main/resources/hibernate.cfg.xml
Normal file
@ -0,0 +1,16 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<!DOCTYPE hibernate-configuration PUBLIC
|
||||
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
|
||||
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
|
||||
<hibernate-configuration>
|
||||
<session-factory>
|
||||
<property name="hibernate.hbm2ddl.auto">update</property>
|
||||
<!-- Optional: Show SQL output for debugging -->
|
||||
<property name="hibernate.show_sql">false</property>
|
||||
<property name="hibernate.format_sql">true</property>
|
||||
|
||||
<property name="hibernate.connection.provider_class">org.hibernate.hikaricp.internal.HikariCPConnectionProvider</property>
|
||||
<property name="hibernate.connection.handling_mode">DELAYED_ACQUISITION_AND_RELEASE_AFTER_STATEMENT</property>
|
||||
<property name="hibernate.jdbc.batch_size">50</property>
|
||||
</session-factory>
|
||||
</hibernate-configuration>
|
Loading…
Reference in New Issue
Block a user