aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMax Audron <audron@cocaine.farm>2021-10-22 19:08:59 +0200
committerMax Audron <audron@cocaine.farm>2021-10-22 19:09:39 +0200
commit309899168a086de88acf97fd6683387a7af7078c (patch)
tree846075c1e9af0d7139edae5597f1147b851ed2b2 /src
parentremove wolfram alpha url shortening (diff)
write tons of documentation and reorganize some modules
Diffstat (limited to 'src')
-rw-r--r--src/config.rs120
-rw-r--r--src/hooks/intensify.rs1
-rw-r--r--src/hooks/mod.rs20
-rw-r--r--src/hooks/pet.rs9
-rw-r--r--src/hooks/sed/mod.rs19
-rw-r--r--src/hooks/sed/parser.rs2
-rw-r--r--src/hooks/shifty_eyes.rs2
-rw-r--r--src/hooks/wolfram_alpha.rs8
-rw-r--r--src/lib.rs55
-rw-r--r--src/main.rs2
-rw-r--r--src/util/formatting/color.rs1
-rw-r--r--src/util/formatting/mod.rs57
-rw-r--r--src/util/formatting/truncate.rs1
-rw-r--r--src/util/mod.rs9
-rw-r--r--src/util/web/mod.rs (renamed from src/util/web.rs)34
-rw-r--r--src/util/web/url_shorteners.rs21
16 files changed, 298 insertions, 63 deletions
diff --git a/src/config.rs b/src/config.rs
index 4429184..ec79591 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -1,3 +1,101 @@
+//! The bots main configuration is housed in the [Config] struct.
+//!
+//! The configuration is by default loaded from `config.toml` in the current directory,
+//! or from the path that is set by the `CATINATOR_CONFIG` variable. Environment
+//! variables prefixed with `CATINATOR_` will also be loaded as configuration.
+//!
+//! The configuration uses [figment](https://docs.rs/figment) for it's configuration.
+//!
+//! # Profiles
+//! The configuration is setup to use different profiles depending on how it is compiled.
+//!
+//! The main configuration file `config.toml` specifies the `default` profile which
+//! sets default variables for most things. The `release` profile is also specified
+//! in `config.toml` and selected when being built with the `--release` flag.
+//! Additional configuration to set a different user while testing can be set in
+//! the `debug` profile, which is selected if built without the `--release` flag.
+//!
+//! When compiled in release mode only the the `config.toml` or `CATINATOR_CONFIG`
+//! and environment variables will be loaded.
+//!
+//! When compiled in debug mode an additional `config.debug.toml` is loaded
+//! from the current directory. You can use this to set different channels and
+//! nickname for testing.
+//!
+//! While developing this library / bot the `config.debug.toml` is also ignored from git.
+//!
+//! # Example
+//! ```toml
+//! [default]
+//! [default.user]
+//! # The username used for sasl and nickserv authentication
+//! username = "catinator"
+//! realname = "moaw"
+//!
+//! [default.server]
+//! hostname = "<host>"
+//! port = 6697
+//! tls = true
+//!
+//! # Enabled sasl, also requires user.password to be set
+//! sasl = true
+//!
+//! [default.settings]
+//! # The prefix to use for commands
+//! # Example: ":about"
+//! prefix = ':'
+//!
+//! [release]
+//! [release.user]
+//! # The backslash has to be escaped here
+//! nickname = "\\__{^-_-^}"
+//! [release.server]
+//! channels = ["<channel 1>", "<channel 2>"]
+//! ```
+//!
+//! # Configuration for hooks
+//!
+//! If you write hooks that require some configuration you can use the
+//! [`Bot::figment`](crate::Bot::figment) function to retrieve the figment and extract your own
+//! configuration.
+//!
+//! ## Example:
+//! ```
+//! use serde::{Serialize, Deserialize};
+//! use anyhow::{Context, Result};
+//! use irc::client::prelude::*;
+//! use figment::providers::Env;
+//!
+//! use catinator::Bot;
+//!
+//! // Define a struct to initialize that contains your configuration
+//! // you need to derive serde's Serialize and Deserialize on it
+//! #[derive(Clone, Debug, Deserialize, Serialize)]
+//! pub struct WolframAlpha {
+//! wa_api_key: String,
+//! }
+//!
+//! impl WolframAlpha {
+//! // Impl a `new()` function that gets passed a reference to the
+//! // bot, or just a reference to the figment.
+//! pub fn new(bot: &Bot) -> Result<WolframAlpha> {
+//! // Get the figment, we have to clone it here to merge our own env var config in.
+//! bot.figment
+//! .clone()
+//! .merge(Env::prefixed("CATINATOR_"))
+//! // Extract the config into the return type of this function
+//! .extract()
+//! .context("failed to extract wolfram alpha config")
+//! }
+//!
+//! pub async fn wa(&self, bot: &Bot, msg: Message) -> Result<()> {
+//! // Impl your command, hook or matcher here to allow it to access it's configuration
+//!
+//! Ok(())
+//! }
+//! }
+//! ```
+
use serde::{Deserialize, Serialize};
use figment::{
@@ -7,10 +105,14 @@ use figment::{
};
use tracing::debug;
+/// Configuration for the Bot
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Deserialize, Serialize)]
pub struct Config {
+ /// Settings related to the [User]
pub user: User,
+ /// Settings related to the [Server]
pub server: Server,
+ /// General bot related [Settings]
pub settings: Settings,
}
@@ -30,24 +132,36 @@ impl From<Config> for irc::client::prelude::Config {
}
}
+/// User related configuration
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Deserialize, Serialize)]
pub struct User {
+ /// The displayed nickname of the bot
pub nickname: String,
+ /// The username used for authentication
pub username: String,
+ /// The password used for authentication
+ /// Defaults to None
#[serde(default)]
pub password: Option<String>,
+ /// The bots realname
pub realname: String,
}
+/// Connection info for the irc server
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Deserialize, Serialize)]
pub struct Server {
+ /// Hostname to connect to
pub hostname: String,
+ /// Port to connect to (default: 6697 TLS)
#[serde(default = "default_port")]
pub port: u16,
+ /// Wether or not to use TLS (default: true)
#[serde(default = "default_tls")]
pub tls: bool,
+ /// Enable or disable sasl authentication (default: false)
#[serde(default)]
pub sasl: bool,
+ /// Channels to join (default: [])
#[serde(default)]
pub channels: Vec<String>,
}
@@ -60,8 +174,10 @@ const fn default_tls() -> bool {
true
}
+/// General settings for the bot
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Deserialize, Serialize)]
pub struct Settings {
+ /// The prefix used for commands like `:about` (default: ':')
#[serde(default = "default_prefix")]
pub prefix: char,
// pub wa_api_key: String,
@@ -72,12 +188,12 @@ const fn default_prefix() -> char {
}
impl Config {
- // Allow the configuration to be extracted from any `Provider`.
+ /// Allow the configuration to be extracted from any [`figment::Provider`].
pub fn from<T: Provider>(provider: T) -> Result<Config, Error> {
Figment::from(provider).extract()
}
- // Provide a default provider, a `Figment`.
+ /// Provide a default provider, a `Figment`.
pub fn figment() -> Figment {
use figment::providers::Env;
diff --git a/src/hooks/intensify.rs b/src/hooks/intensify.rs
index 8e8a817..bbed9d9 100644
--- a/src/hooks/intensify.rs
+++ b/src/hooks/intensify.rs
@@ -2,6 +2,7 @@ use anyhow::{Context, Result};
use irc::client::prelude::*;
use macros::privmsg;
+/// \[Turn things up to eleven\]. Intensifies things written in brackets.
pub fn intensify(bot: &crate::Bot, msg: Message) -> Result<()> {
privmsg!(msg, {
let mut chars = text.chars();
diff --git a/src/hooks/mod.rs b/src/hooks/mod.rs
index 86d5697..822f839 100644
--- a/src/hooks/mod.rs
+++ b/src/hooks/mod.rs
@@ -1,17 +1,22 @@
-extern crate rand;
+//! The bots hooks, commands and matchers. For explanation of different types see [crate::catinator]
+//!
+//! # Implementing hooks
use anyhow::Result;
use irc::client::prelude::*;
-pub mod intensify;
-pub mod pet;
+mod intensify;
+mod pet;
+mod shifty_eyes;
+
+pub use intensify::*;
+pub use pet::*;
+pub use shifty_eyes::*;
+
pub mod sed;
-pub mod shifty_eyes;
pub mod wolfram_alpha;
-pub use intensify::intensify;
-pub use shifty_eyes::shifty_eyes;
-
+/// Replies with some information about the bot
pub fn about(bot: &crate::Bot, msg: Message) -> Result<()> {
bot.send_privmsg(
msg.response_target().unwrap(),
@@ -26,6 +31,7 @@ pub fn about(bot: &crate::Bot, msg: Message) -> Result<()> {
Ok(())
}
+/// Listen to AUTHENTICATE messages and perform SASL authentication
pub fn sasl(bot: &crate::Bot, msg: Message) -> Result<()> {
match msg.command {
Command::AUTHENTICATE(text) => {
diff --git a/src/hooks/pet.rs b/src/hooks/pet.rs
index 3e2defe..d11f562 100644
--- a/src/hooks/pet.rs
+++ b/src/hooks/pet.rs
@@ -1,5 +1,3 @@
-use std::str;
-
use anyhow::{Context, Result};
use irc::client::prelude::*;
use macros::privmsg;
@@ -15,14 +13,9 @@ const PET_RESPONSE: [&str; 5] = [
"strikes you with it's sharp claws",
];
-/// Pet cat
+/// Pet the cat, get rekt
///
/// Sends some random action when petted.
-///
-/// # See also
-///
-/// - [`Bot::send_action`]
-/// - RESPONSE
pub fn pet(bot: &crate::Bot, msg: Message) -> Result<()> {
privmsg!(msg, {
bot.send_action(
diff --git a/src/hooks/sed/mod.rs b/src/hooks/sed/mod.rs
index 3128372..7b7906c 100644
--- a/src/hooks/sed/mod.rs
+++ b/src/hooks/sed/mod.rs
@@ -1,3 +1,22 @@
+//! sed for irc. replace text in past messages
+//!
+//! Perform sed replaces on single messages in the buffers history, up to 10k messages old.
+//!
+//! ```text
+//! <+basso> crystalmett: welcome back
+//! <%audron> s/wel/whale/
+//! <\__{^-_-^}> <basso> crystalmett: whalecome back
+//! ```
+//!
+//! # Supported flags
+//! ```text
+//! g global: match every occurrence of the regex in a line
+//! i case-insensitive: letters match both upper and lower case
+//! s allow . to match \n
+//! U swap the meaning of x* and x*?
+//! x ignore whitespace and allow line comments (starting with `#`)
+//! ```
+
use anyhow::{anyhow, bail, Context, Result};
use irc::client::prelude::*;
diff --git a/src/hooks/sed/parser.rs b/src/hooks/sed/parser.rs
index eb7ef3e..815803c 100644
--- a/src/hooks/sed/parser.rs
+++ b/src/hooks/sed/parser.rs
@@ -3,7 +3,7 @@ use std::{borrow::Cow, str::Chars};
use bitflags::bitflags;
use regex::Regex;
-use crate::util::formatting::Formatting;
+use crate::util::Formatting;
type Commands = Vec<Command>;
diff --git a/src/hooks/shifty_eyes.rs b/src/hooks/shifty_eyes.rs
index 4680007..6203075 100644
--- a/src/hooks/shifty_eyes.rs
+++ b/src/hooks/shifty_eyes.rs
@@ -4,6 +4,7 @@ use irc::client::prelude::*;
const EYES: [char; 11] = ['^', 'v', 'V', '>', '<', 'x', 'X', '-', 'o', 'O', '.'];
const NOSE: [char; 7] = ['.', '_', '-', ';', '\'', '"', '~'];
+/// you are being watched <.<
pub fn shifty_eyes(bot: &crate::Bot, msg: Message) -> Result<()> {
if let Command::PRIVMSG(_, text) = msg.command.clone() {
if text.len() == 3 {
@@ -41,6 +42,7 @@ fn invert(input: char) -> Result<char> {
'>' => Ok('<'),
'<' => Ok('>'),
'x' => Ok('o'),
+ '.' => Ok('o'),
'X' => Ok('O'),
'-' => Ok('o'),
'o' => Ok('-'),
diff --git a/src/hooks/wolfram_alpha.rs b/src/hooks/wolfram_alpha.rs
index cdd32ca..9f56df4 100644
--- a/src/hooks/wolfram_alpha.rs
+++ b/src/hooks/wolfram_alpha.rs
@@ -1,7 +1,7 @@
-use crate::util::{
- formatting::truncate,
- web::{quote_plus, IsgdUrlShortener, UrlShortener},
-};
+//! ask wolfram alpha a query
+
+use crate::util::{quote_plus, truncate};
+// use crate::util::{url_shorteners::Isgd, UrlShortener};
use anyhow::{bail, Context, Error, Result};
use figment::providers::Env;
use futures::join;
diff --git a/src/lib.rs b/src/lib.rs
index 56f64a5..7a7cdda 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,3 +1,35 @@
+//! 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;
@@ -10,22 +42,24 @@ pub mod config;
pub mod hooks;
pub mod util;
+// Rexport of the catinator proc macros
pub use macros::catinator;
-#[macro_export]
-macro_rules! reply {
- ( $msg:expr, $text:expr ) => {
- bot.send_privmsg($msg.response_target().unwrap(), $text.as_str())?;
- };
-}
-
+/// 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")?;
@@ -46,10 +80,14 @@ impl Bot {
Ok(bot)
}
+ /// Get the bots figment to use when building your own configuration.
+ /// See [config]
pub fn figment(&self) -> &figment::Figment {
&self.figment
}
+ /// Initialize a sasl connection, you usually don't need
+ /// to run this yourself as it is done during [Bot::new].
pub async fn sasl_init(&self) -> Result<()> {
self.irc_client
.send_cap_req(&vec![irc::client::prelude::Capability::Sasl])?;
@@ -65,6 +103,7 @@ impl Bot {
Ok(())
}
+ /// Send a privmsg to the target `#channel` or `user`
pub fn send_privmsg(
&self,
target: &str,
@@ -73,6 +112,7 @@ impl Bot {
self.irc_client.send_privmsg(target, message)
}
+ /// Send a notice to the target `#channel` or `user`
pub fn send_notice(
&self,
target: &str,
@@ -81,6 +121,7 @@ impl Bot {
self.irc_client.send_notice(target, message)
}
+ /// Send an action (`/me`) to the target `#channel` or `user`
pub fn send_action(
&self,
target: &str,
diff --git a/src/main.rs b/src/main.rs
index 747948e..66148fd 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -44,7 +44,7 @@ async fn main() {
command(
"pet",
"Pet the cat, cats generally like pets.",
- catinator::hooks::pet::pet
+ catinator::hooks::pet
),
command(
"about",
diff --git a/src/util/formatting/color.rs b/src/util/formatting/color.rs
index 6907805..ac78b9c 100644
--- a/src/util/formatting/color.rs
+++ b/src/util/formatting/color.rs
@@ -1,3 +1,4 @@
+/// The basic 16 IRC color codes
pub enum Color {
White = 00,
Black = 01,
diff --git a/src/util/formatting/mod.rs b/src/util/formatting/mod.rs
index 5cfdfa8..a20b48f 100644
--- a/src/util/formatting/mod.rs
+++ b/src/util/formatting/mod.rs
@@ -1,9 +1,31 @@
+//! Tools for formatting irc messages
+
mod truncate;
mod color;
pub use truncate::*;
pub use color::*;
+/// Turn strings bold, italic,underline, strikethrough, and monospace.
+///
+/// The Formatting trait is implemented on `String` and `&str` for your convenience
+///
+/// # Format a string
+/// ```
+/// use catinator::util::Formatting;
+///
+/// let text = "this should be bold";
+/// assert_eq!("\x02this should be bold\x02", text.bold())
+/// ```
+///
+/// # Use raw formatting code
+/// ```
+/// use catinator::util::Formatting;
+///
+/// let text = "this should be bold";
+/// let result = format!("{}{}", String::BOLD, text);
+/// assert_eq!("\x02this should be bold", result)
+/// ```
pub trait Formatting<'r> {
const BOLD: &'r str = "\x02";
const ITALIC: &'r str = "\x1D";
@@ -12,11 +34,11 @@ pub trait Formatting<'r> {
const MONOSPACE: &'r str = "\x11";
const COLOR: &'r str = "\x03";
- fn bold(self) -> Self;
- fn italic(self) -> Self;
- fn underline(self) -> Self;
- fn strikethrough(self) -> Self;
- fn monospace(self) -> Self;
+ fn bold(self) -> String;
+ fn italic(self) -> String;
+ fn underline(self) -> String;
+ fn strikethrough(self) -> String;
+ fn monospace(self) -> String;
// fn color(self, color: Color) -> Self;
}
@@ -65,6 +87,31 @@ impl<'r> Formatting<'r> for String {
// }
}
+impl<'r> Formatting<'r> for &str {
+ fn bold(self) -> String {
+ format!("{}{}{}", Self::BOLD, self, Self::BOLD)
+ }
+
+ fn italic(self) -> String {
+ format!("{}{}{}", Self::ITALIC, self, Self::ITALIC)
+ }
+
+ fn underline(self) -> String {
+ format!("{}{}{}", Self::UNDERLINE, self, Self::UNDERLINE)
+ }
+
+ fn strikethrough(self) -> String {
+ format!("{}{}{}", Self::STRIKETHROUGH, self, Self::STRIKETHROUGH)
+ }
+
+ fn monospace(self) -> String {
+ format!("{}{}{}", Self::MONOSPACE, self, Self::MONOSPACE)
+ }
+
+ // TODO implement color codes
+ // fn color(mut self, foreground: Option<Color>, background: Option<Color>) -> Self { }
+}
+
#[cfg(all(test, feature = "bench"))]
mod bench {
use super::*;
diff --git a/src/util/formatting/truncate.rs b/src/util/formatting/truncate.rs
index c1b6257..80caa23 100644
--- a/src/util/formatting/truncate.rs
+++ b/src/util/formatting/truncate.rs
@@ -1,4 +1,5 @@
/// Truncates a string after a certain number of characters.
+///
/// Function always tries to truncate on a word boundary.
/// Reimplemented from gonzobot.
pub fn truncate(text: &str, len: usize) -> String {
diff --git a/src/util/mod.rs b/src/util/mod.rs
index a8988c3..23ef2ce 100644
--- a/src/util/mod.rs
+++ b/src/util/mod.rs
@@ -1,2 +1,7 @@
-pub mod formatting;
-pub mod web;
+//! Utilities for dealing with IRC and bot making
+
+mod formatting;
+mod web;
+
+pub use formatting::*;
+pub use web::*;
diff --git a/src/util/web.rs b/src/util/web/mod.rs
index b9f44c1..4e886af 100644
--- a/src/util/web.rs
+++ b/src/util/web/mod.rs
@@ -1,36 +1,18 @@
-use anyhow::{Context, Error, Result};
+use anyhow::{Error, Result};
use async_trait::async_trait;
-use reqwest::{get, Url};
use urlparse::quote_plus as urlparse_quote_plus;
-#[async_trait]
-pub trait UrlShortener {
- fn new() -> Self;
- async fn shorten(&self, url: &str) -> Result<String, Error>;
-}
-
-pub struct IsgdUrlShortener {}
+pub mod url_shorteners;
+/// Shorten urls
#[async_trait]
-impl UrlShortener for IsgdUrlShortener {
- fn new() -> Self {
- Self {}
- }
-
- async fn shorten(&self, url: &str) -> Result<String, Error> {
- Ok(get(Url::parse(&format!(
- "https://is.gd/create.php?format=simple&url={}",
- url
- ))
- .context("Failed to parse url")?)
- .await
- .context("Failed to make request")?
- .text()
- .await
- .context("failed to get request response text")?)
- }
+pub trait UrlShortener {
+ /// Call this method with the url you want shortened.
+ /// Returns the shortened url.
+ async fn shorten(url: &str) -> Result<String, Error>;
}
+/// quote strings to be URL save
pub fn quote_plus(text: &str) -> Result<String, Error> {
Ok(urlparse_quote_plus(text, b"")?)
}
diff --git a/src/util/web/url_shorteners.rs b/src/util/web/url_shorteners.rs
new file mode 100644
index 0000000..74d62ce
--- /dev/null
+++ b/src/util/web/url_shorteners.rs
@@ -0,0 +1,21 @@
+use anyhow::{Context, Error, Result};
+use async_trait::async_trait;
+use reqwest::{get, Url};
+
+pub struct Isgd;
+
+#[async_trait]
+impl super::UrlShortener for Isgd {
+ async fn shorten(url: &str) -> Result<String, Error> {
+ Ok(get(Url::parse(&format!(
+ "https://is.gd/create.php?format=simple&url={}",
+ url
+ ))
+ .context("Failed to parse url")?)
+ .await
+ .context("Failed to make request")?
+ .text()
+ .await
+ .context("failed to get request response text")?)
+ }
+}