1### 2# Copyright (c) 2002-2005, Jeremiah Fincher 3# Copyright (c) 2010, James McCoy 4# All rights reserved. 5# 6# Redistribution and use in source and binary forms, with or without 7# modification, are permitted provided that the following conditions are met: 8# 9# * Redistributions of source code must retain the above copyright notice, 10# this list of conditions, and the following disclaimer. 11# * Redistributions in binary form must reproduce the above copyright notice, 12# this list of conditions, and the following disclaimer in the 13# documentation and/or other materials provided with the distribution. 14# * Neither the name of the author of this software nor the name of 15# contributors to this software may be used to endorse or promote products 16# derived from this software without specific prior written consent. 17# 18# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 22# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28# POSSIBILITY OF SUCH DAMAGE. 29### 30 31""" 32This module provides the basic IrcMsg object used throughout the bot to 33represent the actual messages. It also provides several helper functions to 34construct such messages in an easier way than the constructor for the IrcMsg 35object (which, as you'll read later, is quite...full-featured :)) 36""" 37 38import re 39import time 40import base64 41import datetime 42import warnings 43import functools 44 45from . import conf, ircutils, utils 46from .utils.iter import all 47from .utils import minisix 48 49### 50# IrcMsg class -- used for representing IRC messages acquired from a network. 51### 52 53class MalformedIrcMsg(ValueError): 54 pass 55 56# http://ircv3.net/specs/core/message-tags-3.2.html#escaping-values 57SERVER_TAG_ESCAPE = [ 58 ('\\', '\\\\'), # \ -> \\ 59 (' ', r'\s'), 60 (';', r'\:'), 61 ('\r', r'\r'), 62 ('\n', r'\n'), 63 ] 64escape_server_tag_value = utils.str.MultipleReplacer( 65 dict(SERVER_TAG_ESCAPE)) 66unescape_server_tag_value = utils.str.MultipleReplacer( 67 dict(map(lambda x:(x[1],x[0]), SERVER_TAG_ESCAPE))) 68 69def parse_server_tags(s): 70 server_tags = {} 71 for tag in s.split(';'): 72 if '=' not in tag: 73 server_tags[tag] = None 74 else: 75 (key, value) = tag.split('=', 1) 76 value = unescape_server_tag_value(value) 77 if value == '': 78 # "Implementations MUST interpret empty tag values (e.g. foo=) 79 # as equivalent to missing tag values (e.g. foo)." 80 value = None 81 server_tags[key] = value 82 return server_tags 83def format_server_tags(server_tags): 84 parts = [] 85 for (key, value) in server_tags.items(): 86 if value is None: 87 parts.append(key) 88 else: 89 parts.append('%s=%s' % (key, escape_server_tag_value(value))) 90 return '@' + ';'.join(parts) 91 92class IrcMsg(object): 93 """Class to represent an IRC message. 94 95 As usual, ignore attributes that begin with an underscore. They simply 96 don't exist. Instances of this class are *not* to be modified, since they 97 are hashable. Public attributes of this class are .prefix, .command, 98 .args, .nick, .user, and .host. 99 100 The constructor for this class is pretty intricate. It's designed to take 101 any of three major (sets of) arguments. 102 103 Called with no keyword arguments, it takes a single string that is a raw 104 IRC message (such as one taken straight from the network). 105 106 Called with keyword arguments, it *requires* a command parameter. Args is 107 optional, but with most commands will be necessary. Prefix is obviously 108 optional, since clients aren't allowed (well, technically, they are, but 109 only in a completely useless way) to send prefixes to the server. 110 111 Since this class isn't to be modified, the constructor also accepts a 'msg' 112 keyword argument representing a message from which to take all the 113 attributes not provided otherwise as keyword arguments. So, for instance, 114 if a programmer wanted to take a PRIVMSG they'd gotten and simply redirect 115 it to a different source, they could do this: 116 117 IrcMsg(prefix='', args=(newSource, otherMsg.args[1]), msg=otherMsg) 118 """ 119 # It's too useful to be able to tag IrcMsg objects with extra, unforeseen 120 # data. Goodbye, __slots__. 121 # On second thought, let's use methods for tagging. 122 __slots__ = ('args', 'command', 'host', 'nick', 'prefix', 'user', 123 '_hash', '_str', '_repr', '_len', 'tags', 'reply_env', 124 'server_tags', 'time') 125 def __init__(self, s='', command='', args=(), prefix='', msg=None, 126 reply_env=None): 127 assert not (msg and s), 'IrcMsg.__init__ cannot accept both s and msg' 128 if not s and not command and not msg: 129 raise MalformedIrcMsg('IRC messages require a command.') 130 self._str = None 131 self._repr = None 132 self._hash = None 133 self._len = None 134 self.reply_env = reply_env 135 self.tags = {} 136 if s: 137 originalString = s 138 try: 139 if not s.endswith('\n'): 140 s += '\n' 141 self._str = s 142 if s[0] == '@': 143 (server_tags, s) = s.split(' ', 1) 144 self.server_tags = parse_server_tags(server_tags[1:]) 145 else: 146 self.server_tags = {} 147 if s[0] == ':': 148 self.prefix, s = s[1:].split(None, 1) 149 else: 150 self.prefix = '' 151 if ' :' in s: # Note the space: IPV6 addresses are bad w/o it. 152 s, last = s.split(' :', 1) 153 self.args = s.split() 154 self.args.append(last.rstrip('\r\n')) 155 else: 156 self.args = s.split() 157 self.command = self.args.pop(0) 158 if 'time' in self.server_tags: 159 s = self.server_tags['time'] 160 date = datetime.datetime.strptime(s, '%Y-%m-%dT%H:%M:%S.%fZ') 161 date = minisix.make_datetime_utc(date) 162 self.time = minisix.datetime__timestamp(date) 163 else: 164 self.time = time.time() 165 except (IndexError, ValueError): 166 raise MalformedIrcMsg(repr(originalString)) 167 else: 168 if msg is not None: 169 if prefix: 170 self.prefix = prefix 171 else: 172 self.prefix = msg.prefix 173 if command: 174 self.command = command 175 else: 176 self.command = msg.command 177 if args: 178 self.args = args 179 else: 180 self.args = msg.args 181 if reply_env: 182 self.reply_env = reply_env 183 elif msg.reply_env: 184 self.reply_env = msg.reply_env.copy() 185 else: 186 self.reply_env = None 187 self.tags = msg.tags.copy() 188 self.server_tags = msg.server_tags 189 self.time = msg.time 190 else: 191 self.prefix = prefix 192 self.command = command 193 assert all(ircutils.isValidArgument, args), args 194 self.args = args 195 self.time = None 196 self.server_tags = {} 197 self.args = tuple(self.args) 198 if isUserHostmask(self.prefix): 199 (self.nick,self.user,self.host)=ircutils.splitHostmask(self.prefix) 200 else: 201 (self.nick, self.user, self.host) = (self.prefix,)*3 202 203 def __str__(self): 204 if self._str is not None: 205 return self._str 206 if self.prefix: 207 if len(self.args) > 1: 208 self._str = ':%s %s %s :%s\r\n' % \ 209 (self.prefix, self.command, 210 ' '.join(self.args[:-1]), self.args[-1]) 211 else: 212 if self.args: 213 self._str = ':%s %s :%s\r\n' % \ 214 (self.prefix, self.command, self.args[0]) 215 else: 216 self._str = ':%s %s\r\n' % (self.prefix, self.command) 217 else: 218 if len(self.args) > 1: 219 self._str = '%s %s :%s\r\n' % \ 220 (self.command, 221 ' '.join(self.args[:-1]), self.args[-1]) 222 else: 223 if self.args: 224 self._str = '%s :%s\r\n' % (self.command, self.args[0]) 225 else: 226 self._str = '%s\r\n' % self.command 227 return self._str 228 229 def __len__(self): 230 return len(str(self)) 231 232 def __eq__(self, other): 233 return isinstance(other, self.__class__) and \ 234 hash(self) == hash(other) and \ 235 self.command == other.command and \ 236 self.prefix == other.prefix and \ 237 self.args == other.args 238 __req__ = __eq__ # I don't know exactly what this does, but it can't hurt. 239 240 def __ne__(self, other): 241 return not (self == other) 242 __rne__ = __ne__ # Likewise as above. 243 244 def __hash__(self): 245 if self._hash is not None: 246 return self._hash 247 self._hash = hash(self.command) ^ \ 248 hash(self.prefix) ^ \ 249 hash(repr(self.args)) 250 return self._hash 251 252 def __repr__(self): 253 if self._repr is not None: 254 return self._repr 255 self._repr = format('IrcMsg(prefix=%q, command=%q, args=%r)', 256 self.prefix, self.command, self.args) 257 return self._repr 258 259 def __reduce__(self): 260 return (self.__class__, (str(self),)) 261 262 def tag(self, tag, value=True): 263 """Affect a key:value pair to this message.""" 264 self.tags[tag] = value 265 266 def tagged(self, tag): 267 """Get the value affected to a tag.""" 268 return self.tags.get(tag) # Returns None if it's not there. 269 270 def __getattr__(self, attr): 271 if attr.startswith('__'): # Since PEP 487, Python calls __set_name__ 272 raise AttributeError("'%s' object has no attribute '%s'" % 273 (self.__class__.__name__, attr)) 274 if attr in self.tags: 275 warnings.warn("msg.<tagname> is deprecated. Use " 276 "msg.tagged('<tagname>') or msg.tags['<tagname>']" 277 "instead.", DeprecationWarning) 278 return self.tags[attr] 279 else: 280 # TODO: make this raise AttributeError 281 return None 282 283 284def isCtcp(msg): 285 """Returns whether or not msg is a CTCP message.""" 286 return msg.command in ('PRIVMSG', 'NOTICE') and \ 287 msg.args[1].startswith('\x01') and \ 288 msg.args[1].endswith('\x01') and \ 289 len(msg.args[1]) >= 2 290 291def isAction(msg): 292 """A predicate returning true if the PRIVMSG in question is an ACTION""" 293 if isCtcp(msg): 294 s = msg.args[1] 295 payload = s[1:-1] # Chop off \x01. 296 command = payload.split(None, 1)[0] 297 return command == 'ACTION' 298 else: 299 return False 300 301def isSplit(msg): 302 if msg.command == 'QUIT': 303 # It's a quit. 304 quitmsg = msg.args[0] 305 if not quitmsg.startswith('"') and not quitmsg.endswith('"'): 306 # It's not a user-generated quitmsg. 307 servers = quitmsg.split() 308 if len(servers) == 2: 309 # We could check if domains match, or if the hostnames actually 310 # resolve, but we're going to put that off for now. 311 return True 312 return False 313 314_unactionre = re.compile(r'^\x01ACTION\s+(.*)\x01$') 315def unAction(msg): 316 """Returns the payload (i.e., non-ACTION text) of an ACTION msg.""" 317 assert isAction(msg) 318 return _unactionre.match(msg.args[1]).group(1) 319 320def _escape(s): 321 s = s.replace('&', '&') 322 s = s.replace('"', '"') 323 s = s.replace('<', '<') 324 s = s.replace('>', '>') 325 return s 326 327def toXml(msg, pretty=True, includeTime=True): 328 assert msg.command == _escape(msg.command) 329 L = [] 330 L.append('<msg command="%s" prefix="%s"'%(msg.command,_escape(msg.prefix))) 331 if includeTime: 332 L.append(' time="%s"' % time.time()) 333 L.append('>') 334 if pretty: 335 L.append('\n') 336 for arg in msg.args: 337 if pretty: 338 L.append(' ') 339 L.append('<arg>%s</arg>' % _escape(arg)) 340 if pretty: 341 L.append('\n') 342 L.append('</msg>\n') 343 return ''.join(L) 344 345def prettyPrint(msg, addRecipients=False, timestampFormat=None, showNick=True): 346 """Provides a client-friendly string form for messages. 347 348 IIRC, I copied BitchX's (or was it XChat's?) format for messages. 349 """ 350 def nickorprefix(): 351 return msg.nick or msg.prefix 352 def nick(): 353 if addRecipients: 354 return '%s/%s' % (msg.nick, msg.args[0]) 355 else: 356 return msg.nick 357 if msg.command == 'PRIVMSG': 358 m = _unactionre.match(msg.args[1]) 359 if m: 360 s = '* %s %s' % (nick(), m.group(1)) 361 else: 362 if not showNick: 363 s = '%s' % msg.args[1] 364 else: 365 s = '<%s> %s' % (nick(), msg.args[1]) 366 elif msg.command == 'NOTICE': 367 if not showNick: 368 s = '%s' % msg.args[1] 369 else: 370 s = '-%s- %s' % (nick(), msg.args[1]) 371 elif msg.command == 'JOIN': 372 prefix = msg.prefix 373 if msg.nick: 374 prefix = '%s <%s>' % (msg.nick, prefix) 375 s = '*** %s has joined %s' % (prefix, msg.args[0]) 376 elif msg.command == 'PART': 377 if len(msg.args) > 1: 378 partmsg = ' (%s)' % msg.args[1] 379 else: 380 partmsg = '' 381 s = '*** %s <%s> has parted %s%s' % (msg.nick, msg.prefix, 382 msg.args[0], partmsg) 383 elif msg.command == 'KICK': 384 if len(msg.args) > 2: 385 kickmsg = ' (%s)' % msg.args[1] 386 else: 387 kickmsg = '' 388 s = '*** %s was kicked by %s%s' % (msg.args[1], msg.nick, kickmsg) 389 elif msg.command == 'MODE': 390 s = '*** %s sets mode: %s' % (nickorprefix(), ' '.join(msg.args)) 391 elif msg.command == 'QUIT': 392 if msg.args: 393 quitmsg = ' (%s)' % msg.args[0] 394 else: 395 quitmsg = '' 396 s = '*** %s <%s> has quit IRC%s' % (msg.nick, msg.prefix, quitmsg) 397 elif msg.command == 'TOPIC': 398 s = '*** %s changes topic to %s' % (nickorprefix(), msg.args[1]) 399 elif msg.command == 'NICK': 400 s = '*** %s is now known as %s' % (msg.nick, msg.args[0]) 401 else: 402 s = utils.str.format('--- Unknown command %q', ' '.join(msg.args)) 403 at = msg.tagged('receivedAt') 404 if timestampFormat and at: 405 s = '%s %s' % (time.strftime(timestampFormat, time.localtime(at)), s) 406 return s 407 408### 409# Various IrcMsg functions 410### 411 412isNick = ircutils.isNick 413areNicks = ircutils.areNicks 414isChannel = ircutils.isChannel 415areChannels = ircutils.areChannels 416areReceivers = ircutils.areReceivers 417isUserHostmask = ircutils.isUserHostmask 418 419def pong(payload, prefix='', msg=None): 420 """Takes a payload and returns the proper PONG IrcMsg.""" 421 if conf.supybot.protocols.irc.strictRfc(): 422 assert payload, 'PONG requires a payload' 423 if msg and not prefix: 424 prefix = msg.prefix 425 return IrcMsg(prefix=prefix, command='PONG', args=(payload,), msg=msg) 426 427def ping(payload, prefix='', msg=None): 428 """Takes a payload and returns the proper PING IrcMsg.""" 429 if conf.supybot.protocols.irc.strictRfc(): 430 assert payload, 'PING requires a payload' 431 if msg and not prefix: 432 prefix = msg.prefix 433 return IrcMsg(prefix=prefix, command='PING', args=(payload,), msg=msg) 434 435def op(channel, nick, prefix='', msg=None): 436 """Returns a MODE to op nick on channel.""" 437 if conf.supybot.protocols.irc.strictRfc(): 438 assert isChannel(channel), repr(channel) 439 assert isNick(nick), repr(nick) 440 if msg and not prefix: 441 prefix = msg.prefix 442 return IrcMsg(prefix=prefix, command='MODE', 443 args=(channel, '+o', nick), msg=msg) 444 445def ops(channel, nicks, prefix='', msg=None): 446 """Returns a MODE to op each of nicks on channel.""" 447 if conf.supybot.protocols.irc.strictRfc(): 448 assert isChannel(channel), repr(channel) 449 assert nicks, 'Nicks must not be empty.' 450 assert all(isNick, nicks), nicks 451 if msg and not prefix: 452 prefix = msg.prefix 453 return IrcMsg(prefix=prefix, command='MODE', 454 args=(channel, '+' + ('o'*len(nicks))) + tuple(nicks), 455 msg=msg) 456 457def deop(channel, nick, prefix='', msg=None): 458 """Returns a MODE to deop nick on channel.""" 459 if conf.supybot.protocols.irc.strictRfc(): 460 assert isChannel(channel), repr(channel) 461 assert isNick(nick), repr(nick) 462 if msg and not prefix: 463 prefix = msg.prefix 464 return IrcMsg(prefix=prefix, command='MODE', 465 args=(channel, '-o', nick), msg=msg) 466 467def deops(channel, nicks, prefix='', msg=None): 468 """Returns a MODE to deop each of nicks on channel.""" 469 if conf.supybot.protocols.irc.strictRfc(): 470 assert isChannel(channel), repr(channel) 471 assert nicks, 'Nicks must not be empty.' 472 assert all(isNick, nicks), nicks 473 if msg and not prefix: 474 prefix = msg.prefix 475 return IrcMsg(prefix=prefix, command='MODE', msg=msg, 476 args=(channel, '-' + ('o'*len(nicks))) + tuple(nicks)) 477 478def halfop(channel, nick, prefix='', msg=None): 479 """Returns a MODE to halfop nick on channel.""" 480 if conf.supybot.protocols.irc.strictRfc(): 481 assert isChannel(channel), repr(channel) 482 assert isNick(nick), repr(nick) 483 if msg and not prefix: 484 prefix = msg.prefix 485 return IrcMsg(prefix=prefix, command='MODE', 486 args=(channel, '+h', nick), msg=msg) 487 488def halfops(channel, nicks, prefix='', msg=None): 489 """Returns a MODE to halfop each of nicks on channel.""" 490 if conf.supybot.protocols.irc.strictRfc(): 491 assert isChannel(channel), repr(channel) 492 assert nicks, 'Nicks must not be empty.' 493 assert all(isNick, nicks), nicks 494 if msg and not prefix: 495 prefix = msg.prefix 496 return IrcMsg(prefix=prefix, command='MODE', msg=msg, 497 args=(channel, '+' + ('h'*len(nicks))) + tuple(nicks)) 498 499def dehalfop(channel, nick, prefix='', msg=None): 500 """Returns a MODE to dehalfop nick on channel.""" 501 if conf.supybot.protocols.irc.strictRfc(): 502 assert isChannel(channel), repr(channel) 503 assert isNick(nick), repr(nick) 504 if msg and not prefix: 505 prefix = msg.prefix 506 return IrcMsg(prefix=prefix, command='MODE', 507 args=(channel, '-h', nick), msg=msg) 508 509def dehalfops(channel, nicks, prefix='', msg=None): 510 """Returns a MODE to dehalfop each of nicks on channel.""" 511 if conf.supybot.protocols.irc.strictRfc(): 512 assert isChannel(channel), repr(channel) 513 assert nicks, 'Nicks must not be empty.' 514 assert all(isNick, nicks), nicks 515 if msg and not prefix: 516 prefix = msg.prefix 517 return IrcMsg(prefix=prefix, command='MODE', msg=msg, 518 args=(channel, '-' + ('h'*len(nicks))) + tuple(nicks)) 519 520def voice(channel, nick, prefix='', msg=None): 521 """Returns a MODE to voice nick on channel.""" 522 if conf.supybot.protocols.irc.strictRfc(): 523 assert isChannel(channel), repr(channel) 524 assert isNick(nick), repr(nick) 525 if msg and not prefix: 526 prefix = msg.prefix 527 return IrcMsg(prefix=prefix, command='MODE', 528 args=(channel, '+v', nick), msg=msg) 529 530def voices(channel, nicks, prefix='', msg=None): 531 """Returns a MODE to voice each of nicks on channel.""" 532 if conf.supybot.protocols.irc.strictRfc(): 533 assert isChannel(channel), repr(channel) 534 assert nicks, 'Nicks must not be empty.' 535 assert all(isNick, nicks) 536 if msg and not prefix: 537 prefix = msg.prefix 538 return IrcMsg(prefix=prefix, command='MODE', msg=msg, 539 args=(channel, '+' + ('v'*len(nicks))) + tuple(nicks)) 540 541def devoice(channel, nick, prefix='', msg=None): 542 """Returns a MODE to devoice nick on channel.""" 543 if conf.supybot.protocols.irc.strictRfc(): 544 assert isChannel(channel), repr(channel) 545 assert isNick(nick), repr(nick) 546 if msg and not prefix: 547 prefix = msg.prefix 548 return IrcMsg(prefix=prefix, command='MODE', 549 args=(channel, '-v', nick), msg=msg) 550 551def devoices(channel, nicks, prefix='', msg=None): 552 """Returns a MODE to devoice each of nicks on channel.""" 553 if conf.supybot.protocols.irc.strictRfc(): 554 assert isChannel(channel), repr(channel) 555 assert nicks, 'Nicks must not be empty.' 556 assert all(isNick, nicks), nicks 557 if msg and not prefix: 558 prefix = msg.prefix 559 return IrcMsg(prefix=prefix, command='MODE', msg=msg, 560 args=(channel, '-' + ('v'*len(nicks))) + tuple(nicks)) 561 562def ban(channel, hostmask, exception='', prefix='', msg=None): 563 """Returns a MODE to ban nick on channel.""" 564 if conf.supybot.protocols.irc.strictRfc(): 565 assert isChannel(channel), repr(channel) 566 assert isUserHostmask(hostmask), repr(hostmask) 567 modes = [('+b', hostmask)] 568 if exception: 569 modes.append(('+e', exception)) 570 if msg and not prefix: 571 prefix = msg.prefix 572 return IrcMsg(prefix=prefix, command='MODE', 573 args=[channel] + ircutils.joinModes(modes), msg=msg) 574 575def bans(channel, hostmasks, exceptions=(), prefix='', msg=None): 576 """Returns a MODE to ban each of nicks on channel.""" 577 if conf.supybot.protocols.irc.strictRfc(): 578 assert isChannel(channel), repr(channel) 579 assert all(isUserHostmask, hostmasks), hostmasks 580 modes = [('+b', s) for s in hostmasks] + [('+e', s) for s in exceptions] 581 if msg and not prefix: 582 prefix = msg.prefix 583 return IrcMsg(prefix=prefix, command='MODE', 584 args=[channel] + ircutils.joinModes(modes), msg=msg) 585 586def unban(channel, hostmask, prefix='', msg=None): 587 """Returns a MODE to unban nick on channel.""" 588 if conf.supybot.protocols.irc.strictRfc(): 589 assert isChannel(channel), repr(channel) 590 assert isUserHostmask(hostmask), repr(hostmask) 591 if msg and not prefix: 592 prefix = msg.prefix 593 return IrcMsg(prefix=prefix, command='MODE', 594 args=(channel, '-b', hostmask), msg=msg) 595 596def unbans(channel, hostmasks, prefix='', msg=None): 597 """Returns a MODE to unban each of nicks on channel.""" 598 if conf.supybot.protocols.irc.strictRfc(): 599 assert isChannel(channel), repr(channel) 600 assert all(isUserHostmask, hostmasks), hostmasks 601 modes = [('-b', s) for s in hostmasks] 602 if msg and not prefix: 603 prefix = msg.prefix 604 return IrcMsg(prefix=prefix, command='MODE', 605 args=[channel] + ircutils.joinModes(modes), msg=msg) 606 607def kick(channel, nick, s='', prefix='', msg=None): 608 """Returns a KICK to kick nick from channel with the message msg.""" 609 if conf.supybot.protocols.irc.strictRfc(): 610 assert isChannel(channel), repr(channel) 611 assert isNick(nick), repr(nick) 612 if msg and not prefix: 613 prefix = msg.prefix 614 if minisix.PY2 and isinstance(s, unicode): 615 s = s.encode('utf8') 616 assert isinstance(s, str) 617 if s: 618 return IrcMsg(prefix=prefix, command='KICK', 619 args=(channel, nick, s), msg=msg) 620 else: 621 return IrcMsg(prefix=prefix, command='KICK', 622 args=(channel, nick), msg=msg) 623 624def kicks(channels, nicks, s='', prefix='', msg=None): 625 """Returns a KICK to kick each of nicks from channel with the message msg. 626 """ 627 if isinstance(channels, str): # Backward compatibility 628 channels = [channels] 629 if conf.supybot.protocols.irc.strictRfc(): 630 assert areChannels(channels), repr(channels) 631 assert areNicks(nicks), repr(nicks) 632 if msg and not prefix: 633 prefix = msg.prefix 634 if minisix.PY2 and isinstance(s, unicode): 635 s = s.encode('utf8') 636 assert isinstance(s, str) 637 if s: 638 for channel in channels: 639 return IrcMsg(prefix=prefix, command='KICK', 640 args=(channel, ','.join(nicks), s), msg=msg) 641 else: 642 for channel in channels: 643 return IrcMsg(prefix=prefix, command='KICK', 644 args=(channel, ','.join(nicks)), msg=msg) 645 646def privmsg(recipient, s, prefix='', msg=None): 647 """Returns a PRIVMSG to recipient with the message msg.""" 648 if conf.supybot.protocols.irc.strictRfc(): 649 assert (areReceivers(recipient)), repr(recipient) 650 assert s, 's must not be empty.' 651 if minisix.PY2 and isinstance(s, unicode): 652 s = s.encode('utf8') 653 assert isinstance(s, str) 654 if msg and not prefix: 655 prefix = msg.prefix 656 return IrcMsg(prefix=prefix, command='PRIVMSG', 657 args=(recipient, s), msg=msg) 658 659def dcc(recipient, kind, *args, **kwargs): 660 # Stupid Python won't allow (recipient, kind, *args, prefix=''), so we have 661 # to use the **kwargs form. Blech. 662 assert isNick(recipient), 'Can\'t DCC a channel.' 663 kind = kind.upper() 664 assert kind in ('SEND', 'CHAT', 'RESUME', 'ACCEPT'), 'Invalid DCC command.' 665 args = (kind,) + args 666 return IrcMsg(prefix=kwargs.get('prefix', ''), command='PRIVMSG', 667 args=(recipient, ' '.join(args))) 668 669def action(recipient, s, prefix='', msg=None): 670 """Returns a PRIVMSG ACTION to recipient with the message msg.""" 671 if conf.supybot.protocols.irc.strictRfc(): 672 assert (isChannel(recipient) or isNick(recipient)), repr(recipient) 673 if msg and not prefix: 674 prefix = msg.prefix 675 return IrcMsg(prefix=prefix, command='PRIVMSG', 676 args=(recipient, '\x01ACTION %s\x01' % s), msg=msg) 677 678def notice(recipient, s, prefix='', msg=None): 679 """Returns a NOTICE to recipient with the message msg.""" 680 if conf.supybot.protocols.irc.strictRfc(): 681 assert areReceivers(recipient), repr(recipient) 682 assert s, 'msg must not be empty.' 683 if minisix.PY2 and isinstance(s, unicode): 684 s = s.encode('utf8') 685 assert isinstance(s, str) 686 if msg and not prefix: 687 prefix = msg.prefix 688 return IrcMsg(prefix=prefix, command='NOTICE', args=(recipient, s), msg=msg) 689 690def join(channel, key=None, prefix='', msg=None): 691 """Returns a JOIN to a channel""" 692 if conf.supybot.protocols.irc.strictRfc(): 693 assert areChannels(channel), repr(channel) 694 if msg and not prefix: 695 prefix = msg.prefix 696 if key is None: 697 return IrcMsg(prefix=prefix, command='JOIN', args=(channel,), msg=msg) 698 else: 699 if conf.supybot.protocols.irc.strictRfc(): 700 chars = '\x00\r\n\f\t\v ' 701 assert not any([(ord(x) >= 128 or x in chars) for x in key]) 702 return IrcMsg(prefix=prefix, command='JOIN', 703 args=(channel, key), msg=msg) 704 705def joins(channels, keys=None, prefix='', msg=None): 706 """Returns a JOIN to each of channels.""" 707 if conf.supybot.protocols.irc.strictRfc(): 708 assert all(isChannel, channels), channels 709 if msg and not prefix: 710 prefix = msg.prefix 711 if keys is None: 712 keys = [] 713 assert len(keys) <= len(channels), 'Got more keys than channels.' 714 if not keys: 715 return IrcMsg(prefix=prefix, 716 command='JOIN', 717 args=(','.join(channels),), msg=msg) 718 else: 719 if conf.supybot.protocols.irc.strictRfc(): 720 chars = '\x00\r\n\f\t\v ' 721 for key in keys: 722 assert not any([(ord(x) >= 128 or x in chars) for x in key]) 723 return IrcMsg(prefix=prefix, 724 command='JOIN', 725 args=(','.join(channels), ','.join(keys)), msg=msg) 726 727def part(channel, s='', prefix='', msg=None): 728 """Returns a PART from channel with the message msg.""" 729 if conf.supybot.protocols.irc.strictRfc(): 730 assert isChannel(channel), repr(channel) 731 if msg and not prefix: 732 prefix = msg.prefix 733 if minisix.PY2 and isinstance(s, unicode): 734 s = s.encode('utf8') 735 assert isinstance(s, str) 736 if s: 737 return IrcMsg(prefix=prefix, command='PART', 738 args=(channel, s), msg=msg) 739 else: 740 return IrcMsg(prefix=prefix, command='PART', 741 args=(channel,), msg=msg) 742 743def parts(channels, s='', prefix='', msg=None): 744 """Returns a PART from each of channels with the message msg.""" 745 if conf.supybot.protocols.irc.strictRfc(): 746 assert all(isChannel, channels), channels 747 if msg and not prefix: 748 prefix = msg.prefix 749 if minisix.PY2 and isinstance(s, unicode): 750 s = s.encode('utf8') 751 assert isinstance(s, str) 752 if s: 753 return IrcMsg(prefix=prefix, command='PART', 754 args=(','.join(channels), s), msg=msg) 755 else: 756 return IrcMsg(prefix=prefix, command='PART', 757 args=(','.join(channels),), msg=msg) 758 759def quit(s='', prefix='', msg=None): 760 """Returns a QUIT with the message msg.""" 761 if msg and not prefix: 762 prefix = msg.prefix 763 if s: 764 return IrcMsg(prefix=prefix, command='QUIT', args=(s,), msg=msg) 765 else: 766 return IrcMsg(prefix=prefix, command='QUIT', msg=msg) 767 768def topic(channel, topic=None, prefix='', msg=None): 769 """Returns a TOPIC for channel with the topic topic.""" 770 if conf.supybot.protocols.irc.strictRfc(): 771 assert isChannel(channel), repr(channel) 772 if msg and not prefix: 773 prefix = msg.prefix 774 if topic is None: 775 return IrcMsg(prefix=prefix, command='TOPIC', 776 args=(channel,), msg=msg) 777 else: 778 if minisix.PY2 and isinstance(topic, unicode): 779 topic = topic.encode('utf8') 780 assert isinstance(topic, str) 781 return IrcMsg(prefix=prefix, command='TOPIC', 782 args=(channel, topic), msg=msg) 783 784def nick(nick, prefix='', msg=None): 785 """Returns a NICK with nick nick.""" 786 if conf.supybot.protocols.irc.strictRfc(): 787 assert isNick(nick), repr(nick) 788 if msg and not prefix: 789 prefix = msg.prefix 790 return IrcMsg(prefix=prefix, command='NICK', args=(nick,), msg=msg) 791 792def user(ident, user, prefix='', msg=None): 793 """Returns a USER with ident ident and user user.""" 794 if conf.supybot.protocols.irc.strictRfc(): 795 assert '\x00' not in ident and \ 796 '\r' not in ident and \ 797 '\n' not in ident and \ 798 ' ' not in ident and \ 799 '@' not in ident 800 if msg and not prefix: 801 prefix = msg.prefix 802 return IrcMsg(prefix=prefix, command='USER', 803 args=(ident, '0', '*', user), msg=msg) 804 805def who(hostmaskOrChannel, prefix='', msg=None, args=()): 806 """Returns a WHO for the hostmask or channel hostmaskOrChannel.""" 807 if conf.supybot.protocols.irc.strictRfc(): 808 assert isChannel(hostmaskOrChannel) or \ 809 isUserHostmask(hostmaskOrChannel), repr(hostmaskOrChannel) 810 if msg and not prefix: 811 prefix = msg.prefix 812 return IrcMsg(prefix=prefix, command='WHO', 813 args=(hostmaskOrChannel,) + args, msg=msg) 814 815def _whois(COMMAND, nick, mask='', prefix='', msg=None): 816 """Returns a WHOIS for nick.""" 817 if conf.supybot.protocols.irc.strictRfc(): 818 assert areNicks(nick), repr(nick) 819 if msg and not prefix: 820 prefix = msg.prefix 821 args = (nick,) 822 if mask: 823 args = (nick, mask) 824 return IrcMsg(prefix=prefix, command=COMMAND, args=args, msg=msg) 825whois = functools.partial(_whois, 'WHOIS') 826whowas = functools.partial(_whois, 'WHOWAS') 827 828def names(channel=None, prefix='', msg=None): 829 if conf.supybot.protocols.irc.strictRfc(): 830 assert areChannels(channel) 831 if msg and not prefix: 832 prefix = msg.prefix 833 if channel is not None: 834 return IrcMsg(prefix=prefix, command='NAMES', args=(channel,), msg=msg) 835 else: 836 return IrcMsg(prefix=prefix, command='NAMES', msg=msg) 837 838def mode(channel, args=(), prefix='', msg=None): 839 if msg and not prefix: 840 prefix = msg.prefix 841 if isinstance(args, minisix.string_types): 842 args = (args,) 843 else: 844 args = tuple(map(str, args)) 845 return IrcMsg(prefix=prefix, command='MODE', args=(channel,)+args, msg=msg) 846 847def modes(channel, args=(), prefix='', msg=None): 848 """Returns a MODE message for the channel for all the (mode, targetOrNone) 2-tuples in 'args'.""" 849 if conf.supybot.protocols.irc.strictRfc(): 850 assert isChannel(channel), repr(channel) 851 modes = args 852 if msg and not prefix: 853 prefix = msg.prefix 854 return IrcMsg(prefix=prefix, command='MODE', 855 args=[channel] + ircutils.joinModes(modes), msg=msg) 856 857def limit(channel, limit, prefix='', msg=None): 858 return mode(channel, ['+l', limit], prefix=prefix, msg=msg) 859 860def unlimit(channel, limit, prefix='', msg=None): 861 return mode(channel, ['-l', limit], prefix=prefix, msg=msg) 862 863def invite(nick, channel, prefix='', msg=None): 864 """Returns an INVITE for nick.""" 865 if conf.supybot.protocols.irc.strictRfc(): 866 assert isNick(nick), repr(nick) 867 if msg and not prefix: 868 prefix = msg.prefix 869 return IrcMsg(prefix=prefix, command='INVITE', 870 args=(nick, channel), msg=msg) 871 872def password(password, prefix='', msg=None): 873 """Returns a PASS command for accessing a server.""" 874 if conf.supybot.protocols.irc.strictRfc(): 875 assert password, 'password must not be empty.' 876 if msg and not prefix: 877 prefix = msg.prefix 878 return IrcMsg(prefix=prefix, command='PASS', args=(password,), msg=msg) 879 880def ison(nick, prefix='', msg=None): 881 if conf.supybot.protocols.irc.strictRfc(): 882 assert isNick(nick), repr(nick) 883 if msg and not prefix: 884 prefix = msg.prefix 885 return IrcMsg(prefix=prefix, command='ISON', args=(nick,), msg=msg) 886 887def monitor(subcommand, nicks=None, prefix='', msg=None): 888 if conf.supybot.protocols.irc.strictRfc(): 889 for nick in nicks: 890 assert isNick(nick), repr(nick) 891 assert subcommand in '+-CLS' 892 if subcommand in 'CLS': 893 assert nicks is None 894 if msg and not prefix: 895 prefix = msg.prefix 896 if not isinstance(nicks, str): 897 nicks = ','.join(nicks) 898 return IrcMsg(prefix=prefix, command='MONITOR', args=(subcommand, nicks), 899 msg=msg) 900 901 902def error(s, msg=None): 903 return IrcMsg(command='ERROR', args=(s,), msg=msg) 904 905# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: 906