Retired Document
Important: This sample code may not represent best practices for current development. The project may use deprecated symbols and illustrate technologies and techniques that are no longer recommended.
IRCServicePlugIn/IRC Framework/IRCConnection.m
/* |
File: IRCConnection.m |
Abstract: Handles the IRC connection |
Version: 1.0 |
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple |
Inc. ("Apple") in consideration of your agreement to the following |
terms, and your use, installation, modification or redistribution of |
this Apple software constitutes acceptance of these terms. If you do |
not agree with these terms, please do not use, install, modify or |
redistribute this Apple software. |
In consideration of your agreement to abide by the following terms, and |
subject to these terms, Apple grants you a personal, non-exclusive |
license, under Apple's copyrights in this original Apple software (the |
"Apple Software"), to use, reproduce, modify and redistribute the Apple |
Software, with or without modifications, in source and/or binary forms; |
provided that if you redistribute the Apple Software in its entirety and |
without modifications, you must retain this notice and the following |
text and disclaimers in all such redistributions of the Apple Software. |
Neither the name, trademarks, service marks or logos of Apple Inc. may |
be used to endorse or promote products derived from the Apple Software |
without specific prior written permission from Apple. Except as |
expressly stated in this notice, no other rights or licenses, express or |
implied, are granted by Apple herein, including but not limited to any |
patent rights that may be infringed by your derivative works or by other |
works in which the Apple Software may be incorporated. |
The Apple Software is provided by Apple on an "AS IS" basis. APPLE |
MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION |
THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS |
FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND |
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. |
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL |
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, |
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED |
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), |
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE |
POSSIBILITY OF SUCH DAMAGE. |
Copyright (C) 2011 Apple Inc. All Rights Reserved. |
*/ |
#import "IRCConnection.h" |
#import "IRCServicePlugIn.h" |
@interface IRCConnection (Internal) |
- (void) handleIncomingPRIVMSG:(NSArray *)params; |
- (void) _sendOutgoingNickServRegistration; |
- (BOOL) _handleIncomingNickServMessage:(NSData *)message; |
@end |
@implementation IRCConnection |
- (id) initWithDelegate:(id<IRCConnectionDelegate>)delegate |
{ |
if ((self = [super init])) { |
[self setDelegate:delegate]; |
_namesDictionary = [[NSMutableDictionary alloc] init]; |
_multiLineMessages = [[NSMutableDictionary alloc] init]; |
_connectionState = IRCConnectionDisconnectedState; |
} |
return self; |
} |
- (void) dealloc |
{ |
[self disconnect]; |
[_channelKeys release]; |
[_namesDictionary release]; |
[_multiLineMessages release]; |
[_originalNickname release]; |
[_host release]; |
[self setNickname:nil]; |
[self setNickServPassword:nil]; |
[self setNickServEmailAddress:nil]; |
[self setUserName:nil]; |
[self setRealName:nil]; |
[self setLastErrorMessage:nil]; |
[super dealloc]; |
} |
#pragma mark - |
#pragma mark LineStream Delegate |
- (void) lineReceived:(NSData *)line |
{ |
const char *bytes = [line bytes]; |
NSUInteger length = [line length]; |
NSUInteger i = 0; |
NSMutableString *commandString = [[NSMutableString alloc] initWithCapacity:10]; |
NSMutableArray *params = [[NSMutableArray alloc] initWithCapacity:10]; |
// We have a prefix, store it |
if (length > 0 && bytes[0] == ':') { |
for (i = 1; i < length; i++) { |
if (bytes[i] == ' ') { |
NSData *param = [[NSData alloc] initWithBytes:(void *)(bytes + 1) length:(i - 1)]; |
[params addObject:param]; |
[param release]; |
i++; |
break; |
} |
} |
} |
// Parse commandString |
for ( ; i < length; i++) { |
const char c = bytes[i]; |
if (isalnum(c)) { |
[commandString appendFormat:@"%c", c]; |
} else if (bytes[i] == ' ') { |
i++; |
break; |
} |
} |
// Parse remaining params |
BOOL isTrailingParam = NO; |
NSUInteger paramStart = i; |
for ( ; i < length; i++) { |
BOOL atEnd = (i == (length - 1)); |
if (bytes[i] == ':' && !isTrailingParam) { |
isTrailingParam = YES; |
paramStart = i + 1; |
} else if ((bytes[i] == ' ' && !isTrailingParam) || atEnd) { |
if (atEnd) i++; |
NSData *param = [[NSData alloc] initWithBytes:(void *)(bytes + paramStart) length:(i - paramStart)]; |
[params addObject:param]; |
[param release]; |
paramStart = i + 1; |
} |
} |
// Decide what selector to perform |
NSString *selectorAsString = [NSString stringWithFormat:@"handleIncoming%@:", commandString]; |
SEL selector = NSSelectorFromString(selectorAsString); |
BOOL forceLog = NO; |
if ([self respondsToSelector:selector]) { |
[self performSelector:selector withObject:params]; |
} else { |
forceLog = YES; |
} |
[_delegate connection:self logIncomingLine:line force:forceLog]; |
[commandString release]; |
[params release]; |
} |
- (void) disconnected |
{ |
if (_connectionState != IRCConnectionDisconnectedState) { |
_connectionState = IRCConnectionDisconnectedState; |
[_delegate connection:self connectionStateDidChange:IRCConnectionDisconnectedState]; |
} |
} |
#pragma mark - |
#pragma mark Private Methods |
- (NSString *) _newUserHostFromIndex:(NSUInteger)index ofParameters:(NSArray *)parameters |
{ |
return [[NSString alloc] initWithData:[parameters objectAtIndex:index] encoding:NSASCIIStringEncoding]; |
} |
- (NSString *) _newNicknameFromIndex:(NSUInteger)index ofParameters:(NSArray *)parameters |
{ |
NSString *userHost = [self _newUserHostFromIndex:index ofParameters:parameters]; |
NSString *nickname = nil; |
NSRange rangeOfBang = [userHost rangeOfString:@"!"]; |
if (rangeOfBang.location != NSNotFound) { |
nickname = [[userHost substringToIndex:rangeOfBang.location] retain]; |
[userHost release]; |
} else { |
nickname = userHost; |
} |
return nickname; |
} |
- (NSString *) _newChannelFromIndex:(NSUInteger)index ofParameters:(NSArray *)parameters |
{ |
NSData *data = [parameters objectAtIndex:index]; |
const char *bytes = [data bytes]; |
if ([data length] > 0 && (bytes[0] == '#' || bytes[0] == '&')) { |
return [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]; |
} |
return nil; |
} |
- (NSString *) _newStringFromIndex:(NSUInteger)index ofParameters:(NSArray *)parameters |
{ |
return [[NSString alloc] initWithData:[parameters objectAtIndex:index] encoding:NSASCIIStringEncoding]; |
} |
- (NSData *) _newDataFromIndex:(NSUInteger)index ofParameters:(NSArray *)parameters |
{ |
return [[parameters objectAtIndex:index] retain]; |
} |
- (void) _addValue:(NSString *)value toSetOfKey:(NSString *)key inDictionary:(NSMutableDictionary *)dictionary |
{ |
NSMutableSet *set = [dictionary objectForKey:key]; |
if (!set) { |
set = [[NSMutableSet alloc] init]; |
[dictionary setObject:set forKey:key]; |
[set release]; |
} |
[set addObject:value]; |
} |
#pragma mark - |
#pragma mark Numeric Replies |
- (void) _handleIncomingMultiLineStart:(NSString *)contentNumber withParams:(NSArray *)params |
{ |
[_multiLineMessages setObject:[NSMutableArray arrayWithCapacity:1] forKey:contentNumber]; |
} |
- (void) _handleIncomingMultiLineContent:(NSString *)contentNumber atIndex:(int)index withParams:(NSArray *)params |
{ |
if ([params count] < index) return; |
NSData *message = [self _newDataFromIndex:index ofParameters:params]; |
NSMutableArray* dataArray = [_multiLineMessages objectForKey:contentNumber]; |
if (!dataArray) |
[_multiLineMessages setObject:[NSMutableArray arrayWithCapacity:1] forKey:contentNumber]; |
[dataArray addObject:message]; |
[message release]; |
} |
- (void) _handleIncomingMultiLineEnd:(NSString *)contentNumber withParams:(NSArray *)params |
{ |
[_delegate connection:self postedMultiLineConsoleMessage:[_multiLineMessages objectForKey:contentNumber]]; |
[_multiLineMessages removeObjectForKey:contentNumber]; |
} |
- (void) handleIncoming001:(NSArray *)params // Welcome message |
{ |
if (_connectionState == IRCConnectionConnectingState) { |
_connectionState = IRCConnectionConnectedState; |
[_delegate connection:self connectionStateDidChange:IRCConnectionConnectedState]; |
} |
if (_useNickServ) { |
[self _sendOutgoingNickServRegistration]; |
} |
if ([params count] > 2) |
[self setNickname:[[self _newNicknameFromIndex:1 ofParameters:params] autorelease]]; |
} |
- (void) handleIncoming002:(NSArray *)params { } // eat this |
- (void) handleIncoming003:(NSArray *)params { } // eat this |
- (void) handleIncoming004:(NSArray *)params { } // eat this |
- (void) handleIncoming005:(NSArray *)params { } // eat this |
- (void) handleIncoming250:(NSArray *)params { } // eat this |
- (void) handleIncoming251:(NSArray *)params { } // eat this |
- (void) handleIncoming252:(NSArray *)params { } // eat this |
- (void) handleIncoming253:(NSArray *)params { } // eat this |
- (void) handleIncoming254:(NSArray *)params { } // eat this |
- (void) handleIncoming255:(NSArray *)params { } // eat this |
- (void) handleIncoming265:(NSArray *)params { } // eat this |
- (void) handleIncoming266:(NSArray *)params { } // eat this |
- (void) handleIncoming305:(NSArray *)params { } // eat this |
- (void) handleIncoming306:(NSArray *)params { } // eat this |
- (void) handleIncoming321:(NSArray *)params |
{ |
[self _handleIncomingMultiLineStart:@"322" withParams:params]; |
} |
- (void) handleIncoming322:(NSArray *)params |
{ |
[self _handleIncomingMultiLineContent:@"322" atIndex:2 withParams:params]; |
} |
- (void) handleIncoming323:(NSArray *)params |
{ |
[self _handleIncomingMultiLineEnd:@"322" withParams:params]; |
} |
- (void) handleIncoming332:(NSArray *)params // RPL_TOPIC |
{ |
if ([params count] < 4) return; |
NSString *channel = [self _newChannelFromIndex:2 ofParameters:params]; |
NSData *topic = [self _newDataFromIndex:3 ofParameters:params]; |
NSDictionary *channelProperties = [[NSDictionary alloc] initWithObjectsAndKeys:topic, IRCChannelTopicKey, nil]; |
[_delegate connection:self channel:channel initialProperties:channelProperties]; |
[channelProperties release]; |
[topic release]; |
[channel release]; |
} |
- (void) handleIncoming353:(NSArray *)params // RPL_NAMREPLY |
{ |
if ([params count] < 5) return; |
NSString *channel = [self _newChannelFromIndex:3 ofParameters:params]; |
NSString *nickList = [self _newStringFromIndex:4 ofParameters:params]; |
NSMutableDictionary *channelInformation = [_namesDictionary objectForKey:channel]; |
if (!channelInformation) { |
channelInformation = [[NSMutableDictionary alloc] init]; |
[_namesDictionary setObject:channelInformation forKey:channel]; |
[channelInformation release]; |
} |
NSArray *nicks = [nickList componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; |
for (NSString *nick in nicks) { |
if ([nick length]) { |
unichar firstCharacter = [nick characterAtIndex:0]; |
if (![[NSCharacterSet letterCharacterSet] characterIsMember:firstCharacter]) { |
nick = [nick substringFromIndex:1]; |
if (firstCharacter == IRCOperatorPrefixCharacter) { |
[self _addValue:nick toSetOfKey:IRCChannelOperatorsKey inDictionary:channelInformation]; |
} else if (firstCharacter == IRCHalfOperatorPrefixCharacter) { |
[self _addValue:nick toSetOfKey:IRCChannelHalfOperatorsKey inDictionary:channelInformation]; |
} else if (firstCharacter == IRCVoicedMemberPrefixCharacter) { |
[self _addValue:nick toSetOfKey:IRCChannelVoicedMembersKey inDictionary:channelInformation]; |
} |
} |
[self _addValue:nick toSetOfKey:IRCChannelAllMembersKey inDictionary:channelInformation]; |
} |
} |
[channel release]; |
[nickList release]; |
} |
- (void) handleIncoming366:(NSArray *)params // RPL_ENDOFNAMES |
{ |
if ([params count] < 3) return; |
NSString *channel = [self _newChannelFromIndex:2 ofParameters:params]; |
NSMutableDictionary *channelInformation = [_namesDictionary objectForKey:channel]; |
if (channelInformation) { |
[_delegate connection:self channel:channel initialProperties:channelInformation]; |
[_namesDictionary removeObjectForKey:channel]; |
} |
[channel release]; |
} |
- (void) handleIncoming375:(NSArray *)params |
{ |
[self _handleIncomingMultiLineStart:@"372" withParams:params]; |
} |
- (void) handleIncoming372:(NSArray *)params |
{ |
[self _handleIncomingMultiLineContent:@"372" atIndex:2 withParams:params]; |
} |
- (void) handleIncoming376:(NSArray *)params |
{ |
[self _handleIncomingMultiLineEnd:@"372" withParams:params]; |
} |
- (void) handleIncoming403:(NSArray *)params // ERR_NOSUCHCHANNEL |
{ |
if ([params count] < 3) return; |
NSString *channel = [self _newStringFromIndex:2 ofParameters:params]; |
[_delegate connection:self couldNotJoinChannel:channel error:IRCInvalidNameError]; |
[channel release]; |
} |
- (void) handleIncoming431:(NSArray *)params // ERR_NONICKNAMEGIVEN |
{ |
if (_connectionState == IRCConnectionConnectingState) { |
NSString *message = NSLocalizedStringFromTableInBundle(@"Your nickname contains invalid characters or is too long.", @"IRCLocalizable", [NSBundle bundleForClass: [self class]], @"Message when an invalid nick name is specifed."); |
[self setLastErrorMessage:message]; |
[self disconnect]; |
} |
} |
- (void) handleIncoming432:(NSArray *)params // ERR_ERRONEUSNICKNAME |
{ |
// Treat as ERR_NONICKNAMEGIVEN |
[self handleIncoming431:params]; |
} |
- (void) handleIncoming433:(NSArray *)params // ERR_NICKNAMEINUSE |
{ |
if ([params count] < 3) return; |
NSString *nick = [self _newChannelFromIndex:2 ofParameters:params]; |
if (_connectionState == IRCConnectionConnectingState) { |
// Move _nickname to _originalNickname |
if (!_originalNickname) { |
_originalNickname = [_nickname retain]; |
} |
[self setNickname:[[self nickname] stringByAppendingString:@"_"]]; |
[self sendNICK:[self nickname]]; |
} |
[nick release]; |
} |
- (void) handleIncoming465:(NSArray *)params // ERR_YOUREBANNEDCREEP |
{ |
if (_connectionState == IRCConnectionConnectingState) { |
NSString *message = NSLocalizedStringFromTableInBundle(@"You are banned from this server.", @"IRCLocalizable", [NSBundle bundleForClass: [self class]], @"Message when a user is banned from the server."); |
[self setLastErrorMessage:message]; |
[self disconnect]; |
} |
} |
- (void) handleIncoming471:(NSArray *)params // ERR_CHANNELISFULL |
{ |
if ([params count] < 3) return; |
NSString *channel = [self _newChannelFromIndex:2 ofParameters:params]; |
[_delegate connection:self couldNotJoinChannel:channel error:IRCIsFullError]; |
[channel release]; |
} |
- (void) handleIncoming473:(NSArray *)params // ERR_INVITEONLYCHAN |
{ |
if ([params count] < 3) return; |
NSString *channel = [self _newChannelFromIndex:2 ofParameters:params]; |
[_delegate connection:self couldNotJoinChannel:channel error:IRCInviteOnlyError]; |
[channel release]; |
} |
- (void) handleIncoming474:(NSArray *)params // ERR_BANNEDFROMCHAN |
{ |
if ([params count] < 3) return; |
NSString *channel = [self _newChannelFromIndex:2 ofParameters:params]; |
[_delegate connection:self couldNotJoinChannel:channel error:IRCBannedError]; |
[channel release]; |
} |
- (void) handleIncoming475:(NSArray *)params // ERR_BADCHANNELKEY |
{ |
if ([params count] < 3) return; |
NSString *channel = [self _newChannelFromIndex:2 ofParameters:params]; |
[_delegate connection:self couldNotJoinChannel:channel error:IRCInvalidPasswordError]; |
[channel release]; |
} |
- (void) handleIncoming479:(NSArray *)params // ERR_BADCHANNAME |
{ |
if ([params count] < 3) return; |
NSString *channel = [self _newStringFromIndex:2 ofParameters:params]; |
[_delegate connection:self couldNotJoinChannel:channel error:IRCInvalidNameError]; |
[channel release]; |
} |
- (void) handleIncoming704:(NSArray *)params |
{ |
[self _handleIncomingMultiLineStart:@"705" withParams:params]; |
} |
- (void) handleIncoming705:(NSArray *)params |
{ |
[self _handleIncomingMultiLineContent:@"705" atIndex:3 withParams:params]; |
} |
- (void) handleIncoming706:(NSArray *)params |
{ |
[self _handleIncomingMultiLineEnd:@"705" withParams:params]; |
} |
#pragma mark - |
#pragma mark Command Replies |
- (void) handleIncomingERROR:(NSArray *)params |
{ |
if ([params count] < 1) |
return; |
NSString *lastErrorMessage = [self _newStringFromIndex:0 ofParameters:params]; |
[self setLastErrorMessage:lastErrorMessage]; |
[lastErrorMessage release]; |
} |
- (void) handleIncomingINVITE:(NSArray *)params |
{ |
NSString *nickname = [self _newNicknameFromIndex:0 ofParameters:params]; |
NSString *target = [self _newNicknameFromIndex:1 ofParameters:params]; |
NSString *channel = [self _newChannelFromIndex:2 ofParameters:params]; |
[_delegate connection:self nick:nickname invited:target toChannel:channel]; |
[channel release]; |
[target release]; |
[nickname release]; |
} |
- (void) handleIncomingJOIN:(NSArray *)params |
{ |
if ([params count] < 2) return; |
NSString *nickname = [self _newNicknameFromIndex:0 ofParameters:params]; |
NSString *channel = [self _newChannelFromIndex:1 ofParameters:params]; |
[_delegate connection:self nick:nickname joinedChannel:channel]; |
[channel release]; |
[nickname release]; |
} |
- (void) handleIncomingKICK:(NSArray *)params |
{ |
if ([params count] < 3) return; |
NSString *nickname = [self _newNicknameFromIndex:0 ofParameters:params]; |
NSString *channel = [self _newChannelFromIndex:1 ofParameters:params]; |
NSString *target = [self _newNicknameFromIndex:2 ofParameters:params]; |
NSData *reason = ([params count] >= 4 ? [self _newDataFromIndex:3 ofParameters:params] : nil); |
[_delegate connection:self nick:nickname kicked:target fromChannel:channel withMessage:reason]; |
[reason release]; |
[target release]; |
[channel release]; |
[nickname release]; |
} |
- (void) handleIncomingMODE:(NSArray *)params |
{ |
if ([params count] < 3) return; |
NSString *nickname = [self _newNicknameFromIndex:0 ofParameters:params]; |
if (!nickname) return; |
NSString *channel = [self _newChannelFromIndex:1 ofParameters:params]; |
if (!channel) { |
[nickname release]; |
return; |
} |
NSString *modeString = [self _newStringFromIndex:2 ofParameters:params]; |
NSMutableDictionary *channelProperties = [[NSMutableDictionary alloc] init]; |
NSMutableString *channelFlags = [[NSMutableString alloc] init]; |
NSMutableArray *stringArguments = [[NSMutableArray alloc] init]; |
BOOL isRemove = NO; |
BOOL isLimit = NO; |
NSInteger limit = 0; |
NSString *keyForStringArguments = nil; |
for (NSUInteger i = 0; i < [modeString length]; i++) { |
unichar c = [modeString characterAtIndex:i]; |
if (c == '-') isRemove = YES; |
else if (c == 'l') isLimit = YES; |
else if (c == 'b') keyForStringArguments = IRCChannelBanMasksKey; |
else if (c == 'o') keyForStringArguments = IRCChannelOperatorsKey; |
else if (c == 'h') keyForStringArguments = IRCChannelHalfOperatorsKey; |
else if (c == 'v') keyForStringArguments = IRCChannelVoicedMembersKey; |
else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { |
[channelFlags appendFormat:@"%C", c]; |
} |
} |
for (NSUInteger i = 3; i < [params count]; i++) { |
NSString *stringArgument = [self _newStringFromIndex:i ofParameters:params]; |
unichar firstCharacter = [stringArgument length] > 0 ? [stringArgument characterAtIndex:0] : 0; |
// This works since IRC nicknames cannot start with a number |
if (firstCharacter >= '0' && firstCharacter <= '9') { |
limit = [stringArgument integerValue]; |
} else { |
[stringArguments addObject:stringArgument]; |
} |
[stringArgument release]; |
} |
if ([channelFlags length]) { |
[channelProperties setObject:channelFlags forKey:IRCChannelFlagsKey]; |
} |
if (isLimit) { |
[channelProperties setObject:[NSNumber numberWithInteger:limit] forKey:IRCChannelLimitKey]; |
} |
if (keyForStringArguments) { |
[channelProperties setObject:stringArguments forKey:keyForStringArguments]; |
} |
if (!isRemove) { |
[_delegate connection:self nick:nickname addedProperties:channelProperties toChannel:channel]; |
} else { |
[_delegate connection:self nick:nickname removedProperties:channelProperties fromChannel:channel]; |
} |
[channelFlags release]; |
[stringArguments release]; |
[channelProperties release]; |
[modeString release]; |
[channel release]; |
[nickname release]; |
} |
- (void) handleIncomingNOTICE:(NSArray *)params |
{ |
[self handleIncomingPRIVMSG:params]; |
} |
- (void) handleIncomingNICK:(NSArray *)params |
{ |
if ([params count] < 2) return; |
NSString *oldNick = [self _newNicknameFromIndex:0 ofParameters:params]; |
NSString *newNick = [self _newNicknameFromIndex:1 ofParameters:params]; |
[_delegate connection:self nick:oldNick changedNickTo:newNick]; |
[self setNickname:newNick]; |
[newNick release]; |
[oldNick release]; |
} |
- (void) handleIncomingPART:(NSArray *)params |
{ |
if ([params count] < 2) return; |
NSString *nickname = [self _newNicknameFromIndex:0 ofParameters:params]; |
NSString *channel = [self _newChannelFromIndex:1 ofParameters:params]; |
[_delegate connection:self nick:nickname partedChannel:channel]; |
[channel release]; |
[nickname release]; |
} |
- (void) handleIncomingPING:(NSArray *)params |
{ |
if ([params count] < 1) return; |
NSMutableData *pong = [[NSMutableData alloc] init]; |
[pong appendBytes:"PONG " length: 5]; |
[pong appendData:[params objectAtIndex:0]]; |
[self sendLine:pong]; |
[pong release]; |
} |
- (void) handleIncomingPRIVMSG:(NSArray *)params |
{ |
if ([params count] < 3) return; |
NSString *nick = [self _newNicknameFromIndex:0 ofParameters:params]; |
NSString *to = [self _newStringFromIndex:1 ofParameters:params]; |
NSData *message = [self _newDataFromIndex:2 ofParameters:params]; |
NSUInteger length = [message length]; |
const char *b = [message bytes]; |
// NickServ |
if (_suppressNickServMessages && [_nickServPassword length] && [nick rangeOfString:@"nickserv" options:(NSAnchoredSearch | NSCaseInsensitiveSearch)].location != NSNotFound) { |
// Do nothing |
// CTCP / ACTION |
} else if (length > 1 && b[0] == 1 && b[length - 1] == 1) { |
if (b[1] == 'A' && b[2] == 'C' && b[3] == 'T' && b[4] == 'I' && b[5] == 'O' && b[6] == 'N' && b[length - 1] == 1) { |
[_delegate connection:self nick:nick sentMessage:[message subdataWithRange:NSMakeRange(7, length - 8)] to:to isAction:YES]; |
} else { |
// CTCP REQUEST Handler would go here. Implementing CTCP would expose us to flooding concerns, so just |
// leave it blank for now. |
// |
// NSString *request = [[NSString alloc] initWithBytes:(b + 1) length:(length - 2) encoding:NSASCIIStringEncoding]; |
// NSLog(@"CTCP REQUEST: %@", request); |
// [request release]; |
} |
// Normal Messages |
} else { |
[_delegate connection:self nick:nick sentMessage:message to:to isAction:NO]; |
} |
[message release]; |
[to release]; |
[nick release]; |
} |
- (void) handleIncomingTOPIC:(NSArray *)params |
{ |
NSString *nick = [self _newNicknameFromIndex:0 ofParameters:params]; |
NSString *channel = [self _newChannelFromIndex:1 ofParameters:params]; |
NSData *topic = [self _newDataFromIndex:2 ofParameters:params]; |
NSDictionary *channelProperties = [[NSDictionary alloc] initWithObjectsAndKeys:topic, IRCChannelTopicKey, nil]; |
[_delegate connection:self nick:nick addedProperties:channelProperties toChannel:channel]; |
[channelProperties release]; |
[topic release]; |
[channel release]; |
[nick release]; |
} |
#pragma mark - |
#pragma mark Outgoing Commands |
- (void) sendUserTypedCommand:(NSString *)userTypedCommand encoding:(NSStringEncoding)encoding context:(NSString *)nicknameOrChannel |
{ |
NSData *data = [userTypedCommand dataUsingEncoding:encoding]; |
data = [data subdataWithRange:NSMakeRange(1, [data length] - 1)]; |
[self sendLine:data]; |
} |
- (void) sendAWAY:(NSString *)awayMessageOrNil |
{ |
if ([awayMessageOrNil length]) { |
[self sendLine:[[NSString stringWithFormat:@"AWAY :%@", awayMessageOrNil] dataUsingEncoding:[self commandEncoding]]]; |
} else { |
[self sendLine:[@"AWAY" dataUsingEncoding:[self commandEncoding]]]; |
} |
} |
- (void) sendINVITE:(NSString *)channel to:(NSString *)nickname |
{ |
[self sendLine:[[NSString stringWithFormat:@"INVITE %@ %@", nickname, channel] dataUsingEncoding:[self commandEncoding]]]; |
} |
- (void) _addKey:(NSString *)key forChannel:(NSString *)channel |
{ |
if (!_channelKeys) |
_channelKeys = [[NSMutableDictionary alloc] initWithCapacity:1]; |
NSMutableDictionary *keysForHost = [_channelKeys objectForKey:_host]; |
if (!keysForHost) |
keysForHost = [[[NSMutableDictionary alloc] initWithCapacity:1] autorelease]; |
[keysForHost setObject:key forKey:channel]; |
[_channelKeys setObject:keysForHost forKey:_host]; |
[[NSUserDefaults standardUserDefaults] setObject:_channelKeys forKey:@"ChannelKeys"]; |
[[NSUserDefaults standardUserDefaults] synchronize]; |
} |
- (NSString *) _keyForChannel:(NSString *)channel |
{ |
if (!_channelKeys) |
_channelKeys = [[NSUserDefaults standardUserDefaults] objectForKey:@"ChannelKeys"]; |
return [[_channelKeys objectForKey:_host] objectForKey:channel]; |
} |
- (void) sendJOIN:(NSString *)channel |
{ |
NSArray *items = [channel componentsSeparatedByString:@" "]; |
if ([items count] > 1) { |
channel = [items objectAtIndex:0]; |
[self _addKey:[items objectAtIndex:0] forChannel:channel]; |
[_delegate connection:self postedConsoleMessage:[NSString stringWithFormat:@"Saving token for channel %@.", channel]]; |
} |
NSString *password = [self _keyForChannel:channel]; |
NSString *joinLine = [NSString stringWithFormat:@"JOIN %@", channel]; |
if (password) { |
joinLine = [NSString stringWithFormat:@"%@ %@", joinLine, password]; |
[_delegate connection:self postedConsoleMessage:[NSString stringWithFormat:@"Using stored token for channel %@.", channel]]; |
} |
[self sendLine:[joinLine dataUsingEncoding:[self commandEncoding]]]; |
} |
- (void) sendNICK:(NSString *)nickname |
{ |
[self sendLine:[[NSString stringWithFormat:@"NICK %@", nickname] dataUsingEncoding:[self commandEncoding]]]; |
} |
- (void) sendPART:(NSString *)channel |
{ |
[self sendLine:[[NSString stringWithFormat:@"PART %@", channel] dataUsingEncoding:[self commandEncoding]]]; |
} |
- (void) sendPRIVMSG:(NSData *)message to:(NSString *)channelOrNickname isAction:(BOOL)isAction |
{ |
NSMutableData *data = [[NSMutableData alloc] init]; |
if ([channelOrNickname caseInsensitiveCompare:@"nickserv"] == NSOrderedSame) { |
_suppressNickServMessages = NO; |
} |
[data appendData:[[NSString stringWithFormat:@"PRIVMSG %@ :", channelOrNickname] dataUsingEncoding:[self commandEncoding]]]; |
if (isAction) { |
[data appendBytes:"\001ACTION " length:8]; |
[data appendData:message]; |
[data appendBytes:"\001" length:1]; |
} else { |
[data appendData:message]; |
} |
[self sendLine:data]; |
[data release]; |
} |
- (void) sendQUIT:(NSData *)quitMessage |
{ |
NSMutableData *data = [[NSMutableData alloc] init]; |
[data appendData:[@"QUIT :" dataUsingEncoding:[self commandEncoding]]]; |
[data appendData:quitMessage]; |
[self sendLine:data]; |
[data release]; |
} |
#pragma mark - |
#pragma mark NickServ Support |
- (void) _sendNickServCommand:(NSString *)messageFormat, ... |
{ |
va_list argList; |
va_start(argList, messageFormat); |
NSMutableString *lineAsString = [[NSMutableString alloc] initWithString:@"PRIVMSG NickServ :"]; |
NSString *message = [[NSString alloc] initWithFormat:messageFormat arguments:argList]; |
[lineAsString appendString:message]; |
[self sendLine:[message dataUsingEncoding:[self commandEncoding]]]; |
[message release]; |
[lineAsString release]; |
va_end(argList); |
} |
- (void) _allowNickServMessages |
{ |
_suppressNickServMessages = NO; |
} |
- (void) _sendOutgoingNickServRegistration |
{ |
// If we have _originalNickname, our nickname was in use |
if ([_originalNickname length]) { |
[self _sendNickServCommand:@"RECOVER %@ %@", _originalNickname, _nickServPassword]; |
[self _sendNickServCommand:@"RELEASE %@ %@", _originalNickname, _nickServPassword]; |
[self setNickname:_originalNickname]; |
[self sendNICK:[self nickname]]; |
} else { |
[self _sendNickServCommand:@"IDENTIFY %@ %@", _originalNickname, _nickServPassword]; |
[self _sendNickServCommand:@"REGISTER %@ %@", _nickServPassword, _nickServEmailAddress]; |
} |
[self performSelector:@selector(_allowNickServMessages) withObject:nil afterDelay:2.0]; |
} |
#pragma mark - |
#pragma mark Public Methods |
- (void) connectToHost: (NSString *) host |
port: (UInt16) port |
password: (NSString *) serverPassword |
{ |
[self disconnect]; |
_lineStream = [[IRCLineStream alloc] init]; |
[_lineStream setDelegate:self]; |
[_lineStream connectToHost:host port:port security:nil]; |
[_host release]; |
_host = [host retain]; |
[_originalNickname release]; |
_originalNickname = nil; |
_suppressNickServMessages = YES; |
if ([serverPassword length]) { |
NSString *passString = [NSString stringWithFormat:@"PASS %@", serverPassword]; |
[self sendLine:[passString dataUsingEncoding:[self commandEncoding]]]; |
} |
[self sendNICK:[self nickname]]; |
NSString *userString = [NSString stringWithFormat:@"USER %@ 0 * :%@", [self userName], [self realName]]; |
[self sendLine:[userString dataUsingEncoding:[self commandEncoding]]]; |
if (_connectionState != IRCConnectionConnectingState) { |
_connectionState = IRCConnectionConnectingState; |
[_delegate connection:self connectionStateDidChange:IRCConnectionConnectingState]; |
} |
} |
- (void) disconnect |
{ |
[_lineStream close]; |
[_lineStream release]; |
_lineStream = nil; |
if (_connectionState != IRCConnectionDisconnectedState) { |
_connectionState = IRCConnectionDisconnectedState; |
[_delegate connection:self connectionStateDidChange:IRCConnectionDisconnectedState]; |
} |
} |
- (void) sendLine:(NSData *)line |
{ |
[_delegate connection:self logOutgoingLine:line]; |
[_lineStream sendLine:line]; |
} |
#pragma mark - |
#pragma mark Accessors |
- (NSStringEncoding) commandEncoding |
{ |
return NSUTF8StringEncoding; |
} |
- (IRCConnectionState) connectionState |
{ |
return _connectionState; |
} |
- (void) setNickname:(NSString *)nickname |
{ |
if (nickname != _nickname) { |
[_nickname release]; |
_nickname = [nickname retain]; |
} |
} |
- (NSString *)nickname |
{ |
return _nickname; |
} |
- (void) setUseNickServ:(BOOL)yn |
{ |
_useNickServ = yn; |
} |
- (BOOL) useNickServ |
{ |
return _useNickServ; |
} |
- (void) setNickServPassword:(NSString *)nickServPassword |
{ |
if (nickServPassword != _nickServPassword) { |
[_nickServPassword release]; |
_nickServPassword = [nickServPassword retain]; |
} |
} |
- (NSString *) nickServPassword |
{ |
return _nickServPassword; |
} |
- (void) setNickServEmailAddress:(NSString *)nickServEmailAddress |
{ |
if (nickServEmailAddress != _nickServEmailAddress) { |
[_nickServEmailAddress release]; |
_nickServEmailAddress = [nickServEmailAddress retain]; |
} |
} |
- (NSString *) nickServEmailAddress |
{ |
return _nickServEmailAddress; |
} |
- (void) setUserName:(NSString *)userName |
{ |
if (userName != _userName) { |
[_userName release]; |
_userName = [userName retain]; |
} |
} |
- (NSString *) userName |
{ |
return _userName; |
} |
- (void) setRealName:(NSString *)realName |
{ |
if (realName != _realName) { |
[_realName release]; |
_realName = [realName retain]; |
} |
} |
- (NSString *) realName |
{ |
return _realName; |
} |
- (void) setLastErrorMessage:(NSString *)lastErrorMessage |
{ |
if (lastErrorMessage != _lastErrorMessage) { |
[_lastErrorMessage release]; |
_lastErrorMessage = [lastErrorMessage retain]; |
} |
} |
- (NSString *) lastErrorMessage |
{ |
return _lastErrorMessage; |
} |
- (void) setDelegate:(id<IRCConnectionDelegate>)delegate |
{ |
_delegate = delegate; |
} |
- (id<IRCConnectionDelegate>) delegate |
{ |
return _delegate; |
} |
@end |
Copyright © 2011 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2011-06-08