2003-03-12 07:26:59 +01:00
|
|
|
#!/usr/bin/env python
|
|
|
|
|
|
|
|
###
|
|
|
|
# Copyright (c) 2002, Jeremiah Fincher
|
|
|
|
# All rights reserved.
|
|
|
|
#
|
|
|
|
# Redistribution and use in source and binary forms, with or without
|
|
|
|
# modification, are permitted provided that the following conditions are met:
|
|
|
|
#
|
|
|
|
# * Redistributions of source code must retain the above copyright notice,
|
|
|
|
# this list of conditions, and the following disclaimer.
|
|
|
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
|
|
|
# this list of conditions, and the following disclaimer in the
|
|
|
|
# documentation and/or other materials provided with the distribution.
|
|
|
|
# * Neither the name of the author of this software nor the name of
|
|
|
|
# contributors to this software may be used to endorse or promote products
|
|
|
|
# derived from this software without specific prior written consent.
|
|
|
|
#
|
|
|
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
|
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
|
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
|
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
|
|
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
|
|
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
|
|
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
|
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
|
|
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
|
|
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
|
|
# POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
###
|
|
|
|
|
2003-11-25 09:38:19 +01:00
|
|
|
__revision__ = "$Id$"
|
|
|
|
|
2003-10-05 14:47:19 +02:00
|
|
|
import fix
|
2003-03-12 07:26:59 +01:00
|
|
|
|
|
|
|
import copy
|
2003-07-31 08:20:58 +02:00
|
|
|
import sets
|
2003-03-12 07:26:59 +01:00
|
|
|
import time
|
2003-11-19 17:22:46 +01:00
|
|
|
from itertools import imap, chain
|
2003-03-12 07:26:59 +01:00
|
|
|
|
2003-11-26 19:21:12 +01:00
|
|
|
import log
|
2003-03-12 07:26:59 +01:00
|
|
|
import conf
|
2003-09-04 22:42:37 +02:00
|
|
|
import utils
|
2003-03-12 07:26:59 +01:00
|
|
|
import world
|
|
|
|
import ircdb
|
|
|
|
import ircmsgs
|
|
|
|
import ircutils
|
2003-11-19 17:22:46 +01:00
|
|
|
from structures import queue, smallqueue, RingBuffer
|
2003-03-12 07:26:59 +01:00
|
|
|
|
|
|
|
###
|
|
|
|
# The base class for a callback to be registered with an Irc object. Shows
|
|
|
|
# the required interface for callbacks -- name(),
|
|
|
|
# inFilter(irc, msg), outFilter(irc, msg), and __call__(irc, msg) [used so
|
|
|
|
# functions can be used as callbacks conceivable, and so if refactoring ever
|
|
|
|
# changes the nature of the callbacks from classes to functions, syntactical
|
|
|
|
# changes elsewhere won't be required.
|
|
|
|
###
|
|
|
|
|
2003-05-29 19:03:42 +02:00
|
|
|
class IrcCommandDispatcher(object):
|
|
|
|
"""Base class for classes that must dispatch on a command."""
|
|
|
|
def dispatchCommand(self, command):
|
2003-10-04 14:17:31 +02:00
|
|
|
"""Given a string 'command', dispatches to doCommand."""
|
2003-05-29 19:03:42 +02:00
|
|
|
return getattr(self, 'do' + command.capitalize(), None)
|
|
|
|
|
2003-09-07 08:08:16 +02:00
|
|
|
|
2003-05-29 19:03:42 +02:00
|
|
|
class IrcCallback(IrcCommandDispatcher):
|
2003-03-12 07:26:59 +01:00
|
|
|
"""Base class for standard callbacks.
|
|
|
|
|
|
|
|
Callbacks derived from this class should have methods of the form
|
|
|
|
"doCommand" -- doPrivmsg, doNick, do433, etc. These will be called
|
|
|
|
on matching messages.
|
|
|
|
"""
|
2003-10-04 14:17:31 +02:00
|
|
|
# priority determines the order in which callbacks are called. Lower
|
|
|
|
# numbers mean *higher* priority (like nice values in *nix). Higher
|
|
|
|
# priority means the callback is called *earlier* on the inFilter chain,
|
|
|
|
# *earlier* on the __call__ chain, and *later* on the outFilter chain.
|
2003-09-05 21:28:04 +02:00
|
|
|
priority = 99
|
2003-03-12 07:26:59 +01:00
|
|
|
def name(self):
|
2003-10-04 14:17:31 +02:00
|
|
|
"""Returns the name of the callback."""
|
2003-03-12 07:26:59 +01:00
|
|
|
return self.__class__.__name__
|
|
|
|
|
|
|
|
def inFilter(self, irc, msg):
|
2003-10-04 14:17:31 +02:00
|
|
|
"""Used for filtering/modifying messages as they're entering.
|
|
|
|
|
|
|
|
ircmsgs.IrcMsg objects are immutable, so this method is expected to
|
|
|
|
return another ircmsgs.IrcMsg object. Obviously the same IrcMsg
|
|
|
|
can be returned.
|
|
|
|
"""
|
2003-03-12 07:26:59 +01:00
|
|
|
return msg
|
|
|
|
|
|
|
|
def outFilter(self, irc, msg):
|
2003-10-04 14:17:31 +02:00
|
|
|
"""Used for filtering/modifying messages as they're leaving.
|
|
|
|
|
|
|
|
As with inFilter, an IrcMsg is returned.
|
|
|
|
"""
|
2003-03-12 07:26:59 +01:00
|
|
|
return msg
|
|
|
|
|
|
|
|
def __call__(self, irc, msg):
|
2003-10-04 14:17:31 +02:00
|
|
|
"""Used for handling each message."""
|
2003-05-29 19:03:42 +02:00
|
|
|
method = self.dispatchCommand(msg.command)
|
|
|
|
if method is not None:
|
2003-03-28 03:33:40 +01:00
|
|
|
try:
|
|
|
|
method(irc, msg)
|
2003-09-07 08:08:16 +02:00
|
|
|
except Exception, e:
|
2003-11-26 19:21:12 +01:00
|
|
|
s = 'Exception caught in generic IrcCallback.__call__:'
|
|
|
|
log.exception(s)
|
2003-03-12 07:26:59 +01:00
|
|
|
|
|
|
|
def reset(self):
|
2003-10-04 14:17:31 +02:00
|
|
|
"""Resets the callback. Called when reconnected to the server."""
|
2003-03-12 07:26:59 +01:00
|
|
|
pass
|
|
|
|
|
|
|
|
def die(self):
|
2003-10-04 14:17:31 +02:00
|
|
|
"""Makes the callback die. Called when the parent Irc object dies."""
|
2003-03-12 07:26:59 +01:00
|
|
|
pass
|
|
|
|
|
|
|
|
###
|
|
|
|
# Basic queue for IRC messages. It doesn't presently (but should at some
|
|
|
|
# later point) reorder messages based on priority or penalty calculations.
|
|
|
|
###
|
2003-10-01 13:11:49 +02:00
|
|
|
_high = sets.ImmutableSet(['MODE', 'KICK', 'PONG', 'NICK', 'PASS'])
|
|
|
|
_low = sets.ImmutableSet(['PRIVMSG', 'PING', 'WHO', 'NOTICE'])
|
2003-03-12 07:26:59 +01:00
|
|
|
class IrcMsgQueue(object):
|
|
|
|
"""Class for a queue of IrcMsgs. Eventually, it should be smart.
|
2003-05-29 19:03:42 +02:00
|
|
|
|
|
|
|
Probably smarter than it is now, though it's gotten quite a bit smarter
|
|
|
|
than it originally was. A method to "score" methods, and a heapq to
|
|
|
|
maintain a priority queue of the messages would be the ideal way to do
|
|
|
|
intelligent queueing.
|
2003-10-04 14:17:31 +02:00
|
|
|
|
|
|
|
As it stands, however, we simple keep track of 'high priority' messages,
|
|
|
|
'low priority' messages, and normal messages, and just make sure to return
|
|
|
|
the 'high priority' ones before the normal ones before the 'low priority'
|
|
|
|
ones.
|
2003-03-12 07:26:59 +01:00
|
|
|
"""
|
2003-04-03 11:59:57 +02:00
|
|
|
__slots__ = ('msgs', 'highpriority', 'normal', 'lowpriority')
|
2003-11-19 17:22:46 +01:00
|
|
|
def __init__(self, iterable=()):
|
2003-03-12 07:26:59 +01:00
|
|
|
self.reset()
|
2003-11-19 17:22:46 +01:00
|
|
|
for msg in iterable:
|
|
|
|
self.enqueue(msg)
|
2003-03-12 07:26:59 +01:00
|
|
|
|
|
|
|
def reset(self):
|
2003-10-04 14:17:31 +02:00
|
|
|
"""Clears the queue."""
|
2003-10-09 06:01:27 +02:00
|
|
|
self.highpriority = smallqueue()
|
|
|
|
self.normal = smallqueue()
|
|
|
|
self.lowpriority = smallqueue()
|
2003-07-31 08:20:58 +02:00
|
|
|
self.msgs = sets.Set()
|
2003-03-12 07:26:59 +01:00
|
|
|
|
2003-04-03 11:59:57 +02:00
|
|
|
def enqueue(self, msg):
|
2003-10-04 14:17:31 +02:00
|
|
|
"""Enqueues a given message."""
|
2003-04-01 00:21:42 +02:00
|
|
|
if msg in self.msgs:
|
|
|
|
if not world.startup:
|
2003-11-26 19:21:12 +01:00
|
|
|
log.info('Not adding msg %s to queue' % msg)
|
2003-03-12 07:26:59 +01:00
|
|
|
else:
|
|
|
|
self.msgs.add(msg)
|
2003-10-01 13:11:49 +02:00
|
|
|
if msg.command in _high:
|
2003-04-03 11:59:57 +02:00
|
|
|
self.highpriority.enqueue(msg)
|
2003-10-01 13:11:49 +02:00
|
|
|
elif msg.command in _low:
|
2003-04-02 11:07:52 +02:00
|
|
|
self.lowpriority.enqueue(msg)
|
2003-04-03 11:59:57 +02:00
|
|
|
else:
|
|
|
|
self.normal.enqueue(msg)
|
2003-03-12 07:26:59 +01:00
|
|
|
|
2003-04-03 11:59:57 +02:00
|
|
|
def dequeue(self):
|
2003-10-04 14:17:31 +02:00
|
|
|
"""Dequeues a given message."""
|
2003-11-26 19:21:12 +01:00
|
|
|
msg = None
|
2003-04-03 11:59:57 +02:00
|
|
|
if self.highpriority:
|
|
|
|
msg = self.highpriority.dequeue()
|
|
|
|
elif self.normal:
|
|
|
|
msg = self.normal.dequeue()
|
2003-04-02 11:07:52 +02:00
|
|
|
elif self.lowpriority:
|
|
|
|
msg = self.lowpriority.dequeue()
|
|
|
|
if msg:
|
2003-11-26 19:21:12 +01:00
|
|
|
try:
|
|
|
|
self.msgs.remove(msg)
|
|
|
|
except KeyError:
|
|
|
|
s = 'Odd, dequeuing a message that\'s not in self.msgs.'
|
|
|
|
log.warning(s)
|
2003-04-02 11:07:52 +02:00
|
|
|
return msg
|
2003-03-12 07:26:59 +01:00
|
|
|
|
2003-04-03 11:59:57 +02:00
|
|
|
def __nonzero__(self):
|
2003-07-31 08:20:58 +02:00
|
|
|
return bool(self.highpriority or self.normal or self.lowpriority)
|
2003-03-12 07:26:59 +01:00
|
|
|
|
2003-11-19 17:22:46 +01:00
|
|
|
def __len__(self):
|
|
|
|
return sum(imap(len,[self.highpriority,self.lowpriority,self.normal]))
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
name = self.__class__.__name__
|
|
|
|
return '%s(%r)' % (name, list(chain(self.highpriority,
|
|
|
|
self.normal,
|
|
|
|
self.lowpriority)))
|
|
|
|
__str__ = __repr__
|
|
|
|
|
2003-03-12 07:26:59 +01:00
|
|
|
|
|
|
|
###
|
|
|
|
# Maintains the state of IRC connection -- the most recent messages, the
|
|
|
|
# status of various modes (especially ops/halfops/voices) in channels, etc.
|
|
|
|
###
|
2003-10-01 13:15:31 +02:00
|
|
|
class ChannelState(object):
|
2003-03-31 11:23:32 +02:00
|
|
|
__slots__ = ('users', 'ops', 'halfops', 'voices', 'topic')
|
2003-03-12 07:26:59 +01:00
|
|
|
def __init__(self):
|
|
|
|
self.topic = ''
|
2003-11-04 06:50:19 +01:00
|
|
|
self.users = ircutils.IrcSet()
|
|
|
|
self.ops = ircutils.IrcSet()
|
|
|
|
self.halfops = ircutils.IrcSet()
|
|
|
|
self.voices = ircutils.IrcSet()
|
2003-03-12 07:26:59 +01:00
|
|
|
|
2003-04-29 09:37:54 +02:00
|
|
|
def addUser(self, user):
|
2003-10-04 14:17:31 +02:00
|
|
|
"Adds a given user to the ChannelState. Power prefixes are handled."
|
2003-06-18 09:19:27 +02:00
|
|
|
nick = user.lstrip('@%+')
|
2003-12-02 12:40:46 +01:00
|
|
|
if not nick:
|
|
|
|
return
|
2003-06-02 09:21:08 +02:00
|
|
|
while user and user[0] in '@%+':
|
2003-04-29 09:37:54 +02:00
|
|
|
(marker, user) = (user[0], user[1:])
|
|
|
|
if marker == '@':
|
2003-05-29 19:03:42 +02:00
|
|
|
self.ops.add(nick)
|
2003-04-29 09:37:54 +02:00
|
|
|
elif marker == '%':
|
2003-05-29 19:03:42 +02:00
|
|
|
self.halfops.add(nick)
|
2003-04-29 09:37:54 +02:00
|
|
|
elif marker == '+':
|
2003-05-29 19:03:42 +02:00
|
|
|
self.voices.add(nick)
|
|
|
|
self.users.add(nick)
|
|
|
|
|
|
|
|
def replaceUser(self, oldNick, newNick):
|
2003-10-04 14:17:31 +02:00
|
|
|
"""Changes the user oldNick to newNick; used for NICK changes."""
|
2003-05-29 19:03:42 +02:00
|
|
|
# Note that this doesn't have to have the sigil (@%+) that users
|
|
|
|
# have to have for addUser; it just changes the name of the user
|
|
|
|
# without changing any of his categories.
|
|
|
|
for s in (self.users, self.ops, self.halfops, self.voices):
|
|
|
|
if oldNick in s:
|
2003-10-16 15:27:40 +02:00
|
|
|
s.remove(oldNick)
|
2003-05-29 19:03:42 +02:00
|
|
|
s.add(newNick)
|
2003-04-29 09:37:54 +02:00
|
|
|
|
2003-03-12 07:26:59 +01:00
|
|
|
def removeUser(self, user):
|
2003-10-04 14:17:31 +02:00
|
|
|
"""Removes a given user from the channel."""
|
2003-04-05 12:27:02 +02:00
|
|
|
self.users.discard(user)
|
|
|
|
self.ops.discard(user)
|
|
|
|
self.halfops.discard(user)
|
|
|
|
self.voices.discard(user)
|
2003-03-12 07:26:59 +01:00
|
|
|
|
2003-04-09 20:57:25 +02:00
|
|
|
def __getstate__(self):
|
2003-05-29 19:03:42 +02:00
|
|
|
return [getattr(self, name) for name in self.__slots__]
|
2003-04-29 09:37:54 +02:00
|
|
|
|
|
|
|
def __setstate__(self, t):
|
|
|
|
for (name, value) in zip(self.__slots__, t):
|
|
|
|
setattr(self, name, value)
|
2003-04-09 20:57:25 +02:00
|
|
|
|
2003-04-29 09:37:54 +02:00
|
|
|
def __eq__(self, other):
|
|
|
|
ret = True
|
|
|
|
for name in self.__slots__:
|
|
|
|
ret = ret and getattr(self, name) == getattr(other, name)
|
|
|
|
return ret
|
2003-04-09 20:57:25 +02:00
|
|
|
|
2003-04-29 09:37:54 +02:00
|
|
|
def __ne__(self, other):
|
2003-05-29 19:03:42 +02:00
|
|
|
# This shouldn't even be necessary, grr...
|
2003-04-29 09:37:54 +02:00
|
|
|
return not self == other
|
2003-04-09 20:57:25 +02:00
|
|
|
|
2003-05-29 19:03:42 +02:00
|
|
|
class IrcState(IrcCommandDispatcher):
|
2003-03-12 07:26:59 +01:00
|
|
|
"""Maintains state of the Irc connection. Should also become smarter.
|
|
|
|
"""
|
2003-03-31 11:23:32 +02:00
|
|
|
__slots__ = ('history', 'nicksToHostmasks', 'channels')
|
2003-03-12 07:26:59 +01:00
|
|
|
def __init__(self):
|
2003-08-26 18:39:23 +02:00
|
|
|
self.history = RingBuffer(conf.maxHistory)
|
2003-03-12 07:26:59 +01:00
|
|
|
self.reset()
|
|
|
|
|
|
|
|
def reset(self):
|
2003-10-04 14:17:31 +02:00
|
|
|
"""Resets the state to normal, unconnected state."""
|
2003-08-26 18:39:23 +02:00
|
|
|
self.history.reset()
|
2003-04-11 22:46:23 +02:00
|
|
|
self.channels = ircutils.IrcDict()
|
2003-09-05 20:54:35 +02:00
|
|
|
self.nicksToHostmasks = ircutils.IrcDict()
|
2003-03-12 07:26:59 +01:00
|
|
|
|
2003-04-09 19:59:13 +02:00
|
|
|
def __getstate__(self):
|
2003-11-04 09:26:54 +01:00
|
|
|
return map(curry(getattr, self), self.__slots__)
|
2003-04-09 19:59:13 +02:00
|
|
|
|
2003-04-29 09:37:54 +02:00
|
|
|
def __setstate__(self, t):
|
|
|
|
for (name, value) in zip(self.__slots__, t):
|
2003-04-09 19:59:13 +02:00
|
|
|
setattr(self, name, value)
|
2003-04-29 09:37:54 +02:00
|
|
|
|
|
|
|
def __eq__(self, other):
|
|
|
|
ret = True
|
|
|
|
for name in self.__slots__:
|
|
|
|
ret = ret and getattr(self, name) == getattr(other, name)
|
|
|
|
return ret
|
|
|
|
|
|
|
|
def __ne__(self, other):
|
|
|
|
return not self == other
|
2003-08-20 18:26:23 +02:00
|
|
|
|
2003-03-12 07:26:59 +01:00
|
|
|
def copy(self):
|
|
|
|
ret = self.__class__()
|
2003-10-16 15:27:40 +02:00
|
|
|
ret.history = copy.deepcopy(self.history)
|
|
|
|
ret.nicksToHostmasks = copy.deepcopy(self.nicksToHostmasks)
|
|
|
|
ret.channels = copy.deepcopy(self.channels)
|
2003-03-12 07:26:59 +01:00
|
|
|
return ret
|
|
|
|
|
|
|
|
def addMsg(self, irc, msg):
|
2003-10-04 14:17:31 +02:00
|
|
|
"""Updates the state based on the irc object and the message."""
|
2003-04-22 13:18:57 +02:00
|
|
|
self.history.append(msg)
|
2003-03-31 11:23:32 +02:00
|
|
|
if ircutils.isUserHostmask(msg.prefix) and not msg.command == 'NICK':
|
2003-03-12 07:26:59 +01:00
|
|
|
self.nicksToHostmasks[msg.nick] = msg.prefix
|
2003-05-29 19:03:42 +02:00
|
|
|
method = self.dispatchCommand(msg.command)
|
|
|
|
if method is not None:
|
|
|
|
method(irc, msg)
|
2003-03-12 07:26:59 +01:00
|
|
|
|
|
|
|
def getTopic(self, channel):
|
2003-10-04 14:17:31 +02:00
|
|
|
"""Returns the topic for a given channel."""
|
2003-05-29 19:03:42 +02:00
|
|
|
return self.channels[channel].topic
|
2003-03-12 07:26:59 +01:00
|
|
|
|
|
|
|
def nickToHostmask(self, nick):
|
2003-10-04 14:17:31 +02:00
|
|
|
"""Returns the hostmask for a given nick."""
|
2003-03-12 07:26:59 +01:00
|
|
|
return self.nicksToHostmasks[nick]
|
|
|
|
|
2003-05-29 19:03:42 +02:00
|
|
|
def do352(self, irc, msg):
|
|
|
|
(nick, user, host) = (msg.args[2], msg.args[5], msg.args[3])
|
|
|
|
hostmask = '%s!%s@%s' % (nick, user, host)
|
|
|
|
self.nicksToHostmasks[nick] = hostmask
|
|
|
|
|
|
|
|
def doJoin(self, irc, msg):
|
|
|
|
for channel in msg.args[0].split(','):
|
|
|
|
if channel in self.channels:
|
|
|
|
self.channels[channel].addUser(msg.nick)
|
|
|
|
else:
|
2003-10-01 13:15:31 +02:00
|
|
|
chan = ChannelState()
|
2003-05-29 19:03:42 +02:00
|
|
|
chan.addUser(msg.nick)
|
|
|
|
self.channels[channel] = chan
|
|
|
|
|
2003-09-29 06:30:18 +02:00
|
|
|
def doMode(self, irc, msg):
|
2003-09-29 09:44:27 +02:00
|
|
|
channel = msg.args[0]
|
|
|
|
if ircutils.isChannel(channel):
|
|
|
|
chan = self.channels[channel]
|
|
|
|
for (mode, nick) in ircutils.separateModes(msg.args[1:]):
|
|
|
|
if mode == '-o':
|
|
|
|
chan.ops.discard(nick)
|
|
|
|
elif mode == '+o':
|
|
|
|
chan.ops.add(nick)
|
|
|
|
if mode == '-h':
|
|
|
|
chan.halfops.discard(nick)
|
|
|
|
elif mode == '+h':
|
|
|
|
chan.halfops.add(nick)
|
|
|
|
if mode == '-v':
|
|
|
|
chan.voices.discard(nick)
|
|
|
|
elif mode == '+v':
|
|
|
|
chan.voices.add(nick)
|
2003-09-29 06:30:18 +02:00
|
|
|
|
2003-05-29 19:03:42 +02:00
|
|
|
def do353(self, irc, msg):
|
|
|
|
(_, _, channel, users) = msg.args
|
|
|
|
chan = self.channels[channel]
|
|
|
|
users = users.split()
|
|
|
|
for user in users:
|
|
|
|
chan.addUser(user)
|
|
|
|
|
|
|
|
def doPart(self, irc, msg):
|
|
|
|
for channel in msg.args[0].split(','):
|
|
|
|
chan = self.channels[channel]
|
|
|
|
if msg.nick == irc.nick:
|
|
|
|
del self.channels[channel]
|
|
|
|
else:
|
|
|
|
chan.removeUser(msg.nick)
|
|
|
|
|
|
|
|
def doKick(self, irc, msg):
|
|
|
|
(channel, users) = msg.args[:2]
|
|
|
|
chan = self.channels[channel]
|
|
|
|
for user in users.split(','):
|
|
|
|
chan.removeUser(user)
|
|
|
|
|
|
|
|
def doQuit(self, irc, msg):
|
|
|
|
for channel in self.channels.itervalues():
|
|
|
|
channel.removeUser(msg.nick)
|
|
|
|
|
|
|
|
def doTopic(self, irc, msg):
|
2003-09-06 04:09:02 +02:00
|
|
|
if len(msg.args) == 1:
|
|
|
|
return # Empty TOPIC for information. Does not affect state.
|
2003-05-29 19:03:42 +02:00
|
|
|
chan = self.channels[msg.args[0]]
|
|
|
|
chan.topic = msg.args[1]
|
|
|
|
|
|
|
|
def do332(self, irc, msg):
|
2003-06-02 09:27:43 +02:00
|
|
|
chan = self.channels[msg.args[1]]
|
2003-05-29 19:03:42 +02:00
|
|
|
chan.topic = msg.args[2]
|
|
|
|
|
|
|
|
def doNick(self, irc, msg):
|
2003-08-12 21:12:12 +02:00
|
|
|
newNick = msg.args[0]
|
2003-05-29 19:03:42 +02:00
|
|
|
oldNick = msg.nick
|
|
|
|
try:
|
|
|
|
if msg.user and msg.host:
|
|
|
|
# Nick messages being handed out from the bot itself won't
|
|
|
|
# have the necessary prefix to make a hostmask.
|
|
|
|
newHostmask = ircutils.joinHostmask(newNick,msg.user,msg.host)
|
|
|
|
self.nicksToHostmasks[newNick] = newHostmask
|
|
|
|
del self.nicksToHostmasks[oldNick]
|
|
|
|
except KeyError:
|
|
|
|
pass
|
|
|
|
for channel in self.channels.itervalues():
|
|
|
|
channel.replaceUser(oldNick, newNick)
|
|
|
|
|
2003-03-12 07:26:59 +01:00
|
|
|
|
|
|
|
|
|
|
|
###
|
|
|
|
# The basic class for handling a connection to an IRC server. Accepts
|
|
|
|
# callbacks of the IrcCallback interface. Public attributes include 'driver',
|
|
|
|
# 'queue', and 'state', in addition to the standard nick/user/ident attributes.
|
|
|
|
###
|
2003-10-14 06:48:45 +02:00
|
|
|
class Irc(IrcCommandDispatcher):
|
2003-03-12 07:26:59 +01:00
|
|
|
"""The base class for an IRC connection.
|
|
|
|
|
|
|
|
Handles PING commands already.
|
|
|
|
"""
|
2003-07-31 08:20:58 +02:00
|
|
|
_nickSetters = sets.Set(['001', '002', '003', '004', '250', '251', '252',
|
|
|
|
'254', '255', '265', '266', '372', '375', '376',
|
2003-11-25 12:53:09 +01:00
|
|
|
'333', '353', '332', '366', '005'])
|
2003-04-06 14:22:34 +02:00
|
|
|
def __init__(self, nick, user='', ident='', password='', callbacks=None):
|
2003-03-12 07:26:59 +01:00
|
|
|
world.ircs.append(self)
|
2003-12-02 12:55:26 +01:00
|
|
|
self.originalNick = intern(nick)
|
|
|
|
self.nick = self.originalNick
|
2003-04-06 14:22:34 +02:00
|
|
|
self.password = password
|
2003-11-26 19:21:12 +01:00
|
|
|
self.user = intern(user or nick) # Default to nick
|
|
|
|
self.ident = intern(ident or nick) # Ditto.
|
2003-08-12 21:12:12 +02:00
|
|
|
self.prefix = '%s!%s@%s' % (nick, ident, 'unset.domain')
|
2003-03-27 12:29:42 +01:00
|
|
|
if callbacks is None:
|
|
|
|
self.callbacks = []
|
|
|
|
else:
|
|
|
|
self.callbacks = callbacks
|
2003-03-12 07:26:59 +01:00
|
|
|
self.state = IrcState()
|
|
|
|
self.queue = IrcMsgQueue()
|
2003-04-06 14:22:34 +02:00
|
|
|
self._nickmods = copy.copy(conf.nickmods)
|
|
|
|
self.lastTake = 0
|
2003-11-25 12:53:09 +01:00
|
|
|
self.server = None
|
2003-12-12 13:47:23 +01:00
|
|
|
self.afterConnect = False
|
2003-10-09 06:01:27 +02:00
|
|
|
self.fastqueue = smallqueue()
|
2003-03-12 07:26:59 +01:00
|
|
|
self.lastping = time.time()
|
2003-04-21 06:37:35 +02:00
|
|
|
self.outstandingPing = False
|
2003-03-12 07:26:59 +01:00
|
|
|
self.driver = None # The driver should set this later.
|
2003-04-06 14:22:34 +02:00
|
|
|
if self.password:
|
|
|
|
self.queue.enqueue(ircmsgs.password(self.password))
|
2003-04-03 11:59:57 +02:00
|
|
|
self.queue.enqueue(ircmsgs.nick(self.nick))
|
2003-04-06 14:22:34 +02:00
|
|
|
self.queue.enqueue(ircmsgs.user(self.ident, self.user))
|
2003-03-12 07:26:59 +01:00
|
|
|
|
|
|
|
def reset(self):
|
2003-10-04 14:17:31 +02:00
|
|
|
"""Resets the Irc object. Useful for handling reconnects."""
|
2003-12-02 12:55:26 +01:00
|
|
|
self.nick = self.originalNick
|
|
|
|
self.prefix = '%s!%s@%s' % (self.nick, self.ident, 'unset.domain')
|
2003-03-12 07:26:59 +01:00
|
|
|
self.state.reset()
|
|
|
|
self.queue.reset()
|
2003-11-25 12:53:09 +01:00
|
|
|
self.server = None
|
2003-12-12 13:47:23 +01:00
|
|
|
self.afterConnect = False
|
2003-04-06 14:22:34 +02:00
|
|
|
self.lastping = time.time()
|
2003-06-06 06:47:18 +02:00
|
|
|
self.outstandingPing = False
|
2003-04-02 11:26:16 +02:00
|
|
|
self.fastqueue = queue()
|
2003-04-06 14:22:34 +02:00
|
|
|
if self.password:
|
|
|
|
self.queue.enqueue(ircmsgs.password(self.password))
|
2003-04-03 11:59:57 +02:00
|
|
|
self.queue.enqueue(ircmsgs.nick(self.nick))
|
2003-04-06 14:22:34 +02:00
|
|
|
self.queue.enqueue(ircmsgs.user(self.ident, self.user))
|
2003-03-12 07:26:59 +01:00
|
|
|
for callback in self.callbacks:
|
|
|
|
callback.reset()
|
|
|
|
|
|
|
|
def addCallback(self, callback):
|
2003-10-04 14:17:31 +02:00
|
|
|
"""Adds a callback to the callbacks list."""
|
2003-03-12 07:26:59 +01:00
|
|
|
self.callbacks.append(callback)
|
2003-09-05 21:28:04 +02:00
|
|
|
utils.sortBy(lambda cb: cb.priority, self.callbacks)
|
2003-03-12 07:26:59 +01:00
|
|
|
|
2003-06-06 06:47:18 +02:00
|
|
|
def getCallback(self, name):
|
2003-10-04 14:17:31 +02:00
|
|
|
"""Gets a given callback by name."""
|
2003-07-30 13:04:29 +02:00
|
|
|
name = name.lower()
|
2003-06-06 06:47:18 +02:00
|
|
|
for callback in self.callbacks:
|
2003-07-30 13:04:29 +02:00
|
|
|
if callback.name().lower() == name:
|
2003-06-06 06:47:18 +02:00
|
|
|
return callback
|
|
|
|
else:
|
|
|
|
return None
|
|
|
|
|
2003-03-12 07:26:59 +01:00
|
|
|
def removeCallback(self, name):
|
2003-10-04 14:17:31 +02:00
|
|
|
"""Removes a callback from the callback list."""
|
2003-10-30 19:05:57 +01:00
|
|
|
name = name.lower()
|
|
|
|
def nameMatches(cb):
|
|
|
|
return cb.name().lower() == name
|
|
|
|
(bad, good) = partition(nameMatches, self.callbacks)
|
2003-04-20 19:18:34 +02:00
|
|
|
self.callbacks[:] = good
|
|
|
|
return bad
|
2003-03-12 07:26:59 +01:00
|
|
|
|
|
|
|
def queueMsg(self, msg):
|
2003-10-04 14:17:31 +02:00
|
|
|
"""Queues a message to be sent to the server."""
|
2003-04-03 11:59:57 +02:00
|
|
|
self.queue.enqueue(msg)
|
2003-03-12 07:26:59 +01:00
|
|
|
|
|
|
|
def sendMsg(self, msg):
|
2003-10-04 14:17:31 +02:00
|
|
|
"""Queues a message to be sent to the server *immediately*"""
|
2003-04-02 11:26:16 +02:00
|
|
|
self.fastqueue.enqueue(msg)
|
2003-03-12 07:26:59 +01:00
|
|
|
|
|
|
|
def takeMsg(self):
|
2003-10-04 14:17:31 +02:00
|
|
|
"""Called by the IrcDriver; takes a message to be sent."""
|
2003-03-12 07:26:59 +01:00
|
|
|
now = time.time()
|
|
|
|
msg = None
|
|
|
|
if self.fastqueue:
|
2003-04-02 11:26:16 +02:00
|
|
|
msg = self.fastqueue.dequeue()
|
2003-04-03 11:59:57 +02:00
|
|
|
elif self.queue:
|
2003-08-11 07:07:07 +02:00
|
|
|
if not world.testing and now - self.lastTake <= conf.throttleTime:
|
2003-11-26 19:21:12 +01:00
|
|
|
log.debug('Irc.takeMsg throttling.')
|
2003-03-12 07:26:59 +01:00
|
|
|
else:
|
|
|
|
self.lastTake = now
|
2003-04-03 11:59:57 +02:00
|
|
|
msg = self.queue.dequeue()
|
2003-12-20 08:01:09 +01:00
|
|
|
elif conf.pingServer and \
|
|
|
|
now > (self.lastping + conf.pingInterval) and self.afterConnect:
|
2003-04-21 06:37:35 +02:00
|
|
|
if self.outstandingPing:
|
2003-11-26 19:21:12 +01:00
|
|
|
s = 'Reconnecting to %s, ping not replied to.' % self.server
|
|
|
|
log.warning(s)
|
2003-04-21 06:37:35 +02:00
|
|
|
self.driver.reconnect()
|
2003-11-18 15:22:54 +01:00
|
|
|
self.reset()
|
2003-03-12 07:26:59 +01:00
|
|
|
else:
|
|
|
|
self.lastping = now
|
2003-04-04 10:30:16 +02:00
|
|
|
now = str(int(now))
|
2003-04-21 06:37:35 +02:00
|
|
|
self.outstandingPing = True
|
|
|
|
self.queueMsg(ircmsgs.ping(now))
|
2003-03-12 07:26:59 +01:00
|
|
|
if msg:
|
2003-09-05 21:28:04 +02:00
|
|
|
for callback in reviter(self.callbacks):
|
2003-11-26 19:21:12 +01:00
|
|
|
log.debug(repr(msg))
|
2003-04-21 06:37:35 +02:00
|
|
|
try:
|
|
|
|
outFilter = getattr(callback, 'outFilter')
|
|
|
|
except AttributeError, e:
|
|
|
|
continue
|
|
|
|
try:
|
|
|
|
msg = outFilter(self, msg)
|
|
|
|
except:
|
2003-11-26 19:21:12 +01:00
|
|
|
log.exception('Exception caught in outFilter:')
|
2003-04-21 06:37:35 +02:00
|
|
|
continue
|
2003-03-27 21:59:05 +01:00
|
|
|
if msg is None:
|
2003-11-26 19:21:12 +01:00
|
|
|
log.debug('%s.outFilter returned None' % callback.name())
|
|
|
|
return self.takeMsg()
|
2003-10-23 07:53:49 +02:00
|
|
|
if len(str(msg)) > 512:
|
2003-11-26 19:21:12 +01:00
|
|
|
# Yes, this violates the contract, but at this point it doesn't
|
|
|
|
# matter. That's why we gotta go munging in private attributes
|
2003-10-23 07:53:49 +02:00
|
|
|
msg._str = msg._str[:500] + '\r\n'
|
|
|
|
msg._len = len(str(msg))
|
2003-09-17 09:54:02 +02:00
|
|
|
self.state.addMsg(self, msg)
|
2003-11-26 19:21:12 +01:00
|
|
|
log.info('Outgoing message: ' + str(msg).rstrip('\r\n'))
|
2003-03-12 07:26:59 +01:00
|
|
|
if msg.command == 'NICK':
|
|
|
|
# We don't want a race condition where the server's NICK
|
|
|
|
# back to us is lost and someone else steals our nick and uses
|
|
|
|
# it to abuse our 'owner' power we give to ourselves. Ergo, on
|
|
|
|
# outgoing messages that change our nick, we pre-emptively
|
|
|
|
# delete the 'owner' user we setup for ourselves.
|
2003-09-12 22:06:58 +02:00
|
|
|
user = ircdb.users.getUser(0)
|
|
|
|
user.unsetAuth()
|
|
|
|
user.hostmasks = []
|
|
|
|
ircdb.users.setUser(0, user)
|
2003-03-12 07:26:59 +01:00
|
|
|
return msg
|
|
|
|
else:
|
|
|
|
return None
|
|
|
|
|
2003-12-02 12:40:46 +01:00
|
|
|
def do001(self, msg):
|
|
|
|
"""Prints some logging."""
|
|
|
|
log.info('Received 001 from the server.')
|
|
|
|
log.info('Hostmasks of user 0: %r' % ircdb.users.getUser(0).hostmasks)
|
|
|
|
|
2003-10-14 06:48:45 +02:00
|
|
|
def doPing(self, msg):
|
|
|
|
"""Handles PING messages."""
|
|
|
|
self.sendMsg(ircmsgs.pong(msg.args[0]))
|
|
|
|
|
|
|
|
def doPong(self, msg):
|
|
|
|
"""Handles PONG messages."""
|
|
|
|
self.outstandingPing = False
|
|
|
|
|
2003-12-12 13:29:02 +01:00
|
|
|
def do376(self, msg):
|
2003-12-12 13:47:23 +01:00
|
|
|
self.afterConnect = True
|
|
|
|
do377 = do422 = do376
|
2003-12-12 13:29:02 +01:00
|
|
|
|
2003-10-14 06:48:45 +02:00
|
|
|
def do433(self, msg):
|
|
|
|
"""Handles 'nickname already in use' messages."""
|
2003-12-06 14:20:59 +01:00
|
|
|
if not self._nickmods:
|
|
|
|
self._nickmods = conf.nickmods[:]
|
|
|
|
self.sendMsg(ircmsgs.nick(self._nickmods.pop(0) % self.originalNick))
|
2003-10-14 06:48:45 +02:00
|
|
|
do432 = do433
|
|
|
|
|
|
|
|
def doError(self, msg):
|
|
|
|
"""Handles ERROR messages."""
|
|
|
|
if msg.args[0].startswith('Closing Link'):
|
|
|
|
if hasattr(self.driver, 'scheduleReconnect'):
|
|
|
|
self.driver.scheduleReconnect()
|
|
|
|
self.driver.die()
|
2003-10-23 07:53:49 +02:00
|
|
|
elif 'too fast' in msg.args[0]:
|
|
|
|
if hasattr(self.driver, 'reconnectWaitsIndex'):
|
|
|
|
newIndex = len(self.driver.reconnectWaits)-1
|
|
|
|
self.driver.reconnectWaitsIndex = newIndex
|
|
|
|
self.driver.die()
|
2003-10-14 06:48:45 +02:00
|
|
|
|
|
|
|
def doNick(self, msg):
|
|
|
|
"""Handles NICK messages."""
|
|
|
|
if msg.nick == self.nick:
|
2003-11-26 19:21:12 +01:00
|
|
|
newNick = intern(msg.args[0])
|
2003-10-14 06:48:45 +02:00
|
|
|
user = ircdb.users.getUser(0)
|
|
|
|
user.unsetAuth()
|
|
|
|
user.hostmasks = []
|
2003-11-13 00:56:26 +01:00
|
|
|
try:
|
|
|
|
ircdb.users.getUser(newNick)
|
2003-11-26 19:21:12 +01:00
|
|
|
log.error('User already registered with name %s' % newNick)
|
2003-11-13 00:56:26 +01:00
|
|
|
except KeyError:
|
|
|
|
user.name = newNick
|
2003-10-14 06:48:45 +02:00
|
|
|
ircdb.users.setUser(0, user)
|
|
|
|
self.nick = newNick
|
|
|
|
(nick, user, domain) = ircutils.splitHostmask(msg.prefix)
|
|
|
|
self.prefix = ircutils.joinHostmask(self.nick, user, domain)
|
2003-11-26 19:21:12 +01:00
|
|
|
self.prefix = intern(self.prefix)
|
2003-12-02 12:40:46 +01:00
|
|
|
log.info('Changing user 0 hostmask to %r' % self.prefix)
|
2003-12-17 14:39:05 +01:00
|
|
|
elif conf.followIdentificationThroughNickChanges:
|
|
|
|
try:
|
|
|
|
id = ircdb.users.getUserId(msg.prefix)
|
|
|
|
u = ircdb.users.getUser(id)
|
|
|
|
except KeyError:
|
|
|
|
return
|
|
|
|
if u.auth:
|
|
|
|
(_, user, host) = ircutils.splitHostmask(msg.prefix)
|
|
|
|
newhostmask = ircutils.joinHostmask(msg.args[0], user, host)
|
|
|
|
log.info('Following identification for %s: %s -> %s',
|
|
|
|
u.name, u.auth[1], newhostmask)
|
|
|
|
u.auth = (u.auth[0], newhostmask)
|
|
|
|
ircdb.users.setUser(id, u)
|
2003-10-14 06:48:45 +02:00
|
|
|
|
2003-03-12 07:26:59 +01:00
|
|
|
def feedMsg(self, msg):
|
2003-10-04 14:17:31 +02:00
|
|
|
"""Called by the IrcDriver; feeds a message received."""
|
2003-11-26 19:21:12 +01:00
|
|
|
log.info('Incoming message: ' + str(msg).rstrip('\r\n'))
|
2003-10-14 06:48:45 +02:00
|
|
|
|
2003-08-12 21:12:12 +02:00
|
|
|
# Yeah, so this is odd. Some networks (oftc) seem to give us certain
|
|
|
|
# messages with our nick instead of our prefix. We'll fix that here.
|
|
|
|
if msg.prefix == self.nick:
|
2003-11-26 19:21:12 +01:00
|
|
|
log.debug('Got one of those odd nick-instead-of-prefix msgs.')
|
2003-10-14 06:48:45 +02:00
|
|
|
msg = ircmsgs.IrcMsg(prefix=self.prefix, msg=msg)
|
|
|
|
|
|
|
|
# This catches cases where we know our own nick (from sending it to the
|
|
|
|
# server) but we don't yet know our prefix.
|
|
|
|
if msg.nick == self.nick and self.prefix != msg.prefix:
|
2003-12-02 12:40:46 +01:00
|
|
|
log.info('Updating user 0 prefix: %r' % msg.prefix)
|
2003-08-29 10:09:09 +02:00
|
|
|
self.prefix = msg.prefix
|
2003-09-12 22:06:58 +02:00
|
|
|
user = ircdb.users.getUser(0)
|
|
|
|
user.hostmasks = []
|
|
|
|
user.name = self.nick
|
|
|
|
user.addHostmask(msg.prefix)
|
|
|
|
ircdb.users.setUser(0, user)
|
2003-11-25 12:53:09 +01:00
|
|
|
|
|
|
|
# This keeps our nick and server attributes updated.
|
|
|
|
if msg.command in self._nickSetters:
|
|
|
|
if msg.args[0] != self.nick:
|
2003-12-09 05:46:51 +01:00
|
|
|
log.info('Updating nick attribute.')
|
2003-11-25 12:53:09 +01:00
|
|
|
self.nick = msg.args[0]
|
|
|
|
if msg.prefix != self.server:
|
2003-12-09 05:46:51 +01:00
|
|
|
log.info('Updating server attribute.')
|
2003-11-25 12:53:09 +01:00
|
|
|
self.server = msg.prefix
|
2003-10-14 06:48:45 +02:00
|
|
|
|
2003-11-26 19:21:12 +01:00
|
|
|
# Dispatch to specific handlers for commands.
|
|
|
|
method = self.dispatchCommand(msg.command)
|
|
|
|
if method is not None:
|
|
|
|
method(msg)
|
|
|
|
|
2003-03-12 07:26:59 +01:00
|
|
|
# Now update the IrcState object.
|
|
|
|
try:
|
|
|
|
self.state.addMsg(self, msg)
|
|
|
|
except:
|
2003-11-26 19:21:12 +01:00
|
|
|
log.exception('Exception in update of IrcState object:')
|
2003-10-14 06:48:45 +02:00
|
|
|
|
2003-03-12 07:26:59 +01:00
|
|
|
# Now call the callbacks.
|
|
|
|
for callback in self.callbacks:
|
|
|
|
try:
|
|
|
|
m = callback.inFilter(self, msg)
|
|
|
|
if not m:
|
2003-11-26 19:21:12 +01:00
|
|
|
log.debug('%s.inFilter returned None' % callback.name())
|
2003-09-24 09:26:22 +02:00
|
|
|
return
|
2003-03-12 07:26:59 +01:00
|
|
|
msg = m
|
|
|
|
except:
|
2003-11-26 19:21:12 +01:00
|
|
|
log.exception('Uncaught exception in inFilter:')
|
2003-03-12 07:26:59 +01:00
|
|
|
for callback in self.callbacks:
|
|
|
|
try:
|
|
|
|
if callback is not None:
|
|
|
|
callback(self, msg)
|
|
|
|
except:
|
2003-11-26 19:21:12 +01:00
|
|
|
log.exception('Uncaught exception in callback:')
|
2003-03-12 07:26:59 +01:00
|
|
|
|
|
|
|
def die(self):
|
2003-10-04 14:17:31 +02:00
|
|
|
"""Makes the Irc object die. Dead."""
|
2003-11-26 19:21:12 +01:00
|
|
|
log.info('Irc object for %s dying.' % self.server)
|
2003-03-12 07:26:59 +01:00
|
|
|
for callback in self.callbacks:
|
|
|
|
callback.die()
|
2003-08-26 20:14:59 +02:00
|
|
|
if self.driver is not None:
|
|
|
|
self.driver.die()
|
2003-08-23 12:59:28 +02:00
|
|
|
if self in world.ircs:
|
|
|
|
world.ircs.remove(self)
|
2003-03-26 07:56:02 +01:00
|
|
|
|
|
|
|
def __hash__(self):
|
|
|
|
return id(self)
|
|
|
|
|
|
|
|
def __eq__(self, other):
|
|
|
|
return id(self) == id(other)
|
|
|
|
|
2003-08-23 01:15:29 +02:00
|
|
|
|
2003-03-24 09:41:19 +01:00
|
|
|
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:
|