-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathpluginmanager.py
163 lines (142 loc) · 6.34 KB
/
pluginmanager.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
import fnmatch
import importlib
import os
import traceback
from plugins.plugin import PlushiePlugin
def plugin_error(command):
"""Prints an error message and traceback given the plugin method ``command``"""
print("[{:s}] Error:\n{:s}".format(command.__self__.name, traceback.format_exc()))
class PluginManager:
"""Class specifically made for managing Plushie plugins."""
class CommandContext:
"""Convenience class of sorts that is passed into a plugins method calls."""
def __init__(self, parent, message_sender):
self.chat = message_sender
self.parent = parent
self.config = parent.config["plugins"]
def msg(self, message, target=None):
"""Sends a message to NEaB chat."""
prefix = ""
if target:
prefix = "/msg " + target + " "
self.chat.put(('chat', prefix + message))
def __init__(self, config, message_sender):
#: Stores the configuration file for Plushie.
self.config = config
#: Holds a dict of registered plugins in "Plugin Name":plugin_instance format.
self.plugins = {}
#: Holds a dict of commands in "!command":command_function format.
self.commands = {}
#: Holds a dict of transforms for commands.
self.transforms = {}
#: Holds a list of methods that are called for every message sent.
self.msghandlers = []
#: Holds a list of methods that are called for every tick (~4 seconds).
self.tick = []
#: The instance of the context class that gets passed into plugin methods.
self.ctx = self.CommandContext(self, message_sender)
def register_plugin(self, plugin):
"""Officially registers a plugin instance with the plugin manager.
Args:
plugin (PlushiePlugin): Plugin instance to register.
"""
self.plugins[plugin.name] = plugin
for cmd in plugin.getCommands():
names = getattr(cmd, "plushieCommand")
for n in names:
self.commands[n] = cmd
# Add transforms
self.transforms.update(getattr(cmd, "commandTransforms"))
for tick in plugin.getTick():
self.tick.append(tick)
for handle in plugin.getMessageHandlers():
self.msghandlers.append(handle)
def unregister_plugin(self, plugin):
"""Officially unregisters a plugin instance from the plugin manager."""
for cmd in plugin.getCommands():
for command_name in getattr(cmd, "plushieCommand"):
del self.commands[command_name]
for k, v in getattr(cmd, "commandTransforms"):
del self.transforms[k]
for tick in plugin.getTick():
self.tick.remove(tick)
for handle in plugin.getMessageHandlers():
self.msghandlers.remove(handle)
del self.plugins[plugin.name]
def register_from_list(self, plugin_list):
"""Registers a plugin given a list of plugins."""
for plug in plugin_list:
self.register_from_str(plug)
def register_from_str(self, plugin_name):
"""Registers a plugin given a string."""
path, class_name = plugin_name.rsplit(".", 1)
mod = importlib.import_module("{:s}.{:s}".format("plugins", path))
cls = getattr(mod, class_name)
self.register_plugin(cls())
def load_plugins(self, blacklist=None):
"""Loads plugins from the :mod:`plugins` submodule automatically.
Loads all plugins from the :mod:`plugins` submodule automatically while ignoring classes included in the
blacklist arg.
KWargs:
blacklist (str[]): List of plugin classes to ignore.
"""
if not blacklist:
blacklist = []
# Search the plugins directory for names matching *?plugin.py (anything in front with at least one character)
plugins = fnmatch.filter(os.listdir("./plugins"), "*?plugin.py")
for plugin in map(lambda n: n.split(".")[0], plugins):
if plugin in blacklist:
continue
# This is a bit hacky since I don't maintain a reference
importlib.import_module("plugins.{!s}".format(plugin))
# Now that all the relevant modules have been imported, grab the plugin classes
for plugin_class in PlushiePlugin.__subclasses__():
self.register_plugin(plugin_class())
def reload_plugin(self, plugin_name):
"""Reloads an existing plugin using the name of the plugin class."""
try:
plugin = self.plugins[plugin_name]
except:
return False
class_name = plugin.__class__.__name__
mod = importlib.import_module(plugin.__class__.__module__)
self.unregister_plugin(plugin)
new_mod = importlib.reload(mod)
cls = getattr(new_mod, class_name)
self.register_plugin(cls())
return True
def signal_command(self, message):
if message.isCommand():
if message.player.lower() in map(lambda x: x.lower(), self.config.get("bans", [])):
self.ctx.msg("I'm sorry {:s}, but you've been banned from using Plushie.".format(message.player))
return
args = message.msgArg()
cmd_name = args[0][1:].lower()
# Apply transforms
if cmd_name in self.transforms:
parts = self.transforms[cmd_name].split(" ")
cmd_name = parts[0]
# This is some nasty joo joo; the Message gets hot-modified here
parts.extend(message.msgArg()[1:]) # For now only 1->many transforms are allowed
message.msg = "!" + " ".join(parts)
if cmd_name in self.commands:
cmd = self.commands[cmd_name]
try:
cmd(self.ctx, message)
except:
plugin_error(cmd)
def signal_tick(self):
for cmd in self.tick:
try:
cmd(self.ctx)
except:
plugin_error(cmd)
def signal_message(self, message):
# Handle messages before passing to command handling
for handler in self.msghandlers:
try:
handler(self.ctx, message)
except:
plugin_error(handler)
if message.isCommand():
self.signal_command(message)