tail-fin-cli 0.3.1

Multi-site browser automation CLI — attaches to Chrome or auto-launches a stealth browser to drive 14+ sites
use clap::Subcommand;
use tail_fin_common::TailFinError;

use crate::session::{browser_session, print_json, print_list, Ctx};

#[derive(Subcommand)]
pub enum BloombergAction {
    /// Homepage top stories
    Main {
        #[arg(long, default_value_t = 20)]
        limit: usize,
    },
    /// Markets section headlines
    Markets {
        #[arg(long, default_value_t = 20)]
        limit: usize,
    },
    /// Economics section headlines
    Economics {
        #[arg(long, default_value_t = 20)]
        limit: usize,
    },
    /// Industries section headlines
    Industries {
        #[arg(long, default_value_t = 20)]
        limit: usize,
    },
    /// Technology section headlines
    Tech {
        #[arg(long, default_value_t = 20)]
        limit: usize,
    },
    /// Politics section headlines
    Politics {
        #[arg(long, default_value_t = 20)]
        limit: usize,
    },
    /// Businessweek section headlines
    Businessweek {
        #[arg(long, default_value_t = 20)]
        limit: usize,
    },
    /// List available RSS feed aliases
    Feeds,
    /// Extract article from a Bloomberg story page (browser mode)
    News {
        /// Bloomberg article URL
        url: String,
    },
}

pub async fn run(action: BloombergAction, ctx: &Ctx) -> Result<(), TailFinError> {
    match action {
        BloombergAction::Feeds => {
            let feeds = tail_fin_bloomberg::BloombergClient::feeds();
            print_list("feeds", &feeds, feeds.len())?;
        }
        BloombergAction::News { url } => {
            let host = ctx.connect.as_deref().ok_or_else(|| {
                TailFinError::Api("news requires --connect for browser mode".into())
            })?;
            let session = browser_session(host, ctx.headed).await?;
            let client = tail_fin_bloomberg::BloombergClient::with_session(session);
            let article = client.news(&url).await?;
            print_json(&article)?;
        }
        _ => {
            let feed_name = match &action {
                BloombergAction::Main { .. } => "main",
                BloombergAction::Markets { .. } => "markets",
                BloombergAction::Economics { .. } => "economics",
                BloombergAction::Industries { .. } => "industries",
                BloombergAction::Tech { .. } => "tech",
                BloombergAction::Politics { .. } => "politics",
                BloombergAction::Businessweek { .. } => "businessweek",
                _ => unreachable!(),
            };
            let limit = match &action {
                BloombergAction::Main { limit }
                | BloombergAction::Markets { limit }
                | BloombergAction::Economics { limit }
                | BloombergAction::Industries { limit }
                | BloombergAction::Tech { limit }
                | BloombergAction::Politics { limit }
                | BloombergAction::Businessweek { limit } => *limit,
                _ => unreachable!(),
            };
            let client = tail_fin_bloomberg::BloombergClient::without_session();
            let items = client.fetch_feed(feed_name, limit).await?;
            print_list("items", &items, items.len())?;
        }
    }
    Ok(())
}

pub struct Adapter;

impl crate::adapter::CliAdapter for Adapter {
    fn name(&self) -> &'static str {
        "bloomberg"
    }

    fn about(&self) -> &'static str {
        "Bloomberg news operations"
    }

    fn command(&self) -> clap::Command {
        <BloombergAction as clap::Subcommand>::augment_subcommands(
            clap::Command::new("bloomberg").about("Bloomberg news operations"),
        )
    }

    fn dispatch<'a>(
        &'a self,
        matches: &'a clap::ArgMatches,
        ctx: &'a crate::session::Ctx,
    ) -> std::pin::Pin<
        Box<
            dyn std::future::Future<Output = Result<(), tail_fin_common::TailFinError>> + Send + 'a,
        >,
    > {
        Box::pin(async move {
            let action = <BloombergAction as clap::FromArgMatches>::from_arg_matches(matches)
                .map_err(|e| tail_fin_common::TailFinError::Api(e.to_string()))?;
            run(action, ctx).await
        })
    }
}