diff --git a/src/bot.rs b/src/bot.rs new file mode 100644 index 0000000..5255b0f --- /dev/null +++ b/src/bot.rs @@ -0,0 +1,84 @@ +use serde::{self, Deserialize, Serialize}; +use reqwest::Client; +use crate::matrix::SyncCommand; + +#[derive(Serialize, Deserialize, Debug)] +pub struct MatrixConfig { + user: String, + password: String, + home_server: String, + next_batch: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct Config { + matrix: MatrixConfig, +} + +pub struct DiceBot { + config: Config, + access_token: String, + next_batch: Option, + client: Client, +} + +#[derive(Serialize, Debug)] +struct LoginRequest<'a, 'b, 'c> { + #[serde(rename = "type")] + type_: &'a str, + user: &'b str, + password: &'c str, +} + +#[derive(Deserialize, Debug)] +struct LoginResponse { + access_token: String, +} + +impl DiceBot { + pub async fn new(config: Config) -> Result> { + let client = Client::new(); + let request = LoginRequest { + type_: "m.login.password", + user: &config.matrix.user, + password: &config.matrix.password, + }; + let response = client.post(&format!("https://{}/_matrix/client/r0/login", config.matrix.home_server)) + .body(serde_json::to_string(&request)?) + .send() + .await?; + let body: LoginResponse = serde_json::from_str(&response.text().await?)?; + Ok(DiceBot{ + client, + config, + access_token: body.access_token, + next_batch: None, + }) + } + + pub async fn sync(&mut self) -> Result<(), Box> { + let mut url = format!("https://{}/_matrix/client/r0/sync?access_token={}&timeout=3000", + self.config.matrix.home_server, + self.access_token); + if let Some(since) = &self.next_batch { + url.push_str(&format!("&since={}", since)); + } + let body = self.client.get(&url) + .send() + .await? + .text() + .await?; + let sync: SyncCommand = serde_json::from_str(&body).unwrap(); + println!("{:#?}", sync); + self.next_batch = Some(sync.next_batch); + Ok(()) + } + + pub async fn logout(self) -> Result<(), Box> { + self.client.post(&format!("https://{}/_matrix/client/r0/logout?access_token={}", self.config.matrix.home_server, self.access_token)) + .body("{}") + .send() + .await?; + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..41a394f --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,2 @@ +pub mod matrix; +pub mod bot; diff --git a/src/main.rs b/src/main.rs index 68183af..3e20293 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,109 +1,36 @@ -use serde::{self, Deserialize, Serialize}; -use std::collections::HashMap; -use std::env; use tokio::select; use tokio::signal::unix::{signal, SignalKind}; - -#[derive(Serialize, Deserialize, Debug)] -#[serde(tag = "msgtype")] -enum MessageContent { - #[serde(rename = "m.text")] - Text { body: String }, - - #[serde(other)] - Other, -} - -#[derive(Serialize, Deserialize, Debug)] -#[serde(tag = "membership")] -enum MemberContent { - #[serde(rename = "invite")] - Invite { - // TODO: maybe leave empty? - #[serde(default)] - #[serde(alias = "displayname")] - display_name: Option, - }, - - #[serde(other)] - Other, -} - -#[derive(Serialize, Deserialize, Debug)] -struct RoomEvent { - content: MessageContent, - event_id: String, -} - -#[derive(Serialize, Deserialize, Debug)] -struct MemberEvent { - content: MemberContent, -} - -#[derive(Serialize, Deserialize, Debug)] -#[serde(tag = "type")] -enum Event { - #[serde(rename = "m.room.message")] - Room(RoomEvent), - #[serde(rename = "m.room.member")] - Member(MemberEvent), - - #[serde(other)] - Other, -} - -#[derive(Serialize, Deserialize, Debug)] -struct Timeline { - events: Vec, -} - -#[derive(Serialize, Deserialize, Debug)] -struct Room { - timeline: Timeline, -} - -#[derive(Serialize, Deserialize, Debug)] -struct Rooms { - invite: HashMap, - join: HashMap, -} - -#[derive(Serialize, Deserialize, Debug)] -struct SyncCommand { - next_batch: String, - rooms: Rooms, -} - -async fn sync>(key: S) -> Result<(), Box> { - let body = reqwest::get(&format!( - "https://matrix.org/_matrix/client/r0/sync?access_token={}&timeout=3000", - key.as_ref() - )) - .await? - .text() - .await?; - let sync: SyncCommand = serde_json::from_str(&body)?; - println!("{:#?}", sync); - Ok(()) -} +use axfive_matrix_dicebot::matrix::SyncCommand; +use axfive_matrix_dicebot::bot::{DiceBot, Config}; +use std::fs::read_to_string; #[tokio::main] async fn main() -> Result<(), Box> { - let key = std::env::args() + let config_path = std::env::args() .skip(1) .next() - .expect("Need a key as an argument"); + .expect("Need a config as an argument"); + let config = { + let contents = read_to_string(config_path)?; + toml::from_str(&contents)? + }; + println!("Logging in"); + let mut bot = DiceBot::new(config).await?; + println!("Logged in"); + let mut sigint = signal(SignalKind::interrupt())?; loop { + println!("Loop"); select! { _ = sigint.recv() => { break; } - _ = sync(&key) => { + _ = bot.sync() => { } } } - Ok(()) + println!("Logging out"); + bot.logout().await } diff --git a/src/matrix.rs b/src/matrix.rs new file mode 100644 index 0000000..0766fcb --- /dev/null +++ b/src/matrix.rs @@ -0,0 +1,77 @@ +use serde::{self, Deserialize, Serialize}; +use std::collections::HashMap; + +#[derive(Serialize, Deserialize, Debug)] +#[serde(tag = "msgtype")] +#[serde(rename = "m.text")] +pub struct TextMessage { + body: String +} + +// Need untagged because redactions are blank +#[derive(Serialize, Deserialize, Debug)] +#[serde(untagged)] +pub enum MessageContent { + Text(TextMessage), + Other(serde_json::Value), +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(tag = "membership")] +pub enum MemberContent { + #[serde(rename = "invite")] + Invite { + // TODO: maybe leave empty? + #[serde(default)] + #[serde(alias = "displayname")] + display_name: Option, + }, + + #[serde(other)] + Other, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct RoomEvent { + pub content: MessageContent, + pub event_id: String, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct MemberEvent { + pub content: MemberContent, +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(tag = "type")] +pub enum Event { + #[serde(rename = "m.room.message")] + Room(RoomEvent), + #[serde(rename = "m.room.member")] + Member(MemberEvent), + + #[serde(other)] + Other, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct Timeline { + pub events: Vec, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct Room { + pub timeline: Timeline, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct Rooms { + pub invite: HashMap, + pub join: HashMap, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct SyncCommand { + pub next_batch: String, + pub rooms: Rooms, +}