Plugins/Purple Service/ESIRCAccount.m
author Zachary West <zacw@adium.im>
Sat Oct 31 22:42:41 2009 -0400 (2009-10-31)
changeset 2843 5784d39a3ed5
parent 2275 ad85d31457ce
child 2844 ecd301a427cc
permissions -rw-r--r--
Never send an autoreply to an IRC message. The server does this for us. Fixes #12621.
Evan@227
     1
//
Evan@227
     2
//  ESIRCAccount.m
Evan@227
     3
//  Adium
Evan@227
     4
//
Evan@227
     5
//  Created by Evan Schoenberg on 3/4/06.
Evan@227
     6
//  Copyright 2006 The Adium Team. All rights reserved.
Evan@227
     7
//
Evan@227
     8
Evan@227
     9
#import "ESIRCAccount.h"
Evan@227
    10
#import <Adium/AIHTMLDecoder.h>
Evan@361
    11
#import <Adium/AIChat.h>
Evan@227
    12
#import <Adium/AIContentMessage.h>
zacw@1612
    13
#import <Adium/AIListContact.h>
zacw@1612
    14
#import <Adium/AIMenuControllerProtocol.h>
zacw@1612
    15
#import "AIMessageViewController.h"
zacw@1612
    16
#import <AIUtilities/AIMenuAdditions.h>
Evan@227
    17
#import <AIUtilities/AIAttributedStringAdditions.h>
zacw@1301
    18
#import <libpurple/irc.h>
zacw@1390
    19
#import <libpurple/cmds.h>
zacw@1612
    20
#import "SLPurpleCocoaAdapter.h"
Evan@227
    21
Evan@227
    22
@interface SLPurpleCocoaAdapter ()
Evan@227
    23
- (BOOL)attemptPurpleCommandOnMessage:(NSString *)originalMessage fromAccount:(AIAccount *)sourceAccount inChat:(AIChat *)chat;
Evan@227
    24
@end
Evan@227
    25
zacw@1374
    26
@interface ESIRCAccount()
zacw@1374
    27
- (void)sendRawCommand:(NSString *)command;
zacw@1612
    28
- (void)apply:(BOOL)apply operation:(NSString *)operation flag:(NSString *)flag;
zacw@1374
    29
@end
zacw@1374
    30
zacw@1390
    31
static PurpleConversation *fakeConversation(PurpleAccount *account);
zacw@1390
    32
Evan@227
    33
@implementation ESIRCAccount
Evan@227
    34
zacw@1391
    35
/*!
zacw@1391
    36
 * @brief Our explicit formatted UID contains our hostname, so we can differentiate ourself.
zacw@1391
    37
 */
zacw@1114
    38
- (NSString *)explicitFormattedUID
Evan@227
    39
{
zacw@1391
    40
	if (self.host) {
zacw@1391
    41
		return [NSString stringWithFormat:@"%@ (%@)", self.host, self.displayName];
zacw@1391
    42
	} else {
zacw@1391
    43
		return self.displayName;
zacw@1391
    44
	}
Evan@227
    45
}
Evan@227
    46
zacw@1392
    47
#pragma mark IRC-ism overloads
Evan@227
    48
zacw@963
    49
/*!
zacw@963
    50
 * @brief We always want to autocomplete the UID.
zacw@963
    51
 */
zacw@963
    52
- (BOOL)chatShouldAutocompleteUID:(AIChat *)inChat
zacw@963
    53
{
zacw@963
    54
	return YES;
zacw@963
    55
}
zacw@963
    56
zacw@1221
    57
/*!
zacw@1221
    58
 * @brief Use the object ID for password name
zacw@1221
    59
 *
zacw@1221
    60
 * We mess around a lot with the UID. This lets it actually save right.
zacw@1221
    61
 */
zacw@1221
    62
- (BOOL)useInternalObjectIDForPasswordName
zacw@1221
    63
{
zacw@1221
    64
	return YES;
zacw@1221
    65
}
zacw@1221
    66
zacw@1396
    67
- (BOOL)openChat:(AIChat *)chat
zacw@1396
    68
{
zacw@1396
    69
	chat.hideUserIconAndStatus = YES;
zacw@1396
    70
	
zacw@1396
    71
	return [super openChat:chat];
zacw@1396
    72
}
zacw@1396
    73
zacw@1899
    74
