builds under gnu stable?

This commit is contained in:
OMGeeky
2023-04-01 15:00:16 +02:00
parent 291a75d92a
commit 1938f32e3e
8 changed files with 557 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/target
/Cargo.lock

25
Cargo.toml Normal file
View File

@@ -0,0 +1,25 @@
[package]
name = "google_youtube"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
downloader_config = { path = "../downloader_config" }
exponential_backoff = { path = "../exponential_backoff" }
google-youtube3 = "4.0.1"
reqwest = { version = "0.11.13", features = ["default", "json"] }
tokio = { version = "1.23.0", features = ["full"] }
serde = { version = "1.0.130", features = ["derive", "default"] }
serde_json = "1.0"
async-trait = "0.1.60"
strfmt = "0.2.2"
#[patch.crates-io.yup-oauth2]
#path = "../../Documents/GitHub/OMGeeky/yup-oauth2"
#version = "7.1.0"

0
data/category_ids.json Normal file
View File

119
src/auth.rs Normal file
View File

@@ -0,0 +1,119 @@
use std::collections::HashMap;
use std::error::Error;
use std::future::Future;
use std::path::Path;
use std::pin::Pin;
use std::time::Duration;
use google_youtube3::hyper::client::HttpConnector;
use google_youtube3::hyper_rustls::HttpsConnector;
use google_youtube3::oauth2;
use google_youtube3::oauth2::authenticator::Authenticator;
use google_youtube3::oauth2::authenticator_delegate::InstalledFlowDelegate;
use strfmt::strfmt;
use tokio::time::sleep;
use crate::auth;
use downloader_config::{load_config, Config};
struct CustomFlowDelegate {}
impl InstalledFlowDelegate for CustomFlowDelegate {
fn redirect_uri(&self) -> Option<&str> {
if load_config().use_local_auth_redirect {
Some("http://localhost:8080/googleapi/auth")
} else {
Some("https://game-omgeeky.de:7443/googleapi/auth")
}
}
fn present_user_url<'a>(
&'a self,
url: &'a str,
need_code: bool,
) -> Pin<Box<dyn Future<Output = Result<String, String>> + Send + 'a>> {
Box::pin(present_user_url(url, need_code))
}
}
async fn present_user_url(url: &str, need_code: bool) -> Result<String, String> {
println!("Please open this URL in your browser:\n{}\n", url);
if need_code {
let conf = load_config();
let mut code = String::new();
if conf.use_file_auth_response {
code = get_auth_code(&conf).await.unwrap_or("".to_string());
} else {
println!("Enter the code you get after authorization here: ");
std::io::stdin().read_line(&mut code).unwrap();
}
Ok(code.trim().to_string())
} else {
println!("No code needed");
Ok("".to_string())
}
}
async fn get_auth_code(config: &Config) -> Result<String, Box<dyn Error>> {
let code: String;
let path = &config.path_auth_code;
let path = Path::new(path);
if let Err(e) = std::fs::remove_file(path) {
if e.kind() != std::io::ErrorKind::NotFound {
println!("Error removing file: {:?}", e);
panic!("Error removing file: {:?}", e);
}
}
println!("Waiting for auth code in file: {}", path.display());
loop {
let res = std::fs::read_to_string(path); //try reading the file
if let Ok(content) = res {
let l = content.lines().next(); //code should be on first line of the file
let re = match l {
Some(s) => s,
None => {
sleep(Duration::from_secs(config.auth_file_read_timeout)).await;
continue;
}
};
code = re.to_string();
// std::fs::remove_file(path)?;
break;
}
// wait a few seconds
sleep(Duration::from_secs(config.auth_file_read_timeout)).await;
}
Ok(code)
}
pub(crate) async fn get_authenticator<S: Into<String>>(
path_to_application_secret: String,
scopes: &Vec<String>,
user: Option<S>,
) -> Result<Authenticator<HttpsConnector<HttpConnector>>, Box<dyn Error>> {
let app_secret = oauth2::read_application_secret(path_to_application_secret).await?;
let method = oauth2::InstalledFlowReturnMethod::Interactive;
let config = load_config();
let mut vars: HashMap<String, String> = HashMap::new();
let user = match user {
Some(u) => u.into(),
None => "unknown".to_string(),
};
vars.insert("user".to_string(), user.clone());
let persistent_path: String =
strfmt(&config.path_authentications, &vars).expect("Error formatting path");
println!("Persistent auth path for user:{} => {}", user, persistent_path);
let auth = oauth2::InstalledFlowAuthenticator::builder(app_secret, method)
.flow_delegate(Box::new(auth::CustomFlowDelegate {}))
.persist_tokens_to_disk(persistent_path)
.build()
.await?; //TODO: somehow get rid of this unwrap that is happening in the library
auth.token(&scopes).await?;
Ok(auth)
}

