dreamIRC initial check-in
authorMichael Alexejew <luke_s@users.schwerkraft.elitedvb.net>
Wed, 8 Apr 2009 19:29:58 +0000 (19:29 +0000)
committerMichael Alexejew <luke_s@users.schwerkraft.elitedvb.net>
Wed, 8 Apr 2009 19:29:58 +0000 (19:29 +0000)
19 files changed:
dreamirc/CONTROL/control [new file with mode: 0755]
dreamirc/Makefile.am [new file with mode: 0755]
dreamirc/src/LICENSE [new file with mode: 0755]
dreamirc/src/Makefile.am [new file with mode: 0644]
dreamirc/src/__init__.py [new file with mode: 0755]
dreamirc/src/dreamIRCSetup.py [new file with mode: 0755]
dreamirc/src/dreamIRCTools.py [new file with mode: 0755]
dreamirc/src/e2account.py [new file with mode: 0755]
dreamirc/src/e2chat.py [new file with mode: 0755]
dreamirc/src/e2support.py [new file with mode: 0755]
dreamirc/src/interfaces.py [new file with mode: 0755]
dreamirc/src/ircsupport.py [new file with mode: 0755]
dreamirc/src/keymap.xml [new file with mode: 0755]
dreamirc/src/locals.py [new file with mode: 0755]
dreamirc/src/maintainer.info [new file with mode: 0755]
dreamirc/src/plugin.py [new file with mode: 0755]
dreamirc/src/protocols/Makefile.am [new file with mode: 0644]
dreamirc/src/protocols/__init__.py [new file with mode: 0755]
dreamirc/src/protocols/irc.py [new file with mode: 0755]

