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}