32
src/config.rs Normal file
View File

@@ -0,0 +1,32 @@
// use std::env;
//
// #[derive(Clone)]
// pub struct Config {
// pub path_auth_code: String,
// pub path_authentications: String,
// pub use_file_auth_response: bool,
// pub use_local_auth_redirect: bool,
// pub auth_file_read_timeout: u64,
// }
//
// pub fn load_config() -> Config {
// let path_auth_code =
// env::var("PATH_AUTH_CODE").unwrap_or("/tmp/twba/auth/code.txt".to_string());
// let path_authentications =
// env::var("PATH_AUTHENTICATIONS").unwrap_or("/tmp/twba/auth/{user}.json".to_string());
// let use_file_auth_response =
// env::var("USE_FILE_AUTH_RESPONSE").unwrap_or("1".to_string()) == "1";
// let use_local_auth_redirect =
// env::var("USE_LOCAL_AUTH_REDIRECT").unwrap_or("0".to_string()) == "1";
// let auth_file_read_timeout = env::var("AUTH_FILE_READ_TIMEOUT")
// .unwrap_or("5".to_string())
// .parse()
// .unwrap();
// Config {
// path_auth_code,
// use_file_auth_response,
// path_authentications,
// use_local_auth_redirect,
// auth_file_read_timeout,
// }
// }

294
src/lib.rs Normal file
View File

