Plugins/Purple Service/ESIRCAccount.m
author Zachary West <zacw@adium.im>
Sat Oct 31 22:58:03 2009 -0400 (2009-10-31)
changeset 2845 e47bfb289f64
parent 2844 ecd301a427cc
child 3078 1b883db24823
permissions -rw-r--r--
Allow menu items to be validated if they aren't channel operations. Fixes #13280.
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
/*!
zacw@1392
   335
 * @brief Don't log server contacts (services) or FreeNode's stupidity.
zacw@1392
   336
 */
zacw@1392
   337
- (BOOL)shouldLogChat:(AIChat *)chat
zacw@1392
   338
{
zacw@1392
   339
	NSString *source = chat.listObject.UID;
zacw@1392
   340
	BOOL shouldLog = YES;
zacw@1392
   341
	
zacw@1392
   342
	if (source && (([source caseInsensitiveCompare:@"nickserv"] == NSOrderedSame) ||
zacw@1392
   343
				   ([source caseInsensitiveCompare:@"chanserv"] == NSOrderedSame) ||
zacw@1392
   344
				   ([source rangeOfString:@"-connect" options:(NSBackwardsSearch | NSCaseInsensitiveSearch | NSAnchoredSearch)].location != NSNotFound))) {
zacw@1392
   345
		shouldLog = NO;	
zacw@1392
   346
	}
zacw@1392
   347
zacw@1392
   348
	return (shouldLog && [super shouldLogChat:chat]);
zacw@1392
   349
}
zacw@1392
   350
zacw@1392
   351
#pragma mark Chat handling
zacw@1392
   352
zacw@1392
   353
/*!
zacw@1392
   354
 * @brief Allow the chat to close unless we're quitting.
zacw@1392
   355
 */
zacw@1392
   356
- (BOOL)closeChat:(AIChat*)chat
zacw@1392
   357
{
zacw@1392
   358
	if(adium.isQuitting)
zacw@1392
   359
		return NO;
zacw@1392
   360
	else
zacw@1392
   361
		return [super closeChat:chat];
zacw@1392
   362
}
zacw@1392
   363
zacw@1392
   364
/*!
zacw@1392
   365
 * @brief Do group chats support topics?
zacw@1392
   366
 */
zacw@1392
   367
- (BOOL)groupChatsSupportTopic
zacw@1392
   368
{
zacw@1392
   369
	return YES;
zacw@1392
   370
}
zacw@1392
   371
zacw@1612
   372
/*!
zacw@1612
   373
 * @brief Our flags in a chat
zacw@1612
   374
 */
zacw@1612
   375
- (AIGroupChatFlags)flagsInChat:(AIChat *)chat
zacw@1612
   376
{
zacw@1612
   377
	NSString *ourUID = [NSString stringWithUTF8String:purple_normalize(self.purpleAccount, [self.displayName UTF8String])];
zacw@1612
   378
	
zacw@1612
   379
	// XXX Once we don't create a fake contact for ourself, we should do this the right way.
zacw@1612
   380
	return [chat flagsForContact:[self contactWithUID:ourUID]];
zacw@1612
   381
}
zacw@1612
   382
zacw@1600
   383
#pragma mark Action Menu
zacw@1609
   384
