mod mattermost; use std::path::Path; use anyhow::anyhow; use tokio::{self}; use tracing::{debug, warn}; struct Vav { db_connection: Option } impl Vav{ fn new>(path:Option) -> Self { let ret = Self{ db_connection:path.and_then(|path|sqlite::Connection::open_with_full_mutex(path).ok()), }; if let Some(connection) = &ret.db_connection{ let create_table_res=connection.execute("CREATE TABLE keyval (key text NOT NULL PRIMARY KEY, value text NOT NULL)"); if let Err(err) = create_table_res{ warn!("Error while creating db {err}"); } } ret } } const INSERT_STATEMENT:&str = "INSERT INTO keyval (key,value) VALUES (?,?)"; const SELECT_ONE:&str = "SELECT key,value FROM keyval where key=?"; const SELECT_ALL:&str = "SELECT key,value FROM keyval"; #[async_trait::async_trait] impl mattermost::Handler for Vav { async fn handle( &self, update: mattermost::model::WebsocketUpdate, client: &mattermost::Client, ) -> Result<(), anyhow::Error> { debug!("as json: {update:?}"); let posted = match update.update { mattermost::model::Update::Posted(posted) => posted, _ => return Ok(()), }; let message = &posted.post.message; if !message.starts_with('!') { return Ok(()); } let message=message.strip_prefix('!').unwrap(); let (message,rest) = message.split_once(' ').unwrap_or((message,"")); match message{ "store" =>{ let message = rest; let (name,value) =message.split_once(' ').ok_or(anyhow::anyhow!("missing value in command store"))?; if let Some(connection) = &self.db_connection{ let mut statement = connection.prepare(INSERT_STATEMENT)?; statement.bind((1,name))?; statement.bind((2,value))?; if let Err(err) = statement.next(){ warn!("Error while writing to db {err}"); } } } "lookup" =>{ let name = rest; let response =if let Some(connection) = &self.db_connection{ let mut statement = connection.prepare(SELECT_ONE)?; statement.bind((1,name))?; match statement.next(){ Ok(sqlite::State::Done) => { "no entry under that name".to_owned() }, Ok(sqlite::State::Row) => { statement.read::(1)? }, Err(err) => { warn!("Error while writing to db {err}"); return Err(err.into())}, } } else {"uggh, no db".to_owned()}; client.reply_to(posted.post,response).await?; } "list" =>{ let response =if let Some(connection) = &self.db_connection{ let mut statement = connection.prepare(SELECT_ALL)?; let mut res =vec!["Stored keys:".to_owned()]; while let Ok(result) = statement.next(){ if result == sqlite::State::Done{ break;} res.push(statement.read::(0)?) } res.join("\n") } else {"uggh, no db".to_owned()}; client.reply_to(posted.post,response).await?; } _ => return Err(anyhow!("Unrecognized command {message}")), } Ok(()) } } #[tokio::main(worker_threads = 2)] async fn main() -> Result<(), Box> { tracing_subscriber::fmt::init(); let login = std::env::var("USER_MAIL")?; let password = std::env::var("USER_PASSWORD")?; let db = std::env::var("SQLITE_PATH").ok(); let auth = mattermost::AuthData { login, password }; let mut client = mattermost::Client::new(auth, "https://mattermost.continuum.ii.uni.wroc.pl"); client.update_bearer_token().await?; { let (shutdown_send, shutdown_recv) = tokio::sync::mpsc::unbounded_channel(); let websocket_task =tokio::spawn( async move { client.handle_websocket_stream(Vav::new(db),shutdown_recv).await}); match tokio::signal::ctrl_c().await { Ok(()) => {}, Err(err) => { eprintln!("Unable to listen for shutdown signal: {}", err); // we also shut down in case of error }, } shutdown_send.send(())?; websocket_task.await??; } Ok(()) }