@@ -0,0 +1,294 @@
use std::default::Default;
use std::error::Error;
use std::path::{Path, PathBuf};
use exponential_backoff::youtube::generic_check_backoff_youtube;
use google_youtube3::{
self as youtube,
api::Playlist,
api::PlaylistItem,
api::PlaylistItemSnippet,
api::PlaylistListResponse,
api::PlaylistSnippet,
api::ResourceId,
api::Video,
api::VideoSnippet,
api::VideoStatus,
hyper::client::HttpConnector,
hyper::{Body, Response},
hyper_rustls::HttpsConnector,
};
use youtube::YouTube;
use youtube::{hyper, hyper_rustls::HttpsConnectorBuilder};
mod auth;
pub mod scopes;
// mod config;
pub struct YoutubeClient {
pub client: YouTube<HttpsConnector<HttpConnector>>,
}
pub enum PrivacyStatus {
Public,
Unlisted,
Private,
}
impl PrivacyStatus {
fn to_string(&self) -> String {
match self {
PrivacyStatus::Public => "public".to_string(),
PrivacyStatus::Unlisted => "unlisted".to_string(),
PrivacyStatus::Private => "private".to_string(),
}
}
}
impl YoutubeClient {
pub async fn new<S: Into<String>>(
path_to_application_secret: Option<S>,
scopes: Vec<S>,
user: Option<S>,
) -> Result<Self, Box<dyn Error>> {
let scopes = scopes
.into_iter()
.map(|s| s.into())
.collect::<Vec<String>>();
let hyper_client = hyper::Client::builder().build(
HttpsConnectorBuilder::new()
.with_native_roots()
.https_or_http()
.enable_http1()
.enable_http2()
.build(),
);
let path_to_application_secret = match path_to_application_secret {
None => "auth/service_account2.json".to_string(),
Some(s) => s.into(),
};
let auth = auth::get_authenticator(path_to_application_secret, &scopes, user).await?;
let client: YouTube<HttpsConnector<HttpConnector>> = YouTube::new(hyper_client, auth);
let res = Self { client };
Ok(res)
}
pub async fn find_playlist_by_name(
&self,
name: &str,
) -> Result<Option<Playlist>, Box<dyn Error>> {
let part = vec!["snippet".to_string()];
struct PlaylistParams {
part: Vec<String>,
mine: bool,
}
async fn list_playlist(
client: &YouTube<HttpsConnector<HttpConnector>>,
params: &PlaylistParams,
) -> google_youtube3::Result<(Response<Body>, PlaylistListResponse)> {
client
.playlists()
.list(&params.part)
.mine(params.mine)
.doit()
.await
}
let para = PlaylistParams { part, mine: true };
let (_res, playlists): (Response<Body>, PlaylistListResponse) =
generic_check_backoff_youtube(&self.client, &para, list_playlist).await??;
if let Some(items) = playlists.items {
for element in items {
if let Some(snippet) = &element.snippet {
if let Some(title) = &snippet.title {
if title == name {
return Ok(Some(element));
}
}
}
}
}
Ok(None)
}
pub async fn find_playlist_or_create_by_name(
&self,
name: &str,
) -> Result<Playlist, Box<dyn Error>> {
let playlist = self.find_playlist_by_name(name).await?;
if let Some(playlist) = playlist {
return Ok(playlist);
}
let playlist = self.create_playlist(name).await?;
Ok(playlist)
}
pub async fn add_video_to_playlist(
&self,
video: &Video,
playlist: &Playlist,
) -> Result<(), Box<dyn Error>> {
let playlist_item = PlaylistItem {
snippet: Some(PlaylistItemSnippet {
playlist_id: Some(playlist.id.clone().unwrap()),
resource_id: Some(ResourceId {
kind: Some("youtube#video".to_string()),
video_id: Some(video.id.clone().unwrap()),
..Default::default()
}),
..Default::default()
}),
..Default::default()
};
async fn insert_playlist_item(
client: &YouTube<HttpsConnector<HttpConnector>>,
playlist_item: &PlaylistItem,
) -> google_youtube3::Result<(Response<Body>, PlaylistItem)> {
client
.playlist_items()
.insert(playlist_item.clone())
.doit()
.await
}
// let res = self.client.playlist_items().insert(playlist_item).doit().await?;
let (res, _) =
generic_check_backoff_youtube(&self.client, &playlist_item, insert_playlist_item)
.await??;
if res.status().is_success() {
Ok(())
} else {
Err(format!("got status: {}", res.status().as_u16()).into())
}
}
pub async fn upload_video<S: Into<String>, V: Into<Vec<String>>>(
&self,
path: impl AsRef<Path>,
title: S,
description: S,
tags: V,
privacy_status: PrivacyStatus,
) -> Result<Video, Box<dyn Error>> {
println!("test 123");
let video = Video {
snippet: Some(VideoSnippet {
title: Some(title.into()),
description: Some(description.into()),
category_id: Some("20".to_string()),
tags: Some(tags.into()),
..Default::default()
}),
status: Some(VideoStatus {
privacy_status: Some(privacy_status.to_string()),
public_stats_viewable: Some(true),
embeddable: Some(true),
self_declared_made_for_kids: Some(false),
..Default::default()
}),
..Default::default()
};
// let file = file.into_std().await;
struct UploadParameters {
video: Video,
path: PathBuf,
}
let params = UploadParameters {
video: video.clone(),
path: path.as_ref().into(),
};
async fn upload_fn(
client: &YouTube<HttpsConnector<HttpConnector>>,
para: &UploadParameters,
) -> Result<(Response<Body>, Video), google_youtube3::Error> {
println!("Opening file: {:?}", para.path);
let stream = std::fs::File::open(&para.path)?;
println!("Uploading file: {:?}", para.path);
let insert_call = client
.videos()
.insert(para.video.clone());
println!("Insert call created");
let res = insert_call
.upload(stream, "video/mp4".parse().unwrap());
println!("Upload request");
res
.await
}
println!("Starting upload...");
let (response, video) =
generic_check_backoff_youtube(&self.client, &params, upload_fn).await??;
// let (response, video) = exponential_backoff::youtube::check_backoff_youtube_upload(
// &self.client,
// video,
// &path,
// "video/mp4".parse().unwrap(),
// )
// .await??;
if response.status().is_success() {
println!("Upload successful!");
Ok(video)
} else {
println!("Upload failed!\n=====================================\n");
println!("Status: {}", response.status());
println!("Body: {:?}", response);
println!("Video: {:?}", video);
Err(format!("got status: {}", response.status().as_u16()).into())
}
// return Ok(video);
// let insert: google_youtube3::Result<(Response<Body>, Video)> = self
// .client
// .videos()
// .insert(video)
// .upload(file, "video/mp4".parse().unwrap())
// .await;
//
// match insert {
// Ok(insert) => Ok(insert),
// Err(e) => {
// println!("Error: {:?}", e);
// Err(Box::new(e))
// }
// }
}
async fn create_playlist(&self, name: &str) -> Result<Playlist, Box<dyn Error>> {
let playlist = Playlist {
snippet: Some(PlaylistSnippet {
title: Some(name.to_string()),
..Default::default()
}),
..Default::default()
};
async fn create_playlist(
client: &YouTube<HttpsConnector<HttpConnector>>,
params: &Playlist,
) -> google_youtube3::Result<(Response<Body>, Playlist)> {
client.playlists().insert(params.clone()).doit().await
}
let (res, playlist) =
generic_check_backoff_youtube(&self.client, &playlist, create_playlist).await??;
if res.status().is_success() {
Ok(playlist)
} else {
Err(format!("got status: {}", res.status().as_u16()).into())
}
}
}
pub async fn sample() -> Result<(), Box<dyn Error>> {
println!("Hello from the youtube lib!");
Ok(())
}

