MUC基础
概念
MUC(Multi User Chat),XMPP在其XEP-0045扩展中定义的一个用于多用户文本会议(群聊)的协议,类似于互联网中继聊天(IRC),提供通道或房间让大家能互相交流信息,并查看用户在线情况。
特征
1 每个参与者都可以分享消息(不包含游客Visitor)
2 每个参与者都可以获取聊天室的联系人名单
3 参与者通过昵称标识而不是真实的JabberID
4 聊天室内分享所有参与者的出席情况
5 参与者不局限于人(例:智能机器人)
名词
房间:房间的JID标识 <room@service>(例如:teaparty@conference. ejabberd.org),这里 “room” 是房间的名称而 “service” 是多用户聊天服务运行所在的主机名
房客:房客的JID标识<room@service/nick>,nick是房客在房间的昵称
岗位:表达了用户和房间的长期关系(永久),所有者(owner)-必须、管理者(admin)-推荐、成员(member)-推荐、排斥者(outcast)-推荐,岗位被授予,撤销,和维护都是基于用户的纯JID
角色:表达了用户和房间的临时联系,它只存在与一次访问期间(暂时),主持人(moderator)-必须、与会者(paticipant)-必须、游客(visitor)-推荐,角色的授予,撤销,和维护是基于房客的房间昵称或全JID,而不是纯JID
岗位角色权限
1 owener:包含admin所有功能以及指定admin及销毁聊天室
2 admin:能查看会话内容、发言、踢出参与者和游客并禁止他们进入聊天室、控制他人发言的权利, 查看成员真实的JID、指定member和moderator、重新配置聊天室信息等
3 member:可加入聊天室、查看会话内容、发言(member与participant的区别在于是否注册, member是已注册的用户)
4 outcast:无法进入聊天室,被某个聊天室禁止的用户
5 moderator:能查看会话内容、发言、踢出参与者和游客、控制他人发言的权利
6 participant:即可查看会话内容又可发言
7 visitor:能进入聊天室,查看会话内容但是无法发言
房间类型
1 Hidden Room(隐藏房间) – 一个无法被任何用户以普通方法如搜索和服务查询来发现的房间; 反义词: 公开(public)房间
2 Public Room(公开房间) – 用户可以通过普通方法如搜索和服务查询来发现的房间; 反义词: 隐藏房间
3 Members-Only Room(仅限会员的房间) – 如果一个用户不在成员列表中则无法加入的一个房间; 反义词: 开放(open)房间
4 Open Room(开放房间) – 任何人可以加入而不需要在成员列表中的房间; 反义词: 仅限会员的房间
5 Moderated Room(被主持的房间) – 只有有”发言权”的用户才可以发送消息给所有房客的房间; 反义词: 非主持的(Unmoderated)房间
6 Unmoderated Room(非主持的房间) – 任何房客都被允许发送消息给所有房客的房间; 反义词: 被主持的房间
7 Non-Anonymous Room(非匿名房间) – 一个房客的全JID会暴露给所有其他房客的房间, 尽管房客可以选择任何期望的房间昵称; 相对的是半匿名(Semi-Anonymous)房间
8 Semi-Anonymous Room(半匿名房间) – 一个房客的全JID只能被房间管理员发现的房间; 相对的是非匿名(Non-Anonymous)房间
9 Password-Protected Room(密码保护房间) – 一个用户必须提供正确密码才能加入的房间; 反义词: 非保密房间
10 Unsecured Room(非保密房间) – 任何人不需要提供密码就可以进入的房间; 反义词: 密码保护房间
11 Persistent Room(持久房间) – 如果最后一个房客退出也不会被销毁的房间; 反义词: 临时房间
12 Temporary Room(临时房间) – 如果最后一个房客退出就会被销毁的房间; 反义词: 持久房间
XML架构协议及使用范围
1 http://jabber.org/protocol/muc
用户加入MUC房间,向房间服务发送出席信息
用户新建MUC房间,向房间服务发送出席信息
2 http://jabber.org/protocol/muc#user
用户与房间服务交互(例如:简介邀请,更新出席信息发送,消息通知)
3 http://jabber.org/protocol/muc#admin
房间管理层操作(例如:主持人用例,管理员用例,所有者用例)
4 http://jabber.org/protocol/muc#owner
房间所有者特有权限操作(例如:新建房间,修改房间配置,销毁房间)
5 http://jabber.org/protocol/muc#unique
申请唯一房间名
MUC协议分析
新建房间
若用户试图新建房间被拒绝访问, 服务则返回<not-allowed/> 错误(注:新建房间的权限可能限制在特定用户群或服务级别的管理员,)
<presence
from='darkcave@chat.shakespeare.lit/thirdwitch'
to='hag66@shakespeare.lit/pda'
type='error'>
<x xmlns='http://jabber.org/protocol/muc'/>
<error type='cancel'>
<not-allowed xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
</error>
</presence>
若访问不受限制,则按以下步骤新建群:
1 用户通过发送一个包含满足http://jabber.org/protocol/muc名字空间的空<x/> 子元素的出席信息到<room@service/nick> 声明其对MUC协议的支持
2 如果用户被允许新建房间且房间不存在, 服务根据缺省配置新建此房间, 指定请求的用户作为初始房间拥有者, 并增加这个拥有者到该房间但不允许任何别的用户进入该房间,锁定房间(locking)。从房间向所有者发送包含用户状态指定信息和房间已建立(状态码201标识)的出席信息节,然后等待配置
3 根据新建房间类型分为两种情况
a)如果初始的房间所有者想新建一个持久房间,则房间所有者发送类型为“get”包含一个遵循http://jabber.org/protocol/muc#owner名字空间的<query/>元素的IQ节给该房间请求房间配置 ,然后执行第4和第5步
b)如果初始的房间所有者想新建一个临时房间,则房间所有者发送类型为“set”遵循http://jabber.org/protocol/muc#owner名字空间并包含一个满足 ‘jabber:x:data’ 名字空间,类型为 “submit”的空的<x/> 元素的<query/>元素给该房间,然后跳到第6步
4 如果房间所有者请求了一个配置表格, 服务则发送一个包含配置表格并遵循 ‘jabber:x:data’名字空间的IQ给房间拥有者,如果没有配置选项可用, 房间则返回一个空的<query/>元素给房间所有者
5 初始的房间所有者填写配置表格(或接受缺省配置),通过发送“set”类型并包含完整配置表格的IQ完成配置(设置一个初始化配置的超时值, 如果房间所有者再给定的超时时间内未完成房间配置,房间所有者就被假定已经接受了缺省得配置或取消了配置过程)
6 若服务从初始房间所有者接收到完整准确的配置表格(或接收到了一个临时房间的请求),服务则解锁房间(unlock),允许其他用户进去,并发送“result”类型的IQ给房间所有者;若接收的配置表格违反一个或多个服务策略导致房间创建失败(例:密码保护房间,密码为空),服务返回<not-acceptable/>错误;若服务接收到了取消(指令),则销毁房间
以下展示新建房间协议流程:
首先,Jabber用户发送一个包含满足http://jabber.org/protocol/muc名字空间的空<x/> 子元素的出席信息到房间(寻求进入一个房间时也发送和这同样的节)
<presence
from='crone1@shakespeare.lit/desktop'
to='darkcave@chat.shakespeare.lit/firstwitch'>
<x xmlns='http://jabber.org/protocol/muc'/>
</presence>
如果该房间不存在,服务则新建这个房间(取决于关于新建房间的本地策略),指定发出请求的用户的纯JID成为所有者,添加这个所有者到房间,并通过发送以下格式的出席信息节承认房间新建成功
<presence
from='darkcave@chat.shakespeare.lit/firstwitch'
to='crone1@shakespeare.lit/desktop'>
<x xmlns='http://jabber.org/protocol/muc#user'>
<item affiliation='owner'
role='moderator'/>
<status code='110'/>
<status code='201'/>
</x>
</presence>
分支a,新建临时房间:发送类型为“set”遵循http://jabber.org/protocol/muc#owner名字空间并包含一个满足 ‘jabber:x:data’ 名字空间,类型为 “submit”的空的<x/> 元素的<query/>元素给该房间
<iq from='crone1@shakespeare.lit/desktop'
id='create1'
to='darkcave@chat.shakespeare.lit'
type='set'>
<query xmlns='http://jabber.org/protocol/muc#owner'>
<x xmlns='jabber:x:data' type='submit'/>
</query>
</iq>
分支b,新建持久房间:发送类型为“get”包含一个遵循http://jabber.org/protocol/muc#owner名字空间的<query/>元素的IQ节给该房间请求房间配置
<iq from='crone1@shakespeare.lit/desktop'
id='create1'
to='darkcave@chat.shakespeare.lit'
type='get'>
<query xmlns='http://jabber.org/protocol/muc#owner'/>
</iq>
若房间不存在, 服务则返回一个初始的房间配置表单给该用户(以下是个典型实例)
<iq from='darkcave@chat.shakespeare.lit'
id='create1'
to='crone1@shakespeare.lit/desktop'
type='result'>
<query xmlns='http://jabber.org/protocol/muc#owner'>
<x xmlns='jabber:x:data' type='form'>
<title>Configuration for "darkcave" Room</title>
<instructions>
Your room darkcave@macbeth has been created!
The default configuration is as follows:
- No logging
- No moderation
- Up to 20 occupants
- No password required
- No invitation required
- Room is not persistent
- Only admins may change the subject
- Presence broadcasted for all users
To accept the default configuration, click OK. To
select a different configuration, please complete
this form.
</instructions>
<field
type='hidden'
var='FORM_TYPE'>
<value>http://jabber.org/protocol/muc#roomconfig</value>
</field>
<field
label='Natural-Language Room Name'
type='text-single'
var='muc#roomconfig_roomname'/>
<field
label='Short Description of Room'
type='text-single'
var='muc#roomconfig_roomdesc'/>
<field
label='Natural Language for Room Discussions'
type='text-single'
var='muc#roomconfig_lang'/>
<field
label='Enable Public Logging?'
type='boolean'
var='muc#roomconfig_enablelogging'>
<value>0</value>
</field>
<field
label='Allow Occupants to Change Subject?'
type='boolean'
var='muc#roomconfig_changesubject'>
<value>0</value>
</field>
<field
label='Allow Occupants to Invite Others?'
type='boolean'
var='muc#roomconfig_allowinvites'>
<value>0</value>
</field>
<field
label='Maximum Number of Occupants'
type='list-single'
var='muc#roomconfig_maxusers'>
<value>20</value>
<option label='10'><value>10</value></option>
<option label='20'><value>20</value></option>
<option label='30'><value>30</value></option>
<option label='50'><value>50</value></option>
<option label='100'><value>100</value></option>
<option label='None'><value>none</value></option>
</field>
<field
label='Roles for which Presence is Broadcast'
type='list-multi'
var='muc#roomconfig_presencebroadcast'>
<value>moderator</value>
<value>participant</value>
<value>visitor</value>
<option label='Moderator'><value>moderator</value></option>
<option label='Participant'><value>participant</value></option>
<option label='Visitor'><value>visitor</value></option>
</field>
<field
label='Roles and Affiliations that May Retrieve Member List'
type='list-multi'
var='muc#roomconfig_getmemberlist'>
<value>moderator</value>
<value>participant</value>
<value>visitor</value>
<option label='Moderator'><value>moderator</value></option>
<option label='Participant'><value>participant</value></option>
<option label='Visitor'><value>visitor</value></option>
</field>
<field
label='Make Room Publicly Searchable?'
type='boolean'
var='muc#roomconfig_publicroom'>
<value>1</value>
</field>
<field
label='Make Room Persistent?'
type='boolean'
var='muc#roomconfig_persistentroom'>
<value>0</value>
</field>
<field
label='Make Room Moderated?'
type='boolean'
var='muc#roomconfig_moderatedroom'>
<value>0</value>
</field>
<field
label='Make Room Members-Only?'
type='boolean'
var='muc#roomconfig_membersonly'>
<value>0</value>
</field>
<field
label='Password Required to Enter?'
type='boolean'
var='muc#roomconfig_passwordprotectedroom'>
<value>0</value>
</field>
<field type='fixed'>
<value>
If a password is required to enter this room,
you must specify the password below.
</value>
</field>
<field
label='Password'
type='text-private'
var='muc#roomconfig_roomsecret'/>
<field
label='Who May Discover Real JIDs?'
type='list-single'
var='muc#roomconfig_whois'>
<option label='Moderators Only'>
<value>moderators</value>
</option>
<option label='Anyone'>
<value>anyone</value>
</option>
</field>
<field type='fixed'>
<value>
You may specify additional people who have
administrative privileges in the room. Please
provide one Jabber ID per line.
</value>
</field>
<field
label='Room Admins'
type='jid-multi'
var='muc#roomconfig_roomadmins'/>
<field type='fixed'>
<value>
You may specify additional owners for this
room. Please provide one Jabber ID per line.
</value>
</field>
<field
label='Room Owners'
type='jid-multi'
var='muc#roomconfig_roomowners'/>
</x>
</query>
</iq>
注意:_whois 配置选项指定该房间是非匿名的(值为“anyone”),半匿名的(值为“moderators”),还是全匿名的(值为“none”,不显示在这)
若没有配置选项,则服务返回空的<query/>元素给房间所有者
<iq from='darkcave@chat.shakespeare.lit'
id='create1'
to='crone1@shakespeare.lit/desktop'
type='result'>
<query xmlns='http://jabber.org/protocol/muc#owner'/>
</iq>
房间所有者填写完成表单返回提交给服务
<iq from='crone1@shakespeare.lit/desktop'
id='create2'
to='darkcave@chat.shakespeare.lit'
type='set'>
<query xmlns='http://jabber.org/protocol/muc#owner'>
<x xmlns='jabber:x:data' type='submit'>
<field var='FORM_TYPE'>
<value>http://jabber.org/protocol/muc#roomconfig</value>
</field>
<field var='muc#roomconfig_roomname'>
<value>A Dark Cave</value>
</field>
<field var='muc#roomconfig_roomdesc'>
<value>The place for all good witches!</value>
</field>
<field var='muc#roomconfig_enablelogging'>
<value>0</value>
</field>
<field var='muc#roomconfig_changesubject'>
<value>1</value>
</field>
<field var='muc#roomconfig_allowinvites'>
<value>0</value>
</field>
<field var='muc#roomconfig_maxusers'>
<value>10</value>
</field>
<field var='muc#roomconfig_publicroom'>
<value>0</value>
</field>
<field var='muc#roomconfig_persistentroom'>
<value>0</value>
</field>
<field var='muc#roomconfig_moderatedroom'>
<value>0</value>
</field>
<field var='muc#roomconfig_membersonly'>
<value>0</value>
</field>
<field var='muc#roomconfig_passwordprotectedroom'>
<value>1</value>
</field>
<field var='muc#roomconfig_roomsecret'>
<value>cauldronburn</value>
</field>
<field var='muc#roomconfig_whois'>
<value>moderators</value>
</field>
<field var='muc#roomconfig_roomadmins'>
<value>wiccarocks@shakespeare.lit</value>
<value>hecate@shakespeare.lit</value>
</field>
</x>
</query>
</iq>
若房间创建成功,服务通知所有者房间新建成功
<iq from='darkcave@chat.shakespeare.lit'
id='create2'
to='crone1@shakespeare.lit/desktop'
type='result'/>
若制定房间配置违反一个或多个服务策略导致房间创建失败(例:密码保护房间,密码为空),服务返回<not-acceptable/>错误
<iq from='darkcave@chat.shakespeare.lit'
id='create2'
to='crone1@shakespeare.lit/desktop'
type='error'>
<error type='modify'>
<not-acceptable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
</error>
</iq>
此外,用户取消初始房间配置
<iq from='crone1@shakespeare.lit/desktop'
id='create2'
to='darkcave@chat.shakespeare.lit'
type='set'>
<query xmlns='http://jabber.org/protocol/muc#owner'>
<x xmlns='jabber:x:data' type='cancel'/>
</query>
</iq>
若用户取消房间初始配置或提交表单前失去连接(确保发送不可用出席信息给房间所有者),服务则销毁房间
延伸-申请唯一房间名(避免可能的房间冲突)
房间创建者通过发送一个包含一个空<unique/> 元素,并遵循 http://jabber.org/protocol/muc#unique‘名字空间的IQ-get给服务本身来请求唯一房间名
<iq from='crone1@shakespeare.lit/desktop'
id='unique1'
to='chat.shakespeare.lit'
type='get'>
<unique xmlns='http://jabber.org/protocol/muc#unique'/>
</iq>
如果服务支持这个特性, 则以XML字符数据的方式返回一个唯一房间名,包含一个 <unique/> 元素 (但不创建该房间)
<iq from='chat.shakespeare.lit'
id='unique1'
to='crone1@shakespeare.lit/desktop'
type='result'>
<unique xmlns='http://jabber.org/protocol/muc#unique'>
6d9423a55f499b29ad20bf7b2bdea4f4b885ead1
</unique>
</iq>
服务可使用算法保证房间名的创建在服务上下文中是唯一的(例:对发出的请求JID,datetime和random salt的SHA-1哈希运算)
房间创建者接着使用XML字符数据<unique/>元素作为它请求房间JID的节点标识符(ID)
<presence
from='crone1@shakespeare.lit/desktop'
to='6d9423a55f499b29ad20bf7b2bdea4f4b885ead1@chat.shakespeare.lit/firstwitch'>
<x xmlns='http://jabber.org/protocol/muc'/>
</presence>
加入房间
主动加入-申请
用户客户端直接向需加入房间发送出席信息和用户昵称(若用户存在保留的房间昵称,则会在加入房间前优先查询保留昵称),加入房间后交换出席信息(房间类型为Multi-User Chat,不是Groupchat 1.0),然后服务向新房客发送讨论历史
加入流程:
申请用户直接向需加入房间发送包含满足http://jabber.org/protocol/muc名字空间的<x/>元素(声明用户客户端支持MUC协议)的出席信息和用户昵称
<presence
from="hag66@shakespeare.lit/pda"
to='darkcave@macbeth.shakespeare.lit/thirdwitch'>
<x xmlns='http://jabber.org/protocol/muc'/>
</presence>
若用户加入房间发生错误,则需返回包含MUC子元素的<presence/>节,其type为“error”类型(详见主动加入房间异常情况)
若服务能够添加用户到房间, 则会从所有现存的房客的房间JID发送出席信息给新的房客的全JID, 包括扩展的关于角色的出席信息, 一个满足 http://jabber.org/protocol/muc#user名字空间的 元素并包含一个<item/>子元素, 这个子元素的role属性值设为“moderator”,“participant”,或“visitor”,这个子元素的affiliation属性值设为“owner”,“admin”,“member”,或 “none”中的一个:
<presence
from='darkcave@macbeth.shakespeare.lit/firstwitch'
to='hag66@shakespeare.lit/pda'>
<x xmlns='http://jabber.org/protocol/muc#user'>
<item affiliation='owner' role='moderator'/>
</x>
</presence>
<presence
from='darkcave@macbeth.shakespeare.lit/secondwitch'
to='hag66@shakespeare.lit/pda'>
<x xmlns='http://jabber.org/protocol/muc#user'>
<item affiliation='admin' role='moderator'/>
</x>
</presence>
以上示例表示, 用户进入房间中已有两人存在: 一个是昵称为“firstwitch”(房间拥有者),另一个是昵称为“secondwitch”(房间管理员)。接下来服务从新加入房客房间JID向所有房客发送出息信息(包括新房客自身)
<presence
from='darkcave@macbeth.shakespeare.lit/thirdwitch'
to='crone1@shakespeare.lit/desktop'>
<x xmlns='http://jabber.org/protocol/muc#user'>
<item affiliation='member' role='participant'/>
</x>
</presence>
<presence
from='darkcave@macbeth.shakespeare.lit/thirdwitch'
to='wiccarocks@shakespeare.lit/laptop'>
<x xmlns='http://jabber.org/protocol/muc#user'>
<item affiliation='member' role='participant'/>
</x>
</presence>
<presence
from='darkcave@macbeth.shakespeare.lit/thirdwitch'
to='hag66@shakespeare.lit/pda'>
<x xmlns='http://jabber.org/protocol/muc#user'>
<item affiliation='member' role='participant'/>
<status code='110'/>
</x>
</presence>
以上示例表示由房间代新房客发送出席信息给房客自身时,需额外添加一个110状态码,标识出席信息来自自己,此外,若房间讨论是被记录到公开存档的,服务同时会警告用户记录已被记录,并将房间发送给用户的初始出席信息中包含一个状态码170)
<presence
from='darkcave@chat.shakespeare.lit/thirdwitch'
to='hag66@shakespeare.lit/pda'>
<x xmlns='http://jabber.org/protocol/muc#user'>
<item affiliation='member' role='participant'/>
<status code='100'/>
<status code='110'/>
<status code='170'/>
<status code='210'/>
</x>
</presence>
如上发送完初始出席信息后,服务将历史信息发送给新房客(历史是否发送,发送消息条目,皆有聊天服务部署决定),讨论历史消息满足urn:xmpp:delay名称空间,表示该消息为延迟发送并标明发送时间(用户进入房间接收即时信息,必须在完成历史消息接收后)
<message
from='darkcave@chat.shakespeare.lit/firstwitch'
to='hecate@shakespeare.lit/broom'
type='groupchat'>
<body>Thrice the brinded cat hath mew'd.</body>
<delay xmlns='urn:xmpp:delay'
from='crone1@shakespeare.lit/desktop'
stamp='2002-10-13T23:58:37Z'/>
</message>
<message
from='darkcave@chat.shakespeare.lit/secondwitch'
to='hecate@shakespeare.lit/broom'
type='groupchat'>
<body>Thrice and once the hedge-pig whined.</body>
<delay xmlns='urn:xmpp:delay'
from='wiccarocks@shakespeare.lit/laptop'
stamp='2002-10-13T23:58:43Z'/>
</message>
<message
from='darkcave@chat.shakespeare.lit/thirdwitch'
to='hecate@shakespeare.lit/broom'
type='groupchat'>
<body>Harpier cries 'Tis time, 'tis time.</body>
<delay xmlns='urn:xmpp:delay'
from='hag66@shakespeare.lit/pda'
stamp='2002-10-13T23:58:49Z'/>
</message>
注意:发送给新房客的出席信息顺序很重要, 服务需先发送现有房客的完整列表给这个新房客,然后单发新房客自己的出席信息给新房客。这有助于客户端知道什么时候它收到了完整的房间名册( “room roster”)。发送出席信息广播之后(并且只在这之后),服务才可以发送讨论历史,即时消息,出席信息更新,以及其他房间内的流量
主动加入房间异常情况
1 密码保护房间-加入密码保护房间,用户无法正确密码验证,服务拒绝访问并通知用户未被授权,返回类型为error的出席信息并标明<not-authorized/>错误(正确验证,在出席信息节满足http://jabber.org/protocol/muc名字空间的<x/>的<password/>子元素里添加正确密码即可)
<presence
from='darkcave@chat.shakespeare.lit'
to='hag66@shakespeare.lit/pda'
type='error'>
<x xmlns='http://jabber.org/protocol/muc'/>
<error type='auth'>
<not-authorized xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
</error>
</presence>
2 仅限会员房间-加入仅限会员房间,但申请用户不是该房间成员,服务拒绝访问并通知用户不被允许加入房间,返回类型为error的出席信息并标明<registration-required/>错误条件
<presence
from='darkcave@chat.shakespeare.lit'
to='hag66@shakespeare.lit/pda'
type='error'>
<x xmlns='http://jabber.org/protocol/muc'/>
<error type='auth'>
<registration-required xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
</error>
</presence>
3 禁止用户-被禁止用户(outcast)申请加入房间,服务拒绝访问,并通知用户被禁止,返回类型为error的出席信息并标明<forbidden/>错误条件
<presence
from='darkcave@chat.shakespeare.lit'
to='hag66@shakespeare.lit/pda'
type='error'>
<x xmlns='http://jabber.org/protocol/muc'/>
<error type='auth'>
<forbidden xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
</error>
</presence>
4 昵称冲突-用户申请加入房间,若房间已有新用户预期昵称,服务拒绝访问并通知用户昵称冲突,返回类型为error的出席信息并标明<conflict/>错误条件
<presence
from='darkcave@chat.shakespeare.lit'
to='hag66@shakespeare.lit/pda'
type='error'>
<x xmlns='http://jabber.org/protocol/muc'/>
<error type='cancel'>
<conflict xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
</error>
</presence>
5 最大用户数-用户加入房间已达到最大用户数,服务拒绝访问,并通知用户人数达到限制,返回类型为error的出席信息并标明<service-unavailable/>错误条件
<presence
from='darkcave@chat.shakespeare.lit'
to='hag66@shakespeare.lit/pda'
type='error'>
<x xmlns='http://jabber.org/protocol/muc'/>
<error type='wait'>
<service-unavailable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
</error>
</presence>
6 锁住的房间-用户加入房间处于“locked”状态,服务拒绝访问,并通知用户房间不存在,返回类型为error的出席信息并标明<item-not-found/>错误条件
<presence
from='darkcave@chat.shakespeare.lit'
to='hag66@shakespeare.lit/pda'
type='error'>
<x xmlns='http://jabber.org/protocol/muc'/>
<error type='cancel'>
<item-not-found xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
</error>
</presence>
被动加入-邀请
邀请根据发送邀请的方式分为两个分支
分支1-直接邀请:直接从邀请者(用户)向联系人发送邀请,有助于适应被邀请者那边通信阻塞(对方可能拒绝和不在好友名单中的实体通讯)
直接邀请流程:
邀请者向被邀请者发送一个包含满足 jabber:x:conference名字空间的<x/>元素的<message/> 元素
<message
from='crone1@shakespeare.lit/desktop'
to='hecate@shakespeare.lit'>
<x xmlns='jabber:x:conference'
jid='darkcave@macbeth.shakespeare.lit'
password='cauldronburn'
reason='Hey Hecate, this is the place for all good witches!'/>
</message>
其中jid是必须字段,表示邀请加入的群标识,password(房间是密码密码保护的)和reason(邀请原因)是可选字段
此外还可以添加可选字段continue(继续one-to-one chat)和thread(跟踪会话线索),注意continue是boolean类型
<message
from='crone1@shakespeare.lit/desktop'
to='hecate@shakespeare.lit'>
<x xmlns='jabber:x:conference'
continue='true'
jid='darkcave@macbeth.shakespeare.lit'
password='cauldronburn'
reason='Hey Hecate, this is the place for all good witches!'
thread='e0ffe42b28561960c6b12b944a092794b9683a38'/>
</message>
若被邀请者接受邀请,则加入房间(反之则直接忽略)
分支2-间接邀请:邀请者(用户)以房间(<room@service>)作为介导,将邀请信息发送给房间,然后由房间向被邀请者发送邀请(接受邀请进入房间同分支1进行出席信息交互以及历史信息发送)
间接邀请流程:
邀请者向房间发送一个包含满足 http://jabber.org/protocol/muc#user名字空间的<x/>元素的<message/> 元素
<message
from='crone1@shakespeare.lit/desktop'
to='darkcave@chat.shakespeare.lit'>
<x xmlns='http://jabber.org/protocol/muc#user'>
<invite to='hecate@shakespeare.lit'>
<reason>
Hey Hecate, this is the place for all good witches!
</reason>
</invite>
</x>
</message>
1
2
3
4
5
6
7
8
9
10
11
房间服务接着增加一个 ‘from’ 地址到 <invite/> 元素,其值为邀请者的纯JID,全JID,或房间JID,并发送邀请给‘to’地址所指明的被邀请者,其中password(房间是密码密码保护的)和reason(邀请原因)是可选字段
<message
from='darkcave@chat.shakespeare.lit'
to='hecate@shakespeare.lit'>
<x xmlns='http://jabber.org/protocol/muc#user'>
<invite from='crone1@shakespeare.lit/desktop'>
<reason>
Hey Hecate, this is the place for all good witches!
</reason>
</invite>
<password>cauldronburn</password>
</x>
</message>
1
2
3
4
5
6
7
8
9
10
11
12
如果房间是仅限成员的, 服务需把这个被邀请者加入成员列表。(注意:在仅限成员的房间里邀请的权力应该由房间管理员限定;如果一个没有权限的成员修改成员列表试图邀请别的用户,服务则返回一个<forbidden/> 错误给该房客(查看成员邀请章节)
<message
from='darkcave@chat.shakespeare.lit'
to='crone1@shakespeare.lit/desktop'
type='error'>
<x xmlns='http://jabber.org/protocol/muc#user'>
<invite to='hecate@shakespeare.lit'>
<reason>
Hey Hecate, this is the place for all good witches!
</reason>
</invite>
</x>
<error type='auth'>
<forbidden xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
</error>
</message>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
如果邀请者提供了一个不存在的JID, 房间则返回一个<item-not-found/> 错误给邀请者
被邀请者可以选择正式地拒绝(反之则忽略)邀请;这是发送者希望看到的正式的通知,为了拒绝这个邀请,被邀请者必须发送以下格式的消息给 <room@service>谢绝邀请
<message
from='hecate@shakespeare.lit/broom'
to='darkcave@chat.shakespeare.lit'>
<x xmlns='http://jabber.org/protocol/muc#user'>
<decline to='crone1@shakespeare.lit'>
<reason>
Sorry, I'm too busy right now.
</reason>
</decline>
</x>
</message>
1
2
3
4
5
6
7
8
9
10
11
房间通知邀请者邀请被拒绝
<message
from='darkcave@chat.shakespeare.lit'
to='crone1@shakespeare.lit/desktop'>
<x xmlns='http://jabber.org/protocol/muc#user'>
<decline from='hecate@shakespeare.lit'>
<reason>
Sorry, I'm too busy right now.
</reason>
</decline>
</x>
</message>
1
2
3
4
5
6
7
8
9
10
11
常见行为及相应处理
申请发言-游客
在一个被主持的房间里游客是不能发言的(发送一个消息给所有房客),为了申请发言权,一个游客通过发送包含一个数据表格的<message/> 节给房间本身,这个数据表格仅仅是一个muc#role字段,值为 “participant”
<message from='hag66@shakespeare.lit/pda'
to='darkcave@chat.shakespeare.lit'>
<x xmlns='jabber:x:data' type='submit'>
<field var='FORM_TYPE'>
<value>http://jabber.org/protocol/muc#request</value>
</field>
<field var='muc#role'
type='text-single'
label='Requested role'>
<value>participant</value>
</field>
</x>
</message>
1
2
3
4
5
6
7
8
9
10
11
12
13
批准发言申请-主持人
批准发言申请是主持人权限,房间收到用户发言申请,房间立即通过转发包含一个数据表格的<message/> 节给主持人用于批准或拒绝这个申请
<message from='darkcave@chat.shakespeare.lit'
id='approve'
to='crone1@shakespeare.lit/pda'>
<x xmlns='jabber:x:data' type='form'>
<title>Voice request</title>
<instructions>
To approve this request for voice, select
the "Grant voice to this person?"
checkbox and click OK. To skip this request,
click the cancel button.
</instructions>
<field var='FORM_TYPE' type='hidden'>
<value>http://jabber.org/protocol/muc#request</value>
</field>
<field var='muc#role'
type='text-single'
label='Requested role'>
<value>participant</value>
</field>
<field var='muc#jid'
type='text-single'
label='User ID'>
<value>hag66@shakespeare.lit/pda</value>
</field>
<field var='muc#roomnick'
type='text-single'
label='Room Nickname'>
<value>thirdwitch</value>
</field>
<field var='muc#request_allow'
type='boolean'
label='Grant voice to this person?'>
<value>false</value>
</field>
</x>
</message>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
主持人若同意发言,则提交表格
<message from='crone1@shakespeare.lit/pda'
id='approve'
to='darkcave@chat.shakespeare.lit'>
<x xmlns='jabber:x:data' type='submit'>
<field var='FORM_TYPE' type='hidden'>
<value>http://jabber.org/protocol/muc#request</value>
</field>
<field var='muc#role'>
<value>participant</value>
</field>
<field var='muc#jid'>
<value>hag66@shakespeare.lit/pda</value>
</field>
<field var='muc#roomnick'>
<value>thirdwitch</value>
</field>
<field var='muc#request_allow'>
<value>true</value>
</field>
</x>
</message>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
如果主持人批准了这个发言权申请,服务将授予发言权给该房客并发送一个出席信息更新
授予游客发言权-主持人
主持人通过把游客的角色变更为与会者“participant”给游客授予权限
(发言权的授予是基于游客的房间昵称处理的,服务将从内部把这个房间昵称转成游客的全JID)
主持人向服务发送一个包含满足http://jabber.org/protocol/muc#admin名字空间的<query/>类型为set的<iq/>授予游客权限(reason元素可选)
<iq from='crone1@shakespeare.lit/desktop'
id='voice1'
to='darkcave@chat.shakespeare.lit'
type='set'>
<query xmlns='http://jabber.org/protocol/muc#admin'>
<item nick='thirdwitch'
role='participant'/>
<reason>A worthy witch indeed!</reason>
</query>
</iq>
1
2
3
4
5
6
7
8
9
10
服务通知主持人权限授予成功
<iq from='darkcave@chat.shakespeare.lit'
id='voice1'
to='crone1@shakespeare.lit/desktop'
type='result'/>
1
2
3
4
然后服务以该用户的<room@service/nick>发送更新的出席信息给所有房客,在这个出席信息里包含了一个满足http://jabber.org/protocol/muc#user名字空间的<x/>元素,<x/>元素则包含一个<item/>子元素,其role属性值为“participant”,指明添加了发言权
<presence
from='darkcave@chat.shakespeare.lit/thirdwitch'
to='crone1@shakespeare.lit/desktop'>
<x xmlns='http://jabber.org/protocol/muc#user'>
<item affiliation='member'
nick='thirdwitch'
role='participant'>
<reason>A worthy witch indeed!</reason>
</item>
</x>
</presence>
[ ... ]
1
2
3
4
5
6
7
8
9
10
11
12
13
发送私有消息-任何用户
因为每个房客都有唯一的房间JID,一个房客可以发送私有消息给特定房客,即通过服务发送消息给特定房客的房间JID,消息类型为chat
<message
from='wiccarocks@shakespeare.lit/laptop'
to='darkcave@chat.shakespeare.lit/firstwitch'
type='chat'>
<body>I'll give thee a wind.</body>
</message>
1
2
3
4
5
6
服务作为中转站,修改from属性值为房客放假JID,并重写to属性值为真实用户JID
<message
from='darkcave@chat.shakespeare.lit/secondwitch'
to='crone1@shakespeare.lit/desktop'
type='chat'>
<body>I'll give thee a wind.</body>
</message>
1
2
3
4
5
6
发言-visitor或更高
房客发送一个消息给所有房间内的房客的方法,是发送一个类型为 “groupchat”的消息到<room@service> 本身 (服务可以忽略或拒绝类型不是“groupchat”的消息)(附:在一个被主持的房间,这个权力限于角色为与会者或更高的房客拥有)
<message
from='hag66@shakespeare.lit/pda'
to='darkcave@chat.shakespeare.lit'
type='groupchat'>
<body>Harpier cries: 'tis time, 'tis time.</body>
</message>
1
2
3
4
5
6
若发送者在房间有发言权,服务直接修改from属性为用户房间JID并反射这个消息到每个房客的全JID
<message
from='darkcave@chat.shakespeare.lit/thirdwitch'
to='crone1@shakespeare.lit/desktop'
type='groupchat'>
<body>Harpier cries: 'tis time, 'tis time.</body>
</message>
<message
from='darkcave@chat.shakespeare.lit/thirdwitch'
to='wiccarocks@shakespeare.lit/laptop'
type='groupchat'>
<body>Harpier cries: 'tis time, 'tis time.</body>
</message>
<message
from='darkcave@chat.shakespeare.lit/thirdwitch'
to='hag66@shakespeare.lit/pda'
type='groupchat'>
<body>Harpier cries: 'tis time, 'tis time.</body>
</message>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
撤销与会者发言权-主持人
在一个被主持的房间里,主持人可能希望撤销一个与会者发言的权力,主持人通过把与会者的角色变更为游客“visitor”来撤销一个游客的发言权
主持人向服务发送一个包含满足http://jabber.org/protocol/muc#admin名字空间的<query/>类型为set的<iq/>撤销与会者权限(reason元素可选)
<iq from='crone1@shakespeare.lit/desktop'
id='voice2'
to='darkcave@chat.shakespeare.lit'
type='set'>
<query xmlns='http://jabber.org/protocol/muc#admin'>
<item nick='thirdwitch'
role='visitor'>
<reason>Not so worthy after all!</reason>
</item>
</query>
</iq>
1
2
3
4
5
6
7
8
9
10
11
若失败(主持人试图撤销岗位等于或高于主持人的用户发言权,此外,管理员或所有者发言权不可被任何人撤销),服务拒绝请求并返回<not-allowed/>错误
以下为服务对于尝试从管理员,所有者或更高岗位的用户撤销权限返回错误
<iq from='darkcave@chat.shakespeare.lit'
id='voicetest'
to='crone1@shakespeare.lit/desktop'
type='error'>
<query xmlns='http://jabber.org/protocol/muc#admin'>
<item nick='secondwitch' role='visitor'/>
</query>
<error type='cancel'>
<not-allowed xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
</error>
</iq>
1
2
3
4
5
6
7
8
9
10
11
若成功,则服务通知主持人权限撤销成功
<iq from='darkcave@chat.shakespeare.lit'
id='voice2'
to='crone1@shakespeare.lit/desktop'
type='result'/>
1
2
3
4
服务然后以该用户的<room@service/nick>发送更新的出席信息给所有房客,在这个出席信息里包含了一个满足http://jabber.org/protocol/muc#user名字空间的<x/>元素,<x/>元素则包含一个<item/>子元素,其role属性值为“visitor”,指明移除了发言权
<presence
from='darkcave@chat.shakespeare.lit/thirdwitch'
to='crone1@shakespeare.lit/desktop'>
<x xmlns='http://jabber.org/protocol/muc#user'>
<item affiliation='member'
jid='hag66@shakespeare.lit/pda'
role='visitor'/>
</x>
</presence>
[ ... ]
1
2
3
4
5
6
7
8
9
10
11
修改发言人列表-主持人
在一个被主持的房间里主持人可能希望管理发言权列表,为了达到这个目的,主持人首先通过向服务发送一个包含满足http://jabber.org/protocol/muc#admin名称空间的<query/>元素的类型get的<iq/>请求,查询房间所有角色为participant的房客列表(主持人具备修改权限的发言人列表)
<iq from='bard@shakespeare.lit/globe'
id='voice3'
to='goodfolk@chat.shakespeare.lit'
type='get'>
<query xmlns='http://jabber.org/protocol/muc#admin'>
<item role='participant'/>
</query>
</iq>
1
2
3
4
5
6
7
8
服务返回角色为participant的用户列表给主持人,必须包含nick和role属性,以及应该包含affiliation和jid属性
<iq from='goodfolk@chat.shakespeare.lit'
id='voice3'
to='bard@shakespeare.lit/globe'
type='result'>
<query xmlns='http://jabber.org/protocol/muc#admin'>
<item affiliation='none'
jid='polonius@hamlet/castle'
nick='Polo'
role='participant'/>
<item affiliation='none'
jid='horatio@hamlet/castle'
nick='horotoro'
role='participant'/>
<item affiliation='member'
jid='hecate@shakespeare.lit/broom'
nick='Hecate'
role='participant'/>
</query>
</iq>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
主持人修改发言人列表并返回给服务,必须包含nick和role属性,不应该包含jid属性,不能包含affiliation属性
<iq from='bard@shakespeare.lit/globe'
id='voice4'
to='goodfolk@chat.shakespeare.lit'
type='set'>
<query xmlns='http://jabber.org/protocol/muc#admin'>
<item nick='Hecate'
role='visitor'/>
<item nick='rosencrantz'
role='participant'>
<reason>A worthy fellow.</reason>
</item>
<item nick='guildenstern'
role='participant'>
<reason>A worthy fellow.</reason>
</item>
</query>
</iq>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
若成功,服务通知主持人修改成功
<iq from='goodfolk@chat.shakespeare.lit'
id='voice1'
to='bard@shakespeare.lit/globe'
type='result'/>
1
2
3
4
若失败(主持人试图撤销岗位等于或高于主持人的用户发言权,此外,管理员或所有者发言权不可被任何人撤销),服务拒绝请求并返回<not-allowed/>错误
<iq from='goodfolk@chat.shakespeare.lit'
id='voicetest'
to='bard@shakespeare.lit/globe'
type='error'>
<query xmlns='http://jabber.org/protocol/muc#admin'>
<item jid='hecate@shakespeare.lit'
nick='Hecate'
role='visitor'/>
</query>
<error type='cancel'>
<not-allowed xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
</error>
</iq>
1
2
3
4
5
6
7
8
9
10
11
12
13
注册到房间-无岗位用户(或visitor)
针对无岗位用户(或者被主持房间的游客)通过注册成文房间成员,或者仅限会员的房间,用户加入需要申请会籍
用户通过向服务发送包含一个满足jabber:iq:register名称空间的<query/>元素且类型为get的<iq/>申请注册
<iq from='hag66@shakespeare.lit/pda'
id='reg1'
to='darkcave@chat.shakespeare.lit'
type='get'>
<query xmlns='jabber:iq:register'/>
</iq>
1
2
3
4
5
6
用户申请被拒绝,房间返回<not-allowed/>错误给用户
申请房间不存在,房间返回<item-not-found错误给用户
用户已经注册,房间返回一个类型为result的<iq/>节,并包含一个空的<register/>元素给用户
否则,房间返回一个数据表单“Data Form”给该用户,如下典型示例
<iq from='darkcave@chat.shakespeare.lit'
id='reg1'
to='hag66@shakespeare.lit/pda'
type='result'>
<query xmlns='jabber:iq:register'>
<instructions>
To register on the web, visit http://shakespeare.lit/
</instructions>
<x xmlns='jabber:x:data' type='form'>
<title>Dark Cave Registration</title>
<instructions>
Please provide the following information
to register with this room.
</instructions>
<field
type='hidden'
var='FORM_TYPE'>
<value>http://jabber.org/protocol/muc#register</value>
</field>
<field
label='Given Name'
type='text-single'
var='muc#register_first'>
<required/>
</field>
<field
label='Family Name'
type='text-single'
var='muc#register_last'>
<required/>
</field>
<field
label='Desired Nickname'
type='text-single'
var='muc#register_roomnick'>
<required/>
</field>
<field
label='Your URL'
type='text-single'
var='muc#register_url'/>
<field
label='Email Address'
type='text-single'
var='muc#register_email'/>
<field
label='FAQ Entry'
type='text-multi'
var='muc#register_faqentry'/>
</x>
</query>
</iq>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
用户完成表单选项并提交表单给服务
<iq from='hag66@shakespeare.lit/pda'
id='reg2'
to='darkcave@chat.shakespeare.lit'
type='set'>
<query xmlns='jabber:iq:register'>
<x xmlns='jabber:x:data' type='submit'>
<field var='FORM_TYPE'>
<value>http://jabber.org/protocol/muc#register</value>
</field>
<field var='muc#register_first'>
<value>Brunhilde</value>
</field>
<field var='muc#register_last'>
<value>Entwhistle-Throckmorton</value>
</field>
<field var='muc#register_roomnick'>
<value>thirdwitch</value>
</field>
<field var='muc#register_url'>
<value>http://witchesonline/~hag66/</value>
</field>
<field var='muc#register_email'>
<value>hag66@witchesonline</value>
</field>
<field var='muc#register_faqentry'>
<value>Just another witch.</value>
</field>
</x>
</query>
</iq>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
若成功,房间通知用户注册请求已经被成功处理
<iq from='darkcave@chat.shakespeare.lit'
id='reg2'
to='hag66@shakespeare.lit/pda'
type='result'/>
1
2
3
4
若失败,根据失败原因分为多个分支
分支1 期望房间昵称已经被申请保留,拒绝申请,并返回一个<conflict/>错误给用户
<iq from='darkcave@chat.shakespeare.lit'
id='reg2'
to='hag66@shakespeare.lit/pda'
type='error'>
<error type='cancel'>
<conflict xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
</error>
</iq>
1
2
3
4
5
6
7
8
分支2 申请房间不支持用户注册,拒绝申请,并返回一个<service-unavailable/>错误给用户
<iq from='darkcave@chat.shakespeare.lit'
id='reg2'
to='hag66@shakespeare.lit/pda'
type='error'>
<error type='cancel'>
<service-unavailable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
</error>
</iq>
1
2
3
4
5
6
7
8
分支3 用户提交数据不合法,拒绝申请,并返回一个<bad-request/>错误给用户
<iq from='darkcave@chat.shakespeare.lit'
id='reg2'
to='hag66@shakespeare.lit/pda'
type='error'>
<error type='modify'>
<bad-request xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
</error>
</iq>
1
2
3
4
5
6
7
8
批准注册申请-管理员
如果一个服务不自动接受注册到房间的请求,可为房间管理员提供一个方法来批准或拒绝来自 Jabber 的注册请求,房间收到用户发言申请,房间立即通过转发包含一个数据表格的<message/> 节给管理员用于批准或拒绝这个申请(推荐以下数据表格)
<message from='darkcave@chat.shakespeare.lit'
id='approve'
to='crone1@shakespeare.lit/pda'>
<x xmlns='jabber:x:data' type='form'>
<title>Registration request</title>
<instructions>
To approve this registration request, select the
"Allow this person to register with the room?"
checkbox and click OK. To skip this request, click the
cancel button.
</instructions>
<field var='FORM_TYPE' type='hidden'>
<value>http://jabber.org/protocol/muc#register</value>
</field>
<field var='muc#register_first'
type='text-single'
label='Given Name'>
<value>Brunhilde</value>
</field>
<field var='muc#register_last'
type="text-single"
label="Family Name">
<value>Entwhistle-Throckmorton</value>
</field>
<field var='muc#register_roomnick'
type="text-single"
label="Desired Nickname">
<value>thirdwitch</value>
</field>
<field var='muc#register_url'
type="text-single"
label="User URL">
<value>http://witchesonline/~hag66/</value>
</field>
<field var='muc#register_email'
type="text-single"
label="Email Address">
<value>hag66@witchesonline</value>
</field>
<field var='muc#register_faqentry'
type="text-single"
label="FAQ Entry">
<value>Just another witch.</value>
</field>
<field var='muc#register_allow'
type='boolean'
label='Allow this person to register with the room?'>
<value>0</value>
</field>
</x>
</message>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
若管理员批准,则提交表格
<message from='crone1@shakespeare.lit/pda'
id='approve'
to='darkcave@chat.shakespeare.lit'>
<x xmlns='jabber:x:data' type='submit'>
<field var='FORM_TYPE' type='hidden'>
<value>http://jabber.org/protocol/muc#register</value>
</field>
<field var='muc#register_first'>
<value>Brunhilde</value>
</field>
<field var='muc#register_last'>
<value>Entwhistle-Throckmorton</value>
</field>
<field var='muc#register_roomnick'>
<value>thirdwitch</value>
</field>
<field var='muc#register_url'>
<value>http://witchesonline/~hag66/</value>
</field>
<field var='muc#register_email'>
<value>hag66@witchesonline</value>
</field>
<field var='muc#register_faqentry'>
<value>Just another witch.</value>
</field>
<field var='muc#register_allow'>
<value>1</value>
</field>
</x>
</message>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
服务修改用户none岗位为member岗位,并根据用户在线情况区别广播出席信息
若该用户在线,服务以该用户名义广播更新出席信息,该出席信息包含一个满足http://jabber.org/protocol/muc#user 名字空间 <x/> 元素并包含一个affiliation属性值设为“member”的<item/> 子元素
<presence
from='darkcave@chat.shakespeare.lit/thirdwitch'
to='crone1@shakespeare.lit/desktop'>
<x xmlns='http://jabber.org/protocol/muc#user'>
<item affiliation='member'
jid='hag66@shakespeare.lit/pda'
role='participant'/>
</x>
</presence>
[ ... ]
1
2
3
4
5
6
7
8
9
10
11
若该用户离线,服务从房间本身向其余所有房客发送信息,信息包含一个满足http://jabber.org/protocol/muc#user 名字空间 <x/> 元素并包含一个affiliation属性值设为“member”的<item/> 子元素
<message
from='darkcave@chat.shakespeare.lit'
to='crone1@shakespeare.lit/desktop'>
<x xmlns='http://jabber.org/protocol/muc#user'>
<item affiliation='member'
jid='hag66@shakespeare.lit/pda'
role='participant'/>
</x>
</message>
[ ... ]
1
2
3
4
5
6
7
8
9
10
11
发现保留昵称
若用户通过显示的房间注册,数据库集成或者昵称锁定“lockdown”设置了一个保留昵称,则在用户加入房间前需尝试发现自己的保留昵称,通过发送一个发现服务信息请求并指定一个服务发现节点到房间实现
<iq from='hag66@shakespeare.lit/pda'
id='getnick1'
to='darkcave@chat.shakespeare.lit'
type='get'>
<query xmlns='http://jabber.org/protocol/disco#info'
node='x-roomuser-item'/>
</iq>
1
2
3
4
5
6
7
对多用户聊天以上服务发现节点支持是可选的,若房间或服务不支持上述的服务发现节点,则返回一个<feature-not-implemented/>错误给用户,若支持该特性,则根据用户是否注册保留的房间昵称区别处理
用户已注册,返回一个服务发现的<identity/>元素,其name属性值为昵称(此处category/type应该为“conference”/“text”)
<iq from='darkcave@chat.shakespeare.lit'
id='getnick1'
to='hag66@shakespeare.lit/pda'
type='result'>
<query xmlns='http://jabber.org/protocol/disco#info'
node='x-roomuser-item'>
<identity
category='conference'
name='thirdwitch'
type='text'/>
</query>
</iq>
1
2
3
4
5
6
7
8
9
10
11
12
用户未注册,则返回一个空的服务发现<query/>元素
<iq from='darkcave@chat.shakespeare.lit'
id='getnick1'
to='hag66@shakespeare.lit/pda'
type='result'>
<query xmlns='http://jabber.org/protocol/disco#info'/>
</iq>
1
2
3
4
5
6
更改昵称-任何用户
多用户聊天的一个功能便是房客可以修改自己在房间的昵称,通过发送一个更新的出席信息给房间,准确来说是发送出席信息给一个新的房间JID(只变更房间JID资源部分)
<presence
from='hag66@shakespeare.lit/pda'
to='darkcave@chat.shakespeare.lit/oldhag'/>
1
2
3
昵称修改成功,房间将更新的新旧房间JID出席信息广播给所有用户,用户的旧房间JID类型修改为unavailable,这个不可用出席信息必须在一个满足http://jabber.org/protocol/muc#user名字空间的<x/>子元素里包含扩展信息:新昵称(使接受者关联新旧昵称)和状态码303
<presence
from='darkcave@chat.shakespeare.lit/thirdwitch'
to='crone1@shakespeare.lit/desktop'
type='unavailable'>
<x xmlns='http://jabber.org/protocol/muc#user'>
<item affiliation='member'
jid='hag66@shakespeare.lit/pda'
nick='oldhag'
role='participant'/>
<status code='303'/>
</x>
</presence>
<presence
from='darkcave@chat.shakespeare.lit/thirdwitch'
to='wiccarocks@shakespeare.lit/laptop'
type='unavailable'>
<x xmlns='http://jabber.org/protocol/muc#user'>
<item affiliation='member'
jid='hag66@shakespeare.lit/pda'
nick='oldhag'
role='participant'/>
<status code='303'/>
</x>
</presence>
<presence
from='darkcave@chat.shakespeare.lit/thirdwitch'
to='hag66@shakespeare.lit/pda'
type='unavailable'>
<x xmlns='http://jabber.org/protocol/muc#user'>
<item affiliation='member'
jid='hag66@shakespeare.lit/pda'
nick='oldhag'
role='participant'/>
<status code='303'/>
<status code='110'/>
</x>
</presence>
<presence
from='darkcave@chat.shakespeare.lit/oldhag'
to='crone1@shakespeare.lit/desktop'>
<x xmlns='http://jabber.org/protocol/muc#user'>
<item affiliation='member'
jid='hag66@shakespeare.lit/pda'
role='participant'/>
</x>
</presence>
<presence
from='darkcave@chat.shakespeare.lit/oldhag'
to='wiccarocks@shakespeare.lit/laptop'>
<x xmlns='http://jabber.org/protocol/muc#user'>
<item affiliation='member'
jid='hag66@shakespeare.lit/pda'
role='participant'/>
</x>
</presence>
<presence
from='darkcave@chat.shakespeare.lit/oldhag'
to='hag66@shakespeare.lit/pda'>
<x xmlns='http://jabber.org/protocol/muc#user'>
<item affiliation='member'
jid='hag66@shakespeare.lit/pda'
role='participant'/>
<status code='110'/>
</x>
</presence>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
昵称修改失败,根据昵称修改失败原因分为以下两个分支
分支1 昵称冲突(昵称被其他用户使用或者预期昵称是其他用户保留昵称),服务拒绝这次昵称修改,返回用户一个类型为error的出席信息节,并指明<conflict/>错误条件
<presence
from='darkcave@chat.shakespeare.lit'
to='hag66@shakespeare.lit/pda'
type='error'>
<x xmlns='http://jabber.org/protocol/muc'/>
<error type='cancel'>
<conflict xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
</error>
</presence>
1
2
3
4
5
6
7
8
9
分支2 房间昵称被锁定,用户尝试修改自己被房间锁定昵称,返回用户一个类型为error的出席信息节,并指明<not-acceptable/>错误条件
<presence
from='darkcave@chat.shakespeare.lit'
to='hag66@shakespeare.lit/pda'
type='error'>
<x xmlns='http://jabber.org/protocol/muc'/>
<error type='cancel'>
<not-acceptable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
</error>
</presence>
1
2
3
4
5
6
7
8
9
更改可用性状态-任何用户
多用户聊天的一个功能便是房客可以修改自己可用性状态,通过发送一个更新的出席信息给房间,准确来说是通过调整出席信息中<show/> 和<status/> 元素变更通知
<presence
from='wiccarocks@shakespeare.lit/laptop'
to='darkcave@chat.shakespeare.lit/oldhag'>
<show>xa</show>
<status>gone where the goblins go</status>
</presence>
1
2
3
4
5
6
服务将更新的出席信息广播给每个房客,包含扩展的出席信息,该房客角色和全JID(给有权知道的用户)
<presence
from='darkcave@chat.shakespeare.lit/secondwitch'
to='crone1@shakespeare.lit/desktop'>
<show>xa</show>
<status>gone where the goblins go</status>
<x xmlns='http://jabber.org/protocol/muc#user'>
<item affiliation='admin'
jid='wiccarocks@shakespeare.lit/laptop'
role='moderator'/>
</x>
</presence>
[ ... ]
1
2
3
4
5
6
7
8
9
10
11
12
13
修改房间主题-主持人(可配置)
多用户聊天的一个功能便是变更房间主题,缺省权限为主持人(可配置,允许与会者甚至游客修改),主题变更是通过发送一个类型为groupchat的消息给<room@service>实现的,此外,<message/>必须包含一个<subject/>元素指定新的主题,不可包含<body/>和<thread/>元素
<message
from='wiccarocks@shakespeare.lit/laptop'
to='darkcave@chat.shakespeare.lit'
type='groupchat'>
<subject>Fire Burn and Cauldron Bubble!</subject>
</message>
1
2
3
4
5
6
若主题变更成功,服务修改from属性为变更主题用户房间JID,并发送该变更主题消息给所有其他房客
<message
from='darkcave@chat.shakespeare.lit/secondwitch'
to='crone1@shakespeare.lit/desktop'
type='groupchat'>
<subject>Fire Burn and Cauldron Bubble!</subject>
</message>
[ ... ]
1
2
3
4
5
6
7
8
若主题变更失败(用户没有权限修改房间主题),返回用户一个error类型的消息,并指明<forbidden/>错误条件
<message
from='darkcave@chat.shakespeare.lit'
to='hag66@shakespeare.lit/pda'
type='error'>
<subject>Fire Burn and Cauldron Bubble!</subject>
<error type='auth'>
<forbidden xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
</error>
</message>
1
2
3
4
5
6
7
8
9
延伸,移除当前主题,设置主题为空,用户通过发送一个空的<subject/>元素给<room@service>实现
<message
from='wiccarocks@shakespeare.lit/laptop'
to='darkcave@chat.shakespeare.lit'
type='groupchat'>
<subject></subject>
</message>
1
2
3
4
5
6
禁止用户-管理员或所有者
管理员操作基于房客纯JID,通过向服务发送一个包含满足http://jabber.org/protocol/muc#admin名称空间的<query/>元素且类型为set的<qi/>节,将该房客岗位修改为outcast实现(reason元素可选)
<iq from='kinghenryv@shakespeare.lit/throne'
id='ban1'
to='southampton@henryv.shakespeare.lit'
type='set'>
<query xmlns='http://jabber.org/protocol/muc#admin'>
<item affiliation='outcast'
jid='earlofcambridge@shakespeare.lit'/>
<reason>Treason</reason>
</query>
</iq>
1
2
3
4
5
6
7
8
9
10
若请求失败(尝试禁止岗位比自己高的房客),服务拒绝请求,并返回一个<not-allowed/>错误条件
<iq from='kinghenryv@shakespeare.lit/throne'
id='ban1'
to='southampton@henryv.shakespeare.lit'
type='set'>
<query xmlns='http://jabber.org/protocol/muc#admin'>
<item affiliation='outcast'
jid='earlofcambridge@shakespeare.lit'>
<reason>Treason</reason>
</item>
</query>
<error type='cancel'>
<not-allowed xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
</error>
</iq>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
若请求成功,服务将被禁止用户纯JID加入黑名单,并将被禁止用户昵称从已注册昵称列表移除,返回一个空的类型为result的<iq>节给管理员通知操作成功
<iq from='southampton@henryv.shakespeare.lit'
id='ban1'
to='kinghenryv@shakespeare.lit/throne'
type='result'/>
1
2
3
4
服务移除房间中被禁止的用户,通过发送unavailable类型的出席信息节给每个被禁止用户,扩展信息附带状态码301,禁止执行用户纯JID,被禁止原因(可选)
<presence
from='southampton@henryv.shakespeare.lit/cambridge'
to='earlofcambridge@shakespeare.lit/stabber'
type='unavailable'>
<x xmlns='http://jabber.org/protocol/muc#user'>
<item affiliation='outcast' role='none'>
<actor jid='kinghenryv@shakespeare.lit'/>
<reason>Treason</reason>
</item>
<status code='301'/>
</x>
</presence>
1
2
3
4
5
6
7
8
9
10
11
12
服务以被禁止用户房间JID名义向房间其余房客发送unavailable类型的出席信息,扩展信息包含状态码301,执行者actor,被禁止原因reason(可选)
<presence
type='unavailable'
from='southampton@henryv.shakespeare.lit/cambridge'
to='exeter@shakespeare.lit/pda'>
<x xmlns='http://jabber.org/protocol/muc#user'>
<item affiliation='outcast'
jid='earlofcambridge@shakespeare.lit/stabber'
role='none'/>
<status code='301'/>
</x>
</presence>
[ ... ]
1
2
3
4
5
6
7
8
9
10
11
12
13
修改黑名单-管理员或所有者
黑名单操作是基于用户纯JID的,管理员首先通过向服务发送给一个包含满足http://jabber.org/protocol/muc#admin名称空间的<query/>元素的类型为get的<iq/>节,请求岗位为outcast的用户名单
<iq from='kinghenryv@shakespeare.lit/throne'
id='ban2'
to='southampton@henryv.shakespeare.lit'
type='get'>
<query xmlns='http://jabber.org/protocol/muc#admin'>
<item affiliation='outcast'/>
</query>
</iq>
1
2
3
4
5
6
7
8
服务发送一个类型为result的<iq/>节返回所有岗位为outcast的用户名单给管理员,每个条目必须包含affiliation和jid属性,不应该包含nick和role属性
<iq from='southampton@henryv.shakespeare.lit'
id='ban2'
to='kinghenryv@shakespeare.lit/throne'
type='result'>
<query xmlns='http://jabber.org/protocol/muc#admin'>
<item affiliation='outcast'
jid='earlofcambridge@shakespeare.lit'>
<reason>Treason</reason>
</item>
</query>
</iq>
1
2
3
4
5
6
7
8
9
10
11
管理员修改黑名单条目返回给服务,每个条目必须包含affiliation属性(禁止设置outcast,解封设置none)和jid属性,不应该包含nick属性,不可以包含role属性,此外reason属性和actor属性可选
<iq from='kinghenryv@shakespeare.lit/throne'
id='ban3'
to='southampton@henryv.shakespeare.lit'
type='set'>
<query xmlns='http://jabber.org/protocol/muc#admin'>
<item affiliation='outcast'
jid='earlofcambridge@shakespeare.lit'>
<reason>Treason</reason>
</item>
<item affiliation='outcast'>
jid='lordscroop@shakespeare.lit'>
<reason>Treason</reason>
</item>
<item affiliation='outcast'
jid='sirthomasgrey@shakespeare.lit'>
<reason>Treason</reason>
</item>
</query>
</iq>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
黑名单更新后,服务返回一个空的类型为result的<iq/>节给管理员通知处理成功
<iq from='southampton@henryv.shakespeare.lit'
id='ban3'
to='kinghenryv@shakespeare.lit/throne'
type='result'/>
1
2
3
4
退出房间
主动退出-退出
用户退出一个多用户聊天房间,直接发送一个类型为“unavailable”的出席信息节给正在使用这个房间的
<presence
from='hag66@shakespeare.lit/pda'
to='darkcave@chat.shakespeare.lit/thirdwitch'
type='unavailable'/>
1
2
3
4
服务以退出用户房间JID名义发送unavailable”的出席信息节给这个要离开的房客的全JID(包含状态码110)以及房间内其他所有房客,此外,
服务反射发送的出席信息节必须包含该退出用户的岗位和角色信息(role设置为none,表示该用户不再是一个房客)
注意:
若用户下线未主动发送出席信息,该用户服务器负责替代该用户发送不可用出席信息
若用户服务器下线或该用户服务器与该用户连接的MUC服务失去连接,负责监视的MUC服务收到错误信息节确定该用户是否下线,若确定该用户已下线,则按该用户已发送不可用信息处理
若该房间为临时房间,则最后一个用户退出,服务需负责销毁房间
<presence
from='darkcave@chat.shakespeare.lit/thirdwitch'
to='hag66@shakespeare.lit/pda'
type='unavailable'>
<x xmlns='http://jabber.org/protocol/muc#user'>
<item affiliation='member' role='none'/>
<status code='110'/>
</x>
</presence>
<presence
from='darkcave@chat.shakespeare.lit/thirdwitch'
to='crone1@shakespeare.lit/desktop'
type='unavailable'>
<x xmlns='http://jabber.org/protocol/muc#user'>
<item affiliation='member' role='none'/>
</x>
</presence>
<presence
from='darkcave@chat.shakespeare.lit/thirdwitch'
to='wiccarocks@shakespeare.lit/laptop'
type='unavailable'>
<x xmlns='http://jabber.org/protocol/muc#user'>
<item affiliation='member' role='none'/>
</x>
</presence>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
延伸-自定义退出信息
在出席信息节包含一个常规的 <status/> 信息实现自定义退出信息
<presence
from='wiccarocks@shakespeare.lit/laptop'
to='darkcave@chat.shakespeare.lit/oldhag'
type='unavailable'>
<status>gone where the goblins go</status>
</presence>
1
2
3
4
5
6
被动退出-踢出
踢人通常基于房客的房间昵称来执行,有权限用户将房客角色设为none实现,通过向服务发送一个包含满足http://jabber.org/protocol/muc#admin名称空间的<query/>类型为set的<iq/>节进行操作
<iq from='fluellen@shakespeare.lit/pda'
id='kick1'
to='harfleur@henryv.shakespeare.lit'
type='set'>
<query xmlns='http://jabber.org/protocol/muc#admin'>
<item nick='pistol' role='none'>
<reason>Avaunt, you cullion!</reason>
</item>
</query>
</iq>
1
2
3
4
5
6
7
8
9
10
若踢出成功,服务向被踢出用户发送一个类型为unavailable的出席信息,并在扩展信息中包含状态码307,reason子元素(可选)以及踢人执行者纯JID
<presence
from='harfleur@henryv.shakespeare.lit/pistol'
to='pistol@shakespeare.lit/harfleur'
type='unavailable'>
<x xmlns='http://jabber.org/protocol/muc#user'>
<item affiliation='none' role='none'>
<actor jid='fluellen@shakespeare.lit'/>
<reason>Avaunt, you cullion!</reason>
</item>
<status code='307'/>
</x>
</presence>
1
2
3
4
5
6
7
8
9
10
11
12
服务通过发送给一个空的类型为result的<iq/>节给执行者通知该用户已被踢出
<iq from='harfleur@henryv.shakespeare.lit'
id='kick1'
to='fluellen@shakespeare.lit/pda'
type='result'/>
1
2
3
4
服务以被踢出用户房间JID名义向房间其他房客发送类型为unavailable的出席信息,包含状态码status,可选原因reason以及执行者actor信息
<presence
from='harfleur@henryv.shakespeare.lit/pistol'
to='gower@shakespeare.lit/cell'
type='unavailable'>
<x xmlns='http://jabber.org/protocol/muc#user'>
<item affiliation='none' role='none'/>
<status code='307'/>
</x>
</presence>
[ ... ]
1
2
3
4
5
6
7
8
9
10
11
若踢出失败(用户尝试踢出比自己岗位高的用户),服务拒绝用户请求,并返回一个<not-allowed/>错误条件
<iq from='darkcave@chat.shakespeare.lit'
id='kicktest'
to='wiccarocks@shakespeare.lit/laptop'
type='error'>
<query xmlns='http://jabber.org/protocol/muc#admin'>
<item nick='firstwitch' role='none'>
<reason>Be gone!</reason>
</item>
</query>
<error type='cancel'>
<not-allowed xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
</error>
</iq>
1
2
3
4
5
6
7
8
9
10
11
12
13
销毁房间
自动销毁
若房间类型为临时房间,则在所有用户离开房间后,服务自动销毁房间
主动销毁
房间所有者拥有销毁房间的权限,步骤如下:
1 房间所有者请求销毁房间,如果有需要可指明原因reason和备用场地
2 该房间移除所有房客(包含适当的愿意和备用场地)并销毁房间
房间所有者发送一个包含满足 http://jabber.org/protocol/muc#owner名字空间的<query/>元素且类型为set的<iq/>节给要销毁的房间地址请求销毁,其中<query/>元素包含它将包含一个 <destroy/> 元素,其中替代场地(<destroy/> 元素的jid属性提供),密码保护的替代场(<destroy/>元素的<password/> 子元素的 XML 字符数据提供密码),摧毁房间的原因(<destroy/> 元素的<reason/> 子元素的 XML 字符数据来提供)
<iq from='crone1@shakespeare.lit/desktop'
id='begone'
to='heath@chat.shakespeare.lit'
type='set'>
<query xmlns='http://jabber.org/protocol/muc#owner'>
<destroy jid='darkcave@chat.shakespeare.lit'>
<reason>Macbeth doth come.</reason>
</destroy>
</query>
</iq>
1
2
3
4
5
6
7
8
9
10
若请求销毁所有者信息无误,服务将移除所有用户,通过从所有房客房间JID对应给房客发送unavailable类型出席信息(若所有者扩展信息包含替代场所和销毁原因,则该出席信息必须包含)
<presence
from='heath@chat.shakespeare.lit/firstwitch'
to='crone1@shakespeare.lit/desktop'
type='unavailable'>
<x xmlns='http://jabber.org/protocol/muc#user'>
<item affiliation='none' role='none'/>
<destroy jid='darkcave@chat.shakespeare.lit'>
<reason>Macbeth doth come.</reason>
</destroy>
</x>
</presence>
<presence
from='heath@chat.shakespeare.lit/secondwitch'
to='wiccarocks@shakespeare.lit/laptop'
type='unavailable'>
<x xmlns='http://jabber.org/protocol/muc#user'>
<item affiliation='none' role='none'/>
<destroy jid='darkcave@chat.shakespeare.lit'>
<reason>Macbeth doth come.</reason>
</destroy>
</x>
</presence>
<presence
from='heath@chat.shakespeare.lit/thirdwitch'
to='hag66@shakespeare.lit/pda'
type='unavailable'>
<x xmlns='http://jabber.org/protocol/muc#user'>
<item affiliation='none' role='none'/>
<destroy jid='darkcave@chat.shakespeare.lit'>
<reason>Macbeth doth come.</reason>
</destroy>
</x>
</presence>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
服务返回一个空的类型为result的<iq/>节通知所有者房间销毁成功
<iq from='heath@chat.shakespeare.lit'
id='begone'
to='crone1@shakespeare.lit/desktop'
type='result'/>
1
2
3
4
若请求销毁所有者信息有误(销毁请求中接收到的from地址的<user@host>和一个房间所有者的纯JID不符),服务拒绝访问,并返回一个<forbidden/>错误条件
权限控制
分级权限
角色:大部分情况下,角色存在一个层次结构。例如,一个与会者可以做任何游客能做的事情,而一个主持人可以做任何与会者能做的事情,每个角色拥有其下一级角色所没有的权限
岗位:大部分情况下,岗位存在一个层次结构。例如,一个所有者可以做任何管理员能做的事情,而一个管理员可以做任何成员能做的事情,每个岗位拥有其下一级岗位所没有的权限
权限授予
以管理员授予成员资格为例:
管理员授予成员资格,通过发送一个包含满足http://jabber.org/protocol/muc#admin名称空间的<query/>元素且类型为set的<iq/>节给服务修改用户岗位为member(用户在线基于昵称,用户不在线基于JID)实现(reason属性可选)
<iq from='crone1@shakespeare.lit/desktop'
id='member1'
to='darkcave@chat.shakespeare.lit'
type='set'>
<query xmlns='http://jabber.org/protocol/muc#admin'>
<item affiliation='member'
jid='hag66@shakespeare.lit'/>
<reason>A worthy witch indeed!</reason>
</query>
</iq>
1
2
3
4
5
6
7
8
9
10
房间服务发送一个空的类型为result的<iq/>节给管理员通知处理成功
<iq from='darkcave@chat.shakespeare.lit'
id='member1'
to='crone1@shakespeare.lit/desktop'
type='result'/>
1
2
3
4
若该用户在线,服务以该用户名义广播更新出席信息,该出席信息包含一个满足http://jabber.org/protocol/muc#user 名字空间 <x/> 元素并包含一个affiliation属性值设为“member”的<item/> 子元素
<presence
from='darkcave@chat.shakespeare.lit/thirdwitch'
to='crone1@shakespeare.lit/desktop'>
<x xmlns='http://jabber.org/protocol/muc#user'>
<item affiliation='member'
jid='hag66@shakespeare.lit/pda'
role='participant'/>
</x>
</presence>
[ ... ]
1
2
3
4
5
6
7
8
9
10
11
若该用户离线,服务从房间本身向其余所有房客发送消息,消息包含一个满足http://jabber.org/protocol/muc#user 名字空间 <x/> 元素并包含一个affiliation属性值设为“member”的<item/> 子元素
<message
from='chat.shakespeare.lit'
to='crone1@shakespeare.lit/desktop'>
<x xmlns='http://jabber.org/protocol/muc#user'>
<item affiliation='member'
jid='hag66@shakespeare.lit'
role='none'/>
</x>
</message>
[ ... ]
1
2
3
4
5
6
7
8
9
10
11
权限撤销
以管理员撤销成员资格为例:
管理员授予成员资格,通过发送一个包含满足http://jabber.org/protocol/muc#admin名称空间的<query/>元素且类型为set的<iq/>节给服务修改用户岗位为none实现(reason属性可选)
<iq from='crone1@shakespeare.lit/desktop'
id='member2'
to='darkcave@chat.shakespeare.lit'
type='set'>
<query xmlns='http://jabber.org/protocol/muc#admin'>
<item affiliation='none'
jid='hag66@shakespeare.lit'/>
<reason>Not so worthy after all!</reason>
</query>
</iq>
1
2
3
4
5
6
7
8
9
10
房间服务发送一个空的类型为result的<iq/>节给管理员通知处理成功
<iq from='darkcave@chat.shakespeare.lit'
id='member2'
to='crone1@shakespeare.lit/desktop'
type='result'/>
1
2
3
4
若该用户在线,服务以该用户名义广播更新出席信息,该出席信息包含一个满足http://jabber.org/protocol/muc#user 名字空间 <x/> 元素并包含一个affiliation属性值设为“none”的<item/> 子元素,指明失去成员资格
<presence
from='darkcave@chat.shakespeare.lit/thirdwitch'
to='crone1@shakespeare.lit/desktop'>
<x xmlns='http://jabber.org/protocol/muc#user'>
<item affiliation='none'
jid='hag66@shakespeare.lit/pda'
role='participant'/>
</x>
</presence>
[ ... ]
若该用户离线,服务从房间本身向其余所有房客发送消息,消息包含一个满足http://jabber.org/protocol/muc#user 名字空间 <x/> 元素并包含一个affiliation属性值设为“none”的<item/> 子元素
<message
from='darkcave@chat.shakespeare.lit'
to='crone1@shakespeare.lit/desktop'>
<x xmlns='http://jabber.org/protocol/muc#user'>
<item affiliation='none'
jid='hag66@shakespeare.lit/pda'
role='participant'/>
</x>
</message>
[ ... ]
此外,若房间是仅限会员的,服务必须从房间移除这个用户,包含一个状态码 321 来指明用户被移除是因为岗位变更,并通知所有剩余的房客
<presence
from='darkcave@chat.shakespeare.lit/thirdwitch'
to='crone1@shakespeare.lit/desktop'>
type='unavailable'>
<x xmlns='http://jabber.org/protocol/muc#user'>
<item affiliation='none' role='none'>
<actor jid='bard@shakespeare.lit'/>
</item>
<status code='321'/>
</x>
</presence>
<presence
from='darkcave@chat.shakespeare.lit/thirdwitch'
to='crone1@shakespeare.lit/desktop'>
type='unavailable'>
<x xmlns='http://jabber.org/protocol/muc#user'>
<item affiliation='none' role='none'/>
<status code='321'/>
</x>
</presence>
[ ... ]
附:操作失败情况,可参考修改列表注意事项
修改列表
修改用户列表(成员,主持人,管理员,所有者等),类似修改黑名单流程,注意事项:
1 所有者操作所有者列表,若该用户为唯一所有者,则不允许撤销自己所有权,否则返回一个<conflict/>错误
2 管理员操作主持人列表,不允许撤销管理员或所有者主持人权限,否则返回一个<not-allowed/>错误
3 管理员修改成员列表,若该房间是一个仅限会员的房间,修改成员岗位member为岗位none时,同时需要将用户从该房间踢出