/*!
zacw@1899
    75
 * @brief Open the info inspector when getting info
zacw@1899
    76
 *
zacw@1899
    77
 * A user can /whois; we want to display info for this case.
zacw@1899
    78
 */
zacw@1899
    79
- (void)openInspectorForContactInfo:(AIListContact *)theContact
zacw@1899
    80
{
zacw@1899
    81
	[[NSNotificationCenter defaultCenter] postNotificationName:@"AIShowContactInfo" object:theContact];
zacw@1899
    82
}
zacw@1899
    83
zacw@1392
    84
#pragma mark Command handling
zacw@1391
    85
/*!
zacw@1391
    86
 * @brief We've connected
zacw@1391
    87
 *
zacw@1391
    88
 * Send the commands the user wants sent when we do so. Creates a fake conversation to pipe them through.
zacw@1391
    89
 */
zacw@1375
    90
- (void)didConnect
zacw@1375
    91
{
zacw@1375
    92
	[super didConnect];
zacw@1375
    93
	
zacw@1390
    94
	PurpleConversation *conv = fakeConversation(self.purpleAccount);
zacw@1390
    95
	
zacw@1375
    96
	for (NSString *command in [[self preferenceForKey:KEY_IRC_COMMANDS
zacw@1375
    97
												group:GROUP_ACCOUNT_STATUS] componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]]) {
zacw@1375
    98
		if ([command hasPrefix:@"/"]) {
zacw@1375
    99
			command = [command substringFromIndex:1];
zacw@1375
   100
		}
zacw@1375
   101
		
zacw@1384
   102
		command = [command stringByReplacingOccurrencesOfString:@"$me" withString:self.displayName];
zacw@1384
   103
		
zacw@1375
   104
		if (command.length) {
zacw@1390
   105
			char *error;
zacw@1390
   106
			PurpleCmdStatus cmdStatus = purple_cmd_do_command(conv, [command UTF8String], [command UTF8String], &error);
zacw@1390
   107
			
zacw@1390
   108
			if (cmdStatus == PURPLE_CMD_STATUS_NOT_FOUND) {
zacw@1390
   109
				// If it's not found, send it as a raw command like we do in chats.
zacw@1390
   110
				[self sendRawCommand:command];
zacw@1390
   111
			} else if (cmdStatus != PURPLE_CMD_STATUS_OK) {
zacw@1390
   112
				// The command failed with something other than "not found" - log it.
zacw@1390
   113
				AILogWithSignature(@"Command (%@) failed: %d - %@", command, cmdStatus, [NSString stringWithUTF8String:error]);
zacw@1390
   114
			}
zacw@1375
   115
		}
zacw@1375
   116
	}
zacw@1390
   117
	
zacw@1390
   118
	// The fakeConversation was allocated; now free it.
zacw@1390
   119
	g_free(conv);
zacw@2179
   120
	
zacw@2179
   121
	// Set a fake display name preference since we differ from global always.
zacw@2179
   122
	[self setPreference:[[NSAttributedString stringWithString:@"Adium"] dataRepresentation]
zacw@2179
   123
				 forKey:KEY_ACCOUNT_DISPLAY_NAME
zacw@2179
   124
				  group:GROUP_ACCOUNT_STATUS];
zacw@1375
   125
}
zacw@1375
   126
zacw@1391
   127
/*!
zacw@1391
   128
 * @brief Send a raw command to the IRC server.
zacw@1391
   129
 */
zacw@1374
   130
- (void)sendRawCommand:(NSString *)command
zacw@1301
   131
{
zacw@1301
   132
	PurpleConnection *connection = purple_account_get_connection(account);
zacw@1301
   133
	
zacw@1301
   134
	if (!connection)
zacw@1301
   135
		return;
zacw@1613
   136
zacw@1374
   137
	const char *quote = [command UTF8String];
zacw@1374
   138
	irc_cmd_quote(connection->proto_data, NULL, NULL, &quote);	
zacw@1373
   139
}
zacw@1301
   140
zacw@1390
   141
/*!
zacw@1390
   142
 * @brief This creates a fake PurpleConversation
zacw@1390
   143
 *
zacw@1390
   144
 * This fake conversation is used for sending purple_cmd_do_command() messages, which requires
zacw@1390
   145
 * a conversation for the command to occur. Free this when finished.
zacw@1390
   146
 *
zacw@1390
   147
 * This is taken from irchelper.c, the pidgin plugin.
zacw@1390
   148
 */
