use crate::cost;
use crate::db::{BirdClient, BookmarkRow, RequestContext};
use crate::diag;
use crate::fields;
use crate::output;
use crate::requirements::AuthType;
pub fn run_bookmarks(
client: &mut BirdClient,
pretty: bool,
use_color: bool,
quiet: bool,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let auth_type = AuthType::OAuth2User;
let ctx = RequestContext {
auth_type: &auth_type,
username: None,
};
let me_response = client.get("https://api.x.com/2/users/me", &ctx)?;
if !me_response.is_success() {
return Err(format!(
"GET /2/users/me failed: {}",
output::sanitize_for_stderr(&me_response.body, 200)
)
.into());
}
let me_json = me_response.json.ok_or("invalid JSON from /2/users/me")?;
let user_id = me_json
.get("data")
.and_then(|d| d.get("id"))
.and_then(|id| id.as_str())
.ok_or("no data.id in /2/users/me response")?;
let me_estimate = cost::estimate_cost(
&me_json,
"https://api.x.com/2/users/me",
me_response.cache_hit,
);
cost::display_cost(&me_estimate, use_color, quiet);
let me_username = me_json
.get("data")
.and_then(|d| d.get("username"))
.and_then(|u| u.as_str())
.unwrap_or("")
.to_string();
let mut pagination_token: Option<String> = None;
let mut first_item = true;
let mut bookmark_rows: Vec<BookmarkRow> = Vec::new();
let mut position: i64 = 0;
if pretty {
println!("{{\n \"data\": [");
} else {
print!("{{\"data\":[");
}
loop {
let url = {
let mut u =
url::Url::parse(&format!("https://api.x.com/2/users/{}/bookmarks", user_id))
.unwrap();
{
let mut pairs = u.query_pairs_mut();
pairs.append_pair("max_results", "100");
for (key, value) in fields::tweet_query_params() {
pairs.append_pair(key, value);
}
if let Some(ref pt) = pagination_token {
pairs.append_pair("pagination_token", pt);
}
}
u.to_string()
};
let response = client.get(&url, &ctx)?;
if !response.is_success() {
return Err(format!(
"GET bookmarks failed: {}",
output::sanitize_for_stderr(&response.body, 200)
)
.into());
}
let page = response.json.ok_or("invalid JSON from bookmarks")?;
let page_estimate = cost::estimate_cost(&page, &url, response.cache_hit);
cost::display_cost(&page_estimate, use_color, quiet);
if let Some(data) = page.get("data").and_then(|d| d.as_array()) {
for item in data {
if !first_item {
if pretty {
println!(",");
} else {
print!(",");
}
}
first_item = false;
if pretty {
let s = serde_json::to_string_pretty(item)?;
for line in s.lines() {
println!(" {}", line);
}
} else {
print!("{}", serde_json::to_string(item)?);
}
if let Some(tweet_id) = item.get("id").and_then(|v| v.as_str()) {
bookmark_rows.push(BookmarkRow {
username: me_username.clone(),
tweet_id: tweet_id.to_string(),
position,
refreshed_at: crate::db::unix_now(),
});
position += 1;
}
}
}
pagination_token = page
.get("meta")
.and_then(|m| m.get("next_token"))
.and_then(|t| t.as_str())
.map(String::from);
if pagination_token.is_none() {
break;
}
}
if !me_username.is_empty()
&& !bookmark_rows.is_empty()
&& let Some(db) = client.db()
&& let Err(e) = db.replace_bookmarks(&me_username, &bookmark_rows)
{
diag!(quiet, "[store] warning: bookmark storage failed: {e}");
}
if pretty {
println!("\n ]\n}}");
} else {
println!("]}}");
}
Ok(())
}