From 61f19c0ea139271ff286f20018f83e706ce16a9f Mon Sep 17 00:00:00 2001 From: VC Date: Wed, 15 May 2024 13:26:47 +0200 Subject: [PATCH 1/2] =?UTF-8?q?=E2=AC=86=EF=B8=8F:=20update=20version?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 10 +++++----- Cargo.toml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 69b094d..a7705ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -973,18 +973,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.201" +version = "1.0.202" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "780f1cebed1629e4753a1a38a3c72d30b97ec044f0aef68cb26650a3c5cf363c" +checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.201" +version = "1.0.202" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e405930b9796f1c00bee880d03fc7e0bb4b9a11afc776885ffe84320da2865" +checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838" dependencies = [ "proc-macro2", "quote", @@ -1192,7 +1192,7 @@ dependencies = [ [[package]] name = "tootube" -version = "0.6.1" +version = "0.7.0" dependencies = [ "async-stream", "clap", diff --git a/Cargo.toml b/Cargo.toml index 1e3e6b2..8156dfe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "tootube" authors = ["VC "] -version = "0.6.1" +version = "0.7.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From e46889f1e1c01a214225293058f80d5e3f5043e8 Mon Sep 17 00:00:00 2001 From: VC Date: Thu, 16 May 2024 11:05:33 +0200 Subject: [PATCH 2/2] =?UTF-8?q?=E2=9C=A8:=20add=20back=20video=20to=20peer?= =?UTF-8?q?tube=20playlist=20after=20adding=20to=20youtube=20playlist?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib.rs | 16 +++- src/peertube.rs | 212 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 227 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 0ca34f6..c3cc3bf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,7 +8,7 @@ use config::Config; mod peertube; pub use peertube::register as register_peertube; -use peertube::PeerTube; +use peertube::{get_playlists_to_be_added_to, PeerTube}; mod youtube; pub use youtube::register as register_youtube; @@ -91,4 +91,18 @@ pub async fn run(config: Config, pl: Vec) { debug!("Original Video {} has been deleted", &latest_vid.uuid); } + + // Updates the playlist of PeerTube if necessary + if config.peertube.oauth2.is_some() && !pl.is_empty() { + debug!("Updating playlists on PeerTube"); + + if let Ok(pl_to_add_to) = + get_playlists_to_be_added_to(&peertube, &latest_vid.uuid, &pl, latest_vid.channel.id) + .await + { + for p in pl_to_add_to { + let _ = peertube.add_video_to_playlist(&latest_vid.uuid, &p).await; + } + } + } } diff --git a/src/peertube.rs b/src/peertube.rs index 29b84be..da86a49 100644 --- a/src/peertube.rs +++ b/src/peertube.rs @@ -2,6 +2,7 @@ use crate::{config::PeertubeConfig, error::TootubeError}; use log::debug; use reqwest::{ header::{HeaderMap, HeaderValue}, + multipart::Form, Client, }; use rpassword::prompt_password; @@ -23,6 +24,12 @@ pub struct PeerTubeVideo { #[serde(rename = "streamingPlaylists")] pub streaming_playlists: Option>, pub tags: Option>, + pub channel: PeerTubeVideoChannel, +} + +#[derive(Debug, Deserialize)] +pub struct PeerTubeVideoChannel { + pub id: u8, } #[derive(Debug, Deserialize)] @@ -99,6 +106,48 @@ pub struct PeerTubeVideoStreamingPlaylistsFilesResolution { pub id: u16, } +#[derive(Debug, Deserialize)] +pub struct PeerTubeVideoPlaylists { + pub total: u16, + pub data: Vec, +} + +#[derive(Debug, Deserialize)] +pub struct PeerTubeVideoPlaylist { + pub id: u16, + pub uuid: String, + #[serde(rename = "displayName")] + pub display_name: String, +} + +#[derive(Debug, Serialize)] +struct PeerTubeVideoPlaylistsVideos { + #[serde(rename = "videoId")] + video_id: String, +} + +#[derive(Debug, Deserialize)] +struct PeerTubeVideoPlaylistResponse { + #[serde(rename = "videoPlaylist")] + video_playlist: PeerTubeVideoPlaylistResponseVideoPlaylist, +} + +#[derive(Debug, Deserialize)] +struct PeerTubeVideoPlaylistResponseVideoPlaylist { + uuid: String, +} + +#[derive(Debug, Deserialize)] +struct PeerTubeVideoPlaylistsPlaylistIdVideos { + total: u32, + data: Vec, +} + +#[derive(Debug, Deserialize)] +struct PeerTubeVideoPlaylistsPlaylistIdVideosData { + video: PeerTubeVideo, +} + /// This function makes the registration process a little bit easier #[tokio::main] pub async fn register(config: &PeertubeConfig) -> Result<(), Box> { @@ -290,4 +339,167 @@ impl PeerTube { Ok(()) } + + /// List every playlists on PeerTube + pub async fn list_video_playlists(&self) -> Result, Box> { + let mut playlists: Vec = vec![]; + + let mut start = 0; + let inc = 15; + + while let Ok(mut local_pl) = self + .client + .get(&format!( + "{}/video-playlists?count={}&playlistType=1&start={}", + self.base_url, inc, start + )) + .send() + .await? + .json::() + .await + { + start += inc; + playlists.append(&mut local_pl.data); + if start >= local_pl.total { + break; + } + } + + Ok(playlists) + } + + /// Add a public playlist + pub async fn create_video_playlist( + &self, + c_id: u8, + display_name: &str, + ) -> Result> { + let form = Form::new() + .text("displayName", display_name.to_string()) + .text("privacy", "1") + .text("videoChannelId", format!("{}", c_id)); + + let pl_created = self + .client + .post(&format!("{}/video-playlists", self.base_url)) + .multipart(form) + .send() + .await? + .json::() + .await?; + + Ok(pl_created.video_playlist.uuid) + } + + /// Add a video into a playlist + pub async fn add_video_to_playlist( + &self, + vid_uuid: &str, + pl_uuid: &str, + ) -> Result<(), Box> { + let video_to_add = PeerTubeVideoPlaylistsVideos { + video_id: vid_uuid.to_string(), + }; + + let res = self + .client + .post(&format!( + "{}/video-playlists/{}/videos", + self.base_url, pl_uuid + )) + .json(&video_to_add) + .send() + .await?; + + if !res.status().is_success() { + return Err(TootubeError::new(&format!( + "Cannot add video {} to playlist {}: {}", + vid_uuid, + pl_uuid, + res.text().await? + )) + .into()); + }; + + Ok(()) + } + + /// List all videos of a playlist + pub async fn list_videos_playlist(&self, uuid: &str) -> Result, Box> { + let mut videos: Vec = vec![]; + + let mut start = 0; + let inc = 15; + + while let Ok(l_vid) = self + .client + .get(&format!( + "{}/video-playlists/{}/videos?start={}&count={}", + &self.base_url, &uuid, start, inc + )) + .send() + .await? + .json::() + .await + { + start += inc; + videos.append(&mut l_vid.data.into_iter().map(|x| x.video).collect()); + if start >= l_vid.total { + break; + } + } + + Ok(videos.into_iter().map(|x| x.uuid).collect()) + } +} + +/// Given a PeerTube instance, video UUID, list of named playlists and channel ID, this function: +/// * adds playlists if they do not exists +/// * adds the video to said playlists if they’re not in it already +pub async fn get_playlists_to_be_added_to( + peertube: &PeerTube, + vid_uuid: &str, + pl: &[String], + c_id: u8, +) -> Result, Box> { + let mut playlist_to_be_added_to: Vec = vec![]; + + if let Ok(local_pl) = peertube.list_video_playlists().await { + // list the displayNames of each playlist + let current_playlist: Vec = + local_pl.iter().map(|s| s.display_name.clone()).collect(); + // get the playlist whose displayName does not exist yet + let pl_to_create: Vec<_> = pl + .iter() + .filter(|x| !current_playlist.contains(x)) + .collect(); + + debug!("Playlists to be added: {:?}", &pl_to_create); + + playlist_to_be_added_to = local_pl + .into_iter() + .filter(|x| pl.contains(&x.display_name)) + .map(|x| x.uuid.clone()) + .collect(); + + // create the missing playlists + for p in pl_to_create { + if let Ok(s) = peertube.create_video_playlist(c_id, p).await { + playlist_to_be_added_to.push(s); + } + } + + for p in playlist_to_be_added_to.clone().iter() { + if let Ok(s) = peertube.list_videos_playlist(p).await { + // if a video already exists inside a playlist, drop it + if s.contains(&vid_uuid.to_string()) { + playlist_to_be_added_to.retain(|i| *i != *p) + } + } + } + + debug!("Playlists to be added to: {:?}", &playlist_to_be_added_to); + }; + + Ok(playlist_to_be_added_to) }