123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439 |
- # Copyright (C) 1999--2002 Joel Rosdahl
- #
- # This library is free software; you can redistribute it and/or
- # modify it under the terms of the GNU Lesser General Public
- # License as published by the Free Software Foundation; either
- # version 2.1 of the License, or (at your option) any later version.
- #
- # This library is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- # Lesser General Public License for more details.
- #
- # You should have received a copy of the GNU Lesser General Public
- # License along with this library; if not, write to the Free Software
- # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- #
- # Joel Rosdahl <joel@rosdahl.net>
- #
- # $Id: ircbot.py,v 1.23 2008/09/11 07:38:30 keltus Exp $
-
- """ircbot -- Simple IRC bot library.
-
- This module contains a single-server IRC bot class that can be used to
- write simpler bots.
- """
-
- import sys
- from UserDict import UserDict
-
- from irclib import SimpleIRCClient
- from irclib import nm_to_n, irc_lower, all_events
- from irclib import parse_channel_modes, is_channel
- from irclib import ServerConnectionError
-
- class SingleServerIRCBot(SimpleIRCClient):
- """A single-server IRC bot class.
-
- The bot tries to reconnect if it is disconnected.
-
- The bot keeps track of the channels it has joined, the other
- clients that are present in the channels and which of those that
- have operator or voice modes. The "database" is kept in the
- self.channels attribute, which is an IRCDict of Channels.
- """
- def __init__(self, server_list, nickname, realname, reconnection_interval=60):
- """Constructor for SingleServerIRCBot objects.
-
- Arguments:
-
- server_list -- A list of tuples (server, port) that
- defines which servers the bot should try to
- connect to.
-
- nickname -- The bot's nickname.
-
- realname -- The bot's realname.
-
- reconnection_interval -- How long the bot should wait
- before trying to reconnect.
-
- dcc_connections -- A list of initiated/accepted DCC
- connections.
- """
-
- SimpleIRCClient.__init__(self)
- self.channels = IRCDict()
- self.server_list = server_list
- if not reconnection_interval or reconnection_interval < 0:
- reconnection_interval = 2**31
- self.reconnection_interval = reconnection_interval
-
- self._nickname = nickname
- self._realname = realname
- for i in ["disconnect", "join", "kick", "mode",
- "namreply", "nick", "part", "quit"]:
- self.connection.add_global_handler(i,
- getattr(self, "_on_" + i),
- -10)
- def _connected_checker(self):
- """[Internal]"""
- if not self.connection.is_connected():
- self.connection.execute_delayed(self.reconnection_interval,
- self._connected_checker)
- self.jump_server()
-
- def _connect(self):
- """[Internal]"""
- password = None
- if len(self.server_list[0]) > 2:
- password = self.server_list[0][2]
- try:
- self.connect(self.server_list[0][0],
- self.server_list[0][1],
- self._nickname,
- password,
- ircname=self._realname)
- except ServerConnectionError:
- pass
-
- def _on_disconnect(self, c, e):
- """[Internal]"""
- self.channels = IRCDict()
- self.connection.execute_delayed(self.reconnection_interval,
- self._connected_checker)
-
- def _on_join(self, c, e):
- """[Internal]"""
- ch = e.target()
- nick = nm_to_n(e.source())
- if nick == c.get_nickname():
- self.channels[ch] = Channel()
- self.channels[ch].add_user(nick)
-
- def _on_kick(self, c, e):
- """[Internal]"""
- nick = e.arguments()[0]
- channel = e.target()
-
- if nick == c.get_nickname():
- del self.channels[channel]
- else:
- self.channels[channel].remove_user(nick)
-
- def _on_mode(self, c, e):
- """[Internal]"""
- modes = parse_channel_modes(" ".join(e.arguments()))
- t = e.target()
- if is_channel(t):
- ch = self.channels[t]
- for mode in modes:
- if mode[0] == "+":
- f = ch.set_mode
- else:
- f = ch.clear_mode
- f(mode[1], mode[2])
- else:
- # Mode on self... XXX
- pass
-
- def _on_namreply(self, c, e):
- """[Internal]"""
-
- # e.arguments()[0] == "@" for secret channels,
- # "*" for private channels,
- # "=" for others (public channels)
- # e.arguments()[1] == channel
- # e.arguments()[2] == nick list
-
- ch = e.arguments()[1]
- for nick in e.arguments()[2].split():
- if nick[0] == "@":
- nick = nick[1:]
- self.channels[ch].set_mode("o", nick)
- elif nick[0] == "+":
- nick = nick[1:]
- self.channels[ch].set_mode("v", nick)
- self.channels[ch].add_user(nick)
-
- def _on_nick(self, c, e):
- """[Internal]"""
- before = nm_to_n(e.source())
- after = e.target()
- for ch in self.channels.values():
- if ch.has_user(before):
- ch.change_nick(before, after)
-
- def _on_part(self, c, e):
- """[Internal]"""
- nick = nm_to_n(e.source())
- channel = e.target()
-
- if nick == c.get_nickname():
- del self.channels[channel]
- else:
- self.channels[channel].remove_user(nick)
-
- def _on_quit(self, c, e):
- """[Internal]"""
- nick = nm_to_n(e.source())
- for ch in self.channels.values():
- if ch.has_user(nick):
- ch.remove_user(nick)
-
- def die(self, msg="Bye, cruel world!"):
- """Let the bot die.
-
- Arguments:
-
- msg -- Quit message.
- """
-
- self.connection.disconnect(msg)
- sys.exit(0)
-
- def disconnect(self, msg="I'll be back!"):
- """Disconnect the bot.
-
- The bot will try to reconnect after a while.
-
- Arguments:
-
- msg -- Quit message.
- """
- self.connection.disconnect(msg)
-
- def get_version(self):
- """Returns the bot version.
-
- Used when answering a CTCP VERSION request.
- """
- return "ircbot.py by Joel Rosdahl <joel@rosdahl.net>"
-
- def jump_server(self, msg="Changing servers"):
- """Connect to a new server, possibly disconnecting from the current.
-
- The bot will skip to next server in the server_list each time
- jump_server is called.
- """
- if self.connection.is_connected():
- self.connection.disconnect(msg)
-
- self.server_list.append(self.server_list.pop(0))
- self._connect()
-
- def on_ctcp(self, c, e):
- """Default handler for ctcp events.
-
- Replies to VERSION and PING requests and relays DCC requests
- to the on_dccchat method.
- """
- if e.arguments()[0] == "VERSION":
- c.ctcp_reply(nm_to_n(e.source()),
- "VERSION " + self.get_version())
- elif e.arguments()[0] == "PING":
- if len(e.arguments()) > 1:
- c.ctcp_reply(nm_to_n(e.source()),
- "PING " + e.arguments()[1])
- elif e.arguments()[0] == "DCC" and e.arguments()[1].split(" ", 1)[0] == "CHAT":
- self.on_dccchat(c, e)
-
- def on_dccchat(self, c, e):
- pass
-
- def start(self):
- """Start the bot."""
- self._connect()
- SimpleIRCClient.start(self)
-
-
- class IRCDict:
- """A dictionary suitable for storing IRC-related things.
-
- Dictionary keys a and b are considered equal if and only if
- irc_lower(a) == irc_lower(b)
-
- Otherwise, it should behave exactly as a normal dictionary.
- """
-
- def __init__(self, dict=None):
- self.data = {}
- self.canon_keys = {} # Canonical keys
- if dict is not None:
- self.update(dict)
- def __repr__(self):
- return repr(self.data)
- def __cmp__(self, dict):
- if isinstance(dict, IRCDict):
- return cmp(self.data, dict.data)
- else:
- return cmp(self.data, dict)
- def __len__(self):
- return len(self.data)
- def __getitem__(self, key):
- return self.data[self.canon_keys[irc_lower(key)]]
- def __setitem__(self, key, item):
- if key in self:
- del self[key]
- self.data[key] = item
- self.canon_keys[irc_lower(key)] = key
- def __delitem__(self, key):
- ck = irc_lower(key)
- del self.data[self.canon_keys[ck]]
- del self.canon_keys[ck]
- def __iter__(self):
- return iter(self.data)
- def __contains__(self, key):
- return self.has_key(key)
- def clear(self):
- self.data.clear()
- self.canon_keys.clear()
- def copy(self):
- if self.__class__ is UserDict:
- return UserDict(self.data)
- import copy
- return copy.copy(self)
- def keys(self):
- return self.data.keys()
- def items(self):
- return self.data.items()
- def values(self):
- return self.data.values()
- def has_key(self, key):
- return irc_lower(key) in self.canon_keys
- def update(self, dict):
- for k, v in dict.items():
- self.data[k] = v
- def get(self, key, failobj=None):
- return self.data.get(key, failobj)
-
-
- class Channel:
- """A class for keeping information about an IRC channel.
-
- This class can be improved a lot.
- """
-
- def __init__(self):
- self.userdict = IRCDict()
- self.operdict = IRCDict()
- self.voiceddict = IRCDict()
- self.modes = {}
-
- def users(self):
- """Returns an unsorted list of the channel's users."""
- return self.userdict.keys()
-
- def opers(self):
- """Returns an unsorted list of the channel's operators."""
- return self.operdict.keys()
-
- def voiced(self):
- """Returns an unsorted list of the persons that have voice
- mode set in the channel."""
- return self.voiceddict.keys()
-
- def has_user(self, nick):
- """Check whether the channel has a user."""
- return nick in self.userdict
-
- def is_oper(self, nick):
- """Check whether a user has operator status in the channel."""
- return nick in self.operdict
-
- def is_voiced(self, nick):
- """Check whether a user has voice mode set in the channel."""
- return nick in self.voiceddict
-
- def add_user(self, nick):
- self.userdict[nick] = 1
-
- def remove_user(self, nick):
- for d in self.userdict, self.operdict, self.voiceddict:
- if nick in d:
- del d[nick]
-
- def change_nick(self, before, after):
- self.userdict[after] = 1
- del self.userdict[before]
- if before in self.operdict:
- self.operdict[after] = 1
- del self.operdict[before]
- if before in self.voiceddict:
- self.voiceddict[after] = 1
- del self.voiceddict[before]
-
- def set_mode(self, mode, value=None):
- """Set mode on the channel.
-
- Arguments:
-
- mode -- The mode (a single-character string).
-
- value -- Value
- """
- if mode == "o":
- self.operdict[value] = 1
- elif mode == "v":
- self.voiceddict[value] = 1
- else:
- self.modes[mode] = value
-
- def clear_mode(self, mode, value=None):
- """Clear mode on the channel.
-
- Arguments:
-
- mode -- The mode (a single-character string).
-
- value -- Value
- """
- try:
- if mode == "o":
- del self.operdict[value]
- elif mode == "v":
- del self.voiceddict[value]
- else:
- del self.modes[mode]
- except KeyError:
- pass
-
- def has_mode(self, mode):
- return mode in self.modes
-
- def is_moderated(self):
- return self.has_mode("m")
-
- def is_secret(self):
- return self.has_mode("s")
-
- def is_protected(self):
- return self.has_mode("p")
-
- def has_topic_lock(self):
- return self.has_mode("t")
-
- def is_invite_only(self):
- return self.has_mode("i")
-
- def has_allow_external_messages(self):
- return self.has_mode("n")
-
- def has_limit(self):
- return self.has_mode("l")
-
- def limit(self):
- if self.has_limit():
- return self.modes[l]
- else:
- return None
-
- def has_key(self):
- return self.has_mode("k")
-
- def key(self):
- if self.has_key():
- return self.modes["k"]
- else:
- return None
|