Plugins/Purple Service/CBPurpleAccount.m
author Patrick Steinhardt <steinhardt.p@me.com>
Wed, 04 May 2011 10:23:27 +0200
changeset 3899 b737e71520e4
parent 3782 e660e9081fb6
child 3951 983d9c761506
permissions -rw-r--r--
Fixed broken percentage reflecting connection progress in Accounts.

r=xnyhps. Fixes #15163
David@0
     1
/* 
David@0
     2
 * Adium is the legal property of its developers, whose names are listed in the copyright file included
David@0
     3
 * with this source distribution.
David@0
     4
 * 
David@0
     5
 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
David@0
     6
 * General Public License as published by the Free Software Foundation; either version 2 of the License,
David@0
     7
 * or (at your option) any later version.
David@0
     8
 * 
David@0
     9
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
David@0
    10
 * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
David@0
    11
 * Public License for more details.
David@0
    12
 * 
David@0
    13
 * You should have received a copy of the GNU General Public License along with this program; if not,
David@0
    14
 * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
David@0
    15
 */
David@0
    16
David@0
    17
#import "CBPurpleAccount.h"
David@0
    18
zacw@1509
    19
#import "PurpleService.h"
zacw@1509
    20
David@0
    21
#import <libpurple/notify.h>
David@0
    22
#import <libpurple/cmds.h>
David@0
    23
#import <AdiumLibpurple/SLPurpleCocoaAdapter.h>
David@0
    24
#import <Adium/AIAccount.h>
David@0
    25
#import <Adium/AIChat.h>
David@0
    26
#import <Adium/AIContentMessage.h>
zacw@1292
    27
#import <Adium/AIContentTopic.h>
zacw@1305
    28
#import <Adium/AIContentEvent.h>
zacw@1346
    29
#import <Adium/AIContentContext.h>
David@0
    30
#import <Adium/AIContentNotification.h>
David@0
    31
#import <Adium/AIHTMLDecoder.h>
David@0
    32
#import <Adium/AIListContact.h>
David@0
    33
#import <Adium/AIListGroup.h>
David@0
    34
#import <Adium/AIListObject.h>
David@0
    35
#import <Adium/AIMetaContact.h>
David@0
    36
#import <Adium/AIService.h>
David@0
    37
#import <Adium/AIServiceIcons.h>
David@0
    38
#import <Adium/AIStatus.h>
David@0
    39
#import <Adium/ESFileTransfer.h>
David@0
    40
#import <Adium/AIWindowController.h>
David@0
    41
#import <Adium/AIEmoticon.h>
David@0
    42
#import <Adium/AIAccountControllerProtocol.h>
David@0
    43
#import <Adium/AIChatControllerProtocol.h>
David@0
    44
#import <Adium/AIContactControllerProtocol.h>
David@14
    45
#import <Adium/AIContactObserverManager.h>
David@0
    46
#import <Adium/AIContentControllerProtocol.h>
David@0
    47
#import <Adium/AIInterfaceControllerProtocol.h>
David@0
    48
#import <Adium/AIStatusControllerProtocol.h>
David@0
    49
#import <AIUtilities/AIAttributedStringAdditions.h>
David@0
    50
#import <AIUtilities/AIDictionaryAdditions.h>
David@0
    51
#import <AIUtilities/AIMenuAdditions.h>
David@0
    52
#import <AIUtilities/AIMutableOwnerArray.h>
David@0
    53
#import <AIUtilities/AIStringAdditions.h>
David@0
    54
#import <AIUtilities/AIApplicationAdditions.h>
David@0
    55
#import <AIUtilities/AIObjectAdditions.h>
David@0
    56
#import <AIUtilities/AIImageAdditions.h>
David@0
    57
#import <AIUtilities/AIImageDrawingAdditions.h>
David@0
    58
#import <AIUtilities/AIMutableStringAdditions.h>
David@0
    59
#import <AIUtilities/AISystemNetworkDefaults.h>
David@36
    60
#import <Adium/AdiumAuthorization.h>
zacw@3039
    61
#import <Adium/AIMedia.h>
zacw@3039
    62
#import <Adium/AIMediaControllerProtocol.h>
David@0
    63
David@0
    64
#import "ESiTunesPlugin.h"
David@0
    65
#import "AMPurpleTuneTooltip.h"
David@0
    66
#import "adiumPurpleRequest.h"
zacw@3045
    67
#import "adiumPurpleMedia.h"
David@0
    68
#import "AIDualWindowInterfacePlugin.h"
David@0
    69
zacw@998
    70
#ifdef HAVE_CDSA
zacw@998
    71
#import "AIPurpleCertificateViewer.h"
zacw@998
    72
#endif
zacw@998
    73
David@0
    74
#define NO_GROUP						@"__NoGroup__"
David@0
    75
David@0
    76
#define	PREF_GROUP_ALIASES			@"Aliases"		//Preference group to store aliases in
David@0
    77
#define NEW_ACCOUNT_DISPLAY_TEXT		AILocalizedString(@"<New Account>", "Placeholder displayed as the name of a new account")
David@0
    78
David@0
    79
#define	KEY_PRIVACY_OPTION	@"Privacy Option"
David@0
    80
David@84
    81
@interface CBPurpleAccount ()
David@0
    82
- (NSString *)_mapIncomingGroupName:(NSString *)name;
David@0
    83
- (NSString *)_mapOutgoingGroupName:(NSString *)name;
David@0
    84
- (void)setTypingFlagOfChat:(AIChat *)inChat to:(NSNumber *)typingState;
David@0
    85
- (void)_receivedMessage:(NSAttributedString *)attributedMessage inChat:(AIChat *)chat fromListContact:(AIListContact *)sourceContact flags:(PurpleMessageFlags)flags date:(NSDate *)date;
David@0
    86
- (NSNumber *)shouldCheckMail;
David@0
    87
- (void)configurePurpleAccountNotifyingTarget:(id)target selector:(SEL)selector;
David@0
    88
- (void)continueConnectWithConfiguredProxy;
David@0
    89
- (void)continueRegisterWithConfiguredPurpleAccount;
David@0
    90
- (void)promptForHostBeforeConnecting;
David@0
    91
- (void)setAccountProfileTo:(NSAttributedString *)profile configurePurpleAccountContext:(NSInvocation *)inInvocation;
David@0
    92
- (void)performAccountMenuAction:(NSMenuItem *)sender;
zacw@998
    93
zacw@999
    94
- (void)showServerCertificate;
sholt@3080
    95
sholt@3080
    96
- (void)retrievedProxyConfiguration:(NSDictionary *)proxyConfig context:(NSInvocation *)invocation;
sholt@3080
    97
- (void)iTunesDidUpdate:(NSNotification *)notification;
David@0
    98
@end
David@0
    99
David@0
   100
@implementation CBPurpleAccount
David@0
   101
David@0
   102
static SLPurpleCocoaAdapter *purpleAdapter = nil;
David@0
   103
David@0
   104
// The PurpleAccount currently associated with this Adium account
David@0
   105
- (PurpleAccount*)purpleAccount
David@0
   106
{
David@0
   107
	//Create a purple account if one does not already exist
David@0
   108
	if (!account) {
David@0
   109
		[self createNewPurpleAccount];
David@427
   110
		AILog(@"Created PurpleAccount 0x%x with UID %@, protocolPlugin %s", account, self.UID, [self protocolPlugin]);
David@0
   111
	}
David@0
   112
	
David@0
   113
    return account;
David@0
   114
}
David@0
   115
David@0
   116
- (SLPurpleCocoaAdapter *)purpleAdapter
David@0
   117
{
David@0
   118
	if (!purpleAdapter) {
David@0
   119
		purpleAdapter = [[SLPurpleCocoaAdapter sharedInstance] retain];	
David@0
   120
	}	
David@0
   121
	return purpleAdapter;
David@0
   122
}
David@0
   123
David@0
   124
// Subclasses must override this
David@0
   125
- (const char*)protocolPlugin { return NULL; }
David@0
   126
zacw@2329
   127
- (PurplePluginProtocolInfo *)protocolInfo
zacw@2329
   128
{
zacw@2329
   129
	PurplePlugin				*prpl;
zacw@2329
   130
	
zacw@2330
   131
	if ((prpl = purple_find_prpl(purple_account_get_protocol_id(account)))) {
zacw@2329
   132
		return PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
zacw@2329
   133
	}
zacw@2329
   134
	
zacw@2329
   135
	return NULL;
zacw@2329
   136
}
zacw@2329
   137
David@0
   138
// Contacts ------------------------------------------------------------------------------------------------
David@0
   139
#pragma mark Contacts
David@0
   140
- (void)newContact:(AIListContact *)theContact withName:(NSString *)inName
David@0
   141
{
David@0
   142
David@0
   143
}
David@0
   144
David@702
   145
- (void)addContact:(AIListContact *)theContact toGroupName:(NSString *)groupName contactName:(NSString *)contactName
David@0
   146
{
David@0
   147
	//When a new contact is created, if we aren't already silent and delayed, set it  a second to cover our initial
David@0
   148
	//status updates
David@0
   149
	if (!silentAndDelayed) {
David@0
   150
		[self silenceAllContactUpdatesForInterval:2.0];
David@14
   151
		[[AIContactObserverManager sharedManager] delayListObjectNotificationsUntilInactivity];		
David@0
   152
	}
David@0
   153
	
David@0
   154
	//If the name we were passed differs from the current formatted UID of the contact, it's itself a formatted UID
David@0
   155
	//This is important since we may get an alias ("Evan Schoenberg") from the server but also want the formatted name
David@837
   156
	if (![contactName isEqualToString:theContact.formattedUID] && ![contactName isEqualToString:theContact.UID]) {
David@0
   157
		[theContact setValue:contactName
thijsalkemade@3380
   158
							 forProperty:@"formattedUID"
David@0
   159
							 notify:NotifyLater];
David@0
   160
	}
David@0
   161
	
David@0
   162
	if (groupName && [groupName isEqualToString:@PURPLE_ORPHANS_GROUP_NAME]) {
David@589
   163
		[theContact addRemoteGroupName:AILocalizedString(@"Orphans","Name for the orphans group")];
David@0
   164
	} else if (groupName && [groupName length] != 0) {
David@589
   165
		[theContact addRemoteGroupName:[self _mapIncomingGroupName:groupName]];
David@0
   166
	} else {
David@0
   167
		AILog(@"Got a nil group for %@",theContact);
David@0
   168
	}
David@0
   169
	
David@0
   170
	[self gotGroupForContact:theContact];
David@0
   171
}
David@0
   172
David@702
   173
- (void)removeContact:(AIListContact *)theContact fromGroupName:(NSString *)groupName
David@702
   174
{
David@702
   175
	NSParameterAssert(groupName != nil); //is this always true?
David@702
   176
	NSParameterAssert(theContact != nil);
David@702
   177
	[theContact removeRemoteGroupName:[self _mapIncomingGroupName:groupName]];
David@702
   178
}
David@702
   179
David@0
   180
/*!
David@0
   181
 * @brief Change the UID of a contact
David@0
   182
 *
David@0
   183
 * If we're just passed a formatted version of the current UID, don't change the UID but instead use the information
David@0
   184
 * as the FormattedUID.  For example, we get sent this when an AIM contact's name formatting changes; we always want
David@0
   185
 * to use a lowercase and space-free version for the UID, however.
David@0
   186
 */
David@0
   187
- (void)renameContact:(AIListContact *)theContact toUID:(NSString *)newUID
David@0
   188
{
David@0
   189
	//If the name we were passed differs from the current formatted UID of the contact, it's itself a formatted UID
David@0
   190
	//This is important since we may get an alias ("Evan Schoenberg") from the server but also want the formatted name
David@427
   191
	NSString	*normalizedUID = [self.service normalizeUID:newUID removeIgnoredCharacters:YES];
David@0
   192
	
David@721
   193
	if ([normalizedUID isEqualToString:theContact.UID]) {
David@0
   194
		[theContact setValue:newUID
thijsalkemade@3380
   195
							 forProperty:@"formattedUID"
David@0
   196
							 notify:NotifyLater];		
David@0
   197
	} else {
David@0
   198
		[theContact setUID:newUID];		
David@0
   199
	}
David@0
   200
}
David@0
   201
David@0
   202
