diff --git a/src/main/java/me/kavin/piped/ServerLauncher.java b/src/main/java/me/kavin/piped/ServerLauncher.java index 120bc44..a1750ef 100644 --- a/src/main/java/me/kavin/piped/ServerLauncher.java +++ b/src/main/java/me/kavin/piped/ServerLauncher.java @@ -278,6 +278,19 @@ public class ServerLauncher extends MultithreadedHttpServerLauncher { } catch (Exception e) { return getErrorResponse(e, request.getPath()); } + })).map(POST, "/user/playlists/create", AsyncServlet.ofBlocking(executor, request -> { + try { + var name = Constants.mapper.readTree(request.loadBody().getResult().asArray()).get("name").asText(); + return getJsonResponse(ResponseHelper.createPlaylist(request.getHeader(AUTHORIZATION), name), "private"); + } catch (Exception e) { + return getErrorResponse(e, request.getPath()); + } + })).map(GET, "/user/playlists", AsyncServlet.ofBlocking(executor, request -> { + try { + return getJsonResponse(ResponseHelper.playlistsResponse(request.getHeader(AUTHORIZATION)), "private"); + } catch (Exception e) { + return getErrorResponse(e, request.getPath()); + } })).map(GET, "/registered/badge", AsyncServlet.ofBlocking(executor, request -> { try { return HttpResponse.ofCode(302).withHeader(LOCATION, ResponseHelper.registeredBadgeRedirect()) diff --git a/src/main/java/me/kavin/piped/utils/ResponseHelper.java b/src/main/java/me/kavin/piped/utils/ResponseHelper.java index 16306f8..6a8dd24 100644 --- a/src/main/java/me/kavin/piped/utils/ResponseHelper.java +++ b/src/main/java/me/kavin/piped/utils/ResponseHelper.java @@ -1,6 +1,7 @@ package me.kavin.piped.utils; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.grack.nanojson.JsonObject; import com.grack.nanojson.JsonWriter; import com.rometools.rome.feed.synd.*; @@ -278,7 +279,46 @@ public class ResponseHelper { return Constants.mapper.writeValueAsBytes(relatedStreams); } - public static byte[] playlistResponse(String playlistId) + public static byte[] playlistResponse(String playlistId) throws ExtractionException, IOException { + + if (StringUtils.isBlank(playlistId)) + return Constants.mapper.writeValueAsBytes(new InvalidRequestResponse()); + + if (playlistId.matches("[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}")) + return playlistPipedResponse(playlistId); + + return playlistYouTubeResponse(playlistId); + } + + private static byte[] playlistPipedResponse(String playlistId) throws IOException { + try (Session s = DatabaseSessionFactory.createSession()) { + var cb = s.getCriteriaBuilder(); + var cq = cb.createQuery(me.kavin.piped.utils.obj.db.Playlist.class); + var root = cq.from(me.kavin.piped.utils.obj.db.Playlist.class); + root.fetch("videos", JoinType.LEFT); + root.fetch("owner", JoinType.LEFT); + cq.select(root); + cq.where(cb.equal(root.get("playlist_id"), UUID.fromString(playlistId))); + var query = s.createQuery(cq); + var pl = query.getSingleResult(); + + final List relatedStreams = new ObjectArrayList<>(); + + for (var video : pl.getVideos()) { + var channel = video.getChannel(); + relatedStreams.add(new StreamItem("/watch?v=" + video.getId(), video.getTitle(), rewriteURL(video.getThumbnail()), channel.getUploader(), + "/channel/" + channel.getUploaderId(), rewriteURL(channel.getUploaderAvatar()), null, null, + video.getDuration(), -1, -1, channel.isVerified())); + } + + final Playlist playlist = new Playlist(pl.getName(), rewriteURL(pl.getThumbnail()), null, null, pl.getOwner().getUsername(), + null, null, -1, relatedStreams); + + return Constants.mapper.writeValueAsBytes(playlist); + } + } + + private static byte[] playlistYouTubeResponse(String playlistId) throws IOException, ExtractionException { final PlaylistInfo info = PlaylistInfo.getInfo("https://www.youtube.com/playlist?list=" + playlistId); @@ -910,6 +950,55 @@ public class ResponseHelper { } + public static byte[] createPlaylist(String session, String name) throws IOException { + + if (StringUtils.isBlank(name)) + return Constants.mapper.writeValueAsBytes(new InvalidRequestResponse()); + + User user = DatabaseHelper.getUserFromSession(session); + + if (user == null) + return Constants.mapper.writeValueAsBytes(new AuthenticationFailureResponse()); + + try (Session s = DatabaseSessionFactory.createSession()) { + var playlist = new me.kavin.piped.utils.obj.db.Playlist(name, user, "https://i.ytimg.com/"); + s.save(playlist); + s.getTransaction().begin(); + s.getTransaction().commit(); + } + + return Constants.mapper.writeValueAsBytes(new AcceptedResponse()); + } + + public static byte[] playlistsResponse(String session) throws IOException { + + User user = DatabaseHelper.getUserFromSession(session); + + if (user == null) + return Constants.mapper.writeValueAsBytes(new AuthenticationFailureResponse()); + + try (Session s = DatabaseSessionFactory.createSession()) { + var cb = s.getCriteriaBuilder(); + var query = cb.createQuery(me.kavin.piped.utils.obj.db.Playlist.class); + var root = query.from(me.kavin.piped.utils.obj.db.Playlist.class); + query.select(root); + query.where(cb.equal(root.get("owner"), user)); + + var playlists = new ObjectArrayList<>(); + + for (var playlist : s.createQuery(query).list()) { + ObjectNode node = Constants.mapper.createObjectNode(); + node.put("id", String.valueOf(playlist.getPlaylistId())); + node.put("name", playlist.getName()); + node.put("shortDescription", playlist.getShortDescription()); + node.put("thumbnail", rewriteURL(playlist.getThumbnail())); + playlists.add(node); + } + + return Constants.mapper.writeValueAsBytes(playlists); + } + } + public static String registeredBadgeRedirect() { try (Session s = DatabaseSessionFactory.createSession()) { long registered = (Long) s.createQuery("select count(*) from User").uniqueResult(); diff --git a/src/main/java/me/kavin/piped/utils/obj/db/Playlist.java b/src/main/java/me/kavin/piped/utils/obj/db/Playlist.java index ba65b68..bbcd4ca 100644 --- a/src/main/java/me/kavin/piped/utils/obj/db/Playlist.java +++ b/src/main/java/me/kavin/piped/utils/obj/db/Playlist.java @@ -1,48 +1,45 @@ package me.kavin.piped.utils.obj.db; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; + +import javax.persistence.*; import java.util.List; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.ElementCollection; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Index; -import javax.persistence.OneToMany; -import javax.persistence.Table; - @Entity -@Table(name = "playlists", indexes = { @Index(columnList = "playlist_id", name = "playlists_playlist_id_idx") }) +@Table(name = "playlists", indexes = {@Index(columnList = "playlist_id", name = "playlists_playlist_id_idx")}) public class Playlist { public Playlist() { } - public Playlist(String name, List videos, String thumbnail) { + public Playlist(String name, User owner, String thumbnail) { this.name = name; - this.videos = videos; + this.owner = owner; + this.videos = new ObjectArrayList<>(); this.thumbnail = thumbnail; + this.playlist_id = UUID.randomUUID(); } @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private long id; - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "playlist_id") + @Column(name = "playlist_id", unique = true, nullable = false) + @GeneratedValue(generator = "UUID", strategy = GenerationType.IDENTITY) private UUID playlist_id; @Column(name = "name", length = 100) private String name; - @Column(name = "thumbnail", length = 255) + @Column(name = "short_description", length = 100) + private String short_description; + + @Column(name = "thumbnail", length = 300) private String thumbnail; - @Column(name = "owner") - private long owner; + @Column(name = "owner", nullable = false) + private User owner; @ElementCollection(fetch = FetchType.LAZY) @OneToMany(targetEntity = PlaylistVideo.class) @@ -77,6 +74,14 @@ public class Playlist { this.videos = videos; } + public String getShortDescription() { + return short_description; + } + + public void setShortDescription(String short_description) { + this.short_description = short_description; + } + public String getThumbnail() { return thumbnail; } @@ -85,11 +90,11 @@ public class Playlist { this.thumbnail = thumbnail; } - public long getOwner() { + public User getOwner() { return owner; } - public void setOwner(long owner) { + public void setOwner(User owner) { this.owner = owner; } }