From 396d4816053d7cd76f66092c7117b1fc460ae747 Mon Sep 17 00:00:00 2001 From: Travis Brown Date: Mon, 23 Mar 2026 11:31:17 +0100 Subject: [PATCH 1/2] Add search-all command --- core/src/bin/meta-ads-access.rs | 102 ++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/core/src/bin/meta-ads-access.rs b/core/src/bin/meta-ads-access.rs index 2b5561f..727a79e 100644 --- a/core/src/bin/meta-ads-access.rs +++ b/core/src/bin/meta-ads-access.rs @@ -110,6 +110,78 @@ async fn main() -> Result<(), Error> { writer.flush()?; } + Command::SearchAll { + creds, + version, + query_file, + country, + exact, + output, + limit, + full, + full_output, + delay, + } => { + let creds: Creds = toml::from_str(&std::fs::read_to_string(creds)?)?; + log_token_status(creds.status(Utc::now())); + + let client = meta_ads_access::client::Client::new(creds.token, output.as_deref()); + let library_client = + meta_ads_access::library::Client::new::<_, String>(full_output.as_deref(), None)?; + + let search_type = if exact { + meta_ads_access::client::request::SearchType::KeywordExactPhrase + } else { + meta_ads_access::client::request::SearchType::KeywordUnordered + }; + + let queries = std::fs::read_to_string(&query_file)?; + + let mut writer = csv::WriterBuilder::new() + .has_headers(false) + .from_writer(std::io::stdout()); + + for terms in queries + .lines() + .map(str::trim) + .filter(|line| !line.is_empty()) + { + let results = client + .search(&meta_ads_access::client::SearchOptions { + version, + terms, + countries: &country, + search_type, + after: None, + limit, + delay: std::time::Duration::from_secs(delay), + }) + .await?; + + for result in results { + match result.result() { + Ok(ads) => { + for ad in ads { + writer.write_record([ + ad.id.to_string(), + ad.page_id.to_string(), + ad.page_name.to_string(), + ])?; + + if full { + library_client.app(ad.id).await?; + } + } + } + Err(error) => { + ::log::warn!("{}", error.message); + } + } + } + } + + writer.flush()?; + } Command::LibraryAd { id, output } => { let client = meta_ads_access::library::Client::new::<_, String>(output, None)?; @@ -279,6 +351,36 @@ enum Command { #[clap(long, default_value = "0")] delay: u64, }, + /// Perform searches for a list of queries provided as lines in the indicated text file + SearchAll { + #[clap(long, default_value = "creds.toml")] + creds: PathBuf, + #[clap(long, default_value = "24.0")] + version: GraphApiVersion, + /// Path to a file with one search query per line + #[clap(long)] + query_file: PathBuf, + #[clap(long, default_value = "DE")] + country: Vec, + #[clap(long)] + exact: bool, + /// Archive directory to log requests and responses to + #[clap(long, default_value = "data/search")] + output: Option, + /// Limit to a specified number of pages per query + #[clap(long)] + limit: Option, + /// Download full ad information + #[clap(long)] + full: bool, + /// Archive directory to log full requests and responses to + #[clap(long, default_value = "data/library")] + full_output: Option, + /// Optional duration (in seconds) between requests + #[clap(long, default_value = "0")] + delay: u64, + }, + /// Download ad for the specified ID LibraryAd { #[clap(long)] id: u64, From 965f64367879df3089b60bf07001cd870c028a02 Mon Sep 17 00:00:00 2001 From: Travis Brown Date: Mon, 23 Mar 2026 11:35:56 +0100 Subject: [PATCH 2/2] Allow individual queries to be exact or not --- core/src/bin/meta-ads-access.rs | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/core/src/bin/meta-ads-access.rs b/core/src/bin/meta-ads-access.rs index 727a79e..b9fd1c6 100644 --- a/core/src/bin/meta-ads-access.rs +++ b/core/src/bin/meta-ads-access.rs @@ -1,6 +1,7 @@ use chrono::Utc; use cli_helpers::prelude::*; use meta_ads_access::{ + client::request::SearchType, model::{Ad, Response}, token::Creds, version::GraphApiVersion, @@ -66,9 +67,9 @@ async fn main() -> Result<(), Error> { meta_ads_access::library::Client::new::<_, String>(full_output.as_deref(), None)?; let search_type = if exact { - meta_ads_access::client::request::SearchType::KeywordExactPhrase + SearchType::KeywordExactPhrase } else { - meta_ads_access::client::request::SearchType::KeywordUnordered + SearchType::KeywordUnordered }; let results = client @@ -115,7 +116,6 @@ async fn main() -> Result<(), Error> { version, query_file, country, - exact, output, limit, full, @@ -129,23 +129,23 @@ async fn main() -> Result<(), Error> { let library_client = meta_ads_access::library::Client::new::<_, String>(full_output.as_deref(), None)?; - let search_type = if exact { - meta_ads_access::client::request::SearchType::KeywordExactPhrase - } else { - meta_ads_access::client::request::SearchType::KeywordUnordered - }; - let queries = std::fs::read_to_string(&query_file)?; let mut writer = csv::WriterBuilder::new() .has_headers(false) .from_writer(std::io::stdout()); - for terms in queries + for line in queries .lines() .map(str::trim) .filter(|line| !line.is_empty()) { + let (terms, search_type) = if line.starts_with('"') && line.ends_with('"') { + (&line[1..line.len() - 1], SearchType::KeywordExactPhrase) + } else { + (line, SearchType::KeywordUnordered) + }; + let results = client .search(&meta_ads_access::client::SearchOptions { version, @@ -362,8 +362,6 @@ enum Command { query_file: PathBuf, #[clap(long, default_value = "DE")] country: Vec, - #[clap(long)] - exact: bool, /// Archive directory to log requests and responses to #[clap(long, default_value = "data/search")] output: Option,