use night_fury_core::BrowserSession;
use night_fury_daemon_core::protocol::Response;
use serde_json::Value;
use tail_fin_sa::{Period, SeekingAlphaApi, SeekingAlphaClient};
pub async fn handle(session: &BrowserSession, id: &str, cmd: &str, params: &Value) -> Response {
let client = SeekingAlphaClient::new(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 required_str(params: &Value, key: &str) -> Result<String, String> {
params
.get(key)
.and_then(|v| v.as_str())
.map(String::from)
.ok_or_else(|| format!("missing required param '{key}'"))
}
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)")),
}
}
fn to_response<T: serde::Serialize>(
id: &str,
result: Result<T, tail_fin_common::TailFinError>,
) -> Response {
match result {
Ok(value) => match serde_json::to_value(&value) {
Ok(json) => Response::ok(id, json),
Err(e) => Response::err(id, format!("serialize error: {e}")),
},
Err(e) => Response::err(id, e.to_string()),
}
}