这一篇记录与群相关的XML协议格式。
这里需要注意,我们每次登录之后,都需要获取自己的群列表,并且主动加入到群房间里,否则就不能做群操作,也不能收到群消息。
12.获取自己加入的群列表
关于XMPP中群组的概念,需要注意的是:它分为公开群和非公开群。当我们获取自己加入的所有群时,公开群也会被搜索出来,所以,我们创建的群必须是非公开群。
关于xmpp群的相关协议可以查看 XMPP-0045(多人聊天协议)
获取自己加入的所有群列表,发出的XML:
<iq type="get" to="conference.duimy" id="3D3E0E43-78D7-41A0-A809-B121F9C37618">
<query xmlns="http://jabber.org/protocol/disco#items"></query>
</iq>
群列表,是Openfire服务器发送另外一条IQ消息回来,但是id与我们请求获取群列表的id一样。
返回的结果XML:
<iq xmlns="jabber:client" type="result" id="3D3E0E43-78D7-41A0-A809-B121F9C37618" from="conference.duimy" to="1001@duimy/iOS">
<query xmlns="http://jabber.org/protocol/disco#items">
<item jid="5380030048402158821@conference.duimy" name="群1"/>
<item jid="17678960887197922882@conference.duimy" name="群2"/>
</query>
</iq>
iOS中的获取群列表,并解析返回的XML的代码段:
- (void)getJoinedGroupsWithCompletion:(void (^)(NSArray *aList, NSError *aError))aCompletionBlock
{
XMPPJID *JID = [XMPPJID jidWithString:kGroup_Domain];
XMPPIQ *iq = [XMPPIQ iqWithType:@"get" to:JID elementID:[HLCoreManager manager].stream.generateUUID];
NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:kxmls_disco_item];
[iq addChild:query];
[[HLCoreManager manager] sendElement:iq completion:^(XMPPElement *element, NSError *error) {
if (error) {
if ([NSThread isMainThread]) {
aCompletionBlock(nil, error);
} else {
dispatch_async(dispatch_get_main_queue(), ^{
aCompletionBlock(nil, error);
});
}
return ;
}
NSMutableArray *groups = [NSMutableArray array];
NSXMLElement *query = [element elementForName:@"query" xmlns:kxmls_disco_item];
NSArray *items = [query elementsForName:@"item"];
for (NSXMLElement *item in items) {
NSString *groupName = [item attributeStringValueForName:@"name"];
NSString *groupjid = [item attributeStringValueForName:@"jid"];
XMPPJID *groupJID = [XMPPJID jidWithString:groupjid];
HLGroup *group = [[HLGroup alloc] init];
[group setValue:groupJID.user forKey:@"groupId"];
[group setValue:groupName forKey:@"subject"];
[groups addObject:group];
[[HLDBManager DBManager] insertGroup:group];
}
if ([NSThread isMainThread]) {
aCompletionBlock([groups copy], nil);
} else {
dispatch_async(dispatch_get_main_queue(), ^{
aCompletionBlock([groups copy], nil);
});
}
}];
}
而Android 里因为Smack已经做了一次封装,反而要简单的多:
public void getJoinedGroupsFromServer(final DMValueCallBack<List<DMGroup>> callBack) {
dmCore.execute(new Runnable() {
@Override
public void run() {
try {
String user = dmCore.getConnection().getUser();
Collection<HostedRoom> hostedRooms = multiUserChatManager.getHostedRooms(group_domain);
List<DMGroup> groups = new ArrayList<DMGroup>();
for (HostedRoom hostedRoom : hostedRooms) {
Log.e("房间",hostedRoom.toString());
DMGroup group = new DMGroup();
group.groupId = hostedRoom.getJid();
group.groupName = hostedRoom.getName();
groups.add(group);
}
if (callBack != null) {
callBack.onSuccess(groups);
}
} catch (Exception e) {
e.printStackTrace();
callBack.onError(-1, "获取失败");
}
}
});
}
13.创建一个群
创建一个群,简单来说,会有两步,第一步发出一个创建群的XML,服务器返回结果后,再配置群的参数。
发出创建群的XML:
<presence to="1058823229604891655@conference.duimy/1001@duimy" id="F9D05C66-3B57-4029-A312-FD7459042545">
<x xmlns="http://jabber.org/protocol/muc"></x>
</presence>
然后,会收到服务器返回的一条XML消息:
<presence xmlns="jabber:client" to="1001@duimy/iOS" id="F9D05C66-3B57-4029-A312-FD7459042545" from="1058823229604891655@conference.duimy/1001@duimy">
<x xmlns="http://jabber.org/protocol/muc#user">
<item jid="1001@duimy/iOS" affiliation="owner" role="moderator"/>
<status code="110"/>
<status code="100"/>
<status code="201"/>
</x>
</presence>
然后再配置群信息,配置信息成功后,群就创建好了。
配置群信息里的参数可以看群参数的157,159章节。
有几个关键参数:
x-muc#roomconfig_reservednick 必须设置为0(即为false),不然不能邀请用户。muc#roomconfig_publicroom 必须为为0,非公开群。
然后,发出群配置的XML:
<iq type="set" to="1058823229604891655@conference.duimy" id="3F94CC15-C128-49A2-A5FE-9B39847B9D4C" from="1001@duimy/iOS">
<query xmlns="http://jabber.org/protocol/muc#owner">
<x xmlns="jabber:x:data" type="submit">
<field var="muc#roomconfig_publicroom" type="boolean">
<value>0</value>
</field>
<field var="muc#roomconfig_roomtype" type="boolean">
<value>0</value>
</field>
<field var="muc#roomconfig_persistentroom" type="boolean">
<value>1</value>
</field>
<field var="muc#roomconfig_membersonly" type="boolean">
<value>1</value>
</field>
<field var="muc#roomconfig_allowinvites" type="boolean">
<value>1</value>
</field>
<field var="muc#roomconfig_registration" type="boolean">
<value>1</value>
</field>
<field var="muc#roominfo_subject" type="text-single">
<value>这是群主题</value>
</field>
<field var="muc#roomconfig_roomname" type="text-single">
<value>群名称</value>
</field>
<field var="muc#roomconfig_roomdesc" type="text-single">
<value>这是群描述信息</value>
</field>
<field var="muc#roomconfig_maxusers" type="text-single">
<value>500</value>
</field>
<field var="x-muc#roomconfig_reservednick" type="boolean">
<value>0</value>
</field>
<field var="muc#roomconfig_whois" type="boolean">
<value>1</value>
</field>
<field var="muc#roomconfig_passwordprotectedroom" type="boolean">
<value>0</value>
</field>
</x>
</query>
</iq>
然后服务器会返回一条type 为result 的XML 消息:
<iq xmlns="jabber:client" type="result" id="3F94CC15-C128-49A2-A5FE-9B39847B9D4C" from="1058823229604891655@conference.duimy" to="1001@duimy/iOS"></iq>
如果有错误,服务器会返回一条type为error 的XML 消息,类似这样:
<iq from='coven@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>
在iOS中的代码段:
/*!
* 创建群组
*
* @param aSubject 群组名称
* @param aDescription 群组描述
* @param aInvitees 群组成员(不包括创建者自己)
* @param aMessage 邀请消息
* @param aCompletionBlock 完成的回调
*
*/
- (void)createGroupWithSubject:(NSString *)aSubject
description:(NSString *)aDescription
invitees:(NSArray *)aInvitees
message:(NSString *)aMessage
completion:(void (^)(HLGroup *aGroup, NSError *aError))aCompletionBlock
{
NSString *groupName = aSubject;
if (groupName.length == 0) {
groupName = @"未命名";
}
NSString *groupId = [NSString stringWithFormat:@"%lu",(unsigned long)[[NSDate date] hash]];
NSString *roomId = [NSString stringWithFormat:@"%@@%@/%@",groupId,kGroup_Domain,[HLCoreManager manager].stream.myJID.bare];
XMPPJID *roomJID = [XMPPJID jidWithString:roomId];
XMPPPresence *presence = [XMPPPresence presenceWithType:nil to:roomJID];
[presence addAttributeWithName:@"id" stringValue:[HLCoreManager manager].stream.generateUUID];
NSXMLElement *x = [NSXMLElement elementWithName:@"x" xmlns:kxmls_muc];
[presence addChild:x];
// 1、发送创建群的presence
[[HLCoreManager manager] sendElement:presence completion:^(XMPPElement *element, NSError *error) {
if (error) {
aCompletionBlock(nil, error);
return ;
}
XMPPPresence *presence = (XMPPPresence *)element;
NSXMLElement *x = [presence elementForName:@"x" xmlns:kxmls_muc_user];
BOOL creatRoom = NO;
for (NSXMLElement *status in [x elementsForName:@"status"]) {
int code = [status attributeIntValueForName:@"code"];
if (code == 201) {
creatRoom = YES;
break;
}
}
if (!creatRoom) {
NSDictionary *userInfo = @{NSLocalizedDescriptionKey:@"创建群失败"};
NSError *error = [NSError errorWithDomain:@"HLGroupManager" code:0 userInfo:userInfo];
aCompletionBlock(nil, error);
return;
}
// 2.发送群配置信息的IQ
XMPPIQ *roomIQ = [self roomConfigIqTo:presence.from subject:groupName description:aDescription type:@"0"];
[[HLCoreManager manager] sendElement:roomIQ completion:^(XMPPElement *element, NSError *error) {
if (error) {
aCompletionBlock(nil, error);
return ;
}
HLGroup *group = [[HLGroup alloc] init];
[group setValue:groupId forKey:@"groupId"];
[group setValue:groupName forKey:@"subject"];
if (aDescription) {
[group setValue:aDescription forKey:@"descp"];
}
[group setValue:[[HLCoreManager manager] currentUserName] forKey:@"owner"];
aCompletionBlock(group, nil);
// 3.邀请其他人进群
for (NSString *user in aInvitees) {
XMPPMessage *xmppMessage = [self inviteOne:user toGroup:groupId message:@"join us"];
[self sendElement:xmppMessage completion:^(XMPPElement *element, NSError *error) {
HLLog(@"发送邀请");
}];
}
aCompletionBlock(group, nil);
}];
}];
}
//IQ消息的配置
- (XMPPIQ*)roomConfigIqTo:(XMPPJID *)to subject:(NSString*)groupName description:(NSString *)desc type:(NSString *)type
{
XMPPIQ *configIQ = [XMPPIQ iqWithType:@"set" to:[to bareJID] elementID:[HLCoreManager manager].stream.generateUUID];
[configIQ addAttributeWithName:@"from" stringValue:[HLCoreManager manager].stream.myJID.full];
NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:kxmls_muc_owner];
NSXMLElement *x = [NSXMLElement elementWithName:@"x" xmlns:@"jabber:x:data"];
[x addAttributeWithName:@"type" stringValue:@"submit"];
[query addChild:x];
[configIQ addChild:query];
NSXMLElement *field = [self fieldWithVarName:@"muc#roomconfig_publicroom" value:@"0" type:@"boolean"];
[x addChild:field];
field = [self fieldWithVarName:@"muc#roomconfig_roomtype" value:type type:@"boolean"];
[x addChild:field];
field = [self fieldWithVarName:@"muc#roomconfig_persistentroom" value:@"1" type:@"boolean"];
[x addChild:field];
field = [self fieldWithVarName:@"muc#roomconfig_membersonly" value:@"1" type:@"boolean"];
[x addChild:field];
field = [self fieldWithVarName:@"muc#roomconfig_allowinvites" value:@"1" type:@"boolean"];
[x addChild:field];
field = [self fieldWithVarName:@"muc#roomconfig_registration" value:@"1" type:@"boolean"];
[x addChild:field];
field = [self fieldWithVarName:@"muc#roominfo_subject" value:@"这是群主题" type:@"text-single"];
[x addChild:field];
field = [self fieldWithVarName:@"muc#roomconfig_roomname" value:groupName type:@"text-single"];
[x addChild:field];
field = [self fieldWithVarName:@"muc#roomconfig_roomdesc" value:desc type:@"text-single"];
[x addChild:field];
field = [self fieldWithVarName:@"muc#roomconfig_maxusers" value:@"500" type:@"text-single"];
[x addChild:field];
field = [self fieldWithVarName:@"x-muc#roomconfig_reservednick" value:@"0" type:@"boolean"];
[x addChild:field];
field = [self fieldWithVarName:@"muc#roomconfig_whois" value:@"1" type:@"boolean"];
[x addChild:field];
field = [self fieldWithVarName:@"muc#roomconfig_passwordprotectedroom" value:@"0" type:@"boolean"];
[x addChild:field];
return configIQ;
}
而Android 中也更简单一些:
public void createGroup(final String groupName, final String descp, final String[] invitees, final String reason, final DMCallBack callBack) {
dmCore.execute(new Runnable() {
@Override
public void run() {
String groupId = new Date().hashCode() + "@" + DMGroupManager.group_domain;
MultiUserChat group = multiUserChatManager.getMultiUserChat(groupId);
try {
String user = dmCore.getConnection().getUser();
group.create(user);
Form form = group.getConfigurationForm();
Form answerForm = form.createAnswerForm();
for(FormField field : form.getFields()) {
if (!FormField.Type.hidden.name().equals(field.getType()) && field.getVariable() != null) {
answerForm.setDefaultAnswer(field.getVariable());
}
}
answerForm.setAnswer("muc#roomconfig_publicroom", false);
answerForm.setAnswer("muc#roomconfig_persistentroom", true);
answerForm.setAnswer("muc#roomconfig_membersonly", true);
answerForm.setAnswer("muc#roomconfig_allowinvites", true);
answerForm.setAnswer("muc#roomconfig_roomname", groupName);
answerForm.setAnswer("muc#roomconfig_roomdesc", descp);
List<String> maxUsers = new ArrayList<String>();
maxUsers.add("500");
answerForm.setAnswer("muc#roomconfig_maxusers", maxUsers);
answerForm.setAnswer("x-muc#roomconfig_reservednick", false);
answerForm.setAnswer("muc#roomconfig_passwordprotectedroom", false);
group.sendConfigurationForm(answerForm);
group.join(user);
if (invitees != null && invitees.length > 0) {
//邀请加群
inviteMembers(groupId, invitees, reason, null);
}
if (callBack != null) {
callBack.onSuccess();
}
} catch (Exception e) {
e.printStackTrace();
if (callBack != null) {
callBack.onError(-1, "创建失败");
}
}
}
});
}
14. 邀请用户加入群
发送的XML:
<message to="1058823229604891655@conference.duimy" id="9316EDB0-DC08-45D3-8187-01B0CE5C6DAA">
<x xmlns="http://jabber.org/protocol/muc#user">
<invite to="1002@duimy" creatGroup="1">
<reason>join us</reason>
</invite>
</x>
</message>
目前的群邀请,因为是管理员去邀请,所以群成员是默认同意的。
iOS 中的群邀请,我封装了两个方法:
//邀请多人
- (void)invite:(NSArray *)invitees toGroup:(NSString *)groupId message:(NSString *)message
{
for (NSString *user in invitees) {
XMPPMessage *xmppMessage = [self inviteOne:user toGroup:groupId message:message];
[[HLCoreManager manager] sendElement:xmppMessage completion:^(XMPPElement *element, NSError *error) {
HLLog(@"error--%@",error);
}];
}
}
// 邀请单人的消息
- (XMPPMessage *)inviteOne:(NSString *)user toGroup:(NSString *)groupId message:(NSString *)message
{
XMPPJID *jid = [[HLCoreManager manager] jidWithUsername:user];
NSString *elementID = [HLCoreManager manager].stream.generateUUID;
NSString *tojid = [NSString stringWithFormat:@"%@@%@",groupId,kGroup_Domain];
XMPPJID *toJID = [XMPPJID jidWithString:tojid];
XMPPMessage *xmppMessage = [XMPPMessage messageWithType:nil to:toJID elementID:elementID];
NSXMLElement *x = [NSXMLElement elementWithName:@"x" xmlns:kxmls_muc_user];
NSXMLElement *inviteElement = [NSXMLElement elementWithName:@"invite"];
[inviteElement addAttributeWithName:@"to" stringValue:jid.bare];
[inviteElement addAttributeWithName:@"creatGroup" boolValue:YES];
[inviteElement addChild:[NSXMLElement elementWithName:@"reason" stringValue:message]];
[x addChild:inviteElement];
[xmppMessage addChild:x];
return xmppMessage;
}
Android 里的群邀请就简单多了:
public void inviteMembers(final String groupId, final String[] members, final String reason, final DMCallBack callBack) {
dmCore.execute(new Runnable() {
@Override
public void run() {
try {
MultiUserChat group = multiUserChatManager.getMultiUserChat(groupId);
for (String member : members) {
group.invite(member, reason);
}
if (callBack != null) {
callBack.onSuccess();
}
} catch (SmackException.NotConnectedException e) {
e.printStackTrace();
if (callBack != null) {
callBack.onError(-1, "邀请失败");
}
}
}
});
}
收到的加群邀请XML:
<message xmlns="jabber:client" from="4401241954892920861@conference.duimy" to="1002@duimy">
<x xmlns="http://jabber.org/protocol/muc#user">
<invite from="1001@duimy">
<reason>join us</reason>
</invite>
</x>
<x xmlns="jabber:x:conference" jid="4401241954892920861@conference.duimy"></x>
<delay xmlns="urn:xmpp:delay" from="duimy" stamp="2017-07-06T06:48:45.218Z"></delay>
</message>
因为我这边的逻辑是普通成员收到群邀请,自己加入群,不需要弹出来,让用户操作,所以直接执行下面这个操作即可。
iOS里的代码段:
- (XMPPPresence *)joinGroup:(XMPPJID *)groupJID since:(NSString *)sinceTime
{
NSString *groupjid = [NSString stringWithFormat:@"%@/%@",groupJID.bare,[HLCoreManager manager].stream.myJID.bare];
XMPPJID *fullJID = [XMPPJID jidWithString:groupjid];
XMPPPresence *presence = [XMPPPresence presenceWithType:nil to:fullJID];
[presence addAttributeWithName:@"id" stringValue:[HLCoreManager manager].stream.generateUUID];
NSXMLElement* x = [NSXMLElement elementWithName:@"x" xmlns:kxmls_muc];
if (sinceTime) {
NSXMLElement *history = [NSXMLElement elementWithName:@"history"];
[history addAttributeWithName:@"since" stringValue:sinceTime];
[x addChild:history];
}
[presence addChild:x];
[[HLCoreManager manager] sendElement:presence needResponse:YES completion:^(XMPPElement *element, NSError *error) {
HLLog(@"加群");
}];
return presence;
}
Android 里是有一个监听的回调,超简单:
public void invitationReceived(XMPPConnection xmppConnection, MultiUserChat multiUserChat, String s, String s1, String s2, Message message) {
// 目前是默认收到群邀请后,自动加入到群里
// 上面的s 是发起群邀请的人的jid, s1 是邀请的备注信息
String user = xmppConnection.getUser();
try {
multiUserChat.join(user);
} catch (Exception e) {
e.printStackTrace();
}
}
15.获取某个群里的普通群成员
发送的XML:
<iq type="get" to="17678960887197922882@conference.duimy" id="B1E022D0-7AEC-4FAD-910E-88D913691EBA">
<query xmlns="http://jabber.org/protocol/muc#admin">
<item affiliation="member"/>
</query>
</iq>
然后会受到服务器发过来的另一个iq消息,id 也与我们发送的xml 的id一致:
<iq xmlns="jabber:client" type="result" id="B1E022D0-7AEC-4FAD-910E-88D913691EBA" from="17678960887197922882@conference.duimy" to="1001@duimy/iOS">
<query xmlns="http://jabber.org/protocol/muc#admin">
<item affiliation="member" jid="1003@duimy"/>
<item affiliation="member" jid="1002@duimy"/>
</query>
</iq>
这里我在iOS中封装了一个方法,传不同的参数,可以和获取普通群成员、群管理员、群主列表。
- (void)getUsersWithGroupId:(NSString *)groupId affiliation:(NSString *)affiliation completion:(void (^)(NSArray *members, NSError *aError))aCompletionBlock
{
NSString *groupjid = [NSString stringWithFormat:@"%@@%@",groupId,kGroup_Domain];
XMPPJID *groupJID = [XMPPJID jidWithString:groupjid];
XMPPIQ *iq = [XMPPIQ iqWithType:@"get" to:groupJID elementID:[HLCoreManager manager].stream.generateUUID];
NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:kxmls_muc_admin];
NSXMLElement *item = [NSXMLElement elementWithName:@"item"];
[item addAttributeWithName:@"affiliation" stringValue:affiliation];
[query addChild:item];
[iq addChild:query];
[[HLCoreManager manager] sendElement:iq completion:^(XMPPElement *element, NSError *error) {
if (error) {
if (aCompletionBlock) {
if ([NSThread isMainThread]) {
aCompletionBlock(nil, error);
} else {
dispatch_async(dispatch_get_main_queue(), ^{
aCompletionBlock(nil, error);
});
}
}
return ;
}
XMPPIQ *iq = (XMPPIQ *)element;
NSMutableArray *memberList = [NSMutableArray array];
if ([iq isKindOfClass:[XMPPIQ class]] && [iq.type isEqualToString:@"result"]) {
NSXMLElement *query = [iq elementForName:@"query"];
NSArray *items = [query elementsForName:@"item"];
for (NSXMLElement *item in items) {
NSString *jid = [item attributeStringValueForName:@"jid"];
XMPPJID *memberJID = [XMPPJID jidWithString:jid];
NSString *affiliation = [item attributeStringValueForName:@"affiliation"];
NSDictionary *memberDict = @{@"jid":memberJID.user, @"affiliation":affiliation};
[memberList addObject:memberDict];
}
}
if (aCompletionBlock) {
if ([NSThread isMainThread]) {
aCompletionBlock(memberList, nil);
} else {
dispatch_async(dispatch_get_main_queue(), ^{
aCompletionBlock(memberList, nil);
});
}
}
}];
}
所以获取群成员就很简单了:
//获取群成员
- (void)getGroupMembersWithGroupId:(NSString *)groupId completion:(void (^)(NSArray *members, NSError *aError))aCompletionBlock
{
[self getUsersWithGroupId:groupId affiliation:@"member" completion:aCompletionBlock];
}
//获取管理员列表
- (void)getGroupAdminsWithGroupId:(NSString *)groupId completion:(void (^)(NSArray *admins, NSError *aError))aCompletionBlock
{
[self getUsersWithGroupId:groupId affiliation:@"admin" completion:aCompletionBlock];
}
//获取群主
- (void)getGroupOwnersWithGroupId:(NSString *)groupId completion:(void (^)(NSArray *owners, NSError *aError))aCompletionBlock
{
[self getUsersWithGroupId:groupId affiliation:@"owner" completion:aCompletionBlock];
}
Android 里也是超级简单:
MultiUserChat group = multiUserChatManager.getMultiUserChat(groupId);
//获取群普通成员
List<Affiliate> members = group.getMembers();
//获取管理员
List<Affiliate> admins = group.getAdmins();
//获取群主
List<Affiliate> owners = group.getOwners();
16.获取某个群里的主持人(即群主和管理员)
这个地方item 里的属性用的是role,没用岗位的原因是,role 为主持人的对应的岗位是admin和owner。
关于岗位、角色、权限的关系,可以看岗位、角色和权限第5小节
但是这里通过role 只能获取到当前在线的管理员和拥护者列表,所以有一些弊端。
发出的XML:
<iq type="get" to="1058823229604891655@conference.duimy" id="753940CA-E0E1-4AC1-8FF9-2A0AB895670A">
<query xmlns="http://jabber.org/protocol/muc#admin">
<item role="moderator"/>
</query>
</iq>
虽然没啥用也还是列一下吧:
- (void)getGroupModeratorsWithGroupId:(NSString *)groupId completion:(void (^)(NSArray *moderators, NSError *aError))aCompletionBlock
{
NSString *groupjid = [NSString stringWithFormat:@"%@@%@",groupId,kGroup_Domain];
XMPPJID *groupJID = [XMPPJID jidWithString:groupjid];
XMPPIQ *iq = [XMPPIQ iqWithType:@"get" to:groupJID elementID:[HLCoreManager manager].stream.generateUUID];
NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:kxmls_muc_admin];
NSXMLElement *item = [NSXMLElement elementWithName:@"item"];
[item addAttributeWithName:@"role" stringValue:@"moderator"];
[query addChild:item];
[iq addChild:query];
[[HLCoreManager manager] sendElement:iq completion:^(XMPPElement *element, NSError *error) {
if (error) {
if (aCompletionBlock) {
if ([NSThread isMainThread]) {
aCompletionBlock(nil, error);
} else {
dispatch_async(dispatch_get_main_queue(), ^{
aCompletionBlock(nil, error);
});
}
}
return ;
}
XMPPIQ *iq = (XMPPIQ *)element;
NSMutableArray *moderatorList = [NSMutableArray array];
if ([iq isKindOfClass:[XMPPIQ class]] && [iq.type isEqualToString:@"result"]) {
NSXMLElement *query = [iq elementForName:@"query"];
NSArray *items = [query elementsForName:@"item"];
for (NSXMLElement *item in items) {
NSString *jid = [item attributeStringValueForName:@"jid"];
XMPPJID *moderatorJID = [XMPPJID jidWithString:jid];
NSString *affiliation = [item attributeStringValueForName:@"affiliation"];
NSDictionary *moderatorDict = @{@"jid":moderatorJID.user, @"affiliation":affiliation};
[moderatorList addObject:moderatorDict];
}
}
if (aCompletionBlock) {
if ([NSThread isMainThread]) {
aCompletionBlock(moderatorList, nil);
} else {
dispatch_async(dispatch_get_main_queue(), ^{
aCompletionBlock(moderatorList, nil);
});
}
}
}];
}
返回的XML结果:
<iq xmlns="jabber:client" type="result" id="753940CA-E0E1-4AC1-8FF9-2A0AB895670A" from="1058823229604891655@conference.duimy" to="1001@duimy/iOS">
<query xmlns="http://jabber.org/protocol/muc#admin">
<item role="moderator" jid="1001@duimy/iOS" nick="1001@duimy" affiliation="owner"/>
</query>
</iq>
16.1 获取群里的管理员列表
发出的XML:
<iq type="get" to="17826396822253876953@conference.duimy" id="34CD10BF-311E-404D-8191-E33B4D4FAFF7">
<query xmlns="http://jabber.org/protocol/muc#admin">
<item affiliation="admin"/>
</query>
</iq>
收到的XML:
<iq xmlns="jabber:client" type="result" id="34CD10BF-311E-404D-8191-E33B4D4FAFF7" from="17826396822253876953@conference.duimy" to="1001@duimy/iOS">
<query xmlns="http://jabber.org/protocol/muc#admin">
<item affiliation="admin" jid="1003@duimy"/>
<item affiliation="admin" jid="1002@duimy"/>
</query>
</iq>
具体的例子,见15小节。
16.2 获取群里的拥有者列表
发出的XML:
<iq type="get" to="17826396822253876953@conference.duimy" id="08AC753D-5010-4038-929A-6B1EB0F5A014">
<query xmlns="http://jabber.org/protocol/muc#admin">
<item affiliation="owner"/>
</query>
</iq>
收到的XML:
<iq xmlns="jabber:client" type="result" id="08AC753D-5010-4038-929A-6B1EB0F5A014" from="17826396822253876953@conference.duimy" to="1001@duimy/iOS">
<query xmlns="http://jabber.org/protocol/muc#admin">
<item affiliation="owner" jid="1001@duimy" role="moderator" nick="1001@duimy"/>
</query>
</iq>
具体的例子,见15小节。
17.群主将某人设置为管理员
发出的XML:
<iq type="set" to="1058823229604891655@conference.duimy" id="9A83498C-6B55-4AF7-9155-F4239581D17B" from="1001@duimy">
<query xmlns="http://jabber.org/protocol/muc#admin">
<item affiliation="admin" jid="1003@duimy"/>
</query>
</iq>
如果授权成功会受到一条type 为result 的消息:
<iq xmlns="jabber:client" type="result" id="9A83498C-6B55-4AF7-9155-F4239581D17B" from="1058823229604891655@conference.duimy" to="1001@duimy/iOS"/>
iOS 中的代码段:
- (void)grantAdmin:(NSArray *)aUsers inGroup:(NSString *)groupId completion:(void (^)(NSError *aError))aCompletionBlock
{
XMPPIQ *iq = [self affiliationIQ:@"admin" invitees:aUsers inGroup:groupId];
[[HLCoreManager manager] sendElement:iq needResponse:YES completion:^(XMPPElement *element, NSError *error) {
if (error) {
if (aCompletionBlock) {
if ([NSThread isMainThread]) {
aCompletionBlock(error);
} else {
dispatch_async(dispatch_get_main_queue(), ^{
aCompletionBlock(error);
});
}
}
return ;
}
XMPPIQ *iq = (XMPPIQ *)element;
if ([iq.type isEqualToString:@"result"]) {
if (aCompletionBlock) {
if ([NSThread isMainThread]) {
aCompletionBlock(nil);
} else {
dispatch_async(dispatch_get_main_queue(), ^{
aCompletionBlock(nil);
});
}
}
} else {
NSDictionary *dict = @{NSLocalizedDescriptionKey:@"设置管理员失败"};
NSError *setError = [NSError errorWithDomain:@"HLGroupManager" code:0 userInfo:dict];
if (aCompletionBlock) {
if ([NSThread isMainThread]) {
aCompletionBlock(setError);
} else {
dispatch_async(dispatch_get_main_queue(), ^{
aCompletionBlock(setError);
});
}
}
}
}];
}
Android 里的代码段:
public void addGroupAdmin(final String groupId, final String user, final DMCallBack callBack) {
dmCore.execute(new Runnable() {
@Override
public void run() {
MultiUserChat group = multiUserChatManager.getMultiUserChat(groupId);
try {
group.grantAdmin(user);
if (callBack != null) {
callBack.onSuccess();
}
} catch (Exception e) {
e.printStackTrace();
if (callBack != null) {
callBack.onError(-1, "设置管理员失败啦");
}
}
}
});
}
当然了,如果失败(比如你没有授予管理员的权力),你也会收到一条type 为error 的XML 消息。
18. 撤销管理员权限
其实就是把岗位由admin
改为 member
。
<iq type="set" to="1058823229604891655@conference.duimy" id="FEF3F7B9-7CC5-460E-ADD0-9092204411D6" from="1001@duimy">
<query xmlns="http://jabber.org/protocol/muc#admin">
<item affiliation="member" jid="1003@duimy"/>
</query>
</iq>
如果成功会受到如下的XML:
<iq xmlns="jabber:client" type="result" id="FEF3F7B9-7CC5-460E-ADD0-9092204411D6" from="1058823229604891655@conference.duimy" to="1001@duimy/iOS"/>
iOS 中的代码段:
- (void)removeAdmin:(NSArray *)aUsers inGroup:(NSString *)groupId completion:(void (^)(NSError *aError))aCompletionBlock
{
XMPPIQ *iq = [self affiliationIQ:@"member" invitees:aUsers inGroup:groupId];
[[HLCoreManager manager] sendElement:iq needResponse:YES completion:^(XMPPElement *element, NSError *error) {
if (error) {
if (aCompletionBlock) {
if ([NSThread isMainThread]) {
aCompletionBlock(error);
} else {
dispatch_async(dispatch_get_main_queue(), ^{
aCompletionBlock(error);
});
}
}
return ;
}
XMPPIQ *iq = (XMPPIQ *)element;
if ([iq.type isEqualToString:@"result"]) {
if (aCompletionBlock) {
if ([NSThread isMainThread]) {
aCompletionBlock(nil);
} else {
dispatch_async(dispatch_get_main_queue(), ^{
aCompletionBlock(nil);
});
}
}
} else {
NSDictionary *dict = @{NSLocalizedDescriptionKey:@"移除管理员权限失败"};
NSError *removeError = [NSError errorWithDomain:@"HLGroupManager" code:0 userInfo:dict];
if (aCompletionBlock) {
if ([NSThread isMainThread]) {
aCompletionBlock(removeError);
} else {
dispatch_async(dispatch_get_main_queue(), ^{
aCompletionBlock(removeError);
});
}
}
}
}];
}
Android 代码段:
public void revokeGroupAdmin(final String groupId, final String user, final DMCallBack callBack) {
dmCore.execute(new Runnable() {
@Override
public void run() {
MultiUserChat group = multiUserChatManager.getMultiUserChat(groupId);
try {
group.revokeAdmin(user);
if (callBack != null) {
callBack.onSuccess();
}
} catch (Exception e) {
e.printStackTrace();
if (callBack != null) {
callBack.onError(-1, "取消管理员失败啦");
}
}
}
});
}
如果失败,则会收到一条type
为error
的XML消息。
19.管理员及以上权限踢人
只有管理员和owner 才可以踢人,否则会收到type 为 error 的消息。
踢人,其实是将岗位由member
改为 none
。
<iq type="set" to="9764035491904093803@conference.duimy" id="D5516126-1D35-4499-8B07-5F17DADE974D" from="1001@duimy">
<query xmlns="http://jabber.org/protocol/muc#admin">
<item affiliation="none" jid="1003@duimy"/>
</query>
</iq>
如果成功则会收到如下XML消息:
<iq xmlns="jabber:client" type="result" id="D5516126-1D35-4499-8B07-5F17DADE974D" from="9764035491904093803@conference.duimy" to="1001@duimy/iOS"></iq>
iOS中的代码段:
- (void)kickMembers:(NSArray *)aUsers inGroup:(NSString *)groupId completion:(void (^)(NSError *aError))aCompletionBlock
{
XMPPIQ *iq = [self affiliationIQ:@"none" invitees:aUsers inGroup:groupId];
[[HLCoreManager manager] sendElement:iq needResponse:YES completion:^(XMPPElement *element, NSError *error) {
if (error) {
if (aCompletionBlock) {
if ([NSThread isMainThread]) {
aCompletionBlock(error);
} else {
dispatch_async(dispatch_get_main_queue(), ^{
aCompletionBlock(error);
});
}
}
return ;
}
XMPPIQ *iq = (XMPPIQ *)element;
if ([iq.type isEqualToString:@"result"]) {
if (aCompletionBlock) {
if ([NSThread isMainThread]) {
aCompletionBlock(nil);
} else {
dispatch_async(dispatch_get_main_queue(), ^{
aCompletionBlock(nil);
});
}
}
} else {
NSDictionary *dict = @{NSLocalizedDescriptionKey:@"踢人失败"};
NSError *kickError = [NSError errorWithDomain:@"HLGroupManager" code:0 userInfo:dict];
if (aCompletionBlock) {
if ([NSThread isMainThread]) {
aCompletionBlock(kickError);
} else {
dispatch_async(dispatch_get_main_queue(), ^{
aCompletionBlock(kickError);
});
}
}
}
}];
}
Android 中的代码段:
public void removeMemberFromGroup(final String groupId, final String user, final DMCallBack callBack) {
dmCore.execute(new Runnable() {
@Override
public void run() {
MultiUserChat group = multiUserChatManager.getMultiUserChat(groupId);
try {
group.revokeMembership(user);
if (callBack != null) {
callBack.onSuccess();
}
} catch (Exception e) {
e.printStackTrace();
if (callBack != null) {
callBack.onError(-1, "踢人失败");
}
}
}
});
}
如果失败,则会受到一条type 为 error 的消息。
20. 群主解散群
只有群主才能发送解散群的消息,其他人发送解散群的消息,会受到一条type 为 error 的消息。
<iq type="set" to="9764035491904093803@conference.duimy" id="39402B51-224E-46C9-877B-E7B96059C296">
<query xmlns="http://jabber.org/protocol/muc#owner">
<destroy/>
</query>
</iq>
如果成功,则会收到如下的XML消息:
<iq xmlns="jabber:client" type="result" id="39402B51-224E-46C9-877B-E7B96059C296" from="9764035491904093803@conference.duimy" to="1001@duimy/iOS"/>
如果失败,则会受到一条type 为 error 的消息。
iOS 中的代码段:
- (void)destroyGroup:(NSString *)aGroupId completion:(void (^)(NSError *aError))aCompletionBlock
{
NSString *groupjid = [NSString stringWithFormat:@"%@@%@",aGroupId,kGroup_Domain];
XMPPJID *groupJID = [XMPPJID jidWithString:groupjid];
XMPPIQ *iq = [XMPPIQ iqWithType:@"set" to:groupJID elementID:[HLCoreManager manager].stream.generateUUID];
NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:kxmls_muc_owner];
NSXMLElement *destroy = [NSXMLElement elementWithName:@"destroy"];
[query addChild:destroy];
[iq addChild:query];
[[HLCoreManager manager] sendElement:iq needResponse:YES completion:^(XMPPElement *element, NSError *error) {
if (error && aCompletionBlock) {
if ([NSThread isMainThread]) {
aCompletionBlock(error);
} else {
dispatch_async(dispatch_get_main_queue(), ^{
aCompletionBlock(error);
});
}
return ;
}
XMPPIQ *iq = (XMPPIQ *)element;
if ([iq.type isEqualToString:@"result"]) {
if (aCompletionBlock) {
if ([NSThread isMainThread]) {
aCompletionBlock(nil);
} else {
dispatch_async(dispatch_get_main_queue(), ^{
aCompletionBlock(nil);
});
}
}
} else {
NSDictionary *dict = @{NSLocalizedDescriptionKey:@"解散群失败"};
NSError *destroyError = [NSError errorWithDomain:@"HLGroupManager" code:0 userInfo:dict];
if (aCompletionBlock) {
if ([NSThread isMainThread]) {
aCompletionBlock(destroyError);
} else {
dispatch_async(dispatch_get_main_queue(), ^{
aCompletionBlock(destroyError);
});
}
}
}
}];
}
而Android 中的代码段:
public void destroyGroup(final String groupId, final DMCallBack callBack) {
dmCore.execute(new Runnable() {
@Override
public void run() {
MultiUserChat group = multiUserChatManager.getMultiUserChat(groupId);
try {
group.destroy(null, null);
if (callBack != null) {
callBack.onSuccess();
}
} catch (Exception e) {
e.printStackTrace();
if (callBack != null) {
callBack.onError(-1, "解散群失败");
}
}
}
});
}
21. 退群
这个是重点,XMPP里怎么才能退出群呢?
退群,说白了,就是要将自己的岗位变更为none,可是变更岗位,只有管理员和拥有者才有权限,而群拥有者又不能退群,只能解散群。
所以这个退群功能,只有管理员可以执行。
而正确的做法应该是发送给群发送一条(IQ或者Message )消息,服务器接收到后,将自己从群里的岗位变更为none即可。
所以这个功能需要服务器来配合操作。
如果服务器端统一处理退群操作,那管理员退群也就没什么用了,其实管理员退群跟踢人的代码是一样的。
- (void)leaveGroup:(NSString *)aGroupId completion:(void (^)(NSError *aError))aCompletionBlock
{
NSString *groupjid = [NSString stringWithFormat:@"%@@%@",aGroupId,kGroup_Domain];
XMPPJID *groupJID = [XMPPJID jidWithString:groupjid];
NSString *myjid = [HLCoreManager manager].stream.myJID.bare;
NSString *elementID = [HLCoreManager manager].stream.generateUUID;
XMPPIQ *iq = [XMPPIQ iqWithType:@"set" to:groupJID elementID:elementID];
[iq addAttributeWithName:@"from" stringValue:myjid];
NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:kxmls_muc_admin];
NSXMLElement *item = [NSXMLElement elementWithName:@"item"];
[item addAttributeWithName:@"affiliation" stringValue:@"none"];
[item addAttributeWithName:@"jid" stringValue:myjid];
[query addChild:item];
[iq addChild:query];
[[HLCoreManager manager] sendElement:iq needResponse:YES completion:^(XMPPElement *element, NSError *error) {
if (error) {
if (aCompletionBlock) {
if ([NSThread isMainThread]) {
aCompletionBlock(error);
} else {
dispatch_async(dispatch_get_main_queue(), ^{
aCompletionBlock(error);
});
}
}
return ;
}
XMPPIQ *iq = (XMPPIQ *)element;
if ([iq.type isEqualToString:@"result"]) {
if (aCompletionBlock) {
if ([NSThread isMainThread]) {
aCompletionBlock(nil);
} else {
dispatch_async(dispatch_get_main_queue(), ^{
aCompletionBlock(nil);
});
}
}
} else {
NSDictionary *dict = @{NSLocalizedDescriptionKey:@"退群失败"};
NSError *kickError = [NSError errorWithDomain:@"HLGroupManager" code:0 userInfo:dict];
if (aCompletionBlock) {
if ([NSThread isMainThread]) {
aCompletionBlock(kickError);
} else {
dispatch_async(dispatch_get_main_queue(), ^{
aCompletionBlock(kickError);
});
}
}
}
}];
}