//! Mastodon API types and functions building on elefren use serde::Deserialize; use serde::Serialize; use crate::store::Store; use crate::Config; use elefren::{Registration, Mastodon}; use elefren::http_send::HttpSender; use elefren::helpers::cli; use elefren::scopes::Scopes; use elefren::entities::prelude::{Status, Notification, Event}; use std::str::FromStr; use failure::Fallible; use websocket::{WebSocketResult, OwnedMessage}; const KEY_OAUTH_REGISTRATION: &str = "oauth.registration"; const KEY_OAUTH_SESSION: &str = "oauth.session"; pub type EleRegistratered = elefren::registration::Registered; pub type EleSession = elefren::Mastodon; pub type EleStreamSocket = websocket::sync::Client>; /// Wrapper for the long tuple with Registration state #[derive(Serialize,Deserialize,Debug)] pub struct ElefrenRegistration { pub parts: (String, String, String, String, Scopes, bool), } /// Register "app" in the server software pub fn register(store : &mut Store, config : &Config) -> EleRegistratered { match store.get::(KEY_OAUTH_REGISTRATION) { Some(reg) => { info!("Loaded registration from store"); EleRegistratered::from_parts( // this sucks ®.parts.0, ®.parts.1, ®.parts.2, ®.parts.3, reg.parts.4, reg.parts.5 ) } None => { info!("Creating a new registration"); let registered = Registration::new(&format!("https://{}", config.instance)) .client_name("manabu") .scopes(Scopes::from_str("read write").expect("err parse scopes")) .build().expect("error register"); store.put(KEY_OAUTH_REGISTRATION, ElefrenRegistration { parts : registered.clone().into_parts() }); registered } } } /// Open mastodon API session (get access token) pub fn open_session(store : &mut Store, registered: EleRegistratered) -> EleSession { match store.get::(KEY_OAUTH_SESSION) { Some(data) => { info!("Reusing saved authorization."); let cli = Mastodon::from(data); // TODO check if the session is live, somehow cli } None => { info!("Creating new authorization."); let cli = cli::authenticate(registered).expect("error auth"); store.put(KEY_OAUTH_SESSION, cli.data.clone()); cli } } } /// Open streaming api websocket pub fn open_stream_websocket(session : &EleSession, stream_name : &str) -> Fallible { let connector = native_tls::TlsConnector::new()?; let hostname = &session.data.base[session.data.base.find("://").unwrap()+3..]; let url = format!("wss://{host}/api/v1/streaming/?stream={sname}&access_token={token}", host=hostname, sname=stream_name, token=session.data.token); debug!("WS url = {}", &url); Ok(websocket::ClientBuilder::new(&url) .expect("Error create ClientBuilder") .connect_secure(Some(connector))?) } #[derive(Debug)] pub enum ParsedSocketMsg { /// The socket closed WsClose, /// Server pings us WsPing(Vec), /// Update event Event(Event), } #[derive(Debug,Deserialize)] pub struct StreamingApiRawMessage { pub event : String, pub payload: String, } pub fn parse_stream_socket_msg(msg_result : WebSocketResult) -> Fallible> { Ok(match msg_result { Ok(OwnedMessage::Text(msg)) => { if msg.is_empty() { None } else { trace!("WS text msg: {}", msg); Some(parse_raw_msg(serde_json::from_str::(&msg)?)?) } } Ok(OwnedMessage::Ping(pld)) => { trace!("WS ping request"); Some(ParsedSocketMsg::WsPing(pld)) } Ok(OwnedMessage::Close(..)) => { error!("WS closed by server."); Some(ParsedSocketMsg::WsClose) } Ok(x) => { warn!("Unhandled WS message: {:?}", x); None } Err(e) => { error!("WS error: {}", e); Some(ParsedSocketMsg::WsClose) } }) } fn parse_raw_msg(rawmsg : StreamingApiRawMessage) -> Fallible { Ok(match &rawmsg.event[..] { "notification" => { let notification = serde_json::from_str::(&rawmsg.payload)?; ParsedSocketMsg::Event(Event::Notification(notification)) }, "update" => { let status = serde_json::from_str::(&rawmsg.payload)?; ParsedSocketMsg::Event(Event::Update(status)) }, "delete" => { ParsedSocketMsg::Event(Event::Delete(rawmsg.payload)) }, "filters_changed" => { ParsedSocketMsg::Event(Event::FiltersChanged) }, _ => { return Err(failure::format_err!("Invalid event type from streaming API: {}", &rawmsg.event)) } }) }