141 lines
4.3 KiB
Rust
141 lines
4.3 KiB
Rust
use std::error::Error;
|
|
use std::path::PathBuf;
|
|
|
|
use google_api_auth;
|
|
use google_photoslibrary1;
|
|
use google_photoslibrary1::schemas::SearchMediaItemsRequest;
|
|
use regex::Regex;
|
|
use structopt::StructOpt;
|
|
use yup_oauth2::{Authenticator, InstalledFlow};
|
|
|
|
#[derive(Debug, StructOpt)]
|
|
enum Command {
|
|
/// List albums for the user of the given credentials. Optionally title filter.
|
|
ListAlbums {
|
|
title_filter: Option<Regex>,
|
|
},
|
|
SearchMediaItems {
|
|
album_id: String,
|
|
},
|
|
}
|
|
|
|
#[derive(Debug, StructOpt)]
|
|
#[structopt(
|
|
name = "photosync",
|
|
about = "Utility for interacting with Google Photos API."
|
|
)]
|
|
struct Opt {
|
|
/// Activate debug mode
|
|
#[structopt(short, parse(from_occurrences))]
|
|
verbose: u32,
|
|
|
|
/// Path to json file containing Google client ID and secrets for out of band auth flow.
|
|
#[structopt(long)]
|
|
credentials: PathBuf,
|
|
/// Path to json file where photosync will store auth tokens refreshed from Google.
|
|
#[structopt(long)]
|
|
token_cache: PathBuf,
|
|
|
|
#[structopt(subcommand)]
|
|
cmd: Command,
|
|
}
|
|
|
|
fn new_client(
|
|
credentials: &PathBuf,
|
|
token_cache: &PathBuf,
|
|
) -> Result<google_photoslibrary1::Client, Box<dyn Error>> {
|
|
let secret = yup_oauth2::read_application_secret(credentials)?;
|
|
|
|
// Create an authenticator that uses an InstalledFlow to authenticate. The
|
|
// authentication tokens are persisted to a file named tokencache.json. The
|
|
// authenticator takes care of caching tokens to disk and refreshing tokens once
|
|
// they've expired.
|
|
let auth = Authenticator::new(InstalledFlow::new(
|
|
secret,
|
|
yup_oauth2::InstalledFlowReturnMethod::Interactive,
|
|
))
|
|
.persist_tokens_to_disk(token_cache)
|
|
.build()
|
|
.unwrap();
|
|
|
|
let scopes = vec!["https://www.googleapis.com/auth/photoslibrary.readonly".to_string()];
|
|
|
|
let auth = google_api_auth::yup_oauth2::from_authenticator(auth, scopes);
|
|
|
|
Ok(google_photoslibrary1::Client::new(auth))
|
|
}
|
|
|
|
fn search_media_items(
|
|
client: google_photoslibrary1::Client,
|
|
album_id: String,
|
|
) -> Result<(), Box<dyn Error>> {
|
|
let mut page_token = None;
|
|
let mut total = 0;
|
|
loop {
|
|
let resp = client
|
|
.media_items()
|
|
.search(SearchMediaItemsRequest {
|
|
album_id: Some(album_id.clone()),
|
|
// 100 is the documented max.
|
|
page_size: Some(100),
|
|
page_token,
|
|
..Default::default()
|
|
})
|
|
.execute_with_all_fields()?;
|
|
|
|
let media_items = resp.media_items.ok_or("no results")?;
|
|
println!("got ({}) items", media_items.len());
|
|
total += media_items.len();
|
|
for mi in media_items {
|
|
println!(
|
|
"{} {}",
|
|
mi.id.unwrap_or("NO ID".to_string()),
|
|
mi.filename.unwrap_or("NO FILENAME".to_string())
|
|
);
|
|
}
|
|
page_token = resp.next_page_token;
|
|
if page_token.is_none() {
|
|
println!("({}) items total", total);
|
|
return Ok(());
|
|
}
|
|
}
|
|
}
|
|
|
|
fn list_albums(
|
|
client: google_photoslibrary1::Client,
|
|
title_filter: Option<Regex>,
|
|
) -> Result<(), Box<dyn Error>> {
|
|
for album in client
|
|
.shared_albums()
|
|
.list()
|
|
.iter_shared_albums_with_all_fields()
|
|
{
|
|
let a = album?;
|
|
match (&title_filter, &a.title) {
|
|
// Print everything when no filter or title.
|
|
(None, None) => {}
|
|
// skip when filter given but the media item doesn't have a title (it can't match)
|
|
(_, None) => continue,
|
|
// skip when the media item doesn't match the filter
|
|
(Some(title_filter), Some(title)) if !title_filter.is_match(&title) => continue,
|
|
}
|
|
println!(
|
|
"album: {} {} ({} items)",
|
|
a.id.unwrap_or("NO ID".to_string()),
|
|
a.title.unwrap_or("NO TITLE".to_string()).to_string(),
|
|
a.media_items_count.unwrap_or(0)
|
|
);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
let opt = Opt::from_args();
|
|
println!("opt: {:?}", opt);
|
|
let client = new_client(&opt.credentials, &opt.token_cache)?;
|
|
match opt.cmd {
|
|
Command::ListAlbums { title_filter } => list_albums(client, title_filter),
|
|
Command::SearchMediaItems { album_id } => search_media_items(client, album_id),
|
|
}
|
|
}
|