use async_trait::async_trait;
use night_fury_core::BrowserSession;
use night_fury_daemon_core::protocol::Response;
use serde_json::Value;
use tail_fin_sa::{Period, SeekingAlphaApi, SeekingAlphaClient};
use crate::handlers::params::required_str;
use crate::handlers::response::to_response;
use crate::handlers::SiteHandler;
pub struct SaHandler {
session: BrowserSession,
}
impl SaHandler {
pub fn new(session: BrowserSession) -> Self {
Self { session }
}
}
#[async_trait]
impl SiteHandler for SaHandler {
async fn handle(&self, id: &str, cmd: &str, params: &Value) -> Response {
let client = SeekingAlphaClient::new(self.session.clone());
match cmd {
"sa.quote" => match required_str(params, "ticker") {
Ok(ticker) => to_response(id, client.quote(&ticker).await),
Err(e) => Response::err(id, e),
},
"sa.income-statement" => match (required_str(params, "ticker"), parse_period(params)) {
(Ok(ticker), Ok(period)) => {
to_response(id, client.income_statement(&ticker, period).await)
}
(Err(e), _) | (_, Err(e)) => Response::err(id, e),
},
"sa.balance-sheet" => match (required_str(params, "ticker"), parse_period(params)) {
(Ok(ticker), Ok(period)) => {
to_response(id, client.balance_sheet(&ticker, period).await)
}
(Err(e), _) | (_, Err(e)) => Response::err(id, e),
},
"sa.cash-flow" => match (required_str(params, "ticker"), parse_period(params)) {
(Ok(ticker), Ok(period)) => {
to_response(id, client.cash_flow(&ticker, period).await)
}
(Err(e), _) | (_, Err(e)) => Response::err(id, e),
},
"sa.news" => handle_articles(id, &client, params, "news").await,
"sa.analysis" => handle_articles(id, &client, params, "analysis").await,
"sa.article" => match required_str(params, "id") {
Ok(aid) => to_response(id, client.article(&aid).await),
Err(e) => Response::err(id, e),
},
other => Response::err(id, format!("unknown sa cmd: {other}")),
}
}
}
async fn handle_articles(
id: &str,
client: &SeekingAlphaClient,
params: &Value,
kind: &str,
) -> Response {
let ticker = match required_str(params, "ticker") {
Ok(t) => t,
Err(e) => return Response::err(id, e),
};
let count = params.get("count").and_then(|v| v.as_u64()).unwrap_or(20) as usize;
let since = params.get("since").and_then(|v| v.as_i64());
let until = params.get("until").and_then(|v| v.as_i64());
let result = if kind == "news" {
client.news(&ticker, count, since, until).await
} else {
client.analysis(&ticker, count, since, until).await
};
to_response(id, result)
}
fn parse_period(params: &Value) -> Result<Period, String> {
let s = params
.get("period")
.and_then(|v| v.as_str())
.unwrap_or("annual");
match s.to_lowercase().as_str() {
"annual" | "a" => Ok(Period::Annual),
"quarterly" | "q" => Ok(Period::Quarterly),
other => Err(format!("invalid period '{other}' (annual|quarterly)")),
}
}