Skip to content
This repository has been archived by the owner on Aug 1, 2021. It is now read-only.

Commit

Permalink
Add plugin reloading, improve CLI interface some more
Browse files Browse the repository at this point in the history
  • Loading branch information
b1naryth1ef committed Oct 7, 2016
1 parent ffe5a6f commit 631f0e3
Show file tree
Hide file tree
Showing 8 changed files with 79 additions and 37 deletions.
7 changes: 1 addition & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,11 @@ class SimplePlugin(Plugin):
@Plugin.command('echo', '<content:str...>')
def on_echo_command(self, event, content):
event.msg.reply(content)

if __name__ == '__main__':
Bot.from_cli(
SimplePlugin
).run_forever()
```

Using the default bot configuration, we can now run this script like so:

`./simple.py --token="MY_DISCORD_TOKEN"`
`python -m disco.cli --token="MY_DISCORD_TOKEN" --bot --plugin simpleplugin`

And commands can be triggered by mentioning the bot (configued by the BotConfig.command\_require\_mention flag):

Expand Down
30 changes: 29 additions & 1 deletion disco/bot/bot.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import re
import importlib
import inspect

from six.moves import reload_module

from disco.bot.plugin import Plugin
from disco.bot.command import CommandEvent


Expand Down Expand Up @@ -261,12 +266,35 @@ def rmv_plugin(self, cls):
raise Exception('Cannot remove non-existant plugin: {}'.format(cls.__name__))

self.plugins[cls.__name__].unload()
self.plugins[cls.__name__].destroy()
del self.plugins[cls.__name__]
self.compute_command_matches_re()

def reload_plugin(self, cls):
"""
Reloads a plugin.
"""
config = self.plugins[cls.__name__].config

self.rmv_plugin(cls)
module = reload_module(inspect.getmodule(cls))
self.add_plugin(getattr(module, cls.__name__), config)

def run_forever(self):
"""
Runs this bots core loop forever
"""
self.client.run_forever()

def add_plugin_module(self, path, config=None):
"""
Adds and loads a plugin, based on its module path.
"""

mod = importlib.import_module(path)

for entry in map(lambda i: getattr(mod, i), dir(mod)):
if inspect.isclass(entry) and issubclass(entry, Plugin):
self.add_plugin(entry, config)
break
else:
raise Exception('Could not find any plugins to load within module {}'.format(path))
26 changes: 10 additions & 16 deletions disco/bot/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ def __init__(self, bot, config):
self.state = bot.client.state
self.config = config

def bind_all(self):
self.listeners = []
self.commands = {}
self.schedules = {}
Expand Down Expand Up @@ -226,28 +227,21 @@ def repeat():

self.schedules[func.__name__] = gevent.spawn(repeat)

def destroy(self):
"""
Destroys the plugin, removing all listeners and schedules. Called after
unload.
"""
for listener in self.listeners:
listener.remove()

for schedule in self.schedules.values():
schedule.kill()

self.listeners = []
self.schedules = {}

def load(self):
"""
Called when the plugin is loaded
"""
pass
self.bind_all()

def unload(self):
"""
Called when the plugin is unloaded
"""
pass
for listener in self.listeners:
listener.remove()

for schedule in self.schedules.values():
schedule.kill()

def reload(self):
self.bot.reload_plugin(self.__class__)
20 changes: 17 additions & 3 deletions disco/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@
parser.add_argument('--manhole', action='store_true', help='Enable the manhole', default=False)
parser.add_argument('--manhole-bind', help='host:port for the manhole to bind too', default='localhost:8484')
parser.add_argument('--encoder', help='encoder for gateway data', default='json')
parser.add_argument('--bot', help='run a disco bot on this client', action='store_true', default=False)
parser.add_argument('--plugin', help='load plugins into the bot', nargs='*', default=[])

logging.basicConfig(level=logging.INFO)


def disco_main():
def disco_main(run=False):
"""
Creates an argument parser and parses a standard set of command line arguments,
creating a new :class:`Client`.
Expand All @@ -35,6 +37,7 @@ def disco_main():
args = parser.parse_args()

from disco.client import Client, ClientConfig
from disco.bot import Bot
from disco.gateway.encoding import ENCODERS
from disco.util.token import is_valid_token

Expand All @@ -50,7 +53,18 @@ def disco_main():
cfg.manhole_bind = args.manhole_bind
cfg.encoding_cls = ENCODERS[args.encoder]

return Client(cfg)
client = Client(cfg)

if args.bot:
bot = Bot(client)

for plugin in args.plugin:
bot.add_plugin_module(plugin)

if run:
client.run_forever()

return client

if __name__ == '__main__':
disco_main().run_forever()
disco_main(True)
14 changes: 13 additions & 1 deletion disco/gateway/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,19 @@
from disco.types.base import Model, Field, snowflake, listof, text


# TODO: clean this... use BaseType, etc
class GatewayEvent(Model):
"""
The GatewayEvent class wraps various functionality for events passed to us
over the gateway websocket, and serves as a simple proxy to inner values for
some wrapped event-types (e.g. MessageCreate only contains a message, so we
proxy all attributes to the inner message object).
"""

@staticmethod
def from_dispatch(client, data):
"""
Create a new GatewayEvent instance based on event data.
"""
cls = globals().get(inflection.camelize(data['t'].lower()))
if not cls:
raise Exception('Could not find cls for {}'.format(data['t']))
Expand All @@ -17,6 +26,9 @@ def from_dispatch(client, data):

@classmethod
def create(cls, obj, client):
"""
Create this GatewayEvent class from data and the client.
"""
# If this event is wrapping a model, pull its fields
if hasattr(cls, '_wraps_model'):
alias, model = cls._wraps_model
Expand Down
Empty file added examples/__init__.py
Empty file.
17 changes: 8 additions & 9 deletions examples/basic_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@
import json

from disco import VERSION
from disco.cli import disco_main
from disco.bot import Bot, Plugin
from disco.types.permissions import Permissions
from disco.bot import Plugin


class BasicPlugin(Plugin):
@Plugin.command('reload')
def on_reload(self, event):
self.reload()
event.msg.reply('Reloaded!')

@Plugin.listen('MessageCreate')
def on_message_create(self, msg):
self.log.info('Message created: {}: {}'.format(msg.author, msg.content))
Expand Down Expand Up @@ -82,16 +85,12 @@ def on_airhorn(self, event):

@Plugin.command('lol')
def on_lol(self, event):
event.msg.reply("{}".format(event.channel.can(event.msg.author, Permissions.MANAGE_EMOJIS)))
event.msg.reply(':^)')
# event.msg.reply("{}".format(event.channel.can(event.msg.author, Permissions.MANAGE_EMOJIS)))

@Plugin.command('perms')
def on_perms(self, event):
perms = event.channel.get_permissions(event.msg.author)
event.msg.reply('```json\n{}\n```'.format(
json.dumps(perms.to_dict(), sort_keys=True, indent=2, separators=(',', ': '))
))

if __name__ == '__main__':
bot = Bot(disco_main())
bot.add_plugin(BasicPlugin)
bot.run_forever()
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
gevent==1.1.2
holster==1.0.3
holster==1.0.4
inflection==0.3.1
requests==2.11.1
six==1.10.0
Expand Down

0 comments on commit 631f0e3

Please sign in to comment.