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.
2 * Adium is the legal property of its developers, whose names are listed in the copyright file included
3 * with this source distribution.
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.
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.
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.
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"
28 #import "AdiumAccounts.h"
30 #import <Adium/AIContactControllerProtocol.h>
31 #import <Adium/AIContentControllerProtocol.h>
32 #import <Adium/AIAccountControllerProtocol.h>
34 #import "DCJoinChatViewController.h"
35 #import "AIChatControllerProtocol.h"
36 #import "AIMessageWindowController.h"
37 #import "AIMessageWindow.h"
38 #import "AIInterfaceControllerProtocol.h"
39 #import "AIStatusControllerProtocol.h"
41 #define NEW_ACCOUNT_DISPLAY_TEXT AILocalizedString(@"<New Account>", "Placeholder displayed as the name of a new account")
43 @interface AIAccountDeletionDialog : NSObject <AIAccountControllerRemoveConfirmationDialog> {
49 - (id)initWithAccount:(AIAccount*)ac alert:(NSAlert*)al;
51 @property (readwrite, retain, nonatomic) id userData;
53 - (void)alertDidEnd:(NSAlert *)alert returnCode:(int)returnCode contextInfo:(void *)contextInfo;
57 @implementation AIAccountDeletionDialog
59 - (id)initWithAccount:(AIAccount*)ac alert:(NSAlert*)al {
60 if((self = [super init])) {
76 [self alertDidEnd:alert returnCode:[alert runModal] contextInfo:NULL];
79 - (void)beginSheetModalForWindow:(NSWindow*)window {
80 [alert beginSheetModalForWindow:window modalDelegate:self didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:NULL];
83 - (void)alertDidEnd:(NSAlert *)alert returnCode:(int)returnCode contextInfo:(void *)contextInfo {
84 [account alertForAccountDeletion:self didReturn:returnCode];
89 //Proxy types for applescript
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;
101 @interface AIAccount(AppleScriptPRIVATE)
102 - (AdiumProxyType)proxyTypeFromApplescript:(AdiumProxyTypeApplescript)proxyTypeAS;
103 - (AdiumProxyTypeApplescript)applescriptProxyType:(AdiumProxyType)proxyType;
110 * This abstract class represents an account the user has setup in Adium. Subclass this for every service.
112 @implementation AIAccount
115 * @brief Init Account
117 * Init this account instance
126 * Connect the account, transitioning it into an online state.
131 [self setValue:[NSNumber numberWithBool:YES] forProperty:@"Connecting" notify:NotifyNow];
137 * Rejoin the open group chats after disconnect
139 -(BOOL)rejoinChat:(AIChat*)chat
145 * @brief Do group chats support topics?
147 - (BOOL)groupChatsSupportTopic
153 * @brief Set a chat's topic
155 * This only has an effect on group chats.
157 - (void)setTopic:(NSString *)topic forChat:(AIChat *)chat
164 * Disconnect the account, transitioning it into an offline state.
168 [self cancelAutoReconnect];
169 [self setValue:nil forProperty:@"Connecting" notify:NotifyLater];
171 [self notifyOfChangedPropertiesSilently:NO];
175 * @brief Disconnect as a result of the network connection dropping out
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.
180 * Subclasses should call super's implementation.
182 - (void)disconnectFromDroppedNetworkConnection
188 * @brief Register an account
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.
193 - (void)performRegisterWithPassword:(NSString *)inPassword
199 * @brief The UID will be changed. The account has a chance to perform modifications
201 * For example, MSN adds \@hotmail.com to the proposedUID and returns the new value
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.
206 - (NSString *)accountWillSetUID:(NSString *)proposedUID
212 * @brief The account's UID changed
220 * @brief The account will be deleted
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.
225 - (void)willBeDeleted
227 [self setShouldBeOnline:NO];
229 //Remove our contacts immediately.
230 [self removeAllContacts];
234 * @brief Perform the deletion of this account
236 * This should be called only after proper confirmation has been made by the user.
238 - (void)performDelete
240 [adium.accountController deleteAccount:self];
243 - (id<AIAccountControllerRemoveConfirmationDialog>)confirmationDialogForAccountDeletion
245 //Will be released in alertForAccountDeletion:didReturn:
246 return [[AIAccountDeletionDialog alloc] initWithAccount:self alert:[self alertForAccountDeletion]];
250 * @brief The alert used for confirming the account deletion
252 * Meant for subclassers. By default, returns the dialog that asks the user if the account should really be deleted (and how).
254 - (NSAlert*)alertForAccountDeletion
256 return [NSAlert alertWithMessageText:AILocalizedString(@"Delete Account",nil)
257 defaultButton:AILocalizedString(@"Delete",nil)
258 alternateButton:AILocalizedString(@"Cancel",nil)
260 informativeTextWithFormat:AILocalizedString(@"Delete the account %@?",nil), ([self.formattedUID length] ? self.formattedUID : NEW_ACCOUNT_DISPLAY_TEXT)];
264 * @brief The dialog asking for confirmation for deleting the account did return.
266 * @param dialog The dialog that has completed
267 * @param returnCode One of the regular NSAlert return codes
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.
272 * If this implementation is not called, dialog should be released by the subclass.
274 - (void)alertForAccountDeletion:(id<AIAccountControllerRemoveConfirmationDialog>)dialog didReturn:(int)returnCode
276 if(returnCode == NSAlertDefaultReturn) {
277 [self performDelete];
280 [(AIAccountDeletionDialog*)dialog release];
284 * @brief A formatted UID which may include additional necessary identifying information.
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
289 - (NSString *)explicitFormattedUID
291 return self.formattedUID;
295 * @brief Use our host for the servername when storing password
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.
300 - (BOOL)useHostForPasswordServerName
306 * @brief Use our internal object ID for the username when storing password
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.
311 - (BOOL)useInternalObjectIDForPasswordName
316 //Properties -----------------------------------------------------------------------------------------------------------
317 #pragma mark Properties
319 * @brief Send Autoresponses while away
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.
324 - (BOOL)supportsAutoReplies
330 * @brief Disconnect on fast user switch
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.
335 - (BOOL)disconnectOnFastUserSwitch
341 * @brief Connectivity based on network reachability
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.
346 - (BOOL)connectivityBasedOnNetworkReachability
352 * @brief Suppress typing notifications after send
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
359 - (BOOL)suppressTypingNotificationChangesAfterSend
365 * @brief Support server-side storing of messages to offline users?
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.
371 - (BOOL)canSendOfflineMessageToContact:(AIListContact *)inContact
377 * @brief Support messaging invisible contacts?
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.
383 - (BOOL)maySendMessageToInvisibleContact:(AIListContact *)inContact
389 * @brief Should offline messages be sent without prompting the user?
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.
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.
397 - (BOOL)sendOfflineMessagesWithoutPrompting
403 * @brief Does the account itself display file transfer messages in chat windows?
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.
408 - (BOOL)accountDisplaysFileTransferMessages
414 * @brief Does the account manage its own cache of serverside contact icons?
416 - (BOOL)managesOwnContactIconCache
422 * @brief Called once the display name has been properly filtered
424 * Subclasses may override to pass this name on to the server if appropriate.
425 * Super's implementation should then be called.
427 - (void)gotFilteredDisplayName:(NSAttributedString *)attributedDisplayName
429 [self updateLocalDisplayNameTo:attributedDisplayName];
432 - (NSImage *)userIcon
434 NSData *iconData = [self userIconData];
435 return (iconData ? [[[NSImage alloc] initWithData:iconData] autorelease] : nil);
438 @synthesize isTemporary;
440 //Status ---------------------------------------------------------------------------------------------------------------
443 * @brief Supported properties
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
451 - (NSSet *)supportedPropertyKeys
453 static NSSet *supportedPropertyKeys = nil;
454 if (!supportedPropertyKeys) {
455 supportedPropertyKeys = [[NSSet alloc] initWithObjects:
458 KEY_ACCOUNT_DISPLAY_NAME,
461 KEY_USE_USER_ICON, KEY_USER_ICON, KEY_DEFAULT_USER_ICON,
466 return supportedPropertyKeys;
470 * @brief Status for key
472 * Returns the status this account should be for a specific key
473 * @param key Property
474 * @return id Status value
476 - (id)statusForKey:(NSString *)key
478 return [self preferenceForKey:key group:GROUP_ACCOUNT_STATUS];
482 * @brief Update account status
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
489 - (void)updateStatusForKey:(NSString *)key
491 [self updateCommonStatusForKey:key];
495 * @brief Update contact status
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.
502 - (void)delayedUpdateContactStatus:(AIListContact *)inContact
508 * @brief Update contact interval
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.
514 - (float)delayedUpdateStatusInterval
520 * @brief Perform the setting of a status state
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.
526 * @param statusState The state to enter
527 * @param statusMessage The filtered status message to use.
529 - (void)setStatusState:(AIStatus *)statusState usingStatusMessage:(NSAttributedString *)statusMessage
535 * @brief Set the social networking status message for this account
537 * This will only be called if [self.service isSocialNetworkingService] returns TRUE.
539 * @param statusMessage The status message, which has already been filtered.
541 - (void)setSocialNetworkingStatusMessage:(NSAttributedString *)statusMessage
546 * @brief Should the autorefreshing attributed string associated with a key be updated at the moment?
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.
552 - (BOOL)shouldUpdateAutorefreshingAttributedStringForKey:(NSString *)inKey
557 //Messaging, Chatting, Strings -----------------------------------------------------------------------------------------
558 #pragma mark Messaging, Chatting, Strings
560 * @brief Available for sending content
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.
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.
568 * @param inType A string content type
569 * @param inContact The destination contact, or nil to check global availability
571 - (BOOL)availableForSendingContentType:(NSString *)inType toContact:(AIListContact *)inContact
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]));
578 } else if ([inType isEqualToString:CONTENT_FILE_TRANSFER_TYPE]) {
579 return (self.online && [self conformsToProtocol:@protocol(AIAccount_Files)] &&
580 (!inContact || inContact.online || inContact.isStranger));
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
592 * @param chat The chat to open
593 * @return YES on success
595 - (BOOL)openChat:(AIChat *)chat
601 * @brief Close a chat
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
607 * This method should *only* be called by a core controller. Call [adium.interfaceController closeChat:chat] to perform a close from other code.
609 * @param chat The chat to close
610 * @return YES on success
612 - (BOOL)closeChat:(AIChat *)chat
618 * @brief Invite a contact to an open chat
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
627 - (BOOL)inviteContact:(AIListObject *)contact toChat:(AIChat *)chat withMessage:(NSString *)inviteMessage
629 NSLog(@"invite contact to chat with message");
634 * @brief Send a typing object
636 * The content object contains all the necessary information for sending,
637 * including the destination contact.
639 - (void)sendTypingObject:(AIContentTyping *)inTypingObject
645 * @brief Send a message
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.
650 - (BOOL)sendMessageObject:(AIContentMessage *)inMessageObject
656 * @brief Does the account support sending notifications?
658 - (BOOL)supportsSendingNotifications
664 * @brief Send a notification
666 - (BOOL)sendNotificationObject:(AIContentNotification *)inContentNotification
672 * @brief Encode attributed string (generic)
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
681 - (NSString *)encodedAttributedString:(NSAttributedString *)inAttributedString forListObject:(AIListObject *)inListObject
683 return [inAttributedString string];
687 * @brief Encode attributed string to send as a message
689 - (NSString *)encodedAttributedStringForSendingContentMessage:(AIContentMessage *)inContentMessage
691 return [self encodedAttributedString:inContentMessage.message forListObject:[inContentMessage destination]];
695 * @brief Should an autoreply be sent to this message?
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.
700 - (BOOL)shouldSendAutoreplyToMessage:(AIContentMessage *)message
705 //Presence Tracking ----------------------------------------------------------------------------------------------------
706 #pragma mark Presence Tracking
708 * @brief Contact list editable?
710 * @return YES if the contact list is currently editable
712 - (BOOL)contactListEditable
718 * @brief Add contacts
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
724 - (void)addContact:(AIListContact *)contact toGroup:(AIListGroup *)group
726 //XXX - Our behavior for duplicate contacts isn't specified here. Should we handle that adium-side automatically? -ai
730 * @brief Remove contacts
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.
736 - (void)removeContacts:(NSArray *)objects fromGroups:(NSArray *)groups
742 * @brief Remove a group
744 * Remove a group from this account.
745 * @param group AIListGroup to remove
747 - (void)deleteGroup:(AIListGroup *)group
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
753 * @brief Move contacts
755 * Move existing contacts to a specific group on this account. The passed contacts should already exist somewhere on
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
761 - (void)moveListObjects:(NSArray *)objects fromGroups:(NSSet *)oldGroups toGroups:(NSSet *)groups
763 NSAssert(NO, @"Should not be reached");
767 * @brief Rename a group
769 * Rename a group on this account.
770 * @param group AIListGroup to rename
771 * @param newName NSString name for the group
773 - (void)renameGroup:(AIListGroup *)group to:(NSString *)newName
775 NSAssert(NO, @"Should not be reached");
779 * @brief Menu items for contact
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
786 - (NSArray *)menuItemsForContact:(AIListContact *)inContact
792 * @brief Menu items for chat
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
799 - (NSArray *)menuItemsForChat:(AIChat *)inChat
805 * @brief Menu items for the account's actions
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
811 - (NSArray *)accountActionMenuItems
817 * @brief The account menu item was updated
819 * This method allows the opportunity to update the account menu item, e.g. to add information to it
821 - (void)accountMenuDidUpdate:(NSMenuItem*)menuItem
827 * @brief Is a contact on the contact list intentionally listed?
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".
833 - (BOOL)isContactIntentionallyListed:(AIListContact *)contact
839 * @brief Return the data for the serverside icon for a contact
841 - (NSData *)serversideIconDataForContact:(AIListContact *)contact
846 #pragma mark Secure messsaging
849 * @brief Allow secure messaging toggling on a chat?
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).
857 * @param inChat The query chat
858 * @result Should the state of secure messaging be allowed to change?
860 - (BOOL)allowSecureMessagingTogglingForChat:(AIChat *)inChat
862 //Allow secure messaging via OTR for one-on-one chats
863 return !inChat.isGroupChat;
867 * @brief Provide a localized description of the encryption this account provides
869 * Returns a localized string which describes the encryption this account supports.
871 * @result An <tt>NSString</tt> describing the encryption offerred by this account, if any.
873 - (NSString *)aboutEncryption
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]];
881 * @brief Start or stop secure messaging in a chat
883 * @param inSecureMessaging The desired state of the chat in terms of encryption
884 * @param inChat The chat to change
886 - (void)requestSecureMessaging:(BOOL)inSecureMessaging
887 inChat:(AIChat *)inChat
889 [adium.contentController requestSecureOTRMessaging:inSecureMessaging
894 * @brief Allow the user to verify (or unverify) the identity being used for encryption in a chat
896 * It is an error to call this on a chat which is not currently encrypted.
898 * @param inChat The chat
900 - (void)promptToVerifyEncryptionIdentityInChat:(AIChat *)inChat
902 [adium.contentController promptToVerifyEncryptionIdentityInChat:inChat];
905 #pragma mark Image sending
907 * @brief Can the account send images inline within a chat?
909 - (BOOL)canSendImagesForChat:(AIChat *)inChat
914 #pragma mark Authorization
916 * @brief An authorization prompt closed, granting or denying a contact's request for authorization
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
921 - (void)authorizationWithDict:(NSDictionary *)infoDict response:(AIAuthorizationResponse)authorizationResponse;
924 #pragma mark Group Chats
926 * @brief Should the chat autocomplete the UID instead of the Display Name?
928 - (BOOL)chatShouldAutocompleteUID:(AIChat *)inChat
933 -(NSMenu*)actionMenuForChat:(AIChat*)chat
939 * @brief Does the account manage group chat ignoring?
941 * If it doesn't, the AIChat will handle ignoring itself.
943 - (BOOL)accountManagesGroupChatIgnore
949 * @brief Return if a contact is ignored
951 * @param inContact The AIListContact
952 * @param chat The AIChat the inContact is a member of.
954 * @return YES if ignored, NO otherwise.
956 - (BOOL)contact:(AIListContact *)inContact isIgnoredInChat:(AIChat *)chat
962 * @brief Ignore a contact
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.
968 - (void)setContact:(AIListContact *)inContact ignored:(BOOL)inIgnored inChat:(AIChat *)chat
974 - (BOOL)shouldLogChat:(AIChat *)chat
976 return ![self isTemporary];
979 #pragma mark AppleScript
980 - (NSNumber *)scriptingInternalObjectID
982 return [NSNumber numberWithInt:[self.internalObjectID intValue]];
986 * @brief The standard objectSpecifier for this model object.
988 * AIAccount is contained by AIService, using the 'accounts' key.
989 * Each instance has a unique integer identifier.
991 - (NSScriptObjectSpecifier *)objectSpecifier
994 AIService *theService = self.service;
995 NSScriptObjectSpecifier *containerRef = [theService objectSpecifier];
997 return [[[NSUniqueIDSpecifier alloc]
998 initWithContainerClassDescription:[containerRef keyClassDescription]
999 containerSpecifier:containerRef key:@"accounts"
1000 uniqueID:[self scriptingInternalObjectID]] autorelease];
1004 * @brief Returns the UID of this account.
1006 - (NSString *)scriptingUID
1012 * @brief Ensures that it's impossible to set the UID of an account.
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.
1017 - (void)setScriptingUID:(NSString *)n
1019 [[NSScriptCommand currentCommand] setScriptErrorNumber:errOSACantAssign];
1020 [[NSScriptCommand currentCommand] setScriptErrorString:@"Can't dynamically change the UID of this account."];
1024 * @brief Make a contact, according to the passed dictionary of AppleScript properties
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.
1032 - (id)makeContactWithProperties:(NSDictionary *)properties
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."];
1040 NSString *contactUID = [keyDictionary objectForKey:@"UID"];
1042 [[NSScriptCommand currentCommand] setScriptErrorNumber:errOSACantAssign];
1043 [[NSScriptCommand currentCommand] setScriptErrorString:@"Can't create a contact without specifying the contact name."];
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];
1056 - (void)insertObject:(AIListObject *)contact inContactsAtIndex:(int)index
1058 //Intentially unimplemented. This should never be called (contacts are created a different way), but is required for KVC-compliance.
1060 - (void)removeObjectFromContactsAtIndex:(NSInteger)index
1062 AIListObject *object = [self.contacts objectAtIndex:index];
1064 for (AIListGroup *group in object.groups) {
1065 [object removeFromGroup:group];
1070 * @brief Creates a chat according to the given properties.
1071 * @param resolvedKeyDictionary The dictionary of arguments to the 'make' command.
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.
1079 - (id)makeChatWithProperties:(NSDictionary *)resolvedKeyDictionary
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!"];
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."];
1094 if ([participants count] == 1) {
1095 AIListContact *contact = [[participants objectAtIndex:0] objectsByEvaluatingSpecifier];
1097 [[NSScriptCommand currentCommand] setScriptErrorNumber:errOSACantAssign];
1098 [[NSScriptCommand currentCommand] setScriptErrorString:@"Can't find that contact!"];
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];
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];
1115 if (!chatWindowController) {
1116 [[NSScriptCommand currentCommand] setScriptErrorNumber:errOSACantAssign];
1117 [[NSScriptCommand currentCommand] setScriptErrorString:@"Can't create chat in that chat window."];
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];
1126 if (![self.service canCreateGroupChats]) {
1127 [[NSScriptCommand currentCommand] setScriptErrorNumber:errOSACantAssign];
1128 [[NSScriptCommand currentCommand] setScriptErrorString:@"Can't create a group chat with this service!"];
1131 NSString *name = [resolvedKeyDictionary objectForKey:@"name"];
1133 [[NSScriptCommand currentCommand] setScriptErrorNumber:errOSACantAssign];
1134 [[NSScriptCommand currentCommand] setScriptErrorString:@"Can't create a group chat without a name!"];
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]];
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];
1151 * @brief Returns the current status type of this account.
1153 - (AIStatusTypeApplescript)scriptingStatusType
1155 return [self.statusState statusTypeApplescript];
1159 * @brief Sets the type of the current status.
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.
1164 - (void)setScriptingStatusType:(AIStatusTypeApplescript)scriptingType
1167 switch (scriptingType) {
1168 case AIAvailableStatusTypeAS:
1169 type = AIAvailableStatusType;
1171 case AIAwayStatusTypeAS:
1172 type = AIAwayStatusType;
1174 case AIInvisibleStatusTypeAS:
1175 type = AIInvisibleStatusType;
1177 case AIOfflineStatusTypeAS:
1179 type = AIOfflineStatusType;
1183 AIStatus *currentStatus = self.statusState;
1184 if ([currentStatus mutabilityType] == AILockedStatusState || [currentStatus mutabilityType] == AISecondaryLockedStatusState) {
1186 case AIAvailableStatusType:
1187 currentStatus = [adium.statusController availableStatus];
1189 case AIAwayStatusType:
1190 currentStatus = [adium.statusController awayStatus];
1192 case AIInvisibleStatusType:
1193 currentStatus = [adium.statusController invisibleStatus];
1195 case AIOfflineStatusType:
1196 currentStatus = [adium.statusController offlineStatus];
1200 if ([currentStatus mutabilityType] != AITemporaryEditableStatusState) {
1201 currentStatus = [[currentStatus mutableCopy] autorelease];
1202 [currentStatus setMutabilityType:AITemporaryEditableStatusState];
1204 [currentStatus setStatusType:type];
1205 [currentStatus setStatusName:[adium.statusController defaultStatusNameForType:type]];
1207 [adium.statusController setActiveStatusState:currentStatus forAccount:self];
1211 * @brief Returns a mutable status
1213 * If the current status is built in, we create a temporary copy of the current status and set that.
1215 * @return An AIStatus fit for being modified.
1217 - (AIStatus *)modifiableCurrentStatus
1219 AIStatus *currentStatus = self.statusState;
1221 if ([currentStatus mutabilityType] != AITemporaryEditableStatusState) {
1222 currentStatus = [[currentStatus mutableCopy] autorelease];
1223 [currentStatus setMutabilityType:AITemporaryEditableStatusState];
1226 return currentStatus;
1230 * @brief Sets the status message
1232 * @param message, which may be an NSAttributedString or NSString
1234 - (void)setScriptingStatusMessageWithAttributedString:(id)message
1236 AIStatus *currentStatus = [self modifiableCurrentStatus];
1238 if ([message isKindOfClass:[NSAttributedString class]])
1239 [currentStatus setStatusMessage:(NSAttributedString *)message];
1241 [currentStatus setStatusMessageString:message];
1243 [adium.statusController setActiveStatusState:currentStatus forAccount:self];
1246 - (void)setScriptingStatusMessage:(NSString *)message
1248 [self setScriptingStatusMessageWithAttributedString:message];
1252 * @brief Sets the status message to a NSAttributedString extracted of a NSScriptCommand
1254 - (void)setScriptingStatusMessageFromScriptCommand:(NSScriptCommand *)c
1256 //messageString could also be an NSTextStorage, due to WithMessage being able to also accept rich text
1257 NSString *messageString = [[c evaluatedArguments] objectForKey:@"WithMessage"];
1259 [self setScriptingStatusMessageWithAttributedString:messageString];
1263 * @brief Tells this account to be available, with an optional temporary status message.
1265 - (void)scriptingGoAvailable:(NSScriptCommand *)c
1267 [adium.statusController setActiveStatusState:[adium.statusController availableStatus] forAccount:self];
1269 [self setScriptingStatusMessageFromScriptCommand:c];
1273 * @brief Tells this account to be online, with an optional temporary status message.
1275 - (void)scriptingGoOnline:(NSScriptCommand *)c
1277 if (self.statusType == AIInvisibleStatusType) {
1278 [self scriptingGoAvailable:c];
1281 [self setShouldBeOnline:YES];
1283 [self setScriptingStatusMessageFromScriptCommand:c];
1288 * @brief Tells this account to be offline, with an optional temporary status message.
1290 - (void)scriptingGoOffline:(NSScriptCommand *)c
1292 [self setShouldBeOnline:NO];
1294 [self setScriptingStatusMessageFromScriptCommand:c];
1298 * @brief Tells this account to be away, with an optional temporary status message.
1300 - (void)scriptingGoAway:(NSScriptCommand *)c
1302 [adium.statusController setActiveStatusState:[adium.statusController awayStatus] forAccount:self];
1304 [self setScriptingStatusMessageFromScriptCommand:c];
1308 * @brief Tells this account to be invisible.
1310 - (void)scriptingGoInvisible:(NSScriptCommand *)c
1312 [adium.statusController setActiveStatusState:[adium.statusController invisibleStatus] forAccount:self];
1314 [self setScriptingStatusMessageFromScriptCommand:c];
1318 * @brief True, if a proxy is enabled
1320 - (BOOL)proxyEnabled
1322 return [[self preferenceForKey:KEY_ACCOUNT_PROXY_ENABLED group:GROUP_ACCOUNT_STATUS] boolValue];
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.
1328 - (void)setProxyEnabled:(BOOL)proxyEnabled
1330 [self setPreference:[NSNumber numberWithBool:proxyEnabled] forKey:KEY_ACCOUNT_PROXY_ENABLED group:GROUP_ACCOUNT_STATUS];
1333 * @brief Gets the type of the proxy (one of the defined AdiumProxyTypes)
1335 - (AdiumProxyType)proxyType
1337 return [[self preferenceForKey:KEY_ACCOUNT_PROXY_TYPE group:GROUP_ACCOUNT_STATUS] intValue];
1340 * @brief Sets the proxy type (one of the defined AdiumProxyTypes)
1342 - (void)setProxyType:(AdiumProxyType)type
1344 [self setPreference:[NSNumber numberWithInt:type] forKey:KEY_ACCOUNT_PROXY_TYPE group:GROUP_ACCOUNT_STATUS];
1347 * @brief Gets the proxy host as a string
1349 - (NSString *)proxyHost
1351 return [self preferenceForKey:KEY_ACCOUNT_PROXY_HOST group:GROUP_ACCOUNT_STATUS];
1354 * @brief Sets the proxy host
1356 - (void)setProxyHost:(NSString *)host
1358 [self setPreference:host forKey:KEY_ACCOUNT_PROXY_HOST group:GROUP_ACCOUNT_STATUS];
1361 * @brief Gets the proxy's port
1363 - (NSNumber *)proxyPort
1365 NSString *proxyPort = [self preferenceForKey:KEY_ACCOUNT_PROXY_PORT group:GROUP_ACCOUNT_STATUS];
1366 return [NSNumber numberWithUnsignedShort:[proxyPort integerValue]];
1369 * @brief Set the port to which we should connect when connecting to the proxy
1371 - (void)setProxyPort:(NSNumber *)port
1373 [self setPreference:[port stringValue] forKey:KEY_ACCOUNT_PROXY_PORT group:GROUP_ACCOUNT_STATUS];
1376 * @brief Gets the username we use when connecting to the proxy
1378 - (NSString *)proxyUsername
1380 return [self preferenceForKey:KEY_ACCOUNT_PROXY_USERNAME group:GROUP_ACCOUNT_STATUS];
1383 * @brief Sets the username we should use when connecting to the proxy
1385 - (void)setProxyUsername:(NSString *)username
1387 [self setPreference:username forKey:KEY_ACCOUNT_PROXY_USERNAME group:GROUP_ACCOUNT_STATUS];
1390 * @brief Gets the password we use when connecting to the proxy
1392 - (NSString *)proxyPassword
1394 return [self preferenceForKey:KEY_ACCOUNT_PROXY_PASSWORD group:GROUP_ACCOUNT_STATUS];
1397 * @brief Sets the password we should use when connecting to the proxy
1399 - (void)setProxyPassword:(NSString *)proxyPassword
1401 [self setPreference:proxyPassword forKey:KEY_ACCOUNT_PROXY_PASSWORD group:GROUP_ACCOUNT_STATUS];
1405 * @brief Gets the proxy type for applescript (using the nice four-letter codes defined by AdiumProxyTypeApplescript)
1407 - (AdiumProxyTypeApplescript)scriptingProxyType
1409 return [self applescriptProxyType:[self proxyType]];
1412 * @brief Sets the proxy type to one of the defined AdiumProxyTypeApplescripts
1414 - (void)setScriptingProxyType:(AdiumProxyTypeApplescript)type
1416 [self setProxyType:[self proxyTypeFromApplescript:type]];
1421 @implementation AIAccount(AppleScriptPRIVATE)
1422 - (AdiumProxyType)proxyTypeFromApplescript:(AdiumProxyTypeApplescript)proxyTypeAS
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;
1439 return Adium_Proxy_None;
1442 - (AdiumProxyTypeApplescript)applescriptProxyType:(AdiumProxyType)proxyType
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;
1459 return Adium_Proxy_None_AS;