Plugins/Purple Service/ESIRCAccount.m
author Zachary West <zacw@adiumx.com>
Thu Apr 09 02:14:33 2009 +0000 (2009-04-09)
changeset 1618 d11404141154
parent 1615 65891d9888fe
child 1619 b6e64cfa97bf
permissions -rw-r--r--
Don't validate command menu items when no list objects are selected.
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@1392
    74
#pragma mark Command handling
zacw@1391
    75
/*!
zacw@1391
    76
 * @brief We've connected
zacw@1391
    77
 *
zacw@1391
    78
 * Send the commands the user wants sent when we do so. Creates a fake conversation to pipe them through.
zacw@1391
    79
 */
zacw@1375
    80
- (void)didConnect
zacw@1375
    81
{
zacw@1375
    82
	[super didConnect];
zacw@1375
    83
	
zacw@1390
    84
	PurpleConversation *conv = fakeConversation(self.purpleAccount);
zacw@1390
    85
	
zacw@1375
    86
	for (NSString *command in [[self preferenceForKey:KEY_IRC_COMMANDS
zacw@1375
    87
												group:GROUP_ACCOUNT_STATUS] componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]]) {
zacw@1375
    88
		if ([command hasPrefix:@"/"]) {
zacw@1375
    89
			command = [command substringFromIndex:1];
zacw@1375
    90
		}
zacw@1375
    91
		
zacw@1384
    92
		command = [command stringByReplacingOccurrencesOfString:@"$me" withString:self.displayName];
zacw@1384
    93
		
zacw@1375
    94
		if (command.length) {
zacw@1390
    95
			char *error;
zacw@1390
    96
			PurpleCmdStatus cmdStatus = purple_cmd_do_command(conv, [command UTF8String], [command UTF8String], &error);
zacw@1390
    97
			
zacw@1390
    98
			if (cmdStatus == PURPLE_CMD_STATUS_NOT_FOUND) {
zacw@1390
    99
				// If it's not found, send it as a raw command like we do in chats.
zacw@1390
   100
				[self sendRawCommand:command];
zacw@1390
   101
			} else if (cmdStatus != PURPLE_CMD_STATUS_OK) {
zacw@1390
   102
				// The command failed with something other than "not found" - log it.
zacw@1390
   103
				AILogWithSignature(@"Command (%@) failed: %d - %@", command, cmdStatus, [NSString stringWithUTF8String:error]);
zacw@1390
   104
			}
zacw@1375
   105
		}
zacw@1375
   106
	}
zacw@1390
   107
	
zacw@1390
   108
	// The fakeConversation was allocated; now free it.
zacw@1390
   109
	g_free(conv);
zacw@1375
   110
}
zacw@1375
   111
zacw@1391
   112
/*!
zacw@1391
   113
 * @brief Send a raw command to the IRC server.
zacw@1391
   114
 */
zacw@1374
   115
- (void)sendRawCommand:(NSString *)command
zacw@1301
   116
{
zacw@1301
   117
	PurpleConnection *connection = purple_account_get_connection(account);
zacw@1301
   118
	
zacw@1301
   119
	if (!connection)
zacw@1301
   120
		return;
zacw@1613
   121
zacw@1374
   122
	const char *quote = [command UTF8String];
zacw@1374
   123
	irc_cmd_quote(connection->proto_data, NULL, NULL, &quote);	
zacw@1373
   124
}
zacw@1301
   125
zacw@1390
   126
/*!
zacw@1390
   127
 * @brief This creates a fake PurpleConversation
zacw@1390
   128
 *
zacw@1390
   129
 * This fake conversation is used for sending purple_cmd_do_command() messages, which requires
zacw@1390
   130
 * a conversation for the command to occur. Free this when finished.
zacw@1390
   131
 *
zacw@1390
   132
 * This is taken from irchelper.c, the pidgin plugin.
zacw@1390
   133
 */
zacw@1390
   134