- (void)updateContact:(AIListContact *)theContact toAlias:(NSString *)purpleAlias
David@0
   203
{
David@721
   204
	if (![[purpleAlias compactedString] isEqualToString:[theContact.UID compactedString]]) {
David@0
   205
		//Store this alias as the serverside display name so long as it isn't identical when unformatted to the UID
David@0
   206
		[theContact setServersideAlias:purpleAlias
David@0
   207
							  silently:silentAndDelayed];
David@0
   208
David@0
   209
	} else {
David@0
   210
		//If it's the same characters as the UID, apply it as a formatted UID
David@837
   211
		if (![purpleAlias isEqualToString:theContact.formattedUID] && 
David@721
   212
			![purpleAlias isEqualToString:theContact.UID]) {
David@0
   213
			[theContact setFormattedUID:purpleAlias
David@0
   214
								 notify:NotifyLater];
David@0
   215
David@0
   216
			//Apply any changes
David@0
   217
			[theContact notifyOfChangedPropertiesSilently:silentAndDelayed];
David@0
   218
		}
David@0
   219
	}
David@0
   220
}
David@0
   221
David@0
   222
- (void)updateContact:(AIListContact *)theContact forEvent:(NSNumber *)event
David@0
   223
{
David@0
   224
}		
David@0
   225
David@0
   226
David@0
   227
//Signed online
David@0
   228
- (void)updateSignon:(AIListContact *)theContact withData:(void *)data
David@0
   229
{
David@0
   230
	[theContact setOnline:YES
David@0
   231
				   notify:NotifyLater
David@0
   232
				 silently:silentAndDelayed];
David@0
   233
David@0
   234
	[theContact notifyOfChangedPropertiesSilently:silentAndDelayed];
David@0
   235
}
David@0
   236
David@0
   237
//Signed offline
David@0
   238
- (void)updateSignoff:(AIListContact *)theContact withData:(void *)data
David@0
   239
{
David@0
   240
	[theContact setOnline:NO
David@0
   241
				   notify:NotifyLater
David@0
   242
				 silently:silentAndDelayed];
David@0
   243
	
David@0
   244
	[theContact notifyOfChangedPropertiesSilently:silentAndDelayed];
David@0
   245
}
David@0
   246
David@0
   247
//Signon Time
David@0
   248
- (void)updateSignonTime:(AIListContact *)theContact withData:(NSDate *)signonDate
David@0
   249
{	
David@0
   250
	[theContact setSignonDate:signonDate
David@0
   251
					   notify:NotifyLater];
David@0
   252
	
David@0
   253
	//Apply any changes
David@0
   254
	[theContact notifyOfChangedPropertiesSilently:silentAndDelayed];
David@0
   255
}
David@0
   256
David@0
   257
/*!
David@0
   258
 * @brief Status name to use for a Purple buddy
David@0
   259
 */
David@0
   260
- (NSString *)statusNameForPurpleBuddy:(PurpleBuddy *)buddy
David@0
   261
{
David@0
   262
	return nil;
David@0
   263
}
David@0
   264
David@0
   265
/*!
David@0
   266
 * @brief Status message for a contact
David@0
   267
 */
David@0
   268
- (NSAttributedString *)statusMessageForPurpleBuddy:(PurpleBuddy *)buddy
David@0
   269
{
David@0
   270
	PurplePresence		*presence = purple_buddy_get_presence(buddy);
David@0
   271
	PurpleStatus		*status = (presence ? purple_presence_get_active_status(presence) : NULL);
David@0
   272
	const char			*message = (status ? purple_status_get_attr_string(status, "message") : NULL);
thijsalkemade@3380
   273
	NSString			*buddyStatusMessage = nil;
David@0
   274
	
zacw@2110
   275
	// Get the plugin's status message for this buddy if they don't have a status message
zacw@2110
   276
	if (!message) {
zacw@2329
   277
		PurplePluginProtocolInfo  *prpl_info = self.protocolInfo;
zacw@2110
   278
		
zacw@2110
   279
		if (prpl_info && prpl_info->status_text) {
zacw@2227
   280
			char *status_text = (prpl_info->status_text)(buddy);
zacw@2120
   281
			
zacw@2120
   282
			// Don't display "Offline" as a status message.
zacw@2227
   283
			if (status_text && strcmp(status_text, _("Offline")) != 0) {
thijsalkemade@3380
   284
				buddyStatusMessage = [NSString stringWithUTF8String:status_text];				
zacw@2120
   285
			}
zacw@2227
   286
			
zacw@2227
   287
			g_free(status_text);
zacw@2110
   288
		}
zacw@2227
   289
	} else {
thijsalkemade@3380
   290
		buddyStatusMessage = [NSString stringWithUTF8String:message];
zacw@2110
   291
	}
zacw@2221
   292
	
thijsalkemade@3380
   293
	return buddyStatusMessage ? [AIHTMLDecoder decodeHTML:buddyStatusMessage] : nil;
David@0
   294
}
David@0
   295
David@0
   296
/*!
David@0
   297
 * @brief Update the status message and away state of the contact
David@0
   298
 */
thijsalkemade@3380
   299
- (void)updateStatusForContact:(AIListContact *)theContact toStatusType:(NSNumber *)statusTypeNumber statusName:(NSString *)statusName statusMessage:(NSAttributedString *)inStatusMessage isMobile:(BOOL)isMobile
David@0
   300
{
David@0
   301
	[theContact setStatusWithName:statusName
sholt@3078
   302
					   statusType:[statusTypeNumber intValue]
David@0
   303
						   notify:NotifyLater];
thijsalkemade@3380
   304
	[theContact setStatusMessage:inStatusMessage
David@0
   305
						  notify:NotifyLater];
Evan@153
   306
	[theContact setIsMobile:isMobile notify:NotifyLater];
Evan@153
   307
David@0
   308
	//Apply the change
David@0
   309
	[theContact notifyOfChangedPropertiesSilently:silentAndDelayed];
David@0
   310
}
David@0
   311
David@0
   312
//Idle time
David@0
   313
- (void)updateWentIdle:(AIListContact *)theContact withData:(NSDate *)idleSinceDate
David@0
   314
{
David@0
   315
	[theContact setIdle:YES sinceDate:idleSinceDate notify:NotifyLater];
David@0
   316
David@0
   317
	//Apply any changes
David@0
   318
	[theContact notifyOfChangedPropertiesSilently:silentAndDelayed];
David@0
   319
}
David@0
   320
- (void)updateIdleReturn:(AIListContact *)theContact withData:(void *)data
David@0
   321
{
David@0
   322
	[theContact setIdle:NO
David@0
   323
			  sinceDate:nil
David@0
   324
				 notify:NotifyLater];
David@0
   325
David@0
   326
	//Apply any changes
David@0
   327
	[theContact notifyOfChangedPropertiesSilently:silentAndDelayed];
David@0
   328
}
David@0
   329
	
David@0
   330
//Evil level (warning level)
David@0
   331
- (void)updateEvil:(AIListContact *)theContact withData:(NSNumber *)evilNumber
David@0
   332
{
sholt@2693
   333
	[theContact setWarningLevel:[evilNumber integerValue]
David@0
   334
						 notify:NotifyLater];
David@0
   335
David@0
   336
	//Apply any changes
David@0
   337
	[theContact notifyOfChangedPropertiesSilently:silentAndDelayed];
David@0
   338
}
David@0
   339
David@0
   340
David@0
   341
- (void)clearIconForContact:(AIListContact *)theContact
David@0
   342
{
David@0
   343
	[theContact setServersideIconData:nil
David@0
   344
							   notify:NotifyLater];
David@0
   345
	
David@0
   346
	//Apply any changes
David@0
   347
	[theContact notifyOfChangedPropertiesSilently:silentAndDelayed];	
David@0
   348
}
David@0
   349
David@0
   350
//Buddy Icon
David@0
   351
- (void)updateIcon:(AIListContact *)theContact withData:(NSData *)userIconData
David@0
   352
{
David@0
   353
	[NSObject cancelPreviousPerformRequestsWithTarget:self
David@0
   354
											 selector:@selector(clearIconForContact:)
David@0
   355
											   object:theContact];
David@0
   356
	if (userIconData) {
David@0
   357
		[theContact setServersideIconData:userIconData
David@0
   358
								   notify:NotifyLater];
David@0
   359
		
David@0
   360
		//Apply any changes
David@0
   361
		[theContact notifyOfChangedPropertiesSilently:silentAndDelayed];
David@0
   362
David@0
   363
	} else {
David@0
   364
		/* We may receive an empty icon update just before an actual change. We don't want to flicker through no-icon.
David@0
   365
		 * We therefore cancel empty icon updates when we receive a new icon, and we do the actual clearing on a delay in case
David@0
   366
		 * this is what is about to happen.
David@0
   367
		 */
David@0
   368
		[self performSelector:@selector(clearIconForContact:)
David@0
   369
				   withObject:theContact
David@0
   370
				   afterDelay:10.0];
David@0
   371
	}
David@0
   372
}
David@0
   373
David@0
   374
- (NSString *)processedIncomingUserInfo:(NSString *)inString
David@0
   375
{
David@0
   376
	NSMutableString *returnString = nil;
David@0
   377
	if ([inString rangeOfString:@"Purple could not find any information in the user's profile. The user most likely does not exist."].location != NSNotFound) {
David@0
   378
		returnString = [[inString mutableCopy] autorelease];
David@0
   379
		[returnString replaceOccurrencesOfString:@"Purple could not find any information in the user's profile. The user most likely does not exist."
David@0
   380
									  withString:AILocalizedString(@"Adium could not find any information in the user's profile. This may not be a registered name.", "Message shown when a contact's profile can't be found")
David@0
   381
										 options:NSLiteralSearch
David@0
   382
										   range:NSMakeRange(0, [returnString length])];
David@0
   383
	}
David@0
   384
	
David@0
   385
	return (returnString ? returnString : inString);
David@0
   386
}
David@0
   387
Evan@660
   388
- (NSString *)webProfileStringForContact:(AIListContact *)contact
Evan@660
   389
{
Evan@660
   390
	return [NSString stringWithFormat:NSLocalizedString(@"View %@'s %@ web profile", nil), 
David@837
   391
			contact.formattedUID, [contact.service shortDescription]];
Evan@660
   392
}
Evan@660
   393
Evan@660
   394
- (NSMutableArray *)arrayOfDictionariesFromPurpleNotifyUserInfo:(PurpleNotifyUserInfo *)user_info forContact:(AIListContact *)contact
David@0
   395
{
David@0
   396
	GList *l;
David@0
   397
	NSMutableArray *array = [NSMutableArray array];
David@0
   398
	
David@0
   399
	for (l = purple_notify_user_info_get_entries(user_info); l != NULL; l = l->next) {
David@0
   400
		PurpleNotifyUserInfoEntry *user_info_entry = l->data;
David@0
   401
		
David@0
   402
		switch (purple_notify_user_info_entry_get_type(user_info_entry)) {
David@0
   403
			case PURPLE_NOTIFY_USER_INFO_ENTRY_SECTION_HEADER:
David@0
   404
				[array addObject:[NSDictionary dictionaryWithObjectsAndKeys:
David@0
   405
								  [NSString stringWithUTF8String:purple_notify_user_info_entry_get_label(user_info_entry)], KEY_KEY,
sholt@2693
   406
								  [NSNumber numberWithInteger:AIUserInfoSectionHeader], KEY_TYPE,
David@0
   407
								  nil]];
David@0
   408
				
David@0
   409
				break;
David@0
   410
			case PURPLE_NOTIFY_USER_INFO_ENTRY_SECTION_BREAK:
David@0
   411
				[array addObject:[NSDictionary dictionaryWithObjectsAndKeys:
sholt@2693
   412
								  [NSNumber numberWithInteger:AIUserInfoSectionBreak], KEY_TYPE,
David@0
   413
								  nil]];
David@0
   414
				break;
David@0
   415
				
David@0
   416
			case PURPLE_NOTIFY_USER_INFO_ENTRY_PAIR:
David@0
   417
			{
David@0
   418
				if (purple_notify_user_info_entry_get_label(user_info_entry) && purple_notify_user_info_entry_get_value(user_info_entry)) {
David@0
   419
					[array addObject:[NSDictionary dictionaryWithObjectsAndKeys:
David@0
   420
									  [NSString stringWithUTF8String:purple_notify_user_info_entry_get_label(user_info_entry)], KEY_KEY,
David@0
   421
									  processPurpleImages([NSString stringWithUTF8String:purple_notify_user_info_entry_get_value(user_info_entry)], self), KEY_VALUE,
David@0
   422
									  nil]];
David@0
   423
					
David@0
   424
				} else if (purple_notify_user_info_entry_get_label(user_info_entry)) {
David@0
   425
					[array addObject:[NSDictionary dictionaryWithObject:
David@0
   426
									  [NSString stringWithUTF8String:purple_notify_user_info_entry_get_label(user_info_entry)]
David@0
   427
																 forKey:KEY_KEY]];
David@0
   428
				} else if (purple_notify_user_info_entry_get_value(user_info_entry)) {
David@0
   429
					NSMutableString	*value = [processPurpleImages([NSString stringWithUTF8String:purple_notify_user_info_entry_get_value(user_info_entry)],
David@0
   430
																  self) mutableCopy];
David@0
   431
					[value replaceOccurrencesOfString:@"<br>" withString:@"<br/>" options:(NSCaseInsensitiveSearch | NSLiteralSearch)];
David@0
   432
					[value replaceOccurrencesOfString:@"<br />" withString:@"<br/>" options:(NSCaseInsensitiveSearch | NSLiteralSearch)];
David@0
   433
					[value replaceOccurrencesOfString:@"<B>" withString:@"<b>" options:NSLiteralSearch];
David@0
   434
Evan@166
   435
					for (NSString *valuePair in [value componentsSeparatedByString:@"<br/><b>"]) {
David@0
   436
						NSRange	firstStartBold = [valuePair rangeOfString:@"<b>"];
David@0
   437
						NSRange	firstEndBold = [valuePair rangeOfString:@"</b>"];
David@0
   438
						
David@0
   439
						if (firstEndBold.length > 0) {
David@0
   440
							// Chop off <b> from the beginning and :</b> from the end. The extra -1 is for the colon.
David@0
   441
							[array addObject:[NSDictionary dictionaryWithObjectsAndKeys:
David@0
   442
											  [valuePair substringWithRange:NSMakeRange(firstStartBold.length, firstEndBold.location-firstStartBold.length-1)], KEY_KEY,
David@0
   443
											  [valuePair substringFromIndex:NSMaxRange(firstEndBold)], KEY_VALUE,
David@0
   444
											  nil]];
David@0
   445
						} else {
David@0
   446
							[array addObject:[NSDictionary dictionaryWithObject:valuePair
David@0
   447
																		forKey:KEY_VALUE]];
David@0
   448
						}
David@0
   449
					}
David@0
   450
					[value release];
David@0
   451
				}	
David@0
   452
				break;
David@0
   453
			}
David@0
   454
		}
David@0
   455
	}
David@0
   456
Evan@660
   457
	NSString *webProfileValue = [NSString stringWithFormat:@"%s</a>", _("View web profile")];
Evan@660
   458
	
sholt@2693
   459
	NSInteger i;
sholt@2693
   460
	NSUInteger count = [array count];
Evan@660
   461
	for (i = 0; i < count; i++) {
Evan@660
   462
		NSDictionary *dict = [array objectAtIndex:i];
Evan@660
   463
		NSString *value = [dict objectForKey:KEY_VALUE];
Evan@660
   464
		if (value &&
Evan@660
   465
			[value rangeOfString:webProfileValue options:(NSBackwardsSearch | NSAnchoredSearch | NSLiteralSearch)].location != NSNotFound) {
David@1660
   466
			NSMutableString *newValue = [[value mutableCopy] autorelease];
Evan@660
   467
			[newValue replaceOccurrencesOfString:webProfileValue
Evan@660
   468
									  withString:[self webProfileStringForContact:contact]
Evan@660
   469
										 options:(NSBackwardsSearch | NSAnchoredSearch | NSLiteralSearch)];
Evan@660
   470
			
Evan@660
   471
			NSMutableDictionary *replacementDict = [dict mutableCopy];
Evan@660
   472
			[replacementDict setObject:newValue forKey:KEY_VALUE];
Evan@660
   473
			[array replaceObjectAtIndex:i withObject:replacementDict];
Evan@660
   474
			[replacementDict release];
Evan@660
   475
Evan@660
   476
			/* There will only be 1 (at most) web profile link */
Evan@660
   477
			break;
Evan@660
   478
		}
Evan@660
   479
	}
Evan@660
   480
	
David@0
   481
	return array;
David@0
   482
}
David@0
   483
David@0
   484
- (void)updateUserInfo:(AIListContact *)theContact withData:(PurpleNotifyUserInfo *)user_info
David@0
   485
{
Evan@660
   486
	NSArray		*profileContents = [self arrayOfDictionariesFromPurpleNotifyUserInfo:user_info forContact:theContact];
zacw@1435
   487
David@0
   488
	[theContact setProfileArray:profileContents
David@0
   489
					notify:NotifyLater];
zacw@1400
   490
	
zacw@1435
   491
	[self openInspectorForContactInfo:theContact];
zacw@1409
   492
	
David@0
   493
	//Apply any changes
David@0
   494
	[theContact notifyOfChangedPropertiesSilently:silentAndDelayed];
David@0
   495
}
David@0
   496
David@0
   497
/*!
zacw@1435
   498
 * @brief Open the info inspector when getting info
zacw@1435
   499
 */
zacw@1435
   500
- (void)openInspectorForContactInfo:(AIListContact *)theContact
zacw@1435
   501
{
zacw@1899
   502
zacw@1435
   503
}
zacw@1435
   504
zacw@1435
   505
/*!
David@0
   506
 * @brief Purple removed a contact from the local blist
David@0
   507
 *
David@0
   508
 * This can happen in many situations:
David@0
   509
 *	- For every contact on an account when the account signs off
David@0
   510
 *	- For a contact as it is deleted by the user
David@0
   511
 *	- For a contact as it is deleted by Purple (e.g. when Sametime refuses an addition because it is known to be invalid)
David@0
   512
 *	- In the middle of the move process as a contact moves from one group to another
David@0
   513
 *
David@0
   514
 * We need not take any action; we'll be notified of changes by Purple as necessary.
David@0
   515
 */
David@0
   516
- (void)removeContact:(AIListContact *)theContact
David@0
   517
{
David@0
   518
David@0
   519
}
David@0
   520
David@0
   521
//To allow root level buddies on protocols which don't support them, we map any buddies in a group
David@0
   522
//named after this account's UID to the root group.  These functions handle the mapping.  Group names should
David@0
   523
//be filtered through incoming before being sent to Adium - and group names from Adium should be filtered through
David@0
   524
//outgoing before being used.
David@0
   525
- (NSString *)_mapIncomingGroupName:(NSString *)name
David@0
   526
{
David@427
   527
	if (!name || ([[name compactedString] caseInsensitiveCompare:self.UID] == NSOrderedSame)) {
David@0
   528
		return ADIUM_ROOT_GROUP_NAME;
David@0
   529
	} else {
David@0
   530
		return name;
David@0
   531
	}
David@0
   532
}
David@0
   533
- (NSString *)_mapOutgoingGroupName:(NSString *)name
David@0
   534
{
David@0
   535
	if ([[name compactedString] caseInsensitiveCompare:ADIUM_ROOT_GROUP_NAME] == NSOrderedSame) {
David@427
   536
		return self.UID;
David@0
   537
	} else {
David@0
   538
		return name;
David@0
   539
	}
David@0
   540
}
David@0
   541
David@0
   542
//Update the status of a contact (Request their profile)
David@0
   543
- (void)delayedUpdateContactStatus:(AIListContact *)inContact
David@0
   544
{
David@0
   545
    //Request profile
David@721
   546
	[purpleAdapter getInfoFor:inContact.UID onAccount:self];
David@0
   547
}
David@0
   548
David@0
   549
- (void)requestAddContactWithUID:(NSString *)contactUID
David@0
   550
{
David@89
   551
	[adium.contactController requestAddContactWithUID:contactUID
David@0
   552
												service:[self _serviceForUID:contactUID]
David@0
   553
												account:self];
David@0
   554
}
David@0
   555
David@0
   556
- (AIService *)_serviceForUID:(NSString *)contactUID
David@0
   557
{
David@427
   558
	return self.service;
David@0
   559
}
David@0
   560
David@0
   561
- (void)gotGroupForContact:(AIListContact *)listContact {};
David@0
   562
David@0
   563
/*!
David@0
   564
 * @brief Return the serverside icon for a contact
David@0
   565
 */
David@0
   566
- (NSData *)serversideIconDataForContact:(AIListContact *)contact
David@0
   567
{
David@0
   568
	PurpleBuddy		*buddy;
David@0
   569
	NSData			*data = nil;
David@0
   570
David@427
   571
	if (self.purpleAccount &&
David@715
   572
		(buddy = purple_find_buddy(account, [contact.UID UTF8String]))) {
David@0
   573
		PurpleBuddyIcon *buddyIcon;
David@0
   574
		BOOL			shouldUnref = NO;
David@0
   575
		
David@0
   576
		/* First, try to get a current buddy icon from the PurpleBuddy */
David@0
   577
		buddyIcon = purple_buddy_get_icon(buddy);
David@0
   578
		if (!buddyIcon) {
David@0
   579
			/* Failing that, load one from the cache. We'll need to unreference the returned PurpleBuddyIcon
David@0
   580
			 * when we're done.
David@0
   581
			 */
David@715
   582
			buddyIcon = purple_buddy_icons_find(account, [contact.UID UTF8String]);
David@0
   583
			shouldUnref = YES;
David@0
   584
		}
David@0
   585
		
David@0
   586
		if (buddyIcon) {
David@0
   587
			const guchar	*iconData;
David@0
   588
			size_t			len;
David@0
   589
			
David@0
   590
			iconData = purple_buddy_icon_get_data(buddyIcon, &len);
David@0
   591
			
David@0
   592
			if (iconData && len) {
David@0
   593
				data = [NSData dataWithBytes:iconData length:len];
David@0
   594
			}
David@0
   595
			
David@0
   596
			if (shouldUnref)
David@0
   597
				purple_buddy_icon_unref(buddyIcon);
David@0
   598
		}
David@0
   599
David@0
   600
	} else {
David@0
   601
		AILogWithSignature(@"Could not get serverside icon data for %@. account is %p", contact, account);
David@0
   602
	}
David@0
   603
	
David@0
   604
	return data;
David@0
   605
}
David@0
   606
David@0
   607
/*!
David@0
   608
 * @brief Libpurple manages a contact icon cache; we don't need to duplicate it.
David@0
   609
 */
David@0
   610
- (BOOL)managesOwnContactIconCache
David@0
   611
{
David@0
   612
	return YES;
David@0
   613
}
David@0
   614
David@0
   615
/*********************/
David@0
   616
/* AIAccount_Handles */
David@0
   617
/*********************/
David@0
   618
#pragma mark Contact List Editing
David@0
   619
zacw@2131
   620
- (void)removeContacts:(NSArray *)objects fromGroups:(NSArray *)groups
David@82
   621
{	
zacw@2131
   622
	for (AIListGroup *group in groups) {
zacw@2131
   623
		NSString *groupName = [self _mapOutgoingGroupName:group.UID];
zacw@2131
   624
	
zacw@2131
   625
		for (AIListContact *object in objects) {
David@714
   626
			//Have the purple thread perform the serverside actions
David@837
   627
			[purpleAdapter removeUID:object.UID onAccount:self fromGroup:groupName];
David@714
   628
			
David@714
   629
			//Remove it from Adium's list
David@714
   630
			[object removeRemoteGroupName:groupName];
David@714
   631
		}
David@0
   632
	}
David@0
   633
}
David@0
   634
David@199
   635
- (void)addContact:(AIListContact *)contact toGroup:(AIListGroup *)group
David@0
   636
{
David@721
   637
	NSString		*groupName = [self _mapOutgoingGroupName:group.UID];
David@0
   638
	
David@199
   639
	if(![group containsObject:contact]) {
David@199
   640
		AILogWithSignature(@"%@ adding %@ to %@", self, [self _UIDForAddingObject:contact], groupName);
David@199
   641
		
zacw@2249
   642
		NSString *alias = [contact.parentContact preferenceForKey:@"Alias"
zacw@2249
   643
						   group:PREF_GROUP_ALIASES];
zacw@2249
   644
		
zacw@2249
   645
		[purpleAdapter addUID:[self _UIDForAddingObject:contact] onAccount:self toGroup:groupName withAlias:alias];
David@0
   646
		
David@0
   647
		//Add it to Adium's list
David@703
   648
		[contact addRemoteGroupName:group.UID]; //Use the non-mapped group name locally
David@0
   649
	}
David@0
   650
}
David@0
   651
David@0
   652
- (NSString *)_UIDForAddingObject:(AIListContact *)object
David@0
   653
{
David@837
   654
	return object.UID;
David@0
   655
}
David@0
   656
zacw@2128
   657
- (NSSet *)mappedGroupNamesFromGroups:(NSSet *)groups
David@0
   658
{
zacw@2128
   659
	NSMutableSet *mappedNames = [NSMutableSet set];
zacw@2128
   660
	
zacw@2128
   661
	for (AIListGroup *group in groups) {
zacw@2128
   662
		[mappedNames addObject:[self _mapOutgoingGroupName:group.UID]];
David@709
   663
	}
David@0
   664
	
zacw@2128
   665
	return mappedNames;
zacw@2128
   666
}
zacw@2128
   667
zacw@2128
   668
- (void)moveListObjects:(NSArray *)objects fromGroups:(NSSet *)oldGroups toGroups:(NSSet *)groups
zacw@2128
   669
{
zacw@2128
   670
	NSSet *sourceMappedNames = [self mappedGroupNamesFromGroups:oldGroups];
zacw@2128
   671
	NSSet *destinationMappedNames = [self mappedGroupNamesFromGroups:groups];
zacw@2128
   672
David@0
   673
	//Move the objects to it
David@893
   674
	for (AIListContact *contact in objects) {
zacw@2266
   675
		if (![contact.remoteGroups intersectsSet:oldGroups] && oldGroups.count) {
zacw@2266
   676
			continue;
zacw@2266
   677
		}
zacw@2266
   678
		
zacw@2249
   679
		NSString *alias = [contact.parentContact preferenceForKey:@"Alias"
zacw@2249
   680
						   group:PREF_GROUP_ALIASES];
zacw@2249
   681
		
David@893
   682
		//Tell the purple thread to perform the serverside operation
zacw@2249
   683
		[purpleAdapter moveUID:contact.UID onAccount:self fromGroups:sourceMappedNames toGroups:destinationMappedNames withAlias:alias];
zacw@2128
   684
zacw@2128
   685
		for (AIListGroup *group in oldGroups) {
zacw@2128
   686
			[contact removeRemoteGroupName:group.UID];
zacw@2128
   687
		}
zacw@2128
   688
		
zacw@2128
   689
		for (AIListGroup *group in groups) {
zacw@2128
   690
			[contact addRemoteGroupName:group.UID];
zacw@2128
   691
		}
David@0
   692
	}		
David@0
   693
}
David@0
   694
David@0
   695
- (void)renameGroup:(AIListGroup *)inGroup to:(NSString *)newName
David@0
   696
{
David@721
   697
	NSString		*groupName = [self _mapOutgoingGroupName:inGroup.UID];
David@0
   698
David@0
   699
	//Tell the purple thread to perform the serverside operation	
David@0
   700
	[purpleAdapter renameGroup:groupName onAccount:self to:newName];
David@0
   701
David@0
   702
	//We must also update the remote grouping of all our contacts in that group
David@531
   703
	for (AIListContact *contact in [adium.contactController allContactsInObject:inGroup onAccount:self]) {
David@893
   704
		[contact removeRemoteGroupName:groupName];
David@0
   705
		//Evan: should we use groupName or newName here?
David@589
   706
		[contact addRemoteGroupName:newName];
David@0
   707
	}
David@0
   708
}
David@0
   709
David@0
   710
- (void)deleteGroup:(AIListGroup *)inGroup
David@0
   711
{
David@721
   712
	NSString		*groupName = [self _mapOutgoingGroupName:inGroup.UID];
David@0
   713
David@0
   714
	[purpleAdapter deleteGroup:groupName onAccount:self];
David@0
   715
}
David@0
   716
David@0
   717
// Return YES if the contact list is editable
David@0
   718
- (BOOL)contactListEditable
David@0
   719
{
David@837
   720
    return self.online;
David@0
   721
}
David@0
   722
zacw@1257
   723
- (id)authorizationRequestWithDict:(NSDictionary*)dict
zacw@1257
   724
{
zacw@1257
   725
	// We retain this in case libpurple wants to close the request early. It is freed below.
David@36
   726
	return [[AdiumAuthorization showAuthorizationRequestWithDict:dict forAccount:self] retain];
David@0
   727
}
David@0
   728
zacw@1257
   729
- (void)authorizationWithDict:(NSDictionary *)infoDict response:(AIAuthorizationResponse)authorizationResponse
David@0
   730
{
David@0
   731
	if (account) {
David@0
   732
		NSValue	*callback = nil;
David@0
   733
David@0
   734
		switch (authorizationResponse) {
David@0
   735
			case AIAuthorizationAllowed:
David@0
   736
				callback = [[[infoDict objectForKey:@"authorizeCB"] retain] autorelease];
David@0
   737
				break;
David@0
   738
			case AIAuthorizationDenied:
David@0
   739
				callback = [[[infoDict objectForKey:@"denyCB"] retain] autorelease];
David@0
   740
				break;
David@0
   741
			case AIAuthorizationNoResponse:
David@0
   742
				callback = nil;
David@0
   743
				break;
David@0
   744
		}
David@0
   745
		
zacw@1267
   746
		//libpurple will remove its reference to the handle for this request, which is inDict, in response to this callback invocation
David@0
   747
		if (callback) {
David@0
   748
			[purpleAdapter doAuthRequestCbValue:callback withUserDataValue:[[[infoDict objectForKey:@"userData"] retain] autorelease]];
David@0
   749
David@0
   750
			/* Retained in -[self authorizationRequestWithDict:].  We kept it around before now in case libpurle wanted us to close it early, such as because the
David@0
   751
			 * account disconnected.
David@0
   752
			 */
zacw@1257
   753
			[infoDict release];
David@0
   754
		} else {
zacw@1257
   755
			[purpleAdapter closeAuthRequestWithHandle:infoDict];
David@0
   756
			
David@0
   757
		}
David@0
   758
	}
David@0
   759
}
David@0
   760
zacw@1442
   761
#pragma mark Group chat ignore
zacw@1442
   762
- (BOOL)accountManagesGroupChatIgnore
zacw@1442
   763
{
zacw@1442
   764
	return YES;
zacw@1442
   765
}
zacw@1442
   766
zacw@1442
   767
- (BOOL)contact:(AIListContact *)inContact isIgnoredInChat:(AIChat *)chat
zacw@1442
   768
{
zacw@1649
   769
	if (self.online && chat.isGroupChat) {
zacw@1442
   770
		return [purpleAdapter contact:inContact isIgnoredInChat:chat];
zacw@1442
   771
	} else {
zacw@1442
   772
		return NO;
zacw@1442
   773
	}
zacw@1442
   774
}
zacw@1442
   775
zacw@1442
   776
- (void)setContact:(AIListContact *)inContact ignored:(BOOL)inIgnored inChat:(AIChat *)chat
zacw@1442
   777
{
zacw@1649
   778
	if (self.online && chat.isGroupChat) {
zacw@1442
   779
		[purpleAdapter setContact:inContact ignored:inIgnored inChat:chat];
zacw@1442
   780
	}
zacw@1442
   781
}
zacw@1442
   782
David@0
   783
//Chats ------------------------------------------------------------
David@0
   784
#pragma mark Chats
zacw@1406
   785
- (void)removeUser:(NSString *)contactName fromChat:(AIChat *)chat
zacw@1406
   786
{
zacw@1406
   787
	if (!chat)
zacw@1406
   788
		return;
zacw@1406
   789
	
zacw@1460
   790
	AIListContact *contact = [self contactWithUID:contactName];
zacw@1406
   791
	[chat removeObject:contact];
zacw@2412
   792
	
zacw@2412
   793
	if (contact.isStranger && 
zacw@2412
   794
		![adium.chatController allGroupChatsContainingContact:contact.parentContact].count &&
zacw@2412
   795
		[adium.chatController existingChatWithContact:contact.parentContact]) {
zacw@2412
   796
		// The contact is a stranger, not in any more group chats, but we have a message with them open.
zacw@2412
   797
		// Set their status to unknown.
zacw@2412
   798
		
zacw@2412
   799
		[contact setStatusWithName:nil
zacw@2412
   800
						statusType:AIUnknownStatus
zacw@2412
   801
							notify:NotifyLater];
zacw@2412
   802
		
zacw@2412
   803
		[contact setValue:nil
thijsalkemade@3380
   804
			  forProperty:@"isOnline"
zacw@2412
   805
				   notify:NotifyLater];
zacw@2412
   806
		
zacw@2412
   807
		[contact notifyOfChangedPropertiesSilently:NO];
zacw@2412
   808
	}
zacw@1406
   809
}
zacw@1406
   810
zacw@1406
   811
- (void)removeUsersArray:(NSArray *)usersArray fromChat:(AIChat *)chat
zacw@1406
   812
{
zacw@1406
   813
	for (NSString *contactName in usersArray) {
zacw@1406
   814
		[self removeUser:contactName fromChat:chat];
zacw@1406
   815
	}
zacw@1406
   816
}
David@0
   817
zacw@1460
   818
- (void)updateUserListForChat:(AIChat *)chat users:(NSArray *)users newlyAdded:(BOOL)newlyAdded
David@0
   819
{
zacw@1460
   820
	NSMutableArray *newListObjects = [NSMutableArray array];
zacw@1395
   821
	
zacw@1460
   822
	for (NSDictionary *user in users) {
zacw@1460
   823
		AIListContact *contact = [self contactWithUID:[user objectForKey:@"UID"]];
zacw@1406
   824
		
zacw@3331
   825
		AILogWithSignature(@"%@ join %@", chat, contact);
zacw@3331
   826
		
zacw@1743
   827
		[contact setOnline:YES notify:NotifyNever silently:YES];
zacw@1743
   828
		
zacw@1460
   829
		[newListObjects addObject:contact];
zacw@1395
   830
	}
zacw@1395
   831
	
zacw@1460
   832
	[chat addParticipatingListObjects:newListObjects notify:newlyAdded];
zacw@1395
   833
	
zacw@1460
   834
	for (NSDictionary *user in users) {
zacw@1460
   835
		AIListContact *contact = [self contactWithUID:[user objectForKey:@"UID"]];
zacw@1395
   836
		
zacw@1460
   837
		[chat setFlags:(AIGroupChatFlags)[[user objectForKey:@"Flags"] integerValue] forContact:contact];
zacw@1743
   838
		
zacw@1460
   839
		if ([user objectForKey:@"Alias"]) {
zacw@1460
   840
			[chat setAlias:[user objectForKey:@"Alias"] forContact:contact];
zacw@1740
   841
			
zacw@1740
   842
			if (contact.isStranger) {
zacw@1740
   843
				[contact setServersideAlias:[user objectForKey:@"Alias"] silently:NO];
zacw@1740
   844
			}
zacw@1460
   845
		}
David@1012
   846
	}
David@1732
   847
	
zacw@1395
   848
	// Post an update notification now that we've modified the flags and names.
zacw@1395
   849
	[[NSNotificationCenter defaultCenter] postNotificationName:Chat_ParticipatingListObjectsChanged
zacw@1395
   850
														object:chat];
zacw@1395
   851
}
zacw@1395
   852
zacw@1460
   853
- (void)renameParticipant:(NSString *)oldUID newName:(NSString *)newUID newAlias:(NSString *)newAlias flags:(AIGroupChatFlags)flags inChat:(AIChat *)chat
zacw@1395
   854
{
zacw@1406
   855
	[chat removeSavedValuesForContactUID:oldUID];
zacw@1404
   856
	
zacw@1406
   857
	AIListContact *contact = [adium.contactController existingContactWithService:self.service account:self UID:oldUID];
zacw@1406
   858
zacw@1404
   859
	if (contact) {
zacw@1406
   860
		[adium.contactController setUID:newUID forContact:contact];
zacw@1404
   861
	} else {
zacw@1406
   862
		contact = [self contactWithUID:newUID];
zacw@1404
   863
	}
zacw@1406
   864
zacw@1461
   865
	[chat setFlags:flags forContact:contact];
zacw@1395
   866
	[chat setAlias:newAlias forContact:contact];
zacw@1461
   867
	
zacw@1461
   868
	if (contact.isStranger) {
zacw@1461
   869
		[contact setServersideAlias:newAlias silently:NO];
zacw@1461
   870
	}
zacw@1395
   871
zacw@1395
   872
	// Post an update notification since we modified the user entirely.
zacw@1395
   873
	[[NSNotificationCenter defaultCenter] postNotificationName:Chat_ParticipatingListObjectsChanged
zacw@1395
   874
														object:chat];
zacw@1395
   875
}
zacw@1395
   876