zacw@1390
   149
static PurpleConversation *fakeConversation(PurpleAccount *account)
zacw@1390
   150
{
zacw@1390
   151
	PurpleConversation *conv;
zacw@1390
   152
	
zacw@1390
   153
	conv = g_new0(PurpleConversation, 1);
zacw@1390
   154
	conv->type = PURPLE_CONV_TYPE_IM;
zacw@1390
   155
	/* If we use this then the conversation updated signal is fired and
zacw@1390
   156
	 * other plugins might start doing things to our conversation, such as
zacw@1390
   157
	 * setting data on it which we would then need to free etc. It's easier
zacw@1390
   158
	 * just to be more hacky by setting account directly. */
zacw@1390
   159
	/* purple_conversation_set_account(conv, account); */
zacw@1390
   160
	conv->account = account;
zacw@1390
   161
	
zacw@1390
   162
	return conv;
zacw@1390
   163
}
zacw@1390
   164
zacw@1392
   165
- (NSString *)encodedAttributedStringForSendingContentMessage:(AIContentMessage *)inContentMessage
zacw@1392
   166
{
zacw@1392
   167
zacw@1392
   168
	NSString	*encodedString = nil;
zacw@1392
   169
	BOOL		didCommand = [self.purpleAdapter attemptPurpleCommandOnMessage:inContentMessage.message.string
zacw@1392
   170
																   fromAccount:(AIAccount *)inContentMessage.source
zacw@1392
   171
																	    inChat:inContentMessage.chat];
zacw@1392
   172
	
zacw@1392
   173
	NSRange meRange = [inContentMessage.message.string rangeOfString:@"/me " options:NSCaseInsensitiveSearch];
zacw@1392
   174
zacw@1392
   175
	if (!didCommand || meRange.location == 0) {
zacw@1392
   176
		if (meRange.location == 0) {
zacw@1392
   177
			inContentMessage.sendContent = NO;
zacw@1392
   178
		}
zacw@1392
   179
		/* If we're sending a message on an encryption chat (can this even happen on irc?), we can encode the HTML normally, as links will go through fine.
zacw@1392
   180
		 * If we're sending a message normally, IRC will drop the title of any link, so we preprocess it to be in the form "title (link)"
zacw@1392
   181
		 */
zacw@1619
   182
		encodedString = [AIHTMLDecoder encodeHTML:(inContentMessage.chat.isSecure ? inContentMessage.message : [inContentMessage.message attributedStringByConvertingLinksToURLStrings])
zacw@1392
   183
										  headers:NO
zacw@1392
   184
										 fontTags:YES
zacw@1392
   185
							   includingColorTags:YES
zacw@1392
   186
									closeFontTags:YES
zacw@1392
   187
										styleTags:YES
zacw@1392
   188
					   closeStyleTagsOnFontChange:YES
zacw@1392
   189
								   encodeNonASCII:NO
zacw@1392
   190
									 encodeSpaces:NO
zacw@1392
   191
									   imagesPath:nil
zacw@1392
   192
								attachmentsAsText:YES
zacw@1392
   193
						onlyIncludeOutgoingImages:NO
zacw@1392
   194
								   simpleTagsOnly:YES
zacw@1392
   195
								   bodyBackground:NO
zacw@1392
   196
							  allowJavascriptURLs:YES];
zacw@1392
   197
	}
zacw@1392
   198
	
zacw@1392
   199
	if (!didCommand && [inContentMessage.message.string hasPrefix:@"/"]) {
zacw@1392
   200
		// Try to send it to the server, if we don't know what it is; definitely don't display.
zacw@1392
   201
		[self sendRawCommand:[inContentMessage.message.string substringFromIndex:1]];
zacw@1392
   202
		return nil;
zacw@1392
   203
	} else {
zacw@1392
   204
		return encodedString;
zacw@1392
   205
	}
zacw@1392
   206
}
zacw@1392
   207
zacw@1392
   208
#pragma mark Libpurple
zacw@1392
   209
- (const char *)protocolPlugin
zacw@1392
   210
{
zacw@1392
   211
	return "prpl-irc";
zacw@1392
   212
}
zacw@1392
   213
zacw@1392
   214
- (const char *)purpleAccountName
zacw@1392
   215
{
zacw@1406
   216
	return [[NSString stringWithFormat:@"%@@%@", self.formattedUID, self.host] UTF8String];
zacw@1392
   217
}
zacw@1392
   218
zacw@1413
   219
- (NSString *)defaultUsername
zacw@1413
   220
{
zacw@1413
   221
	return @"Adium";
zacw@1413
   222
}
zacw@1413
   223
zacw@1413
   224
- (NSString *)defaultRealname
zacw@1413
   225
{
zacw@1413
   226
	return AILocalizedString(@"Adium User", nil);
zacw@1413
   227
}
zacw@1413
   228
zacw@1392
   229
- (void)configurePurpleAccount
zacw@1392
   230
{
zacw@1392
   231
	[super configurePurpleAccount];
zacw@1392
   232
zacw@1392
   233
	purple_account_set_username(self.purpleAccount, self.purpleAccountName);
zacw@1392
   234
	
zacw@1455
   235
	// Encoding
zacw@1455
   236
	NSString *encoding = [self preferenceForKey:KEY_IRC_ENCODING group:GROUP_ACCOUNT_STATUS] ?: @"UTF-8";
zacw@1455
   237
	purple_account_set_string(self.purpleAccount, "encoding", [encoding UTF8String]);
zacw@1455
   238
	
zacw@1456
   239
	if (![encoding isEqualToString:@"UTF-8"]) {
zacw@1456
   240
		purple_account_set_bool(self.purpleAccount, "autodetect_utf8", TRUE);
zacw@1456
   241
	}
zacw@1456
   242
	
zacw@1392
   243
	// Use SSL
zacw@1392
   244
	BOOL useSSL = [[self preferenceForKey:KEY_IRC_USE_SSL group:GROUP_ACCOUNT_STATUS] boolValue];
zacw@1392
   245
	purple_account_set_bool(self.purpleAccount, "ssl", useSSL);
zacw@1392
   246
	
zacw@1392
   247
	// Username (for connecting)
zacw@1413
   248
	NSString *username = [self preferenceForKey:KEY_IRC_USERNAME group:GROUP_ACCOUNT_STATUS] ?: self.defaultUsername;
zacw@1413
   249
	purple_account_set_string(self.purpleAccount, "username", [username UTF8String]);
zacw@1392
   250
	
zacw@1392
   251
	// Realname (for connecting)
zacw@1413
   252
	NSString *realname = [self preferenceForKey:KEY_IRC_REALNAME group:GROUP_ACCOUNT_STATUS] ?: self.defaultRealname;
zacw@1413
   253
	purple_account_set_string(self.purpleAccount, "realname", [realname UTF8String]);
zacw@1392
   254
}
zacw@1392
   255
zacw@1392
   256
/*!
zacw@1392
   257
 * @brief Our display name; either retrieve our current nickname, or return our stored one.
zacw@1392
   258
 */
zacw@1392
   259
- (NSString *)displayName
zacw@1392
   260
{
zacw@1392
   261
	// Try and get the purple display name, since it changes without telling us.
zacw@1392
   262
	if (account) {
zacw@1392
   263
		PurpleConnection	*purpleConnection = purple_account_get_connection(account);
zacw@1392
   264
		
zacw@1392
   265
		if (purpleConnection) {
zacw@1392
   266
			return [NSString stringWithUTF8String:purple_connection_get_display_name(purpleConnection)];
zacw@1392
   267
		}
zacw@1392
   268
	}
zacw@1392
   269
	
zacw@1406
   270
	return self.formattedUID;
zacw@1392
   271
}
zacw@1392
   272
zacw@1392
   273
zacw@1392
   274
/*!
zacw@1392
   275
 * @brief Re-create the chat's join options.
zacw@1392
   276
 */
zacw@1392
   277
- (NSDictionary *)extractChatCreationDictionaryFromConversation:(PurpleConversation *)conv
zacw@1392
   278
{
zacw@1392
   279
	NSMutableDictionary *dict = [NSMutableDictionary dictionary];
zacw@1392
   280
	[dict setObject:[NSString stringWithUTF8String:purple_conversation_get_name(conv)] forKey:@"channel"];
zacw@1392
   281
	const char *pass = purple_conversation_get_data(conv, "password");
zacw@1392
   282
	if (pass)
zacw@1392
   283
		[dict setObject: [NSString stringWithUTF8String:pass] forKey:@"password"];
zacw@1392
   284
	
zacw@1392
   285
	return dict;
zacw@1392
   286
}
zacw@1392
   287