82
src/main.rs Normal file
View File

@@ -0,0 +1,82 @@
use std::error::Error;
use std::path::Path;
use google_youtube3::api::Playlist;
use tokio::fs::File;
use google_youtube::{PrivacyStatus, scopes, YoutubeClient};
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
println!("Hello, world!");
sample().await?;
Ok(())
}
pub async fn sample() -> Result<(), Box<dyn Error>> {
// get client
let scopes = vec![
// google_youtube::scopes::YOUTUBE,
google_youtube::scopes::YOUTUBE_UPLOAD,
google_youtube::scopes::YOUTUBE_READONLY,
];
// let client_secret_path = "auth/youtube_client_secret.json";
let client_secret_path = "auth/test_rust_client_secret_2.json";
let user = "nopixelvods";
let client = YoutubeClient::new(Some(client_secret_path), scopes, Some(user)).await?;
/*
// get list of channels of the authenticated user
let part = vec!["snippet".to_string()];
let (_res, channels) = client
.client
.channels()
.list(&part)
.mine(true)
.doit()
.await?;
for element in channels.items.unwrap() {
println!(
"channel name: {:?}",
element.snippet.unwrap().title.unwrap()
);
}
println!("Channels done!\n\n");
*/
// get a playlist by name or create it if it does not exist('LunaOni Clips' for example)
let playlist = client.find_playlist_or_create_by_name("LunaOni Clips").await;
println!("playlist: {:?}", playlist);
println!("Playlist done!\n\n");
println!("Uploading video... (30 times");
for i in 0..30 {
println!("+==={:2}==;uploading video...", i);
let path = Path::new("test/test.mp4");
// let file = File::open(path).await?;
let description = "test video description";
let title = "test video2";
let tags = vec!["test".to_string(), "test2".to_string()];
let privacy_status = PrivacyStatus::Private;
println!("uploading video...");
let insert = client
.upload_video(&path, description, title, tags, privacy_status)
.await;
println!("uploading video... (done)");
println!("adding to playlist...");
if let Ok(video) = &insert{
if let Ok(playlist) = &playlist {
println!("adding video to playlist: {:?}", playlist);
let _ = client.add_video_to_playlist(&video, &playlist).await;
}
}
println!("adding to playlist... (done)");
println!("\n\n{:?}\n\n/==={:2}========;", insert, i);
}
println!("Done!");
Ok(())
}

3
src/scopes.rs Normal file
View File

@@ -0,0 +1,3 @@
pub const YOUTUBE_READONLY: &str = "https://www.googleapis.com/auth/youtube.readonly";
pub const YOUTUBE: &str = "https://www.googleapis.com/auth/youtube";
pub const YOUTUBE_UPLOAD: &str = "https://www.googleapis.com/auth/youtube.upload";