|
|
|
//! 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<HttpSender>;
|
|
|
|
pub type EleSession = elefren::Mastodon<HttpSender>;
|
|
|
|
pub type EleStreamSocket = websocket::sync::Client<native_tls::TlsStream<std::net::TcpStream>>;
|
|
|
|
|
|
|
|
/// 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::<ElefrenRegistration>(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::<elefren::Data>(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<EleStreamSocket> {
|
|
|
|
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<u8>),
|
|
|
|
/// 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<OwnedMessage>) -> Fallible<Option<ParsedSocketMsg>> {
|
|
|
|
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::<StreamingApiRawMessage>(&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<ParsedSocketMsg> {
|
|
|
|
Ok(match &rawmsg.event[..] {
|
|
|
|
"notification" => {
|
|
|
|
let notification = serde_json::from_str::<Notification>(&rawmsg.payload)?;
|
|
|
|
ParsedSocketMsg::Event(Event::Notification(notification))
|
|
|
|
},
|
|
|
|
"update" => {
|
|
|
|
let status = serde_json::from_str::<Status>(&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))
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|