zacw@1740
   877
- (void)setAttribute:(NSString *)name value:(NSString *)value forContact:(AIListContact *)contact
zacw@1740
   878
{
zacw@1740
   879
	NSString *property = nil;
zacw@1740
   880
	
zacw@1740
   881
	if ([name isEqualToString:@"userhost"]) {
zacw@1740
   882
		property = @"User Host";
zacw@1740
   883
	} else if ([name isEqualToString:@"realname"]) {
zacw@1740
   884
		property = @"Real Name";
zacw@1740
   885
	} else {
zacw@1740
   886
		AILog(@"Unknown attribute: %@ value %@", name, value);
zacw@1740
   887
	}
zacw@1740
   888
	
zacw@1740
   889
	if (property) {
zacw@1740
   890
		// Callsite should notify.
zacw@1740
   891
		[contact setValue:value forProperty:property notify:NotifyLater];
zacw@1740
   892
	}
zacw@1740
   893
}
zacw@1740
   894
zacw@1740
   895
zacw@1740
   896
- (void)updateUser:(NSString *)user
zacw@1740
   897
		   forChat:(AIChat *)chat
zacw@1740
   898
			 flags:(AIGroupChatFlags)flags
zacw@2231
   899
			 alias:(NSString *)alias
zacw@1740
   900
		attributes:(NSDictionary *)attributes
zacw@1395
   901
{
zacw@2231
   902
	BOOL triggerUserlistUpdate = NO;
zacw@2231
   903
	
zacw@1460
   904
	AIListContact *contact = [self contactWithUID:user];
zacw@1406
   905
	
zacw@1740
   906
	AIGroupChatFlags oldFlags = [chat flagsForContact:contact];
zacw@2231
   907
	NSString *oldAlias = [chat aliasForContact:contact];
zacw@1740
   908
	
zacw@2231
   909
	// Trigger an update if the alias or flags (ignoring away state) changes.
zacw@2231
   910
	if ((alias && !oldAlias)
zacw@2231
   911
		|| (!alias && oldAlias)
zacw@2231
   912
		|| ![[chat aliasForContact:contact] isEqualToString:alias]
zacw@2231
   913
		|| (flags & ~AIGroupChatAway) != (oldFlags & ~AIGroupChatAway)) {
zacw@2231
   914
		triggerUserlistUpdate = YES;
zacw@2231
   915
	}
zacw@2231
   916
zacw@2231
   917
	[chat setAlias:alias forContact:contact];
zacw@1406
   918
	[chat setFlags:flags forContact:contact];
zacw@1395
   919
	
zacw@1743
   920
	// Away changes only come in after the initial one, so we're safe in only updating it here.
zacw@1744
   921
	if (contact.isStranger) {
zacw@1744
   922
		[contact setStatusWithName:nil
zacw@1744
   923
						statusType:((flags & AIGroupChatAway) == AIGroupChatAway) ? AIAwayStatusType : AIAvailableStatusType
zacw@1744
   924
							notify:NotifyLater];
zacw@1744
   925
	}
zacw@1743
   926
zacw@1740
   927
	for (NSString *key in attributes.allKeys) {
zacw@1740
   928
		[self setAttribute:key value:[attributes objectForKey:key] forContact:contact];
zacw@1740
   929
	}
zacw@1740
   930
	
zacw@1749
   931
	[contact notifyOfChangedPropertiesSilently:YES];
zacw@1740
   932
	
zacw@1768
   933
	// Post an update notification if we modified the flags; don't resort for away changes.
zacw@2231
   934
	if (triggerUserlistUpdate) {
zacw@1740
   935
		[[NSNotificationCenter defaultCenter] postNotificationName:Chat_ParticipatingListObjectsChanged
zacw@1740
   936
															object:chat];
zacw@1740
   937
	}
David@0
   938
}
David@0
   939
David@0
   940
/*!
David@0
   941
 * @brief Called by Purple code when a chat should be opened by the interface
David@0
   942
 *
David@0
   943
 * If the user sent an initial message, this will be triggered and have no effect.
David@0
   944
 *
David@0
   945
 * If a remote user sent an initial message, however, a chat will be created without being opened.  This call is our
David@0
   946
 * cue to actually open chat.
David@0
   947
 *
David@0
   948
 * Another situation in which this is relevant is when we request joining a group chat; the chat should only be actually
David@0
   949
 * opened once the server notifies us that we are in the room.
David@0
   950
 *
David@0
   951
 * This will ultimately call -[CBPurpleAccount openChat:] below if the chat was not previously open.
David@0
   952
 */
zacw@1351
   953
- (void)addChat:(AIChat *)chat
David@0
   954
{
David@0
   955
	AILogWithSignature(@"");
David@0
   956
David@0
   957
	//Open the chat
David@0
   958
	if ([chat isOpen]) {
zacw@1453
   959
		if ([chat boolValueForProperty:@"Rejoining Chat"]) {
zacw@1453
   960
			[self displayYouHaveConnectedInChat:chat];
zacw@1453
   961
			
zacw@1453
   962
			[chat setValue:nil forProperty:@"Rejoining Chat" notify:NotifyNever];
zacw@1453
   963
		}
David@0
   964
	}
David@0
   965
David@100
   966
	[adium.interfaceController openChat:chat];
David@0
   967
	
thijsalkemade@3380
   968
	[chat setValue:[NSNumber numberWithBool:YES] forProperty:@"accountJoined" notify:NotifyNow];
David@0
   969
}
David@0
   970
David@0
   971
//Open a chat for Adium
David@0
   972
- (BOOL)openChat:(AIChat *)chat
David@0
   973
{
David@0
   974
	/* The #if 0'd block below causes crashes in msn_tooltip_text() on MSN */
David@0
   975
#if 0
David@0
   976
	AIListContact	*listContact;
David@0
   977
	
David@0
   978
	//Obtain the contact's information if it's a stranger
David@837
   979
	if ((listContact = chat.listObject) && (listContact.isStranger)) {
David@0
   980
		[self delayedUpdateContactStatus:listContact];
David@0
   981
	}
David@0
   982
#endif
David@0
   983
	
David@426
   984
	AILog(@"purple openChat:%@ for %@",chat,chat.uniqueChatID);
David@0
   985
David@0
   986
	//Inform purple that we have opened this chat
David@0
   987
	[purpleAdapter openChat:chat onAccount:self];
David@0
   988
	
David@0
   989
	//Created the chat successfully
David@0
   990
	return YES;
David@0
   991
}
David@0
   992
David@0
   993
- (BOOL)closeChat:(AIChat*)chat
David@0
   994
{
David@0
   995
	[purpleAdapter closeChat:chat];
David@0
   996
	
zacw@2085
   997
	if (!chat.isGroupChat) {
zacw@2085
   998
		//Be sure any remaining typing flag is cleared as the chat closes
zacw@2085
   999
		[self setTypingFlagOfChat:chat to:nil];
zacw@2085
  1000
	}
zacw@2085
  1001
	
David@426
  1002
	AILog(@"purple closeChat:%@",chat.uniqueChatID);
David@0
  1003
	
David@0
  1004
    return YES;
David@0
  1005
}
David@0
  1006
David@0
  1007
- (void)chatWasDestroyed:(AIChat *)chat
David@0
  1008
{
David@95
  1009
	[adium.chatController accountDidCloseChat:chat];
David@0
  1010
}
David@0
  1011
David@0
  1012
- (void)chatJoinDidFail:(AIChat *)chat
David@0
  1013
{
David@95
  1014
	[adium.chatController accountDidCloseChat:chat];
David@0
  1015
}
David@0
  1016
David@0
  1017
/* 
David@0
  1018
 * @brief Rejoin a chat
David@0
  1019
 */
David@0
  1020
- (BOOL)rejoinChat:(AIChat *)chat
David@0
  1021
{
David@0
  1022
	[chat retain];
David@0
  1023
David@0
  1024
	PurpleConversation *conv = [[chat identifier] pointerValue];
David@0
  1025
	if (conv && conv->ui_data) {
David@0
  1026
		[(AIChat *)(conv->ui_data) release];
David@0
  1027
		conv->ui_data = NULL;
David@0
  1028
	}
David@0
  1029
David@0
  1030
	/* The identifier is how we associate a PurpleConversation with an AIChat.
David@0
  1031
	 * Clear the identifier so a new PurpleConversation will be made. The ChatCreationInfo for the chat is still around, so it can join.
David@0
  1032
	 */
David@0
  1033
	[chat setIdentifier:nil];
zacw@1453
  1034
	
zacw@1453
  1035
	[chat setValue:[NSNumber numberWithBool:YES] forProperty:@"Rejoining Chat" notify:NotifyNever];
zacw@1453
  1036
	
David@0
  1037
	[purpleAdapter openChat:chat onAccount:self];
David@0
  1038
David@0
  1039
	[chat autorelease];
David@0
  1040
David@0
  1041
	//We don't get any immediate feedback as to our success; just return YES.
David@0
  1042
	return YES;
David@0
  1043
}
David@0
  1044
David@0
  1045
/*!
David@0
  1046
 * @brief A chat will be joined
David@0
  1047
 *
David@0
  1048
 * This gives the account a chance to update any information in the chat's creation dictionary if desired.
David@0
  1049
 *
David@0
  1050
 * @result The final chat creation dictionary to use.
David@0
  1051
 */
David@0
  1052
- (NSDictionary *)willJoinChatUsingDictionary:(NSDictionary *)chatCreationDictionary
David@0
  1053
{
David@0
  1054
	return chatCreationDictionary;
David@0
  1055
}
David@0
  1056
David@0
  1057
- (BOOL)chatCreationDictionary:(NSDictionary *)chatCreationDict isEqualToDictionary:(NSDictionary *)baseDict
David@0
  1058
{
David@0
  1059
	return [chatCreationDict isEqualToDictionary:baseDict];
David@0
  1060
}
David@0
  1061
David@1158
  1062
- (NSDictionary *)extractChatCreationDictionaryFromConversation:(PurpleConversation *)conv
David@1158
  1063
{
David@1158
  1064
	AILog(@"%@ needs an implementation of extractChatCreationDictionaryFromConversation to handle rejoins, bookmarks, and invitations properly", NSStringFromClass([self class]));
David@1158
  1065
	return nil;
David@1158
  1066
}
David@1158
  1067
David@0
  1068
- (AIChat *)chatWithContact:(AIListContact *)contact identifier:(id)identifier
David@0
  1069
{
David@95
  1070
	AIChat *chat = [adium.chatController chatWithContact:contact];
David@0
  1071
	[chat setIdentifier:identifier];
David@0
  1072
David@0
  1073
	return chat;
David@0
  1074
}
David@0
  1075
David@0
  1076
David@0
  1077
- (AIChat *)chatWithName:(NSString *)name identifier:(id)identifier
David@0
  1078
{
David@95
  1079
	return [adium.chatController chatWithName:name identifier:identifier onAccount:self chatCreationInfo:nil];
David@0
  1080
}
David@0
  1081
David@0
  1082
//Typing update in an IM
David@0
  1083
- (void)typingUpdateForIMChat:(AIChat *)chat typing:(NSNumber *)typingState
David@0
  1084
{
David@0
  1085
	[self setTypingFlagOfChat:chat
David@0
  1086
						   to:typingState];
David@0
  1087
}
David@0
  1088
David@0
  1089
//Multiuser chat update
David@0
  1090
- (void)convUpdateForChat:(AIChat *)chat type:(NSNumber *)type
David@0
  1091
{
David@0
  1092
David@0
  1093
}
David@0
  1094
David@0
  1095
/*!
David@0
  1096
 * @brief Called when we are informed that we left a multiuser chat
David@0
  1097
 */
David@0
  1098
- (void)leftChat:(AIChat *)chat
David@0
  1099
{
thijsalkemade@3380
  1100
	[chat setValue:nil forProperty:@"accountJoined" notify:NotifyNow];
David@0
  1101
}
David@0
  1102
