2018-03-16 03:13:43 -04:00
|
|
|
#!/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.
|
2018-03-25 14:26:29 -04:00
|
|
|
Users on the admin list in Fulvia's configuration are exempted from rate
|
2018-03-16 03:13:43 -04:00
|
|
|
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):
|
2018-05-25 15:21:18 -04:00
|
|
|
bot.msg(message)
|
2018-03-16 03:13:43 -04:00
|
|
|
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):
|
2018-05-25 15:21:18 -04:00
|
|
|
bot.msg(message)
|
2018-03-16 03:13:43 -04:00
|
|
|
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.
|
|
|
|
"""
|
2018-05-25 12:54:11 -04:00
|
|
|
def add_attribute(function):
|
|
|
|
function.priv = level
|
|
|
|
return add_attribute
|
|
|
|
|
2018-03-16 03:13:43 -04:00
|
|
|
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)
|
2018-05-25 12:54:11 -04:00
|
|
|
channel_privs = bot.channels[trigger.channel].privileges
|
2018-03-16 03:13:43 -04:00
|
|
|
allowed = channel_privs.get(trigger.nick, 0) >= level
|
|
|
|
if not allowed:
|
|
|
|
if message and not callable(message):
|
2018-05-25 15:21:18 -04:00
|
|
|
bot.msg(message)
|
2018-03-16 03:13:43 -04:00
|
|
|
else:
|
|
|
|
return function(bot, trigger, *args, **kwargs)
|
|
|
|
return guarded
|
2018-05-25 12:54:11 -04:00
|
|
|
return add_attribute(actual_decorator)
|
2018-03-16 03:13:43 -04:00
|
|
|
|
|
|
|
|
|
|
|
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):
|
2018-03-25 14:26:29 -04:00
|
|
|
function.priv = 5
|
|
|
|
|
2018-03-16 03:13:43 -04:00
|
|
|
@functools.wraps(function)
|
|
|
|
def guarded(bot, trigger, *args, **kwargs):
|
|
|
|
if not trigger.admin:
|
|
|
|
if message and not callable(message):
|
2018-05-25 15:21:18 -04:00
|
|
|
bot.msg(message)
|
2018-03-16 03:13:43 -04:00
|
|
|
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):
|
2018-03-25 14:26:29 -04:00
|
|
|
function.priv = 10
|
|
|
|
|
2018-03-16 03:13:43 -04:00
|
|
|
@functools.wraps(function)
|
|
|
|
def guarded(bot, trigger, *args, **kwargs):
|
|
|
|
if not trigger.owner:
|
|
|
|
if message and not callable(message):
|
2018-05-25 15:21:18 -04:00
|
|
|
bot.msg(message)
|
2018-03-16 03:13:43 -04:00
|
|
|
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
|