summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client.py56
-rw-r--r--src/config.py56
-rw-r--r--src/connection.py129
-rw-r--r--src/gui.py79
-rw-r--r--src/handler.py134
-rw-r--r--src/logging.py45
-rw-r--r--src/multiuserchat.py187
-rw-r--r--src/singleton.py25
8 files changed, 711 insertions, 0 deletions
diff --git a/src/client.py b/src/client.py
new file mode 100644
index 00000000..e51c278e
--- /dev/null
+++ b/src/client.py
@@ -0,0 +1,56 @@
+#!/usr/bin/python
+# -*- coding:utf-8 -*-
+#
+# Copyright 2010 Le Coz Florent <louizatakk@fedoraproject.org>
+#
+# This file is part of Poezio.
+#
+# Poezio is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, version 3 of the License.
+#
+# Poezio 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 Poezio. If not, see <http://www.gnu.org/licenses/>.
+
+from connection import Connection
+from multiuserchat import MultiUserChat
+from config import config
+from handler import Handler
+from gui import Gui
+
+class Client(object):
+ """
+ Main class
+ Do what should be done automatically by the Client:
+ join the rooms at startup, for example
+ """
+ def __init__(self):
+ self.handler = Handler()
+
+ self.resource = config.get('resource')
+ self.server = config.get('server')
+ self.connection = Connection(self.server, self.resource)
+ self.connection.start()
+
+ self.muc = MultiUserChat(self.connection.client)
+ self.gui = Gui()
+ self.rooms = config.get('rooms').split(':')
+
+ import time
+ time.sleep(1) # remove
+ for room in self.rooms:
+ self.handler.emit('join-room', room = room.split('/')[0], nick=room.split('/')[1])
+ while 1:
+ self.connection.process()
+
+
+def main():
+ client = Client()
+
+if __name__ == '__main__':
+ main()
diff --git a/src/config.py b/src/config.py
new file mode 100644
index 00000000..ed50f622
--- /dev/null
+++ b/src/config.py
@@ -0,0 +1,56 @@
+#!/usr/bin/python
+# -*- coding:utf-8 -*-
+#
+# Copyright 2009 chickenzilla
+# Copyright 2010 Le Coz Florent <louizatakk@fedoraproject.org>
+#
+# This file is part of Poezio.
+#
+# Poezio is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, version 3 of the License.
+#
+# Poezio 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 Poezio. If not, see <http://www.gnu.org/licenses/>.
+
+from ConfigParser import RawConfigParser
+
+class Config(RawConfigParser):
+ """
+ load/save the config to a file
+ """
+ def __init__(self, file_name):
+ self.defsection = "Poezio"
+ self.file_name = file_name
+ RawConfigParser.__init__(self, None)
+ RawConfigParser.read(self, file_name)
+
+ def get(self, option):
+ return RawConfigParser.get(self, self.defsection, option)
+
+ def getint(self, option):
+ return int(self.get(option))
+
+ def getfloat(self, option):
+ return float(self.get(option))
+
+ def getboolean(self, option):
+ return RawConfigParser.getboolean(self, self.defsection, option)
+
+ def set(self, option, value):
+ RawConfigParser.set(self, self.defsection, option, value)
+
+ def save(self):
+ with copen(self.filename, "w", "utf-8", "ignore") as f:
+ RawConfigParser.write(self, f)
+
+ def setAndSave(self, option, value):
+ self.set(option, value)
+ self.save()
+
+config = Config('poezio.cfg')
diff --git a/src/connection.py b/src/connection.py
new file mode 100644
index 00000000..4e34384b
--- /dev/null
+++ b/src/connection.py
@@ -0,0 +1,129 @@
+#!/usr/bin/python
+# -*- coding:utf-8 -*-
+#
+# Copyright 2010 Le Coz Florent <louizatakk@fedoraproject.org>
+#
+# This file is part of Poezio.
+#
+# Poezio is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, version 3 of the License.
+#
+# Poezio 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 Poezio. If not, see <http://www.gnu.org/licenses/>.
+
+import sys
+
+import xmpp
+from config import config
+from logging import log
+from threading import Thread
+from multiuserchat import MultiUserChat
+from handler import Handler
+
+class Connection(Thread):
+ """
+ Handles all network transactions
+ """
+ def __init__(self, server, resource):
+ Thread.__init__(self)
+ self.handler = Handler()
+
+ self.server = server
+ self.resource = resource
+ self.online = 0 # 1:connected, 2:auth confirmed
+ self.jid = '' # we don't know our jid yet (anon account)
+ if not self.server:
+ log.error('You should set a server in the configuration file')
+ self.port = int(config.get('port'))
+ if not self.port:
+ log.warning('No port set in configuration file, defaulting to 5222')
+ self.port = 5222
+
+ def run(self):
+ """
+ connect to server
+ """
+ self.client = xmpp.Client(self.server)
+ if not self.connect_to_server(self.server, self.port):
+ log.error('Could not connect to server')
+ sys.exit(-1)
+ if not self.authenticate():
+ log.error('Could not authenticate to server')
+ sys.exit(-1)
+ self.client.sendInitPresence()
+ self.online = 1 # 2 when confirmation of auth is received
+ self.register_handlers()
+
+ def connect_to_server(self, server, port):
+ # TODO proxy stuff
+ return self.client.connect((server, port))
+
+ def authenticate(self, anon=True):
+ if anon:
+ return self.client.auth(None, None, self.resource)
+ else:
+ log.error('Non-anonymous connections not handled currently')
+ return None
+
+ def register_handlers(self):
+ self.client.RegisterHandler('message', self.handler_message)
+ self.client.RegisterHandler('presence', self.handler_presence)
+ self.client.RegisterHandler('iq', self.handler_iq)
+
+ def handler_message(self, connection, message):
+ # body = message.getTag('body').getData()
+ # fro = str(message.getAttr('from'))
+ # room, nick = fro.split('/')
+ # print 'Message from %s in %s :' % (nick, room), body
+ self.handler.emit('xmpp-message-handler', message=message)
+
+ def handler_presence(self, connection, presence):
+ affil, role = u'', u''
+ fro = presence.getFrom()#presence.getAttr('from')
+ room_from, nick_from = fro.getStripped().encode('utf-8'), fro.getResource().encode('utf-8')
+ to = presence.getAttr('to')
+ room_to, nick_to = to.getStripped().encode('utf-8'), to.getResource().encode('utf-8')
+ if fro == to: # own presence
+ self.online = 2
+ self.jid = to
+ print 'Authentification confirmation received!'
+ return
+ for x in presence.getTags('x'):
+ if x.getTag('item'):
+ affil = x.getTagAttr('item', 'affiliation').encode('utf-8')
+ role = x.getTagAttr('item', 'role').encode('utf-8')
+ break
+# print '[%s] in room {%s}. (%s - %s)'% (nick_from, room_from, affil, role)
+ self.handler.emit('xmpp-presence-handler', presence=presence)
+
+ def handler_iq(self, connection, iq):
+ pass
+
+ def process(self, timeout=10):
+ if self.online:
+ self.client.Process(timeout)
+ else:
+ log.warning('disconnecting...')
+
+if __name__ == '__main__':
+ resource = config.get('resource')
+ server = config.get('server')
+ connection = Connection(server, resource)
+ connection.start()
+ rooms = config.get('rooms').split(':')
+ from time import sleep
+ print connection.online
+ sleep(2)
+ print connection.online
+ for room in rooms:
+ connection.send_join_room(room.split('/')[0], room.split('/')[1])
+ i = 17
+ while i:
+ connection.process()
+ i -= 1
diff --git a/src/gui.py b/src/gui.py
new file mode 100644
index 00000000..44b35f73
--- /dev/null
+++ b/src/gui.py
@@ -0,0 +1,79 @@
+#!/usr/bin/python
+# -*- coding:utf-8 -*-
+#
+# Copyright 2010 Le Coz Florent <louizatakk@fedoraproject.org>
+#
+# This file is part of Poezio.
+#
+# Poezio is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, version 3 of the License.
+#
+# Poezio 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 Poezio. If not, see <http://www.gnu.org/licenses/>.
+
+from handler import Handler
+import curses
+from curses import textpad
+
+class Gui(object):
+ """
+ Graphical user interface using ncurses
+ """
+ def __init__(self):
+ self.handler = Handler()
+
+ self.handler.connect('on-muc-message-received', self.on_message)
+ self.handler.connect('join-room', self.on_join_room)
+ self.handler.connect('on-muc-presence-changed', self.on_presence)
+
+ self.init_curses()
+
+ def __del__(self):
+ curses.nocbreak();
+ self.stdscr.keypad(0);
+ curses.echo()
+ curses.endwin()
+
+ def init_curses(self):
+ curses.initscr()
+ self.stdscr = curses.newwin(1, 1000, 0, 0)
+ curses.noecho()
+ curses.cbreak()
+ curses.meta(True)
+ self.stdscr.keypad(1)
+ self.input = textpad.Textbox(self.stdscr)
+
+ def on_message(self, jid, msg, subject, typ, stanza):
+ print "on_message", jid, msg, subject, typ
+
+ def on_join_room(self, room, nick):
+ print "on_join_room", room, nick
+
+ def on_presence(self, jid, priority, show, status, stanza):
+ print "on presence", jid, priority, show, status
+
+ def get_input(self):
+ return self.stdscr.getch()
+def sigwinch_handler(n, frame):
+ fd = open('fion', 'a')
+ fd.write(str(n)+ '\n')
+ fd.close()
+
+if __name__ == '__main__':
+ gui = Gui()
+ import signal
+ signal.signal(signal.SIGWINCH, sigwinch_handler)
+ while 1:
+ key = gui.stdscr.getch()
+ if key == curses.KEY_RESIZE:
+ print "FION"
+ import sys
+ sys.exit()
+ gui.input.do_command(key)
+
diff --git a/src/handler.py b/src/handler.py
new file mode 100644
index 00000000..b08280a0
--- /dev/null
+++ b/src/handler.py
@@ -0,0 +1,134 @@
+# -*- coding: utf-8 -*-
+
+# Copyright 2009, 2010 Erwan Briand
+# Copyright 2010, Florent Le Coz <louizatakk@fedoraproject.org>
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation version 3 of the License.
+
+# This program 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, see <http://www.gnu.org/licenses/>.
+
+from singleton import Singleton
+
+class Handler(Singleton):
+ """This class is the global handler for the software's signals."""
+ __is_first_instance = True
+
+ def __init__(self):
+ if Handler.__is_first_instance:
+ Handler.__is_first_instance = False
+
+ self.__signals__ = {
+ # - XMPP's core handlers (don't use outside of src/jabber/*.py)
+
+ 'xmpp-presence-handler': list(),
+ # A presence is received
+ # Args: the stanza object
+
+ 'xmpp-iq-handler': list(),
+ # An iq is received
+ # Args: the stanza object
+
+ 'xmpp-message-handler': list(),
+ # A message is received
+ # Args: the stanza object
+
+ # - GUI event
+
+ 'on-quit': list(),
+ # When the user wants to quit.
+
+ # - Roster and presence
+
+ 'on-connected': list(),
+ # At the end of a successful connection process.
+
+ 'on-disconnected': list(),
+ # When the user is disconnected from the server.
+
+ 'on-message-received': list(),
+ # When a message is received.
+ # Args: jid, msg, subject, typ
+
+ 'send-message': list(),
+ # Send a message to someone.
+ # Args: jid, msg, subj, typ
+
+ # - vCard (XEP-0054)
+
+ 'vcard-request': list(),
+ # Request a vcard.
+ # Args: jid
+
+ 'on-vcard-received': list(),
+ # When a vcard is received.
+ # Args: jid, vcard
+
+ # - Multi-User Chat (XEP-0045)
+
+ 'gui-join-room': list(),
+ # Join a room inside the GUI (call `join-room`).
+ # Args: room, nickname
+
+ 'join-room': list(),
+ # Join a room.
+ # Args: room, nick
+
+ 'quit-room': list(),
+ # Quit a room.
+ # Args: room, nick
+
+ 'on-muc-message-received': list(),
+ # When a message is received.
+ # Args: jid, msg, subject, typ, stanza
+
+ 'on-muc-presence-changed': list(),
+ # When someone in the roster changes his presence.
+ # Args: jid, priority, show, status, stanza
+
+ 'on-muc-error': list(),
+ # When the MUC composant sends an error
+ # Args: room, code, msg
+
+ 'eject-user': list(),
+ # When the user try to eject another one.
+ # Args: room, action, nick, reason
+
+ 'change-user-role': list(),
+ # When the user try to change the role of someone.
+ # Args: room, nick, role
+
+ 'change-user-affiliation': list(),
+ # When the user try to change the affiliation of someone.
+ # Args: room, jid, aff
+
+ 'change-subject': list(),
+ # When the user try to change the topic.
+ # Args: room, subject
+
+ 'change-nick': list()
+ # When the user try to change his nick.
+ # Args: room, nick
+ }
+
+ def connect(self, signal, func):
+ """Connect a function to a signal."""
+ if func not in self.__signals__[signal]:
+ self.__signals__[signal].append(func)
+ else:
+ print "%s doesn't exist." % signal
+
+ def emit(self, signal, **kwargs):
+ """Emit a signal."""
+ if self.__signals__.has_key(signal):
+ for func in self.__signals__[signal]:
+ func(**kwargs)
+ else:
+ print "%s doesn't exist." % signal
diff --git a/src/logging.py b/src/logging.py
new file mode 100644
index 00000000..b0b8b1aa
--- /dev/null
+++ b/src/logging.py
@@ -0,0 +1,45 @@
+#!/usr/bin/python
+# -*- coding:utf-8 -*-
+#
+# Copyright 2010 Le Coz Florent <louizatakk@fedoraproject.org>
+#
+# This file is part of Poezio.
+#
+# Poezio is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, version 3 of the License.
+#
+# Poezio 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 Poezio. If not, see <http://www.gnu.org/licenses/>.
+
+from config import config
+import sys
+
+class Logger(object):
+ """
+ Appends things to files. Error/information/warning logs
+ and also log the conversations to logfiles
+ """
+ def __init__(self):
+ self.logfile = config.get('logfile')
+
+ def warning(self, msg):
+ # change me
+ # add timestamp and stuff like that, it's cool
+ print 'Warning/error ' + msg
+ if self.logfile:
+ fd = open(self.logfile, 'a')
+ fd.write(msg+'\n')
+ fd.close()
+
+ def error(self, msg):
+ # change me too
+ self.warning(msg)
+ sys.exit(-1)
+
+log = Logger()
diff --git a/src/multiuserchat.py b/src/multiuserchat.py
new file mode 100644
index 00000000..c3a382cd
--- /dev/null
+++ b/src/multiuserchat.py
@@ -0,0 +1,187 @@
+# -*- coding: utf-8 -*-
+
+# Copyright 2009, 2010 Erwan Briand
+# Copyright 2010, Florent Le Coz <louizatakk@fedoraproject.org>
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation version 3 of the License.
+
+# This program 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, see <http://www.gnu.org/licenses/>.
+
+# Implementation of the XEP-0045: Multi-User Chat.
+
+from xmpp import NS_MUC_ADMIN
+from xmpp.protocol import Presence, Iq, Message, JID
+
+from handler import Handler
+
+def get_stripped_jid(jid):
+ """Return the stripped JID (bare representation)"""
+ if isinstance(jid, basestring):
+ jid = JID(jid)
+ return jid.getStripped()
+
+def is_jid(jid):
+ """Return True if this is a valid JID"""
+ if JID(jid).getNode() != '':
+ return True
+
+class MultiUserChat(object):
+ def __init__(self, connection):
+ self.connection = connection
+
+ self.rooms = []
+ self.rn = {}
+
+ self.handler = Handler()
+ self.handler.connect('join-room', self.join_room)
+ self.handler.connect('quit-room', self.quit_room)
+ self.handler.connect('on-disconnected', self.on_disconnect)
+ self.handler.connect('xmpp-iq-handler', self.on_iq)
+ self.handler.connect('xmpp-presence-handler', self.on_presence)
+ self.handler.connect('xmpp-message-handler', self.on_message)
+ self.handler.connect('eject-user', self.eject_user)
+ self.handler.connect('change-user-role', self.change_role)
+ self.handler.connect('change-user-affiliation', self.change_aff)
+ self.handler.connect('change-subject', self.change_subject)
+ self.handler.connect('change-nick', self.change_nick)
+
+ def join_room(self, room, nick):
+ """Join a new room"""
+ self.rooms.append(room)
+ self.rn[room] = nick
+
+ pres = Presence(to='%s/%s' % (room, nick))
+ self.connection.send(pres)
+
+ def quit_room(self, room, nick):
+ """Quit a room"""
+ if room is None and nick is None:
+ self.on_disconnect()
+ return
+
+ pres = Presence(to='%s/%s' % (room, nick), typ='unavailable')
+ self.connection.send(pres)
+
+ self.rooms.remove(unicode(room))
+ del self.rn[room]
+
+ def on_disconnect(self):
+ """Called at disconnection"""
+ for room in self.rooms:
+ pres = Presence(to='%s/%s' % (room, self.rn[room]),
+ typ='unavailable')
+ self.connection.send(pres)
+
+ self.rooms = []
+ self.rn = {}
+
+ def on_iq(self, iq):
+ """Receive a MUC iq notification"""
+ from_ = iq.getFrom().__str__()
+
+ if get_stripped_jid(from_) in self.rooms:
+ children = iq.getChildren()
+ for child in children:
+ if child.getName() == 'error':
+ code = int(child.getAttr('code'))
+ msg = None
+
+ echildren = child.getChildren()
+ for echild in echildren:
+ if echild.getName() == 'text':
+ msg = echild.getData()
+
+ self.handler.emit('on-muc-error',
+ room=from_,
+ code=code,
+ msg=msg)
+
+
+
+ def on_presence(self, presence):
+ """Receive a MUC presence notification"""
+ from_ = presence.getFrom().__str__()
+
+ if get_stripped_jid(from_) in self.rooms:
+ self.handler.emit('on-muc-presence-changed',
+ jid=from_.encode('utf-8'),
+ priority=presence.getPriority(),
+ show=presence.getShow(),
+ status=presence.getStatus(),
+ stanza=presence
+ )
+
+ def on_message(self, message):
+ """Receive a MUC message notification"""
+ from_ = message.getFrom().__str__().encode('utf-8')
+
+ if get_stripped_jid(from_) in self.rooms:
+ body_ = message.getBody()
+ type_ = message.getType()
+ subj_ = message.getSubject()
+ self.handler.emit('on-muc-message-received',
+ jid=from_, msg=body_, subject=subj_,
+ typ=type_, stanza=message)
+
+ def eject_user(self, room, action, nick, reason):
+ """Eject an user from a room"""
+ iq = Iq(typ='set', to=room)
+ query = iq.addChild('query', namespace=NS_MUC_ADMIN)
+ item = query.addChild('item')
+
+ if action == 'kick':
+ item.setAttr('role', 'none')
+ if is_jid(nick):
+ item.setAttr('jid', nick)
+ else:
+ item.setAttr('nick', nick)
+ elif action == 'ban':
+ item.setAttr('affiliation', 'outcast')
+ item.setAttr('jid', nick)
+
+ if reason is not None:
+ rson = item.addChild('reason')
+ rson.setData(reason)
+
+ self.connection.send(iq)
+
+ def change_role(self, room, nick, role):
+ """Change the role of an user"""
+ iq = Iq(typ='set', to=room)
+ query = iq.addChild('query', namespace=NS_MUC_ADMIN)
+ item = query.addChild('item')
+ item.setAttr('nick', nick)
+ item.setAttr('role', role)
+
+ self.connection.send(iq)
+
+ def change_aff(self, room, jid, aff):
+ """Change the affiliation of an user"""
+ iq = Iq(typ='set', to=room)
+ query = iq.addChild('query', namespace=NS_MUC_ADMIN)
+ item = query.addChild('item')
+ item.setAttr('jid', jid)
+ item.setAttr('affiliation', aff)
+
+ self.connection.send(iq)
+
+ def change_subject(self, room, subject):
+ """Change the subject of a room"""
+ message = Message(typ='groupchat', to=room)
+ subj = message.addChild('subject')
+ subj.setData(subject)
+
+ self.connection.send(message)
+
+ def change_nick(self, room, nick):
+ """Change the nickname"""
+ pres = Presence(to='%s/%s' % (room, nick))
+ self.connection.send(pres)
diff --git a/src/singleton.py b/src/singleton.py
new file mode 100644
index 00000000..4390bc0e
--- /dev/null
+++ b/src/singleton.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+
+# Copyright 2009, 2010 Erwan Briand
+# Copyright 2010, Florent Le Coz <louizatakk@fedoraproject.org>
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation version 3 of the License.
+#
+# This program 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, see <http://www.gnu.org/licenses/>.
+
+class Singleton(object):
+ """ Canonic Singleton implementation.
+ """
+ _instance = None
+ def __new__(cls, *args, **kwargs):
+ if cls._instance is None:
+ cls._instance = super(Singleton, cls).__new__(cls, *args, **kwargs)
+ return cls._instance