r85406 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r85405‎ | r85406 | r85407 >
Date:00:19, 5 April 2011
Author:laner
Status:deferred
Tags:
Comment:
Initial commit of ircecho package. We are using this for nagios-wm bot. I've changed Kate's software to take input from a file instead of needing a pipe. Also, I've written an init script that can be used to start and stop the daemon. The daemon is configured via /etc/default/ircecho.
Modified paths:
  • /trunk/debs/ircecho (added) (history)
  • /trunk/debs/ircecho/debian (added) (history)
  • /trunk/debs/ircecho/debian/changelog (added) (history)
  • /trunk/debs/ircecho/debian/compat (added) (history)
  • /trunk/debs/ircecho/debian/control (added) (history)
  • /trunk/debs/ircecho/debian/copyright (added) (history)
  • /trunk/debs/ircecho/debian/dirs (added) (history)
  • /trunk/debs/ircecho/debian/install (added) (history)
  • /trunk/debs/ircecho/debian/ircecho.default (added) (history)
  • /trunk/debs/ircecho/debian/ircecho.init (added) (history)
  • /trunk/debs/ircecho/debian/rules (added) (history)
  • /trunk/debs/ircecho/ircbot.py (added) (history)
  • /trunk/debs/ircecho/ircecho (added) (history)
  • /trunk/debs/ircecho/irclib.py (added) (history)

Diff [purge]

Index: trunk/debs/ircecho/debian/ircecho.default
@@ -0,0 +1,11 @@
 2+# Defaults for ircecho initscript
 3+# sourced by /etc/init.d/ircecho
 4+# installed at /etc/default/ircecho by the maintainer scripts
 5+
 6+#
 7+# This is a POSIX shell fragment
 8+#
 9+INFILE=""
 10+NICK=""
 11+CHANS=""
 12+SERVER=""
Index: trunk/debs/ircecho/debian/control
@@ -0,0 +1,11 @@
 2+Source: ircecho
 3+Section: net
 4+Priority: extra
 5+Maintainer: Ryan Lane <rlane@wikimedia.org>
 6+Build-Depends: debhelper (>= 4)
 7+Standards-Version: 1.0
 8+
 9+Package: ircecho
 10+Depends: python-pyinotify
 11+Architecture: all
 12+Description: IRC bot for echoing from stdin to IRC.
Index: trunk/debs/ircecho/debian/dirs
@@ -0,0 +1,3 @@
 2+usr/ircecho
 3+usr/ircecho/bin
 4+usr/ircecho/lib
Index: trunk/debs/ircecho/debian/compat
@@ -0,0 +1 @@
 2+6
Index: trunk/debs/ircecho/debian/install
@@ -0,0 +1,3 @@
 2+ircecho /usr/ircecho/bin
 3+ircbot.py /usr/ircecho/lib
 4+irclib.py /usr/ircecho/lib
Index: trunk/debs/ircecho/debian/changelog
@@ -0,0 +1,11 @@
 2+ircecho (1.0-1) lucid-wikimedia; urgency=low
 3+
 4+ * Fix init script
 5+
 6+ -- Ryan Lane <rlane@wikimedia.org> Mon, 4 Apr 2011 23:50:00 +0000
 7+
 8+ircecho (1.0) lucid-wikimedia; urgency=low
 9+
 10+ * Initial package
 11+
 12+ -- Ryan Lane <rlane@wikimedia.org> Wed, 30 Mar 2011 21:35:28 +0000
