diff options
| author | Arne Dußin | 2021-07-31 11:51:25 +0200 |
|---|---|---|
| committer | Arne Dußin | 2021-07-31 11:51:25 +0200 |
| commit | 2f14f7edf29bd7010e5f8be8691a907c8ede1278 (patch) | |
| tree | 2708ee050e3dba8ca75be2160428e1c4bd487367 /src | |
| parent | af7514d42b3ec6743fd8037c6e909ae8134dfa4f (diff) | |
| download | matrix-zizek-bot-2f14f7edf29bd7010e5f8be8691a907c8ede1278.tar.gz matrix-zizek-bot-2f14f7edf29bd7010e5f8be8691a907c8ede1278.zip | |
First basic testing zizek bot
Diffstat (limited to 'src')
| -rw-r--r-- | src/bot_config.rs | 40 | ||||
| -rw-r--r-- | src/command_handler.rs | 85 | ||||
| -rw-r--r-- | src/main.rs | 36 | ||||
| -rw-r--r-- | src/zizek_db.rs | 110 |
4 files changed, 269 insertions, 2 deletions
diff --git a/src/bot_config.rs b/src/bot_config.rs new file mode 100644 index 0000000..2438c40 --- /dev/null +++ b/src/bot_config.rs @@ -0,0 +1,40 @@ +//! Configuration module of the telegram bot. Here it gets its information to +//! login to its homeserver. + +use config::{Config, ConfigError, File as ConfigFile}; + +/// Configuration struct of a bot +pub struct BotConfig +{ + user: String, + password: String, + homeserver_url: String, +} + +impl BotConfig +{ + /// Read the default bot configuration file + /// + /// # Return + /// The configuration needed for the bot to function, or, if the + /// configuration file cannot be read, the error that occured while + /// trying to load the configuration file. + pub fn from_config_file() -> Result<BotConfig, ConfigError> + { + let mut config = Config::default(); + config.merge(ConfigFile::with_name("botconfig"))?; + + Ok(Self { + user: config.get_str("user")?, + password: config.get_str("password")?, + homeserver_url: config.get_str("homeserver_url")?, + }) + } + + /// Get the username that should be used by the bot + pub fn user(&self) -> &String { &self.user } + /// Get the password the bot should use to authenticate its user + pub fn password(&self) -> &String { &self.password } + /// Get the homeserver of the bot where it should send the login data to + pub fn homeserver_url(&self) -> &String { &self.homeserver_url } +} diff --git a/src/command_handler.rs b/src/command_handler.rs new file mode 100644 index 0000000..71aba3f --- /dev/null +++ b/src/command_handler.rs @@ -0,0 +1,85 @@ +//! Module to handle commands sent directly to the bot, as opposed to something +//! that scans the messages for Žižek-trigger-words. + +/// The leading command the bot is looking for before any internal command +pub const COMMAND_PREFIX: &'static str = "/zizek "; + +const ZIZEK_ADD_QUOTE_NOTICE: &'static str = "I think I'll add that to my repertoire."; +const ZIZEK_KNOWN_QUOTE_NOTICE: &'static str = "Hey, I know that one, that's a good one!"; +const ZIZEK_EMPTY_NOTICE: &'static str = + "You know, it's quite funny to me. The moment you call I've ran out of material."; +const CMD_ADD: &'static str = "add"; + +use std::sync::{Arc, Mutex}; + +use matrix_bot_api::handlers::{extract_command, HandleResult, MessageHandler}; +use matrix_bot_api::{ActiveBot, Message, MessageType}; + +use crate::zizek_db::ZizekDb; + +/// Command handler struct that can be passed to the matrix bot as a handler +pub struct CommandHandler +{ + zizek_db: Arc<Mutex<ZizekDb>>, +} + +impl CommandHandler +{ + /// Create a new command handler with access to the zizek quote database + pub fn new(zizek_db: Arc<Mutex<ZizekDb>>) -> Self { Self { zizek_db } } + + fn handle_zizek(&self, bot: &ActiveBot, message: &Message) + { + let zizek_lock = self.zizek_db.lock().unwrap(); + let quote = zizek_lock.random_quote().map_or(ZIZEK_EMPTY_NOTICE, |s| &s); + + bot.send_message(quote, &message.room, MessageType::TextMessage); + } + + fn handle_add_quote(&mut self, bot: &ActiveBot, message: &Message) + { + let mut zizek_lock = self.zizek_db.lock().unwrap(); + + let quote = &message.body[COMMAND_PREFIX.len() + CMD_ADD.len() + 1..]; + if zizek_lock.add_quote(quote) { + bot.send_message( + ZIZEK_ADD_QUOTE_NOTICE, + &message.room, + MessageType::TextMessage, + ); + } + else { + bot.send_message( + ZIZEK_KNOWN_QUOTE_NOTICE, + &message.room, + MessageType::TextMessage, + ); + } + } +} + +impl MessageHandler for CommandHandler +{ + fn handle_message(&mut self, bot: &ActiveBot, message: &Message) -> HandleResult + { + if let Some(command) = extract_command(&message.body, COMMAND_PREFIX) { + match command { + "" => { + self.handle_zizek(bot, message); + HandleResult::StopHandling + }, + CMD_ADD => { + self.handle_add_quote(bot, message); + HandleResult::StopHandling + }, + _ => { + println!("Žižek cannot be handled."); + HandleResult::ContinueHandling + }, + } + } + else { + HandleResult::ContinueHandling + } + } +} diff --git a/src/main.rs b/src/main.rs index e7a11a9..a3b1f64 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,35 @@ -fn main() { - println!("Hello, world!"); +#![warn(missing_docs)] + +//! In case you and your friends ever felt like you don't have enough +//! Slavoj Žižek in your life, this matrix bot can help you satisfying that +//! need. + +use std::sync::{Arc, Mutex}; + +use matrix_bot_api::MatrixBot; + +pub mod bot_config; +pub mod command_handler; +pub mod zizek_db; + +use bot_config::BotConfig; +use command_handler::CommandHandler; +use zizek_db::ZizekDb; + +fn main() +{ + let bot_config = BotConfig::from_config_file().expect("Unable to read bot configuration file"); + let zizek_db = Arc::new(Mutex::new( + ZizekDb::from_file("zizek_quotes.db").expect("Unable to load zizek quote database"), + )); + + /* The command handler is the handler with the highest priority, so it gets + * registered first */ + let bot = MatrixBot::new(CommandHandler::new(zizek_db.clone())); + + bot.run( + bot_config.user(), + bot_config.password(), + bot_config.homeserver_url(), + ); } diff --git a/src/zizek_db.rs b/src/zizek_db.rs new file mode 100644 index 0000000..1195822 --- /dev/null +++ b/src/zizek_db.rs @@ -0,0 +1,110 @@ +//! Handling of a database of Slavoj Žižek quotes + +use std::fs::File; +use std::io::{self, BufRead, BufReader, BufWriter, Write}; +use std::path::Path; + +use rand::seq::SliceRandom; + +/// Struct containing all currently loaded Žižek quotes and the means to +/// manipulate them. +pub struct ZizekDb +{ + quotes: Vec<String>, +} + +impl ZizekDb +{ + /// Read quotes from the file with the given name. + /// + /// # Return + /// Database containing the quotes in case the load was successful or an + /// `io::Error` in case it could not be loaded. + pub fn from_file<P>(filename: P) -> Result<Self, io::Error> + where + P: AsRef<Path>, + { + let file = File::open(filename)?; + let lines = BufReader::new(file).lines(); + + let mut quotes: Vec<String> = Vec::new(); + for line in lines { + let line = line?.trim().to_owned(); + if !line.is_empty() { + quotes.push(line); + } + } + + Ok(Self { quotes }) + } + + /// Write contents of the database to the file. + /// The file is overwritten by the contents of the database, so be careful + /// if you want to concatenate the quotes of this database to the ones + /// of a different one. + pub fn write_to_file<P>(&self, filename: P) -> Result<(), io::Error> + where + P: AsRef<Path>, + { + let file = File::create(filename)?; + let mut writer = BufWriter::new(file); + + for q in &self.quotes { + write!(writer, "{}\n", q)?; + } + + writer.flush() + } + + /// Check if a quote exists in the database + /// + /// # Return + /// `true` if the quote is registered, `false` if not + pub fn has_quote(&self, quote: &str) -> bool { self.quotes.iter().any(|q| q == quote) } + + /// Get a random piece of philosophic goodness + pub fn random_quote(&self) -> Option<&String> + { + let mut rng = rand::thread_rng(); + self.quotes.choose(&mut rng) + } + + /// Add a quote to the index + /// + /// # Return + /// `true` in case the quote was successfully added or `false` if it could + /// not, in case the quote already exists or an empty string was given. + pub fn add_quote(&mut self, quote: &str) -> bool + { + let quote = quote.trim(); + + if quote.is_empty() { + return false; + } + + if !self.has_quote(quote) { + self.quotes.push(quote.to_owned()); + true + } + else { + false + } + } + + /// Remove a quote from the database + /// + /// # Return + /// `true` if the quote was found and removed, `false` if there was no such + /// quote + pub fn remove_quote(&mut self, quote: &str) -> bool + { + for (i, q) in &mut self.quotes.iter_mut().enumerate() { + if q == quote { + self.quotes.remove(i); + return true; + } + } + + false + } +} |
