aboutsummaryrefslogtreecommitdiff
path: root/src/lib.rs
blob: 905f2205e984ba3e064f6492a5e8827159300782 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
//! catinator is a general purpose irc bot making crate.
//!
//! Most of the heavy lifting is done by the [irc](https://docs.rs/irc) crate,
//! but catinator adds useful high level utilities to make the bot making easier.
//!
//! Example:
//! ```no_run
//! #[tokio::main]
//! async fn main() {
//!     tracing_subscriber::fmt::init();
//!
//!     // Initialize the bot, loading the config and establishing the connection
//!     let mut bot = catinator::Bot::new().await.unwrap();
//!
//!     // Setup any modules that require it.
//!     let wolfram_alpha = catinator::hooks::wolfram_alpha::WolframAlpha::new(&bot)
//!         .expect("failed to initialize WolframAlpha command");
//!
//!     // Call the catinator macro to setup the hooks, matchers and commands
//!     catinator::catinator![
//!         // For example add the sasl hook to handle sasl authentication
//!         hook("sasl", "Handle Authentication.", AUTHENTICATE, catinator::hooks::sasl),
//!
//!         // Add a matcher that executes on a specific regex
//!         matcher("shifty_eyes", ">.>", r"^\S{3}$", catinator::hooks::shifty_eyes),
//!
//!         // Add an async command that calls a method on the previously instantiated struct.
//!         async command("wa", "Returns Wolfram Alpha results for a query", wolfram_alpha.wa),
//!     ];
//! }
//! ```

#![cfg_attr(all(test, feature = "bench"), feature(test))]
#[cfg(all(test, feature = "bench"))]
extern crate test;

use anyhow::{Context, Result};

use irc::client::prelude::*;

pub mod config;
pub mod hooks;
pub mod util;

// Rexport of the catinator proc macros
pub use macros::catinator;

/// The struct handling bot actions and configuration
pub struct Bot {
    /// The base config of the bot
    pub config: config::Config,
    /// The figment the config is extracted from
    pub figment: figment::Figment,
    /// The irc client object, used to send messages etc
    /// It is recommended to use the methods directly on the Bot struct instead.
    pub irc_client: irc::client::Client,
}

impl Bot {
    /// Initializes the bot.
    /// Loads configuration from `CATINATOR_` environment variables and the `config.toml` file
    /// Starts the connection to the irc server.
    pub async fn new() -> Result<Bot> {
        let figment = config::Config::figment();
        let config: config::Config = figment.extract().context("failed to extract config")?;

        let irc_client = Client::from_config(config.clone().into()).await?;

        let bot = Bot {
            irc_client,
            config,
            figment,
        };

        if bot.config.server.sasl && bot.config.user.password.is_some() {
            tracing::info!("initializing sasl");
            bot.sasl_init().unwrap()
        } else if let Some(password) = bot.config.server.password.as_ref() {
            tracing::info!("sending server password");
            bot.irc_client.send(Command::PASS(password.clone()))?;
            bot.register_connection()?;
        }

        Ok(bot)
    }

    /// Get the bots figment to use when building your own configuration.
    /// See [config]
    pub fn figment(&self) -> &figment::Figment {
        &self.figment
    }

    pub fn register_connection(&self) -> Result<()> {
        self.irc_client
            .send(Command::NICK(self.config.user.nickname.clone()))?;
        self.irc_client.send(Command::USER(
            self.config.user.username.clone(),
            "0".to_owned(),
            self.config.user.realname.clone(),
        ))?;

        Ok(())
    }

    /// Initialize a sasl connection, you usually don't need
    /// to run this yourself as it is done during [Bot::new].
    pub fn sasl_init(&self) -> Result<()> {
        self.irc_client
            .send_cap_req(&[irc::client::prelude::Capability::Sasl])?;
        self.register_connection()?;
        self.irc_client.send_sasl_plain()?;

        Ok(())
    }

    /// Send a privmsg to the target `#channel` or `user`
    pub fn send_privmsg(
        &self,
        target: &str,
        message: &str,
    ) -> std::result::Result<(), irc::error::Error> {
        self.irc_client.send_privmsg(target, message)
    }

    /// Send a notice to the target `#channel` or `user`
    pub fn send_notice(
        &self,
        target: &str,
        message: &str,
    ) -> std::result::Result<(), irc::error::Error> {
        self.irc_client.send_notice(target, message)
    }

    /// Send an action (`/me`) to the target `#channel` or `user`
    pub fn send_action(
        &self,
        target: &str,
        message: &str,
    ) -> std::result::Result<(), irc::error::Error> {
        self.irc_client.send_action(target, message)
    }
}