Index: trunk/debs/ircecho/debian/copyright
@@ -0,0 +1,23 @@
 2+This package was debianized by Ryan Lane <rlane@wikimedia.org> on
 3+Fri, 1 Apr 2011 21:35:28 +0000.
 4+
 5+It was downloaded from http://svn.wikimedia.org/viewvc/mediawiki/trunk/tools/ircecho
 6+
 7+Upstream Author(s):
 8+
 9+ Kate Turner
 10+
 11+Copyright:
 12+
 13+ Copyright (C) 2011 Kate Turner
 14+
 15+License:
 16+
 17+ Public Domain
 18+
 19+The Debian packaging is:
 20+
 21+ Copyright (C) 2011 Ryan Lane <rlane@wikimedia.org>
 22+
 23+and is licensed under the GPL version 2,
 24+see `/usr/share/common-licenses/GPL-2'.
Index: trunk/debs/ircecho/debian/rules
@@ -0,0 +1,86 @@
 2+#!/usr/bin/make -f
 3+# -*- makefile -*-
 4+# Sample debian/rules that uses debhelper.
 5+# This file was originally written by Joey Hess and Craig Small.
 6+# As a special exception, when this file is copied by dh-make into a
 7+# dh-make output file, you may use that output file without restriction.
 8+# This special exception was added by Craig Small in version 0.37 of dh-make.
 9+
 10+# Uncomment this to turn on verbose mode.
 11+export DH_VERBOSE=1
 12+
 13+
 14+
 15+
 16+
 17+configure: configure-stamp
 18+configure-stamp:
 19+ dh_testdir
 20+ # Add here commands to configure the package.
 21+
 22+ touch configure-stamp
 23+
 24+
 25+build: build-stamp
 26+
 27+build-stamp: configure-stamp
 28+ dh_testdir
 29+
 30+ # Add here commands to compile the package.
 31+ touch $@
 32+
 33+clean:
 34+ dh_testdir
 35+ dh_testroot
 36+ rm -f build-stamp configure-stamp
 37+
 38+ # Add here commands to clean up after the build process.
 39+ rm -rf build
 40+
 41+ dh_clean
 42+
 43+install: build
 44+ dh_testdir
 45+ dh_testroot
 46+ #dh_prep
 47+ dh_installdirs
 48+
 49+# Build architecture-independent files here.
 50+binary-indep: install
 51+ dh_testdir
 52+ dh_testroot
 53+ dh_installinit -n
 54+
 55+# Build architecture-dependent files here.
 56+binary-arch: install
 57+ dh_testdir
 58+ dh_testroot
 59+# dh_installchangelogs
 60+# dh_installdocs
 61+# dh_installexamples
 62+ dh_install
 63+# dh_installmenu
 64+# dh_installdebconf
 65+# dh_installlogrotate
 66+# dh_installemacsen
 67+# dh_installpam
 68+# dh_installmime
 69+# dh_python
 70+# dh_installinit
 71+# dh_installcron
 72+# dh_installinfo
 73+# dh_installman
 74+ dh_link
 75+ dh_strip
 76+ dh_compress
 77+ dh_fixperms
 78+# dh_perl
 79+# dh_makeshlibs
 80+ dh_installdeb
 81+ dh_shlibdeps
 82+ dh_gencontrol
 83+ dh_md5sums
 84+ dh_builddeb
 85+
 86+binary: binary-indep binary-arch
 87+.PHONY: build clean binary-indep binary-arch binary install configure
Property changes on: trunk/debs/ircecho/debian/rules
___________________________________________________________________
Added: svn:executable
188 + *
Index: trunk/debs/ircecho/debian/ircecho.init
@@ -0,0 +1,142 @@
 2+#! /bin/sh
 3+### BEGIN INIT INFO
 4+# Provides: irc-echoer
 5+# Required-Start: $remote_fs $syslog
 6+# Required-Stop: $remote_fs $syslog
 7+# Default-Start: 2 3 4 5
 8+# Default-Stop: 0 1 6
 9+# Short-Description: Input to IRC echoer
 10+# Description: Input to IRC echoer
 11+### END INIT INFO
 12+
 13+# Author: Ryan Lane <rlane@wikimedia.org>
 14+#
 15+# Do NOT "set -e"
 16+
 17+PATH=/sbin:/usr/sbin:/bin:/usr/bin
 18+DESC="Input to IRC echoer"
 19+DAEMON="/usr/ircecho/bin/ircecho"
 20+USER="nobody"
 21+SCRIPTNAME="/etc/init.d/ircecho"
 22+NAME="ircecho"
 23+
 24+# Exit if the package is not installed
 25+[ -x "$DAEMON" ] || exit 0
 26+
 27+# Read configuration variable file if it is present
 28+[ -r /etc/default/$NAME ] && . /etc/default/$NAME
 29+
 30+# Load the VERBOSE setting and other rcS variables
 31+. /lib/init/vars.sh
 32+
 33+# Define LSB log_* functions.
 34+# Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
 35+. /lib/lsb/init-functions
 36+
 37+#
 38+# Function that starts the daemon/service
 39+#
 40+do_start()
 41+{
 42+ # Return
 43+ # 0 if daemon has been started
 44+ # 1 if daemon was already running
 45+ # 2 if daemon could not be started
 46+ start-stop-daemon --start -m --pidfile=/var/run/ircecho.pid -b --quiet -c $USER --exec $DAEMON --test -- --infile=$INFILE $CHANS $NICK $SERVER > /dev/null \
 47+ || return 1
 48+ start-stop-daemon --start -m --pidfile=/var/run/ircecho.pid -b --quiet -c $USER --exec $DAEMON -- --infile=$INFILE $CHANS $NICK $SERVER \
 49+ || return 2
 50+ # Add code here, if necessary, that waits for the process to be ready
 51+ # to handle requests from services started subsequently which depend
 52+ # on this one. As a last resort, sleep for some time.
 53+}
 54+
 55+#
 56+# Function that stops the daemon/service
 57+#
 58+do_stop()
 59+{
 60+ # Return
 61+ # 0 if daemon has been stopped
 62+ # 1 if daemon was already stopped
 63+ # 2 if daemon could not be stopped
 64+ # other if a failure occurred
 65+ start-stop-daemon --stop --pidfile=/var/run/ircecho.pid --signal 9 --quiet
 66+ RETVAL="$?"
 67+ [ "$RETVAL" = 2 ] && return 2
 68+ return "$RETVAL"
 69+}
 70+
 71+#
 72+# Function that sends a SIGHUP to the daemon/service
 73+#
 74+do_reload() {
 75+ #
 76+ # If the daemon can reload its configuration without
 77+ # restarting (for example, when it is sent a SIGHUP),
 78+ # then implement that here.
 79+ #
 80+ do_stop
 81+ do_start
 82+ return 0
 83+}
 84+
 85+case "$1" in
 86+ start)
 87+ [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
 88+ do_start
 89+ case "$?" in
 90+ 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
 91+ 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
 92+ esac
 93+ ;;
 94+ stop)
 95+ [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
 96+ do_stop
 97+ case "$?" in
 98+ 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
 99+ 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
 100+ esac
 101+ ;;
 102+ status)
 103+ status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
 104+ ;;
 105+ #reload|force-reload)
 106+ #
 107+ # If do_reload() is not implemented then leave this commented out
 108+ # and leave 'force-reload' as an alias for 'restart'.
 109+ #
 110+ #log_daemon_msg "Reloading $DESC" "$NAME"
 111+ #do_reload
 112+ #log_end_msg $?
 113+ #;;
 114+ restart|force-reload)
 115+ #
 116+ # If the "reload" option is implemented then remove the
 117+ # 'force-reload' alias
 118+ #
 119+ log_daemon_msg "Restarting $DESC" "$NAME"
 120+ do_stop
 121+ case "$?" in
 122+ 0|1)
 123+ do_start
 124+ case "$?" in
 125+ 0) log_end_msg 0 ;;
 126+ 1) log_end_msg 1 ;; # Old process is still running
 127+ *) log_end_msg 1 ;; # Failed to start
 128+ esac
 129+ ;;
 130+ *)
 131+ # Failed to stop
 132+ log_end_msg 1
 133+ ;;
 134+ esac
 135+ ;;
 136+ *)
 137+ #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
 138+ echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
 139+ exit 3
 140+ ;;
 141+esac
 142+
 143+:
Index: trunk/debs/ircecho/ircbot.py
@@ -0,0 +1,432 @@
 2+# Copyright (C) 1999--2002 Joel Rosdahl
 3+#
 4+# This library is free software; you can redistribute it and/or
 5+# modify it under the terms of the GNU Lesser General Public
 6+# License as published by the Free Software Foundation; either
 7+# version 2.1 of the License, or (at your option) any later version.
 8+#
 9+# This library is distributed in the hope that it will be useful,
 10+# but WITHOUT ANY WARRANTY; without even the implied warranty of
 11+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 12+# Lesser General Public License for more details.
 13+#
 14+# You should have received a copy of the GNU Lesser General Public
 15+# License along with this library; if not, write to the Free Software
 16+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 17+#
 18+# Joel Rosdahl <joel@rosdahl.net>
 19+#
 20+# $Id: ircbot.py,v 1.14 2003/10/29 21:11:07 jrosdahl Exp $
 21+
 22+"""ircbot -- Simple IRC bot library.
 23+
 24+This module contains a single-server IRC bot class that can be used to
 25+write simpler bots.
 26+"""
 27+
 28+import sys
 29+import string
 30+from UserDict import UserDict
 31+
 32+from irclib import SimpleIRCClient
 33+from irclib import nm_to_n, irc_lower, all_events
 34+from irclib import parse_channel_modes, is_channel
 35+from irclib import ServerConnectionError
 36+
 37+class SingleServerIRCBot(SimpleIRCClient):
 38+ """A single-server IRC bot class.
 39+
 40+ The bot tries to reconnect if it is disconnected.
 41+
 42+ The bot keeps track of the channels it has joined, the other
 43+ clients that are present in the channels and which of those that
 44+ have operator or voice modes. The "database" is kept in the
 45+ self.channels attribute, which is an IRCDict of Channels.
 46+ """
 47+ def __init__(self, server_list, nickname, realname, reconnection_interval=60):
 48+ """Constructor for SingleServerIRCBot objects.
 49+
 50+ Arguments:
 51+
 52+ server_list -- A list of tuples (server, port) that
 53+ defines which servers the bot should try to
 54+ connect to.
 55+
 56+ nickname -- The bot's nickname.
 57+
 58+ realname -- The bot's realname.
 59+
 60+ reconnection_interval -- How long the bot should wait
 61+ before trying to reconnect.
 62+
 63+ dcc_connections -- A list of initiated/accepted DCC
 64+ connections.
 65+ """
 66+
 67+ SimpleIRCClient.__init__(self)
 68+ self.channels = IRCDict()
 69+ self.server_list = server_list
 70+ if not reconnection_interval or reconnection_interval < 0:
 71+ reconnection_interval = 2**31
 72+ self.reconnection_interval = reconnection_interval
 73+
 74+ self._nickname = nickname
 75+ self._realname = realname
 76+ for i in ["disconnect", "join", "kick", "mode",
 77+ "namreply", "nick", "part", "quit"]:
 78+ self.connection.add_global_handler(i,
 79+ getattr(self, "_on_" + i),
 80+ -10)
 81+ def _connected_checker(self):
 82+ """[Internal]"""
 83+ if not self.connection.is_connected():
 84+ self.connection.execute_delayed(self.reconnection_interval,
 85+ self._connected_checker)
 86+ self.jump_server()
 87+
 88+ def _connect(self):
 89+ """[Internal]"""
 90+ password = None
 91+ if len(self.server_list[0]) > 2:
 92+ password = self.server_list[0][2]
 93+ try:
 94+ self.connect(self.server_list[0][0],
 95+ self.server_list[0][1],
 96+ self._nickname,
 97+ password,
 98+ ircname=self._realname)
 99+ except ServerConnectionError:
 100+ pass
 101+
 102+ def _on_disconnect(self, c, e):
 103+ """[Internal]"""
 104+ self.channels = IRCDict()
 105+ self.connection.execute_delayed(self.reconnection_interval,
 106+ self._connected_checker)
 107+
 108+ def _on_join(self, c, e):
 109+ """[Internal]"""
 110+ ch = e.target()
 111+ nick = nm_to_n(e.source())
 112+ if nick == c.get_nickname():
 113+ self.channels[ch] = Channel()
 114+ self.channels[ch].add_user(nick)
 115+
 116+ def _on_kick(self, c, e):
 117+ """[Internal]"""
 118+ nick = e.arguments()[0]
 119+ channel = e.target()
 120+
 121+ if nick == c.get_nickname():
 122+ del self.channels[channel]
 123+ else:
 124+ self.channels[channel].remove_user(nick)
 125+
 126+ def _on_mode(self, c, e):
 127+ """[Internal]"""
 128+ modes = parse_channel_modes(string.join(e.arguments()))
 129+ t = e.target()
 130+ if is_channel(t):
 131+ ch = self.channels[t]
 132+ for mode in modes:
 133+ if mode[0] == "+":
 134+ f = ch.set_mode
 135+ else:
 136+ f = ch.clear_mode
 137+ f(mode[1], mode[2])
 138+ else:
 139+ # Mode on self... XXX
 140+ pass
 141+
 142+ def _on_namreply(self, c, e):
 143+ """[Internal]"""
 144+
 145+ # e.arguments()[0] == "=" (why?)
 146+ # e.arguments()[1] == channel
 147+ # e.arguments()[2] == nick list
 148+
 149+ ch = e.arguments()[1]
 150+ for nick in string.split(e.arguments()[2]):
 151+ if nick[0] == "@":
 152+ nick = nick[1:]
 153+ self.channels[ch].set_mode("o", nick)
 154+ elif nick[0] == "+":
 155+ nick = nick[1:]
 156+ self.channels[ch].set_mode("v", nick)
 157+ self.channels[ch].add_user(nick)
 158+
 159+ def _on_nick(self, c, e):
 160+ """[Internal]"""
 161+ before = nm_to_n(e.source())
 162+ after = e.target()
 163+ for ch in self.channels.values():
 164+ if ch.has_user(before):
 165+ ch.change_nick(before, after)
 166+
 167+ def _on_part(self, c, e):
 168+ """[Internal]"""
 169+ nick = nm_to_n(e.source())
 170+ channel = e.target()
 171+
 172+ if nick == c.get_nickname():
 173+ del self.channels[channel]
 174+ else:
 175+ self.channels[channel].remove_user(nick)
 176+
 177+ def _on_quit(self, c, e):
 178+ """[Internal]"""
 179+ nick = nm_to_n(e.source())
 180+ for ch in self.channels.values():
 181+ if ch.has_user(nick):
 182+ ch.remove_user(nick)
 183+
 184+ def die(self, msg="Bye, cruel world!"):
 185+ """Let the bot die.
 186+
 187+ Arguments:
 188+
 189+ msg -- Quit message.
 190+ """
 191+ self.connection.quit(msg)
 192+ sys.exit(0)
 193+
 194+ def disconnect(self, msg="I'll be back!"):
 195+ """Disconnect the bot.
 196+
 197+ The bot will try to reconnect after a while.
 198+
 199+ Arguments:
 200+
 201+ msg -- Quit message.
 202+ """
 203+ self.connection.quit(msg)
 204+
 205+ def get_version(self):
 206+ """Returns the bot version.
 207+
 208+ Used when answering a CTCP VERSION request.
 209+ """
 210+ return "ircbot.py by Joel Rosdahl <joel@rosdahl.net>"
 211+
 212+ def jump_server(self):
 213+ """Connect to a new server, possibly disconnecting from the current.
 214+
 215+ The bot will skip to next server in the server_list each time
 216+ jump_server is called.
 217+ """
 218+ if self.connection.is_connected():
 219+ self.connection.quit("Jumping servers")
 220+ self.server_list.append(self.server_list.pop(0))
 221+ self._connect()
 222+
 223+ def on_ctcp(self, c, e):
 224+ """Default handler for ctcp events.
 225+
 226+ Replies to VERSION and PING requests and relays DCC requests
 227+ to the on_dccchat method.
 228+ """
 229+ if e.arguments()[0] == "VERSION":
 230+ c.ctcp_reply(nm_to_n(e.source()),
 231+ "VERSION " + self.get_version())
 232+ elif e.arguments()[0] == "PING":
 233+ if len(e.arguments()) > 1:
 234+ c.ctcp_reply(nm_to_n(e.source()),
 235+ "PING " + e.arguments()[1])
 236+ elif e.arguments()[0] == "DCC" and e.arguments()[1] == "CHAT":
 237+ self.on_dccchat(c, e)
 238+
 239+ def on_dccchat(self, c, e):
 240+ pass
 241+
 242+ def start(self):
 243+ """Start the bot."""
 244+ self._connect()
 245+ SimpleIRCClient.start(self)
 246+
 247+
 248+class IRCDict:
 249+ """A dictionary suitable for storing IRC-related things.
 250+
 251+ Dictionary keys a and b are considered equal if and only if
 252+ irc_lower(a) == irc_lower(b)
 253+
 254+ Otherwise, it should behave exactly as a normal dictionary.
 255+ """
 256+
 257+ def __init__(self, dict=None):
 258+ self.data = {}
 259+ self.canon_keys = {} # Canonical keys
 260+ if dict is not None:
 261+ self.update(dict)
 262+ def __repr__(self):
 263+ return repr(self.data)
 264+ def __cmp__(self, dict):
 265+ if isinstance(dict, IRCDict):
 266+ return cmp(self.data, dict.data)
 267+ else:
 268+ return cmp(self.data, dict)
 269+ def __len__(self):
 270+ return len(self.data)
 271+ def __getitem__(self, key):
 272+ return self.data[self.canon_keys[irc_lower(key)]]
 273+ def __setitem__(self, key, item):
 274+ if self.has_key(key):
 275+ del self[key]
 276+ self.data[key] = item
 277+ self.canon_keys[irc_lower(key)] = key
 278+ def __delitem__(self, key):
 279+ ck = irc_lower(key)
 280+ del self.data[self.canon_keys[ck]]
 281+ del self.canon_keys[ck]
 282+ def clear(self):
 283+ self.data.clear()
 284+ self.canon_keys.clear()
 285+ def copy(self):
 286+ if self.__class__ is UserDict:
 287+ return UserDict(self.data)
 288+ import copy
 289+ return copy.copy(self)
 290+ def keys(self):
 291+ return self.data.keys()
 292+ def items(self):
 293+ return self.data.items()
 294+ def values(self):
 295+ return self.data.values()
 296+ def has_key(self, key):
 297+ return self.canon_keys.has_key(irc_lower(key))
 298+ def update(self, dict):
 299+ for k, v in dict.items():
 300+ self.data[k] = v
 301+ def get(self, key, failobj=None):
 302+ return self.data.get(key, failobj)
 303+
 304+
 305+class Channel:
 306+ """A class for keeping information about an IRC channel.
 307+
 308+ This class can be improved a lot.
 309+ """
 310+
 311+ def __init__(self):
 312+ self.userdict = IRCDict()
 313+ self.operdict = IRCDict()
 314+ self.voiceddict = IRCDict()
 315+ self.modes = {}
 316+
 317+ def users(self):
 318+ """Returns an unsorted list of the channel's users."""
 319+ return self.userdict.keys()
 320+
 321+ def opers(self):
 322+ """Returns an unsorted list of the channel's operators."""
 323+ return self.operdict.keys()
 324+
 325+ def voiced(self):
 326+ """Returns an unsorted list of the persons that have voice
 327+ mode set in the channel."""
 328+ return self.voiceddict.keys()
 329+
 330+ def has_user(self, nick):
 331+ """Check whether the channel has a user."""
 332+ return self.userdict.has_key(nick)
 333+
 334+ def is_oper(self, nick):
 335+ """Check whether a user has operator status in the channel."""
 336+ return self.operdict.has_key(nick)
 337+
 338+ def is_voiced(self, nick):
 339+ """Check whether a user has voice mode set in the channel."""
 340+ return self.voiceddict.has_key(nick)
 341+
 342+ def add_user(self, nick):
 343+ self.userdict[nick] = 1
 344+
 345+ def remove_user(self, nick):
 346+ for d in self.userdict, self.operdict, self.voiceddict:
 347+ if d.has_key(nick):
 348+ del d[nick]
 349+
 350+ def change_nick(self, before, after):
 351+ self.userdict[after] = 1
 352+ del self.userdict[before]
 353+ if self.operdict.has_key(before):
 354+ self.operdict[after] = 1
 355+ del self.operdict[before]
 356+ if self.voiceddict.has_key(before):
 357+ self.voiceddict[after] = 1
 358+ del self.voiceddict[before]
 359+
 360+ def set_mode(self, mode, value=None):
 361+ """Set mode on the channel.
 362+
 363+ Arguments:
 364+
 365+ mode -- The mode (a single-character string).
 366+
 367+ value -- Value
 368+ """
 369+ if mode == "o":
 370+ self.operdict[value] = 1
 371+ elif mode == "v":
 372+ self.voiceddict[value] = 1
 373+ else:
 374+ self.modes[mode] = value
 375+
 376+ def clear_mode(self, mode, value=None):
 377+ """Clear mode on the channel.
 378+
 379+ Arguments:
 380+
 381+ mode -- The mode (a single-character string).
 382+
 383+ value -- Value
 384+ """
 385+ try:
 386+ if mode == "o":
 387+ del self.operdict[value]
 388+ elif mode == "v":
 389+ del self.voiceddict[value]
 390+ else:
 391+ del self.modes[mode]
 392+ except KeyError:
 393+ pass
 394+
 395+ def has_mode(self, mode):
 396+ return mode in self.modes
 397+
 398+ def is_moderated(self):
 399+ return self.has_mode("m")
 400+
 401+ def is_secret(self):
 402+ return self.has_mode("s")
 403+
 404+ def is_protected(self):
 405+ return self.has_mode("p")
 406+
 407+ def has_topic_lock(self):
 408+ return self.has_mode("t")
 409+
 410+ def is_invite_only(self):
 411+ return self.has_mode("i")
 412+
 413+ def has_message_from_outside_protection(self):
 414+ # Eh... What should it be called, really?
 415+ return self.has_mode("n")
 416+
 417+ def has_limit(self):
 418+ return self.has_mode("l")
 419+
 420+ def limit(self):
 421+ if self.has_limit():
 422+ return self.modes[l]
 423+ else:
 424+ return None
 425+
 426+ def has_key(self):
 427+ return self.has_mode("k")
 428+
 429+ def key(self):
 430+ if self.has_key():
 431+ return self.modes["k"]
 432+ else:
 433+ return None
Index: trunk/debs/ircecho/irclib.py
@@ -0,0 +1,1554 @@
 2+# Copyright (C) 1999--2002 Joel Rosdahl
 3+#
 4+# This library is free software; you can redistribute it and/or
 5+# modify it under the terms of the GNU Lesser General Public
 6+# License as published by the Free Software Foundation; either
 7+# version 2.1 of the License, or (at your option) any later version.
 8+#
 9+# This library is distributed in the hope that it will be useful,
 10+# but WITHOUT ANY WARRANTY; without even the implied warranty of
 11+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 12+# Lesser General Public License for more details.
 13+#
 14+# You should have received a copy of the GNU Lesser General Public
 15+# License along with this library; if not, write to the Free Software
 16+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 17+#
 18+# Joel Rosdahl <joel@rosdahl.net>
 19+#
 20+# $Id: irclib.py,v 1.23 2004/07/09 08:54:40 jrosdahl Exp $
 21+
 22+"""irclib -- Internet Relay Chat (IRC) protocol client library.
 23+
 24+This library is intended to encapsulate the IRC protocol at a quite
 25+low level. It provides an event-driven IRC client framework. It has
 26+a fairly thorough support for the basic IRC protocol and CTCP, but DCC
 27+connection support is not yet implemented.
 28+
 29+In order to understand how to make an IRC client, I'm afraid you more
 30+or less must understand the IRC specifications. They are available
 31+here: [IRC specifications].
 32+
 33+The main features of the IRC client framework are:
 34+
 35+ * Abstraction of the IRC protocol.
 36+ * Handles multiple simultaneous IRC server connections.
 37+ * Handles server PONGing transparently.
 38+ * Messages to the IRC server are done by calling methods on an IRC
 39+ connection object.
 40+ * Messages from an IRC server triggers events, which can be caught
 41+ by event handlers.
 42+ * Reading from and writing to IRC server sockets are normally done
 43+ by an internal select() loop, but the select()ing may be done by
 44+ an external main loop.
 45+ * Functions can be registered to execute at specified times by the
 46+ event-loop.
 47+ * Decodes CTCP tagging correctly (hopefully); I haven't seen any
 48+ other IRC client implementation that handles the CTCP
 49+ specification subtilties.
 50+ * A kind of simple, single-server, object-oriented IRC client class
 51+ that dispatches events to instance methods is included.
 52+
 53+Current limitations:
 54+
 55+ * The IRC protocol shines through the abstraction a bit too much.
 56+ * Data is not written asynchronously to the server, i.e. the write()
 57+ may block if the TCP buffers are stuffed.
 58+ * There are no support for DCC connections.
 59+ * The author haven't even read RFC 2810, 2811, 2812 and 2813.
 60+ * Like most projects, documentation is lacking...
 61+
 62+Since I seldom use IRC anymore, I will probably not work much on the
 63+library. If you want to help or continue developing the library,
 64+please contact me (Joel Rosdahl <joel@rosdahl.net>).
 65+
 66+.. [IRC specifications] http://www.irchelp.org/irchelp/rfc/
 67+"""
 68+
 69+import bisect
 70+import re
 71+import select
 72+import socket
 73+import string
 74+import sys
 75+import time
 76+import types
 77+
 78+VERSION = 0, 4, 2
 79+DEBUG = 0
 80+
 81+# TODO
 82+# ----
 83+# (maybe) thread safety
 84+# (maybe) color parser convenience functions
 85+# documentation (including all event types)
 86+# (maybe) add awareness of different types of ircds
 87+# send data asynchronously to the server (and DCC connections)
 88+# (maybe) automatically close unused, passive DCC connections after a while
 89+
 90+# NOTES
 91+# -----
 92+# connection.quit() only sends QUIT to the server.
 93+# ERROR from the server triggers the error event and the disconnect event.
 94+# dropping of the connection triggers the disconnect event.
 95+
 96+class IRCError(Exception):
 97+ """Represents an IRC exception."""
 98+ pass
 99+
 100+
 101+class IRC:
 102+ """Class that handles one or several IRC server connections.
 103+
 104+ When an IRC object has been instantiated, it can be used to create
 105+ Connection objects that represent the IRC connections. The
 106+ responsibility of the IRC object is to provide an event-driven
 107+ framework for the connections and to keep the connections alive.
 108+ It runs a select loop to poll each connection's TCP socket and
 109+ hands over the sockets with incoming data for processing by the
 110+ corresponding connection.
 111+
 112+ The methods of most interest for an IRC client writer are server,
 113+ add_global_handler, remove_global_handler, execute_at,
 114+ execute_delayed, process_once and process_forever.
 115+
 116+ Here is an example:
 117+
 118+ irc = irclib.IRC()
 119+ server = irc.server()
 120+ server.connect(\"irc.some.where\", 6667, \"my_nickname\")
 121+ server.privmsg(\"a_nickname\", \"Hi there!\")
 122+ server.process_forever()
 123+
 124+ This will connect to the IRC server irc.some.where on port 6667
 125+ using the nickname my_nickname and send the message \"Hi there!\"
 126+ to the nickname a_nickname.
 127+ """
 128+
 129+ def __init__(self, fn_to_add_socket=None,
 130+ fn_to_remove_socket=None,
 131+ fn_to_add_timeout=None):
 132+ """Constructor for IRC objects.
 133+
 134+ Optional arguments are fn_to_add_socket, fn_to_remove_socket
 135+ and fn_to_add_timeout. The first two specify functions that
 136+ will be called with a socket object as argument when the IRC
 137+ object wants to be notified (or stop being notified) of data
 138+ coming on a new socket. When new data arrives, the method
 139+ process_data should be called. Similarly, fn_to_add_timeout
 140+ is called with a number of seconds (a floating point number)
 141+ as first argument when the IRC object wants to receive a
 142+ notification (by calling the process_timeout method). So, if
 143+ e.g. the argument is 42.17, the object wants the
 144+ process_timeout method to be called after 42 seconds and 170
 145+ milliseconds.
 146+
 147+ The three arguments mainly exist to be able to use an external
 148+ main loop (for example Tkinter's or PyGTK's main app loop)
 149+ instead of calling the process_forever method.
 150+
 151+ An alternative is to just call ServerConnection.process_once()
 152+ once in a while.
 153+ """
 154+
 155+ if fn_to_add_socket and fn_to_remove_socket:
 156+ self.fn_to_add_socket = fn_to_add_socket
 157+ self.fn_to_remove_socket = fn_to_remove_socket
 158+ else:
 159+ self.fn_to_add_socket = None
 160+ self.fn_to_remove_socket = None
 161+
 162+ self.fn_to_add_timeout = fn_to_add_timeout
 163+ self.connections = []
 164+ self.handlers = {}
 165+ self.delayed_commands = [] # list of tuples in the format (time, function, arguments)
 166+
 167+ self.add_global_handler("ping", _ping_ponger, -42)
 168+
 169+ def server(self):
 170+ """Creates and returns a ServerConnection object."""
 171+
 172+ c = ServerConnection(self)
 173+ self.connections.append(c)
 174+ return c
 175+
 176+ def process_data(self, sockets):
 177+ """Called when there is more data to read on connection sockets.
 178+
 179+ Arguments:
 180+
 181+ sockets -- A list of socket objects.
 182+
 183+ See documentation for IRC.__init__.
 184+ """
 185+ for s in sockets:
 186+ for c in self.connections:
 187+ if s == c._get_socket():
 188+ c.process_data()
 189+
 190+ def process_timeout(self):
 191+ """Called when a timeout notification is due.
 192+
 193+ See documentation for IRC.__init__.
 194+ """
 195+ t = time.time()
 196+ while self.delayed_commands:
 197+ if t >= self.delayed_commands[0][0]:
 198+ apply(self.delayed_commands[0][1], self.delayed_commands[0][2])
 199+ del self.delayed_commands[0]
 200+ else:
 201+ break
 202+
 203+ def process_once(self, timeout=0):
 204+ """Process data from connections once.
 205+
 206+ Arguments:
 207+
 208+ timeout -- How long the select() call should wait if no
 209+ data is available.
 210+
 211+ This method should be called periodically to check and process
 212+ incoming data, if there are any. If that seems boring, look
 213+ at the process_forever method.
 214+ """
 215+ sockets = map(lambda x: x._get_socket(), self.connections)
 216+ sockets = filter(lambda x: x != None, sockets)
 217+ if sockets:
 218+ (i, o, e) = select.select(sockets, [], [], timeout)
 219+ self.process_data(i)
 220+ else:
 221+ time.sleep(timeout)
 222+ self.process_timeout()
 223+
 224+ def process_forever(self, timeout=0.2):
 225+ """Run an infinite loop, processing data from connections.
 226+
 227+ This method repeatedly calls process_once.
 228+
 229+ Arguments:
 230+
 231+ timeout -- Parameter to pass to process_once.
 232+ """
 233+ while 1:
 234+ self.process_once(timeout)
 235+
 236+ def disconnect_all(self, message=""):
 237+ """Disconnects all connections."""
 238+ for c in self.connections:
 239+ c.quit(message)
 240+ c.disconnect(message)
 241+
 242+ def add_global_handler(self, event, handler, priority=0):
 243+ """Adds a global handler function for a specific event type.
 244+
 245+ Arguments:
 246+
 247+ event -- Event type (a string). Check the values of the
 248+ numeric_events dictionary in irclib.py for possible event
 249+ types.
 250+
 251+ handler -- Callback function.
 252+
 253+ priority -- A number (the lower number, the higher priority).
 254+
 255+ The handler function is called whenever the specified event is
 256+ triggered in any of the connections. See documentation for
 257+ the Event class.
 258+
 259+ The handler functions are called in priority order (lowest
 260+ number is highest priority). If a handler function returns
 261+ \"NO MORE\", no more handlers will be called.
 262+ """
 263+
 264+ if not self.handlers.has_key(event):
 265+ self.handlers[event] = []
 266+ bisect.insort(self.handlers[event], ((priority, handler)))
 267+
 268+ def remove_global_handler(self, event, handler):
 269+ """Removes a global handler function.
 270+
 271+ Arguments:
 272+
 273+ event -- Event type (a string).
 274+
 275+ handler -- Callback function.
 276+
 277+ Returns 1 on success, otherwise 0.
 278+ """
 279+ if not self.handlers.has_key(event):
 280+ return 0
 281+ for h in self.handlers[event]:
 282+ if handler == h[1]:
 283+ self.handlers[event].remove(h)
 284+ return 1
 285+
 286+ def execute_at(self, at, function, arguments=()):
 287+ """Execute a function at a specified time.
 288+
 289+ Arguments:
 290+
 291+ at -- Execute at this time (standard \"time_t\" time).
 292+
 293+ function -- Function to call.
 294+
 295+ arguments -- Arguments to give the function.
 296+ """
 297+ self.execute_delayed(at-time.time(), function, arguments)
 298+
 299+ def execute_delayed(self, delay, function, arguments=()):
 300+ """Execute a function after a specified time.
 301+
 302+ Arguments:
 303+
 304+ delay -- How many seconds to wait.
 305+
 306+ function -- Function to call.
 307+
 308+ arguments -- Arguments to give the function.
 309+ """
 310+ bisect.insort(self.delayed_commands, (delay+time.time(), function, arguments))
 311+ if self.fn_to_add_timeout:
 312+ self.fn_to_add_timeout(delay)
 313+
 314+ def dcc(self, dcctype="chat"):
 315+ """Creates and returns a DCCConnection object.
 316+
 317+ Arguments:
 318+
 319+ dcctype -- "chat" for DCC CHAT connections or "raw" for
 320+ DCC SEND (or other DCC types). If "chat",
 321+ incoming data will be split in newline-separated
 322+ chunks. If "raw", incoming data is not touched.
 323+ """
 324+ c = DCCConnection(self, dcctype)
 325+ self.connections.append(c)
 326+ return c
 327+
 328+ def _handle_event(self, connection, event):
 329+ """[Internal]"""
 330+ h = self.handlers
 331+ for handler in h.get("all_events", []) + h.get(event.eventtype(), []):
 332+ if handler[1](connection, event) == "NO MORE":
 333+ return
 334+
 335+ def _remove_connection(self, connection):
 336+ """[Internal]"""
 337+ self.connections.remove(connection)
 338+ if self.fn_to_remove_socket:
 339+ self.fn_to_remove_socket(connection._get_socket())
 340+
 341+_rfc_1459_command_regexp = re.compile("^(:(?P<prefix>[^ ]+) +)?(?P<command>[^ ]+)( *(?P<argument> .+))?")
 342+
 343+
 344+class Connection:
 345+ """Base class for IRC connections.
 346+
 347+ Must be overridden.
 348+ """
 349+ def __init__(self, irclibobj):
 350+ self.irclibobj = irclibobj
 351+
 352+ def _get_socket():
 353+ raise IRCError, "Not overridden"
 354+
 355+ ##############################
 356+ ### Convenience wrappers.
 357+
 358+ def execute_at(self, at, function, arguments=()):
 359+ self.irclibobj.execute_at(at, function, arguments)
 360+
 361+ def execute_delayed(self, delay, function, arguments=()):
 362+ self.irclibobj.execute_delayed(delay, function, arguments)
 363+
 364+
 365+class ServerConnectionError(IRCError):
 366+ pass
 367+
 368+class ServerNotConnectedError(ServerConnectionError):
 369+ pass
 370+
 371+
 372+# Huh!? Crrrrazy EFNet doesn't follow the RFC: their ircd seems to
 373+# use \n as message separator! :P
 374+_linesep_regexp = re.compile("\r?\n")
 375+
 376+class ServerConnection(Connection):
 377+ """This class represents an IRC server connection.
 378+
 379+ ServerConnection objects are instantiated by calling the server
 380+ method on an IRC object.
 381+ """
 382+
 383+ def __init__(self, irclibobj):
 384+ Connection.__init__(self, irclibobj)
 385+ self.connected = 0 # Not connected yet.
 386+ self.connect_time = 0
 387+
 388+ def connect(self, server, port, nickname, password=None, username=None,
 389+ ircname=None, localaddress="", localport=0):
 390+ """Connect/reconnect to a server.
 391+
 392+ Arguments:
 393+
 394+ server -- Server name.
 395+
 396+ port -- Port number.
 397+
 398+ nickname -- The nickname.
 399+
 400+ password -- Password (if any).
 401+
 402+ username -- The username.
 403+
 404+ ircname -- The IRC name ("realname").
 405+
 406+ localaddress -- Bind the connection to a specific local IP address.
 407+
 408+ localport -- Bind the connection to a specific local port.
 409+
 410+ This function can be called to reconnect a closed connection.
 411+
 412+ Returns the ServerConnection object.
 413+ """
 414+
 415+ # flood control
 416+ delay = self.connect_time - time.time() + 60
 417+ if self.connect_time and delay > 0:
 418+ time.sleep(delay)
 419+
 420+ self.connect_time = time.time()
 421+
 422+ if self.connected:
 423+ self.quit("Changing server")
 424+
 425+ self.socket = None
 426+ self.previous_buffer = ""
 427+ self.handlers = {}
 428+ self.real_server_name = ""
 429+ self.real_nickname = nickname
 430+ self.server = server
 431+ self.port = port
 432+ self.nickname = nickname
 433+ self.username = username or nickname
 434+ self.ircname = ircname or nickname
 435+ self.password = password
 436+ self.localaddress = localaddress
 437+ self.localport = localport
 438+ self.localhost = socket.gethostname()
 439+ self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 440+ try:
 441+ self.socket.bind((self.localaddress, self.localport))
 442+ self.socket.connect((self.server, self.port))
 443+ except socket.error, x:
 444+ raise ServerConnectionError, "Couldn't connect to socket: %s" % x
 445+ self.connected = 1
 446+ if self.irclibobj.fn_to_add_socket:
 447+ self.irclibobj.fn_to_add_socket(self.socket)
 448+
 449+ # Log on...
 450+ if self.password:
 451+ self.pass_(self.password)
 452+ self.nick(self.nickname)
 453+ self.user(self.username, self.ircname)
 454+ return self
 455+
 456+ def close(self):
 457+ """Close the connection.
 458+
 459+ This method closes the connection permanently; after it has
 460+ been called, the object is unusable.
 461+ """
 462+
 463+ self.disconnect("Closing object")
 464+ self.irclibobj._remove_connection(self)
 465+
 466+ def _get_socket(self):
 467+ """[Internal]"""
 468+ return self.socket
 469+
 470+ def get_server_name(self):
 471+ """Get the (real) server name.
 472+
 473+ This method returns the (real) server name, or, more
 474+ specifically, what the server calls itself.
 475+ """
 476+
 477+ if self.real_server_name:
 478+ return self.real_server_name
 479+ else:
 480+ return ""
 481+
 482+ def get_nickname(self):
 483+ """Get the (real) nick name.
 484+
 485+ This method returns the (real) nickname. The library keeps
 486+ track of nick changes, so it might not be the nick name that
 487+ was passed to the connect() method. """
 488+
 489+ return self.real_nickname
 490+
 491+ def process_data(self):
 492+ """[Internal]"""
 493+
 494+ try:
 495+ new_data = self.socket.recv(2**14)
 496+ except socket.error, x:
 497+ # The server hung up.
 498+ self.disconnect("Connection reset by peer")
 499+ return
 500+ if not new_data:
 501+ # Read nothing: connection must be down.
 502+ self.disconnect("Connection reset by peer")
 503+ return
 504+
 505+ lines = _linesep_regexp.split(self.previous_buffer + new_data)
 506+
 507+ # Save the last, unfinished line.
 508+ self.previous_buffer = lines[-1]
 509+ lines = lines[:-1]
 510+
 511+ for line in lines:
 512+ if DEBUG:
 513+ print "FROM SERVER:", line
 514+
 515+ if not line:
 516+ continue
 517+
 518+ prefix = None
 519+ command = None
 520+ arguments = None
 521+ self._handle_event(Event("all_raw_messages",
 522+ self.get_server_name(),
 523+ None,
 524+ [line]))
 525+
 526+ m = _rfc_1459_command_regexp.match(line)
 527+ if m.group("prefix"):
 528+ prefix = m.group("prefix")
 529+ if not self.real_server_name:
 530+ self.real_server_name = prefix
 531+
 532+ if m.group("command"):
 533+ command = string.lower(m.group("command"))
 534+
 535+ if m.group("argument"):
 536+ a = string.split(m.group("argument"), " :", 1)
 537+ arguments = string.split(a[0])
 538+ if len(a) == 2:
 539+ arguments.append(a[1])
 540+
 541+ if command == "nick":
 542+ if nm_to_n(prefix) == self.real_nickname:
 543+ self.real_nickname = arguments[0]
 544+ elif command == "001":
 545+ # Record the nickname in case the client changed nick
 546+ # in a nicknameinuse callback.
 547+ self.real_nickname = arguments[0]
 548+
 549+ if command in ["privmsg", "notice"]:
 550+ target, message = arguments[0], arguments[1]
 551+ messages = _ctcp_dequote(message)
 552+
 553+ if command == "privmsg":
 554+ if is_channel(target):
 555+ command = "pubmsg"
 556+ else:
 557+ if is_channel(target):
 558+ command = "pubnotice"
 559+ else:
 560+ command = "privnotice"
 561+
 562+ for m in messages:
 563+ if type(m) is types.TupleType:
 564+ if command in ["privmsg", "pubmsg"]:
 565+ command = "ctcp"
 566+ else:
 567+ command = "ctcpreply"
 568+
 569+ m = list(m)
 570+ if DEBUG:
 571+ print "command: %s, source: %s, target: %s, arguments: %s" % (
 572+ command, prefix, target, m)
 573+ self._handle_event(Event(command, prefix, target, m))
 574+ else:
 575+ if DEBUG:
 576+ print "command: %s, source: %s, target: %s, arguments: %s" % (
 577+ command, prefix, target, [m])
 578+ self._handle_event(Event(command, prefix, target, [m]))
 579+ else:
 580+ target = None
 581+
 582+ if command == "quit":
 583+ arguments = [arguments[0]]
 584+ elif command == "ping":
 585+ target = arguments[0]
 586+ else:
 587+ target = arguments[0]
 588+ arguments = arguments[1:]
 589+
 590+ if command == "mode":
 591+ if not is_channel(target):
 592+ command = "umode"
 593+
 594+ # Translate numerics into more readable strings.
 595+ if numeric_events.has_key(command):
 596+ command = numeric_events[command]
 597+
 598+ if DEBUG:
 599+ print "command: %s, source: %s, target: %s, arguments: %s" % (
 600+ command, prefix, target, arguments)
 601+ self._handle_event(Event(command, prefix, target, arguments))
 602+
 603+ def _handle_event(self, event):
 604+ """[Internal]"""
 605+ self.irclibobj._handle_event(self, event)
 606+ if self.handlers.has_key(event.eventtype()):
 607+ for fn in self.handlers[event.eventtype()]:
 608+ fn(self, event)
 609+
 610+ def is_connected(self):
 611+ """Return connection status.
 612+
 613+ Returns true if connected, otherwise false.
 614+ """
 615+ return self.connected
 616+
 617+ def add_global_handler(self, *args):
 618+ """Add global handler.
 619+
 620+ See documentation for IRC.add_global_handler.
 621+ """
 622+ apply(self.irclibobj.add_global_handler, args)
 623+
 624+ def remove_global_handler(self, *args):
 625+ """Remove global handler.
 626+
 627+ See documentation for IRC.remove_global_handler.
 628+ """
 629+ apply(self.irclibobj.remove_global_handler, args)
 630+
 631+ def action(self, target, action):
 632+ """Send a CTCP ACTION command."""
 633+ self.ctcp("ACTION", target, action)
 634+
 635+ def admin(self, server=""):
 636+ """Send an ADMIN command."""
 637+ self.send_raw(string.strip(string.join(["ADMIN", server])))
 638+
 639+ def ctcp(self, ctcptype, target, parameter=""):
 640+ """Send a CTCP command."""
 641+ ctcptype = string.upper(ctcptype)
 642+ self.privmsg(target, "\001%s%s\001" % (ctcptype, parameter and (" " + parameter) or ""))
 643+
 644+ def ctcp_reply(self, target, parameter):
 645+ """Send a CTCP REPLY command."""
 646+ self.notice(target, "\001%s\001" % parameter)
 647+
 648+ def disconnect(self, message=""):
 649+ """Hang up the connection.
 650+
 651+ Arguments:
 652+
 653+ message -- Quit message.
 654+ """
 655+ if not self.connected:
 656+ return
 657+
 658+ self.connected = 0
 659+ try:
 660+ self.socket.close()
 661+ except socket.error, x:
 662+ pass
 663+ self.socket = None
 664+ self._handle_event(Event("disconnect", self.server, "", [message]))
 665+
 666+ def globops(self, text):
 667+ """Send a GLOBOPS command."""
 668+ self.send_raw("GLOBOPS :" + text)
 669+
 670+ def info(self, server=""):
 671+ """Send an INFO command."""
 672+ self.send_raw(string.strip(string.join(["INFO", server])))
 673+
 674+ def invite(self, nick, channel):
 675+ """Send an INVITE command."""
 676+ self.send_raw(string.strip(string.join(["INVITE", nick, channel])))
 677+
 678+ def ison(self, nicks):
 679+ """Send an ISON command.
 680+
 681+ Arguments:
 682+
 683+ nicks -- List of nicks.
 684+ """
 685+ self.send_raw("ISON " + string.join(nicks, " "))
 686+
 687+ def join(self, channel, key=""):
 688+ """Send a JOIN command."""
 689+ self.send_raw("JOIN %s%s" % (channel, (key and (" " + key))))
 690+
 691+ def kick(self, channel, nick, comment=""):
 692+ """Send a KICK command."""
 693+ self.send_raw("KICK %s %s%s" % (channel, nick, (comment and (" :" + comment))))
 694+
 695+ def links(self, remote_server="", server_mask=""):
 696+ """Send a LINKS command."""
 697+ command = "LINKS"
 698+ if remote_server:
 699+ command = command + " " + remote_server
 700+ if server_mask:
 701+ command = command + " " + server_mask
 702+ self.send_raw(command)
 703+
 704+ def list(self, channels=None, server=""):
 705+ """Send a LIST command."""
 706+ command = "LIST"
 707+ if channels:
 708+ command = command + " " + string.join(channels, ",")
 709+ if server:
 710+ command = command + " " + server
 711+ self.send_raw(command)
 712+
 713+ def lusers(self, server=""):
 714+ """Send a LUSERS command."""
 715+ self.send_raw("LUSERS" + (server and (" " + server)))
 716+
 717+ def mode(self, target, command):
 718+ """Send a MODE command."""
 719+ self.send_raw("MODE %s %s" % (target, command))
 720+
 721+ def motd(self, server=""):
 722+ """Send an MOTD command."""
 723+ self.send_raw("MOTD" + (server and (" " + server)))
 724+
 725+ def names(self, channels=None):
 726+ """Send a NAMES command."""
 727+ self.send_raw("NAMES" + (channels and (" " + string.join(channels, ",")) or ""))
 728+
 729+ def nick(self, newnick):
 730+ """Send a NICK command."""
 731+ self.send_raw("NICK " + newnick)
 732+
 733+ def notice(self, target, text):
 734+ """Send a NOTICE command."""
 735+ # Should limit len(text) here!
 736+ self.send_raw("NOTICE %s :%s" % (target, text))
 737+
 738+ def oper(self, nick, password):
 739+ """Send an OPER command."""
 740+ self.send_raw("OPER %s %s" % (nick, password))
 741+
 742+ def part(self, channels):
 743+ """Send a PART command."""
 744+ if type(channels) == types.StringType:
 745+ self.send_raw("PART " + channels)
 746+ else:
 747+ self.send_raw("PART " + string.join(channels, ","))
 748+
 749+ def pass_(self, password):
 750+ """Send a PASS command."""
 751+ self.send_raw("PASS " + password)
 752+
 753+ def ping(self, target, target2=""):
 754+ """Send a PING command."""
 755+ self.send_raw("PING %s%s" % (target, target2 and (" " + target2)))
 756+
 757+ def pong(self, target, target2=""):
 758+ """Send a PONG command."""
 759+ self.send_raw("PONG %s%s" % (target, target2 and (" " + target2)))
 760+
 761+ def privmsg(self, target, text):
 762+ """Send a PRIVMSG command."""
 763+ # Should limit len(text) here!
 764+ self.send_raw("PRIVMSG %s :%s" % (target, text))
 765+
 766+ def privmsg_many(self, targets, text):
 767+ """Send a PRIVMSG command to multiple targets."""
 768+ # Should limit len(text) here!
 769+ self.send_raw("PRIVMSG %s :%s" % (string.join(targets, ","), text))
 770+
 771+ def quit(self, message=""):
 772+ """Send a QUIT command."""
 773+ self.send_raw("QUIT" + (message and (" :" + message)))
 774+
 775+ def sconnect(self, target, port="", server=""):
 776+ """Send an SCONNECT command."""
 777+ self.send_raw("CONNECT %s%s%s" % (target,
 778+ port and (" " + port),
 779+ server and (" " + server)))
 780+
 781+ def send_raw(self, string):
 782+ """Send raw string to the server.
 783+
 784+ The string will be padded with appropriate CR LF.
 785+ """
 786+ if self.socket is None:
 787+ raise ServerNotConnectedError, "Not connected."
 788+ try:
 789+ self.socket.send(string + "\r\n")
 790+ if DEBUG:
 791+ print "TO SERVER:", string
 792+ except socket.error, x:
 793+ # Ouch!
 794+ self.disconnect("Connection reset by peer.")
 795+
 796+ def squit(self, server, comment=""):
 797+ """Send an SQUIT command."""
 798+ self.send_raw("SQUIT %s%s" % (server, comment and (" :" + comment)))
 799+
 800+ def stats(self, statstype, server=""):
 801+ """Send a STATS command."""
 802+ self.send_raw("STATS %s%s" % (statstype, server and (" " + server)))
 803+
 804+ def time(self, server=""):
 805+ """Send a TIME command."""
 806+ self.send_raw("TIME" + (server and (" " + server)))
 807+
 808+ def topic(self, channel, new_topic=None):
 809+ """Send a TOPIC command."""
 810+ if new_topic == None:
 811+ self.send_raw("TOPIC " + channel)
 812+ else:
 813+ self.send_raw("TOPIC %s :%s" % (channel, new_topic))
 814+
 815+ def trace(self, target=""):
 816+ """Send a TRACE command."""
 817+ self.send_raw("TRACE" + (target and (" " + target)))
 818+
 819+ def user(self, username, realname):
 820+ """Send a USER command."""
 821+ self.send_raw("USER %s 0 * :%s" % (username, realname))
 822+
 823+ def userhost(self, nicks):
 824+ """Send a USERHOST command."""
 825+ self.send_raw("USERHOST " + string.join(nicks, ","))
 826+
 827+ def users(self, server=""):
 828+ """Send a USERS command."""
 829+ self.send_raw("USERS" + (server and (" " + server)))
 830+
 831+ def version(self, server=""):
 832+ """Send a VERSION command."""
 833+ self.send_raw("VERSION" + (server and (" " + server)))
 834+
 835+ def wallops(self, text):
 836+ """Send a WALLOPS command."""
 837+ self.send_raw("WALLOPS :" + text)
 838+
 839+ def who(self, target="", op=""):
 840+ """Send a WHO command."""
 841+ self.send_raw("WHO%s%s" % (target and (" " + target), op and (" o")))
 842+
 843+ def whois(self, targets):
 844+ """Send a WHOIS command."""
 845+ self.send_raw("WHOIS " + string.join(targets, ","))
 846+
 847+ def whowas(self, nick, max="", server=""):
 848+ """Send a WHOWAS command."""
 849+ self.send_raw("WHOWAS %s%s%s" % (nick,
 850+ max and (" " + max),
 851+ server and (" " + server)))
 852+
 853+
 854+class DCCConnectionError(IRCError):
 855+ pass
 856+
 857+
 858+class DCCConnection(Connection):
 859+ """This class represents a DCC connection.
 860+
 861+ DCCConnection objects are instantiated by calling the dcc
 862+ method on an IRC object.
 863+ """
 864+ def __init__(self, irclibobj, dcctype):
 865+ Connection.__init__(self, irclibobj)
 866+ self.connected = 0
 867+ self.passive = 0
 868+ self.dcctype = dcctype
 869+ self.peeraddress = None
 870+ self.peerport = None
 871+
 872+ def connect(self, address, port):
 873+ """Connect/reconnect to a DCC peer.
 874+
 875+ Arguments:
 876+ address -- Host/IP address of the peer.
 877+
 878+ port -- The port number to connect to.
 879+
 880+ Returns the DCCConnection object.
 881+ """
 882+ self.peeraddress = socket.gethostbyname(address)
 883+ self.peerport = port
 884+ self.socket = None
 885+ self.previous_buffer = ""
 886+ self.handlers = {}
 887+ self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 888+ self.passive = 0
 889+ try:
 890+ self.socket.connect((self.peeraddress, self.peerport))
 891+ except socket.error, x:
 892+ raise DCCConnectionError, "Couldn't connect to socket: %s" % x
 893+ self.connected = 1
 894+ if self.irclibobj.fn_to_add_socket:
 895+ self.irclibobj.fn_to_add_socket(self.socket)
 896+ return self
 897+
 898+ def listen(self):
 899+ """Wait for a connection/reconnection from a DCC peer.
 900+
 901+ Returns the DCCConnection object.
 902+
 903+ The local IP address and port are available as
 904+ self.localaddress and self.localport. After connection from a
 905+ peer, the peer address and port are available as
 906+ self.peeraddress and self.peerport.
 907+ """
 908+ self.previous_buffer = ""
 909+ self.handlers = {}
 910+ self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 911+ self.passive = 1
 912+ try:
 913+ self.socket.bind((socket.gethostbyname(socket.gethostname()), 0))
 914+ self.localaddress, self.localport = self.socket.getsockname()
 915+ self.socket.listen(10)
 916+ except socket.error, x:
 917+ raise DCCConnectionError, "Couldn't bind socket: %s" % x
 918+ return self
 919+
 920+ def disconnect(self, message=""):
 921+ """Hang up the connection and close the object.
 922+
 923+ Arguments:
 924+
 925+ message -- Quit message.
 926+ """
 927+ if not self.connected:
 928+ return
 929+
 930+ self.connected = 0
 931+ try:
 932+ self.socket.close()
 933+ except socket.error, x:
 934+ pass
 935+ self.socket = None
 936+ self.irclibobj._handle_event(
 937+ self,
 938+ Event("dcc_disconnect", self.peeraddress, "", [message]))
 939+ self.irclibobj._remove_connection(self)
 940+
 941+ def process_data(self):
 942+ """[Internal]"""
 943+
 944+ if self.passive and not self.connected:
 945+ conn, (self.peeraddress, self.peerport) = self.socket.accept()
 946+ self.socket.close()
 947+ self.socket = conn
 948+ self.connected = 1
 949+ if DEBUG:
 950+ print "DCC connection from %s:%d" % (
 951+ self.peeraddress, self.peerport)
 952+ self.irclibobj._handle_event(
 953+ self,
 954+ Event("dcc_connect", self.peeraddress, None, None))
 955+ return
 956+
 957+ try:
 958+ new_data = self.socket.recv(2**14)
 959+ except socket.error, x:
 960+ # The server hung up.
 961+ self.disconnect("Connection reset by peer")
 962+ return
 963+ if not new_data:
 964+ # Read nothing: connection must be down.
 965+ self.disconnect("Connection reset by peer")
 966+ return
 967+
 968+ if self.dcctype == "chat":
 969+ # The specification says lines are terminated with LF, but
 970+ # it seems safer to handle CR LF terminations too.
 971+ chunks = _linesep_regexp.split(self.previous_buffer + new_data)
 972+
 973+ # Save the last, unfinished line.
 974+ self.previous_buffer = chunks[-1]
 975+ if len(self.previous_buffer) > 2**14:
 976+ # Bad peer! Naughty peer!
 977+ self.disconnect()
 978+ return
 979+ chunks = chunks[:-1]
 980+ else:
 981+ chunks = [new_data]
 982+
 983+ command = "dccmsg"
 984+ prefix = self.peeraddress
 985+ target = None
 986+ for chunk in chunks:
 987+ if DEBUG:
 988+ print "FROM PEER:", chunk
 989+ arguments = [chunk]
 990+ if DEBUG:
 991+ print "command: %s, source: %s, target: %s, arguments: %s" % (
 992+ command, prefix, target, arguments)
 993+ self.irclibobj._handle_event(
 994+ self,
 995+ Event(command, prefix, target, arguments))
 996+
 997+ def _get_socket(self):
 998+ """[Internal]"""
 999+ return self.socket
 1000+
 1001+ def privmsg(self, string):
 1002+ """Send data to DCC peer.
 1003+
 1004+ The string will be padded with appropriate LF if it's a DCC
 1005+ CHAT session.
 1006+ """
 1007+ try:
 1008+ self.socket.send(string)
 1009+ if self.dcctype == "chat":
 1010+ self.socket.send("\n")
 1011+ if DEBUG:
 1012+ print "TO PEER: %s\n" % string
 1013+ except socket.error, x:
 1014+ # Ouch!
 1015+ self.disconnect("Connection reset by peer.")
 1016+
 1017+class SimpleIRCClient:
 1018+ """A simple single-server IRC client class.
 1019+
 1020+ This is an example of an object-oriented wrapper of the IRC
 1021+ framework. A real IRC client can be made by subclassing this
 1022+ class and adding appropriate methods.
 1023+
 1024+ The method on_join will be called when a "join" event is created
 1025+ (which is done when the server sends a JOIN messsage/command),
 1026+ on_privmsg will be called for "privmsg" events, and so on. The
 1027+ handler methods get two arguments: the connection object (same as
 1028+ self.connection) and the event object.
 1029+
 1030+ Instance attributes that can be used by sub classes:
 1031+
 1032+ ircobj -- The IRC instance.
 1033+
 1034+ connection -- The ServerConnection instance.
 1035+
 1036+ dcc_connections -- A list of DCCConnection instances.
 1037+ """
 1038+ def __init__(self):
 1039+ self.ircobj = IRC()
 1040+ self.connection = self.ircobj.server()
 1041+ self.dcc_connections = []
 1042+ self.ircobj.add_global_handler("all_events", self._dispatcher, -10)
 1043+ self.ircobj.add_global_handler("dcc_disconnect", self._dcc_disconnect, -10)
 1044+
 1045+ def _dispatcher(self, c, e):
 1046+ """[Internal]"""
 1047+ m = "on_" + e.eventtype()
 1048+ if hasattr(self, m):
 1049+ getattr(self, m)(c, e)
 1050+
 1051+ def _dcc_disconnect(self, c, e):
 1052+ self.dcc_connections.remove(c)
 1053+
 1054+ def connect(self, server, port, nickname, password=None, username=None,
 1055+ ircname=None, localaddress="", localport=0):
 1056+ """Connect/reconnect to a server.
 1057+
 1058+ Arguments:
 1059+
 1060+ server -- Server name.
 1061+
 1062+ port -- Port number.
 1063+
 1064+ nickname -- The nickname.
 1065+
 1066+ password -- Password (if any).
 1067+
 1068+ username -- The username.
 1069+
 1070+ ircname -- The IRC name.
 1071+
 1072+ localaddress -- Bind the connection to a specific local IP address.
 1073+
 1074+ localport -- Bind the connection to a specific local port.
 1075+
 1076+ This function can be called to reconnect a closed connection.
 1077+ """
 1078+ self.connection.connect(server, port, nickname,
 1079+ password, username, ircname,
 1080+ localaddress, localport)
 1081+
 1082+ def dcc_connect(self, address, port, dcctype="chat"):
 1083+ """Connect to a DCC peer.
 1084+
 1085+ Arguments:
 1086+
 1087+ address -- IP address of the peer.
 1088+
 1089+ port -- Port to connect to.
 1090+
 1091+ Returns a DCCConnection instance.
 1092+ """
 1093+ dcc = self.ircobj.dcc(dcctype)
 1094+ self.dcc_connections.append(dcc)
 1095+ dcc.connect(address, port)
 1096+ return dcc
 1097+
 1098+ def dcc_listen(self, dcctype="chat"):
 1099+ """Listen for connections from a DCC peer.
 1100+
 1101+ Returns a DCCConnection instance.
 1102+ """
 1103+ dcc = self.ircobj.dcc(dcctype)
 1104+ self.dcc_connections.append(dcc)
 1105+ dcc.listen()
 1106+ return dcc
 1107+
 1108+ def start(self):
 1109+ """Start the IRC client."""
 1110+ self.ircobj.process_forever()
 1111+
 1112+
 1113+class Event:
 1114+ """Class representing an IRC event."""
 1115+ def __init__(self, eventtype, source, target, arguments=None):
 1116+ """Constructor of Event objects.
 1117+
 1118+ Arguments:
 1119+
 1120+ eventtype -- A string describing the event.
 1121+
 1122+ source -- The originator of the event (a nick mask or a server). XXX Correct?
 1123+
 1124+ target -- The target of the event (a nick or a channel). XXX Correct?
 1125+
 1126+ arguments -- Any event specific arguments.
 1127+ """
 1128+ self._eventtype = eventtype
 1129+ self._source = source
 1130+ self._target = target
 1131+ if arguments:
 1132+ self._arguments = arguments
 1133+ else:
 1134+ self._arguments = []
 1135+
 1136+ def eventtype(self):
 1137+ """Get the event type."""
 1138+ return self._eventtype
 1139+
 1140+ def source(self):
 1141+ """Get the event source."""
 1142+ return self._source
 1143+
 1144+ def target(self):
 1145+ """Get the event target."""
 1146+ return self._target
 1147+
 1148+ def arguments(self):
 1149+ """Get the event arguments."""
 1150+ return self._arguments
 1151+
 1152+_LOW_LEVEL_QUOTE = "\020"
 1153+_CTCP_LEVEL_QUOTE = "\134"
 1154+_CTCP_DELIMITER = "\001"
 1155+
 1156+_low_level_mapping = {
 1157+ "0": "\000",
 1158+ "n": "\n",
 1159+ "r": "\r",
 1160+ _LOW_LEVEL_QUOTE: _LOW_LEVEL_QUOTE
 1161+}
 1162+
 1163+_low_level_regexp = re.compile(_LOW_LEVEL_QUOTE + "(.)")
 1164+
 1165+def mask_matches(nick, mask):
 1166+ """Check if a nick matches a mask.
 1167+
 1168+ Returns true if the nick matches, otherwise false.
 1169+ """
 1170+ nick = irc_lower(nick)
 1171+ mask = irc_lower(mask)
 1172+ mask = string.replace(mask, "\\", "\\\\")
 1173+ for ch in ".$|[](){}+":
 1174+ mask = string.replace(mask, ch, "\\" + ch)
 1175+ mask = string.replace(mask, "?", ".")
 1176+ mask = string.replace(mask, "*", ".*")
 1177+ r = re.compile(mask, re.IGNORECASE)
 1178+ return r.match(nick)
 1179+
 1180+_alpha = "abcdefghijklmnopqrstuvwxyz"
 1181+_special = "-[]\\`^{}"
 1182+nick_characters = _alpha + string.upper(_alpha) + string.digits + _special
 1183+_ircstring_translation = string.maketrans(string.upper(_alpha) + "[]\\^",
 1184+ _alpha + "{}|~")
 1185+
 1186+def irc_lower(s):
 1187+ """Returns a lowercased string.
 1188+
 1189+ The definition of lowercased comes from the IRC specification (RFC
 1190+ 1459).
 1191+ """
 1192+ return string.translate(s, _ircstring_translation)
 1193+
 1194+def _ctcp_dequote(message):
 1195+ """[Internal] Dequote a message according to CTCP specifications.
 1196+
 1197+ The function returns a list where each element can be either a
 1198+ string (normal message) or a tuple of one or two strings (tagged
 1199+ messages). If a tuple has only one element (ie is a singleton),
 1200+ that element is the tag; otherwise the tuple has two elements: the
 1201+ tag and the data.
 1202+
 1203+ Arguments:
 1204+
 1205+ message -- The message to be decoded.
 1206+ """
 1207+
 1208+ def _low_level_replace(match_obj):
 1209+ ch = match_obj.group(1)
 1210+
 1211+ # If low_level_mapping doesn't have the character as key, we
 1212+ # should just return the character.
 1213+ return _low_level_mapping.get(ch, ch)
 1214+
 1215+ if _LOW_LEVEL_QUOTE in message:
 1216+ # Yup, there was a quote. Release the dequoter, man!
 1217+ message = _low_level_regexp.sub(_low_level_replace, message)
 1218+
 1219+ if _CTCP_DELIMITER not in message:
 1220+ return [message]
 1221+ else:
 1222+ # Split it into parts. (Does any IRC client actually *use*
 1223+ # CTCP stacking like this?)
 1224+ chunks = string.split(message, _CTCP_DELIMITER)
 1225+
 1226+ messages = []
 1227+ i = 0
 1228+ while i < len(chunks)-1:
 1229+ # Add message if it's non-empty.
 1230+ if len(chunks[i]) > 0:
 1231+ messages.append(chunks[i])
 1232+
 1233+ if i < len(chunks)-2:
 1234+ # Aye! CTCP tagged data ahead!
 1235+ messages.append(tuple(string.split(chunks[i+1], " ", 1)))
 1236+
 1237+ i = i + 2
 1238+
 1239+ if len(chunks) % 2 == 0:
 1240+ # Hey, a lonely _CTCP_DELIMITER at the end! This means
 1241+ # that the last chunk, including the delimiter, is a
 1242+ # normal message! (This is according to the CTCP
 1243+ # specification.)
 1244+ messages.append(_CTCP_DELIMITER + chunks[-1])
 1245+
 1246+ return messages
 1247+
 1248+def is_channel(string):
 1249+ """Check if a string is a channel name.
 1250+
 1251+ Returns true if the argument is a channel name, otherwise false.
 1252+ """
 1253+ return string and string[0] in "#&+!"
 1254+
 1255+def ip_numstr_to_quad(num):
 1256+ """Convert an IP number as an integer given in ASCII
 1257+ representation (e.g. '3232235521') to an IP address string
 1258+ (e.g. '192.168.0.1')."""
 1259+ n = long(num)
 1260+ p = map(str, map(int, [n >> 24 & 0xFF, n >> 16 & 0xFF,
 1261+ n >> 8 & 0xFF, n & 0xFF]))
 1262+ return string.join(p, ".")
 1263+
 1264+def ip_quad_to_numstr(quad):
 1265+ """Convert an IP address string (e.g. '192.168.0.1') to an IP
 1266+ number as an integer given in ASCII representation
 1267+ (e.g. '3232235521')."""
 1268+ p = map(long, string.split(quad, "."))
 1269+ s = str((p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3])
 1270+ if s[-1] == "L":
 1271+ s = s[:-1]
 1272+ return s
 1273+
 1274+def nm_to_n(s):
 1275+ """Get the nick part of a nickmask.
 1276+
 1277+ (The source of an Event is a nickmask.)
 1278+ """
 1279+ return string.split(s, "!")[0]
 1280+
 1281+def nm_to_uh(s):
 1282+ """Get the userhost part of a nickmask.
 1283+
 1284+ (The source of an Event is a nickmask.)
 1285+ """
 1286+ return string.split(s, "!")[1]
 1287+
 1288+def nm_to_h(s):
 1289+ """Get the host part of a nickmask.
 1290+
 1291+ (The source of an Event is a nickmask.)
 1292+ """
 1293+ return string.split(s, "@")[1]
 1294+
 1295+def nm_to_u(s):
 1296+ """Get the user part of a nickmask.
 1297+
 1298+ (The source of an Event is a nickmask.)
 1299+ """
 1300+ s = string.split(s, "!")[1]
 1301+ return string.split(s, "@")[0]
 1302+
 1303+def parse_nick_modes(mode_string):
 1304+ """Parse a nick mode string.
 1305+
 1306+ The function returns a list of lists with three members: sign,
 1307+ mode and argument. The sign is \"+\" or \"-\". The argument is
 1308+ always None.
 1309+
 1310+ Example:
 1311+
 1312+ >>> irclib.parse_nick_modes(\"+ab-c\")
 1313+ [['+', 'a', None], ['+', 'b', None], ['-', 'c', None]]
 1314+ """
 1315+
 1316+ return _parse_modes(mode_string, "")
 1317+
 1318+def parse_channel_modes(mode_string):
 1319+ """Parse a channel mode string.
 1320+
 1321+ The function returns a list of lists with three members: sign,
 1322+ mode and argument. The sign is \"+\" or \"-\". The argument is
 1323+ None if mode isn't one of \"b\", \"k\", \"l\", \"v\" or \"o\".
 1324+
 1325+ Example:
 1326+
 1327+ >>> irclib.parse_channel_modes(\"+ab-c foo\")
 1328+ [['+', 'a', None], ['+', 'b', 'foo'], ['-', 'c', None]]
 1329+ """
 1330+
 1331+ return _parse_modes(mode_string, "bklvo")
 1332+
 1333+def _parse_modes(mode_string, unary_modes=""):
 1334+ """[Internal]"""
 1335+ modes = []
 1336+ arg_count = 0
 1337+
 1338+ # State variable.
 1339+ sign = ""
 1340+
 1341+ a = string.split(mode_string)
 1342+ if len(a) == 0:
 1343+ return []
 1344+ else:
 1345+ mode_part, args = a[0], a[1:]
 1346+
 1347+ if mode_part[0] not in "+-":
 1348+ return []
 1349+ for ch in mode_part:
 1350+ if ch in "+-":
 1351+ sign = ch
 1352+ elif ch == " ":
 1353+ collecting_arguments = 1
 1354+ elif ch in unary_modes:
 1355+ if len(args) >= arg_count + 1:
 1356+ modes.append([sign, ch, args[arg_count]])
 1357+ arg_count = arg_count + 1
 1358+ else:
 1359+ modes.append([sign, ch, None])
 1360+ else:
 1361+ modes.append([sign, ch, None])
 1362+ return modes
 1363+
 1364+def _ping_ponger(connection, event):
 1365+ """[Internal]"""
 1366+ connection.pong(event.target())
 1367+
 1368+# Numeric table mostly stolen from the Perl IRC module (Net::IRC).
 1369+numeric_events = {
 1370+ "001": "welcome",
 1371+ "002": "yourhost",
 1372+ "003": "created",
 1373+ "004": "myinfo",
 1374+ "005": "featurelist", # XXX
 1375+ "200": "tracelink",
 1376+ "201": "traceconnecting",
 1377+ "202": "tracehandshake",
 1378+ "203": "traceunknown",
 1379+ "204": "traceoperator",
 1380+ "205": "traceuser",
 1381+ "206": "traceserver",
 1382+ "207": "traceservice",
 1383+ "208": "tracenewtype",
 1384+ "209": "traceclass",
 1385+ "210": "tracereconnect",
 1386+ "211": "statslinkinfo",
 1387+ "212": "statscommands",
 1388+ "213": "statscline",
 1389+ "214": "statsnline",
 1390+ "215": "statsiline",
 1391+ "216": "statskline",
 1392+ "217": "statsqline",
 1393+ "218": "statsyline",
 1394+ "219": "endofstats",
 1395+ "221": "umodeis",
 1396+ "231": "serviceinfo",
 1397+ "232": "endofservices",
 1398+ "233": "service",
 1399+ "234": "servlist",
 1400+ "235": "servlistend",
 1401+ "241": "statslline",
 1402+ "242": "statsuptime",
 1403+ "243": "statsoline",
 1404+ "244": "statshline",
 1405+ "250": "luserconns",
 1406+ "251": "luserclient",
 1407+ "252": "luserop",
 1408+ "253": "luserunknown",
 1409+ "254": "luserchannels",
 1410+ "255": "luserme",
 1411+ "256": "adminme",
 1412+ "257": "adminloc1",
 1413+ "258": "adminloc2",
 1414+ "259": "adminemail",
 1415+ "261": "tracelog",
 1416+ "262": "endoftrace",
 1417+ "263": "tryagain",
 1418+ "265": "n_local",
 1419+ "266": "n_global",
 1420+ "300": "none",
 1421+ "301": "away",
 1422+ "302": "userhost",
 1423+ "303": "ison",
 1424+ "305": "unaway",
 1425+ "306": "nowaway",
 1426+ "311": "whoisuser",
 1427+ "312": "whoisserver",
 1428+ "313": "whoisoperator",
 1429+ "314": "whowasuser",
 1430+ "315": "endofwho",
 1431+ "316": "whoischanop",
 1432+ "317": "whoisidle",
 1433+ "318": "endofwhois",
 1434+ "319": "whoischannels",
 1435+ "321": "liststart",
 1436+ "322": "list",
 1437+ "323": "listend",
 1438+ "324": "channelmodeis",
 1439+ "329": "channelcreate",
 1440+ "331": "notopic",
 1441+ "332": "topic",
 1442+ "333": "topicinfo",
 1443+ "341": "inviting",
 1444+ "342": "summoning",
 1445+ "346": "invitelist",
 1446+ "347": "endofinvitelist",
 1447+ "348": "exceptlist",
 1448+ "349": "endofexceptlist",
 1449+ "351": "version",
 1450+ "352": "whoreply",
 1451+ "353": "namreply",
 1452+ "361": "killdone",
 1453+ "362": "closing",
 1454+ "363": "closeend",
 1455+ "364": "links",
 1456+ "365": "endoflinks",
 1457+ "366": "endofnames",
 1458+ "367": "banlist",
 1459+ "368": "endofbanlist",
 1460+ "369": "endofwhowas",
 1461+ "371": "info",
 1462+ "372": "motd",
 1463+ "373": "infostart",
 1464+ "374": "endofinfo",
 1465+ "375": "motdstart",
 1466+ "376": "endofmotd",
 1467+ "377": "motd2", # 1997-10-16 -- tkil
 1468+ "381": "youreoper",
 1469+ "382": "rehashing",
 1470+ "384": "myportis",
 1471+ "391": "time",
 1472+ "392": "usersstart",
 1473+ "393": "users",
 1474+ "394": "endofusers",
 1475+ "395": "nousers",
 1476+ "401": "nosuchnick",
 1477+ "402": "nosuchserver",
 1478+ "403": "nosuchchannel",
 1479+ "404": "cannotsendtochan",
 1480+ "405": "toomanychannels",
 1481+ "406": "wasnosuchnick",
 1482+ "407": "toomanytargets",
 1483+ "409": "noorigin",
 1484+ "411": "norecipient",
 1485+ "412": "notexttosend",
 1486+ "413": "notoplevel",
 1487+ "414": "wildtoplevel",
 1488+ "421": "unknowncommand",
 1489+ "422": "nomotd",
 1490+ "423": "noadmininfo",
 1491+ "424": "fileerror",
 1492+ "431": "nonicknamegiven",
 1493+ "432": "erroneusnickname", # Thiss iz how its speld in thee RFC.
 1494+ "433": "nicknameinuse",
 1495+ "436": "nickcollision",
 1496+ "437": "unavailresource", # "Nick temporally unavailable"
 1497+ "441": "usernotinchannel",
 1498+ "442": "notonchannel",
 1499+ "443": "useronchannel",
 1500+ "444": "nologin",
 1501+ "445": "summondisabled",
 1502+ "446": "usersdisabled",
 1503+ "451": "notregistered",
 1504+ "461": "needmoreparams",
 1505+ "462": "alreadyregistered",
 1506+ "463": "nopermforhost",
 1507+ "464": "passwdmismatch",
 1508+ "465": "yourebannedcreep", # I love this one...
 1509+ "466": "youwillbebanned",
 1510+ "467": "keyset",
 1511+ "471": "channelisfull",
 1512+ "472": "unknownmode",
 1513+ "473": "inviteonlychan",
 1514+ "474": "bannedfromchan",
 1515+ "475": "badchannelkey",
 1516+ "476": "badchanmask",
 1517+ "477": "nochanmodes", # "Channel doesn't support modes"
 1518+ "478": "banlistfull",
 1519+ "481": "noprivileges",
 1520+ "482": "chanoprivsneeded",
 1521+ "483": "cantkillserver",
 1522+ "484": "restricted", # Connection is restricted
 1523+ "485": "uniqopprivsneeded",
 1524+ "491": "nooperhost",
 1525+ "492": "noservicehost",
 1526+ "501": "umodeunknownflag",
 1527+ "502": "usersdontmatch",
 1528+}
 1529+
 1530+generated_events = [
 1531+ # Generated events
 1532+ "dcc_connect",
 1533+ "dcc_disconnect",
 1534+ "dccmsg",
 1535+ "disconnect",
 1536+ "ctcp",
 1537+ "ctcpreply",
 1538+]
 1539+
 1540+protocol_events = [
 1541+ # IRC protocol events
 1542+ "error",
 1543+ "join",
 1544+ "kick",
 1545+ "mode",
 1546+ "part",
 1547+ "ping",
 1548+ "privmsg",
 1549+ "privnotice",
 1550+ "pubmsg",
 1551+ "pubnotice",
 1552+ "quit",
 1553+]
 1554+
 1555+all_events = generated_events + protocol_events + numeric_events.values()
Index: trunk/debs/ircecho/ircecho
@@ -0,0 +1,83 @@
 2+#! /usr/bin/env python
 3+#
 4+# stdin -> IRC echo bot, with optional file input support.
 5+#
 6+# Written by Kate Turner <kate.turner@gmail.com>, source is in the public domain.
 7+# Modified by Ryan Lane <rlane@wikimedia.org> for watching and taking input for files.
 8+# Changes are also public domain.
 9+
 10+import sys
 11+import pyinotify
 12+from optparse import OptionParser
 13+
 14+sys.path.append('/usr/ircecho/lib')
 15+
 16+from ircbot import SingleServerIRCBot
 17+from irclib import nm_to_n
 18+
 19+import threading
 20+
 21+class EchoReader(threading.Thread):
 22+ def __init__(self, bot, chans, infile=''):
 23+ threading.Thread.__init__(self)
 24+ self.bot = bot
 25+ self.chans = chans
 26+ self.infile = infile
 27+
 28+ def run(self):
 29+ if self.infile:
 30+ print "Using infile"
 31+ wm = pyinotify.WatchManager()
 32+ mask = pyinotify.IN_MODIFY
 33+ f = open(self.infile)
 34+ # Seek to the end of the file
 35+ f.seek(0,2)
 36+ handler = EventHandler()
 37+ handler.infile = f
 38+ handler.bot = self.bot
 39+ handler.chans = self.chans
 40+ notifier = pyinotify.Notifier(wm, handler)
 41+ wdd = wm.add_watch(self.infile, mask)
 42+ notifier.loop()
 43+ else:
 44+ while True:
 45+ try:
 46+ s = raw_input()
 47+ # this throws an exception if not connected.
 48+ self.bot.connection.privmsg(self.chans, s)
 49+ except EOFError:
 50+ # Once the input is finished, the bot should exit
 51+ break;
 52+ except Exception:
 53+ pass
 54+
 55+class EchoBot(SingleServerIRCBot):
 56+ def __init__(self, chans, nickname, server):
 57+ print "*** Connecting to IRC server %s..." % server
 58+ SingleServerIRCBot.__init__(self, [(server, 6667)], nickname, "IRC echo bot")
 59+ self.chans = chans
 60+
 61+ def on_nicknameinuse(self, c, e):
 62+ c.nick(c.get_nickname() + "_")
 63+
 64+ def on_welcome(self, c, e):
 65+ print "*** Connected"
 66+ for chan in self.chans:
 67+ c.join(chan)
 68+
 69+class EventHandler(pyinotify.ProcessEvent):
 70+ def process_IN_MODIFY(self, event):
 71+ s = self.infile.read()
 72+ self.bot.connection.privmsg(self.chans, s)
 73+
 74+parser = OptionParser(conflict_handler="resolve")
 75+parser.set_usage("ircecho [--infile=<filename>] <channel> <nickname> <server>")
 76+parser.add_option("--infile", dest="infile", help="Read input from the specific file instead of from stdin")
 77+(options, args) = parser.parse_args()
 78+chans = args[0]
 79+nickname = args[1]
 80+server = args[2]
 81+bot = EchoBot([chans], nickname, server)
 82+sthr = EchoReader(bot, chans, options.infile)
 83+sthr.start()
 84+bot.start()
Property changes on: trunk/debs/ircecho/ircecho
___________________________________________________________________
Added: svn:executable
185 + *

Status & tagging log