irc_bot/modules/
default.rs

1use core::BotCmdAuthLvl as Auth;
2use core::*;
3use regex::Captures;
4use std::borrow::Cow;
5use util;
6use yaml_rust::Yaml;
7
8pub fn mk() -> Module {
9    mk_module("default")
10        .command(
11            "join",
12            "<channel>",
13            "Have the bot join the given channel. Note that a channel name containing the \
14             character '#' will need to be enclosed in quotation marks, like '#channel' or \
15             \"#channel\".",
16            Auth::Admin,
17            Box::new(join),
18            &[],
19        )
20        .command(
21            "part",
22            "{chan: '[channel]', msg: '[message]'}",
23            "Have the bot part from the given channel (defaults to the current channel), with an \
24             optional part message.",
25            Auth::Admin,
26            Box::new(part),
27            &[],
28        )
29        .command(
30            "quit",
31            "{msg: '[message]'}",
32            "Have the bot quit.",
33            Auth::Admin,
34            Box::new(quit),
35            &[],
36        )
37        .command(
38            "ping",
39            "",
40            "Request a short message from the bot, typically for testing purposes.",
41            Auth::Public,
42            Box::new(ping),
43            &[],
44        )
45        .command(
46            "framework-info",
47            "",
48            "Request information about the framework with which the bot was built, such as the URL \
49             of a Web page about it.",
50            Auth::Public,
51            Box::new(bot_fw_info),
52            &[],
53        )
54        .command(
55            "help",
56            "{cmd: '[command]', list: '[list name]'}",
57            "Request help with the bot's features, such as commands.",
58            Auth::Public,
59            Box::new(help),
60            &[],
61        )
62        .trigger(
63            "yes?",
64            "^$",
65            "Say \"Yes?\" in response to otherwise empty messages addressed to the bot.",
66            TriggerPriority::Minimum,
67            Box::new(empty_msg_trigger),
68            &[],
69        )
70        .end()
71}
72
73static FW_SYNTAX_CHECK_FAIL: &str =
74    "The framework should have caught this syntax error before it tried to run this command \
75     handler!";
76
77lazy_static! {
78    static ref YAML_STR_CHAN: Yaml = Yaml::String("chan".into());
79    static ref YAML_STR_CMD: Yaml = Yaml::String("cmd".into());
80    static ref YAML_STR_LIST: Yaml = Yaml::String("list".into());
81    static ref YAML_STR_MSG: Yaml = Yaml::String("msg".into());
82}
83
84fn join(_: &State, _: &MsgMetadata, arg: &Yaml) -> Reaction {
85    Reaction::RawMsg(
86        format!(
87            "JOIN {}",
88            util::yaml::scalar_to_str(arg, Cow::Borrowed).expect(FW_SYNTAX_CHECK_FAIL)
89        ).into(),
90    )
91}
92
93fn part(
94    state: &State,
95    &MsgMetadata {
96        dest: MsgDest { server_id, target },
97        ..
98    }: &MsgMetadata,
99    arg: &Yaml,
100) -> BotCmdResult {
101    let arg = arg.as_hash().expect(FW_SYNTAX_CHECK_FAIL);
102
103    let chan = arg.get(&YAML_STR_CHAN)
104        .map(|y| util::yaml::scalar_to_str(y, Cow::Borrowed).expect(FW_SYNTAX_CHECK_FAIL));
105
106    let chan = match (chan, target) {
107        (Some(c), _) => c,
108        (None, t) if t == state.nick(server_id).unwrap_or("".into()) => {
109            return BotCmdResult::ArgMissing1To1("channel".into())
110        }
111        (None, t) => t.into(),
112    };
113
114    let comment = arg.get(&YAML_STR_MSG)
115        .map(|y| util::yaml::scalar_to_str(y, Cow::Borrowed).expect(FW_SYNTAX_CHECK_FAIL));
116
117    Reaction::RawMsg(
118        format!(
119            "PART {}{}{}",
120            chan,
121            if comment.is_some() { " :" } else { "" },
122            comment.unwrap_or_default()
123        ).into(),
124    ).into()
125}
126
127fn quit(_: &State, _: &MsgMetadata, arg: &Yaml) -> Reaction {
128    let comment = arg.as_hash()
129        .expect(FW_SYNTAX_CHECK_FAIL)
130        .get(&YAML_STR_MSG)
131        .map(|y| {
132            util::yaml::scalar_to_str(y, |s| Cow::Owned(s.to_owned())).expect(FW_SYNTAX_CHECK_FAIL)
133        });
134
135    Reaction::Quit(comment)
136}
137
138fn ping(_: &State, _: &MsgMetadata, _: &Yaml) -> BotCmdResult {
139    Reaction::Reply("pong".into()).into()
140}
141
142fn bot_fw_info(state: &State, _: &MsgMetadata, _: &Yaml) -> BotCmdResult {
143    Reaction::Reply(
144        format!(
145            "This bot was built with `{name}.rs`, version {ver}; see <{url}>.",
146            name = state.framework_crate_name(),
147            ver = state.framework_version_str(),
148            url = state.framework_homepage_url_str(),
149        ).into(),
150    ).into()
151}
152
153fn help(state: &State, _: &MsgMetadata, arg: &Yaml) -> BotCmdResult {
154    let arg = arg.as_hash();
155
156    let cmd = arg.and_then(|m| m.get(&YAML_STR_CMD));
157    let list = arg.and_then(|m| m.get(&YAML_STR_LIST));
158
159    if [cmd, list].iter().filter(|x| x.is_some()).count() > 1 {
160        return Reaction::Msg("Please ask for help with one thing at a time.".into()).into();
161    }
162
163    if let Some(&Yaml::String(ref cmd_name)) = cmd {
164        let &BotCommand {
165            ref name,
166            ref provider,
167            ref auth_lvl,
168            ref usage_str,
169            ref help_msg,
170            ..
171        } = match state.command(cmd_name) {
172            Ok(Some(c)) => c,
173            Ok(None) => {
174                return Reaction::Msg(format!("Command {:?} not found.", cmd_name).into()).into()
175            }
176            Err(e) => return BotCmdResult::LibErr(e),
177        };
178
179        Reaction::Msgs(
180            vec![
181                format!("= Help for command {:?}:", name).into(),
182                format!("- [module {:?}, auth level {:?}]", provider.name, auth_lvl).into(),
183                format!("- Syntax: {} {}", name, usage_str).into(),
184                help_msg.clone(),
185            ].into(),
186        ).into()
187    } else if let Some(&Yaml::String(ref list_name)) = list {
188        let list_names = ["commands", "lists"];
189
190        if list_name == "commands" {
191            Reaction::Msg(format!("Available commands: {:?}", state.command_names()).into()).into()
192        } else if list_name == "lists" {
193            Reaction::Msg(format!("Available lists: {:?}", list_names).into()).into()
194        } else {
195            if list_names.contains(&list_name.as_ref()) {
196                error!("Help list {:?} declared but not defined.", list_name);
197            }
198
199            Reaction::Msg(
200                format!(
201                    "List {:?} not found. Available lists: {:?}",
202                    list_name, list_names
203                ).into(),
204            ).into()
205        }
206    } else {
207        Reaction::Msgs(
208            vec![
209                "For help with a command named 'foo', try `help cmd: foo`.".into(),
210                "To see a list of all available commands, try `help list: commands`.".into(),
211                format!(
212                    "For this bot software's documentation, including an introduction to the \
213                     command syntax, see <{homepage}>",
214                    homepage = state.framework_homepage_url_str(),
215                ).into(),
216            ].into(),
217        ).into()
218    }
219}
220
221fn empty_msg_trigger(_: &State, _: &MsgMetadata, _: Captures) -> Reaction {
222    Reaction::Msg("Yes?".into())
223}