# HG changeset patch # User Goffi # Date 1269127735 -39600 # Node ID f271fff3a713a0963e1f349d4e0d304b3e498560 # Parent efe81b61673c553cd4935347248bc2b773cf6fef MUC implementation: first draft /!\ the experimental muc branche of wokkel must be used - bridge: new roomJoined signal - wix: contact list widget is now in a separate file, and manage different kinds of presentation - wix: chat window now manage group chat (first draft, not working yet) - wix: constants are now in a separate class, so then can be accessible from everywhere - wix: new menu to join room (do nothing yet, except entering in a test room) - new plugin for xep 0045 (MUC), use wokkel experimental MUC branch - plugins: the profile is now given for get_handler, cause it can be used internally by a plugin (e.g.: xep-0045 plugin) diff -r efe81b61673c -r f271fff3a713 frontends/quick_frontend/quick_app.py --- a/frontends/quick_frontend/quick_app.py Sat Mar 06 14:57:23 2010 +1100 +++ b/frontends/quick_frontend/quick_app.py Sun Mar 21 10:28:55 2010 +1100 @@ -42,6 +42,7 @@ self.bridge.register("newContact", self.newContact) self.bridge.register("newMessage", self.newMessage) self.bridge.register("presenceUpdate", self.presenceUpdate) + self.bridge.register("roomJoined", self.roomJoined) self.bridge.register("subscribe", self.subscribe) self.bridge.register("paramUpdate", self.paramUpdate) self.bridge.register("contactDeleted", self.contactDeleted) @@ -80,7 +81,7 @@ ## misc ## self.profiles[profile]['onlineContact'] = set() #FIXME: temporary - #TODO: managed multi-profiles here + #TODO: gof: managed multi-profiles here if self.bridge.isConnected(profile): self.setStatusOnline(True) else: @@ -153,7 +154,7 @@ if not self.__check_profile(profile): return print "check ok" - debug (_("presence update for %(jid)s (show=%(show)s, statuses=%(statuses)s)") % {'jid':jabber_id, 'show':show, 'statuses':statuses}); + debug (_("presence update for %(jid)s (show=%(show)s, priority=%(priority)s, statuses=%(statuses)s) [profile:%(profile)s]") % {'jid':jabber_id, 'show':show, 'priority':priority, 'statuses':statuses, 'profile':profile}); from_jid=JID(jabber_id) debug ("from_jid.short=%(from_jid)s whoami.short=%(whoami)s" % {'from_jid':from_jid.short, 'whoami':self.profiles[profile]['whoami'].short}) @@ -187,13 +188,19 @@ self.CM.update(from_jid, 'nick', cache['nick']) if cache.has_key('avatar'): self.CM.update(from_jid, 'avatar', self.bridge.getAvatarFile(cache['avatar'])) - self.contactList.replace(from_jid) + self.contactList.replace(from_jid, self.CM.getAttr(from_jid, 'groups')) if show=="unavailable" and from_jid in self.profiles[profile]['onlineContact']: self.profiles[profile]['onlineContact'].remove(from_jid) self.CM.remove(from_jid) if not self.CM.isConnected(from_jid): self.contactList.disconnect(from_jid) + + def roomJoined(self, room_id, room_service, room_nicks, user_nick, profile): + """Called when a MUC room is joined""" + debug (_("Room [%(room_name)s] joined by %(profile)s") % {'room_name':room_id+'@'+room_service, 'profile': profile}) + + def subscribe(self, type, raw_jid, profile): """Called when a subsciption maangement signal is received""" diff -r efe81b61673c -r f271fff3a713 frontends/quick_frontend/quick_chat.py --- a/frontends/quick_frontend/quick_chat.py Sat Mar 06 14:57:23 2010 +1100 +++ b/frontends/quick_frontend/quick_chat.py Sun Mar 21 10:28:55 2010 +1100 @@ -26,14 +26,21 @@ class QuickChat(): - def __init__(self, to_jid, host): - self.to_jid = to_jid + def __init__(self, target, host, type='one2one'): + self.target = target self.host = host + self.type = type + + def setType(self, type): + """Set the type of the chat + @param type: can be 'one2one' for single conversation or 'group' for chat à la IRC + """ + self.type = type def historyPrint(self, size=20, keep_last=False, profile='@NONE@'): """Print the initial history""" debug (_("now we print history")) - history=self.host.bridge.getHistory(self.host.profiles[profile]['whoami'].short, self.to_jid, 20) + history=self.host.bridge.getHistory(self.host.profiles[profile]['whoami'].short, self.target, 20) stamps=history.keys() stamps.sort() for stamp in stamps: diff -r efe81b61673c -r f271fff3a713 frontends/quick_frontend/quick_contact_list.py --- a/frontends/quick_frontend/quick_contact_list.py Sat Mar 06 14:57:23 2010 +1100 +++ b/frontends/quick_frontend/quick_contact_list.py Sun Mar 21 10:28:55 2010 +1100 @@ -49,6 +49,6 @@ """remove a contact from the list""" raise NotImplementedError - def add(self, jid): + def add(self, jid, param_groups=None): """add a contact to the list""" raise NotImplementedError diff -r efe81b61673c -r f271fff3a713 frontends/sat_bridge_frontend/DBus.py --- a/frontends/sat_bridge_frontend/DBus.py Sat Mar 06 14:57:23 2010 +1100 +++ b/frontends/sat_bridge_frontend/DBus.py Sun Mar 21 10:28:55 2010 +1100 @@ -110,6 +110,9 @@ return self.db_req_iface.confirmationAnswer(id, accepted, data) #methods from plugins + def joinMUC(self, service, roomId, nick, profile_key='@DEFAULT@'): + return self.db_comm_iface.joinMUC(service, roomId, nick, profile_key) + def sendFile(self, to, path, profile_key='@DEFAULT@'): return self.db_comm_iface.sendFile(to, path, profile_key) diff -r efe81b61673c -r f271fff3a713 frontends/wix/chat.py --- a/frontends/wix/chat.py Sat Mar 06 14:57:23 2010 +1100 +++ b/frontends/wix/chat.py Sun Mar 21 10:28:55 2010 +1100 @@ -27,6 +27,7 @@ from logging import debug, info, error from tools.jid import JID from quick_frontend.quick_chat import QuickChat +from contact_list import ContactList idSEND = 1 @@ -34,17 +35,22 @@ class Chat(wx.Frame, QuickChat): """The chat Window for one to one conversations""" - def __init__(self, to_jid, host): - wx.Frame.__init__(self, None, title=to_jid, pos=(0,0), size=(400,200)) - QuickChat.__init__(self, to_jid, host) + def __init__(self, target, host, type='one2one'): + wx.Frame.__init__(self, None, title=target, pos=(0,0), size=(400,200)) + QuickChat.__init__(self, target, host, type) - self.chatWindow = wx.TextCtrl(self, -1, style = wx.TE_MULTILINE | wx.TE_RICH | wx.TE_READONLY) - self.textBox = wx.TextCtrl(self, -1, style = wx.TE_PROCESS_ENTER) - self.sizer = wx.BoxSizer(wx.VERTICAL) - self.sizer.Add(self.chatWindow, 1, flag=wx.EXPAND) - self.sizer.Add(self.textBox, flag=wx.EXPAND) - self.SetSizer(self.sizer) - self.SetAutoLayout(True) + self.splitter = wx.SplitterWindow(self, -1) + + self.conv_panel = wx.Panel(self.splitter) + self.conv_panel.sizer = wx.BoxSizer(wx.VERTICAL) + self.chatWindow = wx.TextCtrl(self.conv_panel, -1, style = wx.TE_MULTILINE | wx.TE_RICH | wx.TE_READONLY) + self.textBox = wx.TextCtrl(self.conv_panel, -1, style = wx.TE_PROCESS_ENTER) + self.conv_panel.sizer.Add(self.chatWindow, 1, flag=wx.EXPAND) + self.conv_panel.sizer.Add(self.textBox, flag=wx.EXPAND) + self.conv_panel.SetSizer(self.conv_panel.sizer) + self.splitter.Initialize(self.conv_panel) + self.setType(self.type) + self.createMenus() #events @@ -61,7 +67,37 @@ #misc self.textBox.SetFocus() self.Hide() #We hide because of the show toggle - + + def __createPresents(self): + """Create a list of present people in a group chat""" + self.present_panel = wx.Panel(self.splitter) + self.present_panel.sizer = wx.BoxSizer(wx.VERTICAL) + self.present_panel.SetBackgroundColour(wx.BLUE) + self.present_panel.presents = ContactList(self.present_panel, self.host, type='nicks') + self.present_panel.presents.SetMinSize(wx.Size(80,20)) + self.present_panel.sizer.Add(self.present_panel.presents, 1, wx.EXPAND) + self.present_panel.SetSizer(self.present_panel.sizer) + self.splitter.SplitVertically(self.present_panel, self.conv_panel, 80) + + def setType(self, type): + QuickChat.setType(self, type) + if type is 'group' and not self.splitter.IsSplit(): + self.__createPresents() + elif type is 'one2one' and self.splitter.IsSplit(): + self.splitter.Unsplit(self.present_panel) + del self.present_panel + + def setPresents(self, nicks): + """Set the users presents in the contact list for a group chat + @param nicks: list of nicknames + """ + debug (_("Adding users %s to room") % nicks) + if self.type != "group": + error (_("[INTERNAL] trying to set presents nicks for a non group chat window")) + return + for nick in nicks: + self.present_panel.presents.replace(nick) + def createMenus(self): info("Creating menus") actionMenu = wx.Menu() @@ -86,7 +122,7 @@ def onEnterPressed(self, event): """Behaviour when enter pressed in send line.""" - self.host.bridge.sendMessage(self.to_jid, event.GetString(), profile_key=self.host.profile) + self.host.bridge.sendMessage(self.target, event.GetString(), profile_key=self.host.profile) self.textBox.Clear() @@ -111,7 +147,7 @@ filename = wx.FileSelector(_("Choose a file to send"), flags = wx.FD_FILE_MUST_EXIST) if filename: debug(_("filename: %s"),filename) - full_jid = self.host.CM.get_full(self.to_jid) + full_jid = self.host.CM.get_full(self.target) id = self.host.bridge.sendFile(full_jid, filename) self.host.waitProgress(id, _("File Transfer"), _("Copying %s") % os.path.basename(filename)) diff -r efe81b61673c -r f271fff3a713 frontends/wix/constants.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/frontends/wix/constants.py Sun Mar 21 10:28:55 2010 +1100 @@ -0,0 +1,13 @@ +import sys +import __builtin__ + +__builtin__.__dict__['IMAGE_DIR'] = sys.path[0]+'/images' + +__builtin__.__dict__['msgOFFLINE'] = _("offline") +__builtin__.__dict__['msgONLINE'] = _("online") +__builtin__.__dict__['const_DEFAULT_GROUP'] = "Unclassed" +__builtin__.__dict__['const_STATUS'] = [("", _("Online"), None), + ("chat", _("Free for chat"), "green"), + ("away", _("AFK"), "brown"), + ("dnd", _("DND"), "red"), + ("xa", _("Away"), "red")] diff -r efe81b61673c -r f271fff3a713 frontends/wix/contact_list.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/frontends/wix/contact_list.py Sun Mar 21 10:28:55 2010 +1100 @@ -0,0 +1,179 @@ +import wx +from quick_frontend.quick_contact_list import QuickContactList +from logging import debug, info, error +from cgi import escape +from tools.jid import JID + + +class Group(str): + """Class used to recognize groups""" + +class Contact(str): + """Class used to recognize groups""" + +class ContactList(wx.SimpleHtmlListBox, QuickContactList): + """Customized control to manage contacts.""" + + def __init__(self, parent, host, type="JID"): + """init the contact list + @param parent: WxWidgets parent of the widget + @param host: wix main app class + @param type: type of contact list: "JID" for the usual big jid contact list + "CUSTOM" for a customized contact list (self.__presentItem must then be overrided) + """ + wx.SimpleHtmlListBox.__init__(self, parent, -1) + QuickContactList.__init__(self, host.CM) + self.host = host + self.type = type + self.__typeSwitch() + self.groups = {} #list contacts in each groups, key = group + self.Bind(wx.EVT_LISTBOX, self.onSelected) + self.Bind(wx.EVT_LISTBOX_DCLICK, self.onActivated) + + def __typeSwitch(self): + if self.type == "JID": + self.__presentItem = self.__presentItemJID + elif type != "CUSTOM": + self.__presentItem = self.__presentItemDefault + + def __find_idx(self, entity): + """Find indexes of given contact (or groups) in contact list, manage jid + @return: list of indexes""" + result=[] + for i in range(self.GetCount()): + if (type(entity) == JID and type(self.GetClientData(i)) == JID and self.GetClientData(i).short == entity.short) or\ + self.GetClientData(i) == entity: + result.append(i) + return result + + def replace(self, contact, groups=None): + debug(_("update %s") % contact) + if not self.__find_idx(contact): + self.add(contact, groups) + else: + for i in self.__find_idx(contact): + self.SetString(i, self.__presentItem(contact)) + + def disconnect(self, contact): + self.remove(contact) #for now, we only show online contacts + + def __eraseGroup(self, group): + """Erase all contacts in group + @param group: group to erase + @return: True if something as been erased""" + erased = False + indexes = self.__find_idx(group) + for idx in indexes: + while idx + + + + + %s %s
+ %s + + + """ % (avatar, + escape(nick or name or jid.node or jid.short), + show_html, + escape(status)) + + return html + + def clear_contacts(self): + """Clear all the contact list""" + self.Clear() + + def add(self, contact, groups = None): + """add a contact to the list""" + debug (_("adding %s"),contact) + #gof: groups = param_groups or self.CM.getAttr(jid, 'groups') + if not groups: + idx = self.Insert(self.__presentItem(contact), 0, contact) + else: + for group in groups: + indexes = self.__find_idx(group) + gp_idx = 0 + if not indexes: #this is a new group, we have to create it + gp_idx = self.Append(self.__presentGroup(group), Group(group)) + else: + gp_idx = indexes[0] + + self.Insert(self.__presentItem(contact), gp_idx+1, contact) + + + + def remove(self, contact): + """remove a contact from the list""" + debug (_("removing %s"), contact) + list_idx = self.__find_idx(contact) + list_idx.reverse() #we me make some deletions, we have to reverse the order + for i in list_idx: + self.Delete(i) + + def onSelected(self, event): + """Called when a contact is selected.""" + data = self.getSelection() + if data == None: #we have a group + group = self.GetClientData(self.GetSelection()) + erased = self.__eraseGroup(group) + if not erased: #the group was already erased, we can add again the contacts + contacts = self.CM.getContFromGroup(group) + contacts.sort() + id_insert = self.GetSelection()+1 + for contact in contacts: + self.Insert(self.__presentItem(contact), id_insert, contact) + self.SetSelection(wx.NOT_FOUND) + event.Skip(False) + else: + event.Skip() + + def onActivated(self, event): + """Called when a contact is clicked or activated with keyboard.""" + data = self.getSelection() + self.onActivatedCB(data) + event.Skip() + + def getSelection(self): + """Return the selected contact, or an empty string if there is not""" + if self.GetSelection() == wx.NOT_FOUND: + return None + data = self.GetClientData(self.GetSelection()) + if type(data) == Group: + return None + return data + + def registerActivatedCB(self, cb): + """Register a callback with manage contact activation.""" + self.onActivatedCB=cb + diff -r efe81b61673c -r f271fff3a713 frontends/wix/main_window.py --- a/frontends/wix/main_window.py Sat Mar 06 14:57:23 2010 +1100 +++ b/frontends/wix/main_window.py Sun Mar 21 10:28:55 2010 +1100 @@ -22,9 +22,9 @@ from quick_frontend.quick_chat_list import QuickChatList from quick_frontend.quick_app import QuickApp -from quick_frontend.quick_contact_list import QuickContactList from quick_frontend.quick_contact_management import QuickContactManagement import wx +from contact_list import ContactList from chat import Chat from param import Param from form import Form @@ -35,14 +35,9 @@ import os.path import pdb from tools.jid import JID -from logging import debug, info, error -from cgi import escape -import sys +from logging import debug, info, warning, error +import constants -IMAGE_DIR = sys.path[0]+'/images' - -msgOFFLINE = "offline" -msgONLINE = "online" idCONNECT,\ idDISCONNECT,\ idEXIT,\ @@ -50,13 +45,8 @@ idADD_CONTACT,\ idREMOVE_CONTACT,\ idSHOW_PROFILE,\ -idFIND_GATEWAYS = range(8) -const_DEFAULT_GROUP = "Unclassed" -const_STATUS = [("", _("Online"), None), - ("chat", _("Free for chat"), "green"), - ("away", _("AFK"), "brown"), - ("dnd", _("DND"), "red"), - ("xa", _("Away"), "red")] +idJOIN_ROOM,\ +idFIND_GATEWAYS = range(9) class ChatList(QuickChatList): """This class manage the list of chat windows""" @@ -67,155 +57,6 @@ def createChat(self, target): return Chat(target, self.host) - -class ContactList(wx.SimpleHtmlListBox, QuickContactList): - """Customized control to manage contacts.""" - - def __init__(self, parent, CM): - wx.SimpleHtmlListBox.__init__(self, parent, -1) - QuickContactList.__init__(self, CM) - self.host = parent - self.groups = {} #list contacts in each groups, key = group - self.Bind(wx.EVT_LISTBOX, self.onSelected) - self.Bind(wx.EVT_LISTBOX_DCLICK, self.onActivated) - - def __find_idx(self, entity, reverse=False): - """Find indexes of given jid (or groups) in contact list - @return: list of indexes""" - result=[] - for i in range(self.GetCount()): - if (type(entity) == JID and type(self.GetClientData(i)) == JID and self.GetClientData(i).short == entity.short) or\ - self.GetClientData(i) == entity: - result.append(i) - return result - - def replace(self, jid): - debug(_("update %s") % jid) - if not self.__find_idx(jid): - self.add(jid) - else: - for i in self.__find_idx(jid): - self.SetString(i, self.__presentItem(jid)) - - def disconnect(self, jid): - self.remove(jid) #for now, we only show online contacts - - def __eraseGroup(self, group): - """Erase all contacts in group - @param group: group to erase - @return: True if something as been erased""" - erased = False - indexes = self.__find_idx(group) - for idx in indexes: - while idx - - - - - %s %s
- %s - - - """ % (avatar, - escape(nick or name or jid.node or jid.short), - show_html, - escape(status)) - - return html - - def clear_contacts(self): - """Clear all the contact list""" - self.Clear() - - def add(self, jid): - """add a contact to the list""" - debug (_("adding %s"),jid) - groups = self.CM.getAttr(jid, 'groups') - if not groups: - idx = self.Append(self.__presentItem(jid), jid) - else: - for group in groups: - indexes = self.__find_idx(group) - gp_idx = 0 - if not indexes: #this is a new group, we have to create it - gp_idx = self.Append(self.__presentGroup(group), group) - else: - gp_idx = indexes[0] - - self.Insert(self.__presentItem(jid), gp_idx+1, jid) - - - - def remove(self, jid): - """remove a contact from the list""" - debug (_("removing %s"),jid) - list_idx = self.__find_idx(jid) - list_idx.reverse() #we me make some deletions, we have to reverse the order - for i in list_idx: - self.Delete(i) - - def onSelected(self, event): - """Called when a contact is selected.""" - data = self.getSelection() - if type(data) == JID: - event.Skip() - else: - group = self.GetClientData(self.GetSelection()) - erased = self.__eraseGroup(group) - if not erased: #the group was already erased, we can add again the contacts - contacts = self.CM.getContFromGroup(group) - contacts.sort() - id_insert = self.GetSelection()+1 - for contact in contacts: - self.Insert(self.__presentItem(contact), id_insert, contact) - self.SetSelection(wx.NOT_FOUND) - event.Skip(False) - - def onActivated(self, event): - """Called when a contact is clicked or activated with keyboard.""" - data = self.getSelection() - self.onActivatedCB(data) - event.Skip() - - def getSelection(self): - """Return the selected contact, or an empty string if there is not""" - if self.GetSelection() == wx.NOT_FOUND: - return None - data = self.GetClientData(self.GetSelection()) - if type(data) != JID: - return None - return data - - def registerActivatedCB(self, cb): - """Register a callback with manage contact activation.""" - self.onActivatedCB=cb - class MainWindow(wx.Frame, QuickApp): """main app window""" @@ -228,7 +69,7 @@ self.SetSizer(self.sizer) #Frame elements - self.contactList = ContactList(self, self.CM) + self.contactList = ContactList(self, self) self.contactList.registerActivatedCB(self.onContactActivated) self.contactList.Hide() self.sizer.Add(self.contactList, 1, flag=wx.EXPAND) @@ -296,6 +137,7 @@ contactMenu.AppendSeparator() contactMenu.Append(idSHOW_PROFILE, _("&Show profile"), _(" Show contact's profile")) communicationMenu = wx.Menu() + communicationMenu.Append(idJOIN_ROOM, _("&Join Room"),_(" Join a Multi-User Chat room")) communicationMenu.Append(idFIND_GATEWAYS, _("&Find Gateways"),_(" Find gateways to legacy IM")) self.menuBar = wx.MenuBar() self.menuBar.Append(connectMenu,_("&General")) @@ -311,12 +153,18 @@ wx.EVT_MENU(self, idADD_CONTACT, self.onAddContact) wx.EVT_MENU(self, idREMOVE_CONTACT, self.onRemoveContact) wx.EVT_MENU(self, idSHOW_PROFILE, self.onShowProfile) + wx.EVT_MENU(self, idJOIN_ROOM, self.onJoinRoom) wx.EVT_MENU(self, idFIND_GATEWAYS, self.onFindGateways) def newMessage(self, from_jid, msg, type, to_jid, profile): QuickApp.newMessage(self, from_jid, msg, type, to_jid, profile) + def roomJoined(self, room_id, room_service, room_nicks, user_nick, profile): + super(MainWindow, self).roomJoined(room_id, room_service, room_nicks, user_nick, profile) + self.chat_wins[room_id+'@'+room_service].setType("group") + self.chat_wins[room_id+'@'+room_service].setPresents([user_nick]+room_nicks) + def showAlert(self, message): # TODO: place this in a separate class popup=wx.PopupWindow(self) @@ -552,6 +400,9 @@ debug (_('Profile received: [%s]') % data) profile=Profile(self, data) + def onJoinRoom(self, e): + warning('FIXME: temporary menu, must be improved') + self.bridge.joinMUC("conference.necton2.int", "test", "Goffi \o/", self.profile) def onFindGateways(self, e): debug(_("Find Gateways request")) diff -r efe81b61673c -r f271fff3a713 frontends/wix/profile_manager.py --- a/frontends/wix/profile_manager.py Sat Mar 06 14:57:23 2010 +1100 +++ b/frontends/wix/profile_manager.py Sun Mar 21 10:28:55 2010 +1100 @@ -132,7 +132,6 @@ if name[0]=='@': wx.MessageDialog(self, _("A profile name can't start with a @"), _("Bad profile name"), wx.ICON_ERROR).ShowModal() return - profile = None # gof profile = self.host.bridge.getProfileName(name) if not profile: debug(_("The profile is new, we create it")) @@ -141,10 +140,11 @@ new_jid = self.login_jid.GetValue() new_pass = self.login_pass.GetValue() if old_jid != new_jid: - debug(_('Saving new JID')) + debug(_('Saving new JID and server')) self.host.bridge.setParam("JabberID", new_jid, "Connection", profile) + self.host.bridge.setParam("Server", JID(new_jid).domain, "Connection", profile) if old_pass != new_pass: debug(_('Saving new password')) - self.host.bridge.setParam("JabberID", new_pass, "Connection", profile) + self.host.bridge.setParam("Password", new_pass, "Connection", profile) self.host.plug_profile(name) diff -r efe81b61673c -r f271fff3a713 plugins/plugin_xep_0045.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/plugin_xep_0045.py Sun Mar 21 10:28:55 2010 +1100 @@ -0,0 +1,133 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +""" +SAT plugin for managing xep-0045 +Copyright (C) 2009, 2010 Jérôme Poisson (goffi@goffi.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, either version 3 of the License, or +(at your option) any later version. + +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 . +""" + +from logging import debug, info, warning, error +from twisted.words.xish import domish +from twisted.internet import protocol, defer, threads, reactor +from twisted.words.protocols.jabber import client, jid, xmlstream +from twisted.words.protocols.jabber import error as jab_error +from twisted.words.protocols.jabber.xmlstream import IQ +import os.path +import pdb + +from zope.interface import implements + +from wokkel import disco, iwokkel, muc + +from base64 import b64decode +from hashlib import sha1 +from time import sleep + +try: + from twisted.words.protocols.xmlstream import XMPPHandler +except ImportError: + from wokkel.subprotocols import XMPPHandler + +AVATAR_PATH = "/avatars" + +IQ_GET = '/iq[@type="get"]' +NS_VCARD = 'vcard-temp' +VCARD_REQUEST = IQ_GET + '/vCard[@xmlns="' + NS_VCARD + '"]' #TODO: manage requests + +PRESENCE = '/presence' +NS_VCARD_UPDATE = 'vcard-temp:x:update' +VCARD_UPDATE = PRESENCE + '/x[@xmlns="' + NS_VCARD_UPDATE + '"]' + +PLUGIN_INFO = { +"name": "XEP 0045 Plugin", +"import_name": "XEP_0045", +"type": "XEP", +"protocols": ["XEP-0045"], +"dependencies": [], +"main": "XEP_0045", +"handler": "yes", +"description": _("""Implementation of Multi-User Chat""") +} + +class XEP_0045(): + + def __init__(self, host): + info(_("Plugin XEP_0045 initialization")) + self.host = host + self.clients={} + host.bridge.addMethod("joinMUC", ".communication", in_sign='ssss', out_sign='', method=self.join) + + def __check_profile(self, profile): + if not profile or not self.clients.has_key(profile) or not self.host.isConnected(profile): + error (_('Unknown or disconnected profile')) + if self.clients.has_key(profile): + del self.clients[profile] + return False + return True + + def __room_joined(self, room, profile): + """Called when the user is in the requested room""" + print "room joined (profile = %s)" % profile + room_jid = room.roomIdentifier+'@'+room.service + self.clients[profile].joined_rooms[room_jid] = room + self.host.bridge.roomJoined(room.roomIdentifier, room.service, room.roster.keys(), room.nick, profile) + + def __err_joining_room(self, failure, profile): #, profile): + """Called when something is going wrong when joining the room""" + error ("Error when joining the room") + pdb.set_trace() + + def join(self, service, roomId, nick, profile_key='@DEFAULT@'): + profile = self.host.memory.getProfileName(profile_key) + if not self.__check_profile(profile): + return + room_jid = roomId+'@'+service + if self.clients[profile].joined_rooms.has_key(room_jid): + warning(_('%(profile)s is already in room %(room_jid)s') % {'profile':profile, 'room_jid':room_jid}) + return + info (_("[%(profile)s] is joining room %(room)s with nick %(nick)s") % {'profile':profile,'room':roomId+'@'+service, 'nick':nick}) + self.clients[profile].join(service, roomId, nick).addCallbacks(self.__room_joined, self.__err_joining_room, callbackKeywords={'profile':profile}, errbackKeywords={'profile':profile}) + + def getHandler(self, profile): + #reactor.callLater(15,self.join,"conference.necton2.int", "test", "Goffi \o/", profile) + self.clients[profile] = SatMUCClient(self) + return self.clients[profile] + + + +class SatMUCClient (muc.MUCClient): + #implements(iwokkel.IDisco) + + def __init__(self, plugin_parent): + self.plugin_parent = plugin_parent + self.host = plugin_parent.host + muc.MUCClient.__init__(self) + self.joined_rooms = {} #FIXME gof: check if necessary + print "init SatMUCClient OK" + + def receivedGroupChat(self, room, user, body): + debug('receivedGroupChat: room=%s user=%s body=%s', room, user, body) + + + #def connectionInitialized(self): + #pass + + #def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + #return [disco.DiscoFeature(NS_VCARD)] + + #def getDiscoItems(self, requestor, target, nodeIdentifier=''): + #return [] + diff -r efe81b61673c -r f271fff3a713 plugins/plugin_xep_0054.py --- a/plugins/plugin_xep_0054.py Sat Mar 06 14:57:23 2010 +1100 +++ b/plugins/plugin_xep_0054.py Sun Mar 21 10:28:55 2010 +1100 @@ -75,7 +75,7 @@ host.bridge.addMethod("getAvatarFile", ".communication", in_sign='s', out_sign='s', method=self.getAvatarFile) host.bridge.addMethod("getCardCache", ".communication", in_sign='s', out_sign='a{ss}', method=self.getCardCache) - def getHandler(self): + def getHandler(self, profile): return XEP_0054_handler(self) def update_cache(self, jid, name, value): diff -r efe81b61673c -r f271fff3a713 plugins/plugin_xep_0065.py --- a/plugins/plugin_xep_0065.py Sat Mar 06 14:57:23 2010 +1100 +++ b/plugins/plugin_xep_0065.py Sun Mar 21 10:28:55 2010 +1100 @@ -480,7 +480,7 @@ info(_("Launching Socks5 Stream server on port %d"), port) reactor.listenTCP(port, self.server_factory) - def getHandler(self): + def getHandler(self, profile): return XEP_0065_handler(self) def getExternalIP(self): diff -r efe81b61673c -r f271fff3a713 plugins/plugin_xep_0096.py --- a/plugins/plugin_xep_0096.py Sat Mar 06 14:57:23 2010 +1100 +++ b/plugins/plugin_xep_0096.py Sun Mar 21 10:28:55 2010 +1100 @@ -60,7 +60,7 @@ self._waiting_for_approval = {} host.bridge.addMethod("sendFile", ".communication", in_sign='sss', out_sign='s', method=self.sendFile) - def getHandler(self): + def getHandler(self, profile): return XEP_0096_handler(self) def xep_96(self, IQ, profile): diff -r efe81b61673c -r f271fff3a713 sat.tac --- a/sat.tac Sat Mar 06 14:57:23 2010 +1100 +++ b/sat.tac Sun Mar 21 10:28:55 2010 +1100 @@ -185,7 +185,7 @@ self.host = host def availableReceived(self, entity, show=None, statuses=None, priority=0): - info (_("presence update for [%s]"), entity) + debug (_("presence update for [%(entity)s] (available, show=%(show)s statuses=%(statuses)s priority=%(priority)d)") % {'entity':entity, 'show':show, 'statuses':statuses, 'priority':priority}) if statuses.has_key(None): #we only want string keys statuses["default"] = statuses[None] @@ -199,6 +199,7 @@ int(priority), statuses, self.parent.profile) def unavailableReceived(self, entity, statuses=None): + debug (_("presence update for [%(entity)s] (unavailable, statuses=%(statuses)s)") % {'entity':entity, 'statuses':statuses}) if statuses and statuses.has_key(None): #we only want string keys statuses["default"] = statuses[None] del statuses[None] @@ -416,7 +417,7 @@ for plugin in self.plugins.iteritems(): if plugin[1].is_handler: - plugin[1].getHandler().setHandlerParent(current) + plugin[1].getHandler(profile).setHandlerParent(current) current.startService() diff -r efe81b61673c -r f271fff3a713 sat_bridge/DBus.py --- a/sat_bridge/DBus.py Sat Mar 06 14:57:23 2010 +1100 +++ b/sat_bridge/DBus.py Sun Mar 21 10:28:55 2010 +1100 @@ -69,6 +69,11 @@ debug("presence update signal (from:%s show:%s priority:%d statuses:%s profile:%s) sended" , entity, show, priority, statuses, profile) @dbus.service.signal(const_INT_PREFIX+const_COMM_SUFFIX, + signature='ssasss') + def roomJoined(self, room_id, room_service, room_nicks, user_nick, profile): + debug("room joined signal: id:%(room_id)s service:%(room_service)s nicks:%(room_nicks)s user:%(user_nick)s profile=%(profile)s" % {'room_id':room_id, 'room_service':room_service, 'room_nicks':room_nicks, 'user_nick':user_nick, 'profile':profile}) + + @dbus.service.signal(const_INT_PREFIX+const_COMM_SUFFIX, signature='sss') def subscribe(self, type, entity, profile): debug("subscribe (type: [%s] from:[%s] profile:[%s])" , type, entity, profile) @@ -307,6 +312,9 @@ def presenceUpdate(self, entity, show, priority, statuses, profile): debug("updating presence for %s",entity) self.dbus_bridge.presenceUpdate(entity, show, priority, statuses, profile) + + def roomJoined(self, room_id, room_service, room_nicks, user_nick, profile): + self.dbus_bridge.roomJoined(room_id, room_service, room_nicks, user_nick, profile) def subscribe(self, type, entity, profile): debug("subscribe request for %s",entity)