Initial commit.
commit
4989108324
|
@ -0,0 +1,2 @@
|
|||
/Cargo.lock
|
||||
/target
|
|
@ -0,0 +1,32 @@
|
|||
[package]
|
||||
authors = ["Damir Jelić <poljar@termina.org.uk"]
|
||||
description = "A high level Matrix client library."
|
||||
edition = "2018"
|
||||
homepage = "https://github.com/poljar/nio-rust"
|
||||
keywords = ["matrix", "chat", "messaging", "ruma", "nio"]
|
||||
license = "MIT"
|
||||
name = "matrix-nio"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/poljar/nio-rust"
|
||||
version = "0.1.0"
|
||||
|
||||
[dependencies]
|
||||
js_int = "*"
|
||||
futures-preview = "0.3.0-alpha.18"
|
||||
reqwest = "0.10.0-alpha.1"
|
||||
http = "0.1.18"
|
||||
ruma-api = "*"
|
||||
ruma-client-api = { git = "https://github.com/ruma/ruma-client-api" }
|
||||
ruma-events = "0.12"
|
||||
|
||||
ruma-identifiers = "*"
|
||||
serde_json = "1.0.40"
|
||||
serde_urlencoded = "0.6.1"
|
||||
url = "2.1.0"
|
||||
|
||||
[dependencies.serde]
|
||||
version = "1.0.101"
|
||||
features = ["derive"]
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = "0.2.0-alpha.6"
|
|
@ -0,0 +1,21 @@
|
|||
Copyright (c) 2016 Jimmy Cuadra
|
||||
Copyright (c) 2019 Damir Jelić
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
# nio-rust
|
||||
|
||||
**nio-rust** is a port of the [matrix-nio][] python library to [Rust][].
|
||||
|
||||
[Matrix]: https://matrix.org/
|
||||
[Rust]: https://www.rust-lang.org/
|
||||
[matrix-nio]: https://github.com/poljar/matrix-nio
|
||||
|
||||
## Status
|
||||
|
||||
This library is very much work in progress.
|
||||
|
||||
## License
|
||||
|
||||
[MIT](http://opensource.org/licenses/MIT)
|
|
@ -0,0 +1,58 @@
|
|||
use std::{env, process::exit};
|
||||
|
||||
use matrix_nio::{
|
||||
self,
|
||||
events::{
|
||||
collections::all::RoomEvent,
|
||||
room::message::{MessageEvent, MessageEventContent, TextMessageEventContent},
|
||||
},
|
||||
AsyncClient, AsyncClientConfig, SyncSettings,
|
||||
};
|
||||
|
||||
async fn login(
|
||||
homeserver_url: String,
|
||||
username: String,
|
||||
password: String,
|
||||
) -> Result<(), matrix_nio::Error> {
|
||||
let client_config = AsyncClientConfig::new()
|
||||
.proxy("http://localhost:8080")?
|
||||
.disable_ssl_verification();
|
||||
let mut client = AsyncClient::new_with_config(&homeserver_url, None, client_config).unwrap();
|
||||
|
||||
client.login(username, password, None).await?;
|
||||
let response = client.sync(SyncSettings::new()).await?;
|
||||
|
||||
for (room_id, room) in response.rooms.join {
|
||||
println!("Room {}", room_id);
|
||||
|
||||
for event in room.timeline.events {
|
||||
if let RoomEvent::RoomMessage(MessageEvent {
|
||||
content: MessageEventContent::Text(TextMessageEventContent { body: msg_body, .. }),
|
||||
sender,
|
||||
..
|
||||
}) = event
|
||||
{
|
||||
println!("{}: {}", sender, msg_body);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), matrix_nio::Error> {
|
||||
let (homeserver_url, username, password) =
|
||||
match (env::args().nth(1), env::args().nth(2), env::args().nth(3)) {
|
||||
(Some(a), Some(b), Some(c)) => (a, b, c),
|
||||
_ => {
|
||||
eprintln!(
|
||||
"Usage: {} <homeserver_url> <username> <password>",
|
||||
env::args().next().unwrap()
|
||||
);
|
||||
exit(1)
|
||||
}
|
||||
};
|
||||
|
||||
login(homeserver_url, username, password).await
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
//! Error conditions.
|
||||
|
||||
use std::error::Error as StdError;
|
||||
use std::fmt::{Display, Formatter, Result as FmtResult};
|
||||
|
||||
use http::uri::InvalidUri;
|
||||
use reqwest::Error as ReqwestError;
|
||||
use ruma_api::Error as RumaApiError;
|
||||
use serde_json::Error as SerdeJsonError;
|
||||
use serde_urlencoded::ser::Error as SerdeUrlEncodedSerializeError;
|
||||
|
||||
/// An error that can occur during client operations.
|
||||
#[derive(Debug)]
|
||||
pub struct Error(pub(crate) InnerError);
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut Formatter) -> FmtResult {
|
||||
let message = match self.0 {
|
||||
InnerError::AuthenticationRequired => "The queried endpoint requires authentication but was called with an anonymous client.",
|
||||
InnerError::Reqwest(_) => "An HTTP error occurred.",
|
||||
InnerError::ConfigurationError(_) => "Error configuring the client",
|
||||
InnerError::Uri(_) => "Provided string could not be converted into a URI.",
|
||||
InnerError::RumaApi(_) => "An error occurred converting between ruma_client_api and hyper types.",
|
||||
InnerError::SerdeJson(_) => "A serialization error occurred.",
|
||||
InnerError::SerdeUrlEncodedSerialize(_) => "An error occurred serializing data to a query string.",
|
||||
};
|
||||
|
||||
write!(f, "{}", message)
|
||||
}
|
||||
}
|
||||
|
||||
impl StdError for Error {}
|
||||
|
||||
/// Internal representation of errors.
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum InnerError {
|
||||
/// Queried endpoint requires authentication but was called on an anonymous client.
|
||||
AuthenticationRequired,
|
||||
/// An error in the client configuration.
|
||||
ConfigurationError(String),
|
||||
/// An error at the HTTP layer.
|
||||
Reqwest(ReqwestError),
|
||||
/// An error when parsing a string as a URI.
|
||||
Uri(InvalidUri),
|
||||
/// An error converting between ruma_client_api types and Hyper types.
|
||||
RumaApi(RumaApiError),
|
||||
/// An error when serializing or deserializing a JSON value.
|
||||
SerdeJson(SerdeJsonError),
|
||||
/// An error when serializing a query string value.
|
||||
SerdeUrlEncodedSerialize(SerdeUrlEncodedSerializeError),
|
||||
}
|
||||
|
||||
impl From<InvalidUri> for Error {
|
||||
fn from(error: InvalidUri) -> Self {
|
||||
Self(InnerError::Uri(error))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RumaApiError> for Error {
|
||||
fn from(error: RumaApiError) -> Self {
|
||||
Self(InnerError::RumaApi(error))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SerdeJsonError> for Error {
|
||||
fn from(error: SerdeJsonError) -> Self {
|
||||
Self(InnerError::SerdeJson(error))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SerdeUrlEncodedSerializeError> for Error {
|
||||
fn from(error: SerdeUrlEncodedSerializeError) -> Self {
|
||||
Self(InnerError::SerdeUrlEncodedSerialize(error))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ReqwestError> for Error {
|
||||
fn from(error: ReqwestError) -> Self {
|
||||
Self(InnerError::Reqwest(error))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,233 @@
|
|||
//! Crate `ruma_client` is a [Matrix](https://matrix.org/) client library.
|
||||
//!
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
|
||||
use http::Method as HttpMethod;
|
||||
use http::Response as HttpResponse;
|
||||
use js_int::UInt;
|
||||
use reqwest;
|
||||
use ruma_api::Endpoint;
|
||||
use url::Url;
|
||||
|
||||
use crate::error::InnerError;
|
||||
|
||||
pub use crate::{error::Error, session::Session};
|
||||
pub use ruma_client_api as api;
|
||||
pub use ruma_events as events;
|
||||
|
||||
//pub mod api;
|
||||
mod error;
|
||||
mod session;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AsyncClient {
|
||||
/// The URL of the homeserver to connect to.
|
||||
homeserver: Url,
|
||||
/// The underlying HTTP client.
|
||||
client: reqwest::Client,
|
||||
/// User session data.
|
||||
session: Option<Session>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct AsyncClientConfig {
|
||||
proxy: Option<reqwest::Proxy>,
|
||||
use_sys_proxy: bool,
|
||||
disable_ssl_verification: bool,
|
||||
}
|
||||
|
||||
impl AsyncClientConfig {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
pub fn proxy(mut self, proxy: &str) -> Result<Self, Error> {
|
||||
if self.use_sys_proxy {
|
||||
return Err(Error(InnerError::ConfigurationError(
|
||||
"Using the system proxy has been previously configured.".to_string(),
|
||||
)));
|
||||
}
|
||||
self.proxy = Some(reqwest::Proxy::all(proxy)?);
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn use_sys_proxy(mut self) -> Result<Self, Error> {
|
||||
if self.proxy.is_some() {
|
||||
return Err(Error(InnerError::ConfigurationError(
|
||||
"A proxy has already been configured.".to_string(),
|
||||
)));
|
||||
}
|
||||
self.use_sys_proxy = true;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn disable_ssl_verification(mut self) -> Self {
|
||||
self.disable_ssl_verification = true;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct SyncSettings {
|
||||
pub(crate) timeout: Option<UInt>,
|
||||
pub(crate) token: Option<String>,
|
||||
pub(crate) full_state: Option<bool>,
|
||||
}
|
||||
|
||||
impl SyncSettings {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
pub fn token<S: Into<String>>(mut self, token: S) -> Self {
|
||||
self.token = Some(token.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn timeout<T: TryInto<UInt>>(mut self, timeout: T) -> Result<Self, js_int::TryFromIntError>
|
||||
where
|
||||
js_int::TryFromIntError:
|
||||
std::convert::From<<T as std::convert::TryInto<js_int::UInt>>::Error>,
|
||||
{
|
||||
self.timeout = Some(timeout.try_into()?);
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn full_state(mut self, full_state: bool) -> Self {
|
||||
self.full_state = Some(full_state);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
use api::r0::session::login;
|
||||
use api::r0::sync::sync_events;
|
||||
|
||||
impl AsyncClient {
|
||||
/// Creates a new client for making HTTP requests to the given homeserver.
|
||||
pub fn new(homeserver_url: &str, session: Option<Session>) -> Result<Self, url::ParseError> {
|
||||
let homeserver = Url::parse(homeserver_url)?;
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
Ok(Self {
|
||||
homeserver,
|
||||
client,
|
||||
session,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new_with_config(
|
||||
homeserver_url: &str,
|
||||
session: Option<Session>,
|
||||
config: AsyncClientConfig,
|
||||
) -> Result<Self, url::ParseError> {
|
||||
let homeserver = Url::parse(homeserver_url)?;
|
||||
let client = reqwest::Client::builder();
|
||||
|
||||
let client = if config.disable_ssl_verification {
|
||||
client.danger_accept_invalid_certs(true)
|
||||
} else {
|
||||
client
|
||||
};
|
||||
|
||||
let client = match config.proxy {
|
||||
Some(p) => client.proxy(p),
|
||||
None => client,
|
||||
};
|
||||
|
||||
let client = if config.use_sys_proxy {
|
||||
client.use_sys_proxy()
|
||||
} else {
|
||||
client
|
||||
};
|
||||
|
||||
let mut headers = reqwest::header::HeaderMap::new();
|
||||
|
||||
headers.insert(reqwest::header::USER_AGENT, reqwest::header::HeaderValue::from_static("ruma"));
|
||||
|
||||
let client = client.default_headers(headers).build().unwrap();
|
||||
|
||||
Ok(Self {
|
||||
homeserver,
|
||||
client,
|
||||
session,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn login<S: Into<String>>(
|
||||
&mut self,
|
||||
user: S,
|
||||
password: S,
|
||||
device_id: Option<S>,
|
||||
) -> Result<login::Response, Error> {
|
||||
let request = login::Request {
|
||||
address: None,
|
||||
login_type: login::LoginType::Password,
|
||||
medium: None,
|
||||
device_id: device_id.map(|d| d.into()),
|
||||
password: password.into(),
|
||||
user: user.into(),
|
||||
};
|
||||
|
||||
let response = self.send(request).await.unwrap();
|
||||
|
||||
let session = Session {
|
||||
access_token: response.access_token.clone(),
|
||||
device_id: response.device_id.clone(),
|
||||
user_id: response.user_id.clone(),
|
||||
};
|
||||
|
||||
self.session = Some(session.clone());
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
pub async fn sync(&self, sync_settings: SyncSettings) -> Result<sync_events::Response, Error> {
|
||||
let request = sync_events::Request {
|
||||
filter: None,
|
||||
since: sync_settings.token,
|
||||
full_state: sync_settings.full_state,
|
||||
set_presence: None,
|
||||
timeout: sync_settings.timeout,
|
||||
};
|
||||
|
||||
let response = self.send(request).await.unwrap();
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
async fn send<Request: Endpoint>(&self, request: Request) -> Result<Request::Response, Error> {
|
||||
let request: http::Request<Vec<u8>> = request.try_into()?;
|
||||
let url = request.uri();
|
||||
let url = self.homeserver.join(url.path()).unwrap();
|
||||
|
||||
let request_builder = match Request::METADATA.method {
|
||||
HttpMethod::GET => self.client.get(url),
|
||||
HttpMethod::POST => {
|
||||
let body = request.body().clone();
|
||||
self.client.post(url).body(body)
|
||||
}
|
||||
HttpMethod::PUT => unimplemented!(),
|
||||
HttpMethod::DELETE => unimplemented!(),
|
||||
_ => panic!("Unsuported method"),
|
||||
};
|
||||
|
||||
let request_builder = if Request::METADATA.requires_authentication {
|
||||
if let Some(ref session) = self.session {
|
||||
request_builder.bearer_auth(&session.access_token)
|
||||
} else {
|
||||
return Err(Error(InnerError::AuthenticationRequired));
|
||||
}
|
||||
} else {
|
||||
request_builder
|
||||
};
|
||||
|
||||
let response = request_builder.send().await?;
|
||||
|
||||
let status = response.status();
|
||||
let body = response.bytes().await?.as_ref().to_owned();
|
||||
let response = HttpResponse::builder().status(status).body(body).unwrap();
|
||||
let response = Request::Response::try_from(response)?;
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
//! User sessions.
|
||||
|
||||
use ruma_identifiers::UserId;
|
||||
|
||||
/// A user session, containing an access token and information about the associated user account.
|
||||
#[derive(Clone, Debug, serde::Deserialize, Eq, Hash, PartialEq, serde::Serialize)]
|
||||
pub struct Session {
|
||||
/// The access token used for this session.
|
||||
pub access_token: String,
|
||||
/// The user the access token was issued for.
|
||||
pub user_id: UserId,
|
||||
/// The ID of the client device
|
||||
pub device_id: String,
|
||||
}
|
||||
|
||||
impl Session {
|
||||
/// Create a new user session from an access token and a user ID.
|
||||
#[deprecated]
|
||||
pub fn new(access_token: String, user_id: UserId, device_id: String) -> Self {
|
||||
Self {
|
||||
access_token,
|
||||
user_id,
|
||||
device_id,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the access token associated with this session.
|
||||
#[deprecated]
|
||||
pub fn access_token(&self) -> &str {
|
||||
&self.access_token
|
||||
}
|
||||
|
||||
/// Get the ID of the user the session belongs to.
|
||||
#[deprecated]
|
||||
pub fn user_id(&self) -> &UserId {
|
||||
&self.user_id
|
||||
}
|
||||
|
||||
/// Get ID of the device the session belongs to.
|
||||
#[deprecated]
|
||||
pub fn device_id(&self) -> &str {
|
||||
&self.device_id
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue