/usr/lib/python3/dist-packages/sopel/module.py is in sopel 6.5.0-1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 | # coding=utf-8
"""This contains decorators and tools for creating callable plugin functions.
"""
# Copyright 2013, Ari Koivula, <ari@koivu.la>
# Copyright © 2013, Elad Alfassa <elad@fedoraproject.org>
# Copyright 2013, Lior Ramati <firerogue517@gmail.com>
# Licensed under the Eiffel Forum License 2.
from __future__ import unicode_literals, absolute_import, print_function, division
import re
import sopel.test_tools
import functools
NOLIMIT = 1
"""Return value for ``callable``\s, which supresses rate limiting for the call.
Returning this value means the triggering user will not be
prevented from triggering the command again within the rate limit. This can
be used, for example, to allow a user to rety a failed command immediately.
.. versionadded:: 4.0
"""
VOICE = 1
HALFOP = 2
OP = 4
ADMIN = 8
OWNER = 16
def unblockable(function):
"""Decorator which exempts the function from nickname and hostname blocking.
This can be used to ensure events such as JOIN are always recorded.
"""
function.unblockable = True
return function
def interval(*args):
"""Decorates a function to be called by the bot every X seconds.
This decorator can be used multiple times for multiple intervals, or all
intervals can be given at once as arguments. The first time the function
will be called is X seconds after the bot was started.
Unlike other plugin functions, ones decorated by interval must only take a
:class:`sopel.bot.Sopel` as their argument; they do not get a trigger. The
bot argument will not have a context, so functions like ``bot.say()`` will
not have a default destination.
There is no guarantee that the bot is connected to a server or joined a
channel when the function is called, so care must be taken.
Example:::
import sopel.module
@sopel.module.interval(5)
def spam_every_5s(bot):
if "#here" in bot.channels:
bot.msg("#here", "It has been five seconds!")
"""
def add_attribute(function):
if not hasattr(function, "interval"):
function.interval = []
for arg in args:
function.interval.append(arg)
return function
return add_attribute
def rule(value):
"""Decorate a function to be called when a line matches the given pattern
This decorator can be used multiple times to add more rules.
Args:
value: A regular expression which will trigger the function.
If the Sopel instance is in a channel, or sent a PRIVMSG, where a string
matching this expression is said, the function will execute. Note that
captured groups here will be retrievable through the Trigger object later.
Inside the regular expression, some special directives can be used. $nick
will be replaced with the nick of the bot and , or :, and $nickname will be
replaced with the nick of the bot.
"""
def add_attribute(function):
if not hasattr(function, "rule"):
function.rule = []
function.rule.append(value)
return function
return add_attribute
def thread(value):
"""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 nickname_commands(*command_list):
"""Decorate a function to trigger on lines starting with "$nickname: command".
This decorator can be used multiple times to add multiple rules. 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 regular expression appended to the rule
attribute. If there is no rule attribute, it is added.
Example:
@nickname_commands("hello!"):
Would trigger on "$nickname: hello!", "$nickname, hello!",
"$nickname hello!", "$nickname hello! parameter1" and
"$nickname hello! p1 p2 p3 p4 p5 p6 p7 p8 p9".
@nickname_commands(".*"):
Would trigger on anything starting with "$nickname[:,]? ", and
would have never have any additional parameters, as the command
would match the rest of the line.
"""
def add_attribute(function):
if not hasattr(function, "rule"):
function.rule = []
rule = r"""
^
$nickname[:,]? # Nickname.
\s+({command}) # Command as group 1.
(?:\s+ # Whitespace to end command.
( # Rest of the line as group 2.
(?:(\S+))? # Parameters 1-4 as groups 3-6.
(?:\s+(\S+))?
(?:\s+(\S+))?
(?:\s+(\S+))?
.* # Accept anything after the parameters. Leave it up to
# the module to parse the line.
))? # Group 1 must be None, if there are no parameters.
$ # EoL, so there are no partial matches.
""".format(command='|'.join(command_list))
function.rule.append(rule)
return function
return add_attribute
def priority(value):
"""Decorate a function to be executed with higher or lower priority.
Args:
value: Priority can be one of "high", "medium", "low". Defaults to
medium.
Priority allows you to control the order of callable execution, if your
module needs it.
"""
def add_attribute(function):
function.priority = value
return function
return add_attribute
def event(*event_list):
"""Decorate a function to be triggered on specific IRC events.
This is one of a number of events, such as 'JOIN', 'PART', 'QUIT', etc.
(More details can be found in RFC 1459.) When the Sopel bot is sent one of
these events, the function will execute. Note that functions with an event
must also be given a rule to match (though it may be '.*', which will
always match) or they will not be triggered.
:class:`sopel.tools.events` provides human-readable names for many of the
numeric events, which may help your code be clearer.
"""
def add_attribute(function):
if not hasattr(function, "event"):
function.event = []
function.event.extend(event_list)
return function
return add_attribute
def intent(*intent_list):
"""Decorate a callable trigger on a message with any of the given intents.
.. versionadded:: 5.2.0
"""
def add_attribute(function):
if not hasattr(function, "intents"):
function.intents = []
function.intents.extend(intent_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 Sopel’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.say(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.say(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 actual_decorator(function):
@functools.wraps(function)
def guarded(bot, trigger, *args, **kwargs):
# If this is a privmsg, ignore privilege requirements
if trigger.is_privmsg:
return function(bot, trigger, *args, **kwargs)
channel_privs = bot.privileges[trigger.sender]
allowed = channel_privs.get(trigger.nick, 0) >= level
if not trigger.is_privmsg and not allowed:
if message and not callable(message):
bot.say(message)
else:
return function(bot, trigger, *args, **kwargs)
return guarded
return 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):
@functools.wraps(function)
def guarded(bot, trigger, *args, **kwargs):
if not trigger.admin:
if message and not callable(message):
bot.say(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):
@functools.wraps(function)
def guarded(bot, trigger, *args, **kwargs):
if not trigger.owner:
if message and not callable(message):
bot.say(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 url(url_rule):
"""Decorate a function to handle URLs.
This decorator takes a regex string that will be matched against URLs in a
message. The function it decorates, in addition to the bot and trigger,
must take a third argument ``match``, which is the regular expression match
of the url. This should be used rather than the matching in trigger, in
order to support e.g. the ``.title`` command.
"""
def actual_decorator(function):
@functools.wraps(function)
def helper(bot, trigger, match=None):
match = match or trigger
return function(bot, trigger, match)
helper.url_regex = re.compile(url_rule)
return helper
return actual_decorator
class example(object):
"""Decorate a function with an example.
Add an example attribute into a function and generate a test.
"""
# TODO dat doc doe >_<
def __init__(self, msg, result=None, privmsg=False, admin=False,
owner=False, repeat=1, re=False, ignore=None):
"""Accepts arguments for the decorator.
Args:
msg - The example message to give to the function as input.
result - Resulting output from calling the function with msg.
privmsg - If true, make the message appear to have sent in a
private message to the bot. If false, make it appear to have
come from a channel.
admin - Bool. Make the message appear to have come from an admin.
owner - Bool. Make the message appear to have come from an owner.
repeat - How many times to repeat the test. Usefull for tests that
return random stuff.
re - Bool. If true, result is interpreted as a regular expression.
ignore - a list of outputs to ignore.
"""
# Wrap result into a list for get_example_test
if isinstance(result, list):
self.result = result
elif result is not None:
self.result = [result]
else:
self.result = None
self.use_re = re
self.msg = msg
self.privmsg = privmsg
self.admin = admin
self.owner = owner
self.repeat = repeat
if isinstance(ignore, list):
self.ignore = ignore
elif ignore is not None:
self.ignore = [ignore]
else:
self.ignore = []
def __call__(self, func):
if not hasattr(func, "example"):
func.example = []
if self.result:
test = sopel.test_tools.get_example_test(
func, self.msg, self.result, self.privmsg, self.admin,
self.owner, self.repeat, self.use_re, self.ignore
)
sopel.test_tools.insert_into_module(
test, func.__module__, func.__name__, 'test_example'
)
record = {
"example": self.msg,
"result": self.result,
"privmsg": self.privmsg,
"admin": self.admin,
}
func.example.append(record)
return func
|