comparison src/plugins/plugin_xep_0045.py @ 507:f98bef71a918

frontends, core, plugin XEP-0045: leave implementation + better nick change - memory: individual entity cache can be deleted - plugin XEP-0045: nick change are now detected and userChangedNick signal is sent instead of joined/left - plugin XEP-0045: leave implementation - frontends: userChangedNick signal management - Primitivus: an alert is shown in notification bar in case of error in sendMessage
author Goffi <goffi@goffi.org>
date Fri, 28 Sep 2012 00:26:24 +0200
parents 2402668b5d05
children 64ff046dc201
comparison
equal deleted inserted replaced
506:2e43c74815ad 507:f98bef71a918
18 You should have received a copy of the GNU Affero General Public License 18 You should have received a copy of the GNU Affero General Public License
19 along with this program. If not, see <http://www.gnu.org/licenses/>. 19 along with this program. If not, see <http://www.gnu.org/licenses/>.
20 """ 20 """
21 21
22 from logging import debug, info, warning, error 22 from logging import debug, info, warning, error
23 from twisted.words.xish import domish 23 from twisted.internet import defer
24 from twisted.internet import protocol, defer, threads, reactor 24 from twisted.words.protocols.jabber import jid
25 from twisted.words.protocols.jabber import client, jid, xmlstream
26 from twisted.words.protocols.jabber import error as jab_error
27 from twisted.words.protocols.jabber.xmlstream import IQ
28 25
29 from sat.core import exceptions 26 from sat.core import exceptions
30 27
31 import os.path
32 import uuid 28 import uuid
33 29
34 from zope.interface import implements 30 from wokkel import muc
35 31
36 from wokkel import disco, iwokkel, muc
37
38 from base64 import b64decode
39 from hashlib import sha1
40 from time import sleep
41 32
42 try: 33 try:
43 from twisted.words.protocols.xmlstream import XMPPHandler 34 from twisted.words.protocols.xmlstream import XMPPHandler
44 except ImportError: 35 except ImportError:
45 from wokkel.subprotocols import XMPPHandler 36 from wokkel.subprotocols import XMPPHandler
63 def __init__(self, host): 54 def __init__(self, host):
64 info(_("Plugin XEP_0045 initialization")) 55 info(_("Plugin XEP_0045 initialization"))
65 self.host = host 56 self.host = host
66 self.clients={} 57 self.clients={}
67 host.bridge.addMethod("joinMUC", ".plugin", in_sign='ssa{ss}s', out_sign='', method=self._join) 58 host.bridge.addMethod("joinMUC", ".plugin", in_sign='ssa{ss}s', out_sign='', method=self._join)
68 host.bridge.addMethod("changeNick", ".plugin", in_sign='sss', out_sign='', method=self.changeNick) 59 host.bridge.addMethod("mucNick", ".plugin", in_sign='sss', out_sign='', method=self.mucNick)
60 host.bridge.addMethod("mucLeave", ".plugin", in_sign='ss', out_sign='', method=self.leave)
69 host.bridge.addMethod("getRoomsJoined", ".plugin", in_sign='s', out_sign='a(sass)', method=self.getRoomsJoined) 61 host.bridge.addMethod("getRoomsJoined", ".plugin", in_sign='s', out_sign='a(sass)', method=self.getRoomsJoined)
70 host.bridge.addMethod("getRoomsSubjects", ".plugin", in_sign='s', out_sign='a(ss)', method=self.getRoomsSubjects) 62 host.bridge.addMethod("getRoomsSubjects", ".plugin", in_sign='s', out_sign='a(ss)', method=self.getRoomsSubjects)
71 host.bridge.addMethod("getUniqueRoomName", ".plugin", in_sign='s', out_sign='s', method=self.getUniqueName) 63 host.bridge.addMethod("getUniqueRoomName", ".plugin", in_sign='s', out_sign='s', method=self.getUniqueName)
72 host.bridge.addSignal("roomJoined", ".plugin", signature='sasss') #args: room_jid, room_nicks, user_nick, profile 64 host.bridge.addSignal("roomJoined", ".plugin", signature='sasss') #args: room_jid, room_nicks, user_nick, profile
65 host.bridge.addSignal("roomLeft", ".plugin", signature='ss') #args: room_jid, profile
73 host.bridge.addSignal("roomUserJoined", ".plugin", signature='ssa{ss}s') #args: room_jid, user_nick, user_data, profile 66 host.bridge.addSignal("roomUserJoined", ".plugin", signature='ssa{ss}s') #args: room_jid, user_nick, user_data, profile
74 host.bridge.addSignal("roomUserLeft", ".plugin", signature='ssa{ss}s') #args: room_jid, user_nick, user_data, profile 67 host.bridge.addSignal("roomUserLeft", ".plugin", signature='ssa{ss}s') #args: room_jid, user_nick, user_data, profile
68 host.bridge.addSignal("roomUserChangedNick", ".plugin", signature='ssss') #args: room_jid, old_nick, new_nick, profile
75 host.bridge.addSignal("roomNewSubject", ".plugin", signature='sss') #args: room_jid, subject, profile 69 host.bridge.addSignal("roomNewSubject", ".plugin", signature='sss') #args: room_jid, subject, profile
70
76 71
77 def __check_profile(self, profile): 72 def __check_profile(self, profile):
78 """check if profile is used and connected 73 """check if profile is used and connected
79 if profile known but disconnected, remove it from known profiles 74 if profile known but disconnected, remove it from known profiles
80 @param profile: profile to check 75 @param profile: profile to check
177 self.host.bridge.newAlert(mess, _("Group chat error"), "ERROR", profile) 172 self.host.bridge.newAlert(mess, _("Group chat error"), "ERROR", profile)
178 return 173 return
179 d = self.join(room_jid, nick, options, profile) 174 d = self.join(room_jid, nick, options, profile)
180 d.addErrback(lambda x: warning(_('Error while joining room'))) #TODO: error management + signal in bridge 175 d.addErrback(lambda x: warning(_('Error while joining room'))) #TODO: error management + signal in bridge
181 176
182 def nick(self, room_jid, nick, profile_key='@DEFAULT@'): 177 def nick(self, room_jid, nick, profile_key):
183 profile = self.host.memory.getProfileName(profile_key) 178 profile = self.host.memory.getProfileName(profile_key)
184 if not self.__check_profile(profile): 179 if not self.__check_profile(profile):
185 raise exceptions.UnknownProfileError("Unknown or disconnected profile") 180 raise exceptions.UnknownProfileError("Unknown or disconnected profile")
186 if not self.clients[profile].joined_rooms.has_key(room_jid.userhost()): 181 if not self.clients[profile].joined_rooms.has_key(room_jid.userhost()):
187 raise UnknownRoom("This room has not been joined") 182 raise UnknownRoom("This room has not been joined")
188 return self.clients[profile].nick(room_jid, nick) 183 return self.clients[profile].nick(room_jid, nick)
189 184
190 def changeNick(self, room_jid_s, nick, profile_key='@DEFAULT@'): 185 def leave(self, room_jid, profile_key):
186 profile = self.host.memory.getProfileName(profile_key)
187 if not self.__check_profile(profile):
188 raise exceptions.UnknownProfileError("Unknown or disconnected profile")
189 if not self.clients[profile].joined_rooms.has_key(room_jid.userhost()):
190 raise UnknownRoom("This room has not been joined")
191 return self.clients[profile].leave(room_jid)
192
193 def mucNick(self, room_jid_s, nick, profile_key='@DEFAULT@'):
191 """Change nickname in a room""" 194 """Change nickname in a room"""
192 return self.nick(jid.JID(room_jid_s), nick, profile_key) 195 return self.nick(jid.JID(room_jid_s), nick, profile_key)
196
197 def mucLeave(self, room_jid_s, profile_key='@DEFAULT@'):
198 """Leave a room"""
199 return self.leave(jid.JID(room_jid_s), profile_key)
193 200
194 def getHandler(self, profile): 201 def getHandler(self, profile):
195 self.clients[profile] = SatMUCClient(self) 202 self.clients[profile] = SatMUCClient(self)
196 return self.clients[profile] 203 return self.clients[profile]
197 204
204 self.plugin_parent = plugin_parent 211 self.plugin_parent = plugin_parent
205 self.host = plugin_parent.host 212 self.host = plugin_parent.host
206 muc.MUCClient.__init__(self) 213 muc.MUCClient.__init__(self)
207 self.joined_rooms = {} 214 self.joined_rooms = {}
208 self.rec_subjects = {} 215 self.rec_subjects = {}
216 self.__changing_nicks = set() # used to keep trace of who is changing nick,
217 # and to discard userJoinedRoom signal in this case
209 print "init SatMUCClient OK" 218 print "init SatMUCClient OK"
210 219
220 def unavailableReceived(self, presence):
221 #XXX: we override this method to manage nickname change
222 #TODO: feed this back to Wokkel
223 """
224 Unavailable presence was received.
225
226 If this was received from a MUC room occupant JID, that occupant has
227 left the room.
228 """
229 room, user = self._getRoomUser(presence)
230
231 if room is None or user is None:
232 return
233
234 room.removeUser(user)
235
236 if muc.STATUS_CODE.NEW_NICK in presence.mucStatuses:
237 self.__changing_nicks.add(presence.nick)
238 self.userChangedNick(room, user, presence.nick)
239 else:
240 self.__changing_nicks.discard(presence.nick)
241 self.userLeftRoom(room, user)
242
211 def receivedGroupChat(self, room, user, body): 243 def receivedGroupChat(self, room, user, body):
212 debug('receivedGroupChat: room=%s user=%s body=%s', room, user, body) 244 debug('receivedGroupChat: room=%s user=%s body=%s', room, user, body)
213 245
214 def userJoinedRoom(self, room, user): 246 def userJoinedRoom(self, room, user):
215 debug (_("user %(nick)s has joined room (%(room_id)s)") % {'nick':user.nick, 'room_id':room.occupantJID.userhost()}) 247 if user.nick in self.__changing_nicks:
216 if not self.host.trigger.point("MUC user joined", room, user, self.parent.profile): 248 self.__changing_nicks.remove(user.nick)
217 return 249 else:
218 user_data={'entity':user.entity.full() if user.entity else '', 'affiliation':user.affiliation, 'role':user.role} 250 debug (_("user %(nick)s has joined room (%(room_id)s)") % {'nick':user.nick, 'room_id':room.occupantJID.userhost()})
219 self.host.bridge.roomUserJoined(room.roomJID.userhost(), user.nick, user_data, self.parent.profile) 251 if not self.host.trigger.point("MUC user joined", room, user, self.parent.profile):
252 return
253 user_data={'entity':user.entity.full() if user.entity else '', 'affiliation':user.affiliation, 'role':user.role}
254 self.host.bridge.roomUserJoined(room.roomJID.userhost(), user.nick, user_data, self.parent.profile)
220 255
221 def userLeftRoom(self, room, user): 256 def userLeftRoom(self, room, user):
222 debug (_("user %(nick)s left room (%(room_id)s)") % {'nick':user.nick, 'room_id':room.occupantJID.userhost()}) 257 if user.nick == room.nick:
223 user_data={'entity':user.entity.full() if user.entity else '', 'affiliation':user.affiliation, 'role':user.role} 258 # we left the room
224 self.host.bridge.roomUserLeft(room.roomJID.userhost(), user.nick, user_data, self.parent.profile) 259 room_jid_s = room.roomJID.userhost()
260 info (_("Room [%(room)s] left (%(profile)s))") % { "room": room_jid_s,
261 "profile": self.parent.profile })
262 self.host.memory.delEntityCache(room.roomJID, self.parent.profile)
263 del self.plugin_parent.clients[self.parent.profile].joined_rooms[room_jid_s]
264 self.host.bridge.roomLeft(room.roomJID.userhost(), self.parent.profile)
265 else:
266 debug (_("user %(nick)s left room (%(room_id)s)") % {'nick':user.nick, 'room_id':room.occupantJID.userhost()})
267 user_data={'entity':user.entity.full() if user.entity else '', 'affiliation':user.affiliation, 'role':user.role}
268 self.host.bridge.roomUserLeft(room.roomJID.userhost(), user.nick, user_data, self.parent.profile)
269
270 def userChangedNick(self, room, user, new_nick):
271 self.host.bridge.roomUserChangedNick(room.roomJID.userhost(), user.nick, new_nick, self.parent.profile)
225 272
226 def userUpdatedStatus(self, room, user, show, status): 273 def userUpdatedStatus(self, room, user, show, status):
227 print("FIXME: MUC status not managed yet") 274 print("FIXME: MUC status not managed yet")
228 #FIXME: gof 275 #FIXME: gof
229 276
230 def receivedSubject(self, room, user, subject): 277 def receivedSubject(self, room, user, subject):
231 debug (_("New subject for room (%(room_id)s): %(subject)s") % {'room_id':room.roomJID.full(),'subject':subject}) 278 debug (_("New subject for room (%(room_id)s): %(subject)s") % {'room_id':room.roomJID.full(),'subject':subject})
232 self.rec_subjects[room.roomJID.userhost()] = (room.roomJID.userhost(), subject) 279 self.rec_subjects[room.roomJID.userhost()] = (room.roomJID.userhost(), subject)
233 self.host.bridge.roomNewSubject(room.roomJID.userhost(), subject, self.parent.profile) 280 self.host.bridge.roomNewSubject(room.roomJID.userhost(), subject, self.parent.profile)
234 281
282
235 #def connectionInitialized(self): 283 #def connectionInitialized(self):
236 #pass 284 #pass
237 285
238 #def getDiscoInfo(self, requestor, target, nodeIdentifier=''): 286 #def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
239 #return [disco.DiscoFeature(NS_VCARD)] 287 #return [disco.DiscoFeature(NS_VCARD)]