#!/usr/bin/env python """ Telepaatti, IRC to Jabber/XMPP gateway. Copyright (C) 2007-2009 Petteri Klemola Telepaatti is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 3 as published by the Free Software Foundation. Telepaatti is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. For more information about Telepaatti see http://23.fi/telepaatti """ import socket import time, datetime import exceptions from threading import * from xmpp import * import getopt, sys STATUSSTATES = ['AVAILABLE','CHAT', 'AWAY', 'XA', 'DND', 'INVISIBLE'] TELEPAATTIVERSION = 1 class JabberThread(Thread): """Class for Jabber connection thread""" def __init__(self, client): """Constructor for JabberThread Class @type client: Client @param client: instace of xmpp.Client class """ Thread.__init__(self) self.client = client self.connected = True def process(self): """Starts the xmpp client process""" try: self.client.Process(1) except: return False return True def run(self): """When xmpp client is connected runs the client process """ time.sleep(5) while self.client.Process(1) and self.connected: pass self.client.disconnect() self.connected = False class ClientThread(Thread): """ ClientThread class for handling IRC and Jabber connections.""" def __init__(self,socket, port, JabberID, passwd, debug=False, nickname=None): """Constructor for ClientThread class @type socket: socket @type port: integer @type JabberID: JID @type passwd: string @param socket: socket on which the connection is made @param port: port of the connection @param JabberID: the Jabber ID of the connetor @param passwd: the passwd of the Jabber ID """ Thread.__init__(self) self.socket = socket self.port = port self.passwd = passwd self.JID = JID("%s/%s" %(JabberID,'telepaatti')) client=Client(self.JID.getDomain(),debug=[]) client.connect(proxy={}) client.RegisterHandler('message',self.messageHandler) client.RegisterHandler('presence',self.presenceHandler) client.RegisterHandler('iq' ,self.iqHandler) client.auth(self.JID.getNode(), self.passwd, self.JID.getResource()) client.sendInitPresence() self.client = client if nickname is None: self.nickname = JabberID[:JabberID.find('@')] else: self.nickname = nickname self.newnick = '' self.notFirstNick = False self.mucs = {} self.mucs['roster'] = {} self.rosterMessages = list() self.joinedRoster = False self.connected = True self.nickmapper = {} self.nickChangeInMucs = {} self.joinQueue = {} self.roomPingQueue = {} self.disconnectedMucs = {} self.pingCounter = 0 self.xmppSem = BoundedSemaphore(value=1) # if set to True debug messages are printed to sdt out self.debug = debug def printError(self, msg): """Error message printing for std out @type msg: string @param msg: error message """ dt = "%s" % datetime.datetime.now() dt = dt[:-7] print "ERROR: [%s] %s" % (dt, msg) def printDebug(self, msg): """print Debug message to std out @type msg: string @param msg: debug message """ if not self.debug: return dt = "%s" % datetime.datetime.now() dt = dt[:-7] print "DEBUG [%s] %s" % (dt, msg) def getMucs(self): """Return joined MUC without roster MUC @rtype: list @return: list of mucs joined (without roster) """ mucs = self.mucs.keys() mucs.remove('roster') return mucs def fixNick(self, nick): """Fixes strange character nicknames that don't work nicely with IRC. This function may cause conflicts and thus unfinished. @type nick: string @param nick: nickname to fix @rtype: string @return: fixed nick """ nick = unicode(nick) fixednick = nick.replace(' ', '_') fixednick = fixednick.replace('!', '_') fixednick = fixednick.replace(':', '_') self.nickmapper[fixednick] = nick return fixednick def unfixNick(self, fixednick): """Reverses nicks fixed with fixNick function. Is also unfinished @type fixednick: string @param fixednick: nickname to unfix @rtype: string @return: unfixed nick """ nick = fixednick try: nick = self.nickmapper[fixednick] except: self.printError('unfixNick did not work') return nick def makeIRCACTION(self, msg): """Makes IRC action message @type msg: string @param msg: message from which to make IRC action @rtype: string @return: IRC action message """ msg = '\001ACTION %s\001' % msg return msg def sendToIRC(self, msg): """Sends message IRC client @type msg: string @param msg: message to send """ msg = msg.encode('utf-8') msg = "%s\r\n" % msg self.printDebug(msg) try: self.socket.send(msg) except: self.connected = False self.printError('FATAL ERROR WHILE TRYING TO WRITE IRC MESSAGE TO SOCKET, DISCONNECTING') def sendToXMPP(self, msg): """Sends message XMPP server @type msg: string @param msg: message to send """ self.xmppSem.acquire() self.client.send(msg) self.xmppSem.release() def ircGetStatus(self, nick, channel): """Get IRC status @type nick: string @type channel: string @param nick: nick whos status to get @param channel: the channel on which status to get @rtype: string @return: IRC-style status string """ sta = 'H' show = '' role = '' if self.mucs.has_key(channel): show = self.mucs[channel][nick]['show'] role = self.mucs[channel][nick]['role'] if show in ['away','xa', 'dnd']: sta = 'G' if role == 'moderator': sta = '%s@' % sta elif role == 'participant': sta = '%s+' % sta return sta def ircCommandJOIN(self, nick, channel): """IRC command join channel @type nick: string @type channel: string @param nick: nick who is joining @param channel: channel whats been joined """ onick = nick resource = self.fixNick(nick.getResource()) nick = self.fixNick(nick) msg = ':%s!%s JOIN :#%s' % ( resource, nick, channel) self.sendToIRC(msg) role = self.mucs[channel][onick]['role'] args = '' if role == 'moderator': args = '+o' elif role == 'participant': args = '+v' if args: self.ircCommandMODEMUCUSER(JID('%s/telepaatti' % channel), onick, channel, args) def ircCommandSELFJOIN(self, nick, room): """IRC command join channel @type nick: string @type room: string @param nick: nick who is joining @param room: channel whats been joined """ snick = self.fixNick(self.nickname) lines = list() lines.append(':%s JOIN :#%s'% (snick, room)) lines.append(':localhost MODE #%s' % room) for nick in self.mucs[room].iterkeys(): onick = nick nick = nick.getResource() nick = self.fixNick(nick) oonick = nick if self.mucs[room][onick]['role'] == 'moderator': oonick = "@%s" % oonick elif self.mucs[room][onick]['role'] == 'participant': oonick = "+%s" % oonick lines.append(':localhost 353 %s = #%s :%s' % (snick, room, oonick)) lines.append(':localhost 366 %s #%s :End of /NAMES list.'% (snick, room)) while lines: msg = lines.pop(0) self.sendToIRC(msg) def ircCommandPART(self, nick, channel, text): """IRC command part channel @type nick: string @type channel: string @type text: string @param nick: nick who is parting @param channel: channel whats been parted @param text: the part message """ resource = self.fixNick(nick.getResource()) nick = self.fixNick(nick) msg = ':%s!%s PART #%s :%s' % ( resource, nick, channel, text) self.sendToIRC(msg) def ircCommandROSTERPART(self): """Part roster channel""" self.joinedRoster = False msg = ':%s!%s PART #roster : ' % (self.nickname, unicode(self.JID)) self.sendToIRC(msg) def ircCommandROSTERPRIVMSGMUC(self, msg): """Handles messages in roster channel. For example if user types HELP in roster channel (s)he gets list of available commands @type msg: string @param msg: message to roster channel """ if msg.upper().startswith('HELP'): msg = ':%s PRIVMSG #%s :%s' % \ ('Telepaatti', 'roster', 'Awailable commands !subscribe, !unsubscribe, !subscribed, !unsubscribed') self.sendToIRC(msg) elif msg.startswith('!'): msglist = msg[1:].split() msglist[0] = msglist[0].upper() if len(msglist) == 2: if msglist[0] == 'SUBSCRIBE': self.xmppCommandSUBSCRIBE(msglist[1]) elif msglist[0] == 'UNSUBSCRIBE': self.xmppCommandUNSUBSCRIBE(msglist[1]) elif msglist[0] == 'SUBSCRIBED': self.xmppCommandSUBSCRIBED(msglist[1]) elif msglist[0] == 'UNSUBSCRIBED': self.xmppCommandUNSUBSCRIBED(msglist[1]) return def ircCommandROSTERSELFJOIN(self): """Join roster channel. This handles cases when user self joins to roster channel""" snick = self.fixNick(self.nickname) lines = list() room = 'roster' lines.append(':%s JOIN :#%s'% (snick, room)) lines.append(':localhost 332 %s #%s :Roster Channel. Telepaatti is tracking here your roster changes. Type Help for help!' % (snick, room)) lines.append(':localhost 333 %s #%s telepaatti 000000001' % (snick, room)) lines.append(':localhost MODE #%s' % room) lines.append(':localhost 353 %s = #%s :%s' % (snick, room, snick)) for nick in self.mucs[room].iterkeys(): nick = self.fixNick(nick) if not (snick == nick): lines.append(':localhost 353 %s = #%s :%s' % (snick, room, nick)) lines.append(':localhost 366 %s #%s :End of /NAMES list.'% (snick, room)) while lines: msg = lines.pop(0) self.sendToIRC(msg) self.joinedRoster = True for msg in self.rosterMessages: self.sendToIRC(msg) def ircCommandROSTERMSG(self, pres): """Roster channel messages. Here is traced roster users statas @type pres: Presence @param pres: Presence of the roster user """ nick = pres.getFrom() jid = pres.getFrom() message = '' dt = "%s" % datetime.datetime.now() dt = dt[:-7] if jid != str(self.JID) and pres.getType() == 'unavailable': # left message = ':%s!%s PART #roster :[%s] Offline' % (nick,jid,dt) try: # remove del (self.mucs['roster'][jid]) except: self.printError('GOT UNEXPECTED ERROR') elif jid != str(self.JID) and not self.mucs['roster'].has_key(jid): message = ':%s!%s JOIN :#roster' % (nick, jid) self.mucs['roster'][jid] = { 'role': 'participant', 'affiliation': 'member'} else: action = self.makeIRCACTION('[%s] %s - %s' % ( dt, pres.getShow(), pres.getStatus())) message = ':%s!%s PRIVMSG #roster :%s' % ( nick, jid, action) self.rosterMessages.append(message) if self.joinedRoster: self.sendToIRC(message) def ircCommandPRIVMSG(self, nick, text, timestamp=''): """Converts private messages to IRC client @type nick: string @type text: string @type timestamp: string @param nick: the nickname whois messaging @param text: the message @param timestamp: timestamp of the message """ nick = self.fixNick(nick) nick = unicode(nick) lines = text.splitlines() messages = list() for line in lines: action = False if line.upper().startswith('/ME '): line = line[4:] action = True if timestamp: line = "[%s] %s " % (timestamp, line) if action: line = self.makeIRCACTION(line) msg = ':%s!%s PRIVMSG %s :%s' % (nick, nick, self.fixNick(self.nickname),line) messages.append(msg) for msg in messages: self.sendToIRC(msg) def ircCommandPRIVMSGMUC(self, orgnick, channel, text, timestamp=''): """Converts MUC messages to IRC channel messages @type orgnick: string @type channel: string @type text: string @type timestamp: string @param orgnick: the nickname whois messaging @param channel: the channel @param text: the message @param timestamp: timestamp of the message """ lines = text.splitlines() messages = list() for line in lines: action = False if line.upper().startswith('/ME '): line = line[4:] action = True if timestamp: line = "[%s] %s " % (timestamp, line) if action: line = self.makeIRCACTION(line) nick = orgnick.getResource() nick = self.fixNick(nick) msg = ':%s PRIVMSG #%s :%s' % (nick, channel, line) messages.append(msg) for msg in messages: self.sendToIRC(msg) def ircCommandTOPIC(self, nick, channel, text): """Converts MUC topic to IRC channel topic @type nick: string @type channel: string @type text: string @param channel: name of the channel @param nick: the nickname whois messaging @param text: the message """ snick = self.fixNick(nick.getResource()) nick = self.fixNick(nick) msg =':%s!%s TOPIC #%s :%s' % (snick, nick, channel, text) self.sendToIRC(msg) def ircCommandMODEMUC(self, channel, args): """Converts MUC mode to IRC channel mode @type channel: string @type args: string @param channel: anme of the channel @param args: arguments of the mode """ nick = self.fixNick(self.nickname) msg = ':localhost 324 %s #%s %s' % (nick, channel, args) self.sendToIRC(msg) msg = ':localhost 329 %s #%s %s' % (nick, channel, '1031538353') self.sendToIRC(msg) def ircCommandMODEMUCBANLIST(self, channel): """Converts MUC ban list to IRC channel banlist, is unfinished @type channel: string @param channel: name of the channel """ nick = self.fixNick(self.nickname) msg = ':localhost 368 %s #%s :End of Channel Ban List' % (nick, channel) self.sendToIRC(msg) def ircCommandMODEMUCUSER(self, giver, taker, muc, args): """Convers MUC user mode to IRC channel user mode. Example use cases are when someone is granted a voice or admin rights on a MUC. @type giver: string @type taker: string @type muc: string @type args: string @param giver: giver of the new mode @param taker: taker of the new mode @param muc: MUC on which the mode takes place @param args: arguments of the mode """ givernick = self.fixNick(giver.getResource()) giver = self.fixNick(giver) taker = self.fixNick(taker.getResource()) msg = ':%s!%s MODE #%s %s %s' % (givernick, giver, muc, args, taker) self.sendToIRC(msg) def ircCommandMODE(self, args): """Converts XMPP mode to IRC mode. Unfinished @type args: string @param args: arguments of the mode """ # just to keep irssi happy, fix later to more meaningfull nick = self.fixNick(self.nickname) msg = ':%s MODE %s :%s' % (nick, nick, args) self.sendToIRC(msg) def ircCommandERROR(self, message='', ircerror=0): """Convert XMPP errors to IRC errors @type message: string @type ircerror: integer @param message: the error message @param ircerror: number of the error """ msg = '' if ircerror == 0: msg = 'ERROR :XMPP ERROR %s' % message elif ircerror == -1: msg = 'ERROR :Telepaatti error %s' % message elif ircerror == 403: msg = ':localhost 403 %s localhost :That channel doesn\'t exist' % self.fixNick(self.nickname) self.sendToIRC(msg) def ircCommandERRORMUC(self, number, errormess, room): """Convert XMPP MUC errors to IRC channel errors @type number: integer @type errormess: string @type room: string @param number: number of the error @param errormess: the error message @param room: the MUC """ text = '' if number == 403: text = 'No such channel' elif number == 404: text = 'Cannot send to channel' elif number == 467: text = 'Channel key already set' elif number == 471: text = 'Cannot join channel (+l)' elif number == 473: text = 'Cannot join channel (+i)' elif number == 474: text = 'Cannot join channel (+b)' elif number == 475: text = 'Cannot join channel (+k)' elif number == 476: text = 'Bad Channel Mask' elif number == 477: text = 'Channel doesn\'t support modes' elif number == 478: text = 'Channel list is full' elif number == 481: text = 'Permission Denied- You\'re not an IRC operator' elif number == 482: text = 'You\'re not channel operator' else: text = 'No text' msg = ':localhost %s %s #%s :%s' % ( number, self.fixNick(self.nickname), room, text) self.sendToIRC(msg) self.ircCommandERROR(errormess) def ircCommandWHO(self, users, channel): """Convert XMPP vcard query to IRC who @type users: list @type channel: string @param users: list of users @param channel: the channel (MUC) """ for user in users: oruser = user user = unicode(user) at = user.find('@') slash = user.find('/') domain = channel[at+1:] if at > 0 and slash > at: onick = user[slash+1:] user = self.fixNick(user) nick = self.fixNick(onick) msg = ':localhost 352 %s #%s %s %s localhost %s %s :0 %s' % ( self.fixNick(self.nickname), channel, self.fixNick(user[:at]), self.fixNick(user[at+1:]), nick, self.ircGetStatus(oruser, channel), onick) self.sendToIRC(msg) msg = ':localhost 315 %s #%s :End of /WHO list.' % (self.fixNick(self.nickname), channel) self.sendToIRC(msg) def ircCommandWHOIS(self, jid): """Convert XMPP vcard query to IRC whois @type jid: JID @param jid: Jabber id of the user whos vcard is quered """ nick = self.fixNick(self.nickname) whonick = jid if self.mucs.has_key(jid.getStripped()): whonick = self.fixNick(jid.getResource()) lines = [ ':localhost 311 %s %s %s %s * : %s' % (nick, whonick, jid.getNode(), jid.getDomain(), whonick), ':localhost 312 %s %s localhost : XMPP telepaatti' % (nick, whonick), ':localhost 318 %s %s :End of /WHOIS list.' % (nick, whonick)] while lines: self.sendToIRC(lines.pop(0)) def ircCommandUNAWAY(self): """Convert XMPP status to IRC away""" nick = self.fixNick(self.nickname) msg = ':localhost 305 %s :%s' % ( nick, 'You are no longer marked as being away') self.sendToIRC(msg) def ircCommandNOWAWAY(self): """Convert XMPP status to IRC not away""" nick = self.fixNick(self.nickname) msg = ':localhost 306 %s :%s' % ( nick, 'You have been marked as being away') self.sendToIRC(msg) def ircCommandNOTICE(self, text): """Convert XMPP message to IRC notice @type text: string @param text: notice text """ nick = self.fixNick(self.nickname) msg = 'NOTICE %s :%s' % (nick, text) self.sendToIRC(msg) def ircCommandSUBSCRIBE(self, pres): """Convert XMPP subscribe message to IRC message @type pres: presence @param pres: presence of which to subscribe """ text = "is making subsciption request to you with message: \"%s\" You MUST either approve the request or refuse the request. You can approve it by joinin #roster channel andthen type \"!subscribed %s\" if you wish to subscibe to contact or \"!unsubscribed %s\" if you wish not to subscibe to contact" % (pres.getStatus(), pres.getFrom(), pres.getFrom()) text = self.makeIRCACTION(text) self.printDebug(str(pres)) self.ircCommandPRIVMSG(pres.getFrom(), text) def ircCommandUNSUBSCRIBE(self, pres): """Convert XMPP unsubscribe message to IRC message. Unfinished. @type pres: presence @param pres: presence of which to unsubscribe """ self.printDebug(str(pres)) def ircCommandSUBSCRIBED(self, pres): """Convert XMPP subscribed message to IRC message. Unfinished. @type pres: presence @param pres: presence of which to subscribed """ self.printDebug(str(pres)) def ircCommandUNSUBSCRIBED(self, pres): """Convert XMPP unsubscribed message to IRC message. Unfinished. @type pres: presence @param pres: presence of which to unsubscribed """ self.printDebug(str(pres)) def xmppCommandSUBSCRIBE(self, jid): """Send XMPP subscribe message. @type jid: string @param jid: Jabber ID to subscribe """ self.sendToXMPP(Presence(to='%s' % jid, typ = 'subscribe')) def xmppCommandUNSUBSCRIBE(self, jid): """Send XMPP unsubscribe message. @type jid: string @param jid: Jabber ID to unsubscribe """ self.sendToXMPP(Presence(to='%s' % jid, typ = 'unsubscribe')) def xmppCommandSUBSCRIBED(self, jid): """Send XMPP subscribed message. @type jid: string @param jid: Jabber ID to subscribed """ self.sendToXMPP(Presence(to='%s' % jid, typ = 'subscribed')) def xmppCommandUNSUBSCRIBED(self, jid): """Send XMPP unsubscribed message. @type jid: string @param jid: Jabber ID to unsubscribed """ self.sendToXMPP(Presence(to='%s' % jid, typ = 'unsubscribed')) def xmppCommandMUCMODE(self, jid): """Send XMPP MUC mode change @type jid: string @param jid: Jabber id of the MUC """ if jid == 'roster': # no query for roster return iq = protocol.Iq(to=jid, queryNS=NS_DISCO_INFO, typ = 'get') iq.setID('disco3') self.sendToXMPP(iq) def xmppCommandMUCUSERS(self, jid): """Send XMPP MUC users query @type jid: string @param jid: Jabber id of the MUC """ if jid == 'roster': # no query for roster return iq = protocol.Iq(frm=unicode(self.JID), to=jid, queryNS=NS_DISCO_ITEMS, typ = 'get') iq.setID('disco4') self.sendToXMPP(iq) def xmppCommandSTATUS(self, show, status): """Send XMPP status change @type show: string @type status: string @param show: status @param status: status """ for muc in self.mucs.keys(): if muc != 'roster': p=Presence(to='%s/%s' % ( muc, self.nickname)) if not show == '': p.setShow(show) p.setStatus(status) self.sendToXMPP(p) p=Presence() p.setShow(show) p.setStatus(status) self.sendToXMPP(p) if show.upper() in STATUSSTATES[2:]: # available, chat self.ircCommandNOWAWAY() else: self.ircCommandUNAWAY() def xmppCommandMUCPRESENCE(self, muc, nick): """Send XMPP presence to MUC room @type muc: string @type nick: string @param muc: Jabber ID of the MUC @param nick: users nickname for the MUC """ self.sendToXMPP(Presence(to='%s/%s' % (muc, nick))) def xmppCommandMUCROLE(self, muc, nick, role): """Send XMPP MUC role to MUC room @type muc: string @type nick: string @type role: role @param muc: Jabber ID of the MUC @param nick: users nickname in the MUC @param role: role of the user """ iq = protocol.Iq(to=muc, queryNS=NS_MUC_ADMIN, typ = 'set') item = iq.getTag('query').setTag('item') item.setAttr('nick', nick) item.setAttr('role', role) self.sendToXMPP(iq) def xmppCommandGETWHOIS(self, jid): """Send XMPP vcard, last activity and sofware version request for some Jabber ID. @type jid: string @param jid: Jabber ID """ # vcard iq = protocol.Iq(to=jid, typ = 'get') iq.setTag(NS_VCARD + ' vCard') iq.setID('v3') self.sendToXMPP(iq) # last activity iq = protocol.Iq(to=jid, typ = 'get', queryNS=NS_LAST) self.sendToXMPP(iq) # software version if not jid.getResource(): # try to find match in roster rosternicks = self.mucs['roster'].keys() for x in rosternicks: if x.getStripped() == jid.getStripped(): jid = x if not jid.getResource(): return iq = protocol.Iq(to=jid, typ = 'get', queryNS=NS_VERSION) self.sendToXMPP(iq) def xmppCommandINFOGET(self, jid): """Not finished """ pass def xmppCommandSOFTWAREVERSION(self, jid): """Send set software version XMPP. Not finished """ pass def xmppCommandLASTACTIVITY(self, jid): """Send last activity XMPP. Not finished """ pass def run(self): """Here is this threads main functionality. Jabber-thread is started and polling of socket for IRC-messages is in here.""" jt = JabberThread(self.client) jt.start() nick = self.fixNick(self.nickname) lines = ["NOTICE AUTH :*** Looking up your hostname...", "NOTICE AUTH :*** Found your hostname, welcome back", "NOTICE AUTH :*** Checking ident", "NOTICE AUTH :*** No identd (auth) response", ":localhost 001 %s :Welcome to Telepaatti, IRC to XMPP gateway" % nick, ":localhost 002 %s :Your host is localhost [localhost port %s] running version telepaatti-%s" % ( nick, self.port, TELEPAATTIVERSION) ] while self.connected and jt.connected: try: data = self.socket.recv(4096) except: self.printError('GOT ERROR SHUTTING DOWN') self.connected = False if data.find ( 'PING' ) != -1: # check that our rooms are still alive if self.pingCounter == 5: self.pingCounter = 0 for muc in self.mucs.keys(): if muc != 'roster': if self.disconnectedMucs.has_key(muc): if self.disconnectedMucs[muc] < 5: self.disconnectedMucs[muc] = self.disconnectedMucs[muc] + 1 else: self.disconnectedMucs[muc] = 0 self.roomPingQueue[muc] = '' self.xmppCommandMUCMODE(muc) else: self.roomPingQueue[muc] = '' self.xmppCommandMUCMODE(muc) else: self.pingCounter = self.pingCounter + 1 self.sendToIRC('PONG localhost') while lines: self.sendToIRC(lines.pop(0)) if data: for line in data.splitlines(): self.commandHandler(line) else: self.connected = False if not jt.connected: self.ircCommandNOTICE('XMPP server disconnected, shutting down Telepaatti.') jt.connected = False self.socket.shutdown(socket.SHUT_RDWR) def messageHandlerError(self, sess, mess): """Handle incoming error messages from XMPP @type sess: string @type mess: Message @param sess: session @param mess: error message """ text = '' try: text = mess.getTag('error').getTag('text').getData() except: pass erc = mess.getErrorCode() jidFrom = mess.getFrom() to = mess.getTo() if erc == '403': self.ircCommandERRORMUC(482, text, jidFrom) else: self.printDebug('MUC ERROR NOT IMPLEMENTED') def messageHandler(self, sess, mess): """Handle incoming XMPP with type message @type sess: string @type mess: Message @param sess: session @param mess: XMPP Message """ if mess.getType() == 'error': self.messageHandlerError(sess,mess) return nick = mess.getFrom() text = mess.getBody() topic = mess.getSubject() room = unicode(mess.getFrom()) x = room.find('/') if x > 0: room = room[:room.find('/')] ts = '' if mess.getTag('x',namespace=NS_DELAY): ts=mess.getTimestamp() if not ts: ts=mess.setTimestamp() ts=mess.getTimestamp() ts = time.strptime(ts,'%Y%m%dT%H:%M:%S') ts = datetime.datetime(*ts[:-3]) if not text and not topic: return MUC = False if mess.getType() == 'groupchat': MUC = True if not MUC: self.ircCommandPRIVMSG(nick, text, ts) elif topic: self.ircCommandTOPIC(nick, room, topic) elif not nick.getResource() == self.nickname or ts: self.ircCommandPRIVMSGMUC(nick, room, text, ts) def iqHandler(self, con, iq): """Handle incoming XMPP with type Iq @type con: Connection @type iq: Iq @param con: XMPP Connection @param iq: XMPP Iq """ ns = iq.getQueryNS() if ns is None: ns = iq.getProperties()[0] if ns == NS_DISCO and iq.getType() in ['get', 'error']: self.iqHandlerInfo(con, iq) elif ns == NS_DISCO_ITEMS and iq.getType() == 'result': self.iqHandlerItems(con, iq) elif ns == NS_DISCO_INFO and iq.getType() == 'result': self.iqHandlerInfo(con, iq) elif ns == NS_DISCO_INFO and iq.getType() == 'get': self.xmppCommandINFOGET(iq.getFrom()) elif ns == NS_DISCO_ITEMS and iq.getType() == 'error': self.iqHandlerError(con, iq) elif ns == NS_DISCO_INFO and iq.getType() == 'error': self.iqHandlerError(con, iq) elif ns == NS_VCARD and iq.getType() == 'result': self.iqHandlerVcard(con, iq) elif ns == NS_VCARD and iq.getType() == 'error': self.iqHandlerVcardError(con, iq) elif ns == NS_LAST and iq.getType() == 'result': self.iqHandlerLast(con, iq) elif ns == NS_LAST and iq.getType() == 'get': self.xmppCommandLASTACTIVITY(iq.getFrom()) elif ns == NS_LAST and iq.getType() == 'error': self.iqHandlerLastError(con, iq) elif ns == NS_VERSION and iq.getType() == 'result': self.iqHandlerVersion(con, iq) elif ns == NS_VERSION and iq.getType() == 'error': self.iqHandlerVersionError(con, iq) elif ns == NS_VERSION and iq.getType() == 'get': self.xmppCommandSOFTWAREVERSION(iq.getFrom()) else: self.printDebug('IQ HANDLER FOR THIS NAMESPACE NOT IMPLEMENTED YET') def iqHandlerLastError(self,con, iq): """Handle incoming XMPP with type Iq and last activity error @type con: Connection @type iq: Iq @param con: XMPP Connection @param iq: XMPP Iq """ self.printDebug('iqHandlerLastError') def iqHandlerLast(self, con, iq): """Handle incoming XMPP with type Iq and last activity @type con: Connection @type iq: Iq @param con: XMPP Connection @param iq: XMPP Iq """ ch = iq.getTag('query') seconds = ch.getAttr('seconds') self.ircCommandNOTICE('** Last active information for %s **' % iq.getFrom()) self.ircCommandNOTICE('Idle %s second**' % seconds) def iqHandlerVersionError(self,con, iq): """Handle incoming XMPP with type Iq and version error @type con: Connection @type iq: Iq @param con: XMPP Connection @param iq: XMPP Iq """ self.printDebug('iqHandlerVersionError') def iqHandlerVersion(self, con, iq): """Handle incoming XMPP with type Iq and version @type con: Connection @type iq: Iq @param con: XMPP Connection @param iq: XMPP Iq """ ch = iq.getTag('query').getChildren() self.ircCommandNOTICE('** Software version information for %s **' % iq.getFrom()) for c in ch: self.ircCommandNOTICE('%s: %s' % (c.getName(), c.getData())) def iqHandlerVcardError(self,con, iq): """Handle incoming XMPP with type Iq and vcard error @type con: Connection @type iq: Iq @param con: XMPP Connection @param iq: XMPP Iq """ self.printDebug('iqHandlerVcardError') def iqHandlerVcard(self, con, iq): """Handle incoming XMPP with type Iq and vcard @type con: Connection @type iq: Iq @param con: XMPP Connection @param iq: XMPP Iq """ card = {} ch = iq.getTag('vCard').getChildren() for c in ch: name = c.getName() if name != 'PHOTO': if name == 'EMAIL': emailv = c.getChildren() card['EMAIL %s' % emailv[0].getName()] = emailv[1].getData() card[c.getName()] = c.getData() self.ircCommandNOTICE('** Vcard information for %s **' % iq.getFrom()) for key in card.keys(): for line in card[key].splitlines(): self.ircCommandNOTICE('%s: %s' % (key, line)) def iqHandlerError(self, con , iq): """Handle incoming XMPP with type Iq and error @type con: Connection @type iq: Iq @param con: XMPP Connection @param iq: XMPP Iq """ jid = iq.getFrom() # this can mess things up, fix later if self.roomPingQueue.has_key(jid): del (self.roomPingQueue[jid]) # room errors errornum = iq.getErrorCode() if jid in self.mucs.keys(): if errornum == '404' and jid not in self.disconnectedMucs.keys(): self.ircCommandERRORMUC(404, 'MUC DISCONNECTED', jid) self.ircCommandPRIVMSGMUC(JID("%s/%s" % (jid, 'telepaatti')), jid, 'MUC IS DISCONNECTED YOUR TEXT WILL NOT SHOW ON CHANNEL. YOU CAN WAIT UNTIL MUC CONNECTS AGAIN OR USE /PART TO LEAVE THIS MUC!', timestamp='') self.disconnectedMucs[jid] = 0 return else: return else: self.ircCommandERROR('iq error num %s jid not room! jid %s' % (errornum, jid)) def iqHandlerItems(self, con, iq): """Handle incoming XMPP with type Iq and items @type con: Connection @type iq: Iq @param con: XMPP Connection @param iq: XMPP Iq """ jid = iq.getFrom() if iq.getType() == 'error': # some fixing is needed self.ircCommandERROR('%s %s ' % (iq.getErrorCode(), iq.getErrorCode())) elif iq.getID() == 'disco4': # muc users ch = iq.getQueryChildren() mucusers = list() for c in ch: name = c.getName() if name == 'item': mucusers.append(c.getAttrs()['jid']) if self.mucs.has_key(jid): pass # we keep track of users else where self.ircCommandWHO(mucusers, unicode(jid)) return else: self.printDebug('UNKNOWN DISCO ITEM %s ' % jid) def iqHandlerInfo(self, con, iq): """Handle incoming XMPP with type Iq and info @type con: Connection @type iq: Iq @param con: XMPP Connection @param iq: XMPP Iq """ roomname = iq.getFrom() # this can mess things up, fix if self.roomPingQueue.has_key(roomname): del (self.roomPingQueue[roomname]) return MUC = False roomfeats = list() if iq.getType() == 'error': # fix this later self.ircCommandERROR('%s %s ' % (iq.getErrorCode(), iq.getErrorCode())) else: ch = iq.getQueryChildren() for c in ch: name = c.getName() if name == 'identity': atrs = c.getAttrs() if atrs.has_key('type') and atrs.has_key('category'): if atrs['type'] == 'text' and \ atrs['category'] == 'conference': MUC = True elif name == 'var': roomfeats.append(c.getAttrs()['var']) elif name == 'feature': roomfeats.append(c.getAttrs()['var']) else: self.printDebug('%s NOT IMPLeMENTED' % name) if MUC: # for MODE modestr = '+' for feat in roomfeats: if feat == 'muc_hidden': modestr += 's' if feat == 'muc_membersonly': modestr += 'p' if feat == 'muc_moderated': modestr += 'm' if feat == 'muc_nonanonymous': modestr += 'A' if feat == 'muc_open': modestr += 'F' if feat == 'muc_passwordprotected': modestr += 'k' if feat == 'muc_persistent': modestr += 'P' if feat == 'muc_public': modestr += 'B' if feat == 'muc_rooms': self.printDebug('muc_rooms not implemented') if feat == 'muc_semianonymous': modestr += 'a' if feat == 'muc_temporary': modestr += 'T' if feat == 'muc_unmoderated': modestr += 'u' if feat == 'muc_unsecured': modestr += 'U' self.ircCommandMODEMUC(roomname, modestr) else: self.printDebug("IQ stuff still missing here") def presenceHandler(self, sess, pres): """Handle incoming XMPP with type presence @type sess: Connection @type pres: Presence @param sess: XMPP Connection @param press: XMPP Presence """ MUC = False ptype = pres.getType() nick=pres.getFrom() tags = pres.getTags('x') for tag in tags: ns = tag.getNamespace() if ns.startswith(NS_MUC): MUC = True role = pres.getRole() affiliation = pres.getAffiliation() show = pres.getShow() status = pres.getStatus() room = unicode(pres.getFrom()) room = room[:room.find('/')] # for affiliation and role changes if MUC and \ self.mucs.has_key(room) and \ nick in self.mucs[room].keys(): xrole = self.mucs[room][nick]['role'] xaffiliation = self.mucs[room][nick]['affiliation'] if role != xrole: # role has changed giver = JID('%s/telepaatti' % room) if role.upper() == 'MODERATOR': self.ircCommandMODEMUCUSER(giver, nick, room, '+o') self.ircCommandMODEMUCUSER(giver, nick, room, '-v') if role.upper() == 'PARTICIPANT': self.ircCommandMODEMUCUSER(giver, nick, room, '-o') self.ircCommandMODEMUCUSER(giver, nick, room, '+v') if role.upper() == 'VISITOR': self.ircCommandMODEMUCUSER(giver, nick, room, '-o') self.ircCommandMODEMUCUSER(giver, nick, room, '-v') else: self.printDebug('MODE NONE') if xaffiliation != affiliation: # affiliation has changed pass # for nick changes if (pres.getNick() == self.newnick or pres.getNick() == self.nickname)\ and pres.getStatusCode() == '303': self.nickChangeInMucs[room] = {'checked': True, 'changed': True} # check if we have checked all MUCs for muc in self.nickChangeInMucs.keys(): if not self.nickChangeInMucs[muc]['checked']: return # no need to go any further # check if it changed in all MUCs for muc in self.nickChangeInMucs.keys(): if not self.nickChangeInMucs[muc]['changed']: changedMucs = list() for gei in self.nickChangeInMucs.keys(): if self.nickChangeInMucs[gei]['changed']: changedMucs.append(gei) self.nickChangeInMucs = {} for muc2 in changedMucs: self.nickChangeInMucs[muc2] = {'checked': False, 'changed': False} self.xmppCommandMUCPRESENCE(muc2, self.nickname) self.ircCommandERROR('Nick conflicts in some MUC wont change') return # out # remove, all have changed self.nickChangeInMucs = {} if pres.getNick() == self.nickname: self.newnick = '' return self.sendToIRC(':%s NICK :%s' % (self.nickname, self.newnick)) for muc in self.getMucs(): del (self.mucs[muc][JID("%s/%s" % (muc, self.nickname))]) # remove the old # add the new self.mucs[muc][JID("%s/%s" % (muc, self.newnick))] = { 'role': role, 'affiliation': affiliation } self.nickname = self.newnick self.newnick = '' return if not MUC and ptype == 'error': erc = pres.getErrorCode() if erc == '409' and self.mucs.has_key(nick.getStripped()): self.nickChangeInMucs[room] = {'checked': True, 'changed': False} # all must have come self.printDebug(str(self.nickChangeInMucs)) for muc in self.nickChangeInMucs.keys(): if not self.nickChangeInMucs[muc]['checked']: return changedMucs = list() for gei in self.nickChangeInMucs.keys(): if self.nickChangeInMucs[gei]['changed']: changedMucs.append(gei) self.nickChangeInMucs = {} for muc2 in changedMucs: self.nickChangeInMucs[muc2] = {'checked': False, 'changed': False} self.xmppCommandMUCPRESENCE(muc2, self.nickname) self.ircCommandERROR('Nick conflicts in some MUC wont change') else: self.ircCommandERROR('Got some error %s' % unicode(pres)) return if not MUC: # normal presence if ptype in ['subscribe']: self.ircCommandSUBSCRIBE(pres) elif ptype in ['unsubscribe']: self.ircCommandUNSUBSCRIBE(pres) elif ptype in ['subscribed']: self.ircCommandSUBSCRIBED(pres) elif ptype in ['unsubscribed']: self.ircCommandUNSUBSCRIBED(pres) else: self.ircCommandROSTERMSG(pres) elif MUC: if ptype == 'error': er = pres.getError() erc = pres.getErrorCode() if erc == '401': self.ircCommandERRORMUC(475, 'Password requeired to join', room) elif erc == '403': self.ircCommandERRORMUC(474, 'Cannot join MUC, you are banned', room) elif erc == '404': self.ircCommandERRORMUC(404, 'No such MUC', room) elif erc == '405': self.ircCommandERRORMUC(478, 'Can\'t create MUC', room) elif erc == '406': self.ircCommandERRORMUC(437, 'You must use reserverd nick to enter', room) elif erc == '407': self.ircCommandERRORMUC(473, 'Must be a member to enter', room) elif erc == '409': self.ircCommandERRORMUC(437, 'You must change nickname to enter', room) elif erc == '503': self.ircCommandERRORMUC(471, 'MUC is full', room) else: self.ircCommandERROR('MUC error not yet implemented (%d %s)' % (erc, er)) else: joining = self.joinQueue.has_key(room) inroom = self.mucs.has_key(room) if ptype == 'unavailable': self.printDebug('unavailable') if nick.getResource() == self.nickname: self.printDebug('our self') if joining: del (self.joinQueue[room]) elif self.nickChangeInMucs.has_key(room): # between nick change self.printDebug('we are between nick change') return elif inroom: self.ircCommandPART(nick, room, ' left') del (self.mucs[room]) else: line = "%s is doing something" % nick self.printDebug(line.encode('utf-8')) else: # someonerin else if joining: self.printDebug("%s left while we are joining room %s" % ( nick, room)) elif inroom: self.ircCommandPART(nick, room, 'left') del (self.mucs[room][nick]) else: line = "%s is doing something" % nick self.printDebug(line.encode('utf-8')) else: # not unavailable type self.printDebug('not unavailable') if nick.getResource() == self.nickname: if joining: # fix this also later self.mucs[room] = self.joinQueue[room]['users'] self.mucs[room][JID("%s/%s" % (room, self.nickname))] = { 'role': role, 'affiliation': affiliation, 'show' : show, 'status': status} del(self.joinQueue[room]) self.ircCommandSELFJOIN(self.nickname, room) elif inroom: self.mucs[room][JID("%s/%s" % (room, self.nickname))] = { 'role': role, 'affiliation': affiliation, 'show' : show, 'status': status} else: line = "%s is doing something" % nick self.printDebug(line.encode('utf-8')) elif nick.getResource() == self.newnick: pass else: # someone else if joining: if nick not in self.joinQueue[room]['users'].keys(): self.joinQueue[room]['users'][nick] = { 'role': role, 'affiliation': affiliation, 'show' : show, 'status': status} elif inroom: if nick not in self.mucs[room].keys(): self.mucs[room][nick] = { 'role': role, 'affiliation': affiliation, 'show' : show, 'status': status } self.ircCommandJOIN(nick, room) else: self.mucs[room][nick] = { 'role': role, 'affiliation': affiliation, 'show' : show, 'status': status } else: self.printDebug('TROUBLE LINE') def commandHandler(self, data): """Command handler for commands and text coming in from IRC-client @type data: string @param data: IRC data coming from IRC-client """ self.printDebug('got ircline: %s' % data) # utf-8 test try: unicode(data, 'utf-8') except exceptions.UnicodeDecodeError: self.printError('GOT ERROR') self.ircCommandERROR('Input form IRC client was not in utf-8. Turn utf-8 support on from your IRC client or input only pure ascii',-1) return args = data.split(' ', 1) arguments = u'' command = args[0] if len(args) == 2: arguments = args[1] command = command.upper() arguments = arguments.strip() MUC = False if arguments.startswith('#'): arguments = arguments[1:] MUC = True if command == 'JOIN': room = arguments if room == 'roster': if self.joinedRoster: # already in #roster return self.ircCommandROSTERSELFJOIN() else: if room.find('@') < 1: self.ircCommandERRORMUC(404, 'No such MUC', room) return if room in self.mucs.keys(): # already in MUC return self.joinQueue[arguments] = {'messages': list(), 'users': {}} p=Presence(to='%s/%s' % ( room, self.nickname)) p.setTag('x',namespace=NS_MUC).setTagData('password','') p.getTag('x').addChild('history',{'maxchars':'10000','maxstanzas':'100'}) self.sendToXMPP(p) elif command == 'PART': x = arguments.find(' :') room = arguments.strip() text = '' if x > 0: text = arguments[x+2:] text = text.strip() room = arguments[:x] room = room.strip() if room == 'roster': if not self.joinedRoster: # not in roster return self.ircCommandROSTERPART() else: if room not in self.mucs.keys(): # not in room return self.sendToXMPP(Presence(to='%s/%s' % (room, self.newnick), typ='unavailable', status=text)) elif command == 'PRIVMSG': x = arguments.find(' :') jid = arguments.strip() text = '' if x > 0: text = arguments[x+2:] text = text.strip() sact = text.find('\001ACTION ') eact = text.rfind('\001') if sact > -1 and eact > -1: text = '/me %s' % text[sact+8:eact] jid = arguments[:x] jid = jid.strip() type = 'chat' if MUC: type = 'groupchat' at = jid.find('@') slash = jid.find('/') if jid == 'roster': self.ircCommandROSTERPRIVMSGMUC(text) return elif (at < 0) and not MUC: # private msg from muc self.ircCommandERROR('You are trying to send private message someone in MUC room. Jabber can\'t send messages with nick only. Try to sen message to whole MUC jid, for example if you are in room jabber@conference.jabber.org and are trying to send message to nick petteri use /msg jabber@conference.jabber.org/petteri message!') targetnicks = list() for muc in self.mucs.iterkeys(): for mn in self.mucs[muc].keys(): mn = unicode(mn) mn = mn.encode('utf-8') if mn[mn.find('/')+1:] == jid: targetnicks.append(mn) if len(targetnicks) != 1: self.printError('Problems') else: jid = targetnicks[0] self.ircCommandERROR('Telepaatti forwarded your message to JID: %s' % jid) self.sendToXMPP(protocol.Message(jid, text, typ = type)) elif command == 'NICK': if self.notFirstNick: self.newnick = arguments for muc in self.getMucs(): self.nickChangeInMucs[muc] = {'checked': False, 'changed': False} for muc in self.nickChangeInMucs.keys(): self.xmppCommandMUCPRESENCE(muc, self.newnick) else: self.notFirstNick = True elif command == 'TOPIC': x = arguments.find(' :') jid = arguments.strip() text = '' if x > 0: text = arguments[x+2:] text = text.strip() jid = arguments[:x] jid = jid.strip() if jid not in self.mucs.keys(): self.ircCommandERROR('', 403) return if jid == 'roster': self.ircCommandERRORMUC(482, 'TOPIC ON ROSTER CANNOT BE CHANGED', jid) return self.sendToXMPP(protocol.Message(jid, typ = 'groupchat', subject = text)) elif command == 'MODE': if not arguments: return x = arguments.find(' ') params = '' jid = arguments if x > -1: jid = arguments[:x] params = arguments[x+1:] x = params.find(' ') tonick = '' if x > -1: tonick = params[x+1:] params = params[x:] params.strip() if jid == 'roster': return elif jid == self.nickname: self.ircCommandMODE(params) else: if params.find('b') > -1: # get bandlist self.ircCommandMODEMUCBANLIST(jid) return elif params.find('+o') > -1: # trying to op someone self.xmppCommandMUCROLE(jid, tonick, 'moderator') elif params.find('-o') > -1: # trying to deop someone self.xmppCommandMUCROLE(jid, tonick, 'participant') elif params.find('+v') > -1: # trying to voice someone self.xmppCommandMUCROLE(jid, tonick, 'participant') elif params.find('-v') > -1: # trying to voice someone self.xmppCommandMUCROLE(jid, tonick, 'visitor') else: self.xmppCommandMUCMODE(jid) elif command == 'WHO': if not arguments: return jid = arguments self.xmppCommandMUCUSERS(jid) elif command == 'WHOIS': self.ircCommandWHOIS(JID(arguments)) self.xmppCommandGETWHOIS(JID(arguments)) elif command == 'AWAY': arguments = arguments[1:] # remove the : show = '' if arguments != '': show = 'away' args = arguments.split(' ',1) status = arguments if args[0].upper() in STATUSSTATES: show = args[0] if len(args) == 2: status = args[1] self.xmppCommandSTATUS(show, status) def usage(): """Usage function for showing commandline options """ print "Usage: telepaatti [OPTION]..." print "OPTIONS" print "-h, --help\t telepaatti help" print "-p, --port\t port which telepaatti listens" print "-u, --user\t Jabber/XMPP username in the format name@xmppserver.tld" print "-w, --password\t Password for Jabber/XMPP account" print "-d, --debug\t turn debug messages on" print "-n, --nick\t nickname to be used in MUCS" def main(): """Main function where the control flow stars """ port = 6667 user = '' password = '' debug = False nickname = None try: opts, args = getopt.getopt(sys.argv[1:], "u:p:w:n:h:d", ["user=", "port=", "password=", "nick=", "help", "debug"]) except getopt.GetoptError: usage() sys.exit(2) if len(opts) == 0: usage() sys.exit(2) for o, a in opts: if o in ("-h", "--help"): usage() sys.exit() if o in ("-p", "--port"): try: port = int(a) except: print "port should be an integer" sys.exit() if o in ("-u", "--user"): if a.find('@') < 1: print "user name should be in form user@xmppserver.tld" sys.exit() else: user = a if o in ("-w", "--password"): password = a if o in ("-d", "--debug"): print "debug messages on" debug = True if o in ("-n", "--nick"): nickname = a service = socket.socket(socket.AF_INET, socket.SOCK_STREAM) service.bind(("", port)) service.listen(1) print "listening on port", port (clientsocket, address ) = service.accept() ct = ClientThread(clientsocket, port, user, password, debug, nickname) ct.start() service.shutdown(socket.SHUT_RDWR) if __name__ == "__main__": main()