static PurpleConversation *fakeConversation(PurpleAccount *account)
zacw@1390
   135
{
zacw@1390
   136
	PurpleConversation *conv;
zacw@1390
   137
	
zacw@1390
   138
	conv = g_new0(PurpleConversation, 1);
zacw@1390
   139
	conv->type = PURPLE_CONV_TYPE_IM;
zacw@1390
   140
	/* If we use this then the conversation updated signal is fired and
zacw@1390
   141
	 * other plugins might start doing things to our conversation, such as
zacw@1390
   142
	 * setting data on it which we would then need to free etc. It's easier
zacw@1390
   143
	 * just to be more hacky by setting account directly. */
zacw@1390
   144
	/* purple_conversation_set_account(conv, account); */
zacw@1390
   145
	conv->account = account;
zacw@1390
   146
	
zacw@1390
   147
	return conv;
zacw@1390
   148
}
zacw@1390
   149
zacw@1392
   150
- (NSString *)encodedAttributedStringForSendingContentMessage:(AIContentMessage *)inContentMessage
zacw@1392
   151
{
zacw@1392
   152
zacw@1392
   153
	NSString	*encodedString = nil;
zacw@1392
   154
	BOOL		didCommand = [self.purpleAdapter attemptPurpleCommandOnMessage:inContentMessage.message.string
zacw@1392
   155
																   fromAccount:(AIAccount *)inContentMessage.source
zacw@1392
   156
																	    inChat:inContentMessage.chat];
zacw@1392
   157
	
zacw@1392
   158
	NSRange meRange = [inContentMessage.message.string rangeOfString:@"/me " options:NSCaseInsensitiveSearch];
zacw@1392
   159
zacw@1392
   160
	if (!didCommand || meRange.location == 0) {
zacw@1392
   161
		if (meRange.location == 0) {
zacw@1392
   162
			inContentMessage.sendContent = NO;
zacw@1392
   163
		}
zacw@1392
   164
		/* 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
   165
		 * 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
   166
		 */
zacw@1392
   167
		encodedString = [AIHTMLDecoder encodeHTML:(inContentMessage.chat.isSecure ? inContentMessage.message : [inContentMessage.message attributedStringByConvertingLinksToStrings])
zacw@1392
   168
										  headers:NO
zacw@1392
   169
										 fontTags:YES
zacw@1392
   170
							   includingColorTags:YES
zacw@1392
   171
									closeFontTags:YES
zacw@1392
   172
										styleTags:YES
zacw@1392
   173
					   closeStyleTagsOnFontChange:YES
zacw@1392
   174
								   encodeNonASCII:NO
zacw@1392
   175
									 encodeSpaces:NO
zacw@1392
   176
									   imagesPath:nil
zacw@1392
   177
								attachmentsAsText:YES
zacw@1392
   178
						onlyIncludeOutgoingImages:NO
zacw@1392
   179
								   simpleTagsOnly:YES
zacw@1392
   180
								   bodyBackground:NO
zacw@1392
   181
							  allowJavascriptURLs:YES];
zacw@1392
   182
	}
zacw@1392
   183
	
zacw@1392
   184
	if (!didCommand && [inContentMessage.message.string hasPrefix:@"/"]) {
zacw@1392
   185
		// Try to send it to the server, if we don't know what it is; definitely don't display.
zacw@1392
   186
		[self sendRawCommand:[inContentMessage.message.string substringFromIndex:1]];
zacw@1392
   187
		return nil;
zacw@1392
   188
	} else {
zacw@1392
   189
		return encodedString;
zacw@1392
   190
	}
zacw@1392
   191
}
zacw@1392
   192
zacw@1392
   193
#pragma mark Libpurple
zacw@1392
   194
- (const char *)protocolPlugin
zacw@1392
   195
{
zacw@1392
   196
	return "prpl-irc";
zacw@1392
   197
}
zacw@1392
   198
zacw@1392
   199
