use log::{debug, error, info, warn};
use oauth2::basic::BasicClient;
use oauth2::reqwest::async_http_client;
use oauth2::{AuthUrl, ClientId, ClientSecret, Scope, TokenResponse, TokenUrl};
use reqwest::header::{HeaderMap, AUTHORIZATION};
use reqwest::Client;
use serde::Deserialize;
use url::Url;
use clio_auth::AuthContext;
#[tokio::main]
async fn main() {
pretty_env_logger::init();
debug!("😃 I'm alive");
let yt_readonly = Scope::new("https://www.googleapis.com/auth/youtube.readonly".to_string());
let yt_force_ssl = Scope::new("https://www.googleapis.com/auth/youtube.force-ssl".to_string());
let g_user_info = Scope::new("https://www.googleapis.com/auth/userinfo.profile".to_string());
let mut auth = clio_auth::CliOAuth::builder()
.timeout(30)
.scope(g_user_info)
.scope(yt_force_ssl)
.scope(yt_readonly)
.build()
.unwrap();
let client_id =
"576721077498-7iacq9cpl4a5al4no0crbta6pet36t44.apps.googleusercontent.com".to_string();
let client_secret = "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".to_string();
let auth_url = "https://accounts.google.com/o/oauth2/v2/auth".to_string();
let token_url = "https://oauth2.googleapis.com/token".to_string();
let oauth_client = BasicClient::new(
ClientId::new(client_id),
Some(ClientSecret::new(client_secret)),
AuthUrl::new(auth_url).unwrap(),
Some(TokenUrl::new(token_url).unwrap()),
)
.set_redirect_uri(auth.redirect_url());
info!("🟢 starting...");
match auth.authorize(&oauth_client).await {
Ok(()) => info!("✅ authorized successfully"),
Err(e) => warn!("⚠️ uh oh! {e:?}"),
};
match auth.validate() {
Ok(AuthContext {
auth_code,
pkce_verifier,
state: _,
}) => {
info!("✅ auth code is good to go");
let token_result = oauth_client
.exchange_code(auth_code)
.set_pkce_verifier(pkce_verifier)
.request_async(async_http_client)
.await;
if let Ok(token_result) = token_result {
let access_token = token_result.access_token();
info!("🔑 token type: {:?}", token_result.token_type());
info!("🔑 scopes: {:?}", token_result.scopes().unwrap());
let access_token = access_token.secret();
match build_client(access_token) {
Ok(client) => {
info!("📞 Invoking Google/YouTube APIs...");
show_account_info(&client).await;
search_for_videos(&client).await;
}
Err(e) => error!("💀 error building HTTP client: {e:?}"),
}
} else {
error!(
"💀 error exchanging auth code: {:?}",
token_result.unwrap_err()
);
}
}
Err(e) => warn!("⚠️ uh oh! {e:?}"),
};
info!("🏁 finished!");
}
fn build_client(access_token: &String) -> reqwest::Result<Client> {
let mut headers = HeaderMap::with_capacity(1);
headers.insert(
AUTHORIZATION,
format!("Bearer {access_token}").parse().unwrap(),
);
Client::builder().default_headers(headers).build()
}
async fn show_account_info(client: &Client) {
match client
.get(
"https://www.googleapis.com/oauth2/v2/userinfo"
.parse::<Url>()
.unwrap(),
)
.query(&[("alt", "json")])
.send()
.await
{
Ok(response) => match response.json::<UserInfo>().await {
Ok(user) => {
info!("🧍 User {}: {}", user.id, user.name);
info!("📸 Avatar: {}", user.picture);
}
Err(e) => error!("💀 response parsing error: {e:?}"),
},
Err(e) => error!("💀 request error: {e:?}"),
}
}
async fn search_for_videos(client: &Client) {
match client
.get(
"https://www.googleapis.com/youtube/v3/search"
.parse::<Url>()
.unwrap(),
)
.query(&[
("maxResults", "5"),
("part", "snippet"),
("q", "never gonna give you up"),
])
.send()
.await
{
Ok(response) => match response.json::<SearchResults>().await {
Ok(result) => {
result.items.into_iter().for_each(|result| {
info!(
"🎬 https://www.youtube.com/watch?v={}: {}",
result.id.video_id, result.snippet.title
)
});
}
Err(e) => error!("💀 response parsing error: {e:?}"),
},
Err(e) => error!("💀 request error: {e:?}"),
}
}
#[derive(Deserialize, Debug)]
struct UserInfo {
id: String,
name: String,
picture: String,
}
#[derive(Deserialize, Debug)]
struct SearchResults {
items: Vec<SearchResult>,
}
#[derive(Deserialize, Debug)]
struct SearchResult {
id: VideoId,
snippet: Snippet,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
struct VideoId {
video_id: String,
}
#[derive(Deserialize, Debug)]
struct Snippet {
title: String,
}