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)
This commit is contained in:
Goffi 2010-03-21 10:28:55 +11:00
commit 724a97b14d
15 changed files with 432 additions and 194 deletions

View file

@ -42,6 +42,7 @@ class QuickApp():
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 @@ class QuickApp():
## 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 @@ class QuickApp():
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 @@ class QuickApp():
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"""

View file

@ -26,14 +26,21 @@ from tools.jid import JID
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:

View file

@ -49,6 +49,6 @@ class QuickContactList():
"""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

View file

@ -110,6 +110,9 @@ class DBusBridgeFrontend(BridgeFrontend):
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)

View file

@ -27,6 +27,7 @@ import pdb
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 @@ idSEND = 1
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.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.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.createMenus()
#events
@ -61,7 +67,37 @@ class Chat(wx.Frame, QuickChat):
#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 @@ class Chat(wx.Frame, QuickChat):
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 @@ class Chat(wx.Frame, QuickChat):
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))

View file

@ -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")]

View file

@ -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<self.GetCount()-1 and type(self.GetClientData(idx+1)) != Group:
erased = True
self.Delete(idx+1)
return erased
def __presentGroup(self, group):
"""Make a nice presentation for the contact groups"""
html = """-- [%s] --""" % group
return html
def __presentItemDefault(self, contact):
"""Make a basic presentation of string contacts in the list."""
return contact
def __presentItemJID(self, jid):
"""Make a nice presentation of the contact in the list for JID contacts."""
name = self.CM.getAttr(jid,'name')
nick = self.CM.getAttr(jid,'nick')
show = filter(lambda x:x[0]==self.CM.getAttr(jid,'show'), const_STATUS)[0]
#show[0]==shortcut
#show[1]==human readable
#show[2]==color (or None)
show_html = "<font color='%s'>[%s]</font>" % (show[2], show[1]) if show[2] else ""
status = self.CM.getAttr(jid,'status') or ''
avatar = self.CM.getAttr(jid,'avatar') or IMAGE_DIR+'/empty_avatar.png'
#XXX: yes table I know :) but wxHTML* doesn't support CSS
html = """
<table border='0'>
<td>
<img height='64' width='64' src='%s' />
</td>
<td>
<b>%s</b> %s<br />
<i>%s</i>
</td>
</table>
""" % (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

View file

@ -22,9 +22,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
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 gobject
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 @@ idPARAM,\
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 @@ class ChatList(QuickChatList):
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<self.GetCount()-1 and type(self.GetClientData(idx+1)) == JID:
erased = True
self.Delete(idx+1)
return erased
def __presentGroup(self, group):
"""Make a nice presentation for the contact groups"""
html = """-- [%s] --""" % group
return html
def __presentItem(self, jid):
"""Make a nice presentation of the contact in the list."""
name = self.CM.getAttr(jid,'name')
nick = self.CM.getAttr(jid,'nick')
show = filter(lambda x:x[0]==self.CM.getAttr(jid,'show'), const_STATUS)[0]
#show[0]==shortcut
#show[1]==human readable
#show[2]==color (or None)
show_html = "<font color='%s'>[%s]</font>" % (show[2], show[1]) if show[2] else ""
status = self.CM.getAttr(jid,'status') or ''
avatar = self.CM.getAttr(jid,'avatar') or IMAGE_DIR+'/empty_avatar.png'
#XXX: yes table I know :) but wxHTML* doesn't support CSS
html = """
<table border='0'>
<td>
<img height='64' width='64' src='%s' />
</td>
<td>
<b>%s</b> %s<br />
<i>%s</i>
</td>
</table>
""" % (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 @@ class MainWindow(wx.Frame, QuickApp):
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 @@ class MainWindow(wx.Frame, QuickApp):
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 @@ class MainWindow(wx.Frame, QuickApp):
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 @@ class MainWindow(wx.Frame, QuickApp):
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"))

View file

@ -132,7 +132,6 @@ class ProfileManager(wx.Panel):
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 @@ class ProfileManager(wx.Panel):
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)

133
plugins/plugin_xep_0045.py Executable file
View file

@ -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 <http://www.gnu.org/licenses/>.
"""
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 []

View file

@ -75,7 +75,7 @@ class XEP_0054():
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):

View file

@ -480,7 +480,7 @@ class XEP_0065():
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):

View file

@ -60,7 +60,7 @@ class XEP_0096():
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):

View file

@ -185,7 +185,7 @@ class SatPresenceProtocol(xmppim.PresenceClientProtocol):
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 @@ class SatPresenceProtocol(xmppim.PresenceClientProtocol):
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 @@ class SAT(service.Service):
for plugin in self.plugins.iteritems():
if plugin[1].is_handler:
plugin[1].getHandler().setHandlerParent(current)
plugin[1].getHandler(profile).setHandlerParent(current)
current.startService()

View file

@ -68,6 +68,11 @@ class DbusObject(dbus.service.Object):
def presenceUpdate(self, entity, show, priority, statuses, profile):
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):
@ -307,6 +312,9 @@ class DBusBridge(Bridge):
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)