- (const char *)purpleAccountName
zacw@1392
   200
{
zacw@1406
   201
	return [[NSString stringWithFormat:@"%@@%@", self.formattedUID, self.host] UTF8String];
zacw@1392
   202
}
zacw@1392
   203
zacw@1413
   204
- (NSString *)defaultUsername
zacw@1413
   205
{
zacw@1413
   206
	return @"Adium";
zacw@1413
   207
}
zacw@1413
   208
zacw@1413
   209
- (NSString *)defaultRealname
zacw@1413
   210
{
zacw@1413
   211
	return AILocalizedString(@"Adium User", nil);
zacw@1413
   212
}
zacw@1413
   213
zacw@1392
   214
- (void)configurePurpleAccount
zacw@1392
   215
{
zacw@1392
   216
	[super configurePurpleAccount];
zacw@1392
   217
zacw@1392
   218
	purple_account_set_username(self.purpleAccount, self.purpleAccountName);
zacw@1392
   219
	
zacw@1455
   220
	// Encoding
zacw@1455
   221
	NSString *encoding = [self preferenceForKey:KEY_IRC_ENCODING group:GROUP_ACCOUNT_STATUS] ?: @"UTF-8";
zacw@1455
   222
	purple_account_set_string(self.purpleAccount, "encoding", [encoding UTF8String]);
zacw@1455
   223
	
zacw@1456
   224
	if (![encoding isEqualToString:@"UTF-8"]) {
zacw@1456
   225
		purple_account_set_bool(self.purpleAccount, "autodetect_utf8", TRUE);
zacw@1456
   226
	}
zacw@1456
   227
	
zacw@1392
   228
	// Use SSL
zacw@1392
   229
	BOOL useSSL = [[self preferenceForKey:KEY_IRC_USE_SSL group:GROUP_ACCOUNT_STATUS] boolValue];
zacw@1392
   230
	purple_account_set_bool(self.purpleAccount, "ssl", useSSL);
zacw@1392
   231
	
zacw@1392
   232
	// Username (for connecting)
zacw@1413
   233
	NSString *username = [self preferenceForKey:KEY_IRC_USERNAME group:GROUP_ACCOUNT_STATUS] ?: self.defaultUsername;
zacw@1413
   234
	purple_account_set_string(self.purpleAccount, "username", [username UTF8String]);
zacw@1392
   235
	
zacw@1392
   236
	// Realname (for connecting)
zacw@1413
   237
	NSString *realname = [self preferenceForKey:KEY_IRC_REALNAME group:GROUP_ACCOUNT_STATUS] ?: self.defaultRealname;
zacw@1413
   238
	purple_account_set_string(self.purpleAccount, "realname", [realname UTF8String]);
zacw@1392
   239
}
zacw@1392
   240
zacw@1392
   241
/*!
zacw@1392
   242
 * @brief Our display name; either retrieve our current nickname, or return our stored one.
zacw@1392
   243
 */
zacw@1392
   244
- (NSString *)displayName
zacw@1392
   245
{
zacw@1392
   246
	// Try and get the purple display name, since it changes without telling us.
zacw@1392
   247
	if (account) {
zacw@1392
   248
		PurpleConnection	*purpleConnection = purple_account_get_connection(account);
zacw@1392
   249
		
zacw@1392
   250
		if (purpleConnection) {
zacw@1392
   251
			return [NSString stringWithUTF8String:purple_connection_get_display_name(purpleConnection)];
zacw@1392
   252
		}
zacw@1392
   253
	}
zacw@1392
   254
	
zacw@1406
   255
	return self.formattedUID;
zacw@1392
   256
}
zacw@1392
   257
zacw@1392
   258
zacw@1392
   259
/*!
zacw@1392
   260
 * @brief Re-create the chat's join options.
zacw@1392
   261
 */
zacw@1392
   262
