diff --git a/Monal/Classes/MLIQProcessor.m b/Monal/Classes/MLIQProcessor.m index f4c277432b..0a1236276d 100644 --- a/Monal/Classes/MLIQProcessor.m +++ b/Monal/Classes/MLIQProcessor.m @@ -558,7 +558,7 @@ +(BOOL) processRosterWithAccount:(xmpp*) account andIqNode:(XMPPIQ*) iqNode } if(!account.connectionProperties.conferenceServer && [features containsObject:@"http://jabber.org/protocol/muc"]) - account.connectionProperties.conferenceServer = iqNode.from; + account.connectionProperties.conferenceServer = iqNode.fromUser; $$ $$class_handler(handleServerDiscoItems, $$ID(xmpp*, account), $$ID(XMPPIQ*, iqNode)) diff --git a/Monal/Classes/MLMucProcessor.h b/Monal/Classes/MLMucProcessor.h index 98b66cb65d..5161c0da09 100644 --- a/Monal/Classes/MLMucProcessor.h +++ b/Monal/Classes/MLMucProcessor.h @@ -25,8 +25,12 @@ NS_ASSUME_NONNULL_BEGIN -(void) join:(NSString*) room; -(void) leave:(NSString*) room withBookmarksUpdate:(BOOL) updateBookmarks; --(void) setAffiliation:(NSString*) affiliation ofUser:(NSString*) jid inRoom:(NSString*) roomJid; --(void) publishAvatar:(UIImage*) image forMuc:(NSString*) room; +//muc management methods +-(NSString* _Nullable) createGroup:(NSString*) node; +-(void) changeNameOfMuc:(NSString*) room to:(NSString*) name; +-(void) changeSubjectOfMuc:(NSString*) room to:(NSString*) subject; +-(void) publishAvatar:(UIImage* _Nullable) image forMuc:(NSString*) room; +-(void) setAffiliation:(NSString*) affiliation ofUser:(NSString*) jid inMuc:(NSString*) roomJid; -(void) pingAllMucs; -(void) ping:(NSString*) roomJid; diff --git a/Monal/Classes/MLMucProcessor.m b/Monal/Classes/MLMucProcessor.m index 97a4ba7c80..928c90d5f6 100644 --- a/Monal/Classes/MLMucProcessor.m +++ b/Monal/Classes/MLMucProcessor.m @@ -23,7 +23,7 @@ #import "MLOMEMO.h" #import "MLImageManager.h" -#define CURRENT_MUC_STATE_VERSION @6 +#define CURRENT_MUC_STATE_VERSION @7 @interface MLMucProcessor() { @@ -31,6 +31,7 @@ @interface MLMucProcessor() //persistent state NSObject* _stateLockObject; NSMutableDictionary* _roomFeatures; + NSMutableDictionary* _creating; NSMutableDictionary* _joining; NSMutableSet* _firstJoin; NSDate* _lastPing; @@ -43,12 +44,35 @@ @interface MLMucProcessor() @implementation MLMucProcessor +static NSDictionary* _mandatoryGroupConfigOptions; +static NSDictionary* _optionalGroupConfigOptions; + ++(void) initialize +{ + _mandatoryGroupConfigOptions = @{ + @"muc#roomconfig_persistentroom": @"1", + @"muc#roomconfig_membersonly": @"1", + @"muc#roomconfig_whois": @"anyone", + }; + _optionalGroupConfigOptions = @{ + @"muc#roomconfig_enablelogging": @"0", + @"muc#roomconfig_changesubject": @"0", + @"muc#roomconfig_allowinvites": @"0", + @"muc#roomconfig_getmemberlist": @"participant", + @"muc#roomconfig_publicroom": @"0", + @"muc#roomconfig_moderatedroom": @"0", + @"muc#maxhistoryfetch": @"0", //should use mam + }; + +} + -(id) initWithAccount:(xmpp*) account { self = [super init]; _account = account; _stateLockObject = [NSObject new]; _roomFeatures = [NSMutableDictionary new]; + _creating = [NSMutableDictionary new]; _joining = [NSMutableDictionary new]; _firstJoin = [NSMutableSet new]; _uiHandler = [NSMutableDictionary new]; @@ -80,6 +104,7 @@ -(void) setInternalState:(NSDictionary*) state //extract state @synchronized(_stateLockObject) { _roomFeatures = [state[@"roomFeatures"] mutableCopy]; + _creating = [state[@"creating"] mutableCopy]; _joining = [state[@"joining"] mutableCopy]; _firstJoin = [state[@"firstJoin"] mutableCopy]; _lastPing = state[@"lastPing"]; @@ -94,6 +119,7 @@ -(NSDictionary*) getInternalState NSDictionary* state = @{ @"version": CURRENT_MUC_STATE_VERSION, @"roomFeatures": [_roomFeatures copy], + @"creating": [_creating copy], @"joining": [_joining copy], @"firstJoin": [_firstJoin copy], @"lastPing": _lastPing, @@ -118,6 +144,9 @@ -(void) handleResourceBound:(NSNotification*) notification NSDictionary* joiningCopy = [_joining copy]; for(NSString* room in joiningCopy) [self removeRoomFromJoining:room]; + NSDictionary* creatingCopy = [_creating copy]; + for(NSString* room in creatingCopy) + [self removeRoomFromCreating:room]; //don't clear _firstJoin and _noUpdateBookmarks to make sure half-joined mucs are still added to muc bookmarks @@ -143,6 +172,13 @@ -(void) handleCatchupDone:(NSNotification*) notification } } +-(BOOL) isCreating:(NSString*) room +{ + @synchronized(_stateLockObject) { + return _creating[room] != nil; + } +} + -(BOOL) isJoining:(NSString*) room { @synchronized(_stateLockObject) { @@ -339,6 +375,125 @@ -(void) handleMembersListUpdate:(XMPPStanza*) node DDLogInfo(@"Ignoring handleMembersListUpdate for %@, MUC not in buddylist", node.fromUser); } +-(void) configureMuc:(NSString*) roomJid withMandatoryOptions:(NSDictionary*) mandatoryOptions andOptionalOptions:(NSDictionary*) optionalOptions deletingMucOnError:(BOOL) deleteOnError +{ + DDLogInfo(@"Fetching room config form: %@", roomJid); + XMPPIQ* configFetchNode = [[XMPPIQ alloc] initWithType:kiqGetType to:roomJid]; + [configFetchNode setGetRoomConfig]; + [_account sendIq:configFetchNode withHandler:$newHandlerWithInvalidation(self, handleRoomConfigForm, handleRoomConfigFormInvalidation, $ID(roomJid), $ID(mandatoryOptions), $ID(optionalOptions), $BOOL(deleteOnError))]; +} + +$$instance_handler(handleRoomConfigFormInvalidation, account.mucProcessor, $$ID(xmpp*, account), $$ID(NSString*, roomJid), $$ID(NSDictionary*, mandatoryOptions), $$ID(NSDictionary*, optionalOptions), $$BOOL(deleteOnError)) + if(deleteOnError) + { + DDLogError(@"Config form fetch failed, removing muc '%@' from _creating...", roomJid); + [self removeRoomFromCreating:roomJid]; + [self deleteMuc:roomJid withBookmarksUpdate:NO keepBuddylistEntry:NO]; + } + else + DDLogError(@"Config form fetch failed for muc '%@'!", roomJid); + [self handleError:[NSString stringWithFormat:NSLocalizedString(@"Could fetch room config form for '%@': timeout", @""), roomJid] forMuc:roomJid withNode:nil andIsSevere:YES]; +$$ + +$$instance_handler(handleRoomConfigForm, account.mucProcessor, $$ID(xmpp*, account), $$ID(XMPPIQ*, iqNode), $$ID(NSString*, roomJid), $$ID(NSDictionary*, mandatoryOptions), $$ID(NSDictionary*, optionalOptions), $$BOOL(deleteOnError)) + MLAssert([iqNode.fromUser isEqualToString:roomJid], @"Room config form response jid not matching query jid!", (@{ + @"iqNode.fromUser": [NSString stringWithFormat:@"%@", iqNode.fromUser], + @"roomJid": [NSString stringWithFormat:@"%@", roomJid], + })); + if([iqNode check:@"/"]) + { + DDLogError(@"Failed to fetch room config form for '%@': %@", roomJid, [iqNode findFirst:@"error"]); + if(deleteOnError) + { + [self removeRoomFromCreating:roomJid]; + [self deleteMuc:roomJid withBookmarksUpdate:NO keepBuddylistEntry:NO]; + } + [self handleError:[NSString stringWithFormat:NSLocalizedString(@"Failed to fetch room config form for '%@'", @""), roomJid] forMuc:roomJid withNode:iqNode andIsSevere:YES]; + return; + } + + XMPPDataForm* dataForm = [[iqNode findFirst:@"{http://jabber.org/protocol/muc#owner}query/\\{http://jabber.org/protocol/muc#roomconfig}form\\"] copy]; + if(dataForm == nil) + { + DDLogError(@"Got empty room config form for '%@'!", roomJid); + if(deleteOnError) + { + [self removeRoomFromCreating:roomJid]; + [self deleteMuc:roomJid withBookmarksUpdate:NO keepBuddylistEntry:NO]; + } + [self handleError:[NSString stringWithFormat:NSLocalizedString(@"Got empty room config form for '%@'", @""), roomJid] forMuc:roomJid withNode:nil andIsSevere:YES]; + return; + } + + //these config options are mandatory and configure the room to be a group --> non anonymous, members only (and persistent) + for(NSString* option in mandatoryOptions) + { + if(!dataForm[option]) + { + DDLogError(@"Could not configure room '%@' to be a groupchat: config option '%@' not available!", roomJid, option); + if(deleteOnError) + { + [self removeRoomFromCreating:roomJid]; + [self deleteMuc:roomJid withBookmarksUpdate:NO keepBuddylistEntry:NO]; + } + [self handleError:[NSString stringWithFormat:@"Could not configure new group '%@': config option '%@' not available!", roomJid, option] forMuc:roomJid withNode:nil andIsSevere:YES]; + return; + } + else + dataForm[option] = mandatoryOptions[option]; + } + + //these config options are optional but most of them should be supported by all modern servers + for(NSString* option in optionalOptions) + { + if(dataForm[option]) + dataForm[option] = optionalOptions[option]; + else + DDLogWarn(@"Ignoring optional config option for room '%@': %@", roomJid, option); + } + + //reconfigure the room + dataForm.type = @"submit"; + XMPPIQ* query = [[XMPPIQ alloc] initWithType:kiqSetType]; + [query setRoomConfig:dataForm]; + [_account sendIq:query withHandler:$newHandlerWithInvalidation(self, handleRoomConfigResult, handleRoomConfigResultInvalidation, $ID(roomJid), $ID(mandatoryOptions), $ID(optionalOptions), $BOOL(deleteOnError))]; +$$ + +$$instance_handler(handleRoomConfigResultInvalidation, account.mucProcessor, $$ID(xmpp*, account), $$ID(NSString*, roomJid), $$ID(NSDictionary*, mandatoryOptions), $$ID(NSDictionary*, optionalOptions), $$BOOL(deleteOnError)) + if(deleteOnError) + { + DDLogError(@"Config form submit failed, removing muc '%@' from _creating...", roomJid); + [self removeRoomFromCreating:roomJid]; + [self deleteMuc:roomJid withBookmarksUpdate:NO keepBuddylistEntry:NO]; + } + else + DDLogError(@"Config form submit failed for muc '%@'!", roomJid); + [self handleError:[NSString stringWithFormat:NSLocalizedString(@"Could not configure group '%@': timeout", @""), roomJid] forMuc:roomJid withNode:nil andIsSevere:YES]; +$$ + +$$instance_handler(handleRoomConfigResult, account.mucProcessor, $$ID(xmpp*, account), $$ID(XMPPIQ*, iqNode), $$ID(NSString*, roomJid), $$ID(NSDictionary*, mandatoryOptions), $$ID(NSDictionary*, optionalOptions), $$BOOL(deleteOnError)) + MLAssert([iqNode.fromUser isEqualToString:roomJid], @"Room config form response jid not matching query jid!", (@{ + @"iqNode.fromUser": [NSString stringWithFormat:@"%@", iqNode.fromUser], + @"roomJid": [NSString stringWithFormat:@"%@", roomJid], + })); + if([iqNode check:@"/"]) + { + DDLogError(@"Failed to submit room config form of '%@': %@", roomJid, [iqNode findFirst:@"error"]); + if(deleteOnError) + { + [self removeRoomFromCreating:roomJid]; + [self deleteMuc:roomJid withBookmarksUpdate:NO keepBuddylistEntry:NO]; + } + [self handleError:[NSString stringWithFormat:NSLocalizedString(@"Could not configure group '%@'", @""), roomJid] forMuc:roomJid withNode:iqNode andIsSevere:YES]; + return; + } + + //group is now properly configured and we are joined, but all the code handling a proper join was not run + //--> join again to make sure everything is sane + [self removeRoomFromCreating:roomJid]; + [self join:roomJid]; +$$ + -(void) handleStatusCodes:(XMPPStanza*) node { NSSet* presenceCodes = [[NSSet alloc] initWithArray:[node find:@"/{jabber:client}presence/{http://jabber.org/protocol/muc#user}x/status@code|int"]]; @@ -354,11 +509,23 @@ -(void) handleStatusCodes:(XMPPStanza*) node //room created and needs configuration now case 201: { - //make instant room - DDLogInfo(@"Creating instant muc room using default config: %@", node.fromUser); - XMPPIQ* configNode = [[XMPPIQ alloc] initWithType:kiqSetType to:node.fromUser]; - [configNode setInstantRoom]; - [_account send:configNode]; + if(![presenceCodes containsObject:@110]) + { + DDLogError(@"Got 'muc needs configuration' status code (201) without self-presence, ignoring!"); + break; + } + if(![self isCreating:node.fromUser]) + { + DDLogError(@"Got 'muc needs configuration' status code (201) without this muc currently being created, ignoring!"); + break; + } + + //now configure newly created locked room + [self configureMuc:node.fromUser withMandatoryOptions:_mandatoryGroupConfigOptions andOptionalOptions:_optionalGroupConfigOptions deletingMucOnError:YES]; + + //stop processing here to not trigger the "successful join" code below + //we will trigger this code by a "second" join presence once the room was created and is not locked anymore + return; break; } //muc service changed our nick @@ -543,6 +710,16 @@ -(void) handleStatusCodes:(XMPPStanza*) node case 102: case 103: case 104: + /* + * If room logging is now enabled, status code 170. + * If room logging is now disabled, status code 171. + * If the room is now non-anonymous, status code 172. + * If the room is now semi-anonymous, status code 173. + */ + case 170: + case 171: + case 172: + case 173: { DDLogInfo(@"Muc config of %@ changed, sending new disco info query to reload muc config...", node.fromUser); [self sendDiscoQueryFor:node.from withJoin:NO andBookmarksUpdate:NO]; @@ -554,6 +731,53 @@ -(void) handleStatusCodes:(XMPPStanza*) node } } +$$instance_handler(handleCreateTimeout, account.mucProcessor, $$ID(xmpp*, account), $$ID(NSString*, room)) + [self removeRoomFromCreating:room]; + [self deleteMuc:room withBookmarksUpdate:NO keepBuddylistEntry:NO]; + [self handleError:[NSString stringWithFormat:NSLocalizedString(@"Could fetch room config form from '%@': timeout", @""), room] forMuc:room withNode:nil andIsSevere:YES]; + [self handleError:[NSString stringWithFormat:NSLocalizedString(@"Could not create group '%@': timeout", @""), room] forMuc:room withNode:nil andIsSevere:YES]; +$$ + +-(NSString*) createGroup:(NSString*) node +{ + NSString* room = [[NSString stringWithFormat:@"%@@%@", node, _account.connectionProperties.conferenceServer] lowercaseString]; + if([[DataLayer sharedInstance] isBuddyMuc:room forAccount:_account.accountNo]) + { + DDLogWarn(@"Cannot create muc already existing in our buddy list, checking if we are still joined and join if needed..."); + [self ping:room]; + return nil; + } + + //remove old non-muc contact from contactlist (we don't want mucs as normal contacts on our (server) roster and shadowed in monal by the real muc contact) + NSDictionary* existingContactDict = [[DataLayer sharedInstance] contactDictionaryForUsername:room forAccount:_account.accountNo]; + if(existingContactDict != nil) + { + MLContact* existingContact = [MLContact createContactFromJid:room andAccountNo:_account.accountNo]; + DDLogVerbose(@"CreateMUC: Removing already existing contact (%@) having raw db dict: %@", existingContact, existingContactDict); + [_account removeFromRoster:existingContact]; + } + //add new muc buddy (potentially deleting a non-muc buddy having the same jid) + NSString* nick = [self calculateNickForMuc:room]; + DDLogInfo(@"CreateMUC: Adding new muc %@ using nick '%@' to buddylist...", room, nick); + [[DataLayer sharedInstance] initMuc:room forAccountId:_account.accountNo andMucNick:nick]; + + DDLogInfo(@"Trying to create muc '%@' with nick '%@' on account %@...", room, nick, _account); + @synchronized(_stateLockObject) { + //add room to "currently creating" list (and remove any present idle timer for this room) + [[DataLayer sharedInstance] delIdleTimerWithId:_creating[room]]; + //add idle timer to display error if we did not receive the reflected create presence after 30 idle seconds + //this will make sure the spinner ui will not spin indefinitely when adding a channel via ui + NSNumber* timerId = [[DataLayer sharedInstance] addIdleTimerWithTimeout:@30 andHandler:$newHandler(self, handleCreateTimeout, $ID(room)) onAccountNo:_account.accountNo]; + _creating[room] = timerId; + //we don't need to force saving of our new state because once this outgoing create presence gets handled by smacks the whole state will be saved + } + XMPPPresence* presence = [XMPPPresence new]; + [presence createRoom:room withNick:nick]; + [_account send:presence]; + + return room; +} + -(void) join:(NSString*) room { [self sendDiscoQueryFor:room withJoin:YES andBookmarksUpdate:YES]; @@ -704,26 +928,53 @@ -(void) ping:(NSString*) roomJid withLastPing:(NSDate* _Nullable) lastPing }]; } --(void) setAffiliation:(NSString*) affiliation ofUser:(NSString*) jid inRoom:(NSString*) roomJid +-(void) setAffiliation:(NSString*) affiliation ofUser:(NSString*) jid inMuc:(NSString*) roomJid { DDLogInfo(@"Changing affiliation of '%@' in '%@' to '%@'", jid, roomJid, affiliation); XMPPIQ* updateIq = [[XMPPIQ alloc] initWithType:kiqSetType to:roomJid]; [updateIq setMucAdminQueryWithAffiliation:affiliation forJid:jid]; - [_account sendIq:updateIq withHandler:$newHandler(self, handleAffiliationUpdateResult, $ID(roomJid), $ID(jid), $ID(affiliation))]; + [_account sendIq:updateIq withHandler:$newHandlerWithInvalidation(self, handleAffiliationUpdateResult, handleAffiliationUpdateResultInvalidation, $ID(roomJid), $ID(jid), $ID(affiliation))]; } +$$instance_handler(handleAffiliationUpdateResultInvalidation, account.mucProcessor, $$ID(xmpp*, account), $$ID(NSString*, affiliation), $$ID(NSString*, jid), $$ID(NSString*, roomJid)) + DDLogError(@"Failed to change affiliation of '%@' in '%@' to '%@': timeout", jid, roomJid, affiliation); + [self handleError:[NSString stringWithFormat:NSLocalizedString(@"Failed to change affiliation of '%@' in '%@' to '%@': timeout", @""), jid, roomJid, affiliation] forMuc:roomJid withNode:nil andIsSevere:YES]; +$$ + $$instance_handler(handleAffiliationUpdateResult, account.mucProcessor, $$ID(xmpp*, account), $$ID(XMPPIQ*, iqNode), $$ID(NSString*, affiliation), $$ID(NSString*, jid), $$ID(NSString*, roomJid)) if([iqNode check:@"/"]) { DDLogError(@"Failed to change affiliation of '%@' in '%@' to '%@': %@", jid, roomJid, affiliation, [iqNode findFirst:@"error"]); - [HelperTools postError:[NSString stringWithFormat:NSLocalizedString(@"Failed to change affiliation of '%@' in '%@' to '%@'", @""), jid, roomJid, affiliation] withNode:iqNode andAccount:_account andIsSevere:YES]; + [self handleError:[NSString stringWithFormat:NSLocalizedString(@"Failed to change affiliation of '%@' in '%@' to '%@'", @""), jid, roomJid, affiliation] forMuc:roomJid withNode:iqNode andIsSevere:YES]; return; } DDLogError(@"Successfully changed affiliation of '%@' in '%@' to '%@'", jid, roomJid, affiliation); $$ --(void) publishAvatar:(UIImage*) image forMuc:(NSString*) room +-(void) changeNameOfMuc:(NSString*) room to:(NSString*) name +{ + [self configureMuc:room withMandatoryOptions:@{ + @"muc#roomconfig_roomname": name, + } andOptionalOptions:@{} deletingMucOnError:NO]; +} + +-(void) changeSubjectOfMuc:(NSString*) room to:(NSString*) subject { + XMPPMessage* msg = [[XMPPMessage alloc] initWithType:kMessageGroupChatType to:room]; + [msg addChildNode:[[MLXMLNode alloc] initWithElement:@"subject" andData:subject]]; + [_account send:msg]; +} + +-(void) publishAvatar:(UIImage* _Nullable) image forMuc:(NSString*) room +{ + if(image == nil) + { + DDLogInfo(@"Removing avatar image for muc '%@'...", room); + XMPPIQ* vcard = [[XMPPIQ alloc] initWithType:kiqSetType to:room]; + [vcard setRemoveVcardAvatar]; + [_account sendIq:vcard withHandler:$newHandlerWithInvalidation(self, handleAvatarPublishResult, handleAvatarPublishResultInvalidation, $ID(room))]; + return; + } //should work for ejabberd >= 19.02 and prosody >= 0.11 NSData* imageData = [HelperTools resizeAvatarImage:image withCircularMask:NO toMaxBase64Size:60000]; NSString* imageHash = [HelperTools hexadecimalString:[HelperTools sha1:imageData]]; @@ -731,14 +982,19 @@ -(void) publishAvatar:(UIImage*) image forMuc:(NSString*) room DDLogInfo(@"Publishing avatar image for muc '%@' with hash %@", room, imageHash); XMPPIQ* vcard = [[XMPPIQ alloc] initWithType:kiqSetType to:room]; [vcard setVcardAvatarWithData:imageData andType:@"image/jpeg"]; - [_account sendIq:vcard withHandler:$newHandler(self, handleAvatarPublishResult)]; + [_account sendIq:vcard withHandler:$newHandlerWithInvalidation(self, handleAvatarPublishResult, handleAvatarPublishResultInvalidation, $ID(room))]; } +$$instance_handler(handleAvatarPublishResultInvalidation, account.mucProcessor, $$ID(xmpp*, account), $$ID(NSString*, room)) + DDLogError(@"Publishing avatar for muc '%@' returned timeout", room); + [self handleError:[NSString stringWithFormat:NSLocalizedString(@"Failed to publish avatar image for group/channel %@", @""), room] forMuc:room withNode:nil andIsSevere:YES]; +$$ + $$instance_handler(handleAvatarPublishResult, account.mucProcessor, $$ID(xmpp*, account), $$ID(XMPPIQ*, iqNode)) if([iqNode check:@"/"]) { DDLogError(@"Publishing avatar for muc '%@' returned error: %@", iqNode.fromUser, [iqNode findFirst:@"error"]); - [HelperTools postError:[NSString stringWithFormat:NSLocalizedString(@"Failed to publish avatar image for group/channel %@", @""), iqNode.fromUser] withNode:iqNode andAccount:_account andIsSevere:YES]; + [self handleError:[NSString stringWithFormat:NSLocalizedString(@"Failed to publish avatar image for group/channel %@", @""), iqNode.fromUser] forMuc:iqNode.fromUser withNode:iqNode andIsSevere:YES]; return; } DDLogInfo(@"Successfully published avatar for muc: %@", iqNode.fromUser); @@ -1156,6 +1412,15 @@ -(NSString*) calculateNickForMuc:(NSString*) room return nick; } +-(void) removeRoomFromCreating:(NSString*) room +{ + @synchronized(_stateLockObject) { + DDLogVerbose(@"Removing from _creating[%@]: %@", room, _creating[room]); + [[DataLayer sharedInstance] delIdleTimerWithId:_creating[room]]; + [_creating removeObjectForKey:room]; + } +} + -(void) removeRoomFromJoining:(NSString*) room { @synchronized(_stateLockObject) { diff --git a/Monal/Classes/XMPPIQ.h b/Monal/Classes/XMPPIQ.h index b6fcaf3e4d..84e6f49f7f 100644 --- a/Monal/Classes/XMPPIQ.h +++ b/Monal/Classes/XMPPIQ.h @@ -9,6 +9,8 @@ #import "XMPPStanza.h" #import "MLContact.h" +@class XMPPDataForm; + NS_ASSUME_NONNULL_BEGIN FOUNDATION_EXPORT NSString* const kiqGetType; @@ -117,6 +119,7 @@ removes a contact from the roster -(void) setInstantRoom; -(void) setVcardAvatarWithData:(NSData*) imageData andType:(NSString*) imageType; +-(void) setRemoveVcardAvatar; -(void) setVcardQuery; #pragma mark - account @@ -130,6 +133,8 @@ removes a contact from the roster -(void) requestBlockList; -(void) setMucAdminQueryWithAffiliation:(NSString*) affiliation forJid:(NSString*) jid; +-(void) setGetRoomConfig; +-(void) setRoomConfig:(XMPPDataForm*) configForm; @end diff --git a/Monal/Classes/XMPPIQ.m b/Monal/Classes/XMPPIQ.m index a03f7f5d20..14b1731892 100644 --- a/Monal/Classes/XMPPIQ.m +++ b/Monal/Classes/XMPPIQ.m @@ -345,6 +345,16 @@ -(void) getEntitySoftWareVersionTo:(NSString*) to #pragma mark MUC +-(void) setGetRoomConfig +{ + [self addChildNode:[[MLXMLNode alloc] initWithElement:@"query" andNamespace:@"http://jabber.org/protocol/muc#owner"]]; +} + +-(void) setRoomConfig:(XMPPDataForm*) configForm +{ + [self addChildNode:[[MLXMLNode alloc] initWithElement:@"query" andNamespace:@"http://jabber.org/protocol/muc#owner" withAttributes:@{} andChildren:@[configForm] andData:nil]]; +} + -(void) setInstantRoom { [self addChildNode:[[MLXMLNode alloc] initWithElement:@"query" andNamespace:@"http://jabber.org/protocol/muc#owner" withAttributes:@{} andChildren:@[ @@ -352,6 +362,16 @@ -(void) setInstantRoom ] andData:nil]]; } +-(void) setRemoveVcardAvatar +{ + [self addChildNode:[[MLXMLNode alloc] initWithElement:@"vCard" andNamespace:@"vcard-temp" withAttributes:@{} andChildren:@[ + [[MLXMLNode alloc] initWithElement:@"PHOTO" withAttributes:@{} andChildren:@[ + [[MLXMLNode alloc] initWithElement:@"PHOTO" andData:nil], + [[MLXMLNode alloc] initWithElement:@"BINVAL" andData:nil], + ] andData:nil] + ] andData:nil]]; +} + -(void) setVcardAvatarWithData:(NSData*) imageData andType:(NSString*) imageType { [self addChildNode:[[MLXMLNode alloc] initWithElement:@"vCard" andNamespace:@"vcard-temp" withAttributes:@{} andChildren:@[ diff --git a/Monal/Classes/XMPPPresence.h b/Monal/Classes/XMPPPresence.h index 9dc9f64581..6124e83d51 100644 --- a/Monal/Classes/XMPPPresence.h +++ b/Monal/Classes/XMPPPresence.h @@ -80,6 +80,9 @@ allow subscription. Called in response to a remote request. -(void) unsubscribedContact:(MLContact*) contact; #pragma mark MUC + +-(void) createRoom:(NSString*) room withNick:(NSString*) nick; + /** join specified room on server */ diff --git a/Monal/Classes/XMPPPresence.m b/Monal/Classes/XMPPPresence.m index 7b6ed92b5f..5659399084 100644 --- a/Monal/Classes/XMPPPresence.m +++ b/Monal/Classes/XMPPPresence.m @@ -68,6 +68,12 @@ -(void) setLastInteraction:(NSDate*) date #pragma mark MUC +-(void) createRoom:(NSString*) room withNick:(NSString*) nick +{ + self.to = [NSString stringWithFormat:@"%@/%@", room, nick]; + [self addChildNode:[[MLXMLNode alloc] initWithElement:@"x" andNamespace:@"http://jabber.org/protocol/muc" withAttributes:@{} andChildren:@[] andData:nil]]; +} + -(void) joinRoom:(NSString*) room withNick:(NSString*) nick { [self.attributes setObject:[NSString stringWithFormat:@"%@/%@", room, nick] forKey:@"to"];