zacw@2843
   288
/*!
zacw@2843
   289
 * @brief Should an autoreply be sent to this message?
zacw@2843
   290
 */
zacw@2843
   291
- (BOOL)shouldSendAutoreplyToMessage:(AIContentMessage *)message
zacw@2843
   292
{
zacw@2843
   293
	return NO;
zacw@2843
   294
}
zacw@2843
   295
zacw@1392
   296
#pragma mark Server contacts (NickServ, ChanServ)
zacw@1392
   297
/*!
zacw@1392
   298
 * @brief Sends a raw command to identify for the nickname
zacw@1392
   299
 */
zacw@1528
   300
- (void)identifyForName:(NSString *)name password:(NSString *)inPassword
zacw@1392
   301
{
zacw@1528
   302
	if ([self.host rangeOfString:@"quakenet" options:NSCaseInsensitiveSearch].location != NSNotFound) {
zacw@1528
   303
		[self sendRawCommand:[NSString stringWithFormat:@"PRIVMSG Q@CServe.quakenet.org :AUTH %@ %@", name, inPassword]];
zacw@1528
   304
	} else if ([self.host rangeOfString:@"undernet" options:NSCaseInsensitiveSearch].location != NSNotFound) {
zacw@1528
   305
		[self sendRawCommand:[NSString stringWithFormat:@"PRIVMSG X@channels.undernet.org :LOGIN %@ %@", name, inPassword]];
zacw@1528
   306
	} else if ([self.host rangeOfString:@"gamesurge" options:NSCaseInsensitiveSearch].location != NSNotFound) {
zacw@1528
   307
		[self sendRawCommand:[NSString stringWithFormat:@"PRIVMSG AuthServ@Services.GameSurge.net :AUTH %@ %@", name, inPassword]];
zacw@1528
   308
	} else {
zacw@2275
   309
		[self sendRawCommand:[NSString stringWithFormat:@"NICKSERV identify %@", inPassword]];	
zacw@1528
   310
	}
zacw@1392
   311
}
zacw@1392
   312
zacw@1392
   313
/*!
zacw@1392
   314
 * @brief Is this contact a server contact?
zacw@1392
   315
 */
zacw@1392
   316
BOOL contactUIDIsServerContact(NSString *contactUID)
zacw@1392
   317
{
zacw@1392
   318
	return (([contactUID caseInsensitiveCompare:@"nickserv"] == NSOrderedSame) ||
zacw@1392
   319
			([contactUID caseInsensitiveCompare:@"chanserv"] == NSOrderedSame) ||
zacw@1392
   320
			([contactUID rangeOfString:@"-connect" options:(NSBackwardsSearch | NSCaseInsensitiveSearch | NSAnchoredSearch)].location != NSNotFound));
zacw@1392
   321
}
zacw@1392
   322
zacw@1392
   323
/*!
zacw@1392
   324
 * @brief Can we send an offline message to this contact?
zacw@1392
   325
 *
zacw@1392
   326
 * We can only send offline messages to the server contacts, since such a message might cause us to connect
zacw@1392
   327
 */
zacw@1392
   328
- (BOOL)canSendOfflineMessageToContact:(AIListContact *)inContact
zacw@1392
   329
{
zacw@1392
   330
	return contactUIDIsServerContact(inContact.UID);
zacw@1392
   331
}
zacw@1392
   332
zacw@1392
   333
/*!
zacw@1392
   334
 * @brief Don't autoreply to server contacts (services) or FreeNode's stupidity.
zacw@1392
   335
 */
zacw@1392
   336
- (BOOL)shouldSendAutoreplyToMessage:(AIContentMessage *)message
zacw@1392
   337
{
zacw@1392
   338
	return !contactUIDIsServerContact(message.source.UID);
zacw@1392
   339
}
zacw@1392
   340
zacw@1392
   341
/*!
zacw@1392
   342
 * @brief Don't log server contacts (services) or FreeNode's stupidity.
zacw@1392
   343
 */
zacw@1392
   344
- (BOOL)shouldLogChat:(AIChat *)chat
zacw@1392
   345
{
zacw@1392
   346
	NSString *source = chat.listObject.UID;
zacw@1392
   347
	BOOL shouldLog = YES;
zacw@1392
   348
	
zacw@1392
   349
	if (source && (([source caseInsensitiveCompare:@"nickserv"] == NSOrderedSame) ||
zacw@1392
   350
				   ([source caseInsensitiveCompare:@"chanserv"] == NSOrderedSame) ||
zacw@1392
   351
				   ([source rangeOfString:@"-connect" options:(NSBackwardsSearch | NSCaseInsensitiveSearch | NSAnchoredSearch)].location != NSNotFound))) {
zacw@1392
   352
		shouldLog = NO;	
zacw@1392
   353
	}
zacw@1392
   354
zacw@1392
   355
	return (shouldLog && [super shouldLogChat:chat]);
zacw@1392
   356
}
zacw@1392
   357
zacw@1392
   358
#pragma mark Chat handling
zacw@1392
   359
zacw@1392
   360
/*!
zacw@1392
   361
 * @brief Allow the chat to close unless we're quitting.
zacw@1392
   362
 */
zacw@1392
   363
- (BOOL)closeChat:(AIChat*)chat
zacw@1392
   364
{
zacw@1392
   365
	if(adium.isQuitting)
zacw@1392
   366
		return NO;
zacw@1392
   367
	else
zacw@1392
   368
		return [super closeChat:chat];
zacw@1392
   369
}
zacw@1392
   370
zacw@1392
   371
/*!
zacw@1392
   372
 * @brief Do group chats support topics?
zacw@1392
   373
 */
zacw@1392
   374
- (BOOL)groupChatsSupportTopic
zacw@1392
   375
{
zacw@1392
   376
	return YES;
zacw@1392
   377
}
zacw@1392
   378
zacw@1612
   379
/*!
zacw@1612
   380
 * @brief Our flags in a chat
zacw@1612
   381
 */
zacw@1612
   382
- (AIGroupChatFlags)flagsInChat:(AIChat *)chat
zacw@1612
   383
{
zacw@1612
   384
	NSString *ourUID = [NSString stringWithUTF8String:purple_normalize(self.purpleAccount, [self.displayName UTF8String])];
zacw@1612
   385
	
zacw@1612
   386
	// XXX Once we don't create a fake contact for ourself, we should do this the right way.
zacw@1612
   387
	return [chat flagsForContact:[self contactWithUID:ourUID]];
zacw@1612
   388
}
zacw@1612
   389
zacw@1600
   390
#pragma mark Action Menu
zacw@1609
   391
-(NSMenu*)actionMenuForChat:(AIChat*)chat
zacw@1600
   392
{
zacw@1612
   393
	NSMenu *menu;
zacw@1600
   394
	
zacw@1612
   395
	NSArray *listObjects = chat.chatContainer.messageViewController.selectedListObjects;
zacw@1612
   396
	AIListObject *listObject = nil;
zacw@1600
   397
	
zacw@1612
   398
	if (listObjects.count) {
zacw@1612
   399
		listObject = [listObjects objectAtIndex:0];
zacw@1612
   400
	}
zacw@1612
   401
	
zacw@1612
   402
	menu = [adium.menuController contextualMenuWithLocations:[NSArray arrayWithObjects:
zacw@1612
   403
															   [NSNumber numberWithInteger:Context_Contact_GroupChat_ParticipantAction],		
zacw@1612
   404
															   [NSNumber numberWithInteger:Context_Contact_Manage],
zacw@1612
   405
															   nil]
zacw@1612
   406
												forListObject:listObject
zacw@1612
   407
													   inChat:chat];
zacw@1612
   408
	
zacw@1612
   409
	
zacw@1612
   410
	
zacw@1612
   411
	[menu addItem:[NSMenuItem separatorItem]];
zacw@1612
   412
	
zacw@1612
   413
	[menu addItemWithTitle:AILocalizedString(@"Op", nil)
zacw@1612
   414
					target:self
zacw@1612
   415
					action:@selector(op)
zacw@1612
   416
			 keyEquivalent:@""
zacw@1612
   417
					   tag:AIRequiresOp];
zacw@1612
   418
	
zacw@1612
   419
	[menu addItemWithTitle:AILocalizedString(@"Deop", nil)
zacw@1612
   420
					target:self
zacw@1612
   421
					action:@selector(deop)
zacw@1612
   422
			 keyEquivalent:@""
zacw@1612
   423
					   tag:AIRequiresOp];
zacw@1612
   424
	
zacw@1612
   425
	[menu addItemWithTitle:AILocalizedString(@"Voice", nil)
zacw@1612
   426
					target:self
zacw@1612
   427
					action:@selector(voice)
zacw@1612
   428
			 keyEquivalent:@""
zacw@1612
   429
					   tag:AIRequiresOp];
zacw@1612
   430
	
zacw@1612
   431
	[menu addItemWithTitle:AILocalizedString(@"Devoice", nil)
zacw@1612
   432
					target:self
zacw@1612
   433
					action:@selector(devoice)
zacw@1612
   434
			 keyEquivalent:@""
zacw@1612
   435
					   tag:AIRequiresOp];
zacw@1612
   436
zacw@1612
   437
	[menu addItem:[NSMenuItem separatorItem]];
zacw@1612
   438
	
zacw@1612
   439
	[menu addItemWithTitle:AILocalizedString(@"Kick", nil)
zacw@1612
   440
					target:self
zacw@1612
   441
					action:@selector(kick)
zacw@1612
   442
			 keyEquivalent:@""
zacw@1612
   443
					   tag:AIRequiresHalfop];
zacw@1612
   444
	
zacw@1612
   445
	[menu addItemWithTitle:AILocalizedString(@"Ban", nil)
zacw@1612
   446
					target:self
zacw@1612
   447
					action:@selector(ban)
zacw@1612
   448
			 keyEquivalent:@""
zacw@1612
   449
					   tag:AIRequiresHalfop];
zacw@1612
   450
	
zacw@1612
   451
	[menu addItemWithTitle:AILocalizedString(@"Bankick", nil)
zacw@1612
   452
					target:self
zacw@1612
   453
					action:@selector(bankick)
zacw@1612
   454
			 keyEquivalent:@""
zacw@1612
   455
					   tag:AIRequiresHalfop];
zacw@1612
   456
	
zacw@1612
   457
	return menu;
zacw@1612
   458
}
zacw@1612
   459
