mod auth;
mod media;
pub mod schema;
mod streaming;
use serde::Serialize;
use crate::api::{self, ApiClient, CallOptions, RequestOptions};
use crate::auth::Auth;
use crate::cli::{Cli, Commands};
use crate::config::Config;
use crate::error::{Result, XurlError};
use crate::output::OutputConfig;
fn print_typed<T: Serialize>(out: &OutputConfig, response: &T) -> Result<()> {
let value = serde_json::to_value(response)?;
out.print_response(&value);
Ok(())
}
pub fn run(cli: Cli, out: &OutputConfig) -> Result<()> {
let cfg = Config::new();
let mut auth = Auth::new(&cfg);
if let Some(ref app_name) = cli.app {
auth.with_app_name(app_name);
}
let no_interactive = cli.no_interactive;
match cli.command {
Some(cmd) => run_subcommand(cmd, &cfg, auth, no_interactive, out),
None => run_raw_mode(&cli, &cfg, auth, out),
}
}
fn run_raw_mode(cli: &Cli, cfg: &Config, auth: Auth, out: &OutputConfig) -> Result<()> {
let url = if let Some(u) = &cli.url {
u.clone()
} else {
return Err(XurlError::validation(
"No URL provided. Usage: xr [OPTIONS] [URL] [COMMAND]. Try 'xr --help' for more information.",
));
};
let method = cli.method.clone().unwrap_or_else(|| "GET".to_string());
let media_file = cli.file.clone().unwrap_or_default();
let mut client = ApiClient::new(cfg, auth);
let options = RequestOptions {
method,
endpoint: url.clone(),
headers: cli.headers.clone(),
data: cli.data.clone().unwrap_or_default(),
auth_type: cli.auth_type.clone().unwrap_or_default(),
username: cli.username.clone().unwrap_or_default(),
no_auth: false,
verbose: cli.verbose,
trace: cli.trace,
};
if api::is_media_append_request(&options.endpoint, &media_file) {
let response = api::handle_media_append_request(&options, &media_file, &mut client)?;
out.print_response(&response);
return Ok(());
}
let should_stream = cli.stream || api::is_streaming_endpoint(&options.endpoint);
if should_stream {
streaming::stream_request_with_output(&mut client, &options, out)
} else {
let response = client.send_request(&options)?;
out.print_response(&response);
Ok(())
}
}
#[allow(clippy::too_many_lines)]
fn run_subcommand(
cmd: Commands,
cfg: &Config,
auth: Auth,
no_interactive: bool,
out: &OutputConfig,
) -> Result<()> {
match cmd {
Commands::Post {
text,
media_ids,
common,
} => {
let mut client = ApiClient::new(cfg, auth);
let opts = common.to_call_options();
let response = client.create_post(&text, &media_ids, &opts)?;
print_typed(out, &response)?;
}
Commands::Reply {
post_id,
text,
media_ids,
common,
} => {
let mut client = ApiClient::new(cfg, auth);
let opts = common.to_call_options();
let response = client.reply_to_post(&post_id, &text, &media_ids, &opts)?;
print_typed(out, &response)?;
}
Commands::Quote {
post_id,
text,
common,
} => {
let mut client = ApiClient::new(cfg, auth);
let opts = common.to_call_options();
let response = client.quote_post(&post_id, &text, &opts)?;
print_typed(out, &response)?;
}
Commands::Delete { post_id, common } => {
let mut client = ApiClient::new(cfg, auth);
let opts = common.to_call_options();
let response = client.delete_post(&post_id, &opts)?;
print_typed(out, &response)?;
}
Commands::Read { post_id, common } => {
let mut client = ApiClient::new(cfg, auth);
let opts = common.to_call_options();
let response = client.read_post(&post_id, &opts)?;
print_typed(out, &response)?;
}
Commands::Search {
query,
max_results,
common,
} => {
let mut client = ApiClient::new(cfg, auth);
let opts = common.to_call_options();
let response = client.search_posts(&query, max_results, &opts)?;
print_typed(out, &response)?;
}
Commands::Whoami { common } => {
let mut client = ApiClient::new(cfg, auth);
let opts = common.to_call_options();
let response = client.get_me(&opts)?;
print_typed(out, &response)?;
}
Commands::User {
target_username,
common,
} => {
let mut client = ApiClient::new(cfg, auth);
let opts = common.to_call_options();
let response = client.lookup_user(&target_username, &opts)?;
print_typed(out, &response)?;
}
Commands::Timeline {
max_results,
common,
} => {
let mut client = ApiClient::new(cfg, auth);
let opts = common.to_call_options();
let user_id = resolve_my_user_id(&mut client, &opts)?;
let response = client.get_timeline(&user_id, max_results, &opts)?;
print_typed(out, &response)?;
}
Commands::Mentions {
max_results,
common,
} => {
let mut client = ApiClient::new(cfg, auth);
let opts = common.to_call_options();
let user_id = resolve_my_user_id(&mut client, &opts)?;
let response = client.get_mentions(&user_id, max_results, &opts)?;
print_typed(out, &response)?;
}
Commands::Like { post_id, common } => {
let mut client = ApiClient::new(cfg, auth);
let opts = common.to_call_options();
let user_id = resolve_my_user_id(&mut client, &opts)?;
let response = client.like_post(&user_id, &post_id, &opts)?;
print_typed(out, &response)?;
}
Commands::Unlike { post_id, common } => {
let mut client = ApiClient::new(cfg, auth);
let opts = common.to_call_options();
let user_id = resolve_my_user_id(&mut client, &opts)?;
let response = client.unlike_post(&user_id, &post_id, &opts)?;
print_typed(out, &response)?;
}
Commands::Repost { post_id, common } => {
let mut client = ApiClient::new(cfg, auth);
let opts = common.to_call_options();
let user_id = resolve_my_user_id(&mut client, &opts)?;
let response = client.repost(&user_id, &post_id, &opts)?;
print_typed(out, &response)?;
}
Commands::Unrepost { post_id, common } => {
let mut client = ApiClient::new(cfg, auth);
let opts = common.to_call_options();
let user_id = resolve_my_user_id(&mut client, &opts)?;
let response = client.unrepost(&user_id, &post_id, &opts)?;
print_typed(out, &response)?;
}
Commands::Bookmark { post_id, common } => {
let mut client = ApiClient::new(cfg, auth);
let opts = common.to_call_options();
let user_id = resolve_my_user_id(&mut client, &opts)?;
let response = client.bookmark(&user_id, &post_id, &opts)?;
print_typed(out, &response)?;
}
Commands::Unbookmark { post_id, common } => {
let mut client = ApiClient::new(cfg, auth);
let opts = common.to_call_options();
let user_id = resolve_my_user_id(&mut client, &opts)?;
let response = client.unbookmark(&user_id, &post_id, &opts)?;
print_typed(out, &response)?;
}
Commands::Bookmarks {
max_results,
common,
} => {
let mut client = ApiClient::new(cfg, auth);
let opts = common.to_call_options();
let user_id = resolve_my_user_id(&mut client, &opts)?;
let response = client.get_bookmarks(&user_id, max_results, &opts)?;
print_typed(out, &response)?;
}
Commands::Likes {
max_results,
common,
} => {
let mut client = ApiClient::new(cfg, auth);
let opts = common.to_call_options();
let user_id = resolve_my_user_id(&mut client, &opts)?;
let response = client.get_liked_posts(&user_id, max_results, &opts)?;
print_typed(out, &response)?;
}
Commands::Follow {
target_username,
common,
} => {
let mut client = ApiClient::new(cfg, auth);
let opts = common.to_call_options();
let my_id = resolve_my_user_id(&mut client, &opts)?;
let target_id = resolve_user_id(&mut client, &target_username, &opts)?;
let response = client.follow_user(&my_id, &target_id, &opts)?;
print_typed(out, &response)?;
}
Commands::Unfollow {
target_username,
common,
} => {
let mut client = ApiClient::new(cfg, auth);
let opts = common.to_call_options();
let my_id = resolve_my_user_id(&mut client, &opts)?;
let target_id = resolve_user_id(&mut client, &target_username, &opts)?;
let response = client.unfollow_user(&my_id, &target_id, &opts)?;
print_typed(out, &response)?;
}
Commands::Following {
max_results,
of,
common,
} => {
let mut client = ApiClient::new(cfg, auth);
let opts = common.to_call_options();
let user_id = if let Some(ref target) = of {
resolve_user_id(&mut client, target, &opts)?
} else {
resolve_my_user_id(&mut client, &opts)?
};
let response = client.get_following(&user_id, max_results, &opts)?;
print_typed(out, &response)?;
}
Commands::Followers {
max_results,
of,
common,
} => {
let mut client = ApiClient::new(cfg, auth);
let opts = common.to_call_options();
let user_id = if let Some(ref target) = of {
resolve_user_id(&mut client, target, &opts)?
} else {
resolve_my_user_id(&mut client, &opts)?
};
let response = client.get_followers(&user_id, max_results, &opts)?;
print_typed(out, &response)?;
}
Commands::Block {
target_username,
common,
} => {
let mut client = ApiClient::new(cfg, auth);
let opts = common.to_call_options();
let my_id = resolve_my_user_id(&mut client, &opts)?;
let target_id = resolve_user_id(&mut client, &target_username, &opts)?;
let response = client.block_user(&my_id, &target_id, &opts)?;
print_typed(out, &response)?;
}
Commands::Unblock {
target_username,
common,
} => {
let mut client = ApiClient::new(cfg, auth);
let opts = common.to_call_options();
let my_id = resolve_my_user_id(&mut client, &opts)?;
let target_id = resolve_user_id(&mut client, &target_username, &opts)?;
let response = client.unblock_user(&my_id, &target_id, &opts)?;
print_typed(out, &response)?;
}
Commands::Mute {
target_username,
common,
} => {
let mut client = ApiClient::new(cfg, auth);
let opts = common.to_call_options();
let my_id = resolve_my_user_id(&mut client, &opts)?;
let target_id = resolve_user_id(&mut client, &target_username, &opts)?;
let response = client.mute_user(&my_id, &target_id, &opts)?;
print_typed(out, &response)?;
}
Commands::Unmute {
target_username,
common,
} => {
let mut client = ApiClient::new(cfg, auth);
let opts = common.to_call_options();
let my_id = resolve_my_user_id(&mut client, &opts)?;
let target_id = resolve_user_id(&mut client, &target_username, &opts)?;
let response = client.unmute_user(&my_id, &target_id, &opts)?;
print_typed(out, &response)?;
}
Commands::Usage { common } => {
let mut client = ApiClient::new(cfg, auth);
let opts = common.to_call_options();
let response = client.get_usage(&opts)?;
print_typed(out, &response)?;
}
Commands::Dm {
target_username,
text,
common,
} => {
let mut client = ApiClient::new(cfg, auth);
let opts = common.to_call_options();
let target_id = resolve_user_id(&mut client, &target_username, &opts)?;
let response = client.send_dm(&target_id, &text, &opts)?;
print_typed(out, &response)?;
}
Commands::Dms {
max_results,
common,
} => {
let mut client = ApiClient::new(cfg, auth);
let opts = common.to_call_options();
let response = client.get_dm_events(max_results, &opts)?;
print_typed(out, &response)?;
}
Commands::Auth { command } => {
return auth::run_auth_command(command, auth, no_interactive, out);
}
Commands::Media { command } => {
return media::run_media_command(command, cfg, auth, out);
}
Commands::Schema { .. } => {
unreachable!("schema is handled before config init in main()")
}
Commands::Completions { .. } => {
unreachable!("completions is handled before config init in main()")
}
Commands::Version => {
unreachable!("version is handled before config init in main()")
}
}
Ok(())
}
fn resolve_my_user_id(client: &mut ApiClient, opts: &CallOptions) -> Result<String> {
let resp = client.get_me(opts)?;
let id = &resp.data.id;
if id.is_empty() {
return Err(XurlError::auth(
"user ID was empty -- check your auth tokens",
));
}
Ok(id.clone())
}
fn resolve_user_id(client: &mut ApiClient, username: &str, opts: &CallOptions) -> Result<String> {
let resp = client.lookup_user(username, opts)?;
let id = &resp.data.id;
if id.is_empty() {
let clean = username.trim_start_matches('@');
return Err(XurlError::validation(format!("user @{clean} not found")));
}
Ok(id.clone())
}