|
David@0
|
1 |
/* |
|
David@0
|
2 |
* Adium is the legal property of its developers, whose names are listed in the copyright file included |
|
David@0
|
3 |
* with this source distribution. |
|
David@0
|
4 |
* |
|
David@0
|
5 |
* This program is free software; you can redistribute it and/or modify it under the terms of the GNU |
|
David@0
|
6 |
* General Public License as published by the Free Software Foundation; either version 2 of the License, |
|
David@0
|
7 |
* or (at your option) any later version. |
|
David@0
|
8 |
* |
|
David@0
|
9 |
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even |
|
David@0
|
10 |
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General |
|
David@0
|
11 |
* Public License for more details. |
|
David@0
|
12 |
* |
|
David@0
|
13 |
* You should have received a copy of the GNU General Public License along with this program; if not, |
|
David@0
|
14 |
* write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
|
David@0
|
15 |
*/ |
|
David@0
|
16 |
|
|
David@0
|
17 |
#import "ESPurpleJabberAccount.h" |
|
David@0
|
18 |
#import <AdiumLibpurple/SLPurpleCocoaAdapter.h> |
|
David@0
|
19 |
#import <Adium/AIAccountControllerProtocol.h> |
|
David@0
|
20 |
#import <Adium/AIInterfaceControllerProtocol.h> |
|
David@0
|
21 |
#import <Adium/AIStatusControllerProtocol.h> |
|
David@0
|
22 |
#import <Adium/AIContactControllerProtocol.h> |
|
David@0
|
23 |
#import <Adium/AIChat.h> |
|
David@0
|
24 |
#import <Adium/AIHTMLDecoder.h> |
|
David@0
|
25 |
#import <Adium/AIListContact.h> |
|
David@0
|
26 |
#import <Adium/AIStatus.h> |
|
David@0
|
27 |
#import <Adium/AIStatusIcons.h> |
|
David@0
|
28 |
#import <Adium/ESFileTransfer.h> |
|
David@0
|
29 |
#import <Adium/AIService.h> |
|
David@0
|
30 |
#import <AIUtilities/AIApplicationAdditions.h> |
|
David@0
|
31 |
#import <AIUtilities/AIAttributedStringAdditions.h> |
|
David@0
|
32 |
#import <AIUtilities/AIDictionaryAdditions.h> |
|
David@0
|
33 |
#import <AIUtilities/AIStringAdditions.h> |
|
catfish@2093
|
34 |
#import <libpurple/presence.h> |
|
catfish@2093
|
35 |
#import <libpurple/si.h> |
|
catfish@2093
|
36 |
#import <SystemConfiguration/SystemConfiguration.h> |
|
David@0
|
37 |
#import "AMXMLConsoleController.h" |
|
David@0
|
38 |
#import "AMPurpleJabberServiceDiscoveryBrowsing.h" |
|
David@0
|
39 |
#import "ESPurpleJabberAccountViewController.h" |
|
David@0
|
40 |
#import "AMPurpleJabberAdHocServer.h" |
|
David@0
|
41 |
#import "AMPurpleJabberAdHocPing.h" |
|
David@0
|
42 |
|
|
David@0
|
43 |
#define DEFAULT_JABBER_HOST @"@jabber.org" |
|
David@0
|
44 |
|
|
David@84
|
45 |
@interface ESPurpleJabberAccount () |
|
David@0
|
46 |
- (BOOL)enableXMLConsole; |
|
David@0
|
47 |
@end |
|
David@0
|
48 |
|
|
David@0
|
49 |
@implementation ESPurpleJabberAccount |
|
David@0
|
50 |
|
|
David@0
|
51 |
/*! |
|
David@0
|
52 |
* @brief The UID will be changed. The account has a chance to perform modifications |
|
David@0
|
53 |
* |
|
David@0
|
54 |
* Upgrade old Jabber accounts stored with the host in a separate key to have the right UID, in the form |
|
David@0
|
55 |
* name@server.org |
|
David@0
|
56 |
* |
|
David@0
|
57 |
* Append @jabber.org to a proposed UID which has no domain name and does not need to be updated. |
|
David@0
|
58 |
* |
|
David@0
|
59 |
* @param proposedUID The proposed, pre-filtered UID (filtered means it has no characters invalid for this servce) |
|
David@0
|
60 |
* @result The UID to use; the default implementation just returns proposedUID. |
|
David@0
|
61 |
*/ |
|
David@0
|
62 |
- (NSString *)accountWillSetUID:(NSString *)proposedUID |
|
David@0
|
63 |
{ |
|
David@0
|
64 |
proposedUID = [proposedUID lowercaseString]; |
|
David@0
|
65 |
NSString *correctUID; |
|
David@0
|
66 |
|
|
David@0
|
67 |
if ((proposedUID && ([proposedUID length] > 0)) && |
|
David@0
|
68 |
([proposedUID rangeOfString:@"@"].location == NSNotFound)) { |
|
David@0
|
69 |
|
|
David@0
|
70 |
NSString *host; |
|
David@0
|
71 |
//Upgrade code: grab a previously specified Jabber host |
|
David@740
|
72 |
if ((host = [self preferenceForKey:@"Jabber:Host" group:GROUP_ACCOUNT_STATUS])) { |
|
David@0
|
73 |
//Determine our new, full UID |
|
David@0
|
74 |
correctUID = [NSString stringWithFormat:@"%@@%@",proposedUID, host]; |
|
David@0
|
75 |
|
|
David@0
|
76 |
//Clear the preference and then set the UID so we don't perform this upgrade again |
|
David@0
|
77 |
[self setPreference:nil forKey:@"Jabber:Host" group:GROUP_ACCOUNT_STATUS]; |
|
David@0
|
78 |
[self setPreference:correctUID forKey:@"FormattedUID" group:GROUP_ACCOUNT_STATUS]; |
|
David@0
|
79 |
|
|
David@0
|
80 |
} else { |
|
David@0
|
81 |
//Append [self serverSuffix] (e.g. @jabber.org) to a Jabber account with no server |
|
David@0
|
82 |
correctUID = [proposedUID stringByAppendingString:[self serverSuffix]]; |
|
David@0
|
83 |
} |
|
David@0
|
84 |
} else { |
|
David@0
|
85 |
correctUID = proposedUID; |
|
David@0
|
86 |
} |
|
David@0
|
87 |
|
|
David@0
|
88 |
return correctUID; |
|
David@0
|
89 |
} |
|
David@0
|
90 |
|
|
David@0
|
91 |
- (const char*)protocolPlugin |
|
David@0
|
92 |
{ |
|
David@0
|
93 |
return "prpl-jabber"; |
|
David@0
|
94 |
} |
|
David@0
|
95 |
|
|
David@0
|
96 |
- (void)dealloc |
|
David@0
|
97 |
{ |
|
David@0
|
98 |
[xmlConsoleController close]; |
|
David@0
|
99 |
[xmlConsoleController release]; |
|
David@0
|
100 |
|
|
David@0
|
101 |
[super dealloc]; |
|
David@0
|
102 |
} |
|
David@0
|
103 |
|
|
David@0
|
104 |
- (NSSet *)supportedPropertyKeys |
|
David@0
|
105 |
{ |
|
David@0
|
106 |
static NSMutableSet *supportedPropertyKeys = nil; |
|
David@0
|
107 |
|
|
David@0
|
108 |
if (!supportedPropertyKeys) { |
|
David@0
|
109 |
supportedPropertyKeys = [[NSMutableSet alloc] initWithObjects: |
|
David@0
|
110 |
@"AvailableMessage", |
|
David@0
|
111 |
@"Invisible", |
|
David@0
|
112 |
nil]; |
|
David@0
|
113 |
[supportedPropertyKeys unionSet:[super supportedPropertyKeys]]; |
|
David@0
|
114 |
} |
|
David@0
|
115 |
|
|
David@0
|
116 |
return supportedPropertyKeys; |
|
David@0
|
117 |
} |
|
David@0
|
118 |
|
|
David@0
|
119 |
- (void)configurePurpleAccount |
|
David@0
|
120 |
{ |
|
David@0
|
121 |
[super configurePurpleAccount]; |
|
David@0
|
122 |
|
|
David@0
|
123 |
NSString *connectServer; |
|
David@0
|
124 |
BOOL forceOldSSL, allowPlaintext, requireTLS; |
|
David@0
|
125 |
|
|
David@427
|
126 |
purple_account_set_username(account, self.purpleAccountName); |
|
David@0
|
127 |
|
|
David@0
|
128 |
//'Connect via' server (nil by default) |
|
David@0
|
129 |
connectServer = [self preferenceForKey:KEY_JABBER_CONNECT_SERVER group:GROUP_ACCOUNT_STATUS]; |
|
David@0
|
130 |
//XXX - As of libpurple 2.0.0, 'localhost' doesn't work properly by 127.0.0.1 does. Hack! |
|
David@0
|
131 |
if (connectServer && [connectServer isEqualToString:@"localhost"]) |
|
David@0
|
132 |
connectServer = @"127.0.0.1"; |
|
David@0
|
133 |
|
|
David@0
|
134 |
purple_account_set_string(account, "connect_server", (connectServer ? |
|
David@0
|
135 |
[connectServer UTF8String] : |
|
David@0
|
136 |
"")); |
|
David@0
|
137 |
|
|
zacw@2615
|
138 |
NSString *boshServer = [self preferenceForKey:KEY_JABBER_BOSH_SERVER group:GROUP_ACCOUNT_STATUS]; |
|
zacw@2615
|
139 |
|
|
zacw@2615
|
140 |
purple_account_set_string(account, "bosh_url", (boshServer ? [boshServer UTF8String] : "")); |
|
zacw@2615
|
141 |
|
|
zacw@2202
|
142 |
// FT proxies |
|
zacw@2202
|
143 |
NSString *ftProxies = [self preferenceForKey:KEY_JABBER_FT_PROXIES group:GROUP_ACCOUNT_STATUS]; |
|
zacw@2202
|
144 |
if (ftProxies.length) { |
|
zacw@2202
|
145 |
purple_account_set_string(account, "ft_proxies", [ftProxies UTF8String]); |
|
zacw@2202
|
146 |
} |
|
zacw@2202
|
147 |
|
|
David@0
|
148 |
//Force old SSL usage? (off by default) |
|
David@0
|
149 |
forceOldSSL = [[self preferenceForKey:KEY_JABBER_FORCE_OLD_SSL group:GROUP_ACCOUNT_STATUS] boolValue]; |
|
David@0
|
150 |
purple_account_set_bool(account, "old_ssl", forceOldSSL); |
|
David@0
|
151 |
|
|
David@0
|
152 |
//Require SSL or TLS? (off by default) |
|
David@0
|
153 |
requireTLS = [[self preferenceForKey:KEY_JABBER_REQUIRE_TLS group:GROUP_ACCOUNT_STATUS] boolValue]; |
|
David@0
|
154 |
purple_account_set_bool(account, "require_tls", requireTLS); |
|
David@0
|
155 |
|
|
David@0
|
156 |
//Allow plaintext authorization over an unencrypted connection? Purple will prompt if this is NO and is needed. |
|
David@0
|
157 |
allowPlaintext = [[self preferenceForKey:KEY_JABBER_ALLOW_PLAINTEXT group:GROUP_ACCOUNT_STATUS] boolValue]; |
|
David@0
|
158 |
purple_account_set_bool(account, "auth_plain_in_clear", allowPlaintext); |
|
David@0
|
159 |
|
|
David@0
|
160 |
/* Mac OS X 10.4's cyrus-sasl's PLAIN mech gives us problems. Is it a bug in the installed library, a bug in its compilation, or a bug |
|
David@0
|
161 |
* in our linkage against it? I don't know. The result is that the username gets included twice before the base64 encoding is performed. |
|
David@0
|
162 |
* |
|
David@0
|
163 |
* Furthermore, on any version, using the cyrus-sasl PLAIN mech prevents us from following Google Talk best practices for handling of domain names. |
|
David@0
|
164 |
* This is because we can't add to the <auth> response's attributes: |
|
David@0
|
165 |
* xmlns:ga='http://www.google.com/talk/protocol/auth' ga:client-uses-full-bind-result='true' |
|
David@0
|
166 |
* as per http://code.google.com/apis/talk/jep_extensions/jid_domain_change.html and therefore we won't automatically resolve changing an |
|
David@0
|
167 |
* "@gmail.com" to "@googlemail.com" or some other domain name. |
|
David@0
|
168 |
* |
|
David@0
|
169 |
* We therefore use the PLAIN implementation in libpurple itself. Libpurple's own DIGEST-MD5 is always used for compatibility with old OpenFire |
|
David@0
|
170 |
* servers. |
|
David@0
|
171 |
* |
|
David@0
|
172 |
* This preference and the changes for it are added via the "libpurple_jabber_avoid_sasl_option_hack.diff" patch we apply during the build process. |
|
David@0
|
173 |
*/ |
|
David@0
|
174 |
purple_prefs_set_bool("/plugins/prpl/jabber/avoid_sasl_for_plain_auth", YES); |
|
David@0
|
175 |
} |
|
David@0
|
176 |
|
|
David@0
|
177 |
- (NSString *)serverSuffix |
|
David@0
|
178 |
{ |
|
David@0
|
179 |
NSString *host = [self preferenceForKey:KEY_JABBER_CONNECT_SERVER group:GROUP_ACCOUNT_STATUS]; |
|
David@0
|
180 |
|
|
David@0
|
181 |
return (host ? host : DEFAULT_JABBER_HOST); |
|
David@0
|
182 |
} |
|
David@0
|
183 |
|
|
David@0
|
184 |
/*! @brief Obtain the resource name for this Jabber account. |
|
David@0
|
185 |
* |
|
David@0
|
186 |
* This could be extended in the future to perform keyword substitution (e.g. s/%computerName%/CSCopyMachineName()/). |
|
David@0
|
187 |
* |
|
David@0
|
188 |
* @return The resource name for the account. |
|
David@0
|
189 |
*/ |
|
David@0
|
190 |
- (NSString *)resourceName |
|
David@0
|
191 |
{ |
|
David@0
|
192 |
NSString *resource = [self preferenceForKey:KEY_JABBER_RESOURCE group:GROUP_ACCOUNT_STATUS]; |
|
David@0
|
193 |
|
|
David@0
|
194 |
if(resource == nil || [resource length] == 0) |
|
David@0
|
195 |
resource = [(NSString*)SCDynamicStoreCopyLocalHostName(NULL) autorelease]; |
|
David@0
|
196 |
|
|
David@0
|
197 |
return resource; |
|
David@0
|
198 |
} |
|
David@0
|
199 |
|
|
David@0
|
200 |
- (const char *)purpleAccountName |
|
David@0
|
201 |
{ |
|
David@0
|
202 |
NSString *userNameWithHost = nil, *completeUserName = nil; |
|
David@0
|
203 |
BOOL serverAppendedToUID; |
|
David@0
|
204 |
|
|
David@0
|
205 |
/* |
|
David@0
|
206 |
* Purple stores the username in the format username@server/resource. We need to pass it a username in this format |
|
David@0
|
207 |
* |
|
David@0
|
208 |
* The user should put the username in username@server format, which is common for Jabber. If the user does |
|
David@0
|
209 |
* not specify the server, use jabber.org. |
|
David@0
|
210 |
*/ |
|
David@0
|
211 |
|
|
David@0
|
212 |
serverAppendedToUID = ([UID rangeOfString:@"@"].location != NSNotFound); |
|
David@0
|
213 |
|
|
David@0
|
214 |
if (serverAppendedToUID) { |
|
David@0
|
215 |
userNameWithHost = UID; |
|
David@0
|
216 |
} else { |
|
David@0
|
217 |
userNameWithHost = [UID stringByAppendingString:[self serverSuffix]]; |
|
David@0
|
218 |
} |
|
David@0
|
219 |
|
|
David@0
|
220 |
completeUserName = [NSString stringWithFormat:@"%@/%@" ,userNameWithHost, [self resourceName]]; |
|
David@0
|
221 |
|
|
David@0
|
222 |
return [completeUserName UTF8String]; |
|
David@0
|
223 |
} |
|
David@0
|
224 |
|
|
David@0
|
225 |
/*! |
|
David@0
|
226 |
* @brief Connect Host |
|
David@0
|
227 |
* |
|
David@0
|
228 |
* Convenience method for retrieving the connect host for this account |
|
David@0
|
229 |
* |
|
David@0
|
230 |
* Rather than having a separate server field, Jabber uses the servername after the user name. |
|
David@0
|
231 |
* username@server.org |
|
David@0
|
232 |
* |
|
David@0
|
233 |
* The connect server, stored in KEY_JABBER_CONNECT_SERVER, overrides this to provide the connect host. It will |
|
David@0
|
234 |
* not be set in most cases. |
|
David@0
|
235 |
*/ |
|
David@0
|
236 |
- (NSString *)host |
|
David@0
|
237 |
{ |
|
David@0
|
238 |
NSString *host; |
|
David@0
|
239 |
|
|
David@0
|
240 |
if (!(host = [self preferenceForKey:KEY_JABBER_CONNECT_SERVER group:GROUP_ACCOUNT_STATUS])) { |
|
David@3
|
241 |
NSUInteger location = [UID rangeOfString:@"@"].location; |
|
David@0
|
242 |
|
|
David@0
|
243 |
if ((location != NSNotFound) && (location + 1 < [UID length])) { |
|
David@0
|
244 |
host = [UID substringFromIndex:(location + 1)]; |
|
David@0
|
245 |
|
|
David@0
|
246 |
} else { |
|
David@0
|
247 |
host = [self serverSuffix]; |
|
David@0
|
248 |
} |
|
David@0
|
249 |
} |
|
David@0
|
250 |
|
|
David@0
|
251 |
return host; |
|
David@0
|
252 |
} |
|
David@0
|
253 |
|
|
David@0
|
254 |
/*! |
|
David@0
|
255 |
* @brief Should set aliases serverside? |
|
David@0
|
256 |
* |
|
David@0
|
257 |
* Jabber supports serverside aliases. |
|
David@0
|
258 |
*/ |
|
David@0
|
259 |
- (BOOL)shouldSetAliasesServerside |
|
David@0
|
260 |
{ |
|
David@0
|
261 |
return YES; |
|
David@0
|
262 |
} |
|
David@0
|
263 |
|
|
David@0
|
264 |
- (AIListContact *)contactWithUID:(NSString *)sourceUID |
|
David@0
|
265 |
{ |
|
David@0
|
266 |
AIListContact *contact; |
|
David@0
|
267 |
|
|
David@89
|
268 |
contact = [adium.contactController existingContactWithService:service |
|
David@0
|
269 |
account:self |
|
David@0
|
270 |
UID:sourceUID]; |
|
David@0
|
271 |
if (!contact) { |
|
David@89
|
272 |
contact = [adium.contactController contactWithService:[self _serviceForUID:sourceUID] |
|
David@0
|
273 |
account:self |
|
David@0
|
274 |
UID:sourceUID]; |
|
David@0
|
275 |
} |
|
David@0
|
276 |
|
|
David@0
|
277 |
return contact; |
|
David@0
|
278 |
} |
|
David@0
|
279 |
|
|
David@0
|
280 |
- (AIService *)_serviceForUID:(NSString *)contactUID |
|
David@0
|
281 |
{ |
|
David@0
|
282 |
AIService *contactService; |
|
David@0
|
283 |
NSString *contactServiceID = nil; |
|
David@0
|
284 |
|
|
David@0
|
285 |
if ([contactUID hasSuffix:@"@gmail.com"] || |
|
David@0
|
286 |
[contactUID hasSuffix:@"@googlemail.com"]) { |
|
David@0
|
287 |
contactServiceID = @"libpurple-jabber-gtalk"; |
|
David@0
|
288 |
|
|
David@0
|
289 |
} else if([contactUID hasSuffix:@"@livejournal.com"]){ |
|
David@0
|
290 |
contactServiceID = @"libpurple-jabber-livejournal"; |
|
David@0
|
291 |
|
|
David@0
|
292 |
} else { |
|
David@0
|
293 |
contactServiceID = @"libpurple-Jabber"; |
|
David@0
|
294 |
} |
|
David@0
|
295 |
|
|
David@95
|
296 |
contactService = [adium.accountController serviceWithUniqueID:contactServiceID]; |
|
David@0
|
297 |
|
|
David@0
|
298 |
return contactService; |
|
David@0
|
299 |
} |
|
David@0
|
300 |
|
|
David@0
|
301 |
- (id)authorizationRequestWithDict:(NSDictionary*)dict { |
|
David@0
|
302 |
switch ([[self preferenceForKey:KEY_JABBER_SUBSCRIPTION_BEHAVIOR group:GROUP_ACCOUNT_STATUS] intValue]) { |
|
David@0
|
303 |
case 2: // always accept + add |
|
David@0
|
304 |
// add |
|
David@0
|
305 |
{ |
|
David@0
|
306 |
NSString *groupname = [self preferenceForKey:KEY_JABBER_SUBSCRIPTION_GROUP group:GROUP_ACCOUNT_STATUS]; |
|
David@0
|
307 |
if ([groupname length] > 0) { |
|
David@427
|
308 |
AIListContact *contact = [adium.contactController contactWithService:self.service account:self UID:[dict objectForKey:@"Remote Name"]]; |
|
David@89
|
309 |
AIListGroup *group = [adium.contactController groupWithUID:groupname]; |
|
David@199
|
310 |
[contact.account addContact:contact toGroup:group]; |
|
David@0
|
311 |
} |
|
David@0
|
312 |
} |
|
David@0
|
313 |
// fallthrough |
|
David@0
|
314 |
case 1: // always accept |
|
David@0
|
315 |
[[self purpleAdapter] doAuthRequestCbValue:[[[dict objectForKey:@"authorizeCB"] retain] autorelease] withUserDataValue:[[[dict objectForKey:@"userData"] retain] autorelease]]; |
|
David@0
|
316 |
break; |
|
David@0
|
317 |
case 3: // always deny |
|
David@0
|
318 |
[[self purpleAdapter] doAuthRequestCbValue:[[[dict objectForKey:@"denyCB"] retain] autorelease] withUserDataValue:[[[dict objectForKey:@"userData"] retain] autorelease]]; |
|
David@0
|
319 |
break; |
|
David@0
|
320 |
default: // ask (should be 0) |
|
David@0
|
321 |
return [super authorizationRequestWithDict:dict]; |
|
David@0
|
322 |
} |
|
David@0
|
323 |
|
|
David@0
|
324 |
return NULL; |
|
David@0
|
325 |
} |
|
David@0
|
326 |
|
|
David@0
|
327 |
- (void)purpleAccountRegistered:(BOOL)success |
|
David@0
|
328 |
{ |
|
David@427
|
329 |
if(success && [self.service accountViewController]) { |
|
David@0
|
330 |
const char *usernamestr = purple_account_get_username(account); |
|
David@0
|
331 |
NSString *username; |
|
David@0
|
332 |
if (usernamestr) { |
|
David@0
|
333 |
NSString *userWithResource = [NSString stringWithUTF8String:usernamestr]; |
|
David@0
|
334 |
NSRange slashrange = [userWithResource rangeOfString:@"/"]; |
|
David@0
|
335 |
if(slashrange.location != NSNotFound) |
|
David@0
|
336 |
username = [userWithResource substringToIndex:slashrange.location]; |
|
David@0
|
337 |
else |
|
David@0
|
338 |
username = userWithResource; |
|
David@0
|
339 |
} else |
|
David@0
|
340 |
username = (id)[NSNull null]; |
|
David@0
|
341 |
|
|
David@0
|
342 |
NSString *pw = (purple_account_get_password(account) ? [NSString stringWithUTF8String:purple_account_get_password(account)] : [NSNull null]); |
|
David@0
|
343 |
|
|
David@1109
|
344 |
[[NSNotificationCenter defaultCenter] postNotificationName:AIAccountUsernameAndPasswordRegisteredNotification |
|
David@0
|
345 |
object:self |
|
David@0
|
346 |
userInfo:[NSDictionary dictionaryWithObjectsAndKeys: |
|
David@0
|
347 |
username, @"username", |
|
David@0
|
348 |
pw, @"password", |
|
David@0
|
349 |
nil]]; |
|
David@0
|
350 |
} |
|
David@0
|
351 |
} |
|
David@0
|
352 |
|
|
David@0
|
353 |
#pragma mark Status |
|
David@0
|
354 |
|
|
David@0
|
355 |
- (NSString *)encodedAttributedString:(NSAttributedString *)inAttributedString forListObject:(AIListObject *)inListObject |
|
David@0
|
356 |
{ |
|
David@0
|
357 |
static AIHTMLDecoder *jabberHtmlEncoder = nil; |
|
David@0
|
358 |
if (!jabberHtmlEncoder) { |
|
David@0
|
359 |
jabberHtmlEncoder = [[AIHTMLDecoder alloc] init]; |
|
David@0
|
360 |
[jabberHtmlEncoder setIncludesHeaders:NO]; |
|
David@0
|
361 |
[jabberHtmlEncoder setIncludesFontTags:YES]; |
|
David@0
|
362 |
[jabberHtmlEncoder setClosesFontTags:YES]; |
|
David@0
|
363 |
[jabberHtmlEncoder setIncludesStyleTags:YES]; |
|
David@0
|
364 |
[jabberHtmlEncoder setIncludesColorTags:YES]; |
|
David@0
|
365 |
[jabberHtmlEncoder setEncodesNonASCII:NO]; |
|
David@0
|
366 |
[jabberHtmlEncoder setPreservesAllSpaces:NO]; |
|
David@0
|
367 |
[jabberHtmlEncoder setUsesAttachmentTextEquivalents:YES]; |
|
David@0
|
368 |
} |
|
David@0
|
369 |
|
|
David@0
|
370 |
return [jabberHtmlEncoder encodeHTML:inAttributedString imagesPath:nil]; |
|
David@0
|
371 |
} |
|
David@0
|
372 |
|
|
David@0
|
373 |
- (NSString *)_UIDForAddingObject:(AIListContact *)object |
|
David@0
|
374 |
{ |
|
David@837
|
375 |
NSString *objectUID = object.UID; |
|
David@0
|
376 |
NSString *properUID; |
|
David@0
|
377 |
|
|
David@0
|
378 |
if ([objectUID rangeOfString:@"@"].location != NSNotFound) { |
|
David@0
|
379 |
properUID = objectUID; |
|
David@0
|
380 |
} else { |
|
David@427
|
381 |
properUID = [NSString stringWithFormat:@"%@@%@",objectUID,self.host]; |
|
David@0
|
382 |
} |
|
David@0
|
383 |
|
|
David@0
|
384 |
return [properUID lowercaseString]; |
|
David@0
|
385 |
} |
|
David@0
|
386 |
|
|
David@0
|
387 |
- (NSString *)unknownGroupName { |
|
David@0
|
388 |
return (AILocalizedString(@"Roster","Roster - the Jabber default group")); |
|
David@0
|
389 |
} |
|
David@0
|
390 |
|
|
David@0
|
391 |
- (NSString *)connectionStringForStep:(int)step |
|
David@0
|
392 |
{ |
|
David@0
|
393 |
switch (step) { |
|
David@0
|
394 |
case 0: |
|
David@0
|
395 |
return AILocalizedString(@"Connecting",nil); |
|
David@0
|
396 |
break; |
|
David@0
|
397 |
case 1: |
|
David@0
|
398 |
return AILocalizedString(@"Initializing Stream",nil); |
|
David@0
|
399 |
break; |
|
David@0
|
400 |
case 2: |
|
David@0
|
401 |
return AILocalizedString(@"Reading data",nil); |
|
David@0
|
402 |
break; |
|
David@0
|
403 |
case 3: |
|
David@0
|
404 |
return AILocalizedString(@"Authenticating",nil); |
|
David@0
|
405 |
break; |
|
David@0
|
406 |
case 5: |
|
David@0
|
407 |
return AILocalizedString(@"Initializing Stream",nil); |
|
David@0
|
408 |
break; |
|
David@0
|
409 |
case 6: |
|
David@0
|
410 |
return AILocalizedString(@"Authenticating",nil); |
|
David@0
|
411 |
break; |
|
David@0
|
412 |
} |
|
David@0
|
413 |
return nil; |
|
David@0
|
414 |
} |
|
David@0
|
415 |
|
|
David@0
|
416 |
- (AIReconnectDelayType)shouldAttemptReconnectAfterDisconnectionError:(NSString **)disconnectionError |
|
David@0
|
417 |
{ |
|
David@0
|
418 |
AIReconnectDelayType shouldAttemptReconnect = [super shouldAttemptReconnectAfterDisconnectionError:disconnectionError]; |
|
David@0
|
419 |
|
|
David@0
|
420 |
if (([self lastDisconnectionReason] == PURPLE_CONNECTION_ERROR_CERT_OTHER_ERROR) && |
|
David@0
|
421 |
([self shouldVerifyCertificates])) { |
|
David@0
|
422 |
shouldAttemptReconnect = AIReconnectNever; |
|
David@0
|
423 |
} else if (!finishedConnectProcess && ![password length] && |
|
David@0
|
424 |
(disconnectionError && |
|
David@0
|
425 |
([*disconnectionError isEqualToString:[NSString stringWithUTF8String:_("Read Error")]] || |
|
David@0
|
426 |
[*disconnectionError isEqualToString:[NSString stringWithUTF8String:_("Service Unavailable")]] || |
|
David@0
|
427 |
[*disconnectionError isEqualToString:[NSString stringWithUTF8String:_("Forbidden")]]))) { |
|
David@0
|
428 |
//No password specified + above error while we're connecting = behavior of various broken servers. Prompt for a password. |
|
David@0
|
429 |
[self serverReportedInvalidPassword]; |
|
David@0
|
430 |
shouldAttemptReconnect = AIReconnectImmediately; |
|
David@0
|
431 |
} |
|
David@0
|
432 |
|
|
David@0
|
433 |
return shouldAttemptReconnect; |
|
David@0
|
434 |
} |
|
David@0
|
435 |
|
|
David@0
|
436 |
- (void)disconnectFromDroppedNetworkConnection |
|
David@0
|
437 |
{ |
|
David@0
|
438 |
/* Before we disconnect from a dropped network connection, set gc->disconnect_timeout to a non-0 value. |
|
David@0
|
439 |
* This will let the prpl know that we are disconnecting with no backing ssl connection and that therefore |
|
David@0
|
440 |
* the ssl connection is has should not be messaged in the process of disconnecting. |
|
David@0
|
441 |
*/ |
|
David@0
|
442 |
PurpleConnection *gc = purple_account_get_connection(account); |
|
David@0
|
443 |
if (PURPLE_CONNECTION_IS_VALID(gc) && |
|
David@0
|
444 |
!gc->disconnect_timeout) { |
|
David@0
|
445 |
gc->disconnect_timeout = -1; |
|
David@0
|
446 |
AILog(@"%@: Disconnecting from a dropped network connection", self); |
|
David@0
|
447 |
} |
|
David@0
|
448 |
|
|
David@0
|
449 |
[super disconnectFromDroppedNetworkConnection]; |
|
David@0
|
450 |
} |
|
David@0
|
451 |
|
|
David@0
|
452 |
#pragma mark File transfer |
|
David@0
|
453 |
- (BOOL)canSendFolders |
|
David@0
|
454 |
{ |
|
David@0
|
455 |
return NO; |
|
David@0
|
456 |
} |
|
David@0
|
457 |
|
|
David@0
|
458 |
- (void)beginSendOfFileTransfer:(ESFileTransfer *)fileTransfer |
|
David@0
|
459 |
{ |
|
David@0
|
460 |
[super _beginSendOfFileTransfer:fileTransfer]; |
|
David@0
|
461 |
} |
|
David@0
|
462 |
|
|
David@0
|
463 |
- (void)acceptFileTransferRequest:(ESFileTransfer *)fileTransfer |
|
David@0
|
464 |
{ |
|
David@0
|
465 |
[super acceptFileTransferRequest:fileTransfer]; |
|
David@0
|
466 |
} |
|
David@0
|
467 |
|
|
David@0
|
468 |
- (void)rejectFileReceiveRequest:(ESFileTransfer *)fileTransfer |
|
David@0
|
469 |
{ |
|
David@0
|
470 |
[super rejectFileReceiveRequest:fileTransfer]; |
|
David@0
|
471 |
} |
|
David@0
|
472 |
|
|
David@0
|
473 |
- (void)cancelFileTransfer:(ESFileTransfer *)fileTransfer |
|
David@0
|
474 |
{ |
|
David@0
|
475 |
[super cancelFileTransfer:fileTransfer]; |
|
David@0
|
476 |
} |
|
David@0
|
477 |
|
|
David@0
|
478 |
#pragma mark Status Messages |
|
David@0
|
479 |
- (NSString *)statusNameForPurpleBuddy:(PurpleBuddy *)buddy |
|
David@0
|
480 |
{ |
|
David@0
|
481 |
NSString *statusName = nil; |
|
David@0
|
482 |
PurplePresence *presence = purple_buddy_get_presence(buddy); |
|
David@0
|
483 |
PurpleStatus *status = purple_presence_get_active_status(presence); |
|
David@0
|
484 |
const char *purpleStatusID = purple_status_get_id(status); |
|
David@0
|
485 |
|
|
David@0
|
486 |
if (!purpleStatusID) return nil; |
|
David@0
|
487 |
|
|
David@0
|
488 |
if (!strcmp(purpleStatusID, jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_CHAT))) { |
|
David@0
|
489 |
statusName = STATUS_NAME_FREE_FOR_CHAT; |
|
David@0
|
490 |
|
|
David@0
|
491 |
} else if (!strcmp(purpleStatusID, jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_XA))) { |
|
David@0
|
492 |
statusName = STATUS_NAME_EXTENDED_AWAY; |
|
David@0
|
493 |
|
|
David@0
|
494 |
} else if (!strcmp(purpleStatusID, jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_DND))) { |
|
David@0
|
495 |
statusName = STATUS_NAME_DND; |
|
David@0
|
496 |
|
|
David@0
|
497 |
} |
|
David@0
|
498 |
|
|
David@0
|
499 |
return statusName; |
|
David@0
|
500 |
} |
|
David@0
|
501 |
|
|
David@0
|
502 |
#pragma mark Menu items |
|
David@0
|
503 |
- (NSString *)titleForContactMenuLabel:(const char *)label forContact:(AIListContact *)inContact |
|
David@0
|
504 |
{ |
|
David@0
|
505 |
if (strcmp(label, "Un-hide From") == 0) { |
|
David@837
|
506 |
return [NSString stringWithFormat:AILocalizedString(@"Un-hide From %@",nil),inContact.formattedUID]; |
|
David@0
|
507 |
|
|
David@0
|
508 |
} else if (strcmp(label, "Temporarily Hide From") == 0) { |
|
David@837
|
509 |
return [NSString stringWithFormat:AILocalizedString(@"Temporarily Hide From %@",nil),inContact.formattedUID]; |
|
David@0
|
510 |
|
|
David@0
|
511 |
} else if (strcmp(label, "Unsubscribe") == 0) { |
|
David@837
|
512 |
return [NSString stringWithFormat:AILocalizedString(@"Unsubscribe %@",nil),inContact.formattedUID]; |
|
David@0
|
513 |
|
|
David@0
|
514 |
} else if (strcmp(label, "(Re-)Request authorization") == 0) { |
|
David@837
|
515 |
return [NSString stringWithFormat:AILocalizedString(@"Re-request Authorization from %@",nil),inContact.formattedUID]; |
|
David@0
|
516 |
|
|
David@0
|
517 |
} else if (strcmp(label, "Cancel Presence Notification") == 0) { |
|
David@837
|
518 |
return [NSString stringWithFormat:AILocalizedString(@"Cancel Presence Notification to %@",nil),inContact.formattedUID]; |
|
zacw@2386
|
519 |
|
|
zacw@2386
|
520 |
} else if (strcmp(label, _("Ping")) == 0) { |
|
zacw@2386
|
521 |
return [NSString stringWithFormat:AILocalizedString(@"Ping %@",nil),inContact.formattedUID]; |
|
zacw@2386
|
522 |
|
|
David@0
|
523 |
} |
|
David@0
|
524 |
|
|
David@0
|
525 |
return [super titleForContactMenuLabel:label forContact:inContact]; |
|
David@0
|
526 |
} |
|
David@0
|
527 |
|
|
David@0
|
528 |
- (NSString *)titleForAccountActionMenuLabel:(const char *)label |
|
David@0
|
529 |
{ |
|
David@0
|
530 |
if (strcmp(label, "Set User Info...") == 0) { |
|
David@0
|
531 |
return [AILocalizedString(@"Set User Info", nil) stringByAppendingEllipsis]; |
|
David@0
|
532 |
|
|
David@0
|
533 |
} else if (strcmp(label, "Search for Users...") == 0) { |
|
David@0
|
534 |
return [AILocalizedString(@"Search for Users", nil) stringByAppendingEllipsis]; |
|
David@0
|
535 |
|
|
David@0
|
536 |
} else if (strcmp(label, "Set Mood...") == 0) { |
|
David@0
|
537 |
return [AILocalizedString(@"Set Mood", nil) stringByAppendingEllipsis]; |
|
David@0
|
538 |
|
|
David@0
|
539 |
} else if (strcmp(label, "Set Nickname...") == 0) { |
|
David@0
|
540 |
return [AILocalizedString(@"Set Nickname", nil) stringByAppendingEllipsis]; |
|
David@0
|
541 |
} |
|
David@0
|
542 |
|
|
David@0
|
543 |
return [super titleForAccountActionMenuLabel:label]; |
|
David@0
|
544 |
} |
|
David@0
|
545 |
|
|
David@0
|
546 |
#pragma mark Multiuser chat |
|
David@0
|
547 |
/*! |
|
David@0
|
548 |
* @brief A chat will be joined |
|
David@0
|
549 |
* |
|
David@0
|
550 |
* This gives the account a chance to update any information in the chat's creation dictionary if desired. |
|
David@0
|
551 |
* |
|
David@0
|
552 |
* @result The final chat creation dictionary to use. |
|
David@0
|
553 |
*/ |
|
David@0
|
554 |
- (NSDictionary *)willJoinChatUsingDictionary:(NSDictionary *)chatCreationDictionary |
|
David@0
|
555 |
{ |
|
David@0
|
556 |
if (![[chatCreationDictionary objectForKey:@"handle"] length]) { |
|
David@0
|
557 |
NSMutableDictionary *dict = [[chatCreationDictionary mutableCopy] autorelease]; |
|
David@0
|
558 |
|
|
David@837
|
559 |
[dict setObject:self.displayName |
|
David@0
|
560 |
forKey:@"handle"]; |
|
David@0
|
561 |
|
|
David@0
|
562 |
chatCreationDictionary = dict; |
|
David@0
|
563 |
} |
|
David@0
|
564 |
|
|
David@0
|
565 |
return chatCreationDictionary; |
|
David@0
|
566 |
} |
|
David@0
|
567 |
|
|
David@0
|
568 |
- (BOOL)chatCreationDictionary:(NSDictionary *)chatCreationDict isEqualToDictionary:(NSDictionary *)baseDict |
|
David@0
|
569 |
{ |
|
David@0
|
570 |
/* If the chat isn't keeping track of a handle, it's because we added it in |
|
David@0
|
571 |
* willJoinChatUsingDictionary: above. Remove it from baseDict so the comparison is accurate. |
|
David@0
|
572 |
*/ |
|
David@0
|
573 |
if (![chatCreationDict objectForKey:@"handle"]) |
|
David@0
|
574 |
baseDict = [baseDict dictionaryWithDifferenceWithSetOfKeys:[NSSet setWithObject:@"handle"]]; |
|
David@0
|
575 |
|
|
David@0
|
576 |
return [chatCreationDict isEqualToDictionary:baseDict]; |
|
David@0
|
577 |
} |
|
David@0
|
578 |
|
|
zacw@1347
|
579 |
/*! |
|
zacw@1347
|
580 |
* @brief Do group chats support topics? |
|
zacw@1347
|
581 |
*/ |
|
zacw@1347
|
582 |
- (BOOL)groupChatsSupportTopic |
|
zacw@1347
|
583 |
{ |
|
zacw@1347
|
584 |
return YES; |
|
zacw@1347
|
585 |
} |
|
zacw@1347
|
586 |
|
|
zacw@2127
|
587 |
/*! |
|
zacw@2127
|
588 |
* @brief Return the "nickname" part of a MUC JID |
|
zacw@2127
|
589 |
* |
|
zacw@2127
|
590 |
* @param contact The AIListContact |
|
zacw@2127
|
591 |
* @param chat the AIChat |
|
zacw@2127
|
592 |
* @return The nickname for a chat participant |
|
zacw@2127
|
593 |
*/ |
|
zacw@2127
|
594 |
- (NSString *)fallbackAliasForContact:(AIListContact *)contact inChat:(AIChat *)chat |
|
zacw@2127
|
595 |
{ |
|
zacw@2127
|
596 |
if (contact.isStranger && [contact.UID.lowercaseString rangeOfString:chat.name.lowercaseString].location != NSNotFound) { |
|
zacw@2127
|
597 |
return [contact.UID substringFromIndex:[contact.UID rangeOfString:@"/"].location + 1]; |
|
zacw@2127
|
598 |
} else { |
|
zacw@2127
|
599 |
return [super fallbackAliasForContact:contact inChat:chat]; |
|
zacw@2127
|
600 |
} |
|
zacw@2127
|
601 |
} |
|
zacw@2127
|
602 |
|
|
David@0
|
603 |
#pragma mark Status |
|
David@0
|
604 |
/*! |
|
David@0
|
605 |
* @brief Return the purple status type to be used for a status |
|
David@0
|
606 |
* |
|
David@0
|
607 |
* Most subclasses should override this method; these generic values may be appropriate for others. |
|
David@0
|
608 |
* |
|
David@0
|
609 |
* Active services provided nonlocalized status names. An AIStatus is passed to this method along with a pointer |
|
David@0
|
610 |
* to the status message. This method should handle any status whose statusNname this service set as well as any statusName |
|
David@0
|
611 |
* defined in AIStatusController.h (which will correspond to the services handled by Adium by default). |
|
David@0
|
612 |
* It should also handle a status name not specified in either of these places with a sane default, most likely by loooking at |
|
David@837
|
613 |
* statusState.statusType for a general idea of the status's type. |
|
David@0
|
614 |
* |
|
David@0
|
615 |
* @param statusState The status for which to find the purple status ID |
|
David@0
|
616 |
* @param arguments Prpl-specific arguments which will be passed with the state. Message is handled automatically. |
|
David@0
|
617 |
* |
|
David@0
|
618 |
* @result The purple status ID |
|
David@0
|
619 |
*/ |
|
David@0
|
620 |
- (const char *)purpleStatusIDForStatus:(AIStatus *)statusState |
|
David@0
|
621 |
arguments:(NSMutableDictionary *)arguments |
|
David@0
|
622 |
{ |
|
David@0
|
623 |
const char *statusID = NULL; |
|
David@837
|
624 |
NSString *statusName = statusState.statusName; |
|
David@0
|
625 |
NSString *statusMessageString = [statusState statusMessageString]; |
|
David@0
|
626 |
NSNumber *priority = nil; |
|
David@0
|
627 |
|
|
David@0
|
628 |
if (!statusMessageString) statusMessageString = @""; |
|
David@0
|
629 |
|
|
David@837
|
630 |
switch (statusState.statusType) { |
|
David@0
|
631 |
case AIAvailableStatusType: |
|
David@0
|
632 |
{ |
|
David@0
|
633 |
if (([statusName isEqualToString:STATUS_NAME_FREE_FOR_CHAT]) || |
|
David@100
|
634 |
([statusMessageString caseInsensitiveCompare:[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_FREE_FOR_CHAT]] == NSOrderedSame)) |
|
David@0
|
635 |
statusID = jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_CHAT); |
|
David@0
|
636 |
priority = [self preferenceForKey:KEY_JABBER_PRIORITY_AVAILABLE group:GROUP_ACCOUNT_STATUS]; |
|
David@0
|
637 |
break; |
|
David@0
|
638 |
} |
|
David@0
|
639 |
|
|
David@0
|
640 |
case AIAwayStatusType: |
|
David@0
|
641 |
{ |
|
David@0
|
642 |
if (([statusName isEqualToString:STATUS_NAME_DND]) || |
|
David@100
|
643 |
([statusMessageString caseInsensitiveCompare:[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_DND]] == NSOrderedSame) || |
|
David@0
|
644 |
[statusName isEqualToString:STATUS_NAME_BUSY]) { |
|
David@0
|
645 |
//Note that Jabber doesn't actually support a 'busy' status; if we have it set because some other service supports it, treat it as DND |
|
David@0
|
646 |
statusID = jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_DND); |
|
David@0
|
647 |
|
|
David@0
|
648 |
} else if (([statusName isEqualToString:STATUS_NAME_EXTENDED_AWAY]) || |
|
David@100
|
649 |
([statusMessageString caseInsensitiveCompare:[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_EXTENDED_AWAY]] == NSOrderedSame)) |
|
David@0
|
650 |
statusID = jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_XA); |
|
David@0
|
651 |
priority = [self preferenceForKey:KEY_JABBER_PRIORITY_AWAY group:GROUP_ACCOUNT_STATUS]; |
|
David@0
|
652 |
break; |
|
David@0
|
653 |
} |
|
David@0
|
654 |
|
|
David@0
|
655 |
case AIInvisibleStatusType: |
|
David@0
|
656 |
AILog(@"Warning: Invisibility is not yet supported in libpurple 2.0.0 jabber"); |
|
David@0
|
657 |
priority = [self preferenceForKey:KEY_JABBER_PRIORITY_AWAY group:GROUP_ACCOUNT_STATUS]; |
|
David@0
|
658 |
statusID = jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_AWAY); |
|
David@0
|
659 |
// statusID = "Invisible"; |
|
David@0
|
660 |
break; |
|
David@0
|
661 |
|
|
David@0
|
662 |
case AIOfflineStatusType: |
|
David@0
|
663 |
break; |
|
David@0
|
664 |
} |
|
David@0
|
665 |
|
|
David@0
|
666 |
//Set our priority, which is actually set along with the status...Default is 0. |
|
David@0
|
667 |
[arguments setObject:(priority ? priority : [NSNumber numberWithInt:0]) |
|
David@0
|
668 |
forKey:@"priority"]; |
|
David@0
|
669 |
|
|
David@0
|
670 |
//We could potentially set buzz on a per-status basis. We have no UI for this, however. |
|
David@0
|
671 |
[arguments setObject:[NSNumber numberWithBool:YES] forKey:@"buzz"]; |
|
David@0
|
672 |
|
|
David@0
|
673 |
//If we didn't get a purple status ID, request one from super |
|
David@0
|
674 |
if (statusID == NULL) statusID = [super purpleStatusIDForStatus:statusState arguments:arguments]; |
|
David@0
|
675 |
|
|
David@0
|
676 |
return statusID; |
|
David@0
|
677 |
} |
|
David@0
|
678 |
|
|
David@0
|
679 |
#pragma mark Gateway Tracking |
|
David@0
|
680 |
|
|
David@703
|
681 |
- (void)addContact:(AIListContact *)theContact toGroupName:(NSString *)groupName contactName:(NSString *)contactName { |
|
David@721
|
682 |
NSRange atsign = [theContact.UID rangeOfString:@"@"]; |
|
David@0
|
683 |
if(atsign.location != NSNotFound) |
|
David@704
|
684 |
[super addContact:theContact toGroupName:groupName contactName:contactName]; |
|
David@0
|
685 |
else { |
|
David@0
|
686 |
NSDictionary *gatewaydict; |
|
David@0
|
687 |
// avoid duplicates! |
|
catfish@1821
|
688 |
for (gatewaydict in gateways) { |
|
catfish@1821
|
689 |
if([[[gatewaydict objectForKey:@"contact"] UID] isEqualToString:theContact.UID]) |
|
David@0
|
690 |
break; |
|
David@0
|
691 |
} |
|
catfish@1821
|
692 |
|
|
catfish@1821
|
693 |
if (gatewaydict) |
|
catfish@1821
|
694 |
[gateways removeObjectIdenticalTo:gatewaydict]; |
|
David@0
|
695 |
|
|
David@0
|
696 |
[gateways addObject:[NSDictionary dictionaryWithObjectsAndKeys: |
|
David@0
|
697 |
theContact, @"contact", |
|
David@0
|
698 |
groupName, @"remoteGroup", |
|
David@0
|
699 |
nil]]; |
|
David@0
|
700 |
} |
|
David@0
|
701 |
} |
|
David@0
|
702 |
|
|
David@0
|
703 |
- (void)removeContact:(AIListContact *)theContact { |
|
David@721
|
704 |
NSRange atsign = [theContact.UID rangeOfString:@"@"]; |
|
David@0
|
705 |
if(atsign.location != NSNotFound) |
|
David@0
|
706 |
[super removeContact:theContact]; |
|
David@0
|
707 |
else { |
|
David@607
|
708 |
for (NSDictionary *gatewaydict in [[gateways copy] autorelease]) { |
|
David@721
|
709 |
if([[[gatewaydict objectForKey:@"contact"] UID] isEqualToString:theContact.UID]) { |
|
David@721
|
710 |
[[self purpleAdapter] removeUID:theContact.UID onAccount:self fromGroup:[gatewaydict objectForKey:@"remoteGroup"]]; |
|
David@0
|
711 |
|
|
David@0
|
712 |
[gateways removeObjectIdenticalTo:gatewaydict]; |
|
David@0
|
713 |
break; |
|
David@0
|
714 |
} |
|
David@0
|
715 |
} |
|
David@0
|
716 |
} |
|
David@0
|
717 |
} |
|
David@0
|
718 |
|
|
David@0
|
719 |
#pragma mark XML Console, Tooltip, AdHoc Server Integration and Gateway Integration |
|
David@0
|
720 |
|
|
David@0
|
721 |
/*! |
|
David@0
|
722 |
* @brief Returns whether or not this account is connected via an encrypted connection. |
|
David@0
|
723 |
*/ |
|
David@0
|
724 |
- (BOOL)encrypted |
|
David@0
|
725 |
{ |
|
David@837
|
726 |
return (self.online && [self secureConnection]); |
|
David@0
|
727 |
} |
|
David@0
|
728 |
|
|
David@0
|
729 |
- (void)didConnect { |
|
David@0
|
730 |
[gateways release]; |
|
David@0
|
731 |
gateways = [[NSMutableArray alloc] init]; |
|
David@0
|
732 |
|
|
David@0
|
733 |
[adhocServer release]; |
|
David@0
|
734 |
adhocServer = [[AMPurpleJabberAdHocServer alloc] initWithAccount:self]; |
|
catfish@2094
|
735 |
[adhocServer addCommand:@"ping" delegate:(id<AMPurpleJabberAdHocServerDelegate>)[AMPurpleJabberAdHocPing class] name:@"Ping"]; |
|
David@0
|
736 |
|
|
David@0
|
737 |
[super didConnect]; |
|
David@0
|
738 |
|
|
David@0
|
739 |
if ([self enableXMLConsole]) { |
|
David@0
|
740 |
if (!xmlConsoleController) xmlConsoleController = [[AMXMLConsoleController alloc] init]; |
|
David@0
|
741 |
[xmlConsoleController setPurpleConnection:purple_account_get_connection(account)]; |
|
David@0
|
742 |
} |
|
David@0
|
743 |
|
|
David@0
|
744 |
discoveryBrowserController = [[AMPurpleJabberServiceDiscoveryBrowsing alloc] initWithAccount:self |
|
David@0
|
745 |
purpleConnection:purple_account_get_connection(account)]; |
|
David@0
|
746 |
} |
|
David@0
|
747 |
|
|
David@0
|
748 |
- (void)didDisconnect { |
|
David@0
|
749 |
[xmlConsoleController setPurpleConnection:NULL]; |
|
David@0
|
750 |
|
|
David@0
|
751 |
[discoveryBrowserController release]; discoveryBrowserController = nil; |
|
David@0
|
752 |
[adhocServer release]; adhocServer = nil; |
|
David@0
|
753 |
|
|
David@0
|
754 |
[super didDisconnect]; |
|
David@0
|
755 |
|
|
David@0
|
756 |
[gateways release]; gateways = nil; |
|
David@0
|
757 |
} |
|
David@0
|
758 |
|
|
David@0
|
759 |
- (IBAction)showXMLConsole:(id)sender { |
|
David@0
|
760 |
if(xmlConsoleController) |
|
David@0
|
761 |
[xmlConsoleController showWindow:sender]; |
|
David@0
|
762 |
else |
|
David@0
|
763 |
NSBeep(); |
|
David@0
|
764 |
} |
|
David@0
|
765 |
|
|
David@0
|
766 |
- (BOOL)enableXMLConsole |
|
David@0
|
767 |
{ |
|
David@0
|
768 |
BOOL enableConsole; |
|
David@0
|
769 |
#ifdef DEBUG_BUILD |
|
David@0
|
770 |
//Always enable the XML console for debug builds |
|
David@0
|
771 |
enableConsole = YES; |
|
David@0
|
772 |
#else |
|
David@0
|
773 |
//For non-debug builds, only enable it if the preference is set |
|
David@0
|
774 |
enableConsole = [[NSUserDefaults standardUserDefaults] boolForKey:@"AMXMPPShowAdvanced"]; |
|
David@0
|
775 |
#endif |
|
David@0
|
776 |
|
|
David@0
|
777 |
return enableConsole; |
|
David@0
|
778 |
} |
|
David@0
|
779 |
|
|
David@0
|
780 |
- (IBAction)showDiscoveryBrowser:(id)sender { |
|
David@0
|
781 |
[discoveryBrowserController browse:sender]; |
|
David@0
|
782 |
} |
|
David@0
|
783 |
|
|
David@0
|
784 |
- (PurpleSslConnection *)secureConnection { |
|
David@0
|
785 |
// this is really ugly |
|
David@427
|
786 |
PurpleConnection *gc = purple_account_get_connection(self.purpleAccount); |
|
David@0
|
787 |
|
|
David@427
|
788 |
return ((gc && gc->proto_data) ? ((JabberStream*)purple_account_get_connection(self.purpleAccount)->proto_data)->gsc : NULL); |
|
David@0
|
789 |
} |
|
David@0
|
790 |
|
|
David@0
|
791 |
- (void)setShouldVerifyCertificates:(BOOL)yesOrNo { |
|
David@0
|
792 |
[self setPreference:[NSNumber numberWithBool:yesOrNo] forKey:KEY_JABBER_VERIFY_CERTS group:GROUP_ACCOUNT_STATUS]; |
|
David@0
|
793 |
} |
|
David@0
|
794 |
|
|
David@0
|
795 |
- (BOOL)shouldVerifyCertificates { |
|
David@0
|
796 |
return [[self preferenceForKey:KEY_JABBER_VERIFY_CERTS group:GROUP_ACCOUNT_STATUS] boolValue]; |
|
David@0
|
797 |
} |
|
David@0
|
798 |
|
|
David@0
|
799 |
- (NSArray *)accountActionMenuItems { |
|
David@0
|
800 |
AILog(@"Getting accountActionMenuItems for %@",self); |
|
David@0
|
801 |
NSMutableArray *menu = [[NSMutableArray alloc] init]; |
|
David@0
|
802 |
|
|
David@0
|
803 |
if([gateways count] > 0) { |
|
David@0
|
804 |
NSDictionary *gatewaydict; |
|
David@75
|
805 |
for(gatewaydict in gateways) { |
|
David@0
|
806 |
AIListContact *gateway = [gatewaydict objectForKey:@"contact"]; |
|
David@837
|
807 |
NSMenuItem *mitem = [[NSMenuItem alloc] initWithTitle:gateway.UID action:@selector(registerGateway:) keyEquivalent:@""]; |
|
David@837
|
808 |
NSMenu *submenu = [[NSMenu alloc] initWithTitle:gateway.UID]; |
|
David@0
|
809 |
|
|
David@0
|
810 |
NSArray *menuitemarray = [self menuItemsForContact:gateway]; |
|
catfish@2104
|
811 |
for (NSMenuItem *m2item in menuitemarray) |
|
David@0
|
812 |
[submenu addItem:m2item]; |
|
David@0
|
813 |
|
|
David@0
|
814 |
if([submenu numberOfItems] > 0) |
|
David@0
|
815 |
[submenu addItem:[NSMenuItem separatorItem]]; |
|
David@0
|
816 |
|
|
David@0
|
817 |
NSMenuItem *removeItem = [[NSMenuItem alloc] initWithTitle:AILocalizedString(@"Remove gateway","gateway menu item") action:@selector(removeGateway:) keyEquivalent:@""]; |
|
David@0
|
818 |
[removeItem setTarget:self]; |
|
David@0
|
819 |
[removeItem setRepresentedObject:gateway]; |
|
David@0
|
820 |
[submenu addItem:removeItem]; |
|
David@0
|
821 |
[removeItem release]; |
|
David@0
|
822 |
|
|
David@0
|
823 |
[mitem setSubmenu:submenu]; |
|
David@0
|
824 |
[submenu release]; |
|
David@0
|
825 |
[mitem setRepresentedObject:gateway]; |
|
David@0
|
826 |
[mitem setImage:[AIStatusIcons statusIconForListObject:gateway |
|
David@0
|
827 |
type:AIStatusIconTab |
|
David@0
|
828 |
direction:AIIconNormal]]; |
|
David@0
|
829 |
[mitem setTarget:self]; |
|
David@0
|
830 |
[menu addObject:mitem]; |
|
David@0
|
831 |
[mitem release]; |
|
David@0
|
832 |
} |
|
David@0
|
833 |
[menu addObject:[NSMenuItem separatorItem]]; |
|
David@0
|
834 |
} |
|
David@0
|
835 |
|
|
David@0
|
836 |
NSArray *supermenu = [super accountActionMenuItems]; |
|
David@0
|
837 |
if(supermenu) { |
|
David@0
|
838 |
[menu addObjectsFromArray:supermenu]; |
|
David@0
|
839 |
[menu addObject:[NSMenuItem separatorItem]]; |
|
David@0
|
840 |
} |
|
David@0
|
841 |
|
|
David@0
|
842 |
if ([self enableXMLConsole]) { |
|
David@0
|
843 |
NSMenuItem *xmlConsoleMenuItem = [[NSMenuItem alloc] initWithTitle:AILocalizedString(@"XML Console",nil) |
|
David@0
|
844 |
action:@selector(showXMLConsole:) |
|
David@0
|
845 |
keyEquivalent:@""]; |
|
David@0
|
846 |
[xmlConsoleMenuItem setTarget:self]; |
|
David@0
|
847 |
[menu addObject:xmlConsoleMenuItem]; |
|
David@0
|
848 |
[xmlConsoleMenuItem release]; |
|
David@0
|
849 |
} |
|
David@0
|
850 |
|
|
David@0
|
851 |
NSMenuItem *discoveryBrowserMenuItem = [[NSMenuItem alloc] initWithTitle:AILocalizedString(@"Discovery Browser",nil) |
|
David@0
|
852 |
action:@selector(showDiscoveryBrowser:) |
|
David@0
|
853 |
keyEquivalent:@""]; |
|
David@0
|
854 |
[discoveryBrowserMenuItem setTarget:self]; |
|
David@0
|
855 |
[menu addObject:discoveryBrowserMenuItem]; |
|
David@0
|
856 |
[discoveryBrowserMenuItem release]; |
|
David@0
|
857 |
|
|
David@0
|
858 |
return [menu autorelease]; |
|
David@0
|
859 |
} |
|
David@0
|
860 |
|
|
David@0
|
861 |
- (void)registerGateway:(NSMenuItem*)mitem { |
|
David@0
|
862 |
if(mitem && [mitem representedObject]) |
|
David@427
|
863 |
jabber_register_gateway((JabberStream*)purple_account_get_connection(self.purpleAccount)->proto_data, [[[mitem representedObject] UID] UTF8String]); |
|
David@0
|
864 |
else |
|
David@0
|
865 |
NSBeep(); |
|
David@0
|
866 |
} |
|
David@0
|
867 |
|
|
David@0
|
868 |
- (void)removeGateway:(NSMenuItem*)mitem { |
|
David@0
|
869 |
AIListContact *gateway = [mitem representedObject]; |
|
David@0
|
870 |
if(![gateway isKindOfClass:[AIListContact class]]) |
|
David@0
|
871 |
return; |
|
David@0
|
872 |
// since this is a potentially dangerous operation, get a confirmation from the user first |
|
David@0
|
873 |
if([[NSAlert alertWithMessageText:AILocalizedString(@"Really remove gateway?",nil) |
|
David@0
|
874 |
defaultButton:AILocalizedString(@"Remove","alert default button") |
|
David@0
|
875 |
alternateButton:AILocalizedString(@"Cancel",nil) |
|
David@0
|
876 |
otherButton:nil |
|
David@837
|
877 |
informativeTextWithFormat:AILocalizedString(@"This operation would remove the gateway %@ itself and all contacts belonging to the gateway on your contact list. It cannot be undone.",nil), gateway.UID] runModal] == NSAlertDefaultReturn) { |
|
David@0
|
878 |
// first, locate all contacts on the roster that belong to this gateway |
|
David@837
|
879 |
NSString *jid = gateway.UID; |
|
David@0
|
880 |
NSString *pattern = [@"@" stringByAppendingString:jid]; |
|
David@0
|
881 |
NSMutableArray *gatewayContacts = [[NSMutableArray alloc] init]; |
|
zacw@2131
|
882 |
NSMutableSet *removeGroups = [NSMutableSet set]; |
|
catfish@2104
|
883 |
for (AIListContact *contact in self.contacts) { |
|
zacw@2131
|
884 |
if([contact.UID hasSuffix:pattern]) { |
|
David@0
|
885 |
[gatewayContacts addObject:contact]; |
|
zacw@2131
|
886 |
[removeGroups unionSet:contact.groups]; |
|
zacw@2131
|
887 |
} |
|
David@0
|
888 |
} |
|
David@0
|
889 |
// now, remove them from the roster |
|
zacw@2131
|
890 |
[self removeContacts:gatewayContacts |
|
zacw@2131
|
891 |
fromGroups:removeGroups.allObjects]; |
|
zacw@2131
|
892 |
|
|
David@0
|
893 |
[gatewayContacts release]; |
|
David@0
|
894 |
|
|
David@0
|
895 |
// finally, remove the gateway itself |
|
David@0
|
896 |
[self removeContact:gateway]; |
|
David@0
|
897 |
} |
|
David@0
|
898 |
} |
|
David@0
|
899 |
|
|
David@0
|
900 |
- (AMPurpleJabberAdHocServer*)adhocServer { |
|
David@0
|
901 |
return adhocServer; |
|
David@0
|
902 |
} |
|
David@0
|
903 |
|
|
David@0
|
904 |
@end |