comparison src/plugins/plugin_misc_room_game.py @ 1359:83127a4c89ce frontends_multi_profiles

plugins room_game, quiz, radiocol, tarot: use JID instead of unicode in many methods + class attributes
author souliane <souliane@mailoo.org>
date Wed, 11 Mar 2015 12:36:22 +0100
parents 83ed877541e3
children 069ad98b360d
comparison
equal deleted inserted replaced
1358:bf3f669a6052 1359:83127a4c89ce
19 19
20 from sat.core.i18n import _ 20 from sat.core.i18n import _
21 from sat.core.constants import Const as C 21 from sat.core.constants import Const as C
22 from sat.core.log import getLogger 22 from sat.core.log import getLogger
23 log = getLogger(__name__) 23 log = getLogger(__name__)
24 from twisted.words.protocols.jabber.jid import JID 24 from twisted.words.protocols.jabber import jid
25 from twisted.words.xish import domish 25 from twisted.words.xish import domish
26 from twisted.internet import defer 26 from twisted.internet import defer
27 from time import time 27 from time import time
28 from wokkel import disco, iwokkel 28 from wokkel import disco, iwokkel
29 from zope.interface import implements 29 from zope.interface import implements
49 49
50 50
51 class RoomGame(object): 51 class RoomGame(object):
52 """This class is used to help launching a MUC game. 52 """This class is used to help launching a MUC game.
53 53
54 Bridge methods callbacks: prepareRoom, playerReady, createGame 54 Bridge methods callbacks: _prepareRoom, _playerReady, _createGame
55 Triggered methods: userJoinedTrigger, userLeftTrigger 55 Triggered methods: userJoinedTrigger, userLeftTrigger
56 Also called from subclasses: newRound 56 Also called from subclasses: newRound
57 57
58 For examples of messages sequences, please look in sub-classes. 58 For examples of messages sequences, please look in sub-classes.
59 """ 59 """
105 if player_init is None: 105 if player_init is None:
106 player_init = {} 106 player_init = {}
107 self.game_init = game_init 107 self.game_init = game_init
108 self.player_init = player_init 108 self.player_init = player_init
109 self.games = {} 109 self.games = {}
110 self.invitations = {} # list of couple (x, y) with x the time and y a list of users 110 self.invitations = {} # values are a couple (x, y) with x the time and y a list of users
111 111
112 # These are the default settings, which can be overwritten by child class after initialization 112 # These are the default settings, which can be overwritten by child class after initialization
113 self.invite_mode = self.FROM_PLAYERS if self.player_init == {} else self.FROM_NONE 113 self.invite_mode = self.FROM_PLAYERS if self.player_init == {} else self.FROM_NONE
114 self.wait_mode = self.FOR_NONE if self.player_init == {} else self.FOR_ALL 114 self.wait_mode = self.FOR_NONE if self.player_init == {} else self.FOR_ALL
115 self.join_mode = self.INVITED 115 self.join_mode = self.INVITED
124 host.trigger.add("MUC user left", self.userLeftTrigger) 124 host.trigger.add("MUC user left", self.userLeftTrigger)
125 125
126 def _createOrInvite(self, room, other_players, profile): 126 def _createOrInvite(self, room, other_players, profile):
127 """ 127 """
128 This is called only when someone explicitly wants to play. 128 This is called only when someone explicitly wants to play.
129
129 The game will not be created if one already exists in the room, 130 The game will not be created if one already exists in the room,
130 also its creation could be postponed until all the expected players 131 also its creation could be postponed until all the expected players
131 join the room (in that case it will be created from userJoinedTrigger). 132 join the room (in that case it will be created from userJoinedTrigger).
132 @param room: instance of wokkel.muc.Room 133 @param room (wokkel.muc.Room): the room
133 @param other_players: list for other players JID userhosts 134 @param other_players (list[jid.JID]): list of the other players JID (bare)
134 """ 135 """
135 user_jid = self.host.getJidNStream(profile)[0] 136 user_jid = self.host.getJidNStream(profile)[0]
136 room_jid_s = room.occupantJID.userhost() 137 room_jid = room.occupantJID.userhostJID()
137 nick = self.host.plugins["XEP-0045"].getRoomNick(room_jid_s, profile) 138 nick = self.host.plugins["XEP-0045"].getRoomNick(room_jid, profile)
138 nicks = [nick] 139 nicks = [nick]
139 if self._gameExists(room_jid_s): 140 if self._gameExists(room_jid):
140 if not self._checkJoinAuth(room_jid_s, user_jid.userhost(), nick): 141 if not self._checkJoinAuth(room_jid, user_jid, nick):
141 return 142 return
142 nicks.extend(self._invitePlayers(room, other_players, nick, profile)) 143 nicks.extend(self._invitePlayers(room, other_players, nick, profile))
143 self._updatePlayers(room_jid_s, nicks, True, profile) 144 self._updatePlayers(room_jid, nicks, True, profile)
144 else: 145 else:
145 self._initGame(room_jid_s, nick) 146 self._initGame(room_jid, nick)
146 (auth, waiting, missing) = self._checkWaitAuth(room, other_players) 147 (auth, waiting, missing) = self._checkWaitAuth(room, other_players)
147 nicks.extend(waiting) 148 nicks.extend(waiting)
148 nicks.extend(self._invitePlayers(room, missing, nick, profile)) 149 nicks.extend(self._invitePlayers(room, missing, nick, profile))
149 if auth: 150 if auth:
150 self.createGame(room_jid_s, nicks, profile) 151 self.createGame(room_jid, nicks, profile)
151 else: 152 else:
152 self._updatePlayers(room_jid_s, nicks, False, profile) 153 self._updatePlayers(room_jid, nicks, False, profile)
153 154
154 def _initGame(self, room_jid_s, referee_nick): 155 def _initGame(self, room_jid, referee_nick):
155 """Important: do not add the referee to 'players' yet. For a 156 """
156 <players /> message to be emitted whenever a new player is joining, 157
157 it is necessary to not modify 'players' outside of _updatePlayers. 158 @param room_jid (jid.JID): JID of the room
158 """ 159 @param referee_nick (unicode): nickname of the referee
159 referee = room_jid_s + '/' + referee_nick 160 """
160 self.games[room_jid_s] = {'referee': referee, 'players': [], 'started': False, 'status': {}} 161 # Important: do not add the referee to 'players' yet. For a
161 self.games[room_jid_s].update(copy.deepcopy(self.game_init)) 162 # <players /> message to be emitted whenever a new player is joining,
162 self.invitations.setdefault(room_jid_s, []) 163 # it is necessary to not modify 'players' outside of _updatePlayers.
163 164 referee_jid = jid.JID(room_jid.userhost() + '/' + referee_nick)
164 def _gameExists(self, room_jid_s, started=False): 165 self.games[room_jid] = {'referee': referee_jid, 'players': [], 'started': False, 'status': {}}
166 self.games[room_jid].update(copy.deepcopy(self.game_init))
167 self.invitations.setdefault(room_jid, [])
168
169 def _gameExists(self, room_jid, started=False):
165 """Return True if a game has been initialized/started. 170 """Return True if a game has been initialized/started.
166 @param started: if False, the game must be initialized to return True, 171 @param started: if False, the game must be initialized to return True,
167 otherwise it must be initialized and started with createGame. 172 otherwise it must be initialized and started with createGame.
168 @return: True if a game is initialized/started in that room""" 173 @return: True if a game is initialized/started in that room"""
169 return room_jid_s in self.games and (not started or self.games[room_jid_s]['started']) 174 return room_jid in self.games and (not started or self.games[room_jid]['started'])
170 175
171 def _checkJoinAuth(self, room_jid_s, user_jid_s=None, nick="", verbose=False): 176 def _checkJoinAuth(self, room_jid, user_jid=None, nick="", verbose=False):
172 """Checks if this profile is allowed to join the game. 177 """Checks if this profile is allowed to join the game.
178
173 The parameter nick is used to check if the user is already 179 The parameter nick is used to check if the user is already
174 a player in that game. When this method is called from 180 a player in that game. When this method is called from
175 userJoinedTrigger, nick is also used to check the user 181 userJoinedTrigger, nick is also used to check the user
176 identity instead of user_jid_s (see TODO comment below). 182 identity instead of user_jid_s (see TODO comment below).
177 @param room_jid_s: the room hosting the game 183 @param room_jid (jid.JID): the JID of the room hosting the game
178 @param user_jid_s: JID userhost of the user 184 @param user_jid (jid.JID): JID of the user
179 @param nick: nick of the user 185 @param nick (unicode): nick of the user
180 @return: True if this profile can join the game 186 @return: True if this profile can join the game
181 """ 187 """
182 auth = False 188 auth = False
183 if not self._gameExists(room_jid_s): 189 if not self._gameExists(room_jid):
184 auth = False 190 auth = False
185 elif self.join_mode == self.ALL or self.isPlayer(room_jid_s, nick): 191 elif self.join_mode == self.ALL or self.isPlayer(room_jid, nick):
186 auth = True 192 auth = True
187 elif self.join_mode == self.INVITED: 193 elif self.join_mode == self.INVITED:
188 user_jid_s = JID(user_jid_s).userhost()
189 # considering all the batches of invitations 194 # considering all the batches of invitations
190 for invitations in self.invitations[room_jid_s]: 195 for invitations in self.invitations[room_jid]:
191 if user_jid_s is not None: 196 if user_jid is not None:
192 if user_jid_s in invitations[1]: 197 if user_jid.userhostJID() in invitations[1]:
193 auth = True 198 auth = True
194 break 199 break
195 else: 200 else:
196 # TODO: that's not secure enough but what to do if 201 # TODO: that's not secure enough but what to do if
197 # wokkel.muc.User's 'entity' attribute is not set?! 202 # wokkel.muc.User's 'entity' attribute is not set?!
198 if nick in [JID(invited).user for invited in invitations[1]]: 203 if nick in [invited.user for invited in invitations[1]]:
199 auth = True 204 auth = True
200 break 205 break
201 206
202 if not auth and (verbose or _DEBUG): 207 if not auth and (verbose or _DEBUG):
203 log.debug(_("%(user)s not allowed to join the game %(game)s in %(room)s") % {'user': user_jid_s or nick, 'game': self.name, 'room': room_jid_s}) 208 log.debug(_("%(user)s not allowed to join the game %(game)s in %(room)s") % {'user': user_jid.userhost() or nick, 'game': self.name, 'room': room_jid.userhost()})
204 return auth 209 return auth
205 210
206 def _updatePlayers(self, room_jid_s, nicks, sync, profile): 211 def _updatePlayers(self, room_jid, nicks, sync, profile):
207 """Update the list of players and signal to the room that some players joined the game. 212 """Update the list of players and signal to the room that some players joined the game.
208 If sync is True, the news players are synchronized with the game data they have missed. 213 If sync is True, the news players are synchronized with the game data they have missed.
209 Remark: self.games[room_jid_s]['players'] should not be modified outside this method. 214 Remark: self.games[room_jid]['players'] should not be modified outside this method.
210 @param room_jid_s: room userhost 215 @param room_jid (jid.JID): JID of the room
211 @param nicks: list of players nicks in the room (referee included, in first position) 216 @param nicks (list[unicode]): list of players nicks in the room (referee included, in first position)
212 @param sync: set to True to send synchronization data to the new players 217 @param sync (bool): set to True to send synchronization data to the new players
213 @param profile 218 @param profile (unicode): %(doc_profile)s
214 """ 219 """
215 if nicks == []: 220 if nicks == []:
216 return 221 return
217 # this is better than set(nicks).difference(...) as it keeps the order 222 # this is better than set(nicks).difference(...) as it keeps the order
218 new_nicks = [nick for nick in nicks if nick not in self.games[room_jid_s]['players']] 223 new_nicks = [nick for nick in nicks if nick not in self.games[room_jid]['players']]
219 if len(new_nicks) == 0: 224 if len(new_nicks) == 0:
220 return 225 return
221 226
222 def setStatus(status): 227 def setStatus(status):
223 for nick in new_nicks: 228 for nick in new_nicks:
224 self.games[room_jid_s]['status'][nick] = status 229 self.games[room_jid]['status'][nick] = status
225 230
226 sync = sync and self._gameExists(room_jid_s, True) and len(self.games[room_jid_s]['players']) > 0 231 sync = sync and self._gameExists(room_jid, True) and len(self.games[room_jid]['players']) > 0
227 setStatus('desync' if sync else 'init') 232 setStatus('desync' if sync else 'init')
228 self.games[room_jid_s]['players'].extend(new_nicks) 233 self.games[room_jid]['players'].extend(new_nicks)
229 self._synchronizeRoom(room_jid_s, [JID(room_jid_s)], profile) 234 self._synchronizeRoom(room_jid, [room_jid], profile)
230 if sync: 235 if sync:
231 setStatus('init') 236 setStatus('init')
232 237
233 def _synchronizeRoom(self, room_jid_s, recipients, profile): 238 def _synchronizeRoom(self, room_jid, recipients, profile):
234 """Communicate the list of players to the whole room or only to some users, 239 """Communicate the list of players to the whole room or only to some users,
235 also send the synchronization data to the players who recently joined the game. 240 also send the synchronization data to the players who recently joined the game.
236 @param room_jid_s: room userhost 241 @param room_jid (jid.JID): JID of the room
237 @recipients: list of JIDs, the recipients of the message could be: 242 @recipients (list[jid.JID]): list of JIDs, the recipients of the message could be:
238 - room JID 243 - room JID
239 - room JID + "/" + user nick 244 - room JID + "/" + user nick
240 @param profile 245 @param profile (unicode): %(doc_profile)s
241 """ 246 """
242 if self._gameExists(room_jid_s, started=True): 247 if self._gameExists(room_jid, started=True):
243 element = self._createStartElement(self.games[room_jid_s]['players']) 248 element = self._createStartElement(self.games[room_jid]['players'])
244 else: 249 else:
245 element = self._createStartElement(self.games[room_jid_s]['players'], name="players") 250 element = self._createStartElement(self.games[room_jid]['players'], name="players")
246 elements = [(element, None, None)] 251 elements = [(element, None, None)]
247 252
248 sync_args = [] 253 sync_args = []
249 sync_data = self._getSyncData(room_jid_s) 254 sync_data = self._getSyncData(room_jid)
250 for nick in sync_data: 255 for nick in sync_data:
251 user_jid = JID(room_jid_s + '/' + nick) 256 user_jid = jid.JID(room_jid.userhost() + '/' + nick)
252 if user_jid in recipients: 257 if user_jid in recipients:
253 user_elements = copy.deepcopy(elements) 258 user_elements = copy.deepcopy(elements)
254 for child in sync_data[nick]: 259 for child in sync_data[nick]:
255 user_elements.append((child, None, None)) 260 user_elements.append((child, None, None))
256 recipients.remove(user_jid) 261 recipients.remove(user_jid)
261 for recipient in recipients: 266 for recipient in recipients:
262 self._sendElements(recipient, elements, profile=profile) 267 self._sendElements(recipient, elements, profile=profile)
263 for args, kwargs in sync_args: 268 for args, kwargs in sync_args:
264 self._sendElements(*args, **kwargs) 269 self._sendElements(*args, **kwargs)
265 270
266 def _getSyncData(self, room_jid_s, force_nicks=None): 271 def _getSyncData(self, room_jid, force_nicks=None):
267 """The synchronization data are returned for each player who 272 """The synchronization data are returned for each player who
268 has the state 'desync' or if he's been contained by force_nicks. 273 has the state 'desync' or if he's been contained by force_nicks.
269 @param room_jid_s: room userhost 274 @param room_jid (jid.JID): JID of the room
270 @param force_nicks: force the synchronization for this list of the nicks 275 @param force_nicks: force the synchronization for this list of the nicks
271 @return: a mapping between player nicks and a list of elements to 276 @return: a mapping between player nicks and a list of elements to
272 be sent by self._synchronizeRoom for the game to be synchronized. 277 be sent by self._synchronizeRoom for the game to be synchronized.
273 """ 278 """
274 if not self._gameExists(room_jid_s): 279 if not self._gameExists(room_jid):
275 return {} 280 return {}
276 data = {} 281 data = {}
277 status = self.games[room_jid_s]['status'] 282 status = self.games[room_jid]['status']
278 nicks = [nick for nick in status if status[nick] == 'desync'] 283 nicks = [nick for nick in status if status[nick] == 'desync']
279 if force_nicks is None: 284 if force_nicks is None:
280 force_nicks = [] 285 force_nicks = []
281 for nick in force_nicks: 286 for nick in force_nicks:
282 if nick not in nicks: 287 if nick not in nicks:
283 nicks.append(nick) 288 nicks.append(nick)
284 for nick in nicks: 289 for nick in nicks:
285 elements = self.getSyncDataForPlayer(room_jid_s, nick) 290 elements = self.getSyncDataForPlayer(room_jid, nick)
286 if elements: 291 if elements:
287 data[nick] = elements 292 data[nick] = elements
288 return data 293 return data
289 294
290 def getSyncDataForPlayer(self, room_jid_s, nick): 295 def getSyncDataForPlayer(self, room_jid, nick):
291 """This method may (and should probably) be overwritten by a child class. 296 """This method may (and should probably) be overwritten by a child class.
292 @param room_jid_s: room userhost 297 @param room_jid (jid.JID): JID of the room
293 @param nick: the nick of the player to be synchronized 298 @param nick: the nick of the player to be synchronized
294 @return: a list of elements to synchronize this player with the game. 299 @return: a list of elements to synchronize this player with the game.
295 """ 300 """
296 return [] 301 return []
297 302
298 def _invitePlayers(self, room, other_players, nick, profile): 303 def _invitePlayers(self, room, other_players, nick, profile):
299 """Invite players to a room, associated game may exist or not. 304 """Invite players to a room, associated game may exist or not.
300 @param room: wokkel.muc.Room instance 305
301 @param other_players: list of JID userhosts to invite 306 @param room (wokkel.muc.Room): the room
302 @param nick: nick of the user who send the invitation 307 @param other_players (list[jid.JID]): list of the players to invite
303 @return: list of room nicks for invited players who are already in the room 308 @param nick (unicode): nick of the user who send the invitation
309 @return: list[unicode] of room nicks for invited players who are already in the room
304 """ 310 """
305 room_jid = room.occupantJID.userhostJID() 311 room_jid = room.occupantJID.userhostJID()
306 room_jid_s = room.occupantJID.userhost() 312 if not self._checkInviteAuth(room_jid, nick):
307 if not self._checkInviteAuth(room_jid_s, nick):
308 return [] 313 return []
309 # TODO: remove invitation waiting for too long, using the time data 314 # TODO: remove invitation waiting for too long, using the time data
310 players_jids = [JID(player) for player in other_players] 315 self.invitations[room_jid].append((time(), [player.userhostJID() for player in other_players]))
311 self.invitations[room_jid_s].append((time(), [player.userhost() for player in players_jids]))
312 nicks = [] 316 nicks = []
313 for player_jid in [player.userhostJID() for player in players_jids]: 317 for player_jid in [player.userhostJID() for player in other_players]:
314 # TODO: find a way to make it secure 318 # TODO: find a way to make it secure
315 other_nick = self.host.plugins["XEP-0045"].getRoomNickOfUser(room, player_jid, secure=self.testing) 319 other_nick = self.host.plugins["XEP-0045"].getRoomNickOfUser(room, player_jid, secure=self.testing)
316 if other_nick is None: 320 if other_nick is None:
317 self.host.plugins["XEP-0249"].invite(player_jid, room_jid, {"game": self.name}, profile) 321 self.host.plugins["XEP-0249"].invite(player_jid, room_jid, {"game": self.name}, profile)
318 else: 322 else:
319 nicks.append(other_nick) 323 nicks.append(other_nick)
320 return nicks 324 return nicks
321 325
322 def _checkInviteAuth(self, room_jid_s, nick, verbose=False): 326 def _checkInviteAuth(self, room_jid, nick, verbose=False):
323 """Checks if this user is allowed to invite players 327 """Checks if this user is allowed to invite players
324 @param room_jid_s: room userhost 328
329 @param room_jid (jid.JID): JID of the room
325 @param nick: user nick in the room 330 @param nick: user nick in the room
326 @param verbose: display debug message 331 @param verbose: display debug message
327 @return: True if the user is allowed to invite other players 332 @return: True if the user is allowed to invite other players
328 """ 333 """
329 auth = False 334 auth = False
330 if self.invite_mode == self.FROM_ALL or not self._gameExists(room_jid_s): 335 if self.invite_mode == self.FROM_ALL or not self._gameExists(room_jid):
331 auth = True 336 auth = True
332 elif self.invite_mode == self.FROM_NONE: 337 elif self.invite_mode == self.FROM_NONE:
333 auth = not self._gameExists(room_jid_s, started=True) and self.isReferee(room_jid_s, nick) 338 auth = not self._gameExists(room_jid, started=True) and self.isReferee(room_jid, nick)
334 elif self.invite_mode == self.FROM_REFEREE: 339 elif self.invite_mode == self.FROM_REFEREE:
335 auth = self.isReferee(room_jid_s, nick) 340 auth = self.isReferee(room_jid, nick)
336 elif self.invite_mode == self.FROM_PLAYERS: 341 elif self.invite_mode == self.FROM_PLAYERS:
337 auth = self.isPlayer(room_jid_s, nick) 342 auth = self.isPlayer(room_jid, nick)
338 if not auth and (verbose or _DEBUG): 343 if not auth and (verbose or _DEBUG):
339 log.debug(_("%(user)s not allowed to invite for the game %(game)s in %(room)s") % {'user': nick, 'game': self.name, 'room': room_jid_s}) 344 log.debug(_("%(user)s not allowed to invite for the game %(game)s in %(room)s") % {'user': nick, 'game': self.name, 'room': room_jid.userhost()})
340 return auth 345 return auth
341 346
342 def isReferee(self, room_jid_s, nick): 347 def isReferee(self, room_jid, nick):
343 """Checks if the player with this nick is the referee for the game in this room" 348 """Checks if the player with this nick is the referee for the game in this room"
344 @param room_jid_s: room userhost 349 @param room_jid (jid.JID): room JID
345 @param nick: user nick in the room 350 @param nick: user nick in the room
346 @return: True if the user is the referee of the game in this room 351 @return: True if the user is the referee of the game in this room
347 """ 352 """
348 if not self._gameExists(room_jid_s): 353 if not self._gameExists(room_jid):
349 return False 354 return False
350 return room_jid_s + '/' + nick == self.games[room_jid_s]['referee'] 355 return jid.JID(room_jid.userhost() + '/' + nick) == self.games[room_jid]['referee']
351 356
352 def isPlayer(self, room_jid_s, nick): 357 def isPlayer(self, room_jid, nick):
353 """Checks if the user with this nick is a player for the game in this room. 358 """Checks if the user with this nick is a player for the game in this room.
354 @param room_jid_s: room userhost 359 @param room_jid (jid.JID): JID of the room
355 @param nick: user nick in the room 360 @param nick: user nick in the room
356 @return: True if the user is a player of the game in this room 361 @return: True if the user is a player of the game in this room
357 """ 362 """
358 if not self._gameExists(room_jid_s): 363 if not self._gameExists(room_jid):
359 return False 364 return False
360 # Important: the referee is not in the 'players' list right after 365 # Important: the referee is not in the 'players' list right after
361 # the game initialization, that's why we do also check with isReferee 366 # the game initialization, that's why we do also check with isReferee
362 return nick in self.games[room_jid_s]['players'] or self.isReferee(room_jid_s, nick) 367 return nick in self.games[room_jid]['players'] or self.isReferee(room_jid, nick)
363 368
364 def _checkWaitAuth(self, room, other_players, verbose=False): 369 def _checkWaitAuth(self, room, other_players, verbose=False):
365 """Check if we must wait for other players before starting the game. 370 """Check if we must wait for other players before starting the game.
366 371
367 @param room: wokkel.muc.Room instance 372 @param room (wokkel.muc.Room): the room
368 @param other_players: list of players JID userhosts without the referee 373 @param other_players (list[jid.JID]): list of the players without the referee
369 @param verbose: display debug message 374 @param verbose (bool): display debug message
370 @return: (x, y, z) with: 375 @return: (x, y, z) with:
371 x: False if we must wait, True otherwise 376 x: False if we must wait, True otherwise
372 y: the nicks of the players that have been checked and confirmed 377 y: the nicks of the players that have been checked and confirmed
373 z: the players that have not been checked or that are missing 378 z: the JID of the players that have not been checked or that are missing
374 """ 379 """
375 if self.wait_mode == self.FOR_NONE or other_players == []: 380 if self.wait_mode == self.FOR_NONE or other_players == []:
376 result = (True, [], other_players) 381 result = (True, [], other_players)
377 elif len(room.roster) < len(other_players): 382 elif len(room.roster) < len(other_players):
378 # do not check the players until we may actually have them all 383 # do not check the players until we may actually have them all
386 return result 391 return result
387 392
388 def getUniqueName(self, muc_service=None, profile_key=C.PROF_KEY_NONE): 393 def getUniqueName(self, muc_service=None, profile_key=C.PROF_KEY_NONE):
389 """Generate unique room name 394 """Generate unique room name
390 395
391 @param muc_service: you can leave empty to autofind the muc service 396 @param muc_service (jid.JID): you can leave empty to autofind the muc service
392 @param profile_key: %(doc_profile_key)s 397 @param profile_key (unicode): %(doc_profile_key)s
393 @return: a unique name for a new room to be created 398 @return: jid.JID (unique name for a new room to be created)
394 """ 399 """
395 # FIXME: jid.JID must be used instead of strings 400 # FIXME: jid.JID must be used instead of strings
396 room = self.host.plugins["XEP-0045"].getUniqueName(muc_service, profile_key=profile_key) 401 room = self.host.plugins["XEP-0045"].getUniqueName(muc_service, profile_key=profile_key)
397 return "sat_%s_%s" % (self.name.lower(), room.full()) 402 return jid.JID("sat_%s_%s" % (self.name.lower(), room.userhost()))
398 403
399 def prepareRoom(self, other_players=None, room_jid_s=None, profile_key=C.PROF_KEY_NONE): 404 def _prepareRoom(self, other_players=None, room_jid_s='', profile_key=C.PROF_KEY_NONE):
405 room_jid = jid.JID(room_jid_s) if room_jid_s else None
406 other_players = [jid.JID(player).userhostJID() for player in other_players]
407 return self.prepareRoom(other_players, room_jid, profile_key)
408
409 def prepareRoom(self, other_players=None, room_jid=None, profile_key=C.PROF_KEY_NONE):
400 """Prepare the room for a game: create it if it doesn't exist and invite players. 410 """Prepare the room for a game: create it if it doesn't exist and invite players.
401 411
402 @param other_players: list for other players JID userhosts 412 @param other_players (list[JID]): list of other players JID (bare)
403 @param room_jid_s: JID userhost of the room, or None to generate a unique name 413 @param room_jid (jid.JID): JID of the room, or None to generate a unique name
404 @param profile_key 414 @param profile_key (unicode): %(doc_profile_key)s
405 """ 415 """
406 log.debug(_('Preparing room for %s game') % self.name) 416 log.debug(_('Preparing room for %s game') % self.name)
407 profile = self.host.memory.getProfileName(profile_key) 417 profile = self.host.memory.getProfileName(profile_key)
408 if not profile: 418 if not profile:
409 log.error(_("Unknown profile")) 419 log.error(_("Unknown profile"))
411 if other_players is None: 421 if other_players is None:
412 other_players = [] 422 other_players = []
413 423
414 def roomJoined(room): 424 def roomJoined(room):
415 """@param room: instance of wokkel.muc.Room""" 425 """@param room: instance of wokkel.muc.Room"""
416 self._createOrInvite(room, [JID(player).userhost() for player in other_players], profile) 426 self._createOrInvite(room, other_players, profile)
417 427
418 # Create/join the given room, or a unique generated one if no room is specified. 428 # Create/join the given room, or a unique generated one if no room is specified.
419 if room_jid_s is not None and room_jid_s != "": # a room name has been specified 429 if room_jid is None:
420 if room_jid_s in self.host.plugins["XEP-0045"].clients[profile].joined_rooms: 430 room_jid = self.getUniqueName(profile_key=profile_key)
421 roomJoined(self.host.plugins["XEP-0045"].clients[profile].joined_rooms[room_jid_s]) 431 else:
432 if room_jid in self.host.plugins["XEP-0045"].clients[profile].joined_rooms:
433 roomJoined(self.host.plugins["XEP-0045"].clients[profile].joined_rooms[room_jid])
422 return defer.succeed(None) 434 return defer.succeed(None)
423 else: 435
424 room_jid_s = self.getUniqueName(profile_key=profile_key)
425 if room_jid_s == "":
426 return defer.succeed(None)
427 user_jid = self.host.getJidNStream(profile)[0] 436 user_jid = self.host.getJidNStream(profile)[0]
428 d = self.host.plugins["XEP-0045"].join(JID(room_jid_s), user_jid.user, {}, profile) 437 d = self.host.plugins["XEP-0045"].join(room_jid, user_jid.user, {}, profile)
429 return d.addCallback(roomJoined) 438 return d.addCallback(roomJoined)
430 439
431 def userJoinedTrigger(self, room, user, profile): 440 def userJoinedTrigger(self, room, user, profile):
432 """This trigger is used to check if the new user can take part of a game, create the game if we were waiting for him or just update the players list. 441 """This trigger is used to check if the new user can take part of a game, create the game if we were waiting for him or just update the players list.
433 442
434 @room: wokkel.muc.Room object. room.roster is a dict{wokkel.muc.User.nick: wokkel.muc.User} 443 @room: wokkel.muc.Room object. room.roster is a dict{wokkel.muc.User.nick: wokkel.muc.User}
435 @user: wokkel.muc.User object. user.nick is a unicode and user.entity a JID 444 @user: wokkel.muc.User object. user.nick is a unicode and user.entity a JID
436 @return: True to not interrupt the main process. 445 @return: True to not interrupt the main process.
437 """ 446 """
438 room_jid_s = room.occupantJID.userhost() 447 room_jid = room.occupantJID.userhostJID()
439 profile_nick = room.occupantJID.resource 448 profile_nick = room.occupantJID.resource
440 if not self.isReferee(room_jid_s, profile_nick): 449 if not self.isReferee(room_jid, profile_nick):
441 return True # profile is not the referee 450 return True # profile is not the referee
442 if not self._checkJoinAuth(room_jid_s, user.entity.userhost() if user.entity else None, user.nick): 451 if not self._checkJoinAuth(room_jid, user.entity if user.entity else None, user.nick):
443 # user not allowed but let him know that we are playing :p 452 # user not allowed but let him know that we are playing :p
444 self._synchronizeRoom(room_jid_s, [JID(room_jid_s + '/' + user.nick)], profile) 453 self._synchronizeRoom(room_jid, [jid.JID(room_jid.userhost() + '/' + user.nick)], profile)
445 return True 454 return True
446 if self.wait_mode == self.FOR_ALL: 455 if self.wait_mode == self.FOR_ALL:
447 # considering the last batch of invitations 456 # considering the last batch of invitations
448 batch = len(self.invitations[room_jid_s]) - 1 457 batch = len(self.invitations[room_jid]) - 1
449 if batch < 0: 458 if batch < 0:
450 log.error("Invitations from %s to play %s in %s have been lost!" % (profile_nick, self.name, room_jid_s)) 459 log.error("Invitations from %s to play %s in %s have been lost!" % (profile_nick, self.name, room_jid.userhost()))
451 return True 460 return True
452 other_players = self.invitations[room_jid_s][batch][1] 461 other_players = self.invitations[room_jid][batch][1]
453 (auth, nicks, dummy) = self._checkWaitAuth(room, other_players) 462 (auth, nicks, dummy) = self._checkWaitAuth(room, other_players)
454 if auth: 463 if auth:
455 del self.invitations[room_jid_s][batch] 464 del self.invitations[room_jid][batch]
456 nicks.insert(0, profile_nick) # add the referee 465 nicks.insert(0, profile_nick) # add the referee
457 self.createGame(room_jid_s, nicks, profile_key=profile) 466 self.createGame(room_jid, nicks, profile_key=profile)
458 return True 467 return True
459 # let the room know that a new player joined 468 # let the room know that a new player joined
460 self._updatePlayers(room_jid_s, [user.nick], True, profile) 469 self._updatePlayers(room_jid, [user.nick], True, profile)
461 return True 470 return True
462 471
463 def userLeftTrigger(self, room, user, profile): 472 def userLeftTrigger(self, room, user, profile):
464 """This trigger is used to update or stop the game when a user leaves. 473 """This trigger is used to update or stop the game when a user leaves.
465 474
466 @room: wokkel.muc.Room object. room.roster is a dict{wokkel.muc.User.nick: wokkel.muc.User} 475 @room: wokkel.muc.Room object. room.roster is a dict{wokkel.muc.User.nick: wokkel.muc.User}
467 @user: wokkel.muc.User object. user.nick is a unicode and user.entity a JID 476 @user: wokkel.muc.User object. user.nick is a unicode and user.entity a JID
468 @return: True to not interrupt the main process. 477 @return: True to not interrupt the main process.
469 """ 478 """
470 room_jid_s = room.occupantJID.userhost() 479 room_jid = room.occupantJID.userhostJID()
471 profile_nick = room.occupantJID.resource 480 profile_nick = room.occupantJID.resource
472 if not self.isReferee(room_jid_s, profile_nick): 481 if not self.isReferee(room_jid, profile_nick):
473 return True # profile is not the referee 482 return True # profile is not the referee
474 if self.isPlayer(room_jid_s, user.nick): 483 if self.isPlayer(room_jid, user.nick):
475 try: 484 try:
476 self.games[room_jid_s]['players'].remove(user.nick) 485 self.games[room_jid]['players'].remove(user.nick)
477 except ValueError: 486 except ValueError:
478 pass 487 pass
479 if len(self.games[room_jid_s]['players']) == 0: 488 if len(self.games[room_jid]['players']) == 0:
480 del self.games[room_jid_s] # finish the game
481 return True 489 return True
482 if self.wait_mode == self.FOR_ALL: 490 if self.wait_mode == self.FOR_ALL:
483 # allow this user to join the game again 491 # allow this user to join the game again
484 user_jid_s = user.entity.userhost() 492 user_jid = user.entity.userhostJID()
485 if len(self.invitations[room_jid_s]) == 0: 493 if len(self.invitations[room_jid]) == 0:
486 self.invitations[room_jid_s].append((time(), [user_jid_s])) 494 self.invitations[room_jid].append((time(), [user_jid]))
487 else: 495 else:
488 batch = 0 # add to the first batch of invitations 496 batch = 0 # add to the first batch of invitations
489 if user_jid_s not in self.invitations[room_jid_s][batch][1]: 497 if user_jid not in self.invitations[room_jid][batch][1]:
490 self.invitations[room_jid_s][batch][1].append(user_jid_s) 498 self.invitations[room_jid][batch][1].append(user_jid)
491 return True 499 return True
492 500
493 def _checkCreateGameAndInit(self, room_jid_s, profile): 501 def _checkCreateGameAndInit(self, room_jid, profile):
494 """Check if that profile can create the game. If the game can be created but is not initialized yet, this method will also do the initialization. 502 """Check if that profile can create the game. If the game can be created
495 503 but is not initialized yet, this method will also do the initialization.
496 @param room_jid_s: room userhost 504
505 @param room_jid (jid.JID): JID of the room
497 @param profile 506 @param profile
498 @return: a couple (create, sync) with: 507 @return: a couple (create, sync) with:
499 - create: set to True to allow the game creation 508 - create: set to True to allow the game creation
500 - sync: set to True to advice a game synchronization 509 - sync: set to True to advice a game synchronization
501 """ 510 """
502 user_nick = self.host.plugins["XEP-0045"].getRoomNick(room_jid_s, profile) 511 user_nick = self.host.plugins["XEP-0045"].getRoomNick(room_jid, profile)
503 if not user_nick: 512 if not user_nick:
504 log.error('Internal error: profile %s has not joined the room %s' % (profile, room_jid_s)) 513 log.error('Internal error: profile %s has not joined the room %s' % (profile, room_jid.userhost()))
505 return False, False 514 return False, False
506 if self._gameExists(room_jid_s): 515 if self._gameExists(room_jid):
507 is_referee = self.isReferee(room_jid_s, user_nick) 516 is_referee = self.isReferee(room_jid, user_nick)
508 if self._gameExists(room_jid_s, started=True): 517 if self._gameExists(room_jid, started=True):
509 log.warning(_("%(game)s game already created in room %(room)s") % {'game': self.name, 'room': room_jid_s}) 518 log.warning(_("%(game)s game already created in room %(room)s") % {'game': self.name, 'room': room_jid.userhost()})
510 return False, is_referee 519 return False, is_referee
511 elif not is_referee: 520 elif not is_referee:
512 log.warning(_("%(game)s game in room %(room)s can only be created by %(user)s") % {'game': self.name, 'room': room_jid_s, 'user': user_nick}) 521 log.warning(_("%(game)s game in room %(room)s can only be created by %(user)s") % {'game': self.name, 'room': room_jid.userhost(), 'user': user_nick})
513 return False, False 522 return False, False
514 else: 523 else:
515 self._initGame(room_jid_s, user_nick) 524 self._initGame(room_jid, user_nick)
516 return True, False 525 return True, False
517 526
518 def createGame(self, room_jid_s, nicks=None, profile_key=C.PROF_KEY_NONE): 527 def _createGame(self, room_jid_s, nicks=None, profile_key=C.PROF_KEY_NONE):
528 self.createGame(jid.JID(room_jid_s), nicks, profile_key)
529
530 def createGame(self, room_jid, nicks=None, profile_key=C.PROF_KEY_NONE):
519 """Create a new game. 531 """Create a new game.
520 532
521 This can be called directly from a frontend and skips all the checks and invitation system, 533 This can be called directly from a frontend and skips all the checks and invitation system,
522 but the game must not exist and all the players must be in the room already. 534 but the game must not exist and all the players must be in the room already.
523 @param room_jid: JID userhost of the room 535 @param room_jid (jid.JID): JID of the room
524 @param nicks: list of players nicks in the room (referee included, in first position) 536 @param nicks (list[unicode]): list of players nicks in the room (referee included, in first position)
525 @param profile_key: %(doc_profile_key)s 537 @param profile_key (unicode): %(doc_profile_key)s
526 """ 538 """
527 log.debug(_("Creating %(game)s game in room %(room)s") % {'game': self.name, 'room': room_jid_s}) 539 log.debug(_("Creating %(game)s game in room %(room)s") % {'game': self.name, 'room': room_jid})
528 profile = self.host.memory.getProfileName(profile_key) 540 profile = self.host.memory.getProfileName(profile_key)
529 if not profile: 541 if not profile:
530 log.error(_("profile %s is unknown") % profile_key) 542 log.error(_("profile %s is unknown") % profile_key)
531 return 543 return
532 (create, sync) = self._checkCreateGameAndInit(room_jid_s, profile) 544 (create, sync) = self._checkCreateGameAndInit(room_jid, profile)
533 if nicks is None: 545 if nicks is None:
534 nicks = [] 546 nicks = []
535 if not create: 547 if not create:
536 if sync: 548 if sync:
537 self._updatePlayers(room_jid_s, nicks, True, profile) 549 self._updatePlayers(room_jid, nicks, True, profile)
538 return 550 return
539 self.games[room_jid_s]['started'] = True 551 self.games[room_jid]['started'] = True
540 self._updatePlayers(room_jid_s, nicks, False, profile) 552 self._updatePlayers(room_jid, nicks, False, profile)
541 if self.player_init: 553 if self.player_init:
542 # specific data to each player (score, private data) 554 # specific data to each player (score, private data)
543 self.games[room_jid_s].setdefault('players_data', {}) 555 self.games[room_jid].setdefault('players_data', {})
544 for nick in nicks: 556 for nick in nicks:
545 # The dict must be COPIED otherwise it is shared between all users 557 # The dict must be COPIED otherwise it is shared between all users
546 self.games[room_jid_s]['players_data'][nick] = copy.deepcopy(self.player_init) 558 self.games[room_jid]['players_data'][nick] = copy.deepcopy(self.player_init)
547 559
548 def playerReady(self, player, referee, profile_key=C.PROF_KEY_NONE): 560 def _playerReady(self, player_nick, referee_jid_s, profile_key=C.PROF_KEY_NONE):
561 self.playerReady(player_nick, jid.JID(referee_jid_s), profile_key)
562
563 def playerReady(self, player_nick, referee_jid, profile_key=C.PROF_KEY_NONE):
549 """Must be called when player is ready to start a new game 564 """Must be called when player is ready to start a new game
550 565
551 @param player: the player nick in the room 566 @param player: the player nick in the room
552 @param referee: referee userhost 567 @param referee_jid (jid.JID): JID of the referee
553 """ 568 """
554 profile = self.host.memory.getProfileName(profile_key) 569 profile = self.host.memory.getProfileName(profile_key)
555 if not profile: 570 if not profile:
556 log.error(_("profile %s is unknown") % profile_key) 571 log.error(_("profile %s is unknown") % profile_key)
557 return 572 return
558 log.debug('new player ready: %s' % profile) 573 log.debug('new player ready: %s' % profile)
559 # TODO: we probably need to add the game and room names in the sent message 574 # TODO: we probably need to add the game and room names in the sent message
560 self.send(JID(referee), 'player_ready', {'player': player}, profile=profile) 575 self.send(referee_jid, 'player_ready', {'player': player_nick}, profile=profile)
561 576
562 def newRound(self, room_jid, data, profile): 577 def newRound(self, room_jid, data, profile):
563 """Launch a new round (reinit the user data) 578 """Launch a new round (reinit the user data)
564 579
565 @param room_jid: room userhost 580 @param room_jid: room userhost
567 - common_data: backend initialization data for the new round 582 - common_data: backend initialization data for the new round
568 - msg_elts: dict to map each user to his specific initialization message 583 - msg_elts: dict to map each user to his specific initialization message
569 @param profile 584 @param profile
570 """ 585 """
571 log.debug(_('new round for %s game') % self.name) 586 log.debug(_('new round for %s game') % self.name)
572 game_data = self.games[room_jid.userhost()] 587 game_data = self.games[room_jid]
573 players = game_data['players'] 588 players = game_data['players']
574 players_data = game_data['players_data'] 589 players_data = game_data['players_data']
575 game_data['stage'] = "init" 590 game_data['stage'] = "init"
576 591
577 common_data, msg_elts = copy.deepcopy(data) if data is not None else (None, None) 592 common_data, msg_elts = copy.deepcopy(data) if data is not None else (None, None)
578 593
579 if isinstance(msg_elts, dict): 594 if isinstance(msg_elts, dict):
580 for player in players: 595 for player in players:
581 to_jid = JID(room_jid.userhost() + "/" + player) # FIXME: gof: 596 to_jid = jid.JID(room_jid.userhost() + "/" + player) # FIXME: gof:
582 elem = msg_elts[player] if isinstance(msg_elts[player], domish.Element) else None 597 elem = msg_elts[player] if isinstance(msg_elts[player], domish.Element) else None
583 self.send(to_jid, elem, profile=profile) 598 self.send(to_jid, elem, profile=profile)
584 elif isinstance(msg_elts, domish.Element): 599 elif isinstance(msg_elts, domish.Element):
585 self.send(room_jid, msg_elts, profile=profile) 600 self.send(room_jid, msg_elts, profile=profile)
586 if common_data is not None: 601 if common_data is not None: