XEP-0045
本文的英文原文来自XEP-0045
XEP-0045: 多用户聊天
摘要: 本文定义了一个XMPP协议扩展用于多用户文本会议.即多个XMPP可以在一个房间或频道互相交流信息, 类似互联网中继聊天系统(IRC).还有标准聊天室功能如聊天室的主题和邀请,本协议定义了一个强有力的房间控制模型,包括能够踢和禁止用户,任命主持人和管理员,要求会员或密码才能加入房间,等等。
作者: Peter Saint-Andre
XMPP扩展协议的版权(1999-2008)归XMPP标准化基金会(XSF)所有
版权: © 1999 - 2010 XMPP标准化基金会(XSF). 参见法律通告.
状态: 草案
类型: 标准跟踪
版本: 1.24
最后更新日期: 2008-07-16
注意: 这里定义的协议是XMPP标准化基金会的一个草案标准.对本协议的执行是被鼓励的,也适于部署到生产系统,但是在它成为最终标准之前可能还会有一些变动.
目录 |
绪论
传统上, 即时消息被视为由一对一的聊天构成而不是多对多聊天(即所谓"群聊"或"文本会议"). 群聊功能常见于一些系统如 Internet Relay Chat (IRC) 和 流行的IM服务所提供的聊天室功能. Jabber社区早在1999年开发和实施了一个基本的群聊协议. 这个 "groupchat 1.0" 协议为聊天室提供了一个最小功能集但是范围很有限. 本协议(多用户聊天或简称MUC)建立在向后兼容旧的"groupchat 1.0"协议的基础上但是提供高级功能如邀请, 房间主持和管理, 以及专门的房间类型.
范围
本文着重于和配置,参与以及管理一个独立的基于文本的会议室相关的通用需求. 这里所指出的需求是应用于单个房间级别的并且是"通用的", 某种意义上它们是在Jabber社区广泛讨论的或在现有的Jabber之外的基于文本的会议环境(例如, 定义在 RFC 1459 1中的Internet Relay Chat 和它的继承者: RFC 2810 2, RFC 2811 3, RFC 2812 4, RFC 2813 5)中已经存在的.
本文明确地不涉及以下需求:
- 房间之间的关系(例如, 房间的层次结构)
- 多用户聊天服务的管理(例如, 管理跨越整个服务级别的权限或注册一个全局可用的房间昵称);这些用例定义在Service Administration 6
- 个别消息的主持
- 通过房间发送的消息的加密
- 高级特性, 如附加文件给一个房间, 集成白板, 以及和语音或视频聊天服务的接口
- MUC部署和外来的聊天系统(例如, 和IRC网关或现有的其他IM系统)之间的交互
- 在多个MUC部署之间进行镜像或复制
这一受限的范围并非蔑视这些都很有用的主题; 无论如何, 这意味着本文专注于讨论和介绍一个易于理解的协议能够被类似的Jabber客户端和组件开发者实现. 将来的协议当然可能涉及以上提到的这些主题.
需求
本文描述了由Jabber现有的多用户聊天服务提供的最小功能集. 为了向后兼容性起见, 本文使用原来的"groupchat 1.0"协议作为基本功能, 包括以下这些:
- 每个房间被标识为 <room@service> (例如, <jdev@conference.jabber.org>), 这里 "room" 是房间的名称而 "service" 是多用户聊天服务运行所在的主机名.
- 在一个房间里每个房客被标识为 <room@service/nick>, 这里 "nick" 是这个房客在这个房间里的昵称,定义于刚加入这个房间的时候,也可以在房客驻留改房间期间修改.
- 一个用户通过发送出席信息给 <room@service/nick> 来加入一个房间(也就是成为房客).
- 在多用户聊天房间里发送的消息使用特殊的类型"groupchat"并且被寻址于房间本身 (room@service), 然后反映给所有房客.
- 通过发送出席信息给 <room@service/newnick>,一个房客可以改变他或她的房间昵称以及在房间中的可用性状态 .
- 通过发送一个类型为"unavailable"的出席信息给当前的<room@service/nick>,一个房客可以退出房间.
本文追加的特性和功能包括以下这些:
- 本地会话日志(不需要房间内的机器人)
- 允许用户申请房间成员
- 在一个非匿名房间里, 允许房客可以察看(另)一个房客的全JID
- 在一个半匿名房间里, 允许主持人可以察看一个房客的全JID
- 允许只有主持人修改房间主题
- 允许主持人从房间里踢出与会者和游客
- 在一个被主持的房间里,主持人可以授予和撤销发言权(也就是说, 发言的权力), 并且管理发言权列表
- 允许管理员授权和取消主持人权力, 并且管理主持人列表
- 允许管理员在房间禁止用户, 并管理黑名单
- 允许管理员授予和撤销成员权力, 并且管理一个仅限成员的房间的成员列表
- 允许所有者限制房客的数量
- 允许所有者指定其他的所有者(们)
- 允许所有者授予或撤销管理特权, 并管理管理员列表
- 允许所有者销毁房间
另外, 本文提供了协议元素用于支持以下房间类型:
- 公共的或隐藏的
- 持久的或临时的
- 密码保护的或不安全的
- 仅限成员的或开放的
- 主持的或非主持的
- 非匿名的或半匿名的
为了实现这些需求, 本扩展协议需要满足 'http://jabber.org/protocol/muc' 名字空间(以及 在主名字空间URI加上 #owner, #admin, 和 #user 片断).
术语
通用术语
Affiliation(级别) -- 一个长期存在的和房间之间的联系或连接; 可能的级别有 "owner"(所有者), "admin"(管理者), "member"(成员), 以及 "outcast"(被排斥者) (当然也可能没有级别); 级别从角色来看是唯一的. 一个级别跨越了用户对一个房间的访问期间.
Ban(禁止) -- 从一个房间移除一个用户以使这个用户不能够再进入这个房间 (直到这个禁令被废除为止). 一个被禁止的用户的级别为 "outcast"(被排斥者).
Bare JID(纯JID) -- 一个用户的标识符 <user@host>, 不同于任何已有会话或资源的上下文, 与之相对的是全JID和房间JID.
Full JID(全JID) -- 一个在线用户的标识符 <user@host/resource> , 不同于一个房间的上下文; 与之相对的是纯JID和房间JID.
GC -- 最小的 "groupchat 1.0" 协议[7], Jabber社区于1999年开发; MUC 向后兼容GC.
History(历史) -- 有限数量的消息节, 由当前讨论的上下文提供发送给一个新的房客.
Invitation(邀请) -- 从一个用户发出的特殊消息给另一个用户, 邀请对方加入房间.
IRC -- Internet Relay Chat.
Kick(踢人) -- 临时从一个房间移除一个与会者或游客; 这个用户任何时候都可以再次进入这个房间. 一个被踢的用户的角色是"none".
Logging(记录) -- 存储发生在一个房间的讨论内容用于公开发布到房间背景之外的地方.
Member(成员) -- 一个用户在一个仅限会员的房间内处于"white list"(白名单)内,或已经注册到一个公开的房间. 一个成员级别是"member".
Moderator(主持人) -- 一个房间角色,通常和房间的管理有关但是这个角色可以被赋予非管理员; 可以踢人, 可以授予和撤销发言权, 等等. 一个主持人的角色是"moderator".
MUC -- 本文所定义的基于文本会议的多用户聊天协议.
Occupant(房客) -- 一个房间里的任何Jabber用户 (这是一个 "抽象类" 并且不对应任何特定的角色).
Outcast(被排斥者) -- 一个被某个房间禁止的用户. 一个被排斥者的级别是 "outcast".
Participant(与会者) -- 一个没有管理权限的房客; 在一个被主持的房间里, 参与者更多地被定义为有发言权的 (与之相反的是游客). 一个与会者的角色是"participant".
Private Message(私有消息) -- 从一个房客直接发给另一个房间JID的消息(不是房间本身广播给所有房客的消息).
Role(角色) -- 在一个房间里的一个临时的地位或者权限级别, 对于这个房间中的用户的长期级别来说是唯一的; 可能的角色有 "moderator"(主持人), "participant"(与会者), 和 "visitor"(游客) (也可能没有预定义的角色). 一个角色仅仅存在于一个房客访问一个房间的期间.
Room(房间) -- 一个虚拟的地方, Jabber用户象征性地加入它, 来和其他用户一起参与一个实时的基于文本的会议.
Room Administrator(房间管理员) -- 一个由房间所有者授权的用户, 可以执行管理功能, 如禁止用户等等; 无论如何, 不允许改变定义的房间特性. 一个管理员的级别是"admin" .
Room ID(房间ID) -- 一个房间JID的节点标识符部分, 它可以是不透明的因而对人类用户没有什么含义(见 语法的商业规则Business Rules for syntax); 与之相对的是房间名.
Room JID(房间JID) -- 在一个房间上下文中的一个房客,以 <room@service/nick> 来标识; 与之相对的是纯JID和全JID.
Room Name(房间名) -- 一个用户友好的, 自然语言的房间名字, 由房间所有者配置并在服务查询中展示; 与之相对的是房间ID.
Room Nickname(房间昵称) -- 房间JID的资源标识符部分(见语法的商业规则); 这是一个房客在这个房间中所呈现的"友好的名字".
Room Owner(房间所有者) -- 建立某个房间的Jabber用户或一个被房间创建者或所有者指派拥有所有者权限(如果允许的话)的Jabber用户; 它被允许改变定义好的房间特性, 也可以执行全部的管理功能. 一个所有者的级别为"owner".
Room Roster(房间名册) -- 一个房间中的所有房客在一个Jabber客户端的展现.
Server(服务器) -- 一个Jabber服务器,可以关联或不关联一个基于文本的会议服务.
Service(服务) -- 一个主机, 提供基于文本的会议的能力; 通常但不必须是一个Jabber服务器的子域(例如, conference.jabber.org).
Subject(主题) -- 一个房间的临时讨论标题.
Visit(访问) -- 一个房间的一个用户的"session"(会话), 当用户进入这个房间时开始(也就是说, 成为一个房客) , 结束于用户离开房间之时.
Visitor(游客) -- 在一个被主持的房间里的一个没有发言权的房客(相反则是一个与会者). 一个游客的角色是"visitor".
Voice(发言权) -- 在一个被主持的房间里, 发送消息给全部房客的权限.
房间类型
Fully-Anonymous Room(全匿名房间) -- 一个房间的房客的全JID或纯JID不能被任何人查询到, 包括房间管理员和房间所有者; 这类房间是不推荐的(NOT RECOMMENDED)或不被MUC显式支持, 但是如果一个服务提供适当的配置选项来使用这个协议,这种情况也是有可能的; 相对的则是非匿名房间和半匿名房间.
Hidden Room(隐藏房间) -- 一个无法被任何用户以普通方法如搜索和服务查询来发现的房间; 反义词: 公开(public)房间.
Members-Only Room(仅限会员的房间) -- 如果一个用户不在成员列表中则无法加入的一个房间; 反义词: 开放(open)房间.
Moderated Room(被主持的房间) -- 只有有"发言权"的用户才可以发送消息给所有房客的房间; 反义词: 非主持的Unmoderated房间.
Non-Anonymous Room(非匿名房间) -- 一个房客的全JID会暴露给所有其他房客的房间, 尽管房客可以选择任何期望的房间昵称; 相对的是半匿名房间和全匿名房间.
Open Room(开放房间) -- 任何人可以加入而不需要在成员列表中的房间; 反义词: 仅限会员的房间.
Password-Protected Room(密码保护房间) -- 一个用户必须提供正确密码才能加入的房间; 反义词: 非保密房间.
Persistent Room(持久房间) -- 如果最后一个房客退出也不会被销毁的房间; 反义词: 临时房间.
Public Room(公开房间) -- 用户可以通过普通方法如搜索和服务查询来发现的房间; 反义词: 隐藏房间.
Semi-Anonymous Room(半匿名房间) -- 一个房客的全JID只能被房间管理员发现的房间; 相对的是全匿名房间和非匿名房间.
Temporary Room(临时房间) -- 如果最后一个房客退出就会被销毁的房间; 反义词: 持久房间.
Unmoderated Room(非主持的房间) -- 任何房客都被允许发送消息给所有房客的房间; 反义词: 被主持的房间.
Unsecured Room(非保密房间) -- 任何人不需要提供密码就可以进入的房间; 反义词: 密码保护房间.
4.3 人物
本文的大部分例子使用了 the scenario of the witches' meeting held in a dark cave at the beginning of Act IV, Scene I of Shakespeare's Macbeth, 在这里代表"darkcave@macbeth.shakespeare.lit"聊天室. 人物如下:
表1: 剧中人
房间昵称 | 全 JID | 级别 |
---|---|---|
firstwitch | crone1@shakespeare.lit/desktop | 所有者 |
secondwitch | wiccarocks@shakespeare.lit/laptop | 管理员 |
thirdwitch | hag66@shakespeare.lit/pda | 无 |
角色和级别
有两个尺度我们可以用来衡量一个用户的连接或在一个房间的地位. 一个是用户和一个房间的长期的级别 -- 例如, 一个用户的状态是一个所有者或一个被排斥者. 另一个是当一个用户驻留于一个聊天室的时候的角色 -- 例如, 一个房客的地位是一个主持人,有权利踢出游客和与会者. 这两个尺度各自都是唯一的, 因为一个级别是跨越访问的, 而一个角色只存在于一次访问期间. 另外, 在角色和级别之间没有一对一的对应关系; 例如, 某个不从属于某房间的人可能成为一个(临时的)主持人, 一个成员可能在一个被主持的房间中是一个与会者或游客者. 这些概念以下全面解释.
角色
以下是已定义的角色:
表2: 角色
名称 | 支持 |
---|---|
主持人 | 必需的 |
无 | 缺少角色 |
与会者 | 必需的 |
游客 | 推荐的 |
角色是临时的,它不一定要在用户对房间的访问中持久化,它可以(MAY)在一个房客访问房间期间改变. 一个实现可以(MAY)在一次访问期间持久化角色并且应该(SHOULD)在被主持的房间这样做 (因为在游客和与会者之间,唯一性对一个被被主持的房间是很关键的).
在角色和级别之间没有一对一的映射(例如, 一个成员可以是一个与会者或一个游客).
在房间会话中,一个主持人是最有权力的房客, 它能在某种程度走上管理房间的其他房客的角色. 一个与会者的权力小于一个主持人, 尽管他或她有权发言. 在一个被主持的房间会话中游客是一个更受限制的角色, 因为访问者不允许发送消息给所有房客.
角色的授予,撤销, 和维护是基于房客的房间昵称或全JID,而不是纯JID. 和这些角色相关的权限,还有角色改变触发的动作, 定义在下文中.
所有在房间中生成或反射的出席信息中关于角色的信息必须(MUST)被发送,从而发送给房客们.
权限
大部分情况下, 角色存在于一个层次中. 例如, 一个与会者可以做任何游客能做的事, 而一个主持人可以做任何与会者能做的事. 每个角色拥有下一级角色所没有的权限; 这些权限定义于下表作为缺省值(一个实现可以(MAY)提供配置选项来重载这些缺省值).
表3: 和角色相关的权限
权限 | 无 | 游客 | 与会者 | 主持人 |
---|---|---|---|---|
在房间中出席 | 否 | 是 | 是 | 是 |
接收消息 | 否 | 是 | 是 | 是 |
接收房客出席信息 | 否 | 是* | 是 | 是 |
出席信息广播到房间 | 否 | 是* | 是 | 是 |
改变可用性状态 | 否 | 是 | 是 | 是 |
改变房间昵称 | 否 | 是* | 是 | 是 |
发送私人消息 | 否 | 是* | 是 | 是 |
邀请其他用户 | 否 | 是* | 是* | 是 |
发送消息给所有人 | 否 | 否** | 是 | 是 |
修改标题 | 否 | 否* | 是* | 是 |
踢出与会者和游客 | 否 | 否 | 否 | 是 |
授予发言权 | 否 | 否 | 否 | 是 |
撤销发言权 | 否 | 否 | 否 | 是*** |
- 缺省; 设定配置时可以(MAY)修改这个权限.
- 一个实现可以(MAY)在非主持的房间里缺省地授予发言权给游客.
- 主持人不能(MUST NOT)从一个管理员或所有者收回发言权.
变更角色
一个房客的角色变更方法是定义好的. 有时候房客自己的动作导致变更 (例如, 加入或退出房间), 反之有时候由主持人,管理员或所有者的动作导致变更. 如果一个房客的角色改变了, 一个 MUC 服务实现必须(MUST)变更这个房客的角色来反映这个变更并且传达这个变更给所有房客. 角色的变更和它们触发的动作定义于下表.
表4: 角色状态表
> | 无 | 游客 | 与会者 | 主持人 |
---|---|---|---|---|
无 | -- | 进入被主持的房间 | 进入非主持的房间 | 管理员或所有者进入房间 |
游客 | 退出房间或被主持人踢出房间 | -- | 主持人授予发言权 | 管理员或所有者授予主持人权限 |
与会者 | 退出房间或被主持人踢出房间 | -- | 管理员或所有者授予主持人权限 | |
主持人 | 退出房间 | 管理员或所有者改变角色成为游客* | 管理员或所有者改变角色成为与会者或撤销主持人权限* | -- |
- 一个主持人不能(MUST NOT)从一个级别属于等于或高于主持人的房客那里收回主持人权限.
注意: 特定的角色一般暗含特定的权限. 例如, 一个管理员或所有者自动成为一个主持人, 所以如果一个房客被授予管理员地位那么这个房客事实上将被授予主持人权限; 类似的, 当一个房客成为一个被主持的房间的成员, 这个房客自动拥有一个与会者的角色. 无论如何, 失去管理员地位并不足以意味这个房客不再是主持人 (因为只要是与会者就可能成为一个主持人). 因此, 当一个房客被授予特定的级别的时候所拥有的角色是固定的, 反之当一个房客失去一个特定的级别它的角色是不确定的并取决于(服务的)实现. 因为一个客户端无法预料是否在撤销某个级别之后这个角色成为什么, 如果它不想同时移除管理员/所有者权限和主持人角色, 那么除了级别变更之外它还必须特意请求角色变更.
级别
已定义了以下级别:
- 所有者
- 管理员
- 成员
- 被排斥者
- 无 (缺少级别)
必须支持"所有者"这个级别,推荐支持"管理员","成员","被排斥者"的级别.("无"表示缺少级别)
这些级别是长时间的跨越一个用户对这个房间的访问期间的并且不受房间里事件的影响. 而且, 这些级别和一个房客在房间中的角色之间没有一对一的映射关系. 级别被授予,撤销, 和维护都是基于这个用户的纯 JID.
如果一个没有已定义的级别的用户进入一个房间, 这个用户的级别被定义为"无"; 无论如何, 这个级别不能跨越(多次的)访问 (也就是说, 一个服务不会跨越访问维护一个 "无 列表").
"成员"级别为房间所有者或管理员提供了一个方法来指定一个"白名单",其中的用户被允许加入一个仅供会员的房间. 当一个成员加入了一个仅供会员的房间, 他或她的级别不会改变, 无论他或她的角色是什么. 成员级别也为用户提供一个方法来高效地注册一个开放的房间并在某种方式意义上保持和那个房间的联系(例如可能在房间里预留那个用户的昵称).
一个被排斥者就是一个被从房间踢出来并且不允许进入那个房间的用户.
关于级别的信息必须(MUST)由房间生成或反射到所有的出席信息节之中发送给房客们.
权限
大部分情况下, 级别存在一个层次结构. 例如, 一个所有者可以做任何管理员能做的事情, 而一个管理员可以做任何成员能做的事情. 每个级别拥有其下一级级别所没有的权限; 这些权限定义在下表中.
表5: 和级别相关的权限
权限 | Outcast(被排斥者) | None(无) | Member(成员) | Admin(管理员) | Owner(所有者) |
---|---|---|---|---|---|
进入房间 | 否 | 是* | 是 | 是 | 是 |
注册一个开放的房间 | 否 | 是 | N/A | N/A | N/A |
接收成员列表 | 否 | 否** | 是 | 是 | 是 |
加入一个仅限会员的房间 | 否 | 否 | 是* | 是 | 是 |
踢出成员并把用户的级别删除 | 否 | 否 | 否 | 是 | 是 |
编辑成员列表 | 否 | 否 | 否 | 是 | 是 |
编辑主持人列表 | 否 | 否 | 否 | 是** | 是** |
编辑管理员列表 | 否 | 否 | 否 | 否 | 是 |
编辑所有者列表 | 否 | 否 | 否 | 否 | 是 |
变更房间定义 | 否 | 否 | 否 | 否 | 是 |
销毁房间 | 否 | 否 | 否 | 否 | 是 |
- 作为缺省值, 一个无级别的用户进入一个被主持的房间的角色是一个游客, 而进入一个开放的房间的角色是一个与会者. 一个成员进入一个房间的角色是与会者. 一个管理员或所有者进入房间的角色是一个主持人.
- 一个管理员或所有者不能(MUST NOT)撤销另一个管理员或所有者的权限.
变更级别
一个用户的级别变更方法已经定义得很完善. 有时用户自己的动作导致这些变更(例如, 注册为一个房间的新成员), 反之有时候一个管理员或所有者的动作导致了这些变更. 如果一个用户的级别改变了, 一个MUC服务实现必须(MUST)变更这个用户的级别来反射这一变更并通知所有房客. 级别变更和他们触发的动作定义在下表中.
表 6: 级别状态表
被排斥者(Outcast) | 无(None) | 成员(Member) | 管理员(Admin) | 所有者(Owner) | |
被排斥者(Outcast) | -- | 管理员或所有者移除屏蔽 | 管理员或所有者增加用户到成员列表 | 所有者增加用户到管理员列表 | 所有者增加用户到所有者列表 |
无(None) | 管理员或所有者使用屏蔽 | -- | 管理员或所有者增加用户到成员列表, 或用户注册一个成员(如果允许) | 所有者增加用户到管理员列表 | 所有者增加用户到所有者列表 |
成员(Member) | 管理员或所有者使用屏蔽 | 管理员或所有者变更级别为"none" | -- | 所有者增加用户到管理员列表 | 所有者增加用户到所有者列表 |
管理员(Admin) | 所有者使用屏蔽 | 所有者变更级别为"none" | 所有者变更级别为"member" | -- | 所有者增加用户到所有者列表 |
所有者(Owner) | 所有者使用屏蔽 | 所有者变更级别为"none" | 所有者变更级别为"member" | 所有者变更级别为"admin" | -- |
实体用例
MUC的发现组件支持
一个Jabber实体可能希望发现是否一个服务实现了多用户聊天协议; 为了达到这个目的, 它发送一个服务发现信息("disco#info")查询给这组件的JID:
例子 1. 用户通过Disco查询聊天服务是否支持MUC
<iq from='hag66@shakespeare.lit/pda' id='disco1' to='macbeth.shakespeare.lit' type='get'> <query xmlns='http://jabber.org/protocol/disco#info'/> </iq>
服务必须(MUST)返回它的的身份和它所支持的特性:
例子 2. 服务返回Disco Info结果
<iq from='macbeth.shakespeare.lit' id='disco1' to='hag66@shakespeare.lit/pda' type='result'> <query xmlns='http://jabber.org/protocol/disco#info'> <identity category='conference' name='Macbeth Chat Service' type='text'/> <feature var='http://jabber.org/protocol/muc'/> </query> </iq>
注意: 因为MUC是旧的"groupchat 1.0"协议的超集, 一个MUC服务不应该(SHOULD NOT)返回一个<feature var='gc-1.0'/>条目在一个disco#info结果中.
发现房间
发现服务条目("disco#items")协议使得一个用户可以向一个服务查询相关的条目列表, 在一个聊天服务中这包含这个服务所承载的所有特定房间的集合.
例子 3. 用户向聊天服务查询房间
<iq from='hag66@shakespeare.lit/pda' id='disco2' to='macbeth.shakespeare.lit' type='get'> <query xmlns='http://jabber.org/protocol/disco#items'/> </iq>
服务应该(SHOULD)返回它承载的所有房间的列表.
例子 4. 服务返回Disco Item结果
<iq from='macbeth.shakespeare.lit' id='disco2' to='hag66@shakespeare.lit/pda' type='result'> <query xmlns='http://jabber.org/protocol/disco#items'> <item jid='heath@macbeth.shakespeare.lit' name='A Lonely Heath'/> <item jid='darkcave@macbeth.shakespeare.lit' name='A Dark Cave'/> <item jid='forres@macbeth.shakespeare.lit' name='The Palace'/> <item jid='inverness@macbeth.shakespeare.lit' name='Macbeth's Castle'/> </query> </iq>
如果全部房间的列表太大(详见[XMPP文档列表/XMPP扩展/XEP-0030]), 服务可以(MAY)只返回部分的房间列表.如果这样做了, 它应该 SHOULD 包含一个 <set/> 元素 (定义在 Result Set Management 8) 以表明这个列表不是全部的结果集.
例子 5. 服务返回Disco Item结果的部分列表
<iq from='rooms.shakespeare.lit' id='disco-rsm-1' to='hag66@shakespeare.lit/pda' type='result'> <query xmlns='http://jabber.org/protocol/disco#items'> <item jid='alls-well-that-ends-well@rooms.shakespeare.lit'/> <item jid='as-you-like-it@rooms.shakespeare.lit'/> <item jid='cleopatra@rooms.shakespeare.lit'/> <item jid='comedy-of-errors@rooms.shakespeare.lit'/> <item jid='coriolanus@rooms.shakespeare.lit'/> <item jid='cymbeline@rooms.shakespeare.lit'/> <item jid='hamlet@rooms.shakespeare.lit'/> <item jid='henry-the-fourth-one@rooms.shakespeare.lit'/> <item jid='henry-the-fourth-two@rooms.shakespeare.lit'/> <item jid='henry-the-fifth@rooms.shakespeare.lit'/> <set xmlns='http://jabber.org/protocol/rsm'> <first index='0'>alls-well-that-ends-well@rooms.shakespeare.lit</first> <last>henry-the-fifth@rooms.shakespeare.lit</last> <count>37</count> </set> </query> </iq>
查询房间信息
使用 disco#info 协议, 一个用户也可以查询一个特定房间的详情. 为了在进入房间之间确定这个房间的隐私和安全配置用户应该(SHOULD)这样做(详见 [XEP-0045#安全事项|安全事项]).
例子 6. 用户查询特定聊天室的信息
<iq from='hag66@shakespeare.lit/pda' id='disco3' to='darkcave@macbeth.shakespeare.lit' type='get'> <query xmlns='http://jabber.org/protocol/disco#info'/> </iq>
房间必须(MUST)返回它的标识并且应该(SHOULD)返回它支持的特性:
例子 7. 房间返回查询信息结果
<iq from='darkcave@macbeth.shakespeare.lit' id='disco3' to='hag66@shakespeare.lit/pda' type='result'> <query xmlns='http://jabber.org/protocol/disco#info'> <identity category='conference' name='A Dark Cave' type='text'/> <feature var='http://jabber.org/protocol/muc'/> <feature var='muc_passwordprotected'/> <feature var='muc_hidden'/> <feature var='muc_temporary'/> <feature var='muc_open'/> <feature var='muc_unmoderated'/> <feature var='muc_nonanonymous'/> </query> </iq>
注意: 因为 MUC 是旧的 "groupchat 1.0" 协议的超集, 一个 MUC 房间不应该(SHOULD NOT)在一个disco#info结果中返回<feature var='gc-1.0'/>条目. 房间应该(SHOULD)返回它支持的实质的有意义的特性, 例如密码保护和房间主持(这些特性被完整地列入了特性注册, 由XMPP Registrar维护; 也见于本文的XMPP注册 章节).
一个聊天室可以(MAY)使用服务查询扩展 9在它的disco#info应答中返回更详细的信息, 通过包含一个隐含的FORM_TYPE属性值"http://jabber.org/protocol/muc#roominfo"来标识. 这些信息可能包括关于一个房间的更详细的描述, 当前的房间标题, 以及这个房间当前的房客数量:
例子 8. 房间返回扩展的查询信息结果
<iq from='darkcave@macbeth.shakespeare.lit' id='disco3a' to='hag66@shakespeare.lit/pda' type='result'> <query xmlns='http://jabber.org/protocol/disco#info'> <identity category='conference' name='A Dark Cave' type='text'/> <feature var='http://jabber.org/protocol/muc'/> <feature var='muc_passwordprotected'/> <feature var='muc_hidden'/> <feature var='muc_temporary'/> <feature var='muc_open'/> <feature var='muc_unmoderated'/> <feature var='muc_nonanonymous'/> <x xmlns='jabber:x:data' type='result'> <field var='FORM_TYPE' type='hidden'> <value>http://jabber.org/protocol/muc#roominfo</value> </field> <field var='muc#roominfo_description' label='Description'> <value>The place for all good witches!</value> </field> <field var='muc#roominfo_changesubject' label='Whether Occupants May Change the Subject'> <value>true</value> </field> <field var='muc#roominfo_contactjid' label='Contact Addresses'> <value>crone1@shakespeare.lit</value> </field> <field var='muc#roominfo_subject' label='Subject'> <value>Spells</value> </field> <field var='muc#roominfo_occupants' label='Number of occupants'> <value>3</value> </field> <field var='muc#roominfo_lang' label='Language of discussion'> <value>en</value> </field> <field var='muc#roominfo_logs' label='URL for discussion logs'> <value>http://www.shakespeare.lit/chatlogs/darkcave/</value> </field> <field var='muc#roominfo_pubsub' label='Associated pubsub node'> <value>xmpp:pubsub.shakespeare.lit?node=chatrooms/darkcave</value> </field> </x> </query> </iq>
某些扩展的房间信息可能是动态生成的(例如, 讨论记录的URL地址, 它可能取决于服务器那一层的配置); 反之另一些信息则可能基于房间那一层的配置,任何定义在muc#roomconfig FORM_TYPE 里的字段都可以用于扩展服务发现的字段(如上文所示的 muc#roomconfig_changesubject 字段).
注意: 前述 'http://jabber.org/protocol/muc#roominfo' FORM_TYPE的扩展服务发现字段将来还可以扩充(通过本文的字段标准化章节描述的机制).
查询房间条目
一个用户也可以(MAY)向一个特定的聊天室查询和它相关的条目:
例子 9. 用户查询和一个特定聊天室相关的条目
<iq from='hag66@shakespeare.lit/pda' id='disco4' to='darkcave@macbeth.shakespeare.lit' type='get'> <query xmlns='http://jabber.org/protocol/disco#items'/> </iq>
一个实现可以(MAY)返回现有房客的列表(如果那信息是可公开的), 或不返回列表(如果那信息是私有的).
例子 10. 房间返回查询条目结果(条目是公开的)
<iq from='darkcave@macbeth.shakespeare.lit' id='disco4' to='hag66@shakespeare.lit/pda' type='result'> <query xmlns='http://jabber.org/protocol/disco#items'> <item jid='darkcave@macbeth.shakespeare.lit/firstwitch'/> <item jid='darkcave@macbeth.shakespeare.lit/secondwitch'/> </query> </iq>
注意: 这些 <item/> 元素由 disco#items 名字空间限定, 而不是 muc 名字空间; 这意味着他们不能拥有 'affiliation' 或 'role' 属性, 例如.
例子 11. 房间返回空的查询条目结果(条目是私有的)
<iq from='darkcave@macbeth.shakespeare.lit' id='disco4' to='hag66@shakespeare.lit/pda' type='result'> <query xmlns='http://jabber.org/protocol/disco#items'/> </iq>
查询一个房间的房客
如果一个非房客试图发送一个查询请求给一个<room@service/nick>类型的地址, 一个 MUC 服务应该(SHOULD)返回这个请求给这个实体并指明一个<bad-request/>错误条件. 如果一个房客发送这样一个请求, 服务可以(MAY)把它传递给指定的接收者; 详见本文的 实施指南章节.
发现客户端对MUC的支持
一个 Jabber 用户可能想发现这个用户的某个联系人是否支持多用户聊天协议. 这可以使用服务发现(协议)来完成.
例子 12. 用户查询联系人对于 MUC 的支持
<iq from='hag66@shakespeare.lit/pda' id='disco5' to='wiccarocks@shakespeare.lit/laptop' type='get'> <query xmlns='http://jabber.org/protocol/disco#info'/> </iq>
客户端应该(SHOULD)返回它的标识和它支持的特性:
例子 13. 联系人返回发现信息结果
<iq from='wiccarocks@shakespeare.lit/laptop' id='disco5' to='hag66@shakespeare.lit/pda' type='result'> <query xmlns='http://jabber.org/protocol/disco#info'> <identity category='client' type='pc'/> ... <feature var='http://jabber.org/protocol/muc'/> ... </query> </iq>
一个用户也可能查询一个联系人在哪个房间. 这可以通过特定服务发现节点 'http://jabber.org/protocol/muc#rooms' 查询联系人的全JID(<user@host/resource>)来完成 :
例子 14. 用户在当前房间查询联系人
<iq from='hag66@shakespeare.lit/pda' id='rooms1' to='wiccarocks@shakespeare.lit/laptop' type='get'> <query xmlns='http://jabber.org/protocol/disco#items' node='http://jabber.org/protocol/muc#rooms'/> </iq>
例子 15. 联系人返回房间查询结果
<iq from='wiccarocks@shakespeare.lit/laptop' id='rooms1' to='hag66@shakespeare.lit/pda' type='result'> <query xmlns='http://jabber.org/protocol/disco#items' node='http://jabber.org/protocol/muc#rooms'/> <item jid='darkcave@macbeth.shakespeare.lit'/> <item jid='characters@conference.shakespeare.lit'/> </query> </iq>
可选的, 联系人可以(MAY)把它的房间昵称作为'name'属性的值返回:
... <item jid='darkcave@macbeth.shakespeare.lit' name='secondwitch'/> ...
房客用例
在一个多用户聊天环境中主要的行为者是房客, 它可以被认为存在于一个多用户聊天室"之内"并且参与那个房间的讨论 (在本协议中, 与会者和游客"仅仅"被认为是房客, 因为他们不拥有管理员权限). 为了更加清晰起见, 本文中的协议元素中涉及到驻留者的用例分为以下三类:
- 现存于 "groupchat 1.0" 协议的最小功能集
- 对于 "groupchat 1.0" 协议直接的应用, 如处理一些和新房间类型有关的错误
- 用来处理"groupchat 1.0"协议未涉及的功能的额外的协议元素(房间邀请, 房间密码, 和房间角色及级别相关的扩展出席信息); 在'http://jabber.org/protocol/muc#user'名字空间
注意: 这里所有客户端生成的例子是从服务的角度来展示的, 所以所有由服务收到的节都包含一个'from'属性来表达发送者的全JID(这个from属性是由一个通用的Jabber路由或会话管理者加入的). 另外, 通常的表示请求已被完成的 IQ 结果节(如 RFC 3920 [10]中所要求的)未显示在这里.
进入一个房间
Groupchat 1.0协议
为了参加一个多用户聊天室的讨论, 一个Jabber用户必须(MUST)首先进入一个房间成为一个房客. 在旧的"groupchat 1.0"协议中, 这是通过发送出席信息<room@service/nick>来实现的, 这里"room"是房间的 ID, "service" 是聊天服务的主机名, "nick" 是这个用户在这房间里预期的昵称:
例子 16. Jabber用户进入一个房间(Groupchat 1.0)
<presence from='hag66@shakespeare.lit/pda' to='darkcave@macbeth.shakespeare.lit/thirdwitch'/>
在这个例子中, 一个全JID为"hag66@shakespeare.lit/pda"的用户请求用昵称"thirdwitch"进入位于"macbeth.shakespeare.lit"聊天服务的房间"darkcave".
如果用户未指定一个房间昵称, 服务应该(SHOULD)返回一个<jid-malformed/>错误:
例子 17. Jabber用户进入一个房间(Groupchat 1.0)
<presence from='darkcave@macbeth.shakespeare.lit' to='hag66@shakespeare.lit/pda' type='error'> <error code='400' type='modify'> <jid-malformed xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> </error> </presence>
基本MUC协议
兼容的多用户聊天服务必须(MUST)接受知道"groupchat 1.0" (GC)协议或multi-user chat (MUC)协议的任何客户端发出上述请求进入会议室; 无论如何, MUC 客户端应该(SHOULD)声明他们的有能力支持 MUC 协议, 方法是在出席信息节里面包含一个空的 <x/> 元素, 满足名字空间 'http://jabber.org/protocol/muc' (注意不需要 '#user' 部分):
例子 18. Jabber用户准备进入一个房间(Multi-User Chat)
<presence from="hag66@shakespeare.lit/pda" to='darkcave@macbeth.shakespeare.lit/thirdwitch'> <x xmlns='http://jabber.org/protocol/muc'/> </presence>
注意: 如果发生了一个和加入房间有关的错误, 服务应该 SHOULD 返回一个包含 MUC 子元素 (i.e., <x xmlns='http://jabber.org/protocol/muc'/>) 的 <presence/> 节,其 type 为 "error".
在尝试进入房间之间, 一个兼容MUC的客户端应该(SHOULD)首先查询它的保留的房间昵称 (如果有的话), 接下来的协议本文中的 发现保留的房间昵称 章节对此作了定义.
出席信息广播
如果服务能够添加用户到房间, 它必须(MUST)从所有现存的房客的房间JID发送出席信息给新的房客的全JID, 包括扩展的关于角色的出席信息, 一个满足 'http://jabber.org/protocol/muc#user' 名字空间的<x/> 元素并包含一个<item/>子元素, 这个子元素的'role'属性值设为"moderator", "participant", 或"visitor", 这个子元素的'affiliation'属性值设为"owner", "admin", "member", 或 "none" 中的一个:
例子 19. 服务从现有的房客发送出席信息给新的房客
<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"的(房间管理员).
服务也必须(MUST)从新进入的房客的房间JID向所有房客的全JID发送出席信息(含新房客):
例子 20. 服务发送新房客的出席信息给所有房客
<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>
在这个例子里, 初始的房间出席信息从新房客(thirdwitch)发送给所有房客, 包括这个新房客自己. 看看上面最后一个节, 由房间以房客的名义发送给用户自己的出席信息,应该 SHOULD 包含一个 110 状态码,这样用户就知道这个出席信息来自于作为房客的那个他自己.
服务可以 MAY 重写新房客的房间昵称 (例如, 如果房间昵称被锁定). 如果服务不接受新房客请求的房间昵称,而是分配一个新的房间昵称, 它必须 MUST 包含一个 "210" 状态码在发送给这个新房客的出席信息广播里.
例子 21. 服务发送新房客的出席信息给新房客
<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'/> <status code='210'/> </x> </presence>
注意: 发送给新房客的出席信息的顺序是很重要的. 服务必须 MUST 首先发送现有房客的完整列表给这个新房客,然后只发送新房客自己的出席信息给新房客. 这有助于客户端知道什么时候它收到了完整的房间名册( "room roster").
发送出席信息广播之后(并且只在这之后), 服务可以发送讨论历史, 即时消息, 出席信息更新, 以及其他房间内的流量.
缺省角色
下表总结了初始缺省的角色,一个服务应该根据用户的级别来设置它们(没有和 被排斥者 "outcast" 级别相关的角色, 因为这些用户不允许进入房间).
表7: 基于级别的初始角色
房间类型 | 无 | 成员 | 管理员 | 所有者 |
---|---|---|---|---|
被主持的 | 游客 | 与会者 | 主持人 | 主持人 |
非主持的 | 与会者 | 与会者 | 主持人 | 主持人 |
仅限会员的 | N/A * | 与会者 | 主持人 | 主持人 |
开放的 | 与会者 | 与会者 | 主持人 | 主持人 |
- 实体不被允许.
非匿名房间
如果房间是非匿名的, 服务必须 MUST 发送新房客的全JID给所有房客,使用满足 'http://jabber.org/protocol/muc#user' 名字空间的扩展出席信息,其中带有 <x/> 元素并包含一个 <item/> 子元素,其 'jid' 属性值为这个房客的全JID:
例子 22. 服务发送全JID给所有房客
<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> [ ... ]
如果这个用户正在进入一个非匿名房间(即, 它如上所示,向所有房客通报每个房客的全JID), 服务应该 SHOULD 允许该用户加入本房间,但是必须 MUST 同时警告该用户本房间是非匿名的. 应该 SHOULD 在房间发送给这个新房客的初始出席信息种包含状态码 "100" 来实现这一点:
例子 23. 服务发送新房客的出席信息给新房客
<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='210'/> </x> </presence>
无论如何, 也可以 MAY 发送一个 "groupchat" 类型的消息给新房客来达到上述目的,这个消息应该包含一个 <x/> 子元素,并拥有 <status/> 子元素,并且其'code'属性值为"100":
例子 24. 服务警告新房客(该房间)非匿名
<message from='darkcave@chat.shakespeare.lit' to='hag66@shakespeare.lit/pda' type='groupchat'> <body>This room is not anonymous.</body> <x xmlns='http://jabber.org/protocol/muc#user'> <status code='100'/> </x> </message>
附带的状态码协助客户端展示它们自己的通知消息 (例如, 和用户所在地方有关的信息).
半匿名房间
如果房间是半匿名的, 服务必须 MUST 如上文所述从新房客发送出席信息给所有房客, 但是必须 MUST 只在发给"主持人"的时候发送新房客的全JID,而非主持人则不发(全JID).
(注意: 所有随后的例子中,涉及的<item/>元素都带有'jid'属性, 即使这个信息在半匿名房间里不被发送给非主持人.)
密码保护房间
如果房间要求密码验证而用户不能提供(或密码错误), 服务必须 MUST 拒绝访问这个房间并且通知该用户它们是未被授权的; 具体方法是返回一个类型为"error"的出席信息节并标明 <not-authorized/> 错误:
例子 25. 服务拒绝访问,因为(用户)未提供密码
<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>
密码应该 SHOULD 通过进入房间时发送的出席信息节来提供, 包含在满足 'http://jabber.org/protocol/muc' 名字空间的 <x/> 元素的<password/> 子元素里. 密码以明码方式发送; 目前不支持其它验证方法, 而且任何这类的验证或授权方法都将会定义在一个独立的协议里(参见本文的安全事项章节).
例子 26. 用户进入房间时提供密码
<presence from='hag66@shakespeare.lit/pda' to='darkcave@chat.shakespeare.lit/thirdwitch'> <x xmlns='http://jabber.org/protocol/muc'> <password>cauldronburn</password> </x> </presence>
仅限会员房间
如果房间是仅限会员的,但用户不是(该房间的)成员, 服务必须 MUST 拒绝访问这个房间并通知用户它们不被允许进入房间; 具体方法是返回一个"error"类型的出席信息节,并包含一个 <registration-required/> 错误条件:
例子 27. 服务拒绝访问,因为用户不在成员列表中
<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>
被禁止的用户
如果用户已经被房间禁止(即, 其级别为被排斥者 "outcast"), 服务必须 MUST 拒绝访问这个房间并通知用户他(她)被禁止了; 具体方法是返回一个出席信息节,类型为"error",标明 <forbidden/> 错误条件:
例子 28. 服务拒绝访问,因为用户被禁止了
<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>
昵称冲突
如果房间里已经有别的用户使用了准备进入房间的新用户预期的昵称(或如果这个昵称被保留给另一个成员列表里面的用户), 服务必须 MUST 拒绝访问这个房间并通知用户这个冲突; 具体方法是返回一个出席信息节,类型为"error",标明 <conflict/> 错误条件:
例子 29. 服务拒绝访问,因为昵称冲突
<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>
无论如何, 如果现有房客的纯 JID <localpart@domain.tld> 和准备进入房间的用户的纯 JID 相同, 那么服务应该 SHOULD 允许这个用户的进入, 所以这个用户就有两个(或更多) 房间内的会话 "sessions" 使用同一个房间昵称, 每一个对应一个资源. 如果一个服务允许相同纯JID可以同时存在多个房客并使用同一个房间的房间昵称, 它应该 SHOULD 路由房间内的消息给该用户的所有资源并允许用户的所有资源发送消息给房间; 视实现而定,服务来决定如何适当的处理从用户的资源发送的出席信息以及如何路由私有消息到所有或某个资源(基于出席信息优先级或其他机制).
如何确定昵称冲突取决于实现(例如, 该服务是否应用于一个特定的惯例, 一个 stringprep 规则如 Resourceprep 或 Nodeprep, 等等).
最大用户数
如果房间达到它的最大房客数量, 服务应该 SHOULD 拒绝访问这个房间并通知该用户这个限制; 方法是返回一个出席信息节,类型为"error",标明 <service-unavailable/> 错误条件:
例子 30. 服务通知用户该房间已达到房客数量极限
<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>
另外, 房间可以踢出空闲用户("idle user")以腾出空间.
如果房间的房客数量已达到最大值但是一个房间管理员或所有者试图进入,该房间应该允许管理员或所有者加入,为了使得额外的房客达到一个合理的数目,该数量可以 MAY 做成可配置的。
锁住的房间
如果一个用户尝试进入一个房间而该房间是锁住的 "locked" (即, 在房间创建者提供初始的配置之前以及也就是在房间正式存在之前), 服务必须 MUST 拒绝进入并返回一个 <item-not-found/> 错误给该用户:
例子 31. 服务拒绝访问,因为房间不存在
<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>
不存在的房间
如果用户准备进入房间时,该房间已经不存在了, 服务应该 SHOULD 建立它; 无论如何, 这不是必需的, 因为一个实现或部署可以 MAY 选择限制建立房间的权限. 详见本文的创建一个房间章节.
房间记录
如果用户进入一个房间,该房间的讨论是被记录到一个公开的存档里面(经常可以通过HTTP访问的), 服务应该 SHOULD 允许该用户加入该房间但是必须 MUST 同时警告该用户讨论已被记录. 方法是应该 SHOULD 在房间发送给该新房客的初始出席信息中包含一个状态码 "170":
例子 32. 服务发送新房客的出席信息给新房客
<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>
讨论历史
如上发送完初始出席信息之后, 一个房间可以 MAY 发送讨论历史给这个新房客. (在完成按照本文出席信息广播章节规定的发送房间出席信息之前,该房间不能 MUST NOT 发送任何讨论历史.) 是否这个历史要被发送, 以及这个历史里面包含多少条消息, 将由聊天服务实现或特定的部署来决定.
例子 33. 讨论历史的发送
<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>
讨论历史消息必须 MUST 标为Delayed Delivery 11信息,满足'urn:xmpp:delay' 名字空间,以表明它们是被延迟发送的并且标明它们最初发出的时间. (注意: 'urn:xmpp:delay' 明子空间定义在 XEP-0203 里面,取代了旧的定义在 Legacy Delayed Delivery 12 里的 'jabber:x:delay' 名字空间 ; 直到XEP - 0091状态更改为已过时, 实现应该 SHOULD 包含两种日期时间(datetime)格式.). 在非匿名房间里,'from'属性应该 SHOULD 是原始发送者的全JID, 但不能 MUST NOT 在半匿名房间里(在那里'from'属性应该 SHOULD 设置为房间本身的JID). 服务应该 SHOULD 在进入该房间之后,发送任何即时("live")消息之前,发送完所有讨论历史消息.
管理讨论历史
用户可能 MAY 希望管理进入房间时(由房间)提供的讨论历史(可能因为用户带宽比较低或正在使用迷你客户端). 他必须 MUST 在加入房间时发出的初始出席信息节里包含一个 <history/> 子元素. 这个元素有四个可用的属性:
表 8: 历史管理属性
属性 | 数据类型 | 含义 |
---|---|---|
maxchars | int | 限制历史中的字符总数为"X" (这里的字符数量是全部 XML 节的字符数, 不只是它们的 XML 字符数据). |
maxstanzas | int | 限制历史中的消息总数为"X". |
seconds | int | 仅发送最后 "X" 秒收到的消息. |
since | dateTime | 仅发送从指定日期时间 datetime 之后收到的消息 (这个datatime必须 MUST 符合XMPP Date and Time Profiles 13 定义的DateTime 规则,). |
服务必须 MUST 发送满足以上条件组合的最小数量的消息, 还要顾及服务级别和房间级别的缺省设置. 服务必须 MUST 只发送完整的消息节(即, 它不能 MUST not 按特定字符数把历史从字面上截断, 但是必须 MUST 发送最大数量的完整节,这使得字符数小于或等于 'maxchars' 属性的值). 如果客户端不希望收到历史, 它必须 MUST 把'maxchars' 属性值设为"0" (zero).
以下例子展示如何使用这个协议.
例子 34. 用户请求在历史中限制消息数量
<presence from='hag66@shakespeare.lit/pda' to='darkcave@chat.shakespeare.lit/thirdwitch'> <x xmlns='http://jabber.org/protocol/muc'> <history maxstanzas='20'/> </x> </presence>
例子 35. 用户请求最后三分钟的历史
<presence from='hag66@shakespeare.lit/pda' to='darkcave@chat.shakespeare.lit/thirdwitch'> <x xmlns='http://jabber.org/protocol/muc'> <history seconds='180'/> </x> </presence>
例子 36. 用户请求从Unix时代到现在的所有历史
<presence from='hag66@shakespeare.lit/pda' to='darkcave@chat.shakespeare.lit/thirdwitch'> <x xmlns='http://jabber.org/protocol/muc'> <history since='1970-01-01T00:00:00Z'/> </x> </presence>
服务绝对不应该 SHOULD NOT 返回从Unix时代开始到现在的所有消息, 而应该 SHOULD 基于服务或房间的缺省值返回适当的有限数量的历史给用户.
例子 37. 用户请求不发送历史
<presence from='hag66@shakespeare.lit/pda' to='darkcave@chat.shakespeare.lit/thirdwitch'> <x xmlns='http://jabber.org/protocol/muc'> <history maxchars='0'/> </x> </presence>
退出一个房间
为了退出一个多用户聊天房间, 一个房客发送一个类型为"unavailable"的出席信息节给正在使用这个房间的 <room@service/nick> .
例子 38. 房客退出一个房间
<presence from='hag66@shakespeare.lit/pda' to='darkcave@chat.shakespeare.lit/thirdwitch' type='unavailable'/>
服务必须 MUST 接着从要离开的房客的房间JID发送"unavailable"类型的出席信息节给这个要离开的房客的全JID们以及留在房间的房客们:
例子 39. 服务发送和离开的房客有关的出席信息
<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>
由房间反射的类型为"unavailable"的出席信息节必须 MUST 包含扩展的关于角色和级别的出席信息; 'role'属性值应该 SHOULD 被设为 "none" 以表示这个人不再是一个房客了.
房客可以 MAY 在出席信息节包含一个常规的 <status/> 信息; 这使房客能在必要的情况下提供一个自定的退出消息:
例子 40. 自定的退出消息
<presence from='wiccarocks@shakespeare.lit/laptop' to='darkcave@chat.shakespeare.lit/oldhag' type='unavailable'> <status>gone where the goblins go</status> </presence>
常规的出席信息节生成规则定义在 XMPP IM 14, 所以如果用户发送一个一般的不可用出席信息节, 用户的服务器将广播那个节到 <room@service/nick> ,而该用户之前曾经发送过直接出席信息给这个<room@service/nick>.
有可能一个用户不能正常地通过直接发送不可用信息给一个房间来退出该房间. 如果该用户没有发送不可用出席信息就下线了, 用户的服务器负责代替该用户发送不可用出席信息 (依据 RFC 3921). 如果该用户的服务器下线或该用户的服务器和该用户连接的MUC服务失去连接(例如, 在联邦通信), 这个MUC服务负责监视它收到的错误信息节以确定该用户是否下线. 如果该MUC服务确定该用户已下线, 它必须 must 当成该用户自己发送了不可用信息一样地处理这个用户.
注意: 如果房间不是持久的并且该房客是最后一个退出的, 服务负责销毁这个房间.
更改昵称
多用户聊天室的一个常用功能是一个房客能修改自己在房间里的昵称. 在 MUC 里这需要发送一个更新出席信息给房间, 具体来说是在相同的房间里发送出席信息给一个新的房间JID (变更的只是这个房间JID的资源).
例子 41. 房客修改昵称
<presence from='hag66@shakespeare.lit/pda' to='darkcave@chat.shakespeare.lit/oldhag'/>
服务接着发送两个出席信息节给每个房客的全JID(包括修改自己昵称的房客本身), 一个是类型为"unavailable"的用于旧的昵称另一个指明新昵称可用了.
这个不可用出席信息必须 MUST 在一个满足'http://jabber.org/protocol/muc#user' 名字空间的 <x/> 子元素里面包含以下扩展的出席信息 :
- 新昵称(在这个例子中, nick='oldhag')
- 一个状态码 303
这使接受者能从旧昵称关联到新昵称.
例子 42. 服务更新昵称
<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>
如果该用户尝试修改他或她的房间昵称,但这个昵称已经被其他用户使用了 (或者这个昵称是被这房间的其他用户级别保留的, 例如, 一个成员或者所有者), 服务必须 MUST 拒绝这次昵称修改并通知该用户这一冲突; 也就是返回一个类型为 "error" 的出席信息节指明 <conflict/> 错误条件:
例子 43. 服务拒绝昵称修改,因为昵称冲突
<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>
无论如何, 如果现有房客的纯JID <localpart@domain.tld> 和尝试变更昵称的房客的纯JID相同, 那么服务可以 MAY 允许昵称变更. 详见本文的昵称冲突章节.
如果该用户尝试变更自己的昵称但是房间昵称被锁定了("locked down"), 服务必须 MUST 拒绝这个昵称变更请求并返回一个"error"类型的出席信息节,指明一个 <not-acceptable/> 错误条件:
例子 44. 服务拒绝昵称变更,因为房间昵称被锁定
<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>
用户应该 SHOULD 接着发现它的保留昵称,如本文的 发现保留的房间昵称章节所述.
更改可用性状态
在一个多用户聊天系统里例如IRC, 一个常用的修改某人房间昵称的行为也意味着变更某人的可用性(例如, 变更某人的房间昵称为"thirdwitch|away"). 在Jabber里面, 可用性当然是通过出席信息 (中 <show/> 和 <status/> 元素)的变更来通知的, 这能提供重要的上下文给聊天室. 一个房客通过发送更新的出席信息给它自己的<room@service/nick>来改变他在房间内的可用性状态.
例子 45. 房客变更可用性状态
<presence from='wiccarocks@shakespeare.lit/laptop' to='darkcave@chat.shakespeare.lit/oldhag'> <show>xa</show> <status>gone where the goblins go</status> </presence>
服务然后从该房客发送一个出席信息节来修改他或她的出席信息给每个房客的全JID, 包含扩展的出席信息,包括这个房客的角色和全JID(给那些有权知道的人):
例子 46. 服务传递修改的出席信息给所有房客
<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> [ ... ]
邀请其他用户进入一个房间
直接邀请
一个办法是发送一个直接的邀请(而不是由房间本身来间接邀请),定义在Direct MUC Invitations [XEP-0045#附录G:备注|15]]. 直接发送邀请有助于适应被邀请者那一边的通信阻塞(对方可能拒绝和和不在好友名单中的实体通信).
间接邀请
邀请别的用户到一个房间成为房客是很有用的. 为了做到这一点, 一个 MUC 客户端必须 MUST 发送以下格式的 XML 给 <room@service> 本身 (原因(reason)是可选的 OPTIONAL 而消息(message)的类型必须 MUST 是显式或隐式的"normal"类型):
例子 47. 房客通过房间发送一个邀请
<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>
<room@service> 本身必须 MUST 接着增加一个 'from' 地址到 <invite/> 元素,其值为邀请者的纯JID, 全JID, 或房间JID,并发送邀请给 'to' 地址所指明的被邀请者(为了旧的客户端,服务可以 MAY 包含一个消息主体"message body"解释这个邀请或包含一个原因"reason"(子元素); 另外, 房间应该 SHOULD 增加 password 如果该房间是密码保护的):
例子 48. 房间代表邀请者发送邀请给被邀请者
<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>
如果房间是仅限成员的, 服务可以 MAY 同时把这个被邀请者加入成员列表. (注意: 在仅限成员的房间里邀请的权力应该 SHOULD 由房间管理员限定; 如果一个没有权限的成员修改成员列表试图邀请别的用户, 服务应该 SHOULD 返回一个 <forbidden/> 错误给该房客; 详见本文的修改成员列表章节.)
如果邀请者提供了一个不存在的JID, 房间应该 SHOULD 返回一个 <item-not-found/> 错误给邀请者.
被邀请者可以 MAY 选择正式地拒绝 (反之则忽略) 邀请; 这是发送者希望看到的正式的通知. 为了拒绝这个邀请, 被邀请者必须 MUST 发送以下格式的消息给 <room@service> 本身:
例子 49. 被邀请者谢绝邀请
<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>
例子 50. 房间通知邀请者邀请被拒绝了
<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>
可能(有人)想知道为什么被邀请者不直接发送拒绝消息给访问者. 主要原因是特定的实现可能 MAY 选择让邀请基于房间JIDs而不是纯JIDs (所以, 例如, 一个房客可能从一个房间邀请某人到另一个房间而不需要知道这个人的纯JID). 因而服务必须 MUST 同时处理邀请和拒绝.
把一对一聊天转为多用户会议
有时候人们需要把一个一对一的聊天转成一个多用户的会议. 以下例子展示了这个流程.
首先, 两个用户开始一个一对一聊天.
例子 51. 一个一对一聊天
<message from='crone1@shakespeare.lit/desktop' to='wiccarocks@shakespeare.lit/laptop' type='chat'> <thread>e0ffe42b28561960c6b12b944a092794b9683a38</thread> <body>Thrice the brinded cat hath mew'd.</body> </message> <message from='wiccarocks@shakespeare.lit/laptop' to='crone1@shakespeare.lit/desktop' type='chat'> <thread>e0ffe42b28561960c6b12b944a092794b9683a38</thread> <body>Thrice and once the hedge-pig whined.</body> </message>
现在第一个用户决定加入第三个人到这个讨论, 所以她 (或, 更准确地说, 她的客户端) 做以下事情:
- 新建一个多用户聊天室
- 可选地发送一对一聊天的历史到房间
- 发送一个邀请给第二个人和第三个人, 包含一个 <continue/> 元素 (可选地包含一个 'thread' 属性).
注意: 新房间应该 SHOULD 是非匿名的, 可以 MAY 是一个即时房间(定义于本文的新建即时房间章节), 也可以 MAY 有一个从服务接收的唯一房间名(定义于本文的请求唯一的房间名章节.
注意: 如果这个一对一的聊天消息包含了一个 <thread/> 元素, 这个新建房间的人应该 SHOULD 在历史消息中包含这个 ThreadID, 在邀请中把这个 ThreadID 的值赋予 <continue/> 元素的 'thread' 属性, 并把这 ThreadID 包含在任何新的消息中发送到房间. ThreadIDs 的使用是推荐的 RECOMMENDED ,因为它帮助提供一对一聊天和多用户聊天的连续性.
例子 52. 继续讨论 I: 用户新建房间
<presence from='crone1@shakespeare.lit/desktop' to='darkcave@chat.shakespeare.lit/firstwitch'> <x xmlns='http://jabber.org/protocol/muc'/> </presence> <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'/> </x> </presence>
例子 53. 继续讨论 II: 所有者发送历史到房间
<message from='crone1@shakespeare.lit/desktop' to='darkcave@chat.shakespeare.lit' type='groupchat'> <thread>e0ffe42b28561960c6b12b944a092794b9683a38</thread> <body>Thrice the brinded cat hath mew'd.</body> <delay xmlns='urn:xmpp:delay' from='crone1@shakespeare.lit/desktop' stamp='2004-09-29T01:54:37Z'/> </message> <message from='crone1@shakespeare.lit/desktop' to='darkcave@chat.shakespeare.lit' type='groupchat'> <thread>e0ffe42b28561960c6b12b944a092794b9683a38</thread> <body>Thrice and once the hedge-pig whined.</body> <delay xmlns='urn:xmpp:delay' from='wiccarocks@shakespeare.lit/laptop' stamp='2004-09-29T01:55:21Z'/> </message>
注意: 使用 Delayed Delivery 协议使房间创建者能够从他一对一聊天历史指明每个消息的日期时间 datetime (通过 'stamp' 属性), 以及每个消息的原始发送者的 JID (通过'from' 属性). 房间创建者应该 SHOULD 在邀请额外的用户到房间之前发送完整的一对一聊天历史, 并且也应该 SHOULD 把第二个人加入该房间之前和第一个人在一对一聊天界面中出现的任何消息当成历史来发送; 如果这个一对一历史特别的大, 发送的客户端可能希望在数秒内发送这个历史而不是一次性发送所有历史(以to 避免触发频率限制). 服务不应该 SHOULD NOT 在从房间所有者接收的历史消息之前添加它自己的延迟元素"delay elements" (见本文的讨论历史章节) .
例子 54. 继续讨论 III: 所有者发送邀请(们), 包含 Continue 标志
<message from='crone1@shakespeare.lit/desktop' to='darkcave@chat.shakespeare.lit'> <x xmlns='http://jabber.org/protocol/muc#user'> <invite to='wiccarocks@shakespeare.lit/laptop'> <reason>This coven needs both wiccarocks and hag66.</reason> <continue thread='e0ffe42b28561960c6b12b944a092794b9683a38'/> </invite> <invite to='hag66@shakespeare.lit'> <reason>This coven needs both wiccarocks and hag66.</reason> <continue thread='e0ffe42b28561960c6b12b944a092794b9683a38'/> </invite> </x> </message>
注意: 当邀请者的客户端一知道和它一对一聊天的那个人的全JID之后, 它就应该 SHOULD 在邀请中包含这个全JID (而不是纯JID).
邀请被递送到被邀请者:
例子 55. 邀请被递送
<message from='darkcave@chat.shakespeare.lit'> to='wiccarocks@shakespeare.lit/laptop'> <x xmlns='http://jabber.org/protocol/muc#user'> <invite from='crone1@shakespeare.lit'> <reason>This coven needs both wiccarocks and hag66.</reason> <continue thread='e0ffe42b28561960c6b12b944a092794b9683a38'/> </invite> </x> </message> <message from='darkcave@chat.shakespeare.lit'> to='hag66@shakespeare.lit'> <x xmlns='http://jabber.org/protocol/muc#user'> <invite from='crone1@shakespeare.lit'> <reason>This coven needs both wiccarocks and hag66.</reason> <continue thread='e0ffe42b28561960c6b12b944a092794b9683a38'/> </invite> </x> </message>
当客户端被 <wiccarocks@shakespeare.lit/laptop> 用来接收邀请, 它应该 SHOULD 自动加入或提示用户是否加入 (取决于用户的选项配置) 并且随后无缝地把现有的一对一聊天窗口转到一个多用户会议的窗口:
例子 56. 被邀请者接受邀请, 加入房间, 并接收出席信息和历史
<presence from='wiccarocks@shakespeare.lit/laptop' to='darkcave@chat.shakespeare.lit/secondwitch'> <x xmlns='http://jabber.org/protocol/muc'/> </presence> <presence from='darkcave@chat.shakespeare.lit/firstwitch' to='wiccarocks@shakespeare.lit/laptop'> <x xmlns='http://jabber.org/protocol/muc#user'> <item affiliation='owner' role='moderator'/> </x> </presence> <presence from='darkcave@chat.shakespeare.lit/secondwitch' to='wiccarocks@shakespeare.lit/laptop'> <x xmlns='http://jabber.org/protocol/muc#user'> <item affiliation='member' role='participant'/> </x> </presence> <message from='darkcave@chat.shakespeare.lit' to='wiccarocks@shakespeare.lit/laptop' type='groupchat'> <thread>e0ffe42b28561960c6b12b944a092794b9683a38</thread> <body>Thrice the brinded cat hath mew'd.</body> <delay xmlns='urn:xmpp:delay' from='crone1@shakespeare.lit/desktop' stamp='2004-09-29T01:54:37Z'/> </message> <message from='darkcave@chat.shakespeare.lit' to='wiccarocks@shakespeare.lit/laptop' type='groupchat'> <thread>e0ffe42b28561960c6b12b944a092794b9683a38</thread> <body>Thrice and once the hedge-pig whined.</body> <delay xmlns='urn:xmpp:delay' from='wiccarocks@shakespeare.lit/laptop' stamp='2004-09-29T01:55:21Z'/> </message>
注意: 事实上,这些消息从 <room@service> 本身而不是 <room@service/nick> 发出,告诉这些接收的客户端这些消息是优先的聊天历史, 因为任何来自房客的消息的 'from' 地址应该等于发送者的房间JID.
房客修改房间标题
如果房间配置允许, 一个房客可以 MAY 被允许修改一个房间的主题. 详见本文的修改房间主题章节.
发送私有消息
因为每个房客有一个唯一的房间JID, 一个房客可以 MAY 发送一个私有消息 "private message" 给选定的房客,即通过服务发送一个消息给那房客的房间JID. 这个消息类型应该 SHOULD 是 "chat" 并且不能 MUST NOT 是 "groupchat", 但是可以 MAY 不表明 (即, 一个常规"normal"消息). 这个权力应该 SHOULD 被任何房客允许 (甚至在一个被主持的房间里的游客).
例子 57. 房客发送私有消息
<message from='wiccarocks@shakespeare.lit/laptop' to='darkcave@chat.shakespeare.lit/firstwitch' type='chat'> <body>I'll give thee a wind.</body> </message>
服务负责把'from'地址改为发送者的房间JID并递送这个消息到预期的接收者的全JID.
例子 58. 接收者接收私有消息
<message from='darkcave@chat.shakespeare.lit/secondwitch' to='crone1@shakespeare.lit/desktop' type='chat'> <body>I'll give thee a wind.</body> </message>
如果发送者尝试发送一个类型为 "groupchat" 的私有消息给特定的房客, 服务必须 MUST 拒绝递送这个消息 (因为接收者的客户端期望的房间内的消息类型为"groupchat") 并且返回一个 <bad-request/> 错误给发送者:
例子 59. 房客尝试发送类型为"Groupchat"的私有消息给特定的房客
<message from='wiccarocks@shakespeare.lit/laptop' to='darkcave@chat.shakespeare.lit/firstwitch' type='groupchat'> <body>I'll give thee a wind.</body> </message> <message from='darkcave@chat.shakespeare.lit' to='wiccarocks@shakespeare.lit/laptop' type='error'> <body>I'll give thee a wind.</body> <error type='modify'> <bad-request xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> </error> </message>
如果发送者尝试发送一个私有消息给一个不存在的房间JID, 服务必须 MUST 返回一个 <item-not-found/> 错误给发送者.
如果发送者不是预期的接收者正在访问的那个房间的房客, 服务必须 MUST 返回一个 <not-acceptable/> 错误给发送者.
发送消息给所有房客
房客发送一个消息给所有房间内的房客的方法,是发送一个类型为 "groupchat" 的消息到 <room@service> 本身 (服务可以 MAY 忽略或拒绝类型不是 "groupchat" 的消息). 在一个被主持的房间, 这个权力限于角色为与会者或更高的房客拥有.
例子 60. 房客发送一个消息给所有房客
<message from='hag66@shakespeare.lit/pda' to='darkcave@chat.shakespeare.lit' type='groupchat'> <body>Harpier cries: 'tis time, 'tis time.</body> </message>
如果发送者在这个房间有发言权 (在被主持的房间里缺省是这样期望), 服务必须 MUST 修改发送者的 'from' 属性成为房间JID并反射这个消息到每个房客的全JID.
例子 61. 服务反射消息给所有房客
<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>
如果发送者是个游客 即, 在一个被主持的房间里没有发言权), 服务可以 MAY 返回一个 <forbidden/> 错误给发送者并且不能 MUST NOT 反射这个消息给所有房客. 如果发送者不是该房间的房客, 服务应该 SHOULD 返回一个 <not-acceptable/> 错误给发送者并且不应该 SHOULD NOT 反射这个消息给所有房客; 这个规则的唯一的例外是,一个实现可以 MAY 允许用户们拥有特定的权限 (例如, 一个房间拥有者, 房间管理员, 或服务级别的管理员) 发送消息到这个房间,即使那些用户不是房客.
注册到房间
一个实现可以 MAY 允许一个无级别的用户(在一个被主持的房间里, 通常是一个与会者) 注册一个房间从而成为该房间的一个成员 (反之, 一个实现也可以 MAY 限制这个权力并且只允许房间管理员添加新的成员). 特别是, 不在成员列表的人是无法加入一个仅限会员的房间的, 所以为了加入这样一个房间,实体需要申请会籍.
如果允许, 这个功能应该 SHOULD 这样被实现。让用户使用 'jabber:iq:register' 名字空间In-Band Registration 16提出注册申请给房间,:
例子 62. 用户提出注册申请
<iq from='hag66@shakespeare.lit/pda' id='reg1' to='darkcave@chat.shakespeare.lit' type='get'> <query xmlns='jabber:iq:register'/> </iq>
如果用户提出的注册申请不被允许注册该房间 (例如, 因为那个权限被限制了), 该房间必须 MUST 返回一个 <not-allowed/> 错误给该用户. 如果该用户已经注册过了, 房间必须 MUST 返回一个类型为"result"的IQ节并包含一个空的<register/>元素(定义于XEP-0077). 如果该房间不存在, 服务必须 MUST 返回一个 <item-not-found/> 错误.
否则, 房间必须 MUST 接着返回一个数据表格"Data Form"给该用户 (定义于Data Forms 17). 注册需要的信息可以 MAY 根据实现和部署的不同而不同并且没有完全定义在本文中 (例如, 本文根据 'http://jabber.org/protocol/muc#register' 名字空间采用的注册字段 FORM_TYPE 可能将来会根据字段标准化章节里描述的得到补充,). 以下是一个典型的例子:
例子 63. 服务返回注册表格
<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>
用户应该 SHOULD 接着提交这个表格:
例子 64. 用户提交注册表格
<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>
如果期望的房间昵称已经被那个房间保留, 房间必须 MUST 返回一个 <conflict/> 错误给该用户:
例子 65. 房间返回冲突错误给用户
<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>
如果该房间或服务不支持注册, 它必须 MUST 返回一个 <service-unavailable/> 错误给用户:
例子 66. 房间返回服务不可用错误给用户
<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>
如果用户没有提交合法的数据表格, 房间必须 MUST 返回一个 <bad-request/> 错误给用户:
例子 67. 房间返回"服务错误的请求"错误给用户
<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>
否则, 该房间必须 MUST 通知用户注册请求被成功地接收到了:
例子 68. 房间通知用户注册请求已经被处理了
<iq from='darkcave@chat.shakespeare.lit' id='reg2' to='hag66@shakespeare.lit/pda' type='result'/>
用户提交表格之后, 服务可以 MAY 向一个房间 管理员/所有者 请求批准该申请 (参见本文的批准注册请求章节) 或也可以 MAY 立刻把该用户的级别从"none"变更为"member"来添加此用户到成员列表. 如果服务变更了该用户的级别并且该用户在房间里, 它必须 MUST 从这个用户发送更新的出席信息给所有房客, 声明级别的变更,这个更新的出席信息应包含一个满足 'http://jabber.org/protocol/muc#user' 名字空间 <x/> 元素并包含一个'affiliation' 属性值设为"member"的 <item/> 子元素.
例子 69. 服务发送成员变更通知给所有房客
<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> [ ... ]
如果一个用户已经注册到一个房间, 该房间可以 MAY 选择限制这个用户在那个房间仅能使用已注册的昵称. 如果它这样做, 当用户尝试以不同于该用户之前已注册的房间昵称来加入该房间 (这使房间锁定"lock down"房间昵称以保证房客身份的一致性)的时候,它应该 SHOULD 返回一个 <not-acceptable/> 错误给该用户.
获取成员列表
根据房间配置如果允许的话, 一个房客可以 MAY 被允许接收房间成员的列表. 详见本文的修改成员列表章节.
发现保留的房间昵称
一个用户可以 MAY 有一个保留的房间昵称, 例如通过显式的房间注册, 数据库集成, 或昵称锁定 "lockdown". 用户应该 SHOULD 在尝试进入该房间之前发现自己的保留昵称. 这可以通过发送一个发现服务信息请求并指定一个服务发现节点"x-roomuser-item"给房间JID来做到.
例子 70. 用户请求保留的昵称
<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>
对一个多用户聊天服务来说,对上述的服务发现节点的支持是可选的 OPTIONAL . 如果房间或服务不支持上述的服务发现节点, 它必须 MUST 返回一个 <feature-not-implemented/> 错误给用户. 如果它支持这个特性并且该用户有一个已注册的昵称, 它必须 MUST 返回这个昵称给这个用户,方法是发送一个服务发现的<identity/>元素,其'name'属性值为这个昵称 (此处 category/type 应该 SHOULD 是 "conference/text"):
例子 71. 房间返回昵称
<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>
如果该用户没有已注册的昵称, 房间必须 MUST 返回一个空的服务发现 <query/> 元素 (根据 XEP-0030).
即使一个用户已经注册了一个房间昵称, 服务应该 SHOULD 允许该用户在加入该房间时指定一个不同的昵称 (例如, 为了从不同的客户端资源加入), 尽管该服务可以 MAY 选择通过一个 <not-acceptable/> 错误来锁定 "lock down" 昵称并拒绝该用户 . 如果该用户的客户端在加入该房间之后发送上述请求,服务不能 MUST NOT 返回一个错误给该用户, 而应该 SHOULD 返回上文所述.
如果另一个用户尝试以第一个用户保留的房间昵称来加入房间, 服务必须 MUST 拒绝第二个用户并返回一个前文所述的 <conflict/> 错误.
申请发言权
在一个被主持的房间里游客是不能发言的 (即, 发送一个消息给所有房客). 为了申请发言权, 一个游客应该 SHOULD 发送包含一个数据表格的 <message/> 节给房间本身, 这个数据表格仅仅是一个 'muc#role' 字段,值为 "participant".
例子 72. 房客申请发言权
<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>
服务接着应该 SHOULD 转发这个请求给房间主持人(们) ,定义于本文的批准发言权申请.
主持人用例
一个主持人有权在房间里执行特定的动作 (例如, 变更某些房客的角色) 但无权变更级别的持久信息 (它只能被管理员或所有者) 或定义关于这个房间的信息. 具体哪些动作可由主持人执行,取决于配置. 无论如何, 对于 MUC 框架来说, 主持人被规定有权执行以下动作:
- 在一个半匿名的房间里发现一个房客的全JID(如上文所述缺省会发生)
- 修改主题
- 从该房间踢出一个与会者或游客
- 在一个被主持的房间里授予或撤销发言权
- 在一个被主持的房间里修改拥有发言权的房客列表
这些特性将通过一个基于 <iq/> 元素的 请求/应答 交换来实现,这个IQ元素包含一个满足 'http://jabber.org/protocol/muc#admin' 名字空间的子元素. 以下例子展示这个协议和实现如何互动达到期望的功能. (以下除非显式地提及, 任何接下来的管理请求必须 MUST 被拒绝,如果该请求的'from'地址 <user@host> 和主持人的纯JID不符的话; 在这种情况下, 服务必须 MUST 返回一个 <forbidden/> 错误.)
修改房间主题
多用户聊天室的一个常用特性是变更房间主题的能力. 缺省地, 一个房间里只有角色为主持人 "moderator" 的用户应该 SHOULD 被允许变更主题 (尽管这应该 SHOULD 是可配置的, 结果是如果需要的话,仅仅与会者或甚至游客都被允许修改主题). 主题变更是通过发送一个类型为 "groupchat" 的消息给 <room@service>来实现的, 在这里 <message/> 必须 MUST 包含一个 <subject/> 元素以指定新的主题,但不应该 SHOULD NOT 包含其他元素 (例如, 不应该有 <body/> 元素或 <thread/> 元素).
例子 73. 主持人变更主题
<message from='wiccarocks@shakespeare.lit/laptop' to='darkcave@chat.shakespeare.lit' type='groupchat'> <subject>Fire Burn and Cauldron Bubble!</subject> </message>
如果一个 MUC 服务接收到这样一个消息, 它必须 MUST 以发送这个变更主题消息的那个用户的房间JID作为'from'地址来反射这个消息给所有其他房客:
例子 74. 服务通知所有房客主题变更
<message from='darkcave@chat.shakespeare.lit/secondwitch' to='crone1@shakespeare.lit/desktop' type='groupchat'> <subject>Fire Burn and Cauldron Bubble!</subject> </message> [ ... ]
另外, 当一个新的房客加入房间时,该房间应该 SHOULD 在被发送的讨论历史中包含最后的主题变更.
一个接收到这类信息的 MUC 客户端可以 MAY 选择显示一个房间内的消息, 如下:
例子 75. 客户端显式房间主题变更消息
* secondwitch has changed the subject to: Fire Burn and Cauldron Bubble!
如果一些没有适当权限的人尝试变更房间主题, 服务必须 MUST 返回一个 "error" 类型的消息指明一个 <forbidden/> 错误条件:
例子 76. 服务返回未被授权变更主题的错误
<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>
为了移除现有的主题而不是提供一个新主题 (即, 设置主题为空), 客户端应该发送一个空的 <subject/> 元素 (即, "<subject/>" 或 "<subject></subject>").
例子 77. 主持人设置空的主题
<message from='wiccarocks@shakespeare.lit/laptop' to='darkcave@chat.shakespeare.lit' type='groupchat'> <subject></subject> </message>
踢出房客
主持人有权从一个房间踢出特定种类的房客 (哪些房客是可被题的 "kickable" 取决于服务规定, 房间配置, 以及主持人的级别 -- 见下文). 踢人通常基于房客的房间昵称来执行 (尽管可以 MAY 基于全JID) 并且完全是通过把与会者或游客的角色设为 "none" 来实现的.
例子 78. 主持人踢出房客
<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>
服务必须 MUST 移除被踢的用户,通过发送一个类型为 "unavailable" 的出席信息节给每个被踢的房客, 这个出席信息应在其扩展出席信息中包含状态码 307 , 或(可选地)包含 reason 子元素(如果提供了) 以及踢人的执行者的纯JID.
例子 79. 服务移除被踢的房客
<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>
包含状态码可使客户端能够提交他们自己的通知消息 (例如, 适当的用户位置的信息). 可选的包含原因 reason 元素以及执行者 actor 使得被踢的用户能理解为什么他或她被踢了, 以及被踢的用户可以找谁去理论. 18
移除被踢的房客(们)之后, 服务必须 MUST 接着通知主持人成功了:
例子 80. 服务通知主持人成功了
<iq from='harfleur@henryv.shakespeare.lit' id='kick1' to='fluellen@shakespeare.lit/pda' type='result'/>
通知主持人之后, 服务必须 MUST 接着通知剩余的房客那个被踢的房客已经不在房间里了,即从被踢者的房间昵称(<room@service/nick>)发送 "unavailable" 类型的出席信息节给所有剩余的房客 (就像房客自愿退出房间时所做的一样), 包含状态码 status 以及可选的原因 reason 和执行者 actor.
例子 81. 服务通知剩余的房客
<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> [ ... ]
一个用户不能被比自己级别低的主持人踢出. 所以, 如果一个身为与会者的主持人尝试踢出一个管理员,或一个身为与会者的主持人或管理员尝试踢出一个所有者, 服务必须 MUST 拒绝这个请求并返回一个 <not-allowed/> 错误给发送者:
例子 82. 服务对于尝试踢出更高级别的用户返回错误
<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>
如果一个主持人尝试踢出他自己, 服务可以 MAY 拒绝这个请求并返回一个 <conflict/> 错误给发送者. (尽管这个踢出自己的行为可能看起来怪异, 它在 IRC 里很常见,用于在房间里为某人的行为道歉.)
授予游客发言权
在一个被主持的房间里, 主持人可能希望管理房间内谁有水没有发言权 "voice" (即, 发送消息给所有房客的能力). 发言权的授予是基于游客的房间昵称来的, 服务将从内部把这个房间昵称转成游客的全JID. 主持人通过把游客的角色变更为与会者 "participant"来给一个游客授予权限.
例子 83. 主持人授予权限给一个游客
<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'/> </query> </iq>
<reason/> 元素是可选的 OPTIONAL:
例子 84. 主持人授予权限给一个游客(包含一个原因 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> </item> </query> </iq>
服务必须 MUST 接着通知主持人成功了:
例子 85. 服务通知主持人成功了
<iq from='darkcave@chat.shakespeare.lit' id='voice1' to='crone1@shakespeare.lit/desktop' type='result'/>
服务必须 MUST 接着以这个人的<room@service/nick>发送更新的出席信息给所有房客, 在这个出席信息里包含了一个满足'http://jabber.org/protocol/muc#user'名字空间的<x/>元素,<x/>元素则包含一个<item/>子元素,其'role'属性值为"participant",指明添加了发言权.
例子 86. 服务发送发言权通知给所有房客
<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> [ ... ]
撤销与会者发言权
在一个被主持的房间里, 主持人可能希望撤销一个与会者发言的权力,主持人通过把与会者的角色变更为游客 "visitor"来撤销一个游客的发言权:
例子 87. 主持人撤销一个与会者的发言权
<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'/> </query> </iq>
<reason/> 元素是可选的 OPTIONAL:
例子 88. 主持人撤销一个与会者的发言权(包含一个原因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>
服务必须 MUST 接着通知主持人成功了:
例子 89. 服务通知主持人成功了
<iq from='darkcave@chat.shakespeare.lit' id='voice2' to='crone1@shakespeare.lit/desktop' type='result'/>
服务必须 MUST 接着以这个人的<room@service/nick>发送更新的出席信息给所有房客, 在这个出席信息里包含了一个满足'http://jabber.org/protocol/muc#user'名字空间的<x/>元素,<x/>元素则包含一个<item/>子元素,其'role'属性值为"visitor",指明移除了发言权.
例子 90. 服务通知失去发言权
<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> [ ... ]
一个主持人不能 MUST NOT 从一个级别等于或高于主持人级别的用户撤销发言权. 另外, 服务不能 MUST NOT 允许一个管理员或所有者的发言权被任何人撤销. 如果一个主持人尝试撤销这些人的发言权, 服务必须 MUST 拒绝这个请求并返回一个 <not-allowed/> 的错误给发送者(通过以下的违规条目):
例子 91. 服务对于尝试从管理员,所有者或更高级别的用户撤销权限返回错误
<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>
修改发言权列表
在一个被主持的房间里主持人可能希望管理发言权列表. 为了达到这个目的, 主持人首先查询房间所有角色为'participant'的房客列表来请求发言权列表.
例子 92. 主持人请求发言权列表
<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>
服务必须 MUST 接着返回发言权列表给主持人; 每个条目必须 MUST 包含 'nick' 和 'role' 属性并且应该 SHOULD 包含 'affiliation' 和 'jid' 属性:
例子 93. 服务发送发言权列表给主持人
<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>
主持人可以 MAY 接着修改发言权列表. 为了达到这个目的, 主持人必须 MUST 发送变更了的条目 (即, 只有 "delta") 给服务; 每个条目必须 MUST 包含 'nick' 属性和 'role' 属性 (通常设置值为 "participant" 或 "visitor") 但是不应该 SHOULD NOT 包含 'jid' 属性并且不能 MUST NOT 包含 'affiliation' 属性 (它用于管理如所有者那样的级别而不是与会者那样的角色):
例子 94. 主持人发送修改的发言权列表给服务
<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>
服务必须 MUST 接着通知主持人成功了:
例子 95. 服务通知主持人成功了
<iq from='goodfolk@chat.shakespeare.lit' id='voice1' to='bard@shakespeare.lit/globe' type='result'/>
服务必须 MUST 接着为任何受影响的人发送更新的出席信息给所有房客, 如前文的用例所述,发送适当的扩展出席信息来指明发言权的变更.
大家知道, 不能撤销一个房间所有者或管理员的发言权, 也不能撤销比发出请求的主持人级别高的用户的发言权. 如果一个房间管理员尝试通过修改发言权列表来撤销这类用户的发言权, 服务必须 MUST 拒绝请求并返回一个 <not-allowed/> 错误给发送者:
例子 96. 服务返回错误给试图撤销管理员,所有者或比发送者级别更高的用户的发言权的发送者
<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>
批准发言权申请
在本文的申请发言权章节提到, 当服务接受到一个来自房客的请求,它应该 SHOULD 转发那个请求给房间的主持人(们). 为了达到这个目的, 服务应该 SHOULD 发送一个 <message/> 节给房间主持人(们), 这里 <message/> 节包含一个数据表格data form来批准或拒绝这个申请, 如下所示.
'例子 97. 申请批准发言权表格
<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>
为了批准这个申请, 主持人将提交此表格:
例子 98. 批准发言权申请
<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>
如果主持人批准了这个发言权申请, 服务将授予发言权给该房客并发送一个出席信息更新,如本文授予游客发言权章节所述.
管理员用例
一个房间管理员有权修改用户级别的持久信息 (例如, 通过禁止用户) 并授予和撤销主持人权限, 但是无权修改房间的定义, 那是唯一属于房间所有者(们)的权力. 具体哪些动作是管理员可以执行的则取决于配置. 无论如何, 在 MUC 框架中的用途, 规定房间管理员最少拥有执行以下操作的权限:
- 在房间里禁止一个用户
- 在房间里修改黑名单
- 授予或撤销成员资格
- 修改成员列表
- 授予或撤销主持人权力
- 修改主持人列表
这些特性将由一个 请求/应答 request/response 式的交换来实现,使用 <iq/> 元素,包含满足 'http://jabber.org/protocol/muc#admin' 名字空间的子元素. 以下例子展示协议如何与实现互动以得到期望的功能. (以下除非显示地声明, 如果发送方的'from'地址中的<user@host>和任何房间管理员的纯JID都不同,接下来的任何管理请求必须 MUST 被拒绝; 这种情况下, 服务必须 MUST 返回一个 <forbidden/> 错误.)
禁止用户
在房间里一个管理员或所有者可以禁止一个或多个用户. 这动作必须 MUST 基于房客的纯JID来执行. 为了禁止一个用户, 管理员必须 MUST 把该用户的级别改为"outcast".
例子 99. 管理员禁止用户
<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'/> </query> </iq>
<reason/> 元素是可选的 OPTIONAL.
例子 100. 管理员禁止用户(包含一个原因 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> </item> </query> </iq>
服务必须 MUST 把那个纯JID添加到黑名单, 应该 SHOULD 把被排斥者的昵称从已注册的昵称列表中移除, 并且必须 MUST 通知管理员或所有者成功了:
例子 101. 服务通知管理员或所有者成功了
<iq from='southampton@henryv.shakespeare.lit' id='ban1' to='kinghenryv@shakespeare.lit/throne' type='result'/>
服务必须 MUST 也移除任何还在房间中的被禁止的用户,通过发送 "unavailable" 类型的出席信息节给每个被禁止的房客, 在扩展的出席信息中包含一个状态码 301 , 可选地带上 reason (如果服务提供的话) 以及执行这个禁止动作的用户的纯JID.
例子 102. 服务移除被禁止的用户
<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>
包含状态码可使客户端能够提交他们自己的通知消息 (例如, 适当的用户位置的信息). 可选的包含原因 reason 元素以及执行者 actor 使得被踢的用户能理解为什么他或她被踢了, 以及被踢的用户可以找谁去理论.
通知主持人之后, 服务必须 MUST 接着通知剩余的房客那个被禁止的房客已经不在房间里了,即从被禁止用户发送 "unavailable" 类型的出席信息节给所有剩余的房客 (就像房客自愿退出房间时所做的一样), 包含状态码 status 以及可选的原因 reason 和执行者 actor.
例子 103. 服务通知剩余的房客
<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> [ ... ]
就像踢出房客一样, 一个用户不能被自己级别低的管理员禁止. 所以, 如果一个管理员尝试禁止一个所有者, 服务必须 MUST 拒绝这个请求并返回一个 <not-allowed/> 错误给发送者:
例子 104. 服务对尝试禁止更高级别用户返回错误
<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>
如果一个管理员或所有者尝试禁止他自己, 服务必须 MUST 拒绝这个请求并返回一个 <conflict/> 错误给发送者. (注意:这和踢出自己时推荐的服务行为不同, 踢自己的行为服务是允许的.)
修改黑名单
房间管理员可能希望修改黑名单. 注意: 黑名单总是基于用户的纯JID. 要修改黑名单, 管理员首先向房间查询所有级别为'outcast'的用户以得到黑名单.
例子 105. 管理员请求黑名单
<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>
服务必须 MUST 接着返回黑名单给管理员; 每个条目必须 MUST 包含 'affiliation' 和 'jid' 属性但不应该 SHOULD NOT 包含 'nick' 和 'role' 属性:
例子 106. 服务发送黑名单给管理员
<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>
管理员可以 MAY 接着修改黑名单. 为此, 管理员必须 MUST 发送变更的条目 (即, 仅是 "delta") 给服务; 每个条目必须 MUST 包含 'affiliation' 属性 (通常设为"outcast"来禁止或"none"来取消禁止) 和 'jid' 属性,但不应该 SHOULD NOT 包含 'nick' 属性,不能 MUST NOT 包含 'role' 属性 (它用来管理角色,例如与会者,而不是被排斥者级别); 另外, reason 和 actor 元素是可选的 OPTIONAL:
例子 107. 管理员发送修改的黑名单给服务
<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>
更新黑名单之后, 服务必须 MUST 通知管理员成功了:
例子 108. 服务通知管理员成功了
<iq from='southampton@henryv.shakespeare.lit' id='ban3' to='kinghenryv@shakespeare.lit/throne' type='result'/>
服务必须 MUST 接着移除受影响的房客 (如果他们在房间里) 并从他们发送更新的出席信息 (包含适当的状态码) 给所有剩余的房客,如 "禁止用户" 用例所述. (服务应该 SHOULD 也移除从保留房间昵称列表中移除每个被禁止的用户的保留昵称, 如果必要.)
当一个实体被一个房间禁止, 实现应该 SHOULD 按以下顺序匹配 JIDs (这些匹配规则和RFC 3921中定义的隐私列表的匹配规则是相同的):
- <user@domain/resource> (仅匹配特定的资源)
- <user@domain> (匹配任何资源)
- <domain/resource> (仅匹配特定资源)
- <domain> (匹配域名本身, 就像任何 user@domain 或 domain/resource 一样)
一些管理员可能希望在一个 MUC 服务中的所有房间里禁止所有和特定域名相关的用户. 这个功能是一个服务级的特性,所以超过了本文的范围, 它定义在 XEP-0133里.
授予成员资格
管理员可以授予成员资格给一个用户; 方法是把用户的级别改为 "member" (通常如果用户在房间里,基于昵称,如果用户不在房间里,则基于纯JID; 在这两种情况下如果提供了昵称, 那么这个昵称就是用户在这个房间的缺省昵称,如果实现支持那个功能的话):
例子 109. 管理员授予成员资格
<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'/> </query> </iq>
<reason/> 元素是可选的 OPTIONAL.
例子 110. 管理员授予成员资格(包含一个原因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> </item> </query> </iq>
服务必须 MUST 把这个用户添加到成员列表,然后通知管理员成功了:
例子 111. 服务通知管理员成功了
<iq from='darkcave@chat.shakespeare.lit' id='member1' to='crone1@shakespeare.lit/desktop' type='result'/>
如果该用户在房间里, 服务必须 MUST 接着以这个用户的名义发送更新的出席信息给所有房客, 在这个出席信息里包含了一个满足'http://jabber.org/protocol/muc#user'名字空间的<x/>元素,<x/>元素则包含一个<item/>子元素,其'affiliation'属性值为"member",指明授予了成员资格.
例子 112. 服务发送成员资格通知给所有房客
<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> [ ... ]
如果该用户不在房间里, 服务可以 MAY 从房间本身发送一个消息给房间的房客们, 在这个消息里包含了一个满足'http://jabber.org/protocol/muc#user'名字空间的<x/>元素,<x/>元素则包含一个<item/>子元素,其'affiliation'属性值为"member",指明授予了成员资格.
例子 113. 服务发送成员资格通知给所有房客
<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> [ ... ]
撤销成员资格
一个管理员可能想撤销一个用户的成员资格; 通过把该用户的级别改为"none":
例子 114. 管理员撤销成员资格
<reason/> 元素是可选的 OPTIONAL.
例子 115. 管理员撤销成员资格(包含一个原因Reason)
服务必须 MUST 从成员列表中移除该用户然后通知主持人成功了:
例子 116. 服务通知主持人成功了
服务必须 MUST 接着以这个用户的名义发送更新的出席信息节给所有房客, 在这个出席信息里包含了一个满足'http://jabber.org/protocol/muc#user'名字空间的<x/>元素,<x/>元素则包含一个<item/>子元素,其'affiliation'属性值为"none",指明失去了成员资格.
例子 117. 服务通知失去成员资格
如果房间是仅限会员的, 服务必须 MUST 从房间移除这个用户, 包含一个状态码 321 来指明用户被移除是因为级别变更, 并通知所有剩余的房客:
例子 118. 服务移除非会员
修改成员列表
授予主持人权限
撤销主持人权限
修改主持人列表
批准注册申请
文档信息
系列: XMPP扩展
编号: 0045
发行者: [XMPP文档列表/XMPP标准基金会]
状态: 草案
类型: 标准跟踪
版本: 1.24
最后更新日期: 2008-07-16
批准机构: [XMPP文档列表/XMPP理事会]
依赖于: XMPP Core, XMPP IM, XEP-0004, XEP-0030, XEP-0068, XEP-0082, XEP-0128
上文: 无
下文: 无
简称: muc
muc名字空间的XML方案: <http://www.xmpp.org/schemas/muc.xsd>
muc#admin名字空间的XML方案: <http://www.xmpp.org/schemas/muc-admin.xsd>
muc#owner名字空间的XML方案: <http://www.xmpp.org/schemas/muc-owner.xsd>
muc#unique名字空间的XML方案: <http://www.xmpp.org/schemas/muc-unique.xsd>
muc#user名字空间的XML方案: <http://www.xmpp.org/schemas/muc-user.xsd>
注册项: <http://www.xmpp.org/registrar/muc.html>
Wiki页: <http://wiki.jabber.org/index.php/Multi-User Chat (XEP-0045)>
作者信息
- Peter Saint-Andre
- Email: stpeter@jabber.org
- JabberID: [xmpp:stpeter@jabber.org stpeter@jabber.org]
法律通告
版权
XMPP扩展协议的版权(1999-2008)归XMPP标准化基金会(XSF)所有.
权限
特此授权,费用全免,对任何获得本协议副本的人,对使用本协议没有限制,包括不限制在软件程序中实现本协议,不限制在网络服务中布署本协议,不限制拷贝,修改,合并,发行,翻译,分发,转授,或销售本协议的副本,被允许使用本协议做了以上工作的人士,应接受前述的版权声明和本许可通知并且必须包含在所有的副本或实质性部分的规格中.除非单独的许可,被重新分发的修改工作,不得含有关于作者,标题,编号,或出版者的规格的误导性资料,并不得宣称修改工作是由本文的作者,作者所属的任何组织或项目,或XMPP标准基金会签注。
免责声明'
注意:本协议是提供的“原样”的基础,没有担保或任何形式的条件,明示或暗示,包括,但不限于任何担保或关于名称,非侵权性,适销性或适合作某一特定目的的条件.在任何情况XMPP标准基金会或作者不对此协议承担任何责任索赔,损害赔偿,或其他责任,无论是在一项行动的合同,侵权,或否则,所产生的,运出,或在他涉嫌与规格或执行,部署或以其它方式使用本协议. ##
责任限制
在任何情况下以及没有任何法律规定时,不论是侵权行为(包括疏忽),合同或其它方面,除非根据适用法律的要求(如蓄意和有严重疏忽行为)或同意以书面形式,XMPP标准基金会或任何作者不对本协议承担所造成的损失,包括任何直接,间接,特殊,偶发,或相应的损害赔偿的任何字符利用所产生的或不能使用的规格(包括但不限于善意的损失,停止作业,电脑失灵或故障,或任何和所有其他商业损害或损失) ,即使XMPP标准基金会或作者已被告知此类损害的可能性。
知识产权的一致性
XMPP扩展协议完全遵守XSF的知识产权策略(可在<http://www.xmpp.org/extensions/ipr-policy.shtml>找到副本或写信给XSF, P.O. Box 1641, Denver, CO 80201 USA).
讨论地点
首选的讨论的地方是标准讨论邮件列表: <http://mail.jabber.org/mailman/listinfo/standards>.
勘误表发送到editor@xmpp.org
XMPP 相关信息
XMPP 是由XSF(XMPP标准化基金会)按互联网标准程序贡献的,和 IETF的RFC 2026兼容的规范,包括 XMPP核心(RFC 3920)和 XMPP IM(RFC 3921).在本文中定义的任何协议,都是在互联网标准程序之外开发的,是扩展XMPP,而不是改变、发展和修改 XMPP本身.
一致性术语
本文中以下关键词的含义如 RFC 2119 所述: "MUST", "SHALL", "REQUIRED"; "MUST NOT", "SHALL NOT"; "SHOULD", "RECOMMENDED"; "SHOULD NOT", "NOT RECOMMENDED"; "MAY", "OPTIONAL".