- (NSDictionary *)extractChatCreationDictionaryFromConversation:(PurpleConversation *)conv
zacw@1392
   263
{
zacw@1392
   264
	NSMutableDictionary *dict = [NSMutableDictionary dictionary];
zacw@1392
   265
	[dict setObject:[NSString stringWithUTF8String:purple_conversation_get_name(conv)] forKey:@"channel"];
zacw@1392
   266
	const char *pass = purple_conversation_get_data(conv, "password");
zacw@1392
   267
	if (pass)
zacw@1392
   268
		[dict setObject: [NSString stringWithUTF8String:pass] forKey:@"password"];
zacw@1392
   269
	
zacw@1392
   270
	return dict;
zacw@1392
   271
}
zacw@1392
   272
zacw@1392
   273
#pragma mark Server contacts (NickServ, ChanServ)
zacw@1392
   274
/*!
zacw@1392
   275
 * @brief Sends a raw command to identify for the nickname
zacw@1392
   276
 */
zacw@1528
   277
- (void)identifyForName:(NSString *)name password:(NSString *)inPassword
zacw@1392
   278
{
zacw@1528
   279
	if ([self.host rangeOfString:@"quakenet" options:NSCaseInsensitiveSearch].location != NSNotFound) {
zacw@1528
   280
		[self sendRawCommand:[NSString stringWithFormat:@"PRIVMSG Q@CServe.quakenet.org :AUTH %@ %@", name, inPassword]];
zacw@1528
   281
	} else if ([self.host rangeOfString:@"undernet" options:NSCaseInsensitiveSearch].location != NSNotFound) {
zacw@1528
   282
		[self sendRawCommand:[NSString stringWithFormat:@"PRIVMSG X@channels.undernet.org :LOGIN %@ %@", name, inPassword]];
zacw@1528
   283
	} else if ([self.host rangeOfString:@"gamesurge" options:NSCaseInsensitiveSearch].location != NSNotFound) {
zacw@1528
   284
		[self sendRawCommand:[NSString stringWithFormat:@"PRIVMSG AuthServ@Services.GameSurge.net :AUTH %@ %@", name, inPassword]];
zacw@1528
   285
	} else {
zacw@1528
   286
		[self sendRawCommand:[NSString stringWithFormat:@"NICKSERV identify %@ %@", name, inPassword]];	
zacw@1528
   287
	}
zacw@1392
   288
}
zacw@1392
   289
zacw@1392
   290
/*!
zacw@1392
   291
 * @brief Is this contact a server contact?
zacw@1392
   292
 */
zacw@1392
   293
BOOL contactUIDIsServerContact(NSString *contactUID)
zacw@1392
   294
{
zacw@1392
   295
	return (([contactUID caseInsensitiveCompare:@"nickserv"] == NSOrderedSame) ||
zacw@1392
   296
			([contactUID caseInsensitiveCompare:@"chanserv"] == NSOrderedSame) ||
zacw@1392
   297
			([contactUID rangeOfString:@"-connect" options:(NSBackwardsSearch | NSCaseInsensitiveSearch | NSAnchoredSearch)].location != NSNotFound));
zacw@1392
   298
}
zacw@1392
   299
zacw@1392
   300
/*!
zacw@1392
   301
 * @brief Can we send an offline message to this contact?
zacw@1392
   302
 *
zacw@1392
   303
 * We can only send offline messages to the server contacts, since such a message might cause us to connect
zacw@1392
   304
 */
zacw@1392
   305
- (BOOL)canSendOfflineMessageToContact:(AIListContact *)inContact
zacw@1392
   306
{
zacw@1392
   307
	return contactUIDIsServerContact(inContact.UID);
zacw@1392
   308
}
zacw@1392
   309
zacw@1392
   310
/*!
zacw@1392
   311
 * @brief Don't autoreply to server contacts (services) or FreeNode's stupidity.
zacw@1392
   312
 */
zacw@1392
   313
- (BOOL)shouldSendAutoreplyToMessage:(AIContentMessage *)message
zacw@1392
   314
{
zacw@1392
   315
	return !contactUIDIsServerContact(message.source.UID);
zacw@1392
   316
}
zacw@1392
   317