-(NSMenu*)actionMenuForChat:(AIChat*)chat
zacw@1600
   385
{
zacw@1612
   386
	NSMenu *menu;
zacw@1600
   387
	
zacw@1612
   388
	NSArray *listObjects = chat.chatContainer.messageViewController.selectedListObjects;
zacw@1612
   389
	AIListObject *listObject = nil;
zacw@1600
   390
	
zacw@1612
   391
	if (listObjects.count) {
zacw@1612
   392
		listObject = [listObjects objectAtIndex:0];
zacw@1612
   393
	}
zacw@1612
   394
	
zacw@1612
   395
	menu = [adium.menuController contextualMenuWithLocations:[NSArray arrayWithObjects:
zacw@1612
   396
															   [NSNumber numberWithInteger:Context_Contact_GroupChat_ParticipantAction],		
zacw@1612
   397
															   [NSNumber numberWithInteger:Context_Contact_Manage],
zacw@1612
   398
															   nil]
zacw@1612
   399
												forListObject:listObject
zacw@1612
   400
													   inChat:chat];
zacw@1612
   401
	
zacw@1612
   402
	
zacw@1612
   403
	
zacw@1612
   404
	[menu addItem:[NSMenuItem separatorItem]];
zacw@1612
   405
	
zacw@1612
   406
	[menu addItemWithTitle:AILocalizedString(@"Op", nil)
zacw@1612
   407
					target:self
zacw@1612
   408
					action:@selector(op)
zacw@1612
   409
			 keyEquivalent:@""
zacw@1612
   410
					   tag:AIRequiresOp];
zacw@1612
   411
	
zacw@1612
   412
	[menu addItemWithTitle:AILocalizedString(@"Deop", nil)
zacw@1612
   413
					target:self
zacw@1612
   414
					action:@selector(deop)
zacw@1612
   415
			 keyEquivalent:@""
zacw@1612
   416
					   tag:AIRequiresOp];
zacw@1612
   417
	
zacw@1612
   418
	[menu addItemWithTitle:AILocalizedString(@"Voice", nil)
zacw@1612
   419
					target:self
zacw@1612
   420
					action:@selector(voice)
zacw@1612
   421
			 keyEquivalent:@""
zacw@1612
   422
					   tag:AIRequiresOp];
zacw@1612
   423
	
zacw@1612
   424
	[menu addItemWithTitle:AILocalizedString(@"Devoice", nil)
zacw@1612
   425
					target:self
zacw@1612
   426
					action:@selector(devoice)
zacw@1612
   427
			 keyEquivalent:@""
zacw@1612
   428
					   tag:AIRequiresOp];
zacw@1612
   429
zacw@1612
   430
	[menu addItem:[NSMenuItem separatorItem]];
zacw@1612
   431
	
zacw@1612
   432
	[menu addItemWithTitle:AILocalizedString(@"Kick", nil)
zacw@1612
   433
					target:self
zacw@1612
   434
					action:@selector(kick)
zacw@1612
   435
			 keyEquivalent:@""
zacw@1612
   436
					   tag:AIRequiresHalfop];
zacw@1612
   437
	
zacw@1612
   438
	[menu addItemWithTitle:AILocalizedString(@"Ban", nil)
zacw@1612
   439
					target:self
zacw@1612
   440
					action:@selector(ban)
zacw@1612
   441
			 keyEquivalent:@""
zacw@1612
   442
					   tag:AIRequiresHalfop];
zacw@1612
   443
	
zacw@1612
   444
	[menu addItemWithTitle:AILocalizedString(@"Bankick", nil)
zacw@1612
   445
					target:self
zacw@1612
   446
					action:@selector(bankick)
zacw@1612
   447
			 keyEquivalent:@""
zacw@1612
   448
					   tag:AIRequiresHalfop];
zacw@1612
   449
	
zacw@1612
   450
	return menu;
zacw@1612
   451
}
zacw@1612
   452
zacw@1612
   453
- (BOOL)validateMenuItem:(NSMenuItem *)menuItem
zacw@1612
   454
{
zacw@1612
   455
	AIOperationRequirement req = menuItem.tag;
zacw@1618
   456
	AIChat *chat = adium.interfaceController.activeChat;
zacw@2845
   457
	BOOL anySelected = chat.chatContainer.messageViewController.selectedListObjects.count > 0;
zacw@2845
   458
		
zacw@1618
   459
	AIGroupChatFlags flags = [self flagsInChat:chat];
zacw@1612
   460
	
zacw@1612
   461
	switch (req) {
zacw@1612
   462
		case AIRequiresHalfop:
zacw@2845
   463
			return (anySelected && ((flags & AIGroupChatOp) == AIGroupChatOp || (flags & AIGroupChatHalfOp) == AIGroupChatHalfOp));
zacw@1612
   464
			break;
zacw@1612
   465
			
zacw@1612
   466
		case AIRequiresOp:
zacw@2845
   467
			return (anySelected && ((flags & AIGroupChatOp) == AIGroupChatOp));
zacw@2845
   468
			break;
zacw@2845
   469
			
zacw@2845
   470
		case AIRequiresNoLevel:
zacw@2845
   471
			return anySelected;
zacw@2845
   472
			break;
zacw@2845
   473
			
zacw@2845
   474
		default:
zacw@2845
   475
			return YES;
zacw@1612
   476
			break;
zacw@1612
   477
	}
zacw@1612
   478
}
zacw@1612
   479
zacw@1612
   480
#pragma mark Action Menu's Actions
zacw@1612
   481
- (void)apply:(BOOL)apply operation:(NSString *)operation flag:(NSString *)flag
zacw@1612
   482
{
zacw@1612
   483
	AIChat *chat = adium.interfaceController.activeChat;
zacw@1612
   484
	NSArray *objects = chat.chatContainer.messageViewController.selectedListObjects;
zacw@1612
   485
	
zacw@1615
   486
	NSMutableString *names = [NSMutableString string];
zacw@1612
   487
	
zacw@1615
   488
	for (NSUInteger x = 0; x < objects.count; x++) {
zacw@1746
   489
		AIListObject *listObject = [objects objectAtIndex:x];
zacw@1746
   490
		
zacw@1746
   491
		if ([flag isEqualToString:@"b"] && [listObject valueForProperty:@"User Host"]) {
zacw@1746
   492
			[names appendString:[NSString stringWithFormat:@"*!%@", [listObject valueForProperty:@"User Host"]]];
zacw@1746
   493
		} else {
zacw@1746
   494
			[names appendString:listObject.UID];
zacw@1746
   495
		}
zacw@1746
   496
		
zacw@1612
   497
		[names appendString:@" "];
zacw@1612
   498
		
zacw@1612
   499
		if ((x+1) % 4 == 0 || x+1 == objects.count) {
zacw@1612
   500
			if ([operation isEqualToString:@"MODE"]) {
zacw@1612
   501
				[self sendRawCommand:[NSString stringWithFormat:@"MODE %@ %@%@ %@",
zacw@1612
   502
									  chat.name,
zacw@1612
   503
									  (apply ? @"+" : @"-"),
zacw@1612
   504
									  [@"" stringByPaddingToLength:(x + 1) % 4 ?: 4
zacw@1612
   505
														withString:flag 
zacw@1612
   506
												   startingAtIndex:0],
zacw@1612
   507
									  names]];
zacw@1612
   508
			} else if ([operation isEqualToString:@"KICK"]) {
zacw@1612
   509
				[self sendRawCommand:[NSString stringWithFormat:@"KICK %@ %@",
zacw@1612
   510
									  chat.name,
zacw@1612
   511
									  [names stringByReplacingOccurrencesOfString:@" " withString:@","]]];
zacw@1612
   512
			}
zacw@1612
   513
			
zacw@1612
   514
			[names setString:@""];
zacw@1612
   515
		}
zacw@1612
   516
	}
zacw@1612
   517
}
zacw@1612
   518
zacw@1612
   519
- (void)op
zacw@1612
   520
{
zacw@1612
   521
	[self apply:YES operation:@"MODE" flag:@"o"];
zacw@1612
   522
}
zacw@1612
   523
zacw@1612
   524
- (void)deop
zacw@1612
   525
{
zacw@1612
   526
	[self apply:NO operation:@"MODE" flag:@"o"];
zacw@1612
   527
}
zacw@1612
   528
zacw@1612
   529
- (void)voice
zacw@1612
   530
{
zacw@1612
   531
	[self apply:YES operation:@"MODE" flag:@"v"];
zacw@1612
   532
}
zacw@1612
   533
zacw@1612
   534
- (void)devoice
zacw@1612
   535
{
zacw@1612
   536
	[self apply:NO operation:@"MODE" flag:@"v"];
zacw@1612
   537
}
zacw@1612
   538
zacw@1612
   539
- (void)kick
zacw@1612
   540
{
zacw@1612
   541
	[self apply:NO operation:@"KICK" flag:nil];
zacw@1612
   542
}
zacw@1612
   543
zacw@1612
   544
- (void)ban
zacw@1612
   545
{
zacw@1612
   546
	[self apply:YES operation:@"MODE" flag:@"b"];
zacw@1612
   547
}
zacw@1612
   548
zacw@1612
   549
- (void)bankick
zacw@1612
   550
{
zacw@1612
   551
	[self ban];
zacw@1612
   552
	[self kick];
zacw@1600
   553
}
zacw@1600
   554
zacw@1777
   555
#pragma mark File transfer
zacw@1777
   556
- (BOOL)canSendFolders
zacw@1777
   557
{
zacw@1777
   558
	return NO;
zacw@1777
   559
}
zacw@1777
   560
zacw@1777
   561
- (void)beginSendOfFileTransfer:(ESFileTransfer *)fileTransfer
zacw@1777
   562
{
zacw@1777
   563
	[super _beginSendOfFileTransfer:fileTransfer];
zacw@1777
   564
}
zacw@1777
   565
zacw@1777
   566
- (void)acceptFileTransferRequest:(ESFileTransfer *)fileTransfer
zacw@1777
   567
{
zacw@1777
   568
    [super acceptFileTransferRequest:fileTransfer];    
zacw@1777
   569
}
zacw@1777
   570
zacw@1777
   571
- (void)rejectFileReceiveRequest:(ESFileTransfer *)fileTransfer
zacw@1777
   572
{
zacw@1777
   573
    [super rejectFileReceiveRequest:fileTransfer];    
zacw@1777
   574
}
zacw@1777
   575
zacw@1777
   576
- (void)cancelFileTransfer:(ESFileTransfer *)fileTransfer
zacw@1777
   577
{
zacw@1777
   578
	[super cancelFileTransfer:fileTransfer];
zacw@1777
   579
}
zacw@1777
   580
Evan@227
   581
@end