add command decorator
This commit is contained in:
parent
65459051da
commit
b09e675189
@ -8,6 +8,8 @@
|
||||
|
||||
import logging
|
||||
from .pyircbot import PyIRCBot
|
||||
from inspect import getargspec
|
||||
import os
|
||||
|
||||
|
||||
class ModuleBase:
|
||||
@ -29,6 +31,9 @@ class ModuleBase:
|
||||
self.hooks = []
|
||||
"""Hooks (aka listeners) this module has"""
|
||||
|
||||
self.irchooks = []
|
||||
"""IRC Hooks this module has"""
|
||||
|
||||
self.services = []
|
||||
"""If this module provides services usable by another module, they're listed
|
||||
here"""
|
||||
@ -53,9 +58,14 @@ class ModuleBase:
|
||||
"""
|
||||
for attr_name in dir(self):
|
||||
attr = getattr(self, attr_name)
|
||||
if callable(attr) and hasattr(attr, ATTR_ACTION_HOOK):
|
||||
if not callable(attr):
|
||||
continue
|
||||
if hasattr(attr, ATTR_ACTION_HOOK):
|
||||
for action in getattr(attr, ATTR_ACTION_HOOK):
|
||||
self.hooks.append(ModuleHook(action, attr))
|
||||
if hasattr(attr, ATTR_COMMAND_HOOK):
|
||||
for action in getattr(attr, ATTR_COMMAND_HOOK):
|
||||
self.irchooks.append(IRCHook(action, attr))
|
||||
|
||||
def loadConfig(self):
|
||||
"""
|
||||
@ -85,7 +95,7 @@ class ModuleBase:
|
||||
: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"""
|
||||
return self.bot.getDataPath(self.moduleName) + (f if f else '')
|
||||
return os.path.join(self.bot.getDataPath(self.moduleName), (f if f else ''))
|
||||
|
||||
|
||||
class ModuleHook:
|
||||
@ -94,12 +104,22 @@ class ModuleHook:
|
||||
self.method = method
|
||||
|
||||
|
||||
class IRCHook:
|
||||
def __init__(self, hook, method):
|
||||
self.hook = hook
|
||||
self.method = method
|
||||
|
||||
def call(self, msg):
|
||||
self.hook.call(self.method, msg)
|
||||
|
||||
|
||||
ATTR_ACTION_HOOK = "__tag_hooks"
|
||||
ATTR_COMMAND_HOOK = "__tag_commands"
|
||||
|
||||
|
||||
class hook(object):
|
||||
"""
|
||||
Decorating for calling module methods in response to IRC actions. Example:
|
||||
Decorator for calling module methods in response to IRC actions. Example:
|
||||
```
|
||||
@hook("PRIVMSG")
|
||||
def mymyethod(self, message):
|
||||
@ -120,3 +140,60 @@ class hook(object):
|
||||
else:
|
||||
getattr(func, ATTR_ACTION_HOOK).extend(self.commands)
|
||||
return func
|
||||
|
||||
|
||||
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
|
||||
|
@ -15,6 +15,7 @@ from socket import AF_INET, AF_INET6
|
||||
from json import load
|
||||
import os.path
|
||||
import asyncio
|
||||
import traceback
|
||||
|
||||
|
||||
ParsedCommand = namedtuple("ParsedCommand", "command args args_str message")
|
||||
@ -67,6 +68,9 @@ class PyIRCBot(object):
|
||||
# Load modules
|
||||
self.initModules()
|
||||
|
||||
# Internal usage hook
|
||||
self.irc.addHook("PRIVMSG", self._hook_privmsg_internal)
|
||||
|
||||
def run(self):
|
||||
self.loop = asyncio.get_event_loop()
|
||||
|
||||
@ -111,6 +115,16 @@ class PyIRCBot(object):
|
||||
for modulename in self.botconfig["modules"]:
|
||||
self.loadmodule(modulename)
|
||||
|
||||
def _hook_privmsg_internal(self, msg):
|
||||
"""
|
||||
IRC hook handler. Calling point for IRCHook based module hooks. This method is called when a PRIVMSG is
|
||||
received. It tests all hooks in all modules against the message can calls the hooked function on hits.
|
||||
"""
|
||||
for module_name, module in self.moduleInstances.items():
|
||||
for hook in module.irchooks:
|
||||
if hook.hook.validate(msg, self):
|
||||
hook.call(msg)
|
||||
|
||||
def importmodule(self, name):
|
||||
"""Import a module
|
||||
|
||||
@ -128,6 +142,7 @@ class PyIRCBot(object):
|
||||
" on failure (usually syntax error in Module code) print an error "
|
||||
self.log.error("Module %s failed to load: " % name)
|
||||
self.log.error("Module load failure reason: " + str(e))
|
||||
traceback.print_exc()
|
||||
return (False, str(e))
|
||||
else:
|
||||
self.log.warning("Module %s already imported" % name)
|
||||
@ -299,9 +314,10 @@ class PyIRCBot(object):
|
||||
|
||||
:param moduleName: the module who's data dir we want
|
||||
:type moduleName: str"""
|
||||
if not os.path.exists("%s/data/%s" % (self.botconfig["bot"]["datadir"], moduleName)):
|
||||
os.mkdir("%s/data/%s/" % (self.botconfig["bot"]["datadir"], moduleName))
|
||||
return "%s/data/%s/" % (self.botconfig["bot"]["datadir"], moduleName)
|
||||
module_dir = os.path.join(self.botconfig["bot"]["datadir"], "data", moduleName)
|
||||
if not os.path.exists(module_dir):
|
||||
os.mkdir(module_dir)
|
||||
return module_dir
|
||||
|
||||
def getConfigPath(self, moduleName):
|
||||
"""Return the absolute path for a module's config file
|
||||
|
Loading…
Reference in New Issue
Block a user