zacw@1392
   318
/*!
zacw@1392
   319
 * @brief Don't log server contacts (services) or FreeNode's stupidity.
zacw@1392
   320
 */
zacw@1392
   321
- (BOOL)shouldLogChat:(AIChat *)chat
zacw@1392
   322
{
zacw@1392
   323
	NSString *source = chat.listObject.UID;
zacw@1392
   324
	BOOL shouldLog = YES;
zacw@1392
   325
	
zacw@1392
   326
	if (source && (([source caseInsensitiveCompare:@"nickserv"] == NSOrderedSame) ||
zacw@1392
   327
				   ([source caseInsensitiveCompare:@"chanserv"] == NSOrderedSame) ||
zacw@1392
   328
				   ([source rangeOfString:@"-connect" options:(NSBackwardsSearch | NSCaseInsensitiveSearch | NSAnchoredSearch)].location != NSNotFound))) {
zacw@1392
   329
		shouldLog = NO;	
zacw@1392
   330
	}
zacw@1392
   331
zacw@1392
   332
	return (shouldLog && [super shouldLogChat:chat]);
zacw@1392
   333
}
zacw@1392
   334
zacw@1392
   335
#pragma mark Chat handling
zacw@1392
   336
zacw@1392
   337
/*!
zacw@1392
   338
 * @brief Allow the chat to close unless we're quitting.
zacw@1392
   339
 */
zacw@1392
   340
- (BOOL)closeChat:(AIChat*)chat
zacw@1392
   341
{
zacw@1392
   342
	if(adium.isQuitting)
zacw@1392
   343
		return NO;
zacw@1392
   344
	else
zacw@1392
   345
		return [super closeChat:chat];
zacw@1392
   346
}
zacw@1392
   347
zacw@1392
   348
/*!
zacw@1392
   349
 * @brief Do group chats support topics?
zacw@1392
   350
 */
zacw@1392
   351
- (BOOL)groupChatsSupportTopic
zacw@1392
   352
{
zacw@1392
   353
	return YES;
zacw@1392
   354
}
zacw@1392
   355
zacw@1612
   356
/*!
zacw@1612
   357
 * @brief Our flags in a chat
zacw@1612
   358
 */
zacw@1612
   359
- (AIGroupChatFlags)flagsInChat:(AIChat *)chat
zacw@1612
   360
{
zacw@1612
   361
	NSString *ourUID = [NSString stringWithUTF8String:purple_normalize(self.purpleAccount, [self.displayName UTF8String])];
zacw@1612
   362
	
zacw@1612
   363
	// XXX Once we don't create a fake contact for ourself, we should do this the right way.
zacw@1612
   364
	return [chat flagsForContact:[self contactWithUID:ourUID]];
zacw@1612
   365
}
zacw@1612
   366
zacw@1600
   367
#pragma mark Action Menu
zacw@1609
   368
