From 638594b11bb0daade75a3f7d7ff84d699fa2c881 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Hru=C5=A1ka?= Date: Sat, 21 Aug 2021 22:17:16 +0200 Subject: [PATCH] improvements in streaming, add debug format structs --- src/data.rs | 2 +- src/debug.rs | 59 ++++++++++++++++++++++++++++++ src/helpers/cli.rs | 4 +- src/helpers/json.rs | 38 +++++++++---------- src/helpers/mod.rs | 4 +- src/helpers/toml.rs | 38 +++++++++---------- src/lib.rs | 33 ++++++++++------- src/page.rs | 8 ++-- src/registration.rs | 8 ++-- src/requests/push.rs | 12 +++--- src/requests/update_credentials.rs | 4 +- src/streaming.rs | 36 ++++++++++++++---- src/unauth.rs | 34 ++++++++++------- 13 files changed, 186 insertions(+), 94 deletions(-) create mode 100644 src/debug.rs diff --git a/src/data.rs b/src/data.rs index ed79303..e05aecf 100644 --- a/src/data.rs +++ b/src/data.rs @@ -4,7 +4,7 @@ use std::borrow::Cow; /// Raw data about mastodon app. Save `Data` using `serde` to prevent needing /// to authenticate on every run. #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -pub struct ClientData { +pub struct AppData { /// Base url of instance eg. `https://mastodon.social`. pub base: Cow<'static, str>, /// The client's id given by the instance. diff --git a/src/debug.rs b/src/debug.rs new file mode 100644 index 0000000..187d5dc --- /dev/null +++ b/src/debug.rs @@ -0,0 +1,59 @@ +use crate::entities::notification::{Notification, NotificationType}; +use std::fmt::{Display, Formatter}; +use crate::entities::event::Event; + +pub struct NotificationDisplay<'a>(pub &'a Notification); + +impl<'a> Display for NotificationDisplay<'a> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let n = self.0; + match n.notification_type { + NotificationType::Follow => { + write!(f, "Follow {{ #{}, @{} }}", n.id, n.account.acct ) + } + NotificationType::Favourite => { + if let Some(ref s) = n.status { + write!(f, "Favourite {{ #{}, acct: @{}, status: «{}» }}", n.id, n.account.acct, s.content ) + } else { + write!(f, "Favourite {{ #{}, acct: @{}, status: -- }}", n.id, n.account.acct ) + } + } + NotificationType::Mention => { + if let Some(ref s) = n.status { + write!(f, "Mention {{ #{}, acct: @{}, status: «{}» }}", n.id, n.account.acct, s.content ) + } else { + write!(f, "Mention {{ #{}, acct: @{}, status: -- }}", n.id, n.account.acct ) + } + } + NotificationType::Reblog => { + if let Some(ref s) = n.status { + write!(f, "Reblog {{ #{}, acct: @{}, status: «{}» }}", n.id, n.account.acct, s.content ) + } else { + write!(f, "Reblog {{ #{}, acct: @{}, status: -- }}", n.id, n.account.acct ) + } + } + } + } +} + +pub struct EventDisplay<'a>(pub &'a Event); + +impl<'a> Display for EventDisplay<'a> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let n = self.0; + match n { + Event::Notification(n) => { + NotificationDisplay(n).fmt(f) + } + Event::Delete(id) => { + write!(f, "Delete {{ #{} }}", id) + } + Event::FiltersChanged => { + write!(f, "FiltersChanged") + } + Event::Update(s) => { + write!(f, "Status {{ #{}, acct: @{}, status: «{}», vis: {:?} }}", s.id, s.account.acct, s.content, s.visibility ) + } + } + } +} diff --git a/src/helpers/cli.rs b/src/helpers/cli.rs index c1b4249..4163b96 100644 --- a/src/helpers/cli.rs +++ b/src/helpers/cli.rs @@ -1,10 +1,10 @@ use std::io::{self, BufRead, Write}; -use crate::{errors::Result, registration::Registered, Client}; +use crate::{errors::Result, registration::Registered, FediClient}; /// Finishes the authentication process for the given `Registered` object, /// using the command-line -pub async fn authenticate(registration: Registered) -> Result { +pub async fn authenticate(registration: Registered) -> Result { let url = registration.authorize_url()?; let stdout = io::stdout(); diff --git a/src/helpers/json.rs b/src/helpers/json.rs index f739340..504c76b 100644 --- a/src/helpers/json.rs +++ b/src/helpers/json.rs @@ -4,46 +4,46 @@ use std::{ path::Path, }; -use crate::{data::ClientData, Result}; +use crate::{data::AppData, Result}; /// Attempts to deserialize a Data struct from a string -pub fn from_str(s: &str) -> Result { +pub fn from_str(s: &str) -> Result { Ok(serde_json::from_str(s)?) } /// Attempts to deserialize a Data struct from a slice of bytes -pub fn from_slice(s: &[u8]) -> Result { +pub fn from_slice(s: &[u8]) -> Result { Ok(serde_json::from_slice(s)?) } /// Attempts to deserialize a Data struct from something that implements /// the std::io::Read trait -pub fn from_reader(mut r: R) -> Result { +pub fn from_reader(mut r: R) -> Result { let mut buffer = Vec::new(); r.read_to_end(&mut buffer)?; from_slice(&buffer) } /// Attempts to deserialize a Data struct from a file -pub fn from_file>(path: P) -> Result { +pub fn from_file>(path: P) -> Result { let path = path.as_ref(); let file = File::open(path)?; Ok(from_reader(file)?) } /// Attempts to serialize a Data struct to a String -pub fn to_string(data: &ClientData) -> Result { +pub fn to_string(data: &AppData) -> Result { Ok(serde_json::to_string_pretty(data)?) } /// Attempts to serialize a Data struct to a Vec of bytes -pub fn to_vec(data: &ClientData) -> Result> { +pub fn to_vec(data: &AppData) -> Result> { Ok(serde_json::to_vec(data)?) } /// Attempts to serialize a Data struct to something that implements the /// std::io::Write trait -pub fn to_writer(data: &ClientData, writer: W) -> Result<()> { +pub fn to_writer(data: &AppData, writer: W) -> Result<()> { let mut buf_writer = BufWriter::new(writer); let vec = to_vec(data)?; buf_writer.write_all(&vec)?; @@ -55,7 +55,7 @@ pub fn to_writer(data: &ClientData, writer: W) -> Result<()> { /// When opening the file, this will set the `.write(true)` and /// `.truncate(true)` options, use the next method for more /// fine-grained control -pub fn to_file>(data: &ClientData, path: P) -> Result<()> { +pub fn to_file>(data: &AppData, path: P) -> Result<()> { let mut options = OpenOptions::new(); options.create(true).write(true).truncate(true); to_file_with_options(data, path, options)?; @@ -63,7 +63,7 @@ pub fn to_file>(data: &ClientData, path: P) -> Result<()> { } /// Attempts to serialize a Data struct to a file -pub fn to_file_with_options>(data: &ClientData, path: P, options: OpenOptions) -> Result<()> { +pub fn to_file_with_options>(data: &AppData, path: P, options: OpenOptions) -> Result<()> { let path = path.as_ref(); let file = options.open(path)?; to_writer(data, file)?; @@ -93,7 +93,7 @@ mod tests { let desered = from_str(DOC).expect("Couldn't deserialize Data"); assert_eq!( desered, - ClientData { + AppData { base: "https://example.com".into(), client_id: "adbc01234".into(), client_secret: "0987dcba".into(), @@ -108,7 +108,7 @@ mod tests { let desered = from_slice(&doc).expect("Couldn't deserialize Data"); assert_eq!( desered, - ClientData { + AppData { base: "https://example.com".into(), client_id: "adbc01234".into(), client_secret: "0987dcba".into(), @@ -124,7 +124,7 @@ mod tests { let desered = from_reader(doc).expect("Couldn't deserialize Data"); assert_eq!( desered, - ClientData { + AppData { base: "https://example.com".into(), client_id: "adbc01234".into(), client_secret: "0987dcba".into(), @@ -140,7 +140,7 @@ mod tests { let desered = from_file(datafile.path()).expect("Couldn't deserialize Data"); assert_eq!( desered, - ClientData { + AppData { base: "https://example.com".into(), client_id: "adbc01234".into(), client_secret: "0987dcba".into(), @@ -151,7 +151,7 @@ mod tests { } #[test] fn test_to_string() { - let data = ClientData { + let data = AppData { base: "https://example.com".into(), client_id: "adbc01234".into(), client_secret: "0987dcba".into(), @@ -164,7 +164,7 @@ mod tests { } #[test] fn test_to_vec() { - let data = ClientData { + let data = AppData { base: "https://example.com".into(), client_id: "adbc01234".into(), client_secret: "0987dcba".into(), @@ -177,7 +177,7 @@ mod tests { } #[test] fn test_to_writer() { - let data = ClientData { + let data = AppData { base: "https://example.com".into(), client_id: "adbc01234".into(), client_secret: "0987dcba".into(), @@ -192,7 +192,7 @@ mod tests { } #[test] fn test_to_file() { - let data = ClientData { + let data = AppData { base: "https://example.com".into(), client_id: "adbc01234".into(), client_secret: "0987dcba".into(), @@ -207,7 +207,7 @@ mod tests { } #[test] fn test_to_file_with_options() { - let data = ClientData { + let data = AppData { base: "https://example.com".into(), client_id: "adbc01234".into(), client_secret: "0987dcba".into(), diff --git a/src/helpers/mod.rs b/src/helpers/mod.rs index 46b0020..91eb007 100644 --- a/src/helpers/mod.rs +++ b/src/helpers/mod.rs @@ -31,13 +31,13 @@ pub async fn deserialise_response serde::Deserialize<'de>>(response: let bytes = response.bytes().await?; match serde_json::from_slice(&bytes) { Ok(t) => { - debug!("{}", String::from_utf8_lossy(&bytes)); + trace!("Resp: {}", String::from_utf8_lossy(&bytes)); Ok(t) } // If deserializing into the desired type fails try again to // see if this is an error response. Err(e) => { - error!("{}", String::from_utf8_lossy(&bytes)); + error!("Error resp: {}", String::from_utf8_lossy(&bytes)); if let Ok(error) = serde_json::from_slice(&bytes) { return Err(crate::Error::Api(error)); } diff --git a/src/helpers/toml.rs b/src/helpers/toml.rs index 70c46ef..a4c5a8c 100644 --- a/src/helpers/toml.rs +++ b/src/helpers/toml.rs @@ -1,47 +1,47 @@ -use crate::data::ClientData; +use crate::data::AppData; use crate::Result; use std::io::{Read, Write, BufWriter}; use std::path::Path; use std::fs::{File, OpenOptions}; /// Attempts to deserialize a Data struct from a string -pub fn from_str(s: &str) -> Result { +pub fn from_str(s: &str) -> Result { Ok(toml::from_str(s)?) } /// Attempts to deserialize a Data struct from a slice of bytes -pub fn from_slice(s: &[u8]) -> Result { +pub fn from_slice(s: &[u8]) -> Result { Ok(toml::from_slice(s)?) } /// Attempts to deserialize a Data struct from something that implements /// the std::io::Read trait -pub fn from_reader(mut r: R) -> Result { +pub fn from_reader(mut r: R) -> Result { let mut buffer = Vec::new(); r.read_to_end(&mut buffer)?; from_slice(&buffer) } /// Attempts to deserialize a Data struct from a file -pub fn from_file>(path: P) -> Result { +pub fn from_file>(path: P) -> Result { let path = path.as_ref(); let file = File::open(path)?; Ok(from_reader(file)?) } /// Attempts to serialize a Data struct to a String -pub fn to_string(data: &ClientData) -> Result { +pub fn to_string(data: &AppData) -> Result { Ok(toml::to_string_pretty(data)?) } /// Attempts to serialize a Data struct to a Vec of bytes -pub fn to_vec(data: &ClientData) -> Result> { +pub fn to_vec(data: &AppData) -> Result> { Ok(toml::to_vec(data)?) } /// Attempts to serialize a Data struct to something that implements the /// std::io::Write trait -pub fn to_writer(data: &ClientData, writer: W) -> Result<()> { +pub fn to_writer(data: &AppData, writer: W) -> Result<()> { let mut buf_writer = BufWriter::new(writer); let vec = to_vec(data)?; buf_writer.write_all(&vec)?; @@ -53,7 +53,7 @@ pub fn to_writer(data: &ClientData, writer: W) -> Result<()> { /// When opening the file, this will set the `.write(true)` and /// `.truncate(true)` options, use the next method for more /// fine-grained control -pub fn to_file>(data: &ClientData, path: P) -> Result<()> { +pub fn to_file>(data: &AppData, path: P) -> Result<()> { let mut options = OpenOptions::new(); options.create(true).write(true).truncate(true); to_file_with_options(data, path, options)?; @@ -61,7 +61,7 @@ pub fn to_file>(data: &ClientData, path: P) -> Result<()> { } /// Attempts to serialize a Data struct to a file -pub fn to_file_with_options>(data: &ClientData, path: P, options: OpenOptions) -> Result<()> { +pub fn to_file_with_options>(data: &AppData, path: P, options: OpenOptions) -> Result<()> { let path = path.as_ref(); let file = options.open(path)?; to_writer(data, file)?; @@ -89,7 +89,7 @@ mod tests { let desered = from_str(DOC).expect("Couldn't deserialize Data"); assert_eq!( desered, - ClientData { + AppData { base: "https://example.com".into(), client_id: "adbc01234".into(), client_secret: "0987dcba".into(), @@ -104,7 +104,7 @@ mod tests { let desered = from_slice(&doc).expect("Couldn't deserialize Data"); assert_eq!( desered, - ClientData { + AppData { base: "https://example.com".into(), client_id: "adbc01234".into(), client_secret: "0987dcba".into(), @@ -120,7 +120,7 @@ mod tests { let desered = from_reader(doc).expect("Couldn't deserialize Data"); assert_eq!( desered, - ClientData { + AppData { base: "https://example.com".into(), client_id: "adbc01234".into(), client_secret: "0987dcba".into(), @@ -136,7 +136,7 @@ mod tests { let desered = from_file(datafile.path()).expect("Couldn't deserialize Data"); assert_eq!( desered, - ClientData { + AppData { base: "https://example.com".into(), client_id: "adbc01234".into(), client_secret: "0987dcba".into(), @@ -147,7 +147,7 @@ mod tests { } #[test] fn test_to_string() { - let data = ClientData { + let data = AppData { base: "https://example.com".into(), client_id: "adbc01234".into(), client_secret: "0987dcba".into(), @@ -160,7 +160,7 @@ mod tests { } #[test] fn test_to_vec() { - let data = ClientData { + let data = AppData { base: "https://example.com".into(), client_id: "adbc01234".into(), client_secret: "0987dcba".into(), @@ -173,7 +173,7 @@ mod tests { } #[test] fn test_to_writer() { - let data = ClientData { + let data = AppData { base: "https://example.com".into(), client_id: "adbc01234".into(), client_secret: "0987dcba".into(), @@ -188,7 +188,7 @@ mod tests { } #[test] fn test_to_file() { - let data = ClientData { + let data = AppData { base: "https://example.com".into(), client_id: "adbc01234".into(), client_secret: "0987dcba".into(), @@ -203,7 +203,7 @@ mod tests { } #[test] fn test_to_file_with_options() { - let data = ClientData { + let data = AppData { base: "https://example.com".into(), client_id: "adbc01234".into(), client_secret: "0987dcba".into(), diff --git a/src/lib.rs b/src/lib.rs index 734224e..b5a3cf9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,8 @@ //! Most of the api is documented on [Mastodon's website](https://docs.joinmastodon.org/client/intro/) //! +#![deny(unused_must_use)] + #[macro_use] extern crate log; #[macro_use] @@ -28,7 +30,7 @@ use streaming::EventReader; pub use streaming::StreamKind; pub use crate::{ - data::ClientData, + data::AppData, media_builder::MediaBuilder, page::Page, registration::Registration, @@ -66,24 +68,26 @@ pub mod unauth; /// Streaming API pub mod streaming; +pub mod debug; + /// Your mastodon application client, handles all requests to and from Mastodon. #[derive(Clone, Debug)] -pub struct Client { +pub struct FediClient { http_client: reqwest::Client, /// Raw data about your mastodon instance. - pub data: ClientData, + pub data: AppData, } -impl From for Client { +impl From for FediClient { /// Creates a mastodon instance from the data struct. - fn from(data: ClientData) -> Client { + fn from(data: AppData) -> FediClient { let mut builder = ClientBuilder::new(); builder.data(data); builder.build().expect("We know `data` is present, so this should be fine") } } -impl Client { +impl FediClient { methods![get, put, post, delete,]; pub fn route(&self, url: &str) -> String { @@ -99,12 +103,13 @@ impl Client { /// Open streaming API of the given kind pub async fn open_streaming_api<'k>(&self, kind: StreamKind<'k>) -> Result { - let mut url: url::Url = self.route(&format!("/api/v1/streaming/{}", kind.get_url_fragment())).parse()?; + let mut url: url::Url = self.route(&"/api/v1/streaming").parse()?; + // let mut url: url::Url = self.route(&format!("/api/v1/streaming/{}", kind.get_url_fragment())).parse()?; { let mut qpm = url.query_pairs_mut(); qpm.append_pair("access_token", &self.token); - //qpm.append_pair("stream", "user"); + qpm.append_pair("stream", kind.get_stream_name()); for (k, v) in kind.get_query_params() { qpm.append_pair(k, v); @@ -390,8 +395,8 @@ impl Client { } } -impl ops::Deref for Client { - type Target = ClientData; +impl ops::Deref for FediClient { + type Target = AppData; fn deref(&self) -> &Self::Target { &self.data @@ -400,7 +405,7 @@ impl ops::Deref for Client { struct ClientBuilder { http_client: Option, - data: Option, + data: Option, } impl ClientBuilder { @@ -416,14 +421,14 @@ impl ClientBuilder { self } - pub fn data(&mut self, data: ClientData) -> &mut Self { + pub fn data(&mut self, data: AppData) -> &mut Self { self.data = Some(data); self } - pub fn build(self) -> Result { + pub fn build(self) -> Result { Ok(if let Some(data) = self.data { - Client { + FediClient { http_client: self.http_client.unwrap_or_else(reqwest::Client::new), data, } diff --git a/src/page.rs b/src/page.rs index c6ee78f..26ba203 100644 --- a/src/page.rs +++ b/src/page.rs @@ -1,4 +1,4 @@ -use super::{deserialise_response, Client, Result}; +use super::{deserialise_response, FediClient, Result}; use crate::entities::itemsiter::ItemsIter; use hyper_old_types::header::{parsing, Link, RelationType}; use reqwest::{Response, header::LINK}; @@ -35,7 +35,7 @@ macro_rules! pages { /// easily stored for later use #[derive(Debug, Clone)] pub struct OwnedPage Deserialize<'de>> { - api_client: Client, + api_client: FediClient, next: Option, prev: Option, /// Initial set of items @@ -63,7 +63,7 @@ impl<'a, T: for<'de> Deserialize<'de>> From> for OwnedPage { /// Represents a single page of API results #[derive(Debug, Clone)] pub struct Page<'a, T: for<'de> Deserialize<'de>> { - api_client: &'a Client, + api_client: &'a FediClient, next: Option, prev: Option, /// Initial set of items @@ -76,7 +76,7 @@ impl<'a, T: for<'de> Deserialize<'de>> Page<'a, T> { prev: prev_page } - pub(crate) async fn new<'m>(api_client: &'m Client, response: Response) -> Result> { + pub(crate) async fn new<'m>(api_client: &'m FediClient, response: Response) -> Result> { let (prev, next) = get_links(&response)?; Ok(Page { initial_items: deserialise_response(response).await?, diff --git a/src/registration.rs b/src/registration.rs index d2c948a..ebb6695 100644 --- a/src/registration.rs +++ b/src/registration.rs @@ -4,8 +4,8 @@ use serde::Deserialize; use std::convert::TryInto; use crate::apps::{AppBuilder, App}; use crate::scopes::Scopes; -use crate::{Result, Error, Client, ClientBuilder}; -use crate::data::ClientData; +use crate::{Result, Error, FediClient, ClientBuilder}; +use crate::data::AppData; const DEFAULT_REDIRECT_URI: &str = "urn:ietf:wg:oauth:2.0:oob"; @@ -191,7 +191,7 @@ impl Registered { /// Create an access token from the client id, client secret, and code /// provided by the authorisation url. - pub async fn complete(&self, code: &str) -> Result { + pub async fn complete(&self, code: &str) -> Result { let url = format!( "{}/oauth/token?client_id={}&client_secret={}&code={}&grant_type=authorization_code&\ redirect_uri={}", @@ -200,7 +200,7 @@ impl Registered { let token: AccessToken = self.send(self.http_client.post(&url)).await?.json().await?; - let data = ClientData { + let data = AppData { base: self.base.clone().into(), client_id: self.client_id.clone().into(), client_secret: self.client_secret.clone().into(), diff --git a/src/requests/push.rs b/src/requests/push.rs index c81cc52..5401053 100644 --- a/src/requests/push.rs +++ b/src/requests/push.rs @@ -45,9 +45,9 @@ impl Keys { /// /// ```no_run /// # extern crate elefren; -/// # use elefren::{MastodonClient, Client, ClientData}; +/// # use elefren::{MastodonClient, FediClient, AppData}; /// # fn main() -> Result<(), elefren::Error> { -/// # let data = ClientData { +/// # let data = AppData { /// # base: "".into(), /// # client_id: "".into(), /// # client_secret: "".into(), @@ -56,7 +56,7 @@ impl Keys { /// # }; /// use elefren::requests::{AddPushRequest, Keys}; /// -/// let client = Client::from(data); +/// let client = FediClient::from(data); /// /// let keys = Keys::new("stahesuahoei293ise===", "tasecoa,nmeozka=="); /// let mut request = AddPushRequest::new("http://example.com/push/endpoint", &keys); @@ -214,9 +214,9 @@ impl AddPushRequest { /// /// ```no_run /// # extern crate elefren; -/// # use elefren::{Client, ClientData}; +/// # use elefren::{FediClient, AppData}; /// # fn main() -> Result<(), elefren::Error> { -/// # let data = ClientData { +/// # let data = AppData { /// # base: "".into(), /// # client_id: "".into(), /// # client_secret: "".into(), @@ -225,7 +225,7 @@ impl AddPushRequest { /// # }; /// use elefren::requests::UpdatePushRequest; /// -/// let client = Client::from(data); +/// let client = FediClient::from(data); /// /// let mut request = UpdatePushRequest::new("foobar"); /// request.follow(true).reblog(true); diff --git a/src/requests/update_credentials.rs b/src/requests/update_credentials.rs index 5283572..80f68e8 100644 --- a/src/requests/update_credentials.rs +++ b/src/requests/update_credentials.rs @@ -15,9 +15,9 @@ use crate::{ /// /// ```no_run /// # extern crate elefren; -/// # use elefren::ClientData; +/// # use elefren::AppData; /// # fn main() -> Result<(), elefren::Error> { -/// # let data = ClientData { +/// # let data = AppData { /// # base: "".into(), /// # client_id: "".into(), /// # client_secret: "".into(), diff --git a/src/streaming.rs b/src/streaming.rs index a893c39..8effe91 100644 --- a/src/streaming.rs +++ b/src/streaming.rs @@ -21,6 +21,19 @@ pub enum StreamKind<'a> { } impl<'a> StreamKind<'a> { + pub(crate) fn get_stream_name(&self) -> &'static str { + match self { + StreamKind::User => "user", + StreamKind::Public => "public", + StreamKind::PublicLocal => "public:local", + StreamKind::Direct => "direct", + StreamKind::Hashtag(_) => "hashtag", + StreamKind::HashtagLocal(_) => "hashtag:local", + StreamKind::List(_) => "list", + } + } + + #[allow(unused)] pub(crate) fn get_url_fragment(&self) -> &'static str { match self { StreamKind::User => "user", @@ -83,26 +96,33 @@ impl Stream for EventReader { fn poll_next(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll> { match Pin::new(&mut self.stream).poll_next(cx) { Poll::Ready(Some(Ok(Message::Text(line)))) => { - debug!("WS rx: {}", line); + trace!("WS rx: {}", line); let line = line.trim().to_string(); if line.starts_with(':') || line.is_empty() { - debug!("discard as comment"); + trace!("discard as comment"); return Poll::Pending; } self.lines.push(line); if let Ok(event) = self.make_event(&self.lines) { - debug!("Parsed event"); + trace!("Parsed event"); self.lines.clear(); return Poll::Ready(Some(event)); } else { - debug!("Failed to parse"); + trace!("Failed to parse"); return Poll::Pending; } } - Poll::Ready(Some(Ok(other))) => { - warn!("Unexpected msg: {:?}", other); + Poll::Ready(Some(Ok(Message::Ping(_)))) | Poll::Ready(Some(Ok(Message::Pong(_)))) => { + // Discard + Poll::Pending + } + Poll::Ready(Some(Ok(Message::Binary(_)))) => { + warn!("Unexpected binary msg"); Poll::Pending } + Poll::Ready(Some(Ok(Message::Close(_)))) => { + Poll::Ready(None) + } Poll::Ready(Some(Err(error))) => { error!("Websocket error: {:?}", error); // Close @@ -124,14 +144,14 @@ impl EventReader { let event; let data; if let Some(event_line) = lines.iter().find(|line| line.starts_with("event:")) { - debug!("plaintext formatted event"); + trace!("plaintext formatted event"); event = event_line[6..].trim().to_string(); data = lines .iter() .find(|line| line.starts_with("data:")) .map(|x| x[5..].trim().to_string()); } else { - debug!("JSON formatted event"); + trace!("JSON formatted event"); use serde::Deserialize; #[derive(Deserialize)] struct Message { diff --git a/src/unauth.rs b/src/unauth.rs index c878363..5db2e96 100644 --- a/src/unauth.rs +++ b/src/unauth.rs @@ -7,27 +7,27 @@ use crate::streaming::EventReader; /// Client that can make unauthenticated calls to a mastodon instance #[derive(Clone, Debug)] -pub struct Client { - client: reqwest::Client, +pub struct FediClient { + http_client: reqwest::Client, base: url::Url, } -impl Client { +impl FediClient { /// Create a new unauthenticated client - pub fn new(base: &str) -> Result { + pub fn new(base: &str) -> Result { let base = if base.starts_with("https://") { base.to_string() } else { format!("https://{}", base) }; - Ok(Client { - client: reqwest::Client::new(), + Ok(FediClient { + http_client: reqwest::Client::new(), base: url::Url::parse(&base)?, }) } } -impl Client { +impl FediClient { /// # Low-level API for extending /// Create a route with the given path fragment pub fn route(&self, url: &str) -> Result { @@ -38,20 +38,28 @@ impl Client { /// Send a request pub async fn send(&self, req: reqwest::RequestBuilder) -> Result { let req = req.build()?; - Ok(self.client.execute(req).await?) + Ok(self.http_client.execute(req).await?) } // TODO verify if this really works without auth /// Get a stream of the public timeline pub async fn streaming_public(&self) -> Result { - let url: url::Url = self.route("/api/v1/streaming/public")?; + let mut url: url::Url = self.route("/api/v1/streaming")?; + { + let mut qpm = url.query_pairs_mut(); + qpm.append_pair("stream", "public"); + } streaming::do_open_streaming(url.as_str()).await } /// Get a stream of the local timeline pub async fn streaming_local(&self) -> Result { - let url: url::Url = self.route("/api/v1/streaming/public/local")?; + let mut url: url::Url = self.route("/api/v1/streaming/public")?; + { + let mut qpm = url.query_pairs_mut(); + qpm.append_pair("stream", "public:local"); + } streaming::do_open_streaming(url.as_str()).await } @@ -59,7 +67,7 @@ impl Client { pub async fn get_status(&self, id: &str) -> Result { let route = self.route("/api/v1/statuses")?; let route = route.join(id)?; - let response = self.send(self.client.get(route)).await?; + let response = self.send(self.http_client.get(route)).await?; deserialise_response(response).await } @@ -68,7 +76,7 @@ impl Client { let route = self.route("/api/v1/statuses")?; let route = route.join(id)?; let route = route.join("context")?; - let response = self.send(self.client.get(route)).await?; + let response = self.send(self.http_client.get(route)).await?; deserialise_response(response).await } @@ -77,7 +85,7 @@ impl Client { let route = self.route("/api/v1/statuses")?; let route = route.join(id)?; let route = route.join("card")?; - let response = self.send(self.client.get(route)).await?; + let response = self.send(self.http_client.get(route)).await?; deserialise_response(response).await } }