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}