-(NSMenu*)actionMenuForChat:(AIChat*)chat
zacw@1600
   369
{
zacw@1612
   370
	NSMenu *menu;
zacw@1600
   371
	
zacw@1612
   372
	NSArray *listObjects = chat.chatContainer.messageViewController.selectedListObjects;
zacw@1612
   373
	AIListObject *listObject = nil;
zacw@1600
   374
	
zacw@1612
   375
	if (listObjects.count) {
zacw@1612
   376
		listObject = [listObjects objectAtIndex:0];
zacw@1612
   377
	}
zacw@1612
   378
	
zacw@1612
   379
	menu = [adium.menuController contextualMenuWithLocations:[NSArray arrayWithObjects:
zacw@1612
   380
															   [NSNumber numberWithInteger:Context_Contact_GroupChat_ParticipantAction],		
zacw@1612
   381
															   [NSNumber numberWithInteger:Context_Contact_Manage],
zacw@1612
   382
															   nil]
zacw@1612
   383
												forListObject:listObject
zacw@1612
   384
													   inChat:chat];
zacw@1612
   385
	
zacw@1612
   386
	
zacw@1612
   387
	
zacw@1612
   388
	[menu addItem:[NSMenuItem separatorItem]];
zacw@1612
   389
	
zacw@1612
   390
	[menu addItemWithTitle:AILocalizedString(@"Op", nil)
zacw@1612
   391
					target:self
zacw@1612
   392
					action:@selector(op)
zacw@1612
   393
			 keyEquivalent:@""
zacw@1612
   394
					   tag:AIRequiresOp];
zacw@1612
   395
	
zacw@1612
   396
	[menu addItemWithTitle:AILocalizedString(@"Deop", nil)
zacw@1612
   397
					target:self
zacw@1612
   398
					action:@selector(deop)
zacw@1612
   399
			 keyEquivalent:@""
zacw@1612
   400
					   tag:AIRequiresOp];
zacw@1612
   401
	
zacw@1612
   402
	[menu addItemWithTitle:AILocalizedString(@"Voice", nil)
zacw@1612
   403
					target:self
zacw@1612
   404
					action:@selector(voice)
zacw@1612
   405
			 keyEquivalent:@""
zacw@1612
   406
					   tag:AIRequiresOp];
zacw@1612
   407
	
zacw@1612
   408
	[menu addItemWithTitle:AILocalizedString(@"Devoice", nil)
zacw@1612
   409
					target:self
zacw@1612
   410
					action:@selector(devoice)
zacw@1612
   411
			 keyEquivalent:@""
zacw@1612
   412
					   tag:AIRequiresOp];
zacw@1612
   413
zacw@1612
   414
	[menu addItem:[NSMenuItem separatorItem]];
zacw@1612
   415
	
zacw@1612
   416
	[menu addItemWithTitle:AILocalizedString(@"Kick", nil)
zacw@1612
   417
					target:self
zacw@1612
   418
					action:@selector(kick)
zacw@1612
   419
			 keyEquivalent:@""
zacw@1612
   420
					   tag:AIRequiresHalfop];
zacw@1612
   421
	
zacw@1612
   422
	[menu addItemWithTitle:AILocalizedString(@"Ban", nil)
zacw@1612
   423
					target:self
zacw@1612
   424
					action:@selector(ban)
zacw@1612
   425
			 keyEquivalent:@""
zacw@1612
   426
					   tag:AIRequiresHalfop];
zacw@1612
   427
	
zacw@1612
   428
	[menu addItemWithTitle:AILocalizedString(@"Bankick", nil)
zacw@1612
   429
					target:self
zacw@1612
   430
					action:@selector(bankick)
zacw@1612
   431
			 keyEquivalent:@""
zacw@1612
   432
					   tag:AIRequiresHalfop];
zacw@1612
   433
	
zacw@1612
   434
	return menu;
zacw@1612
   435
}
zacw@1612
   436
zacw@1612
   437
- (BOOL)validateMenuItem:(NSMenuItem *)menuItem
zacw@1612
   438
{
zacw@1612
   439
	AIOperationRequirement req = menuItem.tag;
zacw@1618
   440
	AIChat *chat = adium.interfaceController.activeChat;
zacw@1618
   441
	
zacw@1618
   442
	if (!chat.chatContainer.messageViewController.selectedListObjects.count) {
zacw@1618
   443
		return NO;
zacw@1618
   444
	}
zacw@1618
   445
	
zacw@1618
   446
	AIGroupChatFlags flags = [self flagsInChat:chat];
zacw@1612
   447
	
zacw@1612
   448
	switch (req) {
zacw@1612
   449
		case AIRequiresHalfop:
zacw@1615
   450
			return ((flags & AIGroupChatOp) == AIGroupChatOp || (flags & AIGroupChatHalfOp) == AIGroupChatHalfOp);
zacw@1612
   451
			break;
zacw@1612
   452
			
zacw@1612
   453
		case AIRequiresOp:
zacw@1612
   454
			return ((flags & AIGroupChatOp) == AIGroupChatOp);
zacw@1612
   455
			break;
zacw@1612
   456
	}
zacw@1612
   457
	
zacw@1612
   458
	return NO;
zacw@1612
   459
}
zacw@1612
   460
