2021-01-09 19:11:10 +01:00
|
|
|
|
use std::{
|
|
|
|
|
convert::TryFrom,
|
|
|
|
|
time::{Duration, SystemTime},
|
|
|
|
|
};
|
2020-12-03 12:12:07 +01:00
|
|
|
|
|
2021-01-09 19:11:10 +01:00
|
|
|
|
use async_stream::stream;
|
2020-11-24 16:11:27 +01:00
|
|
|
|
use matrix_sdk::{
|
2020-12-03 12:12:07 +01:00
|
|
|
|
api::r0::{account::register::Request as RegistrationRequest, uiaa::AuthData},
|
2021-01-09 19:11:10 +01:00
|
|
|
|
events::{
|
|
|
|
|
room::message::{MessageEvent, MessageEventContent},
|
|
|
|
|
AnyMessageEvent, AnyRoomEvent, AnySyncRoomEvent, AnyToDeviceEvent,
|
|
|
|
|
},
|
|
|
|
|
identifiers::{DeviceId, EventId, ServerName, UserId},
|
2020-12-03 12:12:07 +01:00
|
|
|
|
reqwest::Url,
|
|
|
|
|
Client, ClientConfig, LoopCtrl, SyncSettings,
|
2020-11-24 16:11:27 +01:00
|
|
|
|
};
|
|
|
|
|
use serde::{Deserialize, Serialize};
|
2020-11-23 17:18:05 +01:00
|
|
|
|
|
2020-11-24 16:11:27 +01:00
|
|
|
|
pub type Error = anyhow::Error;
|
|
|
|
|
|
|
|
|
|
// Needed to be able to serialize `Session`s. Should be done with serde remote.
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
2020-11-24 21:49:06 +01:00
|
|
|
|
pub struct Session {
|
2020-11-24 16:11:27 +01:00
|
|
|
|
access_token: String,
|
2021-01-16 14:44:18 +01:00
|
|
|
|
pub user_id: UserId,
|
|
|
|
|
pub device_id: Box<DeviceId>,
|
|
|
|
|
pub homeserver: String,
|
2020-11-24 16:11:27 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-24 21:49:06 +01:00
|
|
|
|
impl From<Session> for matrix_sdk::Session {
|
2020-11-24 16:11:27 +01:00
|
|
|
|
fn from(s: Session) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
access_token: s.access_token,
|
|
|
|
|
user_id: s.user_id,
|
|
|
|
|
device_id: s.device_id,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-11-24 09:12:24 +01:00
|
|
|
|
|
2020-12-03 12:12:07 +01:00
|
|
|
|
pub async fn signup(
|
|
|
|
|
username: &str,
|
|
|
|
|
password: &str,
|
|
|
|
|
server: &str,
|
2020-12-08 16:40:36 +01:00
|
|
|
|
device_name: Option<&str>,
|
2020-12-03 12:12:07 +01:00
|
|
|
|
) -> Result<(Client, Session), Error> {
|
|
|
|
|
let url = Url::parse(server)?;
|
|
|
|
|
let client = client(url)?;
|
|
|
|
|
|
|
|
|
|
let mut request = RegistrationRequest::new();
|
|
|
|
|
request.username = Some(username);
|
|
|
|
|
request.password = Some(password);
|
2020-12-08 16:40:36 +01:00
|
|
|
|
request.initial_device_display_name = Some(device_name.unwrap_or("retrix"));
|
2020-12-03 12:12:07 +01:00
|
|
|
|
request.inhibit_login = false;
|
|
|
|
|
|
|
|
|
|
// Get UIAA session key
|
|
|
|
|
let uiaa = match client.register(request.clone()).await {
|
|
|
|
|
Err(e) => match e.uiaa_response().cloned() {
|
|
|
|
|
Some(uiaa) => uiaa,
|
|
|
|
|
None => return Err(anyhow::anyhow!("Missing UIAA response")),
|
|
|
|
|
},
|
|
|
|
|
Ok(_) => {
|
|
|
|
|
return Err(anyhow::anyhow!("Missing UIAA response"));
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
// Get the first step in the authentication flow (we're ignoring the rest)
|
|
|
|
|
let stages = uiaa.flows.get(0);
|
|
|
|
|
let kind = stages.and_then(|flow| flow.stages.get(0)).cloned();
|
|
|
|
|
|
|
|
|
|
// Set authentication data, fallback to password type
|
|
|
|
|
request.auth = Some(AuthData::DirectRequest {
|
|
|
|
|
kind: kind.as_deref().unwrap_or("m.login.password"),
|
|
|
|
|
session: uiaa.session.as_deref(),
|
|
|
|
|
auth_parameters: Default::default(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let response = client.register(request).await?;
|
2021-01-09 19:11:10 +01:00
|
|
|
|
client.sync_once(SyncSettings::new()).await?;
|
2020-12-03 12:12:07 +01:00
|
|
|
|
|
|
|
|
|
let session = Session {
|
|
|
|
|
access_token: response.access_token.unwrap(),
|
|
|
|
|
user_id: response.user_id,
|
|
|
|
|
device_id: response.device_id.unwrap(),
|
|
|
|
|
homeserver: server.to_owned(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Ok((client, session))
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-24 21:49:06 +01:00
|
|
|
|
/// Login with credentials, creating a new authentication session
|
2020-11-24 09:12:24 +01:00
|
|
|
|
pub async fn login(
|
2020-11-23 17:18:05 +01:00
|
|
|
|
username: &str,
|
|
|
|
|
password: &str,
|
|
|
|
|
server: &str,
|
2020-12-08 16:40:36 +01:00
|
|
|
|
device_name: Option<&str>,
|
2020-11-24 09:12:24 +01:00
|
|
|
|
) -> Result<(Client, Session), Error> {
|
2020-11-23 17:18:05 +01:00
|
|
|
|
let url = Url::parse(server)?;
|
2020-11-24 21:49:06 +01:00
|
|
|
|
let client = client(url)?;
|
2020-11-24 16:11:27 +01:00
|
|
|
|
|
2020-11-24 21:49:06 +01:00
|
|
|
|
let response = client
|
|
|
|
|
.login(
|
|
|
|
|
username,
|
|
|
|
|
password,
|
|
|
|
|
None,
|
2020-12-08 16:40:36 +01:00
|
|
|
|
Some(device_name.unwrap_or("retrix")),
|
2020-11-24 21:49:06 +01:00
|
|
|
|
)
|
|
|
|
|
.await?;
|
|
|
|
|
let session = Session {
|
|
|
|
|
access_token: response.access_token,
|
|
|
|
|
user_id: response.user_id,
|
|
|
|
|
device_id: response.device_id,
|
|
|
|
|
homeserver: server.to_owned(),
|
2020-11-24 09:12:24 +01:00
|
|
|
|
};
|
2020-11-24 21:49:06 +01:00
|
|
|
|
write_session(&session)?;
|
2021-01-09 19:11:10 +01:00
|
|
|
|
client.sync_once(SyncSettings::new()).await?;
|
2020-11-23 17:18:05 +01:00
|
|
|
|
|
2020-11-24 09:12:24 +01:00
|
|
|
|
Ok((client, session))
|
2020-11-23 17:18:05 +01:00
|
|
|
|
}
|
2020-11-24 16:11:27 +01:00
|
|
|
|
|
2020-11-24 21:49:06 +01:00
|
|
|
|
pub async fn restore_login(session: Session) -> Result<(Client, Session), Error> {
|
|
|
|
|
let url = Url::parse(&session.homeserver)?;
|
|
|
|
|
let client = client(url)?;
|
|
|
|
|
|
|
|
|
|
client.restore_login(session.clone().into()).await?;
|
2021-01-16 14:44:18 +01:00
|
|
|
|
client.sync_once(SyncSettings::new()).await?;
|
2020-11-24 21:49:06 +01:00
|
|
|
|
|
|
|
|
|
Ok((client, session))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Create a matrix client handler with the desired configuration
|
|
|
|
|
fn client(url: Url) -> Result<Client, matrix_sdk::Error> {
|
|
|
|
|
let config = ClientConfig::new().store_path(&dirs::config_dir().unwrap().join("retrix"));
|
|
|
|
|
Client::new_with_config(url, config)
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-24 16:11:27 +01:00
|
|
|
|
/// File path to store session data in
|
|
|
|
|
fn session_path() -> std::path::PathBuf {
|
|
|
|
|
dirs::config_dir()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.join("retrix")
|
|
|
|
|
.join("session.toml")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Read session data from config file
|
2020-11-24 21:49:06 +01:00
|
|
|
|
pub fn get_session() -> Result<Option<Session>, Error> {
|
2020-11-24 16:11:27 +01:00
|
|
|
|
let path = session_path();
|
|
|
|
|
if !path.is_file() {
|
|
|
|
|
return Ok(None);
|
|
|
|
|
}
|
2020-11-24 21:49:06 +01:00
|
|
|
|
let session: Session = toml::from_slice(&std::fs::read(path)?)?;
|
|
|
|
|
Ok(Some(session))
|
2020-11-24 16:11:27 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Save session data to config file
|
2020-11-24 21:49:06 +01:00
|
|
|
|
fn write_session(session: &Session) -> Result<(), Error> {
|
2020-11-24 16:11:27 +01:00
|
|
|
|
let serialized = toml::to_string(&session)?;
|
2020-11-24 21:49:06 +01:00
|
|
|
|
std::fs::write(session_path(), serialized)?;
|
2020-11-24 16:11:27 +01:00
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2020-12-02 06:52:21 +01:00
|
|
|
|
|
2021-01-09 19:11:10 +01:00
|
|
|
|
/// Break down an mxc url to its authority and path
|
|
|
|
|
pub fn parse_mxc(url: &str) -> Result<(Box<ServerName>, String), Error> {
|
|
|
|
|
let url = Url::parse(&url)?;
|
|
|
|
|
anyhow::ensure!(url.scheme() == "mxc", "Not an mxc url");
|
2021-01-16 14:44:18 +01:00
|
|
|
|
let host = url.host_str().ok_or_else(|| anyhow::anyhow!("url"))?;
|
2021-01-09 19:11:10 +01:00
|
|
|
|
let server_name: Box<ServerName> = <&ServerName>::try_from(host)?.into();
|
|
|
|
|
let path = url.path_segments().and_then(|mut p| p.next());
|
|
|
|
|
match path {
|
|
|
|
|
Some(path) => Ok((server_name, path.to_owned())),
|
|
|
|
|
_ => Err(anyhow::anyhow!("Invalid mxc url")),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-02 06:52:21 +01:00
|
|
|
|
pub struct MatrixSync {
|
|
|
|
|
client: matrix_sdk::Client,
|
2020-12-03 12:12:07 +01:00
|
|
|
|
join: Option<tokio::task::JoinHandle<()>>,
|
2020-12-02 06:52:21 +01:00
|
|
|
|
//id: String,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl MatrixSync {
|
2020-12-07 17:12:21 +01:00
|
|
|
|
pub fn subscription(client: matrix_sdk::Client) -> iced::Subscription<Event> {
|
2020-12-03 12:12:07 +01:00
|
|
|
|
iced::Subscription::from_recipe(MatrixSync { client, join: None })
|
2020-12-02 06:52:21 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-07 17:12:21 +01:00
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
|
pub enum Event {
|
|
|
|
|
Room(AnyRoomEvent),
|
|
|
|
|
ToDevice(AnyToDeviceEvent),
|
2020-12-19 10:00:55 +01:00
|
|
|
|
Token(String),
|
2020-12-07 17:12:21 +01:00
|
|
|
|
}
|
|
|
|
|
|
2021-01-16 14:44:18 +01:00
|
|
|
|
/*pub enum Sync {
|
|
|
|
|
MessageInvited()
|
|
|
|
|
MessageJoined(RoomId, AnyRoomEvent),
|
|
|
|
|
MessageLeft(RoomId, AnyRoomEvent),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct Emitter {
|
|
|
|
|
client: Client,
|
|
|
|
|
sender: tokio::sync::mpsc::Sender<Sync>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[async_trait]
|
|
|
|
|
impl matrix_sdk::EventEmitter for Emitter {
|
|
|
|
|
async fn on_room_message(
|
|
|
|
|
&self,
|
|
|
|
|
room: RoomState,
|
|
|
|
|
message: &SyncMessageEvent<MessageEventContent>,
|
|
|
|
|
) {
|
|
|
|
|
let id = room.room_id().to_owned();
|
|
|
|
|
let full = AnyRoomEvent::Message(
|
|
|
|
|
AnyMessageEvent::RoomMessage(
|
|
|
|
|
message
|
|
|
|
|
.to_owned()
|
|
|
|
|
.into_full_event(id.clone()),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
if let RoomState::Joined(room) = room {
|
|
|
|
|
self.sender
|
|
|
|
|
.send(Sync::MessageJoined(id, full))
|
|
|
|
|
.await
|
|
|
|
|
.ok();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn on_room_member(&self, room: RoomState, member: &SyncStateEvent<MemberEventContent>) {
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}*/
|
|
|
|
|
|
2020-12-02 06:52:21 +01:00
|
|
|
|
impl<H, I> iced_futures::subscription::Recipe<H, I> for MatrixSync
|
|
|
|
|
where
|
|
|
|
|
H: std::hash::Hasher,
|
|
|
|
|
{
|
2020-12-07 17:12:21 +01:00
|
|
|
|
type Output = Event;
|
2020-12-02 06:52:21 +01:00
|
|
|
|
|
|
|
|
|
fn hash(&self, state: &mut H) {
|
|
|
|
|
use std::hash::Hash;
|
|
|
|
|
|
|
|
|
|
std::any::TypeId::of::<Self>().hash(state);
|
|
|
|
|
//self.id.hash(state);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn stream(
|
2020-12-03 12:12:07 +01:00
|
|
|
|
mut self: Box<Self>,
|
2020-12-02 06:52:21 +01:00
|
|
|
|
_input: iced_futures::BoxStream<I>,
|
|
|
|
|
) -> iced_futures::BoxStream<Self::Output> {
|
2021-01-09 19:11:10 +01:00
|
|
|
|
let (sender, mut receiver) = tokio::sync::mpsc::unbounded_channel();
|
2020-12-02 06:52:21 +01:00
|
|
|
|
let client = self.client.clone();
|
2020-12-03 12:12:07 +01:00
|
|
|
|
let join = tokio::task::spawn(async move {
|
2020-12-02 06:52:21 +01:00
|
|
|
|
client
|
2020-12-03 12:12:07 +01:00
|
|
|
|
.sync_with_callback(
|
2020-12-07 17:12:21 +01:00
|
|
|
|
SyncSettings::new()
|
2021-01-09 19:11:10 +01:00
|
|
|
|
.token(client.sync_token().await.unwrap())
|
2021-01-16 14:44:18 +01:00
|
|
|
|
.timeout(Duration::from_secs(30)),
|
2020-12-03 12:12:07 +01:00
|
|
|
|
|response| async {
|
2021-01-16 14:44:18 +01:00
|
|
|
|
//sender.send(Event::Token(response.next_batch)).ok();
|
2020-12-07 17:12:21 +01:00
|
|
|
|
for (id, room) in response.rooms.join {
|
2021-01-16 14:44:18 +01:00
|
|
|
|
for event in room.state.events {
|
|
|
|
|
let id = id.clone();
|
|
|
|
|
sender
|
|
|
|
|
.send(Event::Room(AnyRoomEvent::State(
|
|
|
|
|
event.into_full_event(id),
|
|
|
|
|
)))
|
|
|
|
|
.ok();
|
|
|
|
|
}
|
2020-12-03 12:12:07 +01:00
|
|
|
|
for event in room.timeline.events {
|
2020-12-07 17:12:21 +01:00
|
|
|
|
let id = id.clone();
|
|
|
|
|
let event = match event {
|
|
|
|
|
AnySyncRoomEvent::Message(e) => {
|
|
|
|
|
AnyRoomEvent::Message(e.into_full_event(id))
|
|
|
|
|
}
|
|
|
|
|
AnySyncRoomEvent::State(e) => {
|
|
|
|
|
AnyRoomEvent::State(e.into_full_event(id))
|
|
|
|
|
}
|
|
|
|
|
AnySyncRoomEvent::RedactedMessage(e) => {
|
|
|
|
|
AnyRoomEvent::RedactedMessage(e.into_full_event(id))
|
|
|
|
|
}
|
|
|
|
|
AnySyncRoomEvent::RedactedState(e) => {
|
|
|
|
|
AnyRoomEvent::RedactedState(e.into_full_event(id))
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
sender.send(Event::Room(event)).ok();
|
2020-12-02 06:52:21 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
2020-12-07 17:12:21 +01:00
|
|
|
|
for event in response.to_device.events {
|
|
|
|
|
sender.send(Event::ToDevice(event)).ok();
|
|
|
|
|
}
|
2020-12-03 12:12:07 +01:00
|
|
|
|
LoopCtrl::Continue
|
|
|
|
|
},
|
|
|
|
|
)
|
2020-12-02 06:52:21 +01:00
|
|
|
|
.await;
|
|
|
|
|
});
|
2020-12-03 12:12:07 +01:00
|
|
|
|
self.join = Some(join);
|
2021-01-09 19:11:10 +01:00
|
|
|
|
let stream = stream! {
|
|
|
|
|
while let Some(item) = receiver.recv().await {
|
|
|
|
|
yield item;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
Box::pin(stream)
|
2020-12-02 06:52:21 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
2020-12-19 10:00:55 +01:00
|
|
|
|
|
|
|
|
|
pub trait AnyRoomEventExt {
|
2021-01-09 19:11:10 +01:00
|
|
|
|
/// Gets the event id of the underlying event
|
2020-12-19 10:00:55 +01:00
|
|
|
|
fn event_id(&self) -> &EventId;
|
|
|
|
|
/// Gets the ´origin_server_ts` member of the underlying event
|
|
|
|
|
fn origin_server_ts(&self) -> SystemTime;
|
2021-01-09 19:11:10 +01:00
|
|
|
|
/// Gets the mxc url in a message event if there is noe
|
|
|
|
|
fn image_url(&self) -> Option<String>;
|
2020-12-19 10:00:55 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl AnyRoomEventExt for AnyRoomEvent {
|
|
|
|
|
fn event_id(&self) -> &EventId {
|
|
|
|
|
match self {
|
|
|
|
|
AnyRoomEvent::Message(e) => e.event_id(),
|
|
|
|
|
AnyRoomEvent::State(e) => e.event_id(),
|
|
|
|
|
AnyRoomEvent::RedactedMessage(e) => e.event_id(),
|
|
|
|
|
AnyRoomEvent::RedactedState(e) => e.event_id(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
fn origin_server_ts(&self) -> SystemTime {
|
|
|
|
|
match self {
|
|
|
|
|
AnyRoomEvent::Message(e) => e.origin_server_ts(),
|
|
|
|
|
AnyRoomEvent::State(e) => e.origin_server_ts(),
|
|
|
|
|
AnyRoomEvent::RedactedMessage(e) => e.origin_server_ts(),
|
|
|
|
|
AnyRoomEvent::RedactedState(e) => e.origin_server_ts(),
|
|
|
|
|
}
|
|
|
|
|
.to_owned()
|
|
|
|
|
}
|
2021-01-09 19:11:10 +01:00
|
|
|
|
fn image_url(&self) -> Option<String> {
|
|
|
|
|
match self {
|
|
|
|
|
AnyRoomEvent::Message(message) => message.image_url(),
|
|
|
|
|
_ => None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub trait AnyMessageEventExt {
|
|
|
|
|
fn image_url(&self) -> Option<String>;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl AnyMessageEventExt for AnyMessageEvent {
|
|
|
|
|
fn image_url(&self) -> Option<String> {
|
|
|
|
|
match self {
|
|
|
|
|
AnyMessageEvent::RoomMessage(MessageEvent {
|
|
|
|
|
content: MessageEventContent::Image(ref image),
|
|
|
|
|
..
|
|
|
|
|
}) => image.url.clone(),
|
|
|
|
|
_ => None,
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-12-19 10:00:55 +01:00
|
|
|
|
}
|