Skip to content

Commit db9934b

Browse files
committed
Bots: Add metadata scheme for bots.
* Name & (short) description * Whether to use any default commands (default to enabled): (about, '' (empty), usage [remove?], help/commands) * (Optionally) provide a list of user commands, which help/commands uses
1 parent bcc1489 commit db9934b

File tree

3 files changed

+91
-0
lines changed

3 files changed

+91
-0
lines changed

zulip_bots/zulip_bots/bots/wikipedia/wikipedia.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ class WikipediaHandler(object):
1818
kind of external issue tracker as well.
1919
'''
2020

21+
META = {
22+
'name': 'Wikipedia',
23+
'description': 'Searches Wikipedia for a term and returns the top article.',
24+
'defaults': False, # Let bot handle all messages
25+
}
26+
2127
def usage(self):
2228
return '''
2329
This plugin will allow users to directly search

zulip_bots/zulip_bots/bots/xkcd/xkcd.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import logging
44
import requests
55

6+
from collections import OrderedDict
7+
68
XKCD_TEMPLATE_URL = 'https://xkcd.com/%s/info.0.json'
79
LATEST_XKCD_URL = 'https://xkcd.com/info.0.json'
810

@@ -14,6 +16,17 @@ class XkcdHandler(object):
1416
commands.
1517
'''
1618

19+
META = {
20+
'name': 'XKCD',
21+
'description': 'Fetches comic strips from https://xkcd.com.',
22+
'defaults': True,
23+
'commands': OrderedDict([
24+
('latest', "Show the latest comic strip"),
25+
('random', "Show a random comic strip"),
26+
('<comic id>', "Show a comic strip with a specific 'comic id'"),
27+
]) # NOTE: help not listed here, so default command used
28+
}
29+
1730
def usage(self):
1831
return '''
1932
This plugin allows users to fetch a comic strip provided by

zulip_bots/zulip_bots/lib.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
from zulip import Client
2020

21+
from collections import OrderedDict
22+
2123
def exit_gracefully(signum, frame):
2224
# type: (int, Optional[Any]) -> None
2325
sys.exit(0)
@@ -159,6 +161,48 @@ def state(self, default):
159161
yield new_state
160162
self.set_state(new_state)
161163

164+
def setup_default_commands(bot_details, message_handler):
165+
def def_about():
166+
if bot_details['description'] == "":
167+
return "**{}**".format(bot_details['name'])
168+
return "**{}**: {}".format(bot_details['name'], bot_details['description'])
169+
170+
def def_help():
171+
return ("\n".join("**{}** - {}".format(k, v[1])
172+
for k, v in defaults.items() if k) + "\n" +
173+
"\n".join("**{}** - {}".format(k, v)
174+
for k, v in bot_details['commands'].items() if k))
175+
176+
def def_commands():
177+
return "**Commands**: {} {}".format(
178+
" ".join(k for k in defaults if k),
179+
" ".join(k for k in bot_details['commands'] if k))
180+
defaults = OrderedDict([ # Variable definition required for callbacks above
181+
('', (lambda: "Oops. Your message was empty.", "[BLANK MESSAGE NOT SHOWN]")),
182+
('about', (def_about, "The type and use of this bot")),
183+
('usage', ((lambda: message_handler.usage(), "Bot-provided usage text"))),
184+
('help', (lambda: "{}\n{}\n{}".format(def_about(), message_handler.usage(), def_help()),
185+
"This help text")),
186+
('commands', (def_commands, "A short list of supported commands"))
187+
])
188+
return defaults
189+
190+
def sync_botdetails_defaultcommands(bot_details, default_commands):
191+
# Update default_commands from any changes in bot_details
192+
if not bot_details['defaults']: # Bot class will handle all commands
193+
default_commands = {}
194+
else:
195+
if len(bot_details['commands']) == 0: # No commands specified, so don't use this feature
196+
del default_commands['commands']
197+
del default_commands['help']
198+
else:
199+
for command in bot_details['commands']: # Bot commands override defaults
200+
if command in default_commands:
201+
del default_commands[command]
202+
# Sync default_commands changes with bot_details
203+
if len(default_commands) == 0:
204+
bot_details['defaults'] = False
205+
162206
def run_message_handler_for_bot(lib_module, quiet, config_file, bot_name):
163207
# type: (Any, bool, str) -> Any
164208
#
@@ -178,7 +222,23 @@ def run_message_handler_for_bot(lib_module, quiet, config_file, bot_name):
178222

179223
state_handler = StateHandler()
180224

225+
# Set default bot_details, then override from class, if provided
226+
bot_details = {
227+
'name': bot_name.capitalize(),
228+
'description': "",
229+
'commands': {},
230+
'defaults': True,
231+
}
232+
bot_details.update(getattr(lib_module.handler_class, 'META', {}))
233+
234+
# Initialise default commands, then override & sync with bot_details
235+
default_commands = setup_default_commands(bot_details, message_handler)
236+
sync_botdetails_defaultcommands(bot_details, default_commands)
237+
181238
if not quiet:
239+
print("Running {} Bot:".format(bot_details['name']))
240+
if bot_details['description'] != "":
241+
print("\n{}".format(bot_details['description']))
182242
print(message_handler.usage())
183243

184244
def extract_query_without_mention(message, client):
@@ -220,6 +280,18 @@ def handle_message(message):
220280
return
221281

222282
if is_private_message or is_mentioned:
283+
# Handle any default_commands first
284+
if len(default_commands) > 0:
285+
if '' in default_commands and len(message['content']) == 0:
286+
restricted_client.send_reply(message, default_commands[''][0]())
287+
return
288+
for command in default_commands:
289+
if command == '':
290+
continue
291+
if message['content'].startswith(command):
292+
restricted_client.send_reply(message, default_commands[command][0]())
293+
return
294+
# ...then pass anything else to bot to deal with
223295
message_handler.handle_message(
224296
message=message,
225297
bot_handler=restricted_client,

0 commit comments

Comments
 (0)