summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorArne Dußin2021-07-31 11:51:25 +0200
committerArne Dußin2021-07-31 11:51:25 +0200
commit2f14f7edf29bd7010e5f8be8691a907c8ede1278 (patch)
tree2708ee050e3dba8ca75be2160428e1c4bd487367 /src
parentaf7514d42b3ec6743fd8037c6e909ae8134dfa4f (diff)
downloadmatrix-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.rs40
-rw-r--r--src/command_handler.rs85
-rw-r--r--src/main.rs36
-rw-r--r--src/zizek_db.rs110
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
+ }
+}