fulvia/module.py

255 lines
7.4 KiB
Python
Executable File

#!/usr/bin/env python3
"""
This contains decorators and tools for creating callable plugin functions.
"""
import functools
def hook(value=False):
"""
Decorate a function to be called every time a PRIVMSG is received.
Args:
value: Either True or False. If True the function is called every
time a PRIVMSG is received. If False it is not. Default is False.
PRIVMSGs are ordinary messages sent from either a channel or a user (a
private message). In a busy channel, this function will be called quite
a lot. Please consider this carefully before applying as it can have
significant performance implications.
Note: Do not use this with the commands decorator.
"""
def add_attribute(function):
function.hook = value
return function
return add_attribute
def thread(value=True):
"""
Decorate a function to specify if it should be run in a separate thread.
Functions run in a separate thread (as is the default) will not prevent the
bot from executing other functions at the same time. Functions not run in a
separate thread may be started while other functions are still running, but
additional functions will not start until it is completed.
Args:
value: Either True or False. If True the function is called in
a separate thread. If False from the main thread.
"""
def add_attribute(function):
function.thread = value
return function
return add_attribute
def commands(*command_list):
"""
Decorate a function to set one or more commands to trigger it.
This decorator can be used to add multiple commands to one callable in a
single line. The resulting match object will have the command as the first
group, rest of the line, excluding leading whitespace, as the second group.
Parameters 1 through 4, seperated by whitespace, will be groups 3-6.
Args:
command: A string, which can be a regular expression.
Returns:
A function with a new command appended to the commands
attribute. If there is no commands attribute, it is added.
Example:
@commands("hello"):
If the command prefix is "\.", this would trigger on lines starting
with ".hello".
@commands('j', 'join')
If the command prefix is "\.", this would trigger on lines starting
with either ".j" or ".join".
"""
def add_attribute(function):
if not hasattr(function, "commands"):
function.commands = []
function.commands.extend(command_list)
return function
return add_attribute
def rate(user=0, channel=0, server=0):
"""
Decorate a function to limit how often it can be triggered on a per-user
basis, in a channel, or across the server (bot). A value of zero means no
limit. If a function is given a rate of 20, that function may only be used
once every 20 seconds in the scope corresponding to the parameter.
Users on the admin list in Fulvia's configuration are exempted from rate
limits.
Rate-limited functions that use scheduled future commands should import
threading.Timer() instead of sched, or rate limiting will not work properly.
"""
def add_attribute(function):
function.rate = user
function.channel_rate = channel
function.global_rate = server
return function
return add_attribute
def require_privmsg(message=None):
"""
Decorate a function to only be triggerable from a private message.
If it is triggered in a channel message, `message` will be said if given.
"""
def actual_decorator(function):
@functools.wraps(function)
def _nop(*args, **kwargs):
# Assign trigger and bot for easy access later
bot, trigger = args[0:2]
if trigger.is_privmsg:
return function(*args, **kwargs)
else:
if message and not callable(message):
bot.msg(message)
return _nop
# Hack to allow decorator without parens
if callable(message):
return actual_decorator(message)
return actual_decorator
def require_chanmsg(message=None):
"""
Decorate a function to only be triggerable from a channel message.
If it is triggered in a private message, `message` will be said if given.
"""
def actual_decorator(function):
@functools.wraps(function)
def _nop(*args, **kwargs):
# Assign trigger and bot for easy access later
bot, trigger = args[0:2]
if not trigger.is_privmsg:
return function(*args, **kwargs)
else:
if message and not callable(message):
bot.msg(message)
return _nop
# Hack to allow decorator without parens
if callable(message):
return actual_decorator(message)
return actual_decorator
def require_privilege(level, message=None):
"""
Decorate a function to require at least the given channel permission.
`level` can be one of the privilege levels defined in this module. If the
user does not have the privilege, `message` will be said if given. If it is
a private message, no checking will be done.
"""
def add_attribute(function):
function.priv = level
return add_attribute
def actual_decorator(function):
@functools.wraps(function)
def guarded(bot, trigger, *args, **kwargs):
# If this is a privmsg, ignore privilege requirements
if trigger.is_privmsg or trigger.admin:
return function(bot, trigger, *args, **kwargs)
channel_privs = bot.channels[trigger.channel].privileges
allowed = channel_privs.get(trigger.nick, 0) >= level
if not allowed:
if message and not callable(message):
bot.msg(message)
else:
return function(bot, trigger, *args, **kwargs)
return guarded
return add_attribute(actual_decorator)
def require_admin(message=None):
"""
Decorate a function to require the triggering user to be a bot admin.
If they are not, `message` will be said if given.
"""
def actual_decorator(function):
function.priv = 5
@functools.wraps(function)
def guarded(bot, trigger, *args, **kwargs):
if not trigger.admin:
if message and not callable(message):
bot.msg(message)
else:
return function(bot, trigger, *args, **kwargs)
return guarded
# Hack to allow decorator without parens
if callable(message):
return actual_decorator(message)
return actual_decorator
def require_owner(message=None):
"""
Decorate a function to require the triggering user to be the bot owner.
If they are not, `message` will be said if given.
"""
def actual_decorator(function):
function.priv = 10
@functools.wraps(function)
def guarded(bot, trigger, *args, **kwargs):
if not trigger.owner:
if message and not callable(message):
bot.msg(message)
else:
return function(bot, trigger, *args, **kwargs)
return guarded
# Hack to allow decorator without parens
if callable(message):
return actual_decorator(message)
return actual_decorator
def example(ex_input, ex_output=None):
"""
Decorate a function with an example input, and optionally a sample output.
Examples are added to the bot.doc dictionary with the function name as
the key alongside it's calling command. The 'commands' decorator should
be used with it.
"""
def add_attribute(function):
if not hasattr(function, "example"):
function.example = []
function.example.append((ex_input, ex_output))
return function
return add_attribute
def url_callback(url):
"""
Decore a function with a callback to the URL module.
This URL will be added to the bot.url_callbacks dictionary in the bot's
memory which the URL module will compare it's URL's against. If a key in
the bot.url_callbacks dict is found inside the gathered URL, this
function will be called instead.
"""
def add_attribute(function):
if not hasattr(function, "url_callback"):
function.url_callback = []
function.url_callback.append(url)
return function
return add_attribute