add command decorator
This commit is contained in:
parent
65459051da
commit
b09e675189
|
@ -8,6 +8,8 @@
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from .pyircbot import PyIRCBot
|
from .pyircbot import PyIRCBot
|
||||||
|
from inspect import getargspec
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
class ModuleBase:
|
class ModuleBase:
|
||||||
|
@ -29,6 +31,9 @@ class ModuleBase:
|
||||||
self.hooks = []
|
self.hooks = []
|
||||||
"""Hooks (aka listeners) this module has"""
|
"""Hooks (aka listeners) this module has"""
|
||||||
|
|
||||||
|
self.irchooks = []
|
||||||
|
"""IRC Hooks this module has"""
|
||||||
|
|
||||||
self.services = []
|
self.services = []
|
||||||
"""If this module provides services usable by another module, they're listed
|
"""If this module provides services usable by another module, they're listed
|
||||||
here"""
|
here"""
|
||||||
|
@ -53,9 +58,14 @@ class ModuleBase:
|
||||||
"""
|
"""
|
||||||
for attr_name in dir(self):
|
for attr_name in dir(self):
|
||||||
attr = getattr(self, attr_name)
|
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):
|
for action in getattr(attr, ATTR_ACTION_HOOK):
|
||||||
self.hooks.append(ModuleHook(action, attr))
|
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):
|
def loadConfig(self):
|
||||||
"""
|
"""
|
||||||
|
@ -85,7 +95,7 @@ class ModuleBase:
|
||||||
:type channel: str
|
:type channel: str
|
||||||
:Warning: .. Warning:: this does no error checking if the file exists or is\
|
:Warning: .. Warning:: this does no error checking if the file exists or is\
|
||||||
writable. The bot's data dir *should* always be writable"""
|
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:
|
class ModuleHook:
|
||||||
|
@ -94,12 +104,22 @@ class ModuleHook:
|
||||||
self.method = method
|
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_ACTION_HOOK = "__tag_hooks"
|
||||||
|
ATTR_COMMAND_HOOK = "__tag_commands"
|
||||||
|
|
||||||
|
|
||||||
class hook(object):
|
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")
|
@hook("PRIVMSG")
|
||||||
def mymyethod(self, message):
|
def mymyethod(self, message):
|
||||||
|
@ -120,3 +140,60 @@ class hook(object):
|
||||||
else:
|
else:
|
||||||
getattr(func, ATTR_ACTION_HOOK).extend(self.commands)
|
getattr(func, ATTR_ACTION_HOOK).extend(self.commands)
|
||||||
return func
|
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
|
from json import load
|
||||||
import os.path
|
import os.path
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
|
||||||
ParsedCommand = namedtuple("ParsedCommand", "command args args_str message")
|
ParsedCommand = namedtuple("ParsedCommand", "command args args_str message")
|
||||||
|
@ -67,6 +68,9 @@ class PyIRCBot(object):
|
||||||
# Load modules
|
# Load modules
|
||||||
self.initModules()
|
self.initModules()
|
||||||
|
|
||||||
|
# Internal usage hook
|
||||||
|
self.irc.addHook("PRIVMSG", self._hook_privmsg_internal)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
self.loop = asyncio.get_event_loop()
|
self.loop = asyncio.get_event_loop()
|
||||||
|
|
||||||
|
@ -111,6 +115,16 @@ class PyIRCBot(object):
|
||||||
for modulename in self.botconfig["modules"]:
|
for modulename in self.botconfig["modules"]:
|
||||||
self.loadmodule(modulename)
|
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):
|
def importmodule(self, name):
|
||||||
"""Import a module
|
"""Import a module
|
||||||
|
|
||||||
|
@ -128,6 +142,7 @@ class PyIRCBot(object):
|
||||||
" on failure (usually syntax error in Module code) print an error "
|
" on failure (usually syntax error in Module code) print an error "
|
||||||
self.log.error("Module %s failed to load: " % name)
|
self.log.error("Module %s failed to load: " % name)
|
||||||
self.log.error("Module load failure reason: " + str(e))
|
self.log.error("Module load failure reason: " + str(e))
|
||||||
|
traceback.print_exc()
|
||||||
return (False, str(e))
|
return (False, str(e))
|
||||||
else:
|
else:
|
||||||
self.log.warning("Module %s already imported" % name)
|
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
|
:param moduleName: the module who's data dir we want
|
||||||
:type moduleName: str"""
|
:type moduleName: str"""
|
||||||
if not os.path.exists("%s/data/%s" % (self.botconfig["bot"]["datadir"], moduleName)):
|
module_dir = os.path.join(self.botconfig["bot"]["datadir"], "data", moduleName)
|
||||||
os.mkdir("%s/data/%s/" % (self.botconfig["bot"]["datadir"], moduleName))
|
if not os.path.exists(module_dir):
|
||||||
return "%s/data/%s/" % (self.botconfig["bot"]["datadir"], moduleName)
|
os.mkdir(module_dir)
|
||||||
|
return module_dir
|
||||||
|
|
||||||
def getConfigPath(self, moduleName):
|
def getConfigPath(self, moduleName):
|
||||||
"""Return the absolute path for a module's config file
|
"""Return the absolute path for a module's config file
|
||||||
|
|
Loading…
Reference in New Issue