/* * Grouch.app Copyright (C) 2006 Andy Sveikauskas * ------------------------------------------------------------------------ * This program is free software under the GNU General Public License * -- * Glue that binds all the FLAP services together. * They are: * Auth Gets you your auth cookie and that's it. * BOS Most important * ChatNav Lets you "navigate" the chat rooms * Chat One socket per room */ #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import @implementation OscarClient + clientForServer:(NSString*)str atPort:(int)port withCredentials:(id)creds andUI:(id )i andTag:t { id r = [OscarClient new]; [r initForServer:str atPort:port withCredentials:creds andUI:i andTag:t]; return r; } - initForServer:(NSString*)str atPort:(int)port withCredentials:(id)creds andUI:(id )i andTag:t { NSString *host = [NSString stringWithFormat:@"%@:%i",str,port]; ui = i; [buddyList setClient:ui]; [self tag:t]; [ui statusMessage:[GrouchString getString:@"oscar-auth" withBundle:[NSBundle bundleForClass:[self class]]]]; [OscarAuth authForHost:host withCredentials:creds andClient:self]; // XXX { NSString *login = [(OscarPassword*)creds login]; NSCharacterSet *dec = [NSCharacterSet decimalDigitCharacterSet]; icq = [dec characterIsMember:[login characterAtIndex:0]]; } return self; } - init { [super init]; bos = nil; chatnav = nil; chatRooms = [NSMutableDictionary new]; ui = nil; myNick = nil; tag = nil; queue = [OscarChatNavQueue new]; buddyList = [OscarSsiList new]; return self; } - (void)dealloc { NSEnumerator *e; OscarChat *c; e = [chatRooms objectEnumerator]; while( (c=[e nextObject]) ) [c close]; if( bos ) { [bos setClient:nil]; [bos close]; [bos release]; } if( queue ) [queue release]; if( chatnav ) { [chatnav setClient:nil]; [chatnav close]; // Doesn't need a release; it releases itself. } if( chatRooms ) [chatRooms release]; if( myNick ) [myNick release]; if( tag ) [(NSObject*)tag release]; if( buddyList ) [buddyList release]; [super dealloc]; } - (id )getUI { return ui; } - (void)startBos:(NSString*)host withCookie:(id)cookie { [ui statusMessage:[GrouchString getString:@"oscar-bos" withBundle:[NSBundle bundleForClass:[self class]]]]; bos = [OscarBos bosForHost:host withCredentials:cookie andClient:self]; } - (void)welcome { [ui welcome:myNick]; } - (void)gotChatNav:(NSString*)host withCookie:(id)cookie { chatnav = [OscarChatNav chatNavForHost:host andCookie:cookie andQueue:queue andClient:self]; } - (void)chatNavDied { chatnav = nil; } - (void)askForChannel:(NSString*)name withCookie:(NSString*)cookie andExchange:(int)exchange andInstance:(int)instance { if( bos ) [bos askForChannel:name withCookie:cookie andExchange:exchange andInstance:instance]; } - tag { return tag; } - (void)tag:t { if( tag ) [(NSObject*)tag release]; [(NSObject*)(tag=t) retain]; } - (void)sendMessage:(NSString*)msg to:(NSString*)user withFlags:(GrouchMessageFlags)f { if( icq && (f&GrouchMessageAway) ) return; if( bos ) [bos sendMessage:msg to:user withFlags:f]; } - (void)profile:(NSString*)str { if( bos ) [bos profile:str]; } - (void)away:(NSString*)str { if( bos ) [bos away:str]; } - (void)idle:(time_t)i { if( bos ) [bos idle:i]; } - (void)getInfo:(id)prof forUser:(NSString*)str { if( bos ) [bos getInfo:prof forUser:str]; } - (void)getAwayMessage:(id)prof forUser:(NSString*)str { if( bos ) [bos getAwayMessage:prof forUser:str]; } - (void)joinChannel:(NSString*)chan { if( chatnav ) [chatnav createChannel:chan withExchange:4]; else { if( bos ) [bos askForChatNav]; [queue createChannel:chan withExchange:4]; } } // sent by SNAC redirect handler - (void)gotChannel:(NSString*)chan atHost:(NSString*)host withCookie:(id)cookie { [OscarChat chatForHost:host withCredentials:cookie andClient:self underName:chan]; } // sent by channel class - (void)gotChannel:(OscarChat*)chan { NSString *name = [chan name]; id c = [ui getChannel:name]; [chatRooms setObject:chan forKey:name]; [c welcome]; } // sent by UI - (void)partChannel:(NSString*)chan { OscarChat *chat = [chatRooms objectForKey:chan]; if( chat ) [chat close]; } // sent by channel class - (void)lostChannel:(OscarChat*)chan { NSString *name = [chan name]; id c = [ui getChannel:name]; [chatRooms removeObjectForKey:name]; [c bye]; } - (void)sendMessage:(NSString*)msg toChannel:(NSString*)chan withFlags:(GrouchMessageFlags)fl { OscarChat *chat = [chatRooms objectForKey:chan]; if( chat ) [chat sendMessage:msg withFlags:fl]; } - (void)lostBos { bos = nil; [ui bye]; } // Sent by UI. - (NSArray*)getContactList:(BOOL)offline { return [buddyList getContactList:icq?YES:offline]; } - (OscarCapHandler*)capHandler { return [OscarCapHandler sharedInstance]; } - (OscarSsiList*)buddyList { return buddyList; } - (void)addUser:(NSString*)userString toGroup:(NSString*)group { id user = nil; NSMutableArray *records = nil; NSArray *groups = nil; OscarSsiRecord *groupRecord = nil; int i; if( userString ) user = [ui getUser:userString]; records = [NSMutableArray array]; groups = [buddyList findRecordsOfType:OscarSsiGroup]; // Find the relevant group for( i=0; i<[groups count]; ++i ) { OscarSsiRecord *groupR = [groups objectAtIndex:i]; // While we're at it, let's see if the user specified is // already in the list. That way we can tell the user // they can't add them twice. if(user) { NSArray *children = [groupR children]; int j; for( j=0; j<[children count]; ++j ) { OscarSsiRecord*child=[children objectAtIndex:0]; if( user == [child description] ) { NSString *msg; msg = [GrouchString getString: @"redundant-add" withBundle:[NSBundle bundleForClass:[self class]]]; [ui error:msg fatal:NO]; user = nil; } } } if( [[groupR description] isEqual:group] ) groupRecord = groupR; } // We're going to pretend like the add operation was successful // and add SSI records to our local copy of the buddy list. // If the operation fails, we'll remove them later. // This is a good way to do things because then our GID and item // fields will be correct if the user tries to add more buddies // before the previous operation finishes. // If the group was not found, this is new. if( !groupRecord ) { groupRecord = [[OscarSsiRecord new] autorelease]; [groupRecord setDescription:group]; [groupRecord setType:OscarSsiGroup]; [groupRecord setGid:[buddyList nextGroup]]; [groupRecord setParent:buddyList]; [[groupRecord parent] addChild:groupRecord]; [buddyList setUp:groupRecord]; [records addObject:groupRecord]; } // Are we creating a user entry, too? if( user && ![buddyList containsUser:user] ) { OscarSsiRecord *userRecord = [[OscarSsiRecord new] autorelease]; int gid = [groupRecord gid]; [userRecord setDescription:user]; [userRecord setType:OscarSsiUser]; [userRecord setGid:gid]; [userRecord setItem:[buddyList nextBuddyForGroup:gid]]; [userRecord setParent:groupRecord]; [[userRecord parent] addChild:userRecord]; [buddyList setUp:userRecord]; [records addObject:userRecord]; } if( [records count] ) [bos addSsiRecords:records]; } // XXX deprecated - (void)setAlias:(NSString*)alias forUser:(id)user { [buddyList setAlias:alias forUser:user withBos:bos]; } - (void)rename:obj to:(NSString*)newName { if( [obj conformsToProtocol:@protocol(GrouchUser)] ) [buddyList setAlias:newName forUser:obj withBos:bos]; else if( [obj isKindOfClass:[NSArray class]] ) { OscarSsiRecord *relevantGroup = nil; NSArray *groups = [buddyList findRecordsOfType:OscarSsiGroup]; NSString *oldName = [(NSArray*)obj objectAtIndex:0]; int i; if( [newName isEqual:oldName] ) return; for( i=0; i<[groups count]; ++i ) { OscarSsiRecord *group = [groups objectAtIndex:i]; NSString *name = [group description]; if( [name isEqual:newName] ) { [ui error: [GrouchString getString:@"group-exists" withBundle: [NSBundle bundleForClass:[self class]]] fatal:NO]; return; } if( [name isEqual:oldName] ) relevantGroup = group; } if( relevantGroup ) { [relevantGroup setDescription:newName]; [bos updateSsiRecords:[NSArray arrayWithObject: relevantGroup]]; [ui reloadData:YES]; } else { [ui error:[GrouchString getString:@"no-such-group" withBundle:[NSBundle bundleForClass:[self class]]] fatal:NO]; } } } - (void)removeFromList:item { NSMutableArray *records = [NSMutableArray array]; NSArray *groups = [buddyList findRecordsOfType:OscarSsiGroup]; int i, itemID = -1; OscarSsiRecord *parent = nil; if( [item conformsToProtocol:@protocol(GrouchUser)] ) for( i=0; i<[groups count]; ++i ) { OscarSsiRecord *group = [groups objectAtIndex:i]; NSArray *children = [group children]; int j; for( j=0; j<[children count]; ++j ) { OscarSsiRecord *user=[children objectAtIndex:j]; if( [user description] == item ) { [records addObject:user]; [user remove]; itemID = [user item]; parent=[buddyList groupById:[user gid]]; } } } else if( [item isKindOfClass:[NSArray class]] ) for( i=0; i<[groups count]; ++i ) { OscarSsiRecord *group = [groups objectAtIndex:i]; if( [group description] == [item objectAtIndex:0] ) { [records addObject:group]; [group remove]; itemID = [group gid]; parent = [buddyList groupById:0]; } } // Remove the record from TLV 0xc8 if( parent ) { OscarBuffer *newList = [OscarBuffer new]; OscarBuffer *dest = [[parent tlvRaw] bufferByRemovingTlv:0xc8]; OscarIncomingSnac *src = [[parent tlv] getTLV:0xc8]; while( [src bytesRemaining] ) { int current = [src readInt16]; if( current != itemID ) [newList addInt16:current]; } [dest addTLV:0xc8 with:newList]; [parent setTlv:dest]; [newList release]; [bos updateSsiRecords:[NSArray arrayWithObject:parent]]; } // Remove the record itself if( [records count] ) { [bos removeSsiRecords:records]; [ui reloadData:YES]; } } @end