zacw@1292
  1103
- (void)updateTopic:(NSString *)inTopic forChat:(AIChat *)chat withSource:(NSString *)source
zacw@1292
  1104
{	
zacw@1301
  1105
	// Update (not set) the chat's topic
zacw@1301
  1106
	[chat updateTopic:inTopic withSource:[self contactWithUID:source]];
David@0
  1107
}
zacw@1379
  1108
zacw@1379
  1109
/*!
zacw@1379
  1110
 * @brief Set a chat's topic
zacw@1379
  1111
 *
zacw@1379
  1112
 * This only has an effect on group chats.
zacw@1379
  1113
 */
zacw@1379
  1114
- (void)setTopic:(NSString *)topic forChat:(AIChat *)chat
zacw@1379
  1115
{
zacw@1379
  1116
	if (!chat.isGroupChat) {
zacw@1379
  1117
		return;
zacw@1379
  1118
	}
zacw@1379
  1119
	
zacw@2329
  1120
	PurplePluginProtocolInfo  *prpl_info = self.protocolInfo;
zacw@1379
  1121
	
zacw@1379
  1122
	if (prpl_info && prpl_info->set_chat_topic) {
zacw@1379
  1123
		(prpl_info->set_chat_topic)(purple_account_get_connection(account),
zacw@1379
  1124
									purple_conv_chat_get_id(purple_conversation_get_chat_data(convLookupFromChat(chat, self))),
zacw@1379
  1125
									[topic UTF8String]);
zacw@1379
  1126
	}
zacw@1379
  1127
}
zacw@1379
  1128
zacw@1379
  1129
David@0
  1130
- (void)updateTitle:(NSString *)inTitle forChat:(AIChat *)chat
David@0
  1131
{
David@0
  1132
	[[chat displayArrayForKey:@"Display Name"] setObject:inTitle
David@0
  1133
											   withOwner:self];
David@0
  1134
}
David@0
  1135
David@0
  1136
- (void)updateForChat:(AIChat *)chat type:(NSNumber *)type
David@0
  1137
{
sholt@3078
  1138
	AIChatUpdateType	updateType = [type intValue];
David@0
  1139
	NSString			*key = nil;
David@0
  1140
	switch (updateType) {
David@0
  1141
		case AIChatTimedOut:
David@0
  1142
		case AIChatClosedWindow:
David@0
  1143
			break;
David@0
  1144
	}
David@0
  1145
	
David@0
  1146
	if (key) {
David@0
  1147
		[chat setValue:[NSNumber numberWithBool:YES] forProperty:key notify:NotifyNow];
David@0
  1148
		[chat setValue:nil forProperty:key notify:NotifyNever];
David@0
  1149
		
David@0
  1150
	}
David@0
  1151
}
David@0
  1152
David@0
  1153
- (void)errorForChat:(AIChat *)chat type:(NSNumber *)type
David@0
  1154
{
David@0
  1155
	[chat receivedError:type];
David@0
  1156
}
David@0
  1157
David@0
  1158
- (void)receivedIMChatMessage:(NSDictionary *)messageDict inChat:(AIChat *)chat
David@0
  1159
{
sholt@3080
  1160
	PurpleMessageFlags		flags = [(NSNumber*)[messageDict objectForKey:@"PurpleMessageFlags"] intValue];
zacw@1353
  1161
zacw@1353
  1162
	NSAttributedString		*attributedMessage;
zacw@1353
  1163
	AIListContact			*listContact;
David@0
  1164
	
zacw@1353
  1165
	listContact = chat.listObject;
zacw@1353
  1166
zacw@1353
  1167
	attributedMessage = [adium.contentController decodedIncomingMessage:[messageDict objectForKey:@"Message"]
zacw@1353
  1168
															  fromContact:listContact
zacw@1353
  1169
																onAccount:self];
zacw@1353
  1170
	
zacw@1353
  1171
	//Clear the typing flag of the chat since a message was just received
zacw@1353
  1172
	[self setTypingFlagOfChat:chat to:nil];
zacw@1353
  1173
	
zacw@1353
  1174
	[self _receivedMessage:attributedMessage
zacw@1353
  1175
					inChat:chat 
zacw@1353
  1176
		   fromListContact:listContact
zacw@1353
  1177
					 flags:flags
zacw@1353
  1178
					  date:[messageDict objectForKey:@"Date"]];
zacw@1353
  1179
}
zacw@1353
  1180
zacw@1353
  1181
- (void)receivedEventForChat:(AIChat *)chat
zacw@1353
  1182
					 message:(NSString *)message
zacw@1353
  1183
						date:(NSDate *)date
zacw@2848
  1184
					   flags:(NSNumber *)flagsNumber
zacw@1353
  1185
{
sholt@3078
  1186
	PurpleMessageFlags flags = [flagsNumber intValue];
zacw@2848
  1187
	
zacw@1353
  1188
	AIContentEvent *event = [AIContentEvent eventInChat:chat
zacw@1353
  1189
											 withSource:nil
zacw@1353
  1190
											destination:self
zacw@1353
  1191
												   date:date
zacw@1353
  1192
												message:[AIHTMLDecoder decodeHTML:message]
zacw@1353
  1193
											   withType:@"purple"];
zacw@1353
  1194
	
zacw@1353
  1195
	event.filterContent = (flags & PURPLE_MESSAGE_NO_LINKIFY) != PURPLE_MESSAGE_NO_LINKIFY;
zacw@1353
  1196
	
zacw@1353
  1197
	[adium.contentController receiveContentObject:event];
David@0
  1198
}
David@0
  1199
David@0
  1200
- (void)receivedMultiChatMessage:(NSDictionary *)messageDict inChat:(AIChat *)chat
sholt@3556
  1201
{
sholt@3556
  1202
  PurpleMessageFlags	flags = [(NSNumber*)[messageDict objectForKey:@"PurpleMessageFlags"] intValue];
sholt@3556
  1203
  
sholt@3556
  1204
  if ((![self shouldDisplayOutgoingMUCMessages] && ((flags & PURPLE_MESSAGE_SEND) || (flags & PURPLE_MESSAGE_DELAYED))) ||
sholt@3556
  1205
	  (!(flags & PURPLE_MESSAGE_SEND) || (flags & PURPLE_MESSAGE_DELAYED))) {
sholt@3556
  1206
	
David@0
  1207
	NSAttributedString	*attributedMessage = [messageDict objectForKey:@"AttributedMessage"];;
David@0
  1208
	NSString			*source = [messageDict objectForKey:@"Source"];
zacw@1353
  1209
	
zacw@1353
  1210
	[self _receivedMessage:attributedMessage
zacw@1353
  1211
					inChat:chat 
zacw@1353
  1212
		   fromListContact:[self contactWithUID:source]
zacw@1353
  1213
					 flags:flags
zacw@1353
  1214
					  date:[messageDict objectForKey:@"Date"]];
sholt@3556
  1215
  }
David@0
  1216
}
David@0
  1217
David@0
  1218
- (void)_receivedMessage:(NSAttributedString *)attributedMessage inChat:(AIChat *)chat fromListContact:(AIListContact *)sourceContact flags:(PurpleMessageFlags)flags date:(NSDate *)date
David@0
  1219
{
zacw@3343
  1220
	AILogWithSignature(@"Message: %@ inChat: %@ fromListContact: %@ flags: %d date: %@", attributedMessage, chat, sourceContact, flags, date);
zacw@3343
  1221
	
zacw@1346
  1222
	if ((flags & PURPLE_MESSAGE_DELAYED) == PURPLE_MESSAGE_DELAYED) {
zacw@1346
  1223
		// Display delayed messages as context.
zacw@1346
  1224
zacw@1346
  1225
		AIContentContext *messageObject = [AIContentContext messageInChat:chat
sholt@3565
  1226
															   withSource:[sourceContact.UID isEqualToString:self.UID]? (AIListObject *)self : (AIListObject *)sourceContact
zacw@1346
  1227
															  destination:self
zacw@1346
  1228
																	 date:date
zacw@1346
  1229
																  message:attributedMessage
zacw@1346
  1230
																autoreply:(flags & PURPLE_MESSAGE_AUTO_RESP) != 0];
zacw@1346
  1231
		
zacw@1346
  1232
		messageObject.trackContent = NO;
zacw@1346
  1233
		
zacw@1346
  1234
		[adium.contentController receiveContentObject:messageObject];
zacw@1346
  1235
		
zacw@1346
  1236
	} else {
zacw@1346
  1237
		AIContentMessage *messageObject = [AIContentMessage messageInChat:chat
sholt@3565
  1238
															   withSource:[sourceContact.UID isEqualToString:self.UID]? (AIListObject *)self : (AIListObject *)sourceContact
zacw@1346
  1239
															  destination:self
zacw@1346
  1240
																	 date:date
zacw@1346
  1241
																  message:attributedMessage
zacw@1346
  1242
																autoreply:(flags & PURPLE_MESSAGE_AUTO_RESP) != 0];
zacw@1346
  1243
		[adium.contentController receiveContentObject:messageObject];	
zacw@1346
  1244
	}
David@0
  1245
}
David@0
  1246
David@0
  1247
/*********************/
David@0
  1248
/* AIAccount_Content */
David@0
  1249
/*********************/
David@0
  1250
#pragma mark Content
David@0
  1251
- (void)sendTypingObject:(AIContentTyping *)inContentTyping
David@0
  1252
{
David@813
  1253
	AIChat *chat = inContentTyping.chat;
David@0
  1254
David@428
  1255
	if (!chat.isGroupChat) {
David@813
  1256
		[purpleAdapter sendTyping:inContentTyping.typingState inChat:chat];
David@0
  1257
	}
David@0
  1258
}
David@0
  1259
zacw@1716
  1260
- (BOOL)sendMessageObject:(AIContentMessage *)inContentMessage
zacw@1716
  1261
{
zacw@1716
  1262
	PurpleMessageFlags		flags = PURPLE_MESSAGE_RAW;
zacw@1716
  1263
	
zacw@1716
  1264
	if ([inContentMessage isAutoreply]) {
zacw@1716
  1265
		flags |= PURPLE_MESSAGE_AUTO_RESP;
zacw@1716
  1266
	}
sholt@3556
  1267
  
sholt@3561
  1268
	if (![self shouldDisplayOutgoingMUCMessages] && [inContentMessage.chat isGroupChat]) {
sholt@3556
  1269
		inContentMessage.displayContent = NO;
sholt@3556
  1270
	}
zacw@1716
  1271
zacw@1716
  1272
	[purpleAdapter sendEncodedMessage:[inContentMessage encodedMessage]
zacw@1716
  1273
						 fromAccount:self
zacw@1716
  1274
							  inChat:inContentMessage.chat
zacw@1716
  1275
						   withFlags:flags];
zacw@1716
  1276
zacw@1716
  1277
	return YES;
zacw@1716
  1278
}
zacw@1716
  1279
David@0
  1280
- (BOOL)supportsSendingNotifications
David@0
  1281
{
David@0
  1282
	return (account ? ((PURPLE_PLUGIN_PROTOCOL_INFO(purple_find_prpl(purple_account_get_protocol_id(account)))->send_attention) != NULL) : NO);
David@0
  1283
}
David@0
  1284
zacw@1716
  1285
- (BOOL)sendNotificationObject:(AIContentNotification *)inContentNotification
David@0
  1286
{
zacw@1716
  1287
	[purpleAdapter sendNotificationOfType:[inContentNotification notificationType]
zacw@1716
  1288
							  fromAccount:self
zacw@1716
  1289
								   inChat:inContentNotification.chat];	
David@0
  1290
	
David@0
  1291
	return YES;
David@0
  1292
}
David@0
  1293
David@0
  1294
/*!
David@0
  1295
 * @brief Return the string encoded for sending to a remote contact
David@0
  1296
 *
David@0
  1297
 * We return nil if the string turns out to have been a / command.
David@0
  1298
 */
David@0
  1299
- (NSString *)encodedAttributedStringForSendingContentMessage:(AIContentMessage *)inContentMessage
David@0
  1300
{
David@813
  1301
	BOOL		didCommand = [purpleAdapter attemptPurpleCommandOnMessage:[inContentMessage.message string]
David@0
  1302
														 fromAccount:(AIAccount *)[inContentMessage source]
David@813
  1303
															  inChat:inContentMessage.chat];	
David@0
  1304
	
David@0
  1305
	return (didCommand ? nil : [super encodedAttributedStringForSendingContentMessage:inContentMessage]);
David@0
  1306
}
David@0
  1307
