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