diff --git a/dreamirc/CONTROL/control b/dreamirc/CONTROL/control
new file mode 100755 (executable)
index 0000000..200090f
--- /dev/null
@@ -0,0 +1,10 @@
+Package: enigma2-plugin-extensions-dreamirc
+Version: 1.0-20090406-r0
+Description: simple IRC GroupChat client for e2 -> #dm8000-vip edt
+Architecture: mipsel
+Section: extra
+Priority: optional
+Maintainer: Michael Alexejew <luke_s@opendreambox.org>
+Homepage: http://enigma2-plugins.schwerkraft.elitedvb.net
+Depends: enigma2(>=2.6git20090219)
+Source: http://enigma2-plugins.schwerkraft.elitedvb.net/
diff --git a/dreamirc/Makefile.am b/dreamirc/Makefile.am
new file mode 100755 (executable)
index 0000000..948a117
--- /dev/null
@@ -0,0 +1 @@
+SUBDIRS = src 
diff --git a/dreamirc/src/LICENSE b/dreamirc/src/LICENSE
new file mode 100755 (executable)
index 0000000..8afe30a
--- /dev/null
@@ -0,0 +1,21 @@
+This plugin is licensed under the Creative Commons 
+Attribution-NonCommercial-ShareAlike 3.0 Unported 
+License. To view a copy of this license, visit
+http://creativecommons.org/licenses/by-nc-sa/3.0/ or send a letter to Creative
+Commons, 559 Nathan Abbott Way, Stanford, California 94305, USA.
+
+Alternatively, this plugin may be distributed and executed on hardware which
+is licensed by Dream Multimedia GmbH.
+
+This plugin is NOT free software. It is open source, you are allowed to
+modify it (if you keep the license), but it may not be commercially 
+distributed other than under the conditions noted above.
+
+Some Icons used are taken from NX10 icons by Mazenl77
+(http://www.iconspedia.com/pack/nx10-1-6/)
+licensed under Creative Commons Attribution 3.0 Unported
+http://creativecommons.org/licenses/by/3.0/
+Some others from HydroPro v2 icons by Ben Fleming
+(http://www.iconspedia.com/pack/hydropro-v2-624/)
+licensed under Creative Commons Attribution 2.5 Australia
+http://creativecommons.org/licenses/by/2.5/au/
\ No newline at end of file
diff --git a/dreamirc/src/Makefile.am b/dreamirc/src/Makefile.am
new file mode 100644 (file)
index 0000000..d95e777
--- /dev/null
@@ -0,0 +1,8 @@
+installdir = $(LIBDIR)/enigma2/python/Plugins/Extensions/dreamIRC
+
+install_PYTHON = *.py
+
+install_DATA = *.xml *.info LICENSE
+
+SUBDIRS = protocols
+
diff --git a/dreamirc/src/__init__.py b/dreamirc/src/__init__.py
new file mode 100755 (executable)
index 0000000..e69de29
diff --git a/dreamirc/src/dreamIRCSetup.py b/dreamirc/src/dreamIRCSetup.py
new file mode 100755 (executable)
index 0000000..c33c87e
--- /dev/null
@@ -0,0 +1,271 @@
+##
+from Screens.Screen import Screen
+from Screens.MessageBox import MessageBox
+from Components.ActionMap import ActionMap
+from Components.config import *
+from Components.ConfigList import *
+from Components.Sources.StaticText import StaticText
+from Tools.HardwareInfo import *
+
+import xml.dom.minidom
+from xml.dom.minidom import Node
+from xml.dom import EMPTY_NAMESPACE
+from Tools import XMLTools
+from Tools.XMLTools import elementsWithTag, mergeText
+
+from socket import gethostbyname_ex
+
+from dreamIRCTools import *
+
+accounts_xml="/etc/dreamIRC.xml"
+
+class dreamIRCSetupScreen(ConfigListScreen, Screen):
+       from enigma import getDesktop
+       desk = getDesktop(0)
+       x= int(desk.size().width())
+       y= int(desk.size().height())
+       print "[dreamIRC] setup: current desktop size: %dx%d" % (x,y)
+
+       if (y>=720):
+               skin = """
+                       <screen position="390,205" size="500,300" title="dreamIRC - edit settings" >
+                       <widget name="config" position="10,10" size="480,260" scrollbarMode="showOnDemand" />
+                       <ePixmap pixmap="skin_default/div-h.png" position="10,245" size="480,2" transparent="1" alphatest="on" />
+                       <ePixmap pixmap="skin_default/buttons/red.png" position="60,250" size="160,40" alphatest="on" />
+                       <ePixmap pixmap="skin_default/buttons/green.png" position="280,250" size="160,40" alphatest="on" />
+                       <widget source="key_red" render="Label" position="60,250" zPosition="1" size="150,40" font="Regular;19" halign="center" valign="center" backgroundColor="#9f1313" transparent="1" />
+                       <widget source="key_green" render="Label" position="280,250" zPosition="1" size="150,40" font="Regular;19" halign="center" valign="center" backgroundColor="#1f771f" transparent="1" />
+               </screen>"""
+       else:
+               skin = """
+                       <screen position="110,145" size="500,300" title="dreamIRC - edit settings" >
+                       <widget name="config" position="10,10" size="480,260" scrollbarMode="showOnDemand" />
+                       <ePixmap pixmap="skin_default/div-h.png" position="10,245" size="480,2" transparent="1" alphatest="on" />
+                       <ePixmap pixmap="skin_default/buttons/red.png" position="60,250" size="160,40" alphatest="on" />
+                       <ePixmap pixmap="skin_default/buttons/green.png" position="280,250" size="160,40" alphatest="on" />
+                       <widget source="key_red" render="Label" position="60,250" zPosition="1" size="150,40" font="Regular;19" halign="center" valign="center" backgroundColor="#9f1313" transparent="1" />
+                       <widget source="key_green" render="Label" position="280,250" zPosition="1" size="150,40" font="Regular;19" halign="center" valign="center" backgroundColor="#1f771f" transparent="1" />
+               </screen>"""
+
+       def __init__(self, session, args = 0):
+               Screen.__init__(self, session)
+               self.hardware_info = HardwareInfo()
+               self.device=self.hardware_info.get_device_name()
+               self.mac=getMacAddress()
+               self.mac_end=self.mac[6:]
+#              print "device : %s  -  mac : %s  -  mac_end : %s " %(self.device,self.mac,self.mac_end)
+               self.dreamIRCconf = ConfigSubsection()
+               self.reloadFile()
+               list = []
+               list.append(getConfigListEntry(_('Nickname'), self.dreamIRCconf.nick))      
+               if config.usage.setup_level.index > 1: # advanced
+                       list.append(getConfigListEntry(_('Passwd'), self.dreamIRCconf.passwd))      
+               if config.usage.setup_level.index >= 1: # intermediate+
+                       list.append(getConfigListEntry(_('Server1'), self.dreamIRCconf.server1))      
+               if config.usage.setup_level.index > 1: # advanced
+                       list.append(getConfigListEntry(_('Server2'), self.dreamIRCconf.server2))      
+                       list.append(getConfigListEntry(_('Server3'), self.dreamIRCconf.server3))      
+               if config.usage.setup_level.index >= 1: # intermediate+
+                       list.append(getConfigListEntry(_('Port'), self.dreamIRCconf.port))          
+               list.append(getConfigListEntry(_('Channel'), self.dreamIRCconf.channel))    
+               if config.usage.setup_level.index > 1: # i
+                       list.append(getConfigListEntry(_('Debug'), self.dreamIRCconf.debug))        
+
+               self["key_red"] = StaticText(_("Cancel"))
+               self["key_green"] = StaticText(_("Save"))
+
+               ConfigListScreen.__init__(self, list)                                     
+               self["actions"] = ActionMap(["OkCancelActions", "ColorActions"],
+                               {
+                                               "green": self.saveAndExit,
+                                               "red": self.dontSaveAndExit,
+                                               "cancel": self.dontSaveAndExit
+                               }, -1)
+
+       def load(self):
+               self.reloadFile()
+               self.accounts=[ircsupport.IRCAccount(self.type, string.atoi(self.nr), str(self.nick), str(self.passwd), str(self.server1), string.atoi(self.port), str(self.channel))]
+               print self.accounts
+               return self.accounts
+
+       def reloadFile(self):
+               try:
+                       doc = xml.dom.minidom.parse(accounts_xml)
+                       root = doc.childNodes[0]        
+                       for node in elementsWithTag(root.childNodes, "account"):
+                               self.nick = node.getAttribute("nick")
+                               self.passwd = node.getAttribute("passwd")
+                               self.server1 = node.getAttribute("server1")
+                               self.server2 = node.getAttribute("server2")
+                               self.server3 = node.getAttribute("server3")
+                               self.port = node.getAttribute("port")
+                               self.channel = node.getAttribute("channel")
+                               self.debug = node.getAttribute("debug")
+                       if ((self.nick.lower() == "dreamircuser") or (self.nick == "") or (self.nick[0] == " ") or (self.nick.lower() == "dm8000-vip")) :
+                               print "[dreamIRC] nickname error... restoring default..."
+                               self.nick = self.device+"_"+self.mac_end
+               except IOError:
+                       self.type = "IRC"
+                       self.login = "1"
+                       self.nick = self.device+"_"+self.mac_end
+                       self.passwd = ""
+                       self.server1 = "irc.belwue.de"
+                       self.server2 = "irc.freenet.de"
+                       self.server3 = "irc.tu-illmenau.de"
+                       self.port = "06667"
+                       self.channel = "#dreamirc"
+                       self.debug = ""
+#              self.dreamIRCconf = ConfigSubsection()
+               self.dreamIRCconf.nick = ConfigText(default = self.nick, fixed_size = False)
+               self.dreamIRCconf.passwd = ConfigText(default = self.passwd, fixed_size = False)
+               self.dreamIRCconf.server1 = ConfigText(default = self.server1, fixed_size = False)
+               self.dreamIRCconf.server2 = ConfigText(default = self.server2, fixed_size = False)
+               self.dreamIRCconf.server3 = ConfigText(default = self.server3, fixed_size = False)
+               self.dreamIRCconf.port = ConfigInteger(default = string.atoi(self.port), limits = (0, 99999))
+               self.dreamIRCconf.channel = ConfigText(default = self.channel, fixed_size = False)
+               self.dreamIRCconf.debug = ConfigText(default = self.debug, fixed_size = False)
+
+       def keySave(self):
+               self.accounts=[]
+               self.type = "IRC"
+               self.login = "1"
+               self.nick = self.dreamIRCconf.nick.value
+               self.passwd = self.dreamIRCconf.passwd.value
+               self.server1 = self.dreamIRCconf.server1.value
+               self.server2 = self.dreamIRCconf.server2.value
+               self.server3 = self.dreamIRCconf.server3.value
+               self.port = self.dreamIRCconf.port.value
+               self.channel = self.dreamIRCconf.channel.value
+               self.debug = self.dreamIRCconf.debug.value
+               global accounts_xml
+               fp = file(accounts_xml, 'w')
+               fp.write("<?xml version=\"1.0\" encoding=\"iso-8859-1\" ?>\n")
+               fp.write("<accounts>\n")
+               fp.write("    <account type=\"%s\" login=\"%s\" nick=\"%s\" passwd=\"%s\" server1=\"%s\" server2=\"%s\" server3=\"%s\" port=\"%s\" channel=\"%s\" debug=\"%s\" />\n" % (self.type, self.login, self.nick, self.passwd, self.server1, self.server2, self.server3, self.port, self.channel, self.debug))
+               fp.write("</accounts>\n")
+               fp.close()
+               if self.server1:
+                       try:
+                               self.result1=gethostbyname_ex(self.server1)
+                       except:
+                               self.session.open(MessageBox, _("irc server %s not responding!\nplease check your network settings and/or irc servername..." %self.server1), MessageBox.TYPE_ERROR)
+               if self.server2:
+                       try:
+                               self.result2=gethostbyname_ex(self.server2)
+                       except:
+                               self.session.open(MessageBox, _("irc server %s not responding!\nplease check your network settings and/or irc servername..." %self.server2), MessageBox.TYPE_ERROR)
+               if self.server3:
+                       try:
+                               self.result3=gethostbyname_ex(self.server3)
+                       except:
+                               self.session.open(MessageBox, _("irc server %s not responding!\nplease check your network settings and/or irc servername..." %self.server3), MessageBox.TYPE_ERROR)
+
+
+       def saveAndExit(self):
+               for x in self["config"].list:
+                       x[1].save()
+               self.keySave()
+               self.close()
+
+       def dontSaveAndExit(self):
+               for x in self["config"].list:
+                       x[1].cancel()
+               self.close()
+               
+class dreamIRCConfig:
+       def load(self):
+               self.pipe=MessagePipe()
+               self.status1=False
+               self.status2=False
+               self.status3=False
+               self.hardware_info = HardwareInfo()
+               self.device=self.hardware_info.get_device_name()
+               self.mac=getMacAddress()
+               self.mac_end=self.mac[6:]
+#              print "device : %s  -  mac : %s  -  mac_end : %s " %(self.device,self.mac,self.mac_end)                 
+               try:
+                       doc = xml.dom.minidom.parse(accounts_xml)
+                       root = doc.childNodes[0]
+                       for node in elementsWithTag(root.childNodes, "account"):
+                               self.type = node.getAttribute("type")
+                               self.login = node.getAttribute("login")
+                               self.nick = node.getAttribute("nick")
+                               if ((self.nick.lower() == "dreamircuser") or (self.nick == "") or (self.nick[0] == " ") or (self.nick.lower() == "dm8000-vip")) :
+                                       print "[dreamIRC] nickname error... restoring default..."
+                                       self.nick = self.device+"_"+self.mac_end
+                               self.passwd = node.getAttribute("passwd")
+                               self.server1 = node.getAttribute("server1") # atm only ip.. cause of probs with theads and dns..
+                               self.server2 = node.getAttribute("server2") 
+                               self.server3 = node.getAttribute("server3") 
+                               self.port = node.getAttribute("port")
+                               self.channel = node.getAttribute("channel")
+                               self.debug = node.getAttribute("debug") # not used yet.. later will enable/disable console debug out..
+               except IOError:
+                       self.type = "IRC"
+                       self.login = "1"
+                       self.nick = self.device+"_"+self.mac_end
+                       self.passwd = ""
+                       self.server1 = "irc.freenet.de"
+                       self.server2 = "irc.freenet.de"
+                       self.server3 = "irc.tu-illmenau.de"
+                       self.port = "06667"
+                       self.channel = "#dreamirc"
+                       self.debug = ""
+               self.server1 = self.server1.strip()
+               self.server2 = self.server2.strip()
+               self.server3 = self.server3.strip()
+               if self.server3:
+                       try:
+                               self.result3=gethostbyname_ex(self.server3)
+                               if self.result3:
+                                       for ip_tmp3 in self.result3[2]:
+                                               self.ip=ip_tmp3
+                                               self.server=self.server3
+                                               self.status3=True
+                       except:
+                               print "unable to resolve hostname %s..." % self.server3
+               if self.server2:
+                       try:
+                               self.result2=gethostbyname_ex(self.server2)
+                               if self.result2:
+                                       for ip_tmp2 in self.result2[2]:
+                                               self.ip=ip_tmp2
+                                               self.server=self.server2
+                                               self.status2=True
+                       except:
+                               print "unable to resolve hostname %s..." % self.server2
+               if self.server1:
+                       try:
+                               self.result1=gethostbyname_ex(self.server1)
+                               if self.result1:
+                                       for ip_tmp1 in self.result1[2]:
+                                               self.ip=ip_tmp1
+                                               self.server=self.server1
+                                               self.status1=True
+                       except:
+                               print "unable to resolve hostname %s..." % self.server1
+                               
+               if self.status1==False and self.status2==False and self.status3==False:
+                               self.pipe.add("ERROR!!! no irc server was valid... please check settings...")
+                               return False
+               else:
+                       print " account = type: %s login:%s nick:%s passwd:%s server:%s ip:%s port:%s channel:%s debug:%s " % (self.type, self.login, self.nick, self.passwd, self.server, self.ip, self.port, self.channel, self.debug)
+                       self.accounts=[ircsupport.IRCAccount(self.type, string.atoi(self.login), str(self.nick), str(self.passwd), str(self.ip), string.atoi(self.port), str(self.channel))]
+                       print self.accounts
+                       return self.accounts
+       
+       def channel(self):
+                       doc = xml.dom.minidom.parse(accounts_xml)
+                       root = doc.childNodes[0]
+                       for node in elementsWithTag(root.childNodes, "account"):
+                               self.channel = node.getAttribute("channel")
+                       return self.channel
+               
+
+#def getMacAddress():
+#      for line in os.popen("/sbin/ifconfig"):
+#              if line.find('Ether') > -1:
+#                      mac = line.split()[4]
+#                      new_mac = mac.replace(":","")
+#                      break
+#      return new_mac
diff --git a/dreamirc/src/dreamIRCTools.py b/dreamirc/src/dreamIRCTools.py
new file mode 100755 (executable)
index 0000000..8ea93ef
--- /dev/null
@@ -0,0 +1,204 @@
+#!/usr/bin/env python
+from enigma import *
+from Screens.Screen import Screen
+
+from Components.Pixmap import *
+from Components.Pixmap import Pixmap
+from Components.ActionMap import ActionMap, NumberActionMap
+from Components.ScrollLabel import ScrollLabel
+from Components.GUIComponent import *
+from Components.MenuList import MenuList
+from Components.Input import Input
+from Components.Label import Label
+from Components.config import *
+from Components.ConfigList import ConfigList
+from Components.MenuList import MenuList
+from Components.MultiContent import MultiContentEntryText
+from Plugins.Plugin import PluginDescriptor
+from Tools.NumericalTextInput import *
+from Tools.Directories import *
+
+import skin
+from Components.HTMLComponent import *
+from Components.GUIComponent import *
+from enigma import eLabel, eWidget, eSlider, fontRenderClass, ePoint, eSize
+
+import os
+import string
+import time
+import datetime
+import sys
+
+import xml.dom.minidom
+from xml.dom.minidom import Node
+from xml.dom import EMPTY_NAMESPACE
+from Tools import XMLTools
+from Tools.XMLTools import elementsWithTag, mergeText
+
+import plugin
+from plugin import *
+
+import ircsupport
+
+ChatText=str()
+OutTextTmp=str()
+BuddyList=str()
+NewMsg=str()
+Channel=str("ChatBox")
+
+x=0
+y=0
+
+class ChatWindow(ScrollLabel):
+       def __init__(self,session):
+               ScrollLabel.__init__(self,text="")
+               self.timer=eTimer()
+               self.timer.timeout.get().append(self.updateChatWindow)
+               self.timer.start(250)
+               self.pipe=MessagePipe()
+               self.oldText=""
+               
+       def updateChatWindow(self):
+               if (len(self.pipe.LastMsg()) >0) or (self.oldText!=self.pipe.getChatText()):
+                       self.oldText=self.pipe.getChatText()
+                       self.setText(self.pipe.getChatText())
+                       self.lastPage()
+                       self.pipe.setLastMsg("")
+
+class BuddyWindow(ScrollLabel):
+       def __init__(self,session):
+               ScrollLabel.__init__(self,text="")
+               self.timer=eTimer()
+               self.timer.timeout.get().append(self.updateBuddyWindow)
+               self.timer.start(500)
+               self.oldlist=""
+
+       def updateBuddyWindow(self):
+               if (self.oldlist != BuddyList):
+                       self.setText(BuddyList)
+                       self.oldlist =BuddyList
+
+class ChanName(Label):
+       def __init__(self,session):
+               Label.__init__(self,text=Channel)
+               self.timer=eTimer()
+               self.timer.timeout.get().append(self.updateChanName)
+               self.timer.start(500)
+               self.oldname=self.text
+               self.pipe=MessagePipe()
+
+       def updateChanName(self):
+               self.newname=self.pipe.updateDesc()
+               if (self.oldname != self.newname):
+                       self.setText(self.newname)
+                       self.oldname=self.newname
+
+class MessagePipe():
+       def __init__(self):
+               global BuddyList
+               self.logger=MessageLogger(open("/var/log/dreamIRC.log", "a"))
+
+       def updateBuddyWindow(self):
+               global BuddyList 
+               return BuddyList
+       
+       def getChatText(self):
+               global ChatText
+               return ChatText
+
+       def LastMsg(self):
+               global NewMsg
+               return NewMsg
+
+       def setLastMsg(self,text):
+               global NewMsg
+               NewMsg=str(text)
+
+       def getOutText(self):
+               global OutTextTmp
+               return OutTextTmp
+
+       def addOutText(self,text):
+               global OutTextTmp
+               OutTextTmp =str(text)
+               print "OUTTEXT %s" % OutTextTmp
+
+       def clearOutText(self):
+               global OutTextTmp
+               OutTextTmp=str("")
+               return OutTextTmp
+
+       def add(self,text):
+               timestamp = time.strftime("[%H:%M:%S]", time.localtime(time.time()))
+               global ChatText, NewMsg
+               ChatText=ChatText+"%s %s\n" % (timestamp,text)
+               NewMsg="%s %s" % (timestamp,text)
+               self.logger.log("%s %s" %(timestamp,text))
+
+       def clear(self):
+               global ChatText
+               ChatText=str("")
+
+       def close(self):
+               self.logger.close()
+
+       def buildBuddyList(self,text):
+               global BuddyList    
+               BuddyList= BuddyList+ "%s\n" %text
+
+       def clearBuddyList(self):
+               global BuddyList
+               BuddyList=""
+
+       def showBuddyList(self):
+               global BuddyList    
+#              self["buddy"].setText(BuddyList)
+               return BuddyList
+
+       def updateDesc(self):
+               global Channel
+               return Channel
+
+       def getCannelName(self,text):
+               global Channel
+               Channel = "ChatBox #" + "%s\n" %text
+               
+       def resetDesc(self):
+               global Channel
+               Channel = "ChatBox"
+       
+
+class MessageLogger:
+       def __init__(self, file):
+               self.file = file
+               print '[dreamIRC] %s  MESSAGE LOGGER = %s \n'% (time.strftime("[%H:%M:%S]", time.localtime(time.time())),self.file)
+
+       def log(self, message):
+#              timestamp = time.strftime("[%H:%M:%S]", time.localtime(time.time()))
+               print '[dreamIRC] %s\n' % (message)
+               self.file.write('%s\n' % (message))
+               self.file.flush()
+
+       def close(self):
+               self.file.close()
+
+def readLogFile(args):
+       try:
+               fp = file(args[0], 'r')
+               lines = fp.readlines()
+               fp.close()
+               output = ""
+               for x in lines:
+                       output += x
+       except IOError:
+               output = args[1]
+       return output
+
+def getMacAddress():
+       for line in os.popen("/sbin/ifconfig"):
+               if line.find('Ether') > -1:
+                       mac = line.split()[4]
+                       new_mac = mac.replace(":","")
+                       break
+       return new_mac
+        
\ No newline at end of file
diff --git a/dreamirc/src/e2account.py b/dreamirc/src/e2account.py
new file mode 100755 (executable)
index 0000000..4b2ed47
--- /dev/null
@@ -0,0 +1,78 @@
+# -*- Python -*-
+#
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details. 
+
+#
+import string
+import ircsupport
+import e2chat, dreamIRCTools, dreamIRCSetup
+
+
+class AccountManager:
+       """I am responsible for managing a user's accounts.
+
+       That is, remembering what accounts are available, their settings,
+       adding and removal of accounts, etc.
+
+       @ivar accounts: A collection of available accounts.
+       @type accounts: mapping of strings to L{Account<interfaces.IAccount>}s.
+       """
+       def __init__(self,session):
+               self.chatui = e2chat.ChatUI()
+               self.config = dreamIRCSetup.dreamIRCConfig()
+               self.accounts = self.config.load()
+#              self.startConnect()     
+
+#        if self.accounts==False:
+#            print "You have defined no accounts."
+#        else:
+#            for acct in self.accounts:
+#                              acct.logOn(self.chatui)
+
+       def startConnect(self):
+               if self.accounts==False:
+                       print "You have defined no accounts."
+               else:
+                       for acct in self.accounts:
+                               acct.logOn(self.chatui)
+
+       def getSnapShot(self):
+               """A snapshot of all the accounts and their status.
+
+               @returns: A list of tuples, each of the form
+                       (string:accountName, boolean:isOnline,
+                       boolean:autoLogin, string:gatewayType)
+               """
+               data = []
+#              for account in self.accounts.values():
+               for account in self.accounts:
+                       data.append((account.accountName, account.isOnline(),account.autoLogin, account.gatewayType))
+               return data
+
+       def isEmpty(self):
+               return len(self.accounts) == 0
+
+       def getConnectionInfo(self):
+               connectioninfo = []
+               for account in self.accounts:
+                       connectioninfo.append(account.isOnline())
+               return connectioninfo
+
+       def addAccount(self, account):
+               self.accounts[account.accountName] = account
+
+       def delAccount(self, accountName):
+               del self.accounts[accountName]
+
+       def connect(self, accountName, chatui):
+               """
+               @returntype: Deferred L{interfaces.IClient}
+               """
+               print "----1---- %s" % accountName
+               return self.accounts[accountName].logOn(chatui)
+
+       def quit(self):
+               pass
+               #for account in self.accounts.values():
+               #    account.logOff()  - not yet implemented
diff --git a/dreamirc/src/e2chat.py b/dreamirc/src/e2chat.py
new file mode 100755 (executable)
index 0000000..f0bc69d
--- /dev/null
@@ -0,0 +1,441 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+
+"""Base classes for Instance Messenger clients."""
+from enigma import *
+from Screens.Screen import Screen
+
+from Components.Pixmap import *
+from Components.Pixmap import Pixmap
+from Components.ActionMap import ActionMap, NumberActionMap
+from Components.ScrollLabel import ScrollLabel
+from Components.GUIComponent import *
+from Components.MenuList import MenuList
+from Components.Input import Input
+from Components.Label import Label
+from Components.config import *
+from Components.ConfigList import ConfigList
+from Plugins.Plugin import PluginDescriptor
+from Tools.NumericalTextInput import *
+from Tools.Directories import *
+
+from locals import OFFLINE, ONLINE, AWAY
+import dreamIRCTools
+from enigma import *
+from dreamIRCTools import *
+#from myScrollLabel import *
+#from dreamIRCMainMenu import *
+
+class ContactsList:
+    """A GUI object that displays a contacts list"""
+    def __init__(self, chatui):
+        """
+        @param chatui: ???
+        @type chatui: L{ChatUI}
+        """
+        self.chatui = chatui
+        self.contacts = {}
+        self.onlineContacts = {}
+        self.clients = []
+        
+    def setContactStatus(self, person):
+        """Inform the user that a person's status has changed.
+
+        @type person: L{Person<interfaces.IPerson>}
+        """
+        if not self.contacts.has_key(person.name):
+            self.contacts[person.name] = person
+        if not self.onlineContacts.has_key(person.name) and \
+            (person.status == ONLINE or person.status == AWAY):
+            self.onlineContacts[person.name] = person
+        if self.onlineContacts.has_key(person.name) and \
+           person.status == OFFLINE:
+            del self.onlineContacts[person.name]
+
+    def registerAccountClient(self, client):
+        """Notify the user that an account client has been signed on to.
+
+        @type client: L{Client<interfaces.IClient>}
+        """
+        if not client in self.clients:
+            self.clients.append(client)
+
+    def unregisterAccountClient(self, client):
+        """Notify the user that an account client has been signed off
+        or disconnected from.
+
+        @type client: L{Client<interfaces.IClient>}
+         """
+        if client in self.clients:
+            self.clients.remove(client)
+
+    def contactChangedNick(self, person, newnick):
+        oldname = person.name
+        if self.contacts.has_key(oldname):
+            del self.contacts[oldname]
+            person.name = newnick
+            self.contacts[newnick] = person
+            if self.onlineContacts.has_key(oldname):
+                del self.onlineContacts[oldname]
+                self.onlineContacts[newnick] = person
+
+
+class Conversation:
+    """A GUI window of a conversation with a specific person"""
+    def __init__(self, person, chatui):
+        """
+        @type person: L{Person<interfaces.IPerson>}
+        @type chatui: L{ChatUI}
+        """
+        self.chatui = chatui
+        self.person = person
+        self.pipe = MessagePipe()
+#        self.blist=ChatWindow()
+        self.timer=eTimer()
+        self.timer.timeout.get().append(self.sendOutPipe)
+        self.timer.start(100)
+
+    def show(self):
+        """Displays the ConversationWindow"""
+ #       raise NotImplementedError("Subclasses must implement this method")
+
+    def hide(self):
+        """Hides the ConversationWindow"""
+#        raise NotImplementedError("Subclasses must implement this method")
+
+    def sendText(self, text):
+        """Sends text to the person with whom the user is conversing.
+
+        @returntype: L{Deferred<twisted.internet.defer.Deferred>}
+        """
+               self.person.sendMessage(text, None)
+               self.pipe.add("%s" % text)
+               self.pipe.clearOutText()
+#        print"<%s> %s" % (self.nickname, text)
+
+    def sendOutPipe(self):
+        if len(str(self.pipe.getOutText())) > 0:
+               if (self.pipe.getOutText()=="/QUIT"):
+                       print "/quit detected...."
+                       self.pipe.clearOutText()
+                       self.person.bye()
+                       else:
+                       print "sending chat : %s" % str(self.pipe.getOutText())
+                       self.sendText(str(self.pipe.getOutText()))
+                       self.pipe.clearOutText()
+            
+    def showMessage(self, text, metadata=None):
+        """Display a message sent from the person with whom she is conversing
+
+        @type text: string
+        @type metadata: dict
+        """
+        self.pipe.add("<%s> %s" % (self.person.name, text))
+#        raise NotImplementedError("Subclasses must implement this method")
+
+    def contactChangedNick(self, person, newnick):
+        """Change a person's name.
+
+        @type person: L{Person<interfaces.IPerson>}
+        @type newnick: string
+        """
+        self.person.name = newnick
+        self.pipe.add("-!- %s is now known as %s" % (person.name, newnick))
+#        self.blist.updateBuddyWindow()
+
+    def serverMsg(self, message):
+        """Displays a serverMsg in the group conversation window
+
+        @type message: string
+        """
+        self.pipe.add("-!- %s " % (message))
+#        self.blist.updateBuddyWindow()
+
+class GroupConversation:
+    """A conversation with a group of people."""
+    def __init__(self, group, chatui):
+        """
+        @type group: L{Group<interfaces.IGroup>}
+        @param chatui: ???
+        @type chatui: L{ChatUI}
+        """
+        self.chatui = chatui
+        self.group = group
+        self.members = []
+        self.pipe = MessagePipe()
+#        self.blist=ChatWindow()
+        self.timer=eTimer()
+        self.timer.timeout.get().append(self.sendOutPipe)
+        self.timer.start(100)
+        
+    def show(self):
+        """Displays the GroupConversationWindow."""
+#        raise NotImplementedError("Subclasses must implement this method")
+
+    def hide(self):
+        """Hides the GroupConversationWindow."""
+#        raise NotImplementedError("Subclasses must implement this method")
+
+    def sendText(self, text):
+        """Sends text to the group.
+
+        @type text: string
+        @returntype: L{Deferred<twisted.internet.defer.Deferred>}
+        """
+               self.group.sendGroupMessage(text, None)
+               self.pipe.add("%s" % text)
+               self.pipe.clearOutText()
+#        print "nach im sending nach sending : %s" % str(self.pipe.getOutText())
+        
+    
+    def sendOutPipe(self):
+        if len(str(self.pipe.getOutText())) > 0:
+               if (self.pipe.getOutText()=="/QUIT"):
+                       print "/quit detected...."
+                       self.pipe.clearOutText()
+                       self.group.bye()
+               else:
+                       print "sending group chat : %s" % str(self.pipe.getOutText())
+                       self.sendText(str(self.pipe.getOutText()))
+                       self.pipe.clearOutText()
+            
+    def showGroupMessage(self, sender, text, metadata=None):
+        """Displays to the user a message sent to this group from the given sender
+        @type sender: string (XXX: Not Person?)
+        @type text: string
+        @type metadata: dict
+        """
+        self.pipe.add("<%s/%s> %s" % (sender, self.group.name, text))
+
+    def setGroupMembers(self, members):
+        """Sets the list of members in the group and displays it to the user
+        """
+        self.members = members
+        self.refreshMemberList()
+
+    def setTopic(self, topic, author):
+        """Displays the topic (from the server) for the group conversation window
+
+        @type topic: string
+        @type author: string (XXX: Not Person?)
+        """
+        self.pipe.add("-!- %s set the topic of %s to: %s" % (author, self.group.name, topic))
+#        print "-!- %s set the topic of %s to: %s" % (author, self.group.name, topic)        
+
+    def serverMsg(self, message):
+        """Displays a serverMsg in the group conversation window
+
+        @type message: string
+        """
+        self.pipe.add("-!- %s " % (message))
+
+    def memberJoined(self, member):
+        """Adds the given member to the list of members in the group conversation
+        and displays this to the user
+
+        @type member: string (XXX: Not Person?)
+        """
+        if not member in self.members:
+            self.members.append(member)
+        self.pipe.add("-!- %s joined %s" % (member, self.group.name))
+        self.refreshMemberList()
+
+    def memberChangedNick(self, oldnick, newnick):
+        """Changes the oldnick in the list of members to newnick and displays this
+        change to the user
+
+        @type oldnick: string
+        @type newnick: string
+        """
+        if oldnick in self.members:
+            self.members.remove(oldnick)
+            self.members.append(newnick)
+            #self.chatui.contactChangedNick(oldnick, newnick)
+        self.pipe.add("-!- %s is now known as %s in %s" % (oldnick, newnick, self.group.name))
+        self.refreshMemberList()
+
+    def memberLeft(self, member):
+        """Deletes the given member from the list of members in the group
+        conversation and displays the change to the user
+
+        @type member: string
+        """
+        if member in self.members:
+            self.members.remove(member)
+        self.pipe.add("-!- %s left %s" % (member, self.group.name))
+        self.refreshMemberList()
+        
+        
+    def refreshMemberList(self):
+        self.pipe.clearBuddyList()
+        self.members.sort(lambda x,y: cmp(string.lower(x), string.lower(y)))
+        self.pipe.getCannelName(self.group.name)
+        for member in self.members:
+            self.pipe.buildBuddyList(str(member))
+#        print "Channel : #%s" % self.group.name
+        print "Buddylist of #%s : \n%s" % (self.group.name, self.pipe.showBuddyList())
+#        self.pipe.showBuddyList()
+#        self.pipe.updateBuddyWindow(self.pipe.showBuddyList())
+        self.pipe.updateBuddyWindow()
+        
+class ChatUI:
+    """A GUI chat client"""
+    def __init__(self):
+        self.conversations = {}      # cache of all direct windows
+        self.groupConversations = {} # cache of all group windows
+        self.persons = {}            # keys are (name, client)
+        self.groups = {}             # cache of all groups
+        self.onlineClients = []      # list of message sources currently online
+        self.contactsList = ContactsList(self)
+        self.pipe = MessagePipe()
+        self.helper = ""
+
+    def registerAccountClient(self, client):
+        """Notifies user that an account has been signed on to.
+
+        @type client: L{Client<interfaces.IClient>}
+        @returns: client, so that I may be used in a callback chain
+        """
+        print "signing onto", client.accountName
+        self.onlineClients.append(client)
+        self.contactsList.registerAccountClient(client)
+        self.helper=client
+        print " --- %s ---" % self.helper
+        self.pipe.add("signing onto %s" % client)
+        self.pipe.add("signing onto %s" % client.accountName)
+        return client
+
+    def unregisterAccountClient(self, client):
+        """Notifies user that an account has been signed off or disconnected
+
+        @type client: L{Client<interfaces.IClient>}
+        """
+        print "signing off from", client.accountName
+        self.onlineClients.remove(client)
+        self.contactsList.unregisterAccountClient(client)
+
+    def remClient(self):
+        """Notifies user that an account has been signed off or disconnected
+
+        @type client: L{Client<interfaces.IClient>}
+        """
+        print " --- %s ---" % self.helper
+        print "signing off from", self.helper.accountName
+        self.pipe.add("signing off %s" % helper)
+        self.pipe.add("signing off %s" % helper.accountName)        
+        self.onlineClients.remove(helper)
+        self.contactsList.unregisterAccountClient(helper)
+
+    def getContactsList(self):
+        """
+        @returntype: L{ContactsList}
+        """
+        print "contactlist = %s" % self.contactsList
+        return self.contactsList
+
+
+    def getConversation(self, person, Class=Conversation, stayHidden=0):
+        """For the given person object, returns the conversation window
+        or creates and returns a new conversation window if one does not exist.
+
+        @type person: L{Person<interfaces.IPerson>}
+        @type Class: L{Conversation<interfaces.IConversation>} class
+        @type stayHidden: boolean
+
+        @returntype: L{Conversation<interfaces.IConversation>}
+        """
+        conv = self.conversations.get(person)
+        if not conv:
+            conv = Class(person, self)
+            self.conversations[person] = conv
+        if stayHidden:
+            conv.hide()
+        else:
+            conv.show()
+        return conv
+
+    def getGroupConversation(self,group,Class=GroupConversation,stayHidden=0):
+        """For the given group object, returns the group conversation window or
+        creates and returns a new group conversation window if it doesn't exist
+
+        @type group: L{Group<interfaces.IGroup>}
+        @type Class: L{Conversation<interfaces.IConversation>} class
+        @type stayHidden: boolean
+
+        @returntype: L{GroupConversation<interfaces.IGroupConversation>}
+        """
+        conv = self.groupConversations.get(group)
+        if not conv:
+            conv = Class(group, self)
+            self.groupConversations[group] = conv
+        if stayHidden:
+            conv.hide()
+        else:
+            conv.show()
+#        print "[dreamIRC] : " , conv
+        return conv
+
+    def getPerson(self, name, client):
+        """For the given name and account client, returns the instance of the
+        AbstractPerson subclass, or creates and returns a new AbstractPerson
+        subclass of the type Class
+
+        @type name: string
+        @type client: L{Client<interfaces.IClient>}
+
+        @returntype: L{Person<interfaces.IPerson>}
+        """
+        account = client.account
+        p = self.persons.get((name, account))
+        if not p:
+            p = account.getPerson(name)
+            self.persons[name, account] = p
+        return p
+
+    def getGroup(self, name, client):
+        """For the given name and account client, returns the instance of the
+        AbstractGroup subclass, or creates and returns a new AbstractGroup
+        subclass of the type Class
+
+        @type name: string
+        @type client: L{Client<interfaces.IClient>}
+
+        @returntype: L{Group<interfaces.IGroup>}
+        """
+        # I accept 'client' instead of 'account' in my signature for
+        # backwards compatibility.  (Groups changed to be Account-oriented
+        # in CVS revision 1.8.)
+        account = client.account
+        g = self.groups.get((name, account))
+        if not g:
+            g = account.getGroup(name)
+            self.groups[name, account] = g
+#        self.pipe.add("joined %s" % g)
+        return g
+
+    def contactChangedNick(self, oldnick, newnick):
+        """For the given person, changes the person's name to newnick, and
+        tells the contact list and any conversation windows with that person
+        to change as well.
+
+        @type oldnick: string
+        @type newnick: string
+        """
+        if self.persons.has_key((person.name, person.account)):
+            conv = self.conversations.get(person)
+            if conv:
+                conv.contactChangedNick(person, newnick)
+
+            self.contactsList.contactChangedNick(person, newnick)
+
+            del self.persons[person.name, person.account]
+            person.name = newnick
+            self.persons[person.name, person.account] = person
+
+    def sendOutPipe(self):
+        print "groupchat %s" % self.pipe.OutText
+        if len(self.pipe.OutText()) > 0:
+            self.sendText(self.pipe.OutText())
+            self.pipe.clearOutText()
diff --git a/dreamirc/src/e2support.py b/dreamirc/src/e2support.py
new file mode 100755 (executable)
index 0000000..a212231
--- /dev/null
@@ -0,0 +1,276 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+
+"""Instance Messenger base classes for protocol support.
+
+You will find these useful if you're adding a new protocol to IM.
+"""
+
+# Abstract representation of chat "model" classes
+
+from locals import ONLINE, OFFLINE, OfflineError
+import interfaces
+
+from twisted.internet.protocol import Protocol
+
+from twisted.python.reflect import prefixedMethods
+from twisted.persisted import styles
+
+from twisted.internet import error
+import dreamIRCTools
+from dreamIRCTools import *
+
+class AbstractGroup:
+    def __init__(self, name, account):
+        self.name = name
+        self.account = account
+
+    def getGroupCommands(self):
+        """finds group commands
+
+        these commands are methods on me that start with imgroup_; they are
+        called with no arguments
+        """
+        return prefixedMethods(self, "imgroup_")
+
+    def getTargetCommands(self, target):
+        """finds group commands
+
+        these commands are methods on me that start with imgroup_; they are
+        called with a user present within this room as an argument
+
+        you may want to override this in your group in order to filter for
+        appropriate commands on the given user
+        """
+        return prefixedMethods(self, "imtarget_")
+
+    def join(self):
+        if not self.account.client:
+            raise OfflineError
+        self.account.client.joinGroup(self.name)
+
+    def leave(self):
+        if not self.account.client:
+            raise OfflineError
+        self.account.client.leaveGroup(self.name)
+
+    def __repr__(self):
+        return '<%s %r>' % (self.__class__, self.name)
+
+    def __str__(self):
+        return '%s@%s' % (self.name, self.account.accountName)
+
+class AbstractPerson:
+    def __init__(self, name, baseAccount):
+        self.name = name
+        self.account = baseAccount
+        self.status = OFFLINE
+
+    def getPersonCommands(self):
+        """finds person commands
+
+        these commands are methods on me that start with imperson_; they are
+        called with no arguments
+        """
+        return prefixedMethods(self, "imperson_")
+
+    def getIdleTime(self):
+        """
+        Returns a string.
+        """
+        return '--'
+
+    def __repr__(self):
+        return '<%s %r/%s>' % (self.__class__, self.name, self.status)
+
+    def __str__(self):
+        return '%s@%s' % (self.name, self.account.accountName)
+
+class AbstractClientMixin:
+    """Designed to be mixed in to a Protocol implementing class.
+
+    Inherit from me first.
+
+    @ivar _logonDeferred: Fired when I am done logging in.
+    """
+    def __init__(self, account, chatui, logonDeferred):
+        for base in self.__class__.__bases__:
+            if issubclass(base, Protocol):
+                self.__class__._protoBase = base
+                break
+        else:
+            pass
+        from dreamIRCTools import MessagePipe 
+        self.pipe = MessagePipe()
+        self.account = account
+        self.chat = chatui
+        self._logonDeferred = logonDeferred
+
+    def connectionMade(self):
+        self.pipe.add(_("Connected to IRC server"))
+        self._protoBase.connectionMade(self)
+
+    def connectionLost(self, reason):
+        self.account._clientLost(self, reason)
+        self.unregisterAsAccountClient()
+        self.pipe.add("Uuuups... Connection lost... %s" % reason)
+        return self._protoBase.connectionLost(self, reason)
+
+    def unregisterAsAccountClient(self):
+        """Tell the chat UI that I have `signed off'.
+        """
+        self.chat.unregisterAccountClient(self)
+
+
+class AbstractAccount(styles.Versioned):
+    """Base class for Accounts.
+
+    I am the start of an implementation of L{IAccount<interfaces.IAccount>}, I
+    implement L{isOnline} and most of L{logOn}, though you'll need to implement
+    L{_startLogOn} in a subclass.
+
+    @cvar _groupFactory: A Callable that will return a L{IGroup} appropriate
+        for this account type.
+    @cvar _personFactory: A Callable that will return a L{IPerson} appropriate
+        for this account type.
+
+    @type _isConnecting: boolean
+    @ivar _isConnecting: Whether I am in the process of establishing a
+    connection to the server.
+    @type _isOnline: boolean
+    @ivar _isOnline: Whether I am currently on-line with the server.
+
+    @ivar accountName:
+    @ivar autoLogin:
+    @ivar username:
+    @ivar password:
+    @ivar host:
+    @ivar port:
+    """
+
+    _isOnline = 0
+    _isConnecting = 0
+    client = None
+
+    _groupFactory = AbstractGroup
+    _personFactory = AbstractPerson
+
+    persistanceVersion = 2
+
+    def __init__(self, accountName, autoLogin, username, password, host, port):
+        self.accountName = accountName
+        self.autoLogin = autoLogin
+        self.username = username
+        self.password = password
+        self.host = host
+        self.port = port
+
+        self._groups = {}
+        self._persons = {}
+
+    def upgrateToVersion2(self):
+        # Added in CVS revision 1.16.
+        for k in ('_groups', '_persons'):
+            if not hasattr(self, k):
+                setattr(self, k, {})
+
+    def __getstate__(self):
+        state = styles.Versioned.__getstate__(self)
+        for k in ('client', '_isOnline', '_isConnecting'):
+            try:
+                del state[k]
+            except KeyError:
+                pass
+        return state
+
+    def isOnline(self):
+        return self._isOnline
+
+    def logOn(self, chatui):
+        """Log on to this account.
+
+        Takes care to not start a connection if a connection is
+        already in progress.  You will need to implement
+        L{_startLogOn} for this to work, and it would be a good idea
+        to override L{_loginFailed} too.
+
+        @returntype: Deferred L{interfaces.IClient}
+        """
+        if (not self._isConnecting) and (not self._isOnline):
+            self._isConnecting = 1
+            d = self._startLogOn(chatui)
+            d.addErrback(self._loginFailed)
+            d.addCallback(self._cb_logOn)
+            # if chatui is not None:
+            # (I don't particularly like having to pass chatUI to this function,
+            # but we haven't factored it out yet.)
+            d.addCallback(chatui.registerAccountClient)
+            return d
+        else:
+            raise error.ConnectionError("Connection in progress")
+
+    def getGroup(self, name):
+        """Group factory.
+
+        @param name: Name of the group on this account.
+        @type name: string
+        """
+        group = self._groups.get(name)
+        if group is None:
+            group = self._groupFactory(name, self)
+            self._groups[name] = group
+        return group
+
+    def getPerson(self, name):
+        """Person factory.
+
+        @param name: Name of the person on this account.
+        @type name: string
+        """
+        person = self._persons.get(name)
+        if person is None:
+            person = self._personFactory(name, self)
+            self._persons[name] = person
+        return person
+
+    def _startLogOn(self, chatui):
+        """Start the sign on process.
+
+        Factored out of L{logOn}.
+
+        @returntype: Deferred L{interfaces.IClient}
+        """
+        raise NotImplementedError()
+
+    def _cb_logOn(self, client):
+        self._isConnecting = 0
+        self._isOnline = 1
+        self.client = client
+        return client
+
+    def _loginFailed(self, reason):
+        """Errorback for L{logOn}.
+
+        @type reason: Failure
+
+        @returns: I{reason}, for further processing in the callback chain.
+        @returntype: Failure
+        """
+        self._isConnecting = 0
+        self._isOnline = 0 # just in case
+        return reason
+
+    def _clientLost(self, client, reason):
+        self.client = None
+        self._isConnecting = 0
+        self._isOnline = 0
+        return reason
+
+    def __repr__(self):
+        return "<%s: %s (%s@%s:%s)>" % (self.__class__,
+                                        self.accountName,
+                                        self.username,
+                                        self.host,
+                                        self.port)
diff --git a/dreamirc/src/interfaces.py b/dreamirc/src/interfaces.py
new file mode 100755 (executable)
index 0000000..caf93fb
--- /dev/null
@@ -0,0 +1,327 @@
+# -*- Python -*-
+"""Pan-protocol chat client.
+
+Stability: incendiary, work in progress.
+"""
+from zope.interface import Interface
+
+import locals
+#from locals import *
+#from twisted.words.im import locals
+
+# (Random musings, may not reflect on current state of code:)
+#
+# Accounts have Protocol components (clients)
+# Persons have Conversation components
+# Groups have GroupConversation components
+# Persons and Groups are associated with specific Accounts
+# At run-time, Clients/Accounts are slaved to a User Interface
+#   (Note: User may be a bot, so don't assume all UIs are built on gui toolkits)
+
+
+class IAccount(Interface):
+    """I represent a user's account with a chat service.
+
+    @cvar gatewayType: Identifies the protocol used by this account.
+    @type gatewayType: string
+
+    @ivar client: The Client currently connecting to this account, if any.
+    @type client: L{IClient}
+    """
+
+    def __init__(accountName, autoLogin, username, password, host, port):
+        """
+        @type accountName: string
+        @param accountName: A name to refer to the account by locally.
+        @type autoLogin: boolean
+        @type username: string
+        @type password: string
+        @type host: string
+        @type port: integer
+        """
+
+    def isOnline():
+        """Am I online?
+
+        @returntype: boolean
+        """
+
+    def logOn(chatui):
+        """Go on-line.
+
+        @type chatui: Implementor of C{IChatUI}
+
+        @returntype: Deferred L{Client}
+        """
+
+    def logOff():
+        """Sign off.
+        """
+
+    def getGroup(groupName):
+        """
+        @returntype: L{Group<IGroup>}
+        """
+
+    def getPerson(personName):
+        """
+        @returntype: L{Person<IPerson>}
+        """
+
+class IClient(Interface):
+    """
+    @ivar account: The Account I am a Client for.
+    @type account: L{IAccount}
+    """
+    def __init__(account, chatui, logonDeferred):
+        """
+        @type account: L{IAccount}
+        @type chatui: L{IChatUI}
+        @param logonDeferred: Will be called back once I am logged on.
+        @type logonDeferred: L{Deferred<twisted.internet.defer.Deferred>}
+        """
+
+    def joinGroup(groupName):
+        """
+        @param groupName: The name of the group to join.
+        @type groupName: string
+        """
+
+    def leaveGroup(groupName):
+        """
+        @param groupName: The name of the group to leave.
+        @type groupName: string
+        """
+
+    def getGroupConversation(name,hide=0):
+        pass
+
+    def getPerson(name):
+        pass
+
+
+class IPerson(Interface):
+    def __init__(name, account):
+        """Initialize me.
+
+        @param name: My name, as the server knows me.
+        @type name: string
+        @param account: The account I am accessed through.
+        @type account: I{Account}
+        """
+
+    def isOnline():
+        """Am I online right now?
+
+        @returntype: boolean
+        """
+
+    def getStatus():
+        """What is my on-line status?
+
+        @returns: L{locals.StatusEnum}
+        """
+
+    def getIdleTime():
+        """
+        @returntype: string (XXX: How about a scalar?)
+        """
+
+    def sendMessage(text, metadata=None):
+        """Send a message to this person.
+
+        @type text: string
+        @type metadata: dict
+        """
+
+
+class IGroup(Interface):
+    """A group which you may have a conversation with.
+
+    Groups generally have a loosely-defined set of members, who may
+    leave and join at any time.
+
+    @ivar name: My name, as the server knows me.
+    @type name: string
+    @ivar account: The account I am accessed through.
+    @type account: I{Account<IAccount>}
+    """
+
+    def __init__(name, account):
+        """Initialize me.
+
+        @param name: My name, as the server knows me.
+        @type name: string
+        @param account: The account I am accessed through.
+        @type account: I{Account<IAccount>}
+        """
+
+    def setTopic(text):
+        """Set this Groups topic on the server.
+
+        @type text: string
+        """
+
+    def sendGroupMessage(text, metadata=None):
+        """Send a message to this group.
+
+        @type text: string
+
+        @type metadata: dict
+        @param metadata: Valid keys for this dictionary include:
+
+            - C{'style'}: associated with one of:
+                - C{'emote'}: indicates this is an action
+        """
+
+    def join():
+        pass
+
+    def leave():
+        """Depart this group"""
+
+
+class IConversation(Interface):
+    """A conversation with a specific person."""
+    def __init__(person, chatui):
+        """
+        @type person: L{IPerson}
+        """
+
+    def show():
+        """doesn't seem like it belongs in this interface."""
+
+    def hide():
+        """nor this neither."""
+
+    def sendText(text, metadata):
+        pass
+
+    def showMessage(text, metadata):
+        pass
+
+    def changedNick(person, newnick):
+        """
+        @param person: XXX Shouldn't this always be Conversation.person?
+        """
+
+class IGroupConversation(Interface):
+    def show():
+        """doesn't seem like it belongs in this interface."""
+
+    def hide():
+        """nor this neither."""
+
+    def sendText(text, metadata):
+        pass
+
+    def showGroupMessage(sender, text, metadata):
+        pass
+
+    def setGroupMembers(members):
+        """Sets the list of members in the group and displays it to the user
+        """
+
+    def setTopic(topic, author):
+        """Displays the topic (from the server) for the group conversation window
+
+        @type topic: string
+        @type author: string (XXX: Not Person?)
+        """
+
+    def memberJoined(member):
+        """Adds the given member to the list of members in the group conversation
+        and displays this to the user
+
+        @type member: string (XXX: Not Person?)
+        """
+
+    def memberChangedNick(oldnick, newnick):
+        """Changes the oldnick in the list of members to newnick and displays this
+        change to the user
+
+        @type oldnick: string (XXX: Not Person?)
+        @type newnick: string
+        """
+
+    def memberLeft(member):
+        """Deletes the given member from the list of members in the group
+        conversation and displays the change to the user
+
+        @type member: string (XXX: Not Person?)
+        """
+
+
+class IChatUI(Interface):
+    def registerAccountClient(client):
+        """Notifies user that an account has been signed on to.
+
+        @type client: L{Client<IClient>}
+        """
+
+    def unregisterAccountClient(client):
+        """Notifies user that an account has been signed off or disconnected
+
+        @type client: L{Client<IClient>}
+        """
+
+    def getContactsList():
+        """
+        @returntype: L{ContactsList}
+        """
+
+    # WARNING: You'll want to be polymorphed into something with
+    # intrinsic stoning resistance before continuing.
+
+    def getConversation(person, Class, stayHidden=0):
+        """For the given person object, returns the conversation window
+        or creates and returns a new conversation window if one does not exist.
+
+        @type person: L{Person<IPerson>}
+        @type Class: L{Conversation<IConversation>} class
+        @type stayHidden: boolean
+
+        @returntype: L{Conversation<IConversation>}
+        """
+
+    def getGroupConversation(group,Class,stayHidden=0):
+        """For the given group object, returns the group conversation window or
+        creates and returns a new group conversation window if it doesn't exist.
+
+        @type group: L{Group<interfaces.IGroup>}
+        @type Class: L{Conversation<interfaces.IConversation>} class
+        @type stayHidden: boolean
+
+        @returntype: L{GroupConversation<interfaces.IGroupConversation>}
+        """
+
+    def getPerson(name, client):
+        """Get a Person for a client.
+
+        Duplicates L{IAccount.getPerson}.
+
+        @type name: string
+        @type client: L{Client<IClient>}
+
+        @returntype: L{Person<IPerson>}
+        """
+
+    def getGroup(name, client):
+        """Get a Group for a client.
+
+        Duplicates L{IAccount.getGroup}.
+
+        @type name: string
+        @type client: L{Client<IClient>}
+
+        @returntype: L{Group<IGroup>}
+        """
+
+    def contactChangedNick(oldnick, newnick):
+        """For the given person, changes the person's name to newnick, and
+        tells the contact list and any conversation windows with that person
+        to change as well.
+
+        @type oldnick: string
+        @type newnick: string
+        """
diff --git a/dreamirc/src/ircsupport.py b/dreamirc/src/ircsupport.py
new file mode 100755 (executable)
index 0000000..b0d359d
--- /dev/null
@@ -0,0 +1,286 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""IRC support for Instance Messenger."""
+
+import string
+
+from protocols import irc
+#from twisted.words.protocols import irc
+#from twisted.words.im.locals import ONLINE
+from twisted.internet import defer, reactor, protocol
+from twisted.internet.defer import succeed
+#from twisted.words.im import e2support, interfaces, locals
+import e2support, interfaces,dreamIRCTools
+from zope.interface import implements
+
+
+class IRCPerson(e2support.AbstractPerson):
+
+    def imperson_whois(self):
+        if self.account.client is None:
+            raise locals.OfflineError
+        self.account.client.sendLine("WHOIS %s" % self.name)
+
+    ### interface impl
+
+    def isOnline(self):
+        return ONLINE
+
+    def getStatus(self):
+        return ONLINE
+
+    def setStatus(self,status):
+        self.status=status
+        self.chat.getContactsList().setContactStatus(self)
+
+    def sendMessage(self, text, meta=None):
+        if self.account.client is None:
+            raise locals.OfflineError
+        for line in string.split(text, '\n'):
+            if meta and meta.get("style", None) == "emote":
+                self.account.client.ctcpMakeQuery(self.name,[('ACTION', line)])
+            else:
+                self.account.client.msg(self.name, line)
+        return succeed(text)
+
+    def bye(self):
+        if self.account.client is None:
+            print "not connected"
+        else:
+                 self.account.client.quit("user logged off")             
+
+
+class IRCGroup(e2support.AbstractGroup):
+
+    implements(interfaces.IGroup)
+
+    def imgroup_testAction(self):
+        print 'action test!'
+
+    def imtarget_kick(self, target):
+        if self.account.client is None:
+            raise locals.OfflineError
+        reason = "... and justice for all!"
+        self.account.client.sendLine("KICK #%s %s :%s" % (
+            self.name, target.name, reason))
+
+    ### Interface Implementation
+
+    def setTopic(self, topic):
+        if self.account.client is None:
+            raise locals.OfflineError
+        self.account.client.topic(self.name, topic)
+
+    def sendGroupMessage(self, text, meta={}):
+        if self.account.client is None:
+            raise locals.OfflineError
+        if meta and meta.get("style", None) == "emote":
+            self.account.client.me(self.name,text)
+            return succeed(text)
+        #standard shmandard, clients don't support plain escaped newlines!
+        for line in string.split(text, '\n'):
+            self.account.client.say(self.name, line)
+        return succeed(text)
+
+    def leave(self):
+        if self.account.client is None:
+            raise locals.OfflineError
+        self.account.client.leave(self.name)
+        self.account.client.getGroupConversation(self.name,1)
+        
+    def bye(self):
+        if self.account.client is None:
+            print "not connected"
+        else:
+                       self.account.client.quit("user logged off")       
+
+
+class IRCProto(e2support.AbstractClientMixin, irc.IRCClient):
+    def __init__(self, account, chatui, logonDeferred=None):
+        e2support.AbstractClientMixin.__init__(self, account, chatui,
+                                                 logonDeferred)
+        self._namreplies={}
+        self._ingroups={}
+        self._groups={}
+        self._topics={}
+
+    def getGroupConversation(self, name, hide=0):
+        name=string.lower(name)
+        return self.chat.getGroupConversation(self.chat.getGroup(name, self),
+                                              stayHidden=hide)
+
+    def getPerson(self,name):
+        return self.chat.getPerson(name, self)
+
+    def connectionMade(self):
+        # XXX: Why do I duplicate code in IRCClient.register?
+        try:
+            print 'connection made on irc service!?', self
+            if self.account.password:
+                self.sendLine("PASS :%s" % self.account.password)
+            self.setNick(self.account.username)
+            self.sendLine("USER %s foo bar :dreamIRC e2 v1.0 user" % (self.nickname))
+            for channel in self.account.channels:
+                print "joining channel: %s" % channel
+                self.joinGroup(channel)
+            self.account._isOnline=1
+            print 'uh, registering irc acct'
+            if self._logonDeferred is not None:
+                self._logonDeferred.callback(self)
+            self.chat.getContactsList()
+        except:
+            import traceback
+            traceback.print_exc()
+
+    def setNick(self,nick):
+        self.name=nick
+        self.accountName="%s (IRC)"%nick
+        irc.IRCClient.setNick(self,nick)
+        
+    def quit(self,message='bye bye'):
+#         self.quit_str=str("QUIT :%s" % message)
+         self.sendLine("QUIT :%s" % message)
+
+    def kickedFrom(self, channel, kicker, message):
+        """Called when I am kicked from a channel.
+        """
+        print 'wow i was kicked', channel, kicker, message
+        return self.chat.getGroupConversation(
+            self.chat.getGroup(channel[1:], self), 1)
+
+    def userKicked(self, kickee, channel, kicker, message):
+        print 'whew somebody else', kickee, channel, kicker, message
+
+    def noticed(self, username, channel, message):
+        self.privmsg(username, channel, message, {"dontAutoRespond": 1})
+
+    def privmsg(self, username, channel, message, metadata=None):
+        if metadata is None:
+            metadata = {}
+        username=string.split(username,'!',1)[0]
+        if username==self.name: return
+        if channel[0]=='#':
+            group=channel[1:]
+            self.getGroupConversation(group).showGroupMessage(username, message, metadata)
+            return
+        self.chat.getConversation(self.getPerson(username)).showMessage(message, metadata)
+
+    def action(self,username,channel,emote):
+        username=string.split(username,'!',1)[0]
+        if username==self.name: return
+        meta={'style':'emote'}
+        if channel[0]=='#':
+            group=channel[1:]
+            self.getGroupConversation(group).showGroupMessage(username, emote, meta)
+            return
+        self.chat.getConversation(self.getPerson(username)).showMessage(emote,meta)
+
+    def irc_RPL_NAMREPLY(self,prefix,params):
+        """
+        RPL_NAMREPLY
+        >> NAMES #bnl
+        << :Arlington.VA.US.Undernet.Org 353 z3p = #bnl :pSwede Dan-- SkOyg AG
+        """
+        group=string.lower(params[2][1:])
+        users=string.split(params[3])
+        for ui in range(len(users)):
+            while users[ui][0] in ["@","+"]: # channel modes
+                users[ui]=users[ui][1:]
+        if not self._namreplies.has_key(group):
+            self._namreplies[group]=[]
+        self._namreplies[group].extend(users)
+        for nickname in users:
+                try:
+                    self._ingroups[nickname].append(group)
+                except:
+                    self._ingroups[nickname]=[group]
+
+    def irc_RPL_ENDOFNAMES(self,prefix,params):
+        group=params[1][1:]
+        self.getGroupConversation(group).setGroupMembers(self._namreplies[string.lower(group)])
+        del self._namreplies[string.lower(group)]
+
+    def irc_RPL_TOPIC(self,prefix,params):
+        self._topics[params[1][1:]]=params[2]
+
+    def irc_333(self,prefix,params):
+        group=params[1][1:]
+        self.getGroupConversation(group).setTopic(self._topics[group],params[2])
+        del self._topics[group]
+
+    def irc_TOPIC(self,prefix,params):
+        nickname = string.split(prefix,"!")[0]
+        group = params[0][1:]
+        topic = params[1]
+        self.getGroupConversation(group).setTopic(topic,nickname)
+
+    def irc_JOIN(self,prefix,params):
+        nickname=string.split(prefix,"!")[0]
+        group=string.lower(params[0][1:])
+        if nickname!=self.nickname:
+            try:
+                self._ingroups[nickname].append(group)
+            except:
+                self._ingroups[nickname]=[group]
+            self.getGroupConversation(group).memberJoined(nickname)
+
+    def irc_PART(self,prefix,params):
+        nickname=string.split(prefix,"!")[0]
+        group=string.lower(params[0][1:])
+        if nickname!=self.nickname:
+            if group in self._ingroups[nickname]:
+                self._ingroups[nickname].remove(group)
+                self.getGroupConversation(group).memberLeft(nickname)
+            else:
+                print "%s left %s, but wasn't in the room."%(nickname,group)
+
+    def irc_QUIT(self,prefix,params):
+        nickname=string.split(prefix,"!")[0]
+        if self._ingroups.has_key(nickname):
+            for group in self._ingroups[nickname]:
+                self.getGroupConversation(group).memberLeft(nickname)
+            self._ingroups[nickname]=[]
+        else:
+            print '*** WARNING: ingroups had no such key %s' % nickname
+
+    def irc_NICK(self, prefix, params):
+        fromNick = string.split(prefix, "!")[0]
+        toNick = params[0]
+        if not self._ingroups.has_key(fromNick):
+            print "%s changed nick to %s. But she's not in any groups!?" % (fromNick, toNick)
+            return
+        for group in self._ingroups[fromNick]:
+            self.getGroupConversation(group).memberChangedNick(fromNick, toNick)
+        self._ingroups[toNick] = self._ingroups[fromNick]
+        del self._ingroups[fromNick]
+
+    def irc_unknown(self, prefix, command, params):
+        print "unknown message from IRCserver. prefix: %s, command: %s, params: %s" % (prefix, command, params)
+
+    # GTKIM calls
+    def joinGroup(self,name):
+        self.join(name)
+        self.getGroupConversation(name)
+
+class IRCAccount(e2support.AbstractAccount):
+    implements(interfaces.IAccount)
+    gatewayType = "IRC"
+
+    _groupFactory = IRCGroup
+    _personFactory = IRCPerson
+
+    def __init__(self, accountName, autoLogin, username, password, host, port, channels=''):
+        e2support.AbstractAccount.__init__(self, accountName, autoLogin, username, password, host, port)
+        self.channels = map(string.strip,string.split(channels,','))
+        if self.channels == ['']:
+            self.channels = []
+
+    def _startLogOn(self, chatui):
+        logonDeferred = defer.Deferred()
+        cc = protocol.ClientCreator(reactor, IRCProto, self, chatui, logonDeferred)
+        d = cc.connectTCP(self.host, self.port)
+        d.addErrback(logonDeferred.errback)
+        return logonDeferred
+        
diff --git a/dreamirc/src/keymap.xml b/dreamirc/src/keymap.xml
new file mode 100755 (executable)
index 0000000..66ee835
--- /dev/null
@@ -0,0 +1,16 @@
+<keymap>
+       <map context="dreamIRCActions">
+               <key id="KEY_OK" mapto="ok" flags="m" />
+               <key id="KEY_EXIT" mapto="cancel" flags="m" />
+               <key id="KEY_RED" mapto="red" flags="m" />
+               <key id="KEY_GREEN" mapto="green" flags="m" />
+               <key id="KEY_YELLOW" mapto="yellow" flags="m" />
+               <key id="KEY_BLUE" mapto="blue" flags="m" />
+               <key id="KEY_UP" mapto="up" flags="mr" />
+               <key id="KEY_DOWN" mapto="down" flags="mr" />
+               <key id="KEY_LEFT" mapto="left" flags="mr" />
+               <key id="KEY_RIGHT" mapto="right" flags="mr" />
+               <key id="KEY_CHANNELUP" mapto="buddyUp" flags="mr" />
+               <key id="KEY_CHANNELDOWN" mapto="buddyDown" flags="mr" />       
+       </map>
+</keymap>
\ No newline at end of file
diff --git a/dreamirc/src/locals.py b/dreamirc/src/locals.py
new file mode 100755 (executable)
index 0000000..02025f9
--- /dev/null
@@ -0,0 +1,26 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+class Enum:
+    group = None
+
+    def __init__(self, label):
+        self.label = label
+
+    def __repr__(self):
+        return '<%s: %s>' % (self.group, self.label)
+
+    def __str__(self):
+        return self.label
+
+
+class StatusEnum(Enum):
+    group = 'Status'
+
+OFFLINE = Enum('Offline')
+ONLINE = Enum('Online')
+AWAY = Enum('Away')
+
+class OfflineError(Exception):
+    """The requested action can't happen while offline."""
diff --git a/dreamirc/src/maintainer.info b/dreamirc/src/maintainer.info
new file mode 100755 (executable)
index 0000000..0c74ceb
--- /dev/null
@@ -0,0 +1,2 @@
+luke_s@opendreambox.org
+dreamIRC-Client
diff --git a/dreamirc/src/plugin.py b/dreamirc/src/plugin.py
new file mode 100755 (executable)
index 0000000..4433038
--- /dev/null
@@ -0,0 +1,277 @@
+from enigma import *
+from Screens.Screen import Screen
+from Screens.VirtualKeyBoard import VirtualKeyBoard
+
+from Components.Pixmap import Pixmap
+from Components.ActionMap import HelpableActionMap, ActionMap, NumberActionMap
+from Components.ScrollLabel import ScrollLabel
+from Components.Input import Input
+from Components.Label import Label
+from Components.HTMLComponent import *
+from Components.GUIComponent import *
+from Plugins.Plugin import PluginDescriptor
+
+from Tools.NumericalTextInput import *
+from Tools.Directories import *
+
+#from socket import socket
+
+import e2reactor
+
+from twisted.internet import reactor
+from twisted.internet import protocol
+from twisted.python import log
+from twisted.internet.defer import *
+
+#import e2chat, e2account, e2support, dreamIRCTools, dreamIRCSetup
+from e2chat import *
+from e2account import *
+from e2support import *
+from dreamIRCTools import *
+from dreamIRCSetup import *
+from protocols import irc
+import ircsupport
+
+#from enigma import eLabel, eWidget, eSlider, fontRenderClass, ePoint, eSize, getDesktop
+
+import os 
+import string
+import time
+import datetime
+import sys
+x=0
+y=0
+
+class dreamIRCMainMenu(Screen):
+
+       from enigma import getDesktop
+       desk = getDesktop(0)
+       global x,y
+       x= int(desk.size().width())
+       y= int(desk.size().height())
+       print "[dreamIRC] mainscreen: current desktop size: %dx%d" % (x,y)
+
+       if (y>=720):
+               skin = """
+                       <screen position="80,80" size="1120,600"  title="dreamIRC" >
+                               <widget name="buddy" position="940,35" size="170,450" font="Regular;14" />
+                               <widget name="chat" position="10,35" size="920,460" font="Regular;14" />
+                               <widget name="input" position="10,550" size="830,20" font="Regular;16" />
+                               <widget name="chat.desc" position="10,10" size="460,20" font="Regular;16" />
+                               <widget name="buddy.desc" position="940,10" size="120,20" font="Regular;16" />
+                               <widget name="input.desc" position="10,520" size="360,18" font="Regular;16" />
+                               <widget name="red.pic" position="910,511" size="15,15" pixmap="skin_default/buttons/button_red.png" transparent="1" alphatest="on"/>
+                               <widget name="green.pic" position="910,531" size="15,15" pixmap="skin_default/buttons/button_green.png" transparent="1" alphatest="on"/>
+                               <widget name="yellow.pic" position="910,551" size="15,15" pixmap="skin_default/buttons/button_yellow.png" transparent="1" alphatest="on"/>
+                               <widget name="blue.pic" position="910,571" size="15,15" pixmap="skin_default/buttons/button_blue.png" transparent="1" alphatest="on"/>
+                               <widget name="disconnect.desc" position="940,510" size="110,20" font="Regular;16" />
+                               <widget name="connect.desc" position="940,530" size="110,20" font="Regular;16" />
+                               <widget name="settings.desc" position="940,550" size="110,20" font="Regular;16" />
+                               <widget name="blue.desc" position="940,570" size="180,20" font="Regular;16" />                                
+                       </screen>"""
+       else:   
+               skin = """
+                       <screen position="60,80" size="600,450"  title="dreamIRC" >
+                               <widget name="buddy" position="480,35" size="120,310" font="Regular;14" />
+                               <widget name="chat" position="10,35" size="460,310" font="Regular;14" />
+                               <widget name="input" position="10,400" size="360,20" font="Regular;16" />
+                               <widget name="chat.desc" position="10,10" size="460,20" font="Regular;16" />
+                               <widget name="buddy.desc" position="480,10" size="120,20" font="Regular;16" />
+                               <widget name="input.desc" position="10,370" size="360,18" font="Regular;16" />
+                               <widget name="red.pic" position="470,362" size="15,15" pixmap="skin_default/buttons/button_red.png" transparent="1" alphatest="on"/>
+                               <widget name="green.pic" position="470,382" size="15,15" pixmap="skin_default/buttons/button_green.png" transparent="1" alphatest="on"/>
+                               <widget name="yellow.pic" position="470,402" size="15,15" pixmap="skin_default/buttons/button_yellow.png" transparent="1" alphatest="on"/>
+                               <widget name="blue.pic" position="470,422" size="15,15" pixmap="skin_default/buttons/button_blue.png" transparent="1" alphatest="on"/>
+                               <widget name="disconnect.desc" position="490,360" size="110,20" font="Regular;16" />
+                               <widget name="connect.desc" position="490,380" size="110,20" font="Regular;16" />
+                               <widget name="settings.desc" position="490,400" size="110,20" font="Regular;16" />
+                               <widget name="blue.desc" position="490,420" size="110,20" font="Regular;16" />
+                       </screen>"""
+               
+       
+       def __init__(self, session, args = 0):
+               global x,y
+               self.skin = dreamIRCMainMenu.skin
+               Screen.__init__(self, session)
+
+               self.menu = args
+               self.pipe=MessagePipe()
+               self.account = AccountManager(self.session)
+
+               self.list = []
+               self.menuList = []
+               
+               self.connected = False
+
+               self["buddy"] = BuddyWindow("")
+               self["chat"] = ChatWindow("")
+               self["input"] = Input("")
+
+               self["buddy.desc"] = Label(_("User Online"))
+               self["input.desc"] = Label(_("Type your text here and press OK to send:"))
+               self["chat.desc"] = ChanName(_("ChatBox"))
+               self["connect.desc"] = Label(_("Connect"))
+               self["disconnect.desc"] = Label(_("Disconnect"))
+               self["settings.desc"] = Label(_("Settings"))
+               if y>=720:
+                               self["blue.desc"] = Label(_("virtual Keyboard"))
+               else:
+                               self["blue.desc"] = Label(_("virtual Keyb."))                           
+               self["green.pic"] = Pixmap()
+               self["red.pic"] = Pixmap()
+               self["yellow.pic"] = Pixmap()
+               self["blue.pic"] = Pixmap()
+               
+               self.checkStatus()
+               
+               self["actions"] = NumberActionMap(["dreamIRCActions", "InputBoxActions", "InputAsciiActions", "KeyboardInputActions"],
+               {
+                       "gotAsciiCode": self.gotAsciiCode,
+                       "red": self.redPressed,
+                       "green": self.greenPressed,
+                       "yellow": self.yellowPressed,
+                       "blue": self.bluePressed,
+                       "ok": self.go,
+                       "cancel": self.closePlugin,
+                       "back": self.closePlugin,
+                       "right": self.keyRight,
+                       "left": self.keyLeft,
+                       "up": self["chat"].pageUp,
+                       "down": self["chat"].pageDown,
+                       "buddyUp": self["buddy"].pageUp,
+                       "buddyDown": self["buddy"].pageDown,
+                       "home": self.keyHome,                
+                       "end": self.keyEnd,
+                       "delete": self.keyDelete,
+                       "deleteForward": self.keyDeleteForward,
+                       "deleteBackward": self.keyDeleteBackward,
+                       "tab": self.keyTab,
+                       "1": self.keyNumberGlobal,
+                       "2": self.keyNumberGlobal,
+                       "3": self.keyNumberGlobal,
+                       "4": self.keyNumberGlobal,
+                       "5": self.keyNumberGlobal,
+                       "6": self.keyNumberGlobal,
+                       "7": self.keyNumberGlobal,
+                       "8": self.keyNumberGlobal,
+                       "9": self.keyNumberGlobal,
+                       "0": self.keyNumberGlobal            
+               }, -1)
+               rcinput = eRCInput.getInstance()
+               rcinput.setKeyboardMode(rcinput.kmAscii)
+
+       def gotAsciiCode(self):
+               self["input"].handleAscii(getPrevAsciiCode())
+       
+       def keyUp(self):
+               self["input"].up()
+       
+       def keyDown(self):
+               self["input"].down()
+       
+       def keyLeft(self):
+               self["input"].left()
+       
+       def keyRight(self):
+               self["input"].right()
+       
+       def keyTab(self):
+               self["input"].tab()
+       
+       def keyHome(self):
+               self["input"].home()
+       
+       def keyEnd(self):
+               self["input"].end()
+
+       def keyNumberGlobal(self, number):
+               print "You pressed number " + str(number)
+               self["input"].number(number)
+               
+       def keyDelete(self):
+               self["input"].delete()
+       
+       def keyDeleteForward(self):
+               self["input"].delete()
+       
+       def keyDeleteBackward(self):
+               self["input"].left()
+               self["input"].delete()
+               
+       def closePlugin(self):
+               rcinput = eRCInput.getInstance()
+               rcinput.setKeyboardMode(rcinput.kmNone)
+               self.close(None)
+               
+       def greenPressed(self):
+               if self.checkStatus()==0:
+                       self.pipe.add("connecting... pls wait...")
+                       self.account = AccountManager(self.session)    #reload accounts :)
+                       self.account.startConnect()
+                       self["disconnect.desc"].show()
+                       self["red.pic"].show()
+
+       def redPressed(self):
+               if self.checkStatus()==1:
+                       self.pipe.add("disconnecting... pls wait...")
+                       self.pipe.addOutText("/QUIT")
+                       try:
+                               timestamp = time.strftime("%Y%m%d_%H%M%S", time.localtime(time.time()))
+                               fp = file("/var/log/dreamIRC.log", 'r')
+                               fp.close()
+                               os.rename("/var/log/dreamIRC.log", "/var/log/dreamIRC_%s.log"%timestamp)
+                       except IOError:
+                               print "--- nothing to remove---"
+                       self.pipe.clear()
+                       self.pipe.add(" -- not connected.. pls press green to connect!!\n")
+                       self.pipe.clearBuddyList()
+                       self.pipe.resetDesc()
+                       self["disconnect.desc"].hide()
+                       self["red.pic"].hide()
+
+       def checkStatus(self):
+               status = self.account.getConnectionInfo()
+               if status[0]==1:
+                       self["disconnect.desc"].show()
+                       self["red.pic"].show()
+               elif status[0]==0:
+                       self["disconnect.desc"].hide()
+                       self["red.pic"].hide()
+               return status[0]
+       
+       def bluePressed(self):
+               self.session.openWithCallback(self.VirtualKeyBoardTextEntry, VirtualKeyBoard, title = (_("Enter your text here:")), text = "")
+
+               
+       def yellowPressed(self):
+               self.session.openWithCallback(self.resetKeyboard,dreamIRCSetupScreen)
+               
+       def resetKeyboard(self):
+               rcinput = eRCInput.getInstance()
+               rcinput.setKeyboardMode(rcinput.kmAscii)
+               
+
+       def go(self):
+               print " TEXT = %s   - laenge = %d  !!!!" % (self["input"].getText(),len(self["input"].getText()))
+               if (len(self["input"].getText()) >= 1):
+                       self.pipe.addOutText(self["input"].getText())
+                       self.clearInput()
+                       
+       def clearInput(self):
+               self["input"].setText("")
+                       
+       def VirtualKeyBoardTextEntry(self, callback = None):
+               if callback is not None and len(callback):
+                       print " TEXT = %s   - laenge = %d  !!!!" % (callback,len(callback))
+                       self.pipe.addOutText(callback)
+
+def main(session, **kwargs):
+        session.open(dreamIRCMainMenu)
+
+def Plugins(**kwargs):
+        return PluginDescriptor(
+                name="dreamIRC",
+                description="dreamIRC Client for Enigma2",
+                icon="plugin.png",
+                where=[ PluginDescriptor.WHERE_EXTENSIONSMENU, PluginDescriptor.WHERE_PLUGINMENU ],
+                fnc=main)
\ No newline at end of file
diff --git a/dreamirc/src/protocols/Makefile.am b/dreamirc/src/protocols/Makefile.am
new file mode 100644 (file)
index 0000000..3d99008
--- /dev/null
@@ -0,0 +1,4 @@
+installdir = $(LIBDIR)/enigma2/python/Plugins/Extensions/dreamIRC/protocols
+
+install_PYTHON = *.py
+
diff --git a/dreamirc/src/protocols/__init__.py b/dreamirc/src/protocols/__init__.py
new file mode 100755 (executable)
index 0000000..e69de29
diff --git a/dreamirc/src/protocols/irc.py b/dreamirc/src/protocols/irc.py
new file mode 100755 (executable)
index 0000000..a8220dd
--- /dev/null
@@ -0,0 +1,2329 @@
+# -*- test-case-name: twisted.words.test.test_irc -*-
+# Copyright (c) 2001-2005 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""Internet Relay Chat Protocol for client and server.
+
+Future Plans
+============
+
+The way the IRCClient class works here encourages people to implement
+IRC clients by subclassing the ephemeral protocol class, and it tends
+to end up with way more state than it should for an object which will
+be destroyed as soon as the TCP transport drops.  Someone oughta do
+something about that, ya know?
+
+The DCC support needs to have more hooks for the client for it to be
+able to ask the user things like \"Do you want to accept this session?\"
+and \"Transfer #2 is 67% done.\" and otherwise manage the DCC sessions.
+
+Test coverage needs to be better.
+
+@author: U{Kevin Turner<mailto:acapnotic@twistedmatrix.com>}
+
+@see: RFC 1459: Internet Relay Chat Protocol
+@see: RFC 2812: Internet Relay Chat: Client Protocol
+@see: U{The Client-To-Client-Protocol
+<http://www.irchelp.org/irchelp/rfc/ctcpspec.html>}
+"""
+
+__version__ = '$Revision$'[11:-2]
+
+from twisted.internet import reactor, protocol
+from twisted.persisted import styles
+from twisted.protocols import basic
+from twisted.python import log, reflect, text
+
+# System Imports
+
+import errno
+import os
+import random
+import re
+import stat
+import string
+import struct
+import sys
+import time
+import types
+import traceback
+import socket
+
+from os import path
+
+NUL = chr(0)
+CR = chr(015)
+NL = chr(012)
+LF = NL
+SPC = chr(040)
+
+CHANNEL_PREFIXES = '&#!+'
+
+class IRCBadMessage(Exception):
+    pass
+
+class IRCPasswordMismatch(Exception):
+    pass
+
+def parsemsg(s):
+    """Breaks a message from an IRC server into its prefix, command, and arguments.
+    """
+    prefix = ''
+    trailing = []
+    if not s:
+        raise IRCBadMessage("Empty line.")
+    if s[0] == ':':
+        prefix, s = s[1:].split(' ', 1)
+    if s.find(' :') != -1:
+        s, trailing = s.split(' :', 1)
+        args = s.split()
+        args.append(trailing)
+    else:
+        args = s.split()
+    command = args.pop(0)
+    return prefix, command, args
+
+
+def split(str, length = 80):
+    """I break a message into multiple lines.
+
+    I prefer to break at whitespace near str[length].  I also break at \\n.
+
+    @returns: list of strings
+    """
+    if length <= 0:
+        raise ValueError("Length must be a number greater than zero")
+    r = []
+    while len(str) > length:
+        w, n = str[:length].rfind(' '), str[:length].find('\n')
+        if w == -1 and n == -1:
+            line, str = str[:length], str[length:]
+        else:
+            i = n == -1 and w or n
+            line, str = str[:i], str[i+1:]
+        r.append(line)
+    if len(str):
+        r.extend(str.split('\n'))
+    return r
+
+class IRC(protocol.Protocol):
+    """Internet Relay Chat server protocol.
+    """
+
+    buffer = ""
+    hostname = None
+
+    encoding = None
+
+    def connectionMade(self):
+        self.channels = []
+        if self.hostname is None:
+            self.hostname = socket.getfqdn()
+
+
+    def sendLine(self, line):
+        if self.encoding is not None:
+            if isinstance(line, unicode):
+                line = line.encode(self.encoding)
+        self.transport.write("%s%s%s" % (line, CR, LF))
+
+
+    def sendMessage(self, command, *parameter_list, **prefix):
+        """Send a line formatted as an IRC message.
+
+        First argument is the command, all subsequent arguments
+        are parameters to that command.  If a prefix is desired,
+        it may be specified with the keyword argument 'prefix'.
+        """
+
+        if not command:
+            raise ValueError, "IRC message requires a command."
+
+        if ' ' in command or command[0] == ':':
+            # Not the ONLY way to screw up, but provides a little
+            # sanity checking to catch likely dumb mistakes.
+            raise ValueError, "Somebody screwed up, 'cuz this doesn't" \
+                  " look like a command to me: %s" % command
+
+        line = string.join([command] + list(parameter_list))
+        if prefix.has_key('prefix'):
+            line = ":%s %s" % (prefix['prefix'], line)
+        self.sendLine(line)
+
+        if len(parameter_list) > 15:
+            log.msg("Message has %d parameters (RFC allows 15):\n%s" %
+                    (len(parameter_list), line))
+
+
+    def dataReceived(self, data):
+        """This hack is to support mIRC, which sends LF only,
+        even though the RFC says CRLF.  (Also, the flexibility
+        of LineReceiver to turn "line mode" on and off was not
+        required.)
+        """
+        lines = (self.buffer + data).split(LF)
+        # Put the (possibly empty) element after the last LF back in the
+        # buffer
+        self.buffer = lines.pop()
+
+        for line in lines:
+            if len(line) <= 2:
+                # This is a blank line, at best.
+                continue
+            if line[-1] == CR:
+                line = line[:-1]
+            prefix, command, params = parsemsg(line)
+            # mIRC is a big pile of doo-doo
+            command = command.upper()
+            # DEBUG: log.msg( "%s %s %s" % (prefix, command, params))
+
+            self.handleCommand(command, prefix, params)
+
+
+    def handleCommand(self, command, prefix, params):
+        """Determine the function to call for the given command and call
+        it with the given arguments.
+        """
+        method = getattr(self, "irc_%s" % command, None)
+        try:
+            if method is not None:
+                method(prefix, params)
+            else:
+                self.irc_unknown(prefix, command, params)
+        except:
+            log.deferr()
+
+
+    def irc_unknown(self, prefix, command, params):
+        """Implement me!"""
+        raise NotImplementedError(command, prefix, params)
+
+
+    # Helper methods
+    def privmsg(self, sender, recip, message):
+        """Send a message to a channel or user
+
+        @type sender: C{str} or C{unicode}
+        @param sender: Who is sending this message.  Should be of the form
+        username!ident@hostmask (unless you know better!).
+
+        @type recip: C{str} or C{unicode}
+        @param recip: The recipient of this message.  If a channel, it
+        must start with a channel prefix.
+
+        @type message: C{str} or C{unicode}
+        @param message: The message being sent.
+        """
+        self.sendLine(":%s PRIVMSG %s :%s" % (sender, recip, lowQuote(message)))
+
+
+    def notice(self, sender, recip, message):
+        """Send a \"notice\" to a channel or user.
+
+        Notices differ from privmsgs in that the RFC claims they are different.
+        Robots are supposed to send notices and not respond to them.  Clients
+        typically display notices differently from privmsgs.
+
+        @type sender: C{str} or C{unicode}
+        @param sender: Who is sending this message.  Should be of the form
+        username!ident@hostmask (unless you know better!).
+
+        @type recip: C{str} or C{unicode}
+        @param recip: The recipient of this message.  If a channel, it
+        must start with a channel prefix.
+
+        @type message: C{str} or C{unicode}
+        @param message: The message being sent.
+        """
+        self.sendLine(":%s NOTICE %s :%s" % (sender, recip, message))
+
+
+    def action(self, sender, recip, message):
+        """Send an action to a channel or user.
+
+        @type sender: C{str} or C{unicode}
+        @param sender: Who is sending this message.  Should be of the form
+        username!ident@hostmask (unless you know better!).
+
+        @type recip: C{str} or C{unicode}
+        @param recip: The recipient of this message.  If a channel, it
+        must start with a channel prefix.
+
+        @type message: C{str} or C{unicode}
+        @param message: The action being sent.
+        """
+        self.sendLine(":%s ACTION %s :%s" % (sender, recip, message))
+
+
+    def topic(self, user, channel, topic, author=None):
+        """Send the topic to a user.
+
+        @type user: C{str} or C{unicode}
+        @param user: The user receiving the topic.  Only their nick name, not
+        the full hostmask.
+
+        @type channel: C{str} or C{unicode}
+        @param channel: The channel for which this is the topic.
+
+        @type topic: C{str} or C{unicode} or C{None}
+        @param topic: The topic string, unquoted, or None if there is
+        no topic.
+
+        @type author: C{str} or C{unicode}
+        @param author: If the topic is being changed, the full username and hostmask
+        of the person changing it.
+        """
+        if author is None:
+            if topic is None:
+                self.sendLine(':%s %s %s %s :%s' % (
+                    self.hostname, RPL_NOTOPIC, user, channel, 'No topic is set.'))
+            else:
+                self.sendLine(":%s %s %s %s :%s" % (
+                    self.hostname, RPL_TOPIC, user, channel, lowQuote(topic)))
+        else:
+            self.sendLine(":%s TOPIC %s :%s" % (author, channel, lowQuote(topic)))
+
+
+    def topicAuthor(self, user, channel, author, date):
+        """
+        Send the author of and time at which a topic was set for the given
+        channel.
+
+        This sends a 333 reply message, which is not part of the IRC RFC.
+
+        @type user: C{str} or C{unicode}
+        @param user: The user receiving the topic.  Only their nick name, not
+        the full hostmask.
+
+        @type channel: C{str} or C{unicode}
+        @param channel: The channel for which this information is relevant.
+
+        @type author: C{str} or C{unicode}
+        @param author: The nickname (without hostmask) of the user who last
+        set the topic.
+
+        @type date: C{int}
+        @param date: A POSIX timestamp (number of seconds since the epoch)
+        at which the topic was last set.
+        """
+        self.sendLine(':%s %d %s %s %s %d' % (
+            self.hostname, 333, user, channel, author, date))
+
+
+    def names(self, user, channel, names):
+        """Send the names of a channel's participants to a user.
+
+        @type user: C{str} or C{unicode}
+        @param user: The user receiving the name list.  Only their nick
+        name, not the full hostmask.
+
+        @type channel: C{str} or C{unicode}
+        @param channel: The channel for which this is the namelist.
+
+        @type names: C{list} of C{str} or C{unicode}
+        @param names: The names to send.
+        """
+        # XXX If unicode is given, these limits are not quite correct
+        prefixLength = len(channel) + len(user) + 10
+        namesLength = 512 - prefixLength
+
+        L = []
+        count = 0
+        for n in names:
+            if count + len(n) + 1 > namesLength:
+                self.sendLine(":%s %s %s = %s :%s" % (
+                    self.hostname, RPL_NAMREPLY, user, channel, ' '.join(L)))
+                L = [n]
+                count = len(n)
+            else:
+                L.append(n)
+                count += len(n) + 1
+        if L:
+            self.sendLine(":%s %s %s = %s :%s" % (
+                self.hostname, RPL_NAMREPLY, user, channel, ' '.join(L)))
+        self.sendLine(":%s %s %s %s :End of /NAMES list" % (
+            self.hostname, RPL_ENDOFNAMES, user, channel))
+
+
+    def who(self, user, channel, memberInfo):
+        """
+        Send a list of users participating in a channel.
+
+        @type user: C{str} or C{unicode}
+        @param user: The user receiving this member information.  Only their
+        nick name, not the full hostmask.
+
+        @type channel: C{str} or C{unicode}
+        @param channel: The channel for which this is the member
+        information.
+
+        @type memberInfo: C{list} of C{tuples}
+        @param memberInfo: For each member of the given channel, a 7-tuple
+        containing their username, their hostmask, the server to which they
+        are connected, their nickname, the letter "H" or "G" (wtf do these
+        mean?), the hopcount from C{user} to this member, and this member's
+        real name.
+        """
+        for info in memberInfo:
+            (username, hostmask, server, nickname, flag, hops, realName) = info
+            assert flag in ("H", "G")
+            self.sendLine(":%s %s %s %s %s %s %s %s %s :%d %s" % (
+                self.hostname, RPL_WHOREPLY, user, channel,
+                username, hostmask, server, nickname, flag, hops, realName))
+
+        self.sendLine(":%s %s %s %s :End of /WHO list." % (
+            self.hostname, RPL_ENDOFWHO, user, channel))
+
+
+    def whois(self, user, nick, username, hostname, realName, server, serverInfo, oper, idle, signOn, channels):
+        """
+        Send information about the state of a particular user.
+
+        @type user: C{str} or C{unicode}
+        @param user: The user receiving this information.  Only their nick
+        name, not the full hostmask.
+
+        @type nick: C{str} or C{unicode}
+        @param nick: The nickname of the user this information describes.
+
+        @type username: C{str} or C{unicode}
+        @param username: The user's username (eg, ident response)
+
+        @type hostname: C{str}
+        @param hostname: The user's hostmask
+
+        @type realName: C{str} or C{unicode}
+        @param realName: The user's real name
+
+        @type server: C{str} or C{unicode}
+        @param server: The name of the server to which the user is connected
+
+        @type serverInfo: C{str} or C{unicode}
+        @param serverInfo: A descriptive string about that server
+
+        @type oper: C{bool}
+        @param oper: Indicates whether the user is an IRC operator
+
+        @type idle: C{int}
+        @param idle: The number of seconds since the user last sent a message
+
+        @type signOn: C{int}
+        @param signOn: A POSIX timestamp (number of seconds since the epoch)
+        indicating the time the user signed on
+
+        @type channels: C{list} of C{str} or C{unicode}
+        @param channels: A list of the channels which the user is participating in
+        """
+        self.sendLine(":%s %s %s %s %s %s * :%s" % (
+            self.hostname, RPL_WHOISUSER, user, nick, username, hostname, realName))
+        self.sendLine(":%s %s %s %s %s :%s" % (
+            self.hostname, RPL_WHOISSERVER, user, nick, server, serverInfo))
+        if oper:
+            self.sendLine(":%s %s %s %s :is an IRC operator" % (
+                self.hostname, RPL_WHOISOPERATOR, user, nick))
+        self.sendLine(":%s %s %s %s %d %d :seconds idle, signon time" % (
+            self.hostname, RPL_WHOISIDLE, user, nick, idle, signOn))
+        self.sendLine(":%s %s %s %s :%s" % (
+            self.hostname, RPL_WHOISCHANNELS, user, nick, ' '.join(channels)))
+        self.sendLine(":%s %s %s %s :End of WHOIS list." % (
+            self.hostname, RPL_ENDOFWHOIS, user, nick))
+
+
+    def join(self, who, where):
+        """Send a join message.
+
+        @type who: C{str} or C{unicode}
+        @param who: The name of the user joining.  Should be of the form
+        username!ident@hostmask (unless you know better!).
+
+        @type where: C{str} or C{unicode}
+        @param where: The channel the user is joining.
+        """
+        self.sendLine(":%s JOIN %s" % (who, where))
+
+
+    def part(self, who, where, reason=None):
+        """Send a part message.
+
+        @type who: C{str} or C{unicode}
+        @param who: The name of the user joining.  Should be of the form
+        username!ident@hostmask (unless you know better!).
+
+        @type where: C{str} or C{unicode}
+        @param where: The channel the user is joining.
+
+        @type reason: C{str} or C{unicode}
+        @param reason: A string describing the misery which caused
+        this poor soul to depart.
+        """
+        if reason:
+            self.sendLine(":%s PART %s :%s" % (who, where, reason))
+        else:
+            self.sendLine(":%s PART %s" % (who, where))
+
+
+    def channelMode(self, user, channel, mode, *args):
+        """
+        Send information about the mode of a channel.
+
+        @type user: C{str} or C{unicode}
+        @param user: The user receiving the name list.  Only their nick
+        name, not the full hostmask.
+
+        @type channel: C{str} or C{unicode}
+        @param channel: The channel for which this is the namelist.
+
+        @type mode: C{str}
+        @param mode: A string describing this channel's modes.
+
+        @param args: Any additional arguments required by the modes.
+        """
+        self.sendLine(":%s %s %s %s %s %s" % (
+            self.hostname, RPL_CHANNELMODEIS, user, channel, mode, ' '.join(args)))
+
+
+class IRCClient(basic.LineReceiver):
+    """Internet Relay Chat client protocol, with sprinkles.
+
+    In addition to providing an interface for an IRC client protocol,
+    this class also contains reasonable implementations of many common
+    CTCP methods.
+
+    TODO
+    ====
+     - Limit the length of messages sent (because the IRC server probably
+       does).
+     - Add flood protection/rate limiting for my CTCP replies.
+     - NickServ cooperation.  (a mix-in?)
+     - Heartbeat.  The transport may die in such a way that it does not realize
+       it is dead until it is written to.  Sending something (like \"PING
+       this.irc-host.net\") during idle peroids would alleviate that.  If
+       you're concerned with the stability of the host as well as that of the
+       transport, you might care to watch for the corresponding PONG.
+
+    @ivar nickname: Nickname the client will use.
+    @ivar password: Password used to log on to the server.  May be C{None}.
+    @ivar realname: Supplied to the server during login as the \"Real name\"
+        or \"ircname\".  May be C{None}.
+    @ivar username: Supplied to the server during login as the \"User name\".
+        May be C{None}
+
+    @ivar userinfo: Sent in reply to a X{USERINFO} CTCP query.  If C{None}, no
+        USERINFO reply will be sent.
+        \"This is used to transmit a string which is settable by
+        the user (and never should be set by the client).\"
+    @ivar fingerReply: Sent in reply to a X{FINGER} CTCP query.  If C{None}, no
+        FINGER reply will be sent.
+    @type fingerReply: Callable or String
+
+    @ivar versionName: CTCP VERSION reply, client name.  If C{None}, no VERSION
+        reply will be sent.
+    @ivar versionNum: CTCP VERSION reply, client version,
+    @ivar versionEnv: CTCP VERSION reply, environment the client is running in.
+
+    @ivar sourceURL: CTCP SOURCE reply, a URL where the source code of this
+        client may be found.  If C{None}, no SOURCE reply will be sent.
+
+    @ivar lineRate: Minimum delay between lines sent to the server.  If
+        C{None}, no delay will be imposed.
+    @type lineRate: Number of Seconds.
+    """
+
+    motd = ""
+    nickname = 'irc'
+    password = None
+    realname = None
+    username = None
+    ### Responses to various CTCP queries.
+
+    userinfo = None
+    # fingerReply is a callable returning a string, or a str()able object.
+    fingerReply = None
+    versionName = None
+    versionNum = None
+    versionEnv = None
+
+    sourceURL = "http://twistedmatrix.com/downloads/"
+
+    dcc_destdir = '.'
+    dcc_sessions = None
+
+    # If this is false, no attempt will be made to identify
+    # ourself to the server.
+    performLogin = 1
+
+    lineRate = None
+    _queue = None
+    _queueEmptying = None
+
+    delimiter = '\n' # '\r\n' will also work (see dataReceived)
+
+    __pychecker__ = 'unusednames=params,prefix,channel'
+
+
+    def _reallySendLine(self, line):
+        return basic.LineReceiver.sendLine(self, lowQuote(line) + '\r')
+
+    def sendLine(self, line):
+        if self.lineRate is None:
+            self._reallySendLine(line)
+        else:
+            self._queue.append(line)
+            if not self._queueEmptying:
+                self._sendLine()
+
+    def _sendLine(self):
+        if self._queue:
+            self._reallySendLine(self._queue.pop(0))
+            self._queueEmptying = reactor.callLater(self.lineRate,
+                                                    self._sendLine)
+        else:
+            self._queueEmptying = None
+
+
+    ### Interface level client->user output methods
+    ###
+    ### You'll want to override these.
+
+    ### Methods relating to the server itself
+
+    def created(self, when):
+        """Called with creation date information about the server, usually at logon.
+
+        @type when: C{str}
+        @param when: A string describing when the server was created, probably.
+        """
+
+    def yourHost(self, info):
+        """Called with daemon information about the server, usually at logon.
+
+        @type info: C{str}
+        @param when: A string describing what software the server is running, probably.
+        """
+
+    def myInfo(self, servername, version, umodes, cmodes):
+        """Called with information about the server, usually at logon.
+
+        @type servername: C{str}
+        @param servername: The hostname of this server.
+
+        @type version: C{str}
+        @param version: A description of what software this server runs.
+
+        @type umodes: C{str}
+        @param umodes: All the available user modes.
+
+        @type cmodes: C{str}
+        @param cmodes: All the available channel modes.
+        """
+
+    def luserClient(self, info):
+        """Called with information about the number of connections, usually at logon.
+
+        @type info: C{str}
+        @param info: A description of the number of clients and servers
+        connected to the network, probably.
+        """
+
+    def bounce(self, info):
+        """Called with information about where the client should reconnect.
+
+        @type info: C{str}
+        @param info: A plaintext description of the address that should be
+        connected to.
+        """
+
+    def isupport(self, options):
+        """Called with various information about what the server supports.
+
+        @type options: C{list} of C{str}
+        @param options: Descriptions of features or limits of the server, possibly
+        in the form "NAME=VALUE".
+        """
+
+    def luserChannels(self, channels):
+        """Called with the number of channels existant on the server.
+
+        @type channels: C{int}
+        """
+
+    def luserOp(self, ops):
+        """Called with the number of ops logged on to the server.
+
+        @type ops: C{int}
+        """
+
+    def luserMe(self, info):
+        """Called with information about the server connected to.
+
+        @type info: C{str}
+        @param info: A plaintext string describing the number of users and servers
+        connected to this server.
+        """
+
+    ### Methods involving me directly
+
+    def privmsg(self, user, channel, message):
+        """Called when I have a message from a user to me or a channel.
+        """
+        pass
+
+    def joined(self, channel):
+        """Called when I finish joining a channel.
+
+        channel has the starting character (# or &) intact.
+        """
+        pass
+
+    def left(self, channel):
+        """Called when I have left a channel.
+
+        channel has the starting character (# or &) intact.
+        """
+        pass
+
+    def noticed(self, user, channel, message):
+        """Called when I have a notice from a user to me or a channel.
+
+        By default, this is equivalent to IRCClient.privmsg, but if your
+        client makes any automated replies, you must override this!
+        From the RFC::
+
+            The difference between NOTICE and PRIVMSG is that
+            automatic replies MUST NEVER be sent in response to a
+            NOTICE message. [...] The object of this rule is to avoid
+            loops between clients automatically sending something in
+            response to something it received.
+        """
+        self.privmsg(user, channel, message)
+
+    def modeChanged(self, user, channel, set, modes, args):
+        """Called when a channel's modes are changed
+
+        @type user: C{str}
+        @param user: The user and hostmask which instigated this change.
+
+        @type channel: C{str}
+        @param channel: The channel for which the modes are changing.
+
+        @type set: C{bool} or C{int}
+        @param set: true if the mode is being added, false if it is being
+        removed.
+
+        @type modes: C{str}
+        @param modes: The mode or modes which are being changed.
+
+        @type args: C{tuple}
+        @param args: Any additional information required for the mode
+        change.
+        """
+
+    def pong(self, user, secs):
+        """Called with the results of a CTCP PING query.
+        """
+        pass
+
+    def signedOn(self):
+        """Called after sucessfully signing on to the server.
+        """
+        pass
+
+    def kickedFrom(self, channel, kicker, message):
+        """Called when I am kicked from a channel.
+        """
+        pass
+
+    def nickChanged(self, nick):
+        """Called when my nick has been changed.
+        """
+        self.nickname = nick
+
+
+    ### Things I observe other people doing in a channel.
+
+    def userJoined(self, user, channel):
+        """Called when I see another user joining a channel.
+        """
+        pass
+
+    def userLeft(self, user, channel):
+        """Called when I see another user leaving a channel.
+        """
+        pass
+
+    def userQuit(self, user, quitMessage):
+        """Called when I see another user disconnect from the network.
+        """
+        pass
+
+    def userKicked(self, kickee, channel, kicker, message):
+        """Called when I observe someone else being kicked from a channel.
+        """
+        pass
+
+    def action(self, user, channel, data):
+        """Called when I see a user perform an ACTION on a channel.
+        """
+        pass
+
+    def topicUpdated(self, user, channel, newTopic):
+        """In channel, user changed the topic to newTopic.
+
+        Also called when first joining a channel.
+        """
+        pass
+
+    def userRenamed(self, oldname, newname):
+        """A user changed their name from oldname to newname.
+        """
+        pass
+
+    ### Information from the server.
+
+    def receivedMOTD(self, motd):
+        """I received a message-of-the-day banner from the server.
+
+        motd is a list of strings, where each string was sent as a seperate
+        message from the server. To display, you might want to use::
+
+            string.join(motd, '\\n')
+
+        to get a nicely formatted string.
+        """
+        pass
+
+    ### user input commands, client->server
+    ### Your client will want to invoke these.
+
+    def join(self, channel, key=None):
+        if channel[0] not in '&#!+': channel = '#' + channel
+        if key:
+            self.sendLine("JOIN %s %s" % (channel, key))
+        else:
+            self.sendLine("JOIN %s" % (channel,))
+
+    def leave(self, channel, reason=None):
+        if channel[0] not in '&#!+': channel = '#' + channel
+        if reason:
+            self.sendLine("PART %s :%s" % (channel, reason))
+        else:
+            self.sendLine("PART %s" % (channel,))
+
+    def kick(self, channel, user, reason=None):
+        if channel[0] not in '&#!+': channel = '#' + channel
+        if reason:
+            self.sendLine("KICK %s %s :%s" % (channel, user, reason))
+        else:
+            self.sendLine("KICK %s %s" % (channel, user))
+
+    part = leave
+
+    def topic(self, channel, topic=None):
+        """Attempt to set the topic of the given channel, or ask what it is.
+
+        If topic is None, then I sent a topic query instead of trying to set
+        the topic. The server should respond with a TOPIC message containing
+        the current topic of the given channel.
+        """
+        # << TOPIC #xtestx :fff
+        if channel[0] not in '&#!+': channel = '#' + channel
+        if topic != None:
+            self.sendLine("TOPIC %s :%s" % (channel, topic))
+        else:
+            self.sendLine("TOPIC %s" % (channel,))
+
+    def mode(self, chan, set, modes, limit = None, user = None, mask = None):
+        """Change the modes on a user or channel."""
+        if set:
+            line = 'MODE %s +%s' % (chan, modes)
+        else:
+            line = 'MODE %s -%s' % (chan, modes)
+        if limit is not None:
+            line = '%s %d' % (line, limit)
+        elif user is not None:
+            line = '%s %s' % (line, user)
+        elif mask is not None:
+            line = '%s %s' % (line, mask)
+        self.sendLine(line)
+
+
+    def say(self, channel, message, length = None):
+        if channel[0] not in '&#!+': channel = '#' + channel
+        self.msg(channel, message, length)
+
+    def msg(self, user, message, length = None):
+        """Send a message to a user or channel.
+
+        @type user: C{str}
+        @param user: The username or channel name to which to direct the
+        message.
+
+        @type message: C{str}
+        @param message: The text to send
+
+        @type length: C{int}
+        @param length: The maximum number of octets to send at a time.  This
+        has the effect of turning a single call to msg() into multiple
+        commands to the server.  This is useful when long messages may be
+        sent that would otherwise cause the server to kick us off or silently
+        truncate the text we are sending.  If None is passed, the entire
+        message is always send in one command.
+        """
+
+        fmt = "PRIVMSG %s :%%s" % (user,)
+
+        if length is None:
+            self.sendLine(fmt % (message,))
+        else:
+            # NOTE: minimumLength really equals len(fmt) - 2 (for '%s') + n
+            # where n is how many bytes sendLine sends to end the line.
+            # n was magic numbered to 2, I think incorrectly
+            minimumLength = len(fmt)
+            if length <= minimumLength:
+                raise ValueError("Maximum length must exceed %d for message "
+                                 "to %s" % (minimumLength, user))
+            lines = split(message, length - minimumLength)
+            map(lambda line, self=self, fmt=fmt: self.sendLine(fmt % line),
+                lines)
+
+    def notice(self, user, message):
+        self.sendLine("NOTICE %s :%s" % (user, message))
+
+    def away(self, message=''):
+        self.sendLine("AWAY :%s" % message)
+
+    def register(self, nickname, hostname='foo', servername='bar'):
+        if self.password is not None:
+            self.sendLine("PASS %s" % self.password)
+        self.setNick(nickname)
+        if self.username is None:
+            self.username = nickname
+        self.sendLine("USER %s %s %s :%s" % (self.username, hostname, servername, self.realname))
+
+    def setNick(self, nickname):
+        self.nickname = nickname
+        self.sendLine("NICK %s" % nickname)
+
+    def quit(self, message = ''):
+        self.sendLine("QUIT :%s" % message)
+
+    ### user input commands, client->client
+
+    def me(self, channel, action):
+        """Strike a pose.
+        """
+        if channel[0] not in '&#!+': channel = '#' + channel
+        self.ctcpMakeQuery(channel, [('ACTION', action)])
+
+    _pings = None
+    _MAX_PINGRING = 12
+
+    def ping(self, user, text = None):
+        """Measure round-trip delay to another IRC client.
+        """
+        if self._pings is None:
+            self._pings = {}
+
+        if text is None:
+            chars = string.letters + string.digits + string.punctuation
+            key = ''.join([random.choice(chars) for i in range(12)])
+        else:
+            key = str(text)
+        self._pings[(user, key)] = time.time()
+        self.ctcpMakeQuery(user, [('PING', key)])
+
+        if len(self._pings) > self._MAX_PINGRING:
+            # Remove some of the oldest entries.
+            byValue = [(v, k) for (k, v) in self._pings.items()]
+            byValue.sort()
+            excess = self._MAX_PINGRING - len(self._pings)
+            for i in xrange(excess):
+                del self._pings[byValue[i][1]]
+
+    def dccSend(self, user, file):
+        if type(file) == types.StringType:
+            file = open(file, 'r')
+
+        size = fileSize(file)
+
+        name = getattr(file, "name", "file@%s" % (id(file),))
+
+        factory = DccSendFactory(file)
+        port = reactor.listenTCP(0, factory, 1)
+
+        raise NotImplementedError,(
+            "XXX!!! Help!  I need to bind a socket, have it listen, and tell me its address.  "
+            "(and stop accepting once we've made a single connection.)")
+
+        my_address = struct.pack("!I", my_address)
+
+        args = ['SEND', name, my_address, str(port)]
+
+        if not (size is None):
+            args.append(size)
+
+        args = string.join(args, ' ')
+
+        self.ctcpMakeQuery(user, [('DCC', args)])
+
+    def dccResume(self, user, fileName, port, resumePos):
+        """Send a DCC RESUME request to another user."""
+        self.ctcpMakeQuery(user, [
+            ('DCC', ['RESUME', fileName, port, resumePos])])
+
+    def dccAcceptResume(self, user, fileName, port, resumePos):
+        """Send a DCC ACCEPT response to clients who have requested a resume.
+        """
+        self.ctcpMakeQuery(user, [
+            ('DCC', ['ACCEPT', fileName, port, resumePos])])
+
+    ### server->client messages
+    ### You might want to fiddle with these,
+    ### but it is safe to leave them alone.
+
+    def irc_ERR_NICKNAMEINUSE(self, prefix, params):
+        self.register(self.nickname+'_')
+
+    def irc_ERR_PASSWDMISMATCH(self, prefix, params):
+        raise IRCPasswordMismatch("Password Incorrect.")
+
+    def irc_RPL_WELCOME(self, prefix, params):
+        self.signedOn()
+
+    def irc_JOIN(self, prefix, params):
+        nick = string.split(prefix,'!')[0]
+        channel = params[-1]
+        if nick == self.nickname:
+            self.joined(channel)
+        else:
+            self.userJoined(nick, channel)
+
+    def irc_PART(self, prefix, params):
+        nick = string.split(prefix,'!')[0]
+        channel = params[0]
+        if nick == self.nickname:
+            self.left(channel)
+        else:
+            self.userLeft(nick, channel)
+
+    def irc_QUIT(self, prefix, params):
+        nick = string.split(prefix,'!')[0]
+        self.userQuit(nick, params[0])
+
+    def irc_MODE(self, prefix, params):
+        channel, rest = params[0], params[1:]
+        set = rest[0][0] == '+'
+        modes = rest[0][1:]
+        args = rest[1:]
+        self.modeChanged(prefix, channel, set, modes, tuple(args))
+
+    def irc_PING(self, prefix, params):
+        self.sendLine("PONG %s" % params[-1])
+
+    def irc_PRIVMSG(self, prefix, params):
+        user = prefix
+        channel = params[0]
+        message = params[-1]
+
+        if not message: return # don't raise an exception if some idiot sends us a blank message
+
+        if message[0]==X_DELIM:
+            m = ctcpExtract(message)
+            if m['extended']:
+                self.ctcpQuery(user, channel, m['extended'])
+
+            if not m['normal']:
+                return
+
+            message = string.join(m['normal'], ' ')
+
+        self.privmsg(user, channel, message)
+
+    def irc_NOTICE(self, prefix, params):
+        user = prefix
+        channel = params[0]
+        message = params[-1]
+
+        if message[0]==X_DELIM:
+            m = ctcpExtract(message)
+            if m['extended']:
+                self.ctcpReply(user, channel, m['extended'])
+
+            if not m['normal']:
+                return
+
+            message = string.join(m['normal'], ' ')
+
+        self.noticed(user, channel, message)
+
+    def irc_NICK(self, prefix, params):
+        nick = string.split(prefix,'!', 1)[0]
+        if nick == self.nickname:
+            self.nickChanged(params[0])
+        else:
+            self.userRenamed(nick, params[0])
+
+    def irc_KICK(self, prefix, params):
+        """Kicked?  Who?  Not me, I hope.
+        """
+        kicker = string.split(prefix,'!')[0]
+        channel = params[0]
+        kicked = params[1]
+        message = params[-1]
+        if string.lower(kicked) == string.lower(self.nickname):
+            # Yikes!
+            self.kickedFrom(channel, kicker, message)
+        else:
+            self.userKicked(kicked, channel, kicker, message)
+
+    def irc_TOPIC(self, prefix, params):
+        """Someone in the channel set the topic.
+        """
+        user = string.split(prefix, '!')[0]
+        channel = params[0]
+        newtopic = params[1]
+        self.topicUpdated(user, channel, newtopic)
+
+    def irc_RPL_TOPIC(self, prefix, params):
+        """I just joined the channel, and the server is telling me the current topic.
+        """
+        user = string.split(prefix, '!')[0]
+        channel = params[1]
+        newtopic = params[2]
+        self.topicUpdated(user, channel, newtopic)
+
+    def irc_RPL_NOTOPIC(self, prefix, params):
+        user = string.split(prefix, '!')[0]
+        channel = params[1]
+        newtopic = ""
+        self.topicUpdated(user, channel, newtopic)
+
+    def irc_RPL_MOTDSTART(self, prefix, params):
+        if params[-1].startswith("- "):
+            params[-1] = params[-1][2:]
+        self.motd = [params[-1]]
+
+    def irc_RPL_MOTD(self, prefix, params):
+        if params[-1].startswith("- "):
+            params[-1] = params[-1][2:]
+        self.motd.append(params[-1])
+
+    def irc_RPL_ENDOFMOTD(self, prefix, params):
+        self.receivedMOTD(self.motd)
+
+    def irc_RPL_CREATED(self, prefix, params):
+        self.created(params[1])
+
+    def irc_RPL_YOURHOST(self, prefix, params):
+        self.yourHost(params[1])
+
+    def irc_RPL_MYINFO(self, prefix, params):
+        info = params[1].split(None, 3)
+        while len(info) < 4:
+            info.append(None)
+        self.myInfo(*info)
+
+    def irc_RPL_BOUNCE(self, prefix, params):
+        # 005 is doubly assigned.  Piece of crap dirty trash protocol.
+        if params[-1] == "are available on this server":
+            self.isupport(params[1:-1])
+        else:
+            self.bounce(params[1])
+
+    def irc_RPL_LUSERCLIENT(self, prefix, params):
+        self.luserClient(params[1])
+
+    def irc_RPL_LUSEROP(self, prefix, params):
+        try:
+            self.luserOp(int(params[1]))
+        except ValueError:
+            pass
+
+    def irc_RPL_LUSERCHANNELS(self, prefix, params):
+        try:
+            self.luserChannels(int(params[1]))
+        except ValueError:
+            pass
+
+    def irc_RPL_LUSERME(self, prefix, params):
+        self.luserMe(params[1])
+
+    def irc_unknown(self, prefix, command, params):
+        pass
+
+    ### Receiving a CTCP query from another party
+    ### It is safe to leave these alone.
+
+    def ctcpQuery(self, user, channel, messages):
+        """Dispatch method for any CTCP queries received.
+        """
+        for m in messages:
+            method = getattr(self, "ctcpQuery_%s" % m[0], None)
+            if method:
+                method(user, channel, m[1])
+            else:
+                self.ctcpUnknownQuery(user, channel, m[0], m[1])
+
+    def ctcpQuery_ACTION(self, user, channel, data):
+        self.action(user, channel, data)
+
+    def ctcpQuery_PING(self, user, channel, data):
+        nick = string.split(user,"!")[0]
+        self.ctcpMakeReply(nick, [("PING", data)])
+
+    def ctcpQuery_FINGER(self, user, channel, data):
+        if data is not None:
+            self.quirkyMessage("Why did %s send '%s' with a FINGER query?"
+                               % (user, data))
+        if not self.fingerReply:
+            return
+
+        if callable(self.fingerReply):
+            reply = self.fingerReply()
+        else:
+            reply = str(self.fingerReply)
+
+        nick = string.split(user,"!")[0]
+        self.ctcpMakeReply(nick, [('FINGER', reply)])
+
+    def ctcpQuery_VERSION(self, user, channel, data):
+        if data is not None:
+            self.quirkyMessage("Why did %s send '%s' with a VERSION query?"
+                               % (user, data))
+
+        if self.versionName:
+            nick = string.split(user,"!")[0]
+            self.ctcpMakeReply(nick, [('VERSION', '%s:%s:%s' %
+                                       (self.versionName,
+                                        self.versionNum,
+                                        self.versionEnv))])
+
+    def ctcpQuery_SOURCE(self, user, channel, data):
+        if data is not None:
+            self.quirkyMessage("Why did %s send '%s' with a SOURCE query?"
+                               % (user, data))
+        if self.sourceURL:
+            nick = string.split(user,"!")[0]
+            # The CTCP document (Zeuge, Rollo, Mesander 1994) says that SOURCE
+            # replies should be responded to with the location of an anonymous
+            # FTP server in host:directory:file format.  I'm taking the liberty
+            # of bringing it into the 21st century by sending a URL instead.
+            self.ctcpMakeReply(nick, [('SOURCE', self.sourceURL),
+                                      ('SOURCE', None)])
+
+    def ctcpQuery_USERINFO(self, user, channel, data):
+        if data is not None:
+            self.quirkyMessage("Why did %s send '%s' with a USERINFO query?"
+                               % (user, data))
+        if self.userinfo:
+            nick = string.split(user,"!")[0]
+            self.ctcpMakeReply(nick, [('USERINFO', self.userinfo)])
+
+    def ctcpQuery_CLIENTINFO(self, user, channel, data):
+        """A master index of what CTCP tags this client knows.
+
+        If no arguments are provided, respond with a list of known tags.
+        If an argument is provided, provide human-readable help on
+        the usage of that tag.
+        """
+
+        nick = string.split(user,"!")[0]
+        if not data:
+            # XXX: prefixedMethodNames gets methods from my *class*,
+            # but it's entirely possible that this *instance* has more
+            # methods.
+            names = reflect.prefixedMethodNames(self.__class__,
+                                                'ctcpQuery_')
+
+            self.ctcpMakeReply(nick, [('CLIENTINFO',
+                                       string.join(names, ' '))])
+        else:
+            args = string.split(data)
+            method = getattr(self, 'ctcpQuery_%s' % (args[0],), None)
+            if not method:
+                self.ctcpMakeReply(nick, [('ERRMSG',
+                                           "CLIENTINFO %s :"
+                                           "Unknown query '%s'"
+                                           % (data, args[0]))])
+                return
+            doc = getattr(method, '__doc__', '')
+            self.ctcpMakeReply(nick, [('CLIENTINFO', doc)])
+
+
+    def ctcpQuery_ERRMSG(self, user, channel, data):
+        # Yeah, this seems strange, but that's what the spec says to do
+        # when faced with an ERRMSG query (not a reply).
+        nick = string.split(user,"!")[0]
+        self.ctcpMakeReply(nick, [('ERRMSG',
+                                   "%s :No error has occoured." % data)])
+
+    def ctcpQuery_TIME(self, user, channel, data):
+        if data is not None:
+            self.quirkyMessage("Why did %s send '%s' with a TIME query?"
+                               % (user, data))
+        nick = string.split(user,"!")[0]
+        self.ctcpMakeReply(nick,
+                           [('TIME', ':%s' %
+                             time.asctime(time.localtime(time.time())))])
+
+    def ctcpQuery_DCC(self, user, channel, data):
+        """Initiate a Direct Client Connection
+        """
+
+        if not data: return
+        dcctype = data.split(None, 1)[0].upper()
+        handler = getattr(self, "dcc_" + dcctype, None)
+        if handler:
+            if self.dcc_sessions is None:
+                self.dcc_sessions = []
+            data = data[len(dcctype)+1:]
+            handler(user, channel, data)
+        else:
+            nick = string.split(user,"!")[0]
+            self.ctcpMakeReply(nick, [('ERRMSG',
+                                       "DCC %s :Unknown DCC type '%s'"
+                                       % (data, dcctype))])
+            self.quirkyMessage("%s offered unknown DCC type %s"
+                               % (user, dcctype))
+
+    def dcc_SEND(self, user, channel, data):
+        # Use splitQuoted for those who send files with spaces in the names.
+        data = text.splitQuoted(data)
+        if len(data) < 3:
+            raise IRCBadMessage, "malformed DCC SEND request: %r" % (data,)
+
+        (filename, address, port) = data[:3]
+
+        address = dccParseAddress(address)
+        try:
+            port = int(port)
+        except ValueError:
+            raise IRCBadMessage, "Indecipherable port %r" % (port,)
+
+        size = -1
+        if len(data) >= 4:
+            try:
+                size = int(data[3])
+            except ValueError:
+                pass
+
+        # XXX Should we bother passing this data?
+        self.dccDoSend(user, address, port, filename, size, data)
+
+    def dcc_ACCEPT(self, user, channel, data):
+        data = text.splitQuoted(data)
+        if len(data) < 3:
+            raise IRCBadMessage, "malformed DCC SEND ACCEPT request: %r" % (data,)
+        (filename, port, resumePos) = data[:3]
+        try:
+            port = int(port)
+            resumePos = int(resumePos)
+        except ValueError:
+            return
+
+        self.dccDoAcceptResume(user, filename, port, resumePos)
+
+    def dcc_RESUME(self, user, channel, data):
+        data = text.splitQuoted(data)
+        if len(data) < 3:
+            raise IRCBadMessage, "malformed DCC SEND RESUME request: %r" % (data,)
+        (filename, port, resumePos) = data[:3]
+        try:
+            port = int(port)
+            resumePos = int(resumePos)
+        except ValueError:
+            return
+        self.dccDoResume(user, filename, port, resumePos)
+
+    def dcc_CHAT(self, user, channel, data):
+        data = text.splitQuoted(data)
+        if len(data) < 3:
+            raise IRCBadMessage, "malformed DCC CHAT request: %r" % (data,)
+
+        (filename, address, port) = data[:3]
+
+        address = dccParseAddress(address)
+        try:
+            port = int(port)
+        except ValueError:
+            raise IRCBadMessage, "Indecipherable port %r" % (port,)
+
+        self.dccDoChat(user, channel, address, port, data)
+
+    ### The dccDo methods are the slightly higher-level siblings of
+    ### common dcc_ methods; the arguments have been parsed for them.
+
+    def dccDoSend(self, user, address, port, fileName, size, data):
+        """Called when I receive a DCC SEND offer from a client.
+
+        By default, I do nothing here."""
+        ## filename = path.basename(arg)
+        ## protocol = DccFileReceive(filename, size,
+        ##                           (user,channel,data),self.dcc_destdir)
+        ## reactor.clientTCP(address, port, protocol)
+        ## self.dcc_sessions.append(protocol)
+        pass
+
+    def dccDoResume(self, user, file, port, resumePos):
+        """Called when a client is trying to resume an offered file
+        via DCC send.  It should be either replied to with a DCC
+        ACCEPT or ignored (default)."""
+        pass
+
+    def dccDoAcceptResume(self, user, file, port, resumePos):
+        """Called when a client has verified and accepted a DCC resume
+        request made by us.  By default it will do nothing."""
+        pass
+
+    def dccDoChat(self, user, channel, address, port, data):
+        pass
+        #factory = DccChatFactory(self, queryData=(user, channel, data))
+        #reactor.connectTCP(address, port, factory)
+        #self.dcc_sessions.append(factory)
+
+    #def ctcpQuery_SED(self, user, data):
+    #    """Simple Encryption Doodoo
+    #
+    #    Feel free to implement this, but no specification is available.
+    #    """
+    #    raise NotImplementedError
+
+    def ctcpUnknownQuery(self, user, channel, tag, data):
+        nick = string.split(user,"!")[0]
+        self.ctcpMakeReply(nick, [('ERRMSG',
+                                   "%s %s: Unknown query '%s'"
+                                   % (tag, data, tag))])
+
+        log.msg("Unknown CTCP query from %s: %s %s\n"
+                 % (user, tag, data))
+
+    def ctcpMakeReply(self, user, messages):
+        """Send one or more X{extended messages} as a CTCP reply.
+
+        @type messages: a list of extended messages.  An extended
+        message is a (tag, data) tuple, where 'data' may be C{None}.
+        """
+        self.notice(user, ctcpStringify(messages))
+
+    ### client CTCP query commands
+
+    def ctcpMakeQuery(self, user, messages):
+        """Send one or more X{extended messages} as a CTCP query.
+
+        @type messages: a list of extended messages.  An extended
+        message is a (tag, data) tuple, where 'data' may be C{None}.
+        """
+        self.msg(user, ctcpStringify(messages))
+
+    ### Receiving a response to a CTCP query (presumably to one we made)
+    ### You may want to add methods here, or override UnknownReply.
+
+    def ctcpReply(self, user, channel, messages):
+        """Dispatch method for any CTCP replies received.
+        """
+        for m in messages:
+            method = getattr(self, "ctcpReply_%s" % m[0], None)
+            if method:
+                method(user, channel, m[1])
+            else:
+                self.ctcpUnknownReply(user, channel, m[0], m[1])
+
+    def ctcpReply_PING(self, user, channel, data):
+        nick = user.split('!', 1)[0]
+        if (not self._pings) or (not self._pings.has_key((nick, data))):
+            raise IRCBadMessage,\
+                  "Bogus PING response from %s: %s" % (user, data)
+
+        t0 = self._pings[(nick, data)]
+        self.pong(user, time.time() - t0)
+
+    def ctcpUnknownReply(self, user, channel, tag, data):
+        """Called when a fitting ctcpReply_ method is not found.
+
+        XXX: If the client makes arbitrary CTCP queries,
+        this method should probably show the responses to
+        them instead of treating them as anomolies.
+        """
+        log.msg("Unknown CTCP reply from %s: %s %s\n"
+                 % (user, tag, data))
+
+    ### Error handlers
+    ### You may override these with something more appropriate to your UI.
+
+    def badMessage(self, line, excType, excValue, tb):
+        """When I get a message that's so broken I can't use it.
+        """
+        log.msg(line)
+        log.msg(string.join(traceback.format_exception(excType,
+                                                        excValue,
+                                                        tb),''))
+
+    def quirkyMessage(self, s):
+        """This is called when I receive a message which is peculiar,
+        but not wholly indecipherable.
+        """
+        log.msg(s + '\n')
+
+    ### Protocool methods
+
+    def connectionMade(self):
+        self._queue = []
+        if self.performLogin:
+            self.register(self.nickname)
+
+    def dataReceived(self, data):
+        basic.LineReceiver.dataReceived(self, data.replace('\r', ''))
+
+    def lineReceived(self, line):
+        line = lowDequote(line)
+        try:
+            prefix, command, params = parsemsg(line)
+            if numeric_to_symbolic.has_key(command):
+                command = numeric_to_symbolic[command]
+            self.handleCommand(command, prefix, params)
+        except IRCBadMessage:
+            self.badMessage(line, *sys.exc_info())
+
+
+    def handleCommand(self, command, prefix, params):
+        """Determine the function to call for the given command and call
+        it with the given arguments.
+        """
+        method = getattr(self, "irc_%s" % command, None)
+        try:
+            if method is not None:
+                method(prefix, params)
+            else:
+                self.irc_unknown(prefix, command, params)
+        except:
+            log.deferr()
+
+
+    def __getstate__(self):
+        dct = self.__dict__.copy()
+        dct['dcc_sessions'] = None
+        dct['_pings'] = None
+        return dct
+
+
+def dccParseAddress(address):
+    if '.' in address:
+        pass
+    else:
+        try:
+            address = long(address)
+        except ValueError:
+            raise IRCBadMessage,\
+                  "Indecipherable address %r" % (address,)
+        else:
+            address = (
+                (address >> 24) & 0xFF,
+                (address >> 16) & 0xFF,
+                (address >> 8) & 0xFF,
+                address & 0xFF,
+                )
+            address = '.'.join(map(str,address))
+    return address
+
+
+class DccFileReceiveBasic(protocol.Protocol, styles.Ephemeral):
+    """Bare protocol to receive a Direct Client Connection SEND stream.
+
+    This does enough to keep the other guy talking, but you'll want to
+    extend my dataReceived method to *do* something with the data I get.
+    """
+
+    bytesReceived = 0
+
+    def __init__(self, resumeOffset=0):
+        self.bytesReceived = resumeOffset
+        self.resume = (resumeOffset != 0)
+
+    def dataReceived(self, data):
+        """Called when data is received.
+
+        Warning: This just acknowledges to the remote host that the
+        data has been received; it doesn't *do* anything with the
+        data, so you'll want to override this.
+        """
+        self.bytesReceived = self.bytesReceived + len(data)
+        self.transport.write(struct.pack('!i', self.bytesReceived))
+
+
+class DccSendProtocol(protocol.Protocol, styles.Ephemeral):
+    """Protocol for an outgoing Direct Client Connection SEND.
+    """
+
+    blocksize = 1024
+    file = None
+    bytesSent = 0
+    completed = 0
+    connected = 0
+
+    def __init__(self, file):
+        if type(file) is types.StringType:
+            self.file = open(file, 'r')
+
+    def connectionMade(self):
+        self.connected = 1
+        self.sendBlock()
+
+    def dataReceived(self, data):
+        # XXX: Do we need to check to see if len(data) != fmtsize?
+
+        bytesShesGot = struct.unpack("!I", data)
+        if bytesShesGot < self.bytesSent:
+            # Wait for her.
+            # XXX? Add some checks to see if we've stalled out?
+            return
+        elif bytesShesGot > self.bytesSent:
+            # self.transport.log("DCC SEND %s: She says she has %d bytes "
+            #                    "but I've only sent %d.  I'm stopping "
+            #                    "this screwy transfer."
+            #                    % (self.file,
+            #                       bytesShesGot, self.bytesSent))
+            self.transport.loseConnection()
+            return
+
+        self.sendBlock()
+
+    def sendBlock(self):
+        block = self.file.read(self.blocksize)
+        if block:
+            self.transport.write(block)
+            self.bytesSent = self.bytesSent + len(block)
+        else:
+            # Nothing more to send, transfer complete.
+            self.transport.loseConnection()
+            self.completed = 1
+
+    def connectionLost(self, reason):
+        self.connected = 0
+        if hasattr(self.file, "close"):
+            self.file.close()
+
+
+class DccSendFactory(protocol.Factory):
+    protocol = DccSendProtocol
+    def __init__(self, file):
+        self.file = file
+
+    def buildProtocol(self, connection):
+        p = self.protocol(self.file)
+        p.factory = self
+        return p
+
+
+def fileSize(file):
+    """I'll try my damndest to determine the size of this file object.
+    """
+    size = None
+    if hasattr(file, "fileno"):
+        fileno = file.fileno()
+        try:
+            stat_ = os.fstat(fileno)
+            size = stat_[stat.ST_SIZE]
+        except:
+            pass
+        else:
+            return size
+
+    if hasattr(file, "name") and path.exists(file.name):
+        try:
+            size = path.getsize(file.name)
+        except:
+            pass
+        else:
+            return size
+
+    if hasattr(file, "seek") and hasattr(file, "tell"):
+        try:
+            try:
+                file.seek(0, 2)
+                size = file.tell()
+            finally:
+                file.seek(0, 0)
+        except:
+            pass
+        else:
+            return size
+
+    return size
+
+class DccChat(basic.LineReceiver, styles.Ephemeral):
+    """Direct Client Connection protocol type CHAT.
+
+    DCC CHAT is really just your run o' the mill basic.LineReceiver
+    protocol.  This class only varies from that slightly, accepting
+    either LF or CR LF for a line delimeter for incoming messages
+    while always using CR LF for outgoing.
+
+    The lineReceived method implemented here uses the DCC connection's
+    'client' attribute (provided upon construction) to deliver incoming
+    lines from the DCC chat via IRCClient's normal privmsg interface.
+    That's something of a spoof, which you may well want to override.
+    """
+
+    queryData = None
+    delimiter = CR + NL
+    client = None
+    remoteParty = None
+    buffer = ""
+
+    def __init__(self, client, queryData=None):
+        """Initialize a new DCC CHAT session.
+
+        queryData is a 3-tuple of
+        (fromUser, targetUserOrChannel, data)
+        as received by the CTCP query.
+
+        (To be honest, fromUser is the only thing that's currently
+        used here. targetUserOrChannel is potentially useful, while
+        the 'data' argument is soley for informational purposes.)
+        """
+        self.client = client
+        if queryData:
+            self.queryData = queryData
+            self.remoteParty = self.queryData[0]
+
+    def dataReceived(self, data):
+        self.buffer = self.buffer + data
+        lines = string.split(self.buffer, LF)
+        # Put the (possibly empty) element after the last LF back in the
+        # buffer
+        self.buffer = lines.pop()
+
+        for line in lines:
+            if line[-1] == CR:
+                line = line[:-1]
+            self.lineReceived(line)
+
+    def lineReceived(self, line):
+        log.msg("DCC CHAT<%s> %s" % (self.remoteParty, line))
+        self.client.privmsg(self.remoteParty,
+                            self.client.nickname, line)
+
+
+class DccChatFactory(protocol.ClientFactory):
+    protocol = DccChat
+    noisy = 0
+    def __init__(self, client, queryData):
+        self.client = client
+        self.queryData = queryData
+
+    def buildProtocol(self, addr):
+        p = self.protocol(client=self.client, queryData=self.queryData)
+        p.factory = self
+
+    def clientConnectionFailed(self, unused_connector, unused_reason):
+        self.client.dcc_sessions.remove(self)
+
+    def clientConnectionLost(self, unused_connector, unused_reason):
+        self.client.dcc_sessions.remove(self)
+
+
+def dccDescribe(data):
+    """Given the data chunk from a DCC query, return a descriptive string.
+    """
+
+    orig_data = data
+    data = string.split(data)
+    if len(data) < 4:
+        return orig_data
+
+    (dcctype, arg, address, port) = data[:4]
+
+    if '.' in address:
+        pass
+    else:
+        try:
+            address = long(address)
+        except ValueError:
+            pass
+        else:
+            address = (
+                (address >> 24) & 0xFF,
+                (address >> 16) & 0xFF,
+                (address >> 8) & 0xFF,
+                address & 0xFF,
+                )
+            # The mapping to 'int' is to get rid of those accursed
+            # "L"s which python 1.5.2 puts on the end of longs.
+            address = string.join(map(str,map(int,address)), ".")
+
+    if dcctype == 'SEND':
+        filename = arg
+
+        size_txt = ''
+        if len(data) >= 5:
+            try:
+                size = int(data[4])
+                size_txt = ' of size %d bytes' % (size,)
+            except ValueError:
+                pass
+
+        dcc_text = ("SEND for file '%s'%s at host %s, port %s"
+                    % (filename, size_txt, address, port))
+    elif dcctype == 'CHAT':
+        dcc_text = ("CHAT for host %s, port %s"
+                    % (address, port))
+    else:
+        dcc_text = orig_data
+
+    return dcc_text
+
+
+class DccFileReceive(DccFileReceiveBasic):
+    """Higher-level coverage for getting a file from DCC SEND.
+
+    I allow you to change the file's name and destination directory.
+    I won't overwrite an existing file unless I've been told it's okay
+    to do so. If passed the resumeOffset keyword argument I will attempt to
+    resume the file from that amount of bytes.
+
+    XXX: I need to let the client know when I am finished.
+    XXX: I need to decide how to keep a progress indicator updated.
+    XXX: Client needs a way to tell me \"Do not finish until I say so.\"
+    XXX: I need to make sure the client understands if the file cannot be written.
+    """
+
+    filename = 'dcc'
+    fileSize = -1
+    destDir = '.'
+    overwrite = 0
+    fromUser = None
+    queryData = None
+
+    def __init__(self, filename, fileSize=-1, queryData=None,
+                 destDir='.', resumeOffset=0):
+        DccFileReceiveBasic.__init__(self, resumeOffset=resumeOffset)
+        self.filename = filename
+        self.destDir = destDir
+        self.fileSize = fileSize
+
+        if queryData:
+            self.queryData = queryData
+            self.fromUser = self.queryData[0]
+
+    def set_directory(self, directory):
+        """Set the directory where the downloaded file will be placed.
+
+        May raise OSError if the supplied directory path is not suitable.
+        """
+        if not path.exists(directory):
+            raise OSError(errno.ENOENT, "You see no directory there.",
+                          directory)
+        if not path.isdir(directory):
+            raise OSError(errno.ENOTDIR, "You cannot put a file into "
+                          "something which is not a directory.",
+                          directory)
+        if not os.access(directory, os.X_OK | os.W_OK):
+            raise OSError(errno.EACCES,
+                          "This directory is too hard to write in to.",
+                          directory)
+        self.destDir = directory
+
+    def set_filename(self, filename):
+        """Change the name of the file being transferred.
+
+        This replaces the file name provided by the sender.
+        """
+        self.filename = filename
+
+    def set_overwrite(self, boolean):
+        """May I overwrite existing files?
+        """
+        self.overwrite = boolean
+
+
+    # Protocol-level methods.
+
+    def connectionMade(self):
+        dst = path.abspath(path.join(self.destDir,self.filename))
+        exists = path.exists(dst)
+        if self.resume and exists:
+            # I have been told I want to resume, and a file already
+            # exists - Here we go
+            self.file = open(dst, 'ab')
+            log.msg("Attempting to resume %s - starting from %d bytes" %
+                    (self.file, self.file.tell()))
+        elif self.overwrite or not exists:
+            self.file = open(dst, 'wb')
+        else:
+            raise OSError(errno.EEXIST,
+                          "There's a file in the way.  "
+                          "Perhaps that's why you cannot open it.",
+                          dst)
+
+    def dataReceived(self, data):
+        self.file.write(data)
+        DccFileReceiveBasic.dataReceived(self, data)
+
+        # XXX: update a progress indicator here?
+
+    def connectionLost(self, reason):
+        """When the connection is lost, I close the file.
+        """
+        self.connected = 0
+        logmsg = ("%s closed." % (self,))
+        if self.fileSize > 0:
+            logmsg = ("%s  %d/%d bytes received"
+                      % (logmsg, self.bytesReceived, self.fileSize))
+            if self.bytesReceived == self.fileSize:
+                pass # Hooray!
+            elif self.bytesReceived < self.fileSize:
+                logmsg = ("%s (Warning: %d bytes short)"
+                          % (logmsg, self.fileSize - self.bytesReceived))
+            else:
+                logmsg = ("%s (file larger than expected)"
+                          % (logmsg,))
+        else:
+            logmsg = ("%s  %d bytes received"
+                      % (logmsg, self.bytesReceived))
+
+        if hasattr(self, 'file'):
+            logmsg = "%s and written to %s.\n" % (logmsg, self.file.name)
+            if hasattr(self.file, 'close'): self.file.close()
+
+        # self.transport.log(logmsg)
+
+    def __str__(self):
+        if not self.connected:
+            return "<Unconnected DccFileReceive object at %x>" % (id(self),)
+        from_ = self.transport.getPeer()
+        if self.fromUser:
+            from_ = "%s (%s)" % (self.fromUser, from_)
+
+        s = ("DCC transfer of '%s' from %s" % (self.filename, from_))
+        return s
+
+    def __repr__(self):
+        s = ("<%s at %x: GET %s>"
+             % (self.__class__, id(self), self.filename))
+        return s
+
+
+# CTCP constants and helper functions
+
+X_DELIM = chr(001)
+
+def ctcpExtract(message):
+    """Extract CTCP data from a string.
+
+    Returns a dictionary with two items:
+
+       - C{'extended'}: a list of CTCP (tag, data) tuples
+       - C{'normal'}: a list of strings which were not inside a CTCP delimeter
+    """
+
+    extended_messages = []
+    normal_messages = []
+    retval = {'extended': extended_messages,
+              'normal': normal_messages }
+
+    messages = string.split(message, X_DELIM)
+    odd = 0
+
+    # X1 extended data X2 nomal data X3 extended data X4 normal...
+    while messages:
+        if odd:
+            extended_messages.append(messages.pop(0))
+        else:
+            normal_messages.append(messages.pop(0))
+        odd = not odd
+
+    extended_messages[:] = filter(None, extended_messages)
+    normal_messages[:] = filter(None, normal_messages)
+
+    extended_messages[:] = map(ctcpDequote, extended_messages)
+    for i in xrange(len(extended_messages)):
+        m = string.split(extended_messages[i], SPC, 1)
+        tag = m[0]
+        if len(m) > 1:
+            data = m[1]
+        else:
+            data = None
+
+        extended_messages[i] = (tag, data)
+
+    return retval
+
+# CTCP escaping
+
+M_QUOTE= chr(020)
+
+mQuoteTable = {
+    NUL: M_QUOTE + '0',
+    NL: M_QUOTE + 'n',
+    CR: M_QUOTE + 'r',
+    M_QUOTE: M_QUOTE + M_QUOTE
+    }
+
+mDequoteTable = {}
+for k, v in mQuoteTable.items():
+    mDequoteTable[v[-1]] = k
+del k, v
+
+mEscape_re = re.compile('%s.' % (re.escape(M_QUOTE),), re.DOTALL)
+
+def lowQuote(s):
+    for c in (M_QUOTE, NUL, NL, CR):
+        s = string.replace(s, c, mQuoteTable[c])
+    return s
+
+def lowDequote(s):
+    def sub(matchobj, mDequoteTable=mDequoteTable):
+        s = matchobj.group()[1]
+        try:
+            s = mDequoteTable[s]
+        except KeyError:
+            s = s
+        return s
+
+    return mEscape_re.sub(sub, s)
+
+X_QUOTE = '\\'
+
+xQuoteTable = {
+    X_DELIM: X_QUOTE + 'a',
+    X_QUOTE: X_QUOTE + X_QUOTE
+    }
+
+xDequoteTable = {}
+
+for k, v in xQuoteTable.items():
+    xDequoteTable[v[-1]] = k
+
+xEscape_re = re.compile('%s.' % (re.escape(X_QUOTE),), re.DOTALL)
+
+def ctcpQuote(s):
+    for c in (X_QUOTE, X_DELIM):
+        s = string.replace(s, c, xQuoteTable[c])
+    return s
+
+def ctcpDequote(s):
+    def sub(matchobj, xDequoteTable=xDequoteTable):
+        s = matchobj.group()[1]
+        try:
+            s = xDequoteTable[s]
+        except KeyError:
+            s = s
+        return s
+
+    return xEscape_re.sub(sub, s)
+
+def ctcpStringify(messages):
+    """
+    @type messages: a list of extended messages.  An extended
+    message is a (tag, data) tuple, where 'data' may be C{None}, a
+    string, or a list of strings to be joined with whitespace.
+
+    @returns: String
+    """
+    coded_messages = []
+    for (tag, data) in messages:
+        if data:
+            if not isinstance(data, types.StringType):
+                try:
+                    # data as list-of-strings
+                    data = " ".join(map(str, data))
+                except TypeError:
+                    # No?  Then use it's %s representation.
+                    pass
+            m = "%s %s" % (tag, data)
+        else:
+            m = str(tag)
+        m = ctcpQuote(m)
+        m = "%s%s%s" % (X_DELIM, m, X_DELIM)
+        coded_messages.append(m)
+
+    line = string.join(coded_messages, '')
+    return line
+
+
+# Constants (from RFC 2812)
+RPL_WELCOME = '001'
+RPL_YOURHOST = '002'
+RPL_CREATED = '003'
+RPL_MYINFO = '004'
+RPL_BOUNCE = '005'
+RPL_USERHOST = '302'
+RPL_ISON = '303'
+RPL_AWAY = '301'
+RPL_UNAWAY = '305'
+RPL_NOWAWAY = '306'
+RPL_WHOISUSER = '311'
+RPL_WHOISSERVER = '312'
+RPL_WHOISOPERATOR = '313'
+RPL_WHOISIDLE = '317'
+RPL_ENDOFWHOIS = '318'
+RPL_WHOISCHANNELS = '319'
+RPL_WHOWASUSER = '314'
+RPL_ENDOFWHOWAS = '369'
+RPL_LISTSTART = '321'
+RPL_LIST = '322'
+RPL_LISTEND = '323'
+RPL_UNIQOPIS = '325'
+RPL_CHANNELMODEIS = '324'
+RPL_NOTOPIC = '331'
+RPL_TOPIC = '332'
+RPL_INVITING = '341'
+RPL_SUMMONING = '342'
+RPL_INVITELIST = '346'
+RPL_ENDOFINVITELIST = '347'
+RPL_EXCEPTLIST = '348'
+RPL_ENDOFEXCEPTLIST = '349'
+RPL_VERSION = '351'
+RPL_WHOREPLY = '352'
+RPL_ENDOFWHO = '315'
+RPL_NAMREPLY = '353'
+RPL_ENDOFNAMES = '366'
+RPL_LINKS = '364'
+RPL_ENDOFLINKS = '365'
+RPL_BANLIST = '367'
+RPL_ENDOFBANLIST = '368'
+RPL_INFO = '371'
+RPL_ENDOFINFO = '374'
+RPL_MOTDSTART = '375'
+RPL_MOTD = '372'
+RPL_ENDOFMOTD = '376'
+RPL_YOUREOPER = '381'
+RPL_REHASHING = '382'
+RPL_YOURESERVICE = '383'
+RPL_TIME = '391'
+RPL_USERSSTART = '392'
+RPL_USERS = '393'
+RPL_ENDOFUSERS = '394'
+RPL_NOUSERS = '395'
+RPL_TRACELINK = '200'
+RPL_TRACECONNECTING = '201'
+RPL_TRACEHANDSHAKE = '202'
+RPL_TRACEUNKNOWN = '203'
+RPL_TRACEOPERATOR = '204'
+RPL_TRACEUSER = '205'
+RPL_TRACESERVER = '206'
+RPL_TRACESERVICE = '207'
+RPL_TRACENEWTYPE = '208'
+RPL_TRACECLASS = '209'
+RPL_TRACERECONNECT = '210'
+RPL_TRACELOG = '261'
+RPL_TRACEEND = '262'
+RPL_STATSLINKINFO = '211'
+RPL_STATSCOMMANDS = '212'
+RPL_ENDOFSTATS = '219'
+RPL_STATSUPTIME = '242'
+RPL_STATSOLINE = '243'
+RPL_UMODEIS = '221'
+RPL_SERVLIST = '234'
+RPL_SERVLISTEND = '235'
+RPL_LUSERCLIENT = '251'
+RPL_LUSEROP = '252'
+RPL_LUSERUNKNOWN = '253'
+RPL_LUSERCHANNELS = '254'
+RPL_LUSERME = '255'
+RPL_ADMINME = '256'
+RPL_ADMINLOC = '257'
+RPL_ADMINLOC = '258'
+RPL_ADMINEMAIL = '259'
+RPL_TRYAGAIN = '263'
+ERR_NOSUCHNICK = '401'
+ERR_NOSUCHSERVER = '402'
+ERR_NOSUCHCHANNEL = '403'
+ERR_CANNOTSENDTOCHAN = '404'
+ERR_TOOMANYCHANNELS = '405'
+ERR_WASNOSUCHNICK = '406'
+ERR_TOOMANYTARGETS = '407'
+ERR_NOSUCHSERVICE = '408'
+ERR_NOORIGIN = '409'
+ERR_NORECIPIENT = '411'
+ERR_NOTEXTTOSEND = '412'
+ERR_NOTOPLEVEL = '413'
+ERR_WILDTOPLEVEL = '414'
+ERR_BADMASK = '415'
+ERR_UNKNOWNCOMMAND = '421'
+ERR_NOMOTD = '422'
+ERR_NOADMININFO = '423'
+ERR_FILEERROR = '424'
+ERR_NONICKNAMEGIVEN = '431'
+ERR_ERRONEUSNICKNAME = '432'
+ERR_NICKNAMEINUSE = '433'
+ERR_NICKCOLLISION = '436'
+ERR_UNAVAILRESOURCE = '437'
+ERR_USERNOTINCHANNEL = '441'
+ERR_NOTONCHANNEL = '442'
+ERR_USERONCHANNEL = '443'
+ERR_NOLOGIN = '444'
+ERR_SUMMONDISABLED = '445'
+ERR_USERSDISABLED = '446'
+ERR_NOTREGISTERED = '451'
+ERR_NEEDMOREPARAMS = '461'
+ERR_ALREADYREGISTRED = '462'
+ERR_NOPERMFORHOST = '463'
+ERR_PASSWDMISMATCH = '464'
+ERR_YOUREBANNEDCREEP = '465'
+ERR_YOUWILLBEBANNED = '466'
+ERR_KEYSET = '467'
+ERR_CHANNELISFULL = '471'
+ERR_UNKNOWNMODE = '472'
+ERR_INVITEONLYCHAN = '473'
+ERR_BANNEDFROMCHAN = '474'
+ERR_BADCHANNELKEY = '475'
+ERR_BADCHANMASK = '476'
+ERR_NOCHANMODES = '477'
+ERR_BANLISTFULL = '478'
+ERR_NOPRIVILEGES = '481'
+ERR_CHANOPRIVSNEEDED = '482'
+ERR_CANTKILLSERVER = '483'
+ERR_RESTRICTED = '484'
+ERR_UNIQOPPRIVSNEEDED = '485'
+ERR_NOOPERHOST = '491'
+ERR_NOSERVICEHOST = '492'
+ERR_UMODEUNKNOWNFLAG = '501'
+ERR_USERSDONTMATCH = '502'
+
+# And hey, as long as the strings are already intern'd...
+symbolic_to_numeric = {
+    "RPL_WELCOME": '001',
+    "RPL_YOURHOST": '002',
+    "RPL_CREATED": '003',
+    "RPL_MYINFO": '004',
+    "RPL_BOUNCE": '005',
+    "RPL_USERHOST": '302',
+    "RPL_ISON": '303',
+    "RPL_AWAY": '301',
+    "RPL_UNAWAY": '305',
+    "RPL_NOWAWAY": '306',
+    "RPL_WHOISUSER": '311',
+    "RPL_WHOISSERVER": '312',
+    "RPL_WHOISOPERATOR": '313',
+    "RPL_WHOISIDLE": '317',
+    "RPL_ENDOFWHOIS": '318',
+    "RPL_WHOISCHANNELS": '319',
+    "RPL_WHOWASUSER": '314',
+    "RPL_ENDOFWHOWAS": '369',
+    "RPL_LISTSTART": '321',
+    "RPL_LIST": '322',
+    "RPL_LISTEND": '323',
+    "RPL_UNIQOPIS": '325',
+    "RPL_CHANNELMODEIS": '324',
+    "RPL_NOTOPIC": '331',
+    "RPL_TOPIC": '332',
+    "RPL_INVITING": '341',
+    "RPL_SUMMONING": '342',
+    "RPL_INVITELIST": '346',
+    "RPL_ENDOFINVITELIST": '347',
+    "RPL_EXCEPTLIST": '348',
+    "RPL_ENDOFEXCEPTLIST": '349',
+    "RPL_VERSION": '351',
+    "RPL_WHOREPLY": '352',
+    "RPL_ENDOFWHO": '315',
+    "RPL_NAMREPLY": '353',
+    "RPL_ENDOFNAMES": '366',
+    "RPL_LINKS": '364',
+    "RPL_ENDOFLINKS": '365',
+    "RPL_BANLIST": '367',
+    "RPL_ENDOFBANLIST": '368',
+    "RPL_INFO": '371',
+    "RPL_ENDOFINFO": '374',
+    "RPL_MOTDSTART": '375',
+    "RPL_MOTD": '372',
+    "RPL_ENDOFMOTD": '376',
+    "RPL_YOUREOPER": '381',
+    "RPL_REHASHING": '382',
+    "RPL_YOURESERVICE": '383',
+    "RPL_TIME": '391',
+    "RPL_USERSSTART": '392',
+    "RPL_USERS": '393',
+    "RPL_ENDOFUSERS": '394',
+    "RPL_NOUSERS": '395',
+    "RPL_TRACELINK": '200',
+    "RPL_TRACECONNECTING": '201',
+    "RPL_TRACEHANDSHAKE": '202',
+    "RPL_TRACEUNKNOWN": '203',
+    "RPL_TRACEOPERATOR": '204',
+    "RPL_TRACEUSER": '205',
+    "RPL_TRACESERVER": '206',
+    "RPL_TRACESERVICE": '207',
+    "RPL_TRACENEWTYPE": '208',
+    "RPL_TRACECLASS": '209',
+    "RPL_TRACERECONNECT": '210',
+    "RPL_TRACELOG": '261',
+    "RPL_TRACEEND": '262',
+    "RPL_STATSLINKINFO": '211',
+    "RPL_STATSCOMMANDS": '212',
+    "RPL_ENDOFSTATS": '219',
+    "RPL_STATSUPTIME": '242',
+    "RPL_STATSOLINE": '243',
+    "RPL_UMODEIS": '221',
+    "RPL_SERVLIST": '234',
+    "RPL_SERVLISTEND": '235',
+    "RPL_LUSERCLIENT": '251',
+    "RPL_LUSEROP": '252',
+    "RPL_LUSERUNKNOWN": '253',
+    "RPL_LUSERCHANNELS": '254',
+    "RPL_LUSERME": '255',
+    "RPL_ADMINME": '256',
+    "RPL_ADMINLOC": '257',
+    "RPL_ADMINLOC": '258',
+    "RPL_ADMINEMAIL": '259',
+    "RPL_TRYAGAIN": '263',
+    "ERR_NOSUCHNICK": '401',
+    "ERR_NOSUCHSERVER": '402',
+    "ERR_NOSUCHCHANNEL": '403',
+    "ERR_CANNOTSENDTOCHAN": '404',
+    "ERR_TOOMANYCHANNELS": '405',
+    "ERR_WASNOSUCHNICK": '406',
+    "ERR_TOOMANYTARGETS": '407',
+    "ERR_NOSUCHSERVICE": '408',
+    "ERR_NOORIGIN": '409',
+    "ERR_NORECIPIENT": '411',
+    "ERR_NOTEXTTOSEND": '412',
+    "ERR_NOTOPLEVEL": '413',
+    "ERR_WILDTOPLEVEL": '414',
+    "ERR_BADMASK": '415',
+    "ERR_UNKNOWNCOMMAND": '421',
+    "ERR_NOMOTD": '422',
+    "ERR_NOADMININFO": '423',
+    "ERR_FILEERROR": '424',
+    "ERR_NONICKNAMEGIVEN": '431',
+    "ERR_ERRONEUSNICKNAME": '432',
+    "ERR_NICKNAMEINUSE": '433',
+    "ERR_NICKCOLLISION": '436',
+    "ERR_UNAVAILRESOURCE": '437',
+    "ERR_USERNOTINCHANNEL": '441',
+    "ERR_NOTONCHANNEL": '442',
+    "ERR_USERONCHANNEL": '443',
+    "ERR_NOLOGIN": '444',
+    "ERR_SUMMONDISABLED": '445',
+    "ERR_USERSDISABLED": '446',
+    "ERR_NOTREGISTERED": '451',
+    "ERR_NEEDMOREPARAMS": '461',
+    "ERR_ALREADYREGISTRED": '462',
+    "ERR_NOPERMFORHOST": '463',
+    "ERR_PASSWDMISMATCH": '464',
+    "ERR_YOUREBANNEDCREEP": '465',
+    "ERR_YOUWILLBEBANNED": '466',
+    "ERR_KEYSET": '467',
+    "ERR_CHANNELISFULL": '471',
+    "ERR_UNKNOWNMODE": '472',
+    "ERR_INVITEONLYCHAN": '473',
+    "ERR_BANNEDFROMCHAN": '474',
+    "ERR_BADCHANNELKEY": '475',
+    "ERR_BADCHANMASK": '476',
+    "ERR_NOCHANMODES": '477',
+    "ERR_BANLISTFULL": '478',
+    "ERR_NOPRIVILEGES": '481',
+    "ERR_CHANOPRIVSNEEDED": '482',
+    "ERR_CANTKILLSERVER": '483',
+    "ERR_RESTRICTED": '484',
+    "ERR_UNIQOPPRIVSNEEDED": '485',
+    "ERR_NOOPERHOST": '491',
+    "ERR_NOSERVICEHOST": '492',
+    "ERR_UMODEUNKNOWNFLAG": '501',
+    "ERR_USERSDONTMATCH": '502',
+}
+
+numeric_to_symbolic = {}
+for k, v in symbolic_to_numeric.items():
+    numeric_to_symbolic[v] = k