David@0
  1308
/*!
David@0
  1309
 * @brief Libpurple prints file transfer messages to the chat window. The Adium core therefore shouldn't.
David@0
  1310
 */
David@0
  1311
- (BOOL)accountDisplaysFileTransferMessages
David@0
  1312
{
David@0
  1313
	return YES;
David@0
  1314
}
David@0
  1315
David@0
  1316
/*!
David@0
  1317
 * @brief Available for sending content
David@0
  1318
 *
David@0
  1319
 * Returns YES if the contact is available for receiving content of the specified type.  If contact is nil, instead
David@0
  1320
 * check for the availiability to send any content of the given type.
David@0
  1321
 *
David@0
  1322
 * We override the default implementation to check -[self allowFileTransferWithListObject:] for file transfers
David@0
  1323
 *
David@0
  1324
 * @param inType A string content type
David@0
  1325
 * @param inContact The destination contact, or nil to check global availability
David@0
  1326
 */
David@0
  1327
- (BOOL)availableForSendingContentType:(NSString *)inType toContact:(AIListContact *)inContact
David@0
  1328
{
David@837
  1329
    if (self.online && [inType isEqualToString:CONTENT_FILE_TRANSFER_TYPE]) {
David@0
  1330
		if (inContact) {
David@0
  1331
			return ([self conformsToProtocol:@protocol(AIAccount_Files)] &&
David@837
  1332
					((inContact.online || inContact.isStranger) && [self allowFileTransferWithListObject:inContact]));
David@0
  1333
		} else {
David@0
  1334
			return [self conformsToProtocol:@protocol(AIAccount_Files)];
David@0
  1335
		}
David@0
  1336
	}
David@0
  1337
David@0
  1338
    return [super availableForSendingContentType:inType toContact:inContact];
David@0
  1339
}
David@0
  1340
David@0
  1341
- (BOOL)allowFileTransferWithListObject:(AIListObject *)inListObject
David@0
  1342
{
zacw@2329
  1343
	PurplePluginProtocolInfo *prpl_info = self.protocolInfo;
zacw@2329
  1344
David@0
  1345
	if (prpl_info && prpl_info->send_file)
David@721
  1346
		return (!prpl_info->can_receive_file || prpl_info->can_receive_file(purple_account_get_connection(account), [inListObject.UID UTF8String]));
David@0
  1347
	else
David@0
  1348
		return NO;
David@0
  1349
}
David@0
  1350
David@0
  1351
- (BOOL)supportsAutoReplies
David@0
  1352
{
David@0
  1353
	if (account && purple_account_get_connection(account)) {
David@0
  1354
		return ((purple_account_get_connection(account)->flags & PURPLE_CONNECTION_AUTO_RESP) != 0);
David@0
  1355
	}
David@0
  1356
	
David@0
  1357
	return NO;
David@0
  1358
}
David@0
  1359
David@0
  1360
- (BOOL)canSendOfflineMessageToContact:(AIListContact *)inContact
David@0
  1361
{
zacw@2329
  1362
	PurplePluginProtocolInfo *prpl_info = self.protocolInfo;
zacw@2329
  1363
David@0
  1364
	if (prpl_info && prpl_info->offline_message) {
David@0
  1365
		
David@721
  1366
		return (prpl_info->offline_message(purple_find_buddy(account, [inContact.UID UTF8String])));
David@0
  1367
David@0
  1368
	} else
David@0
  1369
		return NO;
David@0
  1370
	
David@0
  1371
}
David@0
  1372
David@0
  1373
#pragma mark Custom emoticons
David@0
  1374
- (void)chat:(AIChat *)inChat isWaitingOnCustomEmoticon:(NSString *)emoticonEquivalent
David@0
  1375
{
David@0
  1376
	AIEmoticon *emoticon;
David@0
  1377
David@0
  1378
	//Look for an existing emoticon with this equivalent
catfish@1821
  1379
	for (emoticon in inChat.customEmoticons) {
David@0
  1380
		if ([[emoticon textEquivalents] containsObject:emoticonEquivalent]) break;
David@0
  1381
	}
David@0
  1382
	
David@0
  1383
	if (!emoticon) {
David@0
  1384
		emoticon = [AIEmoticon emoticonWithIconPath:nil
David@0
  1385
										equivalents:[NSArray arrayWithObject:emoticonEquivalent]
David@0
  1386
											   name:emoticonEquivalent
David@0
  1387
											   pack:nil];
David@0
  1388
		[inChat addCustomEmoticon:emoticon];			
David@0
  1389
	}
David@0
  1390
	
David@0
  1391
	if (![emoticon path]) {
David@0
  1392
		[emoticon setPath:[[NSBundle bundleForClass:[CBPurpleAccount class]] pathForResource:@"missing_image"
David@0
  1393
																					ofType:@"png"]];
David@0
  1394
	}
David@0
  1395
}
David@0
  1396
David@0
  1397
/*!
David@0
  1398
 * @brief Return the path at which to save an emoticon
David@0
  1399
 */
David@0
  1400
- (NSString *)_emoticonCachePathForEmoticon:(NSString *)emoticonEquivalent type:(AIBitmapImageFileType)fileType inChat:(AIChat *)inChat
David@0
  1401
{
David@0
  1402
	static unsigned long long emoticonID = 0;
David@0
  1403
    NSString    *filename = [NSString stringWithFormat:@"TEMP-CustomEmoticon_%@_%@_%qu.%@",
David@0
  1404
		[inChat uniqueChatID], emoticonEquivalent, emoticonID++, [NSImage extensionForBitmapImageFileType:fileType]];
David@0
  1405
    return [[adium cachesPath] stringByAppendingPathComponent:[filename safeFilenameString]];	
David@0
  1406
}
David@0
  1407
David@0
  1408
David@0
  1409
- (void)chat:(AIChat *)inChat setCustomEmoticon:(NSString *)emoticonEquivalent withImageData:(NSData *)inImageData
David@0
  1410
{
David@0
  1411
	/* XXX Note: If we can set outgoing emoticons, this method needs to be updated to mark emoticons as incoming
David@0
  1412
	 * and AIEmoticonController needs to be able to handle that.
David@0
  1413
	 */
David@0
  1414
	AIEmoticon	*emoticon;
David@0
  1415
David@0
  1416
	//Look for an existing emoticon with this equivalent
catfish@1821
  1417
	for (emoticon in inChat.customEmoticons) {
David@0
  1418
		if ([[emoticon textEquivalents] containsObject:emoticonEquivalent]) break;
David@0
  1419
	}
David@0
  1420
	
David@0
  1421
	//Write out our image
David@0
  1422
	NSString	*path = [self _emoticonCachePathForEmoticon:emoticonEquivalent
David@0
  1423
													   type:[NSImage fileTypeOfData:inImageData]
David@0
  1424
													 inChat:inChat];
David@0
  1425
	[inImageData writeToFile:path
David@0
  1426
				  atomically:NO];
David@0
  1427
David@0
  1428
	if (emoticon) {
David@0
  1429
		//If we already have an emoticon, just update its path
David@0
  1430
		[emoticon setPath:path];
David@0
  1431
David@0
  1432
	} else {
David@0
  1433
		emoticon = [AIEmoticon emoticonWithIconPath:path
David@0
  1434
										equivalents:[NSArray arrayWithObject:emoticonEquivalent]
David@0
  1435
											   name:emoticonEquivalent
David@0
  1436
											   pack:nil];
David@0
  1437
		[inChat addCustomEmoticon:emoticon];
David@0
  1438
	}
David@0
  1439
}
David@0
  1440
David@0
  1441
- (void)chat:(AIChat *)inChat closedCustomEmoticon:(NSString *)emoticonEquivalent
David@0
  1442
{
David@0
  1443
	AIEmoticon	*emoticon;
David@0
  1444
David@0
  1445
	//Look for an existing emoticon with this equivalent
catfish@1821
  1446
	for (emoticon in inChat.customEmoticons) {
David@0
  1447
		if ([[emoticon textEquivalents] containsObject:emoticonEquivalent]) break;
David@0
  1448
	}
David@0
  1449
	
David@0
  1450
	if (emoticon) {
David@1109
  1451
		[[NSNotificationCenter defaultCenter] postNotificationName:@"AICustomEmoticonUpdated"
David@0
  1452
												  object:inChat
David@0
  1453
												userInfo:[NSDictionary dictionaryWithObject:emoticon
David@0
  1454
																					 forKey:@"AIEmoticon"]];
David@0
  1455
	} else {
David@0
  1456
		//This shouldn't happen; chat:setCustomEmoticon:withImageData: should have already been called.
David@0
  1457
		emoticon = [AIEmoticon emoticonWithIconPath:nil
David@0
  1458
										equivalents:[NSArray arrayWithObject:emoticonEquivalent]
David@0
  1459
											   name:emoticonEquivalent
David@0
  1460
											   pack:nil];
David@0
  1461
		NSLog(@"Warning: closed custom emoticon %@ without adding it to the chat", emoticon);
David@0
  1462
		AILog(@"Warning: closed custom emoticon %@ without adding it to the chat", emoticon);
David@0
  1463
	}
David@0
  1464
}
David@0
  1465
David@0
  1466
/*********************/
David@0
  1467
/* AIAccount_Privacy */
David@0
  1468
/*********************/
David@0
  1469
#pragma mark Privacy
David@0
  1470
- (BOOL)addListObject:(AIListObject *)inObject toPrivacyList:(AIPrivacyType)type
David@0
  1471
{
David@0
  1472
    if (type == AIPrivacyTypePermit)
David@715
  1473
        return (purple_privacy_permit_add(account,[inObject.UID UTF8String],FALSE));
David@0
  1474
    else
David@715
  1475
        return (purple_privacy_deny_add(account,[inObject.UID UTF8String],FALSE));
David@0
  1476
}
David@0
  1477
David@0
  1478
- (BOOL)removeListObject:(AIListObject *)inObject fromPrivacyList:(AIPrivacyType)type
David@0
  1479
{
David@0
  1480
    if (type == AIPrivacyTypePermit)
David@715
  1481
        return (purple_privacy_permit_remove(account,[inObject.UID UTF8String],FALSE));
David@0
  1482
    else
David@715
  1483
        return (purple_privacy_deny_remove(account,[inObject.UID UTF8String],FALSE));
David@0
  1484
}
David@0
  1485
David@0
  1486
- (NSArray *)listObjectsOnPrivacyList:(AIPrivacyType)type
David@0
  1487
{
David@0
  1488
	NSMutableArray	*array = [NSMutableArray array];
David@0
  1489
	if (account) {
David@0
  1490
		GSList			*list;
David@0
  1491
		GSList			*sourceList = ((type == AIPrivacyTypePermit) ? account->permit : account->deny);
David@0
  1492
		
David@0
  1493
		for (list = sourceList; (list != NULL); list=list->next) {
David@0
  1494
			[array addObject:[self contactWithUID:[NSString stringWithUTF8String:(char *)list->data]]];
David@0
  1495
		}
David@0
  1496
	}
David@0
  1497
David@0
  1498
	return array;
David@0
  1499
}
David@0
  1500
David@0
  1501
- (void)accountPrivacyList:(AIPrivacyType)type added:(NSString *)sourceUID
David@0
  1502
{
David@0
  1503
	//Can't really trust sourceUID to not be @"" or something silly like that
David@0
  1504
	if ([sourceUID length]) {
David@0
  1505
		//Get our contact
David@0
  1506
		AIListContact   *contact = [self contactWithUID:sourceUID];
David@0
  1507
David@0
  1508
		//Update Adium's knowledge of it
David@0
  1509
		[contact setIsBlocked:((type == AIPrivacyTypeDeny) ? YES : NO) updateList:NO];
David@0
  1510
	}
David@0
  1511
}
David@0
  1512
David@0
  1513
- (void)privacyPermitListAdded:(NSString *)sourceUID
David@0
  1514
{
David@0
  1515
	[self accountPrivacyList:AIPrivacyTypePermit added:sourceUID];
David@0
  1516
}
David@0
  1517
David@0
  1518
- (void)privacyDenyListAdded:(NSString *)sourceUID
David@0
  1519
{
David@0
  1520
	[self accountPrivacyList:AIPrivacyTypeDeny added:sourceUID];
David@0
  1521
}
David@0
  1522
David@0
  1523
- (void)accountPrivacyList:(AIPrivacyType)type removed:(NSString *)sourceUID
David@0
  1524
{
David@0
  1525
	//Can't really trust sourceUID to not be @"" or something silly like that
David@0
  1526
	if ([sourceUID length]) {
David@0
  1527
		if (!namesAreCaseSensitive) {
David@0
  1528
			sourceUID = [sourceUID compactedString];
David@0
  1529
		}
David@0
  1530
David@0
  1531
		//Get our contact, which must already exist for us to care about its removal
David@89
  1532
		AIListContact   *contact = [adium.contactController existingContactWithService:service
David@0
  1533
																				 account:self
David@0
  1534
																					 UID:sourceUID];
David@0
  1535
		
David@0
  1536
		if (contact) {			
David@0
  1537
			//Update Adium's knowledge of it
David@0
  1538
			[contact setIsBlocked:((type == AIPrivacyTypeDeny) ? NO : YES) updateList:NO];
David@0
  1539
		}
David@0
  1540
	}
David@0
  1541
}
David@0
  1542
David@0
  1543
- (void)privacyPermitListRemoved:(NSString *)sourceUID
David@0
  1544
{
David@0
  1545
	[self accountPrivacyList:AIPrivacyTypePermit removed:sourceUID];
David@0
  1546
}
David@0
  1547
David@0
  1548
- (void)privacyDenyListRemoved:(NSString *)sourceUID
David@0
  1549
{
David@0
  1550
	[self accountPrivacyList:AIPrivacyTypeDeny removed:sourceUID];
David@0
  1551
}
David@0
  1552
David@0
  1553
- (void)setPrivacyOptions:(AIPrivacyOption)option
David@0
  1554
{
David@0
  1555
	if (account && purple_account_get_connection(account)) {
David@0
  1556
		PurplePrivacyType privacyType;
David@0
  1557
David@0
  1558
		switch (option) {
David@0
  1559
			case AIPrivacyOptionAllowAll:
David@0
  1560
			default:
David@0
  1561
				privacyType = PURPLE_PRIVACY_ALLOW_ALL;
David@0
  1562
				break;
David@0
  1563
			case AIPrivacyOptionDenyAll:
David@0
  1564
				privacyType = PURPLE_PRIVACY_DENY_ALL;
David@0
  1565
				break;
David@0
  1566
			case AIPrivacyOptionAllowUsers:
David@0
  1567
				privacyType = PURPLE_PRIVACY_ALLOW_USERS;
David@0
  1568
				break;
David@0
  1569
			case AIPrivacyOptionDenyUsers:
David@0
  1570
				privacyType = PURPLE_PRIVACY_DENY_USERS;
David@0
  1571
				break;
David@0
  1572
			case AIPrivacyOptionAllowContactList:
David@0
  1573
				privacyType = PURPLE_PRIVACY_ALLOW_BUDDYLIST;
David@0
  1574
				break;
David@0
  1575
			
David@0
  1576
		}
David@0
  1577
		
David@0
  1578
		if (account->perm_deny != privacyType) {
David@0
  1579
			account->perm_deny = privacyType;
David@0
  1580
			serv_set_permit_deny(purple_account_get_connection(account));
David@0
  1581
			AILog(@"Set privacy options for %@ (%x %x) to %i",
David@0
  1582
				  self,account,purple_account_get_connection(account),account->perm_deny);
David@0
  1583
sholt@2693
  1584
			[self setPreference:[NSNumber numberWithInteger:option]
David@0
  1585
						 forKey:KEY_PRIVACY_OPTION
David@0
  1586
						  group:GROUP_ACCOUNT_STATUS];			
David@0
  1587
		}
David@0
  1588
	} else {
David@0
  1589
		AILog(@"Couldn't set privacy options for %@ (%x %x)",self,account,purple_account_get_connection(account));
David@0
  1590
	}
David@0
  1591
}
David@0
  1592
David@0
  1593
- (AIPrivacyOption)privacyOptions
David@0
  1594
{
David@0
  1595
	AIPrivacyOption privacyOption = -1;
David@0
  1596
	
David@0
  1597
	if (account) {
David@0
  1598
		PurplePrivacyType privacyType = account->perm_deny;
David@0
  1599
		
David@0
  1600
		switch (privacyType) {
David@0
  1601
			case PURPLE_PRIVACY_ALLOW_ALL:
David@0
  1602
			default:
David@0
  1603
				privacyOption = AIPrivacyOptionAllowAll;
David@0
  1604
				break;
David@0
  1605
			case PURPLE_PRIVACY_DENY_ALL:
David@0
  1606
				privacyOption = AIPrivacyOptionDenyAll;
David@0
  1607
				break;
David@0
  1608
			case PURPLE_PRIVACY_ALLOW_USERS:
David@0
  1609
				privacyOption = AIPrivacyOptionAllowUsers;
David@0
  1610
				break;
David@0
  1611
			case PURPLE_PRIVACY_DENY_USERS:
David@0
  1612
				privacyOption = AIPrivacyOptionDenyUsers;
David@0
  1613
				break;
David@0
  1614
			case PURPLE_PRIVACY_ALLOW_BUDDYLIST:
David@0
  1615
				privacyOption = AIPrivacyOptionAllowContactList;
David@0
  1616
				break;
David@0
  1617
		}
David@0
  1618
	}
David@0
  1619
	AILog(@"%@: privacyOptions are %i",self,privacyOption);
David@0
  1620
	return privacyOption;
David@0
  1621
}
David@0
  1622
David@0
  1623
/*****************************************************/
David@0
  1624
/* File transfer / AIAccount_Files inherited methods */
David@0
  1625
/*****************************************************/
David@0
  1626
#pragma mark File Transfer
David@0
  1627
- (BOOL)canSendFolders
David@0
  1628
{
David@0
  1629
	return NO;
David@0
  1630
}
David@0
  1631
David@0
  1632
//Create a protocol-specific xfer object, set it up as requested, and begin sending
David@0
  1633
- (void)_beginSendOfFileTransfer:(ESFileTransfer *)fileTransfer
David@0
  1634
{
David@0
  1635
	PurpleXfer *xfer = [self newOutgoingXferForFileTransfer:fileTransfer];
David@0
  1636
	
David@0
  1637
	if (xfer) {
David@0
  1638
		//Associate the fileTransfer and the xfer with each other
David@0
  1639
		[fileTransfer setAccountData:[NSValue valueWithPointer:xfer]];
David@0
  1640
		xfer->ui_data = [fileTransfer retain];
David@0
  1641
		
David@0
  1642
		//Set the filename
David@0
  1643
		purple_xfer_set_local_filename(xfer, [[fileTransfer localFilename] UTF8String]);
David@0
  1644
		purple_xfer_set_filename(xfer, [[[fileTransfer localFilename] lastPathComponent] UTF8String]);
David@0
  1645
		
David@0
  1646
		/*
David@0
  1647
		 Request that the transfer begins.
David@0
  1648
		 We will be asked to accept it via:
David@0
  1649
			- (void)acceptFileTransferRequest:(ESFileTransfer *)fileTransfer
David@0
  1650
		 below.
David@0
  1651
		 */
David@0
  1652
		[purpleAdapter xferRequest:xfer];
David@0
  1653
		[fileTransfer setStatus: Waiting_on_Remote_User_FileTransfer];
David@0
  1654
	}
David@0
  1655
}
David@0
  1656
//By default, protocols can not create PurpleXfer objects
David@0
  1657
- (PurpleXfer *)newOutgoingXferForFileTransfer:(ESFileTransfer *)fileTransfer
David@0
  1658
{
David@0
  1659
	PurpleXfer				*newPurpleXfer = NULL;
David@0
  1660
David@0
  1661
	if (account && purple_account_get_connection(account)) {
zacw@2329
  1662
		PurplePluginProtocolInfo  *prpl_info = self.protocolInfo;
David@0
  1663
David@0
  1664
		if (prpl_info && prpl_info->new_xfer) {
David@0
  1665
			char *destsn = (char *)[[[fileTransfer contact] UID] UTF8String];
David@0
  1666
			newPurpleXfer = (prpl_info->new_xfer)(purple_account_get_connection(account), destsn);
David@0
  1667
		}
David@0
  1668
	}
David@0
  1669
David@0
  1670
	return newPurpleXfer;
David@0
  1671
}
David@0
  1672
David@0
  1673
/* 
David@0
  1674
 * @brief The account requested that we received a file.
David@0
  1675
 *
David@0
  1676
 * Set up the ESFileTransfer and query the fileTransferController for a save location.
David@0
  1677
 * 
David@0
  1678
 */
David@0
  1679
- (void)requestReceiveOfFileTransfer:(ESFileTransfer *)fileTransfer
David@0
  1680
{
David@0
  1681
	AILog(@"File transfer request received: %@",fileTransfer);
David@100
  1682
	[adium.fileTransferController receiveRequestForFileTransfer:fileTransfer];
David@0
  1683
}
David@0
  1684
David@0
  1685
//Create an ESFileTransfer object from an xfer
David@0
  1686
- (ESFileTransfer *)newFileTransferObjectWith:(NSString *)destinationUID
David@0
  1687
										 size:(unsigned long long)inSize
David@0
  1688
							   remoteFilename:(NSString *)remoteFilename
David@0
  1689
{
David@0
  1690
	AIListContact   *contact = [self contactWithUID:destinationUID];
David@0
  1691
    ESFileTransfer	*fileTransfer;
David@0
  1692
	
David@100
  1693
	fileTransfer = [adium.fileTransferController newFileTransferWithContact:contact
David@0
  1694
																   forAccount:self
David@0
  1695
																		 type:Unknown_FileTransfer]; 
David@0
  1696
	[fileTransfer setSize:inSize];
David@0
  1697
	[fileTransfer setRemoteFilename:remoteFilename];
David@0
  1698
	
David@0
  1699
    return fileTransfer;
David@0
  1700
}
David@0
  1701
David@0
  1702
//Update an ESFileTransfer object progress
David@0
  1703
- (void)updateProgressForFileTransfer:(ESFileTransfer *)fileTransfer percent:(NSNumber *)percent bytesSent:(NSNumber *)bytesSent
David@0
  1704
{
sholt@3078
  1705
	CGFloat percentDone = (CGFloat)[percent doubleValue];
David@0
  1706
    [fileTransfer setPercentDone:percentDone bytesSent:[bytesSent unsignedLongValue]];
David@0
  1707
}
David@0
  1708
David@0
  1709
//The local side cancelled the transfer.  We probably already have this status set, but set it just in case.
David@0
  1710
- (void)fileTransferCancelledLocally:(ESFileTransfer *)fileTransfer
David@0
  1711
{
David@0
  1712
	if (![fileTransfer isStopped]) {
David@0
  1713
		[fileTransfer setStatus:Cancelled_Local_FileTransfer];
David@0
  1714
	}
David@0
  1715
}
David@0
  1716
David@0
  1717
//The remote side cancelled the transfer, the fool. Update our status.
David@0
  1718
- (void)fileTransferCancelledRemotely:(ESFileTransfer *)fileTransfer
David@0
  1719
{
David@0
  1720
	if (![fileTransfer isStopped]) {
David@0
  1721
		[fileTransfer setStatus:Cancelled_Remote_FileTransfer];
David@0
  1722
	}
David@0
  1723
}
David@0
  1724
David@0
  1725
- (void)destroyFileTransfer:(ESFileTransfer *)fileTransfer
David@0
  1726
{
David@0
  1727
	AILog(@"Destroy file transfer %@",fileTransfer);
David@0
  1728
	[fileTransfer release];
David@0
  1729
}
David@0
  1730
David@0
  1731
//Accept a send or receive ESFileTransfer object, beginning the transfer.
David@0
  1732
//Subsequently inform the fileTransferController that the fun has begun.
David@0
  1733
- (void)acceptFileTransferRequest:(ESFileTransfer *)fileTransfer
David@0
  1734
{
David@0
  1735
    AILog(@"Accepted file transfer %@",fileTransfer);
David@0
  1736
	
David@0
  1737
	PurpleXfer		*xfer;
David@0
  1738
	PurpleXferType	xferType;
David@0
  1739
	
David@0
  1740
	xfer = [[fileTransfer accountData] pointerValue];
David@0
  1741
David@0
  1742
    xferType = purple_xfer_get_type(xfer);
David@0
  1743
    if (xferType