pyxmpp.jabber.muc

Package pyxmpp :: Package jabber :: Module muc |
[hide private]

# Source Code for Module pyxmpp.jabber.muc

       1  #
       2  # (C) Copyright 2003-2010 Jacek Konieczny <jajcus@jajcus.net>
       3  #
       4  # This program is free software; you can redistribute it and/or modify
       5  # it under the terms of the GNU Lesser General Public License Version
       6  # 2.1 as published by the Free Software Foundation.
       7  #
       8  # This program is distributed in the hope that it will be useful,
       9  # but WITHOUT ANY WARRANTY; without even the implied warranty of
      10  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      11  # GNU Lesser General Public License for more details.
      12  #
      13  # You should have received a copy of the GNU Lesser General Public
      14  # License along with this program; if not, write to the Free Software
      15  # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
      16  #
      17  """Jabber Multi-User Chat implementation.
      18
      19  Normative reference:
      20    - `JEP 45 <http://www.jabber.org/jeps/jep-0045.html>`__
      21  """
      22
      23  __revision__="$Id: muc.py 714 2010-04-05 10:20:10Z jajcus $"
      24  __docformat__="restructuredtext en"
      25
      26  import logging
      27
      28  from pyxmpp.presence import Presence
      29  from pyxmpp.message import Message
      30  from pyxmpp.iq import Iq
      31  from pyxmpp.jid import JID
      32
      33  from pyxmpp.xmlextra import xml_element_ns_iter
      34
      35  from pyxmpp.jabber.muccore import MucPresence,MucUserX,MucItem,MucStatus
      36  from pyxmpp.jabber.muccore import MUC_OWNER_NS
      37
      38  from pyxmpp.jabber.dataforms import DATAFORM_NS, Form
      39
      40  import weakref
      41
      42 -class MucRoomHandler:
      43      """
      44      Base class for MUC room handlers.
      45
      46      Methods of this class will be called for various events in the room.
      47
      48      :Ivariables:
      49        - `room_state`: MucRoomState object describing room state and its
      50          participants.
      51
      52      """
      53 -    def __init__(self):
      54          """Initialize a `MucRoomHandler` object."""
      55          self.room_state=None
      56          self.__logger=logging.getLogger("pyxmpp.jabber.MucRoomHandler")
      57
      58 -    def assign_state(self,state_obj):
      59          """Assign a state object to this `MucRoomHandler` instance.
      60
      61          :Parameters:
      62              - `state_obj`: the state object.
      63          :Types:
      64              - `state_obj`: `MucRoomState`"""
      65          self.room_state=state_obj
      66
      67 -    def room_created(self, stanza):
      68          """
      69          Called when the room has been created.
      70
      71          Default action is to request an "instant room" by accepting the default
      72          configuration. Instead the application may want to request a
      73          configuration form and submit it.
      74
      75          :Parameters:
      76              - `stanza`: the stanza received.
      77
      78          :Types:
      79              - `stanza`: `pyxmpp.stanza.Stanza`
      80          """
      81          _unused = stanza
      82          self.room_state.request_instant_room()
      83
      84 -    def configuration_form_received(self,form):
      85          """
      86          Called when a requested configuration form is received.
      87
      88          The form, after filling-in shoul be passed to `self.room_state.configure_room`.
      89
      90          :Parameters:
      91              - `form`: the configuration form.
      92
      93          :Types:
      94              - `form`: `pyxmpp.jabber.dataforms.Form`
      95          """
      96          pass
      97
      98 -    def room_configured(self):
      99          """
     100          Called after a successfull room configuration.
     101          """
     102          pass
     103
     104 -    def user_joined(self,user,stanza):
     105          """
     106          Called when a new participant joins the room.
     107
     108          :Parameters:
     109              - `user`: the user joining.
     110              - `stanza`: the stanza received.
     111
     112          :Types:
     113              - `user`: `MucRoomUser`
     114              - `stanza`: `pyxmpp.stanza.Stanza`
     115          """
     116          pass
     117
     118 -    def user_left(self,user,stanza):
     119          """
     120          Called when a participant leaves the room.
     121
     122          :Parameters:
     123              - `user`: the user leaving.
     124              - `stanza`: the stanza received.
     125
     126          :Types:
     127              - `user`: `MucRoomUser`
     128              - `stanza`: `pyxmpp.stanza.Stanza`
     129          """
     130          pass
     131
     132 -    def role_changed(self,user,old_role,new_role,stanza):
     133          """
     134          Called when a role of an user has been changed.
     135
     136          :Parameters:
     137              - `user`: the user (after update).
     138              - `old_role`: user's role before update.
     139              - `new_role`: user's role after update.
     140              - `stanza`: the stanza received.
     141
     142          :Types:
     143              - `user`: `MucRoomUser`
     144              - `old_role`: `unicode`
     145              - `new_role`: `unicode`
     146              - `stanza`: `pyxmpp.stanza.Stanza`
     147          """
     148          pass
     149
     150 -    def affiliation_changed(self,user,old_aff,new_aff,stanza):
     151          """
     152          Called when a affiliation of an user has been changed.
     153
     154          `user` MucRoomUser object describing the user (after update).
     155          `old_aff` is user's affiliation before update.
     156          `new_aff` is user's affiliation after update.
     157          `stanza` the stanza received.
     158          """
     159          pass
     160
     161 -    def nick_change(self,user,new_nick,stanza):
     162          """
     163          Called when user nick change is started.
     164
     165          :Parameters:
     166              - `user`: the user (before update).
     167              - `new_nick`: the new nick.
     168              - `stanza`: the stanza received.
     169
     170          :Types:
     171              - `user`: `MucRoomUser`
     172              - `new_nick`: `unicode`
     173              - `stanza`: `pyxmpp.stanza.Stanza`
     174          """
     175          pass
     176
     177 -    def nick_changed(self,user,old_nick,stanza):
     178          """
     179          Called after a user nick has been changed.
     180
     181          :Parameters:
     182              - `user`: the user (after update).
     183              - `old_nick`: the old nick.
     184              - `stanza`: the stanza received.
     185
     186          :Types:
     187              - `user`: `MucRoomUser`
     188              - `old_nick`: `unicode`
     189              - `stanza`: `pyxmpp.stanza.Stanza`
     190          """
     191          pass
     192
     193 -    def presence_changed(self,user,stanza):
     194          """
     195          Called whenever user's presence changes (includes nick, role or
     196          affiliation changes).
     197
     198          :Parameters:
     199              - `user`: MucRoomUser object describing the user.
     200              - `stanza`: the stanza received.
     201
     202          :Types:
     203              - `user`: `MucRoomUser`
     204              - `stanza`: `pyxmpp.stanza.Stanza`
     205          """
     206          pass
     207
     208 -    def subject_changed(self,user,stanza):
     209          """
     210          Called when the room subject has been changed.
     211
     212          :Parameters:
     213              - `user`: the user changing the subject.
     214              - `stanza`: the stanza used to change the subject.
     215
     216          :Types:
     217              - `user`: `MucRoomUser`
     218              - `stanza`: `pyxmpp.stanza.Stanza`
     219          """
     220          pass
     221
     222 -    def message_received(self,user,stanza):
     223          """
     224          Called when groupchat message has been received.
     225
     226          :Parameters:
     227              - `user`: the sender.
     228              - `stanza`: is the message stanza received.
     229
     230          :Types:
     231              - `user`: `MucRoomUser`
     232              - `stanza`: `pyxmpp.stanza.Stanza`
     233          """
     234          pass
     235
     236 -    def room_configuration_error(self,stanza):
     237          """
     238          Called when an error stanza is received in reply to a room
     239          configuration request.
     240
     241          By default `self.error` is called.
     242
     243          :Parameters:
     244              - `stanza`: the stanza received.
     245          :Types:
     246              - `stanza`: `pyxmpp.stanza.Stanza`
     247          """
     248          self.error(stanza)
     249
     250 -    def error(self,stanza):
     251          """
     252          Called when an error stanza is received.
     253
     254          :Parameters:
     255              - `stanza`: the stanza received.
     256          :Types:
     257              - `stanza`: `pyxmpp.stanza.Stanza`
     258          """
     259          err=stanza.get_error()
     260          self.__logger.debug("Error from: %r Condition: %r"
     261                  % (stanza.get_from(),err.get_condition))
     262
     263 -class MucRoomUser:
     264      """
     265      Describes a user of a MUC room.
     266
     267      The attributes of this object should not be changed directly.
     268
     269      :Ivariables:
     270          - `presence`: last presence stanza received for the user.
     271          - `role`: user's role.
     272          - `affiliation`: user's affiliation.
     273          - `room_jid`: user's room jid.
     274          - `real_jid`: user's real jid or None if not available.
     275          - `nick`: user's nick (resource part of `room_jid`)
     276      :Types:
     277          - `presence`: `MucPresence`
     278          - `role`: `str`
     279          - `affiliation`: `str`
     280          - `room_jid`: `JID`
     281          - `real_jid`: `JID`
     282          - `nick`: `unicode`
     283      """
     284 -    def __init__(self,presence_or_user_or_jid):
     285          """
     286          Initialize a `MucRoomUser` object.
     287
     288          :Parameters:
     289              - `presence_or_user_or_jid`: a MUC presence stanza with user
     290                information, a user object to copy or a room JID of a user.
     291          :Types:
     292              - `presence_or_user_or_jid`: `MucPresence` or `MucRoomUser` or
     293                `JID`
     294
     295          When `presence_or_user_or_jid` is a JID user's
     296          role and affiliation are set to "none".
     297          """
     298          if isinstance(presence_or_user_or_jid,MucRoomUser):
     299              self.presence=presence_or_user_or_jid.presence
     300              self.role=presence_or_user_or_jid.role
     301              self.affiliation=presence_or_user_or_jid.affiliation
     302              self.room_jid=presence_or_user_or_jid.room_jid
     303              self.real_jid=presence_or_user_or_jid.real_jid
     304              self.nick=presence_or_user_or_jid.nick
     305              self.new_nick=None
     306          else:
     307              self.affiliation="none"
     308              self.presence=None
     309              self.real_jid=None
     310              self.new_nick=None
     311              if isinstance(presence_or_user_or_jid,JID):
     312                  self.nick=presence_or_user_or_jid.resource
     313                  self.room_jid=presence_or_user_or_jid
     314                  self.role="none"
     315              elif isinstance(presence_or_user_or_jid,Presence):
     316                  self.nick=None
     317                  self.room_jid=None
     318                  self.role="participant"
     319                  self.update_presence(presence_or_user_or_jid)
     320              else:
     321                  raise TypeError,"Bad argument type for MucRoomUser constructor"
     322
     323 -    def update_presence(self,presence):
     324          """
     325          Update user information.
     326
     327          :Parameters:
     328              - `presence`: a presence stanza with user information update.
     329          :Types:
     330              - `presence`: `MucPresence`
     331          """
     332          self.presence=MucPresence(presence)
     333          t=presence.get_type()
     334          if t=="unavailable":
     335              self.role="none"
     336              self.affiliation="none"
     337          self.room_jid=self.presence.get_from()
     338          self.nick=self.room_jid.resource
     339          mc=self.presence.get_muc_child()
     340          if isinstance(mc,MucUserX):
     341              items=mc.get_items()
     342              for item in items:
     343                  if not isinstance(item,MucItem):
     344                      continue
     345                  if item.role:
     346                      self.role=item.role
     347                  if item.affiliation:
     348                      self.affiliation=item.affiliation
     349                  if item.jid:
     350                      self.real_jid=item.jid
     351                  if item.nick:
     352                      self.new_nick=item.nick
     353                  break
     354
     355 -    def same_as(self,other):
     356          """Check if two `MucRoomUser` objects describe the same user in the
     357          same room.
     358
     359          :Parameters:
     360              - `other`: the user object to compare `self` with.
     361          :Types:
     362              - `other`: `MucRoomUser`
     363
     364          :return: `True` if the two object describe the same user.
     365          :returntype: `bool`"""
     366          return self.room_jid==other.room_jid
     367
     368 -class MucRoomState:
     369      """
     370      Describes the state of a MUC room, handles room events
     371      and provides an interface for room actions.
     372
     373      :Ivariables:
     374          - `own_jid`: real jid of the owner (client using this class).
     375          - `room_jid`: room jid of the owner.
     376          - `handler`: MucRoomHandler object containing callbacks to be called.
     377          - `manager`: MucRoomManager object managing this room.
     378          - `joined`: True if the channel is joined.
     379          - `subject`: current subject of the room.
     380          - `users`: dictionary of users in the room. Nicknames are the keys.
     381          - `me`: MucRoomUser instance of the owner.
     382          - `configured`: `False` if the room requires configuration.
     383      """
     384 -    def __init__(self,manager,own_jid,room_jid,handler):
     385          """
     386          Initialize a `MucRoomState` object.
     387
     388          :Parameters:
     389              - `manager`: an object to manage this room.
     390              - `own_jid`: real JID of the owner (client using this class).
     391              - `room_jid`: room JID of the owner (provides the room name and
     392                the nickname).
     393              - `handler`: an object to handle room events.
     394          :Types:
     395              - `manager`: `MucRoomManager`
     396              - `own_jid`: JID
     397              - `room_jid`: JID
     398              - `handler`: `MucRoomHandler`
     399          """
     400          self.own_jid=own_jid
     401          self.room_jid=room_jid
     402          self.handler=handler
     403          self.manager=weakref.proxy(manager)
     404          self.joined=False
     405          self.subject=None
     406          self.users={}
     407          self.me=MucRoomUser(room_jid)
     408          self.configured = None
     409          self.configuration_form = None
     410          handler.assign_state(self)
     411          self.__logger=logging.getLogger("pyxmpp.jabber.MucRoomState")
     412
     413 -    def get_user(self,nick_or_jid,create=False):
     414          """
     415          Get a room user with given nick or JID.
     416
     417          :Parameters:
     418              - `nick_or_jid`: the nickname or room JID of the user requested.
     419              - `create`: if `True` and `nick_or_jid` is a JID, then a new
     420                user object will be created if there is no such user in the room.
     421          :Types:
     422              - `nick_or_jid`: `unicode` or `JID`
     423              - `create`: `bool`
     424
     425          :return: the named user or `None`
     426          :returntype: `MucRoomUser`
     427          """
     428          if isinstance(nick_or_jid,JID):
     429              if not nick_or_jid.resource:
     430                  return None
     431              for u in self.users.values():
     432                  if nick_or_jid in (u.room_jid,u.real_jid):
     433                      return u
     434              if create:
     435                  return MucRoomUser(nick_or_jid)
     436              else:
     437                  return None
     438          return self.users.get(nick_or_jid)
     439
     440 -    def set_stream(self,stream):
     441          """
     442          Called when current stream changes.
     443
     444          Mark the room not joined and inform `self.handler` that it was left.
     445
     446          :Parameters:
     447              - `stream`: the new stream.
     448          :Types:
     449              - `stream`: `pyxmpp.stream.Stream`
     450          """
     451          _unused = stream
     452          if self.joined and self.handler:
     453              self.handler.user_left(self.me,None)
     454          self.joined=False
     455
     456 -    def join(self, password=None, history_maxchars = None,
     457              history_maxstanzas = None, history_seconds = None, history_since = None):
     458          """
     459          Send a join request for the room.
     460
     461          :Parameters:
     462              - `password`: password to the room.
     463              - `history_maxchars`: limit of the total number of characters in
     464                history.
     465              - `history_maxstanzas`: limit of the total number of messages in
     466                history.
     467              - `history_seconds`: send only messages received in the last
     468                `history_seconds` seconds.
     469              - `history_since`: Send only the messages received since the
     470                dateTime specified (UTC).
     471          :Types:
     472              - `password`: `unicode`
     473              - `history_maxchars`: `int`
     474              - `history_maxstanzas`: `int`
     475              - `history_seconds`: `int`
     476              - `history_since`: `datetime.datetime`
     477          """
     478          if self.joined:
     479              raise RuntimeError,"Room is already joined"
     480          p=MucPresence(to_jid=self.room_jid)
     481          p.make_join_request(password, history_maxchars, history_maxstanzas,
     482                  history_seconds, history_since)
     483          self.manager.stream.send(p)
     484
     485 -    def leave(self):
     486          """
     487          Send a leave request for the room.
     488          """
     489          if self.joined:
     490              p=MucPresence(to_jid=self.room_jid,stanza_type="unavailable")
     491              self.manager.stream.send(p)
     492
     493 -    def send_message(self,body):
     494          """
     495          Send a message to the room.
     496
     497          :Parameters:
     498              - `body`: the message body.
     499          :Types:
     500              - `body`: `unicode`
     501          """
     502          m=Message(to_jid=self.room_jid.bare(),stanza_type="groupchat",body=body)
     503          self.manager.stream.send(m)
     504
     505 -    def set_subject(self,subject):
     506          """
     507          Send a subject change request to the room.
     508
     509          :Parameters:
     510              - `subject`: the new subject.
     511          :Types:
     512              - `subject`: `unicode`
     513          """
     514          m=Message(to_jid=self.room_jid.bare(),stanza_type="groupchat",subject=subject)
     515          self.manager.stream.send(m)
     516
     517 -    def change_nick(self,new_nick):
     518          """
     519          Send a nick change request to the room.
     520
     521          :Parameters:
     522              - `new_nick`: the new nickname requested.
     523          :Types:
     524              - `new_nick`: `unicode`
     525          """
     526          new_room_jid=JID(self.room_jid.node,self.room_jid.domain,new_nick)
     527          p=Presence(to_jid=new_room_jid)
     528          self.manager.stream.send(p)
     529
     530 -    def get_room_jid(self,nick=None):
     531          """
     532          Get own room JID or a room JID for given `nick`.
     533
     534          :Parameters:
     535              - `nick`: a nick for which the room JID is requested.
     536          :Types:
     537              - `nick`: `unicode`
     538
     539          :return: the room JID.
     540          :returntype: `JID`
     541          """
     542          if nick is None:
     543              return self.room_jid
     544          return JID(self.room_jid.node,self.room_jid.domain,nick)
     545
     546 -    def get_nick(self):
     547          """
     548          Get own nick.
     549
     550          :return: own nick.
     551          :returntype: `unicode`
     552          """
     553          return self.room_jid.resource
     554
     555 -    def process_available_presence(self,stanza):
     556          """
     557          Process <presence/> received from the room.
     558
     559          :Parameters:
     560              - `stanza`: the stanza received.
     561          :Types:
     562              - `stanza`: `MucPresence`
     563          """
     564          fr=stanza.get_from()
     565          if not fr.resource:
     566              return
     567          nick=fr.resource
     568          user=self.users.get(nick)
     569          if user:
     570              old_user=MucRoomUser(user)
     571              user.update_presence(stanza)
     572              user.nick=nick
     573          else:
     574              old_user=None
     575              user=MucRoomUser(stanza)
     576              self.users[user.nick]=user
     577          self.handler.presence_changed(user,stanza)
     578          if fr==self.room_jid and not self.joined:
     579              self.joined=True
     580              self.me=user
     581              mc=stanza.get_muc_child()
     582              if isinstance(mc,MucUserX):
     583                  status = [i for i in mc.get_items() if isinstance(i,MucStatus) and i.code==201]
     584                  if status:
     585                      self.configured = False
     586                      self.handler.room_created(stanza)
     587              if self.configured is None:
     588                  self.configured = True
     589          if not old_user or old_user.role=="none":
     590              self.handler.user_joined(user,stanza)
     591          else:
     592              if old_user.nick!=user.nick:
     593                  self.handler.nick_changed(user,old_user.nick,stanza)
     594                  if old_user.room_jid==self.room_jid:
     595                      self.room_jid=fr
     596              if old_user.role!=user.role:
     597                  self.handler.role_changed(user,old_user.role,user.role,stanza)
     598              if old_user.affiliation!=user.affiliation:
     599                  self.handler.affiliation_changed(user,old_user.affiliation,user.affiliation,stanza)
     600
     601 -    def process_unavailable_presence(self,stanza):
     602          """
     603          Process <presence type="unavailable"/> received from the room.
     604
     605          :Parameters:
     606              - `stanza`: the stanza received.
     607          :Types:
     608              - `stanza`: `MucPresence`
     609          """
     610          fr=stanza.get_from()
     611          if not fr.resource:
     612              return
     613          nick=fr.resource
     614          user=self.users.get(nick)
     615          if user:
     616              old_user=MucRoomUser(user)
     617              user.update_presence(stanza)
     618              self.handler.presence_changed(user,stanza)
     619              if user.new_nick:
     620                  mc=stanza.get_muc_child()
     621                  if isinstance(mc,MucUserX):
     622                      renames=[i for i in mc.get_items() if isinstance(i,MucStatus) and i.code==303]
     623                      if renames:
     624                          self.users[user.new_nick]=user
     625                          del self.users[nick]
     626                          return
     627          else:
     628              old_user=None
     629              user=MucRoomUser(stanza)
     630              self.users[user.nick]=user
     631              self.handler.presence_changed(user,stanza)
     632          if fr==self.room_jid and self.joined:
     633              self.joined=False
     634              self.handler.user_left(user,stanza)
     635              self.manager.forget(self)
     636              self.me=user
     637          elif old_user:
     638              self.handler.user_left(user,stanza)
     639          # TODO: kicks
     640
     641 -    def process_groupchat_message(self,stanza):
     642          """
     643          Process <message type="groupchat"/> received from the room.
     644
     645          :Parameters:
     646              - `stanza`: the stanza received.
     647          :Types:
     648              - `stanza`: `Message`
     649          """
     650          fr=stanza.get_from()
     651          user=self.get_user(fr,True)
     652          s=stanza.get_subject()
     653          if s:
     654              self.subject=s
     655              self.handler.subject_changed(user,stanza)
     656          else:
     657              self.handler.message_received(user,stanza)
     658
     659 -    def process_error_message(self,stanza):
     660          """
     661          Process <message type="error"/> received from the room.
     662
     663          :Parameters:
     664              - `stanza`: the stanza received.
     665          :Types:
     666              - `stanza`: `Message`
     667          """
     668          self.handler.error(stanza)
     669
     670 -    def process_error_presence(self,stanza):
     671          """
     672          Process <presence type="error"/> received from the room.
     673
     674          :Parameters:
     675              - `stanza`: the stanza received.
     676          :Types:
     677              - `stanza`: `Presence`
     678          """
     679          self.handler.error(stanza)
     680
     681 -    def process_configuration_form_success(self, stanza):
     682          """
     683          Process successful result of a room configuration form request.
     684
     685          :Parameters:
     686              - `stanza`: the stanza received.
     687          :Types:
     688              - `stanza`: `Presence`
     689          """
     690          if stanza.get_query_ns() != MUC_OWNER_NS:
     691              raise ValueError, "Bad result namespace" # TODO: ProtocolError
     692          query = stanza.get_query()
     693          form = None
     694          for el in xml_element_ns_iter(query.children, DATAFORM_NS):
     695              form = Form(el)
     696              break
     697          if not form:
     698              raise ValueError, "No form received" # TODO: ProtocolError
     699          self.configuration_form = form
     700          self.handler.configuration_form_received(form)
     701
     702 -    def process_configuration_form_error(self, stanza):
     703          """
     704          Process error response for a room configuration form request.
     705
     706          :Parameters:
     707              - `stanza`: the stanza received.
     708          :Types:
     709              - `stanza`: `Presence`
     710          """
     711          self.handler.error(stanza)
     712
     713 -    def request_configuration_form(self):
     714          """
     715          Request a configuration form for the room.
     716
     717          When the form is received `self.handler.configuration_form_received` will be called.
     718          When an error response is received then `self.handler.error` will be called.
     719
     720          :return: id of the request stanza.
     721          :returntype: `unicode`
     722          """
     723          iq = Iq(to_jid = self.room_jid.bare(), stanza_type = "get")
     724          iq.new_query(MUC_OWNER_NS, "query")
     725          self.manager.stream.set_response_handlers(
     726                  iq, self.process_configuration_form_success, self.process_configuration_form_error)
     727          self.manager.stream.send(iq)
     728          return iq.get_id()
     729
     730 -    def process_configuration_success(self, stanza):
     731          """
     732          Process success response for a room configuration request.
     733
     734          :Parameters:
     735              - `stanza`: the stanza received.
     736          :Types:
     737              - `stanza`: `Presence`
     738          """
     739          _unused = stanza
     740          self.configured = True
     741          self.handler.room_configured()
     742
     743 -    def process_configuration_error(self, stanza):
     744          """
     745          Process error response for a room configuration request.
     746
     747          :Parameters:
     748              - `stanza`: the stanza received.
     749          :Types:
     750              - `stanza`: `Presence`
     751          """
     752          self.handler.room_configuration_error(stanza)
     753
     754 -    def configure_room(self, form):
     755          """
     756          Configure the room using the provided data.
     757          Do nothing if the provided form is of type 'cancel'.
     758
     759          :Parameters:
     760              - `form`: the configuration parameters. Should be a 'submit' form made by filling-in
     761                the configuration form retireved using `self.request_configuration_form` or
     762                a 'cancel' form.
     763          :Types:
     764              - `form`: `Form`
     765
     766          :return: id of the request stanza or `None` if a 'cancel' form was provieded.
     767          :returntype: `unicode`
     768          """
     769
     770          if form.type == "cancel":
     771              return None
     772          elif form.type != "submit":
     773              raise ValueError, "A 'submit' form required to configure a room"
     774          iq = Iq(to_jid = self.room_jid.bare(), stanza_type = "set")
     775          query = iq.new_query(MUC_OWNER_NS, "query")
     776          form.as_xml(query)
     777          self.manager.stream.set_response_handlers(
     778                  iq, self.process_configuration_success, self.process_configuration_error)
     779          self.manager.stream.send(iq)
     780          return iq.get_id()
     781
     782 -    def request_instant_room(self):
     783          """
     784          Request an "instant room" -- the default configuration for a MUC room.
     785
     786          :return: id of the request stanza.
     787          :returntype: `unicode`
     788          """
     789          if self.configured:
     790              raise RuntimeError, "Instant room may be requested for unconfigured room only"
     791          form = Form("submit")
     792          return self.configure_room(form)
     793
     794 -class MucRoomManager:
     795      """
     796      Manage collection of MucRoomState objects and dispatch events.
     797
     798      :Ivariables:
     799        - `rooms`: a dictionary containing known MUC rooms. Unicode room JIDs are the
     800          keys.
     801        - `stream`: the stream associated with the room manager.
     802
     803      """
     804 -    def __init__(self,stream):
     805          """
     806          Initialize a `MucRoomManager` object.
     807
     808          :Parameters:
     809              - `stream`: a stream to be initially assigned to `self`.
     810          :Types:
     811              - `stream`: `pyxmpp.stream.Stream`
     812          """
     813          self.rooms={}
     814          self.stream,self.jid=(None,)*2
     815          self.set_stream(stream)
     816          self.__logger=logging.getLogger("pyxmpp.jabber.MucRoomManager")
     817
     818 -    def set_stream(self,stream):
     819          """
     820          Change the stream assigned to `self`.
     821
     822          :Parameters:
     823              - `stream`: the new stream to be assigned to `self`.
     824          :Types:
     825              - `stream`: `pyxmpp.stream.Stream`
     826          """
     827          self.jid=stream.me
     828          self.stream=stream
     829          for r in self.rooms.values():
     830              r.set_stream(stream)
     831
     832 -    def set_handlers(self,priority=10):
     833          """
     834          Assign MUC stanza handlers to the `self.stream`.
     835
     836          :Parameters:
     837              - `priority`: priority for the handlers.
     838          :Types:
     839              - `priority`: `int`
     840          """
     841          self.stream.set_message_handler("groupchat",self.__groupchat_message,None,priority)
     842          self.stream.set_message_handler("error",self.__error_message,None,priority)
     843          self.stream.set_presence_handler("available",self.__presence_available,None,priority)
     844          self.stream.set_presence_handler("unavailable",self.__presence_unavailable,None,priority)
     845          self.stream.set_presence_handler("error",self.__presence_error,None,priority)
     846
     847 -    def join(self, room, nick, handler, password = None, history_maxchars = None,
     848              history_maxstanzas = None, history_seconds = None, history_since = None):
     849          """
     850          Create and return a new room state object and request joining
     851          to a MUC room.
     852
     853          :Parameters:
     854              - `room`: the name of a room to be joined
     855              - `nick`: the nickname to be used in the room
     856              - `handler`: is an object to handle room events.
     857              - `password`: password for the room, if any
     858              - `history_maxchars`: limit of the total number of characters in
     859                history.
     860              - `history_maxstanzas`: limit of the total number of messages in
     861                history.
     862              - `history_seconds`: send only messages received in the last
     863                `history_seconds` seconds.
     864              - `history_since`: Send only the messages received since the
     865                dateTime specified (UTC).
     866
     867          :Types:
     868              - `room`: `JID`
     869              - `nick`: `unicode`
     870              - `handler`: `MucRoomHandler`
     871              - `password`: `unicode`
     872              - `history_maxchars`: `int`
     873              - `history_maxstanzas`: `int`
     874              - `history_seconds`: `int`
     875              - `history_since`: `datetime.datetime`
     876
     877          :return: the room state object created.
     878          :returntype: `MucRoomState`
     879          """
     880
     881          if not room.node or room.resource:
     882              raise ValueError,"Invalid room JID"
     883
     884          room_jid = JID(room.node, room.domain, nick)
     885
     886          cur_rs = self.rooms.get(room_jid.bare().as_unicode())
     887          if cur_rs and cur_rs.joined:
     888              raise RuntimeError,"Room already joined"
     889
     890          rs=MucRoomState(self, self.stream.me, room_jid, handler)
     891          self.rooms[room_jid.bare().as_unicode()]=rs
     892          rs.join(password, history_maxchars, history_maxstanzas,
     893              history_seconds, history_since)
     894          return rs
     895
     896 -    def get_room_state(self,room):
     897          """Get the room state object of a room.
     898
     899          :Parameters:
     900              - `room`: JID or the room which state is requested.
     901          :Types:
     902              - `room`: `JID`
     903
     904          :return: the state object.
     905          :returntype: `MucRoomState`"""
     906          return self.rooms.get(room.bare().as_unicode())
     907
     908 -    def forget(self,rs):
     909          """
     910          Remove a room from the list of managed rooms.
     911
     912          :Parameters:
     913              - `rs`: the state object of the room.
     914          :Types:
     915              - `rs`: `MucRoomState`
     916          """
     917          try:
     918              del self.rooms[rs.room_jid.bare().as_unicode()]
     919          except KeyError:
     920              pass
     921
     922 -    def __groupchat_message(self,stanza):
     923          """Process a groupchat message from a MUC room.
     924
     925          :Parameters:
     926              - `stanza`: the stanza received.
     927          :Types:
     928              - `stanza`: `Message`
     929
     930          :return: `True` if the message was properly recognized as directed to
     931              one of the managed rooms, `False` otherwise.
     932          :returntype: `bool`"""
     933          fr=stanza.get_from()
     934          key=fr.bare().as_unicode()
     935          rs=self.rooms.get(key)
     936          if not rs:
     937              self.__logger.debug("groupchat message from unknown source")
     938              return False
     939          rs.process_groupchat_message(stanza)
     940          return True
     941
     942 -    def __error_message(self,stanza):
     943          """Process an error message from a MUC room.
     944
     945          :Parameters:
     946              - `stanza`: the stanza received.
     947          :Types:
     948              - `stanza`: `Message`
     949
     950          :return: `True` if the message was properly recognized as directed to
     951              one of the managed rooms, `False` otherwise.
     952          :returntype: `bool`"""
     953          fr=stanza.get_from()
     954          key=fr.bare().as_unicode()
     955          rs=self.rooms.get(key)
     956          if not rs:
     957              return False
     958          rs.process_error_message(stanza)
     959          return True
     960
     961 -    def __presence_error(self,stanza):
     962          """Process an presence error from a MUC room.
     963
     964          :Parameters:
     965              - `stanza`: the stanza received.
     966          :Types:
     967              - `stanza`: `Presence`
     968
     969          :return: `True` if the stanza was properly recognized as generated by
     970              one of the managed rooms, `False` otherwise.
     971          :returntype: `bool`"""
     972          fr=stanza.get_from()
     973          key=fr.bare().as_unicode()
     974          rs=self.rooms.get(key)
     975          if not rs:
     976              return False
     977          rs.process_error_presence(stanza)
     978          return True
     979
     980 -    def __presence_available(self,stanza):
     981          """Process an available presence from a MUC room.
     982
     983          :Parameters:
     984              - `stanza`: the stanza received.
     985          :Types:
     986              - `stanza`: `Presence`
     987
     988          :return: `True` if the stanza was properly recognized as generated by
     989              one of the managed rooms, `False` otherwise.
     990          :returntype: `bool`"""
     991          fr=stanza.get_from()
     992          key=fr.bare().as_unicode()
     993          rs=self.rooms.get(key)
     994          if not rs:
     995              return False
     996          rs.process_available_presence(MucPresence(stanza))
     997          return True
     998
     999 -    def __presence_unavailable(self,stanza):
    1000          """Process an unavailable presence from a MUC room.
    1001
    1002          :Parameters:
    1003              - `stanza`: the stanza received.
    1004          :Types:
    1005              - `stanza`: `Presence`
    1006
    1007          :return: `True` if the stanza was properly recognized as generated by
    1008              one of the managed rooms, `False` otherwise.
    1009          :returntype: `bool`"""
    1010          fr=stanza.get_from()
    1011          key=fr.bare().as_unicode()
    1012          rs=self.rooms.get(key)
    1013          if not rs:
    1014              return False
    1015          rs.process_unavailable_presence(MucPresence(stanza))
    1016          return True
    1017
    1018  # vi: sts=4 et sw=4
    1019