Browse Source

Better Prefixes (#160)

* Easily use multiple prefixes... Now onto programmatic fetching and conquering the world!

* Updated a test and added some documentation.
Now someone just needs to find a way to transition into multiple prefixes...

* Allow bots to be run without access to the console.
This is going to allow for custom prefix-getters :D

* Fix flake8's innate hate of monkey patching.

* Documentation!
No idea how to make tests for this...

* Python 2 compatibility is basically impossible.
Implicit Relative Imports ruin the day here (`import logging`) and so why not ruin Python 2's day with type hinting?

* And it's done!

Python 2 **can** keep using disco, because `disco.util.runner`
shouldn't be automatically imported.

Changelog:
+ Added two keys to the config.
    `commands_prefix_getter`
    `commands_prefix`
+ Added documentation.
+ Added a new utility file, `disco.util.runner`

* nekoka.tt code review (github.com/nekocatt)

Some of the stuff edited (`disco.util.config.Config`) are not within the scope of this PR, but `disco.util.runner.create_bot` makes it easier to mess up.

(also, if lines 414 to 417 are premature optimization, that's all me)

* Undo the change to `disco.util.config.Config`.

There is probably a way to do this, but that's topic of a different PR.

* remove unused import

* Python 2 Compatibility

* Move `disco.util.runner.py` to another PR

* b1nzy code review

* fix tests

* stop being dumb

* Remove unintuitive behaviour.

Previously, if you had `{@mention} {prefix}{command}`, `require_mention` was set to `True`, and `prefixes` were set to anything, then the command framework would look for a command named `{prefix}{command}`. However, this specific grouping of settings would not be chosen unless the bot creator wanted the command framework to look for `{command}`.

tl;dr settings weren't being respected

Co-authored-by: Andrei Zbikowski <[email protected]>
staging/v1.0.0
EXPLOSION 5 years ago
committed by Andrei Zbikowski
parent
commit
4bed5b8494
  1. 46
      disco/bot/bot.py
  2. 10
      disco/util/config.py
  3. 8
      docs/bot_tutorial/first_steps.md
  4. 13
      tests/bot/bot.py

46
disco/bot/bot.py

@ -45,7 +45,13 @@ class BotConfig(Config):
command parsing. command parsing.
commands_prefix : str commands_prefix : str
A string prefix that is required for a message to be considered for A string prefix that is required for a message to be considered for
command parsing. command parsing. **DEPRECATED**
command_prefixes : list[string]
A list of string prefixes that are required for a message to be considered
for command parsing.
commands_prefix_getter : Optional[function]
A function which takes in a message object and returns an array of strings
(prefixes).
commands_allow_edit : bool commands_allow_edit : bool
If true, the bot will re-parse an edited message if it was the last sent If true, the bot will re-parse an edited message if it was the last sent
message in a channel, and did not previously trigger a command. This is message in a channel, and did not previously trigger a command. This is
@ -74,6 +80,8 @@ class BotConfig(Config):
http_port : int http_port : int
The port for the HTTP Flask server (if enabled). The port for the HTTP Flask server (if enabled).
""" """
deprecated = {'commands_prefix': 'command_prefixes'}
levels = {} levels = {}
plugins = [] plugins = []
plugin_config = {} plugin_config = {}
@ -87,7 +95,9 @@ class BotConfig(Config):
'role': True, 'role': True,
'user': True, 'user': True,
} }
commands_prefix = '' commands_prefix = '' # now deprecated
command_prefixes = []
commands_prefix_getter = None
commands_allow_edit = True commands_allow_edit = True
commands_level_getter = None commands_level_getter = None
commands_group_abbrev = True commands_group_abbrev = True
@ -239,7 +249,7 @@ class Bot(LoggingClass):
Computes all possible abbreviations for a command grouping. Computes all possible abbreviations for a command grouping.
""" """
# For the first pass, we just want to compute each groups possible # For the first pass, we just want to compute each groups possible
# abbreviations that don't conflict with eachother. # abbreviations that don't conflict with each other.
possible = {} possible = {}
for group in groups: for group in groups:
for index in range(1, len(group)): for index in range(1, len(group)):
@ -275,13 +285,20 @@ class Bot(LoggingClass):
else: else:
self.command_matches_re = None self.command_matches_re = None
def get_commands_for_message(self, require_mention, mention_rules, prefix, msg): def get_commands_for_message(self, require_mention, mention_rules, prefixes, msg):
""" """
Generator of all commands that a given message object triggers, based on Generator of all commands that a given message object triggers, based on
the bots plugins and configuration. the bots plugins and configuration.
Parameters Parameters
--------- ---------
require_mention : bool
Checks if the message starts with a mention (and then ignores the prefix(es))
mention_rules : dict(str, bool)
Whether `user`, `everyone`, and `role` mentions are allowed. Defaults to:
`{'user': True, 'everyone': False, 'role': False}`
prefixes : list[string]
A list of prefixes to check the message starts with.
msg : :class:`disco.types.message.Message` msg : :class:`disco.types.message.Message`
The message object to parse and find matching commands for. The message object to parse and find matching commands for.
@ -290,6 +307,8 @@ class Bot(LoggingClass):
tuple(:class:`disco.bot.command.Command`, `re.MatchObject`) tuple(:class:`disco.bot.command.Command`, `re.MatchObject`)
All commands the message triggers. All commands the message triggers.
""" """
# somebody better figure out what this yields...
content = msg.content content = msg.content
if require_mention: if require_mention:
@ -326,10 +345,17 @@ class Bot(LoggingClass):
content = content.lstrip() content = content.lstrip()
if prefix and not content.startswith(prefix): # Scan through the prefixes to find the first one that matches.
return [] # This may lead to unexpected results, but said unexpectedness
# should be easy to avoid. An example of the unexpected results
# that may occur would be if one prefix was `!` and one was `!a`.
for prefix in prefixes:
if prefix and content.startswith(prefix):
content = content[len(prefix):]
break
else: else:
content = content[len(prefix):] if not require_mention: # don't want to prematurely return
return []
if not self.command_matches_re or not self.command_matches_re.match(content): if not self.command_matches_re or not self.command_matches_re.match(content):
return [] return []
@ -339,6 +365,7 @@ class Bot(LoggingClass):
match = command.compiled_regex.match(content) match = command.compiled_regex.match(content)
if match: if match:
options.append((command, match)) options.append((command, match))
return sorted(options, key=lambda obj: obj[0].group is None) return sorted(options, key=lambda obj: obj[0].group is None)
def get_level(self, actor): def get_level(self, actor):
@ -382,10 +409,13 @@ class Bot(LoggingClass):
bool bool
Whether any commands where successfully triggered by the message. Whether any commands where successfully triggered by the message.
""" """
custom_message_prefixes = (self.config.commands_prefix_getter(msg)
if self.config.commands_prefix_getter else [])
commands = list(self.get_commands_for_message( commands = list(self.get_commands_for_message(
self.config.commands_require_mention, self.config.commands_require_mention,
self.config.commands_mention_rules, self.config.commands_mention_rules,
self.config.commands_prefix, custom_message_prefixes or self.config.command_prefixes,
msg, msg,
)) ))

10
disco/util/config.py

@ -10,6 +10,16 @@ class Config(object):
k: getattr(self, k) for k in dir(self.__class__) k: getattr(self, k) for k in dir(self.__class__)
}) })
# issue `DeprecationWarning`s
if hasattr(self.__class__, 'deprecated') and obj:
for deprecated_key, replacement in self.__class__.deprecated.items():
if deprecated_key in obj.keys():
warning_text = '"{0}" is deprecated.'.format(deprecated_key)
warning_text += ('\nReplace "{0}" with "{1}".'.format(deprecated_key, replacement)
if replacement else '')
raise DeprecationWarning(warning_text)
if obj: if obj:
self.__dict__.update(obj) self.__dict__.update(obj)

8
docs/bot_tutorial/first_steps.md

@ -34,6 +34,14 @@ Now let's setup the configuration file. To start off with we'll paste the follow
} }
``` ```
{% hint style='tip' %}
If you want to use a prefix (or even multiple), you add something this into the `"bot"` dictionary:
```json
"requires_mentions": false,
"command_prefixes": ["!", "?"]
```
{% endhint %}
Now we're ready to write our plugin. Plugins are used to isolate the functionality of your bot into components. Plugins can be dynamically loaded, unloaded and reloaded at runtime. Lets start off by writing a plugin with a "ping" command; Now we're ready to write our plugin. Plugins are used to isolate the functionality of your bot into components. Plugins can be dynamically loaded, unloaded and reloaded at runtime. Lets start off by writing a plugin with a "ping" command;

13
tests/bot/bot.py

@ -85,10 +85,19 @@ class TestBot(TestCase):
msg = Object() msg = Object()
msg.content = '!test a' msg.content = '!test a'
commands = list(self.bot.get_commands_for_message(False, None, '!', msg)) commands = list(self.bot.get_commands_for_message(False, None, ['!'], msg))
self.assertEqual(commands[0][0], self.bot._commands[1]) self.assertEqual(commands[0][0], self.bot._commands[1])
self.assertEqual(commands[1][0], self.bot._commands[0]) self.assertEqual(commands[1][0], self.bot._commands[0])
msg.content = '!test' msg.content = '!test'
commands = list(self.bot.get_commands_for_message(False, None, '!', msg)) commands = list(self.bot.get_commands_for_message(False, None, ['!'], msg))
self.assertEqual(commands[0][0], self.bot._commands[0])
msg.content = '?test a'
commands = list(self.bot.get_commands_for_message(False, None, ['!', '?', ';'], msg))
self.assertEqual(commands[0][0], self.bot._commands[1])
self.assertEqual(commands[1][0], self.bot._commands[0])
msg.content = '?test'
commands = list(self.bot.get_commands_for_message(False, None, ['!', '?', ';'], msg))
self.assertEqual(commands[0][0], self.bot._commands[0]) self.assertEqual(commands[0][0], self.bot._commands[0])

Loading…
Cancel
Save