Skip to content

Commit 952b565

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 9c18c0b commit 952b565

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)
@@ -188,6 +190,48 @@ def is_private_message_from_another_user(message_dict, current_user_id):
188190
return current_user_id != message_dict['sender_id']
189191
return False
190192

193+
def setup_default_commands(bot_details, message_handler):
194+
def def_about():
195+
if bot_details['description'] == "":
196+
return "**{}**".format(bot_details['name'])
197+
return "**{}**: {}".format(bot_details['name'], bot_details['description'])
198+
199+
def def_help():
200+
return ("\n".join("**{}** - {}".format(k, v[1])
201+
for k, v in defaults.items() if k) + "\n" +
202+
"\n".join("**{}** - {}".format(k, v)
203+
for k, v in bot_details['commands'].items() if k))
204+
205+
def def_commands():
206+
return "**Commands**: {} {}".format(
207+
" ".join(k for k in defaults if k),
208+
" ".join(k for k in bot_details['commands'] if k))
209+
defaults = OrderedDict([ # Variable definition required for callbacks above
210+
('', (lambda: "Oops. Your message was empty.", "[BLANK MESSAGE NOT SHOWN]")),
211+
('about', (def_about, "The type and use of this bot")),
212+
('usage', ((lambda: message_handler.usage(), "Bot-provided usage text"))),
213+
('help', (lambda: "{}\n{}\n{}".format(def_about(), message_handler.usage(), def_help()),
214+
"This help text")),
215+
('commands', (def_commands, "A short list of supported commands"))
216+
])
217+
return defaults
218+
219+
def sync_botdetails_defaultcommands(bot_details, default_commands):
220+
# Update default_commands from any changes in bot_details
221+
if not bot_details['defaults']: # Bot class will handle all commands
222+
default_commands = {}
223+
else:
224+
if len(bot_details['commands']) == 0: # No commands specified, so don't use this feature
225+
del default_commands['commands']
226+
del default_commands['help']
227+
else:
228+
for command in bot_details['commands']: # Bot commands override defaults
229+
if command in default_commands:
230+
del default_commands[command]
231+
# Sync default_commands changes with bot_details
232+
if len(default_commands) == 0:
233+
bot_details['defaults'] = False
234+
191235
def run_message_handler_for_bot(lib_module, quiet, config_file, bot_name):
192236
# type: (Any, bool, str) -> Any
193237
#
@@ -207,7 +251,23 @@ def run_message_handler_for_bot(lib_module, quiet, config_file, bot_name):
207251

208252
state_handler = StateHandler()
209253

254+
# Set default bot_details, then override from class, if provided
255+
bot_details = {
256+
'name': bot_name.capitalize(),
257+
'description': "",
258+
'commands': {},
259+
'defaults': True,
260+
}
261+
bot_details.update(getattr(lib_module.handler_class, 'META', {}))
262+
263+
# Initialise default commands, then override & sync with bot_details
264+
default_commands = setup_default_commands(bot_details, message_handler)
265+
sync_botdetails_defaultcommands(bot_details, default_commands)
266+
210267
if not quiet:
268+
print("Running {} Bot:".format(bot_details['name']))
269+
if bot_details['description'] != "":
270+
print("\n{}".format(bot_details['description']))
211271
print(message_handler.usage())
212272

213273
def handle_message(message):
@@ -228,6 +288,18 @@ def handle_message(message):
228288
return
229289

230290
if is_private_message or is_mentioned:
291+
# Handle any default_commands first
292+
if len(default_commands) > 0:
293+
if '' in default_commands and len(message['content']) == 0:
294+
restricted_client.send_reply(message, default_commands[''][0]())
295+
return
296+
for command in default_commands:
297+
if command == '':
298+
continue
299+
if message['content'].startswith(command):
300+
restricted_client.send_reply(message, default_commands[command][0]())
301+
return
302+
# ...then pass anything else to bot to deal with
231303
message_handler.handle_message(
232304
message=message,
233305
bot_handler=restricted_client,

0 commit comments

Comments
 (0)