zacw@1612
   460
- (BOOL)validateMenuItem:(NSMenuItem *)menuItem
zacw@1612
   461
{
zacw@1612
   462
	AIOperationRequirement req = menuItem.tag;
zacw@1618
   463
	AIChat *chat = adium.interfaceController.activeChat;
zacw@1618
   464
	
zacw@1618
   465
	if (!chat.chatContainer.messageViewController.selectedListObjects.count) {
zacw@1618
   466
		return NO;
zacw@1618
   467
	}
zacw@1618
   468
	
zacw@1618
   469
	AIGroupChatFlags flags = [self flagsInChat:chat];
zacw@1612
   470
	
zacw@1612
   471
	switch (req) {
zacw@1612
   472
		case AIRequiresHalfop:
zacw@1615
   473
			return ((flags & AIGroupChatOp) == AIGroupChatOp || (flags & AIGroupChatHalfOp) == AIGroupChatHalfOp);
zacw@1612
   474
			break;
zacw@1612
   475
			
zacw@1612
   476
		case AIRequiresOp:
zacw@1612
   477
			return ((flags & AIGroupChatOp) == AIGroupChatOp);
zacw@1612
   478
			break;
zacw@1612
   479
	}
zacw@1612
   480
	
zacw@1612
   481
	return NO;
zacw@1612
   482
}
zacw@1612
   483
zacw@1612
   484
#pragma mark Action Menu's Actions
zacw@1612
   485
