Skip to content

Commit 6de4f99

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 6de4f99

File tree

3 files changed

+86
-0
lines changed

3 files changed

+86
-0
lines changed

zulip_bots/zulip_bots/bots/wikipedia/wikipedia.py

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

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

zulip_bots/zulip_bots/bots/xkcd/xkcd.py

Lines changed: 12 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,16 @@ class XkcdHandler(object):
1416
commands.
1517
'''
1618

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

zulip_bots/zulip_bots/lib.py

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

179221
state_handler = StateHandler()
180222

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

184241
def extract_query_without_mention(message, client):
@@ -220,6 +277,18 @@ def handle_message(message):
220277
return
221278

222279
if is_private_message or is_mentioned:
280+
# Handle any default_commands first
281+
if len(default_commands) > 0:
282+
if '' in default_commands and len(message['content']) == 0:
283+
restricted_client.send_reply(message, default_commands[''][0]())
284+
return
285+
for command in default_commands:
286+
if command == '':
287+
continue
288+
if message['content'].startswith(command):
289+
restricted_client.send_reply(message, default_commands[command][0]())
290+
return
291+
# ...then pass anything else to bot to deal with
223292
message_handler.handle_message(
224293
message=message,
225294
bot_handler=restricted_client,

0 commit comments

Comments
 (0)