irc_bot/core/
bot_cmd.rs

1use super::BotCmdHandler;
2use super::Error;
3use super::Module;
4use super::MsgMetadata;
5use super::Reaction;
6use super::Result;
7use super::State;
8use std;
9use std::borrow::Cow;
10use std::sync::Arc;
11use util;
12use yaml_rust::Yaml;
13
14pub struct BotCommand {
15    pub name: Cow<'static, str>,
16    pub provider: Arc<Module>,
17    pub auth_lvl: BotCmdAuthLvl,
18    pub(super) handler: Arc<BotCmdHandler>,
19    pub usage_str: Cow<'static, str>,
20    pub(super) usage_yaml: Yaml,
21    pub help_msg: Cow<'static, str>,
22}
23
24#[derive(Debug)]
25pub enum BotCmdAttr {}
26
27#[derive(Debug)]
28pub enum BotCmdResult {
29    /// The command was processed successfully. Pass through a `Reaction`.
30    Ok(Reaction),
31
32    /// A user invoked the command without having sufficient authorization to do so. A reply will
33    /// be sent informing the user of this.
34    Unauthorized,
35
36    /// A user invoked the command with incorrect syntax. A reply will be sent informing the user
37    /// of the correct syntax.
38    SyntaxErr,
39
40    /// A user invoked the command without providing a required argument, named by the given
41    /// string. This is a more specific version of `SyntaxErr` and should be preferred where
42    /// applicable.
43    ArgMissing(Cow<'static, str>),
44
45    /// A user invoked the command in one-to-one communication (a.k.a. "query" and "PM") without
46    /// providing an argument that is required only in one-to-one communication (such as a channel
47    /// name, which could normally default to the name of the channel in which the command was
48    /// used), named by the given string. This is a more specific version of `ArgMissing` and
49    /// should be preferred where applicable.
50    ArgMissing1To1(Cow<'static, str>),
51
52    /// Pass through an instance of the framework's `Error` type.
53    LibErr(Error),
54
55    /// A user made some miscellaneous error in invoking the command. The given string will be
56    /// included in a reply informing the user of their error.
57    UserErrMsg(Cow<'static, str>),
58
59    /// A miscellaneous error that doesn't seem to be the user's fault occurred while the bot was
60    /// processing the command. The given string will be included in a reply informing the user of
61    /// this.
62    BotErrMsg(Cow<'static, str>),
63}
64
65impl From<Reaction> for BotCmdResult {
66    fn from(r: Reaction) -> Self {
67        BotCmdResult::Ok(r)
68    }
69}
70
71#[derive(Clone, Debug, PartialEq, Eq)]
72pub enum BotCmdAuthLvl {
73    Public,
74    Admin,
75}
76
77pub(super) fn run(
78    state: &State,
79    cmd_name: &str,
80    cmd_args: &str,
81    metadata: &MsgMetadata,
82) -> Result<Option<BotCmdResult>> {
83    let &BotCommand {
84        ref name,
85        ref provider,
86        ref auth_lvl,
87        ref handler,
88        ref usage_yaml,
89        usage_str: _,
90        help_msg: _,
91    } = match state.commands.get(cmd_name) {
92        Some(c) => c,
93        None => return Ok(None),
94    };
95
96    let user_authorized = match auth_lvl {
97        &BotCmdAuthLvl::Public => Ok(true),
98        &BotCmdAuthLvl::Admin => state.have_admin(metadata.prefix),
99    };
100
101    let arg = match parse_arg(usage_yaml, cmd_args) {
102        Ok(arg) => arg,
103        Err(res) => return Ok(Some(res)),
104    };
105
106    let result = match user_authorized {
107        Ok(true) => {
108            debug!("Running bot command {:?} with arg: {:?}", name, arg);
109            match util::run_handler("command", name.clone(), || {
110                handler.run(state, &metadata, &arg)
111            }) {
112                Ok(r) => r,
113                Err(e) => BotCmdResult::LibErr(e),
114            }
115        }
116        Ok(false) => BotCmdResult::Unauthorized,
117        Err(e) => BotCmdResult::LibErr(e),
118    };
119
120    // TODO: Filter `QUIT`s in `irc_send` instead, and check `Reaction::RawMsg`s as well.
121    match result {
122        BotCmdResult::Ok(Reaction::Quit(ref s)) if *auth_lvl != BotCmdAuthLvl::Admin => {
123            Ok(Some(BotCmdResult::BotErrMsg(
124                format!(
125                    "Only commands at authorization level {auth_lvl_owner:?} \
126                     may tell the bot to quit, but the command {cmd_name:?} \
127                     from module {provider_name:?}, at authorization level \
128                     {cmd_auth_lvl:?}, has told the bot to quit with quit \
129                     message {quit_msg:?}.",
130                    auth_lvl_owner = BotCmdAuthLvl::Admin,
131                    cmd_name = name,
132                    provider_name = provider.name,
133                    cmd_auth_lvl = auth_lvl,
134                    quit_msg = s
135                ).into(),
136            )))
137        }
138        r => Ok(Some(r)),
139    }
140}
141
142fn parse_arg<'s>(syntax: &'s Yaml, arg_str: &str) -> std::result::Result<Yaml, BotCmdResult> {
143    use util::yaml as uy;
144
145    match uy::parse_and_check_node(arg_str, syntax, "<argument>", || {
146        Yaml::Hash(Default::default())
147    }) {
148        Ok(arg) => Ok(arg),
149        Err(uy::Error(uy::ErrorKind::YamlScan(_), _)) => Err(BotCmdResult::SyntaxErr),
150        Err(err) => Err(BotCmdResult::LibErr(err.into())),
151    }
152}