- (void)apply:(BOOL)apply operation:(NSString *)operation flag:(NSString *)flag
zacw@1612
   486
{
zacw@1612
   487
	AIChat *chat = adium.interfaceController.activeChat;
zacw@1612
   488
	NSArray *objects = chat.chatContainer.messageViewController.selectedListObjects;
zacw@1612
   489
	
zacw@1615
   490
	NSMutableString *names = [NSMutableString string];
zacw@1612
   491
	
zacw@1615
   492
	for (NSUInteger x = 0; x < objects.count; x++) {
zacw@1746
   493
		AIListObject *listObject = [objects objectAtIndex:x];
zacw@1746
   494
		
zacw@1746
   495
		if ([flag isEqualToString:@"b"] && [listObject valueForProperty:@"User Host"]) {
zacw@1746
   496
			[names appendString:[NSString stringWithFormat:@"*!%@", [listObject valueForProperty:@"User Host"]]];
zacw@1746
   497
		} else {
zacw@1746
   498
			[names appendString:listObject.UID];
zacw@1746
   499
		}
zacw@1746
   500
		
zacw@1612
   501
		[names appendString:@" "];
zacw@1612
   502
		
zacw@1612
   503
		if ((x+1) % 4 == 0 || x+1 == objects.count) {
zacw@1612
   504
			if ([operation isEqualToString:@"MODE"]) {
zacw@1612
   505
				[self sendRawCommand:[NSString stringWithFormat:@"MODE %@ %@%@ %@",
zacw@1612
   506
									  chat.name,
zacw@1612
   507
									  (apply ? @"+" : @"-"),
zacw@1612
   508
									  [@"" stringByPaddingToLength:(x + 1) % 4 ?: 4
zacw@1612
   509
														withString:flag 
zacw@1612
   510
												   startingAtIndex:0],
zacw@1612
   511
									  names]];
zacw@1612
   512
			} else if ([operation isEqualToString:@"KICK"]) {
zacw@1612
   513
				[self sendRawCommand:[NSString stringWithFormat:@"KICK %@ %@",
zacw@1612
   514
									  chat.name,
zacw@1612
   515
									  [names stringByReplacingOccurrencesOfString:@" " withString:@","]]];
zacw@1612
   516
			}
zacw@1612
   517
			
zacw@1612
   518
			[names setString:@""];
zacw@1612
   519
		}
zacw@1612
   520
	}
zacw@1612
   521
}
zacw@1612
   522
zacw@1612
   523
- (void)op
zacw@1612
   524
{
zacw@1612
   525
	[self apply:YES operation:@"MODE" flag:@"o"];
zacw@1612
   526
}
zacw@1612
   527
zacw@1612
   528
- (void)deop
zacw@1612
   529
{
zacw@1612
   530
	[self apply:NO operation:@"MODE" flag:@"o"];
zacw@1612
   531
}
zacw@1612
   532
zacw@1612
   533
- (void)voice
zacw@1612
   534
{
zacw@1612
   535
	[self apply:YES operation:@"MODE" flag:@"v"];
zacw@1612
   536
}
zacw@1612
   537
zacw@1612
   538
- (void)devoice
zacw@1612
   539
{
zacw@1612
   540
	[self apply:NO operation:@"MODE" flag:@"v"];
zacw@1612
   541
}
zacw@1612
   542
zacw@1612
   543
- (void)kick
zacw@1612
   544
{
zacw@1612
   545
	[self apply:NO operation:@"KICK" flag:nil];
zacw@1612
   546
}
zacw@1612
   547
zacw@1612
   548
- (void)ban
zacw@1612
   549
{
zacw@1612
   550
	[self apply:YES operation:@"MODE" flag:@"b"];
zacw@1612
   551
}
zacw@1612
   552
zacw@1612
   553
- (void)bankick
zacw@1612
   554
{
zacw@1612
   555
	[self ban];
zacw@1612
   556
	[self kick];
zacw@1600
   557
}
zacw@1600
   558
zacw@1777
   559
#pragma mark File transfer
zacw@1777
   560
- (BOOL)canSendFolders
zacw@1777
   561
{
zacw@1777
   562
	return NO;
zacw@1777
   563
}
zacw@1777
   564
zacw@1777
   565
- (void)beginSendOfFileTransfer:(ESFileTransfer *)fileTransfer
zacw@1777
   566
{
zacw@1777
   567
	[super _beginSendOfFileTransfer:fileTransfer];
zacw@1777
   568
}
zacw@1777
   569
zacw@1777
   570
- (void)acceptFileTransferRequest:(ESFileTransfer *)fileTransfer
zacw@1777
   571
{
zacw@1777
   572
    [super acceptFileTransferRequest:fileTransfer];    
zacw@1777
   573
}
zacw@1777
   574
zacw@1777
   575
- (void)rejectFileReceiveRequest:(ESFileTransfer *)fileTransfer
zacw@1777
   576
{
zacw@1777
   577
    [super rejectFileReceiveRequest:fileTransfer];    
zacw@1777
   578
}
zacw@1777
   579
zacw@1777
   580
- (void)cancelFileTransfer:(ESFileTransfer *)fileTransfer
zacw@1777
   581
{
zacw@1777
   582
	[super cancelFileTransfer:fileTransfer];
zacw@1777
   583
}
zacw@1777
   584
Evan@227
   585
@end