From 4c2433a79e2a71856b083730edd2eb3eb5811b3d Mon Sep 17 00:00:00 2001 From: VC Date: Mon, 9 Sep 2024 13:33:07 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8:=20write=20back=20tokens=20to=20the?= =?UTF-8?q?=20config=20file?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 16 ++++++---------- src/config.rs | 40 ++++++++++++++++++++++++++-------------- src/lib.rs | 13 +++++++++---- src/main.rs | 25 ++++++++++++++++++++----- src/peertube.rs | 43 +++++++++++++++++++------------------------ src/youtube.rs | 10 ++++++---- 6 files changed, 86 insertions(+), 61 deletions(-) diff --git a/README.md b/README.md index 7121fe1..c00345c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # What is it? -This program takes the very last video published on a PeerTube instance and pushes it to a corresponding YouTube channel. +This program takes the very last or a specified video published on a PeerTube instance and pushes it to a corresponding YouTube channel. # What the status of this? @@ -12,7 +12,7 @@ So consider this a work in progress that will slowly get better with time. # What does it do exactly? -* it retrieves the latest PeerTube video download URL from an instance +* it retrieves the latest (or a specified) PeerTube video download URL from an instance * if you register the app into your PeerTube instance (optional), you can retrieve the latest video source instead of the highest quality encoded video * it creates a resumable upload into the target YouTube account * it downloads/uploads the latest PeerTube video to YouTube without using a cache (stream-to-stream) @@ -54,8 +54,8 @@ delete_video_source_after_transfer=true # this option is only available if you h # everything below is given by the register command with --peertube option [peertube.oauth2] client_id="" -client_secret=" ``` -You’ll be then prompted with all the necessary information to register `tootube`. You’ll end with a `refresh_token` that you can now paste inside your tootube configuration. +You’ll be then prompted with all the necessary information to register `tootube`. You’ll end with a `refresh_token` that you will be written back to the config file. If you wish to register `tootube` on PeerTube, you can do so by using: @@ -77,8 +77,4 @@ If you wish to register `tootube` on PeerTube, you can do so by using: tootube register --peertube --config ``` -It will require your username/password (beware that 2FA is not supported for this feature as of now) and generate a first `refresh_token` that you will put inside the aformentioned file: - -```bash -echo -n '' > /var/lib/tootube/refresh_token -``` +It will require your username/password (beware that 2FA is not supported for this feature as of now) and generate a first `refresh_token` that will be written back to the config file. diff --git a/src/config.rs b/src/config.rs index fb1dc68..a6cb770 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,8 +1,11 @@ -use serde::Deserialize; -use std::fs::read_to_string; +use serde::{Deserialize, Serialize}; +use std::{ + error::Error, + fs::{read_to_string, write}, +}; // General configuration Struct -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, Serialize)] pub struct Config { pub peertube: PeertubeConfig, pub youtube: YoutubeConfig, @@ -10,7 +13,7 @@ pub struct Config { pub tootube: TootubeConfig, } -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, Serialize)] pub struct PeertubeConfig { pub base_url: String, #[serde(default)] @@ -28,21 +31,21 @@ impl Default for PeertubeConfig { } } -#[derive(Debug, Clone, Deserialize)] +#[derive(Debug, Clone, Deserialize, Serialize)] pub struct PeertubeConfigOauth2 { pub client_id: String, pub client_secret: String, pub refresh_token: String, } -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, Serialize)] pub struct YoutubeConfig { pub refresh_token: String, pub client_id: String, pub client_secret: String, } -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, Serialize)] pub struct TootubeConfig { pub progress_bar: String, pub progress_chars: String, @@ -57,13 +60,22 @@ impl Default for TootubeConfig { } } -/// Parses the TOML file into a Config struct -pub fn parse_toml(toml_file: &str) -> Config { - let toml_config = - read_to_string(toml_file).unwrap_or_else(|e| panic!("Cannot open file {toml_file}: {e}")); +impl Config { + /// Parses the TOML file into a Config struct + pub fn new(toml_file: &str) -> Self { + let toml_config = read_to_string(toml_file) + .unwrap_or_else(|e| panic!("Cannot open file {toml_file}: {e}")); - let config: Config = toml::from_str(&toml_config) - .unwrap_or_else(|e| panic!("Cannot parse TOML file {toml_file}: {e}")); + let config: Config = toml::from_str(&toml_config) + .unwrap_or_else(|e| panic!("Cannot parse TOML file {toml_file}: {e}")); - config + config + } + + pub fn dump(&mut self, toml_file: &str) -> Result<(), Box> { + // bring back the default for progress bar + self.tootube = TootubeConfig::default(); + write(toml_file, toml::to_string_pretty(self)?)?; + Ok(()) + } } diff --git a/src/lib.rs b/src/lib.rs index fd6c298..9da0400 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,8 +3,8 @@ use log::debug; mod error; mod config; -pub use config::parse_toml; -use config::Config; +pub use config::Config; +pub use config::PeertubeConfigOauth2; mod peertube; pub use peertube::register as register_peertube; @@ -14,12 +14,15 @@ mod youtube; pub use youtube::register as register_youtube; use youtube::YouTube; +/// This is where the magic happens +/// This takes the main options from config & command line args +/// and sends back the updated PeerTube refresh_token if any #[tokio::main] -pub async fn run(config: Config, pl: Vec, pt_video_id: Option<&str>) { +pub async fn run(config: &Config, pl: Vec, pt_video_id: Option<&str>) -> Option { // Create PeerTube struct let peertube = match &config.peertube.oauth2 { Some(s) => PeerTube::new(&config.peertube.base_url) - .with_client(&s.client_id, &s.client_secret, &s.refresh_token) + .with_client(s) .await .unwrap_or_else(|e| panic!("Cannot instantiate PeerTube struct: {}", e)), None => PeerTube::new(&config.peertube.base_url), @@ -120,4 +123,6 @@ pub async fn run(config: Config, pl: Vec, pt_video_id: Option<&str>) { } } } + + peertube.refresh_token } diff --git a/src/main.rs b/src/main.rs index 444679a..f13bc0f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -66,22 +66,30 @@ fn main() { .get_matches(); if let Some(("register", sub_m)) = matches.subcommand() { - let config = parse_toml(sub_m.get_one::("config").unwrap()); + let mut config = Config::new(sub_m.get_one::("config").unwrap()); if sub_m.get_flag("youtube") { - register_youtube(&config.youtube) + let yt_refresh_token = register_youtube(&config.youtube) .unwrap_or_else(|e| panic!("Cannot register to YouTube API: {}", e)); + + config.youtube.refresh_token = yt_refresh_token; } if sub_m.get_flag("peertube") { - register_peertube(&config.peertube) + let pt_oauth2 = register_peertube(&config.peertube) .unwrap_or_else(|e| panic!("Cannot register to PeerTube API: {}", e)); + + config.peertube.oauth2 = Some(pt_oauth2); } + config + .dump(sub_m.get_one::("config").unwrap()) + .unwrap_or_else(|e| panic!("Cannot write back to Tootube Config file: {}", e)); + return; } - let mut config = parse_toml(matches.get_one::("config").unwrap()); + let mut config = Config::new(matches.get_one::("config").unwrap()); if matches.get_flag("vice") { config.tootube.progress_bar = "{msg}\n[{elapsed_precise}] 8{wide_bar:.magenta}ᗺ {bytes}/{total_bytes} ({bytes_per_sec}, {eta})".to_string(); @@ -98,5 +106,12 @@ fn main() { env_logger::init(); - run(config, playlists, pt_video_id); + // runs the main program logic & retrieves the updated PeerTube refresh_token + if let Some(x) = run(&config, playlists, pt_video_id) { + config.peertube.oauth2 = config.peertube.oauth2.map(|y| PeertubeConfigOauth2 { + client_id: y.client_id, + client_secret: y.client_secret, + refresh_token: x, + }) + }; } diff --git a/src/peertube.rs b/src/peertube.rs index 3b44b87..6672e8f 100644 --- a/src/peertube.rs +++ b/src/peertube.rs @@ -1,4 +1,4 @@ -use crate::{config::PeertubeConfig, error::TootubeError}; +use crate::{config::PeertubeConfig, config::PeertubeConfigOauth2, error::TootubeError}; use log::debug; use reqwest::{ header::{HeaderMap, HeaderValue}, @@ -8,7 +8,6 @@ use reqwest::{ use rpassword::prompt_password; use serde::{Deserialize, Serialize}; use std::{boxed::Box, cmp::Ordering, error::Error, io::stdin}; -use tokio::fs::{read_to_string, write}; #[derive(Debug, Deserialize)] pub struct PeerTubeVideos { @@ -149,7 +148,7 @@ struct PeerTubeVideoPlaylistsPlaylistIdVideosData { /// This function makes the registration process a little bit easier #[tokio::main] -pub async fn register(config: &PeertubeConfig) -> Result<(), Box> { +pub async fn register(config: &PeertubeConfig) -> Result> { // Get client ID/secret let oauth2_client = reqwest::get(format!("{}/api/v1/oauth-clients/local", config.base_url)) .await? @@ -185,26 +184,25 @@ pub async fn register(config: &PeertubeConfig) -> Result<(), Box> { .json::() .await?; - println!("You can now paste the following lines inside the `peertube` section of your tootube.toml file:"); - - println!(); - + println!( + "The following lines will be written to the `peertube` section of your tootube.toml file:" + ); println!("[peertube.oauth2]"); println!("client_id=\"{}\"", oauth2_client.client_id); println!("client_secret=\"{}\"", oauth2_client.client_secret); - println!("refresh_token="); + println!("refresh_token=\"{}\"", oauth2_token.refresh_token); - println!(); - - println!("Finally, add the refresh token inside the refresh_token path:"); - println!("{}", oauth2_token.refresh_token); - - Ok(()) + Ok(PeertubeConfigOauth2 { + client_id: oauth2_client.client_id, + client_secret: oauth2_client.client_secret, + refresh_token: oauth2_token.refresh_token, + }) } #[derive(Debug)] pub struct PeerTube { base_url: String, + pub refresh_token: Option, client: Client, } @@ -213,6 +211,7 @@ impl PeerTube { pub fn new(base_url: &str) -> Self { PeerTube { base_url: format!("{}/api/v1", base_url), + refresh_token: None, client: Client::new(), } } @@ -221,17 +220,13 @@ impl PeerTube { /// the default required header pub async fn with_client( mut self, - client_id: &str, - client_secret: &str, - refresh_token_path: &str, + pt_oauth2: &PeertubeConfigOauth2, ) -> Result> { - let refresh_token = read_to_string(refresh_token_path).await?; - let params = PeerTubeUsersToken { - client_id: client_id.to_string(), - client_secret: client_secret.to_string(), + client_id: pt_oauth2.client_id.to_owned(), + client_secret: pt_oauth2.client_secret.to_owned(), grant_type: "refresh_token".to_string(), - refresh_token: Some(refresh_token), + refresh_token: Some(pt_oauth2.refresh_token.to_owned()), username: None, password: None, }; @@ -245,8 +240,6 @@ impl PeerTube { .json::() .await?; - write(refresh_token_path, req.refresh_token).await?; - let mut headers = HeaderMap::new(); headers.insert( "Authorization", @@ -257,6 +250,8 @@ impl PeerTube { .default_headers(headers) .build()?; + self.refresh_token = Some(req.refresh_token); + Ok(self) } diff --git a/src/youtube.rs b/src/youtube.rs index 80e1509..f9a3619 100644 --- a/src/youtube.rs +++ b/src/youtube.rs @@ -160,8 +160,9 @@ struct YoutubePlaylistListResponseItemSnippet { } /// This function makes the registration process a little bit easier +/// It returns the expected refresh_token so that it can be written back to the file #[tokio::main] -pub async fn register(config: &YoutubeConfig) -> Result<(), Box> { +pub async fn register(config: &YoutubeConfig) -> Result> { println!("Click on the link below to authorize {} to upload to YouTube and deal with your playlists:", env!("CARGO_PKG_NAME")); println!("https://accounts.google.com/o/oauth2/v2/auth?client_id={}&redirect_uri=urn:ietf:wg:oauth:2.0:oob&scope=https://www.googleapis.com/auth/youtube%20https://www.googleapis.com/auth/youtube.upload&response_type=code", config.client_id); println!("Paste the returned authorization code:"); @@ -187,11 +188,12 @@ pub async fn register(config: &YoutubeConfig) -> Result<(), Box> { let refresh_token: RegistrationAccessTokenResponse = res.json().await?; - println!("You can now paste the following line inside the `youtube` section of your tootube.toml file:"); - + println!( + "The following line will be written to the `youtube` section of your tootube.toml file:" + ); println!("refresh_token=\"{}\"", refresh_token.refresh_token); - Ok(()) + Ok(refresh_token.refresh_token) } pub struct YouTube {