zacw@1612
   461
#pragma mark Action Menu's Actions
zacw@1612
   462
- (void)apply:(BOOL)apply operation:(NSString *)operation flag:(NSString *)flag
zacw@1612
   463
{
zacw@1612
   464
	AIChat *chat = adium.interfaceController.activeChat;
zacw@1612
   465
	NSArray *objects = chat.chatContainer.messageViewController.selectedListObjects;
zacw@1612
   466
	
zacw@1615
   467
	NSMutableString *names = [NSMutableString string];
zacw@1612
   468
	
zacw@1615
   469
	for (NSUInteger x = 0; x < objects.count; x++) {
zacw@1612
   470
		[names appendString:((AIListObject *)[objects objectAtIndex:x]).UID];
zacw@1612
   471
		[names appendString:@" "];
zacw@1612
   472
		
zacw@1612
   473
		if ((x+1) % 4 == 0 || x+1 == objects.count) {
zacw@1612
   474
			if ([operation isEqualToString:@"MODE"]) {
zacw@1612
   475
				[self sendRawCommand:[NSString stringWithFormat:@"MODE %@ %@%@ %@",
zacw@1612
   476
									  chat.name,
zacw@1612
   477
									  (apply ? @"+" : @"-"),
zacw@1612
   478
									  [@"" stringByPaddingToLength:(x + 1) % 4 ?: 4
zacw@1612
   479
														withString:flag 
zacw@1612
   480
												   startingAtIndex:0],
zacw@1612
   481
									  names]];
zacw@1612
   482
			} else if ([operation isEqualToString:@"KICK"]) {
zacw@1612
   483
				[self sendRawCommand:[NSString stringWithFormat:@"KICK %@ %@",
zacw@1612
   484
									  chat.name,
zacw@1612
   485
									  [names stringByReplacingOccurrencesOfString:@" " withString:@","]]];
zacw@1612
   486
			}
zacw@1612
   487
			
zacw@1612
   488
			[names setString:@""];
zacw@1612
   489
		}
zacw@1612
   490
	}
zacw@1612
   491
}
zacw@1612
   492
zacw@1612
   493
- (void)op
zacw@1612
   494
{
zacw@1612
   495
	[self apply:YES operation:@"MODE" flag:@"o"];
zacw@1612
   496
}
zacw@1612
   497
zacw@1612
   498
- (void)deop
zacw@1612
   499
{
zacw@1612
   500
	[self apply:NO operation:@"MODE" flag:@"o"];
zacw@1612
   501
}
zacw@1612
   502
zacw@1612
   503
- (void)voice
zacw@1612
   504
{
zacw@1612
   505
	[self apply:YES operation:@"MODE" flag:@"v"];
zacw@1612
   506
}
zacw@1612
   507
zacw@1612
   508
- (void)devoice
zacw@1612
   509
{
zacw@1612
   510
	[self apply:NO operation:@"MODE" flag:@"v"];
zacw@1612
   511
}
zacw@1612
   512
zacw@1612
   513
- (void)kick
zacw@1612
   514
{
zacw@1612
   515
	[self apply:NO operation:@"KICK" flag:nil];
zacw@1612
   516
}
zacw@1612
   517
zacw@1612
   518
- (void)ban
zacw@1612
   519
{
zacw@1612
   520
	[self apply:YES operation:@"MODE" flag:@"b"];
zacw@1612
   521
}
zacw@1612
   522
zacw@1612
   523
- (void)bankick
zacw@1612
   524
{
zacw@1612
   525
	[self ban];
zacw@1612
   526
	[self kick];
zacw@1600
   527
}
zacw@1600
   528
Evan@227
   529
@end