catinator/
lib.rs

1//! catinator is a general purpose irc bot making crate.
2//!
3//! Most of the heavy lifting is done by the [irc](https://docs.rs/irc) crate,
4//! but catinator adds useful high level utilities to make the bot making easier.
5//!
6//! Example:
7//! ```no_run
8//! #[tokio::main]
9//! async fn main() {
10//!     tracing_subscriber::fmt::init();
11//!
12//!     // Initialize the bot, loading the config and establishing the connection
13//!     let mut bot = catinator::Bot::new().await.unwrap();
14//!
15//!     // Setup any modules that require it.
16//!     let wolfram_alpha = catinator::hooks::wolfram_alpha::WolframAlpha::new(&bot)
17//!         .expect("failed to initialize WolframAlpha command");
18//!
19//!     // Call the catinator macro to setup the hooks, matchers and commands
20//!     catinator::catinator![
21//!         // For example add the sasl hook to handle sasl authentication
22//!         hook("sasl", "Handle Authentication.", AUTHENTICATE, catinator::hooks::sasl),
23//!
24//!         // Add a matcher that executes on a specific regex
25//!         matcher("shifty_eyes", ">.>", r"^\S{3}$", catinator::hooks::shifty_eyes),
26//!
27//!         // Add an async command that calls a method on the previously instantiated struct.
28//!         async command("wa", "Returns Wolfram Alpha results for a query", wolfram_alpha.wa),
29//!     ];
30//! }
31//! ```
32
33#![cfg_attr(all(test, feature = "bench"), feature(test))]
34#[cfg(all(test, feature = "bench"))]
35extern crate test;
36
37use anyhow::{Context, Result};
38
39use irc::client::prelude::*;
40
41pub mod config;
42pub mod hooks;
43pub mod util;
44
45// Rexport of the catinator proc macros
46pub use macros::catinator;
47
48/// The struct handling bot actions and configuration
49pub struct Bot {
50    /// The base config of the bot
51    pub config: config::Config,
52    /// The figment the config is extracted from
53    pub figment: figment::Figment,
54    /// The irc client object, used to send messages etc
55    /// It is recommended to use the methods directly on the Bot struct instead.
56    pub irc_client: irc::client::Client,
57}
58
59impl Bot {
60    /// Initializes the bot.
61    /// Loads configuration from `CATINATOR_` environment variables and the `config.toml` file
62    /// Starts the connection to the irc server.
63    pub async fn new() -> Result<Bot> {
64        let figment = config::Config::figment();
65        let config: config::Config = figment.extract().context("failed to extract config")?;
66
67        let irc_client = Client::from_config(config.clone().into()).await?;
68
69        let bot = Bot {
70            irc_client,
71            config,
72            figment,
73        };
74
75        if bot.config.server.sasl && bot.config.user.password.is_some() {
76            tracing::info!("initializing sasl");
77            bot.sasl_init().await.unwrap()
78        }
79
80        Ok(bot)
81    }
82
83    /// Get the bots figment to use when building your own configuration.
84    /// See [config]
85    pub fn figment(&self) -> &figment::Figment {
86        &self.figment
87    }
88
89    /// Initialize a sasl connection, you usually don't need
90    /// to run this yourself as it is done during [Bot::new].
91    pub async fn sasl_init(&self) -> Result<()> {
92        self.irc_client
93            .send_cap_req(&vec![irc::client::prelude::Capability::Sasl])?;
94        self.irc_client
95            .send(Command::NICK(self.config.user.nickname.clone()))?;
96        self.irc_client.send(Command::USER(
97            self.config.user.nickname.clone(),
98            "0".to_owned(),
99            self.config.user.realname.clone(),
100        ))?;
101        self.irc_client.send_sasl_plain()?;
102
103        Ok(())
104    }
105
106    /// Send a privmsg to the target `#channel` or `user`
107    pub fn send_privmsg(
108        &self,
109        target: &str,
110        message: &str,
111    ) -> std::result::Result<(), irc::error::Error> {
112        self.irc_client.send_privmsg(target, message)
113    }
114
115    /// Send a notice to the target `#channel` or `user`
116    pub fn send_notice(
117        &self,
118        target: &str,
119        message: &str,
120    ) -> std::result::Result<(), irc::error::Error> {
121        self.irc_client.send_notice(target, message)
122    }
123
124    /// Send an action (`/me`) to the target `#channel` or `user`
125    pub fn send_action(
126        &self,
127        target: &str,
128        message: &str,
129    ) -> std::result::Result<(), irc::error::Error> {
130        self.irc_client.send_action(target, message)
131    }
132}