2014-10-02 14:48:59 -07:00
|
|
|
"""
|
|
|
|
.. module:: ModuleBase
|
2015-11-01 18:03:11 -08:00
|
|
|
:synopsis: Base class that modules will extend
|
2014-10-02 14:48:59 -07:00
|
|
|
|
|
|
|
.. moduleauthor:: Dave Pedu <dave@davepedu.com>
|
|
|
|
|
|
|
|
"""
|
2013-12-28 09:58:20 -08:00
|
|
|
|
|
|
|
import logging
|
2015-06-18 20:43:17 -07:00
|
|
|
from .pyircbot import PyIRCBot
|
2017-07-02 14:48:34 -07:00
|
|
|
from inspect import getargspec
|
|
|
|
import os
|
2013-12-28 09:58:20 -08:00
|
|
|
|
2017-01-01 14:01:23 -08:00
|
|
|
|
2013-12-28 09:58:20 -08:00
|
|
|
class ModuleBase:
|
2015-11-01 18:03:11 -08:00
|
|
|
"""All modules will extend this class
|
2017-01-01 14:01:23 -08:00
|
|
|
|
2015-11-01 18:03:11 -08:00
|
|
|
:param bot: A reference to the main bot passed when this module is created
|
|
|
|
:type bot: PyIRCBot
|
|
|
|
:param moduleName: The name assigned to this module
|
|
|
|
:type moduleName: str
|
|
|
|
"""
|
2017-01-01 14:01:23 -08:00
|
|
|
|
2015-11-01 18:03:11 -08:00
|
|
|
def __init__(self, bot, moduleName):
|
2017-01-01 14:59:01 -08:00
|
|
|
self.moduleName = moduleName
|
2015-11-01 18:03:11 -08:00
|
|
|
"""Assigned name of this module"""
|
2017-01-01 14:01:23 -08:00
|
|
|
|
2015-11-01 18:03:11 -08:00
|
|
|
self.bot = bot
|
|
|
|
"""Reference to the master PyIRCBot object"""
|
2017-01-01 14:01:23 -08:00
|
|
|
|
2017-01-01 14:59:01 -08:00
|
|
|
self.hooks = []
|
2015-11-01 18:03:11 -08:00
|
|
|
"""Hooks (aka listeners) this module has"""
|
2017-01-01 14:01:23 -08:00
|
|
|
|
2017-07-02 14:48:34 -07:00
|
|
|
self.irchooks = []
|
|
|
|
"""IRC Hooks this module has"""
|
|
|
|
|
2017-01-01 14:59:01 -08:00
|
|
|
self.services = []
|
2015-11-01 18:03:11 -08:00
|
|
|
"""If this module provides services usable by another module, they're listed
|
|
|
|
here"""
|
2017-01-01 14:01:23 -08:00
|
|
|
|
2017-01-01 14:59:01 -08:00
|
|
|
self.config = {}
|
2015-11-01 18:03:11 -08:00
|
|
|
"""Configuration dictionary. Autoloaded from `%(datadir)s/%(modulename)s.json`"""
|
2017-01-01 14:01:23 -08:00
|
|
|
|
2015-11-01 18:03:11 -08:00
|
|
|
self.log = logging.getLogger("Module.%s" % self.moduleName)
|
|
|
|
"""Logger object for this module"""
|
2017-01-01 14:01:23 -08:00
|
|
|
|
2015-11-01 18:03:11 -08:00
|
|
|
# Autoload config if available
|
|
|
|
self.loadConfig()
|
2017-01-01 14:01:23 -08:00
|
|
|
|
2017-05-14 12:44:14 -07:00
|
|
|
# Prepare any function hooking
|
|
|
|
self.init_hooks()
|
|
|
|
|
2015-11-01 18:03:11 -08:00
|
|
|
self.log.info("Loaded module %s" % self.moduleName)
|
2017-01-01 14:01:23 -08:00
|
|
|
|
2017-05-14 12:44:14 -07:00
|
|
|
def init_hooks(self):
|
|
|
|
"""
|
|
|
|
Scan the module for tagged methods and set up appropriate protocol hooks.
|
|
|
|
"""
|
|
|
|
for attr_name in dir(self):
|
|
|
|
attr = getattr(self, attr_name)
|
2017-07-02 14:48:34 -07:00
|
|
|
if not callable(attr):
|
|
|
|
continue
|
|
|
|
if hasattr(attr, ATTR_ACTION_HOOK):
|
2017-05-14 12:44:14 -07:00
|
|
|
for action in getattr(attr, ATTR_ACTION_HOOK):
|
|
|
|
self.hooks.append(ModuleHook(action, attr))
|
2017-07-02 14:48:34 -07:00
|
|
|
if hasattr(attr, ATTR_COMMAND_HOOK):
|
|
|
|
for action in getattr(attr, ATTR_COMMAND_HOOK):
|
|
|
|
self.irchooks.append(IRCHook(action, attr))
|
2017-05-14 12:44:14 -07:00
|
|
|
|
2015-11-01 18:03:11 -08:00
|
|
|
def loadConfig(self):
|
2017-04-30 23:27:40 -07:00
|
|
|
"""
|
|
|
|
Loads this module's config into self.config. The bot's main config is checked for a section matching the module
|
|
|
|
name, which will be preferred. If not found, an individual config file will be loaded from the data dir
|
|
|
|
"""
|
|
|
|
self.config = self.bot.botconfig.get("module_configs", {}).get(self.__class__.__name__, {})
|
|
|
|
if not self.config:
|
|
|
|
configPath = self.getConfigPath()
|
|
|
|
if configPath is not None:
|
|
|
|
self.config = PyIRCBot.load(configPath)
|
2017-01-01 14:01:23 -08:00
|
|
|
|
2015-11-01 18:03:11 -08:00
|
|
|
def ondisable(self):
|
|
|
|
"""Called when the module should be disabled. Your module should do any sort
|
|
|
|
of clean-up operations here like ending child threads or saving data files.
|
|
|
|
"""
|
|
|
|
pass
|
2017-01-01 14:01:23 -08:00
|
|
|
|
2015-11-01 18:03:11 -08:00
|
|
|
def getConfigPath(self):
|
|
|
|
"""Returns the absolute path of this module's json config file"""
|
|
|
|
return self.bot.getConfigPath(self.moduleName)
|
2017-01-01 14:01:23 -08:00
|
|
|
|
2015-11-01 18:03:11 -08:00
|
|
|
def getFilePath(self, f=None):
|
|
|
|
"""Returns the absolute path to a file in this Module's data dir
|
2017-01-01 14:01:23 -08:00
|
|
|
|
2015-11-01 18:03:11 -08:00
|
|
|
:param f: The file name included in the path
|
|
|
|
:type channel: str
|
|
|
|
:Warning: .. Warning:: this does no error checking if the file exists or is\
|
|
|
|
writable. The bot's data dir *should* always be writable"""
|
2017-07-02 14:48:34 -07:00
|
|
|
return os.path.join(self.bot.getDataPath(self.moduleName), (f if f else ''))
|
2015-06-20 00:30:12 -07:00
|
|
|
|
2017-01-01 14:59:01 -08:00
|
|
|
|
2013-12-28 09:58:20 -08:00
|
|
|
class ModuleHook:
|
2015-11-01 18:03:11 -08:00
|
|
|
def __init__(self, hook, method):
|
2017-01-01 14:59:01 -08:00
|
|
|
self.hook = hook
|
|
|
|
self.method = method
|
2017-05-14 12:44:14 -07:00
|
|
|
|
|
|
|
|
2017-07-02 14:48:34 -07:00
|
|
|
class IRCHook:
|
|
|
|
def __init__(self, hook, method):
|
|
|
|
self.hook = hook
|
|
|
|
self.method = method
|
|
|
|
|
|
|
|
def call(self, msg):
|
|
|
|
self.hook.call(self.method, msg)
|
|
|
|
|
|
|
|
|
2017-05-14 12:44:14 -07:00
|
|
|
ATTR_ACTION_HOOK = "__tag_hooks"
|
2017-07-02 14:48:34 -07:00
|
|
|
ATTR_COMMAND_HOOK = "__tag_commands"
|
2017-05-14 12:44:14 -07:00
|
|
|
|
|
|
|
|
|
|
|
class hook(object):
|
|
|
|
"""
|
2017-07-02 14:48:34 -07:00
|
|
|
Decorator for calling module methods in response to IRC actions. Example:
|
2017-05-14 12:44:14 -07:00
|
|
|
```
|
|
|
|
@hook("PRIVMSG")
|
|
|
|
def mymyethod(self, message):
|
|
|
|
print("IRC server sent PRIVMSG")
|
|
|
|
```
|
|
|
|
This stores a list of IRC actions each function is tagged for in method.__tag_hooks. This attribute is scanned
|
|
|
|
during module init and appropriate hooks are set up.
|
|
|
|
"""
|
|
|
|
def __init__(self, *args):
|
|
|
|
self.commands = args
|
|
|
|
|
|
|
|
def __call__(self, func):
|
|
|
|
"""
|
|
|
|
|
|
|
|
"""
|
|
|
|
if not hasattr(func, ATTR_ACTION_HOOK):
|
|
|
|
setattr(func, ATTR_ACTION_HOOK, list(self.commands))
|
|
|
|
else:
|
|
|
|
getattr(func, ATTR_ACTION_HOOK).extend(self.commands)
|
|
|
|
return func
|
2017-07-02 14:48:34 -07:00
|
|
|
|
|
|
|
|
|
|
|
class irchook(object):
|
|
|
|
def __call__(self, func):
|
|
|
|
if not hasattr(func, ATTR_COMMAND_HOOK):
|
|
|
|
setattr(func, ATTR_COMMAND_HOOK, [self])
|
|
|
|
else:
|
|
|
|
getattr(func, ATTR_COMMAND_HOOK).extend(self)
|
|
|
|
return func
|
|
|
|
|
|
|
|
def validate(self, msg, bot):
|
|
|
|
"""
|
|
|
|
Return True if the message should be passed on. False otherwise.
|
|
|
|
"""
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
class command(irchook):
|
|
|
|
"""
|
|
|
|
Decorating for calling module methods in response to IRC actions. Example:
|
|
|
|
```
|
|
|
|
@hook("PRIVMSG")
|
|
|
|
def mymyethod(self, message):
|
|
|
|
print("IRC server sent PRIVMSG")
|
|
|
|
```
|
|
|
|
This stores a list of IRC actions each function is tagged for in method.__tag_hooks. This attribute is scanned
|
|
|
|
during module init and appropriate hooks are set up.
|
|
|
|
"""
|
|
|
|
prefix = "."
|
|
|
|
|
|
|
|
def __init__(self, *keywords, require_args=False, allow_private=False):
|
|
|
|
self.keywords = keywords
|
|
|
|
self.require_args = require_args
|
|
|
|
self.allow_private = allow_private
|
|
|
|
self.parsed_cmd = None
|
|
|
|
|
|
|
|
def call(self, method, msg):
|
|
|
|
if len(getargspec(method).args) == 3:
|
|
|
|
method(self.parsed_cmd, msg)
|
|
|
|
else:
|
|
|
|
method(self.parsed_cmd)
|
|
|
|
|
|
|
|
def validate(self, msg, bot):
|
|
|
|
if not self.allow_private and msg.args[0] == "#":
|
|
|
|
return False
|
|
|
|
for keyword in self.keywords:
|
|
|
|
if self._validate_one(msg, keyword):
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
|
|
def _validate_one(self, msg, keyword):
|
|
|
|
with_prefix = "{}{}".format(self.prefix, keyword)
|
|
|
|
cmd = PyIRCBot.messageHasCommand(with_prefix, msg.trailing, requireArgs=self.require_args)
|
|
|
|
if cmd:
|
|
|
|
self.parsed_cmd = cmd
|
|
|
|
return True
|
|
|
|
return False
|