Merge branch '7-re-add-the-target-playlist-into-the-source-playlist' into 'master'

Re-add the target playlist into the source playlist

Closes #7

See merge request veretcle/tootube!21
This commit is contained in:
VC
2024-05-16 09:12:17 +00:00
4 changed files with 233 additions and 7 deletions

10
Cargo.lock generated
View File

@@ -973,18 +973,18 @@ dependencies = [
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.201" version = "1.0.202"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "780f1cebed1629e4753a1a38a3c72d30b97ec044f0aef68cb26650a3c5cf363c" checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.201" version = "1.0.202"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5e405930b9796f1c00bee880d03fc7e0bb4b9a11afc776885ffe84320da2865" checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -1192,7 +1192,7 @@ dependencies = [
[[package]] [[package]]
name = "tootube" name = "tootube"
version = "0.6.1" version = "0.7.0"
dependencies = [ dependencies = [
"async-stream", "async-stream",
"clap", "clap",

View File

@@ -1,7 +1,7 @@
[package] [package]
name = "tootube" name = "tootube"
authors = ["VC <veretcle+framagit@mateu.be>"] authors = ["VC <veretcle+framagit@mateu.be>"]
version = "0.6.1" version = "0.7.0"
edition = "2021" edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View File

@@ -8,7 +8,7 @@ use config::Config;
mod peertube; mod peertube;
pub use peertube::register as register_peertube; pub use peertube::register as register_peertube;
use peertube::PeerTube; use peertube::{get_playlists_to_be_added_to, PeerTube};
mod youtube; mod youtube;
pub use youtube::register as register_youtube; pub use youtube::register as register_youtube;
@@ -91,4 +91,18 @@ pub async fn run(config: Config, pl: Vec<String>) {
debug!("Original Video {} has been deleted", &latest_vid.uuid); 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;
}
}
}
} }

View File

@@ -2,6 +2,7 @@ use crate::{config::PeertubeConfig, error::TootubeError};
use log::debug; use log::debug;
use reqwest::{ use reqwest::{
header::{HeaderMap, HeaderValue}, header::{HeaderMap, HeaderValue},
multipart::Form,
Client, Client,
}; };
use rpassword::prompt_password; use rpassword::prompt_password;
@@ -23,6 +24,12 @@ pub struct PeerTubeVideo {
#[serde(rename = "streamingPlaylists")] #[serde(rename = "streamingPlaylists")]
pub streaming_playlists: Option<Vec<PeerTubeVideoStreamingPlaylists>>, pub streaming_playlists: Option<Vec<PeerTubeVideoStreamingPlaylists>>,
pub tags: Option<Vec<String>>, pub tags: Option<Vec<String>>,
pub channel: PeerTubeVideoChannel,
}
#[derive(Debug, Deserialize)]
pub struct PeerTubeVideoChannel {
pub id: u8,
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
@@ -99,6 +106,48 @@ pub struct PeerTubeVideoStreamingPlaylistsFilesResolution {
pub id: u16, pub id: u16,
} }
#[derive(Debug, Deserialize)]
pub struct PeerTubeVideoPlaylists {
pub total: u16,
pub data: Vec<PeerTubeVideoPlaylist>,
}
#[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<PeerTubeVideoPlaylistsPlaylistIdVideosData>,
}
#[derive(Debug, Deserialize)]
struct PeerTubeVideoPlaylistsPlaylistIdVideosData {
video: PeerTubeVideo,
}
/// This function makes the registration process a little bit easier /// This function makes the registration process a little bit easier
#[tokio::main] #[tokio::main]
pub async fn register(config: &PeertubeConfig) -> Result<(), Box<dyn Error>> { pub async fn register(config: &PeertubeConfig) -> Result<(), Box<dyn Error>> {
@@ -290,4 +339,167 @@ impl PeerTube {
Ok(()) Ok(())
} }
/// List every playlists on PeerTube
pub async fn list_video_playlists(&self) -> Result<Vec<PeerTubeVideoPlaylist>, Box<dyn Error>> {
let mut playlists: Vec<PeerTubeVideoPlaylist> = 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::<PeerTubeVideoPlaylists>()
.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<String, Box<dyn Error>> {
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::<PeerTubeVideoPlaylistResponse>()
.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<dyn Error>> {
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<Vec<String>, Box<dyn Error>> {
let mut videos: Vec<PeerTubeVideo> = 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::<PeerTubeVideoPlaylistsPlaylistIdVideos>()
.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 theyre not in it already
pub async fn get_playlists_to_be_added_to(
peertube: &PeerTube,
vid_uuid: &str,
pl: &[String],
c_id: u8,
) -> Result<Vec<String>, Box<dyn Error>> {
let mut playlist_to_be_added_to: Vec<String> = vec![];
if let Ok(local_pl) = peertube.list_video_playlists().await {
// list the displayNames of each playlist
let current_playlist: Vec<String> =
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)
} }