mirror of
https://framagit.org/veretcle/tootube.git
synced 2025-07-20 20:41:17 +02:00
First functional version
This commit is contained in:
167
src/youtube.rs
Normal file
167
src/youtube.rs
Normal file
@@ -0,0 +1,167 @@
|
||||
use crate::{config::YoutubeConfig, error::TootubeError, peertube::PeerTubeVideo};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{error::Error, sync::Mutex};
|
||||
|
||||
static ACCESS_TOKEN: Mutex<String> = Mutex::new(String::new());
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
struct RefreshTokenRequest {
|
||||
refresh_token: String,
|
||||
client_id: String,
|
||||
client_secret: String,
|
||||
redirect_uri: String,
|
||||
grant_type: String,
|
||||
}
|
||||
|
||||
impl Default for RefreshTokenRequest {
|
||||
fn default() -> Self {
|
||||
RefreshTokenRequest {
|
||||
refresh_token: "".to_string(),
|
||||
client_id: "".to_string(),
|
||||
client_secret: "".to_string(),
|
||||
redirect_uri: "urn:ietf:wg:oauth:2.0:oob".to_string(),
|
||||
grant_type: "refresh_token".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct AccessTokenResponse {
|
||||
access_token: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
struct YoutubeUploadParams {
|
||||
snippet: YoutubeUploadParamsSnippet,
|
||||
status: YoutubeUploadParamsStatus,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
struct YoutubeUploadParamsSnippet {
|
||||
title: String,
|
||||
description: String,
|
||||
#[serde(rename = "categoryId")]
|
||||
category_id: String,
|
||||
#[serde(rename = "defaultAudioLanguage")]
|
||||
default_audio_language: String,
|
||||
tags: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
impl Default for YoutubeUploadParamsSnippet {
|
||||
fn default() -> Self {
|
||||
YoutubeUploadParamsSnippet {
|
||||
title: "".to_string(),
|
||||
description: "".to_string(),
|
||||
category_id: "20".to_string(),
|
||||
default_audio_language: "fr".to_string(),
|
||||
tags: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
struct YoutubeUploadParamsStatus {
|
||||
#[serde(rename = "selfDeclaredMadeForKids")]
|
||||
self_declared_made_for_kids: bool,
|
||||
#[serde(rename = "privacyStatus")]
|
||||
privacy_status: String,
|
||||
license: String,
|
||||
}
|
||||
|
||||
impl Default for YoutubeUploadParamsStatus {
|
||||
fn default() -> Self {
|
||||
YoutubeUploadParamsStatus {
|
||||
self_declared_made_for_kids: false,
|
||||
privacy_status: "public".to_string(),
|
||||
license: "creativeCommon".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensures that Token has been refreshed and that it is unique
|
||||
fn refresh_token(config: &YoutubeConfig) -> Result<String, Box<dyn Error>> {
|
||||
if let Ok(mut unlocked_access_token) = ACCESS_TOKEN.lock() {
|
||||
if unlocked_access_token.is_empty() {
|
||||
let refresh_token = RefreshTokenRequest {
|
||||
refresh_token: config.refresh_token.clone(),
|
||||
client_id: config.client_id.clone(),
|
||||
client_secret: config.client_secret.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let client = reqwest::blocking::Client::new();
|
||||
let res = client
|
||||
.post("https://accounts.google.com/o/oauth2/token")
|
||||
.json(&refresh_token)
|
||||
.send()?;
|
||||
|
||||
let access_token: AccessTokenResponse = res.json()?;
|
||||
|
||||
*unlocked_access_token = access_token.access_token.clone();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ACCESS_TOKEN.lock().unwrap().to_string())
|
||||
}
|
||||
|
||||
pub fn create_resumable_upload(
|
||||
config: &YoutubeConfig,
|
||||
vid: &PeerTubeVideo,
|
||||
) -> Result<String, Box<dyn Error>> {
|
||||
let access_token = refresh_token(config)?;
|
||||
|
||||
let upload_params = YoutubeUploadParams {
|
||||
snippet: {
|
||||
YoutubeUploadParamsSnippet {
|
||||
title: vid.name.clone(),
|
||||
description: vid.description.clone(),
|
||||
tags: vid.tags.clone(),
|
||||
..Default::default()
|
||||
}
|
||||
},
|
||||
status: {
|
||||
YoutubeUploadParamsStatus {
|
||||
..Default::default()
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let client = reqwest::blocking::Client::new();
|
||||
|
||||
let res = client.post("https://www.googleapis.com/upload/youtube/v3/videos?uploadType=resumable&part=snippet%2Cstatus")
|
||||
.header("Authorization", format!("Bearer {}", access_token))
|
||||
.json(&upload_params)
|
||||
.send()?;
|
||||
|
||||
if res.status().is_success() {
|
||||
Ok(res
|
||||
.headers()
|
||||
.get("x-guploader-uploadid")
|
||||
.ok_or("Cannot find suitable header")?
|
||||
.to_str()?
|
||||
.to_string())
|
||||
} else {
|
||||
Err(TootubeError::new("Cannot create resumable upload!").into())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn upload_video(
|
||||
f_path: &str,
|
||||
r_id: &str,
|
||||
config: &YoutubeConfig,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let access_token = refresh_token(config)?;
|
||||
|
||||
let client = reqwest::blocking::Client::new();
|
||||
|
||||
let res = client.put(format!("https://www.googleapis.com/upload/youtube/v3/videos?uploadType=resumable&part=snippet%2Cstatus&upload_id={}", r_id))
|
||||
.header("Authorization", format!("Bearer {}", access_token))
|
||||
.body(f_path.to_string())
|
||||
.send()?;
|
||||
|
||||
if res.status().is_success() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(TootubeError::new(&format!("Cannot upload video: {:?}", res.text())).into())
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user