Frameworks/Adium Framework/Source/AIAccount.m
author Zachary West <zacw@adium.im>
Fri Nov 27 15:50:57 2009 -0500 (2009-11-27)
changeset 2835 1e8c89f99dfe
parent 2356 ca99ae8114c2
child 3051 961833271e26
permissions -rw-r--r--
Simplify the "status message" contact/account property into one getter method. Correct error -1728 from AS on contact's status messages. Fixes #13460.

The totally undocumented -1728 error appears to be caused by runtime type disagreeing with event type. "status message" is apparently assumed to only have 1 code, so specifying one for contacts and one for accounts was confusing it when it was going to fetch it.
     1 /* 
     2  * Adium is the legal property of its developers, whose names are listed in the copyright file included
     3  * with this source distribution.
     4  * 
     5  * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
     6  * General Public License as published by the Free Software Foundation; either version 2 of the License,
     7  * or (at your option) any later version.
     8  * 
     9  * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
    10  * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
    11  * Public License for more details.
    12  * 
    13  * You should have received a copy of the GNU General Public License along with this program; if not,
    14  * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
    15  */
    16 
    17 #import <Adium/AIAbstractAccount.h>
    18 #import <Adium/AIAccount.h>
    19 #import <Adium/AIListContact.h>
    20 #import <Adium/AIListGroup.h>
    21 #import <Adium/AIContentMessage.h>
    22 #import <Adium/AIContentNotification.h>
    23 #import <Adium/AIService.h>
    24 #import <Adium/AIChat.h>
    25 #import <Adium/ESFileTransfer.h>
    26 #import "AIStatusItem.h"
    27 #import "AIStatus.h"
    28 #import "AdiumAccounts.h"
    29 
    30 #import <Adium/AIContactControllerProtocol.h>
    31 #import <Adium/AIContentControllerProtocol.h>
    32 #import <Adium/AIAccountControllerProtocol.h>
    33 
    34 #import "DCJoinChatViewController.h"
    35 #import "AIChatControllerProtocol.h"
    36 #import "AIMessageWindowController.h"
    37 #import "AIMessageWindow.h"
    38 #import "AIInterfaceControllerProtocol.h"
    39 #import "AIStatusControllerProtocol.h"
    40 
    41 #define NEW_ACCOUNT_DISPLAY_TEXT			AILocalizedString(@"<New Account>", "Placeholder displayed as the name of a new account")
    42 
    43 @interface AIAccountDeletionDialog : NSObject <AIAccountControllerRemoveConfirmationDialog> {
    44 	AIAccount *account;
    45 	NSAlert *alert;
    46 	id userData;
    47 }
    48 
    49 - (id)initWithAccount:(AIAccount*)ac alert:(NSAlert*)al;
    50 
    51 @property (readwrite, retain, nonatomic) id userData;
    52 
    53 - (void)alertDidEnd:(NSAlert *)alert returnCode:(int)returnCode contextInfo:(void *)contextInfo;
    54 
    55 @end
    56 
    57 @implementation AIAccountDeletionDialog
    58 
    59 - (id)initWithAccount:(AIAccount*)ac alert:(NSAlert*)al {
    60 	if((self = [super init])) {
    61 		account = ac;
    62 		alert = [al retain];
    63 	}
    64 	return self;
    65 }
    66 
    67 - (void)dealloc {
    68 	[alert release];
    69 	[userData release];
    70 	[super dealloc];
    71 }
    72 
    73 @synthesize userData;
    74 
    75 - (void)runModal {
    76 	[self alertDidEnd:alert returnCode:[alert runModal] contextInfo:NULL];
    77 }
    78 
    79 - (void)beginSheetModalForWindow:(NSWindow*)window {
    80 	[alert beginSheetModalForWindow:window modalDelegate:self didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:NULL];
    81 }
    82 
    83 - (void)alertDidEnd:(NSAlert *)alert returnCode:(int)returnCode contextInfo:(void *)contextInfo {
    84 	[account alertForAccountDeletion:self didReturn:returnCode];
    85 }
    86 
    87 @end
    88 
    89 //Proxy types for applescript
    90 typedef enum
    91 {
    92 	Adium_Proxy_HTTP_AS = 'HTTP',
    93 	Adium_Proxy_SOCKS4_AS = 'SCK4',
    94 	Adium_Proxy_SOCKS5_AS = 'SCK5',
    95 	Adium_Proxy_Default_HTTP_AS = 'DHTP',
    96 	Adium_Proxy_Default_SOCKS4_AS = 'DSK4',
    97 	Adium_Proxy_Default_SOCKS5_AS = 'DSK5',
    98 	Adium_Proxy_None_AS = 'NONE'
    99 } AdiumProxyTypeApplescript;
   100 
   101 @interface AIAccount(AppleScriptPRIVATE)
   102 - (AdiumProxyType)proxyTypeFromApplescript:(AdiumProxyTypeApplescript)proxyTypeAS;
   103 - (AdiumProxyTypeApplescript)applescriptProxyType:(AdiumProxyType)proxyType;
   104 @end
   105 
   106 /*!
   107  * @class AIAccount
   108  * @brief An account
   109  *
   110  * This abstract class represents an account the user has setup in Adium.  Subclass this for every service.
   111  */
   112 @implementation AIAccount
   113 
   114 /*!
   115  * @brief Init Account
   116  *
   117  * Init this account instance
   118  */
   119 - (void)initAccount
   120 {
   121 }
   122 
   123 /*!
   124  * @brief Connect
   125  *
   126  * Connect the account, transitioning it into an online state.
   127  */
   128 - (void)connect
   129 {
   130 	//We are connecting
   131 	[self setValue:[NSNumber numberWithBool:YES] forProperty:@"Connecting" notify:NotifyNow];
   132 }
   133 
   134 /*!
   135  * @brief rejoinChat
   136  * 
   137  * Rejoin the open group chats after disconnect
   138  */
   139 -(BOOL)rejoinChat:(AIChat*)chat
   140 {
   141 	return NO;
   142 }	
   143 
   144 /*!
   145  * @brief Do group chats support topics?
   146  */
   147 - (BOOL)groupChatsSupportTopic
   148 {
   149 	return NO;
   150 }
   151 
   152 /*!
   153  * @brief Set a chat's topic
   154  *
   155  * This only has an effect on group chats.
   156  */
   157 - (void)setTopic:(NSString *)topic forChat:(AIChat *)chat
   158 {
   159 }
   160 
   161 /*!
   162  * @brief Disconnect
   163  *
   164  * Disconnect the account, transitioning it into an offline state.
   165  */
   166 - (void)disconnect
   167 {
   168 	[self cancelAutoReconnect];
   169 	[self setValue:nil forProperty:@"Connecting" notify:NotifyLater];
   170 
   171 	[self notifyOfChangedPropertiesSilently:NO];
   172 }
   173 
   174 /*!
   175  * @brief Disconnect as a result of the network connection dropping out
   176  *
   177  * The default implementation is identical to [self disconect], but subclasses may want to act differently
   178  * if the network connection is gone than if the user chose to disconnect.
   179  *
   180  * Subclasses should call super's implementation.
   181  */
   182 - (void)disconnectFromDroppedNetworkConnection
   183 {
   184 	[self disconnect];
   185 }
   186 
   187 /*!
   188  * @brief Register an account
   189  *
   190  * Register an account on this service using the currently entered information.  This is for services which support
   191  * in-client registration such as jabber.
   192  */
   193 - (void)performRegisterWithPassword:(NSString *)inPassword
   194 {
   195 
   196 }
   197 
   198 /*!
   199  * @brief The UID will be changed. The account has a chance to perform modifications
   200  *
   201  * For example, MSN adds \@hotmail.com to the proposedUID and returns the new value
   202  *
   203  * @param proposedUID The proposed, pre-filtered UID (filtered means it has no characters invalid for this servce)
   204  * @result The UID to use; the default implementation just returns proposedUID.
   205  */
   206 - (NSString *)accountWillSetUID:(NSString *)proposedUID
   207 {
   208 	return proposedUID;
   209 }
   210 
   211 /*!
   212  * @brief The account's UID changed
   213  */
   214 - (void)didChangeUID
   215 {
   216 
   217 }
   218 
   219 /*!
   220  * @brief The account will be deleted
   221  *
   222  * The default implementation disconnects the account.  Subclasses should call super's implementation.
   223  * If asynchronous behavior is required, the next three methods should be overridden instead.
   224  */
   225 - (void)willBeDeleted
   226 {
   227 	[self setShouldBeOnline:NO];
   228 
   229 	//Remove our contacts immediately.
   230 	[self removeAllContacts];
   231 }
   232 
   233 /*!
   234  * @brief Perform the deletion of this account
   235  *
   236  * This should be called only after proper confirmation has been made by the user.
   237  */
   238 - (void)performDelete
   239 {
   240 	[adium.accountController deleteAccount:self];
   241 }
   242 
   243 - (id<AIAccountControllerRemoveConfirmationDialog>)confirmationDialogForAccountDeletion
   244 {
   245 	//Will be released in alertForAccountDeletion:didReturn:
   246 	return [[AIAccountDeletionDialog alloc] initWithAccount:self alert:[self alertForAccountDeletion]];
   247 }
   248 
   249 /*!
   250  * @brief The alert used for confirming the account deletion
   251  *
   252  * Meant for subclassers. By default, returns the dialog that asks the user if the account should really be deleted (and how).
   253  */
   254 - (NSAlert*)alertForAccountDeletion
   255 {
   256 	return [NSAlert alertWithMessageText:AILocalizedString(@"Delete Account",nil)
   257 						   defaultButton:AILocalizedString(@"Delete",nil)
   258 						 alternateButton:AILocalizedString(@"Cancel",nil)
   259 							 otherButton:nil
   260 			   informativeTextWithFormat:AILocalizedString(@"Delete the account %@?",nil), ([self.formattedUID length] ? self.formattedUID : NEW_ACCOUNT_DISPLAY_TEXT)];
   261 }
   262 
   263 /*!
   264  * @brief The dialog asking for confirmation for deleting the account did return.
   265  *
   266  * @param dialog The dialog that has completed
   267  * @param returnCode One of the regular NSAlert return codes
   268  *
   269  * This method should be overridden when alertForAccountDeletion: was overridden, and/or asynchronous behavior is required.
   270  * This implementation disconnects and deletes the account from the accounts list when returnCode == NSAlertDefaultReturn.
   271  *
   272  * If this implementation is not called, dialog should be released by the subclass.
   273  */
   274 - (void)alertForAccountDeletion:(id<AIAccountControllerRemoveConfirmationDialog>)dialog didReturn:(int)returnCode
   275 {
   276 	if(returnCode == NSAlertDefaultReturn) {
   277 		[self performDelete];
   278 	}
   279 
   280 	[(AIAccountDeletionDialog*)dialog release];
   281 }
   282 
   283 /*!
   284  * @brief A formatted UID which may include additional necessary identifying information.
   285  *
   286  * For example, an AIM account (tekjew) and a .Mac account (tekjew@mac.com, entered only as tekjew) may appear identical
   287  * without service information (tekjew). The explicit formatted UID is therefore tekjew@mac.com
   288  */
   289 - (NSString *)explicitFormattedUID
   290 {
   291 	return self.formattedUID;
   292 }
   293 
   294 /*!
   295  * @brief Use our host for the servername when storing password
   296  *
   297  * This should be YES for services which depend upon server information. For example, a password for an IRC account
   298  * is uniqued by what server it is on.
   299  */
   300 - (BOOL)useHostForPasswordServerName
   301 {
   302 	return NO;
   303 }
   304 
   305 /*!
   306  * @brief Use our internal object ID for the username when storing password
   307  *
   308  * For accounts whose signup process may not be contingent upon the UID. For example, a Twitter account using OAuth might
   309  * not know its UID when it wants to save itself.
   310  */
   311 - (BOOL)useInternalObjectIDForPasswordName
   312 {
   313 	return NO;
   314 }
   315 
   316 //Properties -----------------------------------------------------------------------------------------------------------
   317 #pragma mark Properties
   318 /*!
   319  * @brief Send Autoresponses while away
   320  *
   321  * Subclass to alter the behavior of this account with regards to autoresponses.  Certain services expect the client to
   322  * auto-respond with away messages.  Adium will provide this behavior automatically if desired.
   323  */
   324 - (BOOL)supportsAutoReplies
   325 {
   326 	return NO;
   327 }
   328 
   329 /*!
   330  * @brief Disconnect on fast user switch
   331  *
   332  * It may be required for a service to disconnect when logged in users change.  If this is the case, subclass this
   333  * method to return YES and Adium will automatically disconnect and reconnect on FUS events.
   334  */
   335 - (BOOL)disconnectOnFastUserSwitch
   336 {
   337 	return NO;
   338 }
   339 
   340 /*!
   341  * @brief Connectivity based on network reachability
   342  *
   343  * By default, accounts are automatically disconnected and reconnected when network reachability changes.  Accounts
   344  * that do not require persistent network connections can choose to disable this by returning NO from this method.
   345  */
   346 - (BOOL)connectivityBasedOnNetworkReachability
   347 {
   348 	return YES;
   349 }
   350 
   351 /*!
   352  * @brief Suppress typing notifications after send
   353  *
   354  * Some protocols require a 'Stopped typing' notification to be sent along with an instant message.  Other protocols
   355  * implicitly assume that typing has stopped with an incoming message and the extraneous typing notification may cause
   356  * strange behavior.  Return YES from this method to suppress the sending of a stopped typing notification along with
   357  * messages.
   358  */
   359 - (BOOL)suppressTypingNotificationChangesAfterSend
   360 {
   361 	return NO;
   362 }
   363 
   364 /*!
   365  * @brief Support server-side storing of messages to offline users?
   366  *
   367  * Some protocols store messages to offline contacts on the server. Subclasses may return YES if their service supports 
   368  * this. Adium will not store the message as an Event, and will just send it along to the server. This may cause a Gaim
   369  * error on Jabber if the Jabber server they are using is down.
   370  */
   371 - (BOOL)canSendOfflineMessageToContact:(AIListContact *)inContact
   372 {
   373 	return NO;
   374 }
   375 
   376 /*!
   377  * @brief Support messaging invisible contacts?
   378  *
   379  * This will only be called if the protocol returns NO to -[self canSendOfflineMessageToContact:] 
   380  * If invisible contacts exist and can be messaged, return YES.
   381  * If the protocol has no concept of invisible contacts, or invisible contacts can't be messaged, return NO.
   382  */
   383 - (BOOL)maySendMessageToInvisibleContact:(AIListContact *)inContact
   384 {
   385 	return YES;
   386 }
   387 
   388 /*!
   389  * @brief Should offline messages be sent without prompting the user?
   390  *
   391  * If -[self canSendOfflineMessageToContact:] returns YES, Adium typically asks the user whether or not to send a message
   392  * to be stored on the server. If sendOfflineMessagesWithoutPrompting returns YES, this prompt is always suppressed.
   393  *
   394  * This should only be true if offline messaging is a well-established expectation for the service. We assume that
   395  * this is the case by default.
   396  */
   397 - (BOOL)sendOfflineMessagesWithoutPrompting
   398 {
   399 	return YES;
   400 }
   401 
   402 /*!
   403  * @brief Does the account itself display file transfer messages in chat windows?
   404  *
   405  * If YES, Adium won't attempt to display messages in chat windows regarding file transfers.
   406  * If NO, Adium automatically displays appropriate messages in open chats.
   407  */
   408 - (BOOL)accountDisplaysFileTransferMessages
   409 {
   410 	return NO;
   411 }
   412 
   413 /*!
   414  * @brief Does the account manage its own cache of serverside contact icons?
   415  */
   416 - (BOOL)managesOwnContactIconCache
   417 {
   418 	return NO;
   419 }
   420 
   421 /*!
   422  * @brief Called once the display name has been properly filtered
   423  *
   424  * Subclasses may override to pass this name on to the server if appropriate.
   425  * Super's implementation should then be called.
   426  */
   427 - (void)gotFilteredDisplayName:(NSAttributedString *)attributedDisplayName
   428 {
   429 	[self updateLocalDisplayNameTo:attributedDisplayName];
   430 }
   431 
   432 - (NSImage *)userIcon
   433 {
   434 	NSData	*iconData = [self userIconData];
   435 	return (iconData ? [[[NSImage alloc] initWithData:iconData] autorelease] : nil);
   436 }
   437 
   438 @synthesize isTemporary;
   439 
   440 //Status ---------------------------------------------------------------------------------------------------------------
   441 #pragma mark Status
   442 /*!
   443  * @brief Supported properties
   444  *
   445  * Returns an array of properties supported by this account.  This account will not be informed of changes to keys
   446  * it does not support.  Available keys are:
   447  *   @"Display Name", @"Online", @"Offline", @"IdleSince", @"IdleManuallySet", @"User Icon"
   448  *   @"TextProfile", @"DefaultUserIconFilename", @"StatusState"
   449  * @return NSSet of supported keys
   450  */
   451 - (NSSet *)supportedPropertyKeys
   452 {
   453 	static	NSSet	*supportedPropertyKeys = nil;
   454 	if (!supportedPropertyKeys) {
   455 		supportedPropertyKeys = [[NSSet alloc] initWithObjects:
   456 			@"Online",
   457 			@"FormattedUID",
   458 			KEY_ACCOUNT_DISPLAY_NAME,
   459 			@"Display Name",
   460 			@"StatusState",
   461 			KEY_USE_USER_ICON, KEY_USER_ICON, KEY_DEFAULT_USER_ICON,
   462 			@"Enabled",
   463 			nil];
   464 	}
   465 
   466 	return supportedPropertyKeys;
   467 }
   468 
   469 /*!
   470  * @brief Status for key
   471  *
   472  * Returns the status this account should be for a specific key
   473  * @param key Property
   474  * @return id Status value
   475  */
   476 - (id)statusForKey:(NSString *)key
   477 {
   478 	return [self preferenceForKey:key group:GROUP_ACCOUNT_STATUS];
   479 }
   480 
   481 /*!
   482  * @brief Update account status
   483  *
   484  * Update account status for the changed key.  This is called when account status changes Adium-side and the account
   485  * code should update status account/server side in response.  The new value for the key can be accessed using
   486  * the statusForKey method.
   487  * @param key The updated property
   488  */
   489 - (void)updateStatusForKey:(NSString *)key
   490 {
   491 	[self updateCommonStatusForKey:key];
   492 }
   493 
   494 /*!
   495  * @brief Update contact status
   496  *
   497  * Adium is requesting that the account update a contact's status.  This method is primarily called by the get info
   498  * window.  Since this is called sparsely, accounts may choose to look up additional information such as profiles
   499  * in response to this.  Adium guards this method to prevent it from being called too rapidly, so expensive lookups
   500  * are not a problem if the delayedUpdateStatusInterval is set correctly.
   501  */
   502 - (void)delayedUpdateContactStatus:(AIListContact *)inContact
   503 {
   504 	
   505 }
   506 
   507 /*!
   508  * @brief Update contact interval
   509  *
   510  * Specifies the mininum interval at which delayedUpdateContactStatus will be called.  If the account code is performing
   511  * expensive operations (such as profile or web lookups) in response to updateContactStatus, it can guard against
   512  * the lookups being performed too frequently by returning an interval here.
   513  */
   514 - (float)delayedUpdateStatusInterval
   515 {
   516 	return 0.5;
   517 }
   518 
   519 /*!
   520  * @brief Perform the setting of a status state
   521  *
   522  * Sets the account to a passed status state.  The account should set itself to best possible status given the return
   523  * values of statusState's accessors.  The passed statusMessage has been filtered; it should be used rather than
   524  * statusState.statusMessage, which returns an unfiltered statusMessage.
   525  *
   526  * @param statusState The state to enter
   527  * @param statusMessage The filtered status message to use.
   528  */
   529 - (void)setStatusState:(AIStatus *)statusState usingStatusMessage:(NSAttributedString *)statusMessage
   530 {
   531 	
   532 }
   533 
   534 /*!
   535  * @brief Set the social networking status message for this account
   536  *
   537  * This will only be called if [self.service isSocialNetworkingService] returns TRUE.
   538  *
   539  * @param statusMessage The status message, which has already been filtered.
   540  */
   541 - (void)setSocialNetworkingStatusMessage:(NSAttributedString *)statusMessage
   542 {
   543 	
   544 }
   545 /*!
   546  * @brief Should the autorefreshing attributed string associated with a key be updated at the moment?
   547  *
   548  * The default implementation causes all dynamic strings which need updating to be updated if the account is
   549  * online.  Subclasses may choose to implement more complex logic; for example, a nickname seen only in a chat
   550  * might be updated only if a chat is open.
   551  */
   552 - (BOOL)shouldUpdateAutorefreshingAttributedStringForKey:(NSString *)inKey
   553 {
   554 	return self.online;
   555 }
   556 
   557 //Messaging, Chatting, Strings -----------------------------------------------------------------------------------------
   558 #pragma mark Messaging, Chatting, Strings
   559 /*!
   560  * @brief Available for sending content
   561  *
   562  * Returns YES if the contact is available for receiving content of the specified type.  If contact is nil, instead
   563  * check for the availiability to send any content of the given type.
   564  *
   565  * The default implementation indicates the account, if online, can send messages to any online contact.
   566  * It can also send files to any online contact if the account subclass conforms to the AIAccount_Files protocol.
   567  *
   568  * @param inType A string content type
   569  * @param inContact The destination contact, or nil to check global availability
   570  */
   571 - (BOOL)availableForSendingContentType:(NSString *)inType toContact:(AIListContact *)inContact
   572 {
   573 	if ([inType isEqualToString:CONTENT_MESSAGE_TYPE] ||
   574 		[inType isEqualToString:CONTENT_NOTIFICATION_TYPE]) {
   575 		return (self.online &&
   576 				(!inContact || inContact.online || inContact.isStranger || [self canSendOfflineMessageToContact:inContact]));
   577 				
   578 	} else if ([inType isEqualToString:CONTENT_FILE_TRANSFER_TYPE]) {
   579 		return (self.online && [self conformsToProtocol:@protocol(AIAccount_Files)] &&
   580 				(!inContact || inContact.online || inContact.isStranger));
   581 	}
   582 
   583 	return NO;
   584 }
   585 
   586 /*!
   587  * @brief Open a chat
   588  *
   589  * Open the passed chat account-side.  Depending on the protocol, account code may need to establish a connection in
   590  * response to this method or perhaps make no actions at all.  This method is used by both one-on-one chats and
   591  * multi-user chats.
   592  * @param chat The chat to open
   593  * @return YES on success
   594  */
   595 - (BOOL)openChat:(AIChat *)chat
   596 {
   597 	return NO;
   598 }
   599 
   600 /*!
   601  * @brief Close a chat
   602  *
   603  * Close the passed chat account-side.  Depending on the protocol, account code may need to close a connection in
   604  * response to this method or perhaps make no actions at all.  This method is used by both one-on-one chats and
   605  * multi-user chats.
   606  *
   607  * This method should *only* be called by a core controller.  Call [adium.interfaceController closeChat:chat] to perform a close from other code.
   608  *
   609  * @param chat The chat to close
   610  * @return YES on success
   611  */
   612 - (BOOL)closeChat:(AIChat *)chat
   613 {
   614 	return NO;
   615 }
   616 
   617 /*!
   618  * @brief Invite a contact to an open chat
   619  *
   620  * Invite a contact to the passed chat, if supported by the protocol and the specific chat instance.  An invite
   621  * message is provided as a convenience to protocols that require or support one.
   622  * @param contact AIListObject to invite
   623  * @param chat AIChat they are being invited to
   624  * @param inviteMessage NSString invite message for the invited contact
   625  * @return YES on success
   626  */
   627 - (BOOL)inviteContact:(AIListObject *)contact toChat:(AIChat *)chat withMessage:(NSString *)inviteMessage
   628 {
   629 	NSLog(@"invite contact to chat with message");
   630 	return NO;
   631 }
   632 
   633 /*!
   634  * @brief Send a typing object
   635  *
   636  * The content object contains all the necessary information for sending,
   637  * including the destination contact.
   638  */
   639 - (void)sendTypingObject:(AIContentTyping *)inTypingObject
   640 {
   641 
   642 }
   643 
   644 /*!
   645  * @brief Send a message
   646  *
   647  * The content object contains all the necessary information for sending,
   648  * including the destination contact. [inMessageObject encodedMessage] contains the NSString which should be sent.
   649  */
   650 - (BOOL)sendMessageObject:(AIContentMessage *)inMessageObject
   651 {
   652 	return NO;
   653 }
   654 
   655 /*!
   656  * @brief Does the account support sending notifications?
   657  */
   658 - (BOOL)supportsSendingNotifications
   659 {
   660 	return NO;
   661 }
   662 
   663 /*!
   664  * @brief Send a notification
   665  */
   666 - (BOOL)sendNotificationObject:(AIContentNotification *)inContentNotification
   667 {
   668 	return NO;
   669 }
   670 
   671 /*!
   672  * @brief Encode attributed string (generic)
   673  *
   674  * Encode an NSAttributedString into a NSString for this account.  Accounts that support formatted text or require
   675  * special encoding on strings should do that work here.  For example, HTML based accounts should convert the 
   676  * NSAttributedString to HTML appropriate for their protocol (Adium can help with this).
   677  * @param inAttributedString String to encode
   678  * @param inListObject List object associated with the string; nil if the string is not associated with a particular list object, which is the case if encoding for a status message or a group chat message.
   679  * @return NSString result from encoding
   680  */
   681 - (NSString *)encodedAttributedString:(NSAttributedString *)inAttributedString forListObject:(AIListObject *)inListObject
   682 {
   683     return [inAttributedString string];
   684 }
   685 
   686 /*!
   687  * @brief Encode attributed string to send as a message
   688  */
   689 - (NSString *)encodedAttributedStringForSendingContentMessage:(AIContentMessage *)inContentMessage
   690 {
   691     return [self encodedAttributedString:inContentMessage.message forListObject:[inContentMessage destination]];
   692 }
   693 
   694 /*!
   695  * @brief Should an autoreply be sent to this message?
   696  *
   697  * This will only be called if the generic algorithm determines that an autoreply is appropriate. The account
   698  * gets an opportunity to suppress sending the autoreply, e.g. on the basis of the message's content or source.
   699  */
   700 - (BOOL)shouldSendAutoreplyToMessage:(AIContentMessage *)message
   701 {
   702 	return YES;
   703 }
   704 
   705 //Presence Tracking ----------------------------------------------------------------------------------------------------
   706 #pragma mark Presence Tracking
   707 /*!
   708  * @brief Contact list editable?
   709  *
   710  * @return YES if the contact list is currently editable
   711  */
   712 - (BOOL)contactListEditable
   713 {
   714 	return NO;
   715 }
   716 
   717 /*!
   718  * @brief Add contacts
   719  *
   720  * Add contacts to a group on this account.  Create the group if it doesn't already exist.
   721  * @param objects NSArray of AIListContact objects to add
   722  * @param group AIListGroup destination for contacts
   723  */
   724 - (void)addContact:(AIListContact *)contact toGroup:(AIListGroup *)group
   725 {
   726 	//XXX - Our behavior for duplicate contacts isn't specified here.  Should we handle that adium-side automatically? -ai
   727 }
   728 
   729 /*!
   730  * @brief Remove contacts
   731  *
   732  * Remove contacts from this account.
   733  * @param objects NSArray of AIListContact objects to remove
   734  * @param groups NSArray of AIListGroup objects to remove from.
   735  */
   736 - (void)removeContacts:(NSArray *)objects fromGroups:(NSArray *)groups
   737 {
   738 	
   739 }
   740 
   741 /*!
   742  * @brief Remove a group
   743  *
   744  * Remove a group from this account.
   745  * @param group AIListGroup to remove
   746  */
   747 - (void)deleteGroup:(AIListGroup *)group
   748 {
   749 	//XXX - Adium's current behavior is to delete all the contacts within a group, and then delete the group.  This is innefficient on protocols which support deleting groups. -ai
   750 }
   751 
   752 /*!
   753  * @brief Move contacts
   754  *
   755  * Move existing contacts to a specific group on this account.  The passed contacts should already exist somewhere on
   756  * this account.
   757  * @param objects NSArray of AIListContact objects to remove
   758  * @param oldGroups NSSet of AIListGroup source for contacts
   759  * @param group NSSet of AIListGroup destination for contacts
   760  */
   761 - (void)moveListObjects:(NSArray *)objects fromGroups:(NSSet *)oldGroups toGroups:(NSSet *)groups
   762 {
   763 	NSAssert(NO, @"Should not be reached");
   764 }
   765 
   766 /*!
   767  * @brief Rename a group
   768  *
   769  * Rename a group on this account.
   770  * @param group AIListGroup to rename
   771  * @param newName NSString name for the group
   772  */
   773 - (void)renameGroup:(AIListGroup *)group to:(NSString *)newName
   774 {
   775 	NSAssert(NO, @"Should not be reached");
   776 }
   777 
   778 /*!
   779  * @brief Menu items for contact
   780  *
   781  * Returns an array of menu items for a contact on this account.  This is the best place to add protocol-specific
   782  * actions that aren't otherwise supported by Adium.
   783  * @param inContact AIListContact for menu items
   784  * @return NSArray of NSMenuItem instances for the passed contact
   785  */
   786 - (NSArray *)menuItemsForContact:(AIListContact *)inContact
   787 {
   788 	return nil;
   789 }
   790 
   791 /*!
   792  * @brief Menu items for chat
   793  *
   794  * Returns an array of menu items for a chat on this account.  This is the best place to add protocol-specific
   795  * actions that aren't otherwise supported by Adium.
   796  * @param inChat AIChat for menu items
   797  * @return NSArray of NSMenuItem instances for the passed contact
   798  */
   799 - (NSArray *)menuItemsForChat:(AIChat *)inChat
   800 {
   801 	return nil;
   802 }
   803 
   804 /*!
   805  * @brief Menu items for the account's actions
   806  *
   807  * Returns an array of menu items for account-specific actions.  This is the best place to add protocol-specific
   808  * actions that aren't otherwise supported by Adium.  It will only be queried if the account is online.
   809  * @return NSArray of NSMenuItem instances for this account
   810  */
   811 - (NSArray *)accountActionMenuItems
   812 {
   813 	return nil;
   814 }
   815 
   816 /*!
   817  * @brief The account menu item was updated
   818  *
   819  * This method allows the opportunity to update the account menu item, e.g. to add information to it
   820  */
   821 - (void)accountMenuDidUpdate:(NSMenuItem*)menuItem
   822 {
   823 
   824 }
   825 
   826 /*!
   827  * @brief Is a contact on the contact list intentionally listed?
   828  *
   829  * By default, it is assumed that any contact on the list is intended be there.
   830  * This is used by AIListContact to determine if the prescence of itself on the list is indicative of a degree
   831  * of trust, for preferences such as "automatically accept files from contacts on my contact list".
   832  */
   833 - (BOOL)isContactIntentionallyListed:(AIListContact *)contact
   834 {
   835 	return YES;
   836 }
   837 
   838 /*!
   839  * @brief Return the data for the serverside icon for a contact
   840  */
   841 - (NSData *)serversideIconDataForContact:(AIListContact *)contact
   842 {
   843 	return nil;
   844 }
   845 
   846 #pragma mark Secure messsaging
   847 
   848 /*!
   849  * @brief Allow secure messaging toggling on a chat?
   850  *
   851  * Returns YES if secure (encrypted) messaging's status for this chat should be able to be changed.
   852  * This allows the account to determine on a per-chat basis whether the chat's initial security setting should be permanently
   853  * maintained.  If it returns NO, the user can not request for the chat to become encrypted or unencrypted.
   854  * This is currently implemented by Gaim accounts to return YES for one-on-one chats and NO for group chats to indicate
   855  * the functionality provided by Off-the-Record Messaging (OTR).
   856  *
   857  * @param inChat The query chat 
   858  * @result Should the state of secure messaging be allowed to change?
   859  */
   860 - (BOOL)allowSecureMessagingTogglingForChat:(AIChat *)inChat
   861 {
   862 	//Allow secure messaging via OTR for one-on-one chats
   863 	return !inChat.isGroupChat;
   864 }
   865 
   866 /*!
   867  * @brief Provide a localized description of the encryption this account provides
   868  *
   869  * Returns a localized string which describes the encryption this account supports.
   870  *
   871  * @result An <tt>NSString</tt> describing the encryption offerred by this account, if any.
   872  */
   873 - (NSString *)aboutEncryption
   874 {
   875 	return [NSString stringWithFormat:
   876 		AILocalizedStringFromTableInBundle(@"Adium provides encryption, authentication, deniability, and perfect forward secrecy over %@ via Off-the-Record Messaging (OTR). If your contact is not using an OTR-compatible messaging system, your contact will be sent a link to the OTR web site when you attempt to connect. For more information on OTR, visit http://www.cypherpunks.ca/otr/.", nil, [NSBundle bundleForClass:[AIAccount class]], nil),
   877 		[self.service shortDescription]];
   878 }
   879 
   880 /*!
   881  * @brief Start or stop secure messaging in a chat
   882  *
   883  * @param inSecureMessaging The desired state of the chat in terms of encryption
   884  * @param inChat The chat to change
   885  */
   886 - (void)requestSecureMessaging:(BOOL)inSecureMessaging
   887 						inChat:(AIChat *)inChat
   888 {
   889 	[adium.contentController requestSecureOTRMessaging:inSecureMessaging
   890 												  inChat:inChat];
   891 }
   892 
   893 /*!
   894  * @brief Allow the user to verify (or unverify) the identity being used for encryption in a chat
   895  *
   896  * It is an error to call this on a chat which is not currently encrypted.
   897  *
   898  * @param inChat The chat
   899  */
   900 - (void)promptToVerifyEncryptionIdentityInChat:(AIChat *)inChat
   901 {
   902 	[adium.contentController promptToVerifyEncryptionIdentityInChat:inChat];
   903 }
   904 
   905 #pragma mark Image sending
   906 /*!
   907  * @brief Can the account send images inline within a chat?
   908  */
   909 - (BOOL)canSendImagesForChat:(AIChat *)inChat
   910 {
   911 	return NO;
   912 }
   913 
   914 #pragma mark Authorization
   915 /*!
   916  * @brief An authorization prompt closed, granting or denying a contact's request for authorization
   917  *
   918  * @param inDict A dictionary of authorization information created by the account originally and unmodified
   919  * @param authorizationResponse An AIAuthorizationResponse indicating if authorization was granted or denied or if there was no response
   920  */
   921 - (void)authorizationWithDict:(NSDictionary *)infoDict response:(AIAuthorizationResponse)authorizationResponse;
   922 {}
   923 
   924 #pragma mark Group Chats
   925 /*!
   926  * @brief Should the chat autocomplete the UID instead of the Display Name?
   927  */
   928 - (BOOL)chatShouldAutocompleteUID:(AIChat *)inChat
   929 {
   930 	return NO;
   931 }
   932 
   933 -(NSMenu*)actionMenuForChat:(AIChat*)chat
   934 {
   935 	return nil;
   936 }
   937 
   938 /*!
   939  * @brief Does the account manage group chat ignoring?
   940  *
   941  * If it doesn't, the AIChat will handle ignoring itself.
   942  */
   943 - (BOOL)accountManagesGroupChatIgnore
   944 {
   945 	return NO;
   946 }
   947 
   948 /*!
   949  * @brief Return if a contact is ignored
   950  *
   951  * @param inContact The AIListContact
   952  * @param chat The AIChat the inContact is a member of.
   953  *
   954  * @return YES if ignored, NO otherwise.
   955  */
   956 - (BOOL)contact:(AIListContact *)inContact isIgnoredInChat:(AIChat *)chat
   957 {
   958 	return NO;
   959 }
   960 
   961 /*!
   962  * @brief Ignore a contact
   963  *
   964  * @param inContact The AIListContact
   965  * @param inIgnored YES if the contact should be ignored, NO otherwise.
   966  * @param chat The AIChat the inContact is a member of.
   967  */
   968 - (void)setContact:(AIListContact *)inContact ignored:(BOOL)inIgnored inChat:(AIChat *)chat
   969 {
   970 	
   971 }
   972 
   973 #pragma mark Logging
   974 - (BOOL)shouldLogChat:(AIChat *)chat
   975 {
   976 	return ![self isTemporary];
   977 }
   978 
   979 #pragma mark AppleScript
   980 - (NSNumber *)scriptingInternalObjectID
   981 {
   982 	return [NSNumber numberWithInt:[self.internalObjectID intValue]];
   983 }
   984 
   985 /**
   986  * @brief The standard objectSpecifier for this model object.
   987  *
   988  * AIAccount is contained by AIService, using the 'accounts' key.
   989  * Each instance has a unique integer identifier.
   990  */
   991 - (NSScriptObjectSpecifier *)objectSpecifier
   992 {
   993 	//get my service
   994 	AIService *theService = self.service;
   995 	NSScriptObjectSpecifier *containerRef = [theService objectSpecifier];
   996 
   997 	return [[[NSUniqueIDSpecifier alloc]
   998 			 initWithContainerClassDescription:[containerRef keyClassDescription]
   999 			 containerSpecifier:containerRef key:@"accounts"
  1000 			 uniqueID:[self scriptingInternalObjectID]] autorelease];
  1001 }
  1002 
  1003 /**
  1004  * @brief Returns the UID of this account.
  1005  */
  1006 - (NSString *)scriptingUID
  1007 {
  1008 	return self.UID;
  1009 }
  1010 
  1011 /**
  1012  * @brief Ensures that it's impossible to set the UID of an account.
  1013  *
  1014  * This makes sense for the services I'm familiar with, like AIM and GTalk. It may not make sense for other protocols.
  1015  * However, it still doesn't seem necessary to do from code.
  1016  */
  1017 - (void)setScriptingUID:(NSString *)n
  1018 {
  1019 	[[NSScriptCommand currentCommand] setScriptErrorNumber:errOSACantAssign];
  1020 	[[NSScriptCommand currentCommand] setScriptErrorString:@"Can't dynamically change the UID of this account."];
  1021 }
  1022 
  1023 /**
  1024  * @brief Make a contact, according to the passed dictionary of AppleScript properties
  1025  * 
  1026  * @param properties A dictionary of the following keys:
  1027  *		@"KeyDictionary" is the list of the properties in the "with properties" clause of the AS make command.
  1028  *			@"UID" key of KeyDictionary is the required "name" property of contacts
  1029  *			@"parentGroup" key of keyDictionary is the optional "contact group" property of contacts.
  1030  *						   If the parentGroup is not specified, the contact will not be added to the contact list.
  1031  */
  1032 - (id)makeContactWithProperties:(NSDictionary *)properties
  1033 {
  1034 	NSDictionary *keyDictionary = [properties objectForKey:@"KeyDictionary"];
  1035 	if (!keyDictionary) {
  1036 		[[NSScriptCommand currentCommand] setScriptErrorNumber:errOSACantAssign];
  1037 		[[NSScriptCommand currentCommand] setScriptErrorString:@"Can't create a contact without specifying contact properties."];
  1038 		return nil;
  1039 	}
  1040 	NSString *contactUID = [keyDictionary objectForKey:@"UID"];
  1041 	if (!contactUID) {
  1042 		[[NSScriptCommand currentCommand] setScriptErrorNumber:errOSACantAssign];
  1043 		[[NSScriptCommand currentCommand] setScriptErrorString:@"Can't create a contact without specifying the contact name."];
  1044 		return nil;
  1045 	}
  1046 	AIListContact *newContact = [adium.contactController contactWithService:self.service account:self UID:contactUID];
  1047 	NSScriptObjectSpecifier *groupSpecifier = [keyDictionary objectForKey:@"parentGroup"];
  1048 	AIListGroup *group = [groupSpecifier objectsByEvaluatingSpecifier];
  1049 	//If we have a group, we add this contact to the contact list.
  1050 	if (groupSpecifier && group) {
  1051 		[self addContact:newContact toGroup:group];
  1052 	}
  1053 	
  1054 	return newContact;
  1055 }
  1056 - (void)insertObject:(AIListObject *)contact inContactsAtIndex:(int)index
  1057 {
  1058 	//Intentially unimplemented. This should never be called (contacts are created a different way), but is required for KVC-compliance.
  1059 }
  1060 - (void)removeObjectFromContactsAtIndex:(NSInteger)index
  1061 {
  1062 	AIListObject *object = [self.contacts objectAtIndex:index];
  1063 	
  1064 	for (AIListGroup *group in object.groups) {
  1065 		[object removeFromGroup:group];
  1066 	}
  1067 }
  1068 
  1069 /**
  1070  * @brief Creates a chat according to the given properties.
  1071  * @param resolvedKeyDictionary The dictionary of arguments to the 'make' command.
  1072  *
  1073  * This uses my own custom make<Key>WithProperties KVC method. :)
  1074  * The idea is that be default Cocoa-AS will try to make an object using the standard alloc/init routines
  1075  * However, you may not want that to be the case. If an AS model object implements this method, then when its the 
  1076  * target of a 'make' command, it will be called. The method should return a new object, already assigned to a
  1077  * container, as AICreateCommand will not do that for you.
  1078  */
  1079 - (id)makeChatWithProperties:(NSDictionary *)resolvedKeyDictionary
  1080 {
  1081 	AILogWithSignature(@"%@", resolvedKeyDictionary);
  1082 	NSArray *participants = [resolvedKeyDictionary objectForKey:@"withContacts"];
  1083 	if (!participants) {
  1084 		[[NSScriptCommand currentCommand] setScriptErrorNumber:errOSACantAssign];
  1085 		[[NSScriptCommand currentCommand] setScriptErrorString:@"Can't create a chat without a contact!"];
  1086 		return nil;
  1087 	}
  1088 	if (![resolvedKeyDictionary objectForKey:@"newChatWindow"] && ![resolvedKeyDictionary objectForKey:@"Location"]) {
  1089 		[[NSScriptCommand currentCommand] setScriptErrorNumber:errOSACantAssign];
  1090 		[[NSScriptCommand currentCommand] setScriptErrorString:@"Can't create a chat without specifying its containing window."];
  1091 		return nil;
  1092 	}
  1093 	
  1094 	if ([participants count] == 1) {
  1095 		AIListContact *contact = [[participants objectAtIndex:0] objectsByEvaluatingSpecifier];
  1096 		if (!contact) {
  1097 			[[NSScriptCommand currentCommand] setScriptErrorNumber:errOSACantAssign];
  1098 			[[NSScriptCommand currentCommand] setScriptErrorString:@"Can't find that contact!"];
  1099 			return nil;
  1100 		}
  1101 		AIMessageWindowController *chatWindowController = nil;
  1102 		int index = -1; //at end by default
  1103 		if ([resolvedKeyDictionary objectForKey:@"newChatWindow"]) {
  1104 			//I need to put this in a new chat window
  1105 			chatWindowController = [adium.interfaceController openContainerWithID:nil name:nil];
  1106 		} else {
  1107 			//I need to figure out to which chat window the location specifier is referring.
  1108 			//NSLog(@"Here is the info about the location specifier: %@",[resolvedKeyDictionary objectForKey:@"Location"]);
  1109 			NSPositionalSpecifier *location = [resolvedKeyDictionary objectForKey:@"Location"];
  1110 			AIMessageWindow *chatWindow = [location insertionContainer];
  1111 			index = [location insertionIndex];
  1112 			chatWindowController = (AIMessageWindowController *)[chatWindow windowController];
  1113 		}
  1114 		
  1115 		if (!chatWindowController) {
  1116 			[[NSScriptCommand currentCommand] setScriptErrorNumber:errOSACantAssign];
  1117 			[[NSScriptCommand currentCommand] setScriptErrorString:@"Can't create chat in that chat window."];
  1118 			return nil;
  1119 		}
  1120 		
  1121 		AIChat *newChat = [adium.chatController chatWithContact:contact];
  1122 //		NSLog(@"Making new chat %@ in chat window %@:%@",newChat,chatWindowController,[chatWindowController containerID]);
  1123 		[adium.interfaceController openChat:newChat inContainerWithID:[chatWindowController containerID] atIndex:index];
  1124 		return newChat;
  1125 	} else {
  1126 		if (![self.service canCreateGroupChats]) {
  1127 			[[NSScriptCommand currentCommand] setScriptErrorNumber:errOSACantAssign];
  1128 			[[NSScriptCommand currentCommand] setScriptErrorString:@"Can't create a group chat with this service!"];
  1129 			return nil;
  1130 		}
  1131 		NSString *name = [resolvedKeyDictionary objectForKey:@"name"];
  1132 		if (!name) {
  1133 			[[NSScriptCommand currentCommand] setScriptErrorNumber:errOSACantAssign];
  1134 			[[NSScriptCommand currentCommand] setScriptErrorString:@"Can't create a group chat without a name!"];
  1135 			return nil;
  1136 		}
  1137 		//this can take a while...
  1138 		NSMutableArray *newParticipants = [[[NSMutableArray alloc] init] autorelease];
  1139 		for (int i=0;i<[participants count];i++) {
  1140 			[newParticipants addObject:[[participants objectAtIndex:i] objectsByEvaluatingSpecifier]];
  1141 		}
  1142 		
  1143 		//AIChat *newChat = [adium.chatController chatWithName:name identifier:nil onAccount:self chatCreationInfo:nil];
  1144 		DCJoinChatViewController *chatController = [DCJoinChatViewController joinChatView];
  1145 		[chatController doJoinChatWithName:name onAccount:self chatCreationInfo:nil invitingContacts:newParticipants withInvitationMessage:@"Hey, wanna join my chat?"];
  1146 		return [adium.chatController existingChatWithName:name onAccount:self];
  1147 	}
  1148 }
  1149 
  1150 /**
  1151  * @brief Returns the current status type of this account.
  1152  */
  1153 - (AIStatusTypeApplescript)scriptingStatusType
  1154 {
  1155 	return [self.statusState statusTypeApplescript];
  1156 }
  1157 
  1158 /**
  1159  * @brief Sets the type of the current status.
  1160  *
  1161  * If the current status is a temporary status, then we simply set it.
  1162  * Otherwise, we create a temporary copy of it, and set that.
  1163  */
  1164 - (void)setScriptingStatusType:(AIStatusTypeApplescript)scriptingType
  1165 {
  1166 	AIStatusType type;
  1167 	switch (scriptingType) {
  1168 		case AIAvailableStatusTypeAS:
  1169 			type = AIAvailableStatusType;
  1170 			break;
  1171 		case AIAwayStatusTypeAS:
  1172 			type = AIAwayStatusType;
  1173 			break;
  1174 		case AIInvisibleStatusTypeAS:
  1175 			type = AIInvisibleStatusType;
  1176 			break;
  1177 		case AIOfflineStatusTypeAS:
  1178 		default:
  1179 			type = AIOfflineStatusType;
  1180 			break;
  1181 	}
  1182 	
  1183 	AIStatus *currentStatus = self.statusState;
  1184 	if ([currentStatus mutabilityType] == AILockedStatusState || [currentStatus mutabilityType] == AISecondaryLockedStatusState) {
  1185 		switch (type) {
  1186 			case AIAvailableStatusType:
  1187 				currentStatus = [adium.statusController availableStatus];
  1188 				break;
  1189 			case AIAwayStatusType:
  1190 				currentStatus = [adium.statusController awayStatus];
  1191 				break;
  1192 			case AIInvisibleStatusType:
  1193 				currentStatus = [adium.statusController invisibleStatus];
  1194 				break;
  1195 			case AIOfflineStatusType:
  1196 				currentStatus = [adium.statusController offlineStatus];
  1197 				break;
  1198 		}
  1199 	} else {
  1200 		if ([currentStatus mutabilityType] != AITemporaryEditableStatusState) {
  1201 			currentStatus = [[currentStatus mutableCopy] autorelease];
  1202 			[currentStatus setMutabilityType:AITemporaryEditableStatusState];
  1203 		}
  1204 		[currentStatus setStatusType:type];
  1205 		[currentStatus setStatusName:[adium.statusController defaultStatusNameForType:type]];
  1206 	}
  1207 	[adium.statusController setActiveStatusState:currentStatus forAccount:self];
  1208 }
  1209 
  1210 /**
  1211  * @brief Returns a mutable status
  1212  *
  1213  * If the current status is built in, we create a temporary copy of the current status and set that.
  1214  *
  1215  * @return An AIStatus fit for being modified.
  1216  */
  1217 - (AIStatus *)modifiableCurrentStatus
  1218 {
  1219 	AIStatus *currentStatus = self.statusState;
  1220 	
  1221 	if ([currentStatus mutabilityType] != AITemporaryEditableStatusState) {
  1222 		currentStatus = [[currentStatus mutableCopy] autorelease];
  1223 		[currentStatus setMutabilityType:AITemporaryEditableStatusState];
  1224 	}	
  1225 	
  1226 	return currentStatus;
  1227 }
  1228 
  1229 /**
  1230  * @brief Sets the status message
  1231  *
  1232  * @param message, which may be an NSAttributedString or NSString
  1233  */
  1234 - (void)setScriptingStatusMessageWithAttributedString:(id)message
  1235 {
  1236 	AIStatus *currentStatus = [self modifiableCurrentStatus];
  1237 	
  1238 	if ([message isKindOfClass:[NSAttributedString class]])
  1239 		[currentStatus setStatusMessage:(NSAttributedString *)message];
  1240 	else
  1241 		[currentStatus setStatusMessageString:message];
  1242 	
  1243 	[adium.statusController setActiveStatusState:currentStatus forAccount:self];
  1244 }
  1245 
  1246 - (void)setScriptingStatusMessage:(NSString *)message
  1247 {
  1248   [self setScriptingStatusMessageWithAttributedString:message];
  1249 }
  1250 
  1251 /**
  1252  * @brief Sets the status message to a NSAttributedString extracted of a NSScriptCommand
  1253  */
  1254 - (void)setScriptingStatusMessageFromScriptCommand:(NSScriptCommand *)c
  1255 {
  1256 	//messageString could also be an NSTextStorage, due to WithMessage being able to also accept rich text
  1257 	NSString *messageString = [[c evaluatedArguments] objectForKey:@"WithMessage"];
  1258 	if (messageString)
  1259 		[self setScriptingStatusMessageWithAttributedString:messageString];	
  1260 }
  1261 
  1262 /**
  1263  * @brief Tells this account to be available, with an optional temporary status message.
  1264  */
  1265 - (void)scriptingGoAvailable:(NSScriptCommand *)c
  1266 {
  1267 	[adium.statusController setActiveStatusState:[adium.statusController availableStatus] forAccount:self];
  1268 	
  1269 	[self setScriptingStatusMessageFromScriptCommand:c];
  1270 }
  1271 
  1272 /**
  1273  * @brief Tells this account to be online, with an optional temporary status message.
  1274  */
  1275 - (void)scriptingGoOnline:(NSScriptCommand *)c
  1276 {
  1277 	if (self.statusType == AIInvisibleStatusType) {
  1278 		[self scriptingGoAvailable:c];
  1279 
  1280 	} else {		
  1281 		[self setShouldBeOnline:YES];
  1282 		
  1283 		[self setScriptingStatusMessageFromScriptCommand:c];
  1284 	}
  1285 }
  1286 
  1287 /**
  1288  * @brief Tells this account to be offline, with an optional temporary status message.
  1289  */
  1290 - (void)scriptingGoOffline:(NSScriptCommand *)c
  1291 {
  1292 	[self setShouldBeOnline:NO];
  1293 
  1294 	[self setScriptingStatusMessageFromScriptCommand:c];
  1295 }
  1296 
  1297 /**
  1298  * @brief Tells this account to be away, with an optional temporary status message.
  1299  */
  1300 - (void)scriptingGoAway:(NSScriptCommand *)c
  1301 {
  1302 	[adium.statusController setActiveStatusState:[adium.statusController awayStatus] forAccount:self];
  1303 
  1304 	[self setScriptingStatusMessageFromScriptCommand:c];
  1305 }
  1306 
  1307 /**
  1308  * @brief Tells this account to be invisible.
  1309  */
  1310 - (void)scriptingGoInvisible:(NSScriptCommand *)c
  1311 {
  1312 	[adium.statusController setActiveStatusState:[adium.statusController invisibleStatus] forAccount:self];
  1313 	
  1314 	[self setScriptingStatusMessageFromScriptCommand:c];
  1315 }
  1316 
  1317 /**
  1318  * @brief True, if a proxy is enabled
  1319  */
  1320 - (BOOL)proxyEnabled
  1321 {
  1322 	return [[self preferenceForKey:KEY_ACCOUNT_PROXY_ENABLED group:GROUP_ACCOUNT_STATUS] boolValue];
  1323 }
  1324 /**
  1325  * @brief Sets whether or not the proxy is enabled for this account.
  1326  * This does not change the proxy setting immediately, a disconnect and reconnect is still required.
  1327  */
  1328 - (void)setProxyEnabled:(BOOL)proxyEnabled
  1329 {
  1330 	[self setPreference:[NSNumber numberWithBool:proxyEnabled] forKey:KEY_ACCOUNT_PROXY_ENABLED group:GROUP_ACCOUNT_STATUS];
  1331 }
  1332 /**
  1333  * @brief Gets the type of the proxy (one of the defined AdiumProxyTypes)
  1334  */
  1335 - (AdiumProxyType)proxyType
  1336 {
  1337 	return [[self preferenceForKey:KEY_ACCOUNT_PROXY_TYPE group:GROUP_ACCOUNT_STATUS] intValue];
  1338 }
  1339 /**
  1340  * @brief Sets the proxy type (one of the defined AdiumProxyTypes)
  1341  */
  1342 - (void)setProxyType:(AdiumProxyType)type
  1343 {
  1344 	[self setPreference:[NSNumber numberWithInt:type] forKey:KEY_ACCOUNT_PROXY_TYPE group:GROUP_ACCOUNT_STATUS];
  1345 }
  1346 /**
  1347  * @brief Gets the proxy host as a string
  1348  */
  1349 - (NSString *)proxyHost
  1350 {
  1351 	return [self preferenceForKey:KEY_ACCOUNT_PROXY_HOST group:GROUP_ACCOUNT_STATUS];
  1352 }
  1353 /**
  1354  * @brief Sets the proxy host
  1355  */
  1356 - (void)setProxyHost:(NSString *)host
  1357 {
  1358 	[self setPreference:host forKey:KEY_ACCOUNT_PROXY_HOST group:GROUP_ACCOUNT_STATUS];
  1359 }
  1360 /**
  1361  * @brief Gets the proxy's port
  1362  */
  1363 - (NSNumber *)proxyPort
  1364 {
  1365 	NSString *proxyPort = [self preferenceForKey:KEY_ACCOUNT_PROXY_PORT group:GROUP_ACCOUNT_STATUS];
  1366 	return [NSNumber numberWithUnsignedShort:[proxyPort integerValue]];
  1367 }
  1368 /**
  1369  * @brief Set the port to which we should connect when connecting to the proxy
  1370  */
  1371 - (void)setProxyPort:(NSNumber *)port
  1372 {
  1373 	[self setPreference:[port stringValue] forKey:KEY_ACCOUNT_PROXY_PORT group:GROUP_ACCOUNT_STATUS];
  1374 }
  1375 /**
  1376  * @brief Gets the username we use when connecting to the proxy
  1377  */
  1378 - (NSString *)proxyUsername
  1379 {
  1380 	return [self preferenceForKey:KEY_ACCOUNT_PROXY_USERNAME group:GROUP_ACCOUNT_STATUS];
  1381 }
  1382 /**
  1383  * @brief Sets the username we should use when connecting to the proxy
  1384  */
  1385 - (void)setProxyUsername:(NSString *)username
  1386 {
  1387 	[self setPreference:username forKey:KEY_ACCOUNT_PROXY_USERNAME group:GROUP_ACCOUNT_STATUS];
  1388 }
  1389 /**
  1390  * @brief Gets the password we use when connecting to the proxy
  1391  */
  1392 - (NSString *)proxyPassword
  1393 {
  1394 	return [self preferenceForKey:KEY_ACCOUNT_PROXY_PASSWORD group:GROUP_ACCOUNT_STATUS];
  1395 }
  1396 /**
  1397  * @brief Sets the password we should use when connecting to the proxy
  1398  */
  1399 - (void)setProxyPassword:(NSString *)proxyPassword
  1400 {
  1401 	[self setPreference:proxyPassword forKey:KEY_ACCOUNT_PROXY_PASSWORD group:GROUP_ACCOUNT_STATUS];
  1402 }
  1403 
  1404 /**
  1405  * @brief Gets the proxy type for applescript (using the nice four-letter codes defined by AdiumProxyTypeApplescript)
  1406  */
  1407 - (AdiumProxyTypeApplescript)scriptingProxyType
  1408 {
  1409 	return [self applescriptProxyType:[self proxyType]];
  1410 }
  1411 /**
  1412  * @brief Sets the proxy type to one of the defined AdiumProxyTypeApplescripts
  1413  */
  1414 - (void)setScriptingProxyType:(AdiumProxyTypeApplescript)type
  1415 {
  1416 	[self setProxyType:[self proxyTypeFromApplescript:type]];
  1417 }
  1418 
  1419 @end
  1420 
  1421 @implementation AIAccount(AppleScriptPRIVATE)
  1422 - (AdiumProxyType)proxyTypeFromApplescript:(AdiumProxyTypeApplescript)proxyTypeAS
  1423 {
  1424 	switch(proxyTypeAS)
  1425 	{
  1426 		case Adium_Proxy_HTTP_AS:
  1427 			return Adium_Proxy_HTTP;
  1428 		case Adium_Proxy_SOCKS4_AS:
  1429 			return Adium_Proxy_SOCKS4;
  1430 		case Adium_Proxy_SOCKS5_AS:
  1431 			return Adium_Proxy_SOCKS5;
  1432 		case Adium_Proxy_Default_HTTP_AS:
  1433 			return Adium_Proxy_Default_HTTP;
  1434 		case Adium_Proxy_Default_SOCKS4_AS:
  1435 			return Adium_Proxy_Default_SOCKS4;
  1436 		case Adium_Proxy_Default_SOCKS5_AS:
  1437 			return Adium_Proxy_Default_SOCKS5;
  1438 		default:
  1439 			return Adium_Proxy_None;
  1440 	}
  1441 }
  1442 - (AdiumProxyTypeApplescript)applescriptProxyType:(AdiumProxyType)proxyType
  1443 {
  1444 	switch(proxyType)
  1445 	{
  1446 		case Adium_Proxy_HTTP:
  1447 			return Adium_Proxy_HTTP_AS;
  1448 		case Adium_Proxy_SOCKS4:
  1449 			return Adium_Proxy_SOCKS4_AS;
  1450 		case Adium_Proxy_SOCKS5:
  1451 			return Adium_Proxy_SOCKS5_AS;
  1452 		case Adium_Proxy_Default_HTTP:
  1453 			return Adium_Proxy_Default_HTTP_AS;
  1454 		case Adium_Proxy_Default_SOCKS4:
  1455 			return Adium_Proxy_Default_SOCKS4_AS;
  1456 		case Adium_Proxy_Default_SOCKS5:
  1457 			return Adium_Proxy_Default_SOCKS5_AS;
  1458 		default:
  1459 			return Adium_Proxy_None_AS;
  1460 	}
  1461 }
  1462 
  1463 @end