irc_bot/core/
trigger.rs

1use super::BotCmdResult;
2use super::ErrorKind;
3use super::Module;
4use super::MsgMetadata;
5use super::Result;
6use super::State;
7use super::TriggerHandler;
8use rando::Rando;
9use regex::Regex;
10use std::borrow::Cow;
11use std::ops::DerefMut;
12use std::sync::Arc;
13use std::sync::RwLock;
14use std::sync::RwLockReadGuard;
15use util;
16use uuid::Uuid;
17
18pub struct Trigger {
19    pub name: Cow<'static, str>,
20    pub provider: Arc<Module>,
21    pub regex: Arc<RwLock<Regex>>,
22    pub priority: TriggerPriority,
23    pub(super) handler: Arc<TriggerHandler>,
24    pub help_msg: Cow<'static, str>,
25    pub uuid: Uuid,
26}
27
28pub(super) struct TemporaryTrigger {
29    pub(super) inner: Trigger,
30    pub(super) activation_limit: u16,
31}
32
33#[derive(Debug)]
34pub enum TriggerAttr {
35    /// Use this attribute for triggers that should trigger even on messages that aren't addressed
36    /// to the bot.
37    ///
38    /// As of 2018-01-11, this doesn't actually do anything yet.
39    AlwaysWatching,
40}
41
42#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
43pub enum TriggerPriority {
44    /// Designates the trigger as having minimum priority.
45    Minimum,
46
47    /// Designates the trigger as having low priority. This is appropriate for triggers that are
48    /// intended primarily for jocular, fun, playful, comedic, humorous, levitous, frivolous, or
49    /// otherwise non-serious purposes.
50    Low,
51
52    /// Designates the trigger as having medium priority.
53    Medium,
54
55    /// Designates the trigger as having high priority. This is appropriate for triggers that
56    /// implement important functionality of a particular bot.
57    High,
58
59    /// Designates the trigger as having maximum priority.
60    Maximum,
61}
62
63impl Trigger {
64    fn read_regex(&self) -> Result<RwLockReadGuard<Regex>> {
65        self.regex.read().map_err(|_| {
66            ErrorKind::LockPoisoned(
67                format!(
68                    "the regex for trigger {uuid} ({name:?})",
69                    name = self.name,
70                    uuid = self.uuid.hyphenated()
71                ).into(),
72            ).into()
73        })
74    }
75}
76
77/// Returns `None` if no trigger matched.
78pub(super) fn run_any_matching(
79    state: &State,
80    text: &str,
81    msg_metadata: &MsgMetadata,
82) -> Result<Option<BotCmdResult>> {
83    let mut trigger = None;
84
85    for (_priority, triggers) in state.triggers.iter().rev() {
86        if triggers.is_empty() {
87            continue;
88        }
89
90        if let Some(t) = triggers
91            .rand_iter()
92            .with_rng(state.rng()?.deref_mut())
93            .filter(|t| t.read_regex().map(|rx| rx.is_match(text)).unwrap_or(false))
94            .next()
95        {
96            trigger = Some(t);
97            break;
98        }
99    }
100
101    let trigger = match trigger {
102        Some(t) => t,
103        None => return Ok(None),
104    };
105
106    let args = trigger.read_regex()?.captures(text).expect(
107        "We shouldn't have reached this point if the \
108         trigger didn't match!",
109    );
110
111    Ok(Some(util::run_handler(
112        "trigger",
113        trigger.name.clone(),
114        || trigger.handler.run(state, msg_metadata, args),
115    )?))
116}