improvements in streaming, add debug format structs

master
Ondřej Hruška 3 years ago
parent 568b1ff07a
commit 638594b11b
Signed by: MightyPork
GPG Key ID: 2C5FD5035250423D
  1. 2
      src/data.rs
  2. 59
      src/debug.rs
  3. 4
      src/helpers/cli.rs
  4. 38
      src/helpers/json.rs
  5. 4
      src/helpers/mod.rs
  6. 38
      src/helpers/toml.rs
  7. 33
      src/lib.rs
  8. 8
      src/page.rs
  9. 8
      src/registration.rs
  10. 12
      src/requests/push.rs
  11. 4
      src/requests/update_credentials.rs
  12. 36
      src/streaming.rs
  13. 34
      src/unauth.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.

@ -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 )
}
}
}
}

@ -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<Client> {
pub async fn authenticate(registration: Registered) -> Result<FediClient> {
let url = registration.authorize_url()?;
let stdout = io::stdout();

@ -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<ClientData> {
pub fn from_str(s: &str) -> Result<AppData> {
Ok(serde_json::from_str(s)?)
}
/// Attempts to deserialize a Data struct from a slice of bytes
pub fn from_slice(s: &[u8]) -> Result<ClientData> {
pub fn from_slice(s: &[u8]) -> Result<AppData> {
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<R: Read>(mut r: R) -> Result<ClientData> {
pub fn from_reader<R: Read>(mut r: R) -> Result<AppData> {
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<P: AsRef<Path>>(path: P) -> Result<ClientData> {
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<AppData> {
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<String> {
pub fn to_string(data: &AppData) -> Result<String> {
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<Vec<u8>> {
pub fn to_vec(data: &AppData) -> Result<Vec<u8>> {
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<W: Write>(data: &ClientData, writer: W) -> Result<()> {
pub fn to_writer<W: Write>(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<W: Write>(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<P: AsRef<Path>>(data: &ClientData, path: P) -> Result<()> {
pub fn to_file<P: AsRef<Path>>(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<P: AsRef<Path>>(data: &ClientData, path: P) -> Result<()> {
}
/// Attempts to serialize a Data struct to a file
pub fn to_file_with_options<P: AsRef<Path>>(data: &ClientData, path: P, options: OpenOptions) -> Result<()> {
pub fn to_file_with_options<P: AsRef<Path>>(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(),

@ -31,13 +31,13 @@ pub async fn deserialise_response<T: for<'de> 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));
}

@ -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<ClientData> {
pub fn from_str(s: &str) -> Result<AppData> {
Ok(toml::from_str(s)?)
}
/// Attempts to deserialize a Data struct from a slice of bytes
pub fn from_slice(s: &[u8]) -> Result<ClientData> {
pub fn from_slice(s: &[u8]) -> Result<AppData> {
Ok(toml::from_slice(s)?)
}
/// Attempts to deserialize a Data struct from something that implements
/// the std::io::Read trait
pub fn from_reader<R: Read>(mut r: R) -> Result<ClientData> {
pub fn from_reader<R: Read>(mut r: R) -> Result<AppData> {
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<P: AsRef<Path>>(path: P) -> Result<ClientData> {
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<AppData> {
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<String> {
pub fn to_string(data: &AppData) -> Result<String> {
Ok(toml::to_string_pretty(data)?)
}
/// Attempts to serialize a Data struct to a Vec of bytes
pub fn to_vec(data: &ClientData) -> Result<Vec<u8>> {
pub fn to_vec(data: &AppData) -> Result<Vec<u8>> {
Ok(toml::to_vec(data)?)
}
/// Attempts to serialize a Data struct to something that implements the
/// std::io::Write trait
pub fn to_writer<W: Write>(data: &ClientData, writer: W) -> Result<()> {
pub fn to_writer<W: Write>(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<W: Write>(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<P: AsRef<Path>>(data: &ClientData, path: P) -> Result<()> {
pub fn to_file<P: AsRef<Path>>(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<P: AsRef<Path>>(data: &ClientData, path: P) -> Result<()> {
}
/// Attempts to serialize a Data struct to a file
pub fn to_file_with_options<P: AsRef<Path>>(data: &ClientData, path: P, options: OpenOptions) -> Result<()> {
pub fn to_file_with_options<P: AsRef<Path>>(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(),

@ -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<ClientData> for Client {
impl From<AppData> 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<EventReader> {
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<reqwest::Client>,
data: Option<ClientData>,
data: Option<AppData>,
}
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<Client> {
pub fn build(self) -> Result<FediClient> {
Ok(if let Some(data) = self.data {
Client {
FediClient {
http_client: self.http_client.unwrap_or_else(reqwest::Client::new),
data,
}

@ -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<T: for<'de> Deserialize<'de>> {
api_client: Client,
api_client: FediClient,
next: Option<Url>,
prev: Option<Url>,
/// Initial set of items
@ -63,7 +63,7 @@ impl<'a, T: for<'de> Deserialize<'de>> From<Page<'a, T>> for OwnedPage<T> {
/// 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<Url>,
prev: Option<Url>,
/// 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<Page<'m, T>> {
pub(crate) async fn new<'m>(api_client: &'m FediClient, response: Response) -> Result<Page<'m, T>> {
let (prev, next) = get_links(&response)?;
Ok(Page {
initial_items: deserialise_response(response).await?,

@ -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<Client> {
pub async fn complete(&self, code: &str) -> Result<FediClient> {
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(),

@ -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);

@ -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(),

@ -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<Option<Self::Item>> {
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 {

@ -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<Client> {
pub fn new(base: &str) -> Result<FediClient> {
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<url::Url> {
@ -38,20 +38,28 @@ impl Client {
/// Send a request
pub async fn send(&self, req: reqwest::RequestBuilder) -> Result<reqwest::Response> {
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<EventReader> {
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<EventReader> {
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<Status> {
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